@ai-ecoverse/slicc-kernel 1.36.0 → 1.37.0
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 +2 -0
- package/dist/kernel-worker.js +68 -20
- package/dist/kernel-worker.js.map +2 -2
- package/dist/node.js +68 -20
- package/dist/node.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -143,6 +143,8 @@ A WASI preview1 or WASIX program (Zig, Rust, Go, wasi-libc or wasix-libc C) has
|
|
|
143
143
|
|
|
144
144
|
Entries may also set `argv0`, `args` and `env`, and a package can set `slicc.env` for all of its commands. A WASI entry with `"preopenRoot": true` also gets `/` as a preopen (see [WASI programs](#wasi-programs)). An entry with `"argv0Path": true` gets the absolute path it was run by as `argv[0]` when that path is a link outside `/bin` and `/usr/bin` to the command, as a venv's `bin/python` is (the `#!` line of a script naming such a link included), so it can find what sits beside that link; other entries keep their `argv0`. In `env`, `${package}` is the package directory, `${cwd}` the process's working directory, and `${NAME}` the caller's `NAME` when the program starts (a default naming an unset variable is left out); a relative path that exists in the package (`lib/python3.14`, `./`) becomes that absolute path. The caller's environment wins over these defaults; a `null` value removes the variable for the command, after the caller's environment is applied (a host-networked program can drop the kernel's proxy settings that way), and a command entry can set a variable its package's `slicc.env` removes. The first package to define a name wins. `sh` runs bash as `sh` unless a package provides its own. The installed commands appear as executables in a virtual `/usr/bin` and `/bin`, so `PATH` lookups, `command -v` and `ls /usr/bin` work without writing anything to OPFS. A process can also run a program by the path of its glue when the `.wasm` sits next to it, a wasm module by its own path (as a WASI program), and a script through its `#!` line (including `#!/usr/bin/env name`). Executing any other file fails with `ENOEXEC`, so bash runs it as a shell script, as POSIX shells do. Commands also come from pnpm's global directory. Every process gets `PNPM_HOME=/home/.local/share/pnpm`, unless the embedder sets its own, and has `$PNPM_HOME/bin` on `PATH`. The kernel scans each of pnpm's global install groups (`$PNPM_HOME/global/*/<group>/node_modules`) after `/node_modules`, which wins when both provide a command. The catalog is re-read whenever anything under either changes, so `pnpm add -g @ai-ecoverse/wasi-ripgrep` makes `rg` work in the same shell, and `pnpm remove -g` removes it again.
|
|
145
145
|
|
|
146
|
+
A package can also name a command that runs files by their suffix, as `binfmt_misc` does: `"slicc": { "binfmt": { ".jsh": "jsh" } }`. Executing a file that has no `#!` line and is not a wasm module, whose name ends in a mapped suffix, runs that command with the file's path as invoked and the arguments after it, as a `#!` line naming the command would; a `#!` line still wins. The longest matching suffix wins, the whole name is never a suffix (`.jsh` alone is not one), and the first package to map a suffix wins, as for commands. A suffix starts with a dot and the command must be a name the catalog resolves when the file runs.
|
|
147
|
+
|
|
146
148
|
### WASI programs
|
|
147
149
|
|
|
148
150
|
A WASI program runs in a process worker of its own like an Emscripten one, against the same kernel: its descriptors, pipes, terminals and job control are the kernel's, and its files are OPFS. **Preopens**, in order:
|
package/dist/kernel-worker.js
CHANGED
|
@@ -4733,6 +4733,35 @@ async function scanFilesystems(fs, modules) {
|
|
|
4733
4733
|
}
|
|
4734
4734
|
return found;
|
|
4735
4735
|
}
|
|
4736
|
+
var SUFFIX = /^\.[A-Za-z0-9_+-][A-Za-z0-9._+-]*$/;
|
|
4737
|
+
async function scanBinfmts(fs, modules) {
|
|
4738
|
+
const found = /* @__PURE__ */ new Map();
|
|
4739
|
+
for (const pkg of await packagesIn(fs, modules)) {
|
|
4740
|
+
let manifest;
|
|
4741
|
+
try {
|
|
4742
|
+
manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
|
|
4743
|
+
} catch {
|
|
4744
|
+
continue;
|
|
4745
|
+
}
|
|
4746
|
+
const declared = manifest.slicc?.binfmt;
|
|
4747
|
+
if (!declared || typeof declared !== "object") continue;
|
|
4748
|
+
for (const [suffix, command] of Object.entries(declared)) {
|
|
4749
|
+
if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME.test(command)) continue;
|
|
4750
|
+
if (!found.has(suffix)) found.set(suffix, command);
|
|
4751
|
+
}
|
|
4752
|
+
}
|
|
4753
|
+
return found;
|
|
4754
|
+
}
|
|
4755
|
+
function binfmtOf(binfmts, path) {
|
|
4756
|
+
const name2 = path.slice(path.lastIndexOf("/") + 1);
|
|
4757
|
+
let best;
|
|
4758
|
+
for (const suffix of binfmts.keys()) {
|
|
4759
|
+
if (name2.length > suffix.length && name2.endsWith(suffix)) {
|
|
4760
|
+
if (best === void 0 || suffix.length > best.length) best = suffix;
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
return best === void 0 ? void 0 : binfmts.get(best);
|
|
4764
|
+
}
|
|
4736
4765
|
|
|
4737
4766
|
// src/fs/commands.ts
|
|
4738
4767
|
var S_IFDIR2 = 16384;
|
|
@@ -10383,6 +10412,7 @@ var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
|
|
|
10383
10412
|
var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
10384
10413
|
var WASM_MAGIC = [0, 97, 115, 109];
|
|
10385
10414
|
var SHEBANG_MAX = 256;
|
|
10415
|
+
var MAX_INTERPRETERS = 4;
|
|
10386
10416
|
var NOT_FOUND = 127;
|
|
10387
10417
|
var DECIDE_MS = 100;
|
|
10388
10418
|
var INIT_PID = 1;
|
|
@@ -10434,6 +10464,17 @@ function targetOf(command) {
|
|
|
10434
10464
|
...command.preopenRoot ? { preopenRoot: true } : {}
|
|
10435
10465
|
};
|
|
10436
10466
|
}
|
|
10467
|
+
function withScriptEnv(target, command) {
|
|
10468
|
+
if (!command) return target;
|
|
10469
|
+
return {
|
|
10470
|
+
...target,
|
|
10471
|
+
env: { ...target.env, ...command.env },
|
|
10472
|
+
unset: [
|
|
10473
|
+
...(target.unset ?? []).filter((key) => !Object.hasOwn(command.env ?? {}, key)),
|
|
10474
|
+
...command.unset ?? []
|
|
10475
|
+
]
|
|
10476
|
+
};
|
|
10477
|
+
}
|
|
10437
10478
|
function childHandle(handle) {
|
|
10438
10479
|
return {
|
|
10439
10480
|
pid: handle.pid,
|
|
@@ -10458,6 +10499,7 @@ var Launcher = class {
|
|
|
10458
10499
|
modulesDir;
|
|
10459
10500
|
env;
|
|
10460
10501
|
catalog;
|
|
10502
|
+
binfmts;
|
|
10461
10503
|
compiled = /* @__PURE__ */ new Map();
|
|
10462
10504
|
processes = /* @__PURE__ */ new Map();
|
|
10463
10505
|
zombies = /* @__PURE__ */ new Set();
|
|
@@ -10539,6 +10581,7 @@ var Launcher = class {
|
|
|
10539
10581
|
this.fs = keepingOpen(fs, this.nodes);
|
|
10540
10582
|
this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
|
|
10541
10583
|
this.catalog = void 0;
|
|
10584
|
+
this.binfmts = void 0;
|
|
10542
10585
|
});
|
|
10543
10586
|
}
|
|
10544
10587
|
setRoutes(table) {
|
|
@@ -10548,6 +10591,10 @@ var Launcher = class {
|
|
|
10548
10591
|
this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
|
|
10549
10592
|
return this.catalog;
|
|
10550
10593
|
}
|
|
10594
|
+
interpreters() {
|
|
10595
|
+
this.binfmts ??= this.roots().then((roots) => scanBinfmts(this.base, roots));
|
|
10596
|
+
return this.binfmts;
|
|
10597
|
+
}
|
|
10551
10598
|
async roots() {
|
|
10552
10599
|
return [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
|
|
10553
10600
|
}
|
|
@@ -10593,30 +10640,28 @@ var Launcher = class {
|
|
|
10593
10640
|
const command = (await this.commands()).get(name2);
|
|
10594
10641
|
return command?.script ? command : void 0;
|
|
10595
10642
|
}
|
|
10596
|
-
async interpreted(file, argv, cwd) {
|
|
10643
|
+
async interpreted(file, argv, cwd, depth = 0) {
|
|
10597
10644
|
const command = await this.scriptCommand(file);
|
|
10598
10645
|
const script = command?.script ?? this.fs.resolvePath(cwd, file);
|
|
10599
|
-
const
|
|
10646
|
+
const head = await this.head(script);
|
|
10647
|
+
if (!head) return void 0;
|
|
10648
|
+
const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(), script);
|
|
10649
|
+
const words = shebang(head) ?? (binfmt ? [binfmt] : void 0);
|
|
10600
10650
|
if (!words) return void 0;
|
|
10601
10651
|
if (baseName(words[0]) === "env") words.shift();
|
|
10602
10652
|
const [interp, ...rest] = words;
|
|
10603
|
-
const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
|
|
10604
|
-
if (!found) return void 0;
|
|
10605
|
-
const target = command ? {
|
|
10606
|
-
...found,
|
|
10607
|
-
env: { ...found.env, ...command.env },
|
|
10608
|
-
unset: [...found.unset ?? [], ...command.unset ?? []]
|
|
10609
|
-
} : found;
|
|
10610
10653
|
const arg = rest.join(" ");
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
}
|
|
10654
|
+
const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
|
|
10655
|
+
const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
|
|
10656
|
+
if (!found) {
|
|
10657
|
+
if (!interp || depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp)) {
|
|
10658
|
+
return void 0;
|
|
10659
|
+
}
|
|
10660
|
+
const inner = await this.interpreted(interp, [interp, ...passed], cwd, depth + 1);
|
|
10661
|
+
return inner && { ...inner, target: withScriptEnv(inner.target, command) };
|
|
10662
|
+
}
|
|
10663
|
+
const target = withScriptEnv(found, command);
|
|
10664
|
+
return { target, args: [...target.prefix ?? [], ...passed] };
|
|
10620
10665
|
}
|
|
10621
10666
|
async plan(file, argv, cwd) {
|
|
10622
10667
|
const direct = await this.resolve(file, argv[0] ?? file, cwd);
|
|
@@ -10624,8 +10669,10 @@ var Launcher = class {
|
|
|
10624
10669
|
return this.interpreted(file, argv, cwd);
|
|
10625
10670
|
}
|
|
10626
10671
|
async unrunnable(file, cwd) {
|
|
10627
|
-
const
|
|
10628
|
-
|
|
10672
|
+
const path = this.fs.resolvePath(cwd, file);
|
|
10673
|
+
const head = await this.base.readFileBuffer(path).catch(() => null);
|
|
10674
|
+
if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
|
|
10675
|
+
return binfmtOf(await this.interpreters(), path) ? "ENOENT" : "ENOEXEC";
|
|
10629
10676
|
}
|
|
10630
10677
|
module(path) {
|
|
10631
10678
|
return this.base.stat(path).then((st) => {
|
|
@@ -11085,6 +11132,7 @@ var Launcher = class {
|
|
|
11085
11132
|
}
|
|
11086
11133
|
async starting(argv, dir) {
|
|
11087
11134
|
this.catalog = void 0;
|
|
11135
|
+
this.binfmts = void 0;
|
|
11088
11136
|
const cwd = this.fs.resolvePath("/", dir ?? "/");
|
|
11089
11137
|
const [file = ""] = argv;
|
|
11090
11138
|
await this.base.mkdir(cwd, { recursive: true });
|