diff --git a/index.d.ts b/index.d.ts index 2b61406818..7cef754765 100644 --- a/index.d.ts +++ b/index.d.ts @@ -12,7 +12,27 @@ export type StdioOption = | number | undefined; -export type CommonOptions = { +type EncodingOption = + | 'utf8' + // eslint-disable-next-line unicorn/text-encoding-identifier-case + | 'utf-8' + | 'utf16le' + | 'utf-16le' + | 'ucs2' + | 'ucs-2' + | 'latin1' + | 'binary' + | 'ascii' + | 'hex' + | 'base64' + | 'base64url' + | 'buffer' + | null + | undefined; +type DefaultEncodingOption = 'utf8'; +type BufferEncodingOption = 'buffer' | null; + +export type CommonOptions = { /** Kill the spawned process when the parent process exits unless either: - the spawned process is [`detached`](https://nodejs.org/api/child_process.html#child_process_options_detached) @@ -176,7 +196,7 @@ export type CommonOptions = { readonly shell?: boolean | string; /** - 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. + Specify the character encoding used to decode the `stdout` and `stderr` output. If set to `'buffer'` or `null`, then `stdout` and `stderr` will be a `Buffer` instead of a string. @default 'utf8' */ @@ -253,7 +273,7 @@ export type CommonOptions = { readonly verbose?: boolean; }; -export type Options = { +export type Options = { /** Write some input to the `stdin` of your binary. @@ -269,7 +289,7 @@ export type Options = { readonly inputFile?: string; } & CommonOptions; -export type SyncOptions = { +export type SyncOptions = { /** Write some input to the `stdin` of your binary. @@ -285,7 +305,7 @@ export type SyncOptions = { readonly inputFile?: string; } & CommonOptions; -export type NodeOptions = { +export type NodeOptions = { /** The Node.js executable to use. @@ -625,10 +645,10 @@ export function execa( export function execa( file: string, arguments?: readonly string[], - options?: Options + options?: Options ): ExecaChildProcess; export function execa(file: string, options?: Options): ExecaChildProcess; -export function execa(file: string, options?: Options): ExecaChildProcess; +export function execa(file: string, options?: Options): ExecaChildProcess; /** Same as `execa()` but synchronous. @@ -698,12 +718,12 @@ export function execaSync( export function execaSync( file: string, arguments?: readonly string[], - options?: SyncOptions + options?: SyncOptions ): ExecaSyncReturnValue; export function execaSync(file: string, options?: SyncOptions): ExecaSyncReturnValue; export function execaSync( file: string, - options?: SyncOptions + options?: SyncOptions ): ExecaSyncReturnValue; /** @@ -729,7 +749,7 @@ console.log(stdout); ``` */ export function execaCommand(command: string, options?: Options): ExecaChildProcess; -export function execaCommand(command: string, options?: Options): ExecaChildProcess; +export function execaCommand(command: string, options?: Options): ExecaChildProcess; /** Same as `execaCommand()` but synchronous. @@ -748,7 +768,7 @@ console.log(stdout); ``` */ export function execaCommandSync(command: string, options?: SyncOptions): ExecaSyncReturnValue; -export function execaCommandSync(command: string, options?: SyncOptions): ExecaSyncReturnValue; +export function execaCommandSync(command: string, options?: SyncOptions): ExecaSyncReturnValue; type TemplateExpression = | string @@ -783,7 +803,7 @@ type Execa$ = { */ (options: Options): Execa$; (options: Options): Execa$; - (options: Options): Execa$; + (options: Options): Execa$; ( templates: TemplateStringsArray, ...expressions: TemplateExpression[] @@ -929,7 +949,7 @@ export function execaNode( export function execaNode( scriptPath: string, arguments?: readonly string[], - options?: NodeOptions + options?: NodeOptions ): ExecaChildProcess; export function execaNode(scriptPath: string, options?: NodeOptions): ExecaChildProcess; -export function execaNode(scriptPath: string, options?: NodeOptions): ExecaChildProcess; +export function execaNode(scriptPath: string, options?: NodeOptions): ExecaChildProcess; diff --git a/index.test-d.ts b/index.test-d.ts index 46e3572c60..1e1f764605 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -25,7 +25,7 @@ try { execaPromise.cancel(); expectType(execaPromise.all); - const execaBufferPromise = execa('unicorns', {encoding: null}); + const execaBufferPromise = execa('unicorns', {encoding: 'buffer'}); const writeStream = createWriteStream('output.txt'); expectAssignable(execaPromise.pipeStdout); @@ -133,6 +133,7 @@ execa('unicorns', {cleanup: false}); execa('unicorns', {preferLocal: false}); execa('unicorns', {localDir: '.'}); execa('unicorns', {localDir: new URL('file:///test')}); +expectError(execa('unicorns', {encoding: 'unknownEncoding'})); execa('unicorns', {execPath: '/path'}); execa('unicorns', {buffer: false}); execa('unicorns', {input: ''}); @@ -207,10 +208,14 @@ expectType(await execa('unicorns')); expectType( await execa('unicorns', {encoding: 'utf8'}), ); +expectType>(await execa('unicorns', {encoding: 'buffer'})); expectType>(await execa('unicorns', {encoding: null})); expectType( await execa('unicorns', ['foo'], {encoding: 'utf8'}), ); +expectType>( + await execa('unicorns', ['foo'], {encoding: 'buffer'}), +); expectType>( await execa('unicorns', ['foo'], {encoding: null}), ); @@ -219,12 +224,18 @@ expectType(execaSync('unicorns')); expectType( execaSync('unicorns', {encoding: 'utf8'}), ); +expectType>( + execaSync('unicorns', {encoding: 'buffer'}), +); expectType>( execaSync('unicorns', {encoding: null}), ); expectType( execaSync('unicorns', ['foo'], {encoding: 'utf8'}), ); +expectType>( + execaSync('unicorns', ['foo'], {encoding: 'buffer'}), +); expectType>( execaSync('unicorns', ['foo'], {encoding: null}), ); @@ -232,14 +243,18 @@ expectType>( expectType(execaCommand('unicorns')); expectType(await execaCommand('unicorns')); expectType(await execaCommand('unicorns', {encoding: 'utf8'})); +expectType>(await execaCommand('unicorns', {encoding: 'buffer'})); expectType>(await execaCommand('unicorns', {encoding: null})); expectType(await execaCommand('unicorns foo', {encoding: 'utf8'})); +expectType>(await execaCommand('unicorns foo', {encoding: 'buffer'})); expectType>(await execaCommand('unicorns foo', {encoding: null})); expectType(execaCommandSync('unicorns')); expectType(execaCommandSync('unicorns', {encoding: 'utf8'})); +expectType>(execaCommandSync('unicorns', {encoding: 'buffer'})); expectType>(execaCommandSync('unicorns', {encoding: null})); expectType(execaCommandSync('unicorns foo', {encoding: 'utf8'})); +expectType>(execaCommandSync('unicorns foo', {encoding: 'buffer'})); expectType>(execaCommandSync('unicorns foo', {encoding: null})); expectType(execaNode('unicorns')); @@ -247,19 +262,29 @@ expectType(await execaNode('unicorns')); expectType( await execaNode('unicorns', {encoding: 'utf8'}), ); +expectType>(await execaNode('unicorns', {encoding: 'buffer'})); expectType>(await execaNode('unicorns', {encoding: null})); expectType( await execaNode('unicorns', ['foo'], {encoding: 'utf8'}), ); +expectType>( + await execaNode('unicorns', ['foo'], {encoding: 'buffer'}), +); expectType>( await execaNode('unicorns', ['foo'], {encoding: null}), ); expectType(execaNode('unicorns', {nodeOptions: ['--async-stack-traces']})); expectType(execaNode('unicorns', ['foo'], {nodeOptions: ['--async-stack-traces']})); +expectType>( + execaNode('unicorns', {nodeOptions: ['--async-stack-traces'], encoding: 'buffer'}), +); expectType>( execaNode('unicorns', {nodeOptions: ['--async-stack-traces'], encoding: null}), ); +expectType>( + execaNode('unicorns', ['foo'], {nodeOptions: ['--async-stack-traces'], encoding: 'buffer'}), +); expectType>( execaNode('unicorns', ['foo'], {nodeOptions: ['--async-stack-traces'], encoding: null}), ); @@ -277,28 +302,29 @@ expectType(await $({encoding: 'utf8'})`unicorns foo`); expectType($({encoding: 'utf8'}).sync`unicorns foo`); expectType>($({encoding: null})`unicorns`); -expectType>(await $({encoding: null})`unicorns`); -expectType>($({encoding: null}).sync`unicorns`); +expectType>($({encoding: 'buffer'})`unicorns`); +expectType>(await $({encoding: 'buffer'})`unicorns`); +expectType>($({encoding: 'buffer'}).sync`unicorns`); -expectType>($({encoding: null})`unicorns foo`); -expectType>(await $({encoding: null})`unicorns foo`); -expectType>($({encoding: null}).sync`unicorns foo`); +expectType>($({encoding: 'buffer'})`unicorns foo`); +expectType>(await $({encoding: 'buffer'})`unicorns foo`); +expectType>($({encoding: 'buffer'}).sync`unicorns foo`); -expectType($({encoding: null})({encoding: 'utf8'})`unicorns`); -expectType(await $({encoding: null})({encoding: 'utf8'})`unicorns`); -expectType($({encoding: null})({encoding: 'utf8'}).sync`unicorns`); +expectType($({encoding: 'buffer'})({encoding: 'utf8'})`unicorns`); +expectType(await $({encoding: 'buffer'})({encoding: 'utf8'})`unicorns`); +expectType($({encoding: 'buffer'})({encoding: 'utf8'}).sync`unicorns`); -expectType($({encoding: null})({encoding: 'utf8'})`unicorns foo`); -expectType(await $({encoding: null})({encoding: 'utf8'})`unicorns foo`); -expectType($({encoding: null})({encoding: 'utf8'}).sync`unicorns foo`); +expectType($({encoding: 'buffer'})({encoding: 'utf8'})`unicorns foo`); +expectType(await $({encoding: 'buffer'})({encoding: 'utf8'})`unicorns foo`); +expectType($({encoding: 'buffer'})({encoding: 'utf8'}).sync`unicorns foo`); -expectType>($({encoding: null})({})`unicorns`); -expectType>(await $({encoding: null})({})`unicorns`); -expectType>($({encoding: null})({}).sync`unicorns`); +expectType>($({encoding: 'buffer'})({})`unicorns`); +expectType>(await $({encoding: 'buffer'})({})`unicorns`); +expectType>($({encoding: 'buffer'})({}).sync`unicorns`); -expectType>($({encoding: null})({})`unicorns foo`); -expectType>(await $({encoding: null})({})`unicorns foo`); -expectType>($({encoding: null})({}).sync`unicorns foo`); +expectType>($({encoding: 'buffer'})({})`unicorns foo`); +expectType>(await $({encoding: 'buffer'})({})`unicorns foo`); +expectType>($({encoding: 'buffer'})({}).sync`unicorns foo`); expectType(await $`unicorns ${'foo'}`); expectType($.sync`unicorns ${'foo'}`); diff --git a/lib/stream.js b/lib/stream.js index 5182cd030c..4e06459211 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -102,7 +102,7 @@ const getStreamPromise = (stream, {encoding, buffer, maxBuffer}) => { return getStream(stream, {maxBuffer}); } - if (encoding === null) { + if (encoding === null || encoding === 'buffer') { return getStreamAsBuffer(stream, {maxBuffer}); } diff --git a/readme.md b/readme.md index 7d8b15d61c..1babbe5f8e 100644 --- a/readme.md +++ b/readme.md @@ -682,7 +682,7 @@ We recommend against using this option since it is: Type: `string | null`\ 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. +Specify the character encoding used to decode the `stdout` and `stderr` output. If set to `'buffer'` or `null`, then `stdout` and `stderr` will be a `Buffer` instead of a string. #### timeout diff --git a/test/stream.js b/test/stream.js index dec62b0fe4..3911a0151f 100644 --- a/test/stream.js +++ b/test/stream.js @@ -11,6 +11,8 @@ import tempfile from 'tempfile'; import {execa, execaSync, $} from '../index.js'; import {setFixtureDir, FIXTURES_DIR} from './helpers/fixtures-dir.js'; +const pExec = promisify(exec); + setFixtureDir(); test('buffer', async t => { @@ -21,14 +23,15 @@ test('buffer', async t => { const checkEncoding = async (t, encoding) => { const {stdout} = await execa('noop-no-newline.js', [STRING_TO_ENCODE], {encoding}); - t.is(stdout, Buffer.from(STRING_TO_ENCODE).toString(encoding)); + t.is(stdout, BUFFER_TO_ENCODE.toString(encoding)); - const {stdout: nativeStdout} = await promisify(exec)(`node noop-no-newline.js ${STRING_TO_ENCODE}`, {encoding, cwd: FIXTURES_DIR}); + const {stdout: nativeStdout} = await pExec(`node noop-no-newline.js ${STRING_TO_ENCODE}`, {encoding, cwd: FIXTURES_DIR}); t.is(stdout, nativeStdout); }; // This string gives different outputs with each encoding type const STRING_TO_ENCODE = '\u1000.'; +const BUFFER_TO_ENCODE = Buffer.from(STRING_TO_ENCODE); test('can pass encoding "utf8"', checkEncoding, 'utf8'); test('can pass encoding "utf-8"', checkEncoding, 'utf8'); @@ -43,6 +46,21 @@ test('can pass encoding "hex"', checkEncoding, 'hex'); test('can pass encoding "base64"', checkEncoding, 'base64'); test('can pass encoding "base64url"', checkEncoding, 'base64url'); +const checkBufferEncoding = async (t, encoding) => { + const {stdout} = await execa('noop-no-newline.js', [STRING_TO_ENCODE], {encoding}); + t.true(BUFFER_TO_ENCODE.equals(stdout)); + + const {stdout: nativeStdout} = await pExec(`node noop-no-newline.js ${STRING_TO_ENCODE}`, {encoding, cwd: FIXTURES_DIR}); + t.true(BUFFER_TO_ENCODE.equals(nativeStdout)); +}; + +test('can pass encoding "buffer"', checkBufferEncoding, 'buffer'); +test('can pass encoding null', checkBufferEncoding, null); + +test('validate unknown encodings', async t => { + await t.throwsAsync(execa('noop.js', {encoding: 'unknownEncoding'}), {code: 'ERR_UNKNOWN_ENCODING'}); +}); + test('pass `stdout` to a file descriptor', async t => { const file = tempfile({extension: '.txt'}); await execa('noop.js', ['foo bar'], {stdout: fs.openSync(file, 'w')});