FIX:: IPA output dir (#7149)

This commit is contained in:
Marcos Rodriguez Vélez 2024-10-12 09:26:12 -04:00 committed by GitHub
parent bfb69b87fb
commit d6376afbb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 325 additions and 238 deletions

View File

@ -17,65 +17,88 @@ jobs:
outputs: outputs:
new_build_number: ${{ steps.generate_build_number.outputs.build_number }} new_build_number: ${{ steps.generate_build_number.outputs.build_number }}
project_version: ${{ steps.determine_marketing_version.outputs.project_version }} project_version: ${{ steps.determine_marketing_version.outputs.project_version }}
ipa_output_path: ${{ steps.build_app.outputs.ipa_output_path }}
latest_commit_message: ${{ steps.get_latest_commit_message.outputs.commit_message }} latest_commit_message: ${{ steps.get_latest_commit_message.outputs.commit_message }}
env: env:
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
steps: steps:
- name: Checkout project - name: Checkout Project
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 # Fetches all history fetch-depth: 0 # Fetches all history
- name: Specify Node.js Version
- name: Specify node version
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
- uses: maxim-lobanov/setup-xcode@v1 - uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: 16.0 xcode-version: 16.0
- name: Set up Ruby - name: Set Up Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
ruby-version: 3.1.6 ruby-version: 3.1.6
bundler-cache: true bundler-cache: true
- name: Install dependencies with Bundler - name: Install Dependencies with Bundler
run: | run: |
bundle config path vendor/bundle bundle config path vendor/bundle
bundle install --jobs 4 --retry 3 --quiet bundle install --jobs 4 --retry 3 --quiet
- name: Install node_modules - name: Install Node Modules
run: npm install --omit=dev --yes run: npm install --omit=dev --yes
- name: Install CocoaPods Dependencies - name: Install CocoaPods Dependencies
run: | run: |
bundle exec fastlane ios install_pods bundle exec fastlane ios install_pods
- name: Cache CocoaPods Pods - name: Cache CocoaPods Pods
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ios/Pods path: ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
- name: Display release-notes.txt restore-keys: |
run: cat release-notes.txt ${{ runner.os }}-pods-
- name: Get Latest Commit Message - name: Get Latest Commit Message
id: get_latest_commit_message id: get_latest_commit_message
run: | run: |
LATEST_COMMIT_MESSAGE=$(git log -1 --pretty=format:"%s") LATEST_COMMIT_MESSAGE=$(git log -1 --pretty=format:"%s")
echo "LATEST_COMMIT_MESSAGE=${LATEST_COMMIT_MESSAGE}" >> $GITHUB_ENV echo "LATEST_COMMIT_MESSAGE=${LATEST_COMMIT_MESSAGE}" >> $GITHUB_ENV
echo "::set-output name=commit_message::$LATEST_COMMIT_MESSAGE" echo "commit_message=$LATEST_COMMIT_MESSAGE" >> $GITHUB_OUTPUT
- name: Set up Git Authentication
- name: Generate Build Number Based on Timestamp
id: generate_build_number
run: |
NEW_BUILD_NUMBER=$(date +%s)
echo "NEW_BUILD_NUMBER=$NEW_BUILD_NUMBER" >> $GITHUB_ENV
echo "build_number=$NEW_BUILD_NUMBER" >> $GITHUB_OUTPUT
- name: Set Build Number
run: bundle exec fastlane ios increment_build_number_lane
- name: Determine Marketing Version
id: determine_marketing_version
run: |
MARKETING_VERSION=$(grep MARKETING_VERSION BlueWallet.xcodeproj/project.pbxproj | awk -F '= ' '{print $2}' | tr -d ' ;' | head -1)
echo "PROJECT_VERSION=$MARKETING_VERSION" >> $GITHUB_ENV
echo "project_version=$MARKETING_VERSION" >> $GITHUB_OUTPUT
working-directory: ios
- name: Set Up Git Authentication
env: env:
ACCESS_TOKEN: ${{ secrets.GIT_ACCESS_TOKEN }} ACCESS_TOKEN: ${{ secrets.GIT_ACCESS_TOKEN }}
run: | run: |
git config --global credential.helper 'cache --timeout=3600' git config --global credential.helper 'cache --timeout=3600'
git config --global http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n x-access-token:${ACCESS_TOKEN} | base64)" git config --global http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n x-access-token:${ACCESS_TOKEN} | base64)"
- name: Create Temporary Keychain - name: Create Temporary Keychain
run: bundle exec fastlane ios create_temp_keychain run: bundle exec fastlane ios create_temp_keychain
env: env:
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
- name: Setup Provisioning Profiles - name: Setup Provisioning Profiles
env: env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
@ -86,14 +109,16 @@ jobs:
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: | run: |
bundle exec fastlane ios setup_provisioning_profiles bundle exec fastlane ios setup_provisioning_profiles
- name: Cache Provisioning Profiles - name: Cache Provisioning Profiles
id: cache_provisioning_profiles id: cache_provisioning_profiles
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ~/Library/MobileDevice/Provisioning Profiles path: ~/Library/MobileDevice/Provisioning\ Profiles
key: ${{ runner.os }}-provisioning-profiles-${{ github.sha }} key: ${{ runner.os }}-provisioning-profiles-${{ github.sha }}
restore-keys: | restore-keys: |
${{ runner.os }}-provisioning-profiles- ${{ runner.os }}-provisioning-profiles-
- name: Check Cache Status for Provisioning Profiles - name: Check Cache Status for Provisioning Profiles
run: | run: |
if [ -n "${{ steps.cache_provisioning_profiles.outputs.cache-hit }}" ]; then if [ -n "${{ steps.cache_provisioning_profiles.outputs.cache-hit }}" ]; then
@ -101,41 +126,36 @@ jobs:
else else
echo "No cache found for provisioning profiles. A new cache will be created." echo "No cache found for provisioning profiles. A new cache will be created."
fi fi
- name: Verify Provisioning Profiles Exist - name: Verify Provisioning Profiles Exist
run: | run: |
if [ -d "~/Library/MobileDevice/Provisioning Profiles" ]; then if [ -d "~/Library/MobileDevice/Provisioning Profiles" ]; then
echo "Provisioning profiles are available in the cache." echo "Provisioning profiles are available in the cache."
ls -la ~/Library/MobileDevice/Provisioning Profiles ls -la ~/Library/MobileDevice/Provisioning\ Profiles
else else
echo "Provisioning profiles directory does not exist." echo "Provisioning profiles directory does not exist."
fi fi
- name: Generate Build Number based on timestamp
id: generate_build_number
run: |
NEW_BUILD_NUMBER=$(date +%s)
echo "NEW_BUILD_NUMBER=$NEW_BUILD_NUMBER" >> $GITHUB_ENV
echo "::set-output name=build_number::$NEW_BUILD_NUMBER"
- name: Set Build Number
run: bundle exec fastlane ios increment_build_number_lane
- name: Determine Marketing Version
id: determine_marketing_version
run: |
MARKETING_VERSION=$(grep MARKETING_VERSION BlueWallet.xcodeproj/project.pbxproj | awk -F '= ' '{print $2}' | tr -d ' ;' | head -1)
echo "PROJECT_VERSION=$MARKETING_VERSION" >> $GITHUB_ENV
echo "::set-output name=project_version::$MARKETING_VERSION"
working-directory: ios
- name: Expected IPA file name
run: |
echo "IPA file name: BlueWallet_${{env.PROJECT_VERSION}}(${{env.NEW_BUILD_NUMBER}}).ipa"
- name: Build App - name: Build App
run: bundle exec fastlane ios build_app_lane id: build_app
run: |
bundle exec fastlane ios build_app_lane --verbose
echo "ipa_output_path=$IPA_OUTPUT_PATH" >> $GITHUB_OUTPUT # Set the IPA output path for future jobs
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: build_logs
path: ./ios/build_logs/
- name: Upload IPA as Artifact - name: Upload IPA as Artifact
if: success()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: BlueWallet_${{env.PROJECT_VERSION}}_${{env.NEW_BUILD_NUMBER}}.ipa name: BlueWallet_${{env.PROJECT_VERSION}}_${{env.NEW_BUILD_NUMBER}}.ipa
path: ./build/BlueWallet_${{env.PROJECT_VERSION}}_${{env.NEW_BUILD_NUMBER}}.ipa path: ${{ env.IPA_OUTPUT_PATH }} # Directly from Fastfile `IPA_OUTPUT_PATH`
testflight-upload: testflight-upload:
needs: build needs: build
runs-on: macos-latest runs-on: macos-latest
@ -146,13 +166,15 @@ jobs:
PROJECT_VERSION: ${{ needs.build.outputs.project_version }} PROJECT_VERSION: ${{ needs.build.outputs.project_version }}
LATEST_COMMIT_MESSAGE: ${{ needs.build.outputs.latest_commit_message }} LATEST_COMMIT_MESSAGE: ${{ needs.build.outputs.latest_commit_message }}
steps: steps:
- name: Checkout project - name: Checkout Project
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Ruby
- name: Set Up Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
ruby-version: 3.1.6 ruby-version: 3.1.6
bundler-cache: true bundler-cache: true
- name: Cache Ruby Gems - name: Cache Ruby Gems
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
@ -160,19 +182,43 @@ jobs:
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-gems- ${{ runner.os }}-gems-
- name: Install dependencies with Bundler
- name: Install Dependencies with Bundler
run: | run: |
bundle config path vendor/bundle bundle config path vendor/bundle
bundle install --jobs 4 --retry 3 --quiet bundle install --jobs 4 --retry 3 --quiet
- name: Download IPA from Artifact - name: Download IPA from Artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: BlueWallet_${{ needs.build.outputs.project_version }}_${{ needs.build.outputs.new_build_number }}.ipa name: BlueWallet_${{ needs.build.outputs.project_version }}_${{ needs.build.outputs.new_build_number }}.ipa
path: ./ path: ./ # Download the IPA file to the current working directory
- name: Create App Store Connect API Key JSON - name: Create App Store Connect API Key JSON
run: echo '${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}' > ./appstore_api_key.json run: echo '${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}' > ./appstore_api_key.json
- name: Verify IPA File Download
run: |
echo "Current directory:"
pwd
echo "Files in current directory:"
ls -la ./
- name: Set IPA Path Environment Variable
run: echo "IPA_OUTPUT_PATH=$(pwd)/BlueWallet_${{ needs.build.outputs.project_version }}_${{ needs.build.outputs.new_build_number }}.ipa" >> $GITHUB_ENV
- name: Verify IPA Path Before Upload
run: |
if [ ! -f "$IPA_OUTPUT_PATH" ]; then
echo "IPA file not found at path: $IPA_OUTPUT_PATH"
exit 1
fi
- name: Upload to TestFlight - name: Upload to TestFlight
env: run: |
ls -la $IPA_OUTPUT_PATH
bundle exec fastlane ios upload_to_testflight_lane
env:
APP_STORE_CONNECT_API_KEY_PATH: $(pwd)/appstore_api_key.p8 APP_STORE_CONNECT_API_KEY_PATH: $(pwd)/appstore_api_key.p8
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
GIT_ACCESS_TOKEN: ${{ secrets.GIT_ACCESS_TOKEN }} GIT_ACCESS_TOKEN: ${{ secrets.GIT_ACCESS_TOKEN }}
@ -182,7 +228,8 @@ jobs:
APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }} APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }}
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: bundle exec fastlane ios upload_to_testflight_lane IPA_OUTPUT_PATH: ${{ env.IPA_OUTPUT_PATH }}
- name: Post PR Comment - name: Post PR Comment
if: success() && github.event_name == 'pull_request' if: success() && github.event_name == 'pull_request'
uses: actions/github-script@v6 uses: actions/github-script@v6
@ -199,4 +246,4 @@ jobs:
...repo, ...repo,
issue_number: prNumber, issue_number: prNumber,
body: message, body: message,
}); });

