ci: auto-create GitHub Release after each publish#99
Conversation
Adds a `release` job to all five publish workflows (cli, schemas, sdk-typescript, sdk-python, scanner). After a successful publish to npm / PyPI / GHCR, the job runs `gh release create --generate-notes --verify-tag` to create a GitHub Release whose body is auto-built from PR titles since the previous tag. Why: tag-only releases hide the changelog from users browsing the repo and don't trigger watcher notifications. This brings the surface in line with what users expect from an OSS registry product. Implementation notes: - Separate `release` job (vs. an extra step in `publish`) keeps permissions narrow: just `contents: write`, no `id-token`. - The scanner release waits on `publish-docker`, which transitively waits on `publish-pypi`, so PyPI + GHCR + GitHub Release are all consistent. - Backfill of historical tags is intentionally not done — the three releases for this round (schemas-v0.3.0, sdk-typescript-v0.7.0, cli-v0.4.2) were created manually; from the next tag forward, CI handles it.
Addresses QA feedback on PR #99: W1 — Without --notes-start-tag, GitHub auto-detects the "previous tag" by head-SHA date, not by tag prefix. In a monorepo where multiple packages tag on main, the next schemas-v0.3.1 release would diff against (e.g.) cli-v0.4.2 and the body would include unrelated CLI/SDK commits. Fix: derive the previous same-prefix tag explicitly via `git tag -l '<prefix>-v*' --sort=-v:refname` and pass via --notes-start-tag. Falls back to gh's auto-detection when there is no prior same-prefix tag (first release of a new package). Requires fetch-depth: 0 on the checkout step — actions/checkout@v4 defaults to a shallow clone with no tags, which would make `git tag -l` return nothing. S1 — Idempotency: wrap `gh release create` in a `gh release view` guard so re-runs (e.g. after a transient post-publish failure) skip instead of erroring with "release already exists." S3 — Workflow-level concurrency group serializes runs of the same workflow on the same ref, closing a theoretical race if a tag is pushed twice in rapid succession. cancel-in-progress: false because we never want to abort a publish in flight. Declined S2 (Python SDK title style): the parenthetical "(Python SDK)" in `mpak (Python SDK) v$VERSION` does real disambiguation against the npm CLI which is also named `mpak`. Symmetry with the four @nimblebrain/-prefixed titles is less important than clarity at the point of consumption.
|
Addressed QA feedback in W1 — same-prefix notes-start-tag ✅ Applied as suggested. Each workflow's release job now derives S1 — idempotency ✅ Wrapped S3 — concurrency group ✅ Workflow-level S2 — Python SDK title ❌ Declined. The PyPI dist is named Edge cases I checked on the W1 fix:
|
Summary
After PR #94 shipped without a corresponding GitHub Release page (because the project has historically been tag-only), we created the three releases for this round (
schemas-v0.3.0,sdk-typescript-v0.7.0,cli-v0.4.2) by hand. This PR removes the manual step going forward.Each
*-publish.ymlworkflow gets a newreleasejob that runs after the npm/PyPI/GHCR publish completes. It runsgh release create --generate-notes --verify-tag, which builds the release body from PR titles merged since the previous tag.Why
Design choices
releasejob rather than an extra step inpublish. Keeps the publish job's permissions narrow (id-token: writeonly) and the release job's permissions narrow (contents: writeonly). Also makes the release re-runnable from the GH UI if it fails independently of publish.publish-docker, which transitively waits onpublish-pypi, so PyPI + GHCR + GitHub Release are all aligned.schemas-v,sdk-typescript-v,cli-v,sdk-python-v,scanner-v) to derive the version for the release title.Test plan
Files changed
.github/workflows/cli-publish.yml.github/workflows/schemas-publish.yml.github/workflows/sdk-typescript-publish.yml.github/workflows/sdk-python-publish.yml.github/workflows/scanner-publish.yml