Skip to content

Commit ade21d4

Browse files
physicsrobclaude
andcommitted
Release: tag-triggered PyPI publish via Trusted Publishing
Publication plan phase 4. Pushing a v* tag builds with uv and publishes to PyPI through OIDC (no stored tokens); the workflow refuses if the tag doesn't match the pyproject version. The release ritual — notes, version bump, lock refresh, gates, tag — is documented in docs/releasing.md. Test Suite Status: Unknown Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1f73e30 commit ade21d4

2 files changed

Lines changed: 73 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags: ["v*"]
6+
7+
jobs:
8+
publish:
9+
runs-on: ubuntu-latest
10+
timeout-minutes: 15
11+
# Trusted Publishing (OIDC): PyPI is configured to accept publishes
12+
# from this repo + workflow + environment; no token is stored anywhere.
13+
environment: pypi
14+
permissions:
15+
id-token: write
16+
contents: read
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: astral-sh/setup-uv@v6
20+
- name: Check tag matches project version
21+
run: |
22+
V=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
23+
if [ "v$V" != "$GITHUB_REF_NAME" ]; then
24+
echo "pyproject.toml version $V does not match tag $GITHUB_REF_NAME" >&2
25+
exit 1
26+
fi
27+
- name: Build
28+
run: uv build
29+
- name: Publish to PyPI
30+
run: uv publish --trusted-publishing always

docs/releasing.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Releasing torchwright
2+
3+
Versioning is SemVer, currently 0.x: breaking changes are allowed and land
4+
on minor bumps (`0.1.0``0.2.0`); fixes land on patch bumps. The version
5+
in `pyproject.toml` is the single source of truth.
6+
7+
A release is a commit on `main` plus a matching tag. Pushing the tag
8+
triggers `.github/workflows/release.yml`, which rebuilds the package and
9+
publishes it to PyPI via Trusted Publishing (OIDC — no stored tokens; the
10+
`v*` tag must match the pyproject version or the workflow refuses).
11+
12+
## The ritual
13+
14+
1. **Finalize the notes.** In `RELEASE_NOTES.md`, retitle the
15+
`# Unreleased` section to the version and date
16+
(`# 0.2.0 — 2026-08-01`), and start a fresh empty `# Unreleased`
17+
above it.
18+
2. **Bump the version** in `pyproject.toml`.
19+
3. **Refresh the locks.** `make modal-lock` (the standalone lock records
20+
torchwright's own version), and `uv lock` at the umbrella root.
21+
4. **Gate.** `make lint` and `make test` must pass.
22+
5. **Commit and push** (`Release 0.2.0`), wait for CI to go green on
23+
`main`.
24+
6. **Tag and push the tag:**
25+
26+
git tag v0.2.0
27+
git push origin v0.2.0
28+
29+
7. **Verify:** the Release workflow run succeeds and
30+
https://pypi.org/project/torchwright/ shows the new version.
31+
32+
Optionally create a GitHub Release from the tag afterwards
33+
(`gh release create v0.2.0 --notes "..."`), pasting the version's
34+
section from `RELEASE_NOTES.md`.
35+
36+
## One-time setup (already done / do once)
37+
38+
- PyPI → account → Publishing → add a (pending) publisher:
39+
project `torchwright`, owner `physicsrob`, repository `torchwright`,
40+
workflow `release.yml`, environment `pypi`.
41+
- The `pypi` environment exists in the GitHub repo (Settings →
42+
Environments); the workflow's `environment: pypi` line binds publishes
43+
to it.

0 commit comments

Comments
 (0)