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