diff --git a/packages/shorebird_cli/lib/src/command.dart b/packages/shorebird_cli/lib/src/command.dart index 0bf02067..b2e683e3 100644 --- a/packages/shorebird_cli/lib/src/command.dart +++ b/packages/shorebird_cli/lib/src/command.dart @@ -33,14 +33,14 @@ abstract class ShorebirdCommand extends Command { CodePushClientBuilder? buildCodePushClient, RunProcess? runProcess, StartProcess? startProcess, - ShorebirdFlutterValidator? flutterValidator, + List? validators, }) : auth = auth ?? Auth(), cache = cache ?? Cache(), buildCodePushClient = buildCodePushClient ?? CodePushClient.new, runProcess = runProcess ?? ShorebirdProcess.run, startProcess = startProcess ?? ShorebirdProcess.start { - this.flutterValidator = flutterValidator ?? - ShorebirdFlutterValidator(runProcess: this.runProcess); + this.validators = + validators ?? [ShorebirdFlutterValidator(runProcess: this.runProcess)]; } final Auth auth; @@ -49,7 +49,9 @@ abstract class ShorebirdCommand extends Command { final Logger logger; final RunProcess runProcess; final StartProcess startProcess; - late final ShorebirdFlutterValidator flutterValidator; + + /// Checks that the Shorebird install and project are in a good state. + late List validators; /// [ArgResults] used for testing purposes only. @visibleForTesting diff --git a/packages/shorebird_cli/lib/src/commands/build_command.dart b/packages/shorebird_cli/lib/src/commands/build_command.dart index f35bd7cd..b0bba2f2 100644 --- a/packages/shorebird_cli/lib/src/commands/build_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build_command.dart @@ -19,7 +19,7 @@ class BuildCommand extends ShorebirdCommand super.auth, super.buildCodePushClient, super.runProcess, - super.flutterValidator, + super.validators, }); @override @@ -37,7 +37,7 @@ class BuildCommand extends ShorebirdCommand return ExitCode.noUser.code; } - await logFlutterValidationIssues(); + await logValidationIssues(); final buildProgress = logger.progress('Building release '); try { diff --git a/packages/shorebird_cli/lib/src/commands/doctor_command.dart b/packages/shorebird_cli/lib/src/commands/doctor_command.dart index 954e2f7c..6a7b997f 100644 --- a/packages/shorebird_cli/lib/src/commands/doctor_command.dart +++ b/packages/shorebird_cli/lib/src/commands/doctor_command.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_version_mixin.dart'; @@ -14,20 +15,19 @@ class DoctorCommand extends ShorebirdCommand with ShorebirdVersionMixin { /// {@macro doctor_command} DoctorCommand({ required super.logger, - List? validators, + super.validators, super.runProcess, }) { - this.validators = validators ?? - [ - ShorebirdVersionValidator( - isShorebirdVersionCurrent: isShorebirdVersionCurrent, - ), - ShorebirdFlutterValidator(runProcess: runProcess), - AndroidInternetPermissionValidator(), - ]; + validators = _allValidators(baseValidators: validators); } - late final List validators; + late final List _doctorValidators = [ + ShorebirdVersionValidator( + isShorebirdVersionCurrent: isShorebirdVersionCurrent, + ), + ShorebirdFlutterValidator(runProcess: runProcess), + AndroidInternetPermissionValidator(), + ]; @override String get name => 'doctor'; @@ -68,4 +68,20 @@ Shorebird v$packageVersion return ExitCode.success.code; } + + /// Creates a list that is the union of [baseValidators] and + /// [_doctorValidators]. + List _allValidators({ + required List baseValidators, + }) { + final missingValidators = _doctorValidators + .where( + (doctorValidator) => baseValidators.none( + (baseValidator) => baseValidator.id == doctorValidator.id, + ), + ) + .toList(); + + return baseValidators + missingValidators; + } } diff --git a/packages/shorebird_cli/lib/src/commands/patch_command.dart b/packages/shorebird_cli/lib/src/commands/patch_command.dart index c8502a18..9ec31d20 100644 --- a/packages/shorebird_cli/lib/src/commands/patch_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch_command.dart @@ -29,7 +29,7 @@ class PatchCommand extends ShorebirdCommand super.buildCodePushClient, super.cache, super.runProcess, - super.flutterValidator, + super.validators, HashFunction? hashFn, http.Client? httpClient, }) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()), @@ -108,7 +108,7 @@ class PatchCommand extends ShorebirdCommand return ExitCode.usage.code; } - await logFlutterValidationIssues(); + await logValidationIssues(); await cache.updateAll(); diff --git a/packages/shorebird_cli/lib/src/commands/release_command.dart b/packages/shorebird_cli/lib/src/commands/release_command.dart index e04edfab..302a43da 100644 --- a/packages/shorebird_cli/lib/src/commands/release_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release_command.dart @@ -27,7 +27,7 @@ class ReleaseCommand extends ShorebirdCommand super.auth, super.buildCodePushClient, super.runProcess, - super.flutterValidator, + super.validators, HashFunction? hashFn, }) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()) { argParser @@ -70,7 +70,7 @@ make smaller updates to your app. return ExitCode.noUser.code; } - await logFlutterValidationIssues(); + await logValidationIssues(); final buildProgress = logger.progress('Building release'); try { diff --git a/packages/shorebird_cli/lib/src/commands/run_command.dart b/packages/shorebird_cli/lib/src/commands/run_command.dart index 0040b341..69ba20ea 100644 --- a/packages/shorebird_cli/lib/src/commands/run_command.dart +++ b/packages/shorebird_cli/lib/src/commands/run_command.dart @@ -17,7 +17,7 @@ class RunCommand extends ShorebirdCommand super.auth, super.buildCodePushClient, super.startProcess, - super.flutterValidator, + super.validators, }); @override @@ -35,7 +35,7 @@ class RunCommand extends ShorebirdCommand return ExitCode.noUser.code; } - await logFlutterValidationIssues(); + await logValidationIssues(); logger.info('Running app...'); final process = await startProcess( diff --git a/packages/shorebird_cli/lib/src/flutter_validation_mixin.dart b/packages/shorebird_cli/lib/src/flutter_validation_mixin.dart index 6084bde1..4a83cb0e 100644 --- a/packages/shorebird_cli/lib/src/flutter_validation_mixin.dart +++ b/packages/shorebird_cli/lib/src/flutter_validation_mixin.dart @@ -1,13 +1,17 @@ +import 'package:collection/collection.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/validators/shorebird_flutter_validator.dart'; mixin FlutterValidationMixin on ShorebirdCommand { /// Runs [ShorebirdFlutterValidator.validate] and writes validation issues to /// stdout. - Future logFlutterValidationIssues() async { - final flutterValidationIssues = await flutterValidator.validate(); - if (flutterValidationIssues.isNotEmpty) { - for (final issue in flutterValidationIssues) { + Future logValidationIssues() async { + final validationIssues = (await Future.wait( + validators.map((v) => v.validate()), + )) + .flattened; + if (validationIssues.isNotEmpty) { + for (final issue in validationIssues) { logger.info(issue.displayMessage); } } diff --git a/packages/shorebird_cli/lib/src/validators/shorebird_flutter_validator.dart b/packages/shorebird_cli/lib/src/validators/shorebird_flutter_validator.dart index 0c07fe72..f3293d26 100644 --- a/packages/shorebird_cli/lib/src/validators/shorebird_flutter_validator.dart +++ b/packages/shorebird_cli/lib/src/validators/shorebird_flutter_validator.dart @@ -57,7 +57,7 @@ class ShorebirdFlutterValidator extends Validator { final message = """ Shorebird Flutter and the Flutter on your path are different versions. \tShorebird Flutter: $shorebirdFlutterVersion -\tSystem Flutter: $pathFlutterVersion''' +\tSystem Flutter: $pathFlutterVersion This can cause unexpected behavior if the version gap is wide. If you're seeing this unexpectedly, please let us know on Shorebird discord!"""; issues.add( diff --git a/packages/shorebird_cli/lib/src/validators/shorebird_version_validator.dart b/packages/shorebird_cli/lib/src/validators/shorebird_version_validator.dart index 636d402a..5cbb7527 100644 --- a/packages/shorebird_cli/lib/src/validators/shorebird_version_validator.dart +++ b/packages/shorebird_cli/lib/src/validators/shorebird_version_validator.dart @@ -27,9 +27,7 @@ class ShorebirdVersionValidator extends Validator { const ValidationIssue( severity: ValidationIssueSeverity.warning, message: ''' -A new version of shorebird is available! -Run `shorebird upgrade` to upgrade. -''', +A new version of shorebird is available! Run `shorebird upgrade` to upgrade.''', ) ]; } diff --git a/packages/shorebird_cli/lib/src/validators/validators.dart b/packages/shorebird_cli/lib/src/validators/validators.dart index 238b0110..4bd153f9 100644 --- a/packages/shorebird_cli/lib/src/validators/validators.dart +++ b/packages/shorebird_cli/lib/src/validators/validators.dart @@ -65,6 +65,9 @@ class ValidationIssue { /// Checks for a specific issue with either the Shorebird installation or the /// current Shorebird project. abstract class Validator { + /// A unique identifer for this class. + String get id => '$runtimeType'; + /// A one-sentence explanation of what this validator is checking. String get description; diff --git a/packages/shorebird_cli/test/src/commands/build_command_test.dart b/packages/shorebird_cli/test/src/commands/build_command_test.dart index 75d75a1b..acf2e4f4 100644 --- a/packages/shorebird_cli/test/src/commands/build_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/build_command_test.dart @@ -68,7 +68,7 @@ void main() { }) async { return processResult; }, - flutterValidator: flutterValidator, + validators: [flutterValidator], )..testArgResults = argResults; testApplicationConfigHome = (_) => applicationConfigHome.path; diff --git a/packages/shorebird_cli/test/src/commands/doctor_command_test.dart b/packages/shorebird_cli/test/src/commands/doctor_command_test.dart index 4f677a7c..a736687f 100644 --- a/packages/shorebird_cli/test/src/commands/doctor_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/doctor_command_test.dart @@ -10,6 +10,9 @@ class _MockShorebirdVersionValidator extends Mock class _MockAndroidInternetPermissionValidator extends Mock implements AndroidInternetPermissionValidator {} +class _MockShorebirdFlutterValidator extends Mock + implements ShorebirdFlutterValidator {} + class _MockLogger extends Mock implements Logger {} class _MockProgress extends Mock implements Progress {} @@ -21,36 +24,49 @@ void main() { late DoctorCommand command; late AndroidInternetPermissionValidator androidInternetPermissionValidator; late ShorebirdVersionValidator shorebirdVersionValidator; + late ShorebirdFlutterValidator shorebirdFlutterValidator; setUp(() { logger = _MockLogger(); progress = _MockProgress(); + when(() => logger.progress(any())).thenReturn(progress); + when(() => logger.info(any())).thenReturn(null); + androidInternetPermissionValidator = _MockAndroidInternetPermissionValidator(); shorebirdVersionValidator = _MockShorebirdVersionValidator(); + shorebirdFlutterValidator = _MockShorebirdFlutterValidator(); + + when(() => androidInternetPermissionValidator.id) + .thenReturn('$AndroidInternetPermissionValidator'); + when(() => androidInternetPermissionValidator.description) + .thenReturn('Android'); + when(() => androidInternetPermissionValidator.validate()) + .thenAnswer((_) async => []); + + when(() => shorebirdVersionValidator.id) + .thenReturn('$ShorebirdVersionValidator'); + when(() => shorebirdVersionValidator.description) + .thenReturn('Shorebird Version'); + when(() => shorebirdVersionValidator.validate()) + .thenAnswer((_) async => []); + + when(() => shorebirdFlutterValidator.id) + .thenReturn('$ShorebirdFlutterValidator'); + when(() => shorebirdFlutterValidator.description) + .thenReturn('Shorebird Flutter'); + when(() => shorebirdFlutterValidator.validate()) + .thenAnswer((_) async => []); command = DoctorCommand( logger: logger, validators: [ androidInternetPermissionValidator, shorebirdVersionValidator, + shorebirdFlutterValidator, ], ); - - when(() => logger.progress(any())).thenReturn(progress); - - when(() => logger.info(any())).thenReturn(null); - - when(() => androidInternetPermissionValidator.description) - .thenReturn('Android'); - when(() => androidInternetPermissionValidator.validate()) - .thenAnswer((_) async => []); - - when(() => shorebirdVersionValidator.description) - .thenReturn('Shorebird Version'); - when(() => shorebirdVersionValidator.validate()) - .thenAnswer((_) async => []); }); test('prints "no issues" when everything is OK', () async { diff --git a/packages/shorebird_cli/test/src/commands/patch_command_test.dart b/packages/shorebird_cli/test/src/commands/patch_command_test.dart index a0f29802..b0b21876 100644 --- a/packages/shorebird_cli/test/src/commands/patch_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch_command_test.dart @@ -144,7 +144,7 @@ flutter: }, logger: logger, httpClient: httpClient, - flutterValidator: flutterValidator, + validators: [flutterValidator], )..testArgResults = argResults; testApplicationConfigHome = (_) => applicationConfigHome.path; diff --git a/packages/shorebird_cli/test/src/commands/release_command_test.dart b/packages/shorebird_cli/test/src/commands/release_command_test.dart index 4820e832..8962313f 100644 --- a/packages/shorebird_cli/test/src/commands/release_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/release_command_test.dart @@ -135,7 +135,7 @@ flutter: return processResult; }, logger: logger, - flutterValidator: flutterValidator, + validators: [flutterValidator], )..testArgResults = argResults; testApplicationConfigHome = (_) => applicationConfigHome.path; diff --git a/packages/shorebird_cli/test/src/commands/run_command_test.dart b/packages/shorebird_cli/test/src/commands/run_command_test.dart index 4ff24303..49eb8f83 100644 --- a/packages/shorebird_cli/test/src/commands/run_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/run_command_test.dart @@ -63,7 +63,7 @@ void main() { startProcess: (executable, arguments, {bool runInShell = false}) async { return process; }, - flutterValidator: flutterValidator, + validators: [flutterValidator], )..testArgResults = argResults; testApplicationConfigHome = (_) => applicationConfigHome.path;