|
- import {Buffer} from 'node:buffer';
- import path from 'node:path';
- import childProcess from 'node:child_process';
- import process from 'node:process';
- import crossSpawn from 'cross-spawn';
- import stripFinalNewline from 'strip-final-newline';
- import {npmRunPathEnv} from 'npm-run-path';
- import onetime from 'onetime';
- import {makeError} from './lib/error.js';
- import {normalizeStdio, normalizeStdioNode} from './lib/stdio.js';
- import {spawnedKill, spawnedCancel, setupTimeout, validateTimeout, setExitHandler} from './lib/kill.js';
- import {addPipeMethods} from './lib/pipe.js';
- import {handleInput, getSpawnedResult, makeAllStream, handleInputSync} from './lib/stream.js';
- import {mergePromise, getSpawnedPromise} from './lib/promise.js';
- import {joinCommand, parseCommand, parseTemplates, getEscapedCommand} from './lib/command.js';
- import {logCommand, verboseDefault} from './lib/verbose.js';
-
- const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100;
-
- const getEnv = ({env: envOption, extendEnv, preferLocal, localDir, execPath}) => {
- const env = extendEnv ? {...process.env, ...envOption} : envOption;
-
- if (preferLocal) {
- return npmRunPathEnv({env, cwd: localDir, execPath});
- }
-
- return env;
- };
-
- const handleArguments = (file, args, options = {}) => {
- const parsed = crossSpawn._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.cwd(),
- execPath: process.execPath,
- encoding: 'utf8',
- reject: true,
- cleanup: true,
- all: false,
- windowsHide: true,
- verbose: verboseDefault,
- ...options,
- };
-
- options.env = getEnv(options);
-
- options.stdio = normalizeStdio(options);
-
- if (process.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;
- };
-
- export function execa(file, args, options) {
- const parsed = handleArguments(file, args, options);
- const command = joinCommand(file, args);
- const escapedCommand = getEscapedCommand(file, args);
- logCommand(escapedCommand, parsed.options);
-
- 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,
- }));
- mergePromise(dummySpawned, errorPromise);
- return dummySpawned;
- }
-
- 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);
-
- spawned.all = makeAllStream(spawned, parsed.options);
-
- addPipeMethods(spawned);
- mergePromise(spawned, handlePromiseOnce);
- return spawned;
- }
-
- export function execaSync(file, args, options) {
- const parsed = handleArguments(file, args, options);
- const command = joinCommand(file, args);
- const escapedCommand = getEscapedCommand(file, args);
- logCommand(escapedCommand, parsed.options);
-
- const input = handleInputSync(parsed.options);
-
- let result;
- try {
- result = childProcess.spawnSync(parsed.file, parsed.args, {...parsed.options, input});
- } catch (error) {
- throw makeError({
- error,
- stdout: '',
- stderr: '',
- all: '',
- command,
- escapedCommand,
- parsed,
- timedOut: false,
- isCanceled: false,
- killed: false,
- });
- }
-
- const stdout = handleOutput(parsed.options, result.stdout, result.error);
- const stderr = handleOutput(parsed.options, result.stderr, result.error);
-
- if (result.error || result.status !== 0 || result.signal !== null) {
- const error = makeError({
- stdout,
- stderr,
- error: result.error,
- signal: result.signal,
- exitCode: result.status,
- command,
- escapedCommand,
- parsed,
- timedOut: result.error && result.error.code === 'ETIMEDOUT',
- isCanceled: false,
- killed: result.signal !== null,
- });
-
- if (!parsed.options.reject) {
- return error;
- }
-
- throw error;
- }
-
- return {
- command,
- escapedCommand,
- exitCode: 0,
- stdout,
- stderr,
- failed: false,
- timedOut: false,
- isCanceled: false,
- killed: false,
- };
- }
-
- const normalizeScriptStdin = ({input, inputFile, stdio}) => input === undefined && inputFile === undefined && stdio === undefined
- ? {stdin: 'inherit'}
- : {};
-
- const normalizeScriptOptions = (options = {}) => ({
- preferLocal: true,
- ...normalizeScriptStdin(options),
- ...options,
- });
-
- function create$(options) {
- function $(templatesOrOptions, ...expressions) {
- if (!Array.isArray(templatesOrOptions)) {
- return create$({...options, ...templatesOrOptions});
- }
-
- const [file, ...args] = parseTemplates(templatesOrOptions, expressions);
- return execa(file, args, normalizeScriptOptions(options));
- }
-
- $.sync = (templates, ...expressions) => {
- if (!Array.isArray(templates)) {
- throw new TypeError('Please use $(options).sync`command` instead of $.sync(options)`command`.');
- }
-
- const [file, ...args] = parseTemplates(templates, expressions);
- return execaSync(file, args, normalizeScriptOptions(options));
- };
-
- return $;
- }
-
- export const $ = create$();
-
- export function execaCommand(command, options) {
- const [file, ...args] = parseCommand(command);
- return execa(file, args, options);
- }
-
- export function execaCommandSync(command, options) {
- const [file, ...args] = parseCommand(command);
- return execaSync(file, args, options);
- }
-
- export function execaNode(scriptPath, args, options = {}) {
- if (args && !Array.isArray(args) && typeof args === 'object') {
- options = args;
- args = [];
- }
-
- const stdio = normalizeStdioNode(options);
- const defaultExecArgv = process.execArgv.filter(arg => !arg.startsWith('--inspect'));
-
- const {
- nodePath = process.execPath,
- nodeOptions = defaultExecArgv,
- } = options;
-
- return execa(
- nodePath,
- [
- ...nodeOptions,
- scriptPath,
- ...(Array.isArray(args) ? args : []),
- ],
- {
- ...options,
- stdin: undefined,
- stdout: undefined,
- stderr: undefined,
- stdio,
- shell: false,
- },
- );
- }
|