How to cut a release after a PR merges to main. Release often: a single
bug fix or small feature is enough to justify a patch release.
Every PR that changes behavior adds one line under ## [Unreleased] in
CHANGELOG.md, linking to the PR. Nothing else is required;
main is always releasable.
- Pick the version bump under semantic versioning:
patchfor fixes,minorfor backward-compatible features,majorfor breaking changes. - On
main, updateversioninpyproject.toml. - In
CHANGELOG.md, rename## [Unreleased]to## [X.Y.Z] - YYYY-MM-DD, add a fresh empty## [Unreleased]above it, and update the link references at the bottom of the file. - Commit as
Release vX.Y.Z, merge tomainthrough a pull request. - On merge, the
Releaseworkflow (.github/workflows/release.yml) runs itstagjob: reads the version frompyproject.toml, tags the merge commitvX.Y.Z, and creates a GitHub release from the matchingCHANGELOG.mdsection, using theRELEASE_TOKENsecret (see one-time setup below) so the release is attributed to the repository owner rather thangithub-actions[bot]. Itspublishjob then builds the package withuv buildand publishes it to PyPI. Both jobs run in the same workflow deliberately, so the publish job's PyPI trusted-publisher identity is alwaysrelease.yml, whichever job triggered it. - If a publish is ever stuck for a tag that already has a GitHub release
(check the
Releaseworkflow runs in theActionstab), trigger.github/workflows/release.ymlmanually:Actions->Release->Run workflow, entering the tag (e.g.v0.7.0). This runs only thepublishjob, sincetagis skipped for a manualworkflow_dispatchrun.
The tag job authenticates as the repository owner rather than the default
github-actions[bot], so the GitHub release it creates shows as released by
the owner. Create a fine-grained personal access token, scoped to this
repository only, with the Contents: Read and write repository permission
(under github.com/settings/personal-access-tokens),
then add it as a repository secret named RELEASE_TOKEN (Settings ->
Secrets and variables -> Actions -> New repository secret). Rotate it
before expiry; the workflow fails the tag job's gh calls if it lapses.
The release workflow publishes via
PyPI trusted publishing, no API
token stored in the repo. On the heormodel project's PyPI page, under
Publishing, add a trusted publisher for this repository:
- Owner:
pedroliman, repository:heormodel - Workflow file:
release.yml - Environment:
pypi
Every GitHub release also archives to Zenodo with a
DOI, once the one-time setup below is done. No workflow step is needed:
Zenodo's GitHub integration registers its own repository webhook (under
Settings -> Webhooks) that fires on the release: published event
directly, independent of GitHub Actions.
Zenodo reads archive metadata from .zenodo.json; GitHub's
own "Cite this repository" button reads CITATION.cff.
When both files are present, Zenodo uses .zenodo.json and ignores
CITATION.cff. Update .zenodo.json if the title, authors, or keywords
change; there is no version field to keep in sync; each archived record
takes its version from the release tag.
One-time setup, done by the repository owner on zenodo.org (cannot be scripted, requires the owner's GitHub OAuth login):
- Sign in at zenodo.org with the GitHub account that owns this repository, and open the GitHub integration settings.
- Click "Sync now" if
heormodelis not in the repository list yet. - Toggle
heormodelon. This is a one-time step; every release published afterward archives automatically. - After the first archive completes, copy the "concept DOI" badge Zenodo shows for the repository (the one that always resolves to the latest version) into the README's badge row.
CI (.github/workflows/ci.yml) runs ruff, mypy, pytest, and the
doctest suite on every push and PR to main. Do not tag a release on top of
a red main.