Publish to PyPI #27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish to PyPI | |
| # Runs after the full CI workflow (lint, python matrix, cost-map-sync, js) | |
| # succeeds on main — not on the raw push. npm 0.2.0 once shipped while main CI | |
| # was red on cost-map-sync; gating on workflow_run closes that hole. | |
| on: | |
| workflow_run: | |
| workflows: [CI] | |
| types: [completed] | |
| branches: [main] | |
| workflow_dispatch: | |
| concurrency: | |
| group: publish-${{ github.ref }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write # push the release tag; PyPI auth stays the token secret | |
| jobs: | |
| publish: | |
| # workflow_run's `branches` filter matches the HEAD branch NAME of the | |
| # triggering run — a fork PR from a branch named "main" would match it. The | |
| # event/head_repository checks restrict publishing to push-triggered CI on | |
| # this repo's own main. | |
| if: >- | |
| (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') || | |
| (github.event_name == 'workflow_run' && | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.event == 'push' && | |
| github.event.workflow_run.head_repository.full_name == github.repository) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| persist-credentials: false | |
| # workflow_run checks out the default branch by default; pin to the | |
| # exact commit CI validated. | |
| ref: ${{ github.event.workflow_run.head_sha || github.ref }} | |
| - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 | |
| with: | |
| python-version: '3.11' | |
| - name: Upgrade pip and install build | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install build | |
| - name: Install project with dev extras | |
| if: hashFiles('tests/**') != '' | |
| run: pip install -e ".[dev]" | |
| - name: Run tests | |
| if: hashFiles('tests/**') != '' | |
| run: pytest | |
| - name: Read package metadata | |
| id: meta | |
| run: | | |
| PKG_NAME=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['name'])") | |
| PKG_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])") | |
| echo "name=$PKG_NAME" >> "$GITHUB_OUTPUT" | |
| echo "version=$PKG_VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Check if version is already published | |
| id: check | |
| run: | | |
| PKG_NAME="${{ steps.meta.outputs.name }}" | |
| PKG_VERSION="${{ steps.meta.outputs.version }}" | |
| HTTP_CODE=$(curl -sS --connect-timeout 10 --max-time 20 -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/$PKG_NAME/$PKG_VERSION/json") | |
| case "$HTTP_CODE" in | |
| 200) | |
| echo "$PKG_NAME==$PKG_VERSION already published on PyPI, skipping." | |
| echo "should_publish=false" >> "$GITHUB_OUTPUT" | |
| ;; | |
| 404) | |
| echo "$PKG_NAME==$PKG_VERSION not found on PyPI, will publish." | |
| echo "should_publish=true" >> "$GITHUB_OUTPUT" | |
| ;; | |
| *) | |
| echo "Unexpected PyPI response (HTTP $HTTP_CODE). Failing to avoid incorrect publish." | |
| exit 1 | |
| ;; | |
| esac | |
| - name: Build distribution | |
| if: steps.check.outputs.should_publish == 'true' | |
| run: python -m build | |
| - name: Publish to PyPI | |
| if: steps.check.outputs.should_publish == 'true' | |
| uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 | |
| with: | |
| password: ${{ secrets.PYPI_API_TOKEN }} | |
| - name: Tag the release | |
| # Maps the published artifact back to its exact commit ("py-" prefix: | |
| # the npm package versions independently and tags "js-v<version>"). | |
| if: steps.check.outputs.should_publish == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PKG_VERSION: ${{ steps.meta.outputs.version }} | |
| run: | | |
| gh api "repos/${{ github.repository }}/git/refs" \ | |
| -f ref="refs/tags/py-v$PKG_VERSION" \ | |
| -f sha="$(git rev-parse HEAD)" \ | |
| || echo "::warning::tag py-v$PKG_VERSION already exists or could not be created" |