This document describes how to cut a new release of b5 and publish it to PyPI.
b5 is developed using b5 itself (see build/Taskfile), so the
release is automated as a task. Every command below has a plain uv equivalent
in case you cannot or do not want to use b5.
- Make sure
mainis green and the changelog is up to date. - Bump the version, commit, tag and push — automated by
b5 release <version>. - Create a GitHub Release from the new tag.
- Publishing to PyPI happens automatically via GitHub Actions.
b5 follows semantic versioning (MAJOR.MINOR.PATCH).
Breaking changes are marked with BREAKING: in the changelog and require a
minor/major bump.
The version number lives in two places that must stay in sync:
pyproject.toml→[project] versionb5/__init__.py→VERSION
The b5 release task updates both automatically. Keeping them in sync manually
is error-prone — a past release shipped a wrong in-code version number
(see 1.3.5 in the changelog).
- A clean checkout of
mainwith write access to theteam23/b5repository. uvinstalled.- Permission to create GitHub Releases (needed to trigger the PyPI publish).
No local PyPI credentials are required: publishing runs in CI via
trusted publishing (OIDC), see
.github/workflows/release.yml.
Ongoing changes are collected under the top # DEV heading in
CHANGELOG.md.
Before releasing, rename that heading to the version you are about to release and
add a fresh # DEV section on top for future work:
# DEV
* work in progress ;-)
# 1.5.3
* Your release notes here.Commit the changelog (or include it in the release commit — either is fine).
Make sure lint and tests pass before tagging:
b5 qa # runs lint + test
# or individually:
b5 lint # uv run ruff check b5/
b5 test # uv run pytest b5/
# without b5:
uv run ruff check b5/
uv run pytest b5/CI runs the same checks on every push and pull request
(lint.yml,
test.yml) across Python 3.10–3.13.
b5 release <version> # e.g. b5 release 1.5.3This task (see task:release in build/Taskfile):
- Sets the version in
pyproject.tomlviauv version <version>(also refreshesuv.lock). - Rewrites
VERSIONinb5/__init__.py. - Commits
pyproject.toml,uv.lockandb5/__init__.pyasrelease: 🔖 v<version>. - Creates the git tag
v<version>. - Pushes the commit and the tag.
Doing it manually without b5:
VERSION=1.5.3
uv version "$VERSION"
sed -i '' "s/^VERSION = .*/VERSION = '${VERSION}'/" b5/__init__.py # macOS/BSD sed
git add pyproject.toml uv.lock b5/__init__.py
git commit -m "release: 🔖 v${VERSION}" --no-verify
git tag "v${VERSION}"
git push && git push --tagsNote: the tag is
v<version>(prefixed withv), while the PyPI/package version is<version>(no prefix).
Publishing to PyPI is triggered by publishing a GitHub Release, not just by pushing the tag.
- Go to https://github.com/team23/b5/releases and draft a new release.
- Choose the
v<version>tag you just pushed. - Use the changelog entry for the release notes.
- Publish the release.
Or with the GitHub CLI:
gh release create "v${VERSION}" --title "v${VERSION}" --notes-file - <<'EOF'
...release notes...
EOFPublishing the GitHub Release triggers the
RELEASE workflow, which runs uv build
followed by uv publish.
- Watch the run under the repository's Actions tab.
- Once green, confirm the new version at https://pypi.org/project/b5/.
If the automated workflow is unavailable, you can build and publish locally.
This requires PyPI credentials/token configured for uv publish:
b5 pypi:release # uv build && uv publish
# or:
uv build
uv publishPrefer the automated GitHub Release flow whenever possible — it avoids handling PyPI credentials locally.
