concurrently 10.0.0 → 10.0.3

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.
Files changed (63) hide show
  1. package/dist/lib/flow-control/flow-controller.d.ts +13 -0
  2. package/dist/tsconfig.tsbuildinfo +1 -1
  3. package/package.json +11 -14
  4. package/dist/bin/index.spec.js +0 -368
  5. package/dist/bin/normalize-cli-command.spec.d.ts +0 -1
  6. package/dist/bin/normalize-cli-command.spec.js +0 -36
  7. package/dist/lib/__fixtures__/create-mock-instance.d.ts +0 -2
  8. package/dist/lib/__fixtures__/create-mock-instance.js +0 -5
  9. package/dist/lib/__fixtures__/fake-command.d.ts +0 -6
  10. package/dist/lib/__fixtures__/fake-command.js +0 -37
  11. package/dist/lib/assert.spec.d.ts +0 -1
  12. package/dist/lib/assert.spec.js +0 -41
  13. package/dist/lib/command-parser/expand-arguments.spec.d.ts +0 -1
  14. package/dist/lib/command-parser/expand-arguments.spec.js +0 -57
  15. package/dist/lib/command-parser/expand-shortcut.spec.d.ts +0 -1
  16. package/dist/lib/command-parser/expand-shortcut.spec.js +0 -36
  17. package/dist/lib/command-parser/expand-wildcard.spec.d.ts +0 -1
  18. package/dist/lib/command-parser/expand-wildcard.spec.js +0 -288
  19. package/dist/lib/command.spec.d.ts +0 -1
  20. package/dist/lib/command.spec.js +0 -369
  21. package/dist/lib/completion-listener.spec.d.ts +0 -1
  22. package/dist/lib/completion-listener.spec.js +0 -229
  23. package/dist/lib/concurrently.spec.d.ts +0 -1
  24. package/dist/lib/concurrently.spec.js +0 -320
  25. package/dist/lib/date-format.spec.d.ts +0 -1
  26. package/dist/lib/date-format.spec.js +0 -480
  27. package/dist/lib/flow-control/input-handler.spec.d.ts +0 -1
  28. package/dist/lib/flow-control/input-handler.spec.js +0 -116
  29. package/dist/lib/flow-control/kill-on-signal.spec.d.ts +0 -1
  30. package/dist/lib/flow-control/kill-on-signal.spec.js +0 -63
  31. package/dist/lib/flow-control/kill-others.spec.d.ts +0 -1
  32. package/dist/lib/flow-control/kill-others.spec.js +0 -98
  33. package/dist/lib/flow-control/log-error.spec.d.ts +0 -1
  34. package/dist/lib/flow-control/log-error.spec.js +0 -33
  35. package/dist/lib/flow-control/log-exit.spec.d.ts +0 -1
  36. package/dist/lib/flow-control/log-exit.spec.js +0 -24
  37. package/dist/lib/flow-control/log-output.spec.d.ts +0 -1
  38. package/dist/lib/flow-control/log-output.spec.js +0 -33
  39. package/dist/lib/flow-control/log-timings.spec.d.ts +0 -1
  40. package/dist/lib/flow-control/log-timings.spec.js +0 -89
  41. package/dist/lib/flow-control/logger-padding.spec.d.ts +0 -1
  42. package/dist/lib/flow-control/logger-padding.spec.js +0 -60
  43. package/dist/lib/flow-control/output-error-handler.spec.d.ts +0 -1
  44. package/dist/lib/flow-control/output-error-handler.spec.js +0 -41
  45. package/dist/lib/flow-control/restart-process.spec.d.ts +0 -1
  46. package/dist/lib/flow-control/restart-process.spec.js +0 -127
  47. package/dist/lib/flow-control/teardown.spec.d.ts +0 -1
  48. package/dist/lib/flow-control/teardown.spec.js +0 -93
  49. package/dist/lib/jsonc.spec.d.ts +0 -1
  50. package/dist/lib/jsonc.spec.js +0 -73
  51. package/dist/lib/logger.spec.d.ts +0 -1
  52. package/dist/lib/logger.spec.js +0 -507
  53. package/dist/lib/observables.spec.d.ts +0 -1
  54. package/dist/lib/observables.spec.js +0 -29
  55. package/dist/lib/output-writer.spec.d.ts +0 -1
  56. package/dist/lib/output-writer.spec.js +0 -96
  57. package/dist/lib/prefix-color-selector.spec.d.ts +0 -1
  58. package/dist/lib/prefix-color-selector.spec.js +0 -159
  59. package/dist/lib/spawn.spec.d.ts +0 -1
  60. package/dist/lib/spawn.spec.js +0 -100
  61. package/dist/lib/utils.spec.d.ts +0 -1
  62. package/dist/lib/utils.spec.js +0 -58
  63. /package/dist/{bin/index.spec.d.ts → lib/flow-control/flow-controller.js} +0 -0
