[Pkg-javascript-commits] [node-cross-spawn-async] 01/07: Imported Upstream version 2.0.0

Ross Gammon ross-guest at moszumanska.debian.org
Wed Sep 23 20:03:16 UTC 2015


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-async.

commit 73c18a6403ba70751c8bd093b9c2515be179897b
Author: Ross Gammon <rossgammon at mail.dk>
Date:   Wed Sep 9 23:18:59 2015 +0200

    Imported Upstream version 2.0.0
---
 .editorconfig                      |  15 ++
 .gitignore                         |   4 +
 .jshintrc                          |  62 ++++++
 .npmignore                         |   3 +
 .travis.yml                        |   4 +
 LICENSE                            |  19 ++
 README.md                          |  42 ++++
 appveyor.yml                       |  28 +++
 index.js                           |  23 +++
 lib/enoent.js                      |  51 +++++
 lib/parse.js                       | 129 ++++++++++++
 lib/resolveCommand.js              |  31 +++
 lib/util/mixIn.js                  |  15 ++
 package.json                       |  41 ++++
 test/fixtures/()%!^&;, .bat        |   2 +
 test/fixtures/bar space            |   3 +
 test/fixtures/bar space.bat        |   2 +
 test/fixtures/echo.js              |   5 +
 test/fixtures/exit.js              |   1 +
 test/fixtures/exit1                |   3 +
 test/fixtures/exit1.bat            |   1 +
 test/fixtures/foo                  |   3 +
 test/fixtures/foo.bat              |   2 +
 test/fixtures/prepare_()%!^&;, .sh |   3 +
 test/fixtures/shebang              |   3 +
 test/fixtures/shebang_enoent       |   3 +
 test/prepare.js                    |  14 ++
 test/test.js                       | 403 +++++++++++++++++++++++++++++++++++++
 test/util/buffered.js              |  33 +++
 29 files changed, 948 insertions(+)

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..fe988fc
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[.gitignore]
+trim_trailing_whitespace = false
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c7fd650
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+node_modules/
+npm-debug.*
+test/fixtures/(*
+test/fixtures/shebang_noenv
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..5595ed2
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,62 @@
+{
+    "predef": [
+        "console",
+        "describe",
+        "it",
+        "after",
+        "afterEach",
+        "before",
+        "beforeEach"
+    ],
+
+    "indent": 4,
+    "node": true,
+    "devel": true,
+
+    "bitwise": false,
+    "curly": false,
+    "eqeqeq": true,
+    "forin": false,
+    "immed": true,
+    "latedef": false,
+    "newcap": true,
+    "noarg": true,
+    "noempty": false,
+    "nonew": true,
+    "plusplus": false,
+    "regexp": false,
+    "undef": true,
+    "unused": "vars",
+    "quotmark": "single",
+    "strict": false,
+    "trailing": true,
+    "camelcase": true,
+
+    "asi": false,
+    "boss": true,
+    "debug": false,
+    "eqnull": true,
+    "es5": false,
+    "esnext": false,
+    "evil": false,
+    "expr": true,
+    "funcscope": false,
+    "globalstrict": false,
+    "iterator": false,
+    "lastsemic": false,
+    "laxbreak": true,
+    "laxcomma": false,
+    "loopfunc": true,
+    "multistr": false,
+    "onecase": true,
+    "regexdash": false,
+    "scripturl": false,
+    "smarttabs": false,
+    "shadow": false,
+    "sub": false,
+    "supernew": true,
+    "validthis": false,
+
+    "nomen": false,
+    "white": true
+}
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..93f2f73
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,3 @@
+node_modules/
+npm-debug.*
+test/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..4feb925
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+  - '0.10'
+  - '0.12'
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e898822
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 IndigoUnited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8e96bda
--- /dev/null
+++ b/README.md
@@ -0,0 +1,42 @@
+# cross-spawn-async [![Build Status](https://travis-ci.org/IndigoUnited/node-cross-spawn-async.svg?branch=master)](https://travis-ci.org/IndigoUnited/node-cross-spawn-async) [![Build status](https://ci.appveyor.com/api/projects/status/9rglfmcmxuu5lbcq/branch/master?svg=true)](https://ci.appveyor.com/project/satazor/node-cross-spawn-async/branch/master)
+
+A cross platform solution to node's spawn.
+
+
+## Installation
+
+`$ npm install cross-spawn-async`
+
+
+## Why
+
+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)
+- 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-async`.
+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.
+
+
+## Usage
+
+Exactly the same way as node's [`spawn`](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options), so it's a drop in replacement.
+
+```javascript
+var spawn = require('cross-spawn-async');
+
+var process = spawn('npm', ['list', '-g', '-depth' '0'], { stdio: 'inherit' });
+```
+
+
+## Tests
+
+`$ npm test`
+
+
+## License
+
+Released under the [MIT License](http://www.opensource.org/licenses/mit-license.php).
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..22c6f43
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,28 @@
+# appveyor file
+# http://www.appveyor.com/docs/appveyor-yml
+
+# build version format
+version: "{build}"
+
+# fix lineendings in Windows
+init:
+  - git config --global core.autocrlf input
+
+# what combinations to test
+environment:
+  matrix:
+    - nodejs_version: 0.10
+    - nodejs_version: 0.12
+
+# get the latest stable version of Node 0.STABLE.latest
+install:
+  - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
+  - npm install
+
+build: off
+
+test_script:
+  - node --version
+  - npm --version
+  - ps: npm test --no-color  # PowerShell
+  - cmd: npm test --no-color
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..c8d678d
--- /dev/null
+++ b/index.js
@@ -0,0 +1,23 @@
+var cp     = require('child_process');
+var parse  = require('./lib/parse');
+var enoent = require('./lib/enoent');
+
+function spawn(command, args, options) {
+    var parsed;
+    var spawned;
+
+    // Parse the arguments
+    parsed = parse(command, args, options);
+
+    // Spawn the child process
+    spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
+
+    // Hook into child process "exit" event to emit an error if the command
+    // does not exists, see: https://github.com/IndigoUnited/node-cross-spawn/issues/16
+    enoent.hookChildProcess(spawned, parsed);
+
+    return spawned;
+}
+
+module.exports       = spawn;
+module.exports.spawn = spawn;
diff --git a/lib/enoent.js b/lib/enoent.js
new file mode 100644
index 0000000..e09d7f5
--- /dev/null
+++ b/lib/enoent.js
@@ -0,0 +1,51 @@
+'use strict';
+
+var isWin = process.platform === 'win32';
+
+function notFoundError(command, syscall) {
+    var err;
+
+    err = new Error(syscall + ' ' + command + ' ENOENT');
+    err.code = err.errno = 'ENOENT';
+    err.syscall = syscall + ' ' + command;
+
+    return err;
+}
+
+function hookChildProcess(cp, parsed) {
+    var originalEmit;
+
+    if (!isWin) {
+        return;
+    }
+
+    originalEmit = cp.emit;
+    cp.emit = function (name, arg1) {
+        var err;
+
+        // If emitting "exit" event and exit code is 1, we need to check if
+        // the command exists and emit an "error" instead
+        // See: https://github.com/IndigoUnited/node-cross-spawn/issues/16
+        if (name === 'exit') {
+            err = verifyENOENT(arg1, parsed, 'spawn');
+
+            if (err) {
+                return originalEmit.call(cp, 'error', err);
+            }
+        }
+
+        return originalEmit.apply(cp, arguments);
+    };
+}
+
+function verifyENOENT(status, parsed, syscall) {
+    if (isWin && status === 1 && !parsed.file) {
+        return notFoundError(parsed.original, syscall);
+    }
+
+    return null;
+}
+
+module.exports.hookChildProcess = hookChildProcess;
+module.exports.verifyENOENT     = verifyENOENT;
+module.exports.notFoundError    = notFoundError;
diff --git a/lib/parse.js b/lib/parse.js
new file mode 100644
index 0000000..410c6e3
--- /dev/null
+++ b/lib/parse.js
@@ -0,0 +1,129 @@
+var fs             = require('fs');
+var LRU            = require('lru-cache');
+var resolveCommand = require('./resolveCommand');
+var mixIn          = require('./util/mixIn');
+
+var isWin        = process.platform === 'win32';
+var shebangCache = LRU({ max: 50, maxAge: 30 * 1000 });  // Cache just for 30sec
+
+function readShebang(command) {
+    var buffer;
+    var fd;
+    var match;
+    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);
+    } catch (e) {}
+
+    // Check if it is a shebang
+    match = buffer.toString().trim().match(/\#\!(.+)/i);
+
+    if (match) {
+        shebang = match[1].replace(/\/usr\/bin\/env\s+/i, '');   // Remove /usr/bin/env
+    }
+
+    // Store the shebang in the cache
+    shebangCache.set(command, shebang);
+
+    return shebang;
+}
+
+function escapeArg(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(/([\(\)%!\^<>&|;,"' ])/g, '^$1');
+    } else {
+        // Sequence of backslashes followed by a double quote:
+        // double up all the backslashes and escape the double quote
+        arg = arg.replace(/(\\*)"/gi, '$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;
+}
+
+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 unnaccessible
+    return /^[a-z0-9_-]+$/i.test(command) ? command : escapeArg(command, true);
+}
+
+function parseCall(command, args, options) {
+    var shebang;
+    var applyQuotes;
+    var file;
+    var original;
+
+    // Normalize arguments, similar to nodejs
+    if (args && !Array.isArray(args)) {
+        options = args;
+        args = null;
+    }
+
+    args = args ? args.slice(0) : [];  // Clone array to avoid changing the original
+    options = mixIn({}, options);
+    original = command;
+
+    if (isWin) {
+        // Detect & add support for shebangs
+        // Note that if we were able to resolve the command, we skip this step entirely
+        file = resolveCommand(command);
+
+        if (!file) {
+            file = resolveCommand(command, true);
+            shebang = file && readShebang(file);
+            if (shebang) {
+                args.unshift(file);
+                command = shebang;
+            }
+        }
+
+        // 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 {
+        command: command,
+        args: args,
+        options: options,
+        file: file,
+        original: original
+    };
+}
+
+module.exports = parseCall;
diff --git a/lib/resolveCommand.js b/lib/resolveCommand.js
new file mode 100644
index 0000000..943d895
--- /dev/null
+++ b/lib/resolveCommand.js
@@ -0,0 +1,31 @@
+'use strict';
+
+var path  = require('path');
+var which = require('which');
+var LRU   = require('lru-cache');
+
+var resolveCache = LRU({ max: 50, maxAge: 30 * 1000 });  // Cache just for 30sec
+
+function resolveCommand(command, noExtension) {
+    var resolved;
+
+    noExtension = !!noExtension;
+    resolved = resolveCache.get(command + '!' + noExtension);
+
+    // Check if its resolved in the cache
+    if (resolveCache.has(command)) {
+        return resolveCache.get(command);
+    }
+
+    try {
+        resolved = !noExtension ?
+            which.sync(command) :
+            which.sync(command, { pathExt: path.delimiter + (process.env.PATHEXT || '')  });
+    } catch (e) {}
+
+    resolveCache.set(command + '!' + noExtension, resolved);
+
+    return resolved;
+}
+
+module.exports = resolveCommand;
diff --git a/lib/util/mixIn.js b/lib/util/mixIn.js
new file mode 100644
index 0000000..9483f8b
--- /dev/null
+++ b/lib/util/mixIn.js
@@ -0,0 +1,15 @@
+'use strict';
+
+function mixIn(target, source) {
+    var key;
+
+    // No need to check for hasOwnProperty.. this is used
+    // just in plain objects
+    for (key in source) {
+        target[key] = source[key];
+    }
+
+    return target;
+}
+
+module.exports = mixIn;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..f56cf7e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,41 @@
+{
+  "name": "cross-spawn-async",
+  "version": "2.0.0",
+  "description": "Cross platform child_process#spawn",
+  "main": "index.js",
+  "scripts": {
+    "test": "node test/prepare && mocha --bail -R spec test/test"
+  },
+  "bugs": {
+    "url": "https://github.com/IndigoUnited/node-cross-spawn-async/issues/"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/IndigoUnited/node-cross-spawn-async.git"
+  },
+  "keywords": [
+    "spawn",
+    "windows",
+    "cross",
+    "platform",
+    "path",
+    "ext",
+    "path-ext",
+    "path_ext",
+    "shebang",
+    "hashbang",
+    "cmd",
+    "execute"
+  ],
+  "author": "IndigoUnited <hello at indigounited.com> (http://indigounited.com)",
+  "license": "MIT",
+  "dependencies": {
+    "lru-cache": "^2.6.5",
+    "which": "^1.1.1"
+  },
+  "devDependencies": {
+    "expect.js": "^0.3.0",
+    "glob": "^5.0.12",
+    "mocha": "^2.2.5"
+  }
+}
diff --git a/test/fixtures/()%!^&;, .bat b/test/fixtures/()%!^&;, .bat
new file mode 100755
index 0000000..f248e1f
--- /dev/null
+++ b/test/fixtures/()%!^&;, .bat	
@@ -0,0 +1,2 @@
+ at echo off
+echo special
diff --git a/test/fixtures/bar space b/test/fixtures/bar space
new file mode 100755
index 0000000..b492ccc
--- /dev/null
+++ b/test/fixtures/bar space	
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo bar
diff --git a/test/fixtures/bar space.bat b/test/fixtures/bar space.bat
new file mode 100755
index 0000000..96332af
--- /dev/null
+++ b/test/fixtures/bar space.bat	
@@ -0,0 +1,2 @@
+ at echo off
+echo bar
diff --git a/test/fixtures/echo.js b/test/fixtures/echo.js
new file mode 100755
index 0000000..430da7a
--- /dev/null
+++ b/test/fixtures/echo.js
@@ -0,0 +1,5 @@
+var args = process.argv.slice(2);
+
+args.forEach(function (arg, index) {
+    process.stdout.write(arg + (index < args.length - 1 ? '\n' : ''));
+});
diff --git a/test/fixtures/exit.js b/test/fixtures/exit.js
new file mode 100644
index 0000000..ea1a66f
--- /dev/null
+++ b/test/fixtures/exit.js
@@ -0,0 +1 @@
+process.exit(25);
diff --git a/test/fixtures/exit1 b/test/fixtures/exit1
new file mode 100755
index 0000000..d87f29e
--- /dev/null
+++ b/test/fixtures/exit1
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+exit 1
diff --git a/test/fixtures/exit1.bat b/test/fixtures/exit1.bat
new file mode 100755
index 0000000..379a4c9
--- /dev/null
+++ b/test/fixtures/exit1.bat
@@ -0,0 +1 @@
+exit 1
diff --git a/test/fixtures/foo b/test/fixtures/foo
new file mode 100755
index 0000000..17f236c
--- /dev/null
+++ b/test/fixtures/foo
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo foo
diff --git a/test/fixtures/foo.bat b/test/fixtures/foo.bat
new file mode 100755
index 0000000..1d98135
--- /dev/null
+++ b/test/fixtures/foo.bat
@@ -0,0 +1,2 @@
+ at echo off
+echo foo
diff --git a/test/fixtures/prepare_()%!^&;, .sh b/test/fixtures/prepare_()%!^&;, .sh
new file mode 100755
index 0000000..051009c
--- /dev/null
+++ b/test/fixtures/prepare_()%!^&;, .sh	
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo special
diff --git a/test/fixtures/shebang b/test/fixtures/shebang
new file mode 100755
index 0000000..5650cbd
--- /dev/null
+++ b/test/fixtures/shebang
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+process.stdout.write('shebang works!');
diff --git a/test/fixtures/shebang_enoent b/test/fixtures/shebang_enoent
new file mode 100755
index 0000000..bd86265
--- /dev/null
+++ b/test/fixtures/shebang_enoent
@@ -0,0 +1,3 @@
+#!/usr/bin/env somecommandthatwillneverexist
+
+echo foo
diff --git a/test/prepare.js b/test/prepare.js
new file mode 100644
index 0000000..addb078
--- /dev/null
+++ b/test/prepare.js
@@ -0,0 +1,14 @@
+var glob = require('glob');
+var fs   = require('fs');
+
+var fixturesDir = __dirname + '/fixtures';
+
+glob.sync('prepare_*', { cwd: __dirname + '/fixtures' }).forEach(function (file) {
+    var contents = fs.readFileSync(fixturesDir + '/' + file);
+    var finalFile = file.replace(/^prepare_/, '').replace(/\.sh$/, '');
+
+    fs.writeFileSync(fixturesDir + '/' + finalFile, contents);
+    fs.chmodSync(fixturesDir + '/' + finalFile, 0777);
+
+    process.stdout.write('Copied "' + file + '" to "' + finalFile + '"\n');
+});
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..083a95c
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,403 @@
+'use strict';
+
+var path     = require('path');
+var fs       = require('fs');
+var which    = require('which');
+var expect   = require('expect.js');
+var buffered = require('./util/buffered');
+var spawn    = require('../');
+
+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 () {
+    it('should support shebang in executables (with /usr/bin/env)', function (next) {
+        buffered(__dirname + '/fixtures/shebang', function (err, data, code) {
+            var envPath;
+
+            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
+            envPath = process.env.PATH;
+            process.env.PATH = path.normalize(__dirname + '/fixtures/') + path.delimiter + process.env.PATH;
+
+            buffered('shebang', function (err, data, code) {
+                process.env.PATH = envPath;
+
+                expect(err).to.not.be.ok();
+                expect(code).to.be(0);
+                expect(data).to.equal('shebang works!');
+
+                next();
+            });
+        });
+    });
+
+    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(file, function (err, data, code) {
+            var envPath;
+
+            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
+            envPath = process.env.PATH;
+            process.env.PATH = path.normalize(__dirname + '/fixtures/') + path.delimiter + process.env.PATH;
+
+            buffered('shebang_noenv', function (err, data, code) {
+                process.env.PATH = envPath;
+
+                expect(err).to.not.be.ok();
+                expect(code).to.be(0);
+                expect(data).to.equal('shebang works!');
+
+                next();
+            });
+        });
+    });
+
+    it('should expand using PATHEXT properly', function (next) {
+        buffered(__dirname + '/fixtures/foo', function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+            expect(data.trim()).to.equal('foo');
+
+            next();
+        });
+    });
+
+    it('should handle commands with spaces', function (next) {
+        buffered(__dirname + '/fixtures/bar space', function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+            expect(data.trim()).to.equal('bar');
+
+            next();
+        });
+    });
+
+    it('should handle commands with special shell chars', function (next) {
+        buffered(__dirname + '/fixtures/()%!^&;, ', function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+            expect(data.trim()).to.equal('special');
+
+            next();
+        });
+    });
+
+    it('should handle empty arguments', function (next) {
+        buffered('node', [
+            __dirname + '/fixtures/echo',
+            'foo',
+            '',
+            'bar'
+        ], function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+            expect(data).to.equal('foo\n\nbar');
+
+            buffered('echo', [
+                'foo',
+                '',
+                'bar'
+            ], function (err, data, code) {
+                expect(err).to.not.be.ok();
+                expect(code).to.be(0);
+                expect(data.trim()).to.equal('foo  bar');
+
+                next();
+            });
+        });
+    });
+
+    it('should handle non-string arguments', function (next) {
+        buffered('node', [
+            __dirname + '/fixtures/echo',
+            1234
+        ], function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+            expect(data).to.equal('1234');
+
+            next();
+        });
+    });
+
+    it('should handle arguments with spaces', function (next) {
+        buffered('node', [
+            __dirname + '/fixtures/echo',
+            'I am',
+            'André Cruz'
+        ], function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+            expect(data).to.equal('I am\nAndré Cruz');
+
+            next();
+        });
+    });
+
+    it('should handle arguments with \\"', function (next) {
+        buffered('node', [
+            __dirname + '/fixtures/echo',
+            'foo',
+            '\\"',
+            'bar'
+        ], function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+            expect(data).to.equal('foo\n\\"\nbar');
+
+            next();
+        });
+    });
+
+    it('should handle arguments that end with \\', function (next) {
+        buffered('node', [
+            __dirname + '/fixtures/echo',
+            'foo',
+            'bar\\',
+            'baz'
+        ], function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+            expect(data).to.equal('foo\nbar\\\nbaz');
+
+            next();
+        });
+    });
+
+    it('should handle arguments that contain shell special chars', function (next) {
+        buffered('node', [
+            __dirname + '/fixtures/echo',
+            'foo',
+            '()',
+            'foo',
+            '%!',
+            'foo',
+            '^<',
+            'foo',
+            '>&',
+            'foo',
+            '|;',
+            'foo',
+            ', ',
+            'foo'
+        ], function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+            expect(data).to.equal('foo\n()\nfoo\n%!\nfoo\n^<\nfoo\n>&\nfoo\n|;\nfoo\n, \nfoo');
+
+            next();
+        });
+    });
+
+    it('should handle special arguments when using echo', function (next) {
+        buffered('echo', ['foo\\"foo\\foo&bar"foo\'bar'], function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+            expect(data.trim()).to.equal('foo\\"foo\\foo&bar"foo\'bar');
+
+            buffered('echo', [
+                'foo',
+                '()',
+                'foo',
+                '%!',
+                'foo',
+                '^<',
+                'foo',
+                '>&',
+                'foo',
+                '|;',
+                'foo',
+                ', ',
+                'foo'
+            ], function (err, data, code) {
+                expect(err).to.not.be.ok();
+                expect(code).to.be(0);
+                expect(data.trim()).to.equal('foo () foo %! foo ^< foo >& foo |; foo ,  foo');
+
+                next();
+            });
+        });
+    });
+
+    it('should handle optional args correctly', function (next) {
+        buffered(__dirname + '/fixtures/foo', function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+
+            buffered(__dirname + '/fixtures/foo', {
+                stdio: ['pipe', 'ignore', 'pipe'],
+            }, function (err, data, code) {
+                expect(err).to.not.be.ok();
+                expect(code).to.be(0);
+                expect(data).to.be(null);
+
+                buffered(__dirname + '/fixtures/foo', null, {
+                    stdio: ['pipe', 'ignore', 'pipe'],
+                }, function (err, data, code) {
+                    expect(err).to.not.be.ok();
+                    expect(code).to.be(0);
+                    expect(data).to.be(null);
+
+                    next();
+                });
+            });
+        });
+    });
+
+    it('should not mutate args nor options', function (next) {
+        var args = [];
+        var options = {};
+
+        buffered(__dirname + '/fixtures/foo', function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+
+            expect(args).to.have.length(0);
+            expect(Object.keys(options)).to.have.length(0);
+
+            next();
+        });
+    });
+
+    it('should give correct exit code', function (next) {
+        buffered('node', [__dirname + '/fixtures/exit'], function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(25);
+
+            next();
+        });
+    });
+
+    it('should work with a relative command', function (next) {
+        buffered(path.relative(process.cwd(), __dirname + '/fixtures/foo'), function (err, data, code) {
+            expect(err).to.not.be.ok();
+            expect(code).to.be(0);
+            expect(data.trim()).to.equal('foo');
+
+            if (!isWin) {
+                return next();
+            }
+
+            buffered(path.relative(process.cwd(), __dirname + '/fixtures/foo.bat'), function (err, data, code) {
+                expect(err).to.not.be.ok();
+                expect(code).to.be(0);
+                expect(data.trim()).to.equal('foo');
+
+                next();
+            });
+        });
+    });
+
+    it('should emit "error" and "close" if command does not exist', function (next) {
+        var spawned;
+        var errors = [];
+        var timeout;
+
+        this.timeout(5000);
+
+        spawned = spawn('somecommandthatwillneverexist')
+        .on('error', function (err) {
+            errors.push(err);
+        })
+        .on('exit', function () {
+            spawned.removeAllListeners();
+            clearTimeout(timeout);
+            next(new Error('Should not emit exit'));
+        })
+        .on('close', function (code, signal) {
+            expect(code).to.not.be(0);
+            expect(signal).to.be(null);
+
+            timeout = setTimeout(function () {
+                var err;
+
+                expect(errors).to.have.length(1);
+
+                err = errors[0];
+                expect(err).to.be.an(Error);
+                expect(err.message).to.contain('spawn');
+                expect(err.message).to.contain('ENOENT');
+                expect(err.message).to.not.contain('undefined');
+                expect(err.code).to.be('ENOENT');
+                expect(err.errno).to.be('ENOENT');
+                expect(err.syscall).to.contain('spawn');
+                expect(err.syscall).to.not.contain('undefined');
+
+                next();
+            }, 1000);
+        });
+    });
+
+    it('should NOT emit "error" if shebang command does not exist', function (next) {
+        var spawned;
+        var exited;
+        var timeout;
+
+        this.timeout(5000);
+
+        spawned = spawn(__dirname + '/fixtures/shebang_enoent')
+        .on('error', function (err) {
+            spawned.removeAllListeners();
+            clearTimeout(timeout);
+            next(new Error('Should not emit error'));
+        })
+        .on('exit', function () {
+            exited = true;
+        })
+        .on('close', function (code, signal) {
+            expect(code).to.not.be(0);
+            expect(signal).to.be(null);
+            expect(exited).to.be(true);
+
+            timeout = setTimeout(next, 1000);
+        });
+    });
+
+    it('should NOT emit "error" if the command actual exists but exited with 1', function (next) {
+        var spawned;
+        var exited;
+        var timeout;
+
+        this.timeout(5000);
+
+        spawned = spawn(__dirname + '/fixtures/exit1')
+        .on('error', function (err) {
+            spawned.removeAllListeners();
+            clearTimeout(timeout);
+            next(new Error('Should not emit error'));
+        })
+        .on('exit', function () {
+            exited = true;
+        })
+        .on('close', function (code, signal) {
+            expect(code).to.not.be(0);
+            expect(signal).to.be(null);
+            expect(exited).to.be(true);
+
+            timeout = setTimeout(next, 1000);
+        });
+    });
+});
diff --git a/test/util/buffered.js b/test/util/buffered.js
new file mode 100644
index 0000000..e9ce32d
--- /dev/null
+++ b/test/util/buffered.js
@@ -0,0 +1,33 @@
+'use strict';
+
+var spawn = require('../../');
+
+function buffered(command, args, options, callback) {
+    var cp;
+    var data = null;
+
+    if (typeof options === 'function') {
+        callback = options;
+        options = null;
+    }
+
+    if (typeof args === 'function') {
+        callback = args;
+        args = options = null;
+    }
+
+    cp = spawn(command, args, options);
+
+    cp.stdout && cp.stdout.on('data', function(buffer) {
+        data = data || '';
+        data += buffer.toString();
+    });
+
+    cp.on('error', callback);
+
+    cp.on('close', function(code) {
+        callback(null, data, code);
+    });
+}
+
+module.exports = buffered;

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



More information about the Pkg-javascript-commits mailing list