execute method
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;
}