Add `ignore` flag to health workflows (#218)

* Add `ignore` flag to health workflows

* Switch to glob

* Fix multiline

* Add default glob

* Delete multiline

* Fix errors

* Fix health commenting

* Propagate ignore

* Switch to specific ignores

* Add defaults

* Add test repos

* Start adding health tests

* Add golden files

* Fix changelog

* Fix coverage

* Fix breaking

* More fixes

* Merge

* Fixes

* Move

* Fix tests

* Ignore test data licenses

* Add golden without license

* Fix license

* Rename

* Fix

* Fix analyze issue

* Add args printing

* Add defaults

* Add more defaults

* Remove empty

* Match recursive

* Add docs

* Add link to docs
diff --git a/.github/workflows/health.yaml b/.github/workflows/health.yaml
index 87ce1e6..3e848b0 100644
--- a/.github/workflows/health.yaml
+++ b/.github/workflows/health.yaml
@@ -1,4 +1,4 @@
-# A CI configuration to check PR health.
+# A CI configuration to check PR health. Check the docs at https://github.com/dart-lang/ecosystem/tree/main/pkgs/firehose#health.
 
 name: Health
 
@@ -27,7 +27,13 @@
 #       coverage_web: false
 #       upload_coverage: false
 #       use-flutter: true
-
+#       use-flutter: true
+#       use-flutter: true
+#       ignore_license: "**.g.dart"
+#       ignore_coverage: "**.mock.dart,**.g.dart"
+#       ignore_packages: "pkgs/helper_package"
+#       checkout_submodules: false
+#       experiments: "native-assets"
 
 on:
   workflow_call:
@@ -82,6 +88,21 @@
         default: false
         required: false
         type: boolean
+      ignore_license:
+        description: Which files to ignore for the license check.
+        default: "\"\""
+        required: false
+        type: string
+      ignore_coverage:
+        description: Which files to ignore for the coverage check.
+        default: "\"\""
+        required: false
+        type: string
+      ignore_packages:
+        description: Which packages to ignore.
+        default: "\"\""
+        required: false
+        type: string
       checkout_submodules:
         description: Whether to checkout submodules of git repositories.
         default: false
@@ -104,6 +125,7 @@
       warn_on: ${{ inputs.warn_on }}
       local_debug: ${{ inputs.local_debug }}
       use-flutter: ${{ inputs.use-flutter }}
+      ignore_packages: ${{ inputs.ignore_packages }}
       checkout_submodules: ${{ inputs.checkout_submodules }}
 
   changelog:
@@ -116,6 +138,7 @@
       warn_on: ${{ inputs.warn_on }}
       local_debug: ${{ inputs.local_debug }}
       use-flutter: ${{ inputs.use-flutter }}
+      ignore_packages: ${{ inputs.ignore_packages }}
       checkout_submodules: ${{ inputs.checkout_submodules }}
 
   license:
@@ -128,6 +151,8 @@
       warn_on: ${{ inputs.warn_on }}
       local_debug: ${{ inputs.local_debug }}
       use-flutter: ${{ inputs.use-flutter }}
+      ignore_license: ${{ inputs.ignore_license }}
+      ignore_packages: ${{ inputs.ignore_packages }}
       checkout_submodules: ${{ inputs.checkout_submodules }}
 
   coverage:
@@ -142,6 +167,8 @@
       coverage_web: ${{ inputs.coverage_web }}
       local_debug: ${{ inputs.local_debug }}
       use-flutter: ${{ inputs.use-flutter }}
+      ignore_coverage: ${{ inputs.ignore_coverage }}
+      ignore_packages: ${{ inputs.ignore_packages }}
       checkout_submodules: ${{ inputs.checkout_submodules }}
       experiments: ${{ inputs.experiments }}
 
@@ -155,6 +182,7 @@
       warn_on: ${{ inputs.warn_on }}
       local_debug: ${{ inputs.local_debug }}
       use-flutter: ${{ inputs.use-flutter }}
+      ignore_packages: ${{ inputs.ignore_packages }}
       checkout_submodules: ${{ inputs.checkout_submodules }}
 
   do-not-submit:
@@ -167,6 +195,7 @@
       warn_on: ${{ inputs.warn_on }}
       local_debug: ${{ inputs.local_debug }}
       use-flutter: ${{ inputs.use-flutter }}
+      ignore_packages: ${{ inputs.ignore_packages }}
       checkout_submodules: ${{ inputs.checkout_submodules }}
 
   comment:
diff --git a/.github/workflows/health_base.yaml b/.github/workflows/health_base.yaml
index b8faf86..589eabd 100644
--- a/.github/workflows/health_base.yaml
+++ b/.github/workflows/health_base.yaml
@@ -49,6 +49,21 @@
         default: false
         required: false
         type: boolean
+      ignore_license:
+        description: Which files to ignore for the license check.
+        default: "\"\""
+        required: false
+        type: string
+      ignore_coverage:
+        description: Which files to ignore for the coverage check.
+        default: "\"\""
+        required: false
+        type: string
+      ignore_packages:
+        description: Which packages to ignore.
+        default: "\"\""
+        required: false
+        type: string
       checkout_submodules:
         description: Whether to checkout submodules of git repositories.
         default: false
@@ -124,6 +139,9 @@
             ${{ fromJSON('{"true":"--coverage_web","false":""}')[inputs.coverage_web] }} \
             --fail_on ${{ inputs.fail_on }} \
             --warn_on ${{ inputs.warn_on }} \
+            --ignore_license ${{ inputs.ignore_license }} \
+            --ignore_coverage ${{ inputs.ignore_coverage }} \
+            --ignore_packages ${{ inputs.ignore_packages }} \
             --experiments ${{ inputs.experiments }}
 
       - run: test -f current_repo/output/comment.md || echo $'The ${{ inputs.check }} workflow has encountered an exception and did not complete.' >> current_repo/output/comment.md
diff --git a/.github/workflows/health_internal.yaml b/.github/workflows/health_internal.yaml
index a633933..270a0b8 100644
--- a/.github/workflows/health_internal.yaml
+++ b/.github/workflows/health_internal.yaml
@@ -15,3 +15,5 @@
       checks: version,changelog,license,coverage,breaking,do-not-submit
       fail_on: version,changelog,do-not-submit
       warn_on: license,coverage,breaking
+      ignore_license: 'pkgs/firehose/test_data'
+      ignore_coverage: 'pkgs/firehose/bin'
diff --git a/pkgs/firehose/CHANGELOG.md b/pkgs/firehose/CHANGELOG.md
index 2d232c9..ca3a401 100644
--- a/pkgs/firehose/CHANGELOG.md
+++ b/pkgs/firehose/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.6.1
+
+- Add `ignore` flags to the health workflow.
+
 ## 0.6.0
 
 - Make the health workflow testable with golden tests.
diff --git a/pkgs/firehose/README.md b/pkgs/firehose/README.md
index 51e5304..30d7abd 100644
--- a/pkgs/firehose/README.md
+++ b/pkgs/firehose/README.md
@@ -160,10 +160,12 @@
 
 When run from a PR, this tool will check a configurable subset of the following
 
-* If the package versioning is correct and consistent, see `firehose` description above.
-* If a changelog entry has been added.
-* If all `.dart` files have a license header.
+* The package versioning is correct and consistent, see `firehose` description above.
+* A changelog entry has been added.
+* All `.dart` files have a license header.
 * How the test coverage is affected by the PR.
+* The package versioning takes into account any breaking changes in the PR.
+* The PR contains `DO_NOT_SUBMIT` strings in the files or the description.
 
 This tool can work with either single package repos or with mono-repos (repos
 containing several packages).
@@ -186,6 +188,22 @@
 #     checks: "version,changelog,license,coverage"
 ```
 
+### Options
+
+| Name | Type | Description | Example |
+| ------------- | ------------- | ------------- | ------------- |
+| checks  | List of strings  | What to check for in the PR health check | `"version,changelog,license,coverage,breaking,do-not-submit"` |
+| fail_on  | List of strings  | Which checks should lead to failure | `"version,changelog,do-not-submit"` |
+| warn_on  | List of strings  | Which checks should not fail, but only warn | `"license,coverage,breaking"` |
+| upload_coverage  | boolean  | Whether to upload the coverage to [coveralls](https://coveralls.io/) | `true` |
+| coverage_web  | boolean  | Whether to run `dart test -p chrome` for coverage | `false` |
+| use-flutter  | boolean  | Whether to setup Flutter in this workflow | `false` |
+| ignore_license  | List of globs  | | `"**.g.dart"` |
+| ignore_coverage  | List of globs  | Which files to ignore for the license check | `"**.mock.dart,**.g.dart"` |
+| ignore_packages  | List of globs  | Which packages to ignore | `"pkgs/helper_package"` |
+| checkout_submodules  | boolean  | Whether to checkout submodules of git repositories | `false` |
+| experiments  | List of strings  | Which experiments should be enabled for Dart | `"native-assets"` |
+
 ### Workflow docs
 
 The description of the common workflow for repos using this tool can be found at
diff --git a/pkgs/firehose/bin/health.dart b/pkgs/firehose/bin/health.dart
index e1260dc..5a36b21 100644
--- a/pkgs/firehose/bin/health.dart
+++ b/pkgs/firehose/bin/health.dart
@@ -16,6 +16,21 @@
       help: 'Check PR health.',
     )
     ..addMultiOption(
+      'ignore_packages',
+      defaultsTo: [],
+      help: 'Which packages to ignore.',
+    )
+    ..addMultiOption(
+      'ignore_license',
+      defaultsTo: [],
+      help: 'Which files to ignore for the license check.',
+    )
+    ..addMultiOption(
+      'ignore_coverage',
+      defaultsTo: [],
+      help: 'Which files to ignore for the coverage check.',
+    )
+    ..addMultiOption(
       'warn_on',
       allowed: checkTypes,
       help: 'Which checks to display warnings on',
@@ -33,14 +48,15 @@
       'coverage_web',
       help: 'Whether to run web tests for coverage',
     );
-  var parsedArgs = argParser.parse(arguments);
-  var check = parsedArgs['check'] as String;
-  var warnOn = parsedArgs['warn_on'] as List<String>;
-  var failOn = parsedArgs['fail_on'] as List<String>;
-  var experiments = (parsedArgs['experiments'] as List<String>)
-      .where((name) => name.isNotEmpty)
-      .toList();
-  var coverageWeb = parsedArgs['coverage_web'] as bool;
+  final parsedArgs = argParser.parse(arguments);
+  final check = parsedArgs['check'] as String;
+  final warnOn = parsedArgs['warn_on'] as List<String>;
+  final failOn = parsedArgs['fail_on'] as List<String>;
+  final ignorePackages = _listNonEmpty(parsedArgs, 'ignore_packages');
+  final ignoreLicense = _listNonEmpty(parsedArgs, 'ignore_license');
+  final ignoreCoverage = _listNonEmpty(parsedArgs, 'ignore_coverage');
+  final experiments = _listNonEmpty(parsedArgs, 'experiments');
+  final coverageWeb = parsedArgs['coverage_web'] as bool;
   if (warnOn.toSet().intersection(failOn.toSet()).isNotEmpty) {
     throw ArgumentError('The checks for which warnings are displayed and the '
         'checks which lead to failure must be disjoint.');
@@ -51,7 +67,13 @@
     warnOn,
     failOn,
     coverageWeb,
+    ignorePackages,
+    ignoreLicense,
+    ignoreCoverage,
     experiments,
     GithubApi(),
   ).healthCheck();
 }
+
+List<String> _listNonEmpty(ArgResults parsedArgs, String key) =>
+    (parsedArgs[key] as List<String>).where((e) => e.isNotEmpty).toList();
diff --git a/pkgs/firehose/lib/firehose.dart b/pkgs/firehose/lib/firehose.dart
index f5b5c0d..3f4ce73 100644
--- a/pkgs/firehose/lib/firehose.dart
+++ b/pkgs/firehose/lib/firehose.dart
@@ -7,6 +7,8 @@
 import 'dart:io';
 import 'dart:math';
 
+import 'package:glob/glob.dart';
+
 import 'src/github.dart';
 import 'src/pub.dart';
 import 'src/repo.dart';
@@ -90,9 +92,12 @@
     github.close();
   }
 
-  Future<VerificationResults> verify(GithubApi github) async {
+  Future<VerificationResults> verify(
+    GithubApi github, [
+    List<Glob> ignoredPackages = const [],
+  ]) async {
     var repo = Repository(directory);
-    var packages = repo.locatePackages();
+    var packages = repo.locatePackages(ignoredPackages);
 
     var pub = Pub();
 
diff --git a/pkgs/firehose/lib/src/github.dart b/pkgs/firehose/lib/src/github.dart
index e3e07b8..4904c83 100644
--- a/pkgs/firehose/lib/src/github.dart
+++ b/pkgs/firehose/lib/src/github.dart
@@ -5,7 +5,9 @@
 
 import 'dart:io';
 
+import 'package:collection/collection.dart';
 import 'package:github/github.dart';
+import 'package:glob/glob.dart';
 import 'package:http/http.dart' as http;
 import 'package:path/path.dart' as path;
 
@@ -107,7 +109,10 @@
     return matchingComment?.id;
   }
 
-  Future<List<GitFile>> listFilesForPR(Directory directory) async =>
+  Future<List<GitFile>> listFilesForPR(
+    Directory directory, [
+    List<Glob> ignoredFiles = const [],
+  ]) async =>
       await _github.pullRequests
           .listFiles(repoSlug!, issueNumber!)
           .map((prFile) => GitFile(
@@ -115,6 +120,8 @@
                 FileStatus.fromString(prFile.status!),
                 directory,
               ))
+          .where((file) =>
+              ignoredFiles.none((glob) => glob.matches(file.filename)))
           .toList();
 
   /// Write a notice message to the github log.
@@ -135,9 +142,8 @@
   final FileStatus status;
   final Directory directory;
 
-  bool isInPackage(Package package) {
-    return path.isWithin(package.directory.path, pathInRepository);
-  }
+  bool isInPackage(Package package) =>
+      path.isWithin(package.directory.path, pathInRepository);
 
   String get pathInRepository => path.join(directory.path, filename);
 
diff --git a/pkgs/firehose/lib/src/health/changelog.dart b/pkgs/firehose/lib/src/health/changelog.dart
index 3474262..95ad14a 100644
--- a/pkgs/firehose/lib/src/health/changelog.dart
+++ b/pkgs/firehose/lib/src/health/changelog.dart
@@ -4,6 +4,7 @@
 
 import 'dart:io';
 
+import 'package:glob/glob.dart';
 import 'package:path/path.dart' as path;
 
 import '../github.dart';
@@ -11,12 +12,13 @@
 
 Future<Map<Package, List<GitFile>>> packagesWithoutChangelog(
   GithubApi github,
+  List<Glob> ignoredPackages,
   Directory directory,
 ) async {
   final repo = Repository(directory);
-  final packages = repo.locatePackages();
+  final packages = repo.locatePackages(ignoredPackages);
 
-  final files = await github.listFilesForPR(directory);
+  final files = await github.listFilesForPR(directory, ignoredPackages);
 
   var packagesWithoutChangedChangelog = collectPackagesWithoutChangelogChanges(
     packages,
diff --git a/pkgs/firehose/lib/src/health/coverage.dart b/pkgs/firehose/lib/src/health/coverage.dart
index 18004d2..bb0610d 100644
--- a/pkgs/firehose/lib/src/health/coverage.dart
+++ b/pkgs/firehose/lib/src/health/coverage.dart
@@ -6,6 +6,7 @@
 import 'dart:io';
 
 import 'package:collection/collection.dart';
+import 'package:glob/glob.dart';
 import 'package:path/path.dart' as path;
 
 import '../github.dart';
@@ -15,21 +16,28 @@
 
 class Coverage {
   final bool coverageWeb;
+  final List<Glob> ignoredFiles;
+  final List<Glob> ignoredPackages;
   final Directory directory;
   final List<String> experiments;
 
-  Coverage(this.coverageWeb, this.directory, this.experiments);
+  Coverage(
+    this.coverageWeb,
+    this.ignoredFiles,
+    this.ignoredPackages,
+    this.directory,
+    this.experiments,
+  );
 
   Future<CoverageResult> compareCoverages(
       GithubApi github, Directory base) async {
-    var files = await github.listFilesForPR(directory);
-
+    var files = await github.listFilesForPR(directory, ignoredFiles);
     return compareCoveragesFor(files, base);
   }
 
   CoverageResult compareCoveragesFor(List<GitFile> files, Directory base) {
     var repository = Repository(directory);
-    var packages = repository.locatePackages();
+    var packages = repository.locatePackages(ignoredPackages);
     print('Found packages $packages at $directory');
 
     var filesOfInterest = files
@@ -41,7 +49,7 @@
     print('The files of interest are $filesOfInterest');
 
     var baseRepository = Repository(base);
-    var basePackages = baseRepository.locatePackages();
+    var basePackages = baseRepository.locatePackages(ignoredFiles);
     print('Found packages $basePackages at $base');
 
     var changedPackages = packages
@@ -120,9 +128,12 @@
             ],
             workingDirectory: package.directory.path,
           );
-          print(resultChrome.stdout);
-          print(resultChrome.stderr);
+          if (resultChrome.exitCode != 0) {
+            print(resultChrome.stderr);
+          }
+          print('Dart test browser: ${resultChrome.stdout}');
         }
+
         print('Run tests with coverage for vm');
         var resultVm = Process.runSync(
           'dart',
@@ -134,8 +145,11 @@
           ],
           workingDirectory: package.directory.path,
         );
-        print('dart test stdout: ${resultVm.stdout}');
-        print('dart test stderr: ${resultVm.stderr}');
+        if (resultVm.exitCode != 0) {
+          print(resultVm.stderr);
+        }
+        print('Dart test VM: ${resultVm.stdout}');
+
         print('Compute coverage from runs');
         var resultLcov = Process.runSync(
           'dart',
@@ -154,8 +168,10 @@
           ],
           workingDirectory: package.directory.path,
         );
-        print('dart coverage stdout: ${resultLcov.stdout}');
-        print('dart coverage stderr: ${resultLcov.stderr}');
+        if (resultLcov.exitCode != 0) {
+          print(resultLcov.stderr);
+        }
+        print('Dart coverage: ${resultLcov.stdout}');
         return parseLCOV(
           path.join(package.directory.path, 'coverage/lcov.info'),
           relativeTo: package.repository.baseDirectory.path,
diff --git a/pkgs/firehose/lib/src/health/health.dart b/pkgs/firehose/lib/src/health/health.dart
index 94ab8d0..47baf97 100644
--- a/pkgs/firehose/lib/src/health/health.dart
+++ b/pkgs/firehose/lib/src/health/health.dart
@@ -9,6 +9,7 @@
 import 'dart:math';
 
 import 'package:collection/collection.dart';
+import 'package:glob/glob.dart';
 import 'package:path/path.dart' as path;
 import 'package:pub_semver/pub_semver.dart';
 
@@ -52,11 +53,23 @@
     this.warnOn,
     this.failOn,
     this.coverageweb,
+    List<String> ignoredPackages,
+    List<String> ignoredLicense,
+    List<String> ignoredCoverage,
     this.experiments,
     this.github, {
     Directory? base,
     String? comment,
-  })  : baseDirectory = base ?? Directory('../base_repo'),
+  })  : ignoredPackages = ignoredPackages
+            .map((pattern) => Glob(pattern, recursive: true))
+            .toList(),
+        ignoredFilesForCoverage = ignoredCoverage
+            .map((pattern) => Glob(pattern, recursive: true))
+            .toList(),
+        ignoredFilesForLicense = ignoredLicense
+            .map((pattern) => Glob(pattern, recursive: true))
+            .toList(),
+        baseDirectory = base ?? Directory('../base_repo'),
         commentPath = comment ??
             path.join(
               directory.path,
@@ -69,6 +82,9 @@
   final List<String> warnOn;
   final List<String> failOn;
   final bool coverageweb;
+  final List<Glob> ignoredPackages;
+  final List<Glob> ignoredFilesForLicense;
+  final List<Glob> ignoredFilesForCoverage;
   final Directory baseDirectory;
   final List<String> experiments;
 
@@ -78,7 +94,15 @@
     if (!expectEnv(github.issueNumber?.toString(), 'ISSUE_NUMBER')) return;
     if (!expectEnv(github.sha, 'GITHUB_SHA')) return;
 
-    print('Start health check for the check $check');
+    print('Start health check for the check $check with');
+    print(' warnOn: $warnOn');
+    print(' failOn: $failOn');
+    print(' coverageweb: $coverageweb');
+    print(' ignoredPackages: $ignoredPackages');
+    print(' ignoredForLicense: $ignoredFilesForLicense');
+    print(' ignoredForCoverage: $ignoredFilesForCoverage');
+    print(' baseDirectory: $baseDirectory');
+    print(' experiments: $experiments');
     print('Checking for $check');
     if (!github.prLabels.contains('skip-$check-check')) {
       final firstResult = await checkFor(check)();
@@ -121,7 +145,8 @@
 
   Future<HealthCheckResult> validateCheck() async {
     //TODO: Add Flutter support for PR health checks
-    var results = await Firehose(directory, false).verify(github);
+    var results =
+        await Firehose(directory, false).verify(github, ignoredPackages);
 
     var markdownTable = '''
 | Package | Version | Status |
@@ -139,9 +164,9 @@
   }
 
   Future<HealthCheckResult> breakingCheck() async {
-    final filesInPR = await github.listFilesForPR(directory);
+    final filesInPR = await github.listFilesForPR(directory, ignoredPackages);
     final changeForPackage = <Package, BreakingChange>{};
-    for (var package in packagesContaining(filesInPR)) {
+    for (var package in packagesContaining(filesInPR, ignoredPackages)) {
       print('Look for changes in $package with base $baseDirectory');
       var relativePath =
           path.relative(package.directory.path, from: directory.path);
@@ -211,8 +236,11 @@
   }
 
   Future<HealthCheckResult> licenseCheck() async {
-    var files = await github.listFilesForPR(directory);
-    var allFilePaths = await getFilesWithoutLicenses(directory);
+    var files = await github.listFilesForPR(directory, ignoredPackages);
+    var allFilePaths = await getFilesWithoutLicenses(
+      directory,
+      [...ignoredFilesForLicense, ...ignoredPackages],
+    );
 
     var groupedPaths = allFilePaths.groupListsBy((filePath) {
       return files.any((f) => f.filename == filePath);
@@ -255,7 +283,11 @@
   }
 
   Future<HealthCheckResult> changelogCheck() async {
-    var filePaths = await packagesWithoutChangelog(github, directory);
+    var filePaths = await packagesWithoutChangelog(
+      github,
+      ignoredPackages,
+      directory,
+    );
 
     final markdownResult = '''
 | Package | Changed Files |
@@ -276,14 +308,14 @@
     final dns = 'DO_NOT${'_'}SUBMIT';
 
     final body = await github.pullrequestBody();
-    final files = await github.listFilesForPR(directory);
-    print('Checking for $dns strings: $files');
+    final files = await github.listFilesForPR(directory, ignoredPackages);
+    print('Checking for DO_NOT${'_'}SUBMIT strings: $files');
     final filesWithDNS = files
         .where((file) =>
             ![FileStatus.removed, FileStatus.unchanged].contains(file.status))
-        .where((file) => File(file.pathInRepository).existsSync())
-        .where((file) =>
-            File(file.pathInRepository).readAsStringSync().contains(dns))
+        .where((file) => File(file.pathInRepository)
+            .readAsStringSync()
+            .contains('DO_NOT${'_'}SUBMIT'))
         .toList();
     print('Found files with $dns: $filesWithDNS');
 
@@ -306,8 +338,13 @@
   }
 
   Future<HealthCheckResult> coverageCheck() async {
-    var coverage = await Coverage(coverageweb, directory, experiments)
-        .compareCoverages(github, baseDirectory);
+    var coverage = await Coverage(
+      coverageweb,
+      ignoredFilesForCoverage,
+      ignoredPackages,
+      directory,
+      experiments,
+    ).compareCoverages(github, directory);
 
     var markdownResult = '''
 | File | Coverage |
@@ -361,11 +398,13 @@
     }
   }
 
-  List<Package> packagesContaining(List<GitFile> filesInPR) {
+  List<Package> packagesContaining(
+    List<GitFile> filesInPR,
+    List<Glob> ignoredPackages,
+  ) {
     var files = filesInPR.where((element) => element.status.isRelevant);
-    final repo = Repository(directory);
-    return repo
-        .locatePackages()
+    return Repository(directory)
+        .locatePackages(ignoredPackages)
         .where((package) => files.any((file) =>
             path.isWithin(package.directory.path, file.pathInRepository)))
         .toList();
diff --git a/pkgs/firehose/lib/src/health/license.dart b/pkgs/firehose/lib/src/health/license.dart
index b7bec93..18a1e43 100644
--- a/pkgs/firehose/lib/src/health/license.dart
+++ b/pkgs/firehose/lib/src/health/license.dart
@@ -5,6 +5,7 @@
 import 'dart:io';
 
 import 'package:collection/collection.dart';
+import 'package:glob/glob.dart';
 import 'package:path/path.dart' as path;
 
 final license = '''
@@ -12,20 +13,24 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.''';
 
-Future<List<String>> getFilesWithoutLicenses(Directory repositoryDir) async {
+Future<List<String>> getFilesWithoutLicenses(
+    Directory repositoryDir, List<Glob> ignoredFiles) async {
   var dartFiles = await repositoryDir
       .list(recursive: true)
-      .where((f) => f.path.endsWith('.dart'))
+      .where((file) => file.path.endsWith('.dart'))
       .toList();
   print('Collecting files without license headers:');
   var filesWithoutLicenses = dartFiles
       .map((file) {
-        var fileContents = File(file.path).readAsStringSync();
-        var fileContainsCopyright = fileContents.contains('// Copyright (c)');
-        if (!fileContainsCopyright) {
-          var relativePath = path.relative(file.path, from: repositoryDir.path);
-          print(relativePath);
-          return relativePath;
+        var relativePath = path.relative(file.path, from: repositoryDir.path);
+        if (ignoredFiles.none((glob) =>
+            glob.matches(path.relative(file.path, from: repositoryDir.path)))) {
+          var fileContents = File(file.path).readAsStringSync();
+          var fileContainsCopyright = fileContents.contains('// Copyright (c)');
+          if (!fileContainsCopyright) {
+            print(relativePath);
+            return relativePath;
+          }
         }
       })
       .whereType<String>()
diff --git a/pkgs/firehose/lib/src/repo.dart b/pkgs/firehose/lib/src/repo.dart
index 8b29aa4..1cdd1df 100644
--- a/pkgs/firehose/lib/src/repo.dart
+++ b/pkgs/firehose/lib/src/repo.dart
@@ -4,6 +4,7 @@
 
 import 'dart:io';
 
+import 'package:glob/glob.dart';
 import 'package:path/path.dart' as path;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:pubspec_parse/pubspec_parse.dart';
@@ -39,9 +40,11 @@
   /// `publish_to: none` key.
   ///
   /// Once we find a package, we don't look for packages in sub-directories.
-  List<Package> locatePackages() {
+  List<Package> locatePackages([List<Glob> ignore = const []]) {
     final packages = <Package>[];
     _recurseAndGather(baseDirectory, packages);
+    packages.removeWhere((package) => ignore.any((glob) => glob.matches(
+        path.relative(package.directory.path, from: baseDirectory.path))));
     packages.sort((a, b) => a.name.compareTo(b.name));
     return packages;
   }
diff --git a/pkgs/firehose/pubspec.yaml b/pkgs/firehose/pubspec.yaml
index 6c8d36c..8c06421 100644
--- a/pkgs/firehose/pubspec.yaml
+++ b/pkgs/firehose/pubspec.yaml
@@ -1,6 +1,6 @@
 name: firehose
 description: A tool to automate publishing of Pub packages from GitHub actions.
-version: 0.6.0
+version: 0.6.1
 repository: https://github.com/dart-lang/ecosystem/tree/main/pkgs/firehose
 
 environment:
@@ -14,6 +14,7 @@
   args: ^2.3.0
   collection: ^1.17.2
   github: ^9.20.0
+  glob: ^2.1.2
   http: ^1.0.0
   path: ^1.8.0
   pub_semver: ^2.1.0
diff --git a/pkgs/firehose/test/coverage_test.dart b/pkgs/firehose/test/coverage_test.dart
index 51922a0..9a06663 100644
--- a/pkgs/firehose/test/coverage_test.dart
+++ b/pkgs/firehose/test/coverage_test.dart
@@ -43,7 +43,7 @@
 }
 
 class FakeHealth extends Coverage {
-  FakeHealth() : super(true, Directory.current, []);
+  FakeHealth() : super(true, [], [], Directory.current, []);
 
   @override
   Map<String, double> getCoverage(Package? package) {
diff --git a/pkgs/firehose/test/health_test.dart b/pkgs/firehose/test/health_test.dart
index a0e7b13..33025f6 100644
--- a/pkgs/firehose/test/health_test.dart
+++ b/pkgs/firehose/test/health_test.dart
@@ -4,60 +4,97 @@
 
 import 'dart:io';
 
+import 'package:collection/collection.dart';
 import 'package:firehose/src/github.dart';
 import 'package:firehose/src/health/health.dart';
 import 'package:github/src/common/model/repos.dart';
+import 'package:glob/glob.dart';
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
-void main() {
-  test('Check health workflow against golden files', () async {
-    final directory = Directory(p.join('test_data', 'test_repo'));
-    var fakeGithubApi = FakeGithubApi(prLabels: [], files: [
-      GitFile(
-        'pkgs/package1/bin/package1.dart',
-        FileStatus.modified,
-        directory,
-      ),
-      GitFile(
-        'pkgs/package2/lib/anotherLib.dart',
-        FileStatus.added,
-        directory,
-      ),
-    ]);
-    await Process.run('dart', ['pub', 'global', 'activate', 'dart_apitool']);
-    await Process.run('dart', ['pub', 'global', 'activate', 'coverage']);
-    for (var check in checkTypes) {
-      var comment = await checkFor(check, fakeGithubApi, directory);
-      var goldenFile = File(p.join('test_data', 'golden', 'comment_$check.md'));
-      var goldenComment = goldenFile.readAsStringSync();
-      if (Platform.environment.containsKey('RESET_GOLDEN')) {
-        goldenFile.writeAsStringSync(comment);
-      } else {
-        expect(comment, goldenComment);
+Future<void> main() async {
+  final directory = Directory(p.join('test_data', 'test_repo'));
+  var fakeGithubApi = FakeGithubApi(prLabels: [], files: [
+    GitFile(
+      'pkgs/package1/bin/package1.dart',
+      FileStatus.modified,
+      directory,
+    ),
+    GitFile(
+      'pkgs/package2/lib/anotherLib.dart',
+      FileStatus.added,
+      directory,
+    ),
+  ]);
+  await Process.run('dart', ['pub', 'global', 'activate', 'dart_apitool']);
+  await Process.run('dart', ['pub', 'global', 'activate', 'coverage']);
+
+  for (var check in checkTypes) {
+    test(
+      'Check health workflow "$check" against golden files',
+      () async => await checkGolden(check, fakeGithubApi, directory),
+      timeout: const Timeout(Duration(minutes: 2)),
+    );
+  }
+
+  test('Ignore license test', () async {
+    await checkGolden(
+      'license',
+      fakeGithubApi,
+      directory,
+      suffix: '_ignore_license',
+      ignoredLicense: ['pkgs/package3/**'],
+    );
+  });
+
+  test(
+    'Ignore packages test',
+    () async {
+      for (var check in checkTypes) {
+        await checkGolden(
+          check,
+          fakeGithubApi,
+          directory,
+          suffix: '_ignore_package',
+          ignoredPackage: ['pkgs/package1'],
+        );
       }
-    }
-  }, timeout: const Timeout(Duration(minutes: 2)));
+    },
+    timeout: const Timeout(Duration(minutes: 2)),
+  );
 }
 
-Future<String> checkFor(
+Future<void> checkGolden(
   String check,
   FakeGithubApi fakeGithubApi,
-  Directory directory,
-) async {
-  final comment = p.join(Directory.systemTemp.path, 'comment_$check.md');
+  Directory directory, {
+  String suffix = '',
+  List<String> ignoredLicense = const [],
+  List<String> ignoredPackage = const [],
+}) async {
+  final commentPath = p.join(Directory.systemTemp.path, 'comment_$check.md');
   await Health(
     directory,
     check,
     [],
     [],
     false,
+    ignoredPackage,
+    ignoredLicense,
+    [],
     [],
     fakeGithubApi,
     base: Directory(p.join('test_data', 'base_test_repo')),
-    comment: comment,
+    comment: commentPath,
   ).healthCheck();
-  return await File(comment).readAsString();
+  var comment = await File(commentPath).readAsString();
+  var goldenFile =
+      File(p.join('test_data', 'golden', 'comment_$check$suffix.md'));
+  if (Platform.environment['RESET_GOLDEN'] == '1') {
+    goldenFile.writeAsStringSync(comment);
+  } else {
+    expect(comment, goldenFile.readAsStringSync());
+  }
 }
 
 class FakeGithubApi implements GithubApi {
@@ -95,8 +132,12 @@
   int? get issueNumber => 1;
 
   @override
-  Future<List<GitFile>> listFilesForPR(Directory directory) async {
-    return files;
+  Future<List<GitFile>> listFilesForPR(Directory directory,
+      [List<Glob> ignoredFiles = const []]) async {
+    return files
+        .where((element) =>
+            ignoredFiles.none((p0) => p0.matches(element.filename)))
+        .toList();
   }
 
   @override
diff --git a/pkgs/firehose/test/license_test.dart b/pkgs/firehose/test/license_test.dart
index b8af03f..ac971b9 100644
--- a/pkgs/firehose/test/license_test.dart
+++ b/pkgs/firehose/test/license_test.dart
@@ -22,7 +22,7 @@
 
   test('Check for licenses', () async {
     var directory = Directory('test/');
-    var filesWithoutLicenses = await getFilesWithoutLicenses(directory);
+    var filesWithoutLicenses = await getFilesWithoutLicenses(directory, []);
     expect(filesWithoutLicenses,
         [path.relative(fileWithoutLicense.path, from: directory.path)]);
   });
diff --git a/pkgs/firehose/test_data/golden/comment_breaking_ignore_package.md b/pkgs/firehose/test_data/golden/comment_breaking_ignore_package.md
new file mode 100644
index 0000000..d336860
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_breaking_ignore_package.md
@@ -0,0 +1,15 @@
+### Breaking changes :warning:
+
+<details open>
+<summary>
+Details
+</summary>
+
+| Package | Change | Current Version | New Version | Needed Version | Looking good? |
+| :--- | :--- | ---: | ---: | ---: | ---: |
+|package2|Non-Breaking|1.0.0|1.0.0|**1.1.0** <br> Got "1.0.0" expected >= "1.1.0" (non-breaking changes)|:warning:|
+
+
+This check can be disabled by tagging the PR with `skip-breaking-check`
+</details>
+
diff --git a/pkgs/firehose/test_data/golden/comment_changelog_ignore_package.md b/pkgs/firehose/test_data/golden/comment_changelog_ignore_package.md
new file mode 100644
index 0000000..d7b7d12
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_changelog_ignore_package.md
@@ -0,0 +1,17 @@
+### Changelog Entry :exclamation:
+
+<details open>
+<summary>
+Details
+</summary>
+
+| Package | Changed Files |
+| :--- | :--- |
+| package:package2 | pkgs/package2/lib/anotherLib.dart |
+
+Changes to files need to be [accounted for](https://github.com/dart-lang/ecosystem/wiki/Changelog) in their respective changelogs.
+
+
+This check can be disabled by tagging the PR with `skip-changelog-check`
+</details>
+
diff --git a/pkgs/firehose/test_data/golden/comment_coverage_ignore_package.md b/pkgs/firehose/test_data/golden/comment_coverage_ignore_package.md
new file mode 100644
index 0000000..c6bdd11
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_coverage_ignore_package.md
@@ -0,0 +1,17 @@
+### Coverage :heavy_check_mark:
+
+<details>
+<summary>
+Details
+</summary>
+
+| File | Coverage |
+| :--- | :--- |
+|pkgs/package2/lib/anotherLib.dart| :green_heart: 100 % |
+
+This check for [test coverage](https://github.com/dart-lang/ecosystem/wiki/Test-Coverage) is informational (issues shown here will not fail the PR).
+
+
+
+</details>
+
diff --git a/pkgs/firehose/test_data/golden/comment_do-not-submit_ignore_package.md b/pkgs/firehose/test_data/golden/comment_do-not-submit_ignore_package.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_do-not-submit_ignore_package.md
diff --git a/pkgs/firehose/test_data/golden/comment_license_ignore_license.md b/pkgs/firehose/test_data/golden/comment_license_ignore_license.md
new file mode 100644
index 0000000..4fa23f1
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_license_ignore_license.md
@@ -0,0 +1,39 @@
+### License Headers :exclamation:
+
+<details open>
+<summary>
+Details
+</summary>
+
+```
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+```
+
+| Files |
+| :--- |
+|pkgs/package1/bin/package1.dart|
+|pkgs/package2/lib/anotherLib.dart|
+
+All source files should start with a [license header](https://github.com/dart-lang/ecosystem/wiki/License-Header).
+
+<details>
+<summary>
+Unrelated files missing license headers
+</summary>
+
+| Files |
+| :--- |
+|pkgs/package1/lib/package1.dart|
+|pkgs/package1/test/package1_test.dart|
+|pkgs/package2/lib/package2.dart|
+|pkgs/package2/test/package2_test.dart|
+</details>
+
+
+
+
+This check can be disabled by tagging the PR with `skip-license-check`
+</details>
+
diff --git a/pkgs/firehose/test_data/golden/comment_license_ignore_package.md b/pkgs/firehose/test_data/golden/comment_license_ignore_package.md
new file mode 100644
index 0000000..59c637d
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_license_ignore_package.md
@@ -0,0 +1,39 @@
+### License Headers :exclamation:
+
+<details open>
+<summary>
+Details
+</summary>
+
+```
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+```
+
+| Files |
+| :--- |
+|pkgs/package2/lib/anotherLib.dart|
+
+All source files should start with a [license header](https://github.com/dart-lang/ecosystem/wiki/License-Header).
+
+<details>
+<summary>
+Unrelated files missing license headers
+</summary>
+
+| Files |
+| :--- |
+|pkgs/package2/lib/package2.dart|
+|pkgs/package2/test/package2_test.dart|
+|pkgs/package3/bin/package3.dart|
+|pkgs/package3/lib/package3.dart|
+|pkgs/package3/test/package3_test.dart|
+</details>
+
+
+
+
+This check can be disabled by tagging the PR with `skip-license-check`
+</details>
+
diff --git a/pkgs/firehose/test_data/golden/comment_version_ignore_package.md b/pkgs/firehose/test_data/golden/comment_version_ignore_package.md
new file mode 100644
index 0000000..17ab16e
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_version_ignore_package.md
@@ -0,0 +1,18 @@
+### Package publish validation :exclamation:
+
+<details open>
+<summary>
+Details
+</summary>
+
+| Package | Version | Status |
+| :--- | ---: | :--- |
+| package:package2 | 1.0.0 | (error) pub publish dry-run failed; add the `publish-ignore-warnings` label to ignore |
+| package:package3 | 1.0.0 | (error) pub publish dry-run failed; add the `publish-ignore-warnings` label to ignore |
+
+Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
+    
+
+This check can be disabled by tagging the PR with `skip-version-check`
+</details>
+