123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- 'use strict';
- const path = require('path');
- const childProcess = require('child_process');
- const crossSpawn = require('cross-spawn');
- const stripFinalNewline = require('strip-final-newline');
- const npmRunPath = require('npm-run-path');
- const onetime = require('onetime');
- const makeError = require('./lib/error');
- const normalizeStdio = require('./lib/stdio');
- 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 getEnv = ({env: envOption, extendEnv, preferLocal, localDir, execPath}) => {
- const env = extendEnv ? {...process.env, ...envOption} : envOption;
-
- if (preferLocal) {
- return npmRunPath.env({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,
- ...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 `execa.sync()` errors, we normalize it to '' to mimic `execa()`
- return error === undefined ? undefined : '';
- }
-
- if (options.stripFinalNewline) {
- return stripFinalNewline(value);
- }
-
- return value;
- };
-
- const execa = (file, args, options) => {
- const parsed = handleArguments(file, args, options);
- const command = joinCommand(file, args);
-
- 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,
- 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,
- parsed,
- timedOut,
- isCanceled: context.isCanceled,
- killed: spawned.killed
- });
-
- if (!parsed.options.reject) {
- return returnedError;
- }
-
- throw returnedError;
- }
-
- return {
- command,
- exitCode: 0,
- stdout,
- stderr,
- all,
- failed: false,
- timedOut: false,
- isCanceled: false,
- killed: false
- };
- };
-
- const handlePromiseOnce = onetime(handlePromise);
-
- crossSpawn._enoent.hookChildProcess(spawned, parsed.parsed);
-
- handleInput(spawned, parsed.options.input);
-
- spawned.all = makeAllStream(spawned, parsed.options);
-
- return mergePromise(spawned, handlePromiseOnce);
- };
-
- module.exports = execa;
-
- module.exports.sync = (file, args, options) => {
- const parsed = handleArguments(file, args, options);
- const command = joinCommand(file, args);
-
- validateInputSync(parsed.options);
-
- let result;
- try {
- result = childProcess.spawnSync(parsed.file, parsed.args, parsed.options);
- } catch (error) {
- throw makeError({
- error,
- stdout: '',
- stderr: '',
- all: '',
- command,
- 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,
- parsed,
- timedOut: result.error && result.error.code === 'ETIMEDOUT',
- isCanceled: false,
- killed: result.signal !== null
- });
-
- if (!parsed.options.reject) {
- return error;
- }
-
- throw error;
- }
-
- return {
- command,
- exitCode: 0,
- stdout,
- stderr,
- failed: false,
- timedOut: false,
- isCanceled: false,
- killed: false
- };
- };
-
- module.exports.command = (command, options) => {
- const [file, ...args] = parseCommand(command);
- return execa(file, args, options);
- };
-
- module.exports.commandSync = (command, options) => {
- const [file, ...args] = parseCommand(command);
- return execa.sync(file, args, options);
- };
-
- module.exports.node = (scriptPath, args, options = {}) => {
- if (args && !Array.isArray(args) && typeof args === 'object') {
- options = args;
- args = [];
- }
-
- const stdio = normalizeStdio.node(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
- }
- );
- };
|