concurrently 3.1.0 → 3.5.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/.travis.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  language: node_js
2
2
  node_js:
3
+ - 7
3
4
  - 6
4
5
  - 4
5
6
  before_script:
package/CONTRIBUTING.md CHANGED
@@ -22,7 +22,5 @@ You need to have *mocha* installed globally with `npm install -g mocha`.
22
22
  ## Release
23
23
 
24
24
  * Commit all changes
25
- * Run `./tools/release.js minor`, which will create new tag and publish code to GitHub and npm
25
+ * Run `./node_modules/.bin/releasor --bump minor`, which will create new tag and publish code to GitHub and npm. See https://github.com/kimmobrunfeldt/releasor for options
26
26
  * Edit GitHub release notes
27
-
28
-
package/README.md CHANGED
@@ -14,7 +14,7 @@ Like `npm run watch-js & npm run watch-less` but better.
14
14
  * Cross platform, works also in 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
 
@@ -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"
@@ -76,7 +77,7 @@ Options:
76
77
  See https://www.npmjs.com/package/chalk for more information.
77
78
  Default: "gray.dim". Example: "black.bgWhite,cyan,gray.dim"
78
79
 
79
- -t, --timestamp-format <format> specify the timestamp in moment format. Default: YYYY-MM-DD HH:mm:ss.SSS
80
+ -t, --timestamp-format <format> specify the timestamp in moment/date-fns format. Default: YYYY-MM-DD HH:mm:ss.SSS
80
81
 
81
82
  -r, --raw output only raw output of processes, disables prettifying and concurrently coloring
82
83
  -s, --success <first|last|all> Return exit code of zero or one based on the success or failure of the "first" child to terminate, the "last" child, or succeed only if "all" child processes succeed. Default: all
@@ -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,5 +1,6 @@
1
1
  environment:
2
2
  matrix:
3
+ - nodejs_version: '7'
3
4
  - nodejs_version: '6'
4
5
  - nodejs_version: '4'
5
6
  install:
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "concurrently",
3
- "version": "3.1.0",
3
+ "version": "3.5.0",
4
4
  "description": "Run commands concurrently",
5
5
  "main": "src/main.js",
6
6
  "bin": {
7
7
  "concurrent": "./src/main.js",
8
8
  "concurrently": "./src/main.js"
9
9
  },
10
+ "engines": {
11
+ "node": ">=4.0.0"
12
+ },
10
13
  "scripts": {
11
14
  "test": "mocha"
12
15
  },
@@ -29,13 +32,13 @@
29
32
  },
30
33
  "homepage": "https://github.com/kimmobrunfeldt/concurrently",
31
34
  "dependencies": {
32
- "bluebird": "2.9.6",
33
35
  "chalk": "0.5.1",
34
36
  "commander": "2.6.0",
37
+ "date-fns": "^1.23.0",
35
38
  "lodash": "^4.5.1",
36
- "moment": "^2.11.2",
37
39
  "rx": "2.3.24",
38
- "spawn-default-shell": "^1.1.0",
40
+ "spawn-command": "^0.0.2-1",
41
+ "supports-color": "^3.2.3",
39
42
  "tree-kill": "^1.1.0"
40
43
  },
