Claude-skill-registry fastlane

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/fastlane" ~/.claude/skills/majiayu000-claude-skill-registry-fastlane && rm -rf "$T"
manifest: skills/data/fastlane/SKILL.md
source content

Fastlane Deployment Skill

Automate iOS and Android app building, code signing, and distribution.

When to Apply

Reference this skill when:

  • Setting up Fastlane for a new project
  • Configuring code signing with Match
  • Building lanes for TestFlight or App Store distribution
  • Setting up Android Play Store deployment
  • Debugging code signing or build failures
  • Configuring CI/CD pipelines for mobile apps
  • Integrating Fastlane with Tauri v2 projects

Quick Start

Minimal Fastfile Structure

default_platform(:ios)

platform :ios do
  desc "Build and upload to TestFlight"
  lane :beta do
    match(type: "appstore", readonly: true)
    build_app(scheme: "MyApp")
    upload_to_testflight
  end
end

platform :android do
  desc "Build and upload to Play Store beta"
  lane :beta do
    gradle(task: "bundle", build_type: "Release")
    upload_to_play_store(track: "beta")
  end
end

Tool Aliases

Fastlane provides shorter aliases for common actions:

AliasActionPurpose
gym
build_app
Build and sign iOS/macOS apps
pilot
upload_to_testflight
Upload to TestFlight
deliver
upload_to_app_store
Submit to App Store
supply
upload_to_play_store
Upload to Google Play
match
sync_code_signing
Sync certificates and profiles
cert
get_certificates
Download signing certificates
sigh
get_provisioning_profile
Download provisioning profiles
scan
run_tests
Run unit and UI tests
snapshot
capture_screenshots
Automated App Store screenshots
frameit
frame_screenshots
Add device frames to screenshots
produce
create_app_online
Create app in App Store Connect
pem
get_push_certificate
Download push notification certs
precheck
check_app_store_metadata
Validate metadata before submission

iOS Workflows

TestFlight Deployment

lane :beta do
  # Sync code signing
  match(type: "appstore", readonly: true)

  # Increment build number
  increment_build_number(
    build_number: Time.now.utc.strftime("%y%m%d%H%M")
  )

  # Build the app
  build_app(
    scheme: "MyApp",
    export_method: "app-store",
    output_directory: "./build"
  )

  # Upload to TestFlight
  upload_to_testflight(
    skip_waiting_for_build_processing: true,
    uses_non_exempt_encryption: false
  )
end

App Store Release

lane :release do
  match(type: "appstore", readonly: true)

  build_app(
    scheme: "MyApp",
    export_method: "app-store"
  )

  upload_to_app_store(
    skip_screenshots: true,
    skip_metadata: true,
    submit_for_review: false
  )
end

App Store Connect API Key

def api_key
  app_store_connect_api_key(
    key_id: ENV['APP_STORE_CONNECT_API_KEY_KEY_ID'],
    issuer_id: ENV['APP_STORE_CONNECT_API_KEY_ISSUER_ID'],
    key_content: ENV['APP_STORE_CONNECT_API_KEY_KEY'],
    is_key_content_base64: true
  )
end

lane :beta do
  upload_to_testflight(api_key: api_key)
end

Android Workflows

Play Store Beta

platform :android do
  lane :beta do
    gradle(
      task: "bundle",
      build_type: "Release",
      project_dir: "./android"
    )

    upload_to_play_store(
      track: "beta",
      aab: "./android/app/build/outputs/bundle/release/app-release.aab",
      skip_upload_metadata: true,
      skip_upload_images: true
    )
  end
end

Play Store Production

lane :release do
  gradle(task: "bundle", build_type: "Release")

  upload_to_play_store(
    track: "production",
    aab: "./android/app/build/outputs/bundle/release/app-release.aab"
  )
end

Code Signing with Match

Initial Setup

# Initialize Match configuration
fastlane match init

# Generate certificates (run once per team)
fastlane match appstore
fastlane match development

Matchfile Configuration

# fastlane/Matchfile
git_url("git@github.com:your-org/certificates.git")
storage_mode("git")
type("appstore")
app_identifier("com.example.app")
team_id("TEAM_ID")

