306 lines
No EOL
9.9 KiB
JavaScript
Executable file
306 lines
No EOL
9.9 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Design System Migration Script
|
|
*
|
|
* Migrates existing theme classes to new design system classes
|
|
* Replaces hardcoded colors with semantic design system alternatives
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Comprehensive class mappings from old theme/hardcoded classes to design system
|
|
const DESIGN_SYSTEM_REPLACEMENTS = {
|
|
// Background replacements
|
|
'theme-bg': 'ds-bg',
|
|
'theme-bg-secondary': 'ds-bg-secondary',
|
|
'theme-bg-page': 'ds-bg-page',
|
|
'bg-white dark:bg-gray-900': 'ds-bg',
|
|
'bg-gray-50 dark:bg-gray-900': 'ds-bg-page',
|
|
'bg-gray-100 dark:bg-gray-800': 'ds-bg-secondary',
|
|
|
|
// Text replacements
|
|
'theme-text': 'ds-text',
|
|
'theme-text-secondary': 'ds-text-secondary',
|
|
'theme-text-muted': 'ds-text-muted',
|
|
'text-gray-900 dark:text-gray-100': 'ds-text',
|
|
'text-gray-600 dark:text-gray-400': 'ds-text-secondary',
|
|
'text-gray-500 dark:text-gray-400': 'ds-text-muted',
|
|
'text-gray-500': 'ds-text-muted',
|
|
|
|
// Button replacements
|
|
'theme-toggle': 'ds-theme-toggle',
|
|
'btn-primary': 'ds-btn-primary',
|
|
'btn-secondary': 'ds-btn-secondary',
|
|
'btn-ghost': 'ds-btn-ghost',
|
|
|
|
// Navigation replacements
|
|
'nav-link': 'ds-nav-link',
|
|
'nav-link-active': 'ds-nav-link-active',
|
|
|
|
// Card replacements
|
|
'theme-card': 'ds-card',
|
|
'card-base': 'ds-card',
|
|
'card-hover': 'ds-card-hover',
|
|
|
|
// Border replacements
|
|
'theme-border': 'ds-border',
|
|
'border-gray-200 dark:border-gray-700': 'ds-border',
|
|
'border-gray-300 dark:border-gray-600': 'ds-border',
|
|
|
|
// Typography scale replacements
|
|
'text-4xl font-semibold tracking-tight': 'ds-heading-1',
|
|
'text-3xl font-semibold': 'ds-heading-2',
|
|
'text-2xl font-semibold': 'ds-heading-3',
|
|
'text-xl font-semibold': 'ds-heading-4',
|
|
'text-lg': 'ds-body',
|
|
'text-sm': 'ds-caption',
|
|
|
|
// Hardcoded color replacements
|
|
'bg-blue-200': 'bg-ds-brand-primary/20',
|
|
'bg-blue-600': 'bg-ds-brand-primary',
|
|
'bg-green-200': 'bg-ds-success/20',
|
|
'bg-green-600': 'bg-ds-success',
|
|
'bg-purple-500': 'bg-ds-brand-secondary',
|
|
'bg-pink-500': 'bg-ds-brand-accent',
|
|
'text-blue-600 dark:text-blue-400': 'text-ds-brand-primary',
|
|
'text-green-600 dark:text-green-400': 'text-ds-success',
|
|
'text-gray-800 dark:text-gray-200': 'ds-text',
|
|
|
|
// Shadow replacements
|
|
'shadow-sm': 'ds-shadow-sm',
|
|
'shadow-md': 'ds-shadow-md',
|
|
'shadow-lg': 'ds-shadow-lg',
|
|
|
|
// Spacing replacements with container
|
|
'mx-auto max-w-4xl py-16 sm:py-24': 'ds-container-md py-ds-16 sm:py-ds-24',
|
|
'mx-auto max-w-2xl': 'ds-container-sm',
|
|
'mx-auto max-w-6xl': 'ds-container-lg',
|
|
'px-6 lg:px-8': 'ds-container',
|
|
|
|
// Grid and layout
|
|
'grid grid-cols-1 md:grid-cols-2 gap-8': 'grid grid-cols-1 md:grid-cols-2 gap-ds-8',
|
|
'space-y-2': 'space-y-ds-2',
|
|
'space-x-2': 'space-x-ds-2',
|
|
'flex space-x-2': 'flex space-x-ds-2',
|
|
'mb-4': 'mb-ds-4',
|
|
'mb-12': 'mb-ds-12',
|
|
'mt-6': 'mt-ds-6',
|
|
'mt-12': 'mt-ds-12',
|
|
'p-6': 'p-ds-6',
|
|
'p-2': 'p-ds-2',
|
|
'px-6 py-3': 'px-ds-6 py-ds-3',
|
|
'px-4 py-2': 'px-ds-4 py-ds-2',
|
|
|
|
// Rounded corners
|
|
'rounded-lg': 'ds-rounded-lg',
|
|
'rounded-full': 'ds-rounded-full',
|
|
'rounded': 'ds-rounded',
|
|
};
|
|
|
|
// Individual class replacements for when full context patterns don't match
|
|
const INDIVIDUAL_CLASS_REPLACEMENTS = {
|
|
// Hardcoded grays to design system neutrals
|
|
'text-gray-900': 'text-ds-neutral-900',
|
|
'text-gray-800': 'text-ds-neutral-800',
|
|
'text-gray-700': 'text-ds-neutral-700',
|
|
'text-gray-600': 'text-ds-neutral-600',
|
|
'text-gray-500': 'text-ds-neutral-500',
|
|
'text-gray-400': 'text-ds-neutral-400',
|
|
'text-gray-300': 'text-ds-neutral-300',
|
|
'text-gray-200': 'text-ds-neutral-200',
|
|
'text-gray-100': 'text-ds-neutral-100',
|
|
|
|
'bg-gray-900': 'bg-ds-neutral-900',
|
|
'bg-gray-800': 'bg-ds-neutral-800',
|
|
'bg-gray-700': 'bg-ds-neutral-700',
|
|
'bg-gray-600': 'bg-ds-neutral-600',
|
|
'bg-gray-500': 'bg-ds-neutral-500',
|
|
'bg-gray-400': 'bg-ds-neutral-400',
|
|
'bg-gray-300': 'bg-ds-neutral-300',
|
|
'bg-gray-200': 'bg-ds-neutral-200',
|
|
'bg-gray-100': 'bg-ds-neutral-100',
|
|
'bg-gray-50': 'bg-ds-neutral-50',
|
|
|
|
'border-gray-900': 'border-ds-neutral-900',
|
|
'border-gray-800': 'border-ds-neutral-800',
|
|
'border-gray-700': 'border-ds-neutral-700',
|
|
'border-gray-600': 'border-ds-neutral-600',
|
|
'border-gray-500': 'border-ds-neutral-500',
|
|
'border-gray-400': 'border-ds-neutral-400',
|
|
'border-gray-300': 'border-ds-neutral-300',
|
|
'border-gray-200': 'border-ds-neutral-200',
|
|
'border-gray-100': 'border-ds-neutral-100',
|
|
|
|
// Legacy theme classes to design system
|
|
'theme-text': 'ds-text',
|
|
'theme-text-secondary': 'ds-text-secondary',
|
|
'theme-text-muted': 'ds-text-muted',
|
|
'theme-bg': 'ds-bg',
|
|
'theme-bg-secondary': 'ds-bg-secondary',
|
|
'theme-bg-page': 'ds-bg-page',
|
|
'theme-border': 'ds-border',
|
|
'theme-card': 'ds-card',
|
|
'theme-toggle': 'ds-theme-toggle',
|
|
'nav-link': 'ds-nav-link',
|
|
|
|
// Brand colors
|
|
'bg-blue-500': 'bg-ds-brand-primary',
|
|
'bg-blue-600': 'bg-ds-brand-primary',
|
|
'text-blue-500': 'text-ds-brand-primary',
|
|
'text-blue-600': 'text-ds-brand-primary',
|
|
'border-blue-500': 'border-ds-brand-primary',
|
|
|
|
// Success colors
|
|
'bg-green-500': 'bg-ds-success',
|
|
'bg-green-600': 'bg-ds-success',
|
|
'text-green-500': 'text-ds-success',
|
|
'text-green-600': 'text-ds-success',
|
|
|
|
// Error colors
|
|
'bg-red-500': 'bg-ds-error',
|
|
'bg-red-600': 'bg-ds-error',
|
|
'text-red-500': 'text-ds-error',
|
|
'text-red-600': 'text-ds-error',
|
|
|
|
// Warning colors
|
|
'bg-yellow-500': 'bg-ds-warning',
|
|
'bg-amber-500': 'bg-ds-warning',
|
|
'text-yellow-500': 'text-ds-warning',
|
|
'text-amber-500': 'text-ds-warning',
|
|
};
|
|
|
|
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 migrateFileToDesignSystem(filePath, dryRun = false) {
|
|
console.log(`🎨 ${dryRun ? 'Analyzing' : 'Migrating'}: ${filePath}`);
|
|
|
|
let content = fs.readFileSync(filePath, 'utf8');
|
|
let changes = 0;
|
|
|
|
// Apply complex pattern replacements first (order matters)
|
|
const sortedReplacements = Object.entries(DESIGN_SYSTEM_REPLACEMENTS)
|
|
.sort(([a], [b]) => b.length - a.length); // Longer patterns first
|
|
|
|
for (const [pattern, replacement] of sortedReplacements) {
|
|
const regex = new RegExp(pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g');
|
|
const matches = content.match(regex);
|
|
|
|
if (matches) {
|
|
console.log(` 📝 ${matches.length}x: ${pattern} → ${replacement}`);
|
|
content = content.replace(regex, replacement);
|
|
changes += matches.length;
|
|
}
|
|
}
|
|
|
|
// Apply individual class replacements
|
|
for (const [oldClass, newClass] of Object.entries(INDIVIDUAL_CLASS_REPLACEMENTS)) {
|
|
// Use word boundaries to avoid partial matches
|
|
const regex = new RegExp(`\\\\b${oldClass.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\b`, 'g');
|
|
const matches = content.match(regex);
|
|
|
|
if (matches) {
|
|
console.log(` 🔄 ${matches.length}x: ${oldClass} → ${newClass}`);
|
|
content = content.replace(regex, newClass);
|
|
changes += matches.length;
|
|
}
|
|
}
|
|
|
|
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 design system changes needed`);
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
function migrateAllFiles(clientDir, dryRun = false) {
|
|
console.log('🎨 Migrating all files to design system...\\n');
|
|
|
|
const files = findRustFiles(clientDir);
|
|
let totalChanges = 0;
|
|
let filesChanged = 0;
|
|
|
|
for (const file of files) {
|
|
const changes = migrateFileToDesignSystem(file, dryRun);
|
|
if (changes > 0) {
|
|
totalChanges += changes;
|
|
filesChanged++;
|
|
}
|
|
console.log(''); // Add spacing between files
|
|
}
|
|
|
|
console.log(`📊 Migration Summary:`);
|
|
console.log(` Files processed: ${files.length}`);
|
|
console.log(` Files ${dryRun ? 'needing changes' : 'changed'}: ${filesChanged}`);
|
|
console.log(` Total class migrations: ${totalChanges}`);
|
|
|
|
if (totalChanges > 0) {
|
|
console.log('\\n💡 Next Steps:');
|
|
console.log('1. Build CSS: node scripts/build/build-design-system.js');
|
|
console.log('2. Test in browser: just dev');
|
|
console.log('3. Verify themes work: check light/dark mode');
|
|
console.log('4. Test responsive: check mobile/tablet/desktop');
|
|
}
|
|
|
|
return totalChanges;
|
|
}
|
|
|
|
// CLI handling
|
|
if (require.main === module) {
|
|
const args = process.argv.slice(2);
|
|
const command = args[0];
|
|
|
|
if (command === 'file') {
|
|
const filePath = args[1];
|
|
const dryRun = !args.includes('--apply');
|
|
|
|
if (!filePath) {
|
|
console.error('Usage: migrate-to-design-system.js file <path> [--apply]');
|
|
process.exit(1);
|
|
}
|
|
|
|
migrateFileToDesignSystem(filePath, dryRun);
|
|
} else if (command === 'all' || !command) {
|
|
const clientDir = path.join(__dirname, '..', 'crates', 'client');
|
|
const dryRun = !args.includes('--apply');
|
|
|
|
migrateAllFiles(clientDir, dryRun);
|
|
} else {
|
|
console.log('Usage:');
|
|
console.log(' node migrate-to-design-system.js all [--apply] # Migrate all files');
|
|
console.log(' node migrate-to-design-system.js file <path> [--apply] # Migrate single file');
|
|
console.log('');
|
|
console.log('Examples:');
|
|
console.log(' node migrate-to-design-system.js all # Dry run all files');
|
|
console.log(' node migrate-to-design-system.js all --apply # Migrate all files');
|
|
console.log(' node migrate-to-design-system.js file src/pages/Home.rs # Dry run single file');
|
|
}
|
|
}
|
|
|
|
module.exports = { migrateFileToDesignSystem, migrateAllFiles, DESIGN_SYSTEM_REPLACEMENTS }; |