#!/usr/bin/env node /** * Generate CSS variables from colors.mjs into globals.css * Run before npm build to ensure globals.css is in sync with colors.mjs * * Usage: node scripts/generate-css-vars.mjs */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { colors, spacing } from '../colors.mjs'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const globalsPath = path.join(__dirname, '../app/globals.css'); function flattenColorObject(obj, prefix = '') { const result = {}; for (const [key, value] of Object.entries(obj)) { const varName = prefix ? `${prefix}-${key}` : key; if (typeof value === 'object' && value !== null && !key.includes('-')) { // Nested object like primary: { DEFAULT: ..., container: ... } Object.assign(result, flattenColorObject(value, varName)); } else if (typeof value === 'string') { // Flat string value result[varName] = value; } } return result; } function generateCSSVariables() { const cssVars = flattenColorObject(colors); let css = ' :root {\n'; css += ' /* Base colors from DESIGN.md - AUTO-GENERATED from colors.mjs */\n'; // Group variables by category for readability const categories = { 'Base': ['background', 'on-background', 'foreground'], 'Surface': Object.keys(cssVars).filter(k => k.startsWith('surface')), 'Primary': Object.keys(cssVars).filter(k => k.startsWith('primary')), 'Secondary': Object.keys(cssVars).filter(k => k.startsWith('secondary')), 'Tertiary': Object.keys(cssVars).filter(k => k.startsWith('tertiary')), 'Error': Object.keys(cssVars).filter(k => k.startsWith('error')), 'Outline': Object.keys(cssVars).filter(k => k.startsWith('outline')), 'Inverse': Object.keys(cssVars).filter(k => k.startsWith('inverse')), 'Semantic': Object.keys(cssVars).filter(k => ['border', 'muted', 'info', 'success', 'warning', 'alert'].includes(k) ), }; for (const [category, keys] of Object.entries(categories)) { if (keys.length > 0) { css += `\n /* ${category} colors from DESIGN.md */\n`; for (const key of keys) { css += ` --${key}: ${cssVars[key]};\n`; } } } css += '\n /* Semantic spacing tokens */\n'; for (const [key, value] of Object.entries(spacing)) { css += ` --spacing-${key}: ${value};\n`; } css += ' }\n'; return css; } function updateGlobalsCSS() { const content = fs.readFileSync(globalsPath, 'utf-8'); // Find the start of @layer base and the :root closing brace const layerBaseStart = content.indexOf('@layer base {'); const rootStart = content.indexOf(':root {', layerBaseStart); if (layerBaseStart === -1 || rootStart === -1) { console.error('❌ Could not find @layer base or :root in globals.css'); process.exit(1); } // Find the matching closing brace for :root let braceCount = 0; let rootEnd = -1; for (let i = rootStart; i < content.length; i++) { if (content[i] === '{') braceCount++; if (content[i] === '}') { braceCount--; if (braceCount === 0) { rootEnd = i; break; } } } if (rootEnd === -1) { console.error('❌ Could not find closing brace for :root'); process.exit(1); } // Generate new CSS variables const newCSSVars = generateCSSVariables(); // Reconstruct: everything before :root + generated :root + everything after const before = content.substring(0, rootStart); const after = content.substring(rootEnd + 1); const newContent = before + newCSSVars + after; fs.writeFileSync(globalsPath, newContent, 'utf-8'); console.log('✓ Generated CSS variables in globals.css from colors.mjs'); } try { updateGlobalsCSS(); } catch (error) { console.error('❌ Error generating CSS variables:', error.message); process.exit(1); }