Skip to content

Commit 9d164b2

Browse files
committed
Add RELEASING.md documenting the per-release flow
Captures what we've actually been doing: the three Justfile recipes (release-prepare, release.yml in CI, post-CI manual steps), the per-channel manual update commands (Homebrew, Scoop, AUR), and the one-time setup notes a future maintainer would otherwise have to rediscover — especially the AUR registration sequence with the main/master rename gotcha we just hit. Also documents the recovery path for a partially-failed release (re-run just the failed publish job) and the local sanity checks to run before tagging. Doesn't replicate user-facing install instructions — those live in README.md. This file is for maintainers.
1 parent a10181e commit 9d164b2

1 file changed

Lines changed: 129 additions & 0 deletions

File tree

RELEASING.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Releasing `qn`
2+
3+
How to cut a release. The pipeline is mostly automated via cargo-dist; a few channels still need manual maintainer steps until CI has the right credentials to do them itself.
4+
5+
## Per-release flow
6+
7+
Three named recipes in the `Justfile`:
8+
9+
1. **`just release-prepare X.Y.Z`** — orchestrates the bump → branch → PR → squash-merge → tag-push → wait-for-CI sequence. Tag push is what fires `release.yml`; do not also run `release-create-tag` (it races cargo-dist's host job).
10+
11+
Internally calls `release-bump`, `release-open-pr`, `release-merge-pr`, `release-tag-main`, `release-wait-ci`. Each is also runnable standalone if a step fails and you need to retry.
12+
13+
2. **`release.yml` runs in CI** — cross-compiles 7 targets, creates the GitHub Release, attaches archives + sha256 sidecars + SLSA attestations, then fans out to per-channel publish jobs.
14+
15+
Publish channels currently in CI:
16+
- `custom-publish-crates` → publishes `quicknode-cli` to crates.io
17+
- `custom-publish-docker` → builds multi-arch image, pushes to `ghcr.io/quicknode/qn`
18+
- `custom-publish-deb` → packages `.deb` per arch, uploads to the GitHub Release as assets
19+
20+
3. **Maintainer manual steps after CI succeeds.** Three channels still need a person to drive the publish because CI doesn't yet have the credentials it needs.
21+
22+
## Manual steps after each release
23+
24+
Run these from the repo root (`~/qn/cli`) after `release.yml` is green for the new tag.
25+
26+
### Homebrew
27+
28+
Sync the formula cargo-dist generated as a release artifact into the tap repo:
29+
30+
```fish
31+
just release-update-homebrew-tap X.Y.Z ~/qn/homebrew-tap
32+
git -C ~/qn/homebrew-tap push
33+
```
34+
35+
The recipe downloads `qn.rb` from the GitHub Release, copies it to `Formula/qn.rb` in the tap clone, commits with a clean message, and prints the push command. It does not push itself — review the diff first.
36+
37+
### Scoop
38+
39+
Bump the canonical `version` in `bucket/qn.json`:
40+
41+
```fish
42+
just release-update-scoop-bucket X.Y.Z ~/qn/scoop-bucket
43+
git -C ~/qn/scoop-bucket push
44+
```
45+
46+
The recipe pulls the Windows zip's sha256 from the release, renders a manifest with `version`, `hash`, and an `autoupdate` block, and stages it at `bucket/qn.json`. Once a user has tapped the bucket, `scoop update` finds new versions on its own — this manual step just keeps `scoop search qn` honest about what's current.
47+
48+
### AUR
49+
50+
Bump `pkgver` in the `qn-bin` AUR package:
51+
52+
```fish
53+
just release-update-aur-bin X.Y.Z ~/qn/qn-bin
54+
git -C ~/qn/qn-bin push
55+
```
56+
57+
The recipe pulls both Linux gnu sha256 sidecars (x86_64 + aarch64) from the release, renders a `PKGBUILD` + `.SRCINFO`, and stages them. Push goes to `ssh://aur@aur.archlinux.org/qn-bin.git` — the AUR's git remote.
58+
59+
## One-time setup notes
60+
61+
A few channels needed manual setup the first time. Captured here so the next maintainer doesn't have to rediscover them.
62+
63+
### Homebrew tap (`quicknode/homebrew-tap`)
64+
65+
Public repo on GitHub. Must be public — `brew tap` does an anonymous git clone. Has a single `Formula/qn.rb` per formula. cargo-dist generates the formula as a release artifact (whether or not we auto-publish), so the maintainer's job is just to commit it into the tap.
66+
67+
### Scoop bucket (`quicknode/scoop-bucket`)
68+
69+
Public repo on GitHub. Must be public — Scoop does an anonymous git clone. Has `bucket/qn.json` per package. We hand-render the manifest in the recipe (cargo-dist doesn't generate one).
70+
71+
### AUR (`qn-bin`)
72+
73+
Maintainer needs an AUR account at <https://aur.archlinux.org> with an SSH key registered. Once that's set up:
74+
75+
```fish
76+
# Confirm the name isn't taken (one-time, before first push)
77+
curl -sf "https://aur.archlinux.org/rpc/v5/info?arg[]=qn-bin" \
78+
| python3 -c "import json,sys; print(json.load(sys.stdin).get('resultcount'))"
79+
# 0 = free
80+
81+
# Clone the (currently empty) AUR git remote
82+
mkdir -p ~/qn
83+
cd ~/qn
84+
git clone ssh://aur@aur.archlinux.org/qn-bin.git
85+
# AUR returns: "warning: You appear to have cloned an empty repository." — expected.
86+
87+
# Render PKGBUILD + .SRCINFO from the latest release
88+
cd ~/qn/cli
89+
just release-update-aur-bin X.Y.Z ~/qn/qn-bin
90+
91+
# AUR expects the default branch to be `master`. Modern git defaults to `main`,
92+
# so rename the freshly-created branch before the first push.
93+
git -C ~/qn/qn-bin branch -m main master
94+
git -C ~/qn/qn-bin push -u origin master
95+
```
96+
97+
The first push registers the package on the AUR. Subsequent pushes just update it — no rename needed (the branch stays `master`).
98+
99+
After publishing: confirm via `https://aur.archlinux.org/packages/qn-bin` (the RPC at `/rpc/v5/info` can lag the package page by a few minutes — trust the web page, not the RPC, for fresh registrations).
100+
101+
### GHCR image visibility
102+
103+
The `ghcr.io/quicknode/qn` package defaults to inheriting visibility from `quicknode/cli`. Confirm visibility is as intended at <https://github.com/orgs/quicknode/packages>.
104+
105+
## Recovery: a publish channel failed
106+
107+
If a single publish-* job in `release.yml` fails (e.g. crates.io rejected the publish because the token expired), the rest of the release is still good — the GitHub Release, attestations, and other channels remain published.
108+
109+
To retry just the failed job:
110+
111+
```fish
112+
gh run rerun <run-id> --failed --repo quicknode/cli
113+
```
114+
115+
For crates.io specifically, the manual fallback if CI's auth is broken is:
116+
117+
```fish
118+
just release-cargo-publish
119+
# Requires `cargo login` first.
120+
```
121+
122+
## Sanity-checks before tagging
123+
124+
- `just lint` clean (`cargo clippy --all-targets -- -D warnings`)
125+
- `just test` clean
126+
- `just release-cargo-publish-check` clean (validates the crate tarball without uploading)
127+
- `dist plan` exits 0 (verifies the generated workflow matches `dist-workspace.toml`)
128+
129+
If `dist plan` complains the workflow is out of date, run `just dist-regen` to regenerate and commit the result.

0 commit comments

Comments
 (0)