validateIos static method

List<VerificationResult> validateIos(
  1. PlatformConfig localConfig,
  2. ProjectConfig ulinkConfig
)

Validate iOS configuration

Implementation

static List<VerificationResult> validateIos(
  PlatformConfig localConfig,
  ProjectConfig ulinkConfig,
) {
  final results = <VerificationResult>[];

  // Bundle Identifier match
  if (localConfig.bundleIdentifier != null &&
      ulinkConfig.iosBundleIdentifier != null) {
    if (localConfig.bundleIdentifier != ulinkConfig.iosBundleIdentifier) {
      results.add(
        VerificationResult(
          checkName: 'iOS Bundle Identifier Match',
          status: VerificationStatus.error,
          message: 'Bundle identifier mismatch',
          fixSuggestion:
              'Update ULink config: ${localConfig.bundleIdentifier}\n'
              'Or update Info.plist: ${ulinkConfig.iosBundleIdentifier}',
          details: {
            'local': localConfig.bundleIdentifier,
            'ulink': ulinkConfig.iosBundleIdentifier,
          },
        ),
      );
    } else {
      results.add(
        VerificationResult(
          checkName: 'iOS Bundle Identifier Match',
          status: VerificationStatus.success,
          message: 'Bundle identifier matches',
        ),
      );
    }
  } else {
    results.add(
      VerificationResult(
        checkName: 'iOS Bundle Identifier Match',
        status: VerificationStatus.warning,
        message:
            'Bundle identifier not found in local config or ULink config',
        fixSuggestion:
            'Ensure bundle identifier is set in both Info.plist and ULink dashboard',
      ),
    );
  }

  // URL Scheme match - use iOS-specific schemes for Flutter projects
  final schemesToCheck = localConfig.iosUrlSchemes.isNotEmpty
      ? localConfig.iosUrlSchemes
      : localConfig.urlSchemes;

  if (ulinkConfig.iosDeeplinkSchema != null &&
      ulinkConfig.iosDeeplinkSchema!.isNotEmpty) {
    final expectedScheme =
        ulinkConfig.iosDeeplinkSchema!.replaceAll('://', '').toLowerCase();
    final hasMatchingScheme = schemesToCheck.any(
      (scheme) => scheme.toLowerCase() == expectedScheme,
    );

    if (!hasMatchingScheme) {
      results.add(
        VerificationResult(
          checkName: 'iOS URL Scheme Match',
          status: VerificationStatus.error,
          message: 'URL scheme mismatch',
          fixSuggestion:
              'Add URL scheme "$expectedScheme" to Info.plist CFBundleURLSchemes\n'
              'Or update ULink config to match: ${schemesToCheck.isEmpty ? "(none found)" : schemesToCheck.join(", ")}',
          details: {'local': schemesToCheck, 'ulink': expectedScheme},
        ),
      );
    } else {
      // Check if there are extra schemes in local that don't match ULink
      final extraSchemes = schemesToCheck
          .where((scheme) => scheme.toLowerCase() != expectedScheme)
          .toList();

      if (extraSchemes.isNotEmpty) {
        results.add(
          VerificationResult(
            checkName: 'iOS URL Scheme Match',
            status: VerificationStatus.success,
            message: 'URL scheme matches (ULink: $expectedScheme)',
            details: {
              'matched': expectedScheme,
              'local': schemesToCheck,
              'ulink': expectedScheme,
            },
          ),
        );
        results.add(
          VerificationResult(
            checkName: 'iOS Extra URL Schemes',
            status: VerificationStatus.warning,
            message:
                'Local has ${extraSchemes.length} extra URL scheme(s) not configured in ULink',
            fixSuggestion:
                'These schemes are in your Info.plist but not in ULink config:\n${extraSchemes.join(", ")}\n'
                'If these are intentional, you can ignore this warning. Otherwise, remove them or add "$expectedScheme" to ULink.',
            details: {
              'extraSchemes': extraSchemes,
              'ulinkScheme': expectedScheme,
            },
          ),
        );
      } else {
        results.add(
          VerificationResult(
            checkName: 'iOS URL Scheme Match',
            status: VerificationStatus.success,
            message: 'URL scheme matches',
          ),
        );
      }
    }
  } else if (schemesToCheck.isNotEmpty) {
    // Local has schemes but ULink doesn't have iOS deeplink schema configured
    results.add(
      VerificationResult(
        checkName: 'iOS URL Scheme Match',
        status: VerificationStatus.warning,
        message:
            'Local iOS has URL schemes but ULink iOS deeplink schema is not configured',
        fixSuggestion:
            'Configure iOS deeplink schema in ULink dashboard to: ${schemesToCheck.first}',
        details: {'local': schemesToCheck, 'ulink': '(not configured)'},
      ),
    );
  }

  // Associated Domains match - local needs to match at least ONE verified domain
  final iosVerifiedDomains = ulinkConfig.domains
      .where((d) => d.status == 'verified')
      .map((d) => d.host)
      .toList();

  final allUlinkDomains = ulinkConfig.domains.map((d) => d.host).toList();

  if (localConfig.associatedDomains.isNotEmpty) {
    // Check if any local domain matches a verified ULink domain
    final matchedVerifiedDomain = localConfig.associatedDomains.firstWhere(
      (localDomain) => iosVerifiedDomains.contains(localDomain),
      orElse: () => '',
    );

    if (matchedVerifiedDomain.isNotEmpty) {
      // Found a match with a verified domain
      results.add(
        VerificationResult(
          checkName: 'iOS Associated Domain Match',
          status: VerificationStatus.success,
          message: 'Associated domain matches verified ULink domain',
          details: {
            'matched': matchedVerifiedDomain,
            'local': localConfig.associatedDomains,
          },
        ),
      );
    } else {
      // No verified match, check if local domain exists in ULink but not verified
      final unverifiedMatch = localConfig.associatedDomains.firstWhere(
        (localDomain) => allUlinkDomains.contains(localDomain),
        orElse: () => '',
      );

      if (unverifiedMatch.isNotEmpty) {
        // Domain exists in ULink but is not verified
        final domainStatus = ulinkConfig.domains
            .firstWhere((d) => d.host == unverifiedMatch)
            .status;
        results.add(
          VerificationResult(
            checkName: 'iOS Associated Domain Match',
            status: VerificationStatus.error,
            message:
                'Domain "$unverifiedMatch" exists in ULink but is not verified (status: $domainStatus)',
            fixSuggestion:
                'Complete domain verification in ULink dashboard for "$unverifiedMatch"',
            details: {
              'local': localConfig.associatedDomains,
              'ulink': unverifiedMatch,
              'status': domainStatus,
            },
          ),
        );
      } else {
        // Local domain doesn't exist in ULink at all
        results.add(
          VerificationResult(
            checkName: 'iOS Associated Domain Match',
            status: VerificationStatus.error,
            message:
                'Local domain "${localConfig.associatedDomains.first}" not found in ULink',
            fixSuggestion:
                'Add domain "${localConfig.associatedDomains.first}" to ULink dashboard and verify it',
            details: {
              'local': localConfig.associatedDomains,
              'ulink': allUlinkDomains,
            },
          ),
        );
      }
    }
  } else if (iosVerifiedDomains.isNotEmpty) {
    // ULink has verified domains but local doesn't have any
    results.add(
      VerificationResult(
        checkName: 'iOS Associated Domain Match',
        status: VerificationStatus.warning,
        message: 'No associated domains in local config',
        fixSuggestion:
            'Add at least one verified domain to your entitlements file:\n${iosVerifiedDomains.map((d) => '  applinks:$d').join('\n')}',
        details: {
          'local': localConfig.associatedDomains,
          'ulink': iosVerifiedDomains,
        },
      ),
    );
  } else if (ulinkConfig.domains.isNotEmpty) {
    // ULink has domains but none are verified
    results.add(
      VerificationResult(
        checkName: 'iOS Associated Domain Match',
        status: VerificationStatus.warning,
        message: 'ULink has domains but none are verified',
        fixSuggestion:
            'Verify your domains in the ULink dashboard to enable Universal Links',
        details: {
          'local': localConfig.associatedDomains,
          'ulink': ulinkConfig.domains
              .map((d) => '${d.host} (${d.status})')
              .toList(),
        },
      ),
    );
  }

  // Team ID check and comparison
  if (ulinkConfig.iosTeamId == null || ulinkConfig.iosTeamId!.isEmpty) {
    results.add(
      VerificationResult(
        checkName: 'iOS Team ID',
        status: VerificationStatus.warning,
        message: 'Team ID not configured in ULink',
        fixSuggestion:
            'Add your Apple Team ID to ULink project configuration',
      ),
    );
  } else if (localConfig.teamId != null && localConfig.teamId!.isNotEmpty) {
    // Compare local Team ID with ULink Team ID
    if (localConfig.teamId != ulinkConfig.iosTeamId) {
      results.add(
        VerificationResult(
          checkName: 'iOS Team ID Match',
          status: VerificationStatus.error,
          message: 'Team ID mismatch',
          fixSuggestion: 'Update ULink Team ID to: ${localConfig.teamId}\n'
              'Or update your Xcode project to use: ${ulinkConfig.iosTeamId}',
          details: {
            'local': localConfig.teamId,
            'ulink': ulinkConfig.iosTeamId,
          },
        ),
      );
    } else {
      results.add(
        VerificationResult(
          checkName: 'iOS Team ID Match',
          status: VerificationStatus.success,
          message: 'Team ID matches',
        ),
      );
    }
  } else {
    results.add(
      VerificationResult(
        checkName: 'iOS Team ID',
        status: VerificationStatus.success,
        message: 'Team ID is configured in ULink',
        details: {'ulink': ulinkConfig.iosTeamId},
      ),
    );
  }

  return results;
}