docs: release runbook — build, verify, publish on GitHub

- docs/guides/releasing.md: the steps from `just dist` to a working
  download button
- pins the constraint that matters: the asset filename and tag must
  match the URL /install links, or the button 404s
- includes verifying the tarball with the binaries INSIDE it, tagging
  the built commit, `gh release create` with both files, the web-UI
  path, and a curl check of the exact link the site uses
- notes dist/ is gitignored, the shoneyJ/shoneyj path-case difference,
  and what a version bump must touch in install/view.wo

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
shoney.arickathil 2026-08-25 04:51:14 +02:00
parent ef37b8ffa2
commit b11f964eec

163
docs/guides/releasing.md Normal file
View file

@ -0,0 +1,163 @@
# Cutting a release — building the tarball and publishing it on GitHub
The `/install` page on writeonce.de links a GitHub release asset by an
exact URL. Publishing is therefore not "upload a file somewhere": the
**asset filename has to match what the site links**, or the download
button 404s. This runbook keeps the two in step.
The URL the site links today:
```
https://github.com/shoneyj/writeonce/releases/download/v0.1.0/writeonce-0.1.0-linux-amd64.tar.gz
```
which decomposes as `<repo>/releases/download/<tag>/<asset-name>`. So the
tag must be `v0.1.0` and the asset must be named exactly
`writeonce-0.1.0-linux-amd64.tar.gz` — which is what `just dist` already
produces.
## 0. Before you start
`gh` must be authenticated. It is an interactive browser/device flow, so
run it yourself:
```
gh auth login
```
In a Claude Code session, type it with a leading `!` so the output lands
in the conversation: `! gh auth login`.
Check the tree is clean and the version is what you mean to ship —
`VERSION` is the single source, and `mkdist.sh` refuses to build if
`woc version` or `wovm --version` disagree with it:
```
git status --porcelain # expect empty
cat VERSION # e.g. 0.1.0
```
## 1. Build the artifact
```
just dist
```
That builds both release binaries, runs the version drift guard, and
writes two files:
```
dist/writeonce-<ver>-linux-amd64.tar.gz
dist/writeonce-<ver>-linux-amd64.tar.gz.sha256
```
## 2. Verify it before anyone else can
Two checks, both cheap, both worth it. First the digest:
```
cd dist && sha256sum -c writeonce-0.1.0-linux-amd64.tar.gz.sha256 && cd ..
```
Then prove the tarball actually works, using the binaries INSIDE it —
not the ones in your build tree:
```
tmp=$(mktemp -d)
tar -C "$tmp" -xzf dist/writeonce-0.1.0-linux-amd64.tar.gz
export PATH="$tmp/writeonce/bin:$PATH"
woc version && wovm --version
mkdir -p "$tmp/hello" && cd "$tmp/hello"
printf 'name = "hello"\nversion = "0.1.0"\n\n[runtime]\nwo = ">= 0.1"\n' > wo.toml
printf 'fn main() -> Int {\n print("hello, writeonce");\n return 0;\n}\n' > main.wo
woc . && ./target/hello
```
If that prints `hello, writeonce`, the release is sound.
## 3. Tag the commit
The tag is what the download URL points at, so tag the exact commit the
binaries were built from:
```
git tag -a v0.1.0 -m "writeonce 0.1.0"
git push origin v0.1.0
```
## 4. Publish and upload
One command creates the release and attaches both files:
```
gh release create v0.1.0 \
dist/writeonce-0.1.0-linux-amd64.tar.gz \
dist/writeonce-0.1.0-linux-amd64.tar.gz.sha256 \
--title "writeonce 0.1.0" \
--notes-file - <<'EOF'
Linux x86-64, glibc 2.38 or newer. Two binaries — `woc` and `wovm` —
depending only on the system C library.
rm -rf /usr/local/writeonce
tar -C /usr/local -xzf writeonce-0.1.0-linux-amd64.tar.gz
export PATH=$PATH:/usr/local/writeonce/bin
Verify with the published `.sha256` before extracting.
EOF
```
Add `--draft` to stage it without publishing, or `--prerelease` to mark
it as one. If the release already exists and you only need to attach (or
replace) files:
```
gh release upload v0.1.0 dist/writeonce-0.1.0-linux-amd64.tar.gz --clobber
```
Without `gh`, the same thing through the web UI: the repo's **Releases**
page → *Draft a new release* → choose tag `v0.1.0` → drag both files
into the attachment box → *Publish release*. Uploading by hand is where
the filename usually drifts, so paste it rather than retyping it.
## 5. Check the link the site actually uses
```
curl -sIL -o /dev/null -w '%{http_code} %{url_effective}\n' \
https://github.com/shoneyj/writeonce/releases/download/v0.1.0/writeonce-0.1.0-linux-amd64.tar.gz
```
A `200` means `/install`'s download button works for a stranger. Anything
else means the tag, the asset name, or the repo path disagrees with the
link in `docs/examples/site/install/view.wo`.
## 6. Refresh the site's mirror
`/install` offers this site as a mirror beside the GitHub link, served by
the framework's `StaticFiles` out of `$WO_DIST` (default `./dist`). Copy
the same two files there so the mirror is not stale:
```
scp dist/writeonce-0.1.0-linux-amd64.tar.gz* user@host:/srv/writeonce/dist/
```
Both copies are the same bytes, and the published `.sha256` covers
either one — that is the point of shipping the digest beside the
archive.
## Notes
- **`dist/` is gitignored** (`.gitignore:103`). The tarball is a build
artifact; the release is where it lives, not the repository.
- **Repo path case.** The git remote is `shoneyJ/writeonce` while the
site links `shoneyj/writeonce`. GitHub paths are case-insensitive and
redirect, so both resolve — but keep them consistent when either
changes.
- **Bumping the version** means editing `VERSION`, rebuilding, and
updating every place the site names the current release:
`docs/examples/site/install/view.wo` (the download links, the tar
command, and the `.sha256` link). The version appears there as literal
text, so grep for the old number before you publish.
- **One target today.** `mkdist.sh` builds `linux-amd64` only; a cross
matrix is future work. Do not add architectures to the release notes
that no build produces.