@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/async.d.ts CHANGED
@@ -1,20 +1,23 @@
1
1
  /**
2
2
  * `AsyncGh` — the awaitable GitHub client, with the same methods as {@link Gh}.
3
3
  *
4
- * Same tri-modal contract, promised: queries resolve to parsed values and
5
- * never reject on a gh failure, mutations reject with `CommandFailed`, and
6
- * `raw`/`rawChecked` are the hatches. It is driven by the same pure verb
4
+ * Same three doors, promised: the plain method resolves to a value or rejects
5
+ * (a `FailureError` carrying the classified `GhFailure` for a failed gh or output it cannot read;
6
+ * a query for one thing resolves `null` when GitHub has no such thing),
7
+ * `try` resolves to a `Result<T, GhFailure>`, `soft` resolves `null` for a
8
+ * failure, and `raw`/`rawChecked` are the hatches. It is driven by the same pure verb
7
9
  * table as `Gh`, so the two cannot drift on argv or parsing. Use it where the
8
10
  * sync client would block the event loop: a fan-out of per-PR queries.
9
11
  */
10
12
  import { type AsyncCommandRunner, type CommandResult } from '@osdlc/command-seam';
11
- import type { IssueRow, Label, OwnerRepo, PullRequest } from './types.ts';
13
+ import { type Soft, type TriedAsync } from './doors.ts';
14
+ import type { IssueRow, Label, OwnerRepo, PrSearchHit, PullRequest, Repository } from './types.ts';
12
15
  import * as v from './verbs.ts';
13
- import type { ApiOptions, ApiResponse, AuthStatus, IssueListOptions, PrListOptions } from './verbs.ts';
16
+ import type { ApiOptions, ApiResponse, AuthStatus, GhTarget, IssueListOptions, ListResult, PrListOptions } from './verbs.ts';
14
17
  /**
15
- * Per-call options for {@link AsyncGh}. Eleven methods accept them: `raw`, `rawChecked`,
16
- * `prList`, `prView`, `openPrFor`, `prViewSettled`, `prBody`, `api`, `labelList`,
17
- * `issueList` and `issueView`.
18
+ * Per-call options for {@link AsyncGh}. Sixteen methods accept them: `raw`, `rawChecked`,
19
+ * `prList`, `prView`, `prFind`, `openPrFor`, `prViewSettled`, `prBody`, `api`, `graphql`,
20
+ * `labelList`, `issueList`, `issueView`, `repoList`, `repoView` and `searchPrs`.
18
21
  */
19
22
  export interface AsyncCallOptions {
20
23
  /**
@@ -26,25 +29,51 @@ export interface AsyncCallOptions {
26
29
  /** Aborts the call; the runner stops the child. */
27
30
  signal?: AbortSignal | undefined;
28
31
  }
