Skip to content

Release Process

Releases are human-triggered: a maintainer runs cz bump locally, which computes the new version from Conventional Commits, updates CHANGELOG.md, commits, and creates a signed tag. Pushing the tag fires the release.yml workflow, which publishes to PyPI and deploys versioned docs.

One-time infrastructure setup

Complete these steps once before the first v0.1.0 tag.

1. PyPI Trusted Publishing (OIDC)

On pypi.org → Your projects → repo-env → Publishing → Add a new pending publisher (or configure after first upload):

Field Value
PyPI project name repo-env
Owner ditschi
Repository name repo-env
Workflow name release.yml
Environment name pypi

Repeat on test.pypi.org with environment name testpypi.

No long-lived API tokens are stored in GitHub secrets — the workflow uses OIDC (id-token: write).

2. GitHub Environments

In the GitHub repo → Settings → Environments, create:

Environment Purpose Suggested protection
testpypi TestPyPI publish + install smoke test Optional reviewers
pypi Production PyPI publish Required reviewers recommended

Environment names must match release.yml (environment: name: testpypi / pypi).

3. GitHub Pages

Ensure GitHub Pages is enabled for the repo (deploy source: GitHub Actions / gh-pages branch from workflows).

4. Verify locally

nox -s install_dev
nox -s docs
nox -s integration
cz bump --dry-run

Prerequisites (every release)

Install dev extras (includes commitizen and hatch-vcs):

nox -s install_dev

Step-by-step

1. Ensure main is clean and green

git checkout main && git pull
# Verify CI is green before proceeding

2. Dry-run bump (see what would change)

cz bump --dry-run

cz inspects all commits since the last tag and prints the computed next version (patch / minor / major) based on commit types.

3. Run the bump

cz bump

This:

  1. Computes the next SemVer version.
  2. Updates CHANGELOG.md.
  3. Commits with message chore(release): bump version to X.Y.Z.
  4. Creates an annotated git tag vX.Y.Z.

For the first release when history predates Conventional Commits, ensure CHANGELOG.md has a sensible [0.1.0] section before bumping, or use cz bump --increment MINOR after reviewing cz bump --dry-run.

4. Push the commit and tag

git push origin main --follow-tags

5. CI takes over

The release.yml workflow fires on vX.Y.Z tags and:

  1. Builds sdist + wheel with hatch build.
  2. Publishes to TestPyPI and verifies install (uv tool install from TestPyPI).
  3. Publishes to PyPI via OIDC Trusted Publishing.
  4. Creates a GitHub Release with changelog notes for this version.
  5. Deploys versioned docs: mike deploy X.Y.Z stable --update-aliases.

Every release runs TestPyPI first, then PyPI — there is no separate gate flag in the workflow.

6. Verify

uvx repo-env@X.Y.Z --version   # install from PyPI and check

Patch vs minor vs major

Commit type(s) since last tag Resulting bump
fix, docs, refactor, test, build, ci, chore, perf, revert patch (0.0.x)
feat minor (0.x.0)
feat! or BREAKING CHANGE: footer major (x.0.0)

Changelog

CHANGELOG.md at the repo root is auto-generated by cz changelog and updated on every cz bump. It follows the Keep a Changelog format.

To preview the changelog for unreleased commits:

cz changelog --unreleased-version HEAD --dry-run

Versioned docs

mike deploys each release as X.Y.Z and keeps a stable alias pointing to the latest stable release. The version selector dropdown in the docs site shows all deployed versions.

To list deployed versions:

mike list

Hotfix releases

For a hotfix on a previous minor:

git checkout -b hotfix/v1.2.x vX.Y.Z   # branch from the tag
# cherry-pick or apply fix commits
cz bump --increment patch
git push origin hotfix/v1.2.x --follow-tags
# Open a PR from hotfix/v1.2.x -> main for the cherry-pick too