import { readFile, writeFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; import path from "node:path"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const tokenPath = path.join(root, "src/lib/tokens.json"); const outputPath = path.join(root, "src/styles/theme.generated.css"); const source = JSON.parse(await readFile(tokenPath, "utf8")); const variableName = (key) => `--${key .replace(/([a-z0-9])([A-Z])/g, "$1-$2") .replace(/([a-zA-Z])([0-9])/g, "$1-$2") .toLowerCase()}`; function hexToHsl(hex) { const value = hex.replace("#", ""); const [r, g, b] = [0, 2, 4].map((offset) => parseInt(value.slice(offset, offset + 2), 16) / 255); const max = Math.max(r, g, b); const min = Math.min(r, g, b); const lightness = (max + min) / 2; let hue = 0; let saturation = 0; if (max !== min) { const delta = max - min; saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min); if (max === r) hue = (g - b) / delta + (g < b ? 6 : 0); if (max === g) hue = (b - r) / delta + 2; if (max === b) hue = (r - g) / delta + 4; hue /= 6; } return `${Math.round(hue * 360)} ${Math.round(saturation * 100)}% ${Math.round(lightness * 100)}%`; } function block(selector, colors) { const variables = Object.entries(colors) .map(([name, value]) => ` ${variableName(name)}: ${hexToHsl(value)};`) .join("\n"); return `${selector} {\n${variables}\n}`; } const generated = `/* Generated by scripts/generate-theme.mjs from src/lib/tokens.json. */\n${block(":root, .dark", source.color.dark)}\n\n${block(".light", source.color.light)}\n`; const check = process.argv.includes("--check"); if (check) { const current = await readFile(outputPath, "utf8").catch(() => ""); if (current !== generated) { console.error("Generated theme is stale. Run npm run tokens:generate."); process.exit(1); } } else { await writeFile(outputPath, generated); }