View File

@ -24,20 +24,20 @@ GEM
artifactory (3.0.17) artifactory (3.0.17)
atomos (0.1.3) atomos (0.1.3)
aws-eventstream (1.3.0) aws-eventstream (1.3.0)
aws-partitions (1.973.0) aws-partitions (1.989.0)
aws-sdk-core (3.204.0) aws-sdk-core (3.209.1)
aws-eventstream (~> 1, >= 1.3.0) aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0) aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.9) aws-sigv4 (~> 1.9)
jmespath (~> 1, >= 1.6.1) jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.90.0) aws-sdk-kms (1.94.0)
aws-sdk-core (~> 3, >= 3.203.0) aws-sdk-core (~> 3, >= 3.207.0)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.161.0) aws-sdk-s3 (1.167.0)
aws-sdk-core (~> 3, >= 3.203.0) aws-sdk-core (~> 3, >= 3.207.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
aws-sigv4 (1.9.1) aws-sigv4 (1.10.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4) babosa (1.0.4)
base64 (0.2.0) base64 (0.2.0)
@ -96,8 +96,8 @@ GEM
escape (0.0.4) escape (0.0.4)
ethon (0.16.0) ethon (0.16.0)
ffi (>= 1.15.0) ffi (>= 1.15.0)
excon (0.111.0) excon (0.112.0)
faraday (1.10.3) faraday (1.10.4)
faraday-em_http (~> 1.0) faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0) faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1) faraday-excon (~> 1.1)
@ -123,10 +123,10 @@ GEM
faraday-patron (1.0.0) faraday-patron (1.0.0)
faraday-rack (1.0.0) faraday-rack (1.0.0)
faraday-retry (1.0.3) faraday-retry (1.0.3)
faraday_middleware (1.2.0) faraday_middleware (1.2.1)
faraday (~> 1.0) faraday (~> 1.0)
fastimage (2.3.1) fastimage (2.3.1)
fastlane (2.222.0) fastlane (2.224.0)
CFPropertyList (>= 2.3, < 4.0.0) CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0) addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0) artifactory (~> 3.0)
@ -214,16 +214,17 @@ GEM
http-cookie (1.0.7) http-cookie (1.0.7)
domain_name (~> 0.5) domain_name (~> 0.5)
httpclient (2.8.3) httpclient (2.8.3)
i18n (1.14.5) i18n (1.14.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jmespath (1.6.2) jmespath (1.6.2)
json (2.7.2) json (2.7.2)
jwt (2.8.2) jwt (2.9.3)
base64 base64
logger (1.6.1) logger (1.6.1)
mime-types (3.5.2) mime-types (3.6.0)
logger
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2024.0903) mime-types-data (3.2024.1001)
mini_magick (4.13.2) mini_magick (4.13.2)
mini_mime (1.1.5) mini_mime (1.1.5)
minitest (5.25.1) minitest (5.25.1)
@ -250,7 +251,7 @@ GEM
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
netrc (~> 0.8) netrc (~> 0.8)
retriable (3.1.2) retriable (3.1.2)
rexml (3.3.7) rexml (3.3.8)
rouge (2.0.7) rouge (2.0.7)
ruby-macho (2.5.1) ruby-macho (2.5.1)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
@ -278,15 +279,15 @@ GEM
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
uber (0.1.0) uber (0.1.0)
unicode-display_width (2.5.0) unicode-display_width (2.6.0)
word_wrap (1.0.0) word_wrap (1.0.0)
xcodeproj (1.25.0) xcodeproj (1.25.1)
CFPropertyList (>= 2.3.3, < 4.0) CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3) atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0) claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1) colored2 (~> 3.1)
nanaimo (~> 0.3.0) nanaimo (~> 0.3.0)
rexml (>= 3.3.2, < 4.0) rexml (>= 3.3.6, < 4.0)
xcpretty (0.3.0) xcpretty (0.3.0)
rouge (~> 2.0.7) rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1) xcpretty-travis-formatter (1.0.1)

