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