name: Release # Tag-driven release: run the test gate, build, publish to PyPI, then cut the # GitHub Release with the built artifacts attached. # # Auth: the PYPI_API_TOKEN repository secret (an API token from # https://pypi.org/manage/account/token/). Once the project exists on PyPI, # prefer replacing it with a project-scoped token — or switch back to trusted # publishing (OIDC) by restoring `environment: pypi` + `id-token: write` and # registering the publisher on PyPI. on: push: tags: ["v*"] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install dependencies run: | pip install -e ".[pdf]" pip install pytest pytest-asyncio ruff - run: ruff check . - run: pytest -q build: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.11" - name: Verify tag matches package version run: | pip install tomli-w 2>/dev/null || true PKG_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])") TAG_VERSION="${GITHUB_REF_NAME#v}" if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then echo "Tag $GITHUB_REF_NAME does not match pyproject version $PKG_VERSION" >&2 exit 1 fi - name: Build distributions run: | pip install build twine python -m build twine check dist/* - uses: actions/upload-artifact@v4 with: name: dist path: dist/ publish-pypi: needs: build runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: name: dist path: dist/ - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} github-release: needs: publish-pypi runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: name: dist path: dist/ - name: Create GitHub Release env: GH_TOKEN: ${{ github.token }} run: | gh release create "$GITHUB_REF_NAME" dist/* \ --title "$GITHUB_REF_NAME" \ --generate-notes