29
- /** A typed, awaitable gh client bound to one working directory. */
32
+ /** What an {@link AsyncGh} is built with: the runner, and the repository and host it talks to. */
33
+ export interface AsyncGhOptions extends GhTarget {
34
+ runner?: AsyncCommandRunner | undefined;
35
+ }
36
+ /**
37
+ * A typed, awaitable gh client bound to one working directory, and optionally
38
+ * to one repository and host. `repo` and `hostname` work as on `Gh`.
39
+ */
30
40
  export declare class AsyncGh {
31
41
  readonly cwd: string;
42
+ readonly repo: string | undefined;
43
+ readonly hostname: string | undefined;
44
+ private readonly target;
32
45
  private readonly runner;
33
- constructor(cwd: string, opts?: {
34
- runner?: AsyncCommandRunner | undefined;
35
- });
36
- /** The same client rooted at a different directory (same runner). */
46
+ private tried;
47
+ private softened;
48
+ constructor(cwd: string, opts?: AsyncGhOptions);
49
+ /** The same client rooted at a different directory (same runner, repo and host). */
37
50
  at(cwd: string): AsyncGh;
51
+ /** The same client aimed at another repository (same runner, directory and host). */
52
+ withRepo(repo: string): AsyncGh;
53
+ /**
54
+ * The whole surface again, resolving to a `Result<T, GhFailure>` instead of
55
+ * rejecting. A query for one thing that finds nothing is `ok: true` with
56
+ * `null`. See `Gh.try`.
57
+ */
58
+ get try(): TriedAsync<AsyncGh>;
59
+ /** Each query again, resolving `null` instead of rejecting when `gh` fails. See `Gh.soft`. */
60
+ get soft(): Soft<AsyncGh>;
38
61
  /** Unchecked passthrough for verbs not yet lifted into the API. */
39
62
  raw(argv: string[], opts?: AsyncCallOptions): Promise<CommandResult>;
40
- /** Checked passthrough — rejects with `CommandFailed` on a non-zero exit. */
63
+ /** Checked passthrough — rejects with the classified failure on a non-zero exit. */
41
64
  rawChecked(argv: string[], opts?: AsyncCallOptions): Promise<CommandResult>;
42
65
  /** Run one verb from the table, honouring its checked/unchecked contract. */
43
66
  private exec;
44
- /** Pull requests matching `opts`, or null when gh failed. */
45
- prList(opts?: PrListOptions & AsyncCallOptions): Promise<PullRequest[] | null>;
46
- /** One PR with the heavy fields, or null when gh failed. */
47
- prView(number: number, opts?: {
67
+ /** {@link exec} for a verb that never answers `null` as a value: a failed or unreadable run rejects. */
68
+ private read;
69
+ /** Pull requests matching `opts`. Rejects when gh fails; `soft.prList` resolves null instead. */
70
+ prList(opts?: PrListOptions & AsyncCallOptions): Promise<ListResult<PullRequest>>;
71
+ /** One PR by number, branch or URL, with the heavy fields, or null. See `Gh.prView`. */
72
+ prView(ref: number | string, opts?: {
73
+ fields?: readonly string[] | undefined;
74
+ } & AsyncCallOptions): Promise<PullRequest | null>;
75
+ /** The one PR that `input` names, or null. See `Gh.prFind`. */
76
+ prFind(input: string, opts?: {
48
77
  fields?: readonly string[] | undefined;
49
78
  } & AsyncCallOptions): Promise<PullRequest | null>;
50
79
  /** The open PR whose head exactly equals `branch`, or null. See `Gh.openPrFor`. */
@@ -58,18 +87,32 @@ export declare class AsyncGh {
58
87
  fields?: readonly string[] | undefined;
59
88
  sleep?: ((ms: number) => Promise<void>) | undefined;
60
89
  } & AsyncCallOptions): Promise<PullRequest | null>;
61
- /** A PR's body text, or null when gh failed. */
62
- prBody(number: number, opts?: AsyncCallOptions): Promise<string | null>;
90
+ /** A PR's body text. Rejects when gh fails. */
91
+ prBody(number: number, opts?: AsyncCallOptions): Promise<string>;
63
92
  /** A REST GET through `gh api`; failure is `ok: false`, never a rejection. */
64
93
  api(endpoint: string, opts?: ApiOptions & AsyncCallOptions): Promise<ApiResponse>;
65
- /** Every label on the repository (up to `opts.limit`, default 1000), or null when gh failed. */
94
+ /**
95
+ * A GraphQL query; each page's `data`. Rejects on a failed or partial result. See `Gh.graphql`.
96
+ *
97
+ * @experimental
98
+ */
99
+ graphql(query: string, variables?: Record<string, v.GraphqlVariable>, opts?: v.GraphqlOptions & AsyncCallOptions): Promise<unknown[]>;
100
+ /** Every label on the repository (up to `opts.limit`, default 1000). Rejects when gh fails. */
66
101
  labelList(opts?: {
67
102
  limit?: number | undefined;
68
- } & AsyncCallOptions): Promise<Label[] | null>;
69
- /** Issues matching `opts`, or null when gh failed. */
70
- issueList(opts?: IssueListOptions & AsyncCallOptions): Promise<IssueRow[] | null>;
71
- /** One issue with its labels, or null when gh failed. */
103
+ } & AsyncCallOptions): Promise<ListResult<Label>>;
104
+ /** Issues matching `opts`. Rejects when gh fails. */
105
+ issueList(opts?: IssueListOptions & AsyncCallOptions): Promise<ListResult<IssueRow>>;
106
+ /** One issue with its labels; null when there is no such issue. */
72
107
  issueView(number: number, opts?: AsyncCallOptions): Promise<IssueRow | null>;
