加入了node_modules

添加了新的功能项
This commit is contained in:
zyb
2025-05-13 21:23:41 +08:00
parent 8d53374568
commit 0c0b5d869c
3589 changed files with 893641 additions and 233 deletions

4
node_modules/execa/lib/ipc/array.js generated vendored Normal file
View File

@@ -0,0 +1,4 @@
// The `ipc` option adds an `ipc` item to the `stdio` option
export const normalizeIpcStdioArray = (stdioArray, ipc) => ipc && !stdioArray.includes('ipc')
? [...stdioArray, 'ipc']
: stdioArray;

47
node_modules/execa/lib/ipc/buffer-messages.js generated vendored Normal file
View File

@@ -0,0 +1,47 @@
import {checkIpcMaxBuffer} from '../io/max-buffer.js';
import {shouldLogIpc, logIpcOutput} from '../verbose/ipc.js';
import {getFdSpecificValue} from '../arguments/specific.js';
import {loopOnMessages} from './get-each.js';
// Iterate through IPC messages sent by the subprocess
export const waitForIpcOutput = async ({
subprocess,
buffer: bufferArray,
maxBuffer: maxBufferArray,
ipc,
ipcOutput,
verboseInfo,
}) => {
if (!ipc) {
return ipcOutput;
}
const isVerbose = shouldLogIpc(verboseInfo);
const buffer = getFdSpecificValue(bufferArray, 'ipc');
const maxBuffer = getFdSpecificValue(maxBufferArray, 'ipc');
for await (const message of loopOnMessages({
anyProcess: subprocess,
channel: subprocess.channel,
isSubprocess: false,
ipc,
shouldAwait: false,
reference: true,
})) {
if (buffer) {
checkIpcMaxBuffer(subprocess, ipcOutput, maxBuffer);
ipcOutput.push(message);
}
if (isVerbose) {
logIpcOutput(message, verboseInfo);
}
}
return ipcOutput;
};
export const getBufferedIpcOutput = async (ipcOutputPromise, ipcOutput) => {
await Promise.allSettled([ipcOutputPromise]);
return ipcOutput;
};

56
node_modules/execa/lib/ipc/forward.js generated vendored Normal file
View File

@@ -0,0 +1,56 @@
import {EventEmitter} from 'node:events';
import {onMessage, onDisconnect} from './incoming.js';
import {undoAddedReferences} from './reference.js';
// Forward the `message` and `disconnect` events from the process and subprocess to a proxy emitter.
// This prevents the `error` event from stopping IPC.
// This also allows debouncing the `message` event.
export const getIpcEmitter = (anyProcess, channel, isSubprocess) => {
if (IPC_EMITTERS.has(anyProcess)) {
return IPC_EMITTERS.get(anyProcess);
}
// Use an `EventEmitter`, like the `process` that is being proxied
// eslint-disable-next-line unicorn/prefer-event-target
const ipcEmitter = new EventEmitter();
ipcEmitter.connected = true;
IPC_EMITTERS.set(anyProcess, ipcEmitter);
forwardEvents({
ipcEmitter,
anyProcess,
channel,
isSubprocess,
});
return ipcEmitter;
};
const IPC_EMITTERS = new WeakMap();
// The `message` and `disconnect` events are buffered in the subprocess until the first listener is setup.
// However, unbuffering happens after one tick, so this give enough time for the caller to setup the listener on the proxy emitter first.
// See https://github.com/nodejs/node/blob/2aaeaa863c35befa2ebaa98fb7737ec84df4d8e9/lib/internal/child_process.js#L721
const forwardEvents = ({ipcEmitter, anyProcess, channel, isSubprocess}) => {
const boundOnMessage = onMessage.bind(undefined, {
anyProcess,
channel,
isSubprocess,
ipcEmitter,
});
anyProcess.on('message', boundOnMessage);
anyProcess.once('disconnect', onDisconnect.bind(undefined, {
anyProcess,
channel,
isSubprocess,
ipcEmitter,
boundOnMessage,
}));
undoAddedReferences(channel, isSubprocess);
};
// Check whether there might still be some `message` events to receive
export const isConnected = anyProcess => {
const ipcEmitter = IPC_EMITTERS.get(anyProcess);
return ipcEmitter === undefined
? anyProcess.channel !== null
: ipcEmitter.connected;
};

