#!/usr/bin/env node /** * @file One-off codemod: rewrites raw `emerald-*`/`red-*`/`amber-*` Tailwind * utilities across client/src to the shared `status-success`/`status-danger`/ * `status-warning` CSS-variable tokens (see `src/index.css`), so every * badge/button/component that means "success"/"danger"/"warning" uses the * SAME shade per theme instead of each usage picking its own. The * categorical/decorative hue palette (violet, indigo, cyan, teal, sky, rose, * pink, orange, yellow, and `blue` — which doubles as both "info" and a * categorical role color in places like message bubbles) is deliberately * left alone: those distinguish between different *kinds* of thing, not a * status, and collapsing them would erase that distinction. * @author Nguyễn Ngọc Trí Vĩ */ import { readdirSync, readFileSync, writeFileSync, statSync } from "node:fs"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; const COLOR_TO_STATUS = { emerald: "status-success", red: "status-danger", amber: "status-warning", }; const PROPS = ["text", "bg", "border", "ring", "from", "to", "via", "fill", "placeholder"]; function rewrite(text) { let out = text; for (const prop of PROPS) { for (const [color, token] of Object.entries(COLOR_TO_STATUS)) { // Shade number is dropped entirely — the token carries its own // per-theme value, so `emerald-400`, `emerald-500`, `emerald-600` all // collapse onto the one `status-success` (that collapse IS the fix: // no more per-usage shade picking). Opacity suffix (`/10`, `/60`) is // preserved verbatim. const re = new RegExp(`\\b${prop}-${color}-[0-9]+(\\/\\[[0-9.]+\\]|\\/[0-9]+)?`, "g"); out = out.replace(re, (_m, opacity) => `${prop}-${token}${opacity || ""}`); } } return out; } function walk(dir, files = []) { for (const entry of readdirSync(dir)) { const full = join(dir, entry); const st = statSync(full); if (st.isDirectory()) { if (entry === "node_modules") continue; walk(full, files); } else if (entry.endsWith(".tsx")) { files.push(full); } } return files; } const scriptDir = fileURLToPath(new URL(".", import.meta.url)); const root = join(scriptDir, "..", "client", "src"); const files = walk(root); let changed = 0; for (const file of files) { const before = readFileSync(file, "utf8"); const after = rewrite(before); if (after !== before) { writeFileSync(file, after); changed++; } } console.log(`rewrote ${changed} file(s)`);