concurrently 6.5.0 → 7.1.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 +82 -22
- package/dist/bin/concurrently.d.ts +2 -0
- package/dist/bin/concurrently.js +193 -0
- package/dist/bin/epilogue.d.ts +1 -0
- package/dist/bin/epilogue.js +69 -0
- package/dist/src/command-parser/command-parser.d.ts +19 -0
- package/dist/src/command-parser/command-parser.js +2 -0
- package/dist/src/command-parser/expand-npm-shortcut.d.ts +8 -0
- package/{src → dist/src}/command-parser/expand-npm-shortcut.js +10 -3
- package/dist/src/command-parser/expand-npm-wildcard.d.ts +13 -0
- package/dist/src/command-parser/expand-npm-wildcard.js +82 -0
- package/dist/src/command-parser/strip-quotes.d.ts +10 -0
- package/{src → dist/src}/command-parser/strip-quotes.js +10 -4
- package/dist/src/command.d.ts +101 -0
- package/{src → dist/src}/command.js +53 -29
- package/dist/src/completion-listener.d.ts +37 -0
- package/dist/src/completion-listener.js +60 -0
- package/dist/src/concurrently.d.ts +77 -0
- package/dist/src/concurrently.js +120 -0
- package/dist/src/defaults.d.ts +52 -0
- package/dist/src/defaults.js +58 -0
- package/dist/src/flow-control/flow-controller.d.ts +13 -0
- package/dist/src/flow-control/flow-controller.js +2 -0
- package/dist/src/flow-control/input-handler.d.ts +33 -0
- package/dist/src/flow-control/input-handler.js +73 -0
- package/dist/src/flow-control/kill-on-signal.d.ts +17 -0
- package/{src → dist/src}/flow-control/kill-on-signal.js +13 -11
- package/dist/src/flow-control/kill-others.d.ts +18 -0
- package/dist/src/flow-control/kill-others.js +35 -0
- package/dist/src/flow-control/log-error.d.ts +15 -0
- package/dist/src/flow-control/log-error.js +21 -0
- package/dist/src/flow-control/log-exit.d.ts +15 -0
- package/dist/src/flow-control/log-exit.js +19 -0
- package/dist/src/flow-control/log-output.d.ts +15 -0
- package/{src → dist/src}/flow-control/log-output.js +13 -5
- package/dist/src/flow-control/log-timings.d.ts +27 -0
- package/dist/src/flow-control/log-timings.js +88 -0
- package/dist/src/flow-control/restart-process.d.ts +22 -0
- package/dist/src/flow-control/restart-process.js +71 -0
- package/dist/src/get-spawn-opts.d.ts +30 -0
- package/dist/src/get-spawn-opts.js +11 -0
- package/dist/src/index.d.ts +69 -0
- package/dist/src/index.js +69 -0
- package/dist/src/logger.d.ts +72 -0
- package/{src → dist/src}/logger.js +77 -58
- package/dist/src/output-writer.d.ts +19 -0
- package/dist/src/output-writer.js +69 -0
- package/index.js +6 -68
- package/index.mjs +9 -0
- package/package.json +38 -11
- package/bin/concurrently.js +0 -186
- package/bin/concurrently.spec.js +0 -428
- package/bin/epilogue.txt +0 -46
- package/src/command-parser/expand-npm-shortcut.spec.js +0 -36
- package/src/command-parser/expand-npm-wildcard.js +0 -43
- package/src/command-parser/expand-npm-wildcard.spec.js +0 -87
- package/src/command-parser/strip-quotes.spec.js +0 -20
- package/src/command.spec.js +0 -275
- package/src/completion-listener.js +0 -39
- package/src/completion-listener.spec.js +0 -89
- package/src/concurrently.js +0 -116
- package/src/concurrently.spec.js +0 -199
- package/src/defaults.js +0 -35
- package/src/flow-control/base-handler.js +0 -16
- package/src/flow-control/base-handler.spec.js +0 -22
- package/src/flow-control/input-handler.js +0 -50
- package/src/flow-control/input-handler.spec.js +0 -113
- package/src/flow-control/kill-on-signal.spec.js +0 -79
- package/src/flow-control/kill-others.js +0 -38
- package/src/flow-control/kill-others.spec.js +0 -66
- package/src/flow-control/log-error.js +0 -18
- package/src/flow-control/log-error.spec.js +0 -40
- package/src/flow-control/log-exit.js +0 -11
- package/src/flow-control/log-exit.spec.js +0 -36
- package/src/flow-control/log-output.spec.js +0 -41
- package/src/flow-control/log-timings.js +0 -64
- package/src/flow-control/log-timings.spec.js +0 -137
- package/src/flow-control/restart-process.js +0 -56
- package/src/flow-control/restart-process.spec.js +0 -139
- package/src/get-spawn-opts.js +0 -16
- package/src/get-spawn-opts.spec.js +0 -30
- package/src/logger.spec.js +0 -318
package/src/concurrently.spec.js
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
const EventEmitter = require('events');
|
|
2
|
-
|
|
3
|
-
const createFakeCommand = require('./flow-control/fixtures/fake-command');
|
|
4
|
-
const FakeHandler = require('./flow-control/fixtures/fake-handler');
|
|
5
|
-
const concurrently = require('./concurrently');
|
|
6
|
-
|
|
7
|
-
let spawn, kill, controllers, processes = [];
|
|
8
|
-
const create = (commands, options = {}) => concurrently(
|
|
9
|
-
commands,
|
|
10
|
-
Object.assign(options, { controllers, spawn, kill })
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
processes = [];
|
|
15
|
-
spawn = jest.fn(() => {
|
|
16
|
-
const process = new EventEmitter();
|
|
17
|
-
processes.push(process);
|
|
18
|
-
process.pid = processes.length;
|
|
19
|
-
return process;
|
|
20
|
-
});
|
|
21
|
-
kill = jest.fn();
|
|
22
|
-
controllers = [new FakeHandler(), new FakeHandler()];
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('fails if commands is not an array', () => {
|
|
26
|
-
const bomb = () => create('foo');
|
|
27
|
-
expect(bomb).toThrowError();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('fails if no commands were provided', () => {
|
|
31
|
-
const bomb = () => create([]);
|
|
32
|
-
expect(bomb).toThrowError();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('spawns all commands', () => {
|
|
36
|
-
create(['echo', 'kill']);
|
|
37
|
-
expect(spawn).toHaveBeenCalledTimes(2);
|
|
38
|
-
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({}));
|
|
39
|
-
expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({}));
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('spawns commands up to configured limit at once', () => {
|
|
43
|
-
create(['foo', 'bar', 'baz', 'qux'], { maxProcesses: 2 });
|
|
44
|
-
expect(spawn).toHaveBeenCalledTimes(2);
|
|
45
|
-
expect(spawn).toHaveBeenCalledWith('foo', expect.objectContaining({}));
|
|
46
|
-
expect(spawn).toHaveBeenCalledWith('bar', expect.objectContaining({}));
|
|
47
|
-
|
|
48
|
-
// Test out of order completion picking up new processes in-order
|
|
49
|
-
processes[1].emit('close', 1, null);
|
|
50
|
-
expect(spawn).toHaveBeenCalledTimes(3);
|
|
51
|
-
expect(spawn).toHaveBeenCalledWith('baz', expect.objectContaining({}));
|
|
52
|
-
|
|
53
|
-
processes[0].emit('close', null, 'SIGINT');
|
|
54
|
-
expect(spawn).toHaveBeenCalledTimes(4);
|
|
55
|
-
expect(spawn).toHaveBeenCalledWith('qux', expect.objectContaining({}));
|
|
56
|
-
|
|
57
|
-
// Shouldn't attempt to spawn anything else.
|
|
58
|
-
processes[2].emit('close', 1, null);
|
|
59
|
-
expect(spawn).toHaveBeenCalledTimes(4);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('runs controllers with the commands', () => {
|
|
63
|
-
create(['echo', '"echo wrapped"']);
|
|
64
|
-
|
|
65
|
-
controllers.forEach(controller => {
|
|
66
|
-
expect(controller.handle).toHaveBeenCalledWith([
|
|
67
|
-
expect.objectContaining({ command: 'echo', index: 0 }),
|
|
68
|
-
expect.objectContaining({ command: 'echo wrapped', index: 1 }),
|
|
69
|
-
]);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('runs commands with a name or prefix color', () => {
|
|
74
|
-
create([
|
|
75
|
-
{ command: 'echo', prefixColor: 'red', name: 'foo' },
|
|
76
|
-
'kill'
|
|
77
|
-
]);
|
|
78
|
-
|
|
79
|
-
controllers.forEach(controller => {
|
|
80
|
-
expect(controller.handle).toHaveBeenCalledWith([
|
|
81
|
-
expect.objectContaining({ command: 'echo', index: 0, name: 'foo', prefixColor: 'red' }),
|
|
82
|
-
expect.objectContaining({ command: 'kill', index: 1, name: '', prefixColor: '' }),
|
|
83
|
-
]);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('runs commands with a list of colors', () => {
|
|
88
|
-
create(['echo', 'kill'], {
|
|
89
|
-
prefixColors: ['red']
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
controllers.forEach(controller => {
|
|
93
|
-
expect(controller.handle).toHaveBeenCalledWith([
|
|
94
|
-
expect.objectContaining({ command: 'echo', prefixColor: 'red' }),
|
|
95
|
-
expect.objectContaining({ command: 'kill', prefixColor: 'red' }),
|
|
96
|
-
]);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('passes commands wrapped from a controller to the next one', () => {
|
|
101
|
-
const fakeCommand = createFakeCommand('banana', 'banana');
|
|
102
|
-
controllers[0].handle.mockReturnValue({ commands: [fakeCommand] });
|
|
103
|
-
|
|
104
|
-
create(['echo']);
|
|
105
|
-
|
|
106
|
-
expect(controllers[0].handle).toHaveBeenCalledWith([
|
|
107
|
-
expect.objectContaining({ command: 'echo', index: 0 })
|
|
108
|
-
]);
|
|
109
|
-
|
|
110
|
-
expect(controllers[1].handle).toHaveBeenCalledWith([fakeCommand]);
|
|
111
|
-
|
|
112
|
-
expect(fakeCommand.start).toHaveBeenCalledTimes(1);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('merges extra env vars into each command', () => {
|
|
116
|
-
create([
|
|
117
|
-
{ command: 'echo', env: { foo: 'bar' } },
|
|
118
|
-
{ command: 'echo', env: { foo: 'baz' } },
|
|
119
|
-
'kill'
|
|
120
|
-
]);
|
|
121
|
-
|
|
122
|
-
expect(spawn).toHaveBeenCalledTimes(3);
|
|
123
|
-
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
124
|
-
env: expect.objectContaining({ foo: 'bar' })
|
|
125
|
-
}));
|
|
126
|
-
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
127
|
-
env: expect.objectContaining({ foo: 'baz' })
|
|
128
|
-
}));
|
|
129
|
-
expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
|
|
130
|
-
env: expect.not.objectContaining({ foo: expect.anything() })
|
|
131
|
-
}));
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('uses cwd from options for each command', () => {
|
|
135
|
-
create(
|
|
136
|
-
[
|
|
137
|
-
{ command: 'echo', env: { foo: 'bar' } },
|
|
138
|
-
{ command: 'echo', env: { foo: 'baz' } },
|
|
139
|
-
'kill'
|
|
140
|
-
],
|
|
141
|
-
{
|
|
142
|
-
cwd: 'foobar',
|
|
143
|
-
}
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
expect(spawn).toHaveBeenCalledTimes(3);
|
|
147
|
-
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
148
|
-
env: expect.objectContaining({ foo: 'bar' }),
|
|
149
|
-
cwd: 'foobar',
|
|
150
|
-
}));
|
|
151
|
-
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
152
|
-
env: expect.objectContaining({ foo: 'baz' }),
|
|
153
|
-
cwd: 'foobar',
|
|
154
|
-
}));
|
|
155
|
-
expect(spawn).toHaveBeenCalledWith('kill', expect.objectContaining({
|
|
156
|
-
env: expect.not.objectContaining({ foo: expect.anything() }),
|
|
157
|
-
cwd: 'foobar',
|
|
158
|
-
}));
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('uses overridden cwd option for each command if specified', () => {
|
|
162
|
-
create(
|
|
163
|
-
[
|
|
164
|
-
{ command: 'echo', env: { foo: 'bar' }, cwd: 'baz' },
|
|
165
|
-
{ command: 'echo', env: { foo: 'baz' } },
|
|
166
|
-
],
|
|
167
|
-
{
|
|
168
|
-
cwd: 'foobar',
|
|
169
|
-
}
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
expect(spawn).toHaveBeenCalledTimes(2);
|
|
173
|
-
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
174
|
-
env: expect.objectContaining({ foo: 'bar' }),
|
|
175
|
-
cwd: 'baz',
|
|
176
|
-
}));
|
|
177
|
-
expect(spawn).toHaveBeenCalledWith('echo', expect.objectContaining({
|
|
178
|
-
env: expect.objectContaining({ foo: 'baz' }),
|
|
179
|
-
cwd: 'foobar',
|
|
180
|
-
}));
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('runs onFinish hook after all commands run', async () => {
|
|
184
|
-
const promise = create(['foo', 'bar'], { maxProcesses: 1 });
|
|
185
|
-
expect(spawn).toHaveBeenCalledTimes(1);
|
|
186
|
-
expect(controllers[0].onFinish).not.toHaveBeenCalled();
|
|
187
|
-
expect(controllers[1].onFinish).not.toHaveBeenCalled();
|
|
188
|
-
|
|
189
|
-
processes[0].emit('close', 0, null);
|
|
190
|
-
expect(spawn).toHaveBeenCalledTimes(2);
|
|
191
|
-
expect(controllers[0].onFinish).not.toHaveBeenCalled();
|
|
192
|
-
expect(controllers[1].onFinish).not.toHaveBeenCalled();
|
|
193
|
-
|
|
194
|
-
processes[1].emit('close', 0, null);
|
|
195
|
-
await promise;
|
|
196
|
-
|
|
197
|
-
expect(controllers[0].onFinish).toHaveBeenCalled();
|
|
198
|
-
expect(controllers[1].onFinish).toHaveBeenCalled();
|
|
199
|
-
});
|
package/src/defaults.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is meant to be a shared place for default configs.
|
|
3
|
-
* It's read by the flow controllers, the executable, etc.
|
|
4
|
-
*
|
|
5
|
-
* Refer to tests for the meaning of the different possible values.
|
|
6
|
-
*/
|
|
7
|
-
module.exports = {
|
|
8
|
-
defaultInputTarget: 0,
|
|
9
|
-
// Whether process.stdin should be forwarded to child processes
|
|
10
|
-
handleInput: false,
|
|
11
|
-
// How many processes to run at once
|
|
12
|
-
maxProcesses: 0,
|
|
13
|
-
// Indices and names of commands whose output to be not logged
|
|
14
|
-
hide: '',
|
|
15
|
-
nameSeparator: ',',
|
|
16
|
-
// Which prefix style to use when logging processes output.
|
|
17
|
-
prefix: '',
|
|
18
|
-
// Refer to https://www.npmjs.com/package/chalk
|
|
19
|
-
prefixColors: 'reset',
|
|
20
|
-
// How many bytes we'll show on the command prefix
|
|
21
|
-
prefixLength: 10,
|
|
22
|
-
raw: false,
|
|
23
|
-
// Number of attempts of restarting a process, if it exits with non-0 code
|
|
24
|
-
restartTries: 0,
|
|
25
|
-
// How many milliseconds concurrently should wait before restarting a process.
|
|
26
|
-
restartDelay: 0,
|
|
27
|
-
// Condition of success for concurrently itself.
|
|
28
|
-
success: 'all',
|
|
29
|
-
// Refer to https://date-fns.org/v2.0.1/docs/format
|
|
30
|
-
timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS',
|
|
31
|
-
// Current working dir passed as option to spawn command. Default: process.cwd()
|
|
32
|
-
cwd: undefined,
|
|
33
|
-
// Whether to show timing information for processes in console output
|
|
34
|
-
timings: false,
|
|
35
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
module.exports = class BaseHandler {
|
|
2
|
-
constructor(options = {}) {
|
|
3
|
-
const { logger } = options;
|
|
4
|
-
|
|
5
|
-
this.logger = logger;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
handle(commands) {
|
|
9
|
-
return {
|
|
10
|
-
commands,
|
|
11
|
-
// an optional callback to call when all commands have finished
|
|
12
|
-
// (either successful or not)
|
|
13
|
-
onFinish: null,
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
const stream = require('stream');
|
|
2
|
-
const { createMockInstance } = require('jest-create-mock-instance');
|
|
3
|
-
|
|
4
|
-
const Logger = require('../logger');
|
|
5
|
-
const createFakeCommand = require('./fixtures/fake-command');
|
|
6
|
-
const BaseHandler = require('./base-handler');
|
|
7
|
-
|
|
8
|
-
let commands, controller, inputStream, logger;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
commands = [
|
|
12
|
-
createFakeCommand('foo', 'echo foo', 0),
|
|
13
|
-
createFakeCommand('bar', 'echo bar', 1),
|
|
14
|
-
];
|
|
15
|
-
inputStream = new stream.PassThrough();
|
|
16
|
-
logger = createMockInstance(Logger);
|
|
17
|
-
controller = new BaseHandler({ logger });
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('returns same commands and null onFinish callback by default', () => {
|
|
21
|
-
expect(controller.handle(commands)).toMatchObject({ commands, onFinish: null });
|
|
22
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
const Rx = require('rxjs');
|
|
2
|
-
const { map } = require('rxjs/operators');
|
|
3
|
-
|
|
4
|
-
const defaults = require('../defaults');
|
|
5
|
-
const BaseHandler = require('./base-handler');
|
|
6
|
-
|
|
7
|
-
module.exports = class InputHandler extends BaseHandler {
|
|
8
|
-
constructor({ defaultInputTarget, inputStream, pauseInputStreamOnFinish, logger }) {
|
|
9
|
-
super({ logger });
|
|
10
|
-
|
|
11
|
-
this.defaultInputTarget = defaultInputTarget || defaults.defaultInputTarget;
|
|
12
|
-
this.inputStream = inputStream;
|
|
13
|
-
this.pauseInputStreamOnFinish = pauseInputStreamOnFinish !== false;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
handle(commands) {
|
|
17
|
-
if (!this.inputStream) {
|
|
18
|
-
return { commands };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
Rx.fromEvent(this.inputStream, 'data')
|
|
22
|
-
.pipe(map(data => data.toString()))
|
|
23
|
-
.subscribe(data => {
|
|
24
|
-
let [targetId, input] = data.split(/:(.+)/);
|
|
25
|
-
targetId = input ? targetId : this.defaultInputTarget;
|
|
26
|
-
input = input || data;
|
|
27
|
-
|
|
28
|
-
const command = commands.find(command => (
|
|
29
|
-
command.name === targetId ||
|
|
30
|
-
command.index.toString() === targetId.toString()
|
|
31
|
-
));
|
|
32
|
-
|
|
33
|
-
if (command && command.stdin) {
|
|
34
|
-
command.stdin.write(input);
|
|
35
|
-
} else {
|
|
36
|
-
this.logger.logGlobalEvent(`Unable to find command ${targetId}, or it has no stdin open\n`);
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
commands,
|
|
42
|
-
onFinish: () => {
|
|
43
|
-
if (this.pauseInputStreamOnFinish) {
|
|
44
|
-
// https://github.com/kimmobrunfeldt/concurrently/issues/252
|
|
45
|
-
this.inputStream.pause();
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
};
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
const stream = require('stream');
|
|
2
|
-
const { createMockInstance } = require('jest-create-mock-instance');
|
|
3
|
-
|
|
4
|
-
const Logger = require('../logger');
|
|
5
|
-
const createFakeCommand = require('./fixtures/fake-command');
|
|
6
|
-
const InputHandler = require('./input-handler');
|
|
7
|
-
|
|
8
|
-
let commands, controller, inputStream, logger;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
commands = [
|
|
12
|
-
createFakeCommand('foo', 'echo foo', 0),
|
|
13
|
-
createFakeCommand('bar', 'echo bar', 1),
|
|
14
|
-
];
|
|
15
|
-
inputStream = new stream.PassThrough();
|
|
16
|
-
logger = createMockInstance(Logger);
|
|
17
|
-
controller = new InputHandler({
|
|
18
|
-
defaultInputTarget: 0,
|
|
19
|
-
inputStream,
|
|
20
|
-
logger
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('returns same commands', () => {
|
|
25
|
-
expect(controller.handle(commands)).toMatchObject({ commands });
|
|
26
|
-
|
|
27
|
-
controller = new InputHandler({ logger });
|
|
28
|
-
expect(controller.handle(commands)).toMatchObject({ commands });
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('forwards input stream to default target ID', () => {
|
|
32
|
-
controller.handle(commands);
|
|
33
|
-
|
|
34
|
-
inputStream.write('something');
|
|
35
|
-
|
|
36
|
-
expect(commands[0].stdin.write).toHaveBeenCalledTimes(1);
|
|
37
|
-
expect(commands[0].stdin.write).toHaveBeenCalledWith('something');
|
|
38
|
-
expect(commands[1].stdin.write).not.toHaveBeenCalled();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('forwards input stream to target index specified in input', () => {
|
|
42
|
-
controller.handle(commands);
|
|
43
|
-
|
|
44
|
-
inputStream.write('1:something');
|
|
45
|
-
|
|
46
|
-
expect(commands[0].stdin.write).not.toHaveBeenCalled();
|
|
47
|
-
expect(commands[1].stdin.write).toHaveBeenCalledTimes(1);
|
|
48
|
-
expect(commands[1].stdin.write).toHaveBeenCalledWith('something');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('forwards input stream to target index specified in input when input contains colon', () => {
|
|
52
|
-
controller.handle(commands);
|
|
53
|
-
|
|
54
|
-
inputStream.emit('data', Buffer.from('1::something'));
|
|
55
|
-
inputStream.emit('data', Buffer.from('1:some:thing'));
|
|
56
|
-
|
|
57
|
-
expect(commands[0].stdin.write).not.toHaveBeenCalled();
|
|
58
|
-
expect(commands[1].stdin.write).toHaveBeenCalledTimes(2);
|
|
59
|
-
expect(commands[1].stdin.write).toHaveBeenCalledWith(':something');
|
|
60
|
-
expect(commands[1].stdin.write).toHaveBeenCalledWith('some:thing');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('forwards input stream to target name specified in input', () => {
|
|
64
|
-
controller.handle(commands);
|
|
65
|
-
|
|
66
|
-
inputStream.write('bar:something');
|
|
67
|
-
|
|
68
|
-
expect(commands[0].stdin.write).not.toHaveBeenCalled();
|
|
69
|
-
expect(commands[1].stdin.write).toHaveBeenCalledTimes(1);
|
|
70
|
-
expect(commands[1].stdin.write).toHaveBeenCalledWith('something');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('logs error if command has no stdin open', () => {
|
|
74
|
-
commands[0].stdin = null;
|
|
75
|
-
controller.handle(commands);
|
|
76
|
-
|
|
77
|
-
inputStream.write('something');
|
|
78
|
-
|
|
79
|
-
expect(commands[1].stdin.write).not.toHaveBeenCalled();
|
|
80
|
-
expect(logger.logGlobalEvent).toHaveBeenCalledWith('Unable to find command 0, or it has no stdin open\n');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('logs error if command is not found', () => {
|
|
84
|
-
controller.handle(commands);
|
|
85
|
-
|
|
86
|
-
inputStream.write('foobar:something');
|
|
87
|
-
|
|
88
|
-
expect(commands[0].stdin.write).not.toHaveBeenCalled();
|
|
89
|
-
expect(commands[1].stdin.write).not.toHaveBeenCalled();
|
|
90
|
-
expect(logger.logGlobalEvent).toHaveBeenCalledWith('Unable to find command foobar, or it has no stdin open\n');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('pauses input stream when finished', () => {
|
|
94
|
-
expect(inputStream.readableFlowing).toBeNull();
|
|
95
|
-
|
|
96
|
-
const { onFinish } = controller.handle(commands);
|
|
97
|
-
expect(inputStream.readableFlowing).toBe(true);
|
|
98
|
-
|
|
99
|
-
onFinish();
|
|
100
|
-
expect(inputStream.readableFlowing).toBe(false);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('does not pause input stream when pauseInputStreamOnFinish is set to false', () => {
|
|
104
|
-
controller = new InputHandler({ inputStream, pauseInputStreamOnFinish: false });
|
|
105
|
-
|
|
106
|
-
expect(inputStream.readableFlowing).toBeNull();
|
|
107
|
-
|
|
108
|
-
const { onFinish } = controller.handle(commands);
|
|
109
|
-
expect(inputStream.readableFlowing).toBe(true);
|
|
110
|
-
|
|
111
|
-
onFinish();
|
|
112
|
-
expect(inputStream.readableFlowing).toBe(true);
|
|
113
|
-
});
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
const EventEmitter = require('events');
|
|
2
|
-
|
|
3
|
-
const createFakeCommand = require('./fixtures/fake-command');
|
|
4
|
-
const KillOnSignal = require('./kill-on-signal');
|
|
5
|
-
|
|
6
|
-
let commands, controller, process;
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
process = new EventEmitter();
|
|
9
|
-
commands = [
|
|
10
|
-
createFakeCommand(),
|
|
11
|
-
createFakeCommand(),
|
|
12
|
-
];
|
|
13
|
-
controller = new KillOnSignal({ process });
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('returns commands that keep non-close streams from original commands', () => {
|
|
17
|
-
const { commands: newCommands } = controller.handle(commands);
|
|
18
|
-
newCommands.forEach((newCommand, i) => {
|
|
19
|
-
expect(newCommand.close).not.toBe(commands[i].close);
|
|
20
|
-
expect(newCommand.error).toBe(commands[i].error);
|
|
21
|
-
expect(newCommand.stdout).toBe(commands[i].stdout);
|
|
22
|
-
expect(newCommand.stderr).toBe(commands[i].stderr);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('returns commands that map SIGINT to exit code 0', () => {
|
|
27
|
-
const { commands: newCommands } = controller.handle(commands);
|
|
28
|
-
expect(newCommands).not.toBe(commands);
|
|
29
|
-
expect(newCommands).toHaveLength(commands.length);
|
|
30
|
-
|
|
31
|
-
const callback = jest.fn();
|
|
32
|
-
newCommands[0].close.subscribe(callback);
|
|
33
|
-
process.emit('SIGINT');
|
|
34
|
-
|
|
35
|
-
// A fake command's .kill() call won't trigger a close event automatically...
|
|
36
|
-
commands[0].close.next({ exitCode: 1 });
|
|
37
|
-
|
|
38
|
-
expect(callback).not.toHaveBeenCalledWith({ exitCode: 'SIGINT' });
|
|
39
|
-
expect(callback).toHaveBeenCalledWith({ exitCode: 0 });
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('returns commands that keep non-SIGINT exit codes', () => {
|
|
43
|
-
const { commands: newCommands } = controller.handle(commands);
|
|
44
|
-
expect(newCommands).not.toBe(commands);
|
|
45
|
-
expect(newCommands).toHaveLength(commands.length);
|
|
46
|
-
|
|
47
|
-
const callback = jest.fn();
|
|
48
|
-
newCommands[0].close.subscribe(callback);
|
|
49
|
-
commands[0].close.next({ exitCode: 1 });
|
|
50
|
-
|
|
51
|
-
expect(callback).toHaveBeenCalledWith({ exitCode: 1 });
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('kills all commands on SIGINT', () => {
|
|
55
|
-
controller.handle(commands);
|
|
56
|
-
process.emit('SIGINT');
|
|
57
|
-
|
|
58
|
-
expect(process.listenerCount('SIGINT')).toBe(1);
|
|
59
|
-
expect(commands[0].kill).toHaveBeenCalledWith('SIGINT');
|
|
60
|
-
expect(commands[1].kill).toHaveBeenCalledWith('SIGINT');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('kills all commands on SIGTERM', () => {
|
|
64
|
-
controller.handle(commands);
|
|
65
|
-
process.emit('SIGTERM');
|
|
66
|
-
|
|
67
|
-
expect(process.listenerCount('SIGTERM')).toBe(1);
|
|
68
|
-
expect(commands[0].kill).toHaveBeenCalledWith('SIGTERM');
|
|
69
|
-
expect(commands[1].kill).toHaveBeenCalledWith('SIGTERM');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('kills all commands on SIGHUP', () => {
|
|
73
|
-
controller.handle(commands);
|
|
74
|
-
process.emit('SIGHUP');
|
|
75
|
-
|
|
76
|
-
expect(process.listenerCount('SIGHUP')).toBe(1);
|
|
77
|
-
expect(commands[0].kill).toHaveBeenCalledWith('SIGHUP');
|
|
78
|
-
expect(commands[1].kill).toHaveBeenCalledWith('SIGHUP');
|
|
79
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
const _ = require('lodash');
|
|
2
|
-
const { filter, map } = require('rxjs/operators');
|
|
3
|
-
|
|
4
|
-
const BaseHandler = require('./base-handler');
|
|
5
|
-
|
|
6
|
-
module.exports = class KillOthers extends BaseHandler {
|
|
7
|
-
constructor({ logger, conditions }) {
|
|
8
|
-
super({ logger });
|
|
9
|
-
|
|
10
|
-
this.conditions = _.castArray(conditions);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
handle(commands) {
|
|
14
|
-
const conditions = this.conditions.filter(condition => (
|
|
15
|
-
condition === 'failure' ||
|
|
16
|
-
condition === 'success'
|
|
17
|
-
));
|
|
18
|
-
|
|
19
|
-
if (!conditions.length) {
|
|
20
|
-
return { commands };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const closeStates = commands.map(command => command.close.pipe(
|
|
24
|
-
map(({ exitCode }) => exitCode === 0 ? 'success' : 'failure'),
|
|
25
|
-
filter(state => conditions.includes(state))
|
|
26
|
-
));
|
|
27
|
-
|
|
28
|
-
closeStates.forEach(closeState => closeState.subscribe(() => {
|
|
29
|
-
const killableCommands = commands.filter(command => command.killable);
|
|
30
|
-
if (killableCommands.length) {
|
|
31
|
-
this.logger.logGlobalEvent('Sending SIGTERM to other processes..');
|
|
32
|
-
killableCommands.forEach(command => command.kill());
|
|
33
|
-
}
|
|
34
|
-
}));
|
|
35
|
-
|
|
36
|
-
return { commands };
|
|
37
|
-
}
|
|
38
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
const { createMockInstance } = require('jest-create-mock-instance');
|
|
2
|
-
|
|
3
|
-
const Logger = require('../logger');
|
|
4
|
-
const createFakeCommand = require('./fixtures/fake-command');
|
|
5
|
-
const KillOthers = require('./kill-others');
|
|
6
|
-
|
|
7
|
-
let commands, logger;
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
commands = [
|
|
10
|
-
createFakeCommand(),
|
|
11
|
-
createFakeCommand()
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
logger = createMockInstance(Logger);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const createWithConditions = conditions => new KillOthers({
|
|
18
|
-
logger,
|
|
19
|
-
conditions
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('returns same commands', () => {
|
|
23
|
-
expect(createWithConditions(['foo']).handle(commands)).toMatchObject({ commands });
|
|
24
|
-
expect(createWithConditions(['failure']).handle(commands)).toMatchObject({ commands });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('does not kill others if condition does not match', () => {
|
|
28
|
-
createWithConditions(['failure']).handle(commands);
|
|
29
|
-
commands[1].killable = true;
|
|
30
|
-
commands[0].close.next({ exitCode: 0 });
|
|
31
|
-
|
|
32
|
-
expect(logger.logGlobalEvent).not.toHaveBeenCalled();
|
|
33
|
-
expect(commands[0].kill).not.toHaveBeenCalled();
|
|
34
|
-
expect(commands[1].kill).not.toHaveBeenCalled();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('kills other killable processes on success', () => {
|
|
38
|
-
createWithConditions(['success']).handle(commands);
|
|
39
|
-
commands[1].killable = true;
|
|
40
|
-
commands[0].close.next({ exitCode: 0 });
|
|
41
|
-
|
|
42
|
-
expect(logger.logGlobalEvent).toHaveBeenCalledTimes(1);
|
|
43
|
-
expect(logger.logGlobalEvent).toHaveBeenCalledWith('Sending SIGTERM to other processes..');
|
|
44
|
-
expect(commands[0].kill).not.toHaveBeenCalled();
|
|
45
|
-
expect(commands[1].kill).toHaveBeenCalled();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('kills other killable processes on failure', () => {
|
|
49
|
-
createWithConditions(['failure']).handle(commands);
|
|
50
|
-
commands[1].killable = true;
|
|
51
|
-
commands[0].close.next({ exitCode: 1 });
|
|
52
|
-
|
|
53
|
-
expect(logger.logGlobalEvent).toHaveBeenCalledTimes(1);
|
|
54
|
-
expect(logger.logGlobalEvent).toHaveBeenCalledWith('Sending SIGTERM to other processes..');
|
|
55
|
-
expect(commands[0].kill).not.toHaveBeenCalled();
|
|
56
|
-
expect(commands[1].kill).toHaveBeenCalled();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('does not try to kill processes already dead', () => {
|
|
60
|
-
createWithConditions(['failure']).handle(commands);
|
|
61
|
-
commands[0].close.next({ exitCode: 1 });
|
|
62
|
-
|
|
63
|
-
expect(logger.logGlobalEvent).not.toHaveBeenCalled();
|
|
64
|
-
expect(commands[0].kill).not.toHaveBeenCalled();
|
|
65
|
-
expect(commands[1].kill).not.toHaveBeenCalled();
|
|
66
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
const { of } = require('rxjs');
|
|
2
|
-
|
|
3
|
-
const BaseHandler = require('./base-handler');
|
|
4
|
-
|
|
5
|
-
module.exports = class LogExit extends BaseHandler {
|
|
6
|
-
handle(commands) {
|
|
7
|
-
commands.forEach(command => command.error.subscribe(event => {
|
|
8
|
-
this.logger.logCommandEvent(
|
|
9
|
-
`Error occurred when executing command: ${command.command}`,
|
|
10
|
-
command
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
this.logger.logCommandEvent(event.stack || event, command);
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
return { commands };
|
|
17
|
-
}
|
|
18
|
-
};
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
const { createMockInstance } = require('jest-create-mock-instance');
|
|
2
|
-
const Logger = require('../logger');
|
|
3
|
-
const LogError = require('./log-error');
|
|
4
|
-
const createFakeCommand = require('./fixtures/fake-command');
|
|
5
|
-
|
|
6
|
-
let controller, logger, commands;
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
commands = [
|
|
9
|
-
createFakeCommand(),
|
|
10
|
-
createFakeCommand(),
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
logger = createMockInstance(Logger);
|
|
14
|
-
controller = new LogError({ logger });
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('returns same commands', () => {
|
|
18
|
-
expect(controller.handle(commands)).toMatchObject({ commands });
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('logs the error event of each command', () => {
|
|
22
|
-
controller.handle(commands);
|
|
23
|
-
commands[0].error.next('error from command 0');
|
|
24
|
-
|
|
25
|
-
const error = new Error('some error message');
|
|
26
|
-
commands[1].error.next(error);
|
|
27
|
-
|
|
28
|
-
expect(logger.logCommandEvent).toHaveBeenCalledTimes(4);
|
|
29
|
-
expect(logger.logCommandEvent).toHaveBeenCalledWith(
|
|
30
|
-
`Error occurred when executing command: ${commands[0].command}`,
|
|
31
|
-
commands[0]
|
|
32
|
-
);
|
|
33
|
-
expect(logger.logCommandEvent).toHaveBeenCalledWith('error from command 0', commands[0]);
|
|
34
|
-
|
|
35
|
-
expect(logger.logCommandEvent).toHaveBeenCalledWith(
|
|
36
|
-
`Error occurred when executing command: ${commands[1].command}`,
|
|
37
|
-
commands[1]
|
|
38
|
-
);
|
|
39
|
-
expect(logger.logCommandEvent).toHaveBeenCalledWith(error.stack, commands[1]);
|
|
40
|
-
});
|