89
node_modules/execa/lib/ipc/get-each.js generated vendored Normal file
View File

@@ -0,0 +1,89 @@
import {once, on} from 'node:events';
import {validateIpcMethod, disconnect, getStrictResponseError} from './validation.js';
import {getIpcEmitter, isConnected} from './forward.js';
import {addReference, removeReference} from './reference.js';
// Like `[sub]process.on('message')` but promise-based
export const getEachMessage = ({anyProcess, channel, isSubprocess, ipc}, {reference = true} = {}) => loopOnMessages({
anyProcess,
channel,
isSubprocess,
ipc,
shouldAwait: !isSubprocess,
reference,
});
// Same but used internally
export const loopOnMessages = ({anyProcess, channel, isSubprocess, ipc, shouldAwait, reference}) => {
validateIpcMethod({
methodName: 'getEachMessage',
isSubprocess,
ipc,
isConnected: isConnected(anyProcess),
});
addReference(channel, reference);
const ipcEmitter = getIpcEmitter(anyProcess, channel, isSubprocess);
const controller = new AbortController();
const state = {};
stopOnDisconnect(anyProcess, ipcEmitter, controller);
abortOnStrictError({
ipcEmitter,
isSubprocess,
controller,
state,
});
return iterateOnMessages({
anyProcess,
channel,
ipcEmitter,
isSubprocess,
shouldAwait,
controller,
state,
reference,
});
};
const stopOnDisconnect = async (anyProcess, ipcEmitter, controller) => {
try {
await once(ipcEmitter, 'disconnect', {signal: controller.signal});
controller.abort();
} catch {}
};
const abortOnStrictError = async ({ipcEmitter, isSubprocess, controller, state}) => {
try {
const [error] = await once(ipcEmitter, 'strict:error', {signal: controller.signal});
state.error = getStrictResponseError(error, isSubprocess);
controller.abort();
} catch {}
};
const iterateOnMessages = async function * ({anyProcess, channel, ipcEmitter, isSubprocess, shouldAwait, controller, state, reference}) {
try {
for await (const [message] of on(ipcEmitter, 'message', {signal: controller.signal})) {
throwIfStrictError(state);
yield message;
}
} catch {
throwIfStrictError(state);
} finally {
controller.abort();
removeReference(channel, reference);
if (!isSubprocess) {
disconnect(anyProcess);
}
if (shouldAwait) {
await anyProcess;
}
}
};
const throwIfStrictError = ({error}) => {
if (error) {
throw error;
}
};

69
node_modules/execa/lib/ipc/get-one.js generated vendored Normal file
View File

@@ -0,0 +1,69 @@
import {once, on} from 'node:events';
import {
validateIpcMethod,
throwOnEarlyDisconnect,
disconnect,
getStrictResponseError,
} from './validation.js';
import {getIpcEmitter, isConnected} from './forward.js';
import {addReference, removeReference} from './reference.js';
// Like `[sub]process.once('message')` but promise-based
export const getOneMessage = ({anyProcess, channel, isSubprocess, ipc}, {reference = true, filter} = {}) => {
validateIpcMethod({
methodName: 'getOneMessage',
isSubprocess,
ipc,
isConnected: isConnected(anyProcess),
});
return getOneMessageAsync({
anyProcess,
channel,
isSubprocess,
filter,
reference,
});
};
const getOneMessageAsync = async ({anyProcess, channel, isSubprocess, filter, reference}) => {
addReference(channel, reference);
const ipcEmitter = getIpcEmitter(anyProcess, channel, isSubprocess);
const controller = new AbortController();
try {
return await Promise.race([
getMessage(ipcEmitter, filter, controller),
throwOnDisconnect(ipcEmitter, isSubprocess, controller),
throwOnStrictError(ipcEmitter, isSubprocess, controller),
]);
} catch (error) {
disconnect(anyProcess);
throw error;
} finally {
controller.abort();
removeReference(channel, reference);
}
};
const getMessage = async (ipcEmitter, filter, {signal}) => {
if (filter === undefined) {
const [message] = await once(ipcEmitter, 'message', {signal});
return message;
}
for await (const [message] of on(ipcEmitter, 'message', {signal})) {
if (filter(message)) {
return message;
}
}
};
const throwOnDisconnect = async (ipcEmitter, isSubprocess, {signal}) => {
await once(ipcEmitter, 'disconnect', {signal});
throwOnEarlyDisconnect(isSubprocess);
};
const throwOnStrictError = async (ipcEmitter, isSubprocess, {signal}) => {
const [error] = await once(ipcEmitter, 'strict:error', {signal});
throw getStrictResponseError(error, isSubprocess);
};

