replaceFlavor method
Implementation
Future<void> replaceFlavor({
required String oldFlavor,
required String newFlavor,
}) async {
final root = ConfigService.root;
final tempDir = Directory.systemTemp.createTempSync('flavor_cli_backup_');
_log.info('📸 Creating pre-flight snapshot...');
try {
// 1. SNAPSHOT
final pathsToBackup = [
'flavor_cli.yaml',
'ios/Runner.xcodeproj',
'ios/Flutter',
'android/app/build.gradle',
'android/app/build.gradle.kts',
'android/app/src/main/AndroidManifest.xml',
'lib',
'test/widget_test.dart',
'firebase.json',
];
// Include .env files in backup
final config = ConfigService.load();
for (final flavor in config.flavors) {
pathsToBackup.add('.env.$flavor');
}
pathsToBackup.add('.env.$oldFlavor'); // ensure old file is backed up
for (final relativePath in pathsToBackup) {
final src = FileSystemEntity.isDirectorySync(p.join(root, relativePath))
? Directory(p.join(root, relativePath))
: File(p.join(root, relativePath));
if (!src.existsSync()) continue;
final destPath = p.join(tempDir.path, relativePath);
if (src is Directory) {
_copyDirectory(src, Directory(destPath));
} else if (src is File) {
File(destPath).createSync(recursive: true);
src.copySync(destPath);
}
}
// 2. ATTEMPT RENAME SEQUENCE
final isProduction = oldFlavor == config.productionFlavor;
ConfigService.renameFlavor(oldFlavor, newFlavor);
if (isProduction) {
ConfigService.save(
ConfigService.load().copyWith(productionFlavor: newFlavor),
);
}
final updatedConfig = ConfigService.load();
final adapter = RuntimeConfigService();
// Rename adapter-specific files (e.g., .env.<old> -> .env.<new>)
adapter.renameFlavorFiles(oldFlavor, newFlavor, updatedConfig);
adapter.generateAppConfig(updatedConfig);
FileService.renameFlavor(
oldName: oldFlavor,
newName: newFlavor,
log: _log,
);
if (updatedConfig.platforms.contains('android')) {
AndroidService.setupFlavors(config: updatedConfig, logger: _log);
}
if (updatedConfig.platforms.contains('ios')) {
IOSService.setupSchemes(config: updatedConfig, logger: _log);
}
if (updatedConfig.platforms.contains('macos')) {
MacOSService.setupSchemes(config: updatedConfig, logger: _log);
}
if (updatedConfig.platforms.contains('web')) {
WebService.setupFlavors(updatedConfig, _log);
}
FileService.updateTests();
final orphans = FileService.getOrphanedFlavors(updatedConfig.flavors);
if (orphans.isNotEmpty) {
FileService.cleanupFlavors(orphans.toList());
adapter.cleanupFlavorFiles(orphans.toList(), updatedConfig);
for (final orphan in orphans) {
_safe(
() => IOSService.removeFlavorSchemes(orphan, logger: _log),
'iOS cleanup for $orphan',
);
_safe(
() => MacOSService.removeFlavorSchemes(orphan, logger: _log),
'macOS cleanup for $orphan',
);
}
}
FileService.updateVSCodeLaunchConfig();
FileService.injectFirebase(separate: updatedConfig.useSeparateMains);
// Clean up temp dir on success
tempDir.deleteSync(recursive: true);
_log.success(
'✅ Flavor "$oldFlavor" successfully renamed to "$newFlavor".',
);
await FirebaseCommand.checkAndReinit(_log, targetFlavor: newFlavor);
} catch (e) {
_log.error('❌ Rename failed: $e');
_log.info(
'🔄 Initiating automatic rollback to preserve project integrity...',
);
// 3. ROLLBACK FROM SNAPSHOT
for (final entity in tempDir.listSync(recursive: true)) {
if (entity is File) {
final relativePath = p.relative(entity.path, from: tempDir.path);
final targetFile = File(p.join(root, relativePath));
targetFile.createSync(recursive: true);
entity.copySync(targetFile.path);
}
}
try {
tempDir.deleteSync(recursive: true);
} catch (_) {}
_log.success(
'✔ Rollback successful. Project restored to "$oldFlavor" state.',
);
rethrow;
}
}