Skip to content

Commit 89c69fe

Browse files
authored
Support {encoding: 'buffer'} (#572)
1 parent e7dee28 commit 89c69fe

File tree

5 files changed

+100
-36
lines changed

5 files changed

+100
-36
lines changed

index.d.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,27 @@ export type StdioOption =
1212
| number
1313
| undefined;
1414

15-
export type CommonOptions<EncodingType> = {
15+
type EncodingOption =
16+
| 'utf8'
17+
// eslint-disable-next-line unicorn/text-encoding-identifier-case
18+
| 'utf-8'
19+
| 'utf16le'
20+
| 'utf-16le'
21+
| 'ucs2'
22+
| 'ucs-2'
23+
| 'latin1'
24+
| 'binary'
25+
| 'ascii'
26+
| 'hex'
27+
| 'base64'
28+
| 'base64url'
29+
| 'buffer'
30+
| null
31+
| undefined;
32+
type DefaultEncodingOption = 'utf8';
33+
type BufferEncodingOption = 'buffer' | null;
34+
35+
export type CommonOptions<EncodingType extends EncodingOption = DefaultEncodingOption> = {
1636
/**
1737
Kill the spawned process when the parent process exits unless either:
1838
- the spawned process is [`detached`](https://nodejs.org/api/child_process.html#child_process_options_detached)
@@ -176,7 +196,7 @@ export type CommonOptions<EncodingType> = {
176196
readonly shell?: boolean | string;
177197

178198
/**
179-
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.
199+
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.
180200
181201
@default 'utf8'
182202
*/
@@ -253,7 +273,7 @@ export type CommonOptions<EncodingType> = {
253273
readonly verbose?: boolean;
254274
};
255275

256-
export type Options<EncodingType = string> = {
276+
export type Options<EncodingType extends EncodingOption = DefaultEncodingOption> = {
257277
/**
258278
Write some input to the `stdin` of your binary.
259279
@@ -269,7 +289,7 @@ export type Options<EncodingType = string> = {
269289
readonly inputFile?: string;
270290
} & CommonOptions<EncodingType>;
271291

272-
export type SyncOptions<EncodingType = string> = {
292+
export type SyncOptions<EncodingType extends EncodingOption = DefaultEncodingOption> = {
273293
/**
274294
Write some input to the `stdin` of your binary.
275295
@@ -285,7 +305,7 @@ export type SyncOptions<EncodingType = string> = {
285305
readonly inputFile?: string;
286306
} & CommonOptions<EncodingType>;
287307

288-
export type NodeOptions<EncodingType = string> = {
308+
export type NodeOptions<EncodingType extends EncodingOption = DefaultEncodingOption> = {
289309
/**
290310
The Node.js executable to use.
291311
@@ -625,10 +645,10 @@ export function execa(
625645
export function execa(
626646
file: string,
627647
arguments?: readonly string[],
628-
options?: Options<null>
648+
options?: Options<BufferEncodingOption>
629649
): ExecaChildProcess<Buffer>;
630650
export function execa(file: string, options?: Options): ExecaChildProcess;
631-
export function execa(file: string, options?: Options<null>): ExecaChildProcess<Buffer>;
651+
export function execa(file: string, options?: Options<BufferEncodingOption>): ExecaChildProcess<Buffer>;
632652

633653
/**
634654
Same as `execa()` but synchronous.
@@ -698,12 +718,12 @@ export function execaSync(
698718
export function execaSync(
699719
file: string,
700720
arguments?: readonly string[],
701-
options?: SyncOptions<null>
721+
options?: SyncOptions<BufferEncodingOption>
702722
): ExecaSyncReturnValue<Buffer>;
703723
export function execaSync(file: string, options?: SyncOptions): ExecaSyncReturnValue;
704724
export function execaSync(
705725
file: string,
706-
options?: SyncOptions<null>
726+
options?: SyncOptions<BufferEncodingOption>
707727
): ExecaSyncReturnValue<Buffer>;
708728

709729
/**
@@ -729,7 +749,7 @@ console.log(stdout);
729749
```
730750
*/
731751
export function execaCommand(command: string, options?: Options): ExecaChildProcess;
732-
export function execaCommand(command: string, options?: Options<null>): ExecaChildProcess<Buffer>;
752+
export function execaCommand(command: string, options?: Options<BufferEncodingOption>): ExecaChildProcess<Buffer>;
733753

734754
/**
735755
Same as `execaCommand()` but synchronous.
@@ -748,7 +768,7 @@ console.log(stdout);
748768
```
749769
*/
750770
export function execaCommandSync(command: string, options?: SyncOptions): ExecaSyncReturnValue;
751-
export function execaCommandSync(command: string, options?: SyncOptions<null>): ExecaSyncReturnValue<Buffer>;
771+
export function execaCommandSync(command: string, options?: SyncOptions<BufferEncodingOption>): ExecaSyncReturnValue<Buffer>;
752772

753773
type TemplateExpression =
754774
| string
@@ -783,7 +803,7 @@ type Execa$<StdoutStderrType extends StdoutStderrAll = string> = {
783803
*/
784804
(options: Options<undefined>): Execa$<StdoutStderrType>;
785805
(options: Options): Execa$;
786-
(options: Options<null>): Execa$<Buffer>;
806+
(options: Options<BufferEncodingOption>): Execa$<Buffer>;
787807
(
788808
templates: TemplateStringsArray,
789809
...expressions: TemplateExpression[]
@@ -929,7 +949,7 @@ export function execaNode(
929949
export function execaNode(
930950
scriptPath: string,
931951
arguments?: readonly string[],
932-
options?: NodeOptions<null>
952+
options?: NodeOptions<BufferEncodingOption>
933953
): ExecaChildProcess<Buffer>;
934954
export function execaNode(scriptPath: string, options?: NodeOptions): ExecaChildProcess;
935-
export function execaNode(scriptPath: string, options?: NodeOptions<null>): ExecaChildProcess<Buffer>;
955+
export function execaNode(scriptPath: string, options?: NodeOptions<BufferEncodingOption>): ExecaChildProcess<Buffer>;

index.test-d.ts

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ try {
2525
execaPromise.cancel();
2626
expectType<ReadableStream | undefined>(execaPromise.all);
2727

28-
const execaBufferPromise = execa('unicorns', {encoding: null});
28+
const execaBufferPromise = execa('unicorns', {encoding: 'buffer'});
2929
const writeStream = createWriteStream('output.txt');
3030

3131
expectAssignable<Function | undefined>(execaPromise.pipeStdout);
@@ -133,6 +133,7 @@ execa('unicorns', {cleanup: false});
133133
execa('unicorns', {preferLocal: false});
134134
execa('unicorns', {localDir: '.'});
135135
execa('unicorns', {localDir: new URL('file:///test')});
136+
expectError(execa('unicorns', {encoding: 'unknownEncoding'}));
136137
execa('unicorns', {execPath: '/path'});
137138
execa('unicorns', {buffer: false});
138139
execa('unicorns', {input: ''});
@@ -207,10 +208,14 @@ expectType<ExecaReturnValue>(await execa('unicorns'));
207208
expectType<ExecaReturnValue>(
208209
await execa('unicorns', {encoding: 'utf8'}),
209210
);
211+
expectType<ExecaReturnValue<Buffer>>(await execa('unicorns', {encoding: 'buffer'}));
210212
expectType<ExecaReturnValue<Buffer>>(await execa('unicorns', {encoding: null}));
211213
expectType<ExecaReturnValue>(
212214
await execa('unicorns', ['foo'], {encoding: 'utf8'}),
213215
);
216+
expectType<ExecaReturnValue<Buffer>>(
217+
await execa('unicorns', ['foo'], {encoding: 'buffer'}),
218+
);
214219
expectType<ExecaReturnValue<Buffer>>(
215220
await execa('unicorns', ['foo'], {encoding: null}),
216221
);
@@ -219,47 +224,67 @@ expectType<ExecaSyncReturnValue>(execaSync('unicorns'));
219224
expectType<ExecaSyncReturnValue>(
220225
execaSync('unicorns', {encoding: 'utf8'}),
221226
);
227+
expectType<ExecaSyncReturnValue<Buffer>>(
228+
execaSync('unicorns', {encoding: 'buffer'}),
229+
);
222230
expectType<ExecaSyncReturnValue<Buffer>>(
223231
execaSync('unicorns', {encoding: null}),
224232
);
225233
expectType<ExecaSyncReturnValue>(
226234
execaSync('unicorns', ['foo'], {encoding: 'utf8'}),
227235
);
236+
expectType<ExecaSyncReturnValue<Buffer>>(
237+
execaSync('unicorns', ['foo'], {encoding: 'buffer'}),
238+
);
228239
expectType<ExecaSyncReturnValue<Buffer>>(
229240
execaSync('unicorns', ['foo'], {encoding: null}),
230241
);
231242

232243
expectType<ExecaChildProcess>(execaCommand('unicorns'));
233244
expectType<ExecaReturnValue>(await execaCommand('unicorns'));
234245
expectType<ExecaReturnValue>(await execaCommand('unicorns', {encoding: 'utf8'}));
246+
expectType<ExecaReturnValue<Buffer>>(await execaCommand('unicorns', {encoding: 'buffer'}));
235247
expectType<ExecaReturnValue<Buffer>>(await execaCommand('unicorns', {encoding: null}));
236248
expectType<ExecaReturnValue>(await execaCommand('unicorns foo', {encoding: 'utf8'}));
249+
expectType<ExecaReturnValue<Buffer>>(await execaCommand('unicorns foo', {encoding: 'buffer'}));
237250
expectType<ExecaReturnValue<Buffer>>(await execaCommand('unicorns foo', {encoding: null}));
238251

239252
expectType<ExecaSyncReturnValue>(execaCommandSync('unicorns'));
240253
expectType<ExecaSyncReturnValue>(execaCommandSync('unicorns', {encoding: 'utf8'}));
254+
expectType<ExecaSyncReturnValue<Buffer>>(execaCommandSync('unicorns', {encoding: 'buffer'}));
241255
expectType<ExecaSyncReturnValue<Buffer>>(execaCommandSync('unicorns', {encoding: null}));
242256
expectType<ExecaSyncReturnValue>(execaCommandSync('unicorns foo', {encoding: 'utf8'}));
257+
expectType<ExecaSyncReturnValue<Buffer>>(execaCommandSync('unicorns foo', {encoding: 'buffer'}));
243258
expectType<ExecaSyncReturnValue<Buffer>>(execaCommandSync('unicorns foo', {encoding: null}));
244259

245260
expectType<ExecaChildProcess>(execaNode('unicorns'));
246261
expectType<ExecaReturnValue>(await execaNode('unicorns'));
247262
expectType<ExecaReturnValue>(
248263
await execaNode('unicorns', {encoding: 'utf8'}),
249264
);
265+
expectType<ExecaReturnValue<Buffer>>(await execaNode('unicorns', {encoding: 'buffer'}));
250266
expectType<ExecaReturnValue<Buffer>>(await execaNode('unicorns', {encoding: null}));
251267
expectType<ExecaReturnValue>(
252268
await execaNode('unicorns', ['foo'], {encoding: 'utf8'}),
253269
);
270+
expectType<ExecaReturnValue<Buffer>>(
271+
await execaNode('unicorns', ['foo'], {encoding: 'buffer'}),
272+
);
254273
expectType<ExecaReturnValue<Buffer>>(
255274
await execaNode('unicorns', ['foo'], {encoding: null}),
256275
);
257276

258277
expectType<ExecaChildProcess>(execaNode('unicorns', {nodeOptions: ['--async-stack-traces']}));
259278
expectType<ExecaChildProcess>(execaNode('unicorns', ['foo'], {nodeOptions: ['--async-stack-traces']}));
279+
expectType<ExecaChildProcess<Buffer>>(
280+
execaNode('unicorns', {nodeOptions: ['--async-stack-traces'], encoding: 'buffer'}),
281+
);
260282
expectType<ExecaChildProcess<Buffer>>(
261283
execaNode('unicorns', {nodeOptions: ['--async-stack-traces'], encoding: null}),
262284
);
285+
expectType<ExecaChildProcess<Buffer>>(
286+
execaNode('unicorns', ['foo'], {nodeOptions: ['--async-stack-traces'], encoding: 'buffer'}),
287+
);
263288
expectType<ExecaChildProcess<Buffer>>(
264289
execaNode('unicorns', ['foo'], {nodeOptions: ['--async-stack-traces'], encoding: null}),
265290
);
@@ -277,28 +302,29 @@ expectType<ExecaReturnValue>(await $({encoding: 'utf8'})`unicorns foo`);
277302
expectType<ExecaSyncReturnValue>($({encoding: 'utf8'}).sync`unicorns foo`);
278303

279304
expectType<ExecaChildProcess<Buffer>>($({encoding: null})`unicorns`);
280-
expectType<ExecaReturnValue<Buffer>>(await $({encoding: null})`unicorns`);
281-
expectType<ExecaSyncReturnValue<Buffer>>($({encoding: null}).sync`unicorns`);
305+
expectType<ExecaChildProcess<Buffer>>($({encoding: 'buffer'})`unicorns`);
306+
expectType<ExecaReturnValue<Buffer>>(await $({encoding: 'buffer'})`unicorns`);
307+
expectType<ExecaSyncReturnValue<Buffer>>($({encoding: 'buffer'}).sync`unicorns`);
282308

283-
expectType<ExecaChildProcess<Buffer>>($({encoding: null})`unicorns foo`);
284-
expectType<ExecaReturnValue<Buffer>>(await $({encoding: null})`unicorns foo`);
285-
expectType<ExecaSyncReturnValue<Buffer>>($({encoding: null}).sync`unicorns foo`);
309+
expectType<ExecaChildProcess<Buffer>>($({encoding: 'buffer'})`unicorns foo`);
310+
expectType<ExecaReturnValue<Buffer>>(await $({encoding: 'buffer'})`unicorns foo`);
311+
expectType<ExecaSyncReturnValue<Buffer>>($({encoding: 'buffer'}).sync`unicorns foo`);
286312

287-
expectType<ExecaChildProcess>($({encoding: null})({encoding: 'utf8'})`unicorns`);
288-
expectType<ExecaReturnValue>(await $({encoding: null})({encoding: 'utf8'})`unicorns`);
289-
expectType<ExecaSyncReturnValue>($({encoding: null})({encoding: 'utf8'}).sync`unicorns`);
313+
expectType<ExecaChildProcess>($({encoding: 'buffer'})({encoding: 'utf8'})`unicorns`);
314+
expectType<ExecaReturnValue>(await $({encoding: 'buffer'})({encoding: 'utf8'})`unicorns`);
315+
expectType<ExecaSyncReturnValue>($({encoding: 'buffer'})({encoding: 'utf8'}).sync`unicorns`);
290316

291-
expectType<ExecaChildProcess>($({encoding: null})({encoding: 'utf8'})`unicorns foo`);
292-
expectType<ExecaReturnValue>(await $({encoding: null})({encoding: 'utf8'})`unicorns foo`);
293-
expectType<ExecaSyncReturnValue>($({encoding: null})({encoding: 'utf8'}).sync`unicorns foo`);
317+
expectType<ExecaChildProcess>($({encoding: 'buffer'})({encoding: 'utf8'})`unicorns foo`);
318+
expectType<ExecaReturnValue>(await $({encoding: 'buffer'})({encoding: 'utf8'})`unicorns foo`);
319+
expectType<ExecaSyncReturnValue>($({encoding: 'buffer'})({encoding: 'utf8'}).sync`unicorns foo`);
294320

295-
expectType<ExecaChildProcess<Buffer>>($({encoding: null})({})`unicorns`);
296-
expectType<ExecaReturnValue<Buffer>>(await $({encoding: null})({})`unicorns`);
297-
expectType<ExecaSyncReturnValue<Buffer>>($({encoding: null})({}).sync`unicorns`);
321+
expectType<ExecaChildProcess<Buffer>>($({encoding: 'buffer'})({})`unicorns`);
322+
expectType<ExecaReturnValue<Buffer>>(await $({encoding: 'buffer'})({})`unicorns`);
323+
expectType<ExecaSyncReturnValue<Buffer>>($({encoding: 'buffer'})({}).sync`unicorns`);
298324

299-
expectType<ExecaChildProcess<Buffer>>($({encoding: null})({})`unicorns foo`);
300-
expectType<ExecaReturnValue<Buffer>>(await $({encoding: null})({})`unicorns foo`);
301-
expectType<ExecaSyncReturnValue<Buffer>>($({encoding: null})({}).sync`unicorns foo`);
325+
expectType<ExecaChildProcess<Buffer>>($({encoding: 'buffer'})({})`unicorns foo`);
326+
expectType<ExecaReturnValue<Buffer>>(await $({encoding: 'buffer'})({})`unicorns foo`);
327+
expectType<ExecaSyncReturnValue<Buffer>>($({encoding: 'buffer'})({}).sync`unicorns foo`);
302328

303329
expectType<ExecaReturnValue>(await $`unicorns ${'foo'}`);
304330
expectType<ExecaSyncReturnValue>($.sync`unicorns ${'foo'}`);

lib/stream.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ const getStreamPromise = (stream, {encoding, buffer, maxBuffer}) => {
102102
return getStream(stream, {maxBuffer});
103103
}
104104

105-
if (encoding === null) {
105+
if (encoding === null || encoding === 'buffer') {
106106
return getStreamAsBuffer(stream, {maxBuffer});
107107
}
108108

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ We recommend against using this option since it is:
682682
Type: `string | null`\
683683
Default: `utf8`
684684

685-
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.
685+
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.
686686

687687
#### timeout
688688

test/stream.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import tempfile from 'tempfile';
1111
import {execa, execaSync, $} from '../index.js';
1212
import {setFixtureDir, FIXTURES_DIR} from './helpers/fixtures-dir.js';
1313

14+
const pExec = promisify(exec);
15+
1416
setFixtureDir();
1517

1618
test('buffer', async t => {
@@ -21,14 +23,15 @@ test('buffer', async t => {
2123

2224
const checkEncoding = async (t, encoding) => {
2325
const {stdout} = await execa('noop-no-newline.js', [STRING_TO_ENCODE], {encoding});
24-
t.is(stdout, Buffer.from(STRING_TO_ENCODE).toString(encoding));
26+
t.is(stdout, BUFFER_TO_ENCODE.toString(encoding));
2527

26-
const {stdout: nativeStdout} = await promisify(exec)(`node noop-no-newline.js ${STRING_TO_ENCODE}`, {encoding, cwd: FIXTURES_DIR});
28+
const {stdout: nativeStdout} = await pExec(`node noop-no-newline.js ${STRING_TO_ENCODE}`, {encoding, cwd: FIXTURES_DIR});
2729
t.is(stdout, nativeStdout);
2830
};
2931

3032
// This string gives different outputs with each encoding type
3133
const STRING_TO_ENCODE = '\u1000.';
34+
const BUFFER_TO_ENCODE = Buffer.from(STRING_TO_ENCODE);
3235

3336
test('can pass encoding "utf8"', checkEncoding, 'utf8');
3437
test('can pass encoding "utf-8"', checkEncoding, 'utf8');
@@ -43,6 +46,21 @@ test('can pass encoding "hex"', checkEncoding, 'hex');
4346
test('can pass encoding "base64"', checkEncoding, 'base64');
4447
test('can pass encoding "base64url"', checkEncoding, 'base64url');
4548

49+
const checkBufferEncoding = async (t, encoding) => {
50+
const {stdout} = await execa('noop-no-newline.js', [STRING_TO_ENCODE], {encoding});
51+
t.true(BUFFER_TO_ENCODE.equals(stdout));
52+
53+
const {stdout: nativeStdout} = await pExec(`node noop-no-newline.js ${STRING_TO_ENCODE}`, {encoding, cwd: FIXTURES_DIR});
54+
t.true(BUFFER_TO_ENCODE.equals(nativeStdout));
55+
};
56+
57+
test('can pass encoding "buffer"', checkBufferEncoding, 'buffer');
58+
test('can pass encoding null', checkBufferEncoding, null);
59+
60+
test('validate unknown encodings', async t => {
61+
await t.throwsAsync(execa('noop.js', {encoding: 'unknownEncoding'}), {code: 'ERR_UNKNOWN_ENCODING'});
62+
});
63+
4664
test('pass `stdout` to a file descriptor', async t => {
4765
const file = tempfile({extension: '.txt'});
4866
await execa('noop.js', ['foo bar'], {stdout: fs.openSync(file, 'w')});

0 commit comments

Comments
 (0)