108
+ /** An owner's repositories. Rejects when gh fails. See `Gh.repoList`. */
109
+ repoList(owner: string, opts?: v.RepoListOptions & AsyncCallOptions): Promise<ListResult<Repository>>;
110
+ /** One repository; null when there is no such repository. See `Gh.repoView`. */
111
+ repoView(repo?: string | undefined, opts?: {
112
+ fields?: readonly string[] | undefined;
113
+ } & AsyncCallOptions): Promise<Repository | null>;
114
+ /** Pull requests across repositories matching a search query. Rejects when gh fails. See `Gh.searchPrs`. */
115
+ searchPrs(query: string, opts?: v.SearchPrsOptions & AsyncCallOptions): Promise<ListResult<PrSearchHit>>;
73
116
  /** True iff the `gh` binary is on PATH and runs. */
74
117
  installed(): Promise<boolean>;
75
118
  /** True iff `gh` holds credentials. */
@@ -92,7 +135,7 @@ export declare class AsyncGh {
92
135
  }): Promise<void>;
93
136
  /** Rewrite just a PR's body. */
94
137
  editPrBody(number: number, body: string): Promise<void>;
95
- /** The PR's unified diff; rejects with `CommandFailed` when gh cannot produce it. */
138
+ /** The PR's unified diff; rejects with the classified failure when gh cannot produce it. */
96
139
  prDiff(number: number): Promise<string>;
97
140
  /** Post one conversation comment on a PR; resolves to its URL ('' if gh printed none). */
98
141
  prComment(number: number, body: string): Promise<string>;
@@ -118,7 +161,7 @@ export declare class AsyncGh {
118
161
  headSha: string;
119
162
  }): Promise<void>;
120
163
  /** A PR's review threads, or null when they could not be read completely. */
121
- prReviewThreads(number: number): Promise<v.ReviewThread[] | null>;
164
+ prReviewThreads(number: number): Promise<v.ReviewThread[]>;
122
165
  /** Create a label (or force it to `opts`'s color/description if it exists). */
123
166
  createLabel(opts: v.CreateLabelOptions): Promise<void>;
124
167
  /** Recolor and/or redescribe an existing label. */
@@ -129,4 +172,3 @@ export declare class AsyncGh {
129
172
  /** Swap an issue's label in a single `gh issue edit` call. */
130
173
  setIssueLabel(number: number, currentLabel: string | null, newLabel: string): Promise<void>;
131
174
  }
132
- //# sourceMappingURL=async.d.ts.map
package/dist/async.js CHANGED
@@ -1,60 +1,111 @@
1
1
  /**
2
2
  * `AsyncGh` — the awaitable GitHub client, with the same methods as {@link Gh}.
3
3
  *
4
- * Same tri-modal contract, promised: queries resolve to parsed values and
5
- * never reject on a gh failure, mutations reject with `CommandFailed`, and
6
- * `raw`/`rawChecked` are the hatches. It is driven by the same pure verb
4
+ * Same three doors, promised: the plain method resolves to a value or rejects
5
+ * (a `FailureError` carrying the classified `GhFailure` for a failed gh or output it cannot read;
6
+ * a query for one thing resolves `null` when GitHub has no such thing),
7
+ * `try` resolves to a `Result<T, GhFailure>`, `soft` resolves `null` for a
8
+ * failure, and `raw`/`rawChecked` are the hatches. It is driven by the same pure verb
7
9
  * table as `Gh`, so the two cannot drift on argv or parsing. Use it where the
8
10
  * sync client would block the event loop: a fan-out of per-PR queries.
9
11
  */
10
- import { runCheckedAsync, resolveRunnerAsync, } from '@osdlc/command-seam';
11
- import { parseOwnerRepo } from "./repo.js";
12
+ import { resolveRunnerAsync, } from '@osdlc/command-seam';
13
+ import { runCheckedGhAsync, settle, softAsync, triedAsync, } from "./doors.js";
14
+ import { ownerRepoSlug, parseOwnerRepo, parsePrRef } from "./repo.js";
12
15
  import * as v from "./verbs.js";
