concurrently
Advanced tools
Comparing version
@@ -86,3 +86,4 @@ #!/usr/bin/env node | ||
'- Available modifiers: reset, bold, dim, italic, underline, inverse, hidden, strikethrough\n' + | ||
'- Available colors: black, red, green, yellow, blue, magenta, cyan, white, gray\n' + | ||
'- Available colors: black, red, green, yellow, blue, magenta, cyan, white, gray \n' + | ||
'or any hex values for colors, eg #23de43\n' + | ||
'- Available background colors: bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite\n' + | ||
@@ -110,3 +111,5 @@ 'See https://www.npmjs.com/package/chalk for more information.', | ||
'restart-tries': { | ||
describe: 'How many times a process that died should restart.', | ||
describe: | ||
'How many times a process that died should restart.\n' + | ||
'Negative numbers will make the process restart forever.', | ||
default: defaults.restartTries, | ||
@@ -146,16 +149,9 @@ type: 'number' | ||
const prefixColors = args.prefixColors.split(','); | ||
const names = (args.names || '').split(args.nameSeparator); | ||
let lastColor; | ||
concurrently(args._.map((command, index) => { | ||
// Use documented behaviour of repeating last colour when specifying more commands than colours | ||
lastColor = prefixColors[index] || lastColor; | ||
return { | ||
command, | ||
prefixColor: lastColor, | ||
name: names[index] | ||
}; | ||
}), { | ||
inputStream: args.handleInput && process.stdin, | ||
concurrently(args._.map((command, index) => ({ | ||
command, | ||
name: names[index] | ||
})), { | ||
handleInput: args.handleInput, | ||
defaultInputTarget: args.defaultInputTarget, | ||
@@ -168,2 +164,3 @@ killOthers: args.killOthers | ||
prefix: args.prefix, | ||
prefixColors: args.prefixColors.split(','), | ||
prefixLength: args.prefixLength, | ||
@@ -173,3 +170,3 @@ restartDelay: args.restartAfter, | ||
successCondition: args.success, | ||
timestampFormat: args.timestampFormat | ||
timestampFormat: args.timestampFormat, | ||
}).then( | ||
@@ -176,0 +173,0 @@ () => process.exit(0), |
@@ -42,2 +42,2 @@ Examples: | ||
For more details, visit https://github.com/kimmobrunfeldt/concurrently | ||
For more details, visit https://github.com/open-cli-tools/concurrently |
@@ -12,3 +12,3 @@ const InputHandler = require('./src/flow-control/input-handler'); | ||
module.exports = (commands, options = {}) => { | ||
module.exports = exports = (commands, options = {}) => { | ||
const logger = new Logger({ | ||
@@ -26,2 +26,3 @@ outputStream: options.outputStream || process.stdout, | ||
successCondition: options.successCondition, | ||
cwd: options.cwd, | ||
controllers: [ | ||
@@ -34,3 +35,4 @@ new LogError({ logger }), | ||
defaultInputTarget: options.defaultInputTarget, | ||
inputStream: options.inputStream, | ||
inputStream: options.inputStream || (options.handleInput && process.stdin), | ||
pauseInputStreamOnFinish: options.pauseInputStreamOnFinish, | ||
}), | ||
@@ -47,3 +49,4 @@ new KillOnSignal({ process }), | ||
}) | ||
] | ||
], | ||
prefixColors: options.prefixColors || [] | ||
}); | ||
@@ -50,0 +53,0 @@ }; |
{ | ||
"name": "concurrently", | ||
"version": "5.3.0", | ||
"version": "6.3.0", | ||
"description": "Run commands concurrently", | ||
@@ -10,3 +10,3 @@ "main": "index.js", | ||
"engines": { | ||
"node": ">=6.0.0" | ||
"node": ">=10.0.0" | ||
}, | ||
@@ -20,3 +20,3 @@ "scripts": { | ||
"type": "git", | ||
"url": "https://github.com/kimmobrunfeldt/concurrently.git" | ||
"url": "https://github.com/open-cli-tools/concurrently.git" | ||
}, | ||
@@ -34,16 +34,15 @@ "keywords": [ | ||
"dependencies": { | ||
"chalk": "^2.4.2", | ||
"date-fns": "^2.0.1", | ||
"lodash": "^4.17.15", | ||
"read-pkg": "^4.0.1", | ||
"rxjs": "^6.5.2", | ||
"chalk": "^4.1.0", | ||
"date-fns": "^2.16.1", | ||
"lodash": "^4.17.21", | ||
"rxjs": "^6.6.3", | ||
"spawn-command": "^0.0.2-1", | ||
"supports-color": "^6.1.0", | ||
"supports-color": "^8.1.0", | ||
"tree-kill": "^1.2.2", | ||
"yargs": "^13.3.0" | ||
"yargs": "^16.2.0" | ||
}, | ||
"devDependencies": { | ||
"coveralls": "^3.0.4", | ||
"eslint": "^5.16.0", | ||
"jest": "^24.8.0", | ||
"coveralls": "^3.1.0", | ||
"eslint": "^7.17.0", | ||
"jest": "^26.6.3", | ||
"jest-create-mock-instance": "^1.1.0" | ||
@@ -50,0 +49,0 @@ }, |
# Concurrently | ||
[](https://travis-ci.org/kimmobrunfeldt/concurrently) [](https://ci.appveyor.com/project/kimmobrunfeldt/concurrently) *master branch status* | ||
[](https://github.com/open-cli-tools/concurrently/actions?workflow=Tests) | ||
[](https://coveralls.io/github/open-cli-tools/concurrently?branch=master) | ||
@@ -13,11 +14,13 @@ [](https://www.npmjs.com/package/concurrently) | ||
**Table of contents** | ||
- [Why](#why) | ||
- [Install](#install) | ||
- [Usage](#usage) | ||
- [Programmatic Usage](#programmatic-usage) | ||
- [FAQ](#faq) | ||
- [Concurrently](#concurrently) | ||
- [Why](#why) | ||
- [Install](#install) | ||
- [Usage](#usage) | ||
- [Programmatic Usage](#programmatic-usage) | ||
- [`concurrently(commands[, options])`](#concurrentlycommands-options) | ||
- [FAQ](#faq) | ||
## Why | ||
I like [task automation with npm](http://substack.net/task_automation_with_npm_run) | ||
I like [task automation with npm](https://github.com/substack/blog/blob/master/npm_run.markdown) | ||
but the usual way to run multiple commands concurrently is | ||
@@ -144,7 +147,8 @@ `npm run watch-js & npm run watch-css`. That's fine but it's hard to keep | ||
- Available colors: black, red, green, yellow, blue, | ||
magenta, cyan, white, gray | ||
magenta, cyan, white, gray, or any hex values for | ||
colors, eg #23de43 | ||
- Available background colors: bgBlack, bgRed, | ||
bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite | ||
See https://www.npmjs.com/package/chalk for more | ||
information. [string] [default: "gray.dim"] | ||
information. [string] [default: "reset"] | ||
-l, --prefix-length Limit how many characters of the command is displayed | ||
@@ -171,5 +175,6 @@ in prefix. The option can be used to shorten the | ||
Restarting | ||
--restart-tries How many times a process that died should restart. | ||
--restart-tries How many times a process that died should restart. | ||
Negative numbers will make the process restart forever. | ||
[number] [default: 0] | ||
--restart-after Delay time to respawn the process, in milliseconds. | ||
--restart-after Delay time to respawn the process, in milliseconds. | ||
[number] [default: 0] | ||
@@ -223,3 +228,3 @@ | ||
For more details, visit https://github.com/kimmobrunfeldt/concurrently | ||
For more details, visit https://github.com/open-cli-tools/concurrently | ||
``` | ||
@@ -231,9 +236,15 @@ | ||
### `concurrently(commands[, options])` | ||
- `commands`: an array of either strings (containing the commands to run) or objects | ||
with the shape `{ command, name, prefixColor, env }`. | ||
with the shape `{ command, name, prefixColor, env, cwd }`. | ||
- `options` (optional): an object containing any of the below: | ||
- `cwd`: the working directory to be used by all commands. Can be overriden per command. | ||
Default: `process.cwd()`. | ||
- `defaultInputTarget`: the default input target when reading from `inputStream`. | ||
Default: `0`. | ||
- `handleInput`: when `true`, reads input from `process.stdin`. | ||
- `inputStream`: a [`Readable` stream](https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_readable_streams) | ||
to read the input from, eg `process.stdin`. | ||
to read the input from. Should only be used in the rare instance you would like to stream anything other than `process.stdin`. Overrides `handleInput`. | ||
- `pauseInputStreamOnFinish`: by default, pauses the input stream (`process.stdin` when `handleInput` is enabled, or `inputStream` if provided) when all of the processes have finished. If you need to read from the input stream after `concurrently` has finished, set this to `false`. ([#252](https://github.com/kimmobrunfeldt/concurrently/issues/252)). | ||
- `killOthers`: an array of exitting conditions that will cause a process to kill others. | ||
@@ -247,2 +258,5 @@ Can contain any of `success` or `failure`. | ||
Default: the name of the process, or its index if no name is set. | ||
- `prefixColors`: a list of colors as supported by [chalk](https://www.npmjs.com/package/chalk). | ||
If concurrently would run more commands than there are colors, the last color is repeated. | ||
Prefix colors specified per-command take precedence over this list. | ||
- `prefixLength`: how many characters to show when prefixing with `command`. Default: `10` | ||
@@ -261,5 +275,5 @@ - `raw`: whether raw mode should be used, meaning strictly process output will | ||
> or rejects, containing an array of objects with information for each command that has been run, in the order | ||
> that the commands terminated. The objects have the shape `{ command, index, exitCode }`, where `command` is the object | ||
> passed in the `commands` array and `index` its index there. Default values (empty strings or objects) are returned for | ||
> the fields that were not specified. | ||
> that the commands terminated. The objects have the shape `{ command, index, exitCode, killed }`, where `command` is the object | ||
> passed in the `commands` array, `index` its index there and `killed` indicates if the process was killed as a result of | ||
> `killOthers`. Default values (empty strings or objects) are returned for the fields that were not specified. | ||
@@ -273,3 +287,4 @@ Example: | ||
{ command: 'nodemon', name: 'server' }, | ||
{ command: 'deploy', name: 'deploy', env: { PUBLIC_KEY: '...' } } | ||
{ command: 'deploy', name: 'deploy', env: { PUBLIC_KEY: '...' } }, | ||
{ command: 'watch', name: 'watch', cwd: path.resolve(__dirname, 'scripts/watchers')} | ||
], { | ||
@@ -279,2 +294,3 @@ prefix: 'name', | ||
restartTries: 3, | ||
cwd: path.resolve(__dirname, 'scripts'), | ||
}).then(success, failure); | ||
@@ -281,0 +297,0 @@ ``` |
const _ = require('lodash'); | ||
const readPkg = require('read-pkg'); | ||
const fs = require('fs'); | ||
module.exports = class ExpandNpmWildcard { | ||
constructor(readPackage = readPkg.sync) { | ||
static readPackage() { | ||
try { | ||
const json = fs.readFileSync('package.json', { encoding: 'utf-8' }); | ||
return JSON.parse(json); | ||
} catch (e) { | ||
return {}; | ||
} | ||
} | ||
constructor(readPackage = ExpandNpmWildcard.readPackage) { | ||
this.readPackage = readPackage; | ||
@@ -7,0 +16,0 @@ } |
@@ -17,2 +17,3 @@ const Rx = require('rxjs'); | ||
this.spawnOpts = spawnOpts; | ||
this.killed = false; | ||
@@ -45,2 +46,3 @@ this.error = new Rx.Subject(); | ||
exitCode: exitCode === null ? signal : exitCode, | ||
killed: this.killed, | ||
}); | ||
@@ -55,2 +57,3 @@ }); | ||
if (this.killable) { | ||
this.killed = true; | ||
this.killProcess(this.pid, code); | ||
@@ -57,0 +60,0 @@ } |
const Rx = require('rxjs'); | ||
const { bufferCount, map, switchMap, take } = require('rxjs/operators'); | ||
const { bufferCount, switchMap, take } = require('rxjs/operators'); | ||
@@ -4,0 +4,0 @@ module.exports = class CompletionListener { |
@@ -19,3 +19,4 @@ const assert = require('assert'); | ||
raw: false, | ||
controllers: [] | ||
controllers: [], | ||
cwd: undefined, | ||
}; | ||
@@ -35,19 +36,36 @@ | ||
let lastColor = ''; | ||
commands = _(commands) | ||
.map(mapToCommandInfo) | ||
.flatMap(command => parseCommand(command, commandParsers)) | ||
.map((command, index) => new Command( | ||
Object.assign({ | ||
index, | ||
spawnOpts: getSpawnOpts({ raw: options.raw, env: command.env }), | ||
killProcess: options.kill, | ||
spawn: options.spawn, | ||
}, command) | ||
)) | ||
.map((command, index) => { | ||
// Use documented behaviour of repeating last color when specifying more commands than colors | ||
lastColor = options.prefixColors && options.prefixColors[index] || lastColor; | ||
return new Command( | ||
Object.assign({ | ||
index, | ||
spawnOpts: getSpawnOpts({ | ||
raw: options.raw, | ||
env: command.env, | ||
cwd: command.cwd || options.cwd, | ||
}), | ||
prefixColor: lastColor, | ||
killProcess: options.kill, | ||
spawn: options.spawn, | ||
}, command) | ||
); | ||
}) | ||
.value(); | ||
commands = options.controllers.reduce( | ||
(prevCommands, controller) => controller.handle(prevCommands), | ||
commands | ||
const handleResult = options.controllers.reduce( | ||
({ commands: prevCommands, onFinishCallbacks }, controller) => { | ||
const { commands, onFinish } = controller.handle(prevCommands); | ||
return { | ||
commands, | ||
onFinishCallbacks: _.concat(onFinishCallbacks, onFinish ? [onFinish] : []) | ||
}; | ||
}, | ||
{ commands, onFinishCallbacks: [] } | ||
); | ||
commands = handleResult.commands; | ||
@@ -60,12 +78,18 @@ const commandsLeft = commands.slice(); | ||
return new CompletionListener({ successCondition: options.successCondition }).listen(commands); | ||
return new CompletionListener({ successCondition: options.successCondition }) | ||
.listen(commands) | ||
.finally(() => { | ||
handleResult.onFinishCallbacks.forEach((onFinish) => onFinish()); | ||
}); | ||
}; | ||
function mapToCommandInfo(command) { | ||
return { | ||
return Object.assign({ | ||
command: command.command || command, | ||
name: command.name || '', | ||
prefixColor: command.prefixColor || '', | ||
env: command.env || {}, | ||
}; | ||
cwd: command.cwd || '', | ||
}, command.prefixColor ? { | ||
prefixColor: command.prefixColor, | ||
} : {}); | ||
} | ||
@@ -72,0 +96,0 @@ |
@@ -17,3 +17,3 @@ /** | ||
// Refer to https://www.npmjs.com/package/chalk | ||
prefixColors: 'gray.dim', | ||
prefixColors: 'reset', | ||
// How many bytes we'll show on the command prefix | ||
@@ -29,3 +29,5 @@ prefixLength: 10, | ||
// Refer to https://date-fns.org/v2.0.1/docs/format | ||
timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS' | ||
timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS', | ||
// Current working dir passed as option to spawn command. Default: process.cwd() | ||
cwd: undefined | ||
}; |
@@ -5,8 +5,11 @@ const Rx = require('rxjs'); | ||
const defaults = require('../defaults'); | ||
const BaseHandler = require('./base-handler'); | ||
module.exports = class InputHandler { | ||
constructor({ defaultInputTarget, inputStream, logger }) { | ||
module.exports = class InputHandler extends BaseHandler { | ||
constructor({ defaultInputTarget, inputStream, pauseInputStreamOnFinish, logger }) { | ||
super({ logger }); | ||
this.defaultInputTarget = defaultInputTarget || defaults.defaultInputTarget; | ||
this.inputStream = inputStream; | ||
this.logger = logger; | ||
this.pauseInputStreamOnFinish = pauseInputStreamOnFinish !== false; | ||
} | ||
@@ -16,3 +19,3 @@ | ||
if (!this.inputStream) { | ||
return commands; | ||
return { commands }; | ||
} | ||
@@ -23,3 +26,3 @@ | ||
.subscribe(data => { | ||
let [targetId, input] = data.split(':', 2); | ||
let [targetId, input] = data.split(/:(.+)/); | ||
targetId = input ? targetId : this.defaultInputTarget; | ||
@@ -40,4 +43,12 @@ input = input || data; | ||
return commands; | ||
return { | ||
commands, | ||
onFinish: () => { | ||
if (this.pauseInputStreamOnFinish) { | ||
// https://github.com/kimmobrunfeldt/concurrently/issues/252 | ||
this.inputStream.pause(); | ||
} | ||
}, | ||
}; | ||
} | ||
}; |
const { map } = require('rxjs/operators'); | ||
const BaseHandler = require('./base-handler'); | ||
module.exports = class KillOnSignal { | ||
module.exports = class KillOnSignal extends BaseHandler { | ||
constructor({ process }) { | ||
super(); | ||
this.process = process; | ||
@@ -18,14 +21,16 @@ } | ||
return commands.map(command => { | ||
const closeStream = command.close.pipe(map(exitInfo => { | ||
const exitCode = caughtSignal === 'SIGINT' ? 0 : exitInfo.exitCode; | ||
return Object.assign({}, exitInfo, { exitCode }); | ||
})); | ||
return new Proxy(command, { | ||
get(target, prop) { | ||
return prop === 'close' ? closeStream : target[prop]; | ||
} | ||
}); | ||
}); | ||
return { | ||
commands: commands.map(command => { | ||
const closeStream = command.close.pipe(map(exitInfo => { | ||
const exitCode = caughtSignal === 'SIGINT' ? 0 : exitInfo.exitCode; | ||
return Object.assign({}, exitInfo, { exitCode }); | ||
})); | ||
return new Proxy(command, { | ||
get(target, prop) { | ||
return prop === 'close' ? closeStream : target[prop]; | ||
} | ||
}); | ||
}) | ||
}; | ||
} | ||
}; |
const _ = require('lodash'); | ||
const { filter, map } = require('rxjs/operators'); | ||
module.exports = class KillOthers { | ||
const BaseHandler = require('./base-handler'); | ||
module.exports = class KillOthers extends BaseHandler { | ||
constructor({ logger, conditions }) { | ||
this.logger = logger; | ||
super({ logger }); | ||
this.conditions = _.castArray(conditions); | ||
@@ -17,3 +20,3 @@ } | ||
if (!conditions.length) { | ||
return commands; | ||
return { commands }; | ||
} | ||
@@ -34,4 +37,4 @@ | ||
return commands; | ||
return { commands }; | ||
} | ||
}; |
const { of } = require('rxjs'); | ||
module.exports = class LogExit { | ||
constructor({ logger }) { | ||
this.logger = logger; | ||
} | ||
const BaseHandler = require('./base-handler'); | ||
module.exports = class LogExit extends BaseHandler { | ||
handle(commands) { | ||
@@ -18,4 +16,4 @@ commands.forEach(command => command.error.subscribe(event => { | ||
return commands; | ||
return { commands }; | ||
} | ||
}; |
@@ -1,6 +0,4 @@ | ||
module.exports = class LogExit { | ||
constructor({ logger }) { | ||
this.logger = logger; | ||
} | ||
const BaseHandler = require('./base-handler'); | ||
module.exports = class LogExit extends BaseHandler { | ||
handle(commands) { | ||
@@ -11,4 +9,4 @@ commands.forEach(command => command.close.subscribe(({ exitCode }) => { | ||
return commands; | ||
return { commands }; | ||
} | ||
}; |
@@ -1,6 +0,4 @@ | ||
module.exports = class LogOutput { | ||
constructor({ logger }) { | ||
this.logger = logger; | ||
} | ||
const BaseHandler = require('./base-handler'); | ||
module.exports = class LogOutput extends BaseHandler { | ||
handle(commands) { | ||
@@ -12,4 +10,4 @@ commands.forEach(command => { | ||
return commands; | ||
return { commands }; | ||
} | ||
}; |
@@ -5,8 +5,11 @@ const Rx = require('rxjs'); | ||
const defaults = require('../defaults'); | ||
const BaseHandler = require('./base-handler'); | ||
module.exports = class RestartProcess { | ||
module.exports = class RestartProcess extends BaseHandler { | ||
constructor({ delay, tries, logger, scheduler }) { | ||
super({ logger }); | ||
this.delay = +delay || defaults.restartDelay; | ||
this.tries = +tries || defaults.restartTries; | ||
this.logger = logger; | ||
this.tries = this.tries < 0 ? Infinity : this.tries; | ||
this.scheduler = scheduler; | ||
@@ -17,3 +20,3 @@ } | ||
if (this.tries === 0) { | ||
return commands; | ||
return { commands }; | ||
} | ||
@@ -40,15 +43,17 @@ | ||
return commands.map(command => { | ||
const closeStream = command.close.pipe(filter(({ exitCode }, emission) => { | ||
// We let all success codes pass, and failures only after restarting won't happen again | ||
return exitCode === 0 || emission >= this.tries; | ||
})); | ||
return { | ||
commands: commands.map(command => { | ||
const closeStream = command.close.pipe(filter(({ exitCode }, emission) => { | ||
// We let all success codes pass, and failures only after restarting won't happen again | ||
return exitCode === 0 || emission >= this.tries; | ||
})); | ||
return new Proxy(command, { | ||
get(target, prop) { | ||
return prop === 'close' ? closeStream : target[prop]; | ||
} | ||
}); | ||
}); | ||
return new Proxy(command, { | ||
get(target, prop) { | ||
return prop === 'close' ? closeStream : target[prop]; | ||
} | ||
}); | ||
}) | ||
}; | ||
} | ||
}; |
@@ -5,7 +5,10 @@ const supportsColor = require('supports-color'); | ||
colorSupport = supportsColor.stdout, | ||
cwd, | ||
process = global.process, | ||
raw = false, | ||
env = {} | ||
env = {}, | ||
}) => Object.assign( | ||
{}, | ||
{ | ||
cwd: cwd || process.cwd(), | ||
}, | ||
raw && { stdio: 'inherit' }, | ||
@@ -12,0 +15,0 @@ /^win/.test(process.platform) && { detached: false }, |
@@ -60,3 +60,9 @@ const chalk = require('chalk'); | ||
colorText(command, text) { | ||
const color = _.get(chalk, command.prefixColor, chalk.gray.dim); | ||
let color; | ||
if (command.prefixColor && command.prefixColor.startsWith('#')) { | ||
color = chalk.hex(command.prefixColor); | ||
} else { | ||
const defaultColor = _.get(chalk, defaults.prefixColors, chalk.reset); | ||
color = _.get(chalk, command.prefixColor, defaultColor); | ||
} | ||
return color(text); | ||
@@ -70,3 +76,3 @@ } | ||
this.logCommandText(chalk.gray.dim(text) + '\n', command); | ||
this.logCommandText(chalk.reset(text) + '\n', command); | ||
} | ||
@@ -84,3 +90,3 @@ | ||
this.log(chalk.gray.dim('-->') + ' ', chalk.gray.dim(text) + '\n'); | ||
this.log(chalk.reset('-->') + ' ', chalk.reset(text) + '\n'); | ||
} | ||
@@ -87,0 +93,0 @@ |
103824
138.25%8
-11.11%39
69.57%2170
191.28%308
5.48%6
100%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated
Updated