Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- run: npm install
- uses: lycheeverse/lychee-action@v2
with:
args: --cache --verbose --no-progress --include-fragments --exclude packagephobia --exclude /pull/ --exclude linkedin --exclude stackoverflow --exclude stackexchange --exclude github.com/nodejs/node --exclude github.com/denoland/deno --exclude file:///test --exclude invalid.com '*.md' 'docs/*.md' '.github/**/*.md' '*.json' '*.js' 'lib/**/*.js' 'test/**/*.js' '*.ts' 'test-d/**/*.ts'
args: --cache --verbose --no-progress --include-fragments --exclude packagephobia --exclude /pull/ --exclude depot.dev --exclude linkedin --exclude stackoverflow --exclude stackexchange --exclude github.com/nodejs/node --exclude github.com/denoland/deno --exclude file:///test --exclude invalid.com '*.md' 'docs/*.md' '.github/**/*.md' '*.json' '*.js' 'lib/**/*.js' 'test/**/*.js' '*.ts' 'test-d/**/*.ts'
fail: true
if: ${{ matrix.os == 'ubuntu' && matrix.node-version == 26 }}
- run: npm run lint
Expand Down
4 changes: 2 additions & 2 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -916,14 +916,14 @@ More info on [available values](output.md), [streaming](streams.md) and [transfo
### options.stdio

_TypeScript:_ [`Options['stdio']`](typescript.md) or [`SyncOptions['stdio']`](typescript.md)\
_Type:_ `string | Array<string | number | stream.Readable | stream.Writable | ReadableStream | WritableStream | TransformStream | URL | {file: string} | Uint8Array | Iterable<string> | Iterable<Uint8Array> | Iterable<unknown> | AsyncIterable<string | Uint8Array | unknown> | GeneratorFunction<string | Uint8Array | unknown> | AsyncGeneratorFunction<string | Uint8Array | unknown> | {transform: GeneratorFunction | AsyncGeneratorFunction | Duplex | TransformStream}>` (or a tuple of those types)\
_Type:_ `string | Array<string | number | stream.Readable | stream.Writable | ReadableStream | WritableStream | TransformStream | URL | {file: string} | Uint8Array | Iterable<string> | Iterable<Uint8Array> | Iterable<unknown> | AsyncIterable<string | Uint8Array | unknown> | GeneratorFunction<string | Uint8Array | unknown> | AsyncGeneratorFunction<string | Uint8Array | unknown> | {transform: GeneratorFunction | AsyncGeneratorFunction | Duplex | TransformStream} | {value: 'pipe'; input?: boolean}>` (or a tuple of those types)\
_Default:_ `pipe`

Like the [`stdin`](#optionsstdin), [`stdout`](#optionsstdout) and [`stderr`](#optionsstderr) options but for all [file descriptors](https://en.wikipedia.org/wiki/File_descriptor) at once. For example, `{stdio: ['ignore', 'pipe', 'pipe']}` is the same as `{stdin: 'ignore', stdout: 'pipe', stderr: 'pipe'}`.

A single string can be used [as a shortcut](output.md#shortcut).

The array can have more than 3 items, to create [additional file descriptors](output.md#additional-file-descriptors) beyond [`stdin`](#optionsstdin)/[`stdout`](#optionsstdout)/[`stderr`](#optionsstderr).
The array can have more than 3 items, to create [additional file descriptors](output.md#additional-file-descriptors) beyond [`stdin`](#optionsstdin)/[`stdout`](#optionsstdout)/[`stderr`](#optionsstderr). Passing `'pipe'` to an additional file descriptor defaults it to output. To use it for [input](input.md#additional-file-descriptors) instead, pass `{value: 'pipe', input: true}`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can only 'pipe' be ambiguous? For example, I would imagine 'inherit' could be possibly used for both input and output in fd3? Same with streams that are both readable and writable, or with files.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered that, but I scoped it to 'pipe'/'overlapped' because that's what #1214 was about (the buggy ['pipe', []] workaround), but 'inherit' and files indeed share the same limitation. I'll look into it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was only mentioning 'pipe' in the issue because that was the specific instance the user was experiencing, but the problem was more generic, in that it impacts other values.

I am actually wondering whether allowing any value would simplify the implementation, since some of the logic right now is making sure 'pipe' is used. Technically, some values do not make sense as input, namely 1, 2 or any non-readable stream (Node.js or web). But using fd3 + {input: true} + one of those values seems like such an edge case, which is going to fail runtime anyway, that it might not deserve any safeguard.


More info on [available values](output.md), [streaming](streams.md) and [transforms](transform.md).

Expand Down
13 changes: 13 additions & 0 deletions docs/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,19 @@ await execa({
})`npm run build`;
```

When passing `'pipe'` to an additional file descriptor, Execa cannot tell whether it is meant for input or output, so it defaults to output. To use it for input instead, such as when [streaming](streams.md#different-file-descriptor) to it, pass `{value: 'pipe', input: true}`.

```js
import {pipeline} from 'node:stream/promises';

// Stream input to the file descriptor number 3
const subprocess = execa({
stdio: ['pipe', 'pipe', 'pipe', {value: 'pipe', input: true}],
})`npm run build`;

await pipeline(inputStream, subprocess.writable({to: 'fd3'}));
```

<hr>

[**Next**: 📢 Output](output.md)\
Expand Down
8 changes: 8 additions & 0 deletions docs/streams.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ const writable = execa`npm run scaffold`.writable({to: 'fd3'});
const duplex = execa`npm run scaffold`.duplex({from: 'stderr', to: 'fd3'});
```

Writing to an additional file descriptor like `'fd3'` requires marking it as [input](input.md#additional-file-descriptors), since it otherwise defaults to output.

```js
const subprocess = execa({stdio: ['pipe', 'pipe', 'pipe', {value: 'pipe', input: true}]})`npm run scaffold`;

const writable = subprocess.writable({to: 'fd3'});
```

### Error handling

When using [`subprocess.readable()`](api.md#subprocessreadablereadableoptions), [`subprocess.writable()`](api.md#subprocesswritablewritableoptions) or [`subprocess.duplex()`](api.md#subprocessduplexduplexoptions), the stream waits for the subprocess to end, and emits an [`error`](https://nodejs.org/api/stream.html#event-error) event if the subprocess [fails](errors.md). This differs from [`subprocess.stdin`](api.md#subprocessstdin), [`subprocess.stdout`](api.md#subprocessstdout) and [`subprocess.stderr`](api.md#subprocessstderr)'s behavior.
Expand Down
3 changes: 2 additions & 1 deletion lib/arguments/fd-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ Please set the "stdio" option to ensure that file descriptor exists.`);
}

if (fileDescriptor.direction !== 'input' && isWritable) {
throw new TypeError(`"${getOptionName(isWritable)}" must not be ${fdName}. It must be a writable stream, not readable.`);
throw new TypeError(`"${getOptionName(isWritable)}" must not be ${fdName}. It must be a writable stream, not readable.
If you meant to use it as input, please set its "stdio" option to \`{value: 'pipe', input: true}\`.`);
}
};

Expand Down
4 changes: 3 additions & 1 deletion lib/stdio/direction.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export const getStreamDirection = (stdioItems, fdNumber, optionName) => {
return directions.find(Boolean) ?? DEFAULT_DIRECTION;
};

const getStdioItemDirection = ({type, value}, fdNumber) => KNOWN_DIRECTIONS[fdNumber] ?? guessStreamDirection[type](value);
// `direction` is set when the user explicitly requests it, e.g. `{value: 'pipe', input: true}`.
// It takes precedence over guessing, but not over the fixed direction of `stdin`/`stdout`/`stderr`.
const getStdioItemDirection = ({type, value, direction}, fdNumber) => KNOWN_DIRECTIONS[fdNumber] ?? direction ?? guessStreamDirection[type](value);

// `stdin`/`stdout`/`stderr` have a known direction
const KNOWN_DIRECTIONS = ['input', 'output', 'output'];
Expand Down
5 changes: 4 additions & 1 deletion lib/stdio/duplicate.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import {
// Duplicates in the same file descriptor is most likely an error.
// However, this can be useful with generators.
export const filterDuplicates = stdioItems => stdioItems.filter((stdioItemOne, indexOne) =>
stdioItems.every((stdioItemTwo, indexTwo) => stdioItemOne.value !== stdioItemTwo.value
stdioItems.every((stdioItemTwo, indexTwo) => !hasSameValueAndDirection(stdioItemOne, stdioItemTwo)
|| indexOne >= indexTwo
|| stdioItemOne.type === 'generator'
|| stdioItemOne.type === 'asyncGenerator'));

const hasSameValueAndDirection = (stdioItemOne, stdioItemTwo) => stdioItemOne.value === stdioItemTwo.value
&& stdioItemOne.direction === stdioItemTwo.direction;

// Check if two file descriptors are sharing the same target.
// For example `{stdout: {file: './output.txt'}, stderr: {file: './output.txt'}}`.
export const getDuplicateStream = ({stdioItem: {type, value, optionName}, direction, fileDescriptors, isSync}) => {
Expand Down
11 changes: 11 additions & 0 deletions lib/stdio/handle-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ const forbiddenNativeIfSync = ({optionName, value}) => {
return {};
};

// A native input pipe on an additional file descriptor cannot be fed in sync mode, since there is no way to write to it after spawning, so it would hang.
const forbiddenNativeInputIfSync = stdioItem => {
const {optionName, value} = stdioItem;
if (value === 'pipe' && optionName !== 'stdin') {
throw new TypeError(`Only the \`stdin\` option, not \`${optionName}\`, can be an input pipe with synchronous methods.`);
}

return forbiddenNativeIfSync(stdioItem);
};

const throwInvalidSyncValue = (optionName, value) => {
throw new TypeError(`The \`${optionName}\` option cannot be ${value} with synchronous methods.`);
};
Expand All @@ -38,6 +48,7 @@ const addProperties = {
const addPropertiesSync = {
input: {
...addProperties,
native: forbiddenNativeInputIfSync,
fileUrl: ({value}) => ({contents: [bufferToUint8Array(readFileSync(value))]}),
filePath: ({value: {file}}) => ({contents: [bufferToUint8Array(readFileSync(file))]}),
fileNumber: forbiddenIfSync,
Expand Down
30 changes: 25 additions & 5 deletions lib/stdio/handle.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
getStdioItemType,
isRegularUrl,
isUnknownStdioString,
isPipeInputObject,
checkBooleanOption,
FILE_TYPES,
} from './type.js';
import {getStreamDirection} from './direction.js';
Expand Down Expand Up @@ -82,11 +84,29 @@ const isInheritedStdinOnly = stdioItems => stdioItems.length === 1
&& stdioItems[0].type === 'native'
&& stdioItems[0].value === 'inherit';

const initializeStdioItem = (value, optionName) => ({
type: getStdioItemType(value, optionName),
value,
optionName,
});
const initializeStdioItem = (value, optionName) => {
if (isPipeInputObject(value)) {
return initializePipeInputItem(value, optionName);
}

return {
type: getStdioItemType(value, optionName),
value,
optionName,
};
};

// The `{value: 'pipe', input: true}` object form is a native pipe, plus an explicit input direction.
const initializePipeInputItem = ({value, input}, optionName) => {
checkBooleanOption(input, `${optionName}.input`);

return {
type: 'native',
value,
direction: input ? 'input' : undefined,
optionName,
};
};

const validateStdioArray = (stdioItems, isStdioArray, optionName) => {
if (stdioItems.length === 0) {
Expand Down
17 changes: 14 additions & 3 deletions lib/stdio/stdio-option.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {STANDARD_STREAMS_ALIASES} from '../utils/standard-stream.js';
import {normalizeIpcStdioArray} from '../ipc/array.js';
import {isFullVerbose} from '../verbose/values.js';
import {isPipeInputObject} from './type.js';

// Add support for `stdin`/`stdout`/`stderr` as an alias for `stdio`.
// Also normalize the `stdio` option.
Expand Down Expand Up @@ -60,9 +61,19 @@ const normalizeStdioSync = (stdioArray, buffer, verboseInfo) => stdioArray.map((
!buffer[fdNumber]
&& fdNumber !== 0
&& !isFullVerbose(verboseInfo, fdNumber)
&& isOutputPipeOnly(stdioOption)
&& isOutputPipeOnly(stdioOption, fdNumber)
? 'ignore'
: stdioOption);

const isOutputPipeOnly = stdioOption => stdioOption === 'pipe'
|| (Array.isArray(stdioOption) && stdioOption.every(item => item === 'pipe'));
const isOutputPipeOnly = (stdioOption, fdNumber) => isOutputPipe(stdioOption, fdNumber)
|| (Array.isArray(stdioOption) && stdioOption.every(item => isOutputPipe(item, fdNumber)));

const isOutputPipe = (stdioOption, fdNumber) => stdioOption === 'pipe'
|| isOutputPipeObject(stdioOption, fdNumber);

const isOutputPipeObject = (stdioOption, fdNumber) => isPipeInputObject(stdioOption)
&& stdioOption.value === 'pipe'
&& (stdioOption.input === undefined || stdioOption.input === false || isFixedOutputPipe(fdNumber, stdioOption.input));

// On `stdout`/`stderr`, the direction is always output, so `input: true` is ignored and the pipe stays output.
const isFixedOutputPipe = (fdNumber, input) => input === true && (fdNumber === 1 || fdNumber === 2);
11 changes: 10 additions & 1 deletion lib/stdio/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const getGeneratorObjectType = ({transform, final, binary, objectMode}, optionNa
return isAsyncGenerator(transform) || isAsyncGenerator(final) ? 'asyncGenerator' : 'generator';
};

const checkBooleanOption = (value, optionName) => {
export const checkBooleanOption = (value, optionName) => {
if (value !== undefined && typeof value !== 'boolean') {
throw new TypeError(`The \`${optionName}\` option must use a boolean.`);
}
Expand All @@ -130,6 +130,15 @@ const isFilePathObject = value => isPlainObj(value)
const FILE_PATH_KEYS = new Set(['file', 'append']);
export const isFilePathString = file => typeof file === 'string';

// The `{value: 'pipe', input: true}` object form marks an additional file descriptor as input instead of output.
// This is only needed for `'pipe'`/`'overlapped'` on fd3+, whose direction is otherwise ambiguous and defaults to output.
export const isPipeInputObject = value => isPlainObj(value)
&& Object.keys(value).length > 0
&& Object.keys(value).every(key => PIPE_INPUT_KEYS.has(key))
&& NATIVE_PIPE_VALUES.has(value.value);
const PIPE_INPUT_KEYS = new Set(['value', 'input']);
const NATIVE_PIPE_VALUES = new Set(['pipe', 'overlapped']);

export const isUnknownStdioString = (type, value) => type === 'native'
&& typeof value === 'string'
&& !KNOWN_STDIO_STRINGS.has(value);
Expand Down
3 changes: 3 additions & 0 deletions test-d/convert/writable.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ expectError(subprocess.writable({preserveNewlines: false}));

expectError(subprocess.writable('stdin'));
expectError(subprocess.writable({other: 'stdin'}));

const inputPipeSubprocess = execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', {value: 'pipe', input: true}]});
inputPipeSubprocess.writable({to: 'fd3'});
26 changes: 26 additions & 0 deletions test-d/return/result-stdio.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,32 @@ expectType<string>(fd3Result.stdio[3]);
const inputFd3Result = await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', ['pipe', new Readable()]]});
expectType<undefined>(inputFd3Result.stdio[3]);

const inputPipeFd3Result = await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', {value: 'pipe', input: true}]});
expectType<undefined>(inputPipeFd3Result.stdio[3]);

const outputPipeFd3Result = await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', {value: 'pipe'}]});
expectType<string>(outputPipeFd3Result.stdio[3]);

const input = true as boolean;
const booleanInputPipeFd3Result = await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', {value: 'pipe', input}]});
expectType<string | undefined>(booleanInputPipeFd3Result.stdio[3]);

const optionalInputPipe: {readonly value: 'pipe'; readonly input?: boolean} = {value: 'pipe', input};
const optionalInputPipeFd3Result = await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', optionalInputPipe]});
expectType<string | undefined>(optionalInputPipeFd3Result.stdio[3]);

const optionalTrueInputPipe: {readonly value: 'pipe'; readonly input?: true} = {value: 'pipe', input: true};
const optionalTrueInputPipeFd3Result = await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', optionalTrueInputPipe]});
expectType<string | undefined>(optionalTrueInputPipeFd3Result.stdio[3]);

const stdoutInputPipeResult = await execa('unicorns', {stdout: {value: 'pipe', input: true}});
expectType<string>(stdoutInputPipeResult.stdout);
expectType<string>(stdoutInputPipeResult.stdio[1]);

const stderrInputPipeResult = await execa('unicorns', {stderr: {value: 'pipe', input: true}});
expectType<string>(stderrInputPipeResult.stderr);
expectType<string>(stderrInputPipeResult.stdio[2]);

const outputFd3Result = await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', ['pipe', new Writable()]]});
expectType<string>(outputFd3Result.stdio[3]);

Expand Down
49 changes: 49 additions & 0 deletions test-d/stdio/option/input-pipe.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {expectError, expectAssignable} from 'tsd';
import {
execa,
execaSync,
type StdinOption,
type StdinSyncOption,
type StdoutStderrOption,
type StdoutStderrSyncOption,
} from '../../../index.js';

const inputPipe = {value: 'pipe', input: true} as const;
const input = true as boolean;
const booleanInputPipe = {value: 'pipe', input} as const;

await execa('unicorns', {stdin: inputPipe});
execaSync('unicorns', {stdin: inputPipe});
await execa('unicorns', {stdout: inputPipe});
execaSync('unicorns', {stdout: inputPipe});
await execa('unicorns', {stderr: inputPipe});
execaSync('unicorns', {stderr: inputPipe});

await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', inputPipe]});
expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', inputPipe]}));
await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', booleanInputPipe]});
expectError(execaSync('unicorns', {stdio: ['pipe', 'pipe', 'pipe', booleanInputPipe]}));

await execa('unicorns', {stdin: {value: 'pipe'}});
await execa('unicorns', {stdin: {value: 'overlapped', input: true}});
expectError(execaSync('unicorns', {stdin: {value: 'overlapped', input: true}}));

expectError(await execa('unicorns', {stdin: {value: 'other', input: true}}));
expectError(await execa('unicorns', {stdin: {value: 'pipe', input: 'true'}}));

expectAssignable<StdinOption>(inputPipe);
expectAssignable<StdinOption>(booleanInputPipe);
expectAssignable<StdinSyncOption>(inputPipe);
expectAssignable<StdinSyncOption>(booleanInputPipe);
expectAssignable<StdoutStderrOption>(inputPipe);
expectAssignable<StdoutStderrOption>(booleanInputPipe);
expectAssignable<StdoutStderrSyncOption>(inputPipe);
expectAssignable<StdoutStderrSyncOption>(booleanInputPipe);
expectAssignable<StdinOption>([inputPipe]);
expectAssignable<StdinOption>([booleanInputPipe]);
expectAssignable<StdinSyncOption>([inputPipe]);
expectAssignable<StdinSyncOption>([booleanInputPipe]);
expectAssignable<StdoutStderrOption>([inputPipe]);
expectAssignable<StdoutStderrOption>([booleanInputPipe]);
expectAssignable<StdoutStderrSyncOption>([inputPipe]);
expectAssignable<StdoutStderrSyncOption>([booleanInputPipe]);
26 changes: 26 additions & 0 deletions test-d/subprocess/stdio.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,29 @@ expectType<Readable>(multipleStdoutSubprocess.stderr);
expectType<Readable>(multipleStdoutSubprocess.stdio[2]);
expectType<Readable>(multipleStdoutSubprocess.all);
expectError(multipleStdoutSubprocess.stdio[3].destroy());

const inputPipeSubprocess = execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', {value: 'pipe', input: true}]});
expectType<Writable>(inputPipeSubprocess.stdio[3]);

const outputPipeSubprocess = execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', {value: 'pipe'}]});
expectType<Readable>(outputPipeSubprocess.stdio[3]);

const input = true as boolean;
const booleanInputPipeSubprocess = execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', {value: 'pipe', input}]});
expectType<Readable | Writable>(booleanInputPipeSubprocess.stdio[3]);

const optionalInputPipe: {readonly value: 'pipe'; readonly input?: boolean} = {value: 'pipe', input};
const optionalInputPipeSubprocess = execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', optionalInputPipe]});
expectType<Readable | Writable>(optionalInputPipeSubprocess.stdio[3]);

const optionalTrueInputPipe: {readonly value: 'pipe'; readonly input?: true} = {value: 'pipe', input: true};
const optionalTrueInputPipeSubprocess = execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', optionalTrueInputPipe]});
expectType<Readable | Writable>(optionalTrueInputPipeSubprocess.stdio[3]);

const stdoutInputPipeSubprocess = execa('unicorns', {stdout: {value: 'pipe', input: true}});
expectType<Readable>(stdoutInputPipeSubprocess.stdout);
expectType<Readable>(stdoutInputPipeSubprocess.stdio[1]);

const stderrInputPipeSubprocess = execa('unicorns', {stderr: {value: 'pipe', input: true}});
expectType<Readable>(stderrInputPipeSubprocess.stderr);
expectType<Readable>(stderrInputPipeSubprocess.stdio[2]);
5 changes: 5 additions & 0 deletions test/arguments/fd-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,11 @@ test('.duplex() "to" option cannot be an output file descriptor', testNodeStream
to: 'fd3',
message: 'must be a writable stream',
});
test('.duplex() "to" option on an output file descriptor suggests the input-pipe syntax', testNodeStream, {
sourceOptions: fullStdio,
to: 'fd3',
message: '{value: \'pipe\', input: true}',
});
test('.pipe() "to" option cannot be "all"', testPipeError, {
destinationOptions: fullStdio,
to: 'all',
Expand Down
8 changes: 7 additions & 1 deletion test/convert/duplex.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ import {
} from '../helpers/convert.js';
import {foobarString} from '../helpers/input.js';
import {majorNodeVersion} from '../helpers/node-version.js';
import {prematureClose, fullStdio, fullReadableStdio} from '../helpers/stdio.js';
import {
getStdio,
prematureClose,
fullStdio,
fullReadableStdio,
} from '../helpers/stdio.js';
import {defaultHighWaterMark} from '../helpers/stream.js';

setFixtureDirectory();
Expand Down Expand Up @@ -77,6 +82,7 @@ const testWritableDuplexDefault = async (t, fdNumber, to, options) => {

test('.duplex() can use stdin', testWritableDuplexDefault, 0, 'stdin', {});
test('.duplex() can use input stdio[*]', testWritableDuplexDefault, 3, 'fd3', fullReadableStdio());
test('.duplex() can use input stdio[*] with { value: "pipe", input: true }', testWritableDuplexDefault, 3, 'fd3', getStdio(3, {value: 'pipe', input: true}));
test('.duplex() uses stdin by default', testWritableDuplexDefault, 0, undefined, {});

test('.duplex() abort -> subprocess fail', async t => {
Expand Down
Loading
Loading