This repository was archived by the owner on Feb 22, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[tool] version-check publish-check commands can check against pub #3840
Merged
Merged
Changes from 16 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
b6a1cdd
add version check against pub
cf26cd4
close client when not used
46ca9ae
close client
7262ef3
add log
3f2d76d
fix tests
497292e
add next flag
c70da8f
Merge branch 'master' into version_check_pub
e5d149e
publish check query pub
453f8bf
add changelog entry
8a29e36
Merge branch 'version_check_pub' of github.com:cyanglaz/plugins into …
3880f2e
revuiew
4cd1f65
review
3259241
review
6a96692
merge master
3242c8b
review
eb5ee5c
dartfmt
fc26cbb
Merge branch 'master' into version_check_pub
f083fe4
analyze
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,17 +22,40 @@ 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' | ||
'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'; | ||
|
@@ -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'); | ||
_printImportantStatusMessage('$error\n$joinedFailedPackages', | ||
isError: true); | ||
} else { | ||
_printImportantStatusMessage('All packages passed publish check!', | ||
isError: false); | ||
} | ||
|
||
final Colorize colorizedError = Colorize('$error\n$joinedFailedPackages') | ||
..red(); | ||
print(colorizedError); | ||
throw ToolExit(1); | ||
if (argResults[_machineFlag] as bool) { | ||
_setStatus(status); | ||
_machineOutput[_humanMessageKey] = _humanMessages; | ||
print(_prettyJson(_machineOutput)); | ||
} | ||
|
||
final Colorize passedMessage = | ||
Colorize('All packages passed publish check!')..green(); | ||
print(passedMessage); | ||
if (failedPackages.isNotEmpty) { | ||
throw ToolExit(1); | ||
} | ||
} | ||
|
||
Pubspec _tryParsePubspec(Directory package) { | ||
|
@@ -89,17 +159,23 @@ 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(), | ||
); | ||
|
||
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} | ||
'''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm realizing that failures here are going to be almost impossible to debug, because we won't have any information in the log. Perhaps we should consider doing something like a JSON dictionary of output, with status and detailed log messages as separate keys? That would mean we'd need to consume the result with something like a Dart script that can parse the JSON, rather than just a one-liner in the GitHub Action. Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good Idea, updated the code to do exactly that |
||
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, | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.