/** * Clean up overrides.css: * 1. Remove BOM character * 2. Remove `html body #root ` specificity prefix from selectors * 3. Remove `html body ` specificity prefix * 4. Deduplicate doubled class selectors: .foo.foo → .foo * 5. Deduplicate doubled attribute selectors: [attr="val"][attr="val"] → [attr="val"] * 6. Remove all `!important` declarations * 7. Compress multiple consecutive blank lines */ import { readFileSync, writeFileSync } from "node:fs"; const filePath = process.argv[2]; if (!filePath) { console.error("Usage: node cleanup-css-overrides.mjs "); process.exit(1); } let content = readFileSync(filePath, "utf-8"); // 0. Strip BOM content = content.replace(/^\uFEFF/, ""); // 1. Remove `html body #root ` prefix (with space before the next selector) content = content.replace(/^(\s*)html\s+body\s+#root\s+/gm, "$1"); // 2. Remove `html body ` prefix (remaining ones without #root) content = content.replace(/^(\s*)html\s+body\s+/gm, "$1"); // 3. Deduplicate doubled class selectors: .foo.foo → .foo // Must not match across word boundaries incorrectly content = content.replace(/\.([\w-]+)(\.\1)\b/g, ".$1"); // 4. Deduplicate doubled attribute selectors: [data-tool="clone"][data-tool="clone"] → [data-tool="clone"] // Use known patterns const attrDedup = [ [/\[data-tool="clone"\]\[data-tool="clone"\]/g, '[data-tool="clone"]'], [/\[data-tool="kit"\]\[data-tool="kit"\]/g, '[data-tool="kit"]'], [/\[data-tool="video"\]\[data-tool="video"\]/g, '[data-tool="video"]'], ]; for (const [pat, repl] of attrDedup) { content = content.replace(pat, repl); } // 4b. Generic dedup for any other doubled attribute patterns that slipped through // Match [name] or [name="val"] followed by an exact copy content = content.replace(/\[(\w[\w-]*(?:="[^"]*")?)\]\1/g, "[$1]"); // 5. Remove ` !important` — strip the space and the keyword, keeping the semicolon or newline content = content.replace(/\s*!important/g, ""); // 6. Fix artifacts: // - " ; " or " ;; " → "; " content = content.replace(/;\s*;/g, ";"); // - Lone semicolon before closing brace: ";\n}" → "\n}" (just remove trailing ;) content = content.replace(/;(\s*\})/g, "$1"); // - But keep "; " inside rule blocks — only clean up empty declarations // 7. Don't collapse " ;}" — we already handled this above // If after removing !important we have style: value; } → keep it as is // 8. Compress multiple blank lines (>2 consecutive newlines) to at most one content = content.replace(/\n{3,}/g, "\n\n"); // 9. Remove trailing whitespace on lines content = content.replace(/[ \t]+$/gm, ""); // 10. Ensure single trailing newline content = content.trimEnd() + "\n"; writeFileSync(filePath, content, "utf-8"); // Stats const importantCount = (content.match(/!important/g) || []).length; const lines = content.split("\n").length; console.log(`Processed: ${filePath}`); console.log(` Lines: ${lines}`); console.log(` !important remaining: ${importantCount}`);