mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
268 lines
8.3 KiB
JavaScript
Executable File
268 lines
8.3 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
import { spawnSync } from "node:child_process";
|
|
import { writeFileSync, readFileSync } from "node:fs";
|
|
|
|
const REPORT_FILE = "REMEDIATION_REPORT.md";
|
|
const SHOULD_FIX = process.argv.includes("--fix");
|
|
|
|
const CHECKS = [
|
|
{ name: "CSS", command: "npx", args: ["stylelint", "css/tailwind.source.css", "css/tokens.css", "css/docs.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";
|
|
// Also supports ./ prefix if present
|
|
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 ...
|
|
let currentFile = "";
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
// If line looks like a file path (ends with .css) and isn't an error line
|
|
if (trimmed.endsWith(".css") && !trimmed.includes("✖")) {
|
|
currentFile = trimmed;
|
|
} else {
|
|
const match = trimmed.match(/^(\d+):(\d+)\s+✖\s+(.+)$/);
|
|
if (match && currentFile) {
|
|
violations.push({
|
|
file: currentFile,
|
|
line: match[1],
|
|
snippet: match[3]
|
|
});
|
|
} else if (trimmed.includes("✖")) {
|
|
violations.push({
|
|
file: currentFile || "unknown",
|
|
line: 0,
|
|
snippet: trimmed
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return violations;
|
|
}
|
|
|
|
function generateReport(violations) {
|
|
let totalViolations = 0;
|
|
const date = new Date().toISOString().split('T')[0];
|
|
let report = `# Daily Design System Audit Report (${date})\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;
|
|
}
|
|
|
|
function applyAutoFixes(violations) {
|
|
console.log("Attempting to apply auto-fixes...");
|
|
let fixesApplied = 0;
|
|
|
|
const violationsByFile = {};
|
|
|
|
for (const category in violations) {
|
|
for (const v of violations[category]) {
|
|
const filePath = v.file.startsWith("./") ? v.file.slice(2) : v.file;
|
|
if (!violationsByFile[filePath]) violationsByFile[filePath] = [];
|
|
violationsByFile[filePath].push({ ...v, category });
|
|
}
|
|
}
|
|
|
|
for (const file of Object.keys(violationsByFile)) {
|
|
try {
|
|
let content = readFileSync(file, "utf8");
|
|
let originalContent = content;
|
|
|
|
for (const v of violationsByFile[file]) {
|
|
if (v.category === "Hex Colors") {
|
|
content = content.replaceAll("#000000", "var(--color-black)");
|
|
content = content.replaceAll("#ffffff", "var(--color-white)");
|
|
}
|
|
if (v.category === "Tailwind Palette") {
|
|
// Simple semantic mappings
|
|
if (v.value.includes("red-500")) {
|
|
const newValue = v.value.replace("red-500", "status-danger");
|
|
content = content.replaceAll(v.value, newValue);
|
|
}
|
|
if (v.value.includes("green-500")) {
|
|
const newValue = v.value.replace("green-500", "status-success");
|
|
content = content.replaceAll(v.value, newValue);
|
|
}
|
|
if (v.value.includes("blue-500")) {
|
|
const newValue = v.value.replace("blue-500", "status-info");
|
|
content = content.replaceAll(v.value, newValue);
|
|
}
|
|
if (v.value.includes("yellow-500")) {
|
|
const newValue = v.value.replace("yellow-500", "status-warning");
|
|
content = content.replaceAll(v.value, newValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (content !== originalContent) {
|
|
writeFileSync(file, content);
|
|
fixesApplied++;
|
|
console.log(`Fixed violations in ${file}`);
|
|
}
|
|
} catch (e) {
|
|
console.error(`Failed to fix ${file}: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
if (fixesApplied === 0) {
|
|
console.log("No auto-fixes available for current violations.");
|
|
}
|
|
return fixesApplied > 0;
|
|
}
|
|
|
|
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 totalViolations = Object.values(VIOLATIONS).reduce((acc, list) => acc + list.length, 0);
|
|
|
|
if (totalViolations > 0 && SHOULD_FIX) {
|
|
applyAutoFixes(VIOLATIONS);
|
|
}
|
|
|
|
const report = generateReport(VIOLATIONS);
|
|
writeFileSync(REPORT_FILE, report);
|
|
console.log(`Report generated at ${REPORT_FILE}`);
|
|
|
|
process.exit(totalViolations > 0 ? 1 : 0);
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|