@@ -0,0 +1,13 @@
1
+ import type { Command } from '../command.js';
2
+ /**
3
+ * Interface for a class that controls and/or watches the behavior of commands.
4
+ *
5
+ * This may include logging their output, creating interactions between them, or changing when they
6
+ * actually finish.
7
+ */
8
+ export interface FlowController {
9
+ handle(commands: Command[]): {
10
+ commands: Command[];
11
+ onFinish?: () => void | Promise<void>;
12
+ };
13
+ }
@@ -1 +1 @@
1
- {"root":["../bin/index.spec.ts","../bin/index.ts","../bin/normalize-cli-command.spec.ts","../bin/normalize-cli-command.ts","../bin/read-package-json.ts","../lib/assert.spec.ts","../lib/assert.ts","../lib/command.spec.ts","../lib/command.ts","../lib/completion-listener.spec.ts","../lib/completion-listener.ts","../lib/concurrently.spec.ts","../lib/concurrently.ts","../lib/date-format.spec.ts","../lib/date-format.ts","../lib/defaults.ts","../lib/index.ts","../lib/jsonc.spec.ts","../lib/jsonc.ts","../lib/logger.spec.ts","../lib/logger.ts","../lib/observables.spec.ts","../lib/observables.ts","../lib/output-writer.spec.ts","../lib/output-writer.ts","../lib/prefix-color-selector.spec.ts","../lib/prefix-color-selector.ts","../lib/spawn.spec.ts","../lib/spawn.ts","../lib/utils.spec.ts","../lib/utils.ts","../lib/__fixtures__/create-mock-instance.ts","../lib/__fixtures__/fake-command.ts","../lib/command-parser/command-parser.d.ts","../lib/command-parser/expand-arguments.spec.ts","../lib/command-parser/expand-arguments.ts","../lib/command-parser/expand-shortcut.spec.ts","../lib/command-parser/expand-shortcut.ts","../lib/command-parser/expand-wildcard.spec.ts","../lib/command-parser/expand-wildcard.ts","../lib/declarations/intl.d.ts","../lib/flow-control/flow-controller.d.ts","../lib/flow-control/input-handler.spec.ts","../lib/flow-control/input-handler.ts","../lib/flow-control/kill-on-signal.spec.ts","../lib/flow-control/kill-on-signal.ts","../lib/flow-control/kill-others.spec.ts","../lib/flow-control/kill-others.ts","../lib/flow-control/log-error.spec.ts","../lib/flow-control/log-error.ts","../lib/flow-control/log-exit.spec.ts","../lib/flow-control/log-exit.ts","../lib/flow-control/log-output.spec.ts","../lib/flow-control/log-output.ts","../lib/flow-control/log-timings.spec.ts","../lib/flow-control/log-timings.ts","../lib/flow-control/logger-padding.spec.ts","../lib/flow-control/logger-padding.ts","../lib/flow-control/output-error-handler.spec.ts","../lib/flow-control/output-error-handler.ts","../lib/flow-control/restart-process.spec.ts","../lib/flow-control/restart-process.ts","../lib/flow-control/teardown.spec.ts","../lib/flow-control/teardown.ts"],"version":"5.9.3"}
1
+ {"root":["../bin/index.spec.ts","../bin/index.ts","../bin/normalize-cli-command.spec.ts","../bin/normalize-cli-command.ts","../bin/read-package-json.ts","../lib/assert.spec.ts","../lib/assert.ts","../lib/command.spec.ts","../lib/command.ts","../lib/completion-listener.spec.ts","../lib/completion-listener.ts","../lib/concurrently.spec.ts","../lib/concurrently.ts","../lib/date-format.spec.ts","../lib/date-format.ts","../lib/defaults.ts","../lib/index.ts","../lib/jsonc.spec.ts","../lib/jsonc.ts","../lib/logger.spec.ts","../lib/logger.ts","../lib/observables.spec.ts","../lib/observables.ts","../lib/output-writer.spec.ts","../lib/output-writer.ts","../lib/prefix-color-selector.spec.ts","../lib/prefix-color-selector.ts","../lib/spawn.spec.ts","../lib/spawn.ts","../lib/utils.spec.ts","../lib/utils.ts","../lib/__fixtures__/create-mock-instance.ts","../lib/__fixtures__/fake-command.ts","../lib/command-parser/command-parser.d.ts","../lib/command-parser/expand-arguments.spec.ts","../lib/command-parser/expand-arguments.ts","../lib/command-parser/expand-shortcut.spec.ts","../lib/command-parser/expand-shortcut.ts","../lib/command-parser/expand-wildcard.spec.ts","../lib/command-parser/expand-wildcard.ts","../lib/declarations/intl.d.ts","../lib/flow-control/flow-controller.ts","../lib/flow-control/input-handler.spec.ts","../lib/flow-control/input-handler.ts","../lib/flow-control/kill-on-signal.spec.ts","../lib/flow-control/kill-on-signal.ts","../lib/flow-control/kill-others.spec.ts","../lib/flow-control/kill-others.ts","../lib/flow-control/log-error.spec.ts","../lib/flow-control/log-error.ts","../lib/flow-control/log-exit.spec.ts","../lib/flow-control/log-exit.ts","../lib/flow-control/log-output.spec.ts","../lib/flow-control/log-output.ts","../lib/flow-control/log-timings.spec.ts","../lib/flow-control/log-timings.ts","../lib/flow-control/logger-padding.spec.ts","../lib/flow-control/logger-padding.ts","../lib/flow-control/output-error-handler.spec.ts","../lib/flow-control/output-error-handler.ts","../lib/flow-control/restart-process.spec.ts","../lib/flow-control/restart-process.ts","../lib/flow-control/teardown.spec.ts","../lib/flow-control/teardown.ts"],"version":"5.9.3"}
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "concurrently",
3
3
  "type": "module",
