Skip to content

Commit 2633abb

Browse files
committed
Fix execaNode() and the node option missing IPC in their types
`execaNode()` and the `node` option enable the `ipc` option by default, but their types did not: `subprocess.sendMessage()` was `undefined`, `result.ipcOutput` was `[]`, and `result.stdio` and `subprocess.stdio` missed the IPC channel's file descriptor. Also, `ipc: undefined` disabled IPC with the `node` option, instead of using the default value.
1 parent b2c3a54 commit 2633abb

13 files changed

Lines changed: 99 additions & 36 deletions

File tree

‎lib/methods/node.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ export const handleNodeOption = (file, commandArguments, {
8282
],
8383
{
8484
__proto__: null,
85-
ipc: true,
8685
...newOptions,
86+
// Like other options, `undefined` uses the default value
87+
ipc: newOptions.ipc === undefined ? true : newOptions.ipc,
8788
shell: false,
8889
},
8990
];

‎test-d/ipc/send.test-d.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {expectTypeOf, expectError} from 'nz';
22
import {
33
sendMessage,
44
execa,
5+
execaNode,
56
type Message,
67
type Options,
78
} from '../../index.js';
@@ -22,6 +23,9 @@ expectError(await sendMessage(Symbol('test')));
2223
await execa('test', {ipcInput: ''}).sendMessage('');
2324
await execa('test', {ipcInput: '' as Message}).sendMessage('');
2425
await execa('test', {gracefulCancel: true, cancelSignal: AbortSignal.abort()}).sendMessage('');
26+
await execa('test', {node: true}).sendMessage('');
27+
await execaNode('test').sendMessage('');
28+
await execaNode({})`test`.sendMessage('');
2529
await execa('test', {} as Options).sendMessage?.('');
2630
await execa('test', {ipc: true as boolean}).sendMessage?.('');
2731
await execa('test', {ipcInput: '' as '' | undefined}).sendMessage?.('');
@@ -35,6 +39,9 @@ expectTypeOf(execa('test', {gracefulCancel: undefined}).sendMessage).toBe<undefi
3539
expectTypeOf(execa('test', {gracefulCancel: false}).sendMessage).toBe<undefined>();
3640
expectTypeOf(execa('test', {ipc: false, ipcInput: ''}).sendMessage).toBe<undefined>();
3741
expectTypeOf(execa('test', {ipc: false, gracefulCancel: true, cancelSignal: AbortSignal.abort()}).sendMessage).toBe<undefined>();
42+
expectTypeOf(execa('test', {node: false}).sendMessage).toBe<undefined>();
43+
expectTypeOf(execa('test', {ipc: false, node: true}).sendMessage).toBe<undefined>();
44+
expectTypeOf(execaNode('test', {ipc: false}).sendMessage).toBe<undefined>();
3845

3946
await subprocess.sendMessage('', {} as const);
4047
await sendMessage('', {} as const);

‎test-d/methods/command.test-d.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ expectTypeOf(await execa`${parseCommandString('unicorns foo bar')}`).toBe<Result
2020
expectTypeOf(execaSync`${parseCommandString('unicorns foo bar')}`).toBe<SyncResult<{}>>();
2121
expectTypeOf(await $`${parseCommandString('unicorns foo bar')}`).toBe<Result<{}>>();
2222
expectTypeOf($.sync`${parseCommandString('unicorns foo bar')}`).toBe<SyncResult<{}>>();
23-
expectTypeOf(await execaNode`${parseCommandString('foo bar')}`).toBe<Result<{}>>();
23+
expectTypeOf(await execaNode`${parseCommandString('foo bar')}`).toBe<Result<{readonly node: true}>>();
2424

2525
expectTypeOf(await execa`unicorns ${parseCommandString('foo bar')}`).toBe<Result<{}>>();
2626
expectTypeOf(await execa('unicorns', parseCommandString('foo bar'))).toBe<Result<{}>>();

‎test-d/methods/node.test-d.ts‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ expectError(execaNode('unicorns', [], []));
2121
expectError(execaNode('unicorns', {other: true}));
2222

