Files
DSMMG/scripts/migrate-tokens.mjs
Dinawo 62317f2ad7
Release / Release / open changeset PR (push) Has been cancelled
CI / Build, typecheck, test, a11y (push) Has been cancelled
chore: initial DSMMG v0.2 — refonte architecturale complète
Mise en place du Design System ManageMate Group v0.2 — refonte du
système de tokens (préfixe --mmg-color-*), 9 presets accent
user-themable validés WCAG AA, overlays Radix UI + Floating UI,
Storybook 8 + Vitest + axe-core en CI, doc Astro Starlight,
DESIGN.md (format google-labs-code) et exports tokens DTCG/CSS/
TS/Figma/Tailwind v3 et v4.

- 4 packages monorepo pnpm : @managemate/{tokens,css,react,icons}
- 62 composants React headless-first (Sheet, HoverCard, ContextMenu,
  Slider, ToggleGroup, AvatarGroup, UserCard, ProfileHeader,
  MetricCard, PricingCard, FeatureCard, Text/Display/Eyebrow/Lead…)
- Lint contraste WCAG : 37/37 paires AA, branché CI
- Toast pile Sonner-style avec ResizeObserver
- Theming user (9 presets) sans casser sémantique fixe
- Identité Synapse (rose #D12B6A) préservée

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:08:38 +02:00

192 lines
7.2 KiB
JavaScript

#!/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);
});