run method
Runs this command.
The return value is wrapped in a Future if necessary and returned by
CommandRunner.runCommand.
Implementation
@override
Future<void> run() async {
final targetPath = argResults?['path'] as String;
final absolutePath = p.absolute(targetPath);
print('Scanning project at: $absolutePath');
final libPath = p.join(absolutePath, 'lib');
if (!Directory(libPath).existsSync()) {
print('Error: Could not find lib/ directory at $libPath');
print('Make sure you are running this in a Flutter or Dart project root.');
return;
}
print('Detecting Architecture & Tech Stack...');
final archPattern = ArchitectureDetector().detect(absolutePath);
final techStack = TechStackDetector().detect(absolutePath);
print('Parsing Dart files...');
final parser = DartParser(rootPath: absolutePath);
final nodes = await parser.parseProject();
print('Parsed ${nodes.length} files.');
print('Generating graph...');
final graph = DependencyGraph(nodes: nodes);
print('Analyzing Learning Path...');
final learningPath = LearningPathDetector().detect(graph);
final summaryJson = {
'architecture': archPattern.name,
'state_management': techStack.stateManagement,
'routing': techStack.routing,
'core_packages': techStack.corePackages,
'learning_path': learningPath,
};
final graphData = graph.toJson(projectSummaryJson: summaryJson);
final metrics = graphData['metrics'] as Map<String, dynamic>;
print('\nš DevLens Architecture Report\n');
print('Project Overview');
print('-------------------------');
print('Files: ${metrics['total_nodes']}');
print('Screens: ${metrics['total_screens']}');
print('Models: ${metrics['total_models']}');
print('Repositories: ${metrics['total_repositories']}');
print('Services: ${metrics['total_services']}');
print('Widgets: ${metrics['total_widgets']}');
print('\nArchitecture Health');
print('-------------------------');
final circularCount = (metrics['circular_dependencies'] as List).length;
print('Circular Dependencies: $circularCount ${circularCount > 0 ? '(WARNING)' : ''}');
final coupledCount = (metrics['highly_coupled'] as List).length;
print('Highly Coupled Files: $coupledCount ${coupledCount > 0 ? '(WARNING)' : ''}');
final godCount = (metrics['god_classes'] as List).length;
print('God Classes (>15 imports): $godCount ${godCount > 0 ? '(WARNING)' : ''}');
print('\nTop Connected Files (Relevance)');
print('-------------------------');
final ranked = metrics['all_nodes_ranked'] as List;
for (int i = 0; i < ranked.length && i < 3; i++) {
final node = ranked[i];
final id = node['id'] as String;
final name = id.split('/').last;
print('${i + 1}. $name (${node['score']} connections)');
}
print('\nExporting reports...');
final outDir = p.join(absolutePath, '.dep_explorer');
await JsonExporter.export(graph, graphData, outDir);
await HtmlExporter.export(graphData, outDir);
print('\nScan complete! Reports saved to $outDir');
final htmlPath = p.normalize(p.join(outDir, 'index.html'));
final fileUri = 'file:///${htmlPath.replaceAll('\\', '/')}';
print('\nš Visualization ready! Opening in your browser...');
try {
if (Platform.isWindows) {
await Process.run('start', [fileUri], runInShell: true);
} else if (Platform.isMacOS) {
await Process.run('open', [fileUri]);
} else if (Platform.isLinux) {
await Process.run('xdg-open', [fileUri]);
}
} catch (e) {
print('Could not open browser automatically. You can open this file manually:');
print(' $fileUri');
}
}