/** * Token generator — reads bytelyst.tokens.json, outputs 4 platform formats: * 1. CSS custom properties (tokens.css) * 2. TypeScript constants (tokens.ts) * 3. Kotlin object (MindLystTokens.kt) — for KMP shared module * 4. Swift structs (MindLystTheme.swift) — for iOS SwiftUI * * Usage: tsx scripts/generate.ts */ import { readFileSync, writeFileSync, mkdirSync } from "node:fs"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const tokensPath = resolve(__dirname, "../tokens/bytelyst.tokens.json"); const outDir = resolve(__dirname, "../generated"); mkdirSync(outDir, { recursive: true }); const tokens = JSON.parse(readFileSync(tokensPath, "utf-8")); // ── 1. CSS ─────────────────────────────────────────────────────────── function generateCSS(): string { const lines: string[] = [ "/* Auto-generated from bytelyst.tokens.json — do not edit manually */", "", ":root {", ]; // Semantic colors (dark theme as default) for (const [key, value] of Object.entries(tokens.color.semantic.dark)) { lines.push(` --ml-${camelToKebab(key)}: ${value};`); } // Typography for (const [key, value] of Object.entries(tokens.typography.fontFamily)) { lines.push(` --ml-font-${key}: ${value};`); } for (const [key, value] of Object.entries(tokens.typography.fontSize)) { lines.push(` --ml-text-${key}: ${value}px;`); } // Spacing for (const [key, value] of Object.entries(tokens.spacing)) { lines.push(` --ml-space-${key}: ${value}px;`); } // Radius for (const [key, value] of Object.entries(tokens.radius)) { lines.push(` --ml-radius-${key}: ${value}px;`); } // Elevation for (const [key, value] of Object.entries(tokens.elevation)) { lines.push(` --ml-shadow-${key}: ${value};`); } lines.push("}", ""); return lines.join("\n"); } // ── 2. TypeScript ──────────────────────────────────────────────────── function generateTS(): string { return [ "// Auto-generated from bytelyst.tokens.json — do not edit manually", "", `export const tokens = ${JSON.stringify(tokens, null, 2)} as const;`, "", "export type Tokens = typeof tokens;", "", ].join("\n"); } // ── 3. Kotlin ──────────────────────────────────────────────────────── function generateKotlin(): string { const lines: string[] = [ "// Auto-generated from bytelyst.tokens.json — do not edit manually", "package com.mindlyst.shared.theme", "", "object MindLystTokens {", "", " // Semantic colors (dark)", " object Colors {", ]; for (const [key, value] of Object.entries(tokens.color.semantic.dark)) { if (typeof value === "string" && value.startsWith("#")) { const hex = value.replace("#", ""); lines.push(` const val ${key} = 0xFF${hex.toUpperCase()}`); } } lines.push(" }", "", " // Brain gradients", " object Brains {"); for (const [name, grad] of Object.entries(tokens.color.brain) as [string, { from: string; to: string }][]) { const fromHex = grad.from.replace("#", ""); const toHex = grad.to.replace("#", ""); lines.push(` val ${name}From = 0xFF${fromHex.toUpperCase()}`); lines.push(` val ${name}To = 0xFF${toHex.toUpperCase()}`); } lines.push(" }", "", " // Spacing (8pt grid)", " object Spacing {"); for (const [key, value] of Object.entries(tokens.spacing)) { lines.push(` const val x${key} = ${value}`); } lines.push(" }", "", " // Radius", " object Radius {"); for (const [key, value] of Object.entries(tokens.radius)) { lines.push(` const val ${key} = ${value}`); } lines.push(" }", "}"); return lines.join("\n"); } // ── 4. Swift ───────────────────────────────────────────────────────── function generateSwift(): string { const lines: string[] = [ "// Auto-generated from bytelyst.tokens.json — do not edit manually", "import SwiftUI", "", "struct MindLystColors {", ]; for (const [key, value] of Object.entries(tokens.color.semantic.dark)) { if (typeof value === "string" && value.startsWith("#")) { lines.push(` static let ${key} = Color(hex: "${value}")`); } } lines.push("}", "", "struct MindLystSpacing {"); for (const [key, value] of Object.entries(tokens.spacing)) { lines.push(` static let x${key}: CGFloat = ${value}`); } lines.push("}", "", "struct MindLystRadius {"); for (const [key, value] of Object.entries(tokens.radius)) { lines.push(` static let ${key}: CGFloat = ${value}`); } lines.push("}"); return lines.join("\n"); } // ── Helpers ────────────────────────────────────────────────────────── function camelToKebab(str: string): string { return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); } // ── Write all ──────────────────────────────────────────────────────── writeFileSync(resolve(outDir, "tokens.css"), generateCSS()); writeFileSync(resolve(outDir, "tokens.ts"), generateTS()); writeFileSync(resolve(outDir, "MindLystTokens.kt"), generateKotlin()); writeFileSync(resolve(outDir, "MindLystTheme.swift"), generateSwift()); console.log("Generated 4 token files in generated/");