chore(ci): tag-triggered PyPI publish via OIDC trusted-publisher
Tag-on-push (`v0.1.0` shape) builds the sdist+wheel, smoke-imports the console-script entry point from a fresh venv to catch packaging regressions, then uploads to PyPI via trusted publisher OIDC — no API token in repo secrets. Includes a tag-vs-pyproject version-match guard: aborts the publish if the tag doesn't match `pyproject.toml`'s `version`. Cheap defense against the failure mode where the tag advances but pyproject doesn't, which silently re-publishes the same wheel under a new tag. README's "Releasing" section walks through the one-time PyPI trusted- publisher registration the operator must do once before the first tagged push. After this lands and the PyPI registration is complete, the codex tab in the External Connect modal can switch from pip install 'git+https://github.com/Molecule-AI/codex-channel-molecule.git' to pip install codex-channel-molecule Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d6eb78dcee
commit
dba5970177
69
.github/workflows/publish.yml
vendored
Normal file
69
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
name: Publish to PyPI
|
||||
|
||||
# Triggered on tag push (vX.Y.Z). Tag-on-push instead of release-creation
|
||||
# is the cheaper UX — `git tag v0.1.0 && git push origin v0.1.0` ships
|
||||
# without leaving the terminal.
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||
- "v[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
# OIDC token for PyPI trusted-publisher auth — no secret token needed.
|
||||
# PyPI side: register
|
||||
# github.com/Molecule-AI/codex-channel-molecule
|
||||
# workflow=publish.yml environment=pypi
|
||||
# under "Trusted publisher management" on the codex-channel-molecule
|
||||
# PyPI project page (see README "Releasing" section).
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Verify tag matches pyproject version
|
||||
run: |
|
||||
tag="${GITHUB_REF#refs/tags/v}"
|
||||
pkg=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
||||
if [ "$tag" != "$pkg" ]; then
|
||||
echo "::error::tag $tag does not match pyproject version $pkg — aborting publish to keep PyPI in sync with git tags"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build sdist + wheel
|
||||
run: |
|
||||
python -m pip install --upgrade pip build
|
||||
python -m build
|
||||
|
||||
- name: Smoke-import the built wheel
|
||||
run: |
|
||||
python -m venv /tmp/install-test
|
||||
/tmp/install-test/bin/pip install dist/*.whl
|
||||
/tmp/install-test/bin/codex-channel-molecule --help
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
|
||||
publish:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
|
||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||
25
README.md
25
README.md
@ -70,6 +70,31 @@ pytest -q
|
||||
|
||||
Tests are entirely real-subprocess (no mocking the spawn boundary) so the boot path is covered the same way the daemon runs in production.
|
||||
|
||||
## Releasing
|
||||
|
||||
Tag-on-push triggers `publish.yml` which builds + publishes to PyPI via OIDC trusted publishing (no API token needed).
|
||||
|
||||
```sh
|
||||
# Bump pyproject.toml `version`, commit, then:
|
||||
git tag v0.1.1 && git push origin v0.1.1
|
||||
```
|
||||
|
||||
The workflow refuses to publish if the tag doesn't match `pyproject.toml`'s `version` — keeps PyPI versions and git tags in lockstep.
|
||||
|
||||
**One-time PyPI setup** (before the first release):
|
||||
|
||||
1. Create the project on PyPI by uploading the first wheel manually, OR
|
||||
2. Pre-register the project on PyPI under a "Pending publisher" config so the first tagged push creates it.
|
||||
|
||||
Either way, on the project's PyPI page → "Manage" → "Publishing" → "Add a new publisher", configure:
|
||||
|
||||
- Owner: `Molecule-AI`
|
||||
- Repository: `codex-channel-molecule`
|
||||
- Workflow filename: `publish.yml`
|
||||
- Environment name: `pypi`
|
||||
|
||||
After this, every `git push origin v*.*.*` ships the wheel to PyPI without any further intervention.
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user