跳过主要内容
CI/CD

Automatic Capacitor IOS build with GitHub actions using match

如何在 5 分钟内使用 fastlane 和 GitHub Actions 为您的 IOS Ionic 应用程序设置 CI/CD pipeline (2022)

马丁·多纳迪厄

马丁·多纳迪厄

内容营销

使用匹配的自动 Capacitor IOS 构建和 GitHub 动作

使用匹配的自动 iOS 构建和 GitHub 动作

为 Capacitor 应用设置 CI/CD 可能会很复杂和耗时。以下是您需要了解的内容:

我们现在推荐使用 使用 Capgo 构建和 Capgo CLI 进行本地 Capacitor 构建。 本指南是为维护现有 GitHub 动作管道的团队而准备的,但新 iOS 构建应使用 Capgo CLI 以免您需要维护 Fastlane、Match 仓库、Xcode 运行器、证书和上传脚本。

Capgo 构建由 Capgo 提供的 CI/CD

跳过Fastlane Match、Xcode runner、证书、分发配置文件和上传脚本的维护。 Capgo Build 从您的现有CI/CD管道中运行签名的原生iOS构建:

  • 与您的管道兼容: 在您的web构建之后从GitHub Actions、GitLab CI、Jenkins或本地脚本中触发Capgo Build npx cap sync.
  • 从CI密钥中签名: 在您的CI密钥中保留App Store Connect密钥、证书、分发配置文件、密码和团队ID。
  • 无原生运行器维护: Capgo Build提供维护的Apple构建环境,因此您不需要管理macOS运行器、Xcode图像、Fastlane或Match仓库。
  • 工件和提交: 下载用于QA的签名工件或通过Capgo CLI提交发布构建。

定价

  • Capgo 计划从每月 12 美元开始
  • 包括 OTA 更新和每月约 15 个本机构建
  • 额外的构建分钟通过信用额度按分钟计费

在 CI/CD 中设置 Capgo 构建

手动设置指南

以下是您需要做的事情:

使用 Fastlane 和 GitHub 动作进行 iOS 的持续交付

前提条件

继续教程之前,请确保您有 Fastlane

  • 已在开发机器上安装 已安装 __CAPGO_KEEP_0__
  • iOS 开发者计划会员资格.
  • 想看😆…
  • 如果不是团队中有很多开发者,我们建议使用 fastlane cert 来简化工作流程.

关于价格的重要提示.

价格GitHub Action

https://github.com/features/actions

服务在限制内是免费的,具体取决于所选机器.
我们将使用一个 macOS 机器,截图中显示了其价格和限制(价格截至教程创建,可能会在未来发生变化)

🔴 一旦警告了需求和价格,如果您喜欢,我们继续…

📣 在文章中,我们假设我们已经在 iTunes Connect 中创建了应用,我们有 Apple 生态系统的证书,Fastlane 将复制所有内容!

让我们深入!

文章中的步骤

  1. 使用 App Store Connect API 与 Fastlane Match
  2. 需求
  3. 创建 App Store Connect API 密钥
  4. 使用 App Store Connect API 密钥
  5. 复制 Fastlane 文件
  6. 配置 Fastlane match

1. 使用 App Store Connect API 与 Fastlane Match

从 2021 年 2 月开始,所有用户都必须使用两步验证或两步验证登录 App Store Connect。这种额外的安全层对于您的 Apple ID 有助于确保您是唯一可以访问帐户的人。
Apple Support

开始使用 match 需要撤销您的现有证书。但是,不用担心,您将直接获得新的证书。

要求

要使用 App Store Connect API, Fastlane 需要 三个 事项。

  1. 证书 ID。
  2. 密钥 ID。
  3. 密钥文件或密钥内容。

创建 App Store Connect API 密钥

要生成密钥,您必须在 App Store Connect 中具有管理员权限。如果您没有该权限,请将相关人员指向此文章并遵循以下说明。

1 — 使用 App Store Connect 登录 App Store Connect.

2 — 选择 Users and Access.

App Store Connect 用户访问

3 — 选择 Integration 选项卡。

App Store Connect API Integration

4 — 点击生成 API 密钥或添加 (+) 按钮。

App Store Connect API 密钥创建

5 — 为密钥输入一个名称。该名称仅供您参考,不是密钥的一部分。

App Store Connect API 密钥创建名称

