mirror of
https://github.com/flutter/packages.git
synced 2025-06-07 03:48:39 +08:00

common.dart is a large-and-growing file containing all shared code, which makes it hard to navigate. To make maintenance easier, this splits the file (and its test file) into separate files for each major component or category.
290 lines
9.7 KiB
Dart
290 lines
9.7 KiB
Dart
// Copyright 2013 The Flutter Authors. 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 '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:pub_semver/pub_semver.dart';
|
|
import 'package:pubspec_parse/pubspec_parse.dart';
|
|
|
|
import 'common/core.dart';
|
|
import 'common/plugin_command.dart';
|
|
import 'common/process_runner.dart';
|
|
import 'common/pub_version_finder.dart';
|
|
|
|
/// A command to check that packages are publishable via 'dart publish'.
|
|
class PublishCheckCommand extends PluginCommand {
|
|
/// Creates an instance of the publish command.
|
|
PublishCheckCommand(
|
|
Directory packagesDir, {
|
|
ProcessRunner processRunner = const ProcessRunner(),
|
|
http.Client? httpClient,
|
|
}) : _pubVersionFinder =
|
|
PubVersionFinder(httpClient: httpClient ?? http.Client()),
|
|
super(packagesDir, processRunner: processRunner) {
|
|
argParser.addFlag(
|
|
_allowPrereleaseFlag,
|
|
help: 'Allows the pre-release SDK warning to pass.\n'
|
|
'When enabled, a pub warning, which asks to publish the package as a pre-release version when '
|
|
'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';
|
|
|
|
@override
|
|
final String description =
|
|
'Checks to make sure that a plugin *could* be published.';
|
|
|
|
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 = getBoolArg(_machineFlag);
|
|
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()) {
|
|
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 =
|
|
'The following ${failedPackages.length} package(s) failed the '
|
|
'publishing check:';
|
|
final String joinedFailedPackages = failedPackages.join('\n');
|
|
_printImportantStatusMessage('$error\n$joinedFailedPackages',
|
|
isError: true);
|
|
} else {
|
|
_printImportantStatusMessage('All packages passed publish check!',
|
|
isError: false);
|
|
}
|
|
|
|
if (getBoolArg(_machineFlag)) {
|
|
_setStatus(status);
|
|
_machineOutput[_humanMessageKey] = _humanMessages;
|
|
print(_prettyJson(_machineOutput));
|
|
}
|
|
|
|
if (failedPackages.isNotEmpty) {
|
|
throw ToolExit(1);
|
|
}
|
|
}
|
|
|
|
Pubspec? _tryParsePubspec(Directory package) {
|
|
final File pubspecFile = package.childFile('pubspec.yaml');
|
|
|
|
try {
|
|
return Pubspec.parse(pubspecFile.readAsStringSync());
|
|
} on Exception catch (exception) {
|
|
print(
|
|
'Failed to parse `pubspec.yaml` at ${pubspecFile.path}: $exception}',
|
|
);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<bool> _hasValidPublishCheckRun(Directory package) async {
|
|
final io.Process process = await processRunner.start(
|
|
'flutter',
|
|
<String>['pub', 'publish', '--', '--dry-run'],
|
|
workingDirectory: package,
|
|
);
|
|
|
|
final StringBuffer outputBuffer = StringBuffer();
|
|
|
|
final Completer<void> stdOutCompleter = Completer<void>();
|
|
process.stdout.listen(
|
|
(List<int> event) {
|
|
final String output = String.fromCharCodes(event);
|
|
if (output.isNotEmpty) {
|
|
print(output);
|
|
outputBuffer.write(output);
|
|
}
|
|
},
|
|
onDone: () => stdOutCompleter.complete(),
|
|
);
|
|
|
|
final Completer<void> stdInCompleter = Completer<void>();
|
|
process.stderr.listen(
|
|
(List<int> event) {
|
|
final String output = String.fromCharCodes(event);
|
|
if (output.isNotEmpty) {
|
|
// The final result is always printed on stderr, whether success or
|
|
// failure.
|
|
final bool isError = !output.contains('has 0 warnings');
|
|
_printImportantStatusMessage(output, isError: isError);
|
|
outputBuffer.write(output);
|
|
}
|
|
},
|
|
onDone: () => stdInCompleter.complete(),
|
|
);
|
|
|
|
if (await process.exitCode == 0) {
|
|
return true;
|
|
}
|
|
|
|
if (!getBoolArg(_allowPrereleaseFlag)) {
|
|
return false;
|
|
}
|
|
|
|
await stdOutCompleter.future;
|
|
await stdInCompleter.future;
|
|
|
|
final String output = outputBuffer.toString();
|
|
return output.contains('Package has 1 warning') &&
|
|
output.contains(
|
|
'Packages with an SDK constraint on a pre-release of the Dart SDK should themselves be published as a pre-release version.');
|
|
}
|
|
|
|
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) {
|
|
print('no pubspec');
|
|
return _PublishCheckResult._error;
|
|
} else if (pubspec.publishTo == 'none') {
|
|
print('Package $packageName is marked as unpublishable. Skipping.');
|
|
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 _PublishCheckResult._notPublished;
|
|
} else {
|
|
print('Unable to publish $packageName');
|
|
return _PublishCheckResult._error;
|
|
}
|
|
}
|
|
|
|
// Check if `packageName` already has `version` published on pub.
|
|
Future<_PublishCheckResult> _checkIfAlreadyPublished(
|
|
{required String packageName, required Version? version}) async {
|
|
final PubVersionFinderResponse pubVersionFinderResponse =
|
|
await _pubVersionFinder.getPackageVersion(package: packageName);
|
|
switch (pubVersionFinderResponse.result) {
|
|
case PubVersionFinderResult.success:
|
|
return pubVersionFinderResponse.versions.contains(version)
|
|
? _PublishCheckResult._published
|
|
: _PublishCheckResult._notPublished;
|
|
case PubVersionFinderResult.fail:
|
|
print('''
|
|
Error fetching version on pub for $packageName.
|
|
HTTP Status ${pubVersionFinderResponse.httpResponse.statusCode}
|
|
HTTP response: ${pubVersionFinderResponse.httpResponse.body}
|
|
''');
|
|
return _PublishCheckResult._error;
|
|
case PubVersionFinderResult.noPackageFound:
|
|
return _PublishCheckResult._notPublished;
|
|
}
|
|
}
|
|
|
|
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 (getBoolArg(_machineFlag)) {
|
|
print(statusMessage);
|
|
} else {
|
|
final Colorize colorizedMessage = Colorize(statusMessage);
|
|
if (isError) {
|
|
colorizedMessage.red();
|
|
} else {
|
|
colorizedMessage.green();
|
|
}
|
|
print(colorizedMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
enum _PublishCheckResult {
|
|
_notPublished,
|
|
|
|
_published,
|
|
|
|
_error,
|
|
}
|