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