6 — 在访问权限下,选择密钥的角色。密钥的角色与您的团队成员的角色相同。请参阅 角色权限. 我们建议您选择 应用程序管理器.

7 — 点击生成。

一个 API 密钥的访问权限不能被限制到特定的应用程序。

新密钥的名称、密钥 ID、下载链接和其他信息出现在页面上。

App Store Connect 下载密钥

您可以在这里获取所有三个必要的信息。
<1> 问题 ID。
<2> 密钥 ID。
<3> 点击“下载 API 密钥”以下载您的 API 私有密钥。下载链接仅在私有密钥尚未下载时才会出现。苹果公司不会保留私有密钥的副本。因此,您只能下载一次。

🔴 请将您的私有密钥存储在安全的地方。您不应共享您的密钥、将密钥存储在 code 仓库中或将密钥包含在客户端 code 中。

使用 App Store Connect API Key

为了创建用于授权的 JWT 令牌,需要 App Store Connect API Key 文件(下载的 p8 文件)、密钥 ID 和发行者 ID。这些信息可以通过多种方式输入到 Fastlane 中,使用 Fastlane 的新动作 app_store_connect_api_key. 您可以在 Fastlane 文档中学习其他方法。 我展示这个方法,因为我认为它是与大多数 CI 工作最方便的方式,通过设置环境变量

现在我们可以使用 App Store Connect API key 来管理 Fastlane,太棒了!

2. 复制 Fastlane 文件

Fastlane 是一款 Ruby 库,用于自动化移动开发的常见任务。使用 Fastlane,可以配置自定义的“道线”,这些道线包含一系列的“动作”,这些动作执行您通常使用 Android Studio 执行的任务。您可以在 Fastlane 中做很多事情,但在本教程中,我们将仅使用核心动作的一小部分。

在项目根目录创建一个 Fastlane 文件夹,并复制以下文件: Fastfile

default_platform(:ios)

DEVELOPER_APP_IDENTIFIER = ENV["DEVELOPER_APP_IDENTIFIER"]
DEVELOPER_APP_ID = ENV["DEVELOPER_APP_ID"]
PROVISIONING_PROFILE_SPECIFIER = ENV["PROVISIONING_PROFILE_SPECIFIER"]
TEMP_KEYCHAIN_USER = ENV["TEMP_KEYCHAIN_USER"]
TEMP_KEYCHAIN_PASSWORD = ENV["TEMP_KEYCHAIN_PASSWORD"]
APPLE_ISSUER_ID = ENV["APPLE_ISSUER_ID"]
APPLE_KEY_ID = ENV["APPLE_KEY_ID"]
APPLE_KEY_CONTENT = ENV["APPLE_KEY_CONTENT"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]

def delete_temp_keychain(name)
  delete_keychain(
    name: name
  ) if File.exist? File.expand_path("~/Library/Keychains/#{name}-db")
end

def create_temp_keychain(name, password)
  create_keychain(
    name: name,
    password: password,
    unlock: false,
    timeout: 0
  )
end

def ensure_temp_keychain(name, password)
  delete_temp_keychain(name)
  create_temp_keychain(name, password)
end

platform :ios do
  lane :build do
    build_app(
      configuration: "Release",
      workspace: "./ios/App/App.xcworkspace",
      scheme: "App",
      export_method: "app-store",
      export_options: {
        provisioningProfiles: { 
            DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
        }
      }
    )
  end
  lane :refresh_profiles do
    match(
      type: "development",
      force: true)
    match(
      type: "adhoc",
      force: true)
  end
  desc "Register new device"
  lane :register_new_device do  |options|
      device_name = prompt(text: "Enter the device name: ")
      device_udid = prompt(text: "Enter the device UDID: ")
      device_hash = {}
      device_hash[device_name] = device_udid
      register_devices(
                       devices: device_hash
                       )
    refresh_profiles
  end
  lane :closed_beta do
    keychain_name = TEMP_KEYCHAIN_USER
    keychain_password = TEMP_KEYCHAIN_PASSWORD
    ensure_temp_keychain(keychain_name, keychain_password)

    api_key = app_store_connect_api_key(
      key_id: APPLE_KEY_ID,
      issuer_id: APPLE_ISSUER_ID,
      key_content: APPLE_KEY_CONTENT,            
      duration: 1200,            
      in_house: false
    )

    match(
      type: 'appstore',
      git_basic_authorization: Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"),
      readonly: true,
      keychain_name: keychain_name,
      keychain_password: keychain_password,
      api_key: api_key
    )

    gym(
      configuration: "Release",
      workspace: "./ios/App/App.xcworkspace",
      scheme: "App",
      export_method: "app-store",
      export_options: {
        provisioningProfiles: { 
            DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
        }
      }
    )

    pilot(
      apple_id: "#{DEVELOPER_APP_ID}",
      app_identifier: "#{DEVELOPER_APP_IDENTIFIER}",
      skip_waiting_for_build_processing: true,
      skip_submission: true,
      distribute_external: false,
      notify_external_testers: false,
      ipa: "./App.ipa"
    )

    delete_temp_keychain(keychain_name)
  end
  lane :submit_review do
    version = ''
    Dir.chdir("..") do
      file = File.read("package.json")
      data = JSON.parse(file)
      version = data["version"]
    end
    deliver(
      app_version: version,
      submit_for_review: true,
      automatic_release: true,
      force: true, # Skip HTMl report verification
      skip_metadata: false,
      skip_screenshots: false,
      skip_binary_upload: true
    )
  end
