sandboxedjs 0.1.28 → 0.1.30

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);
@@ -19712,6 +19712,10 @@ var KernelChildProcess = class {
19712
19712
  started = false;
19713
19713
  cancelled = false;
19714
19714
  constructor(kernel, cred, config, pid) {
19715
+ if (config.inheritStdio) {
19716
+ this.stdin.isTTY = true;
19717
+ this.stdin.interactive = true;
19718
+ }
19715
19719
  this.kernel = kernel;
19716
19720
  this.cred = cred;
19717
19721
  this.pid = pid;
@@ -21832,7 +21836,7 @@ var VirtualClientRequest = class extends streamModule4__default.default.Writable
21832
21836
  };
21833
21837
  let settled = false;
21834
21838
  try {
21835
- const response = this.router.activePortsIncludes(this.target.port) && isLoopback(this.target.hostname) ? await this.viaRouter(body) : await this.viaFetch(body);
21839
+ const response = isLoopback(this.target.hostname) ? await this.viaRouter(body) : await this.viaFetch(body);
21836
21840
  if (this.destroyedByUser) return;
21837
21841
  clearTimeout(this.timer);
21838
21842
  settled = true;
@@ -21854,6 +21858,12 @@ var VirtualClientRequest = class extends streamModule4__default.default.Writable
21854
21858
  }
21855
21859
  }
