Skip to content

Commit d0b9172

Browse files
authored
Merge pull request #52 from nm-packages/codex/pypi-release-workflow
Add GitHub Actions PyPI release workflow
2 parents c0f9633 + a7dcfc3 commit d0b9172

4 files changed

Lines changed: 172 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
name: Release
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
permissions:
8+
contents: read
9+
id-token: write
10+
11+
jobs:
12+
build:
13+
name: Build distributions
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: "3.13"
26+
27+
- name: Set up uv
28+
uses: astral-sh/setup-uv@v8.1.0
29+
with:
30+
enable-cache: true
31+
32+
- name: Validate release tag matches package version
33+
env:
34+
RELEASE_TAG: ${{ github.event.release.tag_name }}
35+
run: |
36+
set -euo pipefail
37+
PACKAGE_VERSION="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
38+
EXPECTED_TAG="v${PACKAGE_VERSION}"
39+
40+
if [ "${RELEASE_TAG}" != "${EXPECTED_TAG}" ]; then
41+
echo "Release tag ${RELEASE_TAG} does not match expected tag ${EXPECTED_TAG} derived from project version ${PACKAGE_VERSION}."
42+
exit 1
43+
fi
44+
45+
- name: Validate release commit is on main
46+
env:
47+
RELEASE_TAG: ${{ github.event.release.tag_name }}
48+
run: |
49+
set -euo pipefail
50+
git fetch --no-tags origin main
51+
RELEASE_SHA="$(git rev-list -n 1 "refs/tags/${RELEASE_TAG}")"
52+
53+
if ! git merge-base --is-ancestor "${RELEASE_SHA}" origin/main; then
54+
echo "Release tag ${RELEASE_TAG} points to commit ${RELEASE_SHA}, which is not contained in origin/main."
55+
exit 1
56+
fi
57+
58+
- name: Build source distribution and wheel
59+
run: uv build --sdist --wheel --out-dir dist
60+
61+
- name: Validate built artifacts
62+
run: uvx twine check dist/*
63+
64+
- name: Upload built distributions
65+
uses: actions/upload-artifact@v4
66+
with:
67+
name: python-package-distributions
68+
path: dist/
69+
70+
publish:
71+
name: Publish to PyPI
72+
runs-on: ubuntu-latest
73+
needs: build
74+
75+
steps:
76+
- name: Download built distributions
77+
uses: actions/download-artifact@v4
78+
with:
79+
name: python-package-distributions
80+
path: dist/
81+
82+
- name: Publish distributions to PyPI
83+
uses: pypa/gh-action-pypi-publish@release/v1
84+
with:
85+
packages-dir: dist/

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file.
1717
- Update contributor and agent documentation for the new development workflow
1818
- Update the honeypot field documentation for the `aria-hidden="true"` accessibility attribute
1919
- Sync the Ruff pre-commit hook version with the locked Ruff release and correct the developer doc Wagtail baseline
20+
- Publish PyPI releases from GitHub Actions with PyPI Trusted Publishing
2021

2122
## [1.2.1] - 2026-02-05
2223

docs/contributing/releasing.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Releasing wagtail-honeypot
2+
3+
This is the maintainer runbook for publishing package releases to PyPI via GitHub Actions.
4+
5+
## Release Workflow
6+
7+
- Workflow file: `.github/workflows/release.yml`
8+
- Trigger: GitHub Release event `published`
9+
- Authentication: PyPI Trusted Publisher (OIDC), no API token secret
10+
11+
The release workflow:
12+
13+
1. validates GitHub release tag (`vX.Y.Z` or pre-release like `vX.Y.Zrc1`) matches `project.version` in `pyproject.toml`
14+
2. validates the tagged commit is contained in `origin/main`
15+
3. builds `sdist` + `wheel` with `uv build`
16+
4. runs `twine check` on built artifacts
17+
5. publishes artifacts to PyPI using `pypa/gh-action-pypi-publish`
18+
19+
## One-Time Setup (PyPI Trusted Publisher)
20+
21+
In the PyPI project settings for `wagtail-honeypot`, add a Trusted Publisher with:
22+
23+
- Owner or organization: `nm-packages`
24+
- Repository: `wagtail-honeypot`
25+
- Workflow: `release.yml`
26+
- Environment: unset (unless intentionally adding GitHub Environments later)
27+
28+
The GitHub repository and workflow identity must match exactly, or publish will fail.
29+
30+
## Maintainer Release Steps
31+
32+
1. Keep `CHANGELOG` updated under `## Unreleased` for every merged PR landing on `main`.
33+
2. Update `version` in `pyproject.toml` to the intended release version.
34+
3. Convert the current `## Unreleased` notes in `CHANGELOG` into the new release entry, then leave a fresh `## Unreleased` placeholder for future work.
35+
4. Review user-facing docs and examples that intentionally describe the current release, and either:
36+
- update them to the new version where exact release numbers are still useful, or
37+
- remove brittle hardcoded release numbers when the docs work just as well without them.
38+
5. Merge the release-prep changes to `main`.
39+
6. Ensure normal CI on `main` is green (`Tests` workflow).
40+
7. Create a GitHub Release with a tag that matches the package version with `v` prefix.
41+
8. Use the `CHANGELOG` entry as the GitHub Release body.
42+
9. Publish the GitHub Release.
43+
10. Confirm `.github/workflows/release.yml` succeeds and the version appears on PyPI.
44+
45+
For local sandbox/test command workflows before a release, use:
46+
47+
- [developer.md](../developer.md) for canonical developer workflow and quickstart commands
48+
- [AGENTS.md](https://github.com/nm-packages/wagtail-honeypot/blob/release/AGENTS.md) for canonical contributor command/reference guidance
49+
50+
## Changelog Maintenance
51+
52+
- Add or update a `CHANGELOG` entry under `## Unreleased` in every PR.
53+
- During release prep, move or rewrite the `Unreleased` notes into the new versioned release section.
54+
- After release prep, keep an empty `## Unreleased` section in place for subsequent work.
55+
- During release prep, review any docs/examples that intentionally point at the current package release and avoid leaving stale version numbers in user-facing pages.
56+
57+
Examples:
58+
59+
- `pyproject.toml` version `1.3.0` => GitHub tag `v1.3.0`, release title `1.3.0` (or `Release 1.3.0`)
60+
- `pyproject.toml` version `1.4.0rc1` => GitHub tag `v1.4.0rc1`, release title `1.4.0rc1` (or `Release 1.4.0rc1`)
61+
62+
## Local Preflight (Optional but Recommended)
63+
64+
Run these before creating the GitHub Release:
65+
66+
```bash
67+
uv build --sdist --wheel --out-dir /tmp/wagtail-honeypot-dist-check
68+
uvx twine check /tmp/wagtail-honeypot-dist-check/*
69+
```
70+
71+
## Failure Modes and Troubleshooting
72+
73+
- Tag/version mismatch:
74+
- Symptom: workflow fails in "Validate release tag matches package version"
75+
- Fix: align GitHub tag with `pyproject.toml` version and republish release
76+
- Tag commit not on main:
77+
- Symptom: workflow fails in "Validate release commit is on main"
78+
- Fix: retag a commit that is on `main`
79+
- Trusted Publisher identity mismatch:
80+
- Symptom: PyPI publish step reports authorization/trust failure
81+
- Fix: verify owner/repo/workflow values in PyPI Trusted Publisher settings
82+
- Duplicate version:
83+
- Symptom: publish step fails because version already exists on PyPI
84+
- Fix: bump `pyproject.toml` version and publish a new tag/release

docs/developer.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ make lint
7070
make format
7171
```
7272

73+
See [Releasing wagtail-honeypot](contributing/releasing.md) for the maintainer release runbook.
74+
7375
## Dependency management
7476

7577
Use `uv` to change contributor dependencies and keep the lockfile in sync:

0 commit comments

Comments
 (0)