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
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { FlowController } from './flow-control/flow-controller';
|
|
7
|
-
import { Logger } from './logger';
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import { CloseEvent, Command, CommandIdentifier, CommandInfo, KillProcess, SpawnCommand } from './command.js';
|
|
3
|
+
import { SuccessCondition } from './completion-listener.js';
|
|
4
|
+
import { FlowController } from './flow-control/flow-controller.js';
|
|
5
|
+
import { Logger } from './logger.js';
|
|
8
6
|
/**
|
|
9
7
|
* A command that is to be passed into `concurrently()`.
|
|
10
8
|
* If value is a string, then that's the command's command line.
|
|
@@ -13,7 +11,7 @@ import { Logger } from './logger';
|
|
|
13
11
|
export type ConcurrentlyCommandInput = string | ({
|
|
14
12
|
command: string;
|
|
15
13
|
} & Partial<CommandInfo>);
|
|
16
|
-
export
|
|
14
|
+
export interface ConcurrentlyResult {
|
|
17
15
|
/**
|
|
18
16
|
* All commands created and ran by concurrently.
|
|
19
17
|
*/
|
|
@@ -26,8 +24,8 @@ export type ConcurrentlyResult = {
|
|
|
26
24
|
* spawned; commands that didn't spawn are filtered out.
|
|
27
25
|
*/
|
|
28
26
|
result: Promise<CloseEvent[]>;
|
|
29
|
-
}
|
|
30
|
-
export
|
|
27
|
+
}
|
|
28
|
+
export interface ConcurrentlyOptions {
|
|
31
29
|
logger?: Logger;
|
|
32
30
|
/**
|
|
33
31
|
* Which stream should the commands output be written to.
|
|
@@ -38,7 +36,7 @@ export type ConcurrentlyOptions = {
|
|
|
38
36
|
*/
|
|
39
37
|
group?: boolean;
|
|
40
38
|
/**
|
|
41
|
-
* A comma-separated list of
|
|
39
|
+
* A comma-separated list of Chalk colors or a string for available styles listed below to use on prefixes.
|
|
42
40
|
* If there are more commands than colors, the last color will be repeated.
|
|
43
41
|
*
|
|
44
42
|
* Available modifiers:
|
|
@@ -108,7 +106,7 @@ export type ConcurrentlyOptions = {
|
|
|
108
106
|
* @see ExpandArguments
|
|
109
107
|
*/
|
|
110
108
|
additionalArguments?: string[];
|
|
111
|
-
}
|
|
109
|
+
}
|
|
112
110
|
/**
|
|
113
111
|
* Core concurrently functionality -- spawns the given commands concurrently and
|
|
114
112
|
* returns the commands themselves + the result according to the specified success condition.
|
|
@@ -1,26 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const expand_shortcut_1 = require("./command-parser/expand-shortcut");
|
|
15
|
-
const expand_wildcard_1 = require("./command-parser/expand-wildcard");
|
|
16
|
-
const strip_quotes_1 = require("./command-parser/strip-quotes");
|
|
17
|
-
const completion_listener_1 = require("./completion-listener");
|
|
18
|
-
const output_writer_1 = require("./output-writer");
|
|
19
|
-
const prefix_color_selector_1 = require("./prefix-color-selector");
|
|
20
|
-
const spawn_1 = require("./spawn");
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { takeUntil } from 'rxjs';
|
|
4
|
+
import treeKill from 'tree-kill';
|
|
5
|
+
import { Command, } from './command.js';
|
|
6
|
+
import { ExpandArguments } from './command-parser/expand-arguments.js';
|
|
7
|
+
import { ExpandShortcut } from './command-parser/expand-shortcut.js';
|
|
8
|
+
import { ExpandWildcard } from './command-parser/expand-wildcard.js';
|
|
9
|
+
import { CompletionListener } from './completion-listener.js';
|
|
10
|
+
import { OutputWriter } from './output-writer.js';
|
|
11
|
+
import { PrefixColorSelector } from './prefix-color-selector.js';
|
|
12
|
+
import { createSpawn, getSpawnOpts } from './spawn.js';
|
|
13
|
+
import { castArray } from './utils.js';
|
|
21
14
|
const defaults = {
|
|
22
|
-
spawn:
|
|
23
|
-
kill:
|
|
15
|
+
spawn: createSpawn(),
|
|
16
|
+
kill: treeKill,
|
|
24
17
|
raw: false,
|
|
25
18
|
controllers: [],
|
|
26
19
|
cwd: undefined,
|
|
@@ -31,64 +24,59 @@ const defaults = {
|
|
|
31
24
|
*
|
|
32
25
|
* @see CompletionListener
|
|
33
26
|
*/
|
|
34
|
-
function concurrently(baseCommands, baseOptions) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const options =
|
|
38
|
-
const prefixColorSelector = new
|
|
39
|
-
const commandParsers = [
|
|
40
|
-
new strip_quotes_1.StripQuotes(),
|
|
41
|
-
new expand_shortcut_1.ExpandShortcut(),
|
|
42
|
-
new expand_wildcard_1.ExpandWildcard(),
|
|
43
|
-
];
|
|
27
|
+
export function concurrently(baseCommands, baseOptions) {
|
|
28
|
+
assert.ok(Array.isArray(baseCommands), '[concurrently] commands should be an array');
|
|
29
|
+
assert.notStrictEqual(baseCommands.length, 0, '[concurrently] no commands provided');
|
|
30
|
+
const options = { ...defaults, ...baseOptions };
|
|
31
|
+
const prefixColorSelector = new PrefixColorSelector(options.prefixColors || []);
|
|
32
|
+
const commandParsers = [new ExpandShortcut(), new ExpandWildcard()];
|
|
44
33
|
if (options.additionalArguments) {
|
|
45
|
-
commandParsers.push(new
|
|
34
|
+
commandParsers.push(new ExpandArguments(options.additionalArguments));
|
|
46
35
|
}
|
|
47
36
|
const hide = (options.hide || []).map(String);
|
|
48
|
-
let commands =
|
|
37
|
+
let commands = baseCommands
|
|
49
38
|
.map(mapToCommandInfo)
|
|
50
39
|
.flatMap((command) => parseCommand(command, commandParsers))
|
|
51
40
|
.map((command, index) => {
|
|
52
41
|
const hidden = hide.includes(command.name) || hide.includes(String(index));
|
|
53
|
-
return new
|
|
42
|
+
return new Command({
|
|
54
43
|
index,
|
|
55
44
|
prefixColor: prefixColorSelector.getNextColor(),
|
|
56
45
|
...command,
|
|
57
|
-
},
|
|
46
|
+
}, getSpawnOpts({
|
|
58
47
|
ipc: command.ipc,
|
|
59
|
-
stdio: hidden ? 'hidden' : command.raw ?? options.raw ? 'raw' : 'normal',
|
|
48
|
+
stdio: hidden ? 'hidden' : (command.raw ?? options.raw) ? 'raw' : 'normal',
|
|
60
49
|
env: command.env,
|
|
61
50
|
cwd: command.cwd || options.cwd,
|
|
62
51
|
}), options.spawn, options.kill);
|
|
63
|
-
})
|
|
64
|
-
.value();
|
|
52
|
+
});
|
|
65
53
|
const handleResult = options.controllers.reduce(({ commands: prevCommands, onFinishCallbacks }, controller) => {
|
|
66
54
|
const { commands, onFinish } = controller.handle(prevCommands);
|
|
67
55
|
return {
|
|
68
56
|
commands,
|
|
69
|
-
onFinishCallbacks:
|
|
57
|
+
onFinishCallbacks: onFinishCallbacks.concat(onFinish ? [onFinish] : []),
|
|
70
58
|
};
|
|
71
59
|
}, { commands, onFinishCallbacks: [] });
|
|
72
60
|
commands = handleResult.commands;
|
|
73
61
|
if (options.logger && options.outputStream) {
|
|
74
|
-
const outputWriter = new
|
|
62
|
+
const outputWriter = new OutputWriter({
|
|
75
63
|
outputStream: options.outputStream,
|
|
76
64
|
group: !!options.group,
|
|
77
65
|
commands,
|
|
78
66
|
});
|
|
79
67
|
options.logger.output
|
|
80
68
|
// Stop trying to write after there's been an error.
|
|
81
|
-
.pipe(
|
|
69
|
+
.pipe(takeUntil(outputWriter.error))
|
|
82
70
|
.subscribe(({ command, text }) => outputWriter.write(command, text));
|
|
83
71
|
}
|
|
84
72
|
const commandsLeft = commands.slice();
|
|
85
73
|
const maxProcesses = Math.max(1, (typeof options.maxProcesses === 'string' && options.maxProcesses.endsWith('%')
|
|
86
|
-
? Math.round((
|
|
74
|
+
? Math.round((os.cpus().length * Number(options.maxProcesses.slice(0, -1))) / 100)
|
|
87
75
|
: Number(options.maxProcesses)) || commandsLeft.length);
|
|
88
76
|
for (let i = 0; i < maxProcesses; i++) {
|
|
89
77
|
maybeRunMore(commandsLeft, options.abortSignal);
|
|
90
78
|
}
|
|
91
|
-
const result = new
|
|
79
|
+
const result = new CompletionListener({ successCondition: options.successCondition })
|
|
92
80
|
.listen(commands, options.abortSignal)
|
|
93
81
|
.finally(() => Promise.all(handleResult.onFinishCallbacks.map((onFinish) => onFinish())));
|
|
94
82
|
return {
|
|
@@ -96,12 +84,11 @@ function concurrently(baseCommands, baseOptions) {
|
|
|
96
84
|
commands,
|
|
97
85
|
};
|
|
98
86
|
}
|
|
99
|
-
exports.concurrently = concurrently;
|
|
100
87
|
function mapToCommandInfo(command) {
|
|
101
88
|
if (typeof command === 'string') {
|
|
102
89
|
return mapToCommandInfo({ command });
|
|
103
90
|
}
|
|
104
|
-
|
|
91
|
+
assert.ok(command.command, '[concurrently] command cannot be empty');
|
|
105
92
|
return {
|
|
106
93
|
command: command.command,
|
|
107
94
|
name: command.name || '',
|
|
@@ -121,7 +108,7 @@ function mapToCommandInfo(command) {
|
|
|
121
108
|
};
|
|
122
109
|
}
|
|
123
110
|
function parseCommand(command, parsers) {
|
|
124
|
-
return parsers.reduce((commands, parser) =>
|
|
111
|
+
return parsers.reduce((commands, parser) => commands.flatMap((command) => parser.parse(command)), castArray(command));
|
|
125
112
|
}
|
|
126
113
|
function maybeRunMore(commandsLeft, abortSignal) {
|
|
127
114
|
const command = commandsLeft.shift();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { Writable } from 'node:stream';
|
|
3
|
+
import { beforeEach, expect, it, vi } from 'vitest';
|
|
4
|
+
import { createMockInstance } from './__fixtures__/create-mock-instance.js';
|
|
5
|
+
import { createFakeProcess, FakeCommand } from './__fixtures__/fake-command.js';
|
|
6
|
+
import { concurrently } from './concurrently.js';
|
|
7
|
+
import { Logger } from './logger.js';
|
|
8
|
+
let spawn;
|
|
9
|
+
let kill;
|
|
10
|
+
let onFinishHooks;
|
|
11
|
+
let controllers;
|
|
12
|
+
let processes;
|
|
13
|
+
const create = (commands, options = {}) => concurrently(commands, Object.assign(options, { controllers, spawn, kill }));
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.resetAllMocks();
|
|
16
|
+
processes = [];
|
|
17
|
+
spawn = vi.fn(() => {
|
|
18
|
+
const process = createFakeProcess(processes.length);
|
|
19
|
+
processes.push(process);
|
|
20
|
+
return process;
|
|
21
|
+
});
|
|
22
|
+
kill = vi.fn();
|
|
23
|
+
onFinishHooks = [vi.fn(), vi.fn()];
|
|
24
|
+
controllers = [
|
|
25
|
+
{ handle: vi.fn((commands) => ({ commands, onFinish: onFinishHooks[0] })) },
|
|
26
|
+
{ handle: vi.fn((commands) => ({ commands, onFinish: onFinishHooks[1] })) },
|
|
27
|
+
];
|
|
28
|
+
});
|
|
29
|
+
it('fails if commands is not an array', () => {
|
|
30
|
+
const bomb = () => create('foo');
|
|
31
|
+
expect(bomb).toThrow();
|
|
32
|
+
});
|
|
33
|
+
it('fails if no commands were provided', () => {
|
|
34
|
+
const bomb = () => create([]);
|
|
35
|
+
expect(bomb).toThrow();
|
|
36
|
+
});
|
|
37
|
+
it('spawns all commands', () => {
|
|
38
|
+
create(['echo', 'kill']);
|
|
39
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
40
|
+
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({}));
|
|
41
|
+
expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({}));
|
|
42
|
+
});
|
|
43
|
+
it('log output is passed to output stream if logger is specified in options', () => {
|
|
44
|
+
const logger = new Logger({ hide: [] });
|
|
45
|
+
const outputStream = createMockInstance(Writable);
|
|
46
|
+
create(['foo'], { logger, outputStream });
|
|
47
|
+
logger.log('foo', 'bar');
|
|
48
|
+
expect(outputStream.write).toHaveBeenCalledTimes(2);
|
|
49
|
+
expect(outputStream.write).toHaveBeenCalledWith('foo');
|
|
50
|
+
expect(outputStream.write).toHaveBeenCalledWith('bar');
|
|
51
|
+
});
|
|
52
|
+
it('log output is not passed to output stream after it has errored', () => {
|
|
53
|
+
const logger = new Logger({ hide: [] });
|
|
54
|
+
const outputStream = new Writable();
|
|
55
|
+
vi.spyOn(outputStream, 'write');
|
|
56
|
+
create(['foo'], { logger, outputStream });
|
|
57
|
+
outputStream.emit('error', new Error('test'));
|
|
58
|
+
logger.log('foo', 'bar');
|
|
59
|
+
expect(outputStream.write).not.toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
it('spawns commands up to configured limit at once', () => {
|
|
62
|
+
create(['foo', 'bar', 'baz', 'qux'], { maxProcesses: 2 });
|
|
63
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
64
|
+
expect(spawn).toHaveBeenCalledWith('foo', expect.objectContaining({}));
|
|
65
|
+
expect(spawn).toHaveBeenCalledWith('bar', expect.objectContaining({}));
|
|
66
|
+
// Test out of order completion picking up new processes in-order
|
|
67
|
+
processes[1].emit('close', 1, null);
|
|
68
|
+
expect(spawn).toHaveBeenCalledTimes(3);
|
|
69
|
+
expect(spawn).toHaveBeenCalledWith('baz', expect.objectContaining({}));
|
|
70
|
+
processes[0].emit('close', null, 'SIGINT');
|
|
71
|
+
expect(spawn).toHaveBeenCalledTimes(4);
|
|
72
|
+
expect(spawn).toHaveBeenCalledWith('qux', expect.objectContaining({}));
|
|
73
|
+
// Shouldn't attempt to spawn anything else.
|
|
74
|
+
processes[2].emit('close', 1, null);
|
|
75
|
+
expect(spawn).toHaveBeenCalledTimes(4);
|
|
76
|
+
});
|
|
77
|
+
it('spawns commands up to percent based limit at once', () => {
|
|
78
|
+
// Mock architecture with 4 cores
|
|
79
|
+
const cpusSpy = vi.spyOn(os, 'cpus');
|
|
80
|
+
cpusSpy.mockReturnValue(Array.from({ length: 4 }).fill({
|
|
81
|
+
model: 'Intel',
|
|
82
|
+
speed: 0,
|
|
83
|
+
times: { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 },
|
|
84
|
+
}));
|
|
85
|
+
create(['foo', 'bar', 'baz', 'qux'], { maxProcesses: '50%' });
|
|
86
|
+
// Max parallel processes should be 2 (50% of 4 cores)
|
|
87
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
88
|
+
expect(spawn).toHaveBeenCalledWith('foo', expect.objectContaining({}));
|
|
89
|
+
expect(spawn).toHaveBeenCalledWith('bar', expect.objectContaining({}));
|
|
90
|
+
// Close first process and expect third to be spawned
|
|
91
|
+
processes[0].emit('close', 1, null);
|
|
92
|
+
expect(spawn).toHaveBeenCalledTimes(3);
|
|
93
|
+
expect(spawn).toHaveBeenCalledWith('baz', expect.objectContaining({}));
|
|
94
|
+
// Close second process and expect fourth to be spawned
|
|
95
|
+
processes[1].emit('close', 1, null);
|
|
96
|
+
expect(spawn).toHaveBeenCalledTimes(4);
|
|
97
|
+
expect(spawn).toHaveBeenCalledWith('qux', expect.objectContaining({}));
|
|
98
|
+
});
|
|
99
|
+
it('does not spawn further commands on abort signal aborted', () => {
|
|
100
|
+
const abortController = new AbortController();
|
|
101
|
+
create(['foo', 'bar'], { maxProcesses: 1, abortSignal: abortController.signal });
|
|
102
|
+
expect(spawn).toHaveBeenCalledTimes(1);
|
|
103
|
+
abortController.abort();
|
|
104
|
+
processes[0].emit('close', 0, null);
|
|
105
|
+
expect(spawn).toHaveBeenCalledTimes(1);
|
|
106
|
+
});
|
|
107
|
+
it('preserves quotes in well-formed shell commands in the library API', () => {
|
|
108
|
+
create(['"/usr/local/bin/mytool" --flag "some value"']);
|
|
109
|
+
controllers.forEach((controller) => {
|
|
110
|
+
expect(controller.handle).toHaveBeenCalledWith([
|
|
111
|
+
expect.objectContaining({
|
|
112
|
+
command: '"/usr/local/bin/mytool" --flag "some value"',
|
|
113
|
+
index: 0,
|
|
114
|
+
}),
|
|
115
|
+
]);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
it('passes commands with multiple quote sets through unchanged in the library API', () => {
|
|
119
|
+
create(['"/usr/local/bin/mytool" --flag "some value" --other "last arg"']);
|
|
120
|
+
controllers.forEach((controller) => {
|
|
121
|
+
expect(controller.handle).toHaveBeenCalledWith([
|
|
122
|
+
expect.objectContaining({
|
|
123
|
+
command: '"/usr/local/bin/mytool" --flag "some value" --other "last arg"',
|
|
124
|
+
index: 0,
|
|
125
|
+
}),
|
|
126
|
+
]);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
it('runs commands with a name or prefix color', () => {
|
|
130
|
+
create([{ command: 'echo', prefixColor: 'red', name: 'foo' }, 'kill']);
|
|
131
|
+
controllers.forEach((controller) => {
|
|
132
|
+
expect(controller.handle).toHaveBeenCalledWith([
|
|
133
|
+
expect.objectContaining({ command: 'echo', index: 0, name: 'foo', prefixColor: 'red' }),
|
|
134
|
+
expect.objectContaining({ command: 'kill', index: 1, name: '', prefixColor: '' }),
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
it('runs commands with a list of colors', () => {
|
|
139
|
+
create(['echo', 'kill'], {
|
|
140
|
+
prefixColors: ['red'],
|
|
141
|
+
});
|
|
142
|
+
controllers.forEach((controller) => {
|
|
143
|
+
expect(controller.handle).toHaveBeenCalledWith([
|
|
144
|
+
expect.objectContaining({ command: 'echo', prefixColor: 'red' }),
|
|
145
|
+
expect.objectContaining({ command: 'kill', prefixColor: 'red' }),
|
|
146
|
+
]);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
it('passes commands wrapped from a controller to the next one', () => {
|
|
150
|
+
const fakeCommand = new FakeCommand('banana', 'banana');
|
|
151
|
+
controllers[0].handle.mockReturnValue({ commands: [fakeCommand] });
|
|
152
|
+
create(['echo']);
|
|
153
|
+
expect(controllers[0].handle).toHaveBeenCalledWith([
|
|
154
|
+
expect.objectContaining({ command: 'echo', index: 0 }),
|
|
155
|
+
]);
|
|
156
|
+
expect(controllers[1].handle).toHaveBeenCalledWith([fakeCommand]);
|
|
157
|
+
expect(fakeCommand.start).toHaveBeenCalledTimes(1);
|
|
158
|
+
});
|
|
159
|
+
it('merges extra env vars into each command', () => {
|
|
160
|
+
create([
|
|
161
|
+
{ command: 'echo', env: { foo: 'bar' } },
|
|
162
|
+
{ command: 'echo', env: { foo: 'baz' } },
|
|
163
|
+
'kill',
|
|
164
|
+
]);
|
|
165
|
+
expect(spawn).toHaveBeenCalledTimes(3);
|
|
166
|
+
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
167
|
+
env: expect.objectContaining({ foo: 'bar' }),
|
|
168
|
+
}));
|
|
169
|
+
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
170
|
+
env: expect.objectContaining({ foo: 'baz' }),
|
|
171
|
+
}));
|
|
172
|
+
expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
|
|
173
|
+
env: expect.not.objectContaining({ foo: expect.anything() }),
|
|
174
|
+
}));
|
|
175
|
+
});
|
|
176
|
+
it('uses cwd from options for each command', () => {
|
|
177
|
+
create([
|
|
178
|
+
{ command: 'echo', env: { foo: 'bar' } },
|
|
179
|
+
{ command: 'echo', env: { foo: 'baz' } },
|
|
180
|
+
'kill',
|
|
181
|
+
], {
|
|
182
|
+
cwd: 'foobar',
|
|
183
|
+
});
|
|
184
|
+
expect(spawn).toHaveBeenCalledTimes(3);
|
|
185
|
+
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
186
|
+
env: expect.objectContaining({ foo: 'bar' }),
|
|
187
|
+
cwd: 'foobar',
|
|
188
|
+
}));
|
|
189
|
+
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
190
|
+
env: expect.objectContaining({ foo: 'baz' }),
|
|
191
|
+
cwd: 'foobar',
|
|
192
|
+
}));
|
|
193
|
+
expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
|
|
194
|
+
env: expect.not.objectContaining({ foo: expect.anything() }),
|
|
195
|
+
cwd: 'foobar',
|
|
196
|
+
}));
|
|
197
|
+
});
|
|
198
|
+
it('uses overridden cwd option for each command if specified', () => {
|
|
199
|
+
create([
|
|
200
|
+
{ command: 'echo', env: { foo: 'bar' }, cwd: 'baz' },
|
|
201
|
+
{ command: 'echo', env: { foo: 'baz' } },
|
|
202
|
+
], {
|
|
203
|
+
cwd: 'foobar',
|
|
204
|
+
});
|
|
205
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
206
|
+
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
207
|
+
env: expect.objectContaining({ foo: 'bar' }),
|
|
208
|
+
cwd: 'baz',
|
|
209
|
+
}));
|
|
210
|
+
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
211
|
+
env: expect.objectContaining({ foo: 'baz' }),
|
|
212
|
+
cwd: 'foobar',
|
|
213
|
+
}));
|
|
214
|
+
});
|
|
215
|
+
it('uses raw from options for each command', () => {
|
|
216
|
+
create([{ command: 'echo' }, 'kill'], {
|
|
217
|
+
raw: true,
|
|
218
|
+
});
|
|
219
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
220
|
+
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
221
|
+
stdio: ['inherit', 'inherit', 'inherit'],
|
|
222
|
+
}));
|
|
223
|
+
expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
|
|
224
|
+
stdio: ['inherit', 'inherit', 'inherit'],
|
|
225
|
+
}));
|
|
226
|
+
});
|
|
227
|
+
it('uses overridden raw option for each command if specified', () => {
|
|
228
|
+
create([{ command: 'echo', raw: false }, { command: 'echo' }], {
|
|
229
|
+
raw: true,
|
|
230
|
+
});
|
|
231
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
232
|
+
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
233
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
234
|
+
}));
|
|
235
|
+
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
236
|
+
stdio: ['inherit', 'inherit', 'inherit'],
|
|
237
|
+
}));
|
|
238
|
+
});
|
|
239
|
+
it('uses hide from options for each command', () => {
|
|
240
|
+
create([{ command: 'echo' }, 'kill'], {
|
|
241
|
+
hide: [1],
|
|
242
|
+
});
|
|
243
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
244
|
+
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
245
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
246
|
+
}));
|
|
247
|
+
expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
|
|
248
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
249
|
+
}));
|
|
250
|
+
});
|
|
251
|
+
it('hides output for commands even if raw option is on', () => {
|
|
252
|
+
create([{ command: 'echo' }, 'kill'], {
|
|
253
|
+
hide: [1],
|
|
254
|
+
raw: true,
|
|
255
|
+
});
|
|
256
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
257
|
+
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
258
|
+
stdio: ['inherit', 'inherit', 'inherit'],
|
|
259
|
+
}));
|
|
260
|
+
expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
|
|
261
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
262
|
+
}));
|
|
263
|
+
});
|
|
264
|
+
it('argument placeholders are properly replaced when additional arguments are passed', () => {
|
|
265
|
+
create([
|
|
266
|
+
{ command: 'echo {1}' },
|
|
267
|
+
{ command: 'echo {@}' },
|
|
268
|
+
{ command: 'echo {*}' },
|
|
269
|
+
{ command: 'echo \\{@}' },
|
|
270
|
+
], {
|
|
271
|
+
additionalArguments: ['foo', 'bar'],
|
|
272
|
+
});
|
|
273
|
+
expect(spawn).toHaveBeenCalledTimes(4);
|
|
274
|
+
expect(spawn).toHaveBeenCalledWith('echo foo', expect.objectContaining({}));
|
|
275
|
+
expect(spawn).toHaveBeenCalledWith('echo foo bar', expect.objectContaining({}));
|
|
276
|
+
expect(spawn).toHaveBeenCalledWith("echo 'foo bar'", expect.objectContaining({}));
|
|
277
|
+
expect(spawn).toHaveBeenCalledWith('echo {@}', expect.objectContaining({}));
|
|
278
|
+
});
|
|
279
|
+
it('argument placeholders are not replaced when additional arguments are not defined', () => {
|
|
280
|
+
create([
|
|
281
|
+
{ command: 'echo {1}' },
|
|
282
|
+
{ command: 'echo {@}' },
|
|
283
|
+
{ command: 'echo {*}' },
|
|
284
|
+
{ command: 'echo \\{@}' },
|
|
285
|
+
]);
|
|
286
|
+
expect(spawn).toHaveBeenCalledTimes(4);
|
|
287
|
+
expect(spawn).toHaveBeenCalledWith('echo {1}', expect.objectContaining({}));
|
|
288
|
+
expect(spawn).toHaveBeenCalledWith('echo {@}', expect.objectContaining({}));
|
|
289
|
+
expect(spawn).toHaveBeenCalledWith('echo {*}', expect.objectContaining({}));
|
|
290
|
+
expect(spawn).toHaveBeenCalledWith('echo {@}', expect.objectContaining({}));
|
|
291
|
+
});
|
|
292
|
+
it('runs onFinish hook after all commands run', async () => {
|
|
293
|
+
const promise = create(['foo', 'bar'], { maxProcesses: 1 });
|
|
294
|
+
expect(spawn).toHaveBeenCalledTimes(1);
|
|
295
|
+
expect(onFinishHooks[0]).not.toHaveBeenCalled();
|
|
296
|
+
expect(onFinishHooks[1]).not.toHaveBeenCalled();
|
|
297
|
+
processes[0].emit('close', 0, null);
|
|
298
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
299
|
+
expect(onFinishHooks[0]).not.toHaveBeenCalled();
|
|
300
|
+
expect(onFinishHooks[1]).not.toHaveBeenCalled();
|
|
301
|
+
processes[1].emit('close', 0, null);
|
|
302
|
+
await promise.result;
|
|
303
|
+
expect(onFinishHooks[0]).toHaveBeenCalled();
|
|
304
|
+
expect(onFinishHooks[1]).toHaveBeenCalled();
|
|
305
|
+
});
|
|
306
|
+
// This test should time out if broken
|
|
307
|
+
it('waits for onFinish hooks to complete before resolving', async () => {
|
|
308
|
+
onFinishHooks[0].mockResolvedValue(undefined);
|
|
309
|
+
const { result } = create(['foo', 'bar']);
|
|
310
|
+
processes[0].emit('close', 0, null);
|
|
311
|
+
processes[1].emit('close', 0, null);
|
|
312
|
+
await expect(result).resolves.toBeDefined();
|
|
313
|
+
});
|
|
314
|
+
it('rejects if onFinish hooks reject', async () => {
|
|
315
|
+
onFinishHooks[0].mockRejectedValue('error');
|
|
316
|
+
const { result } = create(['foo', 'bar']);
|
|
317
|
+
processes[0].emit('close', 0, null);
|
|
318
|
+
processes[1].emit('close', 0, null);
|
|
319
|
+
await expect(result).rejects.toBe('error');
|
|
320
|
+
});
|