superbee 0.4.6-pre.1 → 0.4.6-pre.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 +7 -2
- package/SKILL.md +13 -5
- package/dist/THIRD-PARTY-NOTICES.txt +2 -2
- package/dist/publication/types.d.ts +8 -0
- package/dist/publication-bridge.mjs +252 -89
- package/dist/publication.mjs +269 -91
- package/dist/superbee.mjs +55729 -50576
- package/package.json +2 -2
- package/references/hosted-checkout.md +1 -1
- package/references/modeling-and-delivery.md +1 -1
- package/references/recipes/review-workflow/references/view-authoring-v0.md +1 -2
- package/references/views/references/view-authoring-v0.md +1 -2
package/dist/publication.mjs
CHANGED
|
@@ -30844,15 +30844,30 @@ var FrontmatterRefusedError = class extends InvalidInputError {
|
|
|
30844
30844
|
}
|
|
30845
30845
|
};
|
|
30846
30846
|
var FilesystemIdentityAliasError = class extends InvalidInputError {
|
|
30847
|
+
reason = "filesystem_alias";
|
|
30847
30848
|
rel;
|
|
30848
30849
|
segment;
|
|
30849
|
-
|
|
30850
|
+
existing;
|
|
30851
|
+
suggestedPath;
|
|
30852
|
+
/**
|
|
30853
|
+
* `noun` names the existing entry ("folder" or "file"); `index` is the position of `segment`
|
|
30854
|
+
* in `rel`. Both default from the position of the first segment spelled `segment`.
|
|
30855
|
+
*/
|
|
30856
|
+
constructor(rel, segment, existing, noun, index2) {
|
|
30857
|
+
const segments = rel.split("/").filter((part) => part !== "" && part !== ".");
|
|
30858
|
+
const at = index2 ?? segments.indexOf(segment);
|
|
30859
|
+
const kind2 = noun ?? (at === segments.length - 1 ? "file" : "folder");
|
|
30860
|
+
const clash = existing === void 0 ? `but a ${kind2} with that name in a different case or Unicode form already exists` : existing.normalize("NFC") === segment.normalize("NFC") ? `but ${kind2} '${existing}' already exists, written in a different Unicode form` : `but ${kind2} '${existing}' already exists`;
|
|
30861
|
+
const difference = existing === void 0 ? "case or Unicode form" : existing.normalize("NFC") === segment.normalize("NFC") ? "Unicode form" : existing.toLowerCase() === segment.toLowerCase() ? "case" : "case and Unicode form";
|
|
30850
30862
|
super(
|
|
30851
|
-
`Path '${rel}'
|
|
30863
|
+
`Path '${rel}' uses ${kind2} '${segment}', ${clash}, and this disk treats names that differ only by ${difference} as the same name. Use the existing spelling.`
|
|
30852
30864
|
);
|
|
30853
30865
|
this.name = "FilesystemIdentityAliasError";
|
|
30854
30866
|
this.rel = rel;
|
|
30855
30867
|
this.segment = segment;
|
|
30868
|
+
this.existing = existing;
|
|
30869
|
+
const unusable = existing === void 0 || at < 0 || segments[at] !== segment || kind2 === "file" && at < segments.length - 1 || existing.normalize("NFC") === segment.normalize("NFC");
|
|
30870
|
+
this.suggestedPath = unusable ? void 0 : segments.map((part, position2) => position2 === at ? existing : part).join("/");
|
|
30856
30871
|
}
|
|
30857
30872
|
};
|
|
30858
30873
|
var ConcurrentReplacementError = class extends Error {
|
|
@@ -31057,6 +31072,9 @@ function delimiterLineEnd(input, offset) {
|
|
|
31057
31072
|
if (input[suffix] === "\r" && input[suffix + 1] === "\n") return suffix + 2;
|
|
31058
31073
|
return null;
|
|
31059
31074
|
}
|
|
31075
|
+
function startsWithFrontmatterSyntax(text4) {
|
|
31076
|
+
return text4.startsWith("\uFEFF") || delimiterLineEnd(text4, 0) !== null;
|
|
31077
|
+
}
|
|
31060
31078
|
function splitLeadingFrontmatter(raw, context) {
|
|
31061
31079
|
const input = raw.startsWith("\uFEFF") ? raw.slice(1) : raw;
|
|
31062
31080
|
const openingEnd = delimiterLineEnd(input, 0);
|
|
@@ -31162,7 +31180,10 @@ function stringifyWithSerializedFrontmatter(dumped, body) {
|
|
|
31162
31180
|
const content3 = body ?? "";
|
|
31163
31181
|
const newline = (value) => value.endsWith("\n") ? value : `${value}
|
|
31164
31182
|
`;
|
|
31165
|
-
if (dumped === "{}")
|
|
31183
|
+
if (dumped === "{}") {
|
|
31184
|
+
const stored = normalizeDocumentBodyForStorage(content3);
|
|
31185
|
+
return `${startsWithFrontmatterSyntax(stored) ? "---\n---\n" : ""}${stored}`;
|
|
31186
|
+
}
|
|
31166
31187
|
return `---
|
|
31167
31188
|
${newline(dumped)}---
|
|
31168
31189
|
${normalizeDocumentBodyForStorage(content3)}`;
|
|
@@ -31980,36 +32001,57 @@ function hasSourceIdentity(sources, identity) {
|
|
|
31980
32001
|
return Array.isArray(sources) && sources.some((entry) => matchesSourceIdentity(entry, identity));
|
|
31981
32002
|
}
|
|
31982
32003
|
|
|
32004
|
+
// ../core/src/skip-reasons.ts
|
|
32005
|
+
var PERMISSION_DENIED_PREFIX = "permission denied (";
|
|
32006
|
+
function permissionDeniedReason(code2) {
|
|
32007
|
+
return `${PERMISSION_DENIED_PREFIX}${code2})`;
|
|
32008
|
+
}
|
|
32009
|
+
function isPermissionDeniedSkip(skip) {
|
|
32010
|
+
return skip.reason.startsWith(PERMISSION_DENIED_PREFIX);
|
|
32011
|
+
}
|
|
32012
|
+
|
|
31983
32013
|
// ../core/src/document-scan.ts
|
|
31984
32014
|
function isEnoent(err) {
|
|
31985
32015
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
31986
32016
|
}
|
|
31987
|
-
|
|
31988
|
-
|
|
31989
|
-
|
|
31990
|
-
|
|
31991
|
-
|
|
31992
|
-
|
|
31993
|
-
|
|
31994
|
-
|
|
31995
|
-
|
|
31996
|
-
|
|
31997
|
-
|
|
31998
|
-
|
|
31999
|
-
|
|
32000
|
-
|
|
32001
|
-
|
|
32002
|
-
|
|
32003
|
-
}
|
|
32004
|
-
|
|
32005
|
-
|
|
32006
|
-
|
|
32007
|
-
|
|
32008
|
-
|
|
32017
|
+
function deniedReason(err) {
|
|
32018
|
+
const code2 = typeof err === "object" && err !== null ? err.code : void 0;
|
|
32019
|
+
return code2 === "EACCES" || code2 === "EPERM" ? permissionDeniedReason(code2) : void 0;
|
|
32020
|
+
}
|
|
32021
|
+
async function readManyExisting(backend, ids, onMalformed, onAliased, onDenied) {
|
|
32022
|
+
const tolerated = (err) => isEnoent(err) || err instanceof MalformedDocumentError && onMalformed !== void 0 || err instanceof FilesystemIdentityAliasError && onAliased !== void 0 || deniedReason(err) !== void 0 && onDenied !== void 0;
|
|
32023
|
+
const readOne = async (id) => {
|
|
32024
|
+
try {
|
|
32025
|
+
return [await backend.read(id)];
|
|
32026
|
+
} catch (candidate) {
|
|
32027
|
+
if (isEnoent(candidate)) return [];
|
|
32028
|
+
if (candidate instanceof MalformedDocumentError && onMalformed) {
|
|
32029
|
+
onMalformed({ id, reason: candidate.detail });
|
|
32030
|
+
return [];
|
|
32031
|
+
}
|
|
32032
|
+
if (candidate instanceof FilesystemIdentityAliasError && onAliased) {
|
|
32033
|
+
onAliased({ id, reason: candidate.message });
|
|
32034
|
+
return [];
|
|
32035
|
+
}
|
|
32036
|
+
const reason = deniedReason(candidate);
|
|
32037
|
+
if (reason !== void 0 && onDenied) {
|
|
32038
|
+
onDenied({ id, reason });
|
|
32039
|
+
return [];
|
|
32009
32040
|
}
|
|
32041
|
+
throw candidate;
|
|
32010
32042
|
}
|
|
32011
|
-
|
|
32012
|
-
|
|
32043
|
+
};
|
|
32044
|
+
const readBatch = async (batch) => {
|
|
32045
|
+
try {
|
|
32046
|
+
return await backend.readMany(batch);
|
|
32047
|
+
} catch (err) {
|
|
32048
|
+
if (!tolerated(err)) throw err;
|
|
32049
|
+
if (batch.length === 1) return readOne(batch[0]);
|
|
32050
|
+
const half = Math.ceil(batch.length / 2);
|
|
32051
|
+
return [...await readBatch(batch.slice(0, half)), ...await readBatch(batch.slice(half))];
|
|
32052
|
+
}
|
|
32053
|
+
};
|
|
32054
|
+
return readBatch(ids);
|
|
32013
32055
|
}
|
|
32014
32056
|
|
|
32015
32057
|
// ../core/src/engine.ts
|
|
@@ -32033,7 +32075,7 @@ async function readDocVersioned(backend, id) {
|
|
|
32033
32075
|
async function scanMatching(backend, filter, onSkip) {
|
|
32034
32076
|
const ids = await backend.list(filter.prefix);
|
|
32035
32077
|
const results = [];
|
|
32036
|
-
for (const result of await readManyExisting(backend, ids, onSkip)) {
|
|
32078
|
+
for (const result of await readManyExisting(backend, ids, onSkip, void 0, onSkip)) {
|
|
32037
32079
|
if (matchesFilter(result.doc, filter)) results.push(result);
|
|
32038
32080
|
}
|
|
32039
32081
|
results.sort((a, b) => compareCodeUnits(a.doc.id, b.doc.id));
|
|
@@ -32158,7 +32200,7 @@ function assertStorableReservedWrite(dir, name, content3) {
|
|
|
32158
32200
|
// ../core/src/filesystem-identity.ts
|
|
32159
32201
|
import { promises as fs2 } from "node:fs";
|
|
32160
32202
|
import path2 from "node:path";
|
|
32161
|
-
import { createHash as createHash2
|
|
32203
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
32162
32204
|
|
|
32163
32205
|
// ../core/src/filesystem-lock.ts
|
|
32164
32206
|
import { promises as fs, realpathSync } from "node:fs";
|
|
@@ -32213,6 +32255,8 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
32213
32255
|
var OWNER_FILE = "owner.json";
|
|
32214
32256
|
var DEFAULT_WAIT_MS = 5e3;
|
|
32215
32257
|
var DEFAULT_POLL_MS = 25;
|
|
32258
|
+
var UNREADABLE_OWNER_POLL_MS = 2e3;
|
|
32259
|
+
var ABANDONED_CLAIM_AGE_MS = 2e3;
|
|
32216
32260
|
var FilesystemMutationLockError = class extends Error {
|
|
32217
32261
|
lockPath;
|
|
32218
32262
|
owner;
|
|
@@ -32300,9 +32344,13 @@ async function readOwnerRecord(lockPath) {
|
|
|
32300
32344
|
return owner === null ? { state: "absent" } : { state: "record", owner };
|
|
32301
32345
|
}
|
|
32302
32346
|
async function resolveOwnerRecord(lockPath, started, waitMs, pollMs) {
|
|
32347
|
+
let firstUnreadable;
|
|
32303
32348
|
while (true) {
|
|
32304
32349
|
const record3 = await readOwnerRecord(lockPath);
|
|
32305
|
-
if (record3.state !== "unreadable"
|
|
32350
|
+
if (record3.state !== "unreadable") return record3;
|
|
32351
|
+
const now = Date.now();
|
|
32352
|
+
firstUnreadable ??= now;
|
|
32353
|
+
if (now - started >= waitMs || now - firstUnreadable >= UNREADABLE_OWNER_POLL_MS) return record3;
|
|
32306
32354
|
await delay(pollMs);
|
|
32307
32355
|
}
|
|
32308
32356
|
}
|
|
@@ -32353,6 +32401,17 @@ async function quarantineStaleLock(lockPath, owner, policy) {
|
|
|
32353
32401
|
throw err;
|
|
32354
32402
|
}
|
|
32355
32403
|
}
|
|
32404
|
+
async function removeAbandonedClaim(lockPath) {
|
|
32405
|
+
try {
|
|
32406
|
+
const info = await fs.lstat(lockPath);
|
|
32407
|
+
const age = Date.now() - info.mtimeMs;
|
|
32408
|
+
if (!info.isDirectory() || age < ABANDONED_CLAIM_AGE_MS) return false;
|
|
32409
|
+
await fs.rmdir(lockPath);
|
|
32410
|
+
return true;
|
|
32411
|
+
} catch {
|
|
32412
|
+
return false;
|
|
32413
|
+
}
|
|
32414
|
+
}
|
|
32356
32415
|
function delay(ms) {
|
|
32357
32416
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
32358
32417
|
}
|
|
@@ -32403,7 +32462,7 @@ async function timeoutError(lockPath, snapshot, guarded) {
|
|
|
32403
32462
|
const sameHost = owner?.hostname === hostname();
|
|
32404
32463
|
let message;
|
|
32405
32464
|
if (malformed) {
|
|
32406
|
-
message = `timed out waiting for filesystem mutation lock '${lockPath}'; its owner metadata is missing or malformed.
|
|
32465
|
+
message = `timed out waiting for filesystem mutation lock '${lockPath}'; its owner metadata is missing or malformed. Remove the lock directory once no Superbee process is running, then retry.`;
|
|
32407
32466
|
} else if (stale) {
|
|
32408
32467
|
message = `stale filesystem mutation lock '${lockPath}' belongs to absent PID ${owner.pid} on ${owner.hostname}. Inspect and remove the lock, then retry.`;
|
|
32409
32468
|
} else if (!sameHost) {
|
|
@@ -32452,7 +32511,9 @@ async function claimLockPath(lockPath, owner, waitMs, pollMs, policy) {
|
|
|
32452
32511
|
} else {
|
|
32453
32512
|
unwitnessedRetryUsed = false;
|
|
32454
32513
|
}
|
|
32455
|
-
|
|
32514
|
+
const existing = await readOwnerRecord(lockPath);
|
|
32515
|
+
let existingOwner = existing.state === "record" ? existing.owner : null;
|
|
32516
|
+
if (existing.state === "absent" && await removeAbandonedClaim(lockPath)) continue;
|
|
32456
32517
|
if (existingOwner !== null) {
|
|
32457
32518
|
if (await quarantineStaleLock(lockPath, existingOwner, policy)) continue;
|
|
32458
32519
|
existingOwner = await readOwner(lockPath);
|
|
@@ -32472,14 +32533,13 @@ async function claimLockPath(lockPath, owner, waitMs, pollMs, policy) {
|
|
|
32472
32533
|
});
|
|
32473
32534
|
} catch (err) {
|
|
32474
32535
|
const code2 = err.code;
|
|
32475
|
-
if (code2 === "EEXIST") {
|
|
32536
|
+
if (code2 === "EEXIST" || code2 === "ENOENT" || code2 === "ENOTDIR" || await claimDirectoryLost(lockPath, claimed)) {
|
|
32476
32537
|
heldTokens.delete(owner.token);
|
|
32477
32538
|
continue;
|
|
32478
32539
|
}
|
|
32479
32540
|
await rollBackOwnClaim(lockPath, owner, claimed, waitMs, pollMs, policy).catch(() => {
|
|
32480
32541
|
});
|
|
32481
32542
|
heldTokens.delete(owner.token);
|
|
32482
|
-
if (code2 === "ENOENT" || code2 === "ENOTDIR") continue;
|
|
32483
32543
|
throw err;
|
|
32484
32544
|
}
|
|
32485
32545
|
let inFlight;
|
|
@@ -32509,9 +32569,18 @@ async function rollBackOwnClaim(lockPath, owner, claimed, waitMs, pollMs, policy
|
|
|
32509
32569
|
return;
|
|
32510
32570
|
}
|
|
32511
32571
|
if (record3.state === "unreadable" || !await isClaimedDirectory(lockPath, claimed)) return;
|
|
32512
|
-
|
|
32513
|
-
|
|
32514
|
-
|
|
32572
|
+
await fs.rmdir(lockPath);
|
|
32573
|
+
}
|
|
32574
|
+
async function claimDirectoryLost(lockPath, claimed) {
|
|
32575
|
+
try {
|
|
32576
|
+
const current = await fs.lstat(lockPath, { bigint: true });
|
|
32577
|
+
if (claimed === null) return false;
|
|
32578
|
+
const reborn = claimed.birthtimeNs > 0n && current.birthtimeNs > 0n && current.birthtimeNs !== claimed.birthtimeNs;
|
|
32579
|
+
return current.dev !== claimed.dev || current.ino !== claimed.ino || reborn;
|
|
32580
|
+
} catch (err) {
|
|
32581
|
+
const code2 = err.code;
|
|
32582
|
+
return code2 === "ENOENT" || code2 === "ENOTDIR";
|
|
32583
|
+
}
|
|
32515
32584
|
}
|
|
32516
32585
|
async function isClaimedDirectory(lockPath, claimed) {
|
|
32517
32586
|
if (claimed === null || claimed.birthtimeNs <= 0n) return false;
|
|
@@ -32613,6 +32682,12 @@ function foldSegment(segment) {
|
|
|
32613
32682
|
return segment.normalize("NFKD").toLowerCase().toUpperCase().toLowerCase();
|
|
32614
32683
|
}
|
|
32615
32684
|
|
|
32685
|
+
// ../core/src/filesystem-temp-name.ts
|
|
32686
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
32687
|
+
function identityTempName(leaf) {
|
|
32688
|
+
return `.${leaf}.${process.pid}.${Date.now()}.${randomUUID2()}.tmp`;
|
|
32689
|
+
}
|
|
32690
|
+
|
|
32616
32691
|
// ../core/src/filesystem-identity.ts
|
|
32617
32692
|
async function replaceExistingLeaf(port, tmp, target, rel) {
|
|
32618
32693
|
try {
|
|
@@ -32757,10 +32832,32 @@ async function probeSegment(port, candidate, rel, segment) {
|
|
|
32757
32832
|
function hasExact(listing, segment) {
|
|
32758
32833
|
return listing.some((entry) => entry.name === segment);
|
|
32759
32834
|
}
|
|
32835
|
+
function existingSpelling(names, segment) {
|
|
32836
|
+
const folded = foldSegment(segment);
|
|
32837
|
+
const matches = [...names].filter((name) => name !== segment && foldSegment(name) === folded);
|
|
32838
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
32839
|
+
}
|
|
32840
|
+
function entryNoun(kind2, leaf) {
|
|
32841
|
+
if (kind2 === void 0) return leaf ? "file" : "folder";
|
|
32842
|
+
return kind2 === "directory" ? "folder" : "file";
|
|
32843
|
+
}
|
|
32844
|
+
function aliasRefusal(rel, segments, index2, names, kind2) {
|
|
32845
|
+
const segment = segments[index2];
|
|
32846
|
+
return new FilesystemIdentityAliasError(rel, segment, existingSpelling(names, segment), entryNoun(kind2, index2 === segments.length - 1), index2);
|
|
32847
|
+
}
|
|
32848
|
+
function aliasRefusalFromListing(rel, index2, listing, leaf) {
|
|
32849
|
+
const segment = relSegments(rel)[index2];
|
|
32850
|
+
const existing = existingSpelling(listing.map((entry) => entry.name), segment);
|
|
32851
|
+
const kind2 = existing === void 0 ? void 0 : listing.find((entry) => entry.name === existing)?.kind;
|
|
32852
|
+
return new FilesystemIdentityAliasError(rel, segment, existing, entryNoun(kind2, leaf), index2);
|
|
32853
|
+
}
|
|
32854
|
+
function listingView(names) {
|
|
32855
|
+
return { has: (name) => names.has(name), names: () => names };
|
|
32856
|
+
}
|
|
32760
32857
|
function freshListings(port) {
|
|
32761
32858
|
return async (dir) => {
|
|
32762
32859
|
const listing = await port.entries(dir);
|
|
32763
|
-
return listing === null ? null : { has: (name) => hasExact(listing, name) };
|
|
32860
|
+
return listing === null ? null : { has: (name) => hasExact(listing, name), names: () => listing.map((entry) => entry.name) };
|
|
32764
32861
|
};
|
|
32765
32862
|
}
|
|
32766
32863
|
async function walkExact(port, rootResolved, segments, rel) {
|
|
@@ -32772,7 +32869,7 @@ async function walkExact(port, rootResolved, segments, rel) {
|
|
|
32772
32869
|
if (probed === null) return { state: "absent", depth: index2 };
|
|
32773
32870
|
const listing = await port.entries(parent);
|
|
32774
32871
|
if (listing === null) return { state: "absent", depth: index2 };
|
|
32775
|
-
if (!hasExact(listing, segment)) throw
|
|
32872
|
+
if (!hasExact(listing, segment)) throw aliasRefusal(rel, segments, index2, listing.map((entry) => entry.name), probed.kind);
|
|
32776
32873
|
if (index2 === segments.length - 1) return { state: "exact", leaf: probed };
|
|
32777
32874
|
if (probed.kind !== "directory") return { state: "shape-mismatch", segment };
|
|
32778
32875
|
parent = candidate;
|
|
@@ -32787,35 +32884,53 @@ async function recordWalk(port, rootResolved, segments, rel) {
|
|
|
32787
32884
|
const segment = segments[index2];
|
|
32788
32885
|
const candidate = path2.join(parent, segment);
|
|
32789
32886
|
const probed = await probeSegment(port, candidate, rel, segment);
|
|
32790
|
-
if (probed === null) return
|
|
32791
|
-
if (index2 < segments.length - 1 && probed.kind !== "directory") return null;
|
|
32887
|
+
if (probed === null) return { recorded, complete: false };
|
|
32792
32888
|
recorded.push(probed);
|
|
32889
|
+
if (index2 < segments.length - 1 && probed.kind !== "directory") return { recorded, complete: false };
|
|
32793
32890
|
parent = candidate;
|
|
32794
32891
|
}
|
|
32795
|
-
return recorded;
|
|
32892
|
+
return { recorded, complete: true };
|
|
32796
32893
|
}
|
|
32797
|
-
async function confirmedWalk(port, rootResolved, segments, rel, recorded, handle, listings = freshListings(port)) {
|
|
32894
|
+
async function confirmedWalk(port, rootResolved, segments, rel, recorded, handle, listings = freshListings(port), stopAt = segments.length) {
|
|
32798
32895
|
let parent = rootResolved;
|
|
32799
|
-
for (let index2 = 0; index2 <
|
|
32896
|
+
for (let index2 = 0; index2 < stopAt; index2++) {
|
|
32800
32897
|
const segment = segments[index2];
|
|
32801
32898
|
const candidate = path2.join(parent, segment);
|
|
32802
32899
|
const listing = await listings(parent);
|
|
32803
32900
|
if (listing === null) return "absent";
|
|
32804
32901
|
const listed = listing.has(segment);
|
|
32805
32902
|
if (handle !== null && index2 === segments.length - 1) {
|
|
32806
|
-
const
|
|
32807
|
-
|
|
32903
|
+
const probe = await probeSegment(port, candidate, rel, segment);
|
|
32904
|
+
const verdict = classifyLeaf({ listed, probe, handle });
|
|
32905
|
+
if (verdict === "aliased") throw aliasRefusal(rel, segments, index2, listing.names(), probe?.kind);
|
|
32808
32906
|
return verdict === "replaced" ? "restart" : verdict;
|
|
32809
32907
|
}
|
|
32810
32908
|
if (index2 < segments.length - 1 || !listed) {
|
|
32811
|
-
const
|
|
32812
|
-
|
|
32909
|
+
const probe = await probeSegment(port, candidate, rel, segment);
|
|
32910
|
+
const verdict = confirmAlias(listed, probe, recorded[index2]);
|
|
32911
|
+
if (verdict === "aliased") throw aliasRefusal(rel, segments, index2, listing.names(), probe?.kind);
|
|
32813
32912
|
if (verdict !== "continue") return verdict;
|
|
32814
32913
|
}
|
|
32815
32914
|
parent = candidate;
|
|
32816
32915
|
}
|
|
32817
32916
|
return "continue";
|
|
32818
32917
|
}
|
|
32918
|
+
async function confirmPrefix(port, rootResolved, segments, rel, recorded) {
|
|
32919
|
+
if (recorded.length === 0) return "absent";
|
|
32920
|
+
let kept = prefixListings.get(port);
|
|
32921
|
+
if (kept === void 0) prefixListings.set(port, kept = /* @__PURE__ */ new Map());
|
|
32922
|
+
const cached = witnessedListings(port, kept, Date.now, PREFIX_LISTING_CAPACITY);
|
|
32923
|
+
let verdict;
|
|
32924
|
+
try {
|
|
32925
|
+
verdict = await confirmedWalk(port, rootResolved, segments, rel, recorded, null, cached.source, recorded.length);
|
|
32926
|
+
} catch (error) {
|
|
32927
|
+
if (!cached.reused()) throw error;
|
|
32928
|
+
kept.clear();
|
|
32929
|
+
verdict = await confirmedWalk(port, rootResolved, segments, rel, recorded, null, freshListings(port), recorded.length);
|
|
32930
|
+
}
|
|
32931
|
+
if (verdict === "restart" && cached.reused()) kept.clear();
|
|
32932
|
+
return verdict === "restart" ? "restart" : "absent";
|
|
32933
|
+
}
|
|
32819
32934
|
function countRestart(restarts, rel) {
|
|
32820
32935
|
if (restarts >= MAX_RESTARTS) throw new ConcurrentReplacementError(rel, restarts + 1);
|
|
32821
32936
|
return restarts + 1;
|
|
@@ -32827,8 +32942,14 @@ async function observeWith(port, rootResolved, rel, read, listings, onExact) {
|
|
|
32827
32942
|
const segments = relSegments(rel);
|
|
32828
32943
|
const target = path2.join(rootResolved, ...segments);
|
|
32829
32944
|
for (let restarts = 0; ; ) {
|
|
32830
|
-
const
|
|
32831
|
-
if (
|
|
32945
|
+
const walk = await recordWalk(port, rootResolved, segments, rel);
|
|
32946
|
+
if (!walk.complete) {
|
|
32947
|
+
const prefix = await confirmPrefix(port, rootResolved, segments, rel, walk.recorded);
|
|
32948
|
+
if (prefix === "absent") return { state: "absent" };
|
|
32949
|
+
restarts = countRestart(restarts, rel);
|
|
32950
|
+
continue;
|
|
32951
|
+
}
|
|
32952
|
+
const recorded = walk.recorded;
|
|
32832
32953
|
const verified = await confirmedWalk(port, rootResolved, segments, rel, recorded, null, listings);
|
|
32833
32954
|
if (verified === "absent") return { state: "absent" };
|
|
32834
32955
|
if (verified === "restart") {
|
|
@@ -32867,8 +32988,14 @@ async function probeExact(port, root, rel) {
|
|
|
32867
32988
|
const segments = relSegments(rel);
|
|
32868
32989
|
const rootResolved = path2.resolve(root);
|
|
32869
32990
|
for (let restarts = 0; ; ) {
|
|
32870
|
-
const
|
|
32871
|
-
if (
|
|
32991
|
+
const walk = await recordWalk(port, rootResolved, segments, rel);
|
|
32992
|
+
if (!walk.complete) {
|
|
32993
|
+
const prefix = await confirmPrefix(port, rootResolved, segments, rel, walk.recorded);
|
|
32994
|
+
if (prefix === "absent") return { state: "absent" };
|
|
32995
|
+
restarts = countRestart(restarts, rel);
|
|
32996
|
+
continue;
|
|
32997
|
+
}
|
|
32998
|
+
const recorded = walk.recorded;
|
|
32872
32999
|
const leaf = recorded[recorded.length - 1];
|
|
32873
33000
|
const verified = await confirmedWalk(port, rootResolved, segments, rel, recorded, null);
|
|
32874
33001
|
const verdict = verified === "continue" ? await confirmedWalk(port, rootResolved, segments, rel, recorded, leaf) : verified;
|
|
@@ -32878,45 +33005,56 @@ async function probeExact(port, root, rel) {
|
|
|
32878
33005
|
}
|
|
32879
33006
|
}
|
|
32880
33007
|
var SETTLED_AFTER_NS = 3000000000n;
|
|
33008
|
+
function witnessCanDetectChange(witness) {
|
|
33009
|
+
return witness.mtimeNs > 0n && witness.ctimeNs > 0n;
|
|
33010
|
+
}
|
|
32881
33011
|
function witnessKey(witness) {
|
|
32882
33012
|
return `${witness.dev}:${witness.ino}:${witness.mtimeNs}:${witness.ctimeNs}`;
|
|
32883
33013
|
}
|
|
32884
|
-
function
|
|
32885
|
-
|
|
32886
|
-
const
|
|
32887
|
-
|
|
32888
|
-
const
|
|
32889
|
-
|
|
33014
|
+
function witnessedListings(port, kept, now, capacity = Infinity) {
|
|
33015
|
+
let reused = false;
|
|
33016
|
+
const portWitness = port.directoryWitness?.bind(port);
|
|
33017
|
+
if (portWitness === void 0) return { source: freshListings(port), reused: () => false };
|
|
33018
|
+
const witnessOf = async (dir) => {
|
|
33019
|
+
const witness = await portWitness(dir);
|
|
33020
|
+
return witness !== null && witnessCanDetectChange(witness) ? witness : null;
|
|
33021
|
+
};
|
|
32890
33022
|
const settled = (witness) => {
|
|
32891
33023
|
const nowNs = BigInt(Math.floor(now())) * 1000000n;
|
|
32892
33024
|
const latest = witness.mtimeNs > witness.ctimeNs ? witness.mtimeNs : witness.ctimeNs;
|
|
32893
33025
|
return nowNs - latest >= SETTLED_AFTER_NS;
|
|
32894
33026
|
};
|
|
32895
|
-
const
|
|
32896
|
-
|
|
32897
|
-
if (
|
|
32898
|
-
|
|
32899
|
-
|
|
32900
|
-
|
|
32901
|
-
|
|
32902
|
-
|
|
32903
|
-
|
|
32904
|
-
|
|
32905
|
-
|
|
32906
|
-
|
|
32907
|
-
|
|
32908
|
-
|
|
32909
|
-
|
|
32910
|
-
|
|
32911
|
-
|
|
32912
|
-
|
|
32913
|
-
|
|
32914
|
-
|
|
32915
|
-
|
|
32916
|
-
return names;
|
|
32917
|
-
};
|
|
32918
|
-
return { source, reused: () => reused };
|
|
33027
|
+
const source = async (dir) => {
|
|
33028
|
+
const keep = kept.get(dir);
|
|
33029
|
+
if (keep !== void 0) {
|
|
33030
|
+
const current = await witnessOf(dir);
|
|
33031
|
+
if (current !== null && witnessKey(current) === keep.witness) {
|
|
33032
|
+
reused = true;
|
|
33033
|
+
return listingView(keep.names);
|
|
33034
|
+
}
|
|
33035
|
+
kept.delete(dir);
|
|
33036
|
+
}
|
|
33037
|
+
const before = await witnessOf(dir);
|
|
33038
|
+
const listing = await port.entries(dir);
|
|
33039
|
+
if (listing === null) return null;
|
|
33040
|
+
const names = new Set(listing.map((entry) => entry.name));
|
|
33041
|
+
const after = await witnessOf(dir);
|
|
33042
|
+
if (before !== null && after !== null && witnessKey(before) === witnessKey(after) && settled(after)) {
|
|
33043
|
+
kept.delete(dir);
|
|
33044
|
+
kept.set(dir, { witness: witnessKey(after), names });
|
|
33045
|
+
while (kept.size > capacity) kept.delete(kept.keys().next().value);
|
|
33046
|
+
}
|
|
33047
|
+
return listingView(names);
|
|
32919
33048
|
};
|
|
33049
|
+
return { source, reused: () => reused };
|
|
33050
|
+
}
|
|
33051
|
+
var prefixListings = /* @__PURE__ */ new WeakMap();
|
|
33052
|
+
var PREFIX_LISTING_CAPACITY = 64;
|
|
33053
|
+
function openObservationPass(port, root, now = Date.now) {
|
|
33054
|
+
const rootResolved = path2.resolve(root);
|
|
33055
|
+
const kept = /* @__PURE__ */ new Map();
|
|
33056
|
+
const reusedVerdicts = [];
|
|
33057
|
+
const listingsFor = () => witnessedListings(port, kept, now);
|
|
32920
33058
|
return {
|
|
32921
33059
|
async observe(rel, read) {
|
|
32922
33060
|
const listings = listingsFor();
|
|
@@ -33019,7 +33157,7 @@ async function readWhole(port, target) {
|
|
|
33019
33157
|
await port.close(opened.handle);
|
|
33020
33158
|
}
|
|
33021
33159
|
}
|
|
33022
|
-
async function mkdirExact(port, parent, segment, isTail, rel) {
|
|
33160
|
+
async function mkdirExact(port, parent, segment, isTail, rel, index2 = -1) {
|
|
33023
33161
|
const outcome = await port.mkdir(path2.join(parent, segment));
|
|
33024
33162
|
if (outcome === "created") return;
|
|
33025
33163
|
const listing = await port.entries(parent);
|
|
@@ -33028,7 +33166,9 @@ async function mkdirExact(port, parent, segment, isTail, rel) {
|
|
|
33028
33166
|
throw new FilesystemSymlinkEntryError(rel, segment);
|
|
33029
33167
|
}
|
|
33030
33168
|
const verdict = classifyMkdir(outcome, listing, segment, isTail);
|
|
33031
|
-
if (verdict === "aliased")
|
|
33169
|
+
if (verdict === "aliased") {
|
|
33170
|
+
throw aliasRefusalFromListing(rel, index2, listing, false);
|
|
33171
|
+
}
|
|
33032
33172
|
if (verdict === "shape-mismatch") throw new FilesystemShapeMismatchError(rel, segment);
|
|
33033
33173
|
}
|
|
33034
33174
|
async function ensureExactDirectories(port, rootResolved, segments, existingDepth, rel) {
|
|
@@ -33048,8 +33188,9 @@ async function ensureExactDirectories(port, rootResolved, segments, existingDept
|
|
|
33048
33188
|
}
|
|
33049
33189
|
}
|
|
33050
33190
|
let parent = path2.join(rootResolved, ...segments.slice(0, existingDepth));
|
|
33051
|
-
for (
|
|
33052
|
-
|
|
33191
|
+
for (let index2 = existingDepth; index2 < segments.length - 1; index2++) {
|
|
33192
|
+
const segment = segments[index2];
|
|
33193
|
+
await mkdirExact(port, parent, segment, false, rel, index2);
|
|
33053
33194
|
parent = path2.join(parent, segment);
|
|
33054
33195
|
}
|
|
33055
33196
|
}
|
|
@@ -33067,7 +33208,7 @@ async function createExactLeaf(port, tmp, target, rel, leaf, rootResolved) {
|
|
|
33067
33208
|
}
|
|
33068
33209
|
const listing = await port.entries(path2.dirname(target)) ?? [];
|
|
33069
33210
|
if (hasExact(listing, leaf)) throw new ConcurrentReplacementError(rel, 1);
|
|
33070
|
-
throw
|
|
33211
|
+
throw aliasRefusalFromListing(rel, relSegments(rel).length - 1, listing, true);
|
|
33071
33212
|
}
|
|
33072
33213
|
async function mutateExact(port, root, rel, body) {
|
|
33073
33214
|
const segments = relSegments(rel);
|
|
@@ -33089,11 +33230,13 @@ async function mutateExact(port, root, rel, body) {
|
|
|
33089
33230
|
async replace(bytes) {
|
|
33090
33231
|
await ensureExactDirectories(port, rootResolved, segments, existingDepth, rel);
|
|
33091
33232
|
const dir = path2.dirname(target);
|
|
33092
|
-
const tmpName =
|
|
33233
|
+
const tmpName = identityTempName(leaf);
|
|
33093
33234
|
const tmp = path2.join(dir, tmpName);
|
|
33094
33235
|
await port.writeTemp(dir, tmpName, bytes);
|
|
33095
33236
|
try {
|
|
33096
33237
|
if (realized.state === "exact") {
|
|
33238
|
+
await port.copyMode?.(target, tmp).catch(() => {
|
|
33239
|
+
});
|
|
33097
33240
|
await replaceExistingLeaf(port, tmp, target, rel);
|
|
33098
33241
|
return;
|
|
33099
33242
|
}
|
|
@@ -33253,6 +33396,9 @@ function createNodeFilesystemIdentityPort(hostPolicy) {
|
|
|
33253
33396
|
async rename(from, to) {
|
|
33254
33397
|
await fs2.rename(from, to);
|
|
33255
33398
|
},
|
|
33399
|
+
async copyMode(from, to) {
|
|
33400
|
+
await fs2.chmod(to, (await fs2.lstat(from)).mode & 511);
|
|
33401
|
+
},
|
|
33256
33402
|
async unlink(target) {
|
|
33257
33403
|
await fs2.unlink(target);
|
|
33258
33404
|
},
|
|
@@ -33275,6 +33421,7 @@ function blobVersion(bytes) {
|
|
|
33275
33421
|
|
|
33276
33422
|
// ../core/src/backend.ts
|
|
33277
33423
|
var publicationContexts = /* @__PURE__ */ new WeakMap();
|
|
33424
|
+
var fileMutations = /* @__PURE__ */ new WeakMap();
|
|
33278
33425
|
function firstString(...vals) {
|
|
33279
33426
|
for (const v of vals) {
|
|
33280
33427
|
if (typeof v === "string" && v.trim() !== "") return v;
|
|
@@ -33391,6 +33538,7 @@ var FilesystemBackend = class _FilesystemBackend {
|
|
|
33391
33538
|
this.#root = path3.resolve(root);
|
|
33392
33539
|
this.#coordinator = createFilesystemMutationCoordinator(this.#port, this.#root, options.hostPolicy);
|
|
33393
33540
|
publicationContexts.set(this, { root: this.#root, port: this.#port });
|
|
33541
|
+
fileMutations.set(this, (rel, body) => this.#mutateExact(rel, body));
|
|
33394
33542
|
}
|
|
33395
33543
|
async read(id) {
|
|
33396
33544
|
return this.#readWithEdition(id, () => this.#readEdition());
|
|
@@ -33776,6 +33924,7 @@ var KIND_FIELD_TYPE_SET = new Set(KIND_FIELD_TYPES);
|
|
|
33776
33924
|
function isKindFieldType(value) {
|
|
33777
33925
|
return typeof value === "string" && KIND_FIELD_TYPE_SET.has(value);
|
|
33778
33926
|
}
|
|
33927
|
+
var UNDECLARED_FIELDS_POLICIES = ["warn", "error"];
|
|
33779
33928
|
function buildKindRegistry(docs, initialWarnings = [], options = {}) {
|
|
33780
33929
|
const accepted = /* @__PURE__ */ new Map();
|
|
33781
33930
|
const warnings = [...initialWarnings];
|
|
@@ -34370,6 +34519,19 @@ function parseConventionDoc(doc, options = {}) {
|
|
|
34370
34519
|
});
|
|
34371
34520
|
}
|
|
34372
34521
|
}
|
|
34522
|
+
let undeclaredFields;
|
|
34523
|
+
if (fm.undeclared_fields !== void 0) {
|
|
34524
|
+
if (UNDECLARED_FIELDS_POLICIES.includes(fm.undeclared_fields)) {
|
|
34525
|
+
undeclaredFields = fm.undeclared_fields;
|
|
34526
|
+
} else {
|
|
34527
|
+
warnings.push({
|
|
34528
|
+
code: "KIND_CONVENTION_BAD_SHAPE",
|
|
34529
|
+
message: `kind convention '${doc.id}' has an invalid 'undeclared_fields' (${describeShape(fm.undeclared_fields)}; expected warn or error); using warn.`,
|
|
34530
|
+
field: "undeclared_fields",
|
|
34531
|
+
severity: "warning"
|
|
34532
|
+
});
|
|
34533
|
+
}
|
|
34534
|
+
}
|
|
34373
34535
|
const kind2 = {
|
|
34374
34536
|
id: doc.id,
|
|
34375
34537
|
title,
|
|
@@ -34387,6 +34549,7 @@ function parseConventionDoc(doc, options = {}) {
|
|
|
34387
34549
|
if (browseCollapsed !== void 0) kind2.browseCollapsed = browseCollapsed;
|
|
34388
34550
|
if (order !== void 0) kind2.order = order;
|
|
34389
34551
|
if (claim !== void 0) kind2.claim = claim;
|
|
34552
|
+
if (undeclaredFields !== void 0) kind2.undeclaredFields = undeclaredFields;
|
|
34390
34553
|
if (Object.hasOwn(fm, "deprecated")) {
|
|
34391
34554
|
kind2.deprecated = parseKindDeprecation(fm.deprecated, kind2, RESERVED_KIND_FIELD_NAMES, claimCoordinates(options.okfVersion, kind2)?.fields ?? []);
|
|
34392
34555
|
warnings.push(...kind2.deprecated.problems ?? []);
|
|
@@ -34463,7 +34626,7 @@ async function loadKinds(bundle) {
|
|
|
34463
34626
|
const docs = await query2(bundle, { prefix: CONVENTIONS_PREFIX, type: CONVENTION_TYPE }, {
|
|
34464
34627
|
onSkip: ({ id, reason }) => warnings.push({
|
|
34465
34628
|
code: "KIND_CONVENTION_MALFORMED",
|
|
34466
|
-
message: `skipped kind convention '${id}' with unparseable frontmatter: ${reason}`,
|
|
34629
|
+
message: isPermissionDeniedSkip({ reason }) ? `skipped kind convention '${id}' that could not be read: ${reason}` : `skipped kind convention '${id}' with unparseable frontmatter: ${reason}`,
|
|
34467
34630
|
field: id,
|
|
34468
34631
|
severity: "warning"
|
|
34469
34632
|
})
|
|
@@ -44601,12 +44764,27 @@ var CAPABILITIES = [
|
|
|
44601
44764
|
];
|
|
44602
44765
|
async function authorizePublicationRoot(requested) {
|
|
44603
44766
|
const entry = await lstat(requested);
|
|
44604
|
-
if (entry.isSymbolicLink()
|
|
44767
|
+
if (entry.isSymbolicLink()) {
|
|
44768
|
+
const target = await realpath(requested).catch(() => void 0);
|
|
44769
|
+
if (target === void 0) {
|
|
44770
|
+
throw new PublicationError("INVALID_OBJECT_IDENTITY", `the filesystem publication root ${JSON.stringify(requested)} is a symlink that does not resolve to a directory`, { actual: requested });
|
|
44771
|
+
}
|
|
44772
|
+
throw new PublicationError(
|
|
44773
|
+
"INVALID_OBJECT_IDENTITY",
|
|
44774
|
+
`the filesystem publication root ${JSON.stringify(requested)} is a symlink to ${JSON.stringify(target)}; capture does not follow symlinks, so pass the resolved path (await fs.realpath(root))`,
|
|
44775
|
+
{ expected: target, actual: requested }
|
|
44776
|
+
);
|
|
44777
|
+
}
|
|
44778
|
+
if (!entry.isDirectory()) {
|
|
44605
44779
|
throw new PublicationError("INVALID_OBJECT_IDENTITY", "the filesystem publication root must be a real directory");
|
|
44606
44780
|
}
|
|
44607
44781
|
const canonical2 = await realpath(requested);
|
|
44608
44782
|
if (canonical2 !== requested) {
|
|
44609
|
-
throw new PublicationError(
|
|
44783
|
+
throw new PublicationError(
|
|
44784
|
+
"INVALID_OBJECT_IDENTITY",
|
|
44785
|
+
`the filesystem publication root ${JSON.stringify(requested)} is not in canonical form (it resolves to ${JSON.stringify(canonical2)}); capture does not follow symlinks or normalize paths, so pass the resolved path (await fs.realpath(root))`,
|
|
44786
|
+
{ expected: canonical2, actual: requested }
|
|
44787
|
+
);
|
|
44610
44788
|
}
|
|
44611
44789
|
const identity = await stat(requested);
|
|
44612
44790
|
return { requested, canonical: canonical2, dev: identity.dev, ino: identity.ino };
|