| '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, validateTimeout, setExitHandler} = require('./lib/kill'); |
| const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = require('./lib/stream'); |
| const {mergePromise, getSpawnedPromise} = require('./lib/promise'); |
| const {joinCommand, parseCommand, getEscapedCommand} = require('./lib/command'); |
| |
| 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); |
| 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, |
| 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); |
| }; |
| |
| module.exports = execa; |
| |
| module.exports.sync = (file, args, options) => { |
| const parsed = handleArguments(file, args, options); |
| const command = joinCommand(file, args); |
| const escapedCommand = getEscapedCommand(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, |
| 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 |
| }; |
| }; |
| |
| 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 |
| } |
| ); |
| }; |