Skip to content

Commit 40e4df5

Browse files
committed
Document full publication flow
1 parent fdea7bf commit 40e4df5

1 file changed

Lines changed: 217 additions & 0 deletions

File tree

docs/PUBLISHING.md

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# Publishing Fishez
2+
3+
How a new Fishez version gets out to users. One source of truth: bump the
4+
version once, push a tag, and the pieces below ship each channel. There are
5+
three distribution channels:
6+
7+
| Channel | Trigger | Automated? |
8+
|---|---|---|
9+
| GitHub Release (binaries) | pushing `vX.Y.Z` tag | yes — `.github/workflows/release.yml` |
10+
| Homebrew tap | `scripts/update-tap.sh` after the release | script, not CI |
11+
| crates.io | `cargo publish` | manual (not wired into CI) |
12+
13+
## Prerequisites
14+
15+
- `gh` CLI authenticated: `gh auth status`
16+
- `git` push access via SSH (`gh` uses `git@github.com:` remotes)
17+
- crates.io: `cargo login` once, or a `CARGO_REGISTRY_TOKEN` (see [crates.io](#cratesio))
18+
- For local brew verification: Homebrew installed
19+
20+
## 1. Bump version and create the tag
21+
22+
```bash
23+
./scripts/release.sh 0.5.0
24+
```
25+
26+
From a **clean working tree**. It:
27+
28+
1. Bumps `version` in `Cargo.toml`
29+
2. Updates `CHANGELOG.md` (moves `[Unreleased]` into a dated `[0.5.0]` section)
30+
3. Refreshes `Cargo.lock`, commits `Release v0.5.0`, creates annotated tag `v0.5.0`
31+
32+
## 2. Push — this is the trigger for the GitHub release
33+
34+
```bash
35+
git push origin main
36+
git push origin v0.5.0
37+
```
38+
39+
Pushing the `v*` tag triggers `.github/workflows/release.yml`. That workflow
40+
builds `cargo build --release` on 4 runners and uploads these assets to the
41+
GitHub Release:
42+
43+
```text
44+
fishez-macos-aarch64
45+
fishez-macos-x86_64
46+
fishez-linux-aarch64
47+
fishez-linux-x86_64
48+
```
49+
50+
**Do not publish any other channel before this finishes** — the Homebrew and
51+
crates.io steps need the tag, and the tap script verifies shas against the
52+
release.
53+
54+
Verify the run:
55+
56+
```bash
57+
gh run list --repo ioma8/fishez --workflow release.yml
58+
gh release view v0.5.0 --repo ioma8/fishez --json assets
59+
```
60+
61+
Check the run's `headSha` matches the tag commit — that is the proof the
62+
binaries were built from the released code (local builds are not
63+
bit-reproducible, so don't compare hashes to a local build).
64+
65+
## 3. Homebrew tap
66+
67+
The tap `ioma8/homebrew-tap` (`brew install ioma8/tap/fishez`) ships the
68+
release binaries. Update it with the one-command script:
69+
70+
```bash
71+
./scripts/update-tap.sh 0.5.0 # version arg optional, defaults to 0.4.0
72+
```
73+
74+
The script clones the tap via `gh`, then:
75+
76+
1. Fetches the authoritative sha256 digests for all 4 assets from the
77+
**GitHub API** (`gh release view --json assets`) — never from curl on the
78+
download URLs, which raced CDN caching / in-flight uploads and once served
79+
stale v0.3.1 bytes (see Troubleshooting).
80+
2. Bumps `version` and all four `sha256` lines in `Formula/fishez.rb`
81+
(platform order: mac-arm, mac-intel, linux-arm, linux-intel).
82+
3. **Self-verifies** the formula shas equal the API digests before pushing —
83+
it refuses to push on mismatch.
84+
4. Commits `Update fishez to 0.5.0` and pushes to `ioma8/homebrew-tap`.
85+
86+
Verify from the remote:
87+
88+
```bash
89+
gh api repos/ioma8/homebrew-tap/contents/Formula/fishez.rb --jq '.content' \
90+
| base64 -d | grep -E 'version "|sha256'
91+
```
92+
93+
### Local machines with a path-based tap
94+
95+
If the tap was installed from a local directory
96+
(`brew tap ioma8/tap ~/projects/customs/homebrew-tap`), brew's clone at
97+
`/opt/homebrew/Library/Taps/ioma8/homebrew-tap` has that local path as its
98+
origin and goes stale. Refresh it:
99+
100+
```bash
101+
cd /opt/homebrew/Library/Taps/ioma8/homebrew-tap && git pull
102+
brew info ioma8/tap/fishez # should show the new version
103+
```
104+
105+
Other users who do a plain `brew tap ioma8/tap` get the GitHub remote and are
106+
always current.
107+
108+
## 4. crates.io
109+
110+
Publishing to crates.io is **manual** — nothing in CI does it yet.
111+
112+
### One-time setup
113+
114+
```bash
115+
cargo login # paste token from https://crates.io/settings/tokens
116+
```
117+
118+
Or export the token for CI use (see below).
119+
120+
### Publish
121+
122+
```bash
123+
cargo publish --dry-run # sanity check, run from a clean tree
124+
cargo publish
125+
```
126+
127+
The crate metadata in `Cargo.toml` (`description`, `license`, `repository`,
128+
`readme`, `keywords`, `categories`, `exclude`) is already set up; the
129+
`exclude` list keeps `demo.gif`, `docs/`, `scripts/`, `tests/` etc. out of the
130+
published crate.
131+
132+
### Optional: automate it in CI
133+
134+
Add a publish job to `.github/workflows/release.yml` (after `build`):
135+
136+
```yaml
137+
publish:
138+
runs-on: ubuntu-24.04
139+
needs: build
140+
steps:
141+
- uses: actions/checkout@v4
142+
- uses: dtolnay/rust-toolchain@stable
143+
- run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
144+
```
145+
146+
and add the `CARGO_REGISTRY_TOKEN` secret in repo Settings → Secrets and
147+
variables → Actions. Until then, publish locally after each release.
148+
149+
## 5. Verify everything
150+
151+
| Channel | Command | Expect |
152+
|---|---|---|
153+
| GitHub | `gh release view v0.5.0` | 4 assets, run on tag commit |
154+
| Homebrew | `brew install ioma8/tap/fishez` | installs 0.5.0 |
155+
| Homebrew | `fishez --init \| grep 'fz()'` | prints `fz()` wrapper |
156+
| crates.io | `cargo info fishez` | version 0.5.0 |
157+
| crates.io | `CARGO_HOME="$(mktemp -d)" cargo install fishez` | builds from registry, no git deps |
158+
159+
Run the Homebrew check on a machine without Rust to prove the binary (not a
160+
build) is what ships.
161+
162+
## Release checklist (new version, end to end)
163+
164+
```bash
165+
# 1. version + tag (clean tree required)
166+
./scripts/release.sh 0.5.0
167+
168+
# 2. trigger GitHub release
169+
git push origin main
170+
git push origin v0.5.0
171+
172+
# 3. wait for release.yml to finish, confirm assets + tag commit
173+
gh run watch --repo ioma8/fishez
174+
gh release view v0.5.0 --repo ioma8/fishez --json assets
175+
176+
# 4. homebrew tap
177+
./scripts/update-tap.sh 0.5.0
178+
179+
# 5. crates.io (manual)
180+
cargo publish
181+
182+
# 6. verify (section above), including a brew install smoke test
183+
```
184+
185+
## Troubleshooting
186+
187+
- **Tag pushed but no release**: check `gh run list --workflow release.yml`;
188+
the workflow only fires on tags matching `v*`. Confirm the tag is on the
189+
remote: `git ls-remote --tags origin`.
190+
- **Tap script: "formula shas do not match the release"**: do not force it —
191+
the release binaries changed or the API digests are what the formula must
192+
equal. Re-run after the release workflow finishes.
193+
- **Stale shas in the formula**: historical issue — the old script fetched
194+
hashes with `curl` from download URLs while the release was still
195+
uploading, and served v0.3.1 bytes for v0.4.0. The current script uses the
196+
GitHub API only, so it cannot regress this way.
197+
- **`brew info` shows the old version on this machine**: path-based tap is
198+
stale — `git pull` in `/opt/homebrew/Library/Taps/ioma8/homebrew-tap`
199+
(see above).
200+
- **crates.io rejects the package**: run `cargo publish --dry-run` and read
201+
the errors; usually a missing `repository`/`license` field or a bad
202+
`exclude` — all already configured for fishez.
203+
204+
## Rollback
205+
206+
GitHub: delete the release (`gh release delete v0.5.0`) and the tag
207+
(`git push origin :refs/tags/v0.5.0`). Homebrew: point the formula back at the
208+
previous version's shas (re-run `update-tap.sh` with the old version — it
209+
reads whatever the release API serves). crates.io: versions are immutable —
210+
you can `cargo yank` a bad version instead.
211+
212+
## Related docs
213+
214+
- `docs/DISTRIBUTION.md` — `jpgfromraw-lib` publishing and clean-machine verification
215+
- `docs/RELEASE_GUIDE.md` — legacy GitLab CI-era guide, superseded by this file
216+
- `.github/workflows/release.yml` — the tag-triggered binary release
217+
- `scripts/release.sh`, `scripts/update-tap.sh` — the two release scripts

0 commit comments

Comments
 (0)