From 4e0dd25282c88090631c3d50b17e3d359ba4b105 Mon Sep 17 00:00:00 2001 From: ehmicky Date: Sun, 21 Apr 2024 10:13:48 +0100 Subject: [PATCH] Add multiple guides and documentation --- docs/binary.md | 64 ++++ docs/debugging.md | 99 +++++ docs/environment.md | 69 ++++ docs/errors.md | 111 ++++++ docs/escaping.md | 81 ++++ docs/execution.md | 125 +++++++ docs/input.md | 62 +++ docs/ipc.md | 40 ++ docs/lines.md | 176 +++++++++ docs/node.md | 37 ++ docs/output.md | 177 +++++++++ docs/pipe.md | 157 ++++++++ docs/scripts.md | 33 +- docs/shell.md | 32 ++ docs/streams.md | 116 ++++++ docs/termination.md | 180 +++++++++ docs/transform.md | 129 +------ docs/windows.md | 35 ++ lib/pipe/abort.js | 2 +- readme.md | 624 ++++++++++++++----------------- test/pipe/abort.js | 2 +- types/arguments/options.d.ts | 158 +++----- types/convert.d.ts | 4 +- types/methods/command.d.ts | 15 +- types/methods/main-async.d.ts | 10 +- types/methods/main-sync.d.ts | 11 - types/methods/script.d.ts | 2 +- types/pipe.d.ts | 8 - types/return/final-error.d.ts | 14 +- types/return/result.d.ts | 61 ++- types/subprocess/subprocess.d.ts | 36 +- 31 files changed, 1966 insertions(+), 704 deletions(-) create mode 100644 docs/binary.md create mode 100644 docs/debugging.md create mode 100644 docs/environment.md create mode 100644 docs/errors.md create mode 100644 docs/escaping.md create mode 100644 docs/execution.md create mode 100644 docs/input.md create mode 100644 docs/ipc.md create mode 100644 docs/lines.md create mode 100644 docs/node.md create mode 100644 docs/output.md create mode 100644 docs/pipe.md create mode 100644 docs/shell.md create mode 100644 docs/streams.md create mode 100644 docs/termination.md create mode 100644 docs/windows.md diff --git a/docs/binary.md b/docs/binary.md new file mode 100644 index 0000000000..4f126fef82 --- /dev/null +++ b/docs/binary.md @@ -0,0 +1,64 @@ + + + execa logo + +
+ +# Binary data + +## Binary output + +By default, the subprocess [output](../readme.md#resultstdout) is a UTF8 string. If it is binary, the [`encoding`](../readme.md#optionsencoding) option should be set to `'buffer'` instead. The output will be an [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array). + +```js +import {execa} from 'execa'; + +const {stdout} = await execa({encoding: 'buffer'})`unzip file.zip`; +console.log(stdout.byteLength); +``` + +## Encoding + +When the output is binary, the [`encoding`](../readme.md#optionsencoding) option can also be set to `'hex'`, `'base64'` or `'base64url'`. The output will be a string then. + +```js +const {stdout} = await execa({encoding: 'hex'})`unzip file.zip`; +console.log(stdout); // Hexadecimal string +``` + +## Iterable + +By default, the subprocess [iterates](lines.md#progressive-splitting) over line strings. However, if the [`encoding`](../readme.md#optionsencoding) subprocess option is binary, or if the [`binary`](../readme.md#readableoptionsbinary) iterable option is `true`, it iterates over arbitrary chunks of [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) data instead. + +```js +for await (const data of execa({encoding: 'buffer'})`unzip file.zip`) { /* ... */ } +``` + +## Streams + +[Streams produced](streams.md#converting-a-subprocess-to-a-stream) by [`subprocess.readable()`](../readme.md#subprocessreadablereadableoptions) and [`subprocess.duplex()`](../readme.md#subprocessduplexduplexoptions) are binary by default, which means they iterate over arbitrary [`Buffer`](https://nodejs.org/api/buffer.html#class-buffer) chunks. However, if the [`binary`](../readme.md#readableoptionsbinary) option is `false`, they iterate over line strings instead, and the stream is [in object mode](https://nodejs.org/api/stream.html#object-mode). + +```js +const readable = execa`npm run build`.readable({binary: false}); +readable.on('data', lineString => { /* ... */ }) +``` + +## Transforms + +The same applies to transforms. When the [`encoding`](../readme.md#optionsencoding) subprocess option is binary, or when the [`binary`](transform.md#transformoptionsbinary) transform option is `true`, it iterates over arbitrary chunks of [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) data instead. + +However, transforms can always `yield` either a `string` or an `Uint8Array`, regardless of whether the output is binary. + +```js +const transform = function * (data) { /* ... */ } + +await execa({stdout: {transform, binary: true}})`unzip file.zip`; +``` + +## Binary input + +There are multiple ways to pass binary input using the [`stdin`](../readme.md#optionsstdin), [`input`](../readme.md#optionsinput) or [`inputFile`](../readme.md#optionsinputfile) options. This includes using a `Uint8Array`, a file, or a stream. + +```js +await execa({stdin: new Uint8Array([/* ... */])})`hexdump`; +``` diff --git a/docs/debugging.md b/docs/debugging.md new file mode 100644 index 0000000000..fbe83e1db9 --- /dev/null +++ b/docs/debugging.md @@ -0,0 +1,99 @@ + + + execa logo + +
+ +# Debugging + +## Command + +[`error.command`](../readme.md#resultcommand) contains the file and arguments that were run. It is intended for logging or debugging. + +[`error.escapedCommand`](../readme.md#resultescapedcommand) is the same, except control characters are escaped. This makes it safe to either print or copy and paste in a terminal, for debugging purposes. + +Since the escaping is fairly basic, this should not be executed directly as a subprocess, including using [`execa()`](#execafile-arguments-options) or [`execaCommand()`](#execacommandcommand-options). + +```js +import {execa} from 'execa'; + +try { + await execa`npm run build\ntask`; +} catch (error) { + console.error(error.command); // 'npm run build\ntask' + console.error(error.escapedCommand); // 'npm run build\\ntask' + throw error; +} +``` + +## Duration + +```js +try { + const result = execa`npm run build`; + console.log('Command duration:', result.durationMs); // 150 +} catch (error) { + console.error('Command duration:', error.durationMs); // 150 + throw error; +} +``` + +## Verbose mode + +### Short mode + +When the [`verbose`](../readme.md#optionsverbose) option is `'short'`, the [command](#command), [duration](#duration) and [error messages](errors.md#error-message) are printed on `stderr`. + +```js +// build.js +await execa({verbose: 'short'})`npm run build`; +``` + +```sh +$ node build.js +[20:36:11.043] [0] $ npm run build +[20:36:11.885] [0] ✔ (done in 842ms) +``` + +### Full mode + +When the [`verbose`](../readme.md#optionsverbose) option is `'full'`, the subprocess' [`stdout` and `stderr`](output.md) are also logged. Both are printed on `stderr`. + +The output is not logged if either: +- the [`stdout`](../readme.md#optionsstdout)/[`stderr`](../readme.md#optionsstderr) option is [`ignore`](output.md#ignore-output) or [`inherit`](input.md#redirect-to-parent-process). +- the `stdout`/`stderr` is redirected to [a stream](streams.md#output), [a file](output.md#file-output), a [file descriptor](input.md#redirect-to-parent-process), or [another subprocess](pipe.md). +- the [`encoding`](../readme.md#optionsencoding) option is [binary](binary.md#binary-output). + +```js +// build.js +await execa({verbose: 'full'})`npm run build`; +``` + +```sh +$ node build.js +[20:36:11.043] [0] $ npm run build +Building application... +Done building. +[20:36:11.885] [0] ✔ (done in 842ms) +``` + +### Global mode + +When the `NODE_DEBUG=execa` environment variable is set, the [`verbose`](../readme.md#optionsverbose) option defaults to `'full'` for all commands. + +```js +// build.js + +// This is logged by default +await execa`npm run build`; +// This is not logged +await execa({verbose: 'none'})`npm run test`; +``` + +```sh +$ NODE_DEBUG=execa node build.js +[20:36:11.043] [0] $ npm run build +Building application... +Done building. +[20:36:11.885] [0] ✔ (done in 842ms) +``` diff --git a/docs/environment.md b/docs/environment.md new file mode 100644 index 0000000000..511d388f8e --- /dev/null +++ b/docs/environment.md @@ -0,0 +1,69 @@ + + + execa logo + +
+ +# Environment + +## [Environment variables](https://en.wikipedia.org/wiki/Environment_variable) + +```js +import {execa} from 'execa'; + +// Keep the current process' environment variables, and set `NO_COLOR` +await execa({env: {NO_COLOR: 'true'})`npm run build`; +// Discard the current process' environment variables, only pass `NO_COLOR` +await execa({env: {NO_COLOR: 'true'}, extendEnv: false)`npm run build`; +``` + +## [Current directory](https://en.wikipedia.org/wiki/Working_directory) + +```js +const {cwd} = await execa`npm run build`; +console.log(cwd); // Current directory when running the command +``` + +```js +await execa({cwd: '/path/to/cwd'})`npm run build`; +``` + +## Local binaries + +```js +await execa('./node_modules/bin/eslint'); +``` + +The [`preferLocal`](../readme.md#optionspreferlocal) option can be used to automatically find binaries installed in local `node_modules`. + +```js +await execa('eslint', {preferLocal: true}); +``` + +Those are searched in the current or any parent directory. The [`localDir`](../readme.md#optionslocaldir) option can select a different directory. + +```js +await execa('eslint', {preferLocal: true, localDir: '/path/to/dir'}); +``` + +## Current package's binary + +Execa can be combined with [`get-bin-path`](https://github.com/ehmicky/get-bin-path) to test the current package's binary. As opposed to hard-coding the path to the binary, this validates that the `package.json` `bin` field is correctly set up. + +```js +import {execa} from 'execa'; +import {getBinPath} from 'get-bin-path'; + +const binPath = await getBinPath(); +await execa(binPath); +``` + +## Background process + +When the [`detached`](../readme.md#optionsdetached) option is `true`, the subprocess [runs independently](https://en.wikipedia.org/wiki/Background_process) from the current process. + +Specific behavior depends on the platform. [More info.](https://nodejs.org/api/child_process.html#child_process_options_detached). + +```js +await execa({detached: true})`npm run start`; +``` diff --git a/docs/errors.md b/docs/errors.md new file mode 100644 index 0000000000..e9d0dd09be --- /dev/null +++ b/docs/errors.md @@ -0,0 +1,111 @@ + + + execa logo + +
+ +# Errors + +## Subprocess failure + +When the subprocess fails, the returned promise is rejected with an [`ExecaError`](../readme.md#execaerror) instance. The `error` has the same shape as successful [results](../readme.md#result), with a few additional [error-specific fields](../readme.md#execaerror). `error.failed` is always `true`. + +```js +import {execa, ExecaError} from 'execa'; + +try { + const result = await execa`npm run build`; + console.log(result.failed); // false +} catch (error) { + if (error instanceof ExecaError) { + console.error(error.failed); // true + } +} +``` + +## Preventing exceptions + +When the [`reject`](../readme.md#optionsreject) option is `false`, the `error` is returned instead. + +```js +const resultOrError = await execa`npm run build`; +if (resultOrError.failed) { + console.error(resultOrError); +} +``` + +## Exit code + +The subprocess fails when its [exit code](https://en.wikipedia.org/wiki/Exit_status) is not `0`. The exit code is available as [`error.exitCode`](../readme.md#resultexitcode). It is `undefined` when the subprocess fails to spawn or when it was [terminated by a signal](termination.md#signal-termination). + +```js +try { + await execa`npm run build`; +} catch (error) { + // Either non-0 integer or undefined + console.error(error.exitCode); +} +``` + +## Failure reason + +The subprocess can fail for other reasons. Each reason can be detected using a specific boolean property: +- [`error.isTerminated`](../readme.md#resultisterminated): [signal termination](termination.md#signal-termination), including [`subprocess.kill()`](termination.md#signal-termination) and the [`cancelSignal`](termination.md#canceling) option. +- [`error.timedOut`](../readme.md#resulttimedout): [`timeout`](termination.md#timeout) option. +- [`error.isCanceled`](../readme.md#resultiscanceled): [`cancelSignal`](termination.md#canceling) option. +- [`error.isMaxBuffer`](../readme.md#resultismaxbuffer): [`maxBuffer`](output.md#big-output) option. +- Otherwise, it could not be spawned because of either: + - The command's binary not found. + - An invalid argument or [option](../readme.md#options) was passed. + - Not enough memory or too many subprocesses. + +```js +try { + await execa`npm run build`; +} catch (error) { + if (error.timedOut) { + handleTimeout(error); + } + + throw error +} +``` + +## Error message + +For better debugging, [`error.message`](../readme.md#errormessage) includes both: +- The command and the [reason it failed](#failure-reason). +- Its [`stdout`, `stderr`](output.md#stdout-and-stderr) and other [file descriptor' output](output.md#additional-file-descriptors), separated with newlines and not interleaved. + +[`error.shortMessage`](../readme.md#errorshortmessage) is the same but without `stdout`/`stderr`. [`error.originalMessage`](../readme.md#errororiginalmessage) is the same but also without the command. + +```js +try { + await execa`npm run build`; +} catch (error) { + console.error(error.originalMessage); + // The task "build" does not exist. + + console.error(error.shortMessage); + // Command failed with exit code 3: npm run build + // The task "build" does not exist. + + console.error(error.message); + // Command failed with exit code 3: npm run build + // The task "build" does not exist. + // [stderr contents...] + // [stdout contents...] +} +``` + +## Retry on error + +Safely handle failures by using automatic retries and exponential backoff with the [`p-retry`](https://github.com/sindresorhus/p-retry) package. + +```js +import pRetry from 'p-retry'; +import {execa} from 'execa'; + +const run = () => execa`curl -sSL https://sindresorhus.com/unicorn`; +console.log(await pRetry(run, {retries: 5})); +``` diff --git a/docs/escaping.md b/docs/escaping.md new file mode 100644 index 0000000000..75d1430beb --- /dev/null +++ b/docs/escaping.md @@ -0,0 +1,81 @@ + + + execa logo + +
+ +# Escaping/quoting + +## Array syntax + +When using the [array syntax](execution.md#array-syntax), arguments are automatically escaped. They can contain any character, including spaces, tabs and newlines. + +```js +import {execa} from 'execa'; + +await execa('npm', ['run', 'task with space']); +``` + +## Template string syntax + +The same applies when using the [template string syntax](execution.md#template-string-syntax). However, spaces, tabs and newlines must use `${}`. + +```js +await execa`npm run ${'task with space'}`; +``` + +## User-defined input + +The above syntaxes allow the file and its arguments to be user-defined by passing a variable. + +```js +const command = 'npm'; +const commandArguments = ['run', 'task with space']; + +await execa(command, commandAndArguments); +await execa`${command} ${commandAndArguments}`; +``` + +However, [`execaCommand()`](../readme.md#execacommandcommand-options) must be used instead if: +- _Both_ the file and its arguments are user-defined +- _And_ those are supplied as a single string + +This is only intended for very specific cases, such as a REPL. This should be avoided otherwise. + +```js +for await (const commandAndArguments of getReplLine()) { + await execaCommand(commandAndArguments); +} +``` + +Arguments passed to `execaCommand()` are automatically escaped. They can contain any character, but spaces must be escaped with a backslash. + +```js +await execaCommand('npm run task\\ with\\ space'); +``` + +## Shells + +[Shells](shell.md) (Bash, cmd.exe, etc.) are not used unless the [`shell`](../readme.md#optionsshell) option is set. This means shell-specific characters and expressions (`$variable`, `&&`, `||`, `;`, `|`, etc.) have no special meaning and do not need to be escaped. + +If you do set the `shell` option, arguments will not be automatically escaped anymore. Instead, they will be concatenated as a single string using spaces as delimiters. + +```js +await execa({shell: true})`npm ${'run'} ${'task with space'}`; +// Is the same as: +await execa({shell: true})`npm run task with space`; +``` + +Therefore, you need to manually quote the arguments, using the shell-specific syntax. + +```js +await execa({shell: true})`npm ${'run'} ${'"task with space"'}`; +// Is the same as: +await execa({shell: true})`npm run "task with space"`; +``` + +Sometimes a shell command is passed as argument to an executable that runs it indirectly. In that case, that shell command must quote its own arguments. + +```js +$`ssh host ${'npm run "task with space"'}`; +``` diff --git a/docs/execution.md b/docs/execution.md new file mode 100644 index 0000000000..cc73c1585d --- /dev/null +++ b/docs/execution.md @@ -0,0 +1,125 @@ + + + execa logo + +
+ +# Basic execution + +## Array syntax + +```js +import {execa} from 'execa'; + +await execa('npm', ['run', 'build']); +``` + +## Template string syntax + +All [available methods](../readme.md#methods) can use either the [array syntax](#array-syntax) or the template string syntax, which are equivalent. + +```js +await execa`npm run build`; +``` + +### String argument + +```js +await execa`npm run ${'task with space'}`; +``` + +### Number argument + +```js +await execa`npm run build --concurrency ${2}`; +``` + +### Subprocess `stdout` + +```js +const result = await execa`get-concurrency`; + +// Uses `result.stdout` +await execa`npm run build --concurrency ${result}`; +``` + +### Multiple arguments + +```js +const result = await execa`get-concurrency`; + +await execa`npm ${['run', 'build', '--concurrency', 2]}`; +``` + +### Multiple lines + +```js +await execa`npm run build + --concurrency 2 + --fail-fast`; +``` + +## Options + +[Options](../readme.md#options) can be passed to influence the execution's behavior. + +### Array syntax + +```js +await execa('npm', ['run', 'build'], {timeout: 5000}); +``` + +### Template string syntax + +```js +await execa({timeout: 5000})`npm run build`; +``` + +### Global/shared options + +```js +const timedExeca = execa({timeout: 5000}); + +await timedExeca('npm', ['run', 'build']); +await timedExeca`npm run test`; +``` + +## Return value + +### Subprocess + +The subprocess is returned as soon as it is spawned. It is a [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess) with [additional methods and properties](../readme.md#subprocess). + +```js +const subprocess = execa`npm run build`; +console.log(subprocess.pid); +``` + +### Result + +The subprocess is also a `Promise` that resolves with the [`result`](../readme.md#result). + +```js +const {stdout} = await execa`npm run build`; +``` + +### Synchronous execution + +[Every method](../readme.md#methods) can be called synchronously by appending `Sync` to the method's name. The [`result`](../readme.md#result) is returned without needing `await`. The [`subprocess`](#subprocess) is not returned: its methods and properties are not available. + +```js +import {execaSync} from 'execa'; + +const {stdout} = execaSync`npm run build`; +``` + +Synchronous execution is generally discouraged as it holds the CPU and prevents parallelization. Also, the following features cannot be used: +- Streams: [`subprocess.stdin`](../readme.md#subprocessstdin), [`subprocess.stdout`](../readme.md#subprocessstdout), [`subprocess.stderr`](../readme.md#subprocessstderr), [`subprocess.readable()`](../readme.md#subprocessreadablereadableoptions), [`subprocess.writable()`](../readme.md#subprocesswritablewritableoptions), [`subprocess.duplex()`](../readme.md#subprocessduplexduplexoptions). +- The [`stdin`](../readme.md#optionsstdin), [`stdout`](../readme.md#optionsstdout), [`stderr`](../readme.md#optionsstderr) and [`stdio`](../readme.md#optionsstdio) options cannot be [`'overlapped'`](../readme.md#optionsstdout), an async iterable, an async [transform](transform.md), a [`Duplex`](transform.md#duplextransform-streams), nor a web stream. Node.js streams can be passed but only if either they [have a file descriptor](streams.md#file-descriptors), or the `input` option is used. +- Signal termination: [`subprocess.kill()`](../readme.md#subprocesskillerror), [`subprocess.pid`](../readme.md#subprocesspid), [`cleanup`](../readme.md#optionscleanup) option, [`cancelSignal`](../readme.md#optionscancelsignal) option, [`forceKillAfterDelay`](../readme.md#optionsforcekillafterdelay) option. +- Piping multiple processes: [`subprocess.pipe()`](../readme.md#subprocesspipefile-arguments-options). +- [`subprocess.iterable()`](lines.md#progressive-splitting). +- [`ipc`](../readme.md#optionsipc) and [`serialization`](../readme.md#optionsserialization) options. +- [`result.all`](../readme.md#resultall) is not interleaved. +- [`detached`](../readme.md#optionsdetached) option. +- The [`maxBuffer`](../readme.md#optionsmaxbuffer) option is always measured in bytes, not in characters, [lines](../readme.md#optionslines) nor [objects](transform.md#object-mode). Also, it ignores transforms and the [`encoding`](../readme.md#optionsencoding) option. diff --git a/docs/input.md b/docs/input.md new file mode 100644 index 0000000000..9cab65e56c --- /dev/null +++ b/docs/input.md @@ -0,0 +1,62 @@ + + + execa logo + +
+ +# Input + +## Input + +The [`stdin`](../readme.md#optionsstdin) option specify the subprocess' input. It defaults to `'pipe'`, which allows incremental input by writing to the [`subprocess.stdin`](../readme.md#subprocessstdin) stream. + +However, a simpler way to specify `stdin` is to pass a string or an [`Uint8Array`](binary.md#binary-input) to the [`stdin`](../readme.md#optionsstdin) or [`input`](../readme.md#optionsinput) option. + +```js +import {execa} from 'execa'; + +await execa({input: 'stdinInput']})`npm run scaffold`; +await execa({input: new Uint8Array([/* ... */])]})`npm run scaffold`; + +// Unlike the `input` option, strings passed to the `stdin` option must be wrapped in two arrays. +await execa({stdin: [['stdinInput']]]})`npm run scaffold`; +await execa({stdin: new Uint8Array([/* ... */])]})`npm run scaffold`; +``` + +[More info.](https://nodejs.org/api/child_process.html#child_process_options_stdio) + +## Ignore input + +```js +const subprocess = execa({stdin: 'ignore'})`npm run scaffold`; +console.log(subprocess.stdin); // undefined +``` + +## File input + +```js +await execa({inputFile: './input.txt'})`npm run scaffold`; +// Or: +await execa({stdin: {file: './input.txt'}})`npm run scaffold`; +// Or: +await execa({stdin: new URL('file:///path/to/input.txt')})`npm run scaffold`; +``` + +## Terminal input + +The parent process' input can be re-used in the subprocess by passing `'inherit'`. This is especially useful to receive interactive input in command line applications. + +```js +await execa({stdin: 'inherit'})`npm run scaffold`; +``` + +## Additional file descriptors + +The [`stdio`](../readme.md#optionsstdio) option can be used to pass some input to any [file descriptor](https://en.wikipedia.org/wiki/File_descriptor), as opposed to only [`stdin`](../readme.md#optionsstdin). + +```js +// Pass file as input to the file descriptor number 3 +await execa({ + stdio: ['pipe', 'pipe', 'pipe', {file: './file'}], +})`npm run build`; +``` diff --git a/docs/ipc.md b/docs/ipc.md new file mode 100644 index 0000000000..bdcd5df1c1 --- /dev/null +++ b/docs/ipc.md @@ -0,0 +1,40 @@ + + + execa logo + +
+ +# Inter-process communication + +## Exchanging messages + +When the [`ipc`](../readme.md#optionsipc) option is `true`, the current process and subprocess can exchange messages. This only works if the subprocess is a Node.js file. + +The `ipc` option defaults to `true` when using [`execaNode()`](node.md#run-nodejs-files) or the [`node`](node.md#run-nodejs-files) option. + +The current process sends messages with [`subprocess.send(message)`](../readme.md#subprocesssendmessage) and receives them with [`subprocess.on('message', (message) => {})`](../readme.md#subprocessonmessage-message--void). The subprocess sends messages with [`process.send(message)`](https://nodejs.org/api/process.html#processsendmessage-sendhandle-options-callback) and [`process.on('message', (message) => {})`](https://nodejs.org/api/process.html#event-message). + +More info on [sending](https://nodejs.org/api/child_process.html#subprocesssendmessage-sendhandle-options-callback) and [receiving](https://nodejs.org/api/child_process.html#event-message) messages. + +```js +// parent.js +const subprocess = execaNode`child.js`; +subprocess.on('message', messageFromChild => { /* ... */ }); +subprocess.send('Hello from parent'); +``` + +```js +// child.js +process.on('message', messageFromParent => { /* ... */ }); +process.send('Hello from child'); +``` + +## Message type + +By default, messages are serialized using [`structuredClone()`](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). This supports most types including objects, arrays, `Error`, `Date`, `RegExp`, `Map`, `Set`, `bigint`, `Uint8Array`, and circular references. This throws when passing functions, symbols or promises. + +To limit messages to JSON instead, the [`serialization`](../readme.md#optionsserialization) option can be set to `'json'`. + +```js +const subprocess = execaNode({serialization: 'json'})`child.js`; +``` diff --git a/docs/lines.md b/docs/lines.md new file mode 100644 index 0000000000..07f1f79e35 --- /dev/null +++ b/docs/lines.md @@ -0,0 +1,176 @@ + + + execa logo + +
+ +# Output lines + +## Simple splitting + +If the [`lines`](../readme.md#optionslines) option is `true`, the output is split into lines, as an array of strings. + +```js +import {execa} from 'execa'; + +const lines = await execa({lines: true})`npm run build`; +console.log(lines.join('\n')); +``` + +## Iteration + +### Progressive splitting + +The subprocess' return value is an [async iterable](../readme.md#subprocesssymbolasynciterator). It iterates over the output's lines while the subprocess is still running. + +```js +for await (const line of execa`npm run build`) { + if (line.includes('ERROR')) { + console.log(line); + } +} +``` + +Alternatively, [`subprocess.iterable()`](../readme.md#subprocessiterablereadableoptions) can be called to pass [options](../readme.md#readableoptions) or use [iterator helpers](#iterator-helpers). + +The iteration waits for the subprocess to end. It throws if the subprocess [fails](../readme.md#result). This means you do not need to `await` the subprocess' [promise](../readme.md#subprocess). + +```js +for await (const line of execa`npm run build`.iterable())) { /* ... */ } +``` + +### Transforms + +[Transforms](transform.md) map or filter the input or output of a subprocess, one line at a time. Unlike [`subprocess.iterable()`](#progressive-splitting), they: +- Modify the subprocess' [output](../readme.md#resultstdout) and [streams](../readme.md#subprocessstdout). +- Can apply to the subprocess' input. +- Are defined using a [generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*), [`Duplex`](https://nodejs.org/api/stream.html#class-streamduplex) stream, Node.js [`Transform`](https://nodejs.org/api/stream.html#class-streamtransform) stream or web [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream). + +[More info.](transform.md) + +### Iterator helpers + +With Node 22 (and later), [iterator methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator) can be used. + +```js +const iterable = execa`npm run build`.iterable(); + +for await (const line of iterable.filter(line => line.includes('ERROR')))) { /* ... */ } +``` + +```js +const iterable = execa`npm run build`.iterable(); + +for await (const line of iterable.map(line => line.replaceAll('secret', '***')))) { /* ... */ } +``` + +```js +const iterable = execa`npm run build`.iterable(); + +// Only first 3 lines +for await (const line of iterable.take(3)) { /* ... */ } +``` + +The subprocess' [promise](../readme.md#subprocess) does not need to be awaited, except with [`Iterator.find()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/find), [`Iterator.some()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/some) or [`Iterator.every()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/every), since those specific methods return as soon as a matching line is found. + +```js +const printErrorLine = async iterable => { + const errorLine = await iterable.find(line => line.includes('ERROR')); + console.log(errorLine); +}; + +const subprocess = execa`npm run build`; +await Promise.all([subprocess, printErrorLine(subprocess.iterable())]); +``` + +### Stdout/stderr + +By default, the subprocess' `stdout` is used. The [`from`](../readme.md#readableoptionsfrom) iterable option can select a different file descriptor, such as `stderr`, `all` or `fd3`. + +```js +for await (const stderrLine of execa`npm run build`.iterable({from: 'stderr'})) { /* ... */ } +``` + +## Newlines + +### Final newline + +The final newline is stripped from the output's last line, unless the [`stripFinalNewline`](../readme.md#optionsstripfinalnewline) is `false`. + +```js +const {stdout} = await execa({stripFinalNewline: false})`npm run build`; +console.log(stdout.endsWith('\n')); // true +``` + +### Array of lines + +When using the [`lines`](#simple-splitting) option, newlines are stripped from each line, unless the [`stripFinalNewline`](../readme.md#optionsstripfinalnewline) is `false`. + +```js +// Each line now ends with '\n'. +// The last `line` might or might not end with '\n', depending on the output. +const lines = await execa({lines: true, stripFinalNewline: false})`npm run build`; +console.log(lines.join('')); +``` + +### Iterable + +When [iterating](#progressive-splitting) over lines, newlines are stripped from each line, unless the [`preserveNewlines`](../readme.md#readableoptionspreservenewlines) iterable option is `true`. + +This option can also be used with [streams produced](streams.md#converting-a-subprocess-to-a-stream) by [`subprocess.readable()`](../readme.md#subprocessreadablereadableoptions) and [`subprocess.duplex()`](../readme.md#subprocessduplexduplexoptions) and the [`binary: false`](binary.md#streams) option. + +```js +// `line` now ends with '\n'. +// The last `line` might or might not end with '\n', depending on the output. +for await (const line of execa`npm run build`.iterable({preserveNewlines: true})) { /* ... */ } +``` + +### Transforms + +When using [transforms](#transforms), newlines are stripped from each `line` argument, unless the [`preserveNewlines`](transform.md#transformoptionspreservenewlines) transform option is `true`. + +```js +// `line` now ends with '\n'. +// The last `line` might or might not end with '\n', depending on the output. +const transform = function * (line) { /* ... */ }; + +await execa({stdout: {transform, preserveNewlines: true}})`npm run build`; +``` + +Each `yield` produces at least one line. Calling `yield` multiple times or calling `yield *` produces multiples lines. + +```js +const transform = function * (line) { + yield 'Important note:'; + yield 'Read the comments below.'; + + // Or: + yield * [ + 'Important note:', + 'Read the comments below.', + ]; + + // Is the same as: + yield 'Important note:\nRead the comments below.\n'; + + yield line +}; + +await execa({stdout: transform})`npm run build`; +``` + +However, if the [`preserveNewlines`](#transformoptionspreservenewlines) transform option is `true`, multiple `yield`s produce a single line instead. + +```js +const transform = function * (line) { + yield 'Important note: '; + yield 'Read the comments below.\n'; + + // Is the same as: + yield 'Important note: Read the comments below.\n'; + + yield line +}; + +await execa({stdout: {transform, preserveNewlines: true}})`npm run build`; +``` diff --git a/docs/node.md b/docs/node.md new file mode 100644 index 0000000000..3d2664cff1 --- /dev/null +++ b/docs/node.md @@ -0,0 +1,37 @@ + + + execa logo + +
+ +# Node.js files + +## Run Node.js files + +```js +import {execaNode, execa} from 'execa'; + +await execaNode('file.js'); +// Is the same as: +await execa('file.js', {node: true}); +// Or: +await execa('node', ['file.js']); +``` + +## Node.js CLI flags + +```js +await execaNode('file.js', {nodeOptions: ['--no-warnings']}); +``` + +## Node.js version + +[`get-node`](https://github.com/ehmicky/get-node) and the [`nodePath`](../readme.md#optionsnodepath) option can be used to run a specific Node.js version. Alternatively, [`nvexeca`](https://github.com/ehmicky/nvexeca) or [`nve`](https://github.com/ehmicky/nve) can be used. + +```js +import {execa} from 'execa'; +import getNode from 'get-node'; + +const {path: nodePath} = await getNode('16.2.0') +await execaNode('file.js', {nodePath}); +``` diff --git a/docs/output.md b/docs/output.md new file mode 100644 index 0000000000..eed228d186 --- /dev/null +++ b/docs/output.md @@ -0,0 +1,177 @@ + + + execa logo + +
+ +# Output + +## Stdout and stderr + +The [`stdout`](../readme.md#optionsstdout) and [`stderr`](../readme.md#optionsstderr) options redirect the subprocess output. + +It defaults to `'pipe'`, which returns the output using [`result.stdout`](../readme.md#resultstdout) and [`result.stderr`](../readme.md#resultstderr). + +```js +import {execa} from 'execa'; + +const {stdout, stderr} = await execa`npm run build`; +console.log(stdout); +console.log(stderr); +``` + +## Ignore output + +```js +const {stdout, stderr} = execa({stdout: 'ignore'})`npm run build`; +console.log(stdout); // undefined +console.log(stderr); // string with errors +``` + +## File output + +```js +await execa({stdout: {file: './output.txt'}})`npm run build`; +// Or: +await execa({stdout: new URL('file:///path/to/output.txt')})`npm run build`; +``` + +## Redirect to parent process + +The parent process' output can be re-used in the subprocess by passing `'inherit'`. This is especially useful to print to the terminal in command line applications. + +```js +await execa({stdout: 'inherit', stderr: 'inherit'})`npm run build`; +``` + +To redirect from/to a different file descriptor, pass its [number](https://en.wikipedia.org/wiki/Standard_streams) or [`process.stdout`](https://nodejs.org/api/process.html#processstdout)/[`process.stderr`](https://nodejs.org/api/process.html#processstderr). + +```js +// Print both stdout/stderr to the parent stdout +await execa({stdout: process.stdout, stderr: process.stdout})`npm run build`; +// Or: +await execa({stdout: 1, stderr: 1})`npm run build`; +``` + +## Multiple targets + +The output can be redirected to multiple targets by setting the [`stdout`](../readme.md#optionsstdout) or [`stderr`](../readme.md#optionsstderr) option to an array of values. This also allows specifying multiple inputs with the [`stdin`](../readme.md#optionsstdin) option. + +The following example redirects `stdout` to both the terminal and an `output.txt` file, while also retrieving its value programmatically. + +```js +const {stdout} = await execa({ + stdout: [ + 'inherit', + {file: './output.txt'}, + 'pipe', + ], +})`npm run build`; +console.log(stdout); +``` + +When combining `'inherit'` with other values, please note that the subprocess will not be an interactive TTY, even if the current process is one. + +## Interleaved output + +If the [`all`](../readme.md#optionsall) option is `true`, `stdout` and `stderr` are combined: +- [`result.all`](../readme.md#resultall): [`result.stdout`](../readme.md#resultstdout) + [`result.stderr`](../readme.md#resultstderr) +- [`subprocess.all`](../readme.md#subprocessall): [`subprocess.stdout`](../readme.md#subprocessstdout) + [`subprocess.stderr`](../readme.md#subprocessstderr) + +```js +const {all} = await execa({all: true})`npm run build`; +``` + +Stdout and stderr are guaranteed to interleave. However, for performance reasons, the subprocess might buffer and merge multiple simultaneous writes to `stdout` or `stderr`. This can prevent proper interleaving. + +For example, this prints `1 3 2` instead of `1 2 3` because both `console.log()` are merged into a single write. + +```js +const {all} = await execa({all: true})`node example.js`; +``` + +```js +// example.js +console.log('1'); // writes to stdout +console.error('2'); // writes to stderr +console.log('3'); // writes to stdout +``` + +This can be worked around by using `setTimeout()`. + +```js +import {setTimeout} from 'timers/promises'; + +console.log('1'); +console.error('2'); +await setTimeout(0); +console.log('3'); +``` + +## Stdout/stderr-specific options + +Some options are related to the subprocess output: [`verbose`](#optionsverbose), [`lines`](#optionslines), [`stripFinalNewline`](#optionsstripfinalnewline), [`buffer`](#optionsbuffer), [`maxBuffer`](#optionsmaxbuffer). By default, those options apply to all file descriptors (`stdout`, `stderr`, etc.). A plain object can be passed instead to apply them to only `stdout`, `stderr`, `fd3`, etc. + +```js +// Same value for stdout and stderr +await execa({verbose: 'full'})`npm run build`; + +// Different values for stdout and stderr +await execa({verbose: {stdout: 'none', stderr: 'full'}})`npm run build`; +``` + +## Additional file descriptors + +The [`stdio`](../readme.md#optionsstdio) option is an array combining [`stdin`](../readme.md#optionsstdin), [`stdout`](../readme.md#optionsstdout), [`stderr`](../readme.md#optionsstderr) and any other file descriptor. It is useful when using additional [file descriptors](https://en.wikipedia.org/wiki/File_descriptor) beyond the [standard ones](https://en.wikipedia.org/wiki/Standard_streams), either for [input](input.md#additional-file-descriptors) or output. + +[`result.stdio`](../readme.md#resultstdio) can be used to retrieve some output from any file descriptor, as opposed to only [`stdout`](../readme.md#optionsstdout) and [`stderr`](../readme.md#optionsstderr). + +```js +// Retrieve output from file descriptor number 3 +const {stdio} = await execa({ + stdio: ['pipe', 'pipe', 'pipe', 'pipe'], +})`npm run build`; +console.log(stdio[3]); +``` + +## Shortcut + +The [`stdio`](../readme.md#optionsstdio) option can also be a single value `'pipe'`, `'overlapped'`, `'ignore'` or `'inherit'`. This is a shortcut for setting that same value with the [`stdin`](../readme.md#optionsstdin), [`stdout`](../readme.md#optionsstdout) and [`stderr`](../readme.md#optionsstderr) options. + +```js +await execa({stdio: 'ignore'})`npm run build`; +// Same as: +await execa({stdin: 'ignore', stdout: 'ignore', stderr: 'ignore'})`npm run build`; +``` + +## Big output + +To prevent high memory consumption, a maximum output size can be set using the [`maxBuffer`](../readme.md#optionsmaxbuffer) option. It defaults to 100MB. + +When this threshold is hit, the subprocess fails and [`error.isMaxBuffer`](../readme.md#resultismaxbuffer) becomes `true`. The truncated output is still available using [`error.stdout`](../readme.md#resultstdout) and [`error.stderr`](../readme.md#resultstderr). + +This is measured: +- By default: in [characters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length). +- If the [`encoding`](binary.md#encoding) option is `'buffer'`: in bytes. +- If the [`lines`](lines.md#simple-splitting) option is `true`: in lines. +- If a [transform in object mode](docs/transform.md#object-mode) is used: in objects. + +```js +try { + await execa({maxBuffer: 1_000_000})`npm run build`; +} catch (error) { + if (error.isMaxBuffer) { + console.error('Error: output larger than 1MB.'); + console.error(error.stdout); + console.error(error.stderr); + } + + throw error +} +``` + +## Low memory + +When the [`buffer`](../readme.md#optionsbuffer) option is `false`, [`result.stdout`](../readme.md#resultstdout), [`result.stderr`](../readme.md#resultstderr), [`result.all`](../readme.md#resultall) and [`result.stdio[*]`](../readme.md#resultstdio) properties are not set. + +This prevents high memory consumption when the output is big. However, the output must be either ignored, [redirected](input.md) or [streamed](streams.md). If streamed, this should be done right away to avoid missing any data. diff --git a/docs/pipe.md b/docs/pipe.md new file mode 100644 index 0000000000..91e886c60f --- /dev/null +++ b/docs/pipe.md @@ -0,0 +1,157 @@ + + + execa logo + +
+ +# Piping multiple subprocesses + +## Array syntax + +A subprocess' output can be piped to another subprocess' input. The syntax is the same as [`execa(file, arguments?, options?)`](execution.md#array-syntax). + +```js +import {execa} from 'execa'; + +// Similar to `npm run build | head -n 2` in shells +const {stdout} = await execa('npm', ['run', 'build']) + .pipe('head', ['-n', '2']); +``` + +## Template string syntax + +```js +const {stdout} = await execa`npm run build` + .pipe`head -n 2`; +``` + +## Advanced syntax + +```js +const subprocess = execa`head -n 2`; +const {stdout} = await execa`npm run build`.pipe(subprocess); +``` + +## Options + +[Options](../readme.md#options) can be passed to either the source or the destination subprocess. Some [pipe-specific options](../readme.md#pipeoptions) can also be set by the destination subprocess. + +```js +const {stdout} = await execa('npm', ['run', 'build'], subprocessOptions) + .pipe('head', ['-n', '2'], subprocessOrPipeOptions); +``` + +```js +const {stdout} = await execa(subprocessOptions)`npm run build` + .pipe(subprocessOrPipeOptions)`head -n 2`; +``` + +```js +const subprocess = execa(subprocessOptions)`head -n 2`; +const {stdout} = await execa(subprocessOptions)`npm run build` + .pipe(subprocess, pipeOptions); +``` + +## Result + +When both subprocesses succeed, the [`result`](../readme.md#result) of the destination subprocess is returned. The [`result`](../readme.md#result) of the source subprocess is available in a [`result.pipedFrom`](../readme.md#resultpipedfrom) array. + +```js +const destinationResult = await execa`npm run build` + .pipe`head -n 2`; +console.log(destinationResult.stdout); // First 2 lines of `npm run build` + +const sourceResult = destinationResult.pipedFrom[0]; +console.log(sourceResult.stdout); // Full output of `npm run build` +``` + +## Errors + +When either subprocess fails, the `.pipe()` is rejected with its error. If the destination subprocess fails, [`error.pipedFrom`](../readme.md#resultpipedfrom) includes the source subprocess' result, which is useful for debugging. + +```js +try { + await execa`npm run build` + .pipe`head -n 2`; +} catch (error) { + if (error.pipedFrom.length === 0) { + console.error('`npm run build` failed due to:', error) + } else { + console.error('`head -n 2` failed due to:', error) + console.error('`npm run build` stdout:', error.pipedFrom[0].stdout); + } + + throw error; +} +``` + +## Series of subprocesses + +```js +await execa`npm run build` + .pipe`sort` + .pipe`head -n 2`; +``` + +## 1 source, multiple destinations + +```js +const subprocess = execa`npm run build`; +const [sortedResult, truncatedResult] = await Promise.all([ + subprocess.pipe`sort`, + subprocess.pipe`head -n 2`, +]); +``` + +## Multiple sources, 1 destination + +```js +const destination = execa`./log-remotely.js`; +await Promise.all([ + execa`npm run build`.pipe(destination), + execa`npm run test`.pipe(destination), +]); +``` + +## Source file descriptor + +By default, the source's `stdout` is used, but this can be changed using the [`from`](../readme.md#pipeoptionsfrom) piping option. + +```js +await execa`npm run build` + .pipe({from: 'stderr})`head -n 2`; +``` + +## Destination file descriptor + +By default, the destination's `stdin` is used, but this can be changed using the [`to`](../readme.md#pipeoptionsto) piping option. + +```js +await execa`npm run build` + .pipe({to: 'fd3'})`./log-remotely.js`; +``` + +## Unpipe + +Piping can be canceled using the [`unpipeSignal`](../readme.md#pipeoptionsunpipesignal) piping option. + +The [`subprocess.pipe()`](../readme.md#subprocesspipefile-arguments-options) method will be rejected with a cancelation error. However, each subprocess will keep running. + +```js +const abortController = new AbortController(); + +process.on('SIGUSR1', () => { + abortController.abort(); +}); + +// If the process receives SIGUSR1, `npm run build` stopped being logged remotely. +// However, it keeps running successfully. +try { + await execa`npm run build` + .pipe({unpipeSignal: abortController.signal})`./log-remotely.js`; +} catch (error) { + if (!abortController.signal.aborted) { + throw error; + } +} +``` diff --git a/docs/scripts.md b/docs/scripts.md index cc8e873bab..73ab8092d0 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -1,12 +1,18 @@ -# Node.js scripts + + + execa logo + +
+ +# Scripts With Execa, you can write scripts with Node.js instead of a shell language. [Compared to Bash and zx](#differences-with-bash-and-zx), this is more: - [performant](#performance) - - [cross-platform](#shell): [no shell](../readme.md#shell-syntax) is used, only JavaScript. + - [cross-platform](#shell): [no shell](shell.md) is used, only JavaScript. - [secure](#escaping): no shell injection. - [simple](#simplicity): minimalistic API, no [globals](#global-variables), no [binary](#main-binary), no [builtin CLI utilities](#builtin-utilities). - - [featureful](#simplicity): all Execa features are available ([subprocess piping](#piping-stdout-to-another-command), [IPC](#ipc), [transforms](#transforms), [background subprocesses](#background-subprocesses), [cancellation](#cancellation), [local binaries](#local-binaries), [cleanup on exit](../readme.md#optionscleanup), [interleaved output](#interleaved-output), [forceful termination](../readme.md#optionsforcekillafterdelay), etc.). - - [easy to debug](#debugging): [verbose mode](#verbose-mode), [detailed errors](#errors), [messages and stack traces](#cancellation), stateless API. + - [featureful](#simplicity): all Execa features are available ([subprocess piping](#piping-stdout-to-another-command), [IPC](#ipc), [transforms](#transforms), [background subprocesses](#background-subprocesses), [cancelation](#cancelation), [local binaries](#local-binaries), [cleanup on exit](../readme.md#optionscleanup), [interleaved output](#interleaved-output), [forceful termination](../readme.md#optionsforcekillafterdelay), etc.). + - [easy to debug](#debugging): [verbose mode](#verbose-mode), [detailed errors](#errors), [messages and stack traces](#cancelation), stateless API. ```js import {$} from 'execa'; @@ -30,11 +36,13 @@ await $`mkdir /tmp/${dirName}`; ## Template string syntax -The examples below use the [template string syntax](../readme.md#template-string-syntax). However, the other syntax using [an array of arguments](../readme.md#execafile-arguments-options) is also available as `$(file, arguments?, options?)`. +The examples below use the [template string syntax](execution.md#template-string-syntax). However, the [array syntax](execution.md#array-syntax) is also available as `$(file, arguments?, options?)`. + +Also, the template string syntax can be used outside of script files: `$` is not required to use that syntax. For example, `execa` [can use it too](execution.md#template-string-syntax). -Also, the template string syntax can be used outside of script files: `$` is not required to use that syntax. For example, `execa` can use it too. +## Script-friendly options -The only difference between `$` and `execa` is that the former includes [script-friendly default options](../readme.md#file-arguments-options). +The only difference between `$` and `execa` is that the former includes script-friendly default options: [`stdin: 'inherit'`](../readme.md#optionsstdin) and [`preferLocal: true`](../readme.md#optionspreferlocal). ```js import {execa, $} from 'execa'; @@ -117,7 +125,7 @@ await $`npm run build ${[ // Execa await $`npm run build --example-flag-one - --example-flag-two` + --example-flag-two`; ``` ### Concatenation @@ -634,12 +642,7 @@ await $`echo ${__filename}`; ```js // Execa -import {fileURLToPath} from 'node:url'; -import path from 'node:path'; - -const __filename = path.basename(fileURLToPath(import.meta.url)); - -await $`echo ${__filename}`; +await $`echo ${import.meta.filename}`; ``` ### Current directory @@ -746,7 +749,7 @@ const transform = function * (line) { await $({stdout: [transform, 'inherit']})`echo ${'This is a secret.'}`; ``` -### Cancellation +### Cancelation ```sh # Bash diff --git a/docs/shell.md b/docs/shell.md new file mode 100644 index 0000000000..f758f99b6c --- /dev/null +++ b/docs/shell.md @@ -0,0 +1,32 @@ + + + execa logo + +
+ +# Shell + +## Avoiding shells + +In general, shells should be avoided because they are: +- not cross-platform, encouraging shell-specific syntax. +- slower, because of the additional shell interpretation. +- unsafe, potentially allowing command injection. + +## Specific shell + +```js +import {execa} from 'execa'; + +await execa({shell: '/bin/bash'})`npm run "$TASK" && npm run test`; +``` + +## OS-specific shell + +When the [`shell`](../readme.md#optionsshell) option is `true`, `sh` is used on Unix and [`cmd.exe`](https://en.wikipedia.org/wiki/Cmd.exe) is used on Windows. + +`sh` and `cmd.exe` syntaxes are very different. Therefore, this is not very useful. + +```js +await execa({shell: true})`npm run build` +``` diff --git a/docs/streams.md b/docs/streams.md new file mode 100644 index 0000000000..807d5f550d --- /dev/null +++ b/docs/streams.md @@ -0,0 +1,116 @@ + + + execa logo + +
+ +# Streams + +## Node.js streams + +### Input + +```js +import {createReadStream} from 'node:fs'; +import {once} from 'node:events'; +import {execa} from 'execa'; + +const readable = createReadStream('./input.txt'); +await once(readable, 'open'); +await execa({stdin: readable})`npm run scaffold`; +``` + +### Output + +```js +import {createWriteStream} from 'node:fs'; +import {once} from 'node:events'; +import {execa} from 'execa'; + +const writable = createWriteStream('./output.txt'); +await once(writable, 'open'); +await execa({stdout: writable})`npm run build`; +``` + +### File descriptors + +When passing a Node.js stream to the [`stdin`](../readme.md#optionsstdin), [`stdout`](../readme.md#optionsstdout) or [`stderr`](../readme.md#optionsstderr) option, that stream must have an underlying file or socket, such as the streams created by the [`fs`](https://nodejs.org/api/fs.html#filehandlecreatereadstreamoptions), [`net`](https://nodejs.org/api/net.html#new-netsocketoptions) or [`http`](https://nodejs.org/api/http.html#class-httpincomingmessage) core modules. Otherwise the following error is thrown. + +``` +TypeError [ERR_INVALID_ARG_VALUE]: The argument 'stdio' is invalid. +``` + +This limitation can be worked around by passing either: +- Using the [`input`](../readme.md#optionsinput) option instead of the [`stdin`](../readme.md#optionsstdin) option. +- A [web stream](#web-streams) +- [`[nodeStream, 'pipe']`](input.md#multiple-targets) instead of `nodeStream`. + +```diff +- await execa(..., {stdout: nodeStream}); ++ await execa(..., {stdout: [nodeStream, 'pipe']}); +``` + +## Web streams + +Web streams ([`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) or [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream)) can be used instead of Node.js streams. + +```js +const response = await fetch('https://example.com'); +await execa({stdin: response.body})`npm run build`; +``` + +## Iterables as input + +```js +const getReplInput = async function * () { + for await (const replLine of getReplLines()) { + yield replLine; + } +}; + +await execa({stdin: getReplInput()})`npm run scaffold`; +``` + +## Manual streaming + +[`subprocess.stdin`](../readme.md#subprocessstdin) is a Node.js [`Readable`](https://nodejs.org/api/stream.html#class-streamreadable) stream and [`subprocess.stdout`](../readme.md#subprocessstdout)/[`subprocess.stderr`](../readme.md#subprocessstderr)/[`subprocess.all`](../readme.md#subprocessall) are Node.js [`Writable`](https://nodejs.org/api/stream.html#class-streamwritable) streams. + +They can be used to stream input/output manually. This is intended for advanced situations. In most cases, the following simpler solutions can be used: +- [`result.stdout`](output.md#stdout-and-stderr), [`result.stderr`](output.md#stdout-and-stderr) or [`result.stdio`](output.md#additional-file-descriptors). +- The [`stdin`](../readme.md#optionsstdin), [`stdout`](../readme.md#optionsstdout), [`stderr`](../readme.md#optionsstderr) or [`stdio`](../readme.md#optionsstdio) [options](input.md). +- [`subprocess.iterable()`](lines.md#progressive-splitting). +- [`subprocess.pipe()`](pipe.md). + +## Converting a subprocess to a stream + +### Convert + +The [`subprocess.readable()`](../readme.md#subprocessreadablereadableoptions), [`subprocess.writable()`](../readme.md#subprocesswritablewritableoptions) and [`subprocess.duplex()`](../readme.md#subprocessduplexduplexoptions) methods convert the subprocess to a Node.js [`Readable`](https://nodejs.org/api/stream.html#class-streamreadable), [`Writable`](https://nodejs.org/api/stream.html#class-streamwritable) and [`Duplex`](https://nodejs.org/api/stream.html#class-streamduplex) stream. + +This is useful when using a library or API that expects Node.js streams as arguments. In every other situation, simpler solutions exist, as described [above](#manual-streaming). + +```js +const readable = execa`npm run scaffold`.readable(); + +const writable = execa`npm run scaffold`.writable(); + +const duplex = execa`npm run scaffold`.duplex(); +``` + +### Different file descriptor + +By default, [`subprocess.readable()`](../readme.md#subprocessreadablereadableoptions), [`subprocess.writable()`](../readme.md#subprocesswritablewritableoptions) and [`subprocess.duplex()`](../readme.md#subprocessduplexduplexoptions) methods use `stdin` and `stdout`. This can be changed using the [`from`](../readme.md#readableoptionsfrom) and [`to`](../readme.md#writableoptionsto) options. + +```js +const readable = execa`npm run scaffold`.readable({from: 'stderr'}); + +const writable = execa`npm run scaffold`.writable({to: 'fd3'}); + +const duplex = execa`npm run scaffold`.duplex({from: 'stderr', to: 'fd3'}); +``` + +### Error handling + +When using [`subprocess.readable()`](../readme.md#subprocessreadablereadableoptions), [`subprocess.writable()`](../readme.md#subprocesswritablewritableoptions) or [`subprocess.duplex()`](../readme.md#subprocessduplexduplexoptions), the stream waits for the subprocess to end, and emits an [`error`](https://nodejs.org/api/stream.html#event-error) event if the subprocess [fails](errors.md). This differs from [`subprocess.stdin`](../readme.md#subprocessstdin), [`subprocess.stdout`](../readme.md#subprocessstdout) and [`subprocess.stderr`](../readme.md#subprocessstderr)'s behavior. + +This means you do not need to `await` the subprocess' [promise](execution.md#result). On the other hand, you (or the library using the stream) do need to both consume the stream, and handle its `error` event. This can be done by using [`await finished(stream)`](https://nodejs.org/api/stream.html#streamfinishedstream-options), [`await pipeline(..., stream, ...)`](https://nodejs.org/api/stream.html#streampipelinesource-transforms-destination-options) or [`await text(stream)`](https://nodejs.org/api/webstreams.html#streamconsumerstextstream) which throw an exception when the stream errors. diff --git a/docs/termination.md b/docs/termination.md new file mode 100644 index 0000000000..d7d2a990af --- /dev/null +++ b/docs/termination.md @@ -0,0 +1,180 @@ + + + execa logo + +
+ +# Termination + +## Canceling + +The [`cancelSignal`](../readme.md#optionscancelsignal) option can be used to cancel a subprocess. When `abortController` is aborted, a [`SIGTERM` signal](#default-signal) is sent to the subprocess. + +```js +import {execa} from 'execa'; + +const abortController = new AbortController(); + +setTimeout(() => { + abortController.abort(); +}, 5000); + +try { + await execa({cancelSignal: abortController.signal})`npm run build`; +} catch (error) { + if (error.isCanceled) { + console.error('Aborted by cancelSignal.') + } + + throw error; +} +``` + +## Timeout + +If the subprocess lasts longer than the [`timeout`](../readme.md#optionstimeout) option, a [`SIGTERM` signal](#default-signal) is sent to it. + +```js +try { + await execa({timeout: 5000})`npm run build`; +} catch (error) { + if (error.timedOut) { + console.error('Timed out.') + } + + throw error; +} +``` + +## Current process exit + +If the current process exits, the subprocess is automatically [terminated](#default-signal) unless either: +- The [`cleanup`](../readme.md#optionscleanup) option is `false`. +- The subprocess is run in the background using the [`detached`](../readme.md#optionsdetached) option. +- The current process was terminated abruptly, for example, with `SIGKILL` as opposed to `SIGTERM` or a normal exit. + +## Signal termination + +[`subprocess.kill()`](../readme.md#subprocesskillsignal-error) sends a signal to the subprocess. This is an inter-process message handled by the OS. Most (but [not all](https://github.com/ehmicky/human-signals#action)) signals terminate the subprocess. + +[More info.](https://nodejs.org/api/child_process.html#subprocesskillsignal) + +### SIGTERM + +`SIGTERM` is the default signal. It terminates the subprocess. + +```js +const subprocess = execa`npm run build`; +subprocess.kill(); +// Or: +subprocess.kill('SIGTERM'); +``` + +The subprocess can [handle that signal](https://nodejs.org/api/process.html#process_signal_events) to run any cleanup logic. + +```js +process.on('SIGTERM', () => { + cleanup(); + process.exit(1); +}) +``` + +### SIGKILL + +`SIGKILL` is like `SIGTERM` except it forcefully terminates, i.e. it does not allow the subprocess to handle the signal. + +```js +subprocess.kill('SIGKILL'); +``` + +### Other signals + +Other signals can be used by passing them as argument. However, most other signals do not fully [work on Windows](https://github.com/ehmicky/cross-platform-node-guide/blob/main/docs/6_networking_ipc/signals.md#cross-platform-signals). + +### Default signal + +The [`killSignal`](../readme.md#optionskillsignal) option sets the default signal used by [`subprocess.kill()`](../readme.md#subprocesskillsignal-error) and the following options: [`cancelSignal`](#canceling), [`timeout`](#timeout), [`maxBuffer`](output.md#big-output) and [`cleanup`](#current-process-exit). It is [`SIGTERM`](#sigterm) by default. + +```js +const subprocess = execa({killSignal: 'SIGKILL'})`npm run build`; +subprocess.kill(); // Forceful termination +``` + +### Signal name and description + +When a subprocess was terminated by a signal, [`error.isTerminated`](../readme.md#resultisterminated) is `true`. + +Also, [`error.signal`](../readme.md#resultsignal) and [`error.signalDescription`](../readme.md#resultsignaldescription) indicate the signal's name and human-friendly description. On Windows, those are only set if the current process terminated the subprocess, as opposed to [another process](#inter-process-termination). + +```js +try { + await execa`npm run build`; +} catch (error) { + if (error.isTerminated) { + console.error(error.signal); // SIGFPE + console.error(error.signalDescription); // 'Floating point arithmetic error' + } + + throw error; +} +``` + +## Forceful termination + +If the subprocess is terminated but does not exit, [`SIGKILL`](https://en.wikipedia.org/wiki/Signal_(IPC)#SIGKILL) is automatically sent to forcefully terminate it. + +The grace period is set by the [`forceKillAfterDelay`](../readme.md#optionsforcekillafterdelay) option, which is 5 seconds by default. This feature can be disabled with `false`. + +This works when the subprocess is terminated by either: +- the [`cancelSignal`](#canceling), [`timeout`](#timeout), [`maxBuffer`](output.md#big-output) or [`cleanup`](#current-process-exit) option +- calling [`subprocess.kill()`](../readme.md#subprocesskillsignal-error) with no arguments + +This does not work when the subprocess is terminated by either: +- calling [`subprocess.kill()`](#subprocesskillsignal-error) with an argument +- calling [`process.kill(subprocess.pid)`](../readme.md#subprocesspid) +- sending a termination signal [from another process](#inter-process-termination) + +Also, this does not work on Windows, because Windows [doesn't support signals](https://nodejs.org/api/process.html#process_signal_events): `SIGKILL` and `SIGTERM` both terminate the subprocess immediately. Other packages (such as [`taskkill`](https://github.com/sindresorhus/taskkill)) can be used to achieve fail-safe termination on Windows. + +```js +// No forceful termination +const subprocess = execa({forceKillAfterDelay: false})`npm run build`; +subprocess.kill(); +``` + +## Inter-process termination + +[`subprocess.kill()`](../readme.md#subprocesskillsignal-error) only work when the current process terminates the subprocess. To terminate the subprocess from a different process (for example, a terminal), its [`subprocess.pid`](../readme.md#subprocesspid) can be used instead. + +```js +const subprocess = execa`npm run build`; +console.log('PID:', subprocess.pid); // PID: 6513 +await subprocess; +``` + +```sh +$ kill -SIGTERM 6513 +``` + +## Error message and stack trace + +When terminating a subprocess, it is possible to include an error message and stack trace by using [`subprocess.kill(error)`](../readme.md#subprocesskillerror). The `error` argument will be available at [`error.cause`](../readme.md#errorcause). + +```js + +try { + const subprocess = execa`npm run build`; + setTimeout(() => { + subprocess.kill(new Error('Timed out after 5 seconds.')); + }, 5000); + await subprocess; +} catch (error) { + if (error.isTerminated) { + console.error(error.cause); // new Error('Timed out after 5 seconds.') + console.error(error.cause.stack); // Stack trace from `error.cause` + console.error(error.originalMessage); // 'Timed out after 5 seconds.' + } + + throw error; +} +``` diff --git a/docs/transform.md b/docs/transform.md index ecef4b99a5..b318b258c2 100644 --- a/docs/transform.md +++ b/docs/transform.md @@ -1,3 +1,9 @@ + + + execa logo + +
+ # Transforms ## Summary @@ -12,17 +18,10 @@ const transform = function * (line) { yield `${prefix}: ${line}`; }; -const {stdout} = await execa('./run.js', {stdout: transform}); +const {stdout} = await execa({stdout: transform})`npm run build`; console.log(stdout); // HELLO ``` -## Encoding - -The `line` argument passed to the transform is a string by default.\ -However, if the [`binary`](#transformoptionsbinary) transform option is `true` or if the [`encoding`](../readme.md#optionsencoding) subprocess option is binary, it is an `Uint8Array` instead. - -The transform can `yield` either a `string` or an `Uint8Array`, regardless of the `line` argument's type. - ## Filtering `yield` can be called 0, 1 or multiple times. Not calling `yield` enables filtering a specific line. @@ -36,83 +35,10 @@ const transform = function * (line) { } }; -const {stdout} = await execa('echo', ['This is a secret.'], {stdout: transform}); +const {stdout} = await execa({stdout: transform})`echo ${'This is a secret'}`; console.log(stdout); // '' ``` -## Binary data - -The transform iterates over lines by default.\ -However, if the [`binary`](#transformoptionsbinary) transform option is `true` or if the [`encoding`](../readme.md#optionsencoding) subprocess option is binary, it iterates over arbitrary chunks of data instead. - -```js -await execa('./binary.js', {stdout: {transform, binary: true}}); -``` - -This is more efficient and recommended if the data is either: -- Binary: Which does not have lines. -- Text: But the transform works even if a line or word is split across multiple chunks. - -## Newlines - -Unless the [`binary`](#transformoptionsbinary) transform option is `true`, the transform iterates over lines. -By default, newlines are stripped from each `line` argument. - -```js -// `line`'s value never ends with '\n'. -const transform = function * (line) { /* ... */ }; - -await execa('./run.js', {stdout: transform}); -``` - -However, if the [`preserveNewlines`](#transformoptionspreservenewlines) transform option is `true`, newlines are kept. - -```js -// `line`'s value ends with '\n'. -// The output's last `line` might or might not end with '\n', depending on the output. -const transform = function * (line) { /* ... */ }; - -await execa('./run.js', {stdout: {transform, preserveNewlines: true}}); -``` - -Each `yield` produces at least one line. Calling `yield` multiple times or calling `yield *` produces multiples lines. - -```js -const transform = function * (line) { - yield 'Important note:'; - yield 'Read the comments below.'; - - // Or: - yield * [ - 'Important note:', - 'Read the comments below.', - ]; - - // Is the same as: - yield 'Important note:\nRead the comments below.\n'; - - yield line -}; - -await execa('./run.js', {stdout: transform}); -``` - -However, if the [`preserveNewlines`](#transformoptionspreservenewlines) transform option is `true`, multiple `yield`s produce a single line instead. - -```js -const transform = function * (line) { - yield 'Important note: '; - yield 'Read the comments below.\n'; - - // Is the same as: - yield 'Important note: Read the comments below.\n'; - - yield line -}; - -await execa('./run.js', {stdout: {transform, preserveNewlines: true}}); -``` - ## Object mode By default, `stdout` and `stderr`'s transforms must return a string or an `Uint8Array`.\ @@ -123,7 +49,7 @@ const transform = function * (line) { yield JSON.parse(line); }; -const {stdout} = await execa('./jsonlines-output.js', {stdout: {transform, objectMode: true}}); +const {stdout} = await execa({stdout: {transform, objectMode: true}})`node jsonlines-output.js`; for (const data of stdout) { console.log(stdout); // {...} } @@ -137,7 +63,7 @@ const transform = function * (line) { }; const input = [{event: 'example'}, {event: 'otherExample'}]; -await execa('./jsonlines-input.js', {stdin: [input, {transform, objectMode: true}]}); +await execa({stdin: [input, {transform, objectMode: true}]})`node jsonlines-input.js`; ``` ## Sharing state @@ -169,7 +95,7 @@ const final = function * () { yield `Number of lines: ${count}`; }; -const {stdout} = await execa('./command.js', {stdout: {transform, final}}); +const {stdout} = await execa({stdout: {transform, final}})`npm run build`; console.log(stdout); // Ends with: 'Number of lines: 54' ``` @@ -179,54 +105,39 @@ A [`Duplex`](https://nodejs.org/api/stream.html#class-streamduplex) stream, Node Like generator functions, web `TransformStream` can be passed either directly or as a `{transform}` plain object. But `Duplex` and `Transform` must always be passed as a `{transform}` plain object. -The [`objectMode`](#object-mode) transform option can be used, but not the [`binary`](#encoding) nor [`preserveNewlines`](#newlines) options. +The [`objectMode`](#object-mode) transform option can be used, but not the [`binary`](#transformoptionsbinary) nor [`preserveNewlines`](#transformoptionspreservenewlines) options. ```js import {createGzip} from 'node:zlib'; import {execa} from 'execa'; -const {stdout} = await execa('./run.js', {stdout: {transform: createGzip()}}); +const {stdout} = await execa({stdout: {transform: createGzip()}})`npm run build`; console.log(stdout); // `stdout` is compressed with gzip ``` ```js -import {execa} from 'execa'; - -const {stdout} = await execa('./run.js', {stdout: new CompressionStream('gzip')}); +const {stdout} = await execa({stdout: new CompressionStream('gzip')})`npm run build`; console.log(stdout); // `stdout` is compressed with gzip ``` ## Combining -The [`stdin`](../readme.md#optionsstdin), [`stdout`](../readme.md#optionsstdout), [`stderr`](../readme.md#optionsstderr) and [`stdio`](../readme.md#optionsstdio) options can accept an array of values. While this is not specific to transforms, this can be useful with them too. For example, the following transform impacts the value printed by `inherit`. +The [`stdin`](../readme.md#optionsstdin), [`stdout`](../readme.md#optionsstdout), [`stderr`](../readme.md#optionsstderr) and [`stdio`](../readme.md#optionsstdio) options can accept [an array of values](input.md#multiple-targets). While this is not specific to transforms, this can be useful with them too. For example, the following transform impacts the value printed by `inherit`. ```js -await execa('echo', ['hello'], {stdout: [transform, 'inherit']}); +await execa({stdout: [transform, 'inherit']})`npm run build`; ``` This also allows using multiple transforms. ```js -await execa('echo', ['hello'], {stdout: [transform, otherTransform]}); +await execa({stdout: [transform, otherTransform]})`npm run build`; ``` Or saving to files. ```js -await execa('./run.js', {stdout: [new CompressionStream('gzip'), {file: './output.gz'}]}); -``` - -## Async iteration - -In some cases, [iterating](../readme.md#subprocessiterablereadableoptions) over the subprocess can be an alternative to transforms. - -```js -import {execa} from 'execa'; - -for await (const line of execa('./run.js')) { - const prefix = line.includes('error') ? 'ERROR' : 'INFO'; - console.log(`${prefix}: ${line}`); -} +await execa({stdout: [new CompressionStream('gzip'), {file: './output.gz'}]})`npm run build`; ``` ## Transform options @@ -258,7 +169,7 @@ Default: `false` If `true`, iterate over arbitrary chunks of `Uint8Array`s instead of line `string`s. -More info [here](#encoding) and [there](#binary-data). +[More info.](binary.md#transforms) ### transformOptions.preserveNewlines @@ -267,7 +178,7 @@ Default: `false` If `true`, keep newlines in each `line` argument. Also, this allows multiple `yield`s to produces a single line. -[More info.](#newlines) +[More info.](lines.md#transforms-1) ### transformOptions.objectMode diff --git a/docs/windows.md b/docs/windows.md new file mode 100644 index 0000000000..c6e32abad7 --- /dev/null +++ b/docs/windows.md @@ -0,0 +1,35 @@ + + + execa logo + +
+ +# Windows + +Although subprocesses are implemented very differently on Unix and Windows, Execa makes it cross-platform. However, there are a few instances where Windows behavior differs, which are noted here. + +## Signals + +Only few [signals](termination.md#other-signals) work on Windows with Node.js: [`SIGTERM`](termination.md#sigterm), [`SIGKILL`](termination.md#sigkill) and `SIGINT`. Also, sending signals from other processes is [not supported](termination.md#signal-name-and-description). Finally, the [`forceKillAfterDelay`](../readme.md#optionsforcekillafterdelay) option [is a noop](termination.md#forceful-termination) on Windows. + +## Asynchronous I/O + +The default value for the [`stdin`](../readme.md#optionsstdin), [`stdout`](../readme.md#optionsstdout) and [`stderr`](../readme.md#optionsstderr) options is `'pipe'`. This returns the output as [`result.stdout`](../readme.md#resultstdout) and [`result.stderr`](../readme.md#resultstderr) and allows for [manual streaming](streams.md#manual-streaming). + +Instead of `'pipe'`, `'overlapped'` can be used instead to use [asynchronous I/O](https://learn.microsoft.com/en-us/windows/win32/fileio/synchronous-and-asynchronous-i-o) under-the-hood instead of the default behavior which is synchronous. + +## `cmd.exe` escaping + +If the [`windowsVerbatimArguments`](../readme.md#optionswindowsverbatimarguments) option is `false`, the command arguments are automatically escaped on Windows. When using a `cmd.exe` [shell](#optionsshell), this is `true` instead by default. + +This is ignored on other platforms: those are [automatically escaped](escaping.md) by default. + +## Console window + +If the [`windowsHide`](../readme.md#optionswindowshide) option is `false`, the subprocess is run in a new console window. This is necessary to make [`SIGINT` work](https://github.com/nodejs/node/issues/29837) on Windows, and to prevent subprocesses not being cleaned up in [some specific situations](https://github.com/sindresorhus/execa/issues/433). + +## UID and GID + +By default, subprocesses are run using the current [user](https://en.wikipedia.org/wiki/User_identifier) and [group](https://en.wikipedia.org/wiki/Group_identifier). The [`uid`](../readme.md#optionsuid) and [`gid`](../readme.md#optionsgid) options can be used to set a different user or group. + +However, since Windows use a different permission model, those options throw. diff --git a/lib/pipe/abort.js b/lib/pipe/abort.js index d8b5d34119..1d3caec588 100644 --- a/lib/pipe/abort.js +++ b/lib/pipe/abort.js @@ -10,7 +10,7 @@ export const unpipeOnAbort = (unpipeSignal, unpipeContext) => unpipeSignal === u const unpipeOnSignalAbort = async (unpipeSignal, {sourceStream, mergedStream, fileDescriptors, sourceOptions, startTime}) => { await aborted(unpipeSignal, sourceStream); await mergedStream.remove(sourceStream); - const error = new Error('Pipe cancelled by `unpipeSignal` option.'); + const error = new Error('Pipe canceled by `unpipeSignal` option.'); throw createNonCommandError({ error, fileDescriptors, diff --git a/readme.md b/readme.md index 904c859e2d..708b41a0b5 100644 --- a/readme.md +++ b/readme.md @@ -48,7 +48,7 @@ This package improves [`child_process`](https://nodejs.org/api/child_process.html) methods with: - [Promise interface](#execafile-arguments-options). -- [Script interface](docs/scripts.md) and [template strings](#template-string-syntax), like `zx`. +- [Script interface](docs/scripts.md) and [template strings](docs/execution.md#template-string-syntax), like `zx`. - Improved [Windows support](https://github.com/IndigoUnited/node-cross-spawn#why), including [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) binaries. - Executes [locally installed binaries](#optionspreferlocal) without `npx`. - [Cleans up](#optionscleanup) subprocesses when the current process ends. @@ -56,7 +56,7 @@ This package improves [`child_process`](https://nodejs.org/api/child_process.htm - [Transform](docs/transform.md) `stdin`/`stdout`/`stderr` with simple functions. - Iterate over [each text line](docs/transform.md#binary-data) output by the subprocess. - [Fail-safe subprocess termination](#optionsforcekillafterdelay). -- Get [interleaved output](#optionsall) from `stdout` and `stderr` similar to what is printed on the terminal. +- Get [interleaved output](docs/output.md#interleaved-output) from `stdout` and `stderr` similar to what is printed on the terminal. - [Strips the final newline](#optionsstripfinalnewline) from the output so you don't have to do `stdout.trim()`. - Convenience methods to pipe subprocesses' [input](#redirect-input-from-a-file) and [output](#redirect-output-to-a-file). - [Verbose mode](#verbose-mode) for debugging. @@ -274,6 +274,32 @@ try { } ``` +## Documentation + +Execution: +- ▶️ [Basic execution](docs/execution.md) +- 💬 [Escaping/quoting](docs/escaping.md) +- 💻 [Shell](docs/shell.md) +- 📜 [Scripts](docs/scripts.md) +- 🐢 [Node.js files](docs/node.md) +- 🌐 [Environment](docs/environment.md) +- ❌ [Errors](docs/errors.md) +- 🏁 [Termination](docs/termination.md) + +Input/output: +- 🎹 [Input](docs/input.md) +- 📢 [Output](docs/output.md) +- 📃 [Output lines](docs/lines.md) +- 🤖 [Binary data](docs/binary.md) +- 🧙 [Transforms](docs/transform.md) + +Advanced usage: +- 🔀 [Piping multiple subprocesses](docs/pipe.md) +- ⏳️ [Streams](docs/streams.md) +- 📞 [Inter-process communication](docs/ipc.md) +- 🐛 [Debugging](docs/debugging.md) +- 📎 [Windows](docs/windows.md) + ## API ### Methods @@ -287,7 +313,7 @@ _Returns_: [`Subprocess`](#subprocess) Executes a command using `file ...arguments`. -Arguments are [automatically escaped](#shell-syntax). They can contain any character, including spaces, tabs and newlines. +More info on the [syntax](docs/execution.md#array-syntax) and [escaping](docs/escaping.md#array-syntax). #### execa\`command\` #### execa(options)\`command\` @@ -296,13 +322,9 @@ Arguments are [automatically escaped](#shell-syntax). They can contain any chara `options`: [`Options`](#options)\ _Returns_: [`Subprocess`](#subprocess) -Executes a command. `command` is a [template string](#template-string-syntax) and includes both the `file` and its `arguments`. - -The `command` template string can inject any `${value}` with the following types: string, number, [`subprocess`](#subprocess) or an array of those types. For example: `` execa`echo one ${'two'} ${3} ${['four', 'five']}` ``. For `${subprocess}`, the subprocess's [`stdout`](#resultstdout) is used. - -Arguments are [automatically escaped](#shell-syntax). They can contain any character, but spaces, tabs and newlines must use `${}` like `` execa`echo ${'has space'}` ``. +Executes a command. `command` is a [template string](docs/execution.md#template-string-syntax) that includes both the `file` and its `arguments`. -The `command` template string can use [multiple lines and indentation](docs/scripts.md#multiline-commands). +More info on the [syntax](docs/execution.md#template-string-syntax) and [escaping](docs/escaping.md#template-string-syntax). #### execa(options) @@ -311,7 +333,7 @@ _Returns_: [`execa`](#execafile-arguments-options) Returns a new instance of Execa but with different default [`options`](#options). Consecutive calls are merged to previous ones. -This allows setting global options or [sharing options](#globalshared-options) between multiple commands. +[More info.](docs/execution.md#globalshared-options) #### execaSync(file, arguments?, options?) #### execaSync\`command\` @@ -320,16 +342,7 @@ Same as [`execa()`](#execafile-arguments-options) but synchronous. Returns or throws a subprocess [`result`](#result). The [`subprocess`](#subprocess) is not returned: its methods and properties are not available. -The following features cannot be used: -- Streams: [`subprocess.stdin`](#subprocessstdin), [`subprocess.stdout`](#subprocessstdout), [`subprocess.stderr`](#subprocessstderr), [`subprocess.readable()`](#subprocessreadablereadableoptions), [`subprocess.writable()`](#subprocesswritablewritableoptions), [`subprocess.duplex()`](#subprocessduplexduplexoptions). -- The [`stdin`](#optionsstdin), [`stdout`](#optionsstdout), [`stderr`](#optionsstderr) and [`stdio`](#optionsstdio) options cannot be [`'overlapped'`](#optionsstdout), an async iterable, an async [transform](docs/transform.md), a [`Duplex`](docs/transform.md#duplextransform-streams), nor a web stream. Node.js streams can be passed but only if either they [have a file descriptor](#redirect-a-nodejs-stream-fromto-stdinstdoutstderr), or the `input` option is used. -- Signal termination: [`subprocess.kill()`](#subprocesskillerror), [`subprocess.pid`](#subprocesspid), [`cleanup`](#optionscleanup) option, [`cancelSignal`](#optionscancelsignal) option, [`forceKillAfterDelay`](#optionsforcekillafterdelay) option. -- Piping multiple processes: [`subprocess.pipe()`](#subprocesspipefile-arguments-options). -- [`subprocess.iterable()`](#subprocessiterablereadableoptions). -- [`ipc`](#optionsipc) and [`serialization`](#optionsserialization) options. -- [`result.all`](#resultall) is not interleaved. -- [`detached`](#optionsdetached) option. -- The [`maxBuffer`](#optionsmaxbuffer) option is always measured in bytes, not in characters, [lines](#optionslines) nor [objects](docs/transform.md#object-mode). Also, it ignores transforms and the [`encoding`](#optionsencoding) option. +[More info.](docs/execution.md#synchronous-execution) #### $(file, arguments?, options?) @@ -338,11 +351,13 @@ The following features cannot be used: `options`: [`Options`](#options)\ _Returns_: [`Subprocess`](#subprocess) -Same as [`execa()`](#execafile-arguments-options) but using the [`stdin: 'inherit'`](#optionsstdin) and [`preferLocal: true`](#optionspreferlocal) options. +Same as [`execa()`](#execafile-arguments-options) but using [script-friendly default options](docs/scripts.md#script-friendly-options). -Just like `execa()`, this can use the [template string syntax](#execacommand) or [bind options](#execaoptions). It can also be [run synchronously](#execasyncfile-arguments-options) using `$.sync()` or `$.s()`. +Just like `execa()`, this can use the [template string syntax](docs/execution.md#template-string-syntax) or [bind options](docs/execution.md#globalshared-options). It can also be [run synchronously](#execasyncfile-arguments-options) using `$.sync()` or `$.s()`. -This is the preferred method when executing multiple commands in a script file. For more information, please see [this page](docs/scripts.md). +This is the preferred method when executing multiple commands in a script file. + +[More info.](docs/scripts.md) #### execaNode(scriptPath, arguments?, options?) @@ -354,27 +369,25 @@ _Returns_: [`Subprocess`](#subprocess) Same as [`execa()`](#execafile-arguments-options) but using the [`node: true`](#optionsnode) option. Executes a Node.js file using `node scriptPath ...arguments`. -Just like `execa()`, this can use the [template string syntax](#execacommand) or [bind options](#execaoptions). +Just like `execa()`, this can use the [template string syntax](docs/execution.md#template-string-syntax) or [bind options](docs/execution.md#globalshared-options). This is the preferred method when executing Node.js files. +[More info.](docs/node.md) + #### execaCommand(command, options?) `command`: `string`\ `options`: [`Options`](#options)\ _Returns_: [`Subprocess`](#subprocess) -[`execa`](#execafile-arguments-options) with the [template string syntax](#execacommand) allows the `file` or the `arguments` to be user-defined (by injecting them with `${}`). However, if _both_ the `file` and the `arguments` are user-defined, _and_ those are supplied as a single string, then `execaCommand(command)` must be used instead. +Executes a command. `command` is a string that includes both the `file` and its `arguments`. This is only intended for very specific cases, such as a REPL. This should be avoided otherwise. -Just like `execa()`, this can [bind options](#execaoptions). It can also be [run synchronously](#execasyncfile-arguments-options) using `execaCommandSync()`. +Just like `execa()`, this can [bind options](docs/execution.md#globalshared-options). It can also be [run synchronously](#execasyncfile-arguments-options) using `execaCommandSync()`. -Arguments are [automatically escaped](#shell-syntax). They can contain any character, but spaces must be escaped with a backslash like `execaCommand('echo has\\ space')`. - -### Shell syntax - -For all the [methods above](#methods), no shell interpreter (Bash, cmd.exe, etc.) is used unless the [`shell`](#optionsshell) option is set. This means shell-specific characters and expressions (`$variable`, `&&`, `||`, `;`, `|`, etc.) have no special meaning and do not need to be escaped. +[More info.](docs/escaping.md#user-defined-input) ### subprocess @@ -382,6 +395,8 @@ The return value of all [asynchronous methods](#methods) is both: - a `Promise` resolving or rejecting with a subprocess [`result`](#result). - a [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess) with the following methods and properties. +[More info.](docs/execution.md#subprocess) + #### subprocess.pipe(file, arguments?, options?) `file`: `string | URL`\ @@ -389,13 +404,11 @@ The return value of all [asynchronous methods](#methods) is both: `options`: [`Options`](#options) and [`PipeOptions`](#pipeoptions)\ _Returns_: [`Promise`](#result) -[Pipe](https://nodejs.org/api/stream.html#readablepipedestination-options) the subprocess' `stdout` to a second Execa subprocess' `stdin`. This resolves with that second subprocess' [result](#result). If either subprocess is rejected, this is rejected with that subprocess' [error](#execaerror) instead. +[Pipe](https://nodejs.org/api/stream.html#readablepipedestination-options) the subprocess' [`stdout`](#subprocessstdout) to a second Execa subprocess' [`stdin`](#subprocessstdin). This resolves with that second subprocess' [result](#result). If either subprocess is rejected, this is rejected with that subprocess' [error](#execaerror) instead. This follows the same syntax as [`execa(file, arguments?, options?)`](#execafile-arguments-options) except both [regular options](#options) and [pipe-specific options](#pipeoptions) can be specified. -This can be called multiple times to chain a series of subprocesses. - -Multiple subprocesses can be piped to the same subprocess. Conversely, the same subprocess can be piped to multiple other subprocesses. +[More info.](docs/pipe.md#array-syntax) #### subprocess.pipe\`command\` #### subprocess.pipe(options)\`command\` @@ -404,7 +417,9 @@ Multiple subprocesses can be piped to the same subprocess. Conversely, the same `options`: [`Options`](#options) and [`PipeOptions`](#pipeoptions)\ _Returns_: [`Promise`](#result) -Like [`subprocess.pipe(file, arguments?, options?)`](#subprocesspipefile-arguments-options) but using a [`command` template string](docs/scripts.md#piping-stdout-to-another-command) instead. This follows the same syntax as `execa` [template strings](#execacommand). +Like [`subprocess.pipe(file, arguments?, options?)`](#subprocesspipefile-arguments-options) but using a [`command` template string](docs/scripts.md#piping-stdout-to-another-command) instead. This follows the same syntax as `execa` [template strings](docs/execution.md#template-string-syntax). + +[More info.](docs/pipe.md#template-string-syntax) #### subprocess.pipe(secondSubprocess, pipeOptions?) @@ -414,7 +429,7 @@ _Returns_: [`Promise`](#result) Like [`subprocess.pipe(file, arguments?, options?)`](#subprocesspipefile-arguments-options) but using the [return value](#subprocess) of another `execa()` call instead. -This is the most advanced method to pipe subprocesses. It is useful in specific cases, such as piping multiple subprocesses to the same subprocess. +[More info.](docs/pipe.md#advanced-syntax) ##### pipeOptions @@ -427,14 +442,18 @@ Default: `"stdout"` Which stream to pipe from the source subprocess. A file descriptor like `"fd3"` can also be passed. -`"all"` pipes both `stdout` and `stderr`. This requires the [`all`](#optionsall) option to be `true`. +`"all"` pipes both [`stdout`](#subprocessstdout) and [`stderr`](#subprocessstderr). This requires the [`all`](#optionsall) option to be `true`. + +[More info.](docs/pipe.md#source-file-descriptor) ##### pipeOptions.to Type: `"stdin" | "fd3" | "fd4" | ...`\ Default: `"stdin"` -Which stream to pipe to the destination subprocess. A file descriptor like `"fd3"` can also be passed. +Which [stream](#subprocessstdin) to pipe to the destination subprocess. A file descriptor like `"fd3"` can also be passed. + +[More info.](docs/pipe.md#destination-file-descriptor) ##### pipeOptions.unpipeSignal @@ -442,7 +461,7 @@ Type: [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSign Unpipe the subprocess when the signal aborts. -The [`subprocess.pipe()`](#subprocesspipefile-arguments-options) method will be rejected with a cancellation error. +[More info.](docs/pipe.md#unpipe) #### subprocess.kill(signal, error?) #### subprocess.kill(error?) @@ -457,7 +476,7 @@ This returns `false` when the signal could not be sent, for example when the sub When an error is passed as argument, it is set to the subprocess' [`error.cause`](#errorcause). The subprocess is then terminated with the default signal. This does not emit the [`error` event](https://nodejs.org/api/child_process.html#event-error). -[More info.](https://nodejs.org/api/child_process.html#subprocesskillsignal) +[More info.](docs/termination.md) #### subprocess.pid @@ -467,6 +486,8 @@ Process identifier ([PID](https://en.wikipedia.org/wiki/Process_identifier)). This is `undefined` if the subprocess failed to spawn. +[More info.](docs/termination.md#inter-process-termination) + #### subprocess.send(message) `message`: `unknown`\ @@ -479,7 +500,7 @@ This returns `true` on success. This requires the [`ipc`](#optionsipc) option to be `true`. -[More info.](https://nodejs.org/api/child_process.html#subprocesssendmessage-sendhandle-options-callback) +[More info.](docs/ipc.md#exchanging-messages) #### subprocess.on('message', (message) => void) @@ -490,7 +511,7 @@ The subprocess sends it using [`process.send(message)`](https://nodejs.org/api/p This requires the [`ipc`](#optionsipc) option to be `true`. -[More info.](https://nodejs.org/api/child_process.html#event-message) +[More info.](docs/ipc.md#exchanging-messages) #### subprocess.stdin @@ -498,9 +519,9 @@ Type: [`Writable | null`](https://nodejs.org/api/stream.html#class-streamwritabl The subprocess [`stdin`](#optionsstdin) as a stream. -This is `null` if the [`stdin`](#optionsstdin) option is set to `'inherit'`, `'ignore'`, `Readable` or `integer`. +This is `null` if the [`stdin`](#optionsstdin) option is set to [`'inherit'`](docs/input.md#terminal-input), [`'ignore'`](docs/input.md#ignore-input), [`Readable`](docs/streams.md#input) or [`integer`](docs/output.md#redirect-to-parent-process). -This is intended for advanced cases. Please consider using the [`stdin`](#optionsstdin) option, [`input`](#optionsinput) option, [`inputFile`](#optionsinputfile) option, or [`subprocess.pipe()`](#subprocesspipefile-arguments-options) instead. +[More info.](docs/streams.md#manual-streaming) #### subprocess.stdout @@ -508,9 +529,9 @@ Type: [`Readable | null`](https://nodejs.org/api/stream.html#class-streamreadabl The subprocess [`stdout`](#optionsstdout) as a stream. -This is `null` if the [`stdout`](#optionsstdout) option is set to `'inherit'`, `'ignore'`, `Writable` or `integer`. +This is `null` if the [`stdout`](#optionsstdout) option is set to [`'inherit'`](docs/output.md#redirect-to-parent-process), [`'ignore'`](docs/output.md#ignore-output), [`Writable`](docs/streams.md#output) or [`integer`](docs/output.md#redirect-to-parent-process), or if the [`buffer`](#optionsbuffer) option is `false`. -This is intended for advanced cases. Please consider using [`result.stdout`](#resultstdout), the [`stdout`](#optionsstdout) option, [`subprocess.iterable()`](#subprocessiterablereadableoptions), or [`subprocess.pipe()`](#subprocesspipefile-arguments-options) instead. +[More info.](docs/streams.md#manual-streaming) #### subprocess.stderr @@ -518,9 +539,9 @@ Type: [`Readable | null`](https://nodejs.org/api/stream.html#class-streamreadabl The subprocess [`stderr`](#optionsstderr) as a stream. -This is `null` if the [`stderr`](#optionsstdout) option is set to `'inherit'`, `'ignore'`, `Writable` or `integer`. +This is `null` if the [`stderr`](#optionsstdout) option is set to [`'inherit'`](docs/output.md#redirect-to-parent-process), [`'ignore'`](docs/output.md#ignore-output), [`Writable`](docs/streams.md#output) or [`integer`](docs/output.md#redirect-to-parent-process), or if the [`buffer`](#optionsbuffer) option is `false`. -This is intended for advanced cases. Please consider using [`result.stderr`](#resultstderr), the [`stderr`](#optionsstderr) option, [`subprocess.iterable()`](#subprocessiterablereadableoptions), or [`subprocess.pipe()`](#subprocesspipefile-arguments-options) instead. +[More info.](docs/streams.md#manual-streaming) #### subprocess.all @@ -528,11 +549,11 @@ Type: [`Readable | undefined`](https://nodejs.org/api/stream.html#class-streamre Stream [combining/interleaving](#ensuring-all-output-is-interleaved) [`subprocess.stdout`](#subprocessstdout) and [`subprocess.stderr`](#subprocessstderr). -This is `undefined` if either: -- the [`all`](#optionsall) option is `false` (the default value). -- both [`stdout`](#optionsstdout) and [`stderr`](#optionsstderr) options are set to `'inherit'`, `'ignore'`, `Writable` or `integer`. +This requires the [`all`](#optionsall) option to be `true`. -This is intended for advanced cases. Please consider using [`result.all`](#resultall), the [`stdout`](#optionsstdout)/[`stderr`](#optionsstderr) option, [`subprocess.iterable()`](#subprocessiterablereadableoptions), or [`subprocess.pipe()`](#subprocesspipefile-arguments-options) instead. +This is `undefined` if [`stdout`](#optionsstdout) and [`stderr`](#optionsstderr) options are set to [`'inherit'`](docs/output.md#redirect-to-parent-process), [`'ignore'`](docs/output.md#ignore-output), [`Writable`](docs/streams.md#output) or [`integer`](docs/output.md#redirect-to-parent-process), or if the [`buffer`](#optionsbuffer) option is `false`. + +More info on [interleaving](docs/output.md#interleaved-output) and [streaming](docs/streams.md#manual-streaming). #### subprocess.stdio @@ -540,9 +561,9 @@ Type: [`[Writable | null, Readable | null, Readable | null, ...Array` The output of the subprocess on [`stdin`](#optionsstdin), [`stdout`](#optionsstdout), [`stderr`](#optionsstderr) and [other file descriptors](#optionsstdio). -Items are `undefined` when their corresponding [`stdio`](#optionsstdio) option is set to `'inherit'`, `'ignore'`, `Writable` or `integer`. Items are arrays when their corresponding `stdio` option is a [transform in object mode](docs/transform.md#object-mode). +Items are `undefined` when their corresponding [`stdio`](#optionsstdio) option is set to [`'inherit'`](docs/output.md#redirect-to-parent-process), [`'ignore'`](docs/output.md#ignore-output), [`Writable`](docs/streams.md#output) or [`integer`](docs/output.md#redirect-to-parent-process), or if the [`buffer`](#optionsbuffer) option is `false`. + +Items are arrays when their corresponding `stdio` option is a [transform in object mode](docs/transform.md#object-mode). + +[More info.](docs/output.md#additional-file-descriptors) #### result.failed @@ -715,11 +755,15 @@ Type: `boolean` Whether the subprocess failed to run. +[More info.](docs/errors.md#subprocess-failure) + #### result.timedOut Type: `boolean` -Whether the subprocess timed out. +Whether the subprocess timed out due to the [`timeout`](#optionstimeout) option. + +[More info.](docs/termination.md#timeout) #### result.isCanceled @@ -727,89 +771,104 @@ Type: `boolean` Whether the subprocess was canceled using the [`cancelSignal`](#optionscancelsignal) option. +[More info.](docs/termination.md#canceling) + #### result.isTerminated Type: `boolean` -Whether the subprocess was terminated by a signal (like `SIGTERM`) sent by either: +Whether the subprocess was terminated by a [signal](docs/termination.md#signal-termination) (like [`SIGTERM`](docs/termination.md#sigterm)) sent by either: - The current process. - Another process. This case is [not supported on Windows](https://nodejs.org/api/process.html#signal-events). +[More info.](docs/termination.md#signal-name-and-description) + #### result.isMaxBuffer Type: `boolean` Whether the subprocess failed because its output was larger than the [`maxBuffer`](#optionsmaxbuffer) option. +[More info.](docs/output.md#big-output) + #### result.exitCode Type: `number | undefined` -The numeric exit code of the subprocess that was run. +The numeric [exit code](https://en.wikipedia.org/wiki/Exit_status) of the subprocess that was run. This is `undefined` when the subprocess could not be spawned or was terminated by a [signal](#resultsignal). +[More info.](docs/errors.md#exit-code) + #### result.signal Type: `string | undefined` -The name of the signal (like `SIGTERM`) that terminated the subprocess, sent by either: +The name of the [signal](docs/termination.md#signal-termination) (like [`SIGTERM`](docs/termination.md#sigterm)) that terminated the subprocess, sent by either: - The current process. - Another process. This case is [not supported on Windows](https://nodejs.org/api/process.html#signal-events). -If a signal terminated the subprocess, this property is defined and included in the error message. Otherwise it is `undefined`. +If a signal terminated the subprocess, this property is defined and included in the [error message](#errormessage). Otherwise it is `undefined`. + +[More info.](docs/termination.md#signal-name-and-description) #### result.signalDescription Type: `string | undefined` -A human-friendly description of the signal that was used to terminate the subprocess. For example, `Floating point arithmetic error`. +A human-friendly description of the [signal](docs/termination.md#signal-termination) that was used to terminate the subprocess. If a signal terminated the subprocess, this property is defined and included in the error message. Otherwise it is `undefined`. It is also `undefined` when the signal is very uncommon which should seldomly happen. +[More info.](docs/termination.md#signal-name-and-description) + #### result.pipedFrom Type: [`Array`](#result) -Results of the other subprocesses that were [piped](#pipe-multiple-subprocesses) into this subprocess. This is useful to inspect a series of subprocesses piped with each other. +[Results](#result) of the other subprocesses that were [piped](#pipe-multiple-subprocesses) into this subprocess. This array is initially empty and is populated each time the [`subprocess.pipe()`](#subprocesspipefile-arguments-options) method resolves. +[More info.](docs/pipe.md#errors) + ### ExecaError ### ExecaSyncError Type: `Error` -Exception thrown when the subprocess [fails](#resultfailed), either: -- its [exit code](#resultexitcode) is not `0` -- it was [terminated](#resultisterminated) with a [signal](#resultsignal), including [`subprocess.kill()`](#subprocesskillerror) -- [timing out](#resulttimedout) -- [being canceled](#resultiscanceled) -- there's not enough memory or there are already too many subprocesses +Exception thrown when the subprocess [fails](docs/errors.md#subprocess-failure). This has the same shape as [successful results](#result), with the following additional properties. +[More info.](docs/errors.md) + #### error.message Type: `string` -Error message when the subprocess failed to run. In addition to the [underlying error message](#errororiginalmessage), it also contains some information related to why the subprocess errored. +Error message when the subprocess [failed](docs/errors.md#subprocess-failure) to run. -The subprocess [`stderr`](#resultstderr), [`stdout`](#resultstdout) and other [file descriptors' output](#resultstdio) are appended to the end, separated with newlines and not interleaved. +[More info.](docs/errors.md#error-message) #### error.shortMessage Type: `string` -This is the same as the [`message` property](#errormessage) except it does not include the subprocess [`stdout`](#resultstdout)/[`stderr`](#resultstderr)/[`stdio`](#resultstdio). +This is the same as [`error.message`](#errormessage) except it does not include the subprocess [output](docs/output.md). + +[More info.](docs/errors.md#error-message) #### error.originalMessage Type: `string | undefined` -Original error message. This is the same as the `message` property excluding the subprocess [`stdout`](#resultstdout)/[`stderr`](#resultstderr)/[`stdio`](#resultstdio) and some additional information added by Execa. +Original error message. This is the same as [`error.message`](#errormessage) excluding the subprocess [output](docs/output.md) and some additional information added by Execa. + +This exists only in specific instances, such as during a [timeout](docs/termination.md#timeout). -This exists only if the subprocess exited due to an `error` event or a timeout. +[More info.](docs/errors.md#error-message) #### error.cause @@ -819,6 +878,8 @@ Underlying error, if there is one. For example, this is set by [`subprocess.kill This is usually an `Error` instance. +[More info.](docs/termination.md#error-message-and-stack-trace) + #### error.code Type: `string | undefined` @@ -831,50 +892,52 @@ Type: `object` This lists all options for [`execa()`](#execafile-arguments-options) and the [other methods](#methods). -Some options are related to the subprocess output: [`verbose`](#optionsverbose), [`lines`](#optionslines), [`stripFinalNewline`](#optionsstripfinalnewline), [`buffer`](#optionsbuffer), [`maxBuffer`](#optionsmaxbuffer). By default, those options apply to all file descriptors (`stdout`, `stderr`, etc.). A plain object can be passed instead to apply them to only `stdout`, `stderr`, `fd3`, etc. - -```js -await execa('./run.js', {verbose: 'full'}) // Same value for stdout and stderr -await execa('./run.js', {verbose: {stdout: 'none', stderr: 'full'}}) // Different values -``` +The following options [can specify different values](docs/output.md#stdoutstderr-specific-options) for `stdout` and `stderr`: [`verbose`](#optionsverbose), [`lines`](#optionslines), [`stripFinalNewline`](#optionsstripfinalnewline), [`buffer`](#optionsbuffer), [`maxBuffer`](#optionsmaxbuffer). #### options.reject Type: `boolean`\ Default: `true` -Setting this to `false` resolves the promise with the [error](#execaerror) instead of rejecting it. +Setting this to `false` resolves the [result's promise](#subprocess) with the [error](#execaerror) instead of rejecting it. + +[More info.](docs/errors.md#preventing-exceptions) #### options.shell Type: `boolean | string | URL`\ Default: `false` -If `true`, runs `file` inside of a shell. Uses `/bin/sh` on UNIX and `cmd.exe` on Windows. A different shell can be specified as a string. The shell should understand the `-c` switch on UNIX or `/d /s /c` on Windows. +If `true`, runs the command inside of a shell. + +Uses `/bin/sh` on UNIX and `cmd.exe` on Windows. A different shell can be specified as a string. The shell should understand the `-c` switch on UNIX or `/d /s /c` on Windows. -We recommend against using this option since it is: -- not cross-platform, encouraging shell-specific syntax. -- slower, because of the additional shell interpretation. -- unsafe, potentially allowing command injection. +We [recommend against](docs/shell.md#avoiding-shells) using this option. + +[More info.](docs/shell.md) #### options.cwd Type: `string | URL`\ Default: `process.cwd()` -Current working directory of the subprocess. +Current [working directory](https://en.wikipedia.org/wiki/Working_directory) of the subprocess. This is also used to resolve the [`nodePath`](#optionsnodepath) option when it is a relative path. +[More info.](docs/environment.md#current-directory) + #### options.env Type: `object`\ -Default: `process.env` +Default: [`process.env`](https://nodejs.org/api/process.html#processenv) -Environment key-value pairs. +[Environment variables](https://en.wikipedia.org/wiki/Environment_variable). Unless the [`extendEnv`](#optionsextendenv) option is `false`, the subprocess also uses the current process' environment variables ([`process.env`](https://nodejs.org/api/process.html#processenv)). +[More info.](docs/environment.md#environment-variables) + #### options.extendEnv Type: `boolean`\ @@ -883,20 +946,25 @@ Default: `true` If `true`, the subprocess uses both the [`env`](#optionsenv) option and the current process' environment variables ([`process.env`](https://nodejs.org/api/process.html#processenv)). If `false`, only the `env` option is used, not `process.env`. +[More info.](docs/environment.md#environment-variables) + #### options.preferLocal Type: `boolean`\ Default: `true` with [`$`](#file-arguments-options), `false` otherwise -Prefer locally installed binaries when looking for a binary to execute.\ -If you `$ npm install foo`, you can then `execa('foo')`. +Prefer locally installed binaries when looking for a binary to execute. + +[More info.](docs/environment.md#local-binaries) #### options.localDir Type: `string | URL`\ -Default: `process.cwd()` +Default: [`cwd`](#optionscwd) option -Preferred path to find locally installed binaries in (use with `preferLocal`). +Preferred path to find locally installed binaries, when using the [`preferLocal`](#optionspreferlocal) option. + +[More info.](docs/environment.md#local-binaries) #### options.node @@ -905,15 +973,19 @@ Default: `true` with [`execaNode()`](#execanodescriptpath-arguments-options), `f If `true`, runs with Node.js. The first argument must be a Node.js file. +[More info.](docs/node.md) + #### options.nodeOptions Type: `string[]`\ Default: [`process.execArgv`](https://nodejs.org/api/process.html#process_process_execargv) (current Node.js CLI options) -List of [CLI options](https://nodejs.org/api/cli.html#cli_options) passed to the [Node.js executable](#optionsnodepath). +List of [CLI flags](https://nodejs.org/api/cli.html#cli_options) passed to the [Node.js executable](#optionsnodepath). Requires the [`node`](#optionsnode) option to be `true`. +[More info.](docs/node.md#nodejs-cli-flags) + #### options.nodePath Type: `string | URL`\ @@ -921,142 +993,108 @@ Default: [`process.execPath`](https://nodejs.org/api/process.html#process_proces Path to the Node.js executable. -For example, this can be used together with [`get-node`](https://github.com/ehmicky/get-node) to run a specific Node.js version. - Requires the [`node`](#optionsnode) option to be `true`. +[More info.](docs/node.md#nodejs-version) + #### options.verbose Type: `'none' | 'short' | 'full'`\ Default: `'none'` -If `verbose` is `'short'` or `'full'`, [prints each command](#verbose-mode) on `stderr` before executing it. When the command completes, prints its duration and (if it failed) its error. +If `verbose` is `'short'`, prints the command on `stderr`: its file, arguments, duration and (if it failed) error message. -If `verbose` is `'full'`, the command's `stdout` and `stderr` are printed too, unless either: -- the [`stdout`](#optionsstdout)/[`stderr`](#optionsstderr) option is `ignore` or `inherit`. -- the `stdout`/`stderr` is redirected to [a stream](https://nodejs.org/api/stream.html#readablepipedestination-options), [a file](#optionsstdout), a file descriptor, or [another subprocess](#subprocesspipefile-arguments-options). -- the [`encoding`](#optionsencoding) option is binary. +If `verbose` is `'full'`, the command's `stdout` and `stderr` are also printed. -This can also be set to `'full'` by setting the `NODE_DEBUG=execa` environment variable in the current process. +By default, this applies to both `stdout` and `stderr`, but [different values can also be passed](docs/output.md#stdoutstderr-specific-options). -By default, this applies to both `stdout` and `stderr`, but [different values can also be passed](#options). +[More info.](docs/debugging.md#verbose-mode) #### options.buffer Type: `boolean`\ Default: `true` -Whether to return the subprocess' output using the [`result.stdout`](#resultstdout), [`result.stderr`](#resultstderr), [`result.all`](#resultall) and [`result.stdio`](#resultstdio) properties. +When `buffer` is `false`, the [`result.stdout`](#resultstdout), [`result.stderr`](#resultstderr), [`result.all`](#resultall) and [`result.stdio`](#resultstdio) properties are not set. -On failure, the [`error.stdout`](#resultstdout), [`error.stderr`](#resultstderr), [`error.all`](#resultall) and [`error.stdio`](#resultstdio) properties are used instead. +By default, this applies to both `stdout` and `stderr`, but [different values can also be passed](docs/output.md#stdoutstderr-specific-options). -When `buffer` is `false`, the output can still be read using the [`subprocess.stdout`](#subprocessstdout), [`subprocess.stderr`](#subprocessstderr), [`subprocess.stdio`](#subprocessstdio) and [`subprocess.all`](#subprocessall) streams. If the output is read, this should be done right away to avoid missing any data. - -By default, this applies to both `stdout` and `stderr`, but [different values can also be passed](#options). +[More info.](docs/output.md#low-memory) #### options.input Type: `string | Uint8Array | stream.Readable` -Write some input to the subprocess' `stdin`. +Write some input to the subprocess' [`stdin`](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). See also the [`inputFile`](#optionsinputfile) and [`stdin`](#optionsstdin) options. +[More info.](docs/input.md#input) + #### options.inputFile Type: `string | URL` -Use a file as input to the subprocess' `stdin`. +Use a file as input to the subprocess' [`stdin`](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). See also the [`input`](#optionsinput) and [`stdin`](#optionsstdin) options. +[More info.](docs/input.md#file-input) + #### options.stdin Type: `string | number | stream.Readable | ReadableStream | TransformStream | URL | {file: string} | Uint8Array | Iterable | AsyncIterable | GeneratorFunction | AsyncGeneratorFunction | {transform: GeneratorFunction | AsyncGeneratorFunction | Duplex | TransformStream}` (or a tuple of those types)\ Default: `inherit` with [`$`](#file-arguments-options), `pipe` otherwise -How to setup the subprocess' standard input. This can be: -- `'pipe'`: Sets [`subprocess.stdin`](#subprocessstdin) stream. -- `'overlapped'`: Like `'pipe'` but asynchronous on Windows. -- `'ignore'`: Do not use `stdin`. -- `'inherit'`: Re-use the current process' `stdin`. -- an integer: Re-use a specific file descriptor from the current process. -- a [Node.js `Readable` stream](#redirect-a-nodejs-stream-fromto-stdinstdoutstderr). -- `{ file: 'path' }` object. -- a file URL. -- a web [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream). -- an [`Iterable`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol) or an [`AsyncIterable`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols) -- an `Uint8Array`. - -This can be an [array of values](#redirect-stdinstdoutstderr-to-multiple-destinations) such as `['inherit', 'pipe']` or `[filePath, 'pipe']`. +How to setup the subprocess' [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). This can be [`'pipe'`](docs/streams.md#manual-streaming), [`'overlapped'`](docs/windows.md#asynchronous-io), [`'ignore`](docs/input.md#ignore-input), [`'inherit'`](docs/input.md#terminal-input), a [file descriptor integer](docs/output.md#redirect-to-parent-process), a [Node.js `Readable` stream](docs/streams.md#input), a web [`ReadableStream`](docs/streams.md#web-streams), a [`{ file: 'path' }` object](docs/input.md#file-input), a [file URL](docs/input.md#file-input), an [`Iterable`](docs/streams.md#iterables-as-input) (including an [array of strings](docs/input.md#input)), an [`AsyncIterable`](docs/streams.md#iterables-as-input), an [`Uint8Array`](docs/input.md#input), a [generator function](docs/transform.md), a [`Duplex`](docs/transform.md#duplextransform-streams) or a web [`TransformStream`](docs/transform.md#duplextransform-streams). -This can also be a generator function, a [`Duplex`](docs/transform.md#duplextransform-streams) or a web [`TransformStream`](docs/transform.md#duplextransform-streams) to transform the input. [Learn more.](docs/transform.md) +This can be an [array of values](docs/output.md#multiple-targets) such as `['inherit', 'pipe']` or `[fileUrl, 'pipe']`. -[More info.](https://nodejs.org/api/child_process.html#child_process_options_stdio) +More info on [available values](docs/input.md), [streaming](docs/streams.md) and [transforms](docs/transform.md). #### options.stdout Type: `string | number | stream.Writable | WritableStream | TransformStream | URL | {file: string} | GeneratorFunction | AsyncGeneratorFunction | {transform: GeneratorFunction | AsyncGeneratorFunction | Duplex | TransformStream}` (or a tuple of those types)\ Default: `pipe` -How to setup the subprocess' standard output. This can be: -- `'pipe'`: Sets [`result.stdout`](#resultstdout) (as a string or `Uint8Array`) and [`subprocess.stdout`](#subprocessstdout) (as a stream). -- `'overlapped'`: Like `'pipe'` but asynchronous on Windows. -- `'ignore'`: Do not use `stdout`. -- `'inherit'`: Re-use the current process' `stdout`. -- an integer: Re-use a specific file descriptor from the current process. -- a [Node.js `Writable` stream](#redirect-a-nodejs-stream-fromto-stdinstdoutstderr). -- `{ file: 'path' }` object. -- a file URL. -- a web [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream). +How to setup the subprocess' [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). This can be [`'pipe'`](docs/output.md#stdout-and-stderr), [`'overlapped'`](docs/windows.md#asynchronous-io), [`'ignore`](docs/output.md#ignore-output), [`'inherit'`](docs/output.md#redirect-to-parent-process), a [file descriptor integer](docs/output.md#redirect-to-parent-process), a [Node.js `Writable` stream](docs/streams.md#output), a web [`WritableStream`](docs/streams.md#web-streams), a [`{ file: 'path' }` object](docs/output.md#file-output), a [file URL](docs/output.md#file-output), a [generator function](docs/transform.md), a [`Duplex`](docs/transform.md#duplextransform-streams) or a web [`TransformStream`](docs/transform.md#duplextransform-streams). -This can be an [array of values](#redirect-stdinstdoutstderr-to-multiple-destinations) such as `['inherit', 'pipe']` or `[filePath, 'pipe']`. +This can be an [array of values](docs/output.md#multiple-targets) such as `['inherit', 'pipe']` or `[fileUrl, 'pipe']`. -This can also be a generator function, a [`Duplex`](docs/transform.md#duplextransform-streams) or a web [`TransformStream`](docs/transform.md#duplextransform-streams) to transform the output. [Learn more.](docs/transform.md) - -[More info.](https://nodejs.org/api/child_process.html#child_process_options_stdio) +More info on [available values](docs/output.md), [streaming](docs/streams.md) and [transforms](docs/transform.md). #### options.stderr Type: `string | number | stream.Writable | WritableStream | TransformStream | URL | {file: string} | GeneratorFunction | AsyncGeneratorFunction | {transform: GeneratorFunction | AsyncGeneratorFunction | Duplex | TransformStream}` (or a tuple of those types)\ Default: `pipe` -How to setup the subprocess' standard error. This can be: -- `'pipe'`: Sets [`result.stderr`](#resultstderr) (as a string or `Uint8Array`) and [`subprocess.stderr`](#subprocessstderr) (as a stream). -- `'overlapped'`: Like `'pipe'` but asynchronous on Windows. -- `'ignore'`: Do not use `stderr`. -- `'inherit'`: Re-use the current process' `stderr`. -- an integer: Re-use a specific file descriptor from the current process. -- a [Node.js `Writable` stream](#redirect-a-nodejs-stream-fromto-stdinstdoutstderr). -- `{ file: 'path' }` object. -- a file URL. -- a web [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream). - -This can be an [array of values](#redirect-stdinstdoutstderr-to-multiple-destinations) such as `['inherit', 'pipe']` or `[filePath, 'pipe']`. +How to setup the subprocess' [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). This can be [`'pipe'`](docs/output.md#stdout-and-stderr), [`'overlapped'`](docs/windows.md#asynchronous-io), [`'ignore`](docs/output.md#ignore-output), [`'inherit'`](docs/output.md#redirect-to-parent-process), a [file descriptor integer](docs/output.md#redirect-to-parent-process), a [Node.js `Writable` stream](docs/streams.md#output), a web [`WritableStream`](docs/streams.md#web-streams), a [`{ file: 'path' }` object](docs/output.md#file-output), a [file URL](docs/output.md#file-output), a [generator function](docs/transform.md), a [`Duplex`](docs/transform.md#duplextransform-streams) or a web [`TransformStream`](docs/transform.md#duplextransform-streams). -This can also be a generator function, a [`Duplex`](docs/transform.md#duplextransform-streams) or a web [`TransformStream`](docs/transform.md#duplextransform-streams) to transform the output. [Learn more.](docs/transform.md) +This can be an [array of values](docs/output.md#multiple-targets) such as `['inherit', 'pipe']` or `[fileUrl, 'pipe']`. -[More info.](https://nodejs.org/api/child_process.html#child_process_options_stdio) +More info on [available values](docs/output.md), [streaming](docs/streams.md) and [transforms](docs/transform.md). #### options.stdio Type: `string | Array | Iterable | Iterable | AsyncIterable | GeneratorFunction | AsyncGeneratorFunction | {transform: GeneratorFunction | AsyncGeneratorFunction | Duplex | TransformStream}>` (or a tuple of those types)\ Default: `pipe` -Like the [`stdin`](#optionsstdin), [`stdout`](#optionsstdout) and [`stderr`](#optionsstderr) options but for all file descriptors at once. For example, `{stdio: ['ignore', 'pipe', 'pipe']}` is the same as `{stdin: 'ignore', stdout: 'pipe', stderr: 'pipe'}`. +Like the [`stdin`](#optionsstdin), [`stdout`](#optionsstdout) and [`stderr`](#optionsstderr) options but for all [file descriptors](https://en.wikipedia.org/wiki/File_descriptor) at once. For example, `{stdio: ['ignore', 'pipe', 'pipe']}` is the same as `{stdin: 'ignore', stdout: 'pipe', stderr: 'pipe'}`. -A single string can be used as a shortcut. For example, `{stdio: 'pipe'}` is the same as `{stdin: 'pipe', stdout: 'pipe', stderr: 'pipe'}`. +A single string can be used [as a shortcut](docs/output.md#shortcut). -The array can have more than 3 items, to create additional file descriptors beyond `stdin`/`stdout`/`stderr`. For example, `{stdio: ['pipe', 'pipe', 'pipe', 'pipe']}` sets a fourth file descriptor. +The array can have more than 3 items, to create [additional file descriptors](docs/output.md#additional-file-descriptors) beyond [`stdin`](#optionsstdin)/[`stdout`](#optionsstdout)/[`stderr`](#optionsstderr). -[More info.](https://nodejs.org/api/child_process.html#child_process_options_stdio) +More info on [available values](docs/output.md), [streaming](docs/streams.md) and [transforms](docs/transform.md). #### options.all Type: `boolean`\ Default: `false` -Add a [`subprocess.all`](#subprocessall) stream and a [`result.all`](#resultall) property. They contain the combined/[interleaved](#ensuring-all-output-is-interleaved) output of the subprocess' `stdout` and `stderr`. +Add a [`subprocess.all`](#subprocessall) stream and a [`result.all`](#resultall) property. + +[More info.](docs/output.md#interleaved-output) #### options.lines @@ -1065,23 +1103,27 @@ Default: `false` Set [`result.stdout`](#resultstdout), [`result.stderr`](#resultstdout), [`result.all`](#resultall) and [`result.stdio`](#resultstdio) as arrays of strings, splitting the subprocess' output into lines. -This cannot be used if the [`encoding`](#optionsencoding) option is binary. +This cannot be used if the [`encoding`](#optionsencoding) option is [binary](docs/binary.md#binary-output). -By default, this applies to both `stdout` and `stderr`, but [different values can also be passed](#options). +By default, this applies to both `stdout` and `stderr`, but [different values can also be passed](docs/output.md#stdoutstderr-specific-options). + +[More info.](docs/lines.md#simple-splitting) #### options.encoding -Type: `string`\ +Type: `'utf8' | 'utf16le' | 'buffer' | 'hex' | 'base64' | 'base64url' | 'latin1' | 'ascii'`\ Default: `'utf8'` If the subprocess outputs text, specifies its character encoding, either `'utf8'` or `'utf16le'`. If it outputs binary data instead, this should be either: -- `'buffer'`: returns the binary output as an `Uint8Array`. +- `'buffer'`: returns the binary output as an [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array). - `'hex'`, `'base64'`, `'base64url'`, [`'latin1'`](https://nodejs.org/api/buffer.html#buffers-and-character-encodings) or [`'ascii'`](https://nodejs.org/api/buffer.html#buffers-and-character-encodings): encodes the binary output as a string. The output is available with [`result.stdout`](#resultstdout), [`result.stderr`](#resultstderr) and [`result.stdio`](#resultstdio). +[More info.](docs/binary.md) + #### options.stripFinalNewline Type: `boolean`\ @@ -1091,7 +1133,9 @@ Strip the final [newline character](https://en.wikipedia.org/wiki/Newline) from If the [`lines`](#optionslines) option is true, this applies to each output line instead. -By default, this applies to both `stdout` and `stderr`, but [different values can also be passed](#options). +By default, this applies to both `stdout` and `stderr`, but [different values can also be passed](docs/output.md#stdoutstderr-specific-options). + +[More info.](docs/lines.md#newlines) #### options.maxBuffer @@ -1100,15 +1144,9 @@ Default: `100_000_000` Largest amount of data allowed on [`stdout`](#resultstdout), [`stderr`](#resultstderr) and [`stdio`](#resultstdio). -When this threshold is hit, the subprocess fails and [`error.isMaxBuffer`](#resultismaxbuffer) becomes `true`. +By default, this applies to both `stdout` and `stderr`, but [different values can also be passed](docs/output.md#stdoutstderr-specific-options). -This is measured: -- By default: in [characters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length). -- If the [`encoding`](#optionsencoding) option is `'buffer'`: in bytes. -- If the [`lines`](#optionslines) option is `true`: in lines. -- If a [transform in object mode](docs/transform.md#object-mode) is used: in objects. - -By default, this applies to both `stdout` and `stderr`, but [different values can also be passed](#options). +[More info.](docs/output.md#big-output) #### options.ipc @@ -1117,34 +1155,34 @@ Default: `true` if the [`node`](#optionsnode) option is enabled, `false` otherwi Enables exchanging messages with the subprocess using [`subprocess.send(message)`](#subprocesssendmessage) and [`subprocess.on('message', (message) => {})`](#subprocessonmessage-message--void). +[More info.](docs/ipc.md) + #### options.serialization -Type: `string`\ +Type: `'json' | 'advanced'`\ Default: `'advanced'` -Specify the kind of serialization used for sending messages between subprocesses when using the [`ipc`](#optionsipc) option: -- `json`: Uses `JSON.stringify()` and `JSON.parse()`. -- `advanced`: Uses [`v8.serialize()`](https://nodejs.org/api/v8.html#v8_v8_serialize_value) +Specify the kind of serialization used for sending messages between subprocesses when using the [`ipc`](#optionsipc) option. -[More info.](https://nodejs.org/api/child_process.html#child_process_advanced_serialization) +[More info.](docs/ipc.md#message-type) #### options.detached Type: `boolean`\ Default: `false` -Prepare subprocess to run independently of the current process. Specific behavior depends on the platform. +Prepare subprocess to run independently of the current process. -[More info.](https://nodejs.org/api/child_process.html#child_process_options_detached). +[More info.](docs/environment.md#background-process) #### options.cleanup Type: `boolean`\ Default: `true` -Kill the subprocess when the current process exits unless either: -- the subprocess is [`detached`](#optionsdetached). -- the current process is terminated abruptly, for example, with `SIGKILL` as opposed to `SIGTERM` or a normal exit. +Kill the subprocess when the current process exits. + +[More info.](docs/termination.md#current-process-exit) #### options.timeout @@ -1153,6 +1191,10 @@ Default: `0` If `timeout` is greater than `0`, the subprocess will be [terminated](#optionskillsignal) if it runs for longer than that amount of milliseconds. +On timeout, [`result.timedOut`](#resulttimedout) becomes `true`. + +[More info.](docs/termination.md#timeout) + #### options.cancelSignal Type: [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) @@ -1161,6 +1203,8 @@ You can abort the subprocess using [`AbortController`](https://developer.mozilla When `AbortController.abort()` is called, [`result.isCanceled`](#resultiscanceled) becomes `true`. +[More info.](docs/termination.md#canceling) + #### options.forceKillAfterDelay Type: `number | false`\ @@ -1168,171 +1212,61 @@ Default: `5000` If the subprocess is terminated but does not exit, forcefully exit it by sending [`SIGKILL`](https://en.wikipedia.org/wiki/Signal_(IPC)#SIGKILL). -The grace period is 5 seconds by default. This feature can be disabled with `false`. - -This works when the subprocess is terminated by either: -- the [`cancelSignal`](#optionscancelsignal), [`timeout`](#optionstimeout), [`maxBuffer`](#optionsmaxbuffer) or [`cleanup`](#optionscleanup) option -- calling [`subprocess.kill()`](#subprocesskillsignal-error) with no arguments - -This does not work when the subprocess is terminated by either: -- calling [`subprocess.kill()`](#subprocesskillsignal-error) with an argument -- calling [`process.kill(subprocess.pid)`](https://nodejs.org/api/process.html#processkillpid-signal) -- sending a termination signal from another process - -Also, this does not work on Windows, because Windows [doesn't support signals](https://nodejs.org/api/process.html#process_signal_events): `SIGKILL` and `SIGTERM` both terminate the subprocess immediately. Other packages (such as [`taskkill`](https://github.com/sindresorhus/taskkill)) can be used to achieve fail-safe termination on Windows. +[More info.](docs/termination.md#forceful-termination) #### options.killSignal Type: `string | number`\ -Default: `SIGTERM` +Default: `'SIGTERM'` -Signal used to terminate the subprocess when: -- using the [`cancelSignal`](#optionscancelsignal), [`timeout`](#optionstimeout), [`maxBuffer`](#optionsmaxbuffer) or [`cleanup`](#optionscleanup) option -- calling [`subprocess.kill()`](#subprocesskillsignal-error) with no arguments +Default [signal](https://en.wikipedia.org/wiki/Signal_(IPC)) used to terminate the subprocess. -This can be either a name (like `"SIGTERM"`) or a number (like `9`). +This can be either a name (like `'SIGTERM'`) or a number (like `9`). + +[More info.](docs/termination.md#default-signal) #### options.argv0 -Type: `string` +Type: `string`\ +Default: file being executed -Explicitly set the value of `argv[0]` sent to the subprocess. This will be set to `file` if not specified. +Value of [`argv[0]`](https://nodejs.org/api/process.html#processargv0) sent to the subprocess. #### options.uid -Type: `number` +Type: `number`\ +Default: current user identifier -Sets the user identity of the subprocess. +Sets the [user identifier](https://en.wikipedia.org/wiki/User_identifier) of the subprocess. + +[More info.](docs/windows.md#uid-and-gid) #### options.gid -Type: `number` +Type: `number`\ +Default: current group identifier + +Sets the [group identifier](https://en.wikipedia.org/wiki/Group_identifier) of the subprocess. -Sets the group identity of the subprocess. +[More info.](docs/windows.md#uid-and-gid) #### options.windowsVerbatimArguments Type: `boolean`\ -Default: `false` +Default: `true` if the [`shell`](#optionsshell) option is `true`, `false` otherwise + +If `false`, escapes the command arguments on Windows. -If `true`, no quoting or escaping of arguments is done on Windows. Ignored on other platforms. This is set to `true` automatically when the `shell` option is `true`. +[More info.](docs/windows.md#cmdexe-escaping) #### options.windowsHide Type: `boolean`\ Default: `true` -On Windows, do not create a new console window. Please note this also prevents `CTRL-C` [from working](https://github.com/nodejs/node/issues/29837) on Windows. +On Windows, do not create a new console window. -## Tips - -### Redirect stdin/stdout/stderr to multiple destinations - -The [`stdin`](#optionsstdin), [`stdout`](#optionsstdout) and [`stderr`](#optionsstderr) options can be an array of values. -The following example redirects `stdout` to both the terminal and an `output.txt` file, while also retrieving its value programmatically. - -```js -const {stdout} = await execa('npm', ['install'], {stdout: ['inherit', './output.txt', 'pipe']}); -console.log(stdout); -``` - -When combining `inherit` with other values, please note that the subprocess will not be an interactive TTY, even if the current process is one. - -### Redirect a Node.js stream from/to stdin/stdout/stderr - -When passing a Node.js stream to the [`stdin`](#optionsstdin), [`stdout`](#optionsstdout) or [`stderr`](#optionsstderr) option, Node.js requires that stream to have an underlying file or socket, such as the streams created by the `fs`, `net` or `http` core modules. Otherwise the following error is thrown. - -``` -TypeError [ERR_INVALID_ARG_VALUE]: The argument 'stdio' is invalid. -``` - -This limitation can be worked around by passing either: -- a web stream ([`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) or [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream)) -- `[nodeStream, 'pipe']` instead of `nodeStream` - -```diff -- await execa(..., {stdout: nodeStream}); -+ await execa(..., {stdout: [nodeStream, 'pipe']}); -``` - -### Retry on error - -Safely handle failures by using automatic retries and exponential backoff with the [`p-retry`](https://github.com/sindresorhus/p-retry) package: - -```js -import pRetry from 'p-retry'; - -const run = async () => { - const results = await execa('curl', ['-sSL', 'https://sindresorhus.com/unicorn']); - return results; -}; - -console.log(await pRetry(run, {retries: 5})); -``` - -### Cancelling a subprocess - -```js -import {execa} from 'execa'; - -const abortController = new AbortController(); -const subprocess = execa('node', [], {cancelSignal: abortController.signal}); - -setTimeout(() => { - abortController.abort(); -}, 1000); - -try { - await subprocess; -} catch (error) { - console.log(error.isTerminated); // true - console.log(error.isCanceled); // true -} -``` - -### Execute the current package's binary - -Execa can be combined with [`get-bin-path`](https://github.com/ehmicky/get-bin-path) to test the current package's binary. As opposed to hard-coding the path to the binary, this validates that the `package.json` `bin` field is correctly set up. - -```js -import {getBinPath} from 'get-bin-path'; - -const binPath = await getBinPath(); -await execa(binPath); -``` - -### Ensuring `all` output is interleaved - -The `subprocess.all` [stream](#subprocessall) and `result.all` [string/`Uint8Array`](#resultall) property are guaranteed to interleave [`stdout`](#resultstdout) and [`stderr`](#resultstderr). - -However, for performance reasons, the subprocess might buffer and merge multiple simultaneous writes to `stdout` or `stderr`. This prevents proper interleaving. - -For example, this prints `1 3 2` instead of `1 2 3` because both `console.log()` are merged into a single write. - -```js -import {execa} from 'execa'; - -const {all} = await execa('node', ['example.js'], {all: true}); -console.log(all); -``` - -```js -// example.js -console.log('1'); // writes to stdout -console.error('2'); // writes to stderr -console.log('3'); // writes to stdout -``` - -This can be worked around by using `setTimeout()`. - -```js -import {setTimeout} from 'timers/promises'; - -console.log('1'); -console.error('2'); -await setTimeout(0); -console.log('3'); -``` +[More info.](docs/windows.md#console-window) ## Related diff --git a/test/pipe/abort.js b/test/pipe/abort.js index b4cff9fc78..5be595c056 100644 --- a/test/pipe/abort.js +++ b/test/pipe/abort.js @@ -26,7 +26,7 @@ const assertUnPipeError = async (t, pipePromise) => { t.deepEqual(error.stdio, Array.from({length: error.stdio.length})); t.deepEqual(error.pipedFrom, []); - t.true(error.originalMessage.includes('Pipe cancelled')); + t.true(error.originalMessage.includes('Pipe canceled')); t.true(error.shortMessage.includes(`Command failed: ${error.command}`)); t.true(error.shortMessage.includes(error.originalMessage)); t.true(error.message.includes(error.shortMessage)); diff --git a/types/arguments/options.d.ts b/types/arguments/options.d.ts index 7cdfcc6f9d..8f10fc1093 100644 --- a/types/arguments/options.d.ts +++ b/types/arguments/options.d.ts @@ -8,16 +8,14 @@ export type CommonOptions = { /** Prefer locally installed binaries when looking for a binary to execute. - If you `$ npm install foo`, you can then `execa('foo')`. - @default `true` with `$`, `false` otherwise */ readonly preferLocal?: boolean; /** - Preferred path to find locally installed binaries in (use with `preferLocal`). + Preferred path to find locally installed binaries, when using the `preferLocal` option. - @default process.cwd() + @default `cwd` option */ readonly localDir?: string | URL; @@ -31,8 +29,6 @@ export type CommonOptions = { /** Path to the Node.js executable. - For example, this can be used together with [`get-node`](https://github.com/ehmicky/get-node) to run a specific Node.js version. - Requires the `node` option to be `true`. @default [`process.execPath`](https://nodejs.org/api/process.html#process_process_execpath) (current Node.js executable) @@ -40,7 +36,7 @@ export type CommonOptions = { readonly nodePath?: string | URL; /** - List of [CLI options](https://nodejs.org/api/cli.html#cli_options) passed to the Node.js executable. + List of [CLI flags](https://nodejs.org/api/cli.html#cli_options) passed to the Node.js executable. Requires the `node` option to be `true`. @@ -49,87 +45,52 @@ export type CommonOptions = { readonly nodeOptions?: readonly string[]; /** - Write some input to the subprocess' `stdin`. + Write some input to the subprocess' [`stdin`](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). See also the `inputFile` and `stdin` options. */ readonly input?: string | Uint8Array | Readable; /** - Use a file as input to the subprocess' `stdin`. + Use a file as input to the subprocess' [`stdin`](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). See also the `input` and `stdin` options. */ readonly inputFile?: string | URL; /** - How to setup the subprocess' standard input. This can be: - - `'pipe'`: Sets `subprocess.stdin` stream. - - `'overlapped'`: Like `'pipe'` but asynchronous on Windows. - - `'ignore'`: Do not use `stdin`. - - `'inherit'`: Re-use the current process' `stdin`. - - an integer: Re-use a specific file descriptor from the current process. - - a Node.js `Readable` stream. - - `{ file: 'path' }` object. - - a file URL. - - a web [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream). - - an [`Iterable`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol) or an [`AsyncIterable`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols) - - an `Uint8Array`. + How to setup the subprocess' [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). This can be `'pipe'`, `'overlapped'`, `'ignore`, `'inherit'`, a file descriptor integer, a Node.js `Readable` stream, a web `ReadableStream`, a `{ file: 'path' }` object, a file URL, an `Iterable`, an `AsyncIterable`, an `Uint8Array`, a generator function, a `Duplex` or a web `TransformStream`. - This can be an [array of values](https://github.com/sindresorhus/execa#redirect-stdinstdoutstderr-to-multiple-destinations) such as `['inherit', 'pipe']` or `[filePath, 'pipe']`. - - This can also be a generator function or a [`Duplex`](https://nodejs.org/api/stream.html#class-streamduplex) or a [web `TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) to transform the input. [Learn more.](https://github.com/sindresorhus/execa/tree/main/docs/transform.md) + This can be an array of values such as `['inherit', 'pipe']` or `[fileUrl, 'pipe']`. @default `inherit` with `$`, `pipe` otherwise */ readonly stdin?: StdinOptionCommon; /** - How to setup the subprocess' standard output. This can be: - - `'pipe'`: Sets `result.stdout` (as a string or `Uint8Array`) and `subprocess.stdout` (as a stream). - - `'overlapped'`: Like `'pipe'` but asynchronous on Windows. - - `'ignore'`: Do not use `stdout`. - - `'inherit'`: Re-use the current process' `stdout`. - - an integer: Re-use a specific file descriptor from the current process. - - a Node.js `Writable` stream. - - `{ file: 'path' }` object. - - a file URL. - - a web [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream). - - This can be an [array of values](https://github.com/sindresorhus/execa#redirect-stdinstdoutstderr-to-multiple-destinations) such as `['inherit', 'pipe']` or `[filePath, 'pipe']`. + How to setup the subprocess' [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). This can be `'pipe'`, `'overlapped'`, `'ignore`, `'inherit'`, a file descriptor integer, a Node.js `Writable` stream, a web `WritableStream`, a `{ file: 'path' }` object, a file URL, a generator function, a `Duplex` or a web `TransformStream`. - This can also be a generator function or a [`Duplex`](https://nodejs.org/api/stream.html#class-streamduplex) or a [web `TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) to transform the output. [Learn more.](https://github.com/sindresorhus/execa/tree/main/docs/transform.md) + This can be an array of values such as `['inherit', 'pipe']` or `[fileUrl, 'pipe']`. @default 'pipe' */ readonly stdout?: StdoutStderrOptionCommon; /** - How to setup the subprocess' standard error. This can be: - - `'pipe'`: Sets `result.stderr` (as a string or `Uint8Array`) and `subprocess.stderr` (as a stream). - - `'overlapped'`: Like `'pipe'` but asynchronous on Windows. - - `'ignore'`: Do not use `stderr`. - - `'inherit'`: Re-use the current process' `stderr`. - - an integer: Re-use a specific file descriptor from the current process. - - a Node.js `Writable` stream. - - `{ file: 'path' }` object. - - a file URL. - - a web [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream). - - This can be an [array of values](https://github.com/sindresorhus/execa#redirect-stdinstdoutstderr-to-multiple-destinations) such as `['inherit', 'pipe']` or `[filePath, 'pipe']`. + How to setup the subprocess' [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). This can be `'pipe'`, `'overlapped'`, `'ignore`, `'inherit'`, a file descriptor integer, a Node.js `Writable` stream, a web `WritableStream`, a `{ file: 'path' }` object, a file URL, a generator function, a `Duplex` or a web `TransformStream`. - This can also be a generator function or a [`Duplex`](https://nodejs.org/api/stream.html#class-streamduplex) or a [web `TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) to transform the output. [Learn more.](https://github.com/sindresorhus/execa/tree/main/docs/transform.md) + This can be an array of values such as `['inherit', 'pipe']` or `[fileUrl, 'pipe']`. @default 'pipe' */ readonly stderr?: StdoutStderrOptionCommon; /** - Like the `stdin`, `stdout` and `stderr` options but for all file descriptors at once. For example, `{stdio: ['ignore', 'pipe', 'pipe']}` is the same as `{stdin: 'ignore', stdout: 'pipe', stderr: 'pipe'}`. + Like the `stdin`, `stdout` and `stderr` options but for all [file descriptors](https://en.wikipedia.org/wiki/File_descriptor) at once. For example, `{stdio: ['ignore', 'pipe', 'pipe']}` is the same as `{stdin: 'ignore', stdout: 'pipe', stderr: 'pipe'}`. - A single string can be used as a shortcut. For example, `{stdio: 'pipe'}` is the same as `{stdin: 'pipe', stdout: 'pipe', stderr: 'pipe'}`. + A single string can be used as a shortcut. - The array can have more than 3 items, to create additional file descriptors beyond `stdin`/`stdout`/`stderr`. For example, `{stdio: ['pipe', 'pipe', 'pipe', 'pipe']}` sets a fourth file descriptor. + The array can have more than 3 items, to create additional file descriptors beyond `stdin`/`stdout`/`stderr`. @default 'pipe' */ @@ -147,7 +108,7 @@ export type CommonOptions = { readonly lines?: FdGenericOption; /** - Setting this to `false` resolves the promise with the error instead of rejecting it. + Setting this to `false` resolves the result's promise with the error instead of rejecting it. @default true */ @@ -173,7 +134,7 @@ export type CommonOptions = { readonly extendEnv?: boolean; /** - Current working directory of the subprocess. + Current [working directory](https://en.wikipedia.org/wiki/Working_directory) of the subprocess. This is also used to resolve the `nodePath` option when it is a relative path. @@ -182,36 +143,41 @@ export type CommonOptions = { readonly cwd?: string | URL; /** - Environment key-value pairs. + [Environment variables](https://en.wikipedia.org/wiki/Environment_variable). Unless the `extendEnv` option is `false`, the subprocess also uses the current process' environment variables ([`process.env`](https://nodejs.org/api/process.html#processenv)). - @default process.env + @default [process.env](https://nodejs.org/api/process.html#processenv) */ readonly env?: NodeJS.ProcessEnv; /** - Explicitly set the value of `argv[0]` sent to the subprocess. This will be set to `command` or `file` if not specified. + Value of [`argv[0]`](https://nodejs.org/api/process.html#processargv0) sent to the subprocess. + + @default file being executed */ readonly argv0?: string; /** - Sets the user identity of the subprocess. + Sets the [user identifier](https://en.wikipedia.org/wiki/User_identifier) of the subprocess. + + @default current user identifier */ readonly uid?: number; /** - Sets the group identity of the subprocess. + Sets the [group identifier](https://en.wikipedia.org/wiki/Group_identifier) of the subprocess. + + @default current group identifier */ readonly gid?: number; /** - If `true`, runs `command` inside of a shell. Uses `/bin/sh` on UNIX and `cmd.exe` on Windows. A different shell can be specified as a string. The shell should understand the `-c` switch on UNIX or `/d /s /c` on Windows. + If `true`, runs the command inside of a shell. - We recommend against using this option since it is: - - not cross-platform, encouraging shell-specific syntax. - - slower, because of the additional shell interpretation. - - unsafe, potentially allowing command injection. + Uses `/bin/sh` on UNIX and `cmd.exe` on Windows. A different shell can be specified as a string. The shell should understand the `-c` switch on UNIX or `/d /s /c` on Windows. + + We recommend against using this option. @default false */ @@ -233,6 +199,8 @@ export type CommonOptions = { /** If `timeout` is greater than `0`, the subprocess will be terminated if it runs for longer than that amount of milliseconds. + On timeout, `result.timedOut` becomes `true`. + @default 0 */ readonly timeout?: number; @@ -240,14 +208,6 @@ export type CommonOptions = { /** Largest amount of data allowed on `stdout`, `stderr` and `stdio`. - When this threshold is hit, the subprocess fails and `error.isMaxBuffer` becomes `true`. - - This is measured: - - By default: in [characters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length). - - If the `encoding` option is `'buffer'`: in bytes. - - If the `lines` option is `true`: in lines. - - If a transform in object mode is used: in objects. - By default, this applies to both `stdout` and `stderr`, but different values can also be passed. @default 100_000_000 @@ -255,11 +215,9 @@ export type CommonOptions = { readonly maxBuffer?: FdGenericOption; /** - Signal used to terminate the subprocess when: - - using the `cancelSignal`, `timeout`, `maxBuffer` or `cleanup` option - - calling `subprocess.kill()` with no arguments + Default [signal](https://en.wikipedia.org/wiki/Signal_(IPC)) used to terminate the subprocess. - This can be either a name (like `"SIGTERM"`) or a number (like `9`). + This can be either a name (like `'SIGTERM'`) or a number (like `9`). @default 'SIGTERM' */ @@ -268,46 +226,28 @@ export type CommonOptions = { /** If the subprocess is terminated but does not exit, forcefully exit it by sending [`SIGKILL`](https://en.wikipedia.org/wiki/Signal_(IPC)#SIGKILL). - The grace period is 5 seconds by default. This feature can be disabled with `false`. - - This works when the subprocess is terminated by either: - - the `cancelSignal`, `timeout`, `maxBuffer` or `cleanup` option - - calling `subprocess.kill()` with no arguments - - This does not work when the subprocess is terminated by either: - - calling `subprocess.kill()` with an argument - - calling [`process.kill(subprocess.pid)`](https://nodejs.org/api/process.html#processkillpid-signal) - - sending a termination signal from another process - - Also, this does not work on Windows, because Windows [doesn't support signals](https://nodejs.org/api/process.html#process_signal_events): `SIGKILL` and `SIGTERM` both terminate the subprocess immediately. Other packages (such as [`taskkill`](https://github.com/sindresorhus/taskkill)) can be used to achieve fail-safe termination on Windows. - @default 5000 */ readonly forceKillAfterDelay?: Unless; /** - If `true`, no quoting or escaping of arguments is done on Windows. Ignored on other platforms. This is set to `true` automatically when the `shell` option is `true`. + If `false`, escapes the command arguments on Windows. - @default false + @default `true` if the `shell` option is `true`, `false` otherwise */ readonly windowsVerbatimArguments?: boolean; /** - On Windows, do not create a new console window. Please note this also prevents `CTRL-C` [from working](https://github.com/nodejs/node/issues/29837) on Windows. + On Windows, do not create a new console window. @default true */ readonly windowsHide?: boolean; /** - If `verbose` is `'short'` or `'full'`, prints each command on `stderr` before executing it. When the command completes, prints its duration and (if it failed) its error. - - If `verbose` is `'full'`, the command's `stdout` and `stderr` are printed too, unless either: - - the `stdout`/`stderr` option is `ignore` or `inherit`. - - the `stdout`/`stderr` is redirected to [a stream](https://nodejs.org/api/stream.html#readablepipedestination-options), a file, a file descriptor, or another subprocess. - - the `encoding` option is binary. + If `verbose` is `'short'`, prints the command on `stderr`: its file, arguments, duration and (if it failed) error message. - This can also be set to `'full'` by setting the `NODE_DEBUG=execa` environment variable in the current process. + If `verbose` is `'full'`, the command's `stdout` and `stderr` are also printed. By default, this applies to both `stdout` and `stderr`, but different values can also be passed. @@ -316,20 +256,14 @@ export type CommonOptions = { readonly verbose?: FdGenericOption<'none' | 'short' | 'full'>; /** - Kill the subprocess when the current process exits unless either: - - the subprocess is `detached`. - - the current process is terminated abruptly, for example, with `SIGKILL` as opposed to `SIGTERM` or a normal exit. + Kill the subprocess when the current process exits. @default true */ readonly cleanup?: Unless; /** - Whether to return the subprocess' output using the `result.stdout`, `result.stderr`, `result.all` and `result.stdio` properties. - - On failure, the `error.stdout`, `error.stderr`, `error.all` and `error.stdio` properties are used instead. - - When `buffer` is `false`, the output can still be read using the `subprocess.stdout`, `subprocess.stderr`, `subprocess.stdio` and `subprocess.all` streams. If the output is read, this should be done right away to avoid missing any data. + When `buffer` is `false`, the `result.stdout`, `result.stderr`, `result.all` and `result.stdio` properties are not set. By default, this applies to both `stdout` and `stderr`, but different values can also be passed. @@ -352,18 +286,14 @@ export type CommonOptions = { readonly ipc?: Unless; /** - Specify the kind of serialization used for sending messages between subprocesses when using the `ipc` option: - - `json`: Uses `JSON.stringify()` and `JSON.parse()`. - - `advanced`: Uses [`v8.serialize()`](https://nodejs.org/api/v8.html#v8_v8_serialize_value) - - [More info.](https://nodejs.org/api/child_process.html#child_process_advanced_serialization) + Specify the kind of serialization used for sending messages between subprocesses when using the `ipc` option. @default 'advanced' */ readonly serialization?: Unless; /** - Prepare subprocess to run independently of the current process. Specific behavior depends on the platform. + Prepare subprocess to run independently of the current process. @default false */ diff --git a/types/convert.d.ts b/types/convert.d.ts index 93df487539..52092a54f7 100644 --- a/types/convert.d.ts +++ b/types/convert.d.ts @@ -14,9 +14,9 @@ export type ReadableOptions = { readonly from?: FromOption; /** - If `false`, the stream iterates over lines. Each line is a string. Also, the stream is in [object mode](https://nodejs.org/api/stream.html#object-mode). + If `false`, iterates over lines. Each line is a string. - If `true`, the stream iterates over arbitrary chunks of data. Each line is an `Uint8Array` (with `subprocess.iterable()`) or a [`Buffer`](https://nodejs.org/api/buffer.html#class-buffer) (otherwise). + If `true`, iterates over arbitrary chunks of data. Each line is an `Uint8Array` (with `subprocess.iterable()`) or a [`Buffer`](https://nodejs.org/api/buffer.html#class-buffer) (with `subprocess.readable()`/`subprocess.duplex()`). This is always `true` when the `encoding` option is binary. diff --git a/types/methods/command.d.ts b/types/methods/command.d.ts index 9389d5ce00..df925b9277 100644 --- a/types/methods/command.d.ts +++ b/types/methods/command.d.ts @@ -15,14 +15,12 @@ type ExecaCommand = { }; /** -`execa` with the template string syntax allows the `file` or the `arguments` to be user-defined (by injecting them with `${}`). However, if _both_ the `file` and the `arguments` are user-defined, _and_ those are supplied as a single string, then `execaCommand(command)` must be used instead. +Executes a command. `command` is a string that includes both the `file` and its `arguments`. This is only intended for very specific cases, such as a REPL. This should be avoided otherwise. Just like `execa()`, this can bind options. It can also be run synchronously using `execaCommandSync()`. -Arguments are automatically escaped. They can contain any character, but spaces must be escaped with a backslash like `execaCommand('echo has\\ space')`. - @param command - The program/script to execute and its arguments. @returns An `ExecaSubprocess` that is both: - a `Promise` resolving or rejecting with a subprocess `result`. @@ -56,17 +54,6 @@ Same as `execaCommand()` but synchronous. Returns or throws a subprocess `result`. The `subprocess` is not returned: its methods and properties are not available. -The following features cannot be used: -- Streams: `subprocess.stdin`, `subprocess.stdout`, `subprocess.stderr`, `subprocess.readable()`, `subprocess.writable()`, `subprocess.duplex()`. -- The `stdin`, `stdout`, `stderr` and `stdio` options cannot be `'overlapped'`, an async iterable, an async transform, a `Duplex`, nor a web stream. Node.js streams can be passed but only if either they [have a file descriptor](#redirect-a-nodejs-stream-fromto-stdinstdoutstderr), or the `input` option is used. -- Signal termination: `subprocess.kill()`, `subprocess.pid`, `cleanup` option, `cancelSignal` option, `forceKillAfterDelay` option. -- Piping multiple processes: `subprocess.pipe()`. -- `subprocess.iterable()`. -- `ipc` and `serialization` options. -- `result.all` is not interleaved. -- `detached` option. -- The `maxBuffer` option is always measured in bytes, not in characters, lines nor objects. Also, it ignores transforms and the `encoding` option. - @param command - The program/script to execute and its arguments. @returns A subprocess `result` object @throws A subprocess `result` error diff --git a/types/methods/main-async.d.ts b/types/methods/main-async.d.ts index b6a60a8ab5..2c8ea0bb5b 100644 --- a/types/methods/main-async.d.ts +++ b/types/methods/main-async.d.ts @@ -22,17 +22,9 @@ type Execa = { /** Executes a command using `file ...arguments`. -Arguments are automatically escaped. They can contain any character, including spaces, tabs and newlines. - When `command` is a template string, it includes both the `file` and its `arguments`. -The `command` template string can inject any `${value}` with the following types: string, number, `subprocess` or an array of those types. For example: `` execa`echo one ${'two'} ${3} ${['four', 'five']}` ``. For `${subprocess}`, the subprocess's `stdout` is used. - -When `command` is a template string, arguments can contain any character, but spaces, tabs and newlines must use `${}` like `` execa`echo ${'has space'}` ``. - -The `command` template string can use multiple lines and indentation. - -`execa(options)` can be used to return a new instance of Execa but with different default `options`. Consecutive calls are merged to previous ones. This allows setting global options or sharing options between multiple commands. +`execa(options)` can be used to return a new instance of Execa but with different default `options`. Consecutive calls are merged to previous ones. @param file - The program/script to execute, as a string or file URL @param arguments - Arguments to pass to `file` on execution. diff --git a/types/methods/main-sync.d.ts b/types/methods/main-sync.d.ts index cd3a4c0d2e..f1785f046e 100644 --- a/types/methods/main-sync.d.ts +++ b/types/methods/main-sync.d.ts @@ -24,17 +24,6 @@ Same as `execa()` but synchronous. Returns or throws a subprocess `result`. The `subprocess` is not returned: its methods and properties are not available. -The following features cannot be used: -- Streams: `subprocess.stdin`, `subprocess.stdout`, `subprocess.stderr`, `subprocess.readable()`, `subprocess.writable()`, `subprocess.duplex()`. -- The `stdin`, `stdout`, `stderr` and `stdio` options cannot be `'overlapped'`, an async iterable, an async transform, a `Duplex`, nor a web stream. Node.js streams can be passed but only if either they [have a file descriptor](#redirect-a-nodejs-stream-fromto-stdinstdoutstderr), or the `input` option is used. -- Signal termination: `subprocess.kill()`, `subprocess.pid`, `cleanup` option, `cancelSignal` option, `forceKillAfterDelay` option. -- Piping multiple processes: `subprocess.pipe()`. -- `subprocess.iterable()`. -- `ipc` and `serialization` options. -- `result.all` is not interleaved. -- `detached` option. -- The `maxBuffer` option is always measured in bytes, not in characters, lines nor objects. Also, it ignores transforms and the `encoding` option. - @param file - The program/script to execute, as a string or file URL @param arguments - Arguments to pass to `file` on execution. @returns A subprocess `result` object diff --git a/types/methods/script.d.ts b/types/methods/script.d.ts index e739f61c42..8c28c65d57 100644 --- a/types/methods/script.d.ts +++ b/types/methods/script.d.ts @@ -48,7 +48,7 @@ type ExecaScript = { } & ExecaScriptCommon; /** -Same as `execa()` but using the `stdin: 'inherit'` and `preferLocal: true` options. +Same as `execa()` but using script-friendly default options. Just like `execa()`, this can use the template string syntax or bind options. It can also be run synchronously using `$.sync()` or `$.s()`. diff --git a/types/pipe.d.ts b/types/pipe.d.ts index 93882f90e2..803d91fa61 100644 --- a/types/pipe.d.ts +++ b/types/pipe.d.ts @@ -20,8 +20,6 @@ type PipeOptions = { /** Unpipe the subprocess when the signal aborts. - - The `subprocess.pipe()` method will be rejected with a cancellation error. */ readonly unpipeSignal?: AbortSignal; }; @@ -32,10 +30,6 @@ export type PipableSubprocess = { [Pipe](https://nodejs.org/api/stream.html#readablepipedestination-options) the subprocess' `stdout` to a second Execa subprocess' `stdin`. This resolves with that second subprocess' result. If either subprocess is rejected, this is rejected with that subprocess' error instead. This follows the same syntax as `execa(file, arguments?, options?)` except both regular options and pipe-specific options can be specified. - - This can be called multiple times to chain a series of subprocesses. - - Multiple subprocesses can be piped to the same subprocess. Conversely, the same subprocess can be piped to multiple other subprocesses. */ pipe( file: string | URL, @@ -58,8 +52,6 @@ export type PipableSubprocess = { /** Like `subprocess.pipe(file, arguments?, options?)` but using the return value of another `execa()` call instead. - - This is the most advanced method to pipe subprocesses. It is useful in specific cases, such as piping multiple subprocesses to the same subprocess. */ pipe(destination: Destination, options?: PipeOptions): Promise> & PipableSubprocess; diff --git a/types/return/final-error.d.ts b/types/return/final-error.d.ts index 0fb1c3bcf5..3b25079fff 100644 --- a/types/return/final-error.d.ts +++ b/types/return/final-error.d.ts @@ -24,12 +24,7 @@ export type ErrorProperties = | 'code'; /** -Exception thrown when the subprocess fails, either: -- its exit code is not `0` -- it was terminated with a signal, including `subprocess.kill()` -- timing out -- being canceled -- there's not enough memory or there are already too many subprocesses +Exception thrown when the subprocess fails. This has the same shape as successful results, with a few additional properties. */ @@ -38,12 +33,7 @@ export class ExecaError extends CommonErr } /** -Exception thrown when the subprocess fails, either: -- its exit code is not `0` -- it was terminated with a signal, including `.kill()` -- timing out -- being canceled -- there's not enough memory or there are already too many subprocesses +Exception thrown when the subprocess fails. This has the same shape as successful results, with a few additional properties. */ diff --git a/types/return/result.d.ts b/types/return/result.d.ts index 8b36e65a54..dc842b4962 100644 --- a/types/return/result.d.ts +++ b/types/return/result.d.ts @@ -10,24 +10,17 @@ export declare abstract class CommonResult< OptionsType extends CommonOptions = CommonOptions, > { /** - The file and arguments that were run, for logging purposes. - - This is not escaped and should not be executed directly as a subprocess, including using `execa()` or `execaCommand()`. + The file and arguments that were run. */ command: string; /** Same as `command` but escaped. - - Unlike `command`, control characters are escaped, which makes it safe to print in a terminal. - - This can also be copied and pasted into a shell, for debugging purposes. - Since the escaping is fairly basic, this should not be executed directly as a subprocess, including using `execa()` or `execaCommand()`. */ escapedCommand: string; /** - The numeric exit code of the subprocess that was run. + The numeric [exit code](https://en.wikipedia.org/wiki/Exit_status) of the subprocess that was run. This is `undefined` when the subprocess could not be spawned or was terminated by a signal. */ @@ -36,21 +29,38 @@ export declare abstract class CommonResult< /** The output of the subprocess on `stdout`. - This is `undefined` if the `stdout` option is set to only `'inherit'`, `'ignore'`, `Writable` or `integer`. This is an array if the `lines` option is `true`, or if the `stdout` option is a transform in object mode. + This is `undefined` if the `stdout` option is set to only `'inherit'`, `'ignore'`, `Writable` or `integer`, or if the `buffer` option is `false`. + + This is an array if the `lines` option is `true`, or if the `stdout` option is a transform in object mode. */ stdout: ResultStdioNotAll<'1', OptionsType>; /** The output of the subprocess on `stderr`. - This is `undefined` if the `stderr` option is set to only `'inherit'`, `'ignore'`, `Writable` or `integer`. This is an array if the `lines` option is `true`, or if the `stderr` option is a transform in object mode. + This is `undefined` if the `stderr` option is set to only `'inherit'`, `'ignore'`, `Writable` or `integer`, or if the `buffer` option is `false`. + + This is an array if the `lines` option is `true`, or if the `stderr` option is a transform in object mode. */ stderr: ResultStdioNotAll<'2', OptionsType>; + /** + The output of the subprocess with `result.stdout` and `result.stderr` interleaved. + + This requires the `all` option to be `true`. + + This is `undefined` if both `stdout` and `stderr` options are set to only `'inherit'`, `'ignore'`, `Writable` or `integer`, or if the `buffer` option is `false`. + + This is an array if the `lines` option is `true`, or if either the `stdout` or `stderr` option is a transform in object mode. + */ + all: ResultAll; + /** The output of the subprocess on `stdin`, `stdout`, `stderr` and other file descriptors. - Items are `undefined` when their corresponding `stdio` option is set to only `'inherit'`, `'ignore'`, `Writable` or `integer`. Items are arrays when their corresponding `stdio` option is a transform in object mode. + Items are `undefined` when their corresponding `stdio` option is set to only `'inherit'`, `'ignore'`, `Writable` or `integer`, or if the `buffer` option is `false`. + + Items are arrays when their corresponding `stdio` option is a transform in object mode. */ stdio: ResultStdioArray; @@ -60,7 +70,7 @@ export declare abstract class CommonResult< failed: boolean; /** - Whether the subprocess timed out. + Whether the subprocess timed out due to the `timeout` option. */ timedOut: boolean; @@ -81,7 +91,7 @@ export declare abstract class CommonResult< signal?: string; /** - A human-friendly description of the signal that was used to terminate the subprocess. For example, `Floating point arithmetic error`. + A human-friendly description of the signal that was used to terminate the subprocess. If a signal terminated the subprocess, this property is defined and included in the error message. Otherwise it is `undefined`. It is also `undefined` when the signal is very uncommon which should seldomly happen. */ @@ -108,39 +118,26 @@ export declare abstract class CommonResult< isMaxBuffer: boolean; /** - The output of the subprocess with `result.stdout` and `result.stderr` interleaved. - - This is `undefined` if either: - - the `all` option is `false` (default value). - - both `stdout` and `stderr` options are set to `'inherit'`, `'ignore'`, `Writable` or `integer`. - - This is an array if the `lines` option is `true`, or if either the `stdout` or `stderr` option is a transform in object mode. - */ - all: ResultAll; - - /** - Results of the other subprocesses that were piped into this subprocess. This is useful to inspect a series of subprocesses piped with each other. + Results of the other subprocesses that were piped into this subprocess. This array is initially empty and is populated each time the `subprocess.pipe()` method resolves. */ pipedFrom: Unless; /** - Error message when the subprocess failed to run. In addition to the underlying error message, it also contains some information related to why the subprocess errored. - - The subprocess `stderr`, `stdout` and other file descriptors' output are appended to the end, separated with newlines and not interleaved. + Error message when the subprocess failed to run. */ message?: string; /** - This is the same as the `message` property except it does not include the subprocess `stdout`/`stderr`/`stdio`. + This is the same as `error.message` except it does not include the subprocess output. */ shortMessage?: string; /** - Original error message. This is the same as the `message` property excluding the subprocess `stdout`/`stderr`/`stdio` and some additional information added by Execa. + Original error message. This is the same as `error.message` excluding the subprocess output and some additional information added by Execa. - This exists only if the subprocess exited due to an `error` event or a timeout. + This exists only in specific instances, such as during a timeout. */ originalMessage?: string; diff --git a/types/subprocess/subprocess.d.ts b/types/subprocess/subprocess.d.ts index a368d1781c..bd997b86f4 100644 --- a/types/subprocess/subprocess.d.ts +++ b/types/subprocess/subprocess.d.ts @@ -44,46 +44,36 @@ export type ExecaResultPromise = { The subprocess `stdin` as a stream. This is `null` if the `stdin` option is set to `'inherit'`, `'ignore'`, `Readable` or `integer`. - - This is intended for advanced cases. Please consider using the `stdin` option, `input` option, `inputFile` option, or `subprocess.pipe()` instead. */ stdin: SubprocessStdioStream<'0', OptionsType>; /** The subprocess `stdout` as a stream. - This is `null` if the `stdout` option is set to `'inherit'`, `'ignore'`, `Writable` or `integer`. - - This is intended for advanced cases. Please consider using `result.stdout`, the `stdout` option, `subprocess.iterable()`, or `subprocess.pipe()` instead. + This is `null` if the `stdout` option is set to `'inherit'`, `'ignore'`, `Writable` or `integer`, or if the `buffer` option is `false`. */ stdout: SubprocessStdioStream<'1', OptionsType>; /** The subprocess `stderr` as a stream. - This is `null` if the `stderr` option is set to `'inherit'`, `'ignore'`, `Writable` or `integer`. - - This is intended for advanced cases. Please consider using `result.stderr`, the `stderr` option, `subprocess.iterable()`, or `subprocess.pipe()` instead. + This is `null` if the `stderr` option is set to `'inherit'`, `'ignore'`, `Writable` or `integer`, or if the `buffer` option is `false`. */ stderr: SubprocessStdioStream<'2', OptionsType>; /** Stream combining/interleaving `subprocess.stdout` and `subprocess.stderr`. - This is `undefined` if either: - - the `all` option is `false` (the default value). - - both `stdout` and `stderr` options are set to `'inherit'`, `'ignore'`, `Writable` or `integer`. + This requires the `all` option to be `true`. - This is intended for advanced cases. Please consider using `result.all`, the `stdout`/`stderr` option, `subprocess.iterable()`, or `subprocess.pipe()` instead. + This is `undefined` if `stdout` and `stderr` options are set to `'inherit'`, `'ignore'`, `Writable` or `integer`, or if the `buffer` option is `false`. */ all: SubprocessAll; /** The subprocess `stdin`, `stdout`, `stderr` and other files descriptors as an array of streams. - Each array item is `null` if the corresponding `stdin`, `stdout`, `stderr` or `stdio` option is set to `'inherit'`, `'ignore'`, `Stream` or `integer`. - - This is intended for advanced cases. Please consider using `result.stdio`, the `stdio` option, `subprocess.iterable()` or `subprocess.pipe()` instead. + Each array item is `null` if the corresponding `stdin`, `stdout`, `stderr` or `stdio` option is set to `'inherit'`, `'ignore'`, `Stream` or `integer`, or if the `buffer` option is `false`. */ stdio: SubprocessStdioArray; @@ -101,40 +91,26 @@ export type ExecaResultPromise = { /** Subprocesses are [async iterables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator). They iterate over each output line. - - The iteration waits for the subprocess to end. It throws if the subprocess fails. This means you do not need to `await` the subprocess' promise. */ [Symbol.asyncIterator](): SubprocessAsyncIterable; /** - Same as `subprocess[Symbol.asyncIterator]` except options can be provided. + Same as `subprocess[Symbol.asyncIterator]` except options can be provided and iterable helpers can be used. */ iterable(readableOptions?: IterableOptions): SubprocessAsyncIterable; /** Converts the subprocess to a readable stream. - - Unlike `subprocess.stdout`, the stream waits for the subprocess to end and emits an [`error`](https://nodejs.org/api/stream.html#event-error) event if the subprocess fails. This means you do not need to `await` the subprocess' promise. On the other hand, you do need to handle to the stream `error` event. This can be done by using [`await finished(stream)`](https://nodejs.org/api/stream.html#streamfinishedstream-options), [`await pipeline(..., stream)`](https://nodejs.org/api/stream.html#streampipelinesource-transforms-destination-options) or [`await text(stream)`](https://nodejs.org/api/webstreams.html#streamconsumerstextstream) which throw an exception when the stream errors. - - Before using this method, please first consider the `stdin`/`stdout`/`stderr`/`stdio` options, `subprocess.pipe()` or `subprocess.iterable()`. */ readable(readableOptions?: ReadableOptions): Readable; /** Converts the subprocess to a writable stream. - - Unlike `subprocess.stdin`, the stream waits for the subprocess to end and emits an [`error`](https://nodejs.org/api/stream.html#event-error) event if the subprocess fails. This means you do not need to `await` the subprocess' promise. On the other hand, you do need to handle to the stream `error` event. This can be done by using [`await finished(stream)`](https://nodejs.org/api/stream.html#streamfinishedstream-options) or [`await pipeline(stream, ...)`](https://nodejs.org/api/stream.html#streampipelinesource-transforms-destination-options) which throw an exception when the stream errors. - - Before using this method, please first consider the `stdin`/`stdout`/`stderr`/`stdio` options or `subprocess.pipe()`. */ writable(writableOptions?: WritableOptions): Writable; /** Converts the subprocess to a duplex stream. - - The stream waits for the subprocess to end and emits an [`error`](https://nodejs.org/api/stream.html#event-error) event if the subprocess fails. This means you do not need to `await` the subprocess' promise. On the other hand, you do need to handle to the stream `error` event. This can be done by using [`await finished(stream)`](https://nodejs.org/api/stream.html#streamfinishedstream-options), [`await pipeline(..., stream, ...)`](https://nodejs.org/api/stream.html#streampipelinesource-transforms-destination-options) or [`await text(stream)`](https://nodejs.org/api/webstreams.html#streamconsumerstextstream) which throw an exception when the stream errors. - - Before using this method, please first consider the `stdin`/`stdout`/`stderr`/`stdio` options, `subprocess.pipe()` or `subprocess.iterable()`. */ duplex(duplexOptions?: DuplexOptions): Duplex; } & PipableSubprocess;