diff --git a/pkgs/firehose/CHANGELOG.md b/pkgs/firehose/CHANGELOG.md index 8b7870a6..e8059a10 100644 --- a/pkgs/firehose/CHANGELOG.md +++ b/pkgs/firehose/CHANGELOG.md @@ -1,6 +1,11 @@ +## 0.3.19 +- Clean-up and optimizations. +- Stop depending on `package:collection` now that SDK 3.0.0 has `firstOrNull`. + ## 0.3.18 - Add Github workflow for PR health. - Refactorings to health workflow. +- Require Dart `3.0.0`. ## 0.3.17 diff --git a/pkgs/firehose/lib/firehose.dart b/pkgs/firehose/lib/firehose.dart index 617ca541..fbbd74cd 100644 --- a/pkgs/firehose/lib/firehose.dart +++ b/pkgs/firehose/lib/firehose.dart @@ -288,18 +288,21 @@ class VerificationResults { bool get hasError => results.any((r) => r.severity == Severity.error); - String describeAsMarkdown([bool withTag = true]) { + String describeAsMarkdown({bool withTag = true}) { results.sort((a, b) => Enum.compareByIndex(a.severity, b.severity)); return results.map((r) { var sev = r.severity == Severity.error ? '(error) ' : ''; - var tag = r.gitTag == null ? '' : '`${r.gitTag}`'; - var publishReleaseUri = r.publishReleaseUri; - if (publishReleaseUri != null) { - tag = '[$tag]($publishReleaseUri)'; - } + var tagColumn = ''; + if (withTag) { + var tag = r.gitTag == null ? '' : '`${r.gitTag}`'; + var publishReleaseUri = r.publishReleaseUri; + if (publishReleaseUri != null) { + tag = '[$tag]($publishReleaseUri)'; + } - var tagColumn = withTag ? ' | $tag' : ''; + tagColumn = ' | $tag'; + } return '| package:${r.package.name} | ${r.package.version} | ' '$sev${r.message}$tagColumn |'; }).join('\n'); diff --git a/pkgs/firehose/lib/health.dart b/pkgs/firehose/lib/health.dart index a1c696fb..8e4590fa 100644 --- a/pkgs/firehose/lib/health.dart +++ b/pkgs/firehose/lib/health.dart @@ -70,7 +70,7 @@ class Health { var markdownTable = ''' | Package | Version | Status | | :--- | ---: | :--- | -${results.describeAsMarkdown(false)} +${results.describeAsMarkdown(withTag: false)} Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation. '''; diff --git a/pkgs/firehose/lib/src/changelog.dart b/pkgs/firehose/lib/src/changelog.dart index efb17e85..07a6986c 100644 --- a/pkgs/firehose/lib/src/changelog.dart +++ b/pkgs/firehose/lib/src/changelog.dart @@ -4,69 +4,79 @@ import 'dart:io'; -import 'package:collection/collection.dart'; - class Changelog { + static const _headerLinePrefix = '## '; + final File file; Changelog(this.file); bool get exists => file.existsSync(); + /// Pattern recognizing some SemVer formats. + /// + /// Accepts: + /// > digits '.' digits '.' digits + /// + /// optionally followed by `-` or `+` character + /// and one or more "word characters", which are + /// ASCII letters (`a`-`z`, `A`-`Z`), digits (`0`-`9`), + /// underscore (`_`) and dollar-sign (`$`). + /// + /// This is not all complete SemVer version strings, + /// since it doesn't allow `.` in the continuation, + /// or a `+...` sequence after a `-` sequence. + /// It should be enough for the user-cases we need it for + /// in this package. + static final _versionRegex = RegExp(r'\d+\.\d+\.\d+(?:[\-+]\w+)?'); + String? get latestVersion { var input = latestHeading; - if (input == null) { - return null; + if (input != null) { + var match = _versionRegex.firstMatch(input); + if (match != null) { + var version = match[0]; + return version; + } } - final versionRegex = RegExp(r'[0-9]+\.[0-9]+\.[0-9]+([-\+]\w+)?'); - - var match = versionRegex.firstMatch(input); - - if (match != null) { - var version = match.group(0); - return version; - } return null; } String? get latestHeading { var sections = _parseSections(); - // Remove all leading "#" - return sections.firstOrNull?.title.replaceAll(RegExp(r'^#*'), '').trim(); + var section = sections.firstOrNull; + if (section == null) return null; + // Remove the leading `_headerLinePrefix`, then trim left-over whitespace. + var title = section.title; + assert(title.startsWith(_headerLinePrefix)); + return title.substring(_headerLinePrefix.length).trim(); } - List get latestChangeEntries { - var sections = _parseSections(); - return sections.isEmpty ? [] : sections.first.entries; - } + List get latestChangeEntries => + _parseSections().firstOrNull?.entries ?? []; Iterable<_Section> _parseSections() sync* { if (!exists) return; _Section? section; - for (var line in file.readAsLinesSync().where((line) => line.isNotEmpty)) { - if (line.startsWith('## ')) { + for (var line in file.readAsLinesSync()) { + if (line.isEmpty) continue; + if (line.startsWith(_headerLinePrefix)) { if (section != null) yield section; section = _Section(line); - } else if (section != null) { - section.entries.add(line); + } else { + section?.entries.add(line); } } if (section != null) yield section; } - String get describeLatestChanges { - var buf = StringBuffer(); - for (var entry in latestChangeEntries) { - buf.writeln(entry); - } - return buf.toString(); - } + String get describeLatestChanges => latestChangeEntries.join(); } class _Section { diff --git a/pkgs/firehose/lib/src/utils.dart b/pkgs/firehose/lib/src/utils.dart index 79054876..f307d7b3 100644 --- a/pkgs/firehose/lib/src/utils.dart +++ b/pkgs/firehose/lib/src/utils.dart @@ -25,41 +25,62 @@ Future runCommand( process.stdout .transform(utf8.decoder) - .transform(LineSplitter()) - .listen((line) => stdout.writeln(' $line')); + .transform(const LineSplitter()) + .listen((line) => stdout + ..write(' ') + ..writeln(line)); process.stderr .transform(utf8.decoder) - .transform(LineSplitter()) - .listen((line) => stderr.writeln(' $line')); + .transform(const LineSplitter()) + .listen((line) => stderr + ..write(' ') + ..writeln(line)); return process.exitCode; } class Tag { + /// RegExp matching a version tag at the start of a line. + /// + /// A version tag is an optional starting seqeuence + /// of non-whitespace, which is the package name, + /// followed by a `v` and a simplified SemVer version + /// number. + /// The version number accepted is + /// > digits '.' digits '.' digits + /// + /// and if followed by a `+`, then it includes the + /// rest of the line. static final RegExp packageVersionTag = - RegExp(r'^(\S+)-v(\d+\.\d+\.\d+(\+.*)?)'); - - static final RegExp versionTag = RegExp(r'^v(\d+\.\d+\.\d+(\+.*)?)'); + RegExp(r'^(?:(\S+)-)?v(\d+\.\d+\.\d+(?:\+.*)?)'); + /// A package version tag. + /// + /// Is expected to have the format: + /// > (package-name)? 'v' SemVer-version + /// + /// If not, the tag is not [valid], and the [package] and [version] + /// will both be `null`. final String tag; Tag(this.tag); bool get valid => version != null; - String? get package { - var match = packageVersionTag.firstMatch(tag); - return match?.group(1); - } + /// The package name before the `v` in the version [tag], if any. + /// + /// Is `null` if there is no package name before the `v`, + /// or if the tag is not [valid]. + String? get package => packageVersionTag.firstMatch(tag)?[1]; - String? get version { - var match = packageVersionTag.firstMatch(tag); - if (match != null) { - return match.group(2); - } - match = versionTag.firstMatch(tag); - return match?.group(1); - } + /// The SemVer version string of the version [tag], if any. + /// + /// This is the part after the `v` of the [tag] string, + /// of the form, which is a major/minor/patch version string + /// optionally followed by a `+` and more characters. + /// + /// Is `null` if the tag is not [valid]. + String? get version => packageVersionTag.firstMatch(tag)?[2]; @override String toString() => tag; diff --git a/pkgs/firehose/pubspec.yaml b/pkgs/firehose/pubspec.yaml index 5fafbc81..cdd73c24 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 +version: 0.3.19 repository: https://github.com/dart-lang/ecosystem/tree/main/pkgs/firehose environment: @@ -11,7 +11,6 @@ executables: dependencies: args: ^2.3.0 - collection: ^1.17.0 http: ^0.13.0 path: ^1.8.0 pub_semver: ^2.1.0