end

Appfile

app_identifier(ENV["DEVELOPER_APP_IDENTIFIER"])
apple_id(ENV["FASTLANE_APPLE_ID"])
itc_team_id(ENV["APP_STORE_CONNECT_TEAM_ID"])
team_id(ENV["DEVELOPER_PORTAL_TEAM_ID"])

配置 Fastlane match

Fastlane 匹配 is a new approach to iOS’s code signing. Fastlane match makes it easy for teams to manage the required certificates and provisioning profiles for your iOS apps.

创建一个私有的仓库名 certificates,例如在你的GitHub个人账户或组织中。

初始化 Fastlane match 以便于你的 iOS 应用。

fastlane match init

然后选择选项 #1 (Git 存储)。

[01:00:00]: fastlane match supports multiple storage modes, please select the one you want to use:1. git2. google_cloud3. s3?

分配新创建仓库的 URL。

[01:00:00]: Please create a new, private git repository to store the certificates and profiles there[01:00:00]: URL of the Git Repo: <YOUR_CERTIFICATES_REPO_URL>

现在你在 Fastlane 文件夹中有一个名为 的文件, 应该设置为证书仓库的 HTTPS URL。可选地,也可以使用 SSH,但这需要一个不同的步骤来运行。 _git_url_接下来,我们去生成证书并在被询问时输入你的凭证使用 Fastlane Match。

# ios/Matchfilegit_url("https://github.com/gitusername/certificates")storage_mode("git")type("appstore")

Fastlane match 是 iOS 的一种新签名方法。

您将被要求输入一个密语。请正确记住它,因为它将在后续的GitHub Actions中用来解密您的证书仓库。

fastlane match appstore

如果一切顺利,您应该看到类似这样的内容:

[01:40:52]: All required keys, certificates and provisioning profiles are installed 🙌

如果您在GitHub和必要的权限方面遇到任何问题,可能这个 帖子 将有助于您为git生成认证令牌。

生成的证书和配置文件将上传到证书仓库资源

App Store Connect 证书

最后,打开您的 project 在Xcode中,更新您的应用程序的发布配置的分发配置文件。

XCode 证书

以下是一些需要注意的事项 💡

MATCH

为了让CI/CD导入证书和配置文件,它需要访问证书仓库。您可以通过生成一个个人访问令牌(应在此之前使用),该令牌具有访问或读取私有仓库的权限来实现。

在GitHub中,前往 设置开发者设置个人访问令牌 → 点击 Generate New Token → 勾选 repo 权限范围 → 然后点击 Generate token.

创建个人访问令牌

保留生成的个人访问令牌副本。您稍后将使用它来设置环境变量 GIT_TOKEN.

然后用Matchfile替换Fastlane文件夹中生成的匹配文件 Matchfile

CERTIFICATE_STORE_URL = ENV["CERTIFICATE_STORE_URL"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]
FASTLANE_APPLE_ID = ENV["FASTLANE_APPLE_ID"]

