Skip to content

Commit

Permalink
Improve validation of execaSync() options (#940)
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Mar 30, 2024
1 parent 8f97132 commit f6e1ac1
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 6 deletions.
16 changes: 14 additions & 2 deletions lib/stdio/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,19 @@ export const handleInputSync = (options, verboseInfo) => {
};

const forbiddenIfSync = ({type, optionName}) => {
throw new TypeError(`The \`${optionName}\` option cannot be ${TYPE_TO_MESSAGE[type]} in sync mode.`);
throwInvalidSyncValue(optionName, TYPE_TO_MESSAGE[type]);
};

const forbiddenNativeIfSync = ({optionName, value}) => {
if (value === 'ipc') {
throwInvalidSyncValue(optionName, `"${value}"`);
}

return {};
};

const throwInvalidSyncValue = (optionName, value) => {
throw new TypeError(`The \`${optionName}\` option cannot be ${value} in sync mode.`);
};

const addProperties = {
Expand All @@ -21,7 +33,7 @@ const addProperties = {
webTransform: forbiddenIfSync,
duplex: forbiddenIfSync,
iterable: forbiddenIfSync,
native() {},
native: forbiddenNativeIfSync,
};

const addPropertiesSync = {
Expand Down
16 changes: 14 additions & 2 deletions lib/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,22 @@ const handleSyncArguments = (rawFile, rawArgs, rawOptions) => {

const normalizeSyncOptions = options => options.node && !options.ipc ? {...options, ipc: false} : options;

const validateSyncOptions = ({ipc}) => {
const validateSyncOptions = ({ipc, detached, cancelSignal}) => {
if (ipc) {
throw new TypeError('The "ipc: true" option cannot be used with synchronous methods.');
throwInvalidSyncOption('ipc: true');
}

if (detached) {
throwInvalidSyncOption('detached: true');
}

if (cancelSignal) {
throwInvalidSyncOption('cancelSignal');
}
};

const throwInvalidSyncOption = value => {
throw new TypeError(`The "${value}" option cannot be used with synchronous methods.`);
};

const spawnSubprocessSync = ({file, args, options, command, escapedCommand, fileDescriptors, startTime}) => {
Expand Down
9 changes: 8 additions & 1 deletion test/exit/cancel.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ test('calling abort on a successfully completed subprocess does not make result.
test('Throws when using the former "signal" option name', t => {
const abortController = new AbortController();
t.throws(() => {
execa('noop.js', {signal: abortController.signal});
execa('empty.js', {signal: abortController.signal});
}, {message: /renamed to "cancelSignal"/});
});

test('Cannot use cancelSignal, sync', t => {
const abortController = new AbortController();
t.throws(() => {
execaSync('empty.js', {cancelSignal: abortController.signal});
}, {message: /The "cancelSignal" option cannot be used/});
});
8 changes: 7 additions & 1 deletion test/exit/cleanup.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {setTimeout} from 'node:timers/promises';
import test from 'ava';
import {pEvent} from 'p-event';
import isRunning from 'is-running';
import {execa} from '../../index.js';
import {execa, execaSync} from '../../index.js';
import {setFixtureDir} from '../helpers/fixtures-dir.js';

setFixtureDir();
Expand Down Expand Up @@ -87,3 +87,9 @@ test('detach subprocess', async t => {

process.kill(pid, 'SIGKILL');
});

test('Cannot use "detached" option, sync', t => {
t.throws(() => {
execaSync('empty.js', {detached: true});
}, {message: /The "detached: true" option cannot be used/});
});
11 changes: 11 additions & 0 deletions test/stdio/handle.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ test('stdio[*] can be ["inherit"]', testNoPipeOption, ['inherit'], 3);
test('stdio[*] can be 3', testNoPipeOption, 3, 3);
test('stdio[*] can be [3]', testNoPipeOption, [3], 3);

const testNoIpcSync = (t, fdNumber) => {
t.throws(() => {
execaSync('empty.js', getStdio(fdNumber, 'ipc'));
}, {message: /cannot be "ipc" in sync mode/});
};

test('stdin cannot be "ipc", sync', testNoIpcSync, 0);
test('stdout cannot be "ipc", sync', testNoIpcSync, 1);
test('stderr cannot be "ipc", sync', testNoIpcSync, 2);
test('stdio[*] cannot be "ipc", sync', testNoIpcSync, 3);

const testInvalidArrayValue = (t, invalidStdio, fdNumber, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', getStdio(fdNumber, ['pipe', invalidStdio]));
Expand Down

0 comments on commit f6e1ac1

Please sign in to comment.