concurrently 7.5.0 → 8.0.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/actions/workflow/status/open-cli-tools/concurrently/test.yml?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)
@@ -25,7 +25,7 @@ Like `npm run watch-js & npm run watch-less` but better.
25
25
 
26
26
  ## Why
27
27
 
28
- I like [task automation with npm](https://github.com/substack/blog/blob/master/npm_run.markdown)
28
+ I like [task automation with npm](https://web.archive.org/web/20220531064025/https://github.com/substack/blog/blob/master/npm_run.markdown)
29
29
  but the usual way to run multiple commands concurrently is
30
30
  `npm run watch-js & npm run watch-css`. That's fine but it's hard to keep
31
31
  on track of different outputs. Also if one process fails, others still keep running
@@ -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 | Bun |
49
+ | ----------- | ----------------------- | ------------------------------ | -------------------------- | ------------------------- |
50
+ | **Global** | `npm i -g concurrently` | `yarn global add concurrently` | `pnpm add -g concurrently` | `bun add -g concurrently` |
51
+ | **Local**\* | `npm i -D concurrently` | `yarn add -D concurrently` | `pnpm add -D concurrently` | `bun add -d concurrently` |
53
52
 
54
- ```bash
55
- npm install concurrently --save
56
- ```
53
+ <sub>\* It's recommended to add **concurrently** to `devDependencies` as it's usually used for developing purposes. Please adjust the command 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]
@@ -219,6 +219,8 @@ Killing other processes
219
219
  -k, --kill-others Kill other processes if one exits or dies.[boolean]
220
220
  --kill-others-on-fail Kill other processes if one exits with non zero
221
221
  status code. [boolean]
222
+ --kill-signal Signal to send to other processes if one exits or dies.
223
+ (SIGTERM/SIGKILL, defaults to SIGTERM) [string]
222
224
 
223
225
  Restarting
224
226
  --restart-tries How many times a process that died should restart.
@@ -427,6 +429,6 @@ It contains the following properties:
427
429
  So _null_ means the process didn't terminate normally. This will make **concurrently**
428
430
  to return non-zero exit code too.
429
431
 
430
- - Does this work with the npm-replacements [yarn](https://github.com/yarnpkg/yarn) or [pnpm](https://pnpm.js.org/)?
432
+ - Does this work with the npm-replacements [yarn](https://github.com/yarnpkg/yarn), [pnpm](https://pnpm.js.org/), or [Bun](https://bun.sh/)?
431
433
 
432
- Yes! In all examples above, you may replace "`npm`" with "`yarn`" or "`pnpm`".
434
+ Yes! In all examples above, you may replace "`npm`" with "`yarn`", "`pnpm`", or "`bun`".
@@ -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',
@@ -126,6 +127,12 @@ const args = (0, yargs_1.default)(argsBeforeSep)
126
127
  describe: 'Kill other processes if one exits with non zero status code.',
127
128
  type: 'boolean',
128
129
  },
130
+ 'kill-signal': {
131
+ alias: 'ks',
132
+ describe: 'Signal to send to other processes if one exits or dies. (SIGTERM/SIGKILL, defaults to SIGTERM)',
133
+ type: 'string',
134
+ default: defaults.killSignal,
135
+ },
129
136
  // Prefix
130
137
  prefix: {
131
138
  alias: 'p',
@@ -189,7 +196,7 @@ const args = (0, yargs_1.default)(argsBeforeSep)
189
196
  .group(['m', 'n', 'name-separator', 's', 'r', 'no-color', 'hide', 'g', 'timings', 'P'], 'General')
190
197
  .group(['p', 'c', 'l', 't'], 'Prefix styling')
191
198
  .group(['i', 'default-input-target'], 'Input handling')
192
- .group(['k', 'kill-others-on-fail'], 'Killing other processes')
199
+ .group(['k', 'kill-others-on-fail', 'kill-signal'], 'Killing other processes')
193
200
  .group(['restart-tries', 'restart-after'], 'Restarting')
194
201
  .epilogue(epilogue_1.epilogue)
195
202
  .parseSync();
@@ -208,6 +215,7 @@ const commands = args.passthroughArguments ? args._ : [...args._, ...argsAfterSe
208
215
  : args.killOthersOnFail
209
216
  ? ['failure']
210
217
  : [],
218
+ killSignal: args.killSignal,
211
219
  maxProcesses: args.maxProcesses,
212
220
  raw: args.raw,
213
221
  hide: args.hide.split(','),
@@ -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
  }
@@ -1,7 +1,7 @@
1
1
  import { CommandInfo } from '../command';
2
2
  import { CommandParser } from './command-parser';
3
3
  /**
4
- * Expands commands prefixed with `npm:`, `yarn:` or `pnpm:` into the full version `npm run <command>` and so on.
4
+ * Expands commands prefixed with `npm:`, `yarn:`, `pnpm:`, or `bun:` into the full version `npm run <command>` and so on.
5
5
  */
6
6
  export declare class ExpandNpmShortcut implements CommandParser {
7
7
  parse(commandInfo: CommandInfo): CommandInfo;
@@ -2,11 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ExpandNpmShortcut = void 0;
4
4
  /**
5
- * Expands commands prefixed with `npm:`, `yarn:` or `pnpm:` into the full version `npm run <command>` and so on.
5
+ * Expands commands prefixed with `npm:`, `yarn:`, `pnpm:`, or `bun:` into the full version `npm run <command>` and so on.
6
6
  */
7
7
  class ExpandNpmShortcut {
8
8
  parse(commandInfo) {
9
- const [, npmCmd, cmdName, args] = commandInfo.command.match(/^(npm|yarn|pnpm):(\S+)(.*)/) || [];
9
+ const [, npmCmd, cmdName, args] = commandInfo.command.match(/^(npm|yarn|pnpm|bun):(\S+)(.*)/) || [];
10
10
  if (!cmdName) {
11
11
  return commandInfo;
12
12
  }
@@ -1,7 +1,7 @@
1
1
  import { CommandInfo } from '../command';
2
2
  import { CommandParser } from './command-parser';
3
3
  /**
4
- * Finds wildcards in npm/yarn/pnpm run commands and replaces them with all matching scripts in the
4
+ * Finds wildcards in npm/yarn/pnpm/bun run commands and replaces them with all matching scripts in the
5
5
  * `package.json` file of the current directory.
6
6
  */
7
7
  export declare class ExpandNpmWildcard implements CommandParser {
@@ -8,13 +8,10 @@ const fs_1 = __importDefault(require("fs"));
8
8
  const lodash_1 = __importDefault(require("lodash"));
9
9
  const OMISSION = /\(!([^)]+)\)/;
10
10
  /**
11
- * Finds wildcards in npm/yarn/pnpm run commands and replaces them with all matching scripts in the
11
+ * Finds wildcards in npm/yarn/pnpm/bun run commands and replaces them with all matching scripts in the
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,8 +21,11 @@ class ExpandNpmWildcard {
24
21
  return {};
25
22
  }
26
23
  }
24
+ constructor(readPackage = ExpandNpmWildcard.readPackage) {
25
+ this.readPackage = readPackage;
26
+ }
27
27
  parse(commandInfo) {
28
- const [, npmCmd, cmdName, args] = commandInfo.command.match(/(npm|yarn|pnpm) run (\S+)([^&]*)/) || [];
28
+ const [, npmCmd, cmdName, args] = commandInfo.command.match(/(npm|yarn|pnpm|bun) run (\S+)([^&]*)/) || [];
29
29
  const wildcardPosition = (cmdName || '').indexOf('*');
30
30
  // If the regex didn't match an npm script, or it has no wildcard,
31
31
  // then we have nothing to do here
@@ -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.
@@ -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 ?? 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.
@@ -73,6 +76,10 @@ export declare type ConcurrentlyOptions = {
73
76
  * Defaults to the `tree-kill` module.
74
77
  */
75
78
  kill: KillProcess;
79
+ /**
80
+ * Signal to send to killed processes.
81
+ */
82
+ killSignal?: string;
76
83
  /**
77
84
  * List of additional arguments passed that will get replaced in each command.
78
85
  * If not defined, no argument replacing will happen.
@@ -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");
@@ -69,13 +70,15 @@ function concurrently(baseCommands, baseOptions) {
69
70
  if (options.logger && options.outputStream) {
70
71
  const outputWriter = new output_writer_1.OutputWriter({
71
72
  outputStream: options.outputStream,
72
- group: options.group,
73
+ group: !!options.group,
73
74
  commands,
74
75
  });
75
76
  options.logger.output.subscribe(({ command, text }) => outputWriter.write(command, text));
76
77
  }
77
78
  const commandsLeft = commands.slice();
78
- 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);
79
82
  for (let i = 0; i < maxProcesses; i++) {
80
83
  maybeRunMore(commandsLeft);
81
84
  }
@@ -92,13 +95,9 @@ function concurrently(baseCommands, baseOptions) {
92
95
  exports.concurrently = concurrently;
93
96
  function mapToCommandInfo(command) {
94
97
  if (typeof command === 'string') {
95
- return {
96
- command,
97
- name: '',
98
- env: {},
99
- cwd: '',
100
- };
98
+ return mapToCommandInfo({ command });
101
99
  }
100
+ assert_1.default.ok(command.command, '[concurrently] command cannot be empty');
102
101
  return {
103
102
  command: command.command,
104
103
  name: command.name || '',
@@ -60,3 +60,9 @@ export declare const timings = false;
60
60
  * Passthrough additional arguments to commands (accessible via placeholders) instead of treating them as commands.
61
61
  */
62
62
  export declare const passthroughArguments = false;
63
+ /**
64
+ * Signal to send to other processes if one exits or dies.
65
+ *
66
+ * Defaults to OS specific signal. (SIGTERM on Linux/MacOS)
67
+ */
68
+ export declare const killSignal: string | undefined;
@@ -3,7 +3,7 @@
3
3
  // It's read by the flow controllers, the executable, etc.
4
4
  // Refer to tests for the meaning of the different possible values.
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.passthroughArguments = exports.timings = exports.cwd = exports.timestampFormat = exports.success = exports.restartDelay = exports.restartTries = exports.raw = exports.prefixLength = exports.prefixColors = exports.prefix = exports.nameSeparator = exports.hide = exports.maxProcesses = exports.handleInput = exports.defaultInputTarget = void 0;
6
+ exports.killSignal = exports.passthroughArguments = exports.timings = exports.cwd = exports.timestampFormat = exports.success = exports.restartDelay = exports.restartTries = exports.raw = exports.prefixLength = exports.prefixColors = exports.prefix = exports.nameSeparator = exports.hide = exports.maxProcesses = exports.handleInput = exports.defaultInputTarget = void 0;
7
7
  exports.defaultInputTarget = 0;
8
8
  /**
9
9
  * Whether process.stdin should be forwarded to child processes.
@@ -65,3 +65,9 @@ exports.timings = false;
65
65
  * Passthrough additional arguments to commands (accessible via placeholders) instead of treating them as commands.
66
66
  */
67
67
  exports.passthroughArguments = false;
68
+ /**
69
+ * Signal to send to other processes if one exits or dies.
70
+ *
71
+ * Defaults to OS specific signal. (SIGTERM on Linux/MacOS)
72
+ */
73
+ exports.killSignal = undefined;
@@ -15,10 +15,10 @@ 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;
@@ -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,16 +1,18 @@
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;
10
10
  private readonly conditions;
11
- constructor({ logger, conditions, }: {
11
+ private readonly killSignal;
12
+ constructor({ logger, conditions, killSignal, }: {
12
13
  logger: Logger;
13
14
  conditions: ProcessCloseCondition | ProcessCloseCondition[];
15
+ killSignal: string | undefined;
14
16
  });
15
17
  handle(commands: Command[]): {
16
18
  commands: Command[];
@@ -6,13 +6,15 @@ 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
- constructor({ logger, conditions, }) {
14
+ constructor({ logger, conditions, killSignal, }) {
14
15
  this.logger = logger;
15
16
  this.conditions = lodash_1.default.castArray(conditions);
17
+ this.killSignal = killSignal;
16
18
  }
17
19
  handle(commands) {
18
20
  const conditions = this.conditions.filter((condition) => condition === 'failure' || condition === 'success');
@@ -21,10 +23,10 @@ class KillOthers {
21
23
  }
22
24
  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
25
  closeStates.forEach((closeState) => closeState.subscribe(() => {
24
- const killableCommands = commands.filter((command) => command.killable);
26
+ const killableCommands = commands.filter((command) => command_1.Command.canKill(command));
25
27
  if (killableCommands.length) {
26
- this.logger.logGlobalEvent('Sending SIGTERM to other processes..');
27
- killableCommands.forEach((command) => command.kill());
28
+ this.logger.logGlobalEvent(`Sending ${this.killSignal || 'SIGTERM'} to other processes..`);
29
+ killableCommands.forEach((command) => command.kill(this.killSignal));
28
30
  }
29
31
  }));
30
32
  return { commands };
@@ -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
  });
@@ -2,6 +2,7 @@
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
4
  /// <reference types="node" />
5
+ /// <reference types="node" />
5
6
  import { SpawnOptions } from 'child_process';
6
7
  import supportsColor from 'supports-color';
7
8
  export declare const getSpawnOpts: ({ colorSupport, cwd, process, raw, env, }: {
@@ -11,23 +12,23 @@ export declare const getSpawnOpts: ({ colorSupport, cwd, process, raw, env, }: {
11
12
  *
12
13
  * Defaults to whatever the terminal's stdout support is.
13
14
  */
14
- colorSupport?: Pick<supportsColor.supportsColor.Level, 'level'> | false;
15
+ colorSupport?: false | Pick<supportsColor.supportsColor.Level, "level"> | undefined;
15
16
  /**
16
17
  * The NodeJS process.
17
18
  */
18
- process?: Pick<NodeJS.Process, 'cwd' | 'platform' | 'env'>;
19
+ process?: Pick<NodeJS.Process, "cwd" | "env" | "platform"> | undefined;
19
20
  /**
20
21
  * A custom working directory to spawn processes in.
21
22
  * Defaults to `process.cwd()`.
22
23
  */
23
- cwd?: string;
24
+ cwd?: string | undefined;
24
25
  /**
25
26
  * Whether to customize the options for spawning processes in raw mode.
26
27
  * Defaults to false.
27
28
  */
28
- raw?: boolean;
29
+ raw?: boolean | undefined;
29
30
  /**
30
31
  * Map of custom environment variables to include in the spawn options.
31
32
  */
32
- env?: Record<string, unknown>;
33
+ env?: Record<string, unknown> | undefined;
33
34
  }) => 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,11 @@ 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
+ killSignal: options.killSignal,
61
62
  }),
62
63
  new log_timings_1.LogTimings({
63
- logger: options.timings ? logger : null,
64
+ logger: options.timings ? logger : undefined,
64
65
  timestampFormat: options.timestampFormat,
65
66
  }),
66
67
  ],
@@ -92,7 +92,7 @@ class Logger {
92
92
  }
93
93
  else {
94
94
  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);
95
+ color = lodash_1.default.get(chalk_1.default, command.prefixColor ?? '', defaultColor);
96
96
  }
97
97
  return color(text);
98
98
  }
@@ -25,7 +25,7 @@ function* createColorGenerator(customColors) {
25
25
  // There could be more "auto" values than auto colors so this needs to be able to refill
26
26
  nextAutoColors.push(...PrefixColorSelector.ACCEPTABLE_CONSOLE_COLORS);
27
27
  }
28
- currentColor = nextAutoColors.shift();
28
+ currentColor = String(nextAutoColors.shift());
29
29
  }
30
30
  yield currentColor; // Auto color
31
31
  }
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * While in local development, make sure you've run `npm run build` first.
2
+ * While in local development, make sure you've run `pnpm run build` first.
3
3
  */
4
4
 
5
5
  // eslint-disable-next-line @typescript-eslint/no-var-requires
package/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * While in local development, make sure you've run `npm run build` first.
2
+ * While in local development, make sure you've run `pnpm run build` first.
3
3
  */
4
4
 
5
5
  import concurrently from './dist/src/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "concurrently",
3
- "version": "7.5.0",
3
+ "version": "8.0.0",
4
4
  "description": "Run commands concurrently",
5
5
  "main": "index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -10,7 +10,7 @@
10
10
  "conc": "./dist/bin/concurrently.js"
11
11
  },
12
12
  "engines": {
13
- "node": "^12.20.0 || ^14.13.0 || >=16.0.0"
13
+ "node": "^14.13.0 || >=16.0.0"
14
14
  },
15
15
  "exports": {
16
16
  ".": {
@@ -21,18 +21,6 @@
21
21
  },
22
22
  "./package.json": "./package.json"
23
23
  },
24
- "scripts": {
25
- "build": "tsc --build",
26
- "postbuild": "chmod +x dist/bin/concurrently.js",
27
- "clean": "tsc --build --clean",
28
- "format": "prettier --ignore-path .gitignore --check '**/{!(package-lock).json,*.y?(a)ml,*.md}'",
29
- "format:fix": "npm run format -- --write",
30
- "lint": "eslint . --ext mjs,js,ts --ignore-path .gitignore",
31
- "lint:fix": "npm run lint -- --fix",
32
- "prepublishOnly": "npm run build",
33
- "report-coverage": "cat coverage/lcov.info | coveralls",
34
- "test": "jest"
35
- },
36
24
  "repository": {
37
25
  "type": "git",
38
26
  "url": "https://github.com/open-cli-tools/concurrently.git"
@@ -49,44 +37,45 @@
49
37
  "author": "Kimmo Brunfeldt",
50
38
  "license": "MIT",
51
39
  "dependencies": {
52
- "chalk": "^4.1.0",
53
- "date-fns": "^2.29.1",
40
+ "chalk": "^4.1.2",
41
+ "date-fns": "^2.29.3",
54
42
  "lodash": "^4.17.21",
55
- "rxjs": "^7.0.0",
56
- "shell-quote": "^1.7.3",
57
- "spawn-command": "^0.0.2-1",
58
- "supports-color": "^8.1.0",
43
+ "rxjs": "^7.8.0",
44
+ "shell-quote": "^1.7.4",
45
+ "spawn-command": "0.0.2-1",
46
+ "supports-color": "^8.1.1",
59
47
  "tree-kill": "^1.2.2",
60
- "yargs": "^17.3.1"
48
+ "yargs": "^17.6.2"
61
49
  },
62
50
  "devDependencies": {
63
51
  "@hirez_io/observer-spy": "^2.2.0",
64
- "@swc/core": "^1.2.224",
65
- "@swc/jest": "^0.2.21",
66
- "@types/jest": "^28.1.8",
67
- "@types/lodash": "^4.14.178",
68
- "@types/node": "^16.11.47",
52
+ "@swc/core": "^1.3.24",
53
+ "@swc/jest": "^0.2.24",
54
+ "@types/jest": "^29.2.5",
55
+ "@types/lodash": "^4.14.191",
56
+ "@types/node": "^14.18.36",
69
57
  "@types/shell-quote": "^1.7.1",
70
58
  "@types/supports-color": "^8.1.1",
71
- "@types/yargs": "^17.0.11",
72
- "@typescript-eslint/eslint-plugin": "^5.33.0",
73
- "@typescript-eslint/parser": "^5.33.0",
74
- "coveralls-next": "^4.1.2",
59
+ "@types/yargs": "^17.0.18",
60
+ "@typescript-eslint/eslint-plugin": "^5.48.0",
61
+ "@typescript-eslint/parser": "^5.48.0",
62
+ "coveralls-next": "^4.2.0",
75
63
  "ctrlc-wrapper": "^0.0.4",
76
- "esbuild": "^0.15.1",
77
- "eslint": "^8.21.0",
78
- "eslint-config-prettier": "^8.5.0",
64
+ "esbuild": "^0.16.13",
65
+ "eslint": "^8.31.0",
66
+ "eslint-config-prettier": "^8.6.0",
79
67
  "eslint-plugin-import": "^2.26.0",
80
- "eslint-plugin-jest": "^27.0.4",
81
- "eslint-plugin-prettier": "^4.0.0",
68
+ "eslint-plugin-jest": "^27.2.0",
69
+ "eslint-plugin-prettier": "^4.2.1",
82
70
  "eslint-plugin-simple-import-sort": "^8.0.0",
83
- "jest": "^28.1.3",
71
+ "husky": "^8.0.3",
72
+ "jest": "^29.3.1",
84
73
  "jest-create-mock-instance": "^2.0.0",
85
- "lint-staged": "^12.4.1",
86
- "prettier": "^2.6.2",
87
- "simple-git-hooks": "^2.7.0",
74
+ "lint-staged": "^13.1.0",
75
+ "prettier": "^2.8.1",
76
+ "safe-publish-latest": "^2.0.0",
88
77
  "string-argv": "^0.3.1",
89
- "typescript": "~4.8.3"
78
+ "typescript": "~4.9.4"
90
79
  },
91
80
  "files": [
92
81
  "dist",
@@ -96,11 +85,19 @@
96
85
  "!**/*.spec.js",
97
86
  "!**/*.spec.d.ts"
98
87
  ],
99
- "simple-git-hooks": {
100
- "pre-commit": "npx lint-staged"
101
- },
102
88
  "lint-staged": {
103
- "*.m?{js,ts}": "eslint --fix",
104
- "{!(package-lock).json,*.{y?(a)ml,md}}": "prettier --write"
89
+ "*.?(m){js,ts}": "eslint --fix",
90
+ "*.{json,y?(a)ml,md}": "prettier --write"
91
+ },
92
+ "scripts": {
93
+ "build": "tsc --build",
94
+ "postbuild": "chmod +x dist/bin/concurrently.js",
95
+ "clean": "tsc --build --clean",
96
+ "format": "prettier --check '**/*.{json,y?(a)ml,md}'",
97
+ "format:fix": "pnpm run format --write",
98
+ "lint": "eslint --ignore-path .gitignore --ext mjs,js,ts .",
99
+ "lint:fix": "pnpm run lint --fix",
100
+ "report-coverage": "cat coverage/lcov.info | coveralls",
101
+ "test": "jest"
105
102
  }
106
- }
103
+ }