sandboxedjs 0.1.7 → 0.1.8

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
@@ -762,10 +762,10 @@ function globToRegexSource(pattern, opts = {}) {
762
762
  continue;
763
763
  }
764
764
  if (c === "[") {
765
- const compiled2 = compileBracket(pattern, i);
766
- if (compiled2) {
767
- out += guardDot() + compiled2.source;
768
- i = compiled2.next;
765
+ const compiled3 = compileBracket(pattern, i);
766
+ if (compiled3) {
767
+ out += guardDot() + compiled3.source;
768
+ i = compiled3.next;
769
769
  atSegmentStart = false;
770
770
  continue;
771
771
  }
@@ -6721,8 +6721,8 @@ var init_ffmpeg_core = __esm({
6721
6721
  };
6722
6722
  function handleMessage(data) {
6723
6723
  if (typeof data == "string") {
6724
- var encoder6 = new TextEncoder();
6725
- data = encoder6.encode(data);
6724
+ var encoder7 = new TextEncoder();
6725
+ data = encoder7.encode(data);
6726
6726
  } else {
6727
6727
  assert(data.byteLength !== void 0);
6728
6728
  if (data.byteLength == 0) {
@@ -10448,12 +10448,40 @@ function strerrorOf(e) {
10448
10448
  var EXIT_NOT_EXECUTABLE = 126;
10449
10449
  var EXIT_NOT_FOUND = 127;
10450
10450
 
10451
+ // src/kernel/binfmt.ts
10452
+ var MAGIC_BYTES = 256;
10453
+ var ExecFormatRegistry = class {
10454
+ formats = [];
10455
+ register(format) {
10456
+ this.formats.push(format);
10457
+ this.formats.sort((a, b) => (a.priority ?? 50) - (b.priority ?? 50));
10458
+ }
10459
+ registerAll(formats) {
10460
+ for (const f of formats) this.register(f);
10461
+ }
10462
+ /** The handler claiming `head`, or null when the bytes match nothing. */
10463
+ match(head2, path) {
10464
+ for (const format of this.formats) {
10465
+ try {
10466
+ if (format.matches(head2, path)) return format;
10467
+ } catch {
10468
+ }
10469
+ }
10470
+ return null;
10471
+ }
10472
+ list() {
10473
+ return this.formats.slice();
10474
+ }
10475
+ };
10476
+
10451
10477
  // src/kernel/kernel.ts
10452
10478
  var BUILTIN_INTERPRETER = "/proc/self/exe";
10453
10479
  var Kernel = class {
10454
10480
  vfs;
10455
10481
  procs = new ProcessTable();
10456
10482
  commands = new CommandRegistry();
10483
+ /** Handlers for executable files that are neither built-ins nor scripts. */
10484
+ formats = new ExecFormatRegistry();
10457
10485
  users;
10458
10486
  pod;
10459
10487
  bootTime;
@@ -10583,14 +10611,15 @@ var Kernel = class {
10583
10611
  const command = this.commands.get(builtinName);
10584
10612
  if (command) return { kind: "builtin", path: candidate, command };
10585
10613
  }
10586
- let head2 = "";
10614
+ let bytes = new Uint8Array(0);
10587
10615
  try {
10588
10616
  if (this.vfs.permitted(st, R_OK, cred)) {
10589
- head2 = new TextDecoder().decode(this.vfs.readFile(real, cred).subarray(0, 256));
10617
+ bytes = this.vfs.readFile(real, cred).subarray(0, MAGIC_BYTES);
10590
10618
  }
10591
10619
  } catch {
10592
10620
  }
10593
- if (head2.startsWith("#!")) {
10621
+ if (bytes[0] === 35 && bytes[1] === 33) {
10622
+ const head2 = new TextDecoder().decode(bytes);
10594
10623
  const lineEnd = head2.indexOf("\n");
10595
10624
  const line = (lineEnd >= 0 ? head2.slice(2, lineEnd) : head2.slice(2)).trim();
10596
10625
  const parts = line.split(/\s+/).filter(Boolean);
@@ -10600,6 +10629,8 @@ var Kernel = class {
10600
10629
  }
10601
10630
  if (parts.length) return { kind: "script", path: real, interpreter: parts };
10602
10631
  }
10632
+ const format = this.formats.match(bytes, real);
10633
+ if (format) return { kind: "binary", path: real, format, head: bytes };
10603
10634
  const ext = extname(real);
10604
10635
  if (ext === ".js" || ext === ".mjs" || ext === ".cjs") return { kind: "script", path: real, interpreter: ["/usr/bin/node"] };
10605
10636
  if (ext === ".py") return { kind: "script", path: real, interpreter: ["/usr/bin/python3"] };
@@ -10752,6 +10783,24 @@ var Kernel = class {
10752
10783
  proc.argv = [interp, ...interpArgs, resolved.path, ...proc.argv.slice(1)];
10753
10784
  return await this.dispatch(proc, depth + 1);
10754
10785
  }
10786
+ if (resolved.kind === "binary" && resolved.format) {
10787
+ const { format } = resolved;
10788
+ if (format.run) {
10789
+ const ctx = createContext({ argv: proc.argv, proc, kernel: this });
10790
+ const previous = this._current;
10791
+ this._current = proc;
10792
+ try {
10793
+ return await format.run(ctx, resolved.path);
10794
+ } finally {
10795
+ this._current = previous;
10796
+ }
10797
+ }
10798
+ const lines = format.explain?.(resolved.head ?? new Uint8Array(0), resolved.path) ?? [
10799
+ `${name}: cannot execute ${format.describe?.(resolved.head ?? new Uint8Array(0), resolved.path) ?? format.name} file`
10800
+ ];
10801
+ for (const line of lines) proc.stderr.write(line + "\n");
10802
+ return EXIT_NOT_EXECUTABLE;
10803
+ }
10755
10804
  proc.stderr.write(`${name}: cannot execute binary file: Exec format error
10756
10805
  `);
10757
10806
  return EXIT_NOT_EXECUTABLE;
@@ -13528,16 +13577,16 @@ sys ${fmt2(Math.floor(ms * 0.2))}
13528
13577
  function splitAliasWords(text) {
13529
13578
  const out = [];
13530
13579
  let current = "";
13531
- let quote2 = null;
13580
+ let quote3 = null;
13532
13581
  for (let i = 0; i < text.length; i++) {
13533
13582
  const c = text[i];
13534
- if (quote2) {
13535
- if (c === quote2) quote2 = null;
13583
+ if (quote3) {
13584
+ if (c === quote3) quote3 = null;
13536
13585
  else current += c;
13537
13586
  continue;
13538
13587
  }
13539
13588
  if (c === "'" || c === '"') {
13540
- quote2 = c;
13589
+ quote3 = c;
13541
13590
  continue;
13542
13591
  }
13543
13592
  if (c === " " || c === " ") {
@@ -14911,8 +14960,10 @@ function describeFile(ctx, abs, st, mime) {
14911
14960
  if (st.size === 0) return mime ? "inode/x-empty" : "empty";
14912
14961
  const bytes = ctx.vfs.readFile(abs, ctx.cred).subarray(0, 512);
14913
14962
  const magic = [...bytes.subarray(0, 4)];
14914
- if (magic[0] === 127 && magic[1] === 69 && magic[2] === 76 && magic[3] === 70) {
14915
- return mime ? "application/x-executable" : "ELF 64-bit LSB executable, x86-64";
14963
+ const format = ctx.kernel.formats.match(bytes, abs);
14964
+ if (format) {
14965
+ if (mime) return format.name === "wasm" ? "application/wasm" : "application/x-executable";
14966
+ return format.describe?.(bytes, abs) ?? `${format.name} executable`;
14916
14967
  }
14917
14968
  if (magic[0] === 31 && magic[1] === 139) return mime ? "application/gzip" : "gzip compressed data";
14918
14969
  if (magic[0] === 80 && magic[1] === 75) return mime ? "application/zip" : "Zip archive data";
@@ -22549,16 +22600,16 @@ async function loadFactory() {
22549
22600
  const path = await nodeBuiltin("path");
22550
22601
  const require2 = createRequire(path.join(process.cwd(), "index.js"));
22551
22602
  const wasmPath = require2.resolve("@ffmpeg/core/wasm");
22552
- const wasm = await fs.readFile(wasmPath);
22603
+ const wasm2 = await fs.readFile(wasmPath);
22553
22604
  const module = await Promise.resolve().then(() => (init_ffmpeg_core(), ffmpeg_core_exports));
22554
- return { factory: module.default, wasm: new Uint8Array(wasm) };
22605
+ return { factory: module.default, wasm: new Uint8Array(wasm2) };
22555
22606
  }
22556
22607
  function getCompiled() {
22557
22608
  compiled ??= (async () => {
22558
- const { factory, wasm } = await loadFactory();
22559
- const bytes = wasm.buffer.slice(
22560
- wasm.byteOffset,
22561
- wasm.byteOffset + wasm.byteLength
22609
+ const { factory, wasm: wasm2 } = await loadFactory();
22610
+ const bytes = wasm2.buffer.slice(
22611
+ wasm2.byteOffset,
22612
+ wasm2.byteOffset + wasm2.byteLength
22562
22613
  );
22563
22614
  return { factory, module: await WebAssembly.compile(bytes) };
22564
22615
  })();
@@ -22635,6 +22686,1019 @@ var ffprobe = defineCommand({
22635
22686
  function ffmpegCommands() {
22636
22687
  return [ffmpeg, ffprobe];
22637
22688
  }
22689
+
22690
+ // src/runtime/wasi-preview1.ts
22691
+ init_errno();
22692
+ init_mode();
22693
+ init_path();
22694
+ var E = {
22695
+ SUCCESS: 0,
22696
+ ACCES: 2,
22697
+ AGAIN: 6,
22698
+ BADF: 8,
22699
+ BUSY: 10,
22700
+ EXIST: 20,
22701
+ FAULT: 21,
22702
+ INVAL: 28,
22703
+ IO: 29,
22704
+ ISDIR: 31,
22705
+ LOOP: 32,
22706
+ MFILE: 33,
22707
+ NAMETOOLONG: 37,
22708
+ NOENT: 44,
22709
+ NOEXEC: 45,
22710
+ NOMEM: 48,
22711
+ NOSPC: 51,
22712
+ NOSYS: 52,
22713
+ NOTDIR: 54,
22714
+ NOTEMPTY: 55,
22715
+ NOTSUP: 58,
22716
+ NOTTY: 59,
22717
+ PERM: 63,
22718
+ PIPE: 64,
22719
+ ROFS: 69,
22720
+ SPIPE: 70,
22721
+ XDEV: 75,
22722
+ NOTCAPABLE: 76
22723
+ };
22724
+ var ERRNO_MAP = {
22725
+ EPERM: E.PERM,
22726
+ ENOENT: E.NOENT,
22727
+ EIO: E.IO,
22728
+ EBADF: E.BADF,
22729
+ EACCES: E.ACCES,
22730
+ EBUSY: E.BUSY,
22731
+ EEXIST: E.EXIST,
22732
+ EXDEV: E.XDEV,
22733
+ ENODEV: E.NOSYS,
22734
+ ENOTDIR: E.NOTDIR,
22735
+ EISDIR: E.ISDIR,
22736
+ EINVAL: E.INVAL,
22737
+ ENFILE: E.MFILE,
22738
+ EMFILE: E.MFILE,
22739
+ ENOSPC: E.NOSPC,
22740
+ EROFS: E.ROFS,
22741
+ ENOTEMPTY: E.NOTEMPTY,
22742
+ ELOOP: E.LOOP,
22743
+ ENOSYS: E.NOSYS,
22744
+ ENAMETOOLONG: E.NAMETOOLONG,
22745
+ EPIPE: E.PIPE,
22746
+ ENOTSUP: E.NOTSUP,
22747
+ ESPIPE: E.SPIPE
22748
+ };
22749
+ var FT = {
22750
+ UNKNOWN: 0,
22751
+ BLOCK_DEVICE: 1,
22752
+ CHARACTER_DEVICE: 2,
22753
+ DIRECTORY: 3,
22754
+ REGULAR_FILE: 4,
22755
+ SOCKET_STREAM: 6,
22756
+ SYMBOLIC_LINK: 7
22757
+ };
22758
+ var O_CREAT = 1 << 0;
22759
+ var O_DIRECTORY = 1 << 1;
22760
+ var O_EXCL = 1 << 2;
22761
+ var O_TRUNC = 1 << 3;
22762
+ var FD_APPEND = 1 << 0;
22763
+ var LOOKUP_SYMLINK_FOLLOW = 1 << 0;
22764
+ var RIGHT_FD_READ = 1n << 1n;
22765
+ var RIGHT_FD_WRITE = 1n << 6n;
22766
+ var RIGHTS_ALL = (1n << 63n) - 1n;
22767
+ var WHENCE_SET = 0;
22768
+ var WHENCE_CUR = 1;
22769
+ var WHENCE_END = 2;
22770
+ var SIZEOF_DIRENT = 24;
22771
+ var SIZEOF_FILESTAT = 64;
22772
+ var SIZEOF_IOVEC = 8;
22773
+ var WasiExit = class extends Error {
22774
+ constructor(code) {
22775
+ super(`wasi exit ${code}`);
22776
+ this.code = code;
22777
+ this.name = "WasiExit";
22778
+ }
22779
+ code;
22780
+ };
22781
+ var encoder6 = new TextEncoder();
22782
+ var decoder7 = new TextDecoder();
22783
+ var WasiPreview1 = class {
22784
+ memory = null;
22785
+ fds = /* @__PURE__ */ new Map();
22786
+ nextFd = 3;
22787
+ opts;
22788
+ now;
22789
+ randomFill;
22790
+ /** Set once `proc_exit` has been seen, so the caller can tell exit 0 from a
22791
+ * module that simply returned. */
22792
+ exited = null;
22793
+ constructor(opts) {
22794
+ this.opts = opts;
22795
+ this.now = opts.now ?? Date.now;
22796
+ this.randomFill = opts.randomFill ?? ((buffer) => {
22797
+ for (let i = 0; i < buffer.length; i++) buffer[i] = Math.floor(Math.random() * 256);
22798
+ });
22799
+ const blank = { pos: 0, data: new Uint8Array(0), dirty: false, fdflags: 0, rights: RIGHTS_ALL };
22800
+ this.fds.set(0, { kind: "stdin", path: "/dev/stdin", ...blank, data: opts.stdin ?? new Uint8Array(0) });
22801
+ this.fds.set(1, { kind: "stdout", path: "/dev/stdout", ...blank });
22802
+ this.fds.set(2, { kind: "stderr", path: "/dev/stderr", ...blank });
22803
+ const preopens = opts.preopens ?? { "/": "/", ".": opts.cwd };
22804
+ for (const [name, path] of Object.entries(preopens)) {
22805
+ const fd = this.nextFd++;
22806
+ this.fds.set(fd, { kind: "dir", path, ...blank, preopen: name });
22807
+ }
22808
+ }
22809
+ /** Give the host access to the instantiated module's memory. */
22810
+ bind(instance) {
22811
+ const memory = instance.exports.memory;
22812
+ if (!(memory instanceof WebAssembly.Memory)) {
22813
+ throw new Error("wasm module does not export its memory; not a WASI command module");
22814
+ }
22815
+ this.memory = memory;
22816
+ }
22817
+ /* ── memory access ──────────────────────────────────────────────────────
22818
+ * Re-derived on every use: a module that grows its memory detaches the old
22819
+ * ArrayBuffer, and a cached view would silently start reading zeroes. */
22820
+ get view() {
22821
+ if (!this.memory) throw new Error("wasi host used before bind()");
22822
+ return new DataView(this.memory.buffer);
22823
+ }
22824
+ get bytes() {
22825
+ if (!this.memory) throw new Error("wasi host used before bind()");
22826
+ return new Uint8Array(this.memory.buffer);
22827
+ }
22828
+ readString(ptr, len) {
22829
+ return decoder7.decode(this.bytes.subarray(ptr, ptr + len));
22830
+ }
22831
+ /** Scatter/gather list, as `fd_read` and `fd_write` take. */
22832
+ iovecs(ptr, count) {
22833
+ const view = this.view;
22834
+ const out = [];
22835
+ for (let i = 0; i < count; i++) {
22836
+ const at = ptr + i * SIZEOF_IOVEC;
22837
+ out.push({ base: view.getUint32(at, true), len: view.getUint32(at + 4, true) });
22838
+ }
22839
+ return out;
22840
+ }
22841
+ /* ── path handling ──────────────────────────────────────────────────────── */
22842
+ /**
22843
+ * Resolve a path supplied by the module against a directory descriptor.
22844
+ *
22845
+ * The result is confined to the descriptor's subtree: `..` that would climb
22846
+ * out of a preopen is rejected with ENOTCAPABLE, which is WASI's own answer
22847
+ * and stops a module reaching a directory it was never granted.
22848
+ */
22849
+ resolveAt(dirfd, path) {
22850
+ const dir3 = this.fds.get(dirfd);
22851
+ if (!dir3) return E.BADF;
22852
+ if (dir3.kind !== "dir") return E.NOTDIR;
22853
+ if (path === "") return E.NOENT;
22854
+ const abs = isAbsolute(path) ? normalize(path) : resolve(dir3.path, path);
22855
+ if (dir3.path !== "/" && !contains(dir3.path, abs) && abs !== dir3.path) return E.NOTCAPABLE;
22856
+ return { path: abs };
22857
+ }
22858
+ errnoOf(e) {
22859
+ if (isSysError(e)) return ERRNO_MAP[e.code] ?? E.IO;
22860
+ return E.IO;
22861
+ }
22862
+ filetypeOf(mode) {
22863
+ switch (mode & 61440) {
22864
+ case S_IFREG:
22865
+ return FT.REGULAR_FILE;
22866
+ case S_IFDIR:
22867
+ return FT.DIRECTORY;
22868
+ case S_IFLNK:
22869
+ return FT.SYMBOLIC_LINK;
22870
+ case S_IFCHR:
22871
+ return FT.CHARACTER_DEVICE;
22872
+ case S_IFBLK:
22873
+ return FT.BLOCK_DEVICE;
22874
+ case S_IFSOCK:
22875
+ return FT.SOCKET_STREAM;
22876
+ default:
22877
+ return FT.UNKNOWN;
22878
+ }
22879
+ }
22880
+ /** Write a 64-byte `filestat` for `path`. */
22881
+ writeFilestat(ptr, path, follow) {
22882
+ try {
22883
+ const st = follow ? this.opts.vfs.stat(path, { cred: this.opts.cred }) : this.opts.vfs.lstat(path);
22884
+ const view = this.view;
22885
+ view.setBigUint64(ptr, BigInt(st.dev), true);
22886
+ view.setBigUint64(ptr + 8, BigInt(st.ino), true);
22887
+ view.setUint8(ptr + 16, this.filetypeOf(st.mode));
22888
+ view.setBigUint64(ptr + 24, BigInt(st.nlink), true);
22889
+ view.setBigUint64(ptr + 32, BigInt(st.size), true);
22890
+ view.setBigUint64(ptr + 40, BigInt(Math.round(st.atimeMs * 1e6)), true);
22891
+ view.setBigUint64(ptr + 48, BigInt(Math.round(st.mtimeMs * 1e6)), true);
22892
+ view.setBigUint64(ptr + 56, BigInt(Math.round(st.ctimeMs * 1e6)), true);
22893
+ return E.SUCCESS;
22894
+ } catch (e) {
22895
+ return this.errnoOf(e);
22896
+ }
22897
+ }
22898
+ /** Persist an open file's buffer back to the filesystem. */
22899
+ flush(fd) {
22900
+ if (!fd.dirty) return E.SUCCESS;
22901
+ try {
22902
+ this.opts.vfs.writeFile(fd.path, fd.data, { cred: this.opts.cred });
22903
+ fd.dirty = false;
22904
+ return E.SUCCESS;
22905
+ } catch (e) {
22906
+ return this.errnoOf(e);
22907
+ }
22908
+ }
22909
+ /* ── the import object ──────────────────────────────────────────────────── */
22910
+ /**
22911
+ * The `wasi_snapshot_preview1` namespace, plus `wasi_unstable` for older
22912
+ * toolchains — the two differ only in `fd_seek`'s argument order, which is
22913
+ * handled by the alias below.
22914
+ */
22915
+ get imports() {
22916
+ const preview1 = this.syscalls();
22917
+ return {
22918
+ wasi_snapshot_preview1: preview1,
22919
+ wasi_unstable: {
22920
+ ...preview1,
22921
+ // preview0's fd_seek took (fd, offset, whence) with whence as the last
22922
+ // u32 but numbered CUR=0/END=1/SET=2 — the reverse of preview1.
22923
+ fd_seek: (fd, offset, whence, out) => preview1.fd_seek(fd, offset, whence === 0 ? WHENCE_CUR : whence === 1 ? WHENCE_END : WHENCE_SET, out)
22924
+ }
22925
+ };
22926
+ }
22927
+ /* eslint-disable @typescript-eslint/no-unused-vars */
22928
+ syscalls() {
22929
+ const vfs = this.opts.vfs;
22930
+ const cred = this.opts.cred;
22931
+ return {
22932
+ /* ── process ──────────────────────────────────────────────────────── */
22933
+ args_sizes_get: (countPtr, bufSizePtr) => {
22934
+ const view = this.view;
22935
+ view.setUint32(countPtr, this.opts.args.length, true);
22936
+ view.setUint32(
22937
+ bufSizePtr,
22938
+ this.opts.args.reduce((sum, a) => sum + encoder6.encode(a).length + 1, 0),
22939
+ true
22940
+ );
22941
+ return E.SUCCESS;
22942
+ },
22943
+ args_get: (argvPtr, bufPtr) => {
22944
+ const view = this.view;
22945
+ const bytes = this.bytes;
22946
+ let cursor = bufPtr;
22947
+ this.opts.args.forEach((arg, i) => {
22948
+ view.setUint32(argvPtr + i * 4, cursor, true);
22949
+ const encoded = encoder6.encode(arg);
22950
+ bytes.set(encoded, cursor);
22951
+ bytes[cursor + encoded.length] = 0;
22952
+ cursor += encoded.length + 1;
22953
+ });
22954
+ return E.SUCCESS;
22955
+ },
22956
+ environ_sizes_get: (countPtr, bufSizePtr) => {
22957
+ const pairs = this.envPairs();
22958
+ const view = this.view;
22959
+ view.setUint32(countPtr, pairs.length, true);
22960
+ view.setUint32(
22961
+ bufSizePtr,
22962
+ pairs.reduce((sum, p) => sum + encoder6.encode(p).length + 1, 0),
22963
+ true
22964
+ );
22965
+ return E.SUCCESS;
22966
+ },
22967
+ environ_get: (environPtr, bufPtr) => {
22968
+ const pairs = this.envPairs();
22969
+ const view = this.view;
22970
+ const bytes = this.bytes;
22971
+ let cursor = bufPtr;
22972
+ pairs.forEach((pair, i) => {
22973
+ view.setUint32(environPtr + i * 4, cursor, true);
22974
+ const encoded = encoder6.encode(pair);
22975
+ bytes.set(encoded, cursor);
22976
+ bytes[cursor + encoded.length] = 0;
22977
+ cursor += encoded.length + 1;
22978
+ });
22979
+ return E.SUCCESS;
22980
+ },
22981
+ proc_exit: (code) => {
22982
+ this.exited = code;
22983
+ throw new WasiExit(code);
22984
+ },
22985
+ proc_raise: (_signal) => E.NOTSUP,
22986
+ sched_yield: () => E.SUCCESS,
22987
+ random_get: (ptr, len) => {
22988
+ const target = new Uint8Array(len);
22989
+ this.randomFill(target);
22990
+ this.bytes.set(target, ptr);
22991
+ return E.SUCCESS;
22992
+ },
22993
+ /* ── clocks ───────────────────────────────────────────────────────── */
22994
+ clock_res_get: (_id, ptr) => {
22995
+ this.view.setBigUint64(ptr, 1000000n, true);
22996
+ return E.SUCCESS;
22997
+ },
22998
+ clock_time_get: (_id, _precision, ptr) => {
22999
+ this.view.setBigUint64(ptr, BigInt(Math.round(this.now() * 1e6)), true);
23000
+ return E.SUCCESS;
23001
+ },
23002
+ /* ── descriptors ──────────────────────────────────────────────────── */
23003
+ fd_close: (fd) => {
23004
+ const entry = this.fds.get(fd);
23005
+ if (!entry) return E.BADF;
23006
+ const status = this.flush(entry);
23007
+ this.fds.delete(fd);
23008
+ return status;
23009
+ },
23010
+ fd_datasync: (fd) => {
23011
+ const entry = this.fds.get(fd);
23012
+ return entry ? this.flush(entry) : E.BADF;
23013
+ },
23014
+ fd_sync: (fd) => {
23015
+ const entry = this.fds.get(fd);
23016
+ return entry ? this.flush(entry) : E.BADF;
23017
+ },
23018
+ fd_fdstat_get: (fd, ptr) => {
23019
+ const entry = this.fds.get(fd);
23020
+ if (!entry) return E.BADF;
23021
+ let filetype = FT.CHARACTER_DEVICE;
23022
+ if (entry.kind === "dir") filetype = FT.DIRECTORY;
23023
+ else if (entry.kind === "file") filetype = FT.REGULAR_FILE;
23024
+ const view = this.view;
23025
+ view.setUint8(ptr, filetype);
23026
+ view.setUint16(ptr + 2, entry.fdflags, true);
23027
+ view.setBigUint64(ptr + 8, entry.rights, true);
23028
+ view.setBigUint64(ptr + 16, entry.rights, true);
23029
+ return E.SUCCESS;
23030
+ },
23031
+ fd_fdstat_set_flags: (fd, flags) => {
23032
+ const entry = this.fds.get(fd);
23033
+ if (!entry) return E.BADF;
23034
+ entry.fdflags = flags;
23035
+ return E.SUCCESS;
23036
+ },
23037
+ fd_fdstat_set_rights: (fd, base, _inheriting) => {
23038
+ const entry = this.fds.get(fd);
23039
+ if (!entry) return E.BADF;
23040
+ entry.rights = base;
23041
+ return E.SUCCESS;
23042
+ },
23043
+ fd_filestat_get: (fd, ptr) => {
23044
+ const entry = this.fds.get(fd);
23045
+ if (!entry) return E.BADF;
23046
+ if (entry.kind === "stdin" || entry.kind === "stdout" || entry.kind === "stderr") {
23047
+ const view = this.view;
23048
+ new Uint8Array(this.memory.buffer, ptr, SIZEOF_FILESTAT).fill(0);
23049
+ view.setUint8(ptr + 16, FT.CHARACTER_DEVICE);
23050
+ view.setBigUint64(ptr + 32, BigInt(entry.data.length), true);
23051
+ return E.SUCCESS;
23052
+ }
23053
+ return this.writeFilestat(ptr, entry.path, true);
23054
+ },
23055
+ fd_filestat_set_size: (fd, size) => {
23056
+ const entry = this.fds.get(fd);
23057
+ if (!entry || entry.kind !== "file") return E.BADF;
23058
+ const length = Number(size);
23059
+ const next = new Uint8Array(length);
23060
+ next.set(entry.data.subarray(0, Math.min(length, entry.data.length)));
23061
+ entry.data = next;
23062
+ entry.dirty = true;
23063
+ return this.flush(entry);
23064
+ },
23065
+ fd_filestat_set_times: (fd, atim, mtim, _flags) => {
23066
+ const entry = this.fds.get(fd);
23067
+ if (!entry) return E.BADF;
23068
+ try {
23069
+ vfs.utimes(entry.path, Number(atim) / 1e6, Number(mtim) / 1e6, cred);
23070
+ return E.SUCCESS;
23071
+ } catch (e) {
23072
+ return this.errnoOf(e);
23073
+ }
23074
+ },
23075
+ fd_prestat_get: (fd, ptr) => {
23076
+ const entry = this.fds.get(fd);
23077
+ if (!entry || entry.preopen === void 0) return E.BADF;
23078
+ const view = this.view;
23079
+ view.setUint8(
23080
+ ptr,
23081
+ 0
23082
+ /* dir */
23083
+ );
23084
+ view.setUint32(ptr + 4, encoder6.encode(entry.preopen).length, true);
23085
+ return E.SUCCESS;
23086
+ },
23087
+ fd_prestat_dir_name: (fd, ptr, len) => {
23088
+ const entry = this.fds.get(fd);
23089
+ if (!entry || entry.preopen === void 0) return E.BADF;
23090
+ const name = encoder6.encode(entry.preopen);
23091
+ if (name.length > len) return E.NAMETOOLONG;
23092
+ this.bytes.set(name, ptr);
23093
+ return E.SUCCESS;
23094
+ },
23095
+ fd_renumber: (from, to) => {
23096
+ const entry = this.fds.get(from);
23097
+ if (!entry) return E.BADF;
23098
+ const existing = this.fds.get(to);
23099
+ if (existing) this.flush(existing);
23100
+ this.fds.set(to, entry);
23101
+ this.fds.delete(from);
23102
+ return E.SUCCESS;
23103
+ },
23104
+ /* ── reading and writing ──────────────────────────────────────────── */
23105
+ fd_read: (fd, iovs, iovsLen, nreadPtr) => this.readInto(fd, iovs, iovsLen, nreadPtr, null),
23106
+ fd_pread: (fd, iovs, iovsLen, offset, nreadPtr) => this.readInto(fd, iovs, iovsLen, nreadPtr, Number(offset)),
23107
+ fd_write: (fd, iovs, iovsLen, nwrittenPtr) => this.writeFrom(fd, iovs, iovsLen, nwrittenPtr, null),
23108
+ fd_pwrite: (fd, iovs, iovsLen, offset, nwrittenPtr) => this.writeFrom(fd, iovs, iovsLen, nwrittenPtr, Number(offset)),
23109
+ fd_seek: (fd, offset, whence, ptr) => {
23110
+ const entry = this.fds.get(fd);
23111
+ if (!entry) return E.BADF;
23112
+ if (entry.kind === "stdout" || entry.kind === "stderr") return E.SPIPE;
23113
+ let next = Number(offset);
23114
+ if (whence === WHENCE_CUR) next += entry.pos;
23115
+ else if (whence === WHENCE_END) next += entry.data.length;
23116
+ if (next < 0) return E.INVAL;
23117
+ entry.pos = next;
23118
+ this.view.setBigUint64(ptr, BigInt(next), true);
23119
+ return E.SUCCESS;
23120
+ },
23121
+ fd_tell: (fd, ptr) => {
23122
+ const entry = this.fds.get(fd);
23123
+ if (!entry) return E.BADF;
23124
+ this.view.setBigUint64(ptr, BigInt(entry.pos), true);
23125
+ return E.SUCCESS;
23126
+ },
23127
+ fd_advise: () => E.SUCCESS,
23128
+ fd_allocate: (fd, offset, len) => {
23129
+ const entry = this.fds.get(fd);
23130
+ if (!entry || entry.kind !== "file") return E.BADF;
23131
+ const end = Number(offset) + Number(len);
23132
+ if (end > entry.data.length) {
23133
+ const next = new Uint8Array(end);
23134
+ next.set(entry.data);
23135
+ entry.data = next;
23136
+ entry.dirty = true;
23137
+ }
23138
+ return this.flush(entry);
23139
+ },
23140
+ fd_readdir: (fd, buf, bufLen, cookie, usedPtr) => {
23141
+ const entry = this.fds.get(fd);
23142
+ if (!entry) return E.BADF;
23143
+ if (entry.kind !== "dir") return E.NOTDIR;
23144
+ let names;
23145
+ try {
23146
+ names = [".", "..", ...vfs.readdir(entry.path, cred)];
23147
+ } catch (e) {
23148
+ return this.errnoOf(e);
23149
+ }
23150
+ const view = this.view;
23151
+ const bytes = this.bytes;
23152
+ let used = 0;
23153
+ for (let i = Number(cookie); i < names.length; i++) {
23154
+ const name = names[i];
23155
+ const encoded = encoder6.encode(name);
23156
+ if (used + SIZEOF_DIRENT + encoded.length > bufLen) break;
23157
+ let type = FT.UNKNOWN;
23158
+ try {
23159
+ const target = name === "." ? entry.path : name === ".." ? dirname(entry.path) : join(entry.path, name);
23160
+ type = this.filetypeOf(vfs.lstat(target).mode);
23161
+ } catch {
23162
+ }
23163
+ const at = buf + used;
23164
+ view.setBigUint64(at, BigInt(i + 1), true);
23165
+ view.setBigUint64(at + 8, 0n, true);
23166
+ view.setUint32(at + 16, encoded.length, true);
23167
+ view.setUint8(at + 20, type);
23168
+ bytes.set(encoded, at + SIZEOF_DIRENT);
23169
+ used += SIZEOF_DIRENT + encoded.length;
23170
+ }
23171
+ view.setUint32(usedPtr, used, true);
23172
+ return E.SUCCESS;
23173
+ },
23174
+ /* ── paths ────────────────────────────────────────────────────────── */
23175
+ path_open: (dirfd, lookupflags, pathPtr, pathLen, oflags, rightsBase, _rightsInheriting, fdflags, fdPtr) => {
23176
+ const resolved = this.resolveAt(dirfd, this.readString(pathPtr, pathLen));
23177
+ if (typeof resolved === "number") return resolved;
23178
+ let path = resolved.path;
23179
+ const wantsWrite = (rightsBase & RIGHT_FD_WRITE) !== 0n || (oflags & (O_CREAT | O_TRUNC)) !== 0;
23180
+ const follow = (lookupflags & LOOKUP_SYMLINK_FOLLOW) !== 0;
23181
+ try {
23182
+ if (follow && vfs.lexists(path)) path = vfs.realpath(path, cred);
23183
+ } catch {
23184
+ }
23185
+ const exists = vfs.lexists(path);
23186
+ if (exists && (oflags & O_EXCL) !== 0 && (oflags & O_CREAT) !== 0) return E.EXIST;
23187
+ if (!exists && (oflags & O_CREAT) === 0) return E.NOENT;
23188
+ try {
23189
+ if (!exists) {
23190
+ if ((oflags & O_DIRECTORY) !== 0) {
23191
+ vfs.mkdir(path, { cred, mode: 493 });
23192
+ } else {
23193
+ vfs.writeFile(path, new Uint8Array(0), { cred, mode: 420 });
23194
+ }
23195
+ }
23196
+ const st = vfs.lstat(path);
23197
+ const isDir = (st.mode & 61440) === S_IFDIR;
23198
+ if ((oflags & O_DIRECTORY) !== 0 && !isDir) return E.NOTDIR;
23199
+ if (isDir && wantsWrite) return E.ISDIR;
23200
+ let data = new Uint8Array(0);
23201
+ if (!isDir) {
23202
+ data = (oflags & O_TRUNC) !== 0 ? new Uint8Array(0) : vfs.readFile(path, cred);
23203
+ if ((oflags & O_TRUNC) !== 0) vfs.writeFile(path, data, { cred });
23204
+ }
23205
+ const fd = this.allocateFd();
23206
+ this.fds.set(fd, {
23207
+ kind: isDir ? "dir" : "file",
23208
+ path,
23209
+ pos: (fdflags & FD_APPEND) !== 0 ? data.length : 0,
23210
+ data,
23211
+ dirty: false,
23212
+ fdflags,
23213
+ /* The requested rights are recorded but not enforced: the
23214
+ * container's own uid/gid permission bits already gated the open,
23215
+ * and layering WASI's capability set on top of them only invents
23216
+ * ENOTCAPABLE failures for modules whose toolchain requests a
23217
+ * narrower set than it then uses. A module may still drop its own
23218
+ * rights through `fd_fdstat_set_rights`, and that is honoured. */
23219
+ rights: RIGHTS_ALL
23220
+ });
23221
+ this.view.setUint32(fdPtr, fd, true);
23222
+ return E.SUCCESS;
23223
+ } catch (e) {
23224
+ return this.errnoOf(e);
23225
+ }
23226
+ },
23227
+ path_filestat_get: (dirfd, flags, pathPtr, pathLen, statPtr) => {
23228
+ const resolved = this.resolveAt(dirfd, this.readString(pathPtr, pathLen));
23229
+ if (typeof resolved === "number") return resolved;
23230
+ return this.writeFilestat(statPtr, resolved.path, (flags & LOOKUP_SYMLINK_FOLLOW) !== 0);
23231
+ },
23232
+ path_filestat_set_times: (dirfd, _flags, pathPtr, pathLen, atim, mtim, _fstflags) => {
23233
+ const resolved = this.resolveAt(dirfd, this.readString(pathPtr, pathLen));
23234
+ if (typeof resolved === "number") return resolved;
23235
+ try {
23236
+ vfs.utimes(resolved.path, Number(atim) / 1e6, Number(mtim) / 1e6, cred);
23237
+ return E.SUCCESS;
23238
+ } catch (e) {
23239
+ return this.errnoOf(e);
23240
+ }
23241
+ },
23242
+ path_create_directory: (dirfd, pathPtr, pathLen) => {
23243
+ const resolved = this.resolveAt(dirfd, this.readString(pathPtr, pathLen));
23244
+ if (typeof resolved === "number") return resolved;
23245
+ try {
23246
+ vfs.mkdir(resolved.path, { cred, mode: 493 });
23247
+ return E.SUCCESS;
23248
+ } catch (e) {
23249
+ return this.errnoOf(e);
23250
+ }
23251
+ },
23252
+ path_remove_directory: (dirfd, pathPtr, pathLen) => {
23253
+ const resolved = this.resolveAt(dirfd, this.readString(pathPtr, pathLen));
23254
+ if (typeof resolved === "number") return resolved;
23255
+ try {
23256
+ vfs.rmdir(resolved.path, cred);
23257
+ return E.SUCCESS;
23258
+ } catch (e) {
23259
+ return this.errnoOf(e);
23260
+ }
23261
+ },
23262
+ path_unlink_file: (dirfd, pathPtr, pathLen) => {
23263
+ const resolved = this.resolveAt(dirfd, this.readString(pathPtr, pathLen));
23264
+ if (typeof resolved === "number") return resolved;
23265
+ try {
23266
+ vfs.unlink(resolved.path, cred);
23267
+ return E.SUCCESS;
23268
+ } catch (e) {
23269
+ return this.errnoOf(e);
23270
+ }
23271
+ },
23272
+ path_rename: (oldFd, oldPtr, oldLen, newFd, newPtr, newLen) => {
23273
+ const from = this.resolveAt(oldFd, this.readString(oldPtr, oldLen));
23274
+ if (typeof from === "number") return from;
23275
+ const to = this.resolveAt(newFd, this.readString(newPtr, newLen));
23276
+ if (typeof to === "number") return to;
23277
+ try {
23278
+ vfs.rename(from.path, to.path, cred);
23279
+ return E.SUCCESS;
23280
+ } catch (e) {
23281
+ return this.errnoOf(e);
23282
+ }
23283
+ },
23284
+ path_symlink: (targetPtr, targetLen, dirfd, pathPtr, pathLen) => {
23285
+ const resolved = this.resolveAt(dirfd, this.readString(pathPtr, pathLen));
23286
+ if (typeof resolved === "number") return resolved;
23287
+ try {
23288
+ vfs.symlink(this.readString(targetPtr, targetLen), resolved.path, cred);
23289
+ return E.SUCCESS;
23290
+ } catch (e) {
23291
+ return this.errnoOf(e);
23292
+ }
23293
+ },
23294
+ path_readlink: (dirfd, pathPtr, pathLen, buf, bufLen, usedPtr) => {
23295
+ const resolved = this.resolveAt(dirfd, this.readString(pathPtr, pathLen));
23296
+ if (typeof resolved === "number") return resolved;
23297
+ try {
23298
+ const target = encoder6.encode(vfs.readlink(resolved.path, cred));
23299
+ const size = Math.min(target.length, bufLen);
23300
+ this.bytes.set(target.subarray(0, size), buf);
23301
+ this.view.setUint32(usedPtr, size, true);
23302
+ return E.SUCCESS;
23303
+ } catch (e) {
23304
+ return this.errnoOf(e);
23305
+ }
23306
+ },
23307
+ path_link: (oldFd, _flags, oldPtr, oldLen, newFd, newPtr, newLen) => {
23308
+ const from = this.resolveAt(oldFd, this.readString(oldPtr, oldLen));
23309
+ if (typeof from === "number") return from;
23310
+ const to = this.resolveAt(newFd, this.readString(newPtr, newLen));
23311
+ if (typeof to === "number") return to;
23312
+ try {
23313
+ vfs.link(from.path, to.path, cred);
23314
+ return E.SUCCESS;
23315
+ } catch (e) {
23316
+ return this.errnoOf(e);
23317
+ }
23318
+ },
23319
+ /* ── polling ──────────────────────────────────────────────────────── */
23320
+ /**
23321
+ * Clock subscriptions only, and they resolve immediately.
23322
+ *
23323
+ * Everything here is synchronous, so a genuine wait would deadlock the
23324
+ * whole container rather than yield to anything. Reporting the timer as
23325
+ * already elapsed lets `sleep` and timeout loops complete instead of
23326
+ * hanging; subscriptions on a descriptor report readiness, since stdin is
23327
+ * already buffered and stdout never blocks.
23328
+ */
23329
+ poll_oneoff: (inPtr, outPtr, nsubs, neventsPtr) => {
23330
+ const view = this.view;
23331
+ for (let i = 0; i < nsubs; i++) {
23332
+ const sub = inPtr + i * 48;
23333
+ const userdata = view.getBigUint64(sub, true);
23334
+ const type = view.getUint8(sub + 8);
23335
+ const event = outPtr + i * 32;
23336
+ view.setBigUint64(event, userdata, true);
23337
+ view.setUint16(event + 8, E.SUCCESS, true);
23338
+ view.setUint8(event + 10, type);
23339
+ view.setBigUint64(event + 16, 0n, true);
23340
+ view.setUint16(event + 24, 0, true);
23341
+ }
23342
+ view.setUint32(neventsPtr, nsubs, true);
23343
+ return E.SUCCESS;
23344
+ },
23345
+ /* ── sockets ──────────────────────────────────────────────────────── */
23346
+ sock_accept: () => E.NOTSUP,
23347
+ sock_recv: () => E.NOTSUP,
23348
+ sock_send: () => E.NOTSUP,
23349
+ sock_shutdown: () => E.NOTSUP
23350
+ };
23351
+ }
23352
+ /* ── shared read/write paths ────────────────────────────────────────────── */
23353
+ readInto(fd, iovs, iovsLen, nreadPtr, offset) {
23354
+ const entry = this.fds.get(fd);
23355
+ if (!entry) return E.BADF;
23356
+ if (entry.kind === "dir") return E.ISDIR;
23357
+ if (entry.kind === "stdout" || entry.kind === "stderr") return E.BADF;
23358
+ if ((entry.rights & RIGHT_FD_READ) === 0n) return E.NOTCAPABLE;
23359
+ let position = offset ?? entry.pos;
23360
+ let read = 0;
23361
+ const bytes = this.bytes;
23362
+ for (const iov of this.iovecs(iovs, iovsLen)) {
23363
+ if (position >= entry.data.length) break;
23364
+ const chunk = entry.data.subarray(position, position + iov.len);
23365
+ bytes.set(chunk, iov.base);
23366
+ position += chunk.length;
23367
+ read += chunk.length;
23368
+ if (chunk.length < iov.len) break;
23369
+ }
23370
+ if (offset === null) entry.pos = position;
23371
+ this.view.setUint32(nreadPtr, read, true);
23372
+ return E.SUCCESS;
23373
+ }
23374
+ writeFrom(fd, iovs, iovsLen, nwrittenPtr, offset) {
23375
+ const entry = this.fds.get(fd);
23376
+ if (!entry) return E.BADF;
23377
+ if (entry.kind === "dir") return E.ISDIR;
23378
+ if (entry.kind === "stdin") return E.BADF;
23379
+ if ((entry.rights & RIGHT_FD_WRITE) === 0n) return E.NOTCAPABLE;
23380
+ const chunks = [];
23381
+ let total = 0;
23382
+ const bytes = this.bytes;
23383
+ for (const iov of this.iovecs(iovs, iovsLen)) {
23384
+ chunks.push(bytes.slice(iov.base, iov.base + iov.len));
23385
+ total += iov.len;
23386
+ }
23387
+ if (entry.kind === "stdout" || entry.kind === "stderr") {
23388
+ const sink = entry.kind === "stdout" ? this.opts.stdout : this.opts.stderr;
23389
+ for (const chunk of chunks) sink(chunk);
23390
+ this.view.setUint32(nwrittenPtr, total, true);
23391
+ return E.SUCCESS;
23392
+ }
23393
+ let position = offset ?? ((entry.fdflags & FD_APPEND) !== 0 ? entry.data.length : entry.pos);
23394
+ const end = Math.max(entry.data.length, position + total);
23395
+ if (end > entry.data.length) {
23396
+ const grown = new Uint8Array(end);
23397
+ grown.set(entry.data);
23398
+ entry.data = grown;
23399
+ }
23400
+ for (const chunk of chunks) {
23401
+ entry.data.set(chunk, position);
23402
+ position += chunk.length;
23403
+ }
23404
+ if (offset === null) entry.pos = position;
23405
+ entry.dirty = true;
23406
+ const status = this.flush(entry);
23407
+ if (status !== E.SUCCESS) return status;
23408
+ this.view.setUint32(nwrittenPtr, total, true);
23409
+ return E.SUCCESS;
23410
+ }
23411
+ allocateFd() {
23412
+ while (this.fds.has(this.nextFd)) this.nextFd++;
23413
+ return this.nextFd++;
23414
+ }
23415
+ envPairs() {
23416
+ return Object.entries(this.opts.env).filter(([, value]) => value !== void 0).map(([key, value]) => `${key}=${value}`);
23417
+ }
23418
+ /** Flush and drop every open descriptor. Safe to call twice. */
23419
+ close() {
23420
+ for (const [fd, entry] of this.fds) {
23421
+ if (entry.kind === "file") this.flush(entry);
23422
+ if (fd > 2) this.fds.delete(fd);
23423
+ }
23424
+ }
23425
+ };
23426
+
23427
+ // src/runtime/wasm.ts
23428
+ init_path();
23429
+ var WASM_MAGIC = [0, 97, 115, 109];
23430
+ function isWasmBinary(head2) {
23431
+ return head2.length >= 4 && WASM_MAGIC.every((byte, i) => head2[i] === byte);
23432
+ }
23433
+ function analyse(module) {
23434
+ const imports = WebAssembly.Module.imports(module);
23435
+ const exports = WebAssembly.Module.exports(module).map((e) => e.name);
23436
+ const modules = [...new Set(imports.map((i) => i.module))];
23437
+ const importsMemory = imports.some((i) => i.kind === "memory");
23438
+ const wantsThreads = imports.some(
23439
+ (i) => i.kind === "memory" && i.type?.shared === true
23440
+ );
23441
+ let flavour = "core";
23442
+ if (modules.some((m) => m === "wasi_snapshot_preview1" || m === "wasi_unstable")) flavour = "wasi";
23443
+ else if (modules.some((m) => m.startsWith("wasi:"))) flavour = "wasi-component";
23444
+ else if (modules.includes("gojs") || modules.includes("go")) flavour = "go-js";
23445
+ else if (modules.some((m) => m.startsWith("./") || m.endsWith("_bg.js"))) flavour = "wasm-bindgen";
23446
+ else if (modules.includes("env") || modules.includes("wasi_snapshot_preview1")) flavour = "emscripten";
23447
+ return { flavour, modules, importsMemory, wantsThreads, exports };
23448
+ }
23449
+ var compiled2 = /* @__PURE__ */ new Map();
23450
+ var MAX_CACHED = 16;
23451
+ async function compile(ctx, path) {
23452
+ const st = ctx.vfs.stat(path, { cred: ctx.cred });
23453
+ const hit = compiled2.get(path);
23454
+ if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) return hit.module;
23455
+ const bytes = ctx.vfs.readFile(path, ctx.cred);
23456
+ const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
23457
+ const module = await WebAssembly.compile(buffer);
23458
+ if (compiled2.size >= MAX_CACHED) {
23459
+ const oldest = compiled2.keys().next().value;
23460
+ if (oldest !== void 0) compiled2.delete(oldest);
23461
+ }
23462
+ compiled2.set(path, { mtimeMs: st.mtimeMs, size: st.size, module });
23463
+ return module;
23464
+ }
23465
+ async function runWasi(ctx, path, argv0) {
23466
+ let module;
23467
+ try {
23468
+ module = await compile(ctx, path);
23469
+ } catch (e) {
23470
+ ctx.warn(`${path}: not a valid WebAssembly module (${e instanceof Error ? e.message : String(e)})`);
23471
+ return 126;
23472
+ }
23473
+ const info = analyse(module);
23474
+ if (info.flavour !== "wasi") {
23475
+ for (const line of explainFlavour(info, path, findLoader(ctx, path))) ctx.stderr.write(line + "\n");
23476
+ return 126;
23477
+ }
23478
+ if (info.wantsThreads) {
23479
+ ctx.warn("this module was built with threads (shared memory), which this container cannot provide");
23480
+ return 126;
23481
+ }
23482
+ let stdin = new Uint8Array(0);
23483
+ if (!ctx.stdin.isTTY && !ctx.stdin.interactive) {
23484
+ stdin = await ctx.stdin.readAll();
23485
+ }
23486
+ const wasi = new WasiPreview1({
23487
+ vfs: ctx.vfs,
23488
+ cred: ctx.cred,
23489
+ args: [argv0 ?? basename(path), ...ctx.args],
23490
+ env: ctx.env,
23491
+ cwd: ctx.cwd,
23492
+ stdin,
23493
+ stdout: (bytes) => ctx.write(bytes),
23494
+ stderr: (bytes) => ctx.stderr.write(bytes),
23495
+ now: ctx.kernel.now
23496
+ });
23497
+ let instance;
23498
+ try {
23499
+ instance = await WebAssembly.instantiate(module, wasi.imports);
23500
+ } catch (e) {
23501
+ ctx.warn(`cannot start: ${e instanceof Error ? e.message : String(e)}`);
23502
+ return 126;
23503
+ }
23504
+ try {
23505
+ wasi.bind(instance);
23506
+ } catch (e) {
23507
+ ctx.warn(e instanceof Error ? e.message : String(e));
23508
+ return 126;
23509
+ }
23510
+ const start = instance.exports._start ?? instance.exports._initialize;
23511
+ if (typeof start !== "function") {
23512
+ ctx.warn("module exports neither _start nor _initialize; it is a library, not a command");
23513
+ return 126;
23514
+ }
23515
+ try {
23516
+ start();
23517
+ return wasi.exited ?? 0;
23518
+ } catch (e) {
23519
+ if (e instanceof WasiExit) return e.code;
23520
+ ctx.warn(e instanceof Error ? e.message : String(e));
23521
+ return 128 + 6;
23522
+ } finally {
23523
+ wasi.close();
23524
+ }
23525
+ }
23526
+ function findLoader(ctx, path) {
23527
+ const stem = join(dirname(path), basename(path, ".wasm")).replace(/_bg$/, "");
23528
+ for (const candidate of [`${stem}.js`, `${stem}.mjs`, `${stem}.cjs`]) {
23529
+ if (ctx.vfs.lexists(candidate)) return candidate;
23530
+ }
23531
+ return null;
23532
+ }
23533
+ function explainFlavour(info, path, loader) {
23534
+ const name = basename(path);
23535
+ if (loader) {
23536
+ return [
23537
+ `${name}: this module is loaded by a JavaScript harness, not executed directly.`,
23538
+ "It imports host functions that only that harness provides. Run it instead:",
23539
+ ` node ${loader}`
23540
+ ];
23541
+ }
23542
+ switch (info.flavour) {
23543
+ case "wasi-component":
23544
+ return [
23545
+ `${name}: this is a WebAssembly component (WASI preview 2), not a core module.`,
23546
+ "Components cannot be instantiated directly. Transpile it to a core module first:",
23547
+ " npx @bytecodealliance/jco transpile " + name + " -o out"
23548
+ ];
23549
+ case "emscripten":
23550
+ return [
23551
+ `${name}: this is an Emscripten build, which is only half a program.`,
23552
+ "It needs the JavaScript loader generated alongside it. Run that instead:",
23553
+ ` node ${basename(path, ".wasm")}.js`
23554
+ ];
23555
+ case "wasm-bindgen":
23556
+ return [
23557
+ `${name}: this is a wasm-bindgen build; its imports come from a JS glue module.`,
23558
+ "Load it from Node rather than executing it:",
23559
+ ` node -e "import('./${basename(path, "_bg.wasm")}.js')"`
23560
+ ];
23561
+ case "go-js":
23562
+ return [
23563
+ `${name}: this was built with GOOS=js, which needs Go's wasm_exec.js harness.`,
23564
+ "Rebuild it for WASI instead, and it will run here directly:",
23565
+ " GOOS=wasip1 GOARCH=wasm go build -o " + name
23566
+ ];
23567
+ default:
23568
+ return [
23569
+ `${name}: a core WebAssembly module with no WASI imports.`,
23570
+ "There is no entry point to call and no way to reach stdin, stdout or the",
23571
+ "filesystem. Build it for wasm32-wasi to run it as a command."
23572
+ ];
23573
+ }
23574
+ }
23575
+ var wasmFormat = {
23576
+ name: "wasm",
23577
+ priority: 10,
23578
+ matches: (head2) => isWasmBinary(head2),
23579
+ describe: () => "WebAssembly (wasm) binary module",
23580
+ run: (ctx, path) => runWasi(ctx, path, ctx.name)
23581
+ };
23582
+ var wasm = defineCommand({
23583
+ name: "wasm",
23584
+ path: "/usr/bin/wasm",
23585
+ summary: "run and inspect WebAssembly modules",
23586
+ usage: "wasm run <module.wasm> [args...] | wasm info <module.wasm>",
23587
+ manual: `Runs a WASI command module against the container's filesystem, or
23588
+ reports what a module was built for.
23589
+
23590
+ Any file starting with the WebAssembly magic bytes is also executable
23591
+ directly, so 'chmod +x tool.wasm && ./tool.wasm' does the same thing as
23592
+ 'wasm run tool.wasm'.`,
23593
+ async run(ctx) {
23594
+ const [subcommand, target, ...rest] = ctx.args;
23595
+ if (subcommand === void 0 || subcommand === "--help" || subcommand === "-h") {
23596
+ ctx.line("Usage: wasm run <module.wasm> [args...]");
23597
+ ctx.line(" wasm info <module.wasm>");
23598
+ return subcommand === void 0 ? 2 : 0;
23599
+ }
23600
+ if (target === void 0) return ctx.fail(`${subcommand}: a module path is required`, 2);
23601
+ const path = ctx.path(target);
23602
+ if (!ctx.vfs.lexists(path)) return ctx.fail(`${target}: No such file or directory`, 1);
23603
+ if (subcommand === "run") {
23604
+ return await runWasi({ ...ctx, args: rest, argv: [basename(path), ...rest] }, path, basename(path));
23605
+ }
23606
+ if (subcommand === "info") {
23607
+ let module;
23608
+ try {
23609
+ module = await compile(ctx, path);
23610
+ } catch (e) {
23611
+ return ctx.fail(`${target}: ${e instanceof Error ? e.message : String(e)}`, 1);
23612
+ }
23613
+ const info = analyse(module);
23614
+ const runnable = info.flavour === "wasi" && !info.wantsThreads;
23615
+ ctx.line(`file: ${path}`);
23616
+ ctx.line(`flavour: ${info.flavour}`);
23617
+ ctx.line(`imports: ${info.modules.join(", ") || "(none)"}`);
23618
+ ctx.line(`threads: ${info.wantsThreads ? "yes (unsupported)" : "no"}`);
23619
+ ctx.line(`entry: ${info.exports.includes("_start") ? "_start" : info.exports.includes("_initialize") ? "_initialize (reactor)" : "none"}`);
23620
+ ctx.line(`runnable: ${runnable ? "yes" : "no"}`);
23621
+ if (!runnable) {
23622
+ ctx.line("");
23623
+ for (const line of explainFlavour(info, path, findLoader(ctx, path))) ctx.line(line);
23624
+ }
23625
+ return 0;
23626
+ }
23627
+ return ctx.fail(`unknown subcommand: ${subcommand}`, 2);
23628
+ }
23629
+ });
23630
+ function wasmCommands() {
23631
+ return [wasm];
23632
+ }
23633
+
23634
+ // src/runtime/native-format.ts
23635
+ init_path();
23636
+ var ELF_MACHINES = {
23637
+ 2: "SPARC",
23638
+ 3: "x86",
23639
+ 8: "MIPS",
23640
+ 20: "PowerPC",
23641
+ 21: "PowerPC 64-bit",
23642
+ 22: "S/390",
23643
+ 40: "ARM",
23644
+ 42: "SuperH",
23645
+ 50: "IA-64",
23646
+ 62: "x86-64",
23647
+ 183: "AArch64",
23648
+ 243: "RISC-V"
23649
+ };
23650
+ var ELF_TYPES = {
23651
+ 1: "relocatable",
23652
+ 2: "executable",
23653
+ 3: "shared object",
23654
+ 4: "core dump"
23655
+ };
23656
+ function describeElf(head2) {
23657
+ const bits = head2[4] === 2 ? "64-bit" : "32-bit";
23658
+ const endian = head2[5] === 2 ? "MSB" : "LSB";
23659
+ if (head2.length < 20) return `ELF ${bits} ${endian} object`;
23660
+ const view = new DataView(head2.buffer, head2.byteOffset, head2.byteLength);
23661
+ const little = head2[5] !== 2;
23662
+ const type = ELF_TYPES[view.getUint16(16, little)] ?? "object";
23663
+ const machine = view.getUint16(18, little);
23664
+ return `ELF ${bits} ${endian} ${type}, ${ELF_MACHINES[machine] ?? `machine 0x${machine.toString(16)}`}`;
23665
+ }
23666
+ var SUGGEST_WASM = [
23667
+ "Nothing in this container can execute machine code \u2014 there is no CPU here to",
23668
+ "run it on, only JavaScript and WebAssembly.",
23669
+ "",
23670
+ "If the tool has a WebAssembly build, it will run here as-is: drop the .wasm",
23671
+ "file in /usr/local/bin, chmod +x it, and call it by name."
23672
+ ];
23673
+ var elfFormat = {
23674
+ name: "elf",
23675
+ priority: 20,
23676
+ matches: (head2) => head2.length >= 4 && head2[0] === 127 && head2[1] === 69 && head2[2] === 76 && head2[3] === 70,
23677
+ describe: describeElf,
23678
+ explain: (head2, path) => [`${basename(path)}: ${describeElf(head2)}.`, ...SUGGEST_WASM]
23679
+ };
23680
+ var machoFormat = {
23681
+ name: "mach-o",
23682
+ priority: 20,
23683
+ matches: (head2) => {
23684
+ if (head2.length < 4) return false;
23685
+ const magic = new DataView(head2.buffer, head2.byteOffset, head2.byteLength).getUint32(0, false);
23686
+ return magic === 4277009102 || magic === 4277009103 || magic === 3472551422 || magic === 3489328638 || magic === 3405691582;
23687
+ },
23688
+ describe: () => "Mach-O executable (macOS)",
23689
+ explain: (_head, path) => [`${basename(path)}: Mach-O executable (macOS native binary).`, ...SUGGEST_WASM]
23690
+ };
23691
+ var peFormat = {
23692
+ name: "pe",
23693
+ priority: 25,
23694
+ // `MZ` alone is weak, so require the DOS stub text that every real PE carries.
23695
+ matches: (head2) => head2.length >= 64 && head2[0] === 77 && head2[1] === 90 && new TextDecoder().decode(head2.subarray(0, 128)).includes("This program cannot be run in DOS mode"),
23696
+ describe: () => "PE32+ executable (Windows)",
23697
+ explain: (_head, path) => [`${basename(path)}: PE executable (Windows native binary).`, ...SUGGEST_WASM]
23698
+ };
23699
+ function nativeFormats() {
23700
+ return [elfFormat, machoFormat, peFormat];
23701
+ }
22638
23702
  var DECLARES_EXPORTS = /(?:^|[\s;{)])(?:var|let|const|function|class)\s+exports\b/;
22639
23703
  var NAME = "exports";
22640
23704
  function renameShadowedExports(source) {
@@ -22854,6 +23918,143 @@ var NOTHING = [];
22854
23918
  var PROPERTY = ["property", "key"];
22855
23919
  var LABEL = ["label"];
22856
23920
 
23921
+ // src/pkg/install.ts
23922
+ init_path();
23923
+ var GLOBAL_PREFIX = "/usr/local";
23924
+ var GLOBAL_MODULES = `${GLOBAL_PREFIX}/lib/node_modules`;
23925
+ var GLOBAL_BIN = `${GLOBAL_PREFIX}/bin`;
23926
+ function readManifestAt(ctx, dir3) {
23927
+ try {
23928
+ return JSON.parse(ctx.vfs.readText(join(dir3, "package.json"), ctx.cred));
23929
+ } catch {
23930
+ return null;
23931
+ }
23932
+ }
23933
+ function binEntries(manifest, packageName) {
23934
+ if (typeof manifest.bin === "string") return { [basename(packageName)]: manifest.bin };
23935
+ if (manifest.bin && typeof manifest.bin === "object") return { ...manifest.bin };
23936
+ return {};
23937
+ }
23938
+ function linkPackageBinaries(ctx, packageDir, packageName, binDir) {
23939
+ const manifest = readManifestAt(ctx, packageDir);
23940
+ if (!manifest) return [];
23941
+ const linked = [];
23942
+ if (!ctx.vfs.lexists(binDir)) ctx.vfs.mkdir(binDir, { recursive: true, mode: 493 });
23943
+ for (const [name, relative2] of Object.entries(binEntries(manifest, packageName))) {
23944
+ const target = resolve(packageDir, relative2);
23945
+ if (!ctx.vfs.lexists(target)) continue;
23946
+ let script;
23947
+ try {
23948
+ script = isJavaScriptEntry(ctx, target) ? `#!/bin/sh
23949
+ exec node ${quote(target)} "$@"
23950
+ ` : `#!/bin/sh
23951
+ exec ${quote(target)} "$@"
23952
+ `;
23953
+ } catch {
23954
+ continue;
23955
+ }
23956
+ const link = join(binDir, name);
23957
+ ctx.vfs.writeFile(link, script, { privileged: true, mode: 493 });
23958
+ ctx.vfs.chmod(link, 493);
23959
+ try {
23960
+ ctx.vfs.chmod(target, 493, ctx.cred);
23961
+ } catch {
23962
+ }
23963
+ linked.push(name);
23964
+ }
23965
+ return linked;
23966
+ }
23967
+ function quote(value) {
23968
+ return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
23969
+ }
23970
+ function isJavaScriptEntry(ctx, target) {
23971
+ const head2 = ctx.vfs.readFile(ctx.vfs.realpath(target, ctx.cred), ctx.cred).subarray(0, 256);
23972
+ if (isWasmBinary(head2)) return false;
23973
+ const text = new TextDecoder().decode(head2);
23974
+ if (text.startsWith("#!")) {
23975
+ return false;
23976
+ }
23977
+ if (head2.includes(0)) return false;
23978
+ const ext = extname(target);
23979
+ if (JS_EXTENSIONS.has(ext) && ext !== "") return true;
23980
+ return !/^\s*(echo|set|export|if|exec)\s/.test(text);
23981
+ }
23982
+ var LIFECYCLE = ["preinstall", "install", "postinstall"];
23983
+ async function runLifecycleScripts(ctx, packageDir, packageName, opts = {}) {
23984
+ const manifest = readManifestAt(ctx, packageDir);
23985
+ const scripts = manifest?.scripts;
23986
+ if (!scripts) return;
23987
+ for (const hook of LIFECYCLE) {
23988
+ const script = scripts[hook];
23989
+ if (!script) continue;
23990
+ if (!opts.quiet) ctx.stderr.write(`
23991
+ > ${packageName} ${hook}
23992
+ > ${script}
23993
+ `);
23994
+ const binDir = join(packageDir, "node_modules", ".bin");
23995
+ const result = await ctx.kernel.run(["/bin/sh", "-c", script], {
23996
+ cwd: packageDir,
23997
+ cred: ctx.cred,
23998
+ ppid: ctx.proc.pid,
23999
+ env: {
24000
+ ...ctx.env,
24001
+ PATH: `${binDir}:${GLOBAL_BIN}:${ctx.env.PATH ?? ""}`,
24002
+ npm_lifecycle_event: hook,
24003
+ npm_lifecycle_script: script,
24004
+ npm_package_name: manifest.name ?? packageName,
24005
+ npm_package_version: manifest.version ?? "",
24006
+ INIT_CWD: ctx.cwd
24007
+ },
24008
+ // Hooks that wait on something that will never arrive are common enough
24009
+ // to need a bound; without one a single package hangs the install.
24010
+ timeoutMs: opts.timeoutMs ?? 6e4
24011
+ });
24012
+ if (result.exitCode !== 0) {
24013
+ ctx.stderr.write(`npm warn ${packageName}: ${hook} script failed (exit ${result.exitCode})
24014
+ `);
24015
+ const detail = (result.stderr || result.stdout).trim().split("\n").slice(-3);
24016
+ for (const line of detail) if (line) ctx.stderr.write(`npm warn ${line}
24017
+ `);
24018
+ return;
24019
+ }
24020
+ if (!opts.quiet && result.stdout) ctx.stderr.write(result.stdout);
24021
+ }
24022
+ }
24023
+ var JS_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ""]);
24024
+ function inspectPackage(ctx, packageDir, packageName) {
24025
+ const manifest = readManifestAt(ctx, packageDir);
24026
+ if (!manifest) return { verdict: "empty", lines: [`${packageName}: installed, but has no package.json`] };
24027
+ const bins = binEntries(manifest, packageName);
24028
+ if (Object.keys(bins).length === 0) {
24029
+ return { verdict: "ok", lines: [] };
24030
+ }
24031
+ const nativeBins = Object.entries(bins).filter(([, relative2]) => !JS_EXTENSIONS.has(extname(relative2)));
24032
+ const platformDeps = Object.keys(manifest.optionalDependencies ?? {}).filter(
24033
+ (dep) => /-(linux|darwin|win32|windows|freebsd)(-|$)|-(x64|arm64|ia32)(-|$)/.test(dep)
24034
+ );
24035
+ if (nativeBins.length === 0 && platformDeps.length === 0) return { verdict: "ok", lines: [] };
24036
+ const lines = [];
24037
+ const label = manifest.name ?? packageName;
24038
+ if (nativeBins.length > 0) {
24039
+ const [name, relative2] = nativeBins[0];
24040
+ lines.push(`${label}: '${name}' points at ${relative2}, which is a compiled executable, not JavaScript.`);
24041
+ } else {
24042
+ lines.push(`${label}: ships its executable as a platform-specific package, not as JavaScript.`);
24043
+ }
24044
+ if (platformDeps.length > 0) {
24045
+ const shown = platformDeps.slice(0, 3).join(", ");
24046
+ lines.push(
24047
+ `Its real binary comes from ${platformDeps.length} platform packages (${shown}${platformDeps.length > 3 ? ", \u2026" : ""}),`
24048
+ );
24049
+ lines.push("each of which is native machine code for one OS and CPU.");
24050
+ }
24051
+ lines.push("");
24052
+ lines.push("This container has no CPU to run machine code on \u2014 only JavaScript and");
24053
+ lines.push("WebAssembly. The package is installed and its files are on disk, but the");
24054
+ lines.push("command it provides cannot start.");
24055
+ return { verdict: "native", lines };
24056
+ }
24057
+
22857
24058
  // src/pkg/index.ts
22858
24059
  init_path();
22859
24060
  var NPM_VERSION = "10.9.0";
@@ -22878,6 +24079,9 @@ function findProjectRoot(ctx) {
22878
24079
  function writeManifest(ctx, path, manifest) {
22879
24080
  ctx.vfs.writeFile(path, JSON.stringify(manifest, null, 2) + "\n", { cred: ctx.cred, mode: 420 });
22880
24081
  }
24082
+ function isGlobal(args) {
24083
+ return args.some((a) => a === "-g" || a === "--global" || a === "--location=global");
24084
+ }
22881
24085
  function splitPackageSpec(spec) {
22882
24086
  const at = spec.lastIndexOf("@");
22883
24087
  if (at <= 0) return { name: spec };
@@ -22910,19 +24114,93 @@ async function installPackages(ctx, specs, opts) {
22910
24114
  note(`added ${name}${version ? `@${version}` : ""}`);
22911
24115
  await installer.install(name, version, {
22912
24116
  onProgress,
22913
- persist: opts.save !== false,
24117
+ // A global install belongs to the prefix, not to whatever project
24118
+ // happens to be in the working directory, so nothing is recorded in
24119
+ // a manifest — which is also what npm does.
24120
+ persist: opts.global ? false : opts.save !== false,
22914
24121
  persistDev: opts.dev === true
22915
24122
  });
22916
24123
  }
22917
24124
  }
22918
24125
  normalizeBinDirectories(ctx, opts.cwd);
22919
24126
  normalizeEsmExports(ctx, opts.cwd);
24127
+ if (opts.global) {
24128
+ return await linkGlobalPackages(ctx, opts.cwd, specs, opts.quiet === true, opts.ignoreScripts !== true);
24129
+ }
24130
+ if (opts.ignoreScripts !== true) await runProjectLifecycleScripts(ctx, opts.cwd, specs, opts.quiet === true);
22920
24131
  return 0;
22921
24132
  } catch (e) {
22922
24133
  ctx.warn(`npm error ${e instanceof Error ? e.message : String(e)}`);
22923
24134
  return 1;
22924
24135
  }
22925
24136
  }
24137
+ async function runProjectLifecycleScripts(ctx, root, specs, quiet) {
24138
+ for (const spec of specs) {
24139
+ const { name } = splitPackageSpec(spec);
24140
+ const dir3 = join(root, "node_modules", name);
24141
+ if (ctx.vfs.lexists(dir3)) await runLifecycleScripts(ctx, dir3, name, { quiet });
24142
+ }
24143
+ normalizeBinDirectories(ctx, root);
24144
+ }
24145
+ async function linkGlobalPackages(ctx, stagingRoot, specs, quiet, runScripts) {
24146
+ const staged = join(stagingRoot, "node_modules");
24147
+ if (!ctx.vfs.lexists(staged)) return 0;
24148
+ if (!ctx.vfs.lexists(GLOBAL_MODULES)) ctx.vfs.mkdir(GLOBAL_MODULES, { recursive: true, mode: 493 });
24149
+ for (const entry of ctx.vfs.readdir(staged, ctx.cred)) {
24150
+ if (entry === ".bin") continue;
24151
+ const from = join(staged, entry);
24152
+ const to = join(GLOBAL_MODULES, entry);
24153
+ try {
24154
+ if (entry.startsWith("@")) {
24155
+ if (!ctx.vfs.lexists(to)) ctx.vfs.mkdir(to, { recursive: true, mode: 493 });
24156
+ for (const scoped of ctx.vfs.readdir(from, ctx.cred)) {
24157
+ const target = join(to, scoped);
24158
+ if (ctx.vfs.lexists(target)) ctx.vfs.rmrf(target, ctx.cred);
24159
+ ctx.vfs.copyTree(join(from, scoped), target, ctx.cred);
24160
+ }
24161
+ } else {
24162
+ if (ctx.vfs.lexists(to)) ctx.vfs.rmrf(to, ctx.cred);
24163
+ ctx.vfs.copyTree(from, to, ctx.cred);
24164
+ }
24165
+ } catch (e) {
24166
+ ctx.warn(`npm warn could not install ${entry} globally: ${e instanceof Error ? e.message : String(e)}`);
24167
+ }
24168
+ }
24169
+ ctx.vfs.rmrf(staged, ctx.cred);
24170
+ for (const leftover of ["package.json", "package-lock.json"]) {
24171
+ const path = join(stagingRoot, leftover);
24172
+ if (stagingRoot === "/" && ctx.vfs.lexists(path)) {
24173
+ try {
24174
+ ctx.vfs.unlink(path, ctx.cred);
24175
+ } catch {
24176
+ }
24177
+ }
24178
+ }
24179
+ let status = 0;
24180
+ for (const spec of specs) {
24181
+ const { name } = splitPackageSpec(spec);
24182
+ const dir3 = join(GLOBAL_MODULES, name);
24183
+ if (!ctx.vfs.lexists(dir3)) continue;
24184
+ if (runScripts) await runLifecycleScripts(ctx, dir3, name, { quiet });
24185
+ const linked = linkPackageBinaries(ctx, dir3, name, GLOBAL_BIN);
24186
+ const report = inspectPackage(ctx, dir3, name);
24187
+ if (report.verdict !== "ok") {
24188
+ ctx.stderr.write("\n");
24189
+ for (const line of report.lines) ctx.stderr.write(`npm warn ${line}
24190
+ `);
24191
+ status = report.verdict === "native" ? 1 : status;
24192
+ continue;
24193
+ }
24194
+ if (!quiet) {
24195
+ if (linked.length > 0) {
24196
+ for (const bin of linked) ctx.line(`${join(GLOBAL_BIN, bin)} -> ${join(dir3, "")}`);
24197
+ } else {
24198
+ ctx.line(`${name} installed to ${GLOBAL_MODULES} (it declares no commands)`);
24199
+ }
24200
+ }
24201
+ }
24202
+ return status;
24203
+ }
22926
24204
  var npm = defineCommand({
22927
24205
  name: "npm",
22928
24206
  path: "/usr/bin/npm",
@@ -22973,17 +24251,56 @@ var npm = defineCommand({
22973
24251
  { long: "production" },
22974
24252
  { long: "omit", arg: true },
22975
24253
  { long: "legacy-peer-deps" },
24254
+ { long: "ignore-scripts" },
22976
24255
  { short: "f", long: "force" }
22977
24256
  ], { allowUnknown: true });
24257
+ if (args.has("global")) {
24258
+ if (args.positional.length === 0) {
24259
+ ctx.warn("npm error a package name is required for a global install");
24260
+ return 1;
24261
+ }
24262
+ const staging = `/var/tmp/.npm-global-${ctx.proc.pid}`;
24263
+ ctx.vfs.mkdir(staging, { recursive: true, mode: 493 });
24264
+ try {
24265
+ return await installPackages(ctx, args.positional, {
24266
+ cwd: staging,
24267
+ save: false,
24268
+ global: true,
24269
+ ignoreScripts: args.has("ignore-scripts")
24270
+ });
24271
+ } finally {
24272
+ try {
24273
+ ctx.vfs.rmrf(staging, ctx.cred);
24274
+ } catch {
24275
+ }
24276
+ }
24277
+ }
22978
24278
  return await installPackages(ctx, args.positional, {
22979
24279
  dev: args.has("save-dev"),
22980
24280
  save: !args.has("no-save"),
22981
- cwd: root
24281
+ cwd: root,
24282
+ ignoreScripts: args.has("ignore-scripts")
22982
24283
  });
22983
24284
  }
22984
24285
  case "uninstall":
22985
24286
  case "remove":
22986
24287
  case "rm": {
24288
+ if (isGlobal(rest)) {
24289
+ for (const name of rest.filter((a) => !a.startsWith("-"))) {
24290
+ const dir3 = join(GLOBAL_MODULES, name);
24291
+ if (!ctx.vfs.lexists(dir3)) {
24292
+ ctx.warn(`npm warn ${name} is not installed globally`);
24293
+ continue;
24294
+ }
24295
+ for (const bin of Object.keys(binEntries(readManifestAt(ctx, dir3) ?? {}, name))) {
24296
+ const link = join(GLOBAL_BIN, bin);
24297
+ if (ctx.vfs.lexists(link)) ctx.vfs.rmrf(link, ctx.cred);
24298
+ }
24299
+ ctx.vfs.rmrf(dir3, ctx.cred);
24300
+ ctx.line(`removed ${name}`);
24301
+ }
24302
+ return 0;
24303
+ }
22987
24304
  const found = readManifest(ctx, root);
22988
24305
  for (const name of rest.filter((a) => !a.startsWith("-"))) {
22989
24306
  const target = join(root, "node_modules", name);
@@ -22999,6 +24316,16 @@ var npm = defineCommand({
22999
24316
  }
23000
24317
  case "ls":
23001
24318
  case "list": {
24319
+ if (isGlobal(rest)) {
24320
+ ctx.line(`${GLOBAL_PREFIX}/lib`);
24321
+ if (!ctx.vfs.lexists(GLOBAL_MODULES)) return 0;
24322
+ const names2 = ctx.vfs.readdir(GLOBAL_MODULES, ctx.cred).filter((n) => !n.startsWith("."));
24323
+ names2.forEach((name, idx) => {
24324
+ const manifest = readManifestAt(ctx, join(GLOBAL_MODULES, name));
24325
+ ctx.line(`${idx === names2.length - 1 ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500"} ${name}@${manifest?.version ?? ""}`);
24326
+ });
24327
+ return 0;
24328
+ }
23002
24329
  const found = readManifest(ctx, root);
23003
24330
  ctx.line(`${found?.manifest.name ?? basename(root)}@${found?.manifest.version ?? "1.0.0"} ${root}`);
23004
24331
  const modules = join(root, "node_modules");
@@ -23046,13 +24373,49 @@ var npm = defineCommand({
23046
24373
  ctx.line("");
23047
24374
  return await runScript(ctx, root, script, scriptArgs, found.manifest);
23048
24375
  }
24376
+ /* Re-run install hooks and rebuild the `$PATH` shims for packages that
24377
+ * are already on disk. Useful here for the same reason it is useful on a
24378
+ * real system — a package whose entry point was generated, replaced or
24379
+ * repaired after installation needs its links redone — and it works
24380
+ * offline, since nothing is fetched. */
24381
+ case "rebuild": {
24382
+ const global = isGlobal(rest);
24383
+ const modulesDir = global ? GLOBAL_MODULES : join(root, "node_modules");
24384
+ const binDir = global ? GLOBAL_BIN : join(root, "node_modules", ".bin");
24385
+ if (!ctx.vfs.lexists(modulesDir)) return ctx.fail(`nothing installed in ${modulesDir}`, 1);
24386
+ const named = rest.filter((a) => !a.startsWith("-"));
24387
+ const targets = named.length > 0 ? named : ctx.vfs.readdir(modulesDir, ctx.cred).filter((n) => !n.startsWith("."));
24388
+ let status = 0;
24389
+ for (const name of targets) {
24390
+ const dir3 = join(modulesDir, name);
24391
+ if (!ctx.vfs.lexists(dir3)) {
24392
+ ctx.warn(`npm warn ${name} is not installed`);
24393
+ status = 1;
24394
+ continue;
24395
+ }
24396
+ if (!rest.includes("--ignore-scripts")) await runLifecycleScripts(ctx, dir3, name, { quiet: true });
24397
+ const linked = linkPackageBinaries(ctx, dir3, name, binDir);
24398
+ const report = inspectPackage(ctx, dir3, name);
24399
+ if (report.verdict !== "ok") {
24400
+ for (const line of report.lines) ctx.stderr.write(`npm warn ${line}
24401
+ `);
24402
+ status = 1;
24403
+ continue;
24404
+ }
24405
+ for (const bin of linked) ctx.line(`${join(binDir, bin)} -> ${dir3}`);
24406
+ }
24407
+ return status;
24408
+ }
23049
24409
  case "exec":
23050
24410
  return await runScript(ctx, root, rest.join(" "), [], readManifest(ctx, root)?.manifest ?? {});
23051
24411
  case "root":
23052
- ctx.line(join(root, "node_modules"));
24412
+ ctx.line(isGlobal(rest) ? GLOBAL_MODULES : join(root, "node_modules"));
23053
24413
  return 0;
23054
24414
  case "prefix":
23055
- ctx.line(root);
24415
+ ctx.line(isGlobal(rest) ? GLOBAL_PREFIX : root);
24416
+ return 0;
24417
+ case "bin":
24418
+ ctx.line(isGlobal(rest) ? GLOBAL_BIN : join(root, "node_modules", ".bin"));
23056
24419
  return 0;
23057
24420
  case "config":
23058
24421
  if (rest[0] === "get") {
@@ -23095,7 +24458,7 @@ async function runScript(ctx, root, script, extraArgs, manifest) {
23095
24458
  npm_config_user_agent: `npm/${NPM_VERSION} node/${NODE_VERSION} linux x64`,
23096
24459
  INIT_CWD: ctx.cwd
23097
24460
  };
23098
- const commandLine = extraArgs.length ? `${script} ${extraArgs.map(quote).join(" ")}` : script;
24461
+ const commandLine = extraArgs.length ? `${script} ${extraArgs.map(quote2).join(" ")}` : script;
23099
24462
  return await ctx.kernel.spawn(["/bin/sh", "-c", commandLine], {
23100
24463
  cwd: root,
23101
24464
  env: env2,
@@ -23106,7 +24469,7 @@ async function runScript(ctx, root, script, extraArgs, manifest) {
23106
24469
  stderr: ctx.stderr
23107
24470
  }).wait();
23108
24471
  }
23109
- function quote(value) {
24472
+ function quote2(value) {
23110
24473
  return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
23111
24474
  }
23112
24475
  function normalizeBinDirectories(ctx, root) {
@@ -23462,11 +24825,16 @@ function allCommands() {
23462
24825
  ...nodeCommands(),
23463
24826
  ...pythonCommands(),
23464
24827
  ...ffmpegCommands(),
24828
+ ...wasmCommands(),
23465
24829
  ...packageCommands()
23466
24830
  ];
23467
24831
  }
24832
+ function allFormats() {
24833
+ return [wasmFormat, ...nativeFormats()];
24834
+ }
23468
24835
  function installUserland(kernel) {
23469
24836
  kernel.installCommands(allCommands());
24837
+ kernel.formats.registerAll(allFormats());
23470
24838
  const vfs = kernel.vfs;
23471
24839
  if (!vfs.lexists("/usr/bin/reset")) {
23472
24840
  vfs.writeFile("/usr/bin/reset", "#!/bin/sh\nprintf '\\033c'\n", { privileged: true, mode: 493 });
@@ -23611,15 +24979,15 @@ var Session = class {
23611
24979
  async run(command, opts = {}) {
23612
24980
  if (this.closed) throw new Error("session is closed");
23613
24981
  const combined = [];
23614
- const decoder7 = new TextDecoder();
24982
+ const decoder8 = new TextDecoder();
23615
24983
  const stdout = new BufferSink((chunk) => {
23616
- const text = decoder7.decode(chunk, { stream: true });
24984
+ const text = decoder8.decode(chunk, { stream: true });
23617
24985
  combined.push(text);
23618
24986
  opts.onStdout?.(text);
23619
24987
  this.hooks.onStdout?.(text);
23620
24988
  });
23621
24989
  const stderr = new BufferSink((chunk) => {
23622
- const text = decoder7.decode(chunk, { stream: true });
24990
+ const text = decoder8.decode(chunk, { stream: true });
23623
24991
  combined.push(text);
23624
24992
  opts.onStderr?.(text);
23625
24993
  this.hooks.onStderr?.(text);
@@ -23955,15 +25323,15 @@ var Container = class _Container {
23955
25323
  }
23956
25324
  makeStdio(opts) {
23957
25325
  const combined = [];
23958
- const decoder7 = new TextDecoder();
25326
+ const decoder8 = new TextDecoder();
23959
25327
  const stdout = new BufferSink((chunk) => {
23960
- const text = decoder7.decode(chunk, { stream: true });
25328
+ const text = decoder8.decode(chunk, { stream: true });
23961
25329
  combined.push(text);
23962
25330
  opts.onStdout?.(text);
23963
25331
  this.hooks.onStdout?.(text);
23964
25332
  });
23965
25333
  const stderr = new BufferSink((chunk) => {
23966
- const text = decoder7.decode(chunk, { stream: true });
25334
+ const text = decoder8.decode(chunk, { stream: true });
23967
25335
  combined.push(text);
23968
25336
  opts.onStderr?.(text);
23969
25337
  this.hooks.onStderr?.(text);