Port the GitHub CI gates (gofmt, go vet, golangci-lint, race tests with the coverage profile, the 100% coverage gate, go build) to the shared ci-workflows go.yaml. v* tags build the GoReleaser-equivalent archives with scripts/build-release.sh and attach them with SHA256SUMS to the Gitea release. The GitHub workflows ran on ubuntu-22.04/macos/windows runners that Gitea does not serve; removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7.8 KiB
Releases
This is the full, end-to-end runbook for cutting a license-tool release. It expands
on RELEASING.md (which is the short reference) with the complete
process: integrating feature work, choosing a version, running the gate, tagging, and
verifying the published artifacts.
Releases are built by scripts/build-release.sh and published to the Gitea release by
the tag build of .gitea/workflows/build.yaml (see RELEASING.md). There is no version constant in source — the
version is injected at build time via -ldflags (main.version, main.commit,
main.date) when the workflow runs against a pushed tag.
1. Branch model
Gitflow-style:
feature/*— active work; each opens a PR intodevelop.develop— the integration branch and the repository default branch. Merged feature work lands here. Issues withCloses #Nclose when their PR merges intodevelop.main— production. Only release-ready commits land here.- Tags
vX.Y.Zonmaintrigger the published release.
feature/GH-XX ──PR──▶ develop ──merge──▶ main ──tag vX.Y.Z──▶ Release workflow
2. Versioning (SemVer, pre-1.0 aware)
Pick the next version from the nature of the [Unreleased] changelog entries:
| Change set | Bump | Example |
|---|---|---|
| New features (backward-compatible) | minor | 0.3.0 → 0.4.0 |
| Only bug fixes | patch | 0.4.0 → 0.4.1 |
| Breaking changes | minor while < 1.0 (major once ≥ 1.0) | 0.4.0 → 0.5.0 |
While 0.x, a minor bump may legitimately include a breaking change (e.g. removing a
flag); call it out under ### Removed/### Changed and in the PR. Record the decision
in the changelog so the bump is self-documenting.
3. Integrate feature PRs into develop
Most releases bundle several feature PRs. When multiple PRs are open they will conflict
(at minimum on CHANGELOG.md, often in shared files like cmd/.../commands.go), so
integrate them locally in one pass and validate the combined result — a set of
individually-green PRs can still break when merged together.
git fetch origin
git checkout develop && git reset --hard origin/develop
# Merge each feature branch with a merge commit (so GitHub marks the PR "Merged"
# and the linked issue closes on the merge into develop).
for b in feature/GH-AAA feature/GH-BBB ... ; do
git merge --no-ff "$b" -m "Merge $b (#N): <summary>"
# Resolve conflicts:
# - CHANGELOG.md / SESSION-STATE.md: take one side now (git checkout --ours <f>);
# you will rewrite the changelog cleanly in step 4.
# - code/test files: keep the UNION of independent additions; never blindly --ours
# a file that also received auto-merged hunks from the other side, or you will
# drop them. Watch for cross-branch refactors (a symbol renamed on one branch and
# referenced on another) — the build/vet step below catches these.
git add -A && git commit --no-edit
done
Conflict-resolution tips learned in practice:
- A rename on one branch (
repoConfigName→RepoConfigName) plus a new reference on another branch surfaces as anundefined:build error, not a merge conflict — always rungo build ./...after each merge (do not pipe it totail, or you mask the exit code). - A function moved between packages on one branch with a test left behind on another yields an orphaned test referencing a now-undefined symbol — delete the orphan (its coverage moved with the function).
4. Finalize the changelog
Rewrite the [Unreleased] section into a dated version section with every merged
change, grouped Added / Changed / Removed / Fixed, each line referencing its issue:
## [Unreleased]
## [X.Y.Z] - YYYY-MM-DD
### Install
` ``bash
brew install --cask KofTwentyTwo/tap/license-tool
` ``
### Added
- ... (#NN)
### Changed
- ... (#NN)
### Removed
- ... (#NN)
### Fixed
- ... (#NN)
The date must be today's date in YYYY-MM-DD — scripts/verify-release.sh checks
it. Keep an empty ## [Unreleased] header above the new section.
5. Run the pre-release gate
From the repo root, the full local gate (the same checks CI enforces, plus the GoReleaser dry run):
./scripts/verify-release.sh
It runs, in order: gofmt, go vet, golangci-lint, race tests with the CI coverage
profile, the 100% coverage checker, goreleaser check, goreleaser release --snapshot --clean, tap-repo/token existence, the CHANGELOG date check, the SECURITY/README
content checks, and a clean-tree check.
If goreleaser is not installed locally, run the non-GoReleaser gates directly and let
CI run GoReleaser on the tag:
gofmt -l . # expect no output
go vet ./...
go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest run
go test ./... -race -coverpkg=./internal/...,./cmd/... -covermode=atomic -coverprofile=cover.out
go run github.com/vladopajic/go-test-coverage/v2@v2.18.8 --config=.testcoverage.yml
The coverage gate is governed by .testcoverage.yml (the interactive TUI in
cmd/.../main.go and wizard.go is excluded as untestable). The checker prints
Total coverage threshold (100%) satisfied: PASS when the gate is met.
6. Push develop, then promote to main
git push origin develop
# wait for develop CI + CodeQL to go green:
gh run list --branch develop --limit 5
git checkout main && git reset --hard origin/main
git merge --no-ff develop -m "Release vX.Y.Z"
git push origin main
# wait for main CI + CodeQL to go green:
gh run list --branch main --limit 5
Branch protection currently enforces no required checks, so direct pushes succeed; still wait for CI/CodeQL to pass on the release commit before tagging.
7. Tag and publish
From main, with CI green:
git tag -s vX.Y.Z -m "vX.Y.Z" # signed annotated tag preferred
git push origin vX.Y.Z
Pushing the tag triggers the Release workflow, which runs goreleaser release --clean
to: cross-compile macOS/Linux arm64+amd64 and Windows amd64, build archives +
checksums.txt, create the GitHub Release, and publish the Homebrew cask to
KofTwentyTwo/homebrew-tap.
A tag with a prerelease suffix (vX.Y.Z-rc.N) is published as a GitHub prerelease
(release.prerelease: auto); use these from a release/* or rc/* branch to stabilize.
8. Verify the published release
gh run watch $(gh run list --workflow Release --limit 1 --json databaseId -q '.[0].databaseId')
gh release view vX.Y.Z # assets + checksums present
git -C "$(brew --repository KofTwentyTwo/homebrew-tap)" log --oneline -3 # cask bumped
brew update && brew install --cask KofTwentyTwo/tap/license-tool && license-tool version
license-tool version should print the new vX.Y.Z, the release commit, and the build
date injected by the workflow.
9. Required GitHub setup (one-time)
- Public Homebrew tap repo:
KofTwentyTwo/homebrew-tap. - Actions secret on
KofTwentyTwo/license-toolnamedHOMEBREW_TAP_TOKEN(a token that can push to the tap). gh auth statusmust show write access before tagging.
10. If a release goes wrong
- Workflow failed before publishing: fix forward on
main, then move the tag to the new commit and re-push:(Only safe before anyone has installed the release.)git tag -fas vX.Y.Z -m "vX.Y.Z" git push --force origin vX.Y.Z - Bad release already published: do not reuse the version. Ship a
vX.Y.(Z+1)patch with the fix; optionally mark the bad GitHub Release as a prerelease or delete it withgh release delete vX.Y.Z. The Homebrew cask should be rolled forward by the new tag, not hand-edited. - Changelog date check failed in CI: the
## [X.Y.Z] - YYYY-MM-DDdate must match the release; correct it onmainand re-tag.