#!/usr/bin/env python3 import os import re from pathlib import Path # Mapping of old icon names to Lucide icon names icon_mapping = { # MiscIcons 'SearchIcon': 'Search', 'GlobeIcon': 'Globe', 'VerifiedIcon': 'BadgeCheck', 'ArrowLeftIcon': 'ArrowLeft', 'ArrowRightIcon': 'ArrowRight', 'ExternalLinkIcon': 'ExternalLink', 'FileTextIcon': 'FileText', 'MessageSquareIcon': 'MessageSquare', 'BriefcaseIcon': 'Briefcase', 'CarIcon': 'Car', 'ChurchIcon': 'Church', 'CoffeeIcon': 'Coffee', 'FuelIcon': 'Fuel', 'HeartIcon': 'Heart', 'HomeIcon': 'Home', 'ShoppingCartIcon': 'ShoppingCart', 'StethoscopeIcon': 'Stethoscope', 'SwimmingPoolIcon': 'Waves', 'WrenchIcon': 'Wrench', 'MapPinIcon': 'MapPin', 'PhoneIcon': 'Phone', 'PlusIcon': 'Plus', 'MailIcon': 'Mail', 'SortIcon': 'ArrowUpDown', 'DollarSignIcon': 'DollarSign', 'MenuIcon': 'Menu', 'LoginIcon': 'LogIn', 'NetworkIcon': 'Network', 'TrendingUpIcon': 'TrendingUp', 'ChevronDownIcon': 'ChevronDown', 'AlertTriangleIcon': 'AlertTriangle', 'ArrowDownIcon': 'ArrowDown', 'ArrowUpIcon': 'ArrowUp', 'BarChartIcon': 'BarChart3', 'CheckCircleIcon': 'CheckCircle', 'ClockIcon': 'Clock', 'FilterIcon': 'Filter', 'MinusIcon': 'Minus', 'TargetIcon': 'Target', 'TrendingDownIcon': 'TrendingDown', 'UsersIcon': 'Users', 'ZapIcon': 'Zap', 'AwardIcon': 'Award', 'GridIcon': 'Grid3X3', 'ListIcon': 'List', 'BeakerIcon': 'FlaskConical', 'ThermometerIcon': 'Thermometer', 'UserIcon': 'User', 'XCircleIcon': 'XCircle', 'XIcon': 'X', # SectorIcons 'Building2Icon': 'Building2', 'FactoryIcon': 'Factory', 'WarehouseIcon': 'Warehouse', # MapIcons 'ZoomInIcon': 'ZoomIn', 'ZoomOutIcon': 'ZoomOut', 'ResetViewIcon': 'RotateCw', 'HistoryIcon': 'History', # StatusIcons 'ErrorIcon': 'XCircle', # HeritageIcons 'BendIcon': 'GitBranch', 'TradeIcon': 'TrendingUp', 'QuillIcon': 'PenTool', 'RailwayIcon': 'Train', 'WarIcon': 'Swords', 'OilDerrickIcon': 'Fuel', 'MedalIcon': 'Medal', 'ArchitectureIcon': 'Landmark', 'CompassIcon': 'Compass', 'PeopleIcon': 'Users', # SymbiosisIcons 'NeedIcon': 'LogIn', 'OfferIcon': 'LogOut', # EditIcons 'EditIcon': 'Pencil', 'UploadCloudIcon': 'UploadCloud', 'EditTrashIcon': 'Trash2', 'EyeIcon': 'Eye', # ChatIcons 'ChatBubbleIcon': 'MessageSquare', 'SendIcon': 'Send', 'CloseIcon': 'X', 'ChatTrashIcon': 'Trash2', 'CopyIcon': 'Copy', 'CheckIcon': 'Check', 'PaperclipIcon': 'Paperclip', 'MicIcon': 'Mic', # StepsIcons 'ProfileIcon': 'User', 'SearchIconSteps': 'UserSearch', 'HandshakeIcon': 'Handshake', # ThemeIcons 'SunIcon': 'Sun', 'MoonIcon': 'Moon', } def find_files_with_icons(): files = [] for root, dirs, filenames in os.walk('.'): # Skip node_modules and other irrelevant directories dirs[:] = [d for d in dirs if not d.startswith('.') and d != 'node_modules'] for filename in filenames: if filename.endswith(('.tsx', '.ts')) and not filename.endswith('.d.ts'): filepath = os.path.join(root, filename) try: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() if '@/components/icons/' in content: files.append(filepath) except: pass return files def analyze_file(filepath): with open(filepath, 'r', encoding='utf-8') as f: content = f.read() changes = [] # Find icon imports import_pattern = r'import\s*\{\s*([^}]+)\s*\}\s*from\s*[\'\"`][^\'\"`]*@\/components\/icons\/[^\'\"`]*[\'\"`];?' import_matches = re.findall(import_pattern, content) if import_matches: lucide_icons = set() for match in import_matches: icons = [icon.strip() for icon in match.split(',') if icon.strip()] for icon in icons: if icon in icon_mapping: lucide_icons.add(icon_mapping[icon]) if lucide_icons: changes.append(f' Import: {sorted(lucide_icons)} from lucide-react') # Find icon usage usage_pattern = r'<(\w+Icon)\s*([^>]*)\/>' usage_matches = re.findall(usage_pattern, content) icon_usages = [] for icon_name, props in usage_matches: if icon_name in icon_mapping: icon_usages.append(f'{icon_name} -> {icon_mapping[icon_name]}') if icon_usages: changes.append(f' Usage: {icon_usages}') return changes def apply_changes(filepath): with open(filepath, 'r', encoding='utf-8') as f: content = f.read() original_content = content # Replace icon imports import_pattern = r'import\s*\{\s*([^}]+)\s*\}\s*from\s*[\'\"`][^\'\"`]*@\/components\/icons\/[^\'\"`]*[\'\"`];?' def replace_import(match): icons_part = match.group(1) icons = [icon.strip() for icon in icons_part.split(',') if icon.strip()] lucide_icons = [icon_mapping[icon] for icon in icons if icon in icon_mapping] if lucide_icons: return f"import {{ {', '.join(sorted(set(lucide_icons)))} }} from 'lucide-react';" return match content = re.sub(import_pattern, replace_import, content) # Replace icon usage - convert to className based approach def replace_icon_usage(content): # Find all icon usages pattern = r'<(\w+Icon)(\s[^>]*)?/>' def replacer(match): icon_name = match.group(1) props = match.group(2) or '' if icon_name in icon_mapping: lucide_name = icon_mapping[icon_name] # Extract size and variant, default to 'sm' and 'default' size = 'sm' variant = 'default' # Simple prop extraction size_match = re.search(r'size=["\'](\w+)["\']', props) if size_match: size = size_match.group(1) variant_match = re.search(r'variant=["\'](\w+)["\']', props) if variant_match: variant = variant_match.group(1) # Size mapping size_classes = { 'xs': 'h-3 w-3', 'sm': 'h-4 w-4', 'md': 'h-5 w-5', 'lg': 'h-6 w-6', 'xl': 'h-8 w-8', '2xl': 'h-10 w-10', } # Variant mapping variant_classes = { 'default': 'text-current', 'muted': 'text-muted-foreground', 'primary': 'text-primary', 'destructive': 'text-destructive', 'success': 'text-green-600', 'warning': 'text-yellow-600', } class_name = f"{size_classes.get(size, size_classes['sm'])} {variant_classes.get(variant, variant_classes['default'])}" # Remove size and variant from props, keep other props other_props = re.sub(r'\s*size=["\']\w+["\']', '', props) other_props = re.sub(r'\s*variant=["\']\w+["\']', '', other_props).strip() if other_props: return f'<{lucide_name} className="{class_name}" {other_props} />' else: return f'<{lucide_name} className="{class_name}" />' return match.group(0) return re.sub(pattern, replacer, content) content = replace_icon_usage(content) if content != original_content: with open(filepath, 'w', encoding='utf-8') as f: f.write(content) print(f'Updated: {filepath}') return True return False def main(): import sys if len(sys.argv) > 1 and sys.argv[1] == '--apply': print('=== APPLYING CHANGES ===') files = find_files_with_icons() updated_count = 0 for filepath in sorted(files): if apply_changes(filepath): updated_count += 1 print(f'\n=== SUMMARY ===') print(f'Updated {updated_count} out of {len(files)} files') else: print('=== DRY RUN: Files that would be changed ===') files = find_files_with_icons() for filepath in sorted(files): print(f'\n{filepath}:') changes = analyze_file(filepath) for change in changes: print(change) print(f'\n=== SUMMARY ===') print(f'Found {len(files)} files with icon imports that need updating') print('Run with --apply to actually make the changes') if __name__ == '__main__': main()