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 {
config = ConfigService(projectDir);
whiteLabel = WhiteLabelService(projectDir: projectDir, config: config);
cleanup = CleanupService(projectDir: projectDir, config: config);
final client = argResults!['client'] as String;
final isTest = argResults!['test'] as bool;
final keep = argResults!['keep'] as bool;
final enableSlack = argResults!['slack'] as bool;
if (enableSlack) {
slackService =
await initializeSlackService(argResults!['slack-channel'] as String);
}
try {
// -----------------------------------------------------------------------
// PHASE 1: VALIDATION & SETUP
// -----------------------------------------------------------------------
Logger.phase('1: Validation & Setup');
checkProjectPrerequisites();
await recoverInterruptedRun(cleanup);
final envFile = await resolveClientEnvFile(client, isTest: isTest);
final envFileName = p.basename(envFile.path);
final version = await runStep(
'Reading pubspec version', () => config.getPubspecVersion());
await notifyBuildStep(slackService,
step: 'Whitelabel Started',
client: client,
platform: '',
status: 'started',
additionalInfo: 'Version: $version');
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 = (envVars['APP_LOGO_PATH'] ?? '').trim().isEmpty
? appIconPath
: envVars['APP_LOGO_PATH']!;
final teamId = envVars['DEVELOPMENT_TEAM'];
// Validate udara.yaml up front so a typo fails before the long work.
final hooks = HooksService(projectDir);
hooks.load();
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: '',
status: 'started');
if (!keep) {
// The iOS project file is deliberately not backed up: restoring it
// would undo the bundle id and team changes that must persist.
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')));
});
}
await runStep('Staging client environment as root .env',
() => config.copyToRootEnv(envFile, trackForCleanup: !keep));
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 (teamId != null &&
teamId.isNotEmpty &&
config.pbxprojFile.existsSync()) {
await runStep('Applying iOS development team',
() async => config.updateDevelopmentTeam(teamId: teamId));
}
Logger.success('Project dynamic assets configured successfully');
// -----------------------------------------------------------------------
// PHASE 3: RUNNING EXTERNAL TOOLS
// -----------------------------------------------------------------------
Logger.phase('3: Running Tools & Generating Assets');
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('Cleaning Android icon cache',
() => whiteLabel.cleanAndroidIconCache());
await hooks.run(
HookPoint.afterBranding,
HookContext(
command: 'whitelabel',
projectDir: projectDir,
client: client,
envFile: envFile,
isTest: isTest,
version: version,
bundleId: bundleId,
appName: appName,
),
);
Logger.success('Whitelabeling process completed successfully!');
await notifyBuildStep(slackService,
step: 'Whitelabel Process',
client: client,
platform: '',
status: 'completed');
Logger.phase('Next Steps');
if (keep) {
Logger.info('The project is now branded as "$client". Run it with:');
Logger.info(' flutter run --dart-define=CLIENT_ENV=.env');
Logger.info(
'Run "udara_cli clean" to restore the project files again.');
} else {
Logger.info(
'Native branding for "$client" is applied; staged project files are being restored.');
Logger.info(
'Use "udara_cli whitelabel --client $client --keep" to keep the project runnable as this client.');
}
} catch (e, s) {
final String errorMessage;
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 occurred during whitelabeling.',
cause: e, stackTrace: s);
}
await notifyBuildStep(slackService,
step: 'Whitelabel Process',
client: client,
platform: '',
status: 'failed',
errorMessage: errorMessage);
rethrow;
} finally {
if (!keep) {
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.');
}
}
}
}