From e0cbc6e2d6a7585757fc2438886718c050f00ff7 Mon Sep 17 00:00:00 2001 From: Lorenzo Croce Date: Sun, 2 Nov 2025 18:12:29 +0100 Subject: [PATCH 1/6] fix(tool): Fail version-check on blank lines in CHANGELOG lists Blank lines between list items in a CHANGELOG.md file cause pub.dev to render them as separate lists, which is not the intended style for the project. This commit updates the `version-check` command to detect this pattern using a multi-line regex. If a match is found, the check fails and prints the offending lines to the console to help the author fix it. Signed-off-by: Lorenzo Croce --- .../tool/lib/src/version_check_command.dart | 33 +++++++++++++++++++ script/tool/pubspec.yaml | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart index 4d147cb92ba..cd9c0b9ac30 100644 --- a/script/tool/lib/src/version_check_command.dart +++ b/script/tool/lib/src/version_check_command.dart @@ -455,9 +455,42 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog. } } + // Check for blank lines between list items in the version section. + final Match? blankLineMatch = _findBlankLineInList(lines.join('\n')); + if (blankLineMatch != null) { + final String offendingLines = blankLineMatch + .group(0)! + .split('\n') + .map((String line) => '$indentation $line') // Add extra indentation + .join('\n'); + + printError( + '${indentation}Blank lines found between list items in CHANGELOG.\n' + '${indentation}This creates multiple separate lists on pub.dev.\n' + '${indentation}Remove blank lines to keep all items in a single list.\n' + '${indentation}The problematic section found is:\n' + '$offendingLines'); + return false; + } + return true; } + /// Validates that there are no blank lines between list items within + /// the changelog using a regex. + /// + /// Returns the first invalid match found, or null if validation passes. + Match? _findBlankLineInList(String changelogContent) { + // This regex requires the multiLine flag to be true. + final RegExp blankLineInListRegex = RegExp( + r'(^[ \t]*[*+-].*$\n)((^[ \t]*$\n)+)(^[ \t]*[*+-].*$)', + multiLine: true); + + // If the regex finds a match, it means there is an error (a blank line + // between list items). + return blankLineInListRegex.firstMatch(changelogContent); + } + Pubspec? _tryParsePubspec(RepositoryPackage package) { try { final Pubspec pubspec = package.parsePubspec(); diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index 7bd5de4dde3..501d2499da3 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_plugin_tools description: Productivity and CI utils for flutter/packages repository: https://github.com/flutter/packages/tree/main/script/tool -version: 1.0.0 +version: 1.0.1 publish_to: none dependencies: From be8e02398b6acb580d041727dd64598c394d73ab Mon Sep 17 00:00:00 2001 From: Lorenzo Croce Date: Mon, 3 Nov 2025 11:16:09 +0100 Subject: [PATCH 2/6] fix(tool): Optimize changelog reading and list regex Moves the regular expression for checking blank lines in lists to a static final field in `VersionCheckCommand` so that it is compiled only once instead of on every execution of `_findBlankLineInList`. Additionally, the `CHANGELOG.md` is now read entirely into a string first. This unified content is then used for both line-by-line version extraction and the subsequent regex-based formatting check. Signed-off-by: Lorenzo Croce --- script/tool/lib/src/version_check_command.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart index cd9c0b9ac30..d04abeda863 100644 --- a/script/tool/lib/src/version_check_command.dart +++ b/script/tool/lib/src/version_check_command.dart @@ -130,6 +130,10 @@ class VersionCheckCommand extends PackageLoopingCommand { hide: true); } + static final RegExp _blankLineInListRegex = RegExp( + r'(^[ \t]*[*+-].*$\n)((^[ \t]*$\n)+)(^[ \t]*[*+-].*$)', + multiLine: true); + static const String _againstPubFlag = 'against-pub'; static const String _prLabelsArg = 'pr-labels'; static const String _checkForMissingChanges = 'check-for-missing-changes'; @@ -377,7 +381,8 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body} // get first version from CHANGELOG final File changelog = package.changelogFile; - final List lines = changelog.readAsLinesSync(); + final String changelogContent = changelog.readAsStringSync(); + final List lines = changelogContent.split('\n'); String? firstLineWithText; final Iterator iterator = lines.iterator; while (iterator.moveNext()) { @@ -456,7 +461,7 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog. } // Check for blank lines between list items in the version section. - final Match? blankLineMatch = _findBlankLineInList(lines.join('\n')); + final Match? blankLineMatch = _findBlankLineInList(changelogContent); if (blankLineMatch != null) { final String offendingLines = blankLineMatch .group(0)! @@ -481,14 +486,9 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog. /// /// Returns the first invalid match found, or null if validation passes. Match? _findBlankLineInList(String changelogContent) { - // This regex requires the multiLine flag to be true. - final RegExp blankLineInListRegex = RegExp( - r'(^[ \t]*[*+-].*$\n)((^[ \t]*$\n)+)(^[ \t]*[*+-].*$)', - multiLine: true); - // If the regex finds a match, it means there is an error (a blank line // between list items). - return blankLineInListRegex.firstMatch(changelogContent); + return _blankLineInListRegex.firstMatch(changelogContent); } Pubspec? _tryParsePubspec(RepositoryPackage package) { From 4eead7be330fa8c184bbd2c75a6c11492c9e8da0 Mon Sep 17 00:00:00 2001 From: Lorenzo Croce Date: Tue, 4 Nov 2025 10:04:21 +0100 Subject: [PATCH 3/6] test(tool): Add tests for CHANGELOG blank line validation Adds unit tests to verify that the `version-check` command correctly fails when it detects blank lines between list items in a CHANGELOG, as this breaks list rendering on pub.dev. Tests are added for both simple lists and nested lists. Also includes a minor formatting fix for the error message. Signed-off-by: Lorenzo Croce --- .../tool/lib/src/version_check_command.dart | 8 +- .../tool/test/version_check_command_test.dart | 77 +++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart index d04abeda863..2820d758bb1 100644 --- a/script/tool/lib/src/version_check_command.dart +++ b/script/tool/lib/src/version_check_command.dart @@ -471,10 +471,10 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog. printError( '${indentation}Blank lines found between list items in CHANGELOG.\n' - '${indentation}This creates multiple separate lists on pub.dev.\n' - '${indentation}Remove blank lines to keep all items in a single list.\n' - '${indentation}The problematic section found is:\n' - '$offendingLines'); + '${indentation}This creates multiple separate lists on pub.dev.\n' + '${indentation}Remove blank lines to keep all items in a single list.\n' + '${indentation}The problematic section found is:\n' + '$offendingLines'); return false; } diff --git a/script/tool/test/version_check_command_test.dart b/script/tool/test/version_check_command_test.dart index d23b7535fda..ca8159d1608 100644 --- a/script/tool/test/version_check_command_test.dart +++ b/script/tool/test/version_check_command_test.dart @@ -421,6 +421,83 @@ void main() { ); }); + test('Fail if CHANGELOG list items have a blank line', () async { + const String version = '1.0.1'; + final RepositoryPackage plugin = + createFakePlugin('plugin', packagesDir, version: version); + + // Blank line breaks the list items. + const String changelog = ''' +## $version +* First item. + +* Second item. +* Third item. +'''; + plugin.changelogFile.writeAsStringSync(changelog); + gitProcessRunner.mockProcessesForExecutable['git-show'] = + [ + FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), + ]; + Error? commandError; + final List output = await runCapturingPrint( + runner, ['version-check', '--base-sha=main'], + errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('Running for plugin'), + contains('1.0.0 -> 1.0.1'), + contains('Blank lines found between list items in CHANGELOG.'), + contains('CHANGELOG.md failed validation.'), + ]), + ); + }); + + test('Fail if CHANGELOG list items have a blank line with nested items', + () async { + const String version = '1.0.1'; + final RepositoryPackage plugin = + createFakePlugin('plugin', packagesDir, version: version); + + // Blank line in nested list items. + const String changelog = ''' +## $version +* Top level item. + * Nested item A. + + * Nested item B. +* Another top level item. +'''; + plugin.changelogFile.writeAsStringSync(changelog); + gitProcessRunner.mockProcessesForExecutable['git-show'] = + [ + FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), + ]; + Error? commandError; + final List output = await runCapturingPrint( + runner, ['version-check', '--base-sha=main'], + errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('Running for plugin'), + contains('1.0.0 -> 1.0.1'), + contains('Blank lines found between list items in CHANGELOG.'), + contains('CHANGELOG.md failed validation.'), + ]), + ); + }); + // END OF NEW TESTS + test( 'Fail if pubspec version only matches an older version listed in CHANGELOG', () async { From 9731cd68d94c4a041a717e49206f362da4e88f4c Mon Sep 17 00:00:00 2001 From: Lorenzo Croce Date: Tue, 4 Nov 2025 14:48:41 +0100 Subject: [PATCH 4/6] refactor(tool): Apply autoformatter to version_check_command_test Applies `dart format` to the `version_check_command_test.dart` file to resolve minor formatting inconsistencies. Signed-off-by: Lorenzo Croce --- .../tool/test/version_check_command_test.dart | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/script/tool/test/version_check_command_test.dart b/script/tool/test/version_check_command_test.dart index ca8159d1608..ed2a21bb501 100644 --- a/script/tool/test/version_check_command_test.dart +++ b/script/tool/test/version_check_command_test.dart @@ -424,7 +424,7 @@ void main() { test('Fail if CHANGELOG list items have a blank line', () async { const String version = '1.0.1'; final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: version); + createFakePlugin('plugin', packagesDir, version: version); // Blank line breaks the list items. const String changelog = ''' @@ -436,15 +436,15 @@ void main() { '''; plugin.changelogFile.writeAsStringSync(changelog); gitProcessRunner.mockProcessesForExecutable['git-show'] = - [ + [ FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), ]; Error? commandError; final List output = await runCapturingPrint( runner, ['version-check', '--base-sha=main'], errorHandler: (Error e) { - commandError = e; - }); + commandError = e; + }); expect(commandError, isA()); expect( @@ -459,13 +459,13 @@ void main() { }); test('Fail if CHANGELOG list items have a blank line with nested items', - () async { - const String version = '1.0.1'; - final RepositoryPackage plugin = + () async { + const String version = '1.0.1'; + final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, version: version); - // Blank line in nested list items. - const String changelog = ''' + // Blank line in nested list items. + const String changelog = ''' ## $version * Top level item. * Nested item A. @@ -473,29 +473,29 @@ void main() { * Nested item B. * Another top level item. '''; - plugin.changelogFile.writeAsStringSync(changelog); - gitProcessRunner.mockProcessesForExecutable['git-show'] = + plugin.changelogFile.writeAsStringSync(changelog); + gitProcessRunner.mockProcessesForExecutable['git-show'] = [ - FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.0.0 -> 1.0.1'), - contains('Blank lines found between list items in CHANGELOG.'), - contains('CHANGELOG.md failed validation.'), - ]), - ); - }); + FakeProcessInfo(MockProcess(stdout: 'version: 1.0.0')), + ]; + Error? commandError; + final List output = await runCapturingPrint( + runner, ['version-check', '--base-sha=main'], + errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('Running for plugin'), + contains('1.0.0 -> 1.0.1'), + contains('Blank lines found between list items in CHANGELOG.'), + contains('CHANGELOG.md failed validation.'), + ]), + ); + }); // END OF NEW TESTS test( From 63cd6787de616e8e09ae48ef14896567b4880146 Mon Sep 17 00:00:00 2001 From: Lorenzo Croce Date: Tue, 4 Nov 2025 21:10:01 +0100 Subject: [PATCH 5/6] fix(tool): Lines like **Breaking change** are now ignored by the script Signed-off-by: Lorenzo Croce --- script/tool/lib/src/version_check_command.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart index 2820d758bb1..08ea600c430 100644 --- a/script/tool/lib/src/version_check_command.dart +++ b/script/tool/lib/src/version_check_command.dart @@ -131,7 +131,7 @@ class VersionCheckCommand extends PackageLoopingCommand { } static final RegExp _blankLineInListRegex = RegExp( - r'(^[ \t]*[*+-].*$\n)((^[ \t]*$\n)+)(^[ \t]*[*+-].*$)', + r'(^[ \t]*[*+-]\s.*$\n)((^[ \t]*$\n)+)(^[ \t]*[*+-]\s.*$)', multiLine: true); static const String _againstPubFlag = 'against-pub'; From 5b464f01ec41e14e3366af45d0e71c1882f7239c Mon Sep 17 00:00:00 2001 From: Lorenzo Croce Date: Thu, 6 Nov 2025 08:56:30 +0100 Subject: [PATCH 6/6] [tool] Remove extra blank lines from CHANGELOGs Signed-off-by: Lorenzo Croce --- packages/google_fonts/CHANGELOG.md | 1 - packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md | 1 - packages/google_sign_in/google_sign_in/CHANGELOG.md | 1 - packages/image_picker/image_picker/CHANGELOG.md | 1 - packages/video_player/video_player/CHANGELOG.md | 2 -- packages/video_player/video_player_android/CHANGELOG.md | 1 - 6 files changed, 7 deletions(-) diff --git a/packages/google_fonts/CHANGELOG.md b/packages/google_fonts/CHANGELOG.md index b49a261fe0d..515350cbf6b 100644 --- a/packages/google_fonts/CHANGELOG.md +++ b/packages/google_fonts/CHANGELOG.md @@ -306,7 +306,6 @@ - `Wix Madefor Display` - `Wix Madefor Text` - `Ysabeau` - - Removed fonts: - `Arima Madurai` - `Fredoka One` diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index eb60690a37d..e631a57faa4 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -594,7 +594,6 @@ GoogleMapController is now uniformly driven by implementing `DefaultLifecycleObs ## 0.5.19 * Adds support for toggling Indoor View on or off. - * Allow BitmapDescriptor scaling override ## 0.5.18 diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md index d3e6c62eb4c..90538ea199b 100644 --- a/packages/google_sign_in/google_sign_in/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in/CHANGELOG.md @@ -183,7 +183,6 @@ For every platform other than `web`, this version should be identical to `5.4.4` ## 5.1.0 * Add reAuthenticate option to signInSilently to allow re-authentication to be requested - * Updated Android lint settings. ## 5.0.7 diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 8f6efb51383..de287c1c477 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -120,7 +120,6 @@ ## 0.8.6+2 * Updates `NSPhotoLibraryUsageDescription` description in README. - * Updates minimum Flutter version to 3.0. ## 0.8.6+1 diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index d01c0ec1d9a..99ab006aa46 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -593,14 +593,12 @@ DefaultHttpDataSourceFactory by default. ## 0.10.0+8 * iOS: Fix an issue where the player sends initialization message incorrectly. - * Fix a few other IDE warnings. ## 0.10.0+7 * Android: Fix issue where buffering status in percentage instead of milliseconds - * Android: Update buffering status everytime we notify for position change ## 0.10.0+6 diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index a6decfda03b..1984c2d5238 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -163,7 +163,6 @@ ## 2.7.2 * Updates minimum supported SDK version to Flutter 3.24/Dart 3.5. - * Re-adds Impeller support. ## 2.7.1