run method
Runs this command.
The return value is wrapped in a Future if necessary and returned by
CommandRunner.runCommand.
Implementation
@override
Future<void> run() async {
final buildStartTime = DateTime.now();
config = ConfigService(projectDir);
whiteLabel = WhiteLabelService(projectDir: projectDir, config: config);
cleanup = CleanupService(projectDir: projectDir, config: config);
final client = argResults!['client'] as String;
final platform = argResults!['platform'] as String;
final isTest = argResults!['test'] as bool;
final enableSlack = argResults!['slack'] as bool;
// iOS always produces an IPA; the --type flag only applies to Android.
final type = platform == 'ios' ? 'ipa' : argResults!['type'] as String;
String? version;
String? errorMessage;
File? builtFile;
var buildSuccess = false;
if (enableSlack) {
slackService =
await initializeSlackService(argResults!['slack-channel'] as String);
}
try {
// -----------------------------------------------------------------------
// PHASE 1: VALIDATION & SETUP
// -----------------------------------------------------------------------
Logger.phase('1: Validation & Setup');
checkProjectPrerequisites();
final envFile = await resolveClientEnvFile(client, isTest: isTest);
final envFileName = p.basename(envFile.path);
version = await runStep(
'Reading pubspec version', () => config.getPubspecVersion());
await notifyBuildStep(slackService,
step: 'Build Started',
client: client,
platform: platform,
status: 'started',
additionalInfo: 'Version: $version, Type: $type');
final envVars = await config.parseEnvFile(envFile);
validateEnvVariables(client, envFileName, envVars);
final appName = envVars['APP_NAME_PROD']!;
final bundleId = envVars['BUNDLE_ID']!;
final clientAssetsPath = envVars['ASSETS_PATH']!;
final appIconPath = envVars['APP_ICON_PATH']!;
final splashImagePath =
_firstNonEmpty([envVars['APP_LOGO_PATH'], envVars['APP_ICON_PATH']])!;
final teamId = envVars['DEVELOPMENT_TEAM'];
Logger.success(
'Validated "$client" ($envFileName): $appName · $bundleId · v$version');
// -----------------------------------------------------------------------
// PHASE 2: PROJECT CONFIGURATION
// -----------------------------------------------------------------------
Logger.phase('2: Project Configuration');
await notifyBuildStep(slackService,
step: 'Project Configuration',
client: client,
platform: platform,
status: 'started');
await runStep('Creating backups', () async {
await config.createBackup(File(p.join(projectDir, 'pubspec.yaml')));
await config.createBackup(
File(p.join(projectDir, 'flutter_launcher_icons.yaml')));
if (config.pbxprojFile.existsSync()) {
await config.createBackup(config.pbxprojFile);
}
});
await runStep('Staging client environment as root .env',
() => config.copyToRootEnv(envFile));
await runStep('Updating launcher icon & splash configs', () async {
await config.updateYamlValue(
File(p.join(projectDir, 'flutter_launcher_icons.yaml')),
['flutter_launcher_icons', 'image_path'],
appIconPath);
await config.updateYamlValue(File(p.join(projectDir, 'pubspec.yaml')),
['splash_master', 'image'], splashImagePath);
});
await runStep('Syncing client branding assets & fonts', () async {
await whiteLabel.syncBrandingAssets(client, clientAssetsPath);
await whiteLabel.applyClientFonts(client, clientAssetsPath);
await config.updatePubspecAssets(
clientAssetPath: client, requiredExtraAssets: ['.env']);
});
if (platform == 'ios' && teamId != null && teamId.isNotEmpty) {
await runStep('Applying iOS development team',
() async => config.updateDevelopmentTeam(teamId: teamId));
}
Logger.success('Project assets configured successfully');
// -----------------------------------------------------------------------
// PHASE 3: RUNNING EXTERNAL TOOLS
// -----------------------------------------------------------------------
Logger.phase('3: Running Build Commands');
await runStep('Resolving dependencies (pub get)',
() => runShell('flutter pub get'));
await runStep(
'Updating Bundle ID ($bundleId)',
() => runShell(
'dart run rename setBundleId --targets ios,android --value "$bundleId"'));
await runStep(
'Updating App Name ($appName)',
() => runShell(
'dart run rename setAppName --targets ios,android --value "$appName"'));
await runStep('Generating Launcher Icons',
() => runShell('dart run flutter_launcher_icons'));
await runStep('Generating Native Splash Screens',
() => runShell('dart run splash_master create'));
await runStep('Applying OS-specific splash & icon patches',
() => whiteLabel.cleanAndroidIconCache());
// -----------------------------------------------------------------------
// PHASE 4: THE FINAL BUILD
// -----------------------------------------------------------------------
Logger.phase('4: Building the App');
final buildCommand = switch (type) {
'apk' => 'flutter build apk --release',
'aab' => 'flutter build appbundle --release',
_ => 'flutter build ipa',
};
await runStep('Building $platform (${type.toUpperCase()})',
() => runShell('$buildCommand --dart-define=CLIENT_ENV=.env'));
builtFile = await runStep(
'Renaming ${type.toUpperCase()} artifact',
() => whiteLabel.renameOutput(
clientName: client, version: version!, type: type));
buildSuccess = true;
Logger.success('Build process completed successfully!');
} catch (e, s) {
buildSuccess = false;
if (e is BuildException) {
errorMessage = e.message;
Logger.error(e.message,
cause: e.fix, stackTrace: e.originalStackTrace ?? s);
} else {
errorMessage = e.toString();
Logger.error('An unexpected error stopped the build.',
cause: e, stackTrace: s);
}
await notifyBuildStep(slackService,
step: 'Build Process',
client: client,
platform: platform,
status: 'failed',
errorMessage: errorMessage);
rethrow;
} finally {
// -----------------------------------------------------------------------
// FINAL PHASE: CLEANUP (ALWAYS RUNS)
// -----------------------------------------------------------------------
Logger.phase('Cleaning Up Project State');
try {
await cleanup.performFullCleanup();
} catch (e) {
Logger.warning(
'Cleanup encountered an issue: $e. Run "udara_cli clean" to finish restoring the project.');
}
final buildDuration = DateTime.now().difference(buildStartTime);
await config.appendBuildHistory(
client: client,
platform: platform,
type: type,
version: version,
success: buildSuccess,
duration: buildDuration,
errorMessage: errorMessage,
artifactPath: builtFile?.path,
);
if (slackService != null && version != null) {
await slackService!.sendBuildSummary(
client: client,
platform: platform,
type: type,
version: version,
success: buildSuccess,
buildTime: buildDuration,
errorMessage: errorMessage,
artifactFile: builtFile,
);
}
_printSummary(
client: client,
platform: platform,
type: type,
version: version,
success: buildSuccess,
duration: buildDuration,
artifact: builtFile,
);
}
}