Repository navigation
pypi-publish #11
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: pypi-publish | |
| # Publishing is deliberate and manual, and it is the exact sibling of npm-publish.yml: same | |
| # ref-proof contract, same package, other ecosystem. It refuses to run against anything but | |
| # the exact current tip of refs/heads/main, and it checks out the statically trusted | |
| # github.sha rather than an indirect ref. | |
| # | |
| # Authentication is PyPI Trusted Publishing (OIDC) -- no token, nothing to rotate or leak. | |
| # The `pypi-production` environment scopes which GitHub identity PyPI will accept: its | |
| # Trusted Publisher must be registered with owner AetherAI3, repo agent-browser, workflow | |
| # pypi-publish.yml, and environment name "pypi-production" (Manage project -> Publishing on | |
| # pypi.org). | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| expected_sha: | |
| description: Exact 40-character commit SHA currently at refs/heads/main | |
| required: true | |
| type: string | |
| dry_run: | |
| description: Build and verify without publishing | |
| required: false | |
| default: true | |
| type: boolean | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: pypi-publish-main | |
| cancel-in-progress: false | |
| jobs: | |
| ref-proof: | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| outputs: | |
| sha: ${{ steps.proof.outputs.sha }} | |
| steps: | |
| - name: Checkout current main without retaining credentials | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: refs/heads/main | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| - name: Prove exact current main commit | |
| id: proof | |
| shell: bash | |
| env: | |
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | |
| EVENT_REF: ${{ github.ref }} | |
| EVENT_SHA: ${{ github.sha }} | |
| EXPECTED_SHA: ${{ inputs.expected_sha }} | |
| run: | | |
| set -Eeuo pipefail | |
| test "$DEFAULT_BRANCH" = "main" | |
| test "$EVENT_REF" = "refs/heads/main" | |
| test "${#EVENT_SHA}" -eq 40 | |
| test "${#EXPECTED_SHA}" -eq 40 | |
| case "$EVENT_SHA" in (*[!0-9a-f]*) exit 1 ;; esac | |
| case "$EXPECTED_SHA" in (*[!0-9a-f]*) exit 1 ;; esac | |
| test "$EXPECTED_SHA" = "$EVENT_SHA" | |
| test "$(git rev-parse --verify HEAD^{commit})" = "$EVENT_SHA" | |
| printf 'sha=%s\n' "$EVENT_SHA" >> "$GITHUB_OUTPUT" | |
| publish: | |
| needs: ref-proof | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| environment: pypi-production | |
| permissions: | |
| contents: read | |
| id-token: write # PyPI Trusted Publishing (OIDC) | |
| steps: | |
| - name: Checkout proved commit | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| # ref-proof has already asserted that github.sha is the exact refs/heads/main tip | |
| # and equals the supplied expected_sha. Use github.sha here rather than the job | |
| # output: it is the identical commit by construction, but it is a statically | |
| # trusted ref, so a privileged run can never execute an unproved tree. The step | |
| # below still cross-checks HEAD against the proved SHA. | |
| ref: ${{ github.sha }} | |
| persist-credentials: false | |
| - name: Verify exact checkout | |
| shell: bash | |
| env: | |
| TRUSTED_SHA: ${{ needs.ref-proof.outputs.sha }} | |
| run: test "$(git rev-parse --verify HEAD^{commit})" = "$TRUSTED_SHA" | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Refuse runtime dependencies | |
| shell: bash | |
| working-directory: clients/python | |
| run: grep -qx 'dependencies = \[\]' pyproject.toml | |
| - name: Unit tests | |
| working-directory: clients/python | |
| env: | |
| PYTHONPATH: src | |
| run: python -m unittest discover -s tests -v | |
| - name: Build sdist and wheel | |
| id: build | |
| shell: bash | |
| working-directory: clients/python | |
| run: | | |
| set -Eeuo pipefail | |
| python -m pip install --upgrade pip build twine | |
| python -m build | |
| python -m twine check --strict dist/* | |
| version="$(python -c 'import tomllib,pathlib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')" | |
| printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT" | |
| test -f "dist/aether_browser-${version}.tar.gz" | |
| test -f "dist/aether_browser-${version}-py3-none-any.whl" | |
| sha256sum dist/* | |
| - name: Publish to PyPI | |
| if: ${{ !inputs.dry_run }} | |
| uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 | |
| with: | |
| packages-dir: clients/python/dist | |
| - name: Dry run summary | |
| if: ${{ inputs.dry_run }} | |
| shell: bash | |
| run: | | |
| set -Eeuo pipefail | |
| echo "Dry run only. Built aether-browser ${{ steps.build.outputs.version }}." | |
| echo "Re-run with dry_run=false to publish." |