mutate-into-logical-tree.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. 'use strict'
  2. var union = require('lodash.union')
  3. var without = require('lodash.without')
  4. var validate = require('aproba')
  5. var flattenTree = require('./flatten-tree.js')
  6. var isExtraneous = require('./is-extraneous.js')
  7. var validateAllPeerDeps = require('./deps.js').validateAllPeerDeps
  8. var packageId = require('../utils/package-id.js')
  9. var moduleName = require('../utils/module-name.js')
  10. var npm = require('../npm.js')
  11. // Return true if tree is a part of a cycle that:
  12. // A) Never connects to the top of the tree
  13. // B) Has not not had a point in the cycle arbitrarily declared its top
  14. // yet.
  15. function isDisconnectedCycle (tree, seen) {
  16. if (!seen) seen = {}
  17. if (tree.isTop || tree.cycleTop || tree.requiredBy.length === 0) {
  18. return false
  19. } else if (seen[tree.path]) {
  20. return true
  21. } else {
  22. seen[tree.path] = true
  23. return tree.requiredBy.every(function (node) {
  24. return isDisconnectedCycle(node, Object.create(seen))
  25. })
  26. }
  27. }
  28. var mutateIntoLogicalTree = module.exports = function (tree) {
  29. validate('O', arguments)
  30. validateAllPeerDeps(tree, function (tree, pkgname, version) {
  31. if (!tree.missingPeers) tree.missingPeers = {}
  32. tree.missingPeers[pkgname] = version
  33. })
  34. var flat = flattenTree(tree)
  35. Object.keys(flat).sort().forEach(function (flatname) {
  36. var node = flat[flatname]
  37. if (!(node.requiredBy && node.requiredBy.length)) return
  38. if (node.parent) {
  39. // If a node is a cycle that never reaches the root of the logical
  40. // tree then we'll leave it attached to the root, or else it
  41. // would go missing. Further we'll note that this is the node in the
  42. // cycle that we picked arbitrarily to be the one attached to the root.
  43. // others will fall
  44. if (isDisconnectedCycle(node)) {
  45. node.cycleTop = true
  46. // Nor do we want to disconnect non-cyclical extraneous modules from the tree.
  47. } else if (node.requiredBy.length) {
  48. // regular deps though, we do, as we're moving them into the capable
  49. // hands of the modules that require them.
  50. node.parent.children = without(node.parent.children, node)
  51. }
  52. }
  53. node.requiredBy.forEach(function (parentNode) {
  54. parentNode.children = union(parentNode.children, [node])
  55. })
  56. })
  57. return tree
  58. }
  59. module.exports.asReadInstalled = function (tree) {
  60. mutateIntoLogicalTree(tree)
  61. return translateTree(tree)
  62. }
  63. function translateTree (tree) {
  64. return translateTree_(tree, new Set())
  65. }
  66. function translateTree_ (tree, seen) {
  67. var pkg = tree.package
  68. if (seen.has(tree)) return pkg
  69. seen.add(tree)
  70. if (pkg._dependencies) return pkg
  71. pkg._dependencies = pkg.dependencies
  72. pkg.dependencies = {}
  73. tree.children.forEach(function (child) {
  74. const dep = pkg.dependencies[moduleName(child)] = translateTree_(child, seen)
  75. if (child.fakeChild) {
  76. dep.missing = true
  77. dep.optional = child.package._optional
  78. dep.requiredBy = child.package._spec
  79. }
  80. })
  81. function markMissing (name, requiredBy) {
  82. if (pkg.dependencies[name]) {
  83. if (pkg.dependencies[name].missing) return
  84. pkg.dependencies[name].invalid = true
  85. pkg.dependencies[name].realName = name
  86. pkg.dependencies[name].extraneous = false
  87. } else {
  88. pkg.dependencies[name] = {
  89. requiredBy: requiredBy,
  90. missing: true,
  91. optional: !!pkg.optionalDependencies[name]
  92. }
  93. }
  94. }
  95. Object.keys(tree.missingDeps).forEach(function (name) {
  96. markMissing(name, tree.missingDeps[name])
  97. })
  98. Object.keys(tree.missingDevDeps).forEach(function (name) {
  99. markMissing(name, tree.missingDevDeps[name])
  100. })
  101. var checkForMissingPeers = (tree.parent ? [] : [tree]).concat(tree.children)
  102. checkForMissingPeers.filter(function (child) {
  103. return child.missingPeers
  104. }).forEach(function (child) {
  105. Object.keys(child.missingPeers).forEach(function (pkgname) {
  106. var version = child.missingPeers[pkgname]
  107. var peerPkg = pkg.dependencies[pkgname]
  108. if (!peerPkg) {
  109. peerPkg = pkg.dependencies[pkgname] = {
  110. _id: pkgname + '@' + version,
  111. name: pkgname,
  112. version: version
  113. }
  114. }
  115. if (!peerPkg.peerMissing) peerPkg.peerMissing = []
  116. peerPkg.peerMissing.push({
  117. requiredBy: packageId(child),
  118. requires: pkgname + '@' + version
  119. })
  120. })
  121. })
  122. pkg.path = tree.path
  123. pkg.error = tree.error
  124. pkg.extraneous = !tree.isTop && (!tree.parent.isTop || !tree.parent.error) && !npm.config.get('global') && isExtraneous(tree)
  125. if (tree.target && tree.parent && !tree.parent.target) pkg.link = tree.realpath
  126. return pkg
  127. }