CI Comment #1680
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: CI Comment | |
| on: | |
| workflow_run: | |
| workflows: [CI] | |
| types: [completed] | |
| permissions: | |
| pull-requests: write | |
| jobs: | |
| comment: | |
| name: Post CI Results | |
| timeout-minutes: 10 | |
| runs-on: ubuntu-latest | |
| if: github.event.workflow_run.event == 'pull_request' | |
| steps: | |
| - name: Get PR number | |
| id: pr | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const prs = context.payload.workflow_run.pull_requests; | |
| if (!prs || prs.length === 0) { | |
| console.log('No PR associated with this workflow run'); | |
| return; | |
| } | |
| core.setOutput('number', prs[0].number); | |
| - name: Download frontend logs | |
| if: steps.pr.outputs.number | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: context.payload.workflow_run.id, | |
| }); | |
| for (const name of ['frontend-logs', 'frontend-results', 'rust-logs', 'rust-results']) { | |
| const artifact = artifacts.data.artifacts.find(a => a.name === name); | |
| if (artifact) { | |
| const download = await github.rest.actions.downloadArtifact({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| artifact_id: artifact.id, | |
| archive_format: 'zip', | |
| }); | |
| fs.writeFileSync(`/tmp/${name}.zip`, Buffer.from(download.data)); | |
| } | |
| } | |
| - name: Extract artifacts | |
| if: steps.pr.outputs.number | |
| run: | | |
| mkdir -p /tmp/frontend-logs /tmp/frontend-results /tmp/rust-logs /tmp/rust-results | |
| for name in frontend-logs frontend-results rust-logs rust-results; do | |
| if [ -f "/tmp/${name}.zip" ]; then | |
| unzip -o "/tmp/${name}.zip" -d "/tmp/${name}" || true | |
| fi | |
| done | |
| - name: Post or clean frontend comment | |
| if: steps.pr.outputs.number | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const prNumber = parseInt('${{ steps.pr.outputs.number }}'); | |
| const marker = '<!-- ci-frontend-failures -->'; | |
| function describeError(error) { | |
| return error instanceof Error ? `${error.name}: ${error.message}` : String(error); | |
| } | |
| function isMissingFile(error) { | |
| return error && typeof error === 'object' && error.code === 'ENOENT'; | |
| } | |
| function readExitCodeFile(path, label) { | |
| try { | |
| return fs.readFileSync(path, 'utf8').trim() !== '0'; | |
| } catch (error) { | |
| if (isMissingFile(error)) { | |
| core.info(`${label} exit code file was not found at ${path}; assuming that check did not fail.`); | |
| } else { | |
| core.warning(`Unable to read ${label} exit code file at ${path}; assuming that check did not fail. ${describeError(error)}`); | |
| } | |
| return false; | |
| } | |
| } | |
| const typecheckFailed = readExitCodeFile('/tmp/frontend-results/typecheck-exit-code', 'frontend typecheck'); | |
| const testsFailed = readExitCodeFile('/tmp/frontend-results/tests-exit-code', 'frontend tests'); | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| const existing = comments.find(c => c.body.startsWith(marker)); | |
| if (!typecheckFailed && !testsFailed) { | |
| if (existing) { | |
| await github.rest.issues.deleteComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| }); | |
| } | |
| return; | |
| } | |
| let body = '## ❌ Frontend CI Failures\n\n'; | |
| if (typecheckFailed) { | |
| try { | |
| const log = fs.readFileSync('/tmp/frontend-logs/typecheck.log', 'utf8'); | |
| const errors = log.split('\n').filter(l => l.includes('error TS')).join('\n'); | |
| body += '### Type Check\n\n```\n' + (errors || log).slice(0, 30000) + '\n```\n\n'; | |
| } catch (error) { | |
| core.warning(`Unable to read frontend typecheck log: ${describeError(error)}`); | |
| body += '### Type Check\n\n_Failed (logs unavailable)_\n\n'; | |
| } | |
| } | |
| if (testsFailed) { | |
| try { | |
| const log = fs.readFileSync('/tmp/frontend-logs/tests.log', 'utf8'); | |
| const lines = log.split('\n'); | |
| const tail = lines.slice(Math.max(0, lines.length - 200)).join('\n'); | |
| body += '### Tests\n\n```\n' + tail.slice(0, 30000) + '\n```\n\n'; | |
| } catch (error) { | |
| core.warning(`Unable to read frontend test log: ${describeError(error)}`); | |
| body += '### Tests\n\n_Failed (logs unavailable)_\n\n'; | |
| } | |
| } | |
| body = marker + '\n' + body; | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body, | |
| }); | |
| } | |
| - name: Post or clean rust comment | |
| if: steps.pr.outputs.number | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const prNumber = parseInt('${{ steps.pr.outputs.number }}'); | |
| const marker = '<!-- ci-rust-failures -->'; | |
| function describeError(error) { | |
| return error instanceof Error ? `${error.name}: ${error.message}` : String(error); | |
| } | |
| function isMissingFile(error) { | |
| return error && typeof error === 'object' && error.code === 'ENOENT'; | |
| } | |
| let testsFailed = false; | |
| try { | |
| testsFailed = fs.readFileSync('/tmp/rust-results/tests-exit-code', 'utf8').trim() !== '0'; | |
| } catch (error) { | |
| if (isMissingFile(error)) { | |
| core.info('Rust test exit code file was not found at /tmp/rust-results/tests-exit-code; assuming that check did not fail.'); | |
| } else { | |
| core.warning(`Unable to read rust test exit code file at /tmp/rust-results/tests-exit-code; assuming that check did not fail. ${describeError(error)}`); | |
| } | |
| } | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| const existing = comments.find(c => c.body.startsWith(marker)); | |
| if (!testsFailed) { | |
| if (existing) { | |
| await github.rest.issues.deleteComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| }); | |
| } | |
| return; | |
| } | |
| let body = '## ❌ Rust CI Failures\n\n'; | |
| try { | |
| const log = fs.readFileSync('/tmp/rust-logs/rust-tests.log', 'utf8'); | |
| const lines = log.split('\n'); | |
| const errorLines = lines.filter(l => | |
| l.includes('error[') || l.includes('error:') || | |
| l.includes('FAILED') || l.includes('panicked') | |
| ); | |
| const tail = lines.slice(Math.max(0, lines.length - 100)).join('\n'); | |
| const errors = errorLines.length > 0 ? errorLines.join('\n') + '\n\n---\n\n' + tail : tail; | |
| body += '```\n' + errors.slice(0, 30000) + '\n```\n'; | |
| } catch (error) { | |
| core.warning(`Unable to read rust test log: ${describeError(error)}`); | |
| body += '_Failed (logs unavailable)_\n'; | |
| } | |
| body = marker + '\n' + body; | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body, | |
| }); | |
| } |