@nimbus-sh/core 0.1.0 → 0.2.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 (60) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/runtime/bash-runner.d.ts +63 -0
  5. package/dist/runtime/bash-runner.d.ts.map +1 -0
  6. package/dist/runtime/bash-runner.generated.d.ts +14 -0
  7. package/dist/runtime/bash-runner.generated.d.ts.map +1 -0
  8. package/dist/runtime/bash-runner.generated.js +13 -0
  9. package/dist/runtime/bash-runner.js +290 -0
  10. package/dist/runtime/cpython-runner.d.ts +86 -0
  11. package/dist/runtime/cpython-runner.d.ts.map +1 -0
  12. package/dist/runtime/cpython-runner.js +425 -0
  13. package/dist/runtime/facet-host.d.ts +159 -0
  14. package/dist/runtime/facet-host.d.ts.map +1 -0
  15. package/dist/runtime/facet-host.js +22 -0
  16. package/dist/runtime/installed-runtimes.d.ts +99 -0
  17. package/dist/runtime/installed-runtimes.d.ts.map +1 -0
  18. package/dist/runtime/installed-runtimes.js +162 -0
  19. package/dist/runtime/local-facet-host.d.ts +38 -0
  20. package/dist/runtime/local-facet-host.d.ts.map +1 -0
  21. package/dist/runtime/local-facet-host.js +171 -0
  22. package/dist/runtime/python-pip.d.ts +38 -0
  23. package/dist/runtime/python-pip.d.ts.map +1 -0
  24. package/dist/runtime/python-pip.js +1063 -0
  25. package/dist/runtime/runtime-manifest.d.ts +86 -0
  26. package/dist/runtime/runtime-manifest.d.ts.map +1 -0
  27. package/dist/runtime/runtime-manifest.js +72 -0
  28. package/dist/runtime/runtime-registry.d.ts +161 -0
  29. package/dist/runtime/runtime-registry.d.ts.map +1 -0
  30. package/dist/runtime/runtime-registry.js +363 -0
  31. package/dist/runtime/vfs-snapshot.d.ts.map +1 -1
  32. package/dist/runtime/vfs-snapshot.js +15 -1
  33. package/dist/runtime/vfs-supervisor.d.ts +22 -0
  34. package/dist/runtime/vfs-supervisor.d.ts.map +1 -0
  35. package/dist/runtime/vfs-supervisor.js +65 -0
  36. package/dist/runtime/virtual-socket-kernel.generated.d.ts +14 -0
  37. package/dist/runtime/virtual-socket-kernel.generated.d.ts.map +1 -0
  38. package/dist/runtime/virtual-socket-kernel.generated.js +13 -0
  39. package/dist/runtime/wasm-runner.d.ts +80 -0
  40. package/dist/runtime/wasm-runner.d.ts.map +1 -0
  41. package/dist/runtime/wasm-runner.js +686 -0
  42. package/dist/workspace/nimbus-workspace.d.ts +16 -0
  43. package/dist/workspace/nimbus-workspace.d.ts.map +1 -1
  44. package/dist/workspace/nimbus-workspace.js +57 -2
  45. package/package.json +4 -2
  46. package/src/index.ts +9 -0
  47. package/src/runtime/bash-runner.generated.ts +14 -0
  48. package/src/runtime/bash-runner.ts +347 -0
  49. package/src/runtime/cpython-runner.ts +504 -0
  50. package/src/runtime/facet-host.ts +170 -0
  51. package/src/runtime/installed-runtimes.ts +235 -0
  52. package/src/runtime/local-facet-host.ts +205 -0
  53. package/src/runtime/python-pip.ts +1211 -0
  54. package/src/runtime/runtime-manifest.ts +155 -0
  55. package/src/runtime/runtime-registry.ts +510 -0
  56. package/src/runtime/vfs-snapshot.ts +15 -1
  57. package/src/runtime/vfs-supervisor.ts +67 -0
  58. package/src/runtime/virtual-socket-kernel.generated.ts +14 -0
  59. package/src/runtime/wasm-runner.ts +835 -0
  60. package/src/workspace/nimbus-workspace.ts +102 -3
package/dist/index.d.ts CHANGED
@@ -13,4 +13,6 @@
13
13
  export { NimbusWorkspace } from './workspace/nimbus-workspace.js';
14
14
  export type { NimbusWorkspaceOptions } from './workspace/nimbus-workspace.js';
15
15
  export type { SqlDatabase, SqlTransactions, SqlRow, SqlValue, TransactionHost, } from './runtime/os-contracts.js';
16
+ export { localFacetHost } from './runtime/local-facet-host.js';
17
+ export type { Facet, FacetBindings, FacetFn, FacetHost, FacetSpec, FacetSubmitOptions, } from './runtime/facet-host.js';
16
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,YAAY,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAC9E,YAAY,EACV,WAAW,EACX,eAAe,EACf,MAAM,EACN,QAAQ,EACR,eAAe,GAChB,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,YAAY,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAC9E,YAAY,EACV,WAAW,EACX,eAAe,EACf,MAAM,EACN,QAAQ,EACR,eAAe,GAChB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,YAAY,EACV,KAAK,EACL,aAAa,EACb,OAAO,EACP,SAAS,EACT,SAAS,EACT,kBAAkB,GACnB,MAAM,yBAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -11,3 +11,4 @@
11
11
  * depends on this package.
12
12
  */
13
13
  export { NimbusWorkspace } from './workspace/nimbus-workspace.js';
