From 12b8a36c5079ebba7c78b32161399067f368c0ad Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Tue, 28 Jan 2025 17:24:21 +0900 Subject: [PATCH 01/22] Update pubspec.lock --- apps/dashboard/pubspec.lock | 112 ++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/apps/dashboard/pubspec.lock b/apps/dashboard/pubspec.lock index 1857133..95cc85e 100644 --- a/apps/dashboard/pubspec.lock +++ b/apps/dashboard/pubspec.lock @@ -13,18 +13,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: "88399e291da5f7e889359681a8f64b18c5123e03576b01f32a6a276611e511c3" url: "https://pub.dev" source: hosted - version: "76.0.0" + version: "78.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: daa1d780fdecf8af925680c06c86563cdd445deea995d5c9176f1302a2b10bbe + sha256: e4f2a7ef31b0ab2c89d2bde35ef3e6e6aff1dce5e66069c6540b0e9cfe33ee6b url: "https://pub.dev" source: hosted - version: "1.3.48" + version: "1.3.50" _macros: dependency: transitive description: dart @@ -42,18 +42,18 @@ packages: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "62899ef43d0b962b056ed2ebac6b47ec76ffd003d5f7c4e4dc870afe63188e33" url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "7.1.0" analyzer_plugin: dependency: transitive description: name: analyzer_plugin - sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + sha256: "1d460d14e3c2ae36dc2b32cef847c4479198cf87704f63c3c3c8150ee50c3916" url: "https://pub.dev" source: hosted - version: "0.11.3" + version: "0.12.0" args: dependency: transitive description: @@ -186,26 +186,26 @@ packages: dependency: "direct main" description: name: cloud_firestore - sha256: ba89d4ae6ddaea0241f50a2dc1ffe36f32891f1a6bc78540f55d79c7f8ed536a + sha256: "9ba2379f319906567f7078ebf086951c4e333c71620455084c18258f267f0636" url: "https://pub.dev" source: hosted - version: "5.6.0" + version: "5.6.2" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface - sha256: "966cfd6beb2e943f3363a7bced4476533caa55d2338ffd876855c07e5296d1d7" + sha256: c3a2987addea08273c582a91f5fb173ca81916ef6d7f8e1a6760c3a8a3a53fc7 url: "https://pub.dev" source: hosted - version: "6.6.0" + version: "6.6.2" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web - sha256: "110ba7ff3322c239527e5be3c0d471a3e14fd0a870b2feff96d1e527a2eae2bb" + sha256: "4c1bc404d825c68153660b12fd937b90b75cf3aa622cc077da5308ccaec17a9e" url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.4.2" code_builder: dependency: transitive description: @@ -258,34 +258,34 @@ packages: dependency: "direct dev" description: name: custom_lint - sha256: "3486c470bb93313a9417f926c7dd694a2e349220992d7b9d14534dc49c15bba9" + sha256: "6d509673c4dd0baa90e60dc8366bc2acc6690f16a7d44bfae31294d82c5d2a62" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.1" custom_lint_builder: dependency: transitive description: name: custom_lint_builder - sha256: "42cdc41994eeeddab0d7a722c7093ec52bd0761921eeb2cbdbf33d192a234759" + sha256: "8cc525c7b160eb47bb1ded8b2633c0f8b907930eb986ac577aded87cdd2835fe" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.1" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: "02450c3e45e2a6e8b26c4d16687596ab3c4644dd5792e3313aa9ceba5a49b7f5" + sha256: "6dcee8a017181941c51a110da7e267c1d104dc74bec8862eeb8c85b5c8759a9e" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.1" custom_lint_visitor: dependency: transitive description: name: custom_lint_visitor - sha256: bfe9b7a09c4775a587b58d10ebb871d4fe618237639b1e84d5ec62d7dfef25f9 + sha256: "14df0760dfa81b7b0c398c876045f4e4a343eb2c9d200c66163671dd3e337c1b" url: "https://pub.dev" source: hosted - version: "1.0.0+6.11.0" + version: "1.0.0+7.1.0" dart_firebase_admin: dependency: transitive description: @@ -298,18 +298,18 @@ packages: dependency: transitive description: name: dart_jsonwebtoken - sha256: "866787dc17afaef46a9ea7dd33eefe60c6d82084b4a36d70e8e788d091cd04ef" + sha256: "06e02e18827d047f206e1051c15b493c9c29a2dba0f9b2a905d73748dec4f931" url: "https://pub.dev" source: hosted - version: "2.14.2" + version: "2.16.0" dart_style: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "3.0.1" dotenv: dependency: transitive description: @@ -362,34 +362,34 @@ packages: dependency: "direct main" description: name: firebase_auth - sha256: "03483af6e67b7c4b696ca9386989a6cd5593569e1ac5af6907ea5f7fd9c16d8b" + sha256: eabb0dddb72956b7004aabf03c0bf650c19f92e470441d6a4e6b19cd4e9ca958 url: "https://pub.dev" source: hosted - version: "5.3.4" + version: "5.4.1" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface - sha256: "3e1409f48c48930635705b1237ebbdee8c54c19106a0a4fb321dbb4b642820c4" + sha256: b8684ade236c20638a17343f89029f7059fef13245cc102413d5c9cf6f98afb5 url: "https://pub.dev" source: hosted - version: "7.4.10" + version: "7.5.1" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - sha256: d83fe95c44d73c9c29b006ac7df3aa5e1b8ce92b62edc44e8f86250951fe2cd0 + sha256: "5ea5f2fbe02900ff2772ed9ea95ed8b1c7e160f4b4b6000d227bdeecd40904f8" url: "https://pub.dev" source: hosted - version: "5.13.5" + version: "5.13.7" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "15d761b95dfa2906dfcc31b7fc6fe293188533d1a3ffe78389ba9e69bd7fdbde" + sha256: d851c1ca98fd5a4c07c747f8c65dacc2edd84a4d9ac055d32a5f0342529069f5 url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "3.10.1" firebase_core_platform_interface: dependency: transitive description: @@ -489,10 +489,10 @@ packages: dependency: transitive description: name: freezed - sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" url: "https://pub.dev" source: hosted - version: "2.5.7" + version: "2.5.8" freezed_annotation: dependency: transitive description: @@ -513,10 +513,10 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" google_fonts: dependency: "direct main" description: @@ -561,18 +561,18 @@ packages: dependency: transitive description: name: hotreloader - sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.3.0" http: dependency: transitive description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" http_multi_server: dependency: transitive description: @@ -832,10 +832,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0" + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" riverpod: dependency: transitive description: @@ -848,10 +848,10 @@ packages: dependency: transitive description: name: riverpod_analyzer_utils - sha256: c6b8222b2b483cb87ae77ad147d6408f400c64f060df7a225b127f4afef4f8c8 + sha256: "837a6dc33f490706c7f4632c516bcd10804ee4d9ccc8046124ca56388715fdf3" url: "https://pub.dev" source: hosted - version: "0.5.8" + version: "0.5.9" riverpod_annotation: dependency: "direct main" description: @@ -864,18 +864,18 @@ packages: dependency: "direct dev" description: name: riverpod_generator - sha256: "63546d70952015f0981361636bf8f356d9cfd9d7f6f0815e3c07789a41233188" + sha256: "120d3310f687f43e7011bb213b90a436f1bbc300f0e4b251a72c39bccb017a4f" url: "https://pub.dev" source: hosted - version: "2.6.3" + version: "2.6.4" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: "83e4caa337a9840469b7b9bd8c2351ce85abad80f570d84146911b32086fbd99" + sha256: b05408412b0f75dec954e032c855bc28349eeed2d2187f94519e1ddfdf8b3693 url: "https://pub.dev" source: hosted - version: "2.6.3" + version: "2.6.4" rxdart: dependency: transitive description: @@ -933,10 +933,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "2.0.0" source_span: dependency: transitive description: @@ -1077,18 +1077,18 @@ packages: dependency: transitive description: name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" win32: dependency: transitive description: name: win32 - sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29" + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "5.10.1" xdg_directories: dependency: transitive description: From f91d6652332dcb32d8a7b1de90d65e6cfdf9f5c6 Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Tue, 28 Jan 2025 17:26:01 +0900 Subject: [PATCH 02/22] Trivial pubspec.yaml whitespace update --- apps/dashboard/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index 48476de..fc20e96 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: riverpod_annotation: ^2.5.3 signals: ^6.0.2 uuid: ^4.5.1 + dev_dependencies: build_runner: ^2.4.14 custom_lint: ^0.7.0 From d4c22cb6d4c94e3e4daf15b984a5d41397a12d42 Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Tue, 28 Jan 2025 17:56:24 +0900 Subject: [PATCH 03/22] Fix build issue --- apps/dashboard/lib/main.dart | 4 ++ apps/dashboard/macos/Podfile.lock | 72 +++++++++---------- .../macos/Runner/DebugProfile.entitlements | 24 +++---- apps/dashboard/macos/Runner/Info.plist | 60 ++++++++-------- .../macos/Runner/Release.entitlements | 22 +++--- apps/dashboard/pubspec.yaml | 6 +- 6 files changed, 96 insertions(+), 92 deletions(-) diff --git a/apps/dashboard/lib/main.dart b/apps/dashboard/lib/main.dart index ab8e710..05dd9be 100644 --- a/apps/dashboard/lib/main.dart +++ b/apps/dashboard/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:dashboard/src/features/auth_gate/presentation/auth_gate.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; @@ -11,6 +12,9 @@ void main() async { await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); + FirebaseFirestore.instance.settings = const Settings( + persistenceEnabled: false, + ); runApp(const ProviderScope(child: MyApp())); } diff --git a/apps/dashboard/macos/Podfile.lock b/apps/dashboard/macos/Podfile.lock index 0ec85d7..65bebca 100644 --- a/apps/dashboard/macos/Podfile.lock +++ b/apps/dashboard/macos/Podfile.lock @@ -1151,52 +1151,52 @@ PODS: - BoringSSL-GRPC/Implementation (0.0.36): - BoringSSL-GRPC/Interface (= 0.0.36) - BoringSSL-GRPC/Interface (0.0.36) - - cloud_firestore (5.6.0): - - Firebase/CoreOnly (~> 11.4.0) - - Firebase/Firestore (~> 11.4.0) + - cloud_firestore (5.6.2): + - Firebase/CoreOnly (~> 11.6.0) + - Firebase/Firestore (~> 11.6.0) - firebase_core - FlutterMacOS - - Firebase/Auth (11.4.2): + - Firebase/Auth (11.6.0): - Firebase/CoreOnly - - FirebaseAuth (~> 11.4.0) - - Firebase/CoreOnly (11.4.2): - - FirebaseCore (= 11.4.2) - - Firebase/Firestore (11.4.2): + - FirebaseAuth (~> 11.6.0) + - Firebase/CoreOnly (11.6.0): + - FirebaseCore (~> 11.6.0) + - Firebase/Firestore (11.6.0): - Firebase/CoreOnly - - FirebaseFirestore (~> 11.4.0) - - firebase_auth (5.3.4): - - Firebase/Auth (~> 11.4.0) - - Firebase/CoreOnly (~> 11.4.0) + - FirebaseFirestore (~> 11.6.0) + - firebase_auth (5.4.1): + - Firebase/Auth (~> 11.6.0) + - Firebase/CoreOnly (~> 11.6.0) - firebase_core - FlutterMacOS - - firebase_core (3.9.0): - - Firebase/CoreOnly (~> 11.4.0) + - firebase_core (3.10.1): + - Firebase/CoreOnly (~> 11.6.0) - FlutterMacOS - FirebaseAppCheckInterop (11.6.0) - - FirebaseAuth (11.4.0): + - FirebaseAuth (11.6.0): - FirebaseAppCheckInterop (~> 11.0) - FirebaseAuthInterop (~> 11.0) - - FirebaseCore (~> 11.4) - - FirebaseCoreExtension (~> 11.4) + - FirebaseCore (~> 11.6.0) + - FirebaseCoreExtension (~> 11.6.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/Environment (~> 8.0) - GTMSessionFetcher/Core (< 5.0, >= 3.4) - RecaptchaInterop (~> 100.0) - FirebaseAuthInterop (11.6.0) - - FirebaseCore (11.4.2): - - FirebaseCoreInternal (< 12.0, >= 11.4.2) + - FirebaseCore (11.6.0): + - FirebaseCoreInternal (~> 11.6.0) - GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Logger (~> 8.0) - - FirebaseCoreExtension (11.4.1): - - FirebaseCore (~> 11.0) + - FirebaseCoreExtension (11.6.0): + - FirebaseCore (~> 11.6.0) - FirebaseCoreInternal (11.6.0): - "GoogleUtilities/NSData+zlib (~> 8.0)" - - FirebaseFirestore (11.4.0): - - FirebaseCore (~> 11.4) - - FirebaseCoreExtension (~> 11.4) - - FirebaseFirestoreInternal (= 11.4.0) + - FirebaseFirestore (11.6.0): + - FirebaseCore (~> 11.6.0) + - FirebaseCoreExtension (~> 11.6.0) + - FirebaseFirestoreInternal (= 11.6.0) - FirebaseSharedSwift (~> 11.0) - - FirebaseFirestoreInternal (11.4.0): + - FirebaseFirestoreInternal (11.6.0): - abseil/algorithm (~> 1.20240116.1) - abseil/base (~> 1.20240116.1) - abseil/container/flat_hash_map (~> 1.20240116.1) @@ -1206,7 +1206,7 @@ PODS: - abseil/time (~> 1.20240116.1) - abseil/types (~> 1.20240116.1) - FirebaseAppCheckInterop (~> 11.0) - - FirebaseCore (~> 11.0) + - FirebaseCore (~> 11.6.0) - "gRPC-C++ (~> 1.65.0)" - gRPC-Core (~> 1.65.0) - leveldb-library (~> 1.22) @@ -1378,18 +1378,18 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: abseil: d121da9ef7e2ff4cab7666e76c5a3e0915ae08c3 BoringSSL-GRPC: ca6a8e5d04812fce8ffd6437810c2d46f925eaeb - cloud_firestore: f49903b68063eb0bdee44f24c486c619eba7790a - Firebase: 7fd5466678d964be78fbf536d8a3385da19c4828 - firebase_auth: 9f96683c096b8cb64cd770513bf3daf8d3301d75 - firebase_core: 14e47e659f61e64000927bf2c1f96f1a10349443 + cloud_firestore: 9b9be35a8c7bcf4c14a22b2902429cacf51fb124 + Firebase: 374a441a91ead896215703a674d58cdb3e9d772b + firebase_auth: c578751fa92513fdbb80f301aa0e99a190030639 + firebase_core: 85444430218fe014688c2c467f0a545dfbc2bd24 FirebaseAppCheckInterop: 347aa09a805219a31249b58fc956888e9fcb314b - FirebaseAuth: c359af98bd703cbf4293eec107a40de08ede6ce6 + FirebaseAuth: 0304982cfe00df8d49bf533bc4becd3de36c7122 FirebaseAuthInterop: a919d415797d23b7bfe195a04f322b86c65020ef - FirebaseCore: 6b32c57269bd999aab34354c3923d92a6e5f3f84 - FirebaseCoreExtension: f1bc67a4702931a7caa097d8e4ac0a1b0d16720e + FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa + FirebaseCoreExtension: 2d77d6430c16cf43ca2b04608302ed02b3598361 FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2 - FirebaseFirestore: 2ccdf893fd7e175aa8ec58faa06338b346d27db8 - FirebaseFirestoreInternal: 004452c4669d5df8869c9f8f7a24ee0009852d5b + FirebaseFirestore: d5dcc15724f291fe4b415322754bfa01616037fe + FirebaseFirestoreInternal: db478fdaeb98fe8686ff49e600f3871c224a76ff FirebaseSharedSwift: a4e5dfca3e210633bb3a3dfb94176c019211948b FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d diff --git a/apps/dashboard/macos/Runner/DebugProfile.entitlements b/apps/dashboard/macos/Runner/DebugProfile.entitlements index 1fbcb4e..a189f23 100644 --- a/apps/dashboard/macos/Runner/DebugProfile.entitlements +++ b/apps/dashboard/macos/Runner/DebugProfile.entitlements @@ -1,16 +1,14 @@ - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.network.client - - com.apple.security.network.server - - keychain-access-groups - - - + + com.apple.security.app-sandbox + + com.apple.security.network.client + + com.apple.security.network.server + + keychain-access-groups + + + \ No newline at end of file diff --git a/apps/dashboard/macos/Runner/Info.plist b/apps/dashboard/macos/Runner/Info.plist index 4789daa..eecdbee 100644 --- a/apps/dashboard/macos/Runner/Info.plist +++ b/apps/dashboard/macos/Runner/Info.plist @@ -1,32 +1,34 @@ - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - $(PRODUCT_COPYRIGHT) - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + FirestoreLocalStorage + + + \ No newline at end of file diff --git a/apps/dashboard/macos/Runner/Release.entitlements b/apps/dashboard/macos/Runner/Release.entitlements index c312f41..a189f23 100644 --- a/apps/dashboard/macos/Runner/Release.entitlements +++ b/apps/dashboard/macos/Runner/Release.entitlements @@ -1,14 +1,14 @@ - - com.apple.security.app-sandbox - - com.apple.security.network.client - - com.apple.security.network.server - - keychain-access-groups - - - + + com.apple.security.app-sandbox + + com.apple.security.network.client + + com.apple.security.network.server + + keychain-access-groups + + + \ No newline at end of file diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index fc20e96..d72c79e 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -6,11 +6,11 @@ environment: sdk: ^3.6.0 dependencies: - cloud_firestore: ^5.6.0 + cloud_firestore: ^5.6.2 cupertino_icons: ^1.0.8 file_picker: ^8.1.7 firebase_auth: ^5.3.1 - firebase_core: ^3.6.0 + firebase_core: ^3.10.1 flutter: sdk: flutter flutter_adaptive_scaffold: ^0.3.1 @@ -24,7 +24,7 @@ dependencies: riverpod_annotation: ^2.5.3 signals: ^6.0.2 uuid: ^4.5.1 - + dev_dependencies: build_runner: ^2.4.14 custom_lint: ^0.7.0 From 406000b57dce6f26b9969cb99affe6466c2d804a Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Wed, 29 Jan 2025 00:03:18 +0900 Subject: [PATCH 04/22] Update dashboard to upload ASC keys --- .dart_tool/extension_discovery/devtools.json | 1 + apps/.DS_Store | Bin 14340 -> 12292 bytes apps/dashboard/firestore.rules | 1 + apps/dashboard/lib/main.dart | 3 + .../steps/presentation/steps_section.dart | 163 +++++++++++++++++- 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 .dart_tool/extension_discovery/devtools.json diff --git a/.dart_tool/extension_discovery/devtools.json b/.dart_tool/extension_discovery/devtools.json new file mode 100644 index 0000000..6c79b2c --- /dev/null +++ b/.dart_tool/extension_discovery/devtools.json @@ -0,0 +1 @@ +{"version":2,"entries":[{"package":"openci","rootUri":"../","packageUri":"lib/"}]} \ No newline at end of file diff --git a/apps/.DS_Store b/apps/.DS_Store index 53fe1bcc83a35a021af2eb5a33293cb01601ada3..42a25bc7b6d46ff1105e5206f4fed64346513c70 100644 GIT binary patch delta 850 zcmdUtUr5tY6vxl!ic;*ilwYptG+MRVBFJeHLOm=a%_JgRYyE+O+UQrdiPIDzOd3Yf zOS{#pkp$I4|Ln~&is~gKgb1=8>>&(#h#;uFM0X8@q8A@KaL@Oi^KcILbHCZH>>T%MkOf1ozF}gAmHWN&YO)k%%H&!kgTv%D`%w^2FrLtO@BqC8sN?L~Gf{e%m zc_b4uDR1P1%*t2E%TGX2f`tb!RaCQrI_ha)J)77@8{6q1&OY{YfCR_rLBsq%1aeC-A9QqmH3|WRb%QND=CW1sHi_nqo^PvqKF79dO#QyND$SXGsx(Vp#JN?z2CzKv0HoXukCZ^c|!re8F4zFppwQVr=+G?(le~%r`6RrG_JKa z*(OE(#^$O}#2;)i8;r22HP)5gT3~B4jT$51iTI;NxhG} zZ{`z^n6gQ{64YK;d;v2W@&<6|Qof8w_xhTlzg7@RV1) navigatorKey = GlobalKey(); + void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( @@ -35,6 +37,7 @@ class MyApp extends StatelessWidget { ThemeData.dark().textTheme, ), ), + navigatorKey: navigatorKey, home: const AuthGate(), ); } diff --git a/apps/dashboard/lib/src/features/workflow/presentation/workflow_editor/presentation/steps/presentation/steps_section.dart b/apps/dashboard/lib/src/features/workflow/presentation/workflow_editor/presentation/steps/presentation/steps_section.dart index e00a549..07be3da 100644 --- a/apps/dashboard/lib/src/features/workflow/presentation/workflow_editor/presentation/steps/presentation/steps_section.dart +++ b/apps/dashboard/lib/src/features/workflow/presentation/workflow_editor/presentation/steps/presentation/steps_section.dart @@ -1,6 +1,13 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:dashboard/main.dart'; import 'package:dashboard/src/common_widgets/margins.dart'; import 'package:dashboard/src/features/secrets/presentation/secret_page_controller.dart'; import 'package:dashboard/src/features/workflow/presentation/workflow_editor/presentation/workflow_editor_controller.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -179,7 +186,7 @@ class _StepItem extends HookConsumerWidget { ), verticalMargin8, TextButton( - onPressed: () { + onPressed: () async { controller ..updateStepName( index, @@ -189,6 +196,152 @@ class _StepItem extends HookConsumerWidget { index, commandTextEditingController.text, ); + + final state = navigatorKey.currentState; + + if (commandTextEditingController.text + .contains('flutter build ipa')) { + final isAppStoreConnectApiKeyUploaded = + await _isAppStoreConnectApiKeyUploaded(); + if (state == null) { + return; + } + if (isAppStoreConnectApiKeyUploaded) { + return; + } + + final issuerIdController = TextEditingController(); + final keyIdController = TextEditingController(); + final keyFileController = TextEditingController(); + + await showAdaptiveDialog( + context: state.context, + builder: (context) => AlertDialog( + title: const Text('AppStore Connect API Key'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'To run flutter build ipa, you need to upload AppStore Connect API Key', + ), + verticalMargin8, + TextFormField( + controller: issuerIdController, + decoration: const InputDecoration( + labelText: 'issuer-id', + ), + ), + TextFormField( + controller: keyIdController, + decoration: const InputDecoration( + labelText: 'key-id', + ), + ), + TextFormField( + controller: keyFileController, + decoration: InputDecoration( + labelText: '.p8 key file, base64 encoded', + suffixIcon: IconButton( + onPressed: () async { + final keyFile = + await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['p8'], + ); + if (keyFile == null) { + return; + } + + final path = keyFile.files.first.path; + if (path == null) { + return; + } + + final bytes = await File(path).readAsBytes(); + final keyFileContentBase64 = + base64Encode(bytes); + keyFileController.text = keyFileContentBase64; + }, + icon: const Icon(Icons.folder), + ), + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text( + 'Cancel', + style: TextStyle(color: Colors.red), + ), + ), + TextButton( + onPressed: () async { + try { + await FirebaseFirestore.instance + .collection(secretsCollectionPath) + .add({ + 'key': 'OPENCI_ASC_ISSUER_ID', + 'value': issuerIdController.text, + 'owners': [ + FirebaseAuth.instance.currentUser!.uid, + ], + 'createdAt': + DateTime.now().millisecondsSinceEpoch, + 'updatedAt': + DateTime.now().millisecondsSinceEpoch, + }); + + await FirebaseFirestore.instance + .collection(secretsCollectionPath) + .add({ + 'key': 'OPENCI_ASC_KEY_ID', + 'value': keyIdController.text, + 'owners': [ + FirebaseAuth.instance.currentUser!.uid, + ], + 'createdAt': + DateTime.now().millisecondsSinceEpoch, + 'updatedAt': + DateTime.now().millisecondsSinceEpoch, + }); + + await FirebaseFirestore.instance + .collection(secretsCollectionPath) + .add({ + 'key': 'OPENCI_ASC_KEY_BASE64', + 'value': keyFileController.text, + 'owners': [ + FirebaseAuth.instance.currentUser!.uid, + ], + 'createdAt': + DateTime.now().millisecondsSinceEpoch, + 'updatedAt': + DateTime.now().millisecondsSinceEpoch, + }); + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'AppStore Connect API Key uploaded', + ), + ), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(e.toString()), + ), + ); + } + }, + child: const Text('Upload'), + ), + ], + ), + ); + } }, child: const Text('Save Step'), ), @@ -210,6 +363,14 @@ class _StepItem extends HookConsumerWidget { ), ); } + + Future _isAppStoreConnectApiKeyUploaded() async { + final secrets = await FirebaseFirestore.instance + .collection(secretsCollectionPath) + .where('key', isEqualTo: 'OPENCI_ASC_ISSUER_ID') + .get(); + return secrets.docs.isNotEmpty; + } } class _Command extends ConsumerWidget { From 60476af648195c241f655131242eb39e7aca2365 Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sat, 1 Feb 2025 17:35:31 +0900 Subject: [PATCH 05/22] Update --- apps/.DS_Store | Bin 12292 -> 14340 bytes apps/dashboard/ios/Podfile.lock | 68 +-- apps/openci_cli/CHANGELOG.md | 20 +- .../openci_cli/lib/src/app_store_connect.dart | 108 +++-- .../lib/src/commands/command_runner.dart | 1 + .../openci_cli/lib/src/commands/commands.dart | 1 + .../lib/src/commands/list_bundle_ids.dart | 53 +++ .../lib/src/create_certificate.dart | 35 +- .../lib/src/create_provisioning_profile.dart | 19 +- apps/openci_cli/lib/src/list_bundle_ids.dart | 32 ++ .../openci_cli/lib/src/list_certificates.dart | 6 +- .../lib/src/list_provisioning_profiles.dart | 6 +- apps/openci_cli/lib/src/read_certificate.dart | 5 +- .../lib/src/read_provisioning_profile.dart | 6 +- apps/openci_cli/lib/src/version.dart | 2 +- apps/openci_cli/pubspec.yaml | 2 +- apps/openci_cli/scripts/create-certificate.sh | 9 +- apps/openci_cli/scripts/create-pp.sh | 15 + apps/openci_cli/scripts/list-bundle-ids.sh | 10 + .../lib/src/handle_flutter_builld_ipa.dart | 447 ++++++++++++++++++ apps/openci_runner/lib/src/run_command.dart | 4 +- .../lib/src/run_multi_commands.dart | 11 + melos.yaml | 3 +- 23 files changed, 722 insertions(+), 141 deletions(-) create mode 100644 apps/openci_cli/lib/src/commands/list_bundle_ids.dart create mode 100644 apps/openci_cli/lib/src/list_bundle_ids.dart create mode 100644 apps/openci_cli/scripts/create-pp.sh create mode 100644 apps/openci_cli/scripts/list-bundle-ids.sh create mode 100644 apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart diff --git a/apps/.DS_Store b/apps/.DS_Store index 42a25bc7b6d46ff1105e5206f4fed64346513c70..138489310889c3ef304db57d74dc412382362e7c 100644 GIT binary patch delta 1098 zcmd^;TS$~q5Xa|tOx@M3G&Rf;Kif(WgS9NRAeJ!F(9F9PrMvD%3+wLYw{Fk{MeIfw z(S3<-uZ2=%MnyfAMo~dTL=hoY(1oyvAcCmAZ4EM_hv=<=bN<83a2S3wr@Og3pos`8 z+gB(cywM$Mx&?2@2m0baHksY|7;8m zXX|v>D>}bNt@hk#*7~EU*5_z-o2~XpcM&s5rYw>YDVJtxlbv!{j>>>slwlc>hw@Bb z%eZ_3;xLmyBB^AOg+?~H6jIDGmQ%tys;Fio%{Xbn)j=z51ZgKk2YcAZeh$#bF-~xj zQ=H~JgIwSmx4FYz?(v8*p7Vltd{v@Q2`W{kshKKEX=<*rs->z>*;SdURt>5#+PNaC zvSC6eL^-5J#4Ijxh%Q^jDSpFi6PH&H1)N^LQ$(HnJ^MMUOUu?+%Kw^u^8C1e#s0C3 z$t!s;pAGiYNFkjZY!tDAQr1#w(5|75%{1XKn7a(%J_2k*cd?7z2K7$5ILIN6&_l05 z{3~-i9T;>W_xy}tnxXCSJ77uvJOWyGI7s3^j3G*TRalscB_9f3A%1IxM zpA{R;+Y$|3HEHYbkH>_k=T%u$^iLE;3d7#K-kfT;*AeuD+$D~nTmQp4|4&Fiegl&{ B<4phn delta 864 zcmdUtUr5tY6vxl!N@DD{9Aj%b`!j2`MUit85%OUfX(kcjTI&xO)TEV7X__U3S;HuL z(ObP5Nl-oX&)zDds9r)uh#>319{K}4L=e5_9tEuuK{h@!9V9PWej0;m{giq&A`_1NHiYuTndmZl`7U#ELNek;Vtj^&JS`*DNEVa z0_9O<%Bw2WT2-a$)OyvZE8GFy;9tb3Zu6Jho0_ebIkE7sC`Nldb7nCm(=sEUs#1DoE_{m4DtlpIY5d-bkk!f9HWoZWEtcPXBlRdHZ1ZaE^~!Z zu5y!G+~zL#dB8Z2cx>3b;3cp1j3=O-f!ylZUo-ak{JWS}Xa#LXH~!(EsV_ykv}T({ X!CzuDq&sScT>mu4{|lw`FDSnN_*cK5 diff --git a/apps/dashboard/ios/Podfile.lock b/apps/dashboard/ios/Podfile.lock index f76ace3..b64c45c 100644 --- a/apps/dashboard/ios/Podfile.lock +++ b/apps/dashboard/ios/Podfile.lock @@ -1151,8 +1151,8 @@ PODS: - BoringSSL-GRPC/Implementation (0.0.36): - BoringSSL-GRPC/Interface (= 0.0.36) - BoringSSL-GRPC/Interface (0.0.36) - - cloud_firestore (5.6.0): - - Firebase/Firestore (= 11.4.0) + - cloud_firestore (5.6.2): + - Firebase/Firestore (= 11.6.0) - firebase_core - Flutter - DKImagePickerController/Core (4.3.9): @@ -1189,46 +1189,46 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter - - Firebase/Auth (11.4.0): + - Firebase/Auth (11.6.0): - Firebase/CoreOnly - - FirebaseAuth (~> 11.4.0) - - Firebase/CoreOnly (11.4.0): - - FirebaseCore (= 11.4.0) - - Firebase/Firestore (11.4.0): + - FirebaseAuth (~> 11.6.0) + - Firebase/CoreOnly (11.6.0): + - FirebaseCore (~> 11.6.0) + - Firebase/Firestore (11.6.0): - Firebase/CoreOnly - - FirebaseFirestore (~> 11.4.0) - - firebase_auth (5.3.4): - - Firebase/Auth (= 11.4.0) + - FirebaseFirestore (~> 11.6.0) + - firebase_auth (5.4.1): + - Firebase/Auth (= 11.6.0) - firebase_core - Flutter - - firebase_core (3.9.0): - - Firebase/CoreOnly (= 11.4.0) + - firebase_core (3.10.1): + - Firebase/CoreOnly (= 11.6.0) - Flutter - FirebaseAppCheckInterop (11.6.0) - - FirebaseAuth (11.4.0): + - FirebaseAuth (11.6.0): - FirebaseAppCheckInterop (~> 11.0) - FirebaseAuthInterop (~> 11.0) - - FirebaseCore (~> 11.4) - - FirebaseCoreExtension (~> 11.4) + - FirebaseCore (~> 11.6.0) + - FirebaseCoreExtension (~> 11.6.0) - GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/Environment (~> 8.0) - GTMSessionFetcher/Core (< 5.0, >= 3.4) - RecaptchaInterop (~> 100.0) - FirebaseAuthInterop (11.6.0) - - FirebaseCore (11.4.0): - - FirebaseCoreInternal (~> 11.0) + - FirebaseCore (11.6.0): + - FirebaseCoreInternal (~> 11.6.0) - GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/Logger (~> 8.0) - - FirebaseCoreExtension (11.4.1): - - FirebaseCore (~> 11.0) + - FirebaseCoreExtension (11.6.0): + - FirebaseCore (~> 11.6.0) - FirebaseCoreInternal (11.6.0): - "GoogleUtilities/NSData+zlib (~> 8.0)" - - FirebaseFirestore (11.4.0): - - FirebaseCore (~> 11.4) - - FirebaseCoreExtension (~> 11.4) - - FirebaseFirestoreInternal (= 11.4.0) + - FirebaseFirestore (11.6.0): + - FirebaseCore (~> 11.6.0) + - FirebaseCoreExtension (~> 11.6.0) + - FirebaseFirestoreInternal (= 11.6.0) - FirebaseSharedSwift (~> 11.0) - - FirebaseFirestoreInternal (11.4.0): + - FirebaseFirestoreInternal (11.6.0): - abseil/algorithm (~> 1.20240116.1) - abseil/base (~> 1.20240116.1) - abseil/container/flat_hash_map (~> 1.20240116.1) @@ -1238,7 +1238,7 @@ PODS: - abseil/time (~> 1.20240116.1) - abseil/types (~> 1.20240116.1) - FirebaseAppCheckInterop (~> 11.0) - - FirebaseCore (~> 11.0) + - FirebaseCore (~> 11.6.0) - "gRPC-C++ (~> 1.65.0)" - gRPC-Core (~> 1.65.0) - leveldb-library (~> 1.22) @@ -1423,21 +1423,21 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: abseil: d121da9ef7e2ff4cab7666e76c5a3e0915ae08c3 BoringSSL-GRPC: ca6a8e5d04812fce8ffd6437810c2d46f925eaeb - cloud_firestore: 50c9e447367f07951ec36e06dffbce8e38bc2149 + cloud_firestore: ad4e5097d52cb08257b37e31c272b2130f964934 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517 - Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99 - firebase_auth: dee97e7428ef7c304083839d4e3bc4313c03afd5 - firebase_core: 726c34112998e66d1ddaf4b1bef78ed2dd4b9804 + Firebase: 374a441a91ead896215703a674d58cdb3e9d772b + firebase_auth: d60969911baf096763178412a28388c39f361e2d + firebase_core: d3deb82d05dac4bc13190f9d1770787fd6a1cda4 FirebaseAppCheckInterop: 347aa09a805219a31249b58fc956888e9fcb314b - FirebaseAuth: c359af98bd703cbf4293eec107a40de08ede6ce6 + FirebaseAuth: 0304982cfe00df8d49bf533bc4becd3de36c7122 FirebaseAuthInterop: a919d415797d23b7bfe195a04f322b86c65020ef - FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771 - FirebaseCoreExtension: f1bc67a4702931a7caa097d8e4ac0a1b0d16720e + FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa + FirebaseCoreExtension: 2d77d6430c16cf43ca2b04608302ed02b3598361 FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2 - FirebaseFirestore: 2ccdf893fd7e175aa8ec58faa06338b346d27db8 - FirebaseFirestoreInternal: 004452c4669d5df8869c9f8f7a24ee0009852d5b + FirebaseFirestore: d5dcc15724f291fe4b415322754bfa01616037fe + FirebaseFirestoreInternal: db478fdaeb98fe8686ff49e600f3871c224a76ff FirebaseSharedSwift: a4e5dfca3e210633bb3a3dfb94176c019211948b Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d diff --git a/apps/openci_cli/CHANGELOG.md b/apps/openci_cli/CHANGELOG.md index c289c18..957c415 100644 --- a/apps/openci_cli/CHANGELOG.md +++ b/apps/openci_cli/CHANGELOG.md @@ -1,2 +1,20 @@ +## 0.1.6 +- Minor bug fix + +## 0.1.5 +- Add `list-bundle-ids` command + +## 0.1.4 +- Minor bug fix + +## 0.1.3 +- Minor bug fix + +## 0.1.2 +- Minor bug fix + +## 0.1.1 +- Minor bug fix + ## 0.1.0 -Initial Release \ No newline at end of file +Initial Release diff --git a/apps/openci_cli/lib/src/app_store_connect.dart b/apps/openci_cli/lib/src/app_store_connect.dart index e074ba7..0153e82 100644 --- a/apps/openci_cli/lib/src/app_store_connect.dart +++ b/apps/openci_cli/lib/src/app_store_connect.dart @@ -46,42 +46,33 @@ class AppStoreConnectClient { final uri = Uri.parse('$_baseUrl$path'); late http.Response response; - try { - switch (method) { - case 'GET': - response = await _client.get(uri, headers: headers); - case 'POST': - response = await _client.post( - uri, - headers: headers, - body: json.encode(body), - ); - case 'PATCH': - response = await _client.patch( - uri, - headers: headers, - body: json.encode(body), - ); - case 'DELETE': - response = await _client.delete(uri, headers: headers); - default: - throw AppStoreConnectError('Unsupported HTTP method: $method'); - } - - if (response.statusCode >= 400) { - throw AppStoreConnectError( - 'API request failed: ${response.statusCode} - ${response.body}', + switch (method) { + case 'GET': + response = await _client.get(uri, headers: headers); + case 'POST': + response = await _client.post( + uri, + headers: headers, + body: json.encode(body), + ); + case 'PATCH': + response = await _client.patch( + uri, + headers: headers, + body: json.encode(body), ); - } + case 'DELETE': + response = await _client.delete(uri, headers: headers); + default: + throw AppStoreConnectError('Unsupported HTTP method: $method'); + } - if (response.body.isEmpty) { - return {}; - } + final responseBody = jsonDecode(response.body) as Map; - return json.decode(response.body) as Map; - } catch (e) { - throw AppStoreConnectError('Request failed: $e'); - } + return { + 'statusCode': response.statusCode, + 'body': responseBody, + }; } /// アプリ情報を取得する @@ -131,26 +122,19 @@ class AppStoreConnectClient { required String csrContent, required String certificateType, }) async { - try { - final response = await _request( - path: '/certificates', - method: 'POST', - body: { - 'data': { - 'type': 'certificates', - 'attributes': { - 'certificateType': certificateType, - 'csrContent': csrContent, - }, + return _request( + path: '/certificates', + method: 'POST', + body: { + 'data': { + 'type': 'certificates', + 'attributes': { + 'certificateType': certificateType, + 'csrContent': csrContent, }, }, - ); - return response; - } catch (e) { - throw AppStoreConnectError( - 'Failed to create certificate: $e', - ); - } + }, + ); } /// プロビジョニングプロファイルを作成する @@ -272,6 +256,28 @@ class AppStoreConnectClient { return response; } + /// バンドルID一覧を取得する + Future> listBundleIds({ + String? filterIdentifier, + }) async { + var path = '/bundleIds'; + final queryParams = []; + + if (filterIdentifier != null && filterIdentifier.isNotEmpty) { + queryParams.add('filter[identifier]=$filterIdentifier'); + } + + if (queryParams.isNotEmpty) { + path += '?${queryParams.join('&')}'; + } + + final response = await _request( + path: path, + method: 'GET', + ); + return response; + } + /// リソースを解放する void dispose() { _client.close(); diff --git a/apps/openci_cli/lib/src/commands/command_runner.dart b/apps/openci_cli/lib/src/commands/command_runner.dart index 40e9d4c..cd671e0 100644 --- a/apps/openci_cli/lib/src/commands/command_runner.dart +++ b/apps/openci_cli/lib/src/commands/command_runner.dart @@ -43,6 +43,7 @@ class OpenciCliCommandRunner extends CompletionCommandRunner { addCommand(ReadCertificateCommand()); addCommand(ListCertificatesCommand()); addCommand(ListProvisioningProfileCommand()); + addCommand(ListBundleIdsCommand()); addCommand(ReadProvisioningProfileCommand()); addCommand(UpdateCommand(logger: _logger, pubUpdater: _pubUpdater)); } diff --git a/apps/openci_cli/lib/src/commands/commands.dart b/apps/openci_cli/lib/src/commands/commands.dart index 8542f9c..c5030ca 100644 --- a/apps/openci_cli/lib/src/commands/commands.dart +++ b/apps/openci_cli/lib/src/commands/commands.dart @@ -2,6 +2,7 @@ export 'create_certificate_command.dart'; export 'create_provisioning_profile.dart'; export 'list_certificates_command.dart'; export 'list_procisioning_profile.dart'; +export 'list_bundle_ids.dart'; export 'read_certificate_command.dart'; export 'read_provisioning_profile.dart'; export 'update_command.dart'; diff --git a/apps/openci_cli/lib/src/commands/list_bundle_ids.dart b/apps/openci_cli/lib/src/commands/list_bundle_ids.dart new file mode 100644 index 0000000..1914ad2 --- /dev/null +++ b/apps/openci_cli/lib/src/commands/list_bundle_ids.dart @@ -0,0 +1,53 @@ +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:openci_cli2/src/list_bundle_ids.dart'; + +class ListBundleIdsCommand extends Command { + ListBundleIdsCommand() { + argParser + ..addOption( + 'issuer-id', + help: 'App Store Connect Issuer ID', + ) + ..addOption( + 'key-id', + help: 'App Store Connect Key ID', + ) + ..addOption( + 'path-to-private-key', + help: 'Path to App Store Connect Private Key, p8 file', + ) + ..addOption( + 'filter-identifier', + help: 'Filter Bundle ID by Identifier', + ); + } + + @override + String get description => 'List Apple Developer Bundle IDs'; + + @override + String get name => 'list-bundle-ids'; + + @override + Future run() async { + final issuerId = argResults?['issuer-id'] as String?; + final keyId = argResults?['key-id'] as String?; + final pathToPrivateKey = argResults?['path-to-private-key'] as String?; + final filterIdentifier = argResults?['filter-identifier'] as String?; + + if (issuerId == null || keyId == null || pathToPrivateKey == null) { + return ExitCode.usage.code; + } + final privateKey = File(pathToPrivateKey).readAsStringSync(); + + return listBundleIds( + issuerId: issuerId, + keyId: keyId, + privateKey: privateKey, + filterIdentifier: filterIdentifier, + ); + } +} diff --git a/apps/openci_cli/lib/src/create_certificate.dart b/apps/openci_cli/lib/src/create_certificate.dart index 2c072ad..7141876 100644 --- a/apps/openci_cli/lib/src/create_certificate.dart +++ b/apps/openci_cli/lib/src/create_certificate.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; import 'package:openci_cli2/src/app_store_connect.dart'; -import 'package:openci_models/openci_models.dart'; Future createCertificate({ required String issuerId, @@ -17,6 +16,8 @@ Future createCertificate({ privateKey: privateKey, ); + Map response; + try { final csr = await generateCSR( commonName: 'OpenCI', @@ -24,31 +25,27 @@ Future createCertificate({ organizationName: 'OpenCI', ); - final response = await client.createCertificate( + response = await client.createCertificate( csrContent: csr.csrContent, certificateType: certificateType, ); - final responseWithKey = { - ...response, - 'key': csr.privateKey, - }; - - final result = SessionResult( - stdout: json.encode(responseWithKey), - stderr: '', - exitCode: 0, - ); + if (response['statusCode'] == 201 || response['statusCode'] == 200) { + final base64Key = base64Encode(utf8.encode(csr.privateKey)); + response = { + ...response, + 'key': base64Key, + }; + } - stdout.write(json.encode(result.toJson())); + stdout.write(jsonEncode(response)); return ExitCode.success.code; } catch (e) { - final result = SessionResult( - stdout: '', - stderr: e.toString(), - exitCode: 1, - ); - stdout.write(json.encode(result.toJson())); + final result = { + 'stderr': e.toString(), + 'exitCode': 1, + }; + stdout.write(jsonEncode(result)); return 1; } finally { client.dispose(); diff --git a/apps/openci_cli/lib/src/create_provisioning_profile.dart b/apps/openci_cli/lib/src/create_provisioning_profile.dart index 0ba611f..3b3ee3f 100644 --- a/apps/openci_cli/lib/src/create_provisioning_profile.dart +++ b/apps/openci_cli/lib/src/create_provisioning_profile.dart @@ -29,21 +29,14 @@ Future createProvisioningProfile({ certificateId: certificateId, ); - final result = SessionResult( - stdout: json.encode(response), - stderr: '', - exitCode: 0, - ); - - stdout.write(json.encode(result.toJson())); + stdout.write(jsonEncode(response)); return ExitCode.success.code; } catch (e) { - final result = SessionResult( - stdout: '', - stderr: e.toString(), - exitCode: 1, - ); - stdout.write(json.encode(result.toJson())); + final result = { + 'stderr': e.toString(), + 'exitCode': 1, + }; + stdout.write(jsonEncode(result)); return 1; } finally { client.dispose(); diff --git a/apps/openci_cli/lib/src/list_bundle_ids.dart b/apps/openci_cli/lib/src/list_bundle_ids.dart new file mode 100644 index 0000000..8bc8e0a --- /dev/null +++ b/apps/openci_cli/lib/src/list_bundle_ids.dart @@ -0,0 +1,32 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:mason_logger/mason_logger.dart'; +import 'package:openci_cli2/src/app_store_connect.dart'; + +Future listBundleIds({ + required String issuerId, + required String keyId, + required String privateKey, + String? filterIdentifier, +}) async { + final client = AppStoreConnectClient( + issuerId: issuerId, + keyId: keyId, + privateKey: privateKey, + ); + + try { + final response = await client.listBundleIds( + filterIdentifier: filterIdentifier, + ); + + stdout.write(jsonEncode(response)); + return ExitCode.success.code; + } catch (e) { + stdout.write(e.toString()); + return 1; + } finally { + client.dispose(); + } +} diff --git a/apps/openci_cli/lib/src/list_certificates.dart b/apps/openci_cli/lib/src/list_certificates.dart index 09e8560..902dc16 100644 --- a/apps/openci_cli/lib/src/list_certificates.dart +++ b/apps/openci_cli/lib/src/list_certificates.dart @@ -26,12 +26,12 @@ Future listCertificates({ ); final result = SessionResult( - stdout: json.encode(response), + stdout: jsonEncode(response), stderr: '', exitCode: 0, ); - stdout.write(json.encode(result.toJson())); + stdout.write(result.toJson()); return ExitCode.success.code; } catch (e) { final result = SessionResult( @@ -39,7 +39,7 @@ Future listCertificates({ stderr: e.toString(), exitCode: 1, ); - stdout.write(json.encode(result.toJson())); + stdout.write(result.toJson()); return 1; } finally { client.dispose(); diff --git a/apps/openci_cli/lib/src/list_provisioning_profiles.dart b/apps/openci_cli/lib/src/list_provisioning_profiles.dart index 93b02d6..04655ed 100644 --- a/apps/openci_cli/lib/src/list_provisioning_profiles.dart +++ b/apps/openci_cli/lib/src/list_provisioning_profiles.dart @@ -28,12 +28,12 @@ Future listProvisioningProfiles({ ); final result = SessionResult( - stdout: json.encode(response), + stdout: jsonEncode(response), stderr: '', exitCode: 0, ); - stdout.write(json.encode(result.toJson())); + stdout.write(result.toJson()); return ExitCode.success.code; } catch (e) { final result = SessionResult( @@ -41,7 +41,7 @@ Future listProvisioningProfiles({ stderr: e.toString(), exitCode: 1, ); - stdout.write(json.encode(result.toJson())); + stdout.write(result.toJson()); return 1; } finally { client.dispose(); diff --git a/apps/openci_cli/lib/src/read_certificate.dart b/apps/openci_cli/lib/src/read_certificate.dart index 07ce54e..869f217 100644 --- a/apps/openci_cli/lib/src/read_certificate.dart +++ b/apps/openci_cli/lib/src/read_certificate.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; @@ -22,7 +21,7 @@ Future readCertificate({ certificateId: certificateId, ); - stdout.write(json.encode(response)); + stdout.write(response); return ExitCode.success.code; } catch (e) { final result = SessionResult( @@ -30,7 +29,7 @@ Future readCertificate({ stderr: e.toString(), exitCode: 1, ); - stdout.write(json.encode(result.toJson())); + stdout.write(result.toJson()); return 1; } finally { client.dispose(); diff --git a/apps/openci_cli/lib/src/read_provisioning_profile.dart b/apps/openci_cli/lib/src/read_provisioning_profile.dart index 15bac65..ba8e6dd 100644 --- a/apps/openci_cli/lib/src/read_provisioning_profile.dart +++ b/apps/openci_cli/lib/src/read_provisioning_profile.dart @@ -23,12 +23,12 @@ Future readProvisioningProfile({ ); final result = SessionResult( - stdout: json.encode(response), + stdout: jsonEncode(response), stderr: '', exitCode: 0, ); - stdout.write(json.encode(result.toJson())); + stdout.write(result.toJson()); return ExitCode.success.code; } catch (e) { final result = SessionResult( @@ -36,7 +36,7 @@ Future readProvisioningProfile({ stderr: e.toString(), exitCode: 1, ); - stdout.write(json.encode(result.toJson())); + stdout.write(result.toJson()); return 1; } finally { client.dispose(); diff --git a/apps/openci_cli/lib/src/version.dart b/apps/openci_cli/lib/src/version.dart index 67a7647..a60edf9 100644 --- a/apps/openci_cli/lib/src/version.dart +++ b/apps/openci_cli/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '0.0.1'; +const packageVersion = '0.1.6'; diff --git a/apps/openci_cli/pubspec.yaml b/apps/openci_cli/pubspec.yaml index 73ef1af..d589f35 100644 --- a/apps/openci_cli/pubspec.yaml +++ b/apps/openci_cli/pubspec.yaml @@ -1,6 +1,6 @@ name: openci_cli2 description: OpenCI CLI -version: 0.1.0 +version: 0.1.6 repository: https://github.com/open-ci-io/openci/tree/develop/apps/openci_runner homepage: https://open-ci.io diff --git a/apps/openci_cli/scripts/create-certificate.sh b/apps/openci_cli/scripts/create-certificate.sh index d3644ad..18b4001 100644 --- a/apps/openci_cli/scripts/create-certificate.sh +++ b/apps/openci_cli/scripts/create-certificate.sh @@ -1,12 +1,11 @@ #!/bin/bash +# create-certificate.sh -response=$(dart run openci_cli create-certificate \ +response=$(openci_cli2 create-certificate \ --issuer-id=a6b7e4ee-e80b-41fb-8c5b-4f63234598eb \ --key-id=TVVJSBM7TY \ --path-to-private-key="/Users/masahiroaoki/Desktop/AuthKey_TVVJSBM7TY.p8" \ --certificate-type=DISTRIBUTION) -exit_code=$? - -# レスポンスをJSONフォーマットで出力 -echo "{\"response\": \"$response\", \"exit_code\": $exit_code}" +json=$(echo "$response" | jq .) +echo "$json" diff --git a/apps/openci_cli/scripts/create-pp.sh b/apps/openci_cli/scripts/create-pp.sh new file mode 100644 index 0000000..d0202cc --- /dev/null +++ b/apps/openci_cli/scripts/create-pp.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# create-certificate.sh + +# Create provisioning profile +response=$(dart run openci_cli2 create-provisioning-profile \ + --issuer-id=a6b7e4ee-e80b-41fb-8c5b-4f63234598eb \ + --key-id=TVVJSBM7TY \ + --path-to-private-key="/Users/masahiroaoki/Desktop/AuthKey_TVVJSBM7TY.p8" \ + --certificate-id=HD4DAU37CS \ + --profile-name="OpenCI PP" \ + --profile-type="IOS_APP_STORE" \ + --bundle-id="DM9VQG9XH9") + +json=$(echo "$response" | jq .) +echo "$json" diff --git a/apps/openci_cli/scripts/list-bundle-ids.sh b/apps/openci_cli/scripts/list-bundle-ids.sh new file mode 100644 index 0000000..69a6c1c --- /dev/null +++ b/apps/openci_cli/scripts/list-bundle-ids.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +response=$(dart run openci_cli2 list-bundle-ids \ + --issuer-id=a6b7e4ee-e80b-41fb-8c5b-4f63234598eb \ + --key-id=TVVJSBM7TY \ + --path-to-private-key="/Users/masahiroaoki/Desktop/AuthKey_TVVJSBM7TY.p8" \ + --filter-identifier="io.openci.dashboard.ios") + +# レスポンスをJSONフォーマットで出力 +echo "$response" \ No newline at end of file diff --git a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart new file mode 100644 index 0000000..7facfad --- /dev/null +++ b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart @@ -0,0 +1,447 @@ +import 'dart:convert'; + +import 'package:dart_firebase_admin_plus/firestore.dart'; +import 'package:dartssh2/dartssh2.dart'; +import 'package:openci_models/openci_models.dart'; +import 'package:openci_runner/src/firebase.dart'; +import 'package:openci_runner/src/run_command.dart'; + +Future handleFlutterBuildIpa( + String logId, + SSHClient client, + WorkflowModel workflow, + BuildJob buildJob, + Firestore firestore, +) async { + // 0. Secretsの存在を確認 + final areASCSecretsUploaded = await _areASCSecretsUploaded(); + if (!areASCSecretsUploaded) { + throw Exception('ASC secrets are not uploaded'); + } + + final isValidP12FileUploaded = await _wasP12FileUploadedWithinOneYear(); + if (isValidP12FileUploaded) { + print('P12 file is uploaded within one year'); + // base64を取得し、リモートに書き込む + // keychainにimport + // PPをDL + // PPをVMに配置 + } else { + print('P12 file is not uploaded within one year'); + // p12ファイルを作成 + final p8Base64 = await _getP8Base64(firestore); + final issuerId = await _getIssuerId(firestore); + final keyId = await _getKeyId(firestore); + await _uploadP8File( + client, + p8Base64, + logId, + workflow.id, + buildJob.id, + workflow.currentWorkingDirectory, + keyId, + ); + + await runCommand( + logId: logId, + client: client, + command: 'flutter build ipa', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + + // await runCommand( + // logId: logId, + // client: client, + // command: 'openci_cli2 update', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + // final result = await runCommand( + // logId: logId, + // client: client, + // command: + // 'openci_cli2 create-certificate --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --certificate-type=DISTRIBUTION', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // final resultJson = jsonDecode(result.stdout); + // final resultCode = resultJson['statusCode']; + + // if (resultCode != 201) { + // throw Exception('Failed to create certificate'); + // } + // final keyBase64 = resultJson['key']; + + // await runCommand( + // logId: logId, + // client: client, + // command: + // 'echo $keyBase64 | base64 -d > /Users/admin/Desktop/certificate.key', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // final csrBase64 = + // resultJson['body']['data']['attributes']['certificateContent']; + + // final certificateId = resultJson['body']['data']['id']; + + // await runCommand( + // logId: logId, + // client: client, + // command: + // 'echo $csrBase64 | base64 -d > /Users/admin/Desktop/certificate.cer', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // const p12Path = '/Users/admin/Desktop/certificate.p12'; + // const p12Password = '12345678'; + // const keychainPassword = '12345678'; + + // await runCommand( + // logId: logId, + // client: client, + // command: + // 'openssl pkcs12 -export -in /Users/admin/Desktop/certificate.cer -inkey /Users/admin/Desktop/certificate.key -out $p12Path -name "Certificate Name" -passout pass:$p12Password', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // // Save p12 file to Firestore (as a secret) + + // const keychainPath = + // '/Users/admin/Library/Keychains/app-signing.keychain-db'; + + // await runCommand( + // logId: logId, + // client: client, + // command: 'security create-keychain -p "$keychainPassword" $keychainPath', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // await runCommand( + // logId: logId, + // client: client, + // command: 'security set-keychain-settings -lut 21600 $keychainPath', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // await runCommand( + // logId: logId, + // client: client, + // command: 'security unlock-keychain -p "$keychainPassword" $keychainPath', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // await runCommand( + // logId: logId, + // client: client, + // command: + // 'security import $p12Path -P $p12Password -A -t cert -f pkcs12 -k $keychainPath', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // await runCommand( + // logId: logId, + // client: client, + // command: + // 'security set-key-partition-list -S apple-tool:,apple: -k $keychainPassword $keychainPath', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // await runCommand( + // logId: logId, + // client: client, + // command: 'security list-keychain -d user -s $keychainPath', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // // await runCommand( + // // logId: logId, + // // client: client, + // // command: + // // 'openci_cli2 create-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --certificate-id=$certificateId --profile-name="OpenCI PP" --profile-type="IOS_APP_STORE" --bundle-id="io.openci.dashboard.ios"', + // // currentWorkingDirectory: workflow.currentWorkingDirectory, + // // jobId: buildJob.id, + // // ); + + // final bundleIdRes = await runCommand( + // logId: logId, + // client: client, + // command: + // r"grep -m1 PRODUCT_BUNDLE_IDENTIFIER ios/Runner.xcodeproj/project.pbxproj | sed -E 's/.*= ([^;]+);.*/\1/'", + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + // print('bundleIdRes: $bundleIdRes'); + + // final bundleId = bundleIdRes.stdout.trim(); + + // final bundleIdsRes = await runCommand( + // logId: logId, + // client: client, + // command: + // 'openci_cli2 list-bundle-ids --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --filter-identifier="$bundleId"', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + // print('bundleIdsRes: $bundleIdsRes'); + + // final bundleIdsJson = jsonDecode(bundleIdsRes.stdout); + // final ascBundleId = bundleIdsJson['body']['data'][0]['id']; + // final teamId = + // bundleIdsJson['body']['data'][0]['attributes']['seedId'] as String; + + // const ppName = 'OpenCI PP'; + + // final ppRes = await runCommand( + // logId: logId, + // client: client, + // command: + // 'openci_cli2 create-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --certificate-id=$certificateId --profile-name="$ppName" --profile-type="IOS_APP_STORE" --bundle-id=$ascBundleId', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // print('ppRes: $ppRes'); + // final ppJson = jsonDecode(ppRes.stdout); + // final ppBase64 = ppJson['body']['data']['attributes']['profileContent']; + // final ppUuid = ppJson['body']['data']['attributes']['uuid']; + + // await runCommand( + // logId: logId, + // client: client, + // command: + // r'mkdir -p /Users/admin/Library/MobileDevice/Provisioning\ Profiles', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // // await runCommand( + // // logId: logId, + // // client: client, + // // command: + // // r'mkdir -p /Users/admin/Library/Developer/Xcode/UserData/Provisioning\ Profiles', + // // currentWorkingDirectory: workflow.currentWorkingDirectory, + // // jobId: buildJob.id, + // // ); + + // await runCommand( + // logId: logId, + // client: client, + // command: 'pwd', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // await runCommand( + // logId: logId, + // client: client, + // command: + // 'echo $ppBase64 | base64 -d > "/Users/admin/Library/MobileDevice/Provisioning Profiles/$ppUuid.mobileprovision"', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // final exportOptionsPlistBase64 = createPlistXmlAsBase64( + // teamId: teamId, + // bundleId: bundleId, + // profileName: ppName, + // ); + + // await runCommand( + // logId: logId, + // client: client, + // command: + // 'echo $exportOptionsPlistBase64 | base64 -d > "/Users/admin/${workflow.currentWorkingDirectory}/ios/ExportOptions.plist"', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + // await runCommand( + // logId: logId, + // client: client, + // command: + // 'flutter build ipa --export-options-plist="/Users/admin/${workflow.currentWorkingDirectory}/ios/ExportOptions.plist"', + // currentWorkingDirectory: workflow.currentWorkingDirectory, + // jobId: buildJob.id, + // ); + + await Future.delayed(const Duration(hours: 10)); + } + + // 1. ビルド +} + +String decodeBase64ToOriginalString(String base64String) { + if (base64String.trim().isEmpty) { + return ''; + } + return utf8.decode(base64Decode(base64String)); +} + +Future _getIssuerId(Firestore firestore) async { + final qs = firestore + .collection(secretsCollectionPath) + .where('key', WhereFilter.equal, 'OPENCI_ASC_ISSUER_ID'); + final docs = await qs.get(); + final data = docs.docs.first.data(); + final issuerId = data['value'] as String?; + if (issuerId == null) { + throw Exception('OPENCI_ASC_ISSUER_ID is not found'); + } + return issuerId; +} + +Future _getKeyId(Firestore firestore) async { + final qs = firestore + .collection(secretsCollectionPath) + .where('key', WhereFilter.equal, 'OPENCI_ASC_KEY_ID'); + final docs = await qs.get(); + final data = docs.docs.first.data(); + final keyId = data['value'] as String?; + if (keyId == null) { + throw Exception('OPENCI_ASC_KEY_ID is not found'); + } + return keyId; +} + +Future _getP8Base64(Firestore firestore) async { + final qs = firestore + .collection(secretsCollectionPath) + .where('key', WhereFilter.equal, 'OPENCI_ASC_KEY_BASE64'); + final docs = await qs.get(); + final data = docs.docs.first.data(); + final keyBase64 = data['value'] as String?; + if (keyBase64 == null) { + throw Exception('OPENCI_ASC_KEY_BASE64 is not found'); + } + return keyBase64; +} + +Future _uploadP8File( + SSHClient client, + String p8Base64, + String logId, + String workflowId, + String buildJobId, + String currentWorkingDirectory, + String keyId, +) async { + // リモートに書き込む + // keychainにimport + // PPをDL + // PPをVMに配置 + await runCommand( + logId: logId, + client: client, + command: + 'echo $p8Base64 | base64 -d > /Users/admin/Desktop/AuthKey_$keyId.p8', + currentWorkingDirectory: currentWorkingDirectory, + jobId: buildJobId, + ); +} + +Future _wasP12FileUploadedWithinOneYear() async { + final firestore = firestoreSignal.value!; + final querySnapshot = await firestore + .collection(secretsCollectionPath) + .where('key', WhereFilter.equal, 'OPENCI_ASC_P12_FILE') + .get(); + + if (querySnapshot.docs.isEmpty) { + return false; + } + + final data = querySnapshot.docs.first.data(); + final updatedAtInt = int.parse(data['updatedAt'].toString()); + + final updatedAtDateTime = + DateTime.fromMillisecondsSinceEpoch(updatedAtInt * 1000); + final now = DateTime.now(); + final aYearAgo = now.subtract(const Duration(days: 365)); + + if (updatedAtDateTime.isBefore(aYearAgo)) { + return false; + } + + return true; +} + +Future _areASCSecretsUploaded() async { + final firestore = firestoreSignal.value!; + final isIssuerIdUploaded = await _isIssuerIdUploaded(firestore); + final isKeyIdUploaded = await _isKeyIdUploaded(firestore); + final isKeyBase64Uploaded = await _isKeyBase64Uploaded(firestore); + + return isIssuerIdUploaded && isKeyIdUploaded && isKeyBase64Uploaded; +} + +Future _isIssuerIdUploaded(Firestore firestore) async { + final qs = firestore + .collection(secretsCollectionPath) + .where('key', WhereFilter.equal, 'OPENCI_ASC_ISSUER_ID'); + final docs = await qs.get(); + return docs.docs.isNotEmpty; +} + +Future _isKeyIdUploaded(Firestore firestore) async { + final qs = firestore + .collection(secretsCollectionPath) + .where('key', WhereFilter.equal, 'OPENCI_ASC_KEY_ID'); + final docs = await qs.get(); + return docs.docs.isNotEmpty; +} + +Future _isKeyBase64Uploaded(Firestore firestore) async { + final qs = firestore + .collection(secretsCollectionPath) + .where('key', WhereFilter.equal, 'OPENCI_ASC_KEY_BASE64'); + final docs = await qs.get(); + return docs.docs.isNotEmpty; +} + +String createPlistXmlAsBase64({ + required String teamId, + required String bundleId, + required String profileName, +}) { + final xmlContent = ''' + + + + + method + app-store + provisioningProfiles + + $bundleId + $profileName + + signingCertificate + Apple Distribution + signingStyle + manual + teamID + $teamId + + +'''; + + if (xmlContent.trim().isEmpty) { + return ''; + } + + final bytes = utf8.encode(xmlContent); + final base64String = base64Encode(bytes); + return base64String; +} diff --git a/apps/openci_runner/lib/src/run_command.dart b/apps/openci_runner/lib/src/run_command.dart index 0f1a3d0..541c291 100644 --- a/apps/openci_runner/lib/src/run_command.dart +++ b/apps/openci_runner/lib/src/run_command.dart @@ -6,7 +6,7 @@ import 'package:openci_runner/src/firebase.dart'; import 'package:openci_runner/src/service/dartssh2/dartssh2_service.dart'; import 'package:openci_runner/src/service/logger_service.dart'; -Future runCommand({ +Future runCommand({ required SSHClient client, required String command, required String? currentWorkingDirectory, @@ -31,7 +31,7 @@ Future runCommand({ } try { - await dartSSH2Service.executeCommand( + return await dartSSH2Service.executeCommand( client: client, command: command, currentWorkingDirectory: currentWorkingDirectory, diff --git a/apps/openci_runner/lib/src/run_multi_commands.dart b/apps/openci_runner/lib/src/run_multi_commands.dart index 0ffd5f4..a54a42e 100644 --- a/apps/openci_runner/lib/src/run_multi_commands.dart +++ b/apps/openci_runner/lib/src/run_multi_commands.dart @@ -6,6 +6,7 @@ import 'package:openci_models/openci_models.dart'; import 'package:openci_runner/src/env.dart'; import 'package:openci_runner/src/features/get_workflow.dart'; import 'package:openci_runner/src/features/update_build_status.dart'; +import 'package:openci_runner/src/handle_flutter_builld_ipa.dart'; import 'package:openci_runner/src/log.dart'; import 'package:openci_runner/src/run_command.dart'; import 'package:openci_runner/src/secrets.dart'; @@ -36,6 +37,16 @@ Future runMultiCommands( secrets: secrets, ); + if (processedCommand.contains('flutter build ipa')) { + await handleFlutterBuildIpa( + logId, + client, + workflow, + buildJob, + firestore, + ); + } + await runCommand( logId: logId, client: client, diff --git a/melos.yaml b/melos.yaml index 522def3..344a262 100644 --- a/melos.yaml +++ b/melos.yaml @@ -5,7 +5,7 @@ packages: - packages/** scripts: - cli: + runner: run: cd apps/openci_runner && dart run openci_runner runner --pem-path ./github_apps.pem --service-account-path ./service_account.json --sentry-dsn https://e66e5ae0245821f62a4b235d829dc3fb@o4507005123166208.ingest.us.sentry.io/4507005125197824 ff: @@ -14,4 +14,3 @@ scripts: dashboard-runner: run: cd apps/dashboard && dart run build_runner watch -d - From 1789f66513287a2664e40a78ab76db56b40c55ee Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sat, 1 Feb 2025 20:00:37 +0900 Subject: [PATCH 06/22] Implement full iOS build and signing workflow for Flutter app --- .../lib/src/handle_flutter_builld_ipa.dart | 358 +++++++++--------- 1 file changed, 175 insertions(+), 183 deletions(-) diff --git a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart index 7facfad..b086574 100644 --- a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart +++ b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart @@ -45,236 +45,228 @@ Future handleFlutterBuildIpa( await runCommand( logId: logId, client: client, - command: 'flutter build ipa', + command: 'openci_cli2 update', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + final result = await runCommand( + logId: logId, + client: client, + command: + 'openci_cli2 create-certificate --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --certificate-type=DISTRIBUTION', currentWorkingDirectory: workflow.currentWorkingDirectory, jobId: buildJob.id, ); - // await runCommand( - // logId: logId, - // client: client, - // command: 'openci_cli2 update', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); - // final result = await runCommand( - // logId: logId, - // client: client, - // command: - // 'openci_cli2 create-certificate --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --certificate-type=DISTRIBUTION', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + final resultJson = jsonDecode(result.stdout); + final resultCode = resultJson['statusCode']; - // final resultJson = jsonDecode(result.stdout); - // final resultCode = resultJson['statusCode']; + if (resultCode != 201) { + throw Exception('Failed to create certificate'); + } + final keyBase64 = resultJson['key']; - // if (resultCode != 201) { - // throw Exception('Failed to create certificate'); - // } - // final keyBase64 = resultJson['key']; + await runCommand( + logId: logId, + client: client, + command: + 'echo $keyBase64 | base64 -d > /Users/admin/Desktop/certificate.key', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // await runCommand( - // logId: logId, - // client: client, - // command: - // 'echo $keyBase64 | base64 -d > /Users/admin/Desktop/certificate.key', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + final csrBase64 = + resultJson['body']['data']['attributes']['certificateContent']; - // final csrBase64 = - // resultJson['body']['data']['attributes']['certificateContent']; + final certificateId = resultJson['body']['data']['id']; - // final certificateId = resultJson['body']['data']['id']; + await runCommand( + logId: logId, + client: client, + command: + 'echo $csrBase64 | base64 -d > /Users/admin/Desktop/certificate.cer', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // await runCommand( - // logId: logId, - // client: client, - // command: - // 'echo $csrBase64 | base64 -d > /Users/admin/Desktop/certificate.cer', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + const p12Path = '/Users/admin/Desktop/certificate.p12'; + const p12Password = '12345678'; + const keychainPassword = '12345678'; - // const p12Path = '/Users/admin/Desktop/certificate.p12'; - // const p12Password = '12345678'; - // const keychainPassword = '12345678'; + await runCommand( + logId: logId, + client: client, + command: + 'openssl pkcs12 -export -in /Users/admin/Desktop/certificate.cer -inkey /Users/admin/Desktop/certificate.key -out $p12Path -name "Certificate Name" -passout pass:$p12Password', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // await runCommand( - // logId: logId, - // client: client, - // command: - // 'openssl pkcs12 -export -in /Users/admin/Desktop/certificate.cer -inkey /Users/admin/Desktop/certificate.key -out $p12Path -name "Certificate Name" -passout pass:$p12Password', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + // Save p12 file to Firestore (as a secret) - // // Save p12 file to Firestore (as a secret) + const keychainPath = + '/Users/admin/Library/Keychains/app-signing.keychain-db'; - // const keychainPath = - // '/Users/admin/Library/Keychains/app-signing.keychain-db'; + await runCommand( + logId: logId, + client: client, + command: 'security create-keychain -p "$keychainPassword" $keychainPath', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // await runCommand( - // logId: logId, - // client: client, - // command: 'security create-keychain -p "$keychainPassword" $keychainPath', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + await runCommand( + logId: logId, + client: client, + command: 'security set-keychain-settings -lut 21600 $keychainPath', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // await runCommand( - // logId: logId, - // client: client, - // command: 'security set-keychain-settings -lut 21600 $keychainPath', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + await runCommand( + logId: logId, + client: client, + command: 'security unlock-keychain -p "$keychainPassword" $keychainPath', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // await runCommand( - // logId: logId, - // client: client, - // command: 'security unlock-keychain -p "$keychainPassword" $keychainPath', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + await runCommand( + logId: logId, + client: client, + command: + 'security import $p12Path -P $p12Password -A -t cert -f pkcs12 -k $keychainPath', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // await runCommand( - // logId: logId, - // client: client, - // command: - // 'security import $p12Path -P $p12Password -A -t cert -f pkcs12 -k $keychainPath', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + await runCommand( + logId: logId, + client: client, + command: + 'security set-key-partition-list -S apple-tool:,apple: -k $keychainPassword $keychainPath', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // await runCommand( - // logId: logId, - // client: client, - // command: - // 'security set-key-partition-list -S apple-tool:,apple: -k $keychainPassword $keychainPath', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + await runCommand( + logId: logId, + client: client, + command: 'security list-keychain -d user -s $keychainPath', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); // await runCommand( // logId: logId, // client: client, - // command: 'security list-keychain -d user -s $keychainPath', + // command: + // 'openci_cli2 create-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --certificate-id=$certificateId --profile-name="OpenCI PP" --profile-type="IOS_APP_STORE" --bundle-id="io.openci.dashboard.ios"', // currentWorkingDirectory: workflow.currentWorkingDirectory, // jobId: buildJob.id, // ); - // // await runCommand( - // // logId: logId, - // // client: client, - // // command: - // // 'openci_cli2 create-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --certificate-id=$certificateId --profile-name="OpenCI PP" --profile-type="IOS_APP_STORE" --bundle-id="io.openci.dashboard.ios"', - // // currentWorkingDirectory: workflow.currentWorkingDirectory, - // // jobId: buildJob.id, - // // ); + final bundleIdRes = await runCommand( + logId: logId, + client: client, + command: + r"grep -m1 PRODUCT_BUNDLE_IDENTIFIER ios/Runner.xcodeproj/project.pbxproj | sed -E 's/.*= ([^;]+);.*/\1/'", + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + print('bundleIdRes: $bundleIdRes'); - // final bundleIdRes = await runCommand( - // logId: logId, - // client: client, - // command: - // r"grep -m1 PRODUCT_BUNDLE_IDENTIFIER ios/Runner.xcodeproj/project.pbxproj | sed -E 's/.*= ([^;]+);.*/\1/'", - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); - // print('bundleIdRes: $bundleIdRes'); + final bundleId = bundleIdRes.stdout.trim(); - // final bundleId = bundleIdRes.stdout.trim(); + final bundleIdsRes = await runCommand( + logId: logId, + client: client, + command: + 'openci_cli2 list-bundle-ids --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --filter-identifier="$bundleId"', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + print('bundleIdsRes: $bundleIdsRes'); - // final bundleIdsRes = await runCommand( - // logId: logId, - // client: client, - // command: - // 'openci_cli2 list-bundle-ids --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --filter-identifier="$bundleId"', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); - // print('bundleIdsRes: $bundleIdsRes'); + final bundleIdsJson = jsonDecode(bundleIdsRes.stdout); + final ascBundleId = bundleIdsJson['body']['data'][0]['id']; + final teamId = + bundleIdsJson['body']['data'][0]['attributes']['seedId'] as String; - // final bundleIdsJson = jsonDecode(bundleIdsRes.stdout); - // final ascBundleId = bundleIdsJson['body']['data'][0]['id']; - // final teamId = - // bundleIdsJson['body']['data'][0]['attributes']['seedId'] as String; + const ppName = 'OpenCI PP'; - // const ppName = 'OpenCI PP'; + final ppRes = await runCommand( + logId: logId, + client: client, + command: + 'openci_cli2 create-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --certificate-id=$certificateId --profile-name="$ppName" --profile-type="IOS_APP_STORE" --bundle-id=$ascBundleId', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // final ppRes = await runCommand( - // logId: logId, - // client: client, - // command: - // 'openci_cli2 create-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --certificate-id=$certificateId --profile-name="$ppName" --profile-type="IOS_APP_STORE" --bundle-id=$ascBundleId', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + print('ppRes: $ppRes'); + final ppJson = jsonDecode(ppRes.stdout); + final ppBase64 = ppJson['body']['data']['attributes']['profileContent']; + final ppUuid = ppJson['body']['data']['attributes']['uuid']; - // print('ppRes: $ppRes'); - // final ppJson = jsonDecode(ppRes.stdout); - // final ppBase64 = ppJson['body']['data']['attributes']['profileContent']; - // final ppUuid = ppJson['body']['data']['attributes']['uuid']; + await runCommand( + logId: logId, + client: client, + command: + r'mkdir -p /Users/admin/Library/MobileDevice/Provisioning\ Profiles', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); // await runCommand( // logId: logId, // client: client, // command: - // r'mkdir -p /Users/admin/Library/MobileDevice/Provisioning\ Profiles', + // r'mkdir -p /Users/admin/Library/Developer/Xcode/UserData/Provisioning\ Profiles', // currentWorkingDirectory: workflow.currentWorkingDirectory, // jobId: buildJob.id, // ); - // // await runCommand( - // // logId: logId, - // // client: client, - // // command: - // // r'mkdir -p /Users/admin/Library/Developer/Xcode/UserData/Provisioning\ Profiles', - // // currentWorkingDirectory: workflow.currentWorkingDirectory, - // // jobId: buildJob.id, - // // ); - - // await runCommand( - // logId: logId, - // client: client, - // command: 'pwd', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + await runCommand( + logId: logId, + client: client, + command: 'pwd', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // await runCommand( - // logId: logId, - // client: client, - // command: - // 'echo $ppBase64 | base64 -d > "/Users/admin/Library/MobileDevice/Provisioning Profiles/$ppUuid.mobileprovision"', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + await runCommand( + logId: logId, + client: client, + command: + 'echo $ppBase64 | base64 -d > "/Users/admin/Library/MobileDevice/Provisioning Profiles/$ppUuid.mobileprovision"', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // final exportOptionsPlistBase64 = createPlistXmlAsBase64( - // teamId: teamId, - // bundleId: bundleId, - // profileName: ppName, - // ); + final exportOptionsPlistBase64 = createPlistXmlAsBase64( + teamId: teamId, + bundleId: bundleId, + profileName: ppName, + ); - // await runCommand( - // logId: logId, - // client: client, - // command: - // 'echo $exportOptionsPlistBase64 | base64 -d > "/Users/admin/${workflow.currentWorkingDirectory}/ios/ExportOptions.plist"', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + await runCommand( + logId: logId, + client: client, + command: + 'echo $exportOptionsPlistBase64 | base64 -d > "/Users/admin/${workflow.currentWorkingDirectory}/ios/ExportOptions.plist"', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // await runCommand( - // logId: logId, - // client: client, - // command: - // 'flutter build ipa --export-options-plist="/Users/admin/${workflow.currentWorkingDirectory}/ios/ExportOptions.plist"', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + await runCommand( + logId: logId, + client: client, + command: + 'flutter build ipa --export-options-plist="/Users/admin/${workflow.currentWorkingDirectory}/ios/ExportOptions.plist"', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); await Future.delayed(const Duration(hours: 10)); } From 6b746648faf03c42228107613a80055fee073d0c Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 10:18:18 +0900 Subject: [PATCH 07/22] Enhance iOS build workflow with dynamic provisioning and signing configuration - Update iOS project configuration to use automatic code signing - Implement dynamic auth key path generation for certificates - Add Ruby script to modify Xcode project build settings - Improve App Store Connect upload process - Remove unused imports and commented code --- apps/.DS_Store | Bin 14340 -> 12292 bytes .../ios/Runner.xcodeproj/project.pbxproj | 21 +--- apps/dashboard/xcode_proj.rb | 15 +++ .../lib/src/create_provisioning_profile.dart | 1 - .../lib/src/handle_flutter_builld_ipa.dart | 108 +++++++++++++++--- 5 files changed, 113 insertions(+), 32 deletions(-) create mode 100755 apps/dashboard/xcode_proj.rb diff --git a/apps/.DS_Store b/apps/.DS_Store index 138489310889c3ef304db57d74dc412382362e7c..30d8d650e0a5569853b95433fad4eac00fa8da62 100644 GIT binary patch delta 153 zcmZoEXh~3DU|?W$DortDV9)?EIe-{M3-ADmHU=<}*!o9z);+=%#uL`hCc<|B=chDD8h zNt2Qq@p{(Q1jF8dUtQ!0sUm87$E?&^bJL8Ay(NCi85(2l~Xk}Q;62nZrVr3=rmoS zt8|~9&>QNfPXH1iBMHecAQM?IBO3)M#Wa**I?AySwWz}~G{c1za7WRC5W-l42%^}6 zZP<<-=)`^;#36LyFuKu$lemDJxP{xeg9qrtbG*QNd}YMIl9+*wVPjbqGqW6KVI{1T z*;ys4V~uRFesTsq($b+g6qu9hiDYt6gwb0s0A#P_C;H)fdfxz?SR5#>duDY8&j;s&?5)l@1=3iL%8pCJox zWrV)WC`{Y189T8Hd$1QB!u5cVjkzAdQ5?f@oWWU~!+Bi7Wn9HI z+z`?e@8L0C;w|3&1UUakKz!5&CVW9*=je=6`RPv+$H(i>ZOJ0e`JqYg_Qz={)>_W= i7M>bQ)O^;C{5p@%8TLjz<<782{Y@+W7yD;o1K$8V3l_5g diff --git a/apps/dashboard/ios/Runner.xcodeproj/project.pbxproj b/apps/dashboard/ios/Runner.xcodeproj/project.pbxproj index 1a30241..9edc2a6 100644 --- a/apps/dashboard/ios/Runner.xcodeproj/project.pbxproj +++ b/apps/dashboard/ios/Runner.xcodeproj/project.pbxproj @@ -470,11 +470,9 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = T4W85DPBVX; + DEVELOPMENT_TEAM = T4W85DPBVX; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -484,7 +482,6 @@ PRODUCT_BUNDLE_IDENTIFIER = io.openci.dashboard.ios; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "for openci dashboard ios"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -659,11 +656,9 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = T4W85DPBVX; + DEVELOPMENT_TEAM = T4W85DPBVX; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -673,7 +668,6 @@ PRODUCT_BUNDLE_IDENTIFIER = io.openci.dashboard.ios; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "for openci dashboard ios"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -688,11 +682,9 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = T4W85DPBVX; + DEVELOPMENT_TEAM = T4W85DPBVX; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -702,7 +694,6 @@ PRODUCT_BUNDLE_IDENTIFIER = io.openci.dashboard.ios; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "for openci dashboard ios"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/apps/dashboard/xcode_proj.rb b/apps/dashboard/xcode_proj.rb new file mode 100755 index 0000000..dd75217 --- /dev/null +++ b/apps/dashboard/xcode_proj.rb @@ -0,0 +1,15 @@ +require 'xcodeproj' + +# プロジェクトのパス(ここでは ios/Runner.xcodeproj を使用) +project_path = 'ios/Runner.xcodeproj' +project = Xcodeproj::Project.open(project_path) + +# 最初のターゲットの全ビルド設定を変更 +target = project.targets.first +target.build_configurations.each do |config| + config.build_settings['CODE_SIGN_STYLE'] = 'Manual' + config.build_settings['DEVELOPMENT_TEAM'] = 'T4W85DPBVX' + config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = 'OpenCI PP' +end + +project.save diff --git a/apps/openci_cli/lib/src/create_provisioning_profile.dart b/apps/openci_cli/lib/src/create_provisioning_profile.dart index 3b3ee3f..096b583 100644 --- a/apps/openci_cli/lib/src/create_provisioning_profile.dart +++ b/apps/openci_cli/lib/src/create_provisioning_profile.dart @@ -4,7 +4,6 @@ import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; import 'package:openci_cli2/src/app_store_connect.dart'; import 'package:openci_cli2/src/profile_type.dart'; -import 'package:openci_models/openci_models.dart'; Future createProvisioningProfile({ required String issuerId, diff --git a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart index b086574..2d42605 100644 --- a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart +++ b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart @@ -49,11 +49,12 @@ Future handleFlutterBuildIpa( currentWorkingDirectory: workflow.currentWorkingDirectory, jobId: buildJob.id, ); + final result = await runCommand( logId: logId, client: client, command: - 'openci_cli2 create-certificate --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --certificate-type=DISTRIBUTION', + 'openci_cli2 create-certificate --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: workflow.currentWorkingDirectory, keyId: keyId)}" --certificate-type=DISTRIBUTION', currentWorkingDirectory: workflow.currentWorkingDirectory, jobId: buildJob.id, ); @@ -182,7 +183,7 @@ Future handleFlutterBuildIpa( logId: logId, client: client, command: - 'openci_cli2 list-bundle-ids --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --filter-identifier="$bundleId"', + 'openci_cli2 list-bundle-ids --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: workflow.currentWorkingDirectory, keyId: keyId)}" --filter-identifier="$bundleId"', currentWorkingDirectory: workflow.currentWorkingDirectory, jobId: buildJob.id, ); @@ -199,7 +200,7 @@ Future handleFlutterBuildIpa( logId: logId, client: client, command: - 'openci_cli2 create-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --certificate-id=$certificateId --profile-name="$ppName" --profile-type="IOS_APP_STORE" --bundle-id=$ascBundleId', + 'openci_cli2 create-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: workflow.currentWorkingDirectory, keyId: keyId)}" --certificate-id=$certificateId --profile-name="$ppName" --profile-type="IOS_APP_STORE" --bundle-id=$ascBundleId', currentWorkingDirectory: workflow.currentWorkingDirectory, jobId: buildJob.id, ); @@ -218,15 +219,6 @@ Future handleFlutterBuildIpa( jobId: buildJob.id, ); - // await runCommand( - // logId: logId, - // client: client, - // command: - // r'mkdir -p /Users/admin/Library/Developer/Xcode/UserData/Provisioning\ Profiles', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); - await runCommand( logId: logId, client: client, @@ -259,6 +251,37 @@ Future handleFlutterBuildIpa( jobId: buildJob.id, ); + final rubyScriptBase64 = generateXcodeprojRubyScriptBase64( + teamId: teamId, + profileName: ppName, + ); + const rubyScriptName = 'xcode_proj.rb'; + + await runCommand( + logId: logId, + client: client, + command: + 'echo $rubyScriptBase64 | base64 -d > "/Users/admin/${workflow.currentWorkingDirectory}/$rubyScriptName"', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + + await runCommand( + logId: logId, + client: client, + command: 'ruby $rubyScriptName', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + + await runCommand( + logId: logId, + client: client, + command: '', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + await runCommand( logId: logId, client: client, @@ -268,10 +291,17 @@ Future handleFlutterBuildIpa( jobId: buildJob.id, ); - await Future.delayed(const Duration(hours: 10)); - } + await runCommand( + logId: logId, + client: client, + command: + 'xcrun altool --upload-app --type ios -f build/ios/ipa/*.ipa --apiKey $keyId --apiIssuer $issuerId', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // 1. ビルド + await Future.delayed(const Duration(hours: 10)); + } } String decodeBase64ToOriginalString(String base64String) { @@ -320,6 +350,13 @@ Future _getP8Base64(Firestore firestore) async { return keyBase64; } +String authKeyPath({ + required String cwd, + required String keyId, +}) { + return '/Users/admin/$cwd/private_keys/AuthKey_$keyId.p8'; +} + Future _uploadP8File( SSHClient client, String p8Base64, @@ -333,11 +370,19 @@ Future _uploadP8File( // keychainにimport // PPをDL // PPをVMに配置 + await runCommand( + client: client, + command: 'mkdir -p /Users/admin/$currentWorkingDirectory/private_keys', + currentWorkingDirectory: currentWorkingDirectory, + jobId: buildJobId, + logId: logId, + ); + await runCommand( logId: logId, client: client, command: - 'echo $p8Base64 | base64 -d > /Users/admin/Desktop/AuthKey_$keyId.p8', + 'echo $p8Base64 | base64 -d > ${authKeyPath(cwd: currentWorkingDirectory, keyId: keyId)}', currentWorkingDirectory: currentWorkingDirectory, jobId: buildJobId, ); @@ -437,3 +482,34 @@ String createPlistXmlAsBase64({ final base64String = base64Encode(bytes); return base64String; } + +/// Returns a base64 encoded string of the Ruby script. +/// This Ruby script modifies the Xcode project build settings. +String generateXcodeprojRubyScriptBase64({ + required String teamId, + required String profileName, +}) { + final rubyScript = ''' +require 'xcodeproj' + +# Project path (using ios/Runner.xcodeproj) +project_path = 'ios/Runner.xcodeproj' +project = Xcodeproj::Project.open(project_path) + +# Modify build settings of each target in the project +project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['CODE_SIGN_STYLE'] = 'Manual' + config.build_settings['DEVELOPMENT_TEAM'] = '$teamId' + config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = '$profileName' + config.build_settings["PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]"] = '$profileName' + end +end + +project.save +'''; + + if (rubyScript.trim().isEmpty) return ''; + + return base64Encode(utf8.encode(rubyScript)); +} From 814027762a8fd6e665a6148b2e5f7ace7f0a53ed Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 10:30:14 +0900 Subject: [PATCH 08/22] Add iOS entitlements file for development environment - Create Runner.entitlements file - Set APS environment to development - Support iOS push notification configuration --- apps/dashboard/ios/Runner/Runner.entitlements | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 apps/dashboard/ios/Runner/Runner.entitlements diff --git a/apps/dashboard/ios/Runner/Runner.entitlements b/apps/dashboard/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..903def2 --- /dev/null +++ b/apps/dashboard/ios/Runner/Runner.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + From 56f0428137bcebe0e9a712e2c63e70314f9f1c7f Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 11:10:29 +0900 Subject: [PATCH 09/22] Update --- .../ios/Runner.xcodeproj/project.pbxproj | 5 + apps/dashboard/ios/Runner/Info.plist | 94 ++++++++++--------- apps/dashboard/pubspec.yaml | 1 + 3 files changed, 54 insertions(+), 46 deletions(-) diff --git a/apps/dashboard/ios/Runner.xcodeproj/project.pbxproj b/apps/dashboard/ios/Runner.xcodeproj/project.pbxproj index 9edc2a6..3a73433 100644 --- a/apps/dashboard/ios/Runner.xcodeproj/project.pbxproj +++ b/apps/dashboard/ios/Runner.xcodeproj/project.pbxproj @@ -65,6 +65,7 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C50137BE76501CB95C146061 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; CF91D7544EB9B7459F2B8EB6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FDAFB4DD2D4F018E005936FA /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -139,6 +140,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + FDAFB4DD2D4F018E005936FA /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -469,6 +471,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -655,6 +658,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -681,6 +685,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; diff --git a/apps/dashboard/ios/Runner/Info.plist b/apps/dashboard/ios/Runner/Info.plist index daf54e7..53d7cd6 100644 --- a/apps/dashboard/ios/Runner/Info.plist +++ b/apps/dashboard/ios/Runner/Info.plist @@ -1,49 +1,51 @@ - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Dashboard - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - dashboard - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UIApplicationSupportsIndirectInputEvents - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Dashboard + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + dashboard + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSPhotoLibraryUsageDescription + App requires access to the photo library to allow users to select and upload photos. + + \ No newline at end of file diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index ba605e2..d72c79e 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: riverpod_annotation: ^2.5.3 signals: ^6.0.2 uuid: ^4.5.1 + dev_dependencies: build_runner: ^2.4.14 custom_lint: ^0.7.0 From 2257147d6e7c6ba1ef4eb30964090ca9ef481111 Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 12:47:48 +0900 Subject: [PATCH 10/22] Update --- apps/dashboard/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index d72c79e..ec6df4a 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -1,7 +1,7 @@ name: dashboard description: "A Dashboard for OpenCI" publish_to: 'none' -version: 1.1.0+1 +version: 1.1.0+2 environment: sdk: ^3.6.0 From 8d91c0e1fe5e8b30fd2e5d5761bae9748c209f17 Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 13:27:41 +0900 Subject: [PATCH 11/22] Add sentry for dashboard --- apps/dashboard/ios/Podfile.lock | 17 ++++++++++ apps/dashboard/lib/main.dart | 10 +++++- .../Flutter/GeneratedPluginRegistrant.swift | 4 +++ apps/dashboard/pubspec.lock | 32 +++++++++++++++++++ apps/dashboard/pubspec.yaml | 3 +- .../lib/src/handle_flutter_builld_ipa.dart | 1 + 6 files changed, 65 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/ios/Podfile.lock b/apps/dashboard/ios/Podfile.lock index b64c45c..6bb6821 100644 --- a/apps/dashboard/ios/Podfile.lock +++ b/apps/dashboard/ios/Podfile.lock @@ -1363,6 +1363,8 @@ PODS: - nanopb/encode (= 3.30910.0) - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) + - package_info_plus (0.4.5): + - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -1370,6 +1372,11 @@ PODS: - SDWebImage (5.20.0): - SDWebImage/Core (= 5.20.0) - SDWebImage/Core (5.20.0) + - Sentry/HybridSDK (8.44.0-beta.1) + - sentry_flutter (8.13.0-beta.3): + - Flutter + - FlutterMacOS + - Sentry/HybridSDK (= 8.44.0-beta.1) - SwiftyGif (5.4.5) DEPENDENCIES: @@ -1378,7 +1385,9 @@ DEPENDENCIES: - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - Flutter (from `Flutter`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) SPEC REPOS: trunk: @@ -1404,6 +1413,7 @@ SPEC REPOS: - nanopb - RecaptchaInterop - SDWebImage + - Sentry - SwiftyGif EXTERNAL SOURCES: @@ -1417,8 +1427,12 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_core/ios" Flutter: :path: Flutter + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" + sentry_flutter: + :path: ".symlinks/plugins/sentry_flutter/ios" SPEC CHECKSUMS: abseil: d121da9ef7e2ff4cab7666e76c5a3e0915ae08c3 @@ -1446,9 +1460,12 @@ SPEC CHECKSUMS: GTMSessionFetcher: 923b710231ad3d6f3f0495ac1ced35421e07d9a6 leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21 SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 + Sentry: 194afe75566708912103f9a32f4cd8d2392e9307 + sentry_flutter: e4f0c56cb886385c1210b3fa618fc6b458d87d06 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 PODFILE CHECKSUM: 605c32f2fdd50c4f52b7b468d638bf62f371d321 diff --git a/apps/dashboard/lib/main.dart b/apps/dashboard/lib/main.dart index 7687ab8..940a8b7 100644 --- a/apps/dashboard/lib/main.dart +++ b/apps/dashboard/lib/main.dart @@ -4,6 +4,7 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'firebase_options.dart'; @@ -17,7 +18,14 @@ void main() async { FirebaseFirestore.instance.settings = const Settings( persistenceEnabled: false, ); - runApp(const ProviderScope(child: MyApp())); + await SentryFlutter.init( + (options) { + options.dsn = + 'https://55f0d94b6b860cada9f108ff096fed75@o4507005123166208.ingest.us.sentry.io/4508619657510912'; + }, + // Init your App. + appRunner: () => runApp(const ProviderScope(child: MyApp())), + ); } const Color primaryColor = Color(0xff03dac6); diff --git a/apps/dashboard/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/dashboard/macos/Flutter/GeneratedPluginRegistrant.swift index 2597bfc..4840ba2 100644 --- a/apps/dashboard/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/apps/dashboard/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,11 +8,15 @@ import Foundation import cloud_firestore import firebase_auth import firebase_core +import package_info_plus import path_provider_foundation +import sentry_flutter func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin")) } diff --git a/apps/dashboard/pubspec.lock b/apps/dashboard/pubspec.lock index 95cc85e..32cf703 100644 --- a/apps/dashboard/pubspec.lock +++ b/apps/dashboard/pubspec.lock @@ -716,6 +716,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: b15fad91c4d3d1f2b48c053dd41cb82da007c27407dc9ab5f9aa59881d0e39d4 + url: "https://pub.dev" + source: hosted + version: "8.1.4" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b + url: "https://pub.dev" + source: hosted + version: "3.0.2" path: dependency: transitive description: @@ -884,6 +900,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + sentry: + dependency: transitive + description: + name: sentry + sha256: f4cc882f03cd1e9c129faa22752eb0b56f2bcdfe398d78afb45f3b03b15dd6af + url: "https://pub.dev" + source: hosted + version: "8.13.0-beta.3" + sentry_flutter: + dependency: "direct main" + description: + name: sentry_flutter + sha256: b6110917b919f2060681a75298a985bf13aaa742e83f8a0a7967d748e89434cf + url: "https://pub.dev" + source: hosted + version: "8.13.0-beta.3" shelf: dependency: transitive description: diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index ec6df4a..593dac2 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -1,7 +1,7 @@ name: dashboard description: "A Dashboard for OpenCI" publish_to: 'none' -version: 1.1.0+2 +version: 1.1.0+3 environment: sdk: ^3.6.0 @@ -22,6 +22,7 @@ dependencies: openci_models: path: ../../packages/openci_models riverpod_annotation: ^2.5.3 + sentry_flutter: ^8.13.0-beta.3 signals: ^6.0.2 uuid: ^4.5.1 diff --git a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart index 2d42605..ef44fa9 100644 --- a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart +++ b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart @@ -503,6 +503,7 @@ project.targets.each do |target| config.build_settings['DEVELOPMENT_TEAM'] = '$teamId' config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = '$profileName' config.build_settings["PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]"] = '$profileName' + config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Distribution' end end From b6f2dbc25c104dee7eb82de3575bfbc6b6568a4e Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 14:29:57 +0900 Subject: [PATCH 12/22] Add firebase to dashboard(ios) --- apps/dashboard/firebase.json | 12 ++++++++++-- apps/dashboard/ios/.gitignore | 2 ++ apps/dashboard/ios/Runner.xcodeproj/project.pbxproj | 4 ++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/firebase.json b/apps/dashboard/firebase.json index 7715f08..f558d1f 100644 --- a/apps/dashboard/firebase.json +++ b/apps/dashboard/firebase.json @@ -13,7 +13,7 @@ "lib/firebase_options.dart": { "projectId": "open-ci-release", "configurations": { - "android": "1:226435817638:android:6848f67578b6d77ac1d467" + "ios": "1:226435817638:ios:aecb8b1ecfed8e9dc1d467" } } }, @@ -23,6 +23,14 @@ "appId": "1:226435817638:android:6848f67578b6d77ac1d467", "fileOutput": "android/app/google-services.json" } + }, + "ios": { + "default": { + "projectId": "open-ci-release", + "appId": "1:226435817638:ios:aecb8b1ecfed8e9dc1d467", + "uploadDebugSymbols": false, + "fileOutput": "ios/Runner/GoogleService-Info.plist" + } } } }, @@ -30,4 +38,4 @@ "rules": "firestore.rules", "indexes": "firestore.indexes.json" } -} \ No newline at end of file +} diff --git a/apps/dashboard/ios/.gitignore b/apps/dashboard/ios/.gitignore index 7a7f987..aca59e0 100644 --- a/apps/dashboard/ios/.gitignore +++ b/apps/dashboard/ios/.gitignore @@ -32,3 +32,5 @@ Runner/GeneratedPluginRegistrant.* !default.mode2v3 !default.pbxuser !default.perspectivev3 + +Runner/GoogleService-Info.plist diff --git a/apps/dashboard/ios/Runner.xcodeproj/project.pbxproj b/apps/dashboard/ios/Runner.xcodeproj/project.pbxproj index 3a73433..172acb8 100644 --- a/apps/dashboard/ios/Runner.xcodeproj/project.pbxproj +++ b/apps/dashboard/ios/Runner.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 2EE79E5054D3A01F89D65173 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF91D7544EB9B7459F2B8EB6 /* Pods_Runner.framework */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 717DA40C048897A625CAC55C /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B92BC7B128327F0FC41F1EEB /* GoogleService-Info.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -63,6 +64,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B92BC7B128327F0FC41F1EEB /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; C50137BE76501CB95C146061 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; CF91D7544EB9B7459F2B8EB6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FDAFB4DD2D4F018E005936FA /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; @@ -125,6 +127,7 @@ 331C8082294A63A400263BE5 /* RunnerTests */, D6AE210BE638C61DFDDC3064 /* Pods */, 738C9C2F936B695B515A128A /* Frameworks */, + B92BC7B128327F0FC41F1EEB /* GoogleService-Info.plist */, ); sourceTree = ""; }; @@ -265,6 +268,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + 717DA40C048897A625CAC55C /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 8507a1b498a06157d591ea543fd4f7364cdd828b Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 14:32:04 +0900 Subject: [PATCH 13/22] Bump dashboard version to 1.1.0+4 --- apps/dashboard/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index 593dac2..7efe68c 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -1,7 +1,7 @@ name: dashboard description: "A Dashboard for OpenCI" publish_to: 'none' -version: 1.1.0+3 +version: 1.1.0+4 environment: sdk: ^3.6.0 From df611e498e6d155ecbc87168e613e847b765288b Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 16:03:34 +0900 Subject: [PATCH 14/22] Add delete-provisioning-profile command to OpenCI CLI - Implement `deleteProfile` method in AppStoreConnectClient - Add `DeleteProvisioningProfileCommand` to command runner - Update command exports and file structure - Bump version to 0.1.7 --- apps/openci_cli/CHANGELOG.md | 11 ++++ apps/openci_cli/README.md | 3 +- .../openci_cli/lib/src/app_store_connect.dart | 10 ++++ .../lib/src/commands/command_runner.dart | 2 + .../openci_cli/lib/src/commands/commands.dart | 9 +-- ... create_provisioning_profile_command.dart} | 0 .../delete_provisioning_profile_command.dart | 55 +++++++++++++++++++ ..._ids.dart => list_bundle_ids_command.dart} | 0 ...=> list_procisioning_profile_command.dart} | 0 ...=> read_provisioning_profile_command.dart} | 0 .../lib/src/delete_provisioning_profile.dart | 36 ++++++++++++ apps/openci_cli/lib/src/version.dart | 2 +- apps/openci_cli/pubspec.yaml | 4 +- 13 files changed, 124 insertions(+), 8 deletions(-) rename apps/openci_cli/lib/src/commands/{create_provisioning_profile.dart => create_provisioning_profile_command.dart} (100%) create mode 100644 apps/openci_cli/lib/src/commands/delete_provisioning_profile_command.dart rename apps/openci_cli/lib/src/commands/{list_bundle_ids.dart => list_bundle_ids_command.dart} (100%) rename apps/openci_cli/lib/src/commands/{list_procisioning_profile.dart => list_procisioning_profile_command.dart} (100%) rename apps/openci_cli/lib/src/commands/{read_provisioning_profile.dart => read_provisioning_profile_command.dart} (100%) create mode 100644 apps/openci_cli/lib/src/delete_provisioning_profile.dart diff --git a/apps/openci_cli/CHANGELOG.md b/apps/openci_cli/CHANGELOG.md index 957c415..8063981 100644 --- a/apps/openci_cli/CHANGELOG.md +++ b/apps/openci_cli/CHANGELOG.md @@ -1,20 +1,31 @@ +## 0.1.7 + +- Add `delete-provisioning-profile` command + ## 0.1.6 + - Minor bug fix ## 0.1.5 + - Add `list-bundle-ids` command ## 0.1.4 + - Minor bug fix ## 0.1.3 + - Minor bug fix ## 0.1.2 + - Minor bug fix ## 0.1.1 + - Minor bug fix ## 0.1.0 + Initial Release diff --git a/apps/openci_cli/README.md b/apps/openci_cli/README.md index 11e8e86..8372521 100644 --- a/apps/openci_cli/README.md +++ b/apps/openci_cli/README.md @@ -8,4 +8,5 @@ A command line interface for interacting with App Store Connect APIs. - 🔐 Certificate management - 📱 Provisioning profile operations - 🚀 Beta build submission -- 🔄 Clean API client implementation \ No newline at end of file +- 🔄 Clean API client implementation + diff --git a/apps/openci_cli/lib/src/app_store_connect.dart b/apps/openci_cli/lib/src/app_store_connect.dart index 0153e82..5d380d2 100644 --- a/apps/openci_cli/lib/src/app_store_connect.dart +++ b/apps/openci_cli/lib/src/app_store_connect.dart @@ -180,6 +180,16 @@ class AppStoreConnectClient { } } + Future> deleteProfile({ + required String profileId, + }) async { + final response = await _request( + path: '/profiles/$profileId', + method: 'DELETE', + ); + return response; + } + Future> getProfile({ required String profileId, }) async { diff --git a/apps/openci_cli/lib/src/commands/command_runner.dart b/apps/openci_cli/lib/src/commands/command_runner.dart index cd671e0..4e3ff3b 100644 --- a/apps/openci_cli/lib/src/commands/command_runner.dart +++ b/apps/openci_cli/lib/src/commands/command_runner.dart @@ -3,6 +3,7 @@ import 'package:args/command_runner.dart'; import 'package:cli_completion/cli_completion.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:openci_cli2/src/commands/commands.dart'; +import 'package:openci_cli2/src/commands/delete_provisioning_profile_command.dart'; import 'package:openci_cli2/src/version.dart'; import 'package:pub_updater/pub_updater.dart'; @@ -40,6 +41,7 @@ class OpenciCliCommandRunner extends CompletionCommandRunner { addCommand(CreateCertificateCommand()); addCommand(CreateProvisioningProfileCommand()); + addCommand(DeleteProvisioningProfileCommand()); addCommand(ReadCertificateCommand()); addCommand(ListCertificatesCommand()); addCommand(ListProvisioningProfileCommand()); diff --git a/apps/openci_cli/lib/src/commands/commands.dart b/apps/openci_cli/lib/src/commands/commands.dart index c5030ca..6cccdb3 100644 --- a/apps/openci_cli/lib/src/commands/commands.dart +++ b/apps/openci_cli/lib/src/commands/commands.dart @@ -1,8 +1,9 @@ export 'create_certificate_command.dart'; -export 'create_provisioning_profile.dart'; +export 'create_provisioning_profile_command.dart'; +export 'delete_provisioning_profile_command.dart'; +export 'list_bundle_ids_command.dart'; export 'list_certificates_command.dart'; -export 'list_procisioning_profile.dart'; -export 'list_bundle_ids.dart'; +export 'list_procisioning_profile_command.dart'; export 'read_certificate_command.dart'; -export 'read_provisioning_profile.dart'; +export 'read_provisioning_profile_command.dart'; export 'update_command.dart'; diff --git a/apps/openci_cli/lib/src/commands/create_provisioning_profile.dart b/apps/openci_cli/lib/src/commands/create_provisioning_profile_command.dart similarity index 100% rename from apps/openci_cli/lib/src/commands/create_provisioning_profile.dart rename to apps/openci_cli/lib/src/commands/create_provisioning_profile_command.dart diff --git a/apps/openci_cli/lib/src/commands/delete_provisioning_profile_command.dart b/apps/openci_cli/lib/src/commands/delete_provisioning_profile_command.dart new file mode 100644 index 0000000..3c782b6 --- /dev/null +++ b/apps/openci_cli/lib/src/commands/delete_provisioning_profile_command.dart @@ -0,0 +1,55 @@ +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:openci_cli2/src/delete_provisioning_profile.dart'; + +class DeleteProvisioningProfileCommand extends Command { + DeleteProvisioningProfileCommand() { + argParser + ..addOption( + 'issuer-id', + help: 'App Store Connect Issuer ID', + ) + ..addOption( + 'key-id', + help: 'App Store Connect Key ID', + ) + ..addOption( + 'path-to-private-key', + help: 'Path to App Store Connect Private Key, p8 file', + ) + ..addOption( + 'profile-id', + help: 'Provisioning Profile ID', + ); + } + + @override + String get description => 'Delete Apple Developer Provisioning Profile'; + + @override + String get name => 'delete-provisioning-profile'; + + @override + Future run() async { + final issuerId = argResults?['issuer-id'] as String?; + final keyId = argResults?['key-id'] as String?; + final pathToPrivateKey = argResults?['path-to-private-key'] as String?; + final profileId = argResults?['profile-id'] as String?; + if (issuerId == null || + keyId == null || + pathToPrivateKey == null || + profileId == null) { + return ExitCode.usage.code; + } + final privateKey = File(pathToPrivateKey).readAsStringSync(); + + return deleteProvisioningProfile( + issuerId: issuerId, + keyId: keyId, + privateKey: privateKey, + profileId: profileId, + ); + } +} diff --git a/apps/openci_cli/lib/src/commands/list_bundle_ids.dart b/apps/openci_cli/lib/src/commands/list_bundle_ids_command.dart similarity index 100% rename from apps/openci_cli/lib/src/commands/list_bundle_ids.dart rename to apps/openci_cli/lib/src/commands/list_bundle_ids_command.dart diff --git a/apps/openci_cli/lib/src/commands/list_procisioning_profile.dart b/apps/openci_cli/lib/src/commands/list_procisioning_profile_command.dart similarity index 100% rename from apps/openci_cli/lib/src/commands/list_procisioning_profile.dart rename to apps/openci_cli/lib/src/commands/list_procisioning_profile_command.dart diff --git a/apps/openci_cli/lib/src/commands/read_provisioning_profile.dart b/apps/openci_cli/lib/src/commands/read_provisioning_profile_command.dart similarity index 100% rename from apps/openci_cli/lib/src/commands/read_provisioning_profile.dart rename to apps/openci_cli/lib/src/commands/read_provisioning_profile_command.dart diff --git a/apps/openci_cli/lib/src/delete_provisioning_profile.dart b/apps/openci_cli/lib/src/delete_provisioning_profile.dart new file mode 100644 index 0000000..768de54 --- /dev/null +++ b/apps/openci_cli/lib/src/delete_provisioning_profile.dart @@ -0,0 +1,36 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:mason_logger/mason_logger.dart'; +import 'package:openci_cli2/src/app_store_connect.dart'; + +Future deleteProvisioningProfile({ + required String issuerId, + required String keyId, + required String privateKey, + required String profileId, +}) async { + final client = AppStoreConnectClient( + issuerId: issuerId, + keyId: keyId, + privateKey: privateKey, + ); + + try { + final response = await client.deleteProfile( + profileId: profileId, + ); + + stdout.write(jsonEncode(response)); + return ExitCode.success.code; + } catch (e) { + final result = { + 'stderr': e.toString(), + 'exitCode': 1, + }; + stdout.write(jsonEncode(result)); + return 1; + } finally { + client.dispose(); + } +} diff --git a/apps/openci_cli/lib/src/version.dart b/apps/openci_cli/lib/src/version.dart index a60edf9..d1dc24a 100644 --- a/apps/openci_cli/lib/src/version.dart +++ b/apps/openci_cli/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '0.1.6'; +const packageVersion = '0.1.7'; diff --git a/apps/openci_cli/pubspec.yaml b/apps/openci_cli/pubspec.yaml index d589f35..88732aa 100644 --- a/apps/openci_cli/pubspec.yaml +++ b/apps/openci_cli/pubspec.yaml @@ -1,7 +1,7 @@ name: openci_cli2 description: OpenCI CLI -version: 0.1.6 -repository: https://github.com/open-ci-io/openci/tree/develop/apps/openci_runner +version: 0.1.7 +repository: https://github.com/open-ci-io/openci/tree/develop/apps/openci_cli homepage: https://open-ci.io environment: From 9654100b73b4e0b1fb7adecc961fcd865d8409df Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 16:07:55 +0900 Subject: [PATCH 15/22] Update OpenCI CLI README with comprehensive documentation - Expand README with detailed usage instructions - Add sections for certificate, provisioning profile, and bundle ID commands - Include installation, requirements, and configuration guidance - Enhance project description and provide example CLI commands --- apps/openci_cli/README.md | 137 +++++++++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 1 deletion(-) diff --git a/apps/openci_cli/README.md b/apps/openci_cli/README.md index 8372521..d30c5aa 100644 --- a/apps/openci_cli/README.md +++ b/apps/openci_cli/README.md @@ -1,6 +1,6 @@ # OpenCI CLI -A command line interface for interacting with App Store Connect APIs. +A command line interface for interacting with Apple's App Store Connect API to manage certificates, provisioning profiles, and more. ## Features @@ -10,3 +10,138 @@ A command line interface for interacting with App Store Connect APIs. - 🚀 Beta build submission - 🔄 Clean API client implementation +## Requirements + +- Dart SDK version 3.5.0 or higher. +- A valid App Store Connect account with API credentials. +- macOS (e.g. M1 Max on macOS Sequoia) is recommended for development. + +## Installation + +Install via Dart pub: + +```bash +dart pub global activate openci_cli2 +``` + +Make sure your Dart pub global bin directory is in your PATH. + + +## Usage + +Display help: + +```bash +openci_cli2 --help +``` + +The CLI supports various commands, which are grouped as follows: + +### Certificate Commands + +- **Create Certificate** + + Create a new certificate using your credentials. + + ```bash + openci_cli2 create-certificate \ + --issuer-id YOUR_ISSUER_ID \ + --key-id YOUR_KEY_ID \ + --path-to-private-key /path/to/AuthKey.p8 \ + --certificate-type DISTRIBUTION + ``` + +- **List Certificates** + + List available certificates. + + ```bash + openci_cli2 list-certificates \ + --issuer-id YOUR_ISSUER_ID \ + --key-id YOUR_KEY_ID \ + --path-to-private-key /path/to/AuthKey.p8 + ``` + +- **Read Certificate** + + Retrieve details of a specific certificate. + + ```bash + openci_cli2 read-certificate \ + --issuer-id YOUR_ISSUER_ID \ + --key-id YOUR_KEY_ID \ + --path-to-private-key /path/to/AuthKey.p8 \ + --certificate-id CERTIFICATE_ID + ``` + +### Provisioning Profile Commands + +- **Create Provisioning Profile** + + Create a new provisioning profile. + + ```bash + openci_cli2 create-provisioning-profile \ + --issuer-id YOUR_ISSUER_ID \ + --key-id YOUR_KEY_ID \ + --path-to-private-key /path/to/AuthKey.p8 \ + --profile-id PROFILE_ID + ``` + +- **List Provisioning Profiles** + + List available provisioning profiles. + + ```bash + openci_cli2 list-provisioning-profile \ + --issuer-id YOUR_ISSUER_ID \ + --key-id YOUR_KEY_ID \ + --path-to-private-key /path/to/AuthKey.p8 + ``` + +- **Delete Provisioning Profile** + + Delete an existing provisioning profile. + + ```bash + openci_cli2 delete-provisioning-profile \ + --issuer-id YOUR_ISSUER_ID \ + --key-id YOUR_KEY_ID \ + --path-to-private-key /path/to/AuthKey.p8 \ + --profile-id PROFILE_ID + ``` + +### Bundle ID Commands + +- **List Bundle IDs** + + Retrieve a list of bundle identifiers. + + ```bash + openci_cli2 list-bundle-ids \ + --issuer-id YOUR_ISSUER_ID \ + --key-id YOUR_KEY_ID \ + --path-to-private-key /path/to/AuthKey.p8 \ + --filter-identifier io.example.app + ``` + +## Configuration + +Before using the CLI, ensure you have the following: + +- Your App Store Connect API credentials (Issuer ID, Key ID, and Private Key). +- An accessible private key file (AuthKey.p8) on your system. + +## License + +This project is licensed under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for details. + +## Contributing + +Contributions are welcome! Please fork the repository and submit pull requests for any improvements or bug fixes. + +## Additional Information + +For detailed documentation and advanced usage, please refer to the official documentation or the source code comments. + + From 20ca912979656d395d65f3174b2683c5ebaa2f2f Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 16:53:13 +0900 Subject: [PATCH 16/22] Improve iOS build workflow with dynamic provisioning profile and cleanup - Generate unique provisioning profile name with bundle ID and timestamp - Capture provisioning profile ID for cleanup - Add cleanup step to delete provisioning profile after build - Remove debug print statements - Bump dashboard version to 1.1.0+5 --- apps/dashboard/pubspec.yaml | 2 +- apps/openci_cli/scripts/delete-pp.sh | 9 ++++ .../lib/src/handle_flutter_builld_ipa.dart | 50 +++++++++++-------- 3 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 apps/openci_cli/scripts/delete-pp.sh diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index 7efe68c..a53d394 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -1,7 +1,7 @@ name: dashboard description: "A Dashboard for OpenCI" publish_to: 'none' -version: 1.1.0+4 +version: 1.1.0+5 environment: sdk: ^3.6.0 diff --git a/apps/openci_cli/scripts/delete-pp.sh b/apps/openci_cli/scripts/delete-pp.sh new file mode 100644 index 0000000..b897025 --- /dev/null +++ b/apps/openci_cli/scripts/delete-pp.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +response=$(dart run openci_cli2 delete-provisioning-profile \ + --issuer-id=a6b7e4ee-e80b-41fb-8c5b-4f63234598eb \ + --key-id=TVVJSBM7TY \ + --path-to-private-key="/Users/masahiroaoki/Desktop/AuthKey_TVVJSBM7TY.p8" \ + --profile-id=X5QSANTX8L) + +echo $response diff --git a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart index ef44fa9..649fd05 100644 --- a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart +++ b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart @@ -175,7 +175,6 @@ Future handleFlutterBuildIpa( currentWorkingDirectory: workflow.currentWorkingDirectory, jobId: buildJob.id, ); - print('bundleIdRes: $bundleIdRes'); final bundleId = bundleIdRes.stdout.trim(); @@ -187,14 +186,13 @@ Future handleFlutterBuildIpa( currentWorkingDirectory: workflow.currentWorkingDirectory, jobId: buildJob.id, ); - print('bundleIdsRes: $bundleIdsRes'); final bundleIdsJson = jsonDecode(bundleIdsRes.stdout); final ascBundleId = bundleIdsJson['body']['data'][0]['id']; final teamId = bundleIdsJson['body']['data'][0]['attributes']['seedId'] as String; - const ppName = 'OpenCI PP'; + final ppName = 'OpenCI_PP_${bundleId}_${DateTime.now().toIso8601String()}'; final ppRes = await runCommand( logId: logId, @@ -205,10 +203,10 @@ Future handleFlutterBuildIpa( jobId: buildJob.id, ); - print('ppRes: $ppRes'); final ppJson = jsonDecode(ppRes.stdout); final ppBase64 = ppJson['body']['data']['attributes']['profileContent']; final ppUuid = ppJson['body']['data']['attributes']['uuid']; + final ppId = ppJson['body']['data']['id']; await runCommand( logId: logId, @@ -282,23 +280,33 @@ Future handleFlutterBuildIpa( jobId: buildJob.id, ); - await runCommand( - logId: logId, - client: client, - command: - 'flutter build ipa --export-options-plist="/Users/admin/${workflow.currentWorkingDirectory}/ios/ExportOptions.plist"', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); - - await runCommand( - logId: logId, - client: client, - command: - 'xcrun altool --upload-app --type ios -f build/ios/ipa/*.ipa --apiKey $keyId --apiIssuer $issuerId', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + try { + await runCommand( + logId: logId, + client: client, + command: + 'flutter build ipa --export-options-plist="/Users/admin/${workflow.currentWorkingDirectory}/ios/ExportOptions.plist"', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + + await runCommand( + logId: logId, + client: client, + command: + 'xcrun altool --upload-app --type ios -f build/ios/ipa/*.ipa --apiKey $keyId --apiIssuer $issuerId', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + } finally { + await runCommand( + logId: logId, + client: client, + command: 'openci_cli2 delete-provisioning-profile --profile-id=$ppId', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + } await Future.delayed(const Duration(hours: 10)); } From 1171654984a3d3407cecaa5a288bf1f84aff89aa Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 17:09:59 +0900 Subject: [PATCH 17/22] Improve provisioning profile deletion in iOS build workflow - Update delete-provisioning-profile script with new profile ID - Modify Flutter build IPA handler to include full provisioning profile deletion command - Bump dashboard version to 1.1.0+6 --- apps/dashboard/pubspec.yaml | 2 +- apps/openci_cli/scripts/delete-pp.sh | 2 +- apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index a53d394..5c9d48e 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -1,7 +1,7 @@ name: dashboard description: "A Dashboard for OpenCI" publish_to: 'none' -version: 1.1.0+5 +version: 1.1.0+6 environment: sdk: ^3.6.0 diff --git a/apps/openci_cli/scripts/delete-pp.sh b/apps/openci_cli/scripts/delete-pp.sh index b897025..5b3f491 100644 --- a/apps/openci_cli/scripts/delete-pp.sh +++ b/apps/openci_cli/scripts/delete-pp.sh @@ -4,6 +4,6 @@ response=$(dart run openci_cli2 delete-provisioning-profile \ --issuer-id=a6b7e4ee-e80b-41fb-8c5b-4f63234598eb \ --key-id=TVVJSBM7TY \ --path-to-private-key="/Users/masahiroaoki/Desktop/AuthKey_TVVJSBM7TY.p8" \ - --profile-id=X5QSANTX8L) + --profile-id=B45N6U8892) echo $response diff --git a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart index 649fd05..17fe770 100644 --- a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart +++ b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart @@ -302,7 +302,8 @@ Future handleFlutterBuildIpa( await runCommand( logId: logId, client: client, - command: 'openci_cli2 delete-provisioning-profile --profile-id=$ppId', + command: + 'openci_cli2 delete-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: workflow.currentWorkingDirectory, keyId: keyId)}" --profile-id=$ppId', currentWorkingDirectory: workflow.currentWorkingDirectory, jobId: buildJob.id, ); From 2dbc2ebc630612b1796eac3df2b5bb92268f914c Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 18:38:00 +0900 Subject: [PATCH 18/22] Improve OpenCI CLI and provisioning profile handling - Bump CLI version to 0.1.8 - Update error handling in delete-provisioning-profile command - Modify AppStoreConnectClient to handle 204 status code - Update provisioning profile generation in Flutter build IPA handler - Update delete-pp script with new profile ID --- apps/dashboard/pubspec.yaml | 2 +- apps/openci_cli/CHANGELOG.md | 5 +++++ apps/openci_cli/lib/src/app_store_connect.dart | 8 ++++++++ apps/openci_cli/lib/src/delete_provisioning_profile.dart | 7 +------ apps/openci_cli/lib/src/version.dart | 2 +- apps/openci_cli/pubspec.yaml | 2 +- apps/openci_cli/scripts/delete-pp.sh | 2 +- apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart | 3 ++- 8 files changed, 20 insertions(+), 11 deletions(-) diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index 5c9d48e..d2c3a73 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -1,7 +1,7 @@ name: dashboard description: "A Dashboard for OpenCI" publish_to: 'none' -version: 1.1.0+6 +version: 1.1.0+7 environment: sdk: ^3.6.0 diff --git a/apps/openci_cli/CHANGELOG.md b/apps/openci_cli/CHANGELOG.md index 8063981..09aae1d 100644 --- a/apps/openci_cli/CHANGELOG.md +++ b/apps/openci_cli/CHANGELOG.md @@ -1,3 +1,8 @@ + +## 0.1.8 + +- Minor bug fix + ## 0.1.7 - Add `delete-provisioning-profile` command diff --git a/apps/openci_cli/lib/src/app_store_connect.dart b/apps/openci_cli/lib/src/app_store_connect.dart index 5d380d2..b9e2be2 100644 --- a/apps/openci_cli/lib/src/app_store_connect.dart +++ b/apps/openci_cli/lib/src/app_store_connect.dart @@ -67,6 +67,13 @@ class AppStoreConnectClient { throw AppStoreConnectError('Unsupported HTTP method: $method'); } + if (response.statusCode == 204) { + return { + 'statusCode': response.statusCode, + 'body': '', + }; + } + final responseBody = jsonDecode(response.body) as Map; return { @@ -187,6 +194,7 @@ class AppStoreConnectClient { path: '/profiles/$profileId', method: 'DELETE', ); + print('response: $response'); return response; } diff --git a/apps/openci_cli/lib/src/delete_provisioning_profile.dart b/apps/openci_cli/lib/src/delete_provisioning_profile.dart index 768de54..fe49ab9 100644 --- a/apps/openci_cli/lib/src/delete_provisioning_profile.dart +++ b/apps/openci_cli/lib/src/delete_provisioning_profile.dart @@ -20,15 +20,10 @@ Future deleteProvisioningProfile({ final response = await client.deleteProfile( profileId: profileId, ); - stdout.write(jsonEncode(response)); return ExitCode.success.code; } catch (e) { - final result = { - 'stderr': e.toString(), - 'exitCode': 1, - }; - stdout.write(jsonEncode(result)); + stdout.write(jsonEncode(e)); return 1; } finally { client.dispose(); diff --git a/apps/openci_cli/lib/src/version.dart b/apps/openci_cli/lib/src/version.dart index d1dc24a..cb77acc 100644 --- a/apps/openci_cli/lib/src/version.dart +++ b/apps/openci_cli/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '0.1.7'; +const packageVersion = '0.1.8'; diff --git a/apps/openci_cli/pubspec.yaml b/apps/openci_cli/pubspec.yaml index 88732aa..b7fdf6e 100644 --- a/apps/openci_cli/pubspec.yaml +++ b/apps/openci_cli/pubspec.yaml @@ -1,6 +1,6 @@ name: openci_cli2 description: OpenCI CLI -version: 0.1.7 +version: 0.1.8 repository: https://github.com/open-ci-io/openci/tree/develop/apps/openci_cli homepage: https://open-ci.io diff --git a/apps/openci_cli/scripts/delete-pp.sh b/apps/openci_cli/scripts/delete-pp.sh index 5b3f491..ab78f85 100644 --- a/apps/openci_cli/scripts/delete-pp.sh +++ b/apps/openci_cli/scripts/delete-pp.sh @@ -4,6 +4,6 @@ response=$(dart run openci_cli2 delete-provisioning-profile \ --issuer-id=a6b7e4ee-e80b-41fb-8c5b-4f63234598eb \ --key-id=TVVJSBM7TY \ --path-to-private-key="/Users/masahiroaoki/Desktop/AuthKey_TVVJSBM7TY.p8" \ - --profile-id=B45N6U8892) + --profile-id=XMTV7FH42S) echo $response diff --git a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart index 17fe770..a3beb0c 100644 --- a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart +++ b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart @@ -5,6 +5,7 @@ import 'package:dartssh2/dartssh2.dart'; import 'package:openci_models/openci_models.dart'; import 'package:openci_runner/src/firebase.dart'; import 'package:openci_runner/src/run_command.dart'; +import 'package:uuid/uuid.dart'; Future handleFlutterBuildIpa( String logId, @@ -192,7 +193,7 @@ Future handleFlutterBuildIpa( final teamId = bundleIdsJson['body']['data'][0]['attributes']['seedId'] as String; - final ppName = 'OpenCI_PP_${bundleId}_${DateTime.now().toIso8601String()}'; + final ppName = 'OpenCI_PP_${const Uuid().v4()}'; final ppRes = await runCommand( logId: logId, From 4adf001486f3739a3a0b2eadbf6dffb64dcd636c Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Sun, 2 Feb 2025 19:15:10 +0900 Subject: [PATCH 19/22] Remove unnecessary delay in Flutter build IPA handler and bump dashboard version --- apps/dashboard/pubspec.yaml | 2 +- apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index d2c3a73..2f76048 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -1,7 +1,7 @@ name: dashboard description: "A Dashboard for OpenCI" publish_to: 'none' -version: 1.1.0+7 +version: 1.1.0+8 environment: sdk: ^3.6.0 diff --git a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart index a3beb0c..28418de 100644 --- a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart +++ b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart @@ -309,8 +309,6 @@ Future handleFlutterBuildIpa( jobId: buildJob.id, ); } - - await Future.delayed(const Duration(hours: 10)); } } From 557b1b15757c986289d83d7a4bf9388b130e09d7 Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Mon, 3 Feb 2025 00:33:53 +0900 Subject: [PATCH 20/22] Bump dashboard version to 1.1.0+9 --- apps/dashboard/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index 2f76048..cbf0dde 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -1,7 +1,7 @@ name: dashboard description: "A Dashboard for OpenCI" publish_to: 'none' -version: 1.1.0+8 +version: 1.1.0+9 environment: sdk: ^3.6.0 From d437e783645eca6213995e97cc0f8df78f10ff41 Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Tue, 4 Feb 2025 23:12:32 +0900 Subject: [PATCH 21/22] Refactor Flutter build IPA handler with improved certificate and provisioning profile management - Add comprehensive handling for Apple Developer certificates and provisioning profiles - Implement dynamic certificate creation and upload process - Enhance keychain and provisioning profile management - Add support for unique provisioning profile generation - Improve error handling and cleanup mechanisms --- apps/dashboard/pubspec.yaml | 2 +- .../lib/src/handle_flutter_builld_ipa.dart | 637 ++++++++++++------ 2 files changed, 417 insertions(+), 222 deletions(-) diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index cbf0dde..e7d22c0 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -1,7 +1,7 @@ name: dashboard description: "A Dashboard for OpenCI" publish_to: 'none' -version: 1.1.0+9 +version: 1.1.0+10 environment: sdk: ^3.6.0 diff --git a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart index 28418de..a314151 100644 --- a/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart +++ b/apps/openci_runner/lib/src/handle_flutter_builld_ipa.dart @@ -7,6 +7,10 @@ import 'package:openci_runner/src/firebase.dart'; import 'package:openci_runner/src/run_command.dart'; import 'package:uuid/uuid.dart'; +const _p12Path = '/Users/admin/Desktop/certificate.p12'; +const _p12Password = '12345678'; +const _keychainPassword = '12345678'; + Future handleFlutterBuildIpa( String logId, SSHClient client, @@ -14,206 +18,289 @@ Future handleFlutterBuildIpa( BuildJob buildJob, Firestore firestore, ) async { - // 0. Secretsの存在を確認 final areASCSecretsUploaded = await _areASCSecretsUploaded(); if (!areASCSecretsUploaded) { throw Exception('ASC secrets are not uploaded'); } + final p8Base64 = await _getP8Base64(firestore); + final issuerId = await _getIssuerId(firestore); + final keyId = await _getKeyId(firestore); + await _uploadP8File( + client, + p8Base64, + logId, + workflow.id, + buildJob.id, + workflow.currentWorkingDirectory, + keyId, + ); + + await runCommand( + logId: logId, + client: client, + command: 'openci_cli2 update', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + + String? certificateId; + final isValidP12FileUploaded = await _wasP12FileUploadedWithinOneYear(); - if (isValidP12FileUploaded) { + final isP12CertificateIdUploaded = + await _isP12CertificateIdUploaded(firestore); + + if (isValidP12FileUploaded && isP12CertificateIdUploaded) { print('P12 file is uploaded within one year'); - // base64を取得し、リモートに書き込む - // keychainにimport - // PPをDL - // PPをVMに配置 - } else { - print('P12 file is not uploaded within one year'); - // p12ファイルを作成 - final p8Base64 = await _getP8Base64(firestore); - final issuerId = await _getIssuerId(firestore); - final keyId = await _getKeyId(firestore); - await _uploadP8File( + + final p12Base64 = await _fetchP12File(firestore); + certificateId = await _fetchP12CertificateId(firestore); + + final isP12RegisteredInASC = await _isP12RegisteredInASC( + certificateId, client, - p8Base64, logId, workflow.id, buildJob.id, workflow.currentWorkingDirectory, + issuerId, keyId, ); - await runCommand( + if (!isP12RegisteredInASC) { + await _deleteP12File(firestore); + await _deleteP12CertificateId(firestore); + + certificateId = await _createP12File( + logId: logId, + client: client, + workflow: workflow, + buildJob: buildJob, + firestore: firestore, + issuerId: issuerId, + keyId: keyId, + ); + + await _uploadP12File( + client, + logId, + workflow.id, + buildJob.id, + workflow.currentWorkingDirectory, + _p12Path, + firestore, + ); + + await _uploadP12CertificateId(certificateId, firestore); + } else { + await runCommand( + logId: logId, + client: client, + command: 'echo $p12Base64 | base64 -d > $_p12Path', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + } + } else if (!isValidP12FileUploaded && !isP12CertificateIdUploaded) { + print('P12 file is not uploaded within one year'); + certificateId = await _createP12File( logId: logId, client: client, - command: 'openci_cli2 update', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, + workflow: workflow, + buildJob: buildJob, + firestore: firestore, + issuerId: issuerId, + keyId: keyId, ); - final result = await runCommand( - logId: logId, - client: client, - command: - 'openci_cli2 create-certificate --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: workflow.currentWorkingDirectory, keyId: keyId)}" --certificate-type=DISTRIBUTION', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, + await _uploadP12File( + client, + logId, + workflow.id, + buildJob.id, + workflow.currentWorkingDirectory, + _p12Path, + firestore, ); - final resultJson = jsonDecode(result.stdout); - final resultCode = resultJson['statusCode']; + await _uploadP12CertificateId(certificateId, firestore); + } else { + throw Exception( + 'P12 file is not uploaded within one year or P12 certificate id is not uploaded, isValidP12FileUploaded:$isValidP12FileUploaded, isP12CertificateIdUploaded:$isP12CertificateIdUploaded', + ); + } - if (resultCode != 201) { - throw Exception('Failed to create certificate'); - } - final keyBase64 = resultJson['key']; + const keychainPath = '/Users/admin/Library/Keychains/app-signing.keychain-db'; - await runCommand( - logId: logId, - client: client, - command: - 'echo $keyBase64 | base64 -d > /Users/admin/Desktop/certificate.key', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + await runCommand( + logId: logId, + client: client, + command: 'security create-keychain -p "$_keychainPassword" $keychainPath', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - final csrBase64 = - resultJson['body']['data']['attributes']['certificateContent']; + await runCommand( + logId: logId, + client: client, + command: 'security set-keychain-settings -lut 21600 $keychainPath', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - final certificateId = resultJson['body']['data']['id']; + await runCommand( + logId: logId, + client: client, + command: 'security unlock-keychain -p "$_keychainPassword" $keychainPath', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - await runCommand( - logId: logId, - client: client, - command: - 'echo $csrBase64 | base64 -d > /Users/admin/Desktop/certificate.cer', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + await runCommand( + logId: logId, + client: client, + command: + 'security import $_p12Path -P $_p12Password -A -t cert -f pkcs12 -k $keychainPath', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - const p12Path = '/Users/admin/Desktop/certificate.p12'; - const p12Password = '12345678'; - const keychainPassword = '12345678'; + await runCommand( + logId: logId, + client: client, + command: + 'security set-key-partition-list -S apple-tool:,apple: -k $_keychainPassword $keychainPath', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - await runCommand( - logId: logId, - client: client, - command: - 'openssl pkcs12 -export -in /Users/admin/Desktop/certificate.cer -inkey /Users/admin/Desktop/certificate.key -out $p12Path -name "Certificate Name" -passout pass:$p12Password', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + await runCommand( + logId: logId, + client: client, + command: 'security list-keychain -d user -s $keychainPath', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // Save p12 file to Firestore (as a secret) + final bundleIdRes = await runCommand( + logId: logId, + client: client, + command: + r"grep -m1 PRODUCT_BUNDLE_IDENTIFIER ios/Runner.xcodeproj/project.pbxproj | sed -E 's/.*= ([^;]+);.*/\1/'", + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - const keychainPath = - '/Users/admin/Library/Keychains/app-signing.keychain-db'; + final bundleId = bundleIdRes.stdout.trim(); - await runCommand( - logId: logId, - client: client, - command: 'security create-keychain -p "$keychainPassword" $keychainPath', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + final bundleIdsRes = await runCommand( + logId: logId, + client: client, + command: + 'openci_cli2 list-bundle-ids --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: workflow.currentWorkingDirectory, keyId: keyId)}" --filter-identifier="$bundleId"', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - await runCommand( - logId: logId, - client: client, - command: 'security set-keychain-settings -lut 21600 $keychainPath', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + final bundleIdsJson = jsonDecode(bundleIdsRes.stdout); + final ascBundleId = bundleIdsJson['body']['data'][0]['id']; + final teamId = + bundleIdsJson['body']['data'][0]['attributes']['seedId'] as String; - await runCommand( - logId: logId, - client: client, - command: 'security unlock-keychain -p "$keychainPassword" $keychainPath', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + final ppName = 'OpenCI_PP_${const Uuid().v4()}'; - await runCommand( - logId: logId, - client: client, - command: - 'security import $p12Path -P $p12Password -A -t cert -f pkcs12 -k $keychainPath', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + final ppRes = await runCommand( + logId: logId, + client: client, + command: + 'openci_cli2 create-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: workflow.currentWorkingDirectory, keyId: keyId)}" --certificate-id=$certificateId --profile-name="$ppName" --profile-type="IOS_APP_STORE" --bundle-id=$ascBundleId', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - await runCommand( - logId: logId, - client: client, - command: - 'security set-key-partition-list -S apple-tool:,apple: -k $keychainPassword $keychainPath', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + final ppJson = jsonDecode(ppRes.stdout); + final ppBase64 = ppJson['body']['data']['attributes']['profileContent']; + final ppUuid = ppJson['body']['data']['attributes']['uuid']; + final ppId = ppJson['body']['data']['id']; - await runCommand( - logId: logId, - client: client, - command: 'security list-keychain -d user -s $keychainPath', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + await runCommand( + logId: logId, + client: client, + command: + r'mkdir -p /Users/admin/Library/MobileDevice/Provisioning\ Profiles', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - // await runCommand( - // logId: logId, - // client: client, - // command: - // 'openci_cli2 create-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="/Users/admin/Desktop/AuthKey_$keyId.p8" --certificate-id=$certificateId --profile-name="OpenCI PP" --profile-type="IOS_APP_STORE" --bundle-id="io.openci.dashboard.ios"', - // currentWorkingDirectory: workflow.currentWorkingDirectory, - // jobId: buildJob.id, - // ); + await runCommand( + logId: logId, + client: client, + command: 'pwd', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - final bundleIdRes = await runCommand( - logId: logId, - client: client, - command: - r"grep -m1 PRODUCT_BUNDLE_IDENTIFIER ios/Runner.xcodeproj/project.pbxproj | sed -E 's/.*= ([^;]+);.*/\1/'", - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + await runCommand( + logId: logId, + client: client, + command: + 'echo $ppBase64 | base64 -d > "/Users/admin/Library/MobileDevice/Provisioning Profiles/$ppUuid.mobileprovision"', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - final bundleId = bundleIdRes.stdout.trim(); + final exportOptionsPlistBase64 = createPlistXmlAsBase64( + teamId: teamId, + bundleId: bundleId, + profileName: ppName, + ); - final bundleIdsRes = await runCommand( - logId: logId, - client: client, - command: - 'openci_cli2 list-bundle-ids --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: workflow.currentWorkingDirectory, keyId: keyId)}" --filter-identifier="$bundleId"', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + await runCommand( + logId: logId, + client: client, + command: + 'echo $exportOptionsPlistBase64 | base64 -d > "/Users/admin/${workflow.currentWorkingDirectory}/ios/ExportOptions.plist"', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - final bundleIdsJson = jsonDecode(bundleIdsRes.stdout); - final ascBundleId = bundleIdsJson['body']['data'][0]['id']; - final teamId = - bundleIdsJson['body']['data'][0]['attributes']['seedId'] as String; + final rubyScriptBase64 = generateXcodeprojRubyScriptBase64( + teamId: teamId, + profileName: ppName, + ); + const rubyScriptName = 'xcode_proj.rb'; - final ppName = 'OpenCI_PP_${const Uuid().v4()}'; + await runCommand( + logId: logId, + client: client, + command: + 'echo $rubyScriptBase64 | base64 -d > "/Users/admin/${workflow.currentWorkingDirectory}/$rubyScriptName"', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - final ppRes = await runCommand( - logId: logId, - client: client, - command: - 'openci_cli2 create-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: workflow.currentWorkingDirectory, keyId: keyId)}" --certificate-id=$certificateId --profile-name="$ppName" --profile-type="IOS_APP_STORE" --bundle-id=$ascBundleId', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + await runCommand( + logId: logId, + client: client, + command: 'ruby $rubyScriptName', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - final ppJson = jsonDecode(ppRes.stdout); - final ppBase64 = ppJson['body']['data']['attributes']['profileContent']; - final ppUuid = ppJson['body']['data']['attributes']['uuid']; - final ppId = ppJson['body']['data']['id']; + await runCommand( + logId: logId, + client: client, + command: '', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + try { await runCommand( logId: logId, client: client, command: - r'mkdir -p /Users/admin/Library/MobileDevice/Provisioning\ Profiles', + 'flutter build ipa --export-options-plist="/Users/admin/${workflow.currentWorkingDirectory}/ios/ExportOptions.plist"', currentWorkingDirectory: workflow.currentWorkingDirectory, jobId: buildJob.id, ); @@ -221,95 +308,171 @@ Future handleFlutterBuildIpa( await runCommand( logId: logId, client: client, - command: 'pwd', + command: + 'xcrun altool --upload-app --type ios -f build/ios/ipa/*.ipa --apiKey $keyId --apiIssuer $issuerId', currentWorkingDirectory: workflow.currentWorkingDirectory, jobId: buildJob.id, ); - + } finally { await runCommand( logId: logId, client: client, command: - 'echo $ppBase64 | base64 -d > "/Users/admin/Library/MobileDevice/Provisioning Profiles/$ppUuid.mobileprovision"', + 'openci_cli2 delete-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: workflow.currentWorkingDirectory, keyId: keyId)}" --profile-id=$ppId', currentWorkingDirectory: workflow.currentWorkingDirectory, jobId: buildJob.id, ); + } +} - final exportOptionsPlistBase64 = createPlistXmlAsBase64( - teamId: teamId, - bundleId: bundleId, - profileName: ppName, - ); +Future _deleteP12File(Firestore firestore) async { + final qs = firestore + .collection(secretsCollectionPath) + .where('key', WhereFilter.equal, 'OPENCI_ASC_P12_FILE'); + final docs = await qs.get(); + if (docs.docs.isNotEmpty) { + await docs.docs.first.ref.delete(); + } +} - await runCommand( - logId: logId, - client: client, - command: - 'echo $exportOptionsPlistBase64 | base64 -d > "/Users/admin/${workflow.currentWorkingDirectory}/ios/ExportOptions.plist"', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); +Future _deleteP12CertificateId(Firestore firestore) async { + final qs = firestore + .collection(secretsCollectionPath) + .where('key', WhereFilter.equal, 'OPENCI_ASC_P12_CERTIFICATE_ID'); + final docs = await qs.get(); + if (docs.docs.isNotEmpty) { + await docs.docs.first.ref.delete(); + } +} - final rubyScriptBase64 = generateXcodeprojRubyScriptBase64( - teamId: teamId, - profileName: ppName, - ); - const rubyScriptName = 'xcode_proj.rb'; +Future _fetchP12File(Firestore firestore) async { + final qs = firestore + .collection(secretsCollectionPath) + .where('key', WhereFilter.equal, 'OPENCI_ASC_P12_FILE'); + final docs = await qs.get(); + final data = docs.docs.first.data(); + final p12Base64 = data['value'] as String?; + if (p12Base64 == null) { + throw Exception('OPENCI_ASC_P12_FILE is not found'); + } - await runCommand( - logId: logId, - client: client, - command: - 'echo $rubyScriptBase64 | base64 -d > "/Users/admin/${workflow.currentWorkingDirectory}/$rubyScriptName"', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + return p12Base64; +} - await runCommand( - logId: logId, - client: client, - command: 'ruby $rubyScriptName', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); +Future _fetchP12CertificateId(Firestore firestore) async { + final qs = firestore + .collection(secretsCollectionPath) + .where('key', WhereFilter.equal, 'OPENCI_ASC_P12_CERTIFICATE_ID'); + final docs = await qs.get(); + final data = docs.docs.first.data(); + final certificateId = data['value'] as String?; + if (certificateId == null) { + throw Exception('OPENCI_ASC_P12_CERTIFICATE_ID is not found'); + } + return certificateId; +} - await runCommand( - logId: logId, - client: client, - command: '', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); +Future _createP12File({ + required String logId, + required SSHClient client, + required WorkflowModel workflow, + required BuildJob buildJob, + required Firestore firestore, + required String issuerId, + required String keyId, +}) async { + final result = await runCommand( + logId: logId, + client: client, + command: + 'openci_cli2 create-certificate --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: workflow.currentWorkingDirectory, keyId: keyId)}" --certificate-type=DISTRIBUTION', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); - try { - await runCommand( - logId: logId, - client: client, - command: - 'flutter build ipa --export-options-plist="/Users/admin/${workflow.currentWorkingDirectory}/ios/ExportOptions.plist"', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); + final resultJson = jsonDecode(result.stdout); + final resultCode = resultJson['statusCode']; - await runCommand( - logId: logId, - client: client, - command: - 'xcrun altool --upload-app --type ios -f build/ios/ipa/*.ipa --apiKey $keyId --apiIssuer $issuerId', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); - } finally { - await runCommand( - logId: logId, - client: client, - command: - 'openci_cli2 delete-provisioning-profile --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: workflow.currentWorkingDirectory, keyId: keyId)}" --profile-id=$ppId', - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); - } + if (resultCode != 201) { + throw Exception('Failed to create certificate'); + } + final keyBase64 = resultJson['key']; + + await runCommand( + logId: logId, + client: client, + command: + 'echo $keyBase64 | base64 -d > /Users/admin/Desktop/certificate.key', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + + final csrBase64 = + resultJson['body']['data']['attributes']['certificateContent']; + + final certificateId = resultJson['body']['data']['id'] as String; + + await runCommand( + logId: logId, + client: client, + command: + 'echo $csrBase64 | base64 -d > /Users/admin/Desktop/certificate.cer', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + + await runCommand( + logId: logId, + client: client, + command: + 'openssl pkcs12 -export -in /Users/admin/Desktop/certificate.cer -inkey /Users/admin/Desktop/certificate.key -out $_p12Path -name "Certificate Name" -passout pass:$_p12Password', + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); + return certificateId; +} + +Future _uploadP12File( + SSHClient client, + String logId, + String workflowId, + String buildJobId, + String currentWorkingDirectory, + String p12Path, + Firestore firestore, +) async { + final p12Base64Result = await runCommand( + logId: logId, + client: client, + command: 'base64 -i $p12Path', + currentWorkingDirectory: currentWorkingDirectory, + jobId: buildJobId, + ); + + if (p12Base64Result.exitCode != 0) { + throw Exception('Failed to encode p12 file to base64'); } + + final p12Base64 = p12Base64Result.stdout.trim(); + + await firestore.collection(secretsCollectionPath).add({ + 'key': 'OPENCI_ASC_P12_FILE', + 'value': p12Base64, + 'updatedAt': DateTime.now().millisecondsSinceEpoch ~/ 1000, + }); + print('Successfully uploaded p12 file'); +} + +Future _uploadP12CertificateId( + String certificateId, + Firestore firestore, +) async { + await firestore.collection(secretsCollectionPath).add({ + 'key': 'OPENCI_ASC_P12_CERTIFICATE_ID', + 'value': certificateId, + 'updatedAt': DateTime.now().millisecondsSinceEpoch ~/ 1000, + }); + print('Successfully uploaded p12 certificate id'); } String decodeBase64ToOriginalString(String base64String) { @@ -422,6 +585,38 @@ Future _wasP12FileUploadedWithinOneYear() async { return true; } +Future _isP12CertificateIdUploaded(Firestore firestore) async { + final qs = firestore + .collection(secretsCollectionPath) + .where('key', WhereFilter.equal, 'OPENCI_ASC_P12_CERTIFICATE_ID'); + final docs = await qs.get(); + return docs.docs.isNotEmpty; +} + +Future _isP12RegisteredInASC( + String certificateId, + SSHClient client, + String logId, + String workflowId, + String buildJobId, + String currentWorkingDirectory, + String issuerId, + String keyId, +) async { + final result = await runCommand( + logId: logId, + client: client, + command: + 'openci_cli2 read-certificate --issuer-id=$issuerId --key-id=$keyId --path-to-private-key="${authKeyPath(cwd: currentWorkingDirectory, keyId: keyId)}" --certificate-id=$certificateId', + currentWorkingDirectory: currentWorkingDirectory, + jobId: buildJobId, + ); + final stdout = result.stdout; + return stdout.contains('200') || + stdout.contains('201') || + stdout.contains('202'); +} + Future _areASCSecretsUploaded() async { final firestore = firestoreSignal.value!; final isIssuerIdUploaded = await _isIssuerIdUploaded(firestore); From 1e28494f78a3cf3185be358993ca50db601e7ede Mon Sep 17 00:00:00 2001 From: Masahiro Aoki Date: Tue, 4 Feb 2025 23:29:08 +0900 Subject: [PATCH 22/22] Fix runMultiCommands logic in openci_runner - Modify command execution flow to handle conditional command running - Bump dashboard version to 1.1.0+11 --- apps/dashboard/pubspec.yaml | 2 +- .../lib/src/run_multi_commands.dart | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/dashboard/pubspec.yaml b/apps/dashboard/pubspec.yaml index e7d22c0..c28c48f 100644 --- a/apps/dashboard/pubspec.yaml +++ b/apps/dashboard/pubspec.yaml @@ -1,7 +1,7 @@ name: dashboard description: "A Dashboard for OpenCI" publish_to: 'none' -version: 1.1.0+10 +version: 1.1.0+11 environment: sdk: ^3.6.0 diff --git a/apps/openci_runner/lib/src/run_multi_commands.dart b/apps/openci_runner/lib/src/run_multi_commands.dart index a54a42e..601e6ec 100644 --- a/apps/openci_runner/lib/src/run_multi_commands.dart +++ b/apps/openci_runner/lib/src/run_multi_commands.dart @@ -45,15 +45,15 @@ Future runMultiCommands( buildJob, firestore, ); + } else { + await runCommand( + logId: logId, + client: client, + command: processedCommand, + currentWorkingDirectory: workflow.currentWorkingDirectory, + jobId: buildJob.id, + ); } - - await runCommand( - logId: logId, - client: client, - command: processedCommand, - currentWorkingDirectory: workflow.currentWorkingDirectory, - jobId: buildJob.id, - ); } await updateBuildStatus(