# This workflow will upload a Python Package using Twine when a release is created # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. name: publish # Cancel in-progress runs when a new commit is pushed to the same branch/PR concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true on: release: types: [published] # publish full release to PyPI when a release is created on Github # schedule: # - cron: "0 17 * * FRI" # tag a pre-release on Github every Friday at 5 PM UTC workflow_dispatch: inputs: create_tag: description: "Create the next pre-release tag before publishing" required: false default: true type: boolean permissions: contents: write id-token: write jobs: tag_pre_release: if: github.event_name == 'workflow_dispatch' && inputs.create_tag runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Create pre-release tag run: | git fetch --tags latest_tag=$(git tag --list --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)?$' | head -n 1) if [ -z "$latest_tag" ]; then echo "Failed to find the latest git tag from list:" > /dev/stderr git tag --list --sort=-v:refname exit 1 else # Bump the tag rc version if [[ "$latest_tag" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(rc([0-9]+))?$ ]]; then major="${BASH_REMATCH[1]}" minor="${BASH_REMATCH[2]}" patch="${BASH_REMATCH[3]}" rc="${BASH_REMATCH[5]}" echo "latest_tag: ${major}.${minor}.${patch}rc${rc:-0}" if [ -z "$rc" ]; then # No rc, so bump patch and set rc=1 # 0.2.1 -> 0.2.2rc1 patch=$((patch + 1)) new_tag="${major}.${minor}.${patch}rc1" else if [ "$rc" -ge 99 ]; then echo "Error: rc version is already at 99 for tag $latest_tag, refusing to increment further." > /dev/stderr exit 1 fi rc=$((rc + 1)) new_tag="${major}.${minor}.${patch}rc${rc}" # 0.2.1rc1 -> 0.2.1rc2 fi else echo "Error: latest_tag '$latest_tag' does not match expected version pattern." > /dev/stderr exit 1 fi fi echo "new_tag: $new_tag" git tag $new_tag git push origin $new_tag publish_to_pypi: if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest environment: release permissions: contents: write # for the stable-branch push at the end id-token: write # for PyPI trusted-publishing OIDC actions: read # for the env protection verification step env: IN_DOCKER: 'True' ANONYMIZED_TELEMETRY: 'false' steps: - name: Verify release environment is protected (fail-closed) env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail ENV_NAME="release" if ! RESP="$(gh api -H 'Accept: application/vnd.github+json' \ "/repos/${GITHUB_REPOSITORY}/environments/${ENV_NAME}" 2>/dev/null)"; then echo "::error::Environment '${ENV_NAME}' does not exist (or is inaccessible). Create it at https://github.com/${GITHUB_REPOSITORY}/settings/environments/new with required reviewers and prevent_self_review enabled, then re-run." exit 1 fi HAS_REVIEWERS="$(echo "$RESP" | jq -r '[.protection_rules[]? | select(.type == "required_reviewers")] | length')" if [ "${HAS_REVIEWERS:-0}" -lt 1 ]; then echo "::error::Environment '${ENV_NAME}' exists but has no required-reviewers protection rule." exit 1 fi SELF_REVIEW_BLOCKED="$(echo "$RESP" | jq -r '[.protection_rules[]? | select(.type=="required_reviewers") | .prevent_self_review] | any')" if [ "$SELF_REVIEW_BLOCKED" != "true" ]; then echo "::error::Environment '${ENV_NAME}' must have prevent_self_review enabled. Without it, the gate collapses to one identity when the dispatcher is also a reviewer." exit 1 fi echo "::notice::Environment '${ENV_NAME}' is protected with prevent_self_review. OK." - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v6 with: enable-cache: true activate-environment: true - run: uv sync - run: uv run --no-sync ruff check --no-fix --select PLE # quick check for syntax errors to avoid waiting time doing the rest of the build - run: uv build # - name: Detect installed Playwright version # run: echo "PLAYWRIGHT_VERSION=$(uv pip list --format json | jq -r '.[] | select(.name == "playwright") | .version')" >> $GITHUB_ENV # - name: Cache playwright binaries # uses: actions/cache@v3 # with: # path: | # ~/.cache/ms-playwright # key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }} - run: uvx playwright install chrome - run: uvx playwright install chromium # TODO: just depend on the other test.yml action for this instead of re-running the tests here # - run: uv run pytest tests/ci/test_tools.py # final sanity check: run a few of the tests before release # publish to PyPI - run: uv publish --trusted-publishing always - name: Push to stable branch (if stable release) if: github.event_name == 'release' && !contains(github.ref_name, 'rc') run: | git checkout -b stable git push origin -f stable