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<EncodingType> = { +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<EncodingType extends EncodingOption = DefaultEncodingOption> = { /** 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<EncodingType> = { 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<EncodingType> = { readonly verbose?: boolean; }; -export type Options<EncodingType = string> = { +export type Options<EncodingType extends EncodingOption = DefaultEncodingOption> = { /** Write some input to the `stdin` of your binary. @@ -269,7 +289,7 @@ export type Options<EncodingType = string> = { readonly inputFile?: string; } & CommonOptions<EncodingType>; -export type SyncOptions<EncodingType = string> = { +export type SyncOptions<EncodingType extends EncodingOption = DefaultEncodingOption> = { /** Write some input to the `stdin` of your binary. @@ -285,7 +305,7 @@ export type SyncOptions<EncodingType = string> = { readonly inputFile?: string; } & CommonOptions<EncodingType>; -export type NodeOptions<EncodingType = string> = { +export type NodeOptions<EncodingType extends EncodingOption = DefaultEncodingOption> = { /** The Node.js executable to use. @@ -625,10 +645,10 @@ export function execa( export function execa( file: string, arguments?: readonly string[], - options?: Options<null> + options?: Options<BufferEncodingOption> ): ExecaChildProcess<Buffer>; export function execa(file: string, options?: Options): ExecaChildProcess; -export function execa(file: string, options?: Options<null>): ExecaChildProcess<Buffer>; +export function execa(file: string, options?: Options<BufferEncodingOption>): ExecaChildProcess<Buffer>; /** Same as `execa()` but synchronous. @@ -698,12 +718,12 @@ export function execaSync( export function execaSync( file: string, arguments?: readonly string[], - options?: SyncOptions<null> + options?: SyncOptions<BufferEncodingOption> ): ExecaSyncReturnValue<Buffer>; export function execaSync(file: string, options?: SyncOptions): ExecaSyncReturnValue; export function execaSync( file: string, - options?: SyncOptions<null> + options?: SyncOptions<BufferEncodingOption> ): ExecaSyncReturnValue<Buffer>; /** @@ -729,7 +749,7 @@ console.log(stdout); ``` */ export function execaCommand(command: string, options?: Options): ExecaChildProcess; -export function execaCommand(command: string, options?: Options<null>): ExecaChildProcess<Buffer>; +export function execaCommand(command: string, options?: Options<BufferEncodingOption>): ExecaChildProcess<Buffer>; /** 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<null>): ExecaSyncReturnValue<Buffer>; +export function execaCommandSync(command: string, options?: SyncOptions<BufferEncodingOption>): ExecaSyncReturnValue<Buffer>; type TemplateExpression = | string @@ -783,7 +803,7 @@ type Execa$<StdoutStderrType extends StdoutStderrAll = string> = { */ (options: Options<undefined>): Execa$<StdoutStderrType>; (options: Options): Execa$; - (options: Options<null>): Execa$<Buffer>; + (options: Options<BufferEncodingOption>): Execa$<Buffer>; ( templates: TemplateStringsArray, ...expressions: TemplateExpression[] @@ -929,7 +949,7 @@ export function execaNode( export function execaNode( scriptPath: string, arguments?: readonly string[], - options?: NodeOptions<null> + options?: NodeOptions<BufferEncodingOption> ): ExecaChildProcess<Buffer>; export function execaNode(scriptPath: string, options?: NodeOptions): ExecaChildProcess; -export function execaNode(scriptPath: string, options?: NodeOptions<null>): ExecaChildProcess<Buffer>; +export function execaNode(scriptPath: string, options?: NodeOptions<BufferEncodingOption>): ExecaChildProcess<Buffer>; 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<ReadableStream | undefined>(execaPromise.all); - const execaBufferPromise = execa('unicorns', {encoding: null}); + const execaBufferPromise = execa('unicorns', {encoding: 'buffer'}); const writeStream = createWriteStream('output.txt'); expectAssignable<Function | undefined>(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<ExecaReturnValue>(await execa('unicorns')); expectType<ExecaReturnValue>( await execa('unicorns', {encoding: 'utf8'}), ); +expectType<ExecaReturnValue<Buffer>>(await execa('unicorns', {encoding: 'buffer'})); expectType<ExecaReturnValue<Buffer>>(await execa('unicorns', {encoding: null})); expectType<ExecaReturnValue>( await execa('unicorns', ['foo'], {encoding: 'utf8'}), ); +expectType<ExecaReturnValue<Buffer>>( + await execa('unicorns', ['foo'], {encoding: 'buffer'}), +); expectType<ExecaReturnValue<Buffer>>( await execa('unicorns', ['foo'], {encoding: null}), ); @@ -219,12 +224,18 @@ expectType<ExecaSyncReturnValue>(execaSync('unicorns')); expectType<ExecaSyncReturnValue>( execaSync('unicorns', {encoding: 'utf8'}), ); +expectType<ExecaSyncReturnValue<Buffer>>( + execaSync('unicorns', {encoding: 'buffer'}), +); expectType<ExecaSyncReturnValue<Buffer>>( execaSync('unicorns', {encoding: null}), ); expectType<ExecaSyncReturnValue>( execaSync('unicorns', ['foo'], {encoding: 'utf8'}), ); +expectType<ExecaSyncReturnValue<Buffer>>( + execaSync('unicorns', ['foo'], {encoding: 'buffer'}), +); expectType<ExecaSyncReturnValue<Buffer>>( execaSync('unicorns', ['foo'], {encoding: null}), ); @@ -232,14 +243,18 @@ expectType<ExecaSyncReturnValue<Buffer>>( expectType<ExecaChildProcess>(execaCommand('unicorns')); expectType<ExecaReturnValue>(await execaCommand('unicorns')); expectType<ExecaReturnValue>(await execaCommand('unicorns', {encoding: 'utf8'})); +expectType<ExecaReturnValue<Buffer>>(await execaCommand('unicorns', {encoding: 'buffer'})); expectType<ExecaReturnValue<Buffer>>(await execaCommand('unicorns', {encoding: null})); expectType<ExecaReturnValue>(await execaCommand('unicorns foo', {encoding: 'utf8'})); +expectType<ExecaReturnValue<Buffer>>(await execaCommand('unicorns foo', {encoding: 'buffer'})); expectType<ExecaReturnValue<Buffer>>(await execaCommand('unicorns foo', {encoding: null})); expectType<ExecaSyncReturnValue>(execaCommandSync('unicorns')); expectType<ExecaSyncReturnValue>(execaCommandSync('unicorns', {encoding: 'utf8'})); +expectType<ExecaSyncReturnValue<Buffer>>(execaCommandSync('unicorns', {encoding: 'buffer'})); expectType<ExecaSyncReturnValue<Buffer>>(execaCommandSync('unicorns', {encoding: null})); expectType<ExecaSyncReturnValue>(execaCommandSync('unicorns foo', {encoding: 'utf8'})); +expectType<ExecaSyncReturnValue<Buffer>>(execaCommandSync('unicorns foo', {encoding: 'buffer'})); expectType<ExecaSyncReturnValue<Buffer>>(execaCommandSync('unicorns foo', {encoding: null})); expectType<ExecaChildProcess>(execaNode('unicorns')); @@ -247,19 +262,29 @@ expectType<ExecaReturnValue>(await execaNode('unicorns')); expectType<ExecaReturnValue>( await execaNode('unicorns', {encoding: 'utf8'}), ); +expectType<ExecaReturnValue<Buffer>>(await execaNode('unicorns', {encoding: 'buffer'})); expectType<ExecaReturnValue<Buffer>>(await execaNode('unicorns', {encoding: null})); expectType<ExecaReturnValue>( await execaNode('unicorns', ['foo'], {encoding: 'utf8'}), ); +expectType<ExecaReturnValue<Buffer>>( + await execaNode('unicorns', ['foo'], {encoding: 'buffer'}), +); expectType<ExecaReturnValue<Buffer>>( await execaNode('unicorns', ['foo'], {encoding: null}), ); expectType<ExecaChildProcess>(execaNode('unicorns', {nodeOptions: ['--async-stack-traces']})); expectType<ExecaChildProcess>(execaNode('unicorns', ['foo'], {nodeOptions: ['--async-stack-traces']})); +expectType<ExecaChildProcess<Buffer>>( + execaNode('unicorns', {nodeOptions: ['--async-stack-traces'], encoding: 'buffer'}), +); expectType<ExecaChildProcess<Buffer>>( execaNode('unicorns', {nodeOptions: ['--async-stack-traces'], encoding: null}), ); +expectType<ExecaChildProcess<Buffer>>( + execaNode('unicorns', ['foo'], {nodeOptions: ['--async-stack-traces'], encoding: 'buffer'}), +); expectType<ExecaChildProcess<Buffer>>( execaNode('unicorns', ['foo'], {nodeOptions: ['--async-stack-traces'], encoding: null}), ); @@ -277,28 +302,29 @@ expectType<ExecaReturnValue>(await $({encoding: 'utf8'})`unicorns foo`); expectType<ExecaSyncReturnValue>($({encoding: 'utf8'}).sync`unicorns foo`); expectType<ExecaChildProcess<Buffer>>($({encoding: null})`unicorns`); -expectType<ExecaReturnValue<Buffer>>(await $({encoding: null})`unicorns`); -expectType<ExecaSyncReturnValue<Buffer>>($({encoding: null}).sync`unicorns`); +expectType<ExecaChildProcess<Buffer>>($({encoding: 'buffer'})`unicorns`); +expectType<ExecaReturnValue<Buffer>>(await $({encoding: 'buffer'})`unicorns`); +expectType<ExecaSyncReturnValue<Buffer>>($({encoding: 'buffer'}).sync`unicorns`); -expectType<ExecaChildProcess<Buffer>>($({encoding: null})`unicorns foo`); -expectType<ExecaReturnValue<Buffer>>(await $({encoding: null})`unicorns foo`); -expectType<ExecaSyncReturnValue<Buffer>>($({encoding: null}).sync`unicorns foo`); +expectType<ExecaChildProcess<Buffer>>($({encoding: 'buffer'})`unicorns foo`); +expectType<ExecaReturnValue<Buffer>>(await $({encoding: 'buffer'})`unicorns foo`); +expectType<ExecaSyncReturnValue<Buffer>>($({encoding: 'buffer'}).sync`unicorns foo`); -expectType<ExecaChildProcess>($({encoding: null})({encoding: 'utf8'})`unicorns`); -expectType<ExecaReturnValue>(await $({encoding: null})({encoding: 'utf8'})`unicorns`); -expectType<ExecaSyncReturnValue>($({encoding: null})({encoding: 'utf8'}).sync`unicorns`); +expectType<ExecaChildProcess>($({encoding: 'buffer'})({encoding: 'utf8'})`unicorns`); +expectType<ExecaReturnValue>(await $({encoding: 'buffer'})({encoding: 'utf8'})`unicorns`); +expectType<ExecaSyncReturnValue>($({encoding: 'buffer'})({encoding: 'utf8'}).sync`unicorns`); -expectType<ExecaChildProcess>($({encoding: null})({encoding: 'utf8'})`unicorns foo`); -expectType<ExecaReturnValue>(await $({encoding: null})({encoding: 'utf8'})`unicorns foo`); -expectType<ExecaSyncReturnValue>($({encoding: null})({encoding: 'utf8'}).sync`unicorns foo`); +expectType<ExecaChildProcess>($({encoding: 'buffer'})({encoding: 'utf8'})`unicorns foo`); +expectType<ExecaReturnValue>(await $({encoding: 'buffer'})({encoding: 'utf8'})`unicorns foo`); +expectType<ExecaSyncReturnValue>($({encoding: 'buffer'})({encoding: 'utf8'}).sync`unicorns foo`); -expectType<ExecaChildProcess<Buffer>>($({encoding: null})({})`unicorns`); -expectType<ExecaReturnValue<Buffer>>(await $({encoding: null})({})`unicorns`); -expectType<ExecaSyncReturnValue<Buffer>>($({encoding: null})({}).sync`unicorns`); +expectType<ExecaChildProcess<Buffer>>($({encoding: 'buffer'})({})`unicorns`); +expectType<ExecaReturnValue<Buffer>>(await $({encoding: 'buffer'})({})`unicorns`); +expectType<ExecaSyncReturnValue<Buffer>>($({encoding: 'buffer'})({}).sync`unicorns`); -expectType<ExecaChildProcess<Buffer>>($({encoding: null})({})`unicorns foo`); -expectType<ExecaReturnValue<Buffer>>(await $({encoding: null})({})`unicorns foo`); -expectType<ExecaSyncReturnValue<Buffer>>($({encoding: null})({}).sync`unicorns foo`); +expectType<ExecaChildProcess<Buffer>>($({encoding: 'buffer'})({})`unicorns foo`); +expectType<ExecaReturnValue<Buffer>>(await $({encoding: 'buffer'})({})`unicorns foo`); +expectType<ExecaSyncReturnValue<Buffer>>($({encoding: 'buffer'})({}).sync`unicorns foo`); expectType<ExecaReturnValue>(await $`unicorns ${'foo'}`); expectType<ExecaSyncReturnValue>($.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')});