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
8 changes: 4 additions & 4 deletions lib/arguments/command-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ const escapeWindowsCommand = parsed => {
assertNoLineBreak(value);
}

const doubleEscape = resolvedFile !== undefined && batchFileRegExp.test(resolvedFile);
const isDoubleEscape = resolvedFile !== undefined && batchFileRegExp.test(resolvedFile);
// POSIX separators must become Windows ones (`foo/bar` -> `foo\bar`), otherwise resolution always fails with ENOENT
const escapedFile = escapeMetaChars(path.normalize(parsed.file));
const escapedArguments = parsed.commandArguments.map(argument => escapeArgument(argument, doubleEscape));
const escapedArguments = parsed.commandArguments.map(argument => escapeArgument(argument, isDoubleEscape));
const commandLine = `"${[escapedFile, ...escapedArguments].join(' ')}"`;

// Let `node:child_process` pass the already-escaped command line through untouched
Expand Down Expand Up @@ -149,8 +149,8 @@ const escapeArgument = (rawArgument, doubleEscape) => {
const argument = `${rawArgument}`
.replaceAll(backslashRunRegExp, (backslashes, offset, string) => {
const nextCharacter = string[offset + backslashes.length];
const precedesDoubleQuote = nextCharacter === '"' || nextCharacter === undefined;
return precedesDoubleQuote ? backslashes.repeat(2) : backslashes;
const isPrecedesDoubleQuote = nextCharacter === '"' || nextCharacter === undefined;
return isPrecedesDoubleQuote ? backslashes.repeat(2) : backslashes;
})
.replaceAll('"', '\\"');

Expand Down
2 changes: 1 addition & 1 deletion lib/arguments/encoding-option.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Please rename it to one of: ${correctEncodings}.`);

const TEXT_ENCODINGS = new Set(['utf8', 'utf16le']);
export const BINARY_ENCODINGS = new Set(['buffer', 'hex', 'base64', 'base64url', 'latin1', 'ascii']);
const ENCODINGS = new Set([...TEXT_ENCODINGS, ...BINARY_ENCODINGS]);
const ENCODINGS = TEXT_ENCODINGS.union(BINARY_ENCODINGS);

const getCorrectEncoding = encoding => {
if (encoding === null) {
Expand Down
6 changes: 3 additions & 3 deletions lib/ipc/graceful.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ export const getCancelSignal = async ({anyProcess, channel, isSubprocess, ipc})
};

const startIpc = async ({anyProcess, channel, isSubprocess, ipc}) => {
if (cancelListening) {
if (isCancelListening) {
return;
}

cancelListening = true;
isCancelListening = true;

if (!ipc) {
throwOnMissingParent();
Expand All @@ -49,7 +49,7 @@ const startIpc = async ({anyProcess, channel, isSubprocess, ipc}) => {
await scheduler.yield();
};

let cancelListening = false;
let isCancelListening = false;

// Reception of IPC message to perform a graceful termination
export const handleAbort = wrappedMessage => {
Expand Down
6 changes: 3 additions & 3 deletions lib/ipc/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ export const addIpcMethods = (target, subprocess, {ipc}) => {
export const getIpcExport = () => {
const anyProcess = process;
const isSubprocess = true;
const ipc = process.channel !== undefined;
const isIpc = process.channel !== undefined;

return {
...getIpcMethods(anyProcess, isSubprocess, ipc),
...getIpcMethods(anyProcess, isSubprocess, isIpc),
getCancelSignal: getCancelSignal.bind(undefined, {
anyProcess,
channel: anyProcess.channel,
isSubprocess,
ipc,
ipc: isIpc,
}),
};
};
Expand Down
8 changes: 4 additions & 4 deletions lib/methods/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const splitByWhitespaces = (template, rawTemplate) => {

const nextTokens = [];
let templateStart = 0;
const leadingWhitespaces = DELIMITERS.has(rawTemplate[0]);
const isLeadingWhitespaces = DELIMITERS.has(rawTemplate[0]);

for (
let templateIndex = 0, rawIndex = 0;
Expand Down Expand Up @@ -87,12 +87,12 @@ const splitByWhitespaces = (template, rawTemplate) => {
}
}

const trailingWhitespaces = templateStart === template.length;
if (!trailingWhitespaces) {
const isTrailingWhitespaces = templateStart === template.length;
if (!isTrailingWhitespaces) {
nextTokens.push(template.slice(templateStart));
}

return {nextTokens, leadingWhitespaces, trailingWhitespaces};
return {nextTokens, leadingWhitespaces: isLeadingWhitespaces, trailingWhitespaces: isTrailingWhitespaces};
};

const DELIMITERS = new Set([' ', '\t', '\r', '\n']);
Expand Down
4 changes: 2 additions & 2 deletions lib/resolve/exit-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import {isFailedExit} from './exit-async.js';
// Retrieve exit code, signal name and error information, with synchronous methods
export const getExitResultSync = ({error, status: exitCode, signal, output}, {maxBuffer}) => {
const resultError = getResultError(error, exitCode, signal);
const timedOut = resultError?.code === 'ETIMEDOUT';
const isTimedOut = resultError?.code === 'ETIMEDOUT';
const isMaxBuffer = isMaxBufferSync(resultError, output, maxBuffer);
return {
resultError,
exitCode,
signal,
timedOut,
timedOut: isTimedOut,
isMaxBuffer,
};
};
Expand Down
20 changes: 11 additions & 9 deletions lib/transform/split.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,20 @@ const splitGenerator = function * (state, preserveNewlines, chunk) {
let start = -1;

for (let end = 0; end < chunk.length; end += 1) {
if (chunk[end] === '\n') {
const newlineLength = getNewlineLength(chunk, end, preserveNewlines, state);
let line = chunk.slice(start + 1, end + 1 - newlineLength);
if (chunk[end] !== '\n') {
continue;
}

if (previousChunks.length > 0) {
line = concatString(previousChunks, line);
previousChunks = '';
}
const newlineLength = getNewlineLength(chunk, end, preserveNewlines, state);
let line = chunk.slice(start + 1, end + 1 - newlineLength);

yield line;
start = end;
if (previousChunks.length > 0) {
line = concatString(previousChunks, line);
previousChunks = '';
}

yield line;
start = end;
}

if (start !== chunk.length - 1) {
Expand Down
4 changes: 2 additions & 2 deletions lib/verbose/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import {isFullVerbose} from './values.js';
export const shouldLogOutput = ({stdioItems, encoding, verboseInfo, fdNumber}) => fdNumber !== 'all'
&& isFullVerbose(verboseInfo, fdNumber)
&& !BINARY_ENCODINGS.has(encoding)
&& fdUsesVerbose(fdNumber)
&& isFdVerbose(fdNumber)
&& (stdioItems.some(({type, value}) => type === 'native' && PIPED_STDIO_VALUES.has(value))
|| stdioItems.every(({type}) => TRANSFORM_TYPES.has(type)));

// Printing input streams would be confusing.
// Files and streams can produce big outputs, which we don't want to print.
// We could print `stdio[3+]` but it often is redirected to files and streams, with the same issue.
// So we only print stdout and stderr.
const fdUsesVerbose = fdNumber => fdNumber === 1 || fdNumber === 2;
const isFdVerbose = fdNumber => fdNumber === 1 || fdNumber === 2;

const PIPED_STDIO_VALUES = new Set(['pipe', 'overlapped']);

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"tempfile": "^6.0.1",
"tsd": "^0.33.0",
"typescript": "^6.0.3",
"xo": "^3.0.2"
"xo": "^4.0.0"
},
"c8": {
"reporter": [
Expand Down
6 changes: 3 additions & 3 deletions test-d/return/result-stdio.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ expectType<undefined>(inputFdFd3Result.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}]});
const isInput = true as boolean;
const booleanInputPipeFd3Result = await execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', {value: 'pipe', input: isInput}]});
expectType<string | undefined>(booleanInputPipeFd3Result.stdio[3]);

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

Expand Down
1 change: 1 addition & 0 deletions test-d/stdio/option/generator-boolean-full.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '../../../index.js';

const booleanGeneratorFull = {
// eslint-disable-next-line unicorn/consistent-boolean-name -- `line` matches the naming convention used by every other generator test file, mistyped on purpose to test the type error.
* transform(line: boolean) {
yield line;
},
Expand Down
1 change: 1 addition & 0 deletions test-d/stdio/option/generator-boolean.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type StdoutStderrSyncOption,
} from '../../../index.js';

// eslint-disable-next-line unicorn/consistent-boolean-name -- `line` matches the naming convention used by every other generator test file, mistyped on purpose to test the type error.
const booleanGenerator = function * (line: boolean) {
yield line;
};
Expand Down
4 changes: 2 additions & 2 deletions test-d/stdio/option/input-pipe.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
} from '../../../index.js';

const inputPipe = {value: 'pipe', input: true} as const;
const input = true as boolean;
const booleanInputPipe = {value: 'pipe', input} as const;
const isInput = true as boolean;
const booleanInputPipe = {value: 'pipe', input: isInput} as const;
const transform = function * (line: string) {
yield line;
};
Expand Down
6 changes: 3 additions & 3 deletions test-d/subprocess/stdio.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ expectType<null>(inputFdSubprocess.stdio[3]);
const inputFileSubprocess = execa('unicorns', {stdio: ['pipe', 'pipe', 'pipe', {value: {file: './example'}, input: true}]});
expectType<Writable>(inputFileSubprocess.stdio[3]);

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

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

Expand Down
2 changes: 2 additions & 0 deletions test-d/verbose.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ await execa('unicorns', {verbose: (verboseLine: string) => ''});
execaSync('unicorns', {verbose: (verboseLine: string) => ''});
await execa('unicorns', {verbose: (verboseLine: unknown) => ''});
execaSync('unicorns', {verbose: (verboseLine: unknown) => ''});
// eslint-disable-next-line unicorn/consistent-boolean-name -- `verboseLine` matches the real callback parameter name, mistyped on purpose to test the type error.
expectError(await execa('unicorns', {verbose: (verboseLine: boolean) => ''}));
// eslint-disable-next-line unicorn/consistent-boolean-name -- `verboseLine` matches the real callback parameter name, mistyped on purpose to test the type error.
expectError(execaSync('unicorns', {verbose: (verboseLine: boolean) => ''}));
expectError(await execa('unicorns', {verbose: (verboseLine: never) => ''}));
expectError(execaSync('unicorns', {verbose: (verboseLine: never) => ''}));
Expand Down
2 changes: 1 addition & 1 deletion test/arguments/escape.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test(testResultCommand, '');

// eslint-disable-next-line max-params
const testEscapedCommand = async (t, commandArguments, expectedUnix, expectedWindows, expectedUnixNoIcu = expectedUnix, expectedWindowsNoIcu = expectedWindows) => {
// eslint-disable-next-line no-use-extend-native/no-use-extend-native -- `RegExp.isMocked` is a static property added by `escape-no-icu.js`, not a native extension
// eslint-disable-next-line no-use-extend-native/no-use-extend-native, unicorn/no-nonstandard-builtin-properties -- `RegExp.isMocked` is a static property added by `escape-no-icu.js`, not a native extension
const expected = RegExp.isMocked
? (isWindows ? expectedWindowsNoIcu : expectedUnixNoIcu)
: (isWindows ? expectedWindows : expectedUnix);
Expand Down
5 changes: 1 addition & 4 deletions test/convert/readable.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,10 +326,7 @@ test('.readable() has the right highWaterMark', async t => {

test('.readable() can iterate over lines', async t => {
const subprocess = execa('noop-fd.js', ['1', simpleFull]);
const lines = [];
for await (const line of subprocess.readable({binary: false, preserveNewlines: false})) {
lines.push(line);
}
const lines = await Array.fromAsync(subprocess.readable({binary: false, preserveNewlines: false}));

const expectedLines = ['aaa', 'bbb', 'ccc'];
t.deepEqual(lines, expectedLines);
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/ipc-get-send-get.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env node
import {argv} from 'node:process';
import {sendMessage, getOneMessage} from '../../index.js';
import {alwaysPass} from '../helpers/ipc.js';
import {isAlwaysTrue} from '../helpers/ipc.js';

const filter = argv[2] === 'true' ? alwaysPass : undefined;
const filter = argv[2] === 'true' ? isAlwaysTrue : undefined;

const message = await getOneMessage({filter});
await Promise.all([
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/ipc-iterate-back-serial.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import process from 'node:process';
import {sendMessage, getOneMessage} from '../../index.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
import {alwaysPass, getFirst} from '../helpers/ipc.js';
import {isAlwaysTrue, getFirst} from '../helpers/ipc.js';

const filter = process.argv[2] === 'true' ? alwaysPass : undefined;
const filter = process.argv[2] === 'true' ? isAlwaysTrue : undefined;

await sendMessage(await getOneMessage({filter}));

Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/ipc-iterate-back.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import process from 'node:process';
import {sendMessage, getOneMessage} from '../../index.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
import {alwaysPass, getFirst} from '../helpers/ipc.js';
import {isAlwaysTrue, getFirst} from '../helpers/ipc.js';

const filter = process.argv[2] === 'true' ? alwaysPass : undefined;
const filter = process.argv[2] === 'true' ? isAlwaysTrue : undefined;

await sendMessage(await getOneMessage({filter}));

Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/ipc-print-many.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#!/usr/bin/env node
import {argv} from 'node:process';
import {getOneMessage} from '../../index.js';
import {alwaysPass} from '../helpers/ipc.js';
import {isAlwaysTrue} from '../helpers/ipc.js';

const count = Number(argv[2]);
const filter = argv[3] === 'true' ? alwaysPass : undefined;
const filter = argv[3] === 'true' ? isAlwaysTrue : undefined;

for (let index = 0; index < count; index += 1) {
// eslint-disable-next-line no-await-in-loop
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/ipc-process-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import process from 'node:process';
import {getOneMessage, sendMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
import {alwaysPass} from '../helpers/ipc.js';
import {isAlwaysTrue} from '../helpers/ipc.js';

process.on('error', () => {});
const filter = process.argv[2] === 'true' ? alwaysPass : undefined;
const filter = process.argv[2] === 'true' ? isAlwaysTrue : undefined;
const promise = getOneMessage({filter});
process.emit('error', new Error(foobarString));
await sendMessage(await promise);
8 changes: 4 additions & 4 deletions test/fixtures/ipc-send-pid.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import process from 'node:process';
import {execa, sendMessage} from '../../index.js';

const cleanup = process.argv[2] === 'true';
const detached = process.argv[3] === 'true';
const isCleanup = process.argv[2] === 'true';
const isDetached = process.argv[3] === 'true';
const file = process.argv[4] === 'no-killable' ? 'no-killable.js' : 'forever.js';
const options = {
cleanup,
detached,
cleanup: isCleanup,
detached: isDetached,
...(file === 'no-killable.js' && {
ipc: true,
killSignal: 'SIGKILL',
Expand Down
7 changes: 2 additions & 5 deletions test/helpers/ipc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import {foobarString} from './input.js';

// @todo: replace with Array.fromAsync(subprocess.getEachMessage()) after dropping support for Node <22.0.0
export const iterateAllMessages = async subprocess => {
const messages = [];
for await (const message of subprocess.getEachMessage()) {
messages.push(message);
}
const messages = await Array.fromAsync(subprocess.getEachMessage());

return messages;
};
Expand All @@ -25,7 +22,7 @@ export const getFirst = async () => {

export const subprocessGetOne = (subprocess, options) => subprocess.getOneMessage(options);

export const alwaysPass = () => true;
export const isAlwaysTrue = () => true;

// `process.send()` can fail due to I/O errors.
// However, I/O errors are seldom and hard to trigger predictably.
Expand Down
6 changes: 3 additions & 3 deletions test/ipc/forward.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarArray} from '../helpers/input.js';
import {iterateAllMessages, alwaysPass} from '../helpers/ipc.js';
import {iterateAllMessages, isAlwaysTrue} from '../helpers/ipc.js';

setFixtureDirectory();

Expand All @@ -25,8 +25,8 @@ const testParentErrorOne = async (t, filter, buffer) => {

test('"error" event does not interrupt subprocess.getOneMessage(), buffer false', testParentErrorOne, undefined, false);
test('"error" event does not interrupt subprocess.getOneMessage(), buffer true', testParentErrorOne, undefined, true);
test('"error" event does not interrupt subprocess.getOneMessage(), buffer false, filter', testParentErrorOne, alwaysPass, false);
test('"error" event does not interrupt subprocess.getOneMessage(), buffer true, filter', testParentErrorOne, alwaysPass, true);
test('"error" event does not interrupt subprocess.getOneMessage(), buffer false, filter', testParentErrorOne, isAlwaysTrue, false);
test('"error" event does not interrupt subprocess.getOneMessage(), buffer true, filter', testParentErrorOne, isAlwaysTrue, true);

const testSubprocessErrorOne = async (t, filter, buffer) => {
const subprocess = execa('ipc-process-error.js', [`${filter}`], {ipc: true, buffer});
Expand Down
Loading
Loading