sandboxedjs 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -7349,15 +7349,15 @@ var Parser = class {
7349
7349
  if (this.isWord("-p")) this.next();
7350
7350
  }
7351
7351
  }
7352
- const commands11 = [this.parseCommand()];
7352
+ const commands12 = [this.parseCommand()];
7353
7353
  const stderrToo = [];
7354
7354
  while (this.isOp("|") || this.isOp("|&")) {
7355
7355
  stderrToo.push(this.next().value === "|&");
7356
7356
  this.skipNewlines();
7357
- commands11.push(this.parseCommand());
7357
+ commands12.push(this.parseCommand());
7358
7358
  }
7359
- if (commands11.length === 1 && !negated && !timed) return commands11[0];
7360
- return { type: "pipeline", commands: commands11, negated, stderrToo, timed };
7359
+ if (commands12.length === 1 && !negated && !timed) return commands12[0];
7360
+ return { type: "pipeline", commands: commands12, negated, stderrToo, timed };
7361
7361
  }
7362
7362
  // ── commands ─────────────────────────────────────────────────────────────
7363
7363
  parseCommand() {
@@ -16980,6 +16980,145 @@ var helpCmd = defineCommand({
16980
16980
  });
16981
16981
  var commands10 = [man, whatis, apropos, less, helpCmd];
16982
16982
 
16983
+ // src/bin/clipboard.ts
16984
+ init_path();
16985
+ var CLIPBOARD_DIR = "/run/clipboard";
16986
+ function selectionPath(selection) {
16987
+ return join(CLIPBOARD_DIR, selection);
16988
+ }
16989
+ function readSelection(ctx, selection) {
16990
+ try {
16991
+ return ctx.vfs.readFile(selectionPath(selection), ctx.cred);
16992
+ } catch {
16993
+ return new Uint8Array();
16994
+ }
16995
+ }
16996
+ function writeSelection(ctx, selection, bytes2) {
16997
+ try {
16998
+ ctx.vfs.mkdir(CLIPBOARD_DIR, { recursive: true, mode: 511 });
16999
+ } catch {
17000
+ }
17001
+ ctx.vfs.writeFile(selectionPath(selection), bytes2, { privileged: true, mode: 438 });
17002
+ }
17003
+ var xsel = defineCommand({
17004
+ name: "xsel",
17005
+ summary: "access an X selection",
17006
+ usage: "xsel [--clipboard|--primary|--secondary] [--input|--output|--clear]",
17007
+ async run(ctx) {
17008
+ let selection = "primary";
17009
+ let mode = "output";
17010
+ for (const arg of ctx.args) {
17011
+ switch (arg) {
17012
+ case "-b":
17013
+ case "--clipboard":
17014
+ selection = "clipboard";
17015
+ break;
17016
+ case "-p":
17017
+ case "--primary":
17018
+ selection = "primary";
17019
+ break;
17020
+ case "-s":
17021
+ case "--secondary":
17022
+ selection = "secondary";
17023
+ break;
17024
+ case "-i":
17025
+ case "--input":
17026
+ case "-a":
17027
+ case "--append":
17028
+ mode = "input";
17029
+ break;
17030
+ case "-o":
17031
+ case "--output":
17032
+ mode = "output";
17033
+ break;
17034
+ case "-c":
17035
+ case "--clear":
17036
+ mode = "clear";
17037
+ break;
17038
+ }
17039
+ }
17040
+ if (mode === "clear") {
17041
+ writeSelection(ctx, selection, new Uint8Array());
17042
+ return 0;
17043
+ }
17044
+ if (mode === "input") {
17045
+ writeSelection(ctx, selection, await ctx.stdin.readAll());
17046
+ return 0;
17047
+ }
17048
+ ctx.write(readSelection(ctx, selection));
17049
+ return 0;
17050
+ }
17051
+ });
17052
+ var xclip = defineCommand({
17053
+ name: "xclip",
17054
+ summary: "access an X selection",
17055
+ usage: "xclip [-selection clipboard|primary|secondary] [-i|-o]",
17056
+ async run(ctx) {
17057
+ let selection = "primary";
17058
+ let mode = "input";
17059
+ const args = ctx.args;
17060
+ for (let index = 0; index < args.length; index++) {
17061
+ const arg = args[index];
17062
+ if (arg === "-selection" || arg === "-sel" || arg === "--selection") {
17063
+ const value = args[++index];
17064
+ if (value === "clipboard" || value === "primary" || value === "secondary") selection = value;
17065
+ continue;
17066
+ }
17067
+ if (arg === "-i" || arg === "-in") mode = "input";
17068
+ else if (arg === "-o" || arg === "-out") mode = "output";
17069
+ }
17070
+ if (mode === "input") {
17071
+ writeSelection(ctx, selection, await ctx.stdin.readAll());
17072
+ return 0;
17073
+ }
17074
+ ctx.write(readSelection(ctx, selection));
17075
+ return 0;
17076
+ }
17077
+ });
17078
+ var pbcopy = defineCommand({
17079
+ name: "pbcopy",
17080
+ summary: "copy standard input to the clipboard",
17081
+ usage: "pbcopy",
17082
+ async run(ctx) {
17083
+ writeSelection(ctx, "clipboard", await ctx.stdin.readAll());
17084
+ return 0;
17085
+ }
17086
+ });
17087
+ var pbpaste = defineCommand({
17088
+ name: "pbpaste",
17089
+ summary: "write the clipboard to standard output",
17090
+ usage: "pbpaste",
17091
+ run(ctx) {
17092
+ ctx.write(readSelection(ctx, "clipboard"));
17093
+ return 0;
17094
+ }
17095
+ });
17096
+ var wlCopy = defineCommand({
17097
+ name: "wl-copy",
17098
+ summary: "copy standard input to the Wayland clipboard",
17099
+ usage: "wl-copy [--primary] [text...]",
17100
+ async run(ctx) {
17101
+ const selection = ctx.args.includes("--primary") || ctx.args.includes("-p") ? "primary" : "clipboard";
17102
+ const operands = ctx.args.filter((arg) => !arg.startsWith("-"));
17103
+ const bytes2 = operands.length ? new TextEncoder().encode(operands.join(" ")) : await ctx.stdin.readAll();
17104
+ writeSelection(ctx, selection, bytes2);
17105
+ return 0;
17106
+ }
17107
+ });
17108
+ var wlPaste = defineCommand({
17109
+ name: "wl-paste",
17110
+ summary: "write the Wayland clipboard to standard output",
17111
+ usage: "wl-paste [--primary] [-n]",
17112
+ run(ctx) {
17113
+ const selection = ctx.args.includes("--primary") || ctx.args.includes("-p") ? "primary" : "clipboard";
17114
+ const bytes2 = readSelection(ctx, selection);
17115
+ ctx.write(bytes2);
17116
+ if (!ctx.args.includes("-n") && !ctx.args.includes("--no-newline")) ctx.write("\n");
17117
+ return 0;
17118
+ }
17119
+ });
17120
+ var commands11 = [xsel, xclip, pbcopy, pbpaste, wlCopy, wlPaste];
17121
+
16983
17122
  // src/runtime/node.ts
16984
17123
  init_path();
16985
17124
  var NODE_VERSION = "v22.12.0";
@@ -17820,22 +17959,22 @@ async function runCPythonProgram(ctx, source, argv, scriptDir, stdinText) {
17820
17959
  });
17821
17960
  } catch {
17822
17961
  }
17823
- const encoder7 = new TextEncoder();
17824
- const decoder7 = new TextDecoder();
17962
+ const encoder8 = new TextEncoder();
17963
+ const decoder8 = new TextDecoder();
17825
17964
  py.setStdout({
17826
17965
  write: (buffer) => {
17827
- ctx.write(decoder7.decode(buffer));
17966
+ ctx.write(decoder8.decode(buffer));
17828
17967
  return buffer.length;
17829
17968
  }
17830
17969
  });
17831
17970
  py.setStderr({
17832
17971
  write: (buffer) => {
17833
- ctx.stderr.write(decoder7.decode(buffer));
17972
+ ctx.stderr.write(decoder8.decode(buffer));
17834
17973
  return buffer.length;
17835
17974
  }
17836
17975
  });