13
16
  /** How long to wait between merge-state retries, in milliseconds. */
14
17
  const MERGE_STATE_RETRY_DELAY_MS = 1_500;
15
- /** A typed, awaitable gh client bound to one working directory. */
18
+ /**
19
+ * A typed, awaitable gh client bound to one working directory, and optionally
20
+ * to one repository and host. `repo` and `hostname` work as on `Gh`.
21
+ */
16
22
  export class AsyncGh {
17
23
  cwd;
24
+ repo;
25
+ hostname;
26
+ target;
18
27
  runner;
28
+ tried;
29
+ softened;
19
30
  constructor(cwd, opts = {}) {
31
+ v.checkTarget(opts);
20
32
  this.cwd = cwd;
33
+ this.repo = opts.repo;
34
+ this.hostname = opts.hostname;
35
+ this.target = { repo: opts.repo, hostname: opts.hostname };
21
36
  this.runner = resolveRunnerAsync('gh', opts.runner);
22
37
  }
23
- /** The same client rooted at a different directory (same runner). */
38
+ /** The same client rooted at a different directory (same runner, repo and host). */
24
39
  at(cwd) {
25
- return new AsyncGh(cwd, { runner: this.runner });
40
+ return new AsyncGh(cwd, { runner: this.runner, repo: this.repo, hostname: this.hostname });
41
+ }
42
+ /** The same client aimed at another repository (same runner, directory and host). */
43
+ withRepo(repo) {
44
+ return new AsyncGh(this.cwd, { runner: this.runner, repo, hostname: this.hostname });
45
+ }
46
+ /**
47
+ * The whole surface again, resolving to a `Result<T, GhFailure>` instead of
48
+ * rejecting. A query for one thing that finds nothing is `ok: true` with
49
+ * `null`. See `Gh.try`.
50
+ */
51
+ get try() {
52
+ return (this.tried ??= triedAsync(this));
53
+ }
54
+ /** Each query again, resolving `null` instead of rejecting when `gh` fails. See `Gh.soft`. */
55
+ get soft() {
56
+ return (this.softened ??= softAsync(this));
26
57
  }
27
58
  // ── escape hatches ────────────────────────────────────────────────────────
28
59
  /** Unchecked passthrough for verbs not yet lifted into the API. */
29
60
  raw(argv, opts = {}) {
30
61
  return this.runner.run(argv, { cwd: this.cwd, ...opts });
31
62
  }
32
- /** Checked passthrough — rejects with `CommandFailed` on a non-zero exit. */
63
+ /** Checked passthrough — rejects with the classified failure on a non-zero exit. */
33
64
  rawChecked(argv, opts = {}) {
34
- return runCheckedAsync(this.runner, argv, { cwd: this.cwd, ...opts });
65
+ return runCheckedGhAsync(this.runner, argv, { cwd: this.cwd, ...opts });
35
66
  }
36
67
  /** Run one verb from the table, honouring its checked/unchecked contract. */
37
68
  async exec(verb, call = {}) {
38
69
  const opts = { cwd: this.cwd };
39
70
  if (verb.input !== undefined)
40
71
  opts.input = verb.input;
72
+ if (verb.env !== undefined)
73
+ opts.env = verb.env;
41
74
  if (call.timeoutMs !== undefined)
42
75
  opts.timeoutMs = call.timeoutMs;
43
76
  if (call.signal !== undefined)
44
77
  opts.signal = call.signal;
45
78
  const result = verb.checked
46
- ? await runCheckedAsync(this.runner, verb.argv, opts)
79
+ ? await runCheckedGhAsync(this.runner, verb.argv, opts)
47
80
  : await this.runner.run(verb.argv, opts);
48
- return verb.parse(result);
81
+ return settle(verb, result, this.runner.binary);
82
+ }
83
+ /** {@link exec} for a verb that never answers `null` as a value: a failed or unreadable run rejects. */
84
+ async read(verb, call = {}) {
85
+ return (await this.exec(verb, call));
49
86
  }
50
87
  // ── queries ───────────────────────────────────────────────────────────────
51
- /** Pull requests matching `opts`, or null when gh failed. */
88
+ /** Pull requests matching `opts`. Rejects when gh fails; `soft.prList` resolves null instead. */
52
89
  prList(opts = {}) {
53
- return this.exec(v.prList(opts), opts);
54
- }
55
- /** One PR with the heavy fields, or null when gh failed. */
56
- prView(number, opts = {}) {
57
- return this.exec(v.prView(number, opts.fields ?? v.PR_VIEW_FIELDS), opts);
90
+ return this.read(v.prList(opts, this.target), opts);
91
+ }
92
+ /** One PR by number, branch or URL, with the heavy fields, or null. See `Gh.prView`. */
93
+ prView(ref, opts = {}) {
94
+ return this.exec(v.prView(ref, opts.fields ?? v.PR_VIEW_FIELDS, this.target), opts);
95
+ }
96
+ /** The one PR that `input` names, or null. See `Gh.prFind`. */
97
+ async prFind(input, opts = {}) {
98
+ const ref = parsePrRef(input);
99
+ if (ref === null)
100
+ return null;
101
+ const call = { ...opts, fields: opts.fields ?? v.PR_LIST_FIELDS };
102
+ if (ref.kind === 'url')
103
+ return this.prView(ref.url, call);
104
+ if (ref.kind === 'branch')
105
+ return prForBranchOn(this, ref.name, call);
106
+ const client = ref.repo === null ? this : this.withRepo(ownerRepoSlug(ref.repo));
107
+ const pr = await client.prView(ref.number, call);
108
+ return pr !== null || ref.orBranch === null ? pr : prForBranchOn(this, ref.orBranch, call);
58
109
  }
59
110
  /** The open PR whose head exactly equals `branch`, or null. See `Gh.openPrFor`. */
60
111
  async openPrFor(branch, opts = {}) {
@@ -65,7 +116,7 @@ export class AsyncGh {
65
116
  timeoutMs: opts.timeoutMs,
66
117
  signal: opts.signal,
67
118
  });
68
- return v.pickPrForBranch(rows, branch);
119
+ return v.pickPrForBranch(rows.rows, branch);
69
120
  }
