/* eslint-disable camelcase */
/* eslint-disable standard/no-callback-literal */
// npm install <pkg> <pkg> <pkg>
// See doc/cli/npm-install.md for more description
// there's a lot of state associated with an "install" operation, including
// packages that are already installed, parent packages, current shrinkwrap, and
// so on. We maintain this state in a "context" object that gets passed around.
// every time we dive into a deeper node_modules folder, the "family" list that
// gets passed along uses the previous "family" list as its __proto__. Any
// "resolved precise dependency" things that aren't already on this object get
// added, and then that's passed to the next generation of installation.
module.exports.Installer = Installer
var usage = require('./utils/usage')
'\nnpm install (with no args, in package dir)' +
'\nnpm install [<@scope>/]<pkg>' +
'\nnpm install [<@scope>/]<pkg>@<tag>' +
'\nnpm install [<@scope>/]<pkg>@<version>' +
'\nnpm install [<@scope>/]<pkg>@<version range>' +
'\nnpm install <alias>@npm:<name>' +
'\nnpm install <folder>' +
'\nnpm install <tarball file>' +
'\nnpm install <tarball url>' +
'\nnpm install <git:// url>' +
'\nnpm install <github username>/<github project>',
'[--save-prod|--save-dev|--save-optional] [--save-exact] [--no-save]'
install.completion = function (opts, cb) {
validate('OF', arguments)
// install can complete to a folder with a package.json, or any package.
// if it has a slash, then it's gotta be a folder
// if it starts with https?://, then just give up, because it's a url
if (/^https?:\/\//.test(opts.partialWord)) {
// do not complete to URLs
if (/\//.test(opts.partialWord)) {
// Complete fully to folder if there is exactly one match and it
// is a folder containing a package.json file. If that is not the
// case we return 0 matches, which will trigger the default bash
var lastSlashIdx = opts.partialWord.lastIndexOf('/')
var partialName = opts.partialWord.slice(lastSlashIdx + 1)
var partialPath = opts.partialWord.slice(0, lastSlashIdx)
if (partialPath === '') partialPath = '/'
var annotatePackageDirMatch = function (sibling, cb) {
var fullPath = path.join(partialPath, sibling)
if (sibling.slice(0, partialName.length) !== partialName) {
return cb(null, null) // not name match
fs.readdir(fullPath, function (err, contents) {
if (err) return cb(null, { isPackage: false })
isPackage: contents.indexOf('package.json') !== -1
return fs.readdir(partialPath, function (err, siblings) {
if (err) return cb(null, []) // invalid dir: no matching
asyncMap(siblings, annotatePackageDirMatch, function (err, matches) {
var cleaned = matches.filter(function (x) { return x !== null })
if (cleaned.length !== 1) return cb(null, [])
if (!cleaned[0].isPackage) return cb(null, [])
// Success - only one match and it is a package dir
return cb(null, [cleaned[0].fullPath])
// FIXME: there used to be registry completion here, but it stopped making
// sense somewhere around 50,000 packages on the registry
var path = require('path')
var log = require('npmlog')
var readPackageTree = require('read-package-tree')
var readPackageJson = require('read-package-json')
var chain = require('slide').chain
var asyncMap = require('slide').asyncMap
var archy = require('archy')
var mkdirp = require('gentle-fs').mkdir
var rimraf = require('rimraf')
var iferr = require('iferr')
var validate = require('aproba')
var uniq = require('lodash.uniq')
var Bluebird = require('bluebird')
var npm = require('./npm.js')
var locker = require('./utils/locker.js')
var unlock = locker.unlock
var parseJSON = require('./utils/parse-json.js')
var output = require('./utils/output.js')
var saveMetrics = require('./utils/metrics.js').save
// install specific libraries
var copyTree = require('./install/copy-tree.js')
var readShrinkwrap = require('./install/read-shrinkwrap.js')
var computeMetadata = require('./install/deps.js').computeMetadata
var prefetchDeps = require('./install/deps.js').prefetchDeps
var loadDeps = require('./install/deps.js').loadDeps
var loadDevDeps = require('./install/deps.js').loadDevDeps
var getAllMetadata = require('./install/deps.js').getAllMetadata
var loadRequestedDeps = require('./install/deps.js').loadRequestedDeps
var loadExtraneous = require('./install/deps.js').loadExtraneous
var diffTrees = require('./install/diff-trees.js')
var checkPermissions = require('./install/check-permissions.js')
var decomposeActions = require('./install/decompose-actions.js')
var validateTree = require('./install/validate-tree.js')
var validateArgs = require('./install/validate-args.js')
var saveRequested = require('./install/save.js').saveRequested
var saveShrinkwrap = require('./install/save.js').saveShrinkwrap
var audit = require('./install/audit.js')
getPrintFundingReportJSON
} = require('./install/fund.js')
var getSaveType = require('./install/save.js').getSaveType
var doSerialActions = require('./install/actions.js').doSerial
var doReverseSerialActions = require('./install/actions.js').doReverseSerial
var doParallelActions = require('./install/actions.js').doParallel
var doOneAction = require('./install/actions.js').doOne
var removeObsoleteDep = require('./install/deps.js').removeObsoleteDep
var removeExtraneous = require('./install/deps.js').removeExtraneous
var computeVersionSpec = require('./install/deps.js').computeVersionSpec
var packageId = require('./utils/package-id.js')
var moduleName = require('./utils/module-name.js')
var errorMessage = require('./utils/error-message.js')
var isExtraneous = require('./install/is-extraneous.js')
function unlockCB (lockPath, name, cb) {
validate('SSF', arguments)
return function (installEr) {
unlock(lockPath, name, reportErrorAndReturn)
process.nextTick(function () {
reportErrorAndReturn(unlockEx)
function reportErrorAndReturn (unlockEr) {
if (unlockEr && unlockEr.code !== 'ENOTLOCKED') {
log.warn('unlock' + name, unlockEr)
return cb.apply(null, args)
if (unlockEr) return cb(unlockEr)
return cb.apply(null, args)
function install (where, args, cb) {
var globalTop = path.resolve(npm.globalDir, '..')
where = npm.config.get('global')
validate('SAF', [where, args, cb])
// the /path/to/node_modules/..
var dryrun = !!npm.config.get('dry-run')
if (npm.config.get('dev')) {
log.warn('install', 'Usage of the `--dev` option is deprecated. Use `--also=dev` instead.')
if (where === globalTop && !args.length) {
args = args.filter(function (a) {
return path.resolve(a) !== npm.prefix
new Installer(where, dryrun, args).run(cb)
function Installer (where, dryrun, args, opts) {
validate('SBA|SBAO', arguments)
// fakechildren are children created from the lockfile and lack relationship data
// the only exist when the tree does not match the lockfile
// this is fine when doing full tree installs/updates but not ok when modifying only
// a few deps via `npm install` or `npm uninstall`.
this.noPackageJsonOk = !!args.length
this.topLevelLifecycles = !args.length
this.autoPrune = npm.config.get('package-lock')
const dev = npm.config.get('dev')
const only = npm.config.get('only')
const onlyProd = /^prod(uction)?$/.test(only)
const onlyDev = /^dev(elopment)?$/.test(only)
const prod = npm.config.get('production')
this.dev = opts.dev != null ? opts.dev : dev || (!onlyProd && !prod) || onlyDev
this.prod = opts.prod != null ? opts.prod : !onlyDev
this.packageLockOnly = opts.packageLockOnly != null
? opts.packageLockOnly : npm.config.get('package-lock-only')
this.rollback = opts.rollback != null ? opts.rollback : npm.config.get('rollback')
this.link = opts.link != null ? opts.link : npm.config.get('link')
this.saveOnlyLock = opts.saveOnlyLock
this.global = opts.global != null ? opts.global : this.where === path.resolve(npm.globalDir, '..')
this.audit = npm.config.get('audit') && !this.global
this.fund = npm.config.get('fund') && !this.global
this.started = Date.now()
Installer.prototype.run = function (_cb) {
validate('F|', arguments)
return _cb.apply(this, arguments)
result = new Promise((resolve, reject) => {
cb = (err, value) => err ? reject(err) : resolve(value)
// FIXME: This is bad and I should feel bad.
// lib/install needs to have some way of sharing _limited_
// state with the things it calls. Passing the object is too
// much. The global config is WAY too much. =( =(
// But not having this is gonna break linked modules in
// subtle stupid ways, and refactoring all this code isn't
// the right thing to do just yet.
var prevGlobal = npm.config.get('global')
npm.config.set('global', true)
npm.config.set('global', prevGlobal)
next.apply(null, arguments)
var postInstallSteps = []
[this.newTracker(log, 'runTopLevelLifecycles', 2)],
[this, this.runPreinstallTopLevelLifecycles])
[this.newTracker(log, 'loadCurrentTree', 4)],
[this, this.loadCurrentTree],
[this, this.finishTracker, 'loadCurrentTree'],
[this.newTracker(log, 'loadIdealTree', 12)],
[this, this.loadIdealTree],
[this, this.finishTracker, 'loadIdealTree'],
[this, this.debugTree, 'currentTree', 'currentTree'],
[this, this.debugTree, 'idealTree', 'idealTree'],
[this.newTracker(log, 'generateActionsToTake')],
[this, this.generateActionsToTake],
[this, this.finishTracker, 'generateActionsToTake'],
[this, this.debugActions, 'diffTrees', 'differences'],
[this, this.debugActions, 'decomposeActions', 'todo'],
if (this.packageLockOnly) {
[this, this.saveToDependencies])
} else if (!this.dryrun) {
[this.newTracker(log, 'executeActions', 8)],
[this, this.executeActions],
[this, this.finishTracker, 'executeActions'])
var node_modules = path.resolve(this.where, 'node_modules')
var staging = path.resolve(node_modules, '.staging')
[this.newTracker(log, 'rollbackFailedOptional', 1)],
[this, this.rollbackFailedOptional, staging, this.todo],
[this, this.finishTracker, 'rollbackFailedOptional'],
[this, this.commit, staging, this.todo],
[this, this.runPostinstallTopLevelLifecycles],
[this, this.finishTracker, 'runTopLevelLifecycles']
// this is necessary as we don't fill in `dependencies` and `devDependencies` in deps loaded from shrinkwrap
// until after we extract them
[this, (next) => { computeMetadata(this.idealTree); next() }],
[this, this.pruneIdealTree],
[this, this.debugLogicalTree, 'saveTree', 'idealTree'],
[this, this.saveToDependencies])
[this, this.printWarnings],
[this, this.printInstalled])
chain(installSteps, function (installEr) {
if (installEr) self.failing = true
chain(postInstallSteps, function (postInstallEr) {
if (installEr && postInstallEr) {
var msg = errorMessage(postInstallEr)
msg.summary.forEach(function (logline) {
log.warn.apply(log, logline)
msg.detail.forEach(function (logline) {
log.verbose.apply(log, logline)
cb(installEr || postInstallEr, self.getInstalledModules(), self.idealTree)
Installer.prototype.loadArgMetadata = function (next) {
getAllMetadata(this.args, this.currentTree, process.cwd(), iferr(next, (args) => {
Installer.prototype.newTracker = function (tracker, name, size) {
validate('OS', [tracker, name])
if (size) validate('N', [size])
this.progress[name] = tracker.newGroup(name, size)
process.emit('time', 'stage:' + name)
Installer.prototype.finishTracker = function (name, cb) {
validate('SF', arguments)
process.emit('timeEnd', 'stage:' + name)
Installer.prototype.loadCurrentTree = function (cb) {
log.silly('install', 'loadCurrentTree')
todo.push([this, this.readGlobalPackageData])
todo.push([this, this.readLocalPackageData])
todo.push([this, this.normalizeCurrentTree])
var createNode = require('./install/node.js').create
var flatNameFromTree = require('./install/flatten-tree.js').flatNameFromTree
Installer.prototype.normalizeCurrentTree = function (cb) {
this.currentTree.isTop = true
normalizeTree(this.currentTree)
// If the user didn't have a package.json then fill in deps with what was on disk
if (this.currentTree.error) {
for (let child of this.currentTree.children) {
if (!child.fakeChild && isExtraneous(child)) {
this.currentTree.package.dependencies[moduleName(child)] = computeVersionSpec(this.currentTree, child)
computeMetadata(this.currentTree)
function normalizeTree (tree, seen) {
if (!seen) seen = new Set()
if (seen.has(tree)) return
tree.location = flatNameFromTree(tree)
tree.children.forEach((child) => normalizeTree(child, seen))
Installer.prototype.loadIdealTree = function (cb) {
log.silly('install', 'loadIdealTree')
[this.newTracker(this.progress.loadIdealTree, 'loadIdealTree:cloneCurrentTree')],
[this, this.cloneCurrentTreeToIdealTree],
[this, this.finishTracker, 'loadIdealTree:cloneCurrentTree'],
[this.newTracker(this.progress.loadIdealTree, 'loadIdealTree:loadShrinkwrap')],
[this, this.loadShrinkwrap],
[this, this.finishTracker, 'loadIdealTree:loadShrinkwrap'],
[this.newTracker(this.progress.loadIdealTree, 'loadIdealTree:loadAllDepsIntoIdealTree', 10)],
[this, this.loadAllDepsIntoIdealTree],
[this, this.finishTracker, 'loadIdealTree:loadAllDepsIntoIdealTree'],
[this, function (next) { computeMetadata(this.idealTree); next() }],
[this, this.pruneIdealTree]
Installer.prototype.pruneIdealTree = function (cb) {
if (!this.idealTree) return cb()
// if our lock file didn't have the requires field and there
// are any fake children then forgo pruning until we have more info.
if (!this.idealTree.hasRequiresFromLock && this.idealTree.children.some((n) => n.fakeChild)) return cb()
const toPrune = this.idealTree.children
.filter((child) => isExtraneous(child) && (this.autoPrune || child.removing))
.map((n) => ({name: moduleName(n)}))
return removeExtraneous(toPrune, this.idealTree, cb)
Installer.prototype.loadAllDepsIntoIdealTree = function (cb) {
log.silly('install', 'loadAllDepsIntoIdealTree')
var saveDeps = getSaveType()
var cg = this.progress['loadIdealTree:loadAllDepsIntoIdealTree']
var installNewModules = !!this.args.length
steps.push([validateArgs, this.idealTree, this.args])
steps.push([loadRequestedDeps, this.args, this.idealTree, saveDeps, cg.newGroup('loadRequestedDeps')])
const depsToPreload = Object.assign({},
this.idealTree.package.devDependencies,
this.idealTree.package.dependencies
[prefetchDeps, this.idealTree, depsToPreload, cg.newGroup('prefetchDeps')],
[loadDeps, this.idealTree, cg.newGroup('loadDeps')],
[loadDevDeps, this.idealTree, cg.newGroup('loadDevDeps')])
[loadExtraneous.andResolveDeps, this.idealTree, cg.newGroup('loadExtraneous')])
Installer.prototype.generateActionsToTake = function (cb) {
log.silly('install', 'generateActionsToTake')
var cg = this.progress.generateActionsToTake
[validateTree, this.idealTree, cg.newGroup('validateTree')],
[diffTrees, this.currentTree, this.idealTree, this.differences, cg.newGroup('diffTrees')],
[this, this.computeLinked],
[checkPermissions, this.differences],
[decomposeActions, this.differences, this.todo]
Installer.prototype.computeLinked = function (cb) {