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.js CHANGED
@@ -4778,7 +4778,15 @@ var Pipe = class _Pipe {
4778
4778
  * whole line on every keystroke — so the terminal must stop echoing, or
4779
4779
  * every character appears twice.
4780
4780
  */
4781
- rawMode = false;
4781
+ raw = false;
4782
+ onRawMode;
4783
+ get rawMode() {
4784
+ return this.raw;
4785
+ }
4786
+ set rawMode(enabled) {
4787
+ this.raw = enabled;
4788
+ this.onRawMode?.(enabled);
4789
+ }
4782
4790
  columns;
4783
4791
  rows;
4784
4792
  get closed() {
@@ -5906,13 +5914,19 @@ var Kernel = class {
5906
5914
  stderr: opts.stderr ?? new NullOutput()
5907
5915
  }
5908
5916
  });
5917
+ const cancelWithParent = () => {
5918
+ if (parent?.termSignal) proc.deliver(parent.termSignal);
5919
+ };
5920
+ parent?.signal.addEventListener("abort", cancelWithParent, { once: true });
5921
+ if (parent?.signal.aborted) cancelWithParent();
5922
+ void proc.wait().then(() => parent?.signal.removeEventListener("abort", cancelWithParent));
5909
5923
  let timer;
5910
5924
  if (opts.timeoutMs !== void 0) {
5911
5925
  timer = setTimeout(() => {
5912
5926
  proc.deliver("SIGKILL");
5913
5927
  }, opts.timeoutMs);
5914
5928
  }
