Files
sf-app-platform/apps/mobile_app/ios/scripts/add-copy-google-service-build-phase.rb
JulianAlcala 81284d7efe feat(firebase): integrate Firebase + APNs/FCM push notifications
Phase 2 of multi-environment setup. Adds Firebase core, Crashlytics,
Analytics, Remote Config, Performance, Messaging and flutter_local_notifications,
plus full APNs configuration for iOS push.

- Wire setupFirebase(env) and setupNotifications() in initApp
- Add firebase_options for dev and staging via flutterfire (sf-platform-pre)
- Register google-services / firebase-perf / crashlytics gradle plugins
- Add per-flavor GoogleService-Info.plist with Build Phase script that
  copies the right plist into the .app bundle based on \$CONFIGURATION
- Bump iOS deployment target 13.0 -> 15.0 (required by firebase_analytics)
- Pin flutter_local_notifications to ^19.4.2 (v20+ needs Dart SDK >=3.10)
- Add aps-environment to staging (development) and production entitlements;
  development flavor intentionally excluded (no App Store Connect entry)
- Fix AppDelegate.swift to call super.application after forwarding to
  AntelopAppDelegate, otherwise Firebase Messaging swizzling breaks and
  the APNs token is never captured
- Crashlytics reports in all builds (debug + release) for early detection
- Tag analytics events with env user property per flavor
- App Check intentionally not included (debug-token friction with large
  QA team); can be re-added release-only later
2026-04-07 03:33:25 +02:00

86 lines
2.8 KiB
Ruby

#!/usr/bin/env ruby
#
# Adds a "Copy GoogleService-Info" Run Script Build Phase to the Runner target.
# The script copies ios/flavors/{flavor}/GoogleService-Info.plist to the .app
# bundle based on the build CONFIGURATION (Debug-development, Release-staging, etc.).
#
# Idempotent: if the build phase already exists, does nothing.
#
# Usage:
# ruby ios/scripts/add-copy-google-service-build-phase.rb
require 'xcodeproj'
PROJECT_PATH = File.expand_path('../../Runner.xcodeproj', __FILE__)
TARGET_NAME = 'Runner'
PHASE_NAME = 'Copy GoogleService-Info'
SHELL_SCRIPT = '"${SRCROOT}/scripts/copy-google-service-plist.sh"'
project = Xcodeproj::Project.open(PROJECT_PATH)
target = project.targets.find { |t| t.name == TARGET_NAME }
unless target
abort "ERROR: Target '#{TARGET_NAME}' not found in project."
end
# Check if the build phase already exists (idempotency)
existing = target.build_phases.find do |phase|
phase.is_a?(Xcodeproj::Project::Object::PBXShellScriptBuildPhase) && phase.name == PHASE_NAME
end
if existing
puts "OK: Build phase '#{PHASE_NAME}' already exists. No changes needed."
exit 0
end
# Create the new build phase
phase = target.new_shell_script_build_phase(PHASE_NAME)
phase.shell_path = '/bin/sh'
phase.shell_script = SHELL_SCRIPT
phase.input_paths = []
phase.output_paths = []
phase.run_only_for_deployment_postprocessing = '0'
# Move it before the embed frameworks phase (or at the end if no such phase)
# Order: Sources -> Frameworks -> Resources -> ... -> ThinBinary -> CopyGoogleService -> EmbedPodsFrameworks -> CopyPodsResources
build_phases = target.build_phases
# Find the index of "Thin Binary" if it exists
thin_binary_idx = build_phases.find_index do |p|
p.respond_to?(:name) && p.name == 'Thin Binary'
end
# Find the index of "[CP] Embed Pods Frameworks" if it exists
embed_pods_idx = build_phases.find_index do |p|
p.respond_to?(:name) && p.name && p.name.include?('Embed Pods Frameworks')
end
# Remove the just-added phase from its current position (it gets appended at the end)
build_phases.delete(phase)
# Insert at the right spot
target_idx = if thin_binary_idx && embed_pods_idx && thin_binary_idx < embed_pods_idx
# Place between Thin Binary and Embed Pods Frameworks
embed_pods_idx
elsif thin_binary_idx
# Place right after Thin Binary
thin_binary_idx + 1
elsif embed_pods_idx
# Place right before Embed Pods Frameworks
embed_pods_idx
else
# Append at the end
build_phases.length
end
build_phases.insert(target_idx, phase)
project.save
puts "OK: Added build phase '#{PHASE_NAME}' at position #{target_idx}."
puts "Build phases order:"
target.build_phases.each_with_index do |p, i|
name = p.respond_to?(:name) && p.name ? p.name : p.class.name
puts " #{i}: #{name}"
end