@effected/github 0.25.0 → 0.26.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/index.d.ts CHANGED
@@ -272,7 +272,7 @@ declare const GitHubError_base: Schema.Class<GitHubError, Schema.TaggedStruct<"G
272
272
  * 422s ("Update is not a fast forward") are prose only.
273
273
  */
274
274
  readonly validation: Schema.optionalKey<Schema.$Array<typeof GitHubValidationEntry>>;
275
- /** The underlying throwable, when one exists. */
275
+ /** The underlying throwable, when one exists; an octokit throwable is kept as a credential-free copy. */
276
276
  readonly cause: Schema.optionalKey<Schema.Defect>;
277
277
  }>, import("effect/Cause").YieldableError>;
278
278
  /**
@@ -318,6 +318,16 @@ export declare class GitHubError extends GitHubError_base {
318
318
  * epoch second, and turning it into a delay needs a "now" the caller controls.
319
319
  * The client supplies `Clock.currentTimeMillis`, which is the `TestClock`
320
320
  * under test.
321
+ *
322
+ * The kept `cause` is a credential-free copy of the throwable, not the
323
+ * throwable itself. octokit redacts only the `authorization` credential and
324
+ * two query values; the copy also drops the request body (a webhook secret,
325
+ * GraphQL variables), the per-request options and the response body beyond
326
+ * its top-level `message` and `documentation_url`, drops `set-cookie`, and
327
+ * reads `REDACTED` for every other credential-bearing header. It keeps the
328
+ * prototype (so `instanceof` still answers), `name`, `message`, `status`,
329
+ * the method, the redacted url and the remaining headers. A throwable with
330
+ * no `request`, `response` or `headers` field is kept as it is.
321
331
  */
322
332
  static fromOctokit(operation: string, error: unknown, nowMillis: number): GitHubError;
323
333
  /**
@@ -437,6 +447,11 @@ export declare class GitHubGraphQLError extends GitHubGraphQLError_base {
437
447
  * failure with a `status`. Both arrive as throwables and both are read
438
448
  * structurally, for the same reason the REST classifier does it — the error
439
449
  * classes live in packages this one does not declare.
450
+ *
451
+ * The kept `cause` is the same credential-free copy
452
+ * {@link GitHubError.fromOctokit} keeps: the GraphQL document, its variables
453
+ * and any partial `data` are dropped with the rest of the request and response
454
+ * bodies.
440
455
  */
441
456
  static fromThrowable(operation: string, error: unknown, nowMillis: number): GitHubGraphQLError;
442
457
  }
