vde-open 0.1.1 → 0.1.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 +22 -2
- package/dist/cli.js +2 -2
- package/dist/daemon.js +2 -2
- package/dist/{main-CLzVgxSL.js → main-CqENdUsw.js} +655 -21
- package/dist/{render-yCayIbhf.js → render-BNZz0CjQ.js} +101 -9
- package/dist/{search-index-CNsfwB72.js → search-index-__laOomT.js} +40 -0
- package/dist/web/assets/index-CC0ZOdtr.css +2 -0
- package/dist/web/assets/index-DsIZiz-K.js +97 -0
- package/dist/web/index.html +2 -2
- package/dist/workers/parse-worker.js +1 -1
- package/dist/workers/search-worker.js +1 -1
- package/docs/agent-usage.md +11 -0
- package/package.json +1 -1
- package/skills/vde-open/SKILL.md +1 -1
- package/dist/web/assets/index-BYu5_12H.css +0 -2
- package/dist/web/assets/index-CLggJFeG.js +0 -95
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { r as __toESM, t as __commonJSMin } from "./rolldown-runtime-DC62tzP2.js";
|
|
2
|
-
import { $ as literal, A as successEnvelope, B as readParamsSchema, C as VdeError, D as ExitCode, E as isVdeError, F as htmlModeChangeParamsSchema, G as sha256Schema, H as reorderParamsSchema, I as htmlModeSchema, J as watchRuleSchema, K as sourceKindSchema, L as linkOpenParamsSchema, M as closeParamsSchema, N as documentFormatSchema, P as documentIdSchema, Q as boolean, R as listParamsSchema, S as validateAnswers, U as revisionSchema, V as renderGrantParamsSchema, W as searchParamsSchema, X as _enum, Y as LIMITS, Z as array, _ as feedbackWaitParamsSchema, a as uniqueTokens, at as canonicalJson, b as submissionSchema, c as feedbackAckParamsSchema, d as feedbackDraftParamsSchema, et as number, f as feedbackForgetParamsSchema, g as feedbackSubmitParamsSchema, h as feedbackStatusSchema, i as codePointLength, it as ZodError, j as assetRecordSchema, k as errorEnvelope, l as feedbackCancelParamsSchema, m as feedbackListParamsSchema, n as batchesOf, nt as strictObject, o as answersSchema, p as feedbackIdParamsSchema, q as sourceStateSchema, r as partsOf, rt as string, s as cancellationSchema, tt as record, u as feedbackCreateParamsSchema, v as questionnaireSchema, w as exitCodeForError, x as utf8Length, y as requestIdSchema, z as openParamsSchema } from "./search-index-
|
|
3
|
-
import { a as analyzeDocument, c as classifyReference, d as hasHiddenSegment, f as isValidLogicalPath, i as ParseLimitError, l as dirnameOfLogicalPath, n as scanReferences, o as assetTypeOf, p as roleAllowed, r as extractTitle, s as classifyLink, u as encodeLogicalPath } from "./render-
|
|
2
|
+
import { $ as literal, A as successEnvelope, B as readParamsSchema, C as VdeError, D as ExitCode, E as isVdeError, F as htmlModeChangeParamsSchema, G as sha256Schema, H as reorderParamsSchema, I as htmlModeSchema, J as watchRuleSchema, K as sourceKindSchema, L as linkOpenParamsSchema, M as closeParamsSchema, N as documentFormatSchema, P as documentIdSchema, Q as boolean, R as listParamsSchema, S as validateAnswers, U as revisionSchema, V as renderGrantParamsSchema, W as searchParamsSchema, X as _enum, Y as LIMITS, Z as array, _ as feedbackWaitParamsSchema, a as uniqueTokens, at as canonicalJson, b as submissionSchema, c as feedbackAckParamsSchema, d as feedbackDraftParamsSchema, et as number, f as feedbackForgetParamsSchema, g as feedbackSubmitParamsSchema, h as feedbackStatusSchema, i as codePointLength, it as ZodError, j as assetRecordSchema, k as errorEnvelope, l as feedbackCancelParamsSchema, m as feedbackListParamsSchema, n as batchesOf, nt as strictObject, o as answersSchema, p as feedbackIdParamsSchema, q as sourceStateSchema, r as partsOf, rt as string, s as cancellationSchema, tt as record, u as feedbackCreateParamsSchema, v as questionnaireSchema, w as exitCodeForError, x as utf8Length, y as requestIdSchema, z as openParamsSchema } from "./search-index-__laOomT.js";
|
|
3
|
+
import { a as analyzeDocument, c as classifyReference, d as hasHiddenSegment, f as isValidLogicalPath, i as ParseLimitError, l as dirnameOfLogicalPath, n as scanReferences, o as assetTypeOf, p as roleAllowed, r as extractTitle, s as classifyLink, u as encodeLogicalPath } from "./render-BNZz0CjQ.js";
|
|
4
4
|
import { n as measureHeap, t as collectGarbage } from "./heap-C3qHm7SE.js";
|
|
5
5
|
import { constants, existsSync, stat, unwatchFile, watch, watchFile } from "node:fs";
|
|
6
|
-
import { appendFile, chmod, link, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, rmdir, stat as stat$1, writeFile } from "node:fs/promises";
|
|
6
|
+
import { appendFile, chmod, link, lstat, mkdir, open, readFile, readdir, readlink, realpath, rename, rm, rmdir, stat as stat$1, writeFile } from "node:fs/promises";
|
|
7
7
|
import * as sp from "node:path";
|
|
8
|
-
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
|
|
8
|
+
import { basename, dirname, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
9
9
|
import { EventEmitter } from "node:events";
|
|
10
10
|
import { execFile } from "node:child_process";
|
|
11
11
|
import { promisify } from "node:util";
|
|
12
12
|
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
13
13
|
import * as nativeFs from "fs";
|
|
14
14
|
import { readdir as readdir$1, readdirSync, realpath as realpath$1, realpathSync, stat as stat$2, statSync } from "fs";
|
|
15
|
-
import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, normalize, posix, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
|
|
15
|
+
import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, normalize, posix as posix$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
17
17
|
import { createRequire } from "module";
|
|
18
18
|
import { fileURLToPath as fileURLToPath$1 } from "node:url";
|
|
@@ -154,7 +154,7 @@ function parseStrictJson(text) {
|
|
|
154
154
|
}
|
|
155
155
|
//#endregion
|
|
156
156
|
//#region package.json
|
|
157
|
-
var version = "0.1.
|
|
157
|
+
var version = "0.1.2";
|
|
158
158
|
//#endregion
|
|
159
159
|
//#region src/documents/cursor.ts
|
|
160
160
|
function invalidCursor(reason) {
|
|
@@ -186,7 +186,7 @@ function createCursorCodec(secret, now = Date.now) {
|
|
|
186
186
|
//#endregion
|
|
187
187
|
//#region ../../packages/document/src/profiles.ts
|
|
188
188
|
const MARKDOWN_PARSER_PROFILE = "markdown-tanstack-v1";
|
|
189
|
-
const HTML_STATIC_PARSER_PROFILE = "html-static-
|
|
189
|
+
const HTML_STATIC_PARSER_PROFILE = "html-static-v2";
|
|
190
190
|
//#endregion
|
|
191
191
|
//#region ../../packages/document/src/source-lines.ts
|
|
192
192
|
const LF = 10;
|
|
@@ -3152,8 +3152,8 @@ function buildFormat(cwd, root, absolute) {
|
|
|
3152
3152
|
};
|
|
3153
3153
|
return (p, isDir) => isDir && p !== "." ? p.slice(0, -1) : p;
|
|
3154
3154
|
}
|
|
3155
|
-
if (absolute) return (p) => posix.relative(cwd, p) || ".";
|
|
3156
|
-
return (p) => posix.relative(cwd, `${root}/${p}`) || ".";
|
|
3155
|
+
if (absolute) return (p) => posix$1.relative(cwd, p) || ".";
|
|
3156
|
+
return (p) => posix$1.relative(cwd, `${root}/${p}`) || ".";
|
|
3157
3157
|
}
|
|
3158
3158
|
function buildRelative(cwd, root) {
|
|
3159
3159
|
if (root.startsWith(`${cwd}/`)) {
|
|
@@ -3161,7 +3161,7 @@ function buildRelative(cwd, root) {
|
|
|
3161
3161
|
return (p) => `${prefix}/${p}`;
|
|
3162
3162
|
}
|
|
3163
3163
|
return (p) => {
|
|
3164
|
-
const result = posix.relative(cwd, `${root}/${p}`);
|
|
3164
|
+
const result = posix$1.relative(cwd, `${root}/${p}`);
|
|
3165
3165
|
return p[p.length - 1] === "/" && result !== "" ? `${result}/` : result || ".";
|
|
3166
3166
|
};
|
|
3167
3167
|
}
|
|
@@ -3216,7 +3216,7 @@ function normalizePattern(pattern, opts, props, isIgnore) {
|
|
|
3216
3216
|
if (pattern[pattern.length - 1] === "/") result = pattern.slice(0, -1);
|
|
3217
3217
|
if (result[result.length - 1] !== "*" && opts.expandDirectories) result += "/**";
|
|
3218
3218
|
const escapedCwd = escapePath(cwd);
|
|
3219
|
-
result = isAbsolute$1(result.replace(ESCAPING_BACKSLASHES, "")) ? posix.relative(escapedCwd, result) : posix.normalize(result);
|
|
3219
|
+
result = isAbsolute$1(result.replace(ESCAPING_BACKSLASHES, "")) ? posix$1.relative(escapedCwd, result) : posix$1.normalize(result);
|
|
3220
3220
|
const parentDir = (_PARENT_DIRECTORY$exe = PARENT_DIRECTORY.exec(result)) === null || _PARENT_DIRECTORY$exe === void 0 ? void 0 : _PARENT_DIRECTORY$exe[0];
|
|
3221
3221
|
const parts = splitPattern(result);
|
|
3222
3222
|
if (parentDir) {
|
|
@@ -3227,7 +3227,7 @@ function normalizePattern(pattern, opts, props, isIgnore) {
|
|
|
3227
3227
|
result = result.slice(0, (n - i - 1) * 3) + result.slice((n - i) * 3 + parts[i + n].length + 1) || ".";
|
|
3228
3228
|
i++;
|
|
3229
3229
|
}
|
|
3230
|
-
const potentialRoot = posix.join(cwd, parentDir.slice(i * 3));
|
|
3230
|
+
const potentialRoot = posix$1.join(cwd, parentDir.slice(i * 3));
|
|
3231
3231
|
if (potentialRoot[0] !== "." && props.root.length > potentialRoot.length) {
|
|
3232
3232
|
props.root = ensureNonDriveRelativePath(potentialRoot);
|
|
3233
3233
|
props.depthOffset = -n + i;
|
|
@@ -3249,7 +3249,7 @@ function normalizePattern(pattern, opts, props, isIgnore) {
|
|
|
3249
3249
|
}
|
|
3250
3250
|
props.depthOffset = newCommonPath.length;
|
|
3251
3251
|
props.commonPath = newCommonPath;
|
|
3252
|
-
props.root = ensureNonDriveRelativePath(newCommonPath.length > 0 ? posix.join(cwd, ...newCommonPath) : cwd);
|
|
3252
|
+
props.root = ensureNonDriveRelativePath(newCommonPath.length > 0 ? posix$1.join(cwd, ...newCommonPath) : cwd);
|
|
3253
3253
|
}
|
|
3254
3254
|
return result;
|
|
3255
3255
|
}
|
|
@@ -3515,6 +3515,550 @@ function isExcludedDirectoryName(name) {
|
|
|
3515
3515
|
return isHidden(name) || EXCLUDED_DIRECTORIES.has(name);
|
|
3516
3516
|
}
|
|
3517
3517
|
//#endregion
|
|
3518
|
+
//#region src/documents/repository.ts
|
|
3519
|
+
const METADATA_LIMIT = 4096;
|
|
3520
|
+
function kindOf(stats) {
|
|
3521
|
+
if (stats.isSymbolicLink()) return "symlink";
|
|
3522
|
+
if (stats.isFile()) return "file";
|
|
3523
|
+
if (stats.isDirectory()) return "directory";
|
|
3524
|
+
return "other";
|
|
3525
|
+
}
|
|
3526
|
+
function notRegularFile(path) {
|
|
3527
|
+
return Object.assign(/* @__PURE__ */ new Error(`${path} is not a regular file.`), { code: "EFTYPE" });
|
|
3528
|
+
}
|
|
3529
|
+
const nodeRepositoryFs = {
|
|
3530
|
+
lstat: async (path) => kindOf(await lstat(path)),
|
|
3531
|
+
stat: async (path) => kindOf(await stat$1(path)),
|
|
3532
|
+
readlink: (path) => readlink(path),
|
|
3533
|
+
realpath: (path) => realpath(path),
|
|
3534
|
+
readSmall: async (path, limit) => {
|
|
3535
|
+
const before = await lstat(path);
|
|
3536
|
+
if (!before.isFile()) throw notRegularFile(path);
|
|
3537
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW);
|
|
3538
|
+
try {
|
|
3539
|
+
const opened = await handle.stat();
|
|
3540
|
+
if (!opened.isFile() || opened.ino !== before.ino || opened.dev !== before.dev) throw notRegularFile(path);
|
|
3541
|
+
return await readBounded(handle, limit);
|
|
3542
|
+
} finally {
|
|
3543
|
+
await handle.close();
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
};
|
|
3547
|
+
function createLimiter(limit) {
|
|
3548
|
+
let active = 0;
|
|
3549
|
+
const waiting = [];
|
|
3550
|
+
return {
|
|
3551
|
+
async run(work) {
|
|
3552
|
+
if (active < limit) active += 1;
|
|
3553
|
+
else await new Promise((resolveTurn) => waiting.push(resolveTurn));
|
|
3554
|
+
try {
|
|
3555
|
+
return await work();
|
|
3556
|
+
} finally {
|
|
3557
|
+
const next = waiting.shift();
|
|
3558
|
+
if (next) next();
|
|
3559
|
+
else active -= 1;
|
|
3560
|
+
}
|
|
3561
|
+
},
|
|
3562
|
+
get active() {
|
|
3563
|
+
return active;
|
|
3564
|
+
}
|
|
3565
|
+
};
|
|
3566
|
+
}
|
|
3567
|
+
function limitRepositoryFs(fs, limiter) {
|
|
3568
|
+
return {
|
|
3569
|
+
lstat: (path) => limiter.run(() => fs.lstat(path)),
|
|
3570
|
+
stat: (path) => limiter.run(() => fs.stat(path)),
|
|
3571
|
+
readlink: (path) => limiter.run(() => fs.readlink(path)),
|
|
3572
|
+
realpath: (path) => limiter.run(() => fs.realpath(path)),
|
|
3573
|
+
readSmall: (path, size) => limiter.run(() => fs.readSmall(path, size))
|
|
3574
|
+
};
|
|
3575
|
+
}
|
|
3576
|
+
var Unresolved = class extends Error {
|
|
3577
|
+
reason;
|
|
3578
|
+
constructor(reason) {
|
|
3579
|
+
super(reason);
|
|
3580
|
+
this.reason = reason;
|
|
3581
|
+
}
|
|
3582
|
+
};
|
|
3583
|
+
function failureOf(error, invalid) {
|
|
3584
|
+
if (error instanceof Unresolved) return error;
|
|
3585
|
+
const code = error?.code;
|
|
3586
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP" || code === "EFTYPE") return new Unresolved(invalid);
|
|
3587
|
+
return new Unresolved("unreadable");
|
|
3588
|
+
}
|
|
3589
|
+
const HEX_OBJECT_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
3590
|
+
const REFTABLE_HEAD = "refs/heads/.invalid";
|
|
3591
|
+
function isValidRefName(ref) {
|
|
3592
|
+
if (ref === "@" || ref.endsWith("/") || ref.endsWith(".")) return false;
|
|
3593
|
+
if (ref.includes("..") || ref.includes("@{") || ref.includes("//")) return false;
|
|
3594
|
+
if (/[\u0000- \u007f~^:?*[\\]/.test(ref)) return false;
|
|
3595
|
+
return ref.split("/").every((part) => part !== "" && !part.startsWith(".") && !part.endsWith(".lock"));
|
|
3596
|
+
}
|
|
3597
|
+
const utf8 = new TextDecoder("utf-8", { fatal: true });
|
|
3598
|
+
function parseHead(bytes) {
|
|
3599
|
+
const content = bytes.toString("latin1").replace(/\s+$/, "");
|
|
3600
|
+
if (HEX_OBJECT_ID.test(content)) return null;
|
|
3601
|
+
if (!content.startsWith("ref:")) throw new Unresolved("invalid-git-dir");
|
|
3602
|
+
const ref = content.slice(4).replace(/^\s+/, "");
|
|
3603
|
+
if (ref === REFTABLE_HEAD) return null;
|
|
3604
|
+
if (!ref.startsWith("refs/") || !isValidRefName(ref)) throw new Unresolved("invalid-git-dir");
|
|
3605
|
+
if (!ref.startsWith("refs/heads/")) return null;
|
|
3606
|
+
try {
|
|
3607
|
+
return utf8.decode(Buffer.from(ref.slice(11), "latin1"));
|
|
3608
|
+
} catch {
|
|
3609
|
+
return null;
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
function parseLinkFile(bytes, prefix, invalid) {
|
|
3613
|
+
let content;
|
|
3614
|
+
try {
|
|
3615
|
+
content = utf8.decode(bytes);
|
|
3616
|
+
} catch {
|
|
3617
|
+
throw new Unresolved(invalid);
|
|
3618
|
+
}
|
|
3619
|
+
content = content.replace(/\r?\n$/, "");
|
|
3620
|
+
if (!content.startsWith(prefix)) throw new Unresolved(invalid);
|
|
3621
|
+
const path = content.slice(prefix.length);
|
|
3622
|
+
if (path === "" || path.includes("\n") || path.includes("\0")) throw new Unresolved(invalid);
|
|
3623
|
+
return path;
|
|
3624
|
+
}
|
|
3625
|
+
function createRepositoryDetector(options = {}) {
|
|
3626
|
+
const fs = options.fs ?? nodeRepositoryFs;
|
|
3627
|
+
const path = options.platform === "win32" ? win32 : posix;
|
|
3628
|
+
const isWindows = options.platform === "win32";
|
|
3629
|
+
const probes = /* @__PURE__ */ new Map();
|
|
3630
|
+
const checkouts = /* @__PURE__ */ new Map();
|
|
3631
|
+
const segmentsOf = (absolute) => absolute.split(path.sep).filter((part) => part !== "");
|
|
3632
|
+
const relativeSegments = (from, to) => segmentsOf(path.relative(from, to));
|
|
3633
|
+
const permits = (documentRoot, target) => {
|
|
3634
|
+
if (!isWindows) return true;
|
|
3635
|
+
const root = path.parse(target).root;
|
|
3636
|
+
if (/^[A-Za-z]:[\\/]$/.test(root)) return true;
|
|
3637
|
+
return root.toLowerCase() === documentRoot.toLowerCase();
|
|
3638
|
+
};
|
|
3639
|
+
const allowed = (documentRoot, target) => {
|
|
3640
|
+
if (!permits(documentRoot, target)) throw new Unresolved("blocked-path");
|
|
3641
|
+
return target;
|
|
3642
|
+
};
|
|
3643
|
+
const probe = (directory) => {
|
|
3644
|
+
let pending = probes.get(directory);
|
|
3645
|
+
if (!pending) {
|
|
3646
|
+
pending = fs.lstat(path.join(directory, ".git")).then((kind) => kind, (error) => error.code === "ENOENT" || error.code === "ENOTDIR" ? "absent" : "error");
|
|
3647
|
+
probes.set(directory, pending);
|
|
3648
|
+
}
|
|
3649
|
+
return pending;
|
|
3650
|
+
};
|
|
3651
|
+
const readMetadata = async (file, invalid) => {
|
|
3652
|
+
let bytes;
|
|
3653
|
+
try {
|
|
3654
|
+
bytes = await fs.readSmall(file, METADATA_LIMIT);
|
|
3655
|
+
} catch (error) {
|
|
3656
|
+
throw failureOf(error, invalid);
|
|
3657
|
+
}
|
|
3658
|
+
if (bytes === null) throw new Unresolved("limit-exceeded");
|
|
3659
|
+
return bytes;
|
|
3660
|
+
};
|
|
3661
|
+
const realpathOf = async (target, invalid) => {
|
|
3662
|
+
try {
|
|
3663
|
+
return await fs.realpath(target);
|
|
3664
|
+
} catch (error) {
|
|
3665
|
+
throw failureOf(error, invalid);
|
|
3666
|
+
}
|
|
3667
|
+
};
|
|
3668
|
+
const expectDirectory = async (target) => {
|
|
3669
|
+
let kind;
|
|
3670
|
+
try {
|
|
3671
|
+
kind = await fs.stat(target);
|
|
3672
|
+
} catch (error) {
|
|
3673
|
+
throw failureOf(error, "invalid-git-dir");
|
|
3674
|
+
}
|
|
3675
|
+
if (kind !== "directory") throw new Unresolved("invalid-git-dir");
|
|
3676
|
+
};
|
|
3677
|
+
const validated = /* @__PURE__ */ new Map();
|
|
3678
|
+
const validateGitDir = (gitDir) => {
|
|
3679
|
+
let pending = validated.get(gitDir);
|
|
3680
|
+
if (!pending) {
|
|
3681
|
+
pending = (async () => {
|
|
3682
|
+
await expectDirectory(gitDir);
|
|
3683
|
+
const branch = parseHead(await readMetadata(path.join(gitDir, "HEAD"), "invalid-git-dir"));
|
|
3684
|
+
await expectDirectory(path.join(gitDir, "objects"));
|
|
3685
|
+
await expectDirectory(path.join(gitDir, "refs"));
|
|
3686
|
+
return branch;
|
|
3687
|
+
})();
|
|
3688
|
+
validated.set(gitDir, pending);
|
|
3689
|
+
}
|
|
3690
|
+
return pending;
|
|
3691
|
+
};
|
|
3692
|
+
const repositoryNameSegments = (commonDir) => {
|
|
3693
|
+
const segments = segmentsOf(commonDir);
|
|
3694
|
+
const last = segments.at(-1) ?? "";
|
|
3695
|
+
if (last === ".git") return segments.slice(0, -1);
|
|
3696
|
+
if (last.length > 4 && last.endsWith(".git")) return [...segments.slice(0, -1), last.slice(0, -4)];
|
|
3697
|
+
return segments;
|
|
3698
|
+
};
|
|
3699
|
+
const standalone = (checkoutDir) => ({
|
|
3700
|
+
state: "resolved",
|
|
3701
|
+
id: checkoutDir,
|
|
3702
|
+
nameSegments: segmentsOf(checkoutDir),
|
|
3703
|
+
checkout: {
|
|
3704
|
+
id: checkoutDir,
|
|
3705
|
+
kind: "main"
|
|
3706
|
+
}
|
|
3707
|
+
});
|
|
3708
|
+
const commonDirOf = async (gitDir, documentRoot) => {
|
|
3709
|
+
try {
|
|
3710
|
+
await fs.lstat(path.join(gitDir, "commondir"));
|
|
3711
|
+
} catch (error) {
|
|
3712
|
+
const code = error.code;
|
|
3713
|
+
if (code === "ENOENT" || code === "ENOTDIR") return null;
|
|
3714
|
+
throw failureOf(error, "invalid-git-file");
|
|
3715
|
+
}
|
|
3716
|
+
const written = parseLinkFile(await readMetadata(path.join(gitDir, "commondir"), "invalid-git-file"), "", "invalid-git-file");
|
|
3717
|
+
return realpathOf(allowed(documentRoot, path.resolve(gitDir, written)), "invalid-git-dir");
|
|
3718
|
+
};
|
|
3719
|
+
const resolveGitFile = async (checkoutDir, gitFile, documentRoot) => {
|
|
3720
|
+
const written = parseLinkFile(await readMetadata(gitFile, "invalid-git-file"), "gitdir: ", "invalid-git-file");
|
|
3721
|
+
const gitDir = await realpathOf(allowed(documentRoot, path.resolve(checkoutDir, written)), "invalid-git-file");
|
|
3722
|
+
const commonDir = await commonDirOf(gitDir, documentRoot);
|
|
3723
|
+
if (commonDir === null) {
|
|
3724
|
+
await validateGitDir(gitDir);
|
|
3725
|
+
return standalone(checkoutDir);
|
|
3726
|
+
}
|
|
3727
|
+
await validateGitDir(commonDir);
|
|
3728
|
+
if (path.dirname(gitDir) !== path.join(commonDir, "worktrees")) throw new Unresolved("link-mismatch");
|
|
3729
|
+
const branch = parseHead(await readMetadata(path.join(gitDir, "HEAD"), "invalid-git-dir"));
|
|
3730
|
+
const backWritten = parseLinkFile(await readMetadata(path.join(gitDir, "gitdir"), "link-mismatch"), "", "link-mismatch");
|
|
3731
|
+
const back = allowed(documentRoot, path.resolve(gitDir, backWritten));
|
|
3732
|
+
if (path.basename(back) !== ".git") throw new Unresolved("link-mismatch");
|
|
3733
|
+
if (await realpathOf(path.dirname(back), "link-mismatch") !== checkoutDir) throw new Unresolved("link-mismatch");
|
|
3734
|
+
return {
|
|
3735
|
+
state: "resolved",
|
|
3736
|
+
id: commonDir,
|
|
3737
|
+
nameSegments: repositoryNameSegments(commonDir),
|
|
3738
|
+
checkout: {
|
|
3739
|
+
id: checkoutDir,
|
|
3740
|
+
kind: "linked",
|
|
3741
|
+
name: path.basename(gitDir),
|
|
3742
|
+
branch
|
|
3743
|
+
}
|
|
3744
|
+
};
|
|
3745
|
+
};
|
|
3746
|
+
const resolveGitLink = async (checkoutDir, gitLink, documentRoot) => {
|
|
3747
|
+
let target;
|
|
3748
|
+
try {
|
|
3749
|
+
target = allowed(documentRoot, path.resolve(checkoutDir, await fs.readlink(gitLink)));
|
|
3750
|
+
} catch (error) {
|
|
3751
|
+
throw failureOf(error, "invalid-git-dir");
|
|
3752
|
+
}
|
|
3753
|
+
let kind;
|
|
3754
|
+
try {
|
|
3755
|
+
kind = await fs.lstat(target);
|
|
3756
|
+
} catch (error) {
|
|
3757
|
+
throw failureOf(error, "invalid-git-dir");
|
|
3758
|
+
}
|
|
3759
|
+
if (kind === "directory") {
|
|
3760
|
+
await validateGitDir(target);
|
|
3761
|
+
return standalone(checkoutDir);
|
|
3762
|
+
}
|
|
3763
|
+
if (kind === "file") {
|
|
3764
|
+
const written = parseLinkFile(await readMetadata(target, "invalid-git-file"), "gitdir: ", "invalid-git-file");
|
|
3765
|
+
const gitDir = await realpathOf(allowed(documentRoot, path.resolve(checkoutDir, written)), "invalid-git-file");
|
|
3766
|
+
const common = await commonDirOf(gitDir, documentRoot);
|
|
3767
|
+
if (common === null) await validateGitDir(gitDir);
|
|
3768
|
+
else {
|
|
3769
|
+
await validateGitDir(common);
|
|
3770
|
+
parseHead(await readMetadata(path.join(gitDir, "HEAD"), "invalid-git-dir"));
|
|
3771
|
+
}
|
|
3772
|
+
return standalone(checkoutDir);
|
|
3773
|
+
}
|
|
3774
|
+
throw new Unresolved("invalid-git-dir");
|
|
3775
|
+
};
|
|
3776
|
+
const resolveCheckout = (checkoutDir, kind, documentRoot) => {
|
|
3777
|
+
let pending = checkouts.get(checkoutDir);
|
|
3778
|
+
if (!pending) {
|
|
3779
|
+
const gitEntry = path.join(checkoutDir, ".git");
|
|
3780
|
+
const work = async () => {
|
|
3781
|
+
if (kind === "directory") {
|
|
3782
|
+
await validateGitDir(gitEntry);
|
|
3783
|
+
return {
|
|
3784
|
+
state: "resolved",
|
|
3785
|
+
id: gitEntry,
|
|
3786
|
+
nameSegments: repositoryNameSegments(gitEntry),
|
|
3787
|
+
checkout: {
|
|
3788
|
+
id: checkoutDir,
|
|
3789
|
+
kind: "main"
|
|
3790
|
+
}
|
|
3791
|
+
};
|
|
3792
|
+
}
|
|
3793
|
+
if (kind === "file") return resolveGitFile(checkoutDir, gitEntry, documentRoot);
|
|
3794
|
+
if (kind === "symlink") return resolveGitLink(checkoutDir, gitEntry, documentRoot);
|
|
3795
|
+
throw new Unresolved("invalid-git-dir");
|
|
3796
|
+
};
|
|
3797
|
+
pending = work().catch((error) => ({
|
|
3798
|
+
state: "unresolved",
|
|
3799
|
+
reason: failureOf(error, "invalid-git-dir").reason
|
|
3800
|
+
}));
|
|
3801
|
+
checkouts.set(checkoutDir, pending);
|
|
3802
|
+
}
|
|
3803
|
+
return pending;
|
|
3804
|
+
};
|
|
3805
|
+
const detect = async (canonicalPath) => {
|
|
3806
|
+
const documentRoot = path.parse(canonicalPath).root;
|
|
3807
|
+
let directory = path.dirname(canonicalPath);
|
|
3808
|
+
let kind;
|
|
3809
|
+
for (;;) {
|
|
3810
|
+
const found = await probe(directory);
|
|
3811
|
+
if (found === "error") return {
|
|
3812
|
+
state: "unresolved",
|
|
3813
|
+
id: directory,
|
|
3814
|
+
nameSegments: segmentsOf(directory),
|
|
3815
|
+
pathInCheckout: relativeSegments(directory, canonicalPath),
|
|
3816
|
+
reason: "unreadable"
|
|
3817
|
+
};
|
|
3818
|
+
if (found !== "absent") {
|
|
3819
|
+
kind = found;
|
|
3820
|
+
break;
|
|
3821
|
+
}
|
|
3822
|
+
const parent = path.dirname(directory);
|
|
3823
|
+
if (parent === directory || directory === options.stopAt) return { state: "outside" };
|
|
3824
|
+
directory = parent;
|
|
3825
|
+
}
|
|
3826
|
+
const gitEntry = path.join(directory, ".git");
|
|
3827
|
+
if (kind === "directory" && canonicalPath.startsWith(gitEntry + path.sep)) {
|
|
3828
|
+
try {
|
|
3829
|
+
await validateGitDir(gitEntry);
|
|
3830
|
+
} catch (error) {
|
|
3831
|
+
return {
|
|
3832
|
+
state: "unresolved",
|
|
3833
|
+
id: directory,
|
|
3834
|
+
nameSegments: segmentsOf(directory),
|
|
3835
|
+
pathInCheckout: relativeSegments(directory, canonicalPath),
|
|
3836
|
+
reason: failureOf(error, "invalid-git-dir").reason
|
|
3837
|
+
};
|
|
3838
|
+
}
|
|
3839
|
+
return {
|
|
3840
|
+
state: "resolved",
|
|
3841
|
+
id: gitEntry,
|
|
3842
|
+
nameSegments: repositoryNameSegments(gitEntry),
|
|
3843
|
+
checkout: null,
|
|
3844
|
+
pathInCheckout: relativeSegments(directory, canonicalPath)
|
|
3845
|
+
};
|
|
3846
|
+
}
|
|
3847
|
+
const resolution = await resolveCheckout(directory, kind, documentRoot);
|
|
3848
|
+
const pathInCheckout = relativeSegments(directory, canonicalPath);
|
|
3849
|
+
if (resolution.state === "unresolved") return {
|
|
3850
|
+
state: "unresolved",
|
|
3851
|
+
id: directory,
|
|
3852
|
+
nameSegments: segmentsOf(directory),
|
|
3853
|
+
pathInCheckout,
|
|
3854
|
+
reason: resolution.reason ?? "invalid-git-dir"
|
|
3855
|
+
};
|
|
3856
|
+
return {
|
|
3857
|
+
state: "resolved",
|
|
3858
|
+
id: resolution.id,
|
|
3859
|
+
nameSegments: resolution.nameSegments,
|
|
3860
|
+
checkout: resolution.checkout,
|
|
3861
|
+
pathInCheckout
|
|
3862
|
+
};
|
|
3863
|
+
};
|
|
3864
|
+
return { detect };
|
|
3865
|
+
}
|
|
3866
|
+
function supersedes(current, sequence) {
|
|
3867
|
+
return current === void 0 || current.sequence < sequence || current.sequence === sequence && current.value?.state === "pending";
|
|
3868
|
+
}
|
|
3869
|
+
var RepositoryTracker = class {
|
|
3870
|
+
#fs;
|
|
3871
|
+
#platform;
|
|
3872
|
+
#limiter = createLimiter(2);
|
|
3873
|
+
#stopAt;
|
|
3874
|
+
#createDetector;
|
|
3875
|
+
waitMs;
|
|
3876
|
+
lateApplyMs;
|
|
3877
|
+
lateApplyMaxMs;
|
|
3878
|
+
#sequence = 0;
|
|
3879
|
+
#documents = /* @__PURE__ */ new Map();
|
|
3880
|
+
#checkouts = /* @__PURE__ */ new Map();
|
|
3881
|
+
#members = /* @__PURE__ */ new Map();
|
|
3882
|
+
constructor(options = {}) {
|
|
3883
|
+
this.#fs = limitRepositoryFs(options.fs ?? nodeRepositoryFs, this.#limiter);
|
|
3884
|
+
this.#platform = options.platform ?? (process.platform === "win32" ? "win32" : "posix");
|
|
3885
|
+
this.waitMs = options.waitMs ?? 2e3;
|
|
3886
|
+
this.lateApplyMs = options.lateApplyMs ?? 250;
|
|
3887
|
+
this.lateApplyMaxMs = options.lateApplyMaxMs ?? 1e3;
|
|
3888
|
+
this.#stopAt = options.stopAt;
|
|
3889
|
+
this.#createDetector = options.createDetector;
|
|
3890
|
+
}
|
|
3891
|
+
begin(paths) {
|
|
3892
|
+
this.#sequence += 1;
|
|
3893
|
+
const detector = this.#createDetector?.() ?? createRepositoryDetector({
|
|
3894
|
+
fs: this.#fs,
|
|
3895
|
+
platform: this.#platform,
|
|
3896
|
+
...this.#stopAt === void 0 ? {} : { stopAt: this.#stopAt }
|
|
3897
|
+
});
|
|
3898
|
+
const results = /* @__PURE__ */ new Map();
|
|
3899
|
+
const settled = /* @__PURE__ */ new Map();
|
|
3900
|
+
const work = [...new Set(paths)].map((path) => {
|
|
3901
|
+
const finished = detector.detect(path).then((detection) => {
|
|
3902
|
+
results.set(path, detection);
|
|
3903
|
+
}, () => {
|
|
3904
|
+
results.set(path, {
|
|
3905
|
+
state: "unresolved",
|
|
3906
|
+
id: path,
|
|
3907
|
+
nameSegments: [],
|
|
3908
|
+
pathInCheckout: [],
|
|
3909
|
+
reason: "unreadable"
|
|
3910
|
+
});
|
|
3911
|
+
});
|
|
3912
|
+
settled.set(path, finished);
|
|
3913
|
+
return finished;
|
|
3914
|
+
});
|
|
3915
|
+
return {
|
|
3916
|
+
sequence: this.#sequence,
|
|
3917
|
+
startedAt: Date.now(),
|
|
3918
|
+
results,
|
|
3919
|
+
done: Promise.all(work).then(() => void 0),
|
|
3920
|
+
settled: (path) => settled.get(path) ?? Promise.resolve()
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
async wait(batch) {
|
|
3924
|
+
const remaining = batch.startedAt + this.waitMs - Date.now();
|
|
3925
|
+
if (remaining <= 0) return;
|
|
3926
|
+
let timer;
|
|
3927
|
+
await Promise.race([batch.done, new Promise((resolveTimeout) => {
|
|
3928
|
+
timer = setTimeout(resolveTimeout, remaining);
|
|
3929
|
+
})]);
|
|
3930
|
+
clearTimeout(timer);
|
|
3931
|
+
}
|
|
3932
|
+
#compose(stored, checkouts = this.#checkouts) {
|
|
3933
|
+
if (stored === void 0) return { state: "pending" };
|
|
3934
|
+
if (stored.state === "outside") return null;
|
|
3935
|
+
if (stored.state !== "resolved") return stored;
|
|
3936
|
+
if (stored.checkoutId === null) {
|
|
3937
|
+
const { checkoutId: _none, ...rest } = stored;
|
|
3938
|
+
return {
|
|
3939
|
+
...rest,
|
|
3940
|
+
checkout: null
|
|
3941
|
+
};
|
|
3942
|
+
}
|
|
3943
|
+
const entry = checkouts.get(stored.checkoutId);
|
|
3944
|
+
if (entry === void 0) return { state: "pending" };
|
|
3945
|
+
return {
|
|
3946
|
+
state: "resolved",
|
|
3947
|
+
id: entry.repositoryId,
|
|
3948
|
+
nameSegments: entry.nameSegments,
|
|
3949
|
+
checkout: entry.info,
|
|
3950
|
+
pathInCheckout: stored.pathInCheckout
|
|
3951
|
+
};
|
|
3952
|
+
}
|
|
3953
|
+
view(documentId) {
|
|
3954
|
+
return this.#compose(this.#documents.get(documentId)?.value);
|
|
3955
|
+
}
|
|
3956
|
+
stage(batch, targets, options) {
|
|
3957
|
+
const documents = /* @__PURE__ */ new Map();
|
|
3958
|
+
const checkouts = /* @__PURE__ */ new Map();
|
|
3959
|
+
const unfinished = [];
|
|
3960
|
+
const appliesTo = (current) => supersedes(current, batch.sequence);
|
|
3961
|
+
for (const target of targets) {
|
|
3962
|
+
const current = this.#documents.get(target.documentId);
|
|
3963
|
+
const detection = batch.results.get(target.path);
|
|
3964
|
+
if (detection === void 0) {
|
|
3965
|
+
unfinished.push(target);
|
|
3966
|
+
if (options.pendingWhenUnfinished && current === void 0) documents.set(target.documentId, {
|
|
3967
|
+
sequence: batch.sequence,
|
|
3968
|
+
value: { state: "pending" }
|
|
3969
|
+
});
|
|
3970
|
+
continue;
|
|
3971
|
+
}
|
|
3972
|
+
if (!appliesTo(current)) continue;
|
|
3973
|
+
let value;
|
|
3974
|
+
if (detection.state === "resolved" && detection.checkout !== null) {
|
|
3975
|
+
const { checkout } = detection;
|
|
3976
|
+
value = {
|
|
3977
|
+
state: "resolved",
|
|
3978
|
+
checkoutId: checkout.id,
|
|
3979
|
+
pathInCheckout: detection.pathInCheckout
|
|
3980
|
+
};
|
|
3981
|
+
if (appliesTo(this.#checkouts.get(checkout.id))) checkouts.set(checkout.id, {
|
|
3982
|
+
sequence: batch.sequence,
|
|
3983
|
+
info: checkout,
|
|
3984
|
+
repositoryId: detection.id,
|
|
3985
|
+
nameSegments: detection.nameSegments
|
|
3986
|
+
});
|
|
3987
|
+
} else if (detection.state === "resolved") {
|
|
3988
|
+
const { checkout: _none, ...rest } = detection;
|
|
3989
|
+
value = {
|
|
3990
|
+
...rest,
|
|
3991
|
+
checkoutId: null
|
|
3992
|
+
};
|
|
3993
|
+
} else value = detection;
|
|
3994
|
+
documents.set(target.documentId, {
|
|
3995
|
+
sequence: batch.sequence,
|
|
3996
|
+
value
|
|
3997
|
+
});
|
|
3998
|
+
}
|
|
3999
|
+
const merged = new Map(this.#checkouts);
|
|
4000
|
+
for (const [id, entry] of checkouts) merged.set(id, entry);
|
|
4001
|
+
const affected = new Set(documents.keys());
|
|
4002
|
+
for (const id of checkouts.keys()) for (const member of this.#members.get(id) ?? []) affected.add(member);
|
|
4003
|
+
let changed = false;
|
|
4004
|
+
for (const documentId of affected) {
|
|
4005
|
+
const before = this.view(documentId);
|
|
4006
|
+
const after = this.#compose(documents.get(documentId)?.value ?? this.#documents.get(documentId)?.value, merged);
|
|
4007
|
+
if (canonicalJson(before) !== canonicalJson(after)) {
|
|
4008
|
+
changed = true;
|
|
4009
|
+
break;
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
return {
|
|
4013
|
+
changed,
|
|
4014
|
+
unfinished,
|
|
4015
|
+
documents,
|
|
4016
|
+
checkouts
|
|
4017
|
+
};
|
|
4018
|
+
}
|
|
4019
|
+
apply(staged) {
|
|
4020
|
+
for (const [id, entry] of staged.checkouts) if (supersedes(this.#checkouts.get(id), entry.sequence)) this.#checkouts.set(id, entry);
|
|
4021
|
+
for (const [documentId, entry] of staged.documents) {
|
|
4022
|
+
const current = this.#documents.get(documentId);
|
|
4023
|
+
if (entry.value.state === "pending" && current !== void 0) continue;
|
|
4024
|
+
if (supersedes(current, entry.sequence)) this.#setDocument(documentId, entry);
|
|
4025
|
+
}
|
|
4026
|
+
this.#dropUnused();
|
|
4027
|
+
}
|
|
4028
|
+
#setDocument(documentId, entry) {
|
|
4029
|
+
const before = this.#documents.get(documentId)?.value;
|
|
4030
|
+
if (before?.state === "resolved" && before.checkoutId !== null) this.#members.get(before.checkoutId)?.delete(documentId);
|
|
4031
|
+
this.#documents.set(documentId, entry);
|
|
4032
|
+
if (entry.value.state === "resolved" && entry.value.checkoutId !== null) {
|
|
4033
|
+
let members = this.#members.get(entry.value.checkoutId);
|
|
4034
|
+
if (!members) {
|
|
4035
|
+
members = /* @__PURE__ */ new Set();
|
|
4036
|
+
this.#members.set(entry.value.checkoutId, members);
|
|
4037
|
+
}
|
|
4038
|
+
members.add(documentId);
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
forget(documentIds) {
|
|
4042
|
+
for (const documentId of documentIds) {
|
|
4043
|
+
const before = this.#documents.get(documentId)?.value;
|
|
4044
|
+
if (before?.state === "resolved" && before.checkoutId !== null) this.#members.get(before.checkoutId)?.delete(documentId);
|
|
4045
|
+
this.#documents.delete(documentId);
|
|
4046
|
+
}
|
|
4047
|
+
this.#dropUnused();
|
|
4048
|
+
}
|
|
4049
|
+
#dropUnused() {
|
|
4050
|
+
for (const [id, members] of this.#members) if (members.size === 0) this.#members.delete(id);
|
|
4051
|
+
for (const id of this.#checkouts.keys()) if (!this.#members.has(id)) this.#checkouts.delete(id);
|
|
4052
|
+
}
|
|
4053
|
+
retainedCounts() {
|
|
4054
|
+
return {
|
|
4055
|
+
repositoryDocuments: this.#documents.size,
|
|
4056
|
+
repositoryCheckouts: this.#checkouts.size,
|
|
4057
|
+
repositoryOperations: this.#limiter.active
|
|
4058
|
+
};
|
|
4059
|
+
}
|
|
4060
|
+
};
|
|
4061
|
+
//#endregion
|
|
3518
4062
|
//#region src/documents/revision.ts
|
|
3519
4063
|
function sha256Hex(bytes) {
|
|
3520
4064
|
return createHash("sha256").update(bytes).digest("hex");
|
|
@@ -3544,7 +4088,7 @@ const MISSING_RETRY_INTERVAL_MS = 100;
|
|
|
3544
4088
|
function profileOf(format) {
|
|
3545
4089
|
return format === "markdown" ? MARKDOWN_PARSER_PROFILE : HTML_STATIC_PARSER_PROFILE;
|
|
3546
4090
|
}
|
|
3547
|
-
function toSummary(record, order, searchState = "excluded", pendingRequestIds = [], interactiveAllowed = false) {
|
|
4091
|
+
function toSummary(record, order, searchState = "excluded", pendingRequestIds = [], interactiveAllowed = false, repository = null) {
|
|
3548
4092
|
return {
|
|
3549
4093
|
documentId: record.documentId,
|
|
3550
4094
|
key: record.key,
|
|
@@ -3553,6 +4097,8 @@ function toSummary(record, order, searchState = "excluded", pendingRequestIds =
|
|
|
3553
4097
|
title: record.title,
|
|
3554
4098
|
displayPath: record.displayPath,
|
|
3555
4099
|
pathSegments: record.pathSegments,
|
|
4100
|
+
canonicalPath: record.sourceKind === "file" ? record.canonicalPath : null,
|
|
4101
|
+
repository,
|
|
3556
4102
|
revision: record.currentRevision,
|
|
3557
4103
|
sourceState: record.sourceState,
|
|
3558
4104
|
searchState,
|
|
@@ -3627,6 +4173,7 @@ var DocumentService = class {
|
|
|
3627
4173
|
#interactive = /* @__PURE__ */ new Map();
|
|
3628
4174
|
#interactiveGenerations = 0;
|
|
3629
4175
|
#linkConfirmations = /* @__PURE__ */ new Map();
|
|
4176
|
+
#repositories;
|
|
3630
4177
|
constructor(options) {
|
|
3631
4178
|
this.#store = options.store;
|
|
3632
4179
|
this.#cursors = options.cursors;
|
|
@@ -3636,6 +4183,68 @@ var DocumentService = class {
|
|
|
3636
4183
|
this.#readSource = options.readSource ?? readSourceFile;
|
|
3637
4184
|
this.#scan = options.scan ?? ((kind, text) => Promise.resolve(scanReferences(kind, text)));
|
|
3638
4185
|
this.#searchStateOf = options.searchStateOf ?? (() => "excluded");
|
|
4186
|
+
this.#repositories = new RepositoryTracker(options.repositories);
|
|
4187
|
+
}
|
|
4188
|
+
async initializeRepositories() {
|
|
4189
|
+
const state = this.#store.payload;
|
|
4190
|
+
const targets = this.#trackedFiles(state, state.openOrder);
|
|
4191
|
+
if (targets.length === 0) return;
|
|
4192
|
+
const batch = this.#repositories.begin(targets.map((target) => target.path));
|
|
4193
|
+
await this.#repositories.wait(batch);
|
|
4194
|
+
const staged = this.#repositories.stage(batch, targets, { pendingWhenUnfinished: true });
|
|
4195
|
+
this.#repositories.apply(staged);
|
|
4196
|
+
this.#applyWhenDone(batch, staged.unfinished);
|
|
4197
|
+
}
|
|
4198
|
+
#trackedFiles(state, documentIds) {
|
|
4199
|
+
const targets = [];
|
|
4200
|
+
for (const documentId of documentIds) {
|
|
4201
|
+
const record = state.documents[documentId];
|
|
4202
|
+
if (record?.isOpen && record.sourceKind === "file" && record.canonicalPath !== null) targets.push({
|
|
4203
|
+
documentId,
|
|
4204
|
+
path: record.canonicalPath,
|
|
4205
|
+
epoch: this.openEpoch(documentId)
|
|
4206
|
+
});
|
|
4207
|
+
}
|
|
4208
|
+
return targets;
|
|
4209
|
+
}
|
|
4210
|
+
async #applyBatch(batch, targets) {
|
|
4211
|
+
let staged = null;
|
|
4212
|
+
await this.#store.transaction((tx) => {
|
|
4213
|
+
const live = targets.filter((target) => tx.state.documents[target.documentId]?.isOpen === true && this.openEpoch(target.documentId) === target.epoch);
|
|
4214
|
+
const next = this.#repositories.stage(batch, live, { pendingWhenUnfinished: false });
|
|
4215
|
+
if (next.changed) tx.state.catalogVersion += 1;
|
|
4216
|
+
staged = next;
|
|
4217
|
+
});
|
|
4218
|
+
const applied = staged;
|
|
4219
|
+
if (applied === null) return [];
|
|
4220
|
+
this.#repositories.apply(applied);
|
|
4221
|
+
if (applied.changed) this.#emit({ type: "catalog-changed" });
|
|
4222
|
+
return applied.unfinished;
|
|
4223
|
+
}
|
|
4224
|
+
#applyWhenDone(batch, targets) {
|
|
4225
|
+
if (targets.length === 0) return;
|
|
4226
|
+
let ready = [];
|
|
4227
|
+
let timer = null;
|
|
4228
|
+
let delay = this.#repositories.lateApplyMs;
|
|
4229
|
+
const flush = () => {
|
|
4230
|
+
timer = null;
|
|
4231
|
+
const now = ready;
|
|
4232
|
+
ready = [];
|
|
4233
|
+
delay = Math.min(delay * 2, this.#repositories.lateApplyMaxMs);
|
|
4234
|
+
this.#applyBatch(batch, now).catch(() => void 0);
|
|
4235
|
+
};
|
|
4236
|
+
for (const target of targets) batch.settled(target.path).then(() => {
|
|
4237
|
+
ready.push(target);
|
|
4238
|
+
timer ??= setTimeout(flush, delay);
|
|
4239
|
+
});
|
|
4240
|
+
}
|
|
4241
|
+
async #redetect(documentIds) {
|
|
4242
|
+
const state = this.#store.payload;
|
|
4243
|
+
const targets = this.#trackedFiles(state, documentIds.filter((documentId) => state.documents[documentId]?.sourceState !== "missing"));
|
|
4244
|
+
if (targets.length === 0) return;
|
|
4245
|
+
const batch = this.#repositories.begin(targets.map((target) => target.path));
|
|
4246
|
+
await this.#repositories.wait(batch);
|
|
4247
|
+
this.#applyWhenDone(batch, await this.#applyBatch(batch, targets));
|
|
3639
4248
|
}
|
|
3640
4249
|
async #withPathLocks(paths, work) {
|
|
3641
4250
|
const releases = [];
|
|
@@ -3807,7 +4416,8 @@ var DocumentService = class {
|
|
|
3807
4416
|
pathQueues: this.#pathQueues.size,
|
|
3808
4417
|
openEpochs: this.#openEpochs.size,
|
|
3809
4418
|
interactive: this.#interactive.size,
|
|
3810
|
-
linkConfirmations: this.#linkConfirmations.size
|
|
4419
|
+
linkConfirmations: this.#linkConfirmations.size,
|
|
4420
|
+
...this.#repositories.retainedCounts()
|
|
3811
4421
|
};
|
|
3812
4422
|
}
|
|
3813
4423
|
async open(rawParams) {
|
|
@@ -3856,15 +4466,18 @@ var DocumentService = class {
|
|
|
3856
4466
|
const settings = await this.#assetSettings(params);
|
|
3857
4467
|
if (settings.explicit !== void 0 && resolved.unique.size !== 1) throw new VdeError("E_INVALID_ARGUMENT", "--asset can only be specified when opening a single document.", { documents: resolved.unique.size });
|
|
3858
4468
|
if (params.htmlMode !== void 0 && ![...resolved.unique.values()].some((entry) => entry.format === "html")) throw new VdeError("E_INVALID_ARGUMENT", "--html-mode applies to HTML documents.");
|
|
4469
|
+
const batch = this.#repositories.begin(resolved.unique.keys());
|
|
3859
4470
|
return this.#withPathLocks(resolved.unique.keys(), async () => {
|
|
3860
4471
|
const incoming = await this.#readCandidates(resolved, settings, warnings);
|
|
3861
4472
|
if (incoming.length === 0 && watchTargets.length === 0) throw new VdeError("E_PATH_NOT_FOUND", "No documents found.", { paths: params.paths });
|
|
3862
|
-
|
|
4473
|
+
await this.#repositories.wait(batch);
|
|
4474
|
+
return this.#commitOpen(params, incoming, watchTargets, settings, warnings, batch);
|
|
3863
4475
|
});
|
|
3864
4476
|
}
|
|
3865
|
-
async #commitOpen(params, incoming, watchTargets, settings, warnings) {
|
|
4477
|
+
async #commitOpen(params, incoming, watchTargets, settings, warnings, batch) {
|
|
3866
4478
|
if ((params.title !== void 0 || params.key !== void 0) && incoming.length !== 1) throw new VdeError("E_INVALID_ARGUMENT", "--title and --key can only be specified when opening a single document.", { documents: incoming.length });
|
|
3867
4479
|
const events = [];
|
|
4480
|
+
let repositories = null;
|
|
3868
4481
|
const result = await this.#store.transaction((tx) => {
|
|
3869
4482
|
const state = tx.state;
|
|
3870
4483
|
const timestamp = this.#now().toISOString();
|
|
@@ -3908,9 +4521,11 @@ var DocumentService = class {
|
|
|
3908
4521
|
rules.push(rule);
|
|
3909
4522
|
}
|
|
3910
4523
|
assertOpenLimits(state);
|
|
3911
|
-
if (
|
|
4524
|
+
if (batch !== void 0) repositories = this.#repositories.stage(batch, this.#trackedFiles(state, touched), { pendingWhenUnfinished: true });
|
|
4525
|
+
const repositoryChanged = repositories?.changed === true;
|
|
4526
|
+
if (created + updated > 0 || repositoryChanged) state.catalogVersion += 1;
|
|
3912
4527
|
if (state.activeDocumentId === null) state.activeDocumentId = state.openOrder[0] ?? null;
|
|
3913
|
-
if (openSetChanged) events.unshift({ type: "catalog-changed" });
|
|
4528
|
+
if (openSetChanged || repositoryChanged) events.unshift({ type: "catalog-changed" });
|
|
3914
4529
|
return {
|
|
3915
4530
|
data: {
|
|
3916
4531
|
documents: touched.map((documentId) => this.#summary(state, documentId)),
|
|
@@ -3923,6 +4538,11 @@ var DocumentService = class {
|
|
|
3923
4538
|
warnings
|
|
3924
4539
|
};
|
|
3925
4540
|
});
|
|
4541
|
+
const staged = repositories;
|
|
4542
|
+
if (staged !== null && batch !== void 0) {
|
|
4543
|
+
this.#repositories.apply(staged);
|
|
4544
|
+
this.#applyWhenDone(batch, staged.unfinished);
|
|
4545
|
+
}
|
|
3926
4546
|
for (const item of incoming) this.#record(item);
|
|
3927
4547
|
const permissionChanged = [];
|
|
3928
4548
|
result.data.documents.forEach((summary, index) => {
|
|
@@ -4185,7 +4805,7 @@ var DocumentService = class {
|
|
|
4185
4805
|
};
|
|
4186
4806
|
}
|
|
4187
4807
|
#summary(state, documentId) {
|
|
4188
|
-
return toSummary(state.documents[documentId], state.openOrder.indexOf(documentId), this.#searchStateOf(documentId), pendingRequestIdsOf(state, documentId), this.interactiveAllowed(documentId));
|
|
4808
|
+
return toSummary(state.documents[documentId], state.openOrder.indexOf(documentId), this.#searchStateOf(documentId), pendingRequestIdsOf(state, documentId), this.interactiveAllowed(documentId), state.documents[documentId]?.sourceKind === "file" ? this.#repositories.view(documentId) : null);
|
|
4189
4809
|
}
|
|
4190
4810
|
#findExisting(state, item, key) {
|
|
4191
4811
|
if (item.sourceKind === "file") return Object.values(state.documents).find((record) => record.sourceKind === "file" && record.canonicalPath === item.canonicalPath);
|
|
@@ -4520,6 +5140,7 @@ var DocumentService = class {
|
|
|
4520
5140
|
};
|
|
4521
5141
|
});
|
|
4522
5142
|
for (const documentId of result.data.closed) this.#openEpochs.set(documentId, this.openEpoch(documentId) + 1);
|
|
5143
|
+
this.#repositories.forget(result.data.closed);
|
|
4523
5144
|
if (changed) this.#emit({ type: "catalog-changed" });
|
|
4524
5145
|
for (const event of cancelled) this.#emit(event);
|
|
4525
5146
|
return result;
|
|
@@ -4597,6 +5218,7 @@ var DocumentService = class {
|
|
|
4597
5218
|
const targets = raw === void 0 ? this.#store.payload.openOrder : [this.#requireOpen(documentIdSchema.parse(raw)).documentId];
|
|
4598
5219
|
const changed = [];
|
|
4599
5220
|
for (const documentId of targets) if ((await this.refreshFromDisk(documentId)).changed) changed.push(documentId);
|
|
5221
|
+
await this.#redetect(targets);
|
|
4600
5222
|
const state = this.#store.payload;
|
|
4601
5223
|
return {
|
|
4602
5224
|
data: {
|
|
@@ -4732,6 +5354,7 @@ var DocumentService = class {
|
|
|
4732
5354
|
const before = this.#store.payload;
|
|
4733
5355
|
const rule = before.watchRules.find((candidate) => candidate.watchId === watchId);
|
|
4734
5356
|
if (!rule) return [];
|
|
5357
|
+
const batch = this.#repositories.begin(candidates.keys());
|
|
4735
5358
|
const isOpenPath = (state, path) => Object.values(state.documents).some((record) => record.isOpen && record.sourceKind === "file" && record.canonicalPath === path);
|
|
4736
5359
|
let totalBytes = openSourceBytes(before);
|
|
4737
5360
|
const discovered = [];
|
|
@@ -4771,7 +5394,9 @@ var DocumentService = class {
|
|
|
4771
5394
|
}
|
|
4772
5395
|
}
|
|
4773
5396
|
if (discovered.length === 0) return [];
|
|
5397
|
+
await this.#repositories.wait(batch);
|
|
4774
5398
|
const appliedItems = [];
|
|
5399
|
+
let repositories = null;
|
|
4775
5400
|
const registered = await this.#store.transaction((tx) => {
|
|
4776
5401
|
const state = tx.state;
|
|
4777
5402
|
const current = state.watchRules.find((candidate) => candidate.watchId === watchId);
|
|
@@ -4787,10 +5412,16 @@ var DocumentService = class {
|
|
|
4787
5412
|
}
|
|
4788
5413
|
if (added.length === 0) return [];
|
|
4789
5414
|
assertOpenLimits(state);
|
|
5415
|
+
repositories = this.#repositories.stage(batch, this.#trackedFiles(state, added), { pendingWhenUnfinished: true });
|
|
4790
5416
|
state.catalogVersion += 1;
|
|
4791
5417
|
if (state.activeDocumentId === null) state.activeDocumentId = state.openOrder[0] ?? null;
|
|
4792
5418
|
return added;
|
|
4793
5419
|
});
|
|
5420
|
+
const staged = repositories;
|
|
5421
|
+
if (staged !== null) {
|
|
5422
|
+
this.#repositories.apply(staged);
|
|
5423
|
+
this.#applyWhenDone(batch, staged.unfinished);
|
|
5424
|
+
}
|
|
4794
5425
|
for (const item of appliedItems) this.#record(item);
|
|
4795
5426
|
if (registered.length > 0) this.#emit({ type: "catalog-changed" });
|
|
4796
5427
|
return registered;
|
|
@@ -5871,7 +6502,8 @@ function createRenderService(options) {
|
|
|
5871
6502
|
role
|
|
5872
6503
|
})),
|
|
5873
6504
|
links: output.links,
|
|
5874
|
-
diagnostics: output.diagnostics
|
|
6505
|
+
diagnostics: output.diagnostics,
|
|
6506
|
+
headingTargets: output.headingTargets
|
|
5875
6507
|
};
|
|
5876
6508
|
snapshots.set(cacheKey, snapshot);
|
|
5877
6509
|
while (snapshots.size > SNAPSHOT_CACHE_SIZE) {
|
|
@@ -5967,6 +6599,7 @@ function createRenderService(options) {
|
|
|
5967
6599
|
target: null,
|
|
5968
6600
|
count: 1
|
|
5969
6601
|
}, ...snapshot.diagnostics] : snapshot.diagnostics,
|
|
6602
|
+
headingTargets: snapshot.headingTargets,
|
|
5970
6603
|
bridge: bridge === null ? null : {
|
|
5971
6604
|
instanceId: bridge.instanceId,
|
|
5972
6605
|
requestId: bridge.requestId
|
|
@@ -13198,6 +13831,7 @@ async function startDaemon(options) {
|
|
|
13198
13831
|
scan: (kind, text) => parser.scan(kind, text),
|
|
13199
13832
|
emit
|
|
13200
13833
|
});
|
|
13834
|
+
await documents.initializeRepositories();
|
|
13201
13835
|
feedback = new FeedbackService({
|
|
13202
13836
|
store: openedStore,
|
|
13203
13837
|
documents,
|