concurrently 9.2.1 → 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 -14
- package/dist/bin/{concurrently.js → index.js} +23 -62
- 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 +2 -2
- package/dist/{src → lib}/command-parser/expand-arguments.js +7 -12
- 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 +21 -28
- 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 +5 -4
- package/dist/{src → lib}/command.js +3 -39
- 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 -2
- package/dist/{src → lib}/completion-listener.js +9 -46
- 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 -10
- package/dist/{src → lib}/concurrently.js +29 -40
- 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 +97 -76
- 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 -4
- package/dist/{src → lib}/flow-control/input-handler.js +8 -44
- 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 -3
- 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 -3
- package/dist/{src → lib}/flow-control/kill-others.js +8 -12
- 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 +8 -45
- 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 -4
- 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/lib/flow-control/restart-process.js +61 -0
- 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/lib/flow-control/teardown.js +45 -0
- 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 -19
- package/dist/lib/index.js +98 -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 +109 -65
- 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 -1
- package/dist/{src → lib}/observables.js +3 -6
- 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 -3
- package/dist/{src → lib}/output-writer.js +4 -41
- 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 +14 -5
- 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 -1
- package/docs/README.md +6 -0
- package/docs/cli/prefixing.md +44 -4
- package/docs/shell-resolution.md +48 -0
- package/package.json +59 -75
- package/dist/bin/read-package.d.ts +0 -6
- package/dist/bin/read-package.js +0 -56
- package/dist/src/assert.d.ts +0 -5
- package/dist/src/assert.js +0 -15
- 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/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/flow-control/restart-process.js +0 -98
- package/dist/src/flow-control/teardown.js +0 -82
- package/dist/src/index.js +0 -96
- package/dist/src/prefix-color-selector.d.ts +0 -11
- package/dist/src/spawn.js +0 -49
- package/dist/src/utils.d.ts +0 -10
- package/dist/src/utils.js +0 -16
- package/index.d.mts +0 -7
- package/index.d.ts +0 -11
- package/index.js +0 -14
- package/index.mjs +0 -9
- /package/dist/bin/{concurrently.d.ts → index.d.ts} +0 -0
- /package/dist/{src → lib}/jsonc.d.ts +0 -0
|
@@ -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
|
+
});
|
|
@@ -1,76 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DateFormatter = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Unicode-compliant date/time formatter.
|
|
6
|
-
*
|
|
7
|
-
* @see https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
|
|
8
|
-
*/
|
|
9
|
-
class DateFormatter {
|
|
10
|
-
options;
|
|
11
|
-
static tokenRegex = /[A-Z]/i;
|
|
12
|
-
parts = [];
|
|
13
|
-
constructor(pattern, options = {}) {
|
|
14
|
-
this.options = options;
|
|
15
|
-
let i = 0;
|
|
16
|
-
while (i < pattern.length) {
|
|
17
|
-
const char = pattern[i];
|
|
18
|
-
const { fn, length } = char === "'"
|
|
19
|
-
? this.compileLiteral(pattern, i)
|
|
20
|
-
: DateFormatter.tokenRegex.test(char)
|
|
21
|
-
? this.compileToken(pattern, i)
|
|
22
|
-
: this.compileOther(pattern, i);
|
|
23
|
-
this.parts.push(fn);
|
|
24
|
-
i += length;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
compileLiteral(pattern, offset) {
|
|
28
|
-
let length = 1;
|
|
29
|
-
let value = '';
|
|
30
|
-
for (; length < pattern.length; length++) {
|
|
31
|
-
const i = offset + length;
|
|
32
|
-
const char = pattern[i];
|
|
33
|
-
if (char === "'") {
|
|
34
|
-
const nextChar = pattern[i + 1];
|
|
35
|
-
length++;
|
|
36
|
-
// if the next character is another single quote, it's been escaped.
|
|
37
|
-
// if not, then the literal has been closed
|
|
38
|
-
if (nextChar !== "'") {
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
value += char;
|
|
43
|
-
}
|
|
44
|
-
return { fn: () => value || "'", length };
|
|
45
|
-
}
|
|
46
|
-
compileOther(pattern, offset) {
|
|
47
|
-
let value = '';
|
|
48
|
-
while (!DateFormatter.tokenRegex.test(pattern[offset]) && pattern[offset] !== "'") {
|
|
49
|
-
value += pattern[offset++];
|
|
50
|
-
}
|
|
51
|
-
return { fn: () => value, length: value.length };
|
|
52
|
-
}
|
|
53
|
-
compileToken(pattern, offset) {
|
|
54
|
-
const type = pattern[offset];
|
|
55
|
-
const token = tokens.get(type);
|
|
56
|
-
if (!token) {
|
|
57
|
-
throw new SyntaxError(`Formatting token "${type}" is invalid`);
|
|
58
|
-
}
|
|
59
|
-
let length = 0;
|
|
60
|
-
while (pattern[offset + length] === type) {
|
|
61
|
-
length++;
|
|
62
|
-
}
|
|
63
|
-
const tokenFn = token[length - 1];
|
|
64
|
-
if (!tokenFn) {
|
|
65
|
-
throw new RangeError(`Formatting token "${type.repeat(length)}" is unsupported`);
|
|
66
|
-
}
|
|
67
|
-
return { fn: tokenFn, length };
|
|
68
|
-
}
|
|
69
|
-
format(date) {
|
|
70
|
-
return this.parts.reduce((output, part) => output + String(part(date, this.options)), '');
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
exports.DateFormatter = DateFormatter;
|
|
74
1
|
/**
|
|
75
2
|
* A map of token to its implementations by length.
|
|
76
3
|
* If an index is undefined, then that token length is unsupported.
|
|
@@ -241,6 +168,99 @@ function getLocale(options) {
|
|
|
241
168
|
}
|
|
242
169
|
return locale;
|
|
243
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Unicode-compliant date/time formatter.
|
|
173
|
+
*
|
|
174
|
+
* @see https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
|
|
175
|
+
*/
|
|
176
|
+
export class DateFormatter {
|
|
177
|
+
options;
|
|
178
|
+
static tokenRegex = /[A-Z]/i;
|
|
179
|
+
parts = [];
|
|
180
|
+
constructor(pattern, options = {}) {
|
|
181
|
+
this.options = options;
|
|
182
|
+
let i = 0;
|
|
183
|
+
while (i < pattern.length) {
|
|
184
|
+
const char = pattern[i];
|
|
185
|
+
const { fn, length } = char === "'"
|
|
186
|
+
? this.compileLiteral(pattern, i)
|
|
187
|
+
: DateFormatter.tokenRegex.test(char)
|
|
188
|
+
? this.compileToken(pattern, i)
|
|
189
|
+
: this.compileOther(pattern, i);
|
|
190
|
+
this.parts.push(fn);
|
|
191
|
+
i += length;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
compileLiteral(pattern, offset) {
|
|
195
|
+
let length = 1;
|
|
196
|
+
let value = '';
|
|
197
|
+
for (; length < pattern.length; length++) {
|
|
198
|
+
const i = offset + length;
|
|
199
|
+
const char = pattern[i];
|
|
200
|
+
if (char === "'") {
|
|
201
|
+
const nextChar = pattern[i + 1];
|
|
202
|
+
length++;
|
|
203
|
+
// if the next character is another single quote, it's been escaped.
|
|
204
|
+
// if not, then the literal has been closed
|
|
205
|
+
if (nextChar !== "'") {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
value += char;
|
|
210
|
+
}
|
|
211
|
+
return { fn: () => value || "'", length };
|
|
212
|
+
}
|
|
213
|
+
compileOther(pattern, offset) {
|
|
214
|
+
let value = '';
|
|
215
|
+
while (!DateFormatter.tokenRegex.test(pattern[offset]) && pattern[offset] !== "'") {
|
|
216
|
+
value += pattern[offset++];
|
|
217
|
+
}
|
|
218
|
+
return { fn: () => value, length: value.length };
|
|
219
|
+
}
|
|
220
|
+
compileToken(pattern, offset) {
|
|
221
|
+
const type = pattern[offset];
|
|
222
|
+
const token = tokens.get(type);
|
|
223
|
+
if (!token) {
|
|
224
|
+
throw new SyntaxError(`Formatting token "${type}" is invalid`);
|
|
225
|
+
}
|
|
226
|
+
let length = 0;
|
|
227
|
+
while (pattern[offset + length] === type) {
|
|
228
|
+
length++;
|
|
229
|
+
}
|
|
230
|
+
const tokenFn = token[length - 1];
|
|
231
|
+
if (!tokenFn) {
|
|
232
|
+
throw new RangeError(`Formatting token "${type.repeat(length)}" is unsupported`);
|
|
233
|
+
}
|
|
234
|
+
return { fn: tokenFn, length };
|
|
235
|
+
}
|
|
236
|
+
format(date) {
|
|
237
|
+
return this.parts.reduce((output, part) => output + String(part(date, this.options)), '');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* See https://github.com/moment/luxon/issues/1693
|
|
242
|
+
*
|
|
243
|
+
* @todo Replace once there's a suitable alternative implementation
|
|
244
|
+
*/
|
|
245
|
+
const fallbackWeekInfo = {
|
|
246
|
+
minimalDays: 4,
|
|
247
|
+
};
|
|
248
|
+
const weekInfoCache = new Map();
|
|
249
|
+
function getWeekInfo(locale) {
|
|
250
|
+
let data = weekInfoCache.get(locale.baseName);
|
|
251
|
+
if (!data) {
|
|
252
|
+
// The specs now envisage a method for this,
|
|
253
|
+
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getWeekInfo
|
|
254
|
+
data = locale.getWeekInfo?.() ?? locale.weekInfo;
|
|
255
|
+
// The specs have dropped minimalDays,
|
|
256
|
+
// see https://github.com/tc39/proposal-intl-locale-info/issues/86
|
|
257
|
+
if (!data.minimalDays) {
|
|
258
|
+
data = { ...fallbackWeekInfo, ...data };
|
|
259
|
+
}
|
|
260
|
+
weekInfoCache.set(locale.baseName, data);
|
|
261
|
+
}
|
|
262
|
+
return data;
|
|
263
|
+
}
|
|
244
264
|
/**
|
|
245
265
|
* Creates a token formatting function that returns the value of the chosen part type,
|
|
246
266
|
* using the current locale's settings.
|
|
@@ -269,7 +289,8 @@ function makeTokenFn(options, type, fallback) {
|
|
|
269
289
|
}
|
|
270
290
|
function startOfWeek(date, options) {
|
|
271
291
|
const locale = getLocale(options);
|
|
272
|
-
const
|
|
292
|
+
const weekInfo = getWeekInfo(locale);
|
|
293
|
+
const firstDay = weekInfo.firstDay === 7 ? 0 : weekInfo.firstDay;
|
|
273
294
|
const day = date.getDay();
|
|
274
295
|
const diff = (day < firstDay ? 7 : 0) + day - firstDay;
|
|
275
296
|
date.setDate(date.getDate() - diff);
|
|
@@ -278,7 +299,7 @@ function startOfWeek(date, options) {
|
|
|
278
299
|
}
|
|
279
300
|
function getWeekYear(date, options) {
|
|
280
301
|
const locale = getLocale(options);
|
|
281
|
-
const minimalDays = locale.
|
|
302
|
+
const minimalDays = getWeekInfo(locale).minimalDays;
|
|
282
303
|
const year = date.getFullYear();
|
|
283
304
|
const thisYear = startOfWeek(new Date(year, 0, minimalDays), options);
|
|
284
305
|
const nextYear = startOfWeek(new Date(year + 1, 0, minimalDays), options);
|
|
@@ -296,7 +317,7 @@ function getWeek(date, options) {
|
|
|
296
317
|
const locale = getLocale(options);
|
|
297
318
|
const weekMs = 7 * 24 * 3600 * 1000;
|
|
298
319
|
const temp = startOfWeek(new Date(date), options);
|
|
299
|
-
const thisYear = new Date(getWeekYear(date, options), 0, locale.
|
|
320
|
+
const thisYear = new Date(getWeekYear(date, options), 0, getWeekInfo(locale).minimalDays);
|
|
300
321
|
startOfWeek(thisYear, options);
|
|
301
322
|
const diff = temp.getTime() - thisYear.getTime();
|
|
302
323
|
return Math.round(diff / weekMs) + 1;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|