diff --git a/bin/common/parse-cli-args.js b/bin/common/parse-cli-args.js index b3013dd..f6e4408 100644 --- a/bin/common/parse-cli-args.js +++ b/bin/common/parse-cli-args.js @@ -174,6 +174,10 @@ function parseCLIArgsCore(set, args) { // eslint-disable-line complexity addGroup(set.groups) break + case "--aggregate-output": + set.aggregateOutput = true + break + case "-p": case "--parallel": if (set.singleMode) { diff --git a/bin/run-p/help.js b/bin/run-p/help.js index 6c5f70d..f7b4cb3 100644 --- a/bin/run-p/help.js +++ b/bin/run-p/help.js @@ -33,6 +33,8 @@ Options: threw error(s). --max-parallel - Set the maximum number of parallelism. Default is unlimited. + --aggregate-output - Avoid interleaving output by delaying printing of + each command's output until it has finished. --npm-path - - - Set the path to npm. Default is the value of environment variable npm_execpath. If the variable is not defined, then it's "npm." diff --git a/bin/run-p/main.js b/bin/run-p/main.js index 46b6f18..be10a7a 100644 --- a/bin/run-p/main.js +++ b/bin/run-p/main.js @@ -52,6 +52,7 @@ module.exports = function npmRunAll(args, stdout, stderr) { arguments: argv.rest, race: argv.race, npmPath: argv.npmPath, + aggregateOutput: argv.aggregateOutput, } ) diff --git a/docs/run-p.md b/docs/run-p.md index 3091394..46e92e9 100644 --- a/docs/run-p.md +++ b/docs/run-p.md @@ -37,6 +37,8 @@ Options: threw error(s). --max-parallel - Set the maximum number of parallelism. Default is unlimited. + --aggregate-output - Avoid interleaving output by delaying printing of + each command's output until it has finished. --npm-path - - - Set the path to npm. Default is the value of environment variable npm_execpath. If the variable is not defined, then it's "npm." diff --git a/lib/index.js b/lib/index.js index 04b4d89..7c569d6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -223,6 +223,7 @@ module.exports = function npmRunAll(patternOrPatterns, options) { const printName = Boolean(options && options.printName) const race = Boolean(options && options.race) const maxParallel = parallel ? ((options && options.maxParallel) || 0) : 1 + const aggregateOutput = Boolean(options && options.aggregateOutput) const npmPath = options && options.npmPath try { const patterns = parsePatterns(patternOrPatterns, args) @@ -273,6 +274,7 @@ module.exports = function npmRunAll(patternOrPatterns, options) { race, maxParallel, npmPath, + aggregateOutput, }) }) } diff --git a/lib/run-tasks.js b/lib/run-tasks.js index 72a93f3..3ede5e3 100644 --- a/lib/run-tasks.js +++ b/lib/run-tasks.js @@ -10,6 +10,7 @@ // Requirements //------------------------------------------------------------------------------ +const streams = require("memory-streams") const NpmRunAllError = require("./npm-run-all-error") const runTask = require("./run-task") @@ -104,8 +105,17 @@ module.exports = function runTasks(tasks, options) { } return } + + const originalOutputStream = options.stdout + const optionsClone = Object.assign({}, options) + const writer = new streams.WritableStream() + + if (options.aggregateOutput) { + optionsClone.stdout = writer + } + const task = queue.shift() - const promise = runTask(task.name, options) + const promise = runTask(task.name, optionsClone) promises.push(promise) promise.then( @@ -115,6 +125,10 @@ module.exports = function runTasks(tasks, options) { return } + if (options.aggregateOutput) { + originalOutputStream.write(writer.toString()) + } + // Save the result. results[task.index].code = result.code diff --git a/package.json b/package.json index 5278b9a..098875b 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "dependencies": { "chalk": "^1.1.3", "cross-spawn": "^5.0.1", + "memory-streams": "^0.1.2", "minimatch": "^3.0.2", "ps-tree": "^1.0.1", "read-pkg": "^2.0.0", diff --git a/test-workspace/package.json b/test-workspace/package.json index 0ef009d..4bc4724 100644 --- a/test-workspace/package.json +++ b/test-workspace/package.json @@ -36,7 +36,8 @@ "test-task:dump": "node tasks/dump.js", "test-task:nest-append:npm-run-all": "node ../bin/npm-run-all/index.js test-task:append", "test-task:nest-append:run-s": "node ../bin/run-s/index.js test-task:append", - "test-task:nest-append:run-p": "node ../bin/run-p/index.js test-task:append" + "test-task:nest-append:run-p": "node ../bin/run-p/index.js test-task:append", + "test-task:delayed": "node tasks/output-with-delay.js" }, "repository": { "type": "git", diff --git a/test-workspace/tasks/output-with-delay.js b/test-workspace/tasks/output-with-delay.js new file mode 100644 index 0000000..8041f84 --- /dev/null +++ b/test-workspace/tasks/output-with-delay.js @@ -0,0 +1,7 @@ +"use strict" + +const text = process.argv[2] +const timeout = process.argv[3] + +process.stdout.write(`[${text}]`) +setTimeout(() => process.stdout.write(`__[${text}]\n`), timeout) diff --git a/test/aggregate-output.js b/test/aggregate-output.js new file mode 100644 index 0000000..f0fa931 --- /dev/null +++ b/test/aggregate-output.js @@ -0,0 +1,93 @@ + +/** + * @author Toru Nagashima + * @copyright 2016 Toru Nagashima. All rights reserved. + * See LICENSE file in root directory for full license. + */ +"use strict" + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("power-assert") +const nodeApi = require("../lib") +const BufferStream = require("./lib/buffer-stream") +const util = require("./lib/util") +const runAll = util.runAll +const runPar = util.runPar +const runSeq = util.runSeq + +//------------------------------------------------------------------------------ +// Test +//------------------------------------------------------------------------------ + +describe("[aggregated-output] npm-run-all", () => { + before(() => process.chdir("test-workspace")) + after(() => process.chdir("..")) + + /** + * create expected text + * @param {string} term the term to use when creating a line + * @returns {string} the complete line + */ + function createExpectedOutput(term) { + return `[${term}]__[${term}]` + } + + describe("should not intermingle output of various commands", () => { + const EXPECTED_SERIALIZED_TEXT = [ + createExpectedOutput("first"), + createExpectedOutput("second"), + `${createExpectedOutput("third")}\n`, + ].join("\n") + + const EXPECTED_PARALLELIZED_TEXT = [ + createExpectedOutput("second"), + createExpectedOutput("third"), + `${createExpectedOutput("first")}\n`, + ].join("\n") + + let stdout = null + + beforeEach(() => { + stdout = new BufferStream() + }) + + it("Node API", () => nodeApi( + ["test-task:delayed first 300", "test-task:delayed second 100", "test-task:delayed third 200"], + {stdout, silent: true, aggregateOutput: true} + ) + .then(() => { + assert.equal(stdout.value, EXPECTED_SERIALIZED_TEXT) + })) + + it("npm-run-all command", () => runAll( + ["test-task:delayed first 300", "test-task:delayed second 100", "test-task:delayed third 200", "--silent", "--aggregate-output"], + stdout + ) + .then(() => { + assert.equal(stdout.value, EXPECTED_SERIALIZED_TEXT) + })) + + it("run-s command", () => runSeq( + ["test-task:delayed first 300", "test-task:delayed second 100", "test-task:delayed third 200", "--silent", "--aggregate-output"], + stdout + ) + .then(() => { + assert.equal(stdout.value, EXPECTED_SERIALIZED_TEXT) + })) + + it("run-p command", () => runPar([ + "test-task:delayed first 3000", + "test-task:delayed second 1000", + "test-task:delayed third 2000", + "--silent", "--aggregate-output"], + stdout + ) + .then(() => { + assert.equal(stdout.value, EXPECTED_PARALLELIZED_TEXT) + })) + }) +}) +