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

1""" 

2Main version migration orchestrator for MoAI-ADK 

3 

4Coordinates version detection, backup creation, file migration, 

5and cleanup processes for automatic project upgrades. 

6""" 

7 

8import logging 

9from pathlib import Path 

10from typing import Dict 

11 

12from .backup_manager import BackupManager 

13from .file_migrator import FileMigrator 

14from .version_detector import VersionDetector 

15 

16logger = logging.getLogger(__name__) 

17 

18 

19class VersionMigrator: 

20 """Main migration orchestrator for MoAI-ADK version upgrades""" 

21 

22 def __init__(self, project_root: Path): 

23 """ 

24 Initialize version migrator 

25 

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) 

33 

34 def detect_version(self) -> str: 

35 """ 

36 Detect current project version 

37 

38 Returns: 

39 Version string (e.g., "0.23.0", "0.24.0+", "unknown") 

40 """ 

41 return self.detector.detect_version() 

42 

43 def needs_migration(self) -> bool: 

44 """ 

45 Check if project needs migration 

46 

47 Returns: 

48 True if migration is needed, False otherwise 

49 """ 

50 return self.detector.needs_migration() 

51 

52 def get_migration_info(self) -> Dict[str, any]: 

53 """ 

54 Get detailed migration information 

55 

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() 

62 

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 } 

70 

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 

74 

75 Args: 

76 dry_run: If True, only show what would be done 

77 cleanup: If True, remove old files after successful migration 

78 

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 

85 

86 if dry_run: 

87 logger.info("🔍 DRY RUN MODE - No changes will be made") 

88 self._show_migration_plan() 

89 return True 

90 

91 logger.info("🚀 Starting migration to v0.24.0...") 

92 

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}") 

98 

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") 

104 

105 # Step 3: Execute migration 

106 logger.info("\n🔄 Step 3: Executing migration...") 

107 results = self.file_migrator.execute_migration_plan(plan) 

108 

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 

116 

117 logger.info("✅ Migration completed successfully!") 

118 logger.info(f" - {results['created_dirs']} directories created") 

119 logger.info(f" - {results['moved_files']} files moved") 

120 

121 # Step 4: Verify migration 

122 logger.info("\n🔍 Step 4: Verifying migration...") 

123 if self._verify_migration(): 

124 logger.info("✅ Verification passed") 

125 

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)") 

133 

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 

141 

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 

153 

154 def _show_migration_plan(self) -> None: 

155 """Display migration plan without executing""" 

156 plan = self.detector.get_migration_plan() 

157 

158 print("\n📋 Migration Plan:") 

159 print("\n📁 Directories to create:") 

160 for directory in plan.get("create", []): 

161 print(f" + {directory}") 

162 

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']}") 

167 

168 print("\n🗑️ Files to cleanup after migration:") 

169 for cleanup_file in plan.get("cleanup", []): 

170 print(f" - {cleanup_file}") 

171 

172 def _verify_migration(self) -> bool: 

173 """ 

174 Verify migration was successful 

175 

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 

184 

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 

190 

191 logger.debug("All verification checks passed") 

192 return True 

193 

194 def check_status(self) -> Dict[str, any]: 

195 """ 

196 Check migration status and return detailed information 

197 

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() 

204 

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 } 

213 

214 def rollback_to_latest_backup(self) -> bool: 

215 """ 

216 Rollback to the most recent backup 

217 

218 Returns: 

219 True if rollback was successful 

220 """ 

221 latest_backup = self.backup_manager.get_latest_backup() 

222 

223 if not latest_backup: 

224 logger.error("No backup found to rollback to") 

225 return False 

226 

227 logger.info(f"🔙 Rolling back to backup: {latest_backup}") 

228 return self.backup_manager.restore_backup(latest_backup)