@ascribed/review 0.2.1-next.13 → 0.2.1-next.15
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 +3 -3
- package/dist/github/index.d.ts +1 -1
- package/dist/github/index.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/shared/git.d.ts +11 -0
- package/dist/shared/git.js +24 -1
- package/dist/shared/git.js.map +1 -1
- package/package.json +3 -3
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/guides/review/).
|
|
5
|
+
This README is for building a host. To review a pull request with Ascribe, read [Review](https://ascribed-dev.com/docs/guides/review/).
|
|
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/contracts/site-render/#7-source-anchors) describes them.
|
|
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/docs/contracts/site-render/#7-source-anchors) describes them.
|
|
17
17
|
|
|
18
18
|
## Marks
|
|
19
19
|
|
|
@@ -137,7 +137,7 @@ interface GitHubTransport {
|
|
|
137
137
|
```
|
|
138
138
|
|
|
139
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>`).
|
|
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.
|
|
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.
|
|
141
141
|
|
|
142
142
|
Neither stores a token, and neither puts one in an error: anything shaped like a GitHub token is removed from error messages.
|
|
143
143
|
|
package/dist/github/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { contentPrefixOf, createSession, findPullRequest, openReview, type Comme
|
|
|
2
2
|
export { answerRequest, baseRevision, OVERLAY_METHODS, pagesShowing, pullRequestBase, type Answer, type ChangedPageRef, type OverlayMethod, type RequestContext, } from "./requests.js";
|
|
3
3
|
export { buildThreadsList, buildThreadsPrompt, promptProject, type ThreadPromptContext, } from "./prompt.js";
|
|
4
4
|
export { parseRemote, readCheckout, type CheckoutInfo, type RepositoryRef } from "./repository.js";
|
|
5
|
-
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";
|
|
6
6
|
export { formatSection, marker, parseSections, type MarkedSection } from "./marker.js";
|
|
7
7
|
export { ReviewError, type ReviewErrorCode } from "../shared/errors.js";
|
|
8
8
|
export type { Author, PromptRequest, Side, Thread, ThreadComment } from "../shared/types.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,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,
|
|
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"]}
|
|
@@ -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/shared/git.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The options every `git` Ascribe starts runs with, each as `-c <option>`, so
|
|
3
|
+
* that reading a repository runs nothing it supplies: `ascribe_core::git`'s
|
|
4
|
+
* `OPTIONS`, which says why each is there. `test/git.test.ts` compares the two.
|
|
5
|
+
*/
|
|
6
|
+
export declare const OPTIONS: readonly string[];
|
|
7
|
+
/**
|
|
8
|
+
* The environment variables cleared for every `git` Ascribe starts, each of
|
|
9
|
+
* which would point it at another repository: `ascribe_core::git`'s `CLEARED`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CLEARED: readonly string[];
|
|
1
12
|
/** Runs `git` in `cwd` and resolves with its standard output. */
|
|
2
13
|
export declare function git(cwd: string, args: readonly string[]): Promise<string>;
|
|
3
14
|
/** Like `git`, but resolves with `undefined` instead of rejecting when git exits non-zero. */
|
package/dist/shared/git.js
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
// Running `git` with a fixed argument list, never through a shell.
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { ReviewError } from "./errors.js";
|
|
4
|
+
/**
|
|
5
|
+
* The options every `git` Ascribe starts runs with, each as `-c <option>`, so
|
|
6
|
+
* that reading a repository runs nothing it supplies: `ascribe_core::git`'s
|
|
7
|
+
* `OPTIONS`, which says why each is there. `test/git.test.ts` compares the two.
|
|
8
|
+
*/
|
|
9
|
+
export const OPTIONS = ["safe.bareRepository=explicit", "core.fsmonitor=false"];
|
|
10
|
+
/**
|
|
11
|
+
* The environment variables cleared for every `git` Ascribe starts, each of
|
|
12
|
+
* which would point it at another repository: `ascribe_core::git`'s `CLEARED`.
|
|
13
|
+
*/
|
|
14
|
+
export const CLEARED = [
|
|
15
|
+
"GIT_DIR",
|
|
16
|
+
"GIT_WORK_TREE",
|
|
17
|
+
"GIT_INDEX_FILE",
|
|
18
|
+
"GIT_OBJECT_DIRECTORY",
|
|
19
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
20
|
+
"GIT_NAMESPACE",
|
|
21
|
+
"GIT_COMMON_DIR",
|
|
22
|
+
];
|
|
23
|
+
/** This process's environment without `CLEARED`. */
|
|
24
|
+
function environment() {
|
|
25
|
+
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !CLEARED.includes(name)));
|
|
26
|
+
}
|
|
4
27
|
/** Runs `git` in `cwd` and resolves with its standard output. */
|
|
5
28
|
export function git(cwd, args) {
|
|
6
29
|
return new Promise((resolve, reject) => {
|
|
7
|
-
execFile("git", ["-c", "core.quotepath=off", ...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: "utf8" }, (error, stdout, stderr) => {
|
|
30
|
+
execFile("git", [...OPTIONS.flatMap((option) => ["-c", option]), "-c", "core.quotepath=off", ...args], { cwd, env: environment(), maxBuffer: 256 * 1024 * 1024, encoding: "utf8" }, (error, stdout, stderr) => {
|
|
8
31
|
if (error === null) {
|
|
9
32
|
resolve(stdout);
|
|
10
33
|
return;
|
package/dist/shared/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/shared/git.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,iEAAiE;AACjE,MAAM,UAAU,GAAG,CAAC,GAAW,EAAE,IAAuB;IACtD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CACN,KAAK,EACL,CAAC,IAAI,EAAE,oBAAoB,EAAE,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/shared/git.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAsB,CAAC,8BAA8B,EAAE,sBAAsB,CAAC,CAAC;AAEnG;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAsB;IACxC,SAAS;IACT,eAAe;IACf,gBAAgB;IAChB,sBAAsB;IACtB,kCAAkC;IAClC,eAAe;IACf,gBAAgB;CACjB,CAAC;AAEF,oDAAoD;AACpD,SAAS,WAAW;IAClB,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CACxE,CAAC;AACJ,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,GAAG,CAAC,GAAW,EAAE,IAAuB;IACtD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CACN,KAAK,EACL,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,GAAG,IAAI,CAAC,EACrF,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAC3E,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,OAAO,CAAC,MAAM,CAAC,CAAC;gBAChB,OAAO;YACT,CAAC;YACD,MAAM,OAAO,GAAI,KAA+B,CAAC,IAAI,KAAK,QAAQ,CAAC;YACnE,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,OAAO,CAAC;YAC9C,MAAM,CACJ,IAAI,WAAW,CACb,KAAK,EACL,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,MAAM,EAAE,EACpF,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CACF,CAAC;QACJ,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAW,EAAE,IAAuB;IACjE,IAAI,CAAC;QACH,OAAO,MAAM,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,WAAW,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC;YAAE,MAAM,KAAK,CAAC;QACzF,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC","sourcesContent":["// Running `git` with a fixed argument list, never through a shell.\nimport { execFile } from \"node:child_process\";\nimport { ReviewError } from \"./errors.js\";\n\n/**\n * The options every `git` Ascribe starts runs with, each as `-c <option>`, so\n * that reading a repository runs nothing it supplies: `ascribe_core::git`'s\n * `OPTIONS`, which says why each is there. `test/git.test.ts` compares the two.\n */\nexport const OPTIONS: readonly string[] = [\"safe.bareRepository=explicit\", \"core.fsmonitor=false\"];\n\n/**\n * The environment variables cleared for every `git` Ascribe starts, each of\n * which would point it at another repository: `ascribe_core::git`'s `CLEARED`.\n */\nexport const CLEARED: readonly string[] = [\n \"GIT_DIR\",\n \"GIT_WORK_TREE\",\n \"GIT_INDEX_FILE\",\n \"GIT_OBJECT_DIRECTORY\",\n \"GIT_ALTERNATE_OBJECT_DIRECTORIES\",\n \"GIT_NAMESPACE\",\n \"GIT_COMMON_DIR\",\n];\n\n/** This process's environment without `CLEARED`. */\nfunction environment(): NodeJS.ProcessEnv {\n return Object.fromEntries(\n Object.entries(process.env).filter(([name]) => !CLEARED.includes(name)),\n );\n}\n\n/** Runs `git` in `cwd` and resolves with its standard output. */\nexport function git(cwd: string, args: readonly string[]): Promise<string> {\n return new Promise((resolve, reject) => {\n execFile(\n \"git\",\n [...OPTIONS.flatMap((option) => [\"-c\", option]), \"-c\", \"core.quotepath=off\", ...args],\n { cwd, env: environment(), maxBuffer: 256 * 1024 * 1024, encoding: \"utf8\" },\n (error, stdout, stderr) => {\n if (error === null) {\n resolve(stdout);\n return;\n }\n const missing = (error as NodeJS.ErrnoException).code === \"ENOENT\";\n const detail = stderr.trim() || error.message;\n reject(\n new ReviewError(\n \"git\",\n missing ? \"`git` isn't on the path.\" : `\\`git ${args.join(\" \")}\\` failed: ${detail}`,\n { cause: error },\n ),\n );\n },\n );\n });\n}\n\n/** Like `git`, but resolves with `undefined` instead of rejecting when git exits non-zero. */\nexport async function gitMaybe(cwd: string, args: readonly string[]): Promise<string | undefined> {\n try {\n return await git(cwd, args);\n } catch (error) {\n if (error instanceof ReviewError && error.message.startsWith(\"`git` isn't\")) throw error;\n return undefined;\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ascribed/review",
|
|
3
|
-
"version": "0.2.1-next.
|
|
3
|
+
"version": "0.2.1-next.15",
|
|
4
4
|
"description": "Reviewing Ascribe pages as readers see them: changed blocks marked on the rendered page, and a pull request's review threads beside them.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"node": ">=24"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@ascribed/elements": "0.2.1-next.
|
|
47
|
+
"@ascribed/elements": "0.2.1-next.15",
|
|
48
48
|
"@types/node": "^24.0.0",
|
|
49
49
|
"esbuild": "^0.28.2",
|
|
50
50
|
"jsdom": "^30.1.2"
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"smol-toml": "^1.9.0"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "b4d4611f06c75553d379e3ad09a93b809071228f",
|
|
59
59
|
"scripts": {
|
|
60
60
|
"build": "tsc -p tsconfig.build.json",
|
|
61
61
|
"typecheck": "tsc --noEmit",
|