diff --git a/.github/workflows/ci-trusted.yml b/.github/workflows/ci-trusted.yml new file mode 100644 index 0000000..24c93f2 --- /dev/null +++ b/.github/workflows/ci-trusted.yml @@ -0,0 +1,88 @@ +name: 🏷️ PR labels & reports + +# ───────────────────────────────────────────────────────────────────────────── +# This workflow runs trusted-only operations that need write access to the PR +# (labeling, commenting, publishing test reports). +# +# SECURITY: uses pull_request_target which runs in the BASE branch context +# with access to secrets and write tokens. It must NEVER checkout or execute +# code from the fork — only use metadata and artifacts. +# ───────────────────────────────────────────────────────────────────────────── + +on: + pull_request_target: + types: [opened, reopened, synchronize, labeled, unlabeled] + +permissions: + contents: read + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + labels: + name: PR labels + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout base branch (trusted code only) + timeout-minutes: 5 + uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.base.ref }} + + - name: Add labels + timeout-minutes: 5 + uses: actions/labeler@v6 + + - name: Enforce labels + timeout-minutes: 5 + uses: mheap/github-action-required-labels@v5 + with: + mode: minimum + count: 1 + labels: | + documentation + ci + sources + dependencies + submodules + add_comment: true + + test-reports: + name: publish test reports + runs-on: ubuntu-latest + timeout-minutes: 10 + # Wait for the CI workflow to complete so the artifact is available + if: github.event.action == 'synchronize' || github.event.action == 'opened' + steps: + - name: Wait for CI workflow + timeout-minutes: 5 + uses: lewagon/wait-on-check-action@v1.3.4 + with: + ref: ${{ github.event.pull_request.head.sha }} + check-name: "lint & test" + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 30 + + - name: Download test results artifact + timeout-minutes: 5 + uses: actions/download-artifact@v4 + with: + name: test-results + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + continue-on-error: true + + - name: Publish test reports + timeout-minutes: 5 + if: hashFiles('tests/.tests.xml') != '' + uses: pmeier/pytest-results-action@v0.8.0 + with: + path: tests/.tests.xml + title: Test results + summary: true + display-options: fEX + fail-on-empty: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92fe195..3fcddc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,24 +6,23 @@ on: - main pull_request: -permissions: - id-token: write - contents: write - actions: write - pull-requests: write - pages: write - concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: + # ────────────────────────────────────────────────────────────────── + # Lint & Test — runs for EVERY PR (including forks) + # Uses GitHub-hosted runners so untrusted code never touches infra. + # Minimal permissions: read-only token, no secrets. + # ────────────────────────────────────────────────────────────────── ci: name: continuous integration - runs-on: public-ledgerhq-shared-small + runs-on: ubuntu-latest timeout-minutes: 60 + permissions: + contents: read steps: - - name: Checkout timeout-minutes: 10 uses: actions/checkout@v7 @@ -31,34 +30,6 @@ jobs: fetch-depth: 0 submodules: "recursive" - - name: Pull request | Add labels - timeout-minutes: 30 - if: github.event_name == 'pull_request' - uses: actions/labeler@v6 - - - name: Pull request | Enforce labels - timeout-minutes: 5 - if: github.event_name == 'pull_request' - uses: mheap/github-action-required-labels@v5 - with: - mode: minimum - count: 1 - labels: | - documentation - ci - sources - dependencies - submodules - add_comment: true - - - name: Get API keys - timeout-minutes: 10 - shell: bash - run: | - cat << EOF | jq -r 'to_entries[] | select(.key|endswith("_API_KEY")) | "\(.key)=\(.value)"' >> "$GITHUB_ENV" - ${{ toJSON(secrets) }} - EOF - - name: Setup pre-commit cache timeout-minutes: 10 uses: actions/cache@v5 @@ -74,6 +45,15 @@ jobs: timeout-minutes: 10 run: pdm install --dev --check --frozen-lockfile + - name: Get API keys + timeout-minutes: 10 + if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push' + shell: bash + run: | + cat << EOF | jq -r 'to_entries[] | select(.key|endswith("_API_KEY")) | "\(.key)=\(.value)"' >> "$GITHUB_ENV" + ${{ toJSON(secrets) }} + EOF + - name: Lint timeout-minutes: 10 run: pdm run lint @@ -84,43 +64,57 @@ jobs: timeout-minutes: 40 run: pdm run test - - name: Publish test reports + - name: Upload test results timeout-minutes: 10 if: ${{ !cancelled() }} - uses: pmeier/pytest-results-action@v0.8.0 + uses: actions/upload-artifact@v4 with: + name: test-results path: tests/.tests.xml - title: Test results - summary: true - display-options: fEX - fail-on-empty: false - - - name: Setup docs cache - timeout-minutes: 10 - if: ${{ !cancelled() }} - uses: actions/cache@v5 - with: - path: docs/build - key: docs-cache-${{ hashFiles('docs') }} - name: Build docs timeout-minutes: 10 if: ${{ !cancelled() }} run: pdm run docs - - name: Setup Github Pages + - name: Upload docs artifact timeout-minutes: 10 if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + uses: actions/upload-artifact@v4 + with: + name: docs-build + path: "./docs/build" + + # ────────────────────────────────────────────────────────────────── + # Deploy docs — only on push to main + # ────────────────────────────────────────────────────────────────── + deploy-docs: + name: deploy docs + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: ci + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + pages: write + id-token: write + steps: + - name: Download docs artifact + timeout-minutes: 10 + uses: actions/download-artifact@v4 + with: + name: docs-build + path: "./docs/build" + + - name: Setup Github Pages + timeout-minutes: 10 uses: actions/configure-pages@v5 - name: Upload docs to Github Pages timeout-minutes: 10 - if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} uses: actions/upload-pages-artifact@v4 with: - path: './docs/build' + path: "./docs/build" - name: Deploy GitHub Pages timeout-minutes: 10 - if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' }} uses: actions/deploy-pages@v4