@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.
Files changed (152) hide show
  1. package/dist/browser-bundle.d.ts +40 -0
  2. package/dist/builtins.d.ts +22 -0
  3. package/dist/code-transform.d.ts +7 -0
  4. package/dist/config/cdn.d.ts +13 -0
  5. package/dist/container.d.ts +101 -0
  6. package/dist/dev-server.d.ts +69 -0
  7. package/dist/errors.d.ts +19 -0
  8. package/dist/frameworks/code-transforms.d.ts +32 -0
  9. package/dist/frameworks/next-api-handler.d.ts +72 -0
  10. package/dist/frameworks/next-dev-server.d.ts +141 -0
  11. package/dist/frameworks/next-html-generator.d.ts +36 -0
  12. package/dist/frameworks/next-route-resolver.d.ts +19 -0
  13. package/dist/frameworks/next-shims.d.ts +78 -0
  14. package/dist/frameworks/remix-dev-server.d.ts +47 -0
  15. package/dist/frameworks/sveltekit-dev-server.d.ts +43 -0
  16. package/dist/frameworks/vite-dev-server.d.ts +50 -0
  17. package/dist/fs-errors.d.ts +36 -0
  18. package/dist/index.cjs +14916 -0
  19. package/dist/index.cjs.map +1 -0
  20. package/dist/index.d.ts +61 -0
  21. package/dist/index.mjs +14898 -0
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/kernel.d.ts +48 -0
  24. package/dist/memfs.d.ts +144 -0
  25. package/dist/metrics.d.ts +78 -0
  26. package/dist/module-resolver.d.ts +60 -0
  27. package/dist/network-interceptor.d.ts +71 -0
  28. package/dist/npm/cache.d.ts +76 -0
  29. package/dist/npm/index.d.ts +60 -0
  30. package/dist/npm/lockfile-reader.d.ts +32 -0
  31. package/dist/npm/pnpm.d.ts +18 -0
  32. package/dist/npm/registry.d.ts +45 -0
  33. package/dist/npm/resolver.d.ts +39 -0
  34. package/dist/npm/sync-installer.d.ts +18 -0
  35. package/dist/npm/tarball.d.ts +4 -0
  36. package/dist/npm/workspaces.d.ts +46 -0
  37. package/dist/persistence.d.ts +94 -0
  38. package/dist/plugin.d.ts +156 -0
  39. package/dist/polyfills/assert.d.ts +30 -0
  40. package/dist/polyfills/child_process.d.ts +116 -0
  41. package/dist/polyfills/chokidar.d.ts +18 -0
  42. package/dist/polyfills/crypto.d.ts +49 -0
  43. package/dist/polyfills/events.d.ts +28 -0
  44. package/dist/polyfills/fs.d.ts +82 -0
  45. package/dist/polyfills/http.d.ts +147 -0
  46. package/dist/polyfills/module.d.ts +29 -0
  47. package/dist/polyfills/net.d.ts +53 -0
  48. package/dist/polyfills/os.d.ts +91 -0
  49. package/dist/polyfills/path.d.ts +96 -0
  50. package/dist/polyfills/perf_hooks.d.ts +21 -0
  51. package/dist/polyfills/process.d.ts +99 -0
  52. package/dist/polyfills/querystring.d.ts +15 -0
  53. package/dist/polyfills/readdirp.d.ts +18 -0
  54. package/dist/polyfills/readline.d.ts +32 -0
  55. package/dist/polyfills/stream.d.ts +106 -0
  56. package/dist/polyfills/stubs.d.ts +737 -0
  57. package/dist/polyfills/tty.d.ts +25 -0
  58. package/dist/polyfills/url.d.ts +41 -0
  59. package/dist/polyfills/util.d.ts +61 -0
  60. package/dist/polyfills/v8.d.ts +43 -0
  61. package/dist/polyfills/vm.d.ts +76 -0
  62. package/dist/polyfills/worker-threads.d.ts +77 -0
  63. package/dist/polyfills/ws.d.ts +32 -0
  64. package/dist/polyfills/zlib.d.ts +87 -0
  65. package/dist/runtime-helpers.d.ts +4 -0
  66. package/dist/runtime-interface.d.ts +39 -0
  67. package/dist/sandbox.d.ts +69 -0
  68. package/dist/server-bridge.d.ts +55 -0
  69. package/dist/shell-commands.d.ts +2 -0
  70. package/dist/shell.d.ts +101 -0
  71. package/dist/transpiler.d.ts +47 -0
  72. package/dist/type-checker.d.ts +57 -0
  73. package/dist/types/package-json.d.ts +17 -0
  74. package/dist/utils/binary-encoding.d.ts +4 -0
  75. package/dist/utils/hash.d.ts +6 -0
  76. package/dist/utils/safe-path.d.ts +6 -0
  77. package/dist/worker-runtime.d.ts +34 -0
  78. package/package.json +59 -0
  79. package/src/browser-bundle.ts +498 -0
  80. package/src/builtins.ts +222 -0
  81. package/src/code-transform.ts +183 -0
  82. package/src/config/cdn.ts +17 -0
  83. package/src/container.ts +343 -0
  84. package/src/dev-server.ts +322 -0
  85. package/src/errors.ts +604 -0
  86. package/src/frameworks/code-transforms.ts +667 -0
  87. package/src/frameworks/next-api-handler.ts +366 -0
  88. package/src/frameworks/next-dev-server.ts +1252 -0
  89. package/src/frameworks/next-html-generator.ts +585 -0
  90. package/src/frameworks/next-route-resolver.ts +521 -0
  91. package/src/frameworks/next-shims.ts +1084 -0
  92. package/src/frameworks/remix-dev-server.ts +163 -0
  93. package/src/frameworks/sveltekit-dev-server.ts +197 -0
  94. package/src/frameworks/vite-dev-server.ts +370 -0
  95. package/src/fs-errors.ts +118 -0
  96. package/src/index.ts +188 -0
  97. package/src/kernel.ts +381 -0
  98. package/src/memfs.ts +1006 -0
  99. package/src/metrics.ts +140 -0
  100. package/src/module-resolver.ts +511 -0
  101. package/src/network-interceptor.ts +143 -0
  102. package/src/npm/cache.ts +172 -0
  103. package/src/npm/index.ts +377 -0
  104. package/src/npm/lockfile-reader.ts +105 -0
  105. package/src/npm/pnpm.ts +108 -0
  106. package/src/npm/registry.ts +120 -0
  107. package/src/npm/resolver.ts +339 -0
  108. package/src/npm/sync-installer.ts +217 -0
  109. package/src/npm/tarball.ts +136 -0
  110. package/src/npm/workspaces.ts +255 -0
  111. package/src/persistence.ts +235 -0
  112. package/src/plugin.ts +293 -0
  113. package/src/polyfills/assert.ts +164 -0
  114. package/src/polyfills/child_process.ts +535 -0
  115. package/src/polyfills/chokidar.ts +52 -0
  116. package/src/polyfills/crypto.ts +433 -0
  117. package/src/polyfills/events.ts +178 -0
  118. package/src/polyfills/fs.ts +297 -0
  119. package/src/polyfills/http.ts +478 -0
  120. package/src/polyfills/module.ts +97 -0
  121. package/src/polyfills/net.ts +123 -0
  122. package/src/polyfills/os.ts +108 -0
  123. package/src/polyfills/path.ts +169 -0
  124. package/src/polyfills/perf_hooks.ts +30 -0
  125. package/src/polyfills/process.ts +349 -0
  126. package/src/polyfills/querystring.ts +66 -0
  127. package/src/polyfills/readdirp.ts +72 -0
  128. package/src/polyfills/readline.ts +80 -0
  129. package/src/polyfills/stream.ts +610 -0
  130. package/src/polyfills/stubs.ts +600 -0
  131. package/src/polyfills/tty.ts +43 -0
  132. package/src/polyfills/url.ts +97 -0
  133. package/src/polyfills/util.ts +173 -0
  134. package/src/polyfills/v8.ts +62 -0
  135. package/src/polyfills/vm.ts +111 -0
  136. package/src/polyfills/worker-threads.ts +189 -0
  137. package/src/polyfills/ws.ts +73 -0
  138. package/src/polyfills/zlib.ts +244 -0
  139. package/src/runtime-helpers.ts +83 -0
  140. package/src/runtime-interface.ts +46 -0
  141. package/src/sandbox.ts +178 -0
  142. package/src/server-bridge.ts +473 -0
  143. package/src/service-worker.ts +153 -0
  144. package/src/shell-commands.ts +708 -0
  145. package/src/shell.ts +795 -0
  146. package/src/transpiler.ts +282 -0
  147. package/src/type-checker.ts +241 -0
  148. package/src/types/package-json.ts +17 -0
  149. package/src/utils/binary-encoding.ts +38 -0
  150. package/src/utils/hash.ts +24 -0
  151. package/src/utils/safe-path.ts +38 -0
  152. package/src/worker-runtime.ts +42 -0
