| // Copyright (c) 2020, 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:async'; | |
| import 'package:collection/collection.dart' show IterableExtension; | |
| import 'package:pub_semver/pub_semver.dart'; | |
| import '../exceptions.dart'; | |
| import '../package_name.dart'; | |
| import '../validator.dart'; | |
| /// Gives an info if the version number has skipped since the last released, or | |
| /// if the version is not sequentially following the latest. | |
| /// | |
| /// Gives an info when publishing a new version, if the latest published | |
| /// version lower to this was not opted into null-safety. | |
| class RelativeVersionNumberingValidator extends Validator { | |
| static const String semverUrl = | |
| 'https://dart.dev/tools/pub/versioning#semantic-versions'; | |
| static const String nullSafetyGuideUrl = | |
| 'https://dart.dev/null-safety/migration-guide'; | |
| @override | |
| Future<void> validate() async { | |
| final hostedSource = cache.hosted; | |
| List<PackageId> existingVersions; | |
| try { | |
| existingVersions = await cache.getVersions( | |
| hostedSource.refFor(package.name, url: serverUrl.toString()), | |
| ); | |
| } on PackageNotFoundException { | |
| existingVersions = []; | |
| } | |
| existingVersions.sort((a, b) => a.version.compareTo(b.version)); | |
| final currentVersion = package.pubspec.version; | |
| final latestVersion = | |
| existingVersions.isEmpty ? null : existingVersions.last.version; | |
| if (latestVersion != null && latestVersion > currentVersion) { | |
| hints.add(''' | |
| The latest published version is $latestVersion. | |
| Your version $currentVersion is earlier than that.'''); | |
| } | |
| final previousRelease = existingVersions.lastWhereOrNull( | |
| (id) => id.version < package.version, | |
| ); | |
| if (previousRelease == null) return; | |
| final previousVersion = previousRelease.version; | |
| final noPrerelease = Version( | |
| currentVersion.major, | |
| currentVersion.minor, | |
| currentVersion.patch, | |
| ); | |
| if (noPrerelease != previousVersion.nextMajor && | |
| noPrerelease != previousVersion.nextMinor && | |
| noPrerelease != previousVersion.nextPatch && | |
| currentVersion.withoutBuild() != previousVersion) { | |
| final hint = ''' | |
| The previous version is $previousVersion. | |
| It seems you are not publishing an incremental update. | |
| Consider one of: | |
| '''; | |
| final String suggestion; | |
| if (previousVersion.major == 0) { | |
| suggestion = ''' | |
| * ${previousVersion.nextMajor} for a first major release. | |
| * ${previousVersion.nextBreaking} for a breaking release. | |
| * ${previousVersion.nextPatch} for a minor release. | |
| '''; | |
| } else { | |
| suggestion = ''' | |
| * ${previousVersion.nextBreaking} for a breaking release. | |
| * ${previousVersion.nextMinor} for a minor release. | |
| * ${previousVersion.nextPatch} for a patch release.'''; | |
| } | |
| hints.add(hint + suggestion); | |
| } | |
| final previousPubspec = await cache.describe(previousRelease); | |
| final currentOptedIn = package.pubspec.languageVersion.supportsNullSafety; | |
| final previousOptedIn = previousPubspec.languageVersion.supportsNullSafety; | |
| if (currentOptedIn && !previousOptedIn) { | |
| hints.add(''' | |
| You're about to publish a package that opts into null safety. | |
| The previous version ($previousVersion) isn't opted in. | |
| See $nullSafetyGuideUrl for best practices.'''); | |
| } else if (!currentOptedIn && previousOptedIn) { | |
| hints.add(''' | |
| You're about to publish a package that doesn't opt into null safety, | |
| but the previous version ($previousVersion) was opted in. | |
| This change is likely to be backwards incompatible. | |
| See $semverUrl for information about versioning.'''); | |
| } | |
| } | |
| } | |
| extension on Version { | |
| Version withoutBuild() => Version( | |
| major, | |
| minor, | |
| patch, | |
| pre: preRelease.isEmpty ? null : preRelease.join('.'), | |
| ); | |
| } |