From d485f4b33528bfb5dc06cfecdd29c0eb6efbf4af Mon Sep 17 00:00:00 2001 From: rzuasti Date: Sun, 7 Jun 2026 11:31:53 -0400 Subject: [PATCH] Add iOS TestFlight CI via Codemagic Set up automated cloud iOS builds and TestFlight distribution without a local Mac: - Add codemagic.yaml: iOS->TestFlight workflow with automatic signing, build-number auto-increment, triggered by the vX.Y.Z release tags. - Set the iOS bundle identifier to net.oott-security.app (was the placeholder com.example.frontend). - Set the iOS display name to OOTT. Co-Authored-By: Claude Opus 4.8 --- codemagic.yaml | 74 +++++++++++++++++++ frontend/ios/Runner.xcodeproj/project.pbxproj | 12 +-- frontend/ios/Runner/Info.plist | 2 +- 3 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 codemagic.yaml diff --git a/codemagic.yaml b/codemagic.yaml new file mode 100644 index 0000000..4214d36 --- /dev/null +++ b/codemagic.yaml @@ -0,0 +1,74 @@ +# Codemagic CI/CD configuration for OOTT. +# +# Builds the Flutter iOS app on a cloud macOS machine and publishes it to +# TestFlight. No local Mac is required. +# +# Triggered automatically by the `vX.Y.Z` git tags that `release.sh` pushes, +# so cutting a release also ships a new TestFlight build. Can also be run +# on demand via the "Run build" button in the Codemagic UI. +# +# SECRETS: none live in this repo. The App Store Connect API key (.p8, Key ID, +# Issuer ID) is stored in Codemagic's encrypted credential store and referenced +# here only by integration name. Signing certificates and provisioning profiles +# are fetched on the build machine at build time and discarded with it. +# +# One-time setup required in the Codemagic UI before the first build: +# 1. Add an App Store Connect integration (upload the .p8, Key ID, Issuer ID) +# and put its name in `integrations.app_store_connect` below. +# 2. Create an environment variable group named `appstore_connect` containing +# APP_STORE_APP_ID = the numeric Apple ID of the app record in +# App Store Connect. + +workflows: + ios-testflight: + name: iOS TestFlight + instance_type: mac_mini_m2 + max_build_duration: 60 + working_directory: frontend + integrations: + # Replace with the name of the App Store Connect integration configured + # in the Codemagic UI. + app_store_connect: OOTT App Store Connect + environment: + flutter: stable + xcode: latest + cocoapods: default + groups: + - appstore_connect # provides APP_STORE_APP_ID (not a secret) + vars: + BUNDLE_ID: "net.oott-security.app" # not secret; ships inside every app + triggering: + events: + - tag + tag_patterns: + - pattern: "v*" # matches the vX.Y.Z tags release.sh pushes + include: true + scripts: + - name: Set up automatic code signing + script: | + keychain initialize + app-store-connect fetch-signing-files "$BUNDLE_ID" \ + --type IOS_APP_STORE \ + --create + keychain add-certificates + xcode-project use-profiles + - name: Get Flutter packages + script: flutter pub get + - name: Run frontend tests + script: flutter test + - name: Build IPA + script: | + # Each TestFlight upload needs a unique, increasing build number. + BUILD_NUMBER=$(($(app-store-connect get-latest-testflight-build-number "$APP_STORE_APP_ID") + 1)) + flutter build ipa --release \ + --build-number=$BUILD_NUMBER \ + --export-options-plist=/Users/builder/export_options.plist + artifacts: + - build/ios/ipa/*.ipa + - /tmp/xcodebuild_logs/*.log + publishing: + app_store_connect: + auth: integration + submit_to_testflight: true + beta_groups: + - Internal Testers diff --git a/frontend/ios/Runner.xcodeproj/project.pbxproj b/frontend/ios/Runner.xcodeproj/project.pbxproj index dc26eb9..ade41ec 100644 --- a/frontend/ios/Runner.xcodeproj/project.pbxproj +++ b/frontend/ios/Runner.xcodeproj/project.pbxproj @@ -368,7 +368,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.frontend; + PRODUCT_BUNDLE_IDENTIFIER = "net.oott-security.app"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -384,7 +384,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.frontend.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = "net.oott-security.app.RunnerTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -401,7 +401,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.frontend.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = "net.oott-security.app.RunnerTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -416,7 +416,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.frontend.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = "net.oott-security.app.RunnerTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -547,7 +547,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.frontend; + PRODUCT_BUNDLE_IDENTIFIER = "net.oott-security.app"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -569,7 +569,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.frontend; + PRODUCT_BUNDLE_IDENTIFIER = "net.oott-security.app"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/frontend/ios/Runner/Info.plist b/frontend/ios/Runner/Info.plist index 848830a..b110363 100644 --- a/frontend/ios/Runner/Info.plist +++ b/frontend/ios/Runner/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Frontend + OOTT CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier