One page, copy-pasteable. Maintainers only. The same shape as the sibling projects' release docs — termlens, mossaic, reconverge — so a maintainer moving between them is not relearning the process.
- crates.io Trusted Publishing, linked to this repository and
release.yml. No token is stored anywhere; the publish job mints a short-lived one over OIDC. v*.*.*tags protected by a ruleset, so only a maintainer can push one.- Eight crates publish in dependency order, and
cargo publishwaits for each to appear on the index before the next. A rate-limited run can be resumed withworkflow_dispatch, which skips crates already on the registry.
# 0. Green main, and no flakes. The gate runs per PR; the hunt does not.
gh workflow run stress.yml -f iterations=100
gh run watch # ten shards, both OSes
# 1. Bump the version. `workspace.package.version` AND the internal
# `version =` pins in [workspace.dependencies] -- both, or `just
# versions` fails. They are not cosmetic: pins left behind a major
# bump make `cargo metadata` refuse to resolve the workspace at all.
$EDITOR Cargo.toml # version = "X.Y.Z", and the eight pins
cargo check --workspace # refreshes Cargo.lock
just versions # the two agree
# 2. Move the CHANGELOG section: [Unreleased] -> [X.Y.Z] - YYYY-MM-DD,
# leaving an empty [Unreleased] above it.
# 3. Bump every version the docs name. Two kinds go stale: the
# `action@vN` refs people copy, and "pin a number" examples a reader
# reasonably reads as current. This finds both:
grep -rEn "launchbound/action@v|[0-9]+\.[0-9]+\.[0-9]+" docs action README.md \
| grep -v CHANGELOG
# 4. Land it.
git switch -c release/vX.Y.Z
git commit -sam "release: vX.Y.Z"
gh pr create --fill
# 5. Tag the squash-merged commit on main.
git switch main && git pull
git tag vX.Y.Z && git push origin vX.Y.ZPushing the tag runs release.yml, which gates, then publishes each crate in
order via Trusted Publishing.
release.yml moves vN to each release, so @v2 follows 2.x without a
manual step. It is covered by the protect-release-tags ruleset only up to
refs/tags/v*.*.* — release tags are immutable, floating pointers are not,
because being moved is the whole of what a floating pointer is for.
That distinction was learned the hard way: the ruleset originally covered
refs/tags/v*, so the job could not create v2 at all and 2.0.0's floating
tag had to be pushed by hand.
-
The GitHub Release is created by hand, from the CHANGELOG section:
gh release create vX.Y.Z --title "launchbound X.Y.Z" --notes-file …. Every released version has one; do not skip it. -
Verify what was published, not what was built.
install.ymlinstalls from crates.io into a clean directory and runs the binaries; dispatch it once the version is live:gh workflow run install.yml
-
Move the semver baseline, in a PR of its own after the publish. It is the
--baseline-versionliteral inci.yml'ssemverjob, not a YAML input —grep baseline-version .github/workflows/ci.ymlfinds it.baseline-versioninci.yml'ssemverjob is a literal. Left at the old release it compares every PR against a version nobody can install any more, and it would also carry this release's own breaks forward as if they were new. Moved before the publish it names a version that does not exist yet and the job cannot fetch it. So: publish, confirm the index has it, then bump the literal.$EDITOR .github/workflows/ci.yml # --baseline-version X.Y.Z
-
A break needs the
breakinglabel on its PR, which switches the semver job frompatchtomajor. Without it the job fails, which is the point; with it, the release notes owe the reader a migration note.
- Breaking (minor pre-1.0, major after): a removed or renamed public item, a changed CLI flag, or a change to what the gate admits that a user would have to relearn.
- Not breaking: new flags, new backends, a corpus addition, a report field.
- MSRV and pinned-toolchain bumps are minor, never patch, and never land in the same change as a behaviour change.
-
Before publish: fix, delete the tag (
git push --delete origin vX.Y.Z), re-tag. Nothing was published; the world never saw it. -
Part-way through the eight crates: re-run
release.ymlby dispatch, passing the tag:gh workflow run release.yml -f tag=vX.Y.Z
It skips what is already on the registry, then moves
vN. The tag is an input rather than inferred because a dispatch has no tag of its own, and a resumed release that skipped the floating tag is a release whose documented@vNstill points at the previous version. -
After publish: crates.io is immutable. Ship
X.Y.Z+1. Yank only if the release is actively harmful — a yanked crate still breaks downstream lockfiles.