mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-13 19:16:52 +01:00
OPS: Refactor iOS pipeline
This commit is contained in:
parent
4c0fd89530
commit
1946fa0dde
3 changed files with 366 additions and 222 deletions
|
@ -22,12 +22,28 @@ jobs:
|
|||
branch_name: ${{ steps.get_latest_commit_details.outputs.branch_name }}
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
# Set Match to read-only for builds, only write new profiles manually
|
||||
MATCH_READONLY: "true"
|
||||
|
||||
steps:
|
||||
- name: Checkout Project
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Ensures the full Git history is available
|
||||
|
||||
# Setup caching to speed up builds
|
||||
- name: Setup Caching
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/Library/Caches/CocoaPods
|
||||
ios/Pods
|
||||
~/.npm
|
||||
node_modules
|
||||
vendor/bundle
|
||||
key: ${{ runner.os }}-ios-${{ hashFiles('**/package-lock.json', '**/Podfile.lock', '**/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-ios-
|
||||
|
||||
- name: Clear All Caches
|
||||
if: github.ref == 'refs/heads/master'
|
||||
|
@ -81,6 +97,7 @@ jobs:
|
|||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'npm'
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
|
@ -90,6 +107,7 @@ jobs:
|
|||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.1.6
|
||||
bundler-cache: true # This caches gems installed via bundler
|
||||
|
||||
- name: Install Dependencies with Bundler
|
||||
run: |
|
||||
|
@ -102,6 +120,7 @@ jobs:
|
|||
- name: Install CocoaPods Dependencies
|
||||
run: |
|
||||
bundle exec fastlane ios install_pods
|
||||
echo "CocoaPods dependencies installed successfully"
|
||||
|
||||
- name: Generate Build Number Based on Timestamp
|
||||
id: generate_build_number
|
||||
|
@ -147,8 +166,7 @@ jobs:
|
|||
- name: Build App
|
||||
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
|
||||
bundle exec fastlane ios build_app_lane
|
||||
|
||||
- name: Upload Bugsnag Sourcemaps
|
||||
if: success()
|
||||
|
@ -156,8 +174,8 @@ jobs:
|
|||
env:
|
||||
BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }}
|
||||
BUGSNAG_RELEASE_STAGE: production
|
||||
PROJECT_VERSION: ${{ needs.build.outputs.project_version }}
|
||||
NEW_BUILD_NUMBER: ${{ needs.build.outputs.new_build_number }}
|
||||
PROJECT_VERSION: ${{ env.PROJECT_VERSION }}
|
||||
NEW_BUILD_NUMBER: ${{ env.NEW_BUILD_NUMBER }}
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
|
@ -165,13 +183,19 @@ jobs:
|
|||
with:
|
||||
name: build_logs
|
||||
path: ./ios/build_logs/
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload IPA as Artifact
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: BlueWallet_${{env.PROJECT_VERSION}}_${{env.NEW_BUILD_NUMBER}}.ipa
|
||||
path: ${{ env.IPA_OUTPUT_PATH }} # Directly from Fastfile `IPA_OUTPUT_PATH`
|
||||
path: ${{ env.IPA_OUTPUT_PATH }}
|
||||
retention-days: 7
|
||||
|
||||
- name: Delete Temporary Keychain
|
||||
if: always()
|
||||
run: bundle exec fastlane ios delete_temp_keychain
|
||||
|
||||
testflight-upload:
|
||||
needs: build
|
||||
|
@ -191,6 +215,7 @@ jobs:
|
|||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.1.6
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install Dependencies with Bundler
|
||||
run: |
|
||||
|
@ -205,13 +230,6 @@ jobs:
|
|||
|
||||
- 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
|
||||
|
@ -219,19 +237,23 @@ jobs:
|
|||
- name: Verify IPA Path Before Upload
|
||||
run: |
|
||||
if [ ! -f "$IPA_OUTPUT_PATH" ]; then
|
||||
echo "IPA file not found at path: $IPA_OUTPUT_PATH"
|
||||
echo "❌ IPA file not found at path: $IPA_OUTPUT_PATH"
|
||||
ls -la $(pwd)
|
||||
exit 1
|
||||
else
|
||||
echo "✅ Found IPA at: $IPA_OUTPUT_PATH"
|
||||
fi
|
||||
|
||||
- name: Print Environment Variables for Debugging
|
||||
run: |
|
||||
echo "LATEST_COMMIT_MESSAGE: $LATEST_COMMIT_MESSAGE"
|
||||
echo "BRANCH_NAME: $BRANCH_NAME"
|
||||
echo "PROJECT_VERSION: $PROJECT_VERSION"
|
||||
echo "NEW_BUILD_NUMBER: $NEW_BUILD_NUMBER"
|
||||
echo "IPA_OUTPUT_PATH: $IPA_OUTPUT_PATH"
|
||||
|
||||
- name: Upload to TestFlight
|
||||
run: |
|
||||
ls -la $IPA_OUTPUT_PATH
|
||||
bundle exec fastlane ios upload_to_testflight_lane
|
||||
run: 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 }}
|
||||
|
@ -242,18 +264,19 @@ 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 }}
|
||||
IPA_OUTPUT_PATH: ${{ env.IPA_OUTPUT_PATH }}
|
||||
|
||||
- name: Post PR Comment
|
||||
if: success() && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
BUILD_NUMBER: ${{ needs.build.outputs.new_build_number }}
|
||||
PROJECT_VERSION: ${{ needs.build.outputs.project_version }}
|
||||
LATEST_COMMIT_MESSAGE: ${{ needs.build.outputs.latest_commit_message }}
|
||||
with:
|
||||
script: |
|
||||
const buildNumber = process.env.BUILD_NUMBER;
|
||||
const message = `The build ${buildNumber} has been uploaded to TestFlight.`;
|
||||
const version = process.env.PROJECT_VERSION;
|
||||
const message = `✅ Build ${version} (${buildNumber}) has been uploaded to TestFlight and will be available for testing soon.`;
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const repo = context.repo;
|
||||
github.rest.issues.createComment({
|
||||
|
|
|
@ -192,223 +192,222 @@ end
|
|||
# ===========================
|
||||
|
||||
platform :ios do
|
||||
# ==== Helper Methods ====
|
||||
|
||||
def ensure_env_vars(vars)
|
||||
vars.each do |var|
|
||||
UI.user_error!("#{var} environment variable is missing") if ENV[var].nil? || ENV[var].empty?
|
||||
end
|
||||
end
|
||||
|
||||
def log_success(message)
|
||||
UI.success("✅ #{message}")
|
||||
end
|
||||
|
||||
def log_error(message)
|
||||
UI.error("❌ #{message}")
|
||||
end
|
||||
|
||||
desc "Register new devices from a file"
|
||||
# ==== Device Management ====
|
||||
|
||||
desc "Register new devices from a file and update provisioning profiles"
|
||||
lane :register_devices_from_txt do
|
||||
UI.message("Registering new devices from file...")
|
||||
|
||||
csv_path = "../../devices.txt" # Update with actual path
|
||||
|
||||
unless File.exist?(csv_path)
|
||||
UI.user_error!("Devices file not found at: #{csv_path}")
|
||||
end
|
||||
|
||||
csv_path = "../../devices.txt" # Update this with the actual path to your file
|
||||
register_devices(devices_file: csv_path)
|
||||
log_success("Devices registered successfully")
|
||||
|
||||
# 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
|
||||
update_provisioning_profiles_for_new_devices
|
||||
end
|
||||
|
||||
desc "Update provisioning profiles for all app identifiers after adding new devices"
|
||||
private_lane :update_provisioning_profiles_for_new_devices do
|
||||
UI.message("Updating provisioning profiles for new devices...")
|
||||
|
||||
app_identifiers.each do |app_identifier|
|
||||
match(
|
||||
type: "development",
|
||||
app_identifier: app_identifier,
|
||||
readonly: false, # Regenerate provisioning profile if needed
|
||||
readonly: false,
|
||||
force_for_new_devices: true,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
end
|
||||
|
||||
UI.message("Development provisioning profiles updated.")
|
||||
end
|
||||
|
||||
desc "Create a temporary keychain"
|
||||
lane :create_temp_keychain do
|
||||
UI.message("Creating a temporary keychain...")
|
||||
|
||||
create_keychain(
|
||||
name: "temp_keychain",
|
||||
password: ENV["KEYCHAIN_PASSWORD"],
|
||||
default_keychain: true,
|
||||
unlock: true,
|
||||
timeout: 3600,
|
||||
lock_when_sleeps: true
|
||||
)
|
||||
|
||||
UI.message("Temporary keychain created successfully.")
|
||||
|
||||
log_success("Development provisioning profiles updated")
|
||||
end
|
||||
|
||||
desc "Synchronize certificates and provisioning profiles"
|
||||
# ==== Keychain Management ====
|
||||
|
||||
desc "Create a temporary keychain for CI builds"
|
||||
lane :create_temp_keychain do
|
||||
ensure_env_vars(["KEYCHAIN_PASSWORD"])
|
||||
UI.message("Creating temporary keychain...")
|
||||
|
||||
begin
|
||||
create_keychain(
|
||||
name: "temp_keychain",
|
||||
password: ENV["KEYCHAIN_PASSWORD"],
|
||||
default_keychain: true,
|
||||
unlock: true,
|
||||
timeout: 3600,
|
||||
lock_when_sleeps: true
|
||||
)
|
||||
|
||||
log_success("Temporary keychain created")
|
||||
rescue => e
|
||||
log_error("Failed to create temporary keychain: #{e.message}")
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
desc "Delete temporary keychain when done with the build"
|
||||
lane :delete_temp_keychain do
|
||||
UI.message("Deleting temporary keychain...")
|
||||
|
||||
begin
|
||||
delete_keychain(name: "temp_keychain")
|
||||
log_success("Temporary keychain deleted")
|
||||
rescue => e
|
||||
log_error("Failed to delete temporary keychain: #{e.message}")
|
||||
# Don't raise error here, as this is cleanup code
|
||||
end
|
||||
end
|
||||
|
||||
# ==== Provisioning Profile Management ====
|
||||
|
||||
desc "Setup provisioning profiles for all app identifiers"
|
||||
lane :setup_provisioning_profiles do
|
||||
required_vars = ["GIT_ACCESS_TOKEN", "GIT_URL", "ITC_TEAM_ID", "ITC_TEAM_NAME", "KEYCHAIN_PASSWORD"]
|
||||
ensure_env_vars(required_vars)
|
||||
|
||||
UI.message("Setting up provisioning profiles...")
|
||||
|
||||
platform = "ios"
|
||||
|
||||
|
||||
# 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
|
||||
platform: platform,
|
||||
app_identifier: app_identifier,
|
||||
team_id: ENV["ITC_TEAM_ID"],
|
||||
team_name: ENV["ITC_TEAM_NAME"],
|
||||
readonly: true,
|
||||
keychain_name: "temp_keychain",
|
||||
keychain_password: ENV["KEYCHAIN_PASSWORD"]
|
||||
)
|
||||
begin
|
||||
match(
|
||||
git_basic_authorization: ENV["GIT_ACCESS_TOKEN"],
|
||||
git_url: ENV["GIT_URL"],
|
||||
type: "appstore",
|
||||
clone_branch_directly: true,
|
||||
platform: "ios",
|
||||
app_identifier: app_identifier,
|
||||
team_id: ENV["ITC_TEAM_ID"],
|
||||
team_name: ENV["ITC_TEAM_NAME"],
|
||||
readonly: true,
|
||||
keychain_name: "temp_keychain",
|
||||
keychain_password: ENV["KEYCHAIN_PASSWORD"]
|
||||
)
|
||||
rescue => e
|
||||
log_error("Failed to fetch provisioning profile for #{app_identifier}: #{e.message}")
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
log_success("All provisioning profiles set up")
|
||||
end
|
||||
|
||||
desc "Fetch development certificates and provisioning profiles for Mac Catalyst"
|
||||
lane :fetch_dev_profiles_catalyst do
|
||||
match(
|
||||
type: "development",
|
||||
platform: "catalyst",
|
||||
app_identifier: app_identifiers,
|
||||
readonly: true,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
end
|
||||
|
||||
desc "Fetch App Store certificates and provisioning profiles for Mac Catalyst"
|
||||
lane :fetch_appstore_profiles_catalyst do
|
||||
match(
|
||||
type: "appstore",
|
||||
platform: "catalyst",
|
||||
app_identifier: app_identifiers,
|
||||
readonly: true,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
end
|
||||
|
||||
# ==== Catalyst Support Lanes ====
|
||||
|
||||
desc "Setup provisioning profiles for Mac Catalyst"
|
||||
lane :setup_catalyst_provisioning_profiles do
|
||||
lane :setup_catalyst_provisioning do
|
||||
UI.message("Setting up Mac Catalyst provisioning profiles...")
|
||||
|
||||
# First development profiles
|
||||
fetch_catalyst_profiles(type: "development")
|
||||
|
||||
# Then App Store profiles
|
||||
fetch_catalyst_profiles(type: "appstore")
|
||||
|
||||
log_success("Mac Catalyst provisioning profiles set up")
|
||||
end
|
||||
|
||||
private_lane :fetch_catalyst_profiles do |options|
|
||||
type = options[:type]
|
||||
readonly = options[:readonly] || true
|
||||
force_for_new_devices = options[:force_for_new_devices] || false
|
||||
|
||||
app_identifiers.each do |app_identifier|
|
||||
match(
|
||||
type: "development",
|
||||
type: type,
|
||||
platform: "catalyst",
|
||||
app_identifier: app_identifier,
|
||||
readonly: false,
|
||||
force_for_new_devices: true,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
|
||||
match(
|
||||
type: "appstore",
|
||||
platform: "catalyst",
|
||||
app_identifier: app_identifier,
|
||||
readonly: false,
|
||||
readonly: readonly,
|
||||
force_for_new_devices: force_for_new_devices,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# ==== Build Preparation ====
|
||||
|
||||
desc "Clear derived data"
|
||||
lane :clear_derived_data_lane do
|
||||
UI.message("Clearing derived data...")
|
||||
clear_derived_data
|
||||
|
||||
begin
|
||||
clear_derived_data
|
||||
log_success("Derived data cleared")
|
||||
rescue => e
|
||||
log_error("Failed to clear derived data: #{e.message}")
|
||||
# Continue despite error
|
||||
end
|
||||
end
|
||||
|
||||
desc "Increment build number"
|
||||
lane :increment_build_number_lane do
|
||||
UI.message("Incrementing build number to current timestamp...")
|
||||
ensure_env_vars(["NEW_BUILD_NUMBER"])
|
||||
UI.message("Incrementing build number to #{ENV['NEW_BUILD_NUMBER']}...")
|
||||
|
||||
# Set the new build number
|
||||
increment_build_number(
|
||||
xcodeproj: "ios/BlueWallet.xcodeproj",
|
||||
build_number: ENV["NEW_BUILD_NUMBER"]
|
||||
)
|
||||
|
||||
UI.message("Build number set to: #{ENV['NEW_BUILD_NUMBER']}")
|
||||
begin
|
||||
increment_build_number(
|
||||
xcodeproj: "ios/BlueWallet.xcodeproj",
|
||||
build_number: ENV["NEW_BUILD_NUMBER"]
|
||||
)
|
||||
|
||||
log_success("Build number set to: #{ENV['NEW_BUILD_NUMBER']}")
|
||||
rescue => e
|
||||
log_error("Failed to increment build number: #{e.message}")
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
desc "Install CocoaPods dependencies"
|
||||
lane :install_pods do
|
||||
UI.message("Installing CocoaPods dependencies...")
|
||||
cocoapods(podfile: "ios/Podfile")
|
||||
|
||||
begin
|
||||
cocoapods(podfile: "ios/Podfile")
|
||||
log_success("CocoaPods dependencies installed")
|
||||
rescue => e
|
||||
log_error("Failed to install CocoaPods dependencies: #{e.message}")
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
desc "Upload IPA to TestFlight"
|
||||
lane :upload_to_testflight_lane do
|
||||
|
||||
branch_name = ENV['BRANCH_NAME'] || "unknown-branch"
|
||||
last_commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found"
|
||||
|
||||
|
||||
changelog = <<~CHANGELOG
|
||||
Build Information:
|
||||
CHANGELOG
|
||||
|
||||
# Include the branch name only if it is not 'master'
|
||||
if branch_name != 'master'
|
||||
changelog += <<~CHANGELOG
|
||||
- Branch: #{branch_name}
|
||||
CHANGELOG
|
||||
end
|
||||
|
||||
changelog += <<~CHANGELOG
|
||||
- Commit: #{last_commit_message}
|
||||
CHANGELOG
|
||||
|
||||
ipa_path = ENV['IPA_OUTPUT_PATH']
|
||||
# ==== Build and Upload ====
|
||||
|
||||
if ipa_path.nil? || ipa_path.empty? || !File.exist?(ipa_path)
|
||||
UI.user_error!("IPA file not found at path: #{ipa_path}")
|
||||
end
|
||||
|
||||
UI.message("Uploading IPA to TestFlight from path: #{ipa_path}")
|
||||
UI.message("Changelog:\n#{changelog}")
|
||||
|
||||
|
||||
upload_to_testflight(
|
||||
api_key_path: "./appstore_api_key.json",
|
||||
ipa: ipa_path,
|
||||
skip_waiting_for_build_processing: true,
|
||||
changelog: changelog
|
||||
)
|
||||
|
||||
UI.success("Successfully uploaded IPA to TestFlight!")
|
||||
end
|
||||
|
||||
desc "Upload iOS source maps to Bugsnag"
|
||||
lane :upload_bugsnag_sourcemaps do
|
||||
bugsnag_api_key = ENV['BUGSNAG_API_KEY']
|
||||
bugsnag_release_stage = ENV['BUGSNAG_RELEASE_STAGE'] || "production"
|
||||
version = ENV['PROJECT_VERSION']
|
||||
build_number = ENV['NEW_BUILD_NUMBER']
|
||||
|
||||
UI.user_error!("BUGSNAG_API_KEY environment variable is missing") if bugsnag_api_key.nil?
|
||||
UI.user_error!("PROJECT_VERSION environment variable is missing") if version.nil?
|
||||
UI.user_error!("NEW_BUILD_NUMBER environment variable is missing") if build_number.nil?
|
||||
|
||||
ios_sourcemap = "./ios/build/Build/Products/Release-iphonesimulator/main.jsbundle.map"
|
||||
|
||||
if File.exist?(ios_sourcemap)
|
||||
UI.message("Uploading iOS source map to Bugsnag...")
|
||||
bugsnag_sourcemaps_upload(
|
||||
api_key: bugsnag_api_key,
|
||||
source_map: ios_sourcemap,
|
||||
minified_file: "./ios/main.jsbundle",
|
||||
code_bundle_id: "#{version}-#{build_number}",
|
||||
release_stage: bugsnag_release_stage,
|
||||
app_version: version
|
||||
)
|
||||
UI.success("iOS source map uploaded successfully.")
|
||||
else
|
||||
UI.error("iOS source map not found at #{ios_sourcemap}")
|
||||
end
|
||||
end
|
||||
|
||||
desc "Build the iOS app"
|
||||
desc "Build the iOS app for distribution"
|
||||
lane :build_app_lane do
|
||||
Dir.chdir(project_root) do
|
||||
UI.message("Building the application from: #{Dir.pwd}")
|
||||
|
||||
UI.message("Building the application...")
|
||||
|
||||
workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace")
|
||||
export_options_path = File.join(project_root, "ios", "export_options.plist")
|
||||
|
||||
output_name = "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa"
|
||||
output_dir = File.join(project_root, "ios", "build")
|
||||
logs_dir = File.join(project_root, "ios", "build_logs")
|
||||
|
||||
# Ensure the build logs directory exists
|
||||
FileUtils.mkdir_p(logs_dir) unless Dir.exist?(logs_dir)
|
||||
|
||||
# Clear derived data before building
|
||||
clear_derived_data_lane
|
||||
|
||||
begin
|
||||
|
@ -422,29 +421,142 @@ end
|
|||
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"),
|
||||
output_directory: output_dir,
|
||||
output_name: output_name,
|
||||
buildlog_path: logs_dir,
|
||||
silent: false,
|
||||
clean: true
|
||||
)
|
||||
|
||||
# Set environment variables for the IPA path
|
||||
ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH]
|
||||
|
||||
if ipa_path && File.exist?(ipa_path)
|
||||
ENV['IPA_OUTPUT_PATH'] = ipa_path
|
||||
sh("echo 'ipa_output_path=#{ipa_path}' >> $GITHUB_OUTPUT") if ENV['GITHUB_OUTPUT']
|
||||
log_success("IPA built successfully at: #{ipa_path}")
|
||||
else
|
||||
UI.user_error!("IPA not found after build_ios_app")
|
||||
end
|
||||
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.user_error!("IPA not found after build_ios_app.")
|
||||
log_error("Failed to build app: #{e.message}")
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
desc "Upload IPA to TestFlight"
|
||||
lane :upload_to_testflight_lane do
|
||||
required_vars = ["IPA_OUTPUT_PATH"]
|
||||
ensure_env_vars(required_vars)
|
||||
|
||||
branch_name = ENV['BRANCH_NAME'] || "unknown-branch"
|
||||
commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found"
|
||||
|
||||
changelog = <<~CHANGELOG
|
||||
Build Information:
|
||||
CHANGELOG
|
||||
|
||||
# Include branch name only if not master
|
||||
if branch_name != 'master'
|
||||
changelog += <<~CHANGELOG
|
||||
- Branch: #{branch_name}
|
||||
CHANGELOG
|
||||
end
|
||||
|
||||
changelog += <<~CHANGELOG
|
||||
- Commit: #{commit_message}
|
||||
CHANGELOG
|
||||
|
||||
ipa_path = ENV['IPA_OUTPUT_PATH']
|
||||
|
||||
if !File.exist?(ipa_path)
|
||||
UI.user_error!("IPA file not found at path: #{ipa_path}")
|
||||
end
|
||||
|
||||
UI.message("Uploading IPA to TestFlight from path: #{ipa_path}")
|
||||
UI.message("Changelog:\n#{changelog}")
|
||||
|
||||
begin
|
||||
upload_to_testflight(
|
||||
api_key_path: "./appstore_api_key.json",
|
||||
ipa: ipa_path,
|
||||
skip_waiting_for_build_processing: true,
|
||||
changelog: changelog
|
||||
)
|
||||
|
||||
log_success("Successfully uploaded IPA to TestFlight!")
|
||||
rescue => e
|
||||
log_error("Failed to upload to TestFlight: #{e.message}")
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
# ==== Bugsnag Integration ====
|
||||
|
||||
desc "Upload iOS source maps to Bugsnag"
|
||||
lane :upload_bugsnag_sourcemaps do
|
||||
required_vars = ["BUGSNAG_API_KEY", "PROJECT_VERSION", "NEW_BUILD_NUMBER"]
|
||||
ensure_env_vars(required_vars)
|
||||
|
||||
bugsnag_api_key = ENV['BUGSNAG_API_KEY']
|
||||
bugsnag_release_stage = ENV['BUGSNAG_RELEASE_STAGE'] || "production"
|
||||
version = ENV['PROJECT_VERSION']
|
||||
build_number = ENV['NEW_BUILD_NUMBER']
|
||||
|
||||
ios_sourcemap = "./ios/build/Build/Products/Release-iphonesimulator/main.jsbundle.map"
|
||||
|
||||
if File.exist?(ios_sourcemap)
|
||||
UI.message("Uploading iOS source map to Bugsnag...")
|
||||
|
||||
begin
|
||||
bugsnag_sourcemaps_upload(
|
||||
api_key: bugsnag_api_key,
|
||||
source_map: ios_sourcemap,
|
||||
minified_file: "./ios/main.jsbundle",
|
||||
code_bundle_id: "#{version}-#{build_number}",
|
||||
release_stage: bugsnag_release_stage,
|
||||
app_version: version
|
||||
)
|
||||
|
||||
log_success("iOS source map uploaded successfully")
|
||||
rescue => e
|
||||
log_error("Failed to upload sourcemaps to Bugsnag: #{e.message}")
|
||||
# Continue despite error, don't fail the build
|
||||
end
|
||||
else
|
||||
log_error("iOS source map not found at #{ios_sourcemap}")
|
||||
end
|
||||
end
|
||||
|
||||
# ==== Complete Deployment Workflow ====
|
||||
|
||||
desc "Complete iOS deployment workflow"
|
||||
lane :deploy_ios do |options|
|
||||
UI.message("Starting iOS deployment process...")
|
||||
|
||||
# Setup everything
|
||||
create_temp_keychain
|
||||
setup_provisioning_profiles
|
||||
clear_derived_data_lane
|
||||
increment_build_number_lane
|
||||
|
||||
# Install pods if needed
|
||||
unless File.directory?("ios/Pods")
|
||||
install_pods
|
||||
end
|
||||
|
||||
# Build and upload
|
||||
build_app_lane
|
||||
upload_to_testflight_lane
|
||||
upload_bugsnag_sourcemaps
|
||||
|
||||
# Cleanup
|
||||
delete_temp_keychain
|
||||
|
||||
log_success("iOS deployment completed successfully!")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ===========================
|
||||
# Global Lanes
|
||||
# ===========================
|
||||
|
|
|
@ -3,33 +3,42 @@
|
|||
# URL of the Git repository to store the certificates
|
||||
git_url(ENV["GIT_URL"])
|
||||
|
||||
# Define the type of match to run, could be one of 'appstore', 'adhoc', 'development', or 'enterprise'.
|
||||
# For example, use 'appstore' for App Store builds, 'adhoc' for Ad Hoc distribution,
|
||||
# 'development' for development builds, and 'enterprise' for In-House (enterprise) distribution.
|
||||
type("appstore")
|
||||
# Define the type of match to run
|
||||
# Default to "appstore" but can be overridden
|
||||
type(ENV["MATCH_TYPE"] || "appstore")
|
||||
|
||||
app_identifier(["io.bluewallet.bluewallet", "io.bluewallet.bluewallet.watch", "io.bluewallet.bluewallet.watch.extension", "io.bluewallet.bluewallet.Stickers", "io.bluewallet.bluewallet.MarketWidget"]) # Replace with your app identifiers
|
||||
# App identifiers for all BlueWallet apps
|
||||
app_identifier([
|
||||
"io.bluewallet.bluewallet",
|
||||
"io.bluewallet.bluewallet.watch",
|
||||
"io.bluewallet.bluewallet.watch.extension",
|
||||
"io.bluewallet.bluewallet.Stickers",
|
||||
"io.bluewallet.bluewallet.MarketWidget"
|
||||
])
|
||||
|
||||
# List of app identifiers to create provisioning profiles for.
|
||||
# Replace with your app's bundle identifier(s).
|
||||
|
||||
# Your Apple Developer account email address.
|
||||
# Your Apple Developer account email address
|
||||
username(ENV["APPLE_ID"])
|
||||
|
||||
# The ID of your Apple Developer team if you're part of multiple teams
|
||||
# The ID of your Apple Developer team
|
||||
team_id(ENV["ITC_TEAM_ID"])
|
||||
|
||||
# Set this to true if match should only read existing certificates and profiles
|
||||
# and not create new ones.
|
||||
readonly(true)
|
||||
# Set readonly based on environment (default to true for safety)
|
||||
# Set to false explicitly when new profiles need to be created
|
||||
readonly(ENV["MATCH_READONLY"] == "false" ? false : true)
|
||||
|
||||
# Optional: The Git branch that is used for match.
|
||||
# Default is 'master'.
|
||||
|
||||
# Optional: Path to a specific SSH key to be used by match.
|
||||
# Only needed if you're using a private repository and match needs to use SSH keys for authentication.
|
||||
# ssh_key("/path/to/your/private/key")
|
||||
|
||||
# Optional: Define the platform to use, can be 'ios', 'macos', or 'tvos'.
|
||||
# For React Native projects, you'll typically use 'ios'.
|
||||
# Define the platform to use
|
||||
platform("ios")
|
||||
|
||||
# Git basic authentication through access token
|
||||
# This is useful for CI/CD environments where SSH keys aren't available
|
||||
git_basic_authorization(ENV["GIT_ACCESS_TOKEN"])
|
||||
|
||||
# Storage mode (git by default)
|
||||
storage_mode("git")
|
||||
|
||||
# Always retry on network failures
|
||||
retry_on_exception(true)
|
||||
|
||||
# Optional: The Git branch that is used for match
|
||||
# Default is 'master'
|
||||
# branch("main")
|
||||
|
|
Loading…
Add table
Reference in a new issue