diff --git a/lib/arguments/command.js b/lib/arguments/command.js index 61c9929508..1aa3bb696e 100644 --- a/lib/arguments/command.js +++ b/lib/arguments/command.js @@ -4,6 +4,7 @@ import {getStartTime} from '../return/duration.js'; import {joinCommand} from './escape.js'; import {normalizeFdSpecificOption} from './specific.js'; +// Compute `result.command`, `result.escapedCommand` and `verbose`-related information export const handleCommand = (filePath, rawArguments, rawOptions) => { const startTime = getStartTime(); const {command, escapedCommand} = joinCommand(filePath, rawArguments); diff --git a/lib/arguments/cwd.js b/lib/arguments/cwd.js index ba1b554693..69b31169ed 100644 --- a/lib/arguments/cwd.js +++ b/lib/arguments/cwd.js @@ -3,6 +3,7 @@ import {resolve} from 'node:path'; import process from 'node:process'; import {safeNormalizeFileUrl} from './file-url.js'; +// Normalize `cwd` option export const normalizeCwd = (cwd = getDefaultCwd()) => { const cwdString = safeNormalizeFileUrl(cwd, 'The "cwd" option'); return resolve(cwdString); @@ -17,6 +18,7 @@ const getDefaultCwd = () => { } }; +// When `cwd` option has an invalid value, provide with a better error message export const fixCwdError = (originalMessage, cwd) => { if (cwd === getDefaultCwd()) { return originalMessage; diff --git a/lib/arguments/encoding-option.js b/lib/arguments/encoding-option.js index 6d39bc80f2..c3ec6b8c0d 100644 --- a/lib/arguments/encoding-option.js +++ b/lib/arguments/encoding-option.js @@ -1,3 +1,4 @@ +// Validate `encoding` option export const validateEncoding = ({encoding}) => { if (ENCODINGS.has(encoding)) { return; diff --git a/lib/arguments/escape.js b/lib/arguments/escape.js index 992686c659..a60f370940 100644 --- a/lib/arguments/escape.js +++ b/lib/arguments/escape.js @@ -1,6 +1,7 @@ import {platform} from 'node:process'; import {stripVTControlCharacters} from 'node:util'; +// Compute `result.command` and `result.escapedCommand` export const joinCommand = (filePath, rawArguments) => { const fileAndArguments = [filePath, ...rawArguments]; const command = fileAndArguments.join(' '); @@ -10,6 +11,7 @@ export const joinCommand = (filePath, rawArguments) => { return {command, escapedCommand}; }; +// Remove ANSI sequences and escape control characters and newlines export const escapeLines = lines => stripVTControlCharacters(lines) .split('\n') .map(line => escapeControlCharacters(line)) diff --git a/lib/arguments/fd-options.js b/lib/arguments/fd-options.js index 941ae6a4f1..cd0e49d7fa 100644 --- a/lib/arguments/fd-options.js +++ b/lib/arguments/fd-options.js @@ -1,5 +1,6 @@ import {parseFd} from './specific.js'; +// Retrieve stream targeted by the `to` option export const getToStream = (destination, to = 'stdin') => { const isWritable = true; const {options, fileDescriptors} = SUBPROCESS_OPTIONS.get(destination); @@ -13,6 +14,7 @@ export const getToStream = (destination, to = 'stdin') => { return destinationStream; }; +// Retrieve stream targeted by the `from` option export const getFromStream = (source, from = 'stdout') => { const isWritable = false; const {options, fileDescriptors} = SUBPROCESS_OPTIONS.get(source); @@ -26,6 +28,7 @@ export const getFromStream = (source, from = 'stdout') => { return sourceStream; }; +// Keeps track of the options passed to each Execa call export const SUBPROCESS_OPTIONS = new WeakMap(); const getFdNumber = (fileDescriptors, fdName, isWritable) => { diff --git a/lib/arguments/file-url.js b/lib/arguments/file-url.js index c66cc865d7..6c4ea9a2d9 100644 --- a/lib/arguments/file-url.js +++ b/lib/arguments/file-url.js @@ -1,5 +1,6 @@ import {fileURLToPath} from 'node:url'; +// Allow some arguments/options to be either a file path string or a file URL export const safeNormalizeFileUrl = (file, name) => { const fileString = normalizeFileUrl(file); @@ -10,4 +11,5 @@ export const safeNormalizeFileUrl = (file, name) => { return fileString; }; +// Same but also allows other values, e.g. `boolean` for the `shell` option export const normalizeFileUrl = file => file instanceof URL ? fileURLToPath(file) : file; diff --git a/lib/arguments/options.js b/lib/arguments/options.js index 6cb4075af4..ec785349a0 100644 --- a/lib/arguments/options.js +++ b/lib/arguments/options.js @@ -10,6 +10,8 @@ import {normalizeCwd} from './cwd.js'; import {normalizeFileUrl} from './file-url.js'; import {normalizeFdSpecificOptions} from './specific.js'; +// Normalize the options object, and sometimes also the file paths and arguments. +// Applies default values, validate allowed options, normalize them. export const normalizeOptions = (filePath, rawArguments, rawOptions) => { rawOptions.cwd = normalizeCwd(rawOptions.cwd); const [processedFile, processedArguments, processedOptions] = handleNodeOption(filePath, rawArguments, rawOptions); diff --git a/lib/arguments/specific.js b/lib/arguments/specific.js index c57bbc2813..cd89e85a9d 100644 --- a/lib/arguments/specific.js +++ b/lib/arguments/specific.js @@ -2,6 +2,9 @@ import isPlainObject from 'is-plain-obj'; import {STANDARD_STREAMS_ALIASES} from '../utils/standard-stream.js'; import {verboseDefault} from '../verbose/info.js'; +// Some options can have different values for `stdout`/`stderr`/`fd3`. +// This normalizes those to array of values. +// For example, `{verbose: {stdout: 'none', stderr: 'full'}}` becomes `{verbose: ['none', 'none', 'full']}` export const normalizeFdSpecificOptions = options => { const optionsCopy = {...options}; @@ -62,6 +65,7 @@ Please set the "stdio" option to ensure that file descriptor exists.`); return fdNumber === 'all' ? [1, 2] : [fdNumber]; }; +// Use the same syntax for fd-specific options and the `from`/`to` options export const parseFd = fdName => { if (fdName === 'all') { return fdName; @@ -91,4 +95,5 @@ const DEFAULT_OPTIONS = { stripFinalNewline: true, }; +// List of options which can have different values for `stdout`/`stderr`. export const FD_SPECIFIC_OPTIONS = ['lines', 'buffer', 'maxBuffer', 'verbose', 'stripFinalNewline']; diff --git a/lib/convert/add.js b/lib/convert/add.js index c80e35f424..699aa2bacd 100644 --- a/lib/convert/add.js +++ b/lib/convert/add.js @@ -4,6 +4,7 @@ import {createWritable} from './writable.js'; import {createDuplex} from './duplex.js'; import {createIterable} from './iterable.js'; +// Add methods to convert the subprocess to a stream or iterable export const addConvertedStreams = (subprocess, {encoding}) => { const concurrentStreams = initializeConcurrentStreams(); subprocess.readable = createReadable.bind(undefined, {subprocess, concurrentStreams, encoding}); diff --git a/lib/convert/duplex.js b/lib/convert/duplex.js index 82357c75ea..ecfcf9eefd 100644 --- a/lib/convert/duplex.js +++ b/lib/convert/duplex.js @@ -15,7 +15,7 @@ import { onWritableDestroy, } from './writable.js'; -// Create a `Duplex` stream combining both +// Create a `Duplex` stream combining both `subprocess.readable()` and `subprocess.writable()` export const createDuplex = ({subprocess, concurrentStreams, encoding}, {from, to, binary: binaryOption = true, preserveNewlines = true} = {}) => { const binary = binaryOption || BINARY_ENCODINGS.has(encoding); const {subprocessStdout, waitReadableDestroy} = getSubprocessStdout(subprocess, from, concurrentStreams); diff --git a/lib/convert/iterable.js b/lib/convert/iterable.js index 0e652ba015..d332f2643c 100644 --- a/lib/convert/iterable.js +++ b/lib/convert/iterable.js @@ -2,6 +2,7 @@ import {BINARY_ENCODINGS} from '../arguments/encoding-option.js'; import {getFromStream} from '../arguments/fd-options.js'; import {iterateOnSubprocessStream} from '../io/iterate.js'; +// Convert the subprocess to an async iterable export const createIterable = (subprocess, encoding, { from, binary: binaryOption = false, diff --git a/lib/io/contents.js b/lib/io/contents.js index bae1b744e2..3de53a2462 100644 --- a/lib/io/contents.js +++ b/lib/io/contents.js @@ -6,6 +6,7 @@ import {iterateForResult} from './iterate.js'; import {handleMaxBuffer} from './max-buffer.js'; import {getStripFinalNewline} from './strip-newline.js'; +// Retrieve `result.stdout|stderr|all|stdio[*]` export const getStreamOutput = async ({stream, onStreamEnd, fdNumber, encoding, buffer, maxBuffer, lines, allMixed, stripFinalNewline, verboseInfo, streamInfo: {fileDescriptors}}) => { if (shouldLogOutput({ stdioItems: fileDescriptors[fdNumber]?.stdioItems, @@ -91,6 +92,7 @@ export const getBufferedData = async streamPromise => { } }; +// Ensure we are returning Uint8Arrays when using `encoding: 'buffer'` const handleBufferedData = ({bufferedData}) => isArrayBuffer(bufferedData) ? new Uint8Array(bufferedData) : bufferedData; diff --git a/lib/io/max-buffer.js b/lib/io/max-buffer.js index 3d51ba7746..5aaae82c21 100644 --- a/lib/io/max-buffer.js +++ b/lib/io/max-buffer.js @@ -1,6 +1,8 @@ import {MaxBufferError} from 'get-stream'; import {getStreamName} from '../utils/standard-stream.js'; +// When the `maxBuffer` option is hit, a MaxBufferError is thrown. +// The stream is aborted, then specific information is kept for the error message. export const handleMaxBuffer = ({error, stream, readableObjectMode, lines, encoding, fdNumber}) => { if (!(error instanceof MaxBufferError)) { throw error; @@ -32,6 +34,7 @@ const getMaxBufferUnit = (readableObjectMode, lines, encoding) => { return 'characters'; }; +// Error message when `maxBuffer` is hit export const getMaxBufferMessage = (error, maxBuffer) => { const {streamName, threshold, unit} = getMaxBufferInfo(error, maxBuffer); return `Command's ${streamName} was larger than ${threshold} ${unit}`; @@ -47,6 +50,9 @@ const getMaxBufferInfo = (error, maxBuffer) => { return {streamName: getStreamName(fdNumber), threshold: maxBuffer[fdNumber], unit}; }; +// The only way to apply `maxBuffer` with `spawnSync()` is to use the native `maxBuffer` option Node.js provides. +// However, this has multiple limitations, and cannot behave the exact same way as the async behavior. +// When the `maxBuffer` is hit, a `ENOBUFS` error is thrown. export const isMaxBufferSync = (resultError, output, maxBuffer) => resultError?.code === 'ENOBUFS' && output !== null && output.some(result => result !== null && result.length > getMaxBufferSync(maxBuffer)); diff --git a/lib/io/output-async.js b/lib/io/output-async.js index 7967e969be..6ce6713a8d 100644 --- a/lib/io/output-async.js +++ b/lib/io/output-async.js @@ -32,7 +32,7 @@ export const pipeOutputAsync = (subprocess, fileDescriptors, controller) => { } }; -// `subprocess.stdin|stdout|stderr|stdio` is directly mutated. +// When using transforms, `subprocess.stdin|stdout|stderr|stdio` is directly mutated const pipeTransform = (subprocess, stream, direction, fdNumber) => { if (direction === 'output') { pipeStreams(subprocess.stdio[fdNumber], stream); @@ -50,6 +50,8 @@ const pipeTransform = (subprocess, stream, direction, fdNumber) => { const SUBPROCESS_STREAM_PROPERTIES = ['stdin', 'stdout', 'stderr']; +// Most `std*` option values involve piping `subprocess.std*` to a stream. +// The stream is either passed by the user or created internally. const pipeStdioItem = ({subprocess, stream, direction, fdNumber, inputStreamsGroups, controller}) => { if (stream === undefined) { return; diff --git a/lib/io/output-sync.js b/lib/io/output-sync.js index e92dcbc612..9d0668d764 100644 --- a/lib/io/output-sync.js +++ b/lib/io/output-sync.js @@ -67,6 +67,7 @@ const transformOutputResultSync = ({result, fileDescriptors, fdNumber, state, is } }; +// Applies transform generators to `stdout`/`stderr` const runOutputGeneratorsSync = (chunks, stdioItems, encoding, state) => { try { return runGeneratorsSync(chunks, stdioItems, encoding, false); @@ -76,6 +77,9 @@ const runOutputGeneratorsSync = (chunks, stdioItems, encoding, state) => { } }; +// The contents is converted to three stages: +// - serializedResult: used when the target is a file path/URL or a file descriptor (including 'inherit') +// - finalResult/returnedResult: returned as `result.std*` const serializeChunks = ({chunks, objectMode, encoding, lines, stripFinalNewline, fdNumber}) => { if (objectMode) { return {serializedResult: chunks}; @@ -93,6 +97,7 @@ const serializeChunks = ({chunks, objectMode, encoding, lines, stripFinalNewline return {serializedResult}; }; +// When the `std*` target is a file path/URL or a file descriptor const writeToFiles = (serializedResult, stdioItems) => { for (const {type, path} of stdioItems) { if (FILE_TYPES.has(type)) { diff --git a/lib/io/pipeline.js b/lib/io/pipeline.js index c02a52e8e7..423639c08c 100644 --- a/lib/io/pipeline.js +++ b/lib/io/pipeline.js @@ -1,7 +1,7 @@ import {finished} from 'node:stream/promises'; import {isStandardStream} from '../utils/standard-stream.js'; -// Like `Stream.pipeline(source, destination)`, but does not destroy standard streams. +// Similar to `Stream.pipeline(source, destination)`, but does not destroy standard streams export const pipeStreams = (source, destination) => { source.pipe(destination); onSourceFinish(source, destination); diff --git a/lib/io/strip-newline.js b/lib/io/strip-newline.js index cffad671c7..78d1401eb0 100644 --- a/lib/io/strip-newline.js +++ b/lib/io/strip-newline.js @@ -1,9 +1,12 @@ import stripFinalNewlineFunction from 'strip-final-newline'; +// Apply `stripFinalNewline` option, which applies to `result.stdout|stderr|all|stdio[*]`. +// If the `lines` option is used, it is applied on each line, but using a different function. export const stripNewline = (value, {stripFinalNewline}, fdNumber) => getStripFinalNewline(stripFinalNewline, fdNumber) && value !== undefined && !Array.isArray(value) ? stripFinalNewlineFunction(value) : value; +// Retrieve `stripFinalNewline` option value, including with `subprocess.all` export const getStripFinalNewline = (stripFinalNewline, fdNumber) => fdNumber === 'all' ? stripFinalNewline[1] || stripFinalNewline[2] : stripFinalNewline[fdNumber]; diff --git a/lib/methods/command.js b/lib/methods/command.js index 3ab67636b9..40599a4664 100644 --- a/lib/methods/command.js +++ b/lib/methods/command.js @@ -1,6 +1,10 @@ +// Main logic for `execaCommand()` export const mapCommandAsync = ({file, commandArguments}) => parseCommand(file, commandArguments); + +// Main logic for `execaCommandSync()` export const mapCommandSync = ({file, commandArguments}) => ({...parseCommand(file, commandArguments), isSync: true}); +// Convert `execaCommand(command)` into `execa(file, ...commandArguments)` const parseCommand = (command, unusedArguments) => { if (unusedArguments.length > 0) { throw new TypeError(`The command and its arguments must be passed as a single string: ${command} ${unusedArguments}.`); diff --git a/lib/methods/create.js b/lib/methods/create.js index 179e55e617..d59fe0da22 100644 --- a/lib/methods/create.js +++ b/lib/methods/create.js @@ -5,6 +5,11 @@ import {execaCoreSync} from './main-sync.js'; import {execaCoreAsync} from './main-async.js'; import {mergeOptions} from './bind.js'; +// Wraps every exported methods to provide the following features: +// - template string syntax: execa`command argument` +// - options binding: boundExeca = execa(options) +// - optional argument/options: execa(file), execa(file, args), execa(file, options), execa(file, args, options) +// `mapArguments()` and `setBoundExeca()` allows for method-specific logic. export const createExeca = (mapArguments, boundOptions, deepOptions, setBoundExeca) => { const createNested = (mapArguments, boundOptions, setBoundExeca) => createExeca(mapArguments, boundOptions, deepOptions, setBoundExeca); const boundExeca = (...execaArguments) => callBoundExeca({ diff --git a/lib/methods/main-async.js b/lib/methods/main-async.js index c888180f2e..93b3851d5d 100644 --- a/lib/methods/main-async.js +++ b/lib/methods/main-async.js @@ -19,6 +19,7 @@ import {waitForSubprocessResult} from '../resolve/wait-subprocess.js'; import {addConvertedStreams} from '../convert/add.js'; import {mergePromise} from './promise.js'; +// Main shared logic for all async methods: `execa()`, `execaCommand()`, `$`, `execaNode()` export const execaCoreAsync = (rawFile, rawArguments, rawOptions, createNested) => { const {file, commandArguments, command, escapedCommand, startTime, verboseInfo, options, fileDescriptors} = handleAsyncArguments(rawFile, rawArguments, rawOptions); const {subprocess, promise} = spawnSubprocessAsync({ @@ -42,6 +43,7 @@ export const execaCoreAsync = (rawFile, rawArguments, rawOptions, createNested) return subprocess; }; +// Compute arguments to pass to `child_process.spawn()` const handleAsyncArguments = (rawFile, rawArguments, rawOptions) => { const {command, escapedCommand, startTime, verboseInfo} = handleCommand(rawFile, rawArguments, rawOptions); @@ -65,6 +67,7 @@ const handleAsyncArguments = (rawFile, rawArguments, rawOptions) => { } }; +// Options normalization logic specific to async methods. // Prevent passing the `timeout` option directly to `child_process.spawn()` const handleAsyncOptions = ({timeout, signal, cancelSignal, ...options}) => { if (signal !== undefined) { @@ -120,6 +123,7 @@ const spawnSubprocessAsync = ({file, commandArguments, options, startTime, verbo return {subprocess, promise}; }; +// Asynchronous logic, as opposed to the previous logic which can be run synchronously, i.e. can be returned to user right away const handlePromise = async ({subprocess, options, startTime, verboseInfo, fileDescriptors, originalStreams, command, escapedCommand, controller}) => { const context = {timedOut: false}; diff --git a/lib/methods/main-sync.js b/lib/methods/main-sync.js index 38d3d6533d..77566f7493 100644 --- a/lib/methods/main-sync.js +++ b/lib/methods/main-sync.js @@ -12,6 +12,7 @@ import {logEarlyResult} from '../verbose/complete.js'; import {getAllSync} from '../resolve/all-sync.js'; import {getExitResultSync} from '../resolve/exit-sync.js'; +// Main shared logic for all sync methods: `execaSync()`, `execaCommandSync()`, `$.sync()` export const execaCoreSync = (rawFile, rawArguments, rawOptions) => { const {file, commandArguments, command, escapedCommand, startTime, verboseInfo, options, fileDescriptors} = handleSyncArguments(rawFile, rawArguments, rawOptions); const result = spawnSubprocessSync({ @@ -27,6 +28,7 @@ export const execaCoreSync = (rawFile, rawArguments, rawOptions) => { return handleResult(result, verboseInfo, options); }; +// Compute arguments to pass to `child_process.spawnSync()` const handleSyncArguments = (rawFile, rawArguments, rawOptions) => { const {command, escapedCommand, startTime, verboseInfo} = handleCommand(rawFile, rawArguments, rawOptions); @@ -51,8 +53,10 @@ const handleSyncArguments = (rawFile, rawArguments, rawOptions) => { } }; +// Options normalization logic specific to sync methods const normalizeSyncOptions = options => options.node && !options.ipc ? {...options, ipc: false} : options; +// Options validation logic specific to sync methods const validateSyncOptions = ({ipc, detached, cancelSignal}) => { if (ipc) { throwInvalidSyncOption('ipc: true'); @@ -128,6 +132,7 @@ const runSubprocessSync = ({file, commandArguments, options, command, escapedCom } }; +// The `encoding` option is handled by Execa, not by `child_process.spawnSync()`. const normalizeSpawnSyncOptions = ({encoding, maxBuffer, ...options}) => ({...options, encoding: 'buffer', maxBuffer: getMaxBufferSync(maxBuffer)}); const getSyncResult = ({error, exitCode, signal, timedOut, isMaxBuffer, stdio, all, options, command, escapedCommand, startTime}) => error === undefined diff --git a/lib/methods/node.js b/lib/methods/node.js index d4d6d29ee0..c95bbb4a63 100644 --- a/lib/methods/node.js +++ b/lib/methods/node.js @@ -2,6 +2,7 @@ import {execPath, execArgv} from 'node:process'; import {basename, resolve} from 'node:path'; import {safeNormalizeFileUrl} from '../arguments/file-url.js'; +// `execaNode()` is a shortcut for `execa(..., {node: true})` export const mapNode = ({options}) => { if (options.node === false) { throw new TypeError('The "node" option cannot be false with `execaNode()`.'); @@ -10,6 +11,9 @@ export const mapNode = ({options}) => { return {options: {...options, node: true}}; }; +// Applies the `node: true` option, and the related `nodePath`/`nodeOptions` options +// Modifies the file commands/arguments to ensure the same Node binary and flags are re-used. +// Also adds `ipc: true` and `shell: false`. export const handleNodeOption = (file, commandArguments, { node: shouldHandleNode = false, nodePath = execPath, diff --git a/lib/methods/parameters.js b/lib/methods/parameters.js index 3e2254fc54..c4e526fa1c 100644 --- a/lib/methods/parameters.js +++ b/lib/methods/parameters.js @@ -1,6 +1,8 @@ import isPlainObject from 'is-plain-obj'; import {safeNormalizeFileUrl} from '../arguments/file-url.js'; +// The command `arguments` and `options` are both optional. +// This also does basic validation on them and on the command file. export const normalizeParameters = (rawFile, rawArguments = [], rawOptions = {}) => { const filePath = safeNormalizeFileUrl(rawFile, 'First argument'); const [commandArguments, options] = isPlainObject(rawArguments) diff --git a/lib/methods/script.js b/lib/methods/script.js index 8839e7c791..a3f98b61a4 100644 --- a/lib/methods/script.js +++ b/lib/methods/script.js @@ -1,15 +1,22 @@ +// Sets `$.sync` and `$.s` export const setScriptSync = (boundExeca, createNested, boundOptions) => { boundExeca.sync = createNested(mapScriptSync, boundOptions); boundExeca.s = boundExeca.sync; }; +// Main logic for `$` export const mapScriptAsync = ({options}) => getScriptOptions(options); + +// Main logic for `$.sync` const mapScriptSync = ({options}) => ({...getScriptOptions(options), isSync: true}); +// `$` is like `execa` but with script-friendly options: `{stdin: 'inherit', preferLocal: true}` const getScriptOptions = options => ({options: {...getScriptStdinOption(options), ...options}}); const getScriptStdinOption = ({input, inputFile, stdio}) => input === undefined && inputFile === undefined && stdio === undefined ? {stdin: 'inherit'} : {}; +// When using $(...).pipe(...), most script-friendly options should apply to both commands. +// However, some options (like `stdin: 'inherit'`) would create issues with piping, i.e. cannot be deep. export const deepScriptOptions = {preferLocal: true}; diff --git a/lib/methods/template.js b/lib/methods/template.js index 0b76e412a3..b641db173d 100644 --- a/lib/methods/template.js +++ b/lib/methods/template.js @@ -2,8 +2,10 @@ import {ChildProcess} from 'node:child_process'; import isPlainObject from 'is-plain-obj'; import {isUint8Array, uint8ArrayToString} from '../utils/uint-array.js'; +// Check whether the template string syntax is being used export const isTemplateString = templates => Array.isArray(templates) && Array.isArray(templates.raw); +// Convert execa`file ...commandArguments` to execa(file, commandArguments) export const parseTemplates = (templates, expressions) => { let tokens = []; @@ -106,6 +108,7 @@ const concatTokens = (tokens, nextTokens, isSeparated) => isSeparated ...nextTokens.slice(1), ]; +// Handle `${expression}` inside the template string syntax const parseExpression = expression => { const typeOfExpression = typeof expression; diff --git a/lib/pipe/abort.js b/lib/pipe/abort.js index d22ec882ef..d8b5d34119 100644 --- a/lib/pipe/abort.js +++ b/lib/pipe/abort.js @@ -1,6 +1,8 @@ import {aborted} from 'node:util'; import {createNonCommandError} from './throw.js'; +// When passing an `unpipeSignal` option, abort piping when the signal is aborted. +// However, do not terminate the subprocesses. export const unpipeOnAbort = (unpipeSignal, unpipeContext) => unpipeSignal === undefined ? [] : [unpipeOnSignalAbort(unpipeSignal, unpipeContext)]; diff --git a/lib/pipe/pipe-arguments.js b/lib/pipe/pipe-arguments.js index 1f732244fb..a1c9e58dd4 100644 --- a/lib/pipe/pipe-arguments.js +++ b/lib/pipe/pipe-arguments.js @@ -2,6 +2,7 @@ import {normalizeParameters} from '../methods/parameters.js'; import {getStartTime} from '../return/duration.js'; import {SUBPROCESS_OPTIONS, getToStream, getFromStream} from '../arguments/fd-options.js'; +// Normalize and validate arguments passed to `source.pipe(destination)` export const normalizePipeArguments = ({source, sourcePromise, boundOptions, createNested}, ...pipeArguments) => { const startTime = getStartTime(); const { @@ -45,6 +46,10 @@ const getDestinationStream = (boundOptions, createNested, pipeArguments) => { } }; +// Piping subprocesses can use three syntaxes: +// - source.pipe('command', commandArguments, pipeOptionsOrDestinationOptions) +// - source.pipe`command commandArgument` or source.pipe(pipeOptionsOrDestinationOptions)`command commandArgument` +// - source.pipe(execa(...), pipeOptions) const getDestination = (boundOptions, createNested, firstArgument, ...pipeArguments) => { if (Array.isArray(firstArgument)) { const destination = createNested(mapDestinationArguments, boundOptions)(firstArgument, ...pipeArguments); @@ -72,6 +77,7 @@ const getDestination = (boundOptions, createNested, firstArgument, ...pipeArgume throw new TypeError(`The first argument must be a template string, an options object, or an Execa subprocess: ${firstArgument}`); }; +// Force `stdin: 'pipe'` with the destination subprocess const mapDestinationArguments = ({options}) => ({options: {...options, stdin: 'pipe', piped: true}}); const getSourceStream = (source, from) => { diff --git a/lib/pipe/setup.js b/lib/pipe/setup.js index 470d6bf216..bf1a87b503 100644 --- a/lib/pipe/setup.js +++ b/lib/pipe/setup.js @@ -25,6 +25,7 @@ export const pipeToSubprocess = (sourceInfo, ...pipeArguments) => { return promise; }; +// Asynchronous logic when piping subprocesses const handlePipePromise = async ({ sourcePromise, sourceStream, diff --git a/lib/pipe/throw.js b/lib/pipe/throw.js index b17a10aa2d..e13f749894 100644 --- a/lib/pipe/throw.js +++ b/lib/pipe/throw.js @@ -1,6 +1,8 @@ import {makeEarlyError} from '../return/result.js'; import {abortSourceStream, endDestinationStream} from '../io/pipeline.js'; +// When passing invalid arguments to `source.pipe()`, throw asynchronously. +// We also abort both subprocesses. export const handlePipeArgumentsError = ({ sourceStream, sourceError, @@ -42,6 +44,7 @@ const getPipeArgumentsError = ({sourceStream, sourceError, destinationStream, de } }; +// Specific error return value when passing invalid arguments to `subprocess.pipe()` or when using `unpipeSignal` export const createNonCommandError = ({error, fileDescriptors, sourceOptions, startTime}) => makeEarlyError({ error, command: PIPE_COMMAND_MESSAGE, diff --git a/lib/resolve/all-sync.js b/lib/resolve/all-sync.js index 065aa6d4c4..bda3a3f1e5 100644 --- a/lib/resolve/all-sync.js +++ b/lib/resolve/all-sync.js @@ -1,6 +1,7 @@ import {isUint8Array, concatUint8Arrays} from '../utils/uint-array.js'; import {stripNewline} from '../io/strip-newline.js'; +// Retrieve `result.all` with synchronous methods export const getAllSync = ([, stdout, stderr], options) => { if (!options.all) { return; diff --git a/lib/resolve/exit-async.js b/lib/resolve/exit-async.js index cfd7189c13..3b0cfc740c 100644 --- a/lib/resolve/exit-async.js +++ b/lib/resolve/exit-async.js @@ -31,6 +31,7 @@ const waitForSubprocessExit = async subprocess => { } }; +// Retrieve the final exit code and|or signal name export const waitForSuccessfulExit = async exitPromise => { const [exitCode, signal] = await exitPromise; @@ -41,5 +42,7 @@ export const waitForSuccessfulExit = async exitPromise => { return [exitCode, signal]; }; +// When the subprocess fails due to an `error` event const isSubprocessErrorExit = (exitCode, signal) => exitCode === undefined && signal === undefined; +// When the subprocess fails due to a non-0 exit code or to a signal termination export const isFailedExit = (exitCode, signal) => exitCode !== 0 || signal !== null; diff --git a/lib/resolve/exit-sync.js b/lib/resolve/exit-sync.js index f4a48fc7a2..2ab0b37427 100644 --- a/lib/resolve/exit-sync.js +++ b/lib/resolve/exit-sync.js @@ -2,6 +2,7 @@ import {DiscardedError} from '../return/final-error.js'; import {isMaxBufferSync} from '../io/max-buffer.js'; 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'; diff --git a/lib/resolve/stdio.js b/lib/resolve/stdio.js index c6890b250b..58abfd26cf 100644 --- a/lib/resolve/stdio.js +++ b/lib/resolve/stdio.js @@ -15,6 +15,7 @@ export const waitForStdioStreams = ({subprocess, encoding, buffer, maxBuffer, li streamInfo, })); +// Read the contents of `subprocess.std*` or `subprocess.all` and|or wait for its completion export const waitForSubprocessStream = async ({stream, fdNumber, encoding, buffer, maxBuffer, lines, allMixed, stripFinalNewline, verboseInfo, streamInfo}) => { if (!stream) { return; diff --git a/lib/resolve/wait-subprocess.js b/lib/resolve/wait-subprocess.js index 817697a644..8d66c1c94b 100644 --- a/lib/resolve/wait-subprocess.js +++ b/lib/resolve/wait-subprocess.js @@ -95,11 +95,14 @@ const waitForCustomStreamsEnd = (fileDescriptors, streamInfo) => fileDescriptors stopOnExit: type === 'native', }))); +// Fails when the subprocess emits an `error` event const throwOnSubprocessError = async (subprocess, {signal}) => { const [error] = await once(subprocess, 'error', {signal}); throw error; }; +// Fails right away when calling `subprocess.kill(error)` +// Does not wait for actual signal termination. const throwOnInternalError = async (subprocess, {signal}) => { const [error] = await once(subprocess, errorSignal, {signal}); throw error; diff --git a/lib/return/duration.js b/lib/return/duration.js index 752f00bc52..bf431e1189 100644 --- a/lib/return/duration.js +++ b/lib/return/duration.js @@ -1,5 +1,8 @@ import {hrtime} from 'node:process'; +// Start counting time before spawning the subprocess export const getStartTime = () => hrtime.bigint(); +// Compute duration after the subprocess ended. +// Printed by the `verbose` option. export const getDurationMs = startTime => Number(hrtime.bigint() - startTime) / 1e6; diff --git a/lib/return/final-error.js b/lib/return/final-error.js index 137bf4bd28..045bb6e3ba 100644 --- a/lib/return/final-error.js +++ b/lib/return/final-error.js @@ -1,3 +1,5 @@ +// When the subprocess fails, this is the error instance being returned. +// If another error instance is being thrown, it is kept as `error.cause`. export const getFinalError = (originalError, message, isSync) => { const ErrorClass = isSync ? ExecaSyncError : ExecaError; const options = originalError instanceof DiscardedError ? {} : {cause: originalError}; @@ -30,6 +32,7 @@ const execaErrorSymbol = Symbol('isExecaError'); export const isErrorInstance = value => Object.prototype.toString.call(value) === '[object Error]'; +// We use two different Error classes for async/sync methods since they have slightly different shape and types export class ExecaError extends Error {} setErrorName(ExecaError, ExecaError.name); diff --git a/lib/return/message.js b/lib/return/message.js index 36fda76be8..48458676e8 100644 --- a/lib/return/message.js +++ b/lib/return/message.js @@ -5,6 +5,7 @@ import {escapeLines} from '../arguments/escape.js'; import {getMaxBufferMessage} from '../io/max-buffer.js'; import {DiscardedError, isExecaError} from './final-error.js'; +// Computes `error.message`, `error.shortMessage` and `error.originalMessage` export const createMessages = ({ stdio, all, diff --git a/lib/return/reject.js b/lib/return/reject.js index 569367af80..f70e7b966f 100644 --- a/lib/return/reject.js +++ b/lib/return/reject.js @@ -1,5 +1,7 @@ import {logFinalResult} from '../verbose/complete.js'; +// Applies the `reject` option. +// Also print the final log line with `verbose`. export const handleResult = (result, verboseInfo, {reject}) => { logFinalResult(result, reject, verboseInfo); diff --git a/lib/return/result.js b/lib/return/result.js index d5af10e635..390fc0dd89 100644 --- a/lib/return/result.js +++ b/lib/return/result.js @@ -3,6 +3,7 @@ import {getDurationMs} from './duration.js'; import {getFinalError} from './final-error.js'; import {createMessages} from './message.js'; +// Object returned on subprocess success export const makeSuccessResult = ({ command, escapedCommand, @@ -28,6 +29,7 @@ export const makeSuccessResult = ({ pipedFrom: [], }); +// Object returned on subprocess failure before spawning export const makeEarlyError = ({ error, command, @@ -49,6 +51,7 @@ export const makeEarlyError = ({ isSync, }); +// Object returned on subprocess failure export const makeError = ({ error: originalError, command, diff --git a/lib/stdio/handle-async.js b/lib/stdio/handle-async.js index 395c7eefb2..c2b992995e 100644 --- a/lib/stdio/handle-async.js +++ b/lib/stdio/handle-async.js @@ -13,6 +13,8 @@ const forbiddenIfAsync = ({type, optionName}) => { throw new TypeError(`The \`${optionName}\` option cannot be ${TYPE_TO_MESSAGE[type]}.`); }; +// Create streams used internally for piping when using specific values for the `std*` options, in async mode. +// For example, `stdout: {file}` creates a file stream, which is piped from/to. const addProperties = { generator: generatorToStream, asyncGenerator: generatorToStream, diff --git a/lib/stdio/handle-sync.js b/lib/stdio/handle-sync.js index 43fc5e907d..0a312f0b6c 100644 --- a/lib/stdio/handle-sync.js +++ b/lib/stdio/handle-sync.js @@ -22,6 +22,8 @@ const throwInvalidSyncValue = (optionName, value) => { throw new TypeError(`The \`${optionName}\` option cannot be ${value} with synchronous methods.`); }; +// Create streams used internally for redirecting when using specific values for the `std*` options, in sync mode. +// For example, `stdin: {file}` reads the file synchronously, then passes it as the `input` option. const addProperties = { generator() {}, asyncGenerator: forbiddenIfSync, diff --git a/lib/stdio/handle.js b/lib/stdio/handle.js index 4dcf6f5964..b97ab5f139 100644 --- a/lib/stdio/handle.js +++ b/lib/stdio/handle.js @@ -13,6 +13,8 @@ import {handleNativeStream} from './native.js'; import {handleInputOptions} from './input-option.js'; // Handle `input`, `inputFile`, `stdin`, `stdout` and `stderr` options, before spawning, in async/sync mode +// They are converted into an array of `fileDescriptors`. +// Each `fileDescriptor` is normalized, validated and contains all information necessary for further handling. export const handleStdio = (addProperties, options, verboseInfo, isSync) => { const stdio = normalizeStdioOption(options, isSync); const fileDescriptors = stdio.map((stdioOption, fdNumber) => getFileDescriptor({ @@ -26,9 +28,6 @@ export const handleStdio = (addProperties, options, verboseInfo, isSync) => { return fileDescriptors; }; -// We make sure passing an array with a single item behaves the same as passing that item without an array. -// This is what users would expect. -// For example, `stdout: ['ignore']` behaves the same as `stdout: 'ignore'`. const getFileDescriptor = ({stdioOption, fdNumber, addProperties, options, isSync}) => { const optionName = getStreamName(fdNumber); const {stdioItems: initialStdioItems, isStdioArray} = initializeStdioItems({ @@ -52,6 +51,9 @@ const getFileDescriptor = ({stdioOption, fdNumber, addProperties, options, isSyn return {direction, objectMode, stdioItems: finalStdioItems}; }; +// We make sure passing an array with a single item behaves the same as passing that item without an array. +// This is what users would expect. +// For example, `stdout: ['ignore']` behaves the same as `stdout: 'ignore'`. const initializeStdioItems = ({stdioOption, fdNumber, options, optionName}) => { const values = Array.isArray(stdioOption) ? stdioOption : [stdioOption]; const initialStdioItems = [ diff --git a/lib/stdio/native.js b/lib/stdio/native.js index 8439cf2244..90fb75d6c8 100644 --- a/lib/stdio/native.js +++ b/lib/stdio/native.js @@ -22,6 +22,8 @@ export const handleNativeStream = ({stdioItem, stdioItem: {type}, isStdioArray, : handleNativeStreamAsync({stdioItem, fdNumber}); }; +// Synchronous methods use a different logic. +// 'inherit', file descriptors and process.std* are handled by readFileSync()/writeFileSync() const handleNativeStreamSync = ({stdioItem, stdioItem: {value, optionName}, fdNumber, direction}) => { const targetFd = getTargetFd({ value, diff --git a/lib/stdio/stdio-option.js b/lib/stdio/stdio-option.js index 3e05989990..01cce33ade 100644 --- a/lib/stdio/stdio-option.js +++ b/lib/stdio/stdio-option.js @@ -1,6 +1,7 @@ import {STANDARD_STREAMS_ALIASES} from '../utils/standard-stream.js'; -// Add support for `stdin`/`stdout`/`stderr` as an alias for `stdio` +// Add support for `stdin`/`stdout`/`stderr` as an alias for `stdio`. +// Also normalize the `stdio` option. export const normalizeStdioOption = ({stdio, ipc, buffer, verbose, ...options}, isSync) => { const stdioArray = getStdioArray(stdio, options).map((stdioOption, fdNumber) => addDefaultValue(stdioOption, fdNumber)); return isSync ? normalizeStdioSync(stdioArray, buffer, verbose) : normalizeStdioAsync(stdioArray, ipc); @@ -41,6 +42,8 @@ const addDefaultValue = (stdioOption, fdNumber) => { return stdioOption; }; +// Using `buffer: false` with synchronous methods implies `stdout`/`stderr`: `ignore`. +// Unless the output is needed, e.g. due to `verbose: 'full'` or to redirecting to a file. const normalizeStdioSync = (stdioArray, buffer, verbose) => stdioArray.map((stdioOption, fdNumber) => !buffer[fdNumber] && fdNumber !== 0 @@ -52,6 +55,7 @@ const normalizeStdioSync = (stdioArray, buffer, verbose) => stdioArray.map((stdi const isOutputPipeOnly = stdioOption => stdioOption === 'pipe' || (Array.isArray(stdioOption) && stdioOption.every(item => item === 'pipe')); +// The `ipc` option adds an `ipc` item to the `stdio` option const normalizeStdioAsync = (stdioArray, ipc) => ipc && !stdioArray.includes('ipc') ? [...stdioArray, 'ipc'] : stdioArray; diff --git a/lib/stdio/type.js b/lib/stdio/type.js index 3279a75bfc..6431585010 100644 --- a/lib/stdio/type.js +++ b/lib/stdio/type.js @@ -142,7 +142,9 @@ const isAsyncIterableObject = value => isObject(value) && typeof value[Symbol.as const isIterableObject = value => isObject(value) && typeof value[Symbol.iterator] === 'function'; const isObject = value => typeof value === 'object' && value !== null; +// Types which modify `subprocess.std*` export const TRANSFORM_TYPES = new Set(['generator', 'asyncGenerator', 'duplex', 'webTransform']); +// Types which write to a file or a file descriptor export const FILE_TYPES = new Set(['fileUrl', 'filePath', 'fileNumber']); // Convert types to human-friendly strings for error messages diff --git a/lib/terminate/cleanup.js b/lib/terminate/cleanup.js index 53554a8018..5e98788d67 100644 --- a/lib/terminate/cleanup.js +++ b/lib/terminate/cleanup.js @@ -1,7 +1,7 @@ import {addAbortListener} from 'node:events'; import {onExit} from 'signal-exit'; -// `cleanup` option handling +// If the `cleanup` option is used, call `subprocess.kill()` when the parent process exits export const cleanupOnExit = (subprocess, {cleanup, detached}, {signal}) => { if (!cleanup || detached) { return; diff --git a/lib/terminate/kill.js b/lib/terminate/kill.js index 2855b5a6fd..a287c64b4e 100644 --- a/lib/terminate/kill.js +++ b/lib/terminate/kill.js @@ -2,6 +2,7 @@ import os from 'node:os'; import {setTimeout} from 'node:timers/promises'; import {isErrorInstance} from '../return/final-error.js'; +// Normalize the `forceKillAfterDelay` option export const normalizeForceKillAfterDelay = forceKillAfterDelay => { if (forceKillAfterDelay === false) { return forceKillAfterDelay; diff --git a/lib/terminate/timeout.js b/lib/terminate/timeout.js index f61157e531..5bed7f914d 100644 --- a/lib/terminate/timeout.js +++ b/lib/terminate/timeout.js @@ -1,13 +1,14 @@ import {setTimeout} from 'node:timers/promises'; import {DiscardedError} from '../return/final-error.js'; +// Validate `timeout` option export const validateTimeout = ({timeout}) => { if (timeout !== undefined && (!Number.isFinite(timeout) || timeout < 0)) { throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`); } }; -// `timeout` option handling +// Fails when the `timeout` option is exceeded export const throwOnTimeout = (subprocess, timeout, context, controller) => timeout === 0 || timeout === undefined ? [] : [killAfterTimeout(subprocess, timeout, context, controller)]; diff --git a/lib/transform/encoding-transform.js b/lib/transform/encoding-transform.js index d3f9ab0973..4e30e76f2d 100644 --- a/lib/transform/encoding-transform.js +++ b/lib/transform/encoding-transform.js @@ -3,8 +3,7 @@ import {StringDecoder} from 'node:string_decoder'; import {isUint8Array, bufferToUint8Array} from '../utils/uint-array.js'; /* -When using generators, add an internal generator that converts chunks from `Buffer` to `string` or `Uint8Array`. -This allows generator functions to operate with those types instead. +When using binary encodings, add an internal generator that converts chunks from `Buffer` to `string` or `Uint8Array`. Chunks might be Buffer, Uint8Array or strings since: - `subprocess.stdout|stderr` emits Buffers - `subprocess.stdin.write()` accepts Buffer, Uint8Array or string diff --git a/lib/transform/generator.js b/lib/transform/generator.js index dc36d7f4eb..a6b61faccb 100644 --- a/lib/transform/generator.js +++ b/lib/transform/generator.js @@ -71,6 +71,7 @@ export const generatorToStream = ({ return {stream}; }; +// Applies transform generators in sync mode export const runGeneratorsSync = (chunks, stdioItems, encoding, isInput) => { const generators = stdioItems.filter(({type}) => type === 'generator'); const reversedGenerators = isInput ? generators.reverse() : generators; @@ -83,6 +84,7 @@ export const runGeneratorsSync = (chunks, stdioItems, encoding, isInput) => { return chunks; }; +// Generators used internally to convert the chunk type, validate it, and split into lines const addInternalGenerators = ( {transform, final, binary, writableObjectMode, readableObjectMode, preserveNewlines}, encoding, diff --git a/lib/transform/run-async.js b/lib/transform/run-async.js index f54a2e6867..7cd1633c23 100644 --- a/lib/transform/run-async.js +++ b/lib/transform/run-async.js @@ -1,5 +1,6 @@ import {callbackify} from 'node:util'; +// Applies a series of generator functions asynchronously export const pushChunks = callbackify(async (getChunks, state, getChunksArguments, transformStream) => { state.currentIterable = getChunks(...getChunksArguments); diff --git a/lib/transform/run-sync.js b/lib/transform/run-sync.js index 5c5af827d4..8e30b8cd00 100644 --- a/lib/transform/run-sync.js +++ b/lib/transform/run-sync.js @@ -1,4 +1,4 @@ -// Duplicate the code from `transform-async.js` but as synchronous functions +// Duplicate the code from `run-async.js` but as synchronous functions export const pushChunksSync = (getChunksSync, getChunksArguments, transformStream, done) => { try { for (const chunk of getChunksSync(...getChunksArguments)) { diff --git a/lib/transform/split.js b/lib/transform/split.js index c925d09877..47eb995b88 100644 --- a/lib/transform/split.js +++ b/lib/transform/split.js @@ -3,6 +3,7 @@ export const getSplitLinesGenerator = (binary, preserveNewlines, skipped, state) ? undefined : initializeSplitLines(preserveNewlines, state); +// Same but for synchronous methods export const splitLinesSync = (chunk, preserveNewlines, objectMode) => objectMode ? chunk.flatMap(item => splitLinesItemSync(item, preserveNewlines)) : splitLinesItemSync(chunk, preserveNewlines); diff --git a/lib/transform/validate.js b/lib/transform/validate.js index 77b58bb85e..820e58b44c 100644 --- a/lib/transform/validate.js +++ b/lib/transform/validate.js @@ -1,6 +1,7 @@ import {Buffer} from 'node:buffer'; import {isUint8Array} from '../utils/uint-array.js'; +// Validate the type of chunk argument passed to transform generators export const getValidateTransformInput = (writableObjectMode, optionName) => writableObjectMode ? undefined : validateStringTransformInput.bind(undefined, optionName); @@ -13,6 +14,7 @@ const validateStringTransformInput = function * (optionName, chunk) { yield chunk; }; +// Validate the type of the value returned by transform generators export const getValidateTransformReturn = (readableObjectMode, optionName) => readableObjectMode ? validateObjectTransformReturn.bind(undefined, optionName) : validateStringTransformReturn.bind(undefined, optionName); diff --git a/lib/utils/max-listeners.js b/lib/utils/max-listeners.js index 0a0c4cccf1..16856936ec 100644 --- a/lib/utils/max-listeners.js +++ b/lib/utils/max-listeners.js @@ -1,5 +1,6 @@ import {addAbortListener} from 'node:events'; +// Temporarily increase the maximum number of listeners on an eventEmitter export const incrementMaxListeners = (eventEmitter, maxListenersIncrement, signal) => { const maxListeners = eventEmitter.getMaxListeners(); if (maxListeners === 0 || maxListeners === Number.POSITIVE_INFINITY) { diff --git a/lib/verbose/info.js b/lib/verbose/info.js index d2c4693a11..63e768ccc3 100644 --- a/lib/verbose/info.js +++ b/lib/verbose/info.js @@ -1,7 +1,9 @@ import {debuglog} from 'node:util'; +// Default value for the `verbose` option export const verboseDefault = debuglog('execa').enabled ? 'full' : 'none'; +// Information computed before spawning, used by the `verbose` option export const getVerboseInfo = verbose => { const verboseId = isVerbose(verbose) ? VERBOSE_ID++ : undefined; return {verbose, verboseId}; @@ -14,4 +16,5 @@ export const getVerboseInfo = verbose => { // As a con, it cannot be used to send signals. let VERBOSE_ID = 0n; +// The `verbose` option can have different values for `stdout`/`stderr` export const isVerbose = verbose => verbose.some(fdVerbose => fdVerbose !== 'none'); diff --git a/lib/verbose/output.js b/lib/verbose/output.js index c447b4afdd..b2fcb88083 100644 --- a/lib/verbose/output.js +++ b/lib/verbose/output.js @@ -24,6 +24,7 @@ const fdUsesVerbose = fdNumber => fdNumber === 1 || fdNumber === 2; const PIPED_STDIO_VALUES = new Set(['pipe', 'overlapped']); +// `verbose` printing logic with async methods export const logLines = async (linesIterable, stream, verboseInfo) => { for await (const line of linesIterable) { if (!isPipingStream(stream)) { @@ -32,6 +33,7 @@ export const logLines = async (linesIterable, stream, verboseInfo) => { } }; +// `verbose` printing logic with sync methods export const logLinesSync = (linesArray, verboseInfo) => { for (const line of linesArray) { logLine(line, verboseInfo); diff --git a/test/arguments/cwd.js b/test/arguments/cwd.js index 0b31e14a8d..742c0f9c44 100644 --- a/test/arguments/cwd.js +++ b/test/arguments/cwd.js @@ -71,6 +71,7 @@ if (!isWindows) { const cwdNotExisting = {cwd: 'does_not_exist', expectedCode: 'ENOENT', expectedMessage: 'The "cwd" option is invalid'}; const cwdTooLong = {cwd: '.'.repeat(1e5), expectedCode: 'ENAMETOOLONG', expectedMessage: 'The "cwd" option is invalid'}; +// @todo: use import.meta.dirname after dropping support for Node <20.11.0 const cwdNotDirectory = {cwd: fileURLToPath(import.meta.url), expectedCode: isWindows ? 'ENOENT' : 'ENOTDIR', expectedMessage: 'The "cwd" option is not a directory'}; const testCwdPostSpawn = async (t, {cwd, expectedCode, expectedMessage}, execaMethod) => { diff --git a/test/helpers/fixtures-directory.js b/test/helpers/fixtures-directory.js index 000bb9c4bd..476cb5e7ee 100644 --- a/test/helpers/fixtures-directory.js +++ b/test/helpers/fixtures-directory.js @@ -5,6 +5,7 @@ import pathKey from 'path-key'; export const PATH_KEY = pathKey(); export const FIXTURES_DIRECTORY_URL = new URL('../fixtures/', import.meta.url); +// @todo: use import.meta.dirname after dropping support for Node <20.11.0 export const FIXTURES_DIRECTORY = resolve(fileURLToPath(FIXTURES_DIRECTORY_URL)); // Add the fixtures directory to PATH so fixtures can be executed without adding diff --git a/test/methods/parameters-command.js b/test/methods/parameters-command.js index 994f79e509..961ccf0185 100644 --- a/test/methods/parameters-command.js +++ b/test/methods/parameters-command.js @@ -72,6 +72,7 @@ test('$\'s command argument must be a string or file URL', testInvalidCommand, [ test('$.sync\'s command argument must be a string or file URL', testInvalidCommand, ['command', 'arg'], $.sync); const testRelativePath = async (t, execaMethod) => { + // @todo: use import.meta.dirname after dropping support for Node <20.11.0 const rootDirectory = basename(fileURLToPath(new URL('../..', import.meta.url))); const pathViaParentDirectory = join('..', rootDirectory, 'test', 'fixtures', 'noop.js'); const {stdout} = await execaMethod(pathViaParentDirectory);