actions.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. 'use strict'
  2. const BB = require('bluebird')
  3. const andAddParentToErrors = require('./and-add-parent-to-errors.js')
  4. const failedDependency = require('./deps.js').failedDependency
  5. const isInstallable = BB.promisify(require('./validate-args.js').isInstallable)
  6. const moduleName = require('../utils/module-name.js')
  7. const npm = require('../npm.js')
  8. const reportOptionalFailure = require('./report-optional-failure.js')
  9. const validate = require('aproba')
  10. const actions = {}
  11. actions.fetch = require('./action/fetch.js')
  12. actions.extract = require('./action/extract.js')
  13. actions.build = require('./action/build.js')
  14. actions.preinstall = require('./action/preinstall.js')
  15. actions.install = require('./action/install.js')
  16. actions.postinstall = require('./action/postinstall.js')
  17. actions.prepare = require('./action/prepare.js')
  18. actions.finalize = require('./action/finalize.js')
  19. actions.remove = require('./action/remove.js')
  20. actions.unbuild = require('./action/unbuild.js')
  21. actions.move = require('./action/move.js')
  22. actions['global-install'] = require('./action/global-install.js')
  23. actions['global-link'] = require('./action/global-link.js')
  24. actions['refresh-package-json'] = require('./action/refresh-package-json.js')
  25. // FIXME: We wrap actions like three ways to sunday here.
  26. // Rewrite this to only work one way.
  27. Object.keys(actions).forEach(function (actionName) {
  28. var action = actions[actionName]
  29. actions[actionName] = (staging, pkg, log) => {
  30. validate('SOO', [staging, pkg, log])
  31. // refuse to run actions for failed packages
  32. if (pkg.failed) return BB.resolve()
  33. if (action.rollback) {
  34. if (!pkg.rollback) pkg.rollback = []
  35. pkg.rollback.unshift(action.rollback)
  36. }
  37. if (action.commit) {
  38. if (!pkg.commit) pkg.commit = []
  39. pkg.commit.push(action.commit)
  40. }
  41. let actionP
  42. if (pkg.knownInstallable) {
  43. actionP = runAction(action, staging, pkg, log)
  44. } else {
  45. actionP = isInstallable(null, pkg.package).then(() => {
  46. pkg.knownInstallable = true
  47. return runAction(action, staging, pkg, log)
  48. })
  49. }
  50. return actionP.then(() => {
  51. log.finish()
  52. }, (err) => {
  53. return BB.fromNode((cb) => {
  54. andAddParentToErrors(pkg.parent, cb)(err)
  55. }).catch((err) => {
  56. return handleOptionalDepErrors(pkg, err)
  57. })
  58. })
  59. }
  60. actions[actionName].init = action.init || (() => BB.resolve())
  61. actions[actionName].teardown = action.teardown || (() => BB.resolve())
  62. })
  63. exports.actions = actions
  64. function runAction (action, staging, pkg, log) {
  65. return BB.fromNode((cb) => {
  66. const result = action(staging, pkg, log, cb)
  67. if (result && result.then) {
  68. result.then(() => cb(), cb)
  69. }
  70. })
  71. }
  72. function markAsFailed (pkg) {
  73. if (pkg.failed) return
  74. pkg.failed = true
  75. pkg.requires.forEach((req) => {
  76. var requiredBy = req.requiredBy.filter((reqReqBy) => !reqReqBy.failed)
  77. if (requiredBy.length === 0 && !req.userRequired) {
  78. markAsFailed(req)
  79. }
  80. })
  81. }
  82. function handleOptionalDepErrors (pkg, err) {
  83. markAsFailed(pkg)
  84. var anyFatal = failedDependency(pkg)
  85. if (anyFatal) {
  86. throw err
  87. } else {
  88. reportOptionalFailure(pkg, null, err)
  89. }
  90. }
  91. exports.doOne = doOne
  92. function doOne (cmd, staging, pkg, log, next) {
  93. validate('SSOOF', arguments)
  94. const prepped = prepareAction([cmd, pkg], staging, log)
  95. return withInit(actions[cmd], () => {
  96. return execAction(prepped)
  97. }).nodeify(next)
  98. }
  99. exports.doParallel = doParallel
  100. function doParallel (type, staging, actionsToRun, log, next) {
  101. validate('SSAOF', arguments)
  102. const acts = actionsToRun.reduce((acc, todo) => {
  103. if (todo[0] === type) {
  104. acc.push(prepareAction(todo, staging, log))
  105. }
  106. return acc
  107. }, [])
  108. log.silly('doParallel', type + ' ' + acts.length)
  109. time(log)
  110. if (!acts.length) { return next() }
  111. return withInit(actions[type], () => {
  112. return BB.map(acts, execAction, {
  113. concurrency: npm.limit.action
  114. })
  115. }).nodeify((err) => {
  116. log.finish()
  117. timeEnd(log)
  118. next(err)
  119. })
  120. }
  121. exports.doSerial = doSerial
  122. function doSerial (type, staging, actionsToRun, log, next) {
  123. validate('SSAOF', arguments)
  124. log.silly('doSerial', '%s %d', type, actionsToRun.length)
  125. runSerial(type, staging, actionsToRun, log, next)
  126. }
  127. exports.doReverseSerial = doReverseSerial
  128. function doReverseSerial (type, staging, actionsToRun, log, next) {
  129. validate('SSAOF', arguments)
  130. log.silly('doReverseSerial', '%s %d', type, actionsToRun.length)
  131. runSerial(type, staging, [].concat(actionsToRun).reverse(), log, next)
  132. }
  133. function runSerial (type, staging, actionsToRun, log, next) {
  134. const acts = actionsToRun.reduce((acc, todo) => {
  135. if (todo[0] === type) {
  136. acc.push(prepareAction(todo, staging, log))
  137. }
  138. return acc
  139. }, [])
  140. time(log)
  141. if (!acts.length) { return next() }
  142. return withInit(actions[type], () => {
  143. return BB.each(acts, execAction)
  144. }).nodeify((err) => {
  145. log.finish()
  146. timeEnd(log)
  147. next(err)
  148. })
  149. }
  150. function time (log) {
  151. process.emit('time', 'action:' + log.name)
  152. }
  153. function timeEnd (log) {
  154. process.emit('timeEnd', 'action:' + log.name)
  155. }
  156. function withInit (action, body) {
  157. return BB.using(
  158. action.init().disposer(() => action.teardown()),
  159. body
  160. )
  161. }
  162. function prepareAction (action, staging, log) {
  163. validate('ASO', arguments)
  164. validate('SO', action)
  165. var cmd = action[0]
  166. var pkg = action[1]
  167. if (!actions[cmd]) throw new Error('Unknown decomposed command "' + cmd + '" (is it new?)')
  168. return [actions[cmd], staging, pkg, log.newGroup(cmd + ':' + moduleName(pkg))]
  169. }
  170. function execAction (todo) {
  171. return todo[0].apply(null, todo.slice(1))
  172. }