View File

@ -1,75 +1,75 @@
# Define app identifiers once for reuse across lanes
def app_identifiers def app_identifiers
["io.bluewallet.bluewallet", "io.bluewallet.bluewallet.watch", "io.bluewallet.bluewallet.watch.extension", "io.bluewallet.bluewallet.Stickers", "io.bluewallet.bluewallet.MarketWidget"] [
"io.bluewallet.bluewallet",
"io.bluewallet.bluewallet.watch",
"io.bluewallet.bluewallet.watch.extension",
"io.bluewallet.bluewallet.Stickers",
"io.bluewallet.bluewallet.MarketWidget"
]
end end
default_platform(:android) default_platform(:android)
project_root = File.expand_path("..", __dir__) project_root = File.expand_path("..", __dir__)
# ===========================
# Android Lanes
# ===========================
platform :android do platform :android do
desc "Prepare the keystore file" desc "Prepare the keystore file"
lane :prepare_keystore do lane :prepare_keystore do
Dir.chdir(project_root) do keystore_file_hex = ENV['KEYSTORE_FILE_HEX']
keystore_file_hex = ENV['KEYSTORE_FILE_HEX'] UI.user_error!("KEYSTORE_FILE_HEX environment variable is missing") if keystore_file_hex.nil?
UI.user_error!("KEYSTORE_FILE_HEX environment variable is missing") if keystore_file_hex.nil?
Dir.chdir("android") do Dir.chdir("android") do
UI.message("Creating keystore hex file...") UI.message("Creating keystore from HEX...")
File.write("bluewallet-release-key.keystore.hex", keystore_file_hex)
File.write("bluewallet-release-key.keystore.hex", keystore_file_hex) sh("xxd -plain -revert bluewallet-release-key.keystore.hex > bluewallet-release-key.keystore") do |status|
UI.user_error!("Error reverting hex to keystore") unless status.success?
sh("xxd -plain -revert bluewallet-release-key.keystore.hex > bluewallet-release-key.keystore") do |status|
UI.user_error!("Error reverting hex to keystore") unless status.success?
end
UI.message("Keystore created successfully.")
File.delete("bluewallet-release-key.keystore.hex")
end end
UI.message("Keystore created successfully.")
File.delete("bluewallet-release-key.keystore.hex")
end end
end end
desc "Update version, build number, and sign APK"
lane :update_version_build_and_sign_apk do lane :update_version_build_and_sign_apk do
Dir.chdir(project_root) do Dir.chdir(project_root) do
build_number = ENV['BUILD_NUMBER'] build_number = ENV['BUILD_NUMBER']
UI.user_error!("BUILD_NUMBER environment variable is missing") if build_number.nil? UI.user_error!("BUILD_NUMBER environment variable is missing") if build_number.nil?
# Get the version name from build.gradle # Extract versionName from build.gradle
version_name = sh("grep versionName android/app/build.gradle | awk '{print $2}' | tr -d '\"'").strip version_name = sh("grep versionName android/app/build.gradle | awk '{print $2}' | tr -d '\"'").strip
# Manually update the versionCode in build.gradle # Update versionCode in build.gradle
UI.message("Updating versionCode in build.gradle to #{build_number}...") UI.message("Updating versionCode in build.gradle to #{build_number}...")
build_gradle_path = "android/app/build.gradle" build_gradle_path = "android/app/build.gradle"
build_gradle_contents = File.read(build_gradle_path) build_gradle_contents = File.read(build_gradle_path)
new_build_gradle_contents = build_gradle_contents.gsub(/versionCode\s+\d+/, "versionCode #{build_number}") new_build_gradle_contents = build_gradle_contents.gsub(/versionCode\s+\d+/, "versionCode #{build_number}")
File.write(build_gradle_path, new_build_gradle_contents) File.write(build_gradle_path, new_build_gradle_contents)
# Get the branch name and default to 'master' if empty # Determine branch name
branch_name = ENV['GITHUB_HEAD_REF'] || `git rev-parse --abbrev-ref HEAD`.strip.gsub(/[\/\\:?*"<>|]/, '_') branch_name = ENV['GITHUB_HEAD_REF'] || `git rev-parse --abbrev-ref HEAD`.strip.gsub(/[\/\\:?*"<>|]/, '_')
if branch_name.nil? || branch_name.empty? branch_name = 'master' if branch_name.nil? || branch_name.empty?
branch_name = 'master'
end # Define APK name based on branch
signed_apk_name = branch_name != 'master' ? "BlueWallet-#{version_name}-#{build_number}-#{branch_name}.apk" : "BlueWallet-#{version_name}-#{build_number}.apk"
# Append branch name only if it's not 'master'
if branch_name != 'master' # Build APK
signed_apk_name = "BlueWallet-#{version_name}-#{build_number}-#{branch_name}.apk"
else
signed_apk_name = "BlueWallet-#{version_name}-#{build_number}.apk"
end
# Continue with the build process
Dir.chdir("android") do Dir.chdir("android") do
UI.message("Building APK...") UI.message("Building APK...")
gradle( gradle(task: "assembleRelease", project_dir: "android")
task: "assembleRelease",
project_dir: "android"
)
UI.message("APK build completed.") UI.message("APK build completed.")
# Define the output paths # Define paths
unsigned_apk_path = "app/build/outputs/apk/release/app-release-unsigned.apk" unsigned_apk_path = "app/build/outputs/apk/release/app-release-unsigned.apk"
signed_apk_path = "app/build/outputs/apk/release/#{signed_apk_name}" signed_apk_path = "app/build/outputs/apk/release/#{signed_apk_name}"
# Rename the unsigned APK to include the version and build number # Rename APK
if File.exist?(unsigned_apk_path) if File.exist?(unsigned_apk_path)
UI.message("Renaming APK to #{signed_apk_name}...") UI.message("Renaming APK to #{signed_apk_name}...")
FileUtils.mv(unsigned_apk_path, signed_apk_path) FileUtils.mv(unsigned_apk_path, signed_apk_path)
@ -78,8 +78,8 @@ platform :android do
UI.error("Unsigned APK not found at path: #{unsigned_apk_path}") UI.error("Unsigned APK not found at path: #{unsigned_apk_path}")
next next
end end
# Sign the APK using apksigner # Sign APK
UI.message("Signing APK with apksigner...") UI.message("Signing APK with apksigner...")
apksigner_path = "#{ENV['ANDROID_HOME']}/build-tools/34.0.0/apksigner" apksigner_path = "#{ENV['ANDROID_HOME']}/build-tools/34.0.0/apksigner"
sh("#{apksigner_path} sign --ks ./bluewallet-release-key.keystore --ks-pass=pass:#{ENV['KEYSTORE_PASSWORD']} #{signed_apk_path}") sh("#{apksigner_path} sign --ks ./bluewallet-release-key.keystore --ks-pass=pass:#{ENV['KEYSTORE_PASSWORD']} #{signed_apk_path}")
@ -91,33 +91,32 @@ platform :android do
desc "Upload APK to BrowserStack and post result as PR comment" desc "Upload APK to BrowserStack and post result as PR comment"
lane :upload_to_browserstack_and_comment do lane :upload_to_browserstack_and_comment do
Dir.chdir(project_root) do Dir.chdir(project_root) do
# Fetch the APK path from environment variables # Determine APK path
apk_path = ENV['APK_PATH'] apk_path = ENV['APK_PATH']
# Attempt to find the APK if not provided
if apk_path.nil? || apk_path.empty? if apk_path.nil? || apk_path.empty?
UI.message("No APK path provided, attempting to find the artifact...") UI.message("No APK path provided, searching for APK...")
apk_path = `find ./ -name "*.apk"`.strip apk_path = `find ./ -name "*.apk"`.strip
UI.user_error!("No APK file found") if apk_path.nil? || apk_path.empty? UI.user_error!("No APK file found") if apk_path.nil? || apk_path.empty?
end end
# Upload to BrowserStack
UI.message("Uploading APK to BrowserStack: #{apk_path}...") UI.message("Uploading APK to BrowserStack: #{apk_path}...")
upload_to_browserstack_app_live( upload_to_browserstack_app_live(
file_path: apk_path, file_path: apk_path,
browserstack_username: ENV['BROWSERSTACK_USERNAME'], browserstack_username: ENV['BROWSERSTACK_USERNAME'],
browserstack_access_key: ENV['BROWSERSTACK_ACCESS_KEY'] browserstack_access_key: ENV['BROWSERSTACK_ACCESS_KEY']
) )
# Extract the BrowserStack URL from the output # Extract BrowserStack URL
app_url = ENV['BROWSERSTACK_LIVE_APP_ID'] app_url = ENV['BROWSERSTACK_LIVE_APP_ID']
UI.user_error!("BrowserStack upload failed, no app URL returned") if app_url.nil? || app_url.empty? UI.user_error!("BrowserStack upload failed, no app URL returned") if app_url.nil? || app_url.empty?
# Prepare necessary values for the PR comment # Prepare PR comment
apk_filename = File.basename(apk_path) apk_filename = File.basename(apk_path)
apk_download_url = ENV['APK_OUTPUT_PATH'] # Assuming this path is accessible to the PR apk_download_url = ENV['APK_OUTPUT_PATH'] # Ensure this path is accessible
browserstack_hashed_id = app_url.gsub('bs://', '') browserstack_hashed_id = app_url.gsub('bs://', '')
pr_number = ENV['GITHUB_PR_NUMBER'] pr_number = ENV['GITHUB_PR_NUMBER']
comment = <<~COMMENT comment = <<~COMMENT
### APK Successfully Uploaded to BrowserStack ### APK Successfully Uploaded to BrowserStack
@ -127,16 +126,17 @@ platform :android do
- [Google Pixel 7 (Android 13.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Google+Pixel+7&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true) - [Google Pixel 7 (Android 13.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Google+Pixel+7&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
- [Google Pixel 8 (Android 14.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=14.0&device=Google+Pixel+8&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true) - [Google Pixel 8 (Android 14.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=14.0&device=Google+Pixel+8&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
- [Google Pixel 3a (Android 9.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=9.0&device=Google+Pixel+3a&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true) - [Google Pixel 3a (Android 9.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=9.0&device=Google+Pixel+3a&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
- [Samsung Galaxy Z Fold 5 (Android 13.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Samsung+Galaxy+Z+Fold+5&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true) - [Samsung Galaxy Z Fold 5 (Android 13.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Samsung+Galaxy+Z+Fold+5&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
- [Samsung Galaxy Z Fold 6 (Android 14.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=14.0&device=Samsung+Galaxy+Z+Fold+6&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true) - [Samsung Galaxy Z Fold 6 (Android 14.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=14.0&device=Samsung+Galaxy+Z+Fold+6&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
- [Samsung Galaxy Tab S9 (Android 13.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Samsung+Galaxy+Tab+S9&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true) - [Samsung Galaxy Tab S9 (Android 13.0)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Samsung+Galaxy+Tab+S9&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
- [Samsung Galaxy Note 9 (Android 8.1)](https://app-live.browserstack.com/dashboard#os=android&os_version=8.1&device=Samsung+Galaxy+Note+9&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true) - [Samsung Galaxy Note 9 (Android 8.1)](https://app-live.browserstack.com/dashboard#os=android&os_version=8.1&device=Samsung+Galaxy+Note+9&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true)
**Filename**: [#{apk_filename}](#{apk_download_url}) **Filename**: [#{apk_filename}](#{apk_download_url})
**BrowserStack App URL**: #{app_url} **BrowserStack App URL**: #{app_url}
COMMENT COMMENT
# Post PR comment if PR number is available
if pr_number if pr_number
begin begin
sh("GH_TOKEN=#{ENV['GH_TOKEN']} gh pr comment #{pr_number} --body '#{comment}'") sh("GH_TOKEN=#{ENV['GH_TOKEN']} gh pr comment #{pr_number} --body '#{comment}'")
@ -149,36 +149,34 @@ platform :android do
end end
end end
end end
end end
# ===========================
# iOS Lanes
# ===========================
platform :ios do platform :ios do
before_all do |lane, options|
UI.message("Setting up for all lanes...")
UI.message("Discarding all untracked changes before running any lane...")
sh("git clean -fd")
sh("git checkout -- .")
end
desc "Register new devices from a file" desc "Register new devices from a file"
lane :register_devices_from_txt do lane :register_devices_from_txt do
UI.message("Registering new devices from file...") UI.message("Registering new devices from file...")
csv_path = "../../devices.txt" # Update this with the actual path to your file csv_path = "../../devices.txt" # Update this with the actual path to your file
# Registering devices using the devices_file parameter # Register devices using the devices_file parameter
register_devices( register_devices(
devices_file: csv_path devices_file: csv_path
) )
UI.message("Devices registered successfully.") UI.message("Devices registered successfully.")
# Update provisioning profiles for all app identifiers
app_identifiers.each do |app_identifier| app_identifiers.each do |app_identifier|
match( match(
type: "development", type: "development",
app_identifier: app_identifier, app_identifier: app_identifier,
readonly: false, # This will regenerate the provisioning profile if needed readonly: false, # Regenerate provisioning profile if needed
force_for_new_devices: true, force_for_new_devices: true,
clone_branch_directly: true clone_branch_directly: true
) )
@ -190,7 +188,7 @@ platform :ios do
desc "Create a temporary keychain" desc "Create a temporary keychain"
lane :create_temp_keychain do lane :create_temp_keychain do
UI.message("Creating a temporary keychain...") UI.message("Creating a temporary keychain...")
create_keychain( create_keychain(
name: "temp_keychain", name: "temp_keychain",
password: ENV["KEYCHAIN_PASSWORD"], password: ENV["KEYCHAIN_PASSWORD"],
@ -199,32 +197,26 @@ platform :ios do
timeout: 3600, timeout: 3600,
lock_when_sleeps: true lock_when_sleeps: true
) )
UI.message("Temporary keychain created successfully.") UI.message("Temporary keychain created successfully.")
end end
desc "Synchronize certificates and provisioning profiles" desc "Synchronize certificates and provisioning profiles"
lane :setup_provisioning_profiles do |options| lane :setup_provisioning_profiles do
UI.message("Setting up provisioning profiles...") UI.message("Setting up provisioning profiles...")
target_to_app_identifier = {
'BlueWallet' => 'io.bluewallet.bluewallet',
'BlueWalletWatch' => 'io.bluewallet.bluewallet.watch',
'BlueWalletWatchExtension' => 'io.bluewallet.bluewallet.watch.extension',
'Stickers' => 'io.bluewallet.bluewallet.Stickers',
'MarketWidget' => 'io.bluewallet.bluewallet.MarketWidget'
}
platform = options[:platform] || "ios" # Default to iOS if not specified platform = "ios"
# Remove local master branch if it exists (Exit status: 128 - 'fatal: a branch named 'master' already exists') # Remove local master branch if it exists to avoid conflicts
sh("git branch -D master || true") sh("git branch -D master || true")
target_to_app_identifier.each do |target, app_identifier| # Iterate over app identifiers to fetch provisioning profiles
app_identifiers.each do |app_identifier|
match( match(
git_basic_authorization: ENV["GIT_ACCESS_TOKEN"], git_basic_authorization: ENV["GIT_ACCESS_TOKEN"],
git_url: ENV["GIT_URL"], git_url: ENV["GIT_URL"],
type: "appstore", type: "appstore",
clone_branch_directly: true, # Skip if the branch already exists (Exit 128 error) clone_branch_directly: true, # Skip if the branch already exists
platform: platform, platform: platform,
app_identifier: app_identifier, app_identifier: app_identifier,
team_id: ENV["ITC_TEAM_ID"], team_id: ENV["ITC_TEAM_ID"],
@ -255,7 +247,6 @@ platform :ios do
app_identifier: app_identifiers, app_identifier: app_identifiers,
readonly: true, readonly: true,
clone_branch_directly: true clone_branch_directly: true
) )
end end
@ -306,94 +297,116 @@ platform :ios do
cocoapods(podfile: "ios/Podfile") cocoapods(podfile: "ios/Podfile")
end end
desc "Build the application" desc "Upload IPA to TestFlight"
lane :build_app_lane do lane :upload_to_testflight_lane do
UI.message("Building the application...") ipa_path = ENV['IPA_OUTPUT_PATH']
build_app( changelog = ENV["LATEST_COMMIT_MESSAGE"]
scheme: "BlueWallet",
workspace: "ios/BlueWallet.xcworkspace",
export_method: "app-store",
include_bitcode: false,
configuration: "Release",
skip_profile_detection: true,
include_symbols: true,
export_team_id: ENV["ITC_TEAM_ID"],
export_options: {
signingStyle: "manual",
provisioningProfiles: {
'io.bluewallet.bluewallet' => 'match AppStore io.bluewallet.bluewallet',
'io.bluewallet.bluewallet.watch' => 'match AppStore io.bluewallet.bluewallet.watch',
'io.bluewallet.bluewallet.watch.extension' => 'match AppStore io.bluewallet.bluewallet.watch.extension',
'io.bluewallet.bluewallet.Stickers' => 'match AppStore io.bluewallet.bluewallet.Stickers',
'io.bluewallet.bluewallet.MarketWidget' => 'match AppStore io.bluewallet.bluewallet.MarketWidget'
}
},
xcargs: "GCC_PREPROCESSOR_DEFINITIONS='$(inherited) VERBOSE_LOGGING=1'",
output_directory: "./build", # Directory where the IPA file will be stored
output_name: "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa", # Check if IPA exists before proceeding
buildlog_path: "./build_logs" if ipa_path.nil? || ipa_path.empty? || !File.exist?(ipa_path)
) UI.user_error!("IPA file not found at path: #{ipa_path}")
end end
desc "Upload to TestFlight without Processing Wait" UI.message("Uploading IPA to TestFlight from path: #{ipa_path}")
lane :upload_to_testflight_lane do
attempts = 0 upload_to_testflight(
max_attempts = 3 api_key_path: "./appstore_api_key.json",
ipa: ipa_path,
skip_waiting_for_build_processing: true, # Do not wait for processing
changelog: changelog
)
UI.success("Successfully uploaded IPA to TestFlight!")
end
desc "Build the iOS app"
lane :build_app_lane do
Dir.chdir(project_root) do
UI.message("Building the application from: #{Dir.pwd}")
workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace")
export_options_path = File.join(project_root, "ios", "export_options.plist")
begin begin
UI.message("Uploading to TestFlight without processing wait...") build_ios_app(
changelog = ENV["LATEST_COMMIT_MESSAGE"] scheme: "BlueWallet",
ipa_path = "./BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa" workspace: workspace_path,
export_method: "app-store",
upload_to_testflight( include_bitcode: false,
api_key_path: "./appstore_api_key.json", configuration: "Release",
ipa: ipa_path, skip_profile_detection: false,
skip_waiting_for_build_processing: true, # Do not wait for processing include_symbols: true,
changelog: changelog export_team_id: ENV["ITC_TEAM_ID"],
export_options: export_options_path,
output_directory: File.join(project_root, "ios", "build"),
output_name: "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa",
buildlog_path: File.join(project_root, "ios", "build_logs"),
silent: false,
clean: true
) )
rescue => exception rescue => e
attempts += 1 UI.user_error!("build_ios_app failed: #{e.message}")
if attempts <= max_attempts
wait_time = 180 # 3 minutes in seconds
UI.message("Attempt ##{attempts} failed with error: #{exception.message}. Waiting #{wait_time} seconds before trying again...")
sleep(wait_time)
retry
else
UI.error("Failed after #{max_attempts} attempts. Error: #{exception.message}")
raise exception
end
end
end
desc "Deploy to TestFlight"
lane :deploy do |options|
UI.message("Starting build process...")
# Update the WWDR certificate
update_wwdr_certificate
setup_app_store_connect_api_key
setup_provisioning_profiles
clear_derived_data_lane
increment_build_number_lane
unless File.directory?("Pods")
install_pods
end end
build_app_lane # Use File.join to construct paths without extra slashes
upload_to_testflight_lane ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH]
# Clean up and delete the temporary keychain if ipa_path && File.exist?(ipa_path)
delete_keychain(name: "temp_keychain") UI.message("IPA successfully found at: #{ipa_path}")
ENV['IPA_OUTPUT_PATH'] = ipa_path
sh("echo 'IPA_OUTPUT_PATH=#{ipa_path}' >> $GITHUB_ENV") # Export for GitHub Actions
else
UI.user_error!("IPA not found after build_ios_app.")
end
end
end
# Mark deployment as completed for the current commit # ===========================
last_commit = last_git_commit # Global Lanes
already_built_flag = ".already_built_#{last_commit[:sha]}" # ===========================
File.write(already_built_flag, Time.now.to_s)
desc "Deploy to TestFlight"
lane :deploy do |options|
UI.message("Starting deployment process...")
# Update WWDR Certificate
update_wwdr_certificate
# Setup App Store Connect API Key
setup_app_store_connect_api_key
# Setup Provisioning Profiles
setup_provisioning_profiles
# Clear Derived Data
clear_derived_data_lane
# Increment Build Number
increment_build_number_lane
# Install CocoaPods if not already installed
unless File.directory?("Pods")
install_pods
end end
desc "Update 'What's New' section in App Store Connect for the 'Prepare for Submission' version" # Build the iOS App
build_app_lane
# Upload IPA to TestFlight
upload_to_testflight_lane
# Clean up and delete the temporary keychain
delete_keychain(name: "temp_keychain")
# Mark deployment as completed for the current commit
last_commit = last_git_commit
already_built_flag = ".already_built_#{last_commit[:sha]}"
File.write(already_built_flag, Time.now.to_s)
end
desc "Update 'What's New' section in App Store Connect for the 'Prepare for Submission' version"
lane :update_release_notes do |options| lane :update_release_notes do |options|
require 'spaceship' require 'spaceship'
@ -402,16 +415,13 @@ lane :update_release_notes do |options|
app = Spaceship::ConnectAPI::App.find(app_identifiers.first) app = Spaceship::ConnectAPI::App.find(app_identifiers.first)
unless app UI.user_error!("Could not find the app with identifier: #{app_identifiers.first}") unless app
UI.user_error!("Could not find the app with identifier: #{app_identifiers.first}")
end
# Retry logic for fetching or creating the edit version # Retry logic for fetching or creating the edit version
retries = 5 retries = 5
begin begin
prepare_version = app.get_edit_app_store_version(platform: Spaceship::ConnectAPI::Platform::IOS) prepare_version = app.get_edit_app_store_version(platform: Spaceship::ConnectAPI::Platform::IOS)
# If no "Prepare for Submission" version is found, create a new one
if prepare_version.nil? if prepare_version.nil?
UI.message("No version in 'Prepare for Submission' found. Creating a new version...") UI.message("No version in 'Prepare for Submission' found. Creating a new version...")
latest_version = app.get_latest_version(platform: Spaceship::ConnectAPI::Platform::IOS) latest_version = app.get_latest_version(platform: Spaceship::ConnectAPI::Platform::IOS)
@ -436,12 +446,11 @@ lane :update_release_notes do |options|
# Extract existing metadata # Extract existing metadata
localized_metadata = prepare_version.get_app_store_version_localizations localized_metadata = prepare_version.get_app_store_version_localizations
# Get all the enabled locales for the app version # Get enabled locales
enabled_locales = localized_metadata.map(&:locale) enabled_locales = localized_metadata.map(&:locale)
# Define valid language codes and filter them based on enabled locales # Define release notes
release_notes_text = options[:release_notes] release_notes_text = options[:release_notes]
if release_notes_text.nil? || release_notes_text.strip.empty? if release_notes_text.nil? || release_notes_text.strip.empty?
release_notes_path = "../../release-notes.txt" release_notes_path = "../../release-notes.txt"
unless File.exist?(release_notes_path) unless File.exist?(release_notes_path)
@ -451,6 +460,7 @@ lane :update_release_notes do |options|
release_notes_text = File.read(release_notes_path) release_notes_text = File.read(release_notes_path)
end end
# Define localized release notes
localized_release_notes = { localized_release_notes = {
'en-US' => release_notes_text, # English (U.S.) - Primary 'en-US' => release_notes_text, # English (U.S.) - Primary
'ar-SA' => release_notes_text, # Arabic 'ar-SA' => release_notes_text, # Arabic
@ -479,7 +489,7 @@ lane :update_release_notes do |options|
'th' => release_notes_text, # Thai 'th' => release_notes_text, # Thai
}.select { |locale, _| enabled_locales.include?(locale) } # Only include enabled locales }.select { |locale, _| enabled_locales.include?(locale) } # Only include enabled locales
# Review what's going to be updated # Review release notes updates
UI.message("Review the following release notes updates:") UI.message("Review the following release notes updates:")
localized_release_notes.each do |locale, notes| localized_release_notes.each do |locale, notes|
UI.message("Locale: #{locale} - Notes: #{notes}") UI.message("Locale: #{locale} - Notes: #{notes}")
@ -487,21 +497,17 @@ lane :update_release_notes do |options|
unless options[:force_yes] unless options[:force_yes]
confirm = UI.confirm("Do you want to proceed with these release notes updates?") confirm = UI.confirm("Do you want to proceed with these release notes updates?")
unless confirm UI.user_error!("User aborted the lane.") unless confirm
UI.user_error!("User aborted the lane.")
end
end end
# Update release notes in App Store Connect and skip all other metadata # Update release notes in App Store Connect
localized_release_notes.each do |locale, notes| localized_release_notes.each do |locale, notes|
app_store_version_localization = localized_metadata.find { |loc| loc.locale == locale } app_store_version_localization = localized_metadata.find { |loc| loc.locale == locale }
if app_store_version_localization if app_store_version_localization
app_store_version_localization.update(attributes: { "whats_new" => notes }) app_store_version_localization.update(attributes: { "whats_new" => notes })
else else
UI.error("No localization found for locale #{locale}") UI.error("No localization found for locale #{locale}")
end end
end end
end end
end end

33
ios/export_options.plist Normal file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
<key>signingStyle</key>
<string>manual</string>
<key>teamID</key>
<string>A7W54YZ4WU</string>
<key>uploadSymbols</key>
<true/>
<key>compileBitcode</key>
<false/>
<key>thinning</key>
<string>none</string>
<key>destination</key>
<string>export</string>
<key>provisioningProfiles</key>
<dict>
<key>io.bluewallet.bluewallet</key>
<string>match AppStore io.bluewallet.bluewallet</string>
<key>io.bluewallet.bluewallet.watch</key>
<string>match AppStore io.bluewallet.bluewallet.watch</string>
<key>io.bluewallet.bluewallet.watch.extension</key>
<string>match AppStore io.bluewallet.bluewallet.watch.extension</string>
<key>io.bluewallet.bluewallet.Stickers</key>
<string>match AppStore io.bluewallet.bluewallet.Stickers</string>
<key>io.bluewallet.bluewallet.MarketWidget</key>
<string>match AppStore io.bluewallet.bluewallet.MarketWidget</string>
</dict>
</dict>
</plist>