Releasing¶
Releases are published to PyPI by the release.yml
workflow when a version tag (vX.Y.Z) is pushed. Publishing uses PyPI Trusted
Publishing (OIDC) — there is no stored PyPI token.
One-time setup (maintainer, on PyPI + TestPyPI)¶
Trusted Publishers must be configured once per index before the first release. This is a manual account action that cannot be automated by this repo.
On both https://pypi.org and https://test.pypi.org → Your projects → Publishing (or “pending publisher” if the project doesn’t exist yet), add a GitHub Actions publisher:
Field |
Value |
|---|---|
Owner |
|
Repository |
|
Workflow filename |
|
Environment |
|
Optionally, in GitHub → Settings → Environments, create the pypi and testpypi
environments and add required reviewers to gate the real publish.
The package version is static in pyproject.toml and drives the built artifact — the
tag name does not. You must bump the version and tag a matching vX.Y.Z; the release
workflow’s verify job fails fast if the tag and the pyproject.toml version disagree,
so a forgotten bump can’t produce a wrong or duplicate upload. __version__ is read from
the installed package metadata, so it stays in sync automatically — only pyproject.toml
needs bumping.
Cutting a release¶
With the just recipes (recommended)¶
The release mechanics — with pre-flight guards — are wrapped in just recipes:
just release-status # current version, last tag, commits since, recent CI
just release-check 0.3.0 # validate the target is a single semver increment
just release-prep 0.3.0 # open the version-bump PR (branch → bump → lock → push → PR)
# ... review + merge that PR (branch protection + required checks apply) ...
git switch main && git pull
just release 0.3.0 # tag main + push the tag (never a commit to main)
release-prep never pushes to main directly; the bump always lands via a PR.
release refuses to run unless you are on an up-to-date main, pyproject.toml
already matches the target version, the tag does not yet exist, and just check passes.
If a tag push goes wrong, undo it with just rollback-release 0.3.0.
Manually¶
Bump
versioninpyproject.toml(e.g.0.1.0→0.2.0).Commit via a PR and merge (branch protection + required checks apply).
Tag and push: .. code:: bash
git checkout main && git pull git tag v0.2.0 git push origin v0.2.0
The workflow then:
Builds sdist + wheel with
uv build.Publishes to TestPyPI first (
skip-existing: true) as a dry run.Publishes to PyPI.
Creates a GitHub Release for the tag with auto-generated notes and the artifacts attached.
Verify¶
uv tool install yt-issue-reviewer==0.2.0
yt-issue-reviewer --help
Read the Docs versions¶
Read the Docs builds two versions:
latesttracks themainbranch.stabletracks the newest release tag.
A release tag must therefore contain the docs build (.readthedocs.yaml and
docs/conf.py); every tag cut from main does, since that setup lives on main.
A tag created before the docs build was added (e.g. an early v0.1.0) has no
docs/conf.py and will fail the stable build — in that case either cut a newer tag
that includes the docs, or deactivate/hide the old stable version in the Read the Docs
dashboard and set the default version to latest.
Notes¶
Version is static (manual bump + matching tag). Dynamic-version-from-tag (hatch-vcs / uv dynamic versioning) is a possible future enhancement.
The release workflow is least-privilege and hash-pinned, and is covered by the required
zizmoraudit like every other workflow.