From 2195e4af4576069f1bd8db9ff9c959221998989f Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:48:06 -0700 Subject: [PATCH] Ok, lets try manual Codecov comments --- .github/workflows/codecov-comment.yml | 220 ++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 .github/workflows/codecov-comment.yml diff --git a/.github/workflows/codecov-comment.yml b/.github/workflows/codecov-comment.yml new file mode 100644 index 000000000..b5fb3c506 --- /dev/null +++ b/.github/workflows/codecov-comment.yml @@ -0,0 +1,220 @@ +name: Codecov PR Comment +on: + workflow_run: + workflows: + - ci + types: + - completed +permissions: + contents: read + pull-requests: write +jobs: + comment: + if: >- + github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-24.04 + steps: + - name: Gather pull request context + id: pr + uses: actions/github-script@v7 + with: + script: | + const run = context.payload.workflow_run; + if (!run.pull_requests || run.pull_requests.length === 0) { + core.info('No associated pull request. Skipping.'); + return { shouldRun: false }; + } + + const pr = run.pull_requests[0]; + return { + shouldRun: true, + prNumber: pr.number, + headSha: run.head_sha, + }; + - name: Fetch Codecov coverage + id: coverage + if: steps.pr.outputs.shouldRun == 'true' + uses: actions/github-script@v7 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + COMMIT_SHA: ${{ steps.pr.outputs.headSha }} + with: + script: | + const token = process.env.CODECOV_TOKEN; + if (!token) { + core.warning('CODECOV_TOKEN secret is not available; skipping comment.'); + core.setOutput('shouldComment', 'false'); + return; + } + + const commitSha = process.env.COMMIT_SHA; + const owner = context.repo.owner; + const repo = context.repo.repo; + const url = `https://codecov.io/api/v2/github/${owner}/repos/${repo}/commits/${commitSha}/report`; + const maxAttempts = 10; + const waitMs = 15000; + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + + let data; + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + core.info(`Fetching Codecov report (attempt ${attempt}/${maxAttempts})`); + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + }); + + if (response.status === 404) { + core.info('Report not ready yet (404). Waiting before retrying.'); + await sleep(waitMs); + continue; + } + + if (!response.ok) { + const text = await response.text(); + throw new Error(`Codecov API returned ${response.status}: ${text}`); + } + + data = await response.json(); + if (data && Object.keys(data).length > 0) { + break; + } + + core.info('Report payload empty. Waiting before retrying.'); + await sleep(waitMs); + } + + if (!data) { + core.warning('Unable to retrieve Codecov report after multiple attempts.'); + core.setOutput('shouldComment', 'false'); + return; + } + + const totals = data.report?.totals ?? data.commit?.totals ?? data.totals; + if (!totals) { + core.warning('Codecov response does not contain coverage totals.'); + core.setOutput('shouldComment', 'false'); + return; + } + + const compareTotals = data.report?.compare?.totals ?? data.compare?.totals; + const flagsRaw = data.report?.totals_by_flag ?? data.report?.components ?? []; + + const toNumber = (value) => { + if (value === null || value === undefined || value === '') { + return undefined; + } + const num = Number(value); + return Number.isFinite(num) ? num : undefined; + }; + + const coverage = toNumber(totals.coverage); + const baseCoverage = toNumber(compareTotals?.base_coverage ?? compareTotals?.base); + const delta = toNumber( + compareTotals?.coverage_change ?? + compareTotals?.coverage_diff ?? + totals.delta ?? + totals.diff ?? + totals.change, + ); + + const formatPercent = (value) => { + if (value === undefined) return '—'; + return `${value.toFixed(2)}%`; + }; + + const formatDelta = (value) => { + if (value === undefined) return '—'; + const sign = value >= 0 ? '+' : ''; + return `${sign}${value.toFixed(2)}%`; + }; + + const shortSha = commitSha.slice(0, 7); + const lines = [ + '', + '**Codecov Coverage**', + '', + `- Head \`${shortSha}\`: ${formatPercent(coverage)}`, + ]; + + if (baseCoverage !== undefined) { + lines.push(`- Base: ${formatPercent(baseCoverage)}`); + } + + if (delta !== undefined) { + lines.push(`- Change: ${formatDelta(delta)}`); + } + + const flagEntries = Array.isArray(flagsRaw) + ? flagsRaw + : Object.entries(flagsRaw).map(([name, totals]) => ({ name, totals })); + + const flagRows = []; + for (const entry of flagEntries) { + const label = entry.flag ?? entry.name ?? entry.component ?? entry.id; + const entryTotals = entry.totals ?? entry; + const entryCoverage = toNumber(entryTotals?.coverage); + const entryDelta = toNumber( + entryTotals?.coverage_change ?? + entryTotals?.coverage_diff ?? + entryTotals?.delta ?? + entryTotals?.diff, + ); + if (!label || entryCoverage === undefined) { + continue; + } + flagRows.push(`| ${label} | ${formatPercent(entryCoverage)} | ${formatDelta(entryDelta)} |`); + } + + if (flagRows.length) { + lines.push(''); + lines.push('| Flag | Coverage | Change |'); + lines.push('| --- | --- | --- |'); + lines.push(...flagRows); + } + + const commentBody = lines.join('\n'); + const shouldComment = coverage !== undefined; + core.setOutput('shouldComment', shouldComment ? 'true' : 'false'); + if (shouldComment) { + core.setOutput('commentBody', commentBody); + } + - name: Upsert coverage comment + if: steps.pr.outputs.shouldRun == 'true' && steps.coverage.outputs.shouldComment == 'true' + uses: actions/github-script@v7 + env: + PR_NUMBER: ${{ steps.pr.outputs.prNumber }} + COMMENT_BODY: ${{ steps.coverage.outputs.commentBody }} + with: + script: | + const prNumber = Number(process.env.PR_NUMBER); + const body = process.env.COMMENT_BODY; + const marker = ''; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 100, + }); + + const existing = comments.find((comment) => comment.body?.includes(marker)); + if (existing) { + core.info(`Updating existing coverage comment (id: ${existing.id}).`); + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + core.info('Creating new coverage comment.'); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body, + }); + }