87 lines
3 KiB
JavaScript
87 lines
3 KiB
JavaScript
|
|
#!/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();
|