Socket
Socket
Sign inDemoInstall

@npmcli/promise-spawn

Package Overview
Dependencies
2
Maintainers
5
Versions
17
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 5.0.0 to 6.0.0

lib/escape.js

155

lib/index.js

@@ -0,15 +1,23 @@

'use strict'
const { spawn } = require('child_process')
const os = require('os')
const which = require('which')
const isPipe = (stdio = 'pipe', fd) =>
stdio === 'pipe' || stdio === null ? true
: Array.isArray(stdio) ? isPipe(stdio[fd], fd)
: false
const escape = require('./escape.js')
// 'extra' object is for decorating the error a bit more
const promiseSpawn = (cmd, args, opts = {}, extra = {}) => {
if (opts.shell) {
return spawnWithShell(cmd, args, opts, extra)
}
let proc
const p = new Promise((res, rej) => {
proc = spawn(cmd, args, opts)
const stdout = []
const stderr = []
const reject = er => rej(Object.assign(er, {

@@ -21,3 +29,5 @@ cmd,

}))
proc.on('error', reject)
if (proc.stdout) {

@@ -27,2 +37,3 @@ proc.stdout.on('data', c => stdout.push(c)).on('error', reject)

}
if (proc.stderr) {

@@ -32,2 +43,3 @@ proc.stderr.on('data', c => stderr.push(c)).on('error', reject)

}
proc.on('close', (code, signal) => {

@@ -42,2 +54,3 @@ const result = {

}
if (code || signal) {

@@ -56,12 +69,132 @@ rej(Object.assign(new Error('command failed'), result))

const stdioResult = (stdout, stderr, { stdioString, stdio }) =>
stdioString ? {
stdout: isPipe(stdio, 1) ? Buffer.concat(stdout).toString().trim() : null,
stderr: isPipe(stdio, 2) ? Buffer.concat(stderr).toString().trim() : null,
const spawnWithShell = (cmd, args, opts, extra) => {
let command = opts.shell
// if shell is set to true, we use a platform default. we can't let the core
// spawn method decide this for us because we need to know what shell is in use
// ahead of time so that we can escape arguments properly. we don't need coverage here.
if (command === true) {
// istanbul ignore next
command = process.platform === 'win32' ? process.env.ComSpec : 'sh'
}
: {
stdout: isPipe(stdio, 1) ? Buffer.concat(stdout) : null,
stderr: isPipe(stdio, 2) ? Buffer.concat(stderr) : null,
const options = { ...opts, shell: false }
const realArgs = []
let script = cmd
// first, determine if we're in windows because if we are we need to know if we're
// running an .exe or a .cmd/.bat since the latter requires extra escaping
const isCmd = /(?:^|\\)cmd(?:\.exe)?$/i.test(command)
if (isCmd) {
let doubleEscape = false
// find the actual command we're running
let initialCmd = ''
let insideQuotes = false
for (let i = 0; i < cmd.length; ++i) {
const char = cmd.charAt(i)
if (char === ' ' && !insideQuotes) {
break
}
initialCmd += char
if (char === '"' || char === "'") {
insideQuotes = !insideQuotes
}
}
let pathToInitial
try {
pathToInitial = which.sync(initialCmd, {
path: (options.env && options.env.PATH) || process.env.PATH,
pathext: (options.env && options.env.PATHEXT) || process.env.PATHEXT,
}).toLowerCase()
} catch (err) {
pathToInitial = initialCmd.toLowerCase()
}
doubleEscape = pathToInitial.endsWith('.cmd') || pathToInitial.endsWith('.bat')
for (const arg of args) {
script += ` ${escape.cmd(arg, doubleEscape)}`
}
realArgs.push('/d', '/s', '/c', script)
options.windowsVerbatimArguments = true
} else {
for (const arg of args) {
script += ` ${escape.sh(arg)}`
}
realArgs.push('-c', script)
}
return promiseSpawn(command, realArgs, options, extra)
}
// open a file with the default application as defined by the user's OS
const open = (_args, opts = {}, extra = {}) => {
const options = { ...opts, shell: true }
const args = [].concat(_args)
let platform = process.platform
// process.platform === 'linux' may actually indicate WSL, if that's the case
// we want to treat things as win32 anyway so the host can open the argument
if (platform === 'linux' && os.release().includes('Microsoft')) {
platform = 'win32'
}
let command = options.command
if (!command) {
if (platform === 'win32') {
// spawnWithShell does not do the additional os.release() check, so we
// have to force the shell here to make sure we treat WSL as windows.
options.shell = process.env.ComSpec
// also, the start command accepts a title so to make sure that we don't
// accidentally interpret the first arg as the title, we stick an empty
// string immediately after the start command
command = 'start ""'
} else if (platform === 'darwin') {
command = 'open'
} else {
command = 'xdg-open'
}
}
return spawnWithShell(command, args, options, extra)
}
promiseSpawn.open = open
const isPipe = (stdio = 'pipe', fd) => {
if (stdio === 'pipe' || stdio === null) {
return true
}
if (Array.isArray(stdio)) {
return isPipe(stdio[fd], fd)
}
return false
}
const stdioResult = (stdout, stderr, { stdioString = true, stdio }) => {
const result = {
stdout: null,
stderr: null,
}
// stdio is [stdin, stdout, stderr]
if (isPipe(stdio, 1)) {
result.stdout = Buffer.concat(stdout)
if (stdioString) {
result.stdout = result.stdout.toString().trim()
}
}
if (isPipe(stdio, 2)) {
result.stderr = Buffer.concat(stderr)
if (stdioString) {
result.stderr = result.stderr.toString().trim()
}
}
return result
}
module.exports = promiseSpawn

10

package.json
{
"name": "@npmcli/promise-spawn",
"version": "5.0.0",
"version": "6.0.0",
"files": [

@@ -35,4 +35,5 @@ "bin/",

"@npmcli/eslint-config": "^4.0.0",
"@npmcli/template-oss": "4.7.1",
"@npmcli/template-oss": "4.8.0",
"minipass": "^3.1.1",
"spawk": "^1.7.1",
"tap": "^16.0.1"

@@ -45,4 +46,7 @@ },

"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
"version": "4.7.1"
"version": "4.8.0"
},
"dependencies": {
"which": "^2.0.2"
}
}

@@ -14,3 +14,3 @@ # @npmcli/promise-spawn

cwd: '/tmp/some/path', // defaults to process.cwd()
stdioString: false, // stdout/stderr as strings rather than buffers
stdioString: true, // stdout/stderr as strings rather than buffers
stdio: 'pipe', // any node spawn stdio arg is valid here

@@ -53,3 +53,3 @@ // any other arguments to node child_process.spawn can go here as well,

- `stdioString` Boolean, default `false`. Return stdout/stderr output as
- `stdioString` Boolean, default `true`. Return stdout/stderr output as
strings rather than buffers.

@@ -59,2 +59,24 @@ - `cwd` String, default `process.cwd()`. Current working directory for

effective uid/gid when run as root on Unix systems.
- `shell` Boolean or String. If false, no shell is used during spawn. If true,
the system default shell is used. If a String, that specific shell is used.
When a shell is used, the given command runs from within that shell by
concatenating the command and its escaped arguments and running the result.
This option is _not_ passed through to `child_process.spawn`.
- Any other options for `child_process.spawn` can be passed as well.
### `promiseSpawn.open(arg, opts, extra)` -> `Promise`
Use the operating system to open `arg` with a default program. This is useful
for things like opening the user's default browser to a specific URL.
Depending on the platform in use this will use `start` (win32), `open` (darwin)
or `xdg-open` (everything else). In the case of Windows Subsystem for Linux we
use the default win32 behavior as it is much more predictable to open the arg
using the host operating system.
#### Options
Options are identical to `promiseSpawn` except for the following:
- `command` String, the command to use to open the file in question. Default is
one of `start`, `open` or `xdg-open` depending on platform in use.
SocketSocket SOC 2 Logo

Product

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

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc