Publishing Releases
This runbook is for maintainers. Contributors record package changes with version plans.
Prepare a release
Run the Release PR workflow from GitHub Actions or the CLI:
gh workflow run release-pr.ymlThe workflow runs only on request and never writes to main.
| Inputs | Result |
|---|---|
| Both empty | Consume pending plans on the current release train: the next beta during beta development, or the bump requested by plans on a stable train. With no pending plans, stop without opening a pull request. |
specifier=2.0.0 | End the beta and prepare the stable release, even without pending plans. |
preid=rc | Change the prerelease identifier, even without pending plans. |
The workflow versions all packages, updates tutorial ranges and the documentation version, prepends release notes to CHANGELOG.md, and deletes consumed plans. It opens or refreshes release/next as the release bot, with a GitHub-signed commit. Preview those preparation steps locally without writing files:
pnpm prepare-release --dry-runThe release GitHub App needs read and write access to contents and pull requests. Its credentials are the repository variable RELEASE_APP_CLIENT_ID and secret RELEASE_APP_PRIVATE_KEY.
For the first stable release, complete documentation promotion on the prepared release branch before advancing main. Include website copy and root and package README updates so the tag contains stable commands, status, and documentation links. Version preparation updates the documentation version number but does not update that prose or move pages.
Review and advance main
Review release/next, approve its latest commit, and wait for its required checks. Merge through GitHub using the repository's configured squash method. GitHub creates the signed commit on main; CI then verifies that exact merge commit. If main has moved or the prepared release changes, refresh the release PR and review the new head before merging.
Publication requires a merged release/next PR whose merge SHA is the release commit, an approval of its final head from a reviewer with repository write access, and no outstanding change request. It also requires a successful main CI run with ci-success and successful Sonar and all three CodeQL checks on that same SHA. The gate reads that commit's stored CodeQL analyses for all three languages and rejects every reported finding without an accepted dismissal. A later fix on another commit does not approve an older release. Missing analysis data fails verification. An administrator bypass does not satisfy this release gate.
Tag and publish
Successful completion of main CI starts Tag release. It recognizes a prepared release from the version's changelog entry and release state, verifies its merged review and exact commit checks, creates the annotated vX.Y.Z tag and draft GitHub release, and dispatches Publish from that tag. A normal commit without a prepared version does not publish. Rerunning the successful CI run can redispatch the same draft.
To curate the draft notes:
gh release edit vX.Y.Z --notes-file notes.mdPublish takes no inputs; its dispatched ref identifies the release. Validation rejects branch refs, tags that do not match the package version, releases that are not drafts, and commits without the required verification and review. It downloads the immutable multiarchitecture image digest recorded by that successful CI run. Missing verification artifacts or unavailable images fail the release. CI retains image evidence for 90 days. If that evidence expires, prepare a new release from a freshly verified main commit; do not move or reuse the old release tag. GitHub limits workflow reruns to 30 days after the initial run, so an expired artifact cannot be recovered by rerunning the original CI job.
Both native binaries and their generated JavaScript and type declarations are built in that verified image and uploaded with SHA-256 checksums. Separate x64 and arm64 jobs publish the staged binary to a disposable registry, scaffold consumers, verify the installed binary and generated bindings against those checksums, run codegen and builds, and exercise the generated application's headless tests. Publication restores the verified bindings before dependency installation, uses those same staged binaries without recompiling, and requires both architectures to produce identical shared JavaScript and declarations.
Every Publish run shares a single queue, including different versions and retries. Each package is published directly to its stable or prerelease channel with pnpm publish --tag, and its exact version and channel tag must become visible before publication continues. The channel can only move forward. npm cannot publish multiple packages atomically, so channel tags advance package by package. An interrupted publication leaves the GitHub draft unpublished, and a retry publishes the remaining packages and verifies the complete channel.
The final job freshly checks every exact package version and channel before publishing the GitHub draft, preserving its notes, and dispatching Website for that tag. A retry of that job fails if a newer release has advanced any package channel. This dispatch is needed because releases published with the workflow token do not trigger Website themselves.
The publication job uses the npm-release GitHub environment. Configure each npm trusted publisher to allow npm publish and require that environment and this workflow, restrict eligible release tags in GitHub, and enable immutable releases. Environment reviewers are optional; they do not replace the exact-commit verification gate. The publishing client authenticates through npm trusted publishing and sets the channel tag as part of publication. The workflow does not use separate npm dist-tag updates or npm stage publish.
Retry a failed publish
A failed build, upload, or registry check leaves the GitHub release in draft. Fix the cause and rerun Publish on the same tag. Already-published versions are skipped and checked for both exact-version visibility and the expected channel tag. Missing packages are published directly to that channel, then the complete package family is verified. If any package in the channel has advanced beyond the candidate version, the retry fails before uploading anything. An old partial release cannot move the channel backwards; prepare a newer release to supersede it.
If an existing version has a missing or incorrect channel tag, the retry fails rather than attempting a separate tag update with OIDC. A maintainer must correct the tag through authenticated npm access, or prepare a newer release. Existing package versions and Git release tags must remain unchanged.
If the GitHub release was published but the Website dispatch failed, rerun only the failed publish-release job. It recognizes the already-published release and retries the Website dispatch without changing the immutable release. Starting a new full Publish run still requires a draft.
The registry visibility timeout is ten minutes per package. GTKX_PUBLISH_VISIBILITY_TIMEOUT_MS overrides it for pnpm release or the publish scripts. It must be a positive integer in milliseconds; invalid values fail before upload. Beta 5 took three to four minutes to become visible.