17837
17976
  if (stdinText !== null) {
17838
- const bytes2 = encoder7.encode(stdinText);
17977
+ const bytes2 = encoder8.encode(stdinText);
17839
17978
  let offset = 0;
17840
17979
  py.setStdin({
17841
17980
  read: (buffer) => {
@@ -17886,9 +18025,9 @@ async function runCPythonRepl(ctx) {
17886
18025
  }
17887
18026
  mountContainerDirs(py, ctx);
17888
18027
  bootstrap(py, ctx, [""], null);
17889
- const decoder7 = new TextDecoder();
17890
- py.setStdout({ write: (data) => (ctx.write(decoder7.decode(data)), data.length) });
17891
- py.setStderr({ write: (data) => (ctx.stderr.write(decoder7.decode(data)), data.length) });
18028
+ const decoder8 = new TextDecoder();
18029
+ py.setStdout({ write: (data) => (ctx.write(decoder8.decode(data)), data.length) });
18030
+ py.setStderr({ write: (data) => (ctx.stderr.write(decoder8.decode(data)), data.length) });
17892
18031
  ctx.line(`Python ${String(py.runPython("import sys; sys.version.split()[0]"))} (Pyodide)`);
17893
18032
  ctx.line('Type "help()" for more information.');
17894
18033
  let source = "";
@@ -19336,6 +19475,7 @@ function allCommands() {
19336
19475
  ...commands8,
19337
19476
  ...commands9,
19338
19477
  ...commands10,
19478
+ ...commands11,
19339
19479
  ...nodeCommands(),
19340
19480
  ...pythonCommands(),
19341
19481
  ...ffmpegCommands(),
@@ -19498,15 +19638,15 @@ var Session = class {
19498
19638
  async run(command, opts = {}) {
19499
19639
  if (this.closed) throw new Error("session is closed");
19500
19640
  const combined = [];
19501
- const decoder7 = new TextDecoder();
19641
+ const decoder8 = new TextDecoder();
19502
19642
  const stdout = new BufferSink((chunk) => {
19503
- const text2 = decoder7.decode(chunk, { stream: true });
19643
+ const text2 = decoder8.decode(chunk, { stream: true });
19504
19644
  combined.push(text2);
19505
19645
  opts.onStdout?.(text2);
19506
19646
  this.hooks.onStdout?.(text2);
19507
19647
  });
19508
19648
  const stderr = new BufferSink((chunk) => {
19509
- const text2 = decoder7.decode(chunk, { stream: true });
19649
+ const text2 = decoder8.decode(chunk, { stream: true });
19510
19650
  combined.push(text2);
19511
19651
  opts.onStderr?.(text2);
19512
19652
  this.hooks.onStderr?.(text2);
@@ -19632,6 +19772,13 @@ var KernelChildProcess = class {
19632
19772
  } catch {
19633
19773
  }
19634
19774
  }
19775
+ /** Signal EOF, so a child reading stdin to the end can finish. */
19776
+ endStdin() {
19777
+ try {
19778
+ this.stdin.end();
19779
+ } catch {
19780
+ }
19781
+ }
19635
19782
  kill(signal = "SIGTERM") {
19636
19783
  if (this.process) {
19637
19784
  this.process.deliver(signal);
@@ -20543,6 +20690,55 @@ function splitSpecifier(specifier) {
20543
20690
  return { name: parts.slice(0, size).join("/"), subpath: parts.slice(size).join("/") };
20544
20691
  }
20545
20692
 
20693
+ // src/runtime/readable-from.ts
20694
+ function createReadableFrom(Readable) {
20695
+ return function from(source, options = {}) {
20696
+ if (source && typeof source.pipe === "function") return source;
20697
+ const iterator = source && typeof source[Symbol.asyncIterator] === "function" ? source[Symbol.asyncIterator]() : source && typeof source[Symbol.iterator] === "function" ? source[Symbol.iterator]() : (function* () {
20698
+ yield source;
20699
+ })();
20700
+ let reading = false;
20701
+ const stream = new Readable({
20702
+ ...options,
20703
+ objectMode: options.objectMode ?? false,
20704
+ read() {
20705
+ if (reading) return;
20706
+ reading = true;
20707
+ void (async () => {
20708
+ try {
20709
+ for (; ; ) {
20710
+ const next = await iterator.next();
20711
+ if (next.done) {
20712
+ stream.push(null);
20713
+ return;
20714
+ }
20715
+ const value = next.value;
20716
+ if (!stream.push(value === void 0 ? null : value)) return;
20717
+ }
20718
+ } catch (error) {
20719
+ stream.destroy(error);
20720
+ } finally {
20721
+ reading = false;
20722
+ }
20723
+ })();
20724
+ },
20725
+ destroy(error, callback) {
20726
+ void (async () => {
20727
+ try {
20728
+ await iterator.return?.();
20729
+ } catch {
20730
+ }
20731
+ callback(error);
20732
+ })();
20733
+ }
20734
+ });
20735
+ return stream;
20736
+ };
20737
+ }
20738
+ function installReadableFrom(streamModule5) {
20739
+ streamModule5.Readable.from = createReadableFrom(streamModule5.Readable);
20740
+ }
20741
+
20546
20742
  // src/runtime/util-module.ts
20547
20743
  var customInspect = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
20548
20744
  var promisifyCustom = /* @__PURE__ */ Symbol.for("nodejs.util.promisify.custom");
@@ -21430,6 +21626,10 @@ var VirtualHttpRouter = class {
21430
21626
  unregister(port, server) {
21431
21627
  if (this.servers.get(port)?.server === server) this.servers.delete(port);
21432
21628
  }
21629
+ /** Whether anything in this container is listening on `port`. */
21630
+ activePortsIncludes(port) {
21631
+ return this.servers.has(port);
21632
+ }
21433
21633
  activePorts(owner) {
21434
21634
  return [...this.servers].filter(([, item]) => owner === void 0 || item.owner === owner).map(([port]) => port).sort((a, b) => a - b);
21435
21635
  }
@@ -21453,13 +21653,275 @@ var VirtualHttpRouter = class {
21453
21653
  }
21454
21654
  }
21455
21655
  };
21456
- function createHttpModule(router, owner) {
21656
+ var VirtualClientResponse = class extends streamModule4__default.default.Readable {
21657
+ constructor(statusCode, statusMessage, headers, body) {
21658
+ super();
21659
+ this.statusCode = statusCode;
21660
+ this.statusMessage = statusMessage;
21661
+ this.headers = headers;
21662
+ this.rawHeaders = Object.entries(headers).flatMap(([key, value]) => [key, value]);
21663
+ if (body.length) this.push(Buffer2.from(body));
21664
+ this.push(null);
21665
+ this.complete = true;
21666
+ }
21667
+ statusCode;
21668
+ statusMessage;
21669
+ headers;
21670
+ httpVersion = "1.1";
21671
+ httpVersionMajor = 1;
21672
+ httpVersionMinor = 1;
21673
+ socket = socketStub();
21674
+ connection = this.socket;
21675
+ rawHeaders;
21676
+ complete = false;
21677
+ _read() {
21678
+ }
21679
+ setTimeout(_milliseconds, callback) {
21680
+ if (callback) this.once("timeout", callback);
21681
+ return this;
21682
+ }
21683
+ };
21684
+ function resolveTarget(input, overrides, defaultProtocol) {
21685
+ const options = {};
21686
+ if (typeof input === "string" || input instanceof URL) {
21687
+ const url = new URL(String(input));
21688
+ options.protocol = url.protocol;
21689
+ options.hostname = url.hostname;
21690
+ if (url.port) options.port = Number(url.port);
21691
+ options.path = `${url.pathname}${url.search}`;
21692
+ if (url.username || url.password) options.auth = `${url.username}:${url.password}`;
21693
+ } else if (input && typeof input === "object") {
21694
+ Object.assign(options, input);
21695
+ }
21696
+ if (overrides) Object.assign(options, overrides);
21697
+ const protocol = String(options.protocol ?? defaultProtocol).replace(/:?$/, ":");
21698
+ const headers = {};
21699
+ for (const [name, value] of Object.entries(options.headers ?? {})) {
21700
+ if (value === void 0 || value === null) continue;
21701
+ headers[String(name)] = Array.isArray(value) ? value.join(", ") : String(value);
21702
+ }
21703
+ if (options.auth && headers.authorization === void 0) {
21704
+ headers.authorization = `Basic ${Buffer2.from(String(options.auth)).toString("base64")}`;
21705
+ }
21706
+ const rawHost = String(options.hostname ?? options.host ?? "localhost");
21707
+ const hostMatch = /^(\[[^\]]*\]|[^:]*)(?::(\d+))?$/.exec(rawHost);
21708
+ const hostname = (hostMatch?.[1] ?? rawHost).replace(/^\[|\]$/g, "");
21709
+ const explicitPort = options.port ?? hostMatch?.[2];
21710
+ const port = explicitPort === void 0 || explicitPort === null || explicitPort === "" || Number.isNaN(Number(explicitPort)) ? protocol === "https:" ? 443 : 80 : Number(explicitPort);
21711
+ return {
21712
+ protocol,
21713
+ hostname,
21714
+ port,
21715
+ path: String(options.path ?? "/"),
21716
+ method: String(options.method ?? "GET").toUpperCase(),
21717
+ headers
21718
+ };
21719
+ }
21720
+ function isLoopback(hostname) {
21721
+ return hostname === "localhost" || hostname === "0.0.0.0" || hostname === "::1" || hostname === "::" || hostname.startsWith("127.") || hostname.endsWith(".localhost");
21722
+ }
21723
+ var VirtualClientRequest = class extends streamModule4__default.default.Writable {
21724
+ constructor(target, router, fetchImpl, trackRequest) {
21725
+ super();
21726
+ this.target = target;
21727
+ this.router = router;
21728
+ this.fetchImpl = fetchImpl;
21729
+ this.trackRequest = trackRequest;
21730
+ for (const [name, value] of Object.entries(target.headers)) this.setHeader(name, value);
21731
+ }
21732
+ target;
21733
+ router;
21734
+ fetchImpl;
21735
+ trackRequest;
21736
+ socket = socketStub();
21737
+ connection = this.socket;
21738
+ chunks = [];
21739
+ headerMap = /* @__PURE__ */ new Map();
21740
+ dispatched = false;
21741
+ destroyedByUser = false;
21742
+ timer;
21743
+ aborted = false;
21744
+ finished = false;
21745
+ reusedSocket = false;
21746
+ get method() {
21747
+ return this.target.method;
21748
+ }
21749
+ get path() {
21750
+ return this.target.path;
21751
+ }
21752
+ get host() {
21753
+ return this.target.hostname;
21754
+ }
21755
+ get protocol() {
21756
+ return this.target.protocol;
21757
+ }
21758
+ setHeader(name, value) {
21759
+ validateHeaderName(name);
21760
+ validateHeaderValue(name, value);
21761
+ this.headerMap.set(name.toLowerCase(), { name, value: Array.isArray(value) ? [...value].map(String).join(", ") : String(value) });
21762
+ return this;
21763
+ }
21764
+ getHeader(name) {
21765
+ return this.headerMap.get(name.toLowerCase())?.value;
21766
+ }
21767
+ getHeaders() {
21768
+ return Object.fromEntries([...this.headerMap].map(([key, entry]) => [key, entry.value]));
21769
+ }
21770
+ getHeaderNames() {
21771
+ return [...this.headerMap.keys()];
21772
+ }
21773
+ hasHeader(name) {
21774
+ return this.headerMap.has(name.toLowerCase());
21775
+ }
21776
+ removeHeader(name) {
21777
+ this.headerMap.delete(name.toLowerCase());
21778
+ }
21779
+ flushHeaders() {
21780
+ }
21781
+ setNoDelay() {
21782
+ }
21783
+ setSocketKeepAlive() {
21784
+ }
21785
+ ref() {
21786
+ return this;
21787
+ }
21788
+ unref() {
21789
+ return this;
21790
+ }
21791
+ setTimeout(milliseconds, callback) {
21792
+ if (callback) this.once("timeout", callback);
21793
+ clearTimeout(this.timer);
21794
+ this.timer = setTimeout(() => this.emit("timeout"), milliseconds);
21795
+ return this;
21796
+ }
21797
+ abort() {
21798
+ this.destroyRequest();
21799
+ }
21800
+ destroy(error) {
21801
+ this.destroyRequest(error);
21802
+ return this;
21803
+ }
21804
+ destroyRequest(error) {
21805
+ if (this.destroyedByUser) return;
21806
+ this.destroyedByUser = true;
21807
+ this.aborted = true;
21808
+ clearTimeout(this.timer);
21809
+ this.emit("abort");
21810
+ if (error) this.emit("error", error);
21811
+ this.emit("close");
21812
+ }
21813
+ _write(chunk, encoding, callback) {
21814
+ this.chunks.push(Buffer2.isBuffer(chunk) ? chunk : Buffer2.from(chunk, encoding));
21815
+ callback();
21816
+ }
21817
+ _final(callback) {
21818
+ this.finished = true;
21819
+ callback();
21820
+ void this.dispatch();
21821
+ }
21822
+ async dispatch() {
21823
+ if (this.dispatched || this.destroyedByUser) return;
21824
+ this.dispatched = true;
21825
+ const body = Buffer2.concat(this.chunks);
21826
+ const release = this.trackRequest?.();
21827
+ let released = false;
21828
+ const untrack = () => {
21829
+ if (released || !release) return;
21830
+ released = true;
21831
+ release();
21832
+ };
21833
+ let settled = false;
21834
+ try {
21835
+ const response = isLoopback(this.target.hostname) ? await this.viaRouter(body) : await this.viaFetch(body);
21836
+ if (this.destroyedByUser) return;
21837
+ clearTimeout(this.timer);
21838
+ settled = true;
21839
+ response.once("end", untrack);
21840
+ response.once("close", untrack);
21841
+ response.once("error", untrack);
21842
+ this.emit("response", response);
21843
+ queueMicrotask(() => {
21844
+ if (this.listenerCount("response") === 0) response.resume();
21845
+ });
21846
+ } catch (error) {
21847
+ clearTimeout(this.timer);
21848
+ if (this.destroyedByUser) return;
21849
+ const failure = error instanceof Error ? error : new Error(String(error));
21850
+ if (!("code" in failure)) Object.assign(failure, { code: "ECONNREFUSED" });
21851
+ this.emit("error", failure);
21852
+ } finally {
21853
+ if (!settled) untrack();
21854
+ }
21855
+ }
21856
+ async viaRouter(body) {
21857
+ if (!this.router.activePortsIncludes(this.target.port)) {
21858
+ throw Object.assign(
21859
+ new Error(`connect ECONNREFUSED ${this.target.hostname}:${this.target.port}`),
21860
+ { code: "ECONNREFUSED", errno: -61, syscall: "connect", address: this.target.hostname, port: this.target.port }
21861
+ );
21862
+ }
21863
+ const result = await this.router.request(this.target.port, {
21864
+ method: this.target.method,
21865
+ path: this.target.path,
21866
+ headers: this.getHeaders(),
21867
+ body: body.length ? new Uint8Array(body) : null
21868
+ });
21869
+ const body_ = result.body;
21870
+ const bytes2 = typeof body_ === "string" ? new TextEncoder().encode(body_) : body_ instanceof ArrayBuffer ? new Uint8Array(body_) : body_ ?? new Uint8Array();
21871
+ const status = result.statusCode ?? 200;
21872
+ return new VirtualClientResponse(status, result.statusMessage ?? STATUS_CODES[status] ?? "", lowercaseHeaders(result.headers), bytes2);
21873
+ }
21874
+ async viaFetch(body) {
21875
+ const fetchImpl = this.fetchImpl ?? globalThis.fetch;
21876
+ if (typeof fetchImpl !== "function") {
21877
+ throw Object.assign(new Error(`getaddrinfo ENOTFOUND ${this.target.hostname}`), { code: "ENOTFOUND" });
21878
+ }
21879
+ const portSuffix = this.target.protocol === "https:" && this.target.port === 443 || this.target.protocol === "http:" && this.target.port === 80 ? "" : `:${this.target.port}`;
21880
+ const url = `${this.target.protocol}//${this.target.hostname}${portSuffix}${this.target.path}`;
21881
+ const bodyless = this.target.method === "GET" || this.target.method === "HEAD";
21882
+ const headers = this.getHeaders();
21883
+ delete headers.host;
21884
+ delete headers.connection;
21885
+ delete headers["content-length"];
21886
+ const response = await fetchImpl(url, {
21887
+ method: this.target.method,
21888
+ headers,
21889
+ ...bodyless || body.length === 0 ? {} : { body: new Uint8Array(body) },
21890
+ redirect: "follow"
21891
+ });
21892
+ const received = {};
21893
+ response.headers.forEach((value, key) => {
21894
+ received[key.toLowerCase()] = value;
21895
+ });
21896
+ const bytes2 = new Uint8Array(await response.arrayBuffer());
21897
+ return new VirtualClientResponse(response.status, response.statusText, received, bytes2);
21898
+ }
21899
+ };
21900
+ function lowercaseHeaders(headers) {
21901
+ return Object.fromEntries(Object.entries(headers ?? {}).map(([key, value]) => [key.toLowerCase(), String(value)]));
21902
+ }
21903
+ function createHttpModule(router, owner, options = {}, defaultProtocol = "http:") {
21457
21904
  const createServer = (listener) => new VirtualHttpServer(router, owner, listener);
21458
- const module = {
21905
+ const request = (input, second, third) => {
21906
+ const overrides = second && typeof second === "object" && !(second instanceof Function) ? second : void 0;
21907
+ const callback = [second, third].find((value) => typeof value === "function");
21908
+ const req = new VirtualClientRequest(resolveTarget(input, overrides, defaultProtocol), router, options.fetch, options.trackRequest);
21909
+ if (callback) req.on("response", callback);
21910
+ return req;
21911
+ };
21912
+ const get = (input, second, third) => {
21913
+ const req = request(input, second, third);
21914
+ req.end();
21915
+ return req;
21916
+ };
21917
+ return {
21459
21918
  createServer,
21919
+ request,
21920
+ get,
21460
21921
  Server: VirtualHttpServer,
21461
21922
  ServerResponse: VirtualServerResponse,
21462
21923
  IncomingMessage: VirtualIncomingMessage,
21924
+ ClientRequest: VirtualClientRequest,
21463
21925
  METHODS,
21464
21926
  STATUS_CODES,
21465
21927
  maxHeaderSize: 16 * 1024,
@@ -21469,10 +21931,6 @@ function createHttpModule(router, owner) {
21469
21931
  Agent: class Agent {
21470
21932
  }
21471
21933
  };
21472
- return { ...module, request: unsupportedClient, get: unsupportedClient };
21473
- }
21474
- function unsupportedClient() {
21475
- throw Object.assign(new Error("http client requests are not implemented yet"), { code: "ERR_NOT_IMPLEMENTED" });
21476
21934
  }
21477
21935
  function validateHeaderName(name) {
21478
21936
  if (!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(name)) throw Object.assign(new TypeError(`Header name must be a valid HTTP token [${name}]`), { code: "ERR_INVALID_HTTP_TOKEN" });
@@ -21612,6 +22070,13 @@ var ChildProcess = class extends EventEmitter4__default.default {
21612
22070
  } catch {
21613
22071
  }
21614
22072
  done();
22073
+ },
22074
+ final: (done) => {
22075
+ try {
22076
+ handle.endStdin?.();
22077
+ } catch {
22078
+ }
22079
+ done();
21615
22080
  }
21616
22081
  });
21617
22082
  this.stdio = [this.stdin, this.stdout, this.stderr];
@@ -21651,7 +22116,7 @@ var ChildProcess = class extends EventEmitter4__default.default {
21651
22116
  queueMicrotask(() => this.emit("close", code, null));
21652
22117
  }
21653
22118
  };
21654
- function createChildProcessModule(spawnChild, defaultCwd) {
22119
+ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn) {
21655
22120
  const throughShell = (command, options) => {
21656
22121
  const shell = typeof options.shell === "string" ? options.shell : "/bin/sh";
21657
22122
  return { file: shell, args: ["-c", command] };
@@ -21708,20 +22173,79 @@ ${err.join("")}`),
21708
22173
  exec,
21709
22174
  execFile,
21710
22175
  fork: (modulePath, args = [], options = {}) => spawn("node", [modulePath, ...args], options),
21711
- execSync: unavailable("execSync"),
21712
- execFileSync: unavailable("execFileSync"),
21713
- spawnSync: unavailable("spawnSync"),
22176
+ ...buildSyncFamily(syncSpawn, throughShell, defaultCwd),
21714
22177
  ChildProcess
21715
22178
  };
21716
22179
  }
22180
+ function buildSyncFamily(syncSpawn, throughShell, defaultCwd) {
22181
+ if (!syncSpawn) {
22182
+ return {
22183
+ execSync: unavailable("execSync"),
22184
+ execFileSync: unavailable("execFileSync"),
22185
+ spawnSync: unavailable("spawnSync")
22186
+ };
22187
+ }
22188
+ const run = (file3, args, options) => {
22189
+ const resolved = options.shell ? throughShell([file3, ...args].join(" "), options) : { file: file3, args };
22190
+ const input = options.input === void 0 ? void 0 : typeof options.input === "string" ? options.input : new TextDecoder().decode(options.input);
22191
+ return syncSpawn({
22192
+ command: resolved.file,
22193
+ args: resolved.args,
22194
+ cwd: options.cwd ?? defaultCwd(),
22195
+ ...options.env ? { env: options.env } : {},
22196
+ ...input === void 0 ? {} : { input }
22197
+ });
22198
+ };
22199
+ const asOutput = (text2, options) => options.encoding === "buffer" || options.encoding === void 0 ? Buffer2.from(text2) : text2;
22200
+ const orThrow = (result, command, options) => {
22201
+ if (result.error) {
22202
+ throw Object.assign(new Error(result.error.message), {
22203
+ ...result.error.code ? { code: result.error.code } : {},
22204
+ stdout: asOutput(result.stdout, options),
22205
+ stderr: asOutput(result.stderr, options)
22206
+ });
22207
+ }
22208
+ if (result.status !== 0) {
22209
+ throw Object.assign(new Error(`Command failed: ${command}
22210
+ ${result.stderr}`), {
22211
+ status: result.status,
22212
+ stdout: asOutput(result.stdout, options),
22213
+ stderr: asOutput(result.stderr, options)
22214
+ });
22215
+ }
22216
+ return asOutput(result.stdout, options);
22217
+ };
22218
+ const spawnSync = (file3, args = [], options = {}) => {
22219
+ const [list, opts] = Array.isArray(args) ? [args, options] : [[], args];
22220
+ const result = run(file3, list, opts);
22221
+ return {
22222
+ pid: 0,
22223
+ status: result.status,
22224
+ signal: result.signal,
22225
+ stdout: asOutput(result.stdout, opts),
22226
+ stderr: asOutput(result.stderr, opts),
22227
+ output: [null, asOutput(result.stdout, opts), asOutput(result.stderr, opts)],
22228
+ ...result.error ? { error: Object.assign(new Error(result.error.message), result.error.code ? { code: result.error.code } : {}) } : {}
22229
+ };
22230
+ };
22231
+ const execFileSync = (file3, args = [], options = {}) => {
22232
+ const [list, opts] = Array.isArray(args) ? [args, options] : [[], args];
22233
+ return orThrow(run(file3, list, opts), [file3, ...list].join(" "), opts);
22234
+ };
22235
+ const execSync = (command, options = {}) => orThrow(run(command, [], { ...options, shell: options.shell ?? true }), command, options);
22236
+ return { spawnSync, execFileSync, execSync };
22237
+ }
21717
22238
  function normalize2(options, callback) {
21718
22239
  if (typeof options === "function") return [{}, options];
21719
22240
  return [options ?? {}, callback];
21720
22241
  }
21721
22242
  function unavailable(name) {
21722
- return () => {
22243
+ return (...args) => {
22244
+ const file3 = typeof args[0] === "string" ? args[0] : void 0;
22245
+ const list = Array.isArray(args[1]) ? args[1].map(String) : [];
22246
+ const command = file3 ? [file3, ...list].join(" ") : void 0;
21723
22247
  const error = new Error(
21724
- `child_process.${name} is not supported in the sandboxed runtime: it would have to block the JavaScript thread. Use the asynchronous form instead.`
22248
+ `child_process.${name} is not supported in the sandboxed runtime` + (command ? ` (tried to run: ${command})` : "") + `: the caller, the child and the event loop share one thread, so blocking the caller would also stop the child. Run it with the asynchronous form (spawn/exec/execFile), or run the command from the container shell.`
21725
22249
  );
21726
22250
  error.code = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
21727
22251
  throw error;
@@ -21785,7 +22309,11 @@ function parseKeys(input) {
21785
22309
  const name = CSI_KEYS[sequence];
21786
22310
  keys.push({
21787
22311
  sequence: `\x1B${sequence}`,
21788
- name: name ?? "undefined",
22312
+ /* Node leaves this undefined for a sequence it does not recognise.
22313
+ * The string "undefined" is not the same thing: a caller testing
22314
+ * `key.name === undefined` would take an unknown key for a known
22315
+ * one. */
22316
+ name,
21789
22317
  ctrl: false,
21790
22318
  /* xterm reports modifiers as `;5` (control) and `;2` (shift) before
21791
22319
  * the final letter. */
@@ -21850,7 +22378,7 @@ function emitKeypressEvents(stream) {
21850
22378
  }
21851
22379
  });
21852
22380
  }
21853
- function createReadlineModule(defaultInput) {
22381
+ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
21854
22382
  class Interface extends EventEmitter4__default.default {
21855
22383
  constructor(input, output, terminal = false) {
21856
22384
  super();
@@ -21861,6 +22389,14 @@ function createReadlineModule(defaultInput) {
21861
22389
  if (terminal) {
21862
22390
  emitKeypressEvents(input);
21863
22391
  input?.on?.("keypress", this.onTerminalKeypress);
22392
+ if (typeof input?.setRawMode === "function") {
22393
+ try {
22394
+ input.setRawMode(true);
22395
+ this.ownsRawMode = true;
22396
+ } catch {
22397
+ }
22398
+ }
22399
+ input?.resume?.();
21864
22400
  } else {
21865
22401
  input?.on?.("data", (chunk) => this.receive(String(chunk)));
21866
22402
  }
@@ -21872,6 +22408,10 @@ function createReadlineModule(defaultInput) {
21872
22408
  buffer = "";
21873
22409
  pending = [];
21874
22410
  lines = [];
22411
+ /** Set while `question` is waiting, so the echoed line carries the query. */
22412
+ questionPrompt = null;
22413
+ /** Raw mode is only ours to restore if we were the one to enable it. */
22414
+ ownsRawMode = false;
21875
22415
  line = "";
21876
22416
  cursor = 0;
21877
22417
  terminal;
@@ -21879,20 +22419,31 @@ function createReadlineModule(defaultInput) {
21879
22419
  /** Split incoming data into lines, answering any waiting `question`. */
21880
22420
  receive(text2) {
21881
22421
  this.buffer += text2;
21882
- let newline = this.buffer.indexOf("\n");
21883
- while (newline >= 0) {
21884
- const line = this.buffer.slice(0, newline).replace(/\r$/, "");
21885
- this.buffer = this.buffer.slice(newline + 1);
21886
- const waiting = this.pending.shift();
21887
- if (waiting) waiting(line);
21888
- else {
21889
- this.lines.push(line);
21890
- this.emit("line", line);
21891
- }
21892
- newline = this.buffer.indexOf("\n");
22422
+ let match2 = /\r\n|\r|\n/.exec(this.buffer);
22423
+ while (match2) {
22424
+ const line = this.buffer.slice(0, match2.index);
22425
+ this.buffer = this.buffer.slice(match2.index + match2[0].length);
22426
+ this.deliver(line);
22427
+ match2 = /\r\n|\r|\n/.exec(this.buffer);
22428
+ }
22429
+ }
22430
+ /** Hand a completed line to whoever is waiting for it. */
22431
+ deliver(line) {
22432
+ const waiting = this.pending.shift();
22433
+ if (waiting) {
22434
+ this.questionPrompt = null;
22435
+ waiting(line);
22436
+ return;
21893
22437
  }
22438
+ this.lines.push(line);
22439
+ this.emit("line", line);
21894
22440
  }
21895
22441
  question(query, callback) {
22442
+ if (this.terminal) {
22443
+ this.questionPrompt = query;
22444
+ this.line = "";
22445
+ this.cursor = 0;
22446
+ }
21896
22447
  this.output?.write?.(query);
21897
22448
  if (callback) {
21898
22449
  this.pending.push(callback);
@@ -21907,6 +22458,9 @@ function createReadlineModule(defaultInput) {
21907
22458
  setPrompt(text2) {
21908
22459
  this.promptText = text2;
21909
22460
  }
22461
+ getPrompt() {
22462
+ return this.promptText;
22463
+ }
21910
22464
  pause() {
21911
22465
  this.input?.pause?.();
21912
22466
  return this;
@@ -21934,21 +22488,48 @@ function createReadlineModule(defaultInput) {
21934
22488
  if (text2) {
21935
22489
  this.line = this.line.slice(0, this.cursor) + text2 + this.line.slice(this.cursor);
21936
22490
  this.cursor += text2.length;
22491
+ this.refresh();
21937
22492
  }
21938
22493
  }
21939
22494
  getCursorPos() {
21940
- return { rows: 0, cols: this.cursor + this.promptText.length };
22495
+ return { rows: 0, cols: this.cursor + this.currentPrompt().length };
21941
22496
  }
21942
22497
  close() {
21943
22498
  if (this.closed) return;
21944
22499
  this.closed = true;
21945
22500
  if (this.terminal) {
21946
22501
  this.input?.off?.("keypress", this.onTerminalKeypress);
22502
+ if (this.ownsRawMode) {
22503
+ this.ownsRawMode = false;
22504
+ try {
22505
+ this.input?.setRawMode?.(false);
22506
+ } catch {
22507
+ }
22508
+ }
21947
22509
  this.input?.pause?.();
21948
22510
  }
21949
22511
  for (const waiting of this.pending.splice(0)) waiting("");
21950
22512
  this.emit("close");
21951
22513
  }
22514
+ /** The text shown to the left of the edited line. */
22515
+ currentPrompt() {
22516
+ return this.questionPrompt ?? this.promptText;
22517
+ }
22518
+ /**
22519
+ * Repaint the edited line.
22520
+ *
22521
+ * In raw mode nothing echoes on its own, so an Interface that does not do
22522
+ * this leaves the user typing blind. Libraries that draw their own frame
22523
+ * (clack, inquirer) simply overwrite this, exactly as they do on Node.
22524
+ */
22525
+ refresh() {
22526
+ if (!this.terminal) return;
22527
+ const prompt = this.currentPrompt();
22528
+ let text2 = `\r\x1B[0K${prompt}${this.line}`;
22529
+ const back = this.line.length - this.cursor;
22530
+ if (back > 0) text2 += `\x1B[${back}D`;
22531
+ this.output?.write?.(text2);
22532
+ }
21952
22533
  edit(sequence, key) {
21953
22534
  if (this.closed) return;
21954
22535
  const name = key.name;
@@ -21956,48 +22537,62 @@ function createReadlineModule(defaultInput) {
21956
22537
  const value = this.line;
21957
22538
  this.line = "";
21958
22539
  this.cursor = 0;
21959
- this.emit("line", value);
22540
+ this.output?.write?.("\r\n");
22541
+ this.deliver(value);
21960
22542
  return;
21961
22543
  }
21962
22544
  if (name === "left") {
21963
22545
  this.cursor = Math.max(0, this.cursor - 1);
22546
+ this.refresh();
21964
22547
  return;
21965
22548
  }
21966
22549
  if (name === "right") {
21967
22550
  this.cursor = Math.min(this.line.length, this.cursor + 1);
22551
+ this.refresh();
21968
22552
  return;
21969
22553
  }
21970
22554
  if (name === "home" || key.ctrl && name === "a") {
21971
22555
  this.cursor = 0;
22556
+ this.refresh();
21972
22557
  return;
21973
22558
  }
21974
22559
  if (name === "end" || key.ctrl && name === "e") {
21975
22560
  this.cursor = this.line.length;
22561
+ this.refresh();
21976
22562
  return;
21977
22563
  }
21978
22564
  if (name === "backspace" || key.ctrl && name === "h") {
21979
22565
  if (this.cursor > 0) {
21980
22566
  this.line = this.line.slice(0, this.cursor - 1) + this.line.slice(this.cursor);
21981
22567
  this.cursor--;
22568
+ this.refresh();
21982
22569
  }
21983
22570
  return;
21984
22571
  }
21985
22572
  if (name === "delete") {
21986
- if (this.cursor < this.line.length) this.line = this.line.slice(0, this.cursor) + this.line.slice(this.cursor + 1);
22573
+ if (this.cursor < this.line.length) {
22574
+ this.line = this.line.slice(0, this.cursor) + this.line.slice(this.cursor + 1);
22575
+ this.refresh();
22576
+ }
21987
22577
  return;
21988
22578
  }
21989
22579
  if (key.ctrl && name === "u") {
21990
22580
  this.line = this.line.slice(this.cursor);
21991
22581
  this.cursor = 0;
22582
+ this.refresh();
21992
22583
  return;
21993
22584
  }
21994
22585
  if (key.ctrl && name === "k") {
21995
22586
  this.line = this.line.slice(0, this.cursor);
22587
+ this.refresh();
21996
22588
  return;
21997
22589
  }
21998
22590
  if (key.ctrl || key.meta || sequence.length !== 1 || sequence < " ") return;
22591
+ const atEnd = this.cursor === this.line.length;
21999
22592
  this.line = this.line.slice(0, this.cursor) + sequence + this.line.slice(this.cursor);
22000
22593
  this.cursor += sequence.length;
22594
+ if (atEnd) this.output?.write?.(sequence);
22595
+ else this.refresh();
22001
22596
  }
22002
22597
  async *[Symbol.asyncIterator]() {
22003
22598
  while (!this.closed || this.lines.length) {
@@ -22027,13 +22622,23 @@ function createReadlineModule(defaultInput) {
22027
22622
  }
22028
22623
  }
22029
22624
  }
22625
+ const isTerminal = (options, output) => {
22626
+ if (typeof options?.terminal === "boolean") return options.terminal;
22627
+ return Boolean(output?.isTTY);
22628
+ };
22030
22629
  const createInterface = (options, output) => {
22031
22630
  if (options && typeof options === "object" && !options.on) {
22032
- const instance = new Interface(options.input ?? defaultInput(), options.output, options.terminal === true);
22631
+ const resolvedOutput2 = options.output ?? defaultOutput();
22632
+ const instance = new Interface(
22633
+ options.input ?? defaultInput(),
22634
+ resolvedOutput2,
22635
+ isTerminal(options, resolvedOutput2)
22636
+ );
22033
22637
  if (typeof options.prompt === "string") instance.setPrompt(options.prompt);
22034
22638
  return instance;
22035
22639
  }
22036
- return new Interface(options ?? defaultInput(), output, Boolean(output));
22640
+ const resolvedOutput = output ?? defaultOutput();
22641
+ return new Interface(options ?? defaultInput(), resolvedOutput, isTerminal(void 0, resolvedOutput));
22037
22642
  };
22038
22643
  const noop = () => {
22039
22644
  };
@@ -22050,6 +22655,7 @@ function createReadlineModule(defaultInput) {
22050
22655
  }
22051
22656
 
22052
22657
  // src/runtime/core-modules.ts
22658
+ installReadableFrom(streamModule4__default.default);
22053
22659
  var Dirent = class {
22054
22660
  /** Node 20+ exposes the containing directory, and `fs.glob` consumers read it. */
22055
22661
  constructor(name, stat2, parentPath = "") {
@@ -22255,9 +22861,20 @@ function createCoreModules(options) {
22255
22861
  throw new Error("createRequire is only available inside a loaded module");
22256
22862
  }
22257
22863
  };
22258
- const http = options.http ? createHttpModule(options.http.router, options.http.owner) : createUnsupportedModule("http");
22259
- const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd) : createUnsupportedModule("child_process");
22260
- const readline = createReadlineModule(() => processObject.stdin);
22864
+ let inFlightRequests = 0;
22865
+ const httpOptions = {
22866
+ ...options.http?.fetch ? { fetch: options.http.fetch } : {},
22867
+ trackRequest: () => {
22868
+ inFlightRequests++;
22869
+ return () => {
22870
+ inFlightRequests--;
22871
+ };
22872
+ }
22873
+ };
22874
+ const http = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "http:") : createUnsupportedModule("http");
22875
+ 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");
22877
+ const readline = createReadlineModule(() => processObject.stdin, () => processObject.stdout);
22261
22878
  const dns = createDnsModule();
22262
22879
  const builtins = {
22263
22880
  assert: assert_module_default,
@@ -22275,7 +22892,7 @@ function createCoreModules(options) {
22275
22892
  "fs/promises": fs.promises,
22276
22893
  module: moduleBuiltin,
22277
22894
  http,
22278
- https: http,
22895
+ https,
22279
22896
  os,
22280
22897
  path,
22281
22898
  "path/posix": path,
@@ -22315,6 +22932,9 @@ function createCoreModules(options) {
22315
22932
  process: processObject,
22316
22933
  pendingHandles: timers.pending,
22317
22934
  pendingUnrefed: timers.pendingUnrefed,
22935
+ /** Client requests sent but not yet read to completion. */
22936
+ pendingRequests: () => inFlightRequests,
22937
+ cancelTimers: timers.cancelAll,
22318
22938
  writeStdin: (data) => {
22319
22939
  if (options.interactiveStdin) stdin.write(data);
22320
22940
  },
@@ -22542,9 +23162,20 @@ function createFsModule(volume, cwd, stdinPath) {
22542
23162
  if (position == null) file3.position = start2 + chunk.length;
22543
23163
  return chunk.length;
22544
23164
  },
22545
- createReadStream: (path) => {
22546
- const data = Buffer2.from(volume.readFileSync(resolveLinks(volume, abs(path))));
22547
- return streamModule4__default.default.Readable.from([data]);
23165
+ createReadStream: (path, options) => {
23166
+ const target = abs(path);
23167
+ const settings = typeof options === "string" ? { encoding: options } : options ?? {};
23168
+ const whole = Buffer2.from(volume.readFileSync(resolveLinks(volume, target)));
23169
+ const start2 = settings.start ?? 0;
23170
+ const end = settings.end === void 0 ? whole.length : Math.min(settings.end + 1, whole.length);
23171
+ const slice = whole.subarray(start2, Math.max(start2, end));
23172
+ const stream = streamModule4__default.default.Readable.from([settings.encoding ? slice.toString(settings.encoding) : slice]);
23173
+ Object.assign(stream, { path: target, bytesRead: slice.length, close: () => stream.destroy() });
23174
+ queueMicrotask(() => {
23175
+ stream.emit("open", 0);
23176
+ stream.emit("ready");
23177
+ });
23178
+ return stream;
22548
23179
  },
22549
23180
  createWriteStream: (path, options) => {
22550
23181
  const target = abs(path);
@@ -22985,6 +23616,27 @@ function createTrackedTimers() {
22985
23616
  live.delete(handle);
22986
23617
  native(handle);
22987
23618
  };
23619
+ const cancelAll = () => {
23620
+ for (const handle of [...live, ...unrefed]) {
23621
+ const state = states.get(handle);
23622
+ if (!state) continue;
23623
+ state.active = false;
23624
+ try {
23625
+ clearTimeout(state.native);
23626
+ } catch {
23627
+ }
23628
+ try {
23629
+ clearInterval(state.native);
23630
+ } catch {
23631
+ }
23632
+ try {
23633
+ (globalThis.clearImmediate ?? clearTimeout)(state.native);
23634
+ } catch {
23635
+ }
23636
+ }
23637
+ live.clear();
23638
+ unrefed.clear();
23639
+ };
22988
23640
  return {
22989
23641
  api: {
22990
23642
  setTimeout: setTimeoutTracked,
@@ -22995,7 +23647,8 @@ function createTrackedTimers() {
22995
23647
  clearImmediate: (handle) => clear2(handle, globalThis.clearImmediate ?? clearTimeout)
22996
23648
  },
22997
23649
  pending: () => live.size,
22998
- pendingUnrefed: () => unrefed.size
23650
+ pendingUnrefed: () => unrefed.size,
23651
+ cancelAll
22999
23652
  };
23000
23653
  }
23001
23654
  function createTimerPromises() {
@@ -23449,22 +24102,209 @@ var MemoryVolume = class {
23449
24102
  }
23450
24103
  };
23451
24104
 
23452
- // src/runtime/host-esbuild.ts
23453
- var ASYNC_API = ["transform", "build", "context", "formatMessages", "analyzeMetafile"];
23454
- function createHostEsbuild() {
23455
- let loading = null;
23456
- let started = null;
23457
- const ensure = async () => {
23458
- loading ??= start().then((api) => started = api);
23459
- return loading;
23460
- };
23461
- const module = new Proxy({}, {
23462
- get(_target, key) {
23463
- if (typeof key !== "string") return void 0;
23464
- if (ASYNC_API.includes(key)) {
23465
- return async (...args) => {
23466
- const api = await ensure();
23467
- if (!api) throw esbuildUnavailable();
24105
+ // src/runtime/mirroring-volume.ts
24106
+ init_path();
24107
+ var MirroringVolume = class {
24108
+ constructor(inner = new MemoryVolume()) {
24109
+ this.inner = inner;
24110
+ }
24111
+ inner;
24112
+ mirror;
24113
+ root = "/";
24114
+ /**
24115
+ * Start mirroring the tree under `root`, seeding it with what is there now.
24116
+ *
24117
+ * Called once the Rolldown binding is known to be in play; before that a
24118
+ * container pays nothing for this.
24119
+ */
24120
+ attach(mirror, root) {
24121
+ this.mirror = mirror;
24122
+ this.root = clean(root);
24123
+ this.seed();
24124
+ }
24125
+ detach() {
24126
+ this.mirror = void 0;
24127
+ }
24128
+ /** Copy the whole subtree across. The only bulk operation that remains. */
24129
+ seed() {
24130
+ const mirror = this.mirror;
24131
+ if (!mirror) return;
24132
+ try {
24133
+ mirror.rmSync?.(this.root, { recursive: true, force: true });
24134
+ } catch {
24135
+ }
24136
+ const copy = (path) => {
24137
+ let stat2;
24138
+ try {
24139
+ stat2 = this.inner.lstatSync(path);
24140
+ } catch {
24141
+ return;
24142
+ }
24143
+ if (stat2.isDirectory()) {
24144
+ this.safely(() => mirror.mkdirSync(path, { recursive: true }));
24145
+ for (const name of this.inner.readdirSync(path)) copy(join(path, name));
24146
+ } else if (stat2.isSymbolicLink()) {
24147
+ this.safely(() => {
24148
+ mirror.mkdirSync(dirname(path), { recursive: true });
24149
+ mirror.symlinkSync(this.inner.readlinkSync(path), path);
24150
+ });
24151
+ } else if (!path.endsWith(".node")) {
24152
+ this.safely(() => {
24153
+ mirror.mkdirSync(dirname(path), { recursive: true });
24154
+ mirror.writeFileSync(path, this.inner.readFileSync(path));
24155
+ });
24156
+ }
24157
+ };
24158
+ copy(this.root);
24159
+ }
24160
+ /** Is this path inside the mirrored subtree? */
24161
+ mirrored(path) {
24162
+ if (!this.mirror) return false;
24163
+ const clean2 = clean(path);
24164
+ return this.root === "/" || clean2 === this.root || clean2.startsWith(`${this.root}/`);
24165
+ }
24166
+ /**
24167
+ * Run a mirror update, swallowing failure.
24168
+ *
24169
+ * The mirror is a read-only cache for another engine. If it rejects
24170
+ * something — an unsupported operation, a path it has not seen — the
24171
+ * container's own write has still happened and must still succeed.
24172
+ */
24173
+ safely(update) {
24174
+ try {
24175
+ update();
24176
+ } catch {
24177
+ }
24178
+ }
24179
+ /** Push a path's current state across, whatever it is now. */
24180
+ sync(path) {
24181
+ const mirror = this.mirror;
24182
+ if (!mirror || !this.mirrored(path)) return;
24183
+ let stat2;
24184
+ try {
24185
+ stat2 = this.inner.lstatSync(path);
24186
+ } catch {
24187
+ this.safely(() => {
24188
+ if (mirror.rmSync) mirror.rmSync(path, { recursive: true, force: true });
24189
+ else mirror.unlinkSync?.(path);
24190
+ });
24191
+ return;
24192
+ }
24193
+ if (stat2.isDirectory()) {
24194
+ this.safely(() => mirror.mkdirSync(path, { recursive: true }));
24195
+ return;
24196
+ }
24197
+ if (stat2.isSymbolicLink()) {
24198
+ this.safely(() => {
24199
+ mirror.mkdirSync(dirname(path), { recursive: true });
24200
+ mirror.symlinkSync(this.inner.readlinkSync(path), path);
24201
+ });
24202
+ return;
24203
+ }
24204
+ if (path.endsWith(".node")) return;
24205
+ this.safely(() => {
24206
+ mirror.mkdirSync(dirname(path), { recursive: true });
24207
+ mirror.writeFileSync(path, this.inner.readFileSync(path));
24208
+ });
24209
+ }
24210
+ // ── reads: straight through ───────────────────────────────────────────────
24211
+ readFileSync(path) {
24212
+ return this.inner.readFileSync(path);
24213
+ }
24214
+ readdirSync(path) {
24215
+ return this.inner.readdirSync(path);
24216
+ }
24217
+ lstatSync(path) {
24218
+ return this.inner.lstatSync(path);
24219
+ }
24220
+ readlinkSync(path) {
24221
+ return this.inner.readlinkSync(path);
24222
+ }
24223
+ getStats() {
24224
+ return this.inner.getStats();
24225
+ }
24226
+ // ── writes: applied, then mirrored ────────────────────────────────────────
24227
+ writeFileSync(path, data) {
24228
+ this.inner.writeFileSync(path, data);
24229
+ this.sync(path);
24230
+ }
24231
+ appendFileSync(path, data) {
24232
+ this.inner.appendFileSync(path, data);
24233
+ this.sync(path);
24234
+ }
24235
+ mkdirSync(path, options) {
24236
+ this.inner.mkdirSync(path, options);
24237
+ this.sync(path);
24238
+ }
24239
+ rmdirSync(path) {
24240
+ this.inner.rmdirSync(path);
24241
+ this.sync(path);
24242
+ }
24243
+ unlinkSync(path) {
24244
+ this.inner.unlinkSync(path);
24245
+ this.sync(path);
24246
+ }
24247
+ renameSync(from, to) {
24248
+ this.inner.renameSync(from, to);
24249
+ this.sync(from);
24250
+ this.sync(to);
24251
+ }
24252
+ symlinkSync(target, path) {
24253
+ this.inner.symlinkSync(target, path);
24254
+ this.sync(path);
24255
+ }
24256
+ linkSync(existing, path) {
24257
+ this.inner.linkSync(existing, path);
24258
+ this.sync(path);
24259
+ }
24260
+ truncateSync(path, length) {
24261
+ this.inner.truncateSync(path, length);
24262
+ this.sync(path);
24263
+ }
24264
+ /* Permissions and timestamps do not change what a bundler resolves, and
24265
+ * `memfs` is stricter about them than this volume is. Applied here only. */
24266
+ chmodSync(path, mode) {
24267
+ this.inner.chmodSync(path, mode);
24268
+ }
24269
+ lchmodSync(path, mode) {
24270
+ this.inner.lchmodSync(path, mode);
24271
+ }
24272
+ chownSync(path, uid, gid) {
24273
+ this.inner.chownSync(path, uid, gid);
24274
+ }
24275
+ lchownSync(path, uid, gid) {
24276
+ this.inner.lchownSync(path, uid, gid);
24277
+ }
24278
+ utimesSync(path, atime, mtime) {
24279
+ this.inner.utimesSync(path, atime, mtime);
24280
+ }
24281
+ // ── snapshots ─────────────────────────────────────────────────────────────
24282
+ snapshot() {
24283
+ return this.inner.snapshot();
24284
+ }
24285
+ /** A restore replaces everything, so the mirror is rebuilt rather than patched. */
24286
+ restore(entries) {
24287
+ this.inner.restore(entries);
24288
+ this.seed();
24289
+ }
24290
+ };
24291
+
24292
+ // src/runtime/host-esbuild.ts
24293
+ var ASYNC_API = ["transform", "build", "context", "formatMessages", "analyzeMetafile"];
24294
+ function createHostEsbuild() {
24295
+ let loading = null;
24296
+ let started = null;
24297
+ const ensure = async () => {
24298
+ loading ??= start().then((api) => started = api);
24299
+ return loading;
24300
+ };
24301
+ const module = new Proxy({}, {
24302
+ get(_target, key) {
24303
+ if (typeof key !== "string") return void 0;
24304
+ if (ASYNC_API.includes(key)) {
24305
+ return async (...args) => {
24306
+ const api = await ensure();
24307
+ if (!api) throw esbuildUnavailable();
23468
24308
  return api[key](...args);
23469
24309
  };
23470
24310
  }
@@ -23518,6 +24358,7 @@ function ensureProcessGlobal() {
23518
24358
  }
23519
24359
 
23520
24360
  // src/runtime/host-rolldown.ts
24361
+ var BUNDLER_HINT = "If this is a bundler pre-bundling the worker away, exclude the binding from dependency optimisation \u2014 in Vite:\n\n optimizeDeps: { exclude: ['@rolldown/binding-wasm32-wasi'] }";
23521
24362
  async function loadHostRolldownBinding() {
23522
24363
  if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) {
23523
24364
  throw new Error(
@@ -23531,7 +24372,13 @@ async function loadHostRolldownBinding() {
23531
24372
  if (typeof process !== "undefined" && process.env?.SANDBOXEDJS_DEBUG) {
23532
24373
  console.error("[sandboxedjs] Rolldown WASI binding unavailable:", error);
23533
24374
  }
23534
- throw new Error("The optional Rolldown WASI binding could not be loaded.", { cause: error });
24375
+ const reason = error instanceof Error ? error.message : String(error);
24376
+ throw new Error(
24377
+ `The optional Rolldown WASI binding could not be loaded: ${reason}` + (typeof window === "undefined" ? "" : `
24378
+
24379
+ ${BUNDLER_HINT}`),
24380
+ { cause: error }
24381
+ );
23535
24382
  }
23536
24383
  }
23537
24384
 
@@ -23571,6 +24418,7 @@ var LocalProcess = class extends EventEmitter4__default.default {
23571
24418
  inputEnded = false;
23572
24419
  pendingInput = [];
23573
24420
  resolveKilled;
24421
+ cleanup;
23574
24422
  killedPromise = new Promise((resolve2) => {
23575
24423
  this.resolveKilled = resolve2;
23576
24424
  });
@@ -23609,13 +24457,32 @@ var LocalProcess = class extends EventEmitter4__default.default {
23609
24457
  for (const chunk of this.pendingInput.splice(0)) input(chunk);
23610
24458
  if (this.inputEnded) end();
23611
24459
  }
24460
+ /**
24461
+ * End the process now.
24462
+ *
24463
+ * Killing has to settle `completion`, not merely record the intent. A
24464
+ * program with an interval outstanding has work pending forever, so the
24465
+ * runtime's own idle check will never end it — before this, killing such a
24466
+ * process left the caller awaiting a promise that could not resolve.
24467
+ */
23612
24468
  kill(_signal = "SIGTERM") {
24469
+ if (this.killed) return;
23613
24470
  this.killed = true;
23614
24471
  this.resolveKilled();
24472
+ this.cleanup?.();
24473
+ this.finish(137);
23615
24474
  }
23616
24475
  waitForKill() {
23617
24476
  return this.killedPromise;
23618
24477
  }
24478
+ isKilled() {
24479
+ return this.killed;
24480
+ }
24481
+ /** Registered by the task so a kill can release what the program still holds. */
24482
+ onKill(cleanup) {
24483
+ this.cleanup = cleanup;
24484
+ if (this.killed) cleanup();
24485
+ }
23619
24486
  /** End the process now, for an asynchronous `process.exit`. */
23620
24487
  exitNow(code) {
23621
24488
  this.finish(code);
@@ -23661,6 +24528,11 @@ var PodChildProcess = class {
23661
24528
  started = false;
23662
24529
  cancelled = false;
23663
24530
  child;
24531
+ /* The child is launched asynchronously, but a parent may write and close
24532
+ * its input synchronously right after spawning. Holding both until the
24533
+ * child exists is what keeps `echo hi | child` from losing the `hi`. */
24534
+ pendingInput = "";
24535
+ inputEnded = false;
23664
24536
  on(event, listener) {
23665
24537
  let set = this.listeners.get(event);
23666
24538
  if (!set) this.listeners.set(event, set = /* @__PURE__ */ new Set());
@@ -23683,6 +24555,11 @@ var PodChildProcess = class {
23683
24555
  (child) => {
23684
24556
  this.child = child;
23685
24557
  if (this.cancelled) child.kill();
24558
+ if (this.pendingInput !== "") {
24559
+ child.write(this.pendingInput);
24560
+ this.pendingInput = "";
24561
+ }
24562
+ if (this.inputEnded) child.endInput?.();
23686
24563
  child.on("output", (text2) => {
23687
24564
  this.stdout += text2;
23688
24565
  this.emit("stdout", text2);
@@ -23705,7 +24582,13 @@ var PodChildProcess = class {
23705
24582
  });
23706
24583
  }
23707
24584
  sendStdin(data) {
23708
- this.child?.write(data);
24585
+ if (this.child) this.child.write(data);
24586
+ else this.pendingInput += data;
24587
+ }
24588
+ /** Signal EOF, so a child reading stdin to the end can finish. */
24589
+ endStdin() {
24590
+ if (this.child) this.child.endInput?.();
24591
+ else this.inputEnded = true;
23709
24592
  }
23710
24593
  kill(signal = "SIGTERM") {
23711
24594
  if (this.child) this.child.kill(signal);
@@ -23761,7 +24644,10 @@ function removeEscapedErrorReporting() {
23761
24644
  }
23762
24645
  var DRAIN_TURNS = 4;
23763
24646
  var LocalRuntimePod = class _LocalRuntimePod {
23764
- volume = new MemoryVolume();
24647
+ /* Wrapped so that a foreign filesystem — Rolldown's WebAssembly memfs — can
24648
+ * be kept in step as writes happen, rather than deep-copied once per spawn.
24649
+ * With nothing attached this is a straight pass-through. */
24650
+ volume = new MirroringVolume(new MemoryVolume());
23765
24651
  packages;
23766
24652
  instanceId = `sbx-${Math.random().toString(36).slice(2)}`;
23767
24653
  router = new VirtualHttpRouter();
@@ -23788,6 +24674,8 @@ var LocalRuntimePod = class _LocalRuntimePod {
23788
24674
  modules;
23789
24675
  esbuild;
23790
24676
  rolldownBinding;
24677
+ /** Backs outbound `http`/`https` client requests from inside the sandbox. */
24678
+ fetch;
23791
24679
  constructor(options) {
23792
24680
  ensureProcessGlobal();
23793
24681
  this.workdir = options.workdir ?? "/";
@@ -23802,6 +24690,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
23802
24690
  const notify = options.onServerReady;
23803
24691
  this.router.onListen = (port) => notify(port, `http://localhost:${port}`);
23804
24692
  }
24693
+ if (options.fetch) this.fetch = options.fetch;
23805
24694
  this.packages = new CleanPackageInstaller(this.volume, {
23806
24695
  cwd: this.workdir,
23807
24696
  ...options.registry ? { registry: options.registry } : {},
@@ -23841,7 +24730,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
23841
24730
  proc.exitNow(code);
23842
24731
  if (engine?.isEvaluating) throw new ProcessExit(code);
23843
24732
  },
23844
- http: { router: this.router, owner },
24733
+ http: { router: this.router, owner, ...this.fetch ? { fetch: this.fetch } : {} },
23845
24734
  spawnChild: (config) => this.processManager.spawn(config),
23846
24735
  ...typeof options.stdinPath === "string" ? { stdinPath: options.stdinPath } : {},
23847
24736
  ...options.interactiveStdin ? { interactiveStdin: true } : {},
@@ -23849,6 +24738,10 @@ var LocalRuntimePod = class _LocalRuntimePod {
23849
24738
  onRawMode: (enabled) => proc.emit("rawmode", enabled)
23850
24739
  });
23851
24740
  proc.acceptInput((data) => core.writeStdin(data), () => core.endStdin());
24741
+ proc.onKill(() => {
24742
+ core.cancelTimers();
24743
+ this.router.closeOwner(owner);
24744
+ });
23852
24745
  engine = new CommonJsEngine(this.volume, {
23853
24746
  cwd,
23854
24747
  builtins: core.builtins,
@@ -23858,7 +24751,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
23858
24751
  });
23859
24752
  try {
23860
24753
  await engine.run(script);
23861
- await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin);
24754
+ await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin, core.pendingRequests, () => proc.isKilled());
23862
24755
  if (this.router.activePorts(owner).length) {
23863
24756
  await proc.waitForKill();
23864
24757
  return 137;
@@ -23879,13 +24772,13 @@ var LocalRuntimePod = class _LocalRuntimePod {
23879
24772
  */
23880
24773
  async prepareRolldown(cwd, env2) {
23881
24774
  const specifier = "@rolldown/binding-wasm32-wasi";
23882
- if (!this.modules[specifier] && packageInstalled(this.volume, cwd, "rolldown")) {
24775
+ if (!this.modules[specifier] && packageIsInstalled(this.volume, cwd, "rolldown")) {
23883
24776
  this.rolldownBinding ??= loadHostRolldownBinding();
23884
24777
  const binding = await this.rolldownBinding;
23885
24778
  if (binding) this.modules[specifier] = binding;
23886
24779
  }
23887
24780
  if (this.modules[specifier]) {
23888
- syncRolldownFileSystem(this.modules[specifier], this.volume, cwd);
24781
+ attachRolldownMirror(this.modules[specifier], this.volume, cwd);
23889
24782
  env2.NAPI_RS_FORCE_WASI ??= "true";
23890
24783
  }
23891
24784
  }
@@ -23904,18 +24797,18 @@ var LocalRuntimePod = class _LocalRuntimePod {
23904
24797
  * A plain script that has genuinely finished falls straight through both,
23905
24798
  * costing a handful of empty turns.
23906
24799
  */
23907
- async settle(owner, pendingHandles, pendingUnrefed, readingStdin) {
24800
+ async settle(owner, pendingHandles, pendingUnrefed, readingStdin, pendingRequests, killed) {
23908
24801
  for (let turn = 0; turn < DRAIN_TURNS; turn++) {
23909
- if (this.router.activePorts(owner).length) return;
24802
+ if (this.router.activePorts(owner).length || killed()) return;
23910
24803
  await new Promise((resolve2) => setTimeout(resolve2, 0));
23911
24804
  }
23912
- if (pendingHandles() === 0 && pendingUnrefed() > 0 && !readingStdin()) {
24805
+ if (pendingHandles() === 0 && pendingRequests() === 0 && pendingUnrefed() > 0 && !readingStdin()) {
23913
24806
  const deadline = Date.now() + 1e3;
23914
- while (!this.router.activePorts(owner).length && Date.now() < deadline) {
24807
+ while (!this.router.activePorts(owner).length && !killed() && Date.now() < deadline) {
23915
24808
  await new Promise((resolve2) => setTimeout(resolve2, 5));
23916
24809
  }
23917
24810
  }
23918
- while (!this.router.activePorts(owner).length && (pendingHandles() > 0 || readingStdin())) {
24811
+ while (!this.router.activePorts(owner).length && !killed() && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
23919
24812
  await new Promise((resolve2) => setTimeout(resolve2, 5));
23920
24813
  }
23921
24814
  }
@@ -23967,7 +24860,7 @@ function formatError(error) {
23967
24860
  function isRecord(value) {
23968
24861
  return typeof value === "object" && value !== null && !Array.isArray(value);
23969
24862
  }
23970
- function packageInstalled(volume, cwd, wanted) {
24863
+ function packageIsInstalled(volume, cwd, wanted) {
23971
24864
  for (let dir3 = clean(cwd); ; dir3 = dirname(dir3)) {
23972
24865
  if (packageTreeContains(volume, join(dir3, "node_modules"), wanted, /* @__PURE__ */ new Set())) return true;
23973
24866
  if (dir3 === "/") return false;
@@ -23996,30 +24889,565 @@ function directory(volume, path) {
23996
24889
  return false;
23997
24890
  }
23998
24891
  }
23999
- function syncRolldownFileSystem(binding, volume, root) {
24892
+ function attachRolldownMirror(binding, volume, root) {
24000
24893
  const fs = binding?.__fs;
24001
24894
  if (!fs?.mkdirSync || !fs?.writeFileSync) return;
24002
- try {
24003
- fs.rmSync?.(root, { recursive: true, force: true });
24004
- } catch {
24895
+ volume.attach(fs, root);
24896
+ }
24897
+
24898
+ // src/runtime/sync-channel.ts
24899
+ var STATE = 0;
24900
+ var LENGTH = 1;
24901
+ var MORE = 2;
24902
+ var CONTROL_WORDS = 4;
24903
+ var STATE_REQUEST = 1;
24904
+ var STATE_RESPONSE = 2;
24905
+ var STATE_CONTINUE = 3;
24906
+ var STATE_CLOSED = 4;
24907
+ function createSyncChannelBuffers(capacityBytes = 1 << 20) {
24908
+ return {
24909
+ control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
24910
+ data: new SharedArrayBuffer(capacityBytes)
24911
+ };
24912
+ }
24913
+ function syncChannelSupported() {
24914
+ if (typeof SharedArrayBuffer !== "function") return false;
24915
+ if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return false;
24916
+ if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) return false;
24917
+ return true;
24918
+ }
24919
+ var SyncChannelServer = class {
24920
+ constructor(buffers, handle) {
24921
+ this.handle = handle;
24922
+ this.control = new Int32Array(buffers.control);
24923
+ this.data = new Uint8Array(buffers.data);
24924
+ this.capacity = this.data.length;
24005
24925
  }
24006
- const copy = (path) => {
24007
- const stat2 = volume.lstatSync(path);
24008
- if (stat2.isDirectory()) {
24009
- fs.mkdirSync(path, { recursive: true });
24010
- for (const name of volume.readdirSync(path)) copy(join(path, name));
24011
- } else if (stat2.isSymbolicLink()) {
24012
- fs.mkdirSync(dirname(path), { recursive: true });
24926
+ handle;
24927
+ control;
24928
+ data;
24929
+ capacity;
24930
+ /** Request chunks gathered so far, and the response still to be sent. */
24931
+ incoming = [];
24932
+ outgoing = null;
24933
+ sent = 0;
24934
+ closed = false;
24935
+ /** Call from the wake message the client sends after each chunk. */
24936
+ async pump() {
24937
+ if (this.closed) return;
24938
+ const state = Atomics.load(this.control, STATE);
24939
+ if (state === STATE_REQUEST) {
24940
+ const size = Atomics.load(this.control, LENGTH);
24941
+ this.incoming.push(this.data.slice(0, size));
24942
+ if (Atomics.load(this.control, MORE) === 1) {
24943
+ this.publish(STATE_CONTINUE);
24944
+ return;
24945
+ }
24946
+ const request = concat3(this.incoming);
24947
+ this.incoming = [];
24948
+ let response;
24013
24949
  try {
24014
- fs.symlinkSync(volume.readlinkSync(path), path);
24950
+ response = await this.handle(request);
24015
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
+ // src/runtime/remote-volume.ts
24996
+ var encoder7 = new TextEncoder();
24997
+ var decoder7 = new TextDecoder();
24998
+ function encodeFrame(header, body) {
24999
+ const json = encoder7.encode(JSON.stringify(header));
25000
+ const frame = new Uint8Array(4 + json.length + (body?.length ?? 0));
25001
+ new DataView(frame.buffer).setUint32(0, json.length, true);
25002
+ frame.set(json, 4);
25003
+ if (body?.length) frame.set(body, 4 + json.length);
25004
+ return frame;
25005
+ }
25006
+ function decodeFrame(frame) {
25007
+ const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
25008
+ const headerLength = view.getUint32(0, true);
25009
+ const header = JSON.parse(decoder7.decode(frame.subarray(4, 4 + headerLength)));
25010
+ return { header, body: frame.subarray(4 + headerLength) };
25011
+ }
25012
+ function flattenStat(stat2) {
25013
+ return {
25014
+ kind: stat2.isDirectory() ? "directory" : stat2.isSymbolicLink() ? "symlink" : "file",
25015
+ mode: stat2.mode,
25016
+ size: stat2.size,
25017
+ uid: stat2.uid,
25018
+ gid: stat2.gid,
25019
+ ino: stat2.ino,
25020
+ nlink: stat2.nlink,
25021
+ atimeMs: stat2.atimeMs,
25022
+ mtimeMs: stat2.mtimeMs,
25023
+ ctimeMs: stat2.ctimeMs,
25024
+ birthtimeMs: stat2.birthtimeMs
25025
+ };
25026
+ }
25027
+ var BINARY_RESULTS = /* @__PURE__ */ new Set(["readFileSync"]);
25028
+ var BINARY_ARGUMENTS = /* @__PURE__ */ new Set(["writeFileSync", "appendFileSync"]);
25029
+ function serveVolume(volume) {
25030
+ return (request) => {
25031
+ const { header, body } = decodeFrame(request);
25032
+ const target = volume;
25033
+ try {
25034
+ const method = target[header.op];
25035
+ if (typeof method !== "function") {
25036
+ return encodeFrame({ ok: false, error: { message: `unknown volume operation: ${header.op}` } });
25037
+ }
25038
+ const plain = header.args.map((value) => value === null ? void 0 : value);
25039
+ const args = BINARY_ARGUMENTS.has(header.op) ? [plain[0], body] : header.op === "utimesSync" ? [plain[0], new Date(plain[1]), new Date(plain[2])] : plain;
25040
+ const result = method.apply(volume, args);
25041
+ if (BINARY_RESULTS.has(header.op)) {
25042
+ return encodeFrame({ ok: true }, result);
24016
25043
  }
24017
- } else if (!path.endsWith(".node")) {
24018
- fs.mkdirSync(dirname(path), { recursive: true });
24019
- fs.writeFileSync(path, volume.readFileSync(path));
25044
+ if (header.op === "lstatSync") {
25045
+ return encodeFrame({ ok: true, value: flattenStat(result) });
25046
+ }
25047
+ return encodeFrame({ ok: true, value: result });
25048
+ } catch (error) {
25049
+ const failure = error;
25050
+ return encodeFrame({
25051
+ ok: false,
25052
+ error: { ...failure.code ? { code: failure.code } : {}, message: failure.message ?? String(error) }
25053
+ });
24020
25054
  }
24021
25055
  };
24022
- copy(clean(root));
25056
+ }
25057
+
25058
+ // src/runtime/sync-syscalls.ts
25059
+ var SPAWN_OP = "spawnSync";
25060
+ function serveSyncSyscalls(options) {
25061
+ const volumeHandler = serveVolume(options.volume);
25062
+ return async (request) => {
25063
+ const { header } = decodeFrame(request);
25064
+ if (header.op !== SPAWN_OP) return volumeHandler(request);
25065
+ try {
25066
+ const result = await options.spawnChild(header.args[0]);
25067
+ return encodeFrame({ ok: true, value: result });
25068
+ } catch (error) {
25069
+ const failure = error;
25070
+ return encodeFrame({
25071
+ ok: true,
25072
+ value: {
25073
+ status: null,
25074
+ stdout: "",
25075
+ stderr: "",
25076
+ signal: null,
25077
+ error: { ...failure.code ? { code: failure.code } : {}, message: failure.message ?? String(error) }
25078
+ }
25079
+ });
25080
+ }
25081
+ };
25082
+ }
25083
+
25084
+ // src/runtime/worker-host.ts
25085
+ function defaultWorkerUrl() {
25086
+ return new URL("./worker-entry.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
25087
+ }
25088
+ function hasDomWorker() {
25089
+ return typeof Worker === "function" && typeof document !== "undefined";
25090
+ }
25091
+ async function startRuntimeWorker(options = {}) {
25092
+ const url = options.url ?? defaultWorkerUrl();
25093
+ const worker = hasDomWorker() ? await startDomWorker(url) : await startNodeWorker(url, options.workerData ?? {});
25094
+ await new Promise((resolve2, reject) => {
25095
+ const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
25096
+ worker.onMessage((message) => {
25097
+ if (message?.type === "sandboxedjs:ready") {
25098
+ clearTimeout(timer);
25099
+ resolve2();
25100
+ }
25101
+ });
25102
+ worker.onError((error) => {
25103
+ clearTimeout(timer);
25104
+ reject(error instanceof Error ? error : new Error(String(error)));
25105
+ });
25106
+ });
25107
+ return worker;
25108
+ }
25109
+ async function startDomWorker(url) {
25110
+ const worker = new Worker(url, { type: "module" });
25111
+ const listeners = [];
25112
+ const errors = [];
25113
+ worker.addEventListener("message", (event) => {
25114
+ for (const listener of listeners) listener(event.data);
25115
+ });
25116
+ worker.addEventListener("error", (event) => {
25117
+ const detail = event.message || "the runtime worker failed to load";
25118
+ for (const listener of errors) listener(new Error(detail));
25119
+ });
25120
+ return {
25121
+ postMessage: (message) => worker.postMessage(message),
25122
+ onMessage: (listener) => {
25123
+ listeners.push(listener);
25124
+ },
25125
+ onError: (listener) => {
25126
+ errors.push(listener);
25127
+ },
25128
+ terminate: () => worker.terminate()
25129
+ };
25130
+ }
25131
+ async function startNodeWorker(url, workerData) {
25132
+ const specifier = ["node", "worker_threads"].join(":");
25133
+ const { Worker: NodeWorker } = await import(
25134
+ /* @vite-ignore */
25135
+ /* webpackIgnore: true */
25136
+ specifier
25137
+ );
25138
+ const worker = new NodeWorker(url, { workerData });
25139
+ return {
25140
+ postMessage: (message) => worker.postMessage(message),
25141
+ onMessage: (listener) => worker.on("message", listener),
25142
+ onError: (listener) => worker.on("error", listener),
25143
+ terminate: () => worker.terminate()
25144
+ };
25145
+ }
25146
+ var WorkerProcess = class extends EventEmitter4__default.default {
25147
+ constructor(worker, release) {
25148
+ super();
25149
+ this.worker = worker;
25150
+ this.release = release;
25151
+ super.on("error", () => {
25152
+ });
25153
+ this.completion = new Promise((resolve2) => {
25154
+ this.resolveCompletion = resolve2;
25155
+ });
25156
+ }
25157
+ worker;
25158
+ release;
25159
+ completion;
25160
+ resolveCompletion;
25161
+ settled = false;
25162
+ out = [];
25163
+ err = [];
25164
+ started = false;
25165
+ pendingInput = [];
25166
+ inputEnded = false;
25167
+ on(event, listener) {
25168
+ return super.on(event, listener);
25169
+ }
25170
+ /** Called once the guest has been told to start; releases anything buffered. */
25171
+ begin() {
25172
+ this.started = true;
25173
+ for (const chunk of this.pendingInput.splice(0)) this.worker.postMessage({ type: "stdin", data: chunk });
25174
+ if (this.inputEnded) this.worker.postMessage({ type: "stdin-end" });
25175
+ }
25176
+ output(text2) {
25177
+ this.out.push(text2);
25178
+ this.emit("output", text2);
25179
+ }
25180
+ error(text2) {
25181
+ this.err.push(text2);
25182
+ this.emit("error", text2);
25183
+ }
25184
+ /**
25185
+ * Deliver input to the running program.
25186
+ *
25187
+ * Held until the guest has been started, for the same reason the in-realm
25188
+ * pod holds it: an interactive session's first keystrokes arrive while the
25189
+ * program is still loading, and dropping them loses the answer to a prompt.
25190
+ */
25191
+ write(data) {
25192
+ if (this.started) this.worker.postMessage({ type: "stdin", data });
25193
+ else this.pendingInput.push(data);
25194
+ }
25195
+ endInput() {
25196
+ this.inputEnded = true;
25197
+ if (this.started) this.worker.postMessage({ type: "stdin-end" });
25198
+ }
25199
+ kill(_signal = "SIGTERM") {
25200
+ this.worker.postMessage({ type: "kill" });
25201
+ this.finish(137);
25202
+ }
25203
+ finish(exitCode) {
25204
+ if (this.settled) return;
25205
+ this.settled = true;
25206
+ this.emit("exit", exitCode);
25207
+ this.resolveCompletion({ exitCode, stdout: this.out.join(""), stderr: this.err.join("") });
25208
+ this.release();
25209
+ }
25210
+ };
25211
+ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
25212
+ workerUrl;
25213
+ /** Live workers, so teardown can stop them all. */
25214
+ live = /* @__PURE__ */ new Set();
25215
+ constructor(options) {
25216
+ super(options);
25217
+ this.workerUrl = options.workerUrl;
25218
+ }
25219
+ /**
25220
+ * Boot a Worker-backed pod, or return null when this host cannot support one.
25221
+ *
25222
+ * Declining is a first-class outcome. `SharedArrayBuffer` needs cross-origin
25223
+ * isolation, a bundler may have made the guest script unreachable, and a host
25224
+ * that supplied its own module objects has handed over things no thread
25225
+ * boundary can carry. In every case the caller falls back to the in-realm pod
25226
+ * and keeps working.
25227
+ */
25228
+ static async tryBoot(options = {}) {
25229
+ if (!syncChannelSupported()) return null;
25230
+ if (options.modules && Object.keys(options.modules).length > 0) return null;
25231
+ const pod = new _WorkerRuntimePod(options);
25232
+ try {
25233
+ const probe = await startRuntimeWorker({ ...options.workerUrl ? { url: options.workerUrl } : {}, timeoutMs: 1e4 });
25234
+ await probe.terminate();
25235
+ return pod;
25236
+ } catch {
25237
+ pod.teardown();
25238
+ return null;
25239
+ }
25240
+ }
25241
+ async spawn(command, args = [], options = {}) {
25242
+ if (command !== "node" && command !== "nodejs") return super.spawn(command, args, options);
25243
+ const script = args[0];
25244
+ if (!script) return super.spawn(command, args, options);
25245
+ const cwd = typeof options.cwd === "string" ? options.cwd : this.workdir;
25246
+ if (this.needsHostModules(cwd)) return super.spawn(command, args, options);
25247
+ return await this.spawnInWorker(script, args, cwd, options);
25248
+ }
25249
+ async spawnInWorker(script, args, cwd, options) {
25250
+ const env2 = { ...this.env, ...isRecord2(options.env) ? options.env : {} };
25251
+ const owner = `${this.instanceId}:${Math.random().toString(36).slice(2)}`;
25252
+ const buffers = createSyncChannelBuffers();
25253
+ const worker = await startRuntimeWorker({
25254
+ ...this.workerUrl ? { url: this.workerUrl } : {},
25255
+ timeoutMs: 15e3
25256
+ });
25257
+ const server = new SyncChannelServer(buffers, serveSyncSyscalls({
25258
+ volume: this.volume,
25259
+ spawnChild: (request) => this.runChildToCompletion(request)
25260
+ }));
25261
+ const entry = { worker, server };
25262
+ this.live.add(entry);
25263
+ const process2 = new WorkerProcess(worker, () => {
25264
+ this.live.delete(entry);
25265
+ server.close();
25266
+ this.closeProxies(owner);
25267
+ void worker.terminate();
25268
+ });
25269
+ const children = /* @__PURE__ */ new Map();
25270
+ worker.onMessage((raw) => {
25271
+ const message = raw;
25272
+ switch (message?.type) {
25273
+ case "wake":
25274
+ void server.pump();
25275
+ return;
25276
+ case "output":
25277
+ process2.output(String(message.text));
25278
+ return;
25279
+ case "error":
25280
+ process2.error(String(message.text));
25281
+ return;
25282
+ case "rawmode":
25283
+ process2.emit("rawmode", Boolean(message.enabled));
25284
+ return;
25285
+ case "exit":
25286
+ process2.finish(Number(message.code));
25287
+ return;
25288
+ case "listen":
25289
+ this.proxyPort(Number(message.port), owner, worker);
25290
+ return;
25291
+ case "http-response":
25292
+ this.settleProxied(Number(message.id), message.response);
25293
+ return;
25294
+ case "child-start":
25295
+ this.startChild(worker, children, message);
25296
+ return;
25297
+ case "child-stdin":
25298
+ children.get(message.id)?.sendStdin?.(String(message.data));
25299
+ return;
25300
+ case "child-stdin-end":
25301
+ children.get(message.id)?.endStdin?.();
25302
+ return;
25303
+ case "child-kill":
25304
+ children.get(message.id)?.kill(String(message.signal));
25305
+ return;
25306
+ default:
25307
+ return;
25308
+ }
25309
+ });
25310
+ worker.postMessage({
25311
+ type: "start",
25312
+ buffers,
25313
+ script,
25314
+ cwd,
25315
+ env: env2,
25316
+ argv: Array.isArray(options.argv) ? options.argv : ["/usr/bin/node", script, ...args.slice(1)],
25317
+ aliases: this.aliases,
25318
+ ...typeof options.stdinPath === "string" ? { stdinPath: options.stdinPath } : {},
25319
+ ...options.interactiveStdin ? { interactiveStdin: true } : {},
25320
+ ...options.tty ? { tty: true } : {}
25321
+ });
25322
+ process2.begin();
25323
+ return process2;
25324
+ }
25325
+ /** Start an asynchronous child on the host's behalf and relay its events. */
25326
+ startChild(worker, children, message) {
25327
+ const handle = this.processManager.spawn(message.config);
25328
+ children.set(message.id, handle);
25329
+ for (const event of ["stdout", "stderr", "exit"]) {
25330
+ handle.on(event, (value) => worker.postMessage({ type: "child-event", id: message.id, event, value }));
25331
+ }
25332
+ handle.exec();
25333
+ }
25334
+ /** Run a child to completion and collect it, for the guest's `spawnSync`. */
25335
+ runChildToCompletion(request) {
25336
+ return new Promise((resolve2) => {
25337
+ let handle;
25338
+ try {
25339
+ handle = this.processManager.spawn({
25340
+ command: request.command,
25341
+ args: request.args,
25342
+ cwd: request.cwd,
25343
+ ...request.env ? { env: request.env } : {}
25344
+ });
25345
+ } catch (error) {
25346
+ const failure = error;
25347
+ resolve2({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure.code ? { code: failure.code } : {}, message: failure.message } });
25348
+ return;
25349
+ }
25350
+ let stdout = "";
25351
+ let stderr = "";
25352
+ handle.on("stdout", (text2) => {
25353
+ stdout += text2;
25354
+ });
25355
+ handle.on("stderr", (text2) => {
25356
+ stderr += text2;
25357
+ });
25358
+ handle.on("exit", (code) => resolve2({ status: code, stdout, stderr, signal: null }));
25359
+ handle.exec();
25360
+ if (request.input !== void 0) {
25361
+ handle.sendStdin?.(request.input);
25362
+ }
25363
+ handle.endStdin?.();
25364
+ });
25365
+ }
25366
+ // ── HTTP servers living on another thread ─────────────────────────────────
25367
+ proxies = /* @__PURE__ */ new Map();
25368
+ waiting = /* @__PURE__ */ new Map();
25369
+ nextRequestId = 1;
25370
+ /**
25371
+ * Register a stand-in for a server that is actually running in the Worker.
25372
+ *
25373
+ * The router only knows how to reach servers on this thread, so each bound
25374
+ * port gets a local server whose whole job is to forward and wait.
25375
+ */
25376
+ proxyPort(port, owner, worker) {
25377
+ if (this.proxies.has(port)) return;
25378
+ const server = new VirtualHttpServer(this.router, owner, (request, response) => {
25379
+ void this.forward(worker, port, request, response);
25380
+ });
25381
+ try {
25382
+ server.listen(port);
25383
+ this.proxies.set(port, server);
25384
+ } catch {
25385
+ }
25386
+ }
25387
+ async forward(worker, port, request, response) {
25388
+ const chunks = [];
25389
+ for await (const chunk of request) chunks.push(chunk);
25390
+ const body = concat4(chunks);
25391
+ const id = this.nextRequestId++;
25392
+ const answered = new Promise((resolve2) => this.waiting.set(id, resolve2));
25393
+ worker.postMessage({
25394
+ type: "http-request",
25395
+ id,
25396
+ port,
25397
+ init: { method: request.method, path: request.url, headers: request.headers, body }
25398
+ });
25399
+ let result;
25400
+ try {
25401
+ result = await answered;
25402
+ } catch (error) {
25403
+ this.waiting.delete(id);
25404
+ response.writeHead(500, "Internal Server Error", {});
25405
+ response.end(new TextEncoder().encode(error instanceof Error ? error.message : String(error)));
25406
+ return;
25407
+ }
25408
+ response.writeHead(Number(result.statusCode ?? 200), String(result.statusMessage ?? ""), result.headers ?? {});
25409
+ response.end(result.body ?? new Uint8Array());
25410
+ }
25411
+ settleProxied(id, response) {
25412
+ const resolve2 = this.waiting.get(id);
25413
+ if (!resolve2) return;
25414
+ this.waiting.delete(id);
25415
+ resolve2(response);
25416
+ }
25417
+ closeProxies(owner) {
25418
+ for (const [port, server] of [...this.proxies]) {
25419
+ if (server.owner !== owner) continue;
25420
+ server.close();
25421
+ this.proxies.delete(port);
25422
+ }
25423
+ }
25424
+ teardown() {
25425
+ for (const { worker, server } of [...this.live]) {
25426
+ server.close();
25427
+ void worker.terminate();
25428
+ }
25429
+ this.live.clear();
25430
+ super.teardown();
25431
+ }
25432
+ /** Does anything under `cwd` need a module only the host can supply? */
25433
+ needsHostModules(cwd) {
25434
+ return packageIsInstalled(this.volume, cwd, "rolldown");
25435
+ }
25436
+ };
25437
+ function isRecord2(value) {
25438
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25439
+ }
25440
+ function concat4(parts) {
25441
+ if (parts.length === 0) return new Uint8Array();
25442
+ if (parts.length === 1) return parts[0];
25443
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
25444
+ const joined = new Uint8Array(total);
25445
+ let at = 0;
25446
+ for (const part of parts) {
25447
+ joined.set(part, at);
25448
+ at += part.length;
25449
+ }
25450
+ return joined;
24023
25451
  }
24024
25452
 
24025
25453
  // src/container/container.ts
@@ -24066,11 +25494,13 @@ var Container = class _Container {
24066
25494
  // ── boot ──────────────────────────────────────────────────────────────────
24067
25495
  static async create(opts = {}) {
24068
25496
  if (opts.python) configurePython(opts.python);
24069
- const pod = opts.pod ?? await LocalRuntimePod.boot({
25497
+ const podOptions = {
24070
25498
  workdir: opts.cwd ?? "/",
24071
25499
  env: opts.env ?? {},
24072
25500
  ...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
24073
- });
25501
+ };
25502
+ const workerOptions = { ...podOptions, ...opts.workerUrl ? { workerUrl: opts.workerUrl } : {} };
25503
+ const pod = opts.pod ?? (opts.isolation === "realm" ? null : await WorkerRuntimePod.tryBoot(workerOptions)) ?? await LocalRuntimePod.boot(podOptions);
24074
25504
  const kernel = new Kernel({
24075
25505
  pod,
24076
25506
  hostname: opts.hostname ?? "sandbox",
@@ -24312,15 +25742,15 @@ var Container = class _Container {
24312
25742
  }
24313
25743
  makeStdio(opts) {
24314
25744
  const combined = [];
24315
- const decoder7 = new TextDecoder();
25745
+ const decoder8 = new TextDecoder();
24316
25746
  const stdout = new BufferSink((chunk) => {
24317
- const text2 = decoder7.decode(chunk, { stream: true });
25747
+ const text2 = decoder8.decode(chunk, { stream: true });
24318
25748
  combined.push(text2);
24319
25749
  opts.onStdout?.(text2);
24320
25750
  this.hooks.onStdout?.(text2);
24321
25751
  });
24322
25752
  const stderr = new BufferSink((chunk) => {
24323
- const text2 = decoder7.decode(chunk, { stream: true });
25753
+ const text2 = decoder8.decode(chunk, { stream: true });
24324
25754
  combined.push(text2);
24325
25755
  opts.onStderr?.(text2);
24326
25756
  this.hooks.onStderr?.(text2);
@@ -24576,17 +26006,18 @@ var Terminal = class {
24576
26006
  // ── key handling ──────────────────────────────────────────────────────────
24577
26007
  key(ch) {
24578
26008
  if (this.running) {
24579
- if (ch === CTRL_C) {
26009
+ const raw = this.currentStdin?.rawMode === true;
26010
+ if (ch === CTRL_C && !raw) {
24580
26011
  this.session.proc.deliver("SIGINT");
24581
26012
  this.write("^C\r\n");
24582
26013
  return;
24583
26014
  }
24584
- if (ch === CTRL_D) {
26015
+ if (ch === CTRL_D && !raw) {
24585
26016
  this.currentStdin?.end();
24586
26017
  return;
24587
26018
  }
24588
- if (!this.currentStdin?.rawMode) this.write(ch === "\r" ? "\r\n" : ch);
24589
- this.currentStdin?.write(ch === "\r" ? "\n" : ch);
26019
+ if (!raw) this.write(ch === "\r" ? "\r\n" : ch);
26020
+ this.currentStdin?.write(!raw && ch === "\r" ? "\n" : ch);
24590
26021
  return;
24591
26022
  }
24592
26023
  if (this.escapeBuffer !== "") {
@@ -25013,7 +26444,9 @@ exports.parseUmask = parseUmask;
25013
26444
  exports.posixPath = path_exports;
25014
26445
  exports.resetPidCounter = resetPidCounter;
25015
26446
  exports.shellQuote = shellQuote;
26447
+ exports.startRuntimeWorker = startRuntimeWorker;
25016
26448
  exports.strerror = strerror;
26449
+ exports.syncChannelSupported = syncChannelSupported;
25017
26450
  exports.transformEsm = transformEsm;
25018
26451
  exports.unameInfo = unameInfo;
25019
26452
  //# sourceMappingURL=index.cjs.map