147 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 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;
 | |
| };
 |