# 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" # Reads the per-app suffix from LoopFollowDisplayNameConfig.xcconfig so the same # Fastfile produces the correct bundle identifiers in every sister repository # (LoopFollow, LoopFollow_Second, LoopFollow_Third) without per-repo edits. def loopfollow_app_suffix root = GITHUB_WORKSPACE || File.expand_path("..", __dir__) path = File.join(root, "LoopFollowDisplayNameConfig.xcconfig") return "" unless File.exist?(path) File.foreach(path) do |line| next if line.strip.start_with?("//") if (match = line.match(/^\s*app_suffix\s*=\s*(.*)$/)) return match[1].strip end end "" end APP_SUFFIX = loopfollow_app_suffix APP_IDENTIFIER = "com.#{TEAMID}.LoopFollow#{APP_SUFFIX}" LA_IDENTIFIER = "#{APP_IDENTIFIER}.LoopFollowLAExtension" platform :ios do desc "Build Loop Follow" lane :build_LoopFollow do setup_ci if ENV['CI'] update_project_team( path: "#{GITHUB_WORKSPACE}/LoopFollow.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: APP_IDENTIFIER, api_key: api_key, ) current_build_number = previous_build_number + 1 increment_build_number( xcodeproj: "#{GITHUB_WORKSPACE}/LoopFollow.xcodeproj", build_number: current_build_number ) match( type: "appstore", git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}"), app_identifier: [ APP_IDENTIFIER, LA_IDENTIFIER ] ) mapping = Actions.lane_context[ SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING ] update_code_signing_settings( path: "#{GITHUB_WORKSPACE}/LoopFollow.xcodeproj", profile_name: mapping[APP_IDENTIFIER], code_sign_identity: "iPhone Distribution", targets: ["LoopFollow"] ) update_code_signing_settings( path: "#{GITHUB_WORKSPACE}/LoopFollow.xcodeproj", profile_name: mapping[LA_IDENTIFIER], code_sign_identity: "iPhone Distribution", targets: ["LoopFollowLAExtensionExtension"] ) gym( export_method: "app-store", scheme: "LoopFollow", output_name: "LoopFollow.ipa", configuration: "Release", destination: 'generic/platform=iOS', buildlog_path: 'buildlog', export_options: { provisioningProfiles: { APP_IDENTIFIER => mapping[APP_IDENTIFIER], LA_IDENTIFIER => mapping[LA_IDENTIFIER] } } ) 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: "LoopFollow.ipa", skip_waiting_for_build_processing: true, ) end desc "Provision Identifiers and Certificates" lane :identifiers 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}" ) 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("LoopFollow", APP_IDENTIFIER, [ Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS, Spaceship::ConnectAPI::BundleIdCapability::Type::PUSH_NOTIFICATIONS ]) configure_bundle_id("LoopFollow Live Activity Extension", LA_IDENTIFIER, [ Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS ]) end desc "Provision Certificates" lane :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( type: "appstore", force: false, verbose: true, git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}"), app_identifier: [ APP_IDENTIFIER, LA_IDENTIFIER ] ) end desc "Validate Secrets" lane :validate_secrets do setup_ci if ENV['CI'] ENV["MATCH_READONLY"] = true.to_s app_store_connect_api_key( key_id: "#{FASTLANE_KEY_ID}", issuer_id: "#{FASTLANE_ISSUER_ID}", key_content: "#{FASTLANE_KEY}" ) 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