veza/apps/web/scripts/codemod-typography-arbitrary.mjs

87 lines
3 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* Codemod: replace text-[10px] and text-[9px] with text-xs (design system).
* Usage: node scripts/codemod-typography-arbitrary.mjs [--dry-run] [--dir src/components]
* Excludes: avatar.tsx (exception for size xs), badge.tsx (doc only).
*/
import fs from 'fs';
import path from 'path';
const DEFAULT_DIRS = ['src/components', 'src/features'];
const IGNORE_DIRS = ['node_modules', 'dist', '.storybook', 'dist_verification'];
const EXCLUDE_FILES = ['avatar.tsx', 'badge.tsx'];
const EXTENSIONS = new Set(['.tsx', '.jsx', '.ts', '.js']);
function parseArgs() {
const args = process.argv.slice(2);
let dirs = DEFAULT_DIRS;
let dryRun = false;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--dry-run') dryRun = true;
else if (args[i] === '--dir' && args[i + 1]) dirs = [args[++i]];
}
return { dirs, dryRun };
}
function* walkFiles(dir, base = dir) {
if (!fs.existsSync(dir)) return;
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const e of entries) {
const full = path.join(dir, e.name);
const rel = path.relative(base, full);
if (e.isDirectory()) {
if (!IGNORE_DIRS.includes(e.name)) yield* walkFiles(full, base);
} else if (EXTENSIONS.has(path.extname(e.name)) && !EXCLUDE_FILES.includes(e.name)) {
yield { full, rel };
}
}
}
function transform(content) {
let out = content;
const replacements = [];
const r10 = /text-\[10px\]/g;
const r9 = /text-\[9px\]/g;
let m;
while ((m = r10.exec(content)) !== null) replacements.push({ index: m.index, from: 'text-[10px]', to: 'text-xs' });
while ((m = r9.exec(content)) !== null) replacements.push({ index: m.index, from: 'text-[9px]', to: 'text-xs' });
if (replacements.length === 0) return { content: out, count: 0 };
replacements.sort((a, b) => a.index - b.index);
let count = 0;
out = out.replace(/text-\[10px\]/g, () => { count++; return 'text-xs'; });
out = out.replace(/text-\[9px\]/g, () => { count++; return 'text-xs'; });
return { content: out, count };
}
function main() {
const { dirs, dryRun } = parseArgs();
const cwd = process.cwd();
const modified = [];
for (const dir of dirs) {
const absDir = path.isAbsolute(dir) ? dir : path.join(cwd, dir);
for (const { full, rel } of walkFiles(absDir)) {
const content = fs.readFileSync(full, 'utf8');
const { content: newContent, count } = transform(content);
if (count > 0) {
modified.push({ rel, count });
if (!dryRun) fs.writeFileSync(full, newContent, 'utf8');
}
}
}
if (dryRun) {
console.log('# [DRY RUN] Would replace text-[10px]/text-[9px] → text-xs in:\n');
} else {
console.log('# Replaced text-[10px]/text-[9px] → text-xs in:\n');
}
for (const { rel, count } of modified) {
console.log(` ${rel} (${count} replacement(s))`);
}
console.log(`\nTotal: ${modified.length} files, ${modified.reduce((s, r) => s + r.count, 0)} replacements.`);
if (dryRun && modified.length > 0) console.log('\nRun without --dry-run to apply.');
}
main();