S3/MinIO Storage (Alternative to Git)

# Matchfile for S3-compatible storage
storage_mode("s3")
s3_region("us-east-1")
s3_bucket("certificates")
s3_access_key(ENV['AWS_ACCESS_KEY_ID'])
s3_secret_access_key(ENV['AWS_SECRET_ACCESS_KEY'])

# For MinIO, set endpoint
# ENV['AWS_ENDPOINT_URL'] = "https://minio.example.com"

Using Match in Lanes

lane :beta do
  match(
    type: "appstore",
    readonly: true,  # Don't create new certs
    keychain_name: ENV['CI'] ? "fastlane_ci" : nil,
    keychain_password: ENV['CI'] ? "fastlane_ci_password" : nil
  )
end

CI/CD Integration

Keychain Setup for CI

CI_KEYCHAIN_NAME = "fastlane_ci"
CI_KEYCHAIN_PASSWORD = "fastlane_ci_password"

def setup_ci_keychain
  if ENV['CI']
    create_keychain(
      name: CI_KEYCHAIN_NAME,
      password: CI_KEYCHAIN_PASSWORD,
      default_keychain: true,
      unlock: true,
      timeout: 3600,
      lock_when_sleeps: false,
      add_to_search_list: true
    )
  end
end

def cleanup_ci_keychain
  if ENV['CI']
    delete_keychain(name: CI_KEYCHAIN_NAME)
  end
end

lane :beta do
  setup_ci_keychain
  match(
    type: "appstore",
    keychain_name: CI_KEYCHAIN_NAME,
    keychain_password: CI_KEYCHAIN_PASSWORD
  )
  # ... build and upload
  cleanup_ci_keychain
end

GitHub Actions Example

# .github/workflows/ios.yml
name: iOS Build
on: [push]

jobs:
  build:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Fastlane
        run: brew install fastlane

      - name: Build and Deploy
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
          APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.ASC_KEY_ID }}
          APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
          APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.ASC_KEY_CONTENT }}
          CI: true
        run: fastlane ios beta

Environment Variables

iOS (App Store Connect)

VariableDescription
APP_STORE_CONNECT_API_KEY_KEY_ID
API Key ID from App Store Connect
APP_STORE_CONNECT_API_KEY_ISSUER_ID
Issuer ID from App Store Connect
APP_STORE_CONNECT_API_KEY_KEY
Base64-encoded .p8 key content
MATCH_PASSWORD
Encryption password for Match
MATCH_GIT_URL
Git repository URL for certificates

Android (Google Play)

VariableDescription
SUPPLY_JSON_KEY_DATA
Google Play service account JSON (base64)
SUPPLY_JSON_KEY
Path to service account JSON file

Encoding API Keys

# Encode .p8 file to base64
base64 -i AuthKey_XXXXXXXXXX.p8 | tr -d '\n'

# Encode Google Play JSON
base64 -i play-store-key.json | tr -d '\n'

App Store Requirements

Metadata Character Limits

FieldLimit
App Name30 characters
Subtitle30 characters
Keywords100 characters
Description4000 characters
Release Notes4000 characters
Promotional Text170 characters

Screenshot Requirements (2024)

DeviceSizeRequired
iPhone 6.7"1290 x 2796Yes (primary)
iPhone 6.5"1284 x 2778Alternative
iPhone 5.5"1242 x 2208Optional
iPad Pro 12.9"2048 x 2732If iPad supported
iPad Pro 11"1668 x 2388Alternative

Known Issues Prevention

IssueRoot CauseSolution
"Multiple commands produce"Duplicate files in buildRemove duplicates from sources
Code signing fails in CINo keychain accessUse
create_keychain
+ Match
Build number rejectedDuplicate build numberUse timestamp:
Time.now.utc.strftime("%y%m%d%H%M")
Profile not foundWrong Match typeUse
appstore
for TestFlight/App Store
Invalid PEM formatWrong key encodingEnsure base64 with
is_key_content_base64: true
"Missing compliance"Encryption declarationSet
uses_non_exempt_encryption: false
Gradle build failsMissing SDK/NDKSet
ANDROID_HOME
and
ANDROID_NDK_HOME

