@osdlc/typed-gh 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +98 -96
- package/SECURITY.md +4 -4
- package/dist/async.d.ts +28 -27
- package/dist/async.js +29 -28
- package/dist/doors.d.ts +25 -23
- package/dist/doors.js +29 -27
- package/dist/failure.d.ts +8 -8
- package/dist/failure.js +6 -6
- package/dist/index.d.ts +13 -14
- package/dist/index.js +14 -15
- package/dist/parse.d.ts +21 -21
- package/dist/parse.js +34 -36
- package/dist/repo.d.ts +13 -13
- package/dist/repo.js +13 -13
- package/dist/run.d.ts +12 -13
- package/dist/run.js +11 -12
- package/dist/sync.d.ts +66 -64
- package/dist/sync.js +67 -65
- package/dist/types.d.ts +31 -32
- package/dist/types.js +8 -8
- package/dist/verbs.d.ts +120 -122
- package/dist/verbs.js +119 -110
- package/package.json +3 -3
package/dist/parse.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `gh --json` payloads
|
|
2
|
+
* Parsers from `gh --json` payloads to the typed model.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Nothing here has side effects, so code that runs its own calls can reuse
|
|
5
|
+
* it without starting a process, and the hard cases can be tested as data.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* It handles two payload differences:
|
|
8
8
|
*
|
|
9
|
-
* 1.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* here, once
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* `
|
|
18
|
-
* verdict
|
|
9
|
+
* 1. Author logins differ between GraphQL and REST. GraphQL
|
|
10
|
+
* (`gh pr view --json`) returns a login without a suffix
|
|
11
|
+
* (`github-actions`). REST (`gh api`) returns the suffixed one
|
|
12
|
+
* (`github-actions[bot]`). Comparing one with the other never matches,
|
|
13
|
+
* and no error shows it. Then a bot's comments are treated as human
|
|
14
|
+
* review feedback. Logins are normalized here, once, to the form without
|
|
15
|
+
* the suffix.
|
|
16
|
+
* 2. `statusCheckRollup` holds two node types. A modern `CheckRun` carries
|
|
17
|
+
* `name`/`status`/`conclusion`. A legacy `StatusContext` carries
|
|
18
|
+
* `context`/`state`. Both map to one verdict, so a caller never has to
|
|
19
|
+
* know which it got.
|
|
19
20
|
*/
|
|
20
21
|
// ── primitives ──────────────────────────────────────────────────────────────
|
|
21
22
|
function isRecord(x) {
|
|
@@ -31,8 +32,8 @@ function bool(x) {
|
|
|
31
32
|
return x === true;
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
34
|
-
* A comment/review id
|
|
35
|
-
*
|
|
35
|
+
* A comment/review id. GraphQL sends a string node id, REST a numeric one.
|
|
36
|
+
* Either becomes a string. A numeric id of 2^53 or more has already lost precision
|
|
36
37
|
* in `JSON.parse` by the time it gets here.
|
|
37
38
|
*/
|
|
38
39
|
function idOrNull(x) {
|
|
@@ -47,15 +48,14 @@ function list(x) {
|
|
|
47
48
|
}
|
|
48
49
|
// ── actors ──────────────────────────────────────────────────────────────────
|
|
49
50
|
/**
|
|
50
|
-
* Strip a single trailing `[bot]` suffix
|
|
51
|
-
*
|
|
52
|
-
* The one normalization that makes a login from either transport comparable.
|
|
51
|
+
* Strip a single trailing `[bot]` suffix, so a REST login matches the
|
|
52
|
+
* GraphQL one.
|
|
53
53
|
*/
|
|
54
54
|
export function normalizeAuthorLogin(login) {
|
|
55
55
|
return login.endsWith('[bot]') ? login.slice(0, -'[bot]'.length) : login;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
|
-
* Parse an author node from
|
|
58
|
+
* Parse an author node from GraphQL (`author`) or REST (`user`).
|
|
59
59
|
* Null when there is no login, or the login is empty once `[bot]` is removed.
|
|
60
60
|
*/
|
|
61
61
|
export function parseActor(x) {
|
|
@@ -67,7 +67,7 @@ export function parseActor(x) {
|
|
|
67
67
|
return null;
|
|
68
68
|
return {
|
|
69
69
|
login,
|
|
70
|
-
// `is_bot
|
|
70
|
+
// Only some payloads carry `is_bot`. The suffix works for both.
|
|
71
71
|
isBot: bool(x['is_bot']) || raw.endsWith('[bot]'),
|
|
72
72
|
};
|
|
73
73
|
}
|
|
@@ -75,10 +75,9 @@ export function parseActor(x) {
|
|
|
75
75
|
/**
|
|
76
76
|
* Conclusions that mean the check failed.
|
|
77
77
|
*
|
|
78
|
-
* `ACTION_REQUIRED` is
|
|
79
|
-
* something, not that the build broke, and
|
|
80
|
-
*
|
|
81
|
-
* make on purpose rather than by adding a plausible-looking member.
|
|
78
|
+
* `ACTION_REQUIRED` is not here on purpose. It means a person must do
|
|
79
|
+
* something, not that the build broke, and callers treat it as neutral.
|
|
80
|
+
* Adding a member changes every caller's verdicts, so decide it on purpose.
|
|
82
81
|
*/
|
|
83
82
|
const FAILED_CONCLUSIONS = new Set(['FAILURE', 'TIMED_OUT', 'CANCELLED', 'STARTUP_FAILURE']);
|
|
84
83
|
/** Legacy StatusContext `state` values that mean failure. */
|
|
@@ -104,12 +103,11 @@ export function parseCheckRun(x) {
|
|
|
104
103
|
return null;
|
|
105
104
|
const status = str(x['status']).toUpperCase();
|
|
106
105
|
const conclusion = strOrNull(x['conclusion']);
|
|
107
|
-
// `detailsUrl` is the modern CheckRun field
|
|
108
|
-
// StatusContext one
|
|
109
|
-
// `name
|
|
110
|
-
// the node-type split strictly.
|
|
106
|
+
// `detailsUrl` is the modern CheckRun field and `targetUrl` the legacy
|
|
107
|
+
// StatusContext one. A payload sometimes carries either on a node with a
|
|
108
|
+
// `name`, so read both.
|
|
111
109
|
const url = strOrNull(x['detailsUrl']) ?? strOrNull(x['targetUrl']);
|
|
112
|
-
// Still running
|
|
110
|
+
// Still running, so not a pass. If "no failure yet" counted as green, a red build could merge.
|
|
113
111
|
if (status !== '' && status !== 'COMPLETED') {
|
|
114
112
|
return { name, status, conclusion, verdict: 'pending', url };
|
|
115
113
|
}
|
|
@@ -127,7 +125,7 @@ export function anyCheckFailed(checks) {
|
|
|
127
125
|
return checks.some((c) => c.verdict === 'failure');
|
|
128
126
|
}
|
|
129
127
|
// ── the pull request ────────────────────────────────────────────────────────
|
|
130
|
-
/** Parse a `labels` field (PR, issue, or a bare `gh label list` array) into {@link Label} rows
|
|
128
|
+
/** Parse a `labels` field (PR, issue, or a bare `gh label list` array) into {@link Label} rows. Every label list in this package goes through it. Skips an item with no name, or an empty name, which reads the same as no name. */
|
|
131
129
|
export function parseLabels(x) {
|
|
132
130
|
const out = [];
|
|
133
131
|
for (const item of list(x)) {
|
|
@@ -170,12 +168,12 @@ function parseReviews(x) {
|
|
|
170
168
|
}
|
|
171
169
|
return out;
|
|
172
170
|
}
|
|
173
|
-
/** `mergeable`
|
|
171
|
+
/** `mergeable` has three states. Anything unknown, or absent, is UNKNOWN. */
|
|
174
172
|
function parseMergeable(x) {
|
|
175
173
|
const v = str(x).toUpperCase();
|
|
176
174
|
return v === 'MERGEABLE' || v === 'CONFLICTING' ? v : 'UNKNOWN';
|
|
177
175
|
}
|
|
178
|
-
/** gh returns `""` for "no decision yet"
|
|
176
|
+
/** gh returns `""` for "no decision yet". The model says `null`. */
|
|
179
177
|
function parseReviewDecision(x) {
|
|
180
178
|
const v = strOrNull(x);
|
|
181
179
|
return v === 'APPROVED' || v === 'CHANGES_REQUESTED' || v === 'REVIEW_REQUIRED' ? v : null;
|
|
@@ -183,8 +181,8 @@ function parseReviewDecision(x) {
|
|
|
183
181
|
/**
|
|
184
182
|
* One PR from a `gh --json` object, or null when it carries no usable number.
|
|
185
183
|
*
|
|
186
|
-
*
|
|
187
|
-
* known" value
|
|
184
|
+
* Every field gets a value. A field `gh` was not asked for gets its "not
|
|
185
|
+
* known" value, not `undefined`, so the result is safe to build from
|
|
188
186
|
* any field subset.
|
|
189
187
|
*/
|
|
190
188
|
export function parsePullRequest(raw) {
|
|
@@ -257,7 +255,7 @@ export function parsePrSearchHit(raw) {
|
|
|
257
255
|
return {
|
|
258
256
|
number,
|
|
259
257
|
title: str(raw['title']),
|
|
260
|
-
// Search answers in lower case
|
|
258
|
+
// Search answers in lower case. `PullRequest.state` is upper case.
|
|
261
259
|
state: str(raw['state']).toUpperCase(),
|
|
262
260
|
url: str(raw['url']),
|
|
263
261
|
isDraft: bool(raw['isDraft']),
|
package/dist/repo.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Reading the GitHub owner and repository from a git remote, and a pull
|
|
3
|
+
* request from a URL or what a person typed.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* scp-style, a trailing `.git`, an `ssh://` port, a bare `owner/repo`.
|
|
5
|
+
* `parseOwnerRepo` uses `@osdlc/typed-git`'s `parseGitUrl`. It lives here so
|
|
6
|
+
* callers do not each write a regex over `remote.origin.url`. Such a regex
|
|
7
|
+
* usually misses a URL form: scp-style, a trailing `.git`, an `ssh://` port,
|
|
8
|
+
* or a bare `owner/repo`.
|
|
9
9
|
*/
|
|
10
10
|
import type { OwnerRepo } from './types.ts';
|
|
11
11
|
/**
|
|
@@ -27,11 +27,11 @@ export interface PullRequestRef extends OwnerRepo {
|
|
|
27
27
|
*
|
|
28
28
|
* Accepts `https://<host>/<owner>/<repo>/pull/<n>` with or without a scheme,
|
|
29
29
|
* a trailing slash, a sub-page (`/files`, `/commits`), a query string or a
|
|
30
|
-
* fragment
|
|
30
|
+
* fragment, as copied from a browser's address bar. The host is
|
|
31
31
|
* `github.com` (a `www.` prefix is dropped) or a GitHub Enterprise host such
|
|
32
|
-
* as `github.acme.com` or `acme.ghe.com
|
|
33
|
-
* contain a dot. `parseGitUrl`
|
|
34
|
-
* as part of the project path.
|
|
32
|
+
* as `github.acme.com` or `acme.ghe.com`. Without a scheme the host must
|
|
33
|
+
* contain a dot. `parseGitUrl` does not fit here, because it reads
|
|
34
|
+
* `/pull/<n>` as part of the project path.
|
|
35
35
|
*/
|
|
36
36
|
export declare function parsePrUrl(url: string): PullRequestRef | null;
|
|
37
37
|
/**
|
|
@@ -58,9 +58,9 @@ export type PrRef = {
|
|
|
58
58
|
* Read what a person typed to name a pull request: a number, `#42`,
|
|
59
59
|
* `owner/name#42`, a URL, or a branch name. Null when it is none of these.
|
|
60
60
|
*
|
|
61
|
-
*
|
|
62
|
-
* `gh pr view 42` reads them
|
|
63
|
-
* clients' `prFind` tries both.
|
|
61
|
+
* It starts no process. Digits are read as a number first, the way
|
|
62
|
+
* `gh pr view 42` reads them. `orBranch` keeps the text for reading it as a
|
|
63
|
+
* branch. The clients' `prFind` tries both.
|
|
64
64
|
*/
|
|
65
65
|
export declare function parsePrRef(input: string): PrRef | null;
|
|
66
66
|
/** True for `owner/name` as GitHub spells it. */
|
package/dist/repo.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Reading the GitHub owner and repository from a git remote, and a pull
|
|
3
|
+
* request from a URL or what a person typed.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* scp-style, a trailing `.git`, an `ssh://` port, a bare `owner/repo`.
|
|
5
|
+
* `parseOwnerRepo` uses `@osdlc/typed-git`'s `parseGitUrl`. It lives here so
|
|
6
|
+
* callers do not each write a regex over `remote.origin.url`. Such a regex
|
|
7
|
+
* usually misses a URL form: scp-style, a trailing `.git`, an `ssh://` port,
|
|
8
|
+
* or a bare `owner/repo`.
|
|
9
9
|
*/
|
|
10
10
|
import { parseGitUrl } from '@osdlc/typed-git';
|
|
11
11
|
/**
|
|
@@ -31,11 +31,11 @@ export function ownerRepoSlug(r) {
|
|
|
31
31
|
*
|
|
32
32
|
* Accepts `https://<host>/<owner>/<repo>/pull/<n>` with or without a scheme,
|
|
33
33
|
* a trailing slash, a sub-page (`/files`, `/commits`), a query string or a
|
|
34
|
-
* fragment
|
|
34
|
+
* fragment, as copied from a browser's address bar. The host is
|
|
35
35
|
* `github.com` (a `www.` prefix is dropped) or a GitHub Enterprise host such
|
|
36
|
-
* as `github.acme.com` or `acme.ghe.com
|
|
37
|
-
* contain a dot. `parseGitUrl`
|
|
38
|
-
* as part of the project path.
|
|
36
|
+
* as `github.acme.com` or `acme.ghe.com`. Without a scheme the host must
|
|
37
|
+
* contain a dot. `parseGitUrl` does not fit here, because it reads
|
|
38
|
+
* `/pull/<n>` as part of the project path.
|
|
39
39
|
*/
|
|
40
40
|
export function parsePrUrl(url) {
|
|
41
41
|
const m = /^(?:(https?:)\/\/)?([a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?::\d+)?)\/([^/\s]+)\/([^/\s]+)\/pull\/(\d+)(?:[/?#].*)?$/i.exec(url.trim());
|
|
@@ -58,9 +58,9 @@ export function parsePrUrl(url) {
|
|
|
58
58
|
* Read what a person typed to name a pull request: a number, `#42`,
|
|
59
59
|
* `owner/name#42`, a URL, or a branch name. Null when it is none of these.
|
|
60
60
|
*
|
|
61
|
-
*
|
|
62
|
-
* `gh pr view 42` reads them
|
|
63
|
-
* clients' `prFind` tries both.
|
|
61
|
+
* It starts no process. Digits are read as a number first, the way
|
|
62
|
+
* `gh pr view 42` reads them. `orBranch` keeps the text for reading it as a
|
|
63
|
+
* branch. The clients' `prFind` tries both.
|
|
64
64
|
*/
|
|
65
65
|
export function parsePrRef(input) {
|
|
66
66
|
const text = input.trim();
|
package/dist/run.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `runVerb` and `runVerbAsync`
|
|
2
|
+
* `runVerb` and `runVerbAsync` run one {@link GhVerb} through a runner the
|
|
3
3
|
* caller owns, with everything the verb asks for applied.
|
|
4
4
|
*
|
|
5
5
|
* A verb carries more than `argv`: `input` (a payload on stdin), `env` (the
|
|
6
6
|
* target host as `GH_HOST`), and `checked` (whether a non-zero exit throws).
|
|
7
|
-
* `verb.parse(await runner.run(verb.argv))` drops the first two
|
|
8
|
-
*
|
|
9
|
-
* apply all of it
|
|
7
|
+
* `verb.parse(await runner.run(verb.argv))` drops the first two with no
|
|
8
|
+
* error, so a query goes to github.com and a review is never sent. These two
|
|
9
|
+
* functions apply all of it. `Gh` and `AsyncGh` call them too.
|
|
10
10
|
*/
|
|
11
11
|
import type { AsyncCommandRunner, CommandRunner } from '@osdlc/command-seam';
|
|
12
12
|
import type { GhVerb } from './verbs.ts';
|
|
@@ -18,20 +18,19 @@ export interface RunVerbOptions {
|
|
|
18
18
|
}
|
|
19
19
|
/** {@link RunVerbOptions} for {@link runVerbAsync}, with the abort signal. */
|
|
20
20
|
export interface RunVerbAsyncOptions extends RunVerbOptions {
|
|
21
|
-
/** Aborts the call
|
|
21
|
+
/** Aborts the call. The runner stops the child process. */
|
|
22
22
|
signal?: AbortSignal | undefined;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* Run `verb` through `runner` and return its parsed value
|
|
26
|
-
* verb's `input`, `env
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* there is no such thing.
|
|
25
|
+
* Run `verb` through `runner` and return its parsed value. It applies the
|
|
26
|
+
* verb's `input`, `env` and `checked`, and the `timeoutMs` option. A failed
|
|
27
|
+
* `gh` throws a `FailureError` carrying the classified `GhFailure`. Output
|
|
28
|
+
* that cannot be read throws one of kind `unreadable`. A query for one thing
|
|
29
|
+
* returns `null` when GitHub says there is no such thing.
|
|
31
30
|
*
|
|
32
|
-
* A plain `(binary, argv, opts) => result` function is not a runner
|
|
31
|
+
* A plain `(binary, argv, opts) => result` function is not a runner. Wrap it
|
|
33
32
|
* as `{ run: (argv, opts) => fn('gh', argv, opts) }`.
|
|
34
33
|
*/
|
|
35
34
|
export declare function runVerb<T>(verb: GhVerb<T>, runner: CommandRunner, opts: RunVerbOptions): T;
|
|
36
|
-
/** {@link runVerb} for an `AsyncCommandRunner
|
|
35
|
+
/** {@link runVerb} for an `AsyncCommandRunner`. It rejects where `runVerb` throws. */
|
|
37
36
|
export declare function runVerbAsync<T>(verb: GhVerb<T>, runner: AsyncCommandRunner, opts: RunVerbAsyncOptions): Promise<T>;
|
package/dist/run.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `runVerb` and `runVerbAsync`
|
|
2
|
+
* `runVerb` and `runVerbAsync` run one {@link GhVerb} through a runner the
|
|
3
3
|
* caller owns, with everything the verb asks for applied.
|
|
4
4
|
*
|
|
5
5
|
* A verb carries more than `argv`: `input` (a payload on stdin), `env` (the
|
|
6
6
|
* target host as `GH_HOST`), and `checked` (whether a non-zero exit throws).
|
|
7
|
-
* `verb.parse(await runner.run(verb.argv))` drops the first two
|
|
8
|
-
*
|
|
9
|
-
* apply all of it
|
|
7
|
+
* `verb.parse(await runner.run(verb.argv))` drops the first two with no
|
|
8
|
+
* error, so a query goes to github.com and a review is never sent. These two
|
|
9
|
+
* functions apply all of it. `Gh` and `AsyncGh` call them too.
|
|
10
10
|
*/
|
|
11
11
|
import { runCheckedGh, runCheckedGhAsync, settle } from "./doors.js";
|
|
12
12
|
/**
|
|
13
|
-
* Run `verb` through `runner` and return its parsed value
|
|
14
|
-
* verb's `input`, `env
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* there is no such thing.
|
|
13
|
+
* Run `verb` through `runner` and return its parsed value. It applies the
|
|
14
|
+
* verb's `input`, `env` and `checked`, and the `timeoutMs` option. A failed
|
|
15
|
+
* `gh` throws a `FailureError` carrying the classified `GhFailure`. Output
|
|
16
|
+
* that cannot be read throws one of kind `unreadable`. A query for one thing
|
|
17
|
+
* returns `null` when GitHub says there is no such thing.
|
|
19
18
|
*
|
|
20
|
-
* A plain `(binary, argv, opts) => result` function is not a runner
|
|
19
|
+
* A plain `(binary, argv, opts) => result` function is not a runner. Wrap it
|
|
21
20
|
* as `{ run: (argv, opts) => fn('gh', argv, opts) }`.
|
|
22
21
|
*/
|
|
23
22
|
export function runVerb(verb, runner, opts) {
|
|
@@ -31,7 +30,7 @@ export function runVerb(verb, runner, opts) {
|
|
|
31
30
|
const result = verb.checked ? runCheckedGh(runner, verb.argv, run) : runner.run(verb.argv, run);
|
|
32
31
|
return settle(verb, result, runner.binary);
|
|
33
32
|
}
|
|
34
|
-
/** {@link runVerb} for an `AsyncCommandRunner
|
|
33
|
+
/** {@link runVerb} for an `AsyncCommandRunner`. It rejects where `runVerb` throws. */
|
|
35
34
|
export async function runVerbAsync(verb, runner, opts) {
|
|
36
35
|
const run = { cwd: opts.cwd };
|
|
37
36
|
if (verb.input !== undefined)
|
package/dist/sync.d.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `Gh
|
|
2
|
+
* `Gh`: the synchronous GitHub client, the companion of `@osdlc/typed-git`'s
|
|
3
3
|
* `Git`, bound to one working directory and one {@link CommandRunner}.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* `gh` handles authentication. No token is read, stored, or passed. That is
|
|
6
|
+
* why this runs the CLI instead of calling the REST API. The credential is
|
|
7
|
+
* already in the user's keychain. A library that reads `GITHUB_TOKEN` can
|
|
8
|
+
* leak it.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* or throws
|
|
12
|
-
* `GhFailure
|
|
13
|
-
* `
|
|
14
|
-
* `
|
|
15
|
-
* `
|
|
10
|
+
* It has the same three call styles as the git client. The plain method
|
|
11
|
+
* returns a value or throws. A failed `gh` throws a `FailureError` carrying
|
|
12
|
+
* the classified `GhFailure`. Output that cannot be read throws one of kind
|
|
13
|
+
* `unreadable`. A query for one thing (`prView`, `issueView`, `repoView`)
|
|
14
|
+
* returns `null` when GitHub says there is no such thing. `gh.try.*` returns
|
|
15
|
+
* the failure as a `Result<T, GhFailure>`. `gh.soft.*` returns `null` for it.
|
|
16
|
+
* `raw` and `rawChecked` run any `gh` call.
|
|
16
17
|
*/
|
|
17
18
|
import { type CommandResult, type CommandRunner } from '@osdlc/command-seam';
|
|
18
19
|
import { type Soft, type TriedSync } from './doors.ts';
|
|
@@ -20,12 +21,12 @@ import * as v from './verbs.ts';
|
|
|
20
21
|
import type { ApiOptions, ApiResponse, AuthStatus, GhTarget, IssueListOptions, ListResult, PrListOptions } from './verbs.ts';
|
|
21
22
|
import type { IssueRow, Label, OwnerRepo, PrSearchHit, PullRequest, Repository } from './types.ts';
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* Makes a runner for a binary. A CLI passes one factory to every place that
|
|
25
|
+
* runs a command. Real code uses `spawnRunner`. Tests pass a factory that
|
|
26
|
+
* records calls, so the CLI runs offline.
|
|
26
27
|
*/
|
|
27
28
|
export type RunnerFactory = (binary: string) => CommandRunner;
|
|
28
|
-
/**
|
|
29
|
+
/** The default factory: real runners that use `spawnSync`. */
|
|
29
30
|
export declare const defaultRunnerFactory: RunnerFactory;
|
|
30
31
|
/** What a client is built with: the runner, and the repository and host it talks to. */
|
|
31
32
|
export interface GhOptions extends GhTarget {
|
|
@@ -60,50 +61,52 @@ export declare class Gh {
|
|
|
60
61
|
withRepo(repo: string): Gh;
|
|
61
62
|
/**
|
|
62
63
|
* The whole surface again, returning a `Result<T, GhFailure>` instead of
|
|
63
|
-
* throwing
|
|
64
|
+
* throwing. `gh.try.prList()` is `{ ok: true, value } | { ok: false, error }`.
|
|
64
65
|
* A query for one thing that finds nothing is `ok: true` with `null`. See
|
|
65
66
|
* `./doors.ts`.
|
|
66
67
|
*
|
|
67
|
-
* The type leaves out
|
|
68
|
-
* the proxy wraps `raw` and `rawChecked` too. Call those on the
|
|
68
|
+
* The type leaves out `raw`, `rawChecked` and the helpers that run nothing.
|
|
69
|
+
* At runtime the proxy wraps `raw` and `rawChecked` too. Call those on the
|
|
70
|
+
* client itself.
|
|
69
71
|
*/
|
|
70
72
|
get try(): TriedSync<Gh>;
|
|
71
73
|
/**
|
|
72
74
|
* Each query again, returning `null` instead of throwing when `gh` fails. A
|
|
73
|
-
* mutation has no soft form. Do not
|
|
74
|
-
*
|
|
75
|
+
* mutation has no soft form. Do not decide a write on a `null` from `soft`.
|
|
76
|
+
* It is the answer for "could not ask" as well as for "nothing there". Use
|
|
75
77
|
* `try` where the difference matters.
|
|
76
78
|
*/
|
|
77
79
|
get soft(): Soft<Gh>;
|
|
78
|
-
/**
|
|
80
|
+
/** Run any `gh` call and return the result, whatever the exit code. */
|
|
79
81
|
raw(argv: string[], opts?: {
|
|
80
82
|
timeoutMs?: number | undefined;
|
|
81
83
|
}): CommandResult;
|
|
82
|
-
/**
|
|
84
|
+
/** Run any `gh` call. Throws the classified failure on a non-zero exit. */
|
|
83
85
|
rawChecked(argv: string[], opts?: {
|
|
84
86
|
timeoutMs?: number | undefined;
|
|
85
87
|
}): CommandResult;
|
|
86
|
-
/** Run one verb from the table,
|
|
88
|
+
/** Run one verb from the table, checked or unchecked as the verb says. */
|
|
87
89
|
private exec;
|
|
88
|
-
/** {@link exec} for a verb that never
|
|
90
|
+
/** {@link exec} for a verb that never returns `null` as a value. A failed or unreadable run throws. */
|
|
89
91
|
private read;
|
|
90
|
-
/** Pull requests matching `opts`. Throws when gh fails
|
|
92
|
+
/** Pull requests matching `opts`. Throws when gh fails. `soft.prList` gives null instead. */
|
|
91
93
|
prList(opts?: PrListOptions & {
|
|
92
94
|
timeoutMs?: number | undefined;
|
|
93
95
|
}): ListResult<PullRequest>;
|
|
94
96
|
/**
|
|
95
|
-
* One PR by number, branch or URL, with the
|
|
96
|
-
* no such PR. Throws when gh fails otherwise.
|
|
97
|
+
* One PR by number, branch or URL, with the full field set. Null when there
|
|
98
|
+
* is no such PR. Throws when gh fails otherwise.
|
|
97
99
|
*/
|
|
98
100
|
prView(ref: number | string, opts?: {
|
|
99
101
|
fields?: readonly string[] | undefined;
|
|
100
102
|
timeoutMs?: number | undefined;
|
|
101
103
|
}): PullRequest | null;
|
|
102
104
|
/**
|
|
103
|
-
* The one PR that `input` names, or null
|
|
104
|
-
* `#42`, `owner/name#42`, a URL or a branch (read by
|
|
105
|
+
* The one PR that `input` names, or null. `input` is what a person typed,
|
|
106
|
+
* such as `42`, `#42`, `owner/name#42`, a URL or a branch (read by
|
|
107
|
+
* `parsePrRef`).
|
|
105
108
|
*
|
|
106
|
-
* Digits are a PR number first
|
|
109
|
+
* Digits are a PR number first. When no such PR comes back, they are tried
|
|
107
110
|
* as a branch. A branch gives its open PR, or else its newest closed or
|
|
108
111
|
* merged one, so a tool run after the merge still finds it. Fields default
|
|
109
112
|
* to {@link PR_LIST_FIELDS}.
|
|
@@ -115,28 +118,26 @@ export declare class Gh {
|
|
|
115
118
|
/**
|
|
116
119
|
* The open PR whose head exactly equals `branch`, or null.
|
|
117
120
|
*
|
|
118
|
-
* Defaults to {@link PR_REF_FIELDS}
|
|
119
|
-
* merge
|
|
120
|
-
* `isDraft`) in the
|
|
121
|
-
*
|
|
122
|
-
* separately would cost a second round trip for the common case where the
|
|
123
|
-
* caller already knows it wants that field up front.
|
|
121
|
+
* Defaults to {@link PR_REF_FIELDS}, so finding a PR does not make GitHub
|
|
122
|
+
* compute the merge state. Pass `opts.fields` to ask for more (such as
|
|
123
|
+
* `isDraft`) in the same call. That saves a second `prList` or `prView`
|
|
124
|
+
* call when the caller already knows it needs the field.
|
|
124
125
|
*/
|
|
125
126
|
openPrFor(branch: string, opts?: {
|
|
126
127
|
limit?: number | undefined;
|
|
127
128
|
fields?: readonly string[] | undefined;
|
|
128
129
|
}): PullRequest | null;
|
|
129
130
|
/**
|
|
130
|
-
* {@link prView},
|
|
131
|
+
* {@link prView}, asked again until GitHub has computed the merge state.
|
|
131
132
|
*
|
|
132
|
-
* GitHub computes mergeability
|
|
133
|
-
* answers `UNKNOWN` while it runs. A caller that
|
|
134
|
-
*
|
|
135
|
-
* `attempts` times
|
|
136
|
-
* returned
|
|
137
|
-
*
|
|
133
|
+
* GitHub computes mergeability only when asked. The first query starts it
|
|
134
|
+
* and answers `UNKNOWN` while it runs. A caller that trusts that first
|
|
135
|
+
* answer misjudges every PR whose computation is slow. So this asks up to
|
|
136
|
+
* `attempts` times while the answer is `UNKNOWN`. The last answer is
|
|
137
|
+
* returned either way. `UNKNOWN` can be the final answer, and callers must
|
|
138
|
+
* still handle it.
|
|
138
139
|
*
|
|
139
|
-
* `sleep` is
|
|
140
|
+
* `sleep` is an option so a test does not spend real seconds waiting.
|
|
140
141
|
*/
|
|
141
142
|
prViewSettled(number: number, opts?: {
|
|
142
143
|
attempts?: number | undefined;
|
|
@@ -149,15 +150,15 @@ export declare class Gh {
|
|
|
149
150
|
timeoutMs?: number | undefined;
|
|
150
151
|
}): string;
|
|
151
152
|
/**
|
|
152
|
-
* A REST GET through `gh api`. Never throws for a failed call, in any
|
|
153
|
-
*
|
|
153
|
+
* A REST GET through `gh api`. Never throws for a failed call, in any call
|
|
154
|
+
* style. Failure is `ok: false` on the returned {@link ApiResponse}.
|
|
154
155
|
*/
|
|
155
156
|
api(endpoint: string, opts?: ApiOptions & {
|
|
156
157
|
timeoutMs?: number | undefined;
|
|
157
158
|
}): ApiResponse;
|
|
158
159
|
/**
|
|
159
|
-
* A GraphQL query
|
|
160
|
-
* `soft.graphql` gives null. See the `graphql` verb.
|
|
160
|
+
* A GraphQL query. Returns each page's `data`. Throws on a failed or partial
|
|
161
|
+
* result. `soft.graphql` gives null. See the `graphql` verb.
|
|
161
162
|
*
|
|
162
163
|
* @experimental
|
|
163
164
|
*/
|
|
@@ -173,7 +174,7 @@ export declare class Gh {
|
|
|
173
174
|
issueList(opts?: IssueListOptions & {
|
|
174
175
|
timeoutMs?: number | undefined;
|
|
175
176
|
}): ListResult<IssueRow>;
|
|
176
|
-
/** One issue with its labels
|
|
177
|
+
/** One issue with its labels, or null when there is no such issue. Throws when gh fails otherwise. */
|
|
177
178
|
issueView(number: number, opts?: {
|
|
178
179
|
timeoutMs?: number | undefined;
|
|
179
180
|
}): IssueRow | null;
|
|
@@ -193,25 +194,26 @@ export declare class Gh {
|
|
|
193
194
|
/** True iff the `gh` binary is on PATH and runs. */
|
|
194
195
|
installed(): boolean;
|
|
195
196
|
/**
|
|
196
|
-
* True iff `gh` holds credentials.
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
197
|
+
* True iff `gh` holds credentials. Call it on its own to report the two
|
|
198
|
+
* checks separately. "gh is missing" and "gh is not logged in" need
|
|
199
|
+
* different fixes. A report that merges them sends someone to install
|
|
200
|
+
* software they already have.
|
|
200
201
|
*/
|
|
201
202
|
authenticated(): boolean;
|
|
202
|
-
/** Login state and what `gh auth status` printed
|
|
203
|
+
/** Login state and what `gh auth status` printed. `authenticated()` is its `ok`. */
|
|
203
204
|
authStatus(): AuthStatus;
|
|
204
205
|
/**
|
|
205
|
-
* True iff `gh` is installed
|
|
206
|
+
* True iff `gh` is installed and authenticated.
|
|
206
207
|
*
|
|
207
|
-
*
|
|
208
|
+
* Skips the auth check when `gh` is not installed, since there is nothing to
|
|
209
|
+
* ask.
|
|
208
210
|
*/
|
|
209
211
|
ready(): boolean;
|
|
210
212
|
/**
|
|
211
213
|
* `{owner, repo}` parsed from the remote URL you pass, or null when it is
|
|
212
214
|
* null or names no project.
|
|
213
215
|
*
|
|
214
|
-
*
|
|
216
|
+
* It reads no git config, starts no process and does not ask GitHub.
|
|
215
217
|
*/
|
|
216
218
|
ownerRepo(remoteUrl: string | null): OwnerRepo | null;
|
|
217
219
|
/** Open a PR and return its URL ('' if gh printed none). */
|
|
@@ -221,12 +223,12 @@ export declare class Gh {
|
|
|
221
223
|
title?: string | undefined;
|
|
222
224
|
body?: string | undefined;
|
|
223
225
|
}): void;
|
|
224
|
-
/** The PR's unified diff
|
|
226
|
+
/** The PR's unified diff. Throws the classified failure when gh cannot produce it. */
|
|
225
227
|
prDiff(number: number): string;
|
|
226
|
-
/** Post one conversation comment on a PR
|
|
228
|
+
/** Post one conversation comment on a PR. Returns its URL ('' if gh printed none). */
|
|
227
229
|
prComment(number: number, body: string): string;
|
|
228
230
|
/**
|
|
229
|
-
* Create a `COMMENT` review with inline comments
|
|
231
|
+
* Create a `COMMENT` review with inline comments. Returns the review's URL ('' if none came back).
|
|
230
232
|
*
|
|
231
233
|
* @experimental
|
|
232
234
|
*/
|
|
@@ -238,12 +240,12 @@ export declare class Gh {
|
|
|
238
240
|
/** Mark a PR ready for review (`draft: false`) or convert it to a draft (`draft: true`). */
|
|
239
241
|
prSetDraft(number: number, draft: boolean): void;
|
|
240
242
|
/**
|
|
241
|
-
* Rewrite one PR conversation comment's body. `commentId` is the numeric REST id
|
|
243
|
+
* Rewrite one PR conversation comment's body. `commentId` is the numeric REST id. Anything else throws `RangeError`.
|
|
242
244
|
*
|
|
243
245
|
* @experimental
|
|
244
246
|
*/
|
|
245
247
|
issueCommentEdit(commentId: string, body: string): void;
|
|
246
|
-
/** Merge a PR
|
|
248
|
+
/** Merge a PR. GitHub refuses when its head is no longer `opts.headSha`. */
|
|
247
249
|
prMerge(number: number, opts: {
|
|
248
250
|
method: v.PrMergeMethod;
|
|
249
251
|
headSha: string;
|
|
@@ -259,8 +261,8 @@ export declare class Gh {
|
|
|
259
261
|
}): void;
|
|
260
262
|
/**
|
|
261
263
|
* Swap an issue's label: remove `currentLabel` (when given) and add
|
|
262
|
-
* `newLabel`, in a single `gh issue edit` call
|
|
263
|
-
* or
|
|
264
|
+
* `newLabel`, in a single `gh issue edit` call. The issue is never left
|
|
265
|
+
* with neither label or both labels in between.
|
|
264
266
|
*/
|
|
265
267
|
setIssueLabel(number: number, currentLabel: string | null, newLabel: string): void;
|
|
266
268
|
}
|