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 CHANGED
@@ -2,14 +2,19 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/kimmobrunfeldt/concurrently.svg)](https://travis-ci.org/kimmobrunfeldt/concurrently)
4
4
 
5
- **Version: 0.1.1** ([*previous stable*](https://github.com/kimmobrunfeldt/concurrently/tree/0.1.0))
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
  ![](docs/demo.gif)
11
11
 
12
- It also works on Windows. You can tune your npm scripts to work across platforms.
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
- concurrent "command1 arg" "command2 arg"
31
+ concurrently "command1 arg" "command2 arg"
27
32
  ```
28
33
 
29
- Otherwise **concurrent** would try to run 4 separate commands:
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
- Usage: concurrent [options] <command ...>
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
- Options:
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
- -r, --raw output only raw output of processes, disables prettifying and colors
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
- Examples:
66
+ $ concurrently --kill-others "grunt watch" "http-server"
53
67
 
54
- - Kill other processes if one exits or dies
68
+ - Output nothing more than stdout+stderr of child processes
55
69
 
56
- $ concurrent --kill-others "grunt watch" "http-server"
70
+ $ concurrently --raw "npm run watch-less" "npm run watch-js"
57
71
 
58
- - Output nothing more than stdout+stderr of child processes
72
+ - Normal output but without colors e.g. when logging to file
59
73
 
60
- $ concurrent --raw "npm run watch-less" "npm run watch-js"
74
+ $ concurrently --no-color "grunt watch" "http-server" > log
61
75
 
62
- - Normal output but without colors e.g. when logging to file
76
+ - Custom prefix
63
77
 
64
- $ concurrent --no-color "grunt watch" "http-server" > log
78
+ $ concurrently --prefix "{time}-{pid}" "grunt watch" "http-server"
65
79
 
66
- For more details, visit https://github.com/kimmobrunfeldt/concurrently
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
- ```npm run watch-js & npm run watch-css```. That's fine but it's hard to keep
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
- > concurrent --kill-others "npm run watch-js" "npm run watch-less"
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": "0.1.1",
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": "3.1.0",
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
- require('./lodash-mixins');
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 colors'
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
- ' $ concurrent --kill-others "grunt watch" "http-server"',
132
+ ' $ concurrently --kill-others "grunt watch" "http-server"',
78
133
  '',
79
134
  ' - Output nothing more than stdout+stderr of child processes',
80
135
  '',
81
- ' $ concurrent --raw "npm run watch-less" "npm run watch-js"',
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
- ' $ concurrent --no-color "grunt watch" "http-server" > log',
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
- var parts = cmd.split(' ');
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 streamList = [
126
- Rx.Node.fromReadableStream(child.stdout),
127
- Rx.Node.fromReadableStream(child.stderr),
128
- Rx.Node.fromEvent(child, 'error'),
129
- Rx.Node.fromEvent(child, 'close')
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
- var mappedStreams = _.map(streamList, function(stream) {
133
- return stream.map(function(data) {
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
- return {
139
- stdout: mappedStreams[0],
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 handleStdout(streams, childrenInfo) {
153
- var stdoutStreams = _.pluck(streams, 'stdout');
154
- var stdoutStream = Rx.Observable.merge.apply(this, stdoutStreams);
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
- stderrStream.subscribe(function(event) {
272
+ combinedSourceStream.subscribe(function(event) {
167
273
  var prefix = getPrefix(childrenInfo, event.child);
168
- log(prefix, event.data.toString());
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
- // Final exit code is 0 when all processes ran succesfully,
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
- child.kill();
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 = _.pluck(streams, 'error');
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
- if (config.prefix === 'pid') {
240
- return '[' + child.pid + '] ';
241
- } else if (config.prefix === 'command') {
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 colorText(prefix, chalk.gray.dim) + coloredLine;
437
+ return coloredPrefix + coloredLine;
301
438
  });
302
439
 
303
440
  console.log(paddedLines.join('\n'));
@@ -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 succesful', function(done) {
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" "exit 1" "echo"', {pipe: DEBUG_TESTS})
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
@@ -0,0 +1,7 @@
1
+ var chalk = require('chalk');
2
+
3
+ // style a string
4
+ console.log(chalk.blue('Hello world!'));
5
+
6
+ // combine styled and normal strings
7
+ console.log(chalk.blue('Hello') + 'World' + chalk.red('!'));
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 (!_.contains(['major', 'minor', 'patch'], config.bumpType)) {
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
  }
@@ -1,11 +0,0 @@
1
- var _ = require('lodash');
2
-
3
- function sum(arr) {
4
- return _.reduce(arr, function(sum, num) {
5
- return sum + num;
6
- }, 0);
7
- }
8
-
9
- _.mixin({
10
- sum: sum
11
- });