5915
- void this.dispatch(proc).then((code) => {
5929
+ void (proc.signal.aborted ? Promise.resolve(proc.exitCode ?? 137) : this.dispatch(proc)).then((code) => {
5916
5930
  proc.exit(code);
5917
5931
  }).catch((e) => {
5918
5932
  try {
@@ -7904,6 +7918,22 @@ var Shell = class _Shell {
7904
7918
  }
7905
7919
  return this.lastStatus;
7906
7920
  }
7921
+ /** Signal the current terminal pipeline without terminating the interactive shell. */
7922
+ interruptForeground(signal, stdin) {
7923
+ const processes = this.kernel.procs.list().filter((process2) => process2.running);
7924
+ const selected = new Set(processes.filter((process2) => process2.ppid === this.proc.pid && process2.stdin === stdin).map((process2) => process2.pid));
7925
+ for (let changed = true; changed; ) {
7926
+ changed = false;
7927
+ const outputs = processes.filter((process2) => selected.has(process2.pid)).flatMap((process2) => [process2.stdout, process2.stderr]);
7928
+ for (const process2 of processes) {
7929
+ if (!selected.has(process2.pid) && (selected.has(process2.ppid) || outputs.some((output) => output === process2.stdin))) {
7930
+ selected.add(process2.pid);
7931
+ changed = true;
7932
+ }
7933
+ }
7934
+ }
7935
+ for (const process2 of processes) if (selected.has(process2.pid)) process2.deliver(signal);
7936
+ }
7907
7937
  get isExiting() {
7908
7938
  return this.exiting;
7909
7939
  }
@@ -8553,6 +8583,7 @@ sys ${fmt2(Math.floor(ms * 0.2))}
8553
8583
  for (const assignment of assignments) {
8554
8584
  env2[assignment.name] = await expandWordToString(assignment.value, this.expandContext());
8555
8585
  }
8586
+ if (this.proc.signal.aborted) return this.proc.exitCode ?? 137;
8556
8587
  const proc = this.kernel.spawn(words, {
8557
8588
  cwd: this.cwd,
8558
8589
  env: env2,
@@ -19695,6 +19726,11 @@ var KernelChildProcess = class {
19695
19726
  started = false;
19696
19727
  cancelled = false;
19697
19728
  constructor(kernel, cred, config, pid) {
19729
+ if (config.inheritStdio) {
19730
+ this.stdin.isTTY = true;
19731
+ this.stdin.interactive = true;
19732
+ }
19733
+ this.stdin.onRawMode = (enabled) => this.emit("rawmode", enabled);
19698
19734
  this.kernel = kernel;
19699
19735
  this.cred = cred;
19700
19736
  this.pid = pid;
@@ -19811,7 +19847,7 @@ function installNodeChildProcessBridge(pod, kernel, cred) {
19811
19847
  const originalSpawn = manager.spawn;
19812
19848
  manager.spawn = (config) => {
19813
19849
  const resolved = kernel.resolveExecutable(config.command, config.cwd ?? "/", config.env ?? {}, cred);
19814
- if (resolved?.kind === "builtin" && resolved.command?.name !== "node" && resolved.command?.name !== "nodejs") {
19850
+ if (resolved?.kind === "builtin") {
19815
19851
  return new KernelChildProcess(kernel, cred, config, nextBridgePid++);
19816
19852
  }
19817
19853
  return originalSpawn.call(manager, config);
@@ -19822,6 +19858,68 @@ function installNodeChildProcessBridge(pod, kernel, cred) {
19822
19858
  };
19823
19859
  }
19824
19860
 
19861
+ // src/runtime/host-module-tracker.ts
19862
+ var HostModuleTracker = class {
19863
+ active = 0;
19864
+ disposed = false;
19865
+ dispose() {
19866
+ this.disposed = true;
19867
+ }
19868
+ wrapped = /* @__PURE__ */ new WeakMap();
19869
+ originals = /* @__PURE__ */ new WeakMap();
19870
+ pending = () => this.active;
19871
+ unwrap = (value) => value && (typeof value === "object" || typeof value === "function") ? this.originals.get(value) ?? value : value;
19872
+ wrap(value) {
19873
+ if (value === null || typeof value !== "object" && typeof value !== "function") return value;
19874
+ const object = value;
19875
+ if (Array.isArray(value) || ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return value;
19876
+ if (this.wrapped.has(object)) return this.wrapped.get(object);
19877
+ const tracker = this;
19878
+ const proxy = new Proxy(object, {
19879
+ // CommonJS native loaders re-export by assigning exports back onto the
19880
+ // binding object. Never store a process-owned proxy in a shared module.
19881
+ set(target, key, member) {
19882
+ return Reflect.set(target, key, tracker.unwrap(member), target);
19883
+ },
19884
+ defineProperty(target, key, descriptor) {
19885
+ return Reflect.defineProperty(target, key, "value" in descriptor ? { ...descriptor, value: tracker.unwrap(descriptor.value) } : descriptor);
19886
+ },
19887
+ get(target, key) {
19888
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
19889
+ const member = Reflect.get(target, key, target);
19890
+ if (descriptor && !descriptor.configurable && "value" in descriptor && !descriptor.writable) return member;
19891
+ return typeof member === "function" ? tracker.wrap(member) : member;
19892
+ },
19893
+ apply(target, receiver, args) {
19894
+ const result = Reflect.apply(target, tracker.unwrap(receiver), args.map(tracker.unwrap));
19895
+ if (result && typeof result.then === "function") {
19896
+ tracker.active++;
19897
+ return Promise.resolve(result).then(
19898
+ (value2) => {
19899
+ tracker.active--;
19900
+ return tracker.disposed ? new Promise(() => {
19901
+ }) : tracker.wrap(value2);
19902
+ },
19903
+ (error) => {
19904
+ tracker.active--;
19905
+ if (tracker.disposed) return new Promise(() => {
19906
+ });
19907
+ throw error;
19908
+ }
19909
+ );
19910
+ }
19911
+ return tracker.wrap(result);
19912
+ },
19913
+ construct(target, args, newTarget) {
19914
+ return tracker.wrap(Reflect.construct(target, args.map(tracker.unwrap), tracker.unwrap(newTarget)));
19915
+ }
19916
+ });
19917
+ this.wrapped.set(object, proxy);
19918
+ this.originals.set(proxy, object);
19919
+ return proxy;
19920
+ }
19921
+ };
19922
+
19825
19923
  // src/runtime/commonjs-engine.ts
19826
19924
  init_path();
19827
19925
  var HELPERS = {
@@ -21559,6 +21657,7 @@ var VirtualHttpServer = class extends EventEmitter4 {
21559
21657
  owner;
21560
21658
  listening = false;
21561
21659
  portValue = null;
21660
+ referenced = true;
21562
21661
  listen(...args) {
21563
21662
  const callback = typeof args.at(-1) === "function" ? args.pop() : void 0;
21564
21663
  const first = args[0];
@@ -21587,11 +21686,16 @@ var VirtualHttpServer = class extends EventEmitter4 {
21587
21686
  return this.portValue === null ? null : { address: "0.0.0.0", family: "IPv4", port: this.portValue };
21588
21687
  }
21589
21688
  ref() {
21689
+ this.referenced = true;
21590
21690
  return this;
21591
21691
  }
21592
21692
  unref() {
21693
+ this.referenced = false;
21593
21694
  return this;
21594
21695
  }
21696
+ hasRef() {
21697
+ return this.referenced;
21698
+ }
21595
21699
  setTimeout(_milliseconds, callback) {
21596
21700
  if (callback) this.on("timeout", callback);
21597
21701
  return this;
@@ -21601,13 +21705,16 @@ var VirtualHttpRouter = class {
21601
21705
  servers = /* @__PURE__ */ new Map();
21602
21706
  /** Notified when a server begins listening, for `onServerReady`. */
21603
21707
  onListen;
21708
+ onClose;
21604
21709
  register(port, server, owner) {
21605
21710
  if (this.servers.has(port)) throw Object.assign(new Error(`listen EADDRINUSE: address already in use 0.0.0.0:${port}`), { code: "EADDRINUSE", port });
21606
21711
  this.servers.set(port, { server, owner });
21607
21712
  this.onListen?.(port);
21608
21713
  }
21609
21714
  unregister(port, server) {
21610
- if (this.servers.get(port)?.server === server) this.servers.delete(port);
21715
+ if (this.servers.get(port)?.server !== server) return;
21716
+ this.servers.delete(port);
21717
+ this.onClose?.(port);
21611
21718
  }
21612
21719
  /** Whether anything in this container is listening on `port`. */
21613
21720
  activePortsIncludes(port) {
@@ -21616,6 +21723,9 @@ var VirtualHttpRouter = class {
21616
21723
  activePorts(owner) {
21617
21724
  return [...this.servers].filter(([, item]) => owner === void 0 || item.owner === owner).map(([port]) => port).sort((a, b) => a - b);
21618
21725
  }
21726
+ referencedPorts(owner) {
21727
+ return [...this.servers].filter(([, item]) => item.owner === owner && item.server.hasRef()).map(([port]) => port);
21728
+ }
21619
21729
  closeOwner(owner) {
21620
21730
  for (const { server, owner: value } of [...this.servers.values()]) if (value === owner) server.close();
21621
21731
  }
@@ -22027,6 +22137,108 @@ function hashFor(algorithm) {
22027
22137
  function toBytes2(data, encoding) {
22028
22138
  return typeof data === "string" ? Buffer2.from(data, encoding) : data;
22029
22139
  }
22140
+
22141
+ // src/runtime/sync-channel.ts
22142
+ var STATE = 0;
22143
+ var LENGTH = 1;
22144
+ var MORE = 2;
22145
+ var CONTROL_WORDS = 4;
22146
+ var STATE_REQUEST = 1;
22147
+ var STATE_RESPONSE = 2;
22148
+ var STATE_CONTINUE = 3;
22149
+ var STATE_CLOSED = 4;
22150
+ function createSyncChannelBuffers(capacityBytes = 1 << 20) {
22151
+ return {
22152
+ control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
22153
+ data: new SharedArrayBuffer(capacityBytes)
22154
+ };
22155
+ }
22156
+ function syncChannelUnavailableReason() {
22157
+ if (typeof globalThis.crossOriginIsolated === "boolean" && !globalThis.crossOriginIsolated) {
22158
+ 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.";
22159
+ }
22160
+ if (typeof SharedArrayBuffer !== "function") return "SharedArrayBuffer is unavailable in this host.";
22161
+ if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return "Atomics.wait is unavailable in this host.";
22162
+ return null;
22163
+ }
22164
+ function syncChannelSupported() {
22165
+ return syncChannelUnavailableReason() === null;
22166
+ }
22167
+ var SyncChannelServer = class {
22168
+ constructor(buffers, handle) {
22169
+ this.handle = handle;
22170
+ this.control = new Int32Array(buffers.control);
22171
+ this.data = new Uint8Array(buffers.data);
22172
+ this.capacity = this.data.length;
22173
+ }
22174
+ handle;
22175
+ control;
22176
+ data;
22177
+ capacity;
22178
+ /** Request chunks gathered so far, and the response still to be sent. */
22179
+ incoming = [];
22180
+ outgoing = null;
22181
+ sent = 0;
22182
+ closed = false;
22183
+ /** Call from the wake message the client sends after each chunk. */
22184
+ async pump() {
22185
+ if (this.closed) return;
22186
+ const state = Atomics.load(this.control, STATE);
22187
+ if (state === STATE_REQUEST) {
22188
+ const size = Atomics.load(this.control, LENGTH);
22189
+ this.incoming.push(this.data.slice(0, size));
22190
+ if (Atomics.load(this.control, MORE) === 1) {
22191
+ this.publish(STATE_CONTINUE);
22192
+ return;
22193
+ }
22194
+ const request = concat3(this.incoming);
22195
+ this.incoming = [];
22196
+ let response;
22197
+ try {
22198
+ response = await this.handle(request);
22199
+ } catch {
22200
+ response = new Uint8Array();
22201
+ }
22202
+ if (this.closed) return;
22203
+ this.outgoing = response;
22204
+ this.sent = 0;
22205
+ this.sendChunk();
22206
+ return;
22207
+ }
22208
+ if (state === STATE_CONTINUE) this.sendChunk();
22209
+ }
22210
+ /** Release a blocked client, e.g. when the container is torn down. */
22211
+ close() {
22212
+ if (this.closed) return;
22213
+ this.closed = true;
22214
+ Atomics.store(this.control, STATE, STATE_CLOSED);
22215
+ Atomics.notify(this.control, STATE);
22216
+ }
22217
+ sendChunk() {
22218
+ const payload = this.outgoing ?? new Uint8Array();
22219
+ const size = Math.min(this.capacity, payload.length - this.sent);
22220
+ this.data.set(payload.subarray(this.sent, this.sent + size), 0);
22221
+ this.sent += size;
22222
+ Atomics.store(this.control, LENGTH, size);
22223
+ Atomics.store(this.control, MORE, this.sent < payload.length ? 1 : 0);
22224
+ this.publish(STATE_RESPONSE);
22225
+ }
22226
+ publish(state) {
22227
+ Atomics.store(this.control, STATE, state);
22228
+ Atomics.notify(this.control, STATE);
22229
+ }
22230
+ };
22231
+ function concat3(parts) {
22232
+ if (parts.length === 1) return parts[0];
22233
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
22234
+ const joined = new Uint8Array(total);
22235
+ let at = 0;
22236
+ for (const part of parts) {
22237
+ joined.set(part, at);
22238
+ at += part.length;
22239
+ }
22240
+ return joined;
22241
+ }
22030
22242
  var ChildProcess = class extends EventEmitter4 {
22031
22243
  stdout = new streamModule4.PassThrough();
22032
22244
  stderr = new streamModule4.PassThrough();
@@ -22099,7 +22311,8 @@ var ChildProcess = class extends EventEmitter4 {
22099
22311
  queueMicrotask(() => this.emit("close", code, null));
22100
22312
  }
22101
22313
  };
22102
- function createChildProcessModule(spawnChild, defaultCwd, syncSpawn) {
22314
+ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn, defaultEnv = () => ({})) {
22315
+ const environmentFor = (options) => options.env ? { ...options.env } : defaultEnv();
22103
22316
  const throughShell = (command, options) => {
22104
22317
  const shell = typeof options.shell === "string" ? options.shell : "/bin/sh";
22105
22318
  return { file: shell, args: ["-c", command] };
@@ -22110,7 +22323,7 @@ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn) {
22110
22323
  command: resolved.file,
22111
22324
  args: resolved.args,
22112
22325
  cwd: options.cwd ?? defaultCwd(),
22113
- ...options.env ? { env: options.env } : {}
22326
+ env: environmentFor(options)
22114
22327
  });
22115
22328
  return new ChildProcess(handle, resolved.file, resolved.args);
22116
22329
  };
@@ -22156,11 +22369,11 @@ ${err.join("")}`),
22156
22369
  exec,
22157
22370
  execFile,
22158
22371
  fork: (modulePath, args = [], options = {}) => spawn("node", [modulePath, ...args], options),
22159
- ...buildSyncFamily(syncSpawn, throughShell, defaultCwd),
22372
+ ...buildSyncFamily(syncSpawn, throughShell, defaultCwd, environmentFor),
22160
22373
  ChildProcess
22161
22374
  };
22162
22375
  }
22163
- function buildSyncFamily(syncSpawn, throughShell, defaultCwd) {
22376
+ function buildSyncFamily(syncSpawn, throughShell, defaultCwd, environmentFor) {
22164
22377
  if (!syncSpawn) {
22165
22378
  return {
22166
22379
  execSync: unavailable("execSync"),
@@ -22171,12 +22384,14 @@ function buildSyncFamily(syncSpawn, throughShell, defaultCwd) {
22171
22384
  const run = (file3, args, options) => {
22172
22385
  const resolved = options.shell ? throughShell([file3, ...args].join(" "), options) : { file: file3, args };
22173
22386
  const input = options.input === void 0 ? void 0 : typeof options.input === "string" ? options.input : new TextDecoder().decode(options.input);
22387
+ const inherit = options.stdio === "inherit" || Array.isArray(options.stdio) && options.stdio[0] === "inherit";
22174
22388
  return syncSpawn({
22175
22389
  command: resolved.file,
22176
22390
  args: resolved.args,
22177
22391
  cwd: options.cwd ?? defaultCwd(),
22178
- ...options.env ? { env: options.env } : {},
22179
- ...input === void 0 ? {} : { input }
22392
+ env: environmentFor(options),
22393
+ ...input === void 0 ? {} : { input },
22394
+ ...inherit ? { inheritStdio: true } : {}
22180
22395
  });
22181
22396
  };
22182
22397
  const asOutput = (text2, options) => options.encoding === "buffer" || options.encoding === void 0 ? Buffer2.from(text2) : text2;
@@ -22228,7 +22443,7 @@ function unavailable(name) {
22228
22443
  const list = Array.isArray(args[1]) ? args[1].map(String) : [];
22229
22444
  const command = file3 ? [file3, ...list].join(" ") : void 0;
22230
22445
  const error = new Error(
22231
- `child_process.${name} is not supported in the sandboxed runtime` + (command ? ` (tried to run: ${command})` : "") + `: the caller, the child and the event loop share one thread, so blocking the caller would also stop the child. Run it with the asynchronous form (spawn/exec/execFile), or run the command from the container shell.`
22446
+ `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.")
22232
22447
  );
22233
22448
  error.code = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
22234
22449
  throw error;
@@ -22516,6 +22731,11 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
22516
22731
  edit(sequence, key) {
22517
22732
  if (this.closed) return;
22518
22733
  const name = key.name;
22734
+ if (key.ctrl && name === "c") {
22735
+ if (this.listenerCount("SIGINT") > 0) this.emit("SIGINT");
22736
+ else this.close();
22737
+ return;
22738
+ }
22519
22739
  if (name === "return" || name === "enter") {
22520
22740
  const value = this.line;
22521
22741
  this.line = "";
@@ -22611,7 +22831,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
22611
22831
  };
22612
22832
  const createInterface = (options, output) => {
22613
22833
  if (options && typeof options === "object" && !options.on) {
22614
- const resolvedOutput2 = options.output ?? defaultOutput();
22834
+ const resolvedOutput2 = options.output;
22615
22835
  const instance = new Interface(
22616
22836
  options.input ?? defaultInput(),
22617
22837
  resolvedOutput2,
@@ -22620,7 +22840,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
22620
22840
  if (typeof options.prompt === "string") instance.setPrompt(options.prompt);
22621
22841
  return instance;
22622
22842
  }
22623
- const resolvedOutput = output ?? defaultOutput();
22843
+ const resolvedOutput = output;
22624
22844
  return new Interface(options ?? defaultInput(), resolvedOutput, isTerminal(void 0, resolvedOutput));
22625
22845
  };
22626
22846
  const noop = () => {
@@ -22856,7 +23076,7 @@ function createCoreModules(options) {
22856
23076
  };
22857
23077
  const http = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "http:") : createUnsupportedModule("http");
22858
23078
  const https = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "https:") : createUnsupportedModule("https");
22859
- const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd, options.syncSpawn) : createUnsupportedModule("child_process");
23079
+ const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd, options.syncSpawn, () => ({ ...processObject.env })) : createUnsupportedModule("child_process");
22860
23080
  const readline = createReadlineModule(() => processObject.stdin, () => processObject.stdout);
22861
23081
  const dns = createDnsModule();
22862
23082
  const builtins = {
@@ -22889,7 +23109,7 @@ function createCoreModules(options) {
22889
23109
  "stream/promises": createStreamPromises(),
22890
23110
  string_decoder: stringDecoderModule,
22891
23111
  timers: { ...timersModule, ...timers.api },
22892
- "timers/promises": createTimerPromises(),
23112
+ "timers/promises": createTimerPromises(timers.api),
22893
23113
  tty: { isatty: () => false, ReadStream: streamModule4.Readable, WriteStream: streamModule4.Writable },
22894
23114
  url: url_module_default,
22895
23115
  util: util_module_default,
@@ -23634,10 +23854,10 @@ function createTrackedTimers() {
23634
23854
  cancelAll
23635
23855
  };
23636
23856
  }
23637
- function createTimerPromises() {
23857
+ function createTimerPromises(timers) {
23638
23858
  return {
23639
- setTimeout: (delay, value) => new Promise((resolve2) => setTimeout(resolve2, delay, value)),
23640
- setImmediate: (value) => new Promise((resolve2) => setTimeout(resolve2, 0, value))
23859
+ setTimeout: (delay, value) => new Promise((resolve2) => timers.setTimeout(resolve2, delay, value)),
23860
+ setImmediate: (value) => new Promise((resolve2) => timers.setImmediate(resolve2, value))
23641
23861
  };
23642
23862
  }
23643
23863
  function createAsyncHooksModule() {
@@ -24101,8 +24321,10 @@ var MirroringVolume = class {
24101
24321
  * container pays nothing for this.
24102
24322
  */
24103
24323
  attach(mirror, root) {
24324
+ const cleanRoot = clean(root);
24325
+ if (this.mirror === mirror && this.root === cleanRoot) return;
24104
24326
  this.mirror = mirror;
24105
- this.root = clean(root);
24327
+ this.root = cleanRoot;
24106
24328
  this.seed();
24107
24329
  }
24108
24330
  detach() {
@@ -24343,11 +24565,8 @@ function ensureProcessGlobal() {
24343
24565
  // src/runtime/host-rolldown.ts
24344
24566
  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'] }";
24345
24567
  async function loadHostRolldownBinding() {
24346
- if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) {
24347
- throw new Error(
24348
- "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."
24349
- );
24350
- }
24568
+ const reason = syncChannelUnavailableReason();
24569
+ if (reason) throw new Error(`SandboxedJs threaded WASM unavailable: ${reason}`);
24351
24570
  try {
24352
24571
  const loaded = await import('@rolldown/binding-wasm32-wasi');
24353
24572
  return "__fs" in loaded ? { ...loaded } : loaded.default ?? loaded;
@@ -24355,9 +24574,9 @@ async function loadHostRolldownBinding() {
24355
24574
  if (typeof process !== "undefined" && process.env?.SANDBOXEDJS_DEBUG) {
24356
24575
  console.error("[sandboxedjs] Rolldown WASI binding unavailable:", error);
24357
24576
  }
24358
- const reason = error instanceof Error ? error.message : String(error);
24577
+ const reason2 = error instanceof Error ? error.message : String(error);
24359
24578
  throw new Error(
24360
- `The optional Rolldown WASI binding could not be loaded: ${reason}` + (typeof window === "undefined" ? "" : `
24579
+ `The optional Rolldown WASI binding could not be loaded: ${reason2}` + (typeof window === "undefined" ? "" : `
24361
24580
 
24362
24581
  ${BUNDLER_HINT}`),
24363
24582
  { cause: error }
@@ -24468,6 +24687,8 @@ var LocalProcess = class extends EventEmitter4 {
24468
24687
  }
24469
24688
  /** End the process now, for an asynchronous `process.exit`. */
24470
24689
  exitNow(code) {
24690
+ this.killed = true;
24691
+ this.cleanup?.();
24471
24692
  this.finish(code);
24472
24693
  this.resolveKilled();
24473
24694
  }
@@ -24651,6 +24872,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
24651
24872
  )
24652
24873
  };
24653
24874
  disposed = false;
24875
+ running = /* @__PURE__ */ new Set();
24654
24876
  workdir;
24655
24877
  env;
24656
24878
  aliases;
@@ -24692,8 +24914,9 @@ var LocalRuntimePod = class _LocalRuntimePod {
24692
24914
  const cwd = typeof options.cwd === "string" ? options.cwd : this.workdir;
24693
24915
  const env2 = { ...this.env, ...isRecord(options.env) ? options.env : {} };
24694
24916
  const owner = `${this.instanceId}:${Math.random().toString(36).slice(2)}`;
24695
- return new LocalProcess(async (proc) => {
24917
+ const process2 = new LocalProcess(async (proc) => {
24696
24918
  await this.prepareRolldown(cwd, env2);
24919
+ if (proc.isKilled()) return 137;
24697
24920
  const untrack = trackProcess(proc);
24698
24921
  let requestedExit = 0;
24699
24922
  let engine;
@@ -24721,7 +24944,9 @@ var LocalRuntimePod = class _LocalRuntimePod {
24721
24944
  onRawMode: (enabled) => proc.emit("rawmode", enabled)
24722
24945
  });
24723
24946
  proc.acceptInput((data) => core.writeStdin(data), () => core.endStdin());
24947
+ const hostWork = new HostModuleTracker();
24724
24948
  proc.onKill(() => {
24949
+ hostWork.dispose();
24725
24950
  core.cancelTimers();
24726
24951
  this.router.closeOwner(owner);
24727
24952
  });
@@ -24730,21 +24955,22 @@ var LocalRuntimePod = class _LocalRuntimePod {
24730
24955
  builtins: core.builtins,
24731
24956
  globals: core.globals,
24732
24957
  aliases: this.aliases,
24733
- overrides: this.modules
24958
+ overrides: Object.fromEntries(Object.entries(this.modules).map(([key, value]) => [key, hostWork.wrap(value)]))
24734
24959
  });
24735
24960
  try {
24736
- await engine.run(script);
24737
- await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin, core.pendingRequests, () => proc.isKilled());
24738
- if (this.router.activePorts(owner).length) {
24739
- await proc.waitForKill();
24740
- return 137;
24741
- }
24961
+ await Promise.race([engine.run(script), proc.waitForKill()]);
24962
+ await this.settle(owner, core.pendingHandles, core.readingStdin, () => core.pendingRequests() + hostWork.pending(), () => proc.isKilled());
24742
24963
  return requestedExit;
24743
24964
  } finally {
24965
+ hostWork.dispose();
24966
+ core.cancelTimers();
24744
24967
  untrack();
24745
24968
  this.router.closeOwner(owner);
24746
24969
  }
24747
24970
  });
24971
+ this.running.add(process2);
24972
+ void process2.completion.then(() => this.running.delete(process2));
24973
+ return process2;
24748
24974
  }
24749
24975
  /**
24750
24976
  * Rolldown's JavaScript API synchronously requires its compiled binding.
@@ -24780,19 +25006,13 @@ var LocalRuntimePod = class _LocalRuntimePod {
24780
25006
  * A plain script that has genuinely finished falls straight through both,
24781
25007
  * costing a handful of empty turns.
24782
25008
  */
24783
- async settle(owner, pendingHandles, pendingUnrefed, readingStdin, pendingRequests, killed) {
24784
- for (let turn = 0; turn < DRAIN_TURNS; turn++) {
24785
- if (this.router.activePorts(owner).length || killed()) return;
24786
- await new Promise((resolve2) => setTimeout(resolve2, 0));
24787
- }
24788
- if (pendingHandles() === 0 && pendingRequests() === 0 && pendingUnrefed() > 0 && !readingStdin()) {
24789
- const deadline = Date.now() + 1e3;
24790
- while (!this.router.activePorts(owner).length && !killed() && Date.now() < deadline) {
24791
- await new Promise((resolve2) => setTimeout(resolve2, 5));
24792
- }
24793
- }
24794
- while (!this.router.activePorts(owner).length && !killed() && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
24795
- await new Promise((resolve2) => setTimeout(resolve2, 5));
25009
+ async settle(owner, pendingHandles, readingStdin, pendingRequests, killed) {
25010
+ let idleTurns = 0;
25011
+ while (!killed()) {
25012
+ const busy = this.router.referencedPorts(owner).length > 0 || pendingHandles() > 0 || pendingRequests() > 0 || readingStdin();
25013
+ idleTurns = busy ? 0 : idleTurns + 1;
25014
+ if (idleTurns >= DRAIN_TURNS) return;
25015
+ await new Promise((resolve2) => setTimeout(resolve2, busy ? 5 : 0));
24796
25016
  }
24797
25017
  }
24798
25018
  async request(_port, _init = {}) {
@@ -24809,6 +25029,8 @@ var LocalRuntimePod = class _LocalRuntimePod {
24809
25029
  }
24810
25030
  teardown() {
24811
25031
  this.disposed = true;
25032
+ for (const process2 of this.running) process2.kill();
25033
+ this.running.clear();
24812
25034
  this.router.closeAll();
24813
25035
  this.esbuild?.stop();
24814
25036
  }
@@ -24878,103 +25100,6 @@ function attachRolldownMirror(binding, volume, root) {
24878
25100
  volume.attach(fs, root);
24879
25101
  }
24880
25102
 
24881
- // src/runtime/sync-channel.ts
24882
- var STATE = 0;
24883
- var LENGTH = 1;
24884
- var MORE = 2;
24885
- var CONTROL_WORDS = 4;
24886
- var STATE_REQUEST = 1;
24887
- var STATE_RESPONSE = 2;
24888
- var STATE_CONTINUE = 3;
24889
- var STATE_CLOSED = 4;
24890
- function createSyncChannelBuffers(capacityBytes = 1 << 20) {
24891
- return {
24892
- control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
24893
- data: new SharedArrayBuffer(capacityBytes)
24894
- };
24895
- }
24896
- function syncChannelSupported() {
24897
- if (typeof SharedArrayBuffer !== "function") return false;
24898
- if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return false;
24899
- if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) return false;
24900
- return true;
24901
- }
24902
- var SyncChannelServer = class {
24903
- constructor(buffers, handle) {
24904
- this.handle = handle;
24905
- this.control = new Int32Array(buffers.control);
24906
- this.data = new Uint8Array(buffers.data);
24907
- this.capacity = this.data.length;
24908
- }
24909
- handle;
24910
- control;
24911
- data;
24912
- capacity;
24913
- /** Request chunks gathered so far, and the response still to be sent. */
24914
- incoming = [];
24915
- outgoing = null;
24916
- sent = 0;
24917
- closed = false;
24918
- /** Call from the wake message the client sends after each chunk. */
24919
- async pump() {
24920
- if (this.closed) return;
24921
- const state = Atomics.load(this.control, STATE);
24922
- if (state === STATE_REQUEST) {
24923
- const size = Atomics.load(this.control, LENGTH);
24924
- this.incoming.push(this.data.slice(0, size));
24925
- if (Atomics.load(this.control, MORE) === 1) {
24926
- this.publish(STATE_CONTINUE);
24927
- return;
24928
- }
24929
- const request = concat3(this.incoming);
24930
- this.incoming = [];
24931
- let response;
24932
- try {
24933
- response = await this.handle(request);
24934
- } catch {
24935
- response = new Uint8Array();
24936
- }
24937
- if (this.closed) return;
24938
- this.outgoing = response;
24939
- this.sent = 0;
24940
- this.sendChunk();
24941
- return;
24942
- }
24943
- if (state === STATE_CONTINUE) this.sendChunk();
24944
- }
24945
- /** Release a blocked client, e.g. when the container is torn down. */
24946
- close() {
24947
- if (this.closed) return;
24948
- this.closed = true;
24949
- Atomics.store(this.control, STATE, STATE_CLOSED);
24950
- Atomics.notify(this.control, STATE);
24951
- }
24952
- sendChunk() {
24953
- const payload = this.outgoing ?? new Uint8Array();
24954
- const size = Math.min(this.capacity, payload.length - this.sent);
24955
- this.data.set(payload.subarray(this.sent, this.sent + size), 0);
24956
- this.sent += size;
24957
- Atomics.store(this.control, LENGTH, size);
24958
- Atomics.store(this.control, MORE, this.sent < payload.length ? 1 : 0);
24959
- this.publish(STATE_RESPONSE);
24960
- }
24961
- publish(state) {
24962
- Atomics.store(this.control, STATE, state);
24963
- Atomics.notify(this.control, STATE);
24964
- }
24965
- };
24966
- function concat3(parts) {
24967
- if (parts.length === 1) return parts[0];
24968
- const total = parts.reduce((sum, part) => sum + part.length, 0);
24969
- const joined = new Uint8Array(total);
24970
- let at = 0;
24971
- for (const part of parts) {
24972
- joined.set(part, at);
24973
- at += part.length;
24974
- }
24975
- return joined;
24976
- }
24977
-
24978
25103
  // src/runtime/remote-volume.ts
24979
25104
  var encoder7 = new TextEncoder();
24980
25105
  var decoder7 = new TextDecoder();
@@ -25069,24 +25194,32 @@ function defaultWorkerUrl() {
25069
25194
  return new URL("./worker-entry.js", import.meta.url);
25070
25195
  }
25071
25196
  function hasDomWorker() {
25072
- return typeof Worker === "function" && typeof document !== "undefined";
25197
+ return typeof Worker === "function";
25073
25198
  }
25074
25199
  async function startRuntimeWorker(options = {}) {
25075
25200
  const url = options.url ?? defaultWorkerUrl();
25076
25201
  const worker = hasDomWorker() ? await startDomWorker(url) : await startNodeWorker(url, options.workerData ?? {});
25077
- await new Promise((resolve2, reject) => {
25078
- const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
25079
- worker.onMessage((message) => {
25080
- if (message?.type === "sandboxedjs:ready") {
25202
+ try {
25203
+ await new Promise((resolve2, reject) => {
25204
+ const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
25205
+ worker.onMessage((message) => {
25206
+ if (message?.type === "sandboxedjs:ready") {
25207
+ clearTimeout(timer);
25208
+ resolve2();
25209
+ }
25210
+ });
25211
+ worker.onError((error) => {
25081
25212
  clearTimeout(timer);
25082
- resolve2();
25083
- }
25084
- });
25085
- worker.onError((error) => {
25086
- clearTimeout(timer);
25087
- reject(error instanceof Error ? error : new Error(String(error)));
25213
+ reject(error instanceof Error ? error : new Error(String(error)));
25214
+ });
25088
25215
  });
25089
- });
25216
+ } catch (error) {
25217
+ try {
25218
+ await worker.terminate();
25219
+ } catch {
25220
+ }
25221
+ throw error;
25222
+ }
25090
25223
  return worker;
25091
25224
  }
25092
25225
  async function startDomWorker(url) {
@@ -25147,6 +25280,8 @@ var WorkerProcess = class extends EventEmitter4 {
25147
25280
  started = false;
25148
25281
  pendingInput = [];
25149
25282
  inputEnded = false;
25283
+ inheritedInput;
25284
+ ownRawMode = false;
25150
25285
  on(event, listener) {
25151
25286
  return super.on(event, listener);
25152
25287
  }
@@ -25172,13 +25307,38 @@ var WorkerProcess = class extends EventEmitter4 {
25172
25307
  * program is still loading, and dropping them loses the answer to a prompt.
25173
25308
  */
25174
25309
  write(data) {
25310
+ if (this.inheritedInput) {
25311
+ this.inheritedInput.sendStdin(data);
25312
+ return;
25313
+ }
25175
25314
  if (this.started) this.worker.postMessage({ type: "stdin", data });
25176
25315
  else this.pendingInput.push(data);
25177
25316
  }
25178
25317
  endInput() {
25179
25318
  this.inputEnded = true;
25319
+ if (this.inheritedInput) {
25320
+ this.inheritedInput.endStdin?.();
25321
+ return;
25322
+ }
25180
25323
  if (this.started) this.worker.postMessage({ type: "stdin-end" });
25181
25324
  }
25325
+ rawMode(enabled) {
25326
+ this.ownRawMode = enabled;
25327
+ if (!this.inheritedInput) this.emit("rawmode", enabled);
25328
+ }
25329
+ /** The parent is blocked in Atomics.wait, so inherited input must bypass it. */
25330
+ inheritInput(child) {
25331
+ this.inheritedInput = child;
25332
+ this.emit("rawmode", false);
25333
+ child.on("rawmode", (enabled) => {
25334
+ if (this.inheritedInput === child) this.emit("rawmode", Boolean(enabled));
25335
+ });
25336
+ return () => {
25337
+ if (this.inheritedInput !== child) return;
25338
+ this.inheritedInput = void 0;
25339
+ this.emit("rawmode", this.ownRawMode);
25340
+ };
25341
+ }
25182
25342
  kill(_signal = "SIGTERM") {
25183
25343
  this.worker.postMessage({ type: "kill" });
25184
25344
  this.finish(137);
@@ -25199,25 +25359,30 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25199
25359
  super(options);
25200
25360
  this.workerUrl = options.workerUrl;
25201
25361
  }
25202
- /**
25203
- * Boot a Worker-backed pod, or return null when this host cannot support one.
25204
- *
25205
- * Declining is a first-class outcome. `SharedArrayBuffer` needs cross-origin
25206
- * isolation, a bundler may have made the guest script unreachable, and a host
25207
- * that supplied its own module objects has handed over things no thread
25208
- * boundary can carry. In every case the caller falls back to the in-realm pod
25209
- * and keeps working.
25210
- */
25211
- static async tryBoot(options = {}) {
25212
- if (!syncChannelSupported()) return null;
25213
- if (options.modules && Object.keys(options.modules).length > 0) return null;
25362
+ /** Boot without silently dropping synchronous child-process support. */
25363
+ static async boot(options = {}) {
25364
+ const reason = syncChannelUnavailableReason();
25365
+ if (reason) throw new Error(`SandboxedJs worker runtime unavailable: ${reason}`);
25366
+ if (options.modules && Object.keys(options.modules).length > 0) {
25367
+ throw new Error("SandboxedJs worker runtime cannot clone host-supplied modules. Use isolation: 'realm' explicitly.");
25368
+ }
25214
25369
  const pod = new _WorkerRuntimePod(options);
25215
25370
  try {
25216
25371
  const probe = await startRuntimeWorker({ ...options.workerUrl ? { url: options.workerUrl } : {}, timeoutMs: 1e4 });
25217
25372
  await probe.terminate();
25218
25373
  return pod;
25219
- } catch {
25374
+ } catch (cause) {
25220
25375
  pod.teardown();
25376
+ const detail = cause instanceof Error ? cause.message : String(cause);
25377
+ 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 });
25378
+ }
25379
+ }
25380
+ /** Compatibility mode, with an observable explanation for every fallback. */
25381
+ static async tryBoot(options = {}, onFallback = (error) => console.warn(`[sandboxedjs] Falling back to the realm runtime; synchronous child processes are unavailable. ${error.message}`)) {
25382
+ try {
25383
+ return await _WorkerRuntimePod.boot(options);
25384
+ } catch (error) {
25385
+ onFallback(error instanceof Error ? error : new Error(String(error)));
25221
25386
  return null;
25222
25387
  }
25223
25388
  }
@@ -25237,18 +25402,23 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25237
25402
  ...this.workerUrl ? { url: this.workerUrl } : {},
25238
25403
  timeoutMs: 15e3
25239
25404
  });
25405
+ const streams = { target: null };
25240
25406
  const server = new SyncChannelServer(buffers, serveSyncSyscalls({
25241
25407
  volume: this.volume,
25242
- spawnChild: (request) => this.runChildToCompletion(request)
25408
+ spawnChild: (request) => this.runChildToCompletion(request, streams.target, ownedChildren)
25243
25409
  }));
25244
25410
  const entry = { worker, server };
25245
25411
  this.live.add(entry);
25412
+ const ownedChildren = /* @__PURE__ */ new Set();
25246
25413
  const process2 = new WorkerProcess(worker, () => {
25414
+ for (const child of ownedChildren) child.kill("SIGTERM");
25415
+ ownedChildren.clear();
25247
25416
  this.live.delete(entry);
25248
25417
  server.close();
25249
25418
  this.closeProxies(owner);
25250
25419
  void worker.terminate();
25251
25420
  });
25421
+ streams.target = process2;
25252
25422
  const children = /* @__PURE__ */ new Map();
25253
25423
  worker.onMessage((raw) => {
25254
25424
  const message = raw;
@@ -25263,7 +25433,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25263
25433
  process2.error(String(message.text));
25264
25434
  return;
25265
25435
  case "rawmode":
25266
- process2.emit("rawmode", Boolean(message.enabled));
25436
+ process2.rawMode(Boolean(message.enabled));
25267
25437
  return;
25268
25438
  case "exit":
25269
25439
  process2.finish(Number(message.code));
@@ -25271,11 +25441,20 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25271
25441
  case "listen":
25272
25442
  this.proxyPort(Number(message.port), owner, worker);
25273
25443
  return;
25444
+ case "close-port": {
25445
+ const port = Number(message.port);
25446
+ const proxy = this.proxies.get(port);
25447
+ if (proxy?.owner === owner) {
25448
+ proxy.close();
25449
+ this.proxies.delete(port);
25450
+ }
25451
+ return;
25452
+ }
25274
25453
  case "http-response":
25275
25454
  this.settleProxied(Number(message.id), message.response);
25276
25455
  return;
25277
25456
  case "child-start":
25278
- this.startChild(worker, children, message);
25457
+ this.startChild(worker, children, message, ownedChildren);
25279
25458
  return;
25280
25459
  case "child-stdin":
25281
25460
  children.get(message.id)?.sendStdin?.(String(message.data));
@@ -25306,16 +25485,21 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25306
25485
  return process2;
25307
25486
  }
25308
25487
  /** Start an asynchronous child on the host's behalf and relay its events. */
25309
- startChild(worker, children, message) {
25488
+ startChild(worker, children, message, owned) {
25310
25489
  const handle = this.processManager.spawn(message.config);
25311
25490
  children.set(message.id, handle);
25491
+ owned.add(handle);
25492
+ handle.on("exit", () => {
25493
+ owned.delete(handle);
25494
+ children.delete(message.id);
25495
+ });
25312
25496
  for (const event of ["stdout", "stderr", "exit"]) {
25313
25497
  handle.on(event, (value) => worker.postMessage({ type: "child-event", id: message.id, event, value }));
25314
25498
  }
25315
25499
  handle.exec();
25316
25500
  }
25317
25501
  /** Run a child to completion and collect it, for the guest's `spawnSync`. */
25318
- runChildToCompletion(request) {
25502
+ runChildToCompletion(request, streamTo, owned) {
25319
25503
  return new Promise((resolve2) => {
25320
25504
  let handle;
25321
25505
  try {
@@ -25323,27 +25507,35 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25323
25507
  command: request.command,
25324
25508
  args: request.args,
25325
25509
  cwd: request.cwd,
25326
- ...request.env ? { env: request.env } : {}
25510
+ ...request.env ? { env: request.env } : {},
25511
+ ...request.inheritStdio ? { inheritStdio: true } : {}
25327
25512
  });
25328
25513
  } catch (error) {
25329
25514
  const failure = error;
25330
25515
  resolve2({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure.code ? { code: failure.code } : {}, message: failure.message } });
25331
25516
  return;
25332
25517
  }
25518
+ owned.add(handle);
25519
+ handle.on("exit", () => owned.delete(handle));
25333
25520
  let stdout = "";
25334
25521
  let stderr = "";
25522
+ const live = request.inheritStdio ? streamTo : null;
25523
+ const restoreInput = live?.inheritInput(handle);
25524
+ if (restoreInput) handle.on("exit", restoreInput);
25335
25525
  handle.on("stdout", (text2) => {
25336
25526
  stdout += text2;
25527
+ live?.output(text2);
25337
25528
  });
25338
25529
  handle.on("stderr", (text2) => {
25339
25530
  stderr += text2;
25531
+ live?.error(text2);
25340
25532
  });
25341
25533
  handle.on("exit", (code) => resolve2({ status: code, stdout, stderr, signal: null }));
25342
25534
  handle.exec();
25343
25535
  if (request.input !== void 0) {
25344
25536
  handle.sendStdin?.(request.input);
25345
25537
  }
25346
- handle.endStdin?.();
25538
+ if (!request.inheritStdio) handle.endStdin?.();
25347
25539
  });
25348
25540
  }
25349
25541
  // ── HTTP servers living on another thread ─────────────────────────────────
@@ -25483,7 +25675,7 @@ var Container = class _Container {
25483
25675
  ...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
25484
25676
  };
25485
25677
  const workerOptions = { ...podOptions, ...opts.workerUrl ? { workerUrl: opts.workerUrl } : {} };
25486
- const pod = opts.pod ?? (opts.isolation === "realm" ? null : await WorkerRuntimePod.tryBoot(workerOptions)) ?? await LocalRuntimePod.boot(podOptions);
25678
+ 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);
25487
25679
  const kernel = new Kernel({
25488
25680
  pod,
25489
25681
  hostname: opts.hostname ?? "sandbox",
@@ -25991,7 +26183,7 @@ var Terminal = class {
25991
26183
  if (this.running) {
25992
26184
  const raw = this.currentStdin?.rawMode === true;
25993
26185
  if (ch === CTRL_C && !raw) {
25994
- this.session.proc.deliver("SIGINT");
26186
+ if (this.currentStdin) this.session.shell.interruptForeground("SIGINT", this.currentStdin);
25995
26187
  this.write("^C\r\n");
25996
26188
  return;
25997
26189
  }
@@ -26344,8 +26536,99 @@ init_variables();
26344
26536
  init_arith();
26345
26537
  init_expand();
26346
26538
  init_builtins();
26539
+
26540
+ // src/preview/register.ts
26541
+ function serveContainerOn(port, box) {
26542
+ port.onmessage = async (event) => {
26543
+ const request = event.data;
26544
+ try {
26545
+ const response = await box.request(request.port, {
26546
+ method: request.method,
26547
+ path: request.path,
26548
+ headers: request.headers,
26549
+ ...request.body ? { body: new Uint8Array(request.body) } : {}
26550
+ });
26551
+ const bytes2 = response.bytes.slice();
26552
+ port.postMessage(
26553
+ {
26554
+ id: request.id,
26555
+ response: {
26556
+ status: response.status,
26557
+ statusText: response.statusText,
26558
+ headers: response.headers,
26559
+ body: bytes2.buffer
26560
+ }
26561
+ },
26562
+ [bytes2.buffer]
26563
+ );
26564
+ } catch (error) {
26565
+ const message = new TextEncoder().encode(error instanceof Error ? error.message : String(error));
26566
+ port.postMessage({
26567
+ id: request.id,
26568
+ response: { status: 502, statusText: "Bad Gateway", headers: {}, body: message.buffer }
26569
+ });
26570
+ }
26571
+ };
26572
+ port.start?.();
26573
+ }
26574
+ async function createPreview(box, options = {}) {
26575
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return null;
26576
+ const scriptUrl = options.scriptUrl ?? new URL("./service-worker.js?no-inline", import.meta.url);
26577
+ let registration;
26578
+ try {
26579
+ registration = await navigator.serviceWorker.register(scriptUrl, {
26580
+ type: "module",
26581
+ ...options.scope ? { scope: options.scope } : {}
26582
+ });
26583
+ } catch {
26584
+ return null;
26585
+ }
26586
+ const worker = registration.active ?? registration.waiting ?? registration.installing;
26587
+ if (!worker) return null;
26588
+ if (worker.state !== "activated") {
26589
+ const activated = await new Promise((resolve2) => {
26590
+ const check = () => {
26591
+ if (worker.state === "activated") {
26592
+ worker.removeEventListener("statechange", check);
26593
+ resolve2(true);
26594
+ } else if (worker.state === "redundant") {
26595
+ worker.removeEventListener("statechange", check);
26596
+ resolve2(false);
26597
+ }
26598
+ };
26599
+ worker.addEventListener("statechange", check);
26600
+ setTimeout(() => resolve2(worker.state === "activated"), 1e4);
26601
+ check();
26602
+ });
26603
+ if (!activated) return null;
26604
+ }
26605
+ const channel = new MessageChannel();
26606
+ serveContainerOn(channel.port1, box);
26607
+ worker.postMessage({ type: "sandboxedjs:connect" }, [channel.port2]);
26608
+ const base2 = registration.scope.replace(/\/$/, "");
26609
+ return {
26610
+ urlFor: (port) => `${base2}/__sbx__/${port}/`,
26611
+ dispose: async () => {
26612
+ channel.port1.close();
26613
+ await registration.unregister();
26614
+ }
26615
+ };
26616
+ }
26617
+ async function renderInto(box, element, options = { port: 80 }) {
26618
+ const response = await box.request(options.port, { path: options.path ?? "/" });
26619
+ const frame = document.createElement("iframe");
26620
+ frame.setAttribute("sandbox", "allow-scripts");
26621
+ frame.style.width = "100%";
26622
+ frame.style.height = "100%";
26623
+ frame.style.border = "0";
26624
+ frame.srcdoc = response.body;
26625
+ element.replaceChildren(frame);
26626
+ return frame;
26627
+ }
26628
+
26629
+ // src/index.ts
26347
26630
  var src_default = createContainer;
26348
26631
 
26349
- export { ArithError, BufferSink, CallbackSink, CleanPackageInstaller, CommandRegistry, CommonJsEngine, Container, ContainerFs, ERRNO, FileInput, FileOutput, IncompleteInputError, KERNEL_NAME, KERNEL_RELEASE, Kernel, LocalRuntimePod, MemoryVolume, NODE_VERSION, NPM_VERSION, NetworkStack, NullInput, NullOutput, OS_RELEASE, PYTHON_VERSION, Pipe, Process, ProcessTable, ROOT_CRED, SIGNALS, SIGNAL_NAMES, Session, Shell, ShellExit, Lexer as ShellLexer, ShellSyntaxError, Stats, SysError, TeeOutput, Terminal, UserDatabase, Variables, Vfs, VirtualHttpRouter, VirtualHttpServer, VirtualIncomingMessage, VirtualServerResponse, WASM_ALIASES, allCommands, applyChmod, braceExpand, buildRootfs, builtinNames, captureStdio, configureCPython, configurePython, createChildProcessModule, createContainer, createContext, createCoreModules, src_default as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, extractNpmTarball, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, installUserland, isBuiltinName, isCPythonAvailable, isPythonAvailable, isSysError, looksLikeEsm, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path_exports as posixPath, resetPidCounter, shellQuote, startRuntimeWorker, strerror, syncChannelSupported, transformEsm, unameInfo };
26632
+ 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 };
26350
26633
  //# sourceMappingURL=index.js.map
26351
26634
  //# sourceMappingURL=index.js.map