concurrently 7.4.0 → 7.6.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
@@ -3,7 +3,7 @@
3
3
  [![Latest Release](https://img.shields.io/github/v/release/open-cli-tools/concurrently?label=Release)](https://github.com/open-cli-tools/concurrently/releases)
4
4
  [![License](https://img.shields.io/github/license/open-cli-tools/concurrently?label=License)](https://github.com/open-cli-tools/concurrently/blob/main/LICENSE)
5
5
  [![Weekly Downloads on NPM](https://img.shields.io/npm/dw/concurrently?label=Downloads&logo=npm)](https://www.npmjs.com/package/concurrently)
6
- [![CI Status](https://img.shields.io/github/workflow/status/open-cli-tools/concurrently/CI?label=CI&logo=github)](https://github.com/open-cli-tools/concurrently/actions/workflows/ci.yml)
6
+ [![CI Status](https://img.shields.io/github/workflow/status/open-cli-tools/concurrently/Test?label=CI&logo=github)](https://github.com/open-cli-tools/concurrently/actions/workflows/test.yml)
7
7
  [![Coverage Status](https://img.shields.io/coveralls/github/open-cli-tools/concurrently/main?label=Coverage&logo=coveralls)](https://coveralls.io/github/open-cli-tools/concurrently?branch=main)
8
8
 
9
9
  Run multiple commands concurrently.
@@ -15,7 +15,7 @@ Like `npm run watch-js & npm run watch-less` but better.
15
15
 
16
16
  - [concurrently](#concurrently)
17
17
  - [Why](#why)
18
- - [Install](#install)
18
+ - [Installation](#installation)
19
19
  - [Usage](#usage)
20
20
  - [API](#api)
21
21
  - [`concurrently(commands[, options])`](#concurrentlycommands-options)
@@ -41,25 +41,24 @@ tired of opening terminals and made **concurrently**.
41
41
  - With `--kill-others` switch, all commands are killed if one dies
42
42
  - Spawns commands with [spawn-command](https://github.com/mmalecki/spawn-command)
43
43
 
44
- ## Install
44
+ ## Installation
45
45
 
46
- The tool is written in Node.js, but you can use it to run **any** commands.
47
-
48
- ```bash
49
- npm install -g concurrently
50
- ```
46
+ **concurrently** can be installed in the global scope (if you'd like to have it available and use it on the whole system) or locally for a specific package (for example if you'd like to use it in the `scripts` section of your package):
51
47
 
52
- or if you are using it from npm scripts:
48
+ | | npm | Yarn | pnpm |
49
+ | ----------- | ----------------------- | ------------------------------ | -------------------------- |
50
+ | **Global** | `npm i -g concurrently` | `yarn global add concurrently` | `pnpm add -g concurrently` |
51
+ | **Local**\* | `npm i concurrently -D` | `yarn add concurrently -D` | `pnpm add -D concurrently` |
53
52
 
54
- ```bash
55
- npm install concurrently --save
56
- ```
53
+ <sub>\* It's recommended to add **concurrently** as `devDependencies` as it's usually used for developing purposes. Please change this flag if this doesn't apply in your case.</sub>
57
54
 
58
55
  ## Usage
59
56
 
60
57
  > **Note**
61
58
  > The `concurrently` command is now also available under the shorthand alias `conc`.
62
59
 
60
+ The tool is written in Node.js, but you can use it to run **any** commands.
61
+
63
62
  Remember to surround separate commands with quotes:
64
63
 
65
64
  ```bash
@@ -146,8 +145,9 @@ concurrently [options] <command ...>
146
145
 
147
146
  General
148
147
  -m, --max-processes How many processes should run at once.
148
+ Exact number or a percent of CPUs available (for example "50%").
149
149
  New processes only spawn after all restart tries
150
- of a process. [number]
150
+ of a process. [string]
151
151
  -n, --names List of custom names to be used in prefix
152
152
  template.
153
153
  Example names: "main,browser,server" [string]
@@ -191,8 +191,9 @@ Prefix styling
191
191
  - Available modifiers: reset, bold, dim, italic,
192
192
  underline, inverse, hidden, strikethrough
193
193
  - Available colors: black, red, green, yellow, blue,
194
- magenta, cyan, white, gray
195
- or any hex values for colors, eg #23de43
194
+ magenta, cyan, white, gray,
195
+ any hex values for colors (e.g. #23de43) or auto for
196
+ an automatically picked color
196
197
  - Available background colors: bgBlack, bgRed,
197
198
  bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite
198
199
  See https://www.npmjs.com/package/chalk for more
@@ -250,6 +251,14 @@ Examples:
250
251
  $ concurrently --names "HTTP,WATCH" -c "bgBlue.bold,bgMagenta.bold"
251
252
  "http-server" "npm run watch"
252
253
 
254
+ - Auto varying colored prefixes
255
+
256
+ $ concurrently -c "auto" "npm run watch" "http-server"
257
+
258
+ - Mixing auto and manual colored prefixes
259
+
260
+ $ concurrently -c "red,auto" "npm run watch" "http-server" "echo hello"
261
+
253
262
  - Configuring via environment variables with CONCURRENTLY_ prefix
254
263
 
255
264
  $ CONCURRENTLY_RAW=true CONCURRENTLY_KILL_OTHERS=true concurrently "echo
@@ -324,12 +333,12 @@ For more details, visit https://github.com/open-cli-tools/concurrently
324
333
  - `prefix`: the prefix type to use when logging processes output.
325
334
  Possible values: `index`, `pid`, `time`, `command`, `name`, `none`, or a template (eg `[{time} process: {pid}]`).
326
335
  Default: the name of the process, or its index if no name is set.
327
- - `prefixColors`: a list of colors as supported by [chalk](https://www.npmjs.com/package/chalk).
328
- If concurrently would run more commands than there are colors, the last color is repeated.
336
+ - `prefixColors`: a list of colors as supported by [chalk](https://www.npmjs.com/package/chalk) or `auto` for an automatically picked color.
337
+ If concurrently would run more commands than there are colors, the last color is repeated, unless if the last color value is `auto` which means following colors are automatically picked to vary.
329
338
  Prefix colors specified per-command take precedence over this list.
330
339
  - `prefixLength`: how many characters to show when prefixing with `command`. Default: `10`
331
340
  - `raw`: whether raw mode should be used, meaning strictly process output will
332
- be logged, without any prefixes, colouring or extra stuff.
341
+ be logged, without any prefixes, coloring or extra stuff.
333
342
  - `successCondition`: the condition to consider the run was successful.
334
343
  If `first`, only the first process to exit will make up the success of the run; if `last`, the last process that exits will determine whether the run succeeds.
335
344
  Anything else means all processes should exit successfully.
@@ -54,8 +54,9 @@ const args = (0, yargs_1.default)(argsBeforeSep)
54
54
  'max-processes': {
55
55
  alias: 'm',
56
56
  describe: 'How many processes should run at once.\n' +
57
- 'New processes only spawn after all restart tries of a process.',
58
- type: 'number',
57
+ 'New processes only spawn after all restart tries of a process.\n' +
58
+ 'Exact number or a percent of CPUs available (for example "50%")',
59
+ type: 'string',
59
60
  },
60
61
  names: {
61
62
  alias: 'n',
@@ -87,7 +88,7 @@ const args = (0, yargs_1.default)(argsBeforeSep)
87
88
  'and concurrently coloring.',
88
89
  type: 'boolean',
89
90
  },
90
- // This one is provided for free. Chalk reads this itself and removes colours.
91
+ // This one is provided for free. Chalk reads this itself and removes colors.
91
92
  // https://www.npmjs.com/package/chalk#chalksupportscolor
92
93
  'no-color': {
93
94
  describe: 'Disables colors from logging',
@@ -140,8 +141,8 @@ const args = (0, yargs_1.default)(argsBeforeSep)
140
141
  describe: 'Comma-separated list of chalk colors to use on prefixes. ' +
141
142
  'If there are more commands than colors, the last color will be repeated.\n' +
142
143
  '- Available modifiers: reset, bold, dim, italic, underline, inverse, hidden, strikethrough\n' +
143
- '- Available colors: black, red, green, yellow, blue, magenta, cyan, white, gray \n' +
144
- 'or any hex values for colors, eg #23de43\n' +
144
+ '- Available colors: black, red, green, yellow, blue, magenta, cyan, white, gray, \n' +
145
+ 'any hex values for colors (e.g. #23de43) or auto for an automatically picked color\n' +
145
146
  '- Available background colors: bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite\n' +
146
147
  'See https://www.npmjs.com/package/chalk for more information.',
147
148
  default: defaults.prefixColors,
@@ -20,6 +20,14 @@ const examples = [
20
20
  description: 'Custom names and colored prefixes',
21
21
  example: '$ $0 --names "HTTP,WATCH" -c "bgBlue.bold,bgMagenta.bold" "http-server" "npm run watch"',
22
22
  },
23
+ {
24
+ description: 'Auto varying colored prefixes',
25
+ example: '$ $0 -c "auto" "npm run watch" "http-server"',
26
+ },
27
+ {
28
+ description: 'Mixing auto and manual colored prefixes',
29
+ example: '$ $0 -c "red,auto" "npm run watch" "http-server" "echo hello"',
30
+ },
23
31
  {
24
32
  description: 'Configuring via environment variables with CONCURRENTLY_ prefix',
25
33
  example: '$ CONCURRENTLY_RAW=true CONCURRENTLY_KILL_OTHERS=true $0 "echo hello" "echo world"',
@@ -9,8 +9,8 @@ export declare class ExpandArguments implements CommandParser {
9
9
  parse(commandInfo: CommandInfo): {
10
10
  command: string;
11
11
  name: string;
12
- env?: Record<string, unknown>;
13
- cwd?: string;
14
- prefixColor?: string;
12
+ env?: Record<string, unknown> | undefined;
13
+ cwd?: string | undefined;
14
+ prefixColor?: string | undefined;
15
15
  };
16
16
  }
@@ -12,9 +12,6 @@ const OMISSION = /\(!([^)]+)\)/;
12
12
  * `package.json` file of the current directory.
13
13
  */
14
14
  class ExpandNpmWildcard {
15
- constructor(readPackage = ExpandNpmWildcard.readPackage) {
16
- this.readPackage = readPackage;
17
- }
18
15
  static readPackage() {
19
16
  try {
20
17
  const json = fs_1.default.readFileSync('package.json', { encoding: 'utf-8' });
@@ -24,6 +21,9 @@ class ExpandNpmWildcard {
24
21
  return {};
25
22
  }
26
23
  }
24
+ constructor(readPackage = ExpandNpmWildcard.readPackage) {
25
+ this.readPackage = readPackage;
26
+ }
27
27
  parse(commandInfo) {
28
28
  const [, npmCmd, cmdName, args] = commandInfo.command.match(/(npm|yarn|pnpm) run (\S+)([^&]*)/) || [];
29
29
  const wildcardPosition = (cmdName || '').indexOf('*');
@@ -40,7 +40,9 @@ class ExpandNpmWildcard {
40
40
  const preWildcard = lodash_1.default.escapeRegExp(cmdNameSansOmission.slice(0, wildcardPosition));
41
41
  const postWildcard = lodash_1.default.escapeRegExp(cmdNameSansOmission.slice(wildcardPosition + 1));
42
42
  const wildcardRegex = new RegExp(`^${preWildcard}(.*?)${postWildcard}$`);
43
- const currentName = commandInfo.name || '';
43
+ // If 'commandInfo.name' doesn't match 'cmdName', this means a custom name
44
+ // has been specified and thus becomes the prefix (as described in the README).
45
+ const prefix = commandInfo.name !== cmdName ? commandInfo.name : '';
44
46
  return this.scripts
45
47
  .map((script) => {
46
48
  const match = script.match(wildcardRegex);
@@ -54,9 +56,9 @@ class ExpandNpmWildcard {
54
56
  return {
55
57
  ...commandInfo,
56
58
  command: `${npmCmd} run ${script}${args}`,
57
- // Will use an empty command name if command has no name and the wildcard match is empty,
58
- // e.g. if `npm:watch-*` matches `npm run watch-`.
59
- name: currentName + match[1],
59
+ // Will use an empty command name if no prefix has been specified and
60
+ // the wildcard match is empty, e.g. if `npm:watch-*` matches `npm run watch-`.
61
+ name: prefix + match[1],
60
62
  };
61
63
  }
62
64
  })
@@ -7,8 +7,8 @@ export declare class StripQuotes implements CommandParser {
7
7
  parse(commandInfo: CommandInfo): {
8
8
  command: string;
9
9
  name: string;
10
- env?: Record<string, unknown>;
11
- cwd?: string;
12
- prefixColor?: string;
10
+ env?: Record<string, unknown> | undefined;
11
+ cwd?: string | undefined;
12
+ prefixColor?: string | undefined;
13
13
  };
14
14
  }
@@ -8,7 +8,7 @@ import { EventEmitter, Writable } from 'stream';
8
8
  /**
9
9
  * Identifier for a command; if string, it's the command's name, if number, it's the index.
10
10
  */
11
- export declare type CommandIdentifier = string | number;
11
+ export type CommandIdentifier = string | number;
12
12
  export interface CommandInfo {
13
13
  /**
14
14
  * Command's name.
@@ -27,7 +27,7 @@ export interface CommandInfo {
27
27
  */
28
28
  cwd?: string;
29
29
  /**
30
- * Color to use on prefix of command.
30
+ * Color to use on prefix of the command.
31
31
  */
32
32
  prefixColor?: string;
33
33
  }
@@ -58,15 +58,15 @@ export interface TimerEvent {
58
58
  /**
59
59
  * Subtype of NodeJS's child_process including only what's actually needed for a command to work.
60
60
  */
61
- export declare type ChildProcess = EventEmitter & Pick<BaseChildProcess, 'pid' | 'stdin' | 'stdout' | 'stderr'>;
61
+ export type ChildProcess = EventEmitter & Pick<BaseChildProcess, 'pid' | 'stdin' | 'stdout' | 'stderr'>;
62
62
  /**
63
63
  * Interface for a function that must kill the process with `pid`, optionally sending `signal` to it.
64
64
  */
65
- export declare type KillProcess = (pid: number, signal?: string) => void;
65
+ export type KillProcess = (pid: number, signal?: string) => void;
66
66
  /**
67
67
  * Interface for a function that spawns a command and returns its child process instance.
68
68
  */
69
- export declare type SpawnCommand = (command: string, options: SpawnOptions) => ChildProcess;
69
+ export type SpawnCommand = (command: string, options: SpawnOptions) => ChildProcess;
70
70
  export declare class Command implements CommandInfo {
71
71
  private readonly killProcess;
72
72
  private readonly spawn;
@@ -77,7 +77,7 @@ export declare class Command implements CommandInfo {
77
77
  /** @inheritdoc */
78
78
  readonly command: string;
79
79
  /** @inheritdoc */
80
- readonly prefixColor: string;
80
+ readonly prefixColor?: string;
81
81
  /** @inheritdoc */
82
82
  readonly env: Record<string, unknown>;
83
83
  /** @inheritdoc */
@@ -92,6 +92,7 @@ export declare class Command implements CommandInfo {
92
92
  pid?: number;
93
93
  killed: boolean;
94
94
  exited: boolean;
95
+ /** @deprecated */
95
96
  get killable(): boolean;
96
97
  constructor({ index, name, command, prefixColor, env, cwd }: CommandInfo & {
97
98
  index: number;
@@ -104,4 +105,13 @@ export declare class Command implements CommandInfo {
104
105
  * Kills this command, optionally specifying a signal to send to it.
105
106
  */
106
107
  kill(code?: string): void;
108
+ /**
109
+ * Detects whether a command can be killed.
110
+ *
111
+ * Also works as a type guard on the input `command`.
112
+ */
113
+ static canKill(command: Command): command is Command & {
114
+ pid: number;
115
+ process: ChildProcess;
116
+ };
107
117
  }
@@ -26,6 +26,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.Command = void 0;
27
27
  const Rx = __importStar(require("rxjs"));
28
28
  class Command {
29
+ /** @deprecated */
30
+ get killable() {
31
+ return Command.canKill(this);
32
+ }
29
33
  constructor({ index, name, command, prefixColor, env, cwd }, spawnOpts, spawn, killProcess) {
30
34
  this.close = new Rx.Subject();
31
35
  this.error = new Rx.Subject();
@@ -38,15 +42,12 @@ class Command {
38
42
  this.name = name;
39
43
  this.command = command;
40
44
  this.prefixColor = prefixColor;
41
- this.env = env;
45
+ this.env = env || {};
42
46
  this.cwd = cwd;
43
47
  this.killProcess = killProcess;
44
48
  this.spawn = spawn;
45
49
  this.spawnOpts = spawnOpts;
46
50
  }
47
- get killable() {
48
- return !!this.process;
49
- }
50
51
  /**
51
52
  * Starts this command, piping output, error and close events onto the corresponding observables.
52
53
  */
@@ -72,7 +73,7 @@ class Command {
72
73
  this.close.next({
73
74
  command: this,
74
75
  index: this.index,
75
- exitCode: exitCode === null ? signal : exitCode,
76
+ exitCode: exitCode !== null && exitCode !== void 0 ? exitCode : String(signal),
76
77
  killed: this.killed,
77
78
  timings: {
78
79
  startDate,
@@ -83,17 +84,25 @@ class Command {
83
84
  });
84
85
  child.stdout && pipeTo(Rx.fromEvent(child.stdout, 'data'), this.stdout);
85
86
  child.stderr && pipeTo(Rx.fromEvent(child.stderr, 'data'), this.stderr);
86
- this.stdin = child.stdin;
87
+ this.stdin = child.stdin || undefined;
87
88
  }
88
89
  /**
89
90
  * Kills this command, optionally specifying a signal to send to it.
90
91
  */
91
92
  kill(code) {
92
- if (this.killable) {
93
+ if (Command.canKill(this)) {
93
94
  this.killed = true;
94
95
  this.killProcess(this.pid, code);
95
96
  }
96
97
  }
98
+ /**
99
+ * Detects whether a command can be killed.
100
+ *
101
+ * Also works as a type guard on the input `command`.
102
+ */
103
+ static canKill(command) {
104
+ return !!command.pid && !!command.process;
105
+ }
97
106
  }
98
107
  exports.Command = Command;
99
108
  /**
@@ -9,7 +9,7 @@ import { CloseEvent, Command } from './command';
9
9
  * - `command-{name|index}`: only the commands with the specified names or index.
10
10
  * - `!command-{name|index}`: all commands but the ones with the specified names or index.
11
11
  */
12
- export declare type SuccessCondition = 'first' | 'last' | 'all' | `command-${string | number}` | `!command-${string | number}`;
12
+ export type SuccessCondition = 'first' | 'last' | 'all' | `command-${string | number}` | `!command-${string | number}`;
13
13
  /**
14
14
  * Provides logic to determine whether lists of commands ran successfully.
15
15
  */
@@ -36,4 +36,5 @@ export declare class CompletionListener {
36
36
  * @returns A Promise that resolves if the success condition is met, or rejects otherwise.
37
37
  */
38
38
  listen(commands: Command[]): Promise<CloseEvent[]>;
39
+ private emitWithScheduler;
39
40
  }
@@ -67,8 +67,11 @@ class CompletionListener {
67
67
  listen(commands) {
68
68
  const closeStreams = commands.map((command) => command.close);
69
69
  return Rx.lastValueFrom(Rx.merge(...closeStreams).pipe((0, operators_1.bufferCount)(closeStreams.length), (0, operators_1.switchMap)((exitInfos) => this.isSuccess(exitInfos)
70
- ? Rx.of(exitInfos, this.scheduler)
71
- : Rx.throwError(exitInfos, this.scheduler)), (0, operators_1.take)(1)));
70
+ ? this.emitWithScheduler(Rx.of(exitInfos))
71
+ : this.emitWithScheduler(Rx.throwError(exitInfos))), (0, operators_1.take)(1)));
72
+ }
73
+ emitWithScheduler(input) {
74
+ return this.scheduler ? input.pipe(Rx.observeOn(this.scheduler)) : input;
72
75
  }
73
76
  }
74
77
  exports.CompletionListener = CompletionListener;
@@ -9,8 +9,10 @@ import { Logger } from './logger';
9
9
  * If value is a string, then that's the command's command line.
10
10
  * Fine grained options can be defined by using the object format.
11
11
  */
12
- export declare type ConcurrentlyCommandInput = string | Partial<CommandInfo>;
13
- export declare type ConcurrentlyResult = {
12
+ export type ConcurrentlyCommandInput = string | ({
13
+ command: string;
14
+ } & Partial<CommandInfo>);
15
+ export type ConcurrentlyResult = {
14
16
  /**
15
17
  * All commands created and ran by concurrently.
16
18
  */
@@ -23,7 +25,7 @@ export declare type ConcurrentlyResult = {
23
25
  */
24
26
  result: Promise<CloseEvent[]>;
25
27
  };
26
- export declare type ConcurrentlyOptions = {
28
+ export type ConcurrentlyOptions = {
27
29
  logger?: Logger;
28
30
  /**
29
31
  * Which stream should the commands output be written to.
@@ -39,11 +41,12 @@ export declare type ConcurrentlyOptions = {
39
41
  prefixColors?: string[];
40
42
  /**
41
43
  * Maximum number of commands to run at once.
44
+ * Exact number or a percent of CPUs available (for example "50%").
42
45
  *
43
46
  * If undefined, then all processes will start in parallel.
44
47
  * Setting this value to 1 will achieve sequential running.
45
48
  */
46
- maxProcesses?: number;
49
+ maxProcesses?: number | string;
47
50
  /**
48
51
  * Whether commands should be spawned in raw mode.
49
52
  * Defaults to false.
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.concurrently = void 0;
7
7
  const assert_1 = __importDefault(require("assert"));
8
8
  const lodash_1 = __importDefault(require("lodash"));
9
+ const os_1 = require("os");
9
10
  const spawn_command_1 = __importDefault(require("spawn-command"));
10
11
  const tree_kill_1 = __importDefault(require("tree-kill"));
11
12
  const command_1 = require("./command");
@@ -16,6 +17,7 @@ const strip_quotes_1 = require("./command-parser/strip-quotes");
16
17
  const completion_listener_1 = require("./completion-listener");
17
18
  const get_spawn_opts_1 = require("./get-spawn-opts");
18
19
  const output_writer_1 = require("./output-writer");
20
+ const prefix_color_selector_1 = require("./prefix-color-selector");
19
21
  const defaults = {
20
22
  spawn: spawn_command_1.default,
21
23
  kill: tree_kill_1.default,
@@ -33,6 +35,7 @@ function concurrently(baseCommands, baseOptions) {
33
35
  assert_1.default.ok(Array.isArray(baseCommands), '[concurrently] commands should be an array');
34
36
  assert_1.default.notStrictEqual(baseCommands.length, 0, '[concurrently] no commands provided');
35
37
  const options = lodash_1.default.defaults(baseOptions, defaults);
38
+ const prefixColorSelector = new prefix_color_selector_1.PrefixColorSelector(options.prefixColors);
36
39
  const commandParsers = [
37
40
  new strip_quotes_1.StripQuotes(),
38
41
  new expand_npm_shortcut_1.ExpandNpmShortcut(),
@@ -41,16 +44,13 @@ function concurrently(baseCommands, baseOptions) {
41
44
  if (options.additionalArguments) {
42
45
  commandParsers.push(new expand_arguments_1.ExpandArguments(options.additionalArguments));
43
46
  }
44
- let lastColor = '';
45
47
  let commands = (0, lodash_1.default)(baseCommands)
46
48
  .map(mapToCommandInfo)
47
49
  .flatMap((command) => parseCommand(command, commandParsers))
48
50
  .map((command, index) => {
49
- // Use documented behaviour of repeating last color when specifying more commands than colors
50
- lastColor = (options.prefixColors && options.prefixColors[index]) || lastColor;
51
51
  return new command_1.Command({
52
52
  index,
53
- prefixColor: lastColor,
53
+ prefixColor: prefixColorSelector.getNextColor(),
54
54
  ...command,
55
55
  }, (0, get_spawn_opts_1.getSpawnOpts)({
56
56
  raw: options.raw,
@@ -70,13 +70,15 @@ function concurrently(baseCommands, baseOptions) {
70
70
  if (options.logger && options.outputStream) {
71
71
  const outputWriter = new output_writer_1.OutputWriter({
72
72
  outputStream: options.outputStream,
73
- group: options.group,
73
+ group: !!options.group,
74
74
  commands,
75
75
  });
76
76
  options.logger.output.subscribe(({ command, text }) => outputWriter.write(command, text));
77
77
  }
78
78
  const commandsLeft = commands.slice();
79
- const maxProcesses = Math.max(1, Number(options.maxProcesses) || commandsLeft.length);
79
+ const maxProcesses = Math.max(1, (typeof options.maxProcesses === 'string' && options.maxProcesses.endsWith('%')
80
+ ? Math.round(((0, os_1.cpus)().length * Number(options.maxProcesses.slice(0, -1))) / 100)
81
+ : Number(options.maxProcesses)) || commandsLeft.length);
80
82
  for (let i = 0; i < maxProcesses; i++) {
81
83
  maybeRunMore(commandsLeft);
82
84
  }
@@ -93,13 +95,9 @@ function concurrently(baseCommands, baseOptions) {
93
95
  exports.concurrently = concurrently;
94
96
  function mapToCommandInfo(command) {
95
97
  if (typeof command === 'string') {
96
- return {
97
- command,
98
- name: '',
99
- env: {},
100
- cwd: '',
101
- };
98
+ return mapToCommandInfo({ command });
102
99
  }
100
+ assert_1.default.ok(command.command, '[concurrently] command cannot be empty');
103
101
  return {
104
102
  command: command.command,
105
103
  name: command.name || '',
@@ -15,19 +15,16 @@ import { FlowController } from './flow-controller';
15
15
  export declare class InputHandler implements FlowController {
16
16
  private readonly logger;
17
17
  private readonly defaultInputTarget;
18
- private readonly inputStream;
18
+ private readonly inputStream?;
19
19
  private readonly pauseInputStreamOnFinish;
20
20
  constructor({ defaultInputTarget, inputStream, pauseInputStreamOnFinish, logger, }: {
21
- inputStream: Readable;
21
+ inputStream?: Readable;
22
22
  logger: Logger;
23
23
  defaultInputTarget?: CommandIdentifier;
24
24
  pauseInputStreamOnFinish?: boolean;
25
25
  });
26
26
  handle(commands: Command[]): {
27
27
  commands: Command[];
28
- onFinish?: undefined;
29
- } | {
30
- commands: Command[];
31
- onFinish: () => void;
28
+ onFinish?: () => void | undefined;
32
29
  };
33
30
  }
@@ -44,11 +44,12 @@ class InputHandler {
44
44
  this.pauseInputStreamOnFinish = pauseInputStreamOnFinish !== false;
45
45
  }
46
46
  handle(commands) {
47
- if (!this.inputStream) {
47
+ const { inputStream } = this;
48
+ if (!inputStream) {
48
49
  return { commands };
49
50
  }
50
- Rx.fromEvent(this.inputStream, 'data')
51
- .pipe((0, operators_1.map)((data) => data.toString()))
51
+ Rx.fromEvent(inputStream, 'data')
52
+ .pipe((0, operators_1.map)((data) => String(data)))
52
53
  .subscribe((data) => {
53
54
  const dataParts = data.split(/:(.+)/);
54
55
  const targetId = dataParts.length > 1 ? dataParts[0] : this.defaultInputTarget;
@@ -67,7 +68,7 @@ class InputHandler {
67
68
  onFinish: () => {
68
69
  if (this.pauseInputStreamOnFinish) {
69
70
  // https://github.com/kimmobrunfeldt/concurrently/issues/252
70
- this.inputStream.pause();
71
+ inputStream.pause();
71
72
  }
72
73
  },
73
74
  };
@@ -1,9 +1,9 @@
1
1
  import { Command } from '../command';
2
2
  import { Logger } from '../logger';
3
3
  import { FlowController } from './flow-controller';
4
- export declare type ProcessCloseCondition = 'failure' | 'success';
4
+ export type ProcessCloseCondition = 'failure' | 'success';
5
5
  /**
6
- * Sends a SIGTERM signal to all commands when one of the exits with a matching condition.
6
+ * Sends a SIGTERM signal to all commands when one of the commands exits with a matching condition.
7
7
  */
8
8
  export declare class KillOthers implements FlowController {
9
9
  private readonly logger;
@@ -6,8 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.KillOthers = void 0;
7
7
  const lodash_1 = __importDefault(require("lodash"));
8
8
  const operators_1 = require("rxjs/operators");
9
+ const command_1 = require("../command");
9
10
  /**
10
- * Sends a SIGTERM signal to all commands when one of the exits with a matching condition.
11
+ * Sends a SIGTERM signal to all commands when one of the commands exits with a matching condition.
11
12
  */
12
13
  class KillOthers {
13
14
  constructor({ logger, conditions, }) {
@@ -21,7 +22,7 @@ class KillOthers {
21
22
  }
22
23
  const closeStates = commands.map((command) => command.close.pipe((0, operators_1.map)(({ exitCode }) => exitCode === 0 ? 'success' : 'failure'), (0, operators_1.filter)((state) => conditions.includes(state))));
23
24
  closeStates.forEach((closeState) => closeState.subscribe(() => {
24
- const killableCommands = commands.filter((command) => command.killable);
25
+ const killableCommands = commands.filter((command) => command_1.Command.canKill(command));
25
26
  if (killableCommands.length) {
26
27
  this.logger.logGlobalEvent('Sending SIGTERM to other processes..');
27
28
  killableCommands.forEach((command) => command.kill());
@@ -27,6 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.LogTimings = void 0;
30
+ const assert = __importStar(require("assert"));
30
31
  const format_1 = __importDefault(require("date-fns/format"));
31
32
  const lodash_1 = __importDefault(require("lodash"));
32
33
  const Rx = __importStar(require("rxjs"));
@@ -36,10 +37,6 @@ const defaults = __importStar(require("../defaults"));
36
37
  * Logs timing information about commands as they start/stop and then a summary when all commands finish.
37
38
  */
38
39
  class LogTimings {
39
- constructor({ logger, timestampFormat = defaults.timestampFormat, }) {
40
- this.logger = logger;
41
- this.timestampFormat = timestampFormat;
42
- }
43
40
  static mapCloseEventToTimingInfo({ command, timings, killed, exitCode, }) {
44
41
  const readableDurationMs = (timings.endDate.getTime() - timings.startDate.getTime()).toLocaleString();
45
42
  return {
@@ -50,7 +47,12 @@ class LogTimings {
50
47
  command: command.command,
51
48
  };
52
49
  }
50
+ constructor({ logger, timestampFormat = defaults.timestampFormat, }) {
51
+ this.logger = logger;
52
+ this.timestampFormat = timestampFormat;
53
+ }
53
54
  printExitInfoTimingTable(exitInfos) {
55
+ assert.ok(this.logger);
54
56
  const exitInfoTable = (0, lodash_1.default)(exitInfos)
55
57
  .sortBy(({ timings }) => timings.durationSeconds)
56
58
  .reverse()
@@ -61,7 +63,8 @@ class LogTimings {
61
63
  return exitInfos;
62
64
  }
63
65
  handle(commands) {
64
- if (!this.logger) {
66
+ const { logger } = this;
67
+ if (!logger) {
65
68
  return { commands };
66
69
  }
67
70
  // individual process timings
@@ -69,12 +72,12 @@ class LogTimings {
69
72
  command.timer.subscribe(({ startDate, endDate }) => {
70
73
  if (!endDate) {
71
74
  const formattedStartDate = (0, format_1.default)(startDate, this.timestampFormat);
72
- this.logger.logCommandEvent(`${command.command} started at ${formattedStartDate}`, command);
75
+ logger.logCommandEvent(`${command.command} started at ${formattedStartDate}`, command);
73
76
  }
74
77
  else {
75
78
  const durationMs = endDate.getTime() - startDate.getTime();
76
79
  const formattedEndDate = (0, format_1.default)(endDate, this.timestampFormat);
77
- this.logger.logCommandEvent(`${command.command} stopped at ${formattedEndDate} after ${durationMs.toLocaleString()}ms`, command);
80
+ logger.logCommandEvent(`${command.command} stopped at ${formattedEndDate} after ${durationMs.toLocaleString()}ms`, command);
78
81
  }
79
82
  });
80
83
  });
@@ -11,23 +11,23 @@ export declare const getSpawnOpts: ({ colorSupport, cwd, process, raw, env, }: {
11
11
  *
12
12
  * Defaults to whatever the terminal's stdout support is.
13
13
  */
14
- colorSupport?: Pick<supportsColor.supportsColor.Level, 'level'> | false;
14
+ colorSupport?: false | Pick<supportsColor.supportsColor.Level, "level"> | undefined;
15
15
  /**
16
16
  * The NodeJS process.
17
17
  */
18
- process?: Pick<NodeJS.Process, 'cwd' | 'platform' | 'env'>;
18
+ process?: Pick<NodeJS.Process, "cwd" | "env" | "platform"> | undefined;
19
19
  /**
20
20
  * A custom working directory to spawn processes in.
21
21
  * Defaults to `process.cwd()`.
22
22
  */
23
- cwd?: string;
23
+ cwd?: string | undefined;
24
24
  /**
25
25
  * Whether to customize the options for spawning processes in raw mode.
26
26
  * Defaults to false.
27
27
  */
28
- raw?: boolean;
28
+ raw?: boolean | undefined;
29
29
  /**
30
30
  * Map of custom environment variables to include in the spawn options.
31
31
  */
32
- env?: Record<string, unknown>;
32
+ env?: Record<string, unknown> | undefined;
33
33
  }) => SpawnOptions;
@@ -12,7 +12,7 @@ import { LogOutput } from './flow-control/log-output';
12
12
  import { LogTimings } from './flow-control/log-timings';
13
13
  import { RestartProcess } from './flow-control/restart-process';
14
14
  import { Logger } from './logger';
15
- export declare type ConcurrentlyOptions = BaseConcurrentlyOptions & {
15
+ export type ConcurrentlyOptions = BaseConcurrentlyOptions & {
16
16
  /**
17
17
  * Which command(s) should have their output hidden.
18
18
  */
package/dist/src/index.js CHANGED
@@ -46,7 +46,7 @@ exports.default = (commands, options = {}) => {
46
46
  new input_handler_1.InputHandler({
47
47
  logger,
48
48
  defaultInputTarget: options.defaultInputTarget,
49
- inputStream: options.inputStream || (options.handleInput && process.stdin),
49
+ inputStream: options.inputStream || (options.handleInput ? process.stdin : undefined),
50
50
  pauseInputStreamOnFinish: options.pauseInputStreamOnFinish,
51
51
  }),
52
52
  new kill_on_signal_1.KillOnSignal({ process }),
@@ -57,10 +57,10 @@ exports.default = (commands, options = {}) => {
57
57
  }),
58
58
  new kill_others_1.KillOthers({
59
59
  logger,
60
- conditions: options.killOthers,
60
+ conditions: options.killOthers || [],
61
61
  }),
62
62
  new log_timings_1.LogTimings({
63
- logger: options.timings ? logger : null,
63
+ logger: options.timings ? logger : undefined,
64
64
  timestampFormat: options.timestampFormat,
65
65
  }),
66
66
  ],
@@ -86,13 +86,14 @@ class Logger {
86
86
  }, prefix);
87
87
  }
88
88
  colorText(command, text) {
89
+ var _a;
89
90
  let color;
90
91
  if (command.prefixColor && command.prefixColor.startsWith('#')) {
91
92
  color = chalk_1.default.hex(command.prefixColor);
92
93
  }
93
94
  else {
94
95
  const defaultColor = lodash_1.default.get(chalk_1.default, defaults.prefixColors, chalk_1.default.reset);
95
- color = lodash_1.default.get(chalk_1.default, command.prefixColor, defaultColor);
96
+ color = lodash_1.default.get(chalk_1.default, (_a = command.prefixColor) !== null && _a !== void 0 ? _a : '', defaultColor);
96
97
  }
97
98
  return color(text);
98
99
  }
@@ -0,0 +1,11 @@
1
+ import chalk from 'chalk';
2
+ export declare class PrefixColorSelector {
3
+ private colorGenerator;
4
+ constructor(customColors?: string[]);
5
+ /** A list of colors that are readable in a terminal. */
6
+ static get ACCEPTABLE_CONSOLE_COLORS(): ("stderr" | keyof chalk.Chalk | "supportsColor" | "Level" | "Color" | "ForegroundColor" | "BackgroundColor" | "Modifiers")[];
7
+ /**
8
+ * @returns The given custom colors then a set of acceptable console colors indefinitely.
9
+ */
10
+ getNextColor(): string;
11
+ }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrefixColorSelector = void 0;
4
+ function getConsoleColorsWithoutCustomColors(customColors) {
5
+ return PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS.filter(
6
+ // Consider the "Bright" variants of colors to be the same as the plain color to avoid similar colors
7
+ (color) => !customColors.includes(color.replace(/Bright$/, '')));
8
+ }
9
+ /**
10
+ * Creates a generator that yields an infinite stream of colors.
11
+ */
12
+ function* createColorGenerator(customColors) {
13
+ // Custom colors should be used as is, except for "auto"
14
+ const nextAutoColors = getConsoleColorsWithoutCustomColors(customColors);
15
+ let lastColor;
16
+ for (const customColor of customColors) {
17
+ let currentColor = customColor;
18
+ if (currentColor !== 'auto') {
19
+ yield currentColor; // Manual color
20
+ }
21
+ else {
22
+ // Find the first auto color that is not the same as the last color
23
+ while (currentColor === 'auto' || lastColor === currentColor) {
24
+ if (!nextAutoColors.length) {
25
+ // There could be more "auto" values than auto colors so this needs to be able to refill
26
+ nextAutoColors.push(...PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS);
27
+ }
28
+ currentColor = String(nextAutoColors.shift());
29
+ }
30
+ yield currentColor; // Auto color
31
+ }
32
+ lastColor = currentColor;
33
+ }
34
+ const lastCustomColor = customColors[customColors.length - 1] || '';
35
+ if (lastCustomColor !== 'auto') {
36
+ while (true) {
37
+ yield lastCustomColor; // If last custom color was not "auto" then return same color forever, to maintain existing behaviour
38
+ }
39
+ }
40
+ // Finish the initial set(s) of auto colors to avoid repetition
41
+ for (const color of nextAutoColors) {
42
+ yield color;
43
+ }
44
+ // Yield an infinite stream of acceptable console colors
45
+ //
46
+ // If the given custom colors use every ACCEPTABLE_CONSOLE_COLORS except one then there is a chance a color will be repeated,
47
+ // however its highly unlikely and low consequence so not worth the extra complexity to account for it
48
+ while (true) {
49
+ for (const color of PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS) {
50
+ yield color; // Repeat colors forever
51
+ }
52
+ }
53
+ }
54
+ class PrefixColorSelector {
55
+ constructor(customColors = []) {
56
+ this.colorGenerator = createColorGenerator(customColors);
57
+ }
58
+ /** A list of colors that are readable in a terminal. */
59
+ static get ACCEPTABLE_CONSOLE_COLORS() {
60
+ // Colors picked randomly, can be amended if required
61
+ return [
62
+ // Prevent duplicates, in case the list becomes significantly large
63
+ ...new Set([
64
+ // Text colors
65
+ 'cyan',
66
+ 'yellow',
67
+ 'greenBright',
68
+ 'blueBright',
69
+ 'magentaBright',
70
+ 'white',
71
+ 'grey',
72
+ 'red',
73
+ // Background colors
74
+ 'bgCyan',
75
+ 'bgYellow',
76
+ 'bgGreenBright',
77
+ 'bgBlueBright',
78
+ 'bgMagenta',
79
+ 'bgWhiteBright',
80
+ 'bgGrey',
81
+ 'bgRed',
82
+ ]),
83
+ ];
84
+ }
85
+ /**
86
+ * @returns The given custom colors then a set of acceptable console colors indefinitely.
87
+ */
88
+ getNextColor() {
89
+ return this.colorGenerator.next().value;
90
+ }
91
+ }
92
+ exports.PrefixColorSelector = PrefixColorSelector;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "concurrently",
3
- "version": "7.4.0",
3
+ "version": "7.6.0",
4
4
  "description": "Run commands concurrently",
5
5
  "main": "index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -25,9 +25,9 @@
25
25
  "build": "tsc --build",
26
26
  "postbuild": "chmod +x dist/bin/concurrently.js",
27
27
  "clean": "tsc --build --clean",
28
- "format": "prettier --ignore-path .gitignore --check '**/{!(package-lock).json,*.y?(a)ml,*.md}'",
28
+ "format": "prettier --check '**/*.{json,y?(a)ml,md}'",
29
29
  "format:fix": "npm run format -- --write",
30
- "lint": "eslint . --ext mjs,js,ts --ignore-path .gitignore",
30
+ "lint": "eslint --ignore-path .gitignore --ext mjs,js,ts .",
31
31
  "lint:fix": "npm run lint -- --fix",
32
32
  "prepublishOnly": "npm run build",
33
33
  "report-coverage": "cat coverage/lcov.info | coveralls",
@@ -63,7 +63,7 @@
63
63
  "@hirez_io/observer-spy": "^2.2.0",
64
64
  "@swc/core": "^1.2.224",
65
65
  "@swc/jest": "^0.2.21",
66
- "@types/jest": "^28.1.6",
66
+ "@types/jest": "^28.1.8",
67
67
  "@types/lodash": "^4.14.178",
68
68
  "@types/node": "^16.11.47",
69
69
  "@types/shell-quote": "^1.7.1",
@@ -77,16 +77,16 @@
77
77
  "eslint": "^8.21.0",
78
78
  "eslint-config-prettier": "^8.5.0",
79
79
  "eslint-plugin-import": "^2.26.0",
80
- "eslint-plugin-jest": "^26.8.2",
80
+ "eslint-plugin-jest": "^27.0.4",
81
81
  "eslint-plugin-prettier": "^4.0.0",
82
- "eslint-plugin-simple-import-sort": "^7.0.0",
82
+ "eslint-plugin-simple-import-sort": "^8.0.0",
83
83
  "jest": "^28.1.3",
84
84
  "jest-create-mock-instance": "^2.0.0",
85
85
  "lint-staged": "^12.4.1",
86
86
  "prettier": "^2.6.2",
87
87
  "simple-git-hooks": "^2.7.0",
88
88
  "string-argv": "^0.3.1",
89
- "typescript": "^4.5.4"
89
+ "typescript": "~4.9.3"
90
90
  },
91
91
  "files": [
92
92
  "dist",
@@ -101,6 +101,6 @@
101
101
  },
102
102
  "lint-staged": {
103
103
  "*.m?{js,ts}": "eslint --fix",
104
- "{!(package-lock).json,*.{y?(a)ml,md}}": "prettier --write"
104
+ "*.{json,y?(a)ml,md}": "prettier --write"
105
105
  }
106
106
  }