4
- "version": "10.0.0",
5
- "packageManager": "pnpm@10.18.2+sha512.9fb969fa749b3ade6035e0f109f0b8a60b5d08a1a87fdf72e337da90dcc93336e2280ca4e44f2358a649b83c17959e9993e777c2080879f3801e6f0d999ad3dd",
4
+ "version": "10.0.3",
6
5
  "description": "Run commands concurrently",
7
6
  "author": "Kimmo Brunfeldt",
8
7
  "license": "MIT",
@@ -39,17 +38,6 @@
39
38
  "engines": {
40
39
  "node": ">=22"
41
40
  },
42
- "scripts": {
43
- "build": "tsc --build",
44
- "postbuild": "chmod +x dist/bin/index.js",
45
- "typecheck": "tsc --noEmit",
46
- "format": "prettier --check '**/*.{json,y?(a)ml,md}'",
47
- "lint": "eslint",
48
- "prepublishOnly": "safe-publish-latest && pnpm run build",
49
- "test": "vitest --project unit",
50
- "test:smoke": "vitest run --project smoke",
51
- "prepare": "husky"
52
- },
53
41
  "dependencies": {
54
42
  "chalk": "5.6.2",
55
43
  "rxjs": "7.8.2",
@@ -86,5 +74,14 @@
86
74
  "lint-staged": {
87
75
  "*.{js,ts}": "eslint --fix",
88
76
  "*.{json,y?(a)ml,md}": "prettier --write"
77
+ },
78
+ "scripts": {
79
+ "build": "tsc --build",
80
+ "postbuild": "chmod +x dist/bin/index.js",
81
+ "typecheck": "tsc --noEmit",
82
+ "format": "prettier --check '**/*.{json,y?(a)ml,md}'",
83
+ "lint": "eslint",
84
+ "test": "vitest --project unit",
85
+ "test:smoke": "vitest run --project smoke"
89
86
  }
90
- }
87
+ }
@@ -1,368 +0,0 @@
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
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,36 +0,0 @@
1
- import { expect, it } from 'vitest';
2
- import { normalizeCliCommand } from './normalize-cli-command.js';
3
- it('strips outer CLI wrapper double quotes', () => {
4
- expect(normalizeCliCommand('"echo foo"')).toBe('echo foo');
5
- });
6
- it('strips outer CLI wrapper single quotes', () => {
7
- expect(normalizeCliCommand("'echo foo'")).toBe('echo foo');
8
- });
9
- it('strips quotes around a single wrapped token', () => {
10
- expect(normalizeCliCommand('"echo"')).toBe('echo');
11
- expect(normalizeCliCommand("'echo'")).toBe('echo');
12
- });
13
- it('preserves quotes in well-formed shell commands', () => {
14
- expect(normalizeCliCommand('"/usr/local/bin/mytool" --flag "some value"')).toBe('"/usr/local/bin/mytool" --flag "some value"');
15
- });
16
- it('preserves well-formed shell commands with multiple quote sets', () => {
17
- expect(normalizeCliCommand('"/usr/local/bin/mytool" --flag "some value" --other "last arg"')).toBe('"/usr/local/bin/mytool" --flag "some value" --other "last arg"');
18
- });
19
- it('preserves single quotes in well-formed shell commands', () => {
20
- expect(normalizeCliCommand("'printf' '%s %s' foo bar")).toBe("'printf' '%s %s' foo bar");
21
- });
22
- it('returns unquoted input unchanged', () => {
23
- expect(normalizeCliCommand('echo foo')).toBe('echo foo');
24
- });
25
- it('returns an empty string unchanged', () => {
26
- expect(normalizeCliCommand('')).toBe('');
27
- });
28
- it('leaves ambiguous input unchanged', () => {
29
- expect(normalizeCliCommand('"echo foo')).toBe('"echo foo');
30
- });
31
- it('leaves input with an unclosed single quote unchanged', () => {
32
- expect(normalizeCliCommand("echo foo'")).toBe("echo foo'");
33
- });
34
- it('leaves input with mismatched quote types unchanged', () => {
35
- expect(normalizeCliCommand('"echo foo\'')).toBe('"echo foo\'');
36
- });
@@ -1,2 +0,0 @@
1
- import { MockedObject } from 'vitest';
2
- export declare function createMockInstance<T>(constructor: new (...args: any[]) => T): MockedObject<T>;
@@ -1,5 +0,0 @@
1
- import { vi } from 'vitest';
2
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
- export function createMockInstance(constructor) {
4
- return new (vi.mockObject(constructor))();
5
- }
@@ -1,6 +0,0 @@
1
- import { ChildProcess, CloseEvent, Command, CommandInfo } from '../command.js';
2
- export declare class FakeCommand extends Command {
3
- constructor(name?: string, command?: string, index?: number, info?: Partial<CommandInfo>);
4
- }
5
- export declare const createFakeProcess: (pid: number) => ChildProcess;
6
- export declare const createFakeCloseEvent: (overrides?: Partial<CloseEvent>) => CloseEvent;
@@ -1,37 +0,0 @@
1
- import EventEmitter from 'node:events';
2
- import { PassThrough, Writable } from 'node:stream';
3
- import { vi } from 'vitest';
4
- import { Command } from '../command.js';
5
- import { createMockInstance } from './create-mock-instance.js';
6
- export class FakeCommand extends Command {
7
- constructor(name = 'foo', command = 'echo foo', index = 0, info) {
8
- super({
9
- index,
10
- name,
11
- command,
12
- ...info,
13
- }, {}, vi.fn(), vi.fn());
14
- this.stdin = createMockInstance(Writable);
15
- this.start = vi.fn();
16
- this.kill = vi.fn();
17
- }
18
- }
19
- export const createFakeProcess = (pid) => Object.assign(new EventEmitter(), {
20
- pid,
21
- send: vi.fn(),
22
- stdin: new PassThrough(),
23
- stdout: new PassThrough(),
24
- stderr: new PassThrough(),
25
- });
26
- export const createFakeCloseEvent = (overrides) => ({
27
- command: new FakeCommand(),
28
- index: 0,
29
- killed: false,
30
- exitCode: 0,
31
- timings: {
32
- startDate: new Date(),
33
- endDate: new Date(),
34
- durationSeconds: 0,
35
- },
36
- ...overrides,
37
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,41 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { assertDeprecated, assertNotRuntime } from './assert.js';
3
- let consoleMock;
4
- beforeEach(() => {
5
- consoleMock = vi.spyOn(console, 'warn').mockImplementation(() => { });
6
- });
7
- afterEach(() => {
8
- vi.clearAllMocks();
9
- });
10
- describe('assertDeprecated()', () => {
11
- it('prints warning with name and message when condition is false', () => {
12
- assertDeprecated(false, 'example-flag', 'This is an example message.');
13
- expect(consoleMock).toHaveBeenLastCalledWith('[concurrently] example-flag is deprecated. This is an example message.');
14
- });
15
- it('prints same warning only once', () => {
16
- assertDeprecated(false, 'example-flag', 'This is an example message.');
17
- assertDeprecated(false, 'different-flag', 'This is another message.');
18
- expect(consoleMock).toBeCalledTimes(1);
19
- expect(consoleMock).toHaveBeenLastCalledWith('[concurrently] different-flag is deprecated. This is another message.');
20
- });
21
- it('prints nothing if condition is true', () => {
22
- assertDeprecated(true, 'example-flag', 'This is an example message.');
23
- expect(consoleMock).not.toHaveBeenCalled();
24
- });
25
- });
26
- describe('assertNotRuntime()', () => {
27
- it('prints warning with name and message when condition is false', () => {
28
- assertNotRuntime(false, 'example-flag', 'This is an example message.');
29
- expect(consoleMock).toHaveBeenLastCalledWith('[concurrently] Running via example-flag is not well supported. This is an example message.');
30
- });
31
- it('prints same warning only once', () => {
32
- assertNotRuntime(false, 'example-flag', 'This is an example message.');
33
- assertNotRuntime(false, 'different-flag', 'This is another message.');
34
- expect(consoleMock).toBeCalledTimes(1);
35
- expect(consoleMock).toHaveBeenLastCalledWith('[concurrently] Running via different-flag is not well supported. This is another message.');
36
- });
37
- it('prints nothing if condition is true', () => {
38
- assertNotRuntime(true, 'example-flag', 'This is an example message.');
39
- expect(consoleMock).not.toHaveBeenCalled();
40
- });
41
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,57 +0,0 @@
1
- import { expect, it } from 'vitest';
2
- import { ExpandArguments } from './expand-arguments.js';
3
- const createCommandInfo = (command) => ({
4
- command,
5
- name: '',
6
- });
7
- it('returns command as is when no placeholders', () => {
8
- const parser = new ExpandArguments(['foo', 'bar']);
9
- const commandInfo = createCommandInfo('echo foo');
10
- expect(parser.parse(commandInfo)).toEqual({ ...commandInfo, command: 'echo foo' });
11
- });
12
- it('single argument placeholder is replaced', () => {
13
- const parser = new ExpandArguments(['foo', 'bar']);
14
- const commandInfo = createCommandInfo('echo {1}');
15
- expect(parser.parse(commandInfo)).toEqual({ ...commandInfo, command: 'echo foo' });
16
- });
17
- it('argument placeholder is replaced and quoted properly', () => {
18
- const parser = new ExpandArguments(['foo bar']);
19
- const commandInfo = createCommandInfo('echo {1}');
20
- expect(parser.parse(commandInfo)).toEqual({ ...commandInfo, command: "echo 'foo bar'" });
21
- });
22
- it('multiple single argument placeholders are replaced', () => {
23
- const parser = new ExpandArguments(['foo', 'bar']);
24
- const commandInfo = createCommandInfo('echo {2} {1}');
25
- expect(parser.parse(commandInfo)).toEqual({ ...commandInfo, command: 'echo bar foo' });
26
- });
27
- it('empty replacement with single placeholder and not enough passthrough arguments', () => {
28
- const parser = new ExpandArguments(['foo', 'bar']);
29
- const commandInfo = createCommandInfo('echo {3}');
30
- expect(parser.parse(commandInfo)).toEqual({ ...commandInfo, command: 'echo ' });
31
- });
32
- it('empty replacement with all placeholder and no passthrough arguments', () => {
33
- const parser = new ExpandArguments([]);
34
- const commandInfo = createCommandInfo('echo {@}');
35
- expect(parser.parse(commandInfo)).toEqual({ ...commandInfo, command: 'echo ' });
36
- });
37
- it('empty replacement with combined placeholder and no passthrough arguments', () => {
38
- const parser = new ExpandArguments([]);
39
- const commandInfo = createCommandInfo('echo {*}');
40
- expect(parser.parse(commandInfo)).toEqual({ ...commandInfo, command: 'echo ' });
41
- });
42
- it('all arguments placeholder is replaced', () => {
43
- const parser = new ExpandArguments(['foo', 'bar']);
44
- const commandInfo = createCommandInfo('echo {@}');
45
- expect(parser.parse(commandInfo)).toEqual({ ...commandInfo, command: 'echo foo bar' });
46
- });
47
- it('combined arguments placeholder is replaced', () => {
48
- const parser = new ExpandArguments(['foo', 'bar']);
49
- const commandInfo = createCommandInfo('echo {*}');
50
- expect(parser.parse(commandInfo)).toEqual({ ...commandInfo, command: "echo 'foo bar'" });
51
- });
52
- it('escaped argument placeholders are not replaced', () => {
53
- const parser = new ExpandArguments(['foo', 'bar']);
54
- // Equals to single backslash on command line
55
- const commandInfo = createCommandInfo('echo \\{1} \\{@} \\{*}');
56
- expect(parser.parse(commandInfo)).toEqual({ ...commandInfo, command: 'echo {1} {@} {*}' });
57
- });
@@ -1 +0,0 @@
1
- export {};