@volter/tabnode 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__sw__.js +259 -5
- package/dist/assets/{runtime-worker-DoV8lqBn.js → runtime-worker-B9buriZs.js} +4021 -2961
- package/dist/fetch-transport.d.ts +20 -0
- package/dist/guest-timers.d.ts +15 -0
- package/dist/http-client-transport.d.ts +19 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.mjs +5204 -3057
- package/dist/native-stream-binding.d.ts +57 -0
- package/dist/native-stream-owner.d.ts +121 -0
- package/dist/node-lib/binding/duplex_stream.d.ts +26 -0
- package/dist/node-lib/binding/fds.d.ts +8 -0
- package/dist/node-lib/binding/fs.d.ts +311 -78
- package/dist/node-lib/binding/misc.d.ts +1 -0
- package/dist/node-lib/binding/pipe_wrap.d.ts +4 -4
- package/dist/node-lib/binding/process_wrap.d.ts +13 -2
- package/dist/node-lib/binding/stream_wrap.d.ts +27 -18
- package/dist/node-lib/binding/tcp_wrap.d.ts +0 -1
- package/dist/node-lib/binding/tty_wrap.d.ts +43 -2
- package/dist/node-lib/binding/util.d.ts +83 -0
- package/dist/node-lib/binding/uv.d.ts +4 -0
- package/dist/node-lib/fs-module.d.ts +1 -0
- package/dist/node-lib/internals/buffer-and-streams.d.ts +30 -17
- package/dist/node-lib/internals/index.d.ts +1 -1
- package/dist/node-lib/load.d.ts +17 -3
- package/dist/node-lib/module-hooks.d.ts +1 -0
- package/dist/node-lib/public-modules.d.ts +1 -1
- package/dist/node-lib/timers.d.ts +38 -0
- package/dist/node-process-host.d.ts +41 -0
- package/dist/process-registry.d.ts +39 -0
- package/dist/process-tokens.d.ts +16 -0
- package/dist/promise-ownership.d.ts +3 -0
- package/dist/runtime.d.ts +10 -9
- package/dist/shims/child_process.d.ts +2 -0
- package/dist/shims/crypto.d.ts +660 -119
- package/dist/shims/dns.d.ts +76 -2
- package/dist/shims/process.d.ts +1 -0
- package/dist/shims/string_decoder.d.ts +19 -3
- package/dist/shims/tls.d.ts +853 -31
- package/dist/source-edits.d.ts +7 -0
- package/package.json +1 -1
- package/src/code-transforms.ts +2 -3
- package/src/fetch-transport.ts +98 -0
- package/src/guest-timers.ts +89 -0
- package/src/http-client-transport.ts +169 -0
- package/src/index.ts +12 -0
- package/src/native-stream-binding.ts +393 -0
- package/src/native-stream-owner.ts +317 -0
- package/src/node-lib/binding/duplex_stream.ts +95 -0
- package/src/node-lib/binding/fds.ts +18 -1
- package/src/node-lib/binding/fs.ts +45 -17
- package/src/node-lib/binding/misc.ts +2 -0
- package/src/node-lib/binding/pipe_wrap.ts +35 -12
- package/src/node-lib/binding/process_wrap.ts +72 -18
- package/src/node-lib/binding/stream_wrap.ts +196 -72
- package/src/node-lib/binding/tcp_wrap.ts +43 -8
- package/src/node-lib/binding/tty_wrap.ts +135 -8
- package/src/node-lib/binding/util.ts +24 -15
- package/src/node-lib/binding/uv.ts +4 -0
- package/src/node-lib/fs-module.ts +2 -18
- package/src/node-lib/internal/navigator.js +149 -0
- package/src/node-lib/internals/buffer-and-streams.ts +50 -11
- package/src/node-lib/internals/events-util.ts +5 -0
- package/src/node-lib/internals/index.ts +12 -2
- package/src/node-lib/load.ts +170 -62
- package/src/node-lib/module-hooks.ts +5 -2
- package/src/node-lib/public-modules.ts +12 -48
- package/src/node-lib/sources.ts +4 -0
- package/src/node-lib/timers.ts +93 -0
- package/src/node-process-host.ts +124 -0
- package/src/process-registry.ts +144 -0
- package/src/process-tokens.ts +60 -6
- package/src/promise-ownership.ts +28 -0
- package/src/runtime.ts +90 -214
- package/src/shims/child_process.ts +313 -80
- package/src/shims/crypto.ts +115 -100
- package/src/shims/dns.ts +17 -1
- package/src/shims/process.ts +33 -11
- package/src/shims/string_decoder.ts +10 -6
- package/src/shims/tls.ts +49 -19
- package/src/source-edits.ts +20 -0
|
@@ -33,6 +33,7 @@ if (typeof globalThis.process === 'undefined') {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
import { forGuestRealm, heldWork } from '../host-globals';
|
|
36
|
+
import { promiseOwner } from '../promise-ownership';
|
|
36
37
|
import { Bash, defineCommand } from 'just-bash';
|
|
37
38
|
import type { CommandContext, ExecResult as JustBashExecResult } from 'just-bash';
|
|
38
39
|
import { EventEmitter } from '../node-lib/events-module';
|
|
@@ -43,12 +44,18 @@ import { VirtualFSAdapter } from './vfs-adapter';
|
|
|
43
44
|
import { __releaseOwnedServers, __ownedServerPorts } from '../node-lib/net-module';
|
|
44
45
|
import { __ownedHandleCount, __releaseOwnedHandles } from '../node-lib/net-module';
|
|
45
46
|
import { setProcessRunner, type RunRequest, type StartedRun } from '../node-lib/binding/process_wrap';
|
|
47
|
+
import { registerRunFd, releaseRunFds, inheritedRunFds } from '../node-lib/binding/fds';
|
|
48
|
+
import { nodeProcessHostFor, nodeProcessHostInstalled, nodeProcessRealmToken, nodeProcessInput, type NodeProcessHost, type NodeProcessLaunch } from '../node-process-host';
|
|
49
|
+
import { nativeStreamDescriptor } from '../native-stream-binding';
|
|
46
50
|
import { Pipe, constants as pipeConstants } from '../node-lib/binding/pipe_wrap';
|
|
47
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
WriteWrap, ShutdownWrap, streamBaseState, kReadBytesOrError, kArrayBufferOffset,
|
|
53
|
+
type LibuvStreamWrap,
|
|
54
|
+
} from '../node-lib/binding/stream_wrap';
|
|
48
55
|
import { UV_ESRCH } from '../node-lib/binding/uv';
|
|
49
|
-
import {
|
|
56
|
+
import { loadNodeLibFor } from '../node-lib/load';
|
|
57
|
+
import type { ChildProcessModule } from '../node-lib/child-process-module';
|
|
50
58
|
import { getCommandNames } from 'just-bash';
|
|
51
|
-
import { diagnosticsChannelModule } from '../node-lib/small-modules';
|
|
52
59
|
import { __substrateExecPath, __substrateProgramName, __substrateLineFor, __substrateShellLine, setProgramResolver } from './command-line';
|
|
53
60
|
|
|
54
61
|
import { __substrateChildren, __onUncaughtException, __reportUncaughtException } from './process';
|
|
@@ -65,6 +72,34 @@ import { resolve as __resolvePath } from './path';
|
|
|
65
72
|
import { setSyncChildVfs, warmSyncChild } from './sync-child';
|
|
66
73
|
import type { PackageJson } from '../types/package-json';
|
|
67
74
|
|
|
75
|
+
// Capture host capabilities before the guest's authority is restricted. These
|
|
76
|
+
// endpoints are private, carry no guest bytes, and close at each checkpoint.
|
|
77
|
+
const HostBroadcastChannel = globalThis.BroadcastChannel;
|
|
78
|
+
const hostChannelName = globalThis.crypto?.randomUUID.bind(globalThis.crypto);
|
|
79
|
+
const isWorkerRealm = typeof (globalThis as Record<string, unknown>).WorkerGlobalScope !== 'undefined';
|
|
80
|
+
|
|
81
|
+
async function settlePrintedEntry(): Promise<void> {
|
|
82
|
+
await new Promise<void>(resolve => (globalThis.__browserRuntimeNativeSetTimeout ?? setTimeout).call(globalThis, resolve, 0));
|
|
83
|
+
if (!isWorkerRealm || nodeProcessRealmToken() === null) return;
|
|
84
|
+
if (!HostBroadcastChannel || !hostChannelName) throw new Error('Native process rejection checkpoint is unavailable.');
|
|
85
|
+
// HTML queues rejection reporting after the microtask checkpoint on the
|
|
86
|
+
// DOM manipulation task source. A timer alone can overtake that report.
|
|
87
|
+
// BroadcastChannel uses the same source, ordering this acknowledgment
|
|
88
|
+
// behind reports already queued before the timer turn above.
|
|
89
|
+
const name = hostChannelName();
|
|
90
|
+
const receiver = new HostBroadcastChannel(name);
|
|
91
|
+
try {
|
|
92
|
+
const sender = new HostBroadcastChannel(name);
|
|
93
|
+
try {
|
|
94
|
+
await new Promise<void>((resolve, reject) => {
|
|
95
|
+
receiver.onmessage = () => resolve();
|
|
96
|
+
receiver.onmessageerror = () => reject(new Error('Native process rejection checkpoint failed.'));
|
|
97
|
+
sender.postMessage(null);
|
|
98
|
+
});
|
|
99
|
+
} finally { sender.close(); }
|
|
100
|
+
} finally { receiver.close(); }
|
|
101
|
+
}
|
|
102
|
+
|
|
68
103
|
// Singleton bash instance - uses VFS adapter for two-way file sync
|
|
69
104
|
let bashInstance: Bash | null = null;
|
|
70
105
|
let vfsAdapter: VirtualFSAdapter | null = null;
|
|
@@ -114,7 +149,12 @@ Object.defineProperty = function(target: object, key: PropertyKey, descriptor: P
|
|
|
114
149
|
} as typeof Object.defineProperty;
|
|
115
150
|
|
|
116
151
|
/** The stdin of a held run's guest, as the run's own. */
|
|
117
|
-
type RunStdin = {
|
|
152
|
+
type RunStdin = {
|
|
153
|
+
emit: (event: string, ...args: unknown[]) => void;
|
|
154
|
+
push: (chunk: string | Uint8Array | null) => boolean;
|
|
155
|
+
/** A hosted sink acknowledges consumption before another pipe read. */
|
|
156
|
+
writeAsync?: (chunk: string | Uint8Array | null) => Promise<void>;
|
|
157
|
+
};
|
|
118
158
|
|
|
119
159
|
/**
|
|
120
160
|
* What a host gave one run: where its output goes, the handle that aborts it,
|
|
@@ -156,6 +196,29 @@ export interface RunStreams {
|
|
|
156
196
|
*/
|
|
157
197
|
const _runStreams = new Map<ProcessToken, RunStreams>();
|
|
158
198
|
|
|
199
|
+
/** Submit before returning to a spawning parent; completion remains async. */
|
|
200
|
+
async function runHostedNode(host: NodeProcessHost, launch: Omit<NodeProcessLaunch, 'identity' | 'inherited' | 'stdinStream'>): Promise<CommandOutcome> {
|
|
201
|
+
const { token, streams } = launch;
|
|
202
|
+
if (!runPid(token)) setRunPid(token, mintPid(), 0);
|
|
203
|
+
let input: ReturnType<typeof nodeProcessInput> | undefined;
|
|
204
|
+
try {
|
|
205
|
+
streams?.signal?.throwIfAborted();
|
|
206
|
+
const inherited = inheritedRunFds(token).map(({ fd, handle }) => {
|
|
207
|
+
const descriptor = nativeStreamDescriptor(handle as LibuvStreamWrap);
|
|
208
|
+
if (!descriptor) throw new Error(`Node descriptor ${fd} has no native owner for process isolation.`);
|
|
209
|
+
return { fd, handle: descriptor };
|
|
210
|
+
});
|
|
211
|
+
input = nodeProcessInput(streams);
|
|
212
|
+
return await host.run({ ...launch, identity: { ...runPid(token)! }, inherited,
|
|
213
|
+
...(input.stream ? { stdinStream: input.stream } : {}) });
|
|
214
|
+
} finally {
|
|
215
|
+
if (streams) streams.stdin = null;
|
|
216
|
+
input?.dispose();
|
|
217
|
+
// Adoption moves owner registration; only the source-local lookup remains.
|
|
218
|
+
forgetRunPid(token);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
159
222
|
/**
|
|
160
223
|
* One line on the host realm's console when a run the tab owns dies and
|
|
161
224
|
* nobody is reading its stderr.
|
|
@@ -274,7 +337,7 @@ forGuestRealm(() => {
|
|
|
274
337
|
if (typeof globalThis.addEventListener !== 'function') return;
|
|
275
338
|
__backstopInstalled = true;
|
|
276
339
|
globalThis.addEventListener('error', (event: Event) => {
|
|
277
|
-
const token = __currentProcessToken() ?? __lastLaunchedToken;
|
|
340
|
+
const token = nodeProcessRealmToken() ?? __currentProcessToken() ?? __lastLaunchedToken;
|
|
278
341
|
if (token === null) return;
|
|
279
342
|
const raised = event as ErrorEvent;
|
|
280
343
|
const error = raised.error ?? new Error(raised.message || 'uncaught exception');
|
|
@@ -333,16 +396,25 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
333
396
|
* of the server's became `TypeError: Cannot read properties of undefined
|
|
334
397
|
* (reading 'catch')` inside a timer, 59 of them in one boot.
|
|
335
398
|
*/
|
|
336
|
-
const __listenForUnhandledRejections = (report: (reason: unknown, promise?: Promise<unknown>) => void): (() => void) => {
|
|
399
|
+
const __listenForUnhandledRejections = (owner: object, report: (reason: unknown, promise?: Promise<unknown>) => void, ownsRealm: boolean): (() => void) => {
|
|
337
400
|
if (typeof globalThis.addEventListener === 'function') {
|
|
338
401
|
const target = globalThis as unknown as EventTarget;
|
|
339
|
-
const listener = (event: Event) => {
|
|
402
|
+
const listener = (event: Event) => {
|
|
403
|
+
const rejection = event as PromiseRejectionEvent;
|
|
404
|
+
// An admitted process worker has exactly one Node owner. Shared
|
|
405
|
+
// embeddings still require provenance and never broadcast failures.
|
|
406
|
+
if (!ownsRealm && promiseOwner(rejection.promise) !== owner) return;
|
|
407
|
+
event.preventDefault();
|
|
408
|
+
report(rejection.reason, rejection.promise);
|
|
409
|
+
};
|
|
340
410
|
target.addEventListener('unhandledrejection', listener);
|
|
341
411
|
return () => target.removeEventListener('unhandledrejection', listener);
|
|
342
412
|
}
|
|
343
413
|
const host = __hostProcess;
|
|
344
414
|
if (host) {
|
|
345
|
-
const listener = (reason: unknown, promise?: Promise<unknown>) =>
|
|
415
|
+
const listener = (reason: unknown, promise?: Promise<unknown>) => {
|
|
416
|
+
if (ownsRealm || promiseOwner(promise) === owner) report(reason, promise);
|
|
417
|
+
};
|
|
346
418
|
host.on('unhandledRejection', listener);
|
|
347
419
|
return () => host.off('unhandledRejection', listener);
|
|
348
420
|
}
|
|
@@ -353,6 +425,18 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
353
425
|
return { stdout: '', stderr: 'VFS not initialized\n', exitCode: 1 };
|
|
354
426
|
}
|
|
355
427
|
|
|
428
|
+
// Capture this run's name, streams and cancellation before another Node
|
|
429
|
+
// entry can start. Forks and shell entries reach this same dispatch seam.
|
|
430
|
+
const runToken = runTokenOf(ctx);
|
|
431
|
+
const streams = runToken === null ? undefined : runStreamsFor(runToken);
|
|
432
|
+
const processHost = nodeProcessHostFor(runToken);
|
|
433
|
+
if (processHost && runToken !== null) {
|
|
434
|
+
return runHostedNode(processHost, {
|
|
435
|
+
token: runToken, argv: [...args], cwd: ctx.cwd, filesystem: tree, env: guestEnvironmentOf(ctx),
|
|
436
|
+
...(typeof ctx.stdin === 'string' ? { stdin: ctx.stdin } : {}), ...(streams ? { streams } : {}),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
356
440
|
// Node reads its own options before the script: `node --turbo-fast-api-calls
|
|
357
441
|
// file.js a b` runs file.js with `a b`, and the options it were given are
|
|
358
442
|
// its `execArgv`. Node's own suite spawns children that way, and the
|
|
@@ -378,8 +462,28 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
378
462
|
// its first fork.
|
|
379
463
|
const execArgv: string[] = [];
|
|
380
464
|
let first = 0;
|
|
465
|
+
// `-e`/`--eval` and `-p`/`--print` carry the program itself as their
|
|
466
|
+
// value: there is no script, and every word after the source is the
|
|
467
|
+
// program's argument. Taken for an option, the source was read as the
|
|
468
|
+
// script's path ("Cannot find module '/workspace/console.log(1+1)'").
|
|
469
|
+
let evaluated: { source: string; print: boolean } | null = null;
|
|
381
470
|
while (first < args.length && args[first]!.startsWith('-') && args[first] !== '-' && args[first] !== '--') {
|
|
382
|
-
|
|
471
|
+
const option = args[first]!;
|
|
472
|
+
const inline = /^(--eval|--print)=([\s\S]*)$/.exec(option);
|
|
473
|
+
if (inline) {
|
|
474
|
+
evaluated = { source: inline[2]!, print: inline[1] === '--print' };
|
|
475
|
+
execArgv.push(option);
|
|
476
|
+
first += 1;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
if (option === '-e' || option === '--eval' || option === '-p' || option === '--print' || option === '-pe') {
|
|
480
|
+
if (first + 1 >= args.length) return { stdout: '', stderr: `node: ${option} requires an argument\n`, exitCode: 9 };
|
|
481
|
+
evaluated = { source: args[first + 1]!, print: option.includes('p') };
|
|
482
|
+
execArgv.push(option, args[first + 1]!);
|
|
483
|
+
first += 2;
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
execArgv.push(option);
|
|
383
487
|
first += 1;
|
|
384
488
|
}
|
|
385
489
|
if (args[first] === '--') first += 1;
|
|
@@ -392,30 +496,21 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
392
496
|
};
|
|
393
497
|
let script = first;
|
|
394
498
|
let resolvedPath: string | null = null;
|
|
395
|
-
for (let index = first; index < args.length; index += 1) {
|
|
499
|
+
for (let index = first; evaluated === null && index < args.length; index += 1) {
|
|
396
500
|
const found = fileNamed(args[index]!);
|
|
397
501
|
if (found !== null) { script = index; resolvedPath = found; break; }
|
|
398
502
|
}
|
|
399
|
-
for (let index = first; index < script; index += 1) execArgv.push(args[index]!);
|
|
400
|
-
if (!args[script]) {
|
|
503
|
+
if (evaluated === null) for (let index = first; index < script; index += 1) execArgv.push(args[index]!);
|
|
504
|
+
if (evaluated === null && !args[script]) {
|
|
401
505
|
return { stdout: '', stderr: 'Usage: node <script.js> [args...]\n', exitCode: 1 };
|
|
402
506
|
}
|
|
403
|
-
if (resolvedPath === null) {
|
|
507
|
+
if (evaluated === null && resolvedPath === null) {
|
|
404
508
|
return { stdout: '', stderr: `Error: Cannot find module '${__resolvePath(ctx.cwd, args[script]!)}'\n`, exitCode: 1 };
|
|
405
509
|
}
|
|
406
510
|
|
|
407
511
|
let stdout = '';
|
|
408
512
|
let stderr = '';
|
|
409
513
|
|
|
410
|
-
// A run the host named is this guest process, for as long as the run
|
|
411
|
-
// lasts: `pendingTimers`, `processPorts` and `stopProcess` are answered
|
|
412
|
-
// from here, and a server the guest opens registers under this name.
|
|
413
|
-
const runToken = runTokenOf(ctx);
|
|
414
|
-
// What the host gave THIS run — its streams, its abort handle, whether it
|
|
415
|
-
// holds the run open. Read once here and nowhere from a module global, so
|
|
416
|
-
// a child spawned by this guest (a second run) cannot take them.
|
|
417
|
-
const streams = runToken === null ? undefined : runStreamsFor(runToken);
|
|
418
|
-
|
|
419
514
|
// Track whether process.exit() was called
|
|
420
515
|
let exitCalled = false;
|
|
421
516
|
let exitCode = 0;
|
|
@@ -485,6 +580,9 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
485
580
|
// whether a pid is a live process.
|
|
486
581
|
if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid);
|
|
487
582
|
const releaseRun = runToken === null ? null : __recordRun(runToken, {
|
|
583
|
+
process: proc,
|
|
584
|
+
stdout: appendStdout,
|
|
585
|
+
stderr: appendStderr,
|
|
488
586
|
pendingTimers: () => pendingGuestTimers(proc),
|
|
489
587
|
stopTimers: () => stopGuestTimers(proc),
|
|
490
588
|
reportUncaught: (error: unknown) => __reportUncaughtException(proc, error),
|
|
@@ -531,7 +629,11 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
531
629
|
// Set up process.argv for the script. Node fills argv[0] with the
|
|
532
630
|
// executable's path, the same value `process.execPath` reports; `argv0`
|
|
533
631
|
// keeps the original argv[0], the plain word.
|
|
534
|
-
|
|
632
|
+
// An evaluated program has no script: its argv is the executable and the
|
|
633
|
+
// words after the source, as Node's is.
|
|
634
|
+
proc.argv = evaluated !== null
|
|
635
|
+
? [__substrateExecPath, ...args.slice(first)]
|
|
636
|
+
: [__substrateExecPath, resolvedPath!, ...args.slice(script + 1)];
|
|
535
637
|
proc.argv0 = 'node';
|
|
536
638
|
proc.execArgv = execArgv;
|
|
537
639
|
|
|
@@ -596,7 +698,8 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
596
698
|
syncExecution = false;
|
|
597
699
|
try { proc.exit(1); } finally { syncExecution = wasSync; }
|
|
598
700
|
};
|
|
599
|
-
const detachRejections = __listenForUnhandledRejections(onUnhandledRejection
|
|
701
|
+
const detachRejections = __listenForUnhandledRejections(proc, onUnhandledRejection,
|
|
702
|
+
runToken !== null && nodeProcessRealmToken() === runToken);
|
|
600
703
|
|
|
601
704
|
// An exception nobody caught ends the program it was thrown in, as it ends
|
|
602
705
|
// one in Node: a guest listener for `uncaughtException` on its process
|
|
@@ -665,7 +768,14 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
665
768
|
// this program's, not whichever run started last. The engine's storage
|
|
666
769
|
// carries it into the timers, microtasks and `then` callbacks the entry
|
|
667
770
|
// schedules from here, so a child spawned later still names its parent.
|
|
668
|
-
|
|
771
|
+
// `-p` prints the completion value of the source, which a direct `eval`
|
|
772
|
+
// in the module body yields with the body's own `require` in scope.
|
|
773
|
+
const runEntry = () => evaluated !== null
|
|
774
|
+
? runtime.evaluate(
|
|
775
|
+
evaluated.print ? `console.log(eval(${JSON.stringify(evaluated.source)}));` : evaluated.source,
|
|
776
|
+
__resolvePath(ctx.cwd, '[eval]'),
|
|
777
|
+
)
|
|
778
|
+
: runtime.runFile(resolvedPath!);
|
|
669
779
|
entrySettling = __substratePendingOf(
|
|
670
780
|
(runToken === null ? runEntry() : enterRun(runToken, runEntry)).exports,
|
|
671
781
|
);
|
|
@@ -731,7 +841,7 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
731
841
|
if ((stdout.length > 0 || stderr.length > 0) && !__printedThenWorking()) {
|
|
732
842
|
// Settling the command is host work. Killing guest timers must not
|
|
733
843
|
// cancel this continuation and leave the command's promise unresolved.
|
|
734
|
-
await
|
|
844
|
+
await settlePrintedEntry();
|
|
735
845
|
// Work that began during that tick is the program's still: a build
|
|
736
846
|
// reached through a dynamic import's chain, a timer the entry set.
|
|
737
847
|
// vue-pure-admin's mock loader, `import('bundle-import').then(...)`
|
|
@@ -947,7 +1057,6 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
947
1057
|
},
|
|
948
1058
|
start: startChildRun,
|
|
949
1059
|
});
|
|
950
|
-
watchChildren();
|
|
951
1060
|
}
|
|
952
1061
|
|
|
953
1062
|
/**
|
|
@@ -958,19 +1067,10 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
958
1067
|
* a realm here holds many programs and the channel belongs to one of them.
|
|
959
1068
|
*/
|
|
960
1069
|
function attachChannel(proc: ReturnType<Runtime['getProcess']>, fd: number, serialization: string): void {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
//
|
|
964
|
-
|
|
965
|
-
// listens for a message.
|
|
966
|
-
channel.unref();
|
|
967
|
-
const control = setupChannel(proc, channel, serialization);
|
|
968
|
-
proc.on('newListener', (name: unknown) => {
|
|
969
|
-
if (name === 'message' || name === 'disconnect') control.refCounted();
|
|
970
|
-
});
|
|
971
|
-
proc.on('removeListener', (name: unknown) => {
|
|
972
|
-
if (name === 'message' || name === 'disconnect') control.unrefCounted();
|
|
973
|
-
});
|
|
1070
|
+
// With a process-bound loader, Node's own bootstrap now owns this too.
|
|
1071
|
+
// The host setupChannel built received sockets from the host net graph;
|
|
1072
|
+
// process.send and every received socket must use the child's graph.
|
|
1073
|
+
(loadNodeLibFor(proc, 'child_process') as ChildProcessModule)._forkChild(fd, serialization);
|
|
974
1074
|
}
|
|
975
1075
|
|
|
976
1076
|
/** The shell's environment as a record: just-bash hands a command a Map. */
|
|
@@ -1248,6 +1348,19 @@ interface ChildProcessHostResult {
|
|
|
1248
1348
|
exitCode: number;
|
|
1249
1349
|
}
|
|
1250
1350
|
|
|
1351
|
+
/**
|
|
1352
|
+
* One of a child's pipes past fd 2, as the host's program holds it: the
|
|
1353
|
+
* parent's handle for that fd is the other end.
|
|
1354
|
+
*/
|
|
1355
|
+
interface ChildProcessHostDescriptor {
|
|
1356
|
+
/** Bytes the parent writes to this fd, in order; ends when the parent closes it. */
|
|
1357
|
+
input: AsyncIterable<Uint8Array>;
|
|
1358
|
+
/** Bytes the child writes on this fd; they are reads on the parent's handle. */
|
|
1359
|
+
write(bytes: Uint8Array): void;
|
|
1360
|
+
/** The child closed this fd: EOF on the parent's handle. */
|
|
1361
|
+
end(): void;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1251
1364
|
/** How a command is handed to that host. */
|
|
1252
1365
|
interface ChildProcessHostRequest {
|
|
1253
1366
|
cwd?: string;
|
|
@@ -1255,10 +1368,13 @@ interface ChildProcessHostRequest {
|
|
|
1255
1368
|
/** Bytes already on fd 0 when the command begins, as Node delivers them. */
|
|
1256
1369
|
stdin?: string;
|
|
1257
1370
|
stdinStream: AsyncIterable<Uint8Array>;
|
|
1371
|
+
terminal?: RunStreams['terminal'];
|
|
1258
1372
|
signal: AbortSignal;
|
|
1259
1373
|
hold: { value: boolean };
|
|
1260
1374
|
onStdout: (data: string) => void;
|
|
1261
1375
|
onStderr: (data: string) => void;
|
|
1376
|
+
/** The child's pipes past fd 2, by number; only where it was started with any. */
|
|
1377
|
+
descriptors?: Record<number, ChildProcessHostDescriptor>;
|
|
1262
1378
|
}
|
|
1263
1379
|
|
|
1264
1380
|
/** The executor a host publishes on the well-known global symbol. */
|
|
@@ -1305,10 +1421,13 @@ interface CommandRun {
|
|
|
1305
1421
|
stdin?: string;
|
|
1306
1422
|
/** Bytes a writer still holds the run's fd 0 open with, as they arrive. */
|
|
1307
1423
|
stdinStream?: AsyncIterable<Uint8Array>;
|
|
1424
|
+
terminal?: RunStreams['terminal'];
|
|
1308
1425
|
signal?: AbortSignal;
|
|
1309
1426
|
onStdout?: (data: string) => void;
|
|
1310
1427
|
onStderr?: (data: string) => void;
|
|
1311
1428
|
vfs?: VirtualFS;
|
|
1429
|
+
/** The child's ends of its pipes past fd 2, for a host that runs it. */
|
|
1430
|
+
descriptors?: { fd: number; pipe: Pipe }[];
|
|
1312
1431
|
}
|
|
1313
1432
|
|
|
1314
1433
|
/**
|
|
@@ -1346,16 +1465,29 @@ async function routeCommand(run: CommandRun): Promise<CommandOutcome> {
|
|
|
1346
1465
|
let streamedErr = '';
|
|
1347
1466
|
const onStdout = (data: string): void => { streamedOut += String(data); run.onStdout?.(String(data)); };
|
|
1348
1467
|
const onStderr = (data: string): void => { streamedErr += String(data); run.onStderr?.(String(data)); };
|
|
1468
|
+
// This token routes the engine's own shell, not a program's environment.
|
|
1469
|
+
// If a host shell inherits it, a later Node launch can override its new
|
|
1470
|
+
// stream identity with the ancestor's and deliver output to both runs.
|
|
1471
|
+
// Host ancestry is already carried by the current process context.
|
|
1472
|
+
const hostEnv = run.env === undefined ? undefined : { ...run.env };
|
|
1473
|
+
if (hostEnv) delete hostEnv[PROCESS_TOKEN_ENV];
|
|
1474
|
+
// The host's program is the one holding the child's pipes past fd 2; the
|
|
1475
|
+
// run's end is its end of them, whatever it did not close itself.
|
|
1476
|
+
const opened = (run.descriptors ?? []).map(({ fd, pipe }) => ({ fd, ...descriptorOver(pipe) }));
|
|
1349
1477
|
try {
|
|
1350
1478
|
const result = await bridge.run(run.command, {
|
|
1351
1479
|
cwd: run.cwd,
|
|
1352
|
-
env:
|
|
1480
|
+
env: hostEnv,
|
|
1353
1481
|
...(typeof run.stdin === 'string' ? { stdin: run.stdin } : {}),
|
|
1354
1482
|
stdinStream: run.stdinStream ?? emptyStdinStream(),
|
|
1483
|
+
...(run.terminal ? { terminal: run.terminal } : {}),
|
|
1355
1484
|
signal: controller.signal,
|
|
1356
1485
|
hold: { value: true },
|
|
1357
1486
|
onStdout,
|
|
1358
1487
|
onStderr,
|
|
1488
|
+
...(opened.length > 0
|
|
1489
|
+
? { descriptors: Object.fromEntries(opened.map(({ fd, descriptor }) => [fd, descriptor])) }
|
|
1490
|
+
: {}),
|
|
1359
1491
|
});
|
|
1360
1492
|
const stdout = result.stdout || streamedOut;
|
|
1361
1493
|
const stderr = result.stderr || streamedErr;
|
|
@@ -1364,6 +1496,7 @@ async function routeCommand(run: CommandRun): Promise<CommandOutcome> {
|
|
|
1364
1496
|
return { stdout, stderr, exitCode: result.exitCode };
|
|
1365
1497
|
} finally {
|
|
1366
1498
|
if (bridge.parentSignal) bridge.parentSignal.removeEventListener('abort', abortWithParent);
|
|
1499
|
+
for (const { release } of opened) release();
|
|
1367
1500
|
}
|
|
1368
1501
|
}
|
|
1369
1502
|
|
|
@@ -1376,6 +1509,63 @@ async function routeCommand(run: CommandRun): Promise<CommandOutcome> {
|
|
|
1376
1509
|
return { stdout: result.stdout || '', stderr: result.stderr || '', exitCode: result.exitCode };
|
|
1377
1510
|
}
|
|
1378
1511
|
|
|
1512
|
+
/**
|
|
1513
|
+
* A child's pipe past fd 2, held by a program the host runs: the parent's
|
|
1514
|
+
* writes on its handle are the program's input, read the way the handle's fd 0
|
|
1515
|
+
* is read, and the program's writes are reads on the parent's handle, as bytes.
|
|
1516
|
+
* `release` is the run's end: reading stops and the input ends.
|
|
1517
|
+
*/
|
|
1518
|
+
function descriptorOver(pipe: Pipe): { descriptor: ChildProcessHostDescriptor; release(): void } {
|
|
1519
|
+
const queued: Uint8Array[] = [];
|
|
1520
|
+
let waiting: ((result: IteratorResult<Uint8Array>) => void) | null = null;
|
|
1521
|
+
let ended = false;
|
|
1522
|
+
const finish = (): void => {
|
|
1523
|
+
if (ended) return;
|
|
1524
|
+
ended = true;
|
|
1525
|
+
pipe.readStop();
|
|
1526
|
+
pipe.onread = null;
|
|
1527
|
+
if (waiting) { const wake = waiting; waiting = null; wake({ value: undefined, done: true }); }
|
|
1528
|
+
};
|
|
1529
|
+
pipe.onread = (arrayBuffer: ArrayBuffer | null): void => {
|
|
1530
|
+
const length = streamBaseState[kReadBytesOrError];
|
|
1531
|
+
if (length <= 0 || arrayBuffer === null) { finish(); return; }
|
|
1532
|
+
const bytes = new Uint8Array(arrayBuffer, streamBaseState[kArrayBufferOffset], length).slice();
|
|
1533
|
+
if (waiting) { const wake = waiting; waiting = null; wake({ value: bytes, done: false }); }
|
|
1534
|
+
else queued.push(bytes);
|
|
1535
|
+
};
|
|
1536
|
+
pipe.readStart();
|
|
1537
|
+
const input: AsyncIterable<Uint8Array> = {
|
|
1538
|
+
[Symbol.asyncIterator](): AsyncIterator<Uint8Array> {
|
|
1539
|
+
return {
|
|
1540
|
+
next(): Promise<IteratorResult<Uint8Array>> {
|
|
1541
|
+
const next = queued.shift();
|
|
1542
|
+
if (next) return Promise.resolve({ value: next, done: false });
|
|
1543
|
+
if (ended) return Promise.resolve({ value: undefined, done: true });
|
|
1544
|
+
return new Promise((resolve) => { waiting = resolve; });
|
|
1545
|
+
},
|
|
1546
|
+
return(): Promise<IteratorResult<Uint8Array>> {
|
|
1547
|
+
finish();
|
|
1548
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
1549
|
+
},
|
|
1550
|
+
};
|
|
1551
|
+
},
|
|
1552
|
+
};
|
|
1553
|
+
return {
|
|
1554
|
+
descriptor: {
|
|
1555
|
+
input,
|
|
1556
|
+
write(bytes: Uint8Array): void {
|
|
1557
|
+
if (pipe.closed) return;
|
|
1558
|
+
pipe.writeBuffer(new WriteWrap(), bytes);
|
|
1559
|
+
},
|
|
1560
|
+
end(): void {
|
|
1561
|
+
finish();
|
|
1562
|
+
if (!pipe.closed) pipe.shutdown(new ShutdownWrap());
|
|
1563
|
+
},
|
|
1564
|
+
},
|
|
1565
|
+
release: finish,
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1379
1569
|
/** A child whose stdin nobody writes to still needs a stream the host can read. */
|
|
1380
1570
|
function emptyStdinStream(): AsyncIterable<Uint8Array> {
|
|
1381
1571
|
return { async *[Symbol.asyncIterator]() { /* nothing is ever written */ } };
|
|
@@ -1488,9 +1678,28 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1488
1678
|
// number of the run that spawned it. `src/process-tokens.ts` says what
|
|
1489
1679
|
// reads them.
|
|
1490
1680
|
const pid = mintPid();
|
|
1491
|
-
|
|
1681
|
+
// In a process realm the admitted guest is always the spawning parent.
|
|
1682
|
+
// Async bookkeeping for a routed child must not make that child its parent.
|
|
1683
|
+
setRunPid(token, pid, runPid(nodeProcessRealmToken() ?? __currentProcessToken() ?? __lastLaunchedToken)?.pid ?? 0);
|
|
1492
1684
|
const controller = new AbortController();
|
|
1493
1685
|
const pendingStdin: Array<Uint8Array | null> = [];
|
|
1686
|
+
// A host terminal consumes input incrementally. The old string-only
|
|
1687
|
+
// route dropped every keystroke that arrived after a child was launched.
|
|
1688
|
+
const admittedNode = nodeProcessHostInstalled() && engineProgramFor(request.file, request.cwd) === 'node';
|
|
1689
|
+
const hostTerminal = request.terminal !== undefined && hostExecutor() !== null && !admittedNode;
|
|
1690
|
+
let wakeInput: (() => void) | undefined;
|
|
1691
|
+
const hostInput: AsyncIterable<Uint8Array> = {
|
|
1692
|
+
async *[Symbol.asyncIterator]() {
|
|
1693
|
+
while (!finished && !controller.signal.aborted) {
|
|
1694
|
+
if (pendingStdin.length) {
|
|
1695
|
+
const bytes = pendingStdin.shift();
|
|
1696
|
+
if (bytes === null) return;
|
|
1697
|
+
if (bytes) yield bytes;
|
|
1698
|
+
} else await new Promise<void>((resolve) => { wakeInput = resolve; });
|
|
1699
|
+
}
|
|
1700
|
+
},
|
|
1701
|
+
};
|
|
1702
|
+
controller.signal.addEventListener('abort', () => { wakeInput?.(); }, { once: true });
|
|
1494
1703
|
let stdinSink: RunStdin | null = null;
|
|
1495
1704
|
let finished = false;
|
|
1496
1705
|
let started = false;
|
|
@@ -1498,9 +1707,22 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1498
1707
|
/** What the parent wrote to the child's fd 0 before the command began. */
|
|
1499
1708
|
const initialStdin: Uint8Array[] = [];
|
|
1500
1709
|
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1710
|
+
let flushingInput: Promise<void> | undefined;
|
|
1711
|
+
const flushStdin = (): void | Promise<void> => {
|
|
1712
|
+
if (flushingInput) return flushingInput;
|
|
1713
|
+
if (!stdinSink || finished) return;
|
|
1714
|
+
while (pendingStdin.length > 0) {
|
|
1715
|
+
const chunk = pendingStdin.shift() as Uint8Array | null;
|
|
1716
|
+
if (stdinSink.writeAsync) {
|
|
1717
|
+
const waiting = stdinSink.writeAsync(chunk);
|
|
1718
|
+
flushingInput = waiting.then(() => {
|
|
1719
|
+
flushingInput = undefined;
|
|
1720
|
+
return flushStdin();
|
|
1721
|
+
}, cause => { flushingInput = undefined; throw cause; });
|
|
1722
|
+
return flushingInput;
|
|
1723
|
+
}
|
|
1724
|
+
stdinSink.push(chunk);
|
|
1725
|
+
}
|
|
1504
1726
|
};
|
|
1505
1727
|
|
|
1506
1728
|
let streamedOut = 0;
|
|
@@ -1512,23 +1734,39 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1512
1734
|
held: false,
|
|
1513
1735
|
stdinOpen: request.stdinIsPipe,
|
|
1514
1736
|
stderrIsPipe: request.stderrIsPipe,
|
|
1737
|
+
terminal: request.terminal,
|
|
1515
1738
|
get stdin(): RunStdin | null | undefined { return stdinSink; },
|
|
1516
|
-
set stdin(sink: RunStdin | null | undefined) {
|
|
1739
|
+
set stdin(sink: RunStdin | null | undefined) {
|
|
1740
|
+
stdinSink = sink ?? null;
|
|
1741
|
+
if (stdinSink) {
|
|
1742
|
+
const pending = flushStdin();
|
|
1743
|
+
if (pending) void pending.catch(cause => {
|
|
1744
|
+
// Normal child exit detaches the sink before cancelling its writer.
|
|
1745
|
+
// That cancellation must not turn a successful exit into failure.
|
|
1746
|
+
if (!finished && stdinSink === sink) { controller.abort(cause); end(1, null); }
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
},
|
|
1517
1750
|
};
|
|
1518
1751
|
registerRunStreams(token, streams);
|
|
1519
1752
|
// The child is started with the descriptors its parent wired: an IPC channel
|
|
1520
1753
|
// is at the number its `NODE_CHANNEL_FD` will name, under this run's name,
|
|
1521
1754
|
// because every child is told the same number.
|
|
1522
1755
|
if (request.channel) registerRunFd(token, request.channel.fd, 'PIPE', request.channel.pipe);
|
|
1756
|
+
// Its pipes past fd 2 are there the same way, at the numbers it was given.
|
|
1757
|
+
for (const { fd, pipe } of request.descriptors) registerRunFd(token, fd, 'PIPE', pipe);
|
|
1523
1758
|
|
|
1524
1759
|
const end = (code: number, signal: string | null): void => {
|
|
1525
1760
|
if (finished) return;
|
|
1526
1761
|
finished = true;
|
|
1762
|
+
wakeInput?.();
|
|
1763
|
+
wakeInput = undefined;
|
|
1527
1764
|
_activeForkedChildren -= 1;
|
|
1528
1765
|
releaseRunStreams(token);
|
|
1529
1766
|
releaseRunFds(token);
|
|
1530
1767
|
// A run that has ended is no longer a process: its number answers ESRCH.
|
|
1531
1768
|
forgetRunPid(token);
|
|
1769
|
+
__substrateChildren.delete(pid);
|
|
1532
1770
|
_onForkedChildExit?.();
|
|
1533
1771
|
request.exit(code, signal);
|
|
1534
1772
|
};
|
|
@@ -1551,16 +1789,24 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1551
1789
|
void (async () => {
|
|
1552
1790
|
let outcome: CommandOutcome;
|
|
1553
1791
|
try {
|
|
1554
|
-
|
|
1792
|
+
const processHost = admittedNode ? nodeProcessHostFor(token) : undefined;
|
|
1793
|
+
const env = { ...request.env };
|
|
1794
|
+
delete env[PROCESS_TOKEN_ENV];
|
|
1795
|
+
outcome = processHost ? await runHostedNode(processHost, {
|
|
1796
|
+
token, argv: request.args.slice(1), cwd: request.cwd ?? '/',
|
|
1797
|
+
filesystem: currentVfs!, env, streams, ...(stdin !== undefined ? { stdin } : {}),
|
|
1798
|
+
}) : await enterRun(token, () => routeCommand({
|
|
1555
1799
|
command: __substrateLineFor(request.file, request.args, request.cwd),
|
|
1556
|
-
engineFirst: programExists(request.file, request.cwd, request.env),
|
|
1800
|
+
engineFirst: !hostTerminal && programExists(request.file, request.cwd, request.env),
|
|
1557
1801
|
cwd: request.cwd,
|
|
1558
1802
|
env: { ...request.env, [PROCESS_TOKEN_ENV]: token },
|
|
1559
1803
|
stdin,
|
|
1804
|
+
...(hostTerminal ? { stdinStream: hostInput, terminal: request.terminal } : {}),
|
|
1560
1805
|
signal: controller.signal,
|
|
1561
1806
|
onStdout: streams.onStdout,
|
|
1562
1807
|
onStderr: streams.onStderr,
|
|
1563
|
-
|
|
1808
|
+
descriptors: request.descriptors,
|
|
1809
|
+
}));
|
|
1564
1810
|
} catch (error) {
|
|
1565
1811
|
outcome = { stdout: '', stderr: `${error instanceof Error ? error.message : String(error)}\n`, exitCode: 1 };
|
|
1566
1812
|
}
|
|
@@ -1574,10 +1820,10 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1574
1820
|
};
|
|
1575
1821
|
|
|
1576
1822
|
_activeForkedChildren += 1;
|
|
1577
|
-
if (request.stdinIsPipe) (globalThis.__browserRuntimeNativeSetTimeout ?? setTimeout).call(globalThis, begin, 0);
|
|
1823
|
+
if (request.stdinIsPipe && !admittedNode) (globalThis.__browserRuntimeNativeSetTimeout ?? setTimeout).call(globalThis, begin, 0);
|
|
1578
1824
|
else begin();
|
|
1579
1825
|
|
|
1580
|
-
|
|
1826
|
+
const control: StartedRun = {
|
|
1581
1827
|
token,
|
|
1582
1828
|
pid,
|
|
1583
1829
|
kill(signal: string): number {
|
|
@@ -1590,12 +1836,14 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1590
1836
|
end(0, killedBy);
|
|
1591
1837
|
return 0;
|
|
1592
1838
|
},
|
|
1593
|
-
writeStdin(bytes: Uint8Array): void {
|
|
1839
|
+
writeStdin(bytes: Uint8Array): void | Promise<void> {
|
|
1840
|
+
if (hostTerminal) { pendingStdin.push(bytes); wakeInput?.(); wakeInput = undefined; return; }
|
|
1594
1841
|
if (!started) { initialStdin.push(bytes); return; }
|
|
1595
1842
|
pendingStdin.push(bytes);
|
|
1596
|
-
flushStdin();
|
|
1843
|
+
return flushStdin();
|
|
1597
1844
|
},
|
|
1598
1845
|
endStdin(): void {
|
|
1846
|
+
if (hostTerminal) { pendingStdin.push(null); wakeInput?.(); wakeInput = undefined; return; }
|
|
1599
1847
|
if (!started) {
|
|
1600
1848
|
// The writer closed before the command began: the run's fd 0 is the
|
|
1601
1849
|
// string already collected and nothing more, so it is not held open.
|
|
@@ -1604,38 +1852,23 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1604
1852
|
return;
|
|
1605
1853
|
}
|
|
1606
1854
|
pendingStdin.push(null);
|
|
1607
|
-
|
|
1855
|
+
const sink = stdinSink;
|
|
1856
|
+
const pending = flushStdin();
|
|
1857
|
+
if (pending) void pending.catch(cause => {
|
|
1858
|
+
if (!finished && stdinSink === sink && sink) { controller.abort(cause); end(1, null); }
|
|
1859
|
+
});
|
|
1608
1860
|
},
|
|
1609
1861
|
};
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
* Node publishes each `ChildProcess` on the `child_process` diagnostics
|
|
1616
|
-
* channel as it is constructed, which is the door this reads: the pid is
|
|
1617
|
-
* filled by `spawn` a moment later, so the child is recorded on the next tick.
|
|
1618
|
-
*/
|
|
1619
|
-
function watchChildren(): void {
|
|
1620
|
-
// Node's own `diagnostics_channel`, the same module the vendored
|
|
1621
|
-
// `child_process.js` publishes on. A channel is a name in one registry, and
|
|
1622
|
-
// the engine's own imitation of the module was a second registry: a
|
|
1623
|
-
// subscriber there heard nothing a vendored file published.
|
|
1624
|
-
const diagnosticsChannel = diagnosticsChannelModule as unknown as {
|
|
1625
|
-
channel(name: string): { subscribe(listener: (message: unknown) => void): void };
|
|
1626
|
-
};
|
|
1627
|
-
diagnosticsChannel.channel('child_process').subscribe((message: unknown) => {
|
|
1628
|
-
const child = (message as { process?: { pid?: number; exitCode: number | null; signalCode: string | null; kill(signal?: string): boolean; once(event: string, listener: () => void): unknown } }).process;
|
|
1629
|
-
if (!child) return;
|
|
1630
|
-
queueMicrotask(() => {
|
|
1631
|
-
const pid = child.pid;
|
|
1632
|
-
if (typeof pid !== 'number' || pid === 0) return;
|
|
1633
|
-
__substrateChildren.set(pid, child);
|
|
1634
|
-
child.once('exit', () => { __substrateChildren.delete(pid); });
|
|
1635
|
-
});
|
|
1862
|
+
// A pid is signalable before spawn returns, including cancellation in
|
|
1863
|
+
// the same turn. A diagnostics-channel microtask registered it too late.
|
|
1864
|
+
__substrateChildren.set(pid, {
|
|
1865
|
+
exitCode: null, signalCode: null,
|
|
1866
|
+
kill: (signal = 'SIGTERM') => control.kill(signal) === 0,
|
|
1636
1867
|
});
|
|
1868
|
+
return control;
|
|
1637
1869
|
}
|
|
1638
1870
|
|
|
1871
|
+
|
|
1639
1872
|
export default {
|
|
1640
1873
|
initChildProcess,
|
|
1641
1874
|
registerRunStreams,
|