Repository navigation
Add a minimal GitHub client for workflow scripts - #14806
Draft
ArmandPhilippot wants to merge 22 commits into
Draft
ArmandPhilippot wants to merge 22 commits into
ArmandPhilippot wants to merge 22 commits into
Conversation
Add `GitHubRepository`, a client for the GitHub REST API without Octokit. Each instance acts on one repository: it reads reviews, checks, open pull requests, comparisons, and permissions, and it creates, labels, updates, merges, comments on, and requests changes on pull requests. Read methods check the fields they return with small type guards, and return only those fields. Branch names are encoded in URLs. Each write sends a single request, so a script can override the writes for a dry run.
`head.repo` tells a branch of the repository apart from a fork branch with the same name. It is `null` when the fork was deleted.
`ReadOnlyRepository` sends reads and logs writes instead of sending them. Its test fails when the client gets a method that is neither listed as a read nor overridden, so a new write cannot reach GitHub during a local run.
Only the endpoint for one pull request sends `mergeable`, so `getPullRequest` returns `PullRequestDetails`, and the list keeps `PullRequest`. `mergeable` is `null` until GitHub has checked for conflicts.
`getFileContent` reads the text of a file on a ref. It returns `null` when GitHub answers "Not Found", which it does for a missing file, ref, or repository. It throws for a directory, and for a file over 1 MB, which GitHub sends without its content.
`getBranchSha` reads the commit at the head of a branch, or returns `null` when GitHub answers "Not Found". `createBranch` creates a branch at a commit.
GitHub filters the open pull requests by owner and head branch, so a script finds the pull request of a branch in one request, and a fork's branch with the same name is left out.
`updateReview` changes the text of a review and keeps its state, so a script can keep its own review current instead of submitting a new one. `Review` gains `body`, so a script can compare the current text first.
`PullRequestDetails` gains `merged`. `updatePullRequest` can change the title, and `mergePullRequest` can set the title of the merge commit.
A shape checks a response and returns a copy with only the fields it lists, so each field is written once: the types come from the shapes, and the functions that copied each response are gone. Errors now name the field that does not match.
`findPullRequest` returns `null` when GitHub answers "Not Found", like `getFileContent` and `getBranchSha`. The three methods and `hasWriteAccess` now share the handling of that answer.
`Repository` is the public shape of `GitHubRepository`, so a test fake can implement it. `hasLabel` tells whether a pull request has a label, without mapping its labels to names first.
GitHub checks a pull request for conflicts in the background, so `mergeable` is `null` until it has. `waitForMergeability` asks again a few times before giving up. Tests pass their own `wait`, so they do not sleep.
A reviewer's state on a pull request is their latest approval, change request, or dismissal: comments and pending reviews change nothing. `getLatestReviewStates` applies that rule to every reviewer.
`updateReview(prNumber, { reviewId, body })` matches `updatePullRequest`
and `mergePullRequest`, which take the pull request number and an
object.
`toChecks` only reduces the check runs and commit statuses to outcomes. `getChecks` checks that each list fits in one page, since it knows the ref the error names.
`sendRequest` and `getAllPages` take the shape of the response, so the request line for errors is built in one place, from the URL, and always without its query. Writes reuse the same request method.
Editors and type errors showed a derived type by the alias that builds it, with the shape of each field. They now show the fields and their types, without `readonly`, like the types written by hand.
The path only names fields in errors, so reading a whole body no longer passes an empty string.
The shapes of the check runs and of the commit statuses refuse a list with more items than one page, so the error names the request, ref included, and `getChecks` no longer checks the lists itself.
`shape.matches` tells whether a value matches a shape without throwing, so error bodies, where any field can be missing, are read with shapes too. `isRecord` is no longer exported.
This was referenced Oct 29, 2026
|
Preview deployment ✅ Deployment complete!
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The "umbrella PR assistant" idea is bigger than I thought and folding everything in a single PR is hard to review. So, I have this PR to create a minimal GitHub client, and the release workflow is built on that branch. The diff is still "big" but there are a lot of tests.
Description (required)
Adds a small GitHub REST client in
scripts/lib/github/for our workflow scripts:withastro/docsand readswithastro/astro(and every methods was designed for this workflow)weekly-contributors.tscould use it to avoid repeating the fetch, and to validate the sape.Why not Octokit:
fetchalready covers what we need.octokitis ~11MB;@octokit/restis ~7MB).Unexpected response from GET …/pulls/7: `head.repo.full_name` should be a string), instead of failing later.The client can create two different instances:
repository.ts: aGitHubRepositorywith the read and write methods.read-only-repository.ts: sends reads and logs writes. This is useful for local runs (e.g. a dry run mode).There is one module that is not directly related and could be extracted from
lib/github/:shape.ts. A minimal "validator". I hesitated to use Zod, but Florian has a PR to decouple Astro from it, and it seemed a bit "heavy" for the use case.This includes tests (and that's why the diff might look big): they're meant for local debugging, so CI doesn't run them. I added a new script in
package.jsonto run them easily:pnpm test:scripts.References