sandboxedjs 0.1.29 → 0.1.31

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
@@ -4795,7 +4795,15 @@ var Pipe = class _Pipe {
4795
4795
  * whole line on every keystroke — so the terminal must stop echoing, or
4796
4796
  * every character appears twice.
4797
4797
  */
4798
- rawMode = false;
4798
+ raw = false;
4799
+ onRawMode;
4800
+ get rawMode() {
4801
+ return this.raw;
4802
+ }
4803
+ set rawMode(enabled) {
4804
+ this.raw = enabled;
4805
+ this.onRawMode?.(enabled);
4806
+ }
4799
4807
  columns;
4800
4808
  rows;
4801
4809
  get closed() {
@@ -5923,13 +5931,19 @@ var Kernel = class {
5923
5931
  stderr: opts.stderr ?? new NullOutput()
5924
5932
  }
5925
5933
  });
5934
+ const cancelWithParent = () => {
5935
+ if (parent?.termSignal) proc.deliver(parent.termSignal);
5936
+ };
5937
+ parent?.signal.addEventListener("abort", cancelWithParent, { once: true });
5938
+ if (parent?.signal.aborted) cancelWithParent();
5939
+ void proc.wait().then(() => parent?.signal.removeEventListener("abort", cancelWithParent));
5926
5940
  let timer;
5927
5941
  if (opts.timeoutMs !== void 0) {
5928
5942
  timer = setTimeout(() => {
5929
5943
  proc.deliver("SIGKILL");
5930
5944
  }, opts.timeoutMs);
5931
5945
  }
