Skip to content

Commit 523cf9b

Browse files
committed
Fix execaNode() result types when the node option is undefined
`execaNode()` forces the `node` option, but the returned type kept the `undefined` value of the call, which made the result's output types wrong.
1 parent 580b250 commit 523cf9b

2 files changed

Lines changed: 65 additions & 2 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import type {Readable, Writable} from 'node:stream';
2+
import {expectTypeOf} from 'nz';
3+
import {execaNode, type sendMessage, type getOneMessage} from '../../index.js';
4+
5+
const result = await execaNode('unicorns', {node: undefined});
6+
expectTypeOf(result.stdout).toBe<string>();
7+
const argumentsResult = await execaNode('unicorns', [], {node: undefined});
8+
expectTypeOf(argumentsResult.stdout).toBe<string>();
9+
const boundResult = await execaNode({node: undefined})`unicorns`;
10+
expectTypeOf(boundResult.stdout).toBe<string>();
11+
12+
const subprocess = execaNode('unicorns', {node: undefined});
13+
expectTypeOf(subprocess.stdin).toBe<Writable>();
14+
expectTypeOf(subprocess.stdout).toBe<Readable>();
15+
expectTypeOf(subprocess.stdio[3]).toBe<null>();
16+
expectTypeOf(subprocess.sendMessage).toBeAssignableTo<typeof sendMessage>();
17+
expectTypeOf(subprocess.getOneMessage).toBeAssignableTo<typeof getOneMessage>();
18+
expectTypeOf(result.stderr).toBe<string>();
19+
expectTypeOf(result.failed).toBe<boolean>();
20+
21+
const binaryResult = await execaNode('unicorns', {node: undefined, encoding: 'buffer'});
22+
expectTypeOf(binaryResult.stdout).toBe<Uint8Array>();
23+
const linesResult = await execaNode({node: undefined, lines: true})`unicorns`;
24+
expectTypeOf(linesResult.stdout).toBe<string[]>();
25+
const reboundResult = await execaNode({node: true})({node: undefined})('unicorns');
26+
expectTypeOf(reboundResult.stdout).toBe<string>();
27+
const callOverrideResult = await execaNode({node: true})('unicorns', [], {node: undefined});
28+
expectTypeOf(callOverrideResult.stdout).toBe<string>();
29+
30+
const optionalNode: {node?: undefined} = {};
31+
const optionalResult = await execaNode('unicorns', optionalNode);
32+
expectTypeOf(optionalResult.stdout).toBe<string>();
33+
expectTypeOf(execaNode(optionalNode)`unicorns`.sendMessage).toBeAssignableTo<typeof sendMessage>();
34+
declare const nodeOutputOptions: {node: undefined; lines: true} | {node: undefined; encoding: 'buffer'};
35+
const unionResult = await execaNode('unicorns', nodeOutputOptions);
36+
expectTypeOf(unionResult.stdout).toBe<string[] | Uint8Array>();
37+
38+
// Omitting `node` or passing `true` preserves output options and the explicit IPC override.
39+
const defaultResult = await execaNode('unicorns');
40+
expectTypeOf(defaultResult.stdout).toBe<string>();
41+
const explicitResult = await execaNode('unicorns', {node: true});
42+
expectTypeOf(explicitResult.stdout).toBe<string>();
43+
const explicitBinaryResult = await execaNode('unicorns', {node: true, encoding: 'buffer'});
44+
expectTypeOf(explicitBinaryResult.stdout).toBe<Uint8Array>();
45+
const explicitLinesResult = await execaNode({node: true, lines: true})`unicorns`;
46+
expectTypeOf(explicitLinesResult.stdout).toBe<string[]>();
47+
expectTypeOf(execaNode('unicorns').sendMessage).toBeAssignableTo<typeof sendMessage>();
48+
expectTypeOf(execaNode('unicorns', {node: true}).stdio[3]).toBe<null>();
49+
expectTypeOf(execaNode('unicorns', {ipc: false}).sendMessage).toBe<undefined>();
50+
expectTypeOf(execaNode('unicorns', {node: true, ipc: false}).sendMessage).toBe<undefined>();
51+
declare const nodeOptions: {node: undefined} | {node: true};
52+
const nodeUnionResult = await execaNode('unicorns', nodeOptions);
53+
expectTypeOf(nodeUnionResult.stdout).toBe<string>();
54+
55+
// `node: false` throws at runtime
56+
expectTypeOf(execaNode('unicorns', {node: false})).toBe<never>();
57+
expectTypeOf(execaNode({node: false})`unicorns`).toBe<never>();

‎types/methods/node.d.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,15 @@ type ExecaNodeArrayShort<OptionsType extends Options> =
6262
<NewOptionsType extends Options = {}>(scriptPath: string | URL, options?: NewOptionsType)
6363
=> ResultPromise<NodeOptions<MergeOptions<OptionsType, NewOptionsType, Options>>>;
6464

65-
// `execaNode()` always uses the `node` option, which also enables the `ipc` option by default
65+
// `execaNode()` always uses the `node` option, which also enables the `ipc` option by default.
66+
// `node: false` throws at runtime.
67+
// Any other `node` value is replaced instead of intersected, since `{node: undefined} & {node: true}` would be `never`. The result is flattened, so it is a plain object type.
6668
type NodeOptions<OptionsType extends Options> = OptionsType extends Options
67-
? OptionsType & {readonly node: true}
69+
? OptionsType extends {readonly node: false}
70+
? never
71+
: ({[Key in keyof OptionsType]: Key extends 'node' ? true : OptionsType[Key]} & {readonly node: true}) extends infer ForcedOptions
72+
? {[Key in keyof ForcedOptions]: ForcedOptions[Key]}
73+
: never
6874
: never;
6975

7076
export {};

0 commit comments

Comments
 (0)