name: Dispatch on: # labels on PRs pull_request_target: types: [labeled] # slash commands & trust commands issue_comment: types: [created, edited] permissions: actions: write pull-requests: write contents: read jobs: # --------------------------------------------------------------------------- # LABEL: TRUSTED AUTHOR # --------------------------------------------------------------------------- label-trusted-author: if: github.event_name == 'pull_request_target' && github.event.label.name == 'trusted-author' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - name: Get PR author login id: pr uses: actions/github-script@v7 with: script: | const login = (context.payload.pull_request.user.login || '').toLowerCase(); if (!login) { core.setFailed('Could not determine PR author login'); return; } core.setOutput('login', login); - id: find uses: peter-evans/find-comment@v3 with: issue-number: ${{ github.event.pull_request.number }} comment-author: github-actions[bot] body-includes: "[HIL trust list]" - name: Upsert trust list for author id: upsert uses: actions/github-script@v7 with: script: | const { upsertTrusted } = require('./.github/scripts/hil-trust.js'); const existing = `${{ toJson(steps.find.outputs.comment-body || '') }}`; const login = '${{ steps.pr.outputs.login }}'.toLowerCase(); const { body } = upsertTrusted(existing, login); core.setOutput('body', body); - uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.pull_request.number }} comment-id: ${{ steps.find.outputs.comment-id }} body: ${{ steps.upsert.outputs.body }} edit-mode: replace - uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.pull_request.number }} body: | Author **@${{ steps.pr.outputs.login }}** was trusted for this PR via the `trusted-author` label. They can now use `/hil quick`, `/hil full` and `/hil ` and their features. # --------------------------------------------------------------------------- # TRUST MANAGEMENT (/trust, /revoke) # --------------------------------------------------------------------------- trust: if: > github.event_name == 'issue_comment' && github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/trust ') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - name: Parse login id: parse uses: actions/github-script@v7 with: script: | const { parseTrustCommand } = require('./.github/scripts/hil-trust.js'); const res = parseTrustCommand(context.payload.comment.body, 'trust'); if (res.error) core.setFailed(res.error); core.setOutput('login', res.login); - id: find uses: peter-evans/find-comment@v3 with: issue-number: ${{ github.event.issue.number }} comment-author: github-actions[bot] body-includes: "[HIL trust list]" - name: Upsert trust list id: upsert uses: actions/github-script@v7 with: script: | const { upsertTrusted } = require('./.github/scripts/hil-trust.js'); const existing = `${{ toJson(steps.find.outputs.comment-body || '') }}`; const login = '${{ steps.parse.outputs.login }}'.toLowerCase(); const { body } = upsertTrusted(existing, login); core.setOutput('body', body); - uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.issue.number }} comment-id: ${{ steps.find.outputs.comment-id }} body: ${{ steps.upsert.outputs.body }} edit-mode: replace - uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.issue.number }} # separate short confirmation comment body: "Trusted **@${{ steps.parse.outputs.login }}** for this PR. They can now use `/hil quick`, `/hil full` and `/hil ` and their features." revoke: if: > github.event_name == 'issue_comment' && github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/revoke ') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - name: Parse login id: parse uses: actions/github-script@v7 with: script: | const { parseTrustCommand } = require('./.github/scripts/hil-trust.js'); const res = parseTrustCommand(context.payload.comment.body, 'revoke'); if (res.error) core.setFailed(res.error); core.setOutput('login', res.login); - id: find uses: peter-evans/find-comment@v3 with: issue-number: ${{ github.event.issue.number }} comment-author: github-actions[bot] body-includes: "[HIL trust list]" - name: Remove from trust list id: update uses: actions/github-script@v7 with: script: | const { revokeTrusted } = require('./.github/scripts/hil-trust.js'); const existing = `${{ toJson(steps.find.outputs.comment-body || '') }}`; const login = '${{ steps.parse.outputs.login }}'.toLowerCase(); const { body } = revokeTrusted(existing, login); core.setOutput('body', body); - uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.issue.number }} comment-id: ${{ steps.find.outputs.comment-id }} body: ${{ steps.update.outputs.body }} edit-mode: replace - uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.issue.number }} body: "Revoked **@${{ steps.parse.outputs.login }}** for this PR." # --------------------------------------------------------------------------- # SLASH-BASED DISPATCHES (/hil quick, /hil full, /hil ) # --------------------------------------------------------------------------- auth: # Only PR comments need auth if: github.event_name == 'issue_comment' && github.event.issue.pull_request runs-on: ubuntu-latest outputs: allowed: ${{ steps.check.outputs.allowed }} steps: - id: check uses: actions/github-script@v7 with: script: | const assoc = context.payload.comment.author_association; const commenter = context.payload.comment.user.login.toLowerCase(); const pr = context.payload.issue.number; if (assoc === 'MEMBER' || assoc === 'OWNER') { core.setOutput('allowed', 'true'); return; } const { owner, repo } = context.repo; const comments = await github.paginate( github.rest.issues.listComments, { owner, repo, issue_number: pr, per_page: 100 } ); const trust = comments.find(c => c.user?.login === 'github-actions[bot]' && typeof c.body === 'string' && c.body.includes('HIL_TRUST_JSON') ); let allowed = false; if (trust) { const m = trust.body.match(//); if (m && m[1]) { try { const data = JSON.parse(m[1]); const list = (data.trusted || []).map(s => String(s).toLowerCase()); allowed = list.includes(commenter); } catch {} } } core.setOutput('allowed', allowed ? 'true' : 'false'); hil-quick: needs: auth if: github.event_name == 'issue_comment' && needs.auth.outputs.allowed == 'true' && startsWith(github.event.comment.body, '/hil quick') runs-on: ubuntu-latest outputs: run_id: ${{ steps.find-run.outputs.run_id }} comment_id: ${{ steps.comment.outputs.comment-id }} steps: - uses: actions/checkout@v4 # for `require` with: fetch-depth: 1 - name: Parse tests id: parse-tests uses: actions/github-script@v7 with: script: | const { parseTests } = require('./.github/scripts/hil-parse.js'); core.setOutput('tests', parseTests(context.payload.comment.body)); - name: Dispatch HIL (quick) uses: benc-uk/workflow-dispatch@v1 with: workflow: hil.yml ref: ${{ github.event.repository.default_branch }} inputs: | { "repository": "${{ github.repository }}", "branch": "refs/pull/${{ github.event.issue.number }}/head", "matrix": "quick", "pr_number": "${{ github.event.issue.number }}", "chips": "", "tests": "${{ steps.parse-tests.outputs.tests }}" } - name: Find HIL run URL (quick) id: find-run uses: actions/github-script@v7 with: script: | const { findHilRun } = require('./.github/scripts/hil-find-run.js'); const { runId, body } = await findHilRun({ github, context, pr: context.payload.issue.number, selector: 'quick', tests: '${{ steps.parse-tests.outputs.tests }}', }); core.setOutput('run_id', runId); core.setOutput('body', body); - name: Confirm in PR id: comment uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.issue.number }} body: ${{ steps.find-run.outputs.body }} hil-quick-status: needs: hil-quick if: needs.hil-quick.outputs.run_id != '' && needs.hil-quick.outputs.comment_id != '' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # for `require` with: fetch-depth: 1 - name: Wait for quick HIL run and update comment uses: actions/github-script@v7 with: script: | const { pollRun } = require('./.github/scripts/hil-status.js'); await pollRun({ github, context, runId: Number('${{ needs.hil-quick.outputs.run_id }}'), commentId: Number('${{ needs.hil-quick.outputs.comment_id }}'), kind: 'HIL (quick)', }); hil-full: needs: auth if: github.event_name == 'issue_comment' && needs.auth.outputs.allowed == 'true' && startsWith(github.event.comment.body, '/hil full') runs-on: ubuntu-latest outputs: run_id: ${{ steps.find-run.outputs.run_id }} comment_id: ${{ steps.comment.outputs.comment-id }} steps: - uses: actions/checkout@v4 # for `require` with: fetch-depth: 1 - name: Parse tests id: parse-tests uses: actions/github-script@v7 with: script: | const { parseTests } = require('./.github/scripts/hil-parse.js'); core.setOutput('tests', parseTests(context.payload.comment.body)); - name: Dispatch HIL (full) uses: benc-uk/workflow-dispatch@v1 with: workflow: hil.yml ref: ${{ github.event.repository.default_branch }} inputs: | { "repository": "${{ github.repository }}", "branch": "refs/pull/${{ github.event.issue.number }}/head", "matrix": "full", "pr_number": "${{ github.event.issue.number }}", "chips": "", "tests": "${{ steps.parse-tests.outputs.tests }}" } - name: Find HIL run URL (full) id: find-run uses: actions/github-script@v7 with: script: | const { findHilRun } = require('./.github/scripts/hil-find-run.js'); const { runId, body } = await findHilRun({ github, context, pr: context.payload.issue.number, selector: 'full', tests: '${{ steps.parse-tests.outputs.tests }}', }); core.setOutput('run_id', runId); core.setOutput('body', body); - name: Confirm in PR id: comment uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.issue.number }} body: ${{ steps.find-run.outputs.body }} hil-full-status: needs: hil-full if: needs.hil-full.outputs.run_id != '' && needs.hil-full.outputs.comment_id != '' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # for `require` with: fetch-depth: 1 - name: Wait for full HIL run and update comment uses: actions/github-script@v7 with: script: | const { pollRun } = require('./.github/scripts/hil-status.js'); await pollRun({ github, context, runId: Number('${{ needs.hil-full.outputs.run_id }}'), commentId: Number('${{ needs.hil-full.outputs.comment_id }}'), kind: 'HIL (full)', }); # --------------------------------------------------------------------------- # PER-CHIP HIL: /hil esp32c3 [esp32s3 ...] [--tests ...] # --------------------------------------------------------------------------- hil-chips: needs: auth if: github.event_name == 'issue_comment' && needs.auth.outputs.allowed == 'true' && startsWith(github.event.comment.body, '/hil ') && !startsWith(github.event.comment.body, '/hil quick') && !startsWith(github.event.comment.body, '/hil full') runs-on: ubuntu-latest outputs: run_id: ${{ steps.find-run.outputs.run_id }} comment_id: ${{ steps.comment.outputs.comment-id }} steps: - uses: actions/checkout@v4 # for `require` with: fetch-depth: 1 - name: Parse chips id: parse uses: actions/github-script@v7 with: script: | const { parseChips } = require('./.github/scripts/hil-parse.js'); const res = parseChips(context.payload.comment.body); core.setOutput('chips', res.chips); core.setOutput('chips_label', res.chipsLabel); core.setOutput('error', res.error); - name: Parse tests id: parse-tests uses: actions/github-script@v7 with: script: | const { parseTests } = require('./.github/scripts/hil-parse.js'); core.setOutput('tests', parseTests(context.payload.comment.body)); - name: Report invalid chips if: steps.parse.outputs.chips == '' && steps.parse.outputs.error != '' uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.issue.number }} body: | @${{ github.event.comment.user.login }}, HIL **per-chip** request failed: ${{ steps.parse.outputs.error }} - name: Dispatch HIL (per-chip) if: steps.parse.outputs.chips != '' uses: benc-uk/workflow-dispatch@v1 with: workflow: hil.yml ref: ${{ github.event.repository.default_branch }} inputs: | { "repository": "${{ github.repository }}", "branch": "refs/pull/${{ github.event.issue.number }}/head", "matrix": "chips", "pr_number": "${{ github.event.issue.number }}", "chips": "${{ steps.parse.outputs.chips }}", "tests": "${{ steps.parse-tests.outputs.tests }}" } - name: Find HIL run URL (per-chip) if: steps.parse.outputs.chips != '' id: find-run uses: actions/github-script@v7 with: script: | const { findHilRun } = require('./.github/scripts/hil-find-run.js'); const { runId, body } = await findHilRun({ github, context, pr: context.payload.issue.number, selector: '${{ steps.parse.outputs.chips }}', tests: '${{ steps.parse-tests.outputs.tests }}', }); core.setOutput('run_id', runId); core.setOutput('body', body); - name: Confirm in PR if: steps.parse.outputs.chips != '' id: comment uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.issue.number }} body: ${{ steps.find-run.outputs.body }} hil-chips-status: needs: hil-chips if: needs.hil-chips.outputs.run_id != '' && needs.hil-chips.outputs.comment_id != '' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # for `require` with: fetch-depth: 1 - name: Wait for per-chip HIL run and update comment uses: actions/github-script@v7 with: script: | const { pollRun } = require('./.github/scripts/hil-status.js'); await pollRun({ github, context, runId: Number('${{ needs.hil-chips.outputs.run_id }}'), commentId: Number('${{ needs.hil-chips.outputs.comment_id }}'), kind: 'HIL (per-chip)', }); hil-deny: needs: auth if: github.event_name == 'issue_comment' && needs.auth.outputs.allowed != 'true' && (startsWith(github.event.comment.body, '/hil quick') || startsWith(github.event.comment.body, '/hil full') || startsWith(github.event.comment.body, '/hil ') || startsWith(github.event.comment.body, '/test-size')) runs-on: ubuntu-latest steps: - name: Inform not allowed uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.issue.number }} body: | @${{ github.event.comment.user.login }}, sorry — you're not allowed to execute HIL runs or binary size analysis for this PR. Please ask an `esp-rs` member/owner to grant access with: ``` /trust @${{ github.event.comment.user.login }} ``` After that, you can use `/hil quick`, `/hil full`, `/hil ` or `/test-size`. hil-help: if: github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/hil') && !startsWith(github.event.comment.body, '/hil quick') && !startsWith(github.event.comment.body, '/hil full') && !contains(github.event.comment.body, 'esp32') runs-on: ubuntu-latest steps: - name: Explain usage uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.issue.number }} body: | Usage: - `/hil quick` — run a quick HIL matrix (only `ESP32-S3` (Xtensa) and `ESP32-C6` (RISC-V) tests) - `/hil full` — run the full HIL matrix for all supported chips - `/hil [ ...]` — run the full HIL tests **only** for the listed chips - `/test-size` — run binary size analysis for this PR - You can optionally append `--test [,...]` to any `/hil` command to only run selected tests. If you aren't a repository **member/owner**, you must be **trusted for this PR**. Maintainers can grant access with a `trusted-author` label or with: ``` /trust @ ``` and revoke with: ``` /revoke @ ``` binary-size: needs: auth if: github.event_name == 'issue_comment' && needs.auth.outputs.allowed == 'true' && startsWith(github.event.comment.body, '/test-size') runs-on: ubuntu-latest steps: - name: Dispatch Binary Size Analysis uses: benc-uk/workflow-dispatch@v1 with: workflow: binary-size.yml ref: ${{ github.event.repository.default_branch }} inputs: | { "pr_number": "${{ github.event.issue.number }}" } - name: Confirm in PR uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.issue.number }} body: > Triggered binary size analysis for #${{ github.event.issue.number }}. Results will be posted as a comment when ready.