diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml new file mode 100644 index 00000000..cfa96554 --- /dev/null +++ b/.github/workflows/pr-comment.yml @@ -0,0 +1,81 @@ +name: ๐Ÿ’ฌ PR Test Comment + +# Posts the test-results comment produced by the "๐Ÿงช Test Suite" workflow. +# +# Why this is a separate workflow: for pull_request events from forks GitHub +# issues a read-only GITHUB_TOKEN regardless of the `permissions:` block, so the +# test workflow itself cannot post a comment (403 "Resource not accessible by +# integration"). A workflow_run workflow runs in the base repository's context +# with a writable token, so it can. +# +# SECURITY: this workflow runs with write permissions and must never execute +# code from the pull request. Do not add actions/checkout of the PR head, and do +# not run build/test tooling here. The downloaded artifact is untrusted input: +# the comment body is passed to the API via `body-path` (never interpolated into +# a run: block or a ${{ }} expression) and the PR number is re-validated as +# numeric before use. + +on: + workflow_run: + # Must match the `name:` of .github/workflows/test.yml exactly. + workflows: ['๐Ÿงช Test Suite'] + types: [completed] + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + comment: + name: Post test results comment + runs-on: ubuntu-latest + if: github.event.workflow_run.event == 'pull_request' + steps: + - name: Download comment artifact + id: download + uses: actions/download-artifact@v4 + continue-on-error: true + with: + name: pr-comment + path: pr-comment + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Read PR number + id: pr + if: steps.download.outcome == 'success' + run: | + if [ ! -f pr-comment/pr-number ] || [ ! -f pr-comment/body.md ]; then + echo "Artifact incomplete; nothing to post." + echo "valid=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + number=$(tr -dc '0-9' < pr-comment/pr-number | head -c 10) + if [ -z "$number" ]; then + echo "PR number is not numeric; refusing to continue." + echo "valid=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "number=$number" >> "$GITHUB_OUTPUT" + echo "valid=true" >> "$GITHUB_OUTPUT" + + - name: Find existing PR comment + id: find_comment + if: steps.pr.outputs.valid == 'true' + uses: peter-evans/find-comment@v3 + with: + issue-number: ${{ steps.pr.outputs.number }} + comment-author: 'github-actions[bot]' + body-includes: '## ๐Ÿงช Test Results Summary' + + - name: Create or update PR comment + if: steps.pr.outputs.valid == 'true' + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find_comment.outputs.comment-id }} + issue-number: ${{ steps.pr.outputs.number }} + edit-mode: replace + body-path: pr-comment/body.md diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ac9f87ab..bbee56c5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -253,81 +253,117 @@ jobs: fi continue-on-error: true - - name: Find existing PR comment - if: github.event_name == 'pull_request' - id: find_comment - uses: peter-evans/find-comment@v3 + # Fork PRs get a read-only GITHUB_TOKEN, so this workflow cannot post the + # comment itself. Instead the rendered body is uploaded as an artifact and + # posted by the privileged `pr-comment.yml` workflow via workflow_run. + - name: Render PR comment body + if: always() && github.event_name == 'pull_request' + env: + OVERALL_SCORE: ${{ steps.summary.outputs.overall_score }} + OVERALL_STATUS: ${{ steps.summary.outputs.overall_status }} + FORMAT_STATUS: ${{ steps.format.outputs.status }} + FORMAT_COUNT: ${{ steps.format.outputs.count }} + COMPILE_STATUS: ${{ steps.compile.outputs.status }} + COMPILE_WARNINGS: ${{ steps.compile.outputs.warnings }} + TESTS_STATUS: ${{ steps.tests.outputs.status }} + TESTS_FAILURES: ${{ steps.tests.outputs.failures }} + TESTS_TOTAL: ${{ steps.tests.outputs.total }} + TESTS_SUCCESS_RATE: ${{ steps.tests.outputs.success_rate }} + COVERAGE_STATUS: ${{ steps.coverage.outputs.status }} + COVERAGE_PERCENTAGE: ${{ steps.coverage.outputs.percentage }} + CREDO_STATUS: ${{ steps.credo.outputs.status }} + CREDO_TOTAL: ${{ steps.credo.outputs.total_issues }} + CREDO_HIGH: ${{ steps.credo.outputs.high_issues }} + CREDO_NORMAL: ${{ steps.credo.outputs.normal_issues }} + CREDO_LOW: ${{ steps.credo.outputs.low_issues }} + DIALYZER_STATUS: ${{ steps.dialyzer.outputs.status }} + DIALYZER_ERRORS: ${{ steps.dialyzer.outputs.errors }} + DIALYZER_WARNINGS: ${{ steps.dialyzer.outputs.warnings }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + mkdir -p pr-comment + # Validate the PR number so the trusted workflow can trust this file. + case "$PR_NUMBER" in + ''|*[!0-9]*) echo "Refusing to emit non-numeric PR number" >&2; exit 1 ;; + esac + printf '%s\n' "$PR_NUMBER" > pr-comment/pr-number + + # Values are interpolated by the shell from env, never by Actions + # expression substitution, so tool output cannot inject workflow syntax. + cat > pr-comment/body.md < + ๐Ÿ“ˆ Progress Toward Goals + + Target goals for the project: + - โœจ **Zero compilation warnings** (currently: ${COMPILE_WARNINGS}) + - โœจ **โ‰ค10 Credo issues** (currently: ${CREDO_TOTAL}) + - โœจ **Zero Dialyzer warnings** (currently: ${DIALYZER_WARNINGS}) + - โœจ **โ‰ฅ85% test coverage** (currently: ${COVERAGE_PERCENTAGE}%) + - โœ… **Zero test failures** (currently: ${TESTS_FAILURES}) + + + +
+ ๐Ÿ”ง Quick Actions + + To improve code quality: + \`\`\`bash + # Fix formatting issues + mix format + + # View detailed Credo analysis + mix credo --strict + + # Check Dialyzer warnings + mix dialyzer + + # Generate detailed coverage report + mix coveralls.html + \`\`\` + +
+ + --- + + ๐Ÿค– *Auto-generated by GitHub Actions* โ€ข Updated: $(date -u '+%Y-%m-%d %H:%M UTC') + + > **Note**: This comment will be updated automatically when new commits are pushed to this PR. + EOF + + # Mirror to the run summary so the report is visible even if the + # follow-up commenting workflow is unavailable. + cat pr-comment/body.md >> "$GITHUB_STEP_SUMMARY" + continue-on-error: true + + - name: Upload PR comment body + if: always() && github.event_name == 'pull_request' + uses: actions/upload-artifact@v4 with: - issue-number: ${{ github.event.pull_request.number }} - comment-author: 'github-actions[bot]' - body-includes: '## ๐Ÿงช Test Results Summary' - - - name: Create or update PR comment - if: github.event_name == 'pull_request' - uses: peter-evans/create-or-update-comment@v4 - with: - comment-id: ${{ steps.find_comment.outputs.comment-id }} - issue-number: ${{ github.event.pull_request.number }} - edit-mode: replace - body: | - ## ๐Ÿงช Test Results Summary - - **Overall Quality Score: ${{ steps.summary.outputs.overall_score }}%** ${{ steps.summary.outputs.overall_status }} - - ### ๐Ÿ“Š Metrics Dashboard - - | Category | Status | Count | Details | - |----------|---------|-------|---------| - | ๐Ÿ“ **Code Formatting** | ${{ steps.format.outputs.status }} | ${{ steps.format.outputs.count }} issues | `mix format --check-formatted` | - | ๐Ÿ”จ **Compilation** | ${{ steps.compile.outputs.status }} | ${{ steps.compile.outputs.warnings }} warnings | `mix compile` | - | ๐Ÿงช **Tests** | ${{ steps.tests.outputs.status }} | ${{ steps.tests.outputs.failures }}/${{ steps.tests.outputs.total }} failed | Success rate: ${{ steps.tests.outputs.success_rate }}% | - | ๐Ÿ“Š **Coverage** | ${{ steps.coverage.outputs.status }} | ${{ steps.coverage.outputs.percentage }}% | `mix coveralls` | - | ๐ŸŽฏ **Credo** | ${{ steps.credo.outputs.status }} | ${{ steps.credo.outputs.total_issues }} issues | High: ${{ steps.credo.outputs.high_issues }}, Normal: ${{ steps.credo.outputs.normal_issues }}, Low: ${{ steps.credo.outputs.low_issues }} | - | ๐Ÿ” **Dialyzer** | ${{ steps.dialyzer.outputs.status }} | ${{ steps.dialyzer.outputs.errors }} errors, ${{ steps.dialyzer.outputs.warnings }} warnings | `mix dialyzer` | - - ### ๐ŸŽฏ Quality Gates - - Based on the project's quality thresholds: - - **Compilation Warnings**: ${{ steps.compile.outputs.warnings }}/148 (limit: 148) - - **Credo Issues**: ${{ steps.credo.outputs.total_issues }}/87 (limit: 87) - - **Dialyzer Warnings**: ${{ steps.dialyzer.outputs.warnings }}/161 (limit: 161) - - **Test Coverage**: ${{ steps.coverage.outputs.percentage }}%/50% (minimum: 50%) - - **Test Failures**: ${{ steps.tests.outputs.failures }}/0 (limit: 0) - -
- ๐Ÿ“ˆ Progress Toward Goals - - Target goals for the project: - - โœจ **Zero compilation warnings** (currently: ${{ steps.compile.outputs.warnings }}) - - โœจ **โ‰ค10 Credo issues** (currently: ${{ steps.credo.outputs.total_issues }}) - - โœจ **Zero Dialyzer warnings** (currently: ${{ steps.dialyzer.outputs.warnings }}) - - โœจ **โ‰ฅ85% test coverage** (currently: ${{ steps.coverage.outputs.percentage }}%) - - โœ… **Zero test failures** (currently: ${{ steps.tests.outputs.failures }}) - -
- -
- ๐Ÿ”ง Quick Actions - - To improve code quality: - ```bash - # Fix formatting issues - mix format - - # View detailed Credo analysis - mix credo --strict - - # Check Dialyzer warnings - mix dialyzer - - # Generate detailed coverage report - mix coveralls.html - ``` - -
- - --- - - ๐Ÿค– *Auto-generated by GitHub Actions* โ€ข Updated: ${{ github.event.head_commit.timestamp }} - - > **Note**: This comment will be updated automatically when new commits are pushed to this PR. + name: pr-comment + path: pr-comment/ + retention-days: 1 + continue-on-error: true