concurrently 6.2.0 → 6.2.1
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 +9 -5
- package/bin/concurrently.js +1 -1
- package/bin/concurrently.spec.js +3 -3
- package/bin/epilogue.txt +1 -1
- package/index.js +3 -2
- package/package.json +2 -2
- package/src/concurrently.js +15 -4
- package/src/concurrently.spec.js +21 -2
- package/src/flow-control/base-handler.js +16 -0
- package/src/flow-control/input-handler.js +16 -5
- package/src/flow-control/input-handler.spec.js +31 -9
- package/src/flow-control/kill-on-signal.js +17 -12
- package/src/flow-control/kill-on-signal.spec.js +3 -3
- package/src/flow-control/kill-others.js +7 -4
- package/src/flow-control/kill-others.spec.js +2 -2
- package/src/flow-control/log-error.js +3 -5
- package/src/flow-control/log-error.spec.js +1 -1
- package/src/flow-control/log-exit.js +3 -5
- package/src/flow-control/log-exit.spec.js +1 -1
- package/src/flow-control/log-output.js +3 -5
- package/src/flow-control/log-output.spec.js +1 -1
- package/src/flow-control/restart-process.js +18 -14
- package/src/flow-control/restart-process.spec.js +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Concurrently
|
|
2
2
|
|
|
3
|
-
[](https://github.com/open-cli-tools/concurrently/actions?workflow=Tests) *master branch status*
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/concurrently)
|
|
6
6
|
|
|
@@ -20,7 +20,7 @@ Like `npm run watch-js & npm run watch-less` but better.
|
|
|
20
20
|
|
|
21
21
|
## Why
|
|
22
22
|
|
|
23
|
-
I like [task automation with npm](
|
|
23
|
+
I like [task automation with npm](https://github.com/substack/blog/blob/master/npm_run.markdown)
|
|
24
24
|
but the usual way to run multiple commands concurrently is
|
|
25
25
|
`npm run watch-js & npm run watch-css`. That's fine but it's hard to keep
|
|
26
26
|
on track of different outputs. Also if one process fails, others still keep running
|
|
@@ -221,22 +221,26 @@ Examples:
|
|
|
221
221
|
|
|
222
222
|
$ concurrently npm:watch-*
|
|
223
223
|
|
|
224
|
-
For more details, visit https://github.com/
|
|
224
|
+
For more details, visit https://github.com/open-cli-tools/concurrently
|
|
225
225
|
```
|
|
226
226
|
|
|
227
227
|
## Programmatic Usage
|
|
228
228
|
concurrently can be used programmatically by using the API documented below:
|
|
229
229
|
|
|
230
230
|
### `concurrently(commands[, options])`
|
|
231
|
+
|
|
231
232
|
- `commands`: an array of either strings (containing the commands to run) or objects
|
|
232
233
|
with the shape `{ command, name, prefixColor, env, cwd }`.
|
|
234
|
+
|
|
233
235
|
- `options` (optional): an object containing any of the below:
|
|
234
236
|
- `cwd`: the working directory to be used by all commands. Can be overriden per command.
|
|
235
237
|
Default: `process.cwd()`.
|
|
236
238
|
- `defaultInputTarget`: the default input target when reading from `inputStream`.
|
|
237
239
|
Default: `0`.
|
|
240
|
+
- `handleInput`: when `true`, reads input from `process.stdin`.
|
|
238
241
|
- `inputStream`: a [`Readable` stream](https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_readable_streams)
|
|
239
|
-
to read the input from
|
|
242
|
+
to read the input from. Should only be used in the rare instance you would like to stream anything other than `process.stdin`. Overrides `handleInput`.
|
|
243
|
+
- `pauseInputStreamOnFinish`: by default, pauses the input stream (`process.stdin` when `handleInput` is enabled, or `inputStream` if provided) when all of the processes have finished. If you need to read from the input stream after `concurrently` has finished, set this to `false`. ([#252](https://github.com/kimmobrunfeldt/concurrently/issues/252)).
|
|
240
244
|
- `killOthers`: an array of exitting conditions that will cause a process to kill others.
|
|
241
245
|
Can contain any of `success` or `failure`.
|
|
242
246
|
- `maxProcesses`: how many processes should run at once.
|
|
@@ -259,7 +263,7 @@ concurrently can be used programmatically by using the API documented below:
|
|
|
259
263
|
> Returns: a `Promise` that resolves if the run was successful (according to `successCondition` option),
|
|
260
264
|
> or rejects, containing an array of objects with information for each command that has been run, in the order
|
|
261
265
|
> that the commands terminated. The objects have the shape `{ command, index, exitCode, killed }`, where `command` is the object
|
|
262
|
-
> passed in the `commands` array, `index` its index there and `killed` indicates if the process was killed as a result of
|
|
266
|
+
> passed in the `commands` array, `index` its index there and `killed` indicates if the process was killed as a result of
|
|
263
267
|
> `killOthers`. Default values (empty strings or objects) are returned for the fields that were not specified.
|
|
264
268
|
|
|
265
269
|
Example:
|
package/bin/concurrently.js
CHANGED
|
@@ -157,7 +157,7 @@ concurrently(args._.map((command, index) => {
|
|
|
157
157
|
name: names[index]
|
|
158
158
|
};
|
|
159
159
|
}), {
|
|
160
|
-
|
|
160
|
+
handleInput: args.handleInput,
|
|
161
161
|
defaultInputTarget: args.defaultInputTarget,
|
|
162
162
|
killOthers: args.killOthers
|
|
163
163
|
? ['success', 'failure']
|
package/bin/concurrently.spec.js
CHANGED
|
@@ -193,7 +193,7 @@ describe('--names', () => {
|
|
|
193
193
|
});
|
|
194
194
|
|
|
195
195
|
describe('--prefix', () => {
|
|
196
|
-
it('is
|
|
196
|
+
it('is aliased to -p', done => {
|
|
197
197
|
const child = run('-p command "echo foo" "echo bar"');
|
|
198
198
|
child.log.pipe(buffer(child.close)).subscribe(lines => {
|
|
199
199
|
expect(lines).toContainEqual(expect.stringContaining('[echo foo] foo'));
|
|
@@ -213,7 +213,7 @@ describe('--prefix', () => {
|
|
|
213
213
|
});
|
|
214
214
|
|
|
215
215
|
describe('--prefix-length', () => {
|
|
216
|
-
it('is
|
|
216
|
+
it('is aliased to -l', done => {
|
|
217
217
|
const child = run('-p command -l 5 "echo foo" "echo bar"');
|
|
218
218
|
child.log.pipe(buffer(child.close)).subscribe(lines => {
|
|
219
219
|
expect(lines).toContainEqual(expect.stringContaining('[ec..o] foo'));
|
|
@@ -247,7 +247,7 @@ describe('--restart-tries', () => {
|
|
|
247
247
|
});
|
|
248
248
|
|
|
249
249
|
describe('--kill-others', () => {
|
|
250
|
-
it('is
|
|
250
|
+
it('is aliased to -k', done => {
|
|
251
251
|
const child = run('-k "sleep 10" "exit 0"');
|
|
252
252
|
child.log.pipe(buffer(child.close)).subscribe(lines => {
|
|
253
253
|
expect(lines).toContainEqual(expect.stringContaining('[1] exit 0 exited with code 0'));
|
package/bin/epilogue.txt
CHANGED
package/index.js
CHANGED
|
@@ -9,7 +9,7 @@ const RestartProcess = require('./src/flow-control/restart-process');
|
|
|
9
9
|
const concurrently = require('./src/concurrently');
|
|
10
10
|
const Logger = require('./src/logger');
|
|
11
11
|
|
|
12
|
-
module.exports = (commands, options = {}) => {
|
|
12
|
+
module.exports = exports = (commands, options = {}) => {
|
|
13
13
|
const logger = new Logger({
|
|
14
14
|
outputStream: options.outputStream || process.stdout,
|
|
15
15
|
prefixFormat: options.prefix,
|
|
@@ -30,7 +30,8 @@ module.exports = (commands, options = {}) => {
|
|
|
30
30
|
new InputHandler({
|
|
31
31
|
logger,
|
|
32
32
|
defaultInputTarget: options.defaultInputTarget,
|
|
33
|
-
inputStream: options.inputStream,
|
|
33
|
+
inputStream: options.inputStream || (options.handleInput && process.stdin),
|
|
34
|
+
pauseInputStreamOnFinish: options.pauseInputStreamOnFinish,
|
|
34
35
|
}),
|
|
35
36
|
new KillOnSignal({ process }),
|
|
36
37
|
new RestartProcess({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "concurrently",
|
|
3
|
-
"version": "6.2.
|
|
3
|
+
"version": "6.2.1",
|
|
4
4
|
"description": "Run commands concurrently",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
|
-
"url": "https://github.com/
|
|
19
|
+
"url": "https://github.com/open-cli-tools/concurrently.git"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
22
22
|
"bash",
|
package/src/concurrently.js
CHANGED
|
@@ -49,10 +49,17 @@ module.exports = (commands, options) => {
|
|
|
49
49
|
))
|
|
50
50
|
.value();
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
(prevCommands, controller) =>
|
|
54
|
-
|
|
52
|
+
const handleResult = options.controllers.reduce(
|
|
53
|
+
({ commands: prevCommands, onFinishCallbacks }, controller) => {
|
|
54
|
+
const { commands, onFinish } = controller.handle(prevCommands);
|
|
55
|
+
return {
|
|
56
|
+
commands,
|
|
57
|
+
onFinishCallbacks: _.concat(onFinishCallbacks, onFinish ? [onFinish] : [])
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{ commands, onFinishCallbacks: [] }
|
|
55
61
|
);
|
|
62
|
+
commands = handleResult.commands
|
|
56
63
|
|
|
57
64
|
const commandsLeft = commands.slice();
|
|
58
65
|
const maxProcesses = Math.max(1, Number(options.maxProcesses) || commandsLeft.length);
|
|
@@ -60,7 +67,11 @@ module.exports = (commands, options) => {
|
|
|
60
67
|
maybeRunMore(commandsLeft);
|
|
61
68
|
}
|
|
62
69
|
|
|
63
|
-
return new CompletionListener({ successCondition: options.successCondition })
|
|
70
|
+
return new CompletionListener({ successCondition: options.successCondition })
|
|
71
|
+
.listen(commands)
|
|
72
|
+
.finally(() => {
|
|
73
|
+
handleResult.onFinishCallbacks.forEach((onFinish) => onFinish());
|
|
74
|
+
});
|
|
64
75
|
};
|
|
65
76
|
|
|
66
77
|
function mapToCommandInfo(command) {
|
package/src/concurrently.spec.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const EventEmitter = require('events');
|
|
2
2
|
|
|
3
3
|
const createFakeCommand = require('./flow-control/fixtures/fake-command');
|
|
4
|
+
const FakeHandler = require('./flow-control/fixtures/fake-handler');
|
|
4
5
|
const concurrently = require('./concurrently');
|
|
5
6
|
|
|
6
7
|
let spawn, kill, controllers, processes = [];
|
|
@@ -18,7 +19,7 @@ beforeEach(() => {
|
|
|
18
19
|
return process;
|
|
19
20
|
});
|
|
20
21
|
kill = jest.fn();
|
|
21
|
-
controllers = [
|
|
22
|
+
controllers = [new FakeHandler(), new FakeHandler()];
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
it('fails if commands is not an array', () => {
|
|
@@ -85,7 +86,7 @@ it('runs commands with a name or prefix color', () => {
|
|
|
85
86
|
|
|
86
87
|
it('passes commands wrapped from a controller to the next one', () => {
|
|
87
88
|
const fakeCommand = createFakeCommand('banana', 'banana');
|
|
88
|
-
controllers[0].handle.mockReturnValue([fakeCommand]);
|
|
89
|
+
controllers[0].handle.mockReturnValue({ commands: [fakeCommand] });
|
|
89
90
|
|
|
90
91
|
create(['echo']);
|
|
91
92
|
|
|
@@ -165,3 +166,21 @@ it('uses overridden cwd option for each command if specified', () => {
|
|
|
165
166
|
cwd: 'foobar',
|
|
166
167
|
}));
|
|
167
168
|
});
|
|
169
|
+
|
|
170
|
+
it('runs onFinish hook after all commands run', async () => {
|
|
171
|
+
const promise = create(['foo', 'bar'], { maxProcesses: 1 });
|
|
172
|
+
expect(spawn).toHaveBeenCalledTimes(1);
|
|
173
|
+
expect(controllers[0].onFinish).not.toHaveBeenCalled();
|
|
174
|
+
expect(controllers[1].onFinish).not.toHaveBeenCalled();
|
|
175
|
+
|
|
176
|
+
processes[0].emit('close', 0, null);
|
|
177
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
178
|
+
expect(controllers[0].onFinish).not.toHaveBeenCalled();
|
|
179
|
+
expect(controllers[1].onFinish).not.toHaveBeenCalled();
|
|
180
|
+
|
|
181
|
+
processes[1].emit('close', 0, null);
|
|
182
|
+
await promise;
|
|
183
|
+
|
|
184
|
+
expect(controllers[0].onFinish).toHaveBeenCalled();
|
|
185
|
+
expect(controllers[1].onFinish).toHaveBeenCalled();
|
|
186
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
};
|
|
@@ -2,17 +2,20 @@ const Rx = require('rxjs');
|
|
|
2
2
|
const { map } = require('rxjs/operators');
|
|
3
3
|
|
|
4
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 });
|
|
5
10
|
|
|
6
|
-
module.exports = class InputHandler {
|
|
7
|
-
constructor({ defaultInputTarget, inputStream, logger }) {
|
|
8
11
|
this.defaultInputTarget = defaultInputTarget || defaults.defaultInputTarget;
|
|
9
12
|
this.inputStream = inputStream;
|
|
10
|
-
this.
|
|
13
|
+
this.pauseInputStreamOnFinish = pauseInputStreamOnFinish !== false;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
handle(commands) {
|
|
14
17
|
if (!this.inputStream) {
|
|
15
|
-
return commands;
|
|
18
|
+
return { commands };
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
Rx.fromEvent(this.inputStream, 'data')
|
|
@@ -34,6 +37,14 @@ module.exports = class InputHandler {
|
|
|
34
37
|
}
|
|
35
38
|
});
|
|
36
39
|
|
|
37
|
-
return
|
|
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
|
+
};
|
|
38
49
|
}
|
|
39
50
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const stream = require('stream');
|
|
2
2
|
const { createMockInstance } = require('jest-create-mock-instance');
|
|
3
3
|
|
|
4
4
|
const Logger = require('../logger');
|
|
@@ -12,7 +12,7 @@ beforeEach(() => {
|
|
|
12
12
|
createFakeCommand('foo', 'echo foo', 0),
|
|
13
13
|
createFakeCommand('bar', 'echo bar', 1),
|
|
14
14
|
];
|
|
15
|
-
inputStream = new
|
|
15
|
+
inputStream = new stream.PassThrough();
|
|
16
16
|
logger = createMockInstance(Logger);
|
|
17
17
|
controller = new InputHandler({
|
|
18
18
|
defaultInputTarget: 0,
|
|
@@ -22,16 +22,16 @@ beforeEach(() => {
|
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
it('returns same commands', () => {
|
|
25
|
-
expect(controller.handle(commands)).
|
|
25
|
+
expect(controller.handle(commands)).toMatchObject({ commands });
|
|
26
26
|
|
|
27
27
|
controller = new InputHandler({ logger });
|
|
28
|
-
expect(controller.handle(commands)).
|
|
28
|
+
expect(controller.handle(commands)).toMatchObject({ commands });
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
it('forwards input stream to default target ID', () => {
|
|
32
32
|
controller.handle(commands);
|
|
33
33
|
|
|
34
|
-
inputStream.
|
|
34
|
+
inputStream.write('something');
|
|
35
35
|
|
|
36
36
|
expect(commands[0].stdin.write).toHaveBeenCalledTimes(1);
|
|
37
37
|
expect(commands[0].stdin.write).toHaveBeenCalledWith('something');
|
|
@@ -41,7 +41,7 @@ it('forwards input stream to default target ID', () => {
|
|
|
41
41
|
it('forwards input stream to target index specified in input', () => {
|
|
42
42
|
controller.handle(commands);
|
|
43
43
|
|
|
44
|
-
inputStream.
|
|
44
|
+
inputStream.write('1:something');
|
|
45
45
|
|
|
46
46
|
expect(commands[0].stdin.write).not.toHaveBeenCalled();
|
|
47
47
|
expect(commands[1].stdin.write).toHaveBeenCalledTimes(1);
|
|
@@ -63,7 +63,7 @@ it('forwards input stream to target index specified in input when input contains
|
|
|
63
63
|
it('forwards input stream to target name specified in input', () => {
|
|
64
64
|
controller.handle(commands);
|
|
65
65
|
|
|
66
|
-
inputStream.
|
|
66
|
+
inputStream.write('bar:something');
|
|
67
67
|
|
|
68
68
|
expect(commands[0].stdin.write).not.toHaveBeenCalled();
|
|
69
69
|
expect(commands[1].stdin.write).toHaveBeenCalledTimes(1);
|
|
@@ -74,7 +74,7 @@ it('logs error if command has no stdin open', () => {
|
|
|
74
74
|
commands[0].stdin = null;
|
|
75
75
|
controller.handle(commands);
|
|
76
76
|
|
|
77
|
-
inputStream.
|
|
77
|
+
inputStream.write('something');
|
|
78
78
|
|
|
79
79
|
expect(commands[1].stdin.write).not.toHaveBeenCalled();
|
|
80
80
|
expect(logger.logGlobalEvent).toHaveBeenCalledWith('Unable to find command 0, or it has no stdin open\n');
|
|
@@ -83,9 +83,31 @@ it('logs error if command has no stdin open', () => {
|
|
|
83
83
|
it('logs error if command is not found', () => {
|
|
84
84
|
controller.handle(commands);
|
|
85
85
|
|
|
86
|
-
inputStream.
|
|
86
|
+
inputStream.write('foobar:something');
|
|
87
87
|
|
|
88
88
|
expect(commands[0].stdin.write).not.toHaveBeenCalled();
|
|
89
89
|
expect(commands[1].stdin.write).not.toHaveBeenCalled();
|
|
90
90
|
expect(logger.logGlobalEvent).toHaveBeenCalledWith('Unable to find command foobar, or it has no stdin open\n');
|
|
91
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,8 +1,11 @@
|
|
|
1
1
|
const { map } = require('rxjs/operators');
|
|
2
2
|
|
|
3
|
+
const BaseHandler = require('./base-handler');
|
|
3
4
|
|
|
4
|
-
module.exports = class KillOnSignal {
|
|
5
|
+
module.exports = class KillOnSignal extends BaseHandler {
|
|
5
6
|
constructor({ process }) {
|
|
7
|
+
super();
|
|
8
|
+
|
|
6
9
|
this.process = process;
|
|
7
10
|
}
|
|
8
11
|
|
|
@@ -15,16 +18,18 @@ module.exports = class KillOnSignal {
|
|
|
15
18
|
});
|
|
16
19
|
});
|
|
17
20
|
|
|
18
|
-
return
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
return {
|
|
22
|
+
commands: commands.map(command => {
|
|
23
|
+
const closeStream = command.close.pipe(map(exitInfo => {
|
|
24
|
+
const exitCode = caughtSignal === 'SIGINT' ? 0 : exitInfo.exitCode;
|
|
25
|
+
return Object.assign({}, exitInfo, { exitCode });
|
|
26
|
+
}));
|
|
27
|
+
return new Proxy(command, {
|
|
28
|
+
get(target, prop) {
|
|
29
|
+
return prop === 'close' ? closeStream : target[prop];
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
})
|
|
33
|
+
};
|
|
29
34
|
}
|
|
30
35
|
};
|
|
@@ -14,7 +14,7 @@ beforeEach(() => {
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
it('returns commands that keep non-close streams from original commands', () => {
|
|
17
|
-
const newCommands = controller.handle(commands);
|
|
17
|
+
const { commands: newCommands } = controller.handle(commands);
|
|
18
18
|
newCommands.forEach((newCommand, i) => {
|
|
19
19
|
expect(newCommand.close).not.toBe(commands[i].close);
|
|
20
20
|
expect(newCommand.error).toBe(commands[i].error);
|
|
@@ -24,7 +24,7 @@ it('returns commands that keep non-close streams from original commands', () =>
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it('returns commands that map SIGINT to exit code 0', () => {
|
|
27
|
-
const newCommands = controller.handle(commands);
|
|
27
|
+
const { commands: newCommands } = controller.handle(commands);
|
|
28
28
|
expect(newCommands).not.toBe(commands);
|
|
29
29
|
expect(newCommands).toHaveLength(commands.length);
|
|
30
30
|
|
|
@@ -40,7 +40,7 @@ it('returns commands that map SIGINT to exit code 0', () => {
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
it('returns commands that keep non-SIGINT exit codes', () => {
|
|
43
|
-
const newCommands = controller.handle(commands);
|
|
43
|
+
const { commands: newCommands } = controller.handle(commands);
|
|
44
44
|
expect(newCommands).not.toBe(commands);
|
|
45
45
|
expect(newCommands).toHaveLength(commands.length);
|
|
46
46
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const { filter, map } = require('rxjs/operators');
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const BaseHandler = require('./base-handler');
|
|
5
|
+
|
|
6
|
+
module.exports = class KillOthers extends BaseHandler {
|
|
5
7
|
constructor({ logger, conditions }) {
|
|
6
|
-
|
|
8
|
+
super({ logger });
|
|
9
|
+
|
|
7
10
|
this.conditions = _.castArray(conditions);
|
|
8
11
|
}
|
|
9
12
|
|
|
@@ -14,7 +17,7 @@ module.exports = class KillOthers {
|
|
|
14
17
|
));
|
|
15
18
|
|
|
16
19
|
if (!conditions.length) {
|
|
17
|
-
return commands;
|
|
20
|
+
return { commands };
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
const closeStates = commands.map(command => command.close.pipe(
|
|
@@ -30,6 +33,6 @@ module.exports = class KillOthers {
|
|
|
30
33
|
}
|
|
31
34
|
}));
|
|
32
35
|
|
|
33
|
-
return commands;
|
|
36
|
+
return { commands };
|
|
34
37
|
}
|
|
35
38
|
};
|
|
@@ -20,8 +20,8 @@ const createWithConditions = conditions => new KillOthers({
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
it('returns same commands', () => {
|
|
23
|
-
expect(createWithConditions(['foo']).handle(commands)).
|
|
24
|
-
expect(createWithConditions(['failure']).handle(commands)).
|
|
23
|
+
expect(createWithConditions(['foo']).handle(commands)).toMatchObject({ commands });
|
|
24
|
+
expect(createWithConditions(['failure']).handle(commands)).toMatchObject({ commands });
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
it('does not kill others if condition does not match', () => {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
const { of } = require('rxjs');
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
constructor({ logger }) {
|
|
5
|
-
this.logger = logger;
|
|
6
|
-
}
|
|
3
|
+
const BaseHandler = require('./base-handler');
|
|
7
4
|
|
|
5
|
+
module.exports = class LogExit extends BaseHandler {
|
|
8
6
|
handle(commands) {
|
|
9
7
|
commands.forEach(command => command.error.subscribe(event => {
|
|
10
8
|
this.logger.logCommandEvent(
|
|
@@ -15,6 +13,6 @@ module.exports = class LogExit {
|
|
|
15
13
|
this.logger.logCommandEvent(event.stack || event, command);
|
|
16
14
|
}));
|
|
17
15
|
|
|
18
|
-
return commands;
|
|
16
|
+
return { commands };
|
|
19
17
|
}
|
|
20
18
|
};
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
constructor({ logger }) {
|
|
3
|
-
this.logger = logger;
|
|
4
|
-
}
|
|
1
|
+
const BaseHandler = require('./base-handler');
|
|
5
2
|
|
|
3
|
+
module.exports = class LogExit extends BaseHandler {
|
|
6
4
|
handle(commands) {
|
|
7
5
|
commands.forEach(command => command.close.subscribe(({ exitCode }) => {
|
|
8
6
|
this.logger.logCommandEvent(`${command.command} exited with code ${exitCode}`, command);
|
|
9
7
|
}));
|
|
10
8
|
|
|
11
|
-
return commands;
|
|
9
|
+
return { commands };
|
|
12
10
|
}
|
|
13
11
|
};
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
constructor({ logger }) {
|
|
3
|
-
this.logger = logger;
|
|
4
|
-
}
|
|
1
|
+
const BaseHandler = require('./base-handler');
|
|
5
2
|
|
|
3
|
+
module.exports = class LogOutput extends BaseHandler {
|
|
6
4
|
handle(commands) {
|
|
7
5
|
commands.forEach(command => {
|
|
8
6
|
command.stdout.subscribe(text => this.logger.logCommandText(text.toString(), command));
|
|
9
7
|
command.stderr.subscribe(text => this.logger.logCommandText(text.toString(), command));
|
|
10
8
|
});
|
|
11
9
|
|
|
12
|
-
return commands;
|
|
10
|
+
return { commands };
|
|
13
11
|
}
|
|
14
12
|
};
|
|
@@ -2,19 +2,21 @@ const Rx = require('rxjs');
|
|
|
2
2
|
const { defaultIfEmpty, delay, filter, mapTo, skip, take, takeWhile } = require('rxjs/operators');
|
|
3
3
|
|
|
4
4
|
const defaults = require('../defaults');
|
|
5
|
+
const BaseHandler = require('./base-handler');
|
|
5
6
|
|
|
6
|
-
module.exports = class RestartProcess {
|
|
7
|
+
module.exports = class RestartProcess extends BaseHandler {
|
|
7
8
|
constructor({ delay, tries, logger, scheduler }) {
|
|
9
|
+
super({ logger });
|
|
10
|
+
|
|
8
11
|
this.delay = +delay || defaults.restartDelay;
|
|
9
12
|
this.tries = +tries || defaults.restartTries;
|
|
10
13
|
this.tries = this.tries < 0 ? Infinity : this.tries;
|
|
11
|
-
this.logger = logger;
|
|
12
14
|
this.scheduler = scheduler;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
handle(commands) {
|
|
16
18
|
if (this.tries === 0) {
|
|
17
|
-
return commands;
|
|
19
|
+
return { commands };
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
commands.map(command => command.close.pipe(
|
|
@@ -36,17 +38,19 @@ module.exports = class RestartProcess {
|
|
|
36
38
|
}
|
|
37
39
|
}));
|
|
38
40
|
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
return {
|
|
42
|
+
commands: commands.map(command => {
|
|
43
|
+
const closeStream = command.close.pipe(filter(({ exitCode }, emission) => {
|
|
44
|
+
// We let all success codes pass, and failures only after restarting won't happen again
|
|
45
|
+
return exitCode === 0 || emission >= this.tries;
|
|
46
|
+
}));
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
return new Proxy(command, {
|
|
49
|
+
get(target, prop) {
|
|
50
|
+
return prop === 'close' ? closeStream : target[prop];
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
})
|
|
54
|
+
};
|
|
51
55
|
}
|
|
52
56
|
};
|
|
@@ -91,17 +91,17 @@ it('restarts processes until they succeed', () => {
|
|
|
91
91
|
describe('returned commands', () => {
|
|
92
92
|
it('are the same if 0 tries are to be attempted', () => {
|
|
93
93
|
controller = new RestartProcess({ logger, scheduler });
|
|
94
|
-
expect(controller.handle(commands)).
|
|
94
|
+
expect(controller.handle(commands)).toMatchObject({ commands });
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
it('are not the same, but with same length if 1+ tries are to be attempted', () => {
|
|
98
|
-
const newCommands = controller.handle(commands);
|
|
98
|
+
const { commands: newCommands } = controller.handle(commands);
|
|
99
99
|
expect(newCommands).not.toBe(commands);
|
|
100
100
|
expect(newCommands).toHaveLength(commands.length);
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
it('skip close events followed by restarts', () => {
|
|
104
|
-
const newCommands = controller.handle(commands);
|
|
104
|
+
const { commands: newCommands } = controller.handle(commands);
|
|
105
105
|
|
|
106
106
|
const callback = jest.fn();
|
|
107
107
|
newCommands[0].close.subscribe(callback);
|
|
@@ -120,7 +120,7 @@ describe('returned commands', () => {
|
|
|
120
120
|
});
|
|
121
121
|
|
|
122
122
|
it('keep non-close streams from original commands', () => {
|
|
123
|
-
const newCommands = controller.handle(commands);
|
|
123
|
+
const { commands: newCommands } = controller.handle(commands);
|
|
124
124
|
newCommands.forEach((newCommand, i) => {
|
|
125
125
|
expect(newCommand.close).not.toBe(commands[i].close);
|
|
126
126
|
expect(newCommand.error).toBe(commands[i].error);
|