Skip to content

Latest commit

 

History

History
135 lines (110 loc) · 5.94 KB

File metadata and controls

135 lines (110 loc) · 5.94 KB

Releasing launchbound

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.

Prerequisites

  • 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 publish waits for each to appear on the index before the next. A rate-limited run can be resumed with workflow_dispatch, which skips crates already on the registry.

Cutting vX.Y.Z

# 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.Z

Pushing the tag runs release.yml, which gates, then publishes each crate in order via Trusted Publishing.

The floating major tag

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.

After the tag

  • The GitHub Release creates itself. release.yml's github-release job runs after publish and builds it from this version's CHANGELOG.md section, with the ## Install block appended. It used to be a manual step here, and 2.2.1 is what that cost: published to crates.io on 2026-09-10, with the repository still showing 2.2.0 as Latest twelve days later — for the release whose whole content was making the documentation people land on work. If the job fails, it is because the changelog has no ## [X.Y.Z] section, and it says so.

  • Verify what was published, not what was built. install.yml installs 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-version literal in ci.yml's semver job, not a YAML input — grep baseline-version .github/workflows/ci.yml finds it. baseline-version in ci.yml's semver job 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
    just versions                      # holds it to the newest published

    scripts/check-versions.sh now checks that literal against the index, so forgetting this step fails just ci rather than quietly measuring every later pull request against the wrong release. It is skipped, with a notice, when the index is unreachable.

  • A break needs the breaking label on its PR, which switches the semver job from patch to major. Without it the job fails, which is the point; with it, the release notes owe the reader a migration note.

What a version number means here

  • 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.

If something fails mid-release

  • 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.yml by 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 @vN still 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.