Release Process¶
Overview¶
mhcgnomes/version.py is the source of truth for package versioning. Releases
use a hybrid flow:
- local release automation runs strict preflight checks, validates the version,
builds the package once locally, and pushes an annotated
v<version>tag - GitHub Actions rebuilds from that tag, verifies the tag still matches
version.py, checks the generated distributions, and publishes to PyPI using trusted publishing
This keeps the existing safety checks from deploy.py while removing local
PyPI credentials and local twine upload.
Detailed Plan¶
- Keep
version.pyas the only version source. - Generate the release tag from
version.pylocally rather than typing it by hand. - Reuse the same Python helper code for local release automation and CI tag/version validation.
- Trigger production publishing from
pushevents on tags matchingv*. - Treat GitHub Actions as the only publisher to PyPI and TestPyPI.
- Document the maintainer checklist so the release process is explicit and reviewable.
Maintainer TODOs¶
These are one-time repository setup tasks outside the codebase:
- [ ] Configure a PyPI trusted publisher for the GitHub Actions workflow
environment named
release - [ ] Configure a TestPyPI trusted publisher for the environment named
release_testpypi - [ ] Decide whether tag pushes matching
v*should be protected or restricted to maintainers - [ ] Decide whether to add release provenance or signing as a later hardening step
Release Checklist¶
- Update
mhcgnomes/version.pyto the new version and commit it onmain. - Run
./deploy.sh --dry-run --fetchto confirm the repository is clean, up-to-date, and ready to release. - Run
./deploy.sh --fetchfrom a clean local checkout ofmain. - Confirm the pushed
v<version>tag starts the.github/workflows/release.ymlworkflow. - Verify the workflow succeeds and the new version appears on PyPI.
- If needed, run
.github/workflows/release_testpypi.ymlmanually against an existing tag before a production release.
What deploy.sh Enforces¶
deploy.sh still runs the full local test and lint gates before calling
deploy.py. deploy.py then enforces:
- current branch is
main - working tree is clean
- local
mainmatchesorigin/main - the release version parses from
mhcgnomes/version.py - the corresponding
v<version>tag does not already exist - a local source and wheel build succeeds before any tag is pushed
Failure Handling¶
- If the local preflight fails, no tag is created.
- If the CI tag/version validation fails, nothing is published.
- If the build or trusted publish step fails, fix the issue on
main, bump the version, and cut a new tag rather than reusing the failed version number.