@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/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
- * Tri-modal, same as the git client: queries return parsed values and never
11
- * throw, mutations throw `CommandFailed`, and `raw`/`rawChecked` are the
12
- * escape hatches.
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
- constructor(cwd: string, opts?: {
37
- runner?: CommandRunner | undefined;
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 `CommandFailed` on a non-zero exit. */
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
- /** Pull requests matching `opts`, or null when gh failed. */
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[] | null;
55
- /** One PR with the heavy fields, or null when gh failed. */
56
- prView(number: number, opts?: {
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, or null when gh failed. */
147
+ /** A PR's body text. Throws when gh fails. */
93
148
  prBody(number: number, opts?: {
94
149
  timeoutMs?: number | undefined;
95
- }): string | null;
150
+ }): string;
96
151
  /**
97
- * A REST GET through `gh api`. Never throws: failure is `ok: false` on the
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
- /** Every label on the repository (up to `opts.limit`, default 1000), or null when gh failed. */
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[] | null;
108
- /** Issues matching `opts` (open by default, up to 200), or null when gh failed. */
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[] | null;
112
- /** One issue with its labels, or null when gh failed. */
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 `CommandFailed` when gh cannot produce it. */
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, or null when they could not be read completely. */
175
- prReviewThreads(number: number): v.ReviewThread[] | null;
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
- * Tri-modal, same as the git client: queries return parsed values and never
11
- * throw, mutations throw `CommandFailed`, and `raw`/`rawChecked` are the
12
- * escape hatches.
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 { runChecked, resolveRunner, spawnRunner, } from '@osdlc/command-seam';
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 `CommandFailed` on a non-zero exit. */
89
+ /** Checked passthrough — throws the classified failure on a non-zero exit. */
45
90
  rawChecked(argv, opts = {}) {
46
- return runChecked(this.runner, argv, { cwd: this.cwd, ...opts });
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
- ? runChecked(this.runner, verb.argv, opts)
103
+ ? runCheckedGh(this.runner, verb.argv, opts)
57
104
  : this.runner.run(verb.argv, opts);
58
- return verb.parse(result);
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`, or null when gh failed. */
112
+ /** Pull requests matching `opts`. Throws when gh fails; `soft.prList` gives null instead. */
62
113
  prList(opts = {}) {
63
- return this.exec(v.prList(opts), opts.timeoutMs);
114
+ return this.read(v.prList(opts, this.target), opts.timeoutMs);
64
115
  }
65
- /** One PR with the heavy fields, or null when gh failed. */
66
- prView(number, opts = {}) {
67
- return this.exec(v.prView(number, opts.fields ?? v.PR_VIEW_FIELDS), opts.timeoutMs);
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, or null when gh failed. */
189
+ /** A PR's body text. Throws when gh fails. */
113
190
  prBody(number, opts = {}) {
114
- return this.exec(v.prBody(number), opts.timeoutMs);
191
+ return this.read(v.prBody(number, this.target), opts.timeoutMs);
115
192
  }
116
193
  /**
117
- * A REST GET through `gh api`. Never throws: failure is `ok: false` on the
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), or null when gh failed. */
209
+ /** Every label on the repository (up to `opts.limit`, default 1000). Throws when gh fails. */
124
210
  labelList(opts = {}) {
125
- return this.exec(v.labelList(opts), opts.timeoutMs);
211
+ return this.read(v.labelList(opts, this.target), opts.timeoutMs);
126
212
  }
127
- /** Issues matching `opts` (open by default, up to 200), or null when gh failed. */
213
+ /** Issues matching `opts` (open by default, up to 200). Throws when gh fails. */
128
214
  issueList(opts = {}) {
129
- return this.exec(v.issueList(opts), opts.timeoutMs);
215
+ return this.read(v.issueList(opts, this.target), opts.timeoutMs);
130
216
  }
131
- /** One issue with its labels, or null when gh failed. */
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 `CommandFailed` when gh cannot produce it. */
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, or null when they could not be read completely. */
316
+ /** A PR's review threads. Throws when they could not be read completely. */
219
317
  prReviewThreads(number) {
220
- return this.exec(v.prReviewThreads(number));
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
- //# sourceMappingURL=types.d.ts.map
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
+ }