fartstack/backend/scripts/run_build_runner.dart
2025-02-06 17:36:53 -07:00

203 lines
5.9 KiB
Dart

// 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);
}