|
|
import {once} from 'node:events';import {isStream as isNodeStream} from 'is-stream';import {throwOnTimeout} from '../terminate/timeout.js';import {throwOnCancel} from '../terminate/cancel.js';import {throwOnGracefulCancel} from '../terminate/graceful.js';import {isStandardStream} from '../utils/standard-stream.js';import {TRANSFORM_TYPES} from '../stdio/type.js';import {getBufferedData} from '../io/contents.js';import {waitForIpcOutput, getBufferedIpcOutput} from '../ipc/buffer-messages.js';import {sendIpcInput} from '../ipc/ipc-input.js';import {waitForAllStream} from './all-async.js';import {waitForStdioStreams} from './stdio.js';import {waitForExit, waitForSuccessfulExit} from './exit-async.js';import {waitForStream} from './wait-stream.js';
// Retrieve result of subprocess: exit code, signal, error, streams (stdout/stderr/all)
export const waitForSubprocessResult = async ({ subprocess, options: { encoding, buffer, maxBuffer, lines, timeoutDuration: timeout, cancelSignal, gracefulCancel, forceKillAfterDelay, stripFinalNewline, ipc, ipcInput, }, context, verboseInfo, fileDescriptors, originalStreams, onInternalError, controller,}) => { const exitPromise = waitForExit(subprocess, context); const streamInfo = { originalStreams, fileDescriptors, subprocess, exitPromise, propagating: false, };
const stdioPromises = waitForStdioStreams({ subprocess, encoding, buffer, maxBuffer, lines, stripFinalNewline, verboseInfo, streamInfo, }); const allPromise = waitForAllStream({ subprocess, encoding, buffer, maxBuffer, lines, stripFinalNewline, verboseInfo, streamInfo, }); const ipcOutput = []; const ipcOutputPromise = waitForIpcOutput({ subprocess, buffer, maxBuffer, ipc, ipcOutput, verboseInfo, }); const originalPromises = waitForOriginalStreams(originalStreams, subprocess, streamInfo); const customStreamsEndPromises = waitForCustomStreamsEnd(fileDescriptors, streamInfo);
try { return await Promise.race([ Promise.all([ {}, waitForSuccessfulExit(exitPromise), Promise.all(stdioPromises), allPromise, ipcOutputPromise, sendIpcInput(subprocess, ipcInput), ...originalPromises, ...customStreamsEndPromises, ]), onInternalError, throwOnSubprocessError(subprocess, controller), ...throwOnTimeout(subprocess, timeout, context, controller), ...throwOnCancel({ subprocess, cancelSignal, gracefulCancel, context, controller, }), ...throwOnGracefulCancel({ subprocess, cancelSignal, gracefulCancel, forceKillAfterDelay, context, controller, }), ]); } catch (error) { context.terminationReason ??= 'other'; return Promise.all([ {error}, exitPromise, Promise.all(stdioPromises.map(stdioPromise => getBufferedData(stdioPromise))), getBufferedData(allPromise), getBufferedIpcOutput(ipcOutputPromise, ipcOutput), Promise.allSettled(originalPromises), Promise.allSettled(customStreamsEndPromises), ]); }};
// Transforms replace `subprocess.std*`, which means they are not exposed to users.
// However, we still want to wait for their completion.
const waitForOriginalStreams = (originalStreams, subprocess, streamInfo) => originalStreams.map((stream, fdNumber) => stream === subprocess.stdio[fdNumber] ? undefined : waitForStream(stream, fdNumber, streamInfo));
// Some `stdin`/`stdout`/`stderr` options create a stream, e.g. when passing a file path.
// The `.pipe()` method automatically ends that stream when `subprocess` ends.
// This makes sure we wait for the completion of those streams, in order to catch any error.
const waitForCustomStreamsEnd = (fileDescriptors, streamInfo) => fileDescriptors.flatMap(({stdioItems}, fdNumber) => stdioItems .filter(({value, stream = value}) => isNodeStream(stream, {checkOpen: false}) && !isStandardStream(stream)) .map(({type, value, stream = value}) => waitForStream(stream, fdNumber, streamInfo, { isSameDirection: TRANSFORM_TYPES.has(type), 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;};
|