@@ -649,17 +664,22 @@ export declare class RetryPolicy extends RetryPolicy_base {
649
664
  */
650
665
  type Route = keyof Endpoints;
651
666
  /**
652
- * Transport knobs octokit accepts on any route, narrowed to the three this
667
+ * Transport knobs octokit accepts on any route, narrowed to the two this
653
668
  * package allows.
654
669
  *
655
670
  * @remarks
656
671
  * octokit's own `RequestParameters` carries an `[parameter: string]: unknown`
657
672
  * index signature, so intersecting it would silently accept every misspelled
658
- * parameter. These three are the ones callers need:
659
- * `headers` for a release asset's `content-type` and the attestations API
660
- * version pin, `mediaType.format` for raw content reads, and `baseUrl` for the
661
- * `uploads.github.com` host that release-asset uploads go to. Everything else a
662
- * caller might reach for is a real endpoint parameter and is already typed.
673
+ * parameter. These two are the ones callers need: `headers` for a release
674
+ * asset's `content-type` and the attestations API version pin, and
675
+ * `mediaType.format` for raw content reads. Everything else a caller might
676
+ * reach for is a real endpoint parameter and is already typed.
677
+ *
678
+ * There is no knob that moves a request: **a client's token only ever goes to
679
+ * the host it was configured with.** octokit reads `baseUrl` and `url` off the
680
+ * flat parameters as the request's host, so the raw surface refuses either
681
+ * name, and an absolute-URL route, with a `rejected` `GitHubError` and nothing
682
+ * sent. A request to another GitHub host is a second client configured for it.
663
683
  *
664
684
  * @public
665
685
  */
@@ -670,16 +690,21 @@ interface RequestExtras {
670
690
  readonly mediaType?: {
671
691
  readonly format?: string;
672
692
  };
673
- /** Overrides the API host for this one request. */
674
- readonly baseUrl?: string;
675
693
  }
676
694
  /**
677
695
  * The parameters `Route` accepts — path, query and body parameters from
678
696
  * GitHub's OpenAPI description, plus the extras below.
679
697
  *
698
+ * @remarks
699
+ * `baseUrl` is typed `never`, so passing one is a compile error: the request
700
+ * goes to the client's configured host and nowhere else. One that arrives
701
+ * through a cast is refused at runtime.
702
+ *
680
703
  * @public
681
704
  */
682
- type Params<R extends Route> = Endpoints[R]["parameters"] & RequestExtras;
705
+ type Params<R extends Route> = Endpoints[R]["parameters"] & RequestExtras & {
706
+ readonly baseUrl?: never;
707
+ };
683
708
  /**
684
709
  * The full response `Route` returns, including `status`, `headers` and `data`.
685
710
  *
@@ -770,6 +795,12 @@ interface GitHubClientShape {
770
795
  /**
771
796
  * One request. The route types both the parameters and the returned `data`.
772
797
  *
798
+ * @remarks
799
+ * The request carries this client's token to the configured base URL and
800
+ * nowhere else. A `baseUrl` or `url` parameter (through a cast), or a
801
+ * route that resolves to another origin, fails `kind: "rejected"` with no
802
+ * status and nothing sent.
803
+ *
773
804
  * @example
774
805
  * ```ts
775
806
  * import { GitHubClient } from "@effected/github";
@@ -794,13 +825,29 @@ interface GitHubClientShape {
794
825
  * checked. The attestations read uses it, because pinning
795
826
  * `X-GitHub-Api-Version` puts the response on a contract the generated types
796
827
  * do not describe.
828
+ *
829
+ * The token goes to the configured base URL and nowhere else. An
830
+ * absolute-URL route (`"POST https://host/path"`, which octokit would send
831
+ * as written) on another origin, a `baseUrl`, `url`, `request` or `method`
832
+ * parameter, or a template that expands to another host fails
833
+ * `kind: "rejected"` with no status and nothing sent. A body field octokit
834
+ * would otherwise read as an option, such as a webhook configuration's
835
+ * `url`, goes inside `data`, which is sent as the whole body.
797
836
  */
798
- readonly requestDecoded: <A, I>(route: string, params: Record<string, unknown> & RequestExtras, schema: Schema.Codec<A, I>) => Effect.Effect<A, GitHubError>;
837
+ readonly requestDecoded: <A, I>(route: string, params: Record<string, unknown> & RequestExtras & {
838
+ readonly baseUrl?: never;
839
+ }, schema: Schema.Codec<A, I>) => Effect.Effect<A, GitHubError>;
799
840
  /**
800
841
  * Collect every page of a paginating route, honoring {@link PageOptions}.
801
842
  *
802
843
  * @remarks
803
844
  * Handing this a non-paginating route is a compile error.
845
+ *
846
+ * Every page goes to the configured host. A route or parameter that
847
+ * resolves elsewhere is refused before the first request, and a `Link`
848
+ * header whose next page names another origin fails the walk
849
+ * `kind: "rejected"` before that page is requested, rather than sending the
850
+ * token there or ending the walk early as if the collection were complete.
804
851
  */
805
852
  readonly paginate: <R extends PaginatingRoute>(route: R, params: Params<R>, options?: PageOptions) => Effect.Effect<ReadonlyArray<Item<R>>, GitHubError>;
806
853
  /**
@@ -808,7 +855,8 @@ interface GitHubClientShape {
808
855
  *
809
856
  * @remarks
810
857
  * Lazy in requests: a downstream `Stream.take` stops the walk rather than
811
- * filtering pages that were already fetched.
858
+ * filtering pages that were already fetched. Confined to the configured
859
+ * host exactly as {@link GitHubClientShape.paginate} is.
812
860
  */
813
861
  readonly paginateStream: <R extends PaginatingRoute>(route: R, params: Params<R>, options?: PageOptions) => Stream.Stream<Item<R>, GitHubError>;
814
862
  /** Run an owned GraphQL document and decode its answer. */
@@ -1116,6 +1164,12 @@ export declare class GitHubClient extends GitHubClient_base {
1116
1164
  * `rateLimit` answers `Option.none()` and `apiVersion` the live default,
1117
1165
  * because a real client legitimately answers both without a request.
1118
1166
  *
1167
+ * It refuses what the live client refuses, before any override runs: a
1168
+ * `baseUrl`, `url`, `request` or `method` parameter (a body field called
1169
+ * `url` goes inside `data`), or a route that resolves off github.com's REST
1170
+ * root, fails `kind: "rejected"` with no status. A double has no
1171
+ * configured base, so an absolute-URL route must name `api.github.com`.
1172
+ *
1119
1173
  * For recorded responses that page for real, use
1120
1174
  * {@link GitHubClient.layerFixture}.
1121
1175
  */
@@ -1140,6 +1194,11 @@ export declare class GitHubClient extends GitHubClient_base {
1140
1194
  * one fixture both records the calls and classifies a raw failure the way
1141
1195
  * the live client would; there is no need to classify by hand inside a
1142
1196
  * {@link GitHubClient.layerTest} handler.
1197
+ *
1198
+ * It refuses what the live client refuses, exactly as
1199
+ * {@link GitHubClient.makeTest} does. A refused call fails
1200
+ * `kind: "rejected"` and is **not** recorded in `requested`: it is never
1201
+ * served, as a refused live call never reaches the wire.
1143
1202
  */
1144
1203
  static readonly layerFixture: (fixtures: GitHubFixtures) => Layer.Layer<GitHubClient>;
1145
1204
  }
@@ -2090,6 +2149,14 @@ interface DeploymentEnvironmentShape {
2090
2149
  * `config` stays an open record because the protection-rule body is a moving
2091
2150
  * target: wait timers, reviewers, deployment-branch policies and whatever
2092
2151
  * GitHub adds next.
2152
+ *
2153
+ * It is sent as the request BODY and nothing else. octokit reads `baseUrl`,
2154
+ * `url`, `method`, `headers`, `request` and `mediaType` off a call's flat
2155
+ * parameters as the request itself, and a `data` key as the whole body, so a
2156
+ * config spread beside the coordinates could send the authenticated `PUT` to
2157
+ * another host with the token attached. A config naming one of those, the
2158
+ * route's own `owner`, `repo` or `environment_name`, or `__proto__`, dies
2159
+ * with a `RangeError` before any request is made.
2093
2160
  */
2094
2161
  readonly upsert: (name: string, config?: Record<string, unknown>) => Effect.Effect<void, GitHubError, Repo>;
2095
2162
  /** The repository's deployment environments. */
@@ -3470,7 +3537,15 @@ declare const ReleaseInfo_base: Schema.Class<ReleaseInfo, Schema.Struct<{
3470
3537
  readonly prerelease: Schema.Boolean;
3471
3538
  /** The web URL. */
3472
3539
  readonly url: Schema.String;
3473
- /** The templated upload endpoint GitHub hands back for assets. */
3540
+ /**
3541
+ * The templated upload endpoint GitHub hands back for assets.
3542
+ *
3543
+ * @remarks
3544
+ * {@link GitHubReleaseShape.uploadAsset} never routes by this value: it is
3545
+ * response data. It only cross-checks that its origin is the upload host
3546
+ * derived from the client's configured base URL, and refuses the upload
3547
+ * when it is not.
3548
+ */
3474
3549
  readonly uploadUrl: Schema.String;
3475
3550
  }>, {}>;
3476
3551
  /**
@@ -3528,9 +3603,9 @@ interface GitHubReleaseShape {
3528
3603
  *
3529
3604
  * @remarks
3530
3605
  * The one route in this package that is **not** in GitHub's generated
3531
- * endpoint map: asset upload goes to `uploads.github.com` with a raw binary
3532
- * body, and the map omits it. So it goes through `requestDecoded` with an
3533
- * owned schema — the escape hatch is from the route table, never from typing.
3606
+ * endpoint map: asset upload goes to a separate upload host with a raw
3607
+ * binary body, and the map omits it. So it is decoded against an owned
3608
+ * schema — the escape hatch is from the route table, never from typing.
3534
3609
  *
3535
3610
  * Being outside the map cuts the other way too: octokit has no schema
3536
3611
  * saying `name` is a **query** parameter, so the route template must carry
@@ -3538,6 +3613,29 @@ interface GitHubReleaseShape {
3538
3613
  * `Invalid name for request` — a hand-written route owns its query
3539
3614
  * parameters in the template, always. `label` is the endpoint's optional
3540
3615
  * display label, shown in place of the file name on the release page.
3616
+ *
3617
+ * The upload host is derived from the base URL the client was **configured**
3618
+ * with, so the token only goes where the caller pointed it: github.com
3619
+ * (`https://api.github.com`, or no `baseUrl`) uploads to
3620
+ * `https://uploads.github.com`, GitHub Enterprise Server
3621
+ * (`https://<host>/api/v3`) to `https://<host>/api/uploads`, and any other
3622
+ * base to itself. The release's own `uploadUrl` is response data, so it is
3623
+ * never the route; its origin must equal the derived host's, or the call
3624
+ * fails `kind: "rejected"` with no status and nothing is sent.
3625
+ *
3626
+ * The client's public `requestDecoded` cannot reach the upload host: it
3627
+ * refuses every host but the configured API host. A client this package
3628
+ * built uploads through a private channel it carries off its public type,
3629
+ * which takes no host from anyone. A shape spread from a built client
3630
+ * (`{ ...client, requestDecoded }`) keeps that channel, so its upload does
3631
+ * **not** go through an overridden `requestDecoded`.
3632
+ *
3633
+ * A client shape this package did not build (`GitHubClient.makeTest`,
3634
+ * `layerFixture`, a hand-written double) has no channel: it is read as
3635
+ * github.com for the `uploadUrl` check, and the upload is handed to its own
3636
+ * `requestDecoded`, so a double records and answers it as usual. Before
3637
+ * these checks, a GitHub Enterprise Server client's upload sent its token to
3638
+ * `uploads.github.com`.
3541
3639
  */
3542
3640
  readonly uploadAsset: (release: ReleaseInfo, asset: {
3543
3641
  readonly name: string;
@@ -3593,11 +3691,20 @@ export declare class GitHubRelease extends GitHubRelease_base {
3593
3691
  */
3594
3692
  type RepositorySettings = Data<"GET /repos/{owner}/{repo}">;
3595
3693
  /**
3596
- * The fields `PATCH /repos/{owner}/{repo}` accepts, minus the coordinate.
3694
+ * The fields `PATCH /repos/{owner}/{repo}` accepts, minus the coordinate and
3695
+ * the per-request transport options.
3696
+ *
3697
+ * @remarks
3698
+ * A patch is the request **body** and nothing else. `GitHubRepository`
3699
+ * sends it as octokit's `data` option, so no key in it is ever read as a
3700
+ * request option (`baseUrl`, `url`, `headers`, `request`, `mediaType`) or as
3701
+ * the route's `owner` / `repo`. The transport knobs of `Rest.RequestExtras`
3702
+ * are therefore left out of the type: carried in a patch they would be body
3703
+ * fields, not the options they are named after.
3597
3704
  *
3598
3705
  * @public
3599
3706
  */
3600
- type RepositoryPatch = Omit<Params<"PATCH /repos/{owner}/{repo}">, "owner" | "repo">;
3707
+ type RepositoryPatch = Omit<Params<"PATCH /repos/{owner}/{repo}">, "owner" | "repo" | keyof RequestExtras>;
3601
3708
  /**
3602
3709
  * A {@link RepositoryPatch} under construction, where an absent field may be
3603
3710
  * spelled as an explicit `undefined`.
@@ -3613,9 +3720,66 @@ type RepositoryPatch = Omit<Params<"PATCH /repos/{owner}/{repo}">, "owner" | "re
3613
3720
  * @public
3614
3721
  */
3615
3722
  type RepositoryPatchDraft = { readonly [K in keyof RepositoryPatch]?: RepositoryPatch[K] | undefined; };
3723
+ /**
3724
+ * Repository settings the generated {@link RepositoryPatch} type does not know
3725
+ * yet, for {@link RepositoryPatchOptions.extra}.
3726
+ *
3727
+ * @remarks
3728
+ * An open map minus every key `RepositoryPatch` declares: a known key written
3729
+ * here is a compile error, so it has to go in the draft, where its value is
3730
+ * checked against octokit's type. Only what octokit has not caught up with
3731
+ * travels unchecked. The guard holds for a literal; a value already typed
3732
+ * `Record<string, unknown>` assigns anyway (an index signature does not meet an
3733
+ * optional property), which is why {@link repositoryPatch} checks again at
3734
+ * runtime.
3735
+ *
3736
+ * @public
3737
+ */
3738
+ type RepositoryPatchExtra = {
3739
+ readonly [key: string]: unknown;
3740
+ } & { readonly [K in keyof RepositoryPatch]?: never; } & { readonly [K in RepositoryPatchReservedKey]?: never; };
3741
+ /**
3742
+ * Names {@link repositoryPatch} refuses in a draft or an extra.
3743
+ *
3744
+ * @remarks
3745
+ * octokit takes a route's path parameters, body fields and transport options
3746
+ * in one flat object, and `@octokit/endpoint` reads these names as the request
3747
+ * itself: `baseUrl` and `url` pick the host the authenticated request goes to,
3748
+ * `headers` its headers, `request` the fetch implementation, `data` the whole
3749
+ * body, `method` the verb, and `owner` / `repo` the path of
3750
+ * `PATCH /repos/{owner}/{repo}`. `__proto__` would set the patch's prototype
3751
+ * instead of a field. None of them is a repository setting.
3752
+ *
3753
+ * @public
3754
+ */
3755
+ type RepositoryPatchReservedKey = "method" | "baseUrl" | "url" | "headers" | "request" | "mediaType" | "data" | "owner" | "repo" | "__proto__";
3756
+ /**
3757
+ * Options for {@link repositoryPatch}.
3758
+ *
3759
+ * @public
3760
+ */
3761
+ interface RepositoryPatchOptions {
3762
+ /**
3763
+ * Settings GitHub accepts that octokit's generated types do not declare
3764
+ * yet, sent verbatim.
3765
+ *
3766
+ * @remarks
3767
+ * **GitHub may ignore them silently.** The REST `PATCH` answers 200 for a
3768
+ * field it does not recognise, so a misspelled extra reads as applied while
3769
+ * nothing changed: read the setting back when it matters. An `undefined`
3770
+ * value is dropped, as in the draft. A key the PATCH body already declares,
3771
+ * or a GraphQL-only setting (`has_sponsorships`, `has_pull_requests`,
3772
+ * `has_discussions`, which this patch cannot set; use
3773
+ * `GitHubRepository.applySettings`), throws a `RangeError` naming it, as
3774
+ * does a {@link RepositoryPatchReservedKey}: an octokit request option
3775
+ * (`baseUrl`, `url`, `headers`, `request`, `data`, and so on), the route's
3776
+ * own `owner` / `repo`, or `__proto__`.
3777
+ */
3778
+ readonly extra?: RepositoryPatchExtra | undefined;
3779
+ }
3616
3780
  /**
3617
3781
  * A {@link RepositoryPatch} from a draft, dropping every explicitly-`undefined`
3618
- * field.
3782
+ * field, plus any settings octokit's types do not know yet.
3619
3783
  *
3620
3784
  * @remarks
3621
3785
  * The supported spelling for "apply only what was configured" — the natural
@@ -3625,6 +3789,13 @@ type RepositoryPatchDraft = { readonly [K in keyof RepositoryPatch]?: Repository
3625
3789
  * `PATCH` treats an absent field as "leave it alone", while an explicit `null`
3626
3790
  * or `undefined` is a value.
3627
3791
  *
3792
+ * `options.extra` is the typed escape hatch for a setting GitHub accepts
3793
+ * before octokit's generated types declare it: the draft stays checked, and
3794
+ * only the extras ride unchecked. The result is still typed `RepositoryPatch`,
3795
+ * and `GitHubRepository.updateSettings` sends every key it carries. A known key
3796
+ * passed through `extra` throws a `RangeError` rather than skipping the draft's
3797
+ * type check (see {@link RepositoryPatchOptions.extra}).
3798
+ *
3628
3799
  * A key-by-key loop still defeats TypeScript's correlation between two indexed
3629
3800
  * accesses (`draft[key] = source[key]` over a union `key`), which no helper can
3630
3801
  * fix — build the draft as a literal where you can.
@@ -3634,19 +3805,24 @@ type RepositoryPatchDraft = { readonly [K in keyof RepositoryPatch]?: Repository
3634
3805
  * import { repositoryPatch } from "@effected/github";
3635
3806
  *
3636
3807
  * // `config.has_issues` is `boolean | undefined`; absent fields drop out.
3637
- * const patch = repositoryPatch({
3638
- * has_issues: config.has_issues,
3639
- * has_wiki: config.has_wiki,
3640
- * description: config.description,
3641
- * });
3808
+ * const patch = repositoryPatch(
3809
+ * {
3810
+ * has_issues: config.has_issues,
3811
+ * has_wiki: config.has_wiki,
3812
+ * description: config.description,
3813
+ * },
3814
+ * // A setting GitHub accepts before octokit's types declare it.
3815
+ * { extra: { has_new_feature: true } },
3816
+ * );
3642
3817
  * ```
3643
3818
  *
3644
3819
  * @param draft - The fields to apply, any of which may be `undefined`.
3820
+ * @param options - `extra`: settings the generated type does not declare.
3645
3821
  * @returns A patch carrying only the fields that were actually set.
3646
3822
  *
3647
3823
  * @public
3648
3824
  */
3649
- export declare const repositoryPatch: (draft: RepositoryPatchDraft) => RepositoryPatch;
3825
+ export declare const repositoryPatch: (draft: RepositoryPatchDraft, options?: RepositoryPatchOptions) => RepositoryPatch;
3650
3826
  /**
3651
3827
  * Whether an account is a user or an organization.
3652
3828
  *
@@ -3741,8 +3917,50 @@ interface AppliedSettings {
3741
3917
  * REST `PATCH` ignores unknown fields and answers 200, so the key would be
3742
3918
  * reported applied while the repository never changed. A caller that wants
3743
3919
  * an unknown key to fail the run checks this list.
3920
+ *
3921
+ * Under `unknownKeys: "send"` an undescribed key is sent instead and named
3922
+ * in {@link AppliedSettings.unverified}; a key the api-version removed is
3923
+ * still ignored, because the description says GitHub drops it.
3924
+ *
3925
+ * Under either mode, a name octokit reads as a request option rather than
3926
+ * a body field (`baseUrl`, `url`, `headers`, `request`, `mediaType`,
3927
+ * `method`, `data`), the route's own `owner` / `repo`, and `__proto__` are
3928
+ * never sent and land here.
3744
3929
  */
3745
3930
  readonly ignored: ReadonlyArray<string>;
3931
+ /**
3932
+ * Keys sent on the REST patch that GitHub's description does not declare,
3933
+ * so nothing confirms GitHub acted on them. Always a subset of
3934
+ * {@link AppliedSettings.rest}, and empty unless the call passed
3935
+ * `unknownKeys: "send"`.
3936
+ *
3937
+ * @remarks
3938
+ * The REST `PATCH` answers 200 whether or not it recognised a field, so a
3939
+ * key here may have changed the repository or done nothing. Read the setting
3940
+ * back when it matters.
3941
+ */
3942
+ readonly unverified: ReadonlyArray<string>;
3943
+ }
3944
+ /**
3945
+ * Options for {@link GitHubRepositoryShape.applySettings}.
3946
+ *
3947
+ * @public
3948
+ */
3949
+ interface ApplySettingsOptions {
3950
+ /**
3951
+ * What to do with a key neither API declares. `"ignore"` (the default)
3952
+ * leaves it unsent and reports it in {@link AppliedSettings.ignored};
3953
+ * `"send"` forwards it on the REST patch verbatim and reports it in
3954
+ * {@link AppliedSettings.rest} and {@link AppliedSettings.unverified}.
3955
+ *
3956
+ * @remarks
3957
+ * `"send"` is for a sync tool applying settings a user declared that GitHub
3958
+ * accepts before its OpenAPI description, and octokit's types, catch up.
3959
+ * The cost is that a typo is sent too and answered 200, so it is only as
3960
+ * safe as the caller's own validation. GraphQL-only keys still route to
3961
+ * GraphQL, and a key the client's api-version removed is still ignored.
3962
+ */
3963
+ readonly unknownKeys?: "ignore" | "send" | undefined;
3746
3964
  }
3747
3965
  /**
3748
3966
  * Read and update a repository's settings and topics, and look up its default
@@ -3809,8 +4027,12 @@ interface GitHubRepositoryShape {
3809
4027
  * in `ignored`, rather than forwarded to a `PATCH` that would ignore it and
3810
4028
  * answer 200. It does not fail the call, so additive GitHub API growth never
3811
4029
  * breaks a run; a caller that owns a schema should still validate first.
4030
+ *
4031
+ * A setting GitHub accepts before its description declares it is sent with
4032
+ * `{ unknownKeys: "send" }` ({@link ApplySettingsOptions}), and reported in
4033
+ * `unverified` as well as `rest`, because a 200 does not say GitHub used it.
3812
4034
  */
3813
- readonly applySettings: (settings: Record<string, unknown>) => Effect.Effect<AppliedSettings, GitHubError | GitHubGraphQLError, Repo>;
4035
+ readonly applySettings: (settings: Record<string, unknown>, options?: ApplySettingsOptions) => Effect.Effect<AppliedSettings, GitHubError | GitHubGraphQLError, Repo>;
3814
4036
  /**
3815
4037
  * The repository's topics, as GitHub stores them.
3816
4038
  *
@@ -4959,5 +5181,5 @@ export declare class WorkflowDispatch extends WorkflowDispatch_base {
4959
5181
  /** {@link WorkflowDispatch.makeTest} behind a `Layer`. */
4960
5182
  static readonly layerTest: (overrides?: Partial<WorkflowDispatchShape>) => Layer.Layer<WorkflowDispatch>;
4961
5183
  }
4962
- export { type ActionsOidcVerifyOptions, type AppCredentials, type AppliedSettings, type ArtifactMetadataShape, type AttestationShape, type AuthenticatedAppShape, type BareLineReference, type BotUserRequest, type BranchOutcome, CLOSING_KEYWORDS, type CachedToken, type CachedTokenRequest, type CheckRunShape, type ClosingKeyword, type CodeScanningSetup, type CodeScanningShape, type CompleteCheckRunOptions, type ConcludeCheckRun, type CreateCheckRunOptions, type DeliveriesOptions, type DeploymentEnvironmentInfo, type DeploymentEnvironmentShape, type GitBranchShape, type GitCommitShape, type GitHubAppOptions, type GitHubAppShape, type GitHubClientOptions, type GitHubClientShape, type GitHubCommitShape, type GitHubContentShape, type GitHubIssueShape, type GitHubReleaseShape, type GitHubRepositoryShape, type GitTagShape, type InstallationTokenStoreShape, type IssueReference, type LatestSemverOptions, type ListCheckRunsOptions, type OwnerType, type PollOptions, type PullRequestCommentShape, type PullRequestShape, type RecordedCall, type RepositoryPatch, type RepositoryPatchDraft, type RepositorySecretShape, type RepositorySecurityShape, type RepositorySettings, type RepositoryVariableShape, type Data as RestData, type RequestExtras as RestExtras, type Item as RestItem, type PaginatingRoute as RestPaginatingRoute, type Params as RestParams, type Response as RestResponse, type Route as RestRoute, type RetryableFailure, type RulesetInfo, type RulesetPayload, type RulesetShape, type SecretInfo, type SecretScope, type TokenRequest, type UpdateCheckRunOptions, type UpsertedPullRequest, type VariableInfo, type VersionFromTag, type WorkflowDispatchShape, type WorkflowInfo, harvestIssueReferences, parseBareLineReference };
5184
+ export { type ActionsOidcVerifyOptions, type AppCredentials, type AppliedSettings, type ApplySettingsOptions, type ArtifactMetadataShape, type AttestationShape, type AuthenticatedAppShape, type BareLineReference, type BotUserRequest, type BranchOutcome, CLOSING_KEYWORDS, type CachedToken, type CachedTokenRequest, type CheckRunShape, type ClosingKeyword, type CodeScanningSetup, type CodeScanningShape, type CompleteCheckRunOptions, type ConcludeCheckRun, type CreateCheckRunOptions, type DeliveriesOptions, type DeploymentEnvironmentInfo, type DeploymentEnvironmentShape, type GitBranchShape, type GitCommitShape, type GitHubAppOptions, type GitHubAppShape, type GitHubClientOptions, type GitHubClientShape, type GitHubCommitShape, type GitHubContentShape, type GitHubIssueShape, type GitHubReleaseShape, type GitHubRepositoryShape, type GitTagShape, type InstallationTokenStoreShape, type IssueReference, type LatestSemverOptions, type ListCheckRunsOptions, type OwnerType, type PollOptions, type PullRequestCommentShape, type PullRequestShape, type RecordedCall, type RepositoryPatch, type RepositoryPatchDraft, type RepositoryPatchExtra, type RepositoryPatchOptions, type RepositoryPatchReservedKey, type RepositorySecretShape, type RepositorySecurityShape, type RepositorySettings, type RepositoryVariableShape, type Data as RestData, type RequestExtras as RestExtras, type Item as RestItem, type PaginatingRoute as RestPaginatingRoute, type Params as RestParams, type Response as RestResponse, type Route as RestRoute, type RetryableFailure, type RulesetInfo, type RulesetPayload, type RulesetShape, type SecretInfo, type SecretScope, type TokenRequest, type UpdateCheckRunOptions, type UpsertedPullRequest, type VariableInfo, type VersionFromTag, type WorkflowDispatchShape, type WorkflowInfo, harvestIssueReferences, parseBareLineReference };
4963
5185
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { GitHubError } from "../GitHubError.js";
2
+ import { reservedRawParam } from "./requestOptions.js";
3
+ import { originOf } from "./uploads.js";
4
+
5
+ // src/internal/confinement.ts
6
+ /**
7
+ * Why one of the caller's parameters may not be sent, or `undefined`.
8
+ *
9
+ * @remarks
10
+ * Checked against the CALLER's parameters, before anything the transport adds
11
+ * (`per_page`, a signal, the upload base): `baseUrl` and `url` move the host,
12
+ * `request` swaps the fetch, `method` rewrites the verb.
13
+ *
14
+ * @internal
15
+ */
16
+ const reservedParamReason = (params) => {
17
+ for (const key of Object.keys(params)) {
18
+ const reason = reservedRawParam(key);
19
+ if (reason !== void 0) return `the parameter "${key}" ${reason}`;
20
+ }
21
+ };
22
+ /**
23
+ * Why `route` with `params` may not be sent to the origin `allowed`, or
24
+ * `undefined` when it may.
25
+ *
26
+ * @remarks
27
+ * The URL checked is the one `expand` builds, which is the one octokit itself
28
+ * would send, so no spelling of the route reaches a host this check did not
29
+ * see.
30
+ *
31
+ * @internal
32
+ */
33
+ const routeReason = (expand, route, params, allowed) => {
34
+ let url;
35
+ try {
36
+ url = expand(route, params).url;
37
+ } catch {
38
+ return "the route does not expand to a URL";
39
+ }
40
+ if (allowed === void 0) return "the configured base URL is not an absolute URL";
41
+ const origin = originOf(url);
42
+ return origin === allowed ? void 0 : `the route resolves to ${origin ?? "a URL that does not parse"}, not the configured host ${allowed}; refusing to send the token there`;
43
+ };
44
+ /**
45
+ * Why a raw-surface call may not be sent to `allowed`, or `undefined`: a
46
+ * reserved parameter first, then the route's resolved origin.
47
+ *
48
+ * @internal
49
+ */
50
+ const rawRequestRefusal = (expand, route, params, allowed) => reservedParamReason(params) ?? routeReason(expand, route, params, allowed);
51
+ /**
52
+ * The error a refused call fails with: `kind: "rejected"`, no status.
53
+ *
54
+ * @internal
55
+ */
56
+ const refusedRequest = (operation, reason) => new GitHubError({
57
+ kind: "rejected",
58
+ operation,
59
+ reason
60
+ });
61
+
62
+ export { rawRequestRefusal, refusedRequest, reservedParamReason, routeReason };
@@ -1,5 +1,7 @@
1
1
  import { GitHubError, readRateLimitHeaders } from "../GitHubError.js";
2
2
  import { GitHubGraphQLError } from "../GraphQL.js";
3
+ import { DOTCOM_API_BASE_URL, originOf } from "./uploads.js";
4
+ import { rawRequestRefusal, refusedRequest, reservedParamReason, routeReason } from "./confinement.js";
3
5
  import { RateLimitSnapshot } from "../Resilience.js";
4
6
  import { Clock, Effect, Option, Redacted, Ref } from "effect";
5
7
  import { Octokit } from "@octokit/core";
@@ -40,6 +42,19 @@ const makeOctokit = (options) => {
40
42
  });
41
43
  return octokit;
42
44
  };
45
+ let dotcomExpand;
46
+ /**
47
+ * octokit's URL expansion against github.com's REST root, for the shipped
48
+ * doubles, which have no transport of their own but must refuse exactly what
49
+ * an unconfigured live client refuses.
50
+ *
51
+ * @remarks
52
+ * Built on first use, from an octokit instance that never sends anything.
53
+ */
54
+ const expandOnDotcom = (route, params) => {
55
+ dotcomExpand ??= new Octokit({ log: SILENT_LOG }).request.endpoint;
56
+ return dotcomExpand(route, params);
57
+ };
43
58
  /**
44
59
  * Build a transport over a freshly constructed octokit instance.
45
60
  *
@@ -72,11 +87,36 @@ const makeTransport = (options) => Effect.gen(function* () {
72
87
  return yield* Effect.fail(classify(error, now));
73
88
  })));
74
89
  const withRetry = (operation, effect) => effect.pipe(Effect.tapError((error) => policy.retries(error) ? Effect.logDebug("github.retry").pipe(Effect.annotateLogs({ operation })) : Effect.void), Effect.retry(policy.schedule()));
75
- const request = (operation, route, params) => withRetry(operation, attempt((signal) => octokit.request(route, withSignal(params, signal)), (error, now) => GitHubError.fromOctokit(operation, error, now)).pipe(Effect.tap((response) => record(response.headers))));
90
+ const apiOrigin = originOf(options.baseUrl ?? "https://api.github.com");
91
+ const expand = (route, params) => octokit.request.endpoint(route, params);
92
+ const refused = refusedRequest;
93
+ const send = (operation, route, params) => withRetry(operation, attempt((signal) => octokit.request(route, withSignal(params, signal)), (error, now) => GitHubError.fromOctokit(operation, error, now)).pipe(Effect.tap((response) => record(response.headers))));
94
+ const request = (operation, route, params) => Effect.suspend(() => {
95
+ const reason = rawRequestRefusal(expand, route, params, apiOrigin);
96
+ return reason !== void 0 ? Effect.fail(refused(operation, reason)) : send(operation, route, params);
97
+ });
98
+ const requestAt = (operation, route, params, baseUrl) => Effect.suspend(() => {
99
+ const target = {
100
+ ...params,
101
+ baseUrl
102
+ };
103
+ const reason = reservedParamReason(params) ?? routeReason(expand, route, target, originOf(baseUrl));
104
+ return reason !== void 0 ? Effect.fail(refused(operation, reason)) : send(operation, route, target);
105
+ });
76
106
  const pageSource = (operation, route, params) => {
77
- const iterator = composePaginateRest.iterator(octokit, route, params)[Symbol.asyncIterator]();
107
+ const reason = rawRequestRefusal(expand, route, params, apiOrigin);
108
+ if (reason !== void 0) return { next: Effect.fail(refused(operation, reason)) };
109
+ let signal;
110
+ const onApiHost = Object.assign((page) => {
111
+ const origin = originOf(page.url);
112
+ return origin === apiOrigin ? octokit.request(signal === void 0 ? page : withSignal(page, signal)) : Promise.reject(new CrossOriginPage(origin, apiOrigin));
113
+ }, { endpoint: (parameters) => octokit.request.endpoint(route, parameters) });
114
+ const iterator = composePaginateRest.iterator(octokit, onApiHost, params)[Symbol.asyncIterator]();
78
115
  let finished = false;
79
- return { next: Effect.suspend(() => finished ? Effect.succeed(Option.none()) : withRetry(operation, attempt(() => iterator.next(), (error, now) => GitHubError.fromOctokit(operation, error, now)).pipe(Effect.flatMap((result) => {
116
+ return { next: Effect.suspend(() => finished ? Effect.succeed(Option.none()) : withRetry(operation, attempt((pageSignal) => {
117
+ signal = pageSignal;
118
+ return iterator.next();
119
+ }, (error, now) => error instanceof CrossOriginPage ? refused(operation, error.message) : GitHubError.fromOctokit(operation, error, now)).pipe(Effect.flatMap((result) => {
80
120
  if (result.done === true || result.value === void 0) {
81
121
  finished = true;
82
122
  return Effect.succeed(Option.none());
@@ -85,14 +125,64 @@ const makeTransport = (options) => Effect.gen(function* () {
85
125
  return record(response.headers).pipe(Effect.as(Option.some(response.data)));
86
126
  })))) };
87
127
  };
88
- const graphql = (operation, document, variables) => withRetry(operation, attempt(() => octokit.graphql(document, variables), (error, now) => GitHubGraphQLError.fromThrowable(operation, error, now)));
128
+ const graphqlUrl = graphqlEndpoint(options.baseUrl);
129
+ const graphql = (operation, document, variables) => withRetry(operation, attempt((signal) => octokit.request("POST /graphql", {
130
+ ...graphqlUrl !== void 0 ? { url: graphqlUrl } : {},
131
+ data: Object.keys(variables).length > 0 ? {
132
+ query: document,
133
+ variables
134
+ } : { query: document },
135
+ request: { signal }
136
+ }).then((response) => readGraphQLResponse(response)), (error, now) => GitHubGraphQLError.fromThrowable(operation, error, now)));
89
137
  return {
90
138
  request,
139
+ requestAt,
91
140
  pageSource,
92
141
  graphql,
93
142
  rateLimit: Ref.get(snapshot)
94
143
  };
95
144
  });
145
+ /**
146
+ * The GraphQL endpoint for a GitHub Enterprise Server base URL, or `undefined`
147
+ * for the route's own `/graphql` under the configured base.
148
+ *
149
+ * @remarks
150
+ * GHES serves REST under `/api/v3` and GraphQL under `/api/graphql`, so
151
+ * `<base>/graphql` would miss it. This is the rewrite `@octokit/graphql`
152
+ * applies, kept because the call no longer goes through it. The result is
153
+ * derived from the configured base alone, so it never names another host.
154
+ */
155
+ const graphqlEndpoint = (baseUrl) => baseUrl !== void 0 && GHES_V3_SUFFIX.test(baseUrl) ? baseUrl.replace(GHES_V3_SUFFIX, "/api/graphql") : void 0;
156
+ const GHES_V3_SUFFIX = /\/api\/v3\/?$/;
157
+ /**
158
+ * The `data` of a GraphQL answer, or a throw carrying its `errors`.
159
+ *
160
+ * @remarks
161
+ * GitHub answers a GraphQL failure with HTTP 200 and an `errors` array. The
162
+ * throw carries `errors` and the response `headers` — the fields
163
+ * `GitHubGraphQLError.fromThrowable` reads structurally — so it classifies
164
+ * exactly as `@octokit/graphql`'s `GraphqlResponseError` did.
165
+ */
166
+ const readGraphQLResponse = (response) => {
167
+ const body = typeof response.data === "object" && response.data !== null ? response.data : {};
168
+ if (body.errors !== void 0) {
169
+ const messages = (Array.isArray(body.errors) ? body.errors : []).map((entry) => typeof entry === "object" && entry !== null && typeof entry.message === "string" ? ` - ${entry.message}` : ` - ${String(entry)}`);
170
+ throw Object.assign(/* @__PURE__ */ new Error(`Request failed due to following response errors:\n${messages.join("\n")}`), {
171
+ name: "GraphqlResponseError",
172
+ errors: body.errors,
173
+ headers: response.headers,
174
+ data: body.data
175
+ });
176
+ }
177
+ return body.data;
178
+ };
179
+ /** A paginated walk's next page named a host other than the configured one. */
180
+ var CrossOriginPage = class extends Error {
181
+ constructor(origin, allowed) {
182
+ super(`a next page is on ${origin ?? "a URL that does not parse"}, not the configured host ${allowed ?? "(none)"}; refusing to send the token there`);
183
+ this.name = "CrossOriginPage";
184
+ }
185
+ };
96
186
  /** Merge our abort signal into octokit's per-request options without clobbering them. */
97
187
  const withSignal = (params, signal) => {
98
188
  const existing = typeof params.request === "object" && params.request !== null ? params.request : {};
@@ -113,4 +203,4 @@ const readThrownHeaders = (error) => {
113
203
  return typeof headers === "object" && headers !== null ? headers : void 0;
114
204
  };
115
205
 
116
- export { DEFAULT_API_VERSION, makeTransport };
206
+ export { DEFAULT_API_VERSION, expandOnDotcom, makeTransport };