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,17 +17,17 @@ jobs:
outputs:
new_build_number: ${{ steps.generate_build_number.outputs.build_number }}
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 }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
steps:
- name: Checkout project
- name: Checkout Project
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetches all history
- name: Specify node version
- name: Specify Node.js Version
uses: actions/setup-node@v4
with:
node-version: 20
@ -36,46 +36,69 @@ jobs:
with:
xcode-version: 16.0
- name: Set up Ruby
- name: Set Up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.1.6
bundler-cache: true
- name: Install dependencies with Bundler
- name: Install Dependencies with Bundler
run: |
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3 --quiet
- name: Install node_modules
- name: Install Node Modules
run: npm install --omit=dev --yes
- name: Install CocoaPods Dependencies
run: |
bundle exec fastlane ios install_pods
- name: Cache CocoaPods Pods
uses: actions/cache@v4
with:
path: ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
- name: Display release-notes.txt
run: cat release-notes.txt
restore-keys: |
${{ runner.os }}-pods-
- name: Get Latest Commit Message
id: get_latest_commit_message
run: |
LATEST_COMMIT_MESSAGE=$(git log -1 --pretty=format:"%s")
echo "LATEST_COMMIT_MESSAGE=${LATEST_COMMIT_MESSAGE}" >> $GITHUB_ENV
echo "::set-output name=commit_message::$LATEST_COMMIT_MESSAGE"
- name: Set up Git Authentication
echo "commit_message=$LATEST_COMMIT_MESSAGE" >> $GITHUB_OUTPUT
- 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:
ACCESS_TOKEN: ${{ secrets.GIT_ACCESS_TOKEN }}
run: |
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)"
- name: Create Temporary Keychain
run: bundle exec fastlane ios create_temp_keychain
env:
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
- name: Setup Provisioning Profiles
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
@ -86,14 +109,16 @@ jobs:
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
bundle exec fastlane ios setup_provisioning_profiles
- name: Cache Provisioning Profiles
id: cache_provisioning_profiles
uses: actions/cache@v4
with:
path: ~/Library/MobileDevice/Provisioning Profiles
path: ~/Library/MobileDevice/Provisioning\ Profiles
key: ${{ runner.os }}-provisioning-profiles-${{ github.sha }}
restore-keys: |
${{ runner.os }}-provisioning-profiles-
- name: Check Cache Status for Provisioning Profiles
run: |
if [ -n "${{ steps.cache_provisioning_profiles.outputs.cache-hit }}" ]; then
@ -101,40 +126,35 @@ jobs:
else
echo "No cache found for provisioning profiles. A new cache will be created."
fi
- name: Verify Provisioning Profiles Exist
run: |
if [ -d "~/Library/MobileDevice/Provisioning Profiles" ]; then
echo "Provisioning profiles are available in the cache."
ls -la ~/Library/MobileDevice/Provisioning Profiles
ls -la ~/Library/MobileDevice/Provisioning\ Profiles
else
echo "Provisioning profiles directory does not exist."
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
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
if: success()
uses: actions/upload-artifact@v4
with:
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:
needs: build
@ -146,13 +166,15 @@ jobs:
PROJECT_VERSION: ${{ needs.build.outputs.project_version }}
LATEST_COMMIT_MESSAGE: ${{ needs.build.outputs.latest_commit_message }}
steps:
- name: Checkout project
- name: Checkout Project
uses: actions/checkout@v4
- name: Set up Ruby
- name: Set Up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.1.6
bundler-cache: true
- name: Cache Ruby Gems
uses: actions/cache@v4
with:
@ -160,18 +182,42 @@ jobs:
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gems-
- name: Install dependencies with Bundler
- name: Install Dependencies with Bundler
run: |
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3 --quiet
- name: Download IPA from Artifact
uses: actions/download-artifact@v4
with:
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
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
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
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
@ -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_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
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
if: success() && github.event_name == 'pull_request'
uses: actions/github-script@v6

View File

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

View File