@@ -0,0 +1,708 @@
1
+ import * as pathShim from "./polyfills/path";
2
+ import { Kernel } from "./kernel";
3
+ import { bundle, initTranspiler } from "./transpiler";
4
+ import {
5
+ getStreamingState,
6
+ setActiveProcessStdin,
7
+ getActiveForkedChildren,
8
+ setOnForkedChildExit,
9
+ } from "./polyfills/child_process";
10
+ import type { ShellContext, ShellResult, CommandHandler } from "./shell";
11
+
12
+ const ok = (stdout = ""): ShellResult => ({ stdout, stderr: "", exitCode: 0 });
13
+ const fail = (stderr: string, code = 1): ShellResult => ({
14
+ stdout: "",
15
+ stderr,
16
+ exitCode: code,
17
+ });
18
+
19
+ function echoCmd(args: string[]): ShellResult {
20
+ return ok(args.join(" ") + "\n");
21
+ }
22
+
23
+ function cdCmd(args: string[], ctx: ShellContext): ShellResult {
24
+ const target = args[0] || ctx.env.HOME || "/";
25
+ const resolved = pathShim.isAbsolute(target)
26
+ ? target
27
+ : pathShim.resolve(ctx.cwd, target);
28
+ if (
29
+ ctx.vfs.existsSync(resolved) &&
30
+ ctx.vfs.statSync(resolved).isDirectory()
31
+ ) {
32
+ ctx.setCwd(resolved);
33
+ ctx.runtime.getProcess().chdir(resolved);
34
+ return ok();
35
+ }
36
+ return fail(`cd: ${target}: No such file or directory\n`);
37
+ }
38
+
39
+ function pwdCmd(_args: string[], ctx: ShellContext): ShellResult {
40
+ return ok(ctx.cwd + "\n");
41
+ }
42
+
43
+ function lsCmd(args: string[], ctx: ShellContext): ShellResult {
44
+ const positional = args.filter(a => !a.startsWith("-"));
45
+ const dir = positional[0]
46
+ ? pathShim.resolve(ctx.cwd, positional[0])
47
+ : ctx.cwd;
48
+ try {
49
+ const entries = ctx.vfs.readdirSync(dir);
50
+ const showAll =
51
+ args.includes("-a") || args.includes("-la") || args.includes("-al");
52
+ const filtered = showAll
53
+ ? entries
54
+ : entries.filter(e => !e.startsWith("."));
55
+ return ok(filtered.join("\n") + "\n");
56
+ } catch (e) {
57
+ return fail(`ls: cannot access '${dir}': ${(e as Error).message}\n`);
58
+ }
59
+ }
60
+
61
+ function catCmd(args: string[], ctx: ShellContext): ShellResult {
62
+ if (args.length === 0) {
63
+ // No file args: read from stdin (pipe data)
64
+ return ok(ctx.stdinData || "");
65
+ }
66
+ let out = "",
67
+ err = "",
68
+ code = 0;
69
+ for (const file of args) {
70
+ const p = pathShim.resolve(ctx.cwd, file);
71
+ try {
72
+ out += ctx.vfs.readFileSync(p, "utf8");
73
+ } catch {
74
+ err += `cat: ${file}: No such file or directory\n`;
75
+ code = 1;
76
+ }
77
+ }
78
+ return { stdout: out, stderr: err, exitCode: code };
79
+ }
80
+
81
+ function mkdirCmd(args: string[], ctx: ShellContext): ShellResult {
82
+ const recursive = args.includes("-p");
83
+ for (const dir of args.filter(a => !a.startsWith("-"))) {
84
+ ctx.vfs.mkdirSync(pathShim.resolve(ctx.cwd, dir), { recursive });
85
+ }
86
+ return ok();
87
+ }
88
+
89
+ function rmCmd(args: string[], ctx: ShellContext): ShellResult {
90
+ const recursive =
91
+ args.includes("-r") || args.includes("-rf") || args.includes("-fr");
92
+ const force =
93
+ args.includes("-f") || args.includes("-rf") || args.includes("-fr");
94
+ for (const file of args.filter(a => !a.startsWith("-"))) {
95
+ const p = pathShim.resolve(ctx.cwd, file);
96
+ try {
97
+ ctx.vfs.rmSync(p, { recursive, force });
98
+ } catch (e) {
99
+ if (!force) return fail(`rm: ${file}: ${(e as Error).message}\n`);
100
+ }
101
+ }
102
+ return ok();
103
+ }
104
+
105
+ function cpCmd(args: string[], ctx: ShellContext): ShellResult {
106
+ const recursive =
107
+ args.includes("-r") ||
108
+ args.includes("-R") ||
109
+ args.includes("-rp") ||
110
+ args.includes("-pr");
111
+ const positional = args.filter(a => !a.startsWith("-"));
112
+ if (positional.length < 2) return fail("cp: missing file operand\n");
113
+ const src = pathShim.resolve(ctx.cwd, positional[0]);
114
+ const dest = pathShim.resolve(ctx.cwd, positional[1]);
115
+
116
+ if (ctx.vfs.existsSync(src) && ctx.vfs.statSync(src).isDirectory()) {
117
+ if (!recursive)
118
+ return fail(
119
+ `cp: -r not specified; omitting directory '${positional[0]}'\n`,
120
+ );
121
+ copyDirRecursive(ctx.vfs, src, dest);
122
+ } else {
123
+ ctx.vfs.copyFileSync(src, dest);
124
+ }
125
+ return ok();
126
+ }
127
+
128
+ function copyDirRecursive(
129
+ vfs: import("./memfs").MemFS,
130
+ src: string,
131
+ dest: string,
132
+ ): void {
133
+ if (!vfs.existsSync(dest)) vfs.mkdirSync(dest, { recursive: true });
134
+ const entries = vfs.readdirSync(src);
135
+ for (const entry of entries) {
136
+ const srcPath = pathShim.join(src, entry);
137
+ const destPath = pathShim.join(dest, entry);
138
+ const stat = vfs.statSync(srcPath);
139
+ if (stat.isDirectory()) {
140
+ copyDirRecursive(vfs, srcPath, destPath);
141
+ } else {
142
+ vfs.copyFileSync(srcPath, destPath);
143
+ }
144
+ }
145
+ }
146
+
147
+ function mvCmd(args: string[], ctx: ShellContext): ShellResult {
148
+ if (args.length < 2) return fail("mv: missing file operand\n");
149
+ ctx.vfs.renameSync(
150
+ pathShim.resolve(ctx.cwd, args[0]),
151
+ pathShim.resolve(ctx.cwd, args[1]),
152
+ );
153
+ return ok();
154
+ }
155
+
156
+ function touchCmd(args: string[], ctx: ShellContext): ShellResult {
157
+ for (const file of args.filter(a => !a.startsWith("-"))) {
158
+ const p = pathShim.resolve(ctx.cwd, file);
159
+ if (ctx.vfs.existsSync(p)) {
160
+ // Update mtime on existing file
161
+ const now = new Date();
162
+ ctx.vfs.utimesSync(p, now, now);
163
+ } else {
164
+ ctx.vfs.writeFileSync(p, "");
165
+ }
166
+ }
167
+ return ok();
168
+ }
169
+
170
+ function whichCmd(args: string[], ctx: ShellContext): ShellResult {
171
+ const target = args[0];
172
+ if (!target) return fail("which: missing argument\n");
173
+ if (["node", "npm", "npx", "pnpm"].includes(target))
174
+ return ok(`/usr/local/bin/${target}\n`);
175
+
176
+ // Search PATH directories
177
+ const pathDirs = (ctx.env.PATH || "").split(":").filter(Boolean);
178
+ for (const dir of pathDirs) {
179
+ const candidate = pathShim.join(dir, target);
180
+ if (ctx.vfs.existsSync(candidate)) return ok(candidate + "\n");
181
+ }
182
+
183
+ return fail(`which: no ${target} in (${ctx.env.PATH || ""})\n`);
184
+ }
185
+
186
+ async function envCmd(args: string[], ctx: ShellContext): Promise<ShellResult> {
187
+ // Parse VAR=val prefixes; if remaining args form a command, execute with modified env
188
+ const envOverrides: Record<string, string> = {};
189
+ let i = 0;
190
+ while (i < args.length && args[i].includes("=")) {
191
+ const eqIdx = args[i].indexOf("=");
192
+ envOverrides[args[i].slice(0, eqIdx)] = args[i].slice(eqIdx + 1);
193
+ i++;
194
+ }
195
+ const remaining = args.slice(i);
196
+ if (remaining.length > 0) {
197
+ // Execute remaining command with modified env
198
+ const savedEnv = { ...ctx.env };
199
+ Object.assign(ctx.env, envOverrides);
200
+ try {
201
+ return await ctx.exec(remaining.join(" "));
202
+ } finally {
203
+ // Restore env
204
+ Object.keys(envOverrides).forEach(k => {
205
+ if (k in savedEnv) ctx.env[k] = savedEnv[k];
206
+ else delete ctx.env[k];
207
+ });
208
+ }
209
+ }
210
+ // No command: just print environment
211
+ const merged = { ...ctx.env, ...envOverrides };
212
+ return ok(
213
+ Object.entries(merged)
214
+ .map(([k, v]) => `${k}=${v}`)
215
+ .join("\n") + "\n",
216
+ );
217
+ }
218
+
219
+ function exportCmd(args: string[], ctx: ShellContext): ShellResult {
220
+ const validName = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
221
+ for (const arg of args) {
222
+ const [k, ...rest] = arg.split("=");
223
+ if (!validName.test(k)) {
224
+ return {
225
+ stdout: "",
226
+ stderr: `export: '${k}': not a valid identifier\n`,
227
+ exitCode: 1,
228
+ };
229
+ }
230
+ if (rest.length) ctx.env[k] = rest.join("=");
231
+ }
232
+ return ok();
233
+ }
234
+
235
+ async function nodeCmd(
236
+ args: string[],
237
+ ctx: ShellContext,
238
+ ): Promise<ShellResult> {
239
+ if (args.includes("-e") || args.includes("--eval")) {
240
+ const idx =
241
+ args.indexOf("-e") >= 0 ? args.indexOf("-e") : args.indexOf("--eval");
242
+ const code = args[idx + 1];
243
+ if (code) {
244
+ try {
245
+ ctx.runtime.execute(code);
246
+ } catch (e) {
247
+ return fail((e as Error).message + "\n");
248
+ }
249
+ }
250
+ return ok();
251
+ }
252
+
253
+ const scriptPath = args[0];
254
+ if (!scriptPath) return ok();
255
+
256
+ const resolvedPath = pathShim.resolve(ctx.cwd, scriptPath);
257
+ if (!ctx.vfs.existsSync(resolvedPath)) {
258
+ return fail(`Error: Cannot find module '${resolvedPath}'\n`);
259
+ }
260
+
261
+ let stdout = "";
262
+ let stderr = "";
263
+ let exitCalled = false;
264
+ let exitCode = 0;
265
+ let syncExecution = true;
266
+ let exitResolve: ((code: number) => void) | null = null;
267
+ const exitPromise = new Promise<number>(resolve => {
268
+ exitResolve = resolve;
269
+ });
270
+
271
+ const { streamStdout, streamStderr, abortSignal } = getStreamingState();
272
+
273
+ const appendStdout = (data: string) => {
274
+ stdout += data;
275
+ if (streamStdout) streamStdout(data);
276
+ };
277
+ const appendStderr = (data: string) => {
278
+ stderr += data;
279
+ if (streamStderr) streamStderr(data);
280
+ };
281
+
282
+ // Pass the plugin registry from the parent runtime so plugins (onResolve,
283
+ // onLoad, onTransform) are available inside `node` sub-processes.
284
+ const parentPlugins = ctx.runtime.pluginRegistry;
285
+ const runtime = new Kernel(
286
+ ctx.vfs,
287
+ {
288
+ cwd: ctx.cwd,
289
+ env: ctx.env,
290
+ onConsole: (method: string, consoleArgs: unknown[]) => {
291
+ const msg = consoleArgs.map(a => String(a)).join(" ") + "\n";
292
+ if (method === "error") appendStderr(msg);
293
+ else appendStdout(msg);
294
+ },
295
+ onStdout: (data: string) => {
296
+ appendStdout(data);
297
+ },
298
+ onStderr: (data: string) => {
299
+ appendStderr(data);
300
+ },
301
+ },
302
+ parentPlugins,
303
+ );
304
+
305
+ const proc = runtime.getProcess();
306
+ proc.exit = ((code = 0) => {
307
+ if (!exitCalled) {
308
+ exitCalled = true;
309
+ exitCode = code;
310
+ proc.emit("exit", code);
311
+ exitResolve!(code);
312
+ }
313
+ if (syncExecution) throw new Error(`Process exited with code ${code}`);
314
+ }) as (code?: number) => never;
315
+
316
+ proc.argv = ["node", resolvedPath, ...args.slice(1)];
317
+
318
+ if (abortSignal) {
319
+ proc.stdout.isTTY = true;
320
+ proc.stderr.isTTY = true;
321
+ proc.stdin.isTTY = true;
322
+ (proc.stdin as any).setRawMode = () => proc.stdin;
323
+ setActiveProcessStdin(proc.stdin as any);
324
+ }
325
+
326
+ try {
327
+ await runtime.prepareFile(resolvedPath);
328
+ runtime.runFile(resolvedPath);
329
+ } catch (error) {
330
+ if (
331
+ error instanceof Error &&
332
+ error.message.startsWith("Process exited with code")
333
+ ) {
334
+ return { stdout, stderr, exitCode };
335
+ }
336
+ const errorMsg =
337
+ error instanceof Error
338
+ ? `${error.message}\n${error.stack || ""}`
339
+ : String(error);
340
+ return { stdout, stderr: stderr + `Error: ${errorMsg}\n`, exitCode: 1 };
341
+ } finally {
342
+ syncExecution = false;
343
+ }
344
+
345
+ if (exitCalled) return { stdout, stderr, exitCode };
346
+
347
+ await new Promise(r => setTimeout(r, 0));
348
+ if (exitCalled) return { stdout, stderr, exitCode };
349
+
350
+ if (
351
+ (stdout.length > 0 || stderr.length > 0) &&
352
+ getActiveForkedChildren() <= 0
353
+ ) {
354
+ return { stdout, stderr, exitCode: 0 };
355
+ }
356
+ if (
357
+ stdout.length === 0 &&
358
+ stderr.length === 0 &&
359
+ getActiveForkedChildren() <= 0
360
+ ) {
361
+ return { stdout, stderr, exitCode: 0 };
362
+ }
363
+
364
+ const addRejectionHandler = typeof globalThis.addEventListener === "function";
365
+ const rejectionHandler = (event: PromiseRejectionEvent) => {
366
+ const reason = event.reason;
367
+ if (
368
+ reason instanceof Error &&
369
+ reason.message.startsWith("Process exited with code")
370
+ ) {
371
+ event.preventDefault();
372
+ return;
373
+ }
374
+ const msg =
375
+ reason instanceof Error
376
+ ? `Unhandled rejection: ${reason.message}\n${reason.stack || ""}\n`
377
+ : `Unhandled rejection: ${String(reason)}\n`;
378
+ appendStderr(msg);
379
+ };
380
+ if (addRejectionHandler) {
381
+ globalThis.addEventListener("unhandledrejection", rejectionHandler);
382
+ }
383
+
384
+ let childrenExited = false;
385
+ const prevChildExitHandler = setOnForkedChildExit(null);
386
+ setOnForkedChildExit(() => {
387
+ if (getActiveForkedChildren() <= 0) childrenExited = true;
388
+ if (typeof prevChildExitHandler === "function") prevChildExitHandler();
389
+ });
390
+
391
+ try {
392
+ const MAX_TOTAL_MS = 60000;
393
+ const IDLE_TIMEOUT_MS = 500;
394
+ const POST_CHILD_EXIT_IDLE_MS = 100;
395
+ const CHECK_MS = 50;
396
+ const startTime = Date.now();
397
+ let lastOutputLen = stdout.length + stderr.length;
398
+ let idleMs = 0;
399
+ const isLongRunning = !!abortSignal;
400
+
401
+ while (!exitCalled) {
402
+ if (abortSignal?.aborted) break;
403
+ const raceResult = await Promise.race([
404
+ exitPromise.then(() => "exit" as const),
405
+ new Promise<"tick">(r => setTimeout(() => r("tick"), CHECK_MS)),
406
+ ]);
407
+ if (raceResult === "exit" || exitCalled) break;
408
+ if (abortSignal?.aborted) break;
409
+
410
+ const currentLen = stdout.length + stderr.length;
411
+ if (currentLen > lastOutputLen) {
412
+ lastOutputLen = currentLen;
413
+ idleMs = 0;
414
+ } else {
415
+ idleMs += CHECK_MS;
416
+ }
417
+ if (!isLongRunning) {
418
+ const effectiveIdle = childrenExited
419
+ ? POST_CHILD_EXIT_IDLE_MS
420
+ : IDLE_TIMEOUT_MS;
421
+ if (lastOutputLen > 0 && idleMs >= effectiveIdle) break;
422
+ }
423
+ if (!isLongRunning && Date.now() - startTime >= MAX_TOTAL_MS) break;
424
+ }
425
+
426
+ return { stdout, stderr, exitCode: exitCalled ? exitCode : 0 };
427
+ } finally {
428
+ setActiveProcessStdin(null);
429
+ setOnForkedChildExit(prevChildExitHandler as (() => void) | null);
430
+ if (addRejectionHandler) {
431
+ globalThis.removeEventListener("unhandledrejection", rejectionHandler);
432
+ }
433
+ }
434
+ }
435
+
436
+ async function npmCmd(args: string[], ctx: ShellContext): Promise<ShellResult> {
437
+ const subCmd = args[0] || "help";
438
+ switch (subCmd) {
439
+ case "install":
440
+ case "i":
441
+ case "add": {
442
+ const packages = args.slice(1).filter(a => !a.startsWith("-"));
443
+ const saveDev = args.includes("-D") || args.includes("--save-dev");
444
+ const noSave = args.includes("--no-save");
445
+ const lines: string[] = [];
446
+ if (packages.length > 0) {
447
+ const result = await ctx.pm.install(packages, {
448
+ save: !noSave && !saveDev,
449
+ saveDev,
450
+ onProgress: msg => {
451
+ lines.push(msg);
452
+ ctx.write?.(msg + "\n");
453
+ },
454
+ });
455
+ const summary = `added ${result.added.length} packages`;
456
+ lines.push(summary);
457
+ ctx.write?.(summary + "\n");
458
+ } else {
459
+ const result = await ctx.pm.installFromPackageJson({
460
+ onProgress: msg => {
461
+ lines.push(msg);
462
+ ctx.write?.(msg + "\n");
463
+ },
464
+ });
465
+ const summary = `added ${result.added.length} packages`;
466
+ lines.push(summary);
467
+ ctx.write?.(summary + "\n");
468
+ }
469
+ return ok(lines.join("\n") + "\n");
470
+ }
471
+ case "run":
472
+ case "run-script": {
473
+ const scriptName = args[1];
474
+ if (!scriptName) return fail("npm run: missing script name\n");
475
+ return runNpmScript(scriptName, ctx);
476
+ }
477
+ case "test":
478
+ case "t":
479
+ case "tst":
480
+ return runNpmScript("test", ctx);
481
+ case "start":
482
+ return runNpmScript("start", ctx);
483
+ case "stop":
484
+ return runNpmScript("stop", ctx);
485
+ case "ls":
486
+ case "list":
487
+ return ok(ctx.pm.list().join("\n") + "\n");
488
+ case "init": {
489
+ const pkgJson = {
490
+ name: pathShim.basename(ctx.cwd),
491
+ version: "1.0.0",
492
+ description: "",
493
+ main: "index.js",
494
+ scripts: { test: 'echo "Error: no test specified" && exit 1' },
495
+ keywords: [],
496
+ author: "",
497
+ license: "ISC",
498
+ };
499
+ ctx.vfs.writeFileSync(
500
+ pathShim.join(ctx.cwd, "package.json"),
501
+ JSON.stringify(pkgJson, null, 2),
502
+ );
503
+ return ok("Created package.json\n");
504
+ }
505
+ default:
506
+ return fail(`npm ${subCmd}: unknown command\n`);
507
+ }
508
+ }
509
+
510
+ async function runNpmScript(
511
+ scriptName: string,
512
+ ctx: ShellContext,
513
+ ): Promise<ShellResult> {
514
+ const pkgJsonPath = pathShim.join(ctx.cwd, "package.json");
515
+ if (!ctx.vfs.existsSync(pkgJsonPath))
516
+ return fail("npm run: no package.json found\n");
517
+ const pkgJson = JSON.parse(ctx.vfs.readFileSync(pkgJsonPath, "utf8"));
518
+ const script = pkgJson.scripts?.[scriptName];
519
+ if (!script) return fail(`npm run: script "${scriptName}" not found\n`);
520
+ return ctx.exec(script);
521
+ }
522
+
523
+ async function npxCmd(args: string[], ctx: ShellContext): Promise<ShellResult> {
524
+ const pkgName = args[0];
525
+ if (!pkgName) return fail("npx: missing package name\n");
526
+ await ctx.pm.install(pkgName);
527
+ const binPath = pathShim.join("/node_modules/.bin", pkgName);
528
+ if (ctx.vfs.existsSync(binPath)) {
529
+ try {
530
+ ctx.runtime.runFile(binPath);
531
+ } catch (e) {
532
+ return fail((e as Error).message + "\n");
533
+ }
534
+ }
535
+ return ok();
536
+ }
537
+
538
+ async function pnpmCmd(
539
+ args: string[],
540
+ ctx: ShellContext,
541
+ ): Promise<ShellResult> {
542
+ const pm = ctx.pnpmPm;
543
+ if (!pm)
544
+ return fail(
545
+ 'pnpm: not configured – set packageManager to "pnpm" in container options\n',
546
+ );
547
+
548
+ const subCmd = args[0] || "help";
549
+ switch (subCmd) {
550
+ case "install":
551
+ case "i":
552
+ case "add": {
553
+ const packages = args.slice(1).filter(a => !a.startsWith("-"));
554
+ const saveDev = args.includes("-D") || args.includes("--save-dev");
555
+ const noSave = args.includes("--no-save");
556
+ const lines: string[] = [];
557
+ if (packages.length > 0) {
558
+ const result = await pm.install(packages, {
559
+ save: !noSave && !saveDev,
560
+ saveDev,
561
+ onProgress: msg => {
562
+ lines.push(msg);
563
+ ctx.write?.(msg + "\n");
564
+ },
565
+ });
566
+ const summary = `added ${result.added.length} packages`;
567
+ lines.push(summary);
568
+ ctx.write?.(summary + "\n");
569
+ } else {
570
+ const result = await pm.installFromPackageJson({
571
+ onProgress: msg => {
572
+ lines.push(msg);
573
+ ctx.write?.(msg + "\n");
574
+ },
575
+ });
576
+ const summary = `added ${result.added.length} packages`;
577
+ lines.push(summary);
578
+ ctx.write?.(summary + "\n");
579
+ }
580
+ return ok(lines.join("\n") + "\n");
581
+ }
582
+ case "run": {
583
+ const scriptName = args[1];
584
+ if (!scriptName) return fail("pnpm run: missing script name\n");
585
+ const pkgJsonPath = pathShim.join(ctx.cwd, "package.json");
586
+ if (!ctx.vfs.existsSync(pkgJsonPath))
587
+ return fail("pnpm run: no package.json found\n");
588
+ const pkgJson = JSON.parse(ctx.vfs.readFileSync(pkgJsonPath, "utf8"));
589
+ const script = pkgJson.scripts?.[scriptName];
590
+ if (!script) return fail(`pnpm run: script "${scriptName}" not found\n`);
591
+ return ctx.exec(script);
592
+ }
593
+ case "ls":
594
+ case "list":
595
+ return ok(pm.list().join("\n") + "\n");
596
+ default:
597
+ return fail(`pnpm ${subCmd}: unknown command\n`);
598
+ }
599
+ }
600
+
601
+ async function esbuildCmd(
602
+ args: string[],
603
+ ctx: ShellContext,
604
+ ): Promise<ShellResult> {
605
+ const entryPoints: string[] = [];
606
+ let outfile: string | undefined;
607
+ let doBundle = false;
608
+ let format: "esm" | "cjs" | "iife" = "esm";
609
+ let platform: "browser" | "node" | "neutral" = "browser";
610
+ let minify = false;
611
+ const external: string[] = [];
612
+ const loaderOverrides: Record<string, string> = {};
613
+
614
+ for (let i = 0; i < args.length; i++) {
615
+ const arg = args[i];
616
+ if (arg === "--bundle") {
617
+ doBundle = true;
618
+ continue;
619
+ }
620
+ if (arg === "--minify") {
621
+ minify = true;
622
+ continue;
623
+ }
624
+ if (arg.startsWith("--outfile=")) {
625
+ outfile = arg.slice("--outfile=".length);
626
+ continue;
627
+ }
628
+ if (arg.startsWith("--format=")) {
629
+ format = arg.slice("--format=".length) as typeof format;
630
+ continue;
631
+ }
632
+ if (arg.startsWith("--platform=")) {
633
+ platform = arg.slice("--platform=".length) as typeof platform;
634
+ continue;
635
+ }
636
+ if (arg.startsWith("--external:")) {
637
+ external.push(arg.slice("--external:".length));
638
+ continue;
639
+ }
640
+ if (arg.startsWith("--loader:")) {
641
+ const rest = arg.slice("--loader:".length);
642
+ const eqIdx = rest.indexOf("=");
643
+ if (eqIdx > 0)
644
+ loaderOverrides[rest.slice(0, eqIdx)] = rest.slice(eqIdx + 1);
645
+ continue;
646
+ }
647
+ if (!arg.startsWith("-")) entryPoints.push(arg);
648
+ }
649
+
650
+ if (entryPoints.length === 0)
651
+ return fail("esbuild: no entry points specified\n");
652
+
653
+ const entry = pathShim.resolve(ctx.cwd, entryPoints[0]);
654
+ if (!ctx.vfs.existsSync(entry))
655
+ return fail(`esbuild: entry not found: ${entry}\n`);
656
+
657
+ const resolvedOutfile = outfile
658
+ ? pathShim.resolve(ctx.cwd, outfile)
659
+ : undefined;
660
+
661
+ try {
662
+ await initTranspiler();
663
+ const result = await bundle(ctx.vfs, {
664
+ entryPoint: entry,
665
+ format,
666
+ platform,
667
+ minify,
668
+ external,
669
+ outfile: resolvedOutfile,
670
+ loader: loaderOverrides,
671
+ });
672
+
673
+ if (result.errors.length > 0) {
674
+ return fail(
675
+ `esbuild errors:\n${result.errors.map(e => e.text).join("\n")}\n`,
676
+ );
677
+ }
678
+ return resolvedOutfile ? ok() : ok(result.code);
679
+ } catch (e) {
680
+ return fail(`esbuild: ${(e as Error).message}\n`);
681
+ }
682
+ }
683
+
684
+ export function createDefaultCommands(): Map<string, CommandHandler> {
685
+ const cmds = new Map<string, CommandHandler>();
686
+ cmds.set("echo", echoCmd);
687
+ cmds.set("cd", cdCmd);
688
+ cmds.set("pwd", pwdCmd);
689
+ cmds.set("ls", lsCmd);
690
+ cmds.set("cat", catCmd);
691
+ cmds.set("mkdir", mkdirCmd);
692
+ cmds.set("rm", rmCmd);
693
+ cmds.set("cp", cpCmd);
694
+ cmds.set("mv", mvCmd);
695
+ cmds.set("touch", touchCmd);
696
+ cmds.set("which", whichCmd);
697
+ cmds.set("env", (args, ctx) => envCmd(args, ctx));
698
+ cmds.set("printenv", (args, ctx) => envCmd(args, ctx));
699
+ cmds.set("export", exportCmd);
700
+ cmds.set("true", () => ok());
701
+ cmds.set("false", () => fail("", 1));
702
+ cmds.set("node", nodeCmd);
703
+ cmds.set("npm", (args, ctx) => npmCmd(args, ctx));
704
+ cmds.set("npx", (args, ctx) => npxCmd(args, ctx));
705
+ cmds.set("pnpm", (args, ctx) => pnpmCmd(args, ctx));
706
+ cmds.set("esbuild", (args, ctx) => esbuildCmd(args, ctx));
707
+ return cmds;
708
+ }