@@ -50,11 +50,11 @@ default `aot`) through `config/config.exs` and stash the atom as
5050## Cutting a release
5151
5252Consumers verify each NIF tarball against the checksum pinned in
53- ` native_checksums.txt ` , which ships in the hex package. Publishing is two
54- commands (step 4 ): ` mix emily.publish ` regenerates that file from the
55- freshly- built release artifacts, then ` mix hex.publish ` publishes — so
56- there is nothing to update or commit by hand (the file is git-ignored and
57- can't go stale).
53+ ` native_checksums.txt ` , which ships in the hex package. The publish step
54+ itself is two commands (steps 4–5 ): ` mix emily.publish ` regenerates that
55+ file from the built release artifacts, then ` mix hex.publish ` publishes —
56+ so there is nothing to update or commit by hand (the file is git-ignored
57+ and can't go stale).
5858
5959### 1. Land changes on ` main `
6060
@@ -68,8 +68,16 @@ mix publisho patch # or minor / major
6868```
6969
7070Bumps ` @version ` , rolls ` RELEASE.md ` into ` CHANGELOG.md ` under a
71- dated ` ## <v> ` heading, commits ` Version <v> ` , tags bare semver (no
72- ` v ` prefix), and pushes both the commit and the tag.
71+ dated ` ## <v> ` heading, commits ` Version <v> ` , and creates an annotated
72+ bare-semver tag (no ` v ` prefix). It does ** not** push — that's the next
73+ step.
74+
75+ ### 3. Push the commit and the tag
76+
77+ ``` sh
78+ git push # the `Version <v>` commit
79+ git push origin < v> # the tag — this is what fires the release workflow
80+ ```
7381
7482The tag push fires ` .github/workflows/release-nif.yml ` , which fans
7583out ` {variant × target} ` :
@@ -89,9 +97,46 @@ sidecar (informational — consumers verify against the pinned
8997` https://github.com/ausimian/emily/releases/tag/<v> ` — the URL the
9098consumer's ` compile.emily_nif ` step fetches from.
9199
92- ### 3. Verify end-to-end
100+ Once both cells finish, the workflow's ` publish-release ` job flips the
101+ release out of draft ** automatically** (` gh release edit <v> --draft=false ` ),
102+ so its assets become public with no manual step. That job is gated to the
103+ tag-push path, so a manual ` workflow_dispatch ` rebuild leaves the release
104+ untouched — see * Rebuilding without retagging* .
105+
106+ ### 4. Pin the checksums
107+
108+ Once the workflow has finished and the release is public:
93109
94- In a throwaway project:
110+ ``` sh
111+ mix emily.publish # alias for `mix emily.checksums`
112+ ```
113+
114+ Downloads each tarball from the now-public release and records its SHA256
115+ into ` native_checksums.txt ` . It does ** not** trust the ` .sha256 ` sidecars —
116+ it hashes the bytes itself. If the release isn't public yet, the download
117+ 404s and the task aborts before writing anything.
118+
119+ ### 5. Publish to Hex
120+
121+ ``` sh
122+ mix hex.publish # publishes the package (incl. native_checksums.txt) + docs
123+ ```
124+
125+ Packages ` native_checksums.txt ` — so it's covered by Hex's package hash in
126+ the consumer's ` mix.lock ` , a trust root independent of the mutable GitHub
127+ release — and publishes the package + docs.
128+
129+ Steps 4 and 5 are two separate commands on purpose — they can't be folded
130+ into one alias. Mix only loads the Hex archive for the task * named on the
131+ command line* , so a ` hex.publish ` step chained inside an alias (whose CLI
132+ name is ` emily.publish ` ) fails with `** (Mix) The task "hex.publish" could
133+ not be found` . Running ` mix hex.publish` directly is what loads Hex.
134+ ` hex.publish ` keeps its ` :docs ` ` preferred_env ` , so it publishes docs as
135+ well as the package.
136+
137+ ### 6. Verify end-to-end (recommended)
138+
139+ After publishing, in a throwaway project:
95140
96141``` sh
97142mix new /tmp/emily-verify && cd /tmp/emily-verify
@@ -106,39 +151,9 @@ iex -S mix
106151downloads the tarball, verifies, validates entries, extracts. A
107152variant-mismatched consumer (` config :emily, variant: :jit ` ) should
108153download the JIT tarball instead — worth spot-checking both lanes on
109- the first release of a bump. (The pin only ever exists in the
110- * published* package — ` native_checksums.txt ` is git-ignored and
111- generated during ` mix hex.publish ` — so run this end-to-end verify
112- against the published package, i.e. after step 4.)
113-
114- ### 4. Promote the draft and publish
115-
116- Promote the release so its assets are public, then publish:
117-
118- ``` sh
119- gh release edit < v> --repo ausimian/emily --draft=false # assets go public
120- mix emily.publish # regenerate native_checksums.txt
121- mix hex.publish # publish package + docs
122- ```
123-
124- ` mix emily.publish ` runs ` mix emily.checksums ` : it downloads each tarball
125- from the (now-public) release and records its SHA256 into
126- ` native_checksums.txt ` . Then ` mix hex.publish ` packages that file and
127- publishes the package + docs.
128-
129- These are two separate commands on purpose — they can't be folded into one
130- alias. Mix only loads the Hex archive for the task * named on the command
131- line* , so a ` hex.publish ` step chained inside an alias (whose CLI name is
132- ` emily.publish ` ) fails with `** (Mix) The task "hex.publish" could not be
133- found` . Running ` mix hex.publish` directly is what loads Hex. ` hex.publish`
134- keeps its ` :docs ` ` preferred_env ` , so it publishes docs as well as the
135- package.
136-
137- So the consumer verifies downloads against a trust root that lives in the
138- immutable Hex package, not the mutable GitHub release — with no file to
139- maintain and nothing to commit. The file is git-ignored and regenerated on
140- every publish, so it can't go stale. If the draft isn't public yet,
141- ` mix emily.checksums ` 404s and aborts before you publish.
154+ the first release of a bump. The checksum pin only exists in the
155+ * published* package, so this must run against the published package,
156+ i.e. after step 5.
142157
143158### Rebuilding without retagging
144159
0 commit comments