diff --git a/skills/website-creator/MIGRATION_WORKFLOW.md b/skills/website-creator/MIGRATION_WORKFLOW.md new file mode 100644 index 0000000..895e1b1 --- /dev/null +++ b/skills/website-creator/MIGRATION_WORKFLOW.md @@ -0,0 +1,65 @@ +# šŸ”„ New Smart Migration Workflow + +**Date:** 2026-03-10 +**Status:** āœ… Safe Migration - No More Broken Websites! + +--- + +## šŸŽÆ **Problem with Old Workflow** + +The previous migration approach had these issues: +- Too aggressive - reorganized everything +- CSS broke frequently +- Deployments failed often +- Lost inline styles +- Changed URLs accidentally +- No planning phase + +--- + +## āœ… **New Smart Workflow** + +### **Phase 1: DETECT** +Detects tech stack and versions automatically. + +### **Phase 2: PLAN** +Creates detailed migration plan with risk assessment. + +### **Phase 3: PRESERVE** +Preserves ALL content exactly - inline CSS, text, routes. + +### **Phase 4: CONVERT** +Converts CSS frameworks carefully (Tailwind v3 to v4). + +### **Phase 5: REBUILD** +Fresh Astro install with preserved content. + +### **Phase 6: ENHANCE** +Adds new features (cookie consent, PDPA, etc.). + +### **Phase 7: TEST** +Comprehensive testing before deployment. + +--- + +## šŸš€ **Quick Start** + +```bash +# Step 1: Create migration plan +python3 skills/website-creator/scripts/migrate_existing_website.py \ + --input "./existing-website" \ + --output "./migrated-website" \ + --plan-only + +# Step 2: Review the plan +cat migration_plan_*.json + +# Step 3: Proceed with migration (after review) +python3 skills/website-creator/scripts/migrate_existing_website.py \ + --input "./existing-website" \ + --output "./migrated-website" +``` + +--- + +**Safe, reliable migrations - no more broken websites!** šŸŽ‰ diff --git a/skills/website-creator/scripts/migrate_existing_website.py b/skills/website-creator/scripts/migrate_existing_website.py new file mode 100644 index 0000000..1ad55d3 --- /dev/null +++ b/skills/website-creator/scripts/migrate_existing_website.py @@ -0,0 +1,552 @@ +#!/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 +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('