This document describes how to cut and publish a new release of
django-codenerix to PyPI.
- Single source of truth for the version:
codenerix/__init__.py(__version__ = "X.Y.Z"). Hatchling reads it at build time via[tool.hatch.version]. - Releases are driven by Git tags + GitHub Releases. Publishing a
GitHub Release triggers
.github/workflows/release.yml, which builds the sdist + wheel and uploads them to PyPI. - No tokens or secrets. Publishing uses PyPI Trusted Publishing (OIDC): the workflow proves its identity to PyPI with a short-lived token that only exists during the run.
The release workflow enforces two guards and refuses to publish if either fails:
- Tag matches
__version__— the tag (vX.Y.Z) must match the version string incodenerix/__init__.py. - Static assets are bundled — the wheel must contain JS/CSS/HTML
assets. This catches build regressions (e.g. the historical
_codenerixsymlink that produced an empty wheel).
Done once per project; you do not repeat this for each release.
-
PyPI Trusted Publisher. On PyPI, go to the project (
django-codenerix) then Settings → Publishing → Add a new publisher, and register:- Owner:
codenerix - Repository:
django-codenerix - Workflow:
release.yml - Environment:
pypi
For a brand-new project that has never been uploaded, create a pending publisher from your PyPI account publishing settings instead.
- Owner:
-
GitHub environment. In GitHub, go to Settings → Environments and create an environment named
pypi. Optionally add required reviewers there to require manual approval before each publish.
Example: releasing 5.0.81.
-
Bump the version. Edit
codenerix/__init__.py:__version__ = "5.0.81"
-
Update the changelog. Add an entry for
5.0.81toCHANGELOG. -
Commit:
git commit -am "Release 5.0.81" -
Tag and push. The tag must be
v+ the exact version:git tag v5.0.81 git push origin master --tags
-
Create the GitHub Release. This is the step that actually triggers the pipeline — pushing the tag alone does not publish. The release workflow listens for the release published event, so the tag just sits there until a Release is published from it.
Option A — CLI (recommended). With the GitHub CLI:
gh release create v5.0.81 --title "5.0.81" --generate-notes--generate-notesauto-builds the notes from commits/PRs since the previous release; use--notes "..."(or both) for custom text.- It publishes immediately (no
--draft), which fires the workflow. If you pass--draft, nothing runs until you publish the draft later.
Option B — Web UI. On GitHub, go to Releases → Draft a new release, pick the
v5.0.81tag, write the notes, and click Publish release. -
Watch the Actions tab. The
Releaseworkflow runs the guards, builds, and publishes. A green check means it is live on PyPI: https://pypi.org/project/django-codenerix/From the CLI you can follow the run directly:
gh run watch
Semantic-ish MAJOR.MINOR.PATCH:
- PATCH (
5.0.80→5.0.81) — bug fixes, no API changes. - MINOR (
5.0.x→5.1.0) — new backwards-compatible features. - MAJOR (
5.x→6.0.0) — backwards-incompatible changes.
The Git tag is always v + the version (e.g. v5.0.81).
Before a real release you can dry-run against TestPyPI:
- Register a Trusted Publisher on https://test.pypi.org for this repo.
- Temporarily add
--publish-url https://test.pypi.org/legacy/to theuv publishstep. - Push a test tag and publish a pre-release.
uv build
unzip -l dist/*.whl | grep -cE '\.(js|css|html)$'The second command is a coherence check that assets are bundled; it should print a number greater than 0.
- I pushed the tag but nothing ran / nothing was published. The tag
push does not trigger anything on its own. The workflow runs on the
release published event, so you must create and publish a GitHub
Release from the tag (step 5:
gh release create vX.Y.Z --generate-notes, or the web UI). A draft release also does nothing until published. - Workflow fails on "Tag must match
__version__". You tagged a version that does not matchcodenerix/__init__.py. Fix the version string (or the tag) so they agree, then re-tag. - Workflow fails on "Verify static assets are bundled" (count 0). The
wheel was built without its static/template files. Make sure the
_codenerixsymlink is not present in the repo; with it gone the default build is correct. The[tool.hatch.build.targets.sdist] only-includeblock also keeps the source tarball clean. uv publishis rejected by PyPI. The Trusted Publisher config on PyPI does not match this repo/workflow/environment. Re-check owner, repository, workflow filename, and environment name.