72
node_modules/execa/lib/ipc/graceful.js generated vendored Normal file
View File

@@ -0,0 +1,72 @@
import {scheduler} from 'node:timers/promises';
import {sendOneMessage} from './send.js';
import {getIpcEmitter} from './forward.js';
import {validateConnection, getAbortDisconnectError, throwOnMissingParent} from './validation.js';
// Send an IPC message so the subprocess performs a graceful termination
export const sendAbort = (subprocess, message) => {
const methodName = 'cancelSignal';
validateConnection(methodName, false, subprocess.connected);
return sendOneMessage({
anyProcess: subprocess,
methodName,
isSubprocess: false,
wrappedMessage: {type: GRACEFUL_CANCEL_TYPE, message},
message,
});
};
// When the signal is being used, start listening for incoming messages.
// Unbuffering messages takes one microtask to complete, so this must be async.
export const getCancelSignal = async ({anyProcess, channel, isSubprocess, ipc}) => {
await startIpc({
anyProcess,
channel,
isSubprocess,
ipc,
});
return cancelController.signal;
};
const startIpc = async ({anyProcess, channel, isSubprocess, ipc}) => {
if (cancelListening) {
return;
}
cancelListening = true;
if (!ipc) {
throwOnMissingParent();
return;
}
if (channel === null) {
abortOnDisconnect();
return;
}
getIpcEmitter(anyProcess, channel, isSubprocess);
await scheduler.yield();
};
let cancelListening = false;
// Reception of IPC message to perform a graceful termination
export const handleAbort = wrappedMessage => {
if (wrappedMessage?.type !== GRACEFUL_CANCEL_TYPE) {
return false;
}
cancelController.abort(wrappedMessage.message);
return true;
};
const GRACEFUL_CANCEL_TYPE = 'execa:ipc:cancel';
// When the current process disconnects early, the subprocess `cancelSignal` is aborted.
// Otherwise, the signal would never be able to be aborted later on.
export const abortOnDisconnect = () => {
cancelController.abort(getAbortDisconnectError());
};
const cancelController = new AbortController();

79
node_modules/execa/lib/ipc/incoming.js generated vendored Normal file
View File

