website-htmx-rustelo-code/scripts/migrations/migrate-theme-classes.js

202 lines
6.2 KiB
JavaScript
Raw Normal View History

2026-07-10 03:44:13 +01:00
#!/usr/bin/env node
/**
* Theme Class Migration Script
*
* This script helps identify and replace hardcoded color classes with theme-aware classes.
*/
const fs = require('fs');
const path = require('path');
// Define replacement mappings
const CLASS_REPLACEMENTS = {
// Background colors
'bg-gray-50 dark:bg-gray-900': 'theme-bg-page',
'bg-gray-50 dark:bg-gray-800': 'theme-bg-secondary',
'bg-white dark:bg-gray-900': 'theme-bg',
'bg-white dark:bg-gray-800': 'theme-bg',
'bg-gray-100 dark:bg-gray-800': 'theme-bg-secondary',
'bg-gray-200 dark:bg-gray-700': 'theme-bg-secondary',
// Text colors
'text-gray-900 dark:text-gray-100': 'theme-text',
'text-gray-900 dark:text-white': 'theme-text',
'text-gray-700 dark:text-gray-200': 'theme-text',
'text-gray-600 dark:text-gray-400': 'theme-text-secondary',
'text-gray-500 dark:text-gray-400': 'theme-text-muted',
'text-gray-500': 'theme-text-muted',
// Border colors
'border-gray-200 dark:border-gray-700': 'theme-border',
'border-gray-300 dark:border-gray-600': 'theme-border',
'border-stone-200 dark:border-gray-700': 'theme-border',
// Common patterns
'bg-gray-50 dark:bg-gray-800 rounded-lg p-6': 'theme-card',
'text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4': 'theme-text-heading',
'text-gray-600 dark:text-gray-400 mb-4': 'theme-text-body',
'text-sm text-gray-500': 'theme-text-caption',
};
// Individual class mappings (for cases where full context isn't available)
const INDIVIDUAL_REPLACEMENTS = {
'text-gray-900': 'theme-text',
'text-gray-700': 'theme-text',
'text-gray-600': 'theme-text-secondary',
'text-gray-500': 'theme-text-muted',
'text-gray-400': 'theme-text-muted',
'bg-gray-50': 'theme-bg-secondary',
'bg-gray-100': 'theme-bg-secondary',
'bg-gray-800': 'theme-bg-secondary',
'bg-gray-900': 'theme-bg-page',
'border-gray-200': 'theme-border',
'border-gray-300': 'theme-border',
};
function findRustFiles(dir) {
const files = [];
function walk(currentPath) {
const items = fs.readdirSync(currentPath);
for (const item of items) {
const fullPath = path.join(currentPath, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory() && !item.startsWith('.') && item !== 'target') {
walk(fullPath);
} else if (item.endsWith('.rs')) {
files.push(fullPath);
}
}
}
walk(dir);
return files;
}
function analyzeFile(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const issues = [];
// Find hardcoded gray/stone classes
const grayClassRegex = /(text|bg|border)-(gray|stone)-\d+/g;
const darkModeRegex = /(text|bg|border)-(gray|stone)-\d+\s+dark:(text|bg|border)-(gray|stone)-\d+/g;
let match;
while ((match = grayClassRegex.exec(content)) !== null) {
issues.push({
type: 'hardcoded-class',
class: match[0],
line: content.substring(0, match.index).split('\n').length,
suggestion: INDIVIDUAL_REPLACEMENTS[match[0]] || 'theme-*'
});
}
// Check for complex patterns
for (const [pattern, replacement] of Object.entries(CLASS_REPLACEMENTS)) {
if (content.includes(pattern)) {
issues.push({
type: 'complex-pattern',
pattern,
replacement,
line: content.substring(0, content.indexOf(pattern)).split('\n').length
});
}
}
return issues;
}
function generateReport(clientDir) {
console.log('🔍 Analyzing theme class usage...\n');
const files = findRustFiles(clientDir);
let totalIssues = 0;
for (const file of files) {
const issues = analyzeFile(file);
if (issues.length > 0) {
console.log(`📄 ${path.relative(process.cwd(), file)}:`);
for (const issue of issues) {
totalIssues++;
if (issue.type === 'hardcoded-class') {
console.log(` Line ${issue.line}: ${issue.class}${issue.suggestion}`);
} else {
console.log(` Line ${issue.line}: Complex pattern found`);
console.log(` Replace: ${issue.pattern}`);
console.log(` With: ${issue.replacement}`);
}
}
console.log('');
}
}
console.log(`📊 Summary: Found ${totalIssues} hardcoded classes to migrate`);
if (totalIssues > 0) {
console.log('\n💡 Migration Strategy:');
console.log('1. Use theme-aware classes: theme-text, theme-bg, theme-border');
console.log('2. For complex patterns, use composite classes: theme-card, theme-text-heading');
console.log('3. Test with different themes: npm run theme:build purple');
console.log('4. Verify colors change when switching themes');
}
}
function migrateSingleFile(filePath, dryRun = true) {
console.log(`🔧 ${dryRun ? 'Analyzing' : 'Migrating'}: ${filePath}`);
let content = fs.readFileSync(filePath, 'utf8');
let changes = 0;
// Apply complex pattern replacements first
for (const [pattern, replacement] of Object.entries(CLASS_REPLACEMENTS)) {
if (content.includes(pattern)) {
console.log(` Replacing: ${pattern}${replacement}`);
content = content.replace(new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), replacement);
changes++;
}
}
if (!dryRun && changes > 0) {
fs.writeFileSync(filePath, content);
console.log(`✅ Applied ${changes} changes to ${filePath}`);
} else if (changes > 0) {
console.log(`📋 Would apply ${changes} changes (use --apply to execute)`);
} else {
console.log(`✨ No changes needed`);
}
return changes;
}
// CLI handling
if (require.main === module) {
const args = process.argv.slice(2);
const command = args[0];
if (command === 'analyze' || !command) {
const clientDir = path.join(__dirname, '..', 'crates', 'client');
generateReport(clientDir);
} else if (command === 'migrate') {
const filePath = args[1];
const dryRun = !args.includes('--apply');
if (!filePath) {
console.error('Usage: migrate <file-path> [--apply]');
process.exit(1);
}
migrateSingleFile(filePath, dryRun);
} else {
console.log('Usage:');
console.log(' node migrate-theme-classes.js analyze');
console.log(' node migrate-theme-classes.js migrate <file> [--apply]');
}
}
module.exports = { generateReport, migrateSingleFile, CLASS_REPLACEMENTS };