diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..1a73fad --- /dev/null +++ b/.github/workflows/publish.yml @@ -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 diff --git a/README.md b/README.md index 9b9aef4..f6350b0 100644 --- a/README.md +++ b/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