diff --git a/index.d.ts b/index.d.ts
new file mode 100644
index 0000000000..6f2a7b6f3b
--- /dev/null
+++ b/index.d.ts
@@ -0,0 +1,494 @@
+///
+import {ChildProcess} from 'child_process';
+import {Stream, Readable as ReadableStream} from 'stream';
+
+export type StdioOption =
+ | 'pipe'
+ | 'ipc'
+ | 'ignore'
+ | 'inherit'
+ | Stream
+ | number
+ | null
+ | undefined;
+
+export interface CommonOptions {
+ /**
+ Current working directory of the child process.
+
+ @default process.cwd()
+ */
+ readonly cwd?: string;
+
+ /**
+ Environment key-value pairs. Extends automatically from `process.env`. Set `extendEnv` to `false` if you don't want this.
+
+ @default process.env
+ */
+ readonly env?: NodeJS.ProcessEnv;
+
+ /**
+ Set to `false` if you don't want to extend the environment variables when providing the `env` property.
+
+ @default true
+ */
+ readonly extendEnv?: boolean;
+
+ /**
+ 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;
+
+ /**
+ Child's [stdio](https://nodejs.org/api/child_process.html#child_process_options_stdio) configuration.
+
+ @default 'pipe'
+ */
+ readonly stdio?: 'pipe' | 'ignore' | 'inherit' | ReadonlyArray;
+
+ /**
+ 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 false
+ */
+ readonly detached?: boolean;
+
+ /**
+ Sets the user identity of the process.
+ */
+ readonly uid?: number;
+
+ /**
+ Sets the group identity of the process.
+ */
+ readonly gid?: number;
+
+ /**
+ If `true`, runs `command` inside of a shell. Uses `/bin/sh` on UNIX and `cmd.exe` on Windows. A different shell can be specified as a string. The shell should understand the `-c` switch on UNIX or `/d /s /c` on Windows.
+
+ @default false
+ */
+ readonly shell?: boolean | string;
+
+ /**
+ Strip the final [newline character](https://en.wikipedia.org/wiki/Newline) from the output.
+
+ @default true
+ */
+ readonly stripFinalNewline?: boolean;
+
+ /**
+ Prefer locally installed binaries when looking for a binary to execute.
+
+ If you `$ npm install foo`, you can then `execa('foo')`.
+
+ @default true
+ */
+ readonly preferLocal?: boolean;
+
+ /**
+ Preferred path to find locally installed binaries in (use with `preferLocal`).
+
+ @default process.cwd()
+ */
+ readonly localDir?: string;
+
+ /**
+ Setting this to `false` resolves the promise with the error instead of rejecting it.
+
+ @default true
+ */
+ readonly reject?: boolean;
+
+ /**
+ Keep track of the spawned process and `kill` it when the parent process exits.
+
+ @default true
+ */
+ readonly cleanup?: boolean;
+
+ /**
+ 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 'utf8'
+ */
+ readonly encoding?: EncodingType;
+
+ /**
+ 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;
+
+ /**
+ Buffer the output from the spawned process. When buffering is disabled you must consume the output of the `stdout` and `stderr` streams because the promise will not be resolved/rejected until they have completed.
+
+ @default true
+ */
+ readonly buffer?: boolean;
+
+ /**
+ Largest amount of data in bytes allowed on `stdout` or `stderr`. Default: 10MB.
+
+ @default 10000000
+ */
+ readonly maxBuffer?: number;
+
+ /**
+ Signal value to be used when the spawned process will be killed.
+
+ @default 'SIGTERM'
+ */
+ readonly killSignal?: string | number;
+
+ /**
+ 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;
+
+ /**
+ 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;
+
+ /**
+ Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).
+
+ @default 'pipe'
+ */
+ readonly stderr?: StdioOption;
+
+ /**
+ 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 false
+ */
+ readonly windowsVerbatimArguments?: boolean;
+}
+
+export interface Options
+ extends CommonOptions {
+ /**
+ Write some input to the `stdin` of your binary.
+ */
+ readonly input?: string | Buffer | ReadableStream;
+}
+
+export interface SyncOptions
+ extends CommonOptions {
+ /**
+ Write some input to the `stdin` of your binary.
+ */
+ readonly input?: string | Buffer;
+}
+
+export interface ExecaReturnBase {
+ /**
+ The numeric exit code of the process that was run.
+ */
+ exitCode: number;
+
+ /**
+ The textual exit code of the process that was run.
+ */
+ exitCodeName: string;
+
+ /**
+ 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;
+
+ /**
+ The signal that was used to terminate the process.
+ */
+ signal?: string;
+
+ /**
+ The command that was run.
+ */
+ cmd: string;
+
+ /**
+ Whether the process timed out.
+ */
+ timedOut: boolean;
+
+ /**
+ Whether the process was killed.
+ */
+ killed: boolean;
+}
+
+export interface ExecaSyncReturnValue
+ extends ExecaReturnBase {
+ /**
+ The exit code of the process that was run.
+ */
+ code: number;
+}
+
+export interface ExecaReturnValue
+ extends ExecaSyncReturnValue {
+ /**
+ The output of the process with `stdout` and `stderr` interleaved.
+ */
+ all: StdOutErrType;
+
+ /**
+ Whether the process was canceled.
+ */
+ isCanceled: boolean;
+}
+
+export interface ExecaSyncError
+ extends Error,
+ ExecaReturnBase {
+ /**
+ The error message.
+ */
+ message: string;
+
+ /**
+ The exit code (either numeric or textual) of the process that was run.
+ */
+ code: number | string;
+}
+
+export interface ExecaError
+ extends ExecaSyncError {
+ /**
+ The output of the process with `stdout` and `stderr` interleaved.
+ */
+ all: StdOutErrType;
+
+ /**
+ Whether the process was canceled.
+ */
+ isCanceled: boolean;
+}
+
+export interface ExecaChildPromise {
+ catch(
+ onRejected?:
+ | ((
+ reason: ExecaError
+ ) => ResultType | PromiseLike)
+ | null
+ ): Promise | ResultType>;
+
+ /**
+ Cancel the subprocess.
+
+ Causes the promise to reject an error with a `.isCanceled = true` property, provided the process gets canceled. The process will not be canceled if it has already exited.
+ */
+ cancel(): void;
+}
+
+export type ExecaChildProcess = ChildProcess &
+ ExecaChildPromise &
+ Promise>;
+
+declare const execa: {
+ /**
+ 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';
+
+ (async () => {
+ const {stdout} = await execa('echo', ['unicorns']);
+ console.log(stdout);
+ //=> 'unicorns'
+
+ // Cancelling a spawned process
+ const subprocess = execa('node');
+ setTimeout(() => { spawned.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);
+ ```
+ */
+ (
+ file: string,
+ arguments?: ReadonlyArray,
+ options?: Options
+ ): ExecaChildProcess;
+ (
+ file: string,
+ arguments?: ReadonlyArray,
+ options?: Options
+ ): ExecaChildProcess;
+ (file: string, options?: Options): ExecaChildProcess;
+ (file: string, options?: Options): ExecaChildProcess;
+
+ /**
+ Same as `execa()`, but returns only `stdout`.
+
+ @param file - The program/script to execute.
+ @param arguments - Arguments to pass to `file` on execution.
+ @returns The contents of the executed process' `stdout`.
+ */
+ stdout(
+ file: string,
+ arguments?: ReadonlyArray,
+ options?: Options
+ ): Promise;
+ stdout(
+ file: string,
+ arguments?: ReadonlyArray,
+ options?: Options
+ ): Promise;
+ stdout(file: string, options?: Options): Promise;
+ stdout(file: string, options?: Options): Promise;
+
+ /**
+ Same as `execa()`, but returns only `stderr`.
+
+ @param file - The program/script to execute.
+ @param arguments - Arguments to pass to `file` on execution.
+ @returns The contents of the executed process' `stderr`.
+ */
+ stderr(
+ file: string,
+ arguments?: ReadonlyArray,
+ options?: Options
+ ): Promise;
+ stderr(
+ file: string,
+ arguments?: ReadonlyArray,
+ options?: Options
+ ): Promise;
+ stderr(file: string, options?: Options): Promise;
+ stderr(file: string, options?: Options): Promise;
+
+ /**
+ Execute a command through the system shell.
+
+ Prefer `execa()` whenever possible, as it's both faster and safer.
+
+ @param command - The command to execute.
+ @returns A [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess).
+
+ @example
+ ```
+ import execa from 'execa';
+
+ (async => {
+ // Run a shell command
+ const {stdout} = await execa.shell('echo unicorns');
+ //=> 'unicorns'
+
+ // Catching an error
+ try {
+ await execa.shell('exit 3');
+ } catch (error) {
+ console.log(error);
+ //{
+ // message: 'Command failed with exit code 3 (ESRCH): exit 3',
+ // code: 3,
+ // exitCode: 3,
+ // exitCodeName: 'ESRCH',
+ // stdout: '',
+ // stderr: '',
+ // all: '',
+ // failed: true,
+ // cmd: 'exit 3',
+ // timedOut: false,
+ // killed: false
+ //}
+ }
+ })();
+ ```
+ */
+ shell(command: string, options?: Options): ExecaChildProcess;
+ shell(command: 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 The same result object as [`child_process.spawnSync`](https://nodejs.org/api/child_process.html#child_process_child_process_spawnsync_command_args_options).
+ */
+ sync(
+ file: string,
+ arguments?: ReadonlyArray,
+ options?: SyncOptions
+ ): ExecaSyncReturnValue;
+ sync(
+ file: string,
+ arguments?: ReadonlyArray,
+ options?: SyncOptions
+ ): ExecaSyncReturnValue;
+ sync(file: string, options?: SyncOptions): ExecaSyncReturnValue;
+ sync(file: string, options?: SyncOptions): ExecaSyncReturnValue;
+
+ /**
+ Execute a command synchronously through the system shell.
+
+ This method throws an `Error` if the command fails.
+
+ @param command - The command to execute.
+ @returns The same result object as [`child_process.spawnSync`](https://nodejs.org/api/child_process.html#child_process_child_process_spawnsync_command_args_options).
+
+ @example
+ ```
+ import execa from 'execa';
+
+ try {
+ execa.shellSync('exit 3');
+ } catch (error) {
+ console.log(error);
+ //{
+ // message: 'Command failed with exit code 3 (ESRCH): exit 3',
+ // code: 3,
+ // exitCode: 3,
+ // exitCodeName: 'ESRCH',
+ // stdout: '',
+ // stderr: '',
+ // failed: true,
+ // cmd: 'exit 3',
+ // timedOut: false
+ //}
+ }
+ ```
+ */
+ shellSync(command: string, options?: Options): ExecaSyncReturnValue;
+ shellSync(
+ command: string,
+ options?: Options
+ ): ExecaSyncReturnValue;
+};
+
+export default execa;
diff --git a/index.js b/index.js
index f5a9ea8651..3266a0771c 100644
--- a/index.js
+++ b/index.js
@@ -225,7 +225,7 @@ function joinCommand(command, args) {
return joinedCommand;
}
-module.exports = (command, args, options) => {
+const execa = (command, args, options) => {
const parsed = handleArgs(command, args, options);
const {encoding, buffer, maxBuffer} = parsed.options;
const joinedCommand = joinCommand(command, args);
@@ -374,19 +374,22 @@ module.exports = (command, args, options) => {
return spawned;
};
+module.exports = execa;
+module.exports.default = execa;
+
// TODO: set `stderr: 'ignore'` when that option is implemented
module.exports.stdout = async (...args) => {
- const {stdout} = await module.exports(...args);
+ const {stdout} = await execa(...args);
return stdout;
};
// TODO: set `stdout: 'ignore'` when that option is implemented
module.exports.stderr = async (...args) => {
- const {stderr} = await module.exports(...args);
+ const {stderr} = await execa(...args);
return stderr;
};
-module.exports.shell = (command, options) => handleShell(module.exports, command, options);
+module.exports.shell = (command, options) => handleShell(execa, command, options);
module.exports.sync = (command, args, options) => {
const parsed = handleArgs(command, args, options);
@@ -424,4 +427,4 @@ module.exports.sync = (command, args, options) => {
};
};
-module.exports.shellSync = (command, options) => handleShell(module.exports.sync, command, options);
+module.exports.shellSync = (command, options) => handleShell(execa.sync, command, options);
diff --git a/index.test-d.ts b/index.test-d.ts
new file mode 100644
index 0000000000..3e6eb23e07
--- /dev/null
+++ b/index.test-d.ts
@@ -0,0 +1,162 @@
+import {expectType, expectError} from 'tsd';
+import execa, {
+ ExecaReturnValue,
+ ExecaChildProcess,
+ ExecaError,
+ ExecaSyncReturnValue,
+ ExecaSyncError
+} from '.';
+
+try {
+ const execaPromise = execa('unicorns');
+ execaPromise.cancel();
+
+ const unicornsResult = await execaPromise;
+ expectType(unicornsResult.cmd);
+ expectType(unicornsResult.code);
+ expectType(unicornsResult.failed);
+ expectType(unicornsResult.killed);
+ expectType(unicornsResult.signal);
+ expectType(unicornsResult.stderr);
+ expectType(unicornsResult.stdout);
+ expectType(unicornsResult.all);
+ expectType(unicornsResult.timedOut);
+ expectType(unicornsResult.isCanceled);
+} catch (error) {
+ const execaError: ExecaError = error;
+
+ expectType(execaError.message);
+ expectType(execaError.code);
+ expectType(execaError.all);
+ expectType(execaError.isCanceled);
+}
+
+try {
+ const unicornsResult = execa.sync('unicorns');
+ expectType(unicornsResult.cmd);
+ expectType(unicornsResult.code);
+ expectType(unicornsResult.failed);
+ expectType(unicornsResult.killed);
+ expectType(unicornsResult.signal);
+ expectType(unicornsResult.stderr);
+ expectType(unicornsResult.stdout);
+ expectType(unicornsResult.timedOut);
+ expectError(unicornsResult.all);
+ expectError(unicornsResult.isCanceled);
+} catch (error) {
+ const execaError: ExecaSyncError = error;
+
+ expectType(execaError.message);
+ expectType(execaError.code);
+ expectError(execaError.all);
+ expectError(execaError.isCanceled);
+}
+
+execa('unicorns', {cwd: '.'});
+execa('unicorns', {env: {PATH: ''}});
+execa('unicorns', {extendEnv: false});
+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, null, undefined]
+});
+execa('unicorns', {detached: true});
+execa('unicorns', {uid: 0});
+execa('unicorns', {gid: 0});
+execa('unicorns', {shell: true});
+execa('unicorns', {shell: '/bin/sh'});
+execa('unicorns', {stripFinalNewline: false});
+execa('unicorns', {preferLocal: false});
+execa('unicorns', {localDir: '.'});
+execa('unicorns', {reject: false});
+execa('unicorns', {cleanup: false});
+execa('unicorns', {timeout: 1000});
+execa('unicorns', {buffer: false});
+execa('unicorns', {maxBuffer: 1000});
+execa('unicorns', {killSignal: 'SIGTERM'});
+execa('unicorns', {killSignal: 9});
+execa('unicorns', {stdin: 'pipe'});
+execa('unicorns', {stdin: 'ipc'});
+execa('unicorns', {stdin: 'ignore'});
+execa('unicorns', {stdin: 'inherit'});
+execa('unicorns', {stdin: process.stdin});
+execa('unicorns', {stdin: 1});
+execa('unicorns', {stdin: null});
+execa('unicorns', {stdin: undefined});
+execa('unicorns', {stderr: 'pipe'});
+execa('unicorns', {stderr: 'ipc'});
+execa('unicorns', {stderr: 'ignore'});
+execa('unicorns', {stderr: 'inherit'});
+execa('unicorns', {stderr: process.stderr});
+execa('unicorns', {stderr: 1});
+execa('unicorns', {stderr: null});
+execa('unicorns', {stderr: undefined});
+execa('unicorns', {stdout: 'pipe'});
+execa('unicorns', {stdout: 'ipc'});
+execa('unicorns', {stdout: 'ignore'});
+execa('unicorns', {stdout: 'inherit'});
+execa('unicorns', {stdout: process.stdout});
+execa('unicorns', {stdout: 1});
+execa('unicorns', {stdout: null});
+execa('unicorns', {stdout: undefined});
+execa('unicorns', {windowsVerbatimArguments: true});
+
+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: null})
+);
+
+expectType>(execa.stdout('unicorns'));
+expectType(await execa.stdout('unicorns'));
+expectType(await execa.stdout('unicorns', {encoding: 'utf8'}));
+expectType(await execa.stdout('unicorns', {encoding: null}));
+expectType(await execa.stdout('unicorns', ['foo'], {encoding: 'utf8'}));
+expectType(await execa.stdout('unicorns', ['foo'], {encoding: null}));
+
+expectType>(execa.stderr('unicorns'));
+expectType(await execa.stderr('unicorns'));
+expectType(await execa.stderr('unicorns', {encoding: 'utf8'}));
+expectType(await execa.stderr('unicorns', {encoding: null}));
+expectType(await execa.stderr('unicorns', ['foo'], {encoding: 'utf8'}));
+expectType(await execa.stderr('unicorns', ['foo'], {encoding: null}));
+
+expectType>(execa.shell('unicorns'));
+expectType>(await execa.shell('unicorns'));
+expectType>(
+ await execa.shell('unicorns', {encoding: 'utf8'})
+);
+expectType>(
+ await execa.shell('unicorns', {encoding: null})
+);
+
+expectType>(execa.sync('unicorns'));
+expectType>(
+ execa.sync('unicorns', {encoding: 'utf8'})
+);
+expectType>(
+ execa.sync('unicorns', {encoding: null})
+);
+expectType>(
+ execa.sync('unicorns', ['foo'], {encoding: 'utf8'})
+);
+expectType>(
+ execa.sync('unicorns', ['foo'], {encoding: null})
+);
+
+expectType>(execa.shellSync('unicorns'));
+expectType>(
+ execa.shellSync('unicorns', {encoding: 'utf8'})
+);
+expectType>(
+ execa.shellSync('unicorns', {encoding: null})
+);
diff --git a/package.json b/package.json
index d4355ee0dc..b478523f2b 100644
--- a/package.json
+++ b/package.json
@@ -13,10 +13,11 @@
"node": ">=8"
},
"scripts": {
- "test": "xo && nyc ava"
+ "test": "xo && nyc ava && tsd"
},
"files": [
"index.js",
+ "index.d.ts",
"lib"
],
"keywords": [
@@ -40,13 +41,14 @@
"cross-spawn": "^6.0.0",
"get-stream": "^4.0.0",
"is-stream": "^1.1.0",
- "merge-stream": "1.0.1",
+ "merge-stream": "^1.0.1",
"npm-run-path": "^2.0.0",
"p-finally": "^1.0.0",
"signal-exit": "^3.0.0",
"strip-final-newline": "^2.0.0"
},
"devDependencies": {
+ "@types/node": "^11.11.4",
"ava": "^1.3.1",
"cat-names": "^2.0.0",
"coveralls": "^3.0.3",
@@ -54,6 +56,7 @@
"is-running": "^2.0.0",
"nyc": "^13.3.0",
"tempfile": "^2.0.0",
+ "tsd": "^0.7.0",
"xo": "^0.24.0"
},
"nyc": {