@run0/jiki 0.1.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/browser-bundle.d.ts +40 -0
- package/dist/builtins.d.ts +22 -0
- package/dist/code-transform.d.ts +7 -0
- package/dist/config/cdn.d.ts +13 -0
- package/dist/container.d.ts +101 -0
- package/dist/dev-server.d.ts +69 -0
- package/dist/errors.d.ts +19 -0
- package/dist/frameworks/code-transforms.d.ts +32 -0
- package/dist/frameworks/next-api-handler.d.ts +72 -0
- package/dist/frameworks/next-dev-server.d.ts +141 -0
- package/dist/frameworks/next-html-generator.d.ts +36 -0
- package/dist/frameworks/next-route-resolver.d.ts +19 -0
- package/dist/frameworks/next-shims.d.ts +78 -0
- package/dist/frameworks/remix-dev-server.d.ts +47 -0
- package/dist/frameworks/sveltekit-dev-server.d.ts +43 -0
- package/dist/frameworks/vite-dev-server.d.ts +50 -0
- package/dist/fs-errors.d.ts +36 -0
- package/dist/index.cjs +14916 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.mjs +14898 -0
- package/dist/index.mjs.map +1 -0
- package/dist/kernel.d.ts +48 -0
- package/dist/memfs.d.ts +144 -0
- package/dist/metrics.d.ts +78 -0
- package/dist/module-resolver.d.ts +60 -0
- package/dist/network-interceptor.d.ts +71 -0
- package/dist/npm/cache.d.ts +76 -0
- package/dist/npm/index.d.ts +60 -0
- package/dist/npm/lockfile-reader.d.ts +32 -0
- package/dist/npm/pnpm.d.ts +18 -0
- package/dist/npm/registry.d.ts +45 -0
- package/dist/npm/resolver.d.ts +39 -0
- package/dist/npm/sync-installer.d.ts +18 -0
- package/dist/npm/tarball.d.ts +4 -0
- package/dist/npm/workspaces.d.ts +46 -0
- package/dist/persistence.d.ts +94 -0
- package/dist/plugin.d.ts +156 -0
- package/dist/polyfills/assert.d.ts +30 -0
- package/dist/polyfills/child_process.d.ts +116 -0
- package/dist/polyfills/chokidar.d.ts +18 -0
- package/dist/polyfills/crypto.d.ts +49 -0
- package/dist/polyfills/events.d.ts +28 -0
- package/dist/polyfills/fs.d.ts +82 -0
- package/dist/polyfills/http.d.ts +147 -0
- package/dist/polyfills/module.d.ts +29 -0
- package/dist/polyfills/net.d.ts +53 -0
- package/dist/polyfills/os.d.ts +91 -0
- package/dist/polyfills/path.d.ts +96 -0
- package/dist/polyfills/perf_hooks.d.ts +21 -0
- package/dist/polyfills/process.d.ts +99 -0
- package/dist/polyfills/querystring.d.ts +15 -0
- package/dist/polyfills/readdirp.d.ts +18 -0
- package/dist/polyfills/readline.d.ts +32 -0
- package/dist/polyfills/stream.d.ts +106 -0
- package/dist/polyfills/stubs.d.ts +737 -0
- package/dist/polyfills/tty.d.ts +25 -0
- package/dist/polyfills/url.d.ts +41 -0
- package/dist/polyfills/util.d.ts +61 -0
- package/dist/polyfills/v8.d.ts +43 -0
- package/dist/polyfills/vm.d.ts +76 -0
- package/dist/polyfills/worker-threads.d.ts +77 -0
- package/dist/polyfills/ws.d.ts +32 -0
- package/dist/polyfills/zlib.d.ts +87 -0
- package/dist/runtime-helpers.d.ts +4 -0
- package/dist/runtime-interface.d.ts +39 -0
- package/dist/sandbox.d.ts +69 -0
- package/dist/server-bridge.d.ts +55 -0
- package/dist/shell-commands.d.ts +2 -0
- package/dist/shell.d.ts +101 -0
- package/dist/transpiler.d.ts +47 -0
- package/dist/type-checker.d.ts +57 -0
- package/dist/types/package-json.d.ts +17 -0
- package/dist/utils/binary-encoding.d.ts +4 -0
- package/dist/utils/hash.d.ts +6 -0
- package/dist/utils/safe-path.d.ts +6 -0
- package/dist/worker-runtime.d.ts +34 -0
- package/package.json +59 -0
- package/src/browser-bundle.ts +498 -0
- package/src/builtins.ts +222 -0
- package/src/code-transform.ts +183 -0
- package/src/config/cdn.ts +17 -0
- package/src/container.ts +343 -0
- package/src/dev-server.ts +322 -0
- package/src/errors.ts +604 -0
- package/src/frameworks/code-transforms.ts +667 -0
- package/src/frameworks/next-api-handler.ts +366 -0
- package/src/frameworks/next-dev-server.ts +1252 -0
- package/src/frameworks/next-html-generator.ts +585 -0
- package/src/frameworks/next-route-resolver.ts +521 -0
- package/src/frameworks/next-shims.ts +1084 -0
- package/src/frameworks/remix-dev-server.ts +163 -0
- package/src/frameworks/sveltekit-dev-server.ts +197 -0
- package/src/frameworks/vite-dev-server.ts +370 -0
- package/src/fs-errors.ts +118 -0
- package/src/index.ts +188 -0
- package/src/kernel.ts +381 -0
- package/src/memfs.ts +1006 -0
- package/src/metrics.ts +140 -0
- package/src/module-resolver.ts +511 -0
- package/src/network-interceptor.ts +143 -0
- package/src/npm/cache.ts +172 -0
- package/src/npm/index.ts +377 -0
- package/src/npm/lockfile-reader.ts +105 -0
- package/src/npm/pnpm.ts +108 -0
- package/src/npm/registry.ts +120 -0
- package/src/npm/resolver.ts +339 -0
- package/src/npm/sync-installer.ts +217 -0
- package/src/npm/tarball.ts +136 -0
- package/src/npm/workspaces.ts +255 -0
- package/src/persistence.ts +235 -0
- package/src/plugin.ts +293 -0
- package/src/polyfills/assert.ts +164 -0
- package/src/polyfills/child_process.ts +535 -0
- package/src/polyfills/chokidar.ts +52 -0
- package/src/polyfills/crypto.ts +433 -0
- package/src/polyfills/events.ts +178 -0
- package/src/polyfills/fs.ts +297 -0
- package/src/polyfills/http.ts +478 -0
- package/src/polyfills/module.ts +97 -0
- package/src/polyfills/net.ts +123 -0
- package/src/polyfills/os.ts +108 -0
- package/src/polyfills/path.ts +169 -0
- package/src/polyfills/perf_hooks.ts +30 -0
- package/src/polyfills/process.ts +349 -0
- package/src/polyfills/querystring.ts +66 -0
- package/src/polyfills/readdirp.ts +72 -0
- package/src/polyfills/readline.ts +80 -0
- package/src/polyfills/stream.ts +610 -0
- package/src/polyfills/stubs.ts +600 -0
- package/src/polyfills/tty.ts +43 -0
- package/src/polyfills/url.ts +97 -0
- package/src/polyfills/util.ts +173 -0
- package/src/polyfills/v8.ts +62 -0
- package/src/polyfills/vm.ts +111 -0
- package/src/polyfills/worker-threads.ts +189 -0
- package/src/polyfills/ws.ts +73 -0
- package/src/polyfills/zlib.ts +244 -0
- package/src/runtime-helpers.ts +83 -0
- package/src/runtime-interface.ts +46 -0
- package/src/sandbox.ts +178 -0
- package/src/server-bridge.ts +473 -0
- package/src/service-worker.ts +153 -0
- package/src/shell-commands.ts +708 -0
- package/src/shell.ts +795 -0
- package/src/transpiler.ts +282 -0
- package/src/type-checker.ts +241 -0
- package/src/types/package-json.ts +17 -0
- package/src/utils/binary-encoding.ts +38 -0
- package/src/utils/hash.ts +24 -0
- package/src/utils/safe-path.ts +38 -0
- package/src/worker-runtime.ts +42 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import { EventEmitter } from "./events";
|
|
2
|
+
import type { MemFS } from "../memfs";
|
|
3
|
+
|
|
4
|
+
let _vfs: MemFS | null = null;
|
|
5
|
+
let _runtimeFactory:
|
|
6
|
+
| ((vfs: MemFS, options?: Record<string, unknown>) => KernelLike)
|
|
7
|
+
| null = null;
|
|
8
|
+
|
|
9
|
+
interface KernelLike {
|
|
10
|
+
runFile: (path: string) => unknown;
|
|
11
|
+
getProcess: () => ProcessLike;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ProcessLike {
|
|
15
|
+
argv: string[];
|
|
16
|
+
exit: (code?: number) => never;
|
|
17
|
+
connected?: boolean;
|
|
18
|
+
send?: (
|
|
19
|
+
message: unknown,
|
|
20
|
+
callback?: (error: Error | null) => void,
|
|
21
|
+
) => boolean;
|
|
22
|
+
stdout: {
|
|
23
|
+
isTTY?: boolean;
|
|
24
|
+
emit: (event: string, ...args: unknown[]) => void;
|
|
25
|
+
};
|
|
26
|
+
stderr: {
|
|
27
|
+
isTTY?: boolean;
|
|
28
|
+
emit: (event: string, ...args: unknown[]) => void;
|
|
29
|
+
};
|
|
30
|
+
stdin: {
|
|
31
|
+
isTTY?: boolean;
|
|
32
|
+
setRawMode?: (mode: boolean) => unknown;
|
|
33
|
+
emit: (event: string, ...args: unknown[]) => void;
|
|
34
|
+
};
|
|
35
|
+
on: (event: string, listener: (...args: unknown[]) => void) => unknown;
|
|
36
|
+
once: (event: string, listener: (...args: unknown[]) => void) => unknown;
|
|
37
|
+
emit: (event: string, ...args: unknown[]) => boolean;
|
|
38
|
+
listeners: (event: string) => ((...args: unknown[]) => void)[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Patch Object.defineProperty to force configurable: true on globalThis properties.
|
|
42
|
+
// Only applied in browser environments where all forks share globalThis.
|
|
43
|
+
// Libraries like vitest define non-configurable properties (e.g. __vitest_index__)
|
|
44
|
+
// that need to be configurable for re-runs in our shared-global environment.
|
|
45
|
+
let _definePropertyPatched = false;
|
|
46
|
+
export function patchDefineProperty(): void {
|
|
47
|
+
if (_definePropertyPatched) return;
|
|
48
|
+
_definePropertyPatched = true;
|
|
49
|
+
const _realDefineProperty = Object.defineProperty;
|
|
50
|
+
Object.defineProperty = function (
|
|
51
|
+
target: object,
|
|
52
|
+
key: PropertyKey,
|
|
53
|
+
descriptor: PropertyDescriptor,
|
|
54
|
+
): object {
|
|
55
|
+
if (target === globalThis && descriptor && !descriptor.configurable) {
|
|
56
|
+
descriptor = { ...descriptor, configurable: true };
|
|
57
|
+
}
|
|
58
|
+
return _realDefineProperty.call(Object, target, key, descriptor) as object;
|
|
59
|
+
} as typeof Object.defineProperty;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let _activeForkedChildren = 0;
|
|
63
|
+
let _onForkedChildExit: (() => void) | null = null;
|
|
64
|
+
|
|
65
|
+
export function getActiveForkedChildren(): number {
|
|
66
|
+
return _activeForkedChildren;
|
|
67
|
+
}
|
|
68
|
+
export function setOnForkedChildExit(
|
|
69
|
+
cb: (() => void) | null,
|
|
70
|
+
): (() => void) | null {
|
|
71
|
+
const prev = _onForkedChildExit;
|
|
72
|
+
_onForkedChildExit = cb;
|
|
73
|
+
return prev;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Streaming callbacks for long-running commands (e.g. vitest watch)
|
|
77
|
+
let _streamStdout: ((data: string) => void) | null = null;
|
|
78
|
+
let _streamStderr: ((data: string) => void) | null = null;
|
|
79
|
+
let _abortSignal: AbortSignal | null = null;
|
|
80
|
+
|
|
81
|
+
export function setStreamingCallbacks(opts: {
|
|
82
|
+
onStdout?: (data: string) => void;
|
|
83
|
+
onStderr?: (data: string) => void;
|
|
84
|
+
signal?: AbortSignal;
|
|
85
|
+
}): void {
|
|
86
|
+
_streamStdout = opts.onStdout || null;
|
|
87
|
+
_streamStderr = opts.onStderr || null;
|
|
88
|
+
_abortSignal = opts.signal || null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function clearStreamingCallbacks(): void {
|
|
92
|
+
_streamStdout = null;
|
|
93
|
+
_streamStderr = null;
|
|
94
|
+
_abortSignal = null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getStreamingState() {
|
|
98
|
+
return {
|
|
99
|
+
streamStdout: _streamStdout,
|
|
100
|
+
streamStderr: _streamStderr,
|
|
101
|
+
abortSignal: _abortSignal,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Reference to stdin of the currently running node process for interactive input
|
|
106
|
+
let _activeProcessStdin: {
|
|
107
|
+
emit: (event: string, ...args: unknown[]) => void;
|
|
108
|
+
} | null = null;
|
|
109
|
+
|
|
110
|
+
export function sendStdin(data: string): void {
|
|
111
|
+
if (_activeProcessStdin) {
|
|
112
|
+
_activeProcessStdin.emit("data", data);
|
|
113
|
+
for (const ch of data) {
|
|
114
|
+
_activeProcessStdin.emit("keypress", ch, {
|
|
115
|
+
sequence: ch,
|
|
116
|
+
name: ch,
|
|
117
|
+
ctrl: false,
|
|
118
|
+
meta: false,
|
|
119
|
+
shift: false,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function setActiveProcessStdin(
|
|
126
|
+
stdin: { emit: (event: string, ...args: unknown[]) => void } | null,
|
|
127
|
+
): void {
|
|
128
|
+
_activeProcessStdin = stdin;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function initChildProcess(
|
|
132
|
+
vfs: MemFS,
|
|
133
|
+
runtimeFactory?: (
|
|
134
|
+
vfs: MemFS,
|
|
135
|
+
options?: Record<string, unknown>,
|
|
136
|
+
) => KernelLike,
|
|
137
|
+
): void {
|
|
138
|
+
_vfs = vfs;
|
|
139
|
+
if (runtimeFactory) _runtimeFactory = runtimeFactory;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export class ChildProcess extends EventEmitter {
|
|
143
|
+
pid: number;
|
|
144
|
+
exitCode: number | null = null;
|
|
145
|
+
signalCode: string | null = null;
|
|
146
|
+
killed = false;
|
|
147
|
+
connected = false;
|
|
148
|
+
spawnargs: string[] = [];
|
|
149
|
+
spawnfile = "";
|
|
150
|
+
|
|
151
|
+
stdout: EventEmitter | null;
|
|
152
|
+
stderr: EventEmitter | null;
|
|
153
|
+
stdin: EventEmitter | null;
|
|
154
|
+
|
|
155
|
+
constructor() {
|
|
156
|
+
super();
|
|
157
|
+
this.pid = Math.floor(Math.random() * 10000) + 1000;
|
|
158
|
+
this.stdout = new EventEmitter();
|
|
159
|
+
this.stderr = new EventEmitter();
|
|
160
|
+
this.stdin = new EventEmitter();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
kill(signal?: string): boolean {
|
|
164
|
+
this.killed = true;
|
|
165
|
+
this.connected = false;
|
|
166
|
+
this.emit("exit", null, signal || "SIGTERM");
|
|
167
|
+
this.emit("close", null, signal || "SIGTERM");
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
send(message: unknown, _cb?: (error: Error | null) => void): boolean {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
disconnect(): void {
|
|
176
|
+
this.connected = false;
|
|
177
|
+
this.emit("disconnect");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
ref(): this {
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
unref(): this {
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface ExecResult {
|
|
189
|
+
stdout: string;
|
|
190
|
+
stderr: string;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function exec(
|
|
194
|
+
command: string,
|
|
195
|
+
optionsOrCb?: unknown | ((err: Error | null, result?: ExecResult) => void),
|
|
196
|
+
cb?: (err: Error | null, result?: ExecResult) => void,
|
|
197
|
+
): ChildProcess {
|
|
198
|
+
const callback =
|
|
199
|
+
typeof optionsOrCb === "function"
|
|
200
|
+
? (optionsOrCb as (err: Error | null, result?: ExecResult) => void)
|
|
201
|
+
: cb;
|
|
202
|
+
const child = new ChildProcess();
|
|
203
|
+
|
|
204
|
+
setTimeout(() => {
|
|
205
|
+
const stdout = `Executed: ${command}`;
|
|
206
|
+
child.stdout?.emit("data", stdout);
|
|
207
|
+
child.stdout?.emit("end");
|
|
208
|
+
child.stderr?.emit("end");
|
|
209
|
+
child.exitCode = 0;
|
|
210
|
+
child.emit("close", 0);
|
|
211
|
+
child.emit("exit", 0);
|
|
212
|
+
if (callback) callback(null, { stdout, stderr: "" });
|
|
213
|
+
}, 0);
|
|
214
|
+
|
|
215
|
+
return child;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function execSync(command: string, _options?: unknown): string {
|
|
219
|
+
return `Executed: ${command}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface SpawnOptions {
|
|
223
|
+
cwd?: string;
|
|
224
|
+
env?: Record<string, string>;
|
|
225
|
+
stdio?:
|
|
226
|
+
| "pipe"
|
|
227
|
+
| "inherit"
|
|
228
|
+
| "ignore"
|
|
229
|
+
| Array<"pipe" | "inherit" | "ignore" | "ipc" | null>;
|
|
230
|
+
shell?: boolean | string;
|
|
231
|
+
detached?: boolean;
|
|
232
|
+
uid?: number;
|
|
233
|
+
gid?: number;
|
|
234
|
+
timeout?: number;
|
|
235
|
+
signal?: AbortSignal;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function spawn(
|
|
239
|
+
command: string,
|
|
240
|
+
args: string[] = [],
|
|
241
|
+
options?: SpawnOptions | unknown,
|
|
242
|
+
): ChildProcess {
|
|
243
|
+
const child = new ChildProcess();
|
|
244
|
+
const opts =
|
|
245
|
+
options && typeof options === "object"
|
|
246
|
+
? (options as SpawnOptions)
|
|
247
|
+
: undefined;
|
|
248
|
+
|
|
249
|
+
// Handle stdio configuration
|
|
250
|
+
if (opts?.stdio) {
|
|
251
|
+
const stdioConfig =
|
|
252
|
+
typeof opts.stdio === "string"
|
|
253
|
+
? [opts.stdio, opts.stdio, opts.stdio]
|
|
254
|
+
: opts.stdio;
|
|
255
|
+
|
|
256
|
+
// stdin (index 0)
|
|
257
|
+
if (stdioConfig[0] === "ignore") {
|
|
258
|
+
child.stdin = null;
|
|
259
|
+
}
|
|
260
|
+
// stdout (index 1)
|
|
261
|
+
if (stdioConfig[1] === "ignore") {
|
|
262
|
+
child.stdout = null;
|
|
263
|
+
}
|
|
264
|
+
// stderr (index 2)
|
|
265
|
+
if (stdioConfig[2] === "ignore") {
|
|
266
|
+
child.stderr = null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// IPC channel stub (index 3 === 'ipc')
|
|
270
|
+
if (stdioConfig.length > 3 && stdioConfig[3] === "ipc") {
|
|
271
|
+
child.connected = true;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Handle shell option — wrap command for shell execution
|
|
276
|
+
if (opts?.shell) {
|
|
277
|
+
child.spawnfile = typeof opts.shell === "string" ? opts.shell : "/bin/sh";
|
|
278
|
+
child.spawnargs = [
|
|
279
|
+
child.spawnfile,
|
|
280
|
+
"-c",
|
|
281
|
+
`${command} ${args.join(" ")}`.trim(),
|
|
282
|
+
];
|
|
283
|
+
} else {
|
|
284
|
+
child.spawnfile = command;
|
|
285
|
+
child.spawnargs = [command, ...args];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
setTimeout(() => {
|
|
289
|
+
child.emit("close", 0);
|
|
290
|
+
child.emit("exit", 0);
|
|
291
|
+
}, 0);
|
|
292
|
+
return child;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function execFile(
|
|
296
|
+
file: string,
|
|
297
|
+
args: string[] = [],
|
|
298
|
+
optionsOrCb?: unknown,
|
|
299
|
+
cb?: (err: Error | null, stdout?: string, stderr?: string) => void,
|
|
300
|
+
): ChildProcess {
|
|
301
|
+
const callback =
|
|
302
|
+
typeof optionsOrCb === "function"
|
|
303
|
+
? (optionsOrCb as (
|
|
304
|
+
err: Error | null,
|
|
305
|
+
stdout?: string,
|
|
306
|
+
stderr?: string,
|
|
307
|
+
) => void)
|
|
308
|
+
: cb;
|
|
309
|
+
return exec(
|
|
310
|
+
`${file} ${args.join(" ")}`,
|
|
311
|
+
{},
|
|
312
|
+
callback
|
|
313
|
+
? (err, result) => callback(err, result?.stdout, result?.stderr)
|
|
314
|
+
: undefined,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function execFileSync(
|
|
319
|
+
file: string,
|
|
320
|
+
args: string[] = [],
|
|
321
|
+
_options?: unknown,
|
|
322
|
+
): string {
|
|
323
|
+
return execSync(`${file} ${args.join(" ")}`, _options);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function spawnSync(
|
|
327
|
+
command: string,
|
|
328
|
+
args: string[] = [],
|
|
329
|
+
_options?: unknown,
|
|
330
|
+
): { stdout: string; stderr: string; status: number } {
|
|
331
|
+
return {
|
|
332
|
+
stdout: `Executed: ${command} ${args.join(" ")}`,
|
|
333
|
+
stderr: "",
|
|
334
|
+
status: 0,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Fork — runs a Node.js module in a simulated child process using a new Kernel.
|
|
340
|
+
* Creates bidirectional IPC between parent and child, with serialized message delivery.
|
|
341
|
+
*/
|
|
342
|
+
export function fork(
|
|
343
|
+
modulePath: string,
|
|
344
|
+
argsOrOptions?: string[] | Record<string, unknown>,
|
|
345
|
+
options?: Record<string, unknown>,
|
|
346
|
+
): ChildProcess {
|
|
347
|
+
if (!_vfs) {
|
|
348
|
+
throw new Error("VFS not initialized");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let args: string[] = [];
|
|
352
|
+
let opts: Record<string, unknown> = {};
|
|
353
|
+
if (Array.isArray(argsOrOptions)) {
|
|
354
|
+
args = argsOrOptions;
|
|
355
|
+
opts = options || {};
|
|
356
|
+
} else if (argsOrOptions) {
|
|
357
|
+
opts = argsOrOptions;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const cwd = (opts.cwd as string) || "/";
|
|
361
|
+
const env = (opts.env as Record<string, string>) || {};
|
|
362
|
+
const execArgv = (opts.execArgv as string[]) || [];
|
|
363
|
+
|
|
364
|
+
const resolvedPath = modulePath.startsWith("/")
|
|
365
|
+
? modulePath
|
|
366
|
+
: `${cwd}/${modulePath}`.replace(/\/+/g, "/");
|
|
367
|
+
|
|
368
|
+
const child = new ChildProcess();
|
|
369
|
+
child.connected = true;
|
|
370
|
+
child.spawnargs = ["node", ...execArgv, resolvedPath, ...args];
|
|
371
|
+
child.spawnfile = "node";
|
|
372
|
+
|
|
373
|
+
if (!_runtimeFactory) {
|
|
374
|
+
setTimeout(() => {
|
|
375
|
+
child.emit("close", 0);
|
|
376
|
+
child.emit("exit", 0);
|
|
377
|
+
}, 0);
|
|
378
|
+
return child;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const childRuntime = _runtimeFactory(_vfs!, {
|
|
382
|
+
cwd,
|
|
383
|
+
env,
|
|
384
|
+
onConsole: (method: string, consoleArgs: unknown[]) => {
|
|
385
|
+
const msg = consoleArgs.map(a => String(a)).join(" ");
|
|
386
|
+
if (method === "error" || method === "warn") {
|
|
387
|
+
child.stderr?.emit("data", msg + "\n");
|
|
388
|
+
} else {
|
|
389
|
+
child.stdout?.emit("data", msg + "\n");
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
onStdout: (data: string) => {
|
|
393
|
+
child.stdout?.emit("data", data);
|
|
394
|
+
},
|
|
395
|
+
onStderr: (data: string) => {
|
|
396
|
+
child.stderr?.emit("data", data);
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const childProc = childRuntime.getProcess();
|
|
401
|
+
childProc.argv = ["node", resolvedPath, ...args];
|
|
402
|
+
|
|
403
|
+
// Clone IPC messages to mimic real Node.js IPC behavior.
|
|
404
|
+
// Real IPC serializes messages across process boundaries.
|
|
405
|
+
// Without cloning, shared object references cause corruption.
|
|
406
|
+
const cloneIpcMessage = (msg: unknown): unknown => {
|
|
407
|
+
try {
|
|
408
|
+
return structuredClone(msg);
|
|
409
|
+
} catch {
|
|
410
|
+
return msg;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// Parent sends -> child process receives
|
|
415
|
+
child.send = (
|
|
416
|
+
message: unknown,
|
|
417
|
+
_callback?: (error: Error | null) => void,
|
|
418
|
+
): boolean => {
|
|
419
|
+
if (!child.connected) return false;
|
|
420
|
+
const cloned = cloneIpcMessage(message);
|
|
421
|
+
setTimeout(() => {
|
|
422
|
+
childProc.emit("message", cloned);
|
|
423
|
+
}, 0);
|
|
424
|
+
return true;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// Child sends -> parent ChildProcess receives (serialized + awaited)
|
|
428
|
+
// We must serialize AND await async handlers so birpc's async onCollected
|
|
429
|
+
// finishes before onTaskUpdate starts.
|
|
430
|
+
let ipcQueue: Promise<void> = Promise.resolve();
|
|
431
|
+
childProc.send = ((
|
|
432
|
+
message: unknown,
|
|
433
|
+
_callback?: (error: Error | null) => void,
|
|
434
|
+
): boolean => {
|
|
435
|
+
if (!child.connected) return false;
|
|
436
|
+
const cloned = cloneIpcMessage(message);
|
|
437
|
+
ipcQueue = ipcQueue.then(async () => {
|
|
438
|
+
const listeners = child.listeners("message");
|
|
439
|
+
for (const listener of listeners) {
|
|
440
|
+
try {
|
|
441
|
+
const result = (listener as (...args: unknown[]) => unknown)(cloned);
|
|
442
|
+
if (
|
|
443
|
+
result &&
|
|
444
|
+
typeof (result as Promise<unknown>).then === "function"
|
|
445
|
+
) {
|
|
446
|
+
await result;
|
|
447
|
+
}
|
|
448
|
+
} catch {
|
|
449
|
+
// Handler errors propagate through the test runner's own error handling
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
return true;
|
|
454
|
+
}) as any;
|
|
455
|
+
childProc.connected = true;
|
|
456
|
+
|
|
457
|
+
_activeForkedChildren++;
|
|
458
|
+
|
|
459
|
+
const notifyChildExit = () => {
|
|
460
|
+
_activeForkedChildren--;
|
|
461
|
+
_onForkedChildExit?.();
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
// Override child's process.exit
|
|
465
|
+
childProc.exit = ((code = 0) => {
|
|
466
|
+
child.exitCode = code;
|
|
467
|
+
child.connected = false;
|
|
468
|
+
childProc.connected = false;
|
|
469
|
+
childProc.emit("exit", code);
|
|
470
|
+
child.emit("exit", code, null);
|
|
471
|
+
child.emit("close", code, null);
|
|
472
|
+
notifyChildExit();
|
|
473
|
+
}) as (code?: number) => never;
|
|
474
|
+
|
|
475
|
+
// Override child's kill to disconnect
|
|
476
|
+
child.kill = (signal?: string): boolean => {
|
|
477
|
+
child.killed = true;
|
|
478
|
+
child.connected = false;
|
|
479
|
+
childProc.connected = false;
|
|
480
|
+
childProc.emit("exit", null, signal || "SIGTERM");
|
|
481
|
+
child.emit("exit", null, signal || "SIGTERM");
|
|
482
|
+
child.emit("close", null, signal || "SIGTERM");
|
|
483
|
+
notifyChildExit();
|
|
484
|
+
return true;
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
child.disconnect = (): void => {
|
|
488
|
+
child.connected = false;
|
|
489
|
+
childProc.connected = false;
|
|
490
|
+
child.emit("disconnect");
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// Run the module asynchronously
|
|
494
|
+
setTimeout(() => {
|
|
495
|
+
try {
|
|
496
|
+
childRuntime.runFile(resolvedPath);
|
|
497
|
+
} catch (error) {
|
|
498
|
+
if (
|
|
499
|
+
error instanceof Error &&
|
|
500
|
+
error.message.startsWith("Process exited with code")
|
|
501
|
+
) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
505
|
+
child.stderr?.emit("data", `Error in forked process: ${errorMsg}\n`);
|
|
506
|
+
child.exitCode = 1;
|
|
507
|
+
child.emit("error", error);
|
|
508
|
+
child.emit("exit", 1, null);
|
|
509
|
+
child.emit("close", 1, null);
|
|
510
|
+
notifyChildExit();
|
|
511
|
+
}
|
|
512
|
+
}, 0);
|
|
513
|
+
|
|
514
|
+
return child;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export default {
|
|
518
|
+
ChildProcess,
|
|
519
|
+
exec,
|
|
520
|
+
execSync,
|
|
521
|
+
spawn,
|
|
522
|
+
execFile,
|
|
523
|
+
execFileSync,
|
|
524
|
+
fork,
|
|
525
|
+
spawnSync,
|
|
526
|
+
initChildProcess,
|
|
527
|
+
setStreamingCallbacks,
|
|
528
|
+
clearStreamingCallbacks,
|
|
529
|
+
sendStdin,
|
|
530
|
+
setActiveProcessStdin,
|
|
531
|
+
getStreamingState,
|
|
532
|
+
getActiveForkedChildren,
|
|
533
|
+
setOnForkedChildExit,
|
|
534
|
+
patchDefineProperty,
|
|
535
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { EventEmitter } from "./events";
|
|
2
|
+
import type { MemFS } from "../memfs";
|
|
3
|
+
|
|
4
|
+
let _vfs: MemFS | null = null;
|
|
5
|
+
export function initChokidar(vfs: MemFS): void {
|
|
6
|
+
_vfs = vfs;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class FSWatcher extends EventEmitter {
|
|
10
|
+
closed = false;
|
|
11
|
+
private watchers: { close(): void }[] = [];
|
|
12
|
+
|
|
13
|
+
add(paths: string | string[]): this {
|
|
14
|
+
if (!_vfs) return this;
|
|
15
|
+
const pathList = Array.isArray(paths) ? paths : [paths];
|
|
16
|
+
for (const p of pathList) {
|
|
17
|
+
try {
|
|
18
|
+
const watcher = _vfs.watch(
|
|
19
|
+
p,
|
|
20
|
+
{ recursive: true },
|
|
21
|
+
(eventType, filename) => {
|
|
22
|
+
if (this.closed) return;
|
|
23
|
+
this.emit("change", `${p}/${filename}`);
|
|
24
|
+
this.emit("all", eventType, `${p}/${filename}`);
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
this.watchers.push(watcher);
|
|
28
|
+
} catch {}
|
|
29
|
+
}
|
|
30
|
+
setTimeout(() => this.emit("ready"), 0);
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
unwatch(_paths: string | string[]): this {
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
close(): Promise<void> {
|
|
38
|
+
this.closed = true;
|
|
39
|
+
this.watchers.forEach(w => w.close());
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
}
|
|
42
|
+
getWatched(): Record<string, string[]> {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function watch(paths: string | string[], _options?: unknown): FSWatcher {
|
|
48
|
+
const watcher = new FSWatcher();
|
|
49
|
+
return watcher.add(paths);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default { watch, FSWatcher, initChokidar };
|