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