From 8df671f19a0f2621c7546381336ceea40ecaa0a8 Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Wed, 14 Oct 2026 08:44:29 +0000 Subject: [PATCH] Add backend test coverage support with c8 and CI integration - Updated CLAUDE.md to document the new `just test-coverage` command for running tests with coverage reporting. - Modified Dockerfile to create a coverage directory for c8 reports. - Added a coverage badge to README.md that displays the current backend test coverage. - Created backend/.c8rc.json configuration for c8 coverage reporting. - Updated backend/package.json to include c8 as a dependency and added a new test script for coverage. - Adjusted docker-compose.tst.yml to support running tests with coverage. - Enhanced justfile to include a `test-coverage` command for running tests with coverage and copying reports. - Updated pnpm-lock.yaml to include new dependencies for coverage reporting. - Added scripts/coverage-report.mjs to generate coverage badges and markdown reports from c8 output. --- .dockerignore | 2 + .github/workflows/backend.yml | 65 +++++++++++- AGENTS.md | 19 ++++ CLAUDE.md | 19 ++++ Dockerfile | 2 + README.md | 2 + backend/.c8rc.json | 9 ++ backend/package.json | 2 + docker-compose.tst.yml | 4 +- justfile | 22 +++- pnpm-lock.yaml | 189 ++++++++++++++++++++++++++++++---- scripts/coverage-report.mjs | 127 +++++++++++++++++++++++ 12 files changed, 439 insertions(+), 23 deletions(-) create mode 100644 backend/.c8rc.json create mode 100755 scripts/coverage-report.mjs diff --git a/.dockerignore b/.dockerignore index 4c8b90cda..7d7905a69 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,3 +2,5 @@ node_modules */node_modules .env .development.env +coverage +*/coverage diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 3439bc42f..27bac0705 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -15,9 +15,70 @@ jobs: steps: - uses: actions/checkout@v3 - uses: extractions/setup-just@v1 - - name: run tests - run: just test + # `just test` under c8 (backend/.c8rc.json): same suite, plus the report copied out of the + # container to backend/coverage/ for the two steps below. + - name: run tests (with coverage) + run: just test-coverage timeout-minutes: 50 + - name: coverage summary + run: node scripts/coverage-report.mjs markdown backend backend/coverage/coverage-summary.json >> "$GITHUB_STEP_SUMMARY" + - uses: actions/upload-artifact@v4 + with: + name: coverage-backend + path: | + backend/coverage/coverage-summary.json + backend/coverage/lcov.info + backend/coverage/lcov-report + if-no-files-found: error + + # Turns the coverage artifact into an SVG badge (scripts/coverage-report.mjs, no dependencies, + # no external service) and force-pushes it — plus the raw summary — to the orphan `badges` + # branch as a single commit, with the workflow's own GITHUB_TOKEN. README.md embeds it from + # https://github.com/rocket-admin/rocketadmin/raw/badges/backend-coverage.svg. + coverage-badge: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: test + runs-on: ubuntu-latest + permissions: + contents: write + concurrency: + group: coverage-badge + cancel-in-progress: false + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: coverage-backend + path: coverage-artifact + - name: render badge + run: | + set -euo pipefail + mkdir -p badges + node scripts/coverage-report.mjs badge coverage-artifact/coverage-summary.json badges/backend-coverage.svg "backend coverage" + cp coverage-artifact/coverage-summary.json badges/backend-coverage-summary.json + pct=$(node -e "console.log(require('./badges/backend-coverage-summary.json').total.lines.pct.toFixed(1))") + { + echo "# Coverage badges" + echo + echo "Generated by \`.github/workflows/backend.yml\` (job \`coverage-badge\`) for" + echo "\`${GITHUB_SHA}\` on $(date -u +%Y-%m-%dT%H:%M:%SZ). Do not edit — every run replaces this branch." + echo + echo "| Package | Lines |" + echo "| --- | --- |" + echo "| backend | ${pct}% |" + } > badges/README.md + - name: publish to the badges branch + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + cd badges + git init -b badges + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add . + git commit -q -m "coverage badge for ${GITHUB_SHA}" + git push --force "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" badges:badges license: runs-on: ubuntu-latest steps: diff --git a/AGENTS.md b/AGENTS.md index e31e8cda0..34eaf0293 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,10 +44,29 @@ The project uses `just` for test orchestration: ```bash just test # Run all backend tests with Docker Compose just test "path/to/test.ts" # Run specific test file +just test-coverage # Same suite under c8; report copied out to backend/coverage/ (what CI runs) ``` This spins up test databases (MySQL, PostgreSQL, MSSQL, Oracle, MongoDB, DynamoDB) via `docker-compose.tst.yml`. +### Backend test coverage + +`just test-coverage` runs the same compose stack with `TEST_SCRIPT=test-all-parallel:coverage`, i.e. the +AVA suite under [c8](https://github.com/bcoe/c8) (V8 native coverage, no instrumented build). `backend/.c8rc.json` +counts every file under `backend/src/` whether loaded or not (`all: true`) except `main.ts`, `migrations/` and +`*.spec.ts`; the suite executes `dist/src/**/*.js`, so `exclude-after-remap` is set for the patterns to match the +source paths the `.js.map` files point back to (`shared-code` is outside `src/` and is not counted). The container +runs as `appuser`, which is why the `Dockerfile` pre-creates `/app/backend/coverage`. The recipe copies +`coverage-summary.json`, `lcov.info` and `lcov-report/` (per-file HTML) out of the stopped container to +`backend/coverage/` (git-ignored) — only those, because `coverage/tmp` holds ~1 GB of raw V8 dumps. + +CI (`.github/workflows/backend.yml`): the `test` job runs `just test-coverage`, writes a lines / statements / +functions / branches table to the job summary (`scripts/coverage-report.mjs markdown`) and uploads +`coverage-backend` as an artifact; on push to `main` the `coverage-badge` job renders the SVG badge at the top of +`README.md` (`scripts/coverage-report.mjs badge`) and force-pushes it to the orphan `badges` branch using the +workflow's own `GITHUB_TOKEN` — no external service or secret. No minimum coverage is enforced; a gate is +`"check-coverage": true` plus `"lines": ` in `backend/.c8rc.json`. + ### Migrations ```bash diff --git a/CLAUDE.md b/CLAUDE.md index e31e8cda0..34eaf0293 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -44,10 +44,29 @@ The project uses `just` for test orchestration: ```bash just test # Run all backend tests with Docker Compose just test "path/to/test.ts" # Run specific test file +just test-coverage # Same suite under c8; report copied out to backend/coverage/ (what CI runs) ``` This spins up test databases (MySQL, PostgreSQL, MSSQL, Oracle, MongoDB, DynamoDB) via `docker-compose.tst.yml`. +### Backend test coverage + +`just test-coverage` runs the same compose stack with `TEST_SCRIPT=test-all-parallel:coverage`, i.e. the +AVA suite under [c8](https://github.com/bcoe/c8) (V8 native coverage, no instrumented build). `backend/.c8rc.json` +counts every file under `backend/src/` whether loaded or not (`all: true`) except `main.ts`, `migrations/` and +`*.spec.ts`; the suite executes `dist/src/**/*.js`, so `exclude-after-remap` is set for the patterns to match the +source paths the `.js.map` files point back to (`shared-code` is outside `src/` and is not counted). The container +runs as `appuser`, which is why the `Dockerfile` pre-creates `/app/backend/coverage`. The recipe copies +`coverage-summary.json`, `lcov.info` and `lcov-report/` (per-file HTML) out of the stopped container to +`backend/coverage/` (git-ignored) — only those, because `coverage/tmp` holds ~1 GB of raw V8 dumps. + +CI (`.github/workflows/backend.yml`): the `test` job runs `just test-coverage`, writes a lines / statements / +functions / branches table to the job summary (`scripts/coverage-report.mjs markdown`) and uploads +`coverage-backend` as an artifact; on push to `main` the `coverage-badge` job renders the SVG badge at the top of +`README.md` (`scripts/coverage-report.mjs badge`) and force-pushes it to the orphan `badges` branch using the +workflow's own `GITHUB_TOKEN` — no external service or secret. No minimum coverage is enforced; a gate is +`"check-coverage": true` plus `"lines": ` in `backend/.c8rc.json`. + ### Migrations ```bash diff --git a/Dockerfile b/Dockerfile index cc3036468..45859676d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,6 +46,8 @@ RUN cd backend && ../node_modules/.bin/tsc && pnpm exec nest build COPY --from=front_builder /app/frontend/dist/rocketadmin/browser /var/www/html COPY frontend/nginx/default.conf /etc/nginx/sites-enabled/default RUN mkdir -p /app/backend/node_modules/.cache && chown -R appuser:appuser /app/backend/node_modules/.cache +# c8 report dir for `just test-coverage` (the test compose runs this image as appuser) +RUN mkdir -p /app/backend/coverage && chown appuser:appuser /app/backend/coverage RUN chown -R appuser:appuser /var/lib/nginx RUN chown -R appuser:appuser /var/log/nginx RUN chown -R appuser:appuser /run diff --git a/README.md b/README.md index d138ebd42..b9721312d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ ## Rocket Admin +[![backend test coverage](https://github.com/rocket-admin/rocketadmin/raw/badges/backend-coverage.svg)](https://github.com/rocket-admin/rocketadmin/actions/workflows/backend.yml) + ![720](https://github.com/rocket-admin/rocketadmin/assets/75169/31f1ac1b-ded0-40b6-984a-e50a3ddc4ee2) Create admin panel for your service with a few clicks diff --git a/backend/.c8rc.json b/backend/.c8rc.json new file mode 100644 index 000000000..ce451f26f --- /dev/null +++ b/backend/.c8rc.json @@ -0,0 +1,9 @@ +{ + "all": true, + "src": ["src"], + "include": ["src/**/*.ts"], + "exclude": ["src/main.ts", "src/migrations/**", "src/**/*.spec.ts", "src/**/*.d.ts"], + "exclude-after-remap": true, + "reporter": ["text-summary", "lcov", "json-summary"], + "reports-dir": "coverage" +} diff --git a/backend/package.json b/backend/package.json index 74e5d97d3..2055a17aa 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,6 +18,7 @@ "test": "node _run-with-timing.mjs ava test/ava-tests/non-saas-tests/* --serial", "test-all": "node _run-with-timing.mjs ava --timeout=5m", "test-all-parallel": "AVA_CONCURRENCY=8 node _run-with-timing.mjs ava --timeout=5m", + "test-all-parallel:coverage": "AVA_CONCURRENCY=8 c8 node _run-with-timing.mjs ava --timeout=5m", "test-saas": "node _run-with-timing.mjs ava test/ava-tests/saas-tests/*", "test-fast": "AVA_CONCURRENCY=6 node _run-with-timing.mjs ava --timeout=5m 'test/ava-tests/non-saas-tests/!(*oracle*|*cassandra*|*elasticsearch*).test.ts' 'test/ava-tests/saas-tests/!(*oracle*|*cassandra*|*elasticsearch*).test.ts'", "typeorm": "ts-node -r tsconfig-paths/register ../node_modules/.bin/typeorm", @@ -113,6 +114,7 @@ "@types/supertest": "^7.2.1", "@types/validator": "^13.15.10", "ava": "8.0.1", + "c8": "^12.0.0", "knip": "^6.41.0", "nock": "^15.0.1", "redis": "^6.3.0", diff --git a/docker-compose.tst.yml b/docker-compose.tst.yml index 98cf0d297..db3d6f0de 100644 --- a/docker-compose.tst.yml +++ b/docker-compose.tst.yml @@ -19,7 +19,9 @@ services: condition: service_healthy # $EXTRA_ARGS is a single ava glob (quoted so the extglob reaches ava # intact instead of being expanded by the container's dash shell). - command: ["/bin/sh", "-c", "pnpm test-all-parallel \"$EXTRA_ARGS\""] + # $TEST_SCRIPT picks the package script: `just test-coverage` sets it to + # test-all-parallel:coverage (same suite under c8, report in /app/backend/coverage). + command: ["/bin/sh", "-c", "pnpm ${TEST_SCRIPT:-test-all-parallel} \"$EXTRA_ARGS\""] develop: watch: - action: rebuild diff --git a/justfile b/justfile index 138c8805f..0fd110b02 100644 --- a/justfile +++ b/justfile @@ -3,5 +3,25 @@ # DynamoDB, ClickHouse, Elasticsearch) are excluded so their heavy containers # aren't needed. Pass an explicit glob to run a different subset locally, e.g. # just test 'test/ava-tests/non-saas-tests/*mongo*.test.ts' -test args='test/ava-tests/non-saas-tests/!(*oracle*|*cassandra*|*elasticsearch*|*mssql*|*mongo*|*redis*|*dynamodb*|*clickhouse*).test.ts': +default_tests := 'test/ava-tests/non-saas-tests/!(*oracle*|*cassandra*|*elasticsearch*|*mssql*|*mongo*|*redis*|*dynamodb*|*clickhouse*).test.ts' + +test args=default_tests: EXTRA_ARGS="{{args}}" docker compose -f docker-compose.tst.yml up --abort-on-container-exit --force-recreate --build --attach=backend --no-log-prefix + +# Coverage: TEST_SCRIPT switches the container to the package's +# test-all-parallel:coverage script (same suite under c8, backend/.c8rc.json). +# The report — coverage-summary.json, lcov.info, lcov-report/ — is copied out of +# the stopped backend container to backend/coverage/; only those items, because +# coverage/tmp (raw V8 dumps) runs to ~1 GB. The suite's exit status is kept. + +# `just test` under c8; report copied out to backend/coverage/ (what CI runs) +test-coverage args=default_tests: + #!/usr/bin/env bash + set -uo pipefail + TEST_SCRIPT=test-all-parallel:coverage EXTRA_ARGS="{{args}}" docker compose -f docker-compose.tst.yml up --abort-on-container-exit --force-recreate --build --attach=backend --no-log-prefix + status=$? + rm -rf backend/coverage && mkdir -p backend/coverage + for item in coverage-summary.json lcov.info lcov-report; do + docker compose -f docker-compose.tst.yml cp "backend:/app/backend/coverage/$item" "backend/coverage/$item" || status=1 + done + exit "$status" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94b7bf189..7ca143c77 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -292,6 +292,9 @@ importers: ava: specifier: 8.0.1 version: 8.0.1(@ava/typescript@7.0.0)(encoding@0.1.13) + c8: + specifier: ^12.0.0 + version: 12.0.0 knip: specifier: ^6.41.0 version: 6.41.0 @@ -719,6 +722,10 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@biomejs/biome@2.5.15': resolution: {integrity: sha512-WZTW4slm/pdkh92K6t/3aEN++44JD1PQ7squ7RCMLI1flHGl43DVOIGXvCjHiZgBMO1V4uYxoFNtqrIA4qGuIQ==} engines: {node: '>=14.21.3'} @@ -1002,6 +1009,10 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1855,6 +1866,9 @@ packages: '@types/inquirer@9.0.10': resolution: {integrity: sha512-vFW2WbXwO9eZpRT5GJGFJ/shgyMNnYozmnjakt9jCQSS1lvqX8pZEQMjJ9RdDPct/YxwciQ8+V8OYn9euIrZDA==} + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -2087,9 +2101,6 @@ packages: ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} @@ -2287,6 +2298,16 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + c8@12.0.0: + resolution: {integrity: sha512-4zpJvrd1nKWutnnKC2pXkFmb6iM1l+ffN//o1CzlTNwW7GSOs9a1xrLqkC48nU8oEkjmPZLPiwMsIaOvoF4Pqg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + hasBin: true + peerDependencies: + monocart-coverage-reports: ^2 + peerDependenciesMeta: + monocart-coverage-reports: + optional: true + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -2506,6 +2527,9 @@ packages: resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} engines: {node: '>=18'} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + convert-to-spaces@2.0.1: resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2897,6 +2921,10 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + flatbuffers@24.12.23: resolution: {integrity: sha512-dLVCAISd5mhls514keQzmEG6QHmUUsNuWsb4tFafIUwvvgDjXhtfAYSKOzt5SWOy+qByV5pbsDZ+Vb7HUOBEdA==} @@ -2916,6 +2944,10 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + fork-ts-checker-webpack-plugin@9.1.0: resolution: {integrity: sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==} engines: {node: '>=14.21.3'} @@ -3052,6 +3084,9 @@ packages: resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} engines: {node: '>=14'} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -3242,6 +3277,18 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + iterare@1.2.1: resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} engines: {node: '>=6'} @@ -3416,6 +3463,10 @@ packages: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -3479,6 +3530,10 @@ packages: magic-string@1.4.1: resolution: {integrity: sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==} + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -3810,10 +3865,18 @@ packages: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + p-map@7.0.4: resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} engines: {node: '>=18'} @@ -4445,6 +4508,10 @@ packages: engines: {node: '>=10'} hasBin: true + test-exclude@8.0.0: + resolution: {integrity: sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==} + engines: {node: 20 || >=22} + text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} @@ -4684,6 +4751,10 @@ packages: v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + validator@13.15.35: resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} engines: {node: '>= 0.10'} @@ -4820,6 +4891,10 @@ packages: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + yargs-parser@22.0.0: resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} @@ -4840,6 +4915,10 @@ packages: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} @@ -5353,6 +5432,8 @@ snapshots: '@babel/helper-validator-identifier@7.28.5': optional: true + '@bcoe/v8-coverage@1.0.2': {} + '@biomejs/biome@2.5.15': optionalDependencies: '@biomejs/cli-darwin-arm64': 2.5.15 @@ -5710,9 +5791,11 @@ snapshots: dependencies: minipass: 7.1.3 + '@istanbuljs/schema@0.1.6': {} + '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} @@ -5729,7 +5812,7 @@ snapshots: '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@jridgewell/trace-mapping@0.3.9': dependencies: @@ -6403,6 +6486,8 @@ snapshots: '@types/through': 0.0.33 rxjs: 7.8.2 + '@types/istanbul-lib-coverage@2.0.6': {} + '@types/json-schema@7.0.15': {} '@types/json2csv@5.0.7': @@ -6662,9 +6747,9 @@ snapshots: agent-base@7.1.4: {} - ajv-formats@2.1.1(ajv@8.18.0): + ajv-formats@2.1.1(ajv@8.20.0): optionalDependencies: - ajv: 8.18.0 + ajv: 8.20.0 ajv-formats@3.0.1(ajv@8.20.0): optionalDependencies: @@ -6675,9 +6760,9 @@ snapshots: ajv: 6.14.0 optional: true - ajv-keywords@5.1.0(ajv@8.18.0): + ajv-keywords@5.1.0(ajv@8.20.0): dependencies: - ajv: 8.18.0 + ajv: 8.20.0 fast-deep-equal: 3.1.3 ajv@6.14.0: @@ -6688,13 +6773,6 @@ snapshots: uri-js: 4.4.1 optional: true - ajv@8.18.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 @@ -6933,6 +7011,20 @@ snapshots: bytes@3.1.2: {} + c8@12.0.0: + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@istanbuljs/schema': 0.1.6 + find-up: 5.0.0 + foreground-child: 3.3.1 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + test-exclude: 8.0.0 + v8-to-istanbul: 9.3.0 + yargs: 18.2.0 + yargs-parser: 21.1.1 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -7137,6 +7229,8 @@ snapshots: content-type@2.1.0: {} + convert-source-map@2.0.0: {} + convert-to-spaces@2.0.1: {} cookie-parser@1.4.7: @@ -7543,6 +7637,11 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + flatbuffers@24.12.23: {} fn.name@1.1.0: {} @@ -7553,6 +7652,11 @@ snapshots: dependencies: is-callable: 1.2.7 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + fork-ts-checker-webpack-plugin@9.1.0(typescript@6.0.3)(webpack@5.106.0): dependencies: '@babel/code-frame': 7.29.0 @@ -7711,6 +7815,8 @@ snapshots: hpagent@1.2.0: {} + html-escaper@2.0.2: {} + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -7866,6 +7972,19 @@ snapshots: isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + iterare@1.2.1: {} jest-worker@27.5.1: @@ -8036,6 +8155,10 @@ snapshots: dependencies: p-locate: 4.1.0 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.camelcase@4.3.0: {} lodash.get@4.4.2: {} @@ -8088,6 +8211,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + make-error@1.3.6: {} marked@14.0.0: {} @@ -8427,10 +8554,18 @@ snapshots: dependencies: p-try: 2.2.0 + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + p-locate@4.1.0: dependencies: p-limit: 2.3.0 + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + p-map@7.0.4: {} p-queue@10.0.0: {} @@ -8751,9 +8886,9 @@ snapshots: schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.18.0 - ajv-formats: 2.1.1(ajv@8.18.0) - ajv-keywords: 5.1.0(ajv@8.18.0) + ajv: 8.20.0 + ajv-formats: 2.1.1(ajv@8.20.0) + ajv-keywords: 5.1.0(ajv@8.20.0) secure-json-parse@3.0.2: {} @@ -9063,6 +9198,12 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + test-exclude@8.0.0: + dependencies: + '@istanbuljs/schema': 0.1.6 + glob: 13.0.6 + minimatch: 10.2.6 + text-hex@1.0.0: {} tildify@2.0.0: {} @@ -9269,6 +9410,12 @@ snapshots: v8-compile-cache-lib@3.0.1: {} + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + validator@13.15.35: {} vary@1.1.2: {} @@ -9420,6 +9567,8 @@ snapshots: camelcase: 5.3.1 decamelize: 1.2.0 + yargs-parser@21.1.1: {} + yargs-parser@22.0.0: {} yargs@15.4.1: @@ -9456,6 +9605,8 @@ snapshots: yn@3.1.1: {} + yocto-queue@0.1.0: {} + yoctocolors@2.1.2: {} zod@4.6.7: {} diff --git a/scripts/coverage-report.mjs b/scripts/coverage-report.mjs new file mode 100755 index 000000000..9202804e5 --- /dev/null +++ b/scripts/coverage-report.mjs @@ -0,0 +1,127 @@ +#!/usr/bin/env node +// Turns a c8 `coverage-summary.json` (reporter `json-summary`) into the two things CI publishes: +// +// node scripts/coverage-report.mjs badge [label] +// → a shields-style SVG badge ("