concurrently 5.0.1 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -113,18 +113,21 @@ Help:
113
113
  concurrently [options] <command ...>
114
114
 
115
115
  General
116
- -n, --names List of custom names to be used in prefix template.
117
- Example names: "main,browser,server" [string]
118
- --name-separator The character to split <names> on. Example usage:
119
- concurrently -n "styles|scripts|server" --name-separator "|"
120
- [default: ","]
121
- -r, --raw Output only raw output of processes, disables prettifying
122
- and concurrently coloring. [boolean]
123
- -s, --success Return exit code of zero or one based on the success or
124
- failure of the "first" child to terminate, the "last child",
125
- or succeed only if "all" child processes succeed.
116
+ -m, --max-processes How many processes should run at once.
117
+ New processes only spawn after all restart tries of a
118
+ process. [number]
119
+ -n, --names List of custom names to be used in prefix template.
120
+ Example names: "main,browser,server" [string]
121
+ --name-separator The character to split <names> on. Example usage:
122
+ concurrently -n "styles|scripts|server" --name-separator
123
+ "|" [default: ","]
124
+ -r, --raw Output only raw output of processes, disables prettifying
125
+ and concurrently coloring. [boolean]
126
+ -s, --success Return exit code of zero or one based on the success or
127
+ failure of the "first" child to terminate, the "last
128
+ child", or succeed only if "all" child processes succeed.
126
129
  [choices: "first", "last", "all"] [default: "all"]
127
- --no-color Disables colors from logging [boolean]
130
+ --no-color Disables colors from logging [boolean]
128
131
 
129
132
  Prefix styling
130
133
  -p, --prefix Prefix used in logging for each process.
@@ -222,7 +225,7 @@ concurrently can be used programmatically by using the API documented below:
222
225
 
223
226
  ### `concurrently(commands[, options])`
224
227
  - `commands`: an array of either strings (containing the commands to run) or objects
225
- with the shape `{ command, name, prefixColor }`.
228
+ with the shape `{ command, name, prefixColor, env }`.
226
229
  - `options` (optional): an object containing any of the below:
227
230
  - `defaultInputTarget`: the default input target when reading from `inputStream`.
228
231
  Default: `0`.
@@ -230,6 +233,7 @@ concurrently can be used programmatically by using the API documented below:
230
233
  to read the input from, eg `process.stdin`.
231
234
  - `killOthers`: an array of exitting conditions that will cause a process to kill others.
232
235
  Can contain any of `success` or `failure`.
236
+ - `maxProcesses`: how many processes should run at once.
233
237
  - `outputStream`: a [`Writable` stream](https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_writable_streams)
234
238
  to write logs to. Default: `process.stdout`.
235
239
  - `prefix`: the prefix type to use when logging processes output.
@@ -247,7 +251,10 @@ concurrently can be used programmatically by using the API documented below:
247
251
  to use when prefixing with `time`. Default: `yyyy-MM-dd HH:mm:ss.ZZZ`
248
252
 
249
253
  > Returns: a `Promise` that resolves if the run was successful (according to `successCondition` option),
250
- > or rejects otherwise.
254
+ > or rejects, containing an array of objects with information for each command that has been run, in the order
255
+ > that the commands terminated. The objects have the shape `{ command, index, exitCode }`, where `command` is the object
256
+ > passed in the `commands` array and `index` its index there. Default values (empty strings or objects) are returned for
257
+ > the fields that were not specified.
251
258
 
252
259
  Example:
253
260
 
@@ -255,7 +262,8 @@ Example:
255
262
  const concurrently = require('concurrently');
