@theholocron/github-client 1.58.0-alpha.12 → 1.58.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -15,6 +15,10 @@ import { ErrorSink, Logger } from "@theholocron/observability/core";
15
15
  * which transport they're talking to.
16
16
  */
17
17
  interface GraphQLClient {
18
+ /**
19
+ * Sends one query or mutation to `POST /graphql` and returns its `data`. Throws a `ProviderApiError` (status `200`, with the
20
+ * response's `errors` array) when GitHub answers with errors, and the transport's own `ProviderApiError` for a network or HTTP failure.
21
+ */
18
22
  query<T>(document: string, variables?: Record<string, unknown>): Promise<T>;
19
23
  }
20
24
  /** Options for creating a GitHub client. */
@@ -115,6 +119,8 @@ interface GitHubCheckRun {
115
119
  conclusion: CheckRunConclusion | null;
116
120
  /** The URL of the check run's page on GitHub. */
117
121
  html_url: string;
122
+ /** The check's own "Details" link, when it set one. */
123
+ details_url?: string | null;
118
124
  }
119
125
  /** The body of an `updateCheckRun()` request; every field is optional. */
120
126
  interface UpdateCheckRunInput {
@@ -433,6 +439,40 @@ interface GitHubPullRequest {
433
439
  /** The head commit. */
434
440
  sha: string;
435
441
  };
442
+ /** Whether the pull request is a draft. */
443
+ draft?: boolean;
444
+ /** When the pull request was opened, as an ISO 8601 timestamp. */
445
+ created_at?: string;
446
+ /** The author. */
447
+ user?: {
448
+ /** The author's login. */
449
+ login: string;
450
+ /** The account type: `User` or `Bot` (Dependabot is a `Bot`). */
451
+ type: string;
452
+ };
453
+ /** Who merged it; `null` for a pull request that is not merged. */
454
+ merged_by?: {
455
+ login: string;
456
+ } | null;
457
+ /** The base branch. */
458
+ base?: {
459
+ /** The base branch's name, e.g. `alpha`. */
460
+ ref: string;
461
+ };
462
+ /** The people whose review is still asked for. */
463
+ requested_reviewers?: {
464
+ login: string;
465
+ }[];
466
+ /** Lines added, as the single-pull-request endpoint returns it (the list endpoint omits it). */
467
+ additions?: number;
468
+ /** Lines removed; see `additions`. */
469
+ deletions?: number;
470
+ /** Files changed; see `additions`. */
471
+ changed_files?: number;
472
+ /** Whether it merges cleanly; `null` while GitHub is still working it out. */
473
+ mergeable?: boolean | null;
474
+ /** `dirty` means it conflicts with its base; see `mergeable`. */
475
+ mergeable_state?: string;
436
476
  }
437
477
  /** A commit's name/email as git itself recorded it — `commit.author`/`commit.committer`, distinct from the GitHub-account `author` below (a commit can carry any email, linked to a GitHub account or not). */
