$npx -y skills add greenstevester/fastlane-skill --skill setup-fastlaneSet up Fastlane for iOS/macOS app automation
| 1 | ## Fastlane Setup (One-Time) |
| 2 | |
| 3 | ``` |
| 4 | ┌─────────────────────────────────────────────────────────────────┐ |
| 5 | │ ONE-TIME SETUP │ |
| 6 | │ ══════════════ │ |
| 7 | │ After this, you'll have: │ |
| 8 | │ │ |
| 9 | │ fastlane ios test → Run tests │ |
| 10 | │ fastlane ios beta → Upload to TestFlight │ |
| 11 | │ fastlane ios release → Submit to App Store │ |
| 12 | │ │ |
| 13 | │ Do this once per project. Takes ~10 minutes. │ |
| 14 | └─────────────────────────────────────────────────────────────────┘ |
| 15 | ``` |
| 16 | |
| 17 | ### Environment Check |
| 18 | - Xcode CLI: !`xcode-select -p 2>/dev/null && echo "✓" || echo "✗ Run: xcode-select --install"` |
| 19 | - Homebrew: !`brew --version 2>/dev/null | head -1 || echo "✗ Run: /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""` |
| 20 | - Fastlane: !`fastlane --version 2>/dev/null | grep -o "fastlane [0-9.]*" | head -1 || echo "✗ Run: brew install fastlane"` |
| 21 | |
| 22 | ### Your Project |
| 23 | - Project: !`find . -maxdepth 2 -name "*.xcodeproj" 2>/dev/null | head -1 || echo "None found"` |
| 24 | - Workspace: !`find . -maxdepth 2 -name "*.xcworkspace" ! -path "*/.build/*" ! -path "*/xcodeproj/*" 2>/dev/null | head -1 || echo "None"` |
| 25 | - Bundle ID: !`grep -r "PRODUCT_BUNDLE_IDENTIFIER" --include="*.pbxproj" . 2>/dev/null | head -1 | sed 's/.*= //' | tr -d '";' || echo "Not found"` |
| 26 | - Team ID: !`grep -r "DEVELOPMENT_TEAM" --include="*.pbxproj" . 2>/dev/null | head -1 | sed 's/.*= //' | tr -d '";' || echo "Not found"` |
| 27 | |
| 28 | ### Target: ${ARGUMENTS:-current directory} |
| 29 | |
| 30 | --- |
| 31 | |
| 32 | ## Step 1: Install Fastlane |
| 33 | |
| 34 | ```bash |
| 35 | brew install fastlane |
| 36 | ``` |
| 37 | |
| 38 | > **Why Homebrew?** Bundler 4.x broke Fastlane's Ruby dependencies. Homebrew avoids all version conflicts. |
| 39 | |
| 40 | --- |
| 41 | |
| 42 | ## Step 2: Create Configuration Files |
| 43 | |
| 44 | ### `fastlane/Appfile` |
| 45 | ```ruby |
| 46 | app_identifier("{{BUNDLE_ID}}") # Your bundle ID |
| 47 | apple_id("{{APPLE_ID}}") # Your Apple ID email |
| 48 | team_id("{{TEAM_ID}}") # Your team ID |
| 49 | ``` |
| 50 | |
| 51 | ### `fastlane/Fastfile` |
| 52 | ```ruby |
| 53 | default_platform(:ios) |
| 54 | |
| 55 | platform :ios do |
| 56 | desc "Run tests" |
| 57 | lane :test do |
| 58 | scan(scheme: "{{SCHEME}}") |
| 59 | end |
| 60 | |
| 61 | desc "Upload to TestFlight (internal testers)" |
| 62 | lane :beta do |options| |
| 63 | increment_build_number unless options[:skip_build_increment] |
| 64 | gym(scheme: "{{SCHEME}}", export_method: "app-store") |
| 65 | pilot(skip_waiting_for_build_processing: true) |
| 66 | end |
| 67 | |
| 68 | desc "Upload to TestFlight and distribute to external testers" |
| 69 | lane :beta_external do |options| |
| 70 | increment_build_number |
| 71 | gym(scheme: "{{SCHEME}}", export_method: "app-store") |
| 72 | pilot( |
| 73 | distribute_external: true, |
| 74 | groups: ["External Testers"], |
| 75 | changelog: options[:changelog] || "Bug fixes and improvements", |
| 76 | skip_waiting_for_build_processing: false |
| 77 | ) |
| 78 | end |
| 79 | |
| 80 | desc "Submit the latest TestFlight build for App Store review" |
| 81 | lane :release do |
| 82 | deliver( |
| 83 | build_number: latest_testflight_build_number.to_s, |
| 84 | submit_for_review: true, |
| 85 | automatic_release: false, |
| 86 | force: true, |
| 87 | skip_binary_upload: true, |
| 88 | skip_metadata: false, |
| 89 | skip_screenshots: false |
| 90 | ) |
| 91 | end |
| 92 | |
| 93 | desc "Build, upload, and submit a fresh build to the App Store" |
| 94 | lane :release_full do |options| |
| 95 | increment_version_number(version_number: options[:version]) if options[:version] |
| 96 | increment_build_number |
| 97 | gym(scheme: "{{SCHEME}}", export_method: "app-store") |
| 98 | deliver( |
| 99 | submit_for_review: true, |
| 100 | automatic_release: options[:auto_release] == true, |
| 101 | force: true |
| 102 | ) |
| 103 | end |
| 104 | end |
| 105 | ``` |
| 106 | |
| 107 | Replace `{{SCHEME}}` with your app's scheme name (usually the app name). |
| 108 | |
| 109 | --- |
| 110 | |
| 111 | ## Step 3: Set Up Metadata (Optional) |
| 112 | |
| 113 | Download your existing App Store listing: |
| 114 | ```bash |
| 115 | fastlane deliver download_metadata |
| 116 | fastlane deliver download_screenshots |
| 117 | ``` |
| 118 | |
| 119 | This creates `fastlane/metadata/` with editable text files for your app description, keywords, etc. |
| 120 | |
| 121 | --- |
| 122 | |
| 123 | ## You're Done! |
| 124 | |
| 125 | ```bash |
| 126 | # Verify setup |
| 127 | fastlane lanes |
| 128 | |
| 129 | # Run your first lane |
| 130 | fastlane ios test |
| 131 | ``` |
| 132 | |
| 133 | ### Quick Reference |
| 134 | | Command | What it does | |
| 135 | |---------|--------------| |
| 136 | | `fastlane ios test` | Run tests | |
| 137 | | `fastlane ios beta` | Build + TestFlight | |
| 138 | | `fastlane ios release` | Build + App Store | |
| 139 | | `fastlane deliver download_metadata` | Fetch App Store listing | |
| 140 | |
| 141 | --- |
| 142 | |
| 143 | ## Next Steps |
| 144 | |
| 145 | - **Code signing issues?** Ask: "Set up Match for code signing" |
| 146 | - **Need screenshots?** Ask: "Automate my App Store screenshots" |
| 147 | - **CI/CD setup?** See [Xcode Cloud guide](../../docs/xcode-cloud.md) |