release-and-store-shipping について
Flutter アプリのストアリリース準備を支援し、ビルド設定、署名、成果物の検証、プライバシー申告、段階的ロールアウトを扱うスキルです。リリース担当者向けに、取り消せない公開操作への注意も示します。
主な機能
- 01バージョンとビルド番号の対応を確認
- 02署名資格情報をソース管理から除外
- 03ビルドを難読化してシンボルを保管
- 04権限とプライバシー申告を確認
- 05実機でリリース成果物を検証
- 06提出前にストア設定を照合
活用シーン
- 01Android または iOS のストアリリース準備
- 02ビルド前のリリース衛生チェック
- 03署名済み成果物とアップグレード経路の試験
- 04ストアのプライバシー・権限申告の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ zakariaf/ flutter-skills --skill release-and-store-shippingインストール後、Claude Code で/release-and-store-shippingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/android-play.mdapp-store-connect-submission.mdios-app-store.mdprivacy-permissions-and-claims.mdscripts/check-ipa-slices.shcheck-release-hygiene.sh詳細
リリースとストアへの出荷
CI は コード を検証し、リリースは 成果物 を検証します。ここで扱うのは、見知らぬ人がインストールする正確なバンドルです。R8、難読化、ツリーシェイキングを適用し、リリースモードではアサーションを削除してビルドされ、決して失ってはならない鍵で署名され、その内容に誤りがあればアプリを取り下げるストアに対して、アプリ自体についての情報を申告します。このスキルの範囲は、ci-pipeline-and-gates の終了地点から始まり、design-review-workflow の承認地点で終わります。
ビルド、署名、アップロード、タグ付けには副作用があり、一部は取り消せません(公開済みのビルド番号は二度と再利用できず、iOS ビルドは一度公開すると取り下げられません)。タスクからリリースの意図が読み取れたというだけで実行してはいけません。開発者が明示的にリリースを依頼した場合に限り実行してください。以下の 設定 ルールを確認して適用することは、常に作業範囲内です。
リリースビルドの前(および CI 内)に scripts/check-release-hygiene.sh を実行してください。これはこのスキルの静的チェックを担います。追跡対象の認証情報、不正なビルド番号、デバッグ署名、デバッグ用機能など、ビルドせずに検出できる問題を確認します。アップロードを始める前に、ビルド済みの IPA に対して scripts/check-ipa-slices.sh を実行してください。プラットフォーム固有の詳細は、references/android-play.md、references/ios-app-store.md、references/app-store-connect-submission.md、references/privacy-permissions-and-claims.md に記載されています。
絶対に守るべきルール
pubspec.yamlのversion: x.y.z+Nだけをバージョンの情報源にします。x.y.z→versionName/CFBundleShortVersionString、N→versionCode/CFBundleVersionに対応します。ビルドごとに上書きする場合は、--build-name/--build-numberのみを使用してください。build.gradleやInfo.plistにバージョンをハードコードしてはいけません。理由:情報源が二つあると、気付かないうちに食い違い、クラッシュレポートに実際にはリリースされていないバージョンが記載されます。- ビルド番号は常に増やし、一度公開した番号は二度と使いません。
Play は再利用された、または以前より小さい
versionCodeを拒否し、App Store Connect は同じ短縮バージョンに対して再利用されたCFBundleVersionを拒否します。アップロードに失敗しても番号は消費されます。再試行せず、番号を増やしてください。 - 署名用の素材は、決してリポジトリに入れません。 Keystore/
.jks/.p12、App Store Connect API キー(.p8)、Play サービスアカウントの JSON、android/key.propertiesは Git の追跡対象から除外し、シークレットストアから注入します。理由:Git の履歴に入ったキーは永久に漏えいしたも同然です。また、アプリ署名キーのローテーションはストア側での手続きが必要であり、リリース中に初めて気付くような事態は避けるべきです。 - Play App Signing に登録し、アップロードキーは分けて保管します。 Play App Signing を使えば、紛失したアップロードキーはサポートを通じて復旧できます。登録していないアプリのアプリ署名キーを紛失すると、掲載情報を二度と更新できず、アプリは停止状態になります。
- 難読化してデバッグ情報を分割し、シンボルをアーティファクトとともにアーカイブします。
--obfuscate --split-debug-info=<dir>は、アップロードするすべてのビルドで必須です。また、<dir>はビルドごとにバージョンを付け(symbols/<x.y.z>+<N>/)、マシン外に保存する必要があります。理由:シンボルファイルは、そのリリースのスタックトレースを読み取る唯一の手段です。後から再生成すると別のシンボルが生成され、デコードできません。 - リリースアーティファクトそのものを実機で検証します。 正確な
.aab(Play 内部アプリ共有/bundletool経由)または正確な TestFlight ビルドをインストールしてください。デバッグビルドやプロファイルビルドは使わず、「エミュレーターでは動いた」では済ませないでください。理由:R8、リソースの縮小、難読化はリリースビルドでのみ実行されるため、リフレクティブなプラグイン、ProGuard ルールの不足、アサーションの削除は、出荷するアーティファクトで最初に問題になります。 - マージ済みマニフェストから権限一式全体を確認します。 推移的に導入されるプラグインが、記述していない権限を追加することがあります。ソースのマニフェストではなく、マージ後の出力を確認し、不要な権限は
tools:node="remove"で削除します。また、コミット済みの期待する権限一覧を用意し、ゲートで差分を検出します。iOS では、存在するすべてのNS*UsageDescriptionが実態に即しており、必要なものがすべて存在しなければなりません。使用目的の文字列が欠けていると App Review で却下され、使っていない文字列があると、説明できない利用を主張していることになります。references/privacy-permissions-and-claims.mdを参照してください。 - ストアへの申告はすべて、リポジトリ内の根拠で裏付けられる必要があります。 Play のデータセーフティ、App Store のプライバシー栄養ラベル、
PrivacyInfo.xcprivacy(理由の申告が必要な API とサードパーティ SDK のプライバシーマニフェスト)には、コードの動作を記載します。クラッシュレポーター、広告 SDK、分析パッケージを追加した場合は、同じ PR で申告も更新してください。理由:虚偽の申告は警告ではなく、配信停止につながります。 - プライバシーに関する絶対的な表現は使いません。 掲載情報やオンボーディングの文言で「データがデバイスの外に出ることは一切ありません」や「完全にプライベート」を禁止します。代わりに、保存されるデータ、保存場所、外部に送信されるデータ、送信のタイミングなど、仕組みを説明してください。理由:クラッシュレポートの送信、共有シートからのエクスポート、地図タイルの取得が一度でもあれば、絶対的な表現は嘘になります。
- デバッグ用の機能は、リリースで利用できないようにします。 開発メニュー、テストデータの投入、
eraseDatabaseOnSchemaChange、すべてのログを記録するシンク、ステージング用エンドポイントは、コンパイル時に除外するか、ストア向けではないフレーバーに限定します。記憶に頼らず、grep ゲートで証明してください。理由:出荷されると、実ユーザーのデータが消去または漏えいするおそれがあります。 - サイズとコールドスタートはリリースビルドで測定し、リリースごとに記録します。 容量の測定には
--analyze-size、初回フレームの測定には実際のローエンド端末でプロファイルモードの--trace-startupを使います。記録済みの予算を超える性能低下は、注記ではなくリリースの阻止要件です。 - 停止基準を文書化したうえで、段階的に公開します。
internal→closed→productionの順に割合を段階的に引き上げ、ロールアウトの停止を判断するクラッシュフリーセッションのしきい値を、開始前に決めておきます。理由:Play のロールアウトは停止できますが、App Store のリリースを置き換えるには別のビルドが必要です。 - タグを付けた一つのコミットから出荷します。 正確なコミットにタグを付け、リリースノートを添付し、アーティファクトとシンボルのアーカイブをタグとともに保管します。日付入りのデザインレビュー承認(
design-review-workflow)は、この工程ではなく前提条件です。 - ストア側のゲートを操作できるのはアカウント保有者のみです。また、ストア側の状態を決めつけてはいけません。 アプリレコードの作成、プライバシーに関する質問票、有料アプリケーション契約には API がなく、人手での対応が必要です。初日に対応を依頼してください。有効でない有料アプリケーション契約があると、StoreKit は商品を一つも返さず、契約の未設定ではなく購入ボタンの不具合として現れます。API で設定できる項目(価格、提供地域、アプリ内購入の状態、スクリーンショット、メタデータ)はすべて、提出前にストアから読み戻して確認します。アップロードログが成功していてもサーバー側の状態が保証されるわけではなく、コミット済みのスクリーンショットフォルダーが、スクリーンショット一式のアップロードを意味するわけでもありません。理由:これらはいずれも、設定内容ではなく症状を示すメッセージで提出をブロックします。
- 初回リリースでは、アプリのバージョンと最初のアプリ内購入を一緒に提出します。
Apple は各種類の初回購入を、アプリバージョンの提出と同時に行うよう求めています。バージョンだけを出荷すると、App Review はアプリを審査せずに提出を終了します(ガイドライン 2.1(b))。その対処には新しいバイナリと審査サイクル全体が必要です。提出前に一つのリクエストで確認してください。
GET /v1/reviewSubmissions/{id}/itemsが返す項目数は 1 ではなく2である必要があります。理由:設定済みの購入は提出可能な状態を示しますが、その状態は、すべてのツールや人間にとって、提出済みの状態とまったく同じように見えます。references/app-store-connect-submission.mdを参照してください。
順序を守るリリース手順
リリースが明示的に依頼された場合にのみ実行してください。順序を入れ替えたり、省略したりしないでください。
- 前提条件。 作業ツリーがクリーンであること、リリースコミットで CI がグリーンであること、日付入りの設計レビュー承認があること、変更履歴/リリースノートが作成済みであること。
pubspec.yamlのversion:を更新する — ユーザーに見える変更に応じたセマンティックバージョンx.y.zを設定し、+Nはこれまでにアップロードされた最大番号より大きい値にします。この変更だけをコミットしてください。- 難読化とバージョン付きシンボルを使ってアーティファクトをビルドする。
flutter build appbundle --release \ --obfuscate --split-debug-info=build/symbols/1.4.0+42 flutter build ipa --release \ --obfuscate --split-debug-info=build/symbols/1.4.0+42 \ --export-options-plist=ios/ExportOptions.plist - アップロード前に、
build/symbols/1.4.0+42/をアーティファクトと一緒にマシン外へアーカイブする。 これを二度忘れる人はいません。 - 実機でアーティファクトを検証する — ビルドしたバンドルをインストールし、主要なフローを実行し、強制終了してから再起動します。また、実際の移行経路が実行されるよう、前回リリースしたバージョンに上書きしてアップグレードします(
run-migrationを参照)。 - 各種予算を測定し、前回リリースの数値と並べて記録します:
flutter build appbundle --release --analyze-size # bytes, by library/asset flutter run --profile --trace-startup # start_up_info.json - 各種宣言の整合性を確認する — 統合された権限セット、データセーフティ/栄養表示/
PrivacyInfo.xcprivacy、および掲載ページに記載したプライバシーに関するすべての文言を、現在のコードの動作と照らし合わせます。 - 内部トラック/TestFlight にアップロードし、ストアから配信されたものをスモークテストする — ローカルインストールからではありません。ストア配信時には、アーティファクトに再署名と再圧縮が行われます。
- ストア側の設定を読み戻して整合性を確認する — 価格と販売地域の提供状況が設定済みであること、アプリ内購入が準備完了でバージョンに関連付けられていること、必要なすべてのディスプレイタイプのスクリーンショットが用意されていること、メタデータが完成していること、アカウント所有者のみが対応できる手続き(プライバシー質問票、有料アプリ契約)が完了していることを確認します。ストアに問い合わせて確認し、設定を書き込んだツールを信用しないでください(
references/app-store-connect-submission.md)。 - コミットにタグを付け、リリースノートを公開してから、段階的なロールアウトを開始し、ステップ 1 で合意した停止基準に照らしてクラッシュフリー率を監視します。
バージョンとビルド番号の対応
pubspec.yaml | Android | iOS | ルール |
|---|---|---|---|
x.y.z | versionName | CFBundleShortVersionString | ユーザーに表示されます。同じリリースのビルド間では同じ値を繰り返し使用できます |
+N | versionCode | CFBundleVersion | 整数で、必ず増加させます。再使用は禁止です。アップロードに失敗しても消費されます |
再ビルド時は --build-name=1.4.0 --build-number=43 で上書きしてください。Gradle や Info.plist に生成された値を直接編集してはいけません。フレーバーを使うアプリでは、ストアフレーバーの applicationId /バンドル ID は掲載ページの存続期間中、固定されます。変更すると、既存ユーザーには届かない新しいアプリが作成されます。
シンボルとクラッシュのシンボル化
難読化されたリリースでは、意味のないシンボルで構成されたスタックトレースが生成されます。これをデコードできるのは、そのビルドで生成されたシンボルファイルを使った場合だけです:
flutter symbolize -i crash.txt -d build/symbols/1.4.0+42/app.android-arm64.symbols
version+build ごとにディレクトリを 1 つ保持し、アーティファクトと同じ耐久性のあるストアにアップロードします。リリースがデバイス上で実行される可能性がある期間中は、少なくとも保持してください。バイナリを再ビルドするとシンボルは異なるものになるため、「もう一度ビルドすればよい」では何も復元できません。
CI では証明できないリリースの価値
CI はクリーンなランナーでデバッグ/JIT コードを実行します。R8 とツリーシェイキング後もリフレクションを使うプラグインが動作すること、統合された権限セットが意図したものになっていること、署名済みアーティファクトをインストールして前回のリリースに上書きアップグレードできること、実機の低価格帯ハードウェアで初回フレームとサイズが予算内に収まること、ストアから配信されたバイナリが動作することは、リリース工程でしか証明できません。パイプラインがグリーンなら出荷可能だと主張するのではなく、この事実を正直に伝えてください。
アンチパターン
build.gradleまたはInfo.plistにバージョンをハードコードする — pubspec と食い違り、クラッシュレポートには存在しないビルドが記載されます。- アップロード失敗後にビルド番号を再利用したり、下げたりする — ストアに拒否されます。さらに悪いことに、より小さい番号が受理されると、新しいビルドが更新に表示されなくなる可能性があります。
- キーストア、
key.properties、.p8、サービスアカウントの JSON をコミットする — 永続的に漏えいします。履歴を消去しても、漏えいを取り消すことはできません。 --obfuscateを指定してビルドしながら、--split-debug-infoの出力をアーカイブしない — ビルドディレクトリを削除した時点で、リリース版のクラッシュは永久に解読不能になります。- デバッグ版またはプロファイル版でしか検証していないビルドをリリースする — R8、縮小処理、アサーションの除去によって動作が変わります。リフレクションを使うプラグインの不具合はリリース版でのみ発生します。
- マージ後のものではなく、自分の
AndroidManifest.xmlを監査する — ストアの掲載ページでユーザーに表示される権限は、マニフェストのマージ結果に基づくものであり、個別のファイルに基づくものではありません。 - クラッシュレポーターや広告 SDK をリンクしているのに「データは収集していません」と宣言する — 不正確なデータセーフティや栄養成分表示の申告は、アプリの削除につながるおそれがあります。
- ストア版から開発者メニュー、シード済みのフィクスチャ、または
eraseDatabaseOnSchemaChangeを利用可能にしておく — 実際のユーザーデータを破壊したり、漏えいさせたりするおそれがあります。 - 最初から 100% に公開する — 段階的なロールアウトを行わないと、配信を止める手段がありません。残るのは、審査にまた時間がかかるホットフィックスだけです。
- 署名の問題を「解決」するために
applicationIdやバンドル ID を変更する — 新しいアプリが作成され、既存のユーザーは全員、古いアプリに取り残されます。 - 最後にシミュレーター向けにビルドした状態のツリーから生成した IPA をアップロードする — Apple に拒否されます(90087/91169)。バイナリを間引いても解決できず、クリーンビルドするしかありません。
- ストアを設定したツールを信頼し、ストア側の設定を確認しない — 価格、提供状況、アプリ内課金の状態、スクリーンショットは、「設定済み」と表示されていても、実際には設定されていないことがよくあります。
- コミット済みのスクリーンショットフォルダーを、アップロード済みのスクリーンショット一式として扱う — ディスプレイの種類ごとに提出がブロックされますが、メッセージに記載されるのはファイル名ではなくデバイスの種類です。
- 最初のアプリバージョンを、最初のアプリ内課金なしで提出する — App Review はアプリを審査せずに提出を終了し、修正には新しいバイナリが必要です。
- 購入アイテムの「提出準備完了」状態を「提出済み」と解釈する — これは設定済みで、誰も手を付けていない状態を意味します。
完了条件
-
pubspec.yamlのversion: x.y.z+Nのみを更新し、Nはこれまでにアップロードされたすべての番号より大きくする。プラットフォームファイルにはバージョンをハードコードしない。 -
--obfuscate --split-debug-info=<per-build dir>を指定して--releaseビルドでアーティファクトを作成し、アップロード前にアーティファクトとともにシンボルをマシン外に保管する。 - キーストア、
key.properties、.p8、サービスアカウント JSON を Git で追跡しない。Play App Signing に登録し、アップロードキーは別途保管する。 - 正確なリリースアーティファクトを実機にインストールし、前回のリリース版からアップグレードしたうえで、強制停止して再起動する。
- マージ後の権限セットをコミット済みの想定リストと比較する。すべての iOS
NS*UsageDescriptionが存在し、内容が誠実であることを確認する。 - Data Safety/プライバシーラベル/
PrivacyInfo.xcprivacyを現在の依存関係セットと照合する。ストア掲載情報やオンボーディングの文言で、プライバシーについて絶対的な主張をしない。 - デバッグ用機能がゲートによってストア向けフレーバーから到達できないことを確認する。
- サイズ(
--analyze-size)とコールドスタート(--trace-startup)を前回のリリースの数値と比較して記録し、説明のつかない悪化がないことを確認する。 - 日付入りのデザインレビュー承認を用意し、コミットにタグを付け、リリースノートを公開する。
- 停止基準を文書化し、監視担当者を決めたうえで段階的にロールアウトする。
- リリースコミットで
scripts/check-release-hygiene.shが成功し、アップロード前の IPA に対してscripts/check-ipa-slices.shが成功する。 - 申請前にストア側の状態を読み戻して確認する。価格と配信地域、アプリ内購入の準備完了とバージョンへの紐付け、必要なすべてのディスプレイタイプのスクリーンショット、メタデータの完成、アカウント所有者のみが実行できる手続きの完了を確認する。
- 初回リリースでは、申請にアプリのバージョンと最初のアプリ内購入の両方を含める(
reviewSubmissions/{id}/itemsが 2 を返す)。各購入には審査用スクリーンショット、税カテゴリ、配信設定、およびアプリの各ロケールに対応したローカライズを用意する。
マルチフレーバーの場合
各フレーバーをそれぞれの --flavor とエントリーポイントでビルドし、フレーバーごと、かつビルドごとにシンボルディレクトリを用意します(symbols/<flavor>/<x.y.z>+<N>/)。ストア向けフレーバーとその applicationId は、掲載情報が存在する限り変更しません。ストア向けではないフレーバー(dev、QA)は決してアップロード可能にしてはなりません。担当者の記憶に頼らず、リリーススクリプトで強制してください。フレーバーのコンポジションルートについては service-boundary-and-native を参照してください。
関連スキル
ci-pipeline-and-gates— この作業を開始する前に通過しておくべきゲートと、CI では証明できないことについての正確な説明です。design-review-workflow— ステップ 1 の前提条件となる、日付入りの承認です。run-migration— ステップ 5 で実施する、前回のリリース版からのアップグレード手順です。dependency-hygiene— 新しい依存関係を導入して、ここでのストア向け宣言を変更する前に監査する方法です。service-boundary-and-native— フレーバー、ネイティブとの境界、およびマージされるマニフェストを持つプラグインです。flutter-performance— パフォーマンス予算の根拠となる、プロファイルモードでの測定手法です。ads-and-iap-monetization— App Review の審査担当者が実際に目にする内容を決める、エンタイトルメント、初期化前の復元、プリロードまたは非表示のルールと、リリースゲートが確認する広告識別子です。
参考資料
- Flutter — Android のビルドとリリース:https://docs.flutter.dev/deployment/android
- Flutter — iOS のビルドとリリース:https://docs.flutter.dev/deployment/ios
- Flutter — Dart コードの難読化/
flutter symbolize:https://docs.flutter.dev/deployment/obfuscate - Flutter — アプリサイズの測定:https://docs.flutter.dev/perf/app-size
- Android — Play App Signing:https://developer.android.com/studio/publish/app-signing
- Android — 複数のマニフェストファイルのマージ:https://developer.android.com/build/manage-manifests
- Google Play — データセーフティフォーム:https://support.google.com/googleplay/android-developer/answer/10787469
- Apple — プライバシーマニフェストファイル:https://developer.apple.com/documentation/bundleresources/privacy_manifest_files