Comparing version 2.0.1 to 2.0.2
257
index.js
@@ -7,14 +7,12 @@ 'use strict'; | ||
const npmRunPath = require('npm-run-path'); | ||
const isStream = require('is-stream'); | ||
const getStream = require('get-stream'); | ||
const mergeStream = require('merge-stream'); | ||
const pFinally = require('p-finally'); | ||
const onetime = require('onetime'); | ||
const makeError = require('./lib/error'); | ||
const normalizeStdio = require('./lib/stdio'); | ||
const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler, cleanup} = require('./lib/kill'); | ||
const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = require('./lib/kill'); | ||
const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = require('./lib/stream.js'); | ||
const {mergePromise, getSpawnedPromise} = require('./lib/promise.js'); | ||
const {joinCommand, parseCommand} = require('./lib/command.js'); | ||
const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100; | ||
const SPACES_REGEXP = / +/g; | ||
const handleArgs = (file, args, options = {}) => { | ||
@@ -63,16 +61,2 @@ const parsed = crossSpawn._parse(file, args, options); | ||
const handleInput = (spawned, input) => { | ||
// Checking for stdin is workaround for https://github.com/nodejs/node/issues/26852 | ||
// TODO: Remove `|| spawned.stdin === undefined` once we drop support for Node.js <=12.2.0 | ||
if (input === undefined || spawned.stdin === undefined) { | ||
return; | ||
} | ||
if (isStream(input)) { | ||
input.pipe(spawned.stdin); | ||
} else { | ||
spawned.stdin.end(input); | ||
} | ||
}; | ||
const handleOutput = (options, value, error) => { | ||
@@ -91,127 +75,2 @@ if (typeof value !== 'string' && !Buffer.isBuffer(value)) { | ||
const makeAllStream = spawned => { | ||
if (!spawned.stdout && !spawned.stderr) { | ||
return; | ||
} | ||
const mixed = mergeStream(); | ||
if (spawned.stdout) { | ||
mixed.add(spawned.stdout); | ||
} | ||
if (spawned.stderr) { | ||
mixed.add(spawned.stderr); | ||
} | ||
return mixed; | ||
}; | ||
const getBufferedData = async (stream, streamPromise) => { | ||
if (!stream) { | ||
return; | ||
} | ||
stream.destroy(); | ||
try { | ||
return await streamPromise; | ||
} catch (error) { | ||
return error.bufferedData; | ||
} | ||
}; | ||
const getStreamPromise = (stream, {encoding, buffer, maxBuffer}) => { | ||
if (!stream) { | ||
return; | ||
} | ||
if (!buffer) { | ||
// TODO: Use `ret = util.promisify(stream.finished)(stream);` when targeting Node.js 10 | ||
return new Promise((resolve, reject) => { | ||
stream | ||
.once('end', resolve) | ||
.once('error', reject); | ||
}); | ||
} | ||
if (encoding) { | ||
return getStream(stream, {encoding, maxBuffer}); | ||
} | ||
return getStream.buffer(stream, {maxBuffer}); | ||
}; | ||
const getPromiseResult = async ({stdout, stderr, all}, {encoding, buffer, maxBuffer}, processDone) => { | ||
const stdoutPromise = getStreamPromise(stdout, {encoding, buffer, maxBuffer}); | ||
const stderrPromise = getStreamPromise(stderr, {encoding, buffer, maxBuffer}); | ||
const allPromise = getStreamPromise(all, {encoding, buffer, maxBuffer: maxBuffer * 2}); | ||
try { | ||
return await Promise.all([processDone, stdoutPromise, stderrPromise, allPromise]); | ||
} catch (error) { | ||
return Promise.all([ | ||
{error, code: error.code, signal: error.signal}, | ||
getBufferedData(stdout, stdoutPromise), | ||
getBufferedData(stderr, stderrPromise), | ||
getBufferedData(all, allPromise) | ||
]); | ||
} | ||
}; | ||
const joinCommand = (file, args = []) => { | ||
if (!Array.isArray(args)) { | ||
return file; | ||
} | ||
return [file, ...args].join(' '); | ||
}; | ||
const mergePromiseProperty = (spawned, getPromise, property) => { | ||
Object.defineProperty(spawned, property, { | ||
value(...args) { | ||
return getPromise()[property](...args); | ||
}, | ||
writable: true, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
}; | ||
// The return value is a mixin of `childProcess` and `Promise` | ||
const mergePromise = (spawned, getPromise) => { | ||
mergePromiseProperty(spawned, getPromise, 'then'); | ||
mergePromiseProperty(spawned, getPromise, 'catch'); | ||
// TODO: Remove the `if`-guard when targeting Node.js 10 | ||
if (Promise.prototype.finally) { | ||
mergePromiseProperty(spawned, getPromise, 'finally'); | ||
} | ||
return spawned; | ||
}; | ||
const handleSpawned = (spawned, context) => { | ||
return new Promise((resolve, reject) => { | ||
spawned.on('exit', (code, signal) => { | ||
if (context.timedOut) { | ||
reject(Object.assign(new Error('Timed out'), {code, signal})); | ||
return; | ||
} | ||
resolve({code, signal}); | ||
}); | ||
spawned.on('error', error => { | ||
reject(error); | ||
}); | ||
if (spawned.stdin) { | ||
spawned.stdin.on('error', error => { | ||
reject(error); | ||
}); | ||
} | ||
}); | ||
}; | ||
const execa = (file, args, options) => { | ||
@@ -225,42 +84,44 @@ const parsed = handleArgs(file, args, options); | ||
} catch (error) { | ||
return mergePromise(new childProcess.ChildProcess(), () => | ||
Promise.reject(makeError({ | ||
error, | ||
stdout: '', | ||
stderr: '', | ||
all: '', | ||
command, | ||
parsed, | ||
timedOut: false, | ||
isCanceled: false, | ||
killed: false | ||
})) | ||
); | ||
// Ensure the returned error is always both a promise and a child process | ||
const dummySpawned = new childProcess.ChildProcess(); | ||
const errorPromise = Promise.reject(makeError({ | ||
error, | ||
stdout: '', | ||
stderr: '', | ||
all: '', | ||
command, | ||
parsed, | ||
timedOut: false, | ||
isCanceled: false, | ||
killed: false | ||
})); | ||
return mergePromise(dummySpawned, errorPromise); | ||
} | ||
const context = {timedOut: false, isCanceled: false}; | ||
const spawnedPromise = getSpawnedPromise(spawned); | ||
const timedPromise = setupTimeout(spawned, parsed.options, spawnedPromise); | ||
const processDone = setExitHandler(spawned, parsed.options, timedPromise); | ||
const context = {isCanceled: false}; | ||
spawned.kill = spawnedKill.bind(null, spawned.kill.bind(spawned)); | ||
spawned.cancel = spawnedCancel.bind(null, spawned, context); | ||
const timeoutId = setupTimeout(spawned, parsed.options, context); | ||
const removeExitHandler = setExitHandler(spawned, parsed.options); | ||
// TODO: Use native "finally" syntax when targeting Node.js 10 | ||
const processDone = pFinally(handleSpawned(spawned, context), () => { | ||
cleanup(timeoutId, removeExitHandler); | ||
}); | ||
const handlePromise = async () => { | ||
const [result, stdout, stderr, all] = await getPromiseResult(spawned, parsed.options, processDone); | ||
result.stdout = handleOutput(parsed.options, stdout); | ||
result.stderr = handleOutput(parsed.options, stderr); | ||
result.all = handleOutput(parsed.options, all); | ||
const [{error, code, signal, timedOut}, stdoutResult, stderrResult, allResult] = await getSpawnedResult(spawned, parsed.options, processDone); | ||
const stdout = handleOutput(parsed.options, stdoutResult); | ||
const stderr = handleOutput(parsed.options, stderrResult); | ||
const all = handleOutput(parsed.options, allResult); | ||
if (result.error || result.code !== 0 || result.signal !== null) { | ||
const error = makeError({ | ||
...result, | ||
if (error || code !== 0 || signal !== null) { | ||
const returnedError = makeError({ | ||
error, | ||
code, | ||
signal, | ||
stdout, | ||
stderr, | ||
all, | ||
command, | ||
parsed, | ||
timedOut: context.timedOut, | ||
timedOut, | ||
isCanceled: context.isCanceled, | ||
@@ -271,6 +132,6 @@ killed: spawned.killed | ||
if (!parsed.options.reject) { | ||
return error; | ||
return returnedError; | ||
} | ||
throw error; | ||
throw returnedError; | ||
} | ||
@@ -282,5 +143,5 @@ | ||
exitCodeName: 'SUCCESS', | ||
stdout: result.stdout, | ||
stderr: result.stderr, | ||
all: result.all, | ||
stdout, | ||
stderr, | ||
all, | ||
failed: false, | ||
@@ -293,2 +154,4 @@ timedOut: false, | ||
const handlePromiseOnce = onetime(handlePromise); | ||
crossSpawn._enoent.hookChildProcess(spawned, parsed.parsed); | ||
@@ -300,3 +163,3 @@ | ||
return mergePromise(spawned, handlePromise); | ||
return mergePromise(spawned, handlePromiseOnce); | ||
}; | ||
@@ -310,5 +173,3 @@ | ||
if (isStream(parsed.options.input)) { | ||
throw new TypeError('The `input` option cannot be a stream in sync mode'); | ||
} | ||
validateInputSync(parsed.options); | ||
@@ -366,24 +227,2 @@ let result; | ||
// Allow spaces to be escaped by a backslash if not meant as a delimiter | ||
const handleEscaping = (tokens, token, index) => { | ||
if (index === 0) { | ||
return [token]; | ||
} | ||
const previousToken = tokens[tokens.length - 1]; | ||
if (previousToken.endsWith('\\')) { | ||
return [...tokens.slice(0, -1), `${previousToken.slice(0, -1)} ${token}`]; | ||
} | ||
return [...tokens, token]; | ||
}; | ||
const parseCommand = command => { | ||
return command | ||
.trim() | ||
.split(SPACES_REGEXP) | ||
.reduce(handleEscaping, []); | ||
}; | ||
module.exports.command = (command, options) => { | ||
@@ -405,3 +244,3 @@ const [file, ...args] = parseCommand(command); | ||
const stdioOption = normalizeStdio.node(options); | ||
const stdio = normalizeStdio.node(options); | ||
@@ -422,3 +261,3 @@ const {nodePath = process.execPath, nodeOptions = process.execArgv} = options; | ||
stderr: undefined, | ||
stdio: stdioOption, | ||
stdio, | ||
shell: false | ||
@@ -425,0 +264,0 @@ } |
@@ -77,3 +77,3 @@ 'use strict'; | ||
error.failed = true; | ||
error.timedOut = timedOut; | ||
error.timedOut = Boolean(timedOut); | ||
error.isCanceled = isCanceled; | ||
@@ -80,0 +80,0 @@ error.killed = killed && !timedOut; |
'use strict'; | ||
const os = require('os'); | ||
const onExit = require('signal-exit'); | ||
const pFinally = require('p-finally'); | ||
@@ -55,14 +56,33 @@ const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5; | ||
const timeoutKill = (spawned, signal, reject) => { | ||
spawned.kill(signal); | ||
reject(Object.assign(new Error('Timed out'), {timedOut: true, signal})); | ||
}; | ||
// `timeout` option handling | ||
const setupTimeout = (spawned, {timeout, killSignal}, context) => { | ||
if (timeout > 0) { | ||
return setTimeout(() => { | ||
context.timedOut = true; | ||
spawned.kill(killSignal); | ||
const setupTimeout = (spawned, {timeout, killSignal = 'SIGTERM'}, spawnedPromise) => { | ||
if (timeout === 0 || timeout === undefined) { | ||
return spawnedPromise; | ||
} | ||
if (!Number.isInteger(timeout) || timeout < 0) { | ||
throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`); | ||
} | ||
let timeoutId; | ||
const timeoutPromise = new Promise((resolve, reject) => { | ||
timeoutId = setTimeout(() => { | ||
timeoutKill(spawned, killSignal, reject); | ||
}, timeout); | ||
} | ||
}); | ||
const safeSpawnedPromise = pFinally(spawnedPromise, () => { | ||
clearTimeout(timeoutId); | ||
}); | ||
return Promise.race([timeoutPromise, safeSpawnedPromise]); | ||
}; | ||
// `cleanup` option handling | ||
const setExitHandler = (spawned, {cleanup, detached}) => { | ||
const setExitHandler = (spawned, {cleanup, detached}, timedPromise) => { | ||
if (!cleanup || detached) { | ||
@@ -72,15 +92,8 @@ return; | ||
return onExit(() => { | ||
const removeExitHandler = onExit(() => { | ||
spawned.kill(); | ||
}); | ||
}; | ||
const cleanup = (timeoutId, removeExitHandler) => { | ||
if (timeoutId !== undefined) { | ||
clearTimeout(timeoutId); | ||
} | ||
if (removeExitHandler !== undefined) { | ||
removeExitHandler(); | ||
} | ||
// TODO: Use native "finally" syntax when targeting Node.js 10 | ||
return pFinally(timedPromise, removeExitHandler); | ||
}; | ||
@@ -92,4 +105,3 @@ | ||
setupTimeout, | ||
setExitHandler, | ||
cleanup | ||
setExitHandler | ||
}; |
{ | ||
"name": "execa", | ||
"version": "2.0.1", | ||
"version": "2.0.2", | ||
"description": "Process execution for humans", | ||
@@ -46,2 +46,3 @@ "license": "MIT", | ||
"npm-run-path": "^3.0.0", | ||
"onetime": "^5.1.0", | ||
"p-finally": "^2.0.0", | ||
@@ -48,0 +49,0 @@ "signal-exit": "^3.0.2", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
48201
12
931
9
+ Addedonetime@^5.1.0
+ Addedmimic-fn@2.1.0(transitive)
+ Addedonetime@5.1.2(transitive)