scan method

Future<ScanReport> scan({
  1. required String projectRoot,
  2. String? configPath,
  3. required bool includeTransitive,
})

Scans the project at projectRoot and returns a report.

Implementation

Future<ScanReport> scan({
  required String projectRoot,
  String? configPath,
  required bool includeTransitive,
}) async {
  final DepSherpaConfig config =
      configService.load(configPath: configPath, projectRoot: projectRoot);
  final ProjectManifest manifest = pubspecService.load(
    projectRoot,
    seedIdentity: config.project,
  );
  final DependencySnapshot snapshot = await depsService.inspect(
    projectRoot: projectRoot,
    manifest: manifest,
  );
  final Map<String, DependencyGraphMetrics> graphMetrics =
      graphService.analyze(snapshot.nodes);
  final List<DependencyRiskEntry> entries = <DependencyRiskEntry>[];

  for (final DependencyNode node in snapshot.nodes.values) {
    if (!includeTransitive && !node.isDirect) {
      continue;
    }
    PackageHealth health = await pubApiService.fetch(node.name, config);
    health = await githubService.enrich(health, config);
    final DependencyProfile profile = DependencyProfile(
      node: node,
      metrics: graphMetrics[node.name] ??
          const DependencyGraphMetrics(
            maxDepth: 0,
            transitiveCount: 0,
            directDependents: 0,
            paths: <List<String>>[],
            centrality: 0,
          ),
      health: health,
    );
    final RiskScore score = scoringService.score(
      dependency: profile,
      project: manifest,
      config: config,
    );
    entries.add(DependencyRiskEntry(dependency: profile, riskScore: score));
  }

  final GraphSummary graph = graphService.summarize(snapshot.nodes);
  final List<DependencyRiskEntry> ranked =
      List<DependencyRiskEntry>.from(entries)
        ..sort((DependencyRiskEntry a, DependencyRiskEntry b) {
          return b.riskScore.finalScore.compareTo(a.riskScore.finalScore);
        });
  final double overallScore = ranked.isEmpty
      ? 0
      : ranked
              .map((DependencyRiskEntry entry) => entry.riskScore.finalScore)
              .reduce((double a, double b) => a + b) /
          ranked.length;

  return ScanReport(
    generatedAt: DateTime.now().toUtc(),
    projectName: manifest.name,
    projectVersion: manifest.version,
    rootPath: p.normalize(projectRoot),
    includeTransitive: includeTransitive,
    offline: config.network.offline,
    graph: graph,
    dependencies: ranked,
    overallScore: overallScore,
    summary: <String, Object?>{
      'dependencyCount': ranked.length,
      'highRiskCount': ranked
          .where((DependencyRiskEntry entry) => entry.riskScore.isHighRisk)
          .length,
      'criticalCount': ranked.where((DependencyRiskEntry entry) {
        return entry.riskScore.classification == RiskClassification.critical;
      }).length,
      'generatedFrom': <String, Object?>{
        'pubspec': p.join(projectRoot, 'pubspec.yaml'),
        'lockfile': p.join(projectRoot, 'pubspec.lock'),
        'depsCommand': manifest.usesFlutter
            ? 'flutter pub deps --json'
            : 'dart pub deps --json',
      },
    },
  );
}