From e407ed5847a07a9dd11bdd21e672b727f1f98ac3 Mon Sep 17 00:00:00 2001 From: rmichaelthomas Date: Sat, 12 Sep 2026 02:21:50 -0700 Subject: [PATCH] ci(release): publish to PyPI, which README has been promising all along MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README.md's install line is `pip install liminate`, and both liminate-receipts and liminate-contract-inheritance depend on the PyPI distribution. PyPI is a real release channel for this project and it was never in this workflow — the workflow builds signed binaries and attaches them to a GitHub Release, and that is all it has ever done. Through 0.18.1 someone uploaded to PyPI by hand, so the binaries and the package could drift apart with nothing to notice. 0.18.2 is where that showed: it shipped three signed binaries while `pip install liminate` stayed on 0.18.1 and on four missing behaviour fixes, one of which stops `includes` from silently emptying a list. Authentication is trusted publishing (OIDC), matching what the TypeScript validator moved to this week: PyPI mints a one-time credential for this workflow, nothing is stored, nothing expires. No GitHub environment gate — the fewer fields that have to match between here and the PyPI form, the fewer ways a release fails at the last step. Also declares [build-system] and package discovery. There was no such table through 0.18.1: every build worked because pip supplies setuptools when a project names no backend and setuptools auto-discovers a src/ layout. Both are conveniences rather than guarantees, and a release build should not rest on either. Verified locally — sdist and wheel build clean, 21 modules and the packs data present in the wheel. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01E6qcDj1e1dEYvc8jLnpGZy --- .github/workflows/release.yml | 38 +++++++++++++++++++++++++++++++++++ pyproject.toml | 12 +++++++++++ 2 files changed, 50 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 714c59d..ed6547d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -129,6 +129,44 @@ jobs: name: liminate-windows-x64 path: dist/liminate-windows-x64.exe + # `pip install liminate` is the install line in README.md, and + # liminate-receipts and liminate-contract-inheritance both depend on the + # PyPI distribution — so PyPI is a real release channel for this project. + # It was never in this workflow. Through 0.18.1 someone uploaded by hand, + # which meant the binaries and the package could drift apart silently, and + # 0.18.2 shipped binaries while PyPI stayed on 0.18.1 until this job existed. + # + # Authentication is trusted publishing (OIDC): PyPI mints a one-time + # credential for this workflow. No token is stored and nothing expires. + publish-pypi: + needs: [build-macos, build-linux, build-windows] + runs-on: ubuntu-latest + permissions: + id-token: write # mints the OIDC token PyPI exchanges for a credential + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Verify the tag matches pyproject.toml + run: | + PKG_VERSION="$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")" + TAG_VERSION="${GITHUB_REF_NAME#v}" + if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then + echo "::error::Tag ${GITHUB_REF_NAME} does not match pyproject.toml ${PKG_VERSION}." + exit 1 + fi + echo "Publishing ${GITHUB_REF_NAME} (pyproject.toml ${PKG_VERSION})." + + - name: Build sdist and wheel + run: | + python -m pip install --upgrade build + python -m build + + - uses: pypa/gh-action-pypi-publish@release/v1 + release: needs: [build-macos, build-linux, build-windows] runs-on: ubuntu-latest diff --git a/pyproject.toml b/pyproject.toml index b37c17c..d1bbdd6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,15 @@ +# Stated explicitly rather than left to pip's implicit setuptools fallback. +# There was no [build-system] table at all through 0.18.1: every build worked +# because pip supplies setuptools when a project names no backend, and +# setuptools auto-discovers a src/ layout. Both are conveniences, not +# guarantees, and a release build is the wrong place to rely on either. +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["src"] + [project] name = "liminate" version = "0.18.2"