UNPKG

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