From 3706ab8f1c0f1ea63710b06f26f9390c8d796eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malo=20L=C3=A9on?= <malo.leon@wyatt-studio.fr> Date: Mon, 19 Feb 2024 12:57:59 +0000 Subject: [PATCH] chore(wyatt_continuous_deployment): migrate the all CLI as package --- .../example/lib/main.dart | 2 +- .../lib/src/bootstrap.dart | 26 ++++ .../build_and_deploy_app_command.dart | 36 +++++ .../build_and_deploy_android_app_command.dart | 32 +++++ .../build_and_deploy_android_app_task.dart | 50 +++++++ .../build_and_deploy_ios_app_command.dart | 31 ++++ .../build_and_deploy_ios_app_task.dart | 50 +++++++ .../commands/build_app/build_app_command.dart | 35 +++++ .../commands/build_app/build_app_states.dart | 32 +++++ .../commands/build_app/build_app_task.dart | 57 ++++++++ .../build_app_bundle_command.dart | 31 ++++ .../build_app_bundle_state.dart | 32 +++++ .../build_app_bundle_task.dart | 91 ++++++++++++ .../commands/build_ipa/build_ipa_command.dart | 31 ++++ .../commands/build_ipa/build_ipa_state.dart | 47 +++++++ .../commands/build_ipa/build_ipa_task.dart | 133 ++++++++++++++++++ .../lib/src/core/commands/commands.dart | 20 +++ .../deploy_android_command.dart | 31 ++++ .../deploy_android/deploy_android_state.dart | 27 ++++ .../deploy_android/deploy_android_task.dart | 84 +++++++++++ .../deploy_ios/deploy_ios_command.dart | 31 ++++ .../commands/deploy_ios/deploy_ios_state.dart | 42 ++++++ .../commands/deploy_ios/deploy_ios_task.dart | 118 ++++++++++++++++ .../deploy_app/deploy_app_command.dart | 35 +++++ .../init_project/init_project_command.dart | 39 +++++ .../init_project/init_project_state.dart | 28 ++++ .../init_project/init_project_task.dart | 85 +++++++++++ .../lib/src/core/commands/task_command.dart | 80 +++++++++++ .../lib/src/core/constants/app_constants.dart | 7 + .../lib/src/core/core.dart | 17 +++ .../lib/src/core/enums/flag.dart | 50 +++++++ .../src/core/extensions/object_extension.dart | 23 +++ .../lib/src/core/task/task_cubit.dart | 90 ++++++++++++ .../lib/src/core/task/task_state.dart | 85 +++++++++++ .../lib/src/core/utils/logging.dart | 5 +- .../lib/src/core/utils/task_observer.dart | 36 +++++ .../lib/src/domain/entities/context.dart | 33 +++++ .../lib/src/domain/entities/flutter.dart | 6 +- .../config/get_android_config_usecase.dart | 14 +- .../config/get_ios_config_usecase.dart | 12 +- .../usecases/config/init_config_usecase.dart | 15 +- .../decrypt_keys/decrypt_keys_usecase.dart | 18 ++- .../usecases/find_last_ipa_file_usecase.dart | 14 +- .../src/domain/usecases/process_usecase.dart | 19 ++- .../check_config_file_usecase.dart | 15 +- .../requirements/check_tools_usecase.dart | 3 +- .../lib/src/domain/usecases/usecase.dart | 34 +++++ .../lib/src/version.dart | 1 + .../lib/src/wyatt_continuous_deployment.dart | 22 +-- ..._continuous_deployment_command_runner.dart | 110 +++++++++++++++ .../lib/wyatt_continuous_deployment.dart | 3 +- .../wyatt_continuous_deployment/pubspec.yaml | 4 + 52 files changed, 1924 insertions(+), 48 deletions(-) create mode 100644 packages/wyatt_continuous_deployment/lib/src/bootstrap.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/build_and_deploy_app_command.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_command.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_task.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_command.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_task.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_command.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_states.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_task.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_command.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_state.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_task.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_command.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_state.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_task.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/commands.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_command.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_state.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_task.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_command.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_state.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_task.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/deploy_app_command.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_command.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_state.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_task.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/task_command.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/core.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/enums/flag.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/extensions/object_extension.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/task/task_cubit.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/task/task_state.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/utils/task_observer.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/domain/entities/context.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/domain/usecases/usecase.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/version.dart create mode 100644 packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment_command_runner.dart diff --git a/packages/wyatt_continuous_deployment/example/lib/main.dart b/packages/wyatt_continuous_deployment/example/lib/main.dart index d3342cf8..6eb17eda 100644 --- a/packages/wyatt_continuous_deployment/example/lib/main.dart +++ b/packages/wyatt_continuous_deployment/example/lib/main.dart @@ -20,7 +20,7 @@ class WyattContinuousDeploymentExample { static Future<void> run(List<String> args) async { const useCase = CheckToolsUsecase(); - final result = await useCase(); + final result = await useCase(null); result.fold((value) => print('Success'), (error) => print('Error: $error')); } diff --git a/packages/wyatt_continuous_deployment/lib/src/bootstrap.dart b/packages/wyatt_continuous_deployment/lib/src/bootstrap.dart new file mode 100644 index 00000000..33660195 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/bootstrap.dart @@ -0,0 +1,26 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:bloc/bloc.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; +import 'package:wyatt_continuous_deployment/src/core/utils/task_observer.dart'; + +Future<void> bootstrap([Logger? logger]) async { + getIt.registerSingleton<Logger>(logger ?? Logger()); + await Injectable.configureDependencies(); + Bloc.observer = TaskObserver(); +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/build_and_deploy_app_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/build_and_deploy_app_command.dart new file mode 100644 index 00000000..e95b779d --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/build_and_deploy_app_command.dart @@ -0,0 +1,36 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:args/command_runner.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_command.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_command.dart'; + +class BuildAndDeployAppCommand extends Command<int> { + BuildAndDeployAppCommand() { + addSubcommand(BuildAndDeployIosAppCommand()); + addSubcommand(BuildAndDeployAndroidAppCommand()); + } + + @override + String get description => 'Build and deploy app. ' + 'Same as running `build` and `deploy` commands separately.'; + + @override + String get name => 'build-deploy'; + + @override + List<String> get aliases => ['bd']; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_command.dart new file mode 100644 index 00000000..8aeb749c --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_command.dart @@ -0,0 +1,32 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:wyatt_continuous_deployment/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart'; + +class BuildAndDeployAndroidAppCommand extends TaskCommand { + BuildAndDeployAndroidAppCommand() + : super(task: BuildAndDeployAndroidAppTask()); + + @override + String get description => 'Build and deploy Android app'; + + @override + String get name => 'android'; + + @override + List<String> get aliases => ['a', 'playstore']; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_task.dart new file mode 100644 index 00000000..e52cd52d --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_task.dart @@ -0,0 +1,50 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:wyatt_continuous_deployment/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/deploy_app/commands/deploy_android/deploy_android_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; +import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart'; + +class BuildAndDeployAndroidAppTask extends TaskCubit { + factory BuildAndDeployAndroidAppTask() => BuildAndDeployAndroidAppTask._( + checkToolsUsecase: getIt(), + checkConfigFileUsecase: getIt(), + ); + + BuildAndDeployAndroidAppTask._({ + required super.checkToolsUsecase, + required super.checkConfigFileUsecase, + }); + + @override + Future<void> onRun(Context ctx) async { + // 1. Build Android app + final buildAppBundleTask = BuildAppBundleTask(); + await buildAppBundleTask.onRun(ctx); + if (buildAppBundleTask.status != 0) { + return emit(buildAppBundleTask.state); + } + + // 2. Deploy it + final deployAndroidTask = DeployAndroidTask(); + await deployAndroidTask.onRun(ctx); + if (deployAndroidTask.status != 0) { + return emit(deployAndroidTask.state); + } + } +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_command.dart new file mode 100644 index 00000000..8b9b7dcd --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_command.dart @@ -0,0 +1,31 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:wyatt_continuous_deployment/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart'; + +class BuildAndDeployIosAppCommand extends TaskCommand { + BuildAndDeployIosAppCommand() : super(task: BuildAndDeployIosAppTask()); + + @override + String get description => 'Build and deploy IOS app'; + + @override + String get name => 'ios'; + + @override + List<String> get aliases => ['i', 'appstore']; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_task.dart new file mode 100644 index 00000000..3cb656b1 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_task.dart @@ -0,0 +1,50 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:wyatt_continuous_deployment/src/core/commands/build_app/commands/build_ipa/build_ipa_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; +import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart'; + +class BuildAndDeployIosAppTask extends TaskCubit { + factory BuildAndDeployIosAppTask() => BuildAndDeployIosAppTask._( + checkToolsUsecase: getIt(), + checkConfigFileUsecase: getIt(), + ); + + BuildAndDeployIosAppTask._({ + required super.checkToolsUsecase, + required super.checkConfigFileUsecase, + }); + + @override + Future<void> onRun(Context ctx) async { + // 1. Build ipa + final buildIpaTask = BuildIpaTask(); + await buildIpaTask.onRun(ctx); + if (buildIpaTask.status != 0) { + return emit(buildIpaTask.state); + } + + // 2. Deploy it + final deployIosTask = DeployIosTask(); + await deployIosTask.onRun(ctx); + if (deployIosTask.status != 0) { + return emit(deployIosTask.state); + } + } +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_command.dart new file mode 100644 index 00000000..18c626f7 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_command.dart @@ -0,0 +1,35 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:args/command_runner.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_command.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/build_app/commands/build_ipa/build_ipa_command.dart'; + +class BuildAppCommand extends Command<int> { + BuildAppCommand() { + addSubcommand(BuildAppBundleCommand()); + addSubcommand(BuildIpaCommand()); + } + + @override + String get description => 'Build the app'; + + @override + String get name => 'build'; + + @override + List<String> get aliases => ['b']; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_states.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_states.dart new file mode 100644 index 00000000..34adf766 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_states.dart @@ -0,0 +1,32 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +part of 'build_app_task.dart'; + +class BuildAppTaskFlutterClean extends TaskStateRunning { + @override + String get description => 'Clean flutter workspace'; +} + +class BuildAppTaskFlutterPubGet extends TaskStateRunning { + @override + String get description => 'Get dependencies'; +} + +class BuildAppTaskBuildApp extends TaskStateRunning { + @override + String get description => 'Build app'; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_task.dart new file mode 100644 index 00000000..6e42d086 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_task.dart @@ -0,0 +1,57 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'dart:async'; + +import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/flutter.dart'; + +part 'build_app_states.dart'; + +abstract class BuildAppTask extends TaskCubit { + BuildAppTask({ + required this.flutter, + required super.checkToolsUsecase, + required super.checkConfigFileUsecase, + }); + + final Flutter flutter; + + FutureOr<void> buildApp(Context ctx); + + @override + Future<void> onRun(Context ctx) async { + emit(BuildAppTaskFlutterClean()); + + final flutterCleanResult = await flutter.clean(); + if (flutterCleanResult.isErr) { + return emit(TaskStateError(message: flutterCleanResult.err.toString())); + } + + emit(BuildAppTaskFlutterPubGet()); + + final flutterGetDependenciesResult = await flutter.getDependencies(); + if (flutterGetDependenciesResult.isErr) { + return emit( + TaskStateError(message: flutterGetDependenciesResult.err.toString()), + ); + } + + emit(BuildAppTaskBuildApp()); + await buildApp(ctx); + } +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_command.dart new file mode 100644 index 00000000..2f86db8d --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_command.dart @@ -0,0 +1,31 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:wyatt_continuous_deployment/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart'; + +class BuildAppBundleCommand extends TaskCommand { + BuildAppBundleCommand() : super(task: BuildAppBundleTask()); + + @override + String get description => 'Build the Android app bundle'; + + @override + String get name => 'appbundle'; + + @override + List<String> get aliases => ['a', 'aab', 'android']; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_state.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_state.dart new file mode 100644 index 00000000..952af2ff --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_state.dart @@ -0,0 +1,32 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +part of 'build_app_bundle_task.dart'; + +class BuildAppBundleStateGetAndroidConfig extends TaskStateRunning { + @override + String get description => 'Get android config'; +} + +class BuildAppBundleStateDecryptAndroidKeys extends TaskStateRunning { + @override + String get description => 'Decrypt android keys'; +} + +class BuildAppBundleStateBuildAppBundle extends TaskStateRunning { + @override + String get description => 'Build App Bundle'; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_task.dart new file mode 100644 index 00000000..bd821b74 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_task.dart @@ -0,0 +1,91 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'dart:async'; + +import 'package:wyatt_continuous_deployment/src/core/commands/build_app/build_app_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart'; +import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; +import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/decrypt_keys_parameters.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/config/get_android_config_usecase.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart'; + +part 'build_app_bundle_state.dart'; + +class BuildAppBundleTask extends BuildAppTask { + factory BuildAppBundleTask() => BuildAppBundleTask._( + flutter: getIt(), + decryptKeysUsecase: getIt(), + getAndroidConfigUsecase: getIt(), + checkToolsUsecase: getIt(), + checkConfigFileUsecase: getIt(), + ); + + BuildAppBundleTask._({ + required DecryptKeysUsecase decryptKeysUsecase, + required GetAndroidConfigUsecase getAndroidConfigUsecase, + required super.flutter, + required super.checkToolsUsecase, + required super.checkConfigFileUsecase, + }) : _decryptKeysUsecase = decryptKeysUsecase, + _getAndroidConfigUsecase = getAndroidConfigUsecase; + + final DecryptKeysUsecase _decryptKeysUsecase; + final GetAndroidConfigUsecase _getAndroidConfigUsecase; + + @override + FutureOr<void> buildApp(Context ctx) async { + emit(BuildAppBundleStateGetAndroidConfig()); + + // 1. Get Android config + final getAndroidConfigResult = + await _getAndroidConfigUsecase(ctx.configFile.path); + if (getAndroidConfigResult.isErr) { + return emit( + TaskStateError(message: getAndroidConfigResult.err.toString()), + ); + } + + // 2. Decrypt Android keys + emit(BuildAppBundleStateDecryptAndroidKeys()); + final decryptAndroidkeysResult = await _decryptKeysUsecase( + DecryptKeysParameters( + encryptedFilePath: getAndroidConfigResult.ok!.encryptedKeyFile.path, + passphrase: ctx.environment[AppConstants.androidKeysSecretPassphrase] + .toString(), + ), + ); + if (decryptAndroidkeysResult.isErr) { + return emit( + TaskStateError(message: decryptAndroidkeysResult.err.toString()), + ); + } + + // 3. Build Android App Bundle + emit(BuildAppBundleStateBuildAppBundle()); + final buildAppBundleResult = await flutter + .buildAppBundle(getAndroidConfigResult.ok!.flutterArgs.split(' ')); + if (buildAppBundleResult.isErr) { + return emit( + TaskStateError(message: decryptAndroidkeysResult.err.toString()), + ); + } + + emit(const TaskStateSucceed()); + } +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_command.dart new file mode 100644 index 00000000..814b6a2f --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_command.dart @@ -0,0 +1,31 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:wyatt_continuous_deployment/src/core/commands/build_app/commands/build_ipa/build_ipa_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart'; + +class BuildIpaCommand extends TaskCommand { + BuildIpaCommand() : super(task: BuildIpaTask()); + + @override + String get description => 'Build the iOS IPA'; + + @override + String get name => 'ipa'; + + @override + List<String> get aliases => ['i', 'ios']; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_state.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_state.dart new file mode 100644 index 00000000..9c36dd03 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_state.dart @@ -0,0 +1,47 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +part of 'build_ipa_task.dart'; + +class BuildIpaStateGetIosConfig extends TaskStateRunning { + @override + String get description => 'Get iOS config'; +} + +class BuildIpaStateDecryptIosKey extends TaskStateRunning { + @override + String get description => 'Decrypt ios keys'; +} + +class BuildIpaStateRetrieveIosKey extends TaskStateRunning { + @override + String get description => 'Retrieve ios keys'; +} + +class BuildIpaStateCreateKeychain extends TaskStateRunning { + @override + String get description => 'Create new keychain'; +} + +class BuildIpaStateRetrieveCertificates extends TaskStateRunning { + @override + String get description => 'Retrieve match certificates'; +} + +class BuildIpaStateBuildIpa extends TaskStateRunning { + @override + String get description => 'Build ipa'; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_task.dart new file mode 100644 index 00000000..bf7f9402 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_task.dart @@ -0,0 +1,133 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'dart:async'; + +import 'package:wyatt_continuous_deployment/src/core/commands/build_app/build_app_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart'; +import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; +import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/decrypt_keys_parameters.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_create_keychain_parameters.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_get_certificates_parameters.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/config/get_ios_config_usecase.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart'; + +part 'build_ipa_state.dart'; + +class BuildIpaTask extends BuildAppTask { + factory BuildIpaTask() => BuildIpaTask._( + flutter: getIt(), + fastlane: getIt(), + decryptKeysUsecase: getIt(), + getIosConfigUsecase: getIt(), + checkToolsUsecase: getIt(), + checkConfigFileUsecase: getIt(), + ); + + BuildIpaTask._({ + required DecryptKeysUsecase decryptKeysUsecase, + required GetIosConfigUsecase getIosConfigUsecase, + required Fastlane fastlane, + required super.flutter, + required super.checkToolsUsecase, + required super.checkConfigFileUsecase, + }) : _decryptKeysUsecase = decryptKeysUsecase, + _getIosConfigUsecase = getIosConfigUsecase, + _fastlane = fastlane; + + final DecryptKeysUsecase _decryptKeysUsecase; + final GetIosConfigUsecase _getIosConfigUsecase; + final Fastlane _fastlane; + + @override + FutureOr<void> buildApp(Context ctx) async { + // 1. Get iOS Config + emit(BuildIpaStateGetIosConfig()); + final getIosConfigResult = await _getIosConfigUsecase(ctx.configFile.path); + if (getIosConfigResult.isErr) { + return emit( + TaskStateError(message: getIosConfigResult.err.toString()), + ); + } + + // 2. Decrypt iOS keys + emit(BuildIpaStateDecryptIosKey()); + final decryptIosKeyResult = await _decryptKeysUsecase( + DecryptKeysParameters( + encryptedFilePath: getIosConfigResult.ok!.encryptedKeyFile.path, + passphrase: + ctx.environment[AppConstants.iosKeysSecretPassphrase].toString(), + ), + ); + if (decryptIosKeyResult.isErr) { + return emit( + TaskStateError(message: decryptIosKeyResult.err.toString()), + ); + } + + // 5. Create Keychain + emit(BuildIpaStateCreateKeychain()); + final fastlaneCreateKeychainResult = await _fastlane.createKeychain( + FastlaneCreateKeychainParameters( + name: 'match', + password: 'flutter-cd-cli', + gitPassphrase: getIosConfigResult.ok!.gitPassphrase, + ), + ); + if (fastlaneCreateKeychainResult.isErr) { + return emit( + TaskStateError(message: fastlaneCreateKeychainResult.err.toString()), + ); + } + + // 6. Retrieve certificates + emit(BuildIpaStateRetrieveCertificates()); + final fastlaneGetCertificateResult = await _fastlane.getCertificates( + FastlaneGetCertificatesParameters( + appIdentifier: getIosConfigResult.ok!.developerAppIdentifier, + gitPrivateKey: + '${getIosConfigResult.ok!.iosFolderPath}/${getIosConfigResult.ok!.gitCertsPrivateKeyFileName}', + keychainName: 'match', + keychainPassword: 'flutter-cd-cli', + userName: getIosConfigResult.ok!.userName, + teamId: getIosConfigResult.ok!.teamId, + teamName: getIosConfigResult.ok!.teamName, + gitUrl: getIosConfigResult.ok!.gitUrl, + gitPassphrase: getIosConfigResult.ok!.gitPassphrase, + ), + ); + if (fastlaneGetCertificateResult.isErr) { + return emit( + TaskStateError(message: fastlaneGetCertificateResult.err.toString()), + ); + } + + // 7. Build IPA + emit(BuildIpaStateBuildIpa()); + final flutterBuildIpaResult = + await flutter.buildIpa(getIosConfigResult.ok!.flutterArgs.split(' ')); + if (flutterBuildIpaResult.isErr) { + return emit( + TaskStateError(message: flutterBuildIpaResult.err.toString()), + ); + } + + emit(const TaskStateSucceed()); + } +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/commands.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/commands.dart new file mode 100644 index 00000000..4bbeb7a8 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/commands.dart @@ -0,0 +1,20 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +export './build_and_deploy_app/build_and_deploy_app_command.dart'; +export './build_app/build_app_command.dart'; +export './deploy_app/deploy_app_command.dart'; +export './init_project/init_project_command.dart'; diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_command.dart new file mode 100644 index 00000000..6b2f97bd --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_command.dart @@ -0,0 +1,31 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:wyatt_continuous_deployment/src/core/commands/deploy_app/commands/deploy_android/deploy_android_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart'; + +class DeployAndroidCommand extends TaskCommand { + DeployAndroidCommand() : super(task: DeployAndroidTask()); + + @override + String get description => 'Deploy the android app on the play store'; + + @override + String get name => 'android'; + + @override + List<String> get aliases => ['a', 'playstore']; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_state.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_state.dart new file mode 100644 index 00000000..96f1a90e --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_state.dart @@ -0,0 +1,27 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +part of 'deploy_android_task.dart'; + +class DeployAndroidStateDeployToPlayStore extends TaskStateRunning { + @override + String get description => 'Upload appbundle to Play Store'; +} + +class DeployAndroidStateGetAndroidConfig extends TaskStateRunning { + @override + String get description => 'Get android options'; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_task.dart new file mode 100644 index 00000000..9fd1b266 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_task.dart @@ -0,0 +1,84 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'dart:async'; + +import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart'; +import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; +import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_deploy_supply_parameters.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/config/get_android_config_usecase.dart'; + +part 'deploy_android_state.dart'; + +class DeployAndroidTask extends TaskCubit { + factory DeployAndroidTask() => DeployAndroidTask._( + getAndroidConfigUsecase: getIt(), + fastlane: getIt(), + checkToolsUsecase: getIt(), + checkConfigFileUsecase: getIt(), + ); + + DeployAndroidTask._({ + required GetAndroidConfigUsecase getAndroidConfigUsecase, + required Fastlane fastlane, + required super.checkToolsUsecase, + required super.checkConfigFileUsecase, + }) : _getAndroidConfigUsecase = getAndroidConfigUsecase, + _fastlane = fastlane; + + final GetAndroidConfigUsecase _getAndroidConfigUsecase; + final Fastlane _fastlane; + + @override + Future<void> onRun(Context ctx) async { + // 1. Get Android Config + emit(DeployAndroidStateGetAndroidConfig()); + + final getAndroidConfigResult = + await _getAndroidConfigUsecase(ctx.configFile.path); + if (getAndroidConfigResult.isErr) { + return emit( + TaskStateError( + message: getAndroidConfigResult.err.toString(), + ), + ); + } + + // 2. Deploy to Play Store + emit(DeployAndroidStateDeployToPlayStore()); + final deploySupplyResult = await _fastlane.deployGooglePlay( + FastlaneDeploySupplyParameters( + packageName: getAndroidConfigResult.ok!.packageName.toString(), + track: getAndroidConfigResult.ok!.track, + serviceAccountPath: + '${getAndroidConfigResult.ok!.androidFolderPath}/${getAndroidConfigResult.ok!.googleServiceAccountFileName}', + appBundlePath: AppConstants.androidBundleBuildPath, + releaseStatus: getAndroidConfigResult.ok!.releaseStatus, + ), + ); + + if (deploySupplyResult.isErr) { + return emit( + TaskStateError( + message: deploySupplyResult.err.toString(), + ), + ); + } + } +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_command.dart new file mode 100644 index 00000000..72be263c --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_command.dart @@ -0,0 +1,31 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:wyatt_continuous_deployment/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart'; + +class DeployIosCommand extends TaskCommand { + DeployIosCommand() : super(task: DeployIosTask()); + + @override + String get description => 'Deploy the ios app on the AppStore connect'; + + @override + String get name => 'ios'; + + @override + List<String> get aliases => ['i', 'appstore']; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_state.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_state.dart new file mode 100644 index 00000000..9bee3d42 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_state.dart @@ -0,0 +1,42 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +part of 'deploy_ios_task.dart'; + +class DeployIosStateGetIosConfig extends TaskStateRunning { + @override + String get description => 'Get iOS config'; +} + +class DeployIosStatedecryptIosKeys extends TaskStateRunning { + @override + String get description => 'Decrypt ios keys'; +} + +class DeployIosStateRetrieveIosKey extends TaskStateRunning { + @override + String get description => 'Retrieve ios keys'; +} + +class DeployIosStateCheckRequiredIosKey extends TaskStateRunning { + @override + String get description => 'Check if all required keys exist'; +} + +class DeployIosStateDeployToAppStoreConnect extends TaskStateRunning { + @override + String get description => 'Upload ipa to AppStore Connect'; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_task.dart new file mode 100644 index 00000000..46ce7b67 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_task.dart @@ -0,0 +1,118 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'dart:async'; +import 'dart:io'; + +import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart'; +import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; +import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart'; +import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/config/appstore_connect_config.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/decrypt_keys_parameters.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_deploy_pilot_parameters.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/config/get_ios_config_usecase.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart'; + +part 'deploy_ios_state.dart'; + +class DeployIosTask extends TaskCubit { + factory DeployIosTask() => DeployIosTask._( + getIosConfigUsecase: getIt(), + decryptKeysUsecase: getIt(), + fastlane: getIt(), + checkToolsUsecase: getIt(), + checkConfigFileUsecase: getIt(), + ); + + DeployIosTask._({ + required GetIosConfigUsecase getIosConfigUsecase, + required DecryptKeysUsecase decryptKeysUsecase, + required Fastlane fastlane, + required super.checkToolsUsecase, + required super.checkConfigFileUsecase, + }) : _getIosConfigUsecase = getIosConfigUsecase, + _decryptKeysUsecase = decryptKeysUsecase, + _fastlane = fastlane; + + final GetIosConfigUsecase _getIosConfigUsecase; + final DecryptKeysUsecase _decryptKeysUsecase; + final Fastlane _fastlane; + + @override + Future<void> onRun(Context ctx) async { + // 1. Get iOS config + emit(DeployIosStateGetIosConfig()); + final getIosConfigResult = await _getIosConfigUsecase(ctx.configFile.path); + if (getIosConfigResult.isErr) { + return emit( + TaskStateError(message: getIosConfigResult.err.toString()), + ); + } + + // 2. Decrypt iOS keys + emit(DeployIosStatedecryptIosKeys()); + final decryptIosKeyResult = await _decryptKeysUsecase( + DecryptKeysParameters( + encryptedFilePath: getIosConfigResult.ok!.encryptedKeyFile.path, + passphrase: + ctx.environment[AppConstants.iosKeysSecretPassphrase].toString(), + ), + ); + if (decryptIosKeyResult.isErr) { + return emit(TaskStateError(message: decryptIosKeyResult.err.toString())); + } + + // 5. Deploy to AppStore Connect + emit(DeployIosStateDeployToAppStoreConnect()); + final deployResult = await _fastlane.deployAppStore( + FastlaneDeployPilotParameters( + appStoreConnectConfig: AppStoreConnectConfig.fromConfig( + getIosConfigResult.ok!, + ), + developerAppId: getIosConfigResult.ok!.developerAppId, + developerAppIdentifier: getIosConfigResult.ok!.developerAppIdentifier, + ipaPath: _findFirstIpaFilePath('build/ios/ipa') ?? '', + ), + ); + if (deployResult.isErr) { + return emit(TaskStateError(message: deployResult.err.toString())); + } + } + + String? _findFirstIpaFilePath(String folderPath) { + final Directory directory = Directory(folderPath); + + if (!directory.existsSync()) { + logger.detail('No IPA file found in $folderPath'); + return null; + } + + final List<FileSystemEntity> files = directory.listSync(); + + for (final FileSystemEntity file in files) { + if (file is File && file.path.endsWith('.ipa')) { + logger.detail('Found IPA file: ${file.path}'); + return file.path; + } + } + + logger.detail('No IPA file found in $folderPath'); + return null; + } +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/deploy_app_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/deploy_app_command.dart new file mode 100644 index 00000000..e30c05e8 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/deploy_app_command.dart @@ -0,0 +1,35 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:args/command_runner.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/deploy_app/commands/deploy_android/deploy_android_command.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_command.dart'; + +class DeployAppCommand extends Command<int> { + DeployAppCommand() { + addSubcommand(DeployAndroidCommand()); + addSubcommand(DeployIosCommand()); + } + + @override + String get description => 'Deploy the app'; + + @override + String get name => 'deploy'; + + @override + List<String> get aliases => ['d']; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_command.dart new file mode 100644 index 00000000..a7be2ab7 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_command.dart @@ -0,0 +1,39 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:wyatt_continuous_deployment/src/core/commands/init_project/init_project_task.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart'; +import 'package:wyatt_continuous_deployment/src/core/enums/flag.dart'; + +class InitProjectCommand extends TaskCommand { + InitProjectCommand() : super(task: InitProjectTask()) { + argParser.addFlag( + Flag.force.name, + abbr: Flag.force.abbr, + help: 'Force the initialization', + negatable: false, + ); + } + + @override + String get description => 'Initialize the project'; + + @override + String get name => 'init'; + + @override + List<String> get aliases => ['i', 'initialize']; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_state.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_state.dart new file mode 100644 index 00000000..f80d2464 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_state.dart @@ -0,0 +1,28 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +part of 'init_project_task.dart'; + +class InitProjectState extends TaskStateRunning { + const InitProjectState({required this.path, required this.force}); + + final String path; + final bool force; + + @override + String get description => + 'Writing config file to `$path`${force ? ' (forced)' : ''}...'; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_task.dart new file mode 100644 index 00000000..eb37bbc8 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_task.dart @@ -0,0 +1,85 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'dart:async'; + +import 'package:wyatt_continuous_deployment/src/core/enums/flag.dart'; +import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; +import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/config/init_config_parameters.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/config/init_config_usecase.dart'; + +part 'init_project_state.dart'; + +class InitProjectTask extends TaskCubit { + factory InitProjectTask() => InitProjectTask._( + initConfigUsecase: getIt(), + checkToolsUsecase: getIt(), + checkConfigFileUsecase: getIt(), + ); + + InitProjectTask._({ + required InitConfigUsecase initConfigUsecase, + required super.checkToolsUsecase, + required super.checkConfigFileUsecase, + }) : _initConfigUsecase = initConfigUsecase; + + final InitConfigUsecase _initConfigUsecase; + + @override + bool get checks => false; // No need to check config file (not created yet) + + @override + Future<void> onRun(Context ctx) async { + // 1. Init config + emit( + InitProjectState( + path: ctx.configFile.path, + force: ctx.flags.contains(Flag.force), + ), + ); + final initResult = await _initConfigUsecase( + InitConfigParameters( + path: ctx.configFile.path, + force: ctx.flags.contains(Flag.force), + ), + ); + + if (initResult.isErr) { + return emit(TaskStateError.fromRes(initResult)); + } + + // 2. Check tools + emit(TaskStateCheckTools()); + + final checkToolsResult = await checkToolsUsecase(null); + if (checkToolsResult.isErr) { + return emit(TaskStateError.fromRes(checkToolsResult)); + } + + // 3. Check config file + emit(TaskStateCheckConfigFile()); + + final checkConfigFileResult = + await checkConfigFileUsecase(ctx.configFile.path); + if (checkConfigFileResult.isErr) { + return emit(TaskStateError.fromRes(checkConfigFileResult)); + } + + return emit(const TaskStateSucceed(message: 'Project initialized')); + } +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/task_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/task_command.dart new file mode 100644 index 00000000..4504d9ae --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/task_command.dart @@ -0,0 +1,80 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'dart:async'; +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:args/command_runner.dart'; +import 'package:meta/meta.dart'; +import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart'; +import 'package:wyatt_continuous_deployment/src/core/enums/flag.dart'; +import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart'; + +abstract class TaskCommand extends Command<int> { + TaskCommand({ + required this.task, + }); + + final TaskCubit task; + + /// [ArgResults] used for testing purposes only. + @visibleForTesting + ArgResults? testArgResults; + + /// [ArgResults] for the current command. + ArgResults get results => testArgResults ?? argResults!; + + /// Context object for the current command. + Context get context => Context( + currentDirectory: Directory.current, + configFile: () { + // Check if config flag is set. + final wyattPath = globalResults![AppConstants.configArg] as String?; + + final wyattFile = File(wyattPath ?? AppConstants.configFileName); + + return wyattFile; + }.call(), + flags: Flag.fromArgs(results: results, globalResults: globalResults!), + environment: () { + final env = <String, String>{}..addAll(Platform.environment); + + // Load environment variables from the dot file. + final dotFile = File('.env'); + if (dotFile.existsSync()) { + final dotFileContent = dotFile.readAsStringSync(); + final lines = dotFileContent.split('\n'); + for (final line in lines) { + final parts = line.split('='); + if (parts.length == 2) { + env[parts[0].trim()] = parts[1].trim(); + } + } + } + + return env; + }.call(), + ); + + @override + FutureOr<int>? run() async { + await task.run(context); + await task.awaitForFinished(); + return task.status; + } +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/constants/app_constants.dart b/packages/wyatt_continuous_deployment/lib/src/core/constants/app_constants.dart index 350c2708..bb26b700 100644 --- a/packages/wyatt_continuous_deployment/lib/src/core/constants/app_constants.dart +++ b/packages/wyatt_continuous_deployment/lib/src/core/constants/app_constants.dart @@ -15,6 +15,9 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. abstract class AppConstants { + static const packageName = 'studio_cli'; + static const executableName = 'wyatt'; + static const configFileName = 'wyatt.yaml'; static const requiredTools = ['fastlane', 'flutter', 'unzip', 'gpg']; @@ -27,4 +30,8 @@ abstract class AppConstants { static const iosFolder = 'ios'; static const iosKeysSecretPassphrase = 'IOS_KEYS_SECRET_PASSPHRASE'; static const iosConfigKey = 'ios'; + + static const configArg = 'config'; + static const verboseArg = 'verbose'; + static const versionArg = 'version'; } diff --git a/packages/wyatt_continuous_deployment/lib/src/core/core.dart b/packages/wyatt_continuous_deployment/lib/src/core/core.dart new file mode 100644 index 00000000..4bee53ec --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/core.dart @@ -0,0 +1,17 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +export './commands/commands.dart'; diff --git a/packages/wyatt_continuous_deployment/lib/src/core/enums/flag.dart b/packages/wyatt_continuous_deployment/lib/src/core/enums/flag.dart new file mode 100644 index 00000000..2aa9025c --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/enums/flag.dart @@ -0,0 +1,50 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:args/args.dart'; +import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart'; + +enum Flag { + verbose('v'), + force('f'); + + const Flag(this.abbr); + + final String abbr; + + static List<Flag> fromArgs({ + required ArgResults results, + required ArgResults globalResults, + }) { + final flags = <Flag>[]; + + for (final flag in Flag.values) { + if (results.options.contains(flag.name)) { + logger.detail('Flag: ${flag.name} was parsed in results'); + if (results[flag.name] as bool) { + flags.add(flag); + } + } else if (globalResults.options.contains(flag.name)) { + logger.detail('Flag: ${flag.name} was parsed in globalResults'); + if (globalResults[flag.name] as bool) { + flags.add(flag); + } + } + } + + return flags; + } +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/extensions/object_extension.dart b/packages/wyatt_continuous_deployment/lib/src/core/extensions/object_extension.dart new file mode 100644 index 00000000..252fadcb --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/extensions/object_extension.dart @@ -0,0 +1,23 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +extension ObjectExtension on Object? { + AppException toException() => this is AppException + ? this! as AppException + : ClientException(this?.toString()); +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/task/task_cubit.dart b/packages/wyatt_continuous_deployment/lib/src/core/task/task_cubit.dart new file mode 100644 index 00000000..51a36466 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/task/task_cubit.dart @@ -0,0 +1,90 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; +import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/usecases.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +part 'task_state.dart'; + +abstract class TaskCubit extends Cubit<TaskState> { + TaskCubit({ + required CheckToolsUsecase checkToolsUsecase, + required CheckConfigFileUsecase checkConfigFileUsecase, + }) : _checkToolsUsecase = checkToolsUsecase, + _checkConfigFileUsecase = checkConfigFileUsecase, + super(TaskStateInitial()); + + final CheckToolsUsecase _checkToolsUsecase; + final CheckConfigFileUsecase _checkConfigFileUsecase; + + int get status => switch (state) { + TaskStateFinished(:final exitCode) => exitCode, + _ => 0, + }; + + /// Whether to check the tools before running the task + bool get checks => true; + + CheckToolsUsecase get checkToolsUsecase => _checkToolsUsecase; + CheckConfigFileUsecase get checkConfigFileUsecase => _checkConfigFileUsecase; + + Future<void> awaitForFinished() async { + if (state is TaskStateFinished) { + return; + } + + await for (final state in stream) { + if (state is TaskStateFinished) { + return; + } + } + } + + Future<void> run(Context ctx) async { + if (checks) { + emit(TaskStateCheckTools()); + + final checkToolsResult = await _checkToolsUsecase(null); + if (checkToolsResult.isErr) { + return emit(TaskStateError.fromRes(checkToolsResult)); + } + + emit(TaskStateCheckConfigFile()); + + final checkConfigFileResult = + await _checkConfigFileUsecase(ctx.configFile.path); + if (checkConfigFileResult.isErr) { + return emit(TaskStateError.fromRes(checkConfigFileResult)); + } + } + + // Run the task + await onRun(ctx); + + if (state is! TaskStateError && state is! TaskStateSucceed) { + // If no error was emitted, and no success state was emitted, + // then emit a success state to indicate that the task has finished + return emit(const TaskStateSucceed()); + } + } + + Future<void> onRun(Context ctx); +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/task/task_state.dart b/packages/wyatt_continuous_deployment/lib/src/core/task/task_state.dart new file mode 100644 index 00000000..aea643bb --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/task/task_state.dart @@ -0,0 +1,85 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +part of 'task_cubit.dart'; + +abstract class TaskState { + const TaskState(); + + String get description; +} + +class TaskStateInitial extends TaskState { + @override + String get description => ''; +} + +class TaskStateCheckTools extends TaskState { + @override + String get description => 'Check that needed tools are installed'; +} + +class TaskStateCheckConfigFile extends TaskState { + @override + String get description => 'Check that config file exists'; +} + +class TaskStateRunning extends TaskState { + const TaskStateRunning(); + + @override + String get description => ''; +} + +abstract class TaskStateFinished extends TaskState { + const TaskStateFinished({ + this.exitCode = 0, + }); + + final int exitCode; +} + +class TaskStateError extends TaskStateFinished { + const TaskStateError({required String? message, super.exitCode = 1}) + : _message = message ?? 'Unknown error'; + + factory TaskStateError.unknown() => const TaskStateError(message: null); + + factory TaskStateError.fromException(AppException e, {int? exitCode}) => + TaskStateError(message: e.message, exitCode: exitCode ?? 1); + + factory TaskStateError.fromRes( + Result<dynamic, AppException> res, { + int? exitCode, + }) => + TaskStateError(message: res.err?.message, exitCode: exitCode ?? 1); + + final String _message; + + @override + String get description => + 'An error occured${_message.isNotEmpty ? ': $_message' : ''}'; +} + +class TaskStateSucceed extends TaskStateFinished { + const TaskStateSucceed({this.message, super.exitCode}); + + final String? message; + + @override + String get description => + message ?? 'The task ended with success (exit code: $exitCode)'; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/core/utils/logging.dart b/packages/wyatt_continuous_deployment/lib/src/core/utils/logging.dart index b3050073..234ac19e 100644 --- a/packages/wyatt_continuous_deployment/lib/src/core/utils/logging.dart +++ b/packages/wyatt_continuous_deployment/lib/src/core/utils/logging.dart @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. -import 'package:wyatt_continuous_deployment/wyatt_continuous_deployment.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; -final logger = WyattContinuousDeployment.logger; +final logger = getIt<Logger>(); diff --git a/packages/wyatt_continuous_deployment/lib/src/core/utils/task_observer.dart b/packages/wyatt_continuous_deployment/lib/src/core/utils/task_observer.dart new file mode 100644 index 00000000..7b076367 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/core/utils/task_observer.dart @@ -0,0 +1,36 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'package:bloc/bloc.dart'; +import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart'; +import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart'; + +class TaskObserver extends BlocObserver { + @override + void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) { + super.onChange(bloc, change); + + return switch (change.nextState) { + TaskStateError(:final description) when description.isNotEmpty => + logger.err(description), + TaskStateSucceed(:final description) when description.isNotEmpty => + logger.success(description), + final TaskState taskState when taskState.description.isNotEmpty => + logger.detail(taskState.description), + _ => logger.detail(change.nextState.toString()), + }; + } +} diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/entities/context.dart b/packages/wyatt_continuous_deployment/lib/src/domain/entities/context.dart new file mode 100644 index 00000000..526a93da --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/domain/entities/context.dart @@ -0,0 +1,33 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'dart:io'; + +import 'package:wyatt_continuous_deployment/src/core/enums/flag.dart'; + +class Context { + const Context({ + required this.currentDirectory, + required this.configFile, + required this.flags, + this.environment = const {}, + }); + + final Directory currentDirectory; + final File configFile; + final List<Flag> flags; + final Map<String, String> environment; +} diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/entities/flutter.dart b/packages/wyatt_continuous_deployment/lib/src/domain/entities/flutter.dart index 9d912310..d2c59a18 100644 --- a/packages/wyatt_continuous_deployment/lib/src/domain/entities/flutter.dart +++ b/packages/wyatt_continuous_deployment/lib/src/domain/entities/flutter.dart @@ -49,9 +49,9 @@ class Flutter { final FlutterBuildIpaUsecase _flutterBuildIpaUsecase; final FlutterBuildAppBundleUsecase _flutterBuildAppBundleUsecase; - FutureOrResult<void> clean() => _flutterCleanUsecase(); - FutureOrResult<void> getDependencies() => _flutterPubGetUsecase(); - FutureOrResult<void> createXcArchive() => _flutterBuildXcarchiveUsecase(); + FutureOrResult<void> clean() => _flutterCleanUsecase(null); + FutureOrResult<void> getDependencies() => _flutterPubGetUsecase(null); + FutureOrResult<void> createXcArchive() => _flutterBuildXcarchiveUsecase(null); FutureOrResult<void> buildIpa(List<String> args) => _flutterBuildIpaUsecase(args); FutureOrResult<void> buildAppBundle(List<String> args) => diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_android_config_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_android_config_usecase.dart index 688824c1..28ab950c 100644 --- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_android_config_usecase.dart +++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_android_config_usecase.dart @@ -21,6 +21,7 @@ import 'package:checked_yaml/checked_yaml.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; import 'package:wyatt_continuous_deployment/src/domain/entities/config/android_config.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart'; import 'package:yaml/yaml.dart'; @usecase @@ -28,8 +29,17 @@ class GetAndroidConfigUsecase extends AsyncUseCase<String, AndroidConfig> { const GetAndroidConfigUsecase() : super(); @override - FutureOr<Res<AndroidConfig>> execute(String params) => unsafe(() { - final file = File(params); + FutureOr<void> onStart(String? params) { + if (params == null) { + throw const ClientException( + 'GetAndroidConfigUsecase: You must provide path', + ); + } + } + + @override + FutureOrResult<AndroidConfig> execute(String? params) => unsafeAsync(() { + final file = File(params!); final content = file.readAsStringSync(); return checkedYamlDecode( diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_ios_config_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_ios_config_usecase.dart index c7d0766b..baed66b9 100644 --- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_ios_config_usecase.dart +++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_ios_config_usecase.dart @@ -21,6 +21,7 @@ import 'package:checked_yaml/checked_yaml.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; import 'package:wyatt_continuous_deployment/src/domain/entities/config/ios_config.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart'; import 'package:yaml/yaml.dart'; @usecase @@ -28,8 +29,15 @@ class GetIosConfigUsecase extends AsyncUseCase<String, IosConfig> { const GetIosConfigUsecase() : super(); @override - FutureOr<Res<IosConfig>> execute(String params) => unsafe(() { - final file = File(params); + FutureOr<void> onStart(String? params) { + if (params == null) { + throw const ClientException('GetIosConfigUsecase: You must provide path'); + } + } + + @override + FutureOrResult<IosConfig> execute(String? params) => unsafeAsync(() { + final file = File(params!); final content = file.readAsStringSync(); return checkedYamlDecode( diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/init_config_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/init_config_usecase.dart index 87ba935e..d4a751fd 100644 --- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/init_config_usecase.dart +++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/init_config_usecase.dart @@ -24,6 +24,7 @@ import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart'; import 'package:wyatt_continuous_deployment/src/domain/entities/config/android_config.dart'; import 'package:wyatt_continuous_deployment/src/domain/entities/config/init_config_parameters.dart'; import 'package:wyatt_continuous_deployment/src/domain/entities/config/ios_config.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart'; import 'package:yaml_edit/yaml_edit.dart'; @usecase @@ -31,8 +32,18 @@ class InitConfigUsecase extends AsyncUseCase<InitConfigParameters, void> { const InitConfigUsecase() : super(); @override - FutureOr<Res<void>> execute(InitConfigParameters params) => unsafe(() async { - final file = File(params.path); + FutureOr<void> onStart(InitConfigParameters? params) { + if (params == null) { + throw const ClientException( + 'InitConfigUsecase: You must provide parameter', + ); + } + } + + @override + FutureOrResult<void> execute(InitConfigParameters? params) => + unsafeAsync(() async { + final file = File(params!.path); if (!file.existsSync()) { logger.detail('Creating config file at `${params.path}`'); file.createSync(); diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart index c96d667e..96b73b61 100644 --- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart +++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart @@ -21,6 +21,7 @@ import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart'; import 'package:wyatt_continuous_deployment/src/domain/entities/decrypt_keys_parameters.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart'; @usecase class DecryptKeysUsecase extends AsyncUseCase<DecryptKeysParameters, void> { @@ -29,21 +30,28 @@ class DecryptKeysUsecase extends AsyncUseCase<DecryptKeysParameters, void> { @override FutureOr<void> onStart(DecryptKeysParameters? params) { if (params == null) { - throw const ClientException('DecryptKeysParameters is null'); + throw const ClientException( + 'DecryptKeysUsecase: DecryptKeysParameters is null', + ); } if (params.encryptedFilePath.isEmpty) { - throw const ClientException('Encrypted file path is empty'); + throw const ClientException( + 'DecryptKeysUsecase: Encrypted file path is empty', + ); } if (!params.encryptedFilePath.endsWith('.gpg')) { - throw const ClientException('Enncrypted file is not a .gpg file'); + throw const ClientException( + 'DecryptKeysUsecase: Enncrypted file is not a .gpg file', + ); } } @override - FutureOr<Res<void>> execute(DecryptKeysParameters params) => unsafe(() async { + FutureOrResult<void> execute(DecryptKeysParameters? params) => + unsafeAsync(() async { // Define gpg command final gpgFilePath = - params.encryptedFilePath.replaceAll(RegExp(r'\.gpg$'), ''); + params!.encryptedFilePath.replaceAll(RegExp(r'\.gpg$'), ''); final outputGpgFilePath = gpgFilePath.endsWith('.zip') ? gpgFilePath : '$gpgFilePath.zip'; diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/find_last_ipa_file_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/find_last_ipa_file_usecase.dart index 8e19fdc4..0634e0ef 100644 --- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/find_last_ipa_file_usecase.dart +++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/find_last_ipa_file_usecase.dart @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. +import 'dart:async'; import 'dart:io'; import 'package:wyatt_architecture/wyatt_architecture.dart'; @@ -26,8 +27,17 @@ class FindLastIpaFileUsecase extends AsyncUseCase<String, String> { const FindLastIpaFileUsecase() : super(); @override - FutureOrResult<String> execute(String params) { - final Directory directory = Directory(params); + FutureOr<void> onStart(String? params) { + if (params == null) { + throw const ClientException( + 'FindLastIpaFileUsecase: You must provide a path', + ); + } + } + + @override + FutureOrResult<String> execute(String? params) { + final Directory directory = Directory(params!); if (!directory.existsSync()) { logger.detail('No IPA file found in $params'); diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/process_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/process_usecase.dart index 2632e8bb..99c8fc89 100644 --- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/process_usecase.dart +++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/process_usecase.dart @@ -19,6 +19,8 @@ import 'dart:io'; import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; abstract class ProcessUsecase<T> extends AsyncUseCase<T, void> { const ProcessUsecase(); @@ -36,9 +38,16 @@ abstract class ProcessUsecase<T> extends AsyncUseCase<T, void> { String get onErrorMessage; @override - FutureOr<Res<void>> execute(T params) async { + FutureOr<void> onStart(T? params) { + if (params == null) { + throw const ClientException('ProcessUsecase: You must provide a param'); + } + } + + @override + FutureOrResult<void> execute(T? params) async { final progress = logger.progress(onStartedMessage); - final processResult = await run(params); + final processResult = await run(params as T); if (processResult != null) { if (processResult.exitCode != 0) { progress.fail(onErrorMessage); @@ -49,7 +58,7 @@ abstract class ProcessUsecase<T> extends AsyncUseCase<T, void> { } progress.complete(onCompletedMessage); logger.detail(processResult?.stdout.toString().trim()); - return Res.ok(null); + return const Ok(null); } /// Run the process with the given parameters. @@ -72,7 +81,7 @@ abstract class ProcessNoParamsUsecase extends NoParamsAsyncUseCase<void> { String get onErrorMessage; @override - FutureOr<Res<void>> execute() async { + FutureOrResult<void> execute(void params) async { final progress = logger.progress(onStartedMessage); final processResult = await run(); if (processResult != null) { @@ -85,7 +94,7 @@ abstract class ProcessNoParamsUsecase extends NoParamsAsyncUseCase<void> { } progress.complete(onCompletedMessage); logger.detail(processResult?.stdout.toString().trim()); - return Res.ok(null); + return const Ok(null); } /// Run the process with no parameters. diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_config_file_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_config_file_usecase.dart index c6d11b54..ad317dfe 100644 --- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_config_file_usecase.dart +++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_config_file_usecase.dart @@ -14,18 +14,29 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. +import 'dart:async'; import 'dart:io'; import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart'; @usecase class CheckConfigFileUsecase extends AsyncUseCase<String, void> { const CheckConfigFileUsecase() : super(); @override - FutureOrResult<void> execute(String params) => unsafe(() { - final configFile = File(params); + FutureOr<void> onStart(String? params) { + if (params == null) { + throw const ClientException( + 'CheckConfigFileUsecase: You must provide path', + ); + } + } + + @override + FutureOrResult<void> execute(String? params) => unsafeAsync(() { + final configFile = File(params!); if (!configFile.existsSync()) { throw ClientException( 'CheckConfigFileUsecase: Cannot find $params', diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_tools_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_tools_usecase.dart index b826b2f6..d4ba6dca 100644 --- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_tools_usecase.dart +++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_tools_usecase.dart @@ -20,13 +20,14 @@ import 'dart:io'; import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart'; import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; +import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart'; @usecase class CheckToolsUsecase extends NoParamsAsyncUseCase<void> { const CheckToolsUsecase() : super(); @override - FutureOr<Res<void>> execute() async => unsafe(() async { + FutureOrResult<void> execute(void params) async => unsafeAsync(() async { final missingTools = <String>[]; for (final tool in AppConstants.requiredTools) { final isInstalled = await checkToolInstalled(tool); diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/usecase.dart new file mode 100644 index 00000000..a458817f --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/usecase.dart @@ -0,0 +1,34 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'dart:async'; + +import 'package:wyatt_architecture/wyatt_architecture.dart'; +import 'package:wyatt_continuous_deployment/src/core/extensions/object_extension.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +typedef Res<T> = Result<T, AppException>; + +/// A wrapper around [Result] to make it easier to use with AppException. +FutureOrResult<T> unsafeAsync<T>(FutureOr<T> Function() fn) => + Result.tryCatchAsync( + () async => fn.call(), + (error) => error.toException(), + ); + +abstract class NoParamsAsyncUseCase<T> extends AsyncUseCase<void, T> { + const NoParamsAsyncUseCase(); +} diff --git a/packages/wyatt_continuous_deployment/lib/src/version.dart b/packages/wyatt_continuous_deployment/lib/src/version.dart new file mode 100644 index 00000000..af0834a9 --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/version.dart @@ -0,0 +1 @@ +const packageVersion = '0.0.1'; diff --git a/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment.dart b/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment.dart index dc040b78..8ac51f30 100644 --- a/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment.dart +++ b/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment.dart @@ -14,23 +14,5 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. -import 'package:get_it/get_it.dart'; -import 'package:mason_logger/mason_logger.dart'; -import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'; - -export 'domain/domain.dart'; - -abstract class WyattContinuousDeployment { - /// The logger used by the package - static Logger logger = Logger(); - - /// The service locator used by the package - static GetIt getIt = GetIt.instance; - - /// Configure the dependencies - /// This method should be called before WyattContinuousDeployment.getIt - /// to ensure that all dependencies are registered - static Future<void> configureDependencies() async { - await Injectable.configureDependencies(); - } -} +// TODO(mleon): export all layers +export ''; diff --git a/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment_command_runner.dart b/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment_command_runner.dart new file mode 100644 index 00000000..51d6eb9b --- /dev/null +++ b/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment_command_runner.dart @@ -0,0 +1,110 @@ +// Copyright (C) 2024 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:args/command_runner.dart'; +import 'package:cli_completion/cli_completion.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:wyatt_continuous_deployment/src/core/commands/commands.dart'; +import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart'; +import 'package:wyatt_continuous_deployment/src/core/enums/flag.dart'; +import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart'; +import 'package:wyatt_continuous_deployment/src/version.dart'; + +class WyattContinuousDeploymentCommandRunner + extends CompletionCommandRunner<int> { + WyattContinuousDeploymentCommandRunner() + : super( + AppConstants.executableName, + '🍺 Where all your dreams come to life', + ) { + argParser.addFlags(); + addCommand(InitProjectCommand()); + addCommand(BuildAppCommand()); + addCommand(DeployAppCommand()); + addCommand(BuildAndDeployAppCommand()); + } + + @override + Future<int> run(Iterable<String> args) async { + try { + final results = parse(args); + + logger.level = results['verbose'] == true ? Level.verbose : Level.info; + + return await runCommand(results) ?? ExitCode.success.code; + } on FormatException catch (e) { + logger + ..err(e.message) + ..info('') + ..info(usage); + return ExitCode.usage.code; + } on UsageException catch (e) { + logger + ..err(e.message) + ..info('') + ..info(e.usage); + return ExitCode.usage.code; + } on ProcessException catch (error) { + logger.err(error.message); + return ExitCode.unavailable.code; + } catch (error) { + logger.err('$error'); + return ExitCode.software.code; + } + } + + @override + Future<int?> runCommand(ArgResults topLevelResults) async { + if (topLevelResults.command?.name == 'completion') { + await super.runCommand(topLevelResults); + return ExitCode.success.code; + } + + int? exitCode = ExitCode.unavailable.code; + if (topLevelResults['version'] == true) { + logger.info(packageVersion); + exitCode = ExitCode.success.code; + } else { + exitCode = await super.runCommand(topLevelResults); + } + return exitCode; + } +} + +extension on ArgParser { + void addFlags() { + addFlag( + AppConstants.versionArg, + negatable: false, + help: 'Print the current version.', + ); + addFlag( + Flag.verbose.name, + abbr: Flag.verbose.abbr, + negatable: false, + help: 'Print verbose output.', + ); + addOption( + AppConstants.configArg, + abbr: 'c', + help: 'Path to the wyatt.yaml file.', + valueHelp: 'path', + ); + } +} diff --git a/packages/wyatt_continuous_deployment/lib/wyatt_continuous_deployment.dart b/packages/wyatt_continuous_deployment/lib/wyatt_continuous_deployment.dart index fc963fd9..40bef808 100644 --- a/packages/wyatt_continuous_deployment/lib/wyatt_continuous_deployment.dart +++ b/packages/wyatt_continuous_deployment/lib/wyatt_continuous_deployment.dart @@ -17,4 +17,5 @@ /// Wyatt CD library wyatt_continuous_deployment; -export 'src/wyatt_continuous_deployment.dart'; +export './src/core/core.dart'; +export './src/domain/domain.dart'; diff --git a/packages/wyatt_continuous_deployment/pubspec.yaml b/packages/wyatt_continuous_deployment/pubspec.yaml index 5398fb8b..189d9466 100644 --- a/packages/wyatt_continuous_deployment/pubspec.yaml +++ b/packages/wyatt_continuous_deployment/pubspec.yaml @@ -9,11 +9,15 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: + args: ^2.4.2 + bloc: ^8.1.3 checked_yaml: ^2.0.3 + cli_completion: ^0.5.0 get_it: ^7.6.7 injectable: ^2.3.2 json_annotation: ^4.8.1 mason_logger: ^0.2.12 + meta: ^1.12.0 wyatt_architecture: hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ version: ^0.2.0+1