Releasing¶
How to cut a Nyctea release, and how the automation behind it works.
The actual flow¶
Release publishing runs on trusted publishing (OIDC) since PR #18 and #22.
There is no PYPI_API_TOKEN, and there should not be one.
A tag push does not publish anything by itself; a human always reviews a draft first.
- Bump
versioninpyproject.toml. - Commit and merge to
mainvia PR, the same as any other change. - Tag the merge commit and push the tag:
Draft Release(release-notes.yaml) fires on the tag push and creates a draft GitHub Release with generated notes, grouped by PR label according to.github/release.yml. Nothing is published yet.- Review the draft. This is the point to edit the notes if the auto-generated summary needs a human pass, not a step to skip. Click Publish.
- Publishing the release fires
Publish to PyPI(pypi-publish.yaml). It builds the package and uploads it to PyPI through trusted publishing, with no token and no manualuv publish.
There is no maintained CHANGELOG.md. The GitHub Release for each tag is the changelog, the same
way Polars does it. Label PRs correctly (bug, enhancement, documentation, breaking) so
.github/release.yml sorts them into the right section automatically.
scripts/release.sh is superseded, do not use it¶
It predates trusted publishing and calls uv publish dist/* directly with a token, which bypasses the draft-review step entirely.
It's kept for now; delete it once nobody reaches for it out of habit.
Versioning¶
Semantic Versioning applies, pre-1.0:
| Change | Bump |
|---|---|
| Breaking change or major new feature | 0.x.0 to 0.(x+1).0 |
| Bug fix, minor improvement | 0.x.y to 0.x.(y+1) |
At 1.0.0, standard semver applies: major for breaking changes, minor for backward-compatible features, patch for fixes.
Docs-only changes, CI or workflow changes, test-only changes, and ADRs do not bump the version.
Anything under src/nyctea/ that affects behavior, and any dependency update that changes runtime behavior, does.
Breaking changes always get an entry in breaking changes. That page is maintained by hand, unlike the release notes; a one-line changelog entry is not enough context for a migration.
Prerequisites, one-time¶
- A PyPI trusted publisher registered at pypi.org for this repo, workflow
pypi-publish.yaml, environmentrelease. See PyPI's trusted publishing docs if this ever needs re-registering; a repo rename or workflow rename invalidates it. - The
Development Statusclassifier inpyproject.tomlshould track reality:3 - Alphanow,4 - Betaonce the API stabilizes,5 - Production/Stableat 1.0.0.
Troubleshooting¶
"Package already exists" on PyPI. Versions cannot be replaced once published. Bump and retry.
Draft release never appeared.
Confirm the tag matches v* and was pushed to the actual remote, not just created locally: git push origin <tag>.