Skip to content

Releasing

Releases are cut from main by tagging. .github/workflows/release.yml does the rest: it verifies the tag matches pyproject.toml, runs the full gate, builds the wheel and sdist, attaches both to a GitHub Release, and publishes to PyPI over OIDC.

One-time PyPI setup

Do this before the first tag. The project does not exist on PyPI yet, so it is registered as a pending publisher:

pypi.org/manage/account/publishing/ → "Add a new pending publisher"

Field Value
PyPI Project Name ocx-indexbot
Owner ocx-sh
Repository name indexbot
Workflow name release.yml
Environment name pypi

No API token is minted and nothing is stored in GitHub secrets. The release job authenticates by OIDC from that exact workflow in that exact environment; change any of the four values and publishing stops working, which is the point. The GitHub pypi environment must exist with the matching name.

Cutting a release

task release:prepare                          # interactive: auto | patch | minor | major
task release:prepare BUMP=minor               # skip the menu
task release:prepare BUMP=auto VERSION=1.4.0  # pin an exact version

It computes the next version from the conventional-commit history with git-cliff, bumps pyproject.toml via uv version, regenerates CHANGELOG.md, and runs task verify. Nothing is committed — review the diff, then:

git add -A && git commit -m 'chore(release): vX.Y.Z'
git tag vX.Y.Z
git push --atomic origin main vX.Y.Z

--atomic matters: it pushes the commit and the tag as one operation, so a tag can never arrive on a commit main does not have.

What the tag triggers

  1. Tag ↔ version gate. vX.Y.Z must equal pyproject.toml's version. A mismatch fails before anything is built.
  2. task verify. The same gate every pull request runs.
  3. uv build. Wheel and sdist.
  4. GitHub Release. Both artifacts attached, notes generated.
  5. PyPI. Trusted Publishing, pypi environment.

workflow_dispatch runs steps 1–3 only — a dry run that builds and inspects the artifacts without publishing anything.

CHANGELOG

CHANGELOG.md is generated by git-cliff from the commit history (cliff.toml). Never edit it by hand — the next task changelog would overwrite the edit. Fix the history instead, or fix cliff.toml.

task changelog:preview   # what the next release section would say
task changelog           # regenerate the whole file

Version policy

Pre-1.0. Breaking changes ship without migration shims: no deprecation warnings, no compatibility layers.

That freedom stops at the wire contract. The published URL shapes (/config.json, /p/<ns>/<pkg>.json, /p/<ns>/<pkg>/o/sha256/<hex>.json, /c/index.json) and their field semantics are backward compatible forever once clients bake the endpoint — additive changes only, format_version gates the rest. That contract belongs to ocx, not to this package's version number.

Consumers should pin an exact version anyway: this bot runs in privileged CI jobs, so which version runs there belongs in a committed lockfile where a bump is a reviewed pull request.