UNPKG

36.6 kBJavaScriptView Raw
1'use strict'
2/* eslint-disable camelcase */
3/* eslint-disable standard/no-callback-literal */
4// npm install <pkg> <pkg> <pkg>
5//
6// See doc/cli/npm-install.md for more description
7//
8// Managing contexts...
9// there's a lot of state associated with an "install" operation, including
10// packages that are already installed, parent packages, current shrinkwrap, and
11// so on. We maintain this state in a "context" object that gets passed around.
12// every time we dive into a deeper node_modules folder, the "family" list that
13// gets passed along uses the previous "family" list as its __proto__. Any
14// "resolved precise dependency" things that aren't already on this object get
15// added, and then that's passed to the next generation of installation.
16
17module.exports = install
18module.exports.Installer = Installer
19
20var usage = require('./utils/usage')
21
22install.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
37install.completion = function (opts, cb) {
38 validate('OF', arguments)
39 // install can complete to a folder with a package.json, or any package.
40 // if it has a slash, then it's gotta be a folder
41 // if it starts with https?://, then just give up, because it's a url
42 if (/^https?:\/\//.test(opts.partialWord)) {
43 // do not complete to URLs
44 return cb(null, [])
45 }
46
47 if (/\//.test(opts.partialWord)) {
48 // Complete fully to folder if there is exactly one match and it
49 // is a folder containing a package.json file. If that is not the
50 // case we return 0 matches, which will trigger the default bash
51 // complete.
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) // not name match
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, []) // invalid dir: no matching
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 // Success - only one match and it is a package dir
86 return cb(null, [cleaned[0].fullPath])
87 })
88 })
89 }
90
91 // FIXME: there used to be registry completion here, but it stopped making
92 // sense somewhere around 50,000 packages on the registry
93 cb()
94}
95
96// system packages
97var fs = require('fs')
98var path = require('path')
99
100// dependencies
101var log = require('npmlog')
102var readPackageTree = require('read-package-tree')
103var readPackageJson = require('read-package-json')
104var chain = require('slide').chain
105var asyncMap = require('slide').asyncMap
106var archy = require('archy')
107var mkdirp = require('mkdirp')
108var rimraf = require('rimraf')
109var iferr = require('iferr')
110var validate = require('aproba')
111var uniq = require('lodash.uniq')
112var Bluebird = require('bluebird')
113
114// npm internal utils
115var npm = require('./npm.js')
116var locker = require('./utils/locker.js')
117var lock = locker.lock
118var unlock = locker.unlock
119var parseJSON = require('./utils/parse-json.js')
120var output = require('./utils/output.js')
121var saveMetrics = require('./utils/metrics.js').save
122
123// install specific libraries
124var copyTree = require('./install/copy-tree.js')
125var readShrinkwrap = require('./install/read-shrinkwrap.js')
126var computeMetadata = require('./install/deps.js').computeMetadata
127var prefetchDeps = require('./install/deps.js').prefetchDeps
128var loadDeps = require('./install/deps.js').loadDeps
129var loadDevDeps = require('./install/deps.js').loadDevDeps
130var getAllMetadata = require('./install/deps.js').getAllMetadata
131var loadRequestedDeps = require('./install/deps.js').loadRequestedDeps
132var loadExtraneous = require('./install/deps.js').loadExtraneous
133var diffTrees = require('./install/diff-trees.js')
134var checkPermissions = require('./install/check-permissions.js')
135var decomposeActions = require('./install/decompose-actions.js')
136var validateTree = require('./install/validate-tree.js')
137var validateArgs = require('./install/validate-args.js')
138var saveRequested = require('./install/save.js').saveRequested
139var saveShrinkwrap = require('./install/save.js').saveShrinkwrap
140var audit = require('./install/audit.js')
141var getSaveType = require('./install/save.js').getSaveType
142var doSerialActions = require('./install/actions.js').doSerial
143var doReverseSerialActions = require('./install/actions.js').doReverseSerial
144var doParallelActions = require('./install/actions.js').doParallel
145var doOneAction = require('./install/actions.js').doOne
146var removeObsoleteDep = require('./install/deps.js').removeObsoleteDep
147var removeExtraneous = require('./install/deps.js').removeExtraneous
148var computeVersionSpec = require('./install/deps.js').computeVersionSpec
149var packageId = require('./utils/package-id.js')
150var moduleName = require('./utils/module-name.js')
151var errorMessage = require('./utils/error-message.js')
152var isExtraneous = require('./install/is-extraneous.js')
153
154function 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
178function 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 // the /path/to/node_modules/..
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
208function 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 // fakechildren are children created from the lockfile and lack relationship data
215 // the only exist when the tree does not match the lockfile
216 // this is fine when doing full tree installs/updates but not ok when modifying only
217 // a few deps via `npm install` or `npm uninstall`.
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}
245Installer.prototype = {}
246
247Installer.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 // FIXME: This is bad and I should feel bad.
263 // lib/install needs to have some way of sharing _limited_
264 // state with the things it calls. Passing the object is too
265 // much. The global config is WAY too much. =( =(
266 // But not having this is gonna break linked modules in
267 // subtle stupid ways, and refactoring all this code isn't
268 // the right thing to do just yet.
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 // this is necessary as we don't fill in `dependencies` and `devDependencies` in deps loaded from shrinkwrap
329 // until after we extract them
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
359Installer.prototype.loadArgMetadata = function (next) {
360 getAllMetadata(this.args, this.currentTree, process.cwd(), iferr(next, (args) => {
361 this.args = args
362 next()
363 }))
364}
365
366Installer.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
376Installer.prototype.finishTracker = function (name, cb) {
377 validate('SF', arguments)
378 process.emit('timeEnd', 'stage:' + name)
379 cb()
380}
381
382Installer.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
395var createNode = require('./install/node.js').create
396var flatNameFromTree = require('./install/flatten-tree.js').flatNameFromTree
397Installer.prototype.normalizeCurrentTree = function (cb) {
398 this.currentTree.isTop = true
399 normalizeTree(this.currentTree)
400 // If the user didn't have a package.json then fill in deps with what was on disk
401 if (this.currentTree.error) {
402 for (let child of this.currentTree.children) {
403 if (!child.fakeChild && isExtraneous(child)) {
404 this.currentTree.package.dependencies[moduleName(child)] = 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
421Installer.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
442Installer.prototype.pruneIdealTree = function (cb) {
443 if (!this.idealTree) return cb()
444 // if our lock file didn't have the requires field and there
445 // are any fake children then forgo pruning until we have more info.
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
453Installer.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
480Installer.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
493Installer.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
520function 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
532Installer.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
572Installer.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
585Installer.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
597Installer.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
614Installer.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
633Installer.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
643Installer.prototype.saveToDependencies = function (cb) {
644 validate('F', arguments)
645 if (this.failing) return cb()
646 log.silly('install', 'saveToDependencies')
647 if (this.saveOnlyLock) {
648 saveShrinkwrap(this.idealTree, cb)
649 } else {
650 saveRequested(this.idealTree, cb)
651 }
652}
653
654Installer.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
672Installer.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
702Installer.prototype.cloneCurrentTreeToIdealTree = function (cb) {
703 validate('F', arguments)
704 log.silly('install', 'cloneCurrentTreeToIdealTree')
705
706 if (npm.config.get('before')) {
707 this.idealTree = {
708 package: this.currentTree.package,
709 path: this.currentTree.path,
710 realpath: this.currentTree.realpath,
711 children: [],
712 requires: [],
713 missingDeps: {},
714 missingDevDeps: {},
715 requiredBy: [],
716 error: this.currentTree.error,
717 warnings: [],
718 isTop: true
719 }
720 } else {
721 this.idealTree = copyTree(this.currentTree)
722 this.idealTree.warnings = []
723 }
724
725 cb()
726}
727
728Installer.prototype.loadShrinkwrap = function (cb) {
729 validate('F', arguments)
730 log.silly('install', 'loadShrinkwrap')
731 readShrinkwrap.andInflate(this.idealTree, iferr(cb, () => {
732 computeMetadata(this.idealTree)
733 cb()
734 }))
735}
736
737Installer.prototype.getInstalledModules = function () {
738 return this.differences.filter(function (action) {
739 var mutation = action[0]
740 return (mutation === 'add' || mutation === 'update')
741 }).map(function (action) {
742 var child = action[1]
743 return [child.package._id, child.path]
744 })
745}
746
747Installer.prototype.printWarnings = function (cb) {
748 if (!this.idealTree) return cb()
749
750 var self = this
751 var warned = false
752 this.idealTree.warnings.forEach(function (warning) {
753 if (warning.code === 'EPACKAGEJSON' && self.global) return
754 if (warning.code === 'ENOTDIR') return
755 warned = true
756 var msg = errorMessage(warning)
757 msg.summary.forEach(function (logline) {
758 log.warn.apply(log, logline)
759 })
760 msg.detail.forEach(function (logline) {
761 log.verbose.apply(log, logline)
762 })
763 })
764 if (warned && log.levels[npm.config.get('loglevel')] <= log.levels.warn) console.error()
765 cb()
766}
767
768Installer.prototype.printInstalled = function (cb) {
769 validate('F', arguments)
770 if (this.failing) return cb()
771 log.silly('install', 'printInstalled')
772 const diffs = this.differences
773 if (!this.idealTree.error && this.idealTree.removedChildren) {
774 const deps = this.currentTree.package.dependencies || {}
775 const dev = this.currentTree.package.devDependencies || {}
776 this.idealTree.removedChildren.forEach((r) => {
777 if (diffs.some((d) => d[0] === 'remove' && d[1].path === r.path)) return
778 if (!deps[moduleName(r)] && !dev[moduleName(r)]) return
779 diffs.push(['remove', r])
780 })
781 }
782 return Bluebird.try(() => {
783 if (!this.auditSubmission) return
784 return Bluebird.resolve(this.auditSubmission).timeout(10000).catch(() => null)
785 }).then((auditResult) => {
786 if (auditResult && !auditResult.metadata) {
787 log.warn('audit', 'Audit result from registry missing metadata. This is probably an issue with the registry.')
788 }
789 // maybe write audit report w/ hash of pjson & shrinkwrap for later reading by `npm audit`
790 if (npm.config.get('json')) {
791 return this.printInstalledForJSON(diffs, auditResult)
792 } else if (npm.config.get('parseable')) {
793 return this.printInstalledForParseable(diffs, auditResult)
794 } else {
795 return this.printInstalledForHuman(diffs, auditResult)
796 }
797 }).asCallback(cb)
798}
799
800Installer.prototype.printInstalledForHuman = function (diffs, auditResult) {
801 var removed = 0
802 var added = 0
803 var updated = 0
804 var moved = 0
805 // Count the number of contributors to packages added, tracking
806 // contributors we've seen, so we can produce a running unique count.
807 var contributors = new Set()
808 diffs.forEach(function (action) {
809 var mutation = action[0]
810 var pkg = action[1]
811 if (pkg.failed) return
812 if (mutation === 'remove') {
813 ++removed
814 } else if (mutation === 'move') {
815 ++moved
816 } else if (mutation === 'add') {
817 ++added
818 // Count contributors to added packages. Start by combining `author`
819 // and `contributors` data into a single array of contributor-people
820 // for this package.
821 var people = []
822 var meta = pkg.package
823 if (meta.author) people.push(meta.author)
824 if (meta.contributors && Array.isArray(meta.contributors)) {
825 people = people.concat(meta.contributors)
826 }
827 // Make sure a normalized string for every person behind this
828 // package is in `contributors`.
829 people.forEach(function (person) {
830 // Ignore errors from malformed `author` and `contributors`.
831 try {
832 var normalized = normalizePerson(person)
833 } catch (error) {
834 return
835 }
836 if (!contributors.has(normalized)) contributors.add(normalized)
837 })
838 } else if (mutation === 'update' || mutation === 'update-linked') {
839 ++updated
840 }
841 })
842 var report = ''
843 if (this.args.length && (added || updated)) {
844 report += this.args.map((p) => {
845 return `+ ${p.name}@${p.version}${
846 !p._requested.name || p._requested.name === p.name
847 ? ''
848 : ` (as ${p._requested.name})`
849 }`
850 }).join('\n') + '\n'
851 }
852 var actions = []
853 if (added) {
854 var action = 'added ' + packages(added)
855 if (contributors.size) action += from(contributors.size)
856 actions.push(action)
857 }
858 if (removed) actions.push('removed ' + packages(removed))
859 if (updated) actions.push('updated ' + packages(updated))
860 if (moved) actions.push('moved ' + packages(moved))
861 if (auditResult && auditResult.metadata && auditResult.metadata.totalDependencies) {
862 actions.push('audited ' + packages(auditResult.metadata.totalDependencies))
863 }
864 if (actions.length === 0) {
865 report += 'up to date'
866 } else if (actions.length === 1) {
867 report += actions[0]
868 } else {
869 var lastAction = actions.pop()
870 report += actions.join(', ') + ' and ' + lastAction
871 }
872 report += ' in ' + ((Date.now() - this.started) / 1000) + 's'
873
874 output(report)
875 return auditResult && audit.printInstallReport(auditResult)
876
877 function packages (num) {
878 return num + ' package' + (num > 1 ? 's' : '')
879 }
880
881 function from (num) {
882 return ' from ' + num + ' contributor' + (num > 1 ? 's' : '')
883 }
884
885 // Values of `author` and elements of `contributors` in `package.json`
886 // files can be e-mail style strings or Objects with `name`, `email,
887 // and `url` String properties. Convert Objects to Strings so that
888 // we can efficiently keep a set of contributors we have already seen.
889 function normalizePerson (argument) {
890 if (typeof argument === 'string') return argument
891 var returned = ''
892 if (argument.name) returned += argument.name
893 if (argument.email) returned += ' <' + argument.email + '>'
894 if (argument.url) returned += ' (' + argument.email + ')'
895 return returned
896 }
897}
898
899Installer.prototype.printInstalledForJSON = function (diffs, auditResult) {
900 var result = {
901 added: [],
902 removed: [],
903 updated: [],
904 moved: [],
905 failed: [],
906 warnings: [],
907 audit: auditResult,
908 elapsed: Date.now() - this.started
909 }
910 var self = this
911 this.idealTree.warnings.forEach(function (warning) {
912 if (warning.code === 'EPACKAGEJSON' && self.global) return
913 if (warning.code === 'ENOTDIR') return
914 var output = errorMessage(warning)
915 var message = flattenMessage(output.summary)
916 if (output.detail.length) {
917 message += '\n' + flattenMessage(output.detail)
918 }
919 result.warnings.push(message)
920 })
921 diffs.forEach(function (action) {
922 var mutation = action[0]
923 var child = action[1]
924 var record = recordAction(action)
925 if (child.failed) {
926 result.failed.push(record)
927 } else if (mutation === 'add') {
928 result.added.push(record)
929 } else if (mutation === 'update' || mutation === 'update-linked') {
930 result.updated.push(record)
931 } else if (mutation === 'move') {
932 result.moved.push(record)
933 } else if (mutation === 'remove') {
934 result.removed.push(record)
935 }
936 })
937 output(JSON.stringify(result, null, 2))
938
939 function flattenMessage (msg) {
940 return msg.map(function (logline) { return logline.slice(1).join(' ') }).join('\n')
941 }
942
943 function recordAction (action) {
944 var mutation = action[0]
945 var child = action[1]
946 const isAlias = child.package && child.package._requested && child.package._requested.type === 'alias'
947 const name = isAlias
948 ? child.package._requested.name
949 : child.package && child.package.name
950 var result = {
951 action: mutation,
952 name,
953 version: child.package && `${isAlias ? `npm:${child.package.name}@` : ''}${child.package.version}`,
954 path: child.path
955 }
956 if (mutation === 'move') {
957 result.previousPath = child.fromPath
958 } else if (mutation === 'update') {
959 result.previousVersion = child.oldPkg.package && child.oldPkg.package.version
960 }
961 return result
962 }
963}
964
965Installer.prototype.printInstalledForParseable = function (diffs) {
966 var self = this
967 diffs.forEach(function (action) {
968 var mutation = action[0]
969 var child = action[1]
970 if (mutation === 'move') {
971 var previousPath = path.relative(self.where, child.fromPath)
972 } else if (mutation === 'update') {
973 var previousVersion = child.oldPkg.package && child.oldPkg.package.version
974 }
975 const isAlias = child.package._requested && child.package._requested.type === 'alias'
976 const version = child.package && isAlias
977 ? `npm:${child.package.name}@${child.package.version}`
978 : child.package
979 ? child.package.version
980 : ''
981 output(
982 mutation + '\t' +
983 moduleName(child) + '\t' +
984 version + '\t' +
985 (child.path ? path.relative(self.where, child.path) : '') + '\t' +
986 (previousVersion || '') + '\t' +
987 (previousPath || ''))
988 })
989}
990
991Installer.prototype.debugActions = function (name, actionListName, cb) {
992 validate('SSF', arguments)
993 var actionsToLog = this[actionListName]
994 log.silly(name, 'action count', actionsToLog.length)
995 actionsToLog.forEach(function (action) {
996 log.silly(name, action.map(function (value) {
997 return (value && value.package) ? packageId(value) : value
998 }).join(' '))
999 })
1000 cb()
1001}
1002
1003// This takes an object and a property name instead of a value to allow us
1004// to define the arguments for use by chain before the property exists yet.
1005Installer.prototype.debugTree = function (name, treeName, cb) {
1006 validate('SSF', arguments)
1007 log.silly(name, this.archyDebugTree(this[treeName]).trim())
1008 cb()
1009}
1010
1011Installer.prototype.archyDebugTree = function (tree) {
1012 validate('O', arguments)
1013 var seen = new Set()
1014 function byName (aa, bb) {
1015 return packageId(aa).localeCompare(packageId(bb))
1016 }
1017 function expandTree (tree) {
1018 seen.add(tree)
1019 return {
1020 label: packageId(tree),
1021 nodes: tree.children.filter((tree) => { return !seen.has(tree) && !tree.removed }).sort(byName).map(expandTree)
1022 }
1023 }
1024 return archy(expandTree(tree), '', { unicode: npm.config.get('unicode') })
1025}
1026
1027Installer.prototype.debugLogicalTree = function (name, treeName, cb) {
1028 validate('SSF', arguments)
1029 this[treeName] && log.silly(name, this.archyDebugLogicalTree(this[treeName]).trim())
1030 cb()
1031}
1032
1033Installer.prototype.archyDebugLogicalTree = function (tree) {
1034 validate('O', arguments)
1035 var seen = new Set()
1036 function byName (aa, bb) {
1037 return packageId(aa).localeCompare(packageId(bb))
1038 }
1039 function expandTree (tree) {
1040 seen.add(tree)
1041 return {
1042 label: packageId(tree),
1043 nodes: tree.requires.filter((tree) => { return !seen.has(tree) && !tree.removed }).sort(byName).map(expandTree)
1044 }
1045 }
1046 return archy(expandTree(tree), '', { unicode: npm.config.get('unicode') })
1047}