@osdlc/typed-gh 0.6.0 → 0.9.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 +56 -1
- package/CONTRIBUTING.md +4 -3
- package/README.md +117 -36
- package/dist/async.d.ts +37 -23
- package/dist/async.js +52 -38
- 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 +8 -3
- package/dist/index.js +8 -2
- package/dist/parse.d.ts +0 -1
- package/dist/repo.d.ts +0 -1
- package/dist/run.d.ts +37 -0
- package/dist/run.js +49 -0
- package/dist/sync.d.ts +53 -29
- package/dist/sync.js +64 -42
- package/dist/types.d.ts +0 -1
- package/dist/verbs.d.ts +25 -8
- package/dist/verbs.js +34 -8
- 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,59 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @osdlc/typed-gh
|
|
2
|
+
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cc2abe4: `api` now keeps GitHub's error body when a request fails. `body` is parsed even when gh exits with an error, and a new `message` field holds `body.message`. `ApiResponse` gains this `message` field as required. `malformed` is now also set when a failed call prints something that is not JSON. The docs now say that `status` is only filled in when headers were requested.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 72f5049: `prList` always requests the `number` field. Before, a `fields` list without `number` returned no rows.
|
|
12
|
+
- Updated dependencies [b5aac07]
|
|
13
|
+
- Updated dependencies [41ef520]
|
|
14
|
+
- @osdlc/typed-git@0.7.0
|
|
15
|
+
|
|
16
|
+
## 0.8.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- ae400e5: Added `runVerb` and `runVerbAsync`, with `RunVerbOptions` and `RunVerbAsyncOptions`. They run one verb through a runner the caller owns and apply what the verb carries: `env` (the target host, as `GH_HOST`, for `repoList` and `searchPrs`), `input` (the payload `createReview` sends on stdin), `timeoutMs` and the checked contract. Running `verb.parse(await run(verb.argv))` yourself dropped `env` and `input` without an error. `Gh` and `AsyncGh` now call them, so there is one copy of that logic.
|
|
21
|
+
|
|
22
|
+
## 0.7.0
|
|
23
|
+
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- 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`.
|
|
27
|
+
|
|
28
|
+
**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.
|
|
29
|
+
|
|
30
|
+
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.
|
|
31
|
+
- 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.
|
|
32
|
+
|
|
33
|
+
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.
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- 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/`.
|
|
38
|
+
- fb712e0: Make every package follow the same conventions. The published manifests and READMEs change; the runtime code and the `dist/` files do not.
|
|
39
|
+
|
|
40
|
+
- `author` is `sksizer` in every manifest (`@osdlc/typed-gh` had none).
|
|
41
|
+
- `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.
|
|
42
|
+
- `@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`.
|
|
43
|
+
- Every README has `## Install` and `## Licence` sections, and every CHANGELOG starts `# @osdlc/<name>`.
|
|
44
|
+
- `@osdlc/color` and `@osdlc/markdown-contract` no longer set a coverage threshold.
|
|
45
|
+
- `@osdlc/yaml-splice` reaches its WebAssembly glue through a `#wasm` entry in `imports`. The shipped `dist/` files are the same.
|
|
46
|
+
- Updated dependencies [d9b0f77]
|
|
47
|
+
- Updated dependencies [d9b0f77]
|
|
48
|
+
- Updated dependencies [fb712e0]
|
|
49
|
+
- Updated dependencies [fb712e0]
|
|
50
|
+
- Updated dependencies [d9b0f77]
|
|
51
|
+
- Updated dependencies [d9b0f77]
|
|
52
|
+
- Updated dependencies [d9b0f77]
|
|
53
|
+
- Updated dependencies [d9b0f77]
|
|
54
|
+
- @osdlc/command-seam@0.9.0
|
|
55
|
+
- @osdlc/typed-git@0.6.0
|
|
56
|
+
- @osdlc/result@0.1.0
|
|
2
57
|
|
|
3
58
|
## 0.6.0
|
|
4
59
|
|
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
|
|
@@ -304,12 +357,31 @@ const result = await runner.run(verb.argv, { cwd: '/repo', timeoutMs: 5_000 })
|
|
|
304
357
|
const prs = verb.parse(result)
|
|
305
358
|
```
|
|
306
359
|
|
|
360
|
+
That is only safe for a verb with no `env` or `input`. `repoList` and `searchPrs` send the target
|
|
361
|
+
host as `GH_HOST` (`env`), and `createReview` sends its payload on stdin (`input`); a runner call
|
|
362
|
+
that passes only `verb.argv` drops both, silently: the query goes to github.com, and the review is
|
|
363
|
+
never sent. `runVerb` (and `runVerbAsync`, for an `AsyncCommandRunner`) run a verb the way `Gh` and
|
|
364
|
+
`AsyncGh` do, with `cwd`, `env`, `input`, `timeoutMs` and the checked contract applied, and return
|
|
365
|
+
the parsed value or throw the classified failure:
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
import { repoList, runVerbAsync } from '@osdlc/typed-gh'
|
|
369
|
+
|
|
370
|
+
const repos = await runVerbAsync(repoList('acme', {}, { hostname: 'ghe.example.com' }), runner, {
|
|
371
|
+
cwd: '/repo',
|
|
372
|
+
timeoutMs: 5_000,
|
|
373
|
+
})
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
They take a `CommandRunner`. A plain `(binary, argv, opts) => result` function is wrapped as
|
|
377
|
+
`{ run: (argv, opts) => fn('gh', argv, opts) }`.
|
|
378
|
+
|
|
307
379
|
## `AsyncGh`
|
|
308
380
|
|
|
309
381
|
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
|
|
382
|
+
Same three doors, promised (the plain method rejects with the classified failure,
|
|
383
|
+
`try` resolves to a `Result<T, GhFailure>`, `soft` resolves `null` for a failed
|
|
384
|
+
query, `raw`/`rawChecked` are the hatches), driven by the same verb table, so the two cannot drift. It takes an
|
|
313
385
|
`AsyncCommandRunner` and defaults to `spawnRunnerAsync("gh")`.
|
|
314
386
|
|
|
315
387
|
```ts
|
|
@@ -317,7 +389,7 @@ import { AsyncGh } from '@osdlc/typed-gh'
|
|
|
317
389
|
|
|
318
390
|
const gh = new AsyncGh('/repo')
|
|
319
391
|
const prs = await Promise.all(branches.map((b) => gh.openPrFor(b)))
|
|
320
|
-
const body = await gh.prBody(42) // string
|
|
392
|
+
const body = await gh.prBody(42) // string; rejects when gh fails
|
|
321
393
|
await gh.editPrBody(42, `${body}\n\nfooter`)
|
|
322
394
|
await gh.prComment(42, 'rebased') // the comment's URL, or ''
|
|
323
395
|
await gh.prUpdateBranch(42, 'rebase')
|
|
@@ -329,7 +401,8 @@ These methods take `AsyncCallOptions`: `raw`, `rawChecked`, `prList`, `prView`,
|
|
|
329
401
|
`signal`.
|
|
330
402
|
|
|
331
403
|
`api(endpoint, opts)` (on `Gh` and `AsyncGh`) is a REST GET through `gh api`.
|
|
332
|
-
It never throws; the result is `{ ok, status, etag, body, malformed, stderr, error? }`.
|
|
404
|
+
It never throws; the result is `{ ok, status, etag, body, message, malformed, stderr, error? }`.
|
|
405
|
+
On failure `body` still carries GitHub's error object and `message` its text; `status` is only observed when headers were requested (`etag` or `includeHeaders`).
|
|
333
406
|
`stderr` is gh's own; `error` holds the runner's message when gh never ran or
|
|
334
407
|
was stopped (a missing binary, a timeout).
|
|
335
408
|
Pass `etag` for a conditional request (`If-None-Match`; a 304 comes back as
|
|
@@ -346,8 +419,8 @@ without one. If `gh` prints more than one header block, the last one is read. `{
|
|
|
346
419
|
const branches = gh.api('repos/{owner}/{repo}/branches', { paginate: true }).body
|
|
347
420
|
```
|
|
348
421
|
|
|
349
|
-
`graphql` (experimental) runs a query and returns each page's `data`. It
|
|
350
|
-
call failed or any page has `errors` or no `data
|
|
422
|
+
`graphql` (experimental) runs a query and returns each page's `data`. It throws when the
|
|
423
|
+
call failed or any page has `errors` or no `data` (`soft.graphql` is `null`): a partial answer is no answer. `nodesAt` joins
|
|
351
424
|
one connection's `nodes` across the pages, and is `null` when any page lacks them.
|
|
352
425
|
|
|
353
426
|
```ts
|
|
@@ -369,9 +442,9 @@ one. `graphql` refuses a mutation with `RangeError`; send one through `raw`.
|
|
|
369
442
|
## Repositories and search
|
|
370
443
|
|
|
371
444
|
```ts
|
|
372
|
-
const repos: ListResult<Repository>
|
|
373
|
-
const repo: Repository | null = gh.repoView('acme/widgets')
|
|
374
|
-
const hits: ListResult<PrSearchHit>
|
|
445
|
+
const repos: ListResult<Repository> = gh.repoList('acme', { archived: false, limit: 200 })
|
|
446
|
+
const repo: Repository | null = gh.repoView('acme/widgets') // null: no such repository
|
|
447
|
+
const hits: ListResult<PrSearchHit> = gh.searchPrs('TICKET-123', { owner: 'acme' })
|
|
375
448
|
```
|
|
376
449
|
|
|
377
450
|
A `Repository` carries `nameWithOwner`, `owner`, `name`, `description`, `defaultBranch`,
|
|
@@ -409,6 +482,8 @@ experimental.
|
|
|
409
482
|
- `GhOptions` and `AsyncGhOptions`: the runner, `repo` and `hostname` a client is built with.
|
|
410
483
|
- `AsyncCallOptions`: `timeoutMs` and `signal`, accepted by sixteen methods of `AsyncGh`.
|
|
411
484
|
- `defaultRunnerFactory` and `RunnerFactory`: how a client makes its default runner for `gh`.
|
|
485
|
+
- `runVerb` and `runVerbAsync`, with `RunVerbOptions` and `RunVerbAsyncOptions`: run one verb through
|
|
486
|
+
a runner you own, with its `env`, `input`, timeout and checked contract applied.
|
|
412
487
|
|
|
413
488
|
Methods on both clients:
|
|
414
489
|
|
|
@@ -422,9 +497,15 @@ Methods on both clients:
|
|
|
422
497
|
- Issue and label queries: `issueList`, `issueView` and `labelList`.
|
|
423
498
|
- Repository queries: `repoList` and `repoView`.
|
|
424
499
|
- Issue and label changes: `setIssueLabel`, `createLabel`, `editLabel` and `issueCommentEdit`.
|
|
425
|
-
- REST and GraphQL: `api` is a GET through `gh api` and `graphql` a query
|
|
500
|
+
- 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
501
|
- Setup checks: `installed`, `authenticated`, `authStatus` and `ready`.
|
|
427
502
|
- `ownerRepo` reads the owner and repository from a remote URL. It starts no process.
|
|
503
|
+
- `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.
|
|
504
|
+
|
|
505
|
+
### Failures
|
|
506
|
+
|
|
507
|
+
- `GhFailure` and `GhFailureKind`: the error of `try`, and its kinds (see "`GhFailure`" above).
|
|
508
|
+
- `classifyGhFailure(commandFailed)`: the `GhFailure` for a `CommandFailed`.
|
|
428
509
|
|
|
429
510
|
### Verbs
|
|
430
511
|
|
|
@@ -473,7 +554,7 @@ the escape hatches, `at`, `withRepo`, `prViewSettled`, `openPrFor`, `prFind`, `a
|
|
|
473
554
|
|
|
474
555
|
### From the command seam
|
|
475
556
|
|
|
476
|
-
- `CommandFailed`:
|
|
557
|
+
- `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
558
|
- `CommandRunner`, `AsyncCommandRunner`, `CommandResult` and `CommandRunOptions`: the runner types
|
|
478
559
|
that appear in signatures.
|
|
479
560
|
|
|
@@ -514,4 +595,4 @@ shell, so nothing in it is specific to one shell.
|
|
|
514
595
|
|
|
515
596
|
## Licence
|
|
516
597
|
|
|
517
|
-
`MIT OR Apache-2.0`, at your option. See
|
|
598
|
+
`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
|