2323
expectTypeOf(execaNode`unicorns`).toBeAssignableTo<ResultPromise>();
24-
expectTypeOf(await execaNode('unicorns')).toBe<Result<{}>>();
25-
expectTypeOf(await execaNode`unicorns`).toBe<Result<{}>>();
24+
expectTypeOf(await execaNode('unicorns')).toBe<Result<{readonly node: true}>>();
25+
expectTypeOf(await execaNode`unicorns`).toBe<Result<{readonly node: true}>>();
2626

2727
expectTypeOf(execaNode({})).toBeAssignableTo<typeof execaNode>();
2828
expectTypeOf(execaNode({})('unicorns')).toBeAssignableTo<ResultPromise>();
@@ -41,19 +41,19 @@ expectTypeOf(await execaNode({encoding: 'buffer'})`unicorns`).toBeAssignableTo<{
4141
expectTypeOf(await execaNode({})({encoding: 'buffer'})`unicorns`).toBeAssignableTo<{stdout: Uint8Array}>();
4242
expectTypeOf(await execaNode({encoding: 'buffer'})({})`unicorns`).toBeAssignableTo<{stdout: Uint8Array}>();
4343

44-
expectTypeOf(await execaNode`${'unicorns'}`).toBe<Result<{}>>();
45-
expectTypeOf(await execaNode`unicorns ${'foo'}`).toBe<Result<{}>>();
46-
expectTypeOf(await execaNode`unicorns ${'foo'} ${'bar'}`).toBe<Result<{}>>();
47-
expectTypeOf(await execaNode`unicorns ${1}`).toBe<Result<{}>>();
48-
expectTypeOf(await execaNode`unicorns ${stringArray}`).toBe<Result<{}>>();
49-
expectTypeOf(await execaNode`unicorns ${[1, 2]}`).toBe<Result<{}>>();
50-
expectTypeOf(await execaNode`unicorns ${false.toString()}`).toBe<Result<{}>>();
44+
expectTypeOf(await execaNode`${'unicorns'}`).toBe<Result<{readonly node: true}>>();
45+
expectTypeOf(await execaNode`unicorns ${'foo'}`).toBe<Result<{readonly node: true}>>();
46+
expectTypeOf(await execaNode`unicorns ${'foo'} ${'bar'}`).toBe<Result<{readonly node: true}>>();
47+
expectTypeOf(await execaNode`unicorns ${1}`).toBe<Result<{readonly node: true}>>();
48+
expectTypeOf(await execaNode`unicorns ${stringArray}`).toBe<Result<{readonly node: true}>>();
49+
expectTypeOf(await execaNode`unicorns ${[1, 2]}`).toBe<Result<{readonly node: true}>>();
50+
expectTypeOf(await execaNode`unicorns ${false.toString()}`).toBe<Result<{readonly node: true}>>();
5151
expectError(await execaNode`unicorns ${false}`);
5252

53-
expectTypeOf(await execaNode`unicorns ${await execaNode`echo foo`}`).toBe<Result<{}>>();
54-
expectTypeOf(await execaNode`unicorns ${await execaNode({reject: false})`echo foo`}`).toBe<Result<{}>>();
53+
expectTypeOf(await execaNode`unicorns ${await execaNode`echo foo`}`).toBe<Result<{readonly node: true}>>();
54+
expectTypeOf(await execaNode`unicorns ${await execaNode({reject: false})`echo foo`}`).toBe<Result<{readonly node: true}>>();
5555
expectError(await execaNode`unicorns ${execaNode`echo foo`}`);
56-
expectTypeOf(await execaNode`unicorns ${[await execaNode`echo foo`, 'bar']}`).toBe<Result<{}>>();
56+
expectTypeOf(await execaNode`unicorns ${[await execaNode`echo foo`, 'bar']}`).toBe<Result<{readonly node: true}>>();
5757
expectError(await execaNode`unicorns ${[execaNode`echo foo`, 'bar']}`);
5858

5959
expectTypeOf(execaNode('unicorns', {nodePath: './node'})).toBeAssignableTo<ResultPromise>();

‎test-d/return/result-ipc.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {expectTypeOf} from 'nz';
22
import {
33
execa,
44
execaSync,
5+
execaNode,
56
type Result,
67
type SyncResult,
78
type ExecaError,
@@ -31,6 +32,15 @@ expectTypeOf(genericInputResult.ipcOutput).toBe<Array<Message<'advanced'>>>();
3132
const gracefulResult = await execa('unicorns', {gracefulCancel: true, cancelSignal: AbortSignal.abort()});
3233
expectTypeOf(gracefulResult.ipcOutput).toBe<Array<Message<'advanced'>>>();
3334

35+
const nodeResult = await execa('unicorns', {node: true});
36+
expectTypeOf(nodeResult.ipcOutput).toBe<Array<Message<'advanced'>>>();
37+
38+
const execaNodeResult = await execaNode('unicorns');
39+
expectTypeOf(execaNodeResult.ipcOutput).toBe<Array<Message<'advanced'>>>();
40+
41+
const execaNodeJsonResult = await execaNode({serialization: 'json'})`unicorns`;
42+
expectTypeOf(execaNodeJsonResult.ipcOutput).toBe<Array<Message<'json'>>>();
43+
3444
const genericResult = await execa('unicorns', {} as Options);
3545
expectTypeOf(genericResult.ipcOutput).toBe<Message[] | []>();
3646

@@ -67,6 +77,12 @@ expectTypeOf(falseGracefulResult.ipcOutput).toBe<[]>();
6777
const gracefulNoIpcResult = await execa('unicorns', {ipc: false, gracefulCancel: true, cancelSignal: AbortSignal.abort()});
6878
expectTypeOf(gracefulNoIpcResult.ipcOutput).toBe<[]>();
6979

80+
const falseNodeResult = await execa('unicorns', {node: false});
81+
expectTypeOf(falseNodeResult.ipcOutput).toBe<[]>();
82+
83+
const execaNodeNoIpcResult = await execaNode('unicorns', {ipc: false});
84+
expectTypeOf(execaNodeNoIpcResult.ipcOutput).toBe<[]>();
85+
7086
const noBufferResult = await execa('unicorns', {ipc: true, buffer: false});
7187
expectTypeOf(noBufferResult.ipcOutput).toBe<[]>();
7288

‎test-d/return/result-stdio.test-d.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {expectTypeOf} from 'nz';
33
import {
44
execa,
55
execaSync,
6+
execaNode,
67
type ExecaError,
78
type ExecaSyncError,
89
} from '../../index.js';
@@ -164,6 +165,15 @@ expectTypeOf(ipcInputResult.stdio).toBe<[undefined, string, string, undefined]>(
164165
const gracefulCancelResult = await execa('unicorns', {cancelSignal: AbortSignal.abort(), gracefulCancel: true});
165166
expectTypeOf(gracefulCancelResult.stdio).toBe<[undefined, string, string, undefined]>();
166167

168+
const nodeResult = await execa('unicorns', {node: true});
169+
expectTypeOf(nodeResult.stdio).toBe<[undefined, string, string, undefined]>();
170+
171+
const execaNodeResult = await execaNode('unicorns');
172+
expectTypeOf(execaNodeResult.stdio).toBe<[undefined, string, string, undefined]>();
173+
174+
const execaNodeNoIpcResult = await execaNode('unicorns', {ipc: false});
175+
expectTypeOf(execaNodeNoIpcResult.stdio).toBe<[undefined, string, string]>();
176+
167177
const fd3IpcResult = await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'pipe'], ipc: true});
168178
expectTypeOf(fd3IpcResult.stdio).toBe<[undefined, string, string, string, undefined]>();
169179

