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.cjs
CHANGED
|
@@ -762,10 +762,10 @@ function globToRegexSource(pattern, opts = {}) {
|
|
|
762
762
|
continue;
|
|
763
763
|
}
|
|
764
764
|
if (c === "[") {
|
|
765
|
-
const
|
|
766
|
-
if (
|
|
767
|
-
out += guardDot() +
|
|
768
|
-
i =
|
|
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
|
|
6725
|
-
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
|
|
10614
|
+
let bytes = new Uint8Array(0);
|
|
10587
10615
|
try {
|
|
10588
10616
|
if (this.vfs.permitted(st, R_OK, cred)) {
|
|
10589
|
-
|
|
10617
|
+
bytes = this.vfs.readFile(real, cred).subarray(0, MAGIC_BYTES);
|
|
10590
10618
|
}
|
|
10591
10619
|
} catch {
|
|
10592
10620
|
}
|
|
10593
|
-
if (
|
|
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
|
|
13580
|
+
let quote3 = null;
|
|
13532
13581
|
for (let i = 0; i < text.length; i++) {
|
|
13533
13582
|
const c = text[i];
|
|
13534
|
-
if (
|
|
13535
|
-
if (c ===
|
|
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
|
-
|
|
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
|
-
|
|
14915
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
22560
|
-
|
|
22561
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
24982
|
+
const decoder8 = new TextDecoder();
|
|
23615
24983
|
const stdout = new BufferSink((chunk) => {
|
|
23616
|
-
const text =
|
|
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 =
|
|
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
|
|
25326
|
+
const decoder8 = new TextDecoder();
|
|
23959
25327
|
const stdout = new BufferSink((chunk) => {
|
|
23960
|
-
const text =
|
|
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 =
|
|
25334
|
+
const text = decoder8.decode(chunk, { stream: true });
|
|
23967
25335
|
combined.push(text);
|
|
23968
25336
|
opts.onStderr?.(text);
|
|
23969
25337
|
this.hooks.onStderr?.(text);
|