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.cjs CHANGED
@@ -17959,22 +17959,22 @@ async function runCPythonProgram(ctx, source, argv, scriptDir, stdinText) {
17959
17959
  });
17960
17960
  } catch {
17961
17961
  }
17962
- const encoder7 = new TextEncoder();
17963
- const decoder7 = new TextDecoder();
17962
+ const encoder8 = new TextEncoder();
17963
+ const decoder8 = new TextDecoder();
17964
17964
  py.setStdout({
17965
17965
  write: (buffer) => {
17966
- ctx.write(decoder7.decode(buffer));
17966
+ ctx.write(decoder8.decode(buffer));
17967
17967
  return buffer.length;
17968
17968
  }
17969
17969
  });
17970
17970
  py.setStderr({
17971
17971
  write: (buffer) => {
17972
- ctx.stderr.write(decoder7.decode(buffer));
17972
+ ctx.stderr.write(decoder8.decode(buffer));
17973
17973
  return buffer.length;
17974
17974
  }
17975
17975
  });
17976
17976
  if (stdinText !== null) {
17977
- const bytes2 = encoder7.encode(stdinText);
17977
+ const bytes2 = encoder8.encode(stdinText);
17978
17978
  let offset = 0;
17979
17979
  py.setStdin({
17980
17980
  read: (buffer) => {
@@ -18025,9 +18025,9 @@ async function runCPythonRepl(ctx) {
18025
18025
  }
18026
18026
  mountContainerDirs(py, ctx);
18027
18027
  bootstrap(py, ctx, [""], null);
18028
- const decoder7 = new TextDecoder();
18029
- py.setStdout({ write: (data) => (ctx.write(decoder7.decode(data)), data.length) });
18030
- py.setStderr({ write: (data) => (ctx.stderr.write(decoder7.decode(data)), data.length) });
18028
+ const decoder8 = new TextDecoder();
18029
+ py.setStdout({ write: (data) => (ctx.write(decoder8.decode(data)), data.length) });
18030
+ py.setStderr({ write: (data) => (ctx.stderr.write(decoder8.decode(data)), data.length) });
18031
18031
  ctx.line(`Python ${String(py.runPython("import sys; sys.version.split()[0]"))} (Pyodide)`);
18032
18032
  ctx.line('Type "help()" for more information.');
18033
18033
  let source = "";
@@ -19638,15 +19638,15 @@ var Session = class {
19638
19638
  async run(command, opts = {}) {
19639
19639
  if (this.closed) throw new Error("session is closed");
19640
19640
  const combined = [];
19641
- const decoder7 = new TextDecoder();
19641
+ const decoder8 = new TextDecoder();
19642
19642
  const stdout = new BufferSink((chunk) => {
19643
- const text2 = decoder7.decode(chunk, { stream: true });
19643
+ const text2 = decoder8.decode(chunk, { stream: true });
19644
19644
  combined.push(text2);
19645
19645
  opts.onStdout?.(text2);
19646
19646
  this.hooks.onStdout?.(text2);
19647
19647
  });
19648
19648
  const stderr = new BufferSink((chunk) => {
19649
- const text2 = decoder7.decode(chunk, { stream: true });
19649
+ const text2 = decoder8.decode(chunk, { stream: true });
19650
19650
  combined.push(text2);
19651
19651
  opts.onStderr?.(text2);
19652
19652
  this.hooks.onStderr?.(text2);
@@ -21832,7 +21832,7 @@ var VirtualClientRequest = class extends streamModule4__default.default.Writable
21832
21832
  };
21833
21833
  let settled = false;
21834
21834
  try {
21835
- const response = this.router.activePortsIncludes(this.target.port) && isLoopback(this.target.hostname) ? await this.viaRouter(body) : await this.viaFetch(body);
21835
+ const response = isLoopback(this.target.hostname) ? await this.viaRouter(body) : await this.viaFetch(body);
21836
21836
  if (this.destroyedByUser) return;
21837
21837
  clearTimeout(this.timer);
21838
21838
  settled = true;
@@ -21854,6 +21854,12 @@ var VirtualClientRequest = class extends streamModule4__default.default.Writable
21854
21854
  }
21855
21855
  }
21856
21856
  async viaRouter(body) {
21857
+ if (!this.router.activePortsIncludes(this.target.port)) {
21858
+ throw Object.assign(
21859
+ new Error(`connect ECONNREFUSED ${this.target.hostname}:${this.target.port}`),
21860
+ { code: "ECONNREFUSED", errno: -61, syscall: "connect", address: this.target.hostname, port: this.target.port }
21861
+ );
21862
+ }
21857
21863
  const result = await this.router.request(this.target.port, {
21858
21864
  method: this.target.method,
21859
21865
  path: this.target.path,
@@ -22110,7 +22116,7 @@ var ChildProcess = class extends EventEmitter4__default.default {
22110
22116
  queueMicrotask(() => this.emit("close", code, null));
22111
22117
  }
22112
22118
  };
22113
- function createChildProcessModule(spawnChild, defaultCwd) {
22119
+ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn) {
22114
22120
  const throughShell = (command, options) => {
22115
22121
  const shell = typeof options.shell === "string" ? options.shell : "/bin/sh";
22116
22122
  return { file: shell, args: ["-c", command] };
@@ -22167,20 +22173,79 @@ ${err.join("")}`),
22167
22173
  exec,
22168
22174
  execFile,
22169
22175
  fork: (modulePath, args = [], options = {}) => spawn("node", [modulePath, ...args], options),
22170
- execSync: unavailable("execSync"),
22171
- execFileSync: unavailable("execFileSync"),
22172
- spawnSync: unavailable("spawnSync"),
22176
+ ...buildSyncFamily(syncSpawn, throughShell, defaultCwd),
22173
22177
  ChildProcess
22174
22178
  };
22175
22179
  }
22180
+ function buildSyncFamily(syncSpawn, throughShell, defaultCwd) {
22181
+ if (!syncSpawn) {
22182
+ return {
22183
+ execSync: unavailable("execSync"),
22184
+ execFileSync: unavailable("execFileSync"),
22185
+ spawnSync: unavailable("spawnSync")
22186
+ };
22187
+ }
22188
+ const run = (file3, args, options) => {
22189
+ const resolved = options.shell ? throughShell([file3, ...args].join(" "), options) : { file: file3, args };
22190
+ const input = options.input === void 0 ? void 0 : typeof options.input === "string" ? options.input : new TextDecoder().decode(options.input);
22191
+ return syncSpawn({
22192
+ command: resolved.file,
22193
+ args: resolved.args,
22194
+ cwd: options.cwd ?? defaultCwd(),
22195
+ ...options.env ? { env: options.env } : {},
22196
+ ...input === void 0 ? {} : { input }
22197
+ });
22198
+ };
22199
+ const asOutput = (text2, options) => options.encoding === "buffer" || options.encoding === void 0 ? Buffer2.from(text2) : text2;
22200
+ const orThrow = (result, command, options) => {
22201
+ if (result.error) {
22202
+ throw Object.assign(new Error(result.error.message), {
22203
+ ...result.error.code ? { code: result.error.code } : {},
22204
+ stdout: asOutput(result.stdout, options),
22205
+ stderr: asOutput(result.stderr, options)
22206
+ });
22207
+ }
22208
+ if (result.status !== 0) {
22209
+ throw Object.assign(new Error(`Command failed: ${command}
22210
+ ${result.stderr}`), {
22211
+ status: result.status,
22212
+ stdout: asOutput(result.stdout, options),
22213
+ stderr: asOutput(result.stderr, options)
22214
+ });
22215
+ }
22216
+ return asOutput(result.stdout, options);
22217
+ };
22218
+ const spawnSync = (file3, args = [], options = {}) => {
22219
+ const [list, opts] = Array.isArray(args) ? [args, options] : [[], args];
22220
+ const result = run(file3, list, opts);
22221
+ return {
22222
+ pid: 0,
22223
+ status: result.status,
22224
+ signal: result.signal,
22225
+ stdout: asOutput(result.stdout, opts),
22226
+ stderr: asOutput(result.stderr, opts),
22227
+ output: [null, asOutput(result.stdout, opts), asOutput(result.stderr, opts)],
22228
+ ...result.error ? { error: Object.assign(new Error(result.error.message), result.error.code ? { code: result.error.code } : {}) } : {}
22229
+ };
22230
+ };
22231
+ const execFileSync = (file3, args = [], options = {}) => {
22232
+ const [list, opts] = Array.isArray(args) ? [args, options] : [[], args];
22233
+ return orThrow(run(file3, list, opts), [file3, ...list].join(" "), opts);
22234
+ };
22235
+ const execSync = (command, options = {}) => orThrow(run(command, [], { ...options, shell: options.shell ?? true }), command, options);
22236
+ return { spawnSync, execFileSync, execSync };
22237
+ }
22176
22238
  function normalize2(options, callback) {
22177
22239
  if (typeof options === "function") return [{}, options];
22178
22240
  return [options ?? {}, callback];
22179
22241
  }
22180
22242
  function unavailable(name) {
22181
- return () => {
22243
+ return (...args) => {
22244
+ const file3 = typeof args[0] === "string" ? args[0] : void 0;
22245
+ const list = Array.isArray(args[1]) ? args[1].map(String) : [];
22246
+ const command = file3 ? [file3, ...list].join(" ") : void 0;
22182
22247
  const error = new Error(
22183
- `child_process.${name} is not supported in the sandboxed runtime: it would have to block the JavaScript thread. Use the asynchronous form instead.`
22248
+ `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.`
22184
22249
  );
22185
22250
  error.code = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
22186
22251
  throw error;
@@ -22244,7 +22309,11 @@ function parseKeys(input) {
22244
22309
  const name = CSI_KEYS[sequence];
22245
22310
  keys.push({
22246
22311
  sequence: `\x1B${sequence}`,
22247
- name: name ?? "undefined",
22312
+ /* Node leaves this undefined for a sequence it does not recognise.
22313
+ * The string "undefined" is not the same thing: a caller testing
22314
+ * `key.name === undefined` would take an unknown key for a known
22315
+ * one. */
22316
+ name,
22248
22317
  ctrl: false,
22249
22318
  /* xterm reports modifiers as `;5` (control) and `;2` (shift) before
22250
22319
  * the final letter. */
@@ -22804,7 +22873,7 @@ function createCoreModules(options) {
22804
22873
  };
22805
22874
  const http = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "http:") : createUnsupportedModule("http");
22806
22875
  const https = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "https:") : createUnsupportedModule("https");
22807
- const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd) : createUnsupportedModule("child_process");
22876
+ const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd, options.syncSpawn) : createUnsupportedModule("child_process");
22808
22877
  const readline = createReadlineModule(() => processObject.stdin, () => processObject.stdout);
22809
22878
  const dns = createDnsModule();
22810
22879
  const builtins = {
@@ -22865,6 +22934,7 @@ function createCoreModules(options) {
22865
22934
  pendingUnrefed: timers.pendingUnrefed,
22866
22935
  /** Client requests sent but not yet read to completion. */
22867
22936
  pendingRequests: () => inFlightRequests,
22937
+ cancelTimers: timers.cancelAll,
22868
22938
  writeStdin: (data) => {
22869
22939
  if (options.interactiveStdin) stdin.write(data);
22870
22940
  },
@@ -23546,6 +23616,27 @@ function createTrackedTimers() {
23546
23616
  live.delete(handle);
23547
23617
  native(handle);
23548
23618
  };
23619
+ const cancelAll = () => {
23620
+ for (const handle of [...live, ...unrefed]) {
23621
+ const state = states.get(handle);
23622
+ if (!state) continue;
23623
+ state.active = false;
23624
+ try {
23625
+ clearTimeout(state.native);
23626
+ } catch {
23627
+ }
23628
+ try {
23629
+ clearInterval(state.native);
23630
+ } catch {
23631
+ }
23632
+ try {
23633
+ (globalThis.clearImmediate ?? clearTimeout)(state.native);
23634
+ } catch {
23635
+ }
23636
+ }
23637
+ live.clear();
23638
+ unrefed.clear();
23639
+ };
23549
23640
  return {
23550
23641
  api: {
23551
23642
  setTimeout: setTimeoutTracked,
@@ -23556,7 +23647,8 @@ function createTrackedTimers() {
23556
23647
  clearImmediate: (handle) => clear2(handle, globalThis.clearImmediate ?? clearTimeout)
23557
23648
  },
23558
23649
  pending: () => live.size,
23559
- pendingUnrefed: () => unrefed.size
23650
+ pendingUnrefed: () => unrefed.size,
23651
+ cancelAll
23560
23652
  };
23561
23653
  }
23562
23654
  function createTimerPromises() {
@@ -24010,6 +24102,193 @@ var MemoryVolume = class {
24010
24102
  }
24011
24103
  };
24012
24104
 
24105
+ // src/runtime/mirroring-volume.ts
24106
+ init_path();
24107
+ var MirroringVolume = class {
24108
+ constructor(inner = new MemoryVolume()) {
24109
+ this.inner = inner;
24110
+ }
24111
+ inner;
24112
+ mirror;
24113
+ root = "/";
24114
+ /**
24115
+ * Start mirroring the tree under `root`, seeding it with what is there now.
24116
+ *
24117
+ * Called once the Rolldown binding is known to be in play; before that a
24118
+ * container pays nothing for this.
24119
+ */
24120
+ attach(mirror, root) {
24121
+ this.mirror = mirror;
24122
+ this.root = clean(root);
24123
+ this.seed();
24124
+ }
24125
+ detach() {
24126
+ this.mirror = void 0;
24127
+ }
24128
+ /** Copy the whole subtree across. The only bulk operation that remains. */
24129
+ seed() {
24130
+ const mirror = this.mirror;
24131
+ if (!mirror) return;
24132
+ try {
24133
+ mirror.rmSync?.(this.root, { recursive: true, force: true });
24134
+ } catch {
24135
+ }
24136
+ const copy = (path) => {
24137
+ let stat2;
24138
+ try {
24139
+ stat2 = this.inner.lstatSync(path);
24140
+ } catch {
24141
+ return;
24142
+ }
24143
+ if (stat2.isDirectory()) {
24144
+ this.safely(() => mirror.mkdirSync(path, { recursive: true }));
24145
+ for (const name of this.inner.readdirSync(path)) copy(join(path, name));
24146
+ } else if (stat2.isSymbolicLink()) {
24147
+ this.safely(() => {
24148
+ mirror.mkdirSync(dirname(path), { recursive: true });
24149
+ mirror.symlinkSync(this.inner.readlinkSync(path), path);
24150
+ });
24151
+ } else if (!path.endsWith(".node")) {
24152
+ this.safely(() => {
24153
+ mirror.mkdirSync(dirname(path), { recursive: true });
24154
+ mirror.writeFileSync(path, this.inner.readFileSync(path));
24155
+ });
24156
+ }
24157
+ };
24158
+ copy(this.root);
24159
+ }
24160
+ /** Is this path inside the mirrored subtree? */
24161
+ mirrored(path) {
24162
+ if (!this.mirror) return false;
24163
+ const clean2 = clean(path);
24164
+ return this.root === "/" || clean2 === this.root || clean2.startsWith(`${this.root}/`);
24165
+ }
24166
+ /**
24167
+ * Run a mirror update, swallowing failure.
24168
+ *
24169
+ * The mirror is a read-only cache for another engine. If it rejects
24170
+ * something — an unsupported operation, a path it has not seen — the
24171
+ * container's own write has still happened and must still succeed.
24172
+ */
24173
+ safely(update) {
24174
+ try {
24175
+ update();
24176
+ } catch {
24177
+ }
24178
+ }
24179
+ /** Push a path's current state across, whatever it is now. */
24180
+ sync(path) {
24181
+ const mirror = this.mirror;
24182
+ if (!mirror || !this.mirrored(path)) return;
24183
+ let stat2;
24184
+ try {
24185
+ stat2 = this.inner.lstatSync(path);
24186
+ } catch {
24187
+ this.safely(() => {
24188
+ if (mirror.rmSync) mirror.rmSync(path, { recursive: true, force: true });
24189
+ else mirror.unlinkSync?.(path);
24190
+ });
24191
+ return;
24192
+ }
24193
+ if (stat2.isDirectory()) {
24194
+ this.safely(() => mirror.mkdirSync(path, { recursive: true }));
24195
+ return;
24196
+ }
24197
+ if (stat2.isSymbolicLink()) {
24198
+ this.safely(() => {
24199
+ mirror.mkdirSync(dirname(path), { recursive: true });
24200
+ mirror.symlinkSync(this.inner.readlinkSync(path), path);
24201
+ });
24202
+ return;
24203
+ }
24204
+ if (path.endsWith(".node")) return;
24205
+ this.safely(() => {
24206
+ mirror.mkdirSync(dirname(path), { recursive: true });
24207
+ mirror.writeFileSync(path, this.inner.readFileSync(path));
24208
+ });
24209
+ }
24210
+ // ── reads: straight through ───────────────────────────────────────────────
24211
+ readFileSync(path) {
24212
+ return this.inner.readFileSync(path);
24213
+ }
24214
+ readdirSync(path) {
24215
+ return this.inner.readdirSync(path);
24216
+ }
24217
+ lstatSync(path) {
24218
+ return this.inner.lstatSync(path);
24219
+ }
24220
+ readlinkSync(path) {
24221
+ return this.inner.readlinkSync(path);
24222
+ }
24223
+ getStats() {
24224
+ return this.inner.getStats();
24225
+ }
24226
+ // ── writes: applied, then mirrored ────────────────────────────────────────
24227
+ writeFileSync(path, data) {
24228
+ this.inner.writeFileSync(path, data);
24229
+ this.sync(path);
24230
+ }
24231
+ appendFileSync(path, data) {
24232
+ this.inner.appendFileSync(path, data);
24233
+ this.sync(path);
24234
+ }
24235
+ mkdirSync(path, options) {
24236
+ this.inner.mkdirSync(path, options);
24237
+ this.sync(path);
24238
+ }
24239
+ rmdirSync(path) {
24240
+ this.inner.rmdirSync(path);
24241
+ this.sync(path);
24242
+ }
24243
+ unlinkSync(path) {
24244
+ this.inner.unlinkSync(path);
24245
+ this.sync(path);
24246
+ }
24247
+ renameSync(from, to) {
24248
+ this.inner.renameSync(from, to);
24249
+ this.sync(from);
24250
+ this.sync(to);
24251
+ }
24252
+ symlinkSync(target, path) {
24253
+ this.inner.symlinkSync(target, path);
24254
+ this.sync(path);
24255
+ }
24256
+ linkSync(existing, path) {
24257
+ this.inner.linkSync(existing, path);
24258
+ this.sync(path);
24259
+ }
24260
+ truncateSync(path, length) {
24261
+ this.inner.truncateSync(path, length);
24262
+ this.sync(path);
24263
+ }
24264
+ /* Permissions and timestamps do not change what a bundler resolves, and
24265
+ * `memfs` is stricter about them than this volume is. Applied here only. */
24266
+ chmodSync(path, mode) {
24267
+ this.inner.chmodSync(path, mode);
24268
+ }
24269
+ lchmodSync(path, mode) {
24270
+ this.inner.lchmodSync(path, mode);
24271
+ }
24272
+ chownSync(path, uid, gid) {
24273
+ this.inner.chownSync(path, uid, gid);
24274
+ }
24275
+ lchownSync(path, uid, gid) {
24276
+ this.inner.lchownSync(path, uid, gid);
24277
+ }
24278
+ utimesSync(path, atime, mtime) {
24279
+ this.inner.utimesSync(path, atime, mtime);
24280
+ }
24281
+ // ── snapshots ─────────────────────────────────────────────────────────────
24282
+ snapshot() {
24283
+ return this.inner.snapshot();
24284
+ }
24285
+ /** A restore replaces everything, so the mirror is rebuilt rather than patched. */
24286
+ restore(entries) {
24287
+ this.inner.restore(entries);
24288
+ this.seed();
24289
+ }
24290
+ };
24291
+
24013
24292
  // src/runtime/host-esbuild.ts
24014
24293
  var ASYNC_API = ["transform", "build", "context", "formatMessages", "analyzeMetafile"];
24015
24294
  function createHostEsbuild() {
@@ -24079,6 +24358,7 @@ function ensureProcessGlobal() {
24079
24358
  }
24080
24359
 
24081
24360
  // src/runtime/host-rolldown.ts
24361
+ 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'] }";
24082
24362
  async function loadHostRolldownBinding() {
24083
24363
  if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) {
24084
24364
  throw new Error(
@@ -24092,7 +24372,13 @@ async function loadHostRolldownBinding() {
24092
24372
  if (typeof process !== "undefined" && process.env?.SANDBOXEDJS_DEBUG) {
24093
24373
  console.error("[sandboxedjs] Rolldown WASI binding unavailable:", error);
24094
24374
  }
24095
- throw new Error("The optional Rolldown WASI binding could not be loaded.", { cause: error });
24375
+ const reason = error instanceof Error ? error.message : String(error);
24376
+ throw new Error(
24377
+ `The optional Rolldown WASI binding could not be loaded: ${reason}` + (typeof window === "undefined" ? "" : `
24378
+
24379
+ ${BUNDLER_HINT}`),
24380
+ { cause: error }
24381
+ );
24096
24382
  }
24097
24383
  }
24098
24384
 
@@ -24132,6 +24418,7 @@ var LocalProcess = class extends EventEmitter4__default.default {
24132
24418
  inputEnded = false;
24133
24419
  pendingInput = [];
24134
24420
  resolveKilled;
24421
+ cleanup;
24135
24422
  killedPromise = new Promise((resolve2) => {
24136
24423
  this.resolveKilled = resolve2;
24137
24424
  });
@@ -24170,13 +24457,32 @@ var LocalProcess = class extends EventEmitter4__default.default {
24170
24457
  for (const chunk of this.pendingInput.splice(0)) input(chunk);
24171
24458
  if (this.inputEnded) end();
24172
24459
  }
24460
+ /**
24461
+ * End the process now.
24462
+ *
24463
+ * Killing has to settle `completion`, not merely record the intent. A
24464
+ * program with an interval outstanding has work pending forever, so the
24465
+ * runtime's own idle check will never end it — before this, killing such a
24466
+ * process left the caller awaiting a promise that could not resolve.
24467
+ */
24173
24468
  kill(_signal = "SIGTERM") {
24469
+ if (this.killed) return;
24174
24470
  this.killed = true;
24175
24471
  this.resolveKilled();
24472
+ this.cleanup?.();
24473
+ this.finish(137);
24176
24474
  }
24177
24475
  waitForKill() {
24178
24476
  return this.killedPromise;
24179
24477
  }
24478
+ isKilled() {
24479
+ return this.killed;
24480
+ }
24481
+ /** Registered by the task so a kill can release what the program still holds. */
24482
+ onKill(cleanup) {
24483
+ this.cleanup = cleanup;
24484
+ if (this.killed) cleanup();
24485
+ }
24180
24486
  /** End the process now, for an asynchronous `process.exit`. */
24181
24487
  exitNow(code) {
24182
24488
  this.finish(code);
@@ -24338,7 +24644,10 @@ function removeEscapedErrorReporting() {
24338
24644
  }
24339
24645
  var DRAIN_TURNS = 4;
24340
24646
  var LocalRuntimePod = class _LocalRuntimePod {
24341
- volume = new MemoryVolume();
24647
+ /* Wrapped so that a foreign filesystem — Rolldown's WebAssembly memfs — can
24648
+ * be kept in step as writes happen, rather than deep-copied once per spawn.
24649
+ * With nothing attached this is a straight pass-through. */
24650
+ volume = new MirroringVolume(new MemoryVolume());
24342
24651
  packages;
24343
24652
  instanceId = `sbx-${Math.random().toString(36).slice(2)}`;
24344
24653
  router = new VirtualHttpRouter();
@@ -24429,6 +24738,10 @@ var LocalRuntimePod = class _LocalRuntimePod {
24429
24738
  onRawMode: (enabled) => proc.emit("rawmode", enabled)
24430
24739
  });
24431
24740
  proc.acceptInput((data) => core.writeStdin(data), () => core.endStdin());
24741
+ proc.onKill(() => {
24742
+ core.cancelTimers();
24743
+ this.router.closeOwner(owner);
24744
+ });
24432
24745
  engine = new CommonJsEngine(this.volume, {
24433
24746
  cwd,
24434
24747
  builtins: core.builtins,
@@ -24438,7 +24751,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
24438
24751
  });
24439
24752
  try {
24440
24753
  await engine.run(script);
24441
- await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin, core.pendingRequests);
24754
+ await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin, core.pendingRequests, () => proc.isKilled());
24442
24755
  if (this.router.activePorts(owner).length) {
24443
24756
  await proc.waitForKill();
24444
24757
  return 137;
@@ -24459,13 +24772,13 @@ var LocalRuntimePod = class _LocalRuntimePod {
24459
24772
  */
24460
24773
  async prepareRolldown(cwd, env2) {
24461
24774
  const specifier = "@rolldown/binding-wasm32-wasi";
24462
- if (!this.modules[specifier] && packageInstalled(this.volume, cwd, "rolldown")) {
24775
+ if (!this.modules[specifier] && packageIsInstalled(this.volume, cwd, "rolldown")) {
24463
24776
  this.rolldownBinding ??= loadHostRolldownBinding();
24464
24777
  const binding = await this.rolldownBinding;
24465
24778
  if (binding) this.modules[specifier] = binding;
24466
24779
  }
24467
24780
  if (this.modules[specifier]) {
24468
- syncRolldownFileSystem(this.modules[specifier], this.volume, cwd);
24781
+ attachRolldownMirror(this.modules[specifier], this.volume, cwd);
24469
24782
  env2.NAPI_RS_FORCE_WASI ??= "true";
24470
24783
  }
24471
24784
  }
@@ -24484,18 +24797,18 @@ var LocalRuntimePod = class _LocalRuntimePod {
24484
24797
  * A plain script that has genuinely finished falls straight through both,
24485
24798
  * costing a handful of empty turns.
24486
24799
  */
24487
- async settle(owner, pendingHandles, pendingUnrefed, readingStdin, pendingRequests) {
24800
+ async settle(owner, pendingHandles, pendingUnrefed, readingStdin, pendingRequests, killed) {
24488
24801
  for (let turn = 0; turn < DRAIN_TURNS; turn++) {
24489
- if (this.router.activePorts(owner).length) return;
24802
+ if (this.router.activePorts(owner).length || killed()) return;
24490
24803
  await new Promise((resolve2) => setTimeout(resolve2, 0));
24491
24804
  }
24492
24805
  if (pendingHandles() === 0 && pendingRequests() === 0 && pendingUnrefed() > 0 && !readingStdin()) {
24493
24806
  const deadline = Date.now() + 1e3;
24494
- while (!this.router.activePorts(owner).length && Date.now() < deadline) {
24807
+ while (!this.router.activePorts(owner).length && !killed() && Date.now() < deadline) {
24495
24808
  await new Promise((resolve2) => setTimeout(resolve2, 5));
24496
24809
  }
24497
24810
  }
24498
- while (!this.router.activePorts(owner).length && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
24811
+ while (!this.router.activePorts(owner).length && !killed() && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
24499
24812
  await new Promise((resolve2) => setTimeout(resolve2, 5));
24500
24813
  }
24501
24814
  }
@@ -24547,7 +24860,7 @@ function formatError(error) {
24547
24860
  function isRecord(value) {
24548
24861
  return typeof value === "object" && value !== null && !Array.isArray(value);
24549
24862
  }
24550
- function packageInstalled(volume, cwd, wanted) {
24863
+ function packageIsInstalled(volume, cwd, wanted) {
24551
24864
  for (let dir3 = clean(cwd); ; dir3 = dirname(dir3)) {
24552
24865
  if (packageTreeContains(volume, join(dir3, "node_modules"), wanted, /* @__PURE__ */ new Set())) return true;
24553
24866
  if (dir3 === "/") return false;
@@ -24576,30 +24889,565 @@ function directory(volume, path) {
24576
24889
  return false;
24577
24890
  }
24578
24891
  }
24579
- function syncRolldownFileSystem(binding, volume, root) {
24892
+ function attachRolldownMirror(binding, volume, root) {
24580
24893
  const fs = binding?.__fs;
24581
24894
  if (!fs?.mkdirSync || !fs?.writeFileSync) return;
24582
- try {
24583
- fs.rmSync?.(root, { recursive: true, force: true });
24584
- } catch {
24895
+ volume.attach(fs, root);
24896
+ }
24897
+
24898
+ // src/runtime/sync-channel.ts
24899
+ var STATE = 0;
24900
+ var LENGTH = 1;
24901
+ var MORE = 2;
24902
+ var CONTROL_WORDS = 4;
24903
+ var STATE_REQUEST = 1;
24904
+ var STATE_RESPONSE = 2;
24905
+ var STATE_CONTINUE = 3;
24906
+ var STATE_CLOSED = 4;
24907
+ function createSyncChannelBuffers(capacityBytes = 1 << 20) {
24908
+ return {
24909
+ control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
24910
+ data: new SharedArrayBuffer(capacityBytes)
24911
+ };
24912
+ }
24913
+ function syncChannelSupported() {
24914
+ if (typeof SharedArrayBuffer !== "function") return false;
24915
+ if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return false;
24916
+ if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) return false;
24917
+ return true;
24918
+ }
24919
+ var SyncChannelServer = class {
24920
+ constructor(buffers, handle) {
24921
+ this.handle = handle;
24922
+ this.control = new Int32Array(buffers.control);
24923
+ this.data = new Uint8Array(buffers.data);
24924
+ this.capacity = this.data.length;
24585
24925
  }
24586
- const copy = (path) => {
24587
- const stat2 = volume.lstatSync(path);
24588
- if (stat2.isDirectory()) {
24589
- fs.mkdirSync(path, { recursive: true });
24590
- for (const name of volume.readdirSync(path)) copy(join(path, name));
24591
- } else if (stat2.isSymbolicLink()) {
24592
- fs.mkdirSync(dirname(path), { recursive: true });
24926
+ handle;
24927
+ control;
24928
+ data;
24929
+ capacity;
24930
+ /** Request chunks gathered so far, and the response still to be sent. */
24931
+ incoming = [];
24932
+ outgoing = null;
24933
+ sent = 0;
24934
+ closed = false;
24935
+ /** Call from the wake message the client sends after each chunk. */
24936
+ async pump() {
24937
+ if (this.closed) return;
24938
+ const state = Atomics.load(this.control, STATE);
24939
+ if (state === STATE_REQUEST) {
24940
+ const size = Atomics.load(this.control, LENGTH);
24941
+ this.incoming.push(this.data.slice(0, size));
24942
+ if (Atomics.load(this.control, MORE) === 1) {
24943
+ this.publish(STATE_CONTINUE);
24944
+ return;
24945
+ }
24946
+ const request = concat3(this.incoming);
24947
+ this.incoming = [];
24948
+ let response;
24593
24949
  try {
24594
- fs.symlinkSync(volume.readlinkSync(path), path);
24950
+ response = await this.handle(request);
24595
24951
  } catch {
24952
+ response = new Uint8Array();
24596
24953
  }
24597
- } else if (!path.endsWith(".node")) {
24598
- fs.mkdirSync(dirname(path), { recursive: true });
24599
- fs.writeFileSync(path, volume.readFileSync(path));
24954
+ if (this.closed) return;
24955
+ this.outgoing = response;
24956
+ this.sent = 0;
24957
+ this.sendChunk();
24958
+ return;
24600
24959
  }
24960
+ if (state === STATE_CONTINUE) this.sendChunk();
24961
+ }
24962
+ /** Release a blocked client, e.g. when the container is torn down. */
24963
+ close() {
24964
+ if (this.closed) return;
24965
+ this.closed = true;
24966
+ Atomics.store(this.control, STATE, STATE_CLOSED);
24967
+ Atomics.notify(this.control, STATE);
24968
+ }
24969
+ sendChunk() {
24970
+ const payload = this.outgoing ?? new Uint8Array();
24971
+ const size = Math.min(this.capacity, payload.length - this.sent);
24972
+ this.data.set(payload.subarray(this.sent, this.sent + size), 0);
24973
+ this.sent += size;
24974
+ Atomics.store(this.control, LENGTH, size);
24975
+ Atomics.store(this.control, MORE, this.sent < payload.length ? 1 : 0);
24976
+ this.publish(STATE_RESPONSE);
24977
+ }
24978
+ publish(state) {
24979
+ Atomics.store(this.control, STATE, state);
24980
+ Atomics.notify(this.control, STATE);
24981
+ }
24982
+ };
24983
+ function concat3(parts) {
24984
+ if (parts.length === 1) return parts[0];
24985
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
24986
+ const joined = new Uint8Array(total);
24987
+ let at = 0;
24988
+ for (const part of parts) {
24989
+ joined.set(part, at);
24990
+ at += part.length;
24991
+ }
24992
+ return joined;
24993
+ }
24994
+
24995
+ // src/runtime/remote-volume.ts
24996
+ var encoder7 = new TextEncoder();
24997
+ var decoder7 = new TextDecoder();
24998
+ function encodeFrame(header, body) {
24999
+ const json = encoder7.encode(JSON.stringify(header));
25000
+ const frame = new Uint8Array(4 + json.length + (body?.length ?? 0));
25001
+ new DataView(frame.buffer).setUint32(0, json.length, true);
25002
+ frame.set(json, 4);
25003
+ if (body?.length) frame.set(body, 4 + json.length);
25004
+ return frame;
25005
+ }
25006
+ function decodeFrame(frame) {
25007
+ const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
25008
+ const headerLength = view.getUint32(0, true);
25009
+ const header = JSON.parse(decoder7.decode(frame.subarray(4, 4 + headerLength)));
25010
+ return { header, body: frame.subarray(4 + headerLength) };
25011
+ }
25012
+ function flattenStat(stat2) {
25013
+ return {
25014
+ kind: stat2.isDirectory() ? "directory" : stat2.isSymbolicLink() ? "symlink" : "file",
25015
+ mode: stat2.mode,
25016
+ size: stat2.size,
25017
+ uid: stat2.uid,
25018
+ gid: stat2.gid,
25019
+ ino: stat2.ino,
25020
+ nlink: stat2.nlink,
25021
+ atimeMs: stat2.atimeMs,
25022
+ mtimeMs: stat2.mtimeMs,
25023
+ ctimeMs: stat2.ctimeMs,
25024
+ birthtimeMs: stat2.birthtimeMs
24601
25025
  };
24602
- copy(clean(root));
25026
+ }
25027
+ var BINARY_RESULTS = /* @__PURE__ */ new Set(["readFileSync"]);
25028
+ var BINARY_ARGUMENTS = /* @__PURE__ */ new Set(["writeFileSync", "appendFileSync"]);
25029
+ function serveVolume(volume) {
25030
+ return (request) => {
25031
+ const { header, body } = decodeFrame(request);
25032
+ const target = volume;
25033
+ try {
25034
+ const method = target[header.op];
25035
+ if (typeof method !== "function") {
25036
+ return encodeFrame({ ok: false, error: { message: `unknown volume operation: ${header.op}` } });
25037
+ }
25038
+ const plain = header.args.map((value) => value === null ? void 0 : value);
25039
+ const args = BINARY_ARGUMENTS.has(header.op) ? [plain[0], body] : header.op === "utimesSync" ? [plain[0], new Date(plain[1]), new Date(plain[2])] : plain;
25040
+ const result = method.apply(volume, args);
25041
+ if (BINARY_RESULTS.has(header.op)) {
25042
+ return encodeFrame({ ok: true }, result);
25043
+ }
25044
+ if (header.op === "lstatSync") {
25045
+ return encodeFrame({ ok: true, value: flattenStat(result) });
25046
+ }
25047
+ return encodeFrame({ ok: true, value: result });
25048
+ } catch (error) {
25049
+ const failure = error;
25050
+ return encodeFrame({
25051
+ ok: false,
25052
+ error: { ...failure.code ? { code: failure.code } : {}, message: failure.message ?? String(error) }
25053
+ });
25054
+ }
25055
+ };
25056
+ }
25057
+
25058
+ // src/runtime/sync-syscalls.ts
25059
+ var SPAWN_OP = "spawnSync";
25060
+ function serveSyncSyscalls(options) {
25061
+ const volumeHandler = serveVolume(options.volume);
25062
+ return async (request) => {
25063
+ const { header } = decodeFrame(request);
25064
+ if (header.op !== SPAWN_OP) return volumeHandler(request);
25065
+ try {
25066
+ const result = await options.spawnChild(header.args[0]);
25067
+ return encodeFrame({ ok: true, value: result });
25068
+ } catch (error) {
25069
+ const failure = error;
25070
+ return encodeFrame({
25071
+ ok: true,
25072
+ value: {
25073
+ status: null,
25074
+ stdout: "",
25075
+ stderr: "",
25076
+ signal: null,
25077
+ error: { ...failure.code ? { code: failure.code } : {}, message: failure.message ?? String(error) }
25078
+ }
25079
+ });
25080
+ }
25081
+ };
25082
+ }
25083
+
25084
+ // src/runtime/worker-host.ts
25085
+ function defaultWorkerUrl() {
25086
+ return new URL("./worker-entry.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
25087
+ }
25088
+ function hasDomWorker() {
25089
+ return typeof Worker === "function" && typeof document !== "undefined";
25090
+ }
25091
+ async function startRuntimeWorker(options = {}) {
25092
+ const url = options.url ?? defaultWorkerUrl();
25093
+ const worker = hasDomWorker() ? await startDomWorker(url) : await startNodeWorker(url, options.workerData ?? {});
25094
+ await new Promise((resolve2, reject) => {
25095
+ const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
25096
+ worker.onMessage((message) => {
25097
+ if (message?.type === "sandboxedjs:ready") {
25098
+ clearTimeout(timer);
25099
+ resolve2();
25100
+ }
25101
+ });
25102
+ worker.onError((error) => {
25103
+ clearTimeout(timer);
25104
+ reject(error instanceof Error ? error : new Error(String(error)));
25105
+ });
25106
+ });
25107
+ return worker;
25108
+ }
25109
+ async function startDomWorker(url) {
25110
+ const worker = new Worker(url, { type: "module" });
25111
+ const listeners = [];
25112
+ const errors = [];
25113
+ worker.addEventListener("message", (event) => {
25114
+ for (const listener of listeners) listener(event.data);
25115
+ });
25116
+ worker.addEventListener("error", (event) => {
25117
+ const detail = event.message || "the runtime worker failed to load";
25118
+ for (const listener of errors) listener(new Error(detail));
25119
+ });
25120
+ return {
25121
+ postMessage: (message) => worker.postMessage(message),
25122
+ onMessage: (listener) => {
25123
+ listeners.push(listener);
25124
+ },
25125
+ onError: (listener) => {
25126
+ errors.push(listener);
25127
+ },
25128
+ terminate: () => worker.terminate()
25129
+ };
25130
+ }
25131
+ async function startNodeWorker(url, workerData) {
25132
+ const specifier = ["node", "worker_threads"].join(":");
25133
+ const { Worker: NodeWorker } = await import(
25134
+ /* @vite-ignore */
25135
+ /* webpackIgnore: true */
25136
+ specifier
25137
+ );
25138
+ const worker = new NodeWorker(url, { workerData });
25139
+ return {
25140
+ postMessage: (message) => worker.postMessage(message),
25141
+ onMessage: (listener) => worker.on("message", listener),
25142
+ onError: (listener) => worker.on("error", listener),
25143
+ terminate: () => worker.terminate()
25144
+ };
25145
+ }
25146
+ var WorkerProcess = class extends EventEmitter4__default.default {
25147
+ constructor(worker, release) {
25148
+ super();
25149
+ this.worker = worker;
25150
+ this.release = release;
25151
+ super.on("error", () => {
25152
+ });
25153
+ this.completion = new Promise((resolve2) => {
25154
+ this.resolveCompletion = resolve2;
25155
+ });
25156
+ }
25157
+ worker;
25158
+ release;
25159
+ completion;
25160
+ resolveCompletion;
25161
+ settled = false;
25162
+ out = [];
25163
+ err = [];
25164
+ started = false;
25165
+ pendingInput = [];
25166
+ inputEnded = false;
25167
+ on(event, listener) {
25168
+ return super.on(event, listener);
25169
+ }
25170
+ /** Called once the guest has been told to start; releases anything buffered. */
25171
+ begin() {
25172
+ this.started = true;
25173
+ for (const chunk of this.pendingInput.splice(0)) this.worker.postMessage({ type: "stdin", data: chunk });
25174
+ if (this.inputEnded) this.worker.postMessage({ type: "stdin-end" });
25175
+ }
25176
+ output(text2) {
25177
+ this.out.push(text2);
25178
+ this.emit("output", text2);
25179
+ }
25180
+ error(text2) {
25181
+ this.err.push(text2);
25182
+ this.emit("error", text2);
25183
+ }
25184
+ /**
25185
+ * Deliver input to the running program.
25186
+ *
25187
+ * Held until the guest has been started, for the same reason the in-realm
25188
+ * pod holds it: an interactive session's first keystrokes arrive while the
25189
+ * program is still loading, and dropping them loses the answer to a prompt.
25190
+ */
25191
+ write(data) {
25192
+ if (this.started) this.worker.postMessage({ type: "stdin", data });
25193
+ else this.pendingInput.push(data);
25194
+ }
25195
+ endInput() {
25196
+ this.inputEnded = true;
25197
+ if (this.started) this.worker.postMessage({ type: "stdin-end" });
25198
+ }
25199
+ kill(_signal = "SIGTERM") {
25200
+ this.worker.postMessage({ type: "kill" });
25201
+ this.finish(137);
25202
+ }
25203
+ finish(exitCode) {
25204
+ if (this.settled) return;
25205
+ this.settled = true;
25206
+ this.emit("exit", exitCode);
25207
+ this.resolveCompletion({ exitCode, stdout: this.out.join(""), stderr: this.err.join("") });
25208
+ this.release();
25209
+ }
25210
+ };
25211
+ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25212
+ workerUrl;
25213
+ /** Live workers, so teardown can stop them all. */
25214
+ live = /* @__PURE__ */ new Set();
25215
+ constructor(options) {
25216
+ super(options);
25217
+ this.workerUrl = options.workerUrl;
25218
+ }
25219
+ /**
25220
+ * Boot a Worker-backed pod, or return null when this host cannot support one.
25221
+ *
25222
+ * Declining is a first-class outcome. `SharedArrayBuffer` needs cross-origin
25223
+ * isolation, a bundler may have made the guest script unreachable, and a host
25224
+ * that supplied its own module objects has handed over things no thread
25225
+ * boundary can carry. In every case the caller falls back to the in-realm pod
25226
+ * and keeps working.
25227
+ */
25228
+ static async tryBoot(options = {}) {
25229
+ if (!syncChannelSupported()) return null;
25230
+ if (options.modules && Object.keys(options.modules).length > 0) return null;
25231
+ const pod = new _WorkerRuntimePod(options);
25232
+ try {
25233
+ const probe = await startRuntimeWorker({ ...options.workerUrl ? { url: options.workerUrl } : {}, timeoutMs: 1e4 });
25234
+ await probe.terminate();
25235
+ return pod;
25236
+ } catch {
25237
+ pod.teardown();
25238
+ return null;
25239
+ }
25240
+ }
25241
+ async spawn(command, args = [], options = {}) {
25242
+ if (command !== "node" && command !== "nodejs") return super.spawn(command, args, options);
25243
+ const script = args[0];
25244
+ if (!script) return super.spawn(command, args, options);
25245
+ const cwd = typeof options.cwd === "string" ? options.cwd : this.workdir;
25246
+ if (this.needsHostModules(cwd)) return super.spawn(command, args, options);
25247
+ return await this.spawnInWorker(script, args, cwd, options);
25248
+ }
25249
+ async spawnInWorker(script, args, cwd, options) {
25250
+ const env2 = { ...this.env, ...isRecord2(options.env) ? options.env : {} };
25251
+ const owner = `${this.instanceId}:${Math.random().toString(36).slice(2)}`;
25252
+ const buffers = createSyncChannelBuffers();
25253
+ const worker = await startRuntimeWorker({
25254
+ ...this.workerUrl ? { url: this.workerUrl } : {},
25255
+ timeoutMs: 15e3
25256
+ });
25257
+ const server = new SyncChannelServer(buffers, serveSyncSyscalls({
25258
+ volume: this.volume,
25259
+ spawnChild: (request) => this.runChildToCompletion(request)
25260
+ }));
25261
+ const entry = { worker, server };
25262
+ this.live.add(entry);
25263
+ const process2 = new WorkerProcess(worker, () => {
25264
+ this.live.delete(entry);
25265
+ server.close();
25266
+ this.closeProxies(owner);
25267
+ void worker.terminate();
25268
+ });
25269
+ const children = /* @__PURE__ */ new Map();
25270
+ worker.onMessage((raw) => {
25271
+ const message = raw;
25272
+ switch (message?.type) {
25273
+ case "wake":
25274
+ void server.pump();
25275
+ return;
25276
+ case "output":
25277
+ process2.output(String(message.text));
25278
+ return;
25279
+ case "error":
25280
+ process2.error(String(message.text));
25281
+ return;
25282
+ case "rawmode":
25283
+ process2.emit("rawmode", Boolean(message.enabled));
25284
+ return;
25285
+ case "exit":
25286
+ process2.finish(Number(message.code));
25287
+ return;
25288
+ case "listen":
25289
+ this.proxyPort(Number(message.port), owner, worker);
25290
+ return;
25291
+ case "http-response":
25292
+ this.settleProxied(Number(message.id), message.response);
25293
+ return;
25294
+ case "child-start":
25295
+ this.startChild(worker, children, message);
25296
+ return;
25297
+ case "child-stdin":
25298
+ children.get(message.id)?.sendStdin?.(String(message.data));
25299
+ return;
25300
+ case "child-stdin-end":
25301
+ children.get(message.id)?.endStdin?.();
25302
+ return;
25303
+ case "child-kill":
25304
+ children.get(message.id)?.kill(String(message.signal));
25305
+ return;
25306
+ default:
25307
+ return;
25308
+ }
25309
+ });
25310
+ worker.postMessage({
25311
+ type: "start",
25312
+ buffers,
25313
+ script,
25314
+ cwd,
25315
+ env: env2,
25316
+ argv: Array.isArray(options.argv) ? options.argv : ["/usr/bin/node", script, ...args.slice(1)],
25317
+ aliases: this.aliases,
25318
+ ...typeof options.stdinPath === "string" ? { stdinPath: options.stdinPath } : {},
25319
+ ...options.interactiveStdin ? { interactiveStdin: true } : {},
25320
+ ...options.tty ? { tty: true } : {}
25321
+ });
25322
+ process2.begin();
25323
+ return process2;
25324
+ }
25325
+ /** Start an asynchronous child on the host's behalf and relay its events. */
25326
+ startChild(worker, children, message) {
25327
+ const handle = this.processManager.spawn(message.config);
25328
+ children.set(message.id, handle);
25329
+ for (const event of ["stdout", "stderr", "exit"]) {
25330
+ handle.on(event, (value) => worker.postMessage({ type: "child-event", id: message.id, event, value }));
25331
+ }
25332
+ handle.exec();
25333
+ }
25334
+ /** Run a child to completion and collect it, for the guest's `spawnSync`. */
25335
+ runChildToCompletion(request) {
25336
+ return new Promise((resolve2) => {
25337
+ let handle;
25338
+ try {
25339
+ handle = this.processManager.spawn({
25340
+ command: request.command,
25341
+ args: request.args,
25342
+ cwd: request.cwd,
25343
+ ...request.env ? { env: request.env } : {}
25344
+ });
25345
+ } catch (error) {
25346
+ const failure = error;
25347
+ resolve2({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure.code ? { code: failure.code } : {}, message: failure.message } });
25348
+ return;
25349
+ }
25350
+ let stdout = "";
25351
+ let stderr = "";
25352
+ handle.on("stdout", (text2) => {
25353
+ stdout += text2;
25354
+ });
25355
+ handle.on("stderr", (text2) => {
25356
+ stderr += text2;
25357
+ });
25358
+ handle.on("exit", (code) => resolve2({ status: code, stdout, stderr, signal: null }));
25359
+ handle.exec();
25360
+ if (request.input !== void 0) {
25361
+ handle.sendStdin?.(request.input);
25362
+ }
25363
+ handle.endStdin?.();
25364
+ });
25365
+ }
25366
+ // ── HTTP servers living on another thread ─────────────────────────────────
25367
+ proxies = /* @__PURE__ */ new Map();
25368
+ waiting = /* @__PURE__ */ new Map();
25369
+ nextRequestId = 1;
25370
+ /**
25371
+ * Register a stand-in for a server that is actually running in the Worker.
25372
+ *
25373
+ * The router only knows how to reach servers on this thread, so each bound
25374
+ * port gets a local server whose whole job is to forward and wait.
25375
+ */
25376
+ proxyPort(port, owner, worker) {
25377
+ if (this.proxies.has(port)) return;
25378
+ const server = new VirtualHttpServer(this.router, owner, (request, response) => {
25379
+ void this.forward(worker, port, request, response);
25380
+ });
25381
+ try {
25382
+ server.listen(port);
25383
+ this.proxies.set(port, server);
25384
+ } catch {
25385
+ }
25386
+ }
25387
+ async forward(worker, port, request, response) {
25388
+ const chunks = [];
25389
+ for await (const chunk of request) chunks.push(chunk);
25390
+ const body = concat4(chunks);
25391
+ const id = this.nextRequestId++;
25392
+ const answered = new Promise((resolve2) => this.waiting.set(id, resolve2));
25393
+ worker.postMessage({
25394
+ type: "http-request",
25395
+ id,
25396
+ port,
25397
+ init: { method: request.method, path: request.url, headers: request.headers, body }
25398
+ });
25399
+ let result;
25400
+ try {
25401
+ result = await answered;
25402
+ } catch (error) {
25403
+ this.waiting.delete(id);
25404
+ response.writeHead(500, "Internal Server Error", {});
25405
+ response.end(new TextEncoder().encode(error instanceof Error ? error.message : String(error)));
25406
+ return;
25407
+ }
25408
+ response.writeHead(Number(result.statusCode ?? 200), String(result.statusMessage ?? ""), result.headers ?? {});
25409
+ response.end(result.body ?? new Uint8Array());
25410
+ }
25411
+ settleProxied(id, response) {
25412
+ const resolve2 = this.waiting.get(id);
25413
+ if (!resolve2) return;
25414
+ this.waiting.delete(id);
25415
+ resolve2(response);
25416
+ }
25417
+ closeProxies(owner) {
25418
+ for (const [port, server] of [...this.proxies]) {
25419
+ if (server.owner !== owner) continue;
25420
+ server.close();
25421
+ this.proxies.delete(port);
25422
+ }
25423
+ }
25424
+ teardown() {
25425
+ for (const { worker, server } of [...this.live]) {
25426
+ server.close();
25427
+ void worker.terminate();
25428
+ }
25429
+ this.live.clear();
25430
+ super.teardown();
25431
+ }
25432
+ /** Does anything under `cwd` need a module only the host can supply? */
25433
+ needsHostModules(cwd) {
25434
+ return packageIsInstalled(this.volume, cwd, "rolldown");
25435
+ }
25436
+ };
25437
+ function isRecord2(value) {
25438
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25439
+ }
25440
+ function concat4(parts) {
25441
+ if (parts.length === 0) return new Uint8Array();
25442
+ if (parts.length === 1) return parts[0];
25443
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
25444
+ const joined = new Uint8Array(total);
25445
+ let at = 0;
25446
+ for (const part of parts) {
25447
+ joined.set(part, at);
25448
+ at += part.length;
25449
+ }
25450
+ return joined;
24603
25451
  }
24604
25452
 
24605
25453
  // src/container/container.ts
@@ -24646,11 +25494,13 @@ var Container = class _Container {
24646
25494
  // ── boot ──────────────────────────────────────────────────────────────────
24647
25495
  static async create(opts = {}) {
24648
25496
  if (opts.python) configurePython(opts.python);
24649
- const pod = opts.pod ?? await LocalRuntimePod.boot({
25497
+ const podOptions = {
24650
25498
  workdir: opts.cwd ?? "/",
24651
25499
  env: opts.env ?? {},
24652
25500
  ...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
24653
- });
25501
+ };
25502
+ const workerOptions = { ...podOptions, ...opts.workerUrl ? { workerUrl: opts.workerUrl } : {} };
25503
+ const pod = opts.pod ?? (opts.isolation === "realm" ? null : await WorkerRuntimePod.tryBoot(workerOptions)) ?? await LocalRuntimePod.boot(podOptions);
24654
25504
  const kernel = new Kernel({
24655
25505
  pod,
24656
25506
  hostname: opts.hostname ?? "sandbox",
@@ -24892,15 +25742,15 @@ var Container = class _Container {
24892
25742
  }
24893
25743
  makeStdio(opts) {
24894
25744
  const combined = [];
24895
- const decoder7 = new TextDecoder();
25745
+ const decoder8 = new TextDecoder();
24896
25746
  const stdout = new BufferSink((chunk) => {
24897
- const text2 = decoder7.decode(chunk, { stream: true });
25747
+ const text2 = decoder8.decode(chunk, { stream: true });
24898
25748
  combined.push(text2);
24899
25749
  opts.onStdout?.(text2);
24900
25750
  this.hooks.onStdout?.(text2);
24901
25751
  });
24902
25752
  const stderr = new BufferSink((chunk) => {
24903
- const text2 = decoder7.decode(chunk, { stream: true });
25753
+ const text2 = decoder8.decode(chunk, { stream: true });
24904
25754
  combined.push(text2);
24905
25755
  opts.onStderr?.(text2);
24906
25756
  this.hooks.onStderr?.(text2);
@@ -25594,7 +26444,9 @@ exports.parseUmask = parseUmask;
25594
26444
  exports.posixPath = path_exports;
25595
26445
  exports.resetPidCounter = resetPidCounter;
25596
26446
  exports.shellQuote = shellQuote;
26447
+ exports.startRuntimeWorker = startRuntimeWorker;
25597
26448
  exports.strerror = strerror;
26449
+ exports.syncChannelSupported = syncChannelSupported;
25598
26450
  exports.transformEsm = transformEsm;
25599
26451
  exports.unameInfo = unameInfo;
25600
26452
  //# sourceMappingURL=index.cjs.map