Claude-skill-registry Mobile App Distribution and Deployment

Submitting, testing, and releasing mobile apps to app stores including App Store and Google Play submission, beta testing, OTA updates, and release management.

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/app-distribution" ~/.claude/skills/majiayu000-claude-skill-registry-mobile-app-distribution-and-deployment && rm -rf "$T"
manifest: skills/data/app-distribution/SKILL.md
safety · automated scan (medium risk)
This is a pattern-based risk scan, not a security review. Our crawler flagged:
  • eval/exec/Function constructor
  • references API keys
Always read a skill's source content before installing. Patterns alone don't mean the skill is malicious — but they warrant attention.
source content

Mobile App Distribution and Deployment

Current Level: Intermediate
Domain: Mobile Development / DevOps


Overview

App distribution covers the process of submitting, testing, and releasing mobile apps to app stores. This guide covers App Store and Google Play submission, beta testing, OTA updates, and release management for successful app launches and updates.


Core Concepts

Table of Contents

  1. App Store Submission
  2. App Metadata
  3. Screenshots and Previews
  4. App Store Optimization (ASO)
  5. Beta Testing
  6. OTA Updates
  7. Release Management
  8. Versioning Strategy
  9. Staged Rollout
  10. Best Practices

App Store Submission

iOS App Store Submission

// Fastlane configuration for iOS
// fastlane/Fastfile

platform :ios do
  desc "Submit to TestFlight"
  lane :testflight do
    increment_build_number

    match(
      type: "appstore",
      readonly: true
    )

    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      export_method: "app-store",
      export_options: {
        provisioningProfiles: {
          "com.example.myapp" => "match AppStore com.example.myapp"
        }
      }
    )

    upload_to_testflight(
      skip_waiting_for_build_processing: true,
      changelog: "Bug fixes and improvements"
    )
  end

  desc "Submit to App Store"
  lane :release do
    increment_build_number

    match(
      type: "appstore",
      readonly: true
    )

    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      export_method: "app-store",
      export_options: {
        provisioningProfiles: {
          "com.example.myapp" => "match AppStore com.example.myapp"
        }
      }
    )

    upload_to_app_store(
      submit_for_review: true,
      automatic_release: false,
      force: true,
      submission_information: {
        add_id_info_uses_idfa: false
      },
      app_rating_config_path: "./fastlane/metadata/app_rating_config.json"
    )
  end
end

Google Play Store Submission

# fastlane/Fastfile

platform :android do
  desc "Submit to Google Play Internal Testing"
  lane :internal do
    gradle(
      task: "assemble",
      build_type: "Release",
      project_dir: "android/"
    )

    upload_to_play_store(
      track: "internal",
      aab: "./android/app/build/outputs/bundle/release/app-release.aab",
      skip_upload_metadata: false,
      skip_upload_images: false,
      skip_upload_screenshots: false,
      skip_upload_changelogs: false
    )
  end

  desc "Submit to Google Play Production"
  lane :release do
    gradle(
      task: "bundle",
      build_type: "Release",
      project_dir: "android/"
    )

    upload_to_play_store(
      track: "production",
      aab: "./android/app/build/outputs/bundle/release/app-release.aab",
      skip_upload_metadata: false,
      skip_upload_images: false,
      skip_upload_screenshots: false,
      skip_upload_changelogs: false,
      release_status: "completed",
      rollout: "0.1" # 10% staged rollout
    )
  end
end

App Metadata

iOS App Metadata

// fastlane/metadata/en-US/whats_new.txt
- Bug fixes and performance improvements
- New feature: Dark mode support
- Improved user experience

// fastlane/metadata/en-US/description.txt
MyApp is the best app for managing your daily tasks. With intuitive design and powerful features, you can stay organized and productive.

Features:
- Create and manage tasks
- Set reminders and notifications
- Sync across devices
- Beautiful dark mode
- Customizable themes

// fastlane/metadata/en-US/keywords.txt
task,manager,productivity,organize,reminders

// fastlane/metadata/en-US/name.txt
MyApp

// fastlane/metadata/en-US/short_description.txt
The best task manager app

Android App Metadata

<!-- android/app/src/main/res/values/strings.xml -->
<resources>
  <string name="app_name">MyApp</string>
  <string name="app_description">The best task manager app for staying organized and productive.</string>
  <string name="app_short_desc">Manage your tasks efficiently</string>
</resources>

Screenshots and Previews

iOS Screenshots

# fastlane/Fastfile

