@osdlc/typed-gh 0.14.1 → 0.16.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @osdlc/typed-gh
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 51efdb6: `raw(argv)` and `rawChecked(argv)` are new verbs: `Gh.raw` and `Gh.rawChecked` as functions, so a tool can make a call the verb table lacks without constructing a `Gh`. Through `runVerb`, `raw` returns the `CommandResult` whatever the exit code, and `rawChecked` returns it when `gh` exits 0 with its whole output and throws the classified `GhFailure` otherwise. Neither is `readOnly`. Both clients' `raw` and `rawChecked` methods now run these verbs. The one visible change is that `AsyncGh.raw` rejects, rather than throwing at the call site, when a runner throws synchronously, as `rawChecked` already did.
8
+
9
+ The README has a new section, "Keeping a bundle small": a bundled tool that makes a few calls should import the verbs it calls and run them with `runVerb`, because a bundler cannot drop a class's unused methods. For a diff and one raw checked call, that is 14.4 KB against 68.0 KB through `Gh` (`bun build --minify`). The decision and the measurements are in `docs/planning/decisions/bundle-size.md`.
10
+
11
+ ## 0.15.0
12
+
13
+ ### Minor Changes
14
+
15
+ - 4f764b7: `graphql` (experimental): fill the target's owner or repo name with the new `fromTarget('owner')` / `fromTarget('repo')` markers. **Breaking for `graphql` callers:** a plain string variable is now always sent as written, so a real value of `{owner}` or `{repo}` (a branch name, say) is no longer replaced. Change `{ owner: '{owner}', name: '{repo}' }` to `{ owner: fromTarget('owner'), name: fromTarget('repo') }`. REST endpoints keep the `{owner}/{repo}` path template, which mirrors gh's own syntax.
16
+
3
17
  ## 0.14.1
4
18
 
5
19
  ### Patch Changes