70
121
  /** {@link prView}, re-asked until GitHub settles the merge state. See `Gh.prViewSettled`. */
71
122
  async prViewSettled(number, opts = {}) {
@@ -81,25 +132,45 @@ export class AsyncGh {
81
132
  }
82
133
  return last;
83
134
  }
84
- /** A PR's body text, or null when gh failed. */
135
+ /** A PR's body text. Rejects when gh fails. */
85
136
  prBody(number, opts = {}) {
86
- return this.exec(v.prBody(number), opts);
137
+ return this.read(v.prBody(number, this.target), opts);
87
138
  }
88
139
  /** A REST GET through `gh api`; failure is `ok: false`, never a rejection. */
89
140
  api(endpoint, opts = {}) {
90
- return this.exec(v.api(endpoint, opts), opts);
141
+ return this.exec(v.api(endpoint, opts, this.target), opts);
91
142
  }
92
- /** Every label on the repository (up to `opts.limit`, default 1000), or null when gh failed. */
143
+ /**
144
+ * A GraphQL query; each page's `data`. Rejects on a failed or partial result. See `Gh.graphql`.
145
+ *
146
+ * @experimental
147
+ */
148
+ graphql(query, variables = {}, opts = {}) {
149
+ return this.read(v.graphql(query, variables, opts, this.target), opts);
150
+ }
151
+ /** Every label on the repository (up to `opts.limit`, default 1000). Rejects when gh fails. */
93
152
  labelList(opts = {}) {
94
- return this.exec(v.labelList(opts), opts);
153
+ return this.read(v.labelList(opts, this.target), opts);
95
154
  }
96
- /** Issues matching `opts`, or null when gh failed. */
155
+ /** Issues matching `opts`. Rejects when gh fails. */
97
156
  issueList(opts = {}) {
98
- return this.exec(v.issueList(opts), opts);
157
+ return this.read(v.issueList(opts, this.target), opts);
99
158
  }
