run method

  1. @override
Future<void> run(
  1. List<String> args, {
  2. required Logger logger,
})
override

The execution body of the command.

Implementation

@override
Future<void> run(List<String> args, {required Logger logger}) async {
  final finder = ProjectFinder();
  final projectRoot = finder.findProjectRoot();
  final project = FlutterProject(projectRoot);

  logger.println('=== Initializing Deep Link Configuration ===\n');

  // 1. Gather inputs
  final defaultScheme = PathUtils.basename(projectRoot).replaceAll(RegExp(r'[^a-zA-Z0-9]'), '').toLowerCase();
  final scheme = Console.ask('Enter Custom URL Scheme (e.g. myapp)', defaultValue: defaultScheme).toLowerCase();

  final genRoutes = Console.askYesNo('Generate sample routes?', defaultValue: true);
  final genManager = Console.askYesNo('Generate DeepLinkManager?', defaultValue: true);
  final appLinks = Console.askYesNo('Enable Android App Links?', defaultValue: false);
  final universalLinks = Console.askYesNo('Enable iOS Universal Links?', defaultValue: false);

  final domains = <String>[];
  if (appLinks || universalLinks) {
    final domainsInput = Console.ask('Enter domain names for App/Universal links (comma-separated, e.g. example.com)');
    domains.addAll(
      domainsInput
          .split(',')
          .map((d) => d.trim().replaceAll(RegExp(r'^https?://'), ''))
          .where((d) => d.isNotEmpty),
    );
  }

  final spinner = Spinner('Configuring files...');
  spinner.start();

  try {
    // 2. Load Config Manager and Backup Manager
    final configManager = ConfigManager(projectRoot);
    final backupManager = BackupManager(projectRoot);

    // Create new configuration
    final config = DeepLinkConfig(
      schemes: [scheme],
      generateSampleRoutes: genRoutes,
      generateDeepLinkManager: genManager,
      androidAppLinks: appLinks,
      iosUniversalLinks: universalLinks,
      domains: domains,
    );

    // 3. Android Changes
    // Manifest
    if (FileUtils.exists(project.androidManifestPath)) {
      backupManager.backupFile(project.androidManifestPath);
      var manifestContent = FileUtils.readAsString(project.androidManifestPath);
      final manifestEditor = AndroidManifestEditor();
      manifestContent = manifestEditor.addScheme(manifestContent, scheme);
      if (appLinks && domains.isNotEmpty) {
        manifestContent = manifestEditor.enableAppLinks(manifestContent, domains);
      }
      FileUtils.writeString(project.androidManifestPath, manifestContent);
    }

    // MainActivity
    try {
      final mainActivityInfo = project.findMainActivity();
      backupManager.backupFile(mainActivityInfo.path);
      var mainActivityContent = FileUtils.readAsString(mainActivityInfo.path);
      final isKotlin = mainActivityInfo.language == MainActivityLanguage.kotlin;
      mainActivityContent = MainActivityEditor().inject(mainActivityContent, isKotlin);
      FileUtils.writeString(mainActivityInfo.path, mainActivityContent);
    } catch (e) {
      logger.warning('MainActivity adjustment skipped: ${e.toString()}');
    }

    // 4. iOS Changes
    // Info.plist
    if (FileUtils.exists(project.iosPlistPath)) {
      backupManager.backupFile(project.iosPlistPath);
      var plistContent = FileUtils.readAsString(project.iosPlistPath);
      plistContent = PlistEditor().addScheme(plistContent, scheme);
      FileUtils.writeString(project.iosPlistPath, plistContent);
    }

    // AppDelegate.swift
    if (FileUtils.exists(project.iosAppDelegatePath)) {
      backupManager.backupFile(project.iosAppDelegatePath);
      var appDelegateContent = FileUtils.readAsString(project.iosAppDelegatePath);
      appDelegateContent = AppDelegateEditor().inject(appDelegateContent);
      FileUtils.writeString(project.iosAppDelegatePath, appDelegateContent);
    }

    // 5. Generate Flutter code
    final generator = FlutterGenerator(projectRoot, logger);
    generator.generate(config);

    // 6. Generate Associated Domain Assets & Documentation if required
    if (appLinks || universalLinks) {
      final setupDir = PathUtils.join(projectRoot, 'deeplink_setup');
      FileUtils.createDirectory(setupDir);

      final templateEngine = TemplateEngine();
      final packageName = project.getAndroidPackageName();

      // Write assetlinks.json
      if (appLinks) {
        final renderedAssetLinks = templateEngine.render(
          TemplatesBundle.assetLinksJson,
          {
            'PACKAGE_NAME': packageName,
            'SHA256_FINGERPRINT': 'YOUR_SHA256_FINGERPRINT_HERE',
          },
        );
        FileUtils.writeString(PathUtils.join(setupDir, 'assetlinks.json'), renderedAssetLinks);
      }

      // Write apple-app-site-association
      if (universalLinks) {
        final renderedAasa = templateEngine.render(
          TemplatesBundle.appleAppSiteAssociation,
          {
            'TEAM_ID': 'YOUR_APPLE_TEAM_ID',
            'BUNDLE_ID': 'YOUR_APPLE_BUNDLE_ID',
          },
        );
        FileUtils.writeString(PathUtils.join(setupDir, 'apple-app-site-association'), renderedAasa);
      }

      // Write instructions
      final renderedInstructions = templateEngine.render(
        TemplatesBundle.setupInstructions,
        {
          'SCHEME': scheme,
          'DOMAINS': domains.join(', '),
          'PACKAGE_NAME': packageName,
          'DOMAIN_1': domains.isNotEmpty ? domains[0] : 'your-domain.com',
          'DOMAIN_2': domains.length > 1 ? domains[1] : 'your-second-domain.com',
        },
      );
      FileUtils.writeString(PathUtils.join(setupDir, 'README.md'), renderedInstructions);
    }

    // Save Config
    configManager.save(config);

    spinner.stop(success: true, finalMessage: 'Deep Link Integration initialized successfully.');

    if (appLinks || universalLinks) {
      logger.info('Action Required: Setup files and verification guides generated under "deeplink_setup/".');
    }
  } catch (e) {
    spinner.stop(success: false, finalMessage: 'Initialization failed.');
    throw DeepLinkException('Init command failed to complete successfully', e);
  }
}