@imenam/mcp-documentor 1.0.88 → 1.0.90
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 +4 -2
- package/dist/src/core/blob-store.d.ts +5 -0
- package/dist/src/core/blob-store.js +49 -0
- package/dist/src/core/git-sync.js +12 -0
- package/dist/src/core/github-code-api.d.ts +5 -4
- package/dist/src/core/github-code-api.js +31 -9
- package/dist/src/index.js +2 -0
- package/gui/dist/assets/{arc-s4GL8QZ_.js → arc-VljQAUvA.js} +1 -1
- package/gui/dist/assets/{architectureDiagram-3BPJPVTR-CfDERg0r.js → architectureDiagram-3BPJPVTR-lbkjtvmN.js} +1 -1
- package/gui/dist/assets/{blockDiagram-GPEHLZMM-CjDoEG3-.js → blockDiagram-GPEHLZMM-G4A-rV4c.js} +1 -1
- package/gui/dist/assets/{c4Diagram-AAUBKEIU-9o7f2UhK.js → c4Diagram-AAUBKEIU-Dq3aMw8v.js} +1 -1
- package/gui/dist/assets/channel-DFvG2cZZ.js +1 -0
- package/gui/dist/assets/{chunk-2J33WTMH-B37ouRYO.js → chunk-2J33WTMH-VSzyIJHo.js} +1 -1
- package/gui/dist/assets/{chunk-4BX2VUAB-Dcq44Skv.js → chunk-4BX2VUAB-DER41q8w.js} +1 -1
- package/gui/dist/assets/{chunk-55IACEB6-CLyM-RQf.js → chunk-55IACEB6-BlOGGRk4.js} +1 -1
- package/gui/dist/assets/{chunk-727SXJPM-BjzEo2iz.js → chunk-727SXJPM-Dv3Ay2KV.js} +1 -1
- package/gui/dist/assets/{chunk-AQP2D5EJ-Co03OHPi.js → chunk-AQP2D5EJ-BycHHbwh.js} +1 -1
- package/gui/dist/assets/{chunk-FMBD7UC4-ClbF2rAp.js → chunk-FMBD7UC4-DBoCiqpY.js} +1 -1
- package/gui/dist/assets/{chunk-ND2GUHAM-CN5cTCWu.js → chunk-ND2GUHAM-DGKypjhb.js} +1 -1
- package/gui/dist/assets/{chunk-QZHKN3VN-Bg4-kEPK.js → chunk-QZHKN3VN-B8Gn_mBo.js} +1 -1
- package/gui/dist/assets/classDiagram-4FO5ZUOK-B8xEoAMI.js +1 -0
- package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-B8xEoAMI.js +1 -0
- package/gui/dist/assets/{cose-bilkent-S5V4N54A-6Dkp59bE.js → cose-bilkent-S5V4N54A-DyamTuYj.js} +1 -1
- package/gui/dist/assets/{dagre-BM42HDAG-2dD-ID2T.js → dagre-BM42HDAG-BKC5D2WN.js} +1 -1
- package/gui/dist/assets/{diagram-2AECGRRQ-DBE2f2Lb.js → diagram-2AECGRRQ-BV2lBpwH.js} +1 -1
- package/gui/dist/assets/{diagram-5GNKFQAL-CkKPHiH9.js → diagram-5GNKFQAL-DGwbAj4W.js} +1 -1
- package/gui/dist/assets/{diagram-KO2AKTUF-uWtGAF2N.js → diagram-KO2AKTUF-CZAde8Sg.js} +1 -1
- package/gui/dist/assets/{diagram-LMA3HP47-DyQaWg_0.js → diagram-LMA3HP47-fQfo0z7i.js} +1 -1
- package/gui/dist/assets/{diagram-OG6HWLK6-H_DfuB5t.js → diagram-OG6HWLK6-BlD3VAFM.js} +1 -1
- package/gui/dist/assets/{erDiagram-TEJ5UH35-Cj9K5EXA.js → erDiagram-TEJ5UH35-DPUEmQfe.js} +1 -1
- package/gui/dist/assets/{flowDiagram-I6XJVG4X-BYF24Jvt.js → flowDiagram-I6XJVG4X-p2HfkBjW.js} +1 -1
- package/gui/dist/assets/{ganttDiagram-6RSMTGT7-D93LjmhI.js → ganttDiagram-6RSMTGT7-CWfFoy4g.js} +1 -1
- package/gui/dist/assets/{gitGraphDiagram-PVQCEYII-EVTcusDb.js → gitGraphDiagram-PVQCEYII-DkTKzmes.js} +1 -1
- package/gui/dist/assets/index-BJ4WCWig.css +1 -0
- package/gui/dist/assets/{index-DvVxuv5N.js → index-C8LVeAsA.js} +181 -181
- package/gui/dist/assets/{infoDiagram-5YYISTIA-DeGbaexj.js → infoDiagram-5YYISTIA-CXHTqZoU.js} +1 -1
- package/gui/dist/assets/{ishikawaDiagram-YF4QCWOH-DNdk7sPJ.js → ishikawaDiagram-YF4QCWOH-DZ61Cc8a.js} +1 -1
- package/gui/dist/assets/{journeyDiagram-JHISSGLW-yLLfyn3W.js → journeyDiagram-JHISSGLW-qYtEmFuB.js} +1 -1
- package/gui/dist/assets/{kanban-definition-UN3LZRKU-D8yvVgr8.js → kanban-definition-UN3LZRKU-Bs-W313s.js} +1 -1
- package/gui/dist/assets/{linear-j3lplktA.js → linear-CU6J91M5.js} +1 -1
- package/gui/dist/assets/{mindmap-definition-RKZ34NQL-t9adVeMH.js → mindmap-definition-RKZ34NQL-CvBqAH4c.js} +1 -1
- package/gui/dist/assets/{pieDiagram-4H26LBE5-6Yrvo8h8.js → pieDiagram-4H26LBE5-BD2UcQce.js} +1 -1
- package/gui/dist/assets/{quadrantDiagram-W4KKPZXB-C_3Su6rX.js → quadrantDiagram-W4KKPZXB-DEuynlP9.js} +1 -1
- package/gui/dist/assets/{requirementDiagram-4Y6WPE33-B1qYSC11.js → requirementDiagram-4Y6WPE33-DPxfzOF3.js} +1 -1
- package/gui/dist/assets/{sankeyDiagram-5OEKKPKP-D3Ervn-u.js → sankeyDiagram-5OEKKPKP-C70_cePR.js} +1 -1
- package/gui/dist/assets/{sequenceDiagram-3UESZ5HK-CKgTeWBS.js → sequenceDiagram-3UESZ5HK-FDb_1B1W.js} +1 -1
- package/gui/dist/assets/{stateDiagram-AJRCARHV-D_PjEdFH.js → stateDiagram-AJRCARHV-BH1bq2it.js} +1 -1
- package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-BfpDu1Qi.js +1 -0
- package/gui/dist/assets/{timeline-definition-PNZ67QCA-HeWRiNHu.js → timeline-definition-PNZ67QCA-DNUx0JIp.js} +1 -1
- package/gui/dist/assets/{vennDiagram-CIIHVFJN-CREk7vzm.js → vennDiagram-CIIHVFJN-DEAfzbjb.js} +1 -1
- package/gui/dist/assets/{wardley-L42UT6IY-BWtRQ7ZH.js → wardley-L42UT6IY-_REPWPb-.js} +1 -1
- package/gui/dist/assets/{wardleyDiagram-YWT4CUSO-BvlqWWwp.js → wardleyDiagram-YWT4CUSO-CZFLXFE3.js} +1 -1
- package/gui/dist/assets/{xychartDiagram-2RQKCTM6-BCjv6pwc.js → xychartDiagram-2RQKCTM6-P2Nhif6M.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/gui/dist/assets/channel-BsKHRH5P.js +0 -1
- package/gui/dist/assets/classDiagram-4FO5ZUOK-DphzXyc8.js +0 -1
- package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-DphzXyc8.js +0 -1
- package/gui/dist/assets/index-CtPuh-0k.css +0 -1
- package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-KPbsissC.js +0 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Connect it to Cursor, Claude Desktop, or any MCP-compatible agent to let your AI
|
|
|
12
12
|
- **Track a branch, not a checkout** — point a project at a GitHub branch from its settings in the GUI and freshness is measured against *published* code: switching branches or editing without committing no longer makes docs look stale, and a server with no copy of the codebase (an MCP gateway) can still verify them
|
|
13
13
|
- **Semantic search** — find relevant docs by meaning (requires an OpenAI API key)
|
|
14
14
|
- **Multi-project support** — organize docs into separate projects; every MCP tool names the project it targets, and the GUI's project selector is a per-browser view preference
|
|
15
|
-
- **GUI** — built-in web interface, either behind the proxy (`PROXY_URL`) or served directly on a local port (`APP_PORT`); with neither, the GUI worker is not started
|
|
15
|
+
- **GUI** — built-in web interface, either behind the proxy (`PROXY_URL`) or served directly on a local port (`APP_PORT`); with neither, the GUI worker is not started. Navigation runs from an icon rail on the left: the current project's badge (click to switch projects or create one), then Documentation, Search (`Ctrl/⌘ + K`, current or all projects), Outdated, Recent, Export bundles and Logs, with Sync and Settings at the bottom. Each destination fills the panel next to the rail; the panel's hide button, or a second click on the active icon, folds it away. Drag the panel's right edge to resize it (double-click resets the width); the browser remembers the width. The details panel on the right of a document (`Ctrl/⌘ + \`) is open by default from 1280 px wide; showing or hiding it by hand is remembered across reloads until the window crosses that width. Badges show the outdated docs, pending commits and the day's failed GitHub calls
|
|
16
16
|
- **Proxy-aware** — integrates with the [proxy-setup](https://github.com/Imena/proxy-setup) for GUI exposure
|
|
17
17
|
- **Links & structure** — `check_links`, `get_doc_tree`, `move_doc`, and `git_diff_doc` for maintenance workflows
|
|
18
18
|
- **Image support** — upload images to a project's assets via `upload_image` (local file path) or directly from the GUI editor; images are stored per-project and rendered inline in the viewer and in PDF exports
|
|
@@ -312,7 +312,9 @@ Freshness is **never computed in the background** — only when something asks f
|
|
|
312
312
|
|
|
313
313
|
Within one such request, the branch head is resolved **once** and every tracked file is compared against that same commit — no mixed verdict if someone pushes mid-scan. The next request resolves it again, so a push is visible immediately: there is no staleness window to configure.
|
|
314
314
|
|
|
315
|
-
|
|
315
|
+
Resolving the branch head is a conditional request: while the branch has not moved, GitHub answers `304 Not Modified`, which does not count against the rate limit of an authenticated token.
|
|
316
|
+
|
|
317
|
+
Everything else is content-addressed: a commit's tree is cached by its sha for the life of the process, and a file's bytes by their blob sha on disk, in `<data dir>/blob-cache/` (machine-local, excluded from sync) — each version of a file is downloaded once per machine, across restarts. Each tracked file also records the blob sha it was snapshotted at, so an unchanged file is proven untouched without being downloaded at all. A full scan of an unchanged repository costs about two API calls — not one per file.
|
|
316
318
|
|
|
317
319
|
## Data Storage
|
|
318
320
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function setBlobStoreDir(dir: string): void;
|
|
2
|
+
/** A stored blob, or undefined when it is not on disk. An entry that does not hash to its sha is dropped. */
|
|
3
|
+
export declare function readStoredBlob(sha: string): Buffer | undefined;
|
|
4
|
+
/** Stores a blob. Written to a temporary file then renamed, so a reader never sees a partial one. */
|
|
5
|
+
export declare function storeBlob(sha: string, bytes: Buffer): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { createLogger } from "@imenam/mcp-gui-interface";
|
|
5
|
+
const logger = createLogger("BlobStore");
|
|
6
|
+
/**
|
|
7
|
+
* Git blobs downloaded from GitHub, kept on disk by sha. A blob's content is fixed by its sha, so
|
|
8
|
+
* an entry never goes stale: each version of a file is downloaded once per machine, whatever the
|
|
9
|
+
* number of restarts. Entries are checked against their sha when read.
|
|
10
|
+
*/
|
|
11
|
+
let storeDir;
|
|
12
|
+
export function setBlobStoreDir(dir) {
|
|
13
|
+
storeDir = dir;
|
|
14
|
+
}
|
|
15
|
+
function blobPath(sha) {
|
|
16
|
+
if (!storeDir || !/^[0-9a-f]{40}$/.test(sha))
|
|
17
|
+
return undefined;
|
|
18
|
+
return path.join(storeDir, sha.slice(0, 2), sha);
|
|
19
|
+
}
|
|
20
|
+
/** The git blob sha of some bytes. */
|
|
21
|
+
function gitBlobSha(bytes) {
|
|
22
|
+
return crypto.createHash("sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
|
|
23
|
+
}
|
|
24
|
+
/** A stored blob, or undefined when it is not on disk. An entry that does not hash to its sha is dropped. */
|
|
25
|
+
export function readStoredBlob(sha) {
|
|
26
|
+
const file = blobPath(sha);
|
|
27
|
+
if (!file || !fs.existsSync(file))
|
|
28
|
+
return undefined;
|
|
29
|
+
const bytes = fs.readFileSync(file);
|
|
30
|
+
if (gitBlobSha(bytes) === sha)
|
|
31
|
+
return bytes;
|
|
32
|
+
logger.warn(`Stored blob ${sha.slice(0, 7)} does not match its sha; removing it`);
|
|
33
|
+
fs.rmSync(file, { force: true });
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
/** Stores a blob. Written to a temporary file then renamed, so a reader never sees a partial one. */
|
|
37
|
+
export function storeBlob(sha, bytes) {
|
|
38
|
+
const file = blobPath(sha);
|
|
39
|
+
if (!file)
|
|
40
|
+
return;
|
|
41
|
+
if (gitBlobSha(bytes) !== sha) {
|
|
42
|
+
logger.warn(`Downloaded content of blob ${sha.slice(0, 7)} does not match its sha; not storing it`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
46
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
47
|
+
fs.writeFileSync(temp, bytes);
|
|
48
|
+
fs.renameSync(temp, file);
|
|
49
|
+
}
|
|
@@ -21,6 +21,7 @@ const GITIGNORE_CONTENT = [
|
|
|
21
21
|
"errors-tmp-files/",
|
|
22
22
|
"export-bundles.json",
|
|
23
23
|
"local-state.json",
|
|
24
|
+
"blob-cache/",
|
|
24
25
|
"",
|
|
25
26
|
].join("\n");
|
|
26
27
|
/**
|
|
@@ -143,10 +144,21 @@ export class SyncEngine {
|
|
|
143
144
|
async ensureRepo() {
|
|
144
145
|
const cfg = this.requireConfig();
|
|
145
146
|
const authed = this.authedUrl(cfg);
|
|
147
|
+
// Entries a newer server added are appended to an existing file, so machine-local state it
|
|
148
|
+
// introduces is never staged in a repo initialized before it.
|
|
146
149
|
const gitignorePath = path.join(this.dataDir, ".gitignore");
|
|
147
150
|
if (!fs.existsSync(gitignorePath)) {
|
|
148
151
|
fs.writeFileSync(gitignorePath, GITIGNORE_CONTENT, "utf-8");
|
|
149
152
|
}
|
|
153
|
+
else {
|
|
154
|
+
const current = fs.readFileSync(gitignorePath, "utf-8");
|
|
155
|
+
const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
|
|
156
|
+
const missing = GITIGNORE_CONTENT.split("\n").filter((line) => line && !line.startsWith("#") && !present.has(line));
|
|
157
|
+
if (missing.length > 0) {
|
|
158
|
+
const separator = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
159
|
+
fs.writeFileSync(gitignorePath, `${current}${separator}${missing.join("\n")}\n`, "utf-8");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
150
162
|
// Rewritten when it drifts, so an upgraded server updates the routing of
|
|
151
163
|
// repos that were initialized by an older version.
|
|
152
164
|
const attributesPath = path.join(this.dataDir, ".gitattributes");
|
|
@@ -59,8 +59,8 @@ export declare function searchRepositories(term: string, token: string | undefin
|
|
|
59
59
|
* - the commit a ref points at — the only time-varying one, so it is resolved once per
|
|
60
60
|
* operation and never carried across them (see `code-operation`);
|
|
61
61
|
* - that commit's tree (path → blob sha) — immutable, cached per commit;
|
|
62
|
-
* - a blob's bytes — immutable, cached per blob sha
|
|
63
|
-
*
|
|
62
|
+
* - a blob's bytes — immutable, cached per blob sha in memory and on disk (see `blob-store`),
|
|
63
|
+
* so a given version of a file is downloaded once per machine.
|
|
64
64
|
*
|
|
65
65
|
* One instance per (repo, ref) pair; instances are shared across projects by `code-source`.
|
|
66
66
|
*/
|
|
@@ -78,7 +78,8 @@ export declare class GitHubCodeApi {
|
|
|
78
78
|
* The commit the configured ref points at right now.
|
|
79
79
|
*
|
|
80
80
|
* Resolved afresh for each operation — so a push is visible to the very next call — but only
|
|
81
|
-
* once within it, however many files that operation ends up reading.
|
|
81
|
+
* once within it, however many files that operation ends up reading. The request is conditional:
|
|
82
|
+
* while the branch has not moved, GitHub answers 304 and the call costs no rate limit.
|
|
82
83
|
*/
|
|
83
84
|
resolveRef(): Promise<ResolvedRef>;
|
|
84
85
|
/**
|
|
@@ -102,7 +103,7 @@ export declare class GitHubCodeApi {
|
|
|
102
103
|
/** Blob sha for one path at one commit, or undefined if the file does not exist there. */
|
|
103
104
|
getBlobSha(commitSha: string, repoPath: string): Promise<string | undefined>;
|
|
104
105
|
/**
|
|
105
|
-
* A blob's bytes
|
|
106
|
+
* A blob's bytes, read from the blob store when this machine already downloaded them.
|
|
106
107
|
* `filePath` names the file in github.log.
|
|
107
108
|
*/
|
|
108
109
|
getBlob(blobSha: string, filePath?: string): Promise<Buffer>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createLogger } from "@imenam/mcp-gui-interface";
|
|
2
2
|
import { oncePerOperation } from "./code-operation.js";
|
|
3
3
|
import { recordGitHubCall, redactSecrets } from "./github-log.js";
|
|
4
|
+
import { readStoredBlob, storeBlob } from "./blob-store.js";
|
|
4
5
|
const logger = createLogger("GitHubCodeApi");
|
|
5
6
|
const API_BASE = "https://api.github.com";
|
|
6
7
|
export function formatRepoSpec(spec) {
|
|
@@ -40,6 +41,8 @@ export class GitHubApiError extends Error {
|
|
|
40
41
|
this.name = "GitHubApiError";
|
|
41
42
|
}
|
|
42
43
|
}
|
|
44
|
+
/** Last answer to each revalidated path, under the token it was read with. */
|
|
45
|
+
const revalidated = new Map();
|
|
43
46
|
function readRateLimit(response) {
|
|
44
47
|
const remaining = Number(response.headers.get("x-ratelimit-remaining"));
|
|
45
48
|
const limit = Number(response.headers.get("x-ratelimit-limit"));
|
|
@@ -61,6 +64,10 @@ async function githubRequest(apiPath, token, purpose) {
|
|
|
61
64
|
};
|
|
62
65
|
if (token)
|
|
63
66
|
headers.Authorization = `Bearer ${token}`;
|
|
67
|
+
const cacheKey = `${token ?? ""} ${apiPath}`;
|
|
68
|
+
const previous = purpose.revalidate ? revalidated.get(cacheKey) : undefined;
|
|
69
|
+
if (previous)
|
|
70
|
+
headers["If-None-Match"] = previous.etag;
|
|
64
71
|
const { label } = purpose;
|
|
65
72
|
const startedAt = Date.now();
|
|
66
73
|
const record = (status, ok, error, rateLimit) => recordGitHubCall({
|
|
@@ -85,6 +92,10 @@ async function githubRequest(apiPath, token, purpose) {
|
|
|
85
92
|
}
|
|
86
93
|
const rateLimit = readRateLimit(response);
|
|
87
94
|
const status = String(response.status);
|
|
95
|
+
if (response.status === 304 && previous) {
|
|
96
|
+
record(status, true, undefined, rateLimit);
|
|
97
|
+
return previous.body;
|
|
98
|
+
}
|
|
88
99
|
if (response.status === 404 || response.status === 422) {
|
|
89
100
|
const message = `not found: ${label} (repository, branch or path missing — or the token cannot see this repository)`;
|
|
90
101
|
record(status, false, message, rateLimit);
|
|
@@ -104,7 +115,11 @@ async function githubRequest(apiPath, token, purpose) {
|
|
|
104
115
|
throw new GitHubApiError(message, response.status);
|
|
105
116
|
}
|
|
106
117
|
record(status, true, undefined, rateLimit);
|
|
107
|
-
|
|
118
|
+
const body = await response.json();
|
|
119
|
+
const etag = response.headers.get("etag");
|
|
120
|
+
if (purpose.revalidate && etag)
|
|
121
|
+
revalidated.set(cacheKey, { etag, body });
|
|
122
|
+
return body;
|
|
108
123
|
}
|
|
109
124
|
/** Visibility and default branch of a repository. */
|
|
110
125
|
export async function getRepositoryInfo(owner, repo, token) {
|
|
@@ -137,7 +152,7 @@ export async function listBranches(owner, repo, token) {
|
|
|
137
152
|
}
|
|
138
153
|
/** The commit a branch points at, with its first message line. */
|
|
139
154
|
export async function getBranchHead(owner, repo, branch, token) {
|
|
140
|
-
const commit = await githubRequest(`/repos/${owner}/${repo}/commits/${encodeURIComponent(branch)}`, token, { label: `${owner}/${repo}@${branch}`, action: `Read the head commit of ${branch}`, repo: `${owner}/${repo}
|
|
155
|
+
const commit = await githubRequest(`/repos/${owner}/${repo}/commits/${encodeURIComponent(branch)}`, token, { label: `${owner}/${repo}@${branch}`, action: `Read the head commit of ${branch}`, repo: `${owner}/${repo}`, revalidate: true });
|
|
141
156
|
return {
|
|
142
157
|
sha: commit.sha,
|
|
143
158
|
message: String(commit.commit?.message ?? "").split("\n")[0],
|
|
@@ -176,8 +191,8 @@ export async function searchRepositories(term, token, limit) {
|
|
|
176
191
|
* - the commit a ref points at — the only time-varying one, so it is resolved once per
|
|
177
192
|
* operation and never carried across them (see `code-operation`);
|
|
178
193
|
* - that commit's tree (path → blob sha) — immutable, cached per commit;
|
|
179
|
-
* - a blob's bytes — immutable, cached per blob sha
|
|
180
|
-
*
|
|
194
|
+
* - a blob's bytes — immutable, cached per blob sha in memory and on disk (see `blob-store`),
|
|
195
|
+
* so a given version of a file is downloaded once per machine.
|
|
181
196
|
*
|
|
182
197
|
* One instance per (repo, ref) pair; instances are shared across projects by `code-source`.
|
|
183
198
|
*/
|
|
@@ -198,23 +213,25 @@ export class GitHubCodeApi {
|
|
|
198
213
|
get label() {
|
|
199
214
|
return formatRepoSpec(this.spec);
|
|
200
215
|
}
|
|
201
|
-
request(path, action) {
|
|
216
|
+
request(path, action, revalidate = false) {
|
|
202
217
|
return githubRequest(path, this.token, {
|
|
203
218
|
label: this.label,
|
|
204
219
|
action,
|
|
205
220
|
repo: `${this.spec.owner}/${this.spec.repo}`,
|
|
221
|
+
revalidate,
|
|
206
222
|
});
|
|
207
223
|
}
|
|
208
224
|
/**
|
|
209
225
|
* The commit the configured ref points at right now.
|
|
210
226
|
*
|
|
211
227
|
* Resolved afresh for each operation — so a push is visible to the very next call — but only
|
|
212
|
-
* once within it, however many files that operation ends up reading.
|
|
228
|
+
* once within it, however many files that operation ends up reading. The request is conditional:
|
|
229
|
+
* while the branch has not moved, GitHub answers 304 and the call costs no rate limit.
|
|
213
230
|
*/
|
|
214
231
|
resolveRef() {
|
|
215
232
|
return oncePerOperation(`ref:${this.label}`, async () => {
|
|
216
233
|
const ref = this.spec.ref ?? "HEAD";
|
|
217
|
-
const commit = await this.request(`/repos/${this.spec.owner}/${this.spec.repo}/commits/${encodeURIComponent(ref)}`, `Resolve ${ref} to its current commit
|
|
234
|
+
const commit = await this.request(`/repos/${this.spec.owner}/${this.spec.repo}/commits/${encodeURIComponent(ref)}`, `Resolve ${ref} to its current commit`, true);
|
|
218
235
|
logger.debug(`${this.label} is at ${String(commit.sha).slice(0, 7)}`);
|
|
219
236
|
return {
|
|
220
237
|
commitSha: commit.sha,
|
|
@@ -294,13 +311,18 @@ export class GitHubCodeApi {
|
|
|
294
311
|
}
|
|
295
312
|
}
|
|
296
313
|
/**
|
|
297
|
-
* A blob's bytes
|
|
314
|
+
* A blob's bytes, read from the blob store when this machine already downloaded them.
|
|
298
315
|
* `filePath` names the file in github.log.
|
|
299
316
|
*/
|
|
300
317
|
getBlob(blobSha, filePath) {
|
|
301
318
|
return this.cached(this.blobCache, blobSha, async () => {
|
|
319
|
+
const stored = readStoredBlob(blobSha);
|
|
320
|
+
if (stored)
|
|
321
|
+
return stored;
|
|
302
322
|
const blob = await this.request(`/repos/${this.spec.owner}/${this.spec.repo}/git/blobs/${blobSha}`, filePath ? `Download ${filePath}` : `Download blob ${blobSha.slice(0, 7)}`);
|
|
303
|
-
|
|
323
|
+
const bytes = Buffer.from(blob.content ?? "", blob.encoding === "base64" ? "base64" : "utf-8");
|
|
324
|
+
storeBlob(blobSha, bytes);
|
|
325
|
+
return bytes;
|
|
304
326
|
});
|
|
305
327
|
}
|
|
306
328
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import { findAndHashSection } from "./core/section-tracker.js";
|
|
|
24
24
|
import { getCodeSource, invalidateCodeSources, setCodeSourceProvider } from "./core/code-source.js";
|
|
25
25
|
import { readCodeSourceConfig } from "./core/project-settings.js";
|
|
26
26
|
import { LocalState } from "./core/local-state.js";
|
|
27
|
+
import { setBlobStoreDir } from "./core/blob-store.js";
|
|
27
28
|
import { migrateCodeSources } from "./core/migrate-code-sources.js";
|
|
28
29
|
import { setCodeRootProvider } from "./core/path-resolver.js";
|
|
29
30
|
import { getCodeSourceTool, getGitHubTokenStatus, GITHUB_TOKEN_VAR, inspectCodeRoot, inspectRepository, listCodeSources, previewCodeSource, saveCodeSource, searchRepositoriesForInput, setCodeRoot, setCodeRootTool, setCodeSourceTool, } from "./tools/code-sources.js";
|
|
@@ -108,6 +109,7 @@ historyStore.init(storageEngine.getDataDir());
|
|
|
108
109
|
storageEngine.setHistoryStore(historyStore);
|
|
109
110
|
const syncEngine = new SyncEngine(storageEngine);
|
|
110
111
|
const localState = new LocalState(storageEngine.getDataDir());
|
|
112
|
+
setBlobStoreDir(path.join(storageEngine.getDataDir(), "blob-cache"));
|
|
111
113
|
setCodeRootProvider((project) => localState.getCodeRoot(project));
|
|
112
114
|
setCodeSourceProvider({
|
|
113
115
|
codeSourceConfig: (project) => readCodeSourceConfig(storageEngine.getProjectDir(project)),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a0 as ln,a1 as an,a2 as y,a3 as tn,a4 as H,a5 as q,a6 as _,a7 as un,a8 as rn,a9 as L,aa as o,ab as B,ac as sn,ad as on,ae as fn}from"./index-
|
|
1
|
+
import{a0 as ln,a1 as an,a2 as y,a3 as tn,a4 as H,a5 as q,a6 as _,a7 as un,a8 as rn,a9 as L,aa as o,ab as B,ac as sn,ad as on,ae as fn}from"./index-C8LVeAsA.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,C,a){var O=I-l,i=D-h,n=C-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function W(l,h,I,D,v,A,C){var a=l-I,O=h-D,i=(C?A:-A)/L(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,F=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*L(fn(0,T*T*R-P*P)),j=(P*g-m*S)/R,z=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=j-F,e=z-t,r=w-F,G=p-t;return x*x+e*e>r*r+G*G&&(j=w,z=p),{cx:j,cy:z,x01:-n,y01:-d,x11:j*(v/T-1),y11:z*(v/T-1)}}function hn(){var l=cn,h=yn,I=B(0),D=null,v=gn,A=dn,C=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-an,c=A.apply(this,arguments)-an,F=un(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(F>tn-y)a.moveTo(s*H(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*H(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=F,S=F,j=C.apply(this,arguments)/2,z=j>y&&(D?+D.apply(this,arguments):L(u*u+s*s)),w=_(un(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if(z>y){var G=sn(z/u*q(j)),M=sn(z/s*q(j));(P-=G*2)>y?(G*=t?1:-1,R+=G,T-=G):(P=0,R=T=(f+c)/2),(S-=M*2)>y?(M*=t?1:-1,m+=M,g-=M):(S=0,m=g=(f+c)/2)}var J=s*H(m),K=s*q(m),N=u*H(T),Q=u*q(T);if(w>y){var U=s*H(g),V=s*q(g),X=u*H(R),Y=u*q(R),E;if(F<rn)if(E=pn(J,K,X,Y,U,V,N,Q)){var Z=J-E[0],$=K-E[1],b=U-E[0],k=V-E[1],nn=1/q(on((Z*b+$*k)/(L(Z*Z+$*$)*L(b*b+k*k)))/2),en=L(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=W(X,Y,J,K,s,x,t),r=W(U,V,N,Q,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(J,K),a.arc(0,0,s,m,g,!t)):a.moveTo(J,K),!(u>y)||!(P>y)?a.lineTo(N,Q):p>y?(e=W(N,Q,U,V,u,-p,t),r=W(J,K,X,Y,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-rn/2;return[H(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:B(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:B(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:B(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:B(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:B(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:B(+n),i):A},i.padAngle=function(n){return arguments.length?(C=typeof n=="function"?n:B(+n),i):C},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
|