desc "Generate screenshots"
lane :screenshots do
  capture_screenshots(
    scheme: "MyApp",
    devices: [
      "iPhone 14 Pro Max",
      "iPhone 14 Pro",
      "iPhone 14",
      "iPad Pro (12.9-inch) (6th generation)"
    ],
    languages: ["en-US", "es-ES", "fr-FR"],
    clear_previous_screenshots: true
  )

  frame_screenshots(
    white: true
  )
end

Android Screenshots

# fastlane/Fastfile

desc "Generate Android screenshots"
lane :android_screenshots do
  capture_android_screenshots(
    locales: ["en-US", "es-ES", "fr-FR"],
    clear_previous_screenshots: true
  )
end

App Store Optimization (ASO)

ASO Checklist

// ASO Configuration
interface ASOConfig {
  title: string;
  subtitle: string;
  keywords: string[];
  description: string;
  promotionalText: string;
  screenshots: string[];
  previewVideo?: string;
}

const asoConfig: ASOConfig = {
  title: "MyApp - Task Manager",
  subtitle: "Organize your life",
  keywords: [
    "task",
    "manager",
    "productivity",
    "organize",
    "reminders",
    "to-do",
    "planner",
    "schedule"
  ],
  description: `
    MyApp is the best app for managing your daily tasks. With intuitive design and powerful features, you can stay organized and productive.

    Features:
    - Create and manage tasks
    - Set reminders and notifications
    - Sync across devices
    - Beautiful dark mode
    - Customizable themes
    - Priority levels
    - Due dates
    - Categories and tags
    - Search and filter
    - Export and share

    Download MyApp today and take control of your tasks!
  `,
  promotionalText: "New: Dark mode support!",
  screenshots: [
    "screenshots/iphone-1.png",
    "screenshots/iphone-2.png",
    "screenshots/iphone-3.png",
    "screenshots/iphone-4.png",
    "screenshots/iphone-5.png",
    "screenshots/iphone-6.png",
  ],
  previewVideo: "videos/app-preview.mp4",
};

ASO Best Practices

// ASO Analyzer
class ASOAnalyzer {
  /**
   * Analyze ASO score
   */
  analyzeASO(config: ASOConfig): {
    score: number;
    suggestions: string[];
  } {
    const suggestions: string[] = [];
    let score = 0;

    // Title analysis
    if (config.title.length >= 10 && config.title.length <= 30) {
      score += 20;
    } else {
      suggestions.push("Title should be between 10-30 characters");
    }

    // Subtitle analysis
    if (config.subtitle.length >= 10 && config.subtitle.length <= 30) {
      score += 20;
    } else {
      suggestions.push("Subtitle should be between 10-30 characters");
    }

    // Keywords analysis
    if (config.keywords.length >= 5 && config.keywords.length <= 10) {
      score += 20;
    } else {
      suggestions.push("Use 5-10 relevant keywords");
    }

    // Description analysis
    if (config.description.length >= 100 && config.description.length <= 4000) {
      score += 20;
    } else {
      suggestions.push("Description should be between 100-4000 characters");
    }

    // Screenshots analysis
    if (config.screenshots.length >= 5 && config.screenshots.length <= 10) {
      score += 20;
    } else {
      suggestions.push("Include 5-10 screenshots");
    }

    return { score, suggestions };
  }

  /**
   * Generate keywords
   */
  generateKeywords(description: string): string[] {
    const words = description
      .toLowerCase()
      .replace(/[^\w\s]/g, '')
      .split(/\s+/);

    const stopWords = new Set([
      'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
      'of', 'with', 'by', 'from', 'up', 'about', 'into', 'through',
      'during', 'before', 'after', 'above', 'below', 'between', 'under',
      'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where',
      'why', 'how', 'all', 'each', 'few', 'more', 'most', 'other', 'some',
      'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than',
      'too', 'very', 'can', 'will', 'just', 'should', 'now'
    ]);

    const wordCount = new Map<string, number>();

    for (const word of words) {
      if (!stopWords.has(word) && word.length > 2) {
        wordCount.set(word, (wordCount.get(word) || 0) + 1);
      }
    }

    return Array.from(wordCount.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 10)
      .map(([word]) => word);
  }
}

Beta Testing

TestFlight Setup

# fastlane/Fastfile

desc "Add beta testers"
lane :add_testers do
  testflight(
    api_key_path: "./fastlane/api_key.json",
    skip_waiting_for_build_processing: true,
    changelog: "Bug fixes and improvements",
    groups: ["Internal Testers", "External Testers"],
    distribute_external: true,
    notify_external_testers: true
  )
end

desc "Add tester"
lane :add_tester do
  testflight_add_tester(
    email: "tester@example.com",
    first_name: "John",
    last_name: "Doe",
    groups: ["External Testers"]
  )
end

