sandboxedjs 0.1.28 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -17942,22 +17942,22 @@ async function runCPythonProgram(ctx, source, argv, scriptDir, stdinText) {
17942
17942
  });
17943
17943
  } catch {
17944
17944
  }
17945
- const encoder7 = new TextEncoder();
17946
- const decoder7 = new TextDecoder();
17945
+ const encoder8 = new TextEncoder();
17946
+ const decoder8 = new TextDecoder();
17947
17947
  py.setStdout({
17948
17948
  write: (buffer) => {
17949
- ctx.write(decoder7.decode(buffer));
17949
+ ctx.write(decoder8.decode(buffer));
17950
17950
  return buffer.length;
17951
17951
  }
17952
17952
  });
17953
17953
  py.setStderr({
17954
17954
  write: (buffer) => {
17955
- ctx.stderr.write(decoder7.decode(buffer));
17955
+ ctx.stderr.write(decoder8.decode(buffer));
17956
17956
  return buffer.length;
17957
17957
  }
17958
17958
  });
17959
17959
  if (stdinText !== null) {
17960
- const bytes2 = encoder7.encode(stdinText);
17960
+ const bytes2 = encoder8.encode(stdinText);
17961
17961
  let offset = 0;
17962
17962
  py.setStdin({
17963
17963
  read: (buffer) => {
@@ -18008,9 +18008,9 @@ async function runCPythonRepl(ctx) {
18008
18008
  }
18009
18009
  mountContainerDirs(py, ctx);
18010
18010
  bootstrap(py, ctx, [""], null);
18011
- const decoder7 = new TextDecoder();
18012
- py.setStdout({ write: (data) => (ctx.write(decoder7.decode(data)), data.length) });
18013
- py.setStderr({ write: (data) => (ctx.stderr.write(decoder7.decode(data)), data.length) });
18011
+ const decoder8 = new TextDecoder();
18012
+ py.setStdout({ write: (data) => (ctx.write(decoder8.decode(data)), data.length) });
18013
+ py.setStderr({ write: (data) => (ctx.stderr.write(decoder8.decode(data)), data.length) });
18014
18014
  ctx.line(`Python ${String(py.runPython("import sys; sys.version.split()[0]"))} (Pyodide)`);
18015
18015
  ctx.line('Type "help()" for more information.');
18016
18016
  let source = "";
@@ -19621,15 +19621,15 @@ var Session = class {
19621
19621
  async run(command, opts = {}) {
19622
19622
  if (this.closed) throw new Error("session is closed");
19623
19623
  const combined = [];
19624
- const decoder7 = new TextDecoder();
19624
+ const decoder8 = new TextDecoder();
19625
19625
  const stdout = new BufferSink((chunk) => {
19626
- const text2 = decoder7.decode(chunk, { stream: true });
19626
+ const text2 = decoder8.decode(chunk, { stream: true });
19627
19627
  combined.push(text2);
19628
19628
  opts.onStdout?.(text2);
19629
19629
  this.hooks.onStdout?.(text2);
19630
19630
  });
19631
19631
  const stderr = new BufferSink((chunk) => {
19632
- const text2 = decoder7.decode(chunk, { stream: true });
19632
+ const text2 = decoder8.decode(chunk, { stream: true });
19633
19633
  combined.push(text2);
19634
19634
  opts.onStderr?.(text2);
19635
19635
  this.hooks.onStderr?.(text2);
@@ -21815,7 +21815,7 @@ var VirtualClientRequest = class extends streamModule4.Writable {
21815
21815
  };
21816
21816
  let settled = false;
21817
21817
  try {
21818
- const response = this.router.activePortsIncludes(this.target.port) && isLoopback(this.target.hostname) ? await this.viaRouter(body) : await this.viaFetch(body);
21818
+ const response = isLoopback(this.target.hostname) ? await this.viaRouter(body) : await this.viaFetch(body);
21819
21819
  if (this.destroyedByUser) return;
21820
21820
  clearTimeout(this.timer);
21821
21821
  settled = true;
@@ -21837,6 +21837,12 @@ var VirtualClientRequest = class extends streamModule4.Writable {
21837
21837
  }
21838
21838
  }
21839
21839
  async viaRouter(body) {
21840
+ if (!this.router.activePortsIncludes(this.target.port)) {
21841
+ throw Object.assign(
21842
+ new Error(`connect ECONNREFUSED ${this.target.hostname}:${this.target.port}`),
21843
+ { code: "ECONNREFUSED", errno: -61, syscall: "connect", address: this.target.hostname, port: this.target.port }
21844
+ );
21845
+ }
21840
21846
  const result = await this.router.request(this.target.port, {
21841
21847
  method: this.target.method,
21842
21848
  path: this.target.path,
@@ -22093,7 +22099,7 @@ var ChildProcess = class extends EventEmitter4 {
22093
22099
  queueMicrotask(() => this.emit("close", code, null));
22094
22100
  }
22095
22101
  };
22096
- function createChildProcessModule(spawnChild, defaultCwd) {
22102
+ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn) {
22097
22103
  const throughShell = (command, options) => {
22098
22104
  const shell = typeof options.shell === "string" ? options.shell : "/bin/sh";
22099
22105
  return { file: shell, args: ["-c", command] };
@@ -22150,20 +22156,79 @@ ${err.join("")}`),
22150
22156
  exec,
22151
22157
  execFile,
22152
22158
  fork: (modulePath, args = [], options = {}) => spawn("node", [modulePath, ...args], options),
22153
- execSync: unavailable("execSync"),
22154
- execFileSync: unavailable("execFileSync"),
22155
- spawnSync: unavailable("spawnSync"),
22159
+ ...buildSyncFamily(syncSpawn, throughShell, defaultCwd),
22156
22160
  ChildProcess
22157
22161
  };
22158
22162
  }
22163
+ function buildSyncFamily(syncSpawn, throughShell, defaultCwd) {
22164
+ if (!syncSpawn) {
22165
+ return {
22166
+ execSync: unavailable("execSync"),
22167
+ execFileSync: unavailable("execFileSync"),
22168
+ spawnSync: unavailable("spawnSync")
22169
+ };
22170
+ }
22171
+ const run = (file3, args, options) => {
22172
+ const resolved = options.shell ? throughShell([file3, ...args].join(" "), options) : { file: file3, args };
22173
+ const input = options.input === void 0 ? void 0 : typeof options.input === "string" ? options.input : new TextDecoder().decode(options.input);
22174
+ return syncSpawn({
22175
+ command: resolved.file,
22176
+ args: resolved.args,
22177
+ cwd: options.cwd ?? defaultCwd(),
22178
+ ...options.env ? { env: options.env } : {},
22179
+ ...input === void 0 ? {} : { input }
22180
+ });
22181
+ };
22182
+ const asOutput = (text2, options) => options.encoding === "buffer" || options.encoding === void 0 ? Buffer2.from(text2) : text2;
22183
+ const orThrow = (result, command, options) => {
22184
+ if (result.error) {
22185
+ throw Object.assign(new Error(result.error.message), {
22186
+ ...result.error.code ? { code: result.error.code } : {},
22187
+ stdout: asOutput(result.stdout, options),
22188
+ stderr: asOutput(result.stderr, options)
22189
+ });
22190
+ }
22191
+ if (result.status !== 0) {
22192
+ throw Object.assign(new Error(`Command failed: ${command}
22193
+ ${result.stderr}`), {
22194
+ status: result.status,
22195
+ stdout: asOutput(result.stdout, options),
22196
+ stderr: asOutput(result.stderr, options)
22197
+ });
22198
+ }
22199
+ return asOutput(result.stdout, options);
22200
+ };
22201
+ const spawnSync = (file3, args = [], options = {}) => {
22202
+ const [list, opts] = Array.isArray(args) ? [args, options] : [[], args];
22203
+ const result = run(file3, list, opts);
22204
+ return {
22205
+ pid: 0,
22206
+ status: result.status,
22207
+ signal: result.signal,
22208
+ stdout: asOutput(result.stdout, opts),
22209
+ stderr: asOutput(result.stderr, opts),
22210
+ output: [null, asOutput(result.stdout, opts), asOutput(result.stderr, opts)],
22211
+ ...result.error ? { error: Object.assign(new Error(result.error.message), result.error.code ? { code: result.error.code } : {}) } : {}
22212
+ };
22213
+ };
22214
+ const execFileSync = (file3, args = [], options = {}) => {
22215
+ const [list, opts] = Array.isArray(args) ? [args, options] : [[], args];
22216
+ return orThrow(run(file3, list, opts), [file3, ...list].join(" "), opts);
22217
+ };
22218
+ const execSync = (command, options = {}) => orThrow(run(command, [], { ...options, shell: options.shell ?? true }), command, options);
22219
+ return { spawnSync, execFileSync, execSync };
22220
+ }
22159
22221
  function normalize2(options, callback) {
22160
22222
  if (typeof options === "function") return [{}, options];
22161
22223
  return [options ?? {}, callback];
22162
22224
  }
22163
22225
  function unavailable(name) {
22164
- return () => {
22226
+ return (...args) => {
22227
+ const file3 = typeof args[0] === "string" ? args[0] : void 0;
22228
+ const list = Array.isArray(args[1]) ? args[1].map(String) : [];
22229
+ const command = file3 ? [file3, ...list].join(" ") : void 0;
22165
22230
  const error = new Error(
22166
- `child_process.${name} is not supported in the sandboxed runtime: it would have to block the JavaScript thread. Use the asynchronous form instead.`
22231
+ `child_process.${name} is not supported in the sandboxed runtime` + (command ? ` (tried to run: ${command})` : "") + `: the caller, the child and the event loop share one thread, so blocking the caller would also stop the child. Run it with the asynchronous form (spawn/exec/execFile), or run the command from the container shell.`
22167
22232
  );
22168
22233
  error.code = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
22169
22234
  throw error;
@@ -22227,7 +22292,11 @@ function parseKeys(input) {
22227
22292
  const name = CSI_KEYS[sequence];
22228
22293
  keys.push({
22229
22294
  sequence: `\x1B${sequence}`,
22230
- name: name ?? "undefined",
22295
+ /* Node leaves this undefined for a sequence it does not recognise.
22296
+ * The string "undefined" is not the same thing: a caller testing
22297
+ * `key.name === undefined` would take an unknown key for a known
22298
+ * one. */
22299
+ name,
22231
22300
  ctrl: false,
22232
22301
  /* xterm reports modifiers as `;5` (control) and `;2` (shift) before
22233
22302
  * the final letter. */
@@ -22787,7 +22856,7 @@ function createCoreModules(options) {
22787
22856
  };
22788
22857
  const http = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "http:") : createUnsupportedModule("http");
22789
22858
  const https = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "https:") : createUnsupportedModule("https");
22790
- const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd) : createUnsupportedModule("child_process");
22859
+ const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd, options.syncSpawn) : createUnsupportedModule("child_process");
22791
22860
  const readline = createReadlineModule(() => processObject.stdin, () => processObject.stdout);
22792
22861
  const dns = createDnsModule();
22793
22862
  const builtins = {
@@ -22848,6 +22917,7 @@ function createCoreModules(options) {
22848
22917
  pendingUnrefed: timers.pendingUnrefed,
22849
22918
  /** Client requests sent but not yet read to completion. */
22850
22919
  pendingRequests: () => inFlightRequests,
22920
+ cancelTimers: timers.cancelAll,
22851
22921
  writeStdin: (data) => {
22852
22922
  if (options.interactiveStdin) stdin.write(data);
22853
22923
  },
@@ -23529,6 +23599,27 @@ function createTrackedTimers() {
23529
23599
  live.delete(handle);
23530
23600
  native(handle);
23531
23601
  };
23602
+ const cancelAll = () => {
23603
+ for (const handle of [...live, ...unrefed]) {
23604
+ const state = states.get(handle);
23605
+ if (!state) continue;
23606
+ state.active = false;
23607
+ try {
23608
+ clearTimeout(state.native);
23609
+ } catch {
23610
+ }
23611
+ try {
23612
+ clearInterval(state.native);
23613
+ } catch {
23614
+ }
23615
+ try {
23616
+ (globalThis.clearImmediate ?? clearTimeout)(state.native);
23617
+ } catch {
23618
+ }
23619
+ }
23620
+ live.clear();
23621
+ unrefed.clear();
23622
+ };
23532
23623
  return {
23533
23624
  api: {
23534
23625
  setTimeout: setTimeoutTracked,
@@ -23539,7 +23630,8 @@ function createTrackedTimers() {
23539
23630
  clearImmediate: (handle) => clear2(handle, globalThis.clearImmediate ?? clearTimeout)
23540
23631
  },
23541
23632
  pending: () => live.size,
23542
- pendingUnrefed: () => unrefed.size
23633
+ pendingUnrefed: () => unrefed.size,
23634
+ cancelAll
23543
23635
  };
23544
23636
  }
23545
23637
  function createTimerPromises() {
@@ -23993,6 +24085,193 @@ var MemoryVolume = class {
23993
24085
  }
23994
24086
  };
23995
24087
 
24088
+ // src/runtime/mirroring-volume.ts
24089
+ init_path();
24090
+ var MirroringVolume = class {
24091
+ constructor(inner = new MemoryVolume()) {
24092
+ this.inner = inner;
24093
+ }
24094
+ inner;
24095
+ mirror;
24096
+ root = "/";
24097
+ /**
24098
+ * Start mirroring the tree under `root`, seeding it with what is there now.
24099
+ *
24100
+ * Called once the Rolldown binding is known to be in play; before that a
24101
+ * container pays nothing for this.
24102
+ */
24103
+ attach(mirror, root) {
24104
+ this.mirror = mirror;
24105
+ this.root = clean(root);
24106
+ this.seed();
24107
+ }
24108
+ detach() {
24109
+ this.mirror = void 0;
24110
+ }
24111
+ /** Copy the whole subtree across. The only bulk operation that remains. */
24112
+ seed() {
24113
+ const mirror = this.mirror;
24114
+ if (!mirror) return;
24115
+ try {
24116
+ mirror.rmSync?.(this.root, { recursive: true, force: true });
24117
+ } catch {
24118
+ }
24119
+ const copy = (path) => {
24120
+ let stat2;
24121
+ try {
24122
+ stat2 = this.inner.lstatSync(path);
24123
+ } catch {
24124
+ return;
24125
+ }
24126
+ if (stat2.isDirectory()) {
24127
+ this.safely(() => mirror.mkdirSync(path, { recursive: true }));
24128
+ for (const name of this.inner.readdirSync(path)) copy(join(path, name));
24129
+ } else if (stat2.isSymbolicLink()) {
24130
+ this.safely(() => {
24131
+ mirror.mkdirSync(dirname(path), { recursive: true });
24132
+ mirror.symlinkSync(this.inner.readlinkSync(path), path);
24133
+ });
24134
+ } else if (!path.endsWith(".node")) {
24135
+ this.safely(() => {
24136
+ mirror.mkdirSync(dirname(path), { recursive: true });
24137
+ mirror.writeFileSync(path, this.inner.readFileSync(path));
24138
+ });
24139
+ }
24140
+ };
24141
+ copy(this.root);
24142
+ }
24143
+ /** Is this path inside the mirrored subtree? */
24144
+ mirrored(path) {
24145
+ if (!this.mirror) return false;
24146
+ const clean2 = clean(path);
24147
+ return this.root === "/" || clean2 === this.root || clean2.startsWith(`${this.root}/`);
24148
+ }
24149
+ /**
24150
+ * Run a mirror update, swallowing failure.
24151
+ *
24152
+ * The mirror is a read-only cache for another engine. If it rejects
24153
+ * something — an unsupported operation, a path it has not seen — the
24154
+ * container's own write has still happened and must still succeed.
24155
+ */
24156
+ safely(update) {
24157
+ try {
24158
+ update();
24159
+ } catch {
24160
+ }
24161
+ }
24162
+ /** Push a path's current state across, whatever it is now. */
24163
+ sync(path) {
24164
+ const mirror = this.mirror;
24165
+ if (!mirror || !this.mirrored(path)) return;
24166
+ let stat2;
24167
+ try {
24168
+ stat2 = this.inner.lstatSync(path);
24169
+ } catch {
24170
+ this.safely(() => {
24171
+ if (mirror.rmSync) mirror.rmSync(path, { recursive: true, force: true });
24172
+ else mirror.unlinkSync?.(path);
24173
+ });
24174
+ return;
24175
+ }
24176
+ if (stat2.isDirectory()) {
24177
+ this.safely(() => mirror.mkdirSync(path, { recursive: true }));
24178
+ return;
24179
+ }
24180
+ if (stat2.isSymbolicLink()) {
24181
+ this.safely(() => {
24182
+ mirror.mkdirSync(dirname(path), { recursive: true });
24183
+ mirror.symlinkSync(this.inner.readlinkSync(path), path);
24184
+ });
24185
+ return;
24186
+ }
24187
+ if (path.endsWith(".node")) return;
24188
+ this.safely(() => {
24189
+ mirror.mkdirSync(dirname(path), { recursive: true });
24190
+ mirror.writeFileSync(path, this.inner.readFileSync(path));
24191
+ });
24192
+ }
24193
+ // ── reads: straight through ───────────────────────────────────────────────
24194
+ readFileSync(path) {
24195
+ return this.inner.readFileSync(path);
24196
+ }
24197
+ readdirSync(path) {
24198
+ return this.inner.readdirSync(path);
24199
+ }
24200
+ lstatSync(path) {
24201
+ return this.inner.lstatSync(path);
24202
+ }
24203
+ readlinkSync(path) {
24204
+ return this.inner.readlinkSync(path);
24205
+ }
24206
+ getStats() {
24207
+ return this.inner.getStats();
24208
+ }
24209
+ // ── writes: applied, then mirrored ────────────────────────────────────────
24210
+ writeFileSync(path, data) {
24211
+ this.inner.writeFileSync(path, data);
24212
+ this.sync(path);
24213
+ }
24214
+ appendFileSync(path, data) {
24215
+ this.inner.appendFileSync(path, data);
24216
+ this.sync(path);
24217
+ }
24218
+ mkdirSync(path, options) {
24219
+ this.inner.mkdirSync(path, options);
24220
+ this.sync(path);
24221
+ }
24222
+ rmdirSync(path) {
24223
+ this.inner.rmdirSync(path);
24224
+ this.sync(path);
24225
+ }
24226
+ unlinkSync(path) {
24227
+ this.inner.unlinkSync(path);
24228
+ this.sync(path);
24229
+ }
24230
+ renameSync(from, to) {
24231
+ this.inner.renameSync(from, to);
24232
+ this.sync(from);
24233
+ this.sync(to);
24234
+ }
24235
+ symlinkSync(target, path) {
24236
+ this.inner.symlinkSync(target, path);
24237
+ this.sync(path);
24238
+ }
24239
+ linkSync(existing, path) {
24240
+ this.inner.linkSync(existing, path);
24241
+ this.sync(path);
24242
+ }
24243
+ truncateSync(path, length) {
24244
+ this.inner.truncateSync(path, length);
24245
+ this.sync(path);
24246
+ }
24247
+ /* Permissions and timestamps do not change what a bundler resolves, and
24248
+ * `memfs` is stricter about them than this volume is. Applied here only. */
24249
+ chmodSync(path, mode) {
24250
+ this.inner.chmodSync(path, mode);
24251
+ }
24252
+ lchmodSync(path, mode) {
24253
+ this.inner.lchmodSync(path, mode);
24254
+ }
24255
+ chownSync(path, uid, gid) {
24256
+ this.inner.chownSync(path, uid, gid);
24257
+ }
24258
+ lchownSync(path, uid, gid) {
24259
+ this.inner.lchownSync(path, uid, gid);
24260
+ }
24261
+ utimesSync(path, atime, mtime) {
24262
+ this.inner.utimesSync(path, atime, mtime);
24263
+ }
24264
+ // ── snapshots ─────────────────────────────────────────────────────────────
24265
+ snapshot() {
24266
+ return this.inner.snapshot();
24267
+ }
24268
+ /** A restore replaces everything, so the mirror is rebuilt rather than patched. */
24269
+ restore(entries) {
24270
+ this.inner.restore(entries);
24271
+ this.seed();
24272
+ }
24273
+ };
24274
+
23996
24275
  // src/runtime/host-esbuild.ts
23997
24276
  var ASYNC_API = ["transform", "build", "context", "formatMessages", "analyzeMetafile"];
23998
24277
  function createHostEsbuild() {
@@ -24062,6 +24341,7 @@ function ensureProcessGlobal() {
24062
24341
  }
24063
24342
 
24064
24343
  // src/runtime/host-rolldown.ts
24344
+ var BUNDLER_HINT = "If this is a bundler pre-bundling the worker away, exclude the binding from dependency optimisation \u2014 in Vite:\n\n optimizeDeps: { exclude: ['@rolldown/binding-wasm32-wasi'] }";
24065
24345
  async function loadHostRolldownBinding() {
24066
24346
  if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) {
24067
24347
  throw new Error(
@@ -24075,7 +24355,13 @@ async function loadHostRolldownBinding() {
24075
24355
  if (typeof process !== "undefined" && process.env?.SANDBOXEDJS_DEBUG) {
24076
24356
  console.error("[sandboxedjs] Rolldown WASI binding unavailable:", error);
24077
24357
  }
24078
- throw new Error("The optional Rolldown WASI binding could not be loaded.", { cause: error });
24358
+ const reason = error instanceof Error ? error.message : String(error);
24359
+ throw new Error(
24360
+ `The optional Rolldown WASI binding could not be loaded: ${reason}` + (typeof window === "undefined" ? "" : `
24361
+
24362
+ ${BUNDLER_HINT}`),
24363
+ { cause: error }
24364
+ );
24079
24365
  }
24080
24366
  }
24081
24367
 
@@ -24115,6 +24401,7 @@ var LocalProcess = class extends EventEmitter4 {
24115
24401
  inputEnded = false;
24116
24402
  pendingInput = [];
24117
24403
  resolveKilled;
24404
+ cleanup;
24118
24405
  killedPromise = new Promise((resolve2) => {
24119
24406
  this.resolveKilled = resolve2;
24120
24407
  });
@@ -24153,13 +24440,32 @@ var LocalProcess = class extends EventEmitter4 {
24153
24440
  for (const chunk of this.pendingInput.splice(0)) input(chunk);
24154
24441
  if (this.inputEnded) end();
24155
24442
  }
24443
+ /**
24444
+ * End the process now.
24445
+ *
24446
+ * Killing has to settle `completion`, not merely record the intent. A
24447
+ * program with an interval outstanding has work pending forever, so the
24448
+ * runtime's own idle check will never end it — before this, killing such a
24449
+ * process left the caller awaiting a promise that could not resolve.
24450
+ */
24156
24451
  kill(_signal = "SIGTERM") {
24452
+ if (this.killed) return;
24157
24453
  this.killed = true;
24158
24454
  this.resolveKilled();
24455
+ this.cleanup?.();
24456
+ this.finish(137);
24159
24457
  }
24160
24458
  waitForKill() {
24161
24459
  return this.killedPromise;
24162
24460
  }
24461
+ isKilled() {
24462
+ return this.killed;
24463
+ }
24464
+ /** Registered by the task so a kill can release what the program still holds. */
24465
+ onKill(cleanup) {
24466
+ this.cleanup = cleanup;
24467
+ if (this.killed) cleanup();
24468
+ }
24163
24469
  /** End the process now, for an asynchronous `process.exit`. */
24164
24470
  exitNow(code) {
24165
24471
  this.finish(code);
@@ -24321,7 +24627,10 @@ function removeEscapedErrorReporting() {
24321
24627
  }
24322
24628
  var DRAIN_TURNS = 4;
24323
24629
  var LocalRuntimePod = class _LocalRuntimePod {
24324
- volume = new MemoryVolume();
24630
+ /* Wrapped so that a foreign filesystem — Rolldown's WebAssembly memfs — can
24631
+ * be kept in step as writes happen, rather than deep-copied once per spawn.
24632
+ * With nothing attached this is a straight pass-through. */
24633
+ volume = new MirroringVolume(new MemoryVolume());
24325
24634
  packages;
24326
24635
  instanceId = `sbx-${Math.random().toString(36).slice(2)}`;
24327
24636
  router = new VirtualHttpRouter();
@@ -24412,6 +24721,10 @@ var LocalRuntimePod = class _LocalRuntimePod {
24412
24721
  onRawMode: (enabled) => proc.emit("rawmode", enabled)
24413
24722
  });
24414
24723
  proc.acceptInput((data) => core.writeStdin(data), () => core.endStdin());
24724
+ proc.onKill(() => {
24725
+ core.cancelTimers();
24726
+ this.router.closeOwner(owner);
24727
+ });
24415
24728
  engine = new CommonJsEngine(this.volume, {
24416
24729
  cwd,
24417
24730
  builtins: core.builtins,
@@ -24421,7 +24734,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
24421
24734
  });
24422
24735
  try {
24423
24736
  await engine.run(script);
24424
- await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin, core.pendingRequests);
24737
+ await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin, core.pendingRequests, () => proc.isKilled());
24425
24738
  if (this.router.activePorts(owner).length) {
24426
24739
  await proc.waitForKill();
24427
24740
  return 137;
@@ -24442,13 +24755,13 @@ var LocalRuntimePod = class _LocalRuntimePod {
24442
24755
  */
24443
24756
  async prepareRolldown(cwd, env2) {
24444
24757
  const specifier = "@rolldown/binding-wasm32-wasi";
24445
- if (!this.modules[specifier] && packageInstalled(this.volume, cwd, "rolldown")) {
24758
+ if (!this.modules[specifier] && packageIsInstalled(this.volume, cwd, "rolldown")) {
24446
24759
  this.rolldownBinding ??= loadHostRolldownBinding();
24447
24760
  const binding = await this.rolldownBinding;
24448
24761
  if (binding) this.modules[specifier] = binding;
24449
24762
  }
24450
24763
  if (this.modules[specifier]) {
24451
- syncRolldownFileSystem(this.modules[specifier], this.volume, cwd);
24764
+ attachRolldownMirror(this.modules[specifier], this.volume, cwd);
24452
24765
  env2.NAPI_RS_FORCE_WASI ??= "true";
24453
24766
  }
24454
24767
  }
@@ -24467,18 +24780,18 @@ var LocalRuntimePod = class _LocalRuntimePod {
24467
24780
  * A plain script that has genuinely finished falls straight through both,
24468
24781
  * costing a handful of empty turns.
24469
24782
  */
24470
- async settle(owner, pendingHandles, pendingUnrefed, readingStdin, pendingRequests) {
24783
+ async settle(owner, pendingHandles, pendingUnrefed, readingStdin, pendingRequests, killed) {
24471
24784
  for (let turn = 0; turn < DRAIN_TURNS; turn++) {
24472
- if (this.router.activePorts(owner).length) return;
24785
+ if (this.router.activePorts(owner).length || killed()) return;
24473
24786
  await new Promise((resolve2) => setTimeout(resolve2, 0));
24474
24787
  }
24475
24788
  if (pendingHandles() === 0 && pendingRequests() === 0 && pendingUnrefed() > 0 && !readingStdin()) {
24476
24789
  const deadline = Date.now() + 1e3;
24477
- while (!this.router.activePorts(owner).length && Date.now() < deadline) {
24790
+ while (!this.router.activePorts(owner).length && !killed() && Date.now() < deadline) {
24478
24791
  await new Promise((resolve2) => setTimeout(resolve2, 5));
24479
24792
  }
24480
24793
  }
24481
- while (!this.router.activePorts(owner).length && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
24794
+ while (!this.router.activePorts(owner).length && !killed() && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
24482
24795
  await new Promise((resolve2) => setTimeout(resolve2, 5));
24483
24796
  }
24484
24797
  }
@@ -24530,7 +24843,7 @@ function formatError(error) {
24530
24843
  function isRecord(value) {
24531
24844
  return typeof value === "object" && value !== null && !Array.isArray(value);
24532
24845
  }
24533
- function packageInstalled(volume, cwd, wanted) {
24846
+ function packageIsInstalled(volume, cwd, wanted) {
24534
24847
  for (let dir3 = clean(cwd); ; dir3 = dirname(dir3)) {
24535
24848
  if (packageTreeContains(volume, join(dir3, "node_modules"), wanted, /* @__PURE__ */ new Set())) return true;
24536
24849
  if (dir3 === "/") return false;
@@ -24559,30 +24872,565 @@ function directory(volume, path) {
24559
24872
  return false;
24560
24873
  }
24561
24874
  }
24562
- function syncRolldownFileSystem(binding, volume, root) {
24875
+ function attachRolldownMirror(binding, volume, root) {
24563
24876
  const fs = binding?.__fs;
24564
24877
  if (!fs?.mkdirSync || !fs?.writeFileSync) return;
24565
- try {
24566
- fs.rmSync?.(root, { recursive: true, force: true });
24567
- } catch {
24878
+ volume.attach(fs, root);
24879
+ }
24880
+
24881
+ // src/runtime/sync-channel.ts
24882
+ var STATE = 0;
24883
+ var LENGTH = 1;
24884
+ var MORE = 2;
24885
+ var CONTROL_WORDS = 4;
24886
+ var STATE_REQUEST = 1;
24887
+ var STATE_RESPONSE = 2;
24888
+ var STATE_CONTINUE = 3;
24889
+ var STATE_CLOSED = 4;
24890
+ function createSyncChannelBuffers(capacityBytes = 1 << 20) {
24891
+ return {
24892
+ control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
24893
+ data: new SharedArrayBuffer(capacityBytes)
24894
+ };
24895
+ }
24896
+ function syncChannelSupported() {
24897
+ if (typeof SharedArrayBuffer !== "function") return false;
24898
+ if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return false;
24899
+ if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) return false;
24900
+ return true;
24901
+ }
24902
+ var SyncChannelServer = class {
24903
+ constructor(buffers, handle) {
24904
+ this.handle = handle;
24905
+ this.control = new Int32Array(buffers.control);
24906
+ this.data = new Uint8Array(buffers.data);
24907
+ this.capacity = this.data.length;
24568
24908
  }
24569
- const copy = (path) => {
24570
- const stat2 = volume.lstatSync(path);
24571
- if (stat2.isDirectory()) {
24572
- fs.mkdirSync(path, { recursive: true });
24573
- for (const name of volume.readdirSync(path)) copy(join(path, name));
24574
- } else if (stat2.isSymbolicLink()) {
24575
- fs.mkdirSync(dirname(path), { recursive: true });
24909
+ handle;
24910
+ control;
24911
+ data;
24912
+ capacity;
24913
+ /** Request chunks gathered so far, and the response still to be sent. */
24914
+ incoming = [];
24915
+ outgoing = null;
24916
+ sent = 0;
24917
+ closed = false;
24918
+ /** Call from the wake message the client sends after each chunk. */
24919
+ async pump() {
24920
+ if (this.closed) return;
24921
+ const state = Atomics.load(this.control, STATE);
24922
+ if (state === STATE_REQUEST) {
24923
+ const size = Atomics.load(this.control, LENGTH);
24924
+ this.incoming.push(this.data.slice(0, size));
24925
+ if (Atomics.load(this.control, MORE) === 1) {
24926
+ this.publish(STATE_CONTINUE);
24927
+ return;
24928
+ }
24929
+ const request = concat3(this.incoming);
24930
+ this.incoming = [];
24931
+ let response;
24576
24932
  try {
24577
- fs.symlinkSync(volume.readlinkSync(path), path);
24933
+ response = await this.handle(request);
24578
24934
  } catch {
24935
+ response = new Uint8Array();
24579
24936
  }
24580
- } else if (!path.endsWith(".node")) {
24581
- fs.mkdirSync(dirname(path), { recursive: true });
24582
- fs.writeFileSync(path, volume.readFileSync(path));
24937
+ if (this.closed) return;
24938
+ this.outgoing = response;
24939
+ this.sent = 0;
24940
+ this.sendChunk();
24941
+ return;
24583
24942
  }
24943
+ if (state === STATE_CONTINUE) this.sendChunk();
24944
+ }
24945
+ /** Release a blocked client, e.g. when the container is torn down. */
24946
+ close() {
24947
+ if (this.closed) return;
24948
+ this.closed = true;
24949
+ Atomics.store(this.control, STATE, STATE_CLOSED);
24950
+ Atomics.notify(this.control, STATE);
24951
+ }
24952
+ sendChunk() {
24953
+ const payload = this.outgoing ?? new Uint8Array();
24954
+ const size = Math.min(this.capacity, payload.length - this.sent);
24955
+ this.data.set(payload.subarray(this.sent, this.sent + size), 0);
24956
+ this.sent += size;
24957
+ Atomics.store(this.control, LENGTH, size);
24958
+ Atomics.store(this.control, MORE, this.sent < payload.length ? 1 : 0);
24959
+ this.publish(STATE_RESPONSE);
24960
+ }
24961
+ publish(state) {
24962
+ Atomics.store(this.control, STATE, state);
24963
+ Atomics.notify(this.control, STATE);
24964
+ }
24965
+ };
24966
+ function concat3(parts) {
24967
+ if (parts.length === 1) return parts[0];
24968
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
24969
+ const joined = new Uint8Array(total);
24970
+ let at = 0;
24971
+ for (const part of parts) {
24972
+ joined.set(part, at);
24973
+ at += part.length;
24974
+ }
24975
+ return joined;
24976
+ }
24977
+
24978
+ // src/runtime/remote-volume.ts
24979
+ var encoder7 = new TextEncoder();
24980
+ var decoder7 = new TextDecoder();
24981
+ function encodeFrame(header, body) {
24982
+ const json = encoder7.encode(JSON.stringify(header));
24983
+ const frame = new Uint8Array(4 + json.length + (body?.length ?? 0));
24984
+ new DataView(frame.buffer).setUint32(0, json.length, true);
24985
+ frame.set(json, 4);
24986
+ if (body?.length) frame.set(body, 4 + json.length);
24987
+ return frame;
24988
+ }
24989
+ function decodeFrame(frame) {
24990
+ const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
24991
+ const headerLength = view.getUint32(0, true);
24992
+ const header = JSON.parse(decoder7.decode(frame.subarray(4, 4 + headerLength)));
24993
+ return { header, body: frame.subarray(4 + headerLength) };
24994
+ }
24995
+ function flattenStat(stat2) {
24996
+ return {
24997
+ kind: stat2.isDirectory() ? "directory" : stat2.isSymbolicLink() ? "symlink" : "file",
24998
+ mode: stat2.mode,
24999
+ size: stat2.size,
25000
+ uid: stat2.uid,
25001
+ gid: stat2.gid,
25002
+ ino: stat2.ino,
25003
+ nlink: stat2.nlink,
25004
+ atimeMs: stat2.atimeMs,
25005
+ mtimeMs: stat2.mtimeMs,
25006
+ ctimeMs: stat2.ctimeMs,
25007
+ birthtimeMs: stat2.birthtimeMs
24584
25008
  };
24585
- copy(clean(root));
25009
+ }
25010
+ var BINARY_RESULTS = /* @__PURE__ */ new Set(["readFileSync"]);
25011
+ var BINARY_ARGUMENTS = /* @__PURE__ */ new Set(["writeFileSync", "appendFileSync"]);
25012
+ function serveVolume(volume) {
25013
+ return (request) => {
25014
+ const { header, body } = decodeFrame(request);
25015
+ const target = volume;
25016
+ try {
25017
+ const method = target[header.op];
25018
+ if (typeof method !== "function") {
25019
+ return encodeFrame({ ok: false, error: { message: `unknown volume operation: ${header.op}` } });
25020
+ }
25021
+ const plain = header.args.map((value) => value === null ? void 0 : value);
25022
+ const args = BINARY_ARGUMENTS.has(header.op) ? [plain[0], body] : header.op === "utimesSync" ? [plain[0], new Date(plain[1]), new Date(plain[2])] : plain;
25023
+ const result = method.apply(volume, args);
25024
+ if (BINARY_RESULTS.has(header.op)) {
25025
+ return encodeFrame({ ok: true }, result);
25026
+ }
25027
+ if (header.op === "lstatSync") {
25028
+ return encodeFrame({ ok: true, value: flattenStat(result) });
25029
+ }
25030
+ return encodeFrame({ ok: true, value: result });
25031
+ } catch (error) {
25032
+ const failure = error;
25033
+ return encodeFrame({
25034
+ ok: false,
25035
+ error: { ...failure.code ? { code: failure.code } : {}, message: failure.message ?? String(error) }
25036
+ });
25037
+ }
25038
+ };
25039
+ }
25040
+
25041
+ // src/runtime/sync-syscalls.ts
25042
+ var SPAWN_OP = "spawnSync";
25043
+ function serveSyncSyscalls(options) {
25044
+ const volumeHandler = serveVolume(options.volume);
25045
+ return async (request) => {
25046
+ const { header } = decodeFrame(request);
25047
+ if (header.op !== SPAWN_OP) return volumeHandler(request);
25048
+ try {
25049
+ const result = await options.spawnChild(header.args[0]);
25050
+ return encodeFrame({ ok: true, value: result });
25051
+ } catch (error) {
25052
+ const failure = error;
25053
+ return encodeFrame({
25054
+ ok: true,
25055
+ value: {
25056
+ status: null,
25057
+ stdout: "",
25058
+ stderr: "",
25059
+ signal: null,
25060
+ error: { ...failure.code ? { code: failure.code } : {}, message: failure.message ?? String(error) }
25061
+ }
25062
+ });
25063
+ }
25064
+ };
25065
+ }
25066
+
25067
+ // src/runtime/worker-host.ts
25068
+ function defaultWorkerUrl() {
25069
+ return new URL("./worker-entry.js", import.meta.url);
25070
+ }
25071
+ function hasDomWorker() {
25072
+ return typeof Worker === "function" && typeof document !== "undefined";
25073
+ }
25074
+ async function startRuntimeWorker(options = {}) {
25075
+ const url = options.url ?? defaultWorkerUrl();
25076
+ const worker = hasDomWorker() ? await startDomWorker(url) : await startNodeWorker(url, options.workerData ?? {});
25077
+ await new Promise((resolve2, reject) => {
25078
+ const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
25079
+ worker.onMessage((message) => {
25080
+ if (message?.type === "sandboxedjs:ready") {
25081
+ clearTimeout(timer);
25082
+ resolve2();
25083
+ }
25084
+ });
25085
+ worker.onError((error) => {
25086
+ clearTimeout(timer);
25087
+ reject(error instanceof Error ? error : new Error(String(error)));
25088
+ });
25089
+ });
25090
+ return worker;
25091
+ }
25092
+ async function startDomWorker(url) {
25093
+ const worker = new Worker(url, { type: "module" });
25094
+ const listeners = [];
25095
+ const errors = [];
25096
+ worker.addEventListener("message", (event) => {
25097
+ for (const listener of listeners) listener(event.data);
25098
+ });
25099
+ worker.addEventListener("error", (event) => {
25100
+ const detail = event.message || "the runtime worker failed to load";
25101
+ for (const listener of errors) listener(new Error(detail));
25102
+ });
25103
+ return {
25104
+ postMessage: (message) => worker.postMessage(message),
25105
+ onMessage: (listener) => {
25106
+ listeners.push(listener);
25107
+ },
25108
+ onError: (listener) => {
25109
+ errors.push(listener);
25110
+ },
25111
+ terminate: () => worker.terminate()
25112
+ };
25113
+ }
25114
+ async function startNodeWorker(url, workerData) {
25115
+ const specifier = ["node", "worker_threads"].join(":");
25116
+ const { Worker: NodeWorker } = await import(
25117
+ /* @vite-ignore */
25118
+ /* webpackIgnore: true */
25119
+ specifier
25120
+ );
25121
+ const worker = new NodeWorker(url, { workerData });
25122
+ return {
25123
+ postMessage: (message) => worker.postMessage(message),
25124
+ onMessage: (listener) => worker.on("message", listener),
25125
+ onError: (listener) => worker.on("error", listener),
25126
+ terminate: () => worker.terminate()
25127
+ };
25128
+ }
25129
+ var WorkerProcess = class extends EventEmitter4 {
25130
+ constructor(worker, release) {
25131
+ super();
25132
+ this.worker = worker;
25133
+ this.release = release;
25134
+ super.on("error", () => {
25135
+ });
25136
+ this.completion = new Promise((resolve2) => {
25137
+ this.resolveCompletion = resolve2;
25138
+ });
25139
+ }
25140
+ worker;
25141
+ release;
25142
+ completion;
25143
+ resolveCompletion;
25144
+ settled = false;
25145
+ out = [];
25146
+ err = [];
25147
+ started = false;
25148
+ pendingInput = [];
25149
+ inputEnded = false;
25150
+ on(event, listener) {
25151
+ return super.on(event, listener);
25152
+ }
25153
+ /** Called once the guest has been told to start; releases anything buffered. */
25154
+ begin() {
25155
+ this.started = true;
25156
+ for (const chunk of this.pendingInput.splice(0)) this.worker.postMessage({ type: "stdin", data: chunk });
25157
+ if (this.inputEnded) this.worker.postMessage({ type: "stdin-end" });
25158
+ }
25159
+ output(text2) {
25160
+ this.out.push(text2);
25161
+ this.emit("output", text2);
25162
+ }
25163
+ error(text2) {
25164
+ this.err.push(text2);
25165
+ this.emit("error", text2);
25166
+ }
25167
+ /**
25168
+ * Deliver input to the running program.
25169
+ *
25170
+ * Held until the guest has been started, for the same reason the in-realm
25171
+ * pod holds it: an interactive session's first keystrokes arrive while the
25172
+ * program is still loading, and dropping them loses the answer to a prompt.
25173
+ */
25174
+ write(data) {
25175
+ if (this.started) this.worker.postMessage({ type: "stdin", data });
25176
+ else this.pendingInput.push(data);
25177
+ }
25178
+ endInput() {
25179
+ this.inputEnded = true;
25180
+ if (this.started) this.worker.postMessage({ type: "stdin-end" });
25181
+ }
25182
+ kill(_signal = "SIGTERM") {
25183
+ this.worker.postMessage({ type: "kill" });
25184
+ this.finish(137);
25185
+ }
25186
+ finish(exitCode) {
25187
+ if (this.settled) return;
25188
+ this.settled = true;
25189
+ this.emit("exit", exitCode);
25190
+ this.resolveCompletion({ exitCode, stdout: this.out.join(""), stderr: this.err.join("") });
25191
+ this.release();
25192
+ }
25193
+ };
25194
+ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25195
+ workerUrl;
25196
+ /** Live workers, so teardown can stop them all. */
25197
+ live = /* @__PURE__ */ new Set();
25198
+ constructor(options) {
25199
+ super(options);
25200
+ this.workerUrl = options.workerUrl;
25201
+ }
25202
+ /**
25203
+ * Boot a Worker-backed pod, or return null when this host cannot support one.
25204
+ *
25205
+ * Declining is a first-class outcome. `SharedArrayBuffer` needs cross-origin
25206
+ * isolation, a bundler may have made the guest script unreachable, and a host
25207
+ * that supplied its own module objects has handed over things no thread
25208
+ * boundary can carry. In every case the caller falls back to the in-realm pod
25209
+ * and keeps working.
25210
+ */
25211
+ static async tryBoot(options = {}) {
25212
+ if (!syncChannelSupported()) return null;
25213
+ if (options.modules && Object.keys(options.modules).length > 0) return null;
25214
+ const pod = new _WorkerRuntimePod(options);
25215
+ try {
25216
+ const probe = await startRuntimeWorker({ ...options.workerUrl ? { url: options.workerUrl } : {}, timeoutMs: 1e4 });
25217
+ await probe.terminate();
25218
+ return pod;
25219
+ } catch {
25220
+ pod.teardown();
25221
+ return null;
25222
+ }
25223
+ }
25224
+ async spawn(command, args = [], options = {}) {
25225
+ if (command !== "node" && command !== "nodejs") return super.spawn(command, args, options);
25226
+ const script = args[0];
25227
+ if (!script) return super.spawn(command, args, options);
25228
+ const cwd = typeof options.cwd === "string" ? options.cwd : this.workdir;
25229
+ if (this.needsHostModules(cwd)) return super.spawn(command, args, options);
25230
+ return await this.spawnInWorker(script, args, cwd, options);
25231
+ }
25232
+ async spawnInWorker(script, args, cwd, options) {
25233
+ const env2 = { ...this.env, ...isRecord2(options.env) ? options.env : {} };
25234
+ const owner = `${this.instanceId}:${Math.random().toString(36).slice(2)}`;
25235
+ const buffers = createSyncChannelBuffers();
25236
+ const worker = await startRuntimeWorker({
25237
+ ...this.workerUrl ? { url: this.workerUrl } : {},
25238
+ timeoutMs: 15e3
25239
+ });
25240
+ const server = new SyncChannelServer(buffers, serveSyncSyscalls({
25241
+ volume: this.volume,
25242
+ spawnChild: (request) => this.runChildToCompletion(request)
25243
+ }));
25244
+ const entry = { worker, server };
25245
+ this.live.add(entry);
25246
+ const process2 = new WorkerProcess(worker, () => {
25247
+ this.live.delete(entry);
25248
+ server.close();
25249
+ this.closeProxies(owner);
25250
+ void worker.terminate();
25251
+ });
25252
+ const children = /* @__PURE__ */ new Map();
25253
+ worker.onMessage((raw) => {
25254
+ const message = raw;
25255
+ switch (message?.type) {
25256
+ case "wake":
25257
+ void server.pump();
25258
+ return;
25259
+ case "output":
25260
+ process2.output(String(message.text));
25261
+ return;
25262
+ case "error":
25263
+ process2.error(String(message.text));
25264
+ return;
25265
+ case "rawmode":
25266
+ process2.emit("rawmode", Boolean(message.enabled));
25267
+ return;
25268
+ case "exit":
25269
+ process2.finish(Number(message.code));
25270
+ return;
25271
+ case "listen":
25272
+ this.proxyPort(Number(message.port), owner, worker);
25273
+ return;
25274
+ case "http-response":
25275
+ this.settleProxied(Number(message.id), message.response);
25276
+ return;
25277
+ case "child-start":
25278
+ this.startChild(worker, children, message);
25279
+ return;
25280
+ case "child-stdin":
25281
+ children.get(message.id)?.sendStdin?.(String(message.data));
25282
+ return;
25283
+ case "child-stdin-end":
25284
+ children.get(message.id)?.endStdin?.();
25285
+ return;
25286
+ case "child-kill":
25287
+ children.get(message.id)?.kill(String(message.signal));
25288
+ return;
25289
+ default:
25290
+ return;
25291
+ }
25292
+ });
25293
+ worker.postMessage({
25294
+ type: "start",
25295
+ buffers,
25296
+ script,
25297
+ cwd,
25298
+ env: env2,
25299
+ argv: Array.isArray(options.argv) ? options.argv : ["/usr/bin/node", script, ...args.slice(1)],
25300
+ aliases: this.aliases,
25301
+ ...typeof options.stdinPath === "string" ? { stdinPath: options.stdinPath } : {},
25302
+ ...options.interactiveStdin ? { interactiveStdin: true } : {},
25303
+ ...options.tty ? { tty: true } : {}
25304
+ });
25305
+ process2.begin();
25306
+ return process2;
25307
+ }
25308
+ /** Start an asynchronous child on the host's behalf and relay its events. */
25309
+ startChild(worker, children, message) {
25310
+ const handle = this.processManager.spawn(message.config);
25311
+ children.set(message.id, handle);
25312
+ for (const event of ["stdout", "stderr", "exit"]) {
25313
+ handle.on(event, (value) => worker.postMessage({ type: "child-event", id: message.id, event, value }));
25314
+ }
25315
+ handle.exec();
25316
+ }
25317
+ /** Run a child to completion and collect it, for the guest's `spawnSync`. */
25318
+ runChildToCompletion(request) {
25319
+ return new Promise((resolve2) => {
25320
+ let handle;
25321
+ try {
25322
+ handle = this.processManager.spawn({
25323
+ command: request.command,
25324
+ args: request.args,
25325
+ cwd: request.cwd,
25326
+ ...request.env ? { env: request.env } : {}
25327
+ });
25328
+ } catch (error) {
25329
+ const failure = error;
25330
+ resolve2({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure.code ? { code: failure.code } : {}, message: failure.message } });
25331
+ return;
25332
+ }
25333
+ let stdout = "";
25334
+ let stderr = "";
25335
+ handle.on("stdout", (text2) => {
25336
+ stdout += text2;
25337
+ });
25338
+ handle.on("stderr", (text2) => {
25339
+ stderr += text2;
25340
+ });
25341
+ handle.on("exit", (code) => resolve2({ status: code, stdout, stderr, signal: null }));
25342
+ handle.exec();
25343
+ if (request.input !== void 0) {
25344
+ handle.sendStdin?.(request.input);
25345
+ }
25346
+ handle.endStdin?.();
25347
+ });
25348
+ }
25349
+ // ── HTTP servers living on another thread ─────────────────────────────────
25350
+ proxies = /* @__PURE__ */ new Map();
25351
+ waiting = /* @__PURE__ */ new Map();
25352
+ nextRequestId = 1;
25353
+ /**
25354
+ * Register a stand-in for a server that is actually running in the Worker.
25355
+ *
25356
+ * The router only knows how to reach servers on this thread, so each bound
25357
+ * port gets a local server whose whole job is to forward and wait.
25358
+ */
25359
+ proxyPort(port, owner, worker) {
25360
+ if (this.proxies.has(port)) return;
25361
+ const server = new VirtualHttpServer(this.router, owner, (request, response) => {
25362
+ void this.forward(worker, port, request, response);
25363
+ });
25364
+ try {
25365
+ server.listen(port);
25366
+ this.proxies.set(port, server);
25367
+ } catch {
25368
+ }
25369
+ }
25370
+ async forward(worker, port, request, response) {
25371
+ const chunks = [];
25372
+ for await (const chunk of request) chunks.push(chunk);
25373
+ const body = concat4(chunks);
25374
+ const id = this.nextRequestId++;
25375
+ const answered = new Promise((resolve2) => this.waiting.set(id, resolve2));
25376
+ worker.postMessage({
25377
+ type: "http-request",
25378
+ id,
25379
+ port,
25380
+ init: { method: request.method, path: request.url, headers: request.headers, body }
25381
+ });
25382
+ let result;
25383
+ try {
25384
+ result = await answered;
25385
+ } catch (error) {
25386
+ this.waiting.delete(id);
25387
+ response.writeHead(500, "Internal Server Error", {});
25388
+ response.end(new TextEncoder().encode(error instanceof Error ? error.message : String(error)));
25389
+ return;
25390
+ }
25391
+ response.writeHead(Number(result.statusCode ?? 200), String(result.statusMessage ?? ""), result.headers ?? {});
25392
+ response.end(result.body ?? new Uint8Array());
25393
+ }
25394
+ settleProxied(id, response) {
25395
+ const resolve2 = this.waiting.get(id);
25396
+ if (!resolve2) return;
25397
+ this.waiting.delete(id);
25398
+ resolve2(response);
25399
+ }
25400
+ closeProxies(owner) {
25401
+ for (const [port, server] of [...this.proxies]) {
25402
+ if (server.owner !== owner) continue;
25403
+ server.close();
25404
+ this.proxies.delete(port);
25405
+ }
25406
+ }
25407
+ teardown() {
25408
+ for (const { worker, server } of [...this.live]) {
25409
+ server.close();
25410
+ void worker.terminate();
25411
+ }
25412
+ this.live.clear();
25413
+ super.teardown();
25414
+ }
25415
+ /** Does anything under `cwd` need a module only the host can supply? */
25416
+ needsHostModules(cwd) {
25417
+ return packageIsInstalled(this.volume, cwd, "rolldown");
25418
+ }
25419
+ };
25420
+ function isRecord2(value) {
25421
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25422
+ }
25423
+ function concat4(parts) {
25424
+ if (parts.length === 0) return new Uint8Array();
25425
+ if (parts.length === 1) return parts[0];
25426
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
25427
+ const joined = new Uint8Array(total);
25428
+ let at = 0;
25429
+ for (const part of parts) {
25430
+ joined.set(part, at);
25431
+ at += part.length;
25432
+ }
25433
+ return joined;
24586
25434
  }
24587
25435
 
24588
25436
  // src/container/container.ts
@@ -24629,11 +25477,13 @@ var Container = class _Container {
24629
25477
  // ── boot ──────────────────────────────────────────────────────────────────
24630
25478
  static async create(opts = {}) {
24631
25479
  if (opts.python) configurePython(opts.python);
24632
- const pod = opts.pod ?? await LocalRuntimePod.boot({
25480
+ const podOptions = {
24633
25481
  workdir: opts.cwd ?? "/",
24634
25482
  env: opts.env ?? {},
24635
25483
  ...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
24636
- });
25484
+ };
25485
+ const workerOptions = { ...podOptions, ...opts.workerUrl ? { workerUrl: opts.workerUrl } : {} };
25486
+ const pod = opts.pod ?? (opts.isolation === "realm" ? null : await WorkerRuntimePod.tryBoot(workerOptions)) ?? await LocalRuntimePod.boot(podOptions);
24637
25487
  const kernel = new Kernel({
24638
25488
  pod,
24639
25489
  hostname: opts.hostname ?? "sandbox",
@@ -24875,15 +25725,15 @@ var Container = class _Container {
24875
25725
  }
24876
25726
  makeStdio(opts) {
24877
25727
  const combined = [];
24878
- const decoder7 = new TextDecoder();
25728
+ const decoder8 = new TextDecoder();
24879
25729
  const stdout = new BufferSink((chunk) => {
24880
- const text2 = decoder7.decode(chunk, { stream: true });
25730
+ const text2 = decoder8.decode(chunk, { stream: true });
24881
25731
  combined.push(text2);
24882
25732
  opts.onStdout?.(text2);
24883
25733
  this.hooks.onStdout?.(text2);
24884
25734
  });
24885
25735
  const stderr = new BufferSink((chunk) => {
24886
- const text2 = decoder7.decode(chunk, { stream: true });
25736
+ const text2 = decoder8.decode(chunk, { stream: true });
24887
25737
  combined.push(text2);
24888
25738
  opts.onStderr?.(text2);
24889
25739
  this.hooks.onStderr?.(text2);
@@ -25496,6 +26346,6 @@ init_expand();
25496
26346
  init_builtins();
25497
26347
  var src_default = createContainer;
25498
26348
 
25499
- export { ArithError, BufferSink, CallbackSink, CleanPackageInstaller, CommandRegistry, CommonJsEngine, Container, ContainerFs, ERRNO, FileInput, FileOutput, IncompleteInputError, KERNEL_NAME, KERNEL_RELEASE, Kernel, LocalRuntimePod, MemoryVolume, NODE_VERSION, NPM_VERSION, NetworkStack, NullInput, NullOutput, OS_RELEASE, PYTHON_VERSION, Pipe, Process, ProcessTable, ROOT_CRED, SIGNALS, SIGNAL_NAMES, Session, Shell, ShellExit, Lexer as ShellLexer, ShellSyntaxError, Stats, SysError, TeeOutput, Terminal, UserDatabase, Variables, Vfs, VirtualHttpRouter, VirtualHttpServer, VirtualIncomingMessage, VirtualServerResponse, WASM_ALIASES, allCommands, applyChmod, braceExpand, buildRootfs, builtinNames, captureStdio, configureCPython, configurePython, createChildProcessModule, createContainer, createContext, createCoreModules, src_default as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, extractNpmTarball, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, installUserland, isBuiltinName, isCPythonAvailable, isPythonAvailable, isSysError, looksLikeEsm, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path_exports as posixPath, resetPidCounter, shellQuote, strerror, transformEsm, unameInfo };
26349
+ export { ArithError, BufferSink, CallbackSink, CleanPackageInstaller, CommandRegistry, CommonJsEngine, Container, ContainerFs, ERRNO, FileInput, FileOutput, IncompleteInputError, KERNEL_NAME, KERNEL_RELEASE, Kernel, LocalRuntimePod, MemoryVolume, NODE_VERSION, NPM_VERSION, NetworkStack, NullInput, NullOutput, OS_RELEASE, PYTHON_VERSION, Pipe, Process, ProcessTable, ROOT_CRED, SIGNALS, SIGNAL_NAMES, Session, Shell, ShellExit, Lexer as ShellLexer, ShellSyntaxError, Stats, SysError, TeeOutput, Terminal, UserDatabase, Variables, Vfs, VirtualHttpRouter, VirtualHttpServer, VirtualIncomingMessage, VirtualServerResponse, WASM_ALIASES, allCommands, applyChmod, braceExpand, buildRootfs, builtinNames, captureStdio, configureCPython, configurePython, createChildProcessModule, createContainer, createContext, createCoreModules, src_default as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, extractNpmTarball, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, installUserland, isBuiltinName, isCPythonAvailable, isPythonAvailable, isSysError, looksLikeEsm, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path_exports as posixPath, resetPidCounter, shellQuote, startRuntimeWorker, strerror, syncChannelSupported, transformEsm, unameInfo };
25500
26350
  //# sourceMappingURL=index.js.map
25501
26351
  //# sourceMappingURL=index.js.map