#!/usr/bin/env python3 """ Smart Website Migration - Detect, Plan, then Migrate This script intelligently migrates existing websites by: 1. Detecting current tech stack and versions 2. Creating a detailed migration plan 3. Preserving ALL inline CSS and content exactly 4. Converting CSS frameworks (Tailwind v3 → v4, etc.) 5. Reinstalling Astro fresh 6. Adding new features without breaking existing functionality Workflow: 1. ANALYZE - Detect tech stack, versions, CSS framework 2. PLAN - Create detailed migration plan 3. BACKUP - Create full backup 4. PRESERVE - Extract inline CSS and content from each page 5. CONVERT - Convert CSS to match target tech stack 6. REBUILD - Fresh Astro install with preserved content 7. ENHANCE - Add new features (cookie consent, PDPA, etc.) 8. TEST - Verify build and all pages Usage: python3 migrate_existing_website.py \ --input "./existing-website" \ --output "./migrated-website" \ --plan-only # Just create plan, don't migrate """ import os import sys import json import shutil import re import subprocess import argparse from pathlib import Path from datetime import datetime from typing import Dict, List, Any, Optional import sys import json import shutil import re import subprocess from pathlib import Path from datetime import datetime from typing import Dict, List, Any, Optional class TechStackDetector: """Detect tech stack and versions from existing website.""" def __init__(self, website_path: Path): self.website_path = website_path self.detected = {} def detect_all(self) -> Dict[str, Any]: """Run all detection methods.""" print("šŸ” Detecting tech stack...\n") self.detect_astro_version() self.detect_node_version() self.detect_css_framework() self.detect_tailwind_version() self.detect_pages_structure() self.detect_content_collections() self.detect_integrations() self.detect_custom_css() return self.detected def detect_astro_version(self): """Detect Astro version from package.json.""" package_json = self.website_path / 'package.json' if package_json.exists(): with open(package_json) as f: package_data = json.load(f) deps = package_data.get('dependencies', {}) dev_deps = package_data.get('devDependencies', {}) astro_version = deps.get('astro') or dev_deps.get('astro') self.detected['astro'] = { 'version': astro_version or 'unknown', 'detected': True } print(f" āœ“ Astro version: {astro_version}") else: print(f" āœ— package.json not found") self.detected['astro'] = {'version': 'unknown', 'detected': False} def detect_node_version(self): """Detect required Node.js version.""" package_json = self.website_path / 'package.json' if package_json.exists(): with open(package_json) as f: package_data = json.load(f) engines = package_data.get('engines', {}) node_version = engines.get('node', '>=18.0.0') self.detected['node'] = { 'required_version': node_version, 'detected': True } print(f" āœ“ Node.js: {node_version}") def detect_css_framework(self): """Detect CSS framework (Tailwind, Bootstrap, etc.).""" package_json = self.website_path / 'package.json' css_frameworks = { 'tailwindcss': 'Tailwind CSS', 'bootstrap': 'Bootstrap', 'bulma': 'Bulma', 'foundation': 'Foundation', 'semantic-ui': 'Semantic UI', 'material-ui': 'Material UI', '@chakra-ui/core': 'Chakra UI', } detected_frameworks = [] if package_json.exists(): with open(package_json) as f: package_data = json.load(f) deps = {**package_data.get('dependencies', {}), **package_data.get('devDependencies', {})} for pkg, name in css_frameworks.items(): if pkg in deps: detected_frameworks.append({ 'name': name, 'package': pkg, 'version': deps[pkg] }) self.detected['css_framework'] = { 'frameworks': detected_frameworks, 'primary': detected_frameworks[0]['name'] if detected_frameworks else 'Custom CSS', 'detected': len(detected_frameworks) > 0 } if detected_frameworks: print(f" āœ“ CSS Framework: {detected_frameworks[0]['name']}") else: print(f" āœ“ CSS: Custom/Inline") def detect_tailwind_version(self): """Detect Tailwind CSS version.""" package_json = self.website_path / 'package.json' tailwind_config = self.website_path / 'tailwind.config.js' tailwind_config_ts = self.website_path / 'tailwind.config.ts' if package_json.exists(): with open(package_json) as f: package_data = json.load(f) deps = {**package_data.get('dependencies', {}), **package_data.get('devDependencies', {})} if 'tailwindcss' in deps: version = deps['tailwindcss'] major_version = version.replace('^', '').replace('~', '').split('.')[0] # Check for v4 features has_v4_features = False if tailwind_config.exists(): with open(tailwind_config) as f: config = f.read() # v4 uses different config format has_v4_features = '@theme' in config or 'import theme' in config self.detected['tailwind'] = { 'version': version, 'major_version': int(major_version) if major_version.isdigit() else 3, 'config_file': 'tailwind.config.js' if tailwind_config.exists() else 'tailwind.config.ts' if tailwind_config_ts.exists() else None, 'needs_upgrade': int(major_version) < 4 if major_version.isdigit() else False, 'detected': True } print(f" āœ“ Tailwind CSS v{major_version}: {'Needs upgrade to v4' if int(major_version) < 4 else 'Up to date'}") def detect_pages_structure(self): """Detect pages structure.""" pages_dir = self.website_path / 'src' / 'pages' if pages_dir.exists(): pages = list(pages_dir.glob('**/*.astro')) pages.extend(list(pages_dir.glob('**/*.md'))) pages.extend(list(pages_dir.glob('**/*.mdx'))) self.detected['pages'] = { 'count': len(pages), 'structure': 'flat' if len(list(pages_dir.glob('*.astro'))) > len(pages) / 2 else 'nested', 'has_i18n': any('/th/' in str(p) or '(th)' in str(p) for p in pages), 'detected': True } print(f" āœ“ Pages: {len(pages)} pages detected") def detect_content_collections(self): """Detect Astro Content Collections.""" content_dir = self.website_path / 'src' / 'content' content_config = self.website_path / 'src' / 'content.config.ts' collections = [] if content_dir.exists(): for subdir in content_dir.iterdir(): if subdir.is_dir() and not subdir.name.startswith('_'): collection_files = list(subdir.glob('*.md')) + list(subdir.glob('*.mdx')) if collection_files: collections.append({ 'name': subdir.name, 'file_count': len(collection_files) }) self.detected['content_collections'] = { 'collections': collections, 'has_config': content_config.exists(), 'detected': len(collections) > 0 } if collections: print(f" āœ“ Content Collections: {len(collections)} collections") def detect_integrations(self): """Detect Astro integrations.""" astro_config = self.website_path / 'astro.config.mjs' astro_config_ts = self.website_path / 'astro.config.ts' config_file = astro_config if astro_config.exists() else astro_config_ts if astro_config_ts.exists() else None integrations = [] if config_file: with open(config_file) as f: config_content = f.read() # Detect common integrations integration_patterns = { 'tailwind': 'tailwind()', 'react': 'react()', 'vue': 'vue()', 'svelte': 'svelte()', 'solid': 'solid()', 'mdx': 'mdx()', 'sitemap': 'sitemap()', 'vercel': 'vercel()', 'netlify': 'netlify()', 'node': 'node()', 'static-adapter': 'staticAdapter', } for name, pattern in integration_patterns.items(): if pattern in config_content: integrations.append(name) self.detected['integrations'] = { 'integrations': integrations, 'config_file': config_file.name if config_file else None, 'detected': len(integrations) > 0 } if integrations: print(f" āœ“ Integrations: {', '.join(integrations)}") def detect_custom_css(self): """Detect custom CSS files and inline styles.""" src_dir = self.website_path / 'src' css_files = [] inline_styles = 0 if src_dir.exists(): # Find CSS files for css_file in src_dir.glob('**/*.css'): css_files.append(str(css_file.relative_to(self.website_path))) # Count inline styles in Astro files for astro_file in src_dir.glob('**/*.astro'): with open(astro_file) as f: content = f.read() # Count style tags inline_styles += content.count('