dedupe.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. var util = require('util')
  2. var path = require('path')
  3. var validate = require('aproba')
  4. var without = require('lodash.without')
  5. var asyncMap = require('slide').asyncMap
  6. var chain = require('slide').chain
  7. var npa = require('npm-package-arg')
  8. var log = require('npmlog')
  9. var npm = require('./npm.js')
  10. var Installer = require('./install.js').Installer
  11. var findRequirement = require('./install/deps.js').findRequirement
  12. var earliestInstallable = require('./install/deps.js').earliestInstallable
  13. var checkPermissions = require('./install/check-permissions.js')
  14. var decomposeActions = require('./install/decompose-actions.js')
  15. var loadExtraneous = require('./install/deps.js').loadExtraneous
  16. var computeMetadata = require('./install/deps.js').computeMetadata
  17. var sortActions = require('./install/diff-trees.js').sortActions
  18. var moduleName = require('./utils/module-name.js')
  19. var packageId = require('./utils/package-id.js')
  20. var childPath = require('./utils/child-path.js')
  21. var usage = require('./utils/usage')
  22. var getRequested = require('./install/get-requested.js')
  23. module.exports = dedupe
  24. module.exports.Deduper = Deduper
  25. dedupe.usage = usage(
  26. 'dedupe',
  27. 'npm dedupe'
  28. )
  29. function dedupe (args, cb) {
  30. validate('AF', arguments)
  31. // the /path/to/node_modules/..
  32. var where = path.resolve(npm.dir, '..')
  33. var dryrun = false
  34. if (npm.command.match(/^find/)) dryrun = true
  35. if (npm.config.get('dry-run')) dryrun = true
  36. if (dryrun && !npm.config.get('json')) npm.config.set('parseable', true)
  37. new Deduper(where, dryrun).run(cb)
  38. }
  39. function Deduper (where, dryrun) {
  40. validate('SB', arguments)
  41. Installer.call(this, where, dryrun, [])
  42. this.noPackageJsonOk = true
  43. this.topLevelLifecycles = false
  44. }
  45. util.inherits(Deduper, Installer)
  46. Deduper.prototype.loadIdealTree = function (cb) {
  47. validate('F', arguments)
  48. log.silly('install', 'loadIdealTree')
  49. var self = this
  50. chain([
  51. [this.newTracker(this.progress.loadIdealTree, 'cloneCurrentTree')],
  52. [this, this.cloneCurrentTreeToIdealTree],
  53. [this, this.finishTracker, 'cloneCurrentTree'],
  54. [this.newTracker(this.progress.loadIdealTree, 'loadAllDepsIntoIdealTree', 10)],
  55. [ function (next) {
  56. loadExtraneous(self.idealTree, self.progress.loadAllDepsIntoIdealTree, next)
  57. } ],
  58. [this, this.finishTracker, 'loadAllDepsIntoIdealTree'],
  59. [this, andComputeMetadata(this.idealTree)]
  60. ], cb)
  61. }
  62. function andComputeMetadata (tree) {
  63. return function (next) {
  64. next(null, computeMetadata(tree))
  65. }
  66. }
  67. Deduper.prototype.generateActionsToTake = function (cb) {
  68. validate('F', arguments)
  69. log.silly('dedupe', 'generateActionsToTake')
  70. chain([
  71. [this.newTracker(log, 'hoist', 1)],
  72. [hoistChildren, this.idealTree, this.differences],
  73. [this, this.finishTracker, 'hoist'],
  74. [this.newTracker(log, 'sort-actions', 1)],
  75. [this, function (next) {
  76. this.differences = sortActions(this.differences)
  77. next()
  78. }],
  79. [this, this.finishTracker, 'sort-actions'],
  80. [checkPermissions, this.differences],
  81. [decomposeActions, this.differences, this.todo]
  82. ], cb)
  83. }
  84. function move (node, hoistTo, diff) {
  85. node.parent.children = without(node.parent.children, node)
  86. hoistTo.children.push(node)
  87. node.fromPath = node.path
  88. node.path = childPath(hoistTo.path, node)
  89. node.parent = hoistTo
  90. if (!diff.filter(function (action) { return action[0] === 'move' && action[1] === node }).length) {
  91. diff.push(['move', node])
  92. }
  93. }
  94. function moveRemainingChildren (node, diff) {
  95. node.children.forEach(function (child) {
  96. move(child, node, diff)
  97. moveRemainingChildren(child, diff)
  98. })
  99. }
  100. function remove (child, diff, done) {
  101. remove_(child, diff, new Set(), done)
  102. }
  103. function remove_ (child, diff, seen, done) {
  104. if (seen.has(child)) return done()
  105. seen.add(child)
  106. diff.push(['remove', child])
  107. child.parent.children = without(child.parent.children, child)
  108. asyncMap(child.children, function (child, next) {
  109. remove_(child, diff, seen, next)
  110. }, done)
  111. }
  112. function hoistChildren (tree, diff, next) {
  113. hoistChildren_(tree, diff, new Set(), next)
  114. }
  115. function hoistChildren_ (tree, diff, seen, next) {
  116. validate('OAOF', arguments)
  117. if (seen.has(tree)) return next()
  118. seen.add(tree)
  119. asyncMap(tree.children, function (child, done) {
  120. if (!tree.parent || child.fromBundle || child.package._inBundle) return hoistChildren_(child, diff, seen, done)
  121. var better = findRequirement(tree.parent, moduleName(child), getRequested(child) || npa(packageId(child)))
  122. if (better) {
  123. return chain([
  124. [remove, child, diff],
  125. [andComputeMetadata(tree)]
  126. ], done)
  127. }
  128. var hoistTo = earliestInstallable(tree, tree.parent, child.package, log)
  129. if (hoistTo) {
  130. move(child, hoistTo, diff)
  131. chain([
  132. [andComputeMetadata(hoistTo)],
  133. [hoistChildren_, child, diff, seen],
  134. [ function (next) {
  135. moveRemainingChildren(child, diff)
  136. next()
  137. } ]
  138. ], done)
  139. } else {
  140. done()
  141. }
  142. }, next)
  143. }