删除 node_modules
This commit is contained in:
4
node_modules/execa/lib/ipc/array.js
generated
vendored
4
node_modules/execa/lib/ipc/array.js
generated
vendored
@@ -1,4 +0,0 @@
|
||||
// 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
47
node_modules/execa/lib/ipc/buffer-messages.js
generated
vendored
@@ -1,47 +0,0 @@
|
||||
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
56
node_modules/execa/lib/ipc/forward.js
generated
vendored
@@ -1,56 +0,0 @@
|
||||
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
89
node_modules/execa/lib/ipc/get-each.js
generated
vendored
@@ -1,89 +0,0 @@
|
||||
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
69
node_modules/execa/lib/ipc/get-one.js
generated
vendored
@@ -1,69 +0,0 @@
|
||||
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
72
node_modules/execa/lib/ipc/graceful.js
generated
vendored
@@ -1,72 +0,0 @@
|
||||
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
79
node_modules/execa/lib/ipc/incoming.js
generated
vendored
@@ -1,79 +0,0 @@
|
||||
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
44
node_modules/execa/lib/ipc/ipc-input.js
generated
vendored
@@ -1,44 +0,0 @@
|
||||
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
49
node_modules/execa/lib/ipc/methods.js
generated
vendored
@@ -1,49 +0,0 @@
|
||||
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
47
node_modules/execa/lib/ipc/outgoing.js
generated
vendored
@@ -1,47 +0,0 @@
|
||||
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
44
node_modules/execa/lib/ipc/reference.js
generated
vendored
@@ -1,44 +0,0 @@
|
||||
// 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
91
node_modules/execa/lib/ipc/send.js
generated
vendored
@@ -1,91 +0,0 @@
|
||||
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
113
node_modules/execa/lib/ipc/strict.js
generated
vendored
@@ -1,113 +0,0 @@
|
||||
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
111
node_modules/execa/lib/ipc/validation.js
generated
vendored
@@ -1,111 +0,0 @@
|
||||
// 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();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user