Coverage for src / moai_adk / core / migration / version_migrator.py: 15.18%
112 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
1"""
2Main version migration orchestrator for MoAI-ADK
4Coordinates version detection, backup creation, file migration,
5and cleanup processes for automatic project upgrades.
6"""
8import logging
9from pathlib import Path
10from typing import Dict
12from .backup_manager import BackupManager
13from .file_migrator import FileMigrator
14from .version_detector import VersionDetector
16logger = logging.getLogger(__name__)
19class VersionMigrator:
20 """Main migration orchestrator for MoAI-ADK version upgrades"""
22 def __init__(self, project_root: Path):
23 """
24 Initialize version migrator
26 Args:
27 project_root: Root directory of the project
28 """
29 self.project_root = Path(project_root)
30 self.detector = VersionDetector(project_root)
31 self.backup_manager = BackupManager(project_root)
32 self.file_migrator = FileMigrator(project_root)
34 def detect_version(self) -> str:
35 """
36 Detect current project version
38 Returns:
39 Version string (e.g., "0.23.0", "0.24.0+", "unknown")
40 """
41 return self.detector.detect_version()
43 def needs_migration(self) -> bool:
44 """
45 Check if project needs migration
47 Returns:
48 True if migration is needed, False otherwise
49 """
50 return self.detector.needs_migration()
52 def get_migration_info(self) -> Dict[str, any]:
53 """
54 Get detailed migration information
56 Returns:
57 Dictionary with migration details
58 """
59 version = self.detector.detect_version()
60 needs_migration = self.detector.needs_migration()
61 plan = self.detector.get_migration_plan()
63 return {
64 "current_version": version,
65 "needs_migration": needs_migration,
66 "target_version": "0.24.0" if needs_migration else version,
67 "migration_plan": plan,
68 "file_count": len(plan.get("move", [])),
69 }
71 def migrate_to_v024(self, dry_run: bool = False, cleanup: bool = True) -> bool:
72 """
73 Migrate project from v0.23.0 to v0.24.0
75 Args:
76 dry_run: If True, only show what would be done
77 cleanup: If True, remove old files after successful migration
79 Returns:
80 True if migration was successful, False otherwise
81 """
82 if not self.needs_migration():
83 logger.info("✅ Project is already up to date")
84 return True
86 if dry_run:
87 logger.info("🔍 DRY RUN MODE - No changes will be made")
88 self._show_migration_plan()
89 return True
91 logger.info("🚀 Starting migration to v0.24.0...")
93 try:
94 # Step 1: Create backup
95 logger.info("\n💾 Step 1: Creating backup...")
96 backup_path = self.backup_manager.create_backup("pre_v024_migration")
97 logger.info(f"✅ Backup created: {backup_path}")
99 # Step 2: Get migration plan
100 logger.info("\n📋 Step 2: Analyzing migration requirements...")
101 plan = self.detector.get_migration_plan()
102 logger.info(f" - {len(plan['create'])} directories to create")
103 logger.info(f" - {len(plan['move'])} files to move")
105 # Step 3: Execute migration
106 logger.info("\n🔄 Step 3: Executing migration...")
107 results = self.file_migrator.execute_migration_plan(plan)
109 if not results["success"]:
110 logger.error("❌ Migration failed with errors:")
111 for error in results["errors"]:
112 logger.error(f" - {error}")
113 logger.info(f"\n🔙 Rolling back using backup: {backup_path}")
114 self.backup_manager.restore_backup(backup_path)
115 return False
117 logger.info("✅ Migration completed successfully!")
118 logger.info(f" - {results['created_dirs']} directories created")
119 logger.info(f" - {results['moved_files']} files moved")
121 # Step 4: Verify migration
122 logger.info("\n🔍 Step 4: Verifying migration...")
123 if self._verify_migration():
124 logger.info("✅ Verification passed")
126 # Step 5: Cleanup (optional)
127 if cleanup:
128 logger.info("\n🗑️ Step 5: Cleaning up old files...")
129 cleaned = self.file_migrator.cleanup_old_files(plan["cleanup"])
130 logger.info(f"✅ Cleaned up {cleaned} old files")
131 else:
132 logger.info("\n⏭️ Step 5: Skipped cleanup (old files preserved)")
134 logger.info("\n✨ Migration to v0.24.0 completed successfully!")
135 return True
136 else:
137 logger.error("❌ Verification failed")
138 logger.info(f"🔙 Rolling back using backup: {backup_path}")
139 self.backup_manager.restore_backup(backup_path)
140 return False
142 except Exception as e:
143 logger.error(f"❌ Migration failed with exception: {e}")
144 logger.info("🔙 Attempting rollback...")
145 try:
146 latest_backup = self.backup_manager.get_latest_backup()
147 if latest_backup:
148 self.backup_manager.restore_backup(latest_backup)
149 logger.info("✅ Rollback completed")
150 except Exception as rollback_error:
151 logger.error(f"❌ Rollback failed: {rollback_error}")
152 return False
154 def _show_migration_plan(self) -> None:
155 """Display migration plan without executing"""
156 plan = self.detector.get_migration_plan()
158 print("\n📋 Migration Plan:")
159 print("\n📁 Directories to create:")
160 for directory in plan.get("create", []):
161 print(f" + {directory}")
163 print("\n📄 Files to move:")
164 for move_op in plan.get("move", []):
165 print(f" • {move_op['description']}")
166 print(f" {move_op['from']} → {move_op['to']}")
168 print("\n🗑️ Files to cleanup after migration:")
169 for cleanup_file in plan.get("cleanup", []):
170 print(f" - {cleanup_file}")
172 def _verify_migration(self) -> bool:
173 """
174 Verify migration was successful
176 Returns:
177 True if verification passed
178 """
179 # Check that new config exists
180 new_config = self.project_root / ".moai" / "config" / "config.json"
181 if not new_config.exists():
182 logger.error("Verification failed: new config.json not found")
183 return False
185 # Check that new config directory exists
186 config_dir = self.project_root / ".moai" / "config"
187 if not config_dir.is_dir():
188 logger.error("Verification failed: config directory not found")
189 return False
191 logger.debug("All verification checks passed")
192 return True
194 def check_status(self) -> Dict[str, any]:
195 """
196 Check migration status and return detailed information
198 Returns:
199 Dictionary with status information
200 """
201 version_info = self.detector.get_version_info()
202 migration_info = self.get_migration_info()
203 backups = self.backup_manager.list_backups()
205 return {
206 "version": version_info,
207 "migration": migration_info,
208 "backups": {
209 "count": len(backups),
210 "latest": backups[0] if backups else None,
211 },
212 }
214 def rollback_to_latest_backup(self) -> bool:
215 """
216 Rollback to the most recent backup
218 Returns:
219 True if rollback was successful
220 """
221 latest_backup = self.backup_manager.get_latest_backup()
223 if not latest_backup:
224 logger.error("No backup found to rollback to")
225 return False
227 logger.info(f"🔙 Rolling back to backup: {latest_backup}")
228 return self.backup_manager.restore_backup(latest_backup)