GitHub
GitHub
Spoken by GitHubProvider. Same resources, same shapes, its own quirks kept inside.
Access
CreatecreateProvider("github")
Importimport { GitHubProvider } from"@agntn/forges/github"
EnvGH_TOKEN, GITHUB_TOKEN
CLIgh auth token
Config~/.config/gh/hosts.yml
- Header
- Authorization: token
- Anonymous
- 60 requests an hour per address
- Threads
- GraphQL, resolved and outdated flags
- Code search
- global, owner and repository scope
- Templates
- repository and owner .github defaults
Address it
import { createProvider } from "@agntn/forges";
const github = await createProvider("github"); // GH_TOKEN, GITHUB_TOKEN, then gh auth token
const enterprise = await createProvider("github", { baseURL: "https://github.example.com/api/v3" });
baseURL is taken as given, so spell out /api/v3 for Enterprise. The token chain asks gh for the host in baseURL, not for github.com.
What it reads
| Resource | Endpoint |
|---|---|
| repos | GET /repos/:owner/:repo, GET /orgs/:owner/repos, GET /users/:owner/repos, GET /user/repos for the viewer, GET …/tags |
| issues | GET /repos/:owner/:repo/issues, GET /search/issues, POST /repos/:owner/:repo/issues, PATCH …/issues/:number, …/issues/:number/comments |
| pull requests | GET /repos/:owner/:repo/pulls, …/pulls/:number/files, …/commits/:sha/check-runs, …/commits/:sha/status |
| issue and pull request edits | PATCH …/pulls/:number, POST/DELETE …/issues/:number/assignees, POST …/issues/:number/labels, DELETE …/labels/:name |
| pull request merges | PUT …/pulls/:number/merge, then a read of the pull request |
| commits | GET /repos/:owner/:repo/commits, …/commits/:sha, …/compare/:base...:head |
| commit checks | GET /repos/:owner/:repo/commits/:ref for the SHA, then …/commits/:sha/status, …/commits/:sha/check-runs |
| CI runs | GET /repos/:owner/:repo/actions/runs, …/actions/runs/:id/jobs, …/actions/jobs/:id, …/actions/jobs/:id/logs |
| releases | GET /repos/:owner/:repo/releases, …/releases/tags/:tag, POST …/releases, PATCH …/releases/:id |
| reactions | GET …/issues/:number/reactions, …/issues/comments/:id/reactions next to a read of the issue and the comment |
| threads | GraphQL reviewThreads, REST POST …/pulls/:number/comments/:id/replies |
| templates | GET /repos/:owner/:repo/contents/.github/…, then :owner/.github |
| code | GET /search/code |
Pagination reads the Link header, and nextCursor carries its rel="next" URL.
What comes back
Repository.parent from parent, viewerPermission from permissions when the token has any. merge from the allow_*_merge flags, squash_merge_commit_title, squash_merge_commit_message and delete_branch_on_merge, which GitHub returns only with push access and never on a list, so anything less is null. With pull requests off, methods is empty, push access or not. Issue from /issues with pull requests filtered out by the missing pull_request key. PullRequest.mergeable is null while GitHub is still computing it, which it does lazily, so the first read after a push often says null. Commit.filesComplete is true when GitHub confirms a complete file list, collected up to its cap of 3 000 files.
Templates: repository files first, then the owner's .github repository with scope: "owner" and inherited: true. Issue and pull request overrides resolve on their own. A host without the x-github-enterprise-version header that is not github.com is treated as repository scope only.
Gotchas
- Review threads need a token. GraphQL has no anonymous access and REST has no resolved flag, so there is no anonymous thread list to be had.
- Anonymous reads share sixty requests an hour per address. A 403 with a rate limit header is a
RateLimitErrorwithretryAfter, not aPermissionError. /issuesreturns pull requests too. Filtered out here. Search returns both, andpullRequests.searchkeeps the ones withpull_request.- Code search:
incompletegoes true on a search timeout, when scope enforcement drops a stray row, or past the cap of 1 000 results. - Issue search keeps GitHub's qualifier syntax.
label:bug is:openworks here and is plain text on GitLab and Gitea. releases/tags/:tagserves published releases only. A draft is found by walking the newest releases, five pages of a hundred, and a draft older than that is aNotFoundErrorby tag.
Where it lives
src/providers/github.ts. Also the template for a new provider. Copy it, do not start from scratch.