Publishing Releases
This runbook is for maintainers. Contributors record user-facing changes with version plans.
Prepare a release
Run the Release workflow from GitHub Actions or the CLI:
gh workflow run release.ymlRelease preparation runs only on request and never writes to main. The same workflow tags prepared releases after main CI succeeds.
| Inputs | Result |
|---|---|
| Both empty | Consume pending plans. For stable versions, apply the requested bump; for prereleases, increment the existing prerelease version. With no pending plans, stop without opening a pull request. |
specifier=2.0.0 | Prepare version 2.0.0, even without pending plans. |
preid=rc | Change the prerelease identifier, even without pending plans. |
The workflow versions all packages, sets the tutorial's @gtkx/* ranges to the new 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 does not update that prose or move pages; the website's version label follows the new packages/create-gtkx version on its own.
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, run the Release workflow again with the same inputs and review the new head before merging. A refresh rebuilds release/next from main and discards commits pushed to it, including documentation promotion.
Repository merge rules enforce review approval, the Verify workspace check, and CodeQL merge protection. Release automation relies on those rules. CodeQL also runs on main, independently of CI; tagging does not wait for that analysis.
Tag and publish
Successful completion of main CI starts the tag job in the Release workflow. It requires that commit to be the merge of release/next and recognizes a prepared release from the version's changelog entry and release state. It creates the annotated vX.Y.Z tag and draft GitHub release, then 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 notes, edit the draft while Publish runs:
gh release edit vX.Y.Z --notes-file notes.mdPublish takes no inputs; its dispatched ref identifies the release. Validation requires a tag matching the package version and a matching draft release. When dispatching Publish manually, maintainers are responsible for selecting a reviewed release commit with successful CI.
Both native binaries and their generated JavaScript and type declarations are built on Ubuntu 26.04 runners and uploaded with SHA-256 checksums. After dependency installation, pnpm release --from-artifacts selects the release-artifacts Nx configuration throughout the release dependency graph. The publication build verifies and restores the staged native bindings without recompiling. Publication 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.
The final job runs node scripts/release/publish-release.ts --verify-only to freshly check 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 publish.yml, limit the environment's deployment tags to v*, and enable immutable releases. Environment reviewers are optional. 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. A rerun uses the workflow files and scripts stored at that tag, so a fix merged to main does not reach it; when the cause is in the tagged code, prepare a newer release. 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 publishing 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 editing the published release. Starting a new full Publish run still requires a draft.