123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- /**
- * @module run-tasks-in-parallel
- * @author Toru Nagashima
- * @copyright 2015 Toru Nagashima. All rights reserved.
- * See LICENSE file in root directory for full license.
- */
- 'use strict'
- // ------------------------------------------------------------------------------
- // Requirements
- // ------------------------------------------------------------------------------
- const MemoryStream = require('memorystream')
- const NpmRunAllError = require('./npm-run-all-error')
- const runTask = require('./run-task')
- // ------------------------------------------------------------------------------
- // Helpers
- // ------------------------------------------------------------------------------
- /**
- * Remove the given value from the array.
- * @template T
- * @param {T[]} array - The array to remove.
- * @param {T} x - The item to be removed.
- * @returns {void}
- */
- function remove (array, x) {
- const index = array.indexOf(x)
- if (index !== -1) {
- array.splice(index, 1)
- }
- }
- const signals = {
- SIGABRT: 6,
- SIGALRM: 14,
- SIGBUS: 10,
- SIGCHLD: 20,
- SIGCONT: 19,
- SIGFPE: 8,
- SIGHUP: 1,
- SIGILL: 4,
- SIGINT: 2,
- SIGKILL: 9,
- SIGPIPE: 13,
- SIGQUIT: 3,
- SIGSEGV: 11,
- SIGSTOP: 17,
- SIGTERM: 15,
- SIGTRAP: 5,
- SIGTSTP: 18,
- SIGTTIN: 21,
- SIGTTOU: 22,
- SIGUSR1: 30,
- SIGUSR2: 31,
- }
- /**
- * Converts a signal name to a number.
- * @param {string} signal - the signal name to convert into a number
- * @returns {number} - the return code for the signal
- */
- function convert (signal) {
- return signals[signal] || 0
- }
- // ------------------------------------------------------------------------------
- // Public Interface
- // ------------------------------------------------------------------------------
- /**
- * Run npm-scripts of given names in parallel.
- *
- * If a npm-script exited with a non-zero code, this aborts other all npm-scripts.
- *
- * @param {string} tasks - A list of npm-script name to run in parallel.
- * @param {object} options - An option object.
- * @returns {Promise} A promise object which becomes fullfilled when all npm-scripts are completed.
- * @private
- */
- module.exports = function runTasks (tasks, options) {
- return new Promise((resolve, reject) => {
- if (tasks.length === 0) {
- resolve([])
- return
- }
- const results = tasks.map(task => ({ name: task, code: undefined }))
- const queue = tasks.map((task, index) => ({ name: task, index }))
- const promises = []
- let error = null
- let aborted = false
- /**
- * Done.
- * @returns {void}
- */
- function done () {
- if (error == null) {
- resolve(results)
- } else {
- reject(error)
- }
- }
- /**
- * Aborts all tasks.
- * @returns {void}
- */
- function abort () {
- if (aborted) {
- return
- }
- aborted = true
- if (promises.length === 0) {
- done()
- } else {
- for (const p of promises) {
- p.abort()
- }
- Promise.all(promises).then(done, reject)
- }
- }
- /**
- * Runs a next task.
- * @returns {void}
- */
- function next () {
- if (aborted) {
- return
- }
- if (queue.length === 0) {
- if (promises.length === 0) {
- done()
- }
- return
- }
- const originalOutputStream = options.stdout
- const optionsClone = Object.assign({}, options)
- const writer = new MemoryStream(null, {
- readable: false,
- })
- if (options.aggregateOutput) {
- optionsClone.stdout = writer
- }
- const task = queue.shift()
- const promise = runTask(task.name, optionsClone)
- promises.push(promise)
- promise.then(
- (result) => {
- remove(promises, promise)
- if (aborted) {
- return
- }
- if (options.aggregateOutput) {
- originalOutputStream.write(writer.toString())
- }
- // Check if the task failed as a result of a signal, and
- // amend the exit code as a result.
- if (result.code === null && result.signal !== null) {
- // An exit caused by a signal must return a status code
- // of 128 plus the value of the signal code.
- // Ref: https://nodejs.org/api/process.html#process_exit_codes
- result.code = 128 + convert(result.signal)
- }
- // Save the result.
- results[task.index].code = result.code
- // Aborts all tasks if it's an error.
- if (result.code) {
- error = new NpmRunAllError(result, results)
- if (!options.continueOnError) {
- abort()
- return
- }
- }
- // Aborts all tasks if options.race is true.
- if (options.race && !result.code) {
- abort()
- return
- }
- // Call the next task.
- next()
- },
- (thisError) => {
- remove(promises, promise)
- if (!options.continueOnError || options.race) {
- error = thisError
- abort()
- return
- }
- next()
- }
- )
- }
- const max = options.maxParallel
- const end = (typeof max === 'number' && max > 0)
- ? Math.min(tasks.length, max)
- : tasks.length
- for (let i = 0; i < end; ++i) {
- next()
- }
- })
- }
|