Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
110 lines
3.4 KiB
JavaScript
110 lines
3.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Rewrites legacy pixel typography / icon sizes in shell surfaces to token utilities.
|
|
* Run: node scripts/codemod-ui-scale.mjs
|
|
*/
|
|
import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
const ROOT = new URL("../apps/web/src", import.meta.url).pathname;
|
|
|
|
const TEXT_REPLACEMENTS = [
|
|
["text-[9px]", "text-ui-caption"],
|
|
["text-[10px]", "text-ui-caption"],
|
|
["text-[11px]", "text-ui-label"],
|
|
["text-[11.5px]", "text-ui-label"],
|
|
["text-[12px]", "text-ui-body-sm"],
|
|
["text-[12.5px]", "text-ui-body-sm"],
|
|
["text-[13px]", "text-ui-body-sm"],
|
|
["text-[13.5px]", "text-ui-body"],
|
|
["text-[14px]", "text-ui-body"],
|
|
["text-[15px]", "text-ui-body"],
|
|
["text-[15.5px]", "text-ui-body"],
|
|
["text-[16px]", "text-ui-body"],
|
|
];
|
|
|
|
const SIZE_REPLACEMENTS = [
|
|
["size={9}", 'className="size-icon-sm"'],
|
|
["size={10}", 'className="size-icon-sm"'],
|
|
["size={11}", 'className="size-icon-sm"'],
|
|
["size={12}", 'className="size-icon-sm"'],
|
|
["size={13}", 'className="size-icon-sm"'],
|
|
["size={14}", 'className="size-icon-md"'],
|
|
["size={15}", 'className="size-icon-md"'],
|
|
["size={16}", 'className="size-icon-md"'],
|
|
["size={18}", 'className="size-icon-lg"'],
|
|
["size={19}", 'className="size-icon-lg"'],
|
|
];
|
|
|
|
const CONTROL_REPLACEMENTS = [
|
|
["grid h-8 w-8", "grid size-control-lg"],
|
|
["h-8 w-8 rounded", "size-control-md rounded"],
|
|
["h-8 w-8 shrink-0", "size-control-lg shrink-0"],
|
|
["h-8 w-8 place-items-center", "size-control-lg place-items-center"],
|
|
["h-7 w-7", "size-control-sm"],
|
|
];
|
|
|
|
function walk(dir, out = []) {
|
|
for (const entry of readdirSync(dir)) {
|
|
const path = join(dir, entry);
|
|
const stat = statSync(path);
|
|
if (stat.isDirectory()) {
|
|
walk(path, out);
|
|
} else if (/\.tsx?$/.test(entry) && !/\.test\.tsx?$/.test(entry)) {
|
|
out.push(path);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function mergeIconClassName(content) {
|
|
return content.replace(
|
|
/<(\w+)\s+([^>]*?)size=\{(9|10|11|12|13|14|15|16|18|19)\}([^>]*?)\/>/g,
|
|
(_match, tag, before, size, after) => {
|
|
const iconClass =
|
|
Number(size) <= 13 ? "size-icon-sm" : Number(size) <= 16 ? "size-icon-md" : "size-icon-lg";
|
|
const combined = `${before}${after}`;
|
|
const classMatch = combined.match(/className="([^"]*)"/);
|
|
if (classMatch) {
|
|
const merged = `${classMatch[1]} ${iconClass}`.trim();
|
|
const next = combined.replace(/className="[^"]*"/, `className="${merged}"`);
|
|
return `<${tag} ${next.trim()} />`;
|
|
}
|
|
return `<${tag} ${before}${after} className="${iconClass}" />`.replace(/\s+/g, " ");
|
|
},
|
|
);
|
|
}
|
|
|
|
function transform(content) {
|
|
let next = content;
|
|
for (const [from, to] of TEXT_REPLACEMENTS) {
|
|
next = next.replaceAll(from, to);
|
|
}
|
|
for (const [from, to] of CONTROL_REPLACEMENTS) {
|
|
next = next.replaceAll(from, to);
|
|
}
|
|
next = mergeIconClassName(next);
|
|
for (const [from, to] of SIZE_REPLACEMENTS) {
|
|
next = next.replaceAll(from, to);
|
|
}
|
|
next = next.replace(/className="([^"]*)"\s+className="([^"]*)"/g, (_, a, b) => {
|
|
return `className="${`${a} ${b}`.trim()}"`;
|
|
});
|
|
return next;
|
|
}
|
|
|
|
const files = walk(ROOT);
|
|
let changed = 0;
|
|
|
|
for (const file of files) {
|
|
const original = readFileSync(file, "utf8");
|
|
const updated = transform(original);
|
|
if (updated !== original) {
|
|
writeFileSync(file, updated);
|
|
changed += 1;
|
|
console.log(`updated ${file.replace(ROOT, "")}`);
|
|
}
|
|
}
|
|
|
|
console.log(`\nDone. ${changed} files updated.`);
|