build(deps): make goreleaser own the release changelog with prefix-grouped sections#118
build(deps): make goreleaser own the release changelog with prefix-grouped sections#118
Conversation
There was a problem hiding this comment.
Code Review
This pull request automates the release process by configuring GoReleaser to generate release notes from git commit messages and updating the CHANGELOG.md to point to GitHub Releases as the source of truth. Feedback includes resolving a contradiction between excluded commit prefixes and the documentation, broadening the regex for dependency updates, and ensuring consistency in regex patterns for scoped DevOps commits.
Match the pattern already used by the feat/fix/refactor groups so a hypothetical 'devops(ci):' style commit is grouped under DevOps instead of falling through to Other Changes. Caught by Gemini review on #118.
…ouped sections
Releases v1.1.4 through v1.1.15 all carried GitHub's default
"Generate release notes" body (an ungrouped, prefix-blind PR list)
because goreleaser's `release` block defaulted to `mode: keep-existing`,
leaving whatever the human pasted intact. CHANGELOG.md was last touched
at v1.1.3 and went stale across 13 subsequent releases.
This wires goreleaser to actually own the release body going forward:
- `release.mode: replace` — goreleaser overwrites the GitHub
--generate-notes content with its own grouped changelog when the
`release: published` workflow fires. Future releases stop carrying
the ungrouped body.
- `changelog.groups` — six sections keyed off the same commit-prefix
convention enforced by `highflame-commit-check` (Features / Bug Fixes /
Dependencies / DevOps / Refactoring / Other Changes). YAML list order
is significant for goreleaser: it's first-match-wins, with the `order`
field controlling display order only. No "Security" group: it would
need to sit in front of "Bug Fixes" in YAML to claim `fix(security):`
commits, and the marginal taxonomy gain wasn't worth the regex risk —
security fixes land under Bug Fixes, which is semantically correct.
- `filters.exclude` extended with `^style:`, `^ci:`, `^Revert ` —
matches the same drop-list the existing `^docs:|^test:|^chore:` was
already using.
CHANGELOG.md is rewritten to its v1.1.3 historical floor with a header
pointing at GitHub Releases as the source of truth. Pre-v1.1.4 entries
preserved in case anyone wants to read them; nothing back-fills the
v1.1.4 → v1.1.16 gap because goreleaser is now responsible for current
releases.
Validated locally with `goreleaser check` (v2 image) — config is valid.
Regex grouping was sanity-checked against `git log v1.1.15..main`: every
prefix-tagged commit lands in the right bucket; four pre-prefix-gate
commits fall through to "Other Changes" as intended.
## Test plan
- [x] `goreleaser check` — green
- [x] Regex dry-run against actual commit log — every prefixed commit
grouped correctly, untaggedcommits land in Other Changes
- [ ] Verify on the next release: cut a tag, watch `release.yml` run,
confirm goreleaser rewrites the body with grouped sections
Pairs with the goreleaser changelog work in the previous commit on this
branch: that commit makes release notes deterministic per commit prefix;
this one makes the version number deterministic from the same prefixes.
## What it does
`svu next` (caarlos0/svu) reads commits since the last `v*` tag and
prints the implied semver bump using Conventional Commits rules:
feat: minor
fix: / build(deps) / devops: patch (the latter two via "no rule
matched -> patch")
feat!: / BREAKING CHANGE: major
The release workflow now runs `svu next` after the tag-format check
and compares the published tag to svu's recommendation:
- tag == svu next -> pass (the common case)
- tag > svu next -> pass with ::notice:: (manual bump up; e.g.
force a minor on a fix-only window)
- tag < svu next -> ::error:: + exit 1. Almost always means a
feat: was missed and we'd silently ship a
feature as a patch.
The Makefile adds `make next-version` so the same suggestion is
available locally before someone clicks "Publish release".
## Bug fix bundled in
The old `Validate Release Tag` step was guarded by
`if: ${{ steps.validate_branch.outputs.enable_branch_build == 'true' }}`,
referencing a `validate_branch` step that never existed in this job.
The condition evaluated to false on every release, so the regex format
check has been silently skipping for as long as it's been there. Removed
the dead conditional so it (and the new svu step) actually run.
## Why this PR and not separate
The user (Highflame engineer) asked for the svu guardrail as part of
"making releases more robust", which is exactly what the existing
commit on this branch does for the changelog. Bundling keeps the
review surface small and the rationale on one PR.
## Verification
- `svu next` against current main reports `v1.1.17` (current orphan tag
v1.1.16, no feat: commits since; patch bump). That matches the next
expected release.
- Comparison-via-sort -V tested in shell against three cases (match,
manual bump-up, missed feat:); all branches behave as documented.
- The new step in release.yml needs `actions/setup-go@v6` because the
validate job didn't previously install Go; added.
Match the pattern already used by the feat/fix/refactor groups so a hypothetical 'devops(ci):' style commit is grouped under DevOps instead of falling through to Other Changes. Caught by Gemini review on #118.
d1298ef to
7420f44
Compare
saucam
left a comment
There was a problem hiding this comment.
Approving — fix matches the diagnosis cleanly. release.mode: replace is the exact flip needed to make goreleaser own the body instead of leaving GitHub's seeded default in place, and the prefix groups mirror the highflame-commit-check gate so CI and release notes will stay in sync going forward.
Gemini rejections look sound:
- The CHANGELOG "contradiction" was a misread — those lines document the CI commit-prefix allow-list, not the changelog visibility policy.
Merge/Revertbeing CI-allowed but goreleaser-excluded is intentional. Bumptrailing space is correct anchoring againstBumped/Bumpingfalse positives; real dependabot subjects on this repo usebuild(deps):which the same group already covers.
The applied ^devops(\(.*\))?: consistency tweak is a nice catch.
Failure mode is benign — if replace somehow doesn't take effect on the next release, worst case is "body still looks ungrouped," which is the current state. Zero regression risk.
Summary
Releases v1.1.4 → v1.1.15 all shipped with GitHub's default "Generate release notes" body (ungrouped PR list, blind to our
feat:/fix:/build(deps)/devops:prefix convention) because goreleaser'sreleaseblock defaulted tomode: keep-existing.CHANGELOG.mdfroze at v1.1.3 and stayed stale across 13 subsequent releases.This PR wires goreleaser to actually own the release body going forward, and resets
CHANGELOG.mdto a brief pointer at GitHub Releases.Changes
.goreleaser.ymlrelease.mode: replacerelease: published— a human creates the release first (UI button orgh release create), then goreleaser runs. Withkeep-existing(default), goreleaser leaves the human's body alone, which is how we ended up with 12 ungrouped releases.replacemakes goreleaser overwrite the body with its grouped changelog every time.changelog.groupshighflame-commit-checkenforces: Features, Bug Fixes, Dependencies, DevOps, Refactoring, Other Changes. YAML list order is significant — goreleaser is first-match-wins;orderfield is display order only.filters.exclude^style:,^ci:,^Revertto match the existing `^docs:No "Security" group: to claim
fix(security):commits it would need to sit in front ofBug Fixesin YAML, and the marginal taxonomy gain wasn't worth the regex risk. Security fixes land under Bug Fixes, which is semantically correct.CHANGELOG.mdTrimmed to a one-screen header pointing at GitHub Releases as the source of truth, with the v1.0.0 → v1.1.3 entries preserved for context. Nothing back-fills the v1.1.4 → v1.1.16 gap — goreleaser is now responsible for current versions.
Validation
Regex grouping sanity-checked against
git log v1.1.15..main— 18 commits:feat: zeroid cli, first commit (#35)fix(security): scope API key get/revoke …,fix: validate SPIFFE path segments …,fix: set typ=JWT …build(deps): upgrade lestrrat-go/jwx v2 → v4 + Go 1.26 (#114),build(deps): bump pgdriver, mapstructure, … (#115)devops: Pushing the V2 changes of workflow (#108), …refactor: go fix ./... — modernize to Go 1.25 idioms (#116)Add identity audit logging (#72),Issue16 (#78),deactivation must stop token use (#90),replace attestation trust-promotion stub …(#93)— pre-prefix-gate commits, fall through cleanlyci: add golangci-lint job (#83)Verification on next release
Once this lands, the next release (likely v1.1.17 after #115 + #117 merge — the orphan v1.1.16 tag has no published release) will exercise the new flow:
gh release create v1.1.17 --generate-notes— GitHub seeds the body with its default content.release: publishedevent fires,release.ymlruns.pkg/authjwt/v1.1.17submodule tag are produced as before.If the next release body still looks ungrouped, the issue is not in this config — it would point at goreleaser's
release.mode: replacefailing to take effect (e.g., GitHub permissions on the goreleaser GitHub App token), and we should investigate that path.Scope
.goreleaser.ymlandCHANGELOG.mdonly.release.ymlkeeps its existingrelease: publishedtrigger.