diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 6413fa09a..03ef76fbc 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -1,7 +1,7 @@ name: Claude Code Review on: - pull_request: + pull_request_target: types: [opened, synchronize] # Optional: Only run on specific file changes # paths: @@ -21,14 +21,19 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - pull-requests: read + pull-requests: write issues: read id-token: write steps: + # SECURITY: Using pull_request_target is safe here because: + # 1. We checkout the base branch (not PR code) - no untrusted code execution + # 2. Claude Code Action uses GitHub API to read PR, doesn't execute PR code + # 3. Allowed tools are restricted to safe read-only gh commands + gh pr comment - name: Checkout repository uses: actions/checkout@v4 with: + ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 1 - name: Run Claude Code Review diff --git a/.github/workflows/e2e-comment.yml b/.github/workflows/e2e-comment.yml new file mode 100644 index 000000000..b418e6611 --- /dev/null +++ b/.github/workflows/e2e-comment.yml @@ -0,0 +1,95 @@ +name: E2E Test Comment + +on: + workflow_run: + workflows: ['Main'] + types: + - completed + +jobs: + comment: + runs-on: ubuntu-latest + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion != 'skipped' + permissions: + pull-requests: write + + steps: + - name: Download PR number + uses: actions/download-artifact@v4 + with: + name: pr-number + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Read PR number + id: pr + run: echo "number=$(cat pr-number.txt)" >> $GITHUB_OUTPUT + + - name: Download test results + uses: actions/download-artifact@v4 + with: + name: test-results + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + continue-on-error: true + + - name: Generate test results message + id: test-results + uses: actions/github-script@v7 + with: + result-encoding: string + script: | + const fs = require('fs'); + + try { + if (fs.existsSync('results.json')) { + const results = JSON.parse(fs.readFileSync('results.json', 'utf8')); + const { stats } = results; + + const failed = stats.unexpected || 0; + const passed = stats.expected || 0; + const flaky = stats.flaky || 0; + const skipped = stats.skipped || 0; + const duration = Math.round((stats.duration || 0) / 1000); + + const summary = failed > 0 + ? `❌ **${failed} test${failed > 1 ? 's' : ''} failed**` + : `✅ **All tests passed**`; + + return `## E2E Test Results + + ${summary} • ${passed} passed • ${skipped} skipped • ${duration}s + + | Status | Count | + |--------|-------| + | ✅ Passed | ${passed} | + | ❌ Failed | ${failed} | + | ⚠️ Flaky | ${flaky} | + | ⏭️ Skipped | ${skipped} | + + [View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }})`; + } else { + return `## E2E Test Results + + ❌ **Test results file not found** + + [View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }})`; + } + } catch (error) { + console.log('Could not parse test results:', error.message); + return `## E2E Test Results + + ❌ **Error reading test results** + + [View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }})`; + } + + - name: Comment PR with test results + uses: mshick/add-pr-comment@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + message: ${{ steps.test-results.outputs.result }} + message-id: e2e-test-results + issue: ${{ steps.pr.outputs.number }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dc5e02697..b341e5487 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -146,63 +146,14 @@ jobs: path: packages/app/test-results/ retention-days: 30 - - name: Generate test results message - id: test-results + - name: Save PR number if: always() && github.event_name == 'pull_request' - uses: actions/github-script@v7 - with: - result-encoding: string - script: | - const fs = require('fs'); - const path = require('path'); - - try { - const resultsPath = path.join('packages/app/test-results/results.json'); - if (fs.existsSync(resultsPath)) { - const results = JSON.parse(fs.readFileSync(resultsPath, 'utf8')); - const { stats } = results; - - const failed = stats.unexpected || 0; - const passed = stats.expected || 0; - const flaky = stats.flaky || 0; - const skipped = stats.skipped || 0; - const duration = Math.round((stats.duration || 0) / 1000); - - const summary = failed > 0 - ? `❌ **${failed} test${failed > 1 ? 's' : ''} failed**` - : `✅ **All tests passed**`; - - return `## E2E Test Results - - ${summary} • ${passed} passed • ${skipped} skipped • ${duration}s - - | Status | Count | - |--------|-------| - | ✅ Passed | ${passed} | - | ❌ Failed | ${failed} | - | ⚠️ Flaky | ${flaky} | - | ⏭️ Skipped | ${skipped} | + run: echo ${{ github.event.pull_request.number }} > pr-number.txt - [View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`; - } else { - return `## E2E Test Results - - ❌ **Test results file not found** - - [View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`; - } - } catch (error) { - console.log('Could not parse test results:', error.message); - return `## E2E Test Results - - ❌ **Error reading test results** - - [View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`; - } - - - name: Comment PR with test results - uses: mshick/add-pr-comment@v2 + - name: Upload PR number + uses: actions/upload-artifact@v4 if: always() && github.event_name == 'pull_request' with: - message: ${{ steps.test-results.outputs.result }} - message-id: e2e-test-results + name: pr-number + path: pr-number.txt + retention-days: 1