#!/usr/bin/env node // DSMMG token migration codemod // Renomme les références --mmg-* legacy vers la nomenclature --mmg-color-*. // Sûr et idempotent : applique les mappings dans l'ordre du plus spécifique // au plus générique (longest-match-first). import { readFile, writeFile, readdir, stat } from "node:fs/promises"; import { join, extname } from "node:path"; import { fileURLToPath } from "node:url"; import { dirname } from "node:path"; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dirname, ".."); // IMPORTANT : ordre du plus long au plus court pour éviter les collisions // (ex: --mmg-text-1 doit matcher AVANT --mmg-text-). const MAPPINGS = [ // — Texte (couleur) — longest first —————————— ["--mmg-text-inverse", "--mmg-color-text-inverse"], ["--mmg-text-1", "--mmg-color-text-primary"], ["--mmg-text-2", "--mmg-color-text-secondary"], ["--mmg-text-3", "--mmg-color-text-tertiary"], ["--mmg-text-4", "--mmg-color-text-quaternary"], // — Texte (font-size) — distincts —————————— ["--mmg-text-xs", "--mmg-font-size-xs"], ["--mmg-text-sm", "--mmg-font-size-sm"], ["--mmg-text-base", "--mmg-font-size-base"], ["--mmg-text-lg", "--mmg-font-size-lg"], ["--mmg-text-xl", "--mmg-font-size-xl"], ["--mmg-text-2xl", "--mmg-font-size-2xl"], ["--mmg-text-3xl", "--mmg-font-size-3xl"], ["--mmg-text-4xl", "--mmg-font-size-4xl"], ["--mmg-text-5xl", "--mmg-font-size-5xl"], // — Background —————————— ["--mmg-bg-page", "--mmg-color-bg-page"], ["--mmg-bg-surface", "--mmg-color-bg-surface"], ["--mmg-bg-raised", "--mmg-color-bg-raised"], ["--mmg-bg-muted", "--mmg-color-bg-muted"], ["--mmg-bg-subtle", "--mmg-color-bg-subtle"], ["--mmg-bg-overlay", "--mmg-color-bg-overlay"], // — Border —————————— ["--mmg-border-soft", "--mmg-color-border-soft"], ["--mmg-border-strong", "--mmg-color-border-strong"], ["--mmg-border", "--mmg-color-border"], // — Brand → Accent —————————— ["--mmg-brand-hover", "--mmg-color-accent-hover"], ["--mmg-brand-active", "--mmg-color-accent-active"], ["--mmg-brand-soft", "--mmg-color-accent-soft"], ["--mmg-brand-border", "--mmg-color-accent-border"], ["--mmg-brand-strong", "--mmg-color-accent-strong"], ["--mmg-brand-on", "--mmg-color-accent-on"], ["--mmg-brand", "--mmg-color-accent"], // — Semantic colors —————————— ["--mmg-success-soft", "--mmg-color-success-soft"], ["--mmg-success-border", "--mmg-color-success-border"], ["--mmg-success-strong", "--mmg-color-success-strong"], ["--mmg-success-on", "--mmg-color-success-on"], ["--mmg-success", "--mmg-color-success"], ["--mmg-warning-soft", "--mmg-color-warning-soft"], ["--mmg-warning-border", "--mmg-color-warning-border"], ["--mmg-warning-strong", "--mmg-color-warning-strong"], ["--mmg-warning-on", "--mmg-color-warning-on"], ["--mmg-warning", "--mmg-color-warning"], ["--mmg-danger-soft", "--mmg-color-danger-soft"], ["--mmg-danger-border", "--mmg-color-danger-border"], ["--mmg-danger-strong", "--mmg-color-danger-strong"], ["--mmg-danger-on", "--mmg-color-danger-on"], ["--mmg-danger", "--mmg-color-danger"], ["--mmg-info-soft", "--mmg-color-info-soft"], ["--mmg-info-border", "--mmg-color-info-border"], ["--mmg-info-strong", "--mmg-color-info-strong"], ["--mmg-info-on", "--mmg-color-info-on"], ["--mmg-info", "--mmg-color-info"], // — Artwork —————————— ["--mmg-art-major", "--mmg-color-art-major"], ["--mmg-art-minor", "--mmg-color-art-minor"], ["--mmg-art-light", "--mmg-color-art-light"], ["--mmg-art-dark", "--mmg-color-art-dark"], ["--mmg-art-accent", "--mmg-color-art-accent"], // — État système —————————— ["--mmg-state-disabled-bg", "--mmg-color-state-disabled-bg"], ["--mmg-state-disabled-text", "--mmg-color-state-disabled-text"], ["--mmg-state-disabled-border", "--mmg-color-state-disabled-border"], ["--mmg-state-selection-bg", "--mmg-color-state-selection-bg"], ["--mmg-state-selection-text", "--mmg-color-state-selection-text"], // — Line height —————————— ["--mmg-leading-tight", "--mmg-line-height-tight"], ["--mmg-leading-snug", "--mmg-line-height-snug"], ["--mmg-leading-normal", "--mmg-line-height-normal"], // — Font weight —————————— ["--mmg-weight-regular", "--mmg-font-weight-regular"], ["--mmg-weight-medium", "--mmg-font-weight-medium"], ["--mmg-weight-semi", "--mmg-font-weight-semi"], ["--mmg-weight-bold", "--mmg-font-weight-bold"], ["--mmg-weight-extra", "--mmg-font-weight-extra"], // — Shadow accent —————————— ["--mmg-shadow-brand-hover", "--mmg-shadow-accent-hover"], ["--mmg-shadow-brand-soft", "--mmg-shadow-accent-soft"], ["--mmg-shadow-brand", "--mmg-shadow-accent"], ]; // Cibles : tous les .css et .tsx hors node_modules / dist / tokens (déjà OK) const TARGET_DIRS = [ "packages/css/src/components", "packages/css/src", // pour base.css uniquement, on filtre tokens via skip "packages/react/src", "demo/src", ]; const SKIP_FILES = new Set([ // Les nouveaux tokens sont déjà à la nouvelle nomenclature. "tokens.css", "primitives.css", "semantic.css", "accent.css", "system.css", ]); const EXTS = new Set([".css", ".tsx", ".ts"]); async function* walk(dir) { const entries = await readdir(dir, { withFileTypes: true }); for (const e of entries) { if (e.name === "node_modules" || e.name === "dist") continue; const full = join(dir, e.name); if (e.isDirectory()) yield* walk(full); else yield full; } } function migrate(content) { let out = content; let count = 0; for (const [from, to] of MAPPINGS) { // Word-boundary safe : --mmg-brand ne doit PAS matcher dans --mmg-brand-soft. // Comme MAPPINGS est trié longest-first, lorsque --mmg-brand est traité, // les longs ont déjà été remplacés. Mais on ajoute un lookahead négatif // pour les caractères pouvant prolonger un identifiant CSS (-, lettre, chiffre). const re = new RegExp(escape(from) + "(?![\\w-])", "g"); const matches = out.match(re); if (matches) { count += matches.length; out = out.replace(re, to); } } return { out, count }; } function escape(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } async function main() { let totalReplacements = 0; let touched = 0; for (const rel of TARGET_DIRS) { const dir = join(ROOT, rel); try { await stat(dir); } catch { continue; } for await (const file of walk(dir)) { const ext = extname(file); if (!EXTS.has(ext)) continue; const base = file.split(/[\\/]/).pop(); if (SKIP_FILES.has(base)) continue; const content = await readFile(file, "utf8"); const { out, count } = migrate(content); if (count > 0) { await writeFile(file, out, "utf8"); touched += 1; totalReplacements += count; console.log(` ${count.toString().padStart(4)} ${file.replace(ROOT, "")}`); } } } console.log(`\n✓ ${totalReplacements} replacements across ${touched} files`); } main().catch((err) => { console.error(err); process.exit(1); });