@osdlc/typed-gh 0.5.0 → 0.7.0
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 +61 -1
- package/CONTRIBUTING.md +4 -3
- package/README.md +247 -56
- package/dist/async.d.ts +70 -28
- package/dist/async.js +127 -44
- package/dist/doors.d.ts +82 -0
- package/dist/doors.js +183 -0
- package/dist/failure.d.ts +59 -0
- package/dist/failure.js +93 -0
- package/dist/index.d.ts +12 -8
- package/dist/index.js +9 -4
- package/dist/parse.d.ts +5 -2
- package/dist/parse.js +48 -0
- package/dist/repo.d.ts +42 -11
- package/dist/repo.js +76 -12
- package/dist/sync.d.ts +104 -28
- package/dist/sync.js +155 -46
- package/dist/types.d.ts +37 -1
- package/dist/verbs.d.ts +213 -45
- package/dist/verbs.js +504 -86
- package/package.json +13 -17
- package/async.ts +0 -285
- package/dist/async.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/parse.d.ts.map +0 -1
- package/dist/repo.d.ts.map +0 -1
- package/dist/sync.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/verbs.d.ts.map +0 -1
- package/index.ts +0 -97
- package/parse.ts +0 -252
- package/repo.ts +0 -59
- package/sync.ts +0 -326
- package/types.ts +0 -186
- package/verbs.ts +0 -799
package/dist/sync.d.ts
CHANGED
|
@@ -7,14 +7,18 @@
|
|
|
7
7
|
* REST — the credential already exists in the user's keychain, and a library
|
|
8
8
|
* that reaches for `GITHUB_TOKEN` is a library that can leak one.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* The same three doors as the git client. The plain method returns a value
|
|
11
|
+
* or throws: a failed `gh` throws a `FailureError` carrying the classified
|
|
12
|
+
* `GhFailure`, output that cannot be read throws one of kind `unreadable`, and a query for one thing (`prView`, `issueView`,
|
|
13
|
+
* `repoView`) answers `null` when GitHub says there is no such thing.
|
|
14
|
+
* `gh.try.*` returns the failure as a `Result<T, GhFailure>`; `gh.soft.*` returns
|
|
15
|
+
* `null` for it. `raw`/`rawChecked` are the escape hatches.
|
|
13
16
|
*/
|
|
14
17
|
import { type CommandResult, type CommandRunner } from '@osdlc/command-seam';
|
|
18
|
+
import { type Soft, type TriedSync } from './doors.ts';
|
|
15
19
|
import * as v from './verbs.ts';
|
|
16
|
-
import type { ApiOptions, ApiResponse, AuthStatus, IssueListOptions, PrListOptions } from './verbs.ts';
|
|
17
|
-
import type { IssueRow, Label, OwnerRepo, PullRequest } from './types.ts';
|
|
20
|
+
import type { ApiOptions, ApiResponse, AuthStatus, GhTarget, IssueListOptions, ListResult, PrListOptions } from './verbs.ts';
|
|
21
|
+
import type { IssueRow, Label, OwnerRepo, PrSearchHit, PullRequest, Repository } from './types.ts';
|
|
18
22
|
/**
|
|
19
23
|
* Factory of per-binary runners — the injectable subprocess seam a CLI tail
|
|
20
24
|
* threads through its call graph. Production uses `spawnRunner`; tests supply
|
|
@@ -23,8 +27,19 @@ import type { IssueRow, Label, OwnerRepo, PullRequest } from './types.ts';
|
|
|
23
27
|
export type RunnerFactory = (binary: string) => CommandRunner;
|
|
24
28
|
/** Default factory: real, spawnSync-backed runners. */
|
|
25
29
|
export declare const defaultRunnerFactory: RunnerFactory;
|
|
30
|
+
/** What a client is built with: the runner, and the repository and host it talks to. */
|
|
31
|
+
export interface GhOptions extends GhTarget {
|
|
32
|
+
runner?: CommandRunner | undefined;
|
|
33
|
+
}
|
|
26
34
|
/**
|
|
27
|
-
* A typed gh client bound to one working directory
|
|
35
|
+
* A typed gh client bound to one working directory, and optionally to one
|
|
36
|
+
* repository and host.
|
|
37
|
+
*
|
|
38
|
+
* With `repo`, every call names that repository (`--repo`, or the
|
|
39
|
+
* `{owner}/{repo}` of an `api` endpoint), so no clone is needed. Without it,
|
|
40
|
+
* gh uses the repository of the clone at `cwd`. With `hostname`, every call
|
|
41
|
+
* goes to that GitHub host. An invalid `repo` or `hostname` throws
|
|
42
|
+
* `RangeError`.
|
|
28
43
|
*
|
|
29
44
|
* Every `timeoutMs` option is a hard cap on the call. An invalid value
|
|
30
45
|
* throws, as in command-seam: `RangeError` for 0, a negative value or `NaN`;
|
|
@@ -32,28 +47,68 @@ export declare const defaultRunnerFactory: RunnerFactory;
|
|
|
32
47
|
*/
|
|
33
48
|
export declare class Gh {
|
|
34
49
|
readonly cwd: string;
|
|
50
|
+
readonly repo: string | undefined;
|
|
51
|
+
readonly hostname: string | undefined;
|
|
52
|
+
private readonly target;
|
|
35
53
|
private readonly runner;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
/** The same client rooted at a different directory (same runner). */
|
|
54
|
+
private tried;
|
|
55
|
+
private softened;
|
|
56
|
+
constructor(cwd: string, opts?: GhOptions);
|
|
57
|
+
/** The same client rooted at a different directory (same runner, repo and host). */
|
|
40
58
|
at(cwd: string): Gh;
|
|
59
|
+
/** The same client aimed at another repository (same runner, directory and host). */
|
|
60
|
+
withRepo(repo: string): Gh;
|
|
61
|
+
/**
|
|
62
|
+
* The whole surface again, returning a `Result<T, GhFailure>` instead of
|
|
63
|
+
* throwing: `gh.try.prList()` is `{ ok: true, value } | { ok: false, error }`.
|
|
64
|
+
* A query for one thing that finds nothing is `ok: true` with `null`. See
|
|
65
|
+
* `./doors.ts`.
|
|
66
|
+
*
|
|
67
|
+
* The type leaves out the escape hatches and the pure helpers; at runtime
|
|
68
|
+
* the proxy wraps `raw` and `rawChecked` too. Call those on the client itself.
|
|
69
|
+
*/
|
|
70
|
+
get try(): TriedSync<Gh>;
|
|
71
|
+
/**
|
|
72
|
+
* Each query again, returning `null` instead of throwing when `gh` fails. A
|
|
73
|
+
* mutation has no soft form. Do not gate a write on a `null` from `soft`:
|
|
74
|
+
* it is the answer for "could not ask" as well as for "nothing there". Use
|
|
75
|
+
* `try` where the difference matters.
|
|
76
|
+
*/
|
|
77
|
+
get soft(): Soft<Gh>;
|
|
41
78
|
/** Unchecked passthrough for verbs not yet lifted into the API. */
|
|
42
79
|
raw(argv: string[], opts?: {
|
|
43
80
|
timeoutMs?: number | undefined;
|
|
44
81
|
}): CommandResult;
|
|
45
|
-
/** Checked passthrough — throws
|
|
82
|
+
/** Checked passthrough — throws the classified failure on a non-zero exit. */
|
|
46
83
|
rawChecked(argv: string[], opts?: {
|
|
47
84
|
timeoutMs?: number | undefined;
|
|
48
85
|
}): CommandResult;
|
|
49
86
|
/** Run one verb from the table, honouring its checked/unchecked contract. */
|
|
50
87
|
private exec;
|
|
51
|
-
/**
|
|
88
|
+
/** {@link exec} for a verb that never answers `null` as a value: a failed or unreadable run throws. */
|
|
89
|
+
private read;
|
|
90
|
+
/** Pull requests matching `opts`. Throws when gh fails; `soft.prList` gives null instead. */
|
|
52
91
|
prList(opts?: PrListOptions & {
|
|
53
92
|
timeoutMs?: number | undefined;
|
|
54
|
-
}): PullRequest
|
|
55
|
-
/**
|
|
56
|
-
|
|
93
|
+
}): ListResult<PullRequest>;
|
|
94
|
+
/**
|
|
95
|
+
* One PR by number, branch or URL, with the heavy fields; null when there is
|
|
96
|
+
* no such PR. Throws when gh fails otherwise.
|
|
97
|
+
*/
|
|
98
|
+
prView(ref: number | string, opts?: {
|
|
99
|
+
fields?: readonly string[] | undefined;
|
|
100
|
+
timeoutMs?: number | undefined;
|
|
101
|
+
}): PullRequest | null;
|
|
102
|
+
/**
|
|
103
|
+
* The one PR that `input` names, or null: what a person typed, such as `42`,
|
|
104
|
+
* `#42`, `owner/name#42`, a URL or a branch (read by `parsePrRef`).
|
|
105
|
+
*
|
|
106
|
+
* Digits are a PR number first; when no such PR comes back, they are tried
|
|
107
|
+
* as a branch. A branch gives its open PR, or else its newest closed or
|
|
108
|
+
* merged one, so a tool run after the merge still finds it. Fields default
|
|
109
|
+
* to {@link PR_LIST_FIELDS}.
|
|
110
|
+
*/
|
|
111
|
+
prFind(input: string, opts?: {
|
|
57
112
|
fields?: readonly string[] | undefined;
|
|
58
113
|
timeoutMs?: number | undefined;
|
|
59
114
|
}): PullRequest | null;
|
|
@@ -89,30 +144,52 @@ export declare class Gh {
|
|
|
89
144
|
sleep?: ((ms: number) => void) | undefined;
|
|
90
145
|
timeoutMs?: number | undefined;
|
|
91
146
|
}): PullRequest | null;
|
|
92
|
-
/** A PR's body text
|
|
147
|
+
/** A PR's body text. Throws when gh fails. */
|
|
93
148
|
prBody(number: number, opts?: {
|
|
94
149
|
timeoutMs?: number | undefined;
|
|
95
|
-
}): string
|
|
150
|
+
}): string;
|
|
96
151
|
/**
|
|
97
|
-
* A REST GET through `gh api`. Never throws
|
|
98
|
-
* returned {@link ApiResponse}.
|
|
152
|
+
* A REST GET through `gh api`. Never throws for a failed call, in any door:
|
|
153
|
+
* failure is `ok: false` on the returned {@link ApiResponse}.
|
|
99
154
|
*/
|
|
100
155
|
api(endpoint: string, opts?: ApiOptions & {
|
|
101
156
|
timeoutMs?: number | undefined;
|
|
102
157
|
}): ApiResponse;
|
|
103
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* A GraphQL query; each page's `data`. Throws on a failed or partial result;
|
|
160
|
+
* `soft.graphql` gives null. See the `graphql` verb.
|
|
161
|
+
*
|
|
162
|
+
* @experimental
|
|
163
|
+
*/
|
|
164
|
+
graphql(query: string, variables?: Record<string, v.GraphqlVariable>, opts?: v.GraphqlOptions & {
|
|
165
|
+
timeoutMs?: number | undefined;
|
|
166
|
+
}): unknown[];
|
|
167
|
+
/** Every label on the repository (up to `opts.limit`, default 1000). Throws when gh fails. */
|
|
104
168
|
labelList(opts?: {
|
|
105
169
|
limit?: number | undefined;
|
|
106
170
|
timeoutMs?: number | undefined;
|
|
107
|
-
}): Label
|
|
108
|
-
/** Issues matching `opts` (open by default, up to 200)
|
|
171
|
+
}): ListResult<Label>;
|
|
172
|
+
/** Issues matching `opts` (open by default, up to 200). Throws when gh fails. */
|
|
109
173
|
issueList(opts?: IssueListOptions & {
|
|
110
174
|
timeoutMs?: number | undefined;
|
|
111
|
-
}): IssueRow
|
|
112
|
-
/** One issue with its labels
|
|
175
|
+
}): ListResult<IssueRow>;
|
|
176
|
+
/** One issue with its labels; null when there is no such issue. Throws when gh fails otherwise. */
|
|
113
177
|
issueView(number: number, opts?: {
|
|
114
178
|
timeoutMs?: number | undefined;
|
|
115
179
|
}): IssueRow | null;
|
|
180
|
+
/** An owner's repositories (up to `opts.limit`, default 100). Throws when gh fails. */
|
|
181
|
+
repoList(owner: string, opts?: v.RepoListOptions & {
|
|
182
|
+
timeoutMs?: number | undefined;
|
|
183
|
+
}): ListResult<Repository>;
|
|
184
|
+
/** One repository: `repo`, else the client's repo, else the clone at `cwd`. Null when there is no such repository. */
|
|
185
|
+
repoView(repo?: string | undefined, opts?: {
|
|
186
|
+
fields?: readonly string[] | undefined;
|
|
187
|
+
timeoutMs?: number | undefined;
|
|
188
|
+
}): Repository | null;
|
|
189
|
+
/** Pull requests across repositories matching a search query. Throws when gh fails. */
|
|
190
|
+
searchPrs(query: string, opts?: v.SearchPrsOptions & {
|
|
191
|
+
timeoutMs?: number | undefined;
|
|
192
|
+
}): ListResult<PrSearchHit>;
|
|
116
193
|
/** True iff the `gh` binary is on PATH and runs. */
|
|
117
194
|
installed(): boolean;
|
|
118
195
|
/**
|
|
@@ -144,7 +221,7 @@ export declare class Gh {
|
|
|
144
221
|
title?: string | undefined;
|
|
145
222
|
body?: string | undefined;
|
|
146
223
|
}): void;
|
|
147
|
-
/** The PR's unified diff; throws
|
|
224
|
+
/** The PR's unified diff; throws the classified failure when gh cannot produce it. */
|
|
148
225
|
prDiff(number: number): string;
|
|
149
226
|
/** Post one conversation comment on a PR; returns its URL ('' if gh printed none). */
|
|
150
227
|
prComment(number: number, body: string): string;
|
|
@@ -171,8 +248,8 @@ export declare class Gh {
|
|
|
171
248
|
method: v.PrMergeMethod;
|
|
172
249
|
headSha: string;
|
|
173
250
|
}): void;
|
|
174
|
-
/** A PR's review threads
|
|
175
|
-
prReviewThreads(number: number): v.ReviewThread[]
|
|
251
|
+
/** A PR's review threads. Throws when they could not be read completely. */
|
|
252
|
+
prReviewThreads(number: number): v.ReviewThread[];
|
|
176
253
|
/** Create a label (or force it to `opts`'s color/description if it already exists). */
|
|
177
254
|
createLabel(opts: v.CreateLabelOptions): void;
|
|
178
255
|
/** Recolor and/or redescribe an existing label. */
|
|
@@ -187,4 +264,3 @@ export declare class Gh {
|
|
|
187
264
|
*/
|
|
188
265
|
setIssueLabel(number: number, currentLabel: string | null, newLabel: string): void;
|
|
189
266
|
}
|
|
190
|
-
//# sourceMappingURL=sync.d.ts.map
|
package/dist/sync.js
CHANGED
|
@@ -7,19 +7,30 @@
|
|
|
7
7
|
* REST — the credential already exists in the user's keychain, and a library
|
|
8
8
|
* that reaches for `GITHUB_TOKEN` is a library that can leak one.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* The same three doors as the git client. The plain method returns a value
|
|
11
|
+
* or throws: a failed `gh` throws a `FailureError` carrying the classified
|
|
12
|
+
* `GhFailure`, output that cannot be read throws one of kind `unreadable`, and a query for one thing (`prView`, `issueView`,
|
|
13
|
+
* `repoView`) answers `null` when GitHub says there is no such thing.
|
|
14
|
+
* `gh.try.*` returns the failure as a `Result<T, GhFailure>`; `gh.soft.*` returns
|
|
15
|
+
* `null` for it. `raw`/`rawChecked` are the escape hatches.
|
|
13
16
|
*/
|
|
14
|
-
import {
|
|
17
|
+
import { resolveRunner, spawnRunner, } from '@osdlc/command-seam';
|
|
18
|
+
import { runCheckedGh, settle, softSync, triedSync } from "./doors.js";
|
|
15
19
|
import * as v from "./verbs.js";
|
|
16
|
-
import { parseOwnerRepo } from "./repo.js";
|
|
20
|
+
import { ownerRepoSlug, parseOwnerRepo, parsePrRef } from "./repo.js";
|
|
17
21
|
/** Default factory: real, spawnSync-backed runners. */
|
|
18
22
|
export const defaultRunnerFactory = spawnRunner;
|
|
19
23
|
/** How long to wait between merge-state retries, in milliseconds. */
|
|
20
24
|
const MERGE_STATE_RETRY_DELAY_MS = 1_500;
|
|
21
25
|
/**
|
|
22
|
-
* A typed gh client bound to one working directory
|
|
26
|
+
* A typed gh client bound to one working directory, and optionally to one
|
|
27
|
+
* repository and host.
|
|
28
|
+
*
|
|
29
|
+
* With `repo`, every call names that repository (`--repo`, or the
|
|
30
|
+
* `{owner}/{repo}` of an `api` endpoint), so no clone is needed. Without it,
|
|
31
|
+
* gh uses the repository of the clone at `cwd`. With `hostname`, every call
|
|
32
|
+
* goes to that GitHub host. An invalid `repo` or `hostname` throws
|
|
33
|
+
* `RangeError`.
|
|
23
34
|
*
|
|
24
35
|
* Every `timeoutMs` option is a hard cap on the call. An invalid value
|
|
25
36
|
* throws, as in command-seam: `RangeError` for 0, a negative value or `NaN`;
|
|
@@ -27,44 +38,110 @@ const MERGE_STATE_RETRY_DELAY_MS = 1_500;
|
|
|
27
38
|
*/
|
|
28
39
|
export class Gh {
|
|
29
40
|
cwd;
|
|
41
|
+
repo;
|
|
42
|
+
hostname;
|
|
43
|
+
target;
|
|
30
44
|
runner;
|
|
45
|
+
tried;
|
|
46
|
+
softened;
|
|
31
47
|
constructor(cwd, opts = {}) {
|
|
48
|
+
v.checkTarget(opts);
|
|
32
49
|
this.cwd = cwd;
|
|
50
|
+
this.repo = opts.repo;
|
|
51
|
+
this.hostname = opts.hostname;
|
|
52
|
+
this.target = { repo: opts.repo, hostname: opts.hostname };
|
|
33
53
|
this.runner = resolveRunner('gh', opts.runner);
|
|
34
54
|
}
|
|
35
|
-
/** The same client rooted at a different directory (same runner). */
|
|
55
|
+
/** The same client rooted at a different directory (same runner, repo and host). */
|
|
36
56
|
at(cwd) {
|
|
37
|
-
return new Gh(cwd, { runner: this.runner });
|
|
57
|
+
return new Gh(cwd, { runner: this.runner, repo: this.repo, hostname: this.hostname });
|
|
58
|
+
}
|
|
59
|
+
/** The same client aimed at another repository (same runner, directory and host). */
|
|
60
|
+
withRepo(repo) {
|
|
61
|
+
return new Gh(this.cwd, { runner: this.runner, repo, hostname: this.hostname });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The whole surface again, returning a `Result<T, GhFailure>` instead of
|
|
65
|
+
* throwing: `gh.try.prList()` is `{ ok: true, value } | { ok: false, error }`.
|
|
66
|
+
* A query for one thing that finds nothing is `ok: true` with `null`. See
|
|
67
|
+
* `./doors.ts`.
|
|
68
|
+
*
|
|
69
|
+
* The type leaves out the escape hatches and the pure helpers; at runtime
|
|
70
|
+
* the proxy wraps `raw` and `rawChecked` too. Call those on the client itself.
|
|
71
|
+
*/
|
|
72
|
+
get try() {
|
|
73
|
+
return (this.tried ??= triedSync(this));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Each query again, returning `null` instead of throwing when `gh` fails. A
|
|
77
|
+
* mutation has no soft form. Do not gate a write on a `null` from `soft`:
|
|
78
|
+
* it is the answer for "could not ask" as well as for "nothing there". Use
|
|
79
|
+
* `try` where the difference matters.
|
|
80
|
+
*/
|
|
81
|
+
get soft() {
|
|
82
|
+
return (this.softened ??= softSync(this));
|
|
38
83
|
}
|
|
39
84
|
// ── escape hatches ────────────────────────────────────────────────────────
|
|
40
85
|
/** Unchecked passthrough for verbs not yet lifted into the API. */
|
|
41
86
|
raw(argv, opts = {}) {
|
|
42
87
|
return this.runner.run(argv, { cwd: this.cwd, ...opts });
|
|
43
88
|
}
|
|
44
|
-
/** Checked passthrough — throws
|
|
89
|
+
/** Checked passthrough — throws the classified failure on a non-zero exit. */
|
|
45
90
|
rawChecked(argv, opts = {}) {
|
|
46
|
-
return
|
|
91
|
+
return runCheckedGh(this.runner, argv, { cwd: this.cwd, ...opts });
|
|
47
92
|
}
|
|
48
93
|
/** Run one verb from the table, honouring its checked/unchecked contract. */
|
|
49
94
|
exec(verb, timeoutMs) {
|
|
50
95
|
const opts = { cwd: this.cwd };
|
|
51
96
|
if (verb.input !== undefined)
|
|
52
97
|
opts.input = verb.input;
|
|
98
|
+
if (verb.env !== undefined)
|
|
99
|
+
opts.env = verb.env;
|
|
53
100
|
if (timeoutMs !== undefined)
|
|
54
101
|
opts.timeoutMs = timeoutMs;
|
|
55
102
|
const result = verb.checked
|
|
56
|
-
?
|
|
103
|
+
? runCheckedGh(this.runner, verb.argv, opts)
|
|
57
104
|
: this.runner.run(verb.argv, opts);
|
|
58
|
-
return verb.
|
|
105
|
+
return settle(verb, result, this.runner.binary);
|
|
106
|
+
}
|
|
107
|
+
/** {@link exec} for a verb that never answers `null` as a value: a failed or unreadable run throws. */
|
|
108
|
+
read(verb, timeoutMs) {
|
|
109
|
+
return this.exec(verb, timeoutMs);
|
|
59
110
|
}
|
|
60
111
|
// ── queries ───────────────────────────────────────────────────────────────
|
|
61
|
-
/** Pull requests matching `opts
|
|
112
|
+
/** Pull requests matching `opts`. Throws when gh fails; `soft.prList` gives null instead. */
|
|
62
113
|
prList(opts = {}) {
|
|
63
|
-
return this.
|
|
114
|
+
return this.read(v.prList(opts, this.target), opts.timeoutMs);
|
|
64
115
|
}
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
116
|
+
/**
|
|
117
|
+
* One PR by number, branch or URL, with the heavy fields; null when there is
|
|
118
|
+
* no such PR. Throws when gh fails otherwise.
|
|
119
|
+
*/
|
|
120
|
+
prView(ref, opts = {}) {
|
|
121
|
+
return this.exec(v.prView(ref, opts.fields ?? v.PR_VIEW_FIELDS, this.target), opts.timeoutMs);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The one PR that `input` names, or null: what a person typed, such as `42`,
|
|
125
|
+
* `#42`, `owner/name#42`, a URL or a branch (read by `parsePrRef`).
|
|
126
|
+
*
|
|
127
|
+
* Digits are a PR number first; when no such PR comes back, they are tried
|
|
128
|
+
* as a branch. A branch gives its open PR, or else its newest closed or
|
|
129
|
+
* merged one, so a tool run after the merge still finds it. Fields default
|
|
130
|
+
* to {@link PR_LIST_FIELDS}.
|
|
131
|
+
*/
|
|
132
|
+
prFind(input, opts = {}) {
|
|
133
|
+
const ref = parsePrRef(input);
|
|
134
|
+
if (ref === null)
|
|
135
|
+
return null;
|
|
136
|
+
const fields = opts.fields ?? v.PR_LIST_FIELDS;
|
|
137
|
+
const call = { fields, timeoutMs: opts.timeoutMs };
|
|
138
|
+
if (ref.kind === 'url')
|
|
139
|
+
return this.prView(ref.url, call);
|
|
140
|
+
if (ref.kind === 'branch')
|
|
141
|
+
return prForBranchOn(this, ref.name, call);
|
|
142
|
+
const client = ref.repo === null ? this : this.withRepo(ownerRepoSlug(ref.repo));
|
|
143
|
+
const pr = client.prView(ref.number, call);
|
|
144
|
+
return pr !== null || ref.orBranch === null ? pr : prForBranchOn(this, ref.orBranch, call);
|
|
68
145
|
}
|
|
69
146
|
/**
|
|
70
147
|
* The open PR whose head exactly equals `branch`, or null.
|
|
@@ -82,7 +159,7 @@ export class Gh {
|
|
|
82
159
|
limit: opts.limit ?? 5,
|
|
83
160
|
fields: opts.fields ?? v.PR_REF_FIELDS,
|
|
84
161
|
});
|
|
85
|
-
return v.pickPrForBranch(rows, branch);
|
|
162
|
+
return v.pickPrForBranch(rows.rows, branch);
|
|
86
163
|
}
|
|
87
164
|
/**
|
|
88
165
|
* {@link prView}, re-asked until GitHub settles the merge state.
|
|
@@ -109,28 +186,49 @@ export class Gh {
|
|
|
109
186
|
}
|
|
110
187
|
return last;
|
|
111
188
|
}
|
|
112
|
-
/** A PR's body text
|
|
189
|
+
/** A PR's body text. Throws when gh fails. */
|
|
113
190
|
prBody(number, opts = {}) {
|
|
114
|
-
return this.
|
|
191
|
+
return this.read(v.prBody(number, this.target), opts.timeoutMs);
|
|
115
192
|
}
|
|
116
193
|
/**
|
|
117
|
-
* A REST GET through `gh api`. Never throws
|
|
118
|
-
* returned {@link ApiResponse}.
|
|
194
|
+
* A REST GET through `gh api`. Never throws for a failed call, in any door:
|
|
195
|
+
* failure is `ok: false` on the returned {@link ApiResponse}.
|
|
119
196
|
*/
|
|
120
197
|
api(endpoint, opts = {}) {
|
|
121
|
-
return this.exec(v.api(endpoint, opts), opts.timeoutMs);
|
|
198
|
+
return this.exec(v.api(endpoint, opts, this.target), opts.timeoutMs);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* A GraphQL query; each page's `data`. Throws on a failed or partial result;
|
|
202
|
+
* `soft.graphql` gives null. See the `graphql` verb.
|
|
203
|
+
*
|
|
204
|
+
* @experimental
|
|
205
|
+
*/
|
|
206
|
+
graphql(query, variables = {}, opts = {}) {
|
|
207
|
+
return this.read(v.graphql(query, variables, opts, this.target), opts.timeoutMs);
|
|
122
208
|
}
|
|
123
|
-
/** Every label on the repository (up to `opts.limit`, default 1000)
|
|
209
|
+
/** Every label on the repository (up to `opts.limit`, default 1000). Throws when gh fails. */
|
|
124
210
|
labelList(opts = {}) {
|
|
125
|
-
return this.
|
|
211
|
+
return this.read(v.labelList(opts, this.target), opts.timeoutMs);
|
|
126
212
|
}
|
|
127
|
-
/** Issues matching `opts` (open by default, up to 200)
|
|
213
|
+
/** Issues matching `opts` (open by default, up to 200). Throws when gh fails. */
|
|
128
214
|
issueList(opts = {}) {
|
|
129
|
-
return this.
|
|
215
|
+
return this.read(v.issueList(opts, this.target), opts.timeoutMs);
|
|
130
216
|
}
|
|
131
|
-
/** One issue with its labels
|
|
217
|
+
/** One issue with its labels; null when there is no such issue. Throws when gh fails otherwise. */
|
|
132
218
|
issueView(number, opts = {}) {
|
|
133
|
-
return this.exec(v.issueView(number), opts.timeoutMs);
|
|
219
|
+
return this.exec(v.issueView(number, this.target), opts.timeoutMs);
|
|
220
|
+
}
|
|
221
|
+
/** An owner's repositories (up to `opts.limit`, default 100). Throws when gh fails. */
|
|
222
|
+
repoList(owner, opts = {}) {
|
|
223
|
+
return this.read(v.repoList(owner, opts, this.target), opts.timeoutMs);
|
|
224
|
+
}
|
|
225
|
+
/** One repository: `repo`, else the client's repo, else the clone at `cwd`. Null when there is no such repository. */
|
|
226
|
+
repoView(repo, opts = {}) {
|
|
227
|
+
return this.exec(v.repoView(repo, opts.fields ?? v.REPO_FIELDS, this.target), opts.timeoutMs);
|
|
228
|
+
}
|
|
229
|
+
/** Pull requests across repositories matching a search query. Throws when gh fails. */
|
|
230
|
+
searchPrs(query, opts = {}) {
|
|
231
|
+
return this.read(v.searchPrs(query, opts, this.target), opts.timeoutMs);
|
|
134
232
|
}
|
|
135
233
|
/** True iff the `gh` binary is on PATH and runs. */
|
|
136
234
|
installed() {
|
|
@@ -147,7 +245,7 @@ export class Gh {
|
|
|
147
245
|
}
|
|
148
246
|
/** Login state and what `gh auth status` printed; `authenticated()` is its `ok`. */
|
|
149
247
|
authStatus() {
|
|
150
|
-
return this.exec(v.authStatus());
|
|
248
|
+
return this.exec(v.authStatus(this.target));
|
|
151
249
|
}
|
|
152
250
|
/**
|
|
153
251
|
* True iff `gh` is installed AND authenticated — the two-step probe.
|
|
@@ -169,19 +267,19 @@ export class Gh {
|
|
|
169
267
|
// ── mutations (checked) ───────────────────────────────────────────────────
|
|
170
268
|
/** Open a PR and return its URL ('' if gh printed none). */
|
|
171
269
|
createPr(opts) {
|
|
172
|
-
return this.exec(v.createPr(opts));
|
|
270
|
+
return this.exec(v.createPr(opts, this.target));
|
|
173
271
|
}
|
|
174
272
|
/** Retitle or rewrite a PR body. */
|
|
175
273
|
editPr(number, opts) {
|
|
176
|
-
this.exec(v.editPr(number, opts));
|
|
274
|
+
this.exec(v.editPr(number, opts, this.target));
|
|
177
275
|
}
|
|
178
|
-
/** The PR's unified diff; throws
|
|
276
|
+
/** The PR's unified diff; throws the classified failure when gh cannot produce it. */
|
|
179
277
|
prDiff(number) {
|
|
180
|
-
return this.exec(v.prDiff(number));
|
|
278
|
+
return this.exec(v.prDiff(number, this.target));
|
|
181
279
|
}
|
|
182
280
|
/** Post one conversation comment on a PR; returns its URL ('' if gh printed none). */
|
|
183
281
|
prComment(number, body) {
|
|
184
|
-
return this.exec(v.prComment(number, body));
|
|
282
|
+
return this.exec(v.prComment(number, body, this.target));
|
|
185
283
|
}
|
|
186
284
|
/**
|
|
187
285
|
* Create a `COMMENT` review with inline comments; returns the review's URL ('' if none came back).
|
|
@@ -189,19 +287,19 @@ export class Gh {
|
|
|
189
287
|
* @experimental
|
|
190
288
|
*/
|
|
191
289
|
createReview(number, opts) {
|
|
192
|
-
return this.exec(v.createReview(number, opts));
|
|
290
|
+
return this.exec(v.createReview(number, opts, this.target));
|
|
193
291
|
}
|
|
194
292
|
/** Rewrite just a PR's body. */
|
|
195
293
|
editPrBody(number, body) {
|
|
196
|
-
this.exec(v.editPrBody(number, body));
|
|
294
|
+
this.exec(v.editPrBody(number, body, this.target));
|
|
197
295
|
}
|
|
198
296
|
/** Bring a PR's head branch up to date with its base. */
|
|
199
297
|
prUpdateBranch(number, style = 'merge') {
|
|
200
|
-
this.exec(v.prUpdateBranch(number, style));
|
|
298
|
+
this.exec(v.prUpdateBranch(number, style, this.target));
|
|
201
299
|
}
|
|
202
300
|
/** Mark a PR ready for review (`draft: false`) or convert it to a draft (`draft: true`). */
|
|
203
301
|
prSetDraft(number, draft) {
|
|
204
|
-
this.exec(v.prSetDraft(number, draft));
|
|
302
|
+
this.exec(v.prSetDraft(number, draft, this.target));
|
|
205
303
|
}
|
|
206
304
|
/**
|
|
207
305
|
* Rewrite one PR conversation comment's body. `commentId` is the numeric REST id; anything else throws `RangeError`.
|
|
@@ -209,23 +307,23 @@ export class Gh {
|
|
|
209
307
|
* @experimental
|
|
210
308
|
*/
|
|
211
309
|
issueCommentEdit(commentId, body) {
|
|
212
|
-
this.exec(v.issueCommentEdit(commentId, body));
|
|
310
|
+
this.exec(v.issueCommentEdit(commentId, body, this.target));
|
|
213
311
|
}
|
|
214
312
|
/** Merge a PR; GitHub refuses when its head is no longer `opts.headSha`. */
|
|
215
313
|
prMerge(number, opts) {
|
|
216
|
-
this.exec(v.prMerge(number, opts));
|
|
314
|
+
this.exec(v.prMerge(number, opts, this.target));
|
|
217
315
|
}
|
|
218
|
-
/** A PR's review threads
|
|
316
|
+
/** A PR's review threads. Throws when they could not be read completely. */
|
|
219
317
|
prReviewThreads(number) {
|
|
220
|
-
return this.
|
|
318
|
+
return this.read(v.prReviewThreads(number, this.target));
|
|
221
319
|
}
|
|
222
320
|
/** Create a label (or force it to `opts`'s color/description if it already exists). */
|
|
223
321
|
createLabel(opts) {
|
|
224
|
-
this.exec(v.createLabel(opts));
|
|
322
|
+
this.exec(v.createLabel(opts, this.target));
|
|
225
323
|
}
|
|
226
324
|
/** Recolor and/or redescribe an existing label. */
|
|
227
325
|
editLabel(name, opts) {
|
|
228
|
-
this.exec(v.editLabel(name, opts));
|
|
326
|
+
this.exec(v.editLabel(name, opts, this.target));
|
|
229
327
|
}
|
|
230
328
|
/**
|
|
231
329
|
* Swap an issue's label: remove `currentLabel` (when given) and add
|
|
@@ -233,9 +331,20 @@ export class Gh {
|
|
|
233
331
|
* or briefly-double-labeled intermediate state.
|
|
234
332
|
*/
|
|
235
333
|
setIssueLabel(number, currentLabel, newLabel) {
|
|
236
|
-
this.exec(v.setIssueLabel(number, currentLabel, newLabel));
|
|
334
|
+
this.exec(v.setIssueLabel(number, currentLabel, newLabel, this.target));
|
|
237
335
|
}
|
|
238
336
|
}
|
|
337
|
+
/** The open PR for `branch`, else its newest closed or merged one. */
|
|
338
|
+
function prForBranchOn(gh, branch, call) {
|
|
339
|
+
const rows = gh.prList({
|
|
340
|
+
head: branch,
|
|
341
|
+
state: 'all',
|
|
342
|
+
limit: 20,
|
|
343
|
+
fields: v.branchPrFields(call.fields),
|
|
344
|
+
timeoutMs: call.timeoutMs,
|
|
345
|
+
});
|
|
346
|
+
return v.branchPr(rows);
|
|
347
|
+
}
|
|
239
348
|
/**
|
|
240
349
|
* Block the calling thread. Crude on purpose: the synchronous client has no
|
|
241
350
|
* event loop to await on, and the alternative — making every caller async to
|
package/dist/types.d.ts
CHANGED
|
@@ -149,4 +149,40 @@ export interface OwnerRepo {
|
|
|
149
149
|
owner: string;
|
|
150
150
|
repo: string;
|
|
151
151
|
}
|
|
152
|
-
|
|
152
|
+
/** Who can see a repository. */
|
|
153
|
+
export type RepositoryVisibility = 'PUBLIC' | 'PRIVATE' | 'INTERNAL' | (string & {});
|
|
154
|
+
/**
|
|
155
|
+
* A repository, off `gh repo list`/`gh repo view --json`. A field not asked
|
|
156
|
+
* for comes back at its "not known" value. `nameWithOwner` is the only field
|
|
157
|
+
* without one: a row without it is dropped.
|
|
158
|
+
*/
|
|
159
|
+
export interface Repository {
|
|
160
|
+
/** `owner/name`. */
|
|
161
|
+
nameWithOwner: string;
|
|
162
|
+
owner: string;
|
|
163
|
+
name: string;
|
|
164
|
+
description: string;
|
|
165
|
+
/** The default branch; null when not asked for, or the repository is empty. */
|
|
166
|
+
defaultBranch: string | null;
|
|
167
|
+
visibility: RepositoryVisibility | null;
|
|
168
|
+
isArchived: boolean;
|
|
169
|
+
url: string;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* One pull request found by `gh search prs`. Search has its own field set:
|
|
173
|
+
* it names the repository but carries no head or base branch, so this is not
|
|
174
|
+
* a {@link PullRequest}. `state` is upper-cased to match `PullRequest`.
|
|
175
|
+
*/
|
|
176
|
+
export interface PrSearchHit {
|
|
177
|
+
number: number;
|
|
178
|
+
title: string;
|
|
179
|
+
state: PullRequestState;
|
|
180
|
+
url: string;
|
|
181
|
+
isDraft: boolean;
|
|
182
|
+
repository: OwnerRepo | null;
|
|
183
|
+
author: Actor | null;
|
|
184
|
+
labels: Label[];
|
|
185
|
+
createdAt: string | null;
|
|
186
|
+
updatedAt: string | null;
|
|
187
|
+
closedAt: string | null;
|
|
188
|
+
}
|