1 | 'use strict'
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | module.exports = install
|
18 | module.exports.Installer = Installer
|
19 |
|
20 | var usage = require('./utils/usage')
|
21 |
|
22 | install.usage = usage(
|
23 | 'install',
|
24 | '\nnpm install (with no args, in package dir)' +
|
25 | '\nnpm install [<@scope>/]<pkg>' +
|
26 | '\nnpm install [<@scope>/]<pkg>@<tag>' +
|
27 | '\nnpm install [<@scope>/]<pkg>@<version>' +
|
28 | '\nnpm install [<@scope>/]<pkg>@<version range>' +
|
29 | '\nnpm install <folder>' +
|
30 | '\nnpm install <tarball file>' +
|
31 | '\nnpm install <tarball url>' +
|
32 | '\nnpm install <git:// url>' +
|
33 | '\nnpm install <github username>/<github project>',
|
34 | '[--save-prod|--save-dev|--save-optional] [--save-exact] [--no-save]'
|
35 | )
|
36 |
|
37 | install.completion = function (opts, cb) {
|
38 | validate('OF', arguments)
|
39 |
|
40 |
|
41 |
|
42 | if (/^https?:\/\//.test(opts.partialWord)) {
|
43 |
|
44 | return cb(null, [])
|
45 | }
|
46 |
|
47 | if (/\//.test(opts.partialWord)) {
|
48 |
|
49 |
|
50 |
|
51 |
|
52 | var lastSlashIdx = opts.partialWord.lastIndexOf('/')
|
53 | var partialName = opts.partialWord.slice(lastSlashIdx + 1)
|
54 | var partialPath = opts.partialWord.slice(0, lastSlashIdx)
|
55 | if (partialPath === '') partialPath = '/'
|
56 |
|
57 | var annotatePackageDirMatch = function (sibling, cb) {
|
58 | var fullPath = path.join(partialPath, sibling)
|
59 | if (sibling.slice(0, partialName.length) !== partialName) {
|
60 | return cb(null, null)
|
61 | }
|
62 | fs.readdir(fullPath, function (err, contents) {
|
63 | if (err) return cb(null, { isPackage: false })
|
64 |
|
65 | cb(
|
66 | null,
|
67 | {
|
68 | fullPath: fullPath,
|
69 | isPackage: contents.indexOf('package.json') !== -1
|
70 | }
|
71 | )
|
72 | })
|
73 | }
|
74 |
|
75 | return fs.readdir(partialPath, function (err, siblings) {
|
76 | if (err) return cb(null, [])
|
77 |
|
78 | asyncMap(siblings, annotatePackageDirMatch, function (err, matches) {
|
79 | if (err) return cb(err)
|
80 |
|
81 | var cleaned = matches.filter(function (x) { return x !== null })
|
82 | if (cleaned.length !== 1) return cb(null, [])
|
83 | if (!cleaned[0].isPackage) return cb(null, [])
|
84 |
|
85 |
|
86 | return cb(null, [cleaned[0].fullPath])
|
87 | })
|
88 | })
|
89 | }
|
90 |
|
91 |
|
92 |
|
93 | cb()
|
94 | }
|
95 |
|
96 |
|
97 | var fs = require('fs')
|
98 | var path = require('path')
|
99 |
|
100 |
|
101 | var log = require('npmlog')
|
102 | var readPackageTree = require('read-package-tree')
|
103 | var readPackageJson = require('read-package-json')
|
104 | var chain = require('slide').chain
|
105 | var asyncMap = require('slide').asyncMap
|
106 | var archy = require('archy')
|
107 | var mkdirp = require('mkdirp')
|
108 | var rimraf = require('rimraf')
|
109 | var iferr = require('iferr')
|
110 | var validate = require('aproba')
|
111 | var uniq = require('lodash.uniq')
|
112 | var Bluebird = require('bluebird')
|
113 |
|
114 |
|
115 | var npm = require('./npm.js')
|
116 | var locker = require('./utils/locker.js')
|
117 | var lock = locker.lock
|
118 | var unlock = locker.unlock
|
119 | var parseJSON = require('./utils/parse-json.js')
|
120 | var output = require('./utils/output.js')
|
121 | var saveMetrics = require('./utils/metrics.js').save
|
122 |
|
123 |
|
124 | var copyTree = require('./install/copy-tree.js')
|
125 | var readShrinkwrap = require('./install/read-shrinkwrap.js')
|
126 | var computeMetadata = require('./install/deps.js').computeMetadata
|
127 | var prefetchDeps = require('./install/deps.js').prefetchDeps
|
128 | var loadDeps = require('./install/deps.js').loadDeps
|
129 | var loadDevDeps = require('./install/deps.js').loadDevDeps
|
130 | var getAllMetadata = require('./install/deps.js').getAllMetadata
|
131 | var loadRequestedDeps = require('./install/deps.js').loadRequestedDeps
|
132 | var loadExtraneous = require('./install/deps.js').loadExtraneous
|
133 | var diffTrees = require('./install/diff-trees.js')
|
134 | var checkPermissions = require('./install/check-permissions.js')
|
135 | var decomposeActions = require('./install/decompose-actions.js')
|
136 | var validateTree = require('./install/validate-tree.js')
|
137 | var validateArgs = require('./install/validate-args.js')
|
138 | var saveRequested = require('./install/save.js').saveRequested
|
139 | var saveShrinkwrap = require('./install/save.js').saveShrinkwrap
|
140 | var audit = require('./install/audit.js')
|
141 | var getSaveType = require('./install/save.js').getSaveType
|
142 | var doSerialActions = require('./install/actions.js').doSerial
|
143 | var doReverseSerialActions = require('./install/actions.js').doReverseSerial
|
144 | var doParallelActions = require('./install/actions.js').doParallel
|
145 | var doOneAction = require('./install/actions.js').doOne
|
146 | var removeObsoleteDep = require('./install/deps.js').removeObsoleteDep
|
147 | var removeExtraneous = require('./install/deps.js').removeExtraneous
|
148 | var computeVersionSpec = require('./install/deps.js').computeVersionSpec
|
149 | var packageId = require('./utils/package-id.js')
|
150 | var moduleName = require('./utils/module-name.js')
|
151 | var errorMessage = require('./utils/error-message.js')
|
152 | var isExtraneous = require('./install/is-extraneous.js')
|
153 |
|
154 | function unlockCB (lockPath, name, cb) {
|
155 | validate('SSF', arguments)
|
156 | return function (installEr) {
|
157 | var args = arguments
|
158 | try {
|
159 | unlock(lockPath, name, reportErrorAndReturn)
|
160 | } catch (unlockEx) {
|
161 | process.nextTick(function () {
|
162 | reportErrorAndReturn(unlockEx)
|
163 | })
|
164 | }
|
165 | function reportErrorAndReturn (unlockEr) {
|
166 | if (installEr) {
|
167 | if (unlockEr && unlockEr.code !== 'ENOTLOCKED') {
|
168 | log.warn('unlock' + name, unlockEr)
|
169 | }
|
170 | return cb.apply(null, args)
|
171 | }
|
172 | if (unlockEr) return cb(unlockEr)
|
173 | return cb.apply(null, args)
|
174 | }
|
175 | }
|
176 | }
|
177 |
|
178 | function install (where, args, cb) {
|
179 | if (!cb) {
|
180 | cb = args
|
181 | args = where
|
182 | where = null
|
183 | }
|
184 | var globalTop = path.resolve(npm.globalDir, '..')
|
185 | if (!where) {
|
186 | where = npm.config.get('global')
|
187 | ? globalTop
|
188 | : npm.prefix
|
189 | }
|
190 | validate('SAF', [where, args, cb])
|
191 |
|
192 | var dryrun = !!npm.config.get('dry-run')
|
193 |
|
194 | if (npm.config.get('dev')) {
|
195 | log.warn('install', 'Usage of the `--dev` option is deprecated. Use `--only=dev` instead.')
|
196 | }
|
197 |
|
198 | if (where === globalTop && !args.length) {
|
199 | args = ['.']
|
200 | }
|
201 | args = args.filter(function (a) {
|
202 | return path.resolve(a) !== npm.prefix
|
203 | })
|
204 |
|
205 | new Installer(where, dryrun, args).run(cb)
|
206 | }
|
207 |
|
208 | function Installer (where, dryrun, args, opts) {
|
209 | validate('SBA|SBAO', arguments)
|
210 | if (!opts) opts = {}
|
211 | this.where = where
|
212 | this.dryrun = dryrun
|
213 | this.args = args
|
214 |
|
215 |
|
216 |
|
217 |
|
218 | this.currentTree = null
|
219 | this.idealTree = null
|
220 | this.differences = []
|
221 | this.todo = []
|
222 | this.progress = {}
|
223 | this.noPackageJsonOk = !!args.length
|
224 | this.topLevelLifecycles = !args.length
|
225 |
|
226 | this.autoPrune = npm.config.get('package-lock')
|
227 |
|
228 | const dev = npm.config.get('dev')
|
229 | const only = npm.config.get('only')
|
230 | const onlyProd = /^prod(uction)?$/.test(only)
|
231 | const onlyDev = /^dev(elopment)?$/.test(only)
|
232 | const prod = npm.config.get('production')
|
233 | this.dev = opts.dev != null ? opts.dev : dev || (!onlyProd && !prod) || onlyDev
|
234 | this.prod = opts.prod != null ? opts.prod : !onlyDev
|
235 |
|
236 | this.packageLockOnly = opts.packageLockOnly != null
|
237 | ? opts.packageLockOnly : npm.config.get('package-lock-only')
|
238 | this.rollback = opts.rollback != null ? opts.rollback : npm.config.get('rollback')
|
239 | this.link = opts.link != null ? opts.link : npm.config.get('link')
|
240 | this.saveOnlyLock = opts.saveOnlyLock
|
241 | this.global = opts.global != null ? opts.global : this.where === path.resolve(npm.globalDir, '..')
|
242 | this.audit = npm.config.get('audit') && !this.global
|
243 | this.started = Date.now()
|
244 | }
|
245 | Installer.prototype = {}
|
246 |
|
247 | Installer.prototype.run = function (_cb) {
|
248 | validate('F|', arguments)
|
249 |
|
250 | var result
|
251 | var cb
|
252 | if (_cb) {
|
253 | cb = function (err) {
|
254 | saveMetrics(!err)
|
255 | return _cb.apply(this, arguments)
|
256 | }
|
257 | } else {
|
258 | result = new Promise((resolve, reject) => {
|
259 | cb = (err, value) => err ? reject(err) : resolve(value)
|
260 | })
|
261 | }
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 | if (this.global) {
|
270 | var prevGlobal = npm.config.get('global')
|
271 | npm.config.set('global', true)
|
272 | var next = cb
|
273 | cb = function () {
|
274 | npm.config.set('global', prevGlobal)
|
275 | next.apply(null, arguments)
|
276 | }
|
277 | }
|
278 |
|
279 | var installSteps = []
|
280 | var postInstallSteps = []
|
281 | if (!this.dryrun) {
|
282 | installSteps.push(
|
283 | [this.newTracker(log, 'runTopLevelLifecycles', 2)],
|
284 | [this, this.runPreinstallTopLevelLifecycles])
|
285 | }
|
286 | installSteps.push(
|
287 | [this.newTracker(log, 'loadCurrentTree', 4)],
|
288 | [this, this.loadCurrentTree],
|
289 | [this, this.finishTracker, 'loadCurrentTree'],
|
290 |
|
291 | [this.newTracker(log, 'loadIdealTree', 12)],
|
292 | [this, this.loadIdealTree],
|
293 | [this, this.finishTracker, 'loadIdealTree'],
|
294 |
|
295 | [this, this.debugTree, 'currentTree', 'currentTree'],
|
296 | [this, this.debugTree, 'idealTree', 'idealTree'],
|
297 |
|
298 | [this.newTracker(log, 'generateActionsToTake')],
|
299 | [this, this.generateActionsToTake],
|
300 | [this, this.finishTracker, 'generateActionsToTake'],
|
301 |
|
302 | [this, this.debugActions, 'diffTrees', 'differences'],
|
303 | [this, this.debugActions, 'decomposeActions', 'todo'],
|
304 | [this, this.startAudit]
|
305 | )
|
306 |
|
307 | if (this.packageLockOnly) {
|
308 | postInstallSteps.push(
|
309 | [this, this.saveToDependencies])
|
310 | } else if (!this.dryrun) {
|
311 | installSteps.push(
|
312 | [this.newTracker(log, 'executeActions', 8)],
|
313 | [this, this.executeActions],
|
314 | [this, this.finishTracker, 'executeActions'])
|
315 | var node_modules = path.resolve(this.where, 'node_modules')
|
316 | var staging = path.resolve(node_modules, '.staging')
|
317 | postInstallSteps.push(
|
318 | [this.newTracker(log, 'rollbackFailedOptional', 1)],
|
319 | [this, this.rollbackFailedOptional, staging, this.todo],
|
320 | [this, this.finishTracker, 'rollbackFailedOptional'],
|
321 | [this, this.commit, staging, this.todo],
|
322 |
|
323 | [this, this.runPostinstallTopLevelLifecycles],
|
324 | [this, this.finishTracker, 'runTopLevelLifecycles']
|
325 | )
|
326 | if (getSaveType()) {
|
327 | postInstallSteps.push(
|
328 |
|
329 |
|
330 | [this, (next) => { computeMetadata(this.idealTree); next() }],
|
331 | [this, this.pruneIdealTree],
|
332 | [this, this.debugLogicalTree, 'saveTree', 'idealTree'],
|
333 | [this, this.saveToDependencies])
|
334 | }
|
335 | }
|
336 | postInstallSteps.push(
|
337 | [this, this.printWarnings],
|
338 | [this, this.printInstalled])
|
339 |
|
340 | var self = this
|
341 | chain(installSteps, function (installEr) {
|
342 | if (installEr) self.failing = true
|
343 | chain(postInstallSteps, function (postInstallEr) {
|
344 | if (installEr && postInstallEr) {
|
345 | var msg = errorMessage(postInstallEr)
|
346 | msg.summary.forEach(function (logline) {
|
347 | log.warn.apply(log, logline)
|
348 | })
|
349 | msg.detail.forEach(function (logline) {
|
350 | log.verbose.apply(log, logline)
|
351 | })
|
352 | }
|
353 | cb(installEr || postInstallEr, self.getInstalledModules(), self.idealTree)
|
354 | })
|
355 | })
|
356 | return result
|
357 | }
|
358 |
|
359 | Installer.prototype.loadArgMetadata = function (next) {
|
360 | getAllMetadata(this.args, this.currentTree, process.cwd(), iferr(next, (args) => {
|
361 | this.args = args
|
362 | next()
|
363 | }))
|
364 | }
|
365 |
|
366 | Installer.prototype.newTracker = function (tracker, name, size) {
|
367 | validate('OS', [tracker, name])
|
368 | if (size) validate('N', [size])
|
369 | this.progress[name] = tracker.newGroup(name, size)
|
370 | return function (next) {
|
371 | process.emit('time', 'stage:' + name)
|
372 | next()
|
373 | }
|
374 | }
|
375 |
|
376 | Installer.prototype.finishTracker = function (name, cb) {
|
377 | validate('SF', arguments)
|
378 | process.emit('timeEnd', 'stage:' + name)
|
379 | cb()
|
380 | }
|
381 |
|
382 | Installer.prototype.loadCurrentTree = function (cb) {
|
383 | validate('F', arguments)
|
384 | log.silly('install', 'loadCurrentTree')
|
385 | var todo = []
|
386 | if (this.global) {
|
387 | todo.push([this, this.readGlobalPackageData])
|
388 | } else {
|
389 | todo.push([this, this.readLocalPackageData])
|
390 | }
|
391 | todo.push([this, this.normalizeCurrentTree])
|
392 | chain(todo, cb)
|
393 | }
|
394 |
|
395 | var createNode = require('./install/node.js').create
|
396 | var flatNameFromTree = require('./install/flatten-tree.js').flatNameFromTree
|
397 | Installer.prototype.normalizeCurrentTree = function (cb) {
|
398 | this.currentTree.isTop = true
|
399 | normalizeTree(this.currentTree)
|
400 |
|
401 | if (this.currentTree.error) {
|
402 | for (let child of this.currentTree.children) {
|
403 | if (!child.fakeChild && isExtraneous(child)) {
|
404 | this.currentTree.package.dependencies[child.package.name] = computeVersionSpec(this.currentTree, child)
|
405 | }
|
406 | }
|
407 | }
|
408 | computeMetadata(this.currentTree)
|
409 | return cb()
|
410 |
|
411 | function normalizeTree (tree, seen) {
|
412 | if (!seen) seen = new Set()
|
413 | if (seen.has(tree)) return
|
414 | seen.add(tree)
|
415 | createNode(tree)
|
416 | tree.location = flatNameFromTree(tree)
|
417 | tree.children.forEach((child) => normalizeTree(child, seen))
|
418 | }
|
419 | }
|
420 |
|
421 | Installer.prototype.loadIdealTree = function (cb) {
|
422 | validate('F', arguments)
|
423 | log.silly('install', 'loadIdealTree')
|
424 |
|
425 | chain([
|
426 | [this.newTracker(this.progress.loadIdealTree, 'loadIdealTree:cloneCurrentTree')],
|
427 | [this, this.cloneCurrentTreeToIdealTree],
|
428 | [this, this.finishTracker, 'loadIdealTree:cloneCurrentTree'],
|
429 |
|
430 | [this.newTracker(this.progress.loadIdealTree, 'loadIdealTree:loadShrinkwrap')],
|
431 | [this, this.loadShrinkwrap],
|
432 | [this, this.finishTracker, 'loadIdealTree:loadShrinkwrap'],
|
433 |
|
434 | [this.newTracker(this.progress.loadIdealTree, 'loadIdealTree:loadAllDepsIntoIdealTree', 10)],
|
435 | [this, this.loadAllDepsIntoIdealTree],
|
436 | [this, this.finishTracker, 'loadIdealTree:loadAllDepsIntoIdealTree'],
|
437 | [this, function (next) { computeMetadata(this.idealTree); next() }],
|
438 | [this, this.pruneIdealTree]
|
439 | ], cb)
|
440 | }
|
441 |
|
442 | Installer.prototype.pruneIdealTree = function (cb) {
|
443 | if (!this.idealTree) return cb()
|
444 |
|
445 |
|
446 | if (!this.idealTree.hasRequiresFromLock && this.idealTree.children.some((n) => n.fakeChild)) return cb()
|
447 | const toPrune = this.idealTree.children
|
448 | .filter((child) => isExtraneous(child) && (this.autoPrune || child.removing))
|
449 | .map((n) => ({name: moduleName(n)}))
|
450 | return removeExtraneous(toPrune, this.idealTree, cb)
|
451 | }
|
452 |
|
453 | Installer.prototype.loadAllDepsIntoIdealTree = function (cb) {
|
454 | validate('F', arguments)
|
455 | log.silly('install', 'loadAllDepsIntoIdealTree')
|
456 | var saveDeps = getSaveType()
|
457 |
|
458 | var cg = this.progress['loadIdealTree:loadAllDepsIntoIdealTree']
|
459 | var installNewModules = !!this.args.length
|
460 | var steps = []
|
461 |
|
462 | if (installNewModules) {
|
463 | steps.push([validateArgs, this.idealTree, this.args])
|
464 | steps.push([loadRequestedDeps, this.args, this.idealTree, saveDeps, cg.newGroup('loadRequestedDeps')])
|
465 | } else {
|
466 | const depsToPreload = Object.assign({},
|
467 | this.idealTree.package.devDependencies,
|
468 | this.idealTree.package.dependencies
|
469 | )
|
470 | steps.push(
|
471 | [prefetchDeps, this.idealTree, depsToPreload, cg.newGroup('prefetchDeps')],
|
472 | [loadDeps, this.idealTree, cg.newGroup('loadDeps')],
|
473 | [loadDevDeps, this.idealTree, cg.newGroup('loadDevDeps')])
|
474 | }
|
475 | steps.push(
|
476 | [loadExtraneous.andResolveDeps, this.idealTree, cg.newGroup('loadExtraneous')])
|
477 | chain(steps, cb)
|
478 | }
|
479 |
|
480 | Installer.prototype.generateActionsToTake = function (cb) {
|
481 | validate('F', arguments)
|
482 | log.silly('install', 'generateActionsToTake')
|
483 | var cg = this.progress.generateActionsToTake
|
484 | chain([
|
485 | [validateTree, this.idealTree, cg.newGroup('validateTree')],
|
486 | [diffTrees, this.currentTree, this.idealTree, this.differences, cg.newGroup('diffTrees')],
|
487 | [this, this.computeLinked],
|
488 | [checkPermissions, this.differences],
|
489 | [decomposeActions, this.differences, this.todo]
|
490 | ], cb)
|
491 | }
|
492 |
|
493 | Installer.prototype.computeLinked = function (cb) {
|
494 | validate('F', arguments)
|
495 | if (!this.link || this.global) return cb()
|
496 | var linkTodoList = []
|
497 | var self = this
|
498 | asyncMap(this.differences, function (action, next) {
|
499 | var cmd = action[0]
|
500 | var pkg = action[1]
|
501 | if (cmd !== 'add' && cmd !== 'update') return next()
|
502 | var isReqByTop = pkg.requiredBy.filter(function (mod) { return mod.isTop }).length
|
503 | var isReqByUser = pkg.userRequired
|
504 | var isExtraneous = pkg.requiredBy.length === 0
|
505 | if (!isReqByTop && !isReqByUser && !isExtraneous) return next()
|
506 | isLinkable(pkg, function (install, link) {
|
507 | if (install) linkTodoList.push(['global-install', pkg])
|
508 | if (link) linkTodoList.push(['global-link', pkg])
|
509 | if (install || link) removeObsoleteDep(pkg)
|
510 | next()
|
511 | })
|
512 | }, function () {
|
513 | if (linkTodoList.length === 0) return cb()
|
514 | self.differences.length = 0
|
515 | Array.prototype.push.apply(self.differences, linkTodoList)
|
516 | diffTrees(self.currentTree, self.idealTree, self.differences, log.newGroup('d2'), cb)
|
517 | })
|
518 | }
|
519 |
|
520 | function isLinkable (pkg, cb) {
|
521 | var globalPackage = path.resolve(npm.globalPrefix, 'lib', 'node_modules', moduleName(pkg))
|
522 | var globalPackageJson = path.resolve(globalPackage, 'package.json')
|
523 | fs.stat(globalPackage, function (er) {
|
524 | if (er) return cb(true, true)
|
525 | fs.readFile(globalPackageJson, function (er, data) {
|
526 | var json = parseJSON.noExceptions(data)
|
527 | cb(false, json && json.version === pkg.package.version)
|
528 | })
|
529 | })
|
530 | }
|
531 |
|
532 | Installer.prototype.executeActions = function (cb) {
|
533 | validate('F', arguments)
|
534 | log.silly('install', 'executeActions')
|
535 | var todo = this.todo
|
536 | var cg = this.progress.executeActions
|
537 |
|
538 | var node_modules = path.resolve(this.where, 'node_modules')
|
539 | var staging = path.resolve(node_modules, '.staging')
|
540 | var steps = []
|
541 | var trackLifecycle = cg.newGroup('lifecycle')
|
542 |
|
543 | cb = unlockCB(node_modules, '.staging', cb)
|
544 |
|
545 | steps.push(
|
546 | [doSerialActions, 'global-install', staging, todo, trackLifecycle.newGroup('global-install')],
|
547 | [lock, node_modules, '.staging'],
|
548 | [rimraf, staging],
|
549 | [doParallelActions, 'extract', staging, todo, cg.newGroup('extract', 100)],
|
550 | [doReverseSerialActions, 'unbuild', staging, todo, cg.newGroup('unbuild')],
|
551 | [doSerialActions, 'remove', staging, todo, cg.newGroup('remove')],
|
552 | [doSerialActions, 'move', staging, todo, cg.newGroup('move')],
|
553 | [doSerialActions, 'finalize', staging, todo, cg.newGroup('finalize')],
|
554 | [doParallelActions, 'refresh-package-json', staging, todo, cg.newGroup('refresh-package-json')],
|
555 | [doParallelActions, 'preinstall', staging, todo, trackLifecycle.newGroup('preinstall')],
|
556 | [doSerialActions, 'build', staging, todo, trackLifecycle.newGroup('build')],
|
557 | [doSerialActions, 'global-link', staging, todo, trackLifecycle.newGroup('global-link')],
|
558 | [doParallelActions, 'update-linked', staging, todo, trackLifecycle.newGroup('update-linked')],
|
559 | [doSerialActions, 'install', staging, todo, trackLifecycle.newGroup('install')],
|
560 | [doSerialActions, 'postinstall', staging, todo, trackLifecycle.newGroup('postinstall')])
|
561 |
|
562 | var self = this
|
563 | chain(steps, function (er) {
|
564 | if (!er || self.rollback) {
|
565 | rimraf(staging, function () { cb(er) })
|
566 | } else {
|
567 | cb(er)
|
568 | }
|
569 | })
|
570 | }
|
571 |
|
572 | Installer.prototype.rollbackFailedOptional = function (staging, actionsToRun, cb) {
|
573 | if (!this.rollback) return cb()
|
574 | var failed = uniq(actionsToRun.map(function (action) {
|
575 | return action[1]
|
576 | }).filter(function (pkg) {
|
577 | return pkg.failed && pkg.rollback
|
578 | }))
|
579 | var top = this.currentTree && this.currentTree.path
|
580 | Bluebird.map(failed, (pkg) => {
|
581 | return Bluebird.map(pkg.rollback, (rollback) => rollback(top, staging, pkg))
|
582 | }).asCallback(cb)
|
583 | }
|
584 |
|
585 | Installer.prototype.commit = function (staging, actionsToRun, cb) {
|
586 | var toCommit = actionsToRun.map(function (action) { return action[1] }).filter(function (pkg) { return !pkg.failed && pkg.commit })
|
587 | asyncMap(toCommit, function (pkg, next) {
|
588 | asyncMap(pkg.commit, function (commit, done) {
|
589 | commit(staging, pkg, done)
|
590 | }, function () {
|
591 | pkg.commit = []
|
592 | next.apply(null, arguments)
|
593 | })
|
594 | }, cb)
|
595 | }
|
596 |
|
597 | Installer.prototype.runPreinstallTopLevelLifecycles = function (cb) {
|
598 | validate('F', arguments)
|
599 | if (this.failing) return cb()
|
600 | if (!this.topLevelLifecycles) return cb()
|
601 | log.silly('install', 'runPreinstallTopLevelLifecycles')
|
602 |
|
603 | readPackageJson(path.join(this.where, 'package.json'), log, false, (err, data) => {
|
604 | if (err) return cb()
|
605 | this.currentTree = createNode({
|
606 | isTop: true,
|
607 | package: data,
|
608 | path: this.where
|
609 | })
|
610 | doOneAction('preinstall', this.where, this.currentTree, log.newGroup('preinstall:.'), cb)
|
611 | })
|
612 | }
|
613 |
|
614 | Installer.prototype.runPostinstallTopLevelLifecycles = function (cb) {
|
615 | validate('F', arguments)
|
616 | if (this.failing) return cb()
|
617 | if (!this.topLevelLifecycles) return cb()
|
618 | log.silly('install', 'runPostinstallTopLevelLifecycles')
|
619 | var steps = []
|
620 | var trackLifecycle = this.progress.runTopLevelLifecycles
|
621 |
|
622 | steps.push(
|
623 | [doOneAction, 'build', this.idealTree.path, this.idealTree, trackLifecycle.newGroup('build:.')],
|
624 | [doOneAction, 'install', this.idealTree.path, this.idealTree, trackLifecycle.newGroup('install:.')],
|
625 | [doOneAction, 'postinstall', this.idealTree.path, this.idealTree, trackLifecycle.newGroup('postinstall:.')])
|
626 | if (this.dev) {
|
627 | steps.push(
|
628 | [doOneAction, 'prepare', this.idealTree.path, this.idealTree, trackLifecycle.newGroup('prepare')])
|
629 | }
|
630 | chain(steps, cb)
|
631 | }
|
632 |
|
633 | Installer.prototype.startAudit = function (cb) {
|
634 | if (!this.audit) return cb()
|
635 | this.auditSubmission = Bluebird.try(() => {
|
636 | return audit.generateFromInstall(this.idealTree, this.differences, this.args, this.remove)
|
637 | }).then((auditData) => {
|
638 | return audit.submitForInstallReport(auditData)
|
639 | }).catch(_ => {})
|
640 | cb()
|
641 | }
|
642 |
|
643 | Installer.prototype.saveToDependencies = function (cb) {
|
644 | validate('F', arguments)
|
645 | if (this.failing) return cb()
|
646 | log.silly('install', 'saveToDependencies')
|
647 | if (this.saveOnlyLock) {
|
648 | return saveShrinkwrap(this.idealTree).nodeify(cb)
|
649 | } else {
|
650 | return saveRequested(this.idealTree).nodeify(cb)
|
651 | }
|
652 | }
|
653 |
|
654 | Installer.prototype.readGlobalPackageData = function (cb) {
|
655 | validate('F', arguments)
|
656 | log.silly('install', 'readGlobalPackageData')
|
657 | var self = this
|
658 | this.loadArgMetadata(iferr(cb, function () {
|
659 | mkdirp(self.where, iferr(cb, function () {
|
660 | var pkgs = {}
|
661 | self.args.forEach(function (pkg) {
|
662 | pkgs[pkg.name] = true
|
663 | })
|
664 | readPackageTree(self.where, function (ctx, kid) { return ctx.parent || pkgs[kid] }, iferr(cb, function (currentTree) {
|
665 | self.currentTree = currentTree
|
666 | return cb()
|
667 | }))
|
668 | }))
|
669 | }))
|
670 | }
|
671 |
|
672 | Installer.prototype.readLocalPackageData = function (cb) {
|
673 | validate('F', arguments)
|
674 | log.silly('install', 'readLocalPackageData')
|
675 | var self = this
|
676 | mkdirp(this.where, iferr(cb, function () {
|
677 | readPackageTree(self.where, iferr(cb, function (currentTree) {
|
678 | self.currentTree = currentTree
|
679 | self.currentTree.warnings = []
|
680 | if (currentTree.error && currentTree.error.code === 'EJSONPARSE') {
|
681 | return cb(currentTree.error)
|
682 | }
|
683 | if (!self.noPackageJsonOk && !currentTree.package) {
|
684 | log.error('install', "Couldn't read dependencies")
|
685 | var er = new Error("ENOENT, open '" + path.join(self.where, 'package.json') + "'")
|
686 | er.code = 'ENOPACKAGEJSON'
|
687 | er.errno = 34
|
688 | return cb(er)
|
689 | }
|
690 | if (!currentTree.package) currentTree.package = {}
|
691 | readShrinkwrap(currentTree, function (err) {
|
692 | if (err) {
|
693 | cb(err)
|
694 | } else {
|
695 | self.loadArgMetadata(cb)
|
696 | }
|
697 | })
|
698 | }))
|
699 | }))
|
700 | }
|
701 |
|
702 | Installer.prototype.cloneCurrentTreeToIdealTree = function (cb) {
|
703 | validate('F', arguments)
|
704 | log.silly('install', 'cloneCurrentTreeToIdealTree')
|
705 |
|
706 | this.idealTree = copyTree(this.currentTree)
|
707 | this.idealTree.warnings = []
|
708 | cb()
|
709 | }
|
710 |
|
711 | Installer.prototype.loadShrinkwrap = function (cb) {
|
712 | validate('F', arguments)
|
713 | log.silly('install', 'loadShrinkwrap')
|
714 | readShrinkwrap.andInflate(this.idealTree, iferr(cb, () => {
|
715 | computeMetadata(this.idealTree)
|
716 | cb()
|
717 | }))
|
718 | }
|
719 |
|
720 | Installer.prototype.getInstalledModules = function () {
|
721 | return this.differences.filter(function (action) {
|
722 | var mutation = action[0]
|
723 | return (mutation === 'add' || mutation === 'update')
|
724 | }).map(function (action) {
|
725 | var child = action[1]
|
726 | return [child.package._id, child.path]
|
727 | })
|
728 | }
|
729 |
|
730 | Installer.prototype.printWarnings = function (cb) {
|
731 | if (!this.idealTree) return cb()
|
732 |
|
733 | var self = this
|
734 | var warned = false
|
735 | this.idealTree.warnings.forEach(function (warning) {
|
736 | if (warning.code === 'EPACKAGEJSON' && self.global) return
|
737 | if (warning.code === 'ENOTDIR') return
|
738 | warned = true
|
739 | var msg = errorMessage(warning)
|
740 | msg.summary.forEach(function (logline) {
|
741 | log.warn.apply(log, logline)
|
742 | })
|
743 | msg.detail.forEach(function (logline) {
|
744 | log.verbose.apply(log, logline)
|
745 | })
|
746 | })
|
747 | if (warned && log.levels[npm.config.get('loglevel')] <= log.levels.warn) console.error()
|
748 | cb()
|
749 | }
|
750 |
|
751 | Installer.prototype.printInstalled = function (cb) {
|
752 | validate('F', arguments)
|
753 | if (this.failing) return cb()
|
754 | log.silly('install', 'printInstalled')
|
755 | const diffs = this.differences
|
756 | if (!this.idealTree.error && this.idealTree.removedChildren) {
|
757 | const deps = this.currentTree.package.dependencies || {}
|
758 | const dev = this.currentTree.package.devDependencies || {}
|
759 | this.idealTree.removedChildren.forEach((r) => {
|
760 | if (diffs.some((d) => d[0] === 'remove' && d[1].path === r.path)) return
|
761 | if (!deps[moduleName(r)] && !dev[moduleName(r)]) return
|
762 | diffs.push(['remove', r])
|
763 | })
|
764 | }
|
765 | return Bluebird.try(() => {
|
766 | if (!this.auditSubmission) return
|
767 | return Bluebird.resolve(this.auditSubmission).timeout(10000).catch(() => null)
|
768 | }).then((auditResult) => {
|
769 | if (auditResult && !auditResult.metadata) {
|
770 | log.warn('audit', 'Audit result from registry missing metadata. This is probably an issue with the registry.')
|
771 | }
|
772 |
|
773 | if (npm.config.get('json')) {
|
774 | return this.printInstalledForJSON(diffs, auditResult)
|
775 | } else if (npm.config.get('parseable')) {
|
776 | return this.printInstalledForParseable(diffs, auditResult)
|
777 | } else {
|
778 | return this.printInstalledForHuman(diffs, auditResult)
|
779 | }
|
780 | }).asCallback(cb)
|
781 | }
|
782 |
|
783 | Installer.prototype.printInstalledForHuman = function (diffs, auditResult) {
|
784 | var removed = 0
|
785 | var added = 0
|
786 | var updated = 0
|
787 | var moved = 0
|
788 |
|
789 |
|
790 | var contributors = new Set()
|
791 | diffs.forEach(function (action) {
|
792 | var mutation = action[0]
|
793 | var pkg = action[1]
|
794 | if (pkg.failed) return
|
795 | if (mutation === 'remove') {
|
796 | ++removed
|
797 | } else if (mutation === 'move') {
|
798 | ++moved
|
799 | } else if (mutation === 'add') {
|
800 | ++added
|
801 |
|
802 |
|
803 |
|
804 | var people = []
|
805 | var meta = pkg.package
|
806 | if (meta.author) people.push(meta.author)
|
807 | if (meta.contributors && Array.isArray(meta.contributors)) {
|
808 | people = people.concat(meta.contributors)
|
809 | }
|
810 |
|
811 |
|
812 | people.forEach(function (person) {
|
813 |
|
814 | try {
|
815 | var normalized = normalizePerson(person)
|
816 | } catch (error) {
|
817 | return
|
818 | }
|
819 | if (!contributors.has(normalized)) contributors.add(normalized)
|
820 | })
|
821 | } else if (mutation === 'update' || mutation === 'update-linked') {
|
822 | ++updated
|
823 | }
|
824 | })
|
825 | var report = ''
|
826 | if (this.args.length && (added || updated)) {
|
827 | report += this.args.map((p) => {
|
828 | return `+ ${p.name}@${p.version}`
|
829 | }).join('\n') + '\n'
|
830 | }
|
831 | var actions = []
|
832 | if (added) {
|
833 | var action = 'added ' + packages(added)
|
834 | if (contributors.size) action += from(contributors.size)
|
835 | actions.push(action)
|
836 | }
|
837 | if (removed) actions.push('removed ' + packages(removed))
|
838 | if (updated) actions.push('updated ' + packages(updated))
|
839 | if (moved) actions.push('moved ' + packages(moved))
|
840 | if (auditResult && auditResult.metadata && auditResult.metadata.totalDependencies) {
|
841 | actions.push('audited ' + packages(auditResult.metadata.totalDependencies))
|
842 | }
|
843 | if (actions.length === 0) {
|
844 | report += 'up to date'
|
845 | } else if (actions.length === 1) {
|
846 | report += actions[0]
|
847 | } else {
|
848 | var lastAction = actions.pop()
|
849 | report += actions.join(', ') + ' and ' + lastAction
|
850 | }
|
851 | report += ' in ' + ((Date.now() - this.started) / 1000) + 's'
|
852 |
|
853 | output(report)
|
854 | return auditResult && audit.printInstallReport(auditResult)
|
855 |
|
856 | function packages (num) {
|
857 | return num + ' package' + (num > 1 ? 's' : '')
|
858 | }
|
859 |
|
860 | function from (num) {
|
861 | return ' from ' + num + ' contributor' + (num > 1 ? 's' : '')
|
862 | }
|
863 |
|
864 |
|
865 |
|
866 |
|
867 |
|
868 | function normalizePerson (argument) {
|
869 | if (typeof argument === 'string') return argument
|
870 | var returned = ''
|
871 | if (argument.name) returned += argument.name
|
872 | if (argument.email) returned += ' <' + argument.email + '>'
|
873 | if (argument.url) returned += ' (' + argument.email + ')'
|
874 | return returned
|
875 | }
|
876 | }
|
877 |
|
878 | Installer.prototype.printInstalledForJSON = function (diffs, auditResult) {
|
879 | var result = {
|
880 | added: [],
|
881 | removed: [],
|
882 | updated: [],
|
883 | moved: [],
|
884 | failed: [],
|
885 | warnings: [],
|
886 | audit: auditResult,
|
887 | elapsed: Date.now() - this.started
|
888 | }
|
889 | var self = this
|
890 | this.idealTree.warnings.forEach(function (warning) {
|
891 | if (warning.code === 'EPACKAGEJSON' && self.global) return
|
892 | if (warning.code === 'ENOTDIR') return
|
893 | var output = errorMessage(warning)
|
894 | var message = flattenMessage(output.summary)
|
895 | if (output.detail.length) {
|
896 | message += '\n' + flattenMessage(output.detail)
|
897 | }
|
898 | result.warnings.push(message)
|
899 | })
|
900 | diffs.forEach(function (action) {
|
901 | var mutation = action[0]
|
902 | var child = action[1]
|
903 | var record = recordAction(action)
|
904 | if (child.failed) {
|
905 | result.failed.push(record)
|
906 | } else if (mutation === 'add') {
|
907 | result.added.push(record)
|
908 | } else if (mutation === 'update' || mutation === 'update-linked') {
|
909 | result.updated.push(record)
|
910 | } else if (mutation === 'move') {
|
911 | result.moved.push(record)
|
912 | } else if (mutation === 'remove') {
|
913 | result.removed.push(record)
|
914 | }
|
915 | })
|
916 | output(JSON.stringify(result, null, 2))
|
917 |
|
918 | function flattenMessage (msg) {
|
919 | return msg.map(function (logline) { return logline.slice(1).join(' ') }).join('\n')
|
920 | }
|
921 |
|
922 | function recordAction (action) {
|
923 | var mutation = action[0]
|
924 | var child = action[1]
|
925 | var result = {
|
926 | action: mutation,
|
927 | name: moduleName(child),
|
928 | version: child.package && child.package.version,
|
929 | path: child.path
|
930 | }
|
931 | if (mutation === 'move') {
|
932 | result.previousPath = child.fromPath
|
933 | } else if (mutation === 'update') {
|
934 | result.previousVersion = child.oldPkg.package && child.oldPkg.package.version
|
935 | }
|
936 | return result
|
937 | }
|
938 | }
|
939 |
|
940 | Installer.prototype.printInstalledForParseable = function (diffs) {
|
941 | var self = this
|
942 | diffs.forEach(function (action) {
|
943 | var mutation = action[0]
|
944 | var child = action[1]
|
945 | if (mutation === 'move') {
|
946 | var previousPath = path.relative(self.where, child.fromPath)
|
947 | } else if (mutation === 'update') {
|
948 | var previousVersion = child.oldPkg.package && child.oldPkg.package.version
|
949 | }
|
950 | output(
|
951 | mutation + '\t' +
|
952 | moduleName(child) + '\t' +
|
953 | (child.package ? child.package.version : '') + '\t' +
|
954 | (child.path ? path.relative(self.where, child.path) : '') + '\t' +
|
955 | (previousVersion || '') + '\t' +
|
956 | (previousPath || ''))
|
957 | })
|
958 | }
|
959 |
|
960 | Installer.prototype.debugActions = function (name, actionListName, cb) {
|
961 | validate('SSF', arguments)
|
962 | var actionsToLog = this[actionListName]
|
963 | log.silly(name, 'action count', actionsToLog.length)
|
964 | actionsToLog.forEach(function (action) {
|
965 | log.silly(name, action.map(function (value) {
|
966 | return (value && value.package) ? packageId(value) : value
|
967 | }).join(' '))
|
968 | })
|
969 | cb()
|
970 | }
|
971 |
|
972 |
|
973 |
|
974 | Installer.prototype.debugTree = function (name, treeName, cb) {
|
975 | validate('SSF', arguments)
|
976 | log.silly(name, this.archyDebugTree(this[treeName]).trim())
|
977 | cb()
|
978 | }
|
979 |
|
980 | Installer.prototype.archyDebugTree = function (tree) {
|
981 | validate('O', arguments)
|
982 | var seen = new Set()
|
983 | function byName (aa, bb) {
|
984 | return packageId(aa).localeCompare(packageId(bb))
|
985 | }
|
986 | function expandTree (tree) {
|
987 | seen.add(tree)
|
988 | return {
|
989 | label: packageId(tree),
|
990 | nodes: tree.children.filter((tree) => { return !seen.has(tree) && !tree.removed }).sort(byName).map(expandTree)
|
991 | }
|
992 | }
|
993 | return archy(expandTree(tree), '', { unicode: npm.config.get('unicode') })
|
994 | }
|
995 |
|
996 | Installer.prototype.debugLogicalTree = function (name, treeName, cb) {
|
997 | validate('SSF', arguments)
|
998 | this[treeName] && log.silly(name, this.archyDebugLogicalTree(this[treeName]).trim())
|
999 | cb()
|
1000 | }
|
1001 |
|
1002 | Installer.prototype.archyDebugLogicalTree = function (tree) {
|
1003 | validate('O', arguments)
|
1004 | var seen = new Set()
|
1005 | function byName (aa, bb) {
|
1006 | return packageId(aa).localeCompare(packageId(bb))
|
1007 | }
|
1008 | function expandTree (tree) {
|
1009 | seen.add(tree)
|
1010 | return {
|
1011 | label: packageId(tree),
|
1012 | nodes: tree.requires.filter((tree) => { return !seen.has(tree) && !tree.removed }).sort(byName).map(expandTree)
|
1013 | }
|
1014 | }
|
1015 | return archy(expandTree(tree), '', { unicode: npm.config.get('unicode') })
|
1016 | }
|