package/README.md CHANGED
@@ -419,6 +419,34 @@ const repos = await runVerbAsync(repoList('acme', {}, { hostname: 'ghe.example.c
419
419
  `(binary, argv, opts) => result` function is not a runner; wrap it yourself as
420
420
  `{ run: (argv, opts) => fn('gh', argv, opts) }`.
421
421
 
422
+ `raw(argv)` and `rawChecked(argv)` are `Gh.raw` and `Gh.rawChecked` as verbs, for a call the verb
423
+ table does not have. Through `runVerb`, `raw` returns the `CommandResult` whatever the exit code,
424
+ and `rawChecked` returns it when `gh` exits 0 with its whole output and throws the classified
425
+ failure otherwise. Like the methods, they add no `--repo` and no host.
426
+
427
+ ## Keeping a bundle small
428
+
429
+ A tool that is bundled and makes a few `gh` calls should import the verbs it calls and run them
430
+ with `runVerb`, and not construct a `Gh`. A bundler drops a function that nothing imports, but it
431
+ cannot drop a method a class never calls. So one `new Gh()` keeps every verb and parser in the
432
+ bundle, and the verb path keeps only the verbs you import:
433
+
434
+ ```ts
435
+ import { defaultRunnerFactory, prDiff, rawChecked, runVerb } from '@osdlc/typed-gh'
436
+
437
+ const runner = defaultRunnerFactory('gh')
438
+ const opts = { cwd: '/repo' }
439
+ const diff = runVerb(prDiff(42), runner, opts)
440
+ const files = runVerb(rawChecked(['api', 'repos/{owner}/{repo}/pulls/42/files']), runner, opts)
441
+ ```
442
+
443
+ Built with `bun build --minify`, those two calls take 14.4 KB through `runVerb` and 68.0 KB through
444
+ `Gh` (7.6 KB of each is command-seam's runner). The verb path gives up `try`, `soft` and the
445
+ helpers that make several calls (`prFind`, `openPrFor`, `prViewSettled`): catch the `FailureError`
446
+ instead. A tool that already uses many methods loses little by constructing `Gh`. The measurements
447
+ and the reasons `Gh` is not split into entry points are in
448
+ [docs/planning/decisions/bundle-size.md](docs/planning/decisions/bundle-size.md).
449
+
422
450
  ## `AsyncGh`
423
451
 
424
452
  The awaitable form of `Gh`, with the same methods, for code that runs many calls at once and must
@@ -466,6 +494,9 @@ const branches = gh.api('repos/{owner}/{repo}/branches', { paginate: true }).bod
466
494
  `graphql` (experimental) runs a query and returns each page's `data`. It throws when the
467
495
  call failed or any page has `errors` or no `data` (`soft.graphql` is `null`). A partial answer counts as a failure. `nodesAt` joins
468
496
  one connection's `nodes` across the pages, and is `null` when any page lacks them.
497
+ `fromTarget('owner')` and `fromTarget('repo')` fill a variable from the client's repo, or let gh
498
+ fill it from the clone. A plain string is always sent as written, so a real value of `{owner}` stays
499
+ `{owner}`.
469
500
 
470
501
  ```ts
471
502
  const query = `query($owner: String!, $name: String!, $endCursor: String) {
@@ -476,7 +507,11 @@ const query = `query($owner: String!, $name: String!, $endCursor: String) {
476
507
  }
477
508
  }
478
509
  }`
479
- const pages = gh.graphql(query, { owner: '{owner}', name: '{repo}' }, { paginate: true })
510
+ const pages = gh.graphql(
511
+ query,
512
+ { owner: fromTarget('owner'), name: fromTarget('repo') },
513
+ { paginate: true },
514
+ )
480
515
  const prs = nodesAt(pages, ['repository', 'pullRequests'])
481
516
  ```
482
517
 
@@ -566,7 +601,9 @@ experimental.
566
601
  - `AsyncCallOptions`: `timeoutMs` and `signal`, accepted by sixteen methods of `AsyncGh`.
567
602
  - `defaultRunnerFactory` and `RunnerFactory`: how a client makes its default runner for `gh`.
568
603
  - `runVerb` and `runVerbAsync`, with `RunVerbOptions` and `RunVerbAsyncOptions`: run one verb through
569
- a runner you own, with its `env`, `input`, timeout and checked contract applied.
604
+ a runner you own, with its `env`, `input`, timeout and checked contract applied. With the verb
605
+ functions, they are the way to call `gh` without a `Gh`, which keeps a bundle small (see
606
+ "Keeping a bundle small").
570
607
 
571
608
  Methods on both clients:
572
609
 
@@ -594,10 +631,11 @@ Methods on both clients:
594
631
 
595
632
  A verb is plain data: argv, a `checked` flag, a `readOnly` flag, optional stdin and environment,
596
633
  and a `parse` function. `readOnly` is true for every query and `prDiff`, and false for every
597
- mutation, so a caller that keeps an allowlist of safe calls can check a verb before running it.
598
- Each verb takes an optional `GhTarget` last. Each client method has a verb of the same name, except
599
- `raw`, `rawChecked`, `at`, `withRepo`, `prViewSettled`, `openPrFor`, `prFind`, `authenticated`,
600
- `ready` and `ownerRepo`. `openPrsForBranch` is a verb with no method.
634
+ mutation and for `raw` and `rawChecked`, so a caller that keeps an allowlist of safe calls can check
635
+ a verb before running it. Each verb takes an optional `GhTarget` last, except `raw` and
636
+ `rawChecked`, which take only argv. Each client method has a verb of the same name, except `at`,
637
+ `withRepo`, `prViewSettled`, `openPrFor`, `prFind`, `authenticated`, `ready` and `ownerRepo`.
638
+ `openPrsForBranch` is a verb with no method.
601
639
 
602
640
  - `GhVerb`: the shape of a verb. `GhTarget`: the `repo` and `hostname` a verb talks to.
603
641
  - Verb builders: `prList`, `prView`, `prBody`, `openPrsForBranch`, `prReviewThreads`, `prDiff`,
@@ -605,6 +643,9 @@ Each verb takes an optional `GhTarget` last. Each client method has a verb of th
605
643
  `createReview`, `issueList`, `issueView`, `labelList`, `setIssueLabel`, `createLabel`,
606
644
  `editLabel`, `issueCommentEdit`, `repoList`, `repoView`, `searchPrs`, `api`, `graphql`,
607
645
  `installed` and `authStatus`.
646
+ - `raw` and `rawChecked`: any argv as a verb whose value is the `CommandResult`. `raw` returns it
647
+ whatever the exit code; `rawChecked` throws the classified failure unless `gh` exits 0 with its
648
+ whole output.
608
649
  - `pickPrForBranch`: the first row whose head branch is exactly the one you name. `prList({ head })`
609
650
  already filters exactly. This is for rows from elsewhere, such as a `head:` search. It takes plain
610
651
  rows or a `ListResult`, so `openPrsForBranch`'s or `prList`'s parsed answer passes straight in.
@@ -663,6 +704,7 @@ The experimental items:
663
704
  - `nodesAt`
664
705
  - `GraphqlOptions`
665
706
  - `GraphqlVariable`
707
+ - `fromTarget` and `FromTarget`
666
708
 
667
709
  Every other export and method is stable, `prDiff` included. So is `@osdlc/typed-gh/testing`: its
668
710
  builders and their row types. Their default values may change in any minor release.
package/dist/async.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * block the event loop, such as many per-PR queries at once.
12
12
  */
13
13
  import { spawnRunnerAsync } from '@osdlc/command-seam';
14
- import { runCheckedGhAsync, softAsync, triedAsync } from "./doors.js";
14
+ import { softAsync, triedAsync } from "./doors.js";
15
15
  import { runVerbAsync } from "./run.js";
16
16
  import { ownerRepoSlug, parseOwnerRepo, parsePrRef } from "./repo.js";
17
17
  import * as v from "./verbs.js";
@@ -80,11 +80,11 @@ export class AsyncGh {
80
80
  * `.at(dir)` first.
81
81
  */
82
82
  raw(argv, opts = {}) {
83
- return this.runner.run(argv, { cwd: this.cwd, ...opts });
83
+ return this.exec(v.raw(argv), opts);
84
84
  }
85
85
  /** Run any `gh` call. Rejects with the classified failure on a non-zero exit. Adds no `--repo`, as {@link AsyncGh.raw}. */
86
86
  rawChecked(argv, opts = {}) {
87
- return runCheckedGhAsync(this.runner, argv, { cwd: this.cwd, ...opts });
87
+ return this.exec(v.rawChecked(argv), opts);
88
88
  }
89
89
  /** Run one verb from the table, checked or unchecked as the verb says. */
90
90
  async exec(verb, call = {}) {
package/dist/doors.d.ts CHANGED
@@ -27,11 +27,11 @@ import type { GhVerb } from './verbs.ts';
27
27
  * Read the result of running `verb` as the methods that throw do.
28
28
  *
29
29
  * A checked verb has already thrown on a non-zero exit (the runner is
30
- * {@link runCheckedGh}). A probe (`installed`, `authStatus`, `api`) carries its
31
- * failure in its value. A read that exited non-zero throws the classified
32
- * failure, unless the verb's `absent` says gh's answer was "there is no such
33
- * thing", which `parse` turns into `null`. A read that exited 0 but parsed to `null`
34
- * could not be read completely, and throws.
30
+ * {@link runCheckedGh}). A probe (`installed`, `authStatus`, `api`, `raw`)
31
+ * carries its failure in its value. A read that exited non-zero throws the
32
+ * classified failure, unless the verb's `absent` says gh's answer was "there
33
+ * is no such thing", which `parse` turns into `null`. A read that exited 0 but
34
+ * parsed to `null` could not be read completely, and throws.
35
35
  *
36
36
  * A result flagged `outputIncomplete` throws for every verb but a probe, as
37
37
  * typed-git's `gate` does: gh exited, but the runner stopped reading at the
package/dist/doors.js CHANGED
@@ -26,11 +26,11 @@ import { classifyGhFailure, unreadableFailure } from "./failure.js";
26
26
  * Read the result of running `verb` as the methods that throw do.
27
27
  *
28
28
  * A checked verb has already thrown on a non-zero exit (the runner is
29
- * {@link runCheckedGh}). A probe (`installed`, `authStatus`, `api`) carries its
30
- * failure in its value. A read that exited non-zero throws the classified
31
- * failure, unless the verb's `absent` says gh's answer was "there is no such
32
- * thing", which `parse` turns into `null`. A read that exited 0 but parsed to `null`
33
- * could not be read completely, and throws.
29
+ * {@link runCheckedGh}). A probe (`installed`, `authStatus`, `api`, `raw`)
30
+ * carries its failure in its value. A read that exited non-zero throws the
31
+ * classified failure, unless the verb's `absent` says gh's answer was "there
32
+ * is no such thing", which `parse` turns into `null`. A read that exited 0 but
33
+ * parsed to `null` could not be read completely, and throws.
34
34
  *
35
35
  * A result flagged `outputIncomplete` throws for every verb but a probe, as
36
36
  * typed-git's `gate` does: gh exited, but the runner stopped reading at the
package/dist/index.d.ts CHANGED
@@ -23,7 +23,7 @@ export { classifyGhFailure, type GhFailure, type GhFailureKind } from './failure
23
23
  export { type Soft, type TriedAsync, type TriedSync } from './doors.ts';
24
24
  export { runVerb, runVerbAsync, type RunVerbAsyncOptions, type RunVerbOptions } from './run.ts';
25
25
  export { Gh, defaultRunnerFactory, type GhOptions, type GhRepoOptions, type RunnerFactory, } from './sync.ts';
26
- export { api, authStatus, createPr, editPr, editPrBody, graphql, createLabel, createReview, editLabel, installed, issueList, issueView, labelList, nodesAt, openPrsForBranch, pickPrForBranch, prBody, prComment, prSetDraft, issueCommentEdit, prDiff, prList, prMerge, prReviewThreads, prUpdateBranch, prView, repoList, repoView, searchPrs, setIssueLabel, PR_LIST_FIELDS, PR_REF_FIELDS, PR_VIEW_FIELDS, REPO_FIELDS, SEARCH_PR_FIELDS, type ApiOptions, type ApiResponse, type AuthStatus, type CreateLabelOptions, type CreatePrOptions, type CreateReviewOptions, type GhTarget, type GraphqlOptions, type GraphqlVariable, type GhVerb, type IssueListOptions, type ListResult, type PrListOptions, type PrMergeMethod, type RepoListOptions, type ReviewComment, type ReviewThread, type SearchPrsOptions, } from './verbs.ts';
26
+ export { api, authStatus, createPr, editPr, editPrBody, fromTarget, graphql, createLabel, createReview, editLabel, installed, issueList, issueView, labelList, nodesAt, openPrsForBranch, pickPrForBranch, prBody, prComment, prSetDraft, issueCommentEdit, prDiff, prList, prMerge, prReviewThreads, prUpdateBranch, prView, raw, rawChecked, repoList, repoView, searchPrs, setIssueLabel, PR_LIST_FIELDS, PR_REF_FIELDS, PR_VIEW_FIELDS, REPO_FIELDS, SEARCH_PR_FIELDS, type ApiOptions, type ApiResponse, type AuthStatus, type CreateLabelOptions, type CreatePrOptions, type CreateReviewOptions, type FromTarget, type GhTarget, type GraphqlOptions, type GraphqlVariable, type GhVerb, type IssueListOptions, type ListResult, type PrListOptions, type PrMergeMethod, type RepoListOptions, type ReviewComment, type ReviewThread, type SearchPrsOptions, } from './verbs.ts';
27
27
  export { anyCheckFailed, normalizeAuthorLogin, parsePullRequest } from './parse.ts';
28
28
  export { ownerRepoSlug, parseOwnerRepo, parsePrRef, parsePrUrl, type PrRef, type PullRequestRef, } from './repo.ts';
29
29
  export type { Actor, CheckConclusion, CheckRun, Comment, IssueRow, Label, MergeableState, MergeStateStatus, OwnerRepo, PullRequest, PrSearchHit, PullRequestState, Repository, RepositoryVisibility, Review, ReviewDecision, ReviewState, } from './types.ts';
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ export { classifyGhFailure } from "./failure.js";
23
23
  export {} from "./doors.js";
24
24
  export { runVerb, runVerbAsync } from "./run.js";
25
25
  export { Gh, defaultRunnerFactory, } from "./sync.js";
26
- export { api, authStatus, createPr, editPr, editPrBody, graphql, createLabel, createReview, editLabel, installed, issueList, issueView, labelList, nodesAt, openPrsForBranch, pickPrForBranch, prBody, prComment, prSetDraft, issueCommentEdit, prDiff, prList, prMerge, prReviewThreads, prUpdateBranch, prView, repoList, repoView, searchPrs, setIssueLabel, PR_LIST_FIELDS, PR_REF_FIELDS, PR_VIEW_FIELDS, REPO_FIELDS, SEARCH_PR_FIELDS, } from "./verbs.js";
26
+ export { api, authStatus, createPr, editPr, editPrBody, fromTarget, graphql, createLabel, createReview, editLabel, installed, issueList, issueView, labelList, nodesAt, openPrsForBranch, pickPrForBranch, prBody, prComment, prSetDraft, issueCommentEdit, prDiff, prList, prMerge, prReviewThreads, prUpdateBranch, prView, raw, rawChecked, repoList, repoView, searchPrs, setIssueLabel, PR_LIST_FIELDS, PR_REF_FIELDS, PR_VIEW_FIELDS, REPO_FIELDS, SEARCH_PR_FIELDS, } from "./verbs.js";
27
27
  export { anyCheckFailed, normalizeAuthorLogin, parsePullRequest } from "./parse.js";
28
28
  export { ownerRepoSlug, parseOwnerRepo, parsePrRef, parsePrUrl, } from "./repo.js";
29
29
  // command-seam's error and runner types appear in this package's signatures, so
package/dist/sync.js CHANGED
@@ -17,7 +17,7 @@
17
17
  * `raw` and `rawChecked` run any `gh` call.
18
18
  */
19
19
  import { spawnRunner } from '@osdlc/command-seam';
20
- import { runCheckedGh, softSync, triedSync } from "./doors.js";
20
+ import { softSync, triedSync } from "./doors.js";
21
21
  import { runVerb } from "./run.js";
22
22
  import * as v from "./verbs.js";
23
23
  import { ownerRepoSlug, parseOwnerRepo, parsePrRef } from "./repo.js";
@@ -122,11 +122,11 @@ export class Gh {
122
122
  * `.at(dir)` first.
123
123
  */
124
124
  raw(argv, opts = {}) {
125
- return this.runner.run(argv, { cwd: this.cwd, ...opts });
125
+ return this.exec(v.raw(argv), opts.timeoutMs);
126
126
  }
127
127
  /** Run any `gh` call. Throws the classified failure on a non-zero exit. Adds no `--repo`, as {@link Gh.raw}. */
128
128
  rawChecked(argv, opts = {}) {
129
- return runCheckedGh(this.runner, argv, { cwd: this.cwd, ...opts });
129
+ return this.exec(v.rawChecked(argv), opts.timeoutMs);
130
130
  }
131
131
  /** Run one verb from the table, checked or unchecked as the verb says. */
132
132
  exec(verb, timeoutMs) {
package/dist/verbs.d.ts CHANGED
@@ -7,22 +7,23 @@
7
7
  * their own runner all use the same definitions and the same parsing.
8
8
  *
9
9
  * `checked` marks a verb the runner throws for on a non-zero exit, with
10
- * stderr. That is every mutation, and `prDiff`, which is a read. Other
11
- * queries run unchecked, and `parse` turns a failure into `null`, which is
12
- * what `soft` returns. The clients' methods that throw read them through
13
- * `settle` (see `doors.ts`) instead. There a failure throws, except where the
14
- * verb declares `absent` or is a `probe`.
10
+ * stderr. That is every mutation, `prDiff`, which is a read, and
11
+ * `rawChecked`. Other queries run unchecked, and `parse` turns a failure into
12
+ * `null`, which is what `soft` returns. The clients' methods that throw read
13
+ * them through `settle` (see `doors.ts`) instead. There a failure throws,
14
+ * except where the verb declares `absent` or is a `probe`.
15
15
  */
16
16
  import { type CommandResult } from '@osdlc/command-seam';
17
17
  import type { IssueRow, Label, PrSearchHit, PullRequest, Repository } from './types.ts';
18
18
  /** One `gh` invocation: what to run, and how to read what came back. */
19
19
  export interface GhVerb<T> {
20
20
  argv: string[];
21
- /** True for every mutation and for `prDiff`: throw on a non-zero exit instead of parsing. */
21
+ /** True for every mutation, `prDiff` and `rawChecked`: throw on a non-zero exit instead of parsing. */
22
22
  checked: boolean;
23
23
  /**
24
24
  * True when the call only reads and changes nothing on GitHub. Every query
25
- * is read-only, and so is `prDiff`. No mutation is. A caller that
25
+ * is read-only, and so is `prDiff`. No mutation is, and neither are `raw`
26
+ * and `rawChecked`, whose argv the library cannot judge. A caller that
26
27
  * keeps an allowlist of safe calls can check this before running a verb.
27
28
  */
28
29
  readOnly: boolean;
@@ -34,10 +35,10 @@ export interface GhVerb<T> {
34
35
  */
35
36
  env?: Record<string, string> | undefined;
36
37
  /**
37
- * True for a read whose failure is part of its answer. `installed`,
38
- * `authStatus` and `api` return what `parse` makes of any result, from the
39
- * plain method and from `try`; they have no soft form. Other reads throw
40
- * from the plain method when gh exits non-zero.
38
+ * True for a call whose failure is part of its answer. `installed`,
39
+ * `authStatus`, `api` and `raw` return what `parse` makes of any result,
40
+ * from the plain method and from `try`; they have no soft form. Other reads
41
+ * throw from the plain method when gh exits non-zero.
41
42
  */
42
43
  probe?: boolean | undefined;
43
44
  /**
@@ -223,11 +224,34 @@ export interface ApiOptions {
223
224
  */
224
225
  export declare function api(endpoint: string, opts?: ApiOptions, target?: GhTarget): GhVerb<ApiResponse>;
225
226
  /**
226
- * A GraphQL variable. gh sends a string with `-f` and the rest with `-F`.
227
+ * A GraphQL variable filled from the target's repository: its owner, or its
228
+ * name. Make one with {@link fromTarget}.
227
229
  *
228
230
  * @experimental
229
231
  */
230
- export type GraphqlVariable = string | number | boolean | null;
232
+ export interface FromTarget {
233
+ readonly fromTarget: 'owner' | 'repo';
234
+ }
235
+ /**
236
+ * A {@link graphql} variable that holds the target repository's owner or
237
+ * name. With a target repo, the value comes from it. Without one, gh fills
238
+ * it from the clone's repository. A plain string is always sent as written,
239
+ * so a real value of `{owner}` stays `{owner}`.
240
+ *
241
+ * ```ts
242
+ * gh.graphql(query, { owner: fromTarget('owner'), name: fromTarget('repo') })
243
+ * ```
244
+ *
245
+ * @experimental
246
+ */
247
+ export declare function fromTarget(part: 'owner' | 'repo'): FromTarget;
248
+ /**
249
+ * A GraphQL variable. gh sends a string with `-f`, as written, and the rest
250
+ * with `-F`. A {@link FromTarget} is filled from the target's repository.
251
+ *
252
+ * @experimental
253
+ */
254
+ export type GraphqlVariable = string | number | boolean | null | FromTarget;
231
255
  /**
232
256
  * Options for {@link graphql}.
233
257
  *
@@ -249,8 +273,8 @@ export interface GraphqlOptions {
249
273
  * carries `errors` or lacks `data`. A partial answer counts as a failure. Read one
250
274
  * connection across the pages with {@link nodesAt}.
251
275
  *
252
- * A string variable of exactly `{owner}` or `{repo}` is filled from the
253
- * target's repo, or by gh from the clone. A document holding any mutation or
276
+ * A {@link fromTarget} variable is filled from the target's repo, or by gh
277
+ * from the clone. A plain string is sent as written. A document holding any mutation or
254
278
  * subscription throws `RangeError`, as does a variable named `query` or
255
279
  * `operationName`, which gh would send as the request's own fields. This is a
256
280
  * read, and changes go through `raw`.
@@ -484,6 +508,21 @@ export interface CreateReviewOptions {
484
508
  * @experimental
485
509
  */
486
510
  export declare function createReview(number: number, opts: CreateReviewOptions, target?: GhTarget): GhVerb<string>;
511
+ /**
512
+ * Any `gh` call, as a verb whose value is the `CommandResult`, whatever the
513
+ * exit code: `Gh.raw` as data. It adds no `--repo` and no host, so put them
514
+ * in `argv`. It is a `probe`, so `runVerb` returns a failed or cut-short
515
+ * result rather than throwing, and it is not `readOnly`, since the library
516
+ * cannot tell what `argv` does.
517
+ */
518
+ export declare function raw(argv: readonly string[]): GhVerb<CommandResult>;
519
+ /**
520
+ * {@link raw}, checked: `Gh.rawChecked` as data. `runVerb` returns the
521
+ * `CommandResult` when gh exits 0 with its whole output, and otherwise throws
522
+ * a `FailureError` carrying the classified `GhFailure`. So a tool that runs
523
+ * one call the verb table lacks needs no `Gh` for it.
524
+ */
525
+ export declare function rawChecked(argv: readonly string[]): GhVerb<CommandResult>;
487
526
  /**
488
527
  * The first row whose `headRefName` exactly equals `branch`, or null.
489
528
  *
package/dist/verbs.js CHANGED
@@ -7,11 +7,11 @@
7
7
  * their own runner all use the same definitions and the same parsing.
8
8
  *
9
9
  * `checked` marks a verb the runner throws for on a non-zero exit, with
10
- * stderr. That is every mutation, and `prDiff`, which is a read. Other
11
- * queries run unchecked, and `parse` turns a failure into `null`, which is
12
- * what `soft` returns. The clients' methods that throw read them through
13
- * `settle` (see `doors.ts`) instead. There a failure throws, except where the
14
- * verb declares `absent` or is a `probe`.
10
+ * stderr. That is every mutation, `prDiff`, which is a read, and
11
+ * `rawChecked`. Other queries run unchecked, and `parse` turns a failure into
12
+ * `null`, which is what `soft` returns. The clients' methods that throw read
13
+ * them through `settle` (see `doors.ts`) instead. There a failure throws,
14
+ * except where the verb declares `absent` or is a `probe`.
15
15
  */
16
16
  import { notAnOption, stderrOrError } from '@osdlc/command-seam';
17
17
  import { parseLabels, parsePrSearchHit, parsePullRequest, parseRepository } from "./parse.js";
@@ -410,6 +410,24 @@ function flattenPages(body) {
410
410
  return body;
411
411
  return body.flat();
412
412
  }
413
+ /**
414
+ * A {@link graphql} variable that holds the target repository's owner or
415
+ * name. With a target repo, the value comes from it. Without one, gh fills
416
+ * it from the clone's repository. A plain string is always sent as written,
417
+ * so a real value of `{owner}` stays `{owner}`.
418
+ *
419
+ * ```ts
420
+ * gh.graphql(query, { owner: fromTarget('owner'), name: fromTarget('repo') })
421
+ * ```
422
+ *
423
+ * @experimental
424
+ */
425
+ export function fromTarget(part) {
426
+ if (part !== 'owner' && part !== 'repo') {
427
+ throw new RangeError(`fromTarget: expected 'owner' or 'repo', got ${JSON.stringify(part)}`);
428
+ }
429
+ return Object.freeze({ fromTarget: part });
430
+ }
413
431
  /**
414
432
  * True when any operation in a GraphQL document is a `mutation` or a
415
433
  * `subscription`. Strings and comments are dropped first, and only the words
@@ -439,8 +457,8 @@ function writesOrStreams(document) {
439
457
  * carries `errors` or lacks `data`. A partial answer counts as a failure. Read one
440
458
  * connection across the pages with {@link nodesAt}.
441
459
  *
442
- * A string variable of exactly `{owner}` or `{repo}` is filled from the
443
- * target's repo, or by gh from the clone. A document holding any mutation or
460
+ * A {@link fromTarget} variable is filled from the target's repo, or by gh
461
+ * from the clone. A plain string is sent as written. A document holding any mutation or
444
462
  * subscription throws `RangeError`, as does a variable named `query` or
445
463
  * `operationName`, which gh would send as the request's own fields. This is a
446
464
  * read, and changes go through `raw`.
@@ -467,14 +485,20 @@ export function graphql(query, variables = {}, opts = {}, target) {
467
485
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || key === 'query' || key === 'operationName') {
468
486
  throw new RangeError(`graphql: bad variable name ${JSON.stringify(key)}`);
469
487
  }
470
- if (typeof value === 'string') {
471
- const filled = value === '{owner}' ? owner : value === '{repo}' ? name : undefined;
488
+ if (typeof value === 'object' && value !== null) {
489
+ const part = value.fromTarget;
490
+ if (part !== 'owner' && part !== 'repo') {
491
+ throw new RangeError(`graphql: variable ${key} is not a fromTarget marker`);
492
+ }
493
+ const filled = part === 'owner' ? owner : name;
494
+ // `-F` lets gh fill the placeholder from the clone; `-f` never fills.
472
495
  if (filled !== undefined)
473
496
  argv.push('-f', `${key}=${filled}`);
474
- else if (value === '{owner}' || value === '{repo}')
475
- argv.push('-F', `${key}=${value}`);
476
497
  else
477
- argv.push('-f', `${key}=${value}`);
498
+ argv.push('-F', `${key}={${part}}`);
499
+ }
500
+ else if (typeof value === 'string') {
501
+ argv.push('-f', `${key}=${value}`);
478
502
  }
479
503
  else {
480
504
  argv.push('-F', `${key}=${value === null ? 'null' : String(value)}`);
@@ -1074,6 +1098,26 @@ export function createReview(number, opts, target) {
1074
1098
  },
1075
1099
  };
1076
1100
  }
1101
+ // ── raw calls ───────────────────────────────────────────────────────────────
1102
+ /**
1103
+ * Any `gh` call, as a verb whose value is the `CommandResult`, whatever the
1104
+ * exit code: `Gh.raw` as data. It adds no `--repo` and no host, so put them
1105
+ * in `argv`. It is a `probe`, so `runVerb` returns a failed or cut-short
1106
+ * result rather than throwing, and it is not `readOnly`, since the library
1107
+ * cannot tell what `argv` does.
1108
+ */
1109
+ export function raw(argv) {
1110
+ return { argv: [...argv], checked: false, readOnly: false, probe: true, parse: (r) => r };
1111
+ }
1112
+ /**
1113
+ * {@link raw}, checked: `Gh.rawChecked` as data. `runVerb` returns the
1114
+ * `CommandResult` when gh exits 0 with its whole output, and otherwise throws
1115
+ * a `FailureError` carrying the classified `GhFailure`. So a tool that runs
1116
+ * one call the verb table lacks needs no `Gh` for it.
1117
+ */
1118
+ export function rawChecked(argv) {
1119
+ return { argv: [...argv], checked: true, readOnly: false, parse: (r) => r };
1120
+ }
1077
1121
  // ── helpers over the parsed rows ────────────────────────────────────────────
1078
1122
  /**
1079
1123
  * The first row whose `headRefName` exactly equals `branch`, or null.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osdlc/typed-gh",
3
- "version": "0.14.1",
3
+ "version": "0.16.0",
4
4
  "description": "A typed GitHub client for TypeScript over the gh CLI. A total PullRequest/Review/CheckRun model, pure argv builders and parsers, and a tri-modal client — queries return parsed values, mutations throw, raw() is the escape hatch. Uses gh's own credentials; reads no token.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "author": "sksizer",
@@ -56,6 +56,7 @@
56
56
  "@vitest/coverage-v8": "^5.0.5",
57
57
  "oxlint": "^1.65.0",
58
58
  "prettier": "3.9.6",
59
+ "rolldown": "^1.2.14",
59
60
  "typescript": "~6.0.3",
60
61
  "vitest": "^5.0.5",
61
62
  "@osdlc/prettier-config": "^0.3.1",