100
- /** One issue with its labels, or null when gh failed. */
159
+ /** One issue with its labels; null when there is no such issue. */
101
160
  issueView(number, opts = {}) {
102
- return this.exec(v.issueView(number), opts);
161
+ return this.exec(v.issueView(number, this.target), opts);
162
+ }
163
+ /** An owner's repositories. Rejects when gh fails. See `Gh.repoList`. */
164
+ repoList(owner, opts = {}) {
165
+ return this.read(v.repoList(owner, opts, this.target), opts);
166
+ }
167
+ /** One repository; null when there is no such repository. See `Gh.repoView`. */
168
+ repoView(repo, opts = {}) {
169
+ return this.exec(v.repoView(repo, opts.fields ?? v.REPO_FIELDS, this.target), opts);
170
+ }
171
+ /** Pull requests across repositories matching a search query. Rejects when gh fails. See `Gh.searchPrs`. */
172
+ searchPrs(query, opts = {}) {
173
+ return this.read(v.searchPrs(query, opts, this.target), opts);
103
174
  }
104
175
  /** True iff the `gh` binary is on PATH and runs. */
105
176
  installed() {
@@ -111,7 +182,7 @@ export class AsyncGh {
111
182
  }
112
183
  /** Login state and what `gh auth status` printed. */
113
184
  authStatus() {
114
- return this.exec(v.authStatus());
185
+ return this.exec(v.authStatus(this.target));
115
186
  }
116
187
  /** True iff `gh` is installed AND authenticated; short-circuits. */
117
188
  async ready() {
@@ -127,23 +198,23 @@ export class AsyncGh {
127
198
  // ── mutations (checked) ───────────────────────────────────────────────────
128
199
  /** Open a PR and resolve to its URL ('' if gh printed none). */
129
200
  createPr(opts) {
130
- return this.exec(v.createPr(opts));
201
+ return this.exec(v.createPr(opts, this.target));
131
202
  }
132
203
  /** Retitle or rewrite a PR body. */
133
204
  editPr(number, opts) {
134
- return this.exec(v.editPr(number, opts));
205
+ return this.exec(v.editPr(number, opts, this.target));
135
206
  }
136
207
  /** Rewrite just a PR's body. */
137
208
  editPrBody(number, body) {
138
- return this.exec(v.editPrBody(number, body));
209
+ return this.exec(v.editPrBody(number, body, this.target));
139
210
  }
140
- /** The PR's unified diff; rejects with `CommandFailed` when gh cannot produce it. */
211
+ /** The PR's unified diff; rejects with the classified failure when gh cannot produce it. */
141
212
  prDiff(number) {
142
- return this.exec(v.prDiff(number));
213
+ return this.exec(v.prDiff(number, this.target));
143
214
  }
144
215
  /** Post one conversation comment on a PR; resolves to its URL ('' if gh printed none). */
145
216
  prComment(number, body) {
146
- return this.exec(v.prComment(number, body));
217
+ return this.exec(v.prComment(number, body, this.target));
147
218
  }
148
219
  /**
149
220
  * Create a `COMMENT` review with inline comments; resolves to the review's URL ('' if none came back).
@@ -151,15 +222,15 @@ export class AsyncGh {
151
222
  * @experimental
152
223
  */
153
224
  createReview(number, opts) {
154
- return this.exec(v.createReview(number, opts));
225
+ return this.exec(v.createReview(number, opts, this.target));
155
226
  }
156
227
  /** Bring a PR's head branch up to date with its base. */
157
228
  prUpdateBranch(number, style = 'merge') {
158
- return this.exec(v.prUpdateBranch(number, style));
229
+ return this.exec(v.prUpdateBranch(number, style, this.target));
159
230
  }
160
231
  /** Mark a PR ready for review (`draft: false`) or convert it to a draft (`draft: true`). */
161
232
  prSetDraft(number, draft) {
162
- return this.exec(v.prSetDraft(number, draft));
233
+ return this.exec(v.prSetDraft(number, draft, this.target));
163
234
  }
164
235
  /**
165
236
  * Rewrite one PR conversation comment's body. `commentId` is the numeric REST id; anything else rejects with `RangeError`.
@@ -168,29 +239,41 @@ export class AsyncGh {
168
239
  */
169
240
  async issueCommentEdit(commentId, body) {
170
241
  // async, so a bad id rejects the Promise instead of throwing at the call.
171
- return this.exec(v.issueCommentEdit(commentId, body));
242
+ return this.exec(v.issueCommentEdit(commentId, body, this.target));
172
243
  }
173
244
  /** Merge a PR; GitHub refuses when its head is no longer `opts.headSha`. */
174
245
  prMerge(number, opts) {
175
- return this.exec(v.prMerge(number, opts));
246
+ return this.exec(v.prMerge(number, opts, this.target));
176
247
  }
177
248
  /** A PR's review threads, or null when they could not be read completely. */
178
249
  prReviewThreads(number) {
179
- return this.exec(v.prReviewThreads(number));
250
+ return this.read(v.prReviewThreads(number, this.target));
180
251
  }
181
252
  /** Create a label (or force it to `opts`'s color/description if it exists). */
182
253
  createLabel(opts) {
183
- return this.exec(v.createLabel(opts));
254
+ return this.exec(v.createLabel(opts, this.target));
184
255
  }
185
256
  /** Recolor and/or redescribe an existing label. */
186
257
  editLabel(name, opts) {
187
- return this.exec(v.editLabel(name, opts));
258
+ return this.exec(v.editLabel(name, opts, this.target));
188
259
  }
189
260
  /** Swap an issue's label in a single `gh issue edit` call. */
190
261
  setIssueLabel(number, currentLabel, newLabel) {
191
- return this.exec(v.setIssueLabel(number, currentLabel, newLabel));
262
+ return this.exec(v.setIssueLabel(number, currentLabel, newLabel, this.target));
192
263
  }
193
264
  }
265
+ /** The open PR for `branch`, else its newest closed or merged one. */
266
+ async function prForBranchOn(gh, branch, call) {
267
+ const rows = await gh.prList({
268
+ head: branch,
269
+ state: 'all',
270
+ limit: 20,
271
+ fields: v.branchPrFields(call.fields),
272
+ timeoutMs: call.timeoutMs,
273
+ signal: call.signal,
274
+ });
275
+ return v.branchPr(rows);
276
+ }
194
277
  function sleepAsync(ms) {
195
278
  return new Promise((resolve) => setTimeout(resolve, ms));
196
279
  }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The three doors, as `@osdlc/typed-git` has them: the plain verb (checked),
3
+ * `gh.try.*` and `gh.soft.*`.
4
+ *
5
+ * - **Plain / checked**: a value, or a throw. A failed `gh` throws a
6
+ * `FailureError` carrying the classified `GhFailure` (its `cause` is the
7
+ * `CommandFailed`), so a caller that reports it, such as `runMain`, reads
8
+ * "signed out" or "rate limited" and not a bare exit; output that exited 0
9
+ * but could not be read throws one of kind `unreadable`. A verb that
10
+ * reads one thing (`prView`, `issueView`, `repoView`) answers `null` when
11
+ * GitHub says there is no such thing: absence is an answer.
12
+ * - **`try`**: the same call, with the failure returned as
13
+ * `Result<T, GhFailure>`. It catches a thrown failure (a `FailureError`, or a
14
+ * `CommandFailed` from `raw`-level code) and nothing else: a `RangeError` for a bad argument still throws.
15
+ * - **`soft`**: the same call, with `null` for a failure. Only the queries
16
+ * have a soft form; no mutation does.
17
+ *
18
+ * Both are facades over the whole client, so a new method costs nothing here.
19
+ */
20
+ import { type AsyncCommandRunner, type AsyncCommandRunOptions, type CommandResult, type CommandRunner, type CommandRunOptions } from '@osdlc/command-seam';
21
+ import { type Failure, type Result } from '@osdlc/result';
22
+ import { type GhFailure } from './failure.ts';
23
+ import type { GhVerb } from './verbs.ts';
24
+ /**
25
+ * Read the result of running `verb` as the plain door does.
26
+ *
27
+ * A checked verb has already thrown on a non-zero exit (the runner is
28
+ * `runChecked`), and a probe (`installed`, `authStatus`, `api`) carries its
29
+ * failure in its value. A read that exited non-zero throws `CommandFailed`,
30
+ * unless the verb's `absent` says gh's answer was "there is no such thing",
31
+ * which `parse` turns into `null`. A read that exited 0 but parsed to `null`
32
+ * could not be read completely, and throws.
33
+ *
34
+ * @throws `FailureError` carrying the classified {@link GhFailure} for a failed run.
35
+ * @throws `FailureError` carrying an `unreadable` {@link GhFailure}.
36
+ */
37
+ export declare function settle<T>(verb: GhVerb<T>, result: CommandResult, binary: string | undefined): T;
38
+ /**
39
+ * The error the plain door throws for a failed run: a `FailureError` carrying
40
+ * the {@link GhFailure} that {@link classifyGhFailure} reads from it, with the
41
+ * `CommandFailed` as its `cause`. Anything else is returned as it is.
42
+ */
43
+ export declare function ghFailureError(error: unknown): unknown;
44
+ /** {@link runChecked}, throwing {@link ghFailureError}'s classified failure. */
45
+ export declare function runCheckedGh(runner: CommandRunner, argv: string[], opts: CommandRunOptions): CommandResult;
46
+ /** {@link runCheckedAsync}, rejecting with {@link ghFailureError}'s classified failure. */
47
+ export declare function runCheckedGhAsync(runner: AsyncCommandRunner, argv: string[], opts: AsyncCommandRunOptions): Promise<CommandResult>;
48
+ /** The methods the doors leave out: the escape hatches, pure helpers and plain properties. */
49
+ type Triable<C> = Omit<C, 'raw' | 'rawChecked' | 'at' | 'withRepo' | 'ownerRepo' | 'try' | 'soft' | 'cwd' | 'repo' | 'hostname'>;
50
+ /** Every synchronous client method, returning a `Result<T, GhFailure>`. */
51
+ export type TriedSync<C> = {
52
+ [K in keyof Triable<C>]: Triable<C>[K] extends (...a: infer A) => infer R ? (...a: A) => Result<R, GhFailure> : never;
53
+ };
54
+ /** Every awaitable client method, promising a `Result<T, GhFailure>`. */
55
+ export type TriedAsync<C> = {
56
+ [K in keyof Triable<C>]: Triable<C>[K] extends (...a: infer A) => Promise<infer R> ? (...a: A) => Promise<Result<R, GhFailure>> : never;
57
+ };
58
+ /**
59
+ * The queries that have a soft form. A mutation has none: swallowing the failure
60
+ * of a write is how a caller ends up believing it worked.
61
+ */
62
+ export declare const SOFT_METHODS: readonly ["prList", "prView", "prFind", "openPrFor", "prViewSettled", "prBody", "graphql", "labelList", "issueList", "issueView", "repoList", "repoView", "searchPrs", "prReviewThreads"];
63
+ /** The name of a method with a soft form. */
64
+ export type SoftMethod = (typeof SOFT_METHODS)[number];
65
+ type OrNull<R> = R extends Promise<infer U> ? Promise<U | null> : R | null;
66
+ /** Each queried method again, returning `null` instead of throwing. One type for both clients. */
67
+ export type Soft<C> = {
68
+ [K in SoftMethod & keyof C]: C[K] extends (...a: infer A) => infer R ? (...a: A) => OrNull<R> : never;
69
+ };
70
+ /** The failure a caught `CommandFailed` or `FailureError` stands for. */
71
+ export declare function failureOf(error: {
72
+ failure: Failure;
73
+ }): GhFailure;
74
+ /** Wrap a synchronous client so every method returns a `Result<T, GhFailure>`. */
75
+ export declare function triedSync<C extends object>(client: C): TriedSync<C>;
76
+ /** Wrap an awaitable client so every method promises a `Result<T, GhFailure>`. */
77
+ export declare function triedAsync<C extends object>(client: C): TriedAsync<C>;
78
+ /** Wrap a synchronous client so a failed query returns `null`. */
79
+ export declare function softSync<C extends object>(client: C): Soft<C>;
80
+ /** Wrap an awaitable client so a failed query resolves to `null`. */
81
+ export declare function softAsync<C extends object>(client: C): Soft<C>;
82
+ export {};