Repository: sindresorhus/execa
Pull request #239 (2019-05-12 16:01:28)

Improve the API section of the readme
This improves the `API` section:
  - the description of each property of the return/resolved value (or thrown/rejected error) was missing.
  - the return value of `execa.sync()` was wrong (it is not the same as the return value of `spawnSync()` although it has some common properties.
  - the extra methods and properties of the `child_process` instance returned by the main function are now separate.
  - the description of the main function has been improved.

To be more efficient, I will update the `index.d.ts` file after a first code review.

ehmicky: Fixed! I've also fixed the `index.d.ts`.

Patch:
diff --git a/fixtures/noop-throw b/fixtures/noop-throw
new file mode 100755
index 0000000..521e70a
--- /dev/null
+++ b/fixtures/noop-throw
@@ -0,0 +1,4 @@
+#!/usr/bin/env node
+'use strict';
+console.error(process.argv[2]);
+process.exit(2);
diff --git a/index.d.ts b/index.d.ts
index 1b50206..a6a1976 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -294,9 +294,7 @@ declare namespace execa {
 		): Promise<ExecaReturnValue<StdoutErrorType> | 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.
+		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;
 	}
@@ -362,7 +360,7 @@ declare const execa: {
 
 	@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).
+	@returns A result `Object` with `stdout` and `stderr` properties.
 	*/
 	sync(
 		file: string,
diff --git a/index.js b/index.js
index dd14df7..3e96202 100644
--- a/index.js
+++ b/index.js
@@ -350,10 +350,10 @@ const execa = (command, args, options) => {
 		const finalize = async () => {
 			const results = await processComplete;
 
-			const result = results[0];
-			result.stdout = results[1];
-			result.stderr = results[2];
-			result.all = results[3];
+			const [result, stdout, stderr, all] = results;
+			result.stdout = handleOutput(parsed.options, stdout);
+			result.stderr = handleOutput(parsed.options, stderr);
+			result.all = handleOutput(parsed.options, all);
 
 			if (result.error || result.code !== 0 || result.signal !== null) {
 				const error = makeError(result, {
@@ -372,9 +372,9 @@ const execa = (command, args, options) => {
 			}
 
 			return {
-				stdout: handleOutput(parsed.options, result.stdout),
-				stderr: handleOutput(parsed.options, result.stderr),
-				all: handleOutput(parsed.options, result.all),
+				stdout: result.stdout,
+				stderr: result.stderr,
+				all: result.all,
 				code: 0,
 				exitCode: 0,
 				exitCodeName: 'SUCCESS',
@@ -425,6 +425,8 @@ module.exports.sync = (command, args, options) => {
 
 	const result = childProcess.spawnSync(parsed.command, parsed.args, parsed.options);
 	result.code = result.status;
+	result.stdout = handleOutput(parsed.options, result.stdout);
+	result.stderr = handleOutput(parsed.options, result.stderr);
 
 	if (result.error || result.status !== 0 || result.signal !== null) {
 		const error = makeError(result, {
@@ -442,8 +444,8 @@ module.exports.sync = (command, args, options) => {
 	}
 
 	return {
-		stdout: handleOutput(parsed.options, result.stdout),
-		stderr: handleOutput(parsed.options, result.stderr),
+		stdout: result.stdout,
+		stderr: result.stderr,
 		code: 0,
 		exitCode: 0,
 		exitCodeName: 'SUCCESS',
diff --git a/readme.md b/readme.md
index 30c84fe..f7be045 100644
--- a/readme.md
+++ b/readme.md
@@ -15,7 +15,7 @@
 - Higher max buffer. 10 MB instead of 200 KB.
 - [Executes locally installed binaries by name.](#preferlocal)
 - [Cleans up spawned processes when the parent process dies.](#cleanup)
-- [Get interleaved output](#execafile-arguments-options) from `stdout` and `stderr` similar to what is printed on the terminal. [*(Async only)*](#execasyncfile-arguments-options)
+- [Get interleaved output](#all) from `stdout` and `stderr` similar to what is printed on the terminal. [*(Async only)*](#execasyncfile-arguments-options)
 - [Can specify command and arguments as a single string without a shell](#execafile-arguments-options)
 - More descriptive errors.
 
@@ -112,36 +112,106 @@ try {
 ### execa(file, [arguments], [options])
 ### execa(command, [options])
 
-Execute a file.
+Execute a file. Think of this as a mix of [`child_process.execFile()`](https://nodejs.org/api/child_process.html#child_process_child_process_execfile_file_args_options_callback) and [`child_process.spawn()`](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options).
 
 Arguments can be specified in either:
   - `arguments`: `execa('echo', ['unicorns'])`.
   - `command`: `execa('echo unicorns')`.
 
-Arguments should not be escaped nor quoted. Exception: inside `command`, spaces can be escaped with a backslash.
-
-Think of this as a mix of `child_process.execFile` and `child_process.spawn`.
+Arguments should not be escaped nor quoted, except inside `command` where spaces can be escaped with a backslash.
 
 Unless the [`shell`](#shell) option is used, no shell interpreter (Bash, `cmd.exe`, etc.) is used, so shell features such as variables substitution (`echo $PATH`) are not allowed.
 
-Returns a [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess) which is enhanced to be a `Promise`.
+Returns a [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess) which:
+  - is also a `Promise` resolving or rejecting with a [`childProcessResult`](#childProcessResult).
+  - exposes the following additional methods and properties.
+
+#### cancel()
 
-It exposes an additional `.all` stream, with `stdout` and `stderr` interleaved.
+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`.
 
-The spawned process can be canceled with the `.cancel()` method on the promise, which 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.
+#### all
 
-The promise result is an `Object` with `stdout`, `stderr` and `all` properties.
+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).
 
 ### execa.sync(file, [arguments], [options])
 ### execa.sync(command, [options])
 
 Execute a file synchronously.
 
-Returns the same result object as [`child_process.spawnSync`](https://nodejs.org/api/child_process.html#child_process_child_process_spawnsync_command_args_options).
+Returns or throws a [`childProcessResult`](#childProcessResult).
+
+### childProcessResult
+
+Type: `object`
+
+Result of a child process execution. On success this is a plain object. On failure this is also an `Error` instance.
+
+#### exitCode
+
+Type: `number`
+
+The numeric exit code of the process that was run.
+
+#### exitCodeName
+
+Type: `string`
+
+The textual exit code of the process that was run.
+
+#### stdout
+
+Type: `string | Buffer`
+
+The output of the process on stdout.
+
+#### stderr
+
+Type: `string | Buffer`
+
+The output of the process on stderr.
+
+#### all
+
+Type: `string | Buffer`
+
+The output of the process on both stdout and stderr. `undefined` if `execa.sync()` was used.
+
+#### command
+
+Type: `string`
+
+The command that was run.
+
+#### failed
+
+Type: `boolean`
+
+Whether the process failed to run.
+
+#### timedOut
+
+Type: `boolean`
+
+Whether the process timed out.
+
+#### killed
+
+Type: `boolean`
+
+Whether the process was killed.
+
+#### isCanceled
+
+Type: `boolean`
+
+Whether the process was canceled.
+
+#### signal
 
-It does not have the `.all` property that `execa()` has because the [underlying synchronous implementation](https://nodejs.org/api/child_process.html#child_process_child_process_execfilesync_file_args_options) only returns `stdout` and `stderr` at the end of the execution, so they cannot be interleaved.
+Type: `string | undefined`
 
-This method throws an `Error` if the command fails.
+The signal that was used to terminate the process.
 
 ### options
 


Test patch:
diff --git a/test.js b/test.js
index 31e4819..9579764 100644
--- a/test.js
+++ b/test.js
@@ -120,11 +120,31 @@ test('skip throwing when using reject option in sync mode', t => {
 	t.is(error.exitCode, 2);
 });
 
-test('stripFinalNewline option', async t => {
+test('stripFinalNewline: true', async t => {
+	const {stdout} = await execa('noop', ['foo']);
+	t.is(stdout, 'foo');
+});
+
+test('stripFinalNewline: false', async t => {
 	const {stdout} = await execa('noop', ['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}));
+	t.is(stderr, 'foo');
+});
+
+test('stripFinalNewline in sync mode', t => {
+	const {stdout} = execa.sync('noop', ['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}));
+	t.is(stderr, 'foo');
+});
+
 test('preferLocal option', async t => {
 	await execa('ava', ['--version'], {env: {PATH: ''}});
 	const errorRegExp = process.platform === 'win32' ? /failed with exit code 1/ : /spawn ava ENOENT/;