Release #60
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: Release | |
| on: | |
| workflow_run: | |
| workflows: ["CI"] | |
| types: [completed] | |
| branches: [main] | |
| # Least privilege by default; elevated per-job. | |
| permissions: | |
| contents: read | |
| # Prevent overlapping releases if two merges land in quick succession. | |
| concurrency: | |
| group: release-main | |
| cancel-in-progress: false | |
| env: | |
| POETRY_VERSION: "2.3.2" | |
| jobs: | |
| # ── Job 1: Semantic Release — bump, changelog, tag ───────────── | |
| release: | |
| name: Semantic Release + Build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| # Gate: CI passed, on main, not a PSR release commit (defense-in-depth). | |
| if: >- | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.head_branch == 'main' && | |
| !startsWith(github.event.workflow_run.head_commit.message, 'chore(release):') | |
| permissions: | |
| contents: write | |
| outputs: | |
| released: ${{ steps.psr.outputs.released }} | |
| tag: ${{ steps.psr.outputs.tag }} | |
| version: ${{ steps.psr.outputs.version }} | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 | |
| with: | |
| app-id: ${{ secrets.RELEASE_APP_ID }} | |
| private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} | |
| - name: Checkout main at CI-tested commit | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| fetch-depth: 0 | |
| token: ${{ steps.app-token.outputs.token }} | |
| - run: pipx install "poetry==${POETRY_VERSION}" | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.12" | |
| cache: "poetry" | |
| - name: Install dependencies | |
| run: poetry install --no-interaction | |
| - name: Python Semantic Release | |
| id: psr | |
| uses: python-semantic-release/python-semantic-release@350c48fcb3ffcdfd2e0a235206bc2ecea6b69df0 # v10.5.3 | |
| with: | |
| github_token: ${{ steps.app-token.outputs.token }} | |
| vcs_release: "true" | |
| changelog: "true" | |
| build: "false" | |
| - name: Build package | |
| if: steps.psr.outputs.released == 'true' | |
| run: poetry build | |
| - name: Upload dist artifacts | |
| if: steps.psr.outputs.released == 'true' | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 | |
| with: | |
| name: dist | |
| path: dist/* | |
| if-no-files-found: error | |
| # ── Job 2: Publish to TestPyPI + smoke test ──────────────────── | |
| publish-testpypi: | |
| name: TestPyPI + Smoke Test | |
| needs: release | |
| if: needs.release.outputs.released == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| environment: testpypi | |
| permissions: | |
| id-token: write # OIDC + PEP 740 attestations | |
| steps: | |
| - name: Checkout (for smoke-test script) | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| sparse-checkout: scripts | |
| sparse-checkout-cone-mode: true | |
| - name: Download dist | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - name: Publish to TestPyPI | |
| uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1 | |
| with: | |
| repository-url: https://test.pypi.org/legacy/ | |
| attestations: true | |
| - name: Smoke test — install and verify from TestPyPI | |
| env: | |
| PKG_VERSION: ${{ needs.release.outputs.version }} | |
| run: ./scripts/smoke-test.sh | |
| # ── Job 3: Publish to PyPI ───────────────────────────────────── | |
| publish-pypi: | |
| name: Publish to PyPI | |
| needs: [release, publish-testpypi] | |
| if: needs.release.outputs.released == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| environment: pypi | |
| permissions: | |
| id-token: write # OIDC + PEP 740 attestations | |
| steps: | |
| - name: Download dist | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1 | |
| with: | |
| attestations: true | |
| # ── Job 4: GitHub Release ────────────────────────────────────── | |
| github-release: | |
| name: GitHub Release | |
| needs: [release, publish-pypi] | |
| if: needs.release.outputs.released == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download dist | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - name: Create GitHub Release + upload assets | |
| uses: python-semantic-release/publish-action@310a9983a0ae878b29f3aac778d7c77c1db27378 # v10.5.3 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| tag: ${{ needs.release.outputs.tag }} |