#!/usr/bin/env node /** * @managemate/tokens — build pipeline * * Source de vérité unique : src/tokens.json (format W3C DTCG). * Génère : * - dist/tokens.css : CSS custom properties --mmg-color-* * - dist/tokens.js / .d.ts: objet JS/TS pour consommation programmatique * - dist/tokens.cjs : CommonJS * - dist/figma-tokens.json: format Tokens Studio pour sync Figma */ import StyleDictionary from "style-dictionary"; import { mkdir, writeFile, readFile } from "node:fs/promises"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { existsSync } from "node:fs"; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = __dirname; const DIST = join(ROOT, "dist"); const SOURCE = join(ROOT, "src", "tokens.json"); if (!existsSync(DIST)) await mkdir(DIST, { recursive: true }); // — Style Dictionary config ———————————————————————————————————— const sd = new StyleDictionary({ source: [SOURCE], log: { warnings: "warn", verbosity: "default" }, platforms: { css: { transformGroup: "css", buildPath: "dist/", files: [ { destination: "tokens.css", format: "css/variables", options: { outputReferences: false, selector: ":root", }, }, ], prefix: "mmg", }, js: { transformGroup: "js", buildPath: "dist/", files: [ { destination: "tokens.js", format: "javascript/esm", }, { destination: "tokens.cjs", format: "javascript/module", }, { destination: "tokens.d.ts", format: "typescript/es6-declarations", }, ], prefix: "mmg", }, }, }); await sd.cleanAllPlatforms(); await sd.buildAllPlatforms(); // — Figma Tokens Studio export ———————————————————————————————————— // Plugin Tokens Studio attend un format proche de DTCG mais avec une // structure aplatie : { "color/neutral/50": { "value": "...", "type": "color" } } const dtcg = JSON.parse(await readFile(SOURCE, "utf8")); function flatten(obj, prefix = "") { const out = {}; for (const [k, v] of Object.entries(obj)) { if (k.startsWith("$")) continue; const path = prefix ? `${prefix}/${k}` : k; if (v && typeof v === "object" && "$value" in v) { out[path] = { value: v.$value, type: v.$type }; } else if (v && typeof v === "object") { Object.assign(out, flatten(v, path)); } } return out; } const figmaTokens = { global: flatten(dtcg), $themes: [], $metadata: { tokenSetOrder: ["global"], generatedAt: new Date().toISOString(), generatedBy: "@managemate/tokens build.mjs", }, }; await writeFile(join(DIST, "figma-tokens.json"), JSON.stringify(figmaTokens, null, 2)); // — Tailwind v3 export (theme.extend JSON) ———————————————————————— function buildTailwindV3() { const colors = {}; for (const [name, ramp] of Object.entries(dtcg.color ?? {})) { if (!ramp || typeof ramp !== "object") continue; const shades = {}; for (const [shade, val] of Object.entries(ramp)) { if (val && typeof val === "object" && "$value" in val) { shades[shade] = val.$value; } } if (Object.keys(shades).length > 0) colors[name] = shades; } const spacing = {}; for (const [k, v] of Object.entries(dtcg.spacing ?? {})) { if (v && typeof v === "object" && "$value" in v) { spacing[`mmg-${k}`] = v.$value; } } const radius = {}; for (const [k, v] of Object.entries(dtcg.radius ?? {})) { if (v && typeof v === "object" && "$value" in v) { radius[`mmg-${k}`] = v.$value; } } const fontSize = {}; for (const [k, v] of Object.entries(dtcg.fontSize ?? {})) { if (v && typeof v === "object" && "$value" in v) { fontSize[`mmg-${k}`] = v.$value; } } return { theme: { extend: { colors: { mmg: colors }, spacing, borderRadius: radius, fontSize, }, }, }; } await writeFile( join(DIST, "tailwind.config.json"), JSON.stringify(buildTailwindV3(), null, 2), ); // — Tailwind v4 export (@theme CSS block) ———————————————————————— function buildTailwindV4() { const lines = ["/* DSMMG tokens — Tailwind v4 @theme block. */", "@theme {"]; // Colors for (const [name, ramp] of Object.entries(dtcg.color ?? {})) { if (!ramp || typeof ramp !== "object") continue; for (const [shade, val] of Object.entries(ramp)) { if (val && typeof val === "object" && "$value" in val) { lines.push(` --color-mmg-${name}-${shade}: ${val.$value};`); } } } // Spacing for (const [k, v] of Object.entries(dtcg.spacing ?? {})) { if (v && typeof v === "object" && "$value" in v) { lines.push(` --spacing-mmg-${k}: ${v.$value};`); } } // Radius for (const [k, v] of Object.entries(dtcg.radius ?? {})) { if (v && typeof v === "object" && "$value" in v) { lines.push(` --radius-mmg-${k}: ${v.$value};`); } } // Font sizes for (const [k, v] of Object.entries(dtcg.fontSize ?? {})) { if (v && typeof v === "object" && "$value" in v) { lines.push(` --text-mmg-${k}: ${v.$value};`); } } // Durations for (const [k, v] of Object.entries(dtcg.duration ?? {})) { if (v && typeof v === "object" && "$value" in v) { lines.push(` --animate-duration-mmg-${k}: ${v.$value};`); } } lines.push("}"); return lines.join("\n") + "\n"; } await writeFile(join(DIST, "tailwind.css"), buildTailwindV4()); console.log(`✓ Tokens built → ${DIST}`); console.log(` - tokens.css (CSS custom properties)`); console.log(` - tokens.{js,cjs,d.ts} (ESM / CJS / TypeScript)`); console.log(` - figma-tokens.json (Tokens Studio for Figma sync)`); console.log(` - tailwind.config.json (Tailwind v3 theme.extend)`); console.log(` - tailwind.css (Tailwind v4 @theme block)`);