14
+ export { localFacetHost } from './runtime/local-facet-host.js';
@@ -0,0 +1,63 @@
1
+ /**
2
+ * bash-runner — real GNU bash 5.2.37 (wasm32-wasi, asyncified) in a
3
+ * dedicated facet.
4
+ *
5
+ * bash CANNOT run through the stock JSPI wasm-runner: the binary is
6
+ * asyncify-instrumented (fork/setjmp/blocking-pipe unwinds) and needs
7
+ * 15 `nimbus_proc` imports plus MULTIPLE instances per facet (fork).
8
+ * This runner embeds the proven multi-instance fork/pipe/exec/setjmp
9
+ * scheduler (packages/worker/wasm/bash/run-bash-fork.mjs — the local
10
+ * acid-test driver, itself a port of the PROVEN-LIVE fork M1/M2/M3
11
+ * mechanisms) as a facet preamble.
12
+ *
13
+ * Architecture (mirrors ruby-runner's facet dispatch):
14
+ * - bash.async.wasm + the coreutil exec targets ship through the facet
15
+ * host, which compiles them and exposes them on
16
+ * globalThis.__NIMBUS_WASM.
17
+ * - The preamble defines __bashBoot / __bashFeed. Boot instantiates
18
+ * bash, pumps the scheduler until the process tree exits or the
19
+ * root parks on a terminal stdin read; each feed delivers stdin
20
+ * bytes and pumps again. Facet state persists across submits on
21
+ * the warm isolate (same mechanism as __rubyInstance caching).
22
+ * - stdout/stderr accumulate per pump slice and stream back to the
23
+ * CommandContext; VFS writes come back as a WasiFsDiff on exit.
24
+ */
25
+ import type { RuntimeManifest } from './runtime-manifest.js';
26
+ import type { CredentialedVfs, SqliteVFS } from '../vfs/sqlite-vfs.js';
27
+ import type { FacetHost } from './facet-host.js';
28
+ import type { Command } from '../substrate/lifo/commands/types.js';
29
+ import type { BashSlice } from './bash/types.js';
30
+ type BashRunnerFactory = (manifest: RuntimeManifest, installRoot: string, binName: string, binKind: string | undefined) => Command;
31
+ export interface BashFacetSession {
32
+ readonly initial: BashSlice;
33
+ push(data: string, eof?: boolean): Promise<BashSlice>;
34
+ close(): Promise<void>;
35
+ }
36
+ export declare function createBashFacetSession(deps: {
37
+ facets: FacetHost;
38
+ vfs: CredentialedVfs;
39
+ manifest: RuntimeManifest;
40
+ installRoot: string;
41
+ argv: string[];
42
+ env: Record<string, string>;
43
+ cwd: string;
44
+ stdinData?: string;
45
+ stdinClosed: boolean;
46
+ stdinTty: boolean;
47
+ extraRoots?: string[];
48
+ }): Promise<BashFacetSession>;
49
+ export declare function makeBashRunnerFactory(deps: {
50
+ facets: FacetHost;
51
+ vfs: SqliteVFS;
52
+ }): BashRunnerFactory;
53
+ /**
54
+ * Source string injected as the facet `preamble`. The facet's scope evaluates
55
+ * it verbatim so `__bashBoot` / `__bashFeed` are in scope when the user fn
56
+ * runs. Self-contained — no closure captures, no imports.
57
+ *
58
+ * The scheduler itself lives in `bash/preamble.ts` as real TypeScript; the build
59
+ * bundles it into `bash-runner.generated.ts`.
60
+ */
61
+ export declare const BASH_RUNNER_PREAMBLE: string;
62
+ export {};
63
+ //# sourceMappingURL=bash-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash-runner.d.ts","sourceRoot":"","sources":["../../src/runtime/bash-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,KAAK,EAAS,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAsC,MAAM,qCAAqC,CAAC;AAGvG,OAAO,KAAK,EAA8B,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAM7E,KAAK,iBAAiB,GAAG,CACvB,QAAQ,EAAE,eAAe,EACzB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GAAG,SAAS,KACxB,OAAO,CAAC;AAsDb,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACtD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,MAAM,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,eAAe,CAAC;IACrB,QAAQ,EAAE,eAAe,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyG5B;AA6BD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,MAAM,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,SAAS,CAAC;CAChB,GAAG,iBAAiB,CAsFpB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAA6B,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * bash-runner.generated.ts — AUTO-GENERATED. DO NOT EDIT.
3
+ *
4
+ * Produced by scripts/bundle-facet-workers.mjs from:
5
+ * - @nimbus-sh/core src/runtime/bash/preamble.ts
6
+ *
7
+ * The facet-side bash scheduler as a self-contained IIFE that installs
8
+ * globalThis.__bashBoot / globalThis.__bashFeed. bash-runner.ts re-exports it
9
+ * as BASH_RUNNER_PREAMBLE and passes it as the facet preamble.
10
+ *
11
+ * Size: 58.56 KiB
12
+ */
13
+ export declare const BASH_RUNNER_BODY_SRC: string;
14
+ //# sourceMappingURL=bash-runner.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash-runner.generated.d.ts","sourceRoot":"","sources":["../../src/runtime/bash-runner.generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,oBAAoB,EAAE,MAA8i5D,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * bash-runner.generated.ts — AUTO-GENERATED. DO NOT EDIT.
3
+ *
4
+ * Produced by scripts/bundle-facet-workers.mjs from:
5
+ * - @nimbus-sh/core src/runtime/bash/preamble.ts
6
+ *
7
+ * The facet-side bash scheduler as a self-contained IIFE that installs
8
+ * globalThis.__bashBoot / globalThis.__bashFeed. bash-runner.ts re-exports it
9
+ * as BASH_RUNNER_PREAMBLE and passes it as the facet preamble.
10
+ *
11
+ * Size: 58.56 KiB
12
+ */
13
+ export const BASH_RUNNER_BODY_SRC = "\"use strict\";\n(() => {\n // ../core/src/runtime/bash/preamble.ts\n var PAGE = 65536, te = new TextEncoder(), td = new TextDecoder();\n var MAIN_SIZE = 8 << 20, SLOT_SIZE = 256 << 10, NSLOT = 32;\n var E = { ACCES: 2, BADF: 8, EXIST: 20, INVAL: 28, ISDIR: 31, LOOP: 32, NOENT: 44, NOSYS: 52, NOTDIR: 54, NOTEMPTY: 55, PERM: 63, SPIPE: 70 };\n var CLOCK_REALTIME = 0, CLOCK_MONOTONIC = 1, CLOCK_PROCESS_CPUTIME = 2, CLOCK_THREAD_CPUTIME = 3;\n function realtimeNs() {\n return BigInt(Date.now()) * 1000000n;\n }\n function monotonicNs() {\n const ms = typeof performance !== \"undefined\" && performance.now ? performance.now() : Date.now();\n return BigInt(Math.floor(ms * 1e3)) * 1000n;\n }\n function clockNs(id) {\n if (id === CLOCK_REALTIME) return realtimeNs();\n if (id === CLOCK_MONOTONIC || id === CLOCK_PROCESS_CPUTIME || id === CLOCK_THREAD_CPUTIME) return monotonicNs();\n return null;\n }\n var Exit = class {\n constructor(c) {\n this.code = c;\n }\n };\n var S = null;\n function norm(p) {\n const parts = [];\n for (const seg of String(p).split(\"/\")) {\n if (!seg || seg === \".\") continue;\n if (seg === \"..\") {\n parts.pop();\n continue;\n }\n parts.push(seg);\n }\n return parts.join(\"/\");\n }\n function b64ToBytes(b64) {\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n }\n function bytesToB64(bytes) {\n let bin = \"\";\n const CH = 32768;\n for (let i = 0; i < bytes.length; i += CH) bin += String.fromCharCode.apply(null, bytes.subarray(i, i + CH));\n return btoa(bin);\n }\n function newSession(args) {\n const wasmTable = globalThis.__NIMBUS_WASM || {};\n const mod = wasmTable[\"bash.async.wasm\"];\n if (!mod) throw new Error(\"bash.async.wasm missing from __NIMBUS_WASM\");\n const coreutils = /* @__PURE__ */ new Map();\n for (const key of Object.keys(wasmTable)) {\n if (key.startsWith(\"cu_\") && key.endsWith(\".wasm\")) coreutils.set(key.slice(3, -5), wasmTable[key]);\n }\n const busybox = wasmTable[\"cu_busybox.wasm\"];\n if (busybox) for (const name of args.busyboxApplets || []) coreutils.set(name, busybox);\n const fs = {\n files: /* @__PURE__ */ new Map(),\n dirs: /* @__PURE__ */ new Set(),\n modes: /* @__PURE__ */ new Map(),\n written: /* @__PURE__ */ new Set(),\n deleted: /* @__PURE__ */ new Set(),\n dirsCreated: /* @__PURE__ */ new Set(),\n dirsDeleted: /* @__PURE__ */ new Set(),\n modesChanged: /* @__PURE__ */ new Map(),\n // vfsPath → mtime ns. The snapshot carries no times, so an inode the\n // session did not touch reports sessionNs: older than anything written\n // here, which is what makes 'is the target newer than its source' — the\n // question make(1) and every incremental build asks — answerable.\n times: /* @__PURE__ */ new Map(),\n sessionNs: realtimeNs(),\n // vfsPath → target string, stored verbatim per POSIX: a symlink is a dumb\n // string resolved at lookup time, not a pointer to an inode.\n symlinks: /* @__PURE__ */ new Map(),\n symlinksCreated: /* @__PURE__ */ new Map()\n };\n const snap = args.fsSnapshot || { files: {}, dirs: [], modes: {} };\n for (const [path, b64] of Object.entries(snap.files || {})) fs.files.set(norm(path), { bytes: b64ToBytes(b64) });\n for (const d of snap.dirs || []) fs.dirs.add(norm(d));\n for (const [path, m] of Object.entries(snap.modes || {})) fs.modes.set(norm(path), Number(m) & 7);\n for (const seed of [\"etc\", \"dev\", \"bin\", \"usr\", \"usr/bin\"]) {\n fs.dirs.add(seed);\n if (!fs.modes.has(seed)) fs.modes.set(seed, 5);\n }\n if (!fs.files.has(\"etc/nimbus.bashrc\")) {\n fs.files.set(\"etc/nimbus.bashrc\", { bytes: te.encode('command cd \"${NIMBUS_PWD:-/}\" 2>/dev/null\\n') });\n fs.modes.set(\"etc/nimbus.bashrc\", 4);\n }\n const home = norm((args.environ.find((e) => e.startsWith(\"HOME=\")) || \"HOME=/home/user\").slice(5));\n if (args.stdinTty && home && !fs.files.has(home + \"/.bashrc\")) {\n fs.files.set(home + \"/.bashrc\", { bytes: te.encode(\". /etc/nimbus.bashrc\\n\") });\n if (!fs.modes.has(home + \"/.bashrc\")) fs.modes.set(home + \"/.bashrc\", 6);\n }\n const cwd = norm((args.environ.find((e) => e.startsWith(\"NIMBUS_PWD=\")) || \"NIMBUS_PWD=/\").slice(11));\n return {\n mod,\n coreutils,\n fs,\n cwd,\n argv: args.argv,\n environ: args.environ,\n stdinTty: !!args.stdinTty,\n stdin: { chunks: args.stdinData ? [te.encode(args.stdinData)] : [], queued: 0, closed: !!args.stdinClosed, waiters: [] },\n procs: /* @__PURE__ */ new Map(),\n pipes: /* @__PURE__ */ new Map(),\n runnable: [],\n exitStatus: /* @__PURE__ */ new Map(),\n waiters: [],\n pidNext: 100,\n pipeNext: 1,\n rootPid: 0,\n rootExit: null,\n steps: 0,\n out: \"\",\n err: \"\",\n missingWasi: /* @__PURE__ */ new Set(),\n stats: { instances: 0, memPeak: 0, mainHi: 0, slotHi: 0 },\n error: null\n };\n }\n function initStdinQueued(s) {\n s.stdin.queued = s.stdin.chunks.reduce((a, c) => a + c.length, 0);\n }\n function newPipe(s) {\n const id = s.pipeNext++;\n s.pipes.set(id, { chunks: [], queued: 0, readers: 1, writers: 1, readW: [] });\n return id;\n }\n function lowestFd(proc) {\n let fd = 0;\n while (proc.fds.has(fd) || fd === 3) fd++;\n return fd;\n }\n function bumpPipe(s, e, d) {\n const pp = s.pipes.get(e.pipeId);\n if (e.end === \"r\") pp.readers += d;\n else pp.writers += d;\n }\n function closeFd(s, proc, fd) {\n const e = proc.fds.get(fd);\n if (!e) return;\n proc.fds.delete(fd);\n if (e.kind === \"pipe\") {\n bumpPipe(s, e, -1);\n wakePipe(s, s.pipes.get(e.pipeId));\n }\n }\n function takeUpTo(src, max) {\n let need = max;\n const parts = [];\n while (need > 0 && src.chunks.length) {\n const ch = src.chunks[0];\n if (ch.length <= need) {\n parts.push(ch);\n need -= ch.length;\n src.chunks.shift();\n } else {\n parts.push(ch.subarray(0, need));\n src.chunks[0] = ch.subarray(need);\n need = 0;\n }\n }\n const total = max - need;\n src.queued -= total;\n const o = new Uint8Array(total);\n let x = 0;\n for (const p of parts) {\n o.set(p, x);\n x += p.length;\n }\n return o;\n }\n var EMPTY_IOV = { list: [], total: 0 };\n function readIovs(dv, iovs, n) {\n const list = [];\n let total = 0;\n for (let i = 0; i < n; i++) {\n const ptr = dv.getUint32(iovs + i * 8, true), len = dv.getUint32(iovs + i * 8 + 4, true);\n if (len > 0) {\n list.push({ ptr, len });\n total += len;\n }\n }\n return { list, total };\n }\n function scatter(u8, iov, bytes) {\n let off = 0;\n for (const b of iov.list) {\n if (off >= bytes.length) break;\n const n = Math.min(b.len, bytes.length - off);\n u8.set(bytes.subarray(off, off + n), b.ptr);\n off += n;\n }\n return off;\n }\n function wakePipe(s, pp) {\n while (pp.readW.length && (pp.queued > 0 || pp.writers === 0)) {\n const w = pp.readW.shift();\n const proc = w.proc;\n const req = proc.ctx.pipeReq;\n const bytes = pp.queued > 0 ? takeUpTo(pp, req.iov.total) : new Uint8Array(0);\n proc.pendingRead = { iov: req.iov, bytes, nreadPtr: req.nreadPtr, pollUserdata: req.pollUserdata };\n resumeProc(proc);\n }\n }\n function wakeStdin(s) {\n const st = s.stdin;\n while (st.waiters.length && (st.queued > 0 || st.closed)) {\n const w = st.waiters.shift();\n const proc = w.proc;\n const req = proc.ctx.pipeReq;\n const bytes = st.queued > 0 ? takeUpTo(st, req.iov.total) : new Uint8Array(0);\n proc.pendingRead = { iov: req.iov, bytes, nreadPtr: req.nreadPtr, pollUserdata: req.pollUserdata };\n resumeProc(proc);\n }\n }\n function fileLookup(s, path) {\n return s.fs.files.get(path) || null;\n }\n function isDir(s, path) {\n return path === \"\" || s.fs.dirs.has(path);\n }\n function isCoreutil(s, path) {\n if (!path.startsWith(\"bin/\") && !path.startsWith(\"usr/bin/\")) return false;\n return s.coreutils.has(path.split(\"/\").pop());\n }\n function fileExists(s, path) {\n return s.fs.files.has(path) || s.fs.modes.has(path) && !s.fs.dirs.has(path);\n }\n function inodeExists(s, path) {\n return s.fs.files.has(path) || s.fs.dirs.has(path) || s.fs.modes.has(path);\n }\n function effMode(s, path) {\n if (path === \"\") return 7;\n const m = s.fs.modes.get(path);\n if (m !== void 0) return m;\n if (isCoreutil(s, path)) return 5;\n return inodeExists(s, path) ? 0 : 7;\n }\n function parentOf(path) {\n const i = path.lastIndexOf(\"/\");\n return i < 0 ? \"\" : path.slice(0, i);\n }\n function followPath(s, path) {\n let cur = path;\n for (let hops = 0; hops < 32; hops++) {\n if (!s.fs.symlinks.size) return { path: cur, errno: 0 };\n const parts = cur.split(\"/\");\n let prefix = \"\";\n let rewrote = false;\n for (let i = 0; i < parts.length; i++) {\n prefix = prefix ? prefix + \"/\" + parts[i] : parts[i];\n const target = s.fs.symlinks.get(prefix);\n if (target === void 0) continue;\n const base = target.startsWith(\"/\") ? norm(target) : norm(parentOf(prefix) + \"/\" + target);\n const rest = parts.slice(i + 1).join(\"/\");\n cur = rest ? norm(base + \"/\" + rest) : base;\n rewrote = true;\n break;\n }\n if (!rewrote) return { path: cur, errno: 0 };\n }\n return { path: cur, errno: E.LOOP };\n }\n function followParents(s, path) {\n const parent = parentOf(path);\n if (!parent) return { path, errno: 0 };\n const r = followPath(s, parent);\n if (r.errno) return r;\n const name = path.slice(parent.length + 1);\n return { path: r.path ? r.path + \"/\" + name : name, errno: 0 };\n }\n function checkTraversal(s, path) {\n const parts = path.split(\"/\");\n let anc = \"\";\n for (let i = 0; i < parts.length - 1; i++) {\n anc = anc ? anc + \"/\" + parts[i] : parts[i];\n if (!isDir(s, anc)) return inodeExists(s, anc) ? E.NOTDIR : E.NOENT;\n if ((effMode(s, anc) & 1) === 0) return E.ACCES;\n }\n return 0;\n }\n function checkParentWritable(s, path) {\n const parent = parentOf(path);\n if (parent && (effMode(s, parent) & 3) !== 3) return E.ACCES;\n return 0;\n }\n function recordDirAdded(s, path) {\n s.fs.dirs.add(path);\n s.fs.dirsDeleted.delete(path);\n s.fs.dirsCreated.add(path);\n if (!s.fs.modes.has(path)) s.fs.modes.set(path, 7);\n touchPath(s, path);\n }\n function recordDirRemoved(s, path) {\n s.fs.dirs.delete(path);\n s.fs.modes.delete(path);\n s.fs.times.delete(path);\n if (s.fs.dirsCreated.has(path)) s.fs.dirsCreated.delete(path);\n else s.fs.dirsDeleted.add(path);\n }\n function recordFileRemoved(s, path) {\n s.fs.files.delete(path);\n s.fs.written.delete(path);\n s.fs.modes.delete(path);\n s.fs.times.delete(path);\n s.fs.symlinks.delete(path);\n s.fs.symlinksCreated.delete(path);\n s.fs.deleted.add(path);\n }\n function markWritten(s, path) {\n s.fs.written.add(path);\n s.fs.deleted.delete(path);\n if (!s.fs.modes.has(path)) s.fs.modes.set(path, 6);\n touchPath(s, path);\n const parts = path.split(\"/\");\n for (let i = 1; i < parts.length; i++) {\n const dir = parts.slice(0, i).join(\"/\");\n if (!s.fs.dirs.has(dir)) recordDirAdded(s, dir);\n }\n }\n function fileWrite(s, entry, path, pos, bytes) {\n const need = pos + bytes.length;\n if (need > entry.bytes.length) {\n const grown = new Uint8Array(need);\n grown.set(entry.bytes);\n entry.bytes = grown;\n }\n entry.bytes.set(bytes, pos);\n markWritten(s, path);\n }\n function mtimeNs(s, path) {\n const t = s.fs.times.get(path);\n return t === void 0 ? s.fs.sessionNs : t;\n }\n function touchPath(s, path) {\n s.fs.times.set(path, realtimeNs());\n }\n function statBuf(dv, u8, buf, filetype, size, timeNs) {\n u8.fill(0, buf, buf + 64);\n dv.setUint8(buf + 16, filetype);\n dv.setBigUint64(buf + 24, 1n, true);\n dv.setBigUint64(buf + 32, BigInt(size), true);\n dv.setBigUint64(buf + 40, timeNs, true);\n dv.setBigUint64(buf + 48, timeNs, true);\n dv.setBigUint64(buf + 56, timeNs, true);\n }\n function isUnderAbsRoot(p, absRoots) {\n if (!absRoots) return false;\n for (const a of absRoots) {\n if (p === a) return true;\n if (a !== \"\" && p.startsWith(a + \"/\")) return true;\n }\n return false;\n }\n function makeResolve(s, pathBase, absRoots) {\n return (raw) => {\n const p = norm(raw);\n if (!pathBase) return p;\n if (isUnderAbsRoot(p, absRoots)) return p;\n const joined = norm(pathBase + \"/\" + p);\n if (inodeExists(s, joined)) return joined;\n if (inodeExists(s, p)) return p;\n if (isDir(s, parentOf(joined))) return joined;\n if (isDir(s, parentOf(p))) return p;\n return joined;\n };\n }\n function makeUnsupported(s) {\n const nosys = (name) => () => {\n s.missingWasi.add(name);\n return E.NOSYS;\n };\n return {\n // Every write is already durable in the session's own heap, so there is\n // nothing to flush and success is the honest answer.\n fd_sync: () => 0,\n fd_datasync: () => 0,\n fd_advise: () => 0,\n // One process runs at a time under this scheduler; a yield has nothing to\n // yield to that the caller has not already reached.\n sched_yield: () => 0,\n fd_pread: nosys(\"fd_pread\"),\n fd_pwrite: nosys(\"fd_pwrite\"),\n fd_allocate: nosys(\"fd_allocate\"),\n fd_filestat_set_size: nosys(\"fd_filestat_set_size\"),\n fd_filestat_set_times: nosys(\"fd_filestat_set_times\"),\n fd_fdstat_set_rights: nosys(\"fd_fdstat_set_rights\"),\n proc_raise: nosys(\"proc_raise\"),\n sock_send: nosys(\"sock_send\"),\n sock_recv: nosys(\"sock_recv\"),\n sock_shutdown: nosys(\"sock_shutdown\")\n };\n }\n function makeWasiFs(s, proc, DV, U8, io, pathBase, absRoots) {\n const resolve = makeResolve(s, pathBase, absRoots);\n return {\n ...makeUnsupported(s),\n fd_prestat_get: (fd, buf) => {\n if (fd === 3) {\n DV().setUint8(buf, 0);\n DV().setUint32(buf + 4, 1, true);\n return 0;\n }\n return E.BADF;\n },\n // The buffer length is a contract, not decoration: fd_prestat_get reports\n // the name is 1 byte, and a guest that offers less must not be written to.\n fd_prestat_dir_name: (fd, path, plen) => {\n if (fd !== 3) return E.BADF;\n if (plen < 1) return E.INVAL;\n U8()[path] = 47;\n return 0;\n },\n path_open: (dirfd, dirflags, pathPtr, pathLen, oflags, rightsBase, _ri, fdflags, retPtr) => {\n const raw = resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen)));\n const r = (dirflags & 1) !== 0 ? followPath(s, raw) : followParents(s, raw);\n if (r.errno) return r.errno;\n const path = r.path;\n const dv = DV();\n if (path === \"dev/null\" || path === \"dev/tty\") {\n const fd2 = lowestFd(proc);\n proc.fds.set(fd2, { kind: path === \"dev/tty\" ? \"tty\" : \"null\" });\n dv.setUint32(retPtr, fd2, true);\n return 0;\n }\n const trav = checkTraversal(s, path);\n if (trav) return trav;\n const wantDir = (oflags & 2) !== 0;\n if (isDir(s, path)) {\n if ((effMode(s, path) & 1) === 0) return E.ACCES;\n const fd2 = lowestFd(proc);\n proc.fds.set(fd2, { kind: \"dir\", path });\n dv.setUint32(retPtr, fd2, true);\n return 0;\n }\n if (wantDir) return fileExists(s, path) ? E.NOTDIR : E.NOENT;\n const rights = typeof rightsBase === \"bigint\" ? rightsBase : BigInt(rightsBase >>> 0);\n let need = 0;\n if ((rights & 2n) !== 0n) need |= 4;\n if ((rights & 64n) !== 0n) need |= 2;\n if ((oflags & 8) !== 0) need |= 2;\n let entry = fileLookup(s, path);\n const exists = entry !== null || fileExists(s, path);\n if (exists && oflags & 4) return E.EXIST;\n if (!exists) {\n if (!(oflags & 1)) return E.NOENT;\n const denied = checkParentWritable(s, path);\n if (denied) return denied;\n entry = { bytes: new Uint8Array(0) };\n s.fs.files.set(path, entry);\n markWritten(s, path);\n } else {\n if (need && (effMode(s, path) & need) !== need) return E.ACCES;\n if (!entry) {\n entry = { bytes: new Uint8Array(0) };\n s.fs.files.set(path, entry);\n }\n if (oflags & 8) {\n entry.bytes = new Uint8Array(0);\n markWritten(s, path);\n }\n }\n const fd = lowestFd(proc);\n proc.fds.set(fd, { kind: \"file\", path, pos: 0, append: (fdflags & 1) !== 0 });\n dv.setUint32(retPtr, fd, true);\n return 0;\n },\n fd_filestat_get: (fd, buf) => {\n const e = proc.fds.get(fd);\n if (!e) return E.BADF;\n if (e.kind === \"file\") {\n const f = fileLookup(s, e.path);\n statBuf(DV(), U8(), buf, 4, f ? f.bytes.length : 0, mtimeNs(s, e.path));\n return 0;\n }\n if (e && (e.kind === \"dir\" || e.kind === \"preopen\")) {\n statBuf(DV(), U8(), buf, 3, 0, mtimeNs(s, e.kind === \"preopen\" ? \"\" : e.path));\n return 0;\n }\n statBuf(DV(), U8(), buf, e && (e.kind === \"stdin\" || e.kind === \"stdout\" || e.kind === \"stderr\" || e.kind === \"tty\") ? 2 : 0, 0, realtimeNs());\n return 0;\n },\n path_filestat_get: (dirfd, flags, pathPtr, pathLen, buf) => {\n const raw = resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen)));\n if (raw === \"dev/null\" || raw === \"dev/tty\") {\n statBuf(DV(), U8(), buf, 2, 0, realtimeNs());\n return 0;\n }\n const follow = (flags & 1) !== 0;\n if (!follow && s.fs.symlinks.has(raw)) {\n statBuf(DV(), U8(), buf, 7, te.encode(s.fs.symlinks.get(raw)).length, mtimeNs(s, raw));\n return 0;\n }\n const r = followPath(s, raw);\n if (r.errno) return r.errno;\n const path = r.path;\n const trav = checkTraversal(s, path);\n if (trav) return trav;\n const f = fileLookup(s, path);\n if (f) {\n statBuf(DV(), U8(), buf, 4, f.bytes.length, mtimeNs(s, path));\n return 0;\n }\n if (isCoreutil(s, path)) {\n statBuf(DV(), U8(), buf, 4, 1024, mtimeNs(s, path));\n return 0;\n }\n if (isDir(s, path)) {\n statBuf(DV(), U8(), buf, 3, 0, mtimeNs(s, path));\n return 0;\n }\n if (fileExists(s, path)) {\n statBuf(DV(), U8(), buf, 4, 0, mtimeNs(s, path));\n return 0;\n }\n return E.NOENT;\n },\n // fstflags: ATIM=1, ATIM_NOW=2, MTIM=4, MTIM_NOW=8. Only the mtime half is\n // recorded — this layer keeps one timestamp per inode, and mtime is the\n // one every build tool reads.\n path_filestat_set_times: (dirfd, _flags, pathPtr, pathLen, _atim, mtim, fstflags) => {\n const path = resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen)));\n if (!inodeExists(s, path) && !s.fs.symlinks.has(path)) return E.NOENT;\n if (fstflags & 8) s.fs.times.set(path, realtimeNs());\n else if (fstflags & 4) s.fs.times.set(path, typeof mtim === \"bigint\" ? mtim : BigInt(mtim >>> 0));\n return 0;\n },\n path_unlink_file: (dirfd, pathPtr, pathLen) => {\n const r = followParents(s, resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen))));\n if (r.errno) return r.errno;\n const path = r.path;\n const trav = checkTraversal(s, path);\n if (trav) return trav;\n if (isDir(s, path)) return E.ISDIR;\n if (!fileExists(s, path) && !s.fs.symlinks.has(path)) return E.NOENT;\n const denied = checkParentWritable(s, path);\n if (denied) return denied;\n recordFileRemoved(s, path);\n return 0;\n },\n path_rename: (fd1, oldPtr, oldLen, fd2, newPtr, newLen) => {\n const from = resolve(td.decode(U8().subarray(oldPtr, oldPtr + oldLen)));\n const to = resolve(td.decode(U8().subarray(newPtr, newPtr + newLen)));\n let trav = checkTraversal(s, from);\n if (trav) return trav;\n trav = checkTraversal(s, to);\n if (trav) return trav;\n const fromIsDir = from !== \"\" && isDir(s, from);\n const f = fileLookup(s, from);\n if (!fromIsDir && !f && !fileExists(s, from)) return E.NOENT;\n if (from === to) return 0;\n let denied = checkParentWritable(s, from);\n if (!denied) denied = checkParentWritable(s, to);\n if (denied) return denied;\n const moveMode = (a, b) => {\n const m = s.fs.modes.get(a);\n s.fs.modes.delete(a);\n if (m !== void 0) s.fs.modes.set(b, m);\n };\n if (isDir(s, to)) {\n if (!fromIsDir) return E.ISDIR;\n const prefix = to + \"/\";\n for (const p of s.fs.files.keys()) if (p.startsWith(prefix)) return E.NOTEMPTY;\n for (const p of s.fs.dirs) if (p.startsWith(prefix)) return E.NOTEMPTY;\n recordDirRemoved(s, to);\n } else if (fileExists(s, to)) {\n if (fromIsDir) return E.NOTDIR;\n recordFileRemoved(s, to);\n }\n if (fromIsDir) {\n const mode2 = s.fs.modes.get(from);\n recordDirRemoved(s, from);\n recordDirAdded(s, to);\n if (mode2 !== void 0) s.fs.modes.set(to, mode2);\n const prefix = from + \"/\";\n const rebase = (key) => to + \"/\" + key.slice(prefix.length);\n for (const key of [...s.fs.files.keys()]) {\n if (!key.startsWith(prefix)) continue;\n const nk = rebase(key);\n const entry2 = s.fs.files.get(key);\n recordFileRemoved(s, key);\n moveMode(key, nk);\n s.fs.files.set(nk, entry2);\n s.fs.written.add(nk);\n s.fs.deleted.delete(nk);\n if (!s.fs.modes.has(nk)) s.fs.modes.set(nk, 6);\n }\n for (const key of [...s.fs.dirs]) {\n if (!key.startsWith(prefix)) continue;\n const nk = rebase(key);\n const mode22 = s.fs.modes.get(key);\n recordDirRemoved(s, key);\n recordDirAdded(s, nk);\n if (mode22 !== void 0) s.fs.modes.set(nk, mode22);\n }\n return 0;\n }\n const entry = f || { bytes: new Uint8Array(0) };\n const mode = s.fs.modes.get(from);\n recordFileRemoved(s, from);\n s.fs.files.set(to, entry);\n markWritten(s, to);\n if (mode !== void 0) s.fs.modes.set(to, mode);\n return 0;\n },\n path_create_directory: (dirfd, pathPtr, pathLen) => {\n const path = resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen)));\n const trav = checkTraversal(s, path);\n if (trav) return trav;\n if (isDir(s, path) || fileExists(s, path)) return E.EXIST;\n const denied = checkParentWritable(s, path);\n if (denied) return denied;\n recordDirAdded(s, path);\n return 0;\n },\n // path_readlink(fd, path, path_len, buf, buf_len, *bufused). Never follows\n // the last component — reading a link is the one op that is about the link.\n path_readlink: (dirfd, pathPtr, pathLen, bufPtr, bufLen, bufUsedPtr) => {\n const r = followParents(s, resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen))));\n if (r.errno) return r.errno;\n const target = s.fs.symlinks.get(r.path);\n if (target === void 0) return inodeExists(s, r.path) ? E.INVAL : E.NOENT;\n const bytes = te.encode(target);\n const n = Math.min(bytes.length, bufLen);\n U8().set(bytes.subarray(0, n), bufPtr);\n DV().setUint32(bufUsedPtr, n, true);\n return 0;\n },\n // path_symlink(old_path, old_path_len, fd, new_path, new_path_len) — the\n // target comes FIRST and is not a path in this filesystem: it is stored\n // verbatim and may dangle, exactly as ln -s allows.\n path_symlink: (oldPtr, oldLen, newFd, newPtr, newLen) => {\n const target = td.decode(U8().subarray(oldPtr, oldPtr + oldLen));\n const r = followParents(s, resolve(td.decode(U8().subarray(newPtr, newPtr + newLen))));\n if (r.errno) return r.errno;\n const link = r.path;\n const trav = checkTraversal(s, link);\n if (trav) return trav;\n if (inodeExists(s, link) || s.fs.symlinks.has(link)) return E.EXIST;\n const denied = checkParentWritable(s, link);\n if (denied) return denied;\n s.fs.symlinks.set(link, target);\n s.fs.symlinksCreated.set(link, target);\n s.fs.modes.set(link, 7);\n s.fs.deleted.delete(link);\n touchPath(s, link);\n return 0;\n },\n path_link: (fd1, _lookupFlags, oldPtr, oldLen, fd2, newPtr, newLen) => {\n const from = resolve(td.decode(U8().subarray(oldPtr, oldPtr + oldLen)));\n const to = resolve(td.decode(U8().subarray(newPtr, newPtr + newLen)));\n let trav = checkTraversal(s, from);\n if (trav) return trav;\n trav = checkTraversal(s, to);\n if (trav) return trav;\n if (isDir(s, from)) return E.PERM;\n const f = fileLookup(s, from);\n if (!f) return fileExists(s, from) ? E.ACCES : E.NOENT;\n if (fileExists(s, to) || isDir(s, to)) return E.EXIST;\n const denied = checkParentWritable(s, to);\n if (denied) return denied;\n s.fs.files.set(to, f);\n markWritten(s, to);\n s.fs.modes.set(to, effMode(s, from));\n return 0;\n },\n path_remove_directory: (dirfd, pathPtr, pathLen) => {\n const path = resolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen)));\n const trav = checkTraversal(s, path);\n if (trav) return trav;\n if (path === \"\" || path === \"etc\" || path === \"dev\" || path === \"bin\" || path === \"usr\" || path === \"usr/bin\") return E.ACCES;\n if (!isDir(s, path)) return fileExists(s, path) ? E.NOTDIR : E.NOENT;\n const denied = checkParentWritable(s, path);\n if (denied) return denied;\n const prefix = path + \"/\";\n for (const p of s.fs.files.keys()) if (p.startsWith(prefix)) return E.NOTEMPTY;\n for (const p of s.fs.dirs) if (p.startsWith(prefix)) return E.NOTEMPTY;\n for (const p of s.fs.modes.keys()) if (p.startsWith(prefix)) return E.NOTEMPTY;\n recordDirRemoved(s, path);\n return 0;\n },\n fd_renumber: (from, to) => {\n const e = proc.fds.get(from);\n if (!e) return E.BADF;\n if (from !== to) {\n closeFd(s, proc, to);\n proc.fds.set(to, e);\n proc.fds.delete(from);\n }\n return 0;\n },\n fd_readdir: (fd, buf, bufLen, cookie, retPtr) => {\n const e = proc.fds.get(fd);\n if (!e || e.kind !== \"dir\" && e.kind !== \"preopen\") return E.BADF;\n const dir = e.kind === \"preopen\" ? \"\" : e.path;\n if ((effMode(s, dir) & 4) === 0) return E.ACCES;\n const prefix = dir ? dir + \"/\" : \"\";\n const seen = /* @__PURE__ */ new Set([\".\", \"..\"]);\n const children = [];\n const push = (name, type) => {\n if (!seen.has(name)) {\n seen.add(name);\n children.push({ name, type });\n }\n };\n for (const p of s.fs.dirs) {\n if (p !== dir && p.startsWith(prefix) && !p.slice(prefix.length).includes(\"/\")) push(p.slice(prefix.length), 3);\n }\n for (const p of s.fs.files.keys()) {\n if (p.startsWith(prefix) && !p.slice(prefix.length).includes(\"/\")) push(p.slice(prefix.length), 4);\n }\n for (const p of s.fs.symlinks.keys()) {\n if (p.startsWith(prefix) && !p.slice(prefix.length).includes(\"/\")) push(p.slice(prefix.length), 7);\n }\n for (const p of s.fs.modes.keys()) {\n if (p !== dir && p.startsWith(prefix) && !p.slice(prefix.length).includes(\"/\") && !s.fs.dirs.has(p)) push(p.slice(prefix.length), 4);\n }\n if (dir === \"bin\" || dir === \"usr/bin\") for (const name of s.coreutils.keys()) push(name, 4);\n children.sort((a, b) => a.name < b.name ? -1 : 1);\n const entries = [{ name: \".\", type: 3 }, { name: \"..\", type: 3 }, ...children];\n const dv = DV(), u8 = U8();\n let off = 0;\n for (let i = Number(cookie); i < entries.length; i++) {\n const nb = te.encode(entries[i].name);\n const need = 24 + nb.length;\n const record = new Uint8Array(need);\n const rdv = new DataView(record.buffer);\n rdv.setBigUint64(0, BigInt(i + 1), true);\n rdv.setBigUint64(8, 1n, true);\n rdv.setUint32(16, nb.length, true);\n record[20] = entries[i].type;\n record.set(nb, 24);\n const room = bufLen - off;\n if (need > room) {\n u8.set(record.subarray(0, room), buf + off);\n off = bufLen;\n break;\n }\n u8.set(record, buf + off);\n off += need;\n }\n dv.setUint32(retPtr, off, true);\n return 0;\n },\n // An fd that was never opened is EBADF everywhere below. It used to be\n // ESPIPE from fd_seek and plain SUCCESS from fd_tell and fd_filestat_get,\n // while fd_close, fd_fdstat_get and fd_write in this same table already\n // answered EBADF — one descriptor table, five different opinions about what\n // a closed descriptor is. ESPIPE in particular is a yes: it means \"open, and\n // not seekable\", so a caller using lseek to test validity read it as valid.\n fd_seek: (fd, offset, whence, retPtr) => {\n const e = proc.fds.get(fd);\n if (!e) return E.BADF;\n if (e.kind === \"file\") {\n const f = fileLookup(s, e.path);\n const len = f ? f.bytes.length : 0;\n const off = Number(offset);\n const next = whence === 0 ? off : whence === 1 ? e.pos + off : len + off;\n if (next < 0) return E.INVAL;\n e.pos = next;\n DV().setBigUint64(retPtr, BigInt(next), true);\n return 0;\n }\n DV().setBigUint64(retPtr, 0n, true);\n return E.SPIPE;\n },\n fd_tell: (fd, retPtr) => {\n const e = proc.fds.get(fd);\n if (!e) return E.BADF;\n DV().setBigUint64(retPtr, BigInt(e.kind === \"file\" ? e.pos : 0), true);\n return 0;\n },\n fd_close: (fd) => {\n if (!proc.fds.has(fd)) return E.BADF;\n closeFd(s, proc, fd);\n return 0;\n },\n fd_fdstat_get: (fd, st) => {\n const dv = DV();\n const e = proc.fds.get(fd);\n let ft = 4;\n if (e && e.kind === \"pipe\") ft = 0;\n else if (e && (e.kind === \"stdin\" || e.kind === \"stdout\" || e.kind === \"stderr\")) ft = s.stdinTty || fd !== 0 ? 2 : 0;\n else if (e && e.kind === \"tty\") ft = 2;\n else if (e && (e.kind === \"dir\" || e.kind === \"preopen\")) ft = 3;\n else if (!e) return E.BADF;\n dv.setUint8(st, ft);\n dv.setUint16(st + 2, 0, true);\n dv.setBigUint64(st + 8, 0xffffffffffffffffn, true);\n dv.setBigUint64(st + 16, 0xffffffffffffffffn, true);\n return 0;\n },\n fd_fdstat_set_flags: () => 0,\n fd_read: (fd, iovs, n, nread) => io.read(fd, readIovs(DV(), iovs, n), nread),\n fd_write: (fd, iovs, n, nw) => {\n if (!proc.fds.has(fd)) return E.BADF;\n const dv = DV(), u8 = U8();\n let w = 0;\n for (let i = 0; i < n; i++) {\n const p = dv.getUint32(iovs + i * 8, true), l = dv.getUint32(iovs + i * 8 + 4, true);\n const n2 = io.write(fd, u8.subarray(p, p + l));\n if (n2 === null) return E.BADF;\n w += n2;\n }\n dv.setUint32(nw, w, true);\n return 0;\n },\n poll_oneoff: (inPtr, outPtr, nsubs, retPtr) => io.poll(inPtr, outPtr, nsubs, retPtr),\n clock_time_get: (id, _pr, t) => {\n const ns = clockNs(id);\n if (ns === null) return E.INVAL;\n DV().setBigUint64(t, ns, true);\n return 0;\n },\n clock_res_get: (id, r) => {\n if (clockNs(id) === null) return E.INVAL;\n DV().setBigUint64(r, id === CLOCK_REALTIME ? 1000000n : 1000n, true);\n return 0;\n },\n // getRandomValues caps at 65536 bytes per call; a guest asking for more\n // gets the same CSPRNG, chunked, never a weaker one.\n random_get: (b, l) => {\n const u = U8();\n for (let off = 0; off < l; off += 65536) {\n crypto.getRandomValues(u.subarray(b + off, b + off + Math.min(l - off, 65536)));\n }\n return 0;\n },\n proc_exit: (code) => {\n throw new Exit(code);\n }\n };\n }\n function writeThroughFd(s, proc, fd, bytes) {\n const e = proc.fds.get(fd);\n if (e && e.kind === \"pipe\") {\n if (e.end === \"r\") return null;\n const pp = s.pipes.get(e.pipeId);\n pp.chunks.push(bytes.slice());\n pp.queued += bytes.length;\n wakePipe(s, pp);\n return bytes.length;\n }\n if (e && e.kind === \"file\") {\n const f = fileLookup(s, e.path) || { bytes: new Uint8Array(0) };\n if (!s.fs.files.has(e.path)) s.fs.files.set(e.path, f);\n const pos = e.append ? f.bytes.length : e.pos;\n fileWrite(s, f, e.path, pos, bytes);\n e.pos = pos + bytes.length;\n return bytes.length;\n }\n if (e && (e.kind === \"null\" || e.kind === \"tty\")) return bytes.length;\n const text = td.decode(bytes);\n if (e.kind === \"stderr\") s.err += text;\n else s.out += text;\n return bytes.length;\n }\n function tryReadFd(s, proc, fd, dv, u8, iov, nreadPtr) {\n const e = proc.fds.get(fd);\n const deliver = (bytes) => {\n dv.setUint32(nreadPtr, scatter(u8, iov, bytes), true);\n return { errno: 0 };\n };\n if (e && e.kind === \"file\") {\n const f = fileLookup(s, e.path);\n const bytes = f ? f.bytes.subarray(e.pos, e.pos + iov.total) : new Uint8Array(0);\n e.pos += bytes.length;\n return deliver(bytes);\n }\n if (e && (e.kind === \"null\" || e.kind === \"tty\")) {\n dv.setUint32(nreadPtr, 0, true);\n return { errno: 0 };\n }\n if (e && e.kind === \"pipe\") {\n const pp = s.pipes.get(e.pipeId);\n if (pp.queued > 0) return deliver(takeUpTo(pp, iov.total));\n if (pp.writers === 0) {\n dv.setUint32(nreadPtr, 0, true);\n return { errno: 0 };\n }\n return null;\n }\n if (e && e.kind === \"stdin\") {\n const st = s.stdin;\n if (st.queued > 0) return deliver(takeUpTo(st, iov.total));\n if (st.closed) {\n dv.setUint32(nreadPtr, 0, true);\n return { errno: 0 };\n }\n return null;\n }\n if (e) {\n dv.setUint32(nreadPtr, 0, true);\n return { errno: 0 };\n }\n return { errno: E.BADF };\n }\n function readSubs(dv, inPtr, nsubs) {\n const subs = [];\n for (let i = 0; i < nsubs; i++) {\n const base = inPtr + i * 48;\n const userdata = dv.getBigUint64(base, true);\n const tag = dv.getUint8(base + 8);\n if (tag === 0) {\n const id = dv.getUint32(base + 16, true);\n const timeout = dv.getBigUint64(base + 24, true);\n const abs = (dv.getUint16(base + 40, true) & 1) !== 0;\n const now = clockNs(id);\n subs.push(now === null ? { tag, userdata, id, bad: true } : { tag, userdata, id, deadline: abs ? timeout : now + timeout });\n } else {\n subs.push({ tag, userdata, fd: dv.getUint32(base + 16, true) });\n }\n }\n return subs;\n }\n function clockExpired(sub) {\n const now = clockNs(sub.id);\n return now !== null && now >= sub.deadline;\n }\n function writeEvent(dv, outPtr, slot, sub, errno, nbytes) {\n const ev = outPtr + slot * 32;\n dv.setBigUint64(ev, sub.userdata, true);\n dv.setUint16(ev + 8, errno, true);\n dv.setUint8(ev + 10, sub.tag);\n dv.setBigUint64(ev + 16, BigInt(nbytes), true);\n dv.setUint16(ev + 24, 0, true);\n }\n function fdReadReady(s, proc, fd) {\n const e = proc.fds.get(fd);\n if (!e) return null;\n if (e.kind === \"pipe\") {\n const pp = s.pipes.get(e.pipeId);\n return { ready: pp.queued > 0 || pp.writers === 0, avail: pp.queued };\n }\n if (e.kind === \"stdin\") return { ready: s.stdin.queued > 0 || s.stdin.closed, avail: s.stdin.queued };\n return { ready: true, avail: 0 };\n }\n function emitReady(s, proc, dv, outPtr, subs) {\n let n = 0;\n for (const sub of subs) {\n if (sub.tag === 0) {\n if (sub.bad) {\n writeEvent(dv, outPtr, n++, sub, E.INVAL, 0);\n continue;\n }\n if (clockExpired(sub)) writeEvent(dv, outPtr, n++, sub, 0, 0);\n continue;\n }\n const st = fdReadReady(s, proc, sub.fd);\n if (!st) {\n writeEvent(dv, outPtr, n++, sub, E.BADF, 0);\n continue;\n }\n if (sub.tag === 2 || st.ready) writeEvent(dv, outPtr, n++, sub, 0, st.avail);\n }\n return n;\n }\n var FROZEN_PROBE = 2e5;\n function waitForDeadline(s, proc, subs) {\n const clocks = subs.filter((x) => x.tag === 0 && !x.bad);\n if (!clocks.length) return;\n const startedNs = realtimeNs();\n let spins = 0;\n while (!clocks.some(clockExpired)) {\n if (s.rootExit !== null) return;\n if (subs.some((x) => x.tag === 1 && (fdReadReady(s, proc, x.fd) || { ready: true }).ready)) return;\n if (s.runnable.length) {\n pumpOne(s);\n continue;\n }\n if (++spins > FROZEN_PROBE && realtimeNs() === startedNs) return;\n }\n }\n function emitClocks(dv, outPtr, subs) {\n let n = 0;\n for (const sub of subs) if (sub.tag === 0 && !sub.bad) writeEvent(dv, outPtr, n++, sub, 0, 0);\n return n;\n }\n function blockTarget(s, proc, fd) {\n const e = proc.fds.get(fd);\n if (e && e.kind === \"pipe\") return { list: s.pipes.get(e.pipeId).readW, wake: () => wakePipe(s, s.pipes.get(e.pipeId)) };\n if (e && e.kind === \"stdin\") return { list: s.stdin.waiters, wake: () => wakeStdin(s) };\n return null;\n }\n function makeProc(s, pid, ppid, fds) {\n const proc = {\n pid,\n ppid,\n fds,\n inst: null,\n __s: s,\n ctx: { reason: null, rewinding: false, captureEnv: 0, ljEnv: 0, ljVal: 0, nextSlot: 0, resume: 0 },\n MAIN_BUF: 0,\n SLOT0: 0,\n pendingRead: null,\n slotByEnv: /* @__PURE__ */ new Map(),\n freeSlots: []\n };\n const DV = () => new DataView(proc.inst.exports.memory.buffer);\n const U8 = () => new Uint8Array(proc.inst.exports.memory.buffer);\n proc.DV = DV;\n proc.U8 = U8;\n const slotAddr = (i) => proc.SLOT0 + i * SLOT_SIZE;\n proc.slotAddr = slotAddr;\n const initHdr = (a, sz) => {\n const dv = DV();\n dv.setUint32(a, a + 8, true);\n dv.setUint32(a + 4, a + sz, true);\n };\n proc.initHdr = initHdr;\n const wstr = (p, str) => {\n const b = te.encode(str);\n U8().set(b, p);\n return b.length;\n };\n const c = proc.ctx;\n const io = {\n read: (fd, iov, nread) => {\n if (proc.pendingRead) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n const pr = proc.pendingRead;\n proc.pendingRead = null;\n DV().setUint32(pr.nreadPtr, scatter(U8(), pr.iov, pr.bytes), true);\n return 0;\n }\n const dv = DV();\n const sync = tryReadFd(s, proc, fd, dv, U8(), iov, nread);\n if (sync) return sync.errno;\n dv.setUint32(nread, 0, true);\n c.reason = \"blockread\";\n c.pipeReq = { fd, iov, nreadPtr: nread };\n initHdr(proc.MAIN_BUF, MAIN_SIZE);\n proc.inst.exports.asyncify_start_unwind(proc.MAIN_BUF);\n return 0;\n },\n write: (fd, bytes) => writeThroughFd(s, proc, fd, bytes),\n poll: (inPtr, outPtr, nsubs, retPtr) => {\n if (proc.pendingRead) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n const pr = proc.pendingRead;\n proc.pendingRead = null;\n const dv2 = DV();\n dv2.setBigUint64(outPtr, pr.pollUserdata ?? 0n, true);\n dv2.setUint16(outPtr + 8, 0, true);\n dv2.setUint8(outPtr + 10, 1);\n dv2.setBigUint64(outPtr + 16, BigInt(pr.bytes.length), true);\n dv2.setUint16(outPtr + 24, 0, true);\n dv2.setUint32(retPtr, 1, true);\n return 0;\n }\n const dv = DV();\n const subs = readSubs(dv, inPtr, nsubs);\n let emitted = emitReady(s, proc, dv, outPtr, subs);\n if (emitted > 0) {\n dv.setUint32(retPtr, emitted, true);\n return 0;\n }\n const blockSub = subs.find((x) => x.tag === 1 && blockTarget(s, proc, x.fd));\n if (!blockSub) {\n waitForDeadline(s, proc, subs);\n emitted = emitReady(s, proc, dv, outPtr, subs) || emitClocks(dv, outPtr, subs);\n dv.setUint32(retPtr, emitted, true);\n return 0;\n }\n dv.setUint32(retPtr, 0, true);\n c.reason = \"blockread\";\n c.pipeReq = { fd: blockSub.fd, iov: EMPTY_IOV, nreadPtr: 0, pollUserdata: blockSub.userdata };\n initHdr(proc.MAIN_BUF, MAIN_SIZE);\n proc.inst.exports.asyncify_start_unwind(proc.MAIN_BUF);\n return 0;\n }\n };\n const wasiBase = makeWasiFs(s, proc, DV, U8, io);\n const wasi = {\n ...wasiBase,\n args_sizes_get: (a, b) => {\n const dv = DV();\n dv.setUint32(a, s.argv.length, true);\n dv.setUint32(b, s.argv.reduce((x, v) => x + te.encode(v).length + 1, 0), true);\n return 0;\n },\n args_get: (ptrs, buf) => {\n const dv = DV();\n let p = buf;\n for (const a of s.argv) {\n dv.setUint32(ptrs, p, true);\n ptrs += 4;\n p += wstr(p, a);\n U8()[p++] = 0;\n }\n return 0;\n },\n environ_sizes_get: (a, b) => {\n const dv = DV();\n dv.setUint32(a, s.environ.length, true);\n dv.setUint32(b, s.environ.reduce((x, v) => x + te.encode(v).length + 1, 0), true);\n return 0;\n },\n environ_get: (ptrs, buf) => {\n const dv = DV();\n let p = buf;\n for (const v of s.environ) {\n dv.setUint32(ptrs, p, true);\n ptrs += 4;\n p += wstr(p, v);\n U8()[p++] = 0;\n }\n return 0;\n }\n };\n const allocSlot = (env) => {\n const prev = proc.slotByEnv.get(env);\n if (prev !== void 0) {\n proc.slotByEnv.delete(env);\n proc.freeSlots.push(prev);\n }\n let idx;\n if (proc.freeSlots.length > 1) idx = proc.freeSlots.shift();\n else if (c.nextSlot < NSLOT) idx = c.nextSlot++;\n else idx = proc.freeSlots.shift();\n if (idx === void 0) throw new Error(\"bash-runner: setjmp slot budget exceeded (\" + NSLOT + \")\");\n proc.slotByEnv.set(env, idx);\n return idx;\n };\n const nimbus_proc = {\n setjmp: (env) => {\n if (c.rewinding) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n return;\n }\n c.reason = \"capture\";\n c.captureEnv = env;\n const idx = allocSlot(env);\n const dv = DV();\n dv.setInt32(env, idx, true);\n dv.setInt32(env + 4, 0, true);\n initHdr(slotAddr(idx), SLOT_SIZE);\n proc.inst.exports.asyncify_start_unwind(slotAddr(idx));\n },\n longjmp: (env, val) => {\n if (c.rewinding) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n return;\n }\n c.reason = \"longjmp\";\n c.ljEnv = env;\n c.ljVal = val;\n initHdr(proc.MAIN_BUF, MAIN_SIZE);\n proc.inst.exports.asyncify_start_unwind(proc.MAIN_BUF);\n },\n fork: () => {\n if (c.rewinding) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n return c.resume;\n }\n c.reason = \"fork\";\n initHdr(proc.MAIN_BUF, MAIN_SIZE);\n proc.inst.exports.asyncify_start_unwind(proc.MAIN_BUF);\n return 0;\n },\n vfork: () => nimbus_proc.fork(),\n waitpid: (pid2, statusPtr, _opt) => {\n if (c.rewinding) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n if (c.waitStatusPtr != null) DV().setInt32(c.waitStatusPtr, c.resumeStatus, true);\n return c.resume;\n }\n c.reason = \"waitpid\";\n c.waitTarget = pid2;\n c.waitStatusPtr = statusPtr;\n initHdr(proc.MAIN_BUF, MAIN_SIZE);\n proc.inst.exports.asyncify_start_unwind(proc.MAIN_BUF);\n return 0;\n },\n execve: (pathPtr, argvFlatPtr, argvLen, envFlatPtr, envLen) => {\n if (c.rewinding) {\n proc.inst.exports.asyncify_stop_rewind();\n c.rewinding = false;\n return c.resume;\n }\n const u8 = U8();\n let e = pathPtr;\n while (u8[e]) e++;\n c.reason = \"exec\";\n c.execPath = td.decode(u8.subarray(pathPtr, e));\n c.execArgv = td.decode(u8.subarray(argvFlatPtr, argvFlatPtr + argvLen)).split(\"\\0\").filter((x) => x.length);\n c.execEnv = td.decode(u8.subarray(envFlatPtr, envFlatPtr + envLen)).split(\"\\0\").filter((x) => x.length);\n initHdr(proc.MAIN_BUF, MAIN_SIZE);\n proc.inst.exports.asyncify_start_unwind(proc.MAIN_BUF);\n return 0;\n },\n pipe: (fdsPtr) => {\n const id = newPipe(s);\n const rfd = lowestFd(proc);\n proc.fds.set(rfd, { kind: \"pipe\", pipeId: id, end: \"r\" });\n const wfd = lowestFd(proc);\n proc.fds.set(wfd, { kind: \"pipe\", pipeId: id, end: \"w\" });\n const dv = DV();\n dv.setInt32(fdsPtr, rfd, true);\n dv.setInt32(fdsPtr + 4, wfd, true);\n return 0;\n },\n dup: (o) => {\n const e = proc.fds.get(o);\n if (!e) return -E.BADF;\n const nf = lowestFd(proc);\n proc.fds.set(nf, { ...e });\n if (e.kind === \"pipe\") bumpPipe(s, e, 1);\n return nf;\n },\n dup2: (o, n) => {\n const e = proc.fds.get(o);\n if (!e) return -E.BADF;\n if (o === n) return n;\n if (proc.fds.has(n)) closeFd(s, proc, n);\n proc.fds.set(n, { ...e });\n if (e.kind === \"pipe\") bumpPipe(s, e, 1);\n return n;\n },\n kill: () => 0,\n setpgid: () => 0,\n getpgid: () => proc.pid,\n getppid: () => proc.ppid,\n tcsetpgrp: () => 0,\n tcgetpgrp: () => proc.pid,\n tcgetattr: () => -1,\n tcsetattr: () => 0\n };\n const envImports = {\n getpid: () => proc.pid,\n getuid: () => 0,\n geteuid: () => 0,\n getgid: () => 0,\n getegid: () => 0,\n setuid: () => 0,\n setgid: () => 0,\n umask: () => 18,\n gethostname: (p, _l) => {\n U8().set(te.encode(\"nimbus\"), p);\n return 0;\n },\n dlopen: () => 0,\n dlsym: () => 0,\n dlclose: () => 0,\n dlerror: () => 0\n };\n proc.inst = new WebAssembly.Instance(s.mod, { wasi_snapshot_preview1: wasi, nimbus_proc, env: envImports });\n s.stats.instances++;\n s.procs.set(pid, proc);\n return proc;\n }\n function setupArena(proc) {\n const base = proc.inst.exports.memory.buffer.byteLength;\n const need = MAIN_SIZE + NSLOT * SLOT_SIZE;\n proc.inst.exports.memory.grow(Math.ceil(need / PAGE));\n proc.MAIN_BUF = base;\n proc.SLOT0 = proc.MAIN_BUF + MAIN_SIZE;\n }\n function resumeProc(proc) {\n proc.ctx.rewinding = true;\n proc.inst.exports.asyncify_start_rewind(proc.MAIN_BUF);\n proc.__s.runnable.push(proc);\n }\n function trackArena(s, proc, bufAddr, size, isSlot) {\n const used = proc.DV().getUint32(bufAddr, true) - (bufAddr + 8);\n if (isSlot) {\n if (used > s.stats.slotHi) s.stats.slotHi = used;\n } else if (used > s.stats.mainHi) s.stats.mainHi = used;\n }\n function step(s, proc) {\n const c = proc.ctx, ex = proc.inst.exports;\n try {\n ex._start();\n } catch (e) {\n if (e instanceof Exit) {\n finishProc(s, proc, e.code);\n return;\n }\n throw e;\n }\n if (c.reason === null) {\n finishProc(s, proc, 0);\n return;\n }\n ex.asyncify_stop_unwind();\n const r = c.reason;\n c.reason = null;\n const dv = proc.DV();\n const mem = proc.inst.exports.memory.buffer.byteLength;\n if (mem > s.stats.memPeak) s.stats.memPeak = mem;\n if (r === \"capture\") {\n const idx = dv.getInt32(c.captureEnv, true);\n trackArena(s, proc, proc.slotAddr(idx), SLOT_SIZE, true);\n dv.setUint32(c.captureEnv + 8, dv.getUint32(proc.slotAddr(idx), true), true);\n c.rewinding = true;\n ex.asyncify_start_rewind(proc.slotAddr(idx));\n s.runnable.push(proc);\n } else if (r === \"longjmp\") {\n trackArena(s, proc, proc.MAIN_BUF, MAIN_SIZE, false);\n const idx = dv.getInt32(c.ljEnv, true), hw = dv.getUint32(c.ljEnv + 8, true);\n dv.setInt32(c.ljEnv + 4, c.ljVal, true);\n dv.setUint32(proc.slotAddr(idx), hw, true);\n c.rewinding = true;\n ex.asyncify_start_rewind(proc.slotAddr(idx));\n s.runnable.push(proc);\n } else if (r === \"fork\") {\n trackArena(s, proc, proc.MAIN_BUF, MAIN_SIZE, false);\n doFork(s, proc);\n } else if (r === \"waitpid\") {\n trackArena(s, proc, proc.MAIN_BUF, MAIN_SIZE, false);\n doWait(s, proc);\n } else if (r === \"blockread\") {\n trackArena(s, proc, proc.MAIN_BUF, MAIN_SIZE, false);\n const target = blockTarget(s, proc, c.pipeReq.fd);\n if (!target) {\n proc.pendingRead = { iov: c.pipeReq.iov, bytes: new Uint8Array(0), nreadPtr: c.pipeReq.nreadPtr, pollUserdata: c.pipeReq.pollUserdata };\n resumeProc(proc);\n } else {\n target.list.push({ proc });\n target.wake();\n }\n } else if (r === \"exec\") {\n trackArena(s, proc, proc.MAIN_BUF, MAIN_SIZE, false);\n doExec(s, proc);\n } else {\n throw new Error(\"bash-runner: unknown unwind reason \" + r);\n }\n }\n function pumpOne(s) {\n if (!s.runnable.length) return false;\n step(s, s.runnable.shift());\n return true;\n }\n function doExec(s, proc) {\n const name = proc.ctx.execPath.split(\"/\").pop();\n const env = proc.ctx.execEnv && proc.ctx.execEnv.length ? proc.ctx.execEnv : s.environ;\n const pwdVar = env.find((x) => x.startsWith(\"PWD=\"));\n const childCwd = pwdVar ? norm(pwdVar.slice(4)) : s.cwd;\n const path = makeResolve(s, childCwd)(proc.ctx.execPath);\n const viaPath = path.startsWith(\"bin/\") || path.startsWith(\"usr/bin/\");\n const m = viaPath ? s.coreutils.get(name) : null;\n if (!m) {\n proc.ctx.resume = fileLookup(s, path) ? -45 : fileExists(s, path) ? -2 : -44;\n proc.ctx.rewinding = true;\n proc.inst.exports.asyncify_start_rewind(proc.MAIN_BUF);\n s.runnable.push(proc);\n return;\n }\n let inst2;\n const DV = () => new DataView(inst2.exports.memory.buffer);\n const U8 = () => new Uint8Array(inst2.exports.memory.buffer);\n const tv = proc.ctx.execArgv;\n const absRoots = /* @__PURE__ */ new Set();\n for (let i = 1; i < tv.length; i++) if (tv[i].startsWith(\"/\")) absRoots.add(norm(tv[i]));\n const wstr = (p, str) => {\n const b = te.encode(str);\n U8().set(b, p);\n return b.length;\n };\n let code = 0;\n const io = {\n read: (fd, iov, nread) => {\n const dv = DV();\n for (; ; ) {\n const sync = tryReadFd(s, proc, fd, dv, U8(), iov, nread);\n if (sync) return sync.errno;\n if (!pumpOne(s)) {\n dv.setUint32(nread, 0, true);\n return 0;\n }\n }\n },\n write: (fd, bytes) => writeThroughFd(s, proc, fd, bytes),\n // An exec'd tool cannot park, so a wait it cannot satisfy locally is spent\n // pumping the scheduler — the same discipline its blocking reads use.\n poll: (inPtr, outPtr, nsubs, retPtr) => {\n const dv = DV();\n const subs = readSubs(dv, inPtr, nsubs);\n let emitted = emitReady(s, proc, dv, outPtr, subs);\n if (emitted === 0) {\n waitForDeadline(s, proc, subs);\n emitted = emitReady(s, proc, dv, outPtr, subs) || emitClocks(dv, outPtr, subs);\n }\n dv.setUint32(retPtr, emitted, true);\n return 0;\n }\n };\n const base = makeWasiFs(s, proc, DV, U8, io, childCwd, absRoots);\n const w = {\n ...base,\n args_sizes_get: (a, b) => {\n const dv = DV();\n dv.setUint32(a, tv.length, true);\n dv.setUint32(b, tv.reduce((x, v) => x + te.encode(v).length + 1, 0), true);\n return 0;\n },\n args_get: (ptrs, buf) => {\n const dv = DV();\n let p = buf;\n for (const a of tv) {\n dv.setUint32(ptrs, p, true);\n ptrs += 4;\n p += wstr(p, a);\n U8()[p++] = 0;\n }\n return 0;\n },\n // POSIX environment inheritance: the child env bash passed to execve.\n environ_sizes_get: (a, b) => {\n const dv = DV();\n dv.setUint32(a, env.length, true);\n dv.setUint32(b, env.reduce((x, v) => x + te.encode(v).length + 1, 0), true);\n return 0;\n },\n environ_get: (ptrs, buf) => {\n const dv = DV();\n let p = buf;\n for (const v of env) {\n dv.setUint32(ptrs, p, true);\n ptrs += 4;\n p += wstr(p, v);\n U8()[p++] = 0;\n }\n return 0;\n },\n proc_exit: (ec) => {\n code = ec;\n throw new Exit(ec);\n }\n };\n const chmodResolve = makeResolve(s, childCwd, absRoots);\n const nimbus_proc = {\n chmod: (pathPtr, pathLen, mode) => {\n const p = chmodResolve(td.decode(U8().subarray(pathPtr, pathPtr + pathLen)));\n const trav = checkTraversal(s, p);\n if (trav) return trav;\n if (!inodeExists(s, p) && !(p !== \"\" && isDir(s, p))) return E.NOENT;\n const bits = mode & 511;\n s.fs.modes.set(p, bits >> 6 & 7);\n s.fs.modesChanged.set(p, bits);\n return 0;\n }\n };\n inst2 = new WebAssembly.Instance(m, { wasi_snapshot_preview1: w, nimbus_proc });\n s.stats.instances++;\n const mem2 = inst2.exports.memory.buffer.byteLength;\n if (mem2 > s.stats.memPeak) s.stats.memPeak = mem2;\n try {\n inst2.exports._start();\n } catch (e) {\n if (e instanceof Exit) code = e.code;\n else throw e;\n }\n finishProc(s, proc, code);\n }\n function doFork(s, parent) {\n const childPid = s.pidNext++;\n const childFds = /* @__PURE__ */ new Map();\n for (const [fd, e] of parent.fds) {\n childFds.set(fd, { ...e });\n if (e.kind === \"pipe\") bumpPipe(s, e, 1);\n }\n const child = makeProc(s, childPid, parent.pid, childFds);\n const pmem = parent.inst.exports.memory, cmem = child.inst.exports.memory;\n if (cmem.buffer.byteLength < pmem.buffer.byteLength) cmem.grow((pmem.buffer.byteLength - cmem.buffer.byteLength) / PAGE);\n new Uint8Array(cmem.buffer).set(new Uint8Array(pmem.buffer));\n for (const [k, v] of Object.entries(parent.inst.exports)) if (v instanceof WebAssembly.Global) child.inst.exports[k].value = v.value;\n child.MAIN_BUF = parent.MAIN_BUF;\n child.SLOT0 = parent.SLOT0;\n child.ctx.nextSlot = parent.ctx.nextSlot;\n child.slotByEnv = new Map(parent.slotByEnv);\n child.freeSlots = parent.freeSlots.slice();\n const total = s.procs.size;\n if (total * cmem.buffer.byteLength > s.stats.memPeak) s.stats.memPeak = total * cmem.buffer.byteLength;\n child.ctx.resume = 0;\n child.ctx.rewinding = true;\n child.inst.exports.asyncify_start_rewind(child.MAIN_BUF);\n s.runnable.push(child);\n parent.ctx.resume = childPid;\n parent.ctx.rewinding = true;\n parent.inst.exports.asyncify_start_rewind(parent.MAIN_BUF);\n s.runnable.push(parent);\n }\n function doWait(s, proc) {\n const t = proc.ctx.waitTarget;\n let pid = null;\n if (t > 0) {\n const e = s.exitStatus.get(t);\n if (e && e.ppid === proc.pid) pid = t;\n } else {\n for (const [p, e] of s.exitStatus) {\n if (e.ppid === proc.pid) {\n pid = p;\n break;\n }\n }\n }\n if (pid != null) {\n const st = s.exitStatus.get(pid).status;\n s.exitStatus.delete(pid);\n proc.ctx.resume = pid;\n proc.ctx.resumeStatus = st;\n resumeProc(proc);\n } else {\n s.waiters.push({ proc, targetPid: t });\n }\n }\n function finishProc(s, proc, code) {\n const st = (code & 255) << 8;\n s.procs.delete(proc.pid);\n for (const fd of [...proc.fds.keys()]) closeFd(s, proc, fd);\n if (proc.ppid === 0) s.rootExit = code;\n s.exitStatus.set(proc.pid, { status: st, ppid: proc.ppid });\n for (let i = 0; i < s.waiters.length; i++) {\n const w = s.waiters[i];\n if (w.proc.pid === proc.ppid && (w.targetPid === proc.pid || w.targetPid <= 0)) {\n s.waiters.splice(i, 1);\n w.proc.ctx.resume = proc.pid;\n w.proc.ctx.resumeStatus = st;\n s.exitStatus.delete(proc.pid);\n resumeProc(w.proc);\n break;\n }\n }\n }\n function composeFsDiff(s) {\n const filesWritten = {};\n for (const path of s.fs.written) {\n if (path.startsWith(\"etc/nimbus.bashrc\")) continue;\n const f = s.fs.files.get(path);\n if (f) filesWritten[path] = bytesToB64(f.bytes);\n }\n return {\n filesWritten,\n filesDeleted: [...s.fs.deleted],\n dirsCreated: [...s.fs.dirsCreated],\n // Deepest-first so the flush rmdirs children before their parents.\n dirsDeleted: [...s.fs.dirsDeleted].sort((a, b) => b.length - a.length),\n modesChanged: Object.fromEntries(s.fs.modesChanged),\n symlinksCreated: Object.fromEntries(s.fs.symlinksCreated)\n };\n }\n function pump(s) {\n try {\n while (s.runnable.length) {\n if (++s.steps > 5e6) throw new Error(\"bash-runner: runaway scheduler (>5M steps)\");\n step(s, s.runnable.shift());\n if (s.rootExit !== null) break;\n }\n } catch (e) {\n s.error = String(e && e.stack || e && e.message || e);\n }\n const out = s.out, err = s.err;\n s.out = \"\";\n s.err = \"\";\n const stats = { ...s.stats, steps: s.steps, missingWasi: [...s.missingWasi] };\n if (s.error) {\n S = null;\n return { state: \"error\", exitCode: 1, stdout: out, stderr: err, error: s.error, stats };\n }\n if (s.rootExit !== null || s.procs.size === 0) {\n const code = s.rootExit === null ? 0 : s.rootExit;\n const fsDiff = composeFsDiff(s);\n S = null;\n return { state: \"exited\", exitCode: code, stdout: out, stderr: err, fsDiff, stats };\n }\n if (s.stdin.waiters.length > 0) {\n return { state: \"need-input\", exitCode: 0, stdout: out, stderr: err, stats };\n }\n S = null;\n return { state: \"error\", exitCode: 1, stdout: out, stderr: err, error: \"bash-runner: deadlock \\u2014 live procs with empty run queue\", stats };\n }\n globalThis.__bashBoot = function __bashBoot(args) {\n try {\n S = newSession(args);\n initStdinQueued(S);\n const root = makeProc(S, S.pidNext++, 0, /* @__PURE__ */ new Map([\n [0, { kind: \"stdin\" }],\n [1, { kind: \"stdout\" }],\n [2, { kind: \"stderr\" }],\n [3, { kind: \"preopen\" }]\n ]));\n S.rootPid = root.pid;\n setupArena(root);\n S.runnable.push(root);\n return pump(S);\n } catch (e) {\n S = null;\n return { state: \"error\", exitCode: 1, stdout: \"\", stderr: \"\", error: \"boot failed: \" + String(e && e.message || e) };\n }\n };\n globalThis.__bashFeed = function __bashFeed(args) {\n if (!S) {\n return { state: \"error\", exitCode: 1, stdout: \"\", stderr: \"\", error: \"bash facet has no active session (warm isolate recycled?)\" };\n }\n try {\n if (args.data) {\n const b = te.encode(args.data);\n S.stdin.chunks.push(b);\n S.stdin.queued += b.length;\n }\n if (args.eof) S.stdin.closed = true;\n wakeStdin(S);\n return pump(S);\n } catch (e) {\n const s = S;\n S = null;\n return { state: \"error\", exitCode: 1, stdout: s ? s.out : \"\", stderr: s ? s.err : \"\", error: \"feed failed: \" + String(e && e.message || e) };\n }\n };\n})();\n";
@@ -0,0 +1,290 @@
1
+ import { z } from 'zod';
2
+ import { BASH_RUNNER_BODY_SRC } from './bash-runner.generated.js';
3
+ import { flushVfsDiff, snapshotVfs } from './vfs-snapshot.js';
4
+ import { requireVfsCred } from './os-contracts.js';
5
+ import { resolveVfsPath } from '../vfs/path.js';
6
+ const BashSliceSchema = z.object({
7
+ state: z.enum(['need-input', 'exited', 'error']),
8
+ exitCode: z.number().optional(),
9
+ stdout: z.string().optional(),
10
+ stderr: z.string().optional(),
11
+ error: z.string().optional(),
12
+ fsDiff: z.custom().optional(),
13
+ stats: z.record(z.string(), z.unknown()).optional(),
14
+ }).passthrough();
15
+ function normalizeSlice(raw) {
16
+ const parsed = BashSliceSchema.safeParse(raw);
17
+ if (!parsed.success)
18
+ return null;
19
+ return {
20
+ state: parsed.data.state,
21
+ exitCode: Number(parsed.data.exitCode ?? 0),
22
+ stdout: parsed.data.stdout || '',
23
+ stderr: parsed.data.stderr || '',
24
+ error: parsed.data.error,
25
+ fsDiff: parsed.data.fsDiff,
26
+ stats: parsed.data.stats,
27
+ };
28
+ }
29
+ function toArrayBuffer(bytes) {
30
+ const out = new ArrayBuffer(bytes.byteLength);
31
+ new Uint8Array(out).set(bytes);
32
+ return out;
33
+ }
34
+ function errorMessage(error) {
35
+ if (error instanceof Error)
36
+ return error.message;
37
+ return String(error);
38
+ }
39
+ async function bashFacetStep(args) {
40
+ const boot = Reflect.get(globalThis, '__bashBoot');
41
+ const feed = Reflect.get(globalThis, '__bashFeed');
42
+ if (typeof boot !== 'function' || typeof feed !== 'function') {
43
+ return {
44
+ state: 'error',
45
+ exitCode: 127,
46
+ stdout: '',
47
+ stderr: '',
48
+ error: 'bash-runner preamble missing (__bashBoot/__bashFeed not in scope)',
49
+ };
50
+ }
51
+ return args.op === 'boot' ? boot(args) : feed(args);
52
+ }
53
+ export async function createBashFacetSession(deps) {
54
+ const findFile = (relativePath) => {
55
+ const entry = deps.manifest.files.find((file) => file.path === relativePath);
56
+ return entry ? `${deps.installRoot}/${entry.path}` : null;
57
+ };
58
+ const bashWasmPath = findFile('share/bash/bash.async.wasm');
59
+ if (!bashWasmPath || !deps.vfs.exists(bashWasmPath)) {
60
+ throw new Error("bash.async.wasm missing (re-run 'nimbus install bash')");
61
+ }
62
+ const userEnv = { ...deps.env };
63
+ userEnv.HOME ||= '/home/user';
64
+ userEnv.PATH ||= '/bin:/usr/bin';
65
+ userEnv.TERM ||= 'dumb';
66
+ userEnv.NIMBUS_PWD = deps.cwd;
67
+ userEnv.BASH_ENV ||= '/etc/nimbus.bashrc';
68
+ userEnv.PWD = deps.cwd;
69
+ const extraRoots = [...(deps.extraRoots ?? [])];
70
+ if (userEnv.HOME !== '/home/user')
71
+ extraRoots.push(userEnv.HOME);
72
+ const fsSnapshot = snapshotVfs(deps.vfs, deps.cwd, { extraRoots });
73
+ if ('error' in fsSnapshot)
74
+ throw new Error(fsSnapshot.error);
75
+ const wasmModules = {
76
+ 'bash.async.wasm': toArrayBuffer(deps.vfs.readFile(bashWasmPath)),
77
+ };
78
+ for (const file of deps.manifest.files) {
79
+ const prefix = 'share/bash/coreutils/';
80
+ if (!file.path.startsWith(prefix) || !file.path.endsWith('.wasm'))
81
+ continue;
82
+ const name = file.path.slice(prefix.length, -'.wasm'.length);
83
+ const vfsPath = `${deps.installRoot}/${file.path}`;
84
+ if (deps.vfs.exists(vfsPath)) {
85
+ wasmModules[`cu_${name}.wasm`] = toArrayBuffer(deps.vfs.readFile(vfsPath));
86
+ }
87
+ }
88
+ const appletsPath = findFile('share/bash/coreutils/busybox.applets');
89
+ const busyboxApplets = appletsPath && deps.vfs.exists(appletsPath)
90
+ ? new TextDecoder().decode(deps.vfs.readFile(appletsPath))
91
+ .split('\n')
92
+ .map((line) => line.trim())
93
+ .filter(Boolean)
94
+ : [];
95
+ const facet = deps.facets.open({
96
+ tag: 'bash-runner',
97
+ concurrency: 1,
98
+ // No supervisor capability: bash is seeded with its subtree by value and
99
+ // hands the whole mutation back as a diff on exit, so it makes no syscall
100
+ // into the session while it runs.
101
+ preamble: BASH_RUNNER_PREAMBLE,
102
+ wasmModules,
103
+ });
104
+ let active = true;
105
+ let closed = false;
106
+ const submit = async (args) => {
107
+ const slice = normalizeSlice(await facet.submit(bashFacetStep, args, { timeoutMs: 300_000 }));
108
+ if (!slice)
109
+ throw new Error('facet returned an invalid payload');
110
+ if (slice.state === 'exited') {
111
+ if (slice.fsDiff)
112
+ flushVfsDiff(deps.vfs, slice.fsDiff);
113
+ active = false;
114
+ }
115
+ else if (slice.state === 'error') {
116
+ active = false;
117
+ }
118
+ return slice;
119
+ };
120
+ try {
121
+ const initial = await submit({
122
+ op: 'boot',
123
+ argv: deps.argv,
124
+ environ: Object.entries(userEnv).map(([key, value]) => `${key}=${value}`),
125
+ cwd: deps.cwd,
126
+ fsSnapshot: fsSnapshot.snapshot,
127
+ stdinData: deps.stdinData ?? '',
128
+ stdinClosed: deps.stdinClosed,
129
+ stdinTty: deps.stdinTty,
130
+ busyboxApplets,
131
+ });
132
+ return {
133
+ initial,
134
+ push(data, eof = false) {
135
+ if (closed)
136
+ throw new Error('bash facet session is closed');
137
+ return submit({ op: 'feed', data, eof });
138
+ },
139
+ async close() {
140
+ if (closed)
141
+ return;
142
+ try {
143
+ if (active)
144
+ await submit({ op: 'feed', data: '', eof: true });
145
+ }
146
+ catch {
147
+ // Session teardown is best-effort; the owning command already
148
+ // reports dispatch failures from boot/push.
149
+ }
150
+ finally {
151
+ closed = true;
152
+ facet.dispose();
153
+ }
154
+ },
155
+ };
156
+ }
157
+ catch (error) {
158
+ facet.dispose();
159
+ throw error;
160
+ }
161
+ }
162
+ /** bash flags that consume the following argv element. */
163
+ const BASH_OPT_WITH_ARG = new Set(['-c', '-o', '+o', '--rcfile', '--init-file']);
164
+ /**
165
+ * Locate the script-path argv element (first non-flag arg when -c is
166
+ * absent) so the handler can resolve it against the session cwd —
167
+ * bash's own cwd inside the facet starts at '/' until the BASH_ENV
168
+ * chdir runs, so relative script paths must be made absolute host-side.
169
+ * Returns the argv index or -1 (interactive / -c / stdin modes).
170
+ */
171
+ function findScriptArgIndex(argv) {
172
+ for (let i = 0; i < argv.length; i++) {
173
+ const a = argv[i];
174
+ if (a === '--')
175
+ return i + 1 < argv.length ? i + 1 : -1;
176
+ if (a === '-')
177
+ return -1; // read from stdin
178
+ if (a.startsWith('-') || a.startsWith('+')) {
179
+ if (BASH_OPT_WITH_ARG.has(a)) {
180
+ if (a === '-c')
181
+ return -1; // command string mode
182
+ i++; // skip the option's argument
183
+ }
184
+ continue;
185
+ }
186
+ return i;
187
+ }
188
+ return -1;
189
+ }
190
+ export function makeBashRunnerFactory(deps) {
191
+ return function bashRunnerFactory(manifest, installRoot, binName, _binKind) {
192
+ return async function bashBinHandler(ctx) {
193
+ // All VFS access (runtime wasm reads, script probes, snapshot,
194
+ // fsDiff writeback) runs as the INVOKING process credential —
195
+ // S2a enforcement applies to bash exactly as to ruby/python.
196
+ const cred = requireVfsCred('cred' in ctx ? ctx.cred : undefined, binName);
197
+ const vfs = deps.vfs.as(cred);
198
+ const argv = [...(ctx.args ?? [])];
199
+ const cwd = ctx.cwd || '/home/user';
200
+ // Resolve a relative script path against the session cwd.
201
+ const scriptIdx = findScriptArgIndex(argv);
202
+ const extraRoots = [];
203
+ if (scriptIdx >= 0) {
204
+ const abs = resolveVfsPath(argv[scriptIdx], cwd);
205
+ if (!vfs.exists(abs)) {
206
+ ctx.stderr.write(`${binName}: ${argv[scriptIdx]}: No such file or directory\n`);
207
+ return 127;
208
+ }
209
+ // Pass bash an ABSOLUTE path: the facet chdir's to the session
210
+ // cwd via BASH_ENV before opening the script, so a relative arg
211
+ // would resolve against cwd twice. resolveVfsPath returns a
212
+ // slash-less canonical key; re-anchor it at root.
213
+ argv[scriptIdx] = '/' + abs;
214
+ const dir = abs.replace(/\/[^/]*$/, '');
215
+ if (dir)
216
+ extraRoots.push(dir);
217
+ }
218
+ // stdin plumbing. A terminal-backed fd 0 feeds incrementally
219
+ // (interactive bash, `read` builtins); a piped stdin is drained
220
+ // upfront and closed so the scheduler never parks on it.
221
+ const stdinIsTty = typeof ctx.isFdTerminal === 'function' ? ctx.isFdTerminal(0) : !ctx.stdin;
222
+ const feedStream = ctx.terminalStdin ?? ctx.stdin;
223
+ let stdinData = '';
224
+ let stdinClosed = true;
225
+ if (stdinIsTty && feedStream) {
226
+ stdinClosed = false;
227
+ }
228
+ else if (ctx.stdin) {
229
+ stdinData = await ctx.stdin.readAll();
230
+ }
231
+ let session = null;
232
+ try {
233
+ session = await createBashFacetSession({
234
+ facets: deps.facets,
235
+ vfs,
236
+ manifest,
237
+ installRoot,
238
+ argv: [binName, ...argv],
239
+ env: ctx.env || {},
240
+ cwd,
241
+ stdinData,
242
+ stdinClosed,
243
+ stdinTty: stdinIsTty,
244
+ extraRoots,
245
+ });
246
+ let slice = session.initial;
247
+ for (;;) {
248
+ if (slice.stdout)
249
+ ctx.stdout.write(slice.stdout);
250
+ if (slice.stderr)
251
+ ctx.stderr.write(slice.stderr);
252
+ if (slice.state === 'exited') {
253
+ return slice.exitCode;
254
+ }
255
+ if (slice.state === 'error') {
256
+ ctx.stderr.write(`${binName}: ${slice.error || 'bash facet error'}\n`);
257
+ return slice.exitCode || 1;
258
+ }
259
+ // need-input: pull the next chunk from the terminal.
260
+ let data = '';
261
+ let eof = true;
262
+ if (!ctx.signal.aborted && feedStream) {
263
+ const chunk = await feedStream.read();
264
+ if (!ctx.signal.aborted) {
265
+ data = chunk === null ? '' : chunk.replace(/\r\n?/g, '\n');
266
+ eof = chunk === null;
267
+ }
268
+ }
269
+ slice = await session.push(data, eof);
270
+ }
271
+ }
272
+ catch (e) {
273
+ ctx.stderr.write(`${binName}: dispatch failed: ${errorMessage(e)}\n`);
274
+ return 1;
275
+ }
276
+ finally {
277
+ await session?.close();
278
+ }
279
+ };
280
+ };
281
+ }
282
+ /**
283
+ * Source string injected as the facet `preamble`. The facet's scope evaluates
284
+ * it verbatim so `__bashBoot` / `__bashFeed` are in scope when the user fn
285
+ * runs. Self-contained — no closure captures, no imports.
286
+ *
287
+ * The scheduler itself lives in `bash/preamble.ts` as real TypeScript; the build
288
+ * bundles it into `bash-runner.generated.ts`.
289
+ */
290
+ export const BASH_RUNNER_PREAMBLE = BASH_RUNNER_BODY_SRC;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * cpython-runner.ts — `python` / `python3` / `pip`, on CPython 3.13 built for
3
+ * wasm32-wasi.
4
+ *
5
+ * This replaces the Pyodide runner, and the reason is not the interpreter: it
6
+ * is the filesystem. Pyodide is CPython built with Emscripten, so it brings its
7
+ * own MEMFS, and every invocation had to copy the session's files in and diff
8
+ * them back out through vfs-snapshot.ts. That made Python the last runtime with
9
+ * a private, parallel filesystem. This build talks to runtime/wasi/preamble.ts
10
+ * like clang, bash and ruby do — `open()` in Python is the same syscall as
11
+ * `open()` in C — so there is nothing to copy and nothing to diff.
12
+ *
13
+ * What follows from that:
14
+ * - manifestVfs, not snapshotVfs: the facet is given sizes and modes and
15
+ * demand-loads the handful of files the program actually opens.
16
+ * - supervisorPid, not omitSupervisor: a pool without a supervisor can read
17
+ * the seeded manifest and can never write anything back. It looks like it
18
+ * works.
19
+ * - No Python-level socket shim. CPython's _socket is real here, over
20
+ * nimbus-net.c and the host's synthetic paths, so loopback is ordinary
21
+ * socket code rather than a monkey-patch.
22
+ *
23
+ * The interpreter is a WASI reactor (see packages/worker/wasm/python), because
24
+ * a command module's _start runs once and that only covers `python script.py`.
25
+ *
26
+ * FOUR THINGS THIS RUNTIME REDISCOVERED THE HARD WAY, ALL OF WHICH ruby-runner
27
+ * ALREADY KNEW. Read the list rather than finding a fifth:
28
+ * 1. Every entry into the VM goes through WebAssembly.promising, not only the
29
+ * calls known to park — a Suspending import traps on an unpromised stack
30
+ * even when it returns a plain integer.
31
+ * 2. The supervisor is adopted AFTER __wasiInitFS, which clears it on purpose,
32
+ * and the facet drains queued writes in a `finally`.
33
+ * 3. modes are seeded `{ '': 7, tmp: 7, home: 7 }` ahead of the manifest,
34
+ * because manifestVfs's walk skips the empty root — without it the preopen
35
+ * at '/' is mode 0 and every traversal under it is EACCES.
36
+ * 4. The loader pool is built per invocation, never cached: supervisorPid is
37
+ * baked into the SUPERVISOR binding at construction, so a held pool hands
38
+ * every later caller the first caller's write credential.
39
+ *
40
+ * One ordering in cpythonRunFacetFn is load-bearing and looks redundant: the
41
+ * supervisor stub is PUBLISHED on globalThis and only then adopted, because
42
+ * __wasiInitFS clears the adoption on purpose and the boot re-adopts it from
43
+ * there afterwards. Adopting once at the entry and deleting the Reflect.set
44
+ * leaves a guest that reads the seeded filesystem and silently writes nowhere —
45
+ * every write queued, none landed, no error anywhere. Ruby carries the same
46
+ * pair for the same reason. The drain in the `finally` is the other half: a
47
+ * program that wrote a file and then raised still wrote the file.
48
+ */
49
+ import type { Command } from '../substrate/lifo/commands/types.js';
50
+ import type { SqliteVFS } from '../vfs/sqlite-vfs.js';
51
+ import type { FacetHost } from './facet-host.js';
52
+ import type { RuntimeManifest } from './runtime-manifest.js';
53
+ /**
54
+ * The one canonical facet preamble. Composed in exactly one place: a
55
+ * hand-rolled second copy is how ruby-repl once drifted into booting a VM whose
56
+ * language prelude was missing.
57
+ */
58
+ export declare function buildCPythonPreamble(): string;
59
+ export interface CPythonFacetResult {
60
+ stdout: string;
61
+ stderr: string;
62
+ exitCode: number;
63
+ error?: string;
64
+ }
65
+ /**
66
+ * Start a program that outlives the invocation, and report where it went.
67
+ *
68
+ * A separate dependency rather than a branch, because a resident process is a
69
+ * property of the DEPLOYMENT and not of Python: it needs a substrate that can
70
+ * keep an actor alive between requests and route inbound HTTP into it. A host
71
+ * that has none does not get a degraded version — it gets none, and says so.
72
+ */
73
+ export type CPythonResidentStart = (spawn: {
74
+ /** VFS path of the interpreter. By path, not by value: it is 10.6 MiB. */
75
+ wasmVfsPath: string;
76
+ startArgs: Record<string, unknown>;
77
+ cwd: string;
78
+ command: string;
79
+ }) => Promise<CPythonFacetResult>;
80
+ export declare function makeCPythonRunnerFactory(deps: {
81
+ facets: FacetHost;
82
+ vfs: SqliteVFS;
83
+ /** Where a program that keeps serving goes. See {@link CPythonResidentStart}. */
84
+ startResident?: CPythonResidentStart;
85
+ }): (manifest: RuntimeManifest, installRoot: string, binName: string, binKind: string | undefined) => Command;
86
+ //# sourceMappingURL=cpython-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cpython-runner.d.ts","sourceRoot":"","sources":["../../src/runtime/cpython-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAkB,MAAM,qCAAqC,CAAC;AAEnF,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAKvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AASjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAsB7D;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAM7C;AAkFD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsCD;;;;;;;GAOG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE;IACzC,0EAA0E;IAC1E,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAElC,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,MAAM,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,SAAS,CAAC;IACf,iFAAiF;IACjF,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC,GAAG,CAAC,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,KAC7F,OAAO,CAkQV"}