21856
21860
  async viaRouter(body) {
21861
+ if (!this.router.activePortsIncludes(this.target.port)) {
21862
+ throw Object.assign(
21863
+ new Error(`connect ECONNREFUSED ${this.target.hostname}:${this.target.port}`),
21864
+ { code: "ECONNREFUSED", errno: -61, syscall: "connect", address: this.target.hostname, port: this.target.port }
21865
+ );
21866
+ }
21857
21867
  const result = await this.router.request(this.target.port, {
21858
21868
  method: this.target.method,
21859
21869
  path: this.target.path,
@@ -22110,7 +22120,8 @@ var ChildProcess = class extends EventEmitter4__default.default {
22110
22120
  queueMicrotask(() => this.emit("close", code, null));
22111
22121
  }
22112
22122
  };
22113
- function createChildProcessModule(spawnChild, defaultCwd) {
22123
+ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn, defaultEnv = () => ({})) {
22124
+ const environmentFor = (options) => options.env ? { ...options.env } : defaultEnv();
22114
22125
  const throughShell = (command, options) => {
22115
22126
  const shell = typeof options.shell === "string" ? options.shell : "/bin/sh";
22116
22127
  return { file: shell, args: ["-c", command] };
@@ -22121,7 +22132,7 @@ function createChildProcessModule(spawnChild, defaultCwd) {
22121
22132
  command: resolved.file,
22122
22133
  args: resolved.args,
22123
22134
  cwd: options.cwd ?? defaultCwd(),
22124
- ...options.env ? { env: options.env } : {}
22135
+ env: environmentFor(options)
22125
22136
  });
22126
22137
  return new ChildProcess(handle, resolved.file, resolved.args);
22127
22138
  };
@@ -22167,20 +22178,81 @@ ${err.join("")}`),
22167
22178
  exec,
22168
22179
  execFile,
22169
22180
  fork: (modulePath, args = [], options = {}) => spawn("node", [modulePath, ...args], options),
22170
- execSync: unavailable("execSync"),
22171
- execFileSync: unavailable("execFileSync"),
22172
- spawnSync: unavailable("spawnSync"),
22181
+ ...buildSyncFamily(syncSpawn, throughShell, defaultCwd, environmentFor),
22173
22182
  ChildProcess
22174
22183
  };
22175
22184
  }
22185
+ function buildSyncFamily(syncSpawn, throughShell, defaultCwd, environmentFor) {
22186
+ if (!syncSpawn) {
22187
+ return {
22188
+ execSync: unavailable("execSync"),
22189
+ execFileSync: unavailable("execFileSync"),
22190
+ spawnSync: unavailable("spawnSync")
22191
+ };
22192
+ }
22193
+ const run = (file3, args, options) => {
22194
+ const resolved = options.shell ? throughShell([file3, ...args].join(" "), options) : { file: file3, args };
22195
+ const input = options.input === void 0 ? void 0 : typeof options.input === "string" ? options.input : new TextDecoder().decode(options.input);
22196
+ const inherit = options.stdio === "inherit" || Array.isArray(options.stdio) && options.stdio[0] === "inherit";
22197
+ return syncSpawn({
22198
+ command: resolved.file,
22199
+ args: resolved.args,
22200
+ cwd: options.cwd ?? defaultCwd(),
22201
+ env: environmentFor(options),
22202
+ ...input === void 0 ? {} : { input },
22203
+ ...inherit ? { inheritStdio: true } : {}
22204
+ });
22205
+ };
22206
+ const asOutput = (text2, options) => options.encoding === "buffer" || options.encoding === void 0 ? Buffer2.from(text2) : text2;
22207
+ const orThrow = (result, command, options) => {
22208
+ if (result.error) {
22209
+ throw Object.assign(new Error(result.error.message), {
22210
+ ...result.error.code ? { code: result.error.code } : {},
22211
+ stdout: asOutput(result.stdout, options),
22212
+ stderr: asOutput(result.stderr, options)
22213
+ });
22214
+ }
22215
+ if (result.status !== 0) {
22216
+ throw Object.assign(new Error(`Command failed: ${command}
22217
+ ${result.stderr}`), {
22218
+ status: result.status,
22219
+ stdout: asOutput(result.stdout, options),
22220
+ stderr: asOutput(result.stderr, options)
22221
+ });
22222
+ }
22223
+ return asOutput(result.stdout, options);
22224
+ };
22225
+ const spawnSync = (file3, args = [], options = {}) => {
22226
+ const [list, opts] = Array.isArray(args) ? [args, options] : [[], args];
22227
+ const result = run(file3, list, opts);
22228
+ return {
22229
+ pid: 0,
22230
+ status: result.status,
22231
+ signal: result.signal,
22232
+ stdout: asOutput(result.stdout, opts),
22233
+ stderr: asOutput(result.stderr, opts),
22234
+ output: [null, asOutput(result.stdout, opts), asOutput(result.stderr, opts)],
22235
+ ...result.error ? { error: Object.assign(new Error(result.error.message), result.error.code ? { code: result.error.code } : {}) } : {}
22236
+ };
22237
+ };
22238
+ const execFileSync = (file3, args = [], options = {}) => {
22239
+ const [list, opts] = Array.isArray(args) ? [args, options] : [[], args];
22240
+ return orThrow(run(file3, list, opts), [file3, ...list].join(" "), opts);
22241
+ };
22242
+ const execSync = (command, options = {}) => orThrow(run(command, [], { ...options, shell: options.shell ?? true }), command, options);
22243
+ return { spawnSync, execFileSync, execSync };
22244
+ }
22176
22245
  function normalize2(options, callback) {
22177
22246
  if (typeof options === "function") return [{}, options];
22178
22247
  return [options ?? {}, callback];
22179
22248
  }
22180
22249
  function unavailable(name) {
22181
- return () => {
22250
+ return (...args) => {
22251
+ const file3 = typeof args[0] === "string" ? args[0] : void 0;
22252
+ const list = Array.isArray(args[1]) ? args[1].map(String) : [];
22253
+ const command = file3 ? [file3, ...list].join(" ") : void 0;
22182
22254
  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.`
22255
+ `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
22256
  );
22185
22257
  error.code = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
22186
22258
  throw error;
@@ -22244,7 +22316,11 @@ function parseKeys(input) {
22244
22316
  const name = CSI_KEYS[sequence];
22245
22317
  keys.push({
22246
22318
  sequence: `\x1B${sequence}`,
22247
- name: name ?? "undefined",
22319
+ /* Node leaves this undefined for a sequence it does not recognise.
22320
+ * The string "undefined" is not the same thing: a caller testing
22321
+ * `key.name === undefined` would take an unknown key for a known
22322
+ * one. */
22323
+ name,
22248
22324
  ctrl: false,
22249
22325
  /* xterm reports modifiers as `;5` (control) and `;2` (shift) before
22250
22326
  * the final letter. */
@@ -22804,7 +22880,7 @@ function createCoreModules(options) {
22804
22880
  };
22805
22881
  const http = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "http:") : createUnsupportedModule("http");
22806
22882
  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");
22883
+ const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd, options.syncSpawn, () => ({ ...processObject.env })) : createUnsupportedModule("child_process");
22808
22884
  const readline = createReadlineModule(() => processObject.stdin, () => processObject.stdout);
22809
22885
  const dns = createDnsModule();
22810
22886
  const builtins = {
@@ -22865,6 +22941,7 @@ function createCoreModules(options) {
22865
22941
  pendingUnrefed: timers.pendingUnrefed,
22866
22942
  /** Client requests sent but not yet read to completion. */
22867
22943
  pendingRequests: () => inFlightRequests,
22944
+ cancelTimers: timers.cancelAll,
22868
22945
  writeStdin: (data) => {
22869
22946
  if (options.interactiveStdin) stdin.write(data);
22870
22947
  },
@@ -23546,6 +23623,27 @@ function createTrackedTimers() {
23546
23623
  live.delete(handle);
23547
23624
  native(handle);
23548
23625
  };
23626
+ const cancelAll = () => {
23627
+ for (const handle of [...live, ...unrefed]) {
23628
+ const state = states.get(handle);
23629
+ if (!state) continue;
23630
+ state.active = false;
23631
+ try {
23632
+ clearTimeout(state.native);
23633
+ } catch {
23634
+ }
23635
+ try {
23636
+ clearInterval(state.native);
23637
+ } catch {
23638
+ }
23639
+ try {
23640
+ (globalThis.clearImmediate ?? clearTimeout)(state.native);
23641
+ } catch {
23642
+ }
23643
+ }
23644
+ live.clear();
23645
+ unrefed.clear();
23646
+ };
23549
23647
  return {
23550
23648
  api: {
23551
23649
  setTimeout: setTimeoutTracked,
@@ -23556,7 +23654,8 @@ function createTrackedTimers() {
23556
23654
  clearImmediate: (handle) => clear2(handle, globalThis.clearImmediate ?? clearTimeout)
23557
23655
  },
23558
23656
  pending: () => live.size,
23559
- pendingUnrefed: () => unrefed.size
23657
+ pendingUnrefed: () => unrefed.size,
23658
+ cancelAll
23560
23659
  };
23561
23660
  }
23562
23661
  function createTimerPromises() {
@@ -24010,6 +24109,195 @@ var MemoryVolume = class {
24010
24109
  }
24011
24110
  };
24012
24111
 
24112
+ // src/runtime/mirroring-volume.ts
24113
+ init_path();
24114
+ var MirroringVolume = class {
24115
+ constructor(inner = new MemoryVolume()) {
24116
+ this.inner = inner;
24117
+ }
24118
+ inner;
24119
+ mirror;
24120
+ root = "/";
24121
+ /**
24122
+ * Start mirroring the tree under `root`, seeding it with what is there now.
24123
+ *
24124
+ * Called once the Rolldown binding is known to be in play; before that a
24125
+ * container pays nothing for this.
24126
+ */
24127
+ attach(mirror, root) {
24128
+ const cleanRoot = clean(root);
24129
+ if (this.mirror === mirror && this.root === cleanRoot) return;
24130
+ this.mirror = mirror;
24131
+ this.root = cleanRoot;
24132
+ this.seed();
24133
+ }
24134
+ detach() {
24135
+ this.mirror = void 0;
24136
+ }
24137
+ /** Copy the whole subtree across. The only bulk operation that remains. */
24138
+ seed() {
24139
+ const mirror = this.mirror;
24140
+ if (!mirror) return;
24141
+ try {
24142
+ mirror.rmSync?.(this.root, { recursive: true, force: true });
24143
+ } catch {
24144
+ }
24145
+ const copy = (path) => {
24146
+ let stat2;
24147
+ try {
24148
+ stat2 = this.inner.lstatSync(path);
24149
+ } catch {
24150
+ return;
24151
+ }
24152
+ if (stat2.isDirectory()) {
24153
+ this.safely(() => mirror.mkdirSync(path, { recursive: true }));
24154
+ for (const name of this.inner.readdirSync(path)) copy(join(path, name));
24155
+ } else if (stat2.isSymbolicLink()) {
24156
+ this.safely(() => {
24157
+ mirror.mkdirSync(dirname(path), { recursive: true });
24158
+ mirror.symlinkSync(this.inner.readlinkSync(path), path);
24159
+ });
24160
+ } else if (!path.endsWith(".node")) {
24161
+ this.safely(() => {
24162
+ mirror.mkdirSync(dirname(path), { recursive: true });
24163
+ mirror.writeFileSync(path, this.inner.readFileSync(path));
24164
+ });
24165
+ }
24166
+ };
24167
+ copy(this.root);
24168
+ }
24169
+ /** Is this path inside the mirrored subtree? */
24170
+ mirrored(path) {
24171
+ if (!this.mirror) return false;
24172
+ const clean2 = clean(path);
24173
+ return this.root === "/" || clean2 === this.root || clean2.startsWith(`${this.root}/`);
24174
+ }
24175
+ /**
24176
+ * Run a mirror update, swallowing failure.
24177
+ *
24178
+ * The mirror is a read-only cache for another engine. If it rejects
24179
+ * something — an unsupported operation, a path it has not seen — the
24180
+ * container's own write has still happened and must still succeed.
24181
+ */
24182
+ safely(update) {
24183
+ try {
24184
+ update();
24185
+ } catch {
24186
+ }
24187
+ }
24188
+ /** Push a path's current state across, whatever it is now. */
24189
+ sync(path) {
24190
+ const mirror = this.mirror;
24191
+ if (!mirror || !this.mirrored(path)) return;
24192
+ let stat2;
24193
+ try {
24194
+ stat2 = this.inner.lstatSync(path);
24195
+ } catch {
24196
+ this.safely(() => {
24197
+ if (mirror.rmSync) mirror.rmSync(path, { recursive: true, force: true });
24198
+ else mirror.unlinkSync?.(path);
24199
+ });
24200
+ return;
24201
+ }
24202
+ if (stat2.isDirectory()) {
24203
+ this.safely(() => mirror.mkdirSync(path, { recursive: true }));
24204
+ return;
24205
+ }
24206
+ if (stat2.isSymbolicLink()) {
24207
+ this.safely(() => {
24208
+ mirror.mkdirSync(dirname(path), { recursive: true });
24209
+ mirror.symlinkSync(this.inner.readlinkSync(path), path);
24210
+ });
24211
+ return;
24212
+ }
24213
+ if (path.endsWith(".node")) return;
24214
+ this.safely(() => {
24215
+ mirror.mkdirSync(dirname(path), { recursive: true });
24216
+ mirror.writeFileSync(path, this.inner.readFileSync(path));
24217
+ });
24218
+ }
24219
+ // ── reads: straight through ───────────────────────────────────────────────
24220
+ readFileSync(path) {
24221
+ return this.inner.readFileSync(path);
24222
+ }
24223
+ readdirSync(path) {
24224
+ return this.inner.readdirSync(path);
24225
+ }
24226
+ lstatSync(path) {
24227
+ return this.inner.lstatSync(path);
24228
+ }
24229
+ readlinkSync(path) {
24230
+ return this.inner.readlinkSync(path);
24231
+ }
24232
+ getStats() {
24233
+ return this.inner.getStats();
24234
+ }
24235
+ // ── writes: applied, then mirrored ────────────────────────────────────────
24236
+ writeFileSync(path, data) {
24237
+ this.inner.writeFileSync(path, data);
24238
+ this.sync(path);
24239
+ }
24240
+ appendFileSync(path, data) {
24241
+ this.inner.appendFileSync(path, data);
24242
+ this.sync(path);
24243
+ }
24244
+ mkdirSync(path, options) {
24245
+ this.inner.mkdirSync(path, options);
24246
+ this.sync(path);
24247
+ }
24248
+ rmdirSync(path) {
24249
+ this.inner.rmdirSync(path);
24250
+ this.sync(path);
24251
+ }
24252
+ unlinkSync(path) {
24253
+ this.inner.unlinkSync(path);
24254
+ this.sync(path);
24255
+ }
24256
+ renameSync(from, to) {
24257
+ this.inner.renameSync(from, to);
24258
+ this.sync(from);
24259
+ this.sync(to);
24260
+ }
24261
+ symlinkSync(target, path) {
24262
+ this.inner.symlinkSync(target, path);
24263
+ this.sync(path);
24264
+ }
24265
+ linkSync(existing, path) {
24266
+ this.inner.linkSync(existing, path);
24267
+ this.sync(path);
24268
+ }
24269
+ truncateSync(path, length) {
24270
+ this.inner.truncateSync(path, length);
24271
+ this.sync(path);
24272
+ }
24273
+ /* Permissions and timestamps do not change what a bundler resolves, and
24274
+ * `memfs` is stricter about them than this volume is. Applied here only. */
24275
+ chmodSync(path, mode) {
24276
+ this.inner.chmodSync(path, mode);
24277
+ }
24278
+ lchmodSync(path, mode) {
24279
+ this.inner.lchmodSync(path, mode);
24280
+ }
24281
+ chownSync(path, uid, gid) {
24282
+ this.inner.chownSync(path, uid, gid);
24283
+ }
24284
+ lchownSync(path, uid, gid) {
24285
+ this.inner.lchownSync(path, uid, gid);
24286
+ }
24287
+ utimesSync(path, atime, mtime) {
24288
+ this.inner.utimesSync(path, atime, mtime);
24289
+ }
24290
+ // ── snapshots ─────────────────────────────────────────────────────────────
24291
+ snapshot() {
24292
+ return this.inner.snapshot();
24293
+ }
24294
+ /** A restore replaces everything, so the mirror is rebuilt rather than patched. */
24295
+ restore(entries) {
24296
+ this.inner.restore(entries);
24297
+ this.seed();
24298
+ }
24299
+ };
24300
+
24013
24301
  // src/runtime/host-esbuild.ts
24014
24302
  var ASYNC_API = ["transform", "build", "context", "formatMessages", "analyzeMetafile"];
24015
24303
  function createHostEsbuild() {
@@ -24079,6 +24367,7 @@ function ensureProcessGlobal() {
24079
24367
  }
24080
24368
 
24081
24369
  // src/runtime/host-rolldown.ts
24370
+ 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
24371
  async function loadHostRolldownBinding() {
24083
24372
  if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) {
24084
24373
  throw new Error(
@@ -24092,7 +24381,13 @@ async function loadHostRolldownBinding() {
24092
24381
  if (typeof process !== "undefined" && process.env?.SANDBOXEDJS_DEBUG) {
24093
24382
  console.error("[sandboxedjs] Rolldown WASI binding unavailable:", error);
24094
24383
  }
24095
- throw new Error("The optional Rolldown WASI binding could not be loaded.", { cause: error });
24384
+ const reason = error instanceof Error ? error.message : String(error);
24385
+ throw new Error(
24386
+ `The optional Rolldown WASI binding could not be loaded: ${reason}` + (typeof window === "undefined" ? "" : `
24387
+
24388
+ ${BUNDLER_HINT}`),
24389
+ { cause: error }
24390
+ );
24096
24391
  }
24097
24392
  }
24098
24393
 
@@ -24132,6 +24427,7 @@ var LocalProcess = class extends EventEmitter4__default.default {
24132
24427
  inputEnded = false;
24133
24428
  pendingInput = [];
24134
24429
  resolveKilled;
24430
+ cleanup;
24135
24431
  killedPromise = new Promise((resolve2) => {
24136
24432
  this.resolveKilled = resolve2;
24137
24433
  });
@@ -24170,13 +24466,32 @@ var LocalProcess = class extends EventEmitter4__default.default {
24170
24466
  for (const chunk of this.pendingInput.splice(0)) input(chunk);
24171
24467
  if (this.inputEnded) end();
24172
24468
  }
24469
+ /**
24470
+ * End the process now.
24471
+ *
24472
+ * Killing has to settle `completion`, not merely record the intent. A
24473
+ * program with an interval outstanding has work pending forever, so the
24474
+ * runtime's own idle check will never end it — before this, killing such a
24475
+ * process left the caller awaiting a promise that could not resolve.
24476
+ */
24173
24477
  kill(_signal = "SIGTERM") {
24478
+ if (this.killed) return;
24174
24479
  this.killed = true;
24175
24480
  this.resolveKilled();
24481
+ this.cleanup?.();
24482
+ this.finish(137);
24176
24483
  }
24177
24484
  waitForKill() {
24178
24485
  return this.killedPromise;
24179
24486
  }
24487
+ isKilled() {
24488
+ return this.killed;
24489
+ }
24490
+ /** Registered by the task so a kill can release what the program still holds. */
24491
+ onKill(cleanup) {
24492
+ this.cleanup = cleanup;
24493
+ if (this.killed) cleanup();
24494
+ }
24180
24495
  /** End the process now, for an asynchronous `process.exit`. */
24181
24496
  exitNow(code) {
24182
24497
  this.finish(code);
@@ -24338,7 +24653,10 @@ function removeEscapedErrorReporting() {
24338
24653
  }
24339
24654
  var DRAIN_TURNS = 4;
24340
24655
  var LocalRuntimePod = class _LocalRuntimePod {
24341
- volume = new MemoryVolume();
24656
+ /* Wrapped so that a foreign filesystem — Rolldown's WebAssembly memfs — can
24657
+ * be kept in step as writes happen, rather than deep-copied once per spawn.
24658
+ * With nothing attached this is a straight pass-through. */
24659
+ volume = new MirroringVolume(new MemoryVolume());
24342
24660
  packages;
24343
24661
  instanceId = `sbx-${Math.random().toString(36).slice(2)}`;
24344
24662
  router = new VirtualHttpRouter();
@@ -24429,6 +24747,10 @@ var LocalRuntimePod = class _LocalRuntimePod {
24429
24747
  onRawMode: (enabled) => proc.emit("rawmode", enabled)
24430
24748
  });
24431
24749
  proc.acceptInput((data) => core.writeStdin(data), () => core.endStdin());
24750
+ proc.onKill(() => {
24751
+ core.cancelTimers();
24752
+ this.router.closeOwner(owner);
24753
+ });
24432
24754
  engine = new CommonJsEngine(this.volume, {
24433
24755
  cwd,
24434
24756
  builtins: core.builtins,
@@ -24438,7 +24760,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
24438
24760
  });
24439
24761
  try {
24440
24762
  await engine.run(script);
24441
- await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin, core.pendingRequests);
24763
+ await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin, core.pendingRequests, () => proc.isKilled());
24442
24764
  if (this.router.activePorts(owner).length) {
24443
24765
  await proc.waitForKill();
24444
24766
  return 137;
@@ -24459,13 +24781,13 @@ var LocalRuntimePod = class _LocalRuntimePod {
24459
24781
  */
24460
24782
  async prepareRolldown(cwd, env2) {
24461
24783
  const specifier = "@rolldown/binding-wasm32-wasi";
24462
- if (!this.modules[specifier] && packageInstalled(this.volume, cwd, "rolldown")) {
24784
+ if (!this.modules[specifier] && packageIsInstalled(this.volume, cwd, "rolldown")) {
24463
24785
  this.rolldownBinding ??= loadHostRolldownBinding();
24464
24786
  const binding = await this.rolldownBinding;
24465
24787
  if (binding) this.modules[specifier] = binding;
24466
24788
  }
24467
24789
  if (this.modules[specifier]) {
24468
- syncRolldownFileSystem(this.modules[specifier], this.volume, cwd);
24790
+ attachRolldownMirror(this.modules[specifier], this.volume, cwd);
24469
24791
  env2.NAPI_RS_FORCE_WASI ??= "true";
24470
24792
  }
24471
24793
  }
@@ -24484,18 +24806,18 @@ var LocalRuntimePod = class _LocalRuntimePod {
24484
24806
  * A plain script that has genuinely finished falls straight through both,
24485
24807
  * costing a handful of empty turns.
24486
24808
  */
24487
- async settle(owner, pendingHandles, pendingUnrefed, readingStdin, pendingRequests) {
24809
+ async settle(owner, pendingHandles, pendingUnrefed, readingStdin, pendingRequests, killed) {
24488
24810
  for (let turn = 0; turn < DRAIN_TURNS; turn++) {
24489
- if (this.router.activePorts(owner).length) return;
24811
+ if (this.router.activePorts(owner).length || killed()) return;
24490
24812
  await new Promise((resolve2) => setTimeout(resolve2, 0));
24491
24813
  }
24492
24814
  if (pendingHandles() === 0 && pendingRequests() === 0 && pendingUnrefed() > 0 && !readingStdin()) {
24493
24815
  const deadline = Date.now() + 1e3;
24494
- while (!this.router.activePorts(owner).length && Date.now() < deadline) {
24816
+ while (!this.router.activePorts(owner).length && !killed() && Date.now() < deadline) {
24495
24817
  await new Promise((resolve2) => setTimeout(resolve2, 5));
24496
24818
  }
24497
24819
  }
24498
- while (!this.router.activePorts(owner).length && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
24820
+ while (!this.router.activePorts(owner).length && !killed() && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
24499
24821
  await new Promise((resolve2) => setTimeout(resolve2, 5));
24500
24822
  }
24501
24823
  }
@@ -24547,7 +24869,7 @@ function formatError(error) {
24547
24869
  function isRecord(value) {
24548
24870
  return typeof value === "object" && value !== null && !Array.isArray(value);
24549
24871
  }
24550
- function packageInstalled(volume, cwd, wanted) {
24872
+ function packageIsInstalled(volume, cwd, wanted) {
24551
24873
  for (let dir3 = clean(cwd); ; dir3 = dirname(dir3)) {
24552
24874
  if (packageTreeContains(volume, join(dir3, "node_modules"), wanted, /* @__PURE__ */ new Set())) return true;
24553
24875
  if (dir3 === "/") return false;
@@ -24576,30 +24898,571 @@ function directory(volume, path) {
24576
24898
  return false;
24577
24899
  }
24578
24900
  }
24579
- function syncRolldownFileSystem(binding, volume, root) {
24901
+ function attachRolldownMirror(binding, volume, root) {
24580
24902
  const fs = binding?.__fs;
24581
24903
  if (!fs?.mkdirSync || !fs?.writeFileSync) return;
24582
- try {
24583
- fs.rmSync?.(root, { recursive: true, force: true });
24584
- } catch {
24904
+ volume.attach(fs, root);
24905
+ }
24906
+
24907
+ // src/runtime/sync-channel.ts
24908
+ var STATE = 0;
24909
+ var LENGTH = 1;
24910
+ var MORE = 2;
24911
+ var CONTROL_WORDS = 4;
24912
+ var STATE_REQUEST = 1;
24913
+ var STATE_RESPONSE = 2;
24914
+ var STATE_CONTINUE = 3;
24915
+ var STATE_CLOSED = 4;
24916
+ function createSyncChannelBuffers(capacityBytes = 1 << 20) {
24917
+ return {
24918
+ control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
24919
+ data: new SharedArrayBuffer(capacityBytes)
24920
+ };
24921
+ }
24922
+ function syncChannelSupported() {
24923
+ if (typeof SharedArrayBuffer !== "function") return false;
24924
+ if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return false;
24925
+ if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) return false;
24926
+ return true;
24927
+ }
24928
+ var SyncChannelServer = class {
24929
+ constructor(buffers, handle) {
24930
+ this.handle = handle;
24931
+ this.control = new Int32Array(buffers.control);
24932
+ this.data = new Uint8Array(buffers.data);
24933
+ this.capacity = this.data.length;
24585
24934
  }
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 });
24935
+ handle;
24936
+ control;
24937
+ data;
24938
+ capacity;
24939
+ /** Request chunks gathered so far, and the response still to be sent. */
24940
+ incoming = [];
24941
+ outgoing = null;
24942
+ sent = 0;
24943
+ closed = false;
24944
+ /** Call from the wake message the client sends after each chunk. */
24945
+ async pump() {
24946
+ if (this.closed) return;
24947
+ const state = Atomics.load(this.control, STATE);
24948
+ if (state === STATE_REQUEST) {
24949
+ const size = Atomics.load(this.control, LENGTH);
24950
+ this.incoming.push(this.data.slice(0, size));
24951
+ if (Atomics.load(this.control, MORE) === 1) {
24952
+ this.publish(STATE_CONTINUE);
24953
+ return;
24954
+ }
24955
+ const request = concat3(this.incoming);
24956
+ this.incoming = [];
24957
+ let response;
24593
24958
  try {
24594
- fs.symlinkSync(volume.readlinkSync(path), path);
24959
+ response = await this.handle(request);
24595
24960
  } catch {
24961
+ response = new Uint8Array();
24962
+ }
24963
+ if (this.closed) return;
24964
+ this.outgoing = response;
24965
+ this.sent = 0;
24966
+ this.sendChunk();
24967
+ return;
24968
+ }
24969
+ if (state === STATE_CONTINUE) this.sendChunk();
24970
+ }
24971
+ /** Release a blocked client, e.g. when the container is torn down. */
24972
+ close() {
24973
+ if (this.closed) return;
24974
+ this.closed = true;
24975
+ Atomics.store(this.control, STATE, STATE_CLOSED);
24976
+ Atomics.notify(this.control, STATE);
24977
+ }
24978
+ sendChunk() {
24979
+ const payload = this.outgoing ?? new Uint8Array();
24980
+ const size = Math.min(this.capacity, payload.length - this.sent);
24981
+ this.data.set(payload.subarray(this.sent, this.sent + size), 0);
24982
+ this.sent += size;
24983
+ Atomics.store(this.control, LENGTH, size);
24984
+ Atomics.store(this.control, MORE, this.sent < payload.length ? 1 : 0);
24985
+ this.publish(STATE_RESPONSE);
24986
+ }
24987
+ publish(state) {
24988
+ Atomics.store(this.control, STATE, state);
24989
+ Atomics.notify(this.control, STATE);
24990
+ }
24991
+ };
24992
+ function concat3(parts) {
24993
+ if (parts.length === 1) return parts[0];
24994
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
24995
+ const joined = new Uint8Array(total);
24996
+ let at = 0;
24997
+ for (const part of parts) {
24998
+ joined.set(part, at);
24999
+ at += part.length;
25000
+ }
25001
+ return joined;
25002
+ }
25003
+
25004
+ // src/runtime/remote-volume.ts
25005
+ var encoder7 = new TextEncoder();
25006
+ var decoder7 = new TextDecoder();
25007
+ function encodeFrame(header, body) {
25008
+ const json = encoder7.encode(JSON.stringify(header));
25009
+ const frame = new Uint8Array(4 + json.length + (body?.length ?? 0));
25010
+ new DataView(frame.buffer).setUint32(0, json.length, true);
25011
+ frame.set(json, 4);
25012
+ if (body?.length) frame.set(body, 4 + json.length);
25013
+ return frame;
25014
+ }
25015
+ function decodeFrame(frame) {
25016
+ const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
25017
+ const headerLength = view.getUint32(0, true);
25018
+ const header = JSON.parse(decoder7.decode(frame.subarray(4, 4 + headerLength)));
25019
+ return { header, body: frame.subarray(4 + headerLength) };
25020
+ }
25021
+ function flattenStat(stat2) {
25022
+ return {
25023
+ kind: stat2.isDirectory() ? "directory" : stat2.isSymbolicLink() ? "symlink" : "file",
25024
+ mode: stat2.mode,
25025
+ size: stat2.size,
25026
+ uid: stat2.uid,
25027
+ gid: stat2.gid,
25028
+ ino: stat2.ino,
25029
+ nlink: stat2.nlink,
25030
+ atimeMs: stat2.atimeMs,
25031
+ mtimeMs: stat2.mtimeMs,
25032
+ ctimeMs: stat2.ctimeMs,
25033
+ birthtimeMs: stat2.birthtimeMs
25034
+ };
25035
+ }
25036
+ var BINARY_RESULTS = /* @__PURE__ */ new Set(["readFileSync"]);
25037
+ var BINARY_ARGUMENTS = /* @__PURE__ */ new Set(["writeFileSync", "appendFileSync"]);
25038
+ function serveVolume(volume) {
25039
+ return (request) => {
25040
+ const { header, body } = decodeFrame(request);
25041
+ const target = volume;
25042
+ try {
25043
+ const method = target[header.op];
25044
+ if (typeof method !== "function") {
25045
+ return encodeFrame({ ok: false, error: { message: `unknown volume operation: ${header.op}` } });
25046
+ }
25047
+ const plain = header.args.map((value) => value === null ? void 0 : value);
25048
+ const args = BINARY_ARGUMENTS.has(header.op) ? [plain[0], body] : header.op === "utimesSync" ? [plain[0], new Date(plain[1]), new Date(plain[2])] : plain;
25049
+ const result = method.apply(volume, args);
25050
+ if (BINARY_RESULTS.has(header.op)) {
25051
+ return encodeFrame({ ok: true }, result);
24596
25052
  }
24597
- } else if (!path.endsWith(".node")) {
24598
- fs.mkdirSync(dirname(path), { recursive: true });
24599
- fs.writeFileSync(path, volume.readFileSync(path));
25053
+ if (header.op === "lstatSync") {
25054
+ return encodeFrame({ ok: true, value: flattenStat(result) });
25055
+ }
25056
+ return encodeFrame({ ok: true, value: result });
25057
+ } catch (error) {
25058
+ const failure = error;
25059
+ return encodeFrame({
25060
+ ok: false,
25061
+ error: { ...failure.code ? { code: failure.code } : {}, message: failure.message ?? String(error) }
25062
+ });
25063
+ }
25064
+ };
25065
+ }
25066
+
25067
+ // src/runtime/sync-syscalls.ts
25068
+ var SPAWN_OP = "spawnSync";
25069
+ function serveSyncSyscalls(options) {
25070
+ const volumeHandler = serveVolume(options.volume);
25071
+ return async (request) => {
25072
+ const { header } = decodeFrame(request);
25073
+ if (header.op !== SPAWN_OP) return volumeHandler(request);
25074
+ try {
25075
+ const result = await options.spawnChild(header.args[0]);
25076
+ return encodeFrame({ ok: true, value: result });
25077
+ } catch (error) {
25078
+ const failure = error;
25079
+ return encodeFrame({
25080
+ ok: true,
25081
+ value: {
25082
+ status: null,
25083
+ stdout: "",
25084
+ stderr: "",
25085
+ signal: null,
25086
+ error: { ...failure.code ? { code: failure.code } : {}, message: failure.message ?? String(error) }
25087
+ }
25088
+ });
24600
25089
  }
24601
25090
  };
24602
- copy(clean(root));
25091
+ }
25092
+
25093
+ // src/runtime/worker-host.ts
25094
+ function defaultWorkerUrl() {
25095
+ 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)));
25096
+ }
25097
+ function hasDomWorker() {
25098
+ return typeof Worker === "function" && typeof document !== "undefined";
25099
+ }
25100
+ async function startRuntimeWorker(options = {}) {
25101
+ const url = options.url ?? defaultWorkerUrl();
25102
+ const worker = hasDomWorker() ? await startDomWorker(url) : await startNodeWorker(url, options.workerData ?? {});
25103
+ await new Promise((resolve2, reject) => {
25104
+ const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
25105
+ worker.onMessage((message) => {
25106
+ if (message?.type === "sandboxedjs:ready") {
25107
+ clearTimeout(timer);
25108
+ resolve2();
25109
+ }
25110
+ });
25111
+ worker.onError((error) => {
25112
+ clearTimeout(timer);
25113
+ reject(error instanceof Error ? error : new Error(String(error)));
25114
+ });
25115
+ });
25116
+ return worker;
25117
+ }
25118
+ async function startDomWorker(url) {
25119
+ const worker = new Worker(url, { type: "module" });
25120
+ const listeners = [];
25121
+ const errors = [];
25122
+ worker.addEventListener("message", (event) => {
25123
+ for (const listener of listeners) listener(event.data);
25124
+ });
25125
+ worker.addEventListener("error", (event) => {
25126
+ const detail = event.message || "the runtime worker failed to load";
25127
+ for (const listener of errors) listener(new Error(detail));
25128
+ });
25129
+ return {
25130
+ postMessage: (message) => worker.postMessage(message),
25131
+ onMessage: (listener) => {
25132
+ listeners.push(listener);
25133
+ },
25134
+ onError: (listener) => {
25135
+ errors.push(listener);
25136
+ },
25137
+ terminate: () => worker.terminate()
25138
+ };
25139
+ }
25140
+ async function startNodeWorker(url, workerData) {
25141
+ const specifier = ["node", "worker_threads"].join(":");
25142
+ const { Worker: NodeWorker } = await import(
25143
+ /* @vite-ignore */
25144
+ /* webpackIgnore: true */
25145
+ specifier
25146
+ );
25147
+ const worker = new NodeWorker(url, { workerData });
25148
+ return {
25149
+ postMessage: (message) => worker.postMessage(message),
25150
+ onMessage: (listener) => worker.on("message", listener),
25151
+ onError: (listener) => worker.on("error", listener),
25152
+ terminate: () => worker.terminate()
25153
+ };
25154
+ }
25155
+ var WorkerProcess = class extends EventEmitter4__default.default {
25156
+ constructor(worker, release) {
25157
+ super();
25158
+ this.worker = worker;
25159
+ this.release = release;
25160
+ super.on("error", () => {
25161
+ });
25162
+ this.completion = new Promise((resolve2) => {
25163
+ this.resolveCompletion = resolve2;
25164
+ });
25165
+ }
25166
+ worker;
25167
+ release;
25168
+ completion;
25169
+ resolveCompletion;
25170
+ settled = false;
25171
+ out = [];
25172
+ err = [];
25173
+ started = false;
25174
+ pendingInput = [];
25175
+ inputEnded = false;
25176
+ on(event, listener) {
25177
+ return super.on(event, listener);
25178
+ }
25179
+ /** Called once the guest has been told to start; releases anything buffered. */
25180
+ begin() {
25181
+ this.started = true;
25182
+ for (const chunk of this.pendingInput.splice(0)) this.worker.postMessage({ type: "stdin", data: chunk });
25183
+ if (this.inputEnded) this.worker.postMessage({ type: "stdin-end" });
25184
+ }
25185
+ output(text2) {
25186
+ this.out.push(text2);
25187
+ this.emit("output", text2);
25188
+ }
25189
+ error(text2) {
25190
+ this.err.push(text2);
25191
+ this.emit("error", text2);
25192
+ }
25193
+ /**
25194
+ * Deliver input to the running program.
25195
+ *
25196
+ * Held until the guest has been started, for the same reason the in-realm
25197
+ * pod holds it: an interactive session's first keystrokes arrive while the
25198
+ * program is still loading, and dropping them loses the answer to a prompt.
25199
+ */
25200
+ write(data) {
25201
+ if (this.started) this.worker.postMessage({ type: "stdin", data });
25202
+ else this.pendingInput.push(data);
25203
+ }
25204
+ endInput() {
25205
+ this.inputEnded = true;
25206
+ if (this.started) this.worker.postMessage({ type: "stdin-end" });
25207
+ }
25208
+ kill(_signal = "SIGTERM") {
25209
+ this.worker.postMessage({ type: "kill" });
25210
+ this.finish(137);
25211
+ }
25212
+ finish(exitCode) {
25213
+ if (this.settled) return;
25214
+ this.settled = true;
25215
+ this.emit("exit", exitCode);
25216
+ this.resolveCompletion({ exitCode, stdout: this.out.join(""), stderr: this.err.join("") });
25217
+ this.release();
25218
+ }
25219
+ };
25220
+ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25221
+ workerUrl;
25222
+ /** Live workers, so teardown can stop them all. */
25223
+ live = /* @__PURE__ */ new Set();
25224
+ constructor(options) {
25225
+ super(options);
25226
+ this.workerUrl = options.workerUrl;
25227
+ }
25228
+ /**
25229
+ * Boot a Worker-backed pod, or return null when this host cannot support one.
25230
+ *
25231
+ * Declining is a first-class outcome. `SharedArrayBuffer` needs cross-origin
25232
+ * isolation, a bundler may have made the guest script unreachable, and a host
25233
+ * that supplied its own module objects has handed over things no thread
25234
+ * boundary can carry. In every case the caller falls back to the in-realm pod
25235
+ * and keeps working.
25236
+ */
25237
+ static async tryBoot(options = {}) {
25238
+ if (!syncChannelSupported()) return null;
25239
+ if (options.modules && Object.keys(options.modules).length > 0) return null;
25240
+ const pod = new _WorkerRuntimePod(options);
25241
+ try {
25242
+ const probe = await startRuntimeWorker({ ...options.workerUrl ? { url: options.workerUrl } : {}, timeoutMs: 1e4 });
25243
+ await probe.terminate();
25244
+ return pod;
25245
+ } catch {
25246
+ pod.teardown();
25247
+ return null;
25248
+ }
25249
+ }
25250
+ async spawn(command, args = [], options = {}) {
25251
+ if (command !== "node" && command !== "nodejs") return super.spawn(command, args, options);
25252
+ const script = args[0];
25253
+ if (!script) return super.spawn(command, args, options);
25254
+ const cwd = typeof options.cwd === "string" ? options.cwd : this.workdir;
25255
+ if (this.needsHostModules(cwd)) return super.spawn(command, args, options);
25256
+ return await this.spawnInWorker(script, args, cwd, options);
25257
+ }
25258
+ async spawnInWorker(script, args, cwd, options) {
25259
+ const env2 = { ...this.env, ...isRecord2(options.env) ? options.env : {} };
25260
+ const owner = `${this.instanceId}:${Math.random().toString(36).slice(2)}`;
25261
+ const buffers = createSyncChannelBuffers();
25262
+ const worker = await startRuntimeWorker({
25263
+ ...this.workerUrl ? { url: this.workerUrl } : {},
25264
+ timeoutMs: 15e3
25265
+ });
25266
+ const streams = { target: null };
25267
+ const server = new SyncChannelServer(buffers, serveSyncSyscalls({
25268
+ volume: this.volume,
25269
+ spawnChild: (request) => this.runChildToCompletion(request, streams.target)
25270
+ }));
25271
+ const entry = { worker, server };
25272
+ this.live.add(entry);
25273
+ const process2 = new WorkerProcess(worker, () => {
25274
+ this.live.delete(entry);
25275
+ server.close();
25276
+ this.closeProxies(owner);
25277
+ void worker.terminate();
25278
+ });
25279
+ streams.target = process2;
25280
+ const children = /* @__PURE__ */ new Map();
25281
+ worker.onMessage((raw) => {
25282
+ const message = raw;
25283
+ switch (message?.type) {
25284
+ case "wake":
25285
+ void server.pump();
25286
+ return;
25287
+ case "output":
25288
+ process2.output(String(message.text));
25289
+ return;
25290
+ case "error":
25291
+ process2.error(String(message.text));
25292
+ return;
25293
+ case "rawmode":
25294
+ process2.emit("rawmode", Boolean(message.enabled));
25295
+ return;
25296
+ case "exit":
25297
+ process2.finish(Number(message.code));
25298
+ return;
25299
+ case "listen":
25300
+ this.proxyPort(Number(message.port), owner, worker);
25301
+ return;
25302
+ case "http-response":
25303
+ this.settleProxied(Number(message.id), message.response);
25304
+ return;
25305
+ case "child-start":
25306
+ this.startChild(worker, children, message);
25307
+ return;
25308
+ case "child-stdin":
25309
+ children.get(message.id)?.sendStdin?.(String(message.data));
25310
+ return;
25311
+ case "child-stdin-end":
25312
+ children.get(message.id)?.endStdin?.();
25313
+ return;
25314
+ case "child-kill":
25315
+ children.get(message.id)?.kill(String(message.signal));
25316
+ return;
25317
+ default:
25318
+ return;
25319
+ }
25320
+ });
25321
+ worker.postMessage({
25322
+ type: "start",
25323
+ buffers,
25324
+ script,
25325
+ cwd,
25326
+ env: env2,
25327
+ argv: Array.isArray(options.argv) ? options.argv : ["/usr/bin/node", script, ...args.slice(1)],
25328
+ aliases: this.aliases,
25329
+ ...typeof options.stdinPath === "string" ? { stdinPath: options.stdinPath } : {},
25330
+ ...options.interactiveStdin ? { interactiveStdin: true } : {},
25331
+ ...options.tty ? { tty: true } : {}
25332
+ });
25333
+ process2.begin();
25334
+ return process2;
25335
+ }
25336
+ /** Start an asynchronous child on the host's behalf and relay its events. */
25337
+ startChild(worker, children, message) {
25338
+ const handle = this.processManager.spawn(message.config);
25339
+ children.set(message.id, handle);
25340
+ for (const event of ["stdout", "stderr", "exit"]) {
25341
+ handle.on(event, (value) => worker.postMessage({ type: "child-event", id: message.id, event, value }));
25342
+ }
25343
+ handle.exec();
25344
+ }
25345
+ /** Run a child to completion and collect it, for the guest's `spawnSync`. */
25346
+ runChildToCompletion(request, streamTo) {
25347
+ return new Promise((resolve2) => {
25348
+ let handle;
25349
+ try {
25350
+ handle = this.processManager.spawn({
25351
+ command: request.command,
25352
+ args: request.args,
25353
+ cwd: request.cwd,
25354
+ ...request.env ? { env: request.env } : {},
25355
+ ...request.inheritStdio ? { inheritStdio: true } : {}
25356
+ });
25357
+ } catch (error) {
25358
+ const failure = error;
25359
+ resolve2({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure.code ? { code: failure.code } : {}, message: failure.message } });
25360
+ return;
25361
+ }
25362
+ let stdout = "";
25363
+ let stderr = "";
25364
+ const live = request.inheritStdio ? streamTo : null;
25365
+ handle.on("stdout", (text2) => {
25366
+ stdout += text2;
25367
+ live?.output(text2);
25368
+ });
25369
+ handle.on("stderr", (text2) => {
25370
+ stderr += text2;
25371
+ live?.error(text2);
25372
+ });
25373
+ handle.on("exit", (code) => resolve2({ status: code, stdout, stderr, signal: null }));
25374
+ handle.exec();
25375
+ if (request.input !== void 0) {
25376
+ handle.sendStdin?.(request.input);
25377
+ }
25378
+ if (!request.inheritStdio) handle.endStdin?.();
25379
+ });
25380
+ }
25381
+ // ── HTTP servers living on another thread ─────────────────────────────────
25382
+ proxies = /* @__PURE__ */ new Map();
25383
+ waiting = /* @__PURE__ */ new Map();
25384
+ nextRequestId = 1;
25385
+ /**
25386
+ * Register a stand-in for a server that is actually running in the Worker.
25387
+ *
25388
+ * The router only knows how to reach servers on this thread, so each bound
25389
+ * port gets a local server whose whole job is to forward and wait.
25390
+ */
25391
+ proxyPort(port, owner, worker) {
25392
+ if (this.proxies.has(port)) return;
25393
+ const server = new VirtualHttpServer(this.router, owner, (request, response) => {
25394
+ void this.forward(worker, port, request, response);
25395
+ });
25396
+ try {
25397
+ server.listen(port);
25398
+ this.proxies.set(port, server);
25399
+ } catch {
25400
+ }
25401
+ }
25402
+ async forward(worker, port, request, response) {
25403
+ const chunks = [];
25404
+ for await (const chunk of request) chunks.push(chunk);
25405
+ const body = concat4(chunks);
25406
+ const id = this.nextRequestId++;
25407
+ const answered = new Promise((resolve2) => this.waiting.set(id, resolve2));
25408
+ worker.postMessage({
25409
+ type: "http-request",
25410
+ id,
25411
+ port,
25412
+ init: { method: request.method, path: request.url, headers: request.headers, body }
25413
+ });
25414
+ let result;
25415
+ try {
25416
+ result = await answered;
25417
+ } catch (error) {
25418
+ this.waiting.delete(id);
25419
+ response.writeHead(500, "Internal Server Error", {});
25420
+ response.end(new TextEncoder().encode(error instanceof Error ? error.message : String(error)));
25421
+ return;
25422
+ }
25423
+ response.writeHead(Number(result.statusCode ?? 200), String(result.statusMessage ?? ""), result.headers ?? {});
25424
+ response.end(result.body ?? new Uint8Array());
25425
+ }
25426
+ settleProxied(id, response) {
25427
+ const resolve2 = this.waiting.get(id);
25428
+ if (!resolve2) return;
25429
+ this.waiting.delete(id);
25430
+ resolve2(response);
25431
+ }
25432
+ closeProxies(owner) {
25433
+ for (const [port, server] of [...this.proxies]) {
25434
+ if (server.owner !== owner) continue;
25435
+ server.close();
25436
+ this.proxies.delete(port);
25437
+ }
25438
+ }
25439
+ teardown() {
25440
+ for (const { worker, server } of [...this.live]) {
25441
+ server.close();
25442
+ void worker.terminate();
25443
+ }
25444
+ this.live.clear();
25445
+ super.teardown();
25446
+ }
25447
+ /** Does anything under `cwd` need a module only the host can supply? */
25448
+ needsHostModules(cwd) {
25449
+ return packageIsInstalled(this.volume, cwd, "rolldown");
25450
+ }
25451
+ };
25452
+ function isRecord2(value) {
25453
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25454
+ }
25455
+ function concat4(parts) {
25456
+ if (parts.length === 0) return new Uint8Array();
25457
+ if (parts.length === 1) return parts[0];
25458
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
25459
+ const joined = new Uint8Array(total);
25460
+ let at = 0;
25461
+ for (const part of parts) {
25462
+ joined.set(part, at);
25463
+ at += part.length;
25464
+ }
25465
+ return joined;
24603
25466
  }
24604
25467
 
24605
25468
  // src/container/container.ts
@@ -24646,11 +25509,13 @@ var Container = class _Container {
24646
25509
  // ── boot ──────────────────────────────────────────────────────────────────
24647
25510
  static async create(opts = {}) {
24648
25511
  if (opts.python) configurePython(opts.python);
24649
- const pod = opts.pod ?? await LocalRuntimePod.boot({
25512
+ const podOptions = {
24650
25513
  workdir: opts.cwd ?? "/",
24651
25514
  env: opts.env ?? {},
24652
25515
  ...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
24653
- });
25516
+ };
25517
+ const workerOptions = { ...podOptions, ...opts.workerUrl ? { workerUrl: opts.workerUrl } : {} };
25518
+ const pod = opts.pod ?? (opts.isolation === "realm" ? null : await WorkerRuntimePod.tryBoot(workerOptions)) ?? await LocalRuntimePod.boot(podOptions);
24654
25519
  const kernel = new Kernel({
24655
25520
  pod,
24656
25521
  hostname: opts.hostname ?? "sandbox",
@@ -24892,15 +25757,15 @@ var Container = class _Container {
24892
25757
  }
24893
25758
  makeStdio(opts) {
24894
25759
  const combined = [];
24895
- const decoder7 = new TextDecoder();
25760
+ const decoder8 = new TextDecoder();
24896
25761
  const stdout = new BufferSink((chunk) => {
24897
- const text2 = decoder7.decode(chunk, { stream: true });
25762
+ const text2 = decoder8.decode(chunk, { stream: true });
24898
25763
  combined.push(text2);
24899
25764
  opts.onStdout?.(text2);
24900
25765
  this.hooks.onStdout?.(text2);
24901
25766
  });
24902
25767
  const stderr = new BufferSink((chunk) => {
24903
- const text2 = decoder7.decode(chunk, { stream: true });
25768
+ const text2 = decoder8.decode(chunk, { stream: true });
24904
25769
  combined.push(text2);
24905
25770
  opts.onStderr?.(text2);
24906
25771
  this.hooks.onStderr?.(text2);
@@ -25511,6 +26376,97 @@ init_variables();
25511
26376
  init_arith();
25512
26377
  init_expand();
25513
26378
  init_builtins();
26379
+
26380
+ // src/preview/register.ts
26381
+ function serveContainerOn(port, box) {
26382
+ port.onmessage = async (event) => {
26383
+ const request = event.data;
26384
+ try {
26385
+ const response = await box.request(request.port, {
26386
+ method: request.method,
26387
+ path: request.path,
26388
+ headers: request.headers,
26389
+ ...request.body ? { body: new Uint8Array(request.body) } : {}
26390
+ });
26391
+ const bytes2 = response.bytes.slice();
26392
+ port.postMessage(
26393
+ {
26394
+ id: request.id,
26395
+ response: {
26396
+ status: response.status,
26397
+ statusText: response.statusText,
26398
+ headers: response.headers,
26399
+ body: bytes2.buffer
26400
+ }
26401
+ },
26402
+ [bytes2.buffer]
26403
+ );
26404
+ } catch (error) {
26405
+ const message = new TextEncoder().encode(error instanceof Error ? error.message : String(error));
26406
+ port.postMessage({
26407
+ id: request.id,
26408
+ response: { status: 502, statusText: "Bad Gateway", headers: {}, body: message.buffer }
26409
+ });
26410
+ }
26411
+ };
26412
+ port.start?.();
26413
+ }
26414
+ async function createPreview(box, options = {}) {
26415
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return null;
26416
+ const scriptUrl = options.scriptUrl ?? new URL("./service-worker.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
26417
+ let registration;
26418
+ try {
26419
+ registration = await navigator.serviceWorker.register(scriptUrl, {
26420
+ type: "module",
26421
+ ...options.scope ? { scope: options.scope } : {}
26422
+ });
26423
+ } catch {
26424
+ return null;
26425
+ }
26426
+ const worker = registration.active ?? registration.waiting ?? registration.installing;
26427
+ if (!worker) return null;
26428
+ if (worker.state !== "activated") {
26429
+ const activated = await new Promise((resolve2) => {
26430
+ const check = () => {
26431
+ if (worker.state === "activated") {
26432
+ worker.removeEventListener("statechange", check);
26433
+ resolve2(true);
26434
+ } else if (worker.state === "redundant") {
26435
+ worker.removeEventListener("statechange", check);
26436
+ resolve2(false);
26437
+ }
26438
+ };
26439
+ worker.addEventListener("statechange", check);
26440
+ setTimeout(() => resolve2(worker.state === "activated"), 1e4);
26441
+ check();
26442
+ });
26443
+ if (!activated) return null;
26444
+ }
26445
+ const channel = new MessageChannel();
26446
+ serveContainerOn(channel.port1, box);
26447
+ worker.postMessage({ type: "sandboxedjs:connect" }, [channel.port2]);
26448
+ const base2 = registration.scope.replace(/\/$/, "");
26449
+ return {
26450
+ urlFor: (port) => `${base2}/__sbx__/${port}/`,
26451
+ dispose: async () => {
26452
+ channel.port1.close();
26453
+ await registration.unregister();
26454
+ }
26455
+ };
26456
+ }
26457
+ async function renderInto(box, element, options = { port: 80 }) {
26458
+ const response = await box.request(options.port, { path: options.path ?? "/" });
26459
+ const frame = document.createElement("iframe");
26460
+ frame.setAttribute("sandbox", "allow-scripts");
26461
+ frame.style.width = "100%";
26462
+ frame.style.height = "100%";
26463
+ frame.style.border = "0";
26464
+ frame.srcdoc = response.body;
26465
+ element.replaceChildren(frame);
26466
+ return frame;
26467
+ }
26468
+
26469
+ // src/index.ts
25514
26470
  var src_default = createContainer;
25515
26471
 
25516
26472
  exports.BufferSink = BufferSink;
@@ -25554,6 +26510,7 @@ exports.VirtualHttpServer = VirtualHttpServer;
25554
26510
  exports.VirtualIncomingMessage = VirtualIncomingMessage;
25555
26511
  exports.VirtualServerResponse = VirtualServerResponse;
25556
26512
  exports.WASM_ALIASES = WASM_ALIASES;
26513
+ exports.WorkerRuntimePod = WorkerRuntimePod;
25557
26514
  exports.allCommands = allCommands;
25558
26515
  exports.applyChmod = applyChmod;
25559
26516
  exports.braceExpand = braceExpand;
@@ -25566,6 +26523,7 @@ exports.createChildProcessModule = createChildProcessModule;
25566
26523
  exports.createContainer = createContainer;
25567
26524
  exports.createContext = createContext;
25568
26525
  exports.createCoreModules = createCoreModules;
26526
+ exports.createPreview = createPreview;
25569
26527
  exports.default = src_default;
25570
26528
  exports.defineCommand = defineCommand;
25571
26529
  exports.evalArith = evalArith;
@@ -25592,9 +26550,12 @@ exports.octalMode = octalMode;
25592
26550
  exports.parseShell = parse;
25593
26551
  exports.parseUmask = parseUmask;
25594
26552
  exports.posixPath = path_exports;
26553
+ exports.renderInto = renderInto;
25595
26554
  exports.resetPidCounter = resetPidCounter;
25596
26555
  exports.shellQuote = shellQuote;
26556
+ exports.startRuntimeWorker = startRuntimeWorker;
25597
26557
  exports.strerror = strerror;
26558
+ exports.syncChannelSupported = syncChannelSupported;
25598
26559
  exports.transformEsm = transformEsm;
25599
26560
  exports.unameInfo = unameInfo;
25600
26561
  //# sourceMappingURL=index.cjs.map