5932
- void this.dispatch(proc).then((code) => {
5946
+ void (proc.signal.aborted ? Promise.resolve(proc.exitCode ?? 137) : this.dispatch(proc)).then((code) => {
5933
5947
  proc.exit(code);
5934
5948
  }).catch((e) => {
5935
5949
  try {
@@ -7921,6 +7935,22 @@ var Shell = class _Shell {
7921
7935
  }
7922
7936
  return this.lastStatus;
7923
7937
  }
7938
+ /** Signal the current terminal pipeline without terminating the interactive shell. */
7939
+ interruptForeground(signal, stdin) {
7940
+ const processes = this.kernel.procs.list().filter((process2) => process2.running);
7941
+ const selected = new Set(processes.filter((process2) => process2.ppid === this.proc.pid && process2.stdin === stdin).map((process2) => process2.pid));
7942
+ for (let changed = true; changed; ) {
7943
+ changed = false;
7944
+ const outputs = processes.filter((process2) => selected.has(process2.pid)).flatMap((process2) => [process2.stdout, process2.stderr]);
7945
+ for (const process2 of processes) {
7946
+ if (!selected.has(process2.pid) && (selected.has(process2.ppid) || outputs.some((output) => output === process2.stdin))) {
7947
+ selected.add(process2.pid);
7948
+ changed = true;
7949
+ }
7950
+ }
7951
+ }
7952
+ for (const process2 of processes) if (selected.has(process2.pid)) process2.deliver(signal);
7953
+ }
7924
7954
  get isExiting() {
7925
7955
  return this.exiting;
7926
7956
  }
@@ -8570,6 +8600,7 @@ sys ${fmt2(Math.floor(ms * 0.2))}
8570
8600
  for (const assignment of assignments) {
8571
8601
  env2[assignment.name] = await expandWordToString(assignment.value, this.expandContext());
8572
8602
  }
8603
+ if (this.proc.signal.aborted) return this.proc.exitCode ?? 137;
8573
8604
  const proc = this.kernel.spawn(words, {
8574
8605
  cwd: this.cwd,
8575
8606
  env: env2,
@@ -19712,6 +19743,11 @@ var KernelChildProcess = class {
19712
19743
  started = false;
19713
19744
  cancelled = false;
19714
19745
  constructor(kernel, cred, config, pid) {
19746
+ if (config.inheritStdio) {
19747
+ this.stdin.isTTY = true;
19748
+ this.stdin.interactive = true;
19749
+ }
19750
+ this.stdin.onRawMode = (enabled) => this.emit("rawmode", enabled);
19715
19751
  this.kernel = kernel;
19716
19752
  this.cred = cred;
19717
19753
  this.pid = pid;
@@ -19828,7 +19864,7 @@ function installNodeChildProcessBridge(pod, kernel, cred) {
19828
19864
  const originalSpawn = manager.spawn;
19829
19865
  manager.spawn = (config) => {
19830
19866
  const resolved = kernel.resolveExecutable(config.command, config.cwd ?? "/", config.env ?? {}, cred);
19831
- if (resolved?.kind === "builtin" && resolved.command?.name !== "node" && resolved.command?.name !== "nodejs") {
19867
+ if (resolved?.kind === "builtin") {
19832
19868
  return new KernelChildProcess(kernel, cred, config, nextBridgePid++);
19833
19869
  }
19834
19870
  return originalSpawn.call(manager, config);
@@ -19839,6 +19875,68 @@ function installNodeChildProcessBridge(pod, kernel, cred) {
19839
19875
  };
19840
19876
  }
19841
19877
 
19878
+ // src/runtime/host-module-tracker.ts
19879
+ var HostModuleTracker = class {
19880
+ active = 0;
19881
+ disposed = false;
19882
+ dispose() {
19883
+ this.disposed = true;
19884
+ }
19885
+ wrapped = /* @__PURE__ */ new WeakMap();
19886
+ originals = /* @__PURE__ */ new WeakMap();
19887
+ pending = () => this.active;
19888
+ unwrap = (value) => value && (typeof value === "object" || typeof value === "function") ? this.originals.get(value) ?? value : value;
19889
+ wrap(value) {
19890
+ if (value === null || typeof value !== "object" && typeof value !== "function") return value;
19891
+ const object = value;
19892
+ if (Array.isArray(value) || ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return value;
19893
+ if (this.wrapped.has(object)) return this.wrapped.get(object);
19894
+ const tracker = this;
19895
+ const proxy = new Proxy(object, {
19896
+ // CommonJS native loaders re-export by assigning exports back onto the
19897
+ // binding object. Never store a process-owned proxy in a shared module.
19898
+ set(target, key, member) {
19899
+ return Reflect.set(target, key, tracker.unwrap(member), target);
19900
+ },
19901
+ defineProperty(target, key, descriptor) {
19902
+ return Reflect.defineProperty(target, key, "value" in descriptor ? { ...descriptor, value: tracker.unwrap(descriptor.value) } : descriptor);
19903
+ },
19904
+ get(target, key) {
19905
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
19906
+ const member = Reflect.get(target, key, target);
19907
+ if (descriptor && !descriptor.configurable && "value" in descriptor && !descriptor.writable) return member;
19908
+ return typeof member === "function" ? tracker.wrap(member) : member;
19909
+ },
19910
+ apply(target, receiver, args) {
19911
+ const result = Reflect.apply(target, tracker.unwrap(receiver), args.map(tracker.unwrap));
19912
+ if (result && typeof result.then === "function") {
19913
+ tracker.active++;
19914
+ return Promise.resolve(result).then(
19915
+ (value2) => {
19916
+ tracker.active--;
19917
+ return tracker.disposed ? new Promise(() => {
19918
+ }) : tracker.wrap(value2);
19919
+ },
19920
+ (error) => {
19921
+ tracker.active--;
19922
+ if (tracker.disposed) return new Promise(() => {
19923
+ });
19924
+ throw error;
19925
+ }
19926
+ );
19927
+ }
19928
+ return tracker.wrap(result);
19929
+ },
19930
+ construct(target, args, newTarget) {
19931
+ return tracker.wrap(Reflect.construct(target, args.map(tracker.unwrap), tracker.unwrap(newTarget)));
19932
+ }
19933
+ });
19934
+ this.wrapped.set(object, proxy);
19935
+ this.originals.set(proxy, object);
19936
+ return proxy;
19937
+ }
19938
+ };
19939
+
19842
19940
  // src/runtime/commonjs-engine.ts
19843
19941
  init_path();
19844
19942
  var HELPERS = {
@@ -21576,6 +21674,7 @@ var VirtualHttpServer = class extends EventEmitter4__default.default {
21576
21674
  owner;
21577
21675
  listening = false;
21578
21676
  portValue = null;
21677
+ referenced = true;
21579
21678
  listen(...args) {
21580
21679
  const callback = typeof args.at(-1) === "function" ? args.pop() : void 0;
21581
21680
  const first = args[0];
@@ -21604,11 +21703,16 @@ var VirtualHttpServer = class extends EventEmitter4__default.default {
21604
21703
  return this.portValue === null ? null : { address: "0.0.0.0", family: "IPv4", port: this.portValue };
21605
21704
  }
21606
21705
  ref() {
21706
+ this.referenced = true;
21607
21707
  return this;
21608
21708
  }
21609
21709
  unref() {
21710
+ this.referenced = false;
21610
21711
  return this;
21611
21712
  }
21713
+ hasRef() {
21714
+ return this.referenced;
21715
+ }
21612
21716
  setTimeout(_milliseconds, callback) {
21613
21717
  if (callback) this.on("timeout", callback);
21614
21718
  return this;
@@ -21618,13 +21722,16 @@ var VirtualHttpRouter = class {
21618
21722
  servers = /* @__PURE__ */ new Map();
21619
21723
  /** Notified when a server begins listening, for `onServerReady`. */
21620
21724
  onListen;
21725
+ onClose;
21621
21726
  register(port, server, owner) {
21622
21727
  if (this.servers.has(port)) throw Object.assign(new Error(`listen EADDRINUSE: address already in use 0.0.0.0:${port}`), { code: "EADDRINUSE", port });
21623
21728
  this.servers.set(port, { server, owner });
21624
21729
  this.onListen?.(port);
21625
21730
  }
21626
21731
  unregister(port, server) {
21627
- if (this.servers.get(port)?.server === server) this.servers.delete(port);
21732
+ if (this.servers.get(port)?.server !== server) return;
21733
+ this.servers.delete(port);
21734
+ this.onClose?.(port);
21628
21735
  }
21629
21736
  /** Whether anything in this container is listening on `port`. */
21630
21737
  activePortsIncludes(port) {
@@ -21633,6 +21740,9 @@ var VirtualHttpRouter = class {
21633
21740
  activePorts(owner) {
21634
21741
  return [...this.servers].filter(([, item]) => owner === void 0 || item.owner === owner).map(([port]) => port).sort((a, b) => a - b);
21635
21742
  }
21743
+ referencedPorts(owner) {
21744
+ return [...this.servers].filter(([, item]) => item.owner === owner && item.server.hasRef()).map(([port]) => port);
21745
+ }
21636
21746
  closeOwner(owner) {
21637
21747
  for (const { server, owner: value } of [...this.servers.values()]) if (value === owner) server.close();
21638
21748
  }
@@ -22044,6 +22154,108 @@ function hashFor(algorithm) {
22044
22154
  function toBytes2(data, encoding) {
22045
22155
  return typeof data === "string" ? Buffer2.from(data, encoding) : data;
22046
22156
  }
22157
+
22158
+ // src/runtime/sync-channel.ts
22159
+ var STATE = 0;
22160
+ var LENGTH = 1;
22161
+ var MORE = 2;
22162
+ var CONTROL_WORDS = 4;
22163
+ var STATE_REQUEST = 1;
22164
+ var STATE_RESPONSE = 2;
22165
+ var STATE_CONTINUE = 3;
22166
+ var STATE_CLOSED = 4;
22167
+ function createSyncChannelBuffers(capacityBytes = 1 << 20) {
22168
+ return {
22169
+ control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
22170
+ data: new SharedArrayBuffer(capacityBytes)
22171
+ };
22172
+ }
22173
+ function syncChannelUnavailableReason() {
22174
+ if (typeof globalThis.crossOriginIsolated === "boolean" && !globalThis.crossOriginIsolated) {
22175
+ return "The browser host is not cross-origin isolated. Serve the host app over HTTPS or localhost with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. For a built app use sandboxedjs-serve <directory>. Embedded browsers or iframe policies may also prevent isolation.";
22176
+ }
22177
+ if (typeof SharedArrayBuffer !== "function") return "SharedArrayBuffer is unavailable in this host.";
22178
+ if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return "Atomics.wait is unavailable in this host.";
22179
+ return null;
22180
+ }
22181
+ function syncChannelSupported() {
22182
+ return syncChannelUnavailableReason() === null;
22183
+ }
22184
+ var SyncChannelServer = class {
22185
+ constructor(buffers, handle) {
22186
+ this.handle = handle;
22187
+ this.control = new Int32Array(buffers.control);
22188
+ this.data = new Uint8Array(buffers.data);
22189
+ this.capacity = this.data.length;
22190
+ }
22191
+ handle;
22192
+ control;
22193
+ data;
22194
+ capacity;
22195
+ /** Request chunks gathered so far, and the response still to be sent. */
22196
+ incoming = [];
22197
+ outgoing = null;
22198
+ sent = 0;
22199
+ closed = false;
22200
+ /** Call from the wake message the client sends after each chunk. */
22201
+ async pump() {
22202
+ if (this.closed) return;
22203
+ const state = Atomics.load(this.control, STATE);
22204
+ if (state === STATE_REQUEST) {
22205
+ const size = Atomics.load(this.control, LENGTH);
22206
+ this.incoming.push(this.data.slice(0, size));
22207
+ if (Atomics.load(this.control, MORE) === 1) {
22208
+ this.publish(STATE_CONTINUE);
22209
+ return;
22210
+ }
22211
+ const request = concat3(this.incoming);
22212
+ this.incoming = [];
22213
+ let response;
22214
+ try {
22215
+ response = await this.handle(request);
22216
+ } catch {
22217
+ response = new Uint8Array();
22218
+ }
22219
+ if (this.closed) return;
22220
+ this.outgoing = response;
22221
+ this.sent = 0;
22222
+ this.sendChunk();
22223
+ return;
22224
+ }
22225
+ if (state === STATE_CONTINUE) this.sendChunk();
22226
+ }
22227
+ /** Release a blocked client, e.g. when the container is torn down. */
22228
+ close() {
22229
+ if (this.closed) return;
22230
+ this.closed = true;
22231
+ Atomics.store(this.control, STATE, STATE_CLOSED);
22232
+ Atomics.notify(this.control, STATE);
22233
+ }
22234
+ sendChunk() {
22235
+ const payload = this.outgoing ?? new Uint8Array();
22236
+ const size = Math.min(this.capacity, payload.length - this.sent);
22237
+ this.data.set(payload.subarray(this.sent, this.sent + size), 0);
22238
+ this.sent += size;
22239
+ Atomics.store(this.control, LENGTH, size);
22240
+ Atomics.store(this.control, MORE, this.sent < payload.length ? 1 : 0);
22241
+ this.publish(STATE_RESPONSE);
22242
+ }
22243
+ publish(state) {
22244
+ Atomics.store(this.control, STATE, state);
22245
+ Atomics.notify(this.control, STATE);
22246
+ }
22247
+ };
22248
+ function concat3(parts) {
22249
+ if (parts.length === 1) return parts[0];
22250
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
22251
+ const joined = new Uint8Array(total);
22252
+ let at = 0;
22253
+ for (const part of parts) {
22254
+ joined.set(part, at);
22255
+ at += part.length;
22256
+ }
22257
+ return joined;
22258
+ }
22047
22259
  var ChildProcess = class extends EventEmitter4__default.default {
22048
22260
  stdout = new streamModule4__default.default.PassThrough();
22049
22261
  stderr = new streamModule4__default.default.PassThrough();
@@ -22116,7 +22328,8 @@ var ChildProcess = class extends EventEmitter4__default.default {
22116
22328
  queueMicrotask(() => this.emit("close", code, null));
22117
22329
  }
22118
22330
  };
22119
- function createChildProcessModule(spawnChild, defaultCwd, syncSpawn) {
22331
+ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn, defaultEnv = () => ({})) {
22332
+ const environmentFor = (options) => options.env ? { ...options.env } : defaultEnv();
22120
22333
  const throughShell = (command, options) => {
22121
22334
  const shell = typeof options.shell === "string" ? options.shell : "/bin/sh";
22122
22335
  return { file: shell, args: ["-c", command] };
@@ -22127,7 +22340,7 @@ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn) {
22127
22340
  command: resolved.file,
22128
22341
  args: resolved.args,
22129
22342
  cwd: options.cwd ?? defaultCwd(),
22130
- ...options.env ? { env: options.env } : {}
22343
+ env: environmentFor(options)
22131
22344
  });
22132
22345
  return new ChildProcess(handle, resolved.file, resolved.args);
22133
22346
  };
@@ -22173,11 +22386,11 @@ ${err.join("")}`),
22173
22386
  exec,
22174
22387
  execFile,
22175
22388
  fork: (modulePath, args = [], options = {}) => spawn("node", [modulePath, ...args], options),
22176
- ...buildSyncFamily(syncSpawn, throughShell, defaultCwd),
22389
+ ...buildSyncFamily(syncSpawn, throughShell, defaultCwd, environmentFor),
22177
22390
  ChildProcess
22178
22391
  };
22179
22392
  }
22180
- function buildSyncFamily(syncSpawn, throughShell, defaultCwd) {
22393
+ function buildSyncFamily(syncSpawn, throughShell, defaultCwd, environmentFor) {
22181
22394
  if (!syncSpawn) {
22182
22395
  return {
22183
22396
  execSync: unavailable("execSync"),
@@ -22188,12 +22401,14 @@ function buildSyncFamily(syncSpawn, throughShell, defaultCwd) {
22188
22401
  const run = (file3, args, options) => {
22189
22402
  const resolved = options.shell ? throughShell([file3, ...args].join(" "), options) : { file: file3, args };
22190
22403
  const input = options.input === void 0 ? void 0 : typeof options.input === "string" ? options.input : new TextDecoder().decode(options.input);
22404
+ const inherit = options.stdio === "inherit" || Array.isArray(options.stdio) && options.stdio[0] === "inherit";
22191
22405
  return syncSpawn({
22192
22406
  command: resolved.file,
22193
22407
  args: resolved.args,
22194
22408
  cwd: options.cwd ?? defaultCwd(),
22195
- ...options.env ? { env: options.env } : {},
22196
- ...input === void 0 ? {} : { input }
22409
+ env: environmentFor(options),
22410
+ ...input === void 0 ? {} : { input },
22411
+ ...inherit ? { inheritStdio: true } : {}
22197
22412
  });
22198
22413
  };
22199
22414
  const asOutput = (text2, options) => options.encoding === "buffer" || options.encoding === void 0 ? Buffer2.from(text2) : text2;
@@ -22245,7 +22460,7 @@ function unavailable(name) {
22245
22460
  const list = Array.isArray(args[1]) ? args[1].map(String) : [];
22246
22461
  const command = file3 ? [file3, ...list].join(" ") : void 0;
22247
22462
  const error = new Error(
22248
- `child_process.${name} is not supported in the sandboxed runtime` + (command ? ` (tried to run: ${command})` : "") + `: the caller, the child and the event loop share one thread, so blocking the caller would also stop the child. Run it with the asynchronous form (spawn/exec/execFile), or run the command from the container shell.`
22463
+ `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. Enable the SandboxedJs worker runtime with createContainer({ isolation: "worker" }) and fix any boot prerequisite it reports. ` + (syncChannelUnavailableReason() ?? "This process is using the realm runtime, which cannot provide synchronous child processes.")
22249
22464
  );
22250
22465
  error.code = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
22251
22466
  throw error;
@@ -22533,6 +22748,11 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
22533
22748
  edit(sequence, key) {
22534
22749
  if (this.closed) return;
22535
22750
  const name = key.name;
22751
+ if (key.ctrl && name === "c") {
22752
+ if (this.listenerCount("SIGINT") > 0) this.emit("SIGINT");
22753
+ else this.close();
22754
+ return;
22755
+ }
22536
22756
  if (name === "return" || name === "enter") {
22537
22757
  const value = this.line;
22538
22758
  this.line = "";
@@ -22628,7 +22848,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
22628
22848
  };
22629
22849
  const createInterface = (options, output) => {
22630
22850
  if (options && typeof options === "object" && !options.on) {
22631
- const resolvedOutput2 = options.output ?? defaultOutput();
22851
+ const resolvedOutput2 = options.output;
22632
22852
  const instance = new Interface(
22633
22853
  options.input ?? defaultInput(),
22634
22854
  resolvedOutput2,
@@ -22637,7 +22857,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
22637
22857
  if (typeof options.prompt === "string") instance.setPrompt(options.prompt);
22638
22858
  return instance;
22639
22859
  }
22640
- const resolvedOutput = output ?? defaultOutput();
22860
+ const resolvedOutput = output;
22641
22861
  return new Interface(options ?? defaultInput(), resolvedOutput, isTerminal(void 0, resolvedOutput));
22642
22862
  };
22643
22863
  const noop = () => {
@@ -22873,7 +23093,7 @@ function createCoreModules(options) {
22873
23093
  };
22874
23094
  const http = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "http:") : createUnsupportedModule("http");
22875
23095
  const https = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "https:") : createUnsupportedModule("https");
22876
- const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd, options.syncSpawn) : createUnsupportedModule("child_process");
23096
+ const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd, options.syncSpawn, () => ({ ...processObject.env })) : createUnsupportedModule("child_process");
22877
23097
  const readline = createReadlineModule(() => processObject.stdin, () => processObject.stdout);
22878
23098
  const dns = createDnsModule();
22879
23099
  const builtins = {
@@ -22906,7 +23126,7 @@ function createCoreModules(options) {
22906
23126
  "stream/promises": createStreamPromises(),
22907
23127
  string_decoder: stringDecoderModule__default.default,
22908
23128
  timers: { ...timersModule__default.default, ...timers.api },
22909
- "timers/promises": createTimerPromises(),
23129
+ "timers/promises": createTimerPromises(timers.api),
22910
23130
  tty: { isatty: () => false, ReadStream: streamModule4__default.default.Readable, WriteStream: streamModule4__default.default.Writable },
22911
23131
  url: url_module_default,
22912
23132
  util: util_module_default,
@@ -23651,10 +23871,10 @@ function createTrackedTimers() {
23651
23871
  cancelAll
23652
23872
  };
23653
23873
  }
23654
- function createTimerPromises() {
23874
+ function createTimerPromises(timers) {
23655
23875
  return {
23656
- setTimeout: (delay, value) => new Promise((resolve2) => setTimeout(resolve2, delay, value)),
23657
- setImmediate: (value) => new Promise((resolve2) => setTimeout(resolve2, 0, value))
23876
+ setTimeout: (delay, value) => new Promise((resolve2) => timers.setTimeout(resolve2, delay, value)),
23877
+ setImmediate: (value) => new Promise((resolve2) => timers.setImmediate(resolve2, value))
23658
23878
  };
23659
23879
  }
23660
23880
  function createAsyncHooksModule() {
@@ -24118,8 +24338,10 @@ var MirroringVolume = class {
24118
24338
  * container pays nothing for this.
24119
24339
  */
24120
24340
  attach(mirror, root) {
24341
+ const cleanRoot = clean(root);
24342
+ if (this.mirror === mirror && this.root === cleanRoot) return;
24121
24343
  this.mirror = mirror;
24122
- this.root = clean(root);
24344
+ this.root = cleanRoot;
24123
24345
  this.seed();
24124
24346
  }
24125
24347
  detach() {
@@ -24360,11 +24582,8 @@ function ensureProcessGlobal() {
24360
24582
  // src/runtime/host-rolldown.ts
24361
24583
  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'] }";
24362
24584
  async function loadHostRolldownBinding() {
24363
- if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) {
24364
- throw new Error(
24365
- "Vite 8/Rolldown requires cross-origin isolation. Serve the app with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp headers."
24366
- );
24367
- }
24585
+ const reason = syncChannelUnavailableReason();
24586
+ if (reason) throw new Error(`SandboxedJs threaded WASM unavailable: ${reason}`);
24368
24587
  try {
24369
24588
  const loaded = await import('@rolldown/binding-wasm32-wasi');
24370
24589
  return "__fs" in loaded ? { ...loaded } : loaded.default ?? loaded;
@@ -24372,9 +24591,9 @@ async function loadHostRolldownBinding() {
24372
24591
  if (typeof process !== "undefined" && process.env?.SANDBOXEDJS_DEBUG) {
24373
24592
  console.error("[sandboxedjs] Rolldown WASI binding unavailable:", error);
24374
24593
  }
24375
- const reason = error instanceof Error ? error.message : String(error);
24594
+ const reason2 = error instanceof Error ? error.message : String(error);
24376
24595
  throw new Error(
24377
- `The optional Rolldown WASI binding could not be loaded: ${reason}` + (typeof window === "undefined" ? "" : `
24596
+ `The optional Rolldown WASI binding could not be loaded: ${reason2}` + (typeof window === "undefined" ? "" : `
24378
24597
 
24379
24598
  ${BUNDLER_HINT}`),
24380
24599
  { cause: error }
@@ -24485,6 +24704,8 @@ var LocalProcess = class extends EventEmitter4__default.default {
24485
24704
  }
24486
24705
  /** End the process now, for an asynchronous `process.exit`. */
24487
24706
  exitNow(code) {
24707
+ this.killed = true;
24708
+ this.cleanup?.();
24488
24709
  this.finish(code);
24489
24710
  this.resolveKilled();
24490
24711
  }
@@ -24668,6 +24889,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
24668
24889
  )
24669
24890
  };
24670
24891
  disposed = false;
24892
+ running = /* @__PURE__ */ new Set();
24671
24893
  workdir;
24672
24894
  env;
24673
24895
  aliases;
@@ -24709,8 +24931,9 @@ var LocalRuntimePod = class _LocalRuntimePod {
24709
24931
  const cwd = typeof options.cwd === "string" ? options.cwd : this.workdir;
24710
24932
  const env2 = { ...this.env, ...isRecord(options.env) ? options.env : {} };
24711
24933
  const owner = `${this.instanceId}:${Math.random().toString(36).slice(2)}`;
24712
- return new LocalProcess(async (proc) => {
24934
+ const process2 = new LocalProcess(async (proc) => {
24713
24935
  await this.prepareRolldown(cwd, env2);
24936
+ if (proc.isKilled()) return 137;
24714
24937
  const untrack = trackProcess(proc);
24715
24938
  let requestedExit = 0;
24716
24939
  let engine;
@@ -24738,7 +24961,9 @@ var LocalRuntimePod = class _LocalRuntimePod {
24738
24961
  onRawMode: (enabled) => proc.emit("rawmode", enabled)
24739
24962
  });
24740
24963
  proc.acceptInput((data) => core.writeStdin(data), () => core.endStdin());
24964
+ const hostWork = new HostModuleTracker();
24741
24965
  proc.onKill(() => {
24966
+ hostWork.dispose();
24742
24967
  core.cancelTimers();
24743
24968
  this.router.closeOwner(owner);
24744
24969
  });
@@ -24747,21 +24972,22 @@ var LocalRuntimePod = class _LocalRuntimePod {
24747
24972
  builtins: core.builtins,
24748
24973
  globals: core.globals,
24749
24974
  aliases: this.aliases,
24750
- overrides: this.modules
24975
+ overrides: Object.fromEntries(Object.entries(this.modules).map(([key, value]) => [key, hostWork.wrap(value)]))
24751
24976
  });
24752
24977
  try {
24753
- await engine.run(script);
24754
- await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin, core.pendingRequests, () => proc.isKilled());
24755
- if (this.router.activePorts(owner).length) {
24756
- await proc.waitForKill();
24757
- return 137;
24758
- }
24978
+ await Promise.race([engine.run(script), proc.waitForKill()]);
24979
+ await this.settle(owner, core.pendingHandles, core.readingStdin, () => core.pendingRequests() + hostWork.pending(), () => proc.isKilled());
24759
24980
  return requestedExit;
24760
24981
  } finally {
24982
+ hostWork.dispose();
24983
+ core.cancelTimers();
24761
24984
  untrack();
24762
24985
  this.router.closeOwner(owner);
24763
24986
  }
24764
24987
  });
24988
+ this.running.add(process2);
24989
+ void process2.completion.then(() => this.running.delete(process2));
24990
+ return process2;
24765
24991
  }
24766
24992
  /**
24767
24993
  * Rolldown's JavaScript API synchronously requires its compiled binding.
@@ -24797,19 +25023,13 @@ var LocalRuntimePod = class _LocalRuntimePod {
24797
25023
  * A plain script that has genuinely finished falls straight through both,
24798
25024
  * costing a handful of empty turns.
24799
25025
  */
24800
- async settle(owner, pendingHandles, pendingUnrefed, readingStdin, pendingRequests, killed) {
24801
- for (let turn = 0; turn < DRAIN_TURNS; turn++) {
24802
- if (this.router.activePorts(owner).length || killed()) return;
24803
- await new Promise((resolve2) => setTimeout(resolve2, 0));
24804
- }
24805
- if (pendingHandles() === 0 && pendingRequests() === 0 && pendingUnrefed() > 0 && !readingStdin()) {
24806
- const deadline = Date.now() + 1e3;
24807
- while (!this.router.activePorts(owner).length && !killed() && Date.now() < deadline) {
24808
- await new Promise((resolve2) => setTimeout(resolve2, 5));
24809
- }
24810
- }
24811
- while (!this.router.activePorts(owner).length && !killed() && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
24812
- await new Promise((resolve2) => setTimeout(resolve2, 5));
25026
+ async settle(owner, pendingHandles, readingStdin, pendingRequests, killed) {
25027
+ let idleTurns = 0;
25028
+ while (!killed()) {
25029
+ const busy = this.router.referencedPorts(owner).length > 0 || pendingHandles() > 0 || pendingRequests() > 0 || readingStdin();
25030
+ idleTurns = busy ? 0 : idleTurns + 1;
25031
+ if (idleTurns >= DRAIN_TURNS) return;
25032
+ await new Promise((resolve2) => setTimeout(resolve2, busy ? 5 : 0));
24813
25033
  }
24814
25034
  }
24815
25035
  async request(_port, _init = {}) {
@@ -24826,6 +25046,8 @@ var LocalRuntimePod = class _LocalRuntimePod {
24826
25046
  }
24827
25047
  teardown() {
24828
25048
  this.disposed = true;
25049
+ for (const process2 of this.running) process2.kill();
25050
+ this.running.clear();
24829
25051
  this.router.closeAll();
24830
25052
  this.esbuild?.stop();
24831
25053
  }
@@ -24895,103 +25117,6 @@ function attachRolldownMirror(binding, volume, root) {
24895
25117
  volume.attach(fs, root);
24896
25118
  }
24897
25119
 
24898
- // src/runtime/sync-channel.ts
24899
- var STATE = 0;
24900
- var LENGTH = 1;
24901
- var MORE = 2;
24902
- var CONTROL_WORDS = 4;
24903
- var STATE_REQUEST = 1;
24904
- var STATE_RESPONSE = 2;
24905
- var STATE_CONTINUE = 3;
24906
- var STATE_CLOSED = 4;
24907
- function createSyncChannelBuffers(capacityBytes = 1 << 20) {
24908
- return {
24909
- control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
24910
- data: new SharedArrayBuffer(capacityBytes)
24911
- };
24912
- }
24913
- function syncChannelSupported() {
24914
- if (typeof SharedArrayBuffer !== "function") return false;
24915
- if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return false;
24916
- if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) return false;
24917
- return true;
24918
- }
24919
- var SyncChannelServer = class {
24920
- constructor(buffers, handle) {
24921
- this.handle = handle;
24922
- this.control = new Int32Array(buffers.control);
24923
- this.data = new Uint8Array(buffers.data);
24924
- this.capacity = this.data.length;
24925
- }
24926
- handle;
24927
- control;
24928
- data;
24929
- capacity;
24930
- /** Request chunks gathered so far, and the response still to be sent. */
24931
- incoming = [];
24932
- outgoing = null;
24933
- sent = 0;
24934
- closed = false;
24935
- /** Call from the wake message the client sends after each chunk. */
24936
- async pump() {
24937
- if (this.closed) return;
24938
- const state = Atomics.load(this.control, STATE);
24939
- if (state === STATE_REQUEST) {
24940
- const size = Atomics.load(this.control, LENGTH);
24941
- this.incoming.push(this.data.slice(0, size));
24942
- if (Atomics.load(this.control, MORE) === 1) {
24943
- this.publish(STATE_CONTINUE);
24944
- return;
24945
- }
24946
- const request = concat3(this.incoming);
24947
- this.incoming = [];
24948
- let response;
24949
- try {
24950
- response = await this.handle(request);
24951
- } catch {
24952
- response = new Uint8Array();
24953
- }
24954
- if (this.closed) return;
24955
- this.outgoing = response;
24956
- this.sent = 0;
24957
- this.sendChunk();
24958
- return;
24959
- }
24960
- if (state === STATE_CONTINUE) this.sendChunk();
24961
- }
24962
- /** Release a blocked client, e.g. when the container is torn down. */
24963
- close() {
24964
- if (this.closed) return;
24965
- this.closed = true;
24966
- Atomics.store(this.control, STATE, STATE_CLOSED);
24967
- Atomics.notify(this.control, STATE);
24968
- }
24969
- sendChunk() {
24970
- const payload = this.outgoing ?? new Uint8Array();
24971
- const size = Math.min(this.capacity, payload.length - this.sent);
24972
- this.data.set(payload.subarray(this.sent, this.sent + size), 0);
24973
- this.sent += size;
24974
- Atomics.store(this.control, LENGTH, size);
24975
- Atomics.store(this.control, MORE, this.sent < payload.length ? 1 : 0);
24976
- this.publish(STATE_RESPONSE);
24977
- }
24978
- publish(state) {
24979
- Atomics.store(this.control, STATE, state);
24980
- Atomics.notify(this.control, STATE);
24981
- }
24982
- };
24983
- function concat3(parts) {
24984
- if (parts.length === 1) return parts[0];
24985
- const total = parts.reduce((sum, part) => sum + part.length, 0);
24986
- const joined = new Uint8Array(total);
24987
- let at = 0;
24988
- for (const part of parts) {
24989
- joined.set(part, at);
24990
- at += part.length;
24991
- }
24992
- return joined;
24993
- }
24994
-
24995
25120
  // src/runtime/remote-volume.ts
24996
25121
  var encoder7 = new TextEncoder();
24997
25122
  var decoder7 = new TextDecoder();
@@ -25086,24 +25211,32 @@ function defaultWorkerUrl() {
25086
25211
  return new URL("./worker-entry.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
25087
25212
  }
25088
25213
  function hasDomWorker() {
25089
- return typeof Worker === "function" && typeof document !== "undefined";
25214
+ return typeof Worker === "function";
25090
25215
  }
25091
25216
  async function startRuntimeWorker(options = {}) {
25092
25217
  const url = options.url ?? defaultWorkerUrl();
25093
25218
  const worker = hasDomWorker() ? await startDomWorker(url) : await startNodeWorker(url, options.workerData ?? {});
25094
- await new Promise((resolve2, reject) => {
25095
- const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
25096
- worker.onMessage((message) => {
25097
- if (message?.type === "sandboxedjs:ready") {
25219
+ try {
25220
+ await new Promise((resolve2, reject) => {
25221
+ const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
25222
+ worker.onMessage((message) => {
25223
+ if (message?.type === "sandboxedjs:ready") {
25224
+ clearTimeout(timer);
25225
+ resolve2();
25226
+ }
25227
+ });
25228
+ worker.onError((error) => {
25098
25229
  clearTimeout(timer);
25099
- resolve2();
25100
- }
25101
- });
25102
- worker.onError((error) => {
25103
- clearTimeout(timer);
25104
- reject(error instanceof Error ? error : new Error(String(error)));
25230
+ reject(error instanceof Error ? error : new Error(String(error)));
25231
+ });
25105
25232
  });
25106
- });
25233
+ } catch (error) {
25234
+ try {
25235
+ await worker.terminate();
25236
+ } catch {
25237
+ }
25238
+ throw error;
25239
+ }
25107
25240
  return worker;
25108
25241
  }
25109
25242
  async function startDomWorker(url) {
@@ -25164,6 +25297,8 @@ var WorkerProcess = class extends EventEmitter4__default.default {
25164
25297
  started = false;
25165
25298
  pendingInput = [];
25166
25299
  inputEnded = false;
25300
+ inheritedInput;
25301
+ ownRawMode = false;
25167
25302
  on(event, listener) {
25168
25303
  return super.on(event, listener);
25169
25304
  }
@@ -25189,13 +25324,38 @@ var WorkerProcess = class extends EventEmitter4__default.default {
25189
25324
  * program is still loading, and dropping them loses the answer to a prompt.
25190
25325
  */
25191
25326
  write(data) {
25327
+ if (this.inheritedInput) {
25328
+ this.inheritedInput.sendStdin(data);
25329
+ return;
25330
+ }
25192
25331
  if (this.started) this.worker.postMessage({ type: "stdin", data });
25193
25332
  else this.pendingInput.push(data);
25194
25333
  }
25195
25334
  endInput() {
25196
25335
  this.inputEnded = true;
25336
+ if (this.inheritedInput) {
25337
+ this.inheritedInput.endStdin?.();
25338
+ return;
25339
+ }
25197
25340
  if (this.started) this.worker.postMessage({ type: "stdin-end" });
25198
25341
  }
25342
+ rawMode(enabled) {
25343
+ this.ownRawMode = enabled;
25344
+ if (!this.inheritedInput) this.emit("rawmode", enabled);
25345
+ }
25346
+ /** The parent is blocked in Atomics.wait, so inherited input must bypass it. */
25347
+ inheritInput(child) {
25348
+ this.inheritedInput = child;
25349
+ this.emit("rawmode", false);
25350
+ child.on("rawmode", (enabled) => {
25351
+ if (this.inheritedInput === child) this.emit("rawmode", Boolean(enabled));
25352
+ });
25353
+ return () => {
25354
+ if (this.inheritedInput !== child) return;
25355
+ this.inheritedInput = void 0;
25356
+ this.emit("rawmode", this.ownRawMode);
25357
+ };
25358
+ }
25199
25359
  kill(_signal = "SIGTERM") {
25200
25360
  this.worker.postMessage({ type: "kill" });
25201
25361
  this.finish(137);
@@ -25216,25 +25376,30 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25216
25376
  super(options);
25217
25377
  this.workerUrl = options.workerUrl;
25218
25378
  }
25219
- /**
25220
- * Boot a Worker-backed pod, or return null when this host cannot support one.
25221
- *
25222
- * Declining is a first-class outcome. `SharedArrayBuffer` needs cross-origin
25223
- * isolation, a bundler may have made the guest script unreachable, and a host
25224
- * that supplied its own module objects has handed over things no thread
25225
- * boundary can carry. In every case the caller falls back to the in-realm pod
25226
- * and keeps working.
25227
- */
25228
- static async tryBoot(options = {}) {
25229
- if (!syncChannelSupported()) return null;
25230
- if (options.modules && Object.keys(options.modules).length > 0) return null;
25379
+ /** Boot without silently dropping synchronous child-process support. */
25380
+ static async boot(options = {}) {
25381
+ const reason = syncChannelUnavailableReason();
25382
+ if (reason) throw new Error(`SandboxedJs worker runtime unavailable: ${reason}`);
25383
+ if (options.modules && Object.keys(options.modules).length > 0) {
25384
+ throw new Error("SandboxedJs worker runtime cannot clone host-supplied modules. Use isolation: 'realm' explicitly.");
25385
+ }
25231
25386
  const pod = new _WorkerRuntimePod(options);
25232
25387
  try {
25233
25388
  const probe = await startRuntimeWorker({ ...options.workerUrl ? { url: options.workerUrl } : {}, timeoutMs: 1e4 });
25234
25389
  await probe.terminate();
25235
25390
  return pod;
25236
- } catch {
25391
+ } catch (cause) {
25237
25392
  pod.teardown();
25393
+ const detail = cause instanceof Error ? cause.message : String(cause);
25394
+ throw new Error(`SandboxedJs guest worker failed to start: ${detail}. Check that worker-entry.js is served as JavaScript; set workerUrl if your bundler moved it.`, { cause });
25395
+ }
25396
+ }
25397
+ /** Compatibility mode, with an observable explanation for every fallback. */
25398
+ static async tryBoot(options = {}, onFallback = (error) => console.warn(`[sandboxedjs] Falling back to the realm runtime; synchronous child processes are unavailable. ${error.message}`)) {
25399
+ try {
25400
+ return await _WorkerRuntimePod.boot(options);
25401
+ } catch (error) {
25402
+ onFallback(error instanceof Error ? error : new Error(String(error)));
25238
25403
  return null;
25239
25404
  }
25240
25405
  }
@@ -25254,18 +25419,23 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25254
25419
  ...this.workerUrl ? { url: this.workerUrl } : {},
25255
25420
  timeoutMs: 15e3
25256
25421
  });
25422
+ const streams = { target: null };
25257
25423
  const server = new SyncChannelServer(buffers, serveSyncSyscalls({
25258
25424
  volume: this.volume,
25259
- spawnChild: (request) => this.runChildToCompletion(request)
25425
+ spawnChild: (request) => this.runChildToCompletion(request, streams.target, ownedChildren)
25260
25426
  }));
25261
25427
  const entry = { worker, server };
25262
25428
  this.live.add(entry);
25429
+ const ownedChildren = /* @__PURE__ */ new Set();
25263
25430
  const process2 = new WorkerProcess(worker, () => {
25431
+ for (const child of ownedChildren) child.kill("SIGTERM");
25432
+ ownedChildren.clear();
25264
25433
  this.live.delete(entry);
25265
25434
  server.close();
25266
25435
  this.closeProxies(owner);
25267
25436
  void worker.terminate();
25268
25437
  });
25438
+ streams.target = process2;
25269
25439
  const children = /* @__PURE__ */ new Map();
25270
25440
  worker.onMessage((raw) => {
25271
25441
  const message = raw;
@@ -25280,7 +25450,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25280
25450
  process2.error(String(message.text));
25281
25451
  return;
25282
25452
  case "rawmode":
25283
- process2.emit("rawmode", Boolean(message.enabled));
25453
+ process2.rawMode(Boolean(message.enabled));
25284
25454
  return;
25285
25455
  case "exit":
25286
25456
  process2.finish(Number(message.code));
@@ -25288,11 +25458,20 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25288
25458
  case "listen":
25289
25459
  this.proxyPort(Number(message.port), owner, worker);
25290
25460
  return;
25461
+ case "close-port": {
25462
+ const port = Number(message.port);
25463
+ const proxy = this.proxies.get(port);
25464
+ if (proxy?.owner === owner) {
25465
+ proxy.close();
25466
+ this.proxies.delete(port);
25467
+ }
25468
+ return;
25469
+ }
25291
25470
  case "http-response":
25292
25471
  this.settleProxied(Number(message.id), message.response);
25293
25472
  return;
25294
25473
  case "child-start":
25295
- this.startChild(worker, children, message);
25474
+ this.startChild(worker, children, message, ownedChildren);
25296
25475
  return;
25297
25476
  case "child-stdin":
25298
25477
  children.get(message.id)?.sendStdin?.(String(message.data));
@@ -25323,16 +25502,21 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25323
25502
  return process2;
25324
25503
  }
25325
25504
  /** Start an asynchronous child on the host's behalf and relay its events. */
25326
- startChild(worker, children, message) {
25505
+ startChild(worker, children, message, owned) {
25327
25506
  const handle = this.processManager.spawn(message.config);
25328
25507
  children.set(message.id, handle);
25508
+ owned.add(handle);
25509
+ handle.on("exit", () => {
25510
+ owned.delete(handle);
25511
+ children.delete(message.id);
25512
+ });
25329
25513
  for (const event of ["stdout", "stderr", "exit"]) {
25330
25514
  handle.on(event, (value) => worker.postMessage({ type: "child-event", id: message.id, event, value }));
25331
25515
  }
25332
25516
  handle.exec();
25333
25517
  }
25334
25518
  /** Run a child to completion and collect it, for the guest's `spawnSync`. */
25335
- runChildToCompletion(request) {
25519
+ runChildToCompletion(request, streamTo, owned) {
25336
25520
  return new Promise((resolve2) => {
25337
25521
  let handle;
25338
25522
  try {
@@ -25340,27 +25524,35 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25340
25524
  command: request.command,
25341
25525
  args: request.args,
25342
25526
  cwd: request.cwd,
25343
- ...request.env ? { env: request.env } : {}
25527
+ ...request.env ? { env: request.env } : {},
25528
+ ...request.inheritStdio ? { inheritStdio: true } : {}
25344
25529
  });
25345
25530
  } catch (error) {
25346
25531
  const failure = error;
25347
25532
  resolve2({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure.code ? { code: failure.code } : {}, message: failure.message } });
25348
25533
  return;
25349
25534
  }
25535
+ owned.add(handle);
25536
+ handle.on("exit", () => owned.delete(handle));
25350
25537
  let stdout = "";
25351
25538
  let stderr = "";
25539
+ const live = request.inheritStdio ? streamTo : null;
25540
+ const restoreInput = live?.inheritInput(handle);
25541
+ if (restoreInput) handle.on("exit", restoreInput);
25352
25542
  handle.on("stdout", (text2) => {
25353
25543
  stdout += text2;
25544
+ live?.output(text2);
25354
25545
  });
25355
25546
  handle.on("stderr", (text2) => {
25356
25547
  stderr += text2;
25548
+ live?.error(text2);
25357
25549
  });
25358
25550
  handle.on("exit", (code) => resolve2({ status: code, stdout, stderr, signal: null }));
25359
25551
  handle.exec();
25360
25552
  if (request.input !== void 0) {
25361
25553
  handle.sendStdin?.(request.input);
25362
25554
  }
25363
- handle.endStdin?.();
25555
+ if (!request.inheritStdio) handle.endStdin?.();
25364
25556
  });
25365
25557
  }
25366
25558
  // ── HTTP servers living on another thread ─────────────────────────────────
@@ -25500,7 +25692,7 @@ var Container = class _Container {
25500
25692
  ...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
25501
25693
  };
25502
25694
  const workerOptions = { ...podOptions, ...opts.workerUrl ? { workerUrl: opts.workerUrl } : {} };
25503
- const pod = opts.pod ?? (opts.isolation === "realm" ? null : await WorkerRuntimePod.tryBoot(workerOptions)) ?? await LocalRuntimePod.boot(podOptions);
25695
+ const pod = opts.pod ?? (opts.isolation === "realm" ? null : opts.isolation === "worker" ? await WorkerRuntimePod.boot(workerOptions) : await WorkerRuntimePod.tryBoot(workerOptions, opts.onRuntimeFallback)) ?? await LocalRuntimePod.boot(podOptions);
25504
25696
  const kernel = new Kernel({
25505
25697
  pod,
25506
25698
  hostname: opts.hostname ?? "sandbox",
@@ -26008,7 +26200,7 @@ var Terminal = class {
26008
26200
  if (this.running) {
26009
26201
  const raw = this.currentStdin?.rawMode === true;
26010
26202
  if (ch === CTRL_C && !raw) {
26011
- this.session.proc.deliver("SIGINT");
26203
+ if (this.currentStdin) this.session.shell.interruptForeground("SIGINT", this.currentStdin);
26012
26204
  this.write("^C\r\n");
26013
26205
  return;
26014
26206
  }
@@ -26361,6 +26553,97 @@ init_variables();
26361
26553
  init_arith();
26362
26554
  init_expand();
26363
26555
  init_builtins();
26556
+
26557
+ // src/preview/register.ts
26558
+ function serveContainerOn(port, box) {
26559
+ port.onmessage = async (event) => {
26560
+ const request = event.data;
26561
+ try {
26562
+ const response = await box.request(request.port, {
26563
+ method: request.method,
26564
+ path: request.path,
26565
+ headers: request.headers,
26566
+ ...request.body ? { body: new Uint8Array(request.body) } : {}
26567
+ });
26568
+ const bytes2 = response.bytes.slice();
26569
+ port.postMessage(
26570
+ {
26571
+ id: request.id,
26572
+ response: {
26573
+ status: response.status,
26574
+ statusText: response.statusText,
26575
+ headers: response.headers,
26576
+ body: bytes2.buffer
26577
+ }
26578
+ },
26579
+ [bytes2.buffer]
26580
+ );
26581
+ } catch (error) {
26582
+ const message = new TextEncoder().encode(error instanceof Error ? error.message : String(error));
26583
+ port.postMessage({
26584
+ id: request.id,
26585
+ response: { status: 502, statusText: "Bad Gateway", headers: {}, body: message.buffer }
26586
+ });
26587
+ }
26588
+ };
26589
+ port.start?.();
26590
+ }
26591
+ async function createPreview(box, options = {}) {
26592
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return null;
26593
+ const scriptUrl = options.scriptUrl ?? new URL("./service-worker.js?no-inline", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
26594
+ let registration;
26595
+ try {
26596
+ registration = await navigator.serviceWorker.register(scriptUrl, {
26597
+ type: "module",
26598
+ ...options.scope ? { scope: options.scope } : {}
26599
+ });
26600
+ } catch {
26601
+ return null;
26602
+ }
26603
+ const worker = registration.active ?? registration.waiting ?? registration.installing;
26604
+ if (!worker) return null;
26605
+ if (worker.state !== "activated") {
26606
+ const activated = await new Promise((resolve2) => {
26607
+ const check = () => {
26608
+ if (worker.state === "activated") {
26609
+ worker.removeEventListener("statechange", check);
26610
+ resolve2(true);
26611
+ } else if (worker.state === "redundant") {
26612
+ worker.removeEventListener("statechange", check);
26613
+ resolve2(false);
26614
+ }
26615
+ };
26616
+ worker.addEventListener("statechange", check);
26617
+ setTimeout(() => resolve2(worker.state === "activated"), 1e4);
26618
+ check();
26619
+ });
26620
+ if (!activated) return null;
26621
+ }
26622
+ const channel = new MessageChannel();
26623
+ serveContainerOn(channel.port1, box);
26624
+ worker.postMessage({ type: "sandboxedjs:connect" }, [channel.port2]);
26625
+ const base2 = registration.scope.replace(/\/$/, "");
26626
+ return {
26627
+ urlFor: (port) => `${base2}/__sbx__/${port}/`,
26628
+ dispose: async () => {
26629
+ channel.port1.close();
26630
+ await registration.unregister();
26631
+ }
26632
+ };
26633
+ }
26634
+ async function renderInto(box, element, options = { port: 80 }) {
26635
+ const response = await box.request(options.port, { path: options.path ?? "/" });
26636
+ const frame = document.createElement("iframe");
26637
+ frame.setAttribute("sandbox", "allow-scripts");
26638
+ frame.style.width = "100%";
26639
+ frame.style.height = "100%";
26640
+ frame.style.border = "0";
26641
+ frame.srcdoc = response.body;
26642
+ element.replaceChildren(frame);
26643
+ return frame;
26644
+ }
26645
+
26646
+ // src/index.ts
26364
26647
  var src_default = createContainer;
26365
26648
 
26366
26649
  exports.BufferSink = BufferSink;
@@ -26404,6 +26687,7 @@ exports.VirtualHttpServer = VirtualHttpServer;
26404
26687
  exports.VirtualIncomingMessage = VirtualIncomingMessage;
26405
26688
  exports.VirtualServerResponse = VirtualServerResponse;
26406
26689
  exports.WASM_ALIASES = WASM_ALIASES;
26690
+ exports.WorkerRuntimePod = WorkerRuntimePod;
26407
26691
  exports.allCommands = allCommands;
26408
26692
  exports.applyChmod = applyChmod;
26409
26693
  exports.braceExpand = braceExpand;
@@ -26416,6 +26700,7 @@ exports.createChildProcessModule = createChildProcessModule;
26416
26700
  exports.createContainer = createContainer;
26417
26701
  exports.createContext = createContext;
26418
26702
  exports.createCoreModules = createCoreModules;
26703
+ exports.createPreview = createPreview;
26419
26704
  exports.default = src_default;
26420
26705
  exports.defineCommand = defineCommand;
26421
26706
  exports.evalArith = evalArith;
@@ -26442,6 +26727,7 @@ exports.octalMode = octalMode;
26442
26727
  exports.parseShell = parse;
26443
26728
  exports.parseUmask = parseUmask;
26444
26729
  exports.posixPath = path_exports;
26730
+ exports.renderInto = renderInto;
26445
26731
  exports.resetPidCounter = resetPidCounter;
26446
26732
  exports.shellQuote = shellQuote;
26447
26733
  exports.startRuntimeWorker = startRuntimeWorker;