Files
bitvid/scripts/daily-design-system-audit.mjs
T

186 lines
5.6 KiB
JavaScript

#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import { writeFileSync } from "node:fs";
const REPORT_FILE = "REMEDIATION_REPORT.md";
const CHECKS = [
{ name: "CSS", command: "npm", args: ["run", "lint:css"] },
{ name: "Hex Colors", command: "npm", args: ["run", "lint:hex"] },
{ name: "Inline Styles", command: "npm", args: ["run", "lint:inline-styles"] },
{ name: "Raw Lengths", command: "npm", args: ["run", "lint:tokens"] }, // check-design-tokens --check=tokens
{ name: "Bracket Utilities", command: "npm", args: ["run", "lint:tailwind-brackets"] }, // check-design-tokens --check=brackets
{ name: "Tailwind Palette", command: "npm", args: ["run", "lint:tailwind-colors"] }
];
const VIOLATIONS = {
"CSS": [],
"Hex Colors": [],
"Inline Styles": [],
"Raw Lengths": [],
"Bracket Utilities": [],
"Tailwind Palette": []
};
function runCheck(check) {
console.log(`Running ${check.name}...`);
const result = spawnSync(check.command, check.args, { encoding: "utf8" });
return {
success: result.status === 0,
output: (result.stdout || "") + (result.stderr || "")
};
}
function parseOutput(category, output) {
const lines = output.split("\n");
const violations = [];
if (category === "Hex Colors") {
// ./js/ui/ambientBackground.js:34: return "#000000";
const regex = /^(\.\/.*):(\d+):(.*)$/;
for (const line of lines) {
const match = line.trim().match(regex);
if (match) {
violations.push({
file: match[1],
line: match[2],
snippet: match[3].trim()
});
}
}
} else if (category === "Inline Styles") {
// js/ui/violation-test.js:2 — Direct .style usage
// document.body.style.color = "red";
// The script prints file:line - label, then snippet on next line.
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
const match = line.match(/^(.+?):(\d+)\s+—\s+(.+)$/);
if (match) {
const snippet = lines[i + 1] ? lines[i + 1].trim() : "";
violations.push({
file: match[1],
line: match[2],
snippet: snippet,
label: match[3]
});
}
}
} else if (category === "Raw Lengths" || category === "Bracket Utilities") {
// file:line → value
// snippet
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
const match = line.match(/^(.+?):(\d+)\s+→\s+(.+)$/);
if (match) {
const snippet = lines[i + 1] ? lines[i + 1].trim() : "";
violations.push({
file: match[1],
line: match[2],
snippet: snippet,
value: match[3]
});
}
}
} else if (category === "Tailwind Palette") {
// file:line → value
// No snippet? check-tailwind-colors.mjs prints: console.error(`${violation.file}:${violation.line} → ${violation.value}`);
for (const line of lines) {
const match = line.trim().match(/^(.+?):(\d+)\s+→\s+(.+)$/);
if (match) {
violations.push({
file: match[1],
line: match[2],
value: match[3],
snippet: match[3] // Use value as snippet for now
});
}
}
} else if (category === "CSS") {
// stylelint output
// css/tailwind.source.css
// 5757:3 ✖ Expected ...
// Just capture raw lines for now if we can't parse easily
// But since stylelint passed in my tests, I might not need complex parsing yet.
// If it fails, I'll just dump the output.
if (output.includes("✖") || output.includes("error")) {
violations.push({
file: "stylelint output",
line: 0,
snippet: output.trim() // Simplification
});
}
}
return violations;
}
function generateReport(violations) {
let totalViolations = 0;
let report = "# Daily Design System Audit Report\n\n";
for (const category in violations) {
totalViolations += violations[category].length;
}
if (totalViolations === 0) {
report += "Headline: ✓ No violations\n";
} else {
report += `Headline: ⚠️ ${totalViolations} violations found\n`;
}
report += "\n";
for (const category of Object.keys(violations)) {
const list = violations[category];
report += `## ${category}\n`;
report += `Total count: ${list.length}\n\n`;
if (list.length > 0) {
report += "| File | Line | Snippet |\n";
report += "|---|---|---|\n";
// Top 10
const top10 = list.slice(0, 10);
for (const v of top10) {
let snippet = v.snippet;
if (snippet && snippet.length > 50) snippet = snippet.substring(0, 50) + "...";
snippet = snippet ? "`" + snippet.replace(/`/g, "") + "`" : "";
report += `| ${v.file} | ${v.line} | ${snippet} |\n`;
}
if (list.length > 10) {
report += `... and ${list.length - 10} more.\n`;
}
}
report += "\n";
}
report += "## Next Steps\n";
report += "- [ ] Review violations\n";
report += "- [ ] Apply auto-fixes (if safe)\n";
report += "- [ ] Open remediation PR\n";
return report;
}
async function main() {
for (const check of CHECKS) {
const { success, output } = runCheck(check);
if (!success) {
const violations = parseOutput(check.name, output);
VIOLATIONS[check.name] = violations;
}
}
const report = generateReport(VIOLATIONS);
writeFileSync(REPORT_FILE, report);
console.log(`Report generated at ${REPORT_FILE}`);
const totalViolations = Object.values(VIOLATIONS).reduce((acc, list) => acc + list.length, 0);
process.exit(totalViolations > 0 ? 1 : 0);
}
main().catch(err => {
console.error(err);
process.exit(1);
});