117 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			117 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import {
 | |
| 	SPECIAL_DUPLICATE_TYPES_SYNC,
 | |
| 	SPECIAL_DUPLICATE_TYPES,
 | |
| 	FORBID_DUPLICATE_TYPES,
 | |
| 	TYPE_TO_MESSAGE,
 | |
| } from './type.js';
 | |
| 
 | |
| // Duplicates in the same file descriptor is most likely an error.
 | |
| // However, this can be useful with generators.
 | |
| export const filterDuplicates = stdioItems => stdioItems.filter((stdioItemOne, indexOne) =>
 | |
| 	stdioItems.every((stdioItemTwo, indexTwo) => stdioItemOne.value !== stdioItemTwo.value
 | |
| 		|| indexOne >= indexTwo
 | |
| 		|| stdioItemOne.type === 'generator'
 | |
| 		|| stdioItemOne.type === 'asyncGenerator'));
 | |
| 
 | |
| // Check if two file descriptors are sharing the same target.
 | |
| // For example `{stdout: {file: './output.txt'}, stderr: {file: './output.txt'}}`.
 | |
| export const getDuplicateStream = ({stdioItem: {type, value, optionName}, direction, fileDescriptors, isSync}) => {
 | |
| 	const otherStdioItems = getOtherStdioItems(fileDescriptors, type);
 | |
| 	if (otherStdioItems.length === 0) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (isSync) {
 | |
| 		validateDuplicateStreamSync({
 | |
| 			otherStdioItems,
 | |
| 			type,
 | |
| 			value,
 | |
| 			optionName,
 | |
| 			direction,
 | |
| 		});
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (SPECIAL_DUPLICATE_TYPES.has(type)) {
 | |
| 		return getDuplicateStreamInstance({
 | |
| 			otherStdioItems,
 | |
| 			type,
 | |
| 			value,
 | |
| 			optionName,
 | |
| 			direction,
 | |
| 		});
 | |
| 	}
 | |
| 
 | |
| 	if (FORBID_DUPLICATE_TYPES.has(type)) {
 | |
| 		validateDuplicateTransform({
 | |
| 			otherStdioItems,
 | |
| 			type,
 | |
| 			value,
 | |
| 			optionName,
 | |
| 		});
 | |
| 	}
 | |
| };
 | |
| 
 | |
| // Values shared by multiple file descriptors
 | |
| const getOtherStdioItems = (fileDescriptors, type) => fileDescriptors
 | |
| 	.flatMap(({direction, stdioItems}) => stdioItems
 | |
| 		.filter(stdioItem => stdioItem.type === type)
 | |
| 		.map((stdioItem => ({...stdioItem, direction}))));
 | |
| 
 | |
| // With `execaSync()`, do not allow setting a file path both in input and output
 | |
| const validateDuplicateStreamSync = ({otherStdioItems, type, value, optionName, direction}) => {
 | |
| 	if (SPECIAL_DUPLICATE_TYPES_SYNC.has(type)) {
 | |
| 		getDuplicateStreamInstance({
 | |
| 			otherStdioItems,
 | |
| 			type,
 | |
| 			value,
 | |
| 			optionName,
 | |
| 			direction,
 | |
| 		});
 | |
| 	}
 | |
| };
 | |
| 
 | |
| // When two file descriptors share the file or stream, we need to re-use the same underlying stream.
 | |
| // Otherwise, the stream would be closed twice when piping ends.
 | |
| // This is only an issue with output file descriptors.
 | |
| // This is not a problem with generator functions since those create a new instance for each file descriptor.
 | |
| // We also forbid input and output file descriptors sharing the same file or stream, since that does not make sense.
 | |
| const getDuplicateStreamInstance = ({otherStdioItems, type, value, optionName, direction}) => {
 | |
| 	const duplicateStdioItems = otherStdioItems.filter(stdioItem => hasSameValue(stdioItem, value));
 | |
| 	if (duplicateStdioItems.length === 0) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	const differentStdioItem = duplicateStdioItems.find(stdioItem => stdioItem.direction !== direction);
 | |
| 	throwOnDuplicateStream(differentStdioItem, optionName, type);
 | |
| 
 | |
| 	return direction === 'output' ? duplicateStdioItems[0].stream : undefined;
 | |
| };
 | |
| 
 | |
| const hasSameValue = ({type, value}, secondValue) => {
 | |
| 	if (type === 'filePath') {
 | |
| 		return value.file === secondValue.file;
 | |
| 	}
 | |
| 
 | |
| 	if (type === 'fileUrl') {
 | |
| 		return value.href === secondValue.href;
 | |
| 	}
 | |
| 
 | |
| 	return value === secondValue;
 | |
| };
 | |
| 
 | |
| // We do not allow two file descriptors to share the same Duplex or TransformStream.
 | |
| // This is because those are set directly to `subprocess.std*`.
 | |
| // For example, this could result in `subprocess.stdout` and `subprocess.stderr` being the same value.
 | |
| // This means reading from either would get data from both stdout and stderr.
 | |
| const validateDuplicateTransform = ({otherStdioItems, type, value, optionName}) => {
 | |
| 	const duplicateStdioItem = otherStdioItems.find(({value: {transform}}) => transform === value.transform);
 | |
| 	throwOnDuplicateStream(duplicateStdioItem, optionName, type);
 | |
| };
 | |
| 
 | |
| const throwOnDuplicateStream = (stdioItem, optionName, type) => {
 | |
| 	if (stdioItem !== undefined) {
 | |
| 		throw new TypeError(`The \`${stdioItem.optionName}\` and \`${optionName}\` options must not target ${TYPE_TO_MESSAGE[type]} that is the same.`);
 | |
| 	}
 | |
| };
 |