diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md index 9606eed20b27..da14eae285e3 100644 --- a/script/tool/CHANGELOG.md +++ b/script/tool/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.8 + +- Allows pre-release versions in `version-check`. + ## 0.8.7 - Supports empty custom analysis allow list files. diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart index 62abdb2a432b..246382dfae00 100644 --- a/script/tool/lib/src/version_check_command.dart +++ b/script/tool/lib/src/version_check_command.dart @@ -31,8 +31,8 @@ enum NextVersionType { /// A bugfix change. PATCH, - /// The release of an existing prerelease version. - RELEASE, + /// The release of an existing pre-1.0 version. + V1_RELEASE, } /// The state of a package's version relative to the comparison base. @@ -53,8 +53,8 @@ enum _CurrentVersionState { unknown, } -/// Returns the set of allowed next versions, with their change type, for -/// [version]. +/// Returns the set of allowed next non-prerelease versions, with their change +/// type, for [version]. /// /// [newVersion] is used to check whether this is a pre-1.0 version bump, as /// those have different semver rules. @@ -78,17 +78,17 @@ Map getAllowedNextVersions( final int currentBuildNumber = version.build.first as int; nextBuildNumber = currentBuildNumber + 1; } - final Version preReleaseVersion = Version( + final Version nextBuildVersion = Version( version.major, version.minor, version.patch, build: nextBuildNumber.toString(), ); allowedNextVersions.clear(); - allowedNextVersions[version.nextMajor] = NextVersionType.RELEASE; + allowedNextVersions[version.nextMajor] = NextVersionType.V1_RELEASE; allowedNextVersions[version.nextMinor] = NextVersionType.BREAKING_MAJOR; allowedNextVersions[version.nextPatch] = NextVersionType.MINOR; - allowedNextVersions[preReleaseVersion] = NextVersionType.PATCH; + allowedNextVersions[nextBuildVersion] = NextVersionType.PATCH; } return allowedNextVersions; } @@ -337,12 +337,11 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body} // Check for reverts when doing local validation. if (!getBoolArg(_againstPubFlag) && currentVersion < previousVersion) { - final Map possibleVersionsFromNewVersion = - getAllowedNextVersions(currentVersion, newVersion: previousVersion); // Since this skips validation, try to ensure that it really is likely // to be a revert rather than a typo by checking that the transition // from the lower version to the new version would have been valid. - if (possibleVersionsFromNewVersion.containsKey(previousVersion)) { + if (_shouldAllowVersionChange( + oldVersion: currentVersion, newVersion: previousVersion)) { logWarning('${indentation}New version is lower than previous version. ' 'This is assumed to be a revert.'); return _CurrentVersionState.validRevert; @@ -352,7 +351,8 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body} final Map allowedNextVersions = getAllowedNextVersions(previousVersion, newVersion: currentVersion); - if (allowedNextVersions.containsKey(currentVersion)) { + if (_shouldAllowVersionChange( + oldVersion: previousVersion, newVersion: currentVersion)) { print('$indentation$previousVersion -> $currentVersion'); } else { printError('${indentation}Incorrectly updated version.\n' @@ -361,7 +361,13 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body} return _CurrentVersionState.invalidChange; } - if (allowedNextVersions[currentVersion] == NextVersionType.BREAKING_MAJOR && + // Check whether the version (or for a pre-release, the version that + // pre-release would eventually be released as) is a breaking change, and + // if so, validate it. + final Version targetReleaseVersion = + currentVersion.isPreRelease ? currentVersion.nextPatch : currentVersion; + if (allowedNextVersions[targetReleaseVersion] == + NextVersionType.BREAKING_MAJOR && !_validateBreakingChange(package)) { printError('${indentation}Breaking change detected.\n' '${indentation}Breaking changes to platform interfaces are not ' @@ -520,6 +526,27 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog. return file.readAsStringSync(); } + /// Returns true if the given version transition should be allowed. + bool _shouldAllowVersionChange( + {required Version oldVersion, required Version newVersion}) { + // Get the non-pre-release next version mapping. + final Map allowedNextVersions = + getAllowedNextVersions(oldVersion, newVersion: newVersion); + + if (allowedNextVersions.containsKey(newVersion)) { + return true; + } + // Allow a pre-release version of a version that would be a valid + // transition. + if (newVersion.isPreRelease) { + final Version targetReleaseVersion = newVersion.nextPatch; + if (allowedNextVersions.containsKey(targetReleaseVersion)) { + return true; + } + } + return false; + } + /// Returns an error string if the changes to this package should have /// resulted in a version change, or shoud have resulted in a CHANGELOG change /// but didn't. diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index 1e631b1c0a17..b8233de11b41 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_plugin_tools description: Productivity utils for flutter/plugins and flutter/packages repository: https://github.com/flutter/plugins/tree/main/script/tool -version: 0.8.7 +version: 0.8.8 dependencies: args: ^2.1.0 diff --git a/script/tool/test/version_check_command_test.dart b/script/tool/test/version_check_command_test.dart index a310f0f09fcb..8f8d510fd106 100644 --- a/script/tool/test/version_check_command_test.dart +++ b/script/tool/test/version_check_command_test.dart @@ -1178,6 +1178,186 @@ ${indentation}HTTP response: null ]), ); }); + + group('prelease versions', () { + test( + 'allow an otherwise-valid transition that also adds a pre-release component', + () async { + createFakePlugin('plugin', packagesDir, version: '2.0.0-dev'); + processRunner.mockProcessesForExecutable['git-show'] = [ + MockProcess(stdout: 'version: 1.0.0'), + ]; + final List output = await runCapturingPrint( + runner, ['version-check', '--base-sha=main']); + + expect( + output, + containsAllInOrder([ + contains('Running for plugin'), + contains('1.0.0 -> 2.0.0-dev'), + ]), + ); + expect( + processRunner.recordedCalls, + containsAllInOrder(const [ + ProcessCall('git-show', + ['main:packages/plugin/pubspec.yaml'], null) + ])); + }); + + test('allow releasing a pre-release', () async { + createFakePlugin('plugin', packagesDir, version: '1.2.0'); + processRunner.mockProcessesForExecutable['git-show'] = [ + MockProcess(stdout: 'version: 1.2.0-dev'), + ]; + final List output = await runCapturingPrint( + runner, ['version-check', '--base-sha=main']); + + expect( + output, + containsAllInOrder([ + contains('Running for plugin'), + contains('1.2.0-dev -> 1.2.0'), + ]), + ); + expect( + processRunner.recordedCalls, + containsAllInOrder(const [ + ProcessCall('git-show', + ['main:packages/plugin/pubspec.yaml'], null) + ])); + }); + + // Allow abandoning a pre-release version in favor of a different version + // change type. + test( + 'allow an otherwise-valid transition that also removes a pre-release component', + () async { + createFakePlugin('plugin', packagesDir, version: '2.0.0'); + processRunner.mockProcessesForExecutable['git-show'] = [ + MockProcess(stdout: 'version: 1.2.0-dev'), + ]; + final List output = await runCapturingPrint( + runner, ['version-check', '--base-sha=main']); + + expect( + output, + containsAllInOrder([ + contains('Running for plugin'), + contains('1.2.0-dev -> 2.0.0'), + ]), + ); + expect( + processRunner.recordedCalls, + containsAllInOrder(const [ + ProcessCall('git-show', + ['main:packages/plugin/pubspec.yaml'], null) + ])); + }); + + test('allow changing only the pre-release version', () async { + createFakePlugin('plugin', packagesDir, version: '1.2.0-dev.2'); + processRunner.mockProcessesForExecutable['git-show'] = [ + MockProcess(stdout: 'version: 1.2.0-dev.1'), + ]; + final List output = await runCapturingPrint( + runner, ['version-check', '--base-sha=main']); + + expect( + output, + containsAllInOrder([ + contains('Running for plugin'), + contains('1.2.0-dev.1 -> 1.2.0-dev.2'), + ]), + ); + expect( + processRunner.recordedCalls, + containsAllInOrder(const [ + ProcessCall('git-show', + ['main:packages/plugin/pubspec.yaml'], null) + ])); + }); + + test('denies invalid version change that also adds a pre-release', + () async { + createFakePlugin('plugin', packagesDir, version: '0.2.0-dev'); + processRunner.mockProcessesForExecutable['git-show'] = [ + MockProcess(stdout: 'version: 0.0.1'), + ]; + 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('Incorrectly updated version.'), + ])); + expect( + processRunner.recordedCalls, + containsAllInOrder(const [ + ProcessCall('git-show', + ['main:packages/plugin/pubspec.yaml'], null) + ])); + }); + + test('denies invalid version change that also removes a pre-release', + () async { + createFakePlugin('plugin', packagesDir, version: '0.2.0'); + processRunner.mockProcessesForExecutable['git-show'] = [ + MockProcess(stdout: 'version: 0.0.1-dev'), + ]; + 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('Incorrectly updated version.'), + ])); + expect( + processRunner.recordedCalls, + containsAllInOrder(const [ + ProcessCall('git-show', + ['main:packages/plugin/pubspec.yaml'], null) + ])); + }); + + test('denies invalid version change between pre-releases', () async { + createFakePlugin('plugin', packagesDir, version: '0.2.0-dev'); + processRunner.mockProcessesForExecutable['git-show'] = [ + MockProcess(stdout: 'version: 0.0.1-dev'), + ]; + 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('Incorrectly updated version.'), + ])); + expect( + processRunner.recordedCalls, + containsAllInOrder(const [ + ProcessCall('git-show', + ['main:packages/plugin/pubspec.yaml'], null) + ])); + }); + }); }); group('Pre 1.0', () {