@ascribed/review 0.2.1-next.9 → 0.3.1-next.18
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 +16 -6
- package/dist/github/index.d.ts +3 -2
- package/dist/github/index.js +1 -0
- package/dist/github/index.js.map +1 -1
- package/dist/github/prompt.d.ts +40 -0
- package/dist/github/prompt.js +115 -0
- package/dist/github/prompt.js.map +1 -0
- package/dist/github/requests.d.ts +4 -0
- package/dist/github/requests.js +5 -1
- package/dist/github/requests.js.map +1 -1
- package/dist/github/transport.d.ts +13 -1
- package/dist/github/transport.js +10 -1
- package/dist/github/transport.js.map +1 -1
- package/dist/marks/index.d.ts +1 -0
- package/dist/marks/index.js +1 -0
- package/dist/marks/index.js.map +1 -1
- package/dist/marks/title.d.ts +8 -0
- package/dist/marks/title.js +17 -0
- package/dist/marks/title.js.map +1 -0
- package/dist/names.d.ts +5 -0
- package/dist/names.js +5 -0
- package/dist/names.js.map +1 -1
- package/dist/overlay/index.d.ts +1 -1
- package/dist/overlay/index.js.map +1 -1
- package/dist/overlay/overlay.js +41 -7
- package/dist/overlay/overlay.js.map +1 -1
- package/dist/overlay/style.d.ts +1 -1
- package/dist/overlay/style.js +24 -16
- package/dist/overlay/style.js.map +1 -1
- package/dist/overlay/types.d.ts +16 -3
- package/dist/overlay/types.js.map +1 -1
- package/dist/place/place.js +3 -2
- package/dist/place/place.js.map +1 -1
- package/dist/prompt/index.d.ts +90 -0
- package/dist/prompt/index.js +301 -0
- package/dist/prompt/index.js.map +1 -0
- package/dist/shapes.d.ts +34 -2
- package/dist/shapes.js.map +1 -1
- package/dist/shared/git.d.ts +11 -0
- package/dist/shared/git.js +24 -1
- package/dist/shared/git.js.map +1 -1
- package/dist/shared/types.d.ts +16 -0
- package/dist/shared/types.js.map +1 -1
- package/package.json +8 -4
- package/src/marks/marks.css +54 -49
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Reviewing [Ascribe](https://github.com/ascribed-dev/ascribe) pages as readers see them: what a change does to each rendered page, and a pull request's review threads beside the blocks they're about.
|
|
4
4
|
|
|
5
|
-
This README is for building a host. To review a pull request with Ascribe, read [Review](https://ascribed-dev.com/
|
|
5
|
+
This README is for building a host. To review a pull request with Ascribe, read [Review](https://ascribed-dev.com/go/review-guide/).
|
|
6
6
|
|
|
7
7
|
Each entry point is separate, so a host bundles only what it uses.
|
|
8
8
|
|
|
@@ -13,7 +13,7 @@ Each entry point is separate, so a host bundles only what it uses.
|
|
|
13
13
|
| `@ascribed/review/github` | Node | Reading and writing a pull request's review threads on GitHub |
|
|
14
14
|
| `@ascribed/review/place` | Node | Placing review threads on the blocks of a page |
|
|
15
15
|
|
|
16
|
-
The rendered page is Ascribe's site output with source anchors (`ascribe build --emit site --anchors`, and always in the editor's page preview), which say where each block is written. The [site-render contract](https://ascribed-dev.com/
|
|
16
|
+
The rendered page is Ascribe's site output with source anchors (`ascribe build --emit site --anchors`, and always in the editor's page preview), which say where each block is written. The [site-render contract](https://ascribed-dev.com/go/7-source-anchors/) describes them.
|
|
17
17
|
|
|
18
18
|
## Marks
|
|
19
19
|
|
|
@@ -32,11 +32,11 @@ Each changed block gets a label (Added, Changed, Removed, Moved) and a bar in th
|
|
|
32
32
|
|
|
33
33
|
The blocks are found by their anchors: the element with exactly a change's anchor, or, when there's none (a paragraph in a tight list item has no element of its own), the smallest anchored element with the same file and includes whose lines contain it.
|
|
34
34
|
|
|
35
|
-
The colors are the `--ascribe-review-*` custom properties at the top of `marks.css`, with light and dark values.
|
|
35
|
+
The colors are the `--ascribe-review-*` custom properties at the top of `marks.css`, with light and dark values. Their names don't change; their defaults are Ascribe's design tokens, shared with `@ascribed/elements`, and a release may change them, noting it in the changelog.
|
|
36
36
|
|
|
37
37
|
## The static report
|
|
38
38
|
|
|
39
|
-
`src/report/` is the script of `ascribe diff --format html`'s report, which draws the list of changed pages and each page with its marks. It isn't an entry point: `pnpm --filter @ascribed/review embed` bundles it with the marks and `@ascribed/elements` into `crates/ascribe-diff/src/html/`, where the binary embeds it, along with one stylesheet made of the element library's, `marks.css`, and `src/report/report.css`. It also writes the script's SHA-256 beside it, which the report's content security policy names as the one script allowed to run. Run it after changing any of them; `test/embedded.test.ts` fails until you do.
|
|
39
|
+
`src/report/` is the script of `ascribe diff --format html`'s report, which draws the list of changed pages and each page with its marks. It isn't an entry point: `pnpm --filter @ascribed/review embed` bundles it with the marks and `@ascribed/elements` into `crates/ascribe-diff/src/html/`, where the binary embeds it, along with one stylesheet made of the element library's, `marks.css`, and `src/report/report.css`. It also writes the script's SHA-256 beside it, which the report's content security policy names as the one script allowed to run. Run it after changing any of them; `test/embedded.test.ts` fails until you do. A page with a `prompt` in the report's data gets **Copy prompt**, which copies it and opens nothing.
|
|
40
40
|
|
|
41
41
|
## `@ascribed/review/overlay`
|
|
42
42
|
|
|
@@ -48,12 +48,22 @@ const overlay = createOverlay({ root: article, host });
|
|
|
48
48
|
|
|
49
49
|
Draws a pull request's threads beside the blocks of `root`, a rendered page with source anchors: in a column beside the page when its container is at least `columnAt` pixels wide (default 600), else as a count on each block that opens its threads. Threads with no block on the page are listed above it, and a bar below it counts unsent comments, with the submit dialog. A reviewer can reply (at once, or with the review), resolve and reopen, comment on any block, and submit or discard the review, all from the keyboard. It draws in shadow roots, so its styles and the page's stay apart; theme it with the `--ascribe-review-*` custom properties `marks.css` declares.
|
|
50
50
|
|
|
51
|
-
The host (`OverlayHost`) supplies the data and does the work: `load()` for the page's threads (`ReviewSession.threads`), its pending review, and the viewer; `commentTarget`, `comment`, `reply`, `resolve`, `submit`, `discard`, and `allThreads`, as `ReviewSession` has them; and `openSource`, `openThread` (a thread on another page), and optionally `openLink` and `
|
|
51
|
+
The host (`OverlayHost`) supplies the data and does the work: `load()` for the page's threads (`ReviewSession.threads`), its pending review, and the viewer; `commentTarget`, `comment`, `reply`, `resolve`, `submit`, `discard`, and `allThreads`, as `ReviewSession` has them; and `openSource`, `openThread` (a thread on another page), and optionally `openLink`, `notify`, and `promptAgent`. With `promptAgent`, each thread's card has **Prompt agent** and the list of every thread **Prompt agent: all open**; the overlay passes the host a `PromptRequest` (`{ kind: "thread", threadId }` or `{ kind: "open-threads" }`, and the hosts' own headers ask for `page-changes` and `fragment-reach`) and builds nothing. A rejection's message is shown as why the prompt couldn't be built. `onDidChange` tells the overlay to read the threads again. The overlay never talks to GitHub itself, so a host can keep the token out of the page.
|
|
52
52
|
|
|
53
53
|
`overlay.refresh()` reads the threads again, `layout()` places them again after the page changed size, `goToThread(id)` goes to one, `showAllComments()` opens the list of every thread, and `dispose()` takes everything away.
|
|
54
54
|
|
|
55
55
|
Comment bodies are other people's Markdown: `renderMarkdown(document, text)` builds a safe subset (paragraphs, emphasis, code, links that open apart from the page, lists, block quotes) as DOM nodes. Raw HTML stays text, links other than web and email ones are dropped, and an image is a link to it, so nothing loads without a click.
|
|
56
56
|
|
|
57
|
+
## `@ascribed/review/prompt`
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { threadPrompt, openThreadsPrompt } from "@ascribed/review/prompt";
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Builds the agent prompts about review threads, in the agents plan's prompt format: `threadPrompt` asks an agent to address one thread, with its place, the block's source lines, and its comments; `openThreadsPrompt` lists every open thread in two lines each. `openThreadsList` isn't a prompt but the answer of an agent's tool (VS Code's `ascribe_review_threads`): every open thread, at most `MAX_LISTED`, with its place, the pages that show it, and all its comments, each cut at `MAX_COMMENT` characters. A comment is other people's text, so it goes in only inside a fence longer than any backtick run in it, under a sentence saying it's data, with its HTML comments taken out first (`stripComments`). A command a prompt names quotes its arguments as a POSIX shell reads them (`shellWord`). A prompt is at most `LIMIT` (5,000) characters, the binary's limit, which a test holds.
|
|
64
|
+
|
|
65
|
+
The builders read nothing. On a host's Node side, `buildThreadsPrompt(context, request)` from `@ascribed/review/github` gathers what they need from a `ReviewSession`, the changed pages, and a `readSource` callback (and `buildThreadsList(context)` does for `openThreadsList`), and `promptProject(projectDir)` finds the project's folder, content root, and `AGENTS.md` as the binary does. A host whose agent runs Ascribe by another command than `ascribe` sets the project's `program`, and the prompts' commands start with it. The prompts about changes come from the binary: `ascribe diff --format prompt`, or the language server's `ascribe/agentPrompt`.
|
|
66
|
+
|
|
57
67
|
## `@ascribed/review/github`
|
|
58
68
|
|
|
59
69
|
```ts
|
|
@@ -127,7 +137,7 @@ interface GitHubTransport {
|
|
|
127
137
|
```
|
|
128
138
|
|
|
129
139
|
- `ghTransport({ host?, command? })` runs `gh api graphql --hostname <host>`, with the query on standard input and each variable as an argument (`-f` for strings, `-F` for numbers, booleans, and `null`). It needs `gh` 2.0.0 or later, signed in to the host (`gh auth login --hostname <host>`).
|
|
130
|
-
- `tokenTransport(getToken, { host?, fetch? })` posts to the host's GraphQL endpoint (`https://api.github.com/graphql`, or `https://<host>/api/graphql`) with the token `getToken` returns, called for each request.
|
|
140
|
+
- `tokenTransport(getToken, { host?, fetch? })` posts to the host's GraphQL endpoint (`https://api.github.com/graphql`, or `https://<host>/api/graphql`) with the token `getToken` returns, called for each request. `getToken` may return a `HostToken`, `{ token, host }`, naming the host the token was issued for: a token for another host is refused (`not-signed-in`) before anything is sent. Redirects aren't followed.
|
|
131
141
|
|
|
132
142
|
Neither stores a token, and neither puts one in an error: anything shaped like a GitHub token is removed from error messages.
|
|
133
143
|
|
package/dist/github/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { contentPrefixOf, createSession, findPullRequest, openReview, type CommentTarget, type LocalState, type OpenReviewOptions, type PendingReview, type PullRequestInfo, type ReviewEvent, type ReviewSession, type SessionOptions, } from "./session.js";
|
|
2
2
|
export { answerRequest, baseRevision, OVERLAY_METHODS, pagesShowing, pullRequestBase, type Answer, type ChangedPageRef, type OverlayMethod, type RequestContext, } from "./requests.js";
|
|
3
|
+
export { buildThreadsList, buildThreadsPrompt, promptProject, type ThreadPromptContext, } from "./prompt.js";
|
|
3
4
|
export { parseRemote, readCheckout, type CheckoutInfo, type RepositoryRef } from "./repository.js";
|
|
4
|
-
export { ghTransport, graphqlUrl, MIN_GH_VERSION, tokenTransport, type GhTransportOptions, type GitHubTransport, type TokenTransportOptions, } from "./transport.js";
|
|
5
|
+
export { ghTransport, graphqlUrl, MIN_GH_VERSION, tokenTransport, type GhTransportOptions, type GitHubTransport, type HostToken, type TokenTransportOptions, } from "./transport.js";
|
|
5
6
|
export { formatSection, marker, parseSections, type MarkedSection } from "./marker.js";
|
|
6
7
|
export { ReviewError, type ReviewErrorCode } from "../shared/errors.js";
|
|
7
|
-
export type { Author, Side, Thread, ThreadComment } from "../shared/types.js";
|
|
8
|
+
export type { Author, PromptRequest, Side, Thread, ThreadComment } from "../shared/types.js";
|
package/dist/github/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Reading and writing a pull request's review threads on GitHub.
|
|
2
2
|
export { contentPrefixOf, createSession, findPullRequest, openReview, } from "./session.js";
|
|
3
3
|
export { answerRequest, baseRevision, OVERLAY_METHODS, pagesShowing, pullRequestBase, } from "./requests.js";
|
|
4
|
+
export { buildThreadsList, buildThreadsPrompt, promptProject, } from "./prompt.js";
|
|
4
5
|
export { parseRemote, readCheckout } from "./repository.js";
|
|
5
6
|
export { ghTransport, graphqlUrl, MIN_GH_VERSION, tokenTransport, } from "./transport.js";
|
|
6
7
|
export { formatSection, marker, parseSections } from "./marker.js";
|
package/dist/github/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/github/index.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,OAAO,EACL,eAAe,EACf,aAAa,EACb,eAAe,EACf,UAAU,GASX,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,eAAe,GAKhB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAyC,MAAM,iBAAiB,CAAC;AACnG,OAAO,EACL,WAAW,EACX,UAAU,EACV,cAAc,EACd,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/github/index.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,OAAO,EACL,eAAe,EACf,aAAa,EACb,eAAe,EACf,UAAU,GASX,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,eAAe,GAKhB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,GAEd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAyC,MAAM,iBAAiB,CAAC;AACnG,OAAO,EACL,WAAW,EACX,UAAU,EACV,cAAc,EACd,cAAc,GAKf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa,EAAsB,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,WAAW,EAAwB,MAAM,qBAAqB,CAAC","sourcesContent":["// Reading and writing a pull request's review threads on GitHub.\nexport {\n contentPrefixOf,\n createSession,\n findPullRequest,\n openReview,\n type CommentTarget,\n type LocalState,\n type OpenReviewOptions,\n type PendingReview,\n type PullRequestInfo,\n type ReviewEvent,\n type ReviewSession,\n type SessionOptions,\n} from \"./session.js\";\nexport {\n answerRequest,\n baseRevision,\n OVERLAY_METHODS,\n pagesShowing,\n pullRequestBase,\n type Answer,\n type ChangedPageRef,\n type OverlayMethod,\n type RequestContext,\n} from \"./requests.js\";\nexport {\n buildThreadsList,\n buildThreadsPrompt,\n promptProject,\n type ThreadPromptContext,\n} from \"./prompt.js\";\nexport { parseRemote, readCheckout, type CheckoutInfo, type RepositoryRef } from \"./repository.js\";\nexport {\n ghTransport,\n graphqlUrl,\n MIN_GH_VERSION,\n tokenTransport,\n type GhTransportOptions,\n type GitHubTransport,\n type HostToken,\n type TokenTransportOptions,\n} from \"./transport.js\";\nexport { formatSection, marker, parseSections, type MarkedSection } from \"./marker.js\";\nexport { ReviewError, type ReviewErrorCode } from \"../shared/errors.js\";\nexport type { Author, PromptRequest, Side, Thread, ThreadComment } from \"../shared/types.js\";\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type PromptProject } from "../prompt/index.js";
|
|
2
|
+
import type { PromptRequest } from "../shared/types.js";
|
|
3
|
+
import { type ChangedPageRef } from "./requests.js";
|
|
4
|
+
import { type ReviewSession } from "./session.js";
|
|
5
|
+
/** What building a prompt about threads needs from its host. */
|
|
6
|
+
export interface ThreadPromptContext {
|
|
7
|
+
session: ReviewSession;
|
|
8
|
+
/** The changed pages, for which pages show a thread's block. */
|
|
9
|
+
changedPages(): Promise<readonly ChangedPageRef[]>;
|
|
10
|
+
/** Where the project is (`promptProject`). */
|
|
11
|
+
project: PromptProject;
|
|
12
|
+
/**
|
|
13
|
+
* A file's text as saved, by content path, and whether an editor has
|
|
14
|
+
* unsaved changes to it; `undefined` when it can't be read.
|
|
15
|
+
*/
|
|
16
|
+
readSource(path: string): Promise<{
|
|
17
|
+
text: string;
|
|
18
|
+
unsaved: boolean;
|
|
19
|
+
} | undefined>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The prompt about one thread, or about every open thread. `undefined` when
|
|
23
|
+
* there's no open thread to prompt about; rejects with a `ReviewError` when
|
|
24
|
+
* the thread isn't on the pull request.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildThreadsPrompt(context: ThreadPromptContext, request: Extract<PromptRequest, {
|
|
27
|
+
kind: "thread" | "open-threads";
|
|
28
|
+
}>): Promise<string | undefined>;
|
|
29
|
+
/**
|
|
30
|
+
* The pull request's open threads, as an agent's tool lists them
|
|
31
|
+
* (`openThreadsList`), with the pages that show each.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildThreadsList(context: Omit<ThreadPromptContext, "readSource">): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Where the project whose `ascribe.toml` is in `projectDir` is, as the
|
|
36
|
+
* binary's prompts find it: its repository is the nearest folder at or above
|
|
37
|
+
* it with a `.git`, and its `AGENTS.md` is the project folder's, or else the
|
|
38
|
+
* repository's.
|
|
39
|
+
*/
|
|
40
|
+
export declare function promptProject(projectDir: string): PromptProject;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Gathering what a prompt about review threads needs, on a host's Node side:
|
|
2
|
+
// the session's threads, the text of a thread's lines, the pages that show
|
|
3
|
+
// it, and where the project is. `../prompt` builds the prompt from them.
|
|
4
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { openThreadsList, openThreadsPrompt, threadPrompt, } from "../prompt/index.js";
|
|
7
|
+
import { ReviewError } from "../shared/errors.js";
|
|
8
|
+
import { pagesShowing } from "./requests.js";
|
|
9
|
+
import { contentPrefixOf } from "./session.js";
|
|
10
|
+
/**
|
|
11
|
+
* The prompt about one thread, or about every open thread. `undefined` when
|
|
12
|
+
* there's no open thread to prompt about; rejects with a `ReviewError` when
|
|
13
|
+
* the thread isn't on the pull request.
|
|
14
|
+
*/
|
|
15
|
+
export async function buildThreadsPrompt(context, request) {
|
|
16
|
+
const threads = await context.session.allThreads();
|
|
17
|
+
if (request.kind === "open-threads") {
|
|
18
|
+
return openThreadsPrompt({
|
|
19
|
+
project: context.project,
|
|
20
|
+
pullRequest: context.session.pullRequest.number,
|
|
21
|
+
threads,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const thread = threads.find((t) => t.id === request.threadId);
|
|
25
|
+
if (thread === undefined) {
|
|
26
|
+
throw new ReviewError("not-found", "That comment isn't on the pull request any more.");
|
|
27
|
+
}
|
|
28
|
+
const onLines = thread.lines !== undefined &&
|
|
29
|
+
thread.detached === undefined &&
|
|
30
|
+
!(thread.kind === "review" && thread.side === "LEFT");
|
|
31
|
+
const [file, pages] = await Promise.all([
|
|
32
|
+
context.readSource(thread.path),
|
|
33
|
+
context.changedPages().catch(() => []),
|
|
34
|
+
]);
|
|
35
|
+
let source;
|
|
36
|
+
if (onLines && thread.lines && file) {
|
|
37
|
+
const lines = file.text.split(/\r?\n/);
|
|
38
|
+
const { first, last } = thread.lines;
|
|
39
|
+
if (last <= lines.length) {
|
|
40
|
+
source = { first, last, text: lines.slice(first - 1, last).join("\n") };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// A fragment's block shows on the pages that include it; a page's on itself.
|
|
44
|
+
const showing = pagesShowing(thread.path, pages);
|
|
45
|
+
const shownOn = showing.some((p) => p.path === thread.path) ? [] : showing.map((p) => p.path);
|
|
46
|
+
return threadPrompt({
|
|
47
|
+
project: context.project,
|
|
48
|
+
thread,
|
|
49
|
+
source,
|
|
50
|
+
unsaved: file?.unsaved ?? false,
|
|
51
|
+
shownOn,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The pull request's open threads, as an agent's tool lists them
|
|
56
|
+
* (`openThreadsList`), with the pages that show each.
|
|
57
|
+
*/
|
|
58
|
+
export async function buildThreadsList(context) {
|
|
59
|
+
const [threads, pages] = await Promise.all([
|
|
60
|
+
context.session.allThreads(),
|
|
61
|
+
context.changedPages().catch(() => []),
|
|
62
|
+
]);
|
|
63
|
+
return openThreadsList({
|
|
64
|
+
project: context.project,
|
|
65
|
+
pullRequest: context.session.pullRequest.number,
|
|
66
|
+
threads,
|
|
67
|
+
shownOn: (file) => pagesShowing(file, pages).map((p) => p.path),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Where the project whose `ascribe.toml` is in `projectDir` is, as the
|
|
72
|
+
* binary's prompts find it: its repository is the nearest folder at or above
|
|
73
|
+
* it with a `.git`, and its `AGENTS.md` is the project folder's, or else the
|
|
74
|
+
* repository's.
|
|
75
|
+
*/
|
|
76
|
+
export function promptProject(projectDir) {
|
|
77
|
+
const real = (p) => {
|
|
78
|
+
try {
|
|
79
|
+
return realpathSync.native(p);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return path.resolve(p);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const dir = real(projectDir);
|
|
86
|
+
let root;
|
|
87
|
+
for (let at = dir;; at = path.dirname(at)) {
|
|
88
|
+
if (existsSync(path.join(at, ".git"))) {
|
|
89
|
+
root = at;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
if (path.dirname(at) === at)
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
const folder = root === undefined ? "" : path.relative(root, dir).split(path.sep).join("/");
|
|
96
|
+
let contentRoot;
|
|
97
|
+
try {
|
|
98
|
+
const base = root ?? dir;
|
|
99
|
+
const contentDir = path.join(base, ...contentPrefixOf(base, dir).split("/"));
|
|
100
|
+
contentRoot = path.relative(dir, contentDir).split(path.sep).join("/");
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// No `ascribe.toml` to read: the content root's default.
|
|
104
|
+
contentRoot = "docs";
|
|
105
|
+
}
|
|
106
|
+
const agents = existsSync(path.join(dir, "AGENTS.md"))
|
|
107
|
+
? folder === ""
|
|
108
|
+
? "AGENTS.md"
|
|
109
|
+
: `${folder}/AGENTS.md`
|
|
110
|
+
: root !== undefined && existsSync(path.join(root, "AGENTS.md"))
|
|
111
|
+
? "AGENTS.md"
|
|
112
|
+
: undefined;
|
|
113
|
+
return { folder, contentRoot, agents };
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/github/prompt.ts"],"names":[],"mappings":"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","sourcesContent":["// Gathering what a prompt about review threads needs, on a host's Node side:\n// the session's threads, the text of a thread's lines, the pages that show\n// it, and where the project is. `../prompt` builds the prompt from them.\nimport { existsSync, realpathSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n openThreadsList,\n openThreadsPrompt,\n threadPrompt,\n type PromptProject,\n} from \"../prompt/index.js\";\nimport { ReviewError } from \"../shared/errors.js\";\nimport type { PromptRequest } from \"../shared/types.js\";\nimport { pagesShowing, type ChangedPageRef } from \"./requests.js\";\nimport { contentPrefixOf, type ReviewSession } from \"./session.js\";\n\n/** What building a prompt about threads needs from its host. */\nexport interface ThreadPromptContext {\n session: ReviewSession;\n /** The changed pages, for which pages show a thread's block. */\n changedPages(): Promise<readonly ChangedPageRef[]>;\n /** Where the project is (`promptProject`). */\n project: PromptProject;\n /**\n * A file's text as saved, by content path, and whether an editor has\n * unsaved changes to it; `undefined` when it can't be read.\n */\n readSource(path: string): Promise<{ text: string; unsaved: boolean } | undefined>;\n}\n\n/**\n * The prompt about one thread, or about every open thread. `undefined` when\n * there's no open thread to prompt about; rejects with a `ReviewError` when\n * the thread isn't on the pull request.\n */\nexport async function buildThreadsPrompt(\n context: ThreadPromptContext,\n request: Extract<PromptRequest, { kind: \"thread\" | \"open-threads\" }>,\n): Promise<string | undefined> {\n const threads = await context.session.allThreads();\n if (request.kind === \"open-threads\") {\n return openThreadsPrompt({\n project: context.project,\n pullRequest: context.session.pullRequest.number,\n threads,\n });\n }\n const thread = threads.find((t) => t.id === request.threadId);\n if (thread === undefined) {\n throw new ReviewError(\"not-found\", \"That comment isn't on the pull request any more.\");\n }\n const onLines =\n thread.lines !== undefined &&\n thread.detached === undefined &&\n !(thread.kind === \"review\" && thread.side === \"LEFT\");\n const [file, pages] = await Promise.all([\n context.readSource(thread.path),\n context.changedPages().catch(() => []),\n ]);\n let source: { first: number; last: number; text: string } | undefined;\n if (onLines && thread.lines && file) {\n const lines = file.text.split(/\\r?\\n/);\n const { first, last } = thread.lines;\n if (last <= lines.length) {\n source = { first, last, text: lines.slice(first - 1, last).join(\"\\n\") };\n }\n }\n // A fragment's block shows on the pages that include it; a page's on itself.\n const showing = pagesShowing(thread.path, pages);\n const shownOn = showing.some((p) => p.path === thread.path) ? [] : showing.map((p) => p.path);\n return threadPrompt({\n project: context.project,\n thread,\n source,\n unsaved: file?.unsaved ?? false,\n shownOn,\n });\n}\n\n/**\n * The pull request's open threads, as an agent's tool lists them\n * (`openThreadsList`), with the pages that show each.\n */\nexport async function buildThreadsList(\n context: Omit<ThreadPromptContext, \"readSource\">,\n): Promise<string> {\n const [threads, pages] = await Promise.all([\n context.session.allThreads(),\n context.changedPages().catch(() => []),\n ]);\n return openThreadsList({\n project: context.project,\n pullRequest: context.session.pullRequest.number,\n threads,\n shownOn: (file) => pagesShowing(file, pages).map((p) => p.path),\n });\n}\n\n/**\n * Where the project whose `ascribe.toml` is in `projectDir` is, as the\n * binary's prompts find it: its repository is the nearest folder at or above\n * it with a `.git`, and its `AGENTS.md` is the project folder's, or else the\n * repository's.\n */\nexport function promptProject(projectDir: string): PromptProject {\n const real = (p: string): string => {\n try {\n return realpathSync.native(p);\n } catch {\n return path.resolve(p);\n }\n };\n const dir = real(projectDir);\n let root: string | undefined;\n for (let at = dir; ; at = path.dirname(at)) {\n if (existsSync(path.join(at, \".git\"))) {\n root = at;\n break;\n }\n if (path.dirname(at) === at) break;\n }\n const folder = root === undefined ? \"\" : path.relative(root, dir).split(path.sep).join(\"/\");\n let contentRoot: string;\n try {\n const base = root ?? dir;\n const contentDir = path.join(base, ...contentPrefixOf(base, dir).split(\"/\"));\n contentRoot = path.relative(dir, contentDir).split(path.sep).join(\"/\");\n } catch {\n // No `ascribe.toml` to read: the content root's default.\n contentRoot = \"docs\";\n }\n const agents = existsSync(path.join(dir, \"AGENTS.md\"))\n ? folder === \"\"\n ? \"AGENTS.md\"\n : `${folder}/AGENTS.md`\n : root !== undefined && existsSync(path.join(root, \"AGENTS.md\"))\n ? \"AGENTS.md\"\n : undefined;\n return { folder, contentRoot, agents };\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PageRef } from "../place/place.js";
|
|
2
|
+
import type { FormattedPiece } from "../shapes.js";
|
|
2
3
|
import type { PullRequestInfo, ReviewSession } from "./session.js";
|
|
3
4
|
/** The overlay's requests, one per `OverlayHost` method that reads or writes threads. */
|
|
4
5
|
export type OverlayMethod = "load" | "commentTarget" | "comment" | "reply" | "allThreads" | "resolve" | "submit" | "discard";
|
|
@@ -9,6 +10,8 @@ export interface ChangedPageRef {
|
|
|
9
10
|
status: "added" | "removed" | "changed";
|
|
10
11
|
because: readonly string[];
|
|
11
12
|
title?: string | null;
|
|
13
|
+
/** The title formatted, when its field sets `inline = "code"`. */
|
|
14
|
+
formatted_title?: readonly FormattedPiece[] | null;
|
|
12
15
|
}
|
|
13
16
|
/** What answering a request needs. */
|
|
14
17
|
export interface RequestContext {
|
|
@@ -48,6 +51,7 @@ export declare function answerRequest(context: RequestContext, method: OverlayMe
|
|
|
48
51
|
export declare function pagesShowing(file: string, pages: readonly ChangedPageRef[]): {
|
|
49
52
|
path: string;
|
|
50
53
|
title: string | null;
|
|
54
|
+
formatted_title: FormattedPiece[] | null;
|
|
51
55
|
}[];
|
|
52
56
|
/**
|
|
53
57
|
* The git revision to compare with for a pull request's base: the remote
|
package/dist/github/requests.js
CHANGED
|
@@ -211,7 +211,11 @@ export function pagesShowing(file, pages) {
|
|
|
211
211
|
.filter((page) => page.status !== "removed")
|
|
212
212
|
.filter((page) => page.path === file || page.because.includes(file))
|
|
213
213
|
.sort((a, b) => Number(b.path === file) - Number(a.path === file))
|
|
214
|
-
.map((page) => ({
|
|
214
|
+
.map((page) => ({
|
|
215
|
+
path: page.path,
|
|
216
|
+
title: page.title ?? null,
|
|
217
|
+
formatted_title: page.formatted_title ? [...page.formatted_title] : null,
|
|
218
|
+
}));
|
|
215
219
|
}
|
|
216
220
|
/**
|
|
217
221
|
* The git revision to compare with for a pull request's base: the remote
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"requests.js","sourceRoot":"","sources":["../../src/github/requests.ts"],"names":[],"mappings":"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","sourcesContent":["// Answering the overlay's requests (`OverlayHost`) from a review session, for\n// a host that keeps GitHub to itself and passes the overlay's requests on as\n// messages: the page preview's extension, or the site preview's dev server.\n// The parameters arrive as untyped messages, so each is checked here.\nimport { anchorKey, parseSource, type Anchor } from \"../place/anchor.js\";\nimport { lineHunks, shiftLine, type Hunk, type Sides } from \"../place/lines.js\";\nimport type { PageRef, PlacedThreads } from \"../place/place.js\";\nimport { ReviewError } from \"../shared/errors.js\";\nimport { gitMaybe } from \"../shared/git.js\";\nimport { parseRemote } from \"./repository.js\";\nimport type { PullRequestInfo, ReviewEvent, ReviewSession } from \"./session.js\";\n\n/** The overlay's requests, one per `OverlayHost` method that reads or writes threads. */\nexport type OverlayMethod =\n \"load\" | \"commentTarget\" | \"comment\" | \"reply\" | \"allThreads\" | \"resolve\" | \"submit\" | \"discard\";\n\nexport const OVERLAY_METHODS: readonly OverlayMethod[] = [\n \"load\",\n \"commentTarget\",\n \"comment\",\n \"reply\",\n \"allThreads\",\n \"resolve\",\n \"submit\",\n \"discard\",\n];\n\n/** A changed page as `ascribe diff` lists it, with its title when the host knows it. */\nexport interface ChangedPageRef {\n path: string;\n status: \"added\" | \"removed\" | \"changed\";\n because: readonly string[];\n title?: string | null;\n}\n\n/** What answering a request needs. */\nexport interface RequestContext {\n session: ReviewSession;\n /** The page the overlay is on: its build and content path. */\n page: { build: string; path: string } | undefined;\n /** The page as the overlay last read it, with its anchors: comments are made on it. */\n lastPage: PageRef | undefined;\n /** The changed pages, for which pages show a thread. */\n changedPages(): Promise<readonly ChangedPageRef[]>;\n /**\n * For a host whose pages are rendered from an editor's unsaved text: a\n * file's text on disk (`saved`) and in the editor (`current`), by content\n * path, or `undefined` when they're the same. The session counts lines on\n * disk, so the page's blocks are moved to their lines there, and back.\n */\n unsaved?(path: string): Promise<{ saved: string; current: string } | undefined>;\n}\n\n/** An answer: the result, the page read (for `load`), and whether the request changed the threads. */\nexport interface Answer {\n result: unknown;\n page?: PageRef;\n changed: boolean;\n}\n\n/** Answers one of the overlay's requests. Rejects with a `ReviewError`. */\nexport async function answerRequest(\n context: RequestContext,\n method: OverlayMethod,\n params: Record<string, unknown>,\n): Promise<Answer> {\n const { session } = context;\n switch (method) {\n case \"load\": {\n const page = context.page;\n if (!page) throw new ReviewError(\"not-found\", \"There's no page to review here.\");\n const shown = anchors(params[\"anchors\"]);\n const edits = await unsavedEdits(context, shown);\n // The page's blocks at their lines on disk, and the way back.\n const back = new Map<string, Anchor>();\n for (const block of shown) {\n const saved = toSaved(edits, block, false);\n if (saved !== undefined && !back.has(anchorKey(saved))) back.set(anchorKey(saved), block);\n }\n const ref: PageRef = {\n build: page.build,\n path: page.path,\n anchors: edits.size === 0 ? shown : [...back.keys()].map((key) => fromKey(key)),\n removed: anchors(params[\"removed\"]),\n };\n const [placed, pending, viewer] = await Promise.all([\n session.threads(ref),\n session.pending(),\n session.viewer(),\n ]);\n const threads: PlacedThreads =\n edits.size === 0\n ? placed\n : {\n ...placed,\n blocks: placed.blocks.map((block) => ({\n ...block,\n anchor: back.get(anchorKey(block.anchor)) ?? block.anchor,\n })),\n };\n const pr = session.pullRequest;\n return {\n result: { pullRequest: { number: pr.number, url: pr.url }, threads, pending, viewer },\n page: ref,\n changed: false,\n };\n }\n case \"commentTarget\": {\n const shown = anchor(params[\"anchor\"]);\n const saved = toSaved(await unsavedEdits(context, [shown]), shown, true);\n if (saved === undefined)\n return { result: { kind: \"push-first\", message: UNSAVED }, changed: false };\n return { result: await session.commentTarget(saved), changed: false };\n }\n case \"allThreads\": {\n const [threads, pages] = await Promise.all([\n session.allThreads(),\n context.changedPages().catch(() => []),\n ]);\n return {\n result: threads.map((thread) => ({ thread, pages: pagesShowing(thread.path, pages) })),\n changed: false,\n };\n }\n case \"comment\": {\n const shown = anchor(params[\"anchor\"]);\n const target = toSaved(await unsavedEdits(context, [shown]), shown, true);\n if (target === undefined) throw new ReviewError(\"push-first\", UNSAVED);\n const page = context.lastPage ?? {\n build: context.page?.build ?? \"\",\n path: context.page?.path ?? \"\",\n anchors: [target],\n };\n const quote = typeof params[\"quote\"] === \"string\" ? params[\"quote\"] : undefined;\n return {\n result: await session.comment(target, text(params[\"body\"]), page, quote),\n changed: true,\n };\n }\n case \"reply\":\n return {\n result: await session.reply(\n text(params[\"threadId\"]),\n text(params[\"body\"]),\n params[\"when\"] === \"now\" ? \"now\" : \"withReview\",\n ),\n changed: true,\n };\n case \"resolve\":\n await session.resolve(text(params[\"threadId\"]), params[\"resolved\"] === true);\n return { result: null, changed: true };\n case \"submit\": {\n const body = typeof params[\"body\"] === \"string\" ? params[\"body\"] : undefined;\n await session.submit(event(params[\"event\"]), body);\n return { result: null, changed: true };\n }\n case \"discard\":\n await session.discard();\n return { result: null, changed: true };\n }\n}\n\n/** A file's unsaved edits: the line changes from the editor's text to the text on disk. */\ninterface Edit {\n hunks: Hunk[];\n sides: Sides;\n}\n\nconst UNSAVED = \"This block has changes that aren't saved. Save the file, then comment.\";\n\n/** The line changes from each shown file's text in the editor to its text on disk, where they differ. */\nasync function unsavedEdits(\n context: RequestContext,\n shown: readonly Anchor[],\n): Promise<Map<string, Edit>> {\n const edits = new Map<string, Edit>();\n if (!context.unsaved) return edits;\n const files = new Set<string>();\n for (const block of shown) {\n const range = parseSource(block.source);\n if (range) files.add(range.path);\n for (const include of block.via) {\n const at = parseInclude(include);\n if (at) files.add(at.path);\n }\n }\n for (const file of files) {\n const text = await context.unsaved(file);\n if (text === undefined) continue;\n const hunks = lineHunks(text.current, text.saved);\n if (hunks.length > 0) {\n edits.set(file, {\n hunks,\n sides: { old: text.current.split(/\\r?\\n/), new: text.saved.split(/\\r?\\n/) },\n });\n }\n }\n return edits;\n}\n\n/**\n * A block's anchor with its lines on disk. `strict` (for commenting) wants\n * every line of the block saved as it is; otherwise an edited block takes\n * the lines its text has on disk. `undefined` when nothing of it is saved.\n */\nfunction toSaved(\n edits: ReadonlyMap<string, Edit>,\n block: Anchor,\n strict: boolean,\n): Anchor | undefined {\n if (edits.size === 0) return block;\n const range = parseSource(block.source);\n if (range === undefined) return block;\n const edit = edits.get(range.path);\n let source = block.source;\n if (edit !== undefined) {\n const lines: number[] = [];\n for (let line = range.first; line <= range.last; line++) {\n const moved = shiftLine(edit.hunks, line, edit.sides);\n if (moved === undefined || moved.replaced) {\n if (strict) return undefined;\n if (moved === undefined) continue;\n }\n lines.push(moved.line);\n }\n if (lines.length === 0) return undefined;\n const colon = block.source.lastIndexOf(\":\");\n source = `${block.source.slice(0, colon)}:${Math.min(...lines)}-${Math.max(...lines)}`;\n }\n const via: string[] = [];\n for (const include of block.via) {\n const at = parseInclude(include);\n const includeEdit = at && edits.get(at.path);\n if (!at || !includeEdit) {\n via.push(include);\n continue;\n }\n const moved = shiftLine(includeEdit.hunks, at.line, includeEdit.sides);\n if (moved === undefined || (strict && moved.replaced)) return undefined;\n via.push(`${include.slice(0, include.lastIndexOf(\":\"))}:${moved.line}`);\n }\n return { source, via };\n}\n\n/** An include, `<path>:<line>`, parsed, with its path decoded. */\nfunction parseInclude(include: string): { path: string; line: number } | undefined {\n const colon = include.lastIndexOf(\":\");\n const line = Number(include.slice(colon + 1));\n if (colon <= 0 || !Number.isInteger(line) || line < 1) return undefined;\n const range = parseSource(`${include.slice(0, colon)}:1-1`);\n return range && { path: range.path, line };\n}\n\n/** The anchor `anchorKey` wrote. */\nfunction fromKey(key: string): Anchor {\n const [source = \"\", ...via] = key.split(\" \");\n return { source, via };\n}\n\n/**\n * The changed pages that show a file (a content path): the file's own page,\n * and the pages that changed through it (an include). `[]` when none does.\n */\nexport function pagesShowing(\n file: string,\n pages: readonly ChangedPageRef[],\n): { path: string; title: string | null }[] {\n return pages\n .filter((page) => page.status !== \"removed\")\n .filter((page) => page.path === file || page.because.includes(file))\n .sort((a, b) => Number(b.path === file) - Number(a.path === file))\n .map((page) => ({ path: page.path, title: page.title ?? null }));\n}\n\n/**\n * The git revision to compare with for a pull request's base: the remote\n * branch, from the remote whose URL is the pull request's repository, when\n * there is one; else the branch's name.\n */\nexport function pullRequestBase(\n remotes: readonly { name: string; repository: string | undefined }[],\n repository: string,\n baseRefName: string,\n): string {\n const remote =\n remotes.find((r) => r.repository === repository && r.name === \"upstream\") ??\n remotes.find((r) => r.repository === repository && r.name === \"origin\") ??\n remotes.find((r) => r.repository === repository);\n return remote ? `${remote.name}/${baseRefName}` : baseRefName;\n}\n\n/**\n * The git revision for the pull request's base in the checkout at `root`:\n * `origin/main` when that remote branch exists, else the branch's name.\n */\nexport async function baseRevision(root: string, pr: PullRequestInfo): Promise<string> {\n const out = (await gitMaybe(root, [\"remote\", \"-v\"]).catch(() => undefined)) ?? \"\";\n const remotes = out\n .split(\"\\n\")\n .map((line) => /^(\\S+)\\s+(\\S+)\\s+\\(fetch\\)$/.exec(line))\n .filter((match) => match !== null)\n .map((match) => {\n const repository = parseRemote(match[2] ?? \"\");\n return { name: match[1] ?? \"\", repository: repository && repoKey(repository) };\n });\n const revision = pullRequestBase(remotes, repoKey(pr.repository), pr.baseRefName);\n if (revision !== pr.baseRefName) {\n const exists = await gitMaybe(root, [\"rev-parse\", \"--verify\", \"--quiet\", revision]).catch(\n () => undefined,\n );\n if (exists !== undefined) return revision;\n }\n return pr.baseRefName;\n}\n\nfunction repoKey(repository: { host: string; owner: string; name: string }): string {\n return `${repository.host}/${repository.owner}/${repository.name}`.toLowerCase();\n}\n\nconst EVENTS: readonly ReviewEvent[] = [\"COMMENT\", \"APPROVE\", \"REQUEST_CHANGES\"];\n\nfunction event(value: unknown): ReviewEvent {\n if (EVENTS.includes(value as ReviewEvent)) return value as ReviewEvent;\n throw new ReviewError(\"refused\", `A review can't be submitted as ${String(value)}.`);\n}\n\nfunction text(value: unknown): string {\n if (typeof value !== \"string\") throw new ReviewError(\"refused\", \"The request is missing text.\");\n return value;\n}\n\nfunction anchor(value: unknown): Anchor {\n const record = value as Partial<Anchor> | null;\n if (typeof record?.source !== \"string\") {\n throw new ReviewError(\"refused\", \"The request is missing a block.\");\n }\n const via = Array.isArray(record.via) ? record.via.filter((v) => typeof v === \"string\") : [];\n return { source: record.source, via };\n}\n\nfunction anchors(value: unknown): Anchor[] {\n return Array.isArray(value) ? value.map(anchor) : [];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"requests.js","sourceRoot":"","sources":["../../src/github/requests.ts"],"names":[],"mappings":"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","sourcesContent":["// Answering the overlay's requests (`OverlayHost`) from a review session, for\n// a host that keeps GitHub to itself and passes the overlay's requests on as\n// messages: the page preview's extension, or the site preview's dev server.\n// The parameters arrive as untyped messages, so each is checked here.\nimport { anchorKey, parseSource, type Anchor } from \"../place/anchor.js\";\nimport { lineHunks, shiftLine, type Hunk, type Sides } from \"../place/lines.js\";\nimport type { PageRef, PlacedThreads } from \"../place/place.js\";\nimport type { FormattedPiece } from \"../shapes.js\";\nimport { ReviewError } from \"../shared/errors.js\";\nimport { gitMaybe } from \"../shared/git.js\";\nimport { parseRemote } from \"./repository.js\";\nimport type { PullRequestInfo, ReviewEvent, ReviewSession } from \"./session.js\";\n\n/** The overlay's requests, one per `OverlayHost` method that reads or writes threads. */\nexport type OverlayMethod =\n \"load\" | \"commentTarget\" | \"comment\" | \"reply\" | \"allThreads\" | \"resolve\" | \"submit\" | \"discard\";\n\nexport const OVERLAY_METHODS: readonly OverlayMethod[] = [\n \"load\",\n \"commentTarget\",\n \"comment\",\n \"reply\",\n \"allThreads\",\n \"resolve\",\n \"submit\",\n \"discard\",\n];\n\n/** A changed page as `ascribe diff` lists it, with its title when the host knows it. */\nexport interface ChangedPageRef {\n path: string;\n status: \"added\" | \"removed\" | \"changed\";\n because: readonly string[];\n title?: string | null;\n /** The title formatted, when its field sets `inline = \"code\"`. */\n formatted_title?: readonly FormattedPiece[] | null;\n}\n\n/** What answering a request needs. */\nexport interface RequestContext {\n session: ReviewSession;\n /** The page the overlay is on: its build and content path. */\n page: { build: string; path: string } | undefined;\n /** The page as the overlay last read it, with its anchors: comments are made on it. */\n lastPage: PageRef | undefined;\n /** The changed pages, for which pages show a thread. */\n changedPages(): Promise<readonly ChangedPageRef[]>;\n /**\n * For a host whose pages are rendered from an editor's unsaved text: a\n * file's text on disk (`saved`) and in the editor (`current`), by content\n * path, or `undefined` when they're the same. The session counts lines on\n * disk, so the page's blocks are moved to their lines there, and back.\n */\n unsaved?(path: string): Promise<{ saved: string; current: string } | undefined>;\n}\n\n/** An answer: the result, the page read (for `load`), and whether the request changed the threads. */\nexport interface Answer {\n result: unknown;\n page?: PageRef;\n changed: boolean;\n}\n\n/** Answers one of the overlay's requests. Rejects with a `ReviewError`. */\nexport async function answerRequest(\n context: RequestContext,\n method: OverlayMethod,\n params: Record<string, unknown>,\n): Promise<Answer> {\n const { session } = context;\n switch (method) {\n case \"load\": {\n const page = context.page;\n if (!page) throw new ReviewError(\"not-found\", \"There's no page to review here.\");\n const shown = anchors(params[\"anchors\"]);\n const edits = await unsavedEdits(context, shown);\n // The page's blocks at their lines on disk, and the way back.\n const back = new Map<string, Anchor>();\n for (const block of shown) {\n const saved = toSaved(edits, block, false);\n if (saved !== undefined && !back.has(anchorKey(saved))) back.set(anchorKey(saved), block);\n }\n const ref: PageRef = {\n build: page.build,\n path: page.path,\n anchors: edits.size === 0 ? shown : [...back.keys()].map((key) => fromKey(key)),\n removed: anchors(params[\"removed\"]),\n };\n const [placed, pending, viewer] = await Promise.all([\n session.threads(ref),\n session.pending(),\n session.viewer(),\n ]);\n const threads: PlacedThreads =\n edits.size === 0\n ? placed\n : {\n ...placed,\n blocks: placed.blocks.map((block) => ({\n ...block,\n anchor: back.get(anchorKey(block.anchor)) ?? block.anchor,\n })),\n };\n const pr = session.pullRequest;\n return {\n result: { pullRequest: { number: pr.number, url: pr.url }, threads, pending, viewer },\n page: ref,\n changed: false,\n };\n }\n case \"commentTarget\": {\n const shown = anchor(params[\"anchor\"]);\n const saved = toSaved(await unsavedEdits(context, [shown]), shown, true);\n if (saved === undefined)\n return { result: { kind: \"push-first\", message: UNSAVED }, changed: false };\n return { result: await session.commentTarget(saved), changed: false };\n }\n case \"allThreads\": {\n const [threads, pages] = await Promise.all([\n session.allThreads(),\n context.changedPages().catch(() => []),\n ]);\n return {\n result: threads.map((thread) => ({ thread, pages: pagesShowing(thread.path, pages) })),\n changed: false,\n };\n }\n case \"comment\": {\n const shown = anchor(params[\"anchor\"]);\n const target = toSaved(await unsavedEdits(context, [shown]), shown, true);\n if (target === undefined) throw new ReviewError(\"push-first\", UNSAVED);\n const page = context.lastPage ?? {\n build: context.page?.build ?? \"\",\n path: context.page?.path ?? \"\",\n anchors: [target],\n };\n const quote = typeof params[\"quote\"] === \"string\" ? params[\"quote\"] : undefined;\n return {\n result: await session.comment(target, text(params[\"body\"]), page, quote),\n changed: true,\n };\n }\n case \"reply\":\n return {\n result: await session.reply(\n text(params[\"threadId\"]),\n text(params[\"body\"]),\n params[\"when\"] === \"now\" ? \"now\" : \"withReview\",\n ),\n changed: true,\n };\n case \"resolve\":\n await session.resolve(text(params[\"threadId\"]), params[\"resolved\"] === true);\n return { result: null, changed: true };\n case \"submit\": {\n const body = typeof params[\"body\"] === \"string\" ? params[\"body\"] : undefined;\n await session.submit(event(params[\"event\"]), body);\n return { result: null, changed: true };\n }\n case \"discard\":\n await session.discard();\n return { result: null, changed: true };\n }\n}\n\n/** A file's unsaved edits: the line changes from the editor's text to the text on disk. */\ninterface Edit {\n hunks: Hunk[];\n sides: Sides;\n}\n\nconst UNSAVED = \"This block has changes that aren't saved. Save the file, then comment.\";\n\n/** The line changes from each shown file's text in the editor to its text on disk, where they differ. */\nasync function unsavedEdits(\n context: RequestContext,\n shown: readonly Anchor[],\n): Promise<Map<string, Edit>> {\n const edits = new Map<string, Edit>();\n if (!context.unsaved) return edits;\n const files = new Set<string>();\n for (const block of shown) {\n const range = parseSource(block.source);\n if (range) files.add(range.path);\n for (const include of block.via) {\n const at = parseInclude(include);\n if (at) files.add(at.path);\n }\n }\n for (const file of files) {\n const text = await context.unsaved(file);\n if (text === undefined) continue;\n const hunks = lineHunks(text.current, text.saved);\n if (hunks.length > 0) {\n edits.set(file, {\n hunks,\n sides: { old: text.current.split(/\\r?\\n/), new: text.saved.split(/\\r?\\n/) },\n });\n }\n }\n return edits;\n}\n\n/**\n * A block's anchor with its lines on disk. `strict` (for commenting) wants\n * every line of the block saved as it is; otherwise an edited block takes\n * the lines its text has on disk. `undefined` when nothing of it is saved.\n */\nfunction toSaved(\n edits: ReadonlyMap<string, Edit>,\n block: Anchor,\n strict: boolean,\n): Anchor | undefined {\n if (edits.size === 0) return block;\n const range = parseSource(block.source);\n if (range === undefined) return block;\n const edit = edits.get(range.path);\n let source = block.source;\n if (edit !== undefined) {\n const lines: number[] = [];\n for (let line = range.first; line <= range.last; line++) {\n const moved = shiftLine(edit.hunks, line, edit.sides);\n if (moved === undefined || moved.replaced) {\n if (strict) return undefined;\n if (moved === undefined) continue;\n }\n lines.push(moved.line);\n }\n if (lines.length === 0) return undefined;\n const colon = block.source.lastIndexOf(\":\");\n source = `${block.source.slice(0, colon)}:${Math.min(...lines)}-${Math.max(...lines)}`;\n }\n const via: string[] = [];\n for (const include of block.via) {\n const at = parseInclude(include);\n const includeEdit = at && edits.get(at.path);\n if (!at || !includeEdit) {\n via.push(include);\n continue;\n }\n const moved = shiftLine(includeEdit.hunks, at.line, includeEdit.sides);\n if (moved === undefined || (strict && moved.replaced)) return undefined;\n via.push(`${include.slice(0, include.lastIndexOf(\":\"))}:${moved.line}`);\n }\n return { source, via };\n}\n\n/** An include, `<path>:<line>`, parsed, with its path decoded. */\nfunction parseInclude(include: string): { path: string; line: number } | undefined {\n const colon = include.lastIndexOf(\":\");\n const line = Number(include.slice(colon + 1));\n if (colon <= 0 || !Number.isInteger(line) || line < 1) return undefined;\n const range = parseSource(`${include.slice(0, colon)}:1-1`);\n return range && { path: range.path, line };\n}\n\n/** The anchor `anchorKey` wrote. */\nfunction fromKey(key: string): Anchor {\n const [source = \"\", ...via] = key.split(\" \");\n return { source, via };\n}\n\n/**\n * The changed pages that show a file (a content path): the file's own page,\n * and the pages that changed through it (an include). `[]` when none does.\n */\nexport function pagesShowing(\n file: string,\n pages: readonly ChangedPageRef[],\n): { path: string; title: string | null; formatted_title: FormattedPiece[] | null }[] {\n return pages\n .filter((page) => page.status !== \"removed\")\n .filter((page) => page.path === file || page.because.includes(file))\n .sort((a, b) => Number(b.path === file) - Number(a.path === file))\n .map((page) => ({\n path: page.path,\n title: page.title ?? null,\n formatted_title: page.formatted_title ? [...page.formatted_title] : null,\n }));\n}\n\n/**\n * The git revision to compare with for a pull request's base: the remote\n * branch, from the remote whose URL is the pull request's repository, when\n * there is one; else the branch's name.\n */\nexport function pullRequestBase(\n remotes: readonly { name: string; repository: string | undefined }[],\n repository: string,\n baseRefName: string,\n): string {\n const remote =\n remotes.find((r) => r.repository === repository && r.name === \"upstream\") ??\n remotes.find((r) => r.repository === repository && r.name === \"origin\") ??\n remotes.find((r) => r.repository === repository);\n return remote ? `${remote.name}/${baseRefName}` : baseRefName;\n}\n\n/**\n * The git revision for the pull request's base in the checkout at `root`:\n * `origin/main` when that remote branch exists, else the branch's name.\n */\nexport async function baseRevision(root: string, pr: PullRequestInfo): Promise<string> {\n const out = (await gitMaybe(root, [\"remote\", \"-v\"]).catch(() => undefined)) ?? \"\";\n const remotes = out\n .split(\"\\n\")\n .map((line) => /^(\\S+)\\s+(\\S+)\\s+\\(fetch\\)$/.exec(line))\n .filter((match) => match !== null)\n .map((match) => {\n const repository = parseRemote(match[2] ?? \"\");\n return { name: match[1] ?? \"\", repository: repository && repoKey(repository) };\n });\n const revision = pullRequestBase(remotes, repoKey(pr.repository), pr.baseRefName);\n if (revision !== pr.baseRefName) {\n const exists = await gitMaybe(root, [\"rev-parse\", \"--verify\", \"--quiet\", revision]).catch(\n () => undefined,\n );\n if (exists !== undefined) return revision;\n }\n return pr.baseRefName;\n}\n\nfunction repoKey(repository: { host: string; owner: string; name: string }): string {\n return `${repository.host}/${repository.owner}/${repository.name}`.toLowerCase();\n}\n\nconst EVENTS: readonly ReviewEvent[] = [\"COMMENT\", \"APPROVE\", \"REQUEST_CHANGES\"];\n\nfunction event(value: unknown): ReviewEvent {\n if (EVENTS.includes(value as ReviewEvent)) return value as ReviewEvent;\n throw new ReviewError(\"refused\", `A review can't be submitted as ${String(value)}.`);\n}\n\nfunction text(value: unknown): string {\n if (typeof value !== \"string\") throw new ReviewError(\"refused\", \"The request is missing text.\");\n return value;\n}\n\nfunction anchor(value: unknown): Anchor {\n const record = value as Partial<Anchor> | null;\n if (typeof record?.source !== \"string\") {\n throw new ReviewError(\"refused\", \"The request is missing a block.\");\n }\n const via = Array.isArray(record.via) ? record.via.filter((v) => typeof v === \"string\") : [];\n return { source: record.source, via };\n}\n\nfunction anchors(value: unknown): Anchor[] {\n return Array.isArray(value) ? value.map(anchor) : [];\n}\n"]}
|
|
@@ -24,10 +24,22 @@ export interface TokenTransportOptions {
|
|
|
24
24
|
/** The `fetch` to use. Defaults to the global one. */
|
|
25
25
|
fetch?: typeof fetch;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* A token, and the host it was issued for. A transport refuses to send it
|
|
29
|
+
* to any other host.
|
|
30
|
+
*/
|
|
31
|
+
export interface HostToken {
|
|
32
|
+
token: string;
|
|
33
|
+
/** The host the sign-in is for: `github.com`, or a GitHub Enterprise Server host. */
|
|
34
|
+
host: string;
|
|
35
|
+
}
|
|
27
36
|
/**
|
|
28
37
|
* A transport that posts to the GraphQL API with `fetch`, using a token from
|
|
29
38
|
* `getToken`, which is called for each request so the host can refresh it.
|
|
39
|
+
*
|
|
40
|
+
* When `getToken` says which host its token is for, a token for another host
|
|
41
|
+
* is refused before anything is sent.
|
|
30
42
|
*/
|
|
31
|
-
export declare function tokenTransport(getToken: () => Promise<string | undefined> | string | undefined, options?: TokenTransportOptions): GitHubTransport;
|
|
43
|
+
export declare function tokenTransport(getToken: () => Promise<string | HostToken | undefined> | string | HostToken | undefined, options?: TokenTransportOptions): GitHubTransport;
|
|
32
44
|
/** The GraphQL endpoint for a host. */
|
|
33
45
|
export declare function graphqlUrl(host: string): string;
|
package/dist/github/transport.js
CHANGED
|
@@ -134,6 +134,9 @@ function rateLimited(retryAfter, detail) {
|
|
|
134
134
|
/**
|
|
135
135
|
* A transport that posts to the GraphQL API with `fetch`, using a token from
|
|
136
136
|
* `getToken`, which is called for each request so the host can refresh it.
|
|
137
|
+
*
|
|
138
|
+
* When `getToken` says which host its token is for, a token for another host
|
|
139
|
+
* is refused before anything is sent.
|
|
137
140
|
*/
|
|
138
141
|
export function tokenTransport(getToken, options = {}) {
|
|
139
142
|
const host = options.host ?? "github.com";
|
|
@@ -141,10 +144,14 @@ export function tokenTransport(getToken, options = {}) {
|
|
|
141
144
|
return {
|
|
142
145
|
async graphql(query, variables) {
|
|
143
146
|
const doFetch = options.fetch ?? globalThis.fetch;
|
|
144
|
-
const
|
|
147
|
+
const answer = await getToken();
|
|
148
|
+
const token = typeof answer === "object" ? answer.token : answer;
|
|
145
149
|
if (token === undefined || token === "") {
|
|
146
150
|
throw new ReviewError("not-signed-in", `There's no GitHub sign-in for ${host}.`);
|
|
147
151
|
}
|
|
152
|
+
if (typeof answer === "object" && answer.host.toLowerCase() !== host.toLowerCase()) {
|
|
153
|
+
throw new ReviewError("not-signed-in", `The GitHub sign-in is for ${answer.host}, not ${host}, so it isn't sent there.`);
|
|
154
|
+
}
|
|
148
155
|
let response;
|
|
149
156
|
try {
|
|
150
157
|
response = await doFetch(url, {
|
|
@@ -156,6 +163,8 @@ export function tokenTransport(getToken, options = {}) {
|
|
|
156
163
|
"user-agent": "ascribe-review",
|
|
157
164
|
},
|
|
158
165
|
body: JSON.stringify({ query, variables }),
|
|
166
|
+
// A redirect could take the token to another host.
|
|
167
|
+
redirect: "error",
|
|
159
168
|
});
|
|
160
169
|
}
|
|
161
170
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/github/transport.ts"],"names":[],"mappings":"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","sourcesContent":["// How requests reach GitHub's GraphQL API: through the GitHub CLI, or with a\n// token the host supplies. Neither stores a token or puts one in an error.\nimport { spawn } from \"node:child_process\";\nimport { ReviewError, scrub } from \"../shared/errors.js\";\n\n/** Sends one GraphQL request and resolves with its `data`. */\nexport interface GitHubTransport {\n graphql<T>(query: string, variables: Record<string, unknown>): Promise<T>;\n}\n\n/** The oldest `gh` this package runs. */\nexport const MIN_GH_VERSION = \"2.0.0\";\n\n/** Options for `ghTransport`. */\nexport interface GhTransportOptions {\n /** The GitHub host: `github.com`, or a GitHub Enterprise Server host. */\n host?: string;\n /** The command to run. Defaults to `gh`, found on the path. */\n command?: string;\n}\n\n/**\n * A transport that runs `gh api graphql` with a fixed argument list: the\n * query on standard input, each variable as an argument. `gh` uses its own\n * sign-in for the host.\n */\nexport function ghTransport(options: GhTransportOptions = {}): GitHubTransport {\n const host = options.host ?? \"github.com\";\n const command = options.command ?? \"gh\";\n let checked: Promise<void> | undefined;\n return {\n async graphql<T>(query: string, variables: Record<string, unknown>): Promise<T> {\n checked ??= checkGh(command);\n try {\n await checked;\n } catch (error) {\n checked = undefined;\n throw error;\n }\n const args = [\"api\", \"graphql\", \"--hostname\", host, \"-F\", \"query=@-\"];\n for (const [name, value] of Object.entries(variables)) {\n if (value === undefined) continue;\n if (typeof value === \"string\") args.push(\"-f\", `${name}=${value}`);\n else if (typeof value === \"number\" || typeof value === \"boolean\" || value === null) {\n args.push(\"-F\", `${name}=${String(value)}`);\n } else {\n throw new TypeError(`ghTransport can't pass the variable ${name}: ${typeof value}`);\n }\n }\n const result = await run(command, args, query);\n const body = parseBody(result.stdout);\n if (result.code === 0 && body !== undefined && body.errors === undefined) {\n return body.data as T;\n }\n throw ghError(result, body, host);\n },\n };\n}\n\nasync function checkGh(command: string): Promise<void> {\n const result = await run(command, [\"--version\"], \"\");\n const version = /gh version (\\d+)\\.(\\d+)\\.(\\d+)/.exec(result.stdout);\n if (result.code !== 0 || version === null) {\n throw new ReviewError(\"gh-too-old\", \"Couldn't read the GitHub CLI's version (`gh --version`).\");\n }\n const found = version.slice(1, 4).map(Number);\n const min = MIN_GH_VERSION.split(\".\").map(Number);\n for (let i = 0; i < 3; i++) {\n const a = found[i] ?? 0;\n const b = min[i] ?? 0;\n if (a > b) return;\n if (a < b) {\n throw new ReviewError(\n \"gh-too-old\",\n `The GitHub CLI is version ${found.join(\".\")}; review needs ${MIN_GH_VERSION} or later. Update it from https://cli.github.com.`,\n );\n }\n }\n}\n\ninterface RunResult {\n code: number;\n stdout: string;\n stderr: string;\n}\n\nfunction run(command: string, args: string[], input: string): Promise<RunResult> {\n return new Promise((resolve, reject) => {\n const child = spawn(command, args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: { ...process.env, GH_PROMPT_DISABLED: \"1\", GH_NO_UPDATE_NOTIFIER: \"1\", NO_COLOR: \"1\" },\n windowsHide: true,\n });\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on(\"data\", (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => stderr.push(chunk));\n child.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\") {\n reject(\n new ReviewError(\n \"gh-missing\",\n \"The GitHub CLI (`gh`) isn't on the path. Install it from https://cli.github.com and run `gh auth login`.\",\n { cause: error },\n ),\n );\n } else {\n reject(\n new ReviewError(\"network\", `Couldn't run \\`gh\\`: ${error.message}`, { cause: error }),\n );\n }\n });\n child.on(\"close\", (code) => {\n resolve({\n code: code ?? 1,\n stdout: Buffer.concat(stdout).toString(\"utf8\"),\n stderr: Buffer.concat(stderr).toString(\"utf8\"),\n });\n });\n // gh may exit before reading its input; the exit code says what happened.\n child.stdin.on(\"error\", () => undefined);\n child.stdin.end(input);\n });\n}\n\ninterface GraphQLError {\n type?: string;\n message?: string;\n}\n\ninterface GraphQLBody {\n data?: unknown;\n errors?: GraphQLError[];\n message?: string;\n}\n\nfunction parseBody(text: string): GraphQLBody | undefined {\n try {\n const value: unknown = JSON.parse(text);\n return typeof value === \"object\" && value !== null ? (value as GraphQLBody) : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction ghError(result: RunResult, body: GraphQLBody | undefined, host: string): ReviewError {\n const stderr = result.stderr.replace(/^gh: /gm, \"\").trim();\n // gh exits with 4 when a command needs authentication it doesn't have.\n if (\n result.code === 4 ||\n /gh auth login|not logged in|HTTP 401|Bad credentials/i.test(result.stderr)\n ) {\n return new ReviewError(\n \"not-signed-in\",\n `The GitHub CLI isn't signed in to ${host}. Run \\`gh auth login --hostname ${host}\\`.`,\n );\n }\n if (body?.errors !== undefined) return graphqlError(body.errors);\n if (/rate limit/i.test(stderr)) return rateLimited(undefined, stderr);\n return new ReviewError(\n \"refused\",\n `GitHub refused the request: ${stderr || body?.message || `gh exited with ${result.code}`}`,\n );\n}\n\nfunction graphqlError(errors: GraphQLError[]): ReviewError {\n const message = errors.map((error) => error.message ?? \"unknown error\").join(\"; \");\n if (errors.some((error) => error.type === \"RATE_LIMITED\")) return rateLimited(undefined, message);\n if (errors.every((error) => error.type === \"NOT_FOUND\")) {\n return new ReviewError(\"not-found\", `GitHub couldn't find it: ${message}`);\n }\n return new ReviewError(\"refused\", `GitHub refused the request: ${message}`);\n}\n\nfunction rateLimited(retryAfter: number | undefined, detail: string): ReviewError {\n const wait =\n retryAfter === undefined\n ? \"Wait a minute\"\n : `Wait ${retryAfter} second${retryAfter === 1 ? \"\" : \"s\"}`;\n return new ReviewError(\n \"rate-limited\",\n `GitHub is limiting how fast requests can be made. ${wait} and try again. (${detail})`,\n retryAfter === undefined ? {} : { retryAfter },\n );\n}\n\n/** Options for `tokenTransport`. */\nexport interface TokenTransportOptions {\n /** The GitHub host: `github.com`, or a GitHub Enterprise Server host. */\n host?: string;\n /** The `fetch` to use. Defaults to the global one. */\n fetch?: typeof fetch;\n}\n\n/**\n * A transport that posts to the GraphQL API with `fetch`, using a token from\n * `getToken`, which is called for each request so the host can refresh it.\n */\nexport function tokenTransport(\n getToken: () => Promise<string | undefined> | string | undefined,\n options: TokenTransportOptions = {},\n): GitHubTransport {\n const host = options.host ?? \"github.com\";\n const url = graphqlUrl(host);\n return {\n async graphql<T>(query: string, variables: Record<string, unknown>): Promise<T> {\n const doFetch = options.fetch ?? globalThis.fetch;\n const token = await getToken();\n if (token === undefined || token === \"\") {\n throw new ReviewError(\"not-signed-in\", `There's no GitHub sign-in for ${host}.`);\n }\n let response: Response;\n try {\n response = await doFetch(url, {\n method: \"POST\",\n headers: {\n authorization: `bearer ${token}`,\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n \"user-agent\": \"ascribe-review\",\n },\n body: JSON.stringify({ query, variables }),\n });\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n throw new ReviewError(\"network\", `Couldn't reach ${host}: ${detail}`, { cause: error });\n }\n const text = await response.text();\n const body = parseBody(text);\n if (response.status === 401) {\n throw new ReviewError(\"not-signed-in\", `${host} didn't accept the GitHub sign-in.`);\n }\n if (response.status === 403 || response.status === 429) {\n const retryAfter = retryAfterOf(response.headers);\n if (\n response.status === 429 ||\n retryAfter !== undefined ||\n /rate limit/i.test(body?.message ?? text)\n ) {\n throw rateLimited(retryAfter, scrub(body?.message ?? `HTTP ${response.status}`));\n }\n }\n if (!response.ok) {\n throw new ReviewError(\n \"refused\",\n `GitHub refused the request (HTTP ${response.status}): ${scrub(body?.message ?? text.slice(0, 200))}`,\n );\n }\n if (body === undefined) {\n throw new ReviewError(\"network\", `${host} answered with something that isn't JSON.`);\n }\n if (body.errors !== undefined && body.errors.length > 0) throw graphqlError(body.errors);\n return body.data as T;\n },\n };\n}\n\n/** The GraphQL endpoint for a host. */\nexport function graphqlUrl(host: string): string {\n return host === \"github.com\" ? \"https://api.github.com/graphql\" : `https://${host}/api/graphql`;\n}\n\nfunction retryAfterOf(headers: Headers): number | undefined {\n const after = headers.get(\"retry-after\");\n if (after !== null && /^\\d+$/.test(after)) return Number(after);\n if (headers.get(\"x-ratelimit-remaining\") === \"0\") {\n const reset = Number(headers.get(\"x-ratelimit-reset\"));\n if (Number.isFinite(reset) && reset > 0) {\n return Math.max(0, Math.ceil(reset - Date.now() / 1000));\n }\n }\n return undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/github/transport.ts"],"names":[],"mappings":"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","sourcesContent":["// How requests reach GitHub's GraphQL API: through the GitHub CLI, or with a\n// token the host supplies. Neither stores a token or puts one in an error.\nimport { spawn } from \"node:child_process\";\nimport { ReviewError, scrub } from \"../shared/errors.js\";\n\n/** Sends one GraphQL request and resolves with its `data`. */\nexport interface GitHubTransport {\n graphql<T>(query: string, variables: Record<string, unknown>): Promise<T>;\n}\n\n/** The oldest `gh` this package runs. */\nexport const MIN_GH_VERSION = \"2.0.0\";\n\n/** Options for `ghTransport`. */\nexport interface GhTransportOptions {\n /** The GitHub host: `github.com`, or a GitHub Enterprise Server host. */\n host?: string;\n /** The command to run. Defaults to `gh`, found on the path. */\n command?: string;\n}\n\n/**\n * A transport that runs `gh api graphql` with a fixed argument list: the\n * query on standard input, each variable as an argument. `gh` uses its own\n * sign-in for the host.\n */\nexport function ghTransport(options: GhTransportOptions = {}): GitHubTransport {\n const host = options.host ?? \"github.com\";\n const command = options.command ?? \"gh\";\n let checked: Promise<void> | undefined;\n return {\n async graphql<T>(query: string, variables: Record<string, unknown>): Promise<T> {\n checked ??= checkGh(command);\n try {\n await checked;\n } catch (error) {\n checked = undefined;\n throw error;\n }\n const args = [\"api\", \"graphql\", \"--hostname\", host, \"-F\", \"query=@-\"];\n for (const [name, value] of Object.entries(variables)) {\n if (value === undefined) continue;\n if (typeof value === \"string\") args.push(\"-f\", `${name}=${value}`);\n else if (typeof value === \"number\" || typeof value === \"boolean\" || value === null) {\n args.push(\"-F\", `${name}=${String(value)}`);\n } else {\n throw new TypeError(`ghTransport can't pass the variable ${name}: ${typeof value}`);\n }\n }\n const result = await run(command, args, query);\n const body = parseBody(result.stdout);\n if (result.code === 0 && body !== undefined && body.errors === undefined) {\n return body.data as T;\n }\n throw ghError(result, body, host);\n },\n };\n}\n\nasync function checkGh(command: string): Promise<void> {\n const result = await run(command, [\"--version\"], \"\");\n const version = /gh version (\\d+)\\.(\\d+)\\.(\\d+)/.exec(result.stdout);\n if (result.code !== 0 || version === null) {\n throw new ReviewError(\"gh-too-old\", \"Couldn't read the GitHub CLI's version (`gh --version`).\");\n }\n const found = version.slice(1, 4).map(Number);\n const min = MIN_GH_VERSION.split(\".\").map(Number);\n for (let i = 0; i < 3; i++) {\n const a = found[i] ?? 0;\n const b = min[i] ?? 0;\n if (a > b) return;\n if (a < b) {\n throw new ReviewError(\n \"gh-too-old\",\n `The GitHub CLI is version ${found.join(\".\")}; review needs ${MIN_GH_VERSION} or later. Update it from https://cli.github.com.`,\n );\n }\n }\n}\n\ninterface RunResult {\n code: number;\n stdout: string;\n stderr: string;\n}\n\nfunction run(command: string, args: string[], input: string): Promise<RunResult> {\n return new Promise((resolve, reject) => {\n const child = spawn(command, args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: { ...process.env, GH_PROMPT_DISABLED: \"1\", GH_NO_UPDATE_NOTIFIER: \"1\", NO_COLOR: \"1\" },\n windowsHide: true,\n });\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on(\"data\", (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => stderr.push(chunk));\n child.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\") {\n reject(\n new ReviewError(\n \"gh-missing\",\n \"The GitHub CLI (`gh`) isn't on the path. Install it from https://cli.github.com and run `gh auth login`.\",\n { cause: error },\n ),\n );\n } else {\n reject(\n new ReviewError(\"network\", `Couldn't run \\`gh\\`: ${error.message}`, { cause: error }),\n );\n }\n });\n child.on(\"close\", (code) => {\n resolve({\n code: code ?? 1,\n stdout: Buffer.concat(stdout).toString(\"utf8\"),\n stderr: Buffer.concat(stderr).toString(\"utf8\"),\n });\n });\n // gh may exit before reading its input; the exit code says what happened.\n child.stdin.on(\"error\", () => undefined);\n child.stdin.end(input);\n });\n}\n\ninterface GraphQLError {\n type?: string;\n message?: string;\n}\n\ninterface GraphQLBody {\n data?: unknown;\n errors?: GraphQLError[];\n message?: string;\n}\n\nfunction parseBody(text: string): GraphQLBody | undefined {\n try {\n const value: unknown = JSON.parse(text);\n return typeof value === \"object\" && value !== null ? (value as GraphQLBody) : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction ghError(result: RunResult, body: GraphQLBody | undefined, host: string): ReviewError {\n const stderr = result.stderr.replace(/^gh: /gm, \"\").trim();\n // gh exits with 4 when a command needs authentication it doesn't have.\n if (\n result.code === 4 ||\n /gh auth login|not logged in|HTTP 401|Bad credentials/i.test(result.stderr)\n ) {\n return new ReviewError(\n \"not-signed-in\",\n `The GitHub CLI isn't signed in to ${host}. Run \\`gh auth login --hostname ${host}\\`.`,\n );\n }\n if (body?.errors !== undefined) return graphqlError(body.errors);\n if (/rate limit/i.test(stderr)) return rateLimited(undefined, stderr);\n return new ReviewError(\n \"refused\",\n `GitHub refused the request: ${stderr || body?.message || `gh exited with ${result.code}`}`,\n );\n}\n\nfunction graphqlError(errors: GraphQLError[]): ReviewError {\n const message = errors.map((error) => error.message ?? \"unknown error\").join(\"; \");\n if (errors.some((error) => error.type === \"RATE_LIMITED\")) return rateLimited(undefined, message);\n if (errors.every((error) => error.type === \"NOT_FOUND\")) {\n return new ReviewError(\"not-found\", `GitHub couldn't find it: ${message}`);\n }\n return new ReviewError(\"refused\", `GitHub refused the request: ${message}`);\n}\n\nfunction rateLimited(retryAfter: number | undefined, detail: string): ReviewError {\n const wait =\n retryAfter === undefined\n ? \"Wait a minute\"\n : `Wait ${retryAfter} second${retryAfter === 1 ? \"\" : \"s\"}`;\n return new ReviewError(\n \"rate-limited\",\n `GitHub is limiting how fast requests can be made. ${wait} and try again. (${detail})`,\n retryAfter === undefined ? {} : { retryAfter },\n );\n}\n\n/** Options for `tokenTransport`. */\nexport interface TokenTransportOptions {\n /** The GitHub host: `github.com`, or a GitHub Enterprise Server host. */\n host?: string;\n /** The `fetch` to use. Defaults to the global one. */\n fetch?: typeof fetch;\n}\n\n/**\n * A token, and the host it was issued for. A transport refuses to send it\n * to any other host.\n */\nexport interface HostToken {\n token: string;\n /** The host the sign-in is for: `github.com`, or a GitHub Enterprise Server host. */\n host: string;\n}\n\n/**\n * A transport that posts to the GraphQL API with `fetch`, using a token from\n * `getToken`, which is called for each request so the host can refresh it.\n *\n * When `getToken` says which host its token is for, a token for another host\n * is refused before anything is sent.\n */\nexport function tokenTransport(\n getToken: () => Promise<string | HostToken | undefined> | string | HostToken | undefined,\n options: TokenTransportOptions = {},\n): GitHubTransport {\n const host = options.host ?? \"github.com\";\n const url = graphqlUrl(host);\n return {\n async graphql<T>(query: string, variables: Record<string, unknown>): Promise<T> {\n const doFetch = options.fetch ?? globalThis.fetch;\n const answer = await getToken();\n const token = typeof answer === \"object\" ? answer.token : answer;\n if (token === undefined || token === \"\") {\n throw new ReviewError(\"not-signed-in\", `There's no GitHub sign-in for ${host}.`);\n }\n if (typeof answer === \"object\" && answer.host.toLowerCase() !== host.toLowerCase()) {\n throw new ReviewError(\n \"not-signed-in\",\n `The GitHub sign-in is for ${answer.host}, not ${host}, so it isn't sent there.`,\n );\n }\n let response: Response;\n try {\n response = await doFetch(url, {\n method: \"POST\",\n headers: {\n authorization: `bearer ${token}`,\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n \"user-agent\": \"ascribe-review\",\n },\n body: JSON.stringify({ query, variables }),\n // A redirect could take the token to another host.\n redirect: \"error\",\n });\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n throw new ReviewError(\"network\", `Couldn't reach ${host}: ${detail}`, { cause: error });\n }\n const text = await response.text();\n const body = parseBody(text);\n if (response.status === 401) {\n throw new ReviewError(\"not-signed-in\", `${host} didn't accept the GitHub sign-in.`);\n }\n if (response.status === 403 || response.status === 429) {\n const retryAfter = retryAfterOf(response.headers);\n if (\n response.status === 429 ||\n retryAfter !== undefined ||\n /rate limit/i.test(body?.message ?? text)\n ) {\n throw rateLimited(retryAfter, scrub(body?.message ?? `HTTP ${response.status}`));\n }\n }\n if (!response.ok) {\n throw new ReviewError(\n \"refused\",\n `GitHub refused the request (HTTP ${response.status}): ${scrub(body?.message ?? text.slice(0, 200))}`,\n );\n }\n if (body === undefined) {\n throw new ReviewError(\"network\", `${host} answered with something that isn't JSON.`);\n }\n if (body.errors !== undefined && body.errors.length > 0) throw graphqlError(body.errors);\n return body.data as T;\n },\n };\n}\n\n/** The GraphQL endpoint for a host. */\nexport function graphqlUrl(host: string): string {\n return host === \"github.com\" ? \"https://api.github.com/graphql\" : `https://${host}/api/graphql`;\n}\n\nfunction retryAfterOf(headers: Headers): number | undefined {\n const after = headers.get(\"retry-after\");\n if (after !== null && /^\\d+$/.test(after)) return Number(after);\n if (headers.get(\"x-ratelimit-remaining\") === \"0\") {\n const reset = Number(headers.get(\"x-ratelimit-reset\"));\n if (Number.isFinite(reset) && reset > 0) {\n return Math.max(0, Math.ceil(reset - Date.now() / 1000));\n }\n }\n return undefined;\n}\n"]}
|
package/dist/marks/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Anchor, Change } from "../shapes.js";
|
|
2
2
|
/** A block's change, where it's written, and its words, as `ascribe diff --format json` writes them. */
|
|
3
3
|
export type { Anchor, Change, Words } from "../shapes.js";
|
|
4
|
+
export { titleNodes, type FormattedPiece, type PieceKind } from "./title.js";
|
|
4
5
|
/** What the page shows: its changes, the page as it will be, or as it was. */
|
|
5
6
|
export type Show = "changes" | "will" | "was";
|
|
6
7
|
export interface MarkOptions {
|