Skip to content

Commit dd5bd33

Browse files
committed
Merge pull request #14 from paulpflug/master
fix for #12
2 parents 774fb56 + 1829194 commit dd5bd33

File tree

6 files changed

+189
-13
lines changed

6 files changed

+189
-13
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,11 @@ This will execute the commands `echo 1` `echo 2` and `echo 3` simultaneously.
3131

3232
Note that on Windows, you need to use double-quotes to avoid confusing the
3333
argument parser.
34+
35+
Available options:
36+
```
37+
-h, --help output usage information
38+
-v, --verbose verbose logging
39+
-w, --wait will not close silbling processes on error
40+
41+
```

index.js

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,101 @@
33
'use strict';
44
var spawn = require('child_process').spawn;
55

6-
function potentialExit (childCmd, code) {
7-
code = code? (code.code || code) : code;
8-
if (code > 0) {
9-
console.error('`' + childCmd + '` failed with exit code ' + code);
10-
process.exit(code);
6+
var sh, shFlag, children, args, wait, cmds, verbose, i ,len;
7+
// parsing argv
8+
cmds = [];
9+
args = process.argv.slice(2);
10+
for (i = 0, len = args.length; i < len; i++) {
11+
if (args[i][0] === '-') {
12+
switch (args[i]) {
13+
case '-w':
14+
case '--wait':
15+
wait = true;
16+
break;
17+
case '-v':
18+
case '--verbose':
19+
verbose = true;
20+
break;
21+
case '-h':
22+
case '--help':
23+
console.log('-h, --help output usage information');
24+
console.log('-v, --verbose verbose logging')
25+
console.log('-w, --wait will not close silbling processes on error')
26+
process.exit();
27+
break;
28+
}
29+
} else {
30+
cmds.push(args[i]);
1131
}
1232
}
13-
var sh = 'sh';
14-
var shFlag = '-c';
1533

34+
// called on close of a child process
35+
function childClose (code) {
36+
var i, len;
37+
code = code ? (code.code || code) : code;
38+
if (verbose) {
39+
if (code > 0) {
40+
console.error('`' + this.cmd + '` failed with exit code ' + code);
41+
} else {
42+
console.log('`' + this.cmd + '` ended successfully');
43+
}
44+
}
45+
if (code > 0 && !wait) close(code);
46+
status();
47+
}
48+
49+
function status () {
50+
if (verbose) {
51+
var i, len;
52+
console.log('\n');
53+
console.log('### Status ###');
54+
for (i = 0, len = children.length; i < len; i++) {
55+
if (children[i].exitCode === null) {
56+
console.log('`' + children[i].cmd + '` is still running');
57+
} else if (children[i].exitCode > 0) {
58+
console.log('`' + children[i].cmd + '` errored');
59+
} else {
60+
console.log('`' + children[i].cmd + '` finished');
61+
}
62+
}
63+
console.log('\n');
64+
}
65+
}
66+
67+
// closes all children and the process
68+
function close (code) {
69+
var i, len;
70+
for (i = 0, len = children.length; i < len; i++) {
71+
if (!children[i].exitCode) {
72+
children[i].removeAllListeners('close');
73+
children[i].kill('SIGINT');
74+
if (verbose) console.log('`' + children[i].cmd + '` will now be closed');
75+
}
76+
}
77+
process.exit(code);
78+
}
79+
80+
// cross platform compatibility
1681
if (process.platform === 'win32') {
1782
sh = 'cmd';
1883
shFlag = '/c';
84+
} else {
85+
sh = 'sh';
86+
shFlag = '-c';
1987
}
20-
process.argv.slice(2).forEach(function (childCmd) {
21-
var child = spawn(sh,[shFlag,childCmd], {
88+
89+
// start the children
90+
children = [];
91+
cmds.forEach(function (cmd) {
92+
var child = spawn(sh,[shFlag,cmd], {
2293
cwd: process.cwd,
2394
env: process.env,
2495
stdio: ['pipe', process.stdout, process.stderr]
2596
})
26-
.on('error', potentialExit.bind(null, childCmd))
27-
.on('exit', potentialExit.bind(null, childCmd));
97+
.on('close', childClose);
98+
child.cmd = cmd
99+
children.push(child)
28100
});
101+
102+
// close all children on ctrl+c
103+
process.on('SIGINT', close)

package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@
1515
"parallelshell": "./index.js"
1616
},
1717
"scripts": {
18-
"test": "echo \"Error: no test specified\" && exit 1"
18+
"test": "mocha"
1919
},
2020
"keywords": [
2121
"parallel",
2222
"shell"
2323
],
2424
"author": "Keith Cirkel <npm@keithcirkel.co.uk> (http://keithcirkel.co.uk/)",
25-
"license": "MIT"
25+
"license": "MIT",
26+
"devDependencies": {
27+
"bluebird": "^2.9.25",
28+
"chai": "^2.3.0",
29+
"coffee-script": "^1.9.2",
30+
"mocha": "^2.2.4"
31+
}
2632
}

test/index.coffee

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
chai = require "chai"
2+
should = chai.should()
3+
spawn = require("child_process").spawn;
4+
Promise = require("bluebird")
5+
6+
# cross platform compatibility
7+
if process.platform == "win32"
8+
sh = "cmd";
9+
shFlag = "/c";
10+
else
11+
sh = "sh";
12+
shFlag = "-c";
13+
14+
15+
# children
16+
waitingProcess = "\\\"node -e 'setTimeout(function(){},10000);'\\\""
17+
failingProcess = "\\\"node -e 'throw new Error(\"someError\");'\\\""
18+
19+
usageInfo = """
20+
-h, --help output usage information
21+
-v, --verbose verbose logging
22+
-w, --wait will not close silbling processes on error
23+
""".split("\n")
24+
25+
spawnParallelshell = (cmd) ->
26+
return spawn sh, [shFlag, "node './index.js' " + cmd], {
27+
cwd: process.cwd
28+
}
29+
30+
testOutput = (cmd, expectedOutput) ->
31+
return new Promise (resolve) ->
32+
ps = spawnParallelshell(cmd)
33+
ps.stdout.setEncoding("utf8")
34+
output = []
35+
ps.stdout.on "data", (data) ->
36+
lines = data.split("\n")
37+
lines.pop() if lines[lines.length-1] == ""
38+
output = output.concat(lines)
39+
ps.stdout.on "end", () ->
40+
for line,i in output
41+
line.should.equal expectedOutput[i]
42+
resolve()
43+
44+
describe "parallelshell", ->
45+
it "should print on -h and --help", (done) ->
46+
Promise.all([testOutput("-h", usageInfo), testOutput("-help", usageInfo)])
47+
.finally done
48+
49+
it "should close with exitCode 2 on child error", (done) ->
50+
ps = spawnParallelshell(failingProcess)
51+
ps.on "close", () ->
52+
ps.exitCode.should.equal 2
53+
done()
54+
55+
it "should run with a normal child", (done) ->
56+
ps = spawnParallelshell(waitingProcess)
57+
setTimeout (() ->
58+
should.not.exist(ps.signalCode)
59+
ps.kill()
60+
done()
61+
),100
62+
63+
it "should close silbling processes on child error", (done) ->
64+
ps = spawnParallelshell([waitingProcess,failingProcess,waitingProcess].join(" "))
65+
ps.on "close", () ->
66+
ps.exitCode.should.equal 2
67+
done()
68+
69+
it "should wait for silbling processes on child error when called with -w or --wait", (done) ->
70+
ps = spawnParallelshell(["-w",waitingProcess,failingProcess,waitingProcess].join(" "))
71+
ps2 = spawnParallelshell(["--wait",waitingProcess,failingProcess,waitingProcess].join(" "))
72+
setTimeout (() ->
73+
should.not.exist(ps.signalCode)
74+
should.not.exist(ps2.signalCode)
75+
ps.kill()
76+
ps2.kill()
77+
done()
78+
),100
79+
it "should close on CTRL+C / SIGINT", (done) ->
80+
ps = spawnParallelshell(["-w",waitingProcess,failingProcess,waitingProcess].join(" "))
81+
ps.on "close", () ->
82+
ps.signalCode.should.equal "SIGINT"
83+
done()
84+
ps.kill("SIGINT")

test/mocha.opts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--compilers coffee:coffee-script/register
2+
--timeout 500

0 commit comments

Comments
 (0)