Initial commit
This commit is contained in:
214
node_modules/execa/lib/stdio/handle.js
generated
vendored
Normal file
214
node_modules/execa/lib/stdio/handle.js
generated
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
import {getStreamName, isStandardStream} from '../utils/standard-stream.js';
|
||||
import {normalizeTransforms} from '../transform/normalize.js';
|
||||
import {getFdObjectMode} from '../transform/object-mode.js';
|
||||
import {
|
||||
getStdioItemType,
|
||||
isRegularUrl,
|
||||
isUnknownStdioString,
|
||||
FILE_TYPES,
|
||||
} from './type.js';
|
||||
import {getStreamDirection} from './direction.js';
|
||||
import {normalizeStdioOption} from './stdio-option.js';
|
||||
import {handleNativeStream} from './native.js';
|
||||
import {handleInputOptions} from './input-option.js';
|
||||
import {filterDuplicates, getDuplicateStream} from './duplicate.js';
|
||||
|
||||
// Handle `input`, `inputFile`, `stdin`, `stdout` and `stderr` options, before spawning, in async/sync mode
|
||||
// They are converted into an array of `fileDescriptors`.
|
||||
// Each `fileDescriptor` is normalized, validated and contains all information necessary for further handling.
|
||||
export const handleStdio = (addProperties, options, verboseInfo, isSync) => {
|
||||
const stdio = normalizeStdioOption(options, verboseInfo, isSync);
|
||||
const initialFileDescriptors = stdio.map((stdioOption, fdNumber) => getFileDescriptor({
|
||||
stdioOption,
|
||||
fdNumber,
|
||||
options,
|
||||
isSync,
|
||||
}));
|
||||
const fileDescriptors = getFinalFileDescriptors({
|
||||
initialFileDescriptors,
|
||||
addProperties,
|
||||
options,
|
||||
isSync,
|
||||
});
|
||||
options.stdio = fileDescriptors.map(({stdioItems}) => forwardStdio(stdioItems));
|
||||
return fileDescriptors;
|
||||
};
|
||||
|
||||
const getFileDescriptor = ({stdioOption, fdNumber, options, isSync}) => {
|
||||
const optionName = getStreamName(fdNumber);
|
||||
const {stdioItems: initialStdioItems, isStdioArray} = initializeStdioItems({
|
||||
stdioOption,
|
||||
fdNumber,
|
||||
options,
|
||||
optionName,
|
||||
});
|
||||
const direction = getStreamDirection(initialStdioItems, fdNumber, optionName);
|
||||
const stdioItems = initialStdioItems.map(stdioItem => handleNativeStream({
|
||||
stdioItem,
|
||||
isStdioArray,
|
||||
fdNumber,
|
||||
direction,
|
||||
isSync,
|
||||
}));
|
||||
const normalizedStdioItems = normalizeTransforms(stdioItems, optionName, direction, options);
|
||||
const objectMode = getFdObjectMode(normalizedStdioItems, direction);
|
||||
validateFileObjectMode(normalizedStdioItems, objectMode);
|
||||
return {direction, objectMode, stdioItems: normalizedStdioItems};
|
||||
};
|
||||
|
||||
// We make sure passing an array with a single item behaves the same as passing that item without an array.
|
||||
// This is what users would expect.
|
||||
// For example, `stdout: ['ignore']` behaves the same as `stdout: 'ignore'`.
|
||||
const initializeStdioItems = ({stdioOption, fdNumber, options, optionName}) => {
|
||||
const values = Array.isArray(stdioOption) ? stdioOption : [stdioOption];
|
||||
const initialStdioItems = [
|
||||
...values.map(value => initializeStdioItem(value, optionName)),
|
||||
...handleInputOptions(options, fdNumber),
|
||||
];
|
||||
|
||||
const stdioItems = filterDuplicates(initialStdioItems);
|
||||
const isStdioArray = stdioItems.length > 1;
|
||||
validateStdioArray(stdioItems, isStdioArray, optionName);
|
||||
validateStreams(stdioItems);
|
||||
return {stdioItems, isStdioArray};
|
||||
};
|
||||
|
||||
const initializeStdioItem = (value, optionName) => ({
|
||||
type: getStdioItemType(value, optionName),
|
||||
value,
|
||||
optionName,
|
||||
});
|
||||
|
||||
const validateStdioArray = (stdioItems, isStdioArray, optionName) => {
|
||||
if (stdioItems.length === 0) {
|
||||
throw new TypeError(`The \`${optionName}\` option must not be an empty array.`);
|
||||
}
|
||||
|
||||
if (!isStdioArray) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const {value, optionName} of stdioItems) {
|
||||
if (INVALID_STDIO_ARRAY_OPTIONS.has(value)) {
|
||||
throw new Error(`The \`${optionName}\` option must not include \`${value}\`.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Using those `stdio` values together with others for the same stream does not make sense, so we make it fail.
|
||||
// However, we do allow it if the array has a single item.
|
||||
const INVALID_STDIO_ARRAY_OPTIONS = new Set(['ignore', 'ipc']);
|
||||
|
||||
const validateStreams = stdioItems => {
|
||||
for (const stdioItem of stdioItems) {
|
||||
validateFileStdio(stdioItem);
|
||||
}
|
||||
};
|
||||
|
||||
const validateFileStdio = ({type, value, optionName}) => {
|
||||
if (isRegularUrl(value)) {
|
||||
throw new TypeError(`The \`${optionName}: URL\` option must use the \`file:\` scheme.
|
||||
For example, you can use the \`pathToFileURL()\` method of the \`url\` core module.`);
|
||||
}
|
||||
|
||||
if (isUnknownStdioString(type, value)) {
|
||||
throw new TypeError(`The \`${optionName}: { file: '...' }\` option must be used instead of \`${optionName}: '...'\`.`);
|
||||
}
|
||||
};
|
||||
|
||||
const validateFileObjectMode = (stdioItems, objectMode) => {
|
||||
if (!objectMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileStdioItem = stdioItems.find(({type}) => FILE_TYPES.has(type));
|
||||
if (fileStdioItem !== undefined) {
|
||||
throw new TypeError(`The \`${fileStdioItem.optionName}\` option cannot use both files and transforms in objectMode.`);
|
||||
}
|
||||
};
|
||||
|
||||
// Some `stdio` values require Execa to create streams.
|
||||
// For example, file paths create file read/write streams.
|
||||
// Those transformations are specified in `addProperties`, which is both direction-specific and type-specific.
|
||||
const getFinalFileDescriptors = ({initialFileDescriptors, addProperties, options, isSync}) => {
|
||||
const fileDescriptors = [];
|
||||
|
||||
try {
|
||||
for (const fileDescriptor of initialFileDescriptors) {
|
||||
fileDescriptors.push(getFinalFileDescriptor({
|
||||
fileDescriptor,
|
||||
fileDescriptors,
|
||||
addProperties,
|
||||
options,
|
||||
isSync,
|
||||
}));
|
||||
}
|
||||
|
||||
return fileDescriptors;
|
||||
} catch (error) {
|
||||
cleanupCustomStreams(fileDescriptors);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const getFinalFileDescriptor = ({
|
||||
fileDescriptor: {direction, objectMode, stdioItems},
|
||||
fileDescriptors,
|
||||
addProperties,
|
||||
options,
|
||||
isSync,
|
||||
}) => {
|
||||
const finalStdioItems = stdioItems.map(stdioItem => addStreamProperties({
|
||||
stdioItem,
|
||||
addProperties,
|
||||
direction,
|
||||
options,
|
||||
fileDescriptors,
|
||||
isSync,
|
||||
}));
|
||||
return {direction, objectMode, stdioItems: finalStdioItems};
|
||||
};
|
||||
|
||||
const addStreamProperties = ({stdioItem, addProperties, direction, options, fileDescriptors, isSync}) => {
|
||||
const duplicateStream = getDuplicateStream({
|
||||
stdioItem,
|
||||
direction,
|
||||
fileDescriptors,
|
||||
isSync,
|
||||
});
|
||||
|
||||
if (duplicateStream !== undefined) {
|
||||
return {...stdioItem, stream: duplicateStream};
|
||||
}
|
||||
|
||||
return {
|
||||
...stdioItem,
|
||||
...addProperties[direction][stdioItem.type](stdioItem, options),
|
||||
};
|
||||
};
|
||||
|
||||
// The stream error handling is performed by the piping logic above, which cannot be performed before subprocess spawning.
|
||||
// If the subprocess spawning fails (e.g. due to an invalid command), the streams need to be manually destroyed.
|
||||
// We need to create those streams before subprocess spawning, in case their creation fails, e.g. when passing an invalid generator as argument.
|
||||
// Like this, an exception would be thrown, which would prevent spawning a subprocess.
|
||||
export const cleanupCustomStreams = fileDescriptors => {
|
||||
for (const {stdioItems} of fileDescriptors) {
|
||||
for (const {stream} of stdioItems) {
|
||||
if (stream !== undefined && !isStandardStream(stream)) {
|
||||
stream.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// When the `std*: Iterable | WebStream | URL | filePath`, `input` or `inputFile` option is used, we pipe to `subprocess.std*`.
|
||||
// When the `std*: Array` option is used, we emulate some of the native values ('inherit', Node.js stream and file descriptor integer). To do so, we also need to pipe to `subprocess.std*`.
|
||||
// Therefore the `std*` options must be either `pipe` or `overlapped`. Other values do not set `subprocess.std*`.
|
||||
const forwardStdio = stdioItems => {
|
||||
if (stdioItems.length > 1) {
|
||||
return stdioItems.some(({value}) => value === 'overlapped') ? 'overlapped' : 'pipe';
|
||||
}
|
||||
|
||||
const [{type, value}] = stdioItems;
|
||||
return type === 'native' ? value : 'pipe';
|
||||
};
|
||||
Reference in New Issue
Block a user