mirror of
https://github.com/flutter/packages.git
synced 2025-06-16 10:15:06 +08:00
[tool] version-check publish-check commands can check against pub (#3840)
Add a PubVersionFinder class to easily fetch the version from pub. Add an against-pub flag to check-version command, which allows it to check the version against pub server Make the 'publish-check' command to check against pub to determine if the specific versions of packages need to be published. Add a log-status flag, which allows the publish-check command to log the final status of the result. This helps other ci tools to easily grab the results and use it to determine what to do next. See option 3 in flutter/flutter#81444 This PR also fixes some tests. partially flutter/flutter#81444
This commit is contained in:
@ -3,10 +3,14 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:colorize/colorize.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:pubspec_parse/pubspec_parse.dart';
|
||||
|
||||
import 'common.dart';
|
||||
@ -18,7 +22,10 @@ class PublishCheckCommand extends PluginCommand {
|
||||
Directory packagesDir,
|
||||
FileSystem fileSystem, {
|
||||
ProcessRunner processRunner = const ProcessRunner(),
|
||||
}) : super(packagesDir, fileSystem, processRunner: processRunner) {
|
||||
this.httpClient,
|
||||
}) : _pubVersionFinder =
|
||||
PubVersionFinder(httpClient: httpClient ?? http.Client()),
|
||||
super(packagesDir, fileSystem, processRunner: processRunner) {
|
||||
argParser.addFlag(
|
||||
_allowPrereleaseFlag,
|
||||
help: 'Allows the pre-release SDK warning to pass.\n'
|
||||
@ -26,9 +33,29 @@ class PublishCheckCommand extends PluginCommand {
|
||||
'the SDK constraint is a pre-release version, is ignored.',
|
||||
defaultsTo: false,
|
||||
);
|
||||
argParser.addFlag(_machineFlag,
|
||||
help: 'Switch outputs to a machine readable JSON. \n'
|
||||
'The JSON contains a "status" field indicating the final status of the command, the possible values are:\n'
|
||||
' $_statusNeedsPublish: There is at least one package need to be published. They also passed all publish checks.\n'
|
||||
' $_statusMessageNoPublish: There are no packages needs to be published. Either no pubspec change detected or all versions have already been published.\n'
|
||||
' $_statusMessageError: Some error has occurred.',
|
||||
defaultsTo: false,
|
||||
negatable: true);
|
||||
}
|
||||
|
||||
static const String _allowPrereleaseFlag = 'allow-pre-release';
|
||||
static const String _machineFlag = 'machine';
|
||||
static const String _statusNeedsPublish = 'needs-publish';
|
||||
static const String _statusMessageNoPublish = 'no-publish';
|
||||
static const String _statusMessageError = 'error';
|
||||
static const String _statusKey = 'status';
|
||||
static const String _humanMessageKey = 'humanMessage';
|
||||
|
||||
final List<String> _validStatus = <String>[
|
||||
_statusNeedsPublish,
|
||||
_statusMessageNoPublish,
|
||||
_statusMessageError
|
||||
];
|
||||
|
||||
@override
|
||||
final String name = 'publish-check';
|
||||
@ -37,31 +64,74 @@ class PublishCheckCommand extends PluginCommand {
|
||||
final String description =
|
||||
'Checks to make sure that a plugin *could* be published.';
|
||||
|
||||
/// The custom http client used to query versions on pub.
|
||||
final http.Client httpClient;
|
||||
|
||||
final PubVersionFinder _pubVersionFinder;
|
||||
|
||||
// The output JSON when the _machineFlag is on.
|
||||
final Map<String, dynamic> _machineOutput = <String, dynamic>{};
|
||||
|
||||
final List<String> _humanMessages = <String>[];
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
final ZoneSpecification logSwitchSpecification = ZoneSpecification(
|
||||
print: (Zone self, ZoneDelegate parent, Zone zone, String message) {
|
||||
final bool logMachineMessage = argResults[_machineFlag] as bool;
|
||||
if (logMachineMessage && message != _prettyJson(_machineOutput)) {
|
||||
_humanMessages.add(message);
|
||||
} else {
|
||||
parent.print(zone, message);
|
||||
}
|
||||
});
|
||||
|
||||
await runZoned(_runCommand, zoneSpecification: logSwitchSpecification);
|
||||
}
|
||||
|
||||
Future<void> _runCommand() async {
|
||||
final List<Directory> failedPackages = <Directory>[];
|
||||
|
||||
String status = _statusMessageNoPublish;
|
||||
await for (final Directory plugin in getPlugins()) {
|
||||
if (!(await _passesPublishCheck(plugin))) {
|
||||
failedPackages.add(plugin);
|
||||
final _PublishCheckResult result = await _passesPublishCheck(plugin);
|
||||
switch (result) {
|
||||
case _PublishCheckResult._notPublished:
|
||||
if (failedPackages.isEmpty) {
|
||||
status = _statusNeedsPublish;
|
||||
}
|
||||
break;
|
||||
case _PublishCheckResult._published:
|
||||
break;
|
||||
case _PublishCheckResult._error:
|
||||
failedPackages.add(plugin);
|
||||
status = _statusMessageError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_pubVersionFinder.httpClient.close();
|
||||
|
||||
if (failedPackages.isNotEmpty) {
|
||||
final String error =
|
||||
'FAIL: The following ${failedPackages.length} package(s) failed the '
|
||||
'The following ${failedPackages.length} package(s) failed the '
|
||||
'publishing check:';
|
||||
final String joinedFailedPackages = failedPackages.join('\n');
|
||||
|
||||
final Colorize colorizedError = Colorize('$error\n$joinedFailedPackages')
|
||||
..red();
|
||||
print(colorizedError);
|
||||
throw ToolExit(1);
|
||||
_printImportantStatusMessage('$error\n$joinedFailedPackages',
|
||||
isError: true);
|
||||
} else {
|
||||
_printImportantStatusMessage('All packages passed publish check!',
|
||||
isError: false);
|
||||
}
|
||||
|
||||
final Colorize passedMessage =
|
||||
Colorize('All packages passed publish check!')..green();
|
||||
print(passedMessage);
|
||||
if (argResults[_machineFlag] as bool) {
|
||||
_setStatus(status);
|
||||
_machineOutput[_humanMessageKey] = _humanMessages;
|
||||
print(_prettyJson(_machineOutput));
|
||||
}
|
||||
|
||||
if (failedPackages.isNotEmpty) {
|
||||
throw ToolExit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Pubspec _tryParsePubspec(Directory package) {
|
||||
@ -89,8 +159,11 @@ class PublishCheckCommand extends PluginCommand {
|
||||
final Completer<void> stdOutCompleter = Completer<void>();
|
||||
process.stdout.listen(
|
||||
(List<int> event) {
|
||||
io.stdout.add(event);
|
||||
outputBuffer.write(String.fromCharCodes(event));
|
||||
final String output = String.fromCharCodes(event);
|
||||
if (output.isNotEmpty) {
|
||||
print(output);
|
||||
outputBuffer.write(output);
|
||||
}
|
||||
},
|
||||
onDone: () => stdOutCompleter.complete(),
|
||||
);
|
||||
@ -98,8 +171,11 @@ class PublishCheckCommand extends PluginCommand {
|
||||
final Completer<void> stdInCompleter = Completer<void>();
|
||||
process.stderr.listen(
|
||||
(List<int> event) {
|
||||
io.stderr.add(event);
|
||||
outputBuffer.write(String.fromCharCodes(event));
|
||||
final String output = String.fromCharCodes(event);
|
||||
if (output.isNotEmpty) {
|
||||
_printImportantStatusMessage(output, isError: true);
|
||||
outputBuffer.write(output);
|
||||
}
|
||||
},
|
||||
onDone: () => stdInCompleter.complete(),
|
||||
);
|
||||
@ -121,24 +197,97 @@ class PublishCheckCommand extends PluginCommand {
|
||||
'Packages with an SDK constraint on a pre-release of the Dart SDK should themselves be published as a pre-release version.');
|
||||
}
|
||||
|
||||
Future<bool> _passesPublishCheck(Directory package) async {
|
||||
Future<_PublishCheckResult> _passesPublishCheck(Directory package) async {
|
||||
final String packageName = package.basename;
|
||||
print('Checking that $packageName can be published.');
|
||||
|
||||
final Pubspec pubspec = _tryParsePubspec(package);
|
||||
if (pubspec == null) {
|
||||
return false;
|
||||
print('no pubspec');
|
||||
return _PublishCheckResult._error;
|
||||
} else if (pubspec.publishTo == 'none') {
|
||||
print('Package $packageName is marked as unpublishable. Skipping.');
|
||||
return true;
|
||||
return _PublishCheckResult._published;
|
||||
}
|
||||
|
||||
final Version version = pubspec.version;
|
||||
final _PublishCheckResult alreadyPublishedResult =
|
||||
await _checkIfAlreadyPublished(
|
||||
packageName: packageName, version: version);
|
||||
if (alreadyPublishedResult == _PublishCheckResult._published) {
|
||||
print(
|
||||
'Package $packageName version: $version has already be published on pub.');
|
||||
return alreadyPublishedResult;
|
||||
} else if (alreadyPublishedResult == _PublishCheckResult._error) {
|
||||
print('Check pub version failed $packageName');
|
||||
return _PublishCheckResult._error;
|
||||
}
|
||||
|
||||
if (await _hasValidPublishCheckRun(package)) {
|
||||
print('Package $packageName is able to be published.');
|
||||
return true;
|
||||
return _PublishCheckResult._notPublished;
|
||||
} else {
|
||||
print('Unable to publish $packageName');
|
||||
return false;
|
||||
return _PublishCheckResult._error;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if `packageName` already has `version` published on pub.
|
||||
Future<_PublishCheckResult> _checkIfAlreadyPublished(
|
||||
{String packageName, Version version}) async {
|
||||
final PubVersionFinderResponse pubVersionFinderResponse =
|
||||
await _pubVersionFinder.getPackageVersion(package: packageName);
|
||||
_PublishCheckResult result;
|
||||
switch (pubVersionFinderResponse.result) {
|
||||
case PubVersionFinderResult.success:
|
||||
result = pubVersionFinderResponse.versions.contains(version)
|
||||
? _PublishCheckResult._published
|
||||
: _PublishCheckResult._notPublished;
|
||||
break;
|
||||
case PubVersionFinderResult.fail:
|
||||
print('''
|
||||
Error fetching version on pub for $packageName.
|
||||
HTTP Status ${pubVersionFinderResponse.httpResponse.statusCode}
|
||||
HTTP response: ${pubVersionFinderResponse.httpResponse.body}
|
||||
''');
|
||||
result = _PublishCheckResult._error;
|
||||
break;
|
||||
case PubVersionFinderResult.noPackageFound:
|
||||
result = _PublishCheckResult._notPublished;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void _setStatus(String status) {
|
||||
assert(_validStatus.contains(status));
|
||||
_machineOutput[_statusKey] = status;
|
||||
}
|
||||
|
||||
String _prettyJson(Map<String, dynamic> map) {
|
||||
return const JsonEncoder.withIndent(' ').convert(_machineOutput);
|
||||
}
|
||||
|
||||
void _printImportantStatusMessage(String message, {@required bool isError}) {
|
||||
final String statusMessage = '${isError ? 'ERROR' : 'SUCCESS'}: $message';
|
||||
if (argResults[_machineFlag] as bool) {
|
||||
print(statusMessage);
|
||||
} else {
|
||||
final Colorize colorizedMessage = Colorize(statusMessage);
|
||||
if (isError) {
|
||||
colorizedMessage.red();
|
||||
} else {
|
||||
colorizedMessage.green();
|
||||
}
|
||||
print(colorizedMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum _PublishCheckResult {
|
||||
_notPublished,
|
||||
|
||||
_published,
|
||||
|
||||
_error,
|
||||
}
|
||||
|
Reference in New Issue
Block a user