[Pkg-javascript-commits] [node-cross-spawn] 01/04: Imported Upstream version 5.1.0

Ross Gammon ross-guest at moszumanska.debian.org
Sun Aug 6 18:26:05 UTC 2017


This is an automated email from the git hooks/post-receive script.

ross-guest pushed a commit to branch master
in repository node-cross-spawn.

commit c5db29578f75810a84a40e9a83b4ff80ac9cf7c7
Author: Ross Gammon <rossgammon at mail.dk>
Date:   Sun Aug 6 19:57:33 2017 +0200

    Imported Upstream version 5.1.0
---
 .npmignore                       |   3 -
 .travis.yml                      |   1 +
 CHANGELOG.md                     |   6 ++
 README.md                        |  28 +++++--
 appveyor.yml                     |   1 +
 lib/enoent.js                    |   2 +-
 lib/hasBrokenSpawn.js            |  11 ---
 lib/parse.js                     | 169 ++++++++++++++++-----------------------
 lib/util/escapeArgument.js       |  30 +++++++
 lib/util/escapeCommand.js        |  12 +++
 lib/util/hasEmptyArgumentBug.js  |  18 +++++
 lib/util/readShebang.js          |  37 +++++++++
 lib/{ => util}/resolveCommand.js |   0
 package.json                     |   4 +-
 test/prepare.js                  |  14 ++++
 test/test.js                     |  81 ++++++++++---------
 test/util/buffered.js            |   2 +
 17 files changed, 258 insertions(+), 161 deletions(-)

diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index 93f2f73..0000000
--- a/.npmignore
+++ /dev/null
@@ -1,3 +0,0 @@
-node_modules/
-npm-debug.*
-test/
diff --git a/.travis.yml b/.travis.yml
index 1fc99b1..57a922d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,3 +4,4 @@ node_js:
   - '0.12'
   - '4'
   - '6'
