turash/bugulma/frontend/migrate_icons.py
Damir Mukimov 6347f42e20
Consolidate repositories: Remove nested frontend .git and merge into main repository
- Remove nested git repository from bugulma/frontend/.git
- Add all frontend files to main repository tracking
- Convert from separate frontend/backend repos to unified monorepo
- Preserve all frontend code and development history as tracked files
- Eliminate nested repository complexity for simpler development workflow

This creates a proper monorepo structure with frontend and backend
coexisting in the same repository for easier development and deployment.
2025-11-25 06:02:57 +01:00

284 lines
8.7 KiB
Python

#!/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()