concurrently 3.4.0 → 3.6.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/.eslintrc.json +23 -0
- package/.travis.yml +4 -3
- package/README.md +115 -18
- package/appveyor.yml +1 -2
- package/package.json +11 -3
- package/src/findChild.js +11 -0
- package/src/main.js +251 -126
- package/src/parseCmds.js +65 -0
- package/src/pkgInfo.js +17 -0
- package/test/support/read-echo.js +12 -0
- package/test/support/signal.js +5 -5
- package/test/test-findChild.js +39 -0
- package/test/test-functional.js +329 -88
- package/test/test-parseCmds.js +204 -0
- package/test/utils.js +42 -17
- package/.eslintrc +0 -31
- package/.jshintrc +0 -17
- package/.npmignore +0 -31
- package/tst.js +0 -1
package/src/main.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const Rx = require('rx');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const formatDate = require('date-fns/format');
|
|
7
|
+
const program = require('commander');
|
|
8
|
+
const _ = require('lodash');
|
|
9
|
+
const treeKill = require('tree-kill');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const spawn = require('spawn-command');
|
|
12
|
+
const supportsColor = require('supports-color');
|
|
13
|
+
const IS_WINDOWS = /^win/.test(process.platform);
|
|
14
|
+
|
|
15
|
+
const findChild = require('./findChild.js');
|
|
16
|
+
const parseCmds = require('./parseCmds');
|
|
17
|
+
|
|
18
|
+
let config = {
|
|
15
19
|
// Kill other processes if one dies
|
|
16
20
|
killOthers: false,
|
|
17
21
|
|
|
@@ -24,7 +28,7 @@ var config = {
|
|
|
24
28
|
|
|
25
29
|
// Prefix logging with pid
|
|
26
30
|
// Possible values: 'pid', 'none', 'time', 'command', 'index', 'name'
|
|
27
|
-
prefix: '
|
|
31
|
+
prefix: '',
|
|
28
32
|
|
|
29
33
|
// List of custom names to be used in prefix template
|
|
30
34
|
names: '',
|
|
@@ -46,19 +50,33 @@ var config = {
|
|
|
46
50
|
color: true,
|
|
47
51
|
|
|
48
52
|
// If true, the output will only be raw output of processes, nothing more
|
|
49
|
-
raw: false
|
|
53
|
+
raw: false,
|
|
54
|
+
|
|
55
|
+
// If true, the process restart when it exited with status code non-zero
|
|
56
|
+
allowRestart: false,
|
|
57
|
+
|
|
58
|
+
// By default, restart instantly
|
|
59
|
+
restartAfter: 0,
|
|
60
|
+
|
|
61
|
+
// By default, restart once
|
|
62
|
+
restartTries: 1,
|
|
63
|
+
|
|
64
|
+
// Default identifier for child to which input on stdin should be sent.
|
|
65
|
+
defaultInputTarget: '0'
|
|
50
66
|
};
|
|
51
67
|
|
|
52
68
|
function main() {
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
const firstBase = path.basename(process.argv[0]);
|
|
70
|
+
const secondBase = path.basename(process.argv[1]);
|
|
55
71
|
if (firstBase === 'concurrent' || secondBase === 'concurrent') {
|
|
56
72
|
console.error('Warning: "concurrent" command is deprecated, use "concurrently" instead.\n');
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
parseArgs();
|
|
60
76
|
config = mergeDefaultsWithArgs(config);
|
|
61
|
-
|
|
77
|
+
|
|
78
|
+
const cmds = parseCmds(program.args, config);
|
|
79
|
+
run(cmds);
|
|
62
80
|
}
|
|
63
81
|
|
|
64
82
|
function parseArgs() {
|
|
@@ -81,7 +99,7 @@ function parseArgs() {
|
|
|
81
99
|
'-p, --prefix <prefix>',
|
|
82
100
|
'prefix used in logging for each process.\n' +
|
|
83
101
|
'Possible values: index, pid, time, command, name, none, or a template. Default: ' +
|
|
84
|
-
|
|
102
|
+
'index or name (when --names is set). Example template: "{time}-{pid}"\n'
|
|
85
103
|
)
|
|
86
104
|
.option(
|
|
87
105
|
'-n, --names <names>',
|
|
@@ -126,10 +144,40 @@ function parseArgs() {
|
|
|
126
144
|
'The option can be used to shorten long commands.\n' +
|
|
127
145
|
'Works only if prefix is set to "command". Default: ' +
|
|
128
146
|
config.prefixLength + '\n'
|
|
147
|
+
)
|
|
148
|
+
.option(
|
|
149
|
+
'--allow-restart',
|
|
150
|
+
'Restart a process which died. Default: ' +
|
|
151
|
+
config.allowRestart + '\n'
|
|
152
|
+
)
|
|
153
|
+
.option(
|
|
154
|
+
'--restart-after <miliseconds>',
|
|
155
|
+
'delay time to respawn the process. Default: ' +
|
|
156
|
+
config.restartAfter + '\n'
|
|
157
|
+
)
|
|
158
|
+
.option(
|
|
159
|
+
'--restart-tries <times>',
|
|
160
|
+
'limit the number of respawn tries. Default: ' +
|
|
161
|
+
config.restartTries + '\n'
|
|
162
|
+
|
|
163
|
+
)
|
|
164
|
+
.option(
|
|
165
|
+
'--default-input-target <identifier>',
|
|
166
|
+
'identifier for child process to which input on ' +
|
|
167
|
+
'stdin should be sent if not specified at start ' +
|
|
168
|
+
'of input. Can be either the index or the name ' +
|
|
169
|
+
'of the process. Default: ' + config.defaultInputTarget + '\n'
|
|
129
170
|
);
|
|
130
171
|
|
|
131
172
|
program.on('--help', function() {
|
|
132
|
-
|
|
173
|
+
const help = [
|
|
174
|
+
' Input:',
|
|
175
|
+
'',
|
|
176
|
+
' Input can be sent to any of the child processes using either the name or',
|
|
177
|
+
' index of the command followed by a colon. If no child identifier is',
|
|
178
|
+
' specified then the input will be sent to the child specified by the',
|
|
179
|
+
' `--default-input-target` option, which defaults to index 0.',
|
|
180
|
+
'',
|
|
133
181
|
' Examples:',
|
|
134
182
|
'',
|
|
135
183
|
' - Kill other processes if one exits or dies',
|
|
@@ -154,12 +202,40 @@ function parseArgs() {
|
|
|
154
202
|
'',
|
|
155
203
|
' - Custom names and colored prefixes',
|
|
156
204
|
'',
|
|
157
|
-
' $ concurrently --
|
|
205
|
+
' $ concurrently --names "HTTP,WATCH" -c "bgBlue.bold,bgMagenta.bold" "http-server" "npm run watch"',
|
|
206
|
+
'',
|
|
207
|
+
' - Send input to default',
|
|
208
|
+
'',
|
|
209
|
+
' $ concurrently "nodemon" "npm run watch-js"',
|
|
210
|
+
' rs # Sends rs command to nodemon process',
|
|
211
|
+
'',
|
|
212
|
+
' - Specify a default-input-target',
|
|
213
|
+
'',
|
|
214
|
+
' $ concurrently --default-input-target 1 "npm run watch-js" nodemon',
|
|
215
|
+
' rs',
|
|
216
|
+
'',
|
|
217
|
+
' - Send input to specific child identified by index',
|
|
218
|
+
'',
|
|
219
|
+
' $ concurrently "npm run watch-js" nodemon',
|
|
220
|
+
' 1:rs',
|
|
221
|
+
'',
|
|
222
|
+
' - Send input to specific child identified by name',
|
|
223
|
+
'',
|
|
224
|
+
' $ concurrently -n js,srv "npm run watch-js" nodemon',
|
|
225
|
+
' srv:rs',
|
|
226
|
+
'',
|
|
227
|
+
' - Shortened NPM run commands',
|
|
228
|
+
'',
|
|
229
|
+
' $ concurrently npm:watch-node npm:watch-js npm:watch-css',
|
|
230
|
+
'',
|
|
231
|
+
' - Shortened NPM run command with wildcard',
|
|
232
|
+
'',
|
|
233
|
+
' $ concurrently npm:watch-*',
|
|
158
234
|
''
|
|
159
235
|
];
|
|
160
236
|
console.log(help.join('\n'));
|
|
161
237
|
|
|
162
|
-
|
|
238
|
+
const url = 'https://github.com/kimmobrunfeldt/concurrently';
|
|
163
239
|
console.log(' For more details, visit ' + url);
|
|
164
240
|
console.log('');
|
|
165
241
|
});
|
|
@@ -172,59 +248,84 @@ function mergeDefaultsWithArgs(config) {
|
|
|
172
248
|
return _.merge(config, program);
|
|
173
249
|
}
|
|
174
250
|
|
|
175
|
-
function stripCmdQuotes(cmd) {
|
|
176
|
-
// Removes the quotes surrounding a command.
|
|
177
|
-
if (cmd[0] === '"' || cmd[0] === '\'') {
|
|
178
|
-
return cmd.substr(1, cmd.length - 2);
|
|
179
|
-
} else {
|
|
180
|
-
return cmd;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
251
|
function run(commands) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// Remove quotes.
|
|
191
|
-
cmd = stripCmdQuotes(cmd);
|
|
192
|
-
|
|
193
|
-
var spawnOpts = config.raw ? {stdio: 'inherit'} : {};
|
|
252
|
+
const childrenInfo = {};
|
|
253
|
+
let lastPrefixColor = _.get(chalk, chalk.gray.dim);
|
|
254
|
+
const children = _.map(commands, function(cmdInfo, index) {
|
|
255
|
+
|
|
256
|
+
const spawnOpts = config.raw ? {stdio: 'inherit'} : {};
|
|
194
257
|
if (IS_WINDOWS) {
|
|
195
258
|
spawnOpts.detached = false;
|
|
196
259
|
}
|
|
197
260
|
if (supportsColor) {
|
|
198
|
-
|
|
261
|
+
spawnOpts.env = Object.assign({FORCE_COLOR: supportsColor.level}, process.env);
|
|
199
262
|
}
|
|
200
263
|
|
|
201
|
-
|
|
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
|
-
}
|
|
264
|
+
const child = spawnChild(cmdInfo.cmd, spawnOpts);
|
|
209
265
|
|
|
210
|
-
if (
|
|
211
|
-
|
|
266
|
+
if (cmdInfo.color) {
|
|
267
|
+
const prefixColorPath = cmdInfo.color;
|
|
212
268
|
lastPrefixColor = _.get(chalk, prefixColorPath, chalk.gray.dim);
|
|
213
269
|
}
|
|
214
270
|
|
|
215
|
-
var name = index < names.length ? names[index] : '';
|
|
216
271
|
childrenInfo[child.pid] = {
|
|
217
|
-
command: cmd,
|
|
272
|
+
command: cmdInfo.cmd,
|
|
218
273
|
index: index,
|
|
219
|
-
name: name,
|
|
274
|
+
name: cmdInfo.name,
|
|
275
|
+
options: spawnOpts,
|
|
276
|
+
restartTries: config.restartTries,
|
|
220
277
|
prefixColor: lastPrefixColor
|
|
221
278
|
};
|
|
222
279
|
return child;
|
|
223
280
|
});
|
|
224
281
|
|
|
282
|
+
const streams = toStreams(children);
|
|
283
|
+
|
|
284
|
+
handleChildEvents(streams, children, childrenInfo);
|
|
285
|
+
|
|
286
|
+
['SIGINT', 'SIGTERM'].forEach(function(signal) {
|
|
287
|
+
process.on(signal, function() {
|
|
288
|
+
children.forEach(function(child) {
|
|
289
|
+
treeKill(child.pid, signal);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
process.stdin.on('data', (chunk) => {
|
|
295
|
+
let line = chunk.toString();
|
|
296
|
+
|
|
297
|
+
let targetId = config.defaultInputTarget;
|
|
298
|
+
if (line.indexOf(':') >= 0) {
|
|
299
|
+
const parts = line.split(':');
|
|
300
|
+
targetId = parts[0];
|
|
301
|
+
line = parts[1];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const target = findChild(targetId, children, childrenInfo);
|
|
305
|
+
if (target) {
|
|
306
|
+
target.stdin.write(line);
|
|
307
|
+
} else {
|
|
308
|
+
logError('', chalk.gray.dim, `Unable to find command [${targetId}]\n`);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function spawnChild(cmd, options) {
|
|
314
|
+
let child;
|
|
315
|
+
try {
|
|
316
|
+
child = spawn(cmd, options);
|
|
317
|
+
} catch (e) {
|
|
318
|
+
logError('', chalk.gray.dim, 'Error occured when executing command: ' + cmd);
|
|
319
|
+
logError('', chalk.gray.dim, e.stack);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
return child;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function toStreams(children) {
|
|
225
326
|
// Transform all process events to rx streams
|
|
226
|
-
|
|
227
|
-
|
|
327
|
+
return _.map(children, function(child) {
|
|
328
|
+
const childStreams = {
|
|
228
329
|
error: Rx.Node.fromEvent(child, 'error'),
|
|
229
330
|
close: Rx.Node.fromEvent(child, 'close')
|
|
230
331
|
};
|
|
@@ -241,71 +342,88 @@ function run(commands) {
|
|
|
241
342
|
return memo;
|
|
242
343
|
}, {});
|
|
243
344
|
});
|
|
345
|
+
}
|
|
244
346
|
|
|
347
|
+
function handleChildEvents(streams, children, childrenInfo) {
|
|
245
348
|
handleClose(streams, children, childrenInfo);
|
|
246
349
|
handleError(streams, childrenInfo);
|
|
247
350
|
if (!config.raw) {
|
|
248
351
|
handleOutput(streams, childrenInfo, 'stdout');
|
|
249
352
|
handleOutput(streams, childrenInfo, 'stderr');
|
|
250
353
|
}
|
|
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
354
|
}
|
|
260
355
|
|
|
261
356
|
function handleOutput(streams, childrenInfo, source) {
|
|
262
|
-
|
|
263
|
-
|
|
357
|
+
const sourceStreams = _.map(streams, source);
|
|
358
|
+
const combinedSourceStream = Rx.Observable.merge.apply(this, sourceStreams);
|
|
264
359
|
|
|
265
360
|
combinedSourceStream.subscribe(function(event) {
|
|
266
|
-
|
|
267
|
-
|
|
361
|
+
const prefix = getPrefix(childrenInfo, event.child);
|
|
362
|
+
const prefixColor = childrenInfo[event.child.pid].prefixColor;
|
|
268
363
|
log(prefix, prefixColor, event.data.toString());
|
|
269
364
|
});
|
|
270
365
|
}
|
|
271
366
|
|
|
272
367
|
function handleClose(streams, children, childrenInfo) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
368
|
+
let aliveChildren = _.clone(children);
|
|
369
|
+
const exitCodes = [];
|
|
370
|
+
const closeStreams = _.map(streams, 'close');
|
|
371
|
+
const closeStream = Rx.Observable.merge.apply(this, closeStreams);
|
|
372
|
+
let othersKilled = false;
|
|
278
373
|
|
|
279
374
|
// TODO: Is it possible that amount of close events !== count of spawned?
|
|
280
375
|
closeStream.subscribe(function(event) {
|
|
281
|
-
|
|
282
|
-
|
|
376
|
+
const exitCode = event.data;
|
|
377
|
+
const nonSuccess = exitCode !== 0;
|
|
283
378
|
exitCodes.push(exitCode);
|
|
284
379
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
380
|
+
const prefix = getPrefix(childrenInfo, event.child);
|
|
381
|
+
const childInfo = childrenInfo[event.child.pid];
|
|
382
|
+
const prefixColor = childInfo.prefixColor;
|
|
383
|
+
const command = childInfo.command;
|
|
288
384
|
logEvent(prefix, prefixColor, command + ' exited with code ' + exitCode);
|
|
289
385
|
|
|
290
386
|
aliveChildren = _.filter(aliveChildren, function(child) {
|
|
291
387
|
return child.pid !== event.child.pid;
|
|
292
388
|
});
|
|
293
389
|
|
|
390
|
+
if (nonSuccess && config.allowRestart && childInfo.restartTries--) {
|
|
391
|
+
respawnChild(event, childrenInfo);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
294
395
|
if (aliveChildren.length === 0) {
|
|
295
396
|
exit(exitCodes);
|
|
296
397
|
}
|
|
297
398
|
if (!othersKilled) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
399
|
+
if (config.killOthers) {
|
|
400
|
+
killOtherProcesses(aliveChildren);
|
|
401
|
+
othersKilled = true;
|
|
402
|
+
} else if (config.killOthersOnFail && nonSuccess) {
|
|
403
|
+
killOtherProcesses(aliveChildren);
|
|
404
|
+
othersKilled = true;
|
|
405
|
+
}
|
|
305
406
|
}
|
|
306
407
|
});
|
|
307
408
|
}
|
|
308
409
|
|
|
410
|
+
function respawnChild(event, childrenInfo) {
|
|
411
|
+
setTimeout(function() {
|
|
412
|
+
const childInfo = childrenInfo[event.child.pid];
|
|
413
|
+
const prefix = getPrefix(childrenInfo, event.child);
|
|
414
|
+
const prefixColor = childInfo.prefixColor;
|
|
415
|
+
logEvent(prefix, prefixColor, childInfo.command + ' restarted');
|
|
416
|
+
const newChild = spawnChild(childInfo.command, childInfo.options);
|
|
417
|
+
|
|
418
|
+
childrenInfo[newChild.pid] = childrenInfo[event.child.pid];
|
|
419
|
+
delete childrenInfo[event.child.pid];
|
|
420
|
+
|
|
421
|
+
const children = [newChild];
|
|
422
|
+
const streams = toStreams(children);
|
|
423
|
+
handleChildEvents(streams, children, childrenInfo);
|
|
424
|
+
}, config.restartAfter);
|
|
425
|
+
}
|
|
426
|
+
|
|
309
427
|
function killOtherProcesses(processes) {
|
|
310
428
|
logEvent('--> ', chalk.gray.dim, 'Sending SIGTERM to other processes..');
|
|
311
429
|
|
|
@@ -316,29 +434,29 @@ function killOtherProcesses(processes) {
|
|
|
316
434
|
}
|
|
317
435
|
|
|
318
436
|
function exit(childExitCodes) {
|
|
319
|
-
|
|
437
|
+
let success;
|
|
320
438
|
switch (config.success) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
439
|
+
case 'first':
|
|
440
|
+
success = _.first(childExitCodes) === 0;
|
|
441
|
+
break;
|
|
442
|
+
case 'last':
|
|
443
|
+
success = _.last(childExitCodes) === 0;
|
|
444
|
+
break;
|
|
445
|
+
default:
|
|
446
|
+
success = _.every(childExitCodes, function(code) {
|
|
447
|
+
return code === 0;
|
|
448
|
+
});
|
|
331
449
|
}
|
|
332
450
|
process.exit(success ? 0 : 1);
|
|
333
451
|
}
|
|
334
452
|
|
|
335
453
|
function handleError(streams, childrenInfo) {
|
|
336
454
|
// Output emitted errors from child process
|
|
337
|
-
|
|
338
|
-
|
|
455
|
+
const errorStreams = _.map(streams, 'error');
|
|
456
|
+
const processErrorStream = Rx.Observable.merge.apply(this, errorStreams);
|
|
339
457
|
|
|
340
458
|
processErrorStream.subscribe(function(event) {
|
|
341
|
-
|
|
459
|
+
const command = childrenInfo[event.child.pid].command;
|
|
342
460
|
logError('', chalk.gray.dim, 'Error occured when executing command: ' + command);
|
|
343
461
|
logError('', chalk.gray.dim, event.data.stack);
|
|
344
462
|
});
|
|
@@ -353,19 +471,20 @@ function colorText(text, color) {
|
|
|
353
471
|
}
|
|
354
472
|
|
|
355
473
|
function getPrefix(childrenInfo, child) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
474
|
+
const prefixes = getPrefixes(childrenInfo, child);
|
|
475
|
+
const prefixType = config.prefix || (prefixes.name ? 'name' : 'index');
|
|
476
|
+
if (_.includes(_.keys(prefixes), prefixType)) {
|
|
477
|
+
return '[' + prefixes[prefixType] + '] ';
|
|
359
478
|
}
|
|
360
479
|
|
|
361
480
|
return _.reduce(prefixes, function(memo, val, key) {
|
|
362
|
-
|
|
481
|
+
const re = new RegExp('{' + key + '}', 'g');
|
|
363
482
|
return memo.replace(re, val);
|
|
364
483
|
}, config.prefix) + ' ';
|
|
365
484
|
}
|
|
366
485
|
|
|
367
486
|
function getPrefixes(childrenInfo, child) {
|
|
368
|
-
|
|
487
|
+
const prefixes = {};
|
|
369
488
|
|
|
370
489
|
prefixes.none = '';
|
|
371
490
|
prefixes.pid = child.pid;
|
|
@@ -373,7 +492,7 @@ function getPrefixes(childrenInfo, child) {
|
|
|
373
492
|
prefixes.name = childrenInfo[child.pid].name;
|
|
374
493
|
prefixes.time = formatDate(Date.now(), config.timestampFormat);
|
|
375
494
|
|
|
376
|
-
|
|
495
|
+
const command = childrenInfo[child.pid].command;
|
|
377
496
|
prefixes.command = shortenText(command, config.prefixLength);
|
|
378
497
|
return prefixes;
|
|
379
498
|
}
|
|
@@ -384,11 +503,11 @@ function shortenText(text, length, cut) {
|
|
|
384
503
|
}
|
|
385
504
|
cut = _.isString(cut) ? cut : '..';
|
|
386
505
|
|
|
387
|
-
|
|
388
|
-
|
|
506
|
+
const endLength = Math.floor(length / 2);
|
|
507
|
+
const startLength = length - endLength;
|
|
389
508
|
|
|
390
|
-
|
|
391
|
-
|
|
509
|
+
const first = text.substring(0, startLength);
|
|
510
|
+
const last = text.substring(text.length - endLength, text.length);
|
|
392
511
|
return first + cut + last;
|
|
393
512
|
}
|
|
394
513
|
|
|
@@ -397,9 +516,11 @@ function log(prefix, prefixColor, text) {
|
|
|
397
516
|
}
|
|
398
517
|
|
|
399
518
|
function logEvent(prefix, prefixColor, text) {
|
|
400
|
-
if (config.raw)
|
|
519
|
+
if (config.raw) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
401
522
|
|
|
402
|
-
logWithPrefix(prefix, prefixColor, text, chalk.gray.dim);
|
|
523
|
+
logWithPrefix(prefix, prefixColor, text + '\n', chalk.gray.dim);
|
|
403
524
|
}
|
|
404
525
|
|
|
405
526
|
function logError(prefix, prefixColor, text) {
|
|
@@ -408,31 +529,35 @@ function logError(prefix, prefixColor, text) {
|
|
|
408
529
|
logWithPrefix(prefix, prefixColor, text, chalk.red.bold);
|
|
409
530
|
}
|
|
410
531
|
|
|
532
|
+
let lastChar;
|
|
533
|
+
|
|
411
534
|
function logWithPrefix(prefix, prefixColor, text, color) {
|
|
412
|
-
var lastChar = text[text.length - 1];
|
|
413
|
-
if (config.raw) {
|
|
414
|
-
if (lastChar !== '\n') {
|
|
415
|
-
text += '\n';
|
|
416
|
-
}
|
|
417
535
|
|
|
536
|
+
if (config.raw) {
|
|
418
537
|
process.stdout.write(text);
|
|
419
538
|
return;
|
|
420
539
|
}
|
|
421
540
|
|
|
422
|
-
|
|
423
|
-
// Remove extra newline from the end to prevent extra newlines in input
|
|
424
|
-
text = text.slice(0, text.length - 1);
|
|
425
|
-
}
|
|
541
|
+
text = text.replace(/\u2026/g,'...'); // Ellipsis
|
|
426
542
|
|
|
427
|
-
|
|
543
|
+
const lines = text.split('\n');
|
|
428
544
|
// Do not bgColor trailing space
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
545
|
+
const coloredPrefix = colorText(prefix.replace(/ $/, ''), prefixColor) + ' ';
|
|
546
|
+
const paddedLines = _.map(lines, function(line, index) {
|
|
547
|
+
let coloredLine = color ? colorText(line, color) : line;
|
|
548
|
+
if (index !== 0 && index !== (lines.length - 1)) {
|
|
549
|
+
coloredLine = coloredPrefix + coloredLine;
|
|
550
|
+
}
|
|
551
|
+
return coloredLine;
|
|
433
552
|
});
|
|
434
553
|
|
|
435
|
-
|
|
554
|
+
if (!lastChar || lastChar === '\n' ) {
|
|
555
|
+
process.stdout.write(coloredPrefix);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
lastChar = text[text.length - 1];
|
|
559
|
+
|
|
560
|
+
process.stdout.write(paddedLines.join('\n'));
|
|
436
561
|
}
|
|
437
562
|
|
|
438
563
|
main();
|
package/src/parseCmds.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
|
|
4
|
+
const pkgInfo = require('./pkgInfo');
|
|
5
|
+
|
|
6
|
+
module.exports = function(cmds, config) {
|
|
7
|
+
config = config || {};
|
|
8
|
+
|
|
9
|
+
const names = (config.names || '').split(config.nameSeparator || ',');
|
|
10
|
+
const prefixColors = config.prefixColors ? config.prefixColors.split(',') : [];
|
|
11
|
+
|
|
12
|
+
cmds = cmds.map(stripCmdQuotes);
|
|
13
|
+
|
|
14
|
+
cmds = cmds.map((cmd, idx) => ({
|
|
15
|
+
cmd: cmd,
|
|
16
|
+
name: names[idx] || ''
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
cmds = _.flatMap(cmds, expandCmdShortcuts);
|
|
20
|
+
|
|
21
|
+
return cmds.map((cmd, idx) => Object.assign(cmd, {
|
|
22
|
+
color: prefixColors[idx]
|
|
23
|
+
}));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function stripCmdQuotes(cmd) {
|
|
27
|
+
// Removes the quotes surrounding a command.
|
|
28
|
+
if (cmd[0] === '"' || cmd[0] === '\'') {
|
|
29
|
+
return cmd.substr(1, cmd.length - 2);
|
|
30
|
+
} else {
|
|
31
|
+
return cmd;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function expandCmdShortcuts(cmd) {
|
|
36
|
+
const shortcut = cmd.cmd.match(/^npm:(\S+)(.*)/);
|
|
37
|
+
if (shortcut) {
|
|
38
|
+
const cmdName = shortcut[1];
|
|
39
|
+
const args = shortcut[2];
|
|
40
|
+
|
|
41
|
+
const wildcard = cmdName.indexOf('*');
|
|
42
|
+
if (wildcard >= 0) {
|
|
43
|
+
return expandNpmWildcard(cmd, cmdName, wildcard, args);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!cmd.name) {
|
|
47
|
+
cmd.name = cmdName;
|
|
48
|
+
}
|
|
49
|
+
cmd.cmd = `npm run ${cmdName}${args}`;
|
|
50
|
+
}
|
|
51
|
+
return [ cmd ];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function expandNpmWildcard(cmd, cmdName, wildcardPos, args) {
|
|
55
|
+
const rePre = _.escapeRegExp(cmdName.substr(0, wildcardPos));
|
|
56
|
+
const reSuf = _.escapeRegExp(cmdName.substr(wildcardPos + 1));
|
|
57
|
+
const wildcardRe = new RegExp(`^${rePre}(.*?)${reSuf}$`);
|
|
58
|
+
|
|
59
|
+
return pkgInfo.getScripts()
|
|
60
|
+
.filter(script => script.match(wildcardRe))
|
|
61
|
+
.map(script => Object.assign({}, cmd, {
|
|
62
|
+
cmd: `npm run ${script}${args}`,
|
|
63
|
+
name: cmd.name + script.match(wildcardRe)[1]
|
|
64
|
+
}));
|
|
65
|
+
}
|
package/src/pkgInfo.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const readPkg = require('read-pkg');
|
|
4
|
+
|
|
5
|
+
let pkg;
|
|
6
|
+
|
|
7
|
+
function getPkg() {
|
|
8
|
+
if (!pkg) {
|
|
9
|
+
pkg = readPkg.sync();
|
|
10
|
+
}
|
|
11
|
+
return pkg;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.getScripts = function() {
|
|
15
|
+
const pkg = getPkg();
|
|
16
|
+
return Object.keys(pkg.scripts || {});
|
|
17
|
+
};
|
package/test/support/signal.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
['SIGINT', 'SIGTERM'].forEach(function(signal) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
process.on(signal, function() {
|
|
3
|
+
console.log(signal);
|
|
4
|
+
process.exit(1);
|
|
5
|
+
});
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
function doNothing() {
|
|
9
|
-
|
|
9
|
+
setTimeout(doNothing, 1000);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
doNothing();
|