ci: add publish_pypi workflow for trusted-publisher PyPI releases#69
ci: add publish_pypi workflow for trusted-publisher PyPI releases#69
Conversation
Adds .github/workflows/publish_pypi.yaml — a workflow_dispatch release pipeline using PyPI/TestPyPI trusted publishing via OIDC. Adapted from a uv/pyproject.toml-based template to fit this project's setuptools-scm + setup.py build, bare-tag convention (0.x.y, no v), and the configured trusted publishers (env=release on PyPI, env=testpypi on TestPyPI). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #69 +/- ##
=======================================
Coverage 97.12% 97.12%
=======================================
Files 9 9
Lines 939 939
=======================================
Hits 912 912
Misses 27 27 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@malteos do you have an action traces tested with dry-run? I cannot find it, and I don't see any "workflow_dispatcher" in the action tab. |
|
That's the tricky thing about PRs like this. You can only run |
|
To simulate this I made a fork and merged this PR: |
lfoppiano
left a comment
There was a problem hiding this comment.
Based on the description and on the test, LGTM. I particularly like the idea of using special dev suffix when dry-run is set to true.
I'm curious about the automated github release, I might steal it 😄
Summary
Adds
.github/workflows/publish_pypi.yaml— a manually-dispatched release pipeline that uses PyPI Trusted Publishing (OIDC, no API tokens) to publishcdx_toolkitto TestPyPI and PyPI, then cuts a GitHub Release.Replaces the manual
make distflow currently documented in theMakefile. The workflow isworkflow_dispatchonly — releases never happen automatically on push.Why this design
The project uses
setup.py+ setuptools-scm (version derived from git tags, nopyproject.toml, nouv/uv.lock). The workflow keeps this versioning model — the git tag is the source of truth. The bare-tag convention (0.9.39, nov) documented in theMakefileis preserved.Trusted publishing is already configured on:
releasetestpypiBoth bound to workflow filename
publish_pypi.yaml.Workflow inputs
version_bumppatch/minor/major)patchdry_runtruetrue:.devN-suffixed build → TestPyPI only; no tag push, no PyPI, no GitHub ReleaseThe
dry_run=truedefault is intentional — the very first dispatch after merging is harmless.Job graph
bump-and-build— checkout (full history + tags), set up Python 3.13,pip install build twine packaging, compute the next version by parsing the latest^[0-9]+\.[0-9]+\.[0-9]+$tag and bumping perversion_bump(or appending.dev${GITHUB_RUN_NUMBER}for dry runs), validate withpackaging.version.Version, build sdist + wheel withSETUPTOOLS_SCM_PRETEND_VERSION_FOR_CDX_TOOLKITset so the artifact carries the intended version before any tag exists, runtwine check, verify built filenames, uploaddist/. Real-release-only final step: create + push annotated tag.publish-testpypi— environmenttestpypi,id-token: write. Downloads the artifact and publishes tohttps://test.pypi.org/legacy/withskip-existing: true. Runs for both dry-run and real release.publish-pypi— environmentrelease,id-token: write. Skipped on dry runs. Noskip-existing(hard fail on accidental version reuse).github-release— checks out the tag, downloads the artifact, runsgh release create "${VERSION}" dist/* --title "${VERSION}" --generate-notes --verify-tag.Concurrency lock
group: publish, cancel-in-progress: falseprevents racing dispatches.Step-by-step usage
Dry run (recommended first invocation)
Expected:
0.9.38→ computed version is0.9.39.dev<run_number>.bump-and-buildproducesdist/cdx_toolkit-0.9.39.dev<run>.tar.gz+ matching wheel.publish-testpypisucceeds; release visible athttps://test.pypi.org/project/cdx-toolkit/0.9.39.dev<run>/.publish-pypiandgithub-releaseare skipped.Real patch release
Expected (latest tag
0.9.38→ release0.9.39):bump-and-buildbuildscdx_toolkit-0.9.39.tar.gz+ wheel and pushes annotated tag0.9.39toorigin.publish-testpypiuploads to TestPyPI (idempotent if re-run).publish-pypiuploads to PyPI; release live athttps://pypi.org/project/cdx-toolkit/0.9.39/.github-releasecuts the GitHub Release on tag0.9.39with auto-generated notes and the sdist + wheel attached.Verifying after a real release
Operational notes
git push --delete origin <tag>and re-dispatch. (Matches the source workflow's behavior; not overengineered into a multi-step rollback.)GITHUB_TOKENis sufficient for tag push andgh release create.testpypiandreleaseGitHub Environments must exist with their respective trusted-publisher configs on test.pypi.org and pypi.org. (User confirmed these are configured.)bump-and-build:contents: write(push tag).publish-testpypi,publish-pypi:id-token: writeonly.github-release:contents: write(create release).Test plan
main(workflow file must be on the default branch beforeworkflow_dispatchis dispatchable).main:gh workflow run publish_pypi.yaml -f version_bump=patch -f dry_run=true. Verify (a) all four jobs behave as expected, (b)0.9.39.dev<run>appears on TestPyPI, (c) no tag is pushed, (d)publish-pypiandgithub-releaseare skipped.gh workflow run publish_pypi.yaml --ref my-branch -f dry_run=true(the workflow file must already be onmainonce for--refdispatches to work).gh workflow run publish_pypi.yaml -f version_bump=patch -f dry_run=false. Verify tag0.9.39on origin, release on pypi.org, GitHub Release page populated, andpip install cdx_toolkit==0.9.39works in a clean venv.🤖 Generated with Claude Code