256
263
  concurrently([
257
264
  'npm:watch-*',
258
- { command: 'nodemon', name: 'server' }
265
+ { command: 'nodemon', name: 'server' },
266
+ { command: 'deploy', name: 'deploy', env: { PUBLIC_KEY: '...' } }
259
267
  ], {
260
268
  prefix: 'name',
261
269
  killOthers: ['failure', 'success'],
@@ -278,6 +286,6 @@ concurrently([
278
286
  So *null* means the process didn't terminate normally. This will make **concurrent**
279
287
  to return non-zero exit code too.
280
288
 
281
- * Does this work with the npm-replacement [yarn](https://github.com/yarnpkg/yarn)?
289
+ * Does this work with the npm-replacements [yarn](https://github.com/yarnpkg/yarn) or [pnpm](https://pnpm.js.org/)?
282
290
 
283
- Yes! In all examples above, you may replace "`npm`" with "`yarn`".
291
+ Yes! In all examples above, you may replace "`npm`" with "`yarn`" or "`pnpm`".
@@ -13,6 +13,13 @@ const args = yargs
13
13
  .alias('v', 'version')
14
14
  .options({
15
15
  // General
16
+ 'm': {
17
+ alias: 'max-processes',
18
+ describe:
19
+ 'How many processes should run at once.\n' +
20
+ 'New processes only spawn after all restart tries of a process.',
21
+ type: 'number'
22
+ },
16
23
  'n': {
17
24
  alias: 'names',
18
25
  describe:
@@ -125,7 +132,7 @@ const args = yargs
125
132
  'Can be either the index or the name of the process.'
126
133
  }
127
134
  })
128
- .group(['n', 'name-separator', 'raw', 's', 'no-color'], 'General')
135
+ .group(['m', 'n', 'name-separator', 'raw', 's', 'no-color'], 'General')
129
136
  .group(['p', 'c', 'l', 't'], 'Prefix styling')
130
137
  .group(['i', 'default-input-target'], 'Input handling')
131
138
  .group(['k', 'kill-others-on-fail'], 'Killing other processes')
@@ -152,6 +159,7 @@ concurrently(args._.map((command, index) => {
152
159
  killOthers: args.killOthers
153
160
  ? ['success', 'failure']
154
161
  : (args.killOthersOnFail ? ['failure'] : []),
162
+ maxProcesses: args.maxProcesses,
155
163
  raw: args.raw,
156
164
  prefix: args.prefix,
157
165
  prefixLength: args.prefixLength,
package/index.js CHANGED
@@ -19,6 +19,7 @@ module.exports = (commands, options = {}) => {
19
19
  });
20
20
 
21
21
  return concurrently(commands, {
22
+ maxProcesses: options.maxProcesses,
22
23
  raw: options.raw,
23
24
  successCondition: options.successCondition,
24
25
  controllers: [
@@ -30,7 +31,7 @@ module.exports = (commands, options = {}) => {
30
31
  defaultInputTarget: options.defaultInputTarget,
31
32
  inputStream: options.inputStream,
32
33
  }),
33
- new KillOnSignal(),
34
+ new KillOnSignal({ process }),
34
35
  new RestartProcess({
35
36
  logger,
36
37
  delay: options.restartDelay,
@@ -47,6 +48,7 @@ module.exports = (commands, options = {}) => {
47
48
  // Export all flow controllers and the main concurrently function,
48
49
  // so that 3rd-parties can use them however they want
49
50
  exports.concurrently = concurrently;
51
+ exports.Logger = Logger;
50
52
  exports.InputHandler = InputHandler;
51
53
  exports.KillOnSignal = KillOnSignal;
52
54
  exports.KillOthers = KillOthers;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "concurrently",
3
- "version": "5.0.1",
3
+ "version": "5.3.0",
4
4
  "description": "Run commands concurrently",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -36,7 +36,7 @@
36
36
  "rxjs": "^6.5.2",
37
37
  "spawn-command": "^0.0.2-1",
38
38
  "supports-color": "^6.1.0",
39
- "tree-kill": "^1.2.1",
39
+ "tree-kill": "^1.2.2",
40
40
  "yargs": "^13.3.0"
41
41
  },
42
42
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  module.exports = class ExpandNpmShortcut {
2
2
  parse(commandInfo) {
3
- const [, npmCmd, cmdName, args] = commandInfo.command.match(/^(npm|yarn):(\S+)(.*)/) || [];
3
+ const [, npmCmd, cmdName, args] = commandInfo.command.match(/^(npm|yarn|pnpm):(\S+)(.*)/) || [];
4
4
  if (!cmdName) {
5
5
  return commandInfo;
6
6
  }
@@ -7,7 +7,7 @@ module.exports = class ExpandNpmWildcard {
7
7
  }
8
8
 
9
9
  parse(commandInfo) {
10
- const [, npmCmd, cmdName, args] = commandInfo.command.match(/(npm|yarn) run (\S+)([^&]*)/) || [];
10
+ const [, npmCmd, cmdName, args] = commandInfo.command.match(/(npm|yarn|pnpm) run (\S+)([^&]*)/) || [];
11
11
  const wildcardPosition = (cmdName || '').indexOf('*');
12
12
 
13
13
  // If the regex didn't match an npm script, or it has no wildcard,
package/src/command.js CHANGED
@@ -5,11 +5,12 @@ module.exports = class Command {
5
5
  return !!this.process;
6
6
  }
7
7
 
8
- constructor({ index, name, command, prefixColor, killProcess, spawn, spawnOpts }) {
8
+ constructor({ index, name, command, prefixColor, env, killProcess, spawn, spawnOpts }) {
9
9
  this.index = index;
10
10
  this.name = name;
11
11
  this.command = command;
12
12
  this.prefixColor = prefixColor;
13
+ this.env = env;
13
14
  this.killProcess = killProcess;
14
15
  this.spawn = spawn;
15
16
  this.spawnOpts = spawnOpts;
@@ -31,7 +32,16 @@ module.exports = class Command {
31
32
  });
32
33
  Rx.fromEvent(child, 'close').subscribe(([exitCode, signal]) => {
33
34
  this.process = undefined;
34
- this.close.next(exitCode === null ? signal : exitCode);
35
+ this.close.next({
36
+ command: {
37
+ command: this.command,
38
+ name: this.name,
39
+ prefixColor: this.prefixColor,
40
+ env: this.env,
41
+ },
42
+ index: this.index,
43
+ exitCode: exitCode === null ? signal : exitCode,
44
+ });
35
45
  });
36
46
  child.stdout && pipeTo(Rx.fromEvent(child.stdout, 'data'), this.stdout);
37
47
  child.stderr && pipeTo(Rx.fromEvent(child.stderr, 'data'), this.stderr);
@@ -7,29 +7,33 @@ module.exports = class CompletionListener {
7
7
  this.scheduler = scheduler;
8
8
  }
9
9
 
10
- listen(commands) {
11
- const closeStreams = commands.map(command => command.close);
12
- const allClosed = Rx.zip(...closeStreams);
13
- return Rx.merge(...closeStreams).pipe(
14
- bufferCount(closeStreams.length),
15
- map(exitCodes => {
16
- switch (this.successCondition) {
17
- /* eslint-disable indent */
18
- case 'first':
19
- return exitCodes[0] === 0;
10
+ isSuccess(exitCodes) {
11
+ switch (this.successCondition) {
12
+ /* eslint-disable indent */
13
+ case 'first':
14
+ return exitCodes[0] === 0;
15
+
16
+ case 'last':
17
+ return exitCodes[exitCodes.length - 1] === 0;
20
18
 
21
- case 'last':
22
- return exitCodes[exitCodes.length - 1] === 0;
19
+ default:
20
+ return exitCodes.every(exitCode => exitCode === 0);
21
+ /* eslint-enable indent */
22
+ }
23
+ }
23
24
 
24
- default:
25
- return exitCodes.every(exitCode => exitCode === 0);
26
- /* eslint-enable indent */
27
- }
28
- }),
29
- switchMap(success => success
30
- ? Rx.of(null, this.scheduler)
31
- : Rx.throwError(new Error(), this.scheduler)),
32
- take(1)
33
- ).toPromise();
25
+ listen(commands) {
26
+ const closeStreams = commands.map(command => command.close);
27
+ return Rx.merge(...closeStreams)
28
+ .pipe(
29
+ bufferCount(closeStreams.length),
30
+ switchMap(exitInfos =>
31
+ this.isSuccess(exitInfos.map(({ exitCode }) => exitCode))
32
+ ? Rx.of(exitInfos, this.scheduler)
33
+ : Rx.throwError(exitInfos, this.scheduler)
34
+ ),
35
+ take(1)
36
+ )
37
+ .toPromise();
34
38
  }
35
39
  };
@@ -31,17 +31,17 @@ module.exports = (commands, options) => {
31
31
  new ExpandNpmWildcard()
32
32
  ];
33
33
 
34
- const spawnOpts = getSpawnOpts({ raw: options.raw });
35
-
36
34
  commands = _(commands)
37
35
  .map(mapToCommandInfo)
38
36
  .flatMap(command => parseCommand(command, commandParsers))
39
- .map((command, index) => new Command(Object.assign({
40
- index,
41
- spawnOpts,
42
- killProcess: options.kill,
43
- spawn: options.spawn,
44
- }, command)))
37
+ .map((command, index) => new Command(
38
+ Object.assign({
39
+ index,
40
+ spawnOpts: getSpawnOpts({ raw: options.raw, env: command.env }),
41
+ killProcess: options.kill,
42
+ spawn: options.spawn,
43
+ }, command)
44
+ ))
45
45
  .value();
46
46
 
47
47
  commands = options.controllers.reduce(
@@ -49,7 +49,12 @@ module.exports = (commands, options) => {
49
49
  commands
50
50
  );
51
51
 
52
- commands.forEach(command => command.start());
52
+ const commandsLeft = commands.slice();
53
+ const maxProcesses = Math.max(1, Number(options.maxProcesses) || commandsLeft.length);
54
+ for (let i = 0; i < maxProcesses; i++) {
55
+ maybeRunMore(commandsLeft);
56
+ }
57
+
53
58
  return new CompletionListener({ successCondition: options.successCondition }).listen(commands);
54
59
  };
55
60
 
@@ -58,6 +63,7 @@ function mapToCommandInfo(command) {
58
63
  command: command.command || command,
59
64
  name: command.name || '',
60
65
  prefixColor: command.prefixColor || '',
66
+ env: command.env || {},
61
67
  };
62
68
  }
63
69
 
@@ -67,3 +73,15 @@ function parseCommand(command, parsers) {
67
73
  _.castArray(command)
68
74
  );
69
75
  }
76
+
77
+ function maybeRunMore(commandsLeft) {
78
+ const command = commandsLeft.shift();
79
+ if (!command) {
80
+ return;
81
+ }
82
+
83
+ command.start();
84
+ command.close.subscribe(() => {
85
+ maybeRunMore(commandsLeft);
86
+ });
87
+ }
package/src/defaults.js CHANGED
@@ -8,6 +8,8 @@ module.exports = {
8
8
  defaultInputTarget: 0,
9
9
  // Whether process.stdin should be forwarded to child processes
10
10
  handleInput: false,
11
+ // How many processes to run at once
12
+ maxProcesses: 0,
11
13
  nameSeparator: ',',
12
14
  // Which prefix style to use when logging processes output.
13
15
  prefix: '',
@@ -2,7 +2,7 @@ const { map } = require('rxjs/operators');
2
2
 
3
3
 
4
4
  module.exports = class KillOnSignal {
5
- constructor({ process = global.process } = {}) {
5
+ constructor({ process }) {
6
6
  this.process = process;
7
7
  }
8
8
 
@@ -16,8 +16,9 @@ module.exports = class KillOnSignal {
16
16
  });
17
17
 
18
18
  return commands.map(command => {
19
- const closeStream = command.close.pipe(map(value => {
20
- return caughtSignal === 'SIGINT' ? 0 : value;
19
+ const closeStream = command.close.pipe(map(exitInfo => {
20
+ const exitCode = caughtSignal === 'SIGINT' ? 0 : exitInfo.exitCode;
21
+ return Object.assign({}, exitInfo, { exitCode });
21
22
  }));
22
23
  return new Proxy(command, {
23
24
  get(target, prop) {
@@ -18,7 +18,7 @@ module.exports = class KillOthers {
18
18
  }
19
19
 
20
20
  const closeStates = commands.map(command => command.close.pipe(
21
- map(exitCode => exitCode === 0 ? 'success' : 'failure'),
21
+ map(({ exitCode }) => exitCode === 0 ? 'success' : 'failure'),
22
22
  filter(state => conditions.includes(state))
23
23
  ));
24
24
 
@@ -4,8 +4,8 @@ module.exports = class LogExit {
4
4
  }
5
5
 
6
6
  handle(commands) {
7
- commands.forEach(command => command.close.subscribe(code => {
8
- this.logger.logCommandEvent(`${command.command} exited with code ${code}`, command);
7
+ commands.forEach(command => command.close.subscribe(({ exitCode }) => {
8
+ this.logger.logCommandEvent(`${command.command} exited with code ${exitCode}`, command);
9
9
  }));
10
10
 
11
11
  return commands;
@@ -18,7 +18,7 @@ module.exports = class RestartProcess {
18
18
 
19
19
  commands.map(command => command.close.pipe(
20
20
  take(this.tries),
21
- takeWhile(code => code !== 0)
21
+ takeWhile(({ exitCode }) => exitCode !== 0)
22
22
  )).map((failure, index) => Rx.merge(
23
23
  // Delay the emission (so that the restarts happen on time),
24
24
  // explicitly telling the subscriber that a restart is needed
@@ -36,9 +36,9 @@ module.exports = class RestartProcess {
36
36
  }));
37
37
 
38
38
  return commands.map(command => {
39
- const closeStream = command.close.pipe(filter((value, emission) => {
39
+ const closeStream = command.close.pipe(filter(({ exitCode }, emission) => {
40
40
  // We let all success codes pass, and failures only after restarting won't happen again
41
- return value === 0 || emission >= this.tries;
41
+ return exitCode === 0 || emission >= this.tries;
42
42
  }));
43
43
 
44
44
  return new Proxy(command, {
@@ -3,10 +3,11 @@ const supportsColor = require('supports-color');
3
3
  module.exports = ({
4
4
  colorSupport = supportsColor.stdout,
5
5
  process = global.process,
6
- raw = false
7
- } = {}) => Object.assign(
6
+ raw = false,
7
+ env = {}
8
+ }) => Object.assign(
8
9
  {},
9
10
  raw && { stdio: 'inherit' },
10
11
  /^win/.test(process.platform) && { detached: false },
11
- colorSupport && { env: Object.assign({ FORCE_COLOR: colorSupport.level }, process.env) }
12
+ { env: Object.assign(colorSupport ? { FORCE_COLOR: colorSupport.level } : {}, process.env, env) }
12
13
  );
package/src/logger.js CHANGED
@@ -53,7 +53,7 @@ module.exports = class Logger {
53
53
  return _.reduce(prefixes, (prev, val, key) => {
54
54
  const keyRegex = new RegExp(_.escapeRegExp(`{${key}}`), 'g');
55
55
  return prev.replace(keyRegex, val);
56
- }, prefix).trim();
56
+ }, prefix);
57
57
  }
58
58
 
59
59
  colorText(command, text) {