chore(wyatt_cd): add wyatt continuous deployment package

This commit is contained in:
2024-02-18 22:49:47 +01:00
parent 59bca9045e
commit 08218f7080
56 changed files with 3002 additions and 0 deletions
@@ -0,0 +1,30 @@
// 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/>.
abstract class AppConstants {
static const configFileName = 'wyatt.yaml';
static const requiredTools = ['fastlane', 'flutter', 'unzip', 'gpg'];
static const androidFolder = 'android';
static const androidBundleBuildPath =
'build/app/outputs/bundle/release/app-release.aab';
static const androidKeysSecretPassphrase = 'ANDROID_KEYS_SECRET_PASSPHRASE';
static const androidConfigKey = 'android';
static const iosFolder = 'ios';
static const iosKeysSecretPassphrase = 'IOS_KEYS_SECRET_PASSPHRASE';
static const iosConfigKey = 'ios';
}
@@ -0,0 +1,96 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// InjectableConfigGenerator
// **************************************************************************
// ignore_for_file: type=lint
// coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:get_it/get_it.dart' as _i1;
import 'package:injectable/injectable.dart' as _i2;
import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart'
as _i21;
import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane.dart'
as _i6;
import 'package:wyatt_continuous_deployment/src/domain/entities/flutter.dart'
as _i12;
import 'package:wyatt_continuous_deployment/src/domain/usecases/config/get_android_config_usecase.dart'
as _i18;
import 'package:wyatt_continuous_deployment/src/domain/usecases/config/get_ios_config_usecase.dart'
as _i19;
import 'package:wyatt_continuous_deployment/src/domain/usecases/config/init_config_usecase.dart'
as _i20;
import 'package:wyatt_continuous_deployment/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart'
as _i5;
import 'package:wyatt_continuous_deployment/src/domain/usecases/fastlane/fastlane_create_keychain_usecase.dart'
as _i7;
import 'package:wyatt_continuous_deployment/src/domain/usecases/fastlane/fastlane_deploy_pilot_usecase.dart'
as _i8;
import 'package:wyatt_continuous_deployment/src/domain/usecases/fastlane/fastlane_deploy_supply_usecase.dart'
as _i9;
import 'package:wyatt_continuous_deployment/src/domain/usecases/fastlane/fastlane_get_certificates_usecase.dart'
as _i10;
import 'package:wyatt_continuous_deployment/src/domain/usecases/find_last_ipa_file_usecase.dart'
as _i11;
import 'package:wyatt_continuous_deployment/src/domain/usecases/flutter/flutter_build_app_bundle_usecase.dart'
as _i13;
import 'package:wyatt_continuous_deployment/src/domain/usecases/flutter/flutter_build_ipa_usecase.dart'
as _i14;
import 'package:wyatt_continuous_deployment/src/domain/usecases/flutter/flutter_build_xcarchive_usecase.dart'
as _i15;
import 'package:wyatt_continuous_deployment/src/domain/usecases/flutter/flutter_clean_usecase.dart'
as _i16;
import 'package:wyatt_continuous_deployment/src/domain/usecases/flutter/flutter_pub_get_usecase.dart'
as _i17;
import 'package:wyatt_continuous_deployment/src/domain/usecases/requirements/check_config_file_usecase.dart'
as _i3;
import 'package:wyatt_continuous_deployment/src/domain/usecases/requirements/check_tools_usecase.dart'
as _i4;
extension GetItInjectableX on _i1.GetIt {
// initializes the registration of main-scope dependencies inside of GetIt
_i1.GetIt init({
String? environment,
_i2.EnvironmentFilter? environmentFilter,
}) {
final gh = _i2.GetItHelper(
this,
environment,
environmentFilter,
);
final registerModule = _$RegisterModule();
gh.singleton<_i3.CheckConfigFileUsecase>(
const _i3.CheckConfigFileUsecase());
gh.singleton<_i4.CheckToolsUsecase>(const _i4.CheckToolsUsecase());
gh.singleton<_i5.DecryptKeysUsecase>(const _i5.DecryptKeysUsecase());
gh.lazySingleton<_i6.Fastlane>(() => registerModule.fastlane);
gh.singleton<_i7.FastlaneCreateKeychainUsecase>(
_i7.FastlaneCreateKeychainUsecase());
gh.singleton<_i8.FastlaneDeployPilotUsecase>(
_i8.FastlaneDeployPilotUsecase());
gh.singleton<_i9.FastlaneDeploySupplyUsecase>(
_i9.FastlaneDeploySupplyUsecase());
gh.singleton<_i10.FastlaneGetCertificateUsecase>(
_i10.FastlaneGetCertificateUsecase());
gh.singleton<_i11.FindLastIpaFileUsecase>(
const _i11.FindLastIpaFileUsecase());
gh.lazySingleton<_i12.Flutter>(() => registerModule.flutter);
gh.singleton<_i13.FlutterBuildAppBundleUsecase>(
const _i13.FlutterBuildAppBundleUsecase());
gh.singleton<_i14.FlutterBuildIpaUsecase>(
const _i14.FlutterBuildIpaUsecase());
gh.singleton<_i15.FlutterBuildXcarchiveUsecase>(
const _i15.FlutterBuildXcarchiveUsecase());
gh.singleton<_i16.FlutterCleanUsecase>(const _i16.FlutterCleanUsecase());
gh.singleton<_i17.FlutterPubGetUsecase>(const _i17.FlutterPubGetUsecase());
gh.singleton<_i18.GetAndroidConfigUsecase>(
const _i18.GetAndroidConfigUsecase());
gh.singleton<_i19.GetIosConfigUsecase>(const _i19.GetIosConfigUsecase());
gh.singleton<_i20.InitConfigUsecase>(const _i20.InitConfigUsecase());
return this;
}
}
class _$RegisterModule extends _i21.RegisterModule {}
@@ -0,0 +1,73 @@
// 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:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'package:wyatt_continuous_deployment/src/core/injection/injectable.config.dart';
import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane.dart';
import 'package:wyatt_continuous_deployment/src/domain/entities/flutter.dart';
final getIt = GetIt.instance;
@InjectableInit(
initializerName: 'init',
preferRelativeImports: false,
asExtension: true,
)
abstract final class Injectable {
static Future<void> configureDependencies() => getIt.init().allReady();
}
@module
abstract class RegisterModule {
@lazySingleton
Flutter get flutter => Flutter();
@lazySingleton
Fastlane get fastlane => Fastlane();
}
class UsecaseInjectable extends Singleton {
const UsecaseInjectable({
super.as,
super.env,
super.dispose,
super.scope,
super.order,
});
}
const usecase = UsecaseInjectable();
class RepositoryInjectable extends Singleton {
const RepositoryInjectable({
super.as,
super.env,
super.dispose,
super.scope,
super.order,
});
}
class DataSourceInjectable extends Singleton {
const DataSourceInjectable({
super.as,
super.env,
super.dispose,
super.scope,
super.order,
});
}
@@ -0,0 +1,19 @@
// 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/wyatt_continuous_deployment.dart';
final logger = WyattContinuousDeployment.logger;
@@ -0,0 +1,18 @@
// 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 'entities/entities.dart';
export 'usecases/usecases.dart';
@@ -0,0 +1,65 @@
// 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:json_annotation/json_annotation.dart';
part 'android_config.g.dart';
@JsonSerializable(
anyMap: true,
fieldRename: FieldRename.snake,
checked: true,
)
class AndroidConfig {
const AndroidConfig({
required this.flutterArgs,
required this.encryptedKeyFileName,
required this.googleServiceAccountFileName,
required this.releaseStatus,
required this.androidFolderPath,
required this.track,
this.packageName,
});
factory AndroidConfig.fromJson(Map<dynamic, dynamic> json) =>
_$AndroidConfigFromJson(json);
Map<String, dynamic> toJson() => _$AndroidConfigToJson(this);
static const defaultValues = AndroidConfig(
flutterArgs: '',
encryptedKeyFileName: 'android_keys.zip.gpg',
googleServiceAccountFileName: 'service_account_key.json',
releaseStatus: 'draft',
track: 'internal',
androidFolderPath: 'android',
);
final String? packageName;
final String flutterArgs;
final String encryptedKeyFileName;
final String googleServiceAccountFileName;
final String androidFolderPath;
final String releaseStatus;
final String track;
File get encryptedKeyFile => File('$androidFolderPath/$encryptedKeyFileName');
File get googleServiceAccountFile =>
File('$androidFolderPath/$googleServiceAccountFileName');
Directory get androidDirectory => Directory(androidFolderPath);
}
@@ -0,0 +1,46 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'android_config.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AndroidConfig _$AndroidConfigFromJson(Map json) => $checkedCreate(
'AndroidConfig',
json,
($checkedConvert) {
final val = AndroidConfig(
flutterArgs: $checkedConvert('flutter_args', (v) => v as String),
encryptedKeyFileName:
$checkedConvert('encrypted_key_file_name', (v) => v as String),
googleServiceAccountFileName: $checkedConvert(
'google_service_account_file_name', (v) => v as String),
releaseStatus: $checkedConvert('release_status', (v) => v as String),
androidFolderPath:
$checkedConvert('android_folder_path', (v) => v as String),
track: $checkedConvert('track', (v) => v as String),
packageName: $checkedConvert('package_name', (v) => v as String?),
);
return val;
},
fieldKeyMap: const {
'flutterArgs': 'flutter_args',
'encryptedKeyFileName': 'encrypted_key_file_name',
'googleServiceAccountFileName': 'google_service_account_file_name',
'releaseStatus': 'release_status',
'androidFolderPath': 'android_folder_path',
'packageName': 'package_name'
},
);
Map<String, dynamic> _$AndroidConfigToJson(AndroidConfig instance) =>
<String, dynamic>{
'package_name': instance.packageName,
'flutter_args': instance.flutterArgs,
'encrypted_key_file_name': instance.encryptedKeyFileName,
'google_service_account_file_name': instance.googleServiceAccountFileName,
'android_folder_path': instance.androidFolderPath,
'release_status': instance.releaseStatus,
'track': instance.track,
};
@@ -0,0 +1,65 @@
// 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/domain/entities/config/ios_config.dart';
class AppStoreConnectConfig {
AppStoreConnectConfig({
required this.keyId,
required this.issuerId,
required this.keyFilePath,
required this.isKeyContentBase64,
required this.duration,
required this.inHouse,
});
factory AppStoreConnectConfig.fromJson(Map<String, dynamic> json) =>
AppStoreConnectConfig(
keyId: json['apple_key_id']?.toString(),
issuerId: json['apple_issuer_id']?.toString(),
keyFilePath: json['apple_key_content']?.toString(),
isKeyContentBase64: false,
duration: 500,
inHouse: false,
);
factory AppStoreConnectConfig.fromConfig(IosConfig config) =>
AppStoreConnectConfig(
keyId: config.appleKeyId,
issuerId: config.appleIssuerId,
keyFilePath:
'${config.iosFolderPath}/${config.appleApiPrivateKeyFileName}',
isKeyContentBase64: false,
duration: 500,
inHouse: false,
);
final String? keyId;
final String? issuerId;
final String? keyFilePath;
final bool? isKeyContentBase64;
final int? duration;
final bool? inHouse;
Map<String, dynamic> toJson(String key) => {
'key_id': keyId,
'issuer_id': issuerId,
'key': key,
'is_key_content_base64': isKeyContentBase64,
'duration': duration,
'in_house': inHouse,
};
}
@@ -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/>.
class InitConfigParameters {
const InitConfigParameters({
required this.path,
required this.force,
});
final String path;
final bool force;
@override
String toString() => 'InitConfigParameters(path: $path, force: $force)';
}
@@ -0,0 +1,94 @@
// 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:json_annotation/json_annotation.dart';
part 'ios_config.g.dart';
@JsonSerializable(
anyMap: true,
fieldRename: FieldRename.snake,
checked: true,
)
class IosConfig {
const IosConfig({
required this.appleApiPrivateKeyFileName,
required this.gitCertsPrivateKeyFileName,
required this.iosFolderPath,
required this.encryptedKeyFileName,
required this.developerAppIdentifier,
required this.teamName,
required this.appleIssuerId,
required this.gitUrl,
required this.gitPassphrase,
required this.developerAppId,
required this.userName,
required this.appleKeyId,
required this.teamId,
required this.provisionningProfiles,
required this.flutterArgs,
required this.iosKeysFileName,
});
factory IosConfig.fromJson(Map<dynamic, dynamic> json) =>
_$IosConfigFromJson(json);
Map<String, dynamic> toJson() => _$IosConfigToJson(this);
static const defaultValues = IosConfig(
iosFolderPath: 'ios',
encryptedKeyFileName: 'ios_keys.zip.gpg',
iosKeysFileName: 'ios_keys.json',
appleApiPrivateKeyFileName: 'key.p8',
gitCertsPrivateKeyFileName: '',
developerAppIdentifier: 'git-key',
teamName: '',
appleIssuerId: '',
gitUrl: '',
gitPassphrase: 'match-password',
developerAppId: '',
userName: '',
appleKeyId: '',
teamId: '',
provisionningProfiles: {},
flutterArgs: '',
);
final String flutterArgs;
final String iosFolderPath;
final String encryptedKeyFileName;
final String appleApiPrivateKeyFileName;
final String gitCertsPrivateKeyFileName;
final String iosKeysFileName;
final String developerAppId;
final String developerAppIdentifier;
final String teamId;
final String teamName;
final String userName;
final String appleIssuerId;
final String appleKeyId;
final String gitPassphrase;
final String gitUrl;
final Map<String, String> provisionningProfiles;
File get encryptedKeyFile => File('$iosFolderPath/$encryptedKeyFileName');
Directory get iosDirectory => Directory(iosFolderPath);
}
@@ -0,0 +1,77 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'ios_config.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
IosConfig _$IosConfigFromJson(Map json) => $checkedCreate(
'IosConfig',
json,
($checkedConvert) {
final val = IosConfig(
appleApiPrivateKeyFileName: $checkedConvert(
'apple_api_private_key_file_name', (v) => v as String),
gitCertsPrivateKeyFileName: $checkedConvert(
'git_certs_private_key_file_name', (v) => v as String),
iosFolderPath: $checkedConvert('ios_folder_path', (v) => v as String),
encryptedKeyFileName:
$checkedConvert('encrypted_key_file_name', (v) => v as String),
developerAppIdentifier:
$checkedConvert('developer_app_identifier', (v) => v as String),
teamName: $checkedConvert('team_name', (v) => v as String),
appleIssuerId: $checkedConvert('apple_issuer_id', (v) => v as String),
gitUrl: $checkedConvert('git_url', (v) => v as String),
gitPassphrase: $checkedConvert('git_passphrase', (v) => v as String),
developerAppId:
$checkedConvert('developer_app_id', (v) => v as String),
userName: $checkedConvert('user_name', (v) => v as String),
appleKeyId: $checkedConvert('apple_key_id', (v) => v as String),
teamId: $checkedConvert('team_id', (v) => v as String),
provisionningProfiles: $checkedConvert('provisionning_profiles',
(v) => Map<String, String>.from(v as Map)),
flutterArgs: $checkedConvert('flutter_args', (v) => v as String),
iosKeysFileName:
$checkedConvert('ios_keys_file_name', (v) => v as String),
);
return val;
},
fieldKeyMap: const {
'appleApiPrivateKeyFileName': 'apple_api_private_key_file_name',
'gitCertsPrivateKeyFileName': 'git_certs_private_key_file_name',
'iosFolderPath': 'ios_folder_path',
'encryptedKeyFileName': 'encrypted_key_file_name',
'developerAppIdentifier': 'developer_app_identifier',
'teamName': 'team_name',
'appleIssuerId': 'apple_issuer_id',
'gitUrl': 'git_url',
'gitPassphrase': 'git_passphrase',
'developerAppId': 'developer_app_id',
'userName': 'user_name',
'appleKeyId': 'apple_key_id',
'teamId': 'team_id',
'provisionningProfiles': 'provisionning_profiles',
'flutterArgs': 'flutter_args',
'iosKeysFileName': 'ios_keys_file_name'
},
);
Map<String, dynamic> _$IosConfigToJson(IosConfig instance) => <String, dynamic>{
'flutter_args': instance.flutterArgs,
'ios_folder_path': instance.iosFolderPath,
'encrypted_key_file_name': instance.encryptedKeyFileName,
'apple_api_private_key_file_name': instance.appleApiPrivateKeyFileName,
'git_certs_private_key_file_name': instance.gitCertsPrivateKeyFileName,
'ios_keys_file_name': instance.iosKeysFileName,
'developer_app_id': instance.developerAppId,
'developer_app_identifier': instance.developerAppIdentifier,
'team_id': instance.teamId,
'team_name': instance.teamName,
'user_name': instance.userName,
'apple_issuer_id': instance.appleIssuerId,
'apple_key_id': instance.appleKeyId,
'git_passphrase': instance.gitPassphrase,
'git_url': instance.gitUrl,
'provisionning_profiles': instance.provisionningProfiles,
};
@@ -0,0 +1,25 @@
// 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/>.
class DecryptKeysParameters {
const DecryptKeysParameters({
required this.encryptedFilePath,
required this.passphrase,
});
final String encryptedFilePath;
final String passphrase;
}
@@ -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/>.
export 'config/android_config.dart';
export 'config/appstore_connect_config.dart';
export 'config/init_config_parameters.dart';
export 'config/ios_config.dart';
export 'decrypt_keys_parameters.dart';
export 'fastlane.dart';
export 'fastlane/fastlane_create_keychain_parameters.dart';
export 'fastlane/fastlane_deploy_pilot_parameters.dart';
export 'fastlane/fastlane_deploy_supply_parameters.dart';
export 'fastlane/fastlane_get_certificates_parameters.dart';
export 'flutter.dart';
@@ -0,0 +1,68 @@
// 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';
import 'package:wyatt_continuous_deployment/src/core/injection/injectable.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_deploy_pilot_parameters.dart';
import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_deploy_supply_parameters.dart';
import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_get_certificates_parameters.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/fastlane/fastlane_create_keychain_usecase.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/fastlane/fastlane_deploy_pilot_usecase.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/fastlane/fastlane_deploy_supply_usecase.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/fastlane/fastlane_get_certificates_usecase.dart';
class Fastlane {
factory Fastlane() => Fastlane._(
createKeychainUsecase: getIt(),
deployPilotUsecase: getIt(),
deploySupplyUsecase: getIt(),
getCertificateUsecase: getIt(),
);
Fastlane._({
required FastlaneCreateKeychainUsecase createKeychainUsecase,
required FastlaneDeployPilotUsecase deployPilotUsecase,
required FastlaneDeploySupplyUsecase deploySupplyUsecase,
required FastlaneGetCertificateUsecase getCertificateUsecase,
}) : _createKeychainUsecase = createKeychainUsecase,
_deployPilotUsecase = deployPilotUsecase,
_deploySupplyUsecase = deploySupplyUsecase,
_getCertificateUsecase = getCertificateUsecase;
final FastlaneCreateKeychainUsecase _createKeychainUsecase;
final FastlaneDeployPilotUsecase _deployPilotUsecase;
final FastlaneDeploySupplyUsecase _deploySupplyUsecase;
final FastlaneGetCertificateUsecase _getCertificateUsecase;
FutureOrResult<void> createKeychain(
FastlaneCreateKeychainParameters params,
) =>
_createKeychainUsecase(params);
FutureOrResult<void> deployAppStore(FastlaneDeployPilotParameters params) =>
_deployPilotUsecase(params);
FutureOrResult<void> deployGooglePlay(
FastlaneDeploySupplyParameters params,
) =>
_deploySupplyUsecase(params);
FutureOrResult<void> getCertificates(
FastlaneGetCertificatesParameters params,
) =>
_getCertificateUsecase(params);
}
@@ -0,0 +1,38 @@
// 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/>.
class FastlaneCreateKeychainParameters {
const FastlaneCreateKeychainParameters({
required this.name,
required this.password,
required this.gitPassphrase,
this.unlock = false,
this.timeout = 0,
});
final String name;
final String password;
final String gitPassphrase;
final bool unlock;
final int timeout;
List<String> toList() => [
'name:$name',
'password:$password',
'unlock:$unlock',
'timeout:$timeout',
];
}
@@ -0,0 +1,49 @@
// 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/domain/entities/config/appstore_connect_config.dart';
class FastlaneDeployPilotParameters {
const FastlaneDeployPilotParameters({
required this.appStoreConnectConfig,
required this.developerAppId,
required this.developerAppIdentifier,
required this.ipaPath,
this.skipWaitingForBuildProcessing = true,
this.skipSubmission = true,
this.distributeExternal = false,
this.notifyExternalTesters = false,
});
final AppStoreConnectConfig appStoreConnectConfig;
final String developerAppId;
final String developerAppIdentifier;
final String ipaPath;
final bool skipWaitingForBuildProcessing;
final bool skipSubmission;
final bool distributeExternal;
final bool notifyExternalTesters;
List<String> toList() => [
'apple_id:$developerAppId',
'app_identifier:$developerAppIdentifier',
'ipa:$ipaPath',
'skip_waiting_for_build_processing:$skipWaitingForBuildProcessing',
'skip_submission:$skipSubmission',
'distribute_external:$distributeExternal',
'notify_external_testers:$notifyExternalTesters',
];
}
@@ -0,0 +1,48 @@
// 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/>.
class FastlaneDeploySupplyParameters {
const FastlaneDeploySupplyParameters({
required this.track,
required this.serviceAccountPath,
required this.appBundlePath,
required this.releaseStatus,
required this.packageName,
this.skipUploadMetadata = true,
this.skipUploadImages = true,
this.skipUploadScreenshots = true,
});
final String track;
final String packageName;
final String serviceAccountPath;
final String appBundlePath;
final String releaseStatus;
final bool skipUploadMetadata;
final bool skipUploadImages;
final bool skipUploadScreenshots;
List<String> toList() => [
'track:$track',
'json_key:$serviceAccountPath',
'aab:$appBundlePath',
'release_status:$releaseStatus',
'package_name:$packageName',
'skip_upload_metadata:$skipUploadMetadata',
'skip_upload_images:$skipUploadImages',
'skip_upload_screenshots:$skipUploadScreenshots',
];
}
@@ -0,0 +1,51 @@
// 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/>.
class FastlaneGetCertificatesParameters {
const FastlaneGetCertificatesParameters({
required this.gitPrivateKey,
required this.appIdentifier,
required this.keychainName,
required this.keychainPassword,
required this.userName,
required this.teamId,
required this.teamName,
required this.gitUrl,
required this.gitPassphrase,
});
final String gitPrivateKey;
final String appIdentifier;
final String keychainName;
final String keychainPassword;
final String userName;
final String teamId;
final String teamName;
final String gitUrl;
final String gitPassphrase;
List<String> toList() => [
'app_identifier:$appIdentifier',
'git_private_key:$gitPrivateKey',
'keychain_name:$keychainName',
'keychain_password:$keychainPassword',
'username:$userName',
'team_id:$teamId',
'team_name:$teamName',
'git_url:$gitUrl',
'storage_mode:git',
];
}
@@ -0,0 +1,59 @@
// 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';
import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/flutter/flutter_build_app_bundle_usecase.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/flutter/flutter_build_ipa_usecase.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/flutter/flutter_build_xcarchive_usecase.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/flutter/flutter_clean_usecase.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/flutter/flutter_pub_get_usecase.dart';
class Flutter {
factory Flutter() => Flutter._(
flutterCleanUsecase: getIt(),
flutterPubGetUsecase: getIt(),
flutterBuildXcarchiveUsecase: getIt(),
flutterBuildIpaUsecase: getIt(),
flutterBuildAppBundleUsecase: getIt(),
);
Flutter._({
required FlutterCleanUsecase flutterCleanUsecase,
required FlutterPubGetUsecase flutterPubGetUsecase,
required FlutterBuildXcarchiveUsecase flutterBuildXcarchiveUsecase,
required FlutterBuildIpaUsecase flutterBuildIpaUsecase,
required FlutterBuildAppBundleUsecase flutterBuildAppBundleUsecase,
}) : _flutterCleanUsecase = flutterCleanUsecase,
_flutterPubGetUsecase = flutterPubGetUsecase,
_flutterBuildXcarchiveUsecase = flutterBuildXcarchiveUsecase,
_flutterBuildIpaUsecase = flutterBuildIpaUsecase,
_flutterBuildAppBundleUsecase = flutterBuildAppBundleUsecase;
final FlutterCleanUsecase _flutterCleanUsecase;
final FlutterPubGetUsecase _flutterPubGetUsecase;
final FlutterBuildXcarchiveUsecase _flutterBuildXcarchiveUsecase;
final FlutterBuildIpaUsecase _flutterBuildIpaUsecase;
final FlutterBuildAppBundleUsecase _flutterBuildAppBundleUsecase;
FutureOrResult<void> clean() => _flutterCleanUsecase();
FutureOrResult<void> getDependencies() => _flutterPubGetUsecase();
FutureOrResult<void> createXcArchive() => _flutterBuildXcarchiveUsecase();
FutureOrResult<void> buildIpa(List<String> args) =>
_flutterBuildIpaUsecase(args);
FutureOrResult<void> buildAppBundle(List<String> args) =>
_flutterBuildAppBundleUsecase(args);
}
@@ -0,0 +1,40 @@
// 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: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:yaml/yaml.dart';
@usecase
class GetAndroidConfigUsecase extends AsyncUseCase<String, AndroidConfig> {
const GetAndroidConfigUsecase() : super();
@override
FutureOr<Res<AndroidConfig>> execute(String params) => unsafe(() {
final file = File(params);
final content = file.readAsStringSync();
return checkedYamlDecode(
content,
(json) => AndroidConfig.fromJson(json!['android'] as YamlMap),
);
});
}
@@ -0,0 +1,40 @@
// 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: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:yaml/yaml.dart';
@usecase
class GetIosConfigUsecase extends AsyncUseCase<String, IosConfig> {
const GetIosConfigUsecase() : super();
@override
FutureOr<Res<IosConfig>> execute(String params) => unsafe(() {
final file = File(params);
final content = file.readAsStringSync();
return checkedYamlDecode(
content,
(json) => IosConfig.fromJson(json!['ios'] as YamlMap),
);
});
}
@@ -0,0 +1,72 @@
// 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_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/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:yaml_edit/yaml_edit.dart';
@usecase
class InitConfigUsecase extends AsyncUseCase<InitConfigParameters, void> {
const InitConfigUsecase() : super();
@override
FutureOr<Res<void>> execute(InitConfigParameters params) => unsafe(() async {
final file = File(params.path);
if (!file.existsSync()) {
logger.detail('Creating config file at `${params.path}`');
file.createSync();
} else {
if (!params.force) {
throw ClientException(
'Config file already exists at `${params.path}`',
);
} else {
logger.detail('Forced to create config file at `${params.path}`');
file
..deleteSync()
..createSync();
}
}
const androidConfig = AndroidConfig.defaultValues;
logger.detail(
'Writing default android config: ${androidConfig.toJson()}',
);
const iosConfig = IosConfig.defaultValues;
logger.detail(
'Writing default ios config: ${iosConfig.toJson()}',
);
final yamlEditor = YamlEditor('')
..update([], {
AppConstants.androidConfigKey: androidConfig.toJson(),
AppConstants.iosConfigKey: iosConfig.toJson(),
});
file.writeAsStringSync(yamlEditor.toString());
});
}
@@ -0,0 +1,95 @@
// 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_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';
@usecase
class DecryptKeysUsecase extends AsyncUseCase<DecryptKeysParameters, void> {
const DecryptKeysUsecase() : super();
@override
FutureOr<void> onStart(DecryptKeysParameters? params) {
if (params == null) {
throw const ClientException('DecryptKeysParameters is null');
}
if (params.encryptedFilePath.isEmpty) {
throw const ClientException('Encrypted file path is empty');
}
if (!params.encryptedFilePath.endsWith('.gpg')) {
throw const ClientException('Enncrypted file is not a .gpg file');
}
}
@override
FutureOr<Res<void>> execute(DecryptKeysParameters params) => unsafe(() async {
// Define gpg command
final gpgFilePath =
params.encryptedFilePath.replaceAll(RegExp(r'\.gpg$'), '');
final outputGpgFilePath =
gpgFilePath.endsWith('.zip') ? gpgFilePath : '$gpgFilePath.zip';
final gpgCommand = 'gpg --quiet --batch --yes --decrypt '
'--passphrase=${params.passphrase} '
'--output $outputGpgFilePath ${params.encryptedFilePath}';
logger.detail(
'''DecryptKeysUsecase: Decrypting the file with the command: ${gpgCommand.replaceAll(params.passphrase, '*' * params.passphrase.length)}''',
);
final progress = logger.progress('Decrypting keys...');
// Verify if the command was executed successfully
final gpgResult = await Process.run('sh', ['-c', gpgCommand]);
logger.detail(gpgResult.stdout.toString());
if (gpgResult.exitCode == 0) {
// Move the extracted files to the current directory
final folder = Directory(outputGpgFilePath.replaceAll('.zip', ''));
progress.update('Unzipping...');
final unzipResult = await Process.run('sh', [
'-c',
'unzip -o $outputGpgFilePath -d ${folder.parent.path} && mv ${folder.path}/* ${folder.parent.path}',
]);
logger.detail(unzipResult.stdout.toString());
if (unzipResult.exitCode != 0) {
progress.fail('Error during the unzip of the file');
throw ClientException(
'DecryptKeysUsecase: Error during the unzip '
'of the file : ${unzipResult.stderr}',
);
}
return progress.complete('Keys decrypted!');
} else {
progress.fail('Error during the decryption of the GPG file');
throw ClientException(
'DecryptKeysUsecase: Error during the decryption '
'of the GPG file : ${gpgResult.stderr}',
);
}
});
}
@@ -0,0 +1,49 @@
// 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/injection/injectable.dart';
import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_create_keychain_parameters.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/process_usecase.dart';
@usecase
class FastlaneCreateKeychainUsecase
extends ProcessUsecase<FastlaneCreateKeychainParameters> {
@override
String get onCompletedMessage => 'Keychain created!';
@override
String get onErrorMessage => 'Error creating keychain!';
@override
String get onStartedMessage => 'Creating keychain...';
@override
Future<ProcessResult?> run(FastlaneCreateKeychainParameters param) =>
Process.run(
'fastlane',
[
'run',
'create_keychain',
...param.toList(),
],
environment: {
'MATCH_PASSWORD': param.gitPassphrase,
},
);
}
@@ -0,0 +1,55 @@
// 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:convert';
import 'dart:io';
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/fastlane/fastlane_deploy_pilot_parameters.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/process_usecase.dart';
@usecase
class FastlaneDeployPilotUsecase
extends ProcessUsecase<FastlaneDeployPilotParameters> {
@override
String get onCompletedMessage => 'Ipa deployed!';
@override
String get onErrorMessage => 'Error deploying ipa!';
@override
String get onStartedMessage => 'Deploying ipa...';
@override
Future<ProcessResult?> run(FastlaneDeployPilotParameters param) async {
final file = File(param.appStoreConnectConfig.keyFilePath!);
final content = file.readAsBytesSync();
final key = utf8.decode(content);
const executable = 'fastlane';
final args = [
'run',
'pilot',
'api_key:${jsonEncode(param.appStoreConnectConfig.toJson(key))}',
...param.toList(),
];
logger.detail('Running: $executable ${args.join(' ')}');
return Process.run(executable, args);
}
}
@@ -0,0 +1,45 @@
// 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/injection/injectable.dart';
import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_deploy_supply_parameters.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/process_usecase.dart';
@usecase
class FastlaneDeploySupplyUsecase
extends ProcessUsecase<FastlaneDeploySupplyParameters> {
@override
String get onCompletedMessage => 'Aab deployed!';
@override
String get onErrorMessage => 'Error deploying aab!';
@override
String get onStartedMessage => 'Deploying aab...';
@override
Future<ProcessResult?> run(FastlaneDeploySupplyParameters param) =>
Process.run(
'fastlane',
[
'run',
'supply',
...param.toList(),
],
);
}
@@ -0,0 +1,49 @@
// 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/injection/injectable.dart';
import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_get_certificates_parameters.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/process_usecase.dart';
@usecase
class FastlaneGetCertificateUsecase
extends ProcessUsecase<FastlaneGetCertificatesParameters> {
@override
String get onCompletedMessage => 'Certificates fetched!';
@override
String get onErrorMessage => 'Error fetching certificates!';
@override
String get onStartedMessage => 'Fetching certificates...';
@override
Future<ProcessResult?> run(FastlaneGetCertificatesParameters param) =>
Process.run(
'fastlane',
[
'run',
'match',
'type:appstore',
...param.toList(),
],
environment: {
'MATCH_PASSWORD': param.gitPassphrase,
},
);
}
@@ -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 '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/core/utils/logging.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
@usecase
class FindLastIpaFileUsecase extends AsyncUseCase<String, String> {
const FindLastIpaFileUsecase() : super();
@override
FutureOrResult<String> execute(String params) {
final Directory directory = Directory(params);
if (!directory.existsSync()) {
logger.detail('No IPA file found in $params');
return Err(ClientException('Directory not found: $params'));
}
final List<FileSystemEntity> files = directory.listSync()
..sort((a, b) => b.statSync().modified.compareTo(a.statSync().modified));
for (final FileSystemEntity file in files) {
if (file.path.endsWith('.ipa')) {
logger.detail('Found IPA file: ${file.path}');
return Ok(file.path);
}
}
logger.detail('No IPA file found in $params');
return Err(ClientException('No IPA file found in $params'));
}
}
@@ -0,0 +1,43 @@
// 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/injection/injectable.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/process_usecase.dart';
@usecase
class FlutterBuildAppBundleUsecase extends ProcessUsecase<List<String>> {
const FlutterBuildAppBundleUsecase();
@override
String get onCompletedMessage => 'App bundle built!';
@override
String get onErrorMessage => 'Error building app bundle!';
@override
String get onStartedMessage => 'Building app bundle...';
@override
Future<ProcessResult?> run(
List<String> param,
) =>
Process.run(
'flutter',
['build', 'appbundle', '--release', ...param],
);
}
@@ -0,0 +1,43 @@
// 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/injection/injectable.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/process_usecase.dart';
@usecase
class FlutterBuildIpaUsecase extends ProcessUsecase<List<String>> {
const FlutterBuildIpaUsecase();
@override
String get onCompletedMessage => 'Ipa built!';
@override
String get onErrorMessage => 'Error building ipa!';
@override
String get onStartedMessage => 'Building ipa...';
@override
Future<ProcessResult?> run(
List<String> param,
) =>
Process.run(
'flutter',
['build', 'ipa', '--release', ...param],
);
}
@@ -0,0 +1,40 @@
// 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/injection/injectable.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/process_usecase.dart';
@usecase
class FlutterBuildXcarchiveUsecase extends ProcessNoParamsUsecase {
const FlutterBuildXcarchiveUsecase();
@override
String get onCompletedMessage => 'Xcarchive built!';
@override
String get onErrorMessage => 'Error building xcarchive!';
@override
String get onStartedMessage => 'Building xcarchive...';
@override
Future<ProcessResult?> run() => Process.run(
'flutter',
['build', 'xcarchive', '--no-codesign', '-v'],
);
}
@@ -0,0 +1,40 @@
// 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/injection/injectable.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/process_usecase.dart';
@usecase
class FlutterCleanUsecase extends ProcessNoParamsUsecase {
const FlutterCleanUsecase();
@override
String get onCompletedMessage => 'Project cleaned!';
@override
String get onErrorMessage => 'Error cleaning project!';
@override
String get onStartedMessage => 'Cleaning project...';
@override
Future<ProcessResult?> run() => Process.run(
'flutter',
['clean'],
);
}
@@ -0,0 +1,37 @@
// 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/injection/injectable.dart';
import 'package:wyatt_continuous_deployment/src/domain/usecases/process_usecase.dart';
@usecase
class FlutterPubGetUsecase extends ProcessNoParamsUsecase {
const FlutterPubGetUsecase();
@override
String get onCompletedMessage => 'Dependencies got!';
@override
String get onErrorMessage => 'Error getting dependencies!';
@override
String get onStartedMessage => 'Getting dependencies...';
@override
Future<ProcessResult?> run() => Process.run('flutter', ['pub', 'get']);
}
@@ -0,0 +1,93 @@
// 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_architecture/wyatt_architecture.dart';
import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart';
abstract class ProcessUsecase<T> extends AsyncUseCase<T, void> {
const ProcessUsecase();
/// If true, the process will not throw an exception if the exit
/// code is not 0.
/// This is useful for commands that may return a non-zero exit code.
/// For example, `git diff` returns 1 if there are differences.
bool get ignoreOnFailure => false;
String get onStartedMessage;
String get onCompletedMessage;
String get onErrorMessage;
@override
FutureOr<Res<void>> execute(T params) async {
final progress = logger.progress(onStartedMessage);
final processResult = await run(params);
if (processResult != null) {
if (processResult.exitCode != 0) {
progress.fail(onErrorMessage);
}
if (processResult.exitCode != 0 && !ignoreOnFailure) {
throw ClientException(processResult.stderr.toString());
}
}
progress.complete(onCompletedMessage);
logger.detail(processResult?.stdout.toString().trim());
return Res.ok(null);
}
/// Run the process with the given parameters.
Future<ProcessResult?> run(T param);
}
abstract class ProcessNoParamsUsecase extends NoParamsAsyncUseCase<void> {
const ProcessNoParamsUsecase() : super();
/// If true, the process will not throw an exception if the exit
/// code is not 0.
/// This is useful for commands that may return a non-zero exit code.
/// For example, `git diff` returns 1 if there are differences.
bool get ignoreOnFailure => false;
String get onStartedMessage;
String get onCompletedMessage;
String get onErrorMessage;
@override
FutureOr<Res<void>> execute() async {
final progress = logger.progress(onStartedMessage);
final processResult = await run();
if (processResult != null) {
if (processResult.exitCode != 0) {
progress.fail(onErrorMessage);
}
if (processResult.exitCode != 0 && !ignoreOnFailure) {
throw ClientException(processResult.stderr.toString());
}
}
progress.complete(onCompletedMessage);
logger.detail(processResult?.stdout.toString().trim());
return Res.ok(null);
}
/// Run the process with no parameters.
Future<ProcessResult?> run();
}
@@ -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 'dart:io';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
@usecase
class CheckConfigFileUsecase extends AsyncUseCase<String, void> {
const CheckConfigFileUsecase() : super();
@override
FutureOrResult<void> execute(String params) => unsafe(() {
final configFile = File(params);
if (!configFile.existsSync()) {
throw ClientException(
'CheckConfigFileUsecase: Cannot find $params',
);
}
});
}
@@ -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 'dart:async';
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';
@usecase
class CheckToolsUsecase extends NoParamsAsyncUseCase<void> {
const CheckToolsUsecase() : super();
@override
FutureOr<Res<void>> execute() async => unsafe(() async {
final missingTools = <String>[];
for (final tool in AppConstants.requiredTools) {
final isInstalled = await checkToolInstalled(tool);
if (!isInstalled) {
missingTools.add(tool);
}
}
if (missingTools.isNotEmpty) {
throw ClientException(
'CheckToolsUsecase: Missing ${missingTools.join(' - ')}',
);
}
});
Future<bool> checkToolInstalled(String tool) async {
final result = await Process.run('which', [tool]);
return result.exitCode == 0 && (result.stdout as String).trim().isNotEmpty;
}
}
@@ -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/>.
export 'config/get_android_config_usecase.dart';
export 'config/get_ios_config_usecase.dart';
export 'config/init_config_usecase.dart';
export 'decrypt_keys/decrypt_keys_usecase.dart';
export 'fastlane/fastlane_create_keychain_usecase.dart';
export 'fastlane/fastlane_deploy_pilot_usecase.dart';
export 'fastlane/fastlane_deploy_supply_usecase.dart';
export 'fastlane/fastlane_get_certificates_usecase.dart';
export 'find_last_ipa_file_usecase.dart';
export 'flutter/flutter_build_app_bundle_usecase.dart';
export 'flutter/flutter_build_ipa_usecase.dart';
export 'flutter/flutter_build_xcarchive_usecase.dart';
export 'flutter/flutter_clean_usecase.dart';
export 'flutter/flutter_pub_get_usecase.dart';
export 'process_usecase.dart';
export 'requirements/check_config_file_usecase.dart';
export 'requirements/check_tools_usecase.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:mason_logger/mason_logger.dart';
export 'domain/domain.dart';
abstract class WyattContinuousDeployment {
static Logger logger = Logger();
}
@@ -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/>.
/// Wyatt CD
library wyatt_continuous_deployment;
export 'src/wyatt_continuous_deployment.dart';