@@ -0,0 +1,79 @@
import {once} from 'node:events';
import {scheduler} from 'node:timers/promises';
import {waitForOutgoingMessages} from './outgoing.js';
import {redoAddedReferences} from './reference.js';
import {handleStrictRequest, handleStrictResponse} from './strict.js';
import {handleAbort, abortOnDisconnect} from './graceful.js';
// By default, Node.js buffers `message` events.
// - Buffering happens when there is a `message` event is emitted but there is no handler.
// - As soon as a `message` event handler is set, all buffered `message` events are emitted, emptying the buffer.
// - This happens both in the current process and the subprocess.
// - See https://github.com/nodejs/node/blob/501546e8f37059cd577041e23941b640d0d4d406/lib/internal/child_process.js#L719
// This is helpful. Notably, this allows sending messages to a subprocess that's still initializing.
// However, it has several problems.
// - This works with `events.on()` but not `events.once()` since all buffered messages are emitted at once.
// For example, users cannot call `await getOneMessage()`/`getEachMessage()` multiple times in a row.
// - When a user intentionally starts listening to `message` at a specific point in time, past `message` events are replayed, which might be unexpected.
// - Buffering is unlimited, which might lead to an out-of-memory crash.
// - This does not work well with multiple consumers.
// For example, Execa consumes events with both `result.ipcOutput` and manual IPC calls like `getOneMessage()`.
// Since `result.ipcOutput` reads all incoming messages, no buffering happens for manual IPC calls.
// - Forgetting to setup a `message` listener, or setting it up too late, is a programming mistake.
// The default behavior does not allow users to realize they made that mistake.
// To solve those problems, instead of buffering messages, we debounce them.
// The `message` event so it is emitted at most once per macrotask.
export const onMessage = async ({anyProcess, channel, isSubprocess, ipcEmitter}, wrappedMessage) => {
if (handleStrictResponse(wrappedMessage) || handleAbort(wrappedMessage)) {
return;
}
if (!INCOMING_MESSAGES.has(anyProcess)) {
INCOMING_MESSAGES.set(anyProcess, []);
}
const incomingMessages = INCOMING_MESSAGES.get(anyProcess);
incomingMessages.push(wrappedMessage);
if (incomingMessages.length > 1) {
return;
}
while (incomingMessages.length > 0) {
// eslint-disable-next-line no-await-in-loop
await waitForOutgoingMessages(anyProcess, ipcEmitter, wrappedMessage);
// eslint-disable-next-line no-await-in-loop
await scheduler.yield();
// eslint-disable-next-line no-await-in-loop
const message = await handleStrictRequest({
wrappedMessage: incomingMessages[0],
anyProcess,
channel,
isSubprocess,
ipcEmitter,
});
incomingMessages.shift();
ipcEmitter.emit('message', message);
ipcEmitter.emit('message:done');
}
};
// If the `message` event is currently debounced, the `disconnect` event must wait for it
export const onDisconnect = async ({anyProcess, channel, isSubprocess, ipcEmitter, boundOnMessage}) => {
abortOnDisconnect();
const incomingMessages = INCOMING_MESSAGES.get(anyProcess);
while (incomingMessages?.length > 0) {
// eslint-disable-next-line no-await-in-loop
await once(ipcEmitter, 'message:done');
}
anyProcess.removeListener('message', boundOnMessage);
redoAddedReferences(channel, isSubprocess);
ipcEmitter.connected = false;
ipcEmitter.emit('disconnect');
};
const INCOMING_MESSAGES = new WeakMap();

44
node_modules/execa/lib/ipc/ipc-input.js generated vendored Normal file
View File

@@ -0,0 +1,44 @@
import {serialize} from 'node:v8';
// Validate the `ipcInput` option
export const validateIpcInputOption = ({ipcInput, ipc, serialization}) => {
if (ipcInput === undefined) {
return;
}
if (!ipc) {
throw new Error('The `ipcInput` option cannot be set unless the `ipc` option is `true`.');
}
validateIpcInput[serialization](ipcInput);
};
const validateAdvancedInput = ipcInput => {
try {
serialize(ipcInput);
} catch (error) {
throw new Error('The `ipcInput` option is not serializable with a structured clone.', {cause: error});
}
};
const validateJsonInput = ipcInput => {
try {
JSON.stringify(ipcInput);
} catch (error) {
throw new Error('The `ipcInput` option is not serializable with JSON.', {cause: error});
}
};
const validateIpcInput = {
advanced: validateAdvancedInput,
json: validateJsonInput,
};
// When the `ipcInput` option is set, it is sent as an initial IPC message to the subprocess
export const sendIpcInput = async (subprocess, ipcInput) => {
if (ipcInput === undefined) {
return;
}
await subprocess.sendMessage(ipcInput);
};

49
node_modules/execa/lib/ipc/methods.js generated vendored Normal file
View File

@@ -0,0 +1,49 @@
import process from 'node:process';
import {sendMessage} from './send.js';
import {getOneMessage} from './get-one.js';
import {getEachMessage} from './get-each.js';
import {getCancelSignal} from './graceful.js';
// Add promise-based IPC methods in current process
export const addIpcMethods = (subprocess, {ipc}) => {
Object.assign(subprocess, getIpcMethods(subprocess, false, ipc));
};
// Get promise-based IPC in the subprocess
export const getIpcExport = () => {
const anyProcess = process;
const isSubprocess = true;
const ipc = process.channel !== undefined;
return {
...getIpcMethods(anyProcess, isSubprocess, ipc),
getCancelSignal: getCancelSignal.bind(undefined, {
anyProcess,
channel: anyProcess.channel,
isSubprocess,
ipc,
}),
};
};
// Retrieve the `ipc` shared by both the current process and the subprocess
const getIpcMethods = (anyProcess, isSubprocess, ipc) => ({
sendMessage: sendMessage.bind(undefined, {
anyProcess,
channel: anyProcess.channel,
isSubprocess,
ipc,
}),
getOneMessage: getOneMessage.bind(undefined, {
anyProcess,
channel: anyProcess.channel,
isSubprocess,
ipc,
}),
getEachMessage: getEachMessage.bind(undefined, {
anyProcess,
channel: anyProcess.channel,
isSubprocess,
ipc,
}),
});

