diff --git a/.github/workflows/bank-sdk.publish.example.app.testflight.yml b/.github/workflows/bank-sdk.publish.example.app.testflight.yml index 1a4b5a0c54..1ba6d0ebf2 100644 --- a/.github/workflows/bank-sdk.publish.example.app.testflight.yml +++ b/.github/workflows/bank-sdk.publish.example.app.testflight.yml @@ -3,8 +3,8 @@ name: Upload GiniBankSDKExample to TestFlight permissions: contents: read on: - # schedule: - # - cron: '0 0 1 1,4,7,10 *' # 1st of Jan, Apr, Jul, Oct at midnight UTC + schedule: + - cron: '0 9 * 2,5,8,11 1' # Every Monday in Feb, May, Aug, Nov at 09:00 UTC workflow_dispatch: concurrency: @@ -13,10 +13,26 @@ concurrency: jobs: + check-schedule: + runs-on: ubuntu-latest + outputs: + should_run: ${{ steps.check.outputs.should_run }} + steps: + - id: check + run: | + day=$(date +%d | sed 's/^0//') + if [ "$day" -ge 8 ] && [ "$day" -le 14 ]; then + echo "should_run=true" >> $GITHUB_OUTPUT + else + echo "should_run=false" >> $GITHUB_OUTPUT + fi + upload-testflight: + needs: check-schedule + if: needs.check-schedule.outputs.should_run == 'true' || github.event_name == 'workflow_dispatch' uses: ./.github/workflows/publish.example.app.testflight.yml with: - xcode_version: '16.3' + xcode_version: '26.2' project_path: 'BankSDK/GiniBankSDKExample/GiniBankSDKExample.xcodeproj' scheme: 'GiniBankSDKExample' bundle_identifier: 'net.gini.banksdk.example' @@ -28,4 +44,4 @@ jobs: KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64}} APP_STORE_API_KEY_ID: ${{ secrets.APP_STORE_API_KEY_ID }} - APP_STORE_API_ISSUER_ID: ${{ secrets.APP_STORE_API_ISSUER_ID }} \ No newline at end of file + APP_STORE_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_API_KEY_ISSUER_ID }} \ No newline at end of file diff --git a/.github/workflows/health-sdk.publish.example.app.testflight.yml b/.github/workflows/health-sdk.publish.example.app.testflight.yml new file mode 100644 index 0000000000..e7ea04f9d4 --- /dev/null +++ b/.github/workflows/health-sdk.publish.example.app.testflight.yml @@ -0,0 +1,46 @@ +name: Upload GiniHealthSDKExample to TestFlight + +permissions: + contents: read +on: + schedule: + - cron: '0 9 * 2,5,8,11 1' # Every Monday in Feb, May, Aug, Nov at 09:00 UTC + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-publish + cancel-in-progress: ${{ !contains(github.ref, 'refs/tags/')}} + + +jobs: + check-schedule: + runs-on: ubuntu-latest + outputs: + should_run: ${{ steps.check.outputs.should_run }} + steps: + - id: check + run: | + day=$(date +%d | sed 's/^0//') + if [ "$day" -ge 8 ] && [ "$day" -le 14 ]; then + echo "should_run=true" >> $GITHUB_OUTPUT + else + echo "should_run=false" >> $GITHUB_OUTPUT + fi + + upload-testflight: + needs: check-schedule + if: needs.check-schedule.outputs.should_run == 'true' || github.event_name == 'workflow_dispatch' + uses: ./.github/workflows/publish.example.app.testflight.yml + with: + xcode_version: '26.2' + project_path: 'HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample.xcodeproj' + scheme: 'GiniHealthSDKExample' + bundle_identifier: 'net.gini.healthsdk.example' + ipa_name: 'GiniHealthSDKExample' + secrets: + MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64}} + APP_STORE_API_KEY_ID: ${{ secrets.APP_STORE_API_KEY_ID }} + APP_STORE_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_API_KEY_ISSUER_ID }} diff --git a/.github/workflows/publish.example.app.testflight.yml b/.github/workflows/publish.example.app.testflight.yml index a0a765034e..888c3afaf1 100644 --- a/.github/workflows/publish.example.app.testflight.yml +++ b/.github/workflows/publish.example.app.testflight.yml @@ -22,7 +22,7 @@ on: xcode_version: description: 'Xcode version to use' required: false - default: '16.3' + default: '26.2' type: string project_path: description: 'Relative path to the .xcodeproj file' @@ -61,7 +61,7 @@ on: required: true APP_STORE_API_KEY_ID: required: true - APP_STORE_API_ISSUER_ID: + APP_STORE_API_KEY_ISSUER_ID: required: true jobs: @@ -90,11 +90,12 @@ jobs: inputs.bundle_identifier_extension != '' && format(',{0}', inputs.bundle_identifier_extension) || '' }} - DISTRIBUTION_TYPE: appstore GIT_URL: ${{ secrets.MATCH_GIT_URL }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} - run: bundle exec fastlane download_profiles + run: | + DISTRIBUTION_TYPE=appstore bundle exec fastlane download_profiles + DISTRIBUTION_TYPE=adhoc APP_IDENTIFIER=${{ inputs.bundle_identifier }} bundle exec fastlane download_profiles - name: Archive Xcode Project run: | @@ -107,6 +108,7 @@ jobs: CODE_SIGN_IDENTITY="Apple Distribution" \ CODE_SIGN_STYLE=Manual \ DEVELOPMENT_TEAM=${{ inputs.team_id }} \ + CURRENT_PROJECT_VERSION=${{ github.run_number }} \ ONLY_ACTIVE_ARCH=NO - name: Export .ipa File @@ -122,16 +124,9 @@ jobs: files: "${{ inputs.ipa_name }}.ipa" fail: true - - name: Decode App Store Connect API Key - env: - APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }} - run: | - mkdir -p build_artifacts - echo "$APP_STORE_CONNECT_API_KEY" | base64 --decode > build_artifacts/AppStoreConnectAPIKey.p8 - - name: Upload to TestFlight env: APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_API_KEY_ID }} - APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_API_ISSUER_ID }} - APP_STORE_CONNECT_API_KEY_PATH: build_artifacts/AppStoreConnectAPIKey.p8 + APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_API_KEY_ISSUER_ID }} + APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }} run: bundle exec fastlane publish_to_testflight ipa_path:"${{ inputs.ipa_name }}.ipa" diff --git a/BankSDK/GiniBankSDKExample/GiniBankSDKExample/Info.plist b/BankSDK/GiniBankSDKExample/GiniBankSDKExample/Info.plist index 535d6f824d..b6e12f1217 100644 --- a/BankSDK/GiniBankSDKExample/GiniBankSDKExample/Info.plist +++ b/BankSDK/GiniBankSDKExample/GiniBankSDKExample/Info.plist @@ -77,7 +77,7 @@ CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ExportOptionsHealthExample.plist b/ExportOptionsAppStoreGiniHealthSDKExample.plist similarity index 54% rename from ExportOptionsHealthExample.plist rename to ExportOptionsAppStoreGiniHealthSDKExample.plist index 6d321d4615..6e52809767 100644 --- a/ExportOptionsHealthExample.plist +++ b/ExportOptionsAppStoreGiniHealthSDKExample.plist @@ -2,25 +2,12 @@ - compileBitcode - - iCloudContainerEnvironment - Production - manifest - - appURL - https:// - displayImageURL - https:// - fullSizeImageURL - https:// - method - ad-hoc + app-store-connect provisioningProfiles net.gini.healthsdk.example - Gini Health SDK Example App Ad-Hoc Distribution + match AppStore net.gini.healthsdk.example signingCertificate Apple Distribution @@ -31,6 +18,6 @@ teamID JA825X8F7Z thinning - <none> + none diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 21948e74b2..b91f03f5f8 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -665,7 +665,7 @@ end Environment Variables: APP_STORE_CONNECT_API_KEY_ID - App Store Connect API key ID APP_STORE_CONNECT_API_ISSUER_ID - App Store Connect API issuer ID - APP_STORE_CONNECT_API_KEY_PATH - Path to the .p8 API key file + APP_STORE_CONNECT_API_KEY - App Store Connect API key content (base64-encoded .p8) DOC lane :build_and_upload_to_testflight do |options| ipa_name = options[:ipa_name] || options[:scheme] || UI.user_error!("Missing required option: ipa_name or scheme") @@ -676,7 +676,7 @@ end end desc <<~DOC - Upload an .ipa to TestFlight via xcrun altool. + Upload an .ipa to TestFlight via the App Store Connect API. Parameters: ipa_path - Path to the .ipa file (default: "./GiniBankSDKExample.ipa") @@ -684,49 +684,34 @@ end Environment Variables: APP_STORE_CONNECT_API_KEY_ID - App Store Connect API key ID APP_STORE_CONNECT_API_ISSUER_ID - App Store Connect API issuer ID - APP_STORE_CONNECT_API_KEY_PATH - Path to the .p8 API key file - (default: build_artifacts/AppStoreConnectAPIKey.p8) + APP_STORE_CONNECT_API_KEY - App Store Connect API key content (base64-encoded .p8) DOC lane :publish_to_testflight do |options| - ipa_path = options[:ipa_path] || "./GiniBankSDKExample.ipa" - key_id = ENV['APP_STORE_CONNECT_API_KEY_ID'] - issuer_id = ENV['APP_STORE_CONNECT_API_ISSUER_ID'] - key_path = ENV['APP_STORE_CONNECT_API_KEY_PATH'] || "build_artifacts/AppStoreConnectAPIKey.p8" + ipa_path = options[:ipa_path] || "./GiniBankSDKExample.ipa" + key_id = ENV['APP_STORE_CONNECT_API_KEY_ID'] + issuer_id = ENV['APP_STORE_CONNECT_API_ISSUER_ID'] + key_base64 = ENV['APP_STORE_CONNECT_API_KEY'] repo_root = File.expand_path("..", __dir__) - resolved_key_path = File.expand_path(key_path, repo_root) resolved_ipa_path = File.expand_path(ipa_path, repo_root) UI.user_error!("APP_STORE_CONNECT_API_KEY_ID is not set") if key_id.nil? || key_id.empty? UI.user_error!("APP_STORE_CONNECT_API_ISSUER_ID is not set") if issuer_id.nil? || issuer_id.empty? - UI.user_error!("API key not found at: #{resolved_key_path}") unless File.exist?(resolved_key_path) + UI.user_error!("APP_STORE_CONNECT_API_KEY is not set") if key_base64.nil? || key_base64.empty? UI.user_error!("IPA not found at: #{resolved_ipa_path}") unless File.exist?(resolved_ipa_path) - # xcrun altool reads .p8 keys from ~/.appstoreconnect/private_keys/AuthKey_.p8 - private_keys_dir = File.expand_path("~/.appstoreconnect/private_keys") - expected_key_path = "#{private_keys_dir}/AuthKey_#{key_id}.p8" - created_temporary_key = false - sh("mkdir -p '#{private_keys_dir}'") - unless resolved_key_path == expected_key_path - sh("cp '#{resolved_key_path}' '#{expected_key_path}'") - created_temporary_key = true - end - # Ensure restrictive permissions on the API key file - sh("chmod 600 '#{expected_key_path}'") if File.exist?(expected_key_path) - - begin - UI.message "🚀 Uploading #{resolved_ipa_path} to TestFlight..." - sh( - "xcrun altool --upload-app" \ - " --file '#{resolved_ipa_path}'" \ - " --type ios" \ - " --apiKey '#{key_id}'" \ - " --apiIssuer '#{issuer_id}'" - ) - ensure - # Remove the temporary key copy to avoid leaving credentials on disk - sh("rm -f '#{expected_key_path}'") if created_temporary_key - end + api_key = app_store_connect_api_key( + key_id: key_id, + issuer_id: issuer_id, + key_content: key_base64, + is_key_content_base64: true + ) + + upload_to_testflight( + ipa: resolved_ipa_path, + api_key: api_key, + skip_waiting_for_build_processing: true + ) end end