Google Play Internal Testing

# fastlane/Fastfile

desc "Add internal testers"
lane :add_internal_testers do
  upload_to_play_store(
    track: "internal",
    aab: "./android/app/build/outputs/bundle/release/app-release.aab",
    skip_upload_metadata: true,
    skip_upload_images: true,
    skip_upload_screenshots: true,
    skip_upload_changelogs: true
  )
end

desc "Add closed testers"
lane :add_closed_testers do
  upload_to_play_store(
    track: "beta",
    aab: "./android/app/build/outputs/bundle/release/app-release.aab",
    skip_upload_metadata: false,
    skip_upload_images: false,
    skip_upload_screenshots: false,
    skip_upload_changelogs: false
  )
end

OTA Updates

CodePush Integration

// npm install react-native-code-push

import CodePush from 'react-native-code-push';

// Wrap your root component
const App = () => {
  return (
    <YourApp />
  );
};

// Configure CodePush
const codePushOptions = {
  checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
  installMode: CodePush.InstallMode.IMMEDIATE,
  updateDialog: {
    appendReleaseDescription: true,
    descriptionPrefix: "\n\nChange log:\n",
    title: 'A new update is available!',
    mandatoryUpdateMessage: 'A mandatory update is available.',
    optionalIgnoreButtonLabel: 'Later',
    optionalInstallButtonLabel: 'Install',
    optionalUpdateMessage: 'An update is available. Would you like to install it?',
    mandatoryContinueButtonLabel: 'Continue',
  },
};

export default CodePush(codePushOptions)(App);

Expo Updates

// app.json
{
  "expo": {
    "updates": {
      "url": "https://u.expo.dev/YOUR_PROJECT_ID"
    },
    "runtimeVersion": {
      "policy": "appVersion"
    }
  }
}

// app.config.ts
import { ExpoConfig, ConfigContext } from '@expo/config';

export default ({ config }: ConfigContext): ExpoConfig => ({
  ...config,
  updates: {
    url: 'https://u.expo.dev/YOUR_PROJECT_ID',
  },
  runtimeVersion: {
    policy: 'appVersion',
  },
});

Release Management

Release Workflow

// Release Manager
class ReleaseManager {
  /**
   * Create release
   */
  async createRelease(params: {
    version: string;
    buildNumber: number;
    platform: 'ios' | 'android';
    environment: 'testflight' | 'beta' | 'production';
    changelog: string;
  }): Promise<void> {
    // 1. Increment version
    await this.incrementVersion(params.version, params.buildNumber);

    // 2. Build app
    await this.buildApp(params.platform, params.environment);

    // 3. Upload to store
    await this.uploadToStore(params.platform, params.environment, params.changelog);

    // 4. Create release notes
    await this.createReleaseNotes(params.version, params.changelog);
  }

  /**
   * Increment version
   */
  private async incrementVersion(
    version: string,
    buildNumber: number
  ): Promise<void> {
    // Update version in package.json
    const packageJson = await fs.readJson('package.json');
    packageJson.version = version;

    await fs.writeJson('package.json', packageJson, { spaces: 2 });

    // Update iOS version
    if (Platform.OS === 'ios') {
      const plistPath = 'ios/MyApp/Info.plist';
      const plist = await fs.readXml(plistPath);
      plist.plist.dict[0].string[1] = version; // CFBundleShortVersionString
      plist.plist.dict[0].integer[0] = buildNumber; // CFBundleVersion
      await fs.writeXml(plistPath, plist);
    }

    // Update Android version
    if (Platform.OS === 'android') {
      const gradlePath = 'android/app/build.gradle';
      let gradle = await fs.readFile(gradlePath, 'utf-8');

      gradle = gradle.replace(
        /versionName ".*"/,
        `versionName "${version}"`
      );
      gradle = gradle.replace(
        /versionCode \d+/,
        `versionCode ${buildNumber}`
      );

      await fs.writeFile(gradlePath, gradle);
    }
  }

  /**
   * Build app
   */
  private async buildApp(
    platform: 'ios' | 'android',
    environment: 'testflight' | 'beta' | 'production'
  ): Promise<void> {
    if (platform === 'ios') {
      await exec('fastlane ios build');
    } else {
      await exec('fastlane android build');
    }
  }

  /**
   * Upload to store
   */
  private async uploadToStore(
    platform: 'ios' | 'android',
    environment: 'testflight' | 'beta' | 'production',
    changelog: string
  ): Promise<void> {
    if (platform === 'ios') {
      if (environment === 'testflight') {
        await exec('fastlane ios testflight');
      } else {
        await exec('fastlane ios release');
      }
    } else {
      if (environment === 'beta') {
        await exec('fastlane android internal');
      } else {
        await exec('fastlane android release');
      }
    }
  }

