fetchBreakingChangeIssues method

Future<List<Map<String, dynamic>>> fetchBreakingChangeIssues(
  1. String owner,
  2. String repo
)

Fetch closed issues labelled with breaking-change indicators.

Searches for issues with the labels: breaking-change, breaking change, and breaking. Only closed issues are returned because open issues represent unreleased changes.

Returns an empty list on failure.

Implementation

Future<List<Map<String, dynamic>>> fetchBreakingChangeIssues(
  String owner,
  String repo,
) async {
  final cacheKey = 'github_breaking_issues_${owner}_$repo';
  final cached = await _cache.get(cacheKey);
  if (cached != null) {
    final entries = cached['entries'] as List<dynamic>? ?? [];
    return entries.cast<Map<String, dynamic>>();
  }

  final allIssues = <Map<String, dynamic>>[];

  // Try several common label conventions.
  final labels = ['breaking-change', 'breaking change', 'breaking'];

  for (final label in labels) {
    try {
      final encodedLabel = Uri.encodeComponent(label);
      final response = await _http.get(
        '$_apiBase/repos/$owner/$repo/issues'
        '?labels=$encodedLabel&state=closed&per_page=100',
      );
      if (!response.isSuccess) continue;

      final issues = response.jsonList
          .map((e) => e as Map<String, dynamic>)
          .toList();

      // De-duplicate by issue number.
      final existingNumbers =
          allIssues.map((i) => i['number'] as int).toSet();
      for (final issue in issues) {
        final number = issue['number'] as int?;
        if (number != null && !existingNumbers.contains(number)) {
          allIssues.add(issue);
          existingNumbers.add(number);
        }
      }
    } catch (e) {
      Logger.warn(
        'Failed to fetch issues with label "$label" for $owner/$repo: $e',
      );
    }
  }

  Logger.debug(
    'Found ${allIssues.length} breaking-change issues for $owner/$repo',
  );

  await _cache.set(
    cacheKey,
    {'entries': allIssues},
    CacheManager.changelogTtl,
  );
  return allIssues;
}