execute method

Future<int> execute()

Executes the generation logic and returns the exit code. Does NOT call exit().

Implementation

Future<int> execute() async {
  final projectDir = findNitroProjectRoot();
  if (projectDir == null) {
    stderr.writeln(red('❌ No Nitro project found in . or its subdirectories (must have nitro dependency in pubspec.yaml).'));
    return 1;
  }

  // If we're not in the project root, let the user know we've found it
  if (projectDir.path != Directory.current.path) {
    stdout.writeln(gray('  📂 Found project in: ${projectDir.path}'));
  }

  stdout.writeln('');
  stdout.writeln(boldCyan('  ╔══════════════════════════╗'));
  stdout.writeln(boldCyan('  ║  nitrogen generate       ║'));
  stdout.writeln(boldCyan('  ╚══════════════════════════╝'));
  stdout.writeln('');

  // ── pub get ─────────────────────────────────────────────────────────────
  stdout.writeln(cyan('  › flutter pub get …'));
  var exitCode = await runStreaming('flutter', ['pub', 'get'], workingDirectory: projectDir.path);
  if (exitCode != 0) {
    stderr.writeln(red('  ✘  flutter pub get failed (exit $exitCode)'));
    return exitCode;
  }
  stdout.writeln('');

  // ── build_runner ─────────────────────────────────────────────────────────
  stdout.writeln(cyan('  › build_runner build …'));
  stdout.writeln('');
  exitCode = await runStreaming(
    'flutter',
    [
      'pub',
      'run',
      'build_runner',
      'build',
      '--delete-conflicting-outputs',
    ],
    workingDirectory: projectDir.path,
  );

  stdout.writeln('');
  if (exitCode != 0) {
    stderr.writeln(boldRed('  ✘  build_runner failed (exit $exitCode)'));
    stderr.writeln(gray('     Check the output above for details.'));
    return exitCode;
  }

  // ── Post-generation bridge cleanup ───────────────────────────────────────
  // Generated Swift bridges live in lib/src/generated/swift/ and are compiled
  // via the podspec source_files pattern. Remove any stale copies from Classes/
  // to prevent "Invalid redeclaration" Swift compiler errors.
  final nitroNativePath = resolveNitroNativePath(projectDir.path);
  createSharedHeaders(nitroNativePath, baseDir: projectDir.path);
  _cleanStaleSwiftBridges(projectDir.path);

  // ── nitrogen link (auto) ─────────────────────────────────────────────────
  // Automatically run the patching logic (build.gradle, Plugin.kt, etc.)
  // so users don't have to remember to run `nitrogen link` manually.
  stdout.writeln(cyan('  › nitrogen link (auto-patching) …'));
  final pluginName = _readPluginName(projectDir.path);
  final moduleInfos = discoverModuleInfos(pluginName, baseDir: projectDir.path);
  final hasCpp = moduleInfos.any((m) => m.isCpp);
  final hasNonCpp = moduleInfos.any((m) => !m.isCpp);

  // Patch CMake and C++ stubs
  linkCMake(pluginName, moduleInfos.map((m) => m.lib).toList(), nitroNativePath, baseDir: projectDir.path, moduleInfos: moduleInfos);

  // Patch iOS/macOS
  if (Directory(p.join(projectDir.path, 'ios')).existsSync()) {
    linkPodspec(pluginName, moduleInfos.map((m) => m.lib).toList(), baseDir: projectDir.path, moduleInfos: moduleInfos);
    if (hasNonCpp) {
      final appleCppLibs = moduleInfos.where((m) => isAppleCppModule(File(p.join(projectDir.path, 'lib', 'src', '${m.lib}.native.dart')))).map((m) => m.lib).toSet();
      final swiftModules = moduleInfos.where((m) => !appleCppLibs.contains(m.lib)).map((m) => m.toMap()).toList();
      linkSwiftPlugin(pluginName, swiftModules, baseDir: projectDir.path);
      purgeStaleCppSwiftRegistrations(moduleInfos.where((m) => appleCppLibs.contains(m.lib)).toList(), platform: 'ios', baseDir: projectDir.path);
    }
  }
  if (Directory(p.join(projectDir.path, 'macos')).existsSync()) {
    linkMacosPodspec(pluginName, moduleInfos.map((m) => m.lib).toList(), baseDir: projectDir.path, moduleInfos: moduleInfos);
    if (hasNonCpp) {
      final appleCppLibs = moduleInfos.where((m) => isAppleCppModule(File(p.join(projectDir.path, 'lib', 'src', '${m.lib}.native.dart')))).map((m) => m.lib).toSet();
      final swiftModules = moduleInfos.where((m) => !appleCppLibs.contains(m.lib)).map((m) => m.toMap()).toList();
      linkMacosSwiftPlugin(pluginName, swiftModules, baseDir: projectDir.path);
      purgeStaleCppSwiftRegistrations(moduleInfos.where((m) => appleCppLibs.contains(m.lib)).toList(), platform: 'macos', baseDir: projectDir.path);
    }
  }

  // Patch Android
  if (Directory(p.join(projectDir.path, 'android')).existsSync()) {
    final androidCppLibs = moduleInfos.where((m) => isNativeCppModule(File(p.join(projectDir.path, 'lib', 'src', '${m.lib}.native.dart')))).map((m) => m.lib).toSet();
    final kotlinModules = moduleInfos.where((m) => !androidCppLibs.contains(m.lib)).map((m) => m.toMap()).toList();
    if (kotlinModules.isNotEmpty) {
      linkKotlinPlugin(pluginName, kotlinModules, baseDir: projectDir.path);
    }
    if (hasCpp) {
      linkKotlinLoadLibraries(moduleInfos.where((m) => m.isCpp).map((m) => m.lib).toList(), baseDir: projectDir.path);
    }
    purgeStaleCppKotlinRegistrations(moduleInfos.where((m) => androidCppLibs.contains(m.lib)).toList(), baseDir: projectDir.path);
    linkAndroid(pluginName, moduleInfos.map((m) => m.lib).toList(), baseDir: projectDir.path, moduleInfos: moduleInfos);
  }

  // Patch Desktop
  if (Directory(p.join(projectDir.path, 'windows')).existsSync()) {
    linkWindows(pluginName, moduleInfos.map((m) => m.lib).toList(), nitroNativePath, baseDir: projectDir.path, moduleInfos: moduleInfos);
  }
  if (Directory(p.join(projectDir.path, 'linux')).existsSync()) {
    linkLinux(pluginName, moduleInfos.map((m) => m.lib).toList(), nitroNativePath, baseDir: projectDir.path, moduleInfos: moduleInfos);
  }

  linkClangd(pluginName, moduleInfos: moduleInfos, baseDir: projectDir.path);

  // ── pod install ──────────────────────────────────────────────────────────
  final podfileDirs = findPodfileDirs(projectDir.path);
  for (final dir in podfileDirs) {
    stdout.writeln(cyan('  › pod install (${p.relative(dir, from: projectDir.path)}) …'));
    final podExitCode = await runStreaming(
      'pod',
      ['install'],
      workingDirectory: dir,
    );
    if (podExitCode != 0) {
      stderr.writeln(red('  ⚠  pod install failed in $dir (exit $podExitCode) — continuing'));
    }
  }

  // Detect whether any spec uses NativeImpl.cpp to tailor the next-steps hint
  final libDir = Directory(p.join(projectDir.path, 'lib'));
  final hasCppModules = libDir.existsSync() && libDir.listSync(recursive: true).whereType<File>().where((f) => f.path.endsWith('.native.dart')).any(isCppModule);

  stdout.writeln('');
  stdout.writeln(boldGreen('  ✨ Generation complete!'));
  if (hasCppModules) {
    final pubspecName = _readPluginName(projectDir.path);
    stdout.writeln(gray('     C++ modules: subclass Hybrid<Module>, call ${pubspecName}_register_impl(&impl).'));
    stdout.writeln(gray('     Run nitrogen link to wire bridges into the build system.'));
  } else {
    stdout.writeln(gray('     Run nitrogen link to wire bridges into the build system.'));
  }
  stdout.writeln('');
  return 0;
}