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