You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1071 lines
24 KiB
1071 lines
24 KiB
import { Buffer } from 'node:buffer';
|
|
import path from 'node:path';
|
|
import childProcess from 'node:child_process';
|
|
import process$1 from 'node:process';
|
|
import { s as signalExit, m as mergeStream, g as getStream, c as crossSpawn } from './vendor-index.7a2cebfe.js';
|
|
import url from 'node:url';
|
|
import { constants } from 'os';
|
|
import os from 'node:os';
|
|
|
|
function stripFinalNewline(input) {
|
|
const LF = typeof input === 'string' ? '\n' : '\n'.charCodeAt();
|
|
const CR = typeof input === 'string' ? '\r' : '\r'.charCodeAt();
|
|
|
|
if (input[input.length - 1] === LF) {
|
|
input = input.slice(0, -1);
|
|
}
|
|
|
|
if (input[input.length - 1] === CR) {
|
|
input = input.slice(0, -1);
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
function pathKey(options = {}) {
|
|
const {
|
|
env = process.env,
|
|
platform = process.platform
|
|
} = options;
|
|
|
|
if (platform !== 'win32') {
|
|
return 'PATH';
|
|
}
|
|
|
|
return Object.keys(env).reverse().find(key => key.toUpperCase() === 'PATH') || 'Path';
|
|
}
|
|
|
|
function npmRunPath(options = {}) {
|
|
const {
|
|
cwd = process$1.cwd(),
|
|
path: path_ = process$1.env[pathKey()],
|
|
execPath = process$1.execPath,
|
|
} = options;
|
|
|
|
let previous;
|
|
const cwdString = cwd instanceof URL ? url.fileURLToPath(cwd) : cwd;
|
|
let cwdPath = path.resolve(cwdString);
|
|
const result = [];
|
|
|
|
while (previous !== cwdPath) {
|
|
result.push(path.join(cwdPath, 'node_modules/.bin'));
|
|
previous = cwdPath;
|
|
cwdPath = path.resolve(cwdPath, '..');
|
|
}
|
|
|
|
// Ensure the running `node` binary is used.
|
|
result.push(path.resolve(cwdString, execPath, '..'));
|
|
|
|
return [...result, path_].join(path.delimiter);
|
|
}
|
|
|
|
function npmRunPathEnv({env = process$1.env, ...options} = {}) {
|
|
env = {...env};
|
|
|
|
const path = pathKey({env});
|
|
options.path = env[path];
|
|
env[path] = npmRunPath(options);
|
|
|
|
return env;
|
|
}
|
|
|
|
const copyProperty = (to, from, property, ignoreNonConfigurable) => {
|
|
// `Function#length` should reflect the parameters of `to` not `from` since we keep its body.
|
|
// `Function#prototype` is non-writable and non-configurable so can never be modified.
|
|
if (property === 'length' || property === 'prototype') {
|
|
return;
|
|
}
|
|
|
|
// `Function#arguments` and `Function#caller` should not be copied. They were reported to be present in `Reflect.ownKeys` for some devices in React Native (#41), so we explicitly ignore them here.
|
|
if (property === 'arguments' || property === 'caller') {
|
|
return;
|
|
}
|
|
|
|
const toDescriptor = Object.getOwnPropertyDescriptor(to, property);
|
|
const fromDescriptor = Object.getOwnPropertyDescriptor(from, property);
|
|
|
|
if (!canCopyProperty(toDescriptor, fromDescriptor) && ignoreNonConfigurable) {
|
|
return;
|
|
}
|
|
|
|
Object.defineProperty(to, property, fromDescriptor);
|
|
};
|
|
|
|
// `Object.defineProperty()` throws if the property exists, is not configurable and either:
|
|
// - one its descriptors is changed
|
|
// - it is non-writable and its value is changed
|
|
const canCopyProperty = function (toDescriptor, fromDescriptor) {
|
|
return toDescriptor === undefined || toDescriptor.configurable || (
|
|
toDescriptor.writable === fromDescriptor.writable &&
|
|
toDescriptor.enumerable === fromDescriptor.enumerable &&
|
|
toDescriptor.configurable === fromDescriptor.configurable &&
|
|
(toDescriptor.writable || toDescriptor.value === fromDescriptor.value)
|
|
);
|
|
};
|
|
|
|
const changePrototype = (to, from) => {
|
|
const fromPrototype = Object.getPrototypeOf(from);
|
|
if (fromPrototype === Object.getPrototypeOf(to)) {
|
|
return;
|
|
}
|
|
|
|
Object.setPrototypeOf(to, fromPrototype);
|
|
};
|
|
|
|
const wrappedToString = (withName, fromBody) => `/* Wrapped ${withName}*/\n${fromBody}`;
|
|
|
|
const toStringDescriptor = Object.getOwnPropertyDescriptor(Function.prototype, 'toString');
|
|
const toStringName = Object.getOwnPropertyDescriptor(Function.prototype.toString, 'name');
|
|
|
|
// We call `from.toString()` early (not lazily) to ensure `from` can be garbage collected.
|
|
// We use `bind()` instead of a closure for the same reason.
|
|
// Calling `from.toString()` early also allows caching it in case `to.toString()` is called several times.
|
|
const changeToString = (to, from, name) => {
|
|
const withName = name === '' ? '' : `with ${name.trim()}() `;
|
|
const newToString = wrappedToString.bind(null, withName, from.toString());
|
|
// Ensure `to.toString.toString` is non-enumerable and has the same `same`
|
|
Object.defineProperty(newToString, 'name', toStringName);
|
|
Object.defineProperty(to, 'toString', {...toStringDescriptor, value: newToString});
|
|
};
|
|
|
|
function mimicFunction(to, from, {ignoreNonConfigurable = false} = {}) {
|
|
const {name} = to;
|
|
|
|
for (const property of Reflect.ownKeys(from)) {
|
|
copyProperty(to, from, property, ignoreNonConfigurable);
|
|
}
|
|
|
|
changePrototype(to, from);
|
|
changeToString(to, from, name);
|
|
|
|
return to;
|
|
}
|
|
|
|
const calledFunctions = new WeakMap();
|
|
|
|
const onetime = (function_, options = {}) => {
|
|
if (typeof function_ !== 'function') {
|
|
throw new TypeError('Expected a function');
|
|
}
|
|
|
|
let returnValue;
|
|
let callCount = 0;
|
|
const functionName = function_.displayName || function_.name || '<anonymous>';
|
|
|
|
const onetime = function (...arguments_) {
|
|
calledFunctions.set(onetime, ++callCount);
|
|
|
|
if (callCount === 1) {
|
|
returnValue = function_.apply(this, arguments_);
|
|
function_ = null;
|
|
} else if (options.throw === true) {
|
|
throw new Error(`Function \`${functionName}\` can only be called once`);
|
|
}
|
|
|
|
return returnValue;
|
|
};
|
|
|
|
mimicFunction(onetime, function_);
|
|
calledFunctions.set(onetime, callCount);
|
|
|
|
return onetime;
|
|
};
|
|
|
|
onetime.callCount = function_ => {
|
|
if (!calledFunctions.has(function_)) {
|
|
throw new Error(`The given function \`${function_.name}\` is not wrapped by the \`onetime\` package`);
|
|
}
|
|
|
|
return calledFunctions.get(function_);
|
|
};
|
|
|
|
const getRealtimeSignals=function(){
|
|
const length=SIGRTMAX-SIGRTMIN+1;
|
|
return Array.from({length},getRealtimeSignal);
|
|
};
|
|
|
|
const getRealtimeSignal=function(value,index){
|
|
return {
|
|
name:`SIGRT${index+1}`,
|
|
number:SIGRTMIN+index,
|
|
action:"terminate",
|
|
description:"Application-specific signal (realtime)",
|
|
standard:"posix"};
|
|
|
|
};
|
|
|
|
const SIGRTMIN=34;
|
|
const SIGRTMAX=64;
|
|
|
|
const SIGNALS=[
|
|
{
|
|
name:"SIGHUP",
|
|
number:1,
|
|
action:"terminate",
|
|
description:"Terminal closed",
|
|
standard:"posix"},
|
|
|
|
{
|
|
name:"SIGINT",
|
|
number:2,
|
|
action:"terminate",
|
|
description:"User interruption with CTRL-C",
|
|
standard:"ansi"},
|
|
|
|
{
|
|
name:"SIGQUIT",
|
|
number:3,
|
|
action:"core",
|
|
description:"User interruption with CTRL-\\",
|
|
standard:"posix"},
|
|
|
|
{
|
|
name:"SIGILL",
|
|
number:4,
|
|
action:"core",
|
|
description:"Invalid machine instruction",
|
|
standard:"ansi"},
|
|
|
|
{
|
|
name:"SIGTRAP",
|
|
number:5,
|
|
action:"core",
|
|
description:"Debugger breakpoint",
|
|
standard:"posix"},
|
|
|
|
{
|
|
name:"SIGABRT",
|
|
number:6,
|
|
action:"core",
|
|
description:"Aborted",
|
|
standard:"ansi"},
|
|
|
|
{
|
|
name:"SIGIOT",
|
|
number:6,
|
|
action:"core",
|
|
description:"Aborted",
|
|
standard:"bsd"},
|
|
|
|
{
|
|
name:"SIGBUS",
|
|
number:7,
|
|
action:"core",
|
|
description:
|
|
"Bus error due to misaligned, non-existing address or paging error",
|
|
standard:"bsd"},
|
|
|
|
{
|
|
name:"SIGEMT",
|
|
number:7,
|
|
action:"terminate",
|
|
description:"Command should be emulated but is not implemented",
|
|
standard:"other"},
|
|
|
|
{
|
|
name:"SIGFPE",
|
|
number:8,
|
|
action:"core",
|
|
description:"Floating point arithmetic error",
|
|
standard:"ansi"},
|
|
|
|
{
|
|
name:"SIGKILL",
|
|
number:9,
|
|
action:"terminate",
|
|
description:"Forced termination",
|
|
standard:"posix",
|
|
forced:true},
|
|
|
|
{
|
|
name:"SIGUSR1",
|
|
number:10,
|
|
action:"terminate",
|
|
description:"Application-specific signal",
|
|
standard:"posix"},
|
|
|
|
{
|
|
name:"SIGSEGV",
|
|
number:11,
|
|
action:"core",
|
|
description:"Segmentation fault",
|
|
standard:"ansi"},
|
|
|
|
{
|
|
name:"SIGUSR2",
|
|
number:12,
|
|
action:"terminate",
|
|
description:"Application-specific signal",
|
|
standard:"posix"},
|
|
|
|
{
|
|
name:"SIGPIPE",
|
|
number:13,
|
|
action:"terminate",
|
|
description:"Broken pipe or socket",
|
|
standard:"posix"},
|
|
|
|
{
|
|
name:"SIGALRM",
|
|
number:14,
|
|
action:"terminate",
|
|
description:"Timeout or timer",
|
|
standard:"posix"},
|
|
|
|
{
|
|
name:"SIGTERM",
|
|
number:15,
|
|
action:"terminate",
|
|
description:"Termination",
|
|
standard:"ansi"},
|
|
|
|
{
|
|
name:"SIGSTKFLT",
|
|
number:16,
|
|
action:"terminate",
|
|
description:"Stack is empty or overflowed",
|
|
standard:"other"},
|
|
|
|
{
|
|
name:"SIGCHLD",
|
|
number:17,
|
|
action:"ignore",
|
|
description:"Child process terminated, paused or unpaused",
|
|
standard:"posix"},
|
|
|
|
{
|
|
name:"SIGCLD",
|
|
number:17,
|
|
action:"ignore",
|
|
description:"Child process terminated, paused or unpaused",
|
|
standard:"other"},
|
|
|
|
{
|
|
name:"SIGCONT",
|
|
number:18,
|
|
action:"unpause",
|
|
description:"Unpaused",
|
|
standard:"posix",
|
|
forced:true},
|
|
|
|
{
|
|
name:"SIGSTOP",
|
|
number:19,
|
|
action:"pause",
|
|
description:"Paused",
|
|
standard:"posix",
|
|
forced:true},
|
|
|
|
{
|
|
name:"SIGTSTP",
|
|
number:20,
|
|
action:"pause",
|
|
description:"Paused using CTRL-Z or \"suspend\"",
|
|
standard:"posix"},
|
|
|
|
{
|
|
name:"SIGTTIN",
|
|
number:21,
|
|
action:"pause",
|
|
description:"Background process cannot read terminal input",
|
|
standard:"posix"},
|
|
|
|
{
|
|
name:"SIGBREAK",
|
|
number:21,
|
|
action:"terminate",
|
|
description:"User interruption with CTRL-BREAK",
|
|
standard:"other"},
|
|
|
|
{
|
|
name:"SIGTTOU",
|
|
number:22,
|
|
action:"pause",
|
|
description:"Background process cannot write to terminal output",
|
|
standard:"posix"},
|
|
|
|
{
|
|
name:"SIGURG",
|
|
number:23,
|
|
action:"ignore",
|
|
description:"Socket received out-of-band data",
|
|
standard:"bsd"},
|
|
|
|
{
|
|
name:"SIGXCPU",
|
|
number:24,
|
|
action:"core",
|
|
description:"Process timed out",
|
|
standard:"bsd"},
|
|
|
|
{
|
|
name:"SIGXFSZ",
|
|
number:25,
|
|
action:"core",
|
|
description:"File too big",
|
|
standard:"bsd"},
|
|
|
|
{
|
|
name:"SIGVTALRM",
|
|
number:26,
|
|
action:"terminate",
|
|
description:"Timeout or timer",
|
|
standard:"bsd"},
|
|
|
|
{
|
|
name:"SIGPROF",
|
|
number:27,
|
|
action:"terminate",
|
|
description:"Timeout or timer",
|
|
standard:"bsd"},
|
|
|
|
{
|
|
name:"SIGWINCH",
|
|
number:28,
|
|
action:"ignore",
|
|
description:"Terminal window size changed",
|
|
standard:"bsd"},
|
|
|
|
{
|
|
name:"SIGIO",
|
|
number:29,
|
|
action:"terminate",
|
|
description:"I/O is available",
|
|
standard:"other"},
|
|
|
|
{
|
|
name:"SIGPOLL",
|
|
number:29,
|
|
action:"terminate",
|
|
description:"Watched event",
|
|
standard:"other"},
|
|
|
|
{
|
|
name:"SIGINFO",
|
|
number:29,
|
|
action:"ignore",
|
|
description:"Request for process information",
|
|
standard:"other"},
|
|
|
|
{
|
|
name:"SIGPWR",
|
|
number:30,
|
|
action:"terminate",
|
|
description:"Device running out of power",
|
|
standard:"systemv"},
|
|
|
|
{
|
|
name:"SIGSYS",
|
|
number:31,
|
|
action:"core",
|
|
description:"Invalid system call",
|
|
standard:"other"},
|
|
|
|
{
|
|
name:"SIGUNUSED",
|
|
number:31,
|
|
action:"terminate",
|
|
description:"Invalid system call",
|
|
standard:"other"}];
|
|
|
|
const getSignals=function(){
|
|
const realtimeSignals=getRealtimeSignals();
|
|
const signals=[...SIGNALS,...realtimeSignals].map(normalizeSignal);
|
|
return signals;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const normalizeSignal=function({
|
|
name,
|
|
number:defaultNumber,
|
|
description,
|
|
action,
|
|
forced=false,
|
|
standard})
|
|
{
|
|
const{
|
|
signals:{[name]:constantSignal}}=
|
|
constants;
|
|
const supported=constantSignal!==undefined;
|
|
const number=supported?constantSignal:defaultNumber;
|
|
return {name,number,description,supported,action,forced,standard};
|
|
};
|
|
|
|
const getSignalsByName=function(){
|
|
const signals=getSignals();
|
|
return signals.reduce(getSignalByName,{});
|
|
};
|
|
|
|
const getSignalByName=function(
|
|
signalByNameMemo,
|
|
{name,number,description,supported,action,forced,standard})
|
|
{
|
|
return {
|
|
...signalByNameMemo,
|
|
[name]:{name,number,description,supported,action,forced,standard}};
|
|
|
|
};
|
|
|
|
const signalsByName=getSignalsByName();
|
|
|
|
|
|
|
|
|
|
const getSignalsByNumber=function(){
|
|
const signals=getSignals();
|
|
const length=SIGRTMAX+1;
|
|
const signalsA=Array.from({length},(value,number)=>
|
|
getSignalByNumber(number,signals));
|
|
|
|
return Object.assign({},...signalsA);
|
|
};
|
|
|
|
const getSignalByNumber=function(number,signals){
|
|
const signal=findSignalByNumber(number,signals);
|
|
|
|
if(signal===undefined){
|
|
return {};
|
|
}
|
|
|
|
const{name,description,supported,action,forced,standard}=signal;
|
|
return {
|
|
[number]:{
|
|
name,
|
|
number,
|
|
description,
|
|
supported,
|
|
action,
|
|
forced,
|
|
standard}};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const findSignalByNumber=function(number,signals){
|
|
const signal=signals.find(({name})=>constants.signals[name]===number);
|
|
|
|
if(signal!==undefined){
|
|
return signal;
|
|
}
|
|
|
|
return signals.find((signalA)=>signalA.number===number);
|
|
};
|
|
|
|
getSignalsByNumber();
|
|
|
|
const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => {
|
|
if (timedOut) {
|
|
return `timed out after ${timeout} milliseconds`;
|
|
}
|
|
|
|
if (isCanceled) {
|
|
return 'was canceled';
|
|
}
|
|
|
|
if (errorCode !== undefined) {
|
|
return `failed with ${errorCode}`;
|
|
}
|
|
|
|
if (signal !== undefined) {
|
|
return `was killed with ${signal} (${signalDescription})`;
|
|
}
|
|
|
|
if (exitCode !== undefined) {
|
|
return `failed with exit code ${exitCode}`;
|
|
}
|
|
|
|
return 'failed';
|
|
};
|
|
|
|
const makeError = ({
|
|
stdout,
|
|
stderr,
|
|
all,
|
|
error,
|
|
signal,
|
|
exitCode,
|
|
command,
|
|
escapedCommand,
|
|
timedOut,
|
|
isCanceled,
|
|
killed,
|
|
parsed: {options: {timeout}},
|
|
}) => {
|
|
// `signal` and `exitCode` emitted on `spawned.on('exit')` event can be `null`.
|
|
// We normalize them to `undefined`
|
|
exitCode = exitCode === null ? undefined : exitCode;
|
|
signal = signal === null ? undefined : signal;
|
|
const signalDescription = signal === undefined ? undefined : signalsByName[signal].description;
|
|
|
|
const errorCode = error && error.code;
|
|
|
|
const prefix = getErrorPrefix({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled});
|
|
const execaMessage = `Command ${prefix}: ${command}`;
|
|
const isError = Object.prototype.toString.call(error) === '[object Error]';
|
|
const shortMessage = isError ? `${execaMessage}\n${error.message}` : execaMessage;
|
|
const message = [shortMessage, stderr, stdout].filter(Boolean).join('\n');
|
|
|
|
if (isError) {
|
|
error.originalMessage = error.message;
|
|
error.message = message;
|
|
} else {
|
|
error = new Error(message);
|
|
}
|
|
|
|
error.shortMessage = shortMessage;
|
|
error.command = command;
|
|
error.escapedCommand = escapedCommand;
|
|
error.exitCode = exitCode;
|
|
error.signal = signal;
|
|
error.signalDescription = signalDescription;
|
|
error.stdout = stdout;
|
|
error.stderr = stderr;
|
|
|
|
if (all !== undefined) {
|
|
error.all = all;
|
|
}
|
|
|
|
if ('bufferedData' in error) {
|
|
delete error.bufferedData;
|
|
}
|
|
|
|
error.failed = true;
|
|
error.timedOut = Boolean(timedOut);
|
|
error.isCanceled = isCanceled;
|
|
error.killed = killed && !timedOut;
|
|
|
|
return error;
|
|
};
|
|
|
|
const aliases = ['stdin', 'stdout', 'stderr'];
|
|
|
|
const hasAlias = options => aliases.some(alias => options[alias] !== undefined);
|
|
|
|
const normalizeStdio = options => {
|
|
if (!options) {
|
|
return;
|
|
}
|
|
|
|
const {stdio} = options;
|
|
|
|
if (stdio === undefined) {
|
|
return aliases.map(alias => options[alias]);
|
|
}
|
|
|
|
if (hasAlias(options)) {
|
|
throw new Error(`It's not possible to provide \`stdio\` in combination with one of ${aliases.map(alias => `\`${alias}\``).join(', ')}`);
|
|
}
|
|
|
|
if (typeof stdio === 'string') {
|
|
return stdio;
|
|
}
|
|
|
|
if (!Array.isArray(stdio)) {
|
|
throw new TypeError(`Expected \`stdio\` to be of type \`string\` or \`Array\`, got \`${typeof stdio}\``);
|
|
}
|
|
|
|
const length = Math.max(stdio.length, aliases.length);
|
|
return Array.from({length}, (value, index) => stdio[index]);
|
|
};
|
|
|
|
const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5;
|
|
|
|
// Monkey-patches `childProcess.kill()` to add `forceKillAfterTimeout` behavior
|
|
const spawnedKill = (kill, signal = 'SIGTERM', options = {}) => {
|
|
const killResult = kill(signal);
|
|
setKillTimeout(kill, signal, options, killResult);
|
|
return killResult;
|
|
};
|
|
|
|
const setKillTimeout = (kill, signal, options, killResult) => {
|
|
if (!shouldForceKill(signal, options, killResult)) {
|
|
return;
|
|
}
|
|
|
|
const timeout = getForceKillAfterTimeout(options);
|
|
const t = setTimeout(() => {
|
|
kill('SIGKILL');
|
|
}, timeout);
|
|
|
|
// Guarded because there's no `.unref()` when `execa` is used in the renderer
|
|
// process in Electron. This cannot be tested since we don't run tests in
|
|
// Electron.
|
|
// istanbul ignore else
|
|
if (t.unref) {
|
|
t.unref();
|
|
}
|
|
};
|
|
|
|
const shouldForceKill = (signal, {forceKillAfterTimeout}, killResult) => isSigterm(signal) && forceKillAfterTimeout !== false && killResult;
|
|
|
|
const isSigterm = signal => signal === os.constants.signals.SIGTERM
|
|
|| (typeof signal === 'string' && signal.toUpperCase() === 'SIGTERM');
|
|
|
|
const getForceKillAfterTimeout = ({forceKillAfterTimeout = true}) => {
|
|
if (forceKillAfterTimeout === true) {
|
|
return DEFAULT_FORCE_KILL_TIMEOUT;
|
|
}
|
|
|
|
if (!Number.isFinite(forceKillAfterTimeout) || forceKillAfterTimeout < 0) {
|
|
throw new TypeError(`Expected the \`forceKillAfterTimeout\` option to be a non-negative integer, got \`${forceKillAfterTimeout}\` (${typeof forceKillAfterTimeout})`);
|
|
}
|
|
|
|
return forceKillAfterTimeout;
|
|
};
|
|
|
|
// `childProcess.cancel()`
|
|
const spawnedCancel = (spawned, context) => {
|
|
const killResult = spawned.kill();
|
|
|
|
if (killResult) {
|
|
context.isCanceled = true;
|
|
}
|
|
};
|
|
|
|
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 = 'SIGTERM'}, spawnedPromise) => {
|
|
if (timeout === 0 || timeout === undefined) {
|
|
return spawnedPromise;
|
|
}
|
|
|
|
let timeoutId;
|
|
const timeoutPromise = new Promise((resolve, reject) => {
|
|
timeoutId = setTimeout(() => {
|
|
timeoutKill(spawned, killSignal, reject);
|
|
}, timeout);
|
|
});
|
|
|
|
const safeSpawnedPromise = spawnedPromise.finally(() => {
|
|
clearTimeout(timeoutId);
|
|
});
|
|
|
|
return Promise.race([timeoutPromise, safeSpawnedPromise]);
|
|
};
|
|
|
|
const validateTimeout = ({timeout}) => {
|
|
if (timeout !== undefined && (!Number.isFinite(timeout) || timeout < 0)) {
|
|
throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`);
|
|
}
|
|
};
|
|
|
|
// `cleanup` option handling
|
|
const setExitHandler = async (spawned, {cleanup, detached}, timedPromise) => {
|
|
if (!cleanup || detached) {
|
|
return timedPromise;
|
|
}
|
|
|
|
const removeExitHandler = signalExit.exports(() => {
|
|
spawned.kill();
|
|
});
|
|
|
|
return timedPromise.finally(() => {
|
|
removeExitHandler();
|
|
});
|
|
};
|
|
|
|
function isStream(stream) {
|
|
return stream !== null
|
|
&& typeof stream === 'object'
|
|
&& typeof stream.pipe === 'function';
|
|
}
|
|
|
|
// `input` option
|
|
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);
|
|
}
|
|
};
|
|
|
|
// `all` interleaves `stdout` and `stderr`
|
|
const makeAllStream = (spawned, {all}) => {
|
|
if (!all || (!spawned.stdout && !spawned.stderr)) {
|
|
return;
|
|
}
|
|
|
|
const mixed = mergeStream();
|
|
|
|
if (spawned.stdout) {
|
|
mixed.add(spawned.stdout);
|
|
}
|
|
|
|
if (spawned.stderr) {
|
|
mixed.add(spawned.stderr);
|
|
}
|
|
|
|
return mixed;
|
|
};
|
|
|
|
// On failure, `result.stdout|stderr|all` should contain the currently buffered stream
|
|
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 || !buffer) {
|
|
return;
|
|
}
|
|
|
|
if (encoding) {
|
|
return getStream.exports(stream, {encoding, maxBuffer});
|
|
}
|
|
|
|
return getStream.exports.buffer(stream, {maxBuffer});
|
|
};
|
|
|
|
// Retrieve result of child process: exit code, signal, error, streams (stdout/stderr/all)
|
|
const getSpawnedResult = 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, signal: error.signal, timedOut: error.timedOut},
|
|
getBufferedData(stdout, stdoutPromise),
|
|
getBufferedData(stderr, stderrPromise),
|
|
getBufferedData(all, allPromise),
|
|
]);
|
|
}
|
|
};
|
|
|
|
const nativePromisePrototype = (async () => {})().constructor.prototype;
|
|
const descriptors = ['then', 'catch', 'finally'].map(property => [
|
|
property,
|
|
Reflect.getOwnPropertyDescriptor(nativePromisePrototype, property),
|
|
]);
|
|
|
|
// The return value is a mixin of `childProcess` and `Promise`
|
|
const mergePromise = (spawned, promise) => {
|
|
for (const [property, descriptor] of descriptors) {
|
|
// Starting the main `promise` is deferred to avoid consuming streams
|
|
const value = typeof promise === 'function'
|
|
? (...args) => Reflect.apply(descriptor.value, promise(), args)
|
|
: descriptor.value.bind(promise);
|
|
|
|
Reflect.defineProperty(spawned, property, {...descriptor, value});
|
|
}
|
|
|
|
return spawned;
|
|
};
|
|
|
|
// Use promises instead of `child_process` events
|
|
const getSpawnedPromise = spawned => new Promise((resolve, reject) => {
|
|
spawned.on('exit', (exitCode, signal) => {
|
|
resolve({exitCode, signal});
|
|
});
|
|
|
|
spawned.on('error', error => {
|
|
reject(error);
|
|
});
|
|
|
|
if (spawned.stdin) {
|
|
spawned.stdin.on('error', error => {
|
|
reject(error);
|
|
});
|
|
}
|
|
});
|
|
|
|
const normalizeArgs = (file, args = []) => {
|
|
if (!Array.isArray(args)) {
|
|
return [file];
|
|
}
|
|
|
|
return [file, ...args];
|
|
};
|
|
|
|
const NO_ESCAPE_REGEXP = /^[\w.-]+$/;
|
|
const DOUBLE_QUOTES_REGEXP = /"/g;
|
|
|
|
const escapeArg = arg => {
|
|
if (typeof arg !== 'string' || NO_ESCAPE_REGEXP.test(arg)) {
|
|
return arg;
|
|
}
|
|
|
|
return `"${arg.replace(DOUBLE_QUOTES_REGEXP, '\\"')}"`;
|
|
};
|
|
|
|
const joinCommand = (file, args) => normalizeArgs(file, args).join(' ');
|
|
|
|
const getEscapedCommand = (file, args) => normalizeArgs(file, args).map(arg => escapeArg(arg)).join(' ');
|
|
|
|
const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100;
|
|
|
|
const getEnv = ({env: envOption, extendEnv, preferLocal, localDir, execPath}) => {
|
|
const env = extendEnv ? {...process$1.env, ...envOption} : envOption;
|
|
|
|
if (preferLocal) {
|
|
return npmRunPathEnv({env, cwd: localDir, execPath});
|
|
}
|
|
|
|
return env;
|
|
};
|
|
|
|
const handleArguments = (file, args, options = {}) => {
|
|
const parsed = crossSpawn.exports._parse(file, args, options);
|
|
file = parsed.command;
|
|
args = parsed.args;
|
|
options = parsed.options;
|
|
|
|
options = {
|
|
maxBuffer: DEFAULT_MAX_BUFFER,
|
|
buffer: true,
|
|
stripFinalNewline: true,
|
|
extendEnv: true,
|
|
preferLocal: false,
|
|
localDir: options.cwd || process$1.cwd(),
|
|
execPath: process$1.execPath,
|
|
encoding: 'utf8',
|
|
reject: true,
|
|
cleanup: true,
|
|
all: false,
|
|
windowsHide: true,
|
|
...options,
|
|
};
|
|
|
|
options.env = getEnv(options);
|
|
|
|
options.stdio = normalizeStdio(options);
|
|
|
|
if (process$1.platform === 'win32' && path.basename(file, '.exe') === 'cmd') {
|
|
// #116
|
|
args.unshift('/q');
|
|
}
|
|
|
|
return {file, args, options, parsed};
|
|
};
|
|
|
|
const handleOutput = (options, value, error) => {
|
|
if (typeof value !== 'string' && !Buffer.isBuffer(value)) {
|
|
// When `execaSync()` errors, we normalize it to '' to mimic `execa()`
|
|
return error === undefined ? undefined : '';
|
|
}
|
|
|
|
if (options.stripFinalNewline) {
|
|
return stripFinalNewline(value);
|
|
}
|
|
|
|
return value;
|
|
};
|
|
|
|
function execa(file, args, options) {
|
|
const parsed = handleArguments(file, args, options);
|
|
const command = joinCommand(file, args);
|
|
const escapedCommand = getEscapedCommand(file, args);
|
|
|
|
validateTimeout(parsed.options);
|
|
|
|
let spawned;
|
|
try {
|
|
spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options);
|
|
} catch (error) {
|
|
// 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,
|
|
escapedCommand,
|
|
parsed,
|
|
timedOut: false,
|
|
isCanceled: false,
|
|
killed: false,
|
|
}));
|
|
return mergePromise(dummySpawned, errorPromise);
|
|
}
|
|
|
|
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 handlePromise = async () => {
|
|
const [{error, exitCode, 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 (error || exitCode !== 0 || signal !== null) {
|
|
const returnedError = makeError({
|
|
error,
|
|
exitCode,
|
|
signal,
|
|
stdout,
|
|
stderr,
|
|
all,
|
|
command,
|
|
escapedCommand,
|
|
parsed,
|
|
timedOut,
|
|
isCanceled: context.isCanceled || (parsed.options.signal ? parsed.options.signal.aborted : false),
|
|
killed: spawned.killed,
|
|
});
|
|
|
|
if (!parsed.options.reject) {
|
|
return returnedError;
|
|
}
|
|
|
|
throw returnedError;
|
|
}
|
|
|
|
return {
|
|
command,
|
|
escapedCommand,
|
|
exitCode: 0,
|
|
stdout,
|
|
stderr,
|
|
all,
|
|
failed: false,
|
|
timedOut: false,
|
|
isCanceled: false,
|
|
killed: false,
|
|
};
|
|
};
|
|
|
|
const handlePromiseOnce = onetime(handlePromise);
|
|
|
|
handleInput(spawned, parsed.options.input);
|
|
|
|
spawned.all = makeAllStream(spawned, parsed.options);
|
|
|
|
return mergePromise(spawned, handlePromiseOnce);
|
|
}
|
|
|
|
export { execa as e };
|
|
|