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