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 +1 -3
- package/README.md +15 -33
- package/appveyor.yml +1 -2
- package/package.json +2 -2
- package/src/main.js +138 -50
- package/test/test-functional.js +170 -37
- package/test/utils.js +27 -12
- package/.npmignore +0 -31
package/.travis.yml
CHANGED
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
|
|
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-
|
|
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
|
|
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 --
|
|
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: '
|
|
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.
|
|
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-
|
|
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
|
|
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
|
-
//
|
|
19
|
-
|
|
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: '
|
|
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
|
-
|
|
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 --
|
|
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
|
-
|
|
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
|
|
277
|
-
var
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
var
|
|
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
|
-
|
|
294
|
-
|
|
370
|
+
childrenInfo[newChild.pid] = childrenInfo[event.child.pid];
|
|
371
|
+
delete childrenInfo[event.child.pid];
|
|
295
372
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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,
|
|
495
|
+
var paddedLines = _.map(lines, function(line, index) {
|
|
417
496
|
var coloredLine = color ? colorText(line, color) : line;
|
|
418
|
-
|
|
497
|
+
if (index !== 0 && index !== (lines.length - 1)) {
|
|
498
|
+
coloredLine = coloredPrefix + coloredLine;
|
|
499
|
+
}
|
|
500
|
+
return coloredLine;
|
|
419
501
|
});
|
|
420
502
|
|
|
421
|
-
|
|
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();
|
package/test/test-functional.js
CHANGED
|
@@ -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
|
-
//
|
|
9
|
-
|
|
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'
|
|
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'
|
|
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"'
|
|
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"'
|
|
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
|
-
|
|
50
|
-
|
|
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
|
|
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
|
|
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" '
|
|
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"'
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|