47
node_modules/execa/lib/ipc/outgoing.js generated vendored Normal file
View File

@@ -0,0 +1,47 @@
import {createDeferred} from '../utils/deferred.js';
import {getFdSpecificValue} from '../arguments/specific.js';
import {SUBPROCESS_OPTIONS} from '../arguments/fd-options.js';
import {validateStrictDeadlock} from './strict.js';
// When `sendMessage()` is ongoing, any `message` being received waits before being emitted.
// This allows calling one or multiple `await sendMessage()` followed by `await getOneMessage()`/`await getEachMessage()`.
// Without running into a race condition when the other process sends a response too fast, before the current process set up a listener.
export const startSendMessage = (anyProcess, wrappedMessage, strict) => {
if (!OUTGOING_MESSAGES.has(anyProcess)) {
OUTGOING_MESSAGES.set(anyProcess, new Set());
}
const outgoingMessages = OUTGOING_MESSAGES.get(anyProcess);
const onMessageSent = createDeferred();
const id = strict ? wrappedMessage.id : undefined;
const outgoingMessage = {onMessageSent, id};
outgoingMessages.add(outgoingMessage);
return {outgoingMessages, outgoingMessage};
};
export const endSendMessage = ({outgoingMessages, outgoingMessage}) => {
outgoingMessages.delete(outgoingMessage);
outgoingMessage.onMessageSent.resolve();
};
// Await while `sendMessage()` is ongoing, unless there is already a `message` listener
export const waitForOutgoingMessages = async (anyProcess, ipcEmitter, wrappedMessage) => {
while (!hasMessageListeners(anyProcess, ipcEmitter) && OUTGOING_MESSAGES.get(anyProcess)?.size > 0) {
const outgoingMessages = [...OUTGOING_MESSAGES.get(anyProcess)];
validateStrictDeadlock(outgoingMessages, wrappedMessage);
// eslint-disable-next-line no-await-in-loop
await Promise.all(outgoingMessages.map(({onMessageSent}) => onMessageSent));
}
};
const OUTGOING_MESSAGES = new WeakMap();
// Whether any `message` listener is setup
export const hasMessageListeners = (anyProcess, ipcEmitter) => ipcEmitter.listenerCount('message') > getMinListenerCount(anyProcess);
// When `buffer` is `false`, we set up a `message` listener that should be ignored.
// That listener is only meant to intercept `strict` acknowledgement responses.
const getMinListenerCount = anyProcess => SUBPROCESS_OPTIONS.has(anyProcess)
&& !getFdSpecificValue(SUBPROCESS_OPTIONS.get(anyProcess).options.buffer, 'ipc')
? 1
: 0;

44
node_modules/execa/lib/ipc/reference.js generated vendored Normal file
View File

@@ -0,0 +1,44 @@
// By default, Node.js keeps the subprocess alive while it has a `message` or `disconnect` listener.
// We replicate the same logic for the events that we proxy.
// This ensures the subprocess is kept alive while `getOneMessage()` and `getEachMessage()` are ongoing.
// This is not a problem with `sendMessage()` since Node.js handles that method automatically.
// We do not use `anyProcess.channel.ref()` since this would prevent the automatic `.channel.refCounted()` Node.js is doing.
// We keep a reference to `anyProcess.channel` since it might be `null` while `getOneMessage()` or `getEachMessage()` is still processing debounced messages.
// See https://github.com/nodejs/node/blob/2aaeaa863c35befa2ebaa98fb7737ec84df4d8e9/lib/internal/child_process.js#L547
export const addReference = (channel, reference) => {
if (reference) {
addReferenceCount(channel);
}
};
const addReferenceCount = channel => {
channel.refCounted();
};
export const removeReference = (channel, reference) => {
if (reference) {
removeReferenceCount(channel);
}
};
const removeReferenceCount = channel => {
channel.unrefCounted();
};
// To proxy events, we setup some global listeners on the `message` and `disconnect` events.
// Those should not keep the subprocess alive, so we remove the automatic counting that Node.js is doing.
// See https://github.com/nodejs/node/blob/1b965270a9c273d4cf70e8808e9d28b9ada7844f/lib/child_process.js#L180
export const undoAddedReferences = (channel, isSubprocess) => {
if (isSubprocess) {
removeReferenceCount(channel);
removeReferenceCount(channel);
}
};
// Reverse it during `disconnect`
export const redoAddedReferences = (channel, isSubprocess) => {
if (isSubprocess) {
addReferenceCount(channel);
addReferenceCount(channel);
}
};

