From ab18eaa12cbf62eeb3736abb7d79327004388f56 Mon Sep 17 00:00:00 2001 From: Dimitri Benin Date: Thu, 7 Mar 2019 11:32:42 +0100 Subject: [PATCH 1/9] Add TypeScript definition --- index.d.ts | 365 ++++++++++++++++++++++++++++++++++++++++++++++++ index.js | 5 +- index.test-d.ts | 111 +++++++++++++++ package.json | 5 +- 4 files changed, 484 insertions(+), 2 deletions(-) create mode 100644 index.d.ts create mode 100644 index.test-d.ts diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000000..bcf46d505a --- /dev/null +++ b/index.d.ts @@ -0,0 +1,365 @@ +/// +import {ChildProcess} from 'child_process'; +import {Stream, Readable} 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). + */ + 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 | Readable; +} + +export interface SyncOptions + extends CommonOptions { + /** + * Write some input to the `stdin` of your binary. + */ + readonly input?: string | Buffer; +} + +export interface ExecaReturns { + /** + * The command that was run. + */ + cmd: string; + + /** + * The exit code of the process that was run. + */ + code: number; + + /** + * Whether the process failed to run. + */ + failed: boolean; + + /** + * Whether the process was killed. + */ + killed: boolean; + + /** + * The signal that was used to terminate the process. + */ + signal: string | null; + + /** + * The output of the process on stderr. + */ + stderr: StdOutErrType; + + /** + * The output of the process on stdout. + */ + stdout: StdOutErrType; + + /** + * Whether the process timed out. + */ + timedOut: boolean; +} + +export type ExecaError = Error & ExecaReturns; + +export interface ExecaChildPromise { + catch( + onrejected?: + | (( + reason: ExecaError + ) => ResultType | PromiseLike) + | null + ): Promise | ResultType>; +} + +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. + */ +export default function execa( + file: string, + arguments?: ReadonlyArray, + options?: Options +): ExecaChildProcess; +export default function execa( + file: string, + arguments?: ReadonlyArray, + options?: Options +): ExecaChildProcess; +export default function execa( + file: string, + options?: Options +): ExecaChildProcess; +export default function execa( + 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 A `Promise` that will be resolved with the contents of executed processe's `stdout` contents. + */ +export function stdout( + file: string, + arguments?: ReadonlyArray, + options?: Options +): Promise; +export function stdout( + file: string, + arguments?: ReadonlyArray, + options?: Options +): Promise; +export function stdout(file: string, options?: Options): Promise; +export function 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 A `Promise` that will be resolved with the contents of executed processe's `stderr` contents. + */ +export function stderr( + file: string, + arguments?: ReadonlyArray, + options?: Options +): Promise; +export function stderr( + file: string, + arguments?: ReadonlyArray, + options?: Options +): Promise; +export function stderr(file: string, options?: Options): Promise; +export function 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). + */ +export function shell(command: string, options?: Options): ExecaChildProcess; +export function 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). + */ +export function sync( + file: string, + arguments?: ReadonlyArray, + options?: SyncOptions +): ExecaReturns; +export function sync( + file: string, + arguments?: ReadonlyArray, + options?: SyncOptions +): ExecaReturns; +export function sync(file: string, options?: SyncOptions): ExecaReturns; +export function sync( + file: string, + options?: SyncOptions +): ExecaReturns; + +/** + * 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). + */ +export function shellSync(command: string, options?: Options): ExecaReturns; +export function shellSync( + command: string, + options?: Options +): ExecaReturns; diff --git a/index.js b/index.js index 872c7fa84f..b37912d4ce 100644 --- a/index.js +++ b/index.js @@ -193,7 +193,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); @@ -320,6 +320,9 @@ 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); diff --git a/index.test-d.ts b/index.test-d.ts new file mode 100644 index 0000000000..a76655e2c4 --- /dev/null +++ b/index.test-d.ts @@ -0,0 +1,111 @@ +import {expectType} from 'tsd-check'; +import execa, { + ExecaReturns, + ExecaChildProcess, + stdout, + stderr, + shell, + sync, + shellSync +} from '.'; + +const unicornsResult = await execa('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); + +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>(stdout('unicorns')); +expectType(await stdout('unicorns')); +expectType(await stdout('unicorns', {encoding: 'utf8'})); +expectType(await stdout('unicorns', {encoding: null})); +expectType(await stdout('unicorns', ['foo'], {encoding: 'utf8'})); +expectType(await stdout('unicorns', ['foo'], {encoding: null})); + +expectType>(stderr('unicorns')); +expectType(await stderr('unicorns')); +expectType(await stderr('unicorns', {encoding: 'utf8'})); +expectType(await stderr('unicorns', {encoding: null})); +expectType(await stderr('unicorns', ['foo'], {encoding: 'utf8'})); +expectType(await stderr('unicorns', ['foo'], {encoding: null})); + +expectType>(shell('unicorns')); +expectType>(await shell('unicorns')); +expectType>(await shell('unicorns', {encoding: 'utf8'})); +expectType>(await shell('unicorns', {encoding: null})); + +expectType>(sync('unicorns')); +expectType>(sync('unicorns', {encoding: 'utf8'})); +expectType>(sync('unicorns', {encoding: null})); +expectType>(sync('unicorns', ['foo'], {encoding: 'utf8'})); +expectType>(sync('unicorns', ['foo'], {encoding: null})); + +expectType>(shellSync('unicorns')); +expectType>(shellSync('unicorns', {encoding: 'utf8'})); +expectType>(shellSync('unicorns', {encoding: null})); diff --git a/package.json b/package.json index d6a84e558c..9941da32e7 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,11 @@ "node": ">=8" }, "scripts": { - "test": "xo && nyc ava" + "test": "xo && nyc ava && tsd-check" }, "files": [ "index.js", + "index.d.ts", "lib" ], "keywords": [ @@ -46,6 +47,7 @@ "strip-final-newline": "^2.0.0" }, "devDependencies": { + "@types/node": "^11.10.0", "ava": "^1.3.1", "cat-names": "^2.0.0", "coveralls": "^3.0.3", @@ -53,6 +55,7 @@ "is-running": "^2.0.0", "nyc": "^13.3.0", "tempfile": "^2.0.0", + "tsd-check": "^0.3.0", "xo": "^0.24.0" }, "nyc": { From 82ff900fa4eb8eabce41ac227b9aedd8d80ed694 Mon Sep 17 00:00:00 2001 From: Dimitri Benin Date: Sun, 10 Mar 2019 21:29:26 +0100 Subject: [PATCH 2/9] Fixes after review --- index.d.ts | 24 ++++++++++++------------ index.test-d.ts | 34 +++++++++++++++++----------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/index.d.ts b/index.d.ts index bcf46d505a..f415b0d5ab 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,6 +1,6 @@ /// import {ChildProcess} from 'child_process'; -import {Stream, Readable} from 'stream'; +import {Stream, Readable as ReadableStream} from 'stream'; export type StdIOOption = | 'pipe' @@ -174,7 +174,7 @@ export interface Options /** * Write some input to the `stdin` of your binary. */ - readonly input?: string | Buffer | Readable; + readonly input?: string | Buffer | ReadableStream; } export interface SyncOptions @@ -185,7 +185,7 @@ export interface SyncOptions readonly input?: string | Buffer; } -export interface ExecaReturns { +export interface ExecaReturnValue { /** * The command that was run. */ @@ -227,7 +227,7 @@ export interface ExecaReturns { timedOut: boolean; } -export type ExecaError = Error & ExecaReturns; +export type ExecaError = Error & ExecaReturnValue; export interface ExecaChildPromise { catch( @@ -236,12 +236,12 @@ export interface ExecaChildPromise { reason: ExecaError ) => ResultType | PromiseLike) | null - ): Promise | ResultType>; + ): Promise | ResultType>; } export type ExecaChildProcess = ChildProcess & ExecaChildPromise & - Promise>; + Promise>; /** * Execute a file. @@ -338,17 +338,17 @@ export function sync( file: string, arguments?: ReadonlyArray, options?: SyncOptions -): ExecaReturns; +): ExecaReturnValue; export function sync( file: string, arguments?: ReadonlyArray, options?: SyncOptions -): ExecaReturns; -export function sync(file: string, options?: SyncOptions): ExecaReturns; +): ExecaReturnValue; +export function sync(file: string, options?: SyncOptions): ExecaReturnValue; export function sync( file: string, options?: SyncOptions -): ExecaReturns; +): ExecaReturnValue; /** * Execute a command synchronously through the system shell. @@ -358,8 +358,8 @@ export function sync( * @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). */ -export function shellSync(command: string, options?: Options): ExecaReturns; +export function shellSync(command: string, options?: Options): ExecaReturnValue; export function shellSync( command: string, options?: Options -): ExecaReturns; +): ExecaReturnValue; diff --git a/index.test-d.ts b/index.test-d.ts index a76655e2c4..56d85cb578 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,6 +1,6 @@ import {expectType} from 'tsd-check'; import execa, { - ExecaReturns, + ExecaReturnValue, ExecaChildProcess, stdout, stderr, @@ -71,13 +71,13 @@ 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>( +expectType>(await execa('unicorns')); +expectType>(await execa('unicorns', {encoding: 'utf8'})); +expectType>(await execa('unicorns', {encoding: null})); +expectType>( await execa('unicorns', ['foo'], {encoding: 'utf8'}) ); -expectType>( +expectType>( await execa('unicorns', ['foo'], {encoding: null}) ); @@ -96,16 +96,16 @@ expectType(await stderr('unicorns', ['foo'], {encoding: 'utf8'})); expectType(await stderr('unicorns', ['foo'], {encoding: null})); expectType>(shell('unicorns')); -expectType>(await shell('unicorns')); -expectType>(await shell('unicorns', {encoding: 'utf8'})); -expectType>(await shell('unicorns', {encoding: null})); +expectType>(await shell('unicorns')); +expectType>(await shell('unicorns', {encoding: 'utf8'})); +expectType>(await shell('unicorns', {encoding: null})); -expectType>(sync('unicorns')); -expectType>(sync('unicorns', {encoding: 'utf8'})); -expectType>(sync('unicorns', {encoding: null})); -expectType>(sync('unicorns', ['foo'], {encoding: 'utf8'})); -expectType>(sync('unicorns', ['foo'], {encoding: null})); +expectType>(sync('unicorns')); +expectType>(sync('unicorns', {encoding: 'utf8'})); +expectType>(sync('unicorns', {encoding: null})); +expectType>(sync('unicorns', ['foo'], {encoding: 'utf8'})); +expectType>(sync('unicorns', ['foo'], {encoding: null})); -expectType>(shellSync('unicorns')); -expectType>(shellSync('unicorns', {encoding: 'utf8'})); -expectType>(shellSync('unicorns', {encoding: null})); +expectType>(shellSync('unicorns')); +expectType>(shellSync('unicorns', {encoding: 'utf8'})); +expectType>(shellSync('unicorns', {encoding: null})); From 8f306bb1b5b8fa52a2410ee4e6e2f7a77699daa1 Mon Sep 17 00:00:00 2001 From: Dimitri Benin Date: Sun, 10 Mar 2019 22:46:21 +0100 Subject: [PATCH 3/9] Extend types to match current state in master --- index.d.ts | 301 +++++++++++++++++++++++++++--------------------- index.test-d.ts | 120 ++++++++++++------- package.json | 2 +- 3 files changed, 249 insertions(+), 174 deletions(-) diff --git a/index.d.ts b/index.d.ts index f415b0d5ab..0ca22f1204 100644 --- a/index.d.ts +++ b/index.d.ts @@ -185,49 +185,90 @@ export interface SyncOptions readonly input?: string | Buffer; } -export interface ExecaReturnValue { +export interface ExecaReturnBase { /** - * The command that was run. + * The numeric exit code of the process that was run. */ - cmd: string; + exitCode: number; /** - * The exit code of the process that was run. + * The textual exit code of the process that was run. */ - code: number; + exitCodeName: string; + + /** + * The output of the process on stdout. + */ + stdout: StdOutErrType; + + /** + * The output of the process on stderr. + */ + stderr: StdOutErrType; /** * Whether the process failed to run. */ failed: boolean; + /** + * The signal that was used to terminate the process. + */ + signal: string | null; + + /** + * 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 signal that was used to terminate the process. + * The exit code of the process that was run. */ - signal: string | null; + code: number; +} +export interface ExecaReturnValue + extends ExecaSyncReturnValue { /** - * The output of the process on stderr. + * The output of the process with `stdout` and `stderr` interleaved. */ - stderr: StdOutErrType; + all: StdOutErrType; +} +export interface ExecaSyncError + extends Error, + ExecaReturnBase { /** - * The output of the process on stdout. + * The error message. */ - stdout: StdOutErrType; + message: string; /** - * Whether the process timed out. + * The exit code (either numeric or textual) of the process that was run. */ - timedOut: boolean; + code: number | string; } -export type ExecaError = Error & ExecaReturnValue; +export interface ExecaError + extends ExecaSyncError { + /** + * The output of the process with `stdout` and `stderr` interleaved. + */ + all: StdOutErrType; +} export interface ExecaChildPromise { catch( @@ -243,123 +284,115 @@ 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. - */ -export default function execa( - file: string, - arguments?: ReadonlyArray, - options?: Options -): ExecaChildProcess; -export default function execa( - file: string, - arguments?: ReadonlyArray, - options?: Options -): ExecaChildProcess; -export default function execa( - file: string, - options?: Options -): ExecaChildProcess; -export default function execa( - 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 A `Promise` that will be resolved with the contents of executed processe's `stdout` contents. - */ -export function stdout( - file: string, - arguments?: ReadonlyArray, - options?: Options -): Promise; -export function stdout( - file: string, - arguments?: ReadonlyArray, - options?: Options -): Promise; -export function stdout(file: string, options?: Options): Promise; -export function 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 A `Promise` that will be resolved with the contents of executed processe's `stderr` contents. - */ -export function stderr( - file: string, - arguments?: ReadonlyArray, - options?: Options -): Promise; -export function stderr( - file: string, - arguments?: ReadonlyArray, - options?: Options -): Promise; -export function stderr(file: string, options?: Options): Promise; -export function 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). - */ -export function shell(command: string, options?: Options): ExecaChildProcess; -export function 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). - */ -export function sync( - file: string, - arguments?: ReadonlyArray, - options?: SyncOptions -): ExecaReturnValue; -export function sync( - file: string, - arguments?: ReadonlyArray, - options?: SyncOptions -): ExecaReturnValue; -export function sync(file: string, options?: SyncOptions): ExecaReturnValue; -export function sync( - file: string, - options?: SyncOptions -): ExecaReturnValue; - -/** - * 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). - */ -export function shellSync(command: string, options?: Options): ExecaReturnValue; -export function shellSync( - command: string, - options?: Options -): ExecaReturnValue; +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. + */ + ( + 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 A `Promise` that will be resolved with the contents of executed processe's `stdout` contents. + */ + 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 A `Promise` that will be resolved with the contents of executed processe's `stderr` contents. + */ + 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). + */ + 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). + */ + shellSync(command: string, options?: Options): ExecaSyncReturnValue; + shellSync( + command: string, + options?: Options + ): ExecaSyncReturnValue; +}; + +export default execa; diff --git a/index.test-d.ts b/index.test-d.ts index 56d85cb578..c0e977bc78 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -2,22 +2,46 @@ import {expectType} from 'tsd-check'; import execa, { ExecaReturnValue, ExecaChildProcess, - stdout, - stderr, - shell, - sync, - shellSync + ExecaError, + ExecaSyncReturnValue, + ExecaSyncError } from '.'; -const unicornsResult = await execa('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); +try { + const unicornsResult = await execa('unicorns'); + 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); +} catch (error) { + const execaError: ExecaError = error; + + expectType(execaError.message); + expectType(execaError.code); + expectType(execaError.all); +} + +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); +} catch (error) { + const execaError: ExecaSyncError = error; + + expectType(execaError.message); + expectType(execaError.code); +} execa('unicorns', {cwd: '.'}); execa('unicorns', {env: {PATH: ''}}); @@ -72,7 +96,9 @@ execa('unicorns', {windowsVerbatimArguments: true}); expectType>(execa('unicorns')); expectType>(await execa('unicorns')); -expectType>(await execa('unicorns', {encoding: 'utf8'})); +expectType>( + await execa('unicorns', {encoding: 'utf8'}) +); expectType>(await execa('unicorns', {encoding: null})); expectType>( await execa('unicorns', ['foo'], {encoding: 'utf8'}) @@ -81,31 +107,47 @@ expectType>( await execa('unicorns', ['foo'], {encoding: null}) ); -expectType>(stdout('unicorns')); -expectType(await stdout('unicorns')); -expectType(await stdout('unicorns', {encoding: 'utf8'})); -expectType(await stdout('unicorns', {encoding: null})); -expectType(await stdout('unicorns', ['foo'], {encoding: 'utf8'})); -expectType(await stdout('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>(stderr('unicorns')); -expectType(await stderr('unicorns')); -expectType(await stderr('unicorns', {encoding: 'utf8'})); -expectType(await stderr('unicorns', {encoding: null})); -expectType(await stderr('unicorns', ['foo'], {encoding: 'utf8'})); -expectType(await stderr('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>(shell('unicorns')); -expectType>(await shell('unicorns')); -expectType>(await shell('unicorns', {encoding: 'utf8'})); -expectType>(await shell('unicorns', {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>(sync('unicorns')); -expectType>(sync('unicorns', {encoding: 'utf8'})); -expectType>(sync('unicorns', {encoding: null})); -expectType>(sync('unicorns', ['foo'], {encoding: 'utf8'})); -expectType>(sync('unicorns', ['foo'], {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>(shellSync('unicorns')); -expectType>(shellSync('unicorns', {encoding: 'utf8'})); -expectType>(shellSync('unicorns', {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 11f7fdd6c2..992dfcc5c7 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "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", From 164e4ae0949692d59810ae4401c22b1f7f14f3c1 Mon Sep 17 00:00:00 2001 From: Dimitri Benin Date: Fri, 15 Mar 2019 11:54:30 +0100 Subject: [PATCH 4/9] Update to latest tsd-check for strict-mode checks --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 992dfcc5c7..03aeebd21e 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "strip-final-newline": "^2.0.0" }, "devDependencies": { - "@types/node": "^11.10.0", + "@types/node": "^11.11.3", "ava": "^1.3.1", "cat-names": "^2.0.0", "coveralls": "^3.0.3", @@ -56,7 +56,7 @@ "is-running": "^2.0.0", "nyc": "^13.3.0", "tempfile": "^2.0.0", - "tsd-check": "^0.3.0", + "tsd-check": "^0.4.0", "xo": "^0.24.0" }, "nyc": { From 4e33d4947eb658a3c2c714d67bf677f9f87de90a Mon Sep 17 00:00:00 2001 From: Dimitri Benin Date: Fri, 15 Mar 2019 14:12:22 +0100 Subject: [PATCH 5/9] Fixes after review --- index.d.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/index.d.ts b/index.d.ts index 0ca22f1204..c8c92a9c5b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,7 +2,7 @@ import {ChildProcess} from 'child_process'; import {Stream, Readable as ReadableStream} from 'stream'; -export type StdIOOption = +export type StdioOption = | 'pipe' | 'ipc' | 'ignore' @@ -44,10 +44,12 @@ export interface CommonOptions { * * @default 'pipe' */ - readonly stdio?: 'pipe' | 'ignore' | 'inherit' | ReadonlyArray; + 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; @@ -145,21 +147,21 @@ export interface CommonOptions { * * @default 'pipe' */ - readonly stdin?: StdIOOption; + 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; + 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; + 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`. @@ -185,7 +187,7 @@ export interface SyncOptions readonly input?: string | Buffer; } -export interface ExecaReturnBase { +export interface ExecaReturnBase { /** * The numeric exit code of the process that was run. */ @@ -199,12 +201,12 @@ export interface ExecaReturnBase { /** * The output of the process on stdout. */ - stdout: StdOutErrType; + stdout: StdoutStderrType; /** * The output of the process on stderr. */ - stderr: StdOutErrType; + stderr: StdoutStderrType; /** * Whether the process failed to run. @@ -272,7 +274,7 @@ export interface ExecaError export interface ExecaChildPromise { catch( - onrejected?: + onRejected?: | (( reason: ExecaError ) => ResultType | PromiseLike) From 38062f6f4fbca192f08800105e21f3ca38398e45 Mon Sep 17 00:00:00 2001 From: Dimitri Benin Date: Wed, 20 Mar 2019 01:16:51 +0100 Subject: [PATCH 6/9] Update types to match changes in master, improve docs --- index.d.ts | 420 +++++++++++++++++++++++++++++------------------- index.js | 8 +- index.test-d.ts | 19 ++- package.json | 2 +- 4 files changed, 277 insertions(+), 172 deletions(-) diff --git a/index.d.ts b/index.d.ts index c8c92a9c5b..36d1d0cc5b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -14,262 +14,272 @@ export type StdioOption = export interface CommonOptions { /** - * Current working directory of the child process. - * - * @default process.cwd() - */ + 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 - */ + 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 - */ + 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. - */ + 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' - */ + 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 - */ + 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. - */ + Sets the user identity of the process. + */ readonly uid?: number; /** - * Sets the group identity of the process. - */ + 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 - */ + 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 - */ + 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 - */ + 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() - */ + 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 - */ + 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 - */ + 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' - */ + 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 - */ + 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 - */ + 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 - */ + 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' - */ + 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' - */ + 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' - */ + 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' - */ + 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 - */ + 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. - */ + 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. - */ + 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. - */ + The numeric exit code of the process that was run. + */ exitCode: number; /** - * The textual exit code of the process that was run. - */ + The textual exit code of the process that was run. + */ exitCodeName: string; /** - * The output of the process on stdout. - */ + The output of the process on stdout. + */ stdout: StdoutStderrType; /** - * The output of the process on stderr. - */ + The output of the process on stderr. + */ stderr: StdoutStderrType; /** - * Whether the process failed to run. - */ + Whether the process failed to run. + */ failed: boolean; /** - * The signal that was used to terminate the process. - */ - signal: string | null; + The signal that was used to terminate the process. + */ + signal?: string; /** - * The command that was run. - */ + The command that was run. + */ cmd: string; /** - * Whether the process timed out. - */ + Whether the process timed out. + */ timedOut: boolean; /** - * Whether the process was killed. - */ + Whether the process was killed. + */ killed: boolean; } export interface ExecaSyncReturnValue extends ExecaReturnBase { /** - * The exit code of the process that was run. - */ + 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. - */ + 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. - */ + The error message. + */ message: string; /** - * The exit code (either numeric or textual) of the process that was run. - */ + 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. - */ + The output of the process with `stdout` and `stderr` interleaved. + */ all: StdOutErrType; + + /** + Whether the process was canceled. + */ + isCanceled: boolean; } export interface ExecaChildPromise { @@ -280,6 +290,13 @@ export interface ExecaChildPromise { ) => ResultType | PromiseLike) | null ): Promise | ResultType>; + + /** + A process can be canceled with `.cancel` method which throws an error with `error.isCanceled` equal to `true`, provided that the process gets canceled. + + Process would not get canceled if it has already exited. + */ + cancel(): void; } export type ExecaChildProcess = ChildProcess & @@ -288,14 +305,38 @@ export type ExecaChildProcess = ChildProcess & 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. - */ + 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, @@ -310,12 +351,12 @@ declare const execa: { (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 A `Promise` that will be resolved with the contents of executed processe's `stdout` contents. - */ + Same as `execa()`, but returns only `stdout`. + + @param file - The program/script to execute. + @param arguments - Arguments to pass to `file` on execution. + @returns A `Promise` that will be resolved with the contents of executed processe's `stdout` contents. + */ stdout( file: string, arguments?: ReadonlyArray, @@ -330,12 +371,12 @@ declare const execa: { 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 A `Promise` that will be resolved with the contents of executed processe's `stderr` contents. - */ + Same as `execa()`, but returns only `stderr`. + + @param file - The program/script to execute. + @param arguments - Arguments to pass to `file` on execution. + @returns A `Promise` that will be resolved with the contents of executed processe's `stderr` contents. + */ stderr( file: string, arguments?: ReadonlyArray, @@ -350,25 +391,56 @@ declare const execa: { 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). - */ + 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). - */ + 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, @@ -383,13 +455,35 @@ declare const execa: { 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). - */ + 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, diff --git a/index.js b/index.js index e2881f006e..3266a0771c 100644 --- a/index.js +++ b/index.js @@ -379,17 +379,17 @@ 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); @@ -427,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 index c0e977bc78..032fb6882c 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,4 +1,4 @@ -import {expectType} from 'tsd-check'; +import {expectType, expectError} from 'tsd-check'; import execa, { ExecaReturnValue, ExecaChildProcess, @@ -8,22 +8,27 @@ import execa, { } from '.'; try { - const unicornsResult = await execa('unicorns'); + 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.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 { @@ -32,15 +37,21 @@ try { expectType(unicornsResult.code); expectType(unicornsResult.failed); expectType(unicornsResult.killed); - expectType(unicornsResult.signal); + expectType(unicornsResult.signal); expectType(unicornsResult.stderr); expectType(unicornsResult.stdout); expectType(unicornsResult.timedOut); + // TODO: this produces false positives, waiting for https://github.com/SamVerschueren/tsd-check/pull/19 + // expectError(unicornsResult.all); + // expectError(unicornsResult.isCanceled); } catch (error) { const execaError: ExecaSyncError = error; expectType(execaError.message); expectType(execaError.code); + // TODO: this produces false positives, waiting for https://github.com/SamVerschueren/tsd-check/pull/19 + // expectError(execaError.all); + // expectError(execaError.isCanceled); } execa('unicorns', {cwd: '.'}); diff --git a/package.json b/package.json index 03aeebd21e..3069025335 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "is-running": "^2.0.0", "nyc": "^13.3.0", "tempfile": "^2.0.0", - "tsd-check": "^0.4.0", + "tsd-check": "^0.6.0", "xo": "^0.24.0" }, "nyc": { From 0d65a6e8c13a23bcb8c4e29e5be8fc9889510848 Mon Sep 17 00:00:00 2001 From: Dimitri Benin Date: Wed, 20 Mar 2019 20:30:28 +0100 Subject: [PATCH 7/9] Update to latest `tsd`, re-enable negative checks --- index.test-d.ts | 10 ++++------ package.json | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/index.test-d.ts b/index.test-d.ts index 032fb6882c..d5b26f1104 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -41,17 +41,15 @@ try { expectType(unicornsResult.stderr); expectType(unicornsResult.stdout); expectType(unicornsResult.timedOut); - // TODO: this produces false positives, waiting for https://github.com/SamVerschueren/tsd-check/pull/19 - // expectError(unicornsResult.all); - // expectError(unicornsResult.isCanceled); + expectError(unicornsResult.all); + expectError(unicornsResult.isCanceled); } catch (error) { const execaError: ExecaSyncError = error; expectType(execaError.message); expectType(execaError.code); - // TODO: this produces false positives, waiting for https://github.com/SamVerschueren/tsd-check/pull/19 - // expectError(execaError.all); - // expectError(execaError.isCanceled); + expectError(execaError.all); + expectError(execaError.isCanceled); } execa('unicorns', {cwd: '.'}); diff --git a/package.json b/package.json index 3069025335..b478523f2b 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "node": ">=8" }, "scripts": { - "test": "xo && nyc ava && tsd-check" + "test": "xo && nyc ava && tsd" }, "files": [ "index.js", @@ -48,7 +48,7 @@ "strip-final-newline": "^2.0.0" }, "devDependencies": { - "@types/node": "^11.11.3", + "@types/node": "^11.11.4", "ava": "^1.3.1", "cat-names": "^2.0.0", "coveralls": "^3.0.3", @@ -56,7 +56,7 @@ "is-running": "^2.0.0", "nyc": "^13.3.0", "tempfile": "^2.0.0", - "tsd-check": "^0.6.0", + "tsd": "^0.7.0", "xo": "^0.24.0" }, "nyc": { From e05229262adb52986fb458b385d42f6d3a67f171 Mon Sep 17 00:00:00 2001 From: Dimitri Benin Date: Wed, 20 Mar 2019 20:36:13 +0100 Subject: [PATCH 8/9] Fix reference to `tsd` --- index.test-d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.test-d.ts b/index.test-d.ts index d5b26f1104..3e6eb23e07 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,4 +1,4 @@ -import {expectType, expectError} from 'tsd-check'; +import {expectType, expectError} from 'tsd'; import execa, { ExecaReturnValue, ExecaChildProcess, From ebeb960efdecc20e66823165b2a4f897571789fb Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 21 Mar 2019 21:04:03 +0700 Subject: [PATCH 9/9] Update index.d.ts --- index.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.d.ts b/index.d.ts index 36d1d0cc5b..6f2a7b6f3b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -292,9 +292,9 @@ export interface ExecaChildPromise { ): Promise | ResultType>; /** - A process can be canceled with `.cancel` method which throws an error with `error.isCanceled` equal to `true`, provided that the process gets canceled. + Cancel the subprocess. - Process would not get canceled if it has already exited. + 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; } @@ -355,7 +355,7 @@ declare const execa: { @param file - The program/script to execute. @param arguments - Arguments to pass to `file` on execution. - @returns A `Promise` that will be resolved with the contents of executed processe's `stdout` contents. + @returns The contents of the executed process' `stdout`. */ stdout( file: string, @@ -375,7 +375,7 @@ declare const execa: { @param file - The program/script to execute. @param arguments - Arguments to pass to `file` on execution. - @returns A `Promise` that will be resolved with the contents of executed processe's `stderr` contents. + @returns The contents of the executed process' `stderr`. */ stderr( file: string,