@@ -172,3 +182,6 @@ expectTypeOf(noIpcResult.stdio).toBe<[undefined, string, string]>();
172182

173183
const noIpcResultSync = execaSync('unicorns');
174184
expectTypeOf(noIpcResultSync.stdio).toBe<[undefined, string, string]>();
185+
186+
const nodeResultSync = execaSync('unicorns', {node: true});
187+
expectTypeOf(nodeResultSync.stdio).toBe<[undefined, string, string]>();

‎test-d/subprocess/stdio.test-d.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {Readable, Writable} from 'node:stream';
22
import {expectTypeOf} from 'nz';
3-
import {execa, type Subprocess} from '../../index.js';
3+
import {execa, execaNode, type Subprocess} from '../../index.js';
44

55
expectTypeOf<Subprocess['stdin']>().toBe<Writable | null>();
66
expectTypeOf<Subprocess['stdout']>().toBe<Readable | null>();
@@ -89,6 +89,9 @@ expectTypeOf(ipcSubprocess.stdio[3]).toBe<null>();
8989
const ipcInputSubprocess = execa('unicorns', {ipcInput: ''});
9090
expectTypeOf(ipcInputSubprocess.stdio[3]).toBe<null>();
9191

92+
const execaNodeSubprocess = execaNode('unicorns');
93+
expectTypeOf(execaNodeSubprocess.stdio[3]).toBe<null>();
94+
9295
const fd3IpcSubprocess = execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', 'pipe'], ipc: true});
9396
expectTypeOf(fd3IpcSubprocess.stdio[3]).toBe<Readable>();
9497
expectTypeOf(fd3IpcSubprocess.stdio[4]).toBe<null>();