438
478
  interface GitHubCommitIdentity {
@@ -511,6 +551,12 @@ interface GitHubPullRequestReview {
511
551
  id: number;
512
552
  /** The URL of the review on GitHub. */
513
553
  html_url: string;
554
+ /** `APPROVED`, `CHANGES_REQUESTED`, `COMMENTED`, `DISMISSED` or `PENDING`; set on a review read back. */
555
+ state?: string;
556
+ /** The reviewer; set on a review read back. */
557
+ user?: {
558
+ login: string;
559
+ } | null;
514
560
  }
515
561
  /**
516
562
  * One review thread — GraphQL-only (`resolveReviewThread`, the mutation
@@ -546,6 +592,8 @@ interface GitHubRepo {
546
592
  default_branch: string;
547
593
  /** The repository's `owner/name`. */
548
594
  full_name: string;
595
+ /** Whether the repository is archived (read-only). */
596
+ archived?: boolean;
549
597
  /** The repository description. */
550
598
  description?: string | null;
551
599
  /** The repository homepage URL. */
@@ -751,6 +799,41 @@ export declare function createAppJWT(creds: GitHubAppCredentials, now?: number):
751
799
  export declare function getInstallationAccessToken(creds: GitHubAppCredentials, installationId: number, opts?: Pick<GitHubClientOptions, "baseUrl" | "fetch">): Promise<InstallationAccessToken>;
752
800
  /** The whole flow in one call — an already-authenticated `GitHubClient` scoped to one installation. */
753
801
  export declare function createInstallationClient(creds: GitHubAppCredentials, installationId: number, opts?: Pick<GitHubClientOptions, "baseUrl" | "fetch">): Promise<GitHubClient>;
802
+ /** The account an App or one of its installations belongs to. */
803
+ interface GitHubAppAccount {
804
+ /** The user or organization login. */
805
+ login: string;
806
+ /** `User` or `Organization` (GitHub also reports `Enterprise` for an enterprise-owned App). */
807
+ type: string;
808
+ }
809
+ /** An App as `GET /app` reports it. */
810
+ interface GitHubApp {
811
+ /** The App's URL name, as in `github.com/apps/<slug>`. */
812
+ slug: string;
813
+ /** The App's display name. */
814
+ name: string;
815
+ /** Who owns the App: decides which settings page its permissions live on. */
816
+ owner: GitHubAppAccount | null;
817
+ /** The permissions the App asks for now: name to `read`, `write` or `admin`. */
818
+ permissions: Record<string, string>;
819
+ /** The webhook events the App is subscribed to now. */
820
+ events: string[];
821
+ }
822
+ /** One installation of an App as `GET /app/installations` reports it. */
823
+ interface GitHubAppInstallation {
824
+ /** The installation id. */
825
+ id: number;
826
+ /** The account the App is installed on. */
827
+ account: GitHubAppAccount | null;
828
+ /** What this installation has accepted: it lags the App's own list until an owner accepts a new request. */
829
+ permissions: Record<string, string>;
830
+ /** The events delivered to this installation. */
831
+ events: string[];
832
+ }
833
+ /** The App this JWT identifies: its slug, owner and the permissions and events it asks for now. */
834
+ export declare function getApp(creds: GitHubAppCredentials, opts?: Pick<GitHubClientOptions, "baseUrl" | "fetch">): Promise<GitHubApp>;
835
+ /** Every installation of the App this JWT identifies, with what each has accepted. Reads all pages. */
836
+ export declare function listAppInstallations(creds: GitHubAppCredentials, opts?: Pick<GitHubClientOptions, "baseUrl" | "fetch">): Promise<GitHubAppInstallation[]>;
754
837
  /**
755
838
  * GitHub's own webhook mechanics — signature verification and header/
756
839
  * payload shapes. Pure functions, no REST call, no auth token: these
@@ -855,6 +938,7 @@ export declare function createGitHubClient(opts: GitHubClientOptions): {
855
938
  removeBranch: (repo: string, branch: string) => Promise<void>;
856
939
  };
857
940
  checks: {
941
+ listCheckRunsForRef: (repo: string, ref: string) => Promise<GitHubCheckRun[]>;
858
942
  createCheckRun: (repo: string, input: CreateCheckRunInput) => Promise<GitHubCheckRun>;
859
943
  updateCheckRun: (repo: string, checkRunId: number, input: UpdateCheckRunInput) => Promise<GitHubCheckRun>;
860
944
  };
@@ -924,6 +1008,7 @@ export declare function createGitHubClient(opts: GitHubClientOptions): {
924
1008
  listPullRequests: (repo: string, state: "open" | "closed") => Promise<GitHubPullRequest[]>;
925
1009
  listCommits: (repo: string, number: number) => Promise<GitHubPullRequestCommit[]>;
926
1010
  listFiles: (repo: string, number: number) => Promise<GitHubPullRequestFile[]>;
1011
+ listReviews: (repo: string, number: number) => Promise<GitHubPullRequestReview[]>;
927
1012
  createReview: (repo: string, number: number, input: CreateReviewInput) => Promise<GitHubPullRequestReview>;
928
1013
  listReviewThreads: (repo: string, number: number) => Promise<ReviewThread[]>;
929
1014
  resolveReviewThread: (threadId: string) => Promise<void>;
@@ -985,4 +1070,4 @@ export declare function createGitHubClient(opts: GitHubClientOptions): {
985
1070
  };
986
1071
  /** The client returned by `createGitHubClient()`: one group of methods per GitHub API area. */
987
1072
  export type GitHubClient = ReturnType<typeof createGitHubClient>;
988
- export type { CheckRunAnnotation, CheckRunAnnotationLevel, CheckRunConclusion, CheckRunOutput, CheckRunStatus, CodeScanningSetupResult, CreateCheckRunInput, CreatePagesPayload, CreatePullInput, CreateReviewComment, CreateReviewInput, GitBlob, GitCommit, GitContents, GitDirectoryEntry, GitHubAppCredentials, GitHubCheckRun, GitHubClientOptions, GitHubContents, GitHubEnvironment, GitHubInstallationWebhookPayload, GitHubIssue, GitHubLabel, GitHubMilestone, GitHubPages, GitHubPublicKey, GitHubPullRequest, GitHubPullRequestCommit, GitHubPullRequestFile, GitHubPullRequestReview, GitHubPullRequestWebhookPayload, GitHubPushWebhookPayload, GitHubRepo, GitHubRuleset, GitHubRulesetDetail, GitHubUser, GitHubUserWithExpiry, GitHubVariable, GitHubWebhookHeaders, GitHubWorkflowRun, GitPull, GitRef, GitTree, GitTreeItem, GraphQLClient, InstallationAccessToken, IssueSearchParams, ListDirectoryOptions, OrgProperty, PagesBuildStatus, PagesBuildType, PutContentsInput, PutContentsResult, RepoPropertyValue, RepoTeam, ReviewThread, SecretScope, TeamMember, TeamPermission, UpdateCheckRunInput, UpdatePagesPayload, VariableScope, VariableVisibility, WorkflowRunFilter };
1073
+ export type { CheckRunAnnotation, CheckRunAnnotationLevel, CheckRunConclusion, CheckRunOutput, CheckRunStatus, CodeScanningSetupResult, CreateCheckRunInput, CreatePagesPayload, CreatePullInput, CreateReviewComment, CreateReviewInput, GitBlob, GitCommit, GitContents, GitDirectoryEntry, GitHubApp, GitHubAppAccount, GitHubAppCredentials, GitHubAppInstallation, GitHubCheckRun, GitHubClientOptions, GitHubContents, GitHubEnvironment, GitHubInstallationWebhookPayload, GitHubIssue, GitHubLabel, GitHubMilestone, GitHubPages, GitHubPublicKey, GitHubPullRequest, GitHubPullRequestCommit, GitHubPullRequestFile, GitHubPullRequestReview, GitHubPullRequestWebhookPayload, GitHubPushWebhookPayload, GitHubRepo, GitHubRuleset, GitHubRulesetDetail, GitHubUser, GitHubUserWithExpiry, GitHubVariable, GitHubWebhookHeaders, GitHubWorkflowRun, GitPull, GitRef, GitTree, GitTreeItem, GraphQLClient, InstallationAccessToken, IssueSearchParams, ListDirectoryOptions, OrgProperty, PagesBuildStatus, PagesBuildType, PutContentsInput, PutContentsResult, RepoPropertyValue, RepoTeam, ReviewThread, SecretScope, TeamMember, TeamPermission, UpdateCheckRunInput, UpdatePagesPayload, VariableScope, VariableVisibility, WorkflowRunFilter };
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ProviderApiError, createRestClient } from "@theholocron/http-client";
2
2
  import { createHmac, timingSafeEqual } from "node:crypto";
