Excise health from firehose (#118)
* Excise health from firehose
* Switch to branch
* Fix call site
* Fix call site 2
* Add changelog entry
* Change PR comment
* Typo
* Refactor severity
* Add version health check
* Change validation markdown
* Add tags to skip
* Prepare for publish
* Changes as per review
diff --git a/.github/workflows/health.yaml b/.github/workflows/health.yaml
index 49f964d..78774f9 100644
--- a/.github/workflows/health.yaml
+++ b/.github/workflows/health.yaml
@@ -41,8 +41,8 @@
required: false
type: string
checks:
- description: What to check for in the PR health check - any subset of "version,changelog,license"
- default: "version,changelog,license"
+ description: What to check for in the PR health check - any subset of "version changelog license"
+ default: "version changelog license"
type: string
required: false
@@ -60,7 +60,7 @@
sdk: ${{ inputs.sdk }}
- name: Install firehose
- run: dart pub global activate --source git https://github.com/dart-lang/ecosystem.git --git-path pkgs/firehose/ --git-ref=unifiedWorkflow #TODO remove, just for testing
+ run: dart pub global activate firehose
- name: Validate packages
if: ${{ github.event_name == 'pull_request' }}
@@ -68,4 +68,4 @@
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ github.event.number }}
PR_LABELS: "${{ join(github.event.pull_request.labels.*.name) }}"
- run: dart pub global run firehose --health ${{ inputs.checks }}
+ run: dart pub global run firehose:health ${{ inputs.checks }}
diff --git a/.github/workflows/health_internal.yaml b/.github/workflows/health_internal.yaml
index 81616c5..f038f24 100644
--- a/.github/workflows/health_internal.yaml
+++ b/.github/workflows/health_internal.yaml
@@ -8,5 +8,3 @@
jobs:
health:
uses: ./.github/workflows/health.yaml
- with:
- checks: "changelog,license"
\ No newline at end of file
diff --git a/pkgs/firehose/CHANGELOG.md b/pkgs/firehose/CHANGELOG.md
index 2fca507..8b7870a 100644
--- a/pkgs/firehose/CHANGELOG.md
+++ b/pkgs/firehose/CHANGELOG.md
@@ -1,5 +1,6 @@
-## 0.3.18-wip
+## 0.3.18
- Add Github workflow for PR health.
+- Refactorings to health workflow.
## 0.3.17
diff --git a/pkgs/firehose/bin/firehose.dart b/pkgs/firehose/bin/firehose.dart
index 5e01d27..e561bd9 100644
--- a/pkgs/firehose/bin/firehose.dart
+++ b/pkgs/firehose/bin/firehose.dart
@@ -20,11 +20,10 @@
var validate = argResults['validate'] == true;
var publish = argResults['publish'] == true;
- var health = argResults['health'] != null;
- if (!validate && !publish && !health) {
- _usage(argParser, error: '''
-Error: one of --validate, --publish, or --health must be specified.''');
+ if (!validate && !publish) {
+ _usage(argParser,
+ error: 'Error: one of --validate or --publish must be specified.');
exit(1);
}
@@ -42,8 +41,6 @@
await firehose.validate();
} else if (publish) {
await firehose.publish();
- } else if (health) {
- await firehose.healthCheck(argResults['health'] as List);
}
} on ArgParserException catch (e) {
_usage(argParser, error: e.message);
@@ -76,11 +73,6 @@
help: 'Validate packages and indicate whether --publish would publish '
'anything.',
)
- ..addMultiOption(
- 'health',
- defaultsTo: ['version', 'license', 'changelog'],
- help: 'Check PR health.',
- )
..addFlag(
'publish',
negatable: false,
diff --git a/pkgs/firehose/bin/health.dart b/pkgs/firehose/bin/health.dart
new file mode 100644
index 0000000..6672f3e
--- /dev/null
+++ b/pkgs/firehose/bin/health.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2023, 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.
+
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:firehose/health.dart';
+
+void main(List<String> arguments) async {
+ var argParser = ArgParser()
+ ..addMultiOption(
+ 'checks',
+ defaultsTo: ['version', 'license', 'changelog'],
+ allowed: ['version', 'license', 'changelog'],
+ help: 'Check PR health.',
+ );
+ var parsedArgs = argParser.parse(arguments);
+
+ await Health(Directory.current)
+ .healthCheck(parsedArgs['checks'] as List<String>);
+}
diff --git a/pkgs/firehose/lib/firehose.dart b/pkgs/firehose/lib/firehose.dart
index 56a51d4..617ca54 100644
--- a/pkgs/firehose/lib/firehose.dart
+++ b/pkgs/firehose/lib/firehose.dart
@@ -8,7 +8,6 @@
import 'dart:math';
import 'package:firehose/src/repo.dart';
-import 'package:path/path.dart' as path;
import 'src/github.dart';
import 'src/pub.dart';
@@ -19,13 +18,6 @@
const String _githubActionsUser = 'github-actions[bot]';
const String _publishBotTag = '## Package publishing';
-const String _publishBotTag2 = '### Package publish validation';
-
-const String _licenseBotTag = '### License Headers';
-
-const String _changelogBotTag = '### Changelog entry';
-
-const String _prHealthTag = '## PR Health';
const String _ignoreWarningsLabel = 'publish-ignore-warnings';
@@ -34,178 +26,6 @@
Firehose(this.directory);
- Future<void> healthCheck(List argResult) async {
- print('Start health check for the checks $argResult');
- var checks = [
- if (argResult.contains('version')) validateCheck,
- if (argResult.contains('license')) licenseCheck,
- if (argResult.contains('changelog')) changelogCheck,
- ];
- await _healthCheck(checks);
- }
-
- Future<void> _healthCheck(
- List<Future<HealthCheckResult> Function(Github)> checks) async {
- var github = Github();
-
- // Do basic validation of our expected env var.
- if (!_expectEnv(github.githubAuthToken, 'GITHUB_TOKEN')) return;
- if (!_expectEnv(github.repoSlug, 'GITHUB_REPOSITORY')) return;
- if (!_expectEnv(github.issueNumber, 'ISSUE_NUMBER')) return;
- if (!_expectEnv(github.sha, 'GITHUB_SHA')) return;
-
- if ((github.actor ?? '').endsWith(_botSuffix)) {
- print('Skipping package validation for ${github.actor} PRs.');
- return;
- }
-
- var checked =
- await Future.wait(checks.map((check) => check(github)).toList());
- await writeInComment(github, checked);
-
- github.close();
- }
-
- Future<HealthCheckResult> validateCheck(Github github) async {
- var results = await _validate(github);
-
- var markdownTable = '''
-| Package | Version | Status | Publish tag (post-merge) |
-| :--- | ---: | :--- | ---: |
-${results.describeAsMarkdown}
-
- ''';
-
- return HealthCheckResult(
- _publishBotTag2,
- results.severity,
- markdownTable,
- );
- }
-
- Future<HealthCheckResult> licenseCheck(Github github) async {
- final license = '''
-// Copyright (c) ${DateTime.now().year}, 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.''';
-
- var filePaths = await _getFilesWithoutLicenses(github);
-
- var markdownResult = '''
-Some `.dart` files were found to not have license headers. Please add the following header to all listed files:
-```
-$license
-```
-
-| Files |
-| :--- |
-${filePaths.map((e) => '|$e|').join('\n')}
-
-Either manually or by running the following in your repository directory
-
-```
-dart pub global activate --source git https://github.com/mosuem/file_licenser
-dart pub global run file_licenser .
-```
-
-'''; //TODO: replace by pub.dev version
-
- return HealthCheckResult(
- _licenseBotTag,
- filePaths.isNotEmpty ? Severity.error : Severity.success,
- markdownResult,
- );
- }
-
- Future<HealthCheckResult> changelogCheck(Github github) async {
- var filePaths = await _packagesWithoutChangelog(github);
-
- final markdownResult = '''
-Changes to these files need to be accounted for in their respective changelogs:
-
-| Package | Files |
-| :--- | :--- |
-${filePaths.entries.map((e) => '| package:${e.key.name} | ${e.value.map((e) => path.relative(e, from: Directory.current.path)).join('<br />')} |').join('\n')}
-''';
-
- return HealthCheckResult(
- _changelogBotTag,
- filePaths.isNotEmpty ? Severity.error : Severity.success,
- markdownResult,
- );
- }
-
- Future<Map<Package, List<String>>> _packagesWithoutChangelog(
- Github github) async {
- final repo = Repository();
- final packages = repo.locatePackages();
-
- final files = await github.listFilesForPR();
- print('Collecting packages without changed changelogs:');
- final packagesWithoutChangedChangelog = packages.where((package) {
- var changelogPath = package.changelog.file.path;
- var changelog =
- path.relative(changelogPath, from: Directory.current.path);
- return !files.contains(changelog);
- }).toList();
- print('Done, found ${packagesWithoutChangedChangelog.length} packages.');
-
- print('Collecting files without license headers in those packages:');
- var packagesWithChanges = <Package, List<String>>{};
- for (final file in files) {
- for (final package in packagesWithoutChangedChangelog) {
- if (fileNeedsEntryInChangelog(package, file)) {
- print(file);
- packagesWithChanges.update(
- package,
- (changedFiles) => [...changedFiles, file],
- ifAbsent: () => [file],
- );
- }
- }
- }
- print('''
-Done, found ${packagesWithChanges.length} packages with a need for a changelog.''');
- return packagesWithChanges;
- }
-
- bool fileNeedsEntryInChangelog(Package package, String file) {
- final directoryPath = package.directory.path;
- final directory =
- path.relative(directoryPath, from: Directory.current.path);
- final isInPackage = path.isWithin(directory, file);
- final isInLib = path.isWithin(path.join(directory, 'lib'), file);
- final isInBin = path.isWithin(path.join(directory, 'bin'), file);
- final isPubspec = file.endsWith('pubspec.yaml');
- final isReadme = file.endsWith('README.md');
- return isInPackage && (isInLib || isInBin || isPubspec || isReadme);
- }
-
- Future<List<String>> _getFilesWithoutLicenses(Github github) async {
- var dir = Directory.current;
- var dartFiles = await dir
- .list(recursive: true)
- .where((f) => f.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: Directory.current.path);
- print(relativePath);
- return relativePath;
- }
- })
- .whereType<String>()
- .toList();
- print('''
-Done, found ${filesWithoutLicenses.length} files without license headers''');
- return filesWithoutLicenses;
- }
-
/// Validate the packages in the repository.
///
/// This method is intended to run in the context of a PR. It will:
@@ -217,22 +37,22 @@
var github = Github();
// Do basic validation of our expected env var.
- if (!_expectEnv(github.githubAuthToken, 'GITHUB_TOKEN')) return;
- if (!_expectEnv(github.repoSlug, 'GITHUB_REPOSITORY')) return;
- if (!_expectEnv(github.issueNumber, 'ISSUE_NUMBER')) return;
- if (!_expectEnv(github.sha, 'GITHUB_SHA')) return;
+ if (!expectEnv(github.githubAuthToken, 'GITHUB_TOKEN')) return;
+ if (!expectEnv(github.repoSlug, 'GITHUB_REPOSITORY')) return;
+ if (!expectEnv(github.issueNumber, 'ISSUE_NUMBER')) return;
+ if (!expectEnv(github.sha, 'GITHUB_SHA')) return;
if ((github.actor ?? '').endsWith(_botSuffix)) {
print('Skipping package validation for ${github.actor} PRs.');
return;
}
- var results = await _validate(github);
+ var results = await verify(github);
var markdownTable = '''
| Package | Version | Status | Publish tag (post-merge) |
| :--- | ---: | :--- | ---: |
-${results.describeAsMarkdown}
+${results.describeAsMarkdown()}
Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
''';
@@ -280,60 +100,7 @@
github.close();
}
- Future<void> writeInComment(
- Github github,
- List<HealthCheckResult> results,
- ) async {
- var commentText = results.map((e) {
- var markdown = e.markdown;
- var s = '''
-<details${e.severity == Severity.error ? ' open' : ''}>
-<summary>
-Details
-</summary>
-
-$markdown
-</details>
-
-''';
- return '${e.tag} ${e.severity.emoji}\n\n$s';
- }).join('\n');
-
- var summary = '$_prHealthTag\n\n$commentText';
- github.appendStepSummary(summary);
-
- var repoSlug = github.repoSlug!;
- var issueNumber = github.issueNumber!;
-
- var existingCommentId = await allowFailure(
- github.findCommentId(
- repoSlug,
- issueNumber,
- user: _githubActionsUser,
- searchTerm: _prHealthTag,
- ),
- logError: print,
- );
-
- if (existingCommentId == null) {
- await allowFailure(
- github.createComment(repoSlug, issueNumber, summary),
- logError: print,
- );
- } else {
- await allowFailure(
- github.updateComment(repoSlug, existingCommentId, summary),
- logError: print,
- );
- }
-
- if (results.any((result) => result.severity == Severity.error) &&
- exitCode == 0) {
- exitCode = 1;
- }
- }
-
- Future<VerificationResults> _validate(Github github) async {
+ Future<VerificationResults> verify(Github github) async {
var repo = Repository();
var packages = repo.locatePackages();
@@ -431,7 +198,7 @@
Future<bool> _publish() async {
var github = Github();
- if (!_expectEnv(github.refName, 'GITHUB_REF_NAME')) return false;
+ if (!expectEnv(github.refName, 'GITHUB_REF_NAME')) return false;
// Validate the git tag.
var tag = Tag(github.refName!);
@@ -507,15 +274,6 @@
}
return result == 0;
}
-
- bool _expectEnv(String? value, String name) {
- if (value == null) {
- print("Expected environment variable not found: '$name'");
- return false;
- } else {
- return true;
- }
- }
}
class VerificationResults {
@@ -530,7 +288,7 @@
bool get hasError => results.any((r) => r.severity == Severity.error);
- String get describeAsMarkdown {
+ String describeAsMarkdown([bool withTag = true]) {
results.sort((a, b) => Enum.compareByIndex(a.severity, b.severity));
return results.map((r) {
@@ -541,20 +299,13 @@
tag = '[$tag]($publishReleaseUri)';
}
+ var tagColumn = withTag ? ' | $tag' : '';
return '| package:${r.package.name} | ${r.package.version} | '
- '$sev${r.message} | $tag |';
+ '$sev${r.message}$tagColumn |';
}).join('\n');
}
}
-class HealthCheckResult {
- final String tag;
- final Severity severity;
- final String markdown;
-
- HealthCheckResult(this.tag, this.severity, this.markdown);
-}
-
class Result {
final Severity severity;
final Package package;
@@ -585,13 +336,11 @@
}
enum Severity {
- success,
- info,
- error;
+ success(':heavy_check_mark:'),
+ info(':heavy_check_mark:'),
+ error(':exclamation:');
- String get emoji => switch (this) {
- Severity.info => ':heavy_check_mark:',
- Severity.error => ':exclamation:',
- success => ':heavy_check_mark:',
- };
+ final String emoji;
+
+ const Severity(this.emoji);
}
diff --git a/pkgs/firehose/lib/health.dart b/pkgs/firehose/lib/health.dart
new file mode 100644
index 0000000..a1c696f
--- /dev/null
+++ b/pkgs/firehose/lib/health.dart
@@ -0,0 +1,261 @@
+// Copyright (c) 2023, 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.
+
+// ignore_for_file: always_declare_return_types
+
+import 'dart:io';
+
+import 'package:firehose/firehose.dart';
+import 'package:firehose/src/repo.dart';
+import 'package:path/path.dart' as path;
+
+import 'src/github.dart';
+import 'src/utils.dart';
+
+const String _botSuffix = '[bot]';
+
+const String _githubActionsUser = 'github-actions[bot]';
+
+const String _publishBotTag2 = '### Package publish validation';
+
+const String _licenseBotTag = '### License Headers';
+
+const String _changelogBotTag = '### Changelog Entry';
+
+const String _prHealthTag = '## PR Health';
+
+class Health {
+ final Directory directory;
+
+ Health(this.directory);
+
+ Future<void> healthCheck(List args) async {
+ var github = Github();
+
+ // Do basic validation of our expected env var.
+ if (!expectEnv(github.githubAuthToken, 'GITHUB_TOKEN')) return;
+ if (!expectEnv(github.repoSlug, 'GITHUB_REPOSITORY')) return;
+ if (!expectEnv(github.issueNumber, 'ISSUE_NUMBER')) return;
+ if (!expectEnv(github.sha, 'GITHUB_SHA')) return;
+
+ if ((github.actor ?? '').endsWith(_botSuffix)) {
+ print('Skipping package validation for ${github.actor} PRs.');
+ return;
+ }
+
+ print('Start health check for the checks $args');
+ var checks = [
+ if (args.contains('version') &&
+ !github.prLabels.contains('skip-validate-check'))
+ validateCheck,
+ if (args.contains('license') &&
+ !github.prLabels.contains('skip-license-check'))
+ licenseCheck,
+ if (args.contains('changelog') &&
+ !github.prLabels.contains('skip-changelog-check'))
+ changelogCheck,
+ ];
+
+ var checked =
+ await Future.wait(checks.map((check) => check(github)).toList());
+ await writeInComment(github, checked);
+
+ github.close();
+ }
+
+ Future<HealthCheckResult> validateCheck(Github github) async {
+ var results = await Firehose(directory).verify(github);
+
+ var markdownTable = '''
+| Package | Version | Status |
+| :--- | ---: | :--- |
+${results.describeAsMarkdown(false)}
+
+Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
+ ''';
+
+ return HealthCheckResult(
+ _publishBotTag2,
+ results.severity,
+ markdownTable,
+ );
+ }
+
+ Future<HealthCheckResult> licenseCheck(Github github) async {
+ final license = '''
+// Copyright (c) ${DateTime.now().year}, 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.''';
+
+ var filePaths = await _getFilesWithoutLicenses(github);
+
+ var markdownResult = '''
+```
+$license
+```
+
+| Files |
+| :--- |
+${filePaths.isNotEmpty ? filePaths.map((e) => '|$e|').join('\n') : '| _no missing headers_ |'}
+
+All source files should start with a [license header](https://github.com/dart-lang/ecosystem/wiki/License-Header).
+''';
+
+ return HealthCheckResult(
+ _licenseBotTag,
+ filePaths.isNotEmpty ? Severity.error : Severity.success,
+ markdownResult,
+ );
+ }
+
+ Future<HealthCheckResult> changelogCheck(Github github) async {
+ var filePaths = await _packagesWithoutChangelog(github);
+
+ final markdownResult = '''
+| Package | Changed Files |
+| :--- | :--- |
+${filePaths.entries.map((e) => '| package:${e.key.name} | ${e.value.map((e) => path.relative(e, from: Directory.current.path)).join('<br />')} |').join('\n')}
+
+Changes to files need to be [accounted for](https://github.com/dart-lang/ecosystem/wiki/Changelog) in their respective changelogs.
+''';
+
+ return HealthCheckResult(
+ _changelogBotTag,
+ filePaths.isNotEmpty ? Severity.error : Severity.success,
+ markdownResult,
+ );
+ }
+
+ Future<Map<Package, List<String>>> _packagesWithoutChangelog(
+ Github github) async {
+ final repo = Repository();
+ final packages = repo.locatePackages();
+
+ final files = await github.listFilesForPR();
+ print('Collecting packages without changed changelogs:');
+ final packagesWithoutChangedChangelog = packages.where((package) {
+ var changelogPath = package.changelog.file.path;
+ var changelog =
+ path.relative(changelogPath, from: Directory.current.path);
+ return !files.contains(changelog);
+ }).toList();
+ print('Done, found ${packagesWithoutChangedChangelog.length} packages.');
+
+ print('Collecting files without license headers in those packages:');
+ var packagesWithChanges = <Package, List<String>>{};
+ for (final file in files) {
+ for (final package in packagesWithoutChangedChangelog) {
+ if (fileNeedsEntryInChangelog(package, file)) {
+ print(file);
+ packagesWithChanges.update(
+ package,
+ (changedFiles) => [...changedFiles, file],
+ ifAbsent: () => [file],
+ );
+ }
+ }
+ }
+ print('''
+Done, found ${packagesWithChanges.length} packages with a need for a changelog.''');
+ return packagesWithChanges;
+ }
+
+ bool fileNeedsEntryInChangelog(Package package, String file) {
+ final directoryPath = package.directory.path;
+ final directory =
+ path.relative(directoryPath, from: Directory.current.path);
+ final isInPackage = path.isWithin(directory, file);
+ final isInLib = path.isWithin(path.join(directory, 'lib'), file);
+ final isInBin = path.isWithin(path.join(directory, 'bin'), file);
+ final isPubspec = file.endsWith('pubspec.yaml');
+ final isReadme = file.endsWith('README.md');
+ return isInPackage && (isInLib || isInBin || isPubspec || isReadme);
+ }
+
+ Future<List<String>> _getFilesWithoutLicenses(Github github) async {
+ var dir = Directory.current;
+ var dartFiles = await dir
+ .list(recursive: true)
+ .where((f) => f.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: Directory.current.path);
+ print(relativePath);
+ return relativePath;
+ }
+ })
+ .whereType<String>()
+ .toList();
+ print('''
+Done, found ${filesWithoutLicenses.length} files without license headers''');
+ return filesWithoutLicenses;
+ }
+
+ Future<void> writeInComment(
+ Github github,
+ List<HealthCheckResult> results,
+ ) async {
+ var commentText = results.map((e) {
+ var markdown = e.markdown;
+ var s = '''
+<details${e.severity == Severity.error ? ' open' : ''}>
+<summary>
+Details
+</summary>
+
+$markdown
+</details>
+
+''';
+ return '${e.tag} ${e.severity.emoji}\n\n$s';
+ }).join('\n');
+
+ var summary = '$_prHealthTag\n\n$commentText';
+ github.appendStepSummary(summary);
+
+ var repoSlug = github.repoSlug!;
+ var issueNumber = github.issueNumber!;
+
+ var existingCommentId = await allowFailure(
+ github.findCommentId(
+ repoSlug,
+ issueNumber,
+ user: _githubActionsUser,
+ searchTerm: _prHealthTag,
+ ),
+ logError: print,
+ );
+
+ if (existingCommentId == null) {
+ await allowFailure(
+ github.createComment(repoSlug, issueNumber, summary),
+ logError: print,
+ );
+ } else {
+ await allowFailure(
+ github.updateComment(repoSlug, existingCommentId, summary),
+ logError: print,
+ );
+ }
+
+ if (results.any((result) => result.severity == Severity.error) &&
+ exitCode == 0) {
+ exitCode = 1;
+ }
+ }
+}
+
+class HealthCheckResult {
+ final String tag;
+ final Severity severity;
+ final String markdown;
+
+ HealthCheckResult(this.tag, this.severity, this.markdown);
+}
diff --git a/pkgs/firehose/lib/src/utils.dart b/pkgs/firehose/lib/src/utils.dart
index afd3d94..7905487 100644
--- a/pkgs/firehose/lib/src/utils.dart
+++ b/pkgs/firehose/lib/src/utils.dart
@@ -78,3 +78,12 @@
return null;
}
}
+
+bool expectEnv(String? value, String name) {
+ if (value == null) {
+ print("Expected environment variable not found: '$name'");
+ return false;
+ } else {
+ return true;
+ }
+}
diff --git a/pkgs/firehose/pubspec.yaml b/pkgs/firehose/pubspec.yaml
index 205f46e..5fafbc8 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.3.18-wip
+version: 0.3.18
repository: https://github.com/dart-lang/ecosystem/tree/main/pkgs/firehose
environment: