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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Concurrently
2
2
 
3
- [![Travis Build Status](https://travis-ci.org/kimmobrunfeldt/concurrently.svg)](https://travis-ci.org/kimmobrunfeldt/concurrently) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/kimmobrunfeldt/concurrently?branch=master&svg=true)](https://ci.appveyor.com/project/kimmobrunfeldt/concurrently) *master branch status*
3
+ [![Build Status](https://github.com/open-cli-tools/concurrently/workflows/Tests/badge.svg)](https://github.com/open-cli-tools/concurrently/actions?workflow=Tests) *master branch status*
4
4
 
5
5
  [![NPM Badge](https://nodei.co/npm/concurrently.png?downloads=true)](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](http://substack.net/task_automation_with_npm_run)
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/kimmobrunfeldt/concurrently
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, eg `process.stdin`.
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:
@@ -157,7 +157,7 @@ concurrently(args._.map((command, index) => {
157
157
  name: names[index]
158
158
  };
159
159
  }), {
160
- inputStream: args.handleInput && process.stdin,
160
+ handleInput: args.handleInput,
161
161
  defaultInputTarget: args.defaultInputTarget,
162
162
  killOthers: args.killOthers
163
163
  ? ['success', 'failure']
@@ -193,7 +193,7 @@ describe('--names', () => {
193
193
  });
194
194
 
195
195
  describe('--prefix', () => {
196
- it('is alised to -p', done => {
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 alised to -l', done => {
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 alised to -k', done => {
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
@@ -39,4 +39,4 @@ Examples:
39
39
 
40
40
  $ $0 npm:watch-*
41
41
 
42
- For more details, visit https://github.com/kimmobrunfeldt/concurrently
42
+ For more details, visit https://github.com/open-cli-tools/concurrently
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.0",
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/kimmobrunfeldt/concurrently.git"
19
+ "url": "https://github.com/open-cli-tools/concurrently.git"
20
20
  },
21
21
  "keywords": [
22
22
  "bash",
@@ -49,10 +49,17 @@ module.exports = (commands, options) => {
49
49
  ))
50
50
  .value();
51
51
 
52
- commands = options.controllers.reduce(
53
- (prevCommands, controller) => controller.handle(prevCommands),
54
- commands
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 }).listen(commands);
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) {
@@ -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 = [{ handle: jest.fn(arg => arg) }, { handle: jest.fn(arg => arg) }];
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.logger = logger;
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 commands;
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 EventEmitter = require('events');
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 EventEmitter();
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)).toBe(commands);
25
+ expect(controller.handle(commands)).toMatchObject({ commands });
26
26
 
27
27
  controller = new InputHandler({ logger });
28
- expect(controller.handle(commands)).toBe(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.emit('data', Buffer.from('something'));
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.emit('data', Buffer.from('1:something'));
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.emit('data', Buffer.from('bar:something'));
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.emit('data', Buffer.from('something'));
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.emit('data', Buffer.from('foobar:something'));
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 commands.map(command => {
19
- const closeStream = command.close.pipe(map(exitInfo => {
20
- const exitCode = caughtSignal === 'SIGINT' ? 0 : exitInfo.exitCode;
21
- return Object.assign({}, exitInfo, { exitCode });
22
- }));
23
- return new Proxy(command, {
24
- get(target, prop) {
25
- return prop === 'close' ? closeStream : target[prop];
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
- module.exports = class KillOthers {
4
+ const BaseHandler = require('./base-handler');
5
+
6
+ module.exports = class KillOthers extends BaseHandler {
5
7
  constructor({ logger, conditions }) {
6
- this.logger = logger;
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)).toBe(commands);
24
- expect(createWithConditions(['failure']).handle(commands)).toBe(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
- module.exports = class LogExit {
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
  };
@@ -15,7 +15,7 @@ beforeEach(() => {
15
15
  });
16
16
 
17
17
  it('returns same commands', () => {
18
- expect(controller.handle(commands)).toBe(commands);
18
+ expect(controller.handle(commands)).toMatchObject({ commands });
19
19
  });
20
20
 
21
21
  it('logs the error event of each command', () => {
@@ -1,13 +1,11 @@
1
- module.exports = class LogExit {
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
  };
@@ -15,7 +15,7 @@ beforeEach(() => {
15
15
  });
16
16
 
17
17
  it('returns same commands', () => {
18
- expect(controller.handle(commands)).toBe(commands);
18
+ expect(controller.handle(commands)).toMatchObject({ commands });
19
19
  });
20
20
 
21
21
  it('logs the close event of each command', () => {
@@ -1,14 +1,12 @@
1
- module.exports = class LogOutput {
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
  };
@@ -15,7 +15,7 @@ beforeEach(() => {
15
15
  });
16
16
 
17
17
  it('returns same commands', () => {
18
- expect(controller.handle(commands)).toBe(commands);
18
+ expect(controller.handle(commands)).toMatchObject({ commands });
19
19
  });
20
20
 
21
21
  it('logs the stdout of each command', () => {
@@ -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 commands.map(command => {
40
- const closeStream = command.close.pipe(filter(({ exitCode }, emission) => {
41
- // We let all success codes pass, and failures only after restarting won't happen again
42
- return exitCode === 0 || emission >= this.tries;
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
- return new Proxy(command, {
46
- get(target, prop) {
47
- return prop === 'close' ? closeStream : target[prop];
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)).toBe(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);