‎test/methods/node.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,9 @@ const testIpc = async (execaMethod, options) => {
313313

314314
test('execaNode() adds an ipc channel', () => testIpc(execaNode, {}));
315315
test('The "node" option adds an ipc channel', () => testIpc(runWithNodeOption, {}));
316+
// Like other options, `undefined` uses the default value
317+
test('execaNode() adds an ipc channel, with "ipc: undefined"', () => testIpc(execaNode, {ipc: undefined}));
318+
test('The "node" option adds an ipc channel, with "ipc: undefined"', () => testIpc(runWithNodeOption, {ipc: undefined}));
316319
test('The "ipc" option adds an ipc channel', () => testIpc(runWithIpc, {}));
317320
test('The "ipc" option works with "stdio: \'pipe\'"', () => testIpc(runWithIpc, {stdio: 'pipe'}));
318321
test('The "ipc" option works with "stdio: [\'pipe\', \'pipe\', \'pipe\']"', () => testIpc(runWithIpc, {stdio: ['pipe', 'pipe', 'pipe']}));

‎test/methods/parameters-options.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ test('The "all" option must use a boolean', () => testInvalidBooleanOption(execa
127127
test('The "reject" option must use a boolean', () => testInvalidBooleanOption(execa, 'reject', 'no'));
128128
test('The "cleanup" option must use a boolean', () => testInvalidBooleanOption(execa, 'cleanup', 'false'));
129129
test('The "ipc" option must use a boolean', () => testInvalidBooleanOption(execa, 'ipc', 'yes'));
130+
test('The "ipc" option must use a boolean - execaNode()', () => testInvalidBooleanOption(execaNode, 'ipc', null));
130131
test('The "extendEnv" option must use a boolean', () => testInvalidBooleanOption(execa, 'extendEnv', 'false'));
131132
test('The "preferLocal" option must use a boolean', () => testInvalidBooleanOption(execa, 'preferLocal', 1));
132133
test('The "node" option must use a boolean', () => testInvalidBooleanOption(execa, 'node', 'yes'));

‎types/ipc.d.ts‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,25 +134,29 @@ export type IpcMethods<
134134
getEachMessage: undefined;
135135
};
136136

137-
// Whether IPC is enabled, based on the `ipc`, `ipcInput` and `gracefulCancel` options
137+
// Whether IPC is enabled, based on the `ipc`, `node`, `ipcInput` and `gracefulCancel` options
138138
export type HasIpc<OptionsType extends Options> = HasIpcOption<
139139
OptionsType['ipc'],
140+
'node' extends keyof OptionsType ? OptionsType['node'] : undefined,
140141
'ipcInput' extends keyof OptionsType ? OptionsType['ipcInput'] : undefined,
141142
'gracefulCancel' extends keyof OptionsType ? OptionsType['gracefulCancel'] : undefined
142143
>;
143144

144145
type HasIpcOption<
145146
IpcOption extends Options['ipc'],
147+
NodeOption extends Options['node'],
146148
IpcInputOption extends Options['ipcInput'],
147149
GracefulCancelOption extends Options['gracefulCancel'],
148150
> = IpcOption extends true
149151
? true
150152
: IpcOption extends false
151153
? false
152-
: IpcInputOption extends undefined
153-
? GracefulCancelOption extends true
154-
? true
155-
: false
156-
: true;
154+
: NodeOption extends true
155+
? true
156+
: IpcInputOption extends undefined
157+
? GracefulCancelOption extends true
158+
? true
159+
: false
160+
: true;
157161

158162
export {};

0 commit comments

Comments
 (0)