Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env node
|
||||
// Renders a Vitest coverage-summary.json into either a shields.io badge URL
|
||||
// (default) or a sticky PR-comment markdown table (--pr-comment).
|
||||
//
|
||||
// Usage:
|
||||
// node scripts/coverage-summary.mjs # prints shields.io URL
|
||||
// node scripts/coverage-summary.mjs --pr-comment # prints markdown for PR
|
||||
// node scripts/coverage-summary.mjs --json # prints raw totals
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
const MARKER = "<!-- coverage-report -->";
|
||||
const repoRoot = path.resolve(fileURLToPath(import.meta.url), "..", "..");
|
||||
const targetsPath = path.join(repoRoot, "scripts", "coverage-targets.json");
|
||||
|
||||
export function resolveSummaryPath({ root = repoRoot, env = process.env, fsApi = fs } = {}) {
|
||||
if (env.COVERAGE_SUMMARY_PATH) {
|
||||
return path.resolve(root, env.COVERAGE_SUMMARY_PATH);
|
||||
}
|
||||
|
||||
const candidates = [
|
||||
path.join(root, "coverage-gate", "coverage-summary.json"),
|
||||
path.join(root, "coverage", "coverage-summary.json"),
|
||||
];
|
||||
|
||||
return candidates.find((candidate) => fsApi.existsSync(candidate)) ?? candidates[0];
|
||||
}
|
||||
|
||||
function loadSummary() {
|
||||
const summaryPath = resolveSummaryPath();
|
||||
if (!fs.existsSync(summaryPath)) return null;
|
||||
return JSON.parse(fs.readFileSync(summaryPath, "utf8"));
|
||||
}
|
||||
|
||||
function loadTargets() {
|
||||
if (!fs.existsSync(targetsPath)) return [];
|
||||
return JSON.parse(fs.readFileSync(targetsPath, "utf8"));
|
||||
}
|
||||
|
||||
function aggregateTotals(summary) {
|
||||
const total = summary.total ?? null;
|
||||
if (!total) return null;
|
||||
return {
|
||||
statements: total.statements?.pct ?? 0,
|
||||
branches: total.branches?.pct ?? 0,
|
||||
functions: total.functions?.pct ?? 0,
|
||||
lines: total.lines?.pct ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
function pickColor(pct) {
|
||||
if (pct >= 90) return "brightgreen";
|
||||
if (pct >= 75) return "green";
|
||||
if (pct >= 60) return "yellowgreen";
|
||||
if (pct >= 50) return "yellow";
|
||||
if (pct >= 40) return "orange";
|
||||
return "red";
|
||||
}
|
||||
|
||||
function shieldsUrl(totals) {
|
||||
if (!totals) return "https://img.shields.io/badge/coverage-unknown-lightgrey";
|
||||
const pct = totals.statements.toFixed(1);
|
||||
return `https://img.shields.io/badge/coverage-${pct}%25-${pickColor(totals.statements)}`;
|
||||
}
|
||||
|
||||
function evaluatePerPath(summary, targets) {
|
||||
const rows = [];
|
||||
for (const target of targets) {
|
||||
const filtered = Object.entries(summary).filter(([file]) => {
|
||||
if (file === "total") return false;
|
||||
const rel = path.relative(repoRoot, file);
|
||||
return target.pathPrefixes.some((prefix) => rel.startsWith(prefix));
|
||||
});
|
||||
if (filtered.length === 0) {
|
||||
rows.push({ name: target.name, statementsPct: null, branchesPct: null, target, status: "skip" });
|
||||
continue;
|
||||
}
|
||||
const totals = filtered.reduce(
|
||||
(acc, [, m]) => ({
|
||||
statements: acc.statements + m.statements.covered,
|
||||
statementsTotal: acc.statementsTotal + m.statements.total,
|
||||
branches: acc.branches + m.branches.covered,
|
||||
branchesTotal: acc.branchesTotal + m.branches.total,
|
||||
}),
|
||||
{ statements: 0, statementsTotal: 0, branches: 0, branchesTotal: 0 }
|
||||
);
|
||||
const statementsPct = totals.statementsTotal > 0 ? (totals.statements / totals.statementsTotal) * 100 : 0;
|
||||
const branchesPct = totals.branchesTotal > 0 ? (totals.branches / totals.branchesTotal) * 100 : 0;
|
||||
const passes = statementsPct >= target.statements && branchesPct >= target.branches;
|
||||
rows.push({
|
||||
name: target.name,
|
||||
statementsPct,
|
||||
branchesPct,
|
||||
target,
|
||||
status: passes ? "pass" : "fail",
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function renderPrComment(totals, rows) {
|
||||
const lines = [];
|
||||
lines.push(MARKER);
|
||||
lines.push("## Coverage Report");
|
||||
lines.push("");
|
||||
if (!totals) {
|
||||
lines.push("_coverage-summary.json not found — vitest with --coverage did not run._");
|
||||
return lines.join("\n");
|
||||
}
|
||||
lines.push("| metric | total |");
|
||||
lines.push("|---|---|");
|
||||
lines.push(`| statements | **${totals.statements.toFixed(1)}%** |`);
|
||||
lines.push(`| branches | **${totals.branches.toFixed(1)}%** |`);
|
||||
lines.push(`| functions | **${totals.functions.toFixed(1)}%** |`);
|
||||
lines.push(`| lines | **${totals.lines.toFixed(1)}%** |`);
|
||||
lines.push("");
|
||||
lines.push("### Per-path thresholds");
|
||||
lines.push("");
|
||||
lines.push("| path | statements | branches | gate | status |");
|
||||
lines.push("|---|---|---|---|---|");
|
||||
for (const row of rows) {
|
||||
const tag = row.target.criticalPath ? " (critical)" : "";
|
||||
const statements = row.statementsPct === null ? "n/a" : `${row.statementsPct.toFixed(1)}%`;
|
||||
const branches = row.branchesPct === null ? "n/a" : `${row.branchesPct.toFixed(1)}%`;
|
||||
const gate = `${row.target.statements}% / ${row.target.branches}%`;
|
||||
const icon = row.status === "pass" ? "PASS" : row.status === "fail" ? "FAIL" : "SKIP";
|
||||
lines.push(`| ${row.name}${tag} | ${statements} | ${branches} | ${gate} | ${icon} |`);
|
||||
}
|
||||
lines.push("");
|
||||
lines.push("_Bump thresholds by editing `scripts/coverage-targets.json`. See `docs/architecture/test-coverage.md`._");
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
export function main() {
|
||||
const argv = process.argv.slice(2);
|
||||
const summary = loadSummary();
|
||||
const targets = loadTargets();
|
||||
const totals = summary ? aggregateTotals(summary) : null;
|
||||
|
||||
if (argv.includes("--pr-comment")) {
|
||||
const rows = summary ? evaluatePerPath(summary, targets) : [];
|
||||
process.stdout.write(renderPrComment(totals, rows) + "\n");
|
||||
return;
|
||||
}
|
||||
if (argv.includes("--json")) {
|
||||
process.stdout.write(JSON.stringify(totals ?? {}, null, 2) + "\n");
|
||||
return;
|
||||
}
|
||||
// Default: shields.io badge URL
|
||||
process.stdout.write(shieldsUrl(totals) + "\n");
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
main();
|
||||
}
|
||||
Reference in new issue
Block a user