195 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import {setMaxListeners} from 'node:events';
 | |
| import {spawn} from 'node:child_process';
 | |
| import {MaxBufferError} from 'get-stream';
 | |
| import {handleCommand} from '../arguments/command.js';
 | |
| import {normalizeOptions} from '../arguments/options.js';
 | |
| import {SUBPROCESS_OPTIONS} from '../arguments/fd-options.js';
 | |
| import {concatenateShell} from '../arguments/shell.js';
 | |
| import {addIpcMethods} from '../ipc/methods.js';
 | |
| import {makeError, makeSuccessResult} from '../return/result.js';
 | |
| import {handleResult} from '../return/reject.js';
 | |
| import {handleEarlyError} from '../return/early-error.js';
 | |
| import {handleStdioAsync} from '../stdio/handle-async.js';
 | |
| import {stripNewline} from '../io/strip-newline.js';
 | |
| import {pipeOutputAsync} from '../io/output-async.js';
 | |
| import {subprocessKill} from '../terminate/kill.js';
 | |
| import {cleanupOnExit} from '../terminate/cleanup.js';
 | |
| import {pipeToSubprocess} from '../pipe/setup.js';
 | |
| import {makeAllStream} from '../resolve/all-async.js';
 | |
| import {waitForSubprocessResult} from '../resolve/wait-subprocess.js';
 | |
| import {addConvertedStreams} from '../convert/add.js';
 | |
| import {createDeferred} from '../utils/deferred.js';
 | |
| import {mergePromise} from './promise.js';
 | |
| 
 | |
| // Main shared logic for all async methods: `execa()`, `$`, `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({
 | |
| 		file,
 | |
| 		commandArguments,
 | |
| 		options,
 | |
| 		startTime,
 | |
| 		verboseInfo,
 | |
| 		command,
 | |
| 		escapedCommand,
 | |
| 		fileDescriptors,
 | |
| 	});
 | |
| 	subprocess.pipe = pipeToSubprocess.bind(undefined, {
 | |
| 		source: subprocess,
 | |
| 		sourcePromise: promise,
 | |
| 		boundOptions: {},
 | |
| 		createNested,
 | |
| 	});
 | |
| 	mergePromise(subprocess, promise);
 | |
| 	SUBPROCESS_OPTIONS.set(subprocess, {options, fileDescriptors});
 | |
| 	return subprocess;
 | |
| };
 | |
| 
 | |
| // Compute arguments to pass to `child_process.spawn()`
 | |
| const handleAsyncArguments = (rawFile, rawArguments, rawOptions) => {
 | |
| 	const {command, escapedCommand, startTime, verboseInfo} = handleCommand(rawFile, rawArguments, rawOptions);
 | |
| 	const {file, commandArguments, options: normalizedOptions} = normalizeOptions(rawFile, rawArguments, rawOptions);
 | |
| 	const options = handleAsyncOptions(normalizedOptions);
 | |
| 	const fileDescriptors = handleStdioAsync(options, verboseInfo);
 | |
| 	return {
 | |
| 		file,
 | |
| 		commandArguments,
 | |
| 		command,
 | |
| 		escapedCommand,
 | |
| 		startTime,
 | |
| 		verboseInfo,
 | |
| 		options,
 | |
| 		fileDescriptors,
 | |
| 	};
 | |
| };
 | |
| 
 | |
| // Options normalization logic specific to async methods.
 | |
| // Prevent passing the `timeout` option directly to `child_process.spawn()`.
 | |
| const handleAsyncOptions = ({timeout, signal, ...options}) => {
 | |
| 	if (signal !== undefined) {
 | |
| 		throw new TypeError('The "signal" option has been renamed to "cancelSignal" instead.');
 | |
| 	}
 | |
| 
 | |
| 	return {...options, timeoutDuration: timeout};
 | |
| };
 | |
| 
 | |