  /**
   * Create release notes
   */
  private async createReleaseNotes(
    version: string,
    changelog: string
  ): Promise<void> {
    const releaseNotes = `
# Release ${version}

## Changes
${changelog}

## Date
${new Date().toISOString()}
    `;

    await fs.writeFile(`RELEASE_NOTES/${version}.md`, releaseNotes);
  }
}

Versioning Strategy

Semantic Versioning

// Version Manager
class VersionManager {
  /**
   * Bump version
   */
  bumpVersion(
    currentVersion: string,
    type: 'major' | 'minor' | 'patch'
  ): string {
    const [major, minor, patch] = currentVersion.split('.').map(Number);

    switch (type) {
      case 'major':
        return `${major + 1}.0.0`;
      case 'minor':
        return `${major}.${minor + 1}.0`;
      case 'patch':
        return `${major}.${minor}.${patch + 1}`;
      default:
        return currentVersion;
    }
  }

  /**
   * Get next version
   */
  getNextVersion(
    currentVersion: string,
    changes: {
      breaking: number;
      features: number;
      fixes: number;
    }
  ): string {
    if (changes.breaking > 0) {
      return this.bumpVersion(currentVersion, 'major');
    } else if (changes.features > 0) {
      return this.bumpVersion(currentVersion, 'minor');
    } else if (changes.fixes > 0) {
      return this.bumpVersion(currentVersion, 'patch');
    }

    return currentVersion;
  }

  /**
   * Compare versions
   */
  compareVersions(v1: string, v2: string): number {
    const [major1, minor1, patch1] = v1.split('.').map(Number);
    const [major2, minor2, patch2] = v2.split('.').map(Number);

    if (major1 !== major2) {
      return major1 - major2;
    } else if (minor1 !== minor2) {
      return minor1 - minor2;
    } else {
      return patch1 - patch2;
    }
  }
}

Staged Rollout

Staged Rollout Strategy

// Rollout Manager
class RolloutManager {
  /**
   * Create staged rollout
   */
  async createStagedRollout(params: {
    version: string;
    platform: 'ios' | 'android';
    initialPercentage: number;
    rolloutSchedule: Array<{
      percentage: number;
      delay: number; // in hours
    }>;
  }): Promise<void> {
    // 1. Initial rollout
    await this.rollout(params.platform, params.initialPercentage);

    // 2. Schedule subsequent rollouts
    for (const schedule of params.rolloutSchedule) {
      setTimeout(() => {
        this.rollout(params.platform, schedule.percentage);
      }, schedule.delay * 60 * 60 * 1000);
    }
  }

  /**
   * Rollout to percentage
   */
  async rollout(
    platform: 'ios' | 'android',
    percentage: number
  ): Promise<void> {
    if (platform === 'android') {
      await exec(`fastlane android rollout rollout:${percentage / 100}`);
    } else {
      // iOS doesn't support staged rollouts
      console.log('iOS staged rollout not supported');
    }
  }

  /**
   * Monitor rollout
   */
  async monitorRollout(params: {
    version: string;
    platform: 'ios' | 'android';
  }): Promise<{
    crashRate: number;
    userRating: number;
    activeUsers: number;
  }> {
    // Fetch metrics from analytics
    const metrics = await this.fetchMetrics(params);

    return metrics;
  }

  /**
   * Rollback if needed
   */
  async rollback(params: {
    version: string;
    platform: 'ios' | 'android';
  }): Promise<void> {
    if (platform === 'android') {
      await exec('fastlane android rollback');
    } else {
      // iOS rollback requires app rejection
      console.log('iOS rollback requires app rejection');
    }
  }

  /**
   * Fetch metrics
   */
  private async fetchMetrics(params: {
    version: string;
    platform: 'ios' | 'android';
  }): Promise<{
    crashRate: number;
    userRating: number;
    activeUsers: number;
  }> {
    // Implement metrics fetching
    return {
      crashRate: 0.01,
      userRating: 4.5,
      activeUsers: 1000,
    };
  }
}

Best Practices

App Distribution Checklist