+  - '7'
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..f1298a8
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,6 @@
+## 5.0.0 - 2016-10-30
+
+- Add support for `options.shell`
+- Improve parsing of shebangs by using [`shebang-command`](https://github.com/kevva/shebang-command) module
+- Refactor some code to make it more clear
+- Update README caveats
diff --git a/README.md b/README.md
index 18cc2b8..dde730d 100644
--- a/README.md
+++ b/README.md
@@ -32,8 +32,8 @@ Node has issues when using spawn on Windows:
 
 - It ignores [PATHEXT](https://github.com/joyent/node/issues/2318)
 - It does not support [shebangs](http://pt.wikipedia.org/wiki/Shebang)
+- No `options.shell` support on node < v6
 - It does not allow you to run `del` or `dir`
-- It does not properly escape arguments with spaces or special characters
 
 All these issues are handled correctly by `cross-spawn`.
 There are some known modules, such as [win-spawn](https://github.com/ForbesLindesay/win-spawn), that try to solve this but they are either broken or provide faulty escaping of shell arguments.
@@ -43,7 +43,8 @@ There are some known modules, such as [win-spawn](https://github.com/ForbesLinde
 
 Exactly the same way as node's [`spawn`](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options) or [`spawnSync`](https://nodejs.org/api/child_process.html#child_process_child_process_spawnsync_command_args_options), so it's a drop in replacement.
 
-```javascript
+
+```js
 var spawn = require('cross-spawn');
 
 // Spawn NPM asynchronously
@@ -53,12 +54,25 @@ var child = spawn('npm', ['list', '-g', '-depth', '0'], { stdio: 'inherit' });
 var results = spawn.sync('npm', ['list', '-g', '-depth', '0'], { stdio: 'inherit' });
 ```
 
-## Caveat
 
-On Windows, cross-spawn will only spawn `cmd.exe` if necessary. If the extension
-of the executable is `.exe` or `.com`, it will spawn it directly. If you wish
-to override this behavior and *always* spawn a shell, pass the `{shell: true}`
-option.
+## Caveats
+
+#### `options.shell` as an alternative to `cross-spawn`
+
+Starting from node v6, `spawn` has a `shell` option that allows you run commands from within a shell. This new option solves most of the problems that `cross-spawn` attempts to solve, but:
+
+- It's not supported in node < v6
+- It has no support for shebangs on Windows
+- You must manually escape the command and arguments which is very error prone, specially when passing user input
+
+If you are using the `shell` option to spawn a command in a cross platform way, consider using `cross-spawn` instead. You have been warned.
+
+
+#### Shebangs
+
+While `cross-spawn` handles shebangs on Windows, its support is limited: e.g.: it doesn't handle arguments after the path, e.g.: `#!/bin/bash -e`.
+
+Remember to always test your code on Windows!
 
 
 ## Tests
diff --git a/appveyor.yml b/appveyor.yml
index f8c4e7a..5874e49 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -15,6 +15,7 @@ environment:
     - nodejs_version: 0.12
     - nodejs_version: 4
     - nodejs_version: 6
+    - nodejs_version: 7
 
 # get the latest stable version of Node 0.STABLE.latest
 install:
diff --git a/lib/enoent.js b/lib/enoent.js
index 74ff06e..d0a193a 100644
--- a/lib/enoent.js
+++ b/lib/enoent.js
@@ -1,7 +1,7 @@
 'use strict';
 
 var isWin = process.platform === 'win32';
-var resolveCommand = require('./resolveCommand');
+var resolveCommand = require('./util/resolveCommand');
 
 var isNode10 = process.version.indexOf('v0.10.') === 0;
 
diff --git a/lib/hasBrokenSpawn.js b/lib/hasBrokenSpawn.js
deleted file mode 100644
index e73f906..0000000
--- a/lib/hasBrokenSpawn.js
+++ /dev/null
@@ -1,11 +0,0 @@
-'use strict';
-
-module.exports = (function () {
-    if (process.platform !== 'win32') {
-        return false;
-    }
-    var nodeVer = process.version.substr(1).split('.').map(function (num) {
-        return parseInt(num, 10);
-    });
-    return (nodeVer[0] === 0 && nodeVer[1] < 12);
-})();
diff --git a/lib/parse.js b/lib/parse.js
index 77cbb83..10a0136 100644
--- a/lib/parse.js
+++ b/lib/parse.js
@@ -1,90 +1,92 @@
 'use strict';
 
-var fs = require('fs');
-var LRU = require('lru-cache');
-var resolveCommand = require('./resolveCommand');
-var hasBrokenSpawn = require('./hasBrokenSpawn');
+var resolveCommand = require('./util/resolveCommand');
+var hasEmptyArgumentBug = require('./util/hasEmptyArgumentBug');
+var escapeArgument = require('./util/escapeArgument');
+var escapeCommand = require('./util/escapeCommand');
+var readShebang = require('./util/readShebang');
 
 var isWin = process.platform === 'win32';
-var shebangCache = new LRU({ max: 50, maxAge: 30 * 1000 });  // Cache just for 30sec
+var skipShellRegExp = /\.(?:com|exe)$/i;
 
-function readShebang(command) {
-    var buffer;
-    var fd;
-    var match;
+// Supported in Node >= 6 and >= 4.8
+var supportsShellOption = parseInt(process.version.substr(1).split('.')[0], 10) >= 6 ||
+ parseInt(process.version.substr(1).split('.')[0], 10) === 4 && parseInt(process.version.substr(1).split('.')[1], 10) >= 8;
+
+function parseNonShell(parsed) {
     var shebang;
+    var needsShell;
+    var applyQuotes;
 
-    // Check if it is in the cache first
-    if (shebangCache.has(command)) {
-        return shebangCache.get(command);
+    if (!isWin) {
+        return parsed;
     }
 
-    // Read the first 150 bytes from the file
-    buffer = new Buffer(150);
-
-    try {
-        fd = fs.openSync(command, 'r');
-        fs.readSync(fd, buffer, 0, 150, 0);
-        fs.closeSync(fd);
-    } catch (e) { /* empty */ }
-
-    // Check if it is a shebang
-    match = buffer.toString().trim().match(/#!(.+)/i);
+    // Detect & add support for shebangs
+    parsed.file = resolveCommand(parsed.command);
+    parsed.file = parsed.file || resolveCommand(parsed.command, true);
+    shebang = parsed.file && readShebang(parsed.file);
 
-    if (match) {
-        shebang = match[1].replace(/\/usr\/bin\/env\s+/i, '');   // Remove /usr/bin/env
+    if (shebang) {
+        parsed.args.unshift(parsed.file);
+        parsed.command = shebang;
+        needsShell = hasEmptyArgumentBug || !skipShellRegExp.test(resolveCommand(shebang) || resolveCommand(shebang, true));
+    } else {
+        needsShell = hasEmptyArgumentBug || !skipShellRegExp.test(parsed.file);
     }
 
-    // Store the shebang in the cache
-    shebangCache.set(command, shebang);
+    // If a shell is required, use cmd.exe and take care of escaping everything correctly
+    if (needsShell) {
+        // Escape command & arguments
+        applyQuotes = (parsed.command !== 'echo');  // Do not quote arguments for the special "echo" command
+        parsed.command = escapeCommand(parsed.command);
+        parsed.args = parsed.args.map(function (arg) {
+            return escapeArgument(arg, applyQuotes);
+        });
+
+        // Make use of cmd.exe
+        parsed.args = ['/d', '/s', '/c', '"' + parsed.command + (parsed.args.length ? ' ' + parsed.args.join(' ') : '') + '"'];
+        parsed.command = process.env.comspec || 'cmd.exe';
+        parsed.options.windowsVerbatimArguments = true;  // Tell node's spawn that the arguments are already escaped
+    }
 
-    return shebang;
+    return parsed;
 }
 
-function escapeArg(arg, quote) {
-    // Convert to string
-    arg = '' + arg;
+function parseShell(parsed) {
+    var shellCommand;
 
-    // If we are not going to quote the argument,
-    // escape shell metacharacters, including double and single quotes:
-    if (!quote) {
-        arg = arg.replace(/([\(\)%!\^<>&|;,"'\s])/g, '^$1');
-    } else {
-        // Sequence of backslashes followed by a double quote:
-        // double up all the backslashes and escape the double quote
-        arg = arg.replace(/(\\*)"/g, '$1$1\\"');
+    // If node supports the shell option, there's no need to mimic its behavior
+    if (supportsShellOption) {
+        return parsed;
+    }
 
-        // Sequence of backslashes followed by the end of the string
-        // (which will become a double quote later):
-        // double up all the backslashes
-        arg = arg.replace(/(\\*)$/, '$1$1');
+    // Mimic node shell option, see: https://github.com/nodejs/node/blob/b9f6a2dc059a1062776133f3d4fd848c4da7d150/lib/child_process.js#L335
+    shellCommand = [parsed.command].concat(parsed.args).join(' ');
 
-        // All other backslashes occur literally
+    if (isWin) {
+        parsed.command = typeof parsed.options.shell === 'string' ? parsed.options.shell : process.env.comspec || 'cmd.exe';
+        parsed.args = ['/d', '/s', '/c', '"' + shellCommand + '"'];
+        parsed.options.windowsVerbatimArguments = true;  // Tell node's spawn that the arguments are already escaped
+    } else {
+        if (typeof parsed.options.shell === 'string') {
+            parsed.command = parsed.options.shell;
+        } else if (process.platform === 'android') {
+            parsed.command = '/system/bin/sh';
+        } else {
+            parsed.command = '/bin/sh';
+        }
 
-        // Quote the whole thing:
-        arg = '"' + arg + '"';
+        parsed.args = ['-c', shellCommand];
     }
 
-    return arg;
+    return parsed;
 }
 
-function escapeCommand(command) {
-    // Do not escape if this command is not dangerous..
-    // We do this so that commands like "echo" or "ifconfig" work
-    // Quoting them, will make them unaccessible
-    return /^[a-z0-9_-]+$/i.test(command) ? command : escapeArg(command, true);
-}
-
-function requiresShell(command) {
-    return !/\.(?:com|exe)$/i.test(command);
-}
+// ------------------------------------------------
 
 function parse(command, args, options) {
-    var shebang;
-    var applyQuotes;
-    var file;
-    var original;
-    var shell;
+    var parsed;
 
     // Normalize arguments, similar to nodejs
     if (args && !Array.isArray(args)) {
@@ -94,47 +96,18 @@ function parse(command, args, options) {
 
     args = args ? args.slice(0) : [];  // Clone array to avoid changing the original
     options = options || {};
-    original = command;
-
-    if (isWin) {
-        // Detect & add support for shebangs
-        file = resolveCommand(command);
-        file = file || resolveCommand(command, true);
-        shebang = file && readShebang(file);
-        shell = options.shell || hasBrokenSpawn;
-
-        if (shebang) {
-            args.unshift(file);
-            command = shebang;
-            shell = shell || requiresShell(resolveCommand(shebang) || resolveCommand(shebang, true));
-        } else {
-            shell = shell || requiresShell(file);
-        }
 
-        if (shell) {
-            // Escape command & arguments
-            applyQuotes = (command !== 'echo');  // Do not quote arguments for the special "echo" command
-            command = escapeCommand(command);
-            args = args.map(function (arg) {
-                return escapeArg(arg, applyQuotes);
-            });
-
-            // Use cmd.exe
-            args = ['/s', '/c', '"' + command + (args.length ? ' ' + args.join(' ') : '') + '"'];
-            command = process.env.comspec || 'cmd.exe';
-
-            // Tell node's spawn that the arguments are already escaped
-            options.windowsVerbatimArguments = true;
-        }
-    }
-
-    return {
+    // Build our parsed object
+    parsed = {
         command: command,
         args: args,
         options: options,
-        file: file,
-        original: original,
+        file: undefined,
+        original: command,
     };
+
+    // Delegate further parsing to shell or non-shell
+    return options.shell ? parseShell(parsed) : parseNonShell(parsed);
 }
 
 module.exports = parse;
diff --git a/lib/util/escapeArgument.js b/lib/util/escapeArgument.js
new file mode 100644
index 0000000..367263f
--- /dev/null
+++ b/lib/util/escapeArgument.js
@@ -0,0 +1,30 @@
+'use strict';
+
+function escapeArgument(arg, quote) {
+    // Convert to string
+    arg = '' + arg;
+
+    // If we are not going to quote the argument,
+    // escape shell metacharacters, including double and single quotes:
+    if (!quote) {
+        arg = arg.replace(/([()%!^<>&|;,"'\s])/g, '^$1');
+    } else {
+        // Sequence of backslashes followed by a double quote:
+        // double up all the backslashes and escape the double quote
+        arg = arg.replace(/(\\*)"/g, '$1$1\\"');
+
+        // Sequence of backslashes followed by the end of the string
+        // (which will become a double quote later):
+        // double up all the backslashes
+        arg = arg.replace(/(\\*)$/, '$1$1');
+
+        // All other backslashes occur literally
+
+        // Quote the whole thing:
+        arg = '"' + arg + '"';
+    }
+
+    return arg;
+}
+
+module.exports = escapeArgument;
diff --git a/lib/util/escapeCommand.js b/lib/util/escapeCommand.js
new file mode 100644
index 0000000..d9c25b2
--- /dev/null
+++ b/lib/util/escapeCommand.js
@@ -0,0 +1,12 @@
+'use strict';
+
+var escapeArgument = require('./escapeArgument');
+
+function escapeCommand(command) {
+    // Do not escape if this command is not dangerous..
+    // We do this so that commands like "echo" or "ifconfig" work
+    // Quoting them, will make them unaccessible
+    return /^[a-z0-9_-]+$/i.test(command) ? command : escapeArgument(command, true);
+}
+
+module.exports = escapeCommand;
diff --git a/lib/util/hasEmptyArgumentBug.js b/lib/util/hasEmptyArgumentBug.js
new file mode 100644
index 0000000..9f2eba6
--- /dev/null
+++ b/lib/util/hasEmptyArgumentBug.js
@@ -0,0 +1,18 @@
+'use strict';
+
+// See: https://github.com/IndigoUnited/node-cross-spawn/pull/34#issuecomment-221623455
+function hasEmptyArgumentBug() {
+    var nodeVer;
+
+    if (process.platform !== 'win32') {
+        return false;
+    }
+
+    nodeVer = process.version.substr(1).split('.').map(function (num) {
+        return parseInt(num, 10);
+    });
+
+    return (nodeVer[0] === 0 && nodeVer[1] < 12);
+}
+
+module.exports = hasEmptyArgumentBug();
diff --git a/lib/util/readShebang.js b/lib/util/readShebang.js
new file mode 100644
index 0000000..2cf3541
--- /dev/null
+++ b/lib/util/readShebang.js
@@ -0,0 +1,37 @@
+'use strict';
+
+var fs = require('fs');
+var LRU = require('lru-cache');
+var shebangCommand = require('shebang-command');
+
+var shebangCache = new LRU({ max: 50, maxAge: 30 * 1000 });  // Cache just for 30sec
+
+function readShebang(command) {
+    var buffer;
+    var fd;
+    var shebang;
+
+    // Check if it is in the cache first
+    if (shebangCache.has(command)) {
+        return shebangCache.get(command);
+    }
+
+    // Read the first 150 bytes from the file
+    buffer = new Buffer(150);
+
+    try {
+        fd = fs.openSync(command, 'r');
+        fs.readSync(fd, buffer, 0, 150, 0);
+        fs.closeSync(fd);
+    } catch (e) { /* empty */ }
+
+    // Attempt to extract shebang (null is returned if not a shebang)
+    shebang = shebangCommand(buffer.toString());
+
+    // Store the shebang in the cache
+    shebangCache.set(command, shebang);
+
+    return shebang;
+}
+
+module.exports = readShebang;
diff --git a/lib/resolveCommand.js b/lib/util/resolveCommand.js
similarity index 100%
rename from lib/resolveCommand.js
rename to lib/util/resolveCommand.js
diff --git a/package.json b/package.json
index 7d7bb88..7c10c97 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "cross-spawn",
-  "version": "4.0.2",
+  "version": "5.1.0",
   "description": "Cross platform child_process#spawn and child_process#spawnSync",
   "main": "index.js",
   "scripts": {
@@ -37,6 +37,7 @@
   "license": "MIT",
   "dependencies": {
     "lru-cache": "^4.0.1",
+    "shebang-command": "^1.2.0",
     "which": "^1.2.9"
   },
   "devDependencies": {
@@ -46,6 +47,7 @@
     "glob": "^7.0.0",
     "mkdirp": "^0.5.1",
     "mocha": "^3.0.2",
+    "once": "^1.4.0",
     "rimraf": "^2.5.0"
   }
 }
diff --git a/test/prepare.js b/test/prepare.js
index e82c7b6..ba83714 100644
--- a/test/prepare.js
+++ b/test/prepare.js
@@ -2,8 +2,11 @@
 
 var glob = require('glob');
 var fs = require('fs');
+var path = require('path');
 var buffered = require('./util/buffered');
 
+
+// Preare fixtures
 var fixturesDir = __dirname + '/fixtures';
 
 glob.sync('prepare_*', { cwd: __dirname + '/fixtures' }).forEach(function (file) {
@@ -16,6 +19,7 @@ glob.sync('prepare_*', { cwd: __dirname + '/fixtures' }).forEach(function (file)
     process.stdout.write('Copied "' + file + '" to "' + finalFile + '"\n');
 });
 
+// Install spawn-sync for older node versions
 if (/^v0\.10\./.test(process.version)) {
     process.stdout.write('Installing spawn-sync..\n');
     buffered('spawn', 'npm', ['install', 'spawn-sync'], { stdio: 'inherit' }, function (err) {
@@ -26,3 +30,13 @@ if (/^v0\.10\./.test(process.version)) {
         process.exit();
     });
 }
+
+// Fix AppVeyor tests because Git bin folder is in PATH and it has a "echo" program there
+if (process.env.APPVEYOR) {
+    process.env.PATH = process.env.PATH
+    .split(path.delimiter)
+    .filter(function (entry) {
+        return !/\\git\\bin$/i.test(path.normalize(entry));
+    })
+    .join(path.delimiter);
+}
diff --git a/test/test.js b/test/test.js
index 76647a1..7e506ee 100644
--- a/test/test.js
+++ b/test/test.js
@@ -5,23 +5,12 @@ var path = require('path');
 var expect = require('expect.js');
 var rimraf = require('rimraf');
 var mkdirp = require('mkdirp');
-var which = require('which');
-var buffered = require('./util/buffered');
-var hasBrokenSpawn = require('../lib/hasBrokenSpawn');
+var hasEmptyArgumentBug = require('../lib/util/hasEmptyArgumentBug');
 var spawn = require('../');
+var buffered = require('./util/buffered');
 
 var isWin = process.platform === 'win32';
 
-// Fix AppVeyor tests because Git bin folder is in PATH and it has a "echo" program there
-if (isWin) {
-    process.env.PATH = process.env.PATH
-    .split(path.delimiter)
-    .filter(function (entry) {
-        return !/\\git\\bin$/i.test(path.normalize(entry));
-    })
-    .join(path.delimiter);
-}
-
 describe('cross-spawn', function () {
     var methods = ['spawn', 'sync'];
 
@@ -66,32 +55,6 @@ describe('cross-spawn', function () {
                 });
             });
 
-            it('should support shebang in executables without /usr/bin/env', function (next) {
-                var nodejs = which.sync('node');
-                var file = __dirname + '/fixtures/shebang_noenv';
-
-                fs.writeFileSync(file, '#!' + nodejs + '\n\nprocess.stdout.write(\'shebang works!\');', {
-                    mode: parseInt('0777', 8),
-                });
-
-                buffered(method, file, function (err, data, code) {
-                    expect(err).to.not.be.ok();
-                    expect(code).to.be(0);
-                    expect(data).to.equal('shebang works!');
-
-                    // Test if the actual shebang file is resolved against the PATH
-                    process.env.PATH = path.normalize(__dirname + '/fixtures/') + path.delimiter + process.env.PATH;
-
-                    buffered(method, 'shebang_noenv', function (err, data, code) {
-                        expect(err).to.not.be.ok();
-                        expect(code).to.be(0);
-                        expect(data).to.equal('shebang works!');
-
-                        next();
-                    });
-                });
-            });
-
             it('should support shebang in executables with relative path', function (next) {
                 var executable = './' + path.relative(process.cwd(), __dirname + '/fixtures/shebang');
 
@@ -503,7 +466,45 @@ extension\');', { mode: parseInt('0777', 8) });
             });
 
             if (isWin) {
-                if (hasBrokenSpawn) {
+                it('should use nodejs\' spawn when option.shell is specified', function (next) {
+                    buffered(method, 'echo', ['%RANDOM%'], { shell: true }, function (err, data, code) {
+                        expect(err).to.not.be.ok();
+                        expect(code).to.be(0);
+                        expect(data.trim()).to.match(/\d+/);
+
+                        buffered(method, 'echo', ['%RANDOM%'], { shell: false }, function (err, data) {
+                            // In some windows versions, the echo exists outside the shell as echo.exe so we must account for that here
+                            if (err) {
+                                expect(err).to.be.an(Error);
+                                expect(err.message).to.contain('ENOENT');
+                            } else {
+                                expect(data.trim()).to.equal('%RANDOM%');
+                            }
+
+                            next();
+                        });
+                    });
+                });
+            } else {
+                it('should use nodejs\' spawn when option.shell is specified', function (next) {
+                    buffered(method, 'echo', ['hello &&', 'echo there'], { shell: true }, function (err, data, code) {
+                        expect(err).to.not.be.ok();
+                        expect(code).to.be(0);
+                        expect(data.trim()).to.equal('hello\nthere');
+
+                        buffered(method, 'echo', ['hello &&', 'echo there'], { shell: false }, function (err, data) {
+                            expect(err).to.not.be.ok();
+                            expect(code).to.be(0);
+                            expect(data.trim()).to.equal('hello && echo there');
+
+                            next();
+                        });
+                    });
+                });
+            }
+
+            if (isWin) {
+                if (hasEmptyArgumentBug) {
                     it('should spawn a shell for a .exe on old Node', function (next) {
                         buffered(method, __dirname + '/fixtures/win-ppid.js', function (err, data, code) {
                             expect(err).to.not.be.ok();
diff --git a/test/util/buffered.js b/test/util/buffered.js
index 7c2f713..2c0ce59 100644
--- a/test/util/buffered.js
+++ b/test/util/buffered.js
@@ -1,6 +1,7 @@
 'use strict';
 
 var spawn = require('../../index');
+var once = require('once');
 
 function buffered(method, command, args, options, callback) {
     var cp;
@@ -24,6 +25,7 @@ function buffered(method, command, args, options, callback) {
     } else {
         cp = spawn(command, args, options);
         stdout = stderr = null;
+        callback = once(callback);
 
         cp.stdout && cp.stdout.on('data', function (buffer) {
             stdout = stdout || '';

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-cross-spawn.git



More information about the Pkg-javascript-commits mailing list