mirror of
https://github.com/flutter/packages.git
synced 2025-08-06 17:28:42 +08:00
277 lines
9.5 KiB
Dart
277 lines
9.5 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:file/file.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:platform/platform.dart';
|
|
import 'package:pub_semver/pub_semver.dart';
|
|
|
|
import 'common/core.dart';
|
|
import 'common/package_looping_command.dart';
|
|
import 'common/process_runner.dart';
|
|
import 'common/pub_version_finder.dart';
|
|
import 'common/repository_package.dart';
|
|
|
|
/// A command to check that packages are publishable via 'dart publish'.
|
|
class PublishCheckCommand extends PackageLoopingCommand {
|
|
/// Creates an instance of the publish command.
|
|
PublishCheckCommand(
|
|
Directory packagesDir, {
|
|
ProcessRunner processRunner = const ProcessRunner(),
|
|
Platform platform = const LocalPlatform(),
|
|
http.Client? httpClient,
|
|
}) : _pubVersionFinder =
|
|
PubVersionFinder(httpClient: httpClient ?? http.Client()),
|
|
super(packagesDir, processRunner: processRunner, platform: platform) {
|
|
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';
|
|
|
|
@override
|
|
final String name = 'publish-check';
|
|
|
|
@override
|
|
final String description =
|
|
'Checks to make sure that a plugin *could* be published.';
|
|
|
|
final PubVersionFinder _pubVersionFinder;
|
|
|
|
/// The overall result of the run for machine-readable output. This is the
|
|
/// highest value that occurs during the run.
|
|
_PublishCheckResult _overallResult = _PublishCheckResult.nothingToPublish;
|
|
|
|
@override
|
|
bool get captureOutput => getBoolArg(_machineFlag);
|
|
|
|
@override
|
|
Future<void> initializeRun() async {
|
|
_overallResult = _PublishCheckResult.nothingToPublish;
|
|
}
|
|
|
|
@override
|
|
Future<PackageResult> runForPackage(RepositoryPackage package) async {
|
|
_PublishCheckResult? result = await _passesPublishCheck(package);
|
|
if (result == null) {
|
|
return PackageResult.skip('Package is marked as unpublishable.');
|
|
}
|
|
if (!_passesAuthorsCheck(package)) {
|
|
_printImportantStatusMessage(
|
|
'No AUTHORS file found. Packages must include an AUTHORS file.',
|
|
isError: true);
|
|
result = _PublishCheckResult.error;
|
|
}
|
|
|
|
if (result.index > _overallResult.index) {
|
|
_overallResult = result;
|
|
}
|
|
return result == _PublishCheckResult.error
|
|
? PackageResult.fail()
|
|
: PackageResult.success();
|
|
}
|
|
|
|
@override
|
|
Future<void> completeRun() async {
|
|
_pubVersionFinder.httpClient.close();
|
|
}
|
|
|
|
@override
|
|
Future<void> handleCapturedOutput(List<String> output) async {
|
|
final Map<String, dynamic> machineOutput = <String, dynamic>{
|
|
_statusKey: _statusStringForResult(_overallResult),
|
|
_humanMessageKey: output,
|
|
};
|
|
|
|
print(const JsonEncoder.withIndent(' ').convert(machineOutput));
|
|
}
|
|
|
|
String _statusStringForResult(_PublishCheckResult result) {
|
|
switch (result) {
|
|
case _PublishCheckResult.nothingToPublish:
|
|
return _statusMessageNoPublish;
|
|
case _PublishCheckResult.needsPublishing:
|
|
return _statusNeedsPublish;
|
|
case _PublishCheckResult.error:
|
|
return _statusMessageError;
|
|
}
|
|
}
|
|
|
|
Pubspec? _tryParsePubspec(RepositoryPackage package) {
|
|
try {
|
|
return package.parsePubspec();
|
|
} on Exception catch (exception) {
|
|
print(
|
|
'Failed to parse `pubspec.yaml` at ${package.pubspecFile.path}: '
|
|
'$exception',
|
|
);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<bool> _hasValidPublishCheckRun(RepositoryPackage package) async {
|
|
print('Running pub publish --dry-run:');
|
|
final io.Process process = await processRunner.start(
|
|
flutterCommand,
|
|
<String>['pub', 'publish', '--', '--dry-run'],
|
|
workingDirectory: package.directory,
|
|
);
|
|
|
|
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.');
|
|
}
|
|
|
|
/// Returns the result of the publish check, or null if the package is marked
|
|
/// as unpublishable.
|
|
Future<_PublishCheckResult?> _passesPublishCheck(
|
|
RepositoryPackage package) async {
|
|
final String packageName = package.directory.basename;
|
|
final Pubspec? pubspec = _tryParsePubspec(package);
|
|
if (pubspec == null) {
|
|
print('No valid pubspec found.');
|
|
return _PublishCheckResult.error;
|
|
} else if (pubspec.publishTo == 'none') {
|
|
return null;
|
|
}
|
|
|
|
final Version? version = pubspec.version;
|
|
final _PublishCheckResult alreadyPublishedResult =
|
|
await _checkPublishingStatus(
|
|
packageName: packageName, version: version);
|
|
if (alreadyPublishedResult == _PublishCheckResult.nothingToPublish) {
|
|
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.needsPublishing;
|
|
} else {
|
|
print('Unable to publish $packageName');
|
|
return _PublishCheckResult.error;
|
|
}
|
|
}
|
|
|
|
// Check if `packageName` already has `version` published on pub.
|
|
Future<_PublishCheckResult> _checkPublishingStatus(
|
|
{required String packageName, required Version? version}) async {
|
|
final PubVersionFinderResponse pubVersionFinderResponse =
|
|
await _pubVersionFinder.getPackageVersion(packageName: packageName);
|
|
switch (pubVersionFinderResponse.result) {
|
|
case PubVersionFinderResult.success:
|
|
return pubVersionFinderResponse.versions.contains(version)
|
|
? _PublishCheckResult.nothingToPublish
|
|
: _PublishCheckResult.needsPublishing;
|
|
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.needsPublishing;
|
|
}
|
|
}
|
|
|
|
bool _passesAuthorsCheck(RepositoryPackage package) {
|
|
final List<String> pathComponents =
|
|
package.directory.fileSystem.path.split(package.directory.path);
|
|
if (pathComponents.contains('third_party')) {
|
|
// Third-party packages aren't required to have an AUTHORS file.
|
|
return true;
|
|
}
|
|
return package.directory.childFile('AUTHORS').existsSync();
|
|
}
|
|
|
|
void _printImportantStatusMessage(String message, {required bool isError}) {
|
|
final String statusMessage = '${isError ? 'ERROR' : 'SUCCESS'}: $message';
|
|
if (getBoolArg(_machineFlag)) {
|
|
print(statusMessage);
|
|
} else {
|
|
if (isError) {
|
|
printError(statusMessage);
|
|
} else {
|
|
printSuccess(statusMessage);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Possible outcomes of of a publishing check.
|
|
enum _PublishCheckResult {
|
|
nothingToPublish,
|
|
needsPublishing,
|
|
error,
|
|
}
|