Cap-go

capacitor-ci-cd

2
0
# Install this skill:
npx skills add Cap-go/capacitor-skills --skill "capacitor-ci-cd"

Install specific skill from multi-skill repository

# Description

Complete CI/CD guide for Capacitor apps covering GitHub Actions, GitLab CI, build automation, app signing, and deployment pipelines. Use this skill when users need to automate their build and release process.

# SKILL.md


name: capacitor-ci-cd
description: Complete CI/CD guide for Capacitor apps covering GitHub Actions, GitLab CI, build automation, app signing, and deployment pipelines. Use this skill when users need to automate their build and release process.


CI/CD for Capacitor Applications

Automate building, testing, and deploying Capacitor apps.

When to Use This Skill

  • User wants to automate builds
  • User needs CI/CD pipeline
  • User asks about GitHub Actions
  • User needs app signing automation
  • User wants automated releases

GitHub Actions

Complete Workflow

# .github/workflows/build.yml
name: Build and Deploy

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: '20'

jobs:
  # Run tests and linting
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: oven-sh/setup-bun@v1

      - name: Install dependencies
        run: bun install

      - name: Lint
        run: bun run lint

      - name: Type check
        run: bun run typecheck

      - name: Unit tests
        run: bun test --coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v4

  # Security scan
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1
      - run: bunx capsec scan --ci

  # Build web assets
  build-web:
    runs-on: ubuntu-latest
    needs: [test, security]
    steps:
      - uses: actions/checkout@v4

      - uses: oven-sh/setup-bun@v1

      - name: Install dependencies
        run: bun install

      - name: Build
        run: bun run build

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: web-build
          path: dist/

  # Build iOS
  build-ios:
    runs-on: macos-latest
    needs: build-web
    steps:
      - uses: actions/checkout@v4

      - uses: oven-sh/setup-bun@v1

      - name: Download web build
        uses: actions/download-artifact@v4
        with:
          name: web-build
          path: dist/

      - name: Install dependencies
        run: bun install

      - name: Sync Capacitor
        run: bunx cap sync ios

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true
          working-directory: ios/App

      - name: Install CocoaPods
        run: cd ios/App && pod install

      - name: Import certificates
        env:
          CERTIFICATE_P12: ${{ secrets.CERTIFICATE_P12 }}
          CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}
          PROVISIONING_PROFILE: ${{ secrets.PROVISIONING_PROFILE }}
        run: |
          # Create keychain
          security create-keychain -p "" build.keychain
          security default-keychain -s build.keychain
          security unlock-keychain -p "" build.keychain
          security set-keychain-settings -t 3600 -u build.keychain

          # Import certificate
          echo "$CERTIFICATE_P12" | base64 --decode > certificate.p12
          security import certificate.p12 -k build.keychain -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain

          # Install provisioning profile
          mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
          echo "$PROVISIONING_PROFILE" | base64 --decode > ~/Library/MobileDevice/Provisioning\ Profiles/profile.mobileprovision

      - name: Build iOS
        run: |
          cd ios/App
          xcodebuild -workspace App.xcworkspace \
            -scheme App \
            -configuration Release \
            -archivePath build/App.xcarchive \
            archive

      - name: Export IPA
        run: |
          cd ios/App
          xcodebuild -exportArchive \
            -archivePath build/App.xcarchive \
            -exportPath build/ \
            -exportOptionsPlist ExportOptions.plist

      - name: Upload IPA
        uses: actions/upload-artifact@v4
        with:
          name: ios-build
          path: ios/App/build/*.ipa

  # Build Android
  build-android:
    runs-on: ubuntu-latest
    needs: build-web
    steps:
      - uses: actions/checkout@v4

      - uses: oven-sh/setup-bun@v1

      - name: Download web build
        uses: actions/download-artifact@v4
        with:
          name: web-build
          path: dist/

      - name: Setup Java
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Setup Android SDK
        uses: android-actions/setup-android@v3

      - name: Install dependencies
        run: bun install

      - name: Sync Capacitor
        run: bunx cap sync android

      - name: Decode keystore
        env:
          KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
        run: |
          echo "$KEYSTORE_BASE64" | base64 --decode > android/app/release.keystore

      - name: Build APK
        env:
          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
        run: |
          cd android
          ./gradlew assembleRelease \
            -Pandroid.injected.signing.store.file=release.keystore \
            -Pandroid.injected.signing.store.password=$KEYSTORE_PASSWORD \
            -Pandroid.injected.signing.key.alias=$KEY_ALIAS \
            -Pandroid.injected.signing.key.password=$KEY_PASSWORD

      - name: Build AAB
        env:
          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
        run: |
          cd android
          ./gradlew bundleRelease \
            -Pandroid.injected.signing.store.file=release.keystore \
            -Pandroid.injected.signing.store.password=$KEYSTORE_PASSWORD \
            -Pandroid.injected.signing.key.alias=$KEY_ALIAS \
            -Pandroid.injected.signing.key.password=$KEY_PASSWORD

      - name: Upload APK
        uses: actions/upload-artifact@v4
        with:
          name: android-apk
          path: android/app/build/outputs/apk/release/*.apk

      - name: Upload AAB
        uses: actions/upload-artifact@v4
        with:
          name: android-aab
          path: android/app/build/outputs/bundle/release/*.aab

  # Deploy to Capgo
  deploy-capgo:
    runs-on: ubuntu-latest
    needs: build-web
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - uses: oven-sh/setup-bun@v1

      - name: Download web build
        uses: actions/download-artifact@v4
        with:
          name: web-build
          path: dist/

      - name: Deploy to Capgo
        run: bunx @capgo/cli upload
        env:
          CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}

  # Deploy to App Store
  deploy-ios:
    runs-on: macos-latest
    needs: build-ios
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - name: Download IPA
        uses: actions/download-artifact@v4
        with:
          name: ios-build
          path: build/

      - name: Upload to App Store Connect
        env:
          APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
        run: |
          xcrun altool --upload-app \
            --type ios \
            --file build/*.ipa \
            --apiKey ${{ secrets.API_KEY_ID }} \
            --apiIssuer ${{ secrets.API_ISSUER_ID }}

  # Deploy to Play Store
  deploy-android:
    runs-on: ubuntu-latest
    needs: build-android
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - name: Download AAB
        uses: actions/download-artifact@v4
        with:
          name: android-aab
          path: build/

      - name: Upload to Play Store
        uses: r0adkll/upload-google-play@v1
        with:
          serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT }}
          packageName: com.yourapp.id
          releaseFiles: build/*.aab
          track: internal

Fastlane Integration

# .github/workflows/fastlane.yml
name: Fastlane Build

on:
  push:
    tags: ['v*']

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

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true

      - name: Install Fastlane
        run: gem install fastlane

      - name: Build and Deploy
        run: fastlane ios release
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}

  android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true

      - name: Install Fastlane
        run: gem install fastlane

      - name: Build and Deploy
        run: fastlane android release
        env:
          PLAY_STORE_JSON_KEY: ${{ secrets.PLAY_SERVICE_ACCOUNT }}

Fastlane Setup

iOS Fastfile

# ios/App/fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "Build and deploy to TestFlight"
  lane :release do
    setup_ci

    # Match for code signing
    match(
      type: "appstore",
      readonly: true
    )

    # Increment build number
    increment_build_number(
      build_number: ENV["GITHUB_RUN_NUMBER"]
    )

    # Build
    build_app(
      workspace: "App.xcworkspace",
      scheme: "App",
      export_method: "app-store"
    )

    # Upload to TestFlight
    upload_to_testflight(
      skip_waiting_for_build_processing: true
    )
  end

  desc "Build for development"
  lane :build do
    match(type: "development", readonly: true)

    build_app(
      workspace: "App.xcworkspace",
      scheme: "App",
      export_method: "development"
    )
  end
end

Android Fastfile

# android/fastlane/Fastfile
default_platform(:android)

platform :android do
  desc "Build and deploy to Play Store"
  lane :release do
    # Increment version code
    increment_version_code(
      version_code: ENV["GITHUB_RUN_NUMBER"].to_i
    )

    # Build AAB
    gradle(
      task: "bundle",
      build_type: "Release"
    )

    # Upload to Play Store
    upload_to_play_store(
      track: "internal",
      aab: lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH]
    )
  end

  desc "Build APK"
  lane :build do
    gradle(
      task: "assemble",
      build_type: "Release"
    )
  end
end

GitLab CI

# .gitlab-ci.yml
stages:
  - test
  - build
  - deploy

variables:
  BUN_VERSION: "1.0"

.bun-cache: &bun-cache
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
      - ~/.bun/install/cache

test:
  stage: test
  image: oven/bun:${BUN_VERSION}
  <<: *bun-cache
  script:
    - bun install
    - bun run lint
    - bun test --coverage
  coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

security:
  stage: test
  image: oven/bun:${BUN_VERSION}
  script:
    - bunx capsec scan --ci --output json --output-file security.json
  artifacts:
    reports:
      security: security.json

build-web:
  stage: build
  image: oven/bun:${BUN_VERSION}
  <<: *bun-cache
  script:
    - bun install
    - bun run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 day

build-ios:
  stage: build
  tags:
    - macos
  needs: [build-web]
  script:
    - bun install
    - bunx cap sync ios
    - cd ios/App && fastlane build
  artifacts:
    paths:
      - ios/App/build/*.ipa
  only:
    - main
    - tags

build-android:
  stage: build
  image: thyrlian/android-sdk
  needs: [build-web]
  script:
    - bun install
    - bunx cap sync android
    - cd android && ./gradlew assembleRelease
  artifacts:
    paths:
      - android/app/build/outputs/apk/release/*.apk
  only:
    - main
    - tags

deploy-capgo:
  stage: deploy
  image: oven/bun:${BUN_VERSION}
  needs: [build-web]
  script:
    - bunx @capgo/cli upload --channel production
  only:
    - main
  environment:
    name: production

Secrets Management

Required Secrets

Secret Description
CERTIFICATE_P12 iOS distribution certificate (base64)
CERTIFICATE_PASSWORD Certificate password
PROVISIONING_PROFILE iOS provisioning profile (base64)
KEYSTORE_BASE64 Android keystore (base64)
KEYSTORE_PASSWORD Keystore password
KEY_ALIAS Signing key alias
KEY_PASSWORD Signing key password
CAPGO_TOKEN Capgo API token
APP_STORE_CONNECT_API_KEY App Store Connect API key
PLAY_SERVICE_ACCOUNT Play Store service account JSON

Encoding Secrets

# iOS certificate
base64 -i certificate.p12 | pbcopy

# iOS provisioning profile
base64 -i profile.mobileprovision | pbcopy

# Android keystore
base64 -i release.keystore | pbcopy

Version Management

Semantic Release

bun add -D semantic-release @semantic-release/git @semantic-release/changelog
// .releaserc.json
{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    [
      "@semantic-release/npm",
      { "npmPublish": false }
    ],
    [
      "@semantic-release/git",
      {
        "assets": ["package.json", "CHANGELOG.md"],
        "message": "chore(release): ${nextRelease.version}"
      }
    ],
    "@semantic-release/github"
  ]
}

Version Bumping

# .github/workflows/release.yml
name: Release

on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          persist-credentials: false

      - uses: oven-sh/setup-bun@v1

      - run: bun install

      - name: Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: bunx semantic-release

Build Caching

Gradle Cache

- name: Cache Gradle
  uses: actions/cache@v4
  with:
    path: |
      ~/.gradle/caches
      ~/.gradle/wrapper
    key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
    restore-keys: gradle-${{ runner.os }}-

CocoaPods Cache

- name: Cache CocoaPods
  uses: actions/cache@v4
  with:
    path: ios/App/Pods
    key: pods-${{ runner.os }}-${{ hashFiles('ios/App/Podfile.lock') }}
    restore-keys: pods-${{ runner.os }}-

Resources

  • GitHub Actions: https://docs.github.com/actions
  • Fastlane: https://fastlane.tools
  • Capgo CLI: https://capgo.app/docs/cli
  • App Store Connect API: https://developer.apple.com/documentation/appstoreconnectapi
  • Google Play API: https://developers.google.com/android-publisher

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.