Socket
Socket
Sign inDemoInstall

cross-spawn

Package Overview
Dependencies
Maintainers
1
Versions
54
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cross-spawn - npm Package Compare versions

Comparing version 5.1.0 to 6.0.0

lib/util/escape.js

34

CHANGELOG.md

@@ -1,3 +0,35 @@

## 5.0.0 - 2016-10-30
# Change Log
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
<a name="6.0.0"></a>
# [6.0.0](https://github.com/moxystudio/node-cross-spawn/compare/5.1.0...6.0.0) (2018-01-23)
### Bug Fixes
* fix certain arguments not being correctly escaped or causing batch syntax error ([900cf10](https://github.com/moxystudio/node-cross-spawn/commit/900cf10)), closes [#82](https://github.com/moxystudio/node-cross-spawn/issues/82) [#51](https://github.com/moxystudio/node-cross-spawn/issues/51) [#82](https://github.com/moxystudio/node-cross-spawn/issues/82) [#51](https://github.com/moxystudio/node-cross-spawn/issues/51)
### BREAKING CHANGES
* remove support for older nodejs versions, only node >= 4 is supported
<a name="5.1.0"></a>
## [5.1.0](https://github.com/moxystudio/node-cross-spawn/compare/5.0.1...5.1.0) (2017-02-26)
- Fix `options.shell` support for NodeJS [v4.8](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V4.md#4.8.0)
<a name="5.0.1"></a>
## [5.0.1](https://github.com/moxystudio/node-cross-spawn/compare/5.0.0...5.0.1) (2016-11-04)
- Fix `options.shell` support for NodeJS v7
<a name="5.0.0"></a>
# [5.0.0](https://github.com/moxystudio/node-cross-spawn/compare/4.0.2...5.0.0) (2016-10-30)
- Add support for `options.shell`

@@ -4,0 +36,0 @@ - Improve parsing of shebangs by using [`shebang-command`](https://github.com/kevva/shebang-command) module

36

index.js
'use strict';
var cp = require('child_process');
var parse = require('./lib/parse');
var enoent = require('./lib/enoent');
const cp = require('child_process');
const parse = require('./lib/parse');
const enoent = require('./lib/enoent');
var cpSpawnSync = cp.spawnSync;
function spawn(command, args, options) {
var parsed;
var spawned;
// Parse the arguments
parsed = parse(command, args, options);
const parsed = parse(command, args, options);
// Spawn the child process
spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);

@@ -27,24 +22,9 @@ // Hook into child process "exit" event to emit an error if the command

function spawnSync(command, args, options) {
var parsed;
var result;
if (!cpSpawnSync) {
try {
cpSpawnSync = require('spawn-sync'); // eslint-disable-line global-require
} catch (ex) {
throw new Error(
'In order to use spawnSync on node 0.10 or older, you must ' +
'install spawn-sync:\n\n' +
' npm install spawn-sync --save'
);
}
}
// Parse the arguments
parsed = parse(command, args, options);
const parsed = parse(command, args, options);
// Spawn the child process
result = cpSpawnSync(parsed.command, parsed.args, parsed.options);
const result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
// Analyze if the command does not exists, see: https://github.com/IndigoUnited/node-cross-spawn/issues/16
// Analyze if the command does not exist, see: https://github.com/IndigoUnited/node-cross-spawn/issues/16
result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);

@@ -51,0 +31,0 @@

'use strict';
var isWin = process.platform === 'win32';
var resolveCommand = require('./util/resolveCommand');
const isWin = process.platform === 'win32';
var isNode10 = process.version.indexOf('v0.10.') === 0;
function notFoundError(command, syscall) {
var err;
err = new Error(syscall + ' ' + command + ' ENOENT');
err.code = err.errno = 'ENOENT';
err.syscall = syscall + ' ' + command;
return err;
function notFoundError(original, syscall) {
return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), {
code: 'ENOENT',
errno: 'ENOENT',
syscall: `${syscall} ${original.command}`,
path: original.command,
spawnargs: original.args,
});
}
function hookChildProcess(cp, parsed) {
var originalEmit;
if (!isWin) {

@@ -25,11 +20,10 @@ return;

originalEmit = cp.emit;
const 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
// See https://github.com/IndigoUnited/node-cross-spawn/issues/16
if (name === 'exit') {
err = verifyENOENT(arg1, parsed, 'spawn');
const err = verifyENOENT(arg1, parsed, 'spawn');

@@ -41,3 +35,3 @@ if (err) {

return originalEmit.apply(cp, arguments);
return originalEmit.apply(cp, arguments); // eslint-disable-line prefer-rest-params
};

@@ -59,18 +53,10 @@ }

// If we are in node 10, then we are using spawn-sync; if it exited
// with -1 it probably means that the command does not exist
if (isNode10 && status === -1) {
parsed.file = isWin ? parsed.file : resolveCommand(parsed.original);
if (!parsed.file) {
return notFoundError(parsed.original, 'spawnSync');
}
}
return null;
}
module.exports.hookChildProcess = hookChildProcess;
module.exports.verifyENOENT = verifyENOENT;
module.exports.verifyENOENTSync = verifyENOENTSync;
module.exports.notFoundError = notFoundError;
module.exports = {
hookChildProcess,
verifyENOENT,
verifyENOENTSync,
notFoundError,
};
'use strict';
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');
const path = require('path');
const niceTry = require('nice-try');
const resolveCommand = require('./util/resolveCommand');
const escape = require('./util/escape');
const readShebang = require('./util/readShebang');
const semver = require('semver');
var isWin = process.platform === 'win32';
var skipShellRegExp = /\.(?:com|exe)$/i;
const isWin = process.platform === 'win32';
const isExecutableRegExp = /\.(?:com|exe)$/i;
const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
// 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;
// `options.shell` is supported in Node ^4.8.0, ^5.7.0 and >= 6.0.0
const supportsShellOption = niceTry(() => semver.satisfies(process.version, '^4.8.0 || ^5.7.0 || >= 6.0.0', true)) || false;
function detectShebang(parsed) {
parsed.file = resolveCommand(parsed);
const shebang = parsed.file && readShebang(parsed.file);
if (shebang) {
parsed.args.unshift(parsed.file);
parsed.command = shebang;
return resolveCommand(parsed);
}
return parsed.file;
}
function parseNonShell(parsed) {
var shebang;
var needsShell;
var applyQuotes;
if (!isWin) {

@@ -26,25 +38,23 @@ return parsed;

// Detect & add support for shebangs
parsed.file = resolveCommand(parsed.command);
parsed.file = parsed.file || resolveCommand(parsed.command, true);
shebang = parsed.file && readShebang(parsed.file);
const commandFile = detectShebang(parsed);
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);
}
// We don't need a shell if the command filename is an executable
const needsShell = !isExecutableRegExp.test(commandFile);
// If a shell is required, use cmd.exe and take care of escaping everything correctly
if (needsShell) {
// Note that `forceShell` is an hidden option used only in tests
if (parsed.options.forceShell || needsShell) {
// Need to double escape meta chars if the command is a cmd-shim located in `node_modules/.bin/`
// The cmd-shim simply calls execute the package bin file with NodeJS, proxying any argument
// Because the escape of metachars with ^ gets interpreted when the cmd.exe is first called,
// we need to double escape them
const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
// 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);
});
parsed.command = escape.command(parsed.command);
parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
// Make use of cmd.exe
parsed.args = ['/d', '/s', '/c', '"' + parsed.command + (parsed.args.length ? ' ' + parsed.args.join(' ') : '') + '"'];
const shellCommand = [parsed.command].concat(parsed.args).join(' ');
parsed.args = ['/d', '/s', '/c', `"${shellCommand}"`];
parsed.command = process.env.comspec || 'cmd.exe';

@@ -58,4 +68,2 @@ parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped

function parseShell(parsed) {
var shellCommand;
// If node supports the shell option, there's no need to mimic its behavior

@@ -66,8 +74,9 @@ if (supportsShellOption) {

// Mimic node shell option, see: https://github.com/nodejs/node/blob/b9f6a2dc059a1062776133f3d4fd848c4da7d150/lib/child_process.js#L335
shellCommand = [parsed.command].concat(parsed.args).join(' ');
// Mimic node shell option
// See https://github.com/nodejs/node/blob/b9f6a2dc059a1062776133f3d4fd848c4da7d150/lib/child_process.js#L335
const shellCommand = [parsed.command].concat(parsed.args).join(' ');
if (isWin) {
parsed.command = typeof parsed.options.shell === 'string' ? parsed.options.shell : process.env.comspec || 'cmd.exe';
parsed.args = ['/d', '/s', '/c', '"' + shellCommand + '"'];
parsed.args = ['/d', '/s', '/c', `"${shellCommand}"`];
parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped

@@ -89,7 +98,3 @@ } else {

// ------------------------------------------------
function parse(command, args, options) {
var parsed;
// Normalize arguments, similar to nodejs

@@ -101,12 +106,15 @@ if (args && !Array.isArray(args)) {

args = args ? args.slice(0) : []; // Clone array to avoid changing the original
options = options || {};
args = args ? args.slice(0) : []; // Clone array to avoid changing the original
options = Object.assign({}, options); // Clone object to avoid changing the original
// Build our parsed object
parsed = {
command: command,
args: args,
options: options,
const parsed = {
command: path.normalize(command),
args,
options,
file: undefined,
original: command,
original: {
command,
args,
},
};

@@ -113,0 +121,0 @@

'use strict';
var fs = require('fs');
var LRU = require('lru-cache');
var shebangCommand = require('shebang-command');
const fs = require('fs');
const 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);
let fd;
const buffer = new Buffer(150);

@@ -26,13 +15,8 @@ try {

fs.closeSync(fd);
} catch (e) { /* empty */ }
} 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;
return shebangCommand(buffer.toString());
}
module.exports = readShebang;
'use strict';
var path = require('path');
var which = require('which');
var LRU = require('lru-cache');
const path = require('path');
const which = require('which');
const pathKey = require('path-key')();
var commandCache = new LRU({ max: 50, maxAge: 30 * 1000 }); // Cache just for 30sec
function resolveCommandAttempt(parsed, withPathExt) {
withPathExt = !!withPathExt;
function resolveCommand(command, noExtension) {
var resolved;
noExtension = !!noExtension;
resolved = commandCache.get(command + '!' + noExtension);
// Check if its resolved in the cache
if (commandCache.has(command)) {
return commandCache.get(command);
}
try {
resolved = !noExtension ?
which.sync(command) :
which.sync(command, { pathExt: path.delimiter + (process.env.PATHEXT || '') });
} catch (e) { /* empty */ }
return which.sync(parsed.command, {
path: (parsed.options.env || process.env)[pathKey],
pathExt: withPathExt && process.env.PATHEXT ? path.delimiter + process.env.PATHEXT : undefined,
});
} catch (e) { /* Empty */ }
}
commandCache.set(command + '!' + noExtension, resolved);
return resolved;
function resolveCommand(parsed) {
return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true);
}
module.exports = resolveCommand;
{
"name": "cross-spawn",
"version": "5.1.0",
"version": "6.0.0",
"description": "Cross platform child_process#spawn and child_process#spawnSync",
"main": "index.js",
"scripts": {
"test": "node test/prepare && mocha --bail test/test",
"lint": "eslint '{*.js,lib/**/*.js,test/**/*.js}'"
},
"bugs": {
"url": "https://github.com/IndigoUnited/node-cross-spawn/issues/"
},
"repository": {
"type": "git",
"url": "git://github.com/IndigoUnited/node-cross-spawn.git"
},
"files": [
"index.js",
"lib"
],
"keywords": [

@@ -25,17 +9,47 @@ "spawn",

"windows",
"cross",
"platform",
"path",
"ext",
"cross-platform",
"path-ext",
"path_ext",
"shebang",
"hashbang",
"cmd",
"execute"
],
"author": "IndigoUnited <hello@indigounited.com> (http://indigounited.com)",
"author": "André Cruz <andre@moxy.studio>",
"homepage": "https://github.com/moxystudio/node-cross-spawn",
"repository": {
"type": "git",
"url": "git@github.com:moxystudio/node-cross-spawn.git"
},
"license": "MIT",
"main": "index.js",
"files": [
"lib"
],
"scripts": {
"lint": "eslint .",
"test": "jest --env node --coverage",
"prerelease": "npm t && npm run lint",
"release": "standard-version",
"precommit": "lint-staged",
"commitmsg": "commitlint -e $GIT_PARAMS"
},
"standard-version": {
"scripts": {
"posttag": "git push --follow-tags origin master && npm publish"
}
},
"lint-staged": {
"*.js": [
"eslint --fix",
"git add"
]
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"dependencies": {
"lru-cache": "^4.0.1",
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
"semver": "^5.5.0",
"shebang-command": "^1.2.0",

@@ -45,11 +59,20 @@ "which": "^1.2.9"

"devDependencies": {
"@satazor/eslint-config": "^3.0.0",
"eslint": "^3.0.0",
"expect.js": "^0.3.0",
"glob": "^7.0.0",
"@commitlint/cli": "^6.0.0",
"@commitlint/config-conventional": "^6.0.2",
"babel-core": "^6.26.0",
"babel-jest": "^22.1.0",
"babel-preset-moxy": "^2.0.4",
"eslint": "^4.3.0",
"eslint-config-moxy": "^4.1.0",
"husky": "^0.14.3",
"jest": "^22.0.0",
"lint-staged": "^6.0.0",
"mkdirp": "^0.5.1",
"mocha": "^3.0.2",
"once": "^1.4.0",
"rimraf": "^2.5.0"
"regenerator-runtime": "^0.11.1",
"rimraf": "^2.6.2",
"standard-version": "^4.2.0"
},
"engines": {
"node": ">=4.8"
}
}
# cross-spawn
[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Build status][appveyor-image]][appveyor-url] [![Dependency status][david-dm-image]][david-dm-url] [![Dev Dependency status][david-dm-dev-image]][david-dm-dev-url]
[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Build status][appveyor-image]][appveyor-url] [![Dependency status][david-dm-image]][david-dm-url] [![Dev Dependency status][david-dm-dev-image]][david-dm-dev-url] [![Greenkeeper badge][greenkeeper-image]][greenkeeper-url]

@@ -8,10 +8,12 @@ [npm-url]:https://npmjs.org/package/cross-spawn

[npm-image]:http://img.shields.io/npm/v/cross-spawn.svg
[travis-url]:https://travis-ci.org/IndigoUnited/node-cross-spawn
[travis-image]:http://img.shields.io/travis/IndigoUnited/node-cross-spawn/master.svg
[travis-url]:https://travis-ci.org/moxystudio/node-cross-spawn
[travis-image]:http://img.shields.io/travis/moxystudio/node-cross-spawn/master.svg
[appveyor-url]:https://ci.appveyor.com/project/satazor/node-cross-spawn
[appveyor-image]:https://img.shields.io/appveyor/ci/satazor/node-cross-spawn/master.svg
[david-dm-url]:https://david-dm.org/IndigoUnited/node-cross-spawn
[david-dm-image]:https://img.shields.io/david/IndigoUnited/node-cross-spawn.svg
[david-dm-dev-url]:https://david-dm.org/IndigoUnited/node-cross-spawn#info=devDependencies
[david-dm-dev-image]:https://img.shields.io/david/dev/IndigoUnited/node-cross-spawn.svg
[david-dm-url]:https://david-dm.org/moxystudio/node-cross-spawn
[david-dm-image]:https://img.shields.io/david/moxystudio/node-cross-spawn.svg
[david-dm-dev-url]:https://david-dm.org/moxystudio/node-cross-spawn?type=dev
[david-dm-dev-image]:https://img.shields.io/david/dev/moxystudio/node-cross-spawn.svg
[greenkeeper-image]:https://badges.greenkeeper.io/moxystudio/node-cross-spawn.svg
[greenkeeper-url]:https://greenkeeper.io/

@@ -25,7 +27,3 @@ A cross platform solution to node's spawn and spawnSync.

If you are using `spawnSync` on node 0.10 or older, you will also need to install `spawn-sync`:
`$ npm install spawn-sync`
## Why

@@ -36,5 +34,7 @@

- 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 support [shebangs](https://en.wikipedia.org/wiki/Shebang_(Unix))
- No `options.shell` support on node `<v4.8`
- Has problems running commands with [spaces](https://github.com/nodejs/node/issues/7367)
- Has problems running commands with posix relative paths (e.g.: `my-folder/my-executable`)
- Circuvents an [issue](https://github.com/moxystudio/node-cross-spawn/issues/82) around command shims (files in node_modules/.bin/), where arguments with quotes and parenthesis would result in an [invalid syntax error](https://github.com/moxystudio/node-cross-spawn/blob/e77b8f22a416db46b6196767bcd35601d7e11d54/test/index.test.js#L149)

@@ -63,17 +63,23 @@ All these issues are handled correctly by `cross-spawn`.

#### `options.shell` as an alternative to `cross-spawn`
### Using `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:
Starting from node `v4.8`, `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
- It's not supported in node `<v4.8`
- You must manually escape the command and arguments which is very error prone, specially when passing user input
- It just solves the [PATHEXT](https://github.com/joyent/node/issues/2318) issue from the [Why](#why) section
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.
### `options.shell` support
#### Shebangs
While `cross-spawn` adds support for `options.shell` in node `<v4.8`, all of its enhancements are disabled.
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`.
This mimics the Node.js behavior. More specifically, the command and its arguments will not be automatically escaped nor shebang support will be offered. This is by design because if you are using `options.shell` you are probably targeting a specific platform anyway and you don't want things to get into your way.
### Shebangs support
While `cross-spawn` handles shebangs on Windows, its support is limited. More specifically, it just supports `#!/usr/bin/env <program>` where `<program>` must not contain any arguments.
If you would like to have the shebang support improved, feel free to contribute via a pull-request.
Remember to always test your code on Windows!

@@ -80,0 +86,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc