diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d54642..269fb9c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,15 @@ jobs: - run: npx nx run-many -t lint test build typecheck --exclude domscribe-test-fixtures + - name: Update coverage badge + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + env: + GH_TOKEN: ${{ secrets.GIST_SECRET }} + run: | + npx nx run domscribe:coverage-badge + gh api --method PATCH "gists/${{ vars.COVERAGE_GIST_ID }}" \ + -f "files[domscribe-coverage.json][content]=$(cat coverage/shields.json)" + build-registry: runs-on: ubuntu-latest needs: checks diff --git a/README.md b/README.md index 90980a6..caf2b92 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

npm version CI status - test coverage + test coverage MIT license TypeScript Node.js >= 18 diff --git a/package.json b/package.json index 2904496..5a0436d 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,13 @@ "command": "node scripts/sync-versions.mjs" } }, + "coverage-badge": { + "executor": "nx:run-commands", + "dependsOn": ["^test"], + "options": { + "command": "node scripts/aggregate-coverage.mjs" + } + }, "local-registry": { "executor": "@nx/js:verdaccio", "options": { diff --git a/packages/domscribe-core/vite.config.ts b/packages/domscribe-core/vite.config.ts index 188efa4..e3ab15f 100644 --- a/packages/domscribe-core/vite.config.ts +++ b/packages/domscribe-core/vite.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ coverage: { enabled: true, provider: 'v8', + reporter: ['text', 'json-summary'], reportsDirectory: './test-output/vitest/coverage', thresholds: { lines: 0.8, diff --git a/packages/domscribe-manifest/vite.config.ts b/packages/domscribe-manifest/vite.config.ts index 7cda7a0..32f30b9 100644 --- a/packages/domscribe-manifest/vite.config.ts +++ b/packages/domscribe-manifest/vite.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ coverage: { enabled: true, provider: 'v8', + reporter: ['text', 'json-summary'], reportsDirectory: './test-output/vitest/coverage', thresholds: { lines: 0.8, diff --git a/packages/domscribe-next/vite.config.ts b/packages/domscribe-next/vite.config.ts index 574faab..90b7650 100644 --- a/packages/domscribe-next/vite.config.ts +++ b/packages/domscribe-next/vite.config.ts @@ -23,6 +23,7 @@ export default defineConfig({ coverage: { enabled: true, provider: 'v8', + reporter: ['text', 'json-summary'], reportsDirectory: './test-output/vitest/coverage', thresholds: { lines: 0.8, diff --git a/packages/domscribe-nuxt/vite.config.ts b/packages/domscribe-nuxt/vite.config.ts index 91deaec..d92c94b 100644 --- a/packages/domscribe-nuxt/vite.config.ts +++ b/packages/domscribe-nuxt/vite.config.ts @@ -20,6 +20,7 @@ export default defineConfig({ coverage: { enabled: true, provider: 'v8', + reporter: ['text', 'json-summary'], reportsDirectory: './test-output/vitest/coverage', thresholds: { lines: 0.8, diff --git a/packages/domscribe-overlay/vite.config.ts b/packages/domscribe-overlay/vite.config.ts index a97af07..9541cf0 100644 --- a/packages/domscribe-overlay/vite.config.ts +++ b/packages/domscribe-overlay/vite.config.ts @@ -21,6 +21,7 @@ export default defineConfig({ coverage: { enabled: true, provider: 'v8', + reporter: ['text', 'json-summary'], reportsDirectory: './test-output/vitest/coverage', thresholds: { lines: 0, diff --git a/packages/domscribe-react/vite.config.ts b/packages/domscribe-react/vite.config.ts index 1a78cac..9ba1983 100644 --- a/packages/domscribe-react/vite.config.ts +++ b/packages/domscribe-react/vite.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ coverage: { enabled: true, provider: 'v8', + reporter: ['text', 'json-summary'], reportsDirectory: './test-output/vitest/coverage', thresholds: { lines: 0.8, diff --git a/packages/domscribe-relay/vite.config.ts b/packages/domscribe-relay/vite.config.ts index 67ca8f6..caf1641 100644 --- a/packages/domscribe-relay/vite.config.ts +++ b/packages/domscribe-relay/vite.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ coverage: { enabled: true, provider: 'v8', + reporter: ['text', 'json-summary'], reportsDirectory: './test-output/vitest/coverage', include: ['src/**/*.ts'], exclude: [ diff --git a/packages/domscribe-runtime/vite.config.ts b/packages/domscribe-runtime/vite.config.ts index 46ebee5..d46aa7f 100644 --- a/packages/domscribe-runtime/vite.config.ts +++ b/packages/domscribe-runtime/vite.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ coverage: { enabled: true, provider: 'v8', + reporter: ['text', 'json-summary'], reportsDirectory: './test-output/vitest/coverage', thresholds: { lines: 0.8, diff --git a/packages/domscribe-transform/vite.config.ts b/packages/domscribe-transform/vite.config.ts index 0d39fac..0c1144f 100644 --- a/packages/domscribe-transform/vite.config.ts +++ b/packages/domscribe-transform/vite.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ coverage: { enabled: true, provider: 'v8', + reporter: ['text', 'json-summary'], reportsDirectory: './test-output/vitest/coverage', thresholds: { lines: 0.8, diff --git a/packages/domscribe-vue/vite.config.ts b/packages/domscribe-vue/vite.config.ts index 804a893..84b1904 100644 --- a/packages/domscribe-vue/vite.config.ts +++ b/packages/domscribe-vue/vite.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ coverage: { enabled: true, provider: 'v8', + reporter: ['text', 'json-summary'], reportsDirectory: './test-output/vitest/coverage', thresholds: { lines: 0.8, diff --git a/scripts/aggregate-coverage.mjs b/scripts/aggregate-coverage.mjs new file mode 100644 index 0000000..7deec60 --- /dev/null +++ b/scripts/aggregate-coverage.mjs @@ -0,0 +1,89 @@ +/** + * @module aggregate-coverage + * + * Reads coverage-summary.json from each package's test-output directory, + * computes a weighted-average line coverage percentage across all packages, + * and writes a shields.io-compatible JSON endpoint to coverage/shields.json. + */ + +import { + readFileSync, + writeFileSync, + mkdirSync, + existsSync, + readdirSync, +} from 'fs'; +import { resolve, dirname, join } from 'path'; + +const ROOT = resolve(dirname(new URL(import.meta.url).pathname), '..'); + +const summaryFiles = readdirSync(join(ROOT, 'packages')) + .filter((d) => d.startsWith('domscribe-')) + .map((d) => + join('packages', d, 'test-output/vitest/coverage/coverage-summary.json'), + ) + .filter((f) => existsSync(join(ROOT, f))); + +if (summaryFiles.length === 0) { + console.error( + 'No coverage-summary.json files found. Run tests with coverage first.', + ); + process.exit(1); +} + +let totalCovered = 0; +let totalStatements = 0; + +for (const file of summaryFiles) { + const abs = resolve(ROOT, file); + const summary = JSON.parse(readFileSync(abs, 'utf8')); + const totals = summary.total; + + if (!totals?.statements) { + console.warn(`Skipping ${file} — no total.statements`); + continue; + } + + totalCovered += totals.statements.covered; + totalStatements += totals.statements.total; + + const pkg = file.split('/')[1]; + const pct = totals.statements.pct; + console.log(` ${pkg}: ${pct}%`); +} + +if (totalStatements === 0) { + console.error('No statement data found across packages.'); + process.exit(1); +} + +const percentage = ((totalCovered / totalStatements) * 100).toFixed(1); + +const color = + percentage >= 90 + ? 'brightgreen' + : percentage >= 80 + ? 'green' + : percentage >= 70 + ? 'yellowgreen' + : percentage >= 60 + ? 'yellow' + : 'red'; + +const badge = { + schemaVersion: 1, + label: 'coverage', + message: `${percentage}%`, + color, +}; + +const outDir = resolve(ROOT, 'coverage'); +if (!existsSync(outDir)) { + mkdirSync(outDir, { recursive: true }); +} + +const outPath = resolve(outDir, 'shields.json'); +writeFileSync(outPath, JSON.stringify(badge, null, 2) + '\n'); + +console.log(`\nAggregate coverage: ${percentage}% (${summaryFiles.length} packages)`); +console.log(`Badge JSON written to ${outPath}`);