run method

  1. @override
Future<void> run()
override

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();
    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'];

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

    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.');
      }
    }
  }
}