Tauri Integration

Version from Cargo.toml

ROOT_DIR = File.expand_path("..", __dir__)

def get_app_version
  cargo_toml = File.read("#{ROOT_DIR}/src-tauri/Cargo.toml")
  if cargo_toml =~ /^version\s*=\s*"([^"]+)"/
    $1
  else
    "1.0.0"
  end
end

def get_next_build_number
  Time.now.utc.strftime("%y%m%d%H%M").to_i
end

Update tauri.conf.json

def update_tauri_config_version
  app_version = get_app_version
  build_number = get_next_build_number

  tauri_conf_path = "#{ROOT_DIR}/src-tauri/tauri.conf.json"
  tauri_conf = JSON.parse(File.read(tauri_conf_path))
  tauri_conf["version"] = app_version
  tauri_conf["bundle"] ||= {}
  tauri_conf["bundle"]["iOS"] ||= {}
  tauri_conf["bundle"]["iOS"]["bundleVersion"] = build_number.to_s

  File.write(tauri_conf_path, JSON.pretty_generate(tauri_conf))
end

Fix Tauri project.yml (Duplicate libapp.a)

def fix_tauri_project_yml
  project_yml_path = "#{ROOT_DIR}/src-tauri/gen/apple/project.yml"
  return unless File.exist?(project_yml_path)

  content = File.read(project_yml_path)

  # Remove "- path: Externals" to prevent duplicate libapp.a
  if content.include?("- path: Externals")
    content.gsub!(/^\s*- path: Externals\n/, "")
    File.write(project_yml_path, content)
    sh("cd #{ROOT_DIR}/src-tauri/gen/apple && xcodegen generate")
  end
end

Tauri iOS Build Lane

lane :beta do
  match(type: "appstore", readonly: true)

  update_tauri_config_version
  fix_tauri_project_yml

  # Configure signing
  update_code_signing_settings(
    use_automatic_signing: false,
    path: "#{ROOT_DIR}/src-tauri/gen/apple/MyApp.xcodeproj",
    team_id: TEAM_ID,
    bundle_identifier: APP_IDENTIFIER,
    profile_name: "match AppStore #{APP_IDENTIFIER}",
    code_sign_identity: "Apple Distribution"
  )

  # Build with Tauri
  sh("cd #{ROOT_DIR}/src-tauri && npx tauri ios build --export-method app-store-connect")

  upload_to_testflight(
    ipa: "#{ROOT_DIR}/src-tauri/gen/apple/build/arm64/MyApp.ipa",
    skip_waiting_for_build_processing: true
  )
end

Tauri Android Build Lane

platform :android do
  lane :beta do
    ENV['ANDROID_HOME'] = "/opt/homebrew/share/android-commandlinetools"
    ENV['ANDROID_NDK_HOME'] = "#{ENV['ANDROID_HOME']}/ndk/28.2.13676358"

    # Update version in tauri.conf.json
    app_version = get_app_version
    build_number = get_next_build_number

    tauri_conf_path = "#{ROOT_DIR}/src-tauri/tauri.conf.json"
    tauri_conf = JSON.parse(File.read(tauri_conf_path))
    tauri_conf["version"] = app_version
    tauri_conf["bundle"]["android"] ||= {}
    tauri_conf["bundle"]["android"]["versionCode"] = build_number
    File.write(tauri_conf_path, JSON.pretty_generate(tauri_conf))

    # Build with Tauri
    sh("cd #{ROOT_DIR}/src-tauri && npx tauri android build")

    upload_to_play_store(
      track: "beta",
      aab: "#{ROOT_DIR}/src-tauri/gen/android/app/build/outputs/bundle/release/app-release.aab"
    )
  end
end

Essential Commands

# Initialize Fastlane
fastlane init

# List available actions
fastlane actions

# Run a specific lane
fastlane ios beta
fastlane android release

# Debug with verbose output
fastlane ios beta --verbose

# Run Match commands
fastlane match appstore
fastlane match development
fastlane match nuke distribution  # Reset all distribution certs

Sources