@nanonets/graft 0.22.0 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/app/checkout.d.ts +0 -32
- package/dist/app/checkout.d.ts.map +1 -1
- package/dist/app/checkout.js +0 -45
- package/dist/app/checkout.js.map +1 -1
- package/dist/app/identity.d.ts +0 -56
- package/dist/app/identity.d.ts.map +1 -1
- package/dist/app/identity.js +0 -119
- package/dist/app/identity.js.map +1 -1
- package/dist/app/main.js +0 -8
- package/dist/app/main.js.map +1 -1
- package/dist/app/server.d.ts +0 -20
- package/dist/app/server.d.ts.map +1 -1
- package/dist/app/server.js +0 -121
- package/dist/app/server.js.map +1 -1
- package/dist/check/learning-edits.d.ts +9 -0
- package/dist/check/learning-edits.d.ts.map +1 -0
- package/dist/check/learning-edits.js +62 -0
- package/dist/check/learning-edits.js.map +1 -0
- package/dist/claude/format.d.ts.map +1 -1
- package/dist/claude/format.js +4 -4
- package/dist/claude/format.js.map +1 -1
- package/dist/claude/shim-template.d.ts.map +1 -1
- package/dist/claude/shim-template.js +39 -3
- package/dist/claude/shim-template.js.map +1 -1
- package/dist/claude/skill-template.d.ts.map +1 -1
- package/dist/claude/skill-template.js +18 -7
- package/dist/claude/skill-template.js.map +1 -1
- package/dist/cli-picker.d.ts +15 -0
- package/dist/cli-picker.d.ts.map +1 -1
- package/dist/cli-picker.js +33 -3
- package/dist/cli-picker.js.map +1 -1
- package/dist/cli.js +100 -27
- package/dist/cli.js.map +1 -1
- package/dist/cloud/diff.d.ts +2 -0
- package/dist/cloud/diff.d.ts.map +1 -1
- package/dist/cloud/diff.js +1 -0
- package/dist/cloud/diff.js.map +1 -1
- package/dist/notes/cold-start.d.ts +54 -4
- package/dist/notes/cold-start.d.ts.map +1 -1
- package/dist/notes/cold-start.js +97 -36
- package/dist/notes/cold-start.js.map +1 -1
- package/dist/notes/notes.d.ts +19 -4
- package/dist/notes/notes.d.ts.map +1 -1
- package/dist/notes/notes.js +53 -15
- package/dist/notes/notes.js.map +1 -1
- package/dist/notes/pr-reviews.d.ts +108 -0
- package/dist/notes/pr-reviews.d.ts.map +1 -0
- package/dist/notes/pr-reviews.js +325 -0
- package/dist/notes/pr-reviews.js.map +1 -0
- package/dist/telemetry/contract.d.ts.map +1 -1
- package/dist/telemetry/contract.js +6 -5
- package/dist/telemetry/contract.js.map +1 -1
- package/package.json +1 -1
- package/dist/app/brain-build-process.d.ts +0 -22
- package/dist/app/brain-build-process.d.ts.map +0 -1
- package/dist/app/brain-build-process.js +0 -129
- package/dist/app/brain-build-process.js.map +0 -1
- package/dist/app/brain-build-worker.d.ts +0 -35
- package/dist/app/brain-build-worker.d.ts.map +0 -1
- package/dist/app/brain-build-worker.js +0 -56
- package/dist/app/brain-build-worker.js.map +0 -1
- package/dist/app/brain-build.d.ts +0 -160
- package/dist/app/brain-build.d.ts.map +0 -1
- package/dist/app/brain-build.js +0 -278
- package/dist/app/brain-build.js.map +0 -1
- package/dist/app/history.d.ts +0 -163
- package/dist/app/history.d.ts.map +0 -1
- package/dist/app/history.js +0 -319
- package/dist/app/history.js.map +0 -1
- package/dist/app/sources.d.ts +0 -81
- package/dist/app/sources.d.ts.map +0 -1
- package/dist/app/sources.js +0 -470
- package/dist/app/sources.js.map +0 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Trail-code gives Claude Code, Cursor, Codex and other coding agents a map of you
|
|
|
22
22
|
<a href="https://trailhq.com/graft"><img src="https://img.shields.io/badge/website-trailhq.com/graft-E5484D?style=for-the-badge" /></a>
|
|
23
23
|
<a href="https://discord.gg/zxmKweAA29"><img src="https://img.shields.io/badge/Discord-join-5865F2?style=for-the-badge&logo=discord&logoColor=white" /></a>
|
|
24
24
|
<a href="https://www.npmjs.com/package/@trailhq/trail-code"><img src="https://img.shields.io/npm/v/%40trailhq%2Ftrail-code?style=for-the-badge&logo=npm&logoColor=white&label=npm" /></a>
|
|
25
|
-
<a href="https://www.npmjs.com/package/@
|
|
25
|
+
<a href="https://www.npmjs.com/package/@nanonets/graft"><img src="https://img.shields.io/npm/dm/%40nanonets%2Fgraft?style=for-the-badge&logo=npm&logoColor=white&label=downloads" /></a>
|
|
26
26
|
<a href="https://nodejs.org"><img src="https://img.shields.io/node/v/%40trailhq%2Ftrail-code?style=for-the-badge&logo=nodedotjs&logoColor=white" /></a>
|
|
27
27
|
<img src="https://img.shields.io/badge/TypeScript-strict-3178C6?style=for-the-badge&logo=typescript&logoColor=white" />
|
|
28
28
|
<img src="https://img.shields.io/badge/License-MIT-20C997?style=for-the-badge" />
|
|
@@ -70,7 +70,7 @@ npm install -g @trailhq/trail-code && trail init
|
|
|
70
70
|
|
|
71
71
|
**Same model, more tasks solved.** On SWE-bench Verified (real GitHub issues, graded by the maintainers' own tests), Claude Sonnet 5 solved **33 of 50** issues with the graft map and **27 of 50** without it, using 23% fewer tokens. [Details](#swe-bench-verified)
|
|
72
72
|
|
|
73
|
-
**Cheaper and faster.** Across 162 runs of the same agent on the same tasks: **32% lower cost**, **42% fewer tokens**, **60% less time**, and the same share of correct answers. On
|
|
73
|
+
**Cheaper and faster.** Across 162 runs of the same agent on the same tasks: **32% lower cost**, **42% fewer uncached input tokens**, **60% less time**, and the same share of correct answers. On PocketBase, 15 real tasks ran **21% cheaper** and **14% faster**. [Details](#benchmark)
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
@@ -94,7 +94,7 @@ npm install -g @trailhq/trail-code && trail init
|
|
|
94
94
|
Trail-code has two parts. Both run on your machine.
|
|
95
95
|
|
|
96
96
|
1. **The graft map: your code.** `trail init` parses your repo with tree-sitter into a map of every file, function and who calls what. No model, no API key. It refreshes itself when the code changes, uncommitted edits included. Your agent gets pointers to the matching files and functions with each prompt, and sees what depends on a file right after it edits it.
|
|
97
|
-
2. **Learnings: what you, your agents and your team know.** When a session works something out, or you correct how something is done, your agent saves it as a learning. The next session on that code, yours or a teammate's, starts from it instead of digging again. Learnings about the code are committed with the code; learnings about you stay on your machine. On day one, `trail init` starts you off with what you told Claude more than once in your past sessions.
|
|
97
|
+
2. **Learnings: what you, your agents and your team know.** When a session works something out, or you correct how something is done, your agent saves it as a learning. The next session on that code, yours or a teammate's, starts from it instead of digging again. Learnings about the code are committed with the code; learnings about you stay on your machine. On day one, `trail init` starts you off with what you told Claude more than once in your past sessions, and what reviewers flagged on more than one of the repo's merged PRs.
|
|
98
98
|
|
|
99
99
|
The graft map tells your agent *where* things are. Learnings tell it *why* they are that way, and *how* the work gets done here.
|
|
100
100
|
|
|
@@ -152,11 +152,11 @@ The graft map builds that understanding once, and keeps it current:
|
|
|
152
152
|
- **Plain files you can read.** Linked markdown nodes your agent opens and greps like any other file. No embeddings, no index to keep warm.
|
|
153
153
|
- **Summaries if you want them.** `trail build --deep` adds plain-English summaries of each part of the system, written by the model provider you choose, under your own key.
|
|
154
154
|
|
|
155
|
-
| | Cold
|
|
155
|
+
| | Cold agent | Agent with the graft map |
|
|
156
156
|
|---|---|---|
|
|
157
157
|
| SWE-bench Verified, 50 real issues | 27 solved | **33 solved (+12 pts)** |
|
|
158
|
-
| Our benchmark, 162 runs | baseline | **32% lower cost, 42% fewer tokens, 60% less time** |
|
|
159
|
-
|
|
|
158
|
+
| Our benchmark, 162 runs | baseline | **32% lower cost, 42% fewer uncached input tokens, 60% less time** |
|
|
159
|
+
| PocketBase, 15 real tasks | baseline | **21% cheaper, 14% faster** |
|
|
160
160
|
|
|
161
161
|
How it is measured: [Benchmark](#benchmark), [SWE-bench Verified](#swe-bench-verified) and [Tested on popular repos](#tested-on-popular-repos). How it is built: [the reference](#how-the-graph-gets-built).
|
|
162
162
|
|
|
@@ -312,6 +312,7 @@ Coming soon: a Trail workspace, for your team's view across repos. A team page w
|
|
|
312
312
|
- **Personal learnings.** Kept in `~/.trail`. Never committed, never uploaded.
|
|
313
313
|
- **Repo learnings.** Files in your repo, shared through git like any other file.
|
|
314
314
|
- **Your past sessions.** `trail init` reads your own messages from Claude Code's transcripts for the repo, on your machine. Your own Claude Code does the reading, with Claude Sonnet under your login, so your messages go only to Anthropic, and it counts toward your Claude usage. None of it goes to Trail.
|
|
315
|
+
- **Your PR reviews.** When `gh` is installed and signed in, `trail init` reads the review comments on the repo's last 50 merged PRs with it, under your GitHub login. Anything that looks like a secret is taken out, and your own Claude Code reads them the same way. None of it goes to Trail.
|
|
315
316
|
- **If you connect a Trail workspace.** No learning goes to Trail until you do. Once connected, the learnings in the repos you picked go to Trail, including those on teammates' pushed branches. So does a record of each search and check that used them: the search text, the files, and whose learnings were shown. That is how the team page counts reuse.
|
|
316
317
|
- **Anonymous usage stats.** Buckets and fixed labels only, never the title, text or paths of a learning. [`TELEMETRY.md`](TELEMETRY.md) lists every field. Turn it off with `trail telemetry disable` or `DO_NOT_TRACK=1`.
|
|
317
318
|
|
package/dist/app/checkout.d.ts
CHANGED
|
@@ -41,38 +41,6 @@ export interface Checkout {
|
|
|
41
41
|
* {@link headDiffBase} is entirely about.
|
|
42
42
|
*/
|
|
43
43
|
export declare function checkoutPullRequest(req: CheckoutRequest): Checkout;
|
|
44
|
-
/** What a plain-repository checkout needs: no pull request, just a ref. */
|
|
45
|
-
export interface RepoCheckoutRequest {
|
|
46
|
-
owner: string;
|
|
47
|
-
repo: string;
|
|
48
|
-
/** Branch to read. Empty means "whatever HEAD points at" (the default branch). */
|
|
49
|
-
ref?: string;
|
|
50
|
-
token: string;
|
|
51
|
-
/** How much history to fetch. The default is deeper than a PR review needs
|
|
52
|
-
* because commit MESSAGES are the payload here, not a diff — 50 commits is a
|
|
53
|
-
* fortnight on an active repo and would mine almost nothing. */
|
|
54
|
-
depth?: number;
|
|
55
|
-
api?: string;
|
|
56
|
-
log?: (msg: string) => void;
|
|
57
|
-
}
|
|
58
|
-
/** A plain checkout: the tree, the commit it landed on, and the cleanup. */
|
|
59
|
-
export interface RepoCheckout {
|
|
60
|
-
dir: string;
|
|
61
|
-
headSha: string;
|
|
62
|
-
/** The branch actually checked out, resolved when the caller named none. */
|
|
63
|
-
branch: string;
|
|
64
|
-
cleanup: () => void;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Clone one repository at a ref, shallow, for reading rather than reviewing.
|
|
68
|
-
*
|
|
69
|
-
* Separate from {@link checkoutPullRequest} rather than a flag on it: that
|
|
70
|
-
* function's whole shape — two refspecs, the merge/head fallback, the diff-base
|
|
71
|
-
* resolution — exists to answer "what does this pull request change", and none
|
|
72
|
-
* of it applies to "read this repository's history". Sharing the hardened `git`
|
|
73
|
-
* runner is the part worth reusing.
|
|
74
|
-
*/
|
|
75
|
-
export declare function checkoutRepository(req: RepoCheckoutRequest): RepoCheckout;
|
|
76
44
|
/** Never let a token reach a log line, even inside git's own error text. */
|
|
77
45
|
export declare function redact(text: string, token: string): string;
|
|
78
46
|
//# sourceMappingURL=checkout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../../src/app/checkout.ts"],"names":[],"mappings":"AAoCA,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;uDAEmD;IACnD,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,QAAQ;IACvB;kDAC8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb;0EACsE;IACtE,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC;IACtB;wBACoB;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AA+CD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,GAAG,QAAQ,CA2DlE;
|
|
1
|
+
{"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../../src/app/checkout.ts"],"names":[],"mappings":"AAoCA,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;uDAEmD;IACnD,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,QAAQ;IACvB;kDAC8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb;0EACsE;IACtE,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC;IACtB;wBACoB;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AA+CD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,GAAG,QAAQ,CA2DlE;AAsED,4EAA4E;AAC5E,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI1D"}
|
package/dist/app/checkout.js
CHANGED
|
@@ -141,51 +141,6 @@ export function checkoutPullRequest(req) {
|
|
|
141
141
|
log(`${tag}: no refs/pull/${req.number}/merge (GitHub deletes it when a pull request closes), reviewing refs/pull/${req.number}/head against ${against}`);
|
|
142
142
|
return { dir, base: "refs/graft/base", ref, cleanup };
|
|
143
143
|
}
|
|
144
|
-
/**
|
|
145
|
-
* Clone one repository at a ref, shallow, for reading rather than reviewing.
|
|
146
|
-
*
|
|
147
|
-
* Separate from {@link checkoutPullRequest} rather than a flag on it: that
|
|
148
|
-
* function's whole shape — two refspecs, the merge/head fallback, the diff-base
|
|
149
|
-
* resolution — exists to answer "what does this pull request change", and none
|
|
150
|
-
* of it applies to "read this repository's history". Sharing the hardened `git`
|
|
151
|
-
* runner is the part worth reusing.
|
|
152
|
-
*/
|
|
153
|
-
export function checkoutRepository(req) {
|
|
154
|
-
const host = (req.api ?? "https://github.com").replace(/\/$/, "");
|
|
155
|
-
const dir = mkdtempSync(join(tmpdir(), `graft-repo-${req.owner}-${req.repo}-`));
|
|
156
|
-
const cleanup = () => rmSync(dir, { recursive: true, force: true });
|
|
157
|
-
const log = req.log ?? (() => { });
|
|
158
|
-
const tag = `${req.owner}/${req.repo}`;
|
|
159
|
-
const fail = (step, err) => {
|
|
160
|
-
cleanup();
|
|
161
|
-
throw new Error(`${tag}: ${step} failed: ${redact(err, req.token)}`);
|
|
162
|
-
};
|
|
163
|
-
const init = git(dir, ["init", "--quiet", "--initial-branch=graft-tmp"]);
|
|
164
|
-
if (!init.ok)
|
|
165
|
-
fail("init", init.err);
|
|
166
|
-
const remote = git(dir, ["remote", "add", "origin", `${host}/${req.owner}/${req.repo}.git`]);
|
|
167
|
-
if (!remote.ok)
|
|
168
|
-
fail("remote", remote.err);
|
|
169
|
-
const depth = req.depth ?? REPO_FETCH_DEPTH;
|
|
170
|
-
// A named branch is fetched by name; with none, `HEAD` resolves to whatever
|
|
171
|
-
// the remote's default branch is, which is what the caller means by "the
|
|
172
|
-
// repository" and saves an API round trip to look the name up.
|
|
173
|
-
const refspec = req.ref
|
|
174
|
-
? `+refs/heads/${req.ref}:refs/graft/head`
|
|
175
|
-
: `+HEAD:refs/graft/head`;
|
|
176
|
-
const fetched = git(dir, ["fetch", "--quiet", `--depth=${depth}`, "--no-recurse-submodules", "origin", refspec], req.token);
|
|
177
|
-
if (!fetched.ok)
|
|
178
|
-
fail("fetch", fetched.err);
|
|
179
|
-
const co = git(dir, ["checkout", "--quiet", "--detach", "refs/graft/head"]);
|
|
180
|
-
if (!co.ok)
|
|
181
|
-
fail("checkout", co.err);
|
|
182
|
-
const headSha = line(dir, ["rev-parse", "HEAD"]) ?? "";
|
|
183
|
-
const branch = req.ref ?? line(dir, ["rev-parse", "--abbrev-ref", "origin/HEAD"]) ?? "";
|
|
184
|
-
log(`${tag}: read ${req.ref || "HEAD"} at ${short(headSha)} (${depth} commits deep)`);
|
|
185
|
-
return { dir, headSha, branch: branch.replace(/^origin\//, ""), cleanup };
|
|
186
|
-
}
|
|
187
|
-
/** Default history depth for a repository read. See RepoCheckoutRequest.depth. */
|
|
188
|
-
const REPO_FETCH_DEPTH = 500;
|
|
189
144
|
/** The one fetch each attempt makes: the PR ref to review plus the base branch. */
|
|
190
145
|
function fetchArgs(req, pull, deepen = false) {
|
|
191
146
|
return [
|
package/dist/app/checkout.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkout.js","sourceRoot":"","sources":["../../src/app/checkout.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"checkout.js","sourceRoot":"","sources":["../../src/app/checkout.ts"],"names":[],"mappings":"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"}
|
package/dist/app/identity.d.ts
CHANGED
|
@@ -31,62 +31,6 @@ export type Fetch = (url: string, init?: {
|
|
|
31
31
|
* Tokens last an hour and a busy repository can fire a dozen webhooks a minute;
|
|
32
32
|
* without the cache every one of them spends a round trip and a signature.
|
|
33
33
|
*/
|
|
34
|
-
/**
|
|
35
|
-
* The installation id for one repository.
|
|
36
|
-
*
|
|
37
|
-
* Every existing caller gets the id from a webhook payload, because every
|
|
38
|
-
* existing caller is reacting to one. A request that names a repository instead
|
|
39
|
-
* has to look it up, and this is the only endpoint that answers it: it is
|
|
40
|
-
* App-JWT authed, so it works before any installation token exists.
|
|
41
|
-
*
|
|
42
|
-
* Returns null when the App is not installed on the repository — which is the
|
|
43
|
-
* expected answer for "the user pasted a repo we cannot see", not an error.
|
|
44
|
-
*/
|
|
45
|
-
/**
|
|
46
|
-
* Headers for a GitHub REST call, with or without a token.
|
|
47
|
-
*
|
|
48
|
-
* An empty token means anonymous, and anonymous has to mean NO authorization
|
|
49
|
-
* header — `Bearer ` with nothing after it is a 401, not a fallback. That
|
|
50
|
-
* distinction is the whole reason this is a function: a public repository read
|
|
51
|
-
* without an installation goes down exactly this path.
|
|
52
|
-
*/
|
|
53
|
-
export declare function ghHeaders(token: string): Record<string, string>;
|
|
54
|
-
/**
|
|
55
|
-
* An installation token for reading PUBLIC repositories the App is not
|
|
56
|
-
* installed on.
|
|
57
|
-
*
|
|
58
|
-
* GitHub answers any public resource to any installation token, at 5,000
|
|
59
|
-
* requests an hour. Anonymous answers the same resources at 60 an hour for the
|
|
60
|
-
* whole box, which one repository's pull-request walk spends in a minute — so
|
|
61
|
-
* without this, the second public repo of the hour reads as "we cannot see it".
|
|
62
|
-
*
|
|
63
|
-
* `owner` is our own account, so this borrows OUR installation's rate limit
|
|
64
|
-
* rather than some customer's. Falls back to the first installation, and to
|
|
65
|
-
* null when the App has none, in which case the caller reads anonymously.
|
|
66
|
-
*/
|
|
67
|
-
export declare function publicReadInstallation(creds: AppCredentials, owner: string, fetchImpl: Fetch, nowMs?: number, api?: string): Promise<number | null>;
|
|
68
|
-
export declare function installationFor(creds: AppCredentials, owner: string, repo: string, fetchImpl: Fetch, nowMs?: number, api?: string): Promise<number | null>;
|
|
69
|
-
/**
|
|
70
|
-
* Why a repository is unreachable, and where to send the user to fix it.
|
|
71
|
-
*
|
|
72
|
-
* Two failures look identical from `/repos/{o}/{r}/installation` — both 404 —
|
|
73
|
-
* and they need different sentences and different links. Either the App is not
|
|
74
|
-
* on the account at all, so they install it; or it is installed and this
|
|
75
|
-
* repository simply is not in its selected list, so they edit that list. Telling
|
|
76
|
-
* someone to install an App they already installed is the kind of dead end that
|
|
77
|
-
* ends the session.
|
|
78
|
-
*
|
|
79
|
-
* ownerId is the account's numeric id, which preselects the right account on
|
|
80
|
-
* GitHub's install screen. There is no equivalent for preselecting a repository;
|
|
81
|
-
* GitHub owns that checkbox list.
|
|
82
|
-
*/
|
|
83
|
-
export interface RepoAccessGap {
|
|
84
|
-
reason: "not_installed" | "repo_not_selected";
|
|
85
|
-
ownerId: number | null;
|
|
86
|
-
installationId: number | null;
|
|
87
|
-
}
|
|
88
|
-
/** Which of the two gaps applies to owner/repo. */
|
|
89
|
-
export declare function repoAccessGap(creds: AppCredentials, owner: string, fetchImpl: Fetch, nowMs?: number, api?: string): Promise<RepoAccessGap>;
|
|
90
34
|
export declare class InstallationTokens {
|
|
91
35
|
private readonly creds;
|
|
92
36
|
private readonly fetchImpl;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/app/identity.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAOnE;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iFAAiF;AACjF,MAAM,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC;IACxH,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7B,CAAC,CAAC;AAOH;;;;;GAKG;AACH
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/app/identity.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAOnE;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iFAAiF;AACjF,MAAM,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC;IACxH,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7B,CAAC,CAAC;AAOH;;;;;GAKG;AACH,qBAAa,kBAAkB;IAI3B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IANtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;gBAGpC,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,KAAK,EAChB,GAAG,GAAE,MAAM,MAAiB,EAC5B,GAAG,SAA2B;IAG3C,GAAG,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBlD,8EAA8E;IAC9E,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;CAGzC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAMjG"}
|
package/dist/app/identity.js
CHANGED
|
@@ -38,125 +38,6 @@ export function appJwt(creds, nowMs) {
|
|
|
38
38
|
* Tokens last an hour and a busy repository can fire a dozen webhooks a minute;
|
|
39
39
|
* without the cache every one of them spends a round trip and a signature.
|
|
40
40
|
*/
|
|
41
|
-
/**
|
|
42
|
-
* The installation id for one repository.
|
|
43
|
-
*
|
|
44
|
-
* Every existing caller gets the id from a webhook payload, because every
|
|
45
|
-
* existing caller is reacting to one. A request that names a repository instead
|
|
46
|
-
* has to look it up, and this is the only endpoint that answers it: it is
|
|
47
|
-
* App-JWT authed, so it works before any installation token exists.
|
|
48
|
-
*
|
|
49
|
-
* Returns null when the App is not installed on the repository — which is the
|
|
50
|
-
* expected answer for "the user pasted a repo we cannot see", not an error.
|
|
51
|
-
*/
|
|
52
|
-
/**
|
|
53
|
-
* Headers for a GitHub REST call, with or without a token.
|
|
54
|
-
*
|
|
55
|
-
* An empty token means anonymous, and anonymous has to mean NO authorization
|
|
56
|
-
* header — `Bearer ` with nothing after it is a 401, not a fallback. That
|
|
57
|
-
* distinction is the whole reason this is a function: a public repository read
|
|
58
|
-
* without an installation goes down exactly this path.
|
|
59
|
-
*/
|
|
60
|
-
export function ghHeaders(token) {
|
|
61
|
-
const headers = {
|
|
62
|
-
accept: "application/vnd.github+json",
|
|
63
|
-
"user-agent": "graft-app",
|
|
64
|
-
};
|
|
65
|
-
if (token)
|
|
66
|
-
headers.authorization = `Bearer ${token}`;
|
|
67
|
-
return headers;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* An installation token for reading PUBLIC repositories the App is not
|
|
71
|
-
* installed on.
|
|
72
|
-
*
|
|
73
|
-
* GitHub answers any public resource to any installation token, at 5,000
|
|
74
|
-
* requests an hour. Anonymous answers the same resources at 60 an hour for the
|
|
75
|
-
* whole box, which one repository's pull-request walk spends in a minute — so
|
|
76
|
-
* without this, the second public repo of the hour reads as "we cannot see it".
|
|
77
|
-
*
|
|
78
|
-
* `owner` is our own account, so this borrows OUR installation's rate limit
|
|
79
|
-
* rather than some customer's. Falls back to the first installation, and to
|
|
80
|
-
* null when the App has none, in which case the caller reads anonymously.
|
|
81
|
-
*/
|
|
82
|
-
export async function publicReadInstallation(creds, owner, fetchImpl, nowMs = Date.now(), api = "https://api.github.com") {
|
|
83
|
-
const res = await fetchImpl(`${api}/app/installations?per_page=100`, {
|
|
84
|
-
headers: {
|
|
85
|
-
authorization: `Bearer ${appJwt(creds, nowMs)}`,
|
|
86
|
-
accept: "application/vnd.github+json",
|
|
87
|
-
"user-agent": "graft-app",
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
if (!res.ok)
|
|
91
|
-
return null;
|
|
92
|
-
const list = JSON.parse(await res.text());
|
|
93
|
-
if (!Array.isArray(list) || list.length === 0)
|
|
94
|
-
return null;
|
|
95
|
-
const wanted = owner.toLowerCase();
|
|
96
|
-
const mine = list.find((i) => (i.account?.login ?? "").toLowerCase() === wanted);
|
|
97
|
-
return (mine ?? list[0]).id ?? null;
|
|
98
|
-
}
|
|
99
|
-
export async function installationFor(creds, owner, repo, fetchImpl, nowMs = Date.now(), api = "https://api.github.com") {
|
|
100
|
-
const res = await fetchImpl(`${api}/repos/${owner}/${repo}/installation`, {
|
|
101
|
-
headers: {
|
|
102
|
-
authorization: `Bearer ${appJwt(creds, nowMs)}`,
|
|
103
|
-
accept: "application/vnd.github+json",
|
|
104
|
-
"user-agent": "graft-app",
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
if (res.status === 404)
|
|
108
|
-
return null;
|
|
109
|
-
if (!res.ok) {
|
|
110
|
-
const body = await res.text();
|
|
111
|
-
throw new Error(`installation lookup for ${owner}/${repo} failed: ${res.status} ${body.slice(0, 200)}`);
|
|
112
|
-
}
|
|
113
|
-
const parsed = JSON.parse(await res.text());
|
|
114
|
-
return typeof parsed.id === "number" ? parsed.id : null;
|
|
115
|
-
}
|
|
116
|
-
/** Which of the two gaps applies to owner/repo. */
|
|
117
|
-
export async function repoAccessGap(creds, owner, fetchImpl, nowMs = Date.now(), api = "https://api.github.com") {
|
|
118
|
-
const headers = {
|
|
119
|
-
authorization: `Bearer ${appJwt(creds, nowMs)}`,
|
|
120
|
-
accept: "application/vnd.github+json",
|
|
121
|
-
"user-agent": "graft-app",
|
|
122
|
-
};
|
|
123
|
-
// An account is an org or a user and the endpoints differ, so try the org one
|
|
124
|
-
// and fall back rather than making the caller know which it is.
|
|
125
|
-
for (const path of [`/orgs/${owner}/installation`, `/users/${owner}/installation`]) {
|
|
126
|
-
try {
|
|
127
|
-
const res = await fetchImpl(`${api}${path}`, { headers });
|
|
128
|
-
if (!res.ok)
|
|
129
|
-
continue;
|
|
130
|
-
const parsed = JSON.parse(await res.text());
|
|
131
|
-
return {
|
|
132
|
-
reason: "repo_not_selected",
|
|
133
|
-
ownerId: parsed.account?.id ?? null,
|
|
134
|
-
installationId: parsed.id ?? null,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
// Try the other shape; a network failure here only costs a less specific
|
|
139
|
-
// message, never the whole answer.
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return { reason: "not_installed", ownerId: await ownerIdFor(owner, fetchImpl, api), installationId: null };
|
|
143
|
-
}
|
|
144
|
-
/** The account's numeric id, for preselecting it on the install screen. */
|
|
145
|
-
async function ownerIdFor(owner, fetchImpl, api) {
|
|
146
|
-
try {
|
|
147
|
-
// Unauthenticated: a public account's id is public, and this runs on a path
|
|
148
|
-
// where the App has no installation token to use anyway.
|
|
149
|
-
const res = await fetchImpl(`${api}/users/${owner}`, {
|
|
150
|
-
headers: { accept: "application/vnd.github+json", "user-agent": "graft-app" },
|
|
151
|
-
});
|
|
152
|
-
if (!res.ok)
|
|
153
|
-
return null;
|
|
154
|
-
return JSON.parse(await res.text()).id ?? null;
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
41
|
export class InstallationTokens {
|
|
161
42
|
creds;
|
|
162
43
|
fetchImpl;
|
package/dist/app/identity.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/app/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEtE,iFAAiF;AACjF,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,uDAAuD;AAC9E,2DAA2D;AAC3D,MAAM,eAAe,GAAG,MAAM,CAAC;AAE/B,MAAM,MAAM,GAAG,CAAC,CAAkB,EAAU,EAAE,CAC5C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAQ9F;;;;;;GAMG;AACH,MAAM,UAAU,MAAM,CAAC,KAAqB,EAAE,KAAa;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACxF,MAAM,MAAM,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;IACxC,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,CAAC;IACtC,OAAO,GAAG,MAAM,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;AACzE,CAAC;AAmBD;;;;;GAKG;AACH
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/app/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEtE,iFAAiF;AACjF,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,uDAAuD;AAC9E,2DAA2D;AAC3D,MAAM,eAAe,GAAG,MAAM,CAAC;AAE/B,MAAM,MAAM,GAAG,CAAC,CAAkB,EAAU,EAAE,CAC5C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAQ9F;;;;;;GAMG;AACH,MAAM,UAAU,MAAM,CAAC,KAAqB,EAAE,KAAa;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACxF,MAAM,MAAM,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;IACxC,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,CAAC;IACtC,OAAO,GAAG,MAAM,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;AACzE,CAAC;AAmBD;;;;;GAKG;AACH,MAAM,OAAO,kBAAkB;IAIV;IACA;IACA;IACA;IANF,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEvD,YACmB,KAAqB,EACrB,SAAgB,EAChB,MAAoB,IAAI,CAAC,GAAG,EAC5B,MAAM,wBAAwB;QAH9B,UAAK,GAAL,KAAK,CAAgB;QACrB,cAAS,GAAT,SAAS,CAAO;QAChB,QAAG,GAAH,GAAG,CAAyB;QAC5B,QAAG,GAAH,GAAG,CAA2B;IAC9C,CAAC;IAEJ,KAAK,CAAC,GAAG,CAAC,cAAsB;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC3C,IAAI,GAAG,IAAI,GAAG,CAAC,SAAS,GAAG,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE;YAAE,OAAO,GAAG,CAAC,KAAK,CAAC;QAE1E,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,GAAG,sBAAsB,cAAc,gBAAgB,EAAE;YAChG,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE;gBACzD,MAAM,EAAE,6BAA6B;gBACrC,YAAY,EAAE,WAAW;aAC1B;SACF,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,6EAA6E;YAC7E,yEAAyE;YACzE,MAAM,IAAI,KAAK,CAAC,0BAA0B,cAAc,YAAY,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1G,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,CAAC;QACjD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAClG,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,8EAA8E;IAC9E,UAAU,CAAC,cAAsB;QAC/B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IACpC,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,IAAY,EAAE,MAA0B;IACtF,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,MAAM,QAAQ,GAAG,UAAU,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IACrF,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACxD,CAAC"}
|
package/dist/app/main.js
CHANGED
|
@@ -43,14 +43,6 @@ const { server, queue } = createApp({
|
|
|
43
43
|
// server that will not start reviews nothing at all, which is strictly worse
|
|
44
44
|
// than one whose old links go stale.
|
|
45
45
|
pageDir: process.env.GRAFT_PAGE_DIR,
|
|
46
|
-
// Optional for the same reason as pageDir: unset simply disables
|
|
47
|
-
// `POST /brain/build`, and a deployment that only reviews pull requests wants
|
|
48
|
-
// exactly that. The route is the one place the app writes to the platform, so
|
|
49
|
-
// it stays off until someone deliberately turns it on.
|
|
50
|
-
brainBuildSecret: process.env.GRAFT_BRAIN_BUILD_SECRET,
|
|
51
|
-
brainBaseUrl: process.env.GRAFT_BRAIN_URL,
|
|
52
|
-
publicToken: process.env.GRAFT_PUBLIC_GITHUB_TOKEN || process.env.GITHUB_TOKEN,
|
|
53
|
-
publicOwner: process.env.GRAFT_PUBLIC_INSTALLATION_OWNER,
|
|
54
46
|
});
|
|
55
47
|
/**
|
|
56
48
|
* Finish what is in flight before dying.
|
package/dist/app/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/app/main.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,cAAc,CAAC,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU;IACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,uBAAuB,CAAC,CAAC;IAC9C,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAClE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QAC3D,OAAO,CAAC,KAAK,CAAC,8DAA8D,CAAC,CAAC;QAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AAC9C,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;IAClC,KAAK,EAAE,QAAQ,CAAC,cAAc,CAAC;IAC/B,UAAU,EAAE,UAAU,EAAE;IACxB,aAAa,EAAE,QAAQ,CAAC,sBAAsB,CAAC;IAC/C,SAAS,EAAE,QAAQ,CAAC,kBAAkB,CAAC;IACvC,IAAI;IACJ,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,CAAC,CAAC;IACvD,8EAA8E;IAC9E,4EAA4E;IAC5E,6EAA6E;IAC7E,6EAA6E;IAC7E,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/app/main.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,cAAc,CAAC,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU;IACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,uBAAuB,CAAC,CAAC;IAC9C,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAClE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QAC3D,OAAO,CAAC,KAAK,CAAC,8DAA8D,CAAC,CAAC;QAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AAC9C,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;IAClC,KAAK,EAAE,QAAQ,CAAC,cAAc,CAAC;IAC/B,UAAU,EAAE,UAAU,EAAE;IACxB,aAAa,EAAE,QAAQ,CAAC,sBAAsB,CAAC;IAC/C,SAAS,EAAE,QAAQ,CAAC,kBAAkB,CAAC;IACvC,IAAI;IACJ,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,CAAC,CAAC;IACvD,8EAA8E;IAC9E,4EAA4E;IAC5E,6EAA6E;IAC7E,6EAA6E;IAC7E,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;CACpC,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,KAAK,MAAM,MAAM,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAU,EAAE,CAAC;IACpD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACtB,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,cAAc,KAAK,CAAC,IAAI,SAAS,CAAC,CAAC;QACxD,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/app/server.d.ts
CHANGED
|
@@ -19,19 +19,12 @@
|
|
|
19
19
|
import { type Server } from "node:http";
|
|
20
20
|
import { type ReviewJob } from "./events.js";
|
|
21
21
|
import { type AppCredentials, type Fetch } from "./identity.js";
|
|
22
|
-
import { buildRepoIntoBrain, checkRepoAccess } from "./brain-build.js";
|
|
23
22
|
import { PageStore } from "./pages.js";
|
|
24
23
|
import { WorkQueue } from "./queue.js";
|
|
25
24
|
import type { reviewPullRequest } from "./review.js";
|
|
26
25
|
/** Seams for tests: nothing here has a default that touches the network. */
|
|
27
26
|
export interface AppSeams {
|
|
28
27
|
fetch?: Fetch;
|
|
29
|
-
/** Swapped out so a test can assert what the route handed the builder without
|
|
30
|
-
* cloning a repository or reaching GitHub. */
|
|
31
|
-
brainBuild?: typeof buildRepoIntoBrain;
|
|
32
|
-
/** Swapped out so a test can assert the access route's answers without
|
|
33
|
-
* reaching GitHub. */
|
|
34
|
-
brainAccess?: typeof checkRepoAccess;
|
|
35
28
|
/** Swapped out to assert what the queue was handed, without a clone — and, being
|
|
36
29
|
* called in-process, without the fork the default reviewer does. */
|
|
37
30
|
review?: typeof reviewPullRequest;
|
|
@@ -39,19 +32,6 @@ export interface AppSeams {
|
|
|
39
32
|
}
|
|
40
33
|
export interface AppConfig extends AppCredentials {
|
|
41
34
|
webhookSecret: string;
|
|
42
|
-
/** Shared secret for `POST /brain/build`. That route is called by the
|
|
43
|
-
* platform, not by GitHub, so it cannot use the webhook's payload signature —
|
|
44
|
-
* it takes a bearer token instead. Unset disables the route entirely, which
|
|
45
|
-
* is the right default for a deployment that only reviews pull requests. */
|
|
46
|
-
brainBuildSecret?: string;
|
|
47
|
-
/** Platform base URL the digest is posted to. Defaults to production. */
|
|
48
|
-
brainBaseUrl?: string;
|
|
49
|
-
/** Token for reading public repositories the App is not installed on.
|
|
50
|
-
* Optional — without it the read borrows one of our own installation's
|
|
51
|
-
* tokens, and only falls back to anonymous if the App has none. */
|
|
52
|
-
publicToken?: string;
|
|
53
|
-
/** Whose installation to borrow for that. Defaults to ours. */
|
|
54
|
-
publicOwner?: string;
|
|
55
35
|
/** Public origin, for the links put in comments, e.g. https://graft.example.com */
|
|
56
36
|
publicUrl: string;
|
|
57
37
|
/** Where pages are kept, so a restart does not strand the links already posted. */
|
package/dist/app/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/app/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/app/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;AACjG,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAuC,KAAK,cAAc,EAAE,KAAK,KAAK,EAAE,MAAM,eAAe,CAAC;AACrG,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKrD,4EAA4E;AAC5E,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;wEACoE;IACpE,MAAM,CAAC,EAAE,OAAO,iBAAiB,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAU,SAAQ,cAAc;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,wBAAgB,SAAS,CACvB,MAAM,EAAE,SAAS,EACjB,KAAK,GAAE,QAAa,GACnB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,CAkFnE"}
|
package/dist/app/server.js
CHANGED
|
@@ -17,11 +17,8 @@
|
|
|
17
17
|
* everything in here is I/O again.
|
|
18
18
|
*/
|
|
19
19
|
import { createServer } from "node:http";
|
|
20
|
-
import { timingSafeEqual } from "node:crypto";
|
|
21
20
|
import { jobKey, reviewJobFor } from "./events.js";
|
|
22
21
|
import { InstallationTokens, verifySignature } from "./identity.js";
|
|
23
|
-
import { checkRepoAccess, RepoNotAccessibleError } from "./brain-build.js";
|
|
24
|
-
import { buildRepoInChildProcess } from "./brain-build-process.js";
|
|
25
22
|
import { PageStore } from "./pages.js";
|
|
26
23
|
import { WorkQueue } from "./queue.js";
|
|
27
24
|
import { reviewInChildProcess } from "./review-process.js";
|
|
@@ -97,112 +94,6 @@ export function createApp(config, seams = {}) {
|
|
|
97
94
|
log(`${jobKey(decided.job)}: queued${decided.job.fromFork ? " (fork)" : ""}`);
|
|
98
95
|
return send(res, 202, "application/json", JSON.stringify({ queued: true }));
|
|
99
96
|
}
|
|
100
|
-
// Can we read this repository? The cheap question, answered on its own.
|
|
101
|
-
//
|
|
102
|
-
// Same secret as /brain/build, because it is the same caller and the same
|
|
103
|
-
// trust. Kept separate from it because it costs a second where the build
|
|
104
|
-
// costs minutes, and the platform needs the answer before it can decide
|
|
105
|
-
// which screen to show.
|
|
106
|
-
if (req.method === "POST" && url.pathname === "/brain/access") {
|
|
107
|
-
if (!config.brainBuildSecret)
|
|
108
|
-
return send(res, 404, "text/plain", "not found");
|
|
109
|
-
if (!bearerMatches(header(req, "authorization"), config.brainBuildSecret)) {
|
|
110
|
-
return send(res, 401, "text/plain", "unauthorized");
|
|
111
|
-
}
|
|
112
|
-
const body = await readBody(req);
|
|
113
|
-
if (body === null)
|
|
114
|
-
return send(res, 413, "text/plain", "payload too large");
|
|
115
|
-
let ask;
|
|
116
|
-
try {
|
|
117
|
-
ask = JSON.parse(body);
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
return send(res, 400, "text/plain", "bad json");
|
|
121
|
-
}
|
|
122
|
-
if (!ask.owner || !ask.repo) {
|
|
123
|
-
return send(res, 400, "application/json", JSON.stringify({ error: "owner and repo are required" }));
|
|
124
|
-
}
|
|
125
|
-
try {
|
|
126
|
-
const access = await (seams.brainAccess ?? checkRepoAccess)({ owner: ask.owner, repo: ask.repo }, {
|
|
127
|
-
creds: config,
|
|
128
|
-
fetch: fetchImpl,
|
|
129
|
-
api: config.api,
|
|
130
|
-
publicToken: config.publicToken,
|
|
131
|
-
publicOwner: config.publicOwner,
|
|
132
|
-
log,
|
|
133
|
-
now: seams.now,
|
|
134
|
-
});
|
|
135
|
-
// 200 either way: "we cannot see it" is an answer, not a failure, and
|
|
136
|
-
// the platform turns it into a choice rather than an error.
|
|
137
|
-
return send(res, 200, "application/json", JSON.stringify(access));
|
|
138
|
-
}
|
|
139
|
-
catch (e) {
|
|
140
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
141
|
-
log(`brain access ${ask.owner}/${ask.repo} failed: ${msg}`);
|
|
142
|
-
return send(res, 502, "application/json", JSON.stringify({ error: msg }));
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
// Build one repository into a brain, on demand. Called by the platform when
|
|
146
|
-
// someone pastes a repository URL during onboarding — there is no webhook
|
|
147
|
-
// behind it, so it authenticates with a bearer token rather than a payload
|
|
148
|
-
// signature.
|
|
149
|
-
if (req.method === "POST" && url.pathname === "/brain/build") {
|
|
150
|
-
if (!config.brainBuildSecret)
|
|
151
|
-
return send(res, 404, "text/plain", "not found");
|
|
152
|
-
// Compared in constant time for the same reason verifySignature is: this
|
|
153
|
-
// is a long-lived shared secret, and a timing oracle on it is worth more
|
|
154
|
-
// to an attacker than one on a per-payload signature.
|
|
155
|
-
if (!bearerMatches(header(req, "authorization"), config.brainBuildSecret)) {
|
|
156
|
-
return send(res, 401, "text/plain", "unauthorized");
|
|
157
|
-
}
|
|
158
|
-
const body = await readBody(req);
|
|
159
|
-
if (body === null)
|
|
160
|
-
return send(res, 413, "text/plain", "payload too large");
|
|
161
|
-
let job;
|
|
162
|
-
try {
|
|
163
|
-
job = JSON.parse(body);
|
|
164
|
-
}
|
|
165
|
-
catch {
|
|
166
|
-
return send(res, 400, "text/plain", "bad json");
|
|
167
|
-
}
|
|
168
|
-
// brainId/brainToken are optional: without them the digest comes back in
|
|
169
|
-
// the response for the caller to ingest itself. See BrainBuildJob.
|
|
170
|
-
if (!job.owner || !job.repo) {
|
|
171
|
-
return send(res, 400, "application/json", JSON.stringify({ error: "owner and repo are required" }));
|
|
172
|
-
}
|
|
173
|
-
// Synchronous on purpose: the caller is a person waiting on a screen, and
|
|
174
|
-
// the platform's own import job is what makes the SLOW half (extraction and
|
|
175
|
-
// placement) asynchronous. This half is a shallow clone and a graph build.
|
|
176
|
-
try {
|
|
177
|
-
// The deployment's platform URL wins over anything a caller sends, so a
|
|
178
|
-
// request cannot redirect a repository's history to another host.
|
|
179
|
-
if (config.brainBaseUrl)
|
|
180
|
-
job.brainBaseUrl = config.brainBaseUrl;
|
|
181
|
-
// Out of this process by default. A read is minutes of blocking work
|
|
182
|
-
// and the App has to keep answering — including the one-second access
|
|
183
|
-
// question the next person's onboarding is waiting on. A test that
|
|
184
|
-
// injects its own builder still runs in-process, which is what a test
|
|
185
|
-
// wants.
|
|
186
|
-
const built = await (seams.brainBuild ?? buildRepoInChildProcess)(job, {
|
|
187
|
-
creds: config,
|
|
188
|
-
fetch: fetchImpl,
|
|
189
|
-
api: config.api,
|
|
190
|
-
publicToken: config.publicToken,
|
|
191
|
-
publicOwner: config.publicOwner,
|
|
192
|
-
log,
|
|
193
|
-
now: seams.now,
|
|
194
|
-
});
|
|
195
|
-
return send(res, 202, "application/json", JSON.stringify(built));
|
|
196
|
-
}
|
|
197
|
-
catch (e) {
|
|
198
|
-
if (e instanceof RepoNotAccessibleError) {
|
|
199
|
-
return send(res, 404, "application/json", JSON.stringify({ error: e.message, ...e.gap }));
|
|
200
|
-
}
|
|
201
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
202
|
-
log(`brain build ${job.owner}/${job.repo} failed: ${msg}`);
|
|
203
|
-
return send(res, 502, "application/json", JSON.stringify({ error: msg }));
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
97
|
return send(res, 404, "text/plain", "not found");
|
|
207
98
|
}
|
|
208
99
|
if (config.port !== undefined)
|
|
@@ -213,18 +104,6 @@ const header = (req, name) => {
|
|
|
213
104
|
const v = req.headers[name];
|
|
214
105
|
return Array.isArray(v) ? v[0] : v;
|
|
215
106
|
};
|
|
216
|
-
/** Constant-time `Authorization: Bearer <secret>` check. */
|
|
217
|
-
function bearerMatches(headerValue, secret) {
|
|
218
|
-
const prefix = "Bearer ";
|
|
219
|
-
if (!headerValue || !headerValue.startsWith(prefix))
|
|
220
|
-
return false;
|
|
221
|
-
const given = Buffer.from(headerValue.slice(prefix.length));
|
|
222
|
-
const want = Buffer.from(secret);
|
|
223
|
-
// timingSafeEqual throws on a length mismatch, which is itself a leak of the
|
|
224
|
-
// secret's length — but one byte of length is not worth a branch that skips
|
|
225
|
-
// the comparison, and the lengths here are fixed by deployment config.
|
|
226
|
-
return given.length === want.length && timingSafeEqual(given, want);
|
|
227
|
-
}
|
|
228
107
|
function send(res, status, type, body, extra = {}) {
|
|
229
108
|
res.writeHead(status, { "content-type": type, ...extra });
|
|
230
109
|
res.end(body);
|