concurrently 3.4.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/README.md +12 -16
- package/package.json +1 -1
- package/src/main.js +93 -23
- package/test/test-functional.js +146 -58
- package/test/utils.js +27 -12
- package/.eslintrc +0 -31
- package/tst.js +0 -1
package/README.md
CHANGED
|
@@ -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,17 +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).
|
package/package.json
CHANGED
package/src/main.js
CHANGED
|
@@ -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
|
|
|
@@ -81,7 +92,7 @@ function parseArgs() {
|
|
|
81
92
|
'-p, --prefix <prefix>',
|
|
82
93
|
'prefix used in logging for each process.\n' +
|
|
83
94
|
'Possible values: index, pid, time, command, name, none, or a template. Default: ' +
|
|
84
|
-
|
|
95
|
+
'index or name (when --names is set). Example template: "{time}-{pid}"\n'
|
|
85
96
|
)
|
|
86
97
|
.option(
|
|
87
98
|
'-n, --names <names>',
|
|
@@ -126,6 +137,21 @@ function parseArgs() {
|
|
|
126
137
|
'The option can be used to shorten long commands.\n' +
|
|
127
138
|
'Works only if prefix is set to "command". Default: ' +
|
|
128
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'
|
|
129
155
|
);
|
|
130
156
|
|
|
131
157
|
program.on('--help', function() {
|
|
@@ -154,7 +180,7 @@ function parseArgs() {
|
|
|
154
180
|
'',
|
|
155
181
|
' - Custom names and colored prefixes',
|
|
156
182
|
'',
|
|
157
|
-
' $ concurrently --
|
|
183
|
+
' $ concurrently --names "HTTP,WATCH" -c "bgBlue.bold,bgMagenta.bold" "http-server" "npm run watch"',
|
|
158
184
|
''
|
|
159
185
|
];
|
|
160
186
|
console.log(help.join('\n'));
|
|
@@ -172,6 +198,12 @@ function mergeDefaultsWithArgs(config) {
|
|
|
172
198
|
return _.merge(config, program);
|
|
173
199
|
}
|
|
174
200
|
|
|
201
|
+
function applyDynamicDefaults(config) {
|
|
202
|
+
if (!config.prefix) {
|
|
203
|
+
config.prefix = config.names ? 'name' : 'index';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
175
207
|
function stripCmdQuotes(cmd) {
|
|
176
208
|
// Removes the quotes surrounding a command.
|
|
177
209
|
if (cmd[0] === '"' || cmd[0] === '\'') {
|
|
@@ -198,14 +230,7 @@ function run(commands) {
|
|
|
198
230
|
spawnOpts.env = Object.assign({FORCE_COLOR: supportsColor.level}, process.env)
|
|
199
231
|
}
|
|
200
232
|
|
|
201
|
-
var child;
|
|
202
|
-
try {
|
|
203
|
-
child = spawn(cmd, spawnOpts);
|
|
204
|
-
} catch (e) {
|
|
205
|
-
logError('', chalk.gray.dim, 'Error occured when executing command: ' + cmd);
|
|
206
|
-
logError('', chalk.gray.dim, e.stack);
|
|
207
|
-
process.exit(1);
|
|
208
|
-
}
|
|
233
|
+
var child = spawnChild(cmd, spawnOpts);
|
|
209
234
|
|
|
210
235
|
if (index < prefixColors.length) {
|
|
211
236
|
var prefixColorPath = prefixColors[index];
|
|
@@ -217,13 +242,41 @@ function run(commands) {
|
|
|
217
242
|
command: cmd,
|
|
218
243
|
index: index,
|
|
219
244
|
name: name,
|
|
245
|
+
options: spawnOpts,
|
|
246
|
+
restartTries: config.restartTries,
|
|
220
247
|
prefixColor: lastPrefixColor
|
|
221
248
|
};
|
|
222
249
|
return child;
|
|
223
250
|
});
|
|
224
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) {
|
|
225
278
|
// Transform all process events to rx streams
|
|
226
|
-
|
|
279
|
+
return _.map(children, function(child) {
|
|
227
280
|
var childStreams = {
|
|
228
281
|
error: Rx.Node.fromEvent(child, 'error'),
|
|
229
282
|
close: Rx.Node.fromEvent(child, 'close')
|
|
@@ -241,21 +294,15 @@ function run(commands) {
|
|
|
241
294
|
return memo;
|
|
242
295
|
}, {});
|
|
243
296
|
});
|
|
297
|
+
}
|
|
244
298
|
|
|
299
|
+
function handleChildEvents(streams, children, childrenInfo) {
|
|
245
300
|
handleClose(streams, children, childrenInfo);
|
|
246
301
|
handleError(streams, childrenInfo);
|
|
247
302
|
if (!config.raw) {
|
|
248
303
|
handleOutput(streams, childrenInfo, 'stdout');
|
|
249
304
|
handleOutput(streams, childrenInfo, 'stderr');
|
|
250
305
|
}
|
|
251
|
-
|
|
252
|
-
['SIGINT', 'SIGTERM'].forEach(function(signal) {
|
|
253
|
-
process.on(signal, function() {
|
|
254
|
-
children.forEach(function(child) {
|
|
255
|
-
treeKill(child.pid, signal);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
306
|
}
|
|
260
307
|
|
|
261
308
|
function handleOutput(streams, childrenInfo, source) {
|
|
@@ -283,14 +330,20 @@ function handleClose(streams, children, childrenInfo) {
|
|
|
283
330
|
exitCodes.push(exitCode);
|
|
284
331
|
|
|
285
332
|
var prefix = getPrefix(childrenInfo, event.child);
|
|
286
|
-
var
|
|
287
|
-
var
|
|
333
|
+
var childInfo = childrenInfo[event.child.pid];
|
|
334
|
+
var prefixColor = childInfo.prefixColor;
|
|
335
|
+
var command = childInfo.command;
|
|
288
336
|
logEvent(prefix, prefixColor, command + ' exited with code ' + exitCode);
|
|
289
337
|
|
|
290
338
|
aliveChildren = _.filter(aliveChildren, function(child) {
|
|
291
339
|
return child.pid !== event.child.pid;
|
|
292
340
|
});
|
|
293
341
|
|
|
342
|
+
if (nonSuccess && config.allowRestart && childInfo.restartTries--) {
|
|
343
|
+
respawnChild(event, childrenInfo);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
294
347
|
if (aliveChildren.length === 0) {
|
|
295
348
|
exit(exitCodes);
|
|
296
349
|
}
|
|
@@ -306,6 +359,23 @@ function handleClose(streams, children, childrenInfo) {
|
|
|
306
359
|
});
|
|
307
360
|
}
|
|
308
361
|
|
|
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);
|
|
369
|
+
|
|
370
|
+
childrenInfo[newChild.pid] = childrenInfo[event.child.pid];
|
|
371
|
+
delete childrenInfo[event.child.pid];
|
|
372
|
+
|
|
373
|
+
var children = [newChild];
|
|
374
|
+
var streams = toStreams(children);
|
|
375
|
+
handleChildEvents(streams, children, childrenInfo);
|
|
376
|
+
}, config.restartAfter);
|
|
377
|
+
}
|
|
378
|
+
|
|
309
379
|
function killOtherProcesses(processes) {
|
|
310
380
|
logEvent('--> ', chalk.gray.dim, 'Sending SIGTERM to other processes..');
|
|
311
381
|
|
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,35 +25,35 @@ 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
|
-
return run('node ./src/main.js --kill-others "sleep 1" "echo test" "sleep 0.1 && nosuchcmd"'
|
|
49
|
+
return run('node ./src/main.js --kill-others "sleep 1" "echo test" "sleep 0.1 && nosuchcmd"')
|
|
50
50
|
.then(function(exitCode) {
|
|
51
51
|
assert.notStrictEqual(exitCode, 0);
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
54
|
|
|
55
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"'
|
|
56
|
+
return run('node ./src/main.js --kill-others-on-fail "sleep 1" "exit 1" "sleep 1"')
|
|
57
57
|
.then(function(exitCode) {
|
|
58
58
|
assert.notStrictEqual(exitCode, 0);
|
|
59
59
|
});
|
|
@@ -65,27 +65,15 @@ describe('concurrently', function() {
|
|
|
65
65
|
var sigtermInOutput = false;
|
|
66
66
|
|
|
67
67
|
run('node ./src/main.js --kill-others-on-fail "echo killTest1" "echo killTest2" "echo killTest3"', {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
console.log(line);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (/SIGTERM/.test(line)) {
|
|
81
|
-
sigtermInOutput = true;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// waiting for exits
|
|
85
|
-
if (/killTest\d$/.test(line)) {
|
|
86
|
-
exits++;
|
|
87
|
-
}
|
|
88
|
-
});
|
|
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
|
+
}
|
|
89
77
|
}
|
|
90
78
|
}).then(function() {
|
|
91
79
|
if(sigtermInOutput) {
|
|
@@ -99,7 +87,7 @@ describe('concurrently', function() {
|
|
|
99
87
|
});
|
|
100
88
|
|
|
101
89
|
it('--success=first should return first exit code', () => {
|
|
102
|
-
return run('node ./src/main.js -k --success first "echo test" "sleep 0.1 && nosuchcmd"'
|
|
90
|
+
return run('node ./src/main.js -k --success first "echo test" "sleep 0.1 && nosuchcmd"')
|
|
103
91
|
// When killed, sleep returns null exit code
|
|
104
92
|
.then(function(exitCode) {
|
|
105
93
|
assert.strictEqual(exitCode, 0);
|
|
@@ -108,26 +96,138 @@ describe('concurrently', function() {
|
|
|
108
96
|
|
|
109
97
|
it('--success=last should return last exit code', () => {
|
|
110
98
|
// When killed, sleep returns null exit code
|
|
111
|
-
return run('node ./src/main.js -k --success last "echo test" "sleep 0.1 && nosuchcmd"'
|
|
99
|
+
return run('node ./src/main.js -k --success last "echo test" "sleep 0.1 && nosuchcmd"')
|
|
112
100
|
.then(function(exitCode) {
|
|
113
101
|
assert.notStrictEqual(exitCode, 0);
|
|
114
102
|
});
|
|
115
103
|
});
|
|
116
104
|
|
|
117
105
|
it('&& nosuchcmd should return non-zero exit code', () => {
|
|
118
|
-
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" ')
|
|
119
107
|
.then(function(exitCode) {
|
|
120
108
|
assert.strictEqual(exitCode, 1);
|
|
121
109
|
});
|
|
122
110
|
});
|
|
123
111
|
|
|
124
112
|
it('--prefix-colors should handle non-existent colors without failing', () => {
|
|
125
|
-
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"')
|
|
126
114
|
.then(function(exitCode) {
|
|
127
115
|
assert.strictEqual(exitCode, 0);
|
|
128
116
|
});
|
|
129
117
|
});
|
|
130
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
|
+
})
|
|
150
|
+
.then(function(exitCode) {
|
|
151
|
+
assert.strictEqual(exitCode, 0);
|
|
152
|
+
|
|
153
|
+
collectedLines.sort()
|
|
154
|
+
assert.deepEqual(collectedLines, [
|
|
155
|
+
'[aa] one',
|
|
156
|
+
'[bb] two'
|
|
157
|
+
])
|
|
158
|
+
});
|
|
159
|
+
});
|
|
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
|
+
|
|
131
231
|
['SIGINT', 'SIGTERM'].forEach((signal) => {
|
|
132
232
|
if (IS_WINDOWS) {
|
|
133
233
|
console.log('IS_WINDOWS=true');
|
|
@@ -149,32 +249,20 @@ describe('concurrently', function() {
|
|
|
149
249
|
}
|
|
150
250
|
|
|
151
251
|
run('node ./src/main.js "node ./test/support/signal.js" "node ./test/support/signal.js"', {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
}
|
|
158
261
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// waiting for startup
|
|
165
|
-
if (/STARTED/.test(line)) {
|
|
166
|
-
waitingStart--;
|
|
167
|
-
}
|
|
168
|
-
if (!waitingStart) {
|
|
169
|
-
// both processes are started
|
|
170
|
-
child.kill(signal);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// waiting for signal
|
|
174
|
-
if (new RegExp(signal).test(line)) {
|
|
175
|
-
waitingSignal--;
|
|
176
|
-
}
|
|
177
|
-
});
|
|
262
|
+
// waiting for signal
|
|
263
|
+
if (new RegExp(signal).test(line)) {
|
|
264
|
+
waitingSignal--;
|
|
265
|
+
}
|
|
178
266
|
}
|
|
179
267
|
}).then(function() {
|
|
180
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/.eslintrc
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"env": {
|
|
3
|
-
"browser": true,
|
|
4
|
-
"node": true
|
|
5
|
-
},
|
|
6
|
-
"globals": {},
|
|
7
|
-
"rules": {
|
|
8
|
-
"no-bitwise": 2,
|
|
9
|
-
"camelcase": 2,
|
|
10
|
-
"curly": 2,
|
|
11
|
-
"eqeqeq": 2,
|
|
12
|
-
"wrap-iife": [
|
|
13
|
-
2,
|
|
14
|
-
"any"
|
|
15
|
-
],
|
|
16
|
-
"indent": [
|
|
17
|
-
2,
|
|
18
|
-
4,
|
|
19
|
-
{
|
|
20
|
-
"SwitchCase": 1
|
|
21
|
-
}
|
|
22
|
-
],
|
|
23
|
-
"no-use-before-define": 0,
|
|
24
|
-
"new-cap": 2,
|
|
25
|
-
"no-caller": 2,
|
|
26
|
-
"quotes": [
|
|
27
|
-
2,
|
|
28
|
-
"single"
|
|
29
|
-
]
|
|
30
|
-
}
|
|
31
|
-
}
|
package/tst.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
setTimeout(() =>console.log("i win!"), 3000);
|