run method
Implementation
Future<void> run(
FlavorConfig config, {
String? newFlavor,
bool skipFirebase = false,
}) async {
try {
final adapter = RuntimeConfigService();
// 1. Save Config as the source of truth
ConfigService.save(config);
_log.info('📦 Running setup with flavors: ${config.flavors.join(", ")}');
// 2. File Structure
FileService.createStructure();
// Generate AppConfig via adapter (JSON or ENV variant)
adapter.generateAppConfig(config);
// Generate flavor-specific runtime files (no-op for JSON, .env files for ENV)
adapter.generateFlavorFiles(config);
// Ensure dependencies and assets are configured in pubspec.yaml
_ensureDependencies(config);
FileService.updateTests();
String? oldMainContent;
if (config.useSeparateMains) {
final mainFile = File(p.join(ConfigService.root, 'lib/main.dart'));
if (mainFile.existsSync()) {
oldMainContent = mainFile.readAsStringSync();
_log.info(
'📝 Preserving your existing main.dart content into the production flavor...',
);
}
}
bool overwriteMains = true;
final existingMains = _checkExistingMains(
config.flavors,
config.useSeparateMains,
);
if (existingMains.isNotEmpty) {
overwriteMains = false;
}
// Create main files using adapter-generated boilerplate
_createMainFiles(
config,
adapter,
overwrite: overwriteMains,
productionContent: oldMainContent,
);
// If we are not in full overwrite mode OR we preserved old content,
// integrate the files to ensure they have AppConfig.init and proper imports.
if (!overwriteMains || oldMainContent != null) {
if (config.useSeparateMains) {
for (final flavor in config.flavors) {
final path = p.join(
ConfigService.root,
'lib/main/main_$flavor.dart',
);
adapter.integrateMainFile(path, config, flavor: flavor);
}
} else {
final path = p.join(ConfigService.root, 'lib/main.dart');
adapter.integrateMainFile(path, config);
}
}
// 3. Platform Setup
if (config.platforms.contains('android')) {
_safe(
() => AndroidService.setupFlavors(config: config, logger: _log),
'Android setup',
);
}
if (config.platforms.contains('ios')) {
_safe(
() => IOSService.setupSchemes(config: config, logger: _log),
'iOS setup',
);
}
if (config.platforms.contains('macos')) {
_safe(
() => MacOSService.setupSchemes(config: config, logger: _log),
'macOS setup',
);
}
if (config.platforms.contains('web')) {
_safe(() => WebService.setupFlavors(config, _log), 'Web setup');
}
// 4. Cleanup
final orphans = FileService.getOrphanedFlavors(config.flavors);
if (orphans.isNotEmpty) {
FileService.cleanupFlavors(orphans.toList());
// Also clean up adapter-specific orphan files
adapter.cleanupFlavorFiles(orphans.toList(), config);
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',
);
}
_log.info('✔ Orphaned files cleaned up (${orphans.join(", ")})');
}
// Cleanup main files based on strategy
if (config.useSeparateMains) {
final rootMain = File(p.join(ConfigService.root, 'lib/main.dart'));
if (rootMain.existsSync()) {
rootMain.deleteSync();
}
} else {
final mainDir = Directory(p.join(ConfigService.root, 'lib/main'));
if (mainDir.existsSync()) {
mainDir.deleteSync(recursive: true);
}
}
_log.success('✅ Flavor system synchronized successfully!');
FileService.updateVSCodeLaunchConfig();
// Ensure Firebase entry points are synchronized (silently)
if (ConfigService.hasFirebaseFiles()) {
FileService.injectFirebase(separate: config.useSeparateMains);
}
// Check and re-initialize Firebase if necessary
if (!skipFirebase) {
await FirebaseCommand.checkAndReinit(_log, targetFlavor: newFlavor);
}
FileService
.updateVSCodeLaunchConfig(); // Call again after Firebase just in case
} catch (e) {
if (e is CliException && e.isLogged) {
rethrow;
}
_log.error('❌ Failed to synchronize flavors: $e');
rethrow;
}
}