import { readFileSync, writeFileSync } from 'fs'; import { dirname, join, resolve } from 'path'; import { fileURLToPath } from 'url'; const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const sourcePath = join(root, 'public', 'design-system', 'tokens', 'source.json'); const cssPath = join(root, 'public', 'design-system', 'tokens.css'); const contractPath = join(root, 'public', 'design-system', 'theme-contract.json'); const source = JSON.parse(readFileSync(sourcePath, 'utf8')); const failures = []; if (source.schemaVersion !== 1) failures.push('schemaVersion muss 1 sein'); if (!/^\d+\.\d+\.\d+$/.test(String(source.contractVersion || ''))) failures.push('contractVersion ist ungültig'); if (!Array.isArray(source.themes) || !source.themes.includes('light') || !source.themes.includes('dark')) failures.push('light und dark müssen definiert sein'); const cssVars = new Set(); for (const [name, token] of Object.entries(source.tokens || {})) { if (!/^[a-z][A-Za-z0-9]*(?:\.[a-z][A-Za-z0-9]*)+$/.test(name)) failures.push(`Ungültiger Tokenname: ${name}`); if (!/^--vd-[a-z0-9-]+$/.test(String(token.css || ''))) failures.push(`Ungültige CSS-Variable: ${name}`); if (cssVars.has(token.css)) failures.push(`Doppelte CSS-Variable: ${token.css}`); cssVars.add(token.css); for (const theme of source.themes) if (typeof token[theme] !== 'string' || !token[theme].trim()) failures.push(`${name} fehlt in Theme ${theme}`); } if (failures.length) { console.error(`Design-Token-Generierung fehlgeschlagen (${failures.length}):`); failures.forEach(item => console.error(`- ${item}`)); process.exit(1); } function block(selector, theme) { const lines = Object.values(source.tokens).map(token => ` ${token.css}: ${token[theme]};`); return `${selector} {\n${lines.join('\n')}\n}`; } const css = `/* Automatisch generiert aus design-system/tokens/source.json. Nicht direkt bearbeiten. */\n` + `/* Contract ${source.contractVersion} */\n` + `${block(':root', 'light')}\n\n${block('[data-theme="dark"]', 'dark')}\n\n${block('[data-theme="contrast"]', 'contrast')}\n\n` + `@media (prefers-reduced-motion: reduce) {\n :root { --vd-motion-fast: 0ms; --vd-motion-normal: 0ms; }\n}\n`; writeFileSync(cssPath, css, 'utf8'); const contract = { schemaVersion: source.schemaVersion, contractVersion: source.contractVersion, themes: source.themes, tokens: Object.fromEntries(Object.entries(source.tokens).map(([name, token]) => [name, { css: token.css, type: token.type, customizable: Boolean(token.customizable), label: token.label || name, group: token.group || name.split('.')[0], ...(token.min !== undefined ? { min: token.min } : {}), ...(token.max !== undefined ? { max: token.max } : {}), }])), }; writeFileSync(contractPath, `${JSON.stringify(contract, null, 2)}\n`, 'utf8'); console.log(`Design Tokens erzeugt: ${Object.keys(source.tokens).length} Tokens, ${source.themes.length} Themes.`);