Skip to content

Release Workflow

Goal

This page describes how to prepare and publish a CANarchy release, with TestPyPI recommended before the first real PyPI publication and for any release workflow changes.

Prerequisites

Before publishing, confirm:

  • package metadata in pyproject.toml is complete and correct
  • src/canarchy/__init__.py contains the intended release version
  • src/homepage/index.html advertises that same version in its hero issue tag and install shell tag
  • CHANGELOG.md has an Unreleased section and the upcoming release is summarized clearly
  • README.md renders correctly as the long project description

Publishing credentials:

  • preferred: PyPI trusted publishing via GitHub Actions OIDC
  • fallback: a scoped PyPI API token stored as a GitHub Actions secret

For the first public release, use TestPyPI first.

  1. Update version metadata in src/canarchy/__init__.py.
  2. Update the release version and issue tag in the hero and install sections of src/homepage/index.html (see docs site). The issue tag drops the leading 0. and zero-pads the minor to two digits, so 0.10.0 is ISSUE 10.0. tests/test_homepage.py enforces both labels against __version__.
  3. Move relevant entries from CHANGELOG.md Unreleased into a new versioned release section.
  4. Commit the release preparation changes.
  5. Tag the release with vX.Y.Z.
  6. Build and verify artifacts locally from the release version.
  7. Publish to TestPyPI first if this is the first release or if the release workflow changed.
  8. Publish to PyPI from the release tag vX.Y.Z — not from main, which carries the next .devN version once development reopens. The workflow refuses any other ref.
  9. Create the GitHub release notes from the full versioned changelog section for X.Y.Z; do not summarize or shorten the release notes.
  10. After the release is cut and the release artifacts are published, advance src/canarchy/__init__.py on main to the next development version in a follow-up commit.

Development Version Naming

After cutting release X.Y.Z, main shall advance to X.Y.(Z+1).dev0 in the follow-up commit that reopens development after the release artifacts are built and published, unless a different next-version target is explicitly planned and documented before or as part of the release.

Examples:

  • after 0.4.0, set src/canarchy/__init__.py to 0.4.1.dev0
  • after 1.2.3, set src/canarchy/__init__.py to 1.2.4.dev0

Rules:

  • keep release tags on the stable version only, such as v0.4.0
  • keep unreleased work on main on a .devN version string
  • build and publish release artifacts from the stable release version, not from the post-release .dev0 version
  • update the version metadata in the same commit or immediate follow-up commit that reopens development on main after the release artifacts are published
  • if the next planned release target is a minor or major bump instead of the next patch version, document that decision explicitly and set the .dev0 version accordingly

Local Verification

Build artifacts:

uv build

Check package metadata rendering:

uvx twine check dist/*

Verify the wheel installs and the entry point works:

tmpdir=$(mktemp -d)
uv venv "$tmpdir/.venv"
uv pip install --python "$tmpdir/.venv/bin/python" dist/*.whl
"$tmpdir/.venv/bin/canarchy" --version

Recommended additional checks:

uv run pytest
uv run --group docs mkdocs build --strict

TestPyPI First-Publish Flow

Recommended for the first public release:

  1. Publish the built artifacts to TestPyPI.
  2. Create a clean virtual environment.
  3. Install from TestPyPI.
  4. Verify canarchy --version and a small representative command such as canarchy config show --json.
  5. Only then publish the same release to PyPI.

GitHub Actions Publish Workflow

The repository includes a manual GitHub Actions workflow at .github/workflows/publish.yml.

How to use it:

  1. Open the publish workflow in GitHub Actions.
  2. Under Use workflow from, switch to the Tags tab and select the release tag vX.Y.Z. The control defaults to the main branch, which is the wrong ref for a release.
  3. Run it with repository=testpypi for a dry run when one is warranted.
  4. Verify the published package from TestPyPI.
  5. Run it again from the same tag with repository=pypi for the real publication.

From the command line, the equivalent real publication is:

gh workflow run publish.yml --ref vX.Y.Z -f repository=pypi

Workflow behavior:

  • when publishing to pypi, refuses to build unless the run was dispatched from a tag, the tag is exactly v plus __version__, and that version is a final X.Y.Z release with no .dev, pre-release, post-release or local segment (scripts/check_release_ref.py, tested in tests/test_release_ref.py)
  • does not restrict testpypi, which stays available for rehearsing workflow changes from any ref
  • builds sdist and wheel artifacts with uv build
  • runs twine check before any upload
  • publishes to separate GitHub environments for testpypi and pypi
  • is structured for PyPI trusted publishing via GitHub OIDC

Trusted Publishing

Preferred long-term setup:

  • configure a PyPI project for CANarchy
  • enable trusted publishing from the GitHub repository
  • restrict publication to the intended release workflow and branch/tag conditions

If trusted publishing is not ready yet, use an API token with the narrowest possible scope and store it only in GitHub Actions secrets.

Notes

  • Use the distribution and launch packet for ecosystem listing copy, launch announcements, and search-engine submission instructions after publication.
  • Release tags should match the package version exactly, prefixed with v.
  • The ref check exists because dispatching publish from main while cutting 0.10.0 uploaded 0.10.1.dev0 to PyPI instead (#559). PyPI never accepts a version twice, so a wrong upload cannot be replaced, only yanked.
  • GitHub release notes should use the complete text from the matching CHANGELOG.md version section verbatim.
  • If the release introduces breaking CLI or output-contract changes, bump the major version according to the documented SemVer policy.
  • If publication metadata or workflow changes, repeat the TestPyPI path before the next real PyPI release.