@ai-ecoverse/slicc-kernel 1.109.0 → 1.110.1
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 +21 -3
- package/dist/kernel-worker.js +100 -15
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +1 -1
- package/dist/node.js +101 -15
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +1 -1
- package/dist/types/commands.d.ts +6 -0
- package/dist/types/cron/command.d.ts +1 -1
- package/dist/types/fs/opfs.d.ts +5 -0
- package/dist/types/launcher.d.ts +2 -1
- package/package.json +1 -6
package/README.md
CHANGED
|
@@ -227,6 +227,8 @@ A command's name is any single path component without whitespace or control char
|
|
|
227
227
|
|
|
228
228
|
**More roots:** `createKernel({ commands: { append: ['/opt/user/node_modules'] } })` (and `createNodeKernel`) scans more directories, each absolute and normalised (anything else throws a `TypeError`). A name is looked up in a user's own global packages first, then in `modules`, then in root's global packages, then in the appended roots in the order given, and the first match wins, so an appended root never shadows another command (or `sudo`). Their commands are in `/usr/bin` for every user, like root's global packages, and their `binfmt` and file system entries count too. A package in an appended root also gets a command for each `bin` entry of its `package.json` (a string `bin` is named after the package, without its scope), which runs as a `#!` script; a name its `slicc.commands` gives wins. A `bin` entry is skipped when its name isn't a command name (see above), or its file, with symlinks resolved, isn't a regular file inside the package. `/node_modules` and the global packages keep reading only `slicc.commands`. A missing root counts as empty until it appears, and an install or removal in a root is picked up as it happens. With `users: 'enforce'` a root counts only when it and every directory above it are root's and not writable by group or others; another root is skipped without a word. Under `'report'` such a root is still scanned, and reported once to `onDenial` and the report log as `{ pid: 0, uid: 0, op: 'commands', path, errno: 'EACCES' }`. Running a `bin` command needs `r` and `x` on its file under `users` (see [Permissions](#permissions)), checked when it runs, so a `0644` file is `EACCES` under `'enforce'`.
|
|
229
229
|
|
|
230
|
+
**Variants by environment** ([homescoop#357](https://github.com/ai-ecoverse/homescoop/issues/357)): one package can carry more than one build of a wasm command and choose between them by the environment. `"variants": [{ "env": { "PYTHON_FORK": "true" }, "wasm": "bin/python-fork.wasm" }]` on a command entry (Emscripten or WASI) is matched in order against the environment the new process gets, at spawn and at exec. Every key must equal its value exactly, and the first match runs its `wasm`; no match runs the command's own `wasm`. This holds however the kernel reaches the entry: by name, by its path (`/usr/bin/python3`, as `sys.executable` gives it to `subprocess` and `multiprocessing`), or through a symlink to that path (a venv's `bin/python`). So `PYTHON_FORK=true python3`, `PYTHON_FORK=true exec /usr/bin/python3` and a child that inherits `PYTHON_FORK=true` all get the variant, and `PYTHON_FORK=1` doesn't. Commands of one package that share a build (`python`, `python3`, `pip`, `pip3`) each carry the same `variants`. A variant replaces only the module: the `abi`, the glue, `env`, `argv0`, `argv0Path` and the `/usr/bin` entry stay the command's. For an Emscripten command that means a variant swaps only the `.wasm` and keeps the command's JS glue, so it must be built against that same glue; a WASI or WASIX command has no glue, so there's no such constraint. Only the chosen module is read, checked and compiled, and compiled modules are cached by its path. A read or `stat` of `/usr/bin/<name>` still shows the default `wasm`. A `variants` that isn't an array, or has an entry without an `env` object of strings and a `wasm` inside the package, is ignored as a whole, with one warning per command, and the command runs its `wasm`. An older kernel ignores the field.
|
|
231
|
+
|
|
230
232
|
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 of two packages that map one suffix, the one whose name sorts first wins, as for commands; the kernel warns once (`console.warn`) when another package maps that suffix to a different command. A suffix starts with a dot and the command must be a name the catalog resolves when the file runs.
|
|
231
233
|
|
|
232
234
|
### WASI programs
|
|
@@ -878,7 +880,7 @@ Not covered: hard links (OPFS cannot share contents between names; they work onl
|
|
|
878
880
|
|
|
879
881
|
Reads retry briefly when a concurrent write has invalidated the OPFS file snapshot (`NotReadableError`).
|
|
880
882
|
|
|
881
|
-
A kernel caches the directory handles it has walked and the sidecar entries it has read. Its own changes keep both caches current, and the other kernels on the origin hear of them through a `BroadcastChannel` named after the database (one for the sidecar, one for directories). A cached directory is checked against its path (`resolve()`) at most once a second before a lookup goes through it, so a directory moved, removed or replaced by another writer, such as a page using the OPFS API, is found at its new place or reported missing within a second.
|
|
883
|
+
A kernel caches the directory handles it has walked and the sidecar entries it has read. Its own changes keep both caches current, and the other kernels on the origin hear of them through a `BroadcastChannel` named after the database (one for the sidecar, one for directories). A cached directory is checked against its path (`resolve()`) at most once a second before a lookup goes through it, so a directory moved, removed or replaced by another writer, such as a page using the OPFS API, is found at its new place or reported missing within a second. It also keeps the file handles it has looked up (up to 4,096, the least recently used dropped first) for a second after each lookup. Before it uses one, it reads the file through it, which is the read a `stat` needs anyway, so a file another writer removed is missing at once and a rewritten one shows its new contents. One replaced by a directory is seen as a directory within a second. Its own changes, and the other kernels' notices, drop the handles they touch at once.
|
|
882
884
|
|
|
883
885
|
The filesystem is our own rather than [ZenFS](https://github.com/zen-fs/core) (which SLICC uses), because OPFS stays the single source of truth: other writers, such as the BIOS installing packages or a page writing files, need no index to stay consistent with, and nothing is preloaded into memory at mount. POSIX metadata lives in the IndexedDB sidecar described above, which has per-entry transactions instead of one JSON file rewritten on every change.
|
|
884
886
|
|
|
@@ -893,6 +895,7 @@ The kernel is ported from SLICC's `packages/webapp/src/kernel/` with the browser
|
|
|
893
895
|
- `src/launcher.ts`, `src/commands.ts`, `src/serve.ts`, `src/index.ts`, `src/users.ts`: command resolution, the kernel worker protocol, the page API and the user database; `src/node.ts`, `src/node-process-worker.ts` and `src/node/` the headless Node entry. An embedder that uses `Launcher` directly, without `createKernel`, calls `await launcher.prepare()` first: it creates `/tmp` and `/home` and writes the CA certificate.
|
|
894
896
|
- `src/kernel/net/`: the proxy, HTTP/1.1, TLS termination and the local CA, and the bridge to the page's transport; `src/transport.ts` and `src/local-proxy-transport.ts` are the page side.
|
|
895
897
|
- `src/cdp/`: the CDP facade on `127.0.0.1:9222`, its WebSocket framing, the registry of hosts and the `MessagePort` bridge to them.
|
|
898
|
+
- `src/cron/`: the cron service (`service.ts`), the schedule and table parsers, the clock, and the `crontab` command with the kernel side of `ctx.crontab`. An embedder that uses `Launcher` directly passes `cron: { lock }` to share the firing lock among the kernels on one root; the default, `slicc-kernel-cron`, is one lock for the whole origin.
|
|
896
899
|
|
|
897
900
|
|
|
898
901
|
## Installing from git
|
|
@@ -954,6 +957,21 @@ Releases are cut by semantic-release on every push to `main`, in two jobs, and o
|
|
|
954
957
|
|
|
955
958
|
Moving the `certified` dist-tag stays manual. Automating it needs an npm token that may change dist-tags, in a step that re-hashes the tarball and tags only on a `certified` line.
|
|
956
959
|
|
|
960
|
+
### Certification in the pull request (`cert-review-pr`)
|
|
961
|
+
|
|
962
|
+
`.github/workflows/cert-review-pr.yml` certifies a pull request before it merges, so `main`, and with it npm's `latest`, only gets certified changes. For now it reports as the `cert-review-pr` commit status and isn't a required check. It becomes the required `cert-review` once it has proved itself on real pull requests; the post-publish review above stays until then.
|
|
963
|
+
|
|
964
|
+
- **Which pull requests:** those that change shipped behaviour, by a conservative path filter (`behaviourFiles` in `lib.mjs`): `src/`, `build.mjs`, `package.json` and its lock, `tsconfig*.json`, `.releaserc*`, `tools/release-recover.mjs`, the workflows that build or publish (`release.yml`, `ci.yml`), and the reviewer's own code (`tools/cert-review/`, `cert-review*.yml`, `.github/runner.mjs`). **Main's code decides:** every job checks out the pull request's base and fetches only the trees under test, so the path filter, pack, suite, findings, the instructions and record all run from `main`. A pull request that changes the reviewer is reviewed by `main`'s reviewer. The workflow file of a `pull_request` run still comes from the pull request, so **pull requests that touch the reviewer always go to the Kernel Coordinator before they are queued.** The pull request that adds this workflow can't pass its own check, because its base has no tools yet. Any other pull request gets a green status that says `not behaviour: <files>`, so the skip is recorded.
|
|
965
|
+
- **When:** only for a same-repo pull request with `merge-ok`, when the label is added and on every push while it is set. Each pull request has its own concurrency group, and a newer push cancels the older review. A behaviour change without `merge-ok` gets a red status, `waits for merge-ok to be reviewed`. Other labels don't start or cancel anything. Fork code never reaches the pools or the key.
|
|
966
|
+
- **What:** the checkout is the event's default `refs/pull/N/merge`, the pull request merged onto `main` at review time, so a review is of what would land, without pushing to the branch. Both sides are packed in the same run, on a GitHub-hosted runner, with no secrets: the candidate (the merge ref, `x.y.z-pr.N.<run>`) and the base (its first parent, `main`). Then:
|
|
967
|
+
- the candidate's whole suite, and the base's;
|
|
968
|
+
- the pull request's new or changed test files on the base build (the NEGATIVE: a fix's test must fail on `main` and pass on the candidate);
|
|
969
|
+
- kernel-compat on the candidate's artifact (#812), against npm's `latest`;
|
|
970
|
+
- the review (Claude through `claude-code-action/base-action`, with the Bedrock key in that step only, no issue filing);
|
|
971
|
+
- and record, which re-hashes the candidate's artifact against the pack job's sha256.
|
|
972
|
+
- **The verdict** goes into the commit status on the pull request's head and a comment on the pull request. Certified is green. A FAIL is red with the line, and the pull request needs a fix. **INCOMPLETE** (a job that didn't succeed, no verdict, a suite without results) is red with `re-run the failed jobs`, never a silent pass. Main moving on after a review doesn't trigger another one; a push does.
|
|
973
|
+
- **Merge groups only look up.** On `merge_group`, the job lists the group's pull requests (its merge commits) and requires, for each one, a green status on the head being merged, or no behaviour change. Otherwise it's red, naming the pull request. It runs in seconds, with no secrets and no model. The group's `ci / test` covers the combined tree.
|
|
974
|
+
|
|
957
975
|
Not part of the run yet: bios's real setup (the base-set sweep in bios, the upgrade boot, the sudo rows under `enforce`). It would plug in as one more job before review that runs slicc-bios's `npm run test:cert` with `CERT_COMMANDS` and the release's tarball, and leaves its results next to the others.
|
|
958
976
|
|
|
959
977
|
## Test fixtures
|
|
@@ -1086,9 +1104,9 @@ A threaded WASIX C program for `test/integration/thread-signal.test.mjs` (#520).
|
|
|
1086
1104
|
|
|
1087
1105
|
A WASIX C program for `test/integration/long-timers.test.mjs` (#767).
|
|
1088
1106
|
- `longwait alarm` sets `alarm(3000000)` (about 35 days), sleeps 0.5 s, and prints how many alarms came and how many seconds were left.
|
|
1089
|
-
- `longwait select|ppoll|nanosleep` waits 3,000,000 s on a pipe that never gets data, until a 300 ms interval timer interrupts the wait, and prints `not before it` when the wait lasted at least 300 ms (`early` otherwise). It sets no upper bound, so a loaded CI host can't fail the row; a wait that never ends times the test out.
|
|
1107
|
+
- `longwait select|ppoll|nanosleep` waits 3,000,000 s on a pipe that never gets data, until a 300 ms interval timer interrupts the wait, and prints `not before it` when the wait lasted at least 300 ms (`early` otherwise), measured from before `setitimer` arms the timer, so the time the arming call takes can't shorten it (#858). It sets no upper bound, so a loaded CI host can't fail the row; a wait that never ends times the test out.
|
|
1090
1108
|
|
|
1091
|
-
**Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 and binaryen's `wasm-opt` on `PATH`. `bin/longwait.wasm` is 44,
|
|
1109
|
+
**Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 and binaryen's `wasm-opt` on `PATH`. `bin/longwait.wasm` is 44,002 bytes, sha256 `24acd996068ea04c526b74c93b896986eef782ec96230e19fb939d1b00a1bad0`.
|
|
1092
1110
|
|
|
1093
1111
|
### `epollmask`
|
|
1094
1112
|
|
package/dist/kernel-worker.js
CHANGED
|
@@ -984,6 +984,8 @@ var S_ISGID2 = 1024;
|
|
|
984
984
|
var READ_ATTEMPTS = 3;
|
|
985
985
|
var MAX_LINKS2 = 40;
|
|
986
986
|
var CHECK_MS = 1e3;
|
|
987
|
+
var FILE_HANDLES = 4096;
|
|
988
|
+
var STALE = /* @__PURE__ */ new Set(["NotFoundError", "TypeMismatchError"]);
|
|
987
989
|
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
988
990
|
var ERRNO_BY_DOM_ERROR = {
|
|
989
991
|
NotFoundError: "ENOENT",
|
|
@@ -1109,6 +1111,9 @@ var OpfsFs = class {
|
|
|
1109
1111
|
root;
|
|
1110
1112
|
meta;
|
|
1111
1113
|
dirs = new PathMap();
|
|
1114
|
+
files = new PathMap();
|
|
1115
|
+
snapshots = /* @__PURE__ */ new WeakMap();
|
|
1116
|
+
found = /* @__PURE__ */ new WeakMap();
|
|
1112
1117
|
checked = /* @__PURE__ */ new WeakMap();
|
|
1113
1118
|
channel;
|
|
1114
1119
|
remote;
|
|
@@ -1346,6 +1351,19 @@ var OpfsFs = class {
|
|
|
1346
1351
|
}
|
|
1347
1352
|
forget(path) {
|
|
1348
1353
|
this.dirs.deleteTree(path);
|
|
1354
|
+
this.files.deleteTree(path);
|
|
1355
|
+
}
|
|
1356
|
+
remember(path, handle, looked = false) {
|
|
1357
|
+
if (handle.kind !== "file") return handle;
|
|
1358
|
+
if (looked) {
|
|
1359
|
+
this.found.set(handle, performance.now());
|
|
1360
|
+
this.snapshots.delete(handle);
|
|
1361
|
+
}
|
|
1362
|
+
this.files.delete(path);
|
|
1363
|
+
if (this.files.size >= FILE_HANDLES)
|
|
1364
|
+
this.files.delete(this.files.keys().next().value);
|
|
1365
|
+
this.files.set(path, handle);
|
|
1366
|
+
return handle;
|
|
1349
1367
|
}
|
|
1350
1368
|
async inDir(path, op) {
|
|
1351
1369
|
const dir = await this.dir(path);
|
|
@@ -1369,16 +1387,30 @@ var OpfsFs = class {
|
|
|
1369
1387
|
}
|
|
1370
1388
|
async handle(path) {
|
|
1371
1389
|
if (split(path).length === 0) return this.root;
|
|
1390
|
+
const cached = this.files.get(path);
|
|
1391
|
+
const fresh2 = cached && performance.now() - (this.found.get(cached) ?? -CHECK_MS) < CHECK_MS;
|
|
1392
|
+
if (cached && !fresh2) this.files.delete(path);
|
|
1393
|
+
if (cached && fresh2) {
|
|
1394
|
+
try {
|
|
1395
|
+
this.snapshots.set(cached, await cached.getFile());
|
|
1396
|
+
return this.remember(path, cached);
|
|
1397
|
+
} catch (err) {
|
|
1398
|
+
if (!STALE.has(err?.name))
|
|
1399
|
+
throw translate(err, path, "read");
|
|
1400
|
+
this.files.delete(path);
|
|
1401
|
+
this.snapshots.delete(cached);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1372
1404
|
const [dir, name2] = await this.parent(path);
|
|
1373
1405
|
const known = this.dirs.has(path);
|
|
1374
1406
|
return this.inDir(dir, async (parent) => {
|
|
1375
1407
|
const [first, second] = known ? ["directory", "file"] : ["file", "directory"];
|
|
1376
1408
|
try {
|
|
1377
|
-
return await this.child(parent, name2, first);
|
|
1409
|
+
return this.remember(path, await this.child(parent, name2, first), true);
|
|
1378
1410
|
} catch (err) {
|
|
1379
1411
|
if (err?.name !== "TypeMismatchError") throw err;
|
|
1380
1412
|
}
|
|
1381
|
-
return this.child(parent, name2, second);
|
|
1413
|
+
return this.remember(path, await this.child(parent, name2, second), true);
|
|
1382
1414
|
}).catch((err) => {
|
|
1383
1415
|
throw translate(err, path);
|
|
1384
1416
|
});
|
|
@@ -1521,9 +1553,15 @@ var OpfsFs = class {
|
|
|
1521
1553
|
if (this.dirTimes.get(path) === ms) this.dirTimes.delete(path);
|
|
1522
1554
|
}
|
|
1523
1555
|
}
|
|
1556
|
+
async snapshot(handle) {
|
|
1557
|
+
const taken = this.snapshots.get(handle);
|
|
1558
|
+
if (!taken) return handle.getFile();
|
|
1559
|
+
this.snapshots.delete(handle);
|
|
1560
|
+
return taken;
|
|
1561
|
+
}
|
|
1524
1562
|
async statOf(path, handle, meta, touched) {
|
|
1525
1563
|
const directory = handle.kind === "directory";
|
|
1526
|
-
const file = directory ? void 0 : await
|
|
1564
|
+
const file = directory ? void 0 : await this.snapshot(handle);
|
|
1527
1565
|
const modified = file?.lastModified ?? 0;
|
|
1528
1566
|
const kept = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
|
|
1529
1567
|
const mtime = touched ?? kept;
|
|
@@ -2645,14 +2683,14 @@ var WsReader = class {
|
|
|
2645
2683
|
}
|
|
2646
2684
|
};
|
|
2647
2685
|
function switchingHead(accept2, protocol = "") {
|
|
2648
|
-
const
|
|
2686
|
+
const chosen2 = protocol ? `Sec-WebSocket-Protocol: ${protocol}\r
|
|
2649
2687
|
` : "";
|
|
2650
2688
|
return latin1Bytes(
|
|
2651
2689
|
`HTTP/1.1 101 Switching Protocols\r
|
|
2652
2690
|
Upgrade: websocket\r
|
|
2653
2691
|
Connection: Upgrade\r
|
|
2654
2692
|
Sec-WebSocket-Accept: ${accept2}\r
|
|
2655
|
-
${
|
|
2693
|
+
${chosen2}\r
|
|
2656
2694
|
`
|
|
2657
2695
|
);
|
|
2658
2696
|
}
|
|
@@ -6108,7 +6146,7 @@ function satisfies(version, range2) {
|
|
|
6108
6146
|
}
|
|
6109
6147
|
|
|
6110
6148
|
// src/version.ts
|
|
6111
|
-
var KERNEL_VERSION = true ? "1.
|
|
6149
|
+
var KERNEL_VERSION = true ? "1.110.1" : "0.0.0-dev";
|
|
6112
6150
|
var OS_RELEASE = "6.0.0-slicc";
|
|
6113
6151
|
function procVersion() {
|
|
6114
6152
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -6156,6 +6194,37 @@ function argsOf(pkg, raw) {
|
|
|
6156
6194
|
if (!raw.every((arg) => typeof arg === "string")) return void 0;
|
|
6157
6195
|
return raw.map((arg) => arg.replace(PACKAGE, pkg));
|
|
6158
6196
|
}
|
|
6197
|
+
var warnedVariants = /* @__PURE__ */ new Set();
|
|
6198
|
+
function variantOf(pkg, raw) {
|
|
6199
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
6200
|
+
const { env, wasm } = raw;
|
|
6201
|
+
const path = inside(pkg, wasm);
|
|
6202
|
+
if (!path || !env || typeof env !== "object" || Array.isArray(env)) return void 0;
|
|
6203
|
+
const pairs = Object.entries(env);
|
|
6204
|
+
if (pairs.length === 0) return void 0;
|
|
6205
|
+
if (!pairs.every(([key, value2]) => ENV_KEY.test(key) && typeof value2 === "string")) {
|
|
6206
|
+
return void 0;
|
|
6207
|
+
}
|
|
6208
|
+
return { env: Object.fromEntries(pairs), wasm: path };
|
|
6209
|
+
}
|
|
6210
|
+
function variantsOf(pkg, name2, raw) {
|
|
6211
|
+
if (raw === void 0) return void 0;
|
|
6212
|
+
const variants = Array.isArray(raw) ? raw.map((one) => variantOf(pkg, one)) : [void 0];
|
|
6213
|
+
if (variants.every((one) => one !== void 0)) {
|
|
6214
|
+
return variants.length > 0 ? variants : void 0;
|
|
6215
|
+
}
|
|
6216
|
+
const key = `${pkg}:${name2}:${JSON.stringify(raw)}`;
|
|
6217
|
+
if (!warnedVariants.has(key)) {
|
|
6218
|
+
warnedVariants.add(key);
|
|
6219
|
+
console.warn(`slicc-kernel: ${pkg}: ${name2}: ignoring malformed "variants", running its wasm`);
|
|
6220
|
+
}
|
|
6221
|
+
return void 0;
|
|
6222
|
+
}
|
|
6223
|
+
function variantFor(variants, env) {
|
|
6224
|
+
return variants?.find(
|
|
6225
|
+
(one) => Object.entries(one.env).every(([key, value2]) => env[key] === value2)
|
|
6226
|
+
)?.wasm;
|
|
6227
|
+
}
|
|
6159
6228
|
function abiOf(raw, fallback) {
|
|
6160
6229
|
const abi = raw ?? fallback;
|
|
6161
6230
|
return abi === "emscripten" || abi === "wasi" || abi === "js" ? abi : void 0;
|
|
@@ -6189,6 +6258,7 @@ function commandOf(pkg, name2, raw, packageAbi, shared) {
|
|
|
6189
6258
|
const glue = abi === "wasi" ? wasm : inside(pkg, raw.glue);
|
|
6190
6259
|
if (!abi || !glue || !wasm) return void 0;
|
|
6191
6260
|
const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
|
|
6261
|
+
const variants = variantsOf(pkg, name2, raw.variants);
|
|
6192
6262
|
return {
|
|
6193
6263
|
name: name2,
|
|
6194
6264
|
abi,
|
|
@@ -6200,7 +6270,8 @@ function commandOf(pkg, name2, raw, packageAbi, shared) {
|
|
|
6200
6270
|
...abi === "wasi" && raw.preopenRoot === true ? { preopenRoot: true } : {},
|
|
6201
6271
|
...args ? { args } : {},
|
|
6202
6272
|
...withEnv,
|
|
6203
|
-
...imports ? { imports } : {}
|
|
6273
|
+
...imports ? { imports } : {},
|
|
6274
|
+
...variants ? { variants } : {}
|
|
6204
6275
|
};
|
|
6205
6276
|
}
|
|
6206
6277
|
function commandsOf(pkg, manifest) {
|
|
@@ -6520,13 +6591,14 @@ var CRONTAB_SOURCE = `export default async function crontab(ctx) {
|
|
|
6520
6591
|
let text;
|
|
6521
6592
|
try {
|
|
6522
6593
|
if (file === "-") {
|
|
6594
|
+
const stream = new TextDecoder();
|
|
6523
6595
|
const parts = [];
|
|
6524
6596
|
for (;;) {
|
|
6525
6597
|
const chunk = await ctx.read(0);
|
|
6526
6598
|
if (chunk.length === 0) break;
|
|
6527
|
-
parts.push(decode(chunk));
|
|
6599
|
+
parts.push(stream.decode(chunk, { stream: true }));
|
|
6528
6600
|
}
|
|
6529
|
-
text = parts.join("");
|
|
6601
|
+
text = parts.join("") + stream.decode();
|
|
6530
6602
|
} else text = decode(await ctx.fs.readFile(ctx.resolve(file)));
|
|
6531
6603
|
} catch (err) {
|
|
6532
6604
|
await say(2, "crontab: " + file + ": " + (err && err.code ? err.code : String(err)) + "\\n");
|
|
@@ -7294,7 +7366,10 @@ var CronService = class {
|
|
|
7294
7366
|
const account = await this.host.lookup(job.user);
|
|
7295
7367
|
if (account) out.push({ source: "system", account, job });
|
|
7296
7368
|
}
|
|
7297
|
-
const names = await fs.readdir(SPOOL_DIR).catch(() =>
|
|
7369
|
+
const names = await fs.readdir(SPOOL_DIR).catch((err) => {
|
|
7370
|
+
if (err?.code === "ENOENT") return [];
|
|
7371
|
+
throw err;
|
|
7372
|
+
});
|
|
7298
7373
|
for (const name2 of names.sort()) {
|
|
7299
7374
|
if (name2.startsWith(".") || name2.endsWith("~")) continue;
|
|
7300
7375
|
const path = `${SPOOL_DIR}/${name2}`;
|
|
@@ -21976,9 +22051,18 @@ function targetOf(command) {
|
|
|
21976
22051
|
...command.env ? { env: command.env } : {},
|
|
21977
22052
|
...command.unset ? { unset: command.unset } : {},
|
|
21978
22053
|
...command.imports ? { imports: command.imports } : {},
|
|
21979
|
-
...command.preopenRoot ? { preopenRoot: true } : {}
|
|
22054
|
+
...command.preopenRoot ? { preopenRoot: true } : {},
|
|
22055
|
+
...command.variants ? { variants: command.variants } : {}
|
|
21980
22056
|
};
|
|
21981
22057
|
}
|
|
22058
|
+
function chosen(planned, env) {
|
|
22059
|
+
const { target } = planned;
|
|
22060
|
+
const wasm = variantFor(target.variants, env);
|
|
22061
|
+
if (wasm === void 0) return planned;
|
|
22062
|
+
const { asked: _asked, ...rest2 } = target;
|
|
22063
|
+
const glue = target.abi === "wasi" ? wasm : target.glue;
|
|
22064
|
+
return { ...planned, target: { ...rest2, wasm, glue } };
|
|
22065
|
+
}
|
|
21982
22066
|
function withScriptEnv(target, command) {
|
|
21983
22067
|
if (!command) return target;
|
|
21984
22068
|
return {
|
|
@@ -22889,14 +22973,15 @@ var Launcher = class {
|
|
|
22889
22973
|
...target.preopenRoot ? { preopenRoot: true } : {}
|
|
22890
22974
|
};
|
|
22891
22975
|
}
|
|
22892
|
-
async launch(
|
|
22893
|
-
const { target, args } = planned;
|
|
22976
|
+
async launch(asked, req) {
|
|
22894
22977
|
const cwd = await physicalPathAsync(this.base, req.cwd);
|
|
22895
22978
|
const seen = { ...req.env, HOSTNAME: this.hostname, cwd };
|
|
22896
|
-
const env = { ...expandDefaults(target.env, seen), ...req.env };
|
|
22897
|
-
for (const key of target.unset ?? []) delete env[key];
|
|
22979
|
+
const env = { ...expandDefaults(asked.target.env, seen), ...req.env };
|
|
22980
|
+
for (const key of asked.target.unset ?? []) delete env[key];
|
|
22898
22981
|
delete env[TICKET_ENV];
|
|
22899
22982
|
env.HOSTNAME = this.hostname;
|
|
22983
|
+
const planned = chosen(asked, env);
|
|
22984
|
+
const { target, args } = planned;
|
|
22900
22985
|
let program;
|
|
22901
22986
|
await this.pnpmReady();
|
|
22902
22987
|
try {
|