elevenode

如果你将 React Native / Expo 应用发布到 App Store,你一定熟悉这个流程。打开 Apple Developer 门户,创建 bundle identifier,勾选功能复选框,生成配置文件,选择正确的证书。然后跳转到 Expo 仪表板,创建 EAS 应用,连接凭证,一次一个屏幕地添加环境变量。

它可以工作,直到你需要为第二个应用或第二个环境重复操作,或者团队成员需要了解为什么某个功能被启用。所有这些都没有记录下来。它会漂移。而通常的移动工具在这里帮助不大:fastlane 和 EAS CLI 很好,但它们是命令式的——执行操作的脚本——而不是对发布设置应该是什么的声明性描述。

这两个提供程序填补了这个空白:

  • elevenode/appstore — App Store Connect:bundle identifiers、配置文件、证书。
  • elevenode/expo — Expo Application Services (EAS):应用、凭证、环境变量、更新通道。

两者都是开源的(Apache 2.0)并在 Terraform Registry 上发布。让我们一起使用它们,将移动应用的发布设置描述为代码。

你需要什么

  • Terraform(或 OpenTofu)
  • 一个 App Store Connect API 密钥(用户和访问 → 集成 → App Store Connect API):密钥、密钥 ID 和发行者 ID
  • 一个 Expo 访问令牌(expo.dev → 账户设置 → 访问令牌)和你的 Expo 账户名称

将凭证导出为环境变量,这样敏感信息就不会进入你的配置中:

export APPSTORE_KEY="$(cat AuthKey_XXXX.p8)"
export APPSTORE_KEY_ID="XXXXXXXXXX"
export APPSTORE_KEY_ISSUER_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

export EXPO_TOKEN="your-expo-access-token"
export EXPO_ACCOUNT_NAME="your-account-name"

Enter fullscreen mode Exit fullscreen mode

连接两个提供程序

terraform {
  required_providers {
    appstore = {
      source = "elevenode/appstore"
    }
    expo = {
      source = "elevenode/expo"
    }
  }
}

# 从环境变量中读取 APPSTORE_KEY / APPSTORE_KEY_ID / APPSTORE_KEY_ISSUER_ID。
provider "appstore" {}

# 从环境变量中读取 EXPO_TOKEN / EXPO_ACCOUNT_NAME。
provider "expo" {}

Enter fullscreen mode Exit fullscreen mode

terraform init 从 registry 中拉取两个提供程序。

第一部分 — Apple 端

首先是 bundle identifier 及其所需的功能。这取代了开发者门户中的复选框界面:

resource "appstore_bundle_identifier" "app" {
  name       = "Acme"
  identifier = "com.acme.app"
  platform   = "IOS"

  capabilities = [
    "PUSH_NOTIFICATIONS",
    "APPLE_ID_AUTH",
    "IN_APP_PURCHASE",
  ]
}

Enter fullscreen mode Exit fullscreen mode

然后是一个将该 bundle ID 绑定到签名证书的配置文件。证书 ID 来自你的 Apple 账户(你可以将它们作为变量传入,以便配置保持可移植性):

variable "distribution_certificate_ids" {
  type        = list(string)
  description = "要嵌入配置文件的 App Store Connect 证书 ID。"
}

resource "appstore_provisioning_profile" "app_store" {
  name                 = "Acme App Store"
  type                 = "IOS_APP_STORE"
  bundle_identifier_id = appstore_bundle_identifier.app.id
  certificate_ids      = var.distribution_certificate_ids
}

Enter fullscreen mode Exit fullscreen mode

注意 bundle_identifier_id 引用——Terraform 现在知道配置文件依赖于 bundle ID,并将按正确顺序创建它们。当你手动操作时,这种依赖关系是不可见的。

第二部分 — Expo 端

创建 EAS 应用,然后声明其环境变量,而不是在仪表板中输入它们:

resource "expo_app" "app" {
  name = "Acme"
  slug = "acme"
}

resource "expo_app_variable" "api_url" {
  app_id       = expo_app.app.id
  name         = "API_URL"
  value        = "https://api.acme.com"
  visibility   = "PUBLIC"
  environments = ["PRODUCTION"]
}

Enter fullscreen mode Exit fullscreen mode

你也可以从这里驱动 EAS Update。定义一个分支和一个将更新映射到其上的通道:

resource "expo_update_branch" "production" {
  app_id = expo_app.app.id
  name   = "production"
}

resource "expo_update_channel" "production" {
  app_id = expo_app.app.id
  name   = "production"

  branch_mapping = jsonencode({
    version = 0
    data = [{
      branchId           = expo_update_branch.production.id
      branchMappingLogic = "true"
    }]
  })
}

Enter fullscreen mode Exit fullscreen mode

应用

terraform apply

Enter fullscreen mode Exit fullscreen mode

Terraform 会显示完整的计划——bundle ID、其功能、配置文件、EAS 应用、其变量、更新通道——在任何内容被创建之前。批准它,你的整个移动发布设置就存在了,在一个地方描述。

现在有趣的部分:针对一个全新的 Apple 团队和 Expo 账户再次运行它,你会得到一个相同的设置。在配置中更改一个功能,打开一个 PR,你的团队成员可以在它到达 Apple 之前审查差异。从配置中删除一个资源,terraform plan 会准确告诉你将要删除什么。这就是基础设施即代码的全部意义,最终应用于移动端总是存在于 web 控制台的那部分。

尝试

两个提供程序都是开源的,并接受 issue 和 pull request:

如果它们为你节省了一些点击,一颗星可以帮助其他人找到它们。如果你希望存在某个资源,请提交 issue——这正是塑造接下来构建内容的那种反馈。