# Fastfile pour GEOSECTOR Flutter App # Version: 2.0 # Date: 2025-10-26 # Auteur: Pierre (avec l'aide de Claude) # # Usage depuis le Mac mini (après transfert depuis Debian): # cd /Users/pierre/dev/geosector/app_346 # fastlane android build # Build seulement (test local) # fastlane android release # Build + Upload vers Play Store # fastlane ios build # Build iOS seulement (test local) # fastlane ios release # Build + Upload vers TestFlight default_platform(:android) # ============================================================================= # ANDROID # ============================================================================= platform :android do desc "Build Android AAB (bundle) en mode release - sans upload" lane :build do UI.header("🤖 Build Android Bundle") # Étape 1 : Nettoyer le projet UI.message("🧹 [1/6] Nettoyage du projet...") sh("cd .. && flutter clean") UI.success("Projet nettoyé") # Étape 2 : Récupérer les dépendances UI.message("📦 [2/6] Récupération des dépendances...") sh("cd .. && flutter pub get") UI.success("Dépendances récupérées") # Étape 3 : Patch nfc_manager UI.message("🔧 [3/6] Application du patch nfc_manager...") sh("cd .. && ./fastlane/scripts/commun/fix-nfc-manager.sh") UI.success("Patch nfc_manager appliqué") # Étape 4 : Analyser le code (non bloquant) UI.message("🔍 [4/6] Analyse du code...") begin sh("cd .. && flutter analyze --no-fatal-infos --no-fatal-warnings") UI.success("Analyse du code terminée") rescue => ex UI.important("⚠️ Des warnings détectés, mais on continue...") end # Étape 5 : Build du bundle AAB UI.message("🏗️ [5/6] Build du bundle Android (RELEASE)...") sh("cd .. && flutter build appbundle --release") UI.success("Bundle AAB généré") # Étape 6 : Copier le bundle avec version UI.message("📋 [6/6] Copie du bundle avec version...") version_info = sh("cd .. && grep '^version:' pubspec.yaml | sed 's/version: //'").strip version_code = version_info.split('+').last final_name = "geosector-#{version_code}.aab" sh("cd .. && cp build/app/outputs/bundle/release/app-release.aab #{final_name}") bundle_size = sh("cd .. && du -h #{final_name} | cut -f1").strip UI.success("✅ Build Android terminé avec succès!") UI.success("📦 Bundle: #{final_name}") UI.success("📊 Taille: #{bundle_size}") end desc "Build Android AAB + Upload vers Google Play Console (Internal Testing)" lane :release do UI.header("🚀 Build & Deploy Android vers Play Store") # Étape 1 : Nettoyer le projet UI.message("🧹 [1/7] Nettoyage du projet...") sh("cd .. && flutter clean") UI.success("Projet nettoyé") # Étape 2 : Récupérer les dépendances UI.message("📦 [2/7] Récupération des dépendances...") sh("cd .. && flutter pub get") UI.success("Dépendances récupérées") # Étape 3 : Patch nfc_manager UI.message("🔧 [3/7] Application du patch nfc_manager...") sh("cd .. && ./fastlane/scripts/commun/fix-nfc-manager.sh") UI.success("Patch nfc_manager appliqué") # Étape 4 : Analyser le code (non bloquant) UI.message("🔍 [4/7] Analyse du code...") begin sh("cd .. && flutter analyze --no-fatal-infos --no-fatal-warnings") UI.success("Analyse du code terminée") rescue => ex UI.important("⚠️ Des warnings détectés, mais on continue...") end # Étape 5 : Build du bundle AAB UI.message("🏗️ [5/7] Build du bundle Android (RELEASE)...") sh("cd .. && flutter build appbundle --release") UI.success("Bundle AAB généré") # Étape 6 : Récupérer les informations de version version_info = sh("cd .. && grep '^version:' pubspec.yaml | sed 's/version: //'").strip version_name = version_info.split('+').first version_code = version_info.split('+').last UI.message("📋 Version: #{version_name} (#{version_code})") # Étape 7 : Upload vers Google Play Console UI.message("📤 [6/7] Upload vers Google Play Console...") upload_to_play_store( json_key: '../android/google-play-api-key.json', # Credentials Google Play API track: 'internal', # Track: internal, alpha, beta, ou production aab: '../build/app/outputs/bundle/release/app-release.aab', skip_upload_apk: true, # On n'upload que l'AAB skip_upload_metadata: true, # On ne met pas à jour les métadonnées skip_upload_images: true, # On ne met pas à jour les images skip_upload_screenshots: true, # On ne met pas à jour les screenshots release_status: 'completed', # completed = publié immédiatement sur le track version_code: version_code.to_i, # Version code explicite package_name: 'fr.geosector.app3' # Package name explicite ) UI.success("✅ Déploiement Android terminé avec succès!") UI.success("📦 Version: #{version_name} (Build #{version_code})") UI.success("🎯 Track: Internal Testing") UI.success("🌐 URL: https://play.google.com/console") end desc "Upload un AAB existant vers Google Play Console" lane :upload do UI.header("📤 Upload AAB vers Play Store") # Vérifier que le bundle existe aab_path = '../build/app/outputs/bundle/release/app-release.aab' if File.exist?(aab_path) # Récupérer la version version_info = sh("cd .. && grep '^version:' pubspec.yaml | sed 's/version: //'").strip version_code = version_info.split('+').last UI.message("📦 Upload du bundle existant...") upload_to_play_store( json_key: '../android/google-play-api-key.json', track: 'internal', aab: aab_path, skip_upload_apk: true, skip_upload_metadata: true, skip_upload_images: true, skip_upload_screenshots: true, release_status: 'completed', version_code: version_code.to_i, package_name: 'fr.geosector.app3' ) UI.success("✅ Upload terminé!") else UI.user_error!("❌ Bundle introuvable: #{aab_path}") UI.message("💡 Lancez d'abord: fastlane android build") end end end # ============================================================================= # iOS # ============================================================================= platform :ios do desc "Build iOS IPA en mode release - sans upload" lane :build do UI.header("🍎 Build iOS IPA") # Étape 1 : Vérifier le cache local (préparé sur Debian) UI.message("📦 [1/6] Vérification du cache local...") cache_path = File.expand_path("../.pub-cache-local") if !Dir.exist?(cache_path) UI.user_error!("❌ Cache local introuvable: #{cache_path}") UI.message("💡 Exécutez './deploy-app.sh' sur Debian pour préparer le projet") end ENV['PUB_CACHE'] = cache_path ENV['GRADLE_USER_HOME'] = File.expand_path("../.gradle-local") UI.success("Cache local configuré: #{cache_path}") UI.important("⚠️ flutter pub get et patchs déjà appliqués sur Debian") # Étape 2 : Nettoyer les artefacts de build UI.message("🧹 [2/6] Nettoyage des artefacts...") sh("cd .. && rm -rf build .dart_tool ios/Pods ios/.symlinks") UI.success("Artefacts nettoyés") # Étape 3 : Installation des pods UI.message("📦 [3/6] Installation des CocoaPods...") sh("cd ../ios && pod install") UI.success("CocoaPods installés") # Étape 4 : Analyser le code (non bloquant) UI.message("🔍 [4/6] Analyse du code...") begin sh("cd .. && flutter analyze --no-fatal-infos --no-fatal-warnings") UI.success("Analyse du code terminée") rescue => ex UI.important("⚠️ Des warnings détectés, mais on continue...") end # Étape 5 : Build Flutter iOS UI.message("🏗️ [5/6] Build Flutter iOS (RELEASE)...") sh("cd .. && flutter build ios --release --no-codesign") UI.success("Build Flutter iOS généré") # Étape 6 : Archive Xcode (gym) UI.message("📦 [6/6] Archive Xcode et export IPA...") build_ios_app( workspace: "./Runner.xcworkspace", scheme: "Runner", export_method: "app-store", export_options: { method: "app-store", teamID: "6WT84NWCTC", uploadBitcode: false, uploadSymbols: true, compileBitcode: false, signingStyle: "automatic", destination: "export", provisioningProfiles: { "fr.geosector.app3" => "match AppStore fr.geosector.app3" } }, output_directory: "../build/ios/ipa", output_name: "Runner.ipa", clean: false, skip_profile_detection: true ) UI.success("IPA généré") # Afficher les informations de version version_info = sh("cd .. && grep '^version:' pubspec.yaml | sed 's/version: //'").strip ipa_size = sh("cd .. && du -h build/ios/ipa/Runner.ipa | cut -f1").strip UI.success("✅ Build iOS terminé avec succès!") UI.success("📦 IPA: build/ios/ipa/Runner.ipa") UI.success("📊 Version: #{version_info}") UI.success("📊 Taille: #{ipa_size}") end desc "Build iOS IPA + Upload vers TestFlight" lane :release do UI.header("🚀 Build & Deploy iOS vers TestFlight") # Étape 1 : Vérifier le cache local (préparé sur Debian) UI.message("📦 [1/7] Vérification du cache local...") cache_path = File.expand_path("../.pub-cache-local") if !Dir.exist?(cache_path) UI.user_error!("❌ Cache local introuvable: #{cache_path}") UI.message("💡 Exécutez './deploy-app.sh' sur Debian pour préparer le projet") end ENV['PUB_CACHE'] = cache_path ENV['GRADLE_USER_HOME'] = File.expand_path("../.gradle-local") UI.success("Cache local configuré: #{cache_path}") UI.important("⚠️ flutter pub get et patchs déjà appliqués sur Debian") # Étape 2 : Nettoyer les artefacts de build UI.message("🧹 [2/7] Nettoyage des artefacts...") sh("cd .. && rm -rf build .dart_tool ios/Pods ios/.symlinks") UI.success("Artefacts nettoyés") # Étape 3 : Installation des pods UI.message("📦 [3/7] Installation des CocoaPods...") sh("cd ../ios && pod install") UI.success("CocoaPods installés") # Étape 4 : Récupérer les informations de version version_info = sh("cd .. && grep '^version:' pubspec.yaml | sed 's/version: //'").strip version_name = version_info.split('+').first version_code = version_info.split('+').last UI.message("📋 Version: #{version_name} (#{version_code})") # Étape 5 : Analyser le code (non bloquant) UI.message("🔍 [4/7] Analyse du code...") begin sh("cd .. && flutter analyze --no-fatal-infos --no-fatal-warnings") UI.success("Analyse du code terminée") rescue => ex UI.important("⚠️ Des warnings détectés, mais on continue...") end # Étape 6 : Build Flutter iOS UI.message("🏗️ [5/7] Build Flutter iOS (RELEASE)...") sh("cd .. && flutter build ios --release --no-codesign") UI.success("Build Flutter iOS généré") # Étape 7 : Archive Xcode UI.message("📦 [6/7] Archive Xcode...") build_ios_app( workspace: "./Runner.xcworkspace", scheme: "Runner", export_method: "app-store", export_options: { method: "app-store", teamID: "6WT84NWCTC", uploadBitcode: false, uploadSymbols: true, compileBitcode: false, signingStyle: "automatic", provisioningProfiles: { "fr.geosector.app3" => "match AppStore fr.geosector.app3" } }, output_directory: "../build/ios/ipa", output_name: "Runner.ipa", clean: false, skip_profile_detection: true ) UI.success("IPA généré") # Étape 7 : Upload vers TestFlight UI.message("📤 [7/7] Upload vers TestFlight...") upload_to_testflight( apple_id: "contact@d6soft.com", app_identifier: "fr.geosector.app3", team_id: "6WT84NWCTC", ipa: "../build/ios/ipa/Runner.ipa", skip_waiting_for_build_processing: true, skip_submission: true, distribute_external: false ) UI.success("✅ Déploiement iOS terminé avec succès!") UI.success("📦 Version: #{version_name} (Build #{version_code})") UI.success("🎯 Track: TestFlight (Internal)") UI.success("🌐 URL: https://appstoreconnect.apple.com") end desc "Upload un IPA existant vers TestFlight" lane :upload do UI.header("📤 Upload IPA vers TestFlight") # Vérifier que l'IPA existe ipa_path = '../build/ios/ipa/Runner.ipa' if File.exist?(ipa_path) # Récupérer la version version_info = sh("cd .. && grep '^version:' pubspec.yaml | sed 's/version: //'").strip version_name = version_info.split('+').first version_code = version_info.split('+').last UI.message("📦 Upload de l'IPA existant...") upload_to_testflight( apple_id: "contact@d6soft.com", app_identifier: "fr.geosector.app3", team_id: "6WT84NWCTC", ipa: ipa_path, skip_waiting_for_build_processing: true, skip_submission: true, distribute_external: false ) UI.success("✅ Upload terminé!") UI.success("📦 Version: #{version_name} (Build #{version_code})") else UI.user_error!("❌ IPA introuvable: #{ipa_path}") UI.message("💡 Lancez d'abord: fastlane ios build") end end end