@ai-ecoverse/slicc-kernel 1.13.0 → 1.14.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 CHANGED
@@ -119,7 +119,7 @@ A WASI preview1 or WASIX program (Zig, Rust, Go, wasi-libc or wasix-libc C) has
119
119
  { "slicc": { "abi": "wasi", "commands": { "rg": { "wasm": "bin/rg.wasm" }, "rustc": { "script": "bin/rustc" } } } }
120
120
  ```
121
121
 
122
- Entries may also set `argv0`, `args` and `env`, and a package can set `slicc.env` for all of its commands. 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. The catalog is re-read at the start of every `run`, so packages installed in between are picked up.
122
+ Entries may also set `argv0`, `args` and `env`, and a package can set `slicc.env` for all of its commands. 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.
123
123
 
124
124
  ### WASI programs
125
125
 
@@ -3022,9 +3022,28 @@ async function packages(fs, modules) {
3022
3022
  }
3023
3023
  return dirs;
3024
3024
  }
3025
+ async function pnpmGlobalRoots(fs, home) {
3026
+ const roots = [];
3027
+ const global = `${home}/global`;
3028
+ for (const version of (await fs.readdir(global).catch(() => [])).sort()) {
3029
+ for (const group of (await fs.readdir(`${global}/${version}`).catch(() => [])).sort()) {
3030
+ const dir = `${global}/${version}/${group}`;
3031
+ const st = await fs.lstat(dir).catch(() => void 0);
3032
+ if (st?.isDirectory && await fs.exists(`${dir}/node_modules`))
3033
+ roots.push(`${dir}/node_modules`);
3034
+ }
3035
+ }
3036
+ return roots;
3037
+ }
3038
+ async function packagesIn(fs, roots) {
3039
+ const dirs = [];
3040
+ for (const root of typeof roots === "string" ? [roots] : roots)
3041
+ dirs.push(...await packages(fs, root));
3042
+ return dirs;
3043
+ }
3025
3044
  async function scanCommands(fs, modules) {
3026
3045
  const found = /* @__PURE__ */ new Map();
3027
- for (const pkg of await packages(fs, modules)) {
3046
+ for (const pkg of await packagesIn(fs, modules)) {
3028
3047
  let manifest;
3029
3048
  try {
3030
3049
  manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
@@ -3043,7 +3062,7 @@ async function scanCommands(fs, modules) {
3043
3062
  }
3044
3063
  async function scanFilesystems(fs, modules) {
3045
3064
  const found = /* @__PURE__ */ new Map();
3046
- for (const pkg of await packages(fs, modules)) {
3065
+ for (const pkg of await packagesIn(fs, modules)) {
3047
3066
  let manifest;
3048
3067
  try {
3049
3068
  manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
@@ -6966,6 +6985,7 @@ function foreignImports(bytes) {
6966
6985
  }
6967
6986
 
6968
6987
  // src/launcher.ts
6988
+ var PNPM_HOME = "/home/.local/share/pnpm";
6969
6989
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
6970
6990
  var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
6971
6991
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
@@ -7055,6 +7075,7 @@ var Launcher = class {
7055
7075
  ca;
7056
7076
  transport;
7057
7077
  watchers = new FsWatchers();
7078
+ pnpmHome;
7058
7079
  mounts;
7059
7080
  createDriverWorker;
7060
7081
  openFiles = /* @__PURE__ */ new Set();
@@ -7071,6 +7092,7 @@ var Launcher = class {
7071
7092
  this.base = this.watchers.wrap(this.mounts.wrap(options.fs));
7072
7093
  this.createWorker = options.createWorker;
7073
7094
  this.createDriverWorker = options.createDriverWorker;
7095
+ this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
7074
7096
  this.modulesDir = options.modules ?? "/node_modules";
7075
7097
  this.env = { ...networkEnv(), ...options.env };
7076
7098
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
@@ -7081,11 +7103,17 @@ var Launcher = class {
7081
7103
  ca: this.ca
7082
7104
  });
7083
7105
  this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
7106
+ this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
7107
+ this.catalog = void 0;
7108
+ });
7084
7109
  }
7085
7110
  commands() {
7086
- this.catalog ??= scanCommands(this.base, this.modulesDir);
7111
+ this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
7087
7112
  return this.catalog;
7088
7113
  }
7114
+ async roots() {
7115
+ return [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
7116
+ }
7089
7117
  async resolve(file, argv0, cwd) {
7090
7118
  const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
7091
7119
  if (name2 !== void 0) {
@@ -7339,7 +7367,7 @@ var Launcher = class {
7339
7367
  }
7340
7368
  };
7341
7369
  }
7342
- const module = (await scanFilesystems(this.base, this.modulesDir)).get(type);
7370
+ const module = (await scanFilesystems(this.base, await this.roots())).get(type);
7343
7371
  if (!module) throw fsError("ENODEV", `unknown file system type ${type}`);
7344
7372
  if (!this.createDriverWorker)
7345
7373
  throw fsError("ENODEV", `this kernel cannot start ${type} drivers`);
@@ -7379,7 +7407,14 @@ var Launcher = class {
7379
7407
  await writeCaFile(this.base, this.ca).catch(() => void 0);
7380
7408
  }
7381
7409
  environment(cwd, extra) {
7382
- return { PATH: "/usr/bin:/bin", HOME: "/home", ...this.env, PWD: cwd, ...extra };
7410
+ return {
7411
+ PATH: `/usr/bin:/bin:${this.pnpmHome}/bin`,
7412
+ HOME: "/home",
7413
+ PNPM_HOME: this.pnpmHome,
7414
+ ...this.env,
7415
+ PWD: cwd,
7416
+ ...extra
7417
+ };
7383
7418
  }
7384
7419
  async starting(argv, dir) {
7385
7420
  this.catalog = void 0;