forked from baron/baron-sso
251 lines
7.5 KiB
JavaScript
251 lines
7.5 KiB
JavaScript
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
|
|
const repoRoot = process.cwd();
|
|
const badgeDir = path.join(repoRoot, "docs", "badges");
|
|
const manifestPath = path.join(badgeDir, "badges.json");
|
|
|
|
const resultStyles = {
|
|
success: { message: "passing", color: "#2ea043" },
|
|
failure: { message: "failing", color: "#cf222e" },
|
|
cancelled: { message: "cancelled", color: "#bf8700" },
|
|
skipped: { message: "skipped", color: "#6e7781" },
|
|
unknown: { message: "unknown", color: "#6e7781" },
|
|
};
|
|
|
|
const badgeDefinitions = {
|
|
"dev-sha": { label: "dev", message: "unknown", color: "#0969da" },
|
|
"code-check": { label: "code check", message: "unknown", color: "#6e7781" },
|
|
biome: { label: "biome", message: "unknown", color: "#6e7781" },
|
|
"userfront-e2e-fast": {
|
|
label: "userfront e2e fast",
|
|
message: "unknown",
|
|
color: "#6e7781",
|
|
},
|
|
"userfront-e2e-full": {
|
|
label: "userfront e2e full",
|
|
message: "unknown",
|
|
color: "#6e7781",
|
|
},
|
|
"adminfront-coverage": {
|
|
label: "adminfront coverage",
|
|
message: "38.89%",
|
|
color: "#bf8700",
|
|
},
|
|
"devfront-coverage": {
|
|
label: "devfront coverage",
|
|
message: "8.87%",
|
|
color: "#cf222e",
|
|
},
|
|
"orgfront-coverage": {
|
|
label: "orgfront coverage",
|
|
message: "37.50%",
|
|
color: "#bf8700",
|
|
},
|
|
};
|
|
|
|
function normalizeResult(result) {
|
|
return resultStyles[result] ? result : "unknown";
|
|
}
|
|
|
|
function styleForResult(result) {
|
|
return resultStyles[normalizeResult(result)];
|
|
}
|
|
|
|
function colorForCoverage(percent) {
|
|
if (percent >= 80) return "#2ea043";
|
|
if (percent >= 35) return "#bf8700";
|
|
return "#cf222e";
|
|
}
|
|
|
|
function escapeXml(value) {
|
|
return String(value)
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """);
|
|
}
|
|
|
|
function textWidth(text) {
|
|
return Math.max(38, Math.ceil(String(text).length * 6.8 + 10));
|
|
}
|
|
|
|
function renderBadge({ label, message, color }) {
|
|
const labelWidth = textWidth(label);
|
|
const messageWidth = textWidth(message);
|
|
const width = labelWidth + messageWidth;
|
|
const labelCenter = labelWidth / 2;
|
|
const messageCenter = labelWidth + messageWidth / 2;
|
|
|
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="20" role="img" aria-label="${escapeXml(label)}: ${escapeXml(message)}">
|
|
<title>${escapeXml(label)}: ${escapeXml(message)}</title>
|
|
<linearGradient id="s" x2="0" y2="100%">
|
|
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
|
<stop offset="1" stop-opacity=".1"/>
|
|
</linearGradient>
|
|
<clipPath id="r"><rect width="${width}" height="20" rx="3" fill="#fff"/></clipPath>
|
|
<g clip-path="url(#r)">
|
|
<rect width="${labelWidth}" height="20" fill="#555"/>
|
|
<rect x="${labelWidth}" width="${messageWidth}" height="20" fill="${color}"/>
|
|
<rect width="${width}" height="20" fill="url(#s)"/>
|
|
</g>
|
|
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
|
|
<text x="${labelCenter}" y="15" fill="#010101" fill-opacity=".3">${escapeXml(label)}</text>
|
|
<text x="${labelCenter}" y="14">${escapeXml(label)}</text>
|
|
<text x="${messageCenter}" y="15" fill="#010101" fill-opacity=".3">${escapeXml(message)}</text>
|
|
<text x="${messageCenter}" y="14">${escapeXml(message)}</text>
|
|
</g>
|
|
</svg>
|
|
`;
|
|
}
|
|
|
|
async function readJsonIfExists(filePath) {
|
|
try {
|
|
return JSON.parse(await readFile(filePath, "utf8"));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function findCoverageSummary(directory) {
|
|
const entries = await readdir(directory, { withFileTypes: true }).catch(
|
|
() => [],
|
|
);
|
|
|
|
for (const entry of entries) {
|
|
const entryPath = path.join(directory, entry.name);
|
|
if (entry.isFile() && entry.name === "vitest-coverage-summary.json") {
|
|
return entryPath;
|
|
}
|
|
if (entry.isDirectory()) {
|
|
const found = await findCoverageSummary(entryPath);
|
|
if (found) return found;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function updateResultBadge(manifest, key, result) {
|
|
const style = styleForResult(result);
|
|
manifest.badges[key] = {
|
|
...(manifest.badges[key] ?? badgeDefinitions[key]),
|
|
message: style.message,
|
|
color: style.color,
|
|
};
|
|
}
|
|
|
|
function updateCoverageBadges(manifest, coverageSummary) {
|
|
for (const row of coverageSummary.packages ?? []) {
|
|
const key = `${row.package}-coverage`;
|
|
if (!badgeDefinitions[key]) continue;
|
|
const statements = Number(row.statements);
|
|
manifest.badges[key] = {
|
|
...(manifest.badges[key] ?? badgeDefinitions[key]),
|
|
message: `${statements.toFixed(2)}%`,
|
|
color: colorForCoverage(statements),
|
|
};
|
|
}
|
|
}
|
|
|
|
function shortSha(value) {
|
|
return String(value ?? "").trim().slice(0, 12);
|
|
}
|
|
|
|
const existingManifest = process.env.RESET_BADGES === "true"
|
|
? null
|
|
: await readJsonIfExists(manifestPath);
|
|
const sourceSha = shortSha(process.env.BADGE_SOURCE_SHA || process.env.GITHUB_SHA);
|
|
const manifest = {
|
|
schemaVersion: 1,
|
|
generatedBy: "scripts/update_code_check_badges.mjs",
|
|
updatedAt: new Date().toISOString(),
|
|
source: {
|
|
branch: process.env.BADGE_SOURCE_BRANCH || "dev",
|
|
sha: process.env.BADGE_SOURCE_SHA || process.env.GITHUB_SHA || null,
|
|
shortSha: sourceSha || null,
|
|
runId: process.env.GITHUB_RUN_ID || null,
|
|
runNumber: process.env.GITHUB_RUN_NUMBER || null,
|
|
},
|
|
badges: {
|
|
...badgeDefinitions,
|
|
...(existingManifest?.badges ?? {}),
|
|
},
|
|
};
|
|
|
|
manifest.badges["dev-sha"] = {
|
|
...badgeDefinitions["dev-sha"],
|
|
message: sourceSha || "unknown",
|
|
};
|
|
|
|
const jobResults = {
|
|
lint: process.env.LINT_RESULT,
|
|
biome: process.env.BIOME_RESULT,
|
|
backend: process.env.BACKEND_RESULT,
|
|
userfront: process.env.USERFRONT_RESULT,
|
|
userfrontE2e: process.env.USERFRONT_E2E_RESULT,
|
|
coverage: process.env.COVERAGE_RESULT,
|
|
adminfront: process.env.ADMINFRONT_RESULT,
|
|
devfront: process.env.DEVFRONT_RESULT,
|
|
orgfront: process.env.ORGFRONT_RESULT,
|
|
};
|
|
|
|
const overallResults = Object.values(jobResults).filter(Boolean);
|
|
const hasFailure = overallResults.some((result) =>
|
|
["failure", "cancelled"].includes(result),
|
|
);
|
|
const allSkipped = overallResults.length > 0 &&
|
|
overallResults.every((result) => result === "skipped");
|
|
if (process.env.BADGE_UPDATE_CODE_CHECK !== "false") {
|
|
updateResultBadge(
|
|
manifest,
|
|
"code-check",
|
|
overallResults.length === 0
|
|
? "unknown"
|
|
: hasFailure
|
|
? "failure"
|
|
: allSkipped
|
|
? "skipped"
|
|
: "success",
|
|
);
|
|
}
|
|
|
|
updateResultBadge(manifest, "biome", jobResults.biome);
|
|
|
|
const e2eWasFull = process.env.USERFRONT_E2E_FULL === "true";
|
|
if (jobResults.userfrontE2e && jobResults.userfrontE2e !== "skipped") {
|
|
updateResultBadge(
|
|
manifest,
|
|
e2eWasFull ? "userfront-e2e-full" : "userfront-e2e-fast",
|
|
jobResults.userfrontE2e,
|
|
);
|
|
}
|
|
|
|
if (jobResults.coverage === "failure" || jobResults.coverage === "cancelled") {
|
|
for (const key of [
|
|
"adminfront-coverage",
|
|
"devfront-coverage",
|
|
"orgfront-coverage",
|
|
]) {
|
|
updateResultBadge(manifest, key, "failure");
|
|
}
|
|
} else {
|
|
const coverageSummaryPath = process.env.COVERAGE_SUMMARY_PATH ||
|
|
(await findCoverageSummary(path.join(repoRoot, "badge-artifacts")));
|
|
const coverageSummary = coverageSummaryPath
|
|
? await readJsonIfExists(coverageSummaryPath)
|
|
: null;
|
|
if (coverageSummary) {
|
|
updateCoverageBadges(manifest, coverageSummary);
|
|
}
|
|
}
|
|
|
|
await mkdir(badgeDir, { recursive: true });
|
|
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
|
|
for (const [key, badge] of Object.entries(manifest.badges)) {
|
|
await writeFile(path.join(badgeDir, `${key}.svg`), renderBadge(badge));
|
|
}
|
|
|
|
console.log(`Updated ${Object.keys(manifest.badges).length} badge files.`);
|