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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { PassThrough } from 'node:stream';
|
|
3
|
+
import { beforeEach, expect, it } from 'vitest';
|
|
4
|
+
import { createMockInstance } from '../__fixtures__/create-mock-instance.js';
|
|
5
|
+
import { FakeCommand } from '../__fixtures__/fake-command.js';
|
|
6
|
+
import { Logger } from '../logger.js';
|
|
7
|
+
import { InputHandler } from './input-handler.js';
|
|
8
|
+
let commands;
|
|
9
|
+
let controller;
|
|
10
|
+
let inputStream;
|
|
11
|
+
let logger;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
commands = [new FakeCommand('foo', 'echo foo', 0), new FakeCommand('bar', 'echo bar', 1)];
|
|
14
|
+
inputStream = new PassThrough();
|
|
15
|
+
logger = createMockInstance(Logger);
|
|
16
|
+
controller = new InputHandler({
|
|
17
|
+
defaultInputTarget: 0,
|
|
18
|
+
inputStream,
|
|
19
|
+
logger,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
it('returns same commands', () => {
|
|
23
|
+
expect(controller.handle(commands)).toMatchObject({ commands });
|
|
24
|
+
controller = new InputHandler({ logger, inputStream });
|
|
25
|
+
expect(controller.handle(commands)).toMatchObject({ commands });
|
|
26
|
+
});
|
|
27
|
+
it('does nothing if called without input stream', () => {
|
|
28
|
+
new InputHandler({
|
|
29
|
+
defaultInputTarget: 0,
|
|
30
|
+
inputStream: undefined,
|
|
31
|
+
logger,
|
|
32
|
+
}).handle(commands);
|
|
33
|
+
inputStream.write('something');
|
|
34
|
+
expect(commands[0].stdin?.write).not.toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
it('forwards input stream to default target ID', () => {
|
|
37
|
+
controller.handle(commands);
|
|
38
|
+
inputStream.write('something');
|
|
39
|
+
expect(commands[0].stdin?.write).toHaveBeenCalledExactlyOnceWith('something');
|
|
40
|
+
expect(commands[1].stdin?.write).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
it('forwards input stream to target index specified in input', () => {
|
|
43
|
+
controller.handle(commands);
|
|
44
|
+
inputStream.write('1:something');
|
|
45
|
+
inputStream.write('1:multi\nline\n');
|
|
46
|
+
expect(commands[0].stdin?.write).not.toHaveBeenCalled();
|
|
47
|
+
expect(commands[1].stdin?.write).toHaveBeenCalledTimes(2);
|
|
48
|
+
expect(commands[1].stdin?.write).toHaveBeenCalledWith('something');
|
|
49
|
+
expect(commands[1].stdin?.write).toHaveBeenCalledWith('multi\nline\n');
|
|
50
|
+
});
|
|
51
|
+
it('forwards input stream to target index specified in input when input contains colon', () => {
|
|
52
|
+
controller.handle(commands);
|
|
53
|
+
inputStream.emit('data', Buffer.from('1:some:thing'));
|
|
54
|
+
inputStream.emit('data', Buffer.from('1: :something'));
|
|
55
|
+
inputStream.emit('data', Buffer.from('1::something'));
|
|
56
|
+
expect(commands[0].stdin?.write).not.toHaveBeenCalled();
|
|
57
|
+
expect(commands[1].stdin?.write).toHaveBeenCalledTimes(3);
|
|
58
|
+
expect(commands[1].stdin?.write).toHaveBeenCalledWith('some:thing');
|
|
59
|
+
expect(commands[1].stdin?.write).toHaveBeenCalledWith(' :something');
|
|
60
|
+
expect(commands[1].stdin?.write).toHaveBeenCalledWith(':something');
|
|
61
|
+
});
|
|
62
|
+
it('does not forward input stream when input contains colon in a different format', () => {
|
|
63
|
+
controller.handle(commands);
|
|
64
|
+
inputStream.emit('data', Buffer.from('Ruby0::Const::Syntax'));
|
|
65
|
+
inputStream.emit('data', Buffer.from('1:Ruby1::Const::Syntax'));
|
|
66
|
+
inputStream.emit('data', Buffer.from('ruby_symbol_arg :my_symbol'));
|
|
67
|
+
inputStream.emit('data', Buffer.from('ruby_symbol_arg(:my_symbol)'));
|
|
68
|
+
inputStream.emit('data', Buffer.from('{ruby_key: :my_val}'));
|
|
69
|
+
inputStream.emit('data', Buffer.from('{:ruby_key=>:my_val}'));
|
|
70
|
+
inputStream.emit('data', Buffer.from('js_obj = {key: "my_val"}'));
|
|
71
|
+
expect(commands[1].stdin?.write).toHaveBeenCalledExactlyOnceWith('Ruby1::Const::Syntax');
|
|
72
|
+
expect(commands[0].stdin?.write).toHaveBeenCalledTimes(6);
|
|
73
|
+
expect(commands[0].stdin?.write).toHaveBeenCalledWith('Ruby0::Const::Syntax');
|
|
74
|
+
expect(commands[0].stdin?.write).toHaveBeenCalledWith('ruby_symbol_arg :my_symbol');
|
|
75
|
+
expect(commands[0].stdin?.write).toHaveBeenCalledWith('ruby_symbol_arg(:my_symbol)');
|
|
76
|
+
expect(commands[0].stdin?.write).toHaveBeenCalledWith('{ruby_key: :my_val}');
|
|
77
|
+
expect(commands[0].stdin?.write).toHaveBeenCalledWith('{:ruby_key=>:my_val}');
|
|
78
|
+
expect(commands[0].stdin?.write).toHaveBeenCalledWith('js_obj = {key: "my_val"}');
|
|
79
|
+
});
|
|
80
|
+
it('forwards input stream to target name specified in input', () => {
|
|
81
|
+
controller.handle(commands);
|
|
82
|
+
inputStream.write('bar:something');
|
|
83
|
+
expect(commands[0].stdin?.write).not.toHaveBeenCalled();
|
|
84
|
+
expect(commands[1].stdin?.write).toHaveBeenCalledExactlyOnceWith('something');
|
|
85
|
+
});
|
|
86
|
+
it('logs error if command has no stdin open', () => {
|
|
87
|
+
commands[0].stdin = undefined;
|
|
88
|
+
controller.handle(commands);
|
|
89
|
+
inputStream.write('something');
|
|
90
|
+
expect(commands[1].stdin?.write).not.toHaveBeenCalled();
|
|
91
|
+
expect(logger.logGlobalEvent).toHaveBeenCalledWith('Unable to find command "0", or it has no stdin open\n');
|
|
92
|
+
});
|
|
93
|
+
it('fallback to default input stream if command is not found', () => {
|
|
94
|
+
controller.handle(commands);
|
|
95
|
+
inputStream.write('foobar:something');
|
|
96
|
+
expect(commands[0].stdin?.write).toHaveBeenCalledExactlyOnceWith('foobar:something');
|
|
97
|
+
expect(commands[1].stdin?.write).not.toHaveBeenCalled();
|
|
98
|
+
expect(logger.logGlobalEvent).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
it('pauses input stream when finished', () => {
|
|
101
|
+
expect(inputStream.readableFlowing).toBeNull();
|
|
102
|
+
const { onFinish } = controller.handle(commands);
|
|
103
|
+
expect(inputStream.readableFlowing).toBe(true);
|
|
104
|
+
expect(onFinish).toBeDefined();
|
|
105
|
+
onFinish?.();
|
|
106
|
+
expect(inputStream.readableFlowing).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
it('does not pause input stream when pauseInputStreamOnFinish is set to false', () => {
|
|
109
|
+
controller = new InputHandler({ logger, inputStream, pauseInputStreamOnFinish: false });
|
|
110
|
+
expect(inputStream.readableFlowing).toBeNull();
|
|
111
|
+
const { onFinish } = controller.handle(commands);
|
|
112
|
+
expect(inputStream.readableFlowing).toBe(true);
|
|
113
|
+
expect(onFinish).toBeDefined();
|
|
114
|
+
onFinish?.();
|
|
115
|
+
expect(inputStream.readableFlowing).toBe(true);
|
|
116
|
+
});
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import { Command } from '../command';
|
|
5
|
-
import { FlowController } from './flow-controller';
|
|
1
|
+
import EventEmitter from 'node:events';
|
|
2
|
+
import { Command } from '../command.js';
|
|
3
|
+
import { FlowController } from './flow-controller.js';
|
|
6
4
|
/**
|
|
7
5
|
* Watches the main concurrently process for signals and sends the same signal down to each spawned
|
|
8
6
|
* command.
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.KillOnSignal = void 0;
|
|
4
|
-
const operators_1 = require("rxjs/operators");
|
|
1
|
+
import { map } from 'rxjs/operators';
|
|
5
2
|
const SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP'];
|
|
6
3
|
/**
|
|
7
4
|
* Watches the main concurrently process for signals and sends the same signal down to each spawned
|
|
8
5
|
* command.
|
|
9
6
|
*/
|
|
10
|
-
class KillOnSignal {
|
|
7
|
+
export class KillOnSignal {
|
|
11
8
|
process;
|
|
12
9
|
abortController;
|
|
13
10
|
constructor({ process, abortController, }) {
|
|
@@ -24,7 +21,7 @@ class KillOnSignal {
|
|
|
24
21
|
SIGNALS.forEach((signal) => this.process.on(signal, signalListener));
|
|
25
22
|
return {
|
|
26
23
|
commands: commands.map((command) => {
|
|
27
|
-
const closeStream = command.close.pipe(
|
|
24
|
+
const closeStream = command.close.pipe(map((exitInfo) => {
|
|
28
25
|
const exitCode = caughtSignal === 'SIGINT' ? 0 : exitInfo.exitCode;
|
|
29
26
|
return { ...exitInfo, exitCode };
|
|
30
27
|
}));
|
|
@@ -44,4 +41,3 @@ class KillOnSignal {
|
|
|
44
41
|
};
|
|
45
42
|
}
|
|
46
43
|
}
|
|
47
|
-
exports.KillOnSignal = KillOnSignal;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { createFakeCloseEvent, FakeCommand } from '../__fixtures__/fake-command.js';
|
|
4
|
+
import { KillOnSignal } from './kill-on-signal.js';
|
|
5
|
+
let commands;
|
|
6
|
+
let controller;
|
|
7
|
+
let process;
|
|
8
|
+
let abortController;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
process = new EventEmitter();
|
|
11
|
+
commands = [new FakeCommand(), new FakeCommand()];
|
|
12
|
+
abortController = new AbortController();
|
|
13
|
+
controller = new KillOnSignal({ process, abortController });
|
|
14
|
+
});
|
|
15
|
+
it('returns commands that keep non-close streams from original commands', () => {
|
|
16
|
+
const { commands: newCommands } = controller.handle(commands);
|
|
17
|
+
newCommands.forEach((newCommand, i) => {
|
|
18
|
+
expect(newCommand.close).not.toBe(commands[i].close);
|
|
19
|
+
expect(newCommand.error).toBe(commands[i].error);
|
|
20
|
+
expect(newCommand.stdout).toBe(commands[i].stdout);
|
|
21
|
+
expect(newCommand.stderr).toBe(commands[i].stderr);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
it('returns commands that map SIGINT to exit code 0', () => {
|
|
25
|
+
const { commands: newCommands } = controller.handle(commands);
|
|
26
|
+
expect(newCommands).not.toBe(commands);
|
|
27
|
+
expect(newCommands).toHaveLength(commands.length);
|
|
28
|
+
const callback = vi.fn();
|
|
29
|
+
newCommands[0].close.subscribe(callback);
|
|
30
|
+
process.emit('SIGINT', 'SIGINT');
|
|
31
|
+
// A fake command's .kill() call won't trigger a close event automatically...
|
|
32
|
+
commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
|
|
33
|
+
expect(callback).not.toHaveBeenCalledWith(expect.objectContaining({ exitCode: 'SIGINT' }));
|
|
34
|
+
expect(callback).toHaveBeenCalledWith(expect.objectContaining({ exitCode: 0 }));
|
|
35
|
+
});
|
|
36
|
+
it('returns commands that keep non-SIGINT exit codes', () => {
|
|
37
|
+
const { commands: newCommands } = controller.handle(commands);
|
|
38
|
+
expect(newCommands).not.toBe(commands);
|
|
39
|
+
expect(newCommands).toHaveLength(commands.length);
|
|
40
|
+
const callback = vi.fn();
|
|
41
|
+
newCommands[0].close.subscribe(callback);
|
|
42
|
+
commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
|
|
43
|
+
expect(callback).toHaveBeenCalledWith(expect.objectContaining({ exitCode: 1 }));
|
|
44
|
+
});
|
|
45
|
+
describe.each(['SIGINT', 'SIGTERM', 'SIGHUP'])('on %s', (signal) => {
|
|
46
|
+
it('kills all commands', () => {
|
|
47
|
+
controller.handle(commands);
|
|
48
|
+
process.emit(signal, signal);
|
|
49
|
+
expect(process.listenerCount(signal)).toBe(1);
|
|
50
|
+
expect(commands[0].kill).toHaveBeenCalledWith(signal);
|
|
51
|
+
expect(commands[1].kill).toHaveBeenCalledWith(signal);
|
|
52
|
+
});
|
|
53
|
+
it('sends abort signal', () => {
|
|
54
|
+
controller.handle(commands);
|
|
55
|
+
process.emit(signal, signal);
|
|
56
|
+
expect(abortController.signal.aborted).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
it('removes event listener on finish', () => {
|
|
59
|
+
const { onFinish } = controller.handle(commands);
|
|
60
|
+
onFinish();
|
|
61
|
+
expect(process.listenerCount(signal)).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { FlowController } from './flow-controller';
|
|
1
|
+
import { Command } from '../command.js';
|
|
2
|
+
import { Logger } from '../logger.js';
|
|
3
|
+
import { FlowController } from './flow-controller.js';
|
|
5
4
|
export type ProcessCloseCondition = 'failure' | 'success';
|
|
6
5
|
/**
|
|
7
6
|
* Sends a SIGTERM signal to all commands when one of the commands exits with a matching condition.
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.KillOthers = void 0;
|
|
7
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
-
const operators_1 = require("rxjs/operators");
|
|
9
|
-
const command_1 = require("../command");
|
|
1
|
+
import { filter, map } from 'rxjs/operators';
|
|
2
|
+
import { Command } from '../command.js';
|
|
3
|
+
import { castArray } from '../utils.js';
|
|
10
4
|
/**
|
|
11
5
|
* Sends a SIGTERM signal to all commands when one of the commands exits with a matching condition.
|
|
12
6
|
*/
|
|
13
|
-
class KillOthers {
|
|
7
|
+
export class KillOthers {
|
|
14
8
|
logger;
|
|
15
9
|
abortController;
|
|
16
10
|
conditions;
|
|
@@ -19,7 +13,7 @@ class KillOthers {
|
|
|
19
13
|
constructor({ logger, abortController, conditions, killSignal, timeoutMs, }) {
|
|
20
14
|
this.logger = logger;
|
|
21
15
|
this.abortController = abortController;
|
|
22
|
-
this.conditions =
|
|
16
|
+
this.conditions = castArray(conditions);
|
|
23
17
|
this.killSignal = killSignal;
|
|
24
18
|
this.timeoutMs = timeoutMs;
|
|
25
19
|
}
|
|
@@ -28,10 +22,10 @@ class KillOthers {
|
|
|
28
22
|
if (!conditions.length) {
|
|
29
23
|
return { commands };
|
|
30
24
|
}
|
|
31
|
-
const closeStates = commands.map((command) => command.close.pipe(
|
|
25
|
+
const closeStates = commands.map((command) => command.close.pipe(map(({ exitCode }) => exitCode === 0 ? 'success' : 'failure'), filter((state) => conditions.includes(state))));
|
|
32
26
|
closeStates.forEach((closeState) => closeState.subscribe(() => {
|
|
33
27
|
this.abortController?.abort();
|
|
34
|
-
const killableCommands = commands.filter((command) =>
|
|
28
|
+
const killableCommands = commands.filter((command) => Command.canKill(command));
|
|
35
29
|
if (killableCommands.length) {
|
|
36
30
|
this.logger.logGlobalEvent(`Sending ${this.killSignal || 'SIGTERM'} to other processes..`);
|
|
37
31
|
killableCommands.forEach((command) => command.kill(this.killSignal));
|
|
@@ -46,7 +40,7 @@ class KillOthers {
|
|
|
46
40
|
return;
|
|
47
41
|
}
|
|
48
42
|
setTimeout(() => {
|
|
49
|
-
const killableCommands = commands.filter((command) =>
|
|
43
|
+
const killableCommands = commands.filter((command) => Command.canKill(command));
|
|
50
44
|
if (killableCommands) {
|
|
51
45
|
this.logger.logGlobalEvent(`Sending SIGKILL to ${killableCommands.length} processes..`);
|
|
52
46
|
killableCommands.forEach((command) => command.kill('SIGKILL'));
|
|
@@ -54,4 +48,3 @@ class KillOthers {
|
|
|
54
48
|
}, this.timeoutMs);
|
|
55
49
|
}
|
|
56
50
|
}
|
|
57
|
-
exports.KillOthers = KillOthers;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { createMockInstance } from '../__fixtures__/create-mock-instance.js';
|
|
3
|
+
import { createFakeCloseEvent, createFakeProcess, FakeCommand, } from '../__fixtures__/fake-command.js';
|
|
4
|
+
import { Logger } from '../logger.js';
|
|
5
|
+
import { KillOthers } from './kill-others.js';
|
|
6
|
+
let commands;
|
|
7
|
+
let logger;
|
|
8
|
+
let abortController;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
commands = [new FakeCommand(), new FakeCommand()];
|
|
11
|
+
logger = createMockInstance(Logger);
|
|
12
|
+
abortController = new AbortController();
|
|
13
|
+
});
|
|
14
|
+
const createWithConditions = (conditions, opts) => new KillOthers({
|
|
15
|
+
logger,
|
|
16
|
+
abortController,
|
|
17
|
+
conditions,
|
|
18
|
+
killSignal: undefined,
|
|
19
|
+
...opts,
|
|
20
|
+
});
|
|
21
|
+
const assignProcess = (command) => {
|
|
22
|
+
const process = createFakeProcess(1);
|
|
23
|
+
command.pid = process.pid;
|
|
24
|
+
command.process = process;
|
|
25
|
+
};
|
|
26
|
+
const unassignProcess = (command) => {
|
|
27
|
+
command.pid = undefined;
|
|
28
|
+
command.process = undefined;
|
|
29
|
+
};
|
|
30
|
+
it('returns same commands', () => {
|
|
31
|
+
expect(createWithConditions(['success']).handle(commands)).toMatchObject({ commands });
|
|
32
|
+
expect(createWithConditions(['failure']).handle(commands)).toMatchObject({ commands });
|
|
33
|
+
});
|
|
34
|
+
it('does not kill others if condition does not match', () => {
|
|
35
|
+
createWithConditions(['failure']).handle(commands);
|
|
36
|
+
assignProcess(commands[1]);
|
|
37
|
+
commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
|
|
38
|
+
expect(logger.logGlobalEvent).not.toHaveBeenCalled();
|
|
39
|
+
expect(commands[0].kill).not.toHaveBeenCalled();
|
|
40
|
+
expect(commands[1].kill).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
describe.each(['success', 'failure'])('on %s', (condition) => {
|
|
43
|
+
const exitCode = condition === 'success' ? 0 : 1;
|
|
44
|
+
const inversedCode = exitCode === 1 ? 0 : 1;
|
|
45
|
+
it('kills other processes', () => {
|
|
46
|
+
createWithConditions([condition]).handle(commands);
|
|
47
|
+
assignProcess(commands[1]);
|
|
48
|
+
commands[0].close.next(createFakeCloseEvent({ exitCode }));
|
|
49
|
+
expect(logger.logGlobalEvent).toHaveBeenCalledExactlyOnceWith('Sending SIGTERM to other processes..');
|
|
50
|
+
expect(commands[0].kill).not.toHaveBeenCalled();
|
|
51
|
+
expect(commands[1].kill).toHaveBeenCalledWith(undefined);
|
|
52
|
+
});
|
|
53
|
+
it('kills other processes, with specified signal', () => {
|
|
54
|
+
createWithConditions([condition], { killSignal: 'SIGKILL' }).handle(commands);
|
|
55
|
+
assignProcess(commands[1]);
|
|
56
|
+
commands[0].close.next(createFakeCloseEvent({ exitCode }));
|
|
57
|
+
expect(logger.logGlobalEvent).toHaveBeenCalledExactlyOnceWith('Sending SIGKILL to other processes..');
|
|
58
|
+
expect(commands[0].kill).not.toHaveBeenCalled();
|
|
59
|
+
expect(commands[1].kill).toHaveBeenCalledWith('SIGKILL');
|
|
60
|
+
});
|
|
61
|
+
it('sends abort signal on condition match', () => {
|
|
62
|
+
createWithConditions([condition]).handle(commands);
|
|
63
|
+
commands[0].close.next(createFakeCloseEvent({ exitCode }));
|
|
64
|
+
expect(abortController.signal.aborted).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
it('does not send abort signal on condition mismatch', () => {
|
|
67
|
+
createWithConditions([condition]).handle(commands);
|
|
68
|
+
commands[0].close.next(createFakeCloseEvent({ exitCode: inversedCode }));
|
|
69
|
+
expect(abortController.signal.aborted).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
it('does nothing if called without conditions', () => {
|
|
73
|
+
createWithConditions([]).handle(commands);
|
|
74
|
+
commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
|
|
75
|
+
expect(logger.logGlobalEvent).not.toHaveBeenCalled();
|
|
76
|
+
expect(commands[0].kill).not.toHaveBeenCalled();
|
|
77
|
+
expect(commands[1].kill).not.toHaveBeenCalled();
|
|
78
|
+
});
|
|
79
|
+
it('does not try to kill processes already dead', () => {
|
|
80
|
+
createWithConditions(['failure']).handle(commands);
|
|
81
|
+
commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
|
|
82
|
+
expect(logger.logGlobalEvent).not.toHaveBeenCalled();
|
|
83
|
+
expect(commands[0].kill).not.toHaveBeenCalled();
|
|
84
|
+
expect(commands[1].kill).not.toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
it('force kills misbehaving processes after a timeout', () => {
|
|
87
|
+
vi.useFakeTimers();
|
|
88
|
+
commands.push(new FakeCommand());
|
|
89
|
+
createWithConditions(['failure'], { timeoutMs: 500 }).handle(commands);
|
|
90
|
+
assignProcess(commands[1]);
|
|
91
|
+
assignProcess(commands[2]);
|
|
92
|
+
commands[2].kill = vi.fn(() => unassignProcess(commands[2]));
|
|
93
|
+
commands[0].close.next(createFakeCloseEvent({ exitCode: 1 }));
|
|
94
|
+
vi.advanceTimersByTime(500);
|
|
95
|
+
expect(commands[1].kill).toHaveBeenCalledTimes(2);
|
|
96
|
+
expect(commands[1].kill).toHaveBeenCalledWith('SIGKILL');
|
|
97
|
+
expect(commands[2].kill).toHaveBeenCalledTimes(1);
|
|
98
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Command } from '../command';
|
|
2
|
-
import { Logger } from '../logger';
|
|
3
|
-
import { FlowController } from './flow-controller';
|
|
1
|
+
import { Command } from '../command.js';
|
|
2
|
+
import { Logger } from '../logger.js';
|
|
3
|
+
import { FlowController } from './flow-controller.js';
|
|
4
4
|
/**
|
|
5
5
|
* Logs when commands failed executing, e.g. due to the executable not existing in the system.
|
|
6
6
|
*/
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LogError = void 0;
|
|
4
1
|
/**
|
|
5
2
|
* Logs when commands failed executing, e.g. due to the executable not existing in the system.
|
|
6
3
|
*/
|
|
7
|
-
class LogError {
|
|
4
|
+
export class LogError {
|
|
8
5
|
logger;
|
|
9
6
|
constructor({ logger }) {
|
|
10
7
|
this.logger = logger;
|
|
@@ -18,4 +15,3 @@ class LogError {
|
|
|
18
15
|
return { commands };
|
|
19
16
|
}
|
|
20
17
|
}
|
|
21
|
-
exports.LogError = LogError;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { beforeEach, expect, it } from 'vitest';
|
|
2
|
+
import { createMockInstance } from '../__fixtures__/create-mock-instance.js';
|
|
3
|
+
import { FakeCommand } from '../__fixtures__/fake-command.js';
|
|
4
|
+
import { Logger } from '../logger.js';
|
|
5
|
+
import { LogError } from './log-error.js';
|
|
6
|
+
let controller;
|
|
7
|
+
let logger;
|
|
8
|
+
let commands;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
commands = [new FakeCommand(), new FakeCommand(), new FakeCommand()];
|
|
11
|
+
logger = createMockInstance(Logger);
|
|
12
|
+
controller = new LogError({ logger });
|
|
13
|
+
});
|
|
14
|
+
it('returns same commands', () => {
|
|
15
|
+
expect(controller.handle(commands)).toMatchObject({ commands });
|
|
16
|
+
});
|
|
17
|
+
it('logs the error event of each command', () => {
|
|
18
|
+
controller.handle(commands);
|
|
19
|
+
commands[0].error.next('error from command 0');
|
|
20
|
+
const error1 = new Error('test');
|
|
21
|
+
commands[1].error.next(error1);
|
|
22
|
+
// Testing Error without stack
|
|
23
|
+
const error2 = new Error('test');
|
|
24
|
+
error2.stack = '';
|
|
25
|
+
commands[2].error.next(error2);
|
|
26
|
+
expect(logger.logCommandEvent).toHaveBeenCalledTimes(6);
|
|
27
|
+
expect(logger.logCommandEvent).toHaveBeenCalledWith(`Error occurred when executing command: ${commands[0].command}`, commands[0]);
|
|
28
|
+
expect(logger.logCommandEvent).toHaveBeenCalledWith('error from command 0', commands[0]);
|
|
29
|
+
expect(logger.logCommandEvent).toHaveBeenCalledWith(`Error occurred when executing command: ${commands[1].command}`, commands[1]);
|
|
30
|
+
expect(logger.logCommandEvent).toHaveBeenCalledWith(error1.stack, commands[1]);
|
|
31
|
+
expect(logger.logCommandEvent).toHaveBeenCalledWith(`Error occurred when executing command: ${commands[2].command}`, commands[2]);
|
|
32
|
+
expect(logger.logCommandEvent).toHaveBeenCalledWith(String(error2), commands[2]);
|
|
33
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Command } from '../command';
|
|
2
|
-
import { Logger } from '../logger';
|
|
3
|
-
import { FlowController } from './flow-controller';
|
|
1
|
+
import { Command } from '../command.js';
|
|
2
|
+
import { Logger } from '../logger.js';
|
|
3
|
+
import { FlowController } from './flow-controller.js';
|
|
4
4
|
/**
|
|
5
5
|
* Logs the exit code/signal of commands.
|
|
6
6
|
*/
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LogExit = void 0;
|
|
4
1
|
/**
|
|
5
2
|
* Logs the exit code/signal of commands.
|
|
6
3
|
*/
|
|
7
|
-
class LogExit {
|
|
4
|
+
export class LogExit {
|
|
8
5
|
logger;
|
|
9
6
|
constructor({ logger }) {
|
|
10
7
|
this.logger = logger;
|
|
@@ -16,4 +13,3 @@ class LogExit {
|
|
|
16
13
|
return { commands };
|
|
17
14
|
}
|
|
18
15
|
}
|
|
19
|
-
exports.LogExit = LogExit;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { beforeEach, expect, it } from 'vitest';
|
|
2
|
+
import { createMockInstance } from '../__fixtures__/create-mock-instance.js';
|
|
3
|
+
import { createFakeCloseEvent, FakeCommand } from '../__fixtures__/fake-command.js';
|
|
4
|
+
import { Logger } from '../logger.js';
|
|
5
|
+
import { LogExit } from './log-exit.js';
|
|
6
|
+
let controller;
|
|
7
|
+
let logger;
|
|
8
|
+
let commands;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
commands = [new FakeCommand(), new FakeCommand()];
|
|
11
|
+
logger = createMockInstance(Logger);
|
|
12
|
+
controller = new LogExit({ logger });
|
|
13
|
+
});
|
|
14
|
+
it('returns same commands', () => {
|
|
15
|
+
expect(controller.handle(commands)).toMatchObject({ commands });
|
|
16
|
+
});
|
|
17
|
+
it('logs the close event of each command', () => {
|
|
18
|
+
controller.handle(commands);
|
|
19
|
+
commands[0].close.next(createFakeCloseEvent({ exitCode: 0 }));
|
|
20
|
+
commands[1].close.next(createFakeCloseEvent({ exitCode: 'SIGTERM' }));
|
|
21
|
+
expect(logger.logCommandEvent).toHaveBeenCalledTimes(2);
|
|
22
|
+
expect(logger.logCommandEvent).toHaveBeenCalledWith(`${commands[0].command} exited with code 0`, commands[0]);
|
|
23
|
+
expect(logger.logCommandEvent).toHaveBeenCalledWith(`${commands[1].command} exited with code SIGTERM`, commands[1]);
|
|
24
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Command } from '../command';
|
|
2
|
-
import { Logger } from '../logger';
|
|
3
|
-
import { FlowController } from './flow-controller';
|
|
1
|
+
import { Command } from '../command.js';
|
|
2
|
+
import { Logger } from '../logger.js';
|
|
3
|
+
import { FlowController } from './flow-controller.js';
|
|
4
4
|
/**
|
|
5
5
|
* Logs the stdout and stderr output of commands.
|
|
6
6
|
*/
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LogOutput = void 0;
|
|
4
1
|
/**
|
|
5
2
|
* Logs the stdout and stderr output of commands.
|
|
6
3
|
*/
|
|
7
|
-
class LogOutput {
|
|
4
|
+
export class LogOutput {
|
|
8
5
|
logger;
|
|
9
6
|
constructor({ logger }) {
|
|
10
7
|
this.logger = logger;
|
|
@@ -17,4 +14,3 @@ class LogOutput {
|
|
|
17
14
|
return { commands };
|
|
18
15
|
}
|
|
19
16
|
}
|
|
20
|
-
exports.LogOutput = LogOutput;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { beforeEach, expect, it } from 'vitest';
|
|
3
|
+
import { createMockInstance } from '../__fixtures__/create-mock-instance.js';
|
|
4
|
+
import { FakeCommand } from '../__fixtures__/fake-command.js';
|
|
5
|
+
import { Logger } from '../logger.js';
|
|
6
|
+
import { LogOutput } from './log-output.js';
|
|
7
|
+
let controller;
|
|
8
|
+
let logger;
|
|
9
|
+
let commands;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
commands = [new FakeCommand(), new FakeCommand()];
|
|
12
|
+
logger = createMockInstance(Logger);
|
|
13
|
+
controller = new LogOutput({ logger });
|
|
14
|
+
});
|
|
15
|
+
it('returns same commands', () => {
|
|
16
|
+
expect(controller.handle(commands)).toMatchObject({ commands });
|
|
17
|
+
});
|
|
18
|
+
it('logs the stdout of each command', () => {
|
|
19
|
+
controller.handle(commands);
|
|
20
|
+
commands[0].stdout.next(Buffer.from('foo'));
|
|
21
|
+
commands[1].stdout.next(Buffer.from('bar'));
|
|
22
|
+
expect(logger.logCommandText).toHaveBeenCalledTimes(2);
|
|
23
|
+
expect(logger.logCommandText).toHaveBeenCalledWith('foo', commands[0]);
|
|
24
|
+
expect(logger.logCommandText).toHaveBeenCalledWith('bar', commands[1]);
|
|
25
|
+
});
|
|
26
|
+
it('logs the stderr of each command', () => {
|
|
27
|
+
controller.handle(commands);
|
|
28
|
+
commands[0].stderr.next(Buffer.from('foo'));
|
|
29
|
+
commands[1].stderr.next(Buffer.from('bar'));
|
|
30
|
+
expect(logger.logCommandText).toHaveBeenCalledTimes(2);
|
|
31
|
+
expect(logger.logCommandText).toHaveBeenCalledWith('foo', commands[0]);
|
|
32
|
+
expect(logger.logCommandText).toHaveBeenCalledWith('bar', commands[1]);
|
|
33
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CloseEvent, Command } from '../command';
|
|
2
|
-
import { Logger } from '../logger';
|
|
3
|
-
import { FlowController } from './flow-controller';
|
|
1
|
+
import { CloseEvent, Command } from '../command.js';
|
|
2
|
+
import { Logger } from '../logger.js';
|
|
3
|
+
import { FlowController } from './flow-controller.js';
|
|
4
4
|
type TimingInfo = {
|
|
5
5
|
name: string;
|
|
6
6
|
duration: string;
|