concurrently 9.2.0 → 10.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 +25 -15
- package/dist/bin/{concurrently.js → index.js} +23 -52
- package/dist/bin/index.spec.d.ts +1 -0
- package/dist/bin/index.spec.js +368 -0
- package/dist/bin/normalize-cli-command.d.ts +1 -0
- package/dist/bin/normalize-cli-command.js +15 -0
- package/dist/bin/normalize-cli-command.spec.d.ts +1 -0
- package/dist/bin/normalize-cli-command.spec.js +36 -0
- package/dist/bin/read-package-json.d.ts +4 -0
- package/dist/bin/read-package-json.js +17 -0
- package/dist/lib/__fixtures__/create-mock-instance.d.ts +2 -0
- package/dist/lib/__fixtures__/create-mock-instance.js +5 -0
- package/dist/lib/__fixtures__/fake-command.d.ts +6 -0
- package/dist/lib/__fixtures__/fake-command.js +37 -0
- package/dist/lib/assert.d.ts +10 -0
- package/dist/lib/assert.js +24 -0
- package/dist/lib/assert.spec.d.ts +1 -0
- package/dist/lib/assert.spec.js +41 -0
- package/dist/{src → lib}/command-parser/expand-arguments.d.ts +7 -7
- package/dist/lib/command-parser/expand-arguments.js +36 -0
- package/dist/lib/command-parser/expand-arguments.spec.d.ts +1 -0
- package/dist/lib/command-parser/expand-arguments.spec.js +57 -0
- package/dist/{src → lib}/command-parser/expand-shortcut.d.ts +2 -2
- package/dist/{src → lib}/command-parser/expand-shortcut.js +1 -5
- package/dist/lib/command-parser/expand-shortcut.spec.d.ts +1 -0
- package/dist/lib/command-parser/expand-shortcut.spec.js +36 -0
- package/dist/{src → lib}/command-parser/expand-wildcard.d.ts +2 -2
- package/dist/{src → lib}/command-parser/expand-wildcard.js +25 -31
- package/dist/lib/command-parser/expand-wildcard.spec.d.ts +1 -0
- package/dist/lib/command-parser/expand-wildcard.spec.js +288 -0
- package/dist/{src → lib}/command.d.ts +7 -10
- package/dist/{src → lib}/command.js +13 -32
- package/dist/lib/command.spec.d.ts +1 -0
- package/dist/lib/command.spec.js +369 -0
- package/dist/{src → lib}/completion-listener.d.ts +2 -3
- package/dist/{src → lib}/completion-listener.js +9 -36
- package/dist/lib/completion-listener.spec.d.ts +1 -0
- package/dist/lib/completion-listener.spec.js +229 -0
- package/dist/{src → lib}/concurrently.d.ts +10 -12
- package/dist/{src → lib}/concurrently.js +34 -47
- package/dist/lib/concurrently.spec.d.ts +1 -0
- package/dist/lib/concurrently.spec.js +320 -0
- package/dist/{src → lib}/date-format.d.ts +2 -2
- package/dist/{src → lib}/date-format.js +101 -77
- package/dist/lib/date-format.spec.d.ts +1 -0
- package/dist/lib/date-format.spec.js +480 -0
- package/dist/{src → lib}/defaults.d.ts +2 -6
- package/dist/{src → lib}/defaults.js +16 -23
- package/dist/{src → lib}/flow-control/input-handler.d.ts +4 -5
- package/dist/{src → lib}/flow-control/input-handler.js +8 -34
- package/dist/lib/flow-control/input-handler.spec.d.ts +1 -0
- package/dist/lib/flow-control/input-handler.spec.js +116 -0
- package/dist/{src → lib}/flow-control/kill-on-signal.d.ts +3 -5
- package/dist/{src → lib}/flow-control/kill-on-signal.js +3 -7
- package/dist/lib/flow-control/kill-on-signal.spec.d.ts +1 -0
- package/dist/lib/flow-control/kill-on-signal.spec.js +63 -0
- package/dist/{src → lib}/flow-control/kill-others.d.ts +3 -4
- package/dist/{src → lib}/flow-control/kill-others.js +8 -15
- package/dist/lib/flow-control/kill-others.spec.d.ts +1 -0
- package/dist/lib/flow-control/kill-others.spec.js +98 -0
- package/dist/{src → lib}/flow-control/log-error.d.ts +3 -3
- package/dist/{src → lib}/flow-control/log-error.js +1 -5
- package/dist/lib/flow-control/log-error.spec.d.ts +1 -0
- package/dist/lib/flow-control/log-error.spec.js +33 -0
- package/dist/{src → lib}/flow-control/log-exit.d.ts +3 -3
- package/dist/{src → lib}/flow-control/log-exit.js +1 -5
- package/dist/lib/flow-control/log-exit.spec.d.ts +1 -0
- package/dist/lib/flow-control/log-exit.spec.js +24 -0
- package/dist/{src → lib}/flow-control/log-output.d.ts +3 -3
- package/dist/{src → lib}/flow-control/log-output.js +1 -5
- package/dist/lib/flow-control/log-output.spec.d.ts +1 -0
- package/dist/lib/flow-control/log-output.spec.js +33 -0
- package/dist/{src → lib}/flow-control/log-timings.d.ts +3 -3
- package/dist/{src → lib}/flow-control/log-timings.js +11 -44
- package/dist/lib/flow-control/log-timings.spec.d.ts +1 -0
- package/dist/lib/flow-control/log-timings.spec.js +89 -0
- package/dist/{src → lib}/flow-control/logger-padding.d.ts +3 -3
- package/dist/{src → lib}/flow-control/logger-padding.js +7 -7
- package/dist/lib/flow-control/logger-padding.spec.d.ts +1 -0
- package/dist/lib/flow-control/logger-padding.spec.js +60 -0
- package/dist/{src → lib}/flow-control/output-error-handler.d.ts +4 -6
- package/dist/{src → lib}/flow-control/output-error-handler.js +3 -7
- package/dist/lib/flow-control/output-error-handler.spec.d.ts +1 -0
- package/dist/lib/flow-control/output-error-handler.spec.js +41 -0
- package/dist/{src → lib}/flow-control/restart-process.d.ts +4 -4
- package/dist/{src → lib}/flow-control/restart-process.js +10 -37
- package/dist/lib/flow-control/restart-process.spec.d.ts +1 -0
- package/dist/lib/flow-control/restart-process.spec.js +127 -0
- package/dist/{src → lib}/flow-control/teardown.d.ts +4 -5
- package/dist/{src → lib}/flow-control/teardown.js +6 -33
- package/dist/lib/flow-control/teardown.spec.d.ts +1 -0
- package/dist/lib/flow-control/teardown.spec.js +93 -0
- package/dist/{src → lib}/index.d.ts +21 -20
- package/dist/lib/index.js +98 -0
- package/dist/lib/jsonc.d.ts +8 -0
- package/dist/{src → lib}/jsonc.js +3 -8
- package/dist/lib/jsonc.spec.d.ts +1 -0
- package/dist/lib/jsonc.spec.js +73 -0
- package/dist/{src → lib}/logger.d.ts +3 -2
- package/dist/{src → lib}/logger.js +112 -53
- package/dist/lib/logger.spec.d.ts +1 -0
- package/dist/lib/logger.spec.js +507 -0
- package/dist/{src → lib}/observables.d.ts +1 -2
- package/dist/{src → lib}/observables.js +3 -7
- package/dist/lib/observables.spec.d.ts +1 -0
- package/dist/lib/observables.spec.js +29 -0
- package/dist/{src → lib}/output-writer.d.ts +3 -4
- package/dist/{src → lib}/output-writer.js +4 -31
- package/dist/lib/output-writer.spec.d.ts +1 -0
- package/dist/lib/output-writer.spec.js +96 -0
- package/dist/lib/prefix-color-selector.d.ts +21 -0
- package/dist/{src → lib}/prefix-color-selector.js +14 -31
- package/dist/lib/prefix-color-selector.spec.d.ts +1 -0
- package/dist/lib/prefix-color-selector.spec.js +159 -0
- package/dist/{src → lib}/spawn.d.ts +19 -16
- package/dist/lib/spawn.js +105 -0
- package/dist/lib/spawn.spec.d.ts +1 -0
- package/dist/lib/spawn.spec.js +100 -0
- package/dist/lib/utils.d.ts +25 -0
- package/dist/lib/utils.js +52 -0
- package/dist/lib/utils.spec.d.ts +1 -0
- package/dist/lib/utils.spec.js +58 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/README.md +6 -0
- package/docs/cli/passthrough-arguments.md +8 -8
- package/docs/cli/prefixing.md +44 -4
- package/docs/cli/shortcuts.md +2 -2
- package/docs/shell-resolution.md +48 -0
- package/package.json +64 -85
- package/dist/bin/read-package.d.ts +0 -6
- package/dist/bin/read-package.js +0 -47
- package/dist/src/assert.d.ts +0 -5
- package/dist/src/assert.js +0 -16
- package/dist/src/command-parser/command-parser.d.ts +0 -19
- package/dist/src/command-parser/command-parser.js +0 -2
- package/dist/src/command-parser/expand-arguments.js +0 -39
- package/dist/src/command-parser/strip-quotes.d.ts +0 -16
- package/dist/src/command-parser/strip-quotes.js +0 -17
- package/dist/src/flow-control/flow-controller.d.ts +0 -13
- package/dist/src/flow-control/flow-controller.js +0 -2
- package/dist/src/index.js +0 -99
- package/dist/src/jsonc.d.ts +0 -8
- package/dist/src/prefix-color-selector.d.ts +0 -11
- package/dist/src/spawn.js +0 -49
- package/index.d.mts +0 -7
- package/index.d.ts +0 -11
- package/index.js +0 -14
- package/index.mjs +0 -10
- /package/dist/bin/{concurrently.d.ts → index.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://github.com/open-cli-tools/concurrently/releases)
|
|
4
4
|
[](https://github.com/open-cli-tools/concurrently/blob/main/LICENSE)
|
|
5
5
|
[](https://www.npmjs.com/package/concurrently)
|
|
6
|
-
[](https://github.com/open-cli-tools/concurrently/actions/workflows/ci.yml)
|
|
7
7
|
[](https://coveralls.io/github/open-cli-tools/concurrently?branch=main)
|
|
8
8
|
|
|
9
9
|
Run multiple commands concurrently.
|
|
@@ -67,11 +67,18 @@ concurrently 'command1 arg' 'command2 arg'
|
|
|
67
67
|
Otherwise **concurrently** would try to run 4 separate commands:
|
|
68
68
|
`command1`, `arg`, `command2`, `arg`.
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
> [!IMPORTANT]
|
|
71
|
+
> Windows only supports double quotes:
|
|
72
|
+
>
|
|
73
|
+
> ```bash
|
|
74
|
+
> concurrently "command1 arg" "command2 arg"
|
|
75
|
+
> ```
|
|
76
|
+
>
|
|
77
|
+
> Remember to escape the double quotes in your package.json when using Windows:
|
|
78
|
+
>
|
|
79
|
+
> ```json
|
|
80
|
+
> "start": "concurrently \"command1 arg\" \"command2 arg\""
|
|
81
|
+
> ```
|
|
75
82
|
|
|
76
83
|
You can always check concurrently's flag list by running `concurrently --help`.
|
|
77
84
|
For the version, run `concurrently --version`.
|
|
@@ -90,21 +97,25 @@ Check out documentation and other usage examples in the [`docs` directory](./doc
|
|
|
90
97
|
- `options` (optional): an object containing any of the below:
|
|
91
98
|
- `cwd`: the working directory to be used by all commands. Can be overridden per command.
|
|
92
99
|
Default: `process.cwd()`.
|
|
100
|
+
- `shell`: shell executable used to run command strings. When unset, uses `npm_config_script_shell` if present (for example when run via `npm run`), otherwise `cmd.exe` on Windows or `/bin/sh` elsewhere. See [shell resolution](./docs/shell-resolution.md).
|
|
93
101
|
- `defaultInputTarget`: the default input target when reading from `inputStream`.
|
|
94
102
|
Default: `0`.
|
|
95
103
|
- `handleInput`: when `true`, reads input from `process.stdin`.
|
|
96
|
-
- `inputStream`: a [`Readable` stream](https://nodejs.org/
|
|
104
|
+
- `inputStream`: a [`Readable` stream](https://nodejs.org/docs/latest/api/stream.html#readable-streams)
|
|
97
105
|
to read the input from. Should only be used in the rare instance you would like to stream anything other than `process.stdin`. Overrides `handleInput`.
|
|
98
106
|
- `pauseInputStreamOnFinish`: by default, pauses the input stream (`process.stdin` when `handleInput` is enabled, or `inputStream` if provided) when all of the processes have finished. If you need to read from the input stream after `concurrently` has finished, set this to `false`. ([#252](https://github.com/kimmobrunfeldt/concurrently/issues/252)).
|
|
99
107
|
- `killOthersOn`: once the first command exits with one of these statuses, kill other commands.
|
|
100
108
|
Can be an array containing the strings `success` (status code zero) and/or `failure` (non-zero exit status).
|
|
101
109
|
- `maxProcesses`: how many processes should run at once.
|
|
102
|
-
- `outputStream`: a [`Writable` stream](https://nodejs.org/
|
|
110
|
+
- `outputStream`: a [`Writable` stream](https://nodejs.org/docs/latest/api/stream.html#writable-streams)
|
|
103
111
|
to write logs to. Default: `process.stdout`.
|
|
104
112
|
- `prefix`: the prefix type to use when logging processes output.
|
|
105
113
|
Possible values: `index`, `pid`, `time`, `command`, `name`, `none`, or a template (eg `[{time} process: {pid}]`).
|
|
106
114
|
Default: the name of the process, or its index if no name is set.
|
|
107
|
-
|
|
115
|
+
Templates can wrap any portion of the prefix with `{color}` and `{/color}` to restrict coloring to that region (eg `[{color}{name}{/color}]` colors only the name, leaving the brackets uncolored). If either marker is omitted the missing side is implicit, so a template with no markers is colored in full.
|
|
116
|
+
- `prefixColors`: a list of colors or a string as supported by [Chalk](https://www.npmjs.com/package/chalk) and additional style `auto` for an automatically picked color.
|
|
117
|
+
Supports all Chalk color functions: `#RRGGBB`, `bg#RRGGBB`, `hex()`, `bgHex()`, `rgb()`, `bgRgb()`, `ansi256()`, `bgAnsi256()`.
|
|
118
|
+
Functions and modifiers can be chained (e.g., `rgb(255,136,0).bold`, `black.bgHex(#00FF00).dim`).
|
|
108
119
|
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.
|
|
109
120
|
Prefix colors specified per-command take precedence over this list.
|
|
110
121
|
- `prefixLength`: how many characters to show when prefixing with `command`. Default: `10`
|
|
@@ -142,7 +153,7 @@ const { result } = concurrently(
|
|
|
142
153
|
],
|
|
143
154
|
{
|
|
144
155
|
prefix: 'name',
|
|
145
|
-
|
|
156
|
+
killOthersOn: ['failure', 'success'],
|
|
146
157
|
restartTries: 3,
|
|
147
158
|
cwd: path.resolve(__dirname, 'scripts'),
|
|
148
159
|
},
|
|
@@ -174,7 +185,6 @@ It has the following properties:
|
|
|
174
185
|
- `timer`: an RxJS observable to the command's timing events (e.g. starting, stopping).
|
|
175
186
|
- `stateChange`: an RxJS observable for changes to the command's `state` property.
|
|
176
187
|
- `messages`: an object with the following properties:
|
|
177
|
-
|
|
178
188
|
- `incoming`: an RxJS observable for the IPC messages received from the underlying process.
|
|
179
189
|
- `outgoing`: an RxJS observable for the IPC messages sent to the underlying process.
|
|
180
190
|
|
|
@@ -213,18 +223,18 @@ It contains the following properties:
|
|
|
213
223
|
|
|
214
224
|
## FAQ
|
|
215
225
|
|
|
216
|
-
- Process exited with code
|
|
226
|
+
- Process exited with code `null`?
|
|
217
227
|
|
|
218
|
-
From [Node child_process documentation](
|
|
228
|
+
From [Node child_process documentation](https://nodejs.org/docs/latest/api/child_process.html#event-exit), `exit` event:
|
|
219
229
|
|
|
220
230
|
> This event is emitted after the child process ends. If the process
|
|
221
231
|
> terminated normally, code is the final exit code of the process,
|
|
222
232
|
> otherwise null. If the process terminated due to receipt of a signal,
|
|
223
233
|
> signal is the string name of the signal, otherwise null.
|
|
224
234
|
|
|
225
|
-
So
|
|
235
|
+
So `null` means the process didn't terminate normally. This will make **concurrently**
|
|
226
236
|
to return non-zero exit code too.
|
|
227
237
|
|
|
228
|
-
- Does this work with the npm-replacements [yarn](https://
|
|
238
|
+
- Does this work with the npm-replacements [yarn](https://yarnpkg.com/), [pnpm](https://pnpm.io/), or [Bun](https://bun.sh/)?
|
|
229
239
|
|
|
230
240
|
Yes! In all examples above, you may replace "`npm`" with "`yarn`", "`pnpm`", or "`bun`".
|
|
@@ -1,43 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
o[k2] = m[k];
|
|
13
|
-
}));
|
|
14
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
-
}) : function(o, v) {
|
|
17
|
-
o["default"] = v;
|
|
18
|
-
});
|
|
19
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
-
if (mod && mod.__esModule) return mod;
|
|
21
|
-
var result = {};
|
|
22
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
-
__setModuleDefault(result, mod);
|
|
24
|
-
return result;
|
|
25
|
-
};
|
|
26
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
|
-
};
|
|
29
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
31
|
-
const yargs_1 = __importDefault(require("yargs"));
|
|
32
|
-
const helpers_1 = require("yargs/helpers");
|
|
33
|
-
const assert_1 = require("../src/assert");
|
|
34
|
-
const defaults = __importStar(require("../src/defaults"));
|
|
35
|
-
const index_1 = require("../src/index");
|
|
36
|
-
const read_package_1 = require("./read-package");
|
|
37
|
-
const version = String((0, read_package_1.readPackage)().version);
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import yargs from 'yargs';
|
|
4
|
+
import { hideBin } from 'yargs/helpers';
|
|
5
|
+
import * as defaults from '../lib/defaults.js';
|
|
6
|
+
import { concurrently } from '../lib/index.js';
|
|
7
|
+
import { castArray, splitOutsideParens } from '../lib/utils.js';
|
|
8
|
+
import { normalizeCliCommand } from './normalize-cli-command.js';
|
|
9
|
+
import { readPackageJson } from './read-package-json.js';
|
|
10
|
+
const version = String(readPackageJson().version);
|
|
38
11
|
const epilogue = `For documentation and more examples, visit:\nhttps://github.com/open-cli-tools/concurrently/tree/v${version}/docs`;
|
|
39
12
|
// Clean-up arguments (yargs expects only the arguments after the program name)
|
|
40
|
-
const program = (
|
|
13
|
+
const program = yargs(hideBin(process.argv))
|
|
41
14
|
.parserConfiguration({
|
|
42
15
|
// Avoids options that can be specified multiple times from requiring a `--` to pass commands
|
|
43
16
|
'greedy-arrays': false,
|
|
@@ -67,11 +40,6 @@ const program = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
|
67
40
|
'Example names: "main,browser,server"',
|
|
68
41
|
type: 'string',
|
|
69
42
|
},
|
|
70
|
-
'name-separator': {
|
|
71
|
-
describe: 'The character to split <names> on. Example usage:\n' +
|
|
72
|
-
'-n "styles|scripts|server" --name-separator "|"',
|
|
73
|
-
default: defaults.nameSeparator,
|
|
74
|
-
},
|
|
75
43
|
success: {
|
|
76
44
|
alias: 's',
|
|
77
45
|
describe: 'Which command(s) must exit with code 0 in order for concurrently exit with ' +
|
|
@@ -92,7 +60,7 @@ const program = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
|
92
60
|
type: 'boolean',
|
|
93
61
|
},
|
|
94
62
|
// This one is provided for free. Chalk reads this itself and removes colors.
|
|
95
|
-
// https://www.npmjs.com/package/chalk#
|
|
63
|
+
// https://www.npmjs.com/package/chalk#supportscolor
|
|
96
64
|
'no-color': {
|
|
97
65
|
describe: 'Disables colors from logging',
|
|
98
66
|
type: 'boolean',
|
|
@@ -113,6 +81,10 @@ const program = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
|
113
81
|
type: 'boolean',
|
|
114
82
|
default: defaults.timings,
|
|
115
83
|
},
|
|
84
|
+
shell: {
|
|
85
|
+
describe: 'Shell to run commands with. Defaults to cmd.exe on Windows and /bin/sh elsewhere.',
|
|
86
|
+
type: 'string',
|
|
87
|
+
},
|
|
116
88
|
'passthrough-arguments': {
|
|
117
89
|
alias: 'P',
|
|
118
90
|
describe: 'Passthrough additional arguments to commands (accessible via placeholders) ' +
|
|
@@ -157,7 +129,7 @@ const program = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
|
157
129
|
},
|
|
158
130
|
'prefix-colors': {
|
|
159
131
|
alias: 'c',
|
|
160
|
-
describe: 'Comma-separated list of
|
|
132
|
+
describe: 'Comma-separated list of Chalk colors to use on prefixes. ' +
|
|
161
133
|
'If there are more commands than colors, the last color will be repeated.\n' +
|
|
162
134
|
'- Available modifiers: reset, bold, dim, italic, underline, inverse, hidden, strikethrough\n' +
|
|
163
135
|
'- Available colors: black, red, green, yellow, blue, magenta, cyan, white, gray, \n' +
|
|
@@ -211,24 +183,22 @@ const program = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
|
211
183
|
'Can be either the index or the name of the process.',
|
|
212
184
|
},
|
|
213
185
|
})
|
|
214
|
-
.group(['m', 'n', '
|
|
186
|
+
.group(['m', 'n', 's', 'r', 'no-color', 'hide', 'g', 'timings', 'shell', 'P', 'teardown'], 'General')
|
|
215
187
|
.group(['p', 'c', 'l', 't', 'pad-prefix'], 'Prefix styling')
|
|
216
188
|
.group(['i', 'default-input-target'], 'Input handling')
|
|
217
189
|
.group(['k', 'kill-others-on-fail', 'kill-signal', 'kill-timeout'], 'Killing other processes')
|
|
218
190
|
.group(['restart-tries', 'restart-after'], 'Restarting')
|
|
219
191
|
.epilogue(epilogue);
|
|
220
192
|
const args = program.parseSync();
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const names = (args.names || '').split(args.nameSeparator);
|
|
224
|
-
const additionalArguments = lodash_1.default.castArray(args['--'] ?? []).map(String);
|
|
193
|
+
const names = (args.names || '').split(',');
|
|
194
|
+
const additionalArguments = castArray(args['--'] ?? []).map(String);
|
|
225
195
|
const commands = args.passthroughArguments ? args._ : args._.concat(additionalArguments);
|
|
226
196
|
if (!commands.length) {
|
|
227
197
|
program.showHelp();
|
|
228
198
|
process.exit();
|
|
229
199
|
}
|
|
230
|
-
|
|
231
|
-
command: String(command),
|
|
200
|
+
concurrently(commands.map((command, index) => ({
|
|
201
|
+
command: normalizeCliCommand(String(command)),
|
|
232
202
|
name: names[index],
|
|
233
203
|
})), {
|
|
234
204
|
handleInput: args.handleInput,
|
|
@@ -245,7 +215,7 @@ if (!commands.length) {
|
|
|
245
215
|
hide: args.hide.split(','),
|
|
246
216
|
group: args.group,
|
|
247
217
|
prefix: args.prefix,
|
|
248
|
-
prefixColors: args.prefixColors
|
|
218
|
+
prefixColors: splitOutsideParens(args.prefixColors, ','),
|
|
249
219
|
prefixLength: args.prefixLength,
|
|
250
220
|
padPrefix: args.padPrefix,
|
|
251
221
|
restartDelay: args.restartAfter === 'exponential' ? 'exponential' : Number(args.restartAfter),
|
|
@@ -253,6 +223,7 @@ if (!commands.length) {
|
|
|
253
223
|
successCondition: args.success,
|
|
254
224
|
timestampFormat: args.timestampFormat,
|
|
255
225
|
timings: args.timings,
|
|
226
|
+
shell: args.shell,
|
|
256
227
|
teardown: args.teardown,
|
|
257
228
|
additionalArguments: args.passthroughArguments ? additionalArguments : undefined,
|
|
258
229
|
}).result.then(() => process.exit(0), () => process.exit(1));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { execSync, spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import readline from 'node:readline';
|
|
5
|
+
import { subscribeSpyTo } from '@hirez_io/observer-spy';
|
|
6
|
+
import { sendCtrlC, spawnWithWrapper } from 'ctrlc-wrapper';
|
|
7
|
+
import Rx from 'rxjs';
|
|
8
|
+
import { map } from 'rxjs/operators';
|
|
9
|
+
import stringArgv from 'string-argv';
|
|
10
|
+
import { beforeAll, describe, expect, it } from 'vitest';
|
|
11
|
+
import { escapeRegExp } from '../lib/utils.js';
|
|
12
|
+
const isWindows = process.platform === 'win32';
|
|
13
|
+
const createKillMessage = (prefix, signal) => {
|
|
14
|
+
const map = {
|
|
15
|
+
SIGTERM: isWindows ? 1 : '(SIGTERM|143)',
|
|
16
|
+
// Could theoretically be anything (e.g. 0) if process has SIGINT handler
|
|
17
|
+
SIGINT: isWindows ? '(3221225786|0)' : '(SIGINT|130|0)',
|
|
18
|
+
};
|
|
19
|
+
return new RegExp(`${escapeRegExp(prefix)} exited with code ${map[signal] ?? signal}`);
|
|
20
|
+
};
|
|
21
|
+
const concurrentlyBin = path.join(__dirname, '..', 'dist', 'bin', 'index.js');
|
|
22
|
+
// Build once, then spawn the real CLI without a shell (see open-cli-tools/concurrently#346).
|
|
23
|
+
beforeAll(() => {
|
|
24
|
+
execSync('pnpm run build', { cwd: path.join(__dirname, '..'), stdio: 'pipe' });
|
|
25
|
+
}, 20_000);
|
|
26
|
+
/**
|
|
27
|
+
* Creates a child process running 'concurrently' with the given args.
|
|
28
|
+
* Returns observables for its combined stdout + stderr output, close events, pid, and stdin stream.
|
|
29
|
+
*/
|
|
30
|
+
const run = (args, ctrlcWrapper) => {
|
|
31
|
+
const spawnFn = ctrlcWrapper ? spawnWithWrapper : spawn;
|
|
32
|
+
const child = spawnFn('node', [concurrentlyBin, ...stringArgv(args)], {
|
|
33
|
+
cwd: __dirname,
|
|
34
|
+
env: {
|
|
35
|
+
...process.env,
|
|
36
|
+
// VS Code extension might allow colors, but this breaks assertions.
|
|
37
|
+
// Force colors to be disabled to avoid that.
|
|
38
|
+
FORCE_COLOR: '0',
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
const stdout = readline.createInterface({
|
|
42
|
+
input: child.stdout,
|
|
43
|
+
});
|
|
44
|
+
const stderr = readline.createInterface({
|
|
45
|
+
input: child.stderr,
|
|
46
|
+
});
|
|
47
|
+
const log = new Rx.Observable((observer) => {
|
|
48
|
+
stdout.on('line', (line) => {
|
|
49
|
+
observer.next(line);
|
|
50
|
+
});
|
|
51
|
+
stderr.on('line', (line) => {
|
|
52
|
+
observer.next(line);
|
|
53
|
+
});
|
|
54
|
+
child.on('close', () => {
|
|
55
|
+
observer.complete();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
const exit = Rx.firstValueFrom(Rx.fromEvent(child, 'exit').pipe(map((event) => {
|
|
59
|
+
const exit = event;
|
|
60
|
+
return {
|
|
61
|
+
/** The exit code if the child exited on its own. */
|
|
62
|
+
code: exit[0],
|
|
63
|
+
/** The signal by which the child process was terminated. */
|
|
64
|
+
signal: exit[1],
|
|
65
|
+
};
|
|
66
|
+
})));
|
|
67
|
+
const getLogLines = async () => {
|
|
68
|
+
const observerSpy = subscribeSpyTo(log);
|
|
69
|
+
await observerSpy.onComplete();
|
|
70
|
+
observerSpy.unsubscribe();
|
|
71
|
+
return observerSpy.getValues();
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
process: child,
|
|
75
|
+
stdin: child.stdin,
|
|
76
|
+
pid: child.pid,
|
|
77
|
+
log,
|
|
78
|
+
getLogLines,
|
|
79
|
+
exit,
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
it('has help command', async () => {
|
|
83
|
+
const exit = await run('--help').exit;
|
|
84
|
+
expect(exit.code).toBe(0);
|
|
85
|
+
});
|
|
86
|
+
it('prints help when no arguments are passed', async () => {
|
|
87
|
+
const exit = await run('').exit;
|
|
88
|
+
expect(exit.code).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
describe('has version command', () => {
|
|
91
|
+
const pkg = fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8');
|
|
92
|
+
const { version } = JSON.parse(pkg);
|
|
93
|
+
it.each(['--version', '-V', '-v'])('%s', async (arg) => {
|
|
94
|
+
const child = run(arg);
|
|
95
|
+
const log = await child.getLogLines();
|
|
96
|
+
expect(log).toContain(version);
|
|
97
|
+
const { code } = await child.exit;
|
|
98
|
+
expect(code).toBe(0);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('exiting conditions', () => {
|
|
102
|
+
it('is of success by default when running successful commands', async () => {
|
|
103
|
+
const exit = await run('"echo foo" "echo bar"').exit;
|
|
104
|
+
expect(exit.code).toBe(0);
|
|
105
|
+
});
|
|
106
|
+
it('strips outer CLI wrapper quotes before running a command', async () => {
|
|
107
|
+
const child = run('"echo foo"');
|
|
108
|
+
const lines = await child.getLogLines();
|
|
109
|
+
const exit = await child.exit;
|
|
110
|
+
expect(lines).toContainEqual(expect.stringContaining('foo'));
|
|
111
|
+
expect(exit.code).toBe(0);
|
|
112
|
+
});
|
|
113
|
+
it('is of failure by default when one of the command fails', async () => {
|
|
114
|
+
const exit = await run('"echo foo" "exit 1"').exit;
|
|
115
|
+
expect(exit.code).toBeGreaterThan(0);
|
|
116
|
+
});
|
|
117
|
+
it('is of success when --success=first and first command to exit succeeds', async () => {
|
|
118
|
+
const exit = await run('--success=first "echo foo" "node __fixtures__/sleep.js 0.5 && exit 1"').exit;
|
|
119
|
+
expect(exit.code).toBe(0);
|
|
120
|
+
});
|
|
121
|
+
it('is of failure when --success=first and first command to exit fails', async () => {
|
|
122
|
+
const exit = await run('--success=first "exit 1" "node __fixtures__/sleep.js 0.5 && echo foo"').exit;
|
|
123
|
+
expect(exit.code).toBeGreaterThan(0);
|
|
124
|
+
});
|
|
125
|
+
describe('is of success when --success=last and last command to exit succeeds', () => {
|
|
126
|
+
it.each(['--success=last', '-s last'])('%s', async (arg) => {
|
|
127
|
+
const exit = await run(`${arg} "exit 1" "node __fixtures__/sleep.js 0.5 && echo foo"`)
|
|
128
|
+
.exit;
|
|
129
|
+
expect(exit.code).toBe(0);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
it('is of failure when --success=last and last command to exit fails', async () => {
|
|
133
|
+
const exit = await run('--success=last "echo foo" "node __fixtures__/sleep.js 0.5 && exit 1"').exit;
|
|
134
|
+
expect(exit.code).toBeGreaterThan(0);
|
|
135
|
+
});
|
|
136
|
+
it('is of success when a SIGINT is sent', async () => {
|
|
137
|
+
// Windows doesn't support sending signals like on POSIX platforms.
|
|
138
|
+
// However, in a console, processes can be interrupted with CTRL+C (like a SIGINT).
|
|
139
|
+
// This is what we simulate here with the help of a wrapper application.
|
|
140
|
+
const child = run('"node __fixtures__/read-echo.js"', isWindows);
|
|
141
|
+
// Wait for command to have started before sending SIGINT
|
|
142
|
+
child.log.subscribe((line) => {
|
|
143
|
+
if (/READING/.test(line)) {
|
|
144
|
+
if (isWindows) {
|
|
145
|
+
// Instruct the wrapper to send CTRL+C to its child
|
|
146
|
+
sendCtrlC(child.process);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
process.kill(Number(child.pid), 'SIGINT');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
const lines = await child.getLogLines();
|
|
154
|
+
const exit = await child.exit;
|
|
155
|
+
expect(exit.code).toBe(0);
|
|
156
|
+
expect(lines).toContainEqual(expect.stringMatching(createKillMessage('[0] node __fixtures__/read-echo.js',
|
|
157
|
+
// TODO: Flappy value due to race condition, sometimes killed by concurrently (exit code 1),
|
|
158
|
+
// sometimes terminated on its own (exit code 0).
|
|
159
|
+
// Related issue: https://github.com/open-cli-tools/concurrently/issues/283
|
|
160
|
+
isWindows ? '(3221225786|0|1)' : 'SIGINT')));
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
describe('does not log any extra output', () => {
|
|
164
|
+
it.each(['--raw', '-r'])('%s', async (arg) => {
|
|
165
|
+
const lines = await run(`${arg} "echo foo" "echo bar"`).getLogLines();
|
|
166
|
+
expect(lines).toHaveLength(2);
|
|
167
|
+
expect(lines).toContainEqual(expect.stringContaining('foo'));
|
|
168
|
+
expect(lines).toContainEqual(expect.stringContaining('bar'));
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe('--hide', () => {
|
|
172
|
+
it('hides the output of a process by its index', async () => {
|
|
173
|
+
const lines = await run('--hide 1 "echo foo" "echo bar"').getLogLines();
|
|
174
|
+
expect(lines).toContainEqual(expect.stringContaining('foo'));
|
|
175
|
+
expect(lines).not.toContainEqual(expect.stringContaining('bar'));
|
|
176
|
+
});
|
|
177
|
+
it('hides the output of a process by its name', async () => {
|
|
178
|
+
const lines = await run('-n foo,bar --hide bar "echo foo" "echo bar"').getLogLines();
|
|
179
|
+
expect(lines).toContainEqual(expect.stringContaining('foo'));
|
|
180
|
+
expect(lines).not.toContainEqual(expect.stringContaining('bar'));
|
|
181
|
+
});
|
|
182
|
+
it('hides the output of a process by its index in raw mode', async () => {
|
|
183
|
+
const lines = await run('--hide 1 --raw "echo foo" "echo bar"').getLogLines();
|
|
184
|
+
expect(lines).toHaveLength(1);
|
|
185
|
+
expect(lines).toContainEqual(expect.stringContaining('foo'));
|
|
186
|
+
expect(lines).not.toContainEqual(expect.stringContaining('bar'));
|
|
187
|
+
});
|
|
188
|
+
it('hides the output of a process by its name in raw mode', async () => {
|
|
189
|
+
const lines = await run('-n foo,bar --hide bar --raw "echo foo" "echo bar"').getLogLines();
|
|
190
|
+
expect(lines).toHaveLength(1);
|
|
191
|
+
expect(lines).toContainEqual(expect.stringContaining('foo'));
|
|
192
|
+
expect(lines).not.toContainEqual(expect.stringContaining('bar'));
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
describe('--group', () => {
|
|
196
|
+
it('groups output per process', async () => {
|
|
197
|
+
const lines = await run('--group "echo foo && node __fixtures__/sleep.js 1 && echo bar" "echo baz"').getLogLines();
|
|
198
|
+
expect(lines.slice(0, 4)).toEqual([
|
|
199
|
+
expect.stringContaining('foo'),
|
|
200
|
+
expect.stringContaining('bar'),
|
|
201
|
+
expect.any(String),
|
|
202
|
+
expect.stringContaining('baz'),
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe('--names', () => {
|
|
207
|
+
describe('prefixes with names', () => {
|
|
208
|
+
it.each(['--names', '-n'])('%s', async (arg) => {
|
|
209
|
+
const lines = await run(`${arg} foo,bar "echo foo" "echo bar"`).getLogLines();
|
|
210
|
+
expect(lines).toContainEqual(expect.stringContaining('[foo] foo'));
|
|
211
|
+
expect(lines).toContainEqual(expect.stringContaining('[bar] bar'));
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe('specifies custom prefix', () => {
|
|
216
|
+
it.each(['--prefix', '-p'])('%s', async (arg) => {
|
|
217
|
+
const lines = await run(`${arg} command "echo foo" "echo bar"`).getLogLines();
|
|
218
|
+
expect(lines).toContainEqual(expect.stringContaining('[echo foo] foo'));
|
|
219
|
+
expect(lines).toContainEqual(expect.stringContaining('[echo bar] bar'));
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
describe('specifies custom prefix length', () => {
|
|
223
|
+
it.each(['--prefix command --prefix-length 5', '-p command -l 5'])('%s', async (arg) => {
|
|
224
|
+
const lines = await run(`${arg} "echo foo" "echo bar"`).getLogLines();
|
|
225
|
+
expect(lines).toContainEqual(expect.stringContaining('[ec..o] foo'));
|
|
226
|
+
expect(lines).toContainEqual(expect.stringContaining('[ec..r] bar'));
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
describe('--pad-prefix', () => {
|
|
230
|
+
it('pads prefixes with spaces', async () => {
|
|
231
|
+
const lines = await run('--pad-prefix -n foo,barbaz "echo foo" "echo bar"').getLogLines();
|
|
232
|
+
expect(lines).toContainEqual(expect.stringContaining('[foo ]'));
|
|
233
|
+
expect(lines).toContainEqual(expect.stringContaining('[barbaz]'));
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
describe('--restart-tries', () => {
|
|
237
|
+
it('changes how many times a command will restart', async () => {
|
|
238
|
+
const lines = await run('--restart-tries 1 "exit 1"').getLogLines();
|
|
239
|
+
expect(lines).toEqual([
|
|
240
|
+
expect.stringContaining('[0] exit 1 exited with code 1'),
|
|
241
|
+
expect.stringContaining('[0] exit 1 restarted'),
|
|
242
|
+
expect.stringContaining('[0] exit 1 exited with code 1'),
|
|
243
|
+
]);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
describe('--kill-others', () => {
|
|
247
|
+
describe('kills on success', () => {
|
|
248
|
+
it.each(['--kill-others', '-k'])('%s', async (arg) => {
|
|
249
|
+
const lines = await run(`${arg} "node __fixtures__/sleep.js 10" "exit 0"`).getLogLines();
|
|
250
|
+
expect(lines).toContainEqual(expect.stringContaining('[1] exit 0 exited with code 0'));
|
|
251
|
+
expect(lines).toContainEqual(expect.stringContaining('Sending SIGTERM to other processes'));
|
|
252
|
+
expect(lines).toContainEqual(expect.stringMatching(createKillMessage('[0] node __fixtures__/sleep.js 10', 'SIGTERM')));
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
it('kills on failure', async () => {
|
|
256
|
+
const lines = await run('--kill-others "node __fixtures__/sleep.js 10" "exit 1"').getLogLines();
|
|
257
|
+
expect(lines).toContainEqual(expect.stringContaining('[1] exit 1 exited with code 1'));
|
|
258
|
+
expect(lines).toContainEqual(expect.stringContaining('Sending SIGTERM to other processes'));
|
|
259
|
+
expect(lines).toContainEqual(expect.stringMatching(createKillMessage('[0] node __fixtures__/sleep.js 10', 'SIGTERM')));
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
describe('--kill-others-on-fail', () => {
|
|
263
|
+
it('does not kill on success', async () => {
|
|
264
|
+
const lines = await run('--kill-others-on-fail "node __fixtures__/sleep.js 0.5" "exit 0"').getLogLines();
|
|
265
|
+
expect(lines).toContainEqual(expect.stringContaining('[1] exit 0 exited with code 0'));
|
|
266
|
+
expect(lines).toContainEqual(expect.stringContaining('[0] node __fixtures__/sleep.js 0.5 exited with code 0'));
|
|
267
|
+
});
|
|
268
|
+
it('kills on failure', async () => {
|
|
269
|
+
const lines = await run('--kill-others-on-fail "node __fixtures__/sleep.js 10" "exit 1"').getLogLines();
|
|
270
|
+
expect(lines).toContainEqual(expect.stringContaining('[1] exit 1 exited with code 1'));
|
|
271
|
+
expect(lines).toContainEqual(expect.stringContaining('Sending SIGTERM to other processes'));
|
|
272
|
+
expect(lines).toContainEqual(expect.stringMatching(createKillMessage('[0] node __fixtures__/sleep.js 10', 'SIGTERM')));
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
describe('--handle-input', () => {
|
|
276
|
+
describe('forwards input to first process by default', () => {
|
|
277
|
+
it.each(['--handle-input', '-i'])('%s', async (arg) => {
|
|
278
|
+
const child = run(`${arg} "node __fixtures__/read-echo.js"`);
|
|
279
|
+
child.log.subscribe((line) => {
|
|
280
|
+
if (/READING/.test(line)) {
|
|
281
|
+
child.stdin.write('stop\n');
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
const lines = await child.getLogLines();
|
|
285
|
+
const exit = await child.exit;
|
|
286
|
+
expect(exit.code).toBe(0);
|
|
287
|
+
expect(lines).toContainEqual(expect.stringContaining('[0] stop'));
|
|
288
|
+
expect(lines).toContainEqual(expect.stringContaining('[0] node __fixtures__/read-echo.js exited with code 0'));
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
it('forwards input to process --default-input-target', async () => {
|
|
292
|
+
const child = run('-ki --default-input-target 1 "node __fixtures__/read-echo.js" "node __fixtures__/read-echo.js"');
|
|
293
|
+
child.log.subscribe((line) => {
|
|
294
|
+
if (/\[1\] READING/.test(line)) {
|
|
295
|
+
child.stdin.write('stop\n');
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
const lines = await child.getLogLines();
|
|
299
|
+
const exit = await child.exit;
|
|
300
|
+
expect(exit.code).toBeGreaterThan(0);
|
|
301
|
+
expect(lines).toContainEqual(expect.stringContaining('[1] stop'));
|
|
302
|
+
expect(lines).toContainEqual(expect.stringMatching(createKillMessage('[0] node __fixtures__/read-echo.js', 'SIGTERM')));
|
|
303
|
+
});
|
|
304
|
+
it('forwards input to specified process', async () => {
|
|
305
|
+
const child = run('-ki "node __fixtures__/read-echo.js" "node __fixtures__/read-echo.js"');
|
|
306
|
+
child.log.subscribe((line) => {
|
|
307
|
+
if (/\[1\] READING/.test(line)) {
|
|
308
|
+
child.stdin.write('1:stop\n');
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
const lines = await child.getLogLines();
|
|
312
|
+
const exit = await child.exit;
|
|
313
|
+
expect(exit.code).toBeGreaterThan(0);
|
|
314
|
+
expect(lines).toContainEqual(expect.stringContaining('[1] stop'));
|
|
315
|
+
expect(lines).toContainEqual(expect.stringMatching(createKillMessage('[0] node __fixtures__/read-echo.js', 'SIGTERM')));
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
describe('--teardown', () => {
|
|
319
|
+
it('runs teardown commands when input commands exit', async () => {
|
|
320
|
+
const lines = await run('--teardown "echo bye" "echo hey"').getLogLines();
|
|
321
|
+
expect(lines).toEqual([
|
|
322
|
+
expect.stringContaining('[0] hey'),
|
|
323
|
+
expect.stringContaining('[0] echo hey exited with code 0'),
|
|
324
|
+
expect.stringContaining('--> Running teardown command "echo bye"'),
|
|
325
|
+
expect.stringContaining('bye'),
|
|
326
|
+
expect.stringContaining('--> Teardown command "echo bye" exited with code 0'),
|
|
327
|
+
]);
|
|
328
|
+
});
|
|
329
|
+
it('runs multiple teardown commands', async () => {
|
|
330
|
+
const lines = await run('--teardown "echo bye" --teardown "echo bye2" "echo hey"').getLogLines();
|
|
331
|
+
expect(lines).toContain('bye');
|
|
332
|
+
expect(lines).toContain('bye2');
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
describe('--timings', () => {
|
|
336
|
+
const defaultTimestampFormatRegex = /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}/;
|
|
337
|
+
const tableTopBorderRegex = /^--> ┌[─┬]+┐$/;
|
|
338
|
+
const tableHeaderRowRegex = /^--> │ name +│ duration +│ exit code +│ killed +│ command +│$/;
|
|
339
|
+
const tableBottomBorderRegex = /^--> └[─┴]+┘$/;
|
|
340
|
+
const timingsTests = {
|
|
341
|
+
'shows timings on success': ['node __fixtures__/sleep.js 0.5', 'exit 0'],
|
|
342
|
+
'shows timings on failure': ['node __fixtures__/sleep.js 0.75', 'exit 1'],
|
|
343
|
+
};
|
|
344
|
+
it.each(Object.entries(timingsTests))('%s', async (_, commands) => {
|
|
345
|
+
const lines = await run(`--timings ${commands.map((command) => `"${command}"`).join(' ')}`).getLogLines();
|
|
346
|
+
// Expect output to contain process start / stop messages for each command
|
|
347
|
+
commands.forEach((command, index) => {
|
|
348
|
+
const escapedCommand = escapeRegExp(command);
|
|
349
|
+
expect(lines).toContainEqual(expect.stringMatching(new RegExp(`^\\[${index}] ${escapedCommand} started at ${defaultTimestampFormatRegex.source}$`)));
|
|
350
|
+
expect(lines).toContainEqual(expect.stringMatching(new RegExp(`^\\[${index}] ${escapedCommand} stopped at ${defaultTimestampFormatRegex.source} after (\\d|,)+ms$`)));
|
|
351
|
+
});
|
|
352
|
+
// Expect output to contain timings table
|
|
353
|
+
expect(lines).toContainEqual(expect.stringMatching(tableTopBorderRegex));
|
|
354
|
+
expect(lines).toContainEqual(expect.stringMatching(tableHeaderRowRegex));
|
|
355
|
+
expect(lines).toContainEqual(expect.stringMatching(tableBottomBorderRegex));
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
describe('--passthrough-arguments', () => {
|
|
359
|
+
it('argument placeholders are properly replaced when passthrough-arguments is enabled', async () => {
|
|
360
|
+
const lines = await run('--passthrough-arguments "echo {1}" -- echo').getLogLines();
|
|
361
|
+
expect(lines).toContainEqual(expect.stringContaining('[0] echo echo exited with code 0'));
|
|
362
|
+
});
|
|
363
|
+
it('argument placeholders are not replaced when passthrough-arguments is disabled', async () => {
|
|
364
|
+
const lines = await run('"echo {1}" -- echo').getLogLines();
|
|
365
|
+
expect(lines).toContainEqual(expect.stringContaining('[0] echo {1} exited with code 0'));
|
|
366
|
+
expect(lines).toContainEqual(expect.stringContaining('[1] echo exited with code 0'));
|
|
367
|
+
});
|
|
368
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function normalizeCliCommand(command: string): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function normalizeCliCommand(command) {
|
|
2
|
+
if (command.length < 2) {
|
|
3
|
+
return command;
|
|
4
|
+
}
|
|
5
|
+
const quote = command[0];
|
|
6
|
+
const last = command.at(-1);
|
|
7
|
+
if ((quote !== '"' && quote !== "'") || last !== quote) {
|
|
8
|
+
return command;
|
|
9
|
+
}
|
|
10
|
+
const inner = command.slice(1, -1);
|
|
11
|
+
if (inner.includes(quote)) {
|
|
12
|
+
return command;
|
|
13
|
+
}
|
|
14
|
+
return inner;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|