| const spawnSubprocessAsync = ({file, commandArguments, options, startTime, verboseInfo, command, escapedCommand, fileDescriptors}) => {
 | |
| 	let subprocess;
 | |
| 	try {
 | |
| 		subprocess = spawn(...concatenateShell(file, commandArguments, options));
 | |
| 	} catch (error) {
 | |
| 		return handleEarlyError({
 | |
| 			error,
 | |
| 			command,
 | |
| 			escapedCommand,
 | |
| 			fileDescriptors,
 | |
| 			options,
 | |
| 			startTime,
 | |
| 			verboseInfo,
 | |
| 		});
 | |
| 	}
 | |
| 
 | |
| 	const controller = new AbortController();
 | |
| 	setMaxListeners(Number.POSITIVE_INFINITY, controller.signal);
 | |
| 
 | |
| 	const originalStreams = [...subprocess.stdio];
 | |
| 	pipeOutputAsync(subprocess, fileDescriptors, controller);
 | |
| 	cleanupOnExit(subprocess, options, controller);
 | |
| 
 | |
| 	const context = {};
 | |
| 	const onInternalError = createDeferred();
 | |
| 	subprocess.kill = subprocessKill.bind(undefined, {
 | |
| 		kill: subprocess.kill.bind(subprocess),
 | |
| 		options,
 | |
| 		onInternalError,
 | |
| 		context,
 | |
| 		controller,
 | |
| 	});
 | |
| 	subprocess.all = makeAllStream(subprocess, options);
 | |
| 	addConvertedStreams(subprocess, options);
 | |
| 	addIpcMethods(subprocess, options);
 | |
| 
 | |
| 	const promise = handlePromise({
 | |
| 		subprocess,
 | |
| 		options,
 | |
| 		startTime,
 | |
| 		verboseInfo,
 | |
| 		fileDescriptors,
 | |
| 		originalStreams,
 | |
| 		command,
 | |
| 		escapedCommand,
 | |
| 		context,
 | |
| 		onInternalError,
 | |
| 		controller,
 | |
| 	});
 | |
| 	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, context, onInternalError, controller}) => {
 | |
| 	const [
 | |
| 		errorInfo,
 | |
| 		[exitCode, signal],
 | |
| 		stdioResults,
 | |
| 		allResult,
 | |
| 		ipcOutput,
 | |
| 	] = await waitForSubprocessResult({
 | |
| 		subprocess,
 | |
| 		options,
 | |
| 		context,
 | |
| 		verboseInfo,
 | |
| 		fileDescriptors,
 | |
| 		originalStreams,
 | |
| 		onInternalError,
 | |
| 		controller,
 | |
| 	});
 | |
| 	controller.abort();
 | |
| 	onInternalError.resolve();
 | |
| 
 | |
| 	const stdio = stdioResults.map((stdioResult, fdNumber) => stripNewline(stdioResult, options, fdNumber));
 | |
| 	const all = stripNewline(allResult, options, 'all');
 | |
| 	const result = getAsyncResult({
 | |
| 		errorInfo,
 | |
| 		exitCode,
 | |
| 		signal,
 | |
| 		stdio,
 | |
| 		all,
 | |
| 		ipcOutput,
 | |
| 		context,
 | |
| 		options,
 | |
| 		command,
 | |
| 		escapedCommand,
 | |
| 		startTime,
 | |
| 	});
 | |
| 	return handleResult(result, verboseInfo, options);
 | |
| };
 | |
| 
 | |
| const getAsyncResult = ({errorInfo, exitCode, signal, stdio, all, ipcOutput, context, options, command, escapedCommand, startTime}) => 'error' in errorInfo
 | |
| 	? makeError({
 | |
| 		error: errorInfo.error,
 | |
| 		command,
 | |
| 		escapedCommand,
 | |
| 		timedOut: context.terminationReason === 'timeout',
 | |
| 		isCanceled: context.terminationReason === 'cancel' || context.terminationReason === 'gracefulCancel',
 | |
| 		isGracefullyCanceled: context.terminationReason === 'gracefulCancel',
 | |
| 		isMaxBuffer: errorInfo.error instanceof MaxBufferError,
 | |
| 		isForcefullyTerminated: context.isForcefullyTerminated,
 | |
| 		exitCode,
 | |
| 		signal,
 | |
| 		stdio,
 | |
| 		all,
 | |
| 		ipcOutput,
 | |
| 		options,
 | |
| 		startTime,
 | |
| 		isSync: false,
 | |
| 	})
 | |
| 	: makeSuccessResult({
 | |
| 		command,
 | |
| 		escapedCommand,
 | |
| 		stdio,
 | |
| 		all,
 | |
| 		ipcOutput,
 | |
| 		options,
 | |
| 		startTime,
 | |
| 	});
 |