// Pre-Release Checklist
const preReleaseChecklist = [
  {
    task: 'Test on all supported devices',
    category: 'Testing',
    required: true,
  },
  {
    task: 'Test on all supported OS versions',
    category: 'Testing',
    required: true,
  },
  {
    task: 'Run automated tests',
    category: 'Testing',
    required: true,
  },
  {
    task: 'Perform manual testing',
    category: 'Testing',
    required: true,
  },
  {
    task: 'Review app metadata',
    category: 'Metadata',
    required: true,
  },
  {
    task: 'Review screenshots',
    category: 'Metadata',
    required: true,
  },
  {
    task: 'Update changelog',
    category: 'Metadata',
    required: true,
  },
  {
    task: 'Check app size',
    category: 'Performance',
    required: false,
  },
  {
    task: 'Check battery usage',
    category: 'Performance',
    required: false,
  },
  {
    task: 'Check memory usage',
    category: 'Performance',
    required: false,
  },
  {
    task: 'Review privacy policy',
    category: 'Compliance',
    required: true,
  },
  {
    task: 'Check permissions',
    category: 'Compliance',
    required: true,
  },
  {
    task: 'Review terms of service',
    category: 'Compliance',
    required: true,
  },
  {
    task: 'Test with beta testers',
    category: 'Beta Testing',
    required: true,
  },
  {
    task: 'Review feedback',
    category: 'Beta Testing',
    required: false,
  },
];

// Release Checklist
const releaseChecklist = [
  {
    task: 'Increment version number',
    category: 'Versioning',
    required: true,
  },
  {
    task: 'Update build number',
    category: 'Versioning',
    required: true,
  },
  {
    task: 'Build production bundle',
    category: 'Build',
    required: true,
  },
  {
    task: 'Sign with production certificate',
    category: 'Build',
    required: true,
  },
  {
    task: 'Upload to app store',
    category: 'Distribution',
    required: true,
  },
  {
    task: 'Submit for review',
    category: 'Distribution',
    required: true,
  },
  {
    task: 'Monitor review status',
    category: 'Distribution',
    required: true,
  },
  {
    task: 'Monitor crash rates',
    category: 'Monitoring',
    required: true,
  },
  {
    task: 'Monitor user feedback',
    category: 'Monitoring',
    required: true,
  },
  {
    task: 'Prepare rollback plan',
    category: 'Monitoring',
    required: false,
  },
];

// Post-Release Checklist
const postReleaseChecklist = [
  {
    task: 'Monitor crash reports',
    category: 'Monitoring',
    required: true,
  },
  {
    task: 'Monitor user feedback',
    category: 'Monitoring',
    required: true,
  },
  {
    task: 'Monitor app ratings',
    category: 'Monitoring',
    required: true,
  },
  {
    task: 'Monitor download numbers',
    category: 'Monitoring',
    required: true,
  },
  {
    task: 'Review analytics',
    category: 'Analytics',
    required: true,
  },
  {
    task: 'Address critical issues',
    category: 'Support',
    required: true,
  },
  {
    task: 'Plan next release',
    category: 'Planning',
    required: false,
  },
];


Quick Start

App Store Submission

# Build iOS app
xcodebuild -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -configuration Release \
  -archivePath MyApp.xcarchive \
  archive

# Export IPA
xcodebuild -exportArchive \
  -archivePath MyApp.xcarchive \
  -exportPath ./build \
  -exportOptionsPlist ExportOptions.plist

Google Play Submission

# Build Android app bundle
./gradlew bundleRelease

# Upload to Play Console
# Use Google Play Console web interface or API

Production Checklist

  • App Store Setup: App Store Connect account configured
  • Google Play Setup: Google Play Console account configured
  • App Metadata: Complete app metadata (name, description, screenshots)
  • Screenshots: Screenshots for all device sizes
  • App Icon: App icon in all required sizes
  • Privacy Policy: Privacy policy URL
  • Versioning: Semantic versioning strategy
  • Beta Testing: Beta testing setup (TestFlight, Internal Testing)
  • OTA Updates: OTA update mechanism (for React Native)
  • Staged Rollout: Staged rollout strategy
  • Monitoring: Monitor app performance post-release
  • ASO: App Store Optimization

Anti-patterns

❌ Don't: No Beta Testing

# ❌ Bad - Direct to production
Build → Submit → Release
# No testing!
# ✅ Good - Beta testing first
Build → TestFlight/Internal Testing → Fix issues → Release

❌ Don't: Poor Metadata

# ❌ Bad - Incomplete metadata
Name: "My App"
Description: "App"
# No screenshots, no keywords
# ✅ Good - Complete metadata
Name: "My App - Task Manager"
Description: "Powerful task management app..."
Keywords: "task, todo, productivity"
Screenshots: All device sizes

Integration Points

  • Mobile CI/CD (
    31-mobile-development/mobile-ci-cd/
    ) - Automated builds
  • React Native Patterns (
    31-mobile-development/react-native-patterns/
    ) - App patterns
  • Versioning (
    01-foundations/versioning/
    ) - Version strategy

Further Reading