@ai-ecoverse/slicc-kernel 1.110.0 → 1.110.2
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 +27 -14
- package/dist/kernel-worker.js +84 -19
- package/dist/kernel-worker.js.map +3 -3
- package/dist/meta.js +10 -1
- package/dist/meta.js.map +2 -2
- package/dist/node-process-worker.js +1 -1
- package/dist/node.js +75 -18
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +1 -1
- package/dist/types/fs/meta.d.ts +6 -4
- package/dist/types/fs/opfs.d.ts +6 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -880,7 +880,7 @@ Not covered: hard links (OPFS cannot share contents between names; they work onl
|
|
|
880
880
|
|
|
881
881
|
Reads retry briefly when a concurrent write has invalidated the OPFS file snapshot (`NotReadableError`).
|
|
882
882
|
|
|
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.
|
|
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.
|
|
884
884
|
|
|
885
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.
|
|
886
886
|
|
|
@@ -931,13 +931,18 @@ Releases are cut by semantic-release on every push to `main`, in two jobs, and o
|
|
|
931
931
|
|
|
932
932
|
### Certification (`cert-review`)
|
|
933
933
|
|
|
934
|
-
|
|
934
|
+
Every change is certified in its pull request, before it merges (`cert-review-pr.yml`, next section), and `cert-review` is a required check of the merge queue, so every commit on `main`, and every release built from it, was certified first. A release is then **verified on npm** (`release / verify on npm` in `release.yml`, `tools/cert-review/verify.mjs`), in code, for every tag the run made:
|
|
935
|
+
|
|
936
|
+
- It waits up to an hour for npm to serve the version (an accepted publish can take a while to appear), then downloads the tarball and checks `dist.integrity` (sha512), the SLSA v1 provenance (the tarball's sha512 is its subject; `release.yml` on `refs/heads/main`, a GitHub-hosted builder; `gitCommit` = `gitHead` = the tag, on `main`), and `npm audit signatures`. A version that `release-recover.mjs` skipped because npm's `latest` is newer (#837) has nothing to verify.
|
|
937
|
+
- It then writes `certified @ai-ecoverse/slicc-kernel@<v> sha256=<64 hex> (covers <first>–<v>; ci: <run>; pr-certified: <each merged pull request's cert-review>; known: <open known-issue issues>)` into the GitHub release's notes, between `<!-- cert-verify -->` markers. homescoop's ci reads exactly that prefix and re-hashes the npm tarball against it. `covers` starts after the last version npm serves, so a skipped version is covered by the next. Notes that already carry the identical line stay unchanged.
|
|
938
|
+
- A mismatch writes no line. The job is red with a `supply-chain alert`, and so is a line already in the notes with another sha256.
|
|
939
|
+
|
|
940
|
+
The tools in `tools/cert-review/`:
|
|
935
941
|
|
|
936
|
-
- **plan:** works out the version, the previous tag, the commit and the base (`base.mjs`). The base is the newest earlier release whose `cert-review` commit status is `success`, or, until there is one, the release `base.json` names (the cert lane's last line). CI never reads the npm `certified` dist-tag.
|
|
937
942
|
- **delta** (`delta.mjs`): lists the pull requests merged between the two tags (their merge commits, plus `pr` for a planted branch). Each gets its conventional-commit kind (`fix`, `feat`, `perf` and `revert` are behaviour changes) and the integration test files and titles it adds or changes. The titles are read from the pull request's patch: the added `test(…)` lines and the hunk headers git puts above changed lines. Escapes are decoded as JavaScript does. A title git cut short matches as a prefix, and each `${…}` in a template-literal title matches anything there, so `` `a forked child of a ${kind} WASIX program…` `` matches the rows it ran as. A title made only of placeholders is skipped.
|
|
938
943
|
- **suite** (`suite.mjs`, `reporter.mjs`) runs on the bench pool, without secrets. It takes the tag's tree with its own dependencies and Chromium, and replaces `dist/` with the **published npm tarball** (for a planted branch, its own `npm pack`). The test command runs in a session of its own (`detached`), and `suite.mjs` notes a `SIGINT` (`ignored` in its summary) instead of dying of it. That way a stray signal to the step's process group can't cut the run short, while a `SIGTERM` (a real cancel) still ends it. The log is written as the run goes, and a `SIGTERM` still writes the summary with the results so far (`exit: 'signal SIGTERM'`). A run that left only its `.jsonl` (the runner was lost) is read from that, as `exit: 'no summary'`. The NEGATIVE step runs even when the release suite's step failed. Before it reports, `suite.mjs` re-runs once just the test files that left no result (no rows, a cancelled row, or a file-level failure, such as a lost process), with a second `node --test` over those files, and merges their results (`retried`, `firstExit`, `unfinished` in the summary). That holds for the release suite and for each NEGATIVE run. A fix whose test passes on its baseline stays `no-negative`.
|
|
939
|
-
- The
|
|
940
|
-
- The base job runs the
|
|
944
|
+
- The candidate job runs the pull request's whole `test/integration/` suite on its packed build.
|
|
945
|
+
- The base job runs `main`'s own suite on `main`'s packed build, then the NEGATIVE (#861): `main`'s tree, with all of the pull request's `test/**` changes applied (test files, helpers and fixtures), on `main`'s build. So a fix in `tools/` fails there just as a fix in `src/` does. A test file that fails to load on `main` (a module or export that only the pull request adds) leaves no result for its titles, which is `prev-not-run`, never a NEGATIVE.
|
|
941
946
|
- Each run uses the tree's own `npm test` command, with a reporter that records every test and subtest with its status and error.
|
|
942
947
|
- **kernel-compat** (`compat.mjs`) supplies the base-set and catalog evidence: homescoop's kernel-compat run for the release.
|
|
943
948
|
- Its reference kernel is the certified base, not slicc-bios's pin. cert-review passes `--reference <base>`, and `kernel-compat.yml` takes a `reference` input that it hands to homescoop's workflow (homescoop#504), recorded in compat.json. So it uses a dispatched run named `Kernel compat v<version> ref <base>` (kernel-compat has no run of its own after Release). Its `candidate` job checks that npm serves the version, or takes a packed tarball from another run (`run`, `artifact`, `sha256`, for PR builds `x.y.z-pr.N.M`), and prints `@ai-ecoverse/slicc-kernel@<version>`, which compat.mjs matches. It dispatches one when none has finished and waits up to 150 minutes; package reruns carry the same reference.
|
|
@@ -948,24 +953,22 @@ Releases are cut by semantic-release on every push to `main`, in two jobs, and o
|
|
|
948
953
|
- `negative` (a fix's test fails on the previous dist and passes on the release) or `passes` (a feature's test passes);
|
|
949
954
|
- otherwise a finding: `no-test`, `no-negative`, `fails-on-release`, `not-run`, `prev-not-run` or `no-titles`.
|
|
950
955
|
|
|
951
|
-
It also diffs the two suites test by test (`regressed`, `newFailing`, `fixed`, `stillFailing`, `removed`), flags a suite run that didn't finish (`suite-incomplete`), and adds the kernel-compat findings. A release suite that didn't finish is a `missing-evidence` finding that always blocks `certified`, with the remedy `re-run the failed jobs`. That covers a signal, a timeout, a cut-off run, no results, or any test file without a result. Its base tests that didn't run are `unrun`, never `removed`. INCOMPLETE is not FAIL: record writes `cert-ci: INCOMPLETE <name>@<version> (<n> files without results: …; re-run the failed jobs)`,
|
|
956
|
+
It also diffs the two suites test by test (`regressed`, `newFailing`, `fixed`, `stillFailing`, `removed`), flags a suite run that didn't finish (`suite-incomplete`), and adds the kernel-compat findings. A release suite that didn't finish is a `missing-evidence` finding that always blocks `certified`, with the remedy `re-run the failed jobs`. That covers a signal, a timeout, a cut-off run, no results, or any test file without a result. Its base tests that didn't run are `unrun`, never `removed`. INCOMPLETE is not FAIL: record writes `cert-ci: INCOMPLETE <name>@<version> (<n> files without results: …; re-run the failed jobs) (ci: …; known: …)`, with the known issues as on a certified line, and the `cert-review` status is `error`, which means re-run, where `failure` means a real defect. A defect found in code (a compat crash) still FAILs. A base suite that is missing or didn't finish is a `missing-evidence` finding. It blocks `certified` in code (record FAILs it) unless the release suite is complete and all-pass and the delta removes or renames no test file and drops no test title (`dropped` per row). Either way the line ends with `base suite incomplete (<n> results)`. Claude, on Bedrock with the key only in that step, then reads those artifacts and `instructions.txt` (which has the roll-forward FAIL list verbatim) and writes `verdict.json` and `comment.md`. Its tools are reading files, `gh pr view`, and `gh issue list`/`view`. `gh issue create` is allowed only for a release on `main`; otherwise, and for an issue in another repository, it lists the issue under `elsewhere` for the cert lane to file.
|
|
952
957
|
- **record** (`record.mjs`), in code, with read-only permissions:
|
|
953
958
|
- It re-downloads the tarball and checks `dist.integrity` (sha512) and the sha256, the SLSA v1 provenance (the tarball is its subject; `release.yml`, `refs/heads/main`, a GitHub-hosted builder; commit = `gitHead` = the tag), `npm audit signatures`, and that the commit is on `main`. A problem there, a `compat-crash`, or a planted build is a FAIL whatever the review says.
|
|
954
|
-
- It adds the **known issues as of the release**: every issue labelled `known-issue` (open or closed) and every one in `known.json`, unless it was fixed before the release. That holds when `known.json`'s `fixedIn` names an earlier or equal version, or the merged pull request that closed it is an ancestor of the tag, or it was closed (with no closing pull request) before the release commit. An issue closed as not planned or as a duplicate is dropped.
|
|
959
|
+
- It adds the **known issues as of the release**: every issue labelled `known-issue` (open or closed) and every one in `known.json`, unless it was fixed before the release. An issue in another repository, whether listed or named by the reviewer, counts only while it is open. That holds when `known.json`'s `fixedIn` names an earlier or equal version, or the merged pull request that closed it is an ancestor of the tag, or it was closed (with no closing pull request) before the release commit. An issue closed as not planned or as a duplicate is dropped.
|
|
955
960
|
- The line is `certified @ai-ecoverse/slicc-kernel@<v> sha256=<…> (ci: <run>; known: …)`. Any test gaps from the findings (`no-test`, `no-negative`, `prev-not-run`, `no-titles`, `not-run`) are appended in code as `test gaps: #<pr> <kind>`, so a fix without a NEGATIVE is never certified silently.
|
|
956
|
-
- **
|
|
957
|
-
|
|
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.
|
|
961
|
+
- **verify** (`verify.mjs`) is the `release / verify on npm` job above.
|
|
959
962
|
|
|
960
|
-
### Certification in the pull request (`cert-review
|
|
963
|
+
### Certification in the pull request (`cert-review`)
|
|
961
964
|
|
|
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.
|
|
965
|
+
`.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. It reports as the `cert-review` commit status, a required check of `main`'s merge queue. Every same-repo pull request gets one (`not behaviour`, `waits for merge-ok`, the review's verdict, or the self-guard's hold). **A pull request from a fork gets none**, because fork code never reaches the pools or the key, so it can't enter the queue: only Lars merges one, after reading it.
|
|
963
966
|
|
|
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
|
|
967
|
+
- **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 a change to the reviewer is held by the **certification self-guard**, the shared composite action `slicc-shared-web/.github/actions/cert-self-guard` at a pinned sha. Its light `self-guard` job runs on every pull request event and every merge group. When the diff touches the reviewer (`.github/workflows/cert*.yml`, `tools/cert-review/**`, `.github/runner.mjs` and the guard's own paths), `cert-review` is red with `reviewer change: needs coordinator review (cert-review-approved on this head)` and no review runs. The label is the Kernel Coordinator's alone: they add `cert-review-approved` after reading the change, and the review then starts with `merge-ok`. The approval is bound to the head's arrival on GitHub, and a push removes the label. A merge group that holds an unapproved reviewer change is red too. If the guard doesn't finish, the status is an error that asks to re-run the failed jobs. **The residual:** the pull request's YAML still decides what runs, so the guard catches an accidental edit, not a deliberate rewrite of this workflow. 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
968
|
- **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
969
|
- **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
970
|
- the candidate's whole suite, and the base's;
|
|
968
|
-
- the pull request's new or changed test files on the
|
|
971
|
+
- the pull request's new or changed test files on `main`'s tree, with the pull request's `test/**` applied, and `main`'s build (the NEGATIVE: a fix's test must fail on `main` and pass on the candidate);
|
|
969
972
|
- kernel-compat on the candidate's artifact (#812), against npm's `latest`;
|
|
970
973
|
- the review (Claude through `claude-code-action/base-action`, with the Bedrock key in that step only, no issue filing);
|
|
971
974
|
- and record, which re-hashes the candidate's artifact against the pack job's sha256.
|
|
@@ -1100,6 +1103,16 @@ A threaded WASIX C program for `test/integration/thread-signal.test.mjs` (#520).
|
|
|
1100
1103
|
|
|
1101
1104
|
**Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 (`-pthread`) and binaryen's `wasm-opt` on `PATH`. `bin/sigthread.wasm` is 44,788 bytes, sha256 `5781fa39d0a0032cd9b214b71de1f15f589b40e4cecd6862362048dd27f0db56`.
|
|
1102
1105
|
|
|
1106
|
+
### `spintest`
|
|
1107
|
+
|
|
1108
|
+
A WASIX C program and a JS command for `test/integration/transport-spin.test.mjs`, which guards the sync transport's paths from #856 (#864).
|
|
1109
|
+
- `spintest rounds N` does N 1-byte write+read rounds on a pipe, a different byte each round, and prints `N rounds in order` if every byte came back as sent. These are fast replies, which take the transport's spin.
|
|
1110
|
+
- `spintest wait` prints `waiting` and blocks in a 1-byte `read` of its terminal, a reply that comes only when the test types. The test measures its own process's CPU (all threads) while the read blocks.
|
|
1111
|
+
- `spintest big M` writes an M MiB file and reads it back in 1 MiB `read` calls, each of which must be whole and match. A 1 MiB read fills the transport window exactly.
|
|
1112
|
+
- `spinread PATH` (`bin/spinread.mjs`, a JS command) reads a whole file through the synchronous `ctx.sync.fs.readFile` and prints its length and a 31-hash. A file over 1 MiB is a reply larger than the window, so it comes in several chunks.
|
|
1113
|
+
|
|
1114
|
+
**Build:** `WASIX_SYSROOT_28=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 and binaryen's `wasm-opt` on `PATH`. `bin/spintest.wasm` is 50,095 bytes, sha256 `be7659422f187a97fac412ac1dd2ec9d412e8dcd067da88157960352ded6178b`.
|
|
1115
|
+
|
|
1103
1116
|
### `longwait`
|
|
1104
1117
|
|
|
1105
1118
|
A WASIX C program for `test/integration/long-timers.test.mjs` (#767).
|
package/dist/kernel-worker.js
CHANGED
|
@@ -219,13 +219,22 @@ var IndexedDbMeta = class {
|
|
|
219
219
|
this.cache.set(path, next3 ?? null);
|
|
220
220
|
}
|
|
221
221
|
async updateMany(changes) {
|
|
222
|
+
if (changes.length === 0) return;
|
|
223
|
+
this.settleRemoves();
|
|
222
224
|
const [transaction, store] = this.transaction("readwrite");
|
|
225
|
+
const next3 = /* @__PURE__ */ new Map();
|
|
223
226
|
for (const { path, change } of changes) {
|
|
224
227
|
const request2 = store.get(path);
|
|
225
|
-
request2.onsuccess = () =>
|
|
228
|
+
request2.onsuccess = () => {
|
|
229
|
+
const entry = change(request2.result);
|
|
230
|
+
next3.set(path, entry ?? null);
|
|
231
|
+
if (entry) store.put(entry);
|
|
232
|
+
else store.delete(path);
|
|
233
|
+
};
|
|
226
234
|
}
|
|
227
235
|
await settle(transaction, () => void 0);
|
|
228
236
|
this.changed({ paths: changes.map(({ path }) => path) });
|
|
237
|
+
for (const [path, entry] of next3) this.cache.set(path, entry);
|
|
229
238
|
}
|
|
230
239
|
close() {
|
|
231
240
|
this.channel.close();
|
|
@@ -984,6 +993,8 @@ var S_ISGID2 = 1024;
|
|
|
984
993
|
var READ_ATTEMPTS = 3;
|
|
985
994
|
var MAX_LINKS2 = 40;
|
|
986
995
|
var CHECK_MS = 1e3;
|
|
996
|
+
var FILE_HANDLES = 4096;
|
|
997
|
+
var STALE = /* @__PURE__ */ new Set(["NotFoundError", "TypeMismatchError"]);
|
|
987
998
|
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
988
999
|
var ERRNO_BY_DOM_ERROR = {
|
|
989
1000
|
NotFoundError: "ENOENT",
|
|
@@ -1019,6 +1030,12 @@ function sidecar(meta) {
|
|
|
1019
1030
|
});
|
|
1020
1031
|
const names = meta.fresh ? [...META_OPS, "fresh"] : META_OPS;
|
|
1021
1032
|
const ops = Object.fromEntries(names.map((op) => [op, guarded2(op)]));
|
|
1033
|
+
const { updateMany } = meta;
|
|
1034
|
+
ops.updateMany = updateMany ? (changes) => updateMany.call(meta, changes).catch((err) => {
|
|
1035
|
+
throw translate(err, changes[0]?.path ?? "/", "sidecar", false);
|
|
1036
|
+
}) : async (changes) => {
|
|
1037
|
+
for (const { path, change } of changes) await ops.update(path, change);
|
|
1038
|
+
};
|
|
1022
1039
|
if (meta.absent) ops.absent = meta.absent.bind(meta);
|
|
1023
1040
|
const { removeFile } = meta;
|
|
1024
1041
|
if (removeFile)
|
|
@@ -1109,6 +1126,9 @@ var OpfsFs = class {
|
|
|
1109
1126
|
root;
|
|
1110
1127
|
meta;
|
|
1111
1128
|
dirs = new PathMap();
|
|
1129
|
+
files = new PathMap();
|
|
1130
|
+
snapshots = /* @__PURE__ */ new WeakMap();
|
|
1131
|
+
found = /* @__PURE__ */ new WeakMap();
|
|
1112
1132
|
checked = /* @__PURE__ */ new WeakMap();
|
|
1113
1133
|
channel;
|
|
1114
1134
|
remote;
|
|
@@ -1346,6 +1366,19 @@ var OpfsFs = class {
|
|
|
1346
1366
|
}
|
|
1347
1367
|
forget(path) {
|
|
1348
1368
|
this.dirs.deleteTree(path);
|
|
1369
|
+
this.files.deleteTree(path);
|
|
1370
|
+
}
|
|
1371
|
+
remember(path, handle, looked = false) {
|
|
1372
|
+
if (handle.kind !== "file") return handle;
|
|
1373
|
+
if (looked) {
|
|
1374
|
+
this.found.set(handle, performance.now());
|
|
1375
|
+
this.snapshots.delete(handle);
|
|
1376
|
+
}
|
|
1377
|
+
this.files.delete(path);
|
|
1378
|
+
if (this.files.size >= FILE_HANDLES)
|
|
1379
|
+
this.files.delete(this.files.keys().next().value);
|
|
1380
|
+
this.files.set(path, handle);
|
|
1381
|
+
return handle;
|
|
1349
1382
|
}
|
|
1350
1383
|
async inDir(path, op) {
|
|
1351
1384
|
const dir = await this.dir(path);
|
|
@@ -1369,16 +1402,30 @@ var OpfsFs = class {
|
|
|
1369
1402
|
}
|
|
1370
1403
|
async handle(path) {
|
|
1371
1404
|
if (split(path).length === 0) return this.root;
|
|
1405
|
+
const cached = this.files.get(path);
|
|
1406
|
+
const fresh2 = cached && performance.now() - (this.found.get(cached) ?? -CHECK_MS) < CHECK_MS;
|
|
1407
|
+
if (cached && !fresh2) this.files.delete(path);
|
|
1408
|
+
if (cached && fresh2) {
|
|
1409
|
+
try {
|
|
1410
|
+
this.snapshots.set(cached, await cached.getFile());
|
|
1411
|
+
return this.remember(path, cached);
|
|
1412
|
+
} catch (err) {
|
|
1413
|
+
if (!STALE.has(err?.name))
|
|
1414
|
+
throw translate(err, path, "read");
|
|
1415
|
+
this.files.delete(path);
|
|
1416
|
+
this.snapshots.delete(cached);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1372
1419
|
const [dir, name2] = await this.parent(path);
|
|
1373
1420
|
const known = this.dirs.has(path);
|
|
1374
1421
|
return this.inDir(dir, async (parent) => {
|
|
1375
1422
|
const [first, second] = known ? ["directory", "file"] : ["file", "directory"];
|
|
1376
1423
|
try {
|
|
1377
|
-
return await this.child(parent, name2, first);
|
|
1424
|
+
return this.remember(path, await this.child(parent, name2, first), true);
|
|
1378
1425
|
} catch (err) {
|
|
1379
1426
|
if (err?.name !== "TypeMismatchError") throw err;
|
|
1380
1427
|
}
|
|
1381
|
-
return this.child(parent, name2, second);
|
|
1428
|
+
return this.remember(path, await this.child(parent, name2, second), true);
|
|
1382
1429
|
}).catch((err) => {
|
|
1383
1430
|
throw translate(err, path);
|
|
1384
1431
|
});
|
|
@@ -1471,14 +1518,20 @@ var OpfsFs = class {
|
|
|
1471
1518
|
this.flushing.file.unref?.();
|
|
1472
1519
|
}
|
|
1473
1520
|
async flushFileTimes() {
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1521
|
+
const pending = [...this.fileTimes];
|
|
1522
|
+
const stamps = [];
|
|
1523
|
+
for (const [path] of pending) stamps.push(await this.modified(path).catch(() => 0));
|
|
1524
|
+
await this.batched(
|
|
1525
|
+
pending.map(([path, ms], i) => ({
|
|
1477
1526
|
path,
|
|
1478
|
-
(entry) => this.fileTimes.get(path) !== ms || this.explicitWins(entry, ms) ? entry : { ...entry, path, mtimeMs: ms, mtimeFor, ctimeMs: ms }
|
|
1479
|
-
)
|
|
1527
|
+
change: (entry) => this.fileTimes.get(path) !== ms || this.explicitWins(entry, ms) ? entry : { ...entry, path, mtimeMs: ms, mtimeFor: stamps[i], ctimeMs: ms }
|
|
1528
|
+
}))
|
|
1529
|
+
);
|
|
1530
|
+
for (const [path, ms] of pending)
|
|
1480
1531
|
if (this.fileTimes.get(path) === ms) this.fileTimes.delete(path);
|
|
1481
|
-
|
|
1532
|
+
}
|
|
1533
|
+
batched(changes) {
|
|
1534
|
+
return this.meta.updateMany(changes);
|
|
1482
1535
|
}
|
|
1483
1536
|
timesOf(path) {
|
|
1484
1537
|
return this.dirTimes.get(path) ?? this.fileTimes.get(path);
|
|
@@ -1510,20 +1563,32 @@ var OpfsFs = class {
|
|
|
1510
1563
|
});
|
|
1511
1564
|
}
|
|
1512
1565
|
async flushDirTimes() {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1566
|
+
while (this.dirDirty.size > 0) {
|
|
1567
|
+
const pending = [];
|
|
1568
|
+
for (const path of this.dirDirty) {
|
|
1569
|
+
const ms = this.dirTimes.get(path);
|
|
1570
|
+
if (ms !== void 0) pending.push([path, ms]);
|
|
1571
|
+
}
|
|
1572
|
+
this.dirDirty.clear();
|
|
1573
|
+
await this.batched(
|
|
1574
|
+
pending.map(([path, ms]) => ({
|
|
1575
|
+
path,
|
|
1576
|
+
change: (entry) => (entry?.timesAt ?? -1) >= ms ? entry : { ...entry, path, mtimeMs: ms, mtimeFor: 0, ctimeMs: ms }
|
|
1577
|
+
}))
|
|
1520
1578
|
);
|
|
1521
|
-
|
|
1579
|
+
for (const [path, ms] of pending)
|
|
1580
|
+
if (this.dirTimes.get(path) === ms) this.dirTimes.delete(path);
|
|
1522
1581
|
}
|
|
1523
1582
|
}
|
|
1583
|
+
async snapshot(handle) {
|
|
1584
|
+
const taken = this.snapshots.get(handle);
|
|
1585
|
+
if (!taken) return handle.getFile();
|
|
1586
|
+
this.snapshots.delete(handle);
|
|
1587
|
+
return taken;
|
|
1588
|
+
}
|
|
1524
1589
|
async statOf(path, handle, meta, touched) {
|
|
1525
1590
|
const directory = handle.kind === "directory";
|
|
1526
|
-
const file = directory ? void 0 : await
|
|
1591
|
+
const file = directory ? void 0 : await this.snapshot(handle);
|
|
1527
1592
|
const modified = file?.lastModified ?? 0;
|
|
1528
1593
|
const kept = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
|
|
1529
1594
|
const mtime = touched ?? kept;
|
|
@@ -6108,7 +6173,7 @@ function satisfies(version, range2) {
|
|
|
6108
6173
|
}
|
|
6109
6174
|
|
|
6110
6175
|
// src/version.ts
|
|
6111
|
-
var KERNEL_VERSION = true ? "1.110.
|
|
6176
|
+
var KERNEL_VERSION = true ? "1.110.2" : "0.0.0-dev";
|
|
6112
6177
|
var OS_RELEASE = "6.0.0-slicc";
|
|
6113
6178
|
function procVersion() {
|
|
6114
6179
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|