install.js 36 KB

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