91
node_modules/execa/lib/ipc/send.js generated vendored Normal file
View File

@@ -0,0 +1,91 @@
import {promisify} from 'node:util';
import {
validateIpcMethod,
handleEpipeError,
handleSerializationError,
disconnect,
} from './validation.js';
import {startSendMessage, endSendMessage} from './outgoing.js';
import {handleSendStrict, waitForStrictResponse} from './strict.js';
// Like `[sub]process.send()` but promise-based.
// We do not `await subprocess` during `.sendMessage()` nor `.getOneMessage()` since those methods are transient.
// Users would still need to `await subprocess` after the method is done.
// Also, this would prevent `unhandledRejection` event from being emitted, making it silent.
export const sendMessage = ({anyProcess, channel, isSubprocess, ipc}, message, {strict = false} = {}) => {
const methodName = 'sendMessage';
validateIpcMethod({
methodName,
isSubprocess,
ipc,
isConnected: anyProcess.connected,
});
return sendMessageAsync({
anyProcess,
channel,
methodName,
isSubprocess,
message,
strict,
});
};
const sendMessageAsync = async ({anyProcess, channel, methodName, isSubprocess, message, strict}) => {
const wrappedMessage = handleSendStrict({
anyProcess,
channel,
isSubprocess,
message,
strict,
});
const outgoingMessagesState = startSendMessage(anyProcess, wrappedMessage, strict);
try {
await sendOneMessage({
anyProcess,
methodName,
isSubprocess,
wrappedMessage,
message,
});
} catch (error) {
disconnect(anyProcess);
throw error;
} finally {
endSendMessage(outgoingMessagesState);
}
};
// Used internally by `cancelSignal`
export const sendOneMessage = async ({anyProcess, methodName, isSubprocess, wrappedMessage, message}) => {
const sendMethod = getSendMethod(anyProcess);
try {
await Promise.all([
waitForStrictResponse(wrappedMessage, anyProcess, isSubprocess),
sendMethod(wrappedMessage),
]);
} catch (error) {
handleEpipeError({error, methodName, isSubprocess});
handleSerializationError({
error,
methodName,
isSubprocess,
message,
});
throw error;
}
};
// [sub]process.send() promisified, memoized
const getSendMethod = anyProcess => {
if (PROCESS_SEND_METHODS.has(anyProcess)) {
return PROCESS_SEND_METHODS.get(anyProcess);
}
const sendMethod = promisify(anyProcess.send.bind(anyProcess));
PROCESS_SEND_METHODS.set(anyProcess, sendMethod);
return sendMethod;
};
const PROCESS_SEND_METHODS = new WeakMap();

113
node_modules/execa/lib/ipc/strict.js generated vendored Normal file
View File

