From 770788027308cb8a5d2002445c65e25024e1a4c6 Mon Sep 17 00:00:00 2001
From: Minh Nguyen <2852660+NMinhNguyen@users.noreply.github.com>
Date: Wed, 17 Nov 2021 10:48:00 +0000
Subject: [PATCH] Require Node.js 12.20 and move to ESM (#478)
---
.github/workflows/main.yml | 1 -
index.d.ts | 940 +++++++++---------
index.js | 69 +-
index.test-d.ts | 90 +-
lib/command.js | 19 +-
lib/error.js | 9 +-
lib/kill.js | 33 +-
lib/promise.js | 44 +-
lib/stdio.js | 7 +-
lib/stream.js | 25 +-
package.json | 26 +-
readme.md | 139 ++-
test/command.js | 64 +-
test/error.js | 92 +-
...mmand with space => command with space.js} | 3 +-
test/fixtures/{delay => delay.js} | 2 +-
test/fixtures/detach | 8 -
test/fixtures/detach.js | 7 +
test/fixtures/{echo-fail => echo-fail.js} | 3 +-
test/fixtures/{echo => echo.js} | 3 +-
test/fixtures/{environment => environment.js} | 3 +-
test/fixtures/{exit => exit.js} | 3 +-
test/fixtures/{fail => fail.js} | 2 +-
test/fixtures/{forever => forever.js} | 2 -
test/fixtures/{max-buffer => max-buffer.js} | 3 +-
test/fixtures/no-killable | 12 -
test/fixtures/no-killable.js | 12 +
.../{non-executable => non-executable.js} | 1 -
test/fixtures/{noop-132 => noop-132.js} | 2 +-
test/fixtures/{noop-err => noop-err.js} | 3 +-
test/fixtures/{noop-throw => noop-throw.js} | 3 +-
test/fixtures/{noop => noop.js} | 3 +-
test/fixtures/{send => send.js} | 5 +-
test/fixtures/{stdin => stdin.js} | 3 +-
test/fixtures/sub-process | 8 -
.../{sub-process-exit => sub-process-exit.js} | 6 +-
test/fixtures/sub-process.js | 8 +
test/helpers/override-promise.js | 11 +
test/kill.js | 70 +-
test/node.js | 44 +-
test/override-promise.js | 24 +-
test/promise.js | 19 +-
test/stdio.js | 10 +-
test/stream.js | 83 +-
test/test.js | 78 +-
45 files changed, 992 insertions(+), 1010 deletions(-)
rename test/fixtures/{command with space => command with space.js} (64%)
rename test/fixtures/{delay => delay.js} (65%)
delete mode 100755 test/fixtures/detach
create mode 100755 test/fixtures/detach.js
rename test/fixtures/{echo-fail => echo-fail.js} (69%)
rename test/fixtures/{echo => echo.js} (64%)
rename test/fixtures/{environment => environment.js} (68%)
rename test/fixtures/{exit => exit.js} (61%)
rename test/fixtures/{fail => fail.js} (51%)
rename test/fixtures/{forever => forever.js} (75%)
rename test/fixtures/{max-buffer => max-buffer.js} (81%)
delete mode 100755 test/fixtures/no-killable
create mode 100755 test/fixtures/no-killable.js
rename test/fixtures/{non-executable => non-executable.js} (58%)
rename test/fixtures/{noop-132 => noop-132.js} (78%)
rename test/fixtures/{noop-err => noop-err.js} (58%)
rename test/fixtures/{noop-throw => noop-throw.js} (65%)
rename test/fixtures/{noop => noop.js} (57%)
rename test/fixtures/{send => send.js} (74%)
rename test/fixtures/{stdin => stdin.js} (60%)
delete mode 100755 test/fixtures/sub-process
rename test/fixtures/{sub-process-exit => sub-process-exit.js} (61%)
create mode 100755 test/fixtures/sub-process.js
create mode 100644 test/helpers/override-promise.js
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index f00996f767..e9ba5d9ef7 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -13,7 +13,6 @@ jobs:
- 16
- 14
- 12
- - 10
os:
- ubuntu-latest
- macos-latest
diff --git a/index.d.ts b/index.d.ts
index 417d535575..fdf9de1c39 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,564 +1,552 @@
-///
-import {ChildProcess} from 'child_process';
-import {Stream, Readable as ReadableStream} from 'stream';
+import {Buffer} from 'node:buffer';
+import {ChildProcess} from 'node:child_process';
+import {Stream, Readable as ReadableStream} from 'node:stream';
+
+export type StdioOption =
+ | 'pipe'
+ | 'ipc'
+ | 'ignore'
+ | 'inherit'
+ | Stream
+ | number
+ | undefined;
+
+export interface CommonOptions {
+ /**
+ Kill the spawned process when the parent process exits unless either:
+ - the spawned process is [`detached`](https://nodejs.org/api/child_process.html#child_process_options_detached)
+ - the parent process is terminated abruptly, for example, with `SIGKILL` as opposed to `SIGTERM` or a normal exit
-declare namespace execa {
- type StdioOption =
- | 'pipe'
- | 'ipc'
- | 'ignore'
- | 'inherit'
- | Stream
- | number
- | undefined;
+ @default true
+ */
+ readonly cleanup?: boolean;
- interface CommonOptions {
- /**
- Kill the spawned process when the parent process exits unless either:
- - the spawned process is [`detached`](https://nodejs.org/api/child_process.html#child_process_options_detached)
- - the parent process is terminated abruptly, for example, with `SIGKILL` as opposed to `SIGTERM` or a normal exit
+ /**
+ Prefer locally installed binaries when looking for a binary to execute.
- @default true
- */
- readonly cleanup?: boolean;
+ If you `$ npm install foo`, you can then `execa('foo')`.
- /**
- Prefer locally installed binaries when looking for a binary to execute.
+ @default false
+ */
+ readonly preferLocal?: boolean;
- If you `$ npm install foo`, you can then `execa('foo')`.
+ /**
+ Preferred path to find locally installed binaries in (use with `preferLocal`).
- @default false
- */
- readonly preferLocal?: boolean;
+ @default process.cwd()
+ */
+ readonly localDir?: string;
- /**
- Preferred path to find locally installed binaries in (use with `preferLocal`).
+ /**
+ Path to the Node.js executable to use in child processes.
- @default process.cwd()
- */
- readonly localDir?: string;
+ This can be either an absolute path or a path relative to the `cwd` option.
- /**
- Path to the Node.js executable to use in child processes.
+ Requires `preferLocal` to be `true`.
- This can be either an absolute path or a path relative to the `cwd` option.
+ For example, this can be used together with [`get-node`](https://github.com/ehmicky/get-node) to run a specific Node.js version in a child process.
- Requires `preferLocal` to be `true`.
+ @default process.execPath
+ */
+ readonly execPath?: string;
- For example, this can be used together with [`get-node`](https://github.com/ehmicky/get-node) to run a specific Node.js version in a child process.
+ /**
+ Buffer the output from the spawned process. When set to `false`, you must read the output of `stdout` and `stderr` (or `all` if the `all` option is `true`). Otherwise the returned promise will not be resolved/rejected.
- @default process.execPath
- */
- readonly execPath?: string;
+ If the spawned process fails, `error.stdout`, `error.stderr`, and `error.all` will contain the buffered data.
- /**
- Buffer the output from the spawned process. When set to `false`, you must read the output of `stdout` and `stderr` (or `all` if the `all` option is `true`). Otherwise the returned promise will not be resolved/rejected.
+ @default true
+ */
+ readonly buffer?: boolean;
- If the spawned process fails, `error.stdout`, `error.stderr`, and `error.all` will contain the buffered data.
+ /**
+ Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).
- @default true
- */
- readonly buffer?: boolean;
+ @default 'pipe'
+ */
+ readonly stdin?: StdioOption;
- /**
- Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).
+ /**
+ Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).
- @default 'pipe'
- */
- readonly stdin?: StdioOption;
+ @default 'pipe'
+ */
+ readonly stdout?: StdioOption;
- /**
- Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).
+ /**
+ Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).
- @default 'pipe'
- */
- readonly stdout?: StdioOption;
+ @default 'pipe'
+ */
+ readonly stderr?: StdioOption;
- /**
- Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).
+ /**
+ Setting this to `false` resolves the promise with the error instead of rejecting it.
- @default 'pipe'
- */
- readonly stderr?: StdioOption;
+ @default true
+ */
+ readonly reject?: boolean;
- /**
- Setting this to `false` resolves the promise with the error instead of rejecting it.
+ /**
+ Add an `.all` property on the promise and the resolved value. The property contains the output of the process with `stdout` and `stderr` interleaved.
- @default true
- */
- readonly reject?: boolean;
+ @default false
+ */
+ readonly all?: boolean;
- /**
- Add an `.all` property on the promise and the resolved value. The property contains the output of the process with `stdout` and `stderr` interleaved.
+ /**
+ Strip the final [newline character](https://en.wikipedia.org/wiki/Newline) from the output.
- @default false
- */
- readonly all?: boolean;
+ @default true
+ */
+ readonly stripFinalNewline?: boolean;
- /**
- Strip the final [newline character](https://en.wikipedia.org/wiki/Newline) from the output.
+ /**
+ Set to `false` if you don't want to extend the environment variables when providing the `env` property.
- @default true
- */
- readonly stripFinalNewline?: boolean;
+ @default true
+ */
+ readonly extendEnv?: boolean;
- /**
- Set to `false` if you don't want to extend the environment variables when providing the `env` property.
+ /**
+ Current working directory of the child process.
- @default true
- */
- readonly extendEnv?: boolean;
+ @default process.cwd()
+ */
+ readonly cwd?: string;
- /**
- Current working directory of the child process.
+ /**
+ Environment key-value pairs. Extends automatically from `process.env`. Set `extendEnv` to `false` if you don't want this.
- @default process.cwd()
- */
- readonly cwd?: string;
+ @default process.env
+ */
+ readonly env?: NodeJS.ProcessEnv;
- /**
- Environment key-value pairs. Extends automatically from `process.env`. Set `extendEnv` to `false` if you don't want this.
+ /**
+ Explicitly set the value of `argv[0]` sent to the child process. This will be set to `command` or `file` if not specified.
+ */
+ readonly argv0?: string;
- @default process.env
- */
- readonly env?: NodeJS.ProcessEnv;
+ /**
+ Child's [stdio](https://nodejs.org/api/child_process.html#child_process_options_stdio) configuration.
- /**
- Explicitly set the value of `argv[0]` sent to the child process. This will be set to `command` or `file` if not specified.
- */
- readonly argv0?: string;
+ @default 'pipe'
+ */
+ readonly stdio?: 'pipe' | 'ignore' | 'inherit' | readonly StdioOption[];
- /**
- Child's [stdio](https://nodejs.org/api/child_process.html#child_process_options_stdio) configuration.
+ /**
+ Specify the kind of serialization used for sending messages between processes when using the `stdio: 'ipc'` option or `execaNode()`:
+ - `json`: Uses `JSON.stringify()` and `JSON.parse()`.
+ - `advanced`: Uses [`v8.serialize()`](https://nodejs.org/api/v8.html#v8_v8_serialize_value)
- @default 'pipe'
- */
- readonly stdio?: 'pipe' | 'ignore' | 'inherit' | readonly StdioOption[];
+ Requires Node.js `13.2.0` or later.
- /**
- Specify the kind of serialization used for sending messages between processes when using the `stdio: 'ipc'` option or `execa.node()`:
- - `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)
- Requires Node.js `13.2.0` or later.
+ @default 'json'
+ */
+ readonly serialization?: 'json' | 'advanced';
- [More info.](https://nodejs.org/api/child_process.html#child_process_advanced_serialization)
+ /**
+ Prepare child to run independently of its parent process. Specific behavior [depends on the platform](https://nodejs.org/api/child_process.html#child_process_options_detached).
- @default 'json'
- */
- readonly serialization?: 'json' | 'advanced';
+ @default false
+ */
+ readonly detached?: boolean;
- /**
- Prepare child to run independently of its parent process. Specific behavior [depends on the platform](https://nodejs.org/api/child_process.html#child_process_options_detached).
+ /**
+ Sets the user identity of the process.
+ */
+ readonly uid?: number;
- @default false
- */
- readonly detached?: boolean;
+ /**
+ Sets the group identity of the process.
+ */
+ readonly gid?: number;
- /**
- Sets the user identity of the process.
- */
- readonly uid?: 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.
- /**
- Sets the group identity of the process.
- */
- readonly gid?: number;
+ 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.
- /**
- 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.
+ @default false
+ */
+ readonly shell?: boolean | string;
- 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.
+ /**
+ Specify the character encoding used to decode the `stdout` and `stderr` output. If set to `null`, then `stdout` and `stderr` will be a `Buffer` instead of a string.
- @default false
- */
- readonly shell?: boolean | string;
+ @default 'utf8'
+ */
+ readonly encoding?: EncodingType;
- /**
- Specify the character encoding used to decode the `stdout` and `stderr` output. If set to `null`, then `stdout` and `stderr` will be a `Buffer` instead of a string.
+ /**
+ If `timeout` is greater than `0`, the parent will send the signal identified by the `killSignal` property (the default is `SIGTERM`) if the child runs longer than `timeout` milliseconds.
+
+ @default 0
+ */
+ readonly timeout?: number;
+
+ /**
+ Largest amount of data in bytes allowed on `stdout` or `stderr`. Default: 100 MB.
- @default 'utf8'
- */
- readonly encoding?: EncodingType;
+ @default 100_000_000
+ */
+ readonly maxBuffer?: number;
- /**
- If `timeout` is greater than `0`, the parent will send the signal identified by the `killSignal` property (the default is `SIGTERM`) if the child runs longer than `timeout` milliseconds.
+ /**
+ Signal value to be used when the spawned process will be killed.
- @default 0
- */
- readonly timeout?: number;
+ @default 'SIGTERM'
+ */
+ readonly killSignal?: string | number;
- /**
- Largest amount of data in bytes allowed on `stdout` or `stderr`. Default: 100 MB.
+ /**
+ 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`.
- @default 100_000_000
- */
- readonly maxBuffer?: number;
+ @default false
+ */
+ readonly windowsVerbatimArguments?: boolean;
- /**
- Signal value to be used when the spawned process will be killed.
+ /**
+ 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.
- @default 'SIGTERM'
- */
- readonly killSignal?: string | number;
+ @default true
+ */
+ readonly windowsHide?: boolean;
+}
- /**
- 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`.
+export interface Options extends CommonOptions {
+ /**
+ Write some input to the `stdin` of your binary.
+ */
+ readonly input?: string | Buffer | ReadableStream;
+}
- @default false
- */
- readonly windowsVerbatimArguments?: boolean;
+export interface SyncOptions extends CommonOptions {
+ /**
+ Write some input to the `stdin` of your binary.
+ */
+ readonly input?: string | Buffer;
+}
- /**
- 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.
+export interface NodeOptions extends Options {
+ /**
+ The Node.js executable to use.
- @default true
- */
- readonly windowsHide?: boolean;
- }
+ @default process.execPath
+ */
+ readonly nodePath?: string;
- interface Options extends CommonOptions {
- /**
- Write some input to the `stdin` of your binary.
- */
- readonly input?: string | Buffer | ReadableStream;
- }
+ /**
+ List of [CLI options](https://nodejs.org/api/cli.html#cli_options) passed to the Node.js executable.
- interface SyncOptions extends CommonOptions {
- /**
- Write some input to the `stdin` of your binary.
- */
- readonly input?: string | Buffer;
- }
-
- interface NodeOptions extends Options {
- /**
- The Node.js executable to use.
-
- @default process.execPath
- */
- readonly nodePath?: string;
-
- /**
- List of [CLI options](https://nodejs.org/api/cli.html#cli_options) passed to the Node.js executable.
-
- @default process.execArgv
- */
- readonly nodeOptions?: string[];
- }
-
- interface ExecaReturnBase {
- /**
- The file and arguments that were run, for logging purposes.
-
- This is not escaped and should not be executed directly as a process, including using `execa()` or `execa.command()`.
- */
- command: string;
-
- /**
- Same as `command` but escaped.
-
- This is meant to be copy and pasted into a shell, for debugging purposes.
- Since the escaping is fairly basic, this should not be executed directly as a process, including using `execa()` or `execa.command()`.
- */
- escapedCommand: string;
-
- /**
- The numeric exit code of the process that was run.
- */
- exitCode: number;
-
- /**
- The output of the process on stdout.
- */
- stdout: StdoutStderrType;
-
- /**
- The output of the process on stderr.
- */
- stderr: StdoutStderrType;
-
- /**
- Whether the process failed to run.
- */
- failed: boolean;
-
- /**
- Whether the process timed out.
- */
- timedOut: boolean;
-
- /**
- Whether the process was killed.
- */
- killed: boolean;
-
- /**
- The name of the signal that was used to terminate the process. For example, `SIGFPE`.
-
- If a signal terminated the process, this property is defined and included in the error message. Otherwise it is `undefined`.
- */
- signal?: string;
-
- /**
- A human-friendly description of the signal that was used to terminate the process. For example, `Floating point arithmetic error`.
-
- If a signal terminated the process, 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.
- */
- signalDescription?: string;
- }
-
- interface ExecaSyncReturnValue
- extends ExecaReturnBase {
- }
-
- /**
- Result of a child process execution. On success this is a plain object. On failure this is also an `Error` instance.
-
- The child process fails when:
- - its exit code is not `0`
- - it was killed with a signal
- - timing out
- - being canceled
- - there's not enough memory or there are already too many child processes
- */
- interface ExecaReturnValue
- extends ExecaSyncReturnValue {
- /**
- The output of the process with `stdout` and `stderr` interleaved.
-
- This is `undefined` if either:
- - the `all` option is `false` (default value)
- - `execa.sync()` was used
- */
- all?: StdoutErrorType;
-
- /**
- Whether the process was canceled.
- */
- isCanceled: boolean;
- }
-
- interface ExecaSyncError
- extends Error,
- ExecaReturnBase {
- /**
- Error message when the child process failed to run. In addition to the underlying error message, it also contains some information related to why the child process errored.
-
- The child process stderr then stdout are appended to the end, separated with newlines and not interleaved.
- */
- message: string;
-
- /**
- This is the same as the `message` property except it does not include the child process stdout/stderr.
- */
- shortMessage: string;
-
- /**
- Original error message. This is the same as the `message` property except it includes neither the child process stdout/stderr nor some additional information added by Execa.
-
- This is `undefined` unless the child process exited due to an `error` event or a timeout.
- */
- originalMessage?: string;
- }
-
- interface ExecaError
- extends ExecaSyncError {
- /**
- The output of the process with `stdout` and `stderr` interleaved.
-
- This is `undefined` if either:
- - the `all` option is `false` (default value)
- - `execa.sync()` was used
- */
- all?: StdoutErrorType;
-
- /**
- Whether the process was canceled.
- */
- isCanceled: boolean;
- }
-
- interface KillOptions {
- /**
- Milliseconds to wait for the child process to terminate before sending `SIGKILL`.
-
- Can be disabled with `false`.
-
- @default 5000
- */
- forceKillAfterTimeout?: number | false;
- }
-
- interface ExecaChildPromise {
- /**
- Stream combining/interleaving [`stdout`](https://nodejs.org/api/child_process.html#child_process_subprocess_stdout) and [`stderr`](https://nodejs.org/api/child_process.html#child_process_subprocess_stderr).
-
- This is `undefined` if either:
- - the `all` option is `false` (the default value)
- - both `stdout` and `stderr` options are set to [`'inherit'`, `'ipc'`, `Stream` or `integer`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio)
- */
- all?: ReadableStream;
-
- catch(
- onRejected?: (reason: ExecaError) => ResultType | PromiseLike
- ): Promise | ResultType>;
-
- /**
- Same as the original [`child_process#kill()`](https://nodejs.org/api/child_process.html#child_process_subprocess_kill_signal), except if `signal` is `SIGTERM` (the default value) and the child process is not terminated after 5 seconds, force it by sending `SIGKILL`.
- */
- kill(signal?: string, options?: KillOptions): void;
-
- /**
- Similar to [`childProcess.kill()`](https://nodejs.org/api/child_process.html#child_process_subprocess_kill_signal). This is preferred when cancelling the child process execution as the error is more descriptive and [`childProcessResult.isCanceled`](#iscanceled) is set to `true`.
- */
- cancel(): void;
- }
-
- type ExecaChildProcess = ChildProcess &
- ExecaChildPromise &
- Promise>;
+ @default process.execArgv
+ */
+ readonly nodeOptions?: string[];
}
-declare const execa: {
+export interface ExecaReturnBase {
/**
- Execute a file.
+ The file and arguments that were run, for logging purposes.
- Think of this as a mix of `child_process.execFile` and `child_process.spawn`.
+ This is not escaped and should not be executed directly as a process, including using `execa()` or `execaCommand()`.
+ */
+ command: string;
- @param file - The program/script to execute.
- @param arguments - Arguments to pass to `file` on execution.
- @returns A [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess), which is enhanced to also be a `Promise` for a result `Object` with `stdout` and `stderr` properties.
+ /**
+ Same as `command` but escaped.
- @example
- ```
- import execa = require('execa');
+ This is meant to be copy and pasted into a shell, for debugging purposes.
+ Since the escaping is fairly basic, this should not be executed directly as a process, including using `execa()` or `execaCommand()`.
+ */
+ escapedCommand: string;
+
+ /**
+ The numeric exit code of the process that was run.
+ */
+ exitCode: number;
+
+ /**
+ The output of the process on stdout.
+ */
+ stdout: StdoutStderrType;
+
+ /**
+ The output of the process on stderr.
+ */
+ stderr: StdoutStderrType;
+
+ /**
+ Whether the process failed to run.
+ */
+ failed: boolean;
+
+ /**
+ Whether the process timed out.
+ */
+ timedOut: boolean;
+
+ /**
+ Whether the process was killed.
+ */
+ killed: boolean;
+
+ /**
+ The name of the signal that was used to terminate the process. For example, `SIGFPE`.
+
+ If a signal terminated the process, this property is defined and included in the error message. Otherwise it is `undefined`.
+ */
+ signal?: string;
+
+ /**
+ A human-friendly description of the signal that was used to terminate the process. For example, `Floating point arithmetic error`.
+
+ If a signal terminated the process, 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.
+ */
+ signalDescription?: string;
+}
+
+export interface ExecaSyncReturnValue
+ extends ExecaReturnBase {
+}
+
+/**
+Result of a child process execution. On success this is a plain object. On failure this is also an `Error` instance.
+
+The child process fails when:
+- its exit code is not `0`
+- it was killed with a signal
+- timing out
+- being canceled
+- there's not enough memory or there are already too many child processes
+*/
+export interface ExecaReturnValue
+ extends ExecaSyncReturnValue {
+ /**
+ The output of the process with `stdout` and `stderr` interleaved.
+
+ This is `undefined` if either:
+ - the `all` option is `false` (default value)
+ - `execaSync()` was used
+ */
+ all?: StdoutErrorType;
- (async () => {
- const {stdout} = await execa('echo', ['unicorns']);
- console.log(stdout);
- //=> 'unicorns'
+ /**
+ Whether the process was canceled.
+ */
+ isCanceled: boolean;
+}
- // Cancelling a spawned process
+export interface ExecaSyncError
+ extends Error,
+ ExecaReturnBase {
+ /**
+ Error message when the child process failed to run. In addition to the underlying error message, it also contains some information related to why the child process errored.
- const subprocess = execa('node');
+ The child process stderr then stdout are appended to the end, separated with newlines and not interleaved.
+ */
+ message: string;
- setTimeout(() => {
- subprocess.cancel()
- }, 1000);
+ /**
+ This is the same as the `message` property except it does not include the child process stdout/stderr.
+ */
+ shortMessage: string;
- try {
- await subprocess;
- } catch (error) {
- console.log(subprocess.killed); // true
- console.log(error.isCanceled); // true
- }
- })();
+ /**
+ Original error message. This is the same as the `message` property except it includes neither the child process stdout/stderr nor some additional information added by Execa.
- // Pipe the child process stdout to the current stdout
- execa('echo', ['unicorns']).stdout.pipe(process.stdout);
- ```
+ This is `undefined` unless the child process exited due to an `error` event or a timeout.
*/
- (
- file: string,
- arguments?: readonly string[],
- options?: execa.Options
- ): execa.ExecaChildProcess;
- (
- file: string,
- arguments?: readonly string[],
- options?: execa.Options
- ): execa.ExecaChildProcess;
- (file: string, options?: execa.Options): execa.ExecaChildProcess;
- (file: string, options?: execa.Options): execa.ExecaChildProcess<
- Buffer
- >;
+ originalMessage?: string;
+}
+export interface ExecaError
+ extends ExecaSyncError {
/**
- Execute a file synchronously.
+ The output of the process with `stdout` and `stderr` interleaved.
- This method throws an `Error` if the command fails.
+ This is `undefined` if either:
+ - the `all` option is `false` (default value)
+ - `execaSync()` was used
+ */
+ all?: StdoutErrorType;
- @param file - The program/script to execute.
- @param arguments - Arguments to pass to `file` on execution.
- @returns A result `Object` with `stdout` and `stderr` properties.
+ /**
+ Whether the process was canceled.
*/
- sync(
- file: string,
- arguments?: readonly string[],
- options?: execa.SyncOptions
- ): execa.ExecaSyncReturnValue;
- sync(
- file: string,
- arguments?: readonly string[],
- options?: execa.SyncOptions
- ): execa.ExecaSyncReturnValue;
- sync(file: string, options?: execa.SyncOptions): execa.ExecaSyncReturnValue;
- sync(
- file: string,
- options?: execa.SyncOptions
- ): execa.ExecaSyncReturnValue;
+ isCanceled: boolean;
+}
+export interface KillOptions {
/**
- Same as `execa()` except both file and arguments are specified in a single `command` string. For example, `execa('echo', ['unicorns'])` is the same as `execa.command('echo unicorns')`.
+ Milliseconds to wait for the child process to terminate before sending `SIGKILL`.
+
+ Can be disabled with `false`.
- If the file or an argument contains spaces, they must be escaped with backslashes. This matters especially if `command` is not a constant but a variable, for example with `__dirname` or `process.cwd()`. Except for spaces, no escaping/quoting is needed.
+ @default 5000
+ */
+ forceKillAfterTimeout?: number | false;
+}
+
+export interface ExecaChildPromise {
+ /**
+ Stream combining/interleaving [`stdout`](https://nodejs.org/api/child_process.html#child_process_subprocess_stdout) and [`stderr`](https://nodejs.org/api/child_process.html#child_process_subprocess_stderr).
- The `shell` option must be used if the `command` uses shell-specific features (for example, `&&` or `||`), as opposed to being a simple `file` followed by its `arguments`.
+ This is `undefined` if either:
+ - the `all` option is `false` (the default value)
+ - both `stdout` and `stderr` options are set to [`'inherit'`, `'ipc'`, `Stream` or `integer`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio)
+ */
+ all?: ReadableStream;
- @param command - The program/script to execute and its arguments.
- @returns A [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess), which is enhanced to also be a `Promise` for a result `Object` with `stdout` and `stderr` properties.
+ catch(
+ onRejected?: (reason: ExecaError) => ResultType | PromiseLike
+ ): Promise | ResultType>;
- @example
- ```
- import execa = require('execa');
+ /**
+ Same as the original [`child_process#kill()`](https://nodejs.org/api/child_process.html#child_process_subprocess_kill_signal), except if `signal` is `SIGTERM` (the default value) and the child process is not terminated after 5 seconds, force it by sending `SIGKILL`.
+ */
+ kill(signal?: string, options?: KillOptions): void;
- (async () => {
- const {stdout} = await execa.command('echo unicorns');
- console.log(stdout);
- //=> 'unicorns'
- })();
- ```
+ /**
+ Similar to [`childProcess.kill()`](https://nodejs.org/api/child_process.html#child_process_subprocess_kill_signal). This is preferred when cancelling the child process execution as the error is more descriptive and [`childProcessResult.isCanceled`](#iscanceled) is set to `true`.
*/
- command(command: string, options?: execa.Options): execa.ExecaChildProcess;
- command(command: string, options?: execa.Options): execa.ExecaChildProcess;
-
- /**
- Same as `execa.command()` but synchronous.
-
- @param command - The program/script to execute and its arguments.
- @returns A result `Object` with `stdout` and `stderr` properties.
- */
- commandSync(command: string, options?: execa.SyncOptions): execa.ExecaSyncReturnValue;
- commandSync(command: string, options?: execa.SyncOptions): execa.ExecaSyncReturnValue;
-
- /**
- Execute a Node.js script as a child process.
-
- Same as `execa('node', [scriptPath, ...arguments], options)` except (like [`child_process#fork()`](https://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options)):
- - the current Node version and options are used. This can be overridden using the `nodePath` and `nodeArguments` options.
- - the `shell` option cannot be used
- - an extra channel [`ipc`](https://nodejs.org/api/child_process.html#child_process_options_stdio) is passed to [`stdio`](#stdio)
-
- @param scriptPath - Node.js script to execute.
- @param arguments - Arguments to pass to `scriptPath` on execution.
- @returns A [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess), which is enhanced to also be a `Promise` for a result `Object` with `stdout` and `stderr` properties.
- */
- node(
- scriptPath: string,
- arguments?: readonly string[],
- options?: execa.NodeOptions
- ): execa.ExecaChildProcess;
- node(
- scriptPath: string,
- arguments?: readonly string[],
- options?: execa.Options
- ): execa.ExecaChildProcess;
- node(scriptPath: string, options?: execa.Options): execa.ExecaChildProcess;
- node(scriptPath: string, options?: execa.Options): execa.ExecaChildProcess;
-};
-
-export = execa;
+ cancel(): void;
+}
+
+export type ExecaChildProcess = ChildProcess &
+ExecaChildPromise &
+Promise>;
+
+/**
+Execute a file.
+
+Think of this as a mix of `child_process.execFile` and `child_process.spawn`.
+
+@param file - The program/script to execute.
+@param arguments - Arguments to pass to `file` on execution.
+@returns A [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess), which is enhanced to also be a `Promise` for a result `Object` with `stdout` and `stderr` properties.
+
+@example
+```
+import {execa} from 'execa';
+
+const {stdout} = await execa('echo', ['unicorns']);
+console.log(stdout);
+//=> 'unicorns'
+
+// Cancelling a spawned process
+
+const subprocess = execa('node');
+
+setTimeout(() => {
+ subprocess.cancel()
+}, 1000);
+
+try {
+ await subprocess;
+} catch (error) {
+ console.log(subprocess.killed); // true
+ console.log(error.isCanceled); // true
+}
+
+// Pipe the child process stdout to the current stdout
+execa('echo', ['unicorns']).stdout.pipe(process.stdout);
+```
+*/
+export function execa(
+ file: string,
+ arguments?: readonly string[],
+ options?: Options
+): ExecaChildProcess;
+export function execa(
+ file: string,
+ arguments?: readonly string[],
+ options?: Options
+): ExecaChildProcess;
+export function execa(file: string, options?: Options): ExecaChildProcess;
+export function execa(file: string, options?: Options): ExecaChildProcess;
+
+/**
+Execute a file synchronously.
+
+This method throws an `Error` if the command fails.
+
+@param file - The program/script to execute.
+@param arguments - Arguments to pass to `file` on execution.
+@returns A result `Object` with `stdout` and `stderr` properties.
+*/
+export function execaSync(
+ file: string,
+ arguments?: readonly string[],
+ options?: SyncOptions
+): ExecaSyncReturnValue;
+export function execaSync(
+ file: string,
+ arguments?: readonly string[],
+ options?: SyncOptions
+): ExecaSyncReturnValue;
+export function execaSync(file: string, options?: SyncOptions): ExecaSyncReturnValue;
+export function execaSync(
+ file: string,
+ options?: SyncOptions
+): ExecaSyncReturnValue;
+
+/**
+Same as `execa()` except both file and arguments are specified in a single `command` string. For example, `execa('echo', ['unicorns'])` is the same as `execaCommand('echo unicorns')`.
+
+If the file or an argument contains spaces, they must be escaped with backslashes. This matters especially if `command` is not a constant but a variable, for example with `__dirname` or `process.cwd()`. Except for spaces, no escaping/quoting is needed.
+
+The `shell` option must be used if the `command` uses shell-specific features (for example, `&&` or `||`), as opposed to being a simple `file` followed by its `arguments`.
+
+@param command - The program/script to execute and its arguments.
+@returns A [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess), which is enhanced to also be a `Promise` for a result `Object` with `stdout` and `stderr` properties.
+
+@example
+```
+import {execaCommand} from 'execa';
+
+const {stdout} = await execaCommand('echo unicorns');
+console.log(stdout);
+//=> 'unicorns'
+```
+*/
+export function execaCommand(command: string, options?: Options): ExecaChildProcess;
+export function execaCommand(command: string, options?: Options): ExecaChildProcess;
+
+/**
+Same as `execaCommand()` but synchronous.
+
+@param command - The program/script to execute and its arguments.
+@returns A result `Object` with `stdout` and `stderr` properties.
+*/
+export function execaCommandSync(command: string, options?: SyncOptions): ExecaSyncReturnValue;
+export function execaCommandSync(command: string, options?: SyncOptions): ExecaSyncReturnValue;
+
+/**
+Execute a Node.js script as a child process.
+
+Same as `execa('node', [scriptPath, ...arguments], options)` except (like [`child_process#fork()`](https://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options)):
+ - the current Node version and options are used. This can be overridden using the `nodePath` and `nodeArguments` options.
+ - the `shell` option cannot be used
+ - an extra channel [`ipc`](https://nodejs.org/api/child_process.html#child_process_options_stdio) is passed to [`stdio`](#stdio)
+
+@param scriptPath - Node.js script to execute.
+@param arguments - Arguments to pass to `scriptPath` on execution.
+@returns A [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess), which is enhanced to also be a `Promise` for a result `Object` with `stdout` and `stderr` properties.
+*/
+export function execaNode(
+ scriptPath: string,
+ arguments?: readonly string[],
+ options?: NodeOptions
+): ExecaChildProcess;
+export function execaNode(
+ scriptPath: string,
+ arguments?: readonly string[],
+ options?: Options
+): ExecaChildProcess;
+export function execaNode(scriptPath: string, options?: Options): ExecaChildProcess;
+export function execaNode(scriptPath: string, options?: Options): ExecaChildProcess;
diff --git a/index.js b/index.js
index 6fc9f12954..ff33fce3aa 100644
--- a/index.js
+++ b/index.js
@@ -1,16 +1,17 @@
-'use strict';
-const path = require('path');
-const childProcess = require('child_process');
-const crossSpawn = require('cross-spawn');
-const stripFinalNewline = require('strip-final-newline');
-const npmRunPath = require('npm-run-path');
-const onetime = require('onetime');
-const makeError = require('./lib/error');
-const normalizeStdio = require('./lib/stdio');
-const {spawnedKill, spawnedCancel, setupTimeout, validateTimeout, setExitHandler} = require('./lib/kill');
-const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = require('./lib/stream');
-const {mergePromise, getSpawnedPromise} = require('./lib/promise');
-const {joinCommand, parseCommand, getEscapedCommand} = require('./lib/command');
+import {Buffer} from 'node:buffer';
+import path from 'node:path';
+import childProcess from 'node:child_process';
+import process from 'node:process';
+import crossSpawn from 'cross-spawn';
+import stripFinalNewline from 'strip-final-newline';
+import {npmRunPathEnv} from 'npm-run-path';
+import onetime from 'onetime';
+import {makeError} from './lib/error.js';
+import {normalizeStdio, normalizeStdioNode} from './lib/stdio.js';
+import {spawnedKill, spawnedCancel, setupTimeout, validateTimeout, setExitHandler} from './lib/kill.js';
+import {handleInput, getSpawnedResult, makeAllStream, validateInputSync} from './lib/stream.js';
+import {mergePromise, getSpawnedPromise} from './lib/promise.js';
+import {joinCommand, parseCommand, getEscapedCommand} from './lib/command.js';
const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100;
@@ -18,7 +19,7 @@ const getEnv = ({env: envOption, extendEnv, preferLocal, localDir, execPath}) =>
const env = extendEnv ? {...process.env, ...envOption} : envOption;
if (preferLocal) {
- return npmRunPath.env({env, cwd: localDir, execPath});
+ return npmRunPathEnv({env, cwd: localDir, execPath});
}
return env;
@@ -43,7 +44,7 @@ const handleArguments = (file, args, options = {}) => {
cleanup: true,
all: false,
windowsHide: true,
- ...options
+ ...options,
};
options.env = getEnv(options);
@@ -60,7 +61,7 @@ const handleArguments = (file, args, options = {}) => {
const handleOutput = (options, value, error) => {
if (typeof value !== 'string' && !Buffer.isBuffer(value)) {
- // When `execa.sync()` errors, we normalize it to '' to mimic `execa()`
+ // When `execaSync()` errors, we normalize it to '' to mimic `execa()`
return error === undefined ? undefined : '';
}
@@ -71,7 +72,7 @@ const handleOutput = (options, value, error) => {
return value;
};
-const execa = (file, args, options) => {
+export const execa = (file, args, options) => {
const parsed = handleArguments(file, args, options);
const command = joinCommand(file, args);
const escapedCommand = getEscapedCommand(file, args);
@@ -94,7 +95,7 @@ const execa = (file, args, options) => {
parsed,
timedOut: false,
isCanceled: false,
- killed: false
+ killed: false,
}));
return mergePromise(dummySpawned, errorPromise);
}
@@ -127,7 +128,7 @@ const execa = (file, args, options) => {
parsed,
timedOut,
isCanceled: context.isCanceled,
- killed: spawned.killed
+ killed: spawned.killed,
});
if (!parsed.options.reject) {
@@ -147,7 +148,7 @@ const execa = (file, args, options) => {
failed: false,
timedOut: false,
isCanceled: false,
- killed: false
+ killed: false,
};
};
@@ -160,9 +161,7 @@ const execa = (file, args, options) => {
return mergePromise(spawned, handlePromiseOnce);
};
-module.exports = execa;
-
-module.exports.sync = (file, args, options) => {
+export const execaSync = (file, args, options) => {
const parsed = handleArguments(file, args, options);
const command = joinCommand(file, args);
const escapedCommand = getEscapedCommand(file, args);
@@ -183,7 +182,7 @@ module.exports.sync = (file, args, options) => {
parsed,
timedOut: false,
isCanceled: false,
- killed: false
+ killed: false,
});
}
@@ -202,7 +201,7 @@ module.exports.sync = (file, args, options) => {
parsed,
timedOut: result.error && result.error.code === 'ETIMEDOUT',
isCanceled: false,
- killed: result.signal !== null
+ killed: result.signal !== null,
});
if (!parsed.options.reject) {
@@ -221,32 +220,32 @@ module.exports.sync = (file, args, options) => {
failed: false,
timedOut: false,
isCanceled: false,
- killed: false
+ killed: false,
};
};
-module.exports.command = (command, options) => {
+export const execaCommand = (command, options) => {
const [file, ...args] = parseCommand(command);
return execa(file, args, options);
};
-module.exports.commandSync = (command, options) => {
+export const execaCommandSync = (command, options) => {
const [file, ...args] = parseCommand(command);
- return execa.sync(file, args, options);
+ return execaSync(file, args, options);
};
-module.exports.node = (scriptPath, args, options = {}) => {
+export const execaNode = (scriptPath, args, options = {}) => {
if (args && !Array.isArray(args) && typeof args === 'object') {
options = args;
args = [];
}
- const stdio = normalizeStdio.node(options);
+ const stdio = normalizeStdioNode(options);
const defaultExecArgv = process.execArgv.filter(arg => !arg.startsWith('--inspect'));
const {
nodePath = process.execPath,
- nodeOptions = defaultExecArgv
+ nodeOptions = defaultExecArgv,
} = options;
return execa(
@@ -254,7 +253,7 @@ module.exports.node = (scriptPath, args, options = {}) => {
[
...nodeOptions,
scriptPath,
- ...(Array.isArray(args) ? args : [])
+ ...(Array.isArray(args) ? args : []),
],
{
...options,
@@ -262,7 +261,7 @@ module.exports.node = (scriptPath, args, options = {}) => {
stdout: undefined,
stderr: undefined,
stdio,
- shell: false
- }
+ shell: false,
+ },
);
};
diff --git a/index.test-d.ts b/index.test-d.ts
index b5da697b24..9b1d0924bf 100644
--- a/index.test-d.ts
+++ b/index.test-d.ts
@@ -1,13 +1,22 @@
+import {Buffer} from 'node:buffer';
+// For some reason a default import of `process` causes
+// `process.stdin`, `process.stderr`, and `process.stdout`
+// to get treated as `any` by `@typescript-eslint/no-unsafe-assignment`.
+import * as process from 'node:process';
+import {Readable as ReadableStream} from 'node:stream';
import {expectType, expectError} from 'tsd';
-import {Readable as ReadableStream} from 'stream';
-import execa = require('.');
import {
+ execa,
+ execaSync,
+ execaCommand,
+ execaCommandSync,
+ execaNode,
ExecaReturnValue,
ExecaChildProcess,
ExecaError,
ExecaSyncReturnValue,
- ExecaSyncError
-} from '.';
+ ExecaSyncError,
+} from './index.js';
try {
const execaPromise = execa('unicorns');
@@ -46,7 +55,7 @@ try {
}
try {
- const unicornsResult = execa.sync('unicorns');
+ const unicornsResult = execaSync('unicorns');
expectType(unicornsResult.command);
expectType(unicornsResult.escapedCommand);
expectType(unicornsResult.exitCode);
@@ -77,6 +86,7 @@ try {
expectType(execaError.originalMessage);
}
+/* eslint-disable @typescript-eslint/no-floating-promises */
execa('unicorns', {cleanup: false});
execa('unicorns', {preferLocal: false});
execa('unicorns', {localDir: '.'});
@@ -111,13 +121,14 @@ execa('unicorns', {reject: false});
execa('unicorns', {stripFinalNewline: false});
execa('unicorns', {extendEnv: false});
execa('unicorns', {cwd: '.'});
+// eslint-disable-next-line @typescript-eslint/naming-convention
execa('unicorns', {env: {PATH: ''}});
execa('unicorns', {argv0: ''});
execa('unicorns', {stdio: 'pipe'});
execa('unicorns', {stdio: 'ignore'});
execa('unicorns', {stdio: 'inherit'});
execa('unicorns', {
- stdio: ['pipe', 'ipc', 'ignore', 'inherit', process.stdin, 1, undefined]
+ stdio: ['pipe', 'ipc', 'ignore', 'inherit', process.stdin, 1, undefined],
});
execa('unicorns', {serialization: 'advanced'});
execa('unicorns', {detached: true});
@@ -131,6 +142,7 @@ execa('unicorns', {killSignal: 'SIGTERM'});
execa('unicorns', {killSignal: 9});
execa('unicorns', {windowsVerbatimArguments: true});
execa('unicorns', {windowsHide: false});
+/* eslint-enable @typescript-eslint/no-floating-promises */
execa('unicorns').kill();
execa('unicorns').kill('SIGKILL');
execa('unicorns').kill(undefined);
@@ -139,55 +151,55 @@ execa('unicorns').kill('SIGKILL', {forceKillAfterTimeout: false});
execa('unicorns').kill('SIGKILL', {forceKillAfterTimeout: 42});
execa('unicorns').kill('SIGKILL', {forceKillAfterTimeout: undefined});
-expectType>(execa('unicorns'));
-expectType>(await execa('unicorns'));
-expectType>(
- await execa('unicorns', {encoding: 'utf8'})
+expectType(execa('unicorns'));
+expectType(await execa('unicorns'));
+expectType(
+ await execa('unicorns', {encoding: 'utf8'}),
);
expectType>(await execa('unicorns', {encoding: null}));
-expectType>(
- await execa('unicorns', ['foo'], {encoding: 'utf8'})
+expectType(
+ await execa('unicorns', ['foo'], {encoding: 'utf8'}),
);
expectType>(
- await execa('unicorns', ['foo'], {encoding: null})
+ await execa('unicorns', ['foo'], {encoding: null}),
);
-expectType>(execa.sync('unicorns'));
-expectType>(
- execa.sync('unicorns', {encoding: 'utf8'})
+expectType(execaSync('unicorns'));
+expectType(
+ execaSync('unicorns', {encoding: 'utf8'}),
);
expectType>(
- execa.sync('unicorns', {encoding: null})
+ execaSync('unicorns', {encoding: null}),
);
-expectType>(
- execa.sync('unicorns', ['foo'], {encoding: 'utf8'})
+expectType(
+ execaSync('unicorns', ['foo'], {encoding: 'utf8'}),
);
expectType>(
- execa.sync('unicorns', ['foo'], {encoding: null})
+ execaSync('unicorns', ['foo'], {encoding: null}),
);
-expectType>(execa.command('unicorns'));
-expectType>(await execa.command('unicorns'));
-expectType>(await execa.command('unicorns', {encoding: 'utf8'}));
-expectType>(await execa.command('unicorns', {encoding: null}));
-expectType>(await execa.command('unicorns foo', {encoding: 'utf8'}));
-expectType>(await execa.command('unicorns foo', {encoding: null}));
+expectType(execaCommand('unicorns'));
+expectType(await execaCommand('unicorns'));
+expectType(await execaCommand('unicorns', {encoding: 'utf8'}));
+expectType>(await execaCommand('unicorns', {encoding: null}));
+expectType(await execaCommand('unicorns foo', {encoding: 'utf8'}));
+expectType>(await execaCommand('unicorns foo', {encoding: null}));
-expectType>(execa.commandSync('unicorns'));
-expectType>(execa.commandSync('unicorns', {encoding: 'utf8'}));
-expectType>(execa.commandSync('unicorns', {encoding: null}));
-expectType>(execa.commandSync('unicorns foo', {encoding: 'utf8'}));
-expectType>(execa.commandSync('unicorns foo', {encoding: null}));
+expectType(execaCommandSync('unicorns'));
+expectType(execaCommandSync('unicorns', {encoding: 'utf8'}));
+expectType>(execaCommandSync('unicorns', {encoding: null}));
+expectType(execaCommandSync('unicorns foo', {encoding: 'utf8'}));
+expectType>(execaCommandSync('unicorns foo', {encoding: null}));
-expectType>(execa.node('unicorns'));
-expectType>(await execa.node('unicorns'));
-expectType>(
- await execa.node('unicorns', {encoding: 'utf8'})
+expectType(execaNode('unicorns'));
+expectType(await execaNode('unicorns'));
+expectType(
+ await execaNode('unicorns', {encoding: 'utf8'}),
);
-expectType>(await execa.node('unicorns', {encoding: null}));
-expectType>(
- await execa.node('unicorns', ['foo'], {encoding: 'utf8'})
+expectType>(await execaNode('unicorns', {encoding: null}));
+expectType(
+ await execaNode('unicorns', ['foo'], {encoding: 'utf8'}),
);
expectType>(
- await execa.node('unicorns', ['foo'], {encoding: null})
+ await execaNode('unicorns', ['foo'], {encoding: null}),
);
diff --git a/lib/command.js b/lib/command.js
index 859b006a06..be64255216 100644
--- a/lib/command.js
+++ b/lib/command.js
@@ -1,4 +1,3 @@
-'use strict';
const normalizeArgs = (file, args = []) => {
if (!Array.isArray(args)) {
return [file];
@@ -18,18 +17,14 @@ const escapeArg = arg => {
return `"${arg.replace(DOUBLE_QUOTES_REGEXP, '\\"')}"`;
};
-const joinCommand = (file, args) => {
- return normalizeArgs(file, args).join(' ');
-};
+export const joinCommand = (file, args) => normalizeArgs(file, args).join(' ');
-const getEscapedCommand = (file, args) => {
- return normalizeArgs(file, args).map(arg => escapeArg(arg)).join(' ');
-};
+export const getEscapedCommand = (file, args) => normalizeArgs(file, args).map(arg => escapeArg(arg)).join(' ');
const SPACES_REGEXP = / +/g;
-// Handle `execa.command()`
-const parseCommand = command => {
+// Handle `execaCommand()`
+export const parseCommand = command => {
const tokens = [];
for (const token of command.trim().split(SPACES_REGEXP)) {
// Allow spaces to be escaped by a backslash if not meant as a delimiter
@@ -44,9 +39,3 @@ const parseCommand = command => {
return tokens;
};
-
-module.exports = {
- joinCommand,
- getEscapedCommand,
- parseCommand
-};
diff --git a/lib/error.js b/lib/error.js
index 42144674dc..b12c144428 100644
--- a/lib/error.js
+++ b/lib/error.js
@@ -1,5 +1,4 @@
-'use strict';
-const {signalsByName} = require('human-signals');
+import {signalsByName} from 'human-signals';
const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => {
if (timedOut) {
@@ -25,7 +24,7 @@ const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription
return 'failed';
};
-const makeError = ({
+export const makeError = ({
stdout,
stderr,
all,
@@ -37,7 +36,7 @@ const makeError = ({
timedOut,
isCanceled,
killed,
- parsed: {options: {timeout}}
+ parsed: {options: {timeout}},
}) => {
// `signal` and `exitCode` emitted on `spawned.on('exit')` event can be `null`.
// We normalize them to `undefined`
@@ -84,5 +83,3 @@ const makeError = ({
return error;
};
-
-module.exports = makeError;
diff --git a/lib/kill.js b/lib/kill.js
index 287a14238e..efc6b9ba41 100644
--- a/lib/kill.js
+++ b/lib/kill.js
@@ -1,11 +1,10 @@
-'use strict';
-const os = require('os');
-const onExit = require('signal-exit');
+import os from 'node:os';
+import onExit from 'signal-exit';
const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5;
// Monkey-patches `childProcess.kill()` to add `forceKillAfterTimeout` behavior
-const spawnedKill = (kill, signal = 'SIGTERM', options = {}) => {
+export const spawnedKill = (kill, signal = 'SIGTERM', options = {}) => {
const killResult = kill(signal);
setKillTimeout(kill, signal, options, killResult);
return killResult;
@@ -30,14 +29,10 @@ const setKillTimeout = (kill, signal, options, killResult) => {
}
};
-const shouldForceKill = (signal, {forceKillAfterTimeout}, killResult) => {
- return isSigterm(signal) && forceKillAfterTimeout !== false && killResult;
-};
+const shouldForceKill = (signal, {forceKillAfterTimeout}, killResult) => isSigterm(signal) && forceKillAfterTimeout !== false && killResult;
-const isSigterm = signal => {
- return signal === os.constants.signals.SIGTERM ||
- (typeof signal === 'string' && signal.toUpperCase() === 'SIGTERM');
-};
+const isSigterm = signal => signal === os.constants.signals.SIGTERM
+ || (typeof signal === 'string' && signal.toUpperCase() === 'SIGTERM');
const getForceKillAfterTimeout = ({forceKillAfterTimeout = true}) => {
if (forceKillAfterTimeout === true) {
@@ -52,7 +47,7 @@ const getForceKillAfterTimeout = ({forceKillAfterTimeout = true}) => {
};
// `childProcess.cancel()`
-const spawnedCancel = (spawned, context) => {
+export const spawnedCancel = (spawned, context) => {
const killResult = spawned.kill();
if (killResult) {
@@ -66,7 +61,7 @@ const timeoutKill = (spawned, signal, reject) => {
};
// `timeout` option handling
-const setupTimeout = (spawned, {timeout, killSignal = 'SIGTERM'}, spawnedPromise) => {
+export const setupTimeout = (spawned, {timeout, killSignal = 'SIGTERM'}, spawnedPromise) => {
if (timeout === 0 || timeout === undefined) {
return spawnedPromise;
}
@@ -85,14 +80,14 @@ const setupTimeout = (spawned, {timeout, killSignal = 'SIGTERM'}, spawnedPromise
return Promise.race([timeoutPromise, safeSpawnedPromise]);
};
-const validateTimeout = ({timeout}) => {
+export const validateTimeout = ({timeout}) => {
if (timeout !== undefined && (!Number.isFinite(timeout) || timeout < 0)) {
throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`);
}
};
// `cleanup` option handling
-const setExitHandler = async (spawned, {cleanup, detached}, timedPromise) => {
+export const setExitHandler = async (spawned, {cleanup, detached}, timedPromise) => {
if (!cleanup || detached) {
return timedPromise;
}
@@ -105,11 +100,3 @@ const setExitHandler = async (spawned, {cleanup, detached}, timedPromise) => {
removeExitHandler();
});
};
-
-module.exports = {
- spawnedKill,
- spawnedCancel,
- setupTimeout,
- validateTimeout,
- setExitHandler
-};
diff --git a/lib/promise.js b/lib/promise.js
index bd9d52333d..c655c0e4a5 100644
--- a/lib/promise.js
+++ b/lib/promise.js
@@ -1,18 +1,16 @@
-'use strict';
-
const nativePromisePrototype = (async () => {})().constructor.prototype;
const descriptors = ['then', 'catch', 'finally'].map(property => [
property,
- Reflect.getOwnPropertyDescriptor(nativePromisePrototype, property)
+ Reflect.getOwnPropertyDescriptor(nativePromisePrototype, property),
]);
// The return value is a mixin of `childProcess` and `Promise`
-const mergePromise = (spawned, promise) => {
+export const mergePromise = (spawned, promise) => {
for (const [property, descriptor] of descriptors) {
// Starting the main `promise` is deferred to avoid consuming streams
- const value = typeof promise === 'function' ?
- (...args) => Reflect.apply(descriptor.value, promise(), args) :
- descriptor.value.bind(promise);
+ const value = typeof promise === 'function'
+ ? (...args) => Reflect.apply(descriptor.value, promise(), args)
+ : descriptor.value.bind(promise);
Reflect.defineProperty(spawned, property, {...descriptor, value});
}
@@ -21,26 +19,18 @@ const mergePromise = (spawned, promise) => {
};
// Use promises instead of `child_process` events
-const getSpawnedPromise = spawned => {
- return new Promise((resolve, reject) => {
- spawned.on('exit', (exitCode, signal) => {
- resolve({exitCode, signal});
- });
-
- spawned.on('error', error => {
- reject(error);
- });
-
- if (spawned.stdin) {
- spawned.stdin.on('error', error => {
- reject(error);
- });
- }
+export const getSpawnedPromise = spawned => new Promise((resolve, reject) => {
+ spawned.on('exit', (exitCode, signal) => {
+ resolve({exitCode, signal});
});
-};
-module.exports = {
- mergePromise,
- getSpawnedPromise
-};
+ spawned.on('error', error => {
+ reject(error);
+ });
+ if (spawned.stdin) {
+ spawned.stdin.on('error', error => {
+ reject(error);
+ });
+ }
+});
diff --git a/lib/stdio.js b/lib/stdio.js
index 45129ed7ef..e8c1132dc1 100644
--- a/lib/stdio.js
+++ b/lib/stdio.js
@@ -1,9 +1,8 @@
-'use strict';
const aliases = ['stdin', 'stdout', 'stderr'];
const hasAlias = options => aliases.some(alias => options[alias] !== undefined);
-const normalizeStdio = options => {
+export const normalizeStdio = options => {
if (!options) {
return;
}
@@ -30,10 +29,8 @@ const normalizeStdio = options => {
return Array.from({length}, (value, index) => stdio[index]);
};
-module.exports = normalizeStdio;
-
// `ipc` is pushed unless it is already present
-module.exports.node = options => {
+export const normalizeStdioNode = options => {
const stdio = normalizeStdio(options);
if (stdio === 'ipc') {
diff --git a/lib/stream.js b/lib/stream.js
index d445dd4710..b140bf14f9 100644
--- a/lib/stream.js
+++ b/lib/stream.js
@@ -1,10 +1,9 @@
-'use strict';
-const isStream = require('is-stream');
-const getStream = require('get-stream');
-const mergeStream = require('merge-stream');
+import {isStream} from 'is-stream';
+import getStream from 'get-stream';
+import mergeStream from 'merge-stream';
// `input` option
-const handleInput = (spawned, input) => {
+export const handleInput = (spawned, input) => {
// Checking for stdin is workaround for https://github.com/nodejs/node/issues/26852
// @todo remove `|| spawned.stdin === undefined` once we drop support for Node.js <=12.2.0
if (input === undefined || spawned.stdin === undefined) {
@@ -19,7 +18,7 @@ const handleInput = (spawned, input) => {
};
// `all` interleaves `stdout` and `stderr`
-const makeAllStream = (spawned, {all}) => {
+export const makeAllStream = (spawned, {all}) => {
if (!all || (!spawned.stdout && !spawned.stderr)) {
return;
}
@@ -65,7 +64,7 @@ const getStreamPromise = (stream, {encoding, buffer, maxBuffer}) => {
};
// Retrieve result of child process: exit code, signal, error, streams (stdout/stderr/all)
-const getSpawnedResult = async ({stdout, stderr, all}, {encoding, buffer, maxBuffer}, processDone) => {
+export const getSpawnedResult = async ({stdout, stderr, all}, {encoding, buffer, maxBuffer}, processDone) => {
const stdoutPromise = getStreamPromise(stdout, {encoding, buffer, maxBuffer});
const stderrPromise = getStreamPromise(stderr, {encoding, buffer, maxBuffer});
const allPromise = getStreamPromise(all, {encoding, buffer, maxBuffer: maxBuffer * 2});
@@ -77,21 +76,13 @@ const getSpawnedResult = async ({stdout, stderr, all}, {encoding, buffer, maxBuf
{error, signal: error.signal, timedOut: error.timedOut},
getBufferedData(stdout, stdoutPromise),
getBufferedData(stderr, stderrPromise),
- getBufferedData(all, allPromise)
+ getBufferedData(all, allPromise),
]);
}
};
-const validateInputSync = ({input}) => {
+export const validateInputSync = ({input}) => {
if (isStream(input)) {
throw new TypeError('The `input` option cannot be a stream in sync mode');
}
};
-
-module.exports = {
- handleInput,
- makeAllStream,
- getSpawnedResult,
- validateInputSync
-};
-
diff --git a/package.json b/package.json
index 22556f28d8..3f3a87bb0c 100644
--- a/package.json
+++ b/package.json
@@ -11,10 +11,12 @@
"url": "https://sindresorhus.com"
},
"engines": {
- "node": ">=10"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
+ "type": "module",
+ "exports": "./index.js",
"scripts": {
- "test": "xo && nyc ava && tsd"
+ "test": "xo && c8 ava && tsd"
},
"files": [
"index.js",
@@ -41,26 +43,26 @@
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^6.0.0",
- "human-signals": "^2.1.0",
- "is-stream": "^2.0.0",
+ "human-signals": "^3.0.1",
+ "is-stream": "^3.0.0",
"merge-stream": "^2.0.0",
- "npm-run-path": "^4.0.1",
- "onetime": "^5.1.2",
+ "npm-run-path": "^5.0.1",
+ "onetime": "^6.0.0",
"signal-exit": "^3.0.3",
- "strip-final-newline": "^2.0.0"
+ "strip-final-newline": "^3.0.0"
},
"devDependencies": {
- "@types/node": "^14.14.10",
- "ava": "^2.4.0",
+ "@types/node": "^16.11.7",
+ "ava": "^3.15.0",
+ "c8": "^7.10.0",
"get-node": "^11.0.1",
"is-running": "^2.1.0",
- "nyc": "^15.1.0",
"p-event": "^4.2.0",
"tempfile": "^3.0.0",
"tsd": "^0.13.1",
- "xo": "^0.35.0"
+ "xo": "^0.46.4"
},
- "nyc": {
+ "c8": {
"reporter": [
"text",
"lcov"
diff --git a/readme.md b/readme.md
index 843edbc7d1..33f031a365 100644
--- a/readme.md
+++ b/readme.md
@@ -29,19 +29,17 @@ $ npm install execa
## Usage
```js
-const execa = require('execa');
+import {execa} from 'execa';
-(async () => {
- const {stdout} = await execa('echo', ['unicorns']);
- console.log(stdout);
- //=> 'unicorns'
-})();
+const {stdout} = await execa('echo', ['unicorns']);
+console.log(stdout);
+//=> 'unicorns'
```
### Pipe the child process stdout to the parent
```js
-const execa = require('execa');
+import {execa} from 'execa';
execa('echo', ['unicorns']).stdout.pipe(process.stdout);
```
@@ -49,66 +47,63 @@ execa('echo', ['unicorns']).stdout.pipe(process.stdout);
### Handling Errors
```js
-const execa = require('execa');
-
-(async () => {
- // Catching an error
- try {
- await execa('unknown', ['command']);
- } catch (error) {
- console.log(error);
- /*
- {
- message: 'Command failed with ENOENT: unknown command spawn unknown ENOENT',
- errno: -2,
- code: 'ENOENT',
- syscall: 'spawn unknown',
- path: 'unknown',
- spawnargs: ['command'],
- originalMessage: 'spawn unknown ENOENT',
- shortMessage: 'Command failed with ENOENT: unknown command spawn unknown ENOENT',
- command: 'unknown command',
- escapedCommand: 'unknown command',
- stdout: '',
- stderr: '',
- all: '',
- failed: true,
- timedOut: false,
- isCanceled: false,
- killed: false
- }
- */
- }
+import {execa} from 'execa';
-})();
+// Catching an error
+try {
+ await execa('unknown', ['command']);
+} catch (error) {
+ console.log(error);
+ /*
+ {
+ message: 'Command failed with ENOENT: unknown command spawn unknown ENOENT',
+ errno: -2,
+ code: 'ENOENT',
+ syscall: 'spawn unknown',
+ path: 'unknown',
+ spawnargs: ['command'],
+ originalMessage: 'spawn unknown ENOENT',
+ shortMessage: 'Command failed with ENOENT: unknown command spawn unknown ENOENT',
+ command: 'unknown command',
+ escapedCommand: 'unknown command',
+ stdout: '',
+ stderr: '',
+ all: '',
+ failed: true,
+ timedOut: false,
+ isCanceled: false,
+ killed: false
+ }
+ */
+}
```
### Cancelling a spawned process
```js
-const execa = require('execa');
+import {execa} from 'execa';
-(async () => {
- const subprocess = execa('node');
+const subprocess = execa('node');
- setTimeout(() => {
- subprocess.cancel();
- }, 1000);
+setTimeout(() => {
+ subprocess.cancel();
+}, 1000);
- try {
- await subprocess;
- } catch (error) {
- console.log(subprocess.killed); // true
- console.log(error.isCanceled); // true
- }
-})()
+try {
+ await subprocess;
+} catch (error) {
+ console.log(subprocess.killed); // true
+ console.log(error.isCanceled); // true
+}
```
### Catching an error with the sync method
```js
+import {execaSync} from 'execa';
+
try {
- execa.sync('unknown', ['command']);
+ execaSync('unknown', ['command']);
} catch (error) {
console.log(error);
/*
@@ -190,27 +185,27 @@ This is `undefined` if either:
- the [`all` option](#all-2) is `false` (the default value)
- both [`stdout`](#stdout-1) and [`stderr`](#stderr-1) options are set to [`'inherit'`, `'ipc'`, `Stream` or `integer`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio)
-### execa.sync(file, arguments?, options?)
+### execaSync(file, arguments?, options?)
Execute a file synchronously.
Returns or throws a [`childProcessResult`](#childProcessResult).
-### execa.command(command, options?)
+### execaCommand(command, options?)
-Same as [`execa()`](#execafile-arguments-options) except both file and arguments are specified in a single `command` string. For example, `execa('echo', ['unicorns'])` is the same as `execa.command('echo unicorns')`.
+Same as [`execa()`](#execafile-arguments-options) except both file and arguments are specified in a single `command` string. For example, `execa('echo', ['unicorns'])` is the same as `execaCommand('echo unicorns')`.
If the file or an argument contains spaces, they must be escaped with backslashes. This matters especially if `command` is not a constant but a variable, for example with `__dirname` or `process.cwd()`. Except for spaces, no escaping/quoting is needed.
The [`shell` option](#shell) must be used if the `command` uses shell-specific features (for example, `&&` or `||`), as opposed to being a simple `file` followed by its `arguments`.
-### execa.commandSync(command, options?)
+### execaCommandSync(command, options?)
-Same as [`execa.command()`](#execacommand-command-options) but synchronous.
+Same as [`execaCommand()`](#execacommand-command-options) but synchronous.
Returns or throws a [`childProcessResult`](#childProcessResult).
-### execa.node(scriptPath, arguments?, options?)
+### execaNode(scriptPath, arguments?, options?)
Execute a Node.js script as a child process.
@@ -238,7 +233,7 @@ Type: `string`
The file and arguments that were run, for logging purposes.
-This is not escaped and should not be executed directly as a process, including using [`execa()`](#execafile-arguments-options) or [`execa.command()`](#execacommandcommand-options).
+This is not escaped and should not be executed directly as a process, including using [`execa()`](#execafile-arguments-options) or [`execaCommand()`](#execacommandcommand-options).
#### escapedCommand
@@ -247,7 +242,7 @@ Type: `string`
Same as [`command`](#command) but escaped.
This is meant to be copy and pasted into a shell, for debugging purposes.
-Since the escaping is fairly basic, this should not be executed directly as a process, including using [`execa()`](#execafile-arguments-options) or [`execa.command()`](#execacommandcommand-options).
+Since the escaping is fairly basic, this should not be executed directly as a process, including using [`execa()`](#execafile-arguments-options) or [`execaCommand()`](#execacommandcommand-options).
#### exitCode
@@ -275,7 +270,7 @@ The output of the process with `stdout` and `stderr` interleaved.
This is `undefined` if either:
- the [`all` option](#all-2) is `false` (the default value)
- - `execa.sync()` was used
+ - `execaSync()` was used
#### failed
@@ -481,7 +476,7 @@ Child's [stdio](https://nodejs.org/api/child_process.html#child_process_options_
Type: `string`\
Default: `'json'`
-Specify the kind of serialization used for sending messages between processes when using the [`stdio: 'ipc'`](#stdio) option or [`execa.node()`](#execanodescriptpath-arguments-options):
+Specify the kind of serialization used for sending messages between processes when using the [`stdio: 'ipc'`](#stdio) option or [`execaNode()`](#execanodescriptpath-arguments-options):
- `json`: Uses `JSON.stringify()` and `JSON.parse()`.
- `advanced`: Uses [`v8.serialize()`](https://nodejs.org/api/v8.html#v8_v8_serialize_value)
@@ -582,16 +577,14 @@ List of [CLI options](https://nodejs.org/api/cli.html#cli_options) passed to the
Gracefully handle failures by using automatic retries and exponential backoff with the [`p-retry`](https://github.com/sindresorhus/p-retry) package:
```js
-const pRetry = require('p-retry');
+import pRetry from 'p-retry';
const run = async () => {
const results = await execa('curl', ['-sSL', 'https://sindresorhus.com/unicorn']);
return results;
};
-(async () => {
- console.log(await pRetry(run, {retries: 5}));
-})();
+console.log(await pRetry(run, {retries: 5}));
```
### Save and pipe output from a child process
@@ -599,21 +592,19 @@ const run = async () => {
Let's say you want to show the output of a child process in real-time while also saving it to a variable.
```js
-const execa = require('execa');
+import {execa} from 'execa';
const subprocess = execa('echo', ['foo']);
subprocess.stdout.pipe(process.stdout);
-(async () => {
- const {stdout} = await subprocess;
- console.log('child output:', stdout);
-})();
+const {stdout} = await subprocess;
+console.log('child output:', stdout);
```
### Redirect output to a file
```js
-const execa = require('execa');
+import {execa} from 'execa';
const subprocess = execa('echo', ['foo'])
subprocess.stdout.pipe(fs.createWriteStream('stdout.txt'))
@@ -622,7 +613,7 @@ subprocess.stdout.pipe(fs.createWriteStream('stdout.txt'))
### Redirect input from a file
```js
-const execa = require('execa');
+import {execa} from 'execa';
const subprocess = execa('cat')
fs.createReadStream('stdin.txt').pipe(subprocess.stdin)
@@ -631,7 +622,7 @@ fs.createReadStream('stdin.txt').pipe(subprocess.stdin)
### Execute the current package's binary
```js
-const {getBinPathSync} = require('get-bin-path');
+import {getBinPathSync} from 'get-bin-path';
const binPath = getBinPathSync();
const subprocess = execa(binPath);
diff --git a/test/command.js b/test/command.js
index 6c45715388..0db7ab9c49 100644
--- a/test/command.js
+++ b/test/command.js
@@ -1,15 +1,17 @@
-import path from 'path';
+import path from 'node:path';
+import process from 'node:process';
+import {fileURLToPath} from 'node:url';
import test from 'ava';
-import execa from '..';
+import {execa, execaSync, execaCommand, execaCommandSync} from '../index.js';
-process.env.PATH = path.join(__dirname, 'fixtures') + path.delimiter + process.env.PATH;
+process.env.PATH = fileURLToPath(new URL('./fixtures', import.meta.url)) + path.delimiter + process.env.PATH;
const command = async (t, expected, ...args) => {
- const {command: failCommand} = await t.throwsAsync(execa('fail', args));
- t.is(failCommand, `fail${expected}`);
+ const {command: failCommand} = await t.throwsAsync(execa('fail.js', args));
+ t.is(failCommand, `fail.js${expected}`);
- const {command} = await execa('noop', args);
- t.is(command, `noop${expected}`);
+ const {command} = await execa('noop.js', args);
+ t.is(command, `noop.js${expected}`);
};
command.title = (message, expected) => `command is: ${JSON.stringify(expected)}`;
@@ -19,19 +21,19 @@ test(command, ' baz quz', 'baz', 'quz');
test(command, '');
const testEscapedCommand = async (t, expected, args) => {
- const {escapedCommand: failEscapedCommand} = await t.throwsAsync(execa('fail', args));
- t.is(failEscapedCommand, `fail ${expected}`);
+ const {escapedCommand: failEscapedCommand} = await t.throwsAsync(execa('fail.js', args));
+ t.is(failEscapedCommand, `fail.js ${expected}`);
const {escapedCommand: failEscapedCommandSync} = t.throws(() => {
- execa.sync('fail', args);
+ execaSync('fail.js', args);
});
- t.is(failEscapedCommandSync, `fail ${expected}`);
+ t.is(failEscapedCommandSync, `fail.js ${expected}`);
- const {escapedCommand} = await execa('noop', args);
- t.is(escapedCommand, `noop ${expected}`);
+ const {escapedCommand} = await execa('noop.js', args);
+ t.is(escapedCommand, `noop.js ${expected}`);
- const {escapedCommand: escapedCommandSync} = execa.sync('noop', args);
- t.is(escapedCommandSync, `noop ${expected}`);
+ const {escapedCommand: escapedCommandSync} = execaSync('noop.js', args);
+ t.is(escapedCommandSync, `noop.js ${expected}`);
};
testEscapedCommand.title = (message, expected) => `escapedCommand is: ${JSON.stringify(expected)}`;
@@ -42,46 +44,46 @@ test(testEscapedCommand, '"\\"foo\\""', ['"foo"']);
test(testEscapedCommand, '"*"', ['*']);
test('allow commands with spaces and no array arguments', async t => {
- const {stdout} = await execa('command with space');
+ const {stdout} = await execa('command with space.js');
t.is(stdout, '');
});
test('allow commands with spaces and array arguments', async t => {
- const {stdout} = await execa('command with space', ['foo', 'bar']);
+ const {stdout} = await execa('command with space.js', ['foo', 'bar']);
t.is(stdout, 'foo\nbar');
});
-test('execa.command()', async t => {
- const {stdout} = await execa.command('node test/fixtures/echo foo bar');
+test('execaCommand()', async t => {
+ const {stdout} = await execaCommand('node test/fixtures/echo.js foo bar');
t.is(stdout, 'foo\nbar');
});
-test('execa.command() ignores consecutive spaces', async t => {
- const {stdout} = await execa.command('node test/fixtures/echo foo bar');
+test('execaCommand() ignores consecutive spaces', async t => {
+ const {stdout} = await execaCommand('node test/fixtures/echo.js foo bar');
t.is(stdout, 'foo\nbar');
});
-test('execa.command() allows escaping spaces in commands', async t => {
- const {stdout} = await execa.command('command\\ with\\ space foo bar');
+test('execaCommand() allows escaping spaces in commands', async t => {
+ const {stdout} = await execaCommand('command\\ with\\ space.js foo bar');
t.is(stdout, 'foo\nbar');
});
-test('execa.command() allows escaping spaces in arguments', async t => {
- const {stdout} = await execa.command('node test/fixtures/echo foo\\ bar');
+test('execaCommand() allows escaping spaces in arguments', async t => {
+ const {stdout} = await execaCommand('node test/fixtures/echo.js foo\\ bar');
t.is(stdout, 'foo bar');
});
-test('execa.command() escapes other whitespaces', async t => {
- const {stdout} = await execa.command('node test/fixtures/echo foo\tbar');
+test('execaCommand() escapes other whitespaces', async t => {
+ const {stdout} = await execaCommand('node test/fixtures/echo.js foo\tbar');
t.is(stdout, 'foo\tbar');
});
-test('execa.command() trims', async t => {
- const {stdout} = await execa.command(' node test/fixtures/echo foo bar ');
+test('execaCommand() trims', async t => {
+ const {stdout} = await execaCommand(' node test/fixtures/echo.js foo bar ');
t.is(stdout, 'foo\nbar');
});
-test('execa.command.sync()', t => {
- const {stdout} = execa.commandSync('node test/fixtures/echo foo bar');
+test('execaCommandSync()', t => {
+ const {stdout} = execaCommandSync('node test/fixtures/echo.js foo bar');
t.is(stdout, 'foo\nbar');
});
diff --git a/test/error.js b/test/error.js
index c375bc0763..a9c7746f86 100644
--- a/test/error.js
+++ b/test/error.js
@@ -1,24 +1,29 @@
-import path from 'path';
-import childProcess from 'child_process';
+import path from 'node:path';
+import process from 'node:process';
+import childProcess from 'node:child_process';
+import {fileURLToPath} from 'node:url';
+import {promisify} from 'node:util';
import test from 'ava';
-import execa from '..';
+import {execa, execaSync} from '../index.js';
-process.env.PATH = path.join(__dirname, 'fixtures') + path.delimiter + process.env.PATH;
+const pExec = promisify(childProcess.exec);
+
+process.env.PATH = fileURLToPath(new URL('./fixtures', import.meta.url)) + path.delimiter + process.env.PATH;
const TIMEOUT_REGEXP = /timed out after/;
const getExitRegExp = exitMessage => new RegExp(`failed with exit code ${exitMessage}`);
test('stdout/stderr/all available on errors', async t => {
- const {stdout, stderr, all} = await t.throwsAsync(execa('exit', ['2'], {all: true}), {message: getExitRegExp('2')});
+ const {stdout, stderr, all} = await t.throwsAsync(execa('exit.js', ['2'], {all: true}), {message: getExitRegExp('2')});
t.is(typeof stdout, 'string');
t.is(typeof stderr, 'string');
t.is(typeof all, 'string');
});
-const WRONG_COMMAND = process.platform === 'win32' ?
- '\'wrong\' is not recognized as an internal or external command,\r\noperable program or batch file.' :
- '';
+const WRONG_COMMAND = process.platform === 'win32'
+ ? '\'wrong\' is not recognized as an internal or external command,\r\noperable program or batch file.'
+ : '';
test('stdout/stderr/all on process errors', async t => {
const {stdout, stderr, all} = await t.throwsAsync(execa('wrong command', {all: true}));
@@ -29,7 +34,7 @@ test('stdout/stderr/all on process errors', async t => {
test('stdout/stderr/all on process errors, in sync mode', t => {
const {stdout, stderr, all} = t.throws(() => {
- execa.sync('wrong command');
+ execaSync('wrong command');
});
t.is(stdout, '');
t.is(stderr, WRONG_COMMAND);
@@ -37,12 +42,12 @@ test('stdout/stderr/all on process errors, in sync mode', t => {
});
test('exitCode is 0 on success', async t => {
- const {exitCode} = await execa('noop', ['foo']);
+ const {exitCode} = await execa('noop.js', ['foo']);
t.is(exitCode, 0);
});
const testExitCode = async (t, number) => {
- const {exitCode} = await t.throwsAsync(execa('exit', [`${number}`]), {message: getExitRegExp(number)});
+ const {exitCode} = await t.throwsAsync(execa('exit.js', [`${number}`]), {message: getExitRegExp(number)});
t.is(exitCode, number);
};
@@ -51,44 +56,47 @@ test('exitCode is 3', testExitCode, 3);
test('exitCode is 4', testExitCode, 4);
test('error.message contains the command', async t => {
- await t.throwsAsync(execa('exit', ['2', 'foo', 'bar']), {message: /exit 2 foo bar/});
+ await t.throwsAsync(execa('exit.js', ['2', 'foo', 'bar']), {message: /exit.js 2 foo bar/});
});
test('error.message contains stdout/stderr if available', async t => {
- const {message} = await t.throwsAsync(execa('echo-fail'));
+ const {message} = await t.throwsAsync(execa('echo-fail.js'));
t.true(message.includes('stderr'));
t.true(message.includes('stdout'));
});
test('error.message does not contain stdout/stderr if not available', async t => {
- const {message} = await t.throwsAsync(execa('echo-fail', {stdio: 'ignore'}));
+ const {message} = await t.throwsAsync(execa('echo-fail.js', {stdio: 'ignore'}));
t.false(message.includes('stderr'));
t.false(message.includes('stdout'));
});
test('error.shortMessage does not contain stdout/stderr', async t => {
- const {shortMessage} = await t.throwsAsync(execa('echo-fail'));
+ const {shortMessage} = await t.throwsAsync(execa('echo-fail.js'));
t.false(shortMessage.includes('stderr'));
t.false(shortMessage.includes('stdout'));
});
test('Original error.message is kept', async t => {
- const {originalMessage} = await t.throwsAsync(execa('noop', {cwd: 1}));
- t.true(originalMessage.startsWith('The "options.cwd" property must be of type string. Received type number'));
+ const {originalMessage} = await t.throwsAsync(execa('noop.js', {cwd: 1}));
+ // On Node >=14.18.0, the error message is
+ // `The "options.cwd" property must be of type string or an instance of Buffer or URL. Received type number (1)`
+ t.true(originalMessage.startsWith('The "options.cwd" property must be of type string'));
+ t.true(originalMessage.includes('. Received type number'));
});
test('failed is false on success', async t => {
- const {failed} = await execa('noop', ['foo']);
+ const {failed} = await execa('noop.js', ['foo']);
t.false(failed);
});
test('failed is true on failure', async t => {
- const {failed} = await t.throwsAsync(execa('exit', ['2']));
+ const {failed} = await t.throwsAsync(execa('exit.js', ['2']));
t.true(failed);
});
test('error.killed is true if process was killed directly', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
subprocess.kill();
@@ -97,7 +105,7 @@ test('error.killed is true if process was killed directly', async t => {
});
test('error.killed is false if process was killed indirectly', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
process.kill(subprocess.pid, 'SIGINT');
@@ -108,12 +116,12 @@ test('error.killed is false if process was killed indirectly', async t => {
});
test('result.killed is false if not killed', async t => {
- const {killed} = await execa('noop');
+ const {killed} = await execa('noop.js');
t.false(killed);
});
test('result.killed is false if not killed, in sync mode', t => {
- const {killed} = execa.sync('noop');
+ const {killed} = execaSync('noop.js');
t.false(killed);
});
@@ -124,26 +132,26 @@ test('result.killed is false on process error', async t => {
test('result.killed is false on process error, in sync mode', t => {
const {killed} = t.throws(() => {
- execa.sync('wrong command');
+ execaSync('wrong command');
});
t.false(killed);
});
if (process.platform === 'darwin') {
- test.cb('sanity check: child_process.exec also has killed.false if killed indirectly', t => {
- const {pid} = childProcess.exec('noop', error => {
- t.truthy(error);
- t.false(error.killed);
- t.end();
- });
-
- process.kill(pid, 'SIGINT');
+ test('sanity check: child_process.exec also has killed.false if killed indirectly', async t => {
+ const promise = pExec('noop.js');
+
+ process.kill(promise.child.pid, 'SIGINT');
+
+ const error = await t.throwsAsync(promise);
+ t.truthy(error);
+ t.false(error.killed);
});
}
if (process.platform !== 'win32') {
test('error.signal is SIGINT', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
process.kill(subprocess.pid, 'SIGINT');
@@ -152,7 +160,7 @@ if (process.platform !== 'win32') {
});
test('error.signalDescription is defined', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
process.kill(subprocess.pid, 'SIGINT');
@@ -161,7 +169,7 @@ if (process.platform !== 'win32') {
});
test('error.signal is SIGTERM', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
process.kill(subprocess.pid, 'SIGTERM');
@@ -170,12 +178,12 @@ if (process.platform !== 'win32') {
});
test('custom error.signal', async t => {
- const {signal} = await t.throwsAsync(execa('noop', {killSignal: 'SIGHUP', timeout: 1, message: TIMEOUT_REGEXP}));
+ const {signal} = await t.throwsAsync(execa('noop.js', {killSignal: 'SIGHUP', timeout: 1, message: TIMEOUT_REGEXP}));
t.is(signal, 'SIGHUP');
});
test('exitCode is undefined on signal termination', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
process.kill(subprocess.pid);
@@ -185,26 +193,26 @@ if (process.platform !== 'win32') {
}
test('result.signal is undefined for successful execution', async t => {
- const {signal} = await execa('noop');
+ const {signal} = await execa('noop.js');
t.is(signal, undefined);
});
test('result.signal is undefined if process failed, but was not killed', async t => {
- const {signal} = await t.throwsAsync(execa('exit', [2]), {message: getExitRegExp('2')});
+ const {signal} = await t.throwsAsync(execa('exit.js', [2]), {message: getExitRegExp('2')});
t.is(signal, undefined);
});
test('result.signalDescription is undefined for successful execution', async t => {
- const {signalDescription} = await execa('noop');
+ const {signalDescription} = await execa('noop.js');
t.is(signalDescription, undefined);
});
test('error.code is undefined on success', async t => {
- const {code} = await execa('noop');
+ const {code} = await execa('noop.js');
t.is(code, undefined);
});
test('error.code is defined on failure if applicable', async t => {
- const {code} = await t.throwsAsync(execa('noop', {cwd: 1}));
+ const {code} = await t.throwsAsync(execa('noop.js', {cwd: 1}));
t.is(code, 'ERR_INVALID_ARG_TYPE');
});
diff --git a/test/fixtures/command with space b/test/fixtures/command with space.js
similarity index 64%
rename from test/fixtures/command with space
rename to test/fixtures/command with space.js
index e9baf7ef67..828107d6f1 100755
--- a/test/fixtures/command with space
+++ b/test/fixtures/command with space.js
@@ -1,3 +1,4 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
+
console.log(process.argv.slice(2).join('\n'));
diff --git a/test/fixtures/delay b/test/fixtures/delay.js
similarity index 65%
rename from test/fixtures/delay
rename to test/fixtures/delay.js
index ed89fd8ddb..795190fc1e 100755
--- a/test/fixtures/delay
+++ b/test/fixtures/delay.js
@@ -1,4 +1,4 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
setTimeout(() => {}, Number(process.argv[2]));
diff --git a/test/fixtures/detach b/test/fixtures/detach
deleted file mode 100755
index 80f8980097..0000000000
--- a/test/fixtures/detach
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env node
-'use strict';
-
-const execa = require('../..');
-
-const subprocess = execa('node', ['./test/fixtures/forever'], {detached: true});
-console.log(subprocess.pid);
-process.exit();
diff --git a/test/fixtures/detach.js b/test/fixtures/detach.js
new file mode 100755
index 0000000000..b21d056a3a
--- /dev/null
+++ b/test/fixtures/detach.js
@@ -0,0 +1,7 @@
+#!/usr/bin/env node
+import process from 'node:process';
+import {execa} from '../../index.js';
+
+const subprocess = execa('node', ['./test/fixtures/forever.js'], {detached: true});
+console.log(subprocess.pid);
+process.exit(0);
diff --git a/test/fixtures/echo-fail b/test/fixtures/echo-fail.js
similarity index 69%
rename from test/fixtures/echo-fail
rename to test/fixtures/echo-fail.js
index 233f1d0c07..7ac2f13676 100755
--- a/test/fixtures/echo-fail
+++ b/test/fixtures/echo-fail.js
@@ -1,5 +1,6 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
+
console.log('stdout');
console.error('stderr');
process.exit(1);
diff --git a/test/fixtures/echo b/test/fixtures/echo.js
similarity index 64%
rename from test/fixtures/echo
rename to test/fixtures/echo.js
index e9baf7ef67..828107d6f1 100755
--- a/test/fixtures/echo
+++ b/test/fixtures/echo.js
@@ -1,3 +1,4 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
+
console.log(process.argv.slice(2).join('\n'));
diff --git a/test/fixtures/environment b/test/fixtures/environment.js
similarity index 68%
rename from test/fixtures/environment
rename to test/fixtures/environment.js
index 280faea9cb..0a4698ae59 100755
--- a/test/fixtures/environment
+++ b/test/fixtures/environment.js
@@ -1,4 +1,5 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
+
console.log(process.env.FOO);
console.log(process.env.BAR);
diff --git a/test/fixtures/exit b/test/fixtures/exit.js
similarity index 61%
rename from test/fixtures/exit
rename to test/fixtures/exit.js
index 0650d1108b..76f1f91666 100755
--- a/test/fixtures/exit
+++ b/test/fixtures/exit.js
@@ -1,3 +1,4 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
+
process.exit(Number(process.argv[2]));
diff --git a/test/fixtures/fail b/test/fixtures/fail.js
similarity index 51%
rename from test/fixtures/fail
rename to test/fixtures/fail.js
index 02e87041b5..881d915444 100755
--- a/test/fixtures/fail
+++ b/test/fixtures/fail.js
@@ -1,4 +1,4 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
process.exit(2);
diff --git a/test/fixtures/forever b/test/fixtures/forever.js
similarity index 75%
rename from test/fixtures/forever
rename to test/fixtures/forever.js
index b64113fa86..406c861da1 100755
--- a/test/fixtures/forever
+++ b/test/fixtures/forever.js
@@ -1,4 +1,2 @@
#!/usr/bin/env node
-'use strict';
-
setTimeout(() => {}, 1e8);
diff --git a/test/fixtures/max-buffer b/test/fixtures/max-buffer.js
similarity index 81%
rename from test/fixtures/max-buffer
rename to test/fixtures/max-buffer.js
index d1cfea941c..4d28851820 100755
--- a/test/fixtures/max-buffer
+++ b/test/fixtures/max-buffer.js
@@ -1,5 +1,6 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
+
const output = process.argv[2] || 'stdout';
const bytes = Number(process.argv[3] || 1e7);
diff --git a/test/fixtures/no-killable b/test/fixtures/no-killable
deleted file mode 100755
index 3ce9ba121f..0000000000
--- a/test/fixtures/no-killable
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env node
-'use strict';
-
-process.on('SIGTERM', () => {
- console.log('Received SIGTERM, but we ignore it');
-});
-
-process.send('');
-
-setInterval(() => {
- // Run forever
-}, 20000);
diff --git a/test/fixtures/no-killable.js b/test/fixtures/no-killable.js
new file mode 100755
index 0000000000..b27edf71d3
--- /dev/null
+++ b/test/fixtures/no-killable.js
@@ -0,0 +1,12 @@
+#!/usr/bin/env node
+import process from 'node:process';
+
+process.on('SIGTERM', () => {
+ console.log('Received SIGTERM, but we ignore it');
+});
+
+process.send('');
+
+setInterval(() => {
+ // Run forever
+}, 20_000);
diff --git a/test/fixtures/non-executable b/test/fixtures/non-executable.js
similarity index 58%
rename from test/fixtures/non-executable
rename to test/fixtures/non-executable.js
index 0e12b08794..908ba8417a 100644
--- a/test/fixtures/non-executable
+++ b/test/fixtures/non-executable.js
@@ -1,2 +1 @@
#!/usr/bin/env node
-'use strict';
diff --git a/test/fixtures/noop-132 b/test/fixtures/noop-132.js
similarity index 78%
rename from test/fixtures/noop-132
rename to test/fixtures/noop-132.js
index 277e817db6..77dfb77d66 100755
--- a/test/fixtures/noop-132
+++ b/test/fixtures/noop-132.js
@@ -1,5 +1,5 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
process.stdout.write('1');
process.stderr.write('3');
diff --git a/test/fixtures/noop-err b/test/fixtures/noop-err.js
similarity index 58%
rename from test/fixtures/noop-err
rename to test/fixtures/noop-err.js
index 8f6266d78f..505fb97fc2 100755
--- a/test/fixtures/noop-err
+++ b/test/fixtures/noop-err.js
@@ -1,3 +1,4 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
+
console.error(process.argv[2]);
diff --git a/test/fixtures/noop-throw b/test/fixtures/noop-throw.js
similarity index 65%
rename from test/fixtures/noop-throw
rename to test/fixtures/noop-throw.js
index 521e70abf8..2a42f98f33 100755
--- a/test/fixtures/noop-throw
+++ b/test/fixtures/noop-throw.js
@@ -1,4 +1,5 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
+
console.error(process.argv[2]);
process.exit(2);
diff --git a/test/fixtures/noop b/test/fixtures/noop.js
similarity index 57%
rename from test/fixtures/noop
rename to test/fixtures/noop.js
index 258e2bd30a..d55e447c8b 100755
--- a/test/fixtures/noop
+++ b/test/fixtures/noop.js
@@ -1,3 +1,4 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
+
console.log(process.argv[2]);
diff --git a/test/fixtures/send b/test/fixtures/send.js
similarity index 74%
rename from test/fixtures/send
rename to test/fixtures/send.js
index c215df6f74..ff52052af8 100755
--- a/test/fixtures/send
+++ b/test/fixtures/send.js
@@ -1,5 +1,6 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
+
process.on('message', message => {
if (message === 'ping') {
process.send('pong');
@@ -7,3 +8,5 @@ process.on('message', message => {
throw new Error('Receive wrong message');
}
});
+
+process.send('');
diff --git a/test/fixtures/stdin b/test/fixtures/stdin.js
similarity index 60%
rename from test/fixtures/stdin
rename to test/fixtures/stdin.js
index ddb0796f0c..4a10065ba7 100755
--- a/test/fixtures/stdin
+++ b/test/fixtures/stdin.js
@@ -1,3 +1,4 @@
#!/usr/bin/env node
-'use strict';
+import process from 'node:process';
+
process.stdin.pipe(process.stdout);
diff --git a/test/fixtures/sub-process b/test/fixtures/sub-process
deleted file mode 100755
index 6920e79348..0000000000
--- a/test/fixtures/sub-process
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env node
-'use strict';
-const execa = require('../..');
-
-const cleanup = process.argv[2] === 'true';
-const detached = process.argv[3] === 'true';
-const subprocess = execa('node', ['./test/fixtures/forever'], {cleanup, detached});
-process.send(subprocess.pid);
diff --git a/test/fixtures/sub-process-exit b/test/fixtures/sub-process-exit.js
similarity index 61%
rename from test/fixtures/sub-process-exit
rename to test/fixtures/sub-process-exit.js
index ea05f0e2d4..ae21147ef1 100755
--- a/test/fixtures/sub-process-exit
+++ b/test/fixtures/sub-process-exit.js
@@ -1,13 +1,13 @@
#!/usr/bin/env node
-'use strict';
-const execa = require('../..');
+import process from 'node:process';
+import {execa} from '../../index.js';
const cleanup = process.argv[2] === 'true';
const detached = process.argv[3] === 'true';
const runChild = async () => {
try {
- await execa('node', ['./test/fixtures/noop'], {cleanup, detached});
+ await execa('node', ['./test/fixtures/noop.js'], {cleanup, detached});
} catch (error) {
console.error(error);
process.exit(1);
diff --git a/test/fixtures/sub-process.js b/test/fixtures/sub-process.js
new file mode 100755
index 0000000000..a1b806db05
--- /dev/null
+++ b/test/fixtures/sub-process.js
@@ -0,0 +1,8 @@
+#!/usr/bin/env node
+import process from 'node:process';
+import {execa} from '../../index.js';
+
+const cleanup = process.argv[2] === 'true';
+const detached = process.argv[3] === 'true';
+const subprocess = execa('node', ['./test/fixtures/forever.js'], {cleanup, detached});
+process.send(subprocess.pid);
diff --git a/test/helpers/override-promise.js b/test/helpers/override-promise.js
new file mode 100644
index 0000000000..f69bb8ae5d
--- /dev/null
+++ b/test/helpers/override-promise.js
@@ -0,0 +1,11 @@
+// Can't use `test.before`, because `ava` needs `Promise`.
+const nativePromise = Promise;
+global.Promise = class BrokenPromise {
+ then() {
+ throw new Error('error');
+ }
+};
+
+export function restorePromise() {
+ global.Promise = nativePromise;
+}
diff --git a/test/kill.js b/test/kill.js
index 08bf8336ce..7cf7c09eff 100644
--- a/test/kill.js
+++ b/test/kill.js
@@ -1,15 +1,17 @@
-import path from 'path';
+import path from 'node:path';
+import process from 'node:process';
+import {fileURLToPath} from 'node:url';
import test from 'ava';
import pEvent from 'p-event';
import isRunning from 'is-running';
-import execa from '..';
+import {execa, execaSync} from '../index.js';
-process.env.PATH = path.join(__dirname, 'fixtures') + path.delimiter + process.env.PATH;
+process.env.PATH = fileURLToPath(new URL('./fixtures', import.meta.url)) + path.delimiter + process.env.PATH;
const TIMEOUT_REGEXP = /timed out after/;
test('kill("SIGKILL") should terminate cleanly', async t => {
- const subprocess = execa('node', ['./test/fixtures/no-killable'], {stdio: ['ipc']});
+ const subprocess = execa('node', ['./test/fixtures/no-killable.js'], {stdio: ['ipc']});
await pEvent(subprocess, 'message');
subprocess.kill('SIGKILL');
@@ -22,7 +24,7 @@ test('kill("SIGKILL") should terminate cleanly', async t => {
// Therefore, this feature and those tests do not make sense on Windows.
if (process.platform !== 'win32') {
test('`forceKillAfterTimeout: false` should not kill after a timeout', async t => {
- const subprocess = execa('node', ['./test/fixtures/no-killable'], {stdio: ['ipc']});
+ const subprocess = execa('node', ['./test/fixtures/no-killable.js'], {stdio: ['ipc']});
await pEvent(subprocess, 'message');
subprocess.kill('SIGTERM', {forceKillAfterTimeout: false});
@@ -32,7 +34,7 @@ if (process.platform !== 'win32') {
});
test('`forceKillAfterTimeout: number` should kill after a timeout', async t => {
- const subprocess = execa('node', ['./test/fixtures/no-killable'], {stdio: ['ipc']});
+ const subprocess = execa('node', ['./test/fixtures/no-killable.js'], {stdio: ['ipc']});
await pEvent(subprocess, 'message');
subprocess.kill('SIGTERM', {forceKillAfterTimeout: 50});
@@ -42,7 +44,7 @@ if (process.platform !== 'win32') {
});
test('`forceKillAfterTimeout: true` should kill after a timeout', async t => {
- const subprocess = execa('node', ['./test/fixtures/no-killable'], {stdio: ['ipc']});
+ const subprocess = execa('node', ['./test/fixtures/no-killable.js'], {stdio: ['ipc']});
await pEvent(subprocess, 'message');
subprocess.kill('SIGTERM', {forceKillAfterTimeout: true});
@@ -52,7 +54,7 @@ if (process.platform !== 'win32') {
});
test('kill() with no arguments should kill after a timeout', async t => {
- const subprocess = execa('node', ['./test/fixtures/no-killable'], {stdio: ['ipc']});
+ const subprocess = execa('node', ['./test/fixtures/no-killable.js'], {stdio: ['ipc']});
await pEvent(subprocess, 'message');
subprocess.kill();
@@ -63,38 +65,38 @@ if (process.platform !== 'win32') {
test('`forceKillAfterTimeout` should not be NaN', t => {
t.throws(() => {
- execa('noop').kill('SIGTERM', {forceKillAfterTimeout: Number.NaN});
+ execa('noop.js').kill('SIGTERM', {forceKillAfterTimeout: Number.NaN});
}, {instanceOf: TypeError, message: /non-negative integer/});
});
test('`forceKillAfterTimeout` should not be negative', t => {
t.throws(() => {
- execa('noop').kill('SIGTERM', {forceKillAfterTimeout: -1});
+ execa('noop.js').kill('SIGTERM', {forceKillAfterTimeout: -1});
}, {instanceOf: TypeError, message: /non-negative integer/});
});
}
test('execa() returns a promise with kill()', t => {
- const {kill} = execa('noop', ['foo']);
+ const {kill} = execa('noop.js', ['foo']);
t.is(typeof kill, 'function');
});
test('timeout kills the process if it times out', async t => {
- const {killed, timedOut} = await t.throwsAsync(execa('noop', {timeout: 1}), TIMEOUT_REGEXP);
+ const {killed, timedOut} = await t.throwsAsync(execa('noop.js', {timeout: 1}), {message: TIMEOUT_REGEXP});
t.false(killed);
t.true(timedOut);
});
test('timeout kills the process if it times out, in sync mode', async t => {
const {killed, timedOut} = await t.throws(() => {
- execa.sync('noop', {timeout: 1, message: TIMEOUT_REGEXP});
+ execaSync('noop.js', {timeout: 1, message: TIMEOUT_REGEXP});
});
t.false(killed);
t.true(timedOut);
});
test('timeout does not kill the process if it does not time out', async t => {
- const {timedOut} = await execa('delay', ['500'], {timeout: 1e8});
+ const {timedOut} = await execa('delay.js', ['500'], {timeout: 1e8});
t.false(timedOut);
});
@@ -102,34 +104,34 @@ const INVALID_TIMEOUT_REGEXP = /`timeout` option to be a non-negative integer/;
test('timeout must not be negative', async t => {
await t.throws(() => {
- execa('noop', {timeout: -1});
- }, INVALID_TIMEOUT_REGEXP);
+ execa('noop.js', {timeout: -1});
+ }, {message: INVALID_TIMEOUT_REGEXP});
});
test('timeout must be an integer', async t => {
await t.throws(() => {
- execa('noop', {timeout: false});
- }, INVALID_TIMEOUT_REGEXP);
+ execa('noop.js', {timeout: false});
+ }, {message: INVALID_TIMEOUT_REGEXP});
});
test('timedOut is false if timeout is undefined', async t => {
- const {timedOut} = await execa('noop');
+ const {timedOut} = await execa('noop.js');
t.false(timedOut);
});
test('timedOut is false if timeout is 0', async t => {
- const {timedOut} = await execa('noop', {timeout: 0});
+ const {timedOut} = await execa('noop.js', {timeout: 0});
t.false(timedOut);
});
test('timedOut is false if timeout is undefined and exit code is 0 in sync mode', t => {
- const {timedOut} = execa.sync('noop');
+ const {timedOut} = execaSync('noop.js');
t.false(timedOut);
});
// When child process exits before parent process
const spawnAndExit = async (t, cleanup, detached) => {
- await t.notThrowsAsync(execa('sub-process-exit', [cleanup, detached]));
+ await t.notThrowsAsync(execa('sub-process-exit.js', [cleanup, detached]));
};
test('spawnAndExit', spawnAndExit, false, false);
@@ -139,7 +141,7 @@ test('spawnAndExit cleanup detached', spawnAndExit, true, true);
// When parent process exits before child process
const spawnAndKill = async (t, [signal, cleanup, detached, isKilled]) => {
- const subprocess = execa('sub-process', [cleanup, detached], {stdio: ['ignore', 'ignore', 'ignore', 'ipc']});
+ const subprocess = execa('sub-process.js', [cleanup, detached], {stdio: ['ignore', 'ignore', 'ignore', 'ipc']});
const pid = await pEvent(subprocess, 'message');
t.true(Number.isInteger(pid));
@@ -178,7 +180,7 @@ test('removes exit handler on exit', async t => {
// @todo this relies on `signal-exit` internals
const emitter = process.__signal_exit_emitter__;
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
const listener = emitter.listeners('exit').pop();
await new Promise((resolve, reject) => {
@@ -197,49 +199,49 @@ test('cancel method kills the subprocess', t => {
});
test('result.isCanceled is false when spawned.cancel() isn\'t called (success)', async t => {
- const {isCanceled} = await execa('noop');
+ const {isCanceled} = await execa('noop.js');
t.false(isCanceled);
});
test('result.isCanceled is false when spawned.cancel() isn\'t called (failure)', async t => {
- const {isCanceled} = await t.throwsAsync(execa('fail'));
+ const {isCanceled} = await t.throwsAsync(execa('fail.js'));
t.false(isCanceled);
});
test('result.isCanceled is false when spawned.cancel() isn\'t called in sync mode (success)', t => {
- const {isCanceled} = execa.sync('noop');
+ const {isCanceled} = execaSync('noop.js');
t.false(isCanceled);
});
test('result.isCanceled is false when spawned.cancel() isn\'t called in sync mode (failure)', t => {
const {isCanceled} = t.throws(() => {
- execa.sync('fail');
+ execaSync('fail.js');
});
t.false(isCanceled);
});
test('calling cancel method throws an error with message "Command was canceled"', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
subprocess.cancel();
await t.throwsAsync(subprocess, {message: /Command was canceled/});
});
test('error.isCanceled is true when cancel method is used', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
subprocess.cancel();
const {isCanceled} = await t.throwsAsync(subprocess);
t.true(isCanceled);
});
test('error.isCanceled is false when kill method is used', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
subprocess.kill();
const {isCanceled} = await t.throwsAsync(subprocess);
t.false(isCanceled);
});
test('calling cancel method twice should show the same behaviour as calling it once', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
subprocess.cancel();
subprocess.cancel();
const {isCanceled} = await t.throwsAsync(subprocess);
@@ -248,14 +250,14 @@ test('calling cancel method twice should show the same behaviour as calling it o
});
test('calling cancel method on a successfully completed process does not make result.isCanceled true', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
const {isCanceled} = await subprocess;
subprocess.cancel();
t.false(isCanceled);
});
test('calling cancel method on a process which has been killed does not make error.isCanceled true', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
subprocess.kill();
const {isCanceled} = await t.throwsAsync(subprocess);
t.false(isCanceled);
diff --git a/test/node.js b/test/node.js
index 51ca5576e3..acebef121d 100644
--- a/test/node.js
+++ b/test/node.js
@@ -1,16 +1,18 @@
-import path from 'path';
+import path from 'node:path';
+import process from 'node:process';
+import {fileURLToPath} from 'node:url';
import test from 'ava';
import pEvent from 'p-event';
-import execa from '..';
+import {execaNode} from '../index.js';
-process.env.PATH = path.join(__dirname, 'fixtures') + path.delimiter + process.env.PATH;
+process.env.PATH = fileURLToPath(new URL('./fixtures', import.meta.url)) + path.delimiter + process.env.PATH;
async function inspectMacro(t, input) {
const originalArgv = process.execArgv;
process.execArgv = [input, '-e'];
try {
- const subprocess = execa.node('console.log("foo")', {
- reject: false
+ const subprocess = execaNode('console.log("foo")', {
+ reject: false,
});
const {stdout, stderr} = await subprocess;
@@ -23,32 +25,32 @@ async function inspectMacro(t, input) {
}
test('node()', async t => {
- const {exitCode} = await execa.node('test/fixtures/noop');
+ const {exitCode} = await execaNode('test/fixtures/noop.js');
t.is(exitCode, 0);
});
test('node pipe stdout', async t => {
- const {stdout} = await execa.node('test/fixtures/noop', ['foo'], {
- stdout: 'pipe'
+ const {stdout} = await execaNode('test/fixtures/noop.js', ['foo'], {
+ stdout: 'pipe',
});
t.is(stdout, 'foo');
});
test('node correctly use nodePath', async t => {
- const {stdout} = await execa.node(process.platform === 'win32' ? 'hello.cmd' : 'hello.sh', {
+ const {stdout} = await execaNode(process.platform === 'win32' ? 'hello.cmd' : 'hello.sh', {
stdout: 'pipe',
nodePath: process.platform === 'win32' ? 'cmd.exe' : 'bash',
- nodeOptions: process.platform === 'win32' ? ['/c'] : []
+ nodeOptions: process.platform === 'win32' ? ['/c'] : [],
});
t.is(stdout, 'Hello World');
});
test('node pass on nodeOptions', async t => {
- const {stdout} = await execa.node('console.log("foo")', {
+ const {stdout} = await execaNode('console.log("foo")', {
stdout: 'pipe',
- nodeOptions: ['-e']
+ nodeOptions: ['-e'],
});
t.is(stdout, 'foo');
@@ -57,42 +59,44 @@ test('node pass on nodeOptions', async t => {
test.serial(
'node removes --inspect from nodeOptions when defined by parent process',
inspectMacro,
- '--inspect'
+ '--inspect',
);
test.serial(
'node removes --inspect=9222 from nodeOptions when defined by parent process',
inspectMacro,
- '--inspect=9222'
+ '--inspect=9222',
);
test.serial(
'node removes --inspect-brk from nodeOptions when defined by parent process',
inspectMacro,
- '--inspect-brk'
+ '--inspect-brk',
);
test.serial(
'node removes --inspect-brk=9222 from nodeOptions when defined by parent process',
inspectMacro,
- '--inspect-brk=9222'
+ '--inspect-brk=9222',
);
test.serial(
'node should not remove --inspect when passed through nodeOptions',
async t => {
- const {stdout, stderr} = await execa.node('console.log("foo")', {
+ const {stdout, stderr} = await execaNode('console.log("foo")', {
reject: false,
- nodeOptions: ['--inspect', '-e']
+ nodeOptions: ['--inspect', '-e'],
});
t.is(stdout, 'foo');
t.true(stderr.includes('Debugger listening'));
- }
+ },
);
test('node\'s forked script has a communication channel', async t => {
- const subprocess = execa.node('test/fixtures/send');
+ const subprocess = execaNode('test/fixtures/send.js');
+ await pEvent(subprocess, 'message');
+
subprocess.send('ping');
const message = await pEvent(subprocess, 'message');
diff --git a/test/override-promise.js b/test/override-promise.js
index 30c2465c55..4c200e37f4 100644
--- a/test/override-promise.js
+++ b/test/override-promise.js
@@ -1,20 +1,18 @@
-import path from 'path';
+import path from 'node:path';
+import process from 'node:process';
+import {fileURLToPath} from 'node:url';
import test from 'ava';
+// The helper module overrides Promise on import so has to be imported before `execa`.
+// Can't use top-level await (TLA) + `import(…)` since Node.js 12 doesn't support TLA.
+import {restorePromise} from './helpers/override-promise.js';
+// eslint-disable-next-line import/order
+import {execa} from '../index.js';
-process.env.PATH = path.join(__dirname, 'fixtures') + path.delimiter + process.env.PATH;
+restorePromise();
-// Can't use `test.before`, because `ava` needs `Promise`.
-// Can't use `import(…)` either, because `execa` is not an ES Module.
-const nativePromise = Promise;
-global.Promise = class BrokenPromise {
- then() {
- throw new Error('error');
- }
-};
-const execa = require('..');
-global.Promise = nativePromise;
+process.env.PATH = fileURLToPath(new URL('./fixtures', import.meta.url)) + path.delimiter + process.env.PATH;
test('should work with third-party Promise', async t => {
- const {stdout} = await execa('noop', ['foo']);
+ const {stdout} = await execa('noop.js', ['foo']);
t.is(stdout, 'foo');
});
diff --git a/test/promise.js b/test/promise.js
index db9e7a4e4c..9f52892e7c 100644
--- a/test/promise.js
+++ b/test/promise.js
@@ -1,12 +1,13 @@
-import path from 'path';
+import path from 'node:path';
+import process from 'node:process';
+import {fileURLToPath} from 'node:url';
import test from 'ava';
-import execa from '..';
+import {execa} from '../index.js';
-process.env.PATH = path.join(__dirname, 'fixtures') + path.delimiter + process.env.PATH;
+process.env.PATH = fileURLToPath(new URL('./fixtures', import.meta.url)) + path.delimiter + process.env.PATH;
test('promise methods are not enumerable', t => {
- const descriptors = Object.getOwnPropertyDescriptors(execa('noop'));
- // eslint-disable-next-line promise/prefer-await-to-then
+ const descriptors = Object.getOwnPropertyDescriptors(execa('noop.js'));
t.false(descriptors.then.enumerable);
t.false(descriptors.catch.enumerable);
t.false(descriptors.finally.enumerable);
@@ -14,7 +15,7 @@ test('promise methods are not enumerable', t => {
test('finally function is executed on success', async t => {
let isCalled = false;
- const {stdout} = await execa('noop', ['foo']).finally(() => {
+ const {stdout} = await execa('noop.js', ['foo']).finally(() => {
isCalled = true;
});
t.is(isCalled, true);
@@ -23,7 +24,7 @@ test('finally function is executed on success', async t => {
test('finally function is executed on failure', async t => {
let isError = false;
- const {stdout, stderr} = await t.throwsAsync(execa('exit', ['2']).finally(() => {
+ const {stdout, stderr} = await t.throwsAsync(execa('exit.js', ['2']).finally(() => {
isError = true;
}));
t.is(isError, true);
@@ -32,14 +33,14 @@ test('finally function is executed on failure', async t => {
});
test('throw in finally function bubbles up on success', async t => {
- const {message} = await t.throwsAsync(execa('noop', ['foo']).finally(() => {
+ const {message} = await t.throwsAsync(execa('noop.js', ['foo']).finally(() => {
throw new Error('called');
}));
t.is(message, 'called');
});
test('throw in finally bubbles up on error', async t => {
- const {message} = await t.throwsAsync(execa('exit', ['2']).finally(() => {
+ const {message} = await t.throwsAsync(execa('exit.js', ['2']).finally(() => {
throw new Error('called');
}));
t.is(message, 'called');
diff --git a/test/stdio.js b/test/stdio.js
index 3f849e78b0..9d9e5547ee 100644
--- a/test/stdio.js
+++ b/test/stdio.js
@@ -1,12 +1,12 @@
-import {inspect} from 'util';
+import {inspect} from 'node:util';
import test from 'ava';
-import normalizeStdio from '../lib/stdio';
+import {normalizeStdio, normalizeStdioNode} from '../lib/stdio.js';
const macro = (t, input, expected, func) => {
if (expected instanceof Error) {
t.throws(() => {
normalizeStdio(input);
- }, expected.message);
+ }, {message: expected.message});
return;
}
@@ -47,8 +47,8 @@ test(stdioMacro, {stdin: 'inherit', stdio: ['pipe']}, new Error('It\'s not possi
test(stdioMacro, {stdin: 'inherit', stdio: [undefined, 'pipe']}, new Error('It\'s not possible to provide `stdio` in combination with one of `stdin`, `stdout`, `stderr`'));
test(stdioMacro, {stdin: 0, stdio: 'pipe'}, new Error('It\'s not possible to provide `stdio` in combination with one of `stdin`, `stdout`, `stderr`'));
-const forkMacro = (...args) => macro(...args, normalizeStdio.node);
-forkMacro.title = macroTitle('execa.fork()');
+const forkMacro = (...args) => macro(...args, normalizeStdioNode);
+forkMacro.title = macroTitle('execaNode()');
test(forkMacro, undefined, [undefined, undefined, undefined, 'ipc']);
test(forkMacro, {stdio: 'ignore'}, ['ignore', 'ignore', 'ignore', 'ipc']);
diff --git a/test/stream.js b/test/stream.js
index 08cc53958c..7788ae99dc 100644
--- a/test/stream.js
+++ b/test/stream.js
@@ -1,62 +1,65 @@
-import path from 'path';
-import fs from 'fs';
-import Stream from 'stream';
+import {Buffer} from 'node:buffer';
+import path from 'node:path';
+import process from 'node:process';
+import fs from 'node:fs';
+import {fileURLToPath} from 'node:url';
+import Stream from 'node:stream';
import test from 'ava';
import getStream from 'get-stream';
import tempfile from 'tempfile';
-import execa from '..';
+import {execa, execaSync} from '../index.js';
-process.env.PATH = path.join(__dirname, 'fixtures') + path.delimiter + process.env.PATH;
+process.env.PATH = fileURLToPath(new URL('./fixtures', import.meta.url)) + path.delimiter + process.env.PATH;
test('buffer', async t => {
- const {stdout} = await execa('noop', ['foo'], {encoding: null});
+ const {stdout} = await execa('noop.js', ['foo'], {encoding: null});
t.true(Buffer.isBuffer(stdout));
t.is(stdout.toString(), 'foo');
});
test('pass `stdout` to a file descriptor', async t => {
const file = tempfile('.txt');
- await execa('test/fixtures/noop', ['foo bar'], {stdout: fs.openSync(file, 'w')});
+ await execa('test/fixtures/noop.js', ['foo bar'], {stdout: fs.openSync(file, 'w')});
t.is(fs.readFileSync(file, 'utf8'), 'foo bar\n');
});
test('pass `stderr` to a file descriptor', async t => {
const file = tempfile('.txt');
- await execa('test/fixtures/noop-err', ['foo bar'], {stderr: fs.openSync(file, 'w')});
+ await execa('test/fixtures/noop-err.js', ['foo bar'], {stderr: fs.openSync(file, 'w')});
t.is(fs.readFileSync(file, 'utf8'), 'foo bar\n');
});
test.serial('result.all shows both `stdout` and `stderr` intermixed', async t => {
- const {all} = await execa('noop-132', {all: true});
+ const {all} = await execa('noop-132.js', {all: true});
t.is(all, '132');
});
test('result.all is undefined unless opts.all is true', async t => {
- const {all} = await execa('noop');
+ const {all} = await execa('noop.js');
t.is(all, undefined);
});
test('stdout/stderr/all are undefined if ignored', async t => {
- const {stdout, stderr, all} = await execa('noop', {stdio: 'ignore', all: true});
+ const {stdout, stderr, all} = await execa('noop.js', {stdio: 'ignore', all: true});
t.is(stdout, undefined);
t.is(stderr, undefined);
t.is(all, undefined);
});
test('stdout/stderr/all are undefined if ignored in sync mode', t => {
- const {stdout, stderr, all} = execa.sync('noop', {stdio: 'ignore', all: true});
+ const {stdout, stderr, all} = execaSync('noop.js', {stdio: 'ignore', all: true});
t.is(stdout, undefined);
t.is(stderr, undefined);
t.is(all, undefined);
});
test('input option can be a String', async t => {
- const {stdout} = await execa('stdin', {input: 'foobar'});
+ const {stdout} = await execa('stdin.js', {input: 'foobar'});
t.is(stdout, 'foobar');
});
test('input option can be a Buffer', async t => {
- const {stdout} = await execa('stdin', {input: 'testing12'});
+ const {stdout} = await execa('stdin.js', {input: 'testing12'});
t.is(stdout, 'testing12');
});
@@ -64,30 +67,30 @@ test('input can be a Stream', async t => {
const stream = new Stream.PassThrough();
stream.write('howdy');
stream.end();
- const {stdout} = await execa('stdin', {input: stream});
+ const {stdout} = await execa('stdin.js', {input: stream});
t.is(stdout, 'howdy');
});
test('you can write to child.stdin', async t => {
- const subprocess = execa('stdin');
+ const subprocess = execa('stdin.js');
subprocess.stdin.end('unicorns');
t.is((await subprocess).stdout, 'unicorns');
});
test('input option can be a String - sync', t => {
- const {stdout} = execa.sync('stdin', {input: 'foobar'});
+ const {stdout} = execaSync('stdin.js', {input: 'foobar'});
t.is(stdout, 'foobar');
});
test('input option can be a Buffer - sync', t => {
- const {stdout} = execa.sync('stdin', {input: Buffer.from('testing12', 'utf8')});
+ const {stdout} = execaSync('stdin.js', {input: Buffer.from('testing12', 'utf8')});
t.is(stdout, 'testing12');
});
test('opts.stdout:ignore - stdout will not collect data', async t => {
- const {stdout} = await execa('stdin', {
+ const {stdout} = await execa('stdin.js', {
input: 'hello',
- stdio: [undefined, 'ignore', undefined]
+ stdio: [undefined, 'ignore', undefined],
});
t.is(stdout, undefined);
});
@@ -95,31 +98,31 @@ test('opts.stdout:ignore - stdout will not collect data', async t => {
test('helpful error trying to provide an input stream in sync mode', t => {
t.throws(
() => {
- execa.sync('stdin', {input: new Stream.PassThrough()});
+ execaSync('stdin.js', {input: new Stream.PassThrough()});
},
- /The `input` option cannot be a stream in sync mode/
+ {message: /The `input` option cannot be a stream in sync mode/},
);
});
test('maxBuffer affects stdout', async t => {
- await t.notThrowsAsync(execa('max-buffer', ['stdout', '10'], {maxBuffer: 10}));
- const {stdout, all} = await t.throwsAsync(execa('max-buffer', ['stdout', '11'], {maxBuffer: 10, all: true}), /max-buffer stdout/);
+ await t.notThrowsAsync(execa('max-buffer.js', ['stdout', '10'], {maxBuffer: 10}));
+ const {stdout, all} = await t.throwsAsync(execa('max-buffer.js', ['stdout', '11'], {maxBuffer: 10, all: true}), {message: /max-buffer.js stdout/});
t.is(stdout, '.'.repeat(10));
t.is(all, '.'.repeat(10));
});
test('maxBuffer affects stderr', async t => {
- await t.notThrowsAsync(execa('max-buffer', ['stderr', '10'], {maxBuffer: 10}));
- const {stderr, all} = await t.throwsAsync(execa('max-buffer', ['stderr', '11'], {maxBuffer: 10, all: true}), /max-buffer stderr/);
+ await t.notThrowsAsync(execa('max-buffer.js', ['stderr', '10'], {maxBuffer: 10}));
+ const {stderr, all} = await t.throwsAsync(execa('max-buffer.js', ['stderr', '11'], {maxBuffer: 10, all: true}), {message: /max-buffer.js stderr/});
t.is(stderr, '.'.repeat(10));
t.is(all, '.'.repeat(10));
});
test('do not buffer stdout when `buffer` set to `false`', async t => {
- const promise = execa('max-buffer', ['stdout', '10'], {buffer: false});
+ const promise = execa('max-buffer.js', ['stdout', '10'], {buffer: false});
const [result, stdout] = await Promise.all([
promise,
- getStream(promise.stdout)
+ getStream(promise.stdout),
]);
t.is(result.stdout, undefined);
@@ -127,10 +130,10 @@ test('do not buffer stdout when `buffer` set to `false`', async t => {
});
test('do not buffer stderr when `buffer` set to `false`', async t => {
- const promise = execa('max-buffer', ['stderr', '10'], {buffer: false});
+ const promise = execa('max-buffer.js', ['stderr', '10'], {buffer: false});
const [result, stderr] = await Promise.all([
promise,
- getStream(promise.stderr)
+ getStream(promise.stderr),
]);
t.is(result.stderr, undefined);
@@ -138,44 +141,44 @@ test('do not buffer stderr when `buffer` set to `false`', async t => {
});
test('do not buffer when streaming', async t => {
- const {stdout} = execa('max-buffer', ['stdout', '21'], {maxBuffer: 10});
+ const {stdout} = execa('max-buffer.js', ['stdout', '21'], {maxBuffer: 10});
const result = await getStream(stdout);
t.is(result, '....................\n');
});
test('buffer: false > promise resolves', async t => {
- await t.notThrowsAsync(execa('noop', {buffer: false}));
+ await t.notThrowsAsync(execa('noop.js', {buffer: false}));
});
test('buffer: false > promise resolves when output is big but is not pipable', async t => {
- await t.notThrowsAsync(execa('max-buffer', {buffer: false, stdout: 'ignore'}));
+ await t.notThrowsAsync(execa('max-buffer.js', {buffer: false, stdout: 'ignore'}));
});
test('buffer: false > promise resolves when output is big and is read', async t => {
- const subprocess = execa('max-buffer', {buffer: false});
+ const subprocess = execa('max-buffer.js', {buffer: false});
subprocess.stdout.resume();
subprocess.stderr.resume();
await t.notThrowsAsync(subprocess);
});
test('buffer: false > promise resolves when output is big and "all" is used and is read', async t => {
- const subprocess = execa('max-buffer', {buffer: false, all: true});
+ const subprocess = execa('max-buffer.js', {buffer: false, all: true});
subprocess.all.resume();
await t.notThrowsAsync(subprocess);
});
test('buffer: false > promise rejects when process returns non-zero', async t => {
- const subprocess = execa('fail', {buffer: false});
+ const subprocess = execa('fail.js', {buffer: false});
const {exitCode} = await t.throwsAsync(subprocess);
t.is(exitCode, 2);
});
test('can use all: true with stdout: ignore', async t => {
- await t.notThrowsAsync(execa('max-buffer', {buffer: false, stdout: 'ignore', all: true}));
+ await t.notThrowsAsync(execa('max-buffer.js', {buffer: false, stdout: 'ignore', all: true}));
});
test('can use all: true with stderr: ignore', async t => {
- await t.notThrowsAsync(execa('max-buffer', ['stderr'], {buffer: false, stderr: 'ignore', all: true}));
+ await t.notThrowsAsync(execa('max-buffer.js', ['stderr'], {buffer: false, stderr: 'ignore', all: true}));
});
const BUFFER_TIMEOUT = 1e3;
@@ -183,12 +186,12 @@ const BUFFER_TIMEOUT = 1e3;
// On Unix (not Windows), a process won't exit if stdout has not been read.
if (process.platform !== 'win32') {
test.serial('buffer: false > promise does not resolve when output is big and is not read', async t => {
- const {timedOut} = await t.throwsAsync(execa('max-buffer', {buffer: false, timeout: BUFFER_TIMEOUT}));
+ const {timedOut} = await t.throwsAsync(execa('max-buffer.js', {buffer: false, timeout: BUFFER_TIMEOUT}));
t.true(timedOut);
});
test.serial('buffer: false > promise does not resolve when output is big and "all" is used but not read', async t => {
- const subprocess = execa('max-buffer', {buffer: false, all: true, timeout: BUFFER_TIMEOUT});
+ const subprocess = execa('max-buffer.js', {buffer: false, all: true, timeout: BUFFER_TIMEOUT});
subprocess.stdout.resume();
subprocess.stderr.resume();
const {timedOut} = await t.throwsAsync(subprocess);
diff --git a/test/test.js b/test/test.js
index ba59d7c37d..23449f52b8 100644
--- a/test/test.js
+++ b/test/test.js
@@ -1,16 +1,18 @@
-import path from 'path';
+import path from 'node:path';
+import process from 'node:process';
+import {fileURLToPath} from 'node:url';
import test from 'ava';
import isRunning from 'is-running';
import getNode from 'get-node';
-import execa from '..';
+import {execa, execaSync} from '../index.js';
-process.env.PATH = path.join(__dirname, 'fixtures') + path.delimiter + process.env.PATH;
+process.env.PATH = fileURLToPath(new URL('./fixtures', import.meta.url)) + path.delimiter + process.env.PATH;
process.env.FOO = 'foo';
const ENOENT_REGEXP = process.platform === 'win32' ? /failed with exit code 1/ : /spawn.* ENOENT/;
test('execa()', async t => {
- const {stdout} = await execa('noop', ['foo']);
+ const {stdout} = await execa('noop.js', ['foo']);
t.is(stdout, 'foo');
});
@@ -26,50 +28,50 @@ if (process.platform === 'win32') {
});
}
-test('execa.sync()', t => {
- const {stdout} = execa.sync('noop', ['foo']);
+test('execaSync()', t => {
+ const {stdout} = execaSync('noop.js', ['foo']);
t.is(stdout, 'foo');
});
-test('execa.sync() throws error if written to stderr', t => {
+test('execaSync() throws error if written to stderr', t => {
t.throws(() => {
- execa.sync('foo');
- }, ENOENT_REGEXP);
+ execaSync('foo');
+ }, {message: ENOENT_REGEXP});
});
test('skip throwing when using reject option', async t => {
- const {exitCode} = await execa('fail', {reject: false});
+ const {exitCode} = await execa('fail.js', {reject: false});
t.is(exitCode, 2);
});
test('skip throwing when using reject option in sync mode', t => {
- const {exitCode} = execa.sync('fail', {reject: false});
+ const {exitCode} = execaSync('fail.js', {reject: false});
t.is(exitCode, 2);
});
test('stripFinalNewline: true', async t => {
- const {stdout} = await execa('noop', ['foo']);
+ const {stdout} = await execa('noop.js', ['foo']);
t.is(stdout, 'foo');
});
test('stripFinalNewline: false', async t => {
- const {stdout} = await execa('noop', ['foo'], {stripFinalNewline: false});
+ const {stdout} = await execa('noop.js', ['foo'], {stripFinalNewline: false});
t.is(stdout, 'foo\n');
});
test('stripFinalNewline on failure', async t => {
- const {stderr} = await t.throwsAsync(execa('noop-throw', ['foo'], {stripFinalNewline: true}));
+ const {stderr} = await t.throwsAsync(execa('noop-throw.js', ['foo'], {stripFinalNewline: true}));
t.is(stderr, 'foo');
});
test('stripFinalNewline in sync mode', t => {
- const {stdout} = execa.sync('noop', ['foo'], {stripFinalNewline: true});
+ const {stdout} = execaSync('noop.js', ['foo'], {stripFinalNewline: true});
t.is(stdout, 'foo');
});
test('stripFinalNewline in sync mode on failure', t => {
const {stderr} = t.throws(() => {
- execa.sync('noop-throw', ['foo'], {stripFinalNewline: true});
+ execaSync('noop-throw.js', ['foo'], {stripFinalNewline: true});
});
t.is(stderr, 'foo');
});
@@ -79,11 +81,11 @@ test('preferLocal: true', async t => {
});
test('preferLocal: false', async t => {
- await t.throwsAsync(execa('ava', ['--version'], {preferLocal: false, env: {Path: '', PATH: ''}}), ENOENT_REGEXP);
+ await t.throwsAsync(execa('ava', ['--version'], {preferLocal: false, env: {Path: '', PATH: ''}}), {message: ENOENT_REGEXP});
});
test('preferLocal: undefined', async t => {
- await t.throwsAsync(execa('ava', ['--version'], {env: {Path: '', PATH: ''}}), ENOENT_REGEXP);
+ await t.throwsAsync(execa('ava', ['--version'], {env: {Path: '', PATH: ''}}), {message: ENOENT_REGEXP});
});
test('localDir option', async t => {
@@ -100,30 +102,30 @@ test('execPath option', async t => {
});
test('stdin errors are handled', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
subprocess.stdin.emit('error', new Error('test'));
- await t.throwsAsync(subprocess, /test/);
+ await t.throwsAsync(subprocess, {message: /test/});
});
test('child process errors are handled', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
subprocess.emit('error', new Error('test'));
- await t.throwsAsync(subprocess, /test/);
+ await t.throwsAsync(subprocess, {message: /test/});
});
test('child process errors rejects promise right away', async t => {
- const subprocess = execa('noop');
+ const subprocess = execa('noop.js');
subprocess.emit('error', new Error('test'));
- await t.throwsAsync(subprocess, /test/);
+ await t.throwsAsync(subprocess, {message: /test/});
});
test('execa() returns a promise with pid', t => {
- const {pid} = execa('noop', ['foo']);
+ const {pid} = execa('noop.js', ['foo']);
t.is(typeof pid, 'number');
});
test('child_process.spawn() propagated errors have correct shape', t => {
- const subprocess = execa('noop', {uid: -1});
+ const subprocess = execa('noop.js', {uid: -1});
t.notThrows(() => {
subprocess.catch(() => {});
subprocess.unref();
@@ -132,39 +134,39 @@ test('child_process.spawn() propagated errors have correct shape', t => {
});
test('child_process.spawn() errors are propagated', async t => {
- const {failed} = await t.throwsAsync(execa('noop', {uid: -1}));
+ const {failed} = await t.throwsAsync(execa('noop.js', {uid: -1}));
t.true(failed);
});
test('child_process.spawnSync() errors are propagated with a correct shape', t => {
const {failed} = t.throws(() => {
- execa.sync('noop', {timeout: -1});
+ execaSync('noop.js', {timeout: -1});
});
t.true(failed);
});
test('do not try to consume streams twice', async t => {
- const subprocess = execa('noop', ['foo']);
+ const subprocess = execa('noop.js', ['foo']);
t.is((await subprocess).stdout, 'foo');
t.is((await subprocess).stdout, 'foo');
});
test('use relative path with \'..\' chars', async t => {
- const pathViaParentDir = path.join('..', path.basename(path.dirname(__dirname)), 'test', 'fixtures', 'noop');
+ const pathViaParentDir = path.join('..', path.basename(fileURLToPath(new URL('..', import.meta.url))), 'test', 'fixtures', 'noop.js');
const {stdout} = await execa(pathViaParentDir, ['foo']);
t.is(stdout, 'foo');
});
if (process.platform !== 'win32') {
test('execa() rejects if running non-executable', async t => {
- const subprocess = execa('non-executable');
+ const subprocess = execa('non-executable.js');
await t.throwsAsync(subprocess);
});
test('execa() rejects with correct error and doesn\'t throw if running non-executable with input', async t => {
// On Node <12.6.0, `EACCESS` is emitted on `childProcess`.
// On Node >=12.6.0, `EPIPE` is emitted on `childProcess.stdin`.
- await t.throwsAsync(execa('non-executable', {input: 'Hey!'}), /EACCES|EPIPE/);
+ await t.throwsAsync(execa('non-executable.js', {input: 'Hey!'}), {message: /EACCES|EPIPE/});
});
}
@@ -182,28 +184,28 @@ if (process.platform !== 'win32') {
}
test('use environment variables by default', async t => {
- const {stdout} = await execa('environment');
+ const {stdout} = await execa('environment.js');
t.deepEqual(stdout.split('\n'), ['foo', 'undefined']);
});
test('extend environment variables by default', async t => {
- const {stdout} = await execa('environment', [], {env: {BAR: 'bar'}});
+ const {stdout} = await execa('environment.js', [], {env: {BAR: 'bar'}});
t.deepEqual(stdout.split('\n'), ['foo', 'bar']);
});
test('do not extend environment with `extendEnv: false`', async t => {
- const {stdout} = await execa('environment', [], {env: {BAR: 'bar', PATH: process.env.PATH}, extendEnv: false});
+ const {stdout} = await execa('environment.js', [], {env: {BAR: 'bar', PATH: process.env.PATH}, extendEnv: false});
t.deepEqual(stdout.split('\n'), ['undefined', 'bar']);
});
test('can use `options.shell: true`', async t => {
- const {stdout} = await execa('node test/fixtures/noop foo', {shell: true});
+ const {stdout} = await execa('node test/fixtures/noop.js foo', {shell: true});
t.is(stdout, 'foo');
});
test('can use `options.shell: string`', async t => {
const shell = process.platform === 'win32' ? 'cmd.exe' : '/bin/bash';
- const {stdout} = await execa('node test/fixtures/noop foo', {shell});
+ const {stdout} = await execa('node test/fixtures/noop.js foo', {shell});
t.is(stdout, 'foo');
});
@@ -216,7 +218,7 @@ test('use extend environment with `extendEnv: true` and `shell: true`', async t
});
test('detach child process', async t => {
- const {stdout} = await execa('detach');
+ const {stdout} = await execa('detach.js');
const pid = Number(stdout);
t.true(Number.isInteger(pid));
t.true(isRunning(pid));