elevenode

React Native / ExpoアプリをApp Storeへリリースする場合、定番の手順をご存知でしょう。Apple Developerポータルを開き、バンドルIDを作成し、機能のチェックボックスにチェックを入れ、プロビジョニングプロファイルを生成し、適切な証明書を選択します。その後Expoダッシュボードに移動し、EASアプリを作成し、認証情報を結びつけ、1画面ずつ環境変数を追加します。

これは1回ならうまくいきますが、2つ目のアプリや2つ目の環境、またはチームメイトがなぜ特定の機能が有効になっているのかを知りたい場合に再び行う必要があります。どこにも記録されていません。構成がずれてしまいます。また、通常のモバイルツールはこの問題をあまり解決しません:fastlaneやEAS CLIは優れていますが、それらは命令型 — 何かを行うスクリプト — であり、リリース設定がどうあるべきかを宣言的に記述するものではありません。

これら2つのプロバイダーが埋めるギャップがそれです:

  • elevenode/appstore — App Store Connect:バンドルID、プロビジョニングプロファイル、証明書。
  • elevenode/expo — Expo Application Services (EAS):アプリ、認証情報、環境変数、アップデートチャンネル。

どちらもオープンソース(Apache 2.0)でTerraform Registryに公開されています。これらを組み合わせて、モバイルアプリのリリース設定をコードとして記述してみましょう。

必要なもの

  • Terraform(またはOpenTofu)
  • App Store Connect APIキー(Users and Access → Integrations → App Store Connect API):キー、キーID、発行者ID
  • Expoアクセストークン(expo.dev → account settings → Access Tokens)と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"
    }
  }
}

# Reads APPSTORE_KEY / APPSTORE_KEY_ID / APPSTORE_KEY_ISSUER_ID from the env.
provider "appstore" {}

# Reads EXPO_TOKEN / EXPO_ACCOUNT_NAME from the env.
provider "expo" {}

Enter fullscreen mode Exit fullscreen mode

terraform initで両プロバイダーをレジストリから取得します。

パート1 — Apple側

まず、バンドルIDと必要な機能を定義します。これはDeveloperポータルのチェックボックス画面を置き換えるものです:

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

次に、そのバンドルIDを署名証明書に関連付けるプロビジョニングプロファイルを作成します。証明書IDはAppleアカウントから取得します(設定を移植しやすくするため、変数として渡すことができます):

variable "distribution_certificate_ids" {
  type        = list(string)
  description = "App Store Connect certificate IDs to embed in the profile."
}

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はプロファイルがバンドルIDに依存していることを認識し、正しい順序で作成します。この依存関係は手作業では見えませんでした。

パート2 — 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は、バンドルID、その機能、プロファイル、EASアプリ、その変数、アップデートチャンネルなど、すべてが作成される前に完全な計画を表示します。承認すると、モバイルリリース設定全体が1つの場所に記述された状態で作成されます。

ここで興味深い点:新しいAppleチームとExpoアカウントに対して再度実行すると、同一の設定が得られます。設定で機能を変更し、PRを開くと、チームメイトはAppleに反映される前に差分を確認できます。設定からリソースを削除すると、terraform planで何が削除されるかが正確にわかります。これがインフラストラクチャ・アズ・コードの目的であり、モバイルのウェブコンソール部分にようやく適用されたものです。

試してみる

どちらのプロバイダーもオープンソースで、issueやpull requestを受け付けています:

クリック作業の削減に役立つ場合は、スターを付けて他の人が見つけやすくしましょう。存在してほしいリソースがあればissueを開いてください — それがまさに次に構築されるものを形作るフィードバックです。