mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Design System Audit: Fix hex check tool and automate reporting
Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
d937f04ba3
commit
82dea1e63e
4 files changed
+249
-3
No files matched your search
@@ -0,0 +1,45 @@
|
||||
# Daily Design System Audit Report
|
||||
|
||||
Headline: ⚠️ 16 violations found
|
||||
|
||||
## CSS
|
||||
Total count: 0
|
||||
|
||||
|
||||
## Hex Colors
|
||||
Total count: 16
|
||||
|
||||
| File | Line | Snippet |
|
||||
|---|---|---|
|
||||
| ./js/themeController.js | 7 | `dark: "#0f172a",` |
|
||||
| ./js/themeController.js | 8 | `light: "#f8fafc",` |
|
||||
| ./js/ui/ambientBackground.js | 34 | `return "#000000";` |
|
||||
| ./js/ui/loginModalController.js | 1303 | `let qrLightColor = "#ffffff";` |
|
||||
| ./js/ui/loginModalController.js | 1304 | `let qrDarkColor = "#000000";` |
|
||||
| ./js/ui/components/VideoModal.js | 2168 | `return "#000000";` |
|
||||
| ./config/validate-config.js | 97 | `THEME_ACCENT_OVERRIDES.${theme}.${token} must be ...` |
|
||||
| ./config/instance-config.js | 291 | `* Operators can provide hex color strings (e.g., ...` |
|
||||
| ./config/instance-config.js | 299 | `accent: "#540011",` |
|
||||
| ./config/instance-config.js | 300 | `accentStrong: "#fe0032",` |
|
||||
... and 6 more.
|
||||
|
||||
## Inline Styles
|
||||
Total count: 0
|
||||
|
||||
|
||||
## Raw Lengths
|
||||
Total count: 0
|
||||
|
||||
|
||||
## Bracket Utilities
|
||||
Total count: 0
|
||||
|
||||
|
||||
## Tailwind Palette
|
||||
Total count: 0
|
||||
|
||||
|
||||
## Next Steps
|
||||
- [ ] Review violations
|
||||
- [ ] Apply auto-fixes (if safe)
|
||||
- [ ] Open remediation PR
|
||||
+18
-3
@@ -2,19 +2,34 @@
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const SEARCH_PATTERN = '\\b#[0-9a-fA-F]{3,8}\\b';
|
||||
// Use PCRE2 lookbehind to avoid:
|
||||
// 1. Matches preceded by a word character (e.g. `Issue#123`)
|
||||
// 2. Matches preceded by `&` (e.g. HTML entities `'`)
|
||||
const SEARCH_PATTERN = '(?<!&|[\\w])#[0-9a-fA-F]{3,8}\\b';
|
||||
|
||||
// Allow-list tokens and vector logos so brand assets can keep their baked colors.
|
||||
const IGNORED_GLOBS = [
|
||||
'!css/tokens.css',
|
||||
'!**/*.svg',
|
||||
'!**/node_modules/**',
|
||||
'!.git/**'
|
||||
'!.git/**',
|
||||
'!**/*.min.js',
|
||||
'!**/*.map',
|
||||
'!css/tailwind.generated.css',
|
||||
'!dist/**',
|
||||
'!build/**',
|
||||
'!vendor/**',
|
||||
'!js/utils/qrcode.js',
|
||||
'!**/dist/**',
|
||||
'!REMEDIATION_REPORT.md',
|
||||
'!scripts/daily-design-system-audit.mjs'
|
||||
];
|
||||
|
||||
const rgArgs = [
|
||||
'--color=never',
|
||||
'--no-heading',
|
||||
'-n'
|
||||
'-n',
|
||||
'-P' // Enable PCRE2 for lookbehind support
|
||||
];
|
||||
|
||||
for (const glob of IGNORED_GLOBS) {
|
||||
|
||||
@@ -38,6 +38,7 @@ const IGNORED_FILES = new Set([
|
||||
"js/webtorrent.min.js",
|
||||
"js/webtorrent.min.js.map",
|
||||
"scripts/check-inline-styles.mjs",
|
||||
"scripts/daily-design-system-audit.mjs",
|
||||
"sw.min.js",
|
||||
]);
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
#!/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);
|
||||
});
|
||||
Reference in new issue
Block a user