From 3c07866cdc7efc11b2452e91ee1fc7d466b151e8 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Thu, 1 Jun 2023 12:25:25 -0400 Subject: [PATCH] refactor(shorebird_cli): consolidate command precondition checks in ShorebirdValidationMixin (#575) --- .../lib/src/auth_logger_mixin.dart | 15 ----- .../account/subscribe_account_command.dart | 13 +++-- .../commands/apps/create_apps_command.dart | 16 +++-- .../commands/apps/delete_apps_command.dart | 13 +++-- .../src/commands/apps/list_apps_command.dart | 13 +++-- .../src/commands/build/build_aar_command.dart | 25 ++++---- .../src/commands/build/build_apk_command.dart | 23 +++----- .../build/build_app_bundle_command.dart | 23 +++----- .../src/commands/build/build_ipa_command.dart | 23 +++----- .../add_collaborators_command.dart | 13 +++-- .../delete_collaborators_command.dart | 13 +++-- .../list_collaborators_command.dart | 13 +++-- .../lib/src/commands/init_command.dart | 13 +++-- .../commands/patch/patch_android_command.dart | 26 +++------ .../release_android_archive_command.dart | 28 +++------ .../release/release_android_command.dart | 26 +++------ .../commands/release/release_ios_command.dart | 29 +++------- .../releases/delete_releases_command.dart | 19 +++--- .../releases/list_releases_command.dart | 19 +++--- .../lib/src/commands/run_command.dart | 20 +++---- .../cancel_subscription_command.dart | 13 +++-- .../lib/src/shorebird_validation_mixin.dart | 58 ++++++++++++++++++- .../subscribe_account_command_test.dart | 4 +- .../build/build_aar_command_test.dart | 5 ++ 24 files changed, 229 insertions(+), 234 deletions(-) delete mode 100644 packages/shorebird_cli/lib/src/auth_logger_mixin.dart diff --git a/packages/shorebird_cli/lib/src/auth_logger_mixin.dart b/packages/shorebird_cli/lib/src/auth_logger_mixin.dart deleted file mode 100644 index d559fd71..00000000 --- a/packages/shorebird_cli/lib/src/auth_logger_mixin.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/command.dart'; - -mixin AuthLoggerMixin on ShorebirdCommand { - void printNeedsAuthInstructions() { - logger - ..err('You must be logged in to run this command.') - ..info( - '''If you already have an account, run ${lightCyan.wrap('shorebird login')} to sign in.''', - ) - ..info( - '''If you don't have a Shorebird account, run ${lightCyan.wrap('shorebird account create')} to create one.''', - ); - } -} diff --git a/packages/shorebird_cli/lib/src/commands/account/subscribe_account_command.dart b/packages/shorebird_cli/lib/src/commands/account/subscribe_account_command.dart index cc562098..27c9c3be 100644 --- a/packages/shorebird_cli/lib/src/commands/account/subscribe_account_command.dart +++ b/packages/shorebird_cli/lib/src/commands/account/subscribe_account_command.dart @@ -1,16 +1,16 @@ import 'dart:async'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template subscribe_account_command} /// `shorebird account subscribe` /// {@endtemplate} class SubscribeAccountCommand extends ShorebirdCommand - with AuthLoggerMixin, ShorebirdConfigMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin { /// {@macro subscribe_account_command} SubscribeAccountCommand({ required super.logger, @@ -35,9 +35,12 @@ Visit ${styleUnderlined.wrap(lightCyan.wrap('https://shorebird.dev'))} for more @override Future run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.software.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final client = buildCodePushClient( diff --git a/packages/shorebird_cli/lib/src/commands/apps/create_apps_command.dart b/packages/shorebird_cli/lib/src/commands/apps/create_apps_command.dart index f26fa382..fda777e0 100644 --- a/packages/shorebird_cli/lib/src/commands/apps/create_apps_command.dart +++ b/packages/shorebird_cli/lib/src/commands/apps/create_apps_command.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_create_app_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template create_app_command} @@ -13,7 +13,10 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// Create a new app on Shorebird. /// {@endtemplate} class CreateAppCommand extends ShorebirdCommand - with AuthLoggerMixin, ShorebirdConfigMixin, ShorebirdCreateAppMixin { + with + ShorebirdConfigMixin, + ShorebirdValidationMixin, + ShorebirdCreateAppMixin { /// {@macro create_app_command} CreateAppCommand({ required super.logger, @@ -36,9 +39,12 @@ Defaults to the name in "pubspec.yaml".''', @override Future? run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final appName = results['app-name'] as String?; diff --git a/packages/shorebird_cli/lib/src/commands/apps/delete_apps_command.dart b/packages/shorebird_cli/lib/src/commands/apps/delete_apps_command.dart index 1a088d1f..27120dfa 100644 --- a/packages/shorebird_cli/lib/src/commands/apps/delete_apps_command.dart +++ b/packages/shorebird_cli/lib/src/commands/apps/delete_apps_command.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; /// {@template delete_app_command} /// @@ -11,7 +11,7 @@ import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; /// Delete an existing app on Shorebird. /// {@endtemplate} class DeleteAppCommand extends ShorebirdCommand - with AuthLoggerMixin, ShorebirdConfigMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin { /// {@macro delete_app_command} DeleteAppCommand({ required super.logger, @@ -34,9 +34,12 @@ Defaults to the app_id in "shorebird.yaml".''', @override Future? run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final appIdArg = results['app-id'] as String?; diff --git a/packages/shorebird_cli/lib/src/commands/apps/list_apps_command.dart b/packages/shorebird_cli/lib/src/commands/apps/list_apps_command.dart index 948ac0c6..137f06b0 100644 --- a/packages/shorebird_cli/lib/src/commands/apps/list_apps_command.dart +++ b/packages/shorebird_cli/lib/src/commands/apps/list_apps_command.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:barbecue/barbecue.dart'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template list_apps_command} @@ -13,7 +13,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// List all apps using Shorebird. /// {@endtemplate} class ListAppsCommand extends ShorebirdCommand - with AuthLoggerMixin, ShorebirdConfigMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin { /// {@macro list_apps_command} ListAppsCommand({ required super.logger, @@ -32,9 +32,12 @@ class ListAppsCommand extends ShorebirdCommand @override Future? run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final client = buildCodePushClient( diff --git a/packages/shorebird_cli/lib/src/commands/build/build_aar_command.dart b/packages/shorebird_cli/lib/src/commands/build/build_aar_command.dart index 051b558f..1f2ab81c 100644 --- a/packages/shorebird_cli/lib/src/commands/build/build_aar_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build/build_aar_command.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; @@ -15,14 +14,11 @@ import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; /// Build an Android aar file from your app. /// {@endtemplate} class BuildAarCommand extends ShorebirdCommand - with - AuthLoggerMixin, - ShorebirdValidationMixin, - ShorebirdConfigMixin, - ShorebirdBuildMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin, ShorebirdBuildMixin { BuildAarCommand({ required super.logger, super.auth, + super.validators, }) { // We would have a "target" option here, similar to what [BuildApkCommand] // and [BuildAabCommand] have, but target cannot currently be configured in @@ -49,17 +45,16 @@ class BuildAarCommand extends ShorebirdCommand @override Future run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; - } - - final pubspec = getPubspecYaml(); - if (pubspec == null) { - logger.err('No pubspec.yaml file found.'); - return ExitCode.config.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } + final pubspec = getPubspecYaml()!; final module = pubspec.flutter?['module'] as Map?; final androidPackageName = module?['androidPackage'] as String?; if (androidPackageName == null) { diff --git a/packages/shorebird_cli/lib/src/commands/build/build_apk_command.dart b/packages/shorebird_cli/lib/src/commands/build/build_apk_command.dart index a89b704e..918db811 100644 --- a/packages/shorebird_cli/lib/src/commands/build/build_apk_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build/build_apk_command.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; @@ -14,11 +13,7 @@ import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; /// Build an Android APK file from your app. /// {@endtemplate} class BuildApkCommand extends ShorebirdCommand - with - AuthLoggerMixin, - ShorebirdValidationMixin, - ShorebirdConfigMixin, - ShorebirdBuildMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin, ShorebirdBuildMixin { /// {@macro build_apk_command} BuildApkCommand({ required super.logger, @@ -45,15 +40,13 @@ class BuildApkCommand extends ShorebirdCommand @override Future run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; - } - - final validationIssues = await runValidators(); - if (validationIssuesContainsError(validationIssues)) { - logValidationFailure(issues: validationIssues); - return ExitCode.config.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + checkValidators: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final flavor = results['flavor'] as String?; diff --git a/packages/shorebird_cli/lib/src/commands/build/build_app_bundle_command.dart b/packages/shorebird_cli/lib/src/commands/build/build_app_bundle_command.dart index 98487789..8ff71fb0 100644 --- a/packages/shorebird_cli/lib/src/commands/build/build_app_bundle_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build/build_app_bundle_command.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; @@ -14,11 +13,7 @@ import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; /// Build an Android App Bundle file from your app. /// {@endtemplate} class BuildAppBundleCommand extends ShorebirdCommand - with - AuthLoggerMixin, - ShorebirdValidationMixin, - ShorebirdConfigMixin, - ShorebirdBuildMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin, ShorebirdBuildMixin { /// {@macro build_app_bundle_command} BuildAppBundleCommand({ required super.logger, @@ -45,15 +40,13 @@ class BuildAppBundleCommand extends ShorebirdCommand @override Future run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; - } - - final validationIssues = await runValidators(); - if (validationIssuesContainsError(validationIssues)) { - logValidationFailure(issues: validationIssues); - return ExitCode.config.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + checkValidators: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final flavor = results['flavor'] as String?; diff --git a/packages/shorebird_cli/lib/src/commands/build/build_ipa_command.dart b/packages/shorebird_cli/lib/src/commands/build/build_ipa_command.dart index 0046e5d3..3835387f 100644 --- a/packages/shorebird_cli/lib/src/commands/build/build_ipa_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build/build_ipa_command.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; @@ -14,11 +13,7 @@ import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; /// App Store submission. /// {@endtemplate} class BuildIpaCommand extends ShorebirdCommand - with - AuthLoggerMixin, - ShorebirdValidationMixin, - ShorebirdConfigMixin, - ShorebirdBuildMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin, ShorebirdBuildMixin { /// {@macro build_ipa_command} BuildIpaCommand({required super.logger, super.auth, super.validators}) { argParser @@ -47,15 +42,13 @@ class BuildIpaCommand extends ShorebirdCommand @override Future run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; - } - - final validationIssues = await runValidators(); - if (validationIssuesContainsError(validationIssues)) { - logValidationFailure(issues: validationIssues); - return ExitCode.config.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + checkValidators: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final flavor = results['flavor'] as String?; diff --git a/packages/shorebird_cli/lib/src/commands/collaborators/add_collaborators_command.dart b/packages/shorebird_cli/lib/src/commands/collaborators/add_collaborators_command.dart index 0271d2cd..40a9ec97 100644 --- a/packages/shorebird_cli/lib/src/commands/collaborators/add_collaborators_command.dart +++ b/packages/shorebird_cli/lib/src/commands/collaborators/add_collaborators_command.dart @@ -1,16 +1,16 @@ import 'dart:async'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; /// {@template add_collaborators_command} /// `shorebird collaborators add` /// Add a new collaborator to a Shorebird app. /// {@endtemplate} class AddCollaboratorsCommand extends ShorebirdCommand - with AuthLoggerMixin, ShorebirdConfigMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin { /// {@macro add_collaborators_command} AddCollaboratorsCommand({ required super.logger, @@ -39,9 +39,12 @@ class AddCollaboratorsCommand extends ShorebirdCommand @override Future? run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final client = buildCodePushClient( diff --git a/packages/shorebird_cli/lib/src/commands/collaborators/delete_collaborators_command.dart b/packages/shorebird_cli/lib/src/commands/collaborators/delete_collaborators_command.dart index b4a436be..9b5c67c7 100644 --- a/packages/shorebird_cli/lib/src/commands/collaborators/delete_collaborators_command.dart +++ b/packages/shorebird_cli/lib/src/commands/collaborators/delete_collaborators_command.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template delete_collaborators_command} @@ -12,7 +12,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// Delete an existing collaborator from a Shorebird app. /// {@endtemplate} class DeleteCollaboratorsCommand extends ShorebirdCommand - with AuthLoggerMixin, ShorebirdConfigMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin { /// {@macro delete_collaborators_command} DeleteCollaboratorsCommand({ required super.logger, @@ -42,9 +42,12 @@ class DeleteCollaboratorsCommand extends ShorebirdCommand @override Future? run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final client = buildCodePushClient( diff --git a/packages/shorebird_cli/lib/src/commands/collaborators/list_collaborators_command.dart b/packages/shorebird_cli/lib/src/commands/collaborators/list_collaborators_command.dart index 4f2328cf..505c90e3 100644 --- a/packages/shorebird_cli/lib/src/commands/collaborators/list_collaborators_command.dart +++ b/packages/shorebird_cli/lib/src/commands/collaborators/list_collaborators_command.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:barbecue/barbecue.dart'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template list_collaborators_command} @@ -12,7 +12,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// List all collaborators for a Shorebird app. /// {@endtemplate} class ListCollaboratorsCommand extends ShorebirdCommand - with AuthLoggerMixin, ShorebirdConfigMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin { /// {@macro list_collaborators_command} ListCollaboratorsCommand({ required super.logger, @@ -38,9 +38,12 @@ class ListCollaboratorsCommand extends ShorebirdCommand @override Future? run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final client = buildCodePushClient( diff --git a/packages/shorebird_cli/lib/src/commands/init_command.dart b/packages/shorebird_cli/lib/src/commands/init_command.dart index 23b98c63..7cbd37f5 100644 --- a/packages/shorebird_cli/lib/src/commands/init_command.dart +++ b/packages/shorebird_cli/lib/src/commands/init_command.dart @@ -1,12 +1,12 @@ import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_create_app_mixin.dart'; import 'package:shorebird_cli/src/shorebird_flavor_mixin.dart'; import 'package:shorebird_cli/src/shorebird_java_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; /// {@template init_command} /// @@ -15,8 +15,8 @@ import 'package:shorebird_cli/src/shorebird_java_mixin.dart'; /// {@endtemplate} class InitCommand extends ShorebirdCommand with - AuthLoggerMixin, ShorebirdConfigMixin, + ShorebirdValidationMixin, ShorebirdCreateAppMixin, ShorebirdJavaMixin, ShorebirdFlavorMixin { @@ -38,9 +38,12 @@ class InitCommand extends ShorebirdCommand @override Future run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } try { diff --git a/packages/shorebird_cli/lib/src/commands/patch/patch_android_command.dart b/packages/shorebird_cli/lib/src/commands/patch/patch_android_command.dart index d7adffcb..efb6f08a 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patch_android_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patch_android_command.dart @@ -6,7 +6,6 @@ import 'package:http/http.dart' as http; import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; import 'package:shorebird_cli/src/aab/aab.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/config/shorebird_yaml.dart'; import 'package:shorebird_cli/src/formatters/formatters.dart'; @@ -44,9 +43,8 @@ class PatchArtifactBundle { /// {@endtemplate} class PatchAndroidCommand extends ShorebirdCommand with - AuthLoggerMixin, - ShorebirdValidationMixin, ShorebirdConfigMixin, + ShorebirdValidationMixin, ShorebirdBuildMixin, ShorebirdCreateAppMixin, ShorebirdJavaMixin, @@ -114,16 +112,14 @@ class PatchAndroidCommand extends ShorebirdCommand @override Future run() async { - if (!isShorebirdInitialized) { - logger.err( - 'Shorebird is not initialized. Did you run "shorebird init"?', + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + checkValidators: true, ); - return ExitCode.config.code; - } - - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final force = results['force'] == true; @@ -134,12 +130,6 @@ class PatchAndroidCommand extends ShorebirdCommand return ExitCode.usage.code; } - final validationIssues = await runValidators(); - if (validationIssuesContainsError(validationIssues)) { - logValidationFailure(issues: validationIssues); - return ExitCode.config.code; - } - await cache.updateAll(); final flavor = results['flavor'] as String?; diff --git a/packages/shorebird_cli/lib/src/commands/release/release_android_archive_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_android_archive_command.dart index 6de1e468..58ef0886 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_android_archive_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_android_archive_command.dart @@ -6,7 +6,6 @@ import 'package:collection/collection.dart'; import 'package:crypto/crypto.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/config/config.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; @@ -23,9 +22,8 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@endtemplate} class ReleaseAndroidArchiveCommand extends ShorebirdCommand with - AuthLoggerMixin, - ShorebirdValidationMixin, ShorebirdConfigMixin, + ShorebirdValidationMixin, ShorebirdBuildMixin, ShorebirdCreateAppMixin, ShorebirdJavaMixin, @@ -75,25 +73,17 @@ make smaller updates to your app. @override Future run() async { - if (!isShorebirdInitialized) { - logger.err( - 'Shorebird is not initialized. Did you run "shorebird init"?', + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + checkValidators: true, ); - return ExitCode.config.code; + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; - } - - final validationIssues = await runValidators(); - if (validationIssuesContainsError(validationIssues)) { - logValidationFailure(issues: validationIssues); - return ExitCode.config.code; - } - - // We know the pubspec exists due to the call to isShorebirdInitialized + // We know the pubspec exists due to the checkShorebirdInitialized check // above. final pubspec = getPubspecYaml()!; final module = pubspec.flutter?['module'] as Map?; diff --git a/packages/shorebird_cli/lib/src/commands/release/release_android_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_android_command.dart index 25090220..ac8ff49c 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_android_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_android_command.dart @@ -4,7 +4,6 @@ import 'package:collection/collection.dart'; import 'package:crypto/crypto.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/config/shorebird_yaml.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; @@ -21,9 +20,8 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@endtemplate} class ReleaseAndroidCommand extends ShorebirdCommand with - AuthLoggerMixin, - ShorebirdValidationMixin, ShorebirdConfigMixin, + ShorebirdValidationMixin, ShorebirdBuildMixin, ShorebirdCreateAppMixin, ShorebirdJavaMixin, @@ -69,22 +67,14 @@ make smaller updates to your app. @override Future run() async { - if (!isShorebirdInitialized) { - logger.err( - 'Shorebird is not initialized. Did you run "shorebird init"?', + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + checkValidators: true, ); - return ExitCode.config.code; - } - - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; - } - - final validationIssues = await runValidators(); - if (validationIssuesContainsError(validationIssues)) { - logValidationFailure(issues: validationIssues); - return ExitCode.config.code; + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final flavor = results['flavor'] as String?; diff --git a/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart index c0e785d8..360b9971 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/config/config.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; @@ -15,11 +14,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// Create new app releases for iOS. /// {@endtemplate} class ReleaseIosCommand extends ShorebirdCommand - with - AuthLoggerMixin, - ShorebirdValidationMixin, - ShorebirdConfigMixin, - ShorebirdBuildMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin, ShorebirdBuildMixin { /// {@macro release_ios_command} ReleaseIosCommand({ required super.logger, @@ -58,22 +53,14 @@ make smaller updates to your app. @override Future run() async { - if (!isShorebirdInitialized) { - logger.err( - 'Shorebird is not initialized. Did you run "shorebird init"?', + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, + checkValidators: true, ); - return ExitCode.config.code; - } - - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; - } - - final validationIssues = await runValidators(); - if (validationIssuesContainsError(validationIssues)) { - logValidationFailure(issues: validationIssues); - return ExitCode.config.code; + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final flavor = results['flavor'] as String?; diff --git a/packages/shorebird_cli/lib/src/commands/releases/delete_releases_command.dart b/packages/shorebird_cli/lib/src/commands/releases/delete_releases_command.dart index ff7eb42b..81155362 100644 --- a/packages/shorebird_cli/lib/src/commands/releases/delete_releases_command.dart +++ b/packages/shorebird_cli/lib/src/commands/releases/delete_releases_command.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/config/shorebird_yaml.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template delete_releases_command} @@ -14,7 +14,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// Delete the specified release. /// {@endtemplate} class DeleteReleasesCommand extends ShorebirdCommand - with AuthLoggerMixin, ShorebirdConfigMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin { /// {@macro delete_releases_command} DeleteReleasesCommand({ required super.logger, @@ -40,16 +40,13 @@ class DeleteReleasesCommand extends ShorebirdCommand @override Future run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; - } - - if (!hasShorebirdYaml) { - logger.err( - '''Shorebird is not initialized. Did you run ${lightCyan.wrap('shorebird init')}?''', + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, ); - return ExitCode.config.code; + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final flavor = results['flavor'] as String?; diff --git a/packages/shorebird_cli/lib/src/commands/releases/list_releases_command.dart b/packages/shorebird_cli/lib/src/commands/releases/list_releases_command.dart index fc313922..b85ce76f 100644 --- a/packages/shorebird_cli/lib/src/commands/releases/list_releases_command.dart +++ b/packages/shorebird_cli/lib/src/commands/releases/list_releases_command.dart @@ -1,9 +1,9 @@ import 'package:barbecue/barbecue.dart'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/config/shorebird_yaml.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template list_releases_command} @@ -12,7 +12,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// List all releases for this app. /// {@endtemplate} class ListReleasesCommand extends ShorebirdCommand - with AuthLoggerMixin, ShorebirdConfigMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin { /// {@macro list_releases_command} ListReleasesCommand({ required super.logger, @@ -33,16 +33,13 @@ class ListReleasesCommand extends ShorebirdCommand @override Future run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; - } - - if (!hasShorebirdYaml) { - logger.err( - '''Shorebird is not initialized. Did you run ${lightCyan.wrap('shorebird init')}?''', + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + checkShorebirdInitialized: true, ); - return ExitCode.config.code; + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final flavor = results['flavor'] as String?; diff --git a/packages/shorebird_cli/lib/src/commands/run_command.dart b/packages/shorebird_cli/lib/src/commands/run_command.dart index 0ea9ac3a..6919e26d 100644 --- a/packages/shorebird_cli/lib/src/commands/run_command.dart +++ b/packages/shorebird_cli/lib/src/commands/run_command.dart @@ -1,7 +1,5 @@ import 'dart:convert'; -import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; @@ -11,7 +9,7 @@ import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; /// Run the Flutter application. /// {@endtemplate} class RunCommand extends ShorebirdCommand - with AuthLoggerMixin, ShorebirdValidationMixin, ShorebirdConfigMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin { /// {@macro run_command} RunCommand({ required super.logger, @@ -44,15 +42,13 @@ class RunCommand extends ShorebirdCommand @override Future run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; - } - - final validationIssues = await runValidators(); - if (validationIssuesContainsError(validationIssues)) { - logValidationFailure(issues: validationIssues); - return ExitCode.config.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + checkValidators: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } logger.info('Running app...'); diff --git a/packages/shorebird_cli/lib/src/commands/subscription/cancel_subscription_command.dart b/packages/shorebird_cli/lib/src/commands/subscription/cancel_subscription_command.dart index 07b76187..4853b27e 100644 --- a/packages/shorebird_cli/lib/src/commands/subscription/cancel_subscription_command.dart +++ b/packages/shorebird_cli/lib/src/commands/subscription/cancel_subscription_command.dart @@ -2,13 +2,13 @@ import 'dart:async'; import 'package:intl/intl.dart'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/auth_logger_mixin.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_validation_mixin.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; class CancelSubscriptionCommand extends ShorebirdCommand - with AuthLoggerMixin, ShorebirdConfigMixin { + with ShorebirdConfigMixin, ShorebirdValidationMixin { CancelSubscriptionCommand({ required super.logger, super.auth, @@ -23,9 +23,12 @@ class CancelSubscriptionCommand extends ShorebirdCommand @override Future run() async { - if (!auth.isAuthenticated) { - printNeedsAuthInstructions(); - return ExitCode.noUser.code; + try { + await validatePreconditions( + checkUserIsAuthenticated: true, + ); + } on PreconditionFailedException catch (e) { + return e.exitCode.code; } final client = buildCodePushClient( diff --git a/packages/shorebird_cli/lib/src/shorebird_validation_mixin.dart b/packages/shorebird_cli/lib/src/shorebird_validation_mixin.dart index 80d33739..ceaffed7 100644 --- a/packages/shorebird_cli/lib/src/shorebird_validation_mixin.dart +++ b/packages/shorebird_cli/lib/src/shorebird_validation_mixin.dart @@ -1,9 +1,63 @@ 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_config_mixin.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; -mixin ShorebirdValidationMixin on ShorebirdCommand { +abstract interface class PreconditionFailedException implements Exception { + ExitCode get exitCode; +} + +class ShorebirdNotInitializedException implements PreconditionFailedException { + @override + ExitCode get exitCode => ExitCode.config; +} + +class UserNotAuthorizedException implements PreconditionFailedException { + @override + ExitCode get exitCode => ExitCode.noUser; +} + +class ValidationFailedException implements PreconditionFailedException { + @override + ExitCode get exitCode => ExitCode.config; +} + +mixin ShorebirdValidationMixin on ShorebirdConfigMixin { + /// Checks common preconditions for running a command and throws an + /// appropriate [PreconditionFailedException] if any of them fail. + Future validatePreconditions({ + bool checkShorebirdInitialized = false, + bool checkUserIsAuthenticated = false, + bool checkValidators = false, + }) async { + if (checkUserIsAuthenticated && !auth.isAuthenticated) { + logger + ..err('You must be logged in to run this command.') + ..info( + '''If you already have an account, run ${lightCyan.wrap('shorebird login')} to sign in.''', + ) + ..info( + '''If you don't have a Shorebird account, run ${lightCyan.wrap('shorebird account create')} to create one.''', + ); + throw UserNotAuthorizedException(); + } + + if (checkShorebirdInitialized && !isShorebirdInitialized) { + logger.err( + 'Shorebird is not initialized. Did you run "shorebird init"?', + ); + throw ShorebirdNotInitializedException(); + } + + if (checkValidators) { + final validationIssues = await runValidators(); + if (validationIssuesContainsError(validationIssues)) { + logValidationFailure(issues: validationIssues); + throw ValidationFailedException(); + } + } + } + /// Runs [Validator.validate] on all [validators] and writes results to /// stdout. Future> runValidators() async { diff --git a/packages/shorebird_cli/test/src/commands/account/subscribe_account_command_test.dart b/packages/shorebird_cli/test/src/commands/account/subscribe_account_command_test.dart index f1efe33f..569ea20c 100644 --- a/packages/shorebird_cli/test/src/commands/account/subscribe_account_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/account/subscribe_account_command_test.dart @@ -71,12 +71,12 @@ void main() { ); }); - test('exits with code 70 when user is not logged in', () async { + test('exits with code 67 when user is not logged in', () async { when(() => auth.isAuthenticated).thenReturn(false); final result = await subscribeAccountCommand.run(); - expect(result, ExitCode.software.code); + expect(result, ExitCode.noUser.code); verify( () => logger.err(any(that: contains('You must be logged in to run'))), diff --git a/packages/shorebird_cli/test/src/commands/build/build_aar_command_test.dart b/packages/shorebird_cli/test/src/commands/build/build_aar_command_test.dart index 5c4da9e2..b7b8ea33 100644 --- a/packages/shorebird_cli/test/src/commands/build/build_aar_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/build/build_aar_command_test.dart @@ -23,6 +23,7 @@ class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} void main() { group(BuildAarCommand, () { + const appId = 'test-app-id'; const buildNumber = '1.0'; const noModulePubspecYamlContent = ''' name: example @@ -64,6 +65,9 @@ flutter: ).writeAsStringSync( includeModule ? pubspecYamlContent : noModulePubspecYamlContent, ); + File( + p.join(tempDir.path, 'shorebird.yaml'), + ).writeAsStringSync('app_id: $appId'); return tempDir; } @@ -78,6 +82,7 @@ flutter: command = BuildAarCommand( auth: auth, logger: logger, + validators: [], ) ..testArgResults = argResults ..testProcess = shorebirdProcess