concurrently 0.1.1 → 2.2.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 +40 -26
- package/package.json +5 -3
- package/src/main.js +214 -77
- package/test/test-functional.js +22 -2
- package/test.js +7 -0
- package/tools/release.js +1 -1
- package/src/lodash-mixins.js +0 -11
package/README.md
CHANGED
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://travis-ci.org/kimmobrunfeldt/concurrently)
|
|
4
4
|
|
|
5
|
-
**Version:
|
|
5
|
+
**Version: 2.2.0** ([*previous stable*](https://github.com/kimmobrunfeldt/concurrently/tree/2.1.0))
|
|
6
6
|
|
|
7
7
|
Run multiple commands concurrently.
|
|
8
8
|
Like `npm run watch-js & npm run watch-less` but better.
|
|
9
9
|
|
|
10
10
|

|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
**Features:**
|
|
13
|
+
|
|
14
|
+
* Cross platform, works also in Windows
|
|
15
|
+
* Output is easy to follow with prefixes
|
|
16
|
+
* With `--kill-others` switch, all commands are killed if one dies
|
|
17
|
+
|
|
13
18
|
|
|
14
19
|
## Install
|
|
15
20
|
|
|
@@ -23,47 +28,56 @@ npm install -g concurrently
|
|
|
23
28
|
|
|
24
29
|
Remember to surround separate commands with quotes, like this:
|
|
25
30
|
```bash
|
|
26
|
-
|
|
31
|
+
concurrently "command1 arg" "command2 arg"
|
|
27
32
|
```
|
|
28
33
|
|
|
29
|
-
Otherwise **
|
|
34
|
+
Otherwise **concurrently** would try to run 4 separate commands:
|
|
30
35
|
`command1`, `arg`, `command2`, `arg`.
|
|
31
36
|
|
|
32
37
|
Help:
|
|
33
38
|
|
|
34
39
|
```
|
|
35
|
-
|
|
40
|
+
Usage: concurrently [options] <command ...>
|
|
41
|
+
|
|
42
|
+
Options:
|
|
43
|
+
|
|
44
|
+
-h, --help output usage information
|
|
45
|
+
-V, --version output the version number
|
|
46
|
+
-k, --kill-others kill other processes if one exits or dies
|
|
47
|
+
--no-color disable colors from logging
|
|
48
|
+
--names names different processes, i.e --name "web,api,hot-server" to be used in the prefix switch
|
|
49
|
+
-p, --prefix <prefix> prefix used in logging for each process.
|
|
50
|
+
Possible values: index, pid, time, command, name, none or a template. Default: index. Example template "{time}-{pid}"
|
|
51
|
+
|
|
52
|
+
-tf, --timestamp-format <format> specify the timestamp in moment format. Default: YYYY-MM-DD HH:mm:ss.SSS
|
|
53
|
+
|
|
54
|
+
-r, --raw output only raw output of processes, disables prettifying and concurrently coloring
|
|
55
|
+
-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
|
|
36
56
|
|
|
37
|
-
|
|
57
|
+
-l, --prefix-length <length> limit how many characters of the command is displayed in prefix.
|
|
58
|
+
The option can be used to shorten long commands.
|
|
59
|
+
Works only if prefix is set to "command". Default: 10
|
|
38
60
|
|
|
39
|
-
-h, --help output usage information
|
|
40
|
-
-V, --version output the version number
|
|
41
|
-
-k, --kill-others kill other processes if one exits or dies
|
|
42
|
-
--no-color disable colors from logging
|
|
43
|
-
-p, --prefix <prefix> prefix used in logging for each process.
|
|
44
|
-
Possible values: index, pid, command, none. Default: index
|
|
45
61
|
|
|
46
|
-
|
|
47
|
-
-l, --prefix-length <length> limit how many characters of the command is displayed in prefix.
|
|
48
|
-
The option can be used to shorten long commands.
|
|
49
|
-
Works only if prefix is set to "command". Default: 10
|
|
62
|
+
Examples:
|
|
50
63
|
|
|
64
|
+
- Kill other processes if one exits or dies
|
|
51
65
|
|
|
52
|
-
|
|
66
|
+
$ concurrently --kill-others "grunt watch" "http-server"
|
|
53
67
|
|
|
54
|
-
|
|
68
|
+
- Output nothing more than stdout+stderr of child processes
|
|
55
69
|
|
|
56
|
-
|
|
70
|
+
$ concurrently --raw "npm run watch-less" "npm run watch-js"
|
|
57
71
|
|
|
58
|
-
|
|
72
|
+
- Normal output but without colors e.g. when logging to file
|
|
59
73
|
|
|
60
|
-
|
|
74
|
+
$ concurrently --no-color "grunt watch" "http-server" > log
|
|
61
75
|
|
|
62
|
-
|
|
76
|
+
- Custom prefix
|
|
63
77
|
|
|
64
|
-
|
|
78
|
+
$ concurrently --prefix "{time}-{pid}" "grunt watch" "http-server"
|
|
65
79
|
|
|
66
|
-
|
|
80
|
+
For more details, visit https://github.com/kimmobrunfeldt/concurrently
|
|
67
81
|
```
|
|
68
82
|
|
|
69
83
|
## FAQ
|
|
@@ -86,7 +100,7 @@ Help:
|
|
|
86
100
|
|
|
87
101
|
I like [task automation with npm](http://substack.net/task_automation_with_npm_run)
|
|
88
102
|
but the usual way to run multiple commands concurrently is
|
|
89
|
-
|
|
103
|
+
`npm run watch-js & npm run watch-css`. That's fine but it's hard to keep
|
|
90
104
|
on track of different outputs. Also if one process fails, others still keep running
|
|
91
105
|
and you won't even notice the difference.
|
|
92
106
|
|
|
@@ -100,7 +114,7 @@ Previously I thought this could fix some problems I had with watching scripts an
|
|
|
100
114
|
> When running watch or serve tasks, I'd recommend to use `--kill-others` option:
|
|
101
115
|
>
|
|
102
116
|
> ```bash
|
|
103
|
-
>
|
|
117
|
+
> concurrently --kill-others "npm run watch-js" "npm run watch-less"
|
|
104
118
|
> ```
|
|
105
119
|
>
|
|
106
120
|
> That way, if for some reason e.g. your `watch-less` died, you would notice it easier.
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "concurrently",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Run commands concurrently",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"concurrent": "./src/main.js"
|
|
7
|
+
"concurrent": "./src/main.js",
|
|
8
|
+
"concurrently": "./src/main.js"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
11
|
"test": "mocha"
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
"chalk": "0.5.1",
|
|
33
34
|
"commander": "2.6.0",
|
|
34
35
|
"cross-spawn": "^0.2.9",
|
|
35
|
-
"lodash": "
|
|
36
|
+
"lodash": "^4.5.1",
|
|
37
|
+
"moment": "^2.11.2",
|
|
36
38
|
"rx": "2.3.24"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
package/src/main.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
var Rx = require('rx');
|
|
4
|
+
var path = require('path');
|
|
4
5
|
var Promise = require('bluebird');
|
|
6
|
+
var moment = require('moment');
|
|
5
7
|
var program = require('commander');
|
|
6
8
|
var _ = require('lodash');
|
|
7
9
|
var chalk = require('chalk');
|
|
8
10
|
var spawn = Promise.promisifyAll(require('cross-spawn'));
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
var isWindows = /^win/.test(process.platform);
|
|
11
12
|
|
|
12
13
|
var config = {
|
|
13
14
|
// Kill other processes if one dies
|
|
@@ -16,10 +17,26 @@ var config = {
|
|
|
16
17
|
// How much in ms we wait before killing other processes
|
|
17
18
|
killDelay: 1000,
|
|
18
19
|
|
|
20
|
+
// Return success or failure of the 'first' child to terminate, the 'last' child,
|
|
21
|
+
// or succeed only if 'all' children succeed
|
|
22
|
+
success: 'all',
|
|
23
|
+
|
|
19
24
|
// Prefix logging with pid
|
|
20
|
-
// Possible values: 'pid', 'none', 'command', 'index'
|
|
25
|
+
// Possible values: 'pid', 'none', 'time', 'command', 'index', 'name'
|
|
21
26
|
prefix: 'index',
|
|
22
27
|
|
|
28
|
+
// List of custom names to be used in prefix template
|
|
29
|
+
names: '',
|
|
30
|
+
|
|
31
|
+
// What to split the list of custom names on
|
|
32
|
+
nameSeparator: ',',
|
|
33
|
+
|
|
34
|
+
// Comma-separated list of chalk color paths to use on prefixes.
|
|
35
|
+
prefixColors: 'gray.dim',
|
|
36
|
+
|
|
37
|
+
// moment format
|
|
38
|
+
timestampFormat: 'YYYY-MM-DD HH:mm:ss.SSS',
|
|
39
|
+
|
|
23
40
|
// How many characters to display from start of command in prefix if
|
|
24
41
|
// command is defined. Note that also '..' will be added in the middle
|
|
25
42
|
prefixLength: 10,
|
|
@@ -32,6 +49,12 @@ var config = {
|
|
|
32
49
|
};
|
|
33
50
|
|
|
34
51
|
function main() {
|
|
52
|
+
var firstBase = path.basename(process.argv[0]);
|
|
53
|
+
var secondBase = path.basename(process.argv[1]);
|
|
54
|
+
if (firstBase === 'concurrent' || secondBase === 'concurrent') {
|
|
55
|
+
console.error('"concurrent" command is deprecated, use "concurrently" instead.\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
35
58
|
parseArgs();
|
|
36
59
|
config = mergeDefaultsWithArgs(config);
|
|
37
60
|
run(program.args);
|
|
@@ -52,13 +75,45 @@ function parseArgs() {
|
|
|
52
75
|
.option(
|
|
53
76
|
'-p, --prefix <prefix>',
|
|
54
77
|
'prefix used in logging for each process.\n' +
|
|
55
|
-
'Possible values: index, pid, command, none. Default: ' +
|
|
56
|
-
config.prefix + '\n'
|
|
78
|
+
'Possible values: index, pid, time, command, name, none, or a template. Default: ' +
|
|
79
|
+
config.prefix + '. Example template: "{time}-{pid}"\n'
|
|
80
|
+
)
|
|
81
|
+
.option(
|
|
82
|
+
'-n, --names <names>',
|
|
83
|
+
'List of custom names to be used in prefix template.\n' +
|
|
84
|
+
'Example names: "main,browser,server"\n'
|
|
85
|
+
)
|
|
86
|
+
.option(
|
|
87
|
+
'--name-separator <char>',
|
|
88
|
+
'The character to split <names> on.\n' +
|
|
89
|
+
'Default: "' + config.nameSeparator + '". Example usage: ' +
|
|
90
|
+
'concurrently -n "styles,scripts|server" --name-separator "|" <command ...>\n'
|
|
91
|
+
)
|
|
92
|
+
.option(
|
|
93
|
+
'-c, --prefix-colors <colors>',
|
|
94
|
+
'Comma-separated list of chalk colors to use on prefixes. If there are more commands than colors, the last color will be repeated.\n' +
|
|
95
|
+
'Available modifiers: reset, bold, dim, italic, underline, inverse, hidden, strikethrough\n' +
|
|
96
|
+
'Available colors: black, red, green, yellow, blue, magenta, cyan, white, gray\n' +
|
|
97
|
+
'Available background colors: bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite\n' +
|
|
98
|
+
'See https://www.npmjs.com/package/chalk for more information.\n' +
|
|
99
|
+
'Default: "' + config.prefixColors + '". Example: "black.bgWhite,cyan,gray.dim"\n'
|
|
100
|
+
)
|
|
101
|
+
.option(
|
|
102
|
+
'-t, --timestamp-format <format>',
|
|
103
|
+
'specify the timestamp in moment format. Default: ' +
|
|
104
|
+
config.timestampFormat + '\n'
|
|
57
105
|
)
|
|
58
106
|
.option(
|
|
59
107
|
'-r, --raw',
|
|
60
108
|
'output only raw output of processes,' +
|
|
61
|
-
' disables prettifying and
|
|
109
|
+
' disables prettifying and concurrently coloring'
|
|
110
|
+
)
|
|
111
|
+
.option(
|
|
112
|
+
'-s, --success <first|last|all>',
|
|
113
|
+
'Return exit code of zero or one based on the success or failure ' +
|
|
114
|
+
'of the "first" child to terminate, the "last" child, or succeed ' +
|
|
115
|
+
' only if "all" child processes succeed. Default: ' +
|
|
116
|
+
config.success + '\n'
|
|
62
117
|
)
|
|
63
118
|
.option(
|
|
64
119
|
'-l, --prefix-length <length>',
|
|
@@ -74,15 +129,19 @@ function parseArgs() {
|
|
|
74
129
|
'',
|
|
75
130
|
' - Kill other processes if one exits or dies',
|
|
76
131
|
'',
|
|
77
|
-
' $
|
|
132
|
+
' $ concurrently --kill-others "grunt watch" "http-server"',
|
|
78
133
|
'',
|
|
79
134
|
' - Output nothing more than stdout+stderr of child processes',
|
|
80
135
|
'',
|
|
81
|
-
' $
|
|
136
|
+
' $ concurrently --raw "npm run watch-less" "npm run watch-js"',
|
|
82
137
|
'',
|
|
83
138
|
' - Normal output but without colors e.g. when logging to file',
|
|
84
139
|
'',
|
|
85
|
-
' $
|
|
140
|
+
' $ concurrently --no-color "grunt watch" "http-server" > log',
|
|
141
|
+
'',
|
|
142
|
+
' - Custom prefix',
|
|
143
|
+
'',
|
|
144
|
+
' $ concurrently --prefix "{time}-{pid}" "grunt watch" "http-server"',
|
|
86
145
|
''
|
|
87
146
|
];
|
|
88
147
|
console.log(help.join('\n'));
|
|
@@ -100,80 +159,127 @@ function mergeDefaultsWithArgs(config) {
|
|
|
100
159
|
return _.merge(config, program);
|
|
101
160
|
}
|
|
102
161
|
|
|
162
|
+
function stripCmdQuotes(cmd) {
|
|
163
|
+
// Removes the quotes surrounding a command.
|
|
164
|
+
if (cmd[0] === '"' || cmd[0] === '\'') {
|
|
165
|
+
return cmd.substr(1, cmd.length - 2);
|
|
166
|
+
} else {
|
|
167
|
+
return cmd;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function separateCmdArgs(cmd) {
|
|
172
|
+
// We're splitting up the command into space-separated parts.
|
|
173
|
+
// The first item is the command, all remaining items are the
|
|
174
|
+
// arguments. To permit commands with spaces in the name
|
|
175
|
+
// (or directory name), double slashes is a usable escape sequence.
|
|
176
|
+
var escape = cmd.search('\\\s'),
|
|
177
|
+
divide = cmd.search(/[^\\]\s/),
|
|
178
|
+
path, args, parts;
|
|
179
|
+
|
|
180
|
+
if (escape === -1) {
|
|
181
|
+
// Not an escaped path. Most common case.
|
|
182
|
+
parts = cmd.split(' ');
|
|
183
|
+
} else if (escape > -1 && divide === -1) {
|
|
184
|
+
// Escaped path without arguments.
|
|
185
|
+
parts = [cmd.replace('\\ ', ' ')];
|
|
186
|
+
} else {
|
|
187
|
+
// Escaped path with arguments.
|
|
188
|
+
path = cmd.substr(0, divide + 1).replace('\\ ', ' ');
|
|
189
|
+
args = cmd.substr(divide + 1).split(' ').filter(function(part) {
|
|
190
|
+
return part.trim() != '';
|
|
191
|
+
});
|
|
192
|
+
parts = [path].concat(args);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Parts contains the command as the first item and any arguments
|
|
196
|
+
// as subsequent items.
|
|
197
|
+
return parts;
|
|
198
|
+
}
|
|
199
|
+
|
|
103
200
|
function run(commands) {
|
|
104
201
|
var childrenInfo = {};
|
|
202
|
+
var lastPrefixColor = _.get(chalk, chalk.gray.dim);
|
|
203
|
+
var prefixColors = config.prefixColors.split(',');
|
|
204
|
+
var names = config.names.split(config.nameSeparator);
|
|
105
205
|
var children = _.map(commands, function(cmd, index) {
|
|
106
|
-
|
|
206
|
+
// Remove quotes.
|
|
207
|
+
cmd = stripCmdQuotes(cmd);
|
|
208
|
+
|
|
209
|
+
// Split the command up in the command path and its arguments.
|
|
210
|
+
var parts = separateCmdArgs(cmd);
|
|
211
|
+
|
|
212
|
+
var spawnOpts = config.raw ? {stdio: 'inherit'} : {};
|
|
213
|
+
if (isWindows) {
|
|
214
|
+
spawnOpts.detached = false;
|
|
215
|
+
}
|
|
107
216
|
var child;
|
|
108
217
|
try {
|
|
109
|
-
child = spawn(_.head(parts), _.tail(parts));
|
|
218
|
+
child = spawn(_.head(parts), _.tail(parts), spawnOpts);
|
|
110
219
|
} catch (e) {
|
|
111
|
-
logError('', 'Error occured when executing command: ' + cmd);
|
|
112
|
-
logError('', e.stack);
|
|
220
|
+
logError('', chalk.gray.dim, 'Error occured when executing command: ' + cmd);
|
|
221
|
+
logError('', chalk.gray.dim, e.stack);
|
|
113
222
|
process.exit(1);
|
|
114
223
|
}
|
|
115
224
|
|
|
225
|
+
if (index < prefixColors.length) {
|
|
226
|
+
var prefixColorPath = prefixColors[index];
|
|
227
|
+
lastPrefixColor = _.get(chalk, prefixColorPath);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
var name = index < names.length ? names[index] : '';
|
|
116
231
|
childrenInfo[child.pid] = {
|
|
117
232
|
command: cmd,
|
|
118
|
-
index: index
|
|
233
|
+
index: index,
|
|
234
|
+
name: name,
|
|
235
|
+
prefixColor: lastPrefixColor
|
|
119
236
|
};
|
|
120
237
|
return child;
|
|
121
238
|
});
|
|
122
239
|
|
|
123
240
|
// Transform all process events to rx streams
|
|
124
241
|
var streams = _.map(children, function(child) {
|
|
125
|
-
var
|
|
126
|
-
Rx.Node.
|
|
127
|
-
Rx.Node.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
242
|
+
var childStreams = {
|
|
243
|
+
error: Rx.Node.fromEvent(child, 'error'),
|
|
244
|
+
close: Rx.Node.fromEvent(child, 'close')
|
|
245
|
+
};
|
|
246
|
+
if (!config.raw) {
|
|
247
|
+
childStreams.stdout = Rx.Node.fromReadableStream(child.stdout);
|
|
248
|
+
childStreams.stderr = Rx.Node.fromReadableStream(child.stderr);
|
|
249
|
+
}
|
|
131
250
|
|
|
132
|
-
|
|
133
|
-
|
|
251
|
+
return _.reduce(childStreams, function(memo, stream, key) {
|
|
252
|
+
memo[key] = stream.map(function(data) {
|
|
134
253
|
return {child: child, data: data};
|
|
135
254
|
});
|
|
136
|
-
});
|
|
137
255
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
stderr: mappedStreams[1],
|
|
141
|
-
error: mappedStreams[2],
|
|
142
|
-
close: mappedStreams[3]
|
|
143
|
-
};
|
|
256
|
+
return memo;
|
|
257
|
+
}, {});
|
|
144
258
|
});
|
|
145
259
|
|
|
146
|
-
handleStdout(streams, childrenInfo);
|
|
147
|
-
handleStderr(streams, childrenInfo);
|
|
148
260
|
handleClose(streams, children, childrenInfo);
|
|
149
261
|
handleError(streams, childrenInfo);
|
|
262
|
+
if (!config.raw) {
|
|
263
|
+
handleOutput(streams, childrenInfo, 'stdout');
|
|
264
|
+
handleOutput(streams, childrenInfo, 'stderr');
|
|
265
|
+
}
|
|
150
266
|
}
|
|
151
267
|
|
|
152
|
-
function
|
|
153
|
-
var
|
|
154
|
-
var
|
|
155
|
-
|
|
156
|
-
stdoutStream.subscribe(function(event) {
|
|
157
|
-
var prefix = getPrefix(childrenInfo, event.child);
|
|
158
|
-
log(prefix, event.data.toString());
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function handleStderr(streams, childrenInfo) {
|
|
163
|
-
var stderrStreams = _.pluck(streams, 'stderr');
|
|
164
|
-
var stderrStream = Rx.Observable.merge.apply(this, stderrStreams);
|
|
268
|
+
function handleOutput(streams, childrenInfo, source) {
|
|
269
|
+
var sourceStreams = _.map(streams, source);
|
|
270
|
+
var combinedSourceStream = Rx.Observable.merge.apply(this, sourceStreams);
|
|
165
271
|
|
|
166
|
-
|
|
272
|
+
combinedSourceStream.subscribe(function(event) {
|
|
167
273
|
var prefix = getPrefix(childrenInfo, event.child);
|
|
168
|
-
|
|
274
|
+
var prefixColor = childrenInfo[event.child.pid].prefixColor;
|
|
275
|
+
log(prefix, prefixColor, event.data.toString());
|
|
169
276
|
});
|
|
170
277
|
}
|
|
171
278
|
|
|
172
279
|
function handleClose(streams, children, childrenInfo) {
|
|
173
280
|
var aliveChildren = _.clone(children);
|
|
174
281
|
var exitCodes = [];
|
|
175
|
-
|
|
176
|
-
var closeStreams = _.pluck(streams, 'close');
|
|
282
|
+
var closeStreams = _.map(streams, 'close');
|
|
177
283
|
var closeStream = Rx.Observable.merge.apply(this, closeStreams);
|
|
178
284
|
|
|
179
285
|
// TODO: Is it possible that amount of close events !== count of spawned?
|
|
@@ -182,21 +288,16 @@ function handleClose(streams, children, childrenInfo) {
|
|
|
182
288
|
exitCodes.push(exitCode);
|
|
183
289
|
|
|
184
290
|
var prefix = getPrefix(childrenInfo, event.child);
|
|
291
|
+
var prefixColor = childrenInfo[event.child.pid].prefixColor;
|
|
185
292
|
var command = childrenInfo[event.child.pid].command;
|
|
186
|
-
logEvent(prefix, command + ' exited with code ' + exitCode);
|
|
293
|
+
logEvent(prefix, prefixColor, command + ' exited with code ' + exitCode);
|
|
187
294
|
|
|
188
295
|
aliveChildren = _.filter(aliveChildren, function(child) {
|
|
189
296
|
return child.pid !== event.child.pid;
|
|
190
297
|
});
|
|
191
298
|
|
|
192
299
|
if (aliveChildren.length === 0) {
|
|
193
|
-
|
|
194
|
-
// in other cases exit code 1 is used
|
|
195
|
-
var someFailed = _.some(exitCodes, function(code) {
|
|
196
|
-
return code !== 0 || code === null;
|
|
197
|
-
});
|
|
198
|
-
var finalExitCode = someFailed ? 1 : 0;
|
|
199
|
-
process.exit(finalExitCode);
|
|
300
|
+
exit(exitCodes);
|
|
200
301
|
}
|
|
201
302
|
});
|
|
202
303
|
|
|
@@ -205,25 +306,46 @@ function handleClose(streams, children, childrenInfo) {
|
|
|
205
306
|
var delayedExit = closeStream.delay(config.killDelay);
|
|
206
307
|
|
|
207
308
|
delayedExit.subscribe(function() {
|
|
208
|
-
logEvent('--> ', 'Sending SIGTERM to other processes..');
|
|
309
|
+
logEvent('--> ', chalk.gray.dim, 'Sending SIGTERM to other processes..');
|
|
209
310
|
|
|
210
311
|
// Send SIGTERM to alive children
|
|
211
312
|
_.each(aliveChildren, function(child) {
|
|
212
|
-
|
|
313
|
+
if (isWindows) {
|
|
314
|
+
spawn('taskkill', ["/pid", child.pid, '/f', '/t']);
|
|
315
|
+
} else {
|
|
316
|
+
child.kill('SIGINT');
|
|
317
|
+
}
|
|
213
318
|
});
|
|
214
319
|
});
|
|
215
320
|
}
|
|
216
321
|
}
|
|
217
322
|
|
|
323
|
+
function exit(childExitCodes) {
|
|
324
|
+
var success;
|
|
325
|
+
switch (config.success) {
|
|
326
|
+
case 'first':
|
|
327
|
+
success = _.first(childExitCodes) === 0;
|
|
328
|
+
break;
|
|
329
|
+
case 'last':
|
|
330
|
+
success = _.last(childExitCodes) === 0;
|
|
331
|
+
break;
|
|
332
|
+
default:
|
|
333
|
+
success = _.every(childExitCodes, function(code) {
|
|
334
|
+
return code === 0;
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
process.exit(success ? 0 : 1);
|
|
338
|
+
}
|
|
339
|
+
|
|
218
340
|
function handleError(streams, childrenInfo) {
|
|
219
341
|
// Output emitted errors from child process
|
|
220
|
-
var errorStreams = _.
|
|
342
|
+
var errorStreams = _.map(streams, 'error');
|
|
221
343
|
var processErrorStream = Rx.Observable.merge.apply(this, errorStreams);
|
|
222
344
|
|
|
223
345
|
processErrorStream.subscribe(function(event) {
|
|
224
346
|
var command = childrenInfo[event.child.pid].command;
|
|
225
|
-
logError('', 'Error occured when executing command: ' + command);
|
|
226
|
-
logError('', event.data.stack);
|
|
347
|
+
logError('', chalk.gray.dim, 'Error occured when executing command: ' + command);
|
|
348
|
+
logError('', chalk.gray.dim, event.data.stack);
|
|
227
349
|
});
|
|
228
350
|
}
|
|
229
351
|
|
|
@@ -236,16 +358,29 @@ function colorText(text, color) {
|
|
|
236
358
|
}
|
|
237
359
|
|
|
238
360
|
function getPrefix(childrenInfo, child) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
var command = childrenInfo[child.pid].command;
|
|
243
|
-
return '[' + shortenText(command, config.prefixLength) + '] ';
|
|
244
|
-
} else if (config.prefix === 'index') {
|
|
245
|
-
return '[' + childrenInfo[child.pid].index + '] ';
|
|
361
|
+
var prefixes = getPrefixes(childrenInfo, child);
|
|
362
|
+
if (_.includes(_.keys(prefixes), config.prefix)) {
|
|
363
|
+
return '[' + prefixes[config.prefix] + '] ';
|
|
246
364
|
}
|
|
247
365
|
|
|
248
|
-
return
|
|
366
|
+
return _.reduce(prefixes, function(memo, val, key) {
|
|
367
|
+
var re = new RegExp('{' + key + '}', 'g');
|
|
368
|
+
return memo.replace(re, val);
|
|
369
|
+
}, config.prefix) + ' ';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function getPrefixes(childrenInfo, child) {
|
|
373
|
+
var prefixes = {};
|
|
374
|
+
|
|
375
|
+
prefixes.none = '';
|
|
376
|
+
prefixes.pid = child.pid;
|
|
377
|
+
prefixes.index = childrenInfo[child.pid].index;
|
|
378
|
+
prefixes.name = childrenInfo[child.pid].name;
|
|
379
|
+
prefixes.time = moment().format(config.timestampFormat);
|
|
380
|
+
|
|
381
|
+
var command = childrenInfo[child.pid].command;
|
|
382
|
+
prefixes.command = shortenText(command, config.prefixLength);
|
|
383
|
+
return prefixes;
|
|
249
384
|
}
|
|
250
385
|
|
|
251
386
|
function shortenText(text, length, cut) {
|
|
@@ -262,23 +397,23 @@ function shortenText(text, length, cut) {
|
|
|
262
397
|
return first + cut + last;
|
|
263
398
|
}
|
|
264
399
|
|
|
265
|
-
function log(prefix, text) {
|
|
266
|
-
logWithPrefix(prefix, text);
|
|
400
|
+
function log(prefix, prefixColor, text) {
|
|
401
|
+
logWithPrefix(prefix, prefixColor, text);
|
|
267
402
|
}
|
|
268
403
|
|
|
269
|
-
function logEvent(prefix, text) {
|
|
404
|
+
function logEvent(prefix, prefixColor, text) {
|
|
270
405
|
if (config.raw) return;
|
|
271
406
|
|
|
272
|
-
logWithPrefix(prefix, text, chalk.gray.dim);
|
|
407
|
+
logWithPrefix(prefix, prefixColor, text, chalk.gray.dim);
|
|
273
408
|
}
|
|
274
409
|
|
|
275
|
-
function logError(prefix, text) {
|
|
410
|
+
function logError(prefix, prefixColor, text) {
|
|
276
411
|
// This is for now same as log, there might be separate colors for stderr
|
|
277
412
|
// and stdout
|
|
278
|
-
logWithPrefix(prefix, text, chalk.red.bold);
|
|
413
|
+
logWithPrefix(prefix, prefixColor, text, chalk.red.bold);
|
|
279
414
|
}
|
|
280
415
|
|
|
281
|
-
function logWithPrefix(prefix, text, color) {
|
|
416
|
+
function logWithPrefix(prefix, prefixColor, text, color) {
|
|
282
417
|
var lastChar = text[text.length - 1];
|
|
283
418
|
if (config.raw) {
|
|
284
419
|
if (lastChar !== '\n') {
|
|
@@ -295,9 +430,11 @@ function logWithPrefix(prefix, text, color) {
|
|
|
295
430
|
}
|
|
296
431
|
|
|
297
432
|
var lines = text.split('\n');
|
|
433
|
+
// Do not bgColor trailing space
|
|
434
|
+
var coloredPrefix = colorText(prefix.replace(/ $/, ''), prefixColor) + ' ';
|
|
298
435
|
var paddedLines = _.map(lines, function(line, i) {
|
|
299
436
|
var coloredLine = color ? colorText(line, color) : line;
|
|
300
|
-
return
|
|
437
|
+
return coloredPrefix + coloredLine;
|
|
301
438
|
});
|
|
302
439
|
|
|
303
440
|
console.log(paddedLines.join('\n'));
|
package/test/test-functional.js
CHANGED
|
@@ -15,7 +15,7 @@ process.chdir(path.join(testDir, '..'));
|
|
|
15
15
|
describe('concurrently', function() {
|
|
16
16
|
this.timeout(5000);
|
|
17
17
|
|
|
18
|
-
it('help should be
|
|
18
|
+
it('help should be successful', function(done) {
|
|
19
19
|
run('node ./src/main.js --help', {pipe: DEBUG_TESTS})
|
|
20
20
|
.then(function(exitCode) {
|
|
21
21
|
// exit code 0 means success
|
|
@@ -41,7 +41,7 @@ describe('concurrently', function() {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
it('at least one unsuccessful commands should exit non-zero', function(done) {
|
|
44
|
-
run('node ./src/main.js "echo" "
|
|
44
|
+
run('node ./src/main.js "echo" "return 1" "echo"', {pipe: DEBUG_TESTS})
|
|
45
45
|
.then(function(exitCode) {
|
|
46
46
|
assert.notStrictEqual(exitCode, 0);
|
|
47
47
|
done();
|
|
@@ -56,8 +56,28 @@ describe('concurrently', function() {
|
|
|
56
56
|
done();
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
|
+
|
|
60
|
+
it('--success=first should return first exit code', function(done) {
|
|
61
|
+
run('node ./src/main.js -k --success first "echo" "sleep 1000" ', {pipe: DEBUG_TESTS})
|
|
62
|
+
// When killed, sleep returns null exit code
|
|
63
|
+
.then(function(exitCode) {
|
|
64
|
+
assert.strictEqual(exitCode, 0);
|
|
65
|
+
done();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('--success=last should return last exit code', function(done) {
|
|
70
|
+
// When killed, sleep returns null exit code
|
|
71
|
+
run('node ./src/main.js -k --success last "echo" "sleep 1000" ', {pipe: DEBUG_TESTS})
|
|
72
|
+
.then(function(exitCode) {
|
|
73
|
+
assert.notStrictEqual(exitCode, 0);
|
|
74
|
+
done();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
59
78
|
});
|
|
60
79
|
|
|
61
80
|
function resolve(relativePath) {
|
|
62
81
|
return path.join(testDir, relativePath);
|
|
63
82
|
}
|
|
83
|
+
|
package/test.js
ADDED
package/tools/release.js
CHANGED
|
@@ -107,7 +107,7 @@ function mergeArgsToDefaults(config) {
|
|
|
107
107
|
if (program.args[0]) {
|
|
108
108
|
config.bumpType = program.args[0];
|
|
109
109
|
|
|
110
|
-
if (!_.
|
|
110
|
+
if (!_.includes(['major', 'minor', 'patch'], config.bumpType)) {
|
|
111
111
|
console.error('Error:', config.bumpType, 'is not a valid bump type');
|
|
112
112
|
process.exit(1);
|
|
113
113
|
}
|