// ignore_for_file: avoid_print import 'dart:async'; import 'dart:io'; const packages = ['backend', 'frontend', 'shared_models']; Map<int, Process> runnerWatchProcesses = {}; void main(List<String> args) async { // Get args and create bool for if `--watch` passed in final watch = args.contains('--watch'); if (watch) { print('👀 Running build_runner in watch mode for all packages...\n'); } else { print('🏃 Running build_runner in build mode for all packages...\n'); } final projectRootPath = await Process.run( 'git', ['rev-parse', '--show-toplevel'], ).then((result) => result.stdout.toString().trim()); for (final package in packages) { unawaited(runBuildRunner('$projectRootPath/$package', watch: watch)); } await Future<void>.delayed(const Duration(seconds: 1)); var i = 0; var j = 0; while (runnerWatchProcesses.isNotEmpty) { await Future<void>.delayed(const Duration(seconds: 1)); i++; if (i == 10) { if (!watch) print(' Waiting for build to finish... ${runnerWatchProcesses.keys}'); if (packages.length != runnerWatchProcesses.entries.length) { j++; } else { j = 0; } if (j == 2) { print( 'Missing build runners for too long in tracked processes. Something probably went wrong. Check logs and fix the script.', ); exitRunners(); } } } print('\n✅ All build_runner tasks completed successfully!'); } Future<void> runBuildRunner(String packagePath, {required bool watch}) async { final package = packagePath.split('/').last; print('📦 Starting build_runner for $package...'); Future<(Process, bool, String)> buildRunnerFunc() => watch ? _runBuildRunnerWatch(packagePath) : _runBuildRunner(packagePath); const maxAttempts = 5; Future<void> attemptRecover(Object? e, int i) async { try { print('\n⚠️ Error in $package:\n$e🤞 Attempting recovery (${i + 1} / $maxAttempts)...'); print(' Waiting 10 seconds before retry...'); await Future<void>.delayed(const Duration(seconds: 10)); // Recovery attempt final cmd = package == 'frontend' ? 'flutter' : 'dart'; print('📥 Running $cmd pub get...'); await Process.run( cmd, ['pub', 'get'], workingDirectory: packagePath, ); print(' Retrying build_runner...'); return; } catch (e) { print('Error while trying to recover $package: $e'); } } Object? err; for (var i = 0; i < maxAttempts; i++) { try { final (process, needsRestart, errLogs) = await buildRunnerFunc(); final removed = runnerWatchProcesses.remove(process.pid); assert(removed != null, true); if (needsRestart) { await attemptRecover(errLogs, i); } else { return; } } catch (e) { await attemptRecover(e, i); } } print('\n❌ Failed to recover $packagePath: $err'); exitRunners(); } Future<(Process, bool, String)> _runBuildRunner(String package) async { late final Process process; if (package == 'frontend') { process = await Process.start( 'flutter', ['pub', 'run', 'build_runner', 'build', '--delete-conflicting-outputs'], workingDirectory: package, ); } else { process = await Process.start( 'dart', ['run', 'build_runner', 'build', '--delete-conflicting-outputs'], workingDirectory: package, ); } runnerWatchProcesses.addAll({process.pid: process}); final outputLines = <String>[]; final errorLines = <String>[]; process.stdout.transform(const SystemEncoding().decoder).listen(outputLines.add); process.stderr.transform(const SystemEncoding().decoder).listen(errorLines.add); final exitCode = await process.exitCode; if (exitCode != 0) { // Print filtered error output final relevantErrors = errorLines .where((line) => line.contains('ERROR') || line.contains('Exception') || line.contains('Failed')) .join('\n'); print('Build runner failed for $package:\n$relevantErrors'); return (process, true, ''); } // Print success with minimal output final successMessage = outputLines.where((line) => line.contains('Succeeded') || line.contains('Generated')).join('\n').trim(); print(' ${successMessage.isEmpty ? "Completed successfully" : successMessage}'); return (process, false, ''); } Future<(Process, bool, String)> _runBuildRunnerWatch(String packagePath) async { final package = packagePath.split('/').last; late final Process process; if (package == 'frontend') { process = await Process.start( 'flutter', ['pub', 'run', 'build_runner', 'watch', '--delete-conflicting-outputs'], workingDirectory: packagePath, ); } else { process = await Process.start( 'dart', ['run', 'build_runner', 'watch', '--delete-conflicting-outputs'], workingDirectory: packagePath, ); } final logs = <String>[]; runnerWatchProcesses.addAll({process.pid: process}); var restart = false; process.stdout.transform(const SystemEncoding().decoder).listen((line) { if (line.contains('Succeeded') || line.contains('Generated')) { print('🎯 [${package.toUpperCase()}] - ${line.trim()}'); } if (line.contains('SEVERE')) { print('🚩 [${package.toUpperCase()}] - ${line.trim()}'); restart = true; } logs.add(line); }); process.stderr.transform(const SystemEncoding().decoder).listen((line) { if (line.contains('ERROR') || line.contains('Exception') || line.contains('Failed')) { print('🎯 [${package.toUpperCase()}] - ${line.trim()}'); restart = true; } logs.add(line); }); final code = await process.exitCode; if (!restart) { restart = code != 0; } return (process, restart, logs.join('\n')); } void exitRunners() { print('❌ Killing processes'); for (final pEntry in runnerWatchProcesses.entries) { pEntry.value.kill(); } exit(1); }