run method
Runs this command.
The return value is wrapped in a Future if necessary and returned by
CommandRunner.runCommand.
Implementation
@override
Future<int> run() async {
final cwd = Directory.current.path;
var hasErrors = false;
stdout.writeln('');
stdout.writeln('\x1B[1mFlAI Doctor\x1B[0m');
stdout.writeln('');
// ── 1. Flutter project check ─────────────────────────────────
final isFlutter = isFlutterProject(cwd);
if (isFlutter) {
_pass('Flutter project detected');
} else {
_fail('Not a Flutter project (no pubspec.yaml with Flutter dependency)');
hasErrors = true;
}
// ── 2. flai.yaml check ───────────────────────────────────────
final configManager = FlaiConfigManager(projectRoot: cwd);
FlaiConfig? config;
if (configManager.exists) {
try {
config = configManager.read();
_pass('flai.yaml is valid');
} catch (e) {
_fail('flai.yaml exists but is invalid: $e');
hasErrors = true;
}
} else {
_fail('flai.yaml not found — run "flai init"');
hasErrors = true;
}
// ── 3. Core files check ──────────────────────────────────────
if (config != null) {
final basePath = p.join(cwd, config.outputDir);
// Check for brick-generated file structure
final expectedCoreFiles = [
p.join('core', 'theme', 'flai_theme.dart'),
p.join('core', 'theme', 'flai_colors.dart'),
p.join('core', 'theme', 'flai_typography.dart'),
p.join('core', 'models', 'message.dart'),
p.join('core', 'models', 'chat_event.dart'),
p.join('providers', 'ai_provider.dart'),
];
var allCorePresent = true;
for (final filePath in expectedCoreFiles) {
final file = File(p.join(basePath, filePath));
if (file.existsSync()) {
_pass('Core file: $filePath');
} else {
_warn('Missing: $filePath');
allCorePresent = false;
hasErrors = true;
}
}
if (!allCorePresent) {
stdout.writeln(
' \x1B[33m Tip: Run "flai init" to regenerate core files.\x1B[0m',
);
}
// ── 4. Installed component checks ────────────────────────────
if (config.installed.isNotEmpty) {
stdout.writeln('');
stdout.writeln('\x1B[1mInstalled components:\x1B[0m');
for (final name in config.installed) {
final info = BrickRegistry.lookup(name);
if (info == null && name != 'flai_init') {
_warn('$name is listed but not in the registry');
} else {
_pass(name);
}
}
}
// ── 5. Pub dependency checks ─────────────────────────────────
final pubspecFile = File(p.join(cwd, 'pubspec.yaml'));
if (pubspecFile.existsSync() && config.installed.isNotEmpty) {
final pubspecContent = pubspecFile.readAsStringSync();
final pubspecYaml = loadYaml(pubspecContent);
final projectDeps =
(pubspecYaml is Map && pubspecYaml['dependencies'] is Map)
? (pubspecYaml['dependencies'] as Map).keys
.cast<String>()
.toSet()
: <String>{};
final missingPubDeps = <String>[];
for (final componentName in config.installed) {
final info = BrickRegistry.lookup(componentName);
if (info == null) continue;
for (final pubDep in info.pubDependencies) {
if (!projectDeps.contains(pubDep)) {
missingPubDeps.add(pubDep);
}
}
}
if (missingPubDeps.isNotEmpty) {
stdout.writeln('');
stdout.writeln('\x1B[1mMissing pub dependencies:\x1B[0m');
for (final dep in missingPubDeps.toSet()) {
_warn('$dep is required but not in pubspec.yaml');
}
hasErrors = true;
} else if (config.installed.isNotEmpty) {
stdout.writeln('');
_pass('All pub dependencies are declared');
}
}
}
// ── Summary ──────────────────────────────────────────────────
stdout.writeln('');
if (hasErrors) {
stdout.writeln(
'\x1B[33mSome issues were found. See above for details.\x1B[0m',
);
} else {
stdout.writeln('\x1B[32mNo issues found!\x1B[0m');
}
stdout.writeln('');
return hasErrors ? 1 : 0;
}