107 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			107 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import {readFileSync} from 'node:fs';
 | |
| import tty from 'node:tty';
 | |
| import {isStream as isNodeStream} from 'is-stream';
 | |
| import {STANDARD_STREAMS} from '../utils/standard-stream.js';
 | |
| import {bufferToUint8Array} from '../utils/uint-array.js';
 | |
| import {serializeOptionValue} from '../arguments/fd-options.js';
 | |
| 
 | |
| // When we use multiple `stdio` values for the same streams, we pass 'pipe' to `child_process.spawn()`.
 | |
| // We then emulate the piping done by core Node.js.
 | |
| // To do so, we transform the following values:
 | |
| //  - Node.js streams are marked as `type: nodeStream`
 | |
| //  - 'inherit' becomes `process.stdin|stdout|stderr`
 | |
| //  - any file descriptor integer becomes `process.stdio[fdNumber]`
 | |
| // All of the above transformations tell Execa to perform manual piping.
 | |
| export const handleNativeStream = ({stdioItem, stdioItem: {type}, isStdioArray, fdNumber, direction, isSync}) => {
 | |
| 	if (!isStdioArray || type !== 'native') {
 | |
| 		return stdioItem;
 | |
| 	}
 | |
| 
 | |
| 	return isSync
 | |
| 		? handleNativeStreamSync({stdioItem, fdNumber, direction})
 | |
| 		: 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,
 | |
| 		optionName,
 | |
| 		fdNumber,
 | |
| 		direction,
 | |
| 	});
 | |
| 	if (targetFd !== undefined) {
 | |
| 		return targetFd;
 | |
| 	}
 | |
| 
 | |
| 	if (isNodeStream(value, {checkOpen: false})) {
 | |
| 		throw new TypeError(`The \`${optionName}: Stream\` option cannot both be an array and include a stream with synchronous methods.`);
 | |
| 	}
 | |
| 
 | |
| 	return stdioItem;
 | |
| };
 | |
| 
 | |
| const getTargetFd = ({value, optionName, fdNumber, direction}) => {
 | |
| 	const targetFdNumber = getTargetFdNumber(value, fdNumber);
 | |
| 	if (targetFdNumber === undefined) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (direction === 'output') {
 | |
| 		return {type: 'fileNumber', value: targetFdNumber, optionName};
 | |
| 	}
 | |
| 
 | |
| 	if (tty.isatty(targetFdNumber)) {
 | |
| 		throw new TypeError(`The \`${optionName}: ${serializeOptionValue(value)}\` option is invalid: it cannot be a TTY with synchronous methods.`);
 | |
| 	}
 | |
| 
 | |
| 	return {type: 'uint8Array', value: bufferToUint8Array(readFileSync(targetFdNumber)), optionName};
 | |
| };
 | |
| 
 | |
| const getTargetFdNumber = (value, fdNumber) => {
 | |
| 	if (value === 'inherit') {
 | |
| 		return fdNumber;
 | |
| 	}
 | |
| 
 | |
| 	if (typeof value === 'number') {
 | |
| 		return value;
 | |
| 	}
 | |
| 
 | |
| 	const standardStreamIndex = STANDARD_STREAMS.indexOf(value);
 | |
| 	if (standardStreamIndex !== -1) {
 | |
| 		return standardStreamIndex;
 | |
| 	}
 | |
| };
 | |
| 
 | |
| const handleNativeStreamAsync = ({stdioItem, stdioItem: {value, optionName}, fdNumber}) => {
 | |
| 	if (value === 'inherit') {
 | |
| 		return {type: 'nodeStream', value: getStandardStream(fdNumber, value, optionName), optionName};
 | |
| 	}
 | |
| 
 | |
| 	if (typeof value === 'number') {
 | |
| 		return {type: 'nodeStream', value: getStandardStream(value, value, optionName), optionName};
 | |
| 	}
 | |
| 
 | |
| 	if (isNodeStream(value, {checkOpen: false})) {
 | |
| 		return {type: 'nodeStream', value, optionName};
 | |
| 	}
 | |
| 
 | |
| 	return stdioItem;
 | |
| };
 | |
| 
 | |
| // Node.js does not allow to easily retrieve file descriptors beyond stdin/stdout/stderr as streams.
 | |
| //  - `fs.createReadStream()`/`fs.createWriteStream()` with the `fd` option do not work with character devices that use blocking reads/writes (such as interactive TTYs).
 | |
| //  - Using a TCP `Socket` would work but be rather complex to implement.
 | |
| // Since this is an edge case, we simply throw an error message.
 | |
| // See https://github.com/sindresorhus/execa/pull/643#discussion_r1435905707
 | |
| const getStandardStream = (fdNumber, value, optionName) => {
 | |
| 	const standardStream = STANDARD_STREAMS[fdNumber];
 | |
| 
 | |
| 	if (standardStream === undefined) {
 | |
| 		throw new TypeError(`The \`${optionName}: ${value}\` option is invalid: no such standard stream.`);
 | |
| 	}
 | |
| 
 | |
| 	return standardStream;
 | |
| };
 |