41
44
  "devDependencies": {
package/src/main.js CHANGED
@@ -2,21 +2,21 @@
2
2
 
3
3
  var Rx = require('rx');
4
4
  var path = require('path');
5
- var Promise = require('bluebird');
6
- var moment = require('moment');
5
+ var formatDate = require('date-fns/format');
7
6
  var program = require('commander');
8
7
  var _ = require('lodash');
9
8
  var treeKill = require('tree-kill');
10
9
  var chalk = require('chalk');
11
- var defaultShell = require('spawn-default-shell');
10
+ var spawn = require('spawn-command');
11
+ var supportsColor = require('supports-color');
12
12
  var IS_WINDOWS = /^win/.test(process.platform);
13
13
 
14
14
  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: '',
@@ -35,7 +35,7 @@ var config = {
35
35
  // Comma-separated list of chalk color paths to use on prefixes.
36
36
  prefixColors: 'gray.dim',
37
37
 
38
- // moment format
38
+ // moment/date-fns format
39
39
  timestampFormat: 'YYYY-MM-DD HH:mm:ss.SSS',
40
40
 
41
41
  // How many characters to display from start of command in prefix if
@@ -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>',
@@ -101,7 +116,7 @@ function parseArgs() {
101
116
  )
102
117
  .option(
103
118
  '-t, --timestamp-format <format>',
104
- 'specify the timestamp in moment format. Default: ' +
119
+ 'specify the timestamp in moment/date-fns format. Default: ' +
105
120
  config.timestampFormat + '\n'
106
121
  )
107
122
  .option(
@@ -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] === '\'') {
@@ -186,19 +226,15 @@ function run(commands) {
186
226
  if (IS_WINDOWS) {
187
227
  spawnOpts.detached = false;
188
228
  }
189
-
190
- var child;
191
- try {
192
- child = defaultShell.spawn(cmd, spawnOpts);
193
- } catch (e) {
194
- logError('', chalk.gray.dim, 'Error occured when executing command: ' + cmd);
195
- logError('', chalk.gray.dim, e.stack);
196
- process.exit(1);
229
+ if (supportsColor) {
230
+ spawnOpts.env = Object.assign({FORCE_COLOR: supportsColor.level}, process.env)
197
231
  }
198
232
 
233
+ var child = spawnChild(cmd, spawnOpts);
234
+
199
235
  if (index < prefixColors.length) {
200
236
  var prefixColorPath = prefixColors[index];
201
- lastPrefixColor = _.get(chalk, prefixColorPath);
237
+ lastPrefixColor = _.get(chalk, prefixColorPath, chalk.gray.dim);
202
238
  }
203
239
 
204
240
  var name = index < names.length ? names[index] : '';
@@ -206,13 +242,41 @@ function run(commands) {
206
242
  command: cmd,
207
243
  index: index,
208
244
  name: name,
245
+ options: spawnOpts,
246
+ restartTries: config.restartTries,
209
247
  prefixColor: lastPrefixColor
210
248
  };
211
249
  return child;
212
250
  });
213
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) {
214
278
  // Transform all process events to rx streams
215
- var streams = _.map(children, function(child) {
279
+ return _.map(children, function(child) {
216
280
  var childStreams = {
217
281
  error: Rx.Node.fromEvent(child, 'error'),
218
282
  close: Rx.Node.fromEvent(child, 'close')
@@ -230,21 +294,15 @@ function run(commands) {
230
294
  return memo;
231
295
  }, {});
232
296
  });
297
+ }
233
298
 
299
+ function handleChildEvents(streams, children, childrenInfo) {
234
300
  handleClose(streams, children, childrenInfo);
235
301
  handleError(streams, childrenInfo);
236
302
  if (!config.raw) {
237
303
  handleOutput(streams, childrenInfo, 'stdout');
238
304
  handleOutput(streams, childrenInfo, 'stderr');
239
305
  }
240
-
241
- ['SIGINT', 'SIGTERM'].forEach(function(signal) {
242
- process.on(signal, function() {
243
- children.forEach(function(child) {
244
- child.kill(signal);
245
- });
246
- });
247
- });
248
306
  }
249
307
 
250
308
  function handleOutput(streams, childrenInfo, source) {
@@ -263,39 +321,68 @@ function handleClose(streams, children, childrenInfo) {
263
321
  var exitCodes = [];
264
322
  var closeStreams = _.map(streams, 'close');
265
323
  var closeStream = Rx.Observable.merge.apply(this, closeStreams);
324
+ var othersKilled = false
266
325
 
267
326
  // TODO: Is it possible that amount of close events !== count of spawned?
268
327
  closeStream.subscribe(function(event) {
269
328
  var exitCode = event.data;
329
+ var nonSuccess = exitCode !== 0;
270
330
  exitCodes.push(exitCode);
271
331
 
272
332
  var prefix = getPrefix(childrenInfo, event.child);
273
- var prefixColor = childrenInfo[event.child.pid].prefixColor;
274
- var command = childrenInfo[event.child.pid].command;
333
+ var childInfo = childrenInfo[event.child.pid];
334
+ var prefixColor = childInfo.prefixColor;
335
+ var command = childInfo.command;
275
336
  logEvent(prefix, prefixColor, command + ' exited with code ' + exitCode);
276
337
 
277
338
  aliveChildren = _.filter(aliveChildren, function(child) {
278
339
  return child.pid !== event.child.pid;
279
340
  });
280
341
 
342
+ if (nonSuccess && config.allowRestart && childInfo.restartTries--) {
343
+ respawnChild(event, childrenInfo);
344
+ return;
345
+ }
346
+
281
347
  if (aliveChildren.length === 0) {
282
348
  exit(exitCodes);
283
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
+ }
284
359
  });
360
+ }
285
361
 
286
- if (config.killOthers) {
287
- // Give other processes some time to stop cleanly before killing them
288
- 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);
289
369
 
290
- delayedExit.subscribe(function() {
291
- logEvent('--> ', chalk.gray.dim, 'Sending SIGTERM to other processes..');
370
+ childrenInfo[newChild.pid] = childrenInfo[event.child.pid];
371
+ delete childrenInfo[event.child.pid];
292
372
 
293
- // Send SIGTERM to alive children
294
- _.each(aliveChildren, function(child) {
295
- treeKill(child.pid, 'SIGTERM');
296
- });
297
- });
298
- }
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
+ });
299
386
  }
300
387
 
301
388
  function exit(childExitCodes) {
@@ -354,7 +441,7 @@ function getPrefixes(childrenInfo, child) {
354
441
  prefixes.pid = child.pid;
355
442
  prefixes.index = childrenInfo[child.pid].index;
356
443
  prefixes.name = childrenInfo[child.pid].name;
357
- prefixes.time = moment().format(config.timestampFormat);
444
+ prefixes.time = formatDate(Date.now(), config.timestampFormat);
358
445
 
359
446
  var command = childrenInfo[child.pid].command;
360
447
  prefixes.command = shortenText(command, config.prefixLength);