diff --git a/.github/workflows/groundskeeper.yml b/.github/workflows/groundskeeper.yml index 066d5ff1..72b2c3cc 100644 --- a/.github/workflows/groundskeeper.yml +++ b/.github/workflows/groundskeeper.yml @@ -1,5 +1,5 @@ name: Groundskeeper -description: Reusable workflow to run dart format and dart fix across packages using groundskeeper, and create a PR if there are changes. +description: Reusable workflow to run dart format and dart fix on a single package, and create a PR if there are changes. on: workflow_call: @@ -34,11 +34,6 @@ on: required: false type: boolean default: true - exclude: - description: 'Comma-separated list of package directories to exclude' - required: false - type: string - default: '' branch: description: 'PR branch name' required: false @@ -90,22 +85,21 @@ jobs: run: dart pub get - name: Run Tidy Script + working-directory: ${{ inputs.directory }} run: | - dart .github/ecosystem-helper/pkgs/firehose/bin/groundskeeper.dart \ - --directory=${{ inputs.directory }} \ + dart $GITHUB_WORKSPACE/.github/ecosystem-helper/pkgs/firehose/bin/groundskeeper.dart \ --format=${{ inputs.run-format }} \ - --fix=${{ inputs.run-fix }} \ - --exclude="${{ inputs.exclude }}" - - - name: Clean up helper - run: rm -rf .github/ecosystem-helper + --fix=${{ inputs.run-fix }} - name: Update changelog if: ${{ inputs.update-changelog }} - working-directory: ${{ inputs.directory }} + working-directory: .github/ecosystem-helper/pkgs/repo_manage run: | - dart install --git-path pkgs/repo_manage --git-ref addChangelogUpdater https://github.com/dart-lang/ecosystem.git - report changelog "${{ inputs.changelog-message }}" + dart pub get + dart run bin/report.dart changelog --changelog "../../../../${{ inputs.directory }}/CHANGELOG.md" "${{ inputs.changelog-message }}" + + - name: Clean up helper + run: rm -rf .github/ecosystem-helper - name: Create Pull Request uses: peter-evans/create-pull-request@6fff569d741c6b8131d2a49663b16573ef90be31 diff --git a/.github/workflows/groundskeeper_internal.yml b/.github/workflows/groundskeeper_internal.yml index 85b507f5..63044eb6 100644 --- a/.github/workflows/groundskeeper_internal.yml +++ b/.github/workflows/groundskeeper_internal.yml @@ -10,5 +10,20 @@ jobs: permissions: contents: write pull-requests: write + strategy: + matrix: + package: + - blast_repo + - canary + - corpus + - dart_flutter_team_lints + - firehose + - repo_manage + - sdk_triage_bot + - trebuchet uses: ./.github/workflows/groundskeeper.yml + with: + directory: pkgs/${{ matrix.package }} + branch: auto-tidy-${{ matrix.package }} + title: 'Tidy: package:${{ matrix.package }}' secrets: inherit diff --git a/pkgs/firehose/CHANGELOG.md b/pkgs/firehose/CHANGELOG.md index 0b406ab1..81c33573 100644 --- a/pkgs/firehose/CHANGELOG.md +++ b/pkgs/firehose/CHANGELOG.md @@ -14,6 +14,7 @@ - Fix web coverage reporting and coverage aggregation. - Improve and clarify readme documentation. - Add description for the table 'Package publishing'. +- Simplify `groundskeeper` workflow ## 0.13.1 diff --git a/pkgs/firehose/bin/groundskeeper.dart b/pkgs/firehose/bin/groundskeeper.dart index 20b668d0..182183fb 100644 --- a/pkgs/firehose/bin/groundskeeper.dart +++ b/pkgs/firehose/bin/groundskeeper.dart @@ -1,51 +1,41 @@ +// 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/firehose.dart'; -import 'package:glob/glob.dart'; +import 'package:path/path.dart' as path; void main(List args) async { final parser = ArgParser() - ..addOption('directory', defaultsTo: '.') ..addOption('format', defaultsTo: 'true', allowed: ['true', 'false']) - ..addOption('fix', defaultsTo: 'true', allowed: ['true', 'false']) - ..addOption('exclude', defaultsTo: ''); + ..addOption('fix', defaultsTo: 'true', allowed: ['true', 'false']); final argResults = parser.parse(args); - final targetDirectoryPath = argResults['directory'] as String; - final targetDirectory = Directory(targetDirectoryPath); - + final targetDirectory = Directory.current; final runFormat = argResults['format'] != 'false'; final runFix = argResults['fix'] != 'false'; - final excludeStr = argResults['exclude'] as String; - - final excludes = excludeStr.isEmpty - ? [] - : excludeStr.split(',').map((e) => e.trim()).toList(); print('Target Directory: ${targetDirectory.absolute.path}'); - print('Excludes: $excludes'); print('Run format: $runFormat'); print('Run fix: $runFix'); - // Locate packages early to use for both format and fix - final repo = Repository(targetDirectory); - final packages = repo.locatePackages( - ignore: excludes.map(Glob.new).toList(), - includeUnpublished: true, - ); + final pubspecFile = File(path.join(targetDirectory.path, 'pubspec.yaml')); + final isPackage = pubspecFile.existsSync(); + + if (runFix && !isPackage) { + print(''' +Error: Run fix is enabled, but no pubspec.yaml found in ${targetDirectory.path}'''); + exit(1); + } if (runFormat) { print('Running dart format...'); - // Respect excludes by only formatting located packages. - // If no packages are found, default to the target directory. - final pathsToFormat = packages.isEmpty - ? [targetDirectory.path] - : packages.map((p) => p.directory.path).toList(); - final result = await Process.run( 'dart', - ['format', ...pathsToFormat], + ['format', targetDirectory.path], ); stdout.write(result.stdout); stderr.write(result.stderr); @@ -55,38 +45,35 @@ void main(List args) async { } if (runFix) { - print('Found packages: ${packages.map((e) => e.directory.path).toList()}'); + final repo = Repository(targetDirectory); + final pkg = Package(targetDirectory, repo); + final pkgPath = targetDirectory.path; - for (final pkg in packages) { - final pkgPath = pkg.directory.path; + // Detect if it is a Flutter package + final isFlutter = pkg.pubspec.dependencies.containsKey('flutter') || + pkg.pubspec.devDependencies.containsKey('flutter'); + final tool = isFlutter ? 'flutter' : 'dart'; - // Detect if it is a Flutter package - final isFlutter = pkg.pubspec.dependencies.containsKey('flutter') || - pkg.pubspec.devDependencies.containsKey('flutter'); - final tool = isFlutter ? 'flutter' : 'dart'; + print('Tidying package in $pkgPath (${isFlutter ? 'Flutter' : 'Dart'})...'); - print( - 'Tidying package in $pkgPath (${isFlutter ? 'Flutter' : 'Dart'})...'); - - print(' Running $tool pub get...'); - final pubGetResult = - await Process.run(tool, ['pub', 'get'], workingDirectory: pkgPath); - stdout.write(pubGetResult.stdout); - stderr.write(pubGetResult.stderr); - if (pubGetResult.exitCode != 0) { - print('Error: $tool pub get failed in $pkgPath'); - exit(pubGetResult.exitCode); - } + print(' Running $tool pub get...'); + final pubGetResult = + await Process.run(tool, ['pub', 'get'], workingDirectory: pkgPath); + stdout.write(pubGetResult.stdout); + stderr.write(pubGetResult.stderr); + if (pubGetResult.exitCode != 0) { + print('Error: $tool pub get failed in $pkgPath'); + exit(pubGetResult.exitCode); + } - print(' Running dart fix --apply...'); - final fixResult = await Process.run('dart', ['fix', '--apply'], - workingDirectory: pkgPath); - stdout.write(fixResult.stdout); - stderr.write(fixResult.stderr); - if (fixResult.exitCode != 0) { - print('Error: dart fix failed in $pkgPath'); - exit(fixResult.exitCode); - } + print(' Running dart fix --apply...'); + final fixResult = await Process.run('dart', ['fix', '--apply'], + workingDirectory: pkgPath); + stdout.write(fixResult.stdout); + stderr.write(fixResult.stderr); + if (fixResult.exitCode != 0) { + print('Error: dart fix failed in $pkgPath'); + exit(fixResult.exitCode); } } } diff --git a/pkgs/firehose/test/changelog_test.dart b/pkgs/firehose/test/changelog_test.dart index 84716c86..26fbd9c9 100644 --- a/pkgs/firehose/test/changelog_test.dart +++ b/pkgs/firehose/test/changelog_test.dart @@ -172,13 +172,10 @@ void main() { void withChangelog(String contents, void Function(File file) closure) { final dir = Directory.systemTemp.createTempSync(); + addTearDown(() => dir.deleteSync(recursive: true)); final file = File('${dir.path}/CHANGELOG.md'); - try { - file.writeAsStringSync(contents); - closure(file); - } finally { - dir.deleteSync(recursive: true); - } + file.writeAsStringSync(contents); + closure(file); } const _defaultContents = ''' diff --git a/pkgs/firehose/test/health_test.dart b/pkgs/firehose/test/health_test.dart index badeb655..8ee07c4d 100644 --- a/pkgs/firehose/test/health_test.dart +++ b/pkgs/firehose/test/health_test.dart @@ -126,8 +126,9 @@ Future checkGolden( List ignoredPackage = const [], List flutterPackages = const [], }) async { - final commentPath = p.join(Directory.systemTemp.createTempSync().path, - 'comment_${check.displayName}.md'); + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync(recursive: true)); + final commentPath = p.join(tempDir.path, 'comment_${check.displayName}.md'); await FakeHealth( directory, check, diff --git a/pkgs/repo_manage/lib/changelog_updater.dart b/pkgs/repo_manage/lib/changelog_updater.dart index f8176e9c..0fad4dd5 100644 --- a/pkgs/repo_manage/lib/changelog_updater.dart +++ b/pkgs/repo_manage/lib/changelog_updater.dart @@ -5,10 +5,33 @@ import 'dart:convert'; import 'dart:io'; +import 'package:path/path.dart' as path; + import 'src/common.dart'; -String updateChangelogContent(String changelog, String message) { - final lines = LineSplitter.split(changelog).toList(); +void updatePubspecVersion(File pubspecFile, String newVersion) { + final content = pubspecFile.readAsStringSync(); + final lines = LineSplitter.split(content).toList(); + var updated = false; + for (var i = 0; i < lines.length; i++) { + final line = lines[i]; + if (line.startsWith('version:')) { + lines[i] = 'version: $newVersion'; + updated = true; + break; + } + } + if (updated) { + pubspecFile.writeAsStringSync('${lines.join('\n')}\n'); + } +} + +void updateChangelog({ + required File changelogFile, + required String message, +}) { + final changelogContent = changelogFile.readAsStringSync(); + final lines = LineSplitter.split(changelogContent).toList(); var currentVersion = '0.0.1'; var currentVersionLine = 0; @@ -49,7 +72,16 @@ String updateChangelogContent(String changelog, String message) { ]); } - return '${output.join('\n')}\n'; + changelogFile.writeAsStringSync('${output.join('\n')}\n'); + + if (!isWip) { + final newVersion = '$currentVersion-wip'; + final changelogDir = path.dirname(changelogFile.path); + final pubspecFile = File(path.join(changelogDir, 'pubspec.yaml')); + if (pubspecFile.existsSync()) { + updatePubspecVersion(pubspecFile, newVersion); + } + } } class ChangelogUpdaterCommand extends ReportCommand { @@ -74,6 +106,7 @@ Usage: dart run report.dart changelog [--changelog ] "Your changelog messa } final message = args.join(' '); + final changelogFile = File(argResults?['changelog'] as String? ?? 'CHANGELOG.md'); @@ -82,11 +115,7 @@ Usage: dart run report.dart changelog [--changelog ] "Your changelog messa return 1; } - final newChangelog = updateChangelogContent( - changelogFile.readAsStringSync(), - message, - ); - changelogFile.writeAsStringSync(newChangelog); + updateChangelog(changelogFile: changelogFile, message: message); stdout.writeln('Changelog updated successfully.'); return 0; } diff --git a/pkgs/repo_manage/test/changelog_updater_test.dart b/pkgs/repo_manage/test/changelog_updater_test.dart index 052f6d5e..3b7b4b0a 100644 --- a/pkgs/repo_manage/test/changelog_updater_test.dart +++ b/pkgs/repo_manage/test/changelog_updater_test.dart @@ -1,10 +1,15 @@ +// Copyright (c) 2026, 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:path/path.dart' as path; import 'package:repo_manage/changelog_updater.dart'; import 'package:test/test.dart'; void main() { - group('updateChangelogContent', () { + group('updateChangelog', () { test('adds an entry to a wip changelog', () { expectGolden( inputPath: 'test/data/changelog_wip.md', @@ -45,6 +50,105 @@ void main() { ); }); }); + + group('updatePubspecVersion', () { + test('updates version line', () { + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync(recursive: true)); + final pubspecFile = File('${tempDir.path}/pubspec.yaml'); + pubspecFile.writeAsStringSync(''' +name: my_package +version: 1.0.0 +dependencies: + path: ^1.8.0 +'''); + + updatePubspecVersion(pubspecFile, '1.0.1-wip'); + + expect(pubspecFile.readAsStringSync(), ''' +name: my_package +version: 1.0.1-wip +dependencies: + path: ^1.8.0 +'''); + }); + }); + + group('updateChangelog (with pubspec)', () { + test('updates pubspec if transitioned to WIP', () { + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync(recursive: true)); + final changelogFile = File('${tempDir.path}/CHANGELOG.md'); + final pubspecFile = File('${tempDir.path}/pubspec.yaml'); + + changelogFile.writeAsStringSync(''' +## 1.0.0 + +- Released version +'''); + pubspecFile.writeAsStringSync(''' +name: my_package +version: 1.0.0 +'''); + + updateChangelog(changelogFile: changelogFile, message: 'New WIP change'); + + expect(changelogFile.readAsStringSync(), ''' +## 1.0.0-wip + +- New WIP change + +## 1.0.0 + +- Released version +'''); + + expect(pubspecFile.readAsStringSync(), ''' +name: my_package +version: 1.0.0-wip +'''); + }); + + test('does not update pubspec if already WIP', () { + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync(recursive: true)); + final changelogFile = File('${tempDir.path}/CHANGELOG.md'); + final pubspecFile = File('${tempDir.path}/pubspec.yaml'); + + changelogFile.writeAsStringSync(''' +## 1.0.0-wip + +- WIP change + +## 1.0.0 + +- Released version +'''); + pubspecFile.writeAsStringSync(''' +name: my_package +version: 1.0.0-wip +'''); + + updateChangelog( + changelogFile: changelogFile, message: 'Another WIP change'); + + expect(changelogFile.readAsStringSync(), ''' +## 1.0.0-wip + +- WIP change +- Another WIP change + +## 1.0.0 + +- Released version +'''); + + expect(pubspecFile.readAsStringSync(), ''' +name: my_package +version: 1.0.0-wip +'''); + }); + }); } void expectGolden({ @@ -52,8 +156,14 @@ void expectGolden({ required String goldenPath, required String message, }) { - final input = File(inputPath).readAsStringSync(); - final output = updateChangelogContent(input, message); + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync(recursive: true)); + final tempChangelog = File(path.join(tempDir.path, 'CHANGELOG.md')); + tempChangelog.writeAsStringSync(File(inputPath).readAsStringSync()); + + updateChangelog(changelogFile: tempChangelog, message: message); + + final output = tempChangelog.readAsStringSync(); final goldenFile = File(goldenPath); if (!goldenFile.existsSync()) {