@ -1,21 +1,30 @@
# Define app identifiers once for reuse across lanes
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
default_platform(:android)
project_root = File.expand_path("..", __dir__)
# ===========================
# Android Lanes
# ===========================
platform :android do
desc "Prepare the keystore file"
lane :prepare_keystore do
Dir.chdir(project_root) do
keystore_file_hex = ENV['KEYSTORE_FILE_HEX']
UI.user_error!("KEYSTORE_FILE_HEX environment variable is missing") if keystore_file_hex.nil?
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)
sh("xxd -plain -revert bluewallet-release-key.keystore.hex > bluewallet-release-key.keystore") do |status|
@ -26,50 +35,41 @@ platform :android do
File.delete("bluewallet-release-key.keystore.hex")
end
end
end
desc "Update version, build number, and sign APK"
lane :update_version_build_and_sign_apk do
Dir.chdir(project_root) do
build_number = ENV['BUILD_NUMBER']
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
# Manually update the versionCode in build.gradle
# Update versionCode in build.gradle
UI.message("Updating versionCode in build.gradle to #{build_number}...")
build_gradle_path = "android/app/build.gradle"
build_gradle_contents = File.read(build_gradle_path)
new_build_gradle_contents = build_gradle_contents.gsub(/versionCode\s+\d+/, "versionCode #{build_number}")
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(/[\/\\:?*"<>|]/, '_')
if branch_name.nil? || branch_name.empty?
branch_name = 'master'
end
branch_name = 'master' if branch_name.nil? || branch_name.empty?
# Append branch name only if it's not 'master'
if branch_name != 'master'
signed_apk_name = "BlueWallet-#{version_name}-#{build_number}-#{branch_name}.apk"
else
signed_apk_name = "BlueWallet-#{version_name}-#{build_number}.apk"
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"
# Continue with the build process
# Build APK
Dir.chdir("android") do
UI.message("Building APK...")
gradle(
task: "assembleRelease",
project_dir: "android"
)
gradle(task: "assembleRelease", project_dir: "android")
UI.message("APK build completed.")
# Define the output paths
# Define paths
unsigned_apk_path = "app/build/outputs/apk/release/app-release-unsigned.apk"
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)
UI.message("Renaming APK to #{signed_apk_name}...")
FileUtils.mv(unsigned_apk_path, signed_apk_path)
@ -79,7 +79,7 @@ platform :android do
next
end
# Sign the APK using apksigner
# Sign APK
UI.message("Signing APK with 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}")
@ -91,16 +91,15 @@ platform :android do
desc "Upload APK to BrowserStack and post result as PR comment"
lane :upload_to_browserstack_and_comment do
Dir.chdir(project_root) do
# Fetch the APK path from environment variables
# Determine APK path
apk_path = ENV['APK_PATH']
# Attempt to find the APK if not provided
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
UI.user_error!("No APK file found") if apk_path.nil? || apk_path.empty?
end
# Upload to BrowserStack
UI.message("Uploading APK to BrowserStack: #{apk_path}...")
upload_to_browserstack_app_live(
file_path: apk_path,
@ -108,13 +107,13 @@ platform :android do
browserstack_access_key: ENV['BROWSERSTACK_ACCESS_KEY']
)
# Extract the BrowserStack URL from the output
# Extract BrowserStack URL
app_url = ENV['BROWSERSTACK_LIVE_APP_ID']
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_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://', '')
pr_number = ENV['GITHUB_PR_NUMBER']
@ -137,6 +136,7 @@ platform :android do
**BrowserStack App URL**: #{app_url}
COMMENT
# Post PR comment if PR number is available
if pr_number
begin
sh("GH_TOKEN=#{ENV['GH_TOKEN']} gh pr comment #{pr_number} --body '#{comment}'")
@ -149,36 +149,34 @@ platform :android do
end
end
end
end
# ===========================
# iOS Lanes
# ===========================
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"
lane :register_devices_from_txt do
UI.message("Registering new devices from 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(
devices_file: csv_path
)
UI.message("Devices registered successfully.")
# Update provisioning profiles for all app identifiers
app_identifiers.each do |app_identifier|
match(
type: "development",
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,
clone_branch_directly: true
)
@ -204,27 +202,21 @@ platform :ios do
end
desc "Synchronize certificates and provisioning profiles"
lane :setup_provisioning_profiles do |options|
lane :setup_provisioning_profiles do
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")
target_to_app_identifier.each do |target, app_identifier|
# Iterate over app identifiers to fetch provisioning profiles
app_identifiers.each do |app_identifier|
match(
git_basic_authorization: ENV["GIT_ACCESS_TOKEN"],
git_url: ENV["GIT_URL"],
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,
app_identifier: app_identifier,
team_id: ENV["ITC_TEAM_ID"],
@ -255,7 +247,6 @@ platform :ios do
app_identifier: app_identifiers,
readonly: true,
clone_branch_directly: true
)
end
@ -306,44 +297,17 @@ platform :ios do
cocoapods(podfile: "ios/Podfile")
end
desc "Build the application"
lane :build_app_lane do
UI.message("Building the application...")
build_app(
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
desc "Upload IPA to TestFlight"
lane :upload_to_testflight_lane do
ipa_path = ENV['IPA_OUTPUT_PATH']
changelog = ENV["LATEST_COMMIT_MESSAGE"]
output_name: "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa",
buildlog_path: "./build_logs"
)
# Check if IPA exists before proceeding
if ipa_path.nil? || ipa_path.empty? || !File.exist?(ipa_path)
UI.user_error!("IPA file not found at path: #{ipa_path}")
end
desc "Upload to TestFlight without Processing Wait"
lane :upload_to_testflight_lane do
attempts = 0
max_attempts = 3
begin
UI.message("Uploading to TestFlight without processing wait...")
changelog = ENV["LATEST_COMMIT_MESSAGE"]
ipa_path = "./BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa"
UI.message("Uploading IPA to TestFlight from path: #{ipa_path}")
upload_to_testflight(
api_key_path: "./appstore_api_key.json",
@ -351,37 +315,86 @@ platform :ios do
skip_waiting_for_build_processing: true, # Do not wait for processing
changelog: changelog
)
rescue => exception
attempts += 1
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
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
build_ios_app(
scheme: "BlueWallet",
workspace: workspace_path,
export_method: "app-store",
include_bitcode: false,
configuration: "Release",
skip_profile_detection: false,
include_symbols: true,
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 => e
UI.user_error!("build_ios_app failed: #{e.message}")
end
# Use File.join to construct paths without extra slashes
ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH]
if ipa_path && File.exist?(ipa_path)
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.error("Failed after #{max_attempts} attempts. Error: #{exception.message}")
raise exception
end
UI.user_error!("IPA not found after build_ios_app.")
end
end
end
desc "Deploy to TestFlight"
lane :deploy do |options|
UI.message("Starting build process...")
# ===========================
# Global Lanes
# ===========================
# Update the WWDR certificate
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
# Build the iOS App
build_app_lane
# Upload IPA to TestFlight
upload_to_testflight_lane
# Clean up and delete the temporary keychain
@ -391,9 +404,9 @@ platform :ios do
last_commit = last_git_commit
already_built_flag = ".already_built_#{last_commit[:sha]}"
File.write(already_built_flag, Time.now.to_s)
end
end
desc "Update 'What's New' section in App Store Connect for the 'Prepare for Submission' version"
desc "Update 'What's New' section in App Store Connect for the 'Prepare for Submission' version"
lane :update_release_notes do |options|
require 'spaceship'
@ -402,16 +415,13 @@ lane :update_release_notes do |options|
app = Spaceship::ConnectAPI::App.find(app_identifiers.first)
unless app
UI.user_error!("Could not find the app with identifier: #{app_identifiers.first}")
end
UI.user_error!("Could not find the app with identifier: #{app_identifiers.first}") unless app
# Retry logic for fetching or creating the edit version
retries = 5
begin
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?
UI.message("No version in 'Prepare for Submission' found. Creating a new version...")
latest_version = app.get_latest_version(platform: Spaceship::ConnectAPI::Platform::IOS)
@ -436,12 +446,11 @@ lane :update_release_notes do |options|
# Extract existing metadata
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)
# Define valid language codes and filter them based on enabled locales
# Define release notes
release_notes_text = options[:release_notes]
if release_notes_text.nil? || release_notes_text.strip.empty?
release_notes_path = "../../release-notes.txt"
unless File.exist?(release_notes_path)
@ -451,6 +460,7 @@ lane :update_release_notes do |options|
release_notes_text = File.read(release_notes_path)
end
# Define localized release notes
localized_release_notes = {
'en-US' => release_notes_text, # English (U.S.) - Primary
'ar-SA' => release_notes_text, # Arabic
@ -479,7 +489,7 @@ lane :update_release_notes do |options|
'th' => release_notes_text, # Thai
}.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:")
localized_release_notes.each do |locale, notes|
UI.message("Locale: #{locale} - Notes: #{notes}")
@ -487,21 +497,17 @@ lane :update_release_notes do |options|
unless options[:force_yes]
confirm = UI.confirm("Do you want to proceed with these release notes updates?")
unless confirm
UI.user_error!("User aborted the lane.")
end
UI.user_error!("User aborted the lane.") unless confirm
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|
app_store_version_localization = localized_metadata.find { |loc| loc.locale == locale }
if app_store_version_localization
app_store_version_localization.update(attributes: { "whats_new" => notes })
else
UI.error("No localization found for locale #{locale}")
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>