git_url(CERTIFICATE_STORE_URL)
storage_mode("git")
type("appstore")
git_basic_authorization(Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"))
username(FASTLANE_APPLE_ID)

此文件将由GitHub Actions导入证书和配置文件。 并且变量将在GitHub Secrets中设置,而不是在文件中硬编码。

Build Processing

在 GitHub 动作中, 您根据运行 CI/CD 工作流的分钟数而被收费。从经验来看,需要约 10–15 分钟才能在 App Store Connect 中处理一个构建。 对于私有项目,估计每个构建的成本最高可达

$0.08/分钟 x 15 分钟 = $1.2 ,或更多,具体取决于您的项目的配置或依赖项。

如果您对私有项目的定价有同样的担忧,就像我一样,您可以保留 skip_waiting_for_build_processingtrue.

什么是陷阱呢?您必须手动更新 App Store Connect 中的应用程序符合性,以便将构建分发给您的用户。

这只是一个可选参数,如果您想为私有项目节省构建分钟数,则可以更新。如果您是免费项目,则完全不用担心。见 定价.

3. 配置 GitHub 动作

配置 GitHub 秘密

你曾经想知道这些值是从哪里来的?答案不再是秘密了 – 它们来自你的项目的秘密。 🤦 ENV 设置 __CAPGO_KEEP_0__ 秘密

Set GitHub secrets

1. APP_STORE_CONNECT_TEAM_ID - 在 App Store Connect 中,进入应用程序 →

2. DEVELOPER_APP_ID 应用程序信息 → 滚动到底部到应用程序的 部分并寻找 General Information - 应用程序的包标识符。 Apple ID.

3. DEVELOPER_APP_IDENTIFIER - 如果你属于多个团队,需要开发者门户团队 ID。

4. DEVELOPER_PORTAL_TEAM_ID 设置 __CAPGO_KEEP_0__ 秘密

5. FASTLANE_APPLE_ID - 使用管理应用程序的 Apple ID 或开发者电子邮件。

6. GIT_USERNAME &amp; GIT_TOKEN - 你的 Git 用户名和个人访问令牌。

7. MATCH_PASSWORD - 初始化匹配时分配的密钥串,将用于解密证书和分发配置文件。

8. PROVISIONING_PROFILE_SPECIFIER - match AppStore <YOUR_APP_BUNDLE_IDENTIFIER>, eg. match AppStore com.domain.blabla.demo.

9. TEMP_KEYCHAIN_USER &amp; TEMP_KEYCHAIN_PASSWORD - 为你的工作流分配一个临时密钥链用户和密码。

10. APPLE_KEY_ID — App Store Connect API Key 🔺Key ID。

11. APPLE_ISSUER_ID — App Store Connect API Key 🔺Issuer ID。

12. APPLE_KEY_CONTENT — App Store Connect API Key 🔺 Key 文件或 Key 内容 (.p8)。 check it, — App Store Connect __CAPGO_KEEP_0__ Key 🔺

13. CERTIFICATE_STORE_URL — 你的 Match keys 的仓库 URL(例如: https://github.com/***/fastlane_match.git)

4. 配置 GitHub 工作流文件

创建一个 GitHub 工作流目录。

cd .github/workflows

在该目录中,创建一个名为 workflow 的文件并添加以下内容。 build-upload-ios.yml此工作流应在每次 __CAPGO_KEEP_0__

name: Build source code on ios

on:
  push:
    tags:
      - '*'

jobs:
  build_ios:
    runs-on: macOS-latest
    steps:
      - uses: actions/checkout@v6
      - name: set Node.js
        uses: actions/setup-node@v6
        with:
          node-version: '24'
          cache: npm
      - name: Install dependencies
        id: install_code
        run: npm ci
      - name: Build
        id: build_code
        run: npm run build
      - uses: actions/cache@v5
        with:
          path: ios/App/Pods
          key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-pods-
      - name: Sync
        id: sync_code
        run: npx cap sync
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.7.2
      - uses: maierj/fastlane-action@v2.3.0
        env:
          DEVELOPER_APP_IDENTIFIER: ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
          DEVELOPER_APP_ID: ${{ secrets.DEVELOPER_APP_ID }}
          PROVISIONING_PROFILE_SPECIFIER: match AppStore ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
          TEMP_KEYCHAIN_USER: ${{ secrets.TEMP_KEYCHAIN_USER }}
          TEMP_KEYCHAIN_PASSWORD: ${{ secrets.TEMP_KEYCHAIN_PASSWORD }}
          APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
          APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
          APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
          CERTIFICATE_STORE_URL: https://github.com/${{ secrets.CERTIFICATE_STORE_REPO }}.git
          GIT_USERNAME: ${{ secrets.GIT_USERNAME }}
          GIT_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
          FASTLANE_APPLE_ID: ${{ secrets.FASTLANE_APPLE_ID }}
          MATCH_USERNAME: ${{ secrets.FASTLANE_APPLE_ID }}
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
          DEVELOPER_PORTAL_TEAM_ID: ${{ secrets.DEVELOPER_PORTAL_TEAM_ID }}
        with:
          lane: closed_beta
      - name: Upload release bundle
        uses: actions/upload-artifact@v2
        with:
          name: ios-release
          path: ./App.ipa
          retention-days: 60

This workflow should be triggered after each GitHub 使用 __CAPGO_KEEP_0__ 动作自动构建和发布步骤一: Automatic build and release with GitHub actions first.

然后这个工作流程将拉取您的 NodeJS 依赖项,安装它们并构建您的 JavaScript 应用程序。

每次您发送新的提交时,测试飞行中的发布将被构建。

您的 App 不需要使用 Ionic,只需 Capacitor 基础即可,它可以使用旧的 Cordova 模块,但 Capacitor JS 插件应优先使用。

5. 触发工作流程

创建提交

制作 提交您应该在仓库中看到正在运行的工作流程。

触发工作流程

将新提交推送到分支 maindevelopment 触发工作流程以触发工作流程。

开始了一个提交

几分钟后,构建应该在您的 App Store Connect 控制台中可用。

Testflight 控制台

是否可以从本地机器部署?

是的,您可以,而且这很容易。

假设您有一个私有仓库,您已经用完了免费计划的分钟数,并且您不想为新发布支付费用,或者您更喜欢手动提交应用程序。

让我们开始吧

好的,我们需要在 my_project_path/fastlane 路径创建一个名为 .env, 仅在同一路径下 快速文件 为了能够创建相同的 机密 在我们的_GitHub中找到以下属性:

.env文件用于从本地机器部署

现在,您可以前往 终端 并从您的机器上启动 Fastlane ❌ 关键信息关于

fastlane closed_beta

.env .env 为了避免暴露数据,我们必须在我们的文件中添加它 .gitignore

fastlane/*.env

它应该与在远程机器上的GitHub Actions发生的事件一样工作,但是在我们的本地机器上

本地Fastlane运行

终端执行:$ Fastlane closed_beta

如果您已经走到这一步,我恭喜您,现在您已经拥有了使用Fastlane和GitHub Actions来自动化您的iOS应用的完全过程

每次您发送新的提交,Google Play商店中会构建一个beta版本 martin@capgo.app

martin@__CAPGO_KEEP_0__.app

在您的设备上构建 如果您仍然需要在您的设备上构建,需要手动将它们添加到配置文件中
将您的设备连接到您的Mac,然后打开设备菜单
找到设备iOS菜单 然后复制您的标识符 查找标识符 ios 然后启动命令: fastlane register_new_device 它会要求您设置设备名称和标识符: 设置标识符 ios

如果您遇到问题

如果您遇到开发设备无法测试等问题,通常可以通过以下方式解决:

有一个神奇的命令可以帮助您:

fastlane match nuke development
fastlane match development

然后: 清理项目,按住 Shift(⇧)+ Command(⌘)+ K 或选择 Product > Clean(它可能标记为“清理构建文件夹”)

然后再次尝试在设备上运行应用。

感谢

本博客基于以下文章:

从自动 Capacitor IOS 构建中继续前进,使用 GitHub 动作和 match

如果您正在使用 自动 Capacitor IOS 构建,使用 GitHub 动作和 match 来规划 CI/CD 自动化,连接它 Capgo CI/CD 在 Capgo CI/CD 中, Capgo 原生构建 在 Capgo 原生构建中, Capgo 集成 为产品工作流程中的Capgo Integrations CI/CD集成 为CI/CD集成的实施细节 GitHub Actions集成 为GitHub Actions集成的实施细节

实时更新Capacitor应用

当web层bug出现时,通过Capgo直接发布修复,而不是等待几天的app store审批。用户在后台接收更新,而原生代码仍然在正常审批路径中。

立即开始

最新博客

Capgo为您提供创建真正专业的移动应用所需的最佳见解。