@osdlc/typed-gh 0.6.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 +37 -1
- package/CONTRIBUTING.md +4 -3
- package/README.md +94 -35
- package/dist/async.d.ts +37 -23
- package/dist/async.js +48 -27
- 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 +7 -3
- package/dist/index.js +7 -2
- package/dist/parse.d.ts +0 -1
- package/dist/repo.d.ts +0 -1
- package/dist/sync.d.ts +53 -29
- package/dist/sync.js +64 -33
- package/dist/types.d.ts +0 -1
- package/dist/verbs.d.ts +19 -4
- package/dist/verbs.js +23 -4
- package/package.json +13 -17
- package/async.ts +0 -379
- 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 -120
- package/parse.ts +0 -302
- package/repo.ts +0 -133
- package/sync.ts +0 -438
- package/types.ts +0 -226
- package/verbs.ts +0 -1324
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,40 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @osdlc/typed-gh
|
|
2
|
+
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d9b0f77: **Breaking:** the plain door throws the classified failure. A failed `gh` (a mutation, `prDiff`, `rawChecked`, a query) now throws a `FailureError` whose `failure` is the `GhFailure` and whose `failure.cause` is the `CommandFailed`, where it threw the `CommandFailed` itself. The bare `CommandFailed` only had command-seam's generic `exit` kind, so a signed-out `gh` during a write exited 1 from `runMain` instead of 23. Read `error.failure.argv` and `error.failure.cause.result` where you read `error.argv` and `error.result`.
|
|
8
|
+
|
|
9
|
+
**Breaking:** the single-thing queries no longer treat every `Could not resolve to a …` as absence. `prView` answers `null` only for a missing pull request, `issueView` only for a missing issue and `repoView` only for a missing repository; a mistyped `--repo` is now a `not-found` failure, and a bare `HTTP 404` is a failure. The absence test reads stderr and stdout, as the classifier does.
|
|
10
|
+
|
|
11
|
+
An `HTTP 403` is now `not-authenticated` (SAML, a missing scope), the same as Forgejo's 401 and 403, unless the text says it is a rate limit, which stays `rate-limited`. The `try` and `soft` doors recognise a thrown failure by shape, so two installed copies of `@osdlc/command-seam` or `@osdlc/result` agree.
|
|
12
|
+
- d9b0f77: `Gh` and `AsyncGh` get `typed-git`'s three doors. **Breaking:** a query no longer answers `null` for a failure. The plain method returns its value or throws `CommandFailed` (a `FailureError` carrying a `GhFailure` of kind `unreadable` when `gh` exited 0 but its output could not be read). A query for one thing (`prView`, `issueView`, `repoView`, and `prFind`, `openPrFor`, `prViewSettled` over them) still answers `null` when GitHub says there is no such thing. `prList`, `prBody`, `graphql`, `labelList`, `issueList`, `repoList`, `searchPrs` and `prReviewThreads` now return a non-null value and throw. `installed`, `authenticated`, `authStatus`, `ready` and `api` are unchanged: their failure is part of their value.
|
|
13
|
+
|
|
14
|
+
New: `gh.try.*` returns `Result<T, GhFailure>` from `@osdlc/result`; `gh.soft.*` returns `null` for a failed query, which is the old query behaviour, now opt-in (`TriedSync`, `TriedAsync`, `Soft`). `GhFailure` carries a `kind`, a one-line `message`, a `hint` for a person, `detail`, `argv`, `exitCode` and the `CommandFailed` as `cause`. `GhFailureKind` is every `@osdlc/command-seam` kind plus `not-authenticated`, `network`, `not-found`, `rate-limited` and `unreadable`, read from `gh`'s stderr. `classifyGhFailure(commandFailed)` is exported. `@osdlc/result` is now a dependency.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- fb712e0: Ship the compiled `dist/` only. The `bun` export condition and the TypeScript source are no longer in the package, so Bun, Node and every bundler load the same compiled ESM and `.d.ts` files. Bun still works through the compiled `dist/`.
|
|
19
|
+
- fb712e0: Make every package follow the same conventions. The published manifests and READMEs change; the runtime code and the `dist/` files do not.
|
|
20
|
+
|
|
21
|
+
- `author` is `sksizer` in every manifest (`@osdlc/typed-gh` had none).
|
|
22
|
+
- `engines.node` is no wider than any dependency's. `@osdlc/typed-gh` and `@osdlc/code-host` now declare `^22.22.2 || ^24.15.0 || >=26.0.0`, the range of `@osdlc/typed-git` and its dependency `hosted-git-info`. `@osdlc/cli-tool` and `@osdlc/trust-repo` declare `>=22`, not `>=22.18`: a `dist/` install needs no TypeScript stripping.
|
|
23
|
+
- `@osdlc/tsconfig` and `@osdlc/prettier-config` are ESM with `sideEffects: false`, and ship SECURITY.md and CONTRIBUTING.md. `@osdlc/prettier-config` has an `exports` map (`.` is `index.json`) in place of `main`.
|
|
24
|
+
- Every README has `## Install` and `## Licence` sections, and every CHANGELOG starts `# @osdlc/<name>`.
|
|
25
|
+
- `@osdlc/color` and `@osdlc/markdown-contract` no longer set a coverage threshold.
|
|
26
|
+
- `@osdlc/yaml-splice` reaches its WebAssembly glue through a `#wasm` entry in `imports`. The shipped `dist/` files are the same.
|
|
27
|
+
- Updated dependencies [d9b0f77]
|
|
28
|
+
- Updated dependencies [d9b0f77]
|
|
29
|
+
- Updated dependencies [fb712e0]
|
|
30
|
+
- Updated dependencies [fb712e0]
|
|
31
|
+
- Updated dependencies [d9b0f77]
|
|
32
|
+
- Updated dependencies [d9b0f77]
|
|
33
|
+
- Updated dependencies [d9b0f77]
|
|
34
|
+
- Updated dependencies [d9b0f77]
|
|
35
|
+
- @osdlc/command-seam@0.9.0
|
|
36
|
+
- @osdlc/typed-git@0.6.0
|
|
37
|
+
- @osdlc/result@0.1.0
|
|
2
38
|
|
|
3
39
|
## 0.6.0
|
|
4
40
|
|
package/CONTRIBUTING.md
CHANGED
|
@@ -39,8 +39,9 @@ pnpm --filter @osdlc/typed-gh smoke
|
|
|
39
39
|
|
|
40
40
|
## Running the tests
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
pnpm installs and runs the scripts, and the `test` script runs Vitest under
|
|
43
|
+
Node. Build first, since the workspace packages resolve each other through
|
|
44
|
+
`dist/`. The tests run offline: a fake
|
|
44
45
|
runner stands in for `gh`, so they need no network, no login and no `gh`
|
|
45
46
|
binary.
|
|
46
47
|
|
|
@@ -71,4 +72,4 @@ version and writes the CHANGELOG from the pending changesets. See
|
|
|
71
72
|
call.
|
|
72
73
|
- Add a changeset for a change users can see. See above.
|
|
73
74
|
- Record a design choice in
|
|
74
|
-
[`docs/decisions/`](https://github.com/osdlc/osdlc/tree/main/packages/ts/typed-gh/docs/decisions).
|
|
75
|
+
[`docs/planning/decisions/`](https://github.com/osdlc/osdlc/tree/main/packages/ts/typed-gh/docs/planning/decisions).
|
package/README.md
CHANGED
|
@@ -15,7 +15,9 @@ scripts, release bots and coding agents. It is the GitHub-side companion of
|
|
|
15
15
|
npm install @osdlc/typed-gh
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
`pnpm add @osdlc/typed-gh` and `yarn add @osdlc/typed-gh` work too.
|
|
19
|
+
|
|
20
|
+
It needs Node `^22.22.2 || ^24.15.0 || >=26.0.0`, the range of its dependency `@osdlc/typed-git` (Bun also works, through the compiled `dist/`), and the `gh` CLI on `PATH`, signed in with `gh auth login`.
|
|
19
21
|
It is supported on macOS and Linux. Windows is not supported yet; it will be once a Windows CI job
|
|
20
22
|
runs the tests.
|
|
21
23
|
|
|
@@ -88,18 +90,69 @@ and `ReviewDecision` (`APPROVED`, `CHANGES_REQUESTED`, `REVIEW_REQUIRED`,
|
|
|
88
90
|
`null`). A value GitHub adds to either reads as `UNKNOWN` or `null`. A check
|
|
89
91
|
that decides whether to merge can then match every member.
|
|
90
92
|
|
|
91
|
-
## Three
|
|
93
|
+
## Three doors, as in `typed-git`, so failure is never ambiguous
|
|
94
|
+
|
|
95
|
+
| | a value | on failure |
|
|
96
|
+
| --- | --- | --- |
|
|
97
|
+
| **`gh.query()`** (the plain method) | the value | throws a `FailureError` carrying the classified `GhFailure` (kind `unreadable` when `gh` succeeded but its output cannot be read) |
|
|
98
|
+
| **`gh.try.query()`** | `{ ok: true, value }` | `{ ok: false, error: GhFailure }` |
|
|
99
|
+
| **`gh.soft.query()`** | the value | `null` (queries only; a mutation has no soft form) |
|
|
100
|
+
| **`raw(argv)`** | the raw `CommandResult`; you decide | the same |
|
|
101
|
+
|
|
102
|
+
A query returns a list or a value, never an empty list on failure.
|
|
103
|
+
`prList()` returning `{ rows: [], truncated: false }` means *this repository
|
|
104
|
+
has no open PRs*. A failed call throws, or is `{ ok: false }` through `try`, or
|
|
105
|
+
`null` through `soft`: *we could not ask*. If the two were the same, an
|
|
106
|
+
expired credential would read as "nothing to do".
|
|
107
|
+
|
|
108
|
+
**Absence is an answer.** A query for one thing (`prView`, `issueView`,
|
|
109
|
+
`repoView`, and `prFind` and `openPrFor` over them) returns `null` on every
|
|
110
|
+
door when GitHub says there is no such thing: "Could not resolve to a
|
|
111
|
+
PullRequest" or "no pull requests found for branch" for `prView`, "Could not
|
|
112
|
+
resolve to an Issue" for `issueView`, "Could not resolve to a Repository" for
|
|
113
|
+
`repoView`. `gh.try.prView(99)` is `{ ok: true, value: null }`. Every other
|
|
114
|
+
failure is a failure, including a wrong repository: `prView(7)` against a
|
|
115
|
+
mistyped `--repo` is a `not-found` failure, not an absent PR. The check reads
|
|
116
|
+
stderr and stdout, as the classifier does.
|
|
117
|
+
|
|
118
|
+
**Probes answer.** `installed()`, `authenticated()`, `authStatus()`, `ready()`
|
|
119
|
+
and `api()` return their failure as part of their value, in every door, because
|
|
120
|
+
"gh is not installed" is the answer to the question they ask.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { describe } from '@osdlc/result'
|
|
92
124
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
125
|
+
const result = gh.try.prList({ limit: 50 })
|
|
126
|
+
if (!result.ok) {
|
|
127
|
+
console.error(describe(result.error)) // e.g. "gh is not signed in to GitHub. Run `gh auth login`."
|
|
128
|
+
}
|
|
129
|
+
```
|
|
98
130
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
131
|
+
### `GhFailure`
|
|
132
|
+
|
|
133
|
+
The error of `try` is a `GhFailure`, a `Failure` from `@osdlc/result`: a `kind`
|
|
134
|
+
to branch on, a one-line `message` and a `hint` for a person (`describe(error)`
|
|
135
|
+
joins them), `gh`'s own output in `detail`, the `argv` and `exitCode`, and the
|
|
136
|
+
`CommandFailed` it was read from in `cause`. The plain door throws the same
|
|
137
|
+
failure, wrapped in a `FailureError` (`error.failure`), so a CLI that reports a
|
|
138
|
+
thrown failure, such as `runMain` from `@osdlc/cli-tool`, shows "gh is not
|
|
139
|
+
signed in" for a write and exits with the same code as for a read.
|
|
140
|
+
|
|
141
|
+
| `kind` | When (gh's stderr) | `message` / `hint` |
|
|
142
|
+
| --- | --- | --- |
|
|
143
|
+
| `not-installed` | `gh` is not on `PATH` | `` `gh` is not installed or not on PATH. `` / ``Install it from https://cli.github.com, then run `gh auth login`.`` |
|
|
144
|
+
| `not-authenticated` | `gh auth login`, `HTTP 401`, `HTTP 403` (unless a rate limit), `Bad credentials`, `not logged in` | `gh is not signed in to GitHub.` / ``Run `gh auth login`.`` |
|
|
145
|
+
| `network` | `error connecting to`, `Could not resolve host`, `no such host`, `dial tcp`, `i/o timeout` | `GitHub could not be reached.` / `Check your network connection.` |
|
|
146
|
+
| `not-found` | `Could not resolve to a …`, `HTTP 404`, `no pull requests found for branch` | `GitHub has no such pull request, issue or repository, or you cannot see it.` / `Check the number or name, and that your account can see the repository.` |
|
|
147
|
+
| `rate-limited` | `API rate limit exceeded`, `secondary rate limit`, `HTTP 429` | `GitHub's API rate limit was reached.` / `Wait a few minutes and try again.` |
|
|
148
|
+
| `unreadable` | `gh` exited 0 but its output could not be read completely | `gh answered, but its output could not be read completely.` (no hint, no `cause`) |
|
|
149
|
+
| `not-executable`, `bad-cwd`, `timeout`, `output-cap`, `signal`, `aborted`, `spawn` | as in `@osdlc/command-seam` | command-seam's message, no hint |
|
|
150
|
+
| `exit` | any other non-zero exit | `gh`'s first stderr line |
|
|
151
|
+
|
|
152
|
+
The patterns are `gh`'s English messages; anything else is a plain `exit`,
|
|
153
|
+
never a wrong kind. Rate limiting is checked before authentication, then
|
|
154
|
+
network, then not-found. `classifyGhFailure(commandFailed)` makes the same
|
|
155
|
+
classification for a `CommandFailed` you caught yourself.
|
|
103
156
|
|
|
104
157
|
## A list says when its limit cut it short
|
|
105
158
|
|
|
@@ -108,9 +161,9 @@ one row more than `limit` and returns `{ rows, truncated }`: at most `limit`
|
|
|
108
161
|
rows, and `truncated: true` when there were more.
|
|
109
162
|
|
|
110
163
|
```ts
|
|
111
|
-
const page: ListResult<PullRequest>
|
|
164
|
+
const page: ListResult<PullRequest> = gh.prList({ limit: 50 })
|
|
112
165
|
|
|
113
|
-
if (page
|
|
166
|
+
if (page.truncated) console.warn('more than 50 open PRs; raise the limit')
|
|
114
167
|
```
|
|
115
168
|
|
|
116
169
|
`prList`, `issueList`, `labelList`, `repoList` and `searchPrs` all work this
|
|
@@ -130,7 +183,7 @@ acme.withRepo('acme/gadgets').prList()
|
|
|
130
183
|
acme.api('repos/{owner}/{repo}/branches')
|
|
131
184
|
```
|
|
132
185
|
|
|
133
|
-
The target goes into the argv, so a `
|
|
186
|
+
The target goes into the argv, so a failure's `argv` shows which repository a
|
|
134
187
|
call hit: `pr`, `issue` and `label` calls get `--repo github.acme.com/acme/widgets`,
|
|
135
188
|
`api` calls get `--hostname` and a filled `{owner}/{repo}`, and `authStatus`
|
|
136
189
|
asks about that host. `repoList` and `searchPrs` have no host flag in gh, so
|
|
@@ -213,7 +266,7 @@ about staleness, never the measure of it. Compute that from the commit graph
|
|
|
213
266
|
|
|
214
267
|
## Changing pull requests
|
|
215
268
|
|
|
216
|
-
Mutations throw `
|
|
269
|
+
Mutations throw the classified failure (a `FailureError`) when `gh` fails. `gh.try.editPr(…)` returns the failure as a `Result` instead; a mutation has no `soft` form.
|
|
217
270
|
|
|
218
271
|
```ts
|
|
219
272
|
const url = gh.createPr(options)
|
|
@@ -241,23 +294,23 @@ gh.prMerge(9, { method, headSha: 'abc1234' })
|
|
|
241
294
|
```
|
|
242
295
|
|
|
243
296
|
`prReviewThreads(n)` reads the review threads, so you can look for open ones before you merge. It
|
|
244
|
-
|
|
245
|
-
than 100 threads). That is not the same as "no threads".
|
|
297
|
+
throws when it could not read all of them (a failed call, an unexpected payload, or more
|
|
298
|
+
than 100 threads; `soft.prReviewThreads` is `null`). That is not the same as "no threads".
|
|
246
299
|
|
|
247
300
|
```ts
|
|
248
|
-
const threads: ReviewThread[]
|
|
301
|
+
const threads: ReviewThread[] = gh.prReviewThreads(9)
|
|
249
302
|
|
|
250
|
-
threads
|
|
303
|
+
threads.filter((t) => !t.isResolved)
|
|
251
304
|
```
|
|
252
305
|
|
|
253
306
|
## Issues and labels
|
|
254
307
|
|
|
255
308
|
```ts
|
|
256
|
-
const issue: IssueRow | null = gh.issueView(7)
|
|
257
|
-
const triage: ListResult<IssueRow>
|
|
309
|
+
const issue: IssueRow | null = gh.issueView(7) // null: there is no issue 7
|
|
310
|
+
const triage: ListResult<IssueRow> = gh.issueList({ label: 'status:triage', limit: 50 })
|
|
258
311
|
gh.setIssueLabel(7, 'status:triage', 'status:define')
|
|
259
312
|
|
|
260
|
-
const labels: ListResult<Label>
|
|
313
|
+
const labels: ListResult<Label> = gh.labelList({ limit: 100 })
|
|
261
314
|
gh.createLabel({ name: 'status:triage', color: '1f6feb', description: 'Triage stage' })
|
|
262
315
|
gh.editLabel('status:triage', { description: 'Waiting for triage' })
|
|
263
316
|
```
|
|
@@ -279,7 +332,7 @@ const review = gh.createReview(9, {
|
|
|
279
332
|
gh.issueCommentEdit('4204154439', 'Updated: CI is green.')
|
|
280
333
|
```
|
|
281
334
|
|
|
282
|
-
`prDiff` returns the unified diff and throws
|
|
335
|
+
`prDiff` returns the unified diff and throws the classified failure when `gh` cannot produce it.
|
|
283
336
|
|
|
284
337
|
`createReview` is experimental. It posts a review of kind `COMMENT`: it cannot approve or request
|
|
285
338
|
changes. Each inline comment sits on one line of the new side of the diff. It returns the review's
|
|
@@ -307,9 +360,9 @@ const prs = verb.parse(result)
|
|
|
307
360
|
## `AsyncGh`
|
|
308
361
|
|
|
309
362
|
The awaitable form of `Gh`, with the same methods, for a fan-out that must not block the event loop.
|
|
310
|
-
Same
|
|
311
|
-
|
|
312
|
-
hatches), driven by the same verb table, so the two cannot drift. It takes an
|
|
363
|
+
Same three doors, promised (the plain method rejects with the classified failure,
|
|
364
|
+
`try` resolves to a `Result<T, GhFailure>`, `soft` resolves `null` for a failed
|
|
365
|
+
query, `raw`/`rawChecked` are the hatches), driven by the same verb table, so the two cannot drift. It takes an
|
|
313
366
|
`AsyncCommandRunner` and defaults to `spawnRunnerAsync("gh")`.
|
|
314
367
|
|
|
315
368
|
```ts
|
|
@@ -317,7 +370,7 @@ import { AsyncGh } from '@osdlc/typed-gh'
|
|
|
317
370
|
|
|
318
371
|
const gh = new AsyncGh('/repo')
|
|
319
372
|
const prs = await Promise.all(branches.map((b) => gh.openPrFor(b)))
|
|
320
|
-
const body = await gh.prBody(42) // string
|
|
373
|
+
const body = await gh.prBody(42) // string; rejects when gh fails
|
|
321
374
|
await gh.editPrBody(42, `${body}\n\nfooter`)
|
|
322
375
|
await gh.prComment(42, 'rebased') // the comment's URL, or ''
|
|
323
376
|
await gh.prUpdateBranch(42, 'rebase')
|
|
@@ -346,8 +399,8 @@ without one. If `gh` prints more than one header block, the last one is read. `{
|
|
|
346
399
|
const branches = gh.api('repos/{owner}/{repo}/branches', { paginate: true }).body
|
|
347
400
|
```
|
|
348
401
|
|
|
349
|
-
`graphql` (experimental) runs a query and returns each page's `data`. It
|
|
350
|
-
call failed or any page has `errors` or no `data
|
|
402
|
+
`graphql` (experimental) runs a query and returns each page's `data`. It throws when the
|
|
403
|
+
call failed or any page has `errors` or no `data` (`soft.graphql` is `null`): a partial answer is no answer. `nodesAt` joins
|
|
351
404
|
one connection's `nodes` across the pages, and is `null` when any page lacks them.
|
|
352
405
|
|
|
353
406
|
```ts
|
|
@@ -369,9 +422,9 @@ one. `graphql` refuses a mutation with `RangeError`; send one through `raw`.
|
|
|
369
422
|
## Repositories and search
|
|
370
423
|
|
|
371
424
|
```ts
|
|
372
|
-
const repos: ListResult<Repository>
|
|
373
|
-
const repo: Repository | null = gh.repoView('acme/widgets')
|
|
374
|
-
const hits: ListResult<PrSearchHit>
|
|
425
|
+
const repos: ListResult<Repository> = gh.repoList('acme', { archived: false, limit: 200 })
|
|
426
|
+
const repo: Repository | null = gh.repoView('acme/widgets') // null: no such repository
|
|
427
|
+
const hits: ListResult<PrSearchHit> = gh.searchPrs('TICKET-123', { owner: 'acme' })
|
|
375
428
|
```
|
|
376
429
|
|
|
377
430
|
A `Repository` carries `nameWithOwner`, `owner`, `name`, `description`, `defaultBranch`,
|
|
@@ -422,9 +475,15 @@ Methods on both clients:
|
|
|
422
475
|
- Issue and label queries: `issueList`, `issueView` and `labelList`.
|
|
423
476
|
- Repository queries: `repoList` and `repoView`.
|
|
424
477
|
- Issue and label changes: `setIssueLabel`, `createLabel`, `editLabel` and `issueCommentEdit`.
|
|
425
|
-
- REST and GraphQL: `api` is a GET through `gh api` and `graphql` a query
|
|
478
|
+
- REST and GraphQL: `api` is a GET through `gh api` and never throws (failure is `ok: false`); `graphql` is a query and throws on a failed or partial answer.
|
|
426
479
|
- Setup checks: `installed`, `authenticated`, `authStatus` and `ready`.
|
|
427
480
|
- `ownerRepo` reads the owner and repository from a remote URL. It starts no process.
|
|
481
|
+
- `try` and `soft`: the whole surface again as a `Result<T, GhFailure>`, and the queries again with `null` for a failure. `TriedSync`, `TriedAsync` and `Soft` are their types.
|
|
482
|
+
|
|
483
|
+
### Failures
|
|
484
|
+
|
|
485
|
+
- `GhFailure` and `GhFailureKind`: the error of `try`, and its kinds (see "`GhFailure`" above).
|
|
486
|
+
- `classifyGhFailure(commandFailed)`: the `GhFailure` for a `CommandFailed`.
|
|
428
487
|
|
|
429
488
|
### Verbs
|
|
430
489
|
|
|
@@ -473,7 +532,7 @@ the escape hatches, `at`, `withRepo`, `prViewSettled`, `openPrFor`, `prFind`, `a
|
|
|
473
532
|
|
|
474
533
|
### From the command seam
|
|
475
534
|
|
|
476
|
-
- `CommandFailed`:
|
|
535
|
+
- `CommandFailed`: re-exported from `@osdlc/command-seam`. A failed call throws a `FailureError` whose `failure.cause` is the `CommandFailed`, which carries the argv and the result.
|
|
477
536
|
- `CommandRunner`, `AsyncCommandRunner`, `CommandResult` and `CommandRunOptions`: the runner types
|
|
478
537
|
that appear in signatures.
|
|
479
538
|
|
|
@@ -514,4 +573,4 @@ shell, so nothing in it is specific to one shell.
|
|
|
514
573
|
|
|
515
574
|
## Licence
|
|
516
575
|
|
|
517
|
-
`MIT OR Apache-2.0`, at your option. See
|
|
576
|
+
`MIT OR Apache-2.0`, at your option. See [LICENSE-MIT](./LICENSE-MIT) and [LICENSE-APACHE](./LICENSE-APACHE).
|
package/dist/async.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `AsyncGh` — the awaitable GitHub client, with the same methods as {@link Gh}.
|
|
3
3
|
*
|
|
4
|
-
* Same
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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';
|
|
13
|
+
import { type Soft, type TriedAsync } from './doors.ts';
|
|
11
14
|
import type { IssueRow, Label, OwnerRepo, PrSearchHit, PullRequest, Repository } from './types.ts';
|
|
12
15
|
import * as v from './verbs.ts';
|
|
13
16
|
import type { ApiOptions, ApiResponse, AuthStatus, GhTarget, IssueListOptions, ListResult, PrListOptions } from './verbs.ts';
|
|
@@ -40,19 +43,31 @@ export declare class AsyncGh {
|
|
|
40
43
|
readonly hostname: string | undefined;
|
|
41
44
|
private readonly target;
|
|
42
45
|
private readonly runner;
|
|
46
|
+
private tried;
|
|
47
|
+
private softened;
|
|
43
48
|
constructor(cwd: string, opts?: AsyncGhOptions);
|
|
44
49
|
/** The same client rooted at a different directory (same runner, repo and host). */
|
|
45
50
|
at(cwd: string): AsyncGh;
|
|
46
51
|
/** The same client aimed at another repository (same runner, directory and host). */
|
|
47
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>;
|
|
48
61
|
/** Unchecked passthrough for verbs not yet lifted into the API. */
|
|
49
62
|
raw(argv: string[], opts?: AsyncCallOptions): Promise<CommandResult>;
|
|
50
|
-
/** Checked passthrough — rejects with
|
|
63
|
+
/** Checked passthrough — rejects with the classified failure on a non-zero exit. */
|
|
51
64
|
rawChecked(argv: string[], opts?: AsyncCallOptions): Promise<CommandResult>;
|
|
52
65
|
/** Run one verb from the table, honouring its checked/unchecked contract. */
|
|
53
66
|
private exec;
|
|
54
|
-
/**
|
|
55
|
-
|
|
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>>;
|
|
56
71
|
/** One PR by number, branch or URL, with the heavy fields, or null. See `Gh.prView`. */
|
|
57
72
|
prView(ref: number | string, opts?: {
|
|
58
73
|
fields?: readonly string[] | undefined;
|
|
@@ -72,32 +87,32 @@ export declare class AsyncGh {
|
|
|
72
87
|
fields?: readonly string[] | undefined;
|
|
73
88
|
sleep?: ((ms: number) => Promise<void>) | undefined;
|
|
74
89
|
} & AsyncCallOptions): Promise<PullRequest | null>;
|
|
75
|
-
/** A PR's body text
|
|
76
|
-
prBody(number: number, opts?: AsyncCallOptions): Promise<string
|
|
90
|
+
/** A PR's body text. Rejects when gh fails. */
|
|
91
|
+
prBody(number: number, opts?: AsyncCallOptions): Promise<string>;
|
|
77
92
|
/** A REST GET through `gh api`; failure is `ok: false`, never a rejection. */
|
|
78
93
|
api(endpoint: string, opts?: ApiOptions & AsyncCallOptions): Promise<ApiResponse>;
|
|
79
94
|
/**
|
|
80
|
-
* A GraphQL query; each page's `data
|
|
95
|
+
* A GraphQL query; each page's `data`. Rejects on a failed or partial result. See `Gh.graphql`.
|
|
81
96
|
*
|
|
82
97
|
* @experimental
|
|
83
98
|
*/
|
|
84
|
-
graphql(query: string, variables?: Record<string, v.GraphqlVariable>, opts?: v.GraphqlOptions & AsyncCallOptions): Promise<unknown[]
|
|
85
|
-
/** Every label on the repository (up to `opts.limit`, default 1000)
|
|
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. */
|
|
86
101
|
labelList(opts?: {
|
|
87
102
|
limit?: number | undefined;
|
|
88
|
-
} & AsyncCallOptions): Promise<ListResult<Label
|
|
89
|
-
/** Issues matching `opts
|
|
90
|
-
issueList(opts?: IssueListOptions & AsyncCallOptions): Promise<ListResult<IssueRow
|
|
91
|
-
/** One issue with its labels
|
|
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. */
|
|
92
107
|
issueView(number: number, opts?: AsyncCallOptions): Promise<IssueRow | null>;
|
|
93
|
-
/** An owner's repositories
|
|
94
|
-
repoList(owner: string, opts?: v.RepoListOptions & AsyncCallOptions): Promise<ListResult<Repository
|
|
95
|
-
/** One repository
|
|
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`. */
|
|
96
111
|
repoView(repo?: string | undefined, opts?: {
|
|
97
112
|
fields?: readonly string[] | undefined;
|
|
98
113
|
} & AsyncCallOptions): Promise<Repository | null>;
|
|
99
|
-
/** Pull requests across repositories matching a search query
|
|
100
|
-
searchPrs(query: string, opts?: v.SearchPrsOptions & AsyncCallOptions): Promise<ListResult<PrSearchHit
|
|
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>>;
|
|
101
116
|
/** True iff the `gh` binary is on PATH and runs. */
|
|
102
117
|
installed(): Promise<boolean>;
|
|
103
118
|
/** True iff `gh` holds credentials. */
|
|
@@ -120,7 +135,7 @@ export declare class AsyncGh {
|
|
|
120
135
|
}): Promise<void>;
|
|
121
136
|
/** Rewrite just a PR's body. */
|
|
122
137
|
editPrBody(number: number, body: string): Promise<void>;
|
|
123
|
-
/** The PR's unified diff; rejects with
|
|
138
|
+
/** The PR's unified diff; rejects with the classified failure when gh cannot produce it. */
|
|
124
139
|
prDiff(number: number): Promise<string>;
|
|
125
140
|
/** Post one conversation comment on a PR; resolves to its URL ('' if gh printed none). */
|
|
126
141
|
prComment(number: number, body: string): Promise<string>;
|
|
@@ -146,7 +161,7 @@ export declare class AsyncGh {
|
|
|
146
161
|
headSha: string;
|
|
147
162
|
}): Promise<void>;
|
|
148
163
|
/** A PR's review threads, or null when they could not be read completely. */
|
|
149
|
-
prReviewThreads(number: number): Promise<v.ReviewThread[]
|
|
164
|
+
prReviewThreads(number: number): Promise<v.ReviewThread[]>;
|
|
150
165
|
/** Create a label (or force it to `opts`'s color/description if it exists). */
|
|
151
166
|
createLabel(opts: v.CreateLabelOptions): Promise<void>;
|
|
152
167
|
/** Recolor and/or redescribe an existing label. */
|
|
@@ -157,4 +172,3 @@ export declare class AsyncGh {
|
|
|
157
172
|
/** Swap an issue's label in a single `gh issue edit` call. */
|
|
158
173
|
setIssueLabel(number: number, currentLabel: string | null, newLabel: string): Promise<void>;
|
|
159
174
|
}
|
|
160
|
-
//# sourceMappingURL=async.d.ts.map
|
package/dist/async.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `AsyncGh` — the awaitable GitHub client, with the same methods as {@link Gh}.
|
|
3
3
|
*
|
|
4
|
-
* Same
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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 {
|
|
12
|
+
import { resolveRunnerAsync, } from '@osdlc/command-seam';
|
|
13
|
+
import { runCheckedGhAsync, settle, softAsync, triedAsync, } from "./doors.js";
|
|
11
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. */
|
|
@@ -22,6 +25,8 @@ export class AsyncGh {
|
|
|
22
25
|
hostname;
|
|
23
26
|
target;
|
|
24
27
|
runner;
|
|
28
|
+
tried;
|
|
29
|
+
softened;
|
|
25
30
|
constructor(cwd, opts = {}) {
|
|
26
31
|
v.checkTarget(opts);
|
|
27
32
|
this.cwd = cwd;
|
|
@@ -38,14 +43,26 @@ export class AsyncGh {
|
|
|
38
43
|
withRepo(repo) {
|
|
39
44
|
return new AsyncGh(this.cwd, { runner: this.runner, repo, hostname: this.hostname });
|
|
40
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));
|
|
57
|
+
}
|
|
41
58
|
// ── escape hatches ────────────────────────────────────────────────────────
|
|
42
59
|
/** Unchecked passthrough for verbs not yet lifted into the API. */
|
|
43
60
|
raw(argv, opts = {}) {
|
|
44
61
|
return this.runner.run(argv, { cwd: this.cwd, ...opts });
|
|
45
62
|
}
|
|
46
|
-
/** Checked passthrough — rejects with
|
|
63
|
+
/** Checked passthrough — rejects with the classified failure on a non-zero exit. */
|
|
47
64
|
rawChecked(argv, opts = {}) {
|
|
48
|
-
return
|
|
65
|
+
return runCheckedGhAsync(this.runner, argv, { cwd: this.cwd, ...opts });
|
|
49
66
|
}
|
|
50
67
|
/** Run one verb from the table, honouring its checked/unchecked contract. */
|
|
51
68
|
async exec(verb, call = {}) {
|
|
@@ -59,14 +76,18 @@ export class AsyncGh {
|
|
|
59
76
|
if (call.signal !== undefined)
|
|
60
77
|
opts.signal = call.signal;
|
|
61
78
|
const result = verb.checked
|
|
62
|
-
? await
|
|
79
|
+
? await runCheckedGhAsync(this.runner, verb.argv, opts)
|
|
63
80
|
: await this.runner.run(verb.argv, opts);
|
|
64
|
-
return verb.
|
|
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));
|
|
65
86
|
}
|
|
66
87
|
// ── queries ───────────────────────────────────────────────────────────────
|
|
67
|
-
/** Pull requests matching `opts
|
|
88
|
+
/** Pull requests matching `opts`. Rejects when gh fails; `soft.prList` resolves null instead. */
|
|
68
89
|
prList(opts = {}) {
|
|
69
|
-
return this.
|
|
90
|
+
return this.read(v.prList(opts, this.target), opts);
|
|
70
91
|
}
|
|
71
92
|
/** One PR by number, branch or URL, with the heavy fields, or null. See `Gh.prView`. */
|
|
72
93
|
prView(ref, opts = {}) {
|
|
@@ -95,7 +116,7 @@ export class AsyncGh {
|
|
|
95
116
|
timeoutMs: opts.timeoutMs,
|
|
96
117
|
signal: opts.signal,
|
|
97
118
|
});
|
|
98
|
-
return v.pickPrForBranch(rows
|
|
119
|
+
return v.pickPrForBranch(rows.rows, branch);
|
|
99
120
|
}
|
|
100
121
|
/** {@link prView}, re-asked until GitHub settles the merge state. See `Gh.prViewSettled`. */
|
|
101
122
|
async prViewSettled(number, opts = {}) {
|
|
@@ -111,45 +132,45 @@ export class AsyncGh {
|
|
|
111
132
|
}
|
|
112
133
|
return last;
|
|
113
134
|
}
|
|
114
|
-
/** A PR's body text
|
|
135
|
+
/** A PR's body text. Rejects when gh fails. */
|
|
115
136
|
prBody(number, opts = {}) {
|
|
116
|
-
return this.
|
|
137
|
+
return this.read(v.prBody(number, this.target), opts);
|
|
117
138
|
}
|
|
118
139
|
/** A REST GET through `gh api`; failure is `ok: false`, never a rejection. */
|
|
119
140
|
api(endpoint, opts = {}) {
|
|
120
141
|
return this.exec(v.api(endpoint, opts, this.target), opts);
|
|
121
142
|
}
|
|
122
143
|
/**
|
|
123
|
-
* A GraphQL query; each page's `data
|
|
144
|
+
* A GraphQL query; each page's `data`. Rejects on a failed or partial result. See `Gh.graphql`.
|
|
124
145
|
*
|
|
125
146
|
* @experimental
|
|
126
147
|
*/
|
|
127
148
|
graphql(query, variables = {}, opts = {}) {
|
|
128
|
-
return this.
|
|
149
|
+
return this.read(v.graphql(query, variables, opts, this.target), opts);
|
|
129
150
|
}
|
|
130
|
-
/** Every label on the repository (up to `opts.limit`, default 1000)
|
|
151
|
+
/** Every label on the repository (up to `opts.limit`, default 1000). Rejects when gh fails. */
|
|
131
152
|
labelList(opts = {}) {
|
|
132
|
-
return this.
|
|
153
|
+
return this.read(v.labelList(opts, this.target), opts);
|
|
133
154
|
}
|
|
134
|
-
/** Issues matching `opts
|
|
155
|
+
/** Issues matching `opts`. Rejects when gh fails. */
|
|
135
156
|
issueList(opts = {}) {
|
|
136
|
-
return this.
|
|
157
|
+
return this.read(v.issueList(opts, this.target), opts);
|
|
137
158
|
}
|
|
138
|
-
/** One issue with its labels
|
|
159
|
+
/** One issue with its labels; null when there is no such issue. */
|
|
139
160
|
issueView(number, opts = {}) {
|
|
140
161
|
return this.exec(v.issueView(number, this.target), opts);
|
|
141
162
|
}
|
|
142
|
-
/** An owner's repositories
|
|
163
|
+
/** An owner's repositories. Rejects when gh fails. See `Gh.repoList`. */
|
|
143
164
|
repoList(owner, opts = {}) {
|
|
144
|
-
return this.
|
|
165
|
+
return this.read(v.repoList(owner, opts, this.target), opts);
|
|
145
166
|
}
|
|
146
|
-
/** One repository
|
|
167
|
+
/** One repository; null when there is no such repository. See `Gh.repoView`. */
|
|
147
168
|
repoView(repo, opts = {}) {
|
|
148
169
|
return this.exec(v.repoView(repo, opts.fields ?? v.REPO_FIELDS, this.target), opts);
|
|
149
170
|
}
|
|
150
|
-
/** Pull requests across repositories matching a search query
|
|
171
|
+
/** Pull requests across repositories matching a search query. Rejects when gh fails. See `Gh.searchPrs`. */
|
|
151
172
|
searchPrs(query, opts = {}) {
|
|
152
|
-
return this.
|
|
173
|
+
return this.read(v.searchPrs(query, opts, this.target), opts);
|
|
153
174
|
}
|
|
154
175
|
/** True iff the `gh` binary is on PATH and runs. */
|
|
155
176
|
installed() {
|
|
@@ -187,7 +208,7 @@ export class AsyncGh {
|
|
|
187
208
|
editPrBody(number, body) {
|
|
188
209
|
return this.exec(v.editPrBody(number, body, this.target));
|
|
189
210
|
}
|
|
190
|
-
/** The PR's unified diff; rejects with
|
|
211
|
+
/** The PR's unified diff; rejects with the classified failure when gh cannot produce it. */
|
|
191
212
|
prDiff(number) {
|
|
192
213
|
return this.exec(v.prDiff(number, this.target));
|
|
193
214
|
}
|
|
@@ -226,7 +247,7 @@ export class AsyncGh {
|
|
|
226
247
|
}
|
|
227
248
|
/** A PR's review threads, or null when they could not be read completely. */
|
|
228
249
|
prReviewThreads(number) {
|
|
229
|
-
return this.
|
|
250
|
+
return this.read(v.prReviewThreads(number, this.target));
|
|
230
251
|
}
|
|
231
252
|
/** Create a label (or force it to `opts`'s color/description if it exists). */
|
|
232
253
|
createLabel(opts) {
|