From d08becf4b20aed480a6a65008f991c9ea75ef43e Mon Sep 17 00:00:00 2001 From: Tommaso Casaburi Date: Mon, 16 Mar 2026 20:55:01 +0800 Subject: [PATCH] chore(readme): add dynamic coverage badge --- .github/workflows/ci.yml | 38 ++++++++++++++++++++++++ README.md | 1 + badges/coverage.json | 6 ++++ scripts/write-coverage-badge.mjs | 51 ++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 badges/coverage.json create mode 100644 scripts/write-coverage-badge.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ebfb867..7ad5873a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,18 @@ jobs: - name: Collect coverage run: yarn test:coverage + - name: Write coverage badge payload + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + run: node scripts/write-coverage-badge.mjs + + - name: Upload coverage badge + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + uses: actions/upload-artifact@v4 + with: + name: coverage-badge + path: badges/coverage.json + if-no-files-found: error + - name: Lint run: yarn lint @@ -84,6 +96,32 @@ jobs: path: coverage/coverage-summary.json if-no-files-found: warn + publish-coverage-badge: + name: Publish Coverage Badge + runs-on: ubuntu-22.04 + needs: quality + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + + - name: Download coverage badge + uses: actions/download-artifact@v4 + with: + name: coverage-badge + path: badges/ + + - run: git add badges/coverage.json + - run: git status + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: 'chore(ci): update coverage badge' + file_pattern: 'badges/coverage.json' + branch: ${{ github.ref_name }} + android-build: name: Android Build runs-on: ubuntu-22.04 diff --git a/README.md b/README.md index cc8508ca..dfafcb0e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![Build Status](https://img.shields.io/github/actions/workflow/status/bitsocialnet/5chan/ci.yml?branch=master)](https://github.com/bitsocialnet/5chan/actions/workflows/ci.yml) +[![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/bitsocialnet/5chan/master/badges/coverage.json)](https://github.com/bitsocialnet/5chan/actions/workflows/ci.yml) [![Release](https://img.shields.io/github/v/release/bitsocialnet/5chan)](https://github.com/bitsocialnet/5chan/releases/latest) [![License](https://img.shields.io/badge/license-GPL--2.0--only-red.svg)](https://github.com/bitsocialnet/5chan/blob/master/LICENSE) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) diff --git a/badges/coverage.json b/badges/coverage.json new file mode 100644 index 00000000..ba05f7db --- /dev/null +++ b/badges/coverage.json @@ -0,0 +1,6 @@ +{ + "schemaVersion": 1, + "label": "coverage", + "message": "pending", + "color": "lightgrey" +} diff --git a/scripts/write-coverage-badge.mjs b/scripts/write-coverage-badge.mjs new file mode 100644 index 00000000..254d6af5 --- /dev/null +++ b/scripts/write-coverage-badge.mjs @@ -0,0 +1,51 @@ +import fs from "fs"; +import path from "path"; + +const CWD = process.cwd(); +const COVERAGE_SUMMARY_PATHS = [ + path.join(CWD, "coverage", "coverage-summary.json"), + path.join(CWD, "src", "coverage", "coverage-summary.json"), +]; +const BADGE_OUTPUT_PATH = path.join(CWD, "badges", "coverage.json"); + +const coverageSummaryPath = COVERAGE_SUMMARY_PATHS.find((candidate) => fs.existsSync(candidate)); +if (!coverageSummaryPath) { + const checkedPaths = COVERAGE_SUMMARY_PATHS.map((candidate) => `"${candidate}"`).join(", "); + console.error(`[coverage-badge] Missing coverage summary. Checked: ${checkedPaths}.`); + process.exit(1); +} + +const summary = JSON.parse(fs.readFileSync(coverageSummaryPath, "utf8")); +const lineCoveragePct = summary?.total?.lines?.pct; +if (typeof lineCoveragePct !== "number") { + console.error(`[coverage-badge] Missing total.lines.pct in "${coverageSummaryPath}".`); + process.exit(1); +} + +const roundedPct = Number(lineCoveragePct.toFixed(1)); +const color = + lineCoveragePct >= 95 + ? "brightgreen" + : lineCoveragePct >= 90 + ? "green" + : lineCoveragePct >= 80 + ? "yellowgreen" + : lineCoveragePct >= 70 + ? "yellow" + : lineCoveragePct >= 60 + ? "orange" + : "red"; + +const badge = { + schemaVersion: 1, + label: "coverage", + message: `${roundedPct}%`, + color, +}; + +fs.mkdirSync(path.dirname(BADGE_OUTPUT_PATH), { recursive: true }); +fs.writeFileSync(BADGE_OUTPUT_PATH, `${JSON.stringify(badge, null, 2)}\n`); + +console.log( + `[coverage-badge] Wrote "${BADGE_OUTPUT_PATH}" from "${coverageSummaryPath}" (${roundedPct}%).`, +);