# This file contains the fastlane.tools configuration # You can find the documentation at https://docs.fastlane.tools # # For a list of all available actions, check out # # https://docs.fastlane.tools/actions # # For a list of all available plugins, check out # # https://docs.fastlane.tools/plugins/available-plugins # default_platform(:ios) TEAMID = ENV["TEAMID"] GH_PAT = ENV["GH_PAT"] GITHUB_WORKSPACE = ENV["GITHUB_WORKSPACE"] GITHUB_REPOSITORY_OWNER = ENV["GITHUB_REPOSITORY_OWNER"] FASTLANE_KEY_ID = ENV["FASTLANE_KEY_ID"] FASTLANE_ISSUER_ID = ENV["FASTLANE_ISSUER_ID"] FASTLANE_KEY = ENV["FASTLANE_KEY"] DEVICE_NAME = ENV["DEVICE_NAME"] DEVICE_ID = ENV["DEVICE_ID"] ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120" # Define method to parse xcconfig file, and replace $(DEVELOPMENT_TEAM) with ENV["TEAMID"] def parse_xcconfig_file(path) xcconfig = {} File.open(path).each_line do |line| line.strip! next if line.empty? || line.start_with?('//') parts = line.split('=') next if parts.length < 2 # Skip lines without '=' key, value = parts.map(&:strip) # Replace $(DEVELOPMENT_TEAM) with ENV["TEAMID"] value = value.gsub('$(DEVELOPMENT_TEAM)', TEAMID) xcconfig[key] = value end xcconfig end # Path to config.xcconfig file xcconfig_path = "#{GITHUB_WORKSPACE}/Config.xcconfig" # Load the variables from config.xcconfig xcconfig = parse_xcconfig_file(xcconfig_path) # Access BUNDLE_IDENTIFIER from the xcconfig file after replacing $(DEVELOPMENT_TEAM) with ENV["TEAMID"] ENV["BUNDLE_ID"] = xcconfig["BUNDLE_IDENTIFIER"] # limit lane names to letters and underscores platform :ios do desc "Build Trio" lane :build_trio do setup_ci if ENV['CI'] BUNDLE_ID = ENV["BUNDLE_ID"] update_project_team( path: "#{GITHUB_WORKSPACE}/Trio.xcodeproj", teamid: "#{TEAMID}" ) api_key = app_store_connect_api_key( key_id: "#{FASTLANE_KEY_ID}", issuer_id: "#{FASTLANE_ISSUER_ID}", key_content: "#{FASTLANE_KEY}" ) previous_build_number = latest_testflight_build_number( app_identifier: "#{BUNDLE_ID}", api_key: api_key, ) current_build_number = previous_build_number + 1 increment_build_number( xcodeproj: "#{GITHUB_WORKSPACE}/Trio.xcodeproj", build_number: current_build_number ) match( type: "appstore", git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}"), app_identifier: [ "#{BUNDLE_ID}", "#{BUNDLE_ID}.watchkitapp", "#{BUNDLE_ID}.watchkitapp.TrioWatchComplication", "#{BUNDLE_ID}.LiveActivity" ] ) mapping = Actions.lane_context[ SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING ] update_code_signing_settings( path: "#{GITHUB_WORKSPACE}/Trio.xcodeproj", profile_name: mapping["#{BUNDLE_ID}"], code_sign_identity: "iPhone Distribution", targets: ["Trio"] ) update_code_signing_settings( path: "#{GITHUB_WORKSPACE}/Trio.xcodeproj", profile_name: mapping["#{BUNDLE_ID}.watchkitapp"], code_sign_identity: "iPhone Distribution", targets: ["Trio Watch App"] ) update_code_signing_settings( path: "#{GITHUB_WORKSPACE}/Trio.xcodeproj", profile_name: mapping["#{BUNDLE_ID}.watchkitapp.TrioWatchComplication"], code_sign_identity: "iPhone Distribution", targets: ["Trio Watch Complication Extension"] ) update_code_signing_settings( path: "#{GITHUB_WORKSPACE}/Trio.xcodeproj", profile_name: mapping["#{BUNDLE_ID}.LiveActivity"], code_sign_identity: "iPhone Distribution", targets: ["LiveActivityExtension"] ) gym( export_method: "app-store", scheme: "Trio", output_name: "Trio.ipa", configuration: "Release", destination: 'generic/platform=iOS', buildlog_path: 'buildlog' ) copy_artifacts( target_path: "artifacts", artifacts: ["*.mobileprovision", "*.ipa", "*.dSYM.zip"] ) end desc "Push to TestFlight" lane :release do api_key = app_store_connect_api_key( key_id: "#{FASTLANE_KEY_ID}", issuer_id: "#{FASTLANE_ISSUER_ID}", key_content: "#{FASTLANE_KEY}" ) upload_to_testflight( api_key: api_key, skip_submission: false, ipa: "Trio.ipa", skip_waiting_for_build_processing: true, changelog: git_branch+" "+last_git_commit[:abbreviated_commit_hash], ) end desc "Provision Identifiers and Certificates" lane :identifiers do setup_ci if ENV['CI'] ENV["MATCH_READONLY"] = false.to_s BUNDLE_ID = ENV["BUNDLE_ID"] app_store_connect_api_key( key_id: "#{FASTLANE_KEY_ID}", issuer_id: "#{FASTLANE_ISSUER_ID}", key_content: "#{FASTLANE_KEY}" ) def configure_bundle_id(name, identifier, capabilities) bundle_id = Spaceship::ConnectAPI::BundleId.find(identifier) || Spaceship::ConnectAPI::BundleId.create( name: name, identifier: identifier, platform: "IOS" ) existing = bundle_id.get_capabilities.map(&:capability_type) capabilities.reject { |c| existing.include?(c) }.each do |cap| bundle_id.create_capability(cap) end end configure_bundle_id("Trio", "#{BUNDLE_ID}", [ Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS, Spaceship::ConnectAPI::BundleIdCapability::Type::HEALTHKIT, Spaceship::ConnectAPI::BundleIdCapability::Type::NFC_TAG_READING, Spaceship::ConnectAPI::BundleIdCapability::Type::PUSH_NOTIFICATIONS ]) configure_bundle_id("Trio Watch Complication", "#{BUNDLE_ID}.watchkitapp.TrioWatchComplication", [ Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS ]) configure_bundle_id("Trio Watch App", "#{BUNDLE_ID}.watchkitapp", [ Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS, Spaceship::ConnectAPI::BundleIdCapability::Type::HEALTHKIT ]) configure_bundle_id("Trio LiveActivity", "#{BUNDLE_ID}.LiveActivity", []) end desc "Provision Certificates" lane :certs do setup_ci if ENV['CI'] ENV["MATCH_READONLY"] = false.to_s BUNDLE_ID = ENV["BUNDLE_ID"] app_store_connect_api_key( key_id: "#{FASTLANE_KEY_ID}", issuer_id: "#{FASTLANE_ISSUER_ID}", key_content: "#{FASTLANE_KEY}" ) match( type: "appstore", force: false, verbose: true, git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}"), app_identifier: [ "#{BUNDLE_ID}", "#{BUNDLE_ID}.watchkitapp", "#{BUNDLE_ID}.watchkitapp.TrioWatchComplication", "#{BUNDLE_ID}.LiveActivity" ] ) end desc "Validate Secrets" lane :validate_secrets do setup_ci if ENV['CI'] ENV["MATCH_READONLY"] = true.to_s BUNDLE_ID = ENV["BUNDLE_ID"] app_store_connect_api_key( key_id: "#{FASTLANE_KEY_ID}", issuer_id: "#{FASTLANE_ISSUER_ID}", key_content: "#{FASTLANE_KEY}" ) def find_bundle_id(identifier) bundle_id = Spaceship::ConnectAPI::BundleId.find(identifier) end find_bundle_id("#{BUNDLE_ID}") match( type: "appstore", git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}"), app_identifier: [], ) end desc "Nuke Certs" lane :nuke_certs do setup_ci if ENV['CI'] ENV["MATCH_READONLY"] = false.to_s app_store_connect_api_key( key_id: "#{FASTLANE_KEY_ID}", issuer_id: "#{FASTLANE_ISSUER_ID}", key_content: "#{FASTLANE_KEY}" ) match_nuke( type: "appstore", team_id: "#{TEAMID}", skip_confirmation: true, git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}") ) end desc "Check Certificates and Trigger Workflow for Expired or Missing Certificates" lane :check_and_renew_certificates do setup_ci if ENV['CI'] ENV["MATCH_READONLY"] = false.to_s # Authenticate using App Store Connect API Key api_key = app_store_connect_api_key( key_id: ENV["FASTLANE_KEY_ID"], issuer_id: ENV["FASTLANE_ISSUER_ID"], key_content: ENV["FASTLANE_KEY"] # Ensure valid key content ) # Initialize flag to track if renewal of certificates is needed new_certificate_needed = false # Fetch all certificates certificates = Spaceship::ConnectAPI::Certificate.all # Filter for Distribution Certificates distribution_certs = certificates.select { |cert| cert.certificate_type == "DISTRIBUTION" } # Handle case where no distribution certificates are found if distribution_certs.empty? puts "No Distribution certificates found! Triggering action to create certificate." new_certificate_needed = true else # Check for expiration distribution_certs.each do |cert| expiration_date = Time.parse(cert.expiration_date) puts "Current Distribution Certificate: #{cert.id}, Expiration date: #{expiration_date}" if expiration_date < Time.now puts "Distribution Certificate #{cert.id} is expired! Triggering action to renew certificate." new_certificate_needed = true else puts "Distribution certificate #{cert.id} is valid. No action required." end end end # Write result to new_certificate_needed.txt file_path = File.expand_path('new_certificate_needed.txt') File.write(file_path, new_certificate_needed ? 'true' : 'false') # Log the absolute path and contents of the new_certificate_needed.txt file puts "" puts "Absolute path of new_certificate_needed.txt: #{file_path}" new_certificate_needed_content = File.read(file_path) puts "Certificate creation or renewal needed: #{new_certificate_needed_content}" end end