3
3
  // src/graphql/graphql.ts
4
+ /** A {@link GraphQLClient} for GitHub's GraphQL API, built on the shared REST transport. */
4
5
  function createGitHubGraphQLClient(config) {
5
6
  const rest = createRestClient({
6
7
  baseUrl: config.baseUrl ?? "https://api.github.com",
@@ -107,8 +108,21 @@ function branches(rest) {
107
108
  * do for you; slice with it rather than hardcoding the number.
108
109
  */
109
110
  const MAX_CHECK_RUN_ANNOTATIONS = 50;
111
+ /** Page size and the most pages {@link checks}`.listCheckRunsForRef` reads: 100 a page over 10 pages, so 1,000 check runs. */
112
+ const CHECK_RUN_PAGE_SIZE = 100;
113
+ const CHECK_RUN_MAX_PAGES = 10;
110
114
  function checks(rest) {
111
115
  return {
116
+ /** Every check run on a commit, read a page at a time until a page comes back short. */
117
+ listCheckRunsForRef: async (repo, ref) => {
118
+ const all = [];
119
+ for (let page = 1; page <= CHECK_RUN_MAX_PAGES; page++) {
120
+ const batch = await rest.request(`${repoBase(repo)}/commits/${ref}/check-runs?per_page=${CHECK_RUN_PAGE_SIZE}&page=${page}`);
121
+ all.push(...batch.check_runs);
122
+ if (batch.check_runs.length < CHECK_RUN_PAGE_SIZE) break;
123
+ }
124
+ return all;
125
+ },
112
126
  createCheckRun: (repo, input) => rest.request(`${repoBase(repo)}/check-runs`, {
113
127
  method: "POST",
114
128
  body: input
@@ -413,6 +427,8 @@ function pulls(rest, graphql) {
413
427
  * is an edge case no consumer here needs to paginate for today.
414
428
  */
415
429
  listFiles: (repo, number) => rest.request(`${repoBase(repo)}/pulls/${number}/files?per_page=100`),
430
+ /** A PR's reviews, oldest first. `per_page=100` covers every real PR in this org. */
431
+ listReviews: (repo, number) => rest.request(`${repoBase(repo)}/pulls/${number}/reviews?per_page=100`),
416
432
  /**
417
433
  * Submits a review in one call — GitHub has no separate "create draft,
418
434
  * then submit" step when `event` is provided directly (the multi-step
@@ -875,6 +891,62 @@ async function createInstallationClient(creds, installationId, opts = {}) {
875
891
  fetch: opts.fetch
876
892
  });
877
893
  }
894
+ // src/app-info/app-info.ts
895
+ /**
896
+ * What a GitHub App is right now, read with the App's own JWT (`GET /app`, `GET /app/installations`). GitHub has no
897
+ * endpoint that changes an existing App's permissions or event subscriptions, so these reads are how a caller
898
+ * checks the settings page was done by hand.
899
+ *
900
+ * https://docs.github.com/en/rest/apps/apps#get-the-authenticated-app
901
+ * https://docs.github.com/en/rest/apps/apps#list-installations-for-the-authenticated-app
902
+ */
903
+ const PAGE_SIZE = 100;
904
+ const account = (value) => {
905
+ const { login, type } = value ?? {};
906
+ return typeof login === "string" ? {
907
+ login,
908
+ type: typeof type === "string" ? type : ""
909
+ } : null;
910
+ };
911
+ async function appGet(creds, path, opts) {
912
+ const jwt = await createAppJWT(creds);
913
+ const baseUrl = opts.baseUrl ?? "https://api.github.com";
914
+ const res = await (opts.fetch ?? globalThis.fetch)(`${baseUrl}${path}`, { headers: {
915
+ authorization: `Bearer ${jwt}`,
916
+ accept: "application/vnd.github+json",
917
+ "x-github-api-version": "2022-11-28"
918
+ } });
919
+ if (!res.ok) {
920
+ const body = await res.text().catch(() => "");
921
+ throw new ProviderApiError(`GitHub GET ${path} → ${res.status}`, res.status, body);
922
+ }
923
+ return res.json();
924
+ }
925
+ /** The App this JWT identifies: its slug, owner and the permissions and events it asks for now. */
926
+ async function getApp(creds, opts = {}) {
927
+ const raw = await appGet(creds, "/app", opts);
928
+ return {
929
+ slug: String(raw.slug),
930
+ name: String(raw.name),
931
+ owner: account(raw.owner),
932
+ permissions: raw.permissions ?? {},
933
+ events: raw.events ?? []
934
+ };
935
+ }
936
+ /** Every installation of the App this JWT identifies, with what each has accepted. Reads all pages. */
937
+ async function listAppInstallations(creds, opts = {}) {
938
+ const installations = [];
939
+ for (let page = 1;; page++) {
940
+ const raws = await appGet(creds, `/app/installations?per_page=${PAGE_SIZE}&page=${page}`, opts);
941
+ for (const raw of raws) installations.push({
942
+ id: Number(raw.id),
943
+ account: account(raw.account),
944
+ permissions: raw.permissions ?? {},
945
+ events: raw.events ?? []
946
+ });
947
+ if (raws.length < PAGE_SIZE) return installations;
948
+ }
949
+ }
878
950
  // src/webhooks/webhooks.ts
879
951
  /**
880
952
  * GitHub's own webhook mechanics — signature verification and header/
@@ -948,4 +1020,4 @@ function createGitHubClient(opts) {
948
1020
  workflows: workflows(rest)
949
1021
  };
950
1022
  }
951
- export { MAX_CHECK_RUN_ANNOTATIONS, createAppJWT, createGitHubClient, createInstallationClient, getInstallationAccessToken, parseGitHubWebhookHeaders, parseTokenExpiration, verifyGitHubWebhookSignature };
1023
+ export { MAX_CHECK_RUN_ANNOTATIONS, createAppJWT, createGitHubClient, createInstallationClient, getApp, getInstallationAccessToken, listAppInstallations, parseGitHubWebhookHeaders, parseTokenExpiration, verifyGitHubWebhookSignature };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/github-client",
3
- "version": "1.58.0-alpha.12",
3
+ "version": "1.58.0-alpha.14",
4
4
  "description": "A TypeScript client for the GitHub REST API",
5
5
  "keywords": [
6
6
  "github",
@@ -32,7 +32,7 @@
32
32
  "dist"
33
33
  ],
34
34
  "dependencies": {
35
- "@theholocron/http-client": "^1.58.0-alpha.12",
35
+ "@theholocron/http-client": "^1.58.0-alpha.14",
36
36
  "@theholocron/observability": "^0.7.0-alpha.1"
37
37
  },
38
38
  "devDependencies": {
@@ -45,10 +45,10 @@
45
45
  "tsdown": "^0.23.4",
46
46
  "typescript": "^5.9.3",
47
47
  "vitest": "^5.0.5",
48
- "@theholocron/tsconfig": "^8.9.0-alpha.8",
49
- "@theholocron/eslint-config": "^8.9.0-alpha.8",
50
- "@theholocron/tsdown-config": "^8.9.0-alpha.8",
51
- "@theholocron/vitest-config": "^8.9.0-alpha.8"
48
+ "@theholocron/eslint-config": "^8.9.0-alpha.9",
49
+ "@theholocron/tsdown-config": "^8.9.0-alpha.9",
50
+ "@theholocron/tsconfig": "^8.9.0-alpha.9",
51
+ "@theholocron/vitest-config": "^8.9.0-alpha.9"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=22.0.0"