@@ -0,0 +1,113 @@
import {once} from 'node:events';
import {createDeferred} from '../utils/deferred.js';
import {incrementMaxListeners} from '../utils/max-listeners.js';
import {sendMessage} from './send.js';
import {throwOnMissingStrict, throwOnStrictDisconnect, throwOnStrictDeadlockError} from './validation.js';
import {getIpcEmitter} from './forward.js';
import {hasMessageListeners} from './outgoing.js';
// When using the `strict` option, wrap the message with metadata during `sendMessage()`
export const handleSendStrict = ({anyProcess, channel, isSubprocess, message, strict}) => {
if (!strict) {
return message;
}
const ipcEmitter = getIpcEmitter(anyProcess, channel, isSubprocess);
const hasListeners = hasMessageListeners(anyProcess, ipcEmitter);
return {
id: count++,
type: REQUEST_TYPE,
message,
hasListeners,
};
};
let count = 0n;
// Handles when both processes are calling `sendMessage()` with `strict` at the same time.
// If neither process is listening, this would create a deadlock. We detect it and throw.
export const validateStrictDeadlock = (outgoingMessages, wrappedMessage) => {
if (wrappedMessage?.type !== REQUEST_TYPE || wrappedMessage.hasListeners) {
return;
}
for (const {id} of outgoingMessages) {
if (id !== undefined) {
STRICT_RESPONSES[id].resolve({isDeadlock: true, hasListeners: false});
}
}
};
// The other process then sends the acknowledgment back as a response
export const handleStrictRequest = async ({wrappedMessage, anyProcess, channel, isSubprocess, ipcEmitter}) => {
if (wrappedMessage?.type !== REQUEST_TYPE || !anyProcess.connected) {
return wrappedMessage;
}
const {id, message} = wrappedMessage;
const response = {id, type: RESPONSE_TYPE, message: hasMessageListeners(anyProcess, ipcEmitter)};
try {
await sendMessage({
anyProcess,
channel,
isSubprocess,
ipc: true,
}, response);
} catch (error) {
ipcEmitter.emit('strict:error', error);
}
return message;
};
// Reception of the acknowledgment response
export const handleStrictResponse = wrappedMessage => {
if (wrappedMessage?.type !== RESPONSE_TYPE) {
return false;
}
const {id, message: hasListeners} = wrappedMessage;
STRICT_RESPONSES[id]?.resolve({isDeadlock: false, hasListeners});
return true;
};
// Wait for the other process to receive the message from `sendMessage()`
export const waitForStrictResponse = async (wrappedMessage, anyProcess, isSubprocess) => {
if (wrappedMessage?.type !== REQUEST_TYPE) {
return;
}
const deferred = createDeferred();
STRICT_RESPONSES[wrappedMessage.id] = deferred;
const controller = new AbortController();
try {
const {isDeadlock, hasListeners} = await Promise.race([
deferred,
throwOnDisconnect(anyProcess, isSubprocess, controller),
]);
if (isDeadlock) {
throwOnStrictDeadlockError(isSubprocess);
}
if (!hasListeners) {
throwOnMissingStrict(isSubprocess);
}
} finally {
controller.abort();
delete STRICT_RESPONSES[wrappedMessage.id];
}
};
const STRICT_RESPONSES = {};
const throwOnDisconnect = async (anyProcess, isSubprocess, {signal}) => {
incrementMaxListeners(anyProcess, 1, signal);
await once(anyProcess, 'disconnect', {signal});
throwOnStrictDisconnect(isSubprocess);
};
const REQUEST_TYPE = 'execa:ipc:request';
const RESPONSE_TYPE = 'execa:ipc:response';

111
node_modules/execa/lib/ipc/validation.js generated vendored Normal file
View File

