concurrently 3.2.0 → 3.5.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/.travis.yml CHANGED
@@ -1,10 +1,8 @@
1
1
  language: node_js
2
2
  node_js:
3
- - 7
3
+ - 8
4
4
  - 6
5
5
  - 4
6
- before_script:
7
- - npm install -g mocha
8
6
  notifications:
9
7
  email:
10
8
  - kimmobrunfeldt+concurrent@gmail.com
package/README.md CHANGED
@@ -11,10 +11,10 @@ Like `npm run watch-js & npm run watch-less` but better.
11
11
 
12
12
  **Features:**
13
13
 
14
- * Cross platform, works also in Windows
14
+ * Cross platform (including Windows)
15
15
  * Output is easy to follow with prefixes
16
16
  * With `--kill-others` switch, all commands are killed if one dies
17
- * Spawns commands with [spawn-default-shell](https://github.com/kimmobrunfeldt/spawn-default-shell)
17
+ * Spawns commands with [spawn-command](https://github.com/mmalecki/spawn-command)
18
18
 
19
19
  ## Install
20
20
 
@@ -32,7 +32,7 @@ npm install concurrently --save
32
32
 
33
33
  ## Usage
34
34
 
35
- Remember to surround separate commands with quotes, like this:
35
+ Remember to surround separate commands with quotes:
36
36
  ```bash
37
37
  concurrently "command1 arg" "command2 arg"
38
38
  ```
@@ -59,9 +59,10 @@ Options:
59
59
  -h, --help output usage information
60
60
  -V, --version output the version number
61
61
  -k, --kill-others kill other processes if one exits or dies
62
+ --kill-others-on-fail kill other processes if one exits with non zero status code
62
63
  --no-color disable colors from logging
63
64
  -p, --prefix <prefix> prefix used in logging for each process.
64
- Possible values: index, pid, time, command, name, none, or a template. Default: index. Example template: "{time}-{pid}"
65
+ Possible values: index, pid, time, command, name, none, or a template. Default: index or name (when --names is set). Example template: "{time}-{pid}"
65
66
 
66
67
  -n, --names <names> List of custom names to be used in prefix template.
67
68
  Example names: "main,browser,server"
@@ -85,12 +86,21 @@ Options:
85
86
  The option can be used to shorten long commands.
86
87
  Works only if prefix is set to "command". Default: 10
87
88
 
89
+ --allow-restart Restart a process which died. Default: false
90
+
91
+ --restart-after <miliseconds> delay time to respawn the process. Default: 0
92
+
93
+ --restart-tries <times> limit the number of respawn tries. Default: 1
88
94
 
89
95
  Examples:
90
96
 
91
97
  - Kill other processes if one exits or dies
92
98
 
93
99
  $ concurrently --kill-others "grunt watch" "http-server"
100
+
101
+ - Kill other processes if one exits with non zero status code
102
+
103
+ $ concurrently --kill-others-on-fail "npm run build:client" "npm run build:server"
94
104
 
95
105
  - Output nothing more than stdout+stderr of child processes
96
106
 
@@ -106,7 +116,7 @@ Examples:
106
116
 
107
117
  - Custom names and colored prefixes
108
118
 
109
- $ concurrently --prefix "[{name}]" --names "HTTP,WATCH" -c "bgBlue.bold,bgMagenta.bold" "npm run watch" "http-server"
119
+ $ concurrently --names "HTTP,WATCH" -c "bgBlue.bold,bgMagenta.bold" "http-server" "npm run watch"
110
120
 
111
121
  For more details, visit https://github.com/kimmobrunfeldt/concurrently
112
122
  ```
@@ -137,31 +147,3 @@ and you won't even notice the difference.
137
147
 
138
148
  Another option would be to just run all commands in separate terminals. I got
139
149
  tired of opening terminals and made **concurrently**.
140
-
141
- ### NPM Issue
142
-
143
- Previously I thought this could fix some problems I had with watching scripts and this readme said:
144
-
145
- > When running watch or serve tasks, I'd recommend to use `--kill-others` option:
146
- >
147
- > ```bash
148
- > concurrently --kill-others "npm run watch-js" "npm run watch-less"
149
- > ```
150
- >
151
- > That way, if for some reason e.g. your `watch-less` died, you would notice it easier.
152
-
153
- However NPM didn't work as I hoped it would. See [this issue](https://github.com/kimmobrunfeldt/concurrently/issues/4).
154
-
155
-
156
- ## Debugging
157
-
158
- A few common debugging commands.
159
-
160
- ### spawn-default-shell
161
-
162
- Which shell and flag does [spawn-default-shell](https://github.com/kimmobrunfeldt/spawn-default-shell/)
163
- use?
164
-
165
- ```bash
166
- npm i spawn-default-shell && node -e "console.log(require('spawn-default-shell/src/get-shell')())"
167
- ```
package/appveyor.yml CHANGED
@@ -1,13 +1,12 @@
1
1
  environment:
2
2
  matrix:
3
- - nodejs_version: '7'
3
+ - nodejs_version: '8'
4
4
  - nodejs_version: '6'
5
5
  - nodejs_version: '4'
6
6
  install:
7
7
  - ps: Install-Product node $env:nodejs_version
8
8
  - set CI=true
9
9
  - npm -g install npm@latest
10
- - npm install -g mocha
11
10
  - set PATH=%APPDATA%\npm;%PATH%
12
11
  - npm install
13
12
  build: off
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "concurrently",
3
- "version": "3.2.0",
3
+ "version": "3.5.1",
4
4
  "description": "Run commands concurrently",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "date-fns": "^1.23.0",
38
38
  "lodash": "^4.5.1",
39
39
  "rx": "2.3.24",
40
- "spawn-default-shell": "^2.0.0",
40
+ "spawn-command": "^0.0.2-1",
41
41
  "supports-color": "^3.2.3",
42
42
  "tree-kill": "^1.1.0"
43
43
  },
package/src/main.js CHANGED
@@ -7,7 +7,7 @@ var program = require('commander');
7
7
  var _ = require('lodash');
8
8
  var treeKill = require('tree-kill');
9
9
  var chalk = require('chalk');
10
- var defaultShell = require('spawn-default-shell');
10
+ var spawn = require('spawn-command');
11
11
  var supportsColor = require('supports-color');
12
12
  var IS_WINDOWS = /^win/.test(process.platform);
13
13
 
@@ -15,8 +15,8 @@ var config = {
15
15
  // Kill other processes if one dies
16
16
  killOthers: false,
17
17
 
18
- // How much in ms we wait before killing other processes
19
- killDelay: 1000,
18
+ // Kill other processes if one exits with non zero status code
19
+ killOthersOnFail: false,
20
20
 
21
21
  // Return success or failure of the 'first' child to terminate, the 'last' child,
22
22
  // or succeed only if 'all' children succeed
@@ -24,7 +24,7 @@ var config = {
24
24
 
25
25
  // Prefix logging with pid
26
26
  // Possible values: 'pid', 'none', 'time', 'command', 'index', 'name'
27
- prefix: 'index',
27
+ prefix: '',
28
28
 
29
29
  // List of custom names to be used in prefix template
30
30
  names: '',
@@ -46,7 +46,16 @@ var config = {
46
46
  color: true,
47
47
 
48
48
  // If true, the output will only be raw output of processes, nothing more
49
- raw: false
49
+ raw: false,
50
+
51
+ // If true, the process restart when it exited with status code non-zero
52
+ allowRestart: false,
53
+
54
+ // By default, restart instantly
55
+ restartAfter: 0,
56
+
57
+ // By default, restart once
58
+ restartTries: 1
50
59
  };
51
60
 
52
61
  function main() {
@@ -58,6 +67,8 @@ function main() {
58
67
 
59
68
  parseArgs();
60
69
  config = mergeDefaultsWithArgs(config);
70
+ applyDynamicDefaults(config)
71
+
61
72
  run(program.args);
62
73
  }
63
74
 
@@ -69,6 +80,10 @@ function parseArgs() {
69
80
  '-k, --kill-others',
70
81
  'kill other processes if one exits or dies'
71
82
  )
83
+ .option(
84
+ '--kill-others-on-fail',
85
+ 'kill other processes if one exits with non zero status code'
86
+ )
72
87
  .option(
73
88
  '--no-color',
74
89
  'disable colors from logging'
@@ -77,7 +92,7 @@ function parseArgs() {
77
92
  '-p, --prefix <prefix>',
78
93
  'prefix used in logging for each process.\n' +
79
94
  'Possible values: index, pid, time, command, name, none, or a template. Default: ' +
80
- config.prefix + '. Example template: "{time}-{pid}"\n'
95
+ 'index or name (when --names is set). Example template: "{time}-{pid}"\n'
81
96
  )
82
97
  .option(
83
98
  '-n, --names <names>',
@@ -122,6 +137,21 @@ function parseArgs() {
122
137
  'The option can be used to shorten long commands.\n' +
123
138
  'Works only if prefix is set to "command". Default: ' +
124
139
  config.prefixLength + '\n'
140
+ )
141
+ .option(
142
+ '--allow-restart',
143
+ 'Restart a process which died. Default: ' +
144
+ config.allowRestart + '\n'
145
+ )
146
+ .option(
147
+ '--restart-after <miliseconds>',
148
+ 'delay time to respawn the process. Default: ' +
149
+ config.restartAfter + '\n'
150
+ )
151
+ .option(
152
+ '--restart-tries <times>',
153
+ 'limit the number of respawn tries. Default: ' +
154
+ config.restartTries + '\n'
125
155
  );
126
156
 
127
157
  program.on('--help', function() {
@@ -132,6 +162,10 @@ function parseArgs() {
132
162
  '',
133
163
  ' $ concurrently --kill-others "grunt watch" "http-server"',
134
164
  '',
165
+ ' - Kill other processes if one exits with non zero status code',
166
+ '',
167
+ ' $ concurrently --kill-others-on-fail "npm run build:client" "npm run build:server"',
168
+ '',
135
169
  ' - Output nothing more than stdout+stderr of child processes',
136
170
  '',
137
171
  ' $ concurrently --raw "npm run watch-less" "npm run watch-js"',
@@ -146,7 +180,7 @@ function parseArgs() {
146
180
  '',
147
181
  ' - Custom names and colored prefixes',
148
182
  '',
149
- ' $ concurrently --prefix "[{name}]" --names "HTTP,WATCH" -c "bgBlue.bold,bgMagenta.bold" "npm run watch" "http-server"',
183
+ ' $ concurrently --names "HTTP,WATCH" -c "bgBlue.bold,bgMagenta.bold" "http-server" "npm run watch"',
150
184
  ''
151
185
  ];
152
186
  console.log(help.join('\n'));
@@ -164,6 +198,12 @@ function mergeDefaultsWithArgs(config) {
164
198
  return _.merge(config, program);
165
199
  }
166
200
 
201
+ function applyDynamicDefaults(config) {
202
+ if (!config.prefix) {
203
+ config.prefix = config.names ? 'name' : 'index';
204
+ }
205
+ }
206
+
167
207
  function stripCmdQuotes(cmd) {
168
208
  // Removes the quotes surrounding a command.
169
209
  if (cmd[0] === '"' || cmd[0] === '\'') {
@@ -190,14 +230,7 @@ function run(commands) {
190
230
  spawnOpts.env = Object.assign({FORCE_COLOR: supportsColor.level}, process.env)
191
231
  }
192
232
 
193
- var child;
194
- try {
195
- child = defaultShell.spawn(cmd, spawnOpts);
196
- } catch (e) {
197
- logError('', chalk.gray.dim, 'Error occured when executing command: ' + cmd);
198
- logError('', chalk.gray.dim, e.stack);
199
- process.exit(1);
200
- }
233
+ var child = spawnChild(cmd, spawnOpts);
201
234
 
202
235
  if (index < prefixColors.length) {
203
236
  var prefixColorPath = prefixColors[index];
@@ -209,13 +242,41 @@ function run(commands) {
209
242
  command: cmd,
210
243
  index: index,
211
244
  name: name,
245
+ options: spawnOpts,
246
+ restartTries: config.restartTries,
212
247
  prefixColor: lastPrefixColor
213
248
  };
214
249
  return child;
215
250
  });
216
251
 
252
+ var streams = toStreams(children);
253
+
254
+ handleChildEvents(streams, children, childrenInfo);
255
+
256
+ ['SIGINT', 'SIGTERM'].forEach(function(signal) {
257
+ process.on(signal, function() {
258
+ children.forEach(function(child) {
259
+ treeKill(child.pid, signal);
260
+ });
261
+ });
262
+ });
263
+ }
264
+
265
+ function spawnChild(cmd, options) {
266
+ var child;
267
+ try {
268
+ child = spawn(cmd, options);
269
+ } catch (e) {
270
+ logError('', chalk.gray.dim, 'Error occured when executing command: ' + cmd);
271
+ logError('', chalk.gray.dim, e.stack);
272
+ process.exit(1);
273
+ }
274
+ return child;
275
+ }
276
+
277
+ function toStreams(children) {
217
278
  // Transform all process events to rx streams
218
- var streams = _.map(children, function(child) {
279
+ return _.map(children, function(child) {
219
280
  var childStreams = {
220
281
  error: Rx.Node.fromEvent(child, 'error'),
221
282
  close: Rx.Node.fromEvent(child, 'close')
@@ -233,21 +294,15 @@ function run(commands) {
233
294
  return memo;
234
295
  }, {});
235
296
  });
297
+ }
236
298
 
299
+ function handleChildEvents(streams, children, childrenInfo) {
237
300
  handleClose(streams, children, childrenInfo);
238
301
  handleError(streams, childrenInfo);
239
302
  if (!config.raw) {
240
303
  handleOutput(streams, childrenInfo, 'stdout');
241
304
  handleOutput(streams, childrenInfo, 'stderr');
242
305
  }
243
-
244
- ['SIGINT', 'SIGTERM'].forEach(function(signal) {
245
- process.on(signal, function() {
246
- children.forEach(function(child) {
247
- treeKill(child.pid, signal);
248
- });
249
- });
250
- });
251
306
  }
252
307
 
253
308
  function handleOutput(streams, childrenInfo, source) {
@@ -266,39 +321,68 @@ function handleClose(streams, children, childrenInfo) {
266
321
  var exitCodes = [];
267
322
  var closeStreams = _.map(streams, 'close');
268
323
  var closeStream = Rx.Observable.merge.apply(this, closeStreams);
324
+ var othersKilled = false
269
325
 
270
326
  // TODO: Is it possible that amount of close events !== count of spawned?
271
327
  closeStream.subscribe(function(event) {
272
328
  var exitCode = event.data;
329
+ var nonSuccess = exitCode !== 0;
273
330
  exitCodes.push(exitCode);
274
331
 
275
332
  var prefix = getPrefix(childrenInfo, event.child);
276
- var prefixColor = childrenInfo[event.child.pid].prefixColor;
277
- var command = childrenInfo[event.child.pid].command;
333
+ var childInfo = childrenInfo[event.child.pid];
334
+ var prefixColor = childInfo.prefixColor;
335
+ var command = childInfo.command;
278
336
  logEvent(prefix, prefixColor, command + ' exited with code ' + exitCode);
279
337
 
280
338
  aliveChildren = _.filter(aliveChildren, function(child) {
281
339
  return child.pid !== event.child.pid;
282
340
  });
283
341
 
342
+ if (nonSuccess && config.allowRestart && childInfo.restartTries--) {
343
+ respawnChild(event, childrenInfo);
344
+ return;
345
+ }
346
+
284
347
  if (aliveChildren.length === 0) {
285
348
  exit(exitCodes);
286
349
  }
350
+ if (!othersKilled) {
351
+ if (config.killOthers) {
352
+ killOtherProcesses(aliveChildren);
353
+ othersKilled = true;
354
+ } else if (config.killOthersOnFail && nonSuccess) {
355
+ killOtherProcesses(aliveChildren);
356
+ othersKilled = true;
357
+ }
358
+ }
287
359
  });
360
+ }
288
361
 
289
- if (config.killOthers) {
290
- // Give other processes some time to stop cleanly before killing them
291
- var delayedExit = closeStream.delay(config.killDelay);
362
+ function respawnChild(event, childrenInfo) {
363
+ setTimeout(function() {
364
+ var childInfo = childrenInfo[event.child.pid];
365
+ var prefix = getPrefix(childrenInfo, event.child);
366
+ var prefixColor = childInfo.prefixColor;
367
+ logEvent(prefix, prefixColor, childInfo.command + ' restarted');
368
+ var newChild = spawnChild(childInfo.command, childInfo.options);
292
369
 
293
- delayedExit.subscribe(function() {
294
- logEvent('--> ', chalk.gray.dim, 'Sending SIGTERM to other processes..');
370
+ childrenInfo[newChild.pid] = childrenInfo[event.child.pid];
371
+ delete childrenInfo[event.child.pid];
295
372
 
296
- // Send SIGTERM to alive children
297
- _.each(aliveChildren, function(child) {
298
- treeKill(child.pid, 'SIGTERM');
299
- });
300
- });
301
- }
373
+ var children = [newChild];
374
+ var streams = toStreams(children);
375
+ handleChildEvents(streams, children, childrenInfo);
376
+ }, config.restartAfter);
377
+ }
378
+
379
+ function killOtherProcesses(processes) {
380
+ logEvent('--> ', chalk.gray.dim, 'Sending SIGTERM to other processes..');
381
+
382
+ // Send SIGTERM to alive children
383
+ _.each(processes, function(child) {
384
+ treeKill(child.pid, 'SIGTERM');
385
+ });
302
386
  }
303
387
 
304
388
  function exit(childExitCodes) {
@@ -385,7 +469,7 @@ function log(prefix, prefixColor, text) {
385
469
  function logEvent(prefix, prefixColor, text) {
386
470
  if (config.raw) return;
387
471
 
388
- logWithPrefix(prefix, prefixColor, text, chalk.gray.dim);
472
+ logWithPrefix(prefix, prefixColor, text + '\n', chalk.gray.dim);
389
473
  }
390
474
 
391
475
  function logError(prefix, prefixColor, text) {
@@ -394,31 +478,35 @@ function logError(prefix, prefixColor, text) {
394
478
  logWithPrefix(prefix, prefixColor, text, chalk.red.bold);
395
479
  }
396
480
 
481
+ var lastChar;
482
+
397
483
  function logWithPrefix(prefix, prefixColor, text, color) {
398
- var lastChar = text[text.length - 1];
399
- if (config.raw) {
400
- if (lastChar !== '\n') {
401
- text += '\n';
402
- }
403
484
 
485
+ if (config.raw) {
404
486
  process.stdout.write(text);
405
487
  return;
406
488
  }
407
489
 
408
- if (lastChar === '\n') {
409
- // Remove extra newline from the end to prevent extra newlines in input
410
- text = text.slice(0, text.length - 1);
411
- }
490
+ text = text.replace(/\u2026/g,'...'); // Ellipsis
412
491
 
413
492
  var lines = text.split('\n');
414
493
  // Do not bgColor trailing space
415
494
  var coloredPrefix = colorText(prefix.replace(/ $/, ''), prefixColor) + ' ';
416
- var paddedLines = _.map(lines, function(line, i) {
495
+ var paddedLines = _.map(lines, function(line, index) {
417
496
  var coloredLine = color ? colorText(line, color) : line;
418
- return coloredPrefix + coloredLine;
497
+ if (index !== 0 && index !== (lines.length - 1)) {
498
+ coloredLine = coloredPrefix + coloredLine;
499
+ }
500
+ return coloredLine;
419
501
  });
420
502
 
421
- console.log(paddedLines.join('\n'));
503
+ if (!lastChar || lastChar == '\n' ){
504
+ process.stdout.write(coloredPrefix);
505
+ }
506
+
507
+ lastChar = text[text.length - 1];
508
+
509
+ process.stdout.write(paddedLines.join('\n'));
422
510
  }
423
511
 
424
512
  main();
@@ -5,8 +5,8 @@ var assert = require('assert');
5
5
  var run = require('./utils').run;
6
6
  var IS_WINDOWS = /^win/.test(process.platform);
7
7
 
8
- // If true, output of commands are shown
9
- var DEBUG_TESTS = process.env.DEBUG_TESTS === 'true';
8
+ // Note: Set the DEBUG_TESTS environment variable to `true` to see output of test commands.
9
+
10
10
  var TEST_DIR = 'dir/';
11
11
 
12
12
  // Abs path to test directory
@@ -17,7 +17,7 @@ describe('concurrently', function() {
17
17
  this.timeout(5000);
18
18
 
19
19
  it('help should be successful', () => {
20
- return run('node ./src/main.js --help', {pipe: DEBUG_TESTS})
20
+ return run('node ./src/main.js --help')
21
21
  .then(function(exitCode) {
22
22
  // exit code 0 means success
23
23
  assert.strictEqual(exitCode, 0);
@@ -25,36 +25,69 @@ describe('concurrently', function() {
25
25
  });
26
26
 
27
27
  it('version should be successful', () => {
28
- return run('node ./src/main.js -V', {pipe: DEBUG_TESTS})
28
+ return run('node ./src/main.js -V')
29
29
  .then(function(exitCode) {
30
30
  assert.strictEqual(exitCode, 0);
31
31
  });
32
32
  });
33
33
 
34
34
  it('two successful commands should exit 0', () => {
35
- return run('node ./src/main.js "echo test" "echo test"', {pipe: DEBUG_TESTS})
35
+ return run('node ./src/main.js "echo test" "echo test"')
36
36
  .then(function(exitCode) {
37
37
  assert.strictEqual(exitCode, 0);
38
38
  });
39
39
  });
40
40
 
41
41
  it('at least one unsuccessful commands should exit non-zero', () => {
42
- return run('node ./src/main.js "echo test" "nosuchcmd" "echo test"', {pipe: DEBUG_TESTS})
42
+ return run('node ./src/main.js "echo test" "nosuchcmd" "echo test"')
43
43
  .then(function(exitCode) {
44
44
  assert.notStrictEqual(exitCode, 0);
45
45
  });
46
46
  });
47
47
 
48
48
  it('--kill-others should kill other commands if one dies', () => {
49
- // This test would timeout if kill others option does not work
50
- return run('node ./src/main.js --kill-others "sleep 1000" "echo test" "sleep 1000"', {pipe: DEBUG_TESTS})
49
+ return run('node ./src/main.js --kill-others "sleep 1" "echo test" "sleep 0.1 && nosuchcmd"')
50
+ .then(function(exitCode) {
51
+ assert.notStrictEqual(exitCode, 0);
52
+ });
53
+ });
54
+
55
+ it('--kill-others-on-fail should kill other commands if one exits with non-zero status code', () => {
56
+ return run('node ./src/main.js --kill-others-on-fail "sleep 1" "exit 1" "sleep 1"')
51
57
  .then(function(exitCode) {
52
58
  assert.notStrictEqual(exitCode, 0);
53
59
  });
54
60
  });
55
61
 
62
+ it('--kill-others-on-fail should NOT kill other commands if none of them exits with non-zero status code', (done) => {
63
+ var readline = require('readline');
64
+ var exits = 0;
65
+ var sigtermInOutput = false;
66
+
67
+ run('node ./src/main.js --kill-others-on-fail "echo killTest1" "echo killTest2" "echo killTest3"', {
68
+ onOutputLine: function(line) {
69
+ if (/SIGTERM/.test(line)) {
70
+ sigtermInOutput = true;
71
+ }
72
+
73
+ // waiting for exits
74
+ if (/killTest\d$/.test(line)) {
75
+ exits++;
76
+ }
77
+ }
78
+ }).then(function() {
79
+ if(sigtermInOutput) {
80
+ done(new Error('There was a "SIGTERM" in console output'));
81
+ } else if (exits !== 3) {
82
+ done(new Error('There was wrong number of echoes(' + exits + ') from executed commands'));
83
+ } else {
84
+ done();
85
+ }
86
+ });
87
+ });
88
+
56
89
  it('--success=first should return first exit code', () => {
57
- return run('node ./src/main.js -k --success first "echo test" "sleep 1000" ', {pipe: DEBUG_TESTS})
90
+ return run('node ./src/main.js -k --success first "echo test" "sleep 0.1 && nosuchcmd"')
58
91
  // When killed, sleep returns null exit code
59
92
  .then(function(exitCode) {
60
93
  assert.strictEqual(exitCode, 0);
@@ -63,26 +96,138 @@ describe('concurrently', function() {
63
96
 
64
97
  it('--success=last should return last exit code', () => {
65
98
  // When killed, sleep returns null exit code
66
- return run('node ./src/main.js -k --success last "echo test" "sleep 1000" ', {pipe: DEBUG_TESTS})
99
+ return run('node ./src/main.js -k --success last "echo test" "sleep 0.1 && nosuchcmd"')
67
100
  .then(function(exitCode) {
68
101
  assert.notStrictEqual(exitCode, 0);
69
102
  });
70
103
  });
71
104
 
72
105
  it('&& nosuchcmd should return non-zero exit code', () => {
73
- return run('node ./src/main.js "echo 1 && nosuchcmd" "echo 1 && nosuchcmd" ', {pipe: DEBUG_TESTS})
106
+ return run('node ./src/main.js "echo 1 && nosuchcmd" "echo 1 && nosuchcmd" ')
74
107
  .then(function(exitCode) {
75
108
  assert.strictEqual(exitCode, 1);
76
109
  });
77
110
  });
78
111
 
79
112
  it('--prefix-colors should handle non-existent colors without failing', () => {
80
- return run('node ./src/main.js -c "not.a.color" "echo colors"', {pipe: DEBUG_TESTS})
113
+ return run('node ./src/main.js -c "not.a.color" "echo colors"')
114
+ .then(function(exitCode) {
115
+ assert.strictEqual(exitCode, 0);
116
+ });
117
+ });
118
+
119
+ it('--prefix should default to "index"', () => {
120
+ var collectedLines = []
121
+
122
+ return run('node ./src/main.js "echo one" "echo two"', {
123
+ onOutputLine: (line) => {
124
+ if (/(one|two)$/.exec(line)) {
125
+ collectedLines.push(line)
126
+ }
127
+ }
128
+ })
129
+ .then(function(exitCode) {
130
+ assert.strictEqual(exitCode, 0);
131
+
132
+ collectedLines.sort()
133
+ assert.deepEqual(collectedLines, [
134
+ '[0] one',
135
+ '[1] two'
136
+ ])
137
+ });
138
+ });
139
+
140
+ it('--names should set a different default prefix', () => {
141
+ var collectedLines = []
142
+
143
+ return run('node ./src/main.js -n aa,bb "echo one" "echo two"', {
144
+ onOutputLine: (line) => {
145
+ if (/(one|two)$/.exec(line)) {
146
+ collectedLines.push(line)
147
+ }
148
+ }
149
+ })
81
150
  .then(function(exitCode) {
82
151
  assert.strictEqual(exitCode, 0);
152
+
153
+ collectedLines.sort()
154
+ assert.deepEqual(collectedLines, [
155
+ '[aa] one',
156
+ '[bb] two'
157
+ ])
83
158
  });
84
159
  });
85
160
 
161
+ it('--allow-restart should restart a proccess with non-zero exit code', (done) => {
162
+ var readline = require('readline');
163
+ var exitedWithOne = false;
164
+ var restarted = false;
165
+
166
+ run('node ./src/main.js --allow-restart "sleep 0.1 && exit 1" "sleep 1"', {
167
+ pipe: false,
168
+ onOutputLine: (line) => {
169
+ var re = /exited with code (.+)/.exec(line);
170
+ if (re && re[1] === '1') {
171
+ exitedWithOne = true
172
+ }
173
+
174
+ if (/restarted/.test(line)) {
175
+ restarted = true;
176
+ }
177
+ }
178
+ }).then(function() {
179
+ if (exitedWithOne && restarted) {
180
+ done();
181
+ } else {
182
+ done(new Error('No restarted process exited with code 1'));
183
+ }
184
+ });
185
+ });
186
+
187
+ it('--restart-after=n should restart a proccess after n miliseconds', (done) => {
188
+ var readline = require('readline');
189
+ var start, end;
190
+
191
+ run('node ./src/main.js --allow-restart --restart-after 300 "exit 1" "sleep 1"', {
192
+ pipe: false,
193
+ onOutputLine: (line) => {
194
+ if (!start && /exited with code (.+)/.test(line)) {
195
+ start = new Date().getTime();
196
+ }
197
+
198
+ if (!end && /restarted/.test(line)) {
199
+ end = new Date().getTime();
200
+ }
201
+ }
202
+ }).then(function() {
203
+ // we accept 100 miliseconds of error
204
+ if (end - start >= 300 && end - start < 400) {
205
+ done();
206
+ } else {
207
+ done(new Error('No restarted process after 300 miliseconds'));
208
+ }
209
+ });
210
+ });
211
+ it('--restart-tries=n should restart a proccess at most n times', (done) => {
212
+ var readline = require('readline');
213
+ var restartedTimes = 0;
214
+
215
+ run('node ./src/main.js --allow-restart --restart-tries 2 "exit 1" "sleep 1"', {
216
+ pipe: false,
217
+ onOutputLine: (line) => {
218
+ if (/restarted/.test(line)) {
219
+ restartedTimes++;
220
+ }
221
+ }
222
+ }).then(function() {
223
+ if (restartedTimes == 2) {
224
+ done();
225
+ } else {
226
+ done(new Error('No restarted process twice'));
227
+ }
228
+ });
229
+ });
230
+
86
231
  ['SIGINT', 'SIGTERM'].forEach((signal) => {
87
232
  if (IS_WINDOWS) {
88
233
  console.log('IS_WINDOWS=true');
@@ -104,32 +249,20 @@ describe('concurrently', function() {
104
249
  }
105
250
 
106
251
  run('node ./src/main.js "node ./test/support/signal.js" "node ./test/support/signal.js"', {
107
- pipe: false,
108
- callback: function(child) {
109
- var rl = readline.createInterface({
110
- input: child.stdout,
111
- output: null
112
- });
252
+ onOutputLine: function(line, child) {
253
+ // waiting for startup
254
+ if (/STARTED/.test(line)) {
255
+ waitingStart--;
256
+ }
257
+ if (!waitingStart) {
258
+ // both processes are started
259
+ child.kill(signal);
260
+ }
113
261
 
114
- rl.on('line', function(line) {
115
- if (DEBUG_TESTS) {
116
- console.log(line);
117
- }
118
-
119
- // waiting for startup
120
- if (/STARTED/.test(line)) {
121
- waitingStart--;
122
- }
123
- if (!waitingStart) {
124
- // both processes are started
125
- child.kill(signal);
126
- }
127
-
128
- // waiting for signal
129
- if (new RegExp(signal).test(line)) {
130
- waitingSignal--;
131
- }
132
- });
262
+ // waiting for signal
263
+ if (new RegExp(signal).test(line)) {
264
+ waitingSignal--;
265
+ }
133
266
  }
134
267
  }).then(function() {
135
268
  waitForSignal(done);
package/test/utils.js CHANGED
@@ -1,31 +1,31 @@
1
1
  var childProcess = require('child_process');
2
2
  var _ = require('lodash');
3
+ var readline = require('readline')
3
4
  var shellQuote = require('shell-quote');
4
5
 
6
+ // If true, output of commands are shown
7
+ var DEBUG_TESTS = process.env.DEBUG_TESTS === 'true';
8
+
5
9
  function run(cmd, opts) {
6
10
  opts = _.merge({
7
- pipe: true,
8
- cwd: undefined,
9
- callback: function(child) {
10
- // Since we return promise, we need to provide
11
- // this callback if one wants to access the child
12
- // process reference
13
- // Called immediately after successful child process
14
- // spawn
15
- }
11
+ // If set to a function, it will be called for each line
12
+ // written to the child process's stdout as (line, child)
13
+ onOutputLine: undefined,
16
14
  }, opts);
17
15
 
18
16
  var child;
19
17
  var parts = shellQuote.parse(cmd);
20
18
  try {
21
19
  child = childProcess.spawn(_.head(parts), _.tail(parts), {
22
- cwd: opts.cwd,
23
- stdio: opts.pipe ? "inherit" : null
20
+ stdio: DEBUG_TESTS && !opts.onOutputLine ? 'inherit': null,
24
21
  });
25
22
  } catch (e) {
26
23
  return Promise.reject(e);
27
24
  }
28
- opts.callback(child);
25
+
26
+ if (opts.onOutputLine) {
27
+ readLines(child, opts.onOutputLine);
28
+ }
29
29
 
30
30
  return new Promise(function(resolve, reject) {
31
31
  child.on('error', function(err) {
@@ -38,6 +38,21 @@ function run(cmd, opts) {
38
38
  });
39
39
  }
40
40
 
41
+ function readLines(child, callback) {
42
+ var rl = readline.createInterface({
43
+ input: child.stdout,
44
+ output: null
45
+ });
46
+
47
+ rl.on('line', function(line) {
48
+ if (DEBUG_TESTS) {
49
+ console.log(line);
50
+ }
51
+
52
+ callback(line, child)
53
+ });
54
+ }
55
+
41
56
  module.exports = {
42
57
  run: run
43
58
  };
package/.npmignore DELETED
@@ -1,31 +0,0 @@
1
- # Logs
2
- logs
3
- *.log
4
-
5
- # Runtime data
6
- pids
7
- *.pid
8
- *.seed
9
-
10
- # Directory for instrumented libs generated by jscoverage/JSCover
11
- lib-cov
12
-
13
- # Coverage directory used by tools like istanbul
14
- coverage
15
-
16
- # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17
- .grunt
18
-
19
- # Compiled binary addons (http://nodejs.org/api/addons.html)
20
- build/Release
21
-
22
- # Dependency directory
23
- # Deployed apps should consider commenting this line out:
24
- # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
25
- node_modules
26
-
27
- # OS X
28
- .DS_Store
29
-
30
- # Vagrant directory
31
- .vagrant