@@ -0,0 +1,111 @@
// Validate the IPC channel is connected before receiving/sending messages
export const validateIpcMethod = ({methodName, isSubprocess, ipc, isConnected}) => {
validateIpcOption(methodName, isSubprocess, ipc);
validateConnection(methodName, isSubprocess, isConnected);
};
// Better error message when forgetting to set `ipc: true` and using the IPC methods
const validateIpcOption = (methodName, isSubprocess, ipc) => {
if (!ipc) {
throw new Error(`${getMethodName(methodName, isSubprocess)} can only be used if the \`ipc\` option is \`true\`.`);
}
};
// Better error message when one process does not send/receive messages once the other process has disconnected.
// This also makes it clear that any buffered messages are lost once either process has disconnected.
// Also when aborting `cancelSignal` after disconnecting the IPC.
export const validateConnection = (methodName, isSubprocess, isConnected) => {
if (!isConnected) {
throw new Error(`${getMethodName(methodName, isSubprocess)} cannot be used: the ${getOtherProcessName(isSubprocess)} has already exited or disconnected.`);
}
};
// When `getOneMessage()` could not complete due to an early disconnection
export const throwOnEarlyDisconnect = isSubprocess => {
throw new Error(`${getMethodName('getOneMessage', isSubprocess)} could not complete: the ${getOtherProcessName(isSubprocess)} exited or disconnected.`);
};
// When both processes use `sendMessage()` with `strict` at the same time
export const throwOnStrictDeadlockError = isSubprocess => {
throw new Error(`${getMethodName('sendMessage', isSubprocess)} failed: the ${getOtherProcessName(isSubprocess)} is sending a message too, instead of listening to incoming messages.
This can be fixed by both sending a message and listening to incoming messages at the same time:
const [receivedMessage] = await Promise.all([
${getMethodName('getOneMessage', isSubprocess)},
${getMethodName('sendMessage', isSubprocess, 'message, {strict: true}')},
]);`);
};
// When the other process used `strict` but the current process had I/O error calling `sendMessage()` for the response
export const getStrictResponseError = (error, isSubprocess) => new Error(`${getMethodName('sendMessage', isSubprocess)} failed when sending an acknowledgment response to the ${getOtherProcessName(isSubprocess)}.`, {cause: error});
// When using `strict` but the other process was not listening for messages
export const throwOnMissingStrict = isSubprocess => {
throw new Error(`${getMethodName('sendMessage', isSubprocess)} failed: the ${getOtherProcessName(isSubprocess)} is not listening to incoming messages.`);
};
// When using `strict` but the other process disconnected before receiving the message
export const throwOnStrictDisconnect = isSubprocess => {
throw new Error(`${getMethodName('sendMessage', isSubprocess)} failed: the ${getOtherProcessName(isSubprocess)} exited without listening to incoming messages.`);
};
// When the current process disconnects while the subprocess is listening to `cancelSignal`
export const getAbortDisconnectError = () => new Error(`\`cancelSignal\` aborted: the ${getOtherProcessName(true)} disconnected.`);
// When the subprocess uses `cancelSignal` but not the current process
export const throwOnMissingParent = () => {
throw new Error('`getCancelSignal()` cannot be used without setting the `cancelSignal` subprocess option.');
};
// EPIPE can happen when sending a message to a subprocess that is closing but has not disconnected yet
export const handleEpipeError = ({error, methodName, isSubprocess}) => {
if (error.code === 'EPIPE') {
throw new Error(`${getMethodName(methodName, isSubprocess)} cannot be used: the ${getOtherProcessName(isSubprocess)} is disconnecting.`, {cause: error});
}
};
// Better error message when sending messages which cannot be serialized.
// Works with both `serialization: 'advanced'` and `serialization: 'json'`.
export const handleSerializationError = ({error, methodName, isSubprocess, message}) => {
if (isSerializationError(error)) {
throw new Error(`${getMethodName(methodName, isSubprocess)}'s argument type is invalid: the message cannot be serialized: ${String(message)}.`, {cause: error});
}
};
const isSerializationError = ({code, message}) => SERIALIZATION_ERROR_CODES.has(code)
|| SERIALIZATION_ERROR_MESSAGES.some(serializationErrorMessage => message.includes(serializationErrorMessage));
// `error.code` set by Node.js when it failed to serialize the message
const SERIALIZATION_ERROR_CODES = new Set([
// Message is `undefined`
'ERR_MISSING_ARGS',
// Message is a function, a bigint, a symbol
'ERR_INVALID_ARG_TYPE',
]);
// `error.message` set by Node.js when it failed to serialize the message
const SERIALIZATION_ERROR_MESSAGES = [
// Message is a promise or a proxy, with `serialization: 'advanced'`
'could not be cloned',
// Message has cycles, with `serialization: 'json'`
'circular structure',
// Message has cycles inside toJSON(), with `serialization: 'json'`
'call stack size exceeded',
];
const getMethodName = (methodName, isSubprocess, parameters = '') => methodName === 'cancelSignal'
? '`cancelSignal`\'s `controller.abort()`'
: `${getNamespaceName(isSubprocess)}${methodName}(${parameters})`;
const getNamespaceName = isSubprocess => isSubprocess ? '' : 'subprocess.';
const getOtherProcessName = isSubprocess => isSubprocess ? 'parent process' : 'subprocess';
// When any error arises, we disconnect the IPC.
// Otherwise, it is likely that one of the processes will stop sending/receiving messages.
// This would leave the other process hanging.
export const disconnect = anyProcess => {
if (anyProcess.connected) {
anyProcess.disconnect();
}
};