From d133d1a7fea1051d375fb0080ca7bcddbe7863fb Mon Sep 17 00:00:00 2001 From: tianma-if Date: Wed, 29 Jul 2026 20:39:15 +0800 Subject: [PATCH] Submit store deliveries for review --- .github/workflows/store-delivery.yml | 42 ++- apps/mobile/Gemfile | 3 + apps/mobile/Gemfile.lock | 344 +++++++++++++++++++++++ apps/mobile/eas.json | 3 +- apps/mobile/fastlane/Fastfile | 33 +++ docs/mobile-build.md | 8 +- docs/store-delivery.md | 43 ++- docs/store-delivery.zh-CN.md | 38 ++- scripts/store-delivery.mjs | 18 +- scripts/store-delivery.test.mjs | 18 +- scripts/validate-store-delivery.mjs | 17 +- scripts/validate-store-delivery.test.mjs | 19 +- 12 files changed, 458 insertions(+), 128 deletions(-) create mode 100644 apps/mobile/Gemfile create mode 100644 apps/mobile/Gemfile.lock create mode 100644 apps/mobile/fastlane/Fastfile diff --git a/.github/workflows/store-delivery.yml b/.github/workflows/store-delivery.yml index 4e2518af..8c666e57 100644 --- a/.github/workflows/store-delivery.yml +++ b/.github/workflows/store-delivery.yml @@ -17,20 +17,15 @@ on: - android - ios android_track: - description: "Google Play track (production is uploaded as a Draft)" + description: "Google Play track" required: true - default: internal + default: production type: choice options: - - internal + - production - alpha - beta - - production - production_confirmation: - description: "For Google Play production, type the exact release tag" - required: false - default: "" - type: string + - internal permissions: contents: read @@ -96,15 +91,13 @@ jobs: ANDROID_TRACK: ${{ inputs.android_track }} PLATFORM: ${{ inputs.platform }} PREVIOUS_TAG: ${{ steps.release.outputs.previous_tag }} - PRODUCTION_CONFIRMATION: ${{ inputs.production_confirmation }} RELEASE_TAG: ${{ inputs.release_tag }} run: | node scripts/validate-store-delivery.mjs \ "$RELEASE_TAG" \ "$PREVIOUS_TAG" \ "$PLATFORM" \ - "$ANDROID_TRACK" \ - "$PRODUCTION_CONFIRMATION" | + "$ANDROID_TRACK" | tee -a "$GITHUB_OUTPUT" android: @@ -192,6 +185,13 @@ jobs: with: bun-version: 1.3.14 + - name: Set up Ruby and fastlane + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + bundler-cache: true + working-directory: apps/mobile + - name: Install dependencies run: bun install --frozen-lockfile @@ -213,9 +213,13 @@ jobs: --json )" build_id="$(jq -r 'if type == "array" then .[0].id else .id end' <<<"$build_json")" + build_number="$(jq -r 'if type == "array" then .[0].appBuildVersion else .appBuildVersion end' <<<"$build_json")" test -n "$build_id" test "$build_id" != "null" + test -n "$build_number" + test "$build_number" != "null" echo "build_id=$build_id" >> "$GITHUB_OUTPUT" + echo "build_number=$build_number" >> "$GITHUB_OUTPUT" working-directory: apps/mobile - name: Upload archive to App Store Connect and TestFlight @@ -229,3 +233,17 @@ jobs: --non-interactive \ --wait working-directory: apps/mobile + + - name: Submit the uploaded build to App Review + env: + APP_STORE_BUILD_NUMBER: ${{ steps.build.outputs.build_number }} + APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }} + APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} + APP_STORE_CONNECT_API_KEY_P8_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_P8_BASE64 }} + APP_STORE_VERSION: ${{ needs.plan.outputs.version }} + run: | + test -n "$APP_STORE_CONNECT_API_ISSUER_ID" + test -n "$APP_STORE_CONNECT_API_KEY_ID" + test -n "$APP_STORE_CONNECT_API_KEY_P8_BASE64" + bundle exec fastlane ios submit_review + working-directory: apps/mobile diff --git a/apps/mobile/Gemfile b/apps/mobile/Gemfile new file mode 100644 index 00000000..0b115797 --- /dev/null +++ b/apps/mobile/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane", "2.237.0" diff --git a/apps/mobile/Gemfile.lock b/apps/mobile/Gemfile.lock new file mode 100644 index 00000000..13646321 --- /dev/null +++ b/apps/mobile/Gemfile.lock @@ -0,0 +1,344 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.8) + abbrev (0.1.2) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.4.0) + aws-partitions (1.1274.0) + aws-sdk-core (3.254.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.130.0) + aws-sdk-core (~> 3, >= 3.254.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.228.1) + aws-sdk-core (~> 3, >= 3.254.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.1.2) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + csv (3.3.6) + declarative (0.0.20) + digest-crc (0.7.0) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (1.6.0) + logger + faraday (1.10.6) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.8) + faraday (>= 0.8.0) + http-cookie (>= 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.1) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.2.0) + multipart-post (~> 2.0) + faraday-net_http (1.0.2) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.4) + faraday_middleware (1.2.1) + faraday (~> 1.0) + fastimage (2.4.1) + fastlane (2.237.0) + CFPropertyList (>= 2.3, < 5.0.0) + abbrev (~> 0.1) + addressable (>= 2.9.0, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.197) + babosa (>= 1.0.3, < 2.0.0) + base64 (~> 0.2) + benchmark (>= 0.1.0) + bundler (>= 2.4.0, < 5.0.0) + colored (~> 1.2) + commander (~> 4.6) + csv (~> 3.3) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 2.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.1.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.3.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.10.3, < 4) + logger (>= 1.6, < 2.0) + mini_magick (>= 4.9.4, < 5.0.0) + multi_json (~> 1.12) + multipart-post (>= 2.0.0, < 3.0.0) + mutex_m (~> 0.3) + naturally (~> 2.2) + nkf (~> 0.2) + optparse (>= 0.1.1, < 1.0.0) + ostruct (>= 0.1.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.4.1) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-sirp (1.1.0) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.106.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-core (0.18.0) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 1.9) + httpclient (>= 2.8.3, < 3.a) + mini_mime (~> 1.0) + mutex_m + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + google-apis-iamcredentials_v1 (0.28.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-playcustomapp_v1 (0.18.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-storage_v1 (0.65.0) + google-apis-core (>= 0.15.0, < 2.a) + google-cloud-core (1.9.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (2.2.2) + base64 (~> 0.2) + faraday (>= 1.0, < 3.a) + google-cloud-errors (1.7.0) + google-cloud-storage (1.62.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-core (>= 0.18, < 2) + google-apis-iamcredentials_v1 (~> 0.18) + google-apis-storage_v1 (>= 0.42) + google-cloud-core (~> 1.6) + googleauth (~> 1.9) + mini_mime (~> 1.0) + google-logging-utils (0.2.0) + googleauth (1.17.3) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) + jwt (>= 1.4, < 4.0) + os (>= 0.9, < 2.0) + pstore (~> 0.1) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.8) + domain_name (~> 0.5) + httpclient (2.9.0) + mutex_m + jmespath (1.6.2) + json (2.21.1) + jwt (3.2.0) + base64 + logger (1.7.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.21.1) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) + naturally (2.3.0) + nkf (0.3.0) + optparse (0.8.1) + os (1.1.4) + ostruct (0.6.3) + plist (3.7.2) + pstore (0.2.1) + public_suffix (7.0.5) + rake (13.4.2) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.8.0) + rexml (3.4.4) + rouge (3.28.0) + ruby2_keywords (0.0.5) + rubyzip (2.4.1) + security (0.1.5) + signet (0.22.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 4.0) + simctl (1.6.10) + CFPropertyList + naturally + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unicode-display_width (2.6.0) + word_wrap (1.0.0) + xcodeproj (1.28.1) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + base64 + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.4.0) + nkf + rexml (>= 3.3.6, < 4.0) + xcpretty (0.4.1) + rouge (~> 3.28.0) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + arm64-darwin-25 + ruby + +DEPENDENCIES + fastlane (= 2.237.0) + +CHECKSUMS + CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261 + abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242 + addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af + artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263 + atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f + aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b + aws-partitions (1.1274.0) sha256=6e555daee7f561541ef7871036e4e943afeabc2eab87186161e5ed08eb083c75 + aws-sdk-core (3.254.0) sha256=ee3e3220b8468a3c9e59daba18e6ec897bf5c7ce8adcc0670cfa2f1f092112fe + aws-sdk-kms (1.130.0) sha256=a2e83662ca31b77a2a19c9aa2f40a98165a67270c18c718fe1c70d0cbd7cd749 + aws-sdk-s3 (1.228.1) sha256=8865f6c6d068a9713a2aa6fe8107d11b0020a93cd5110b3994526bd9560f553e + aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 + babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99 + base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b + benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c + bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd + claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e + colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c + colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a + commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9 + csv (3.3.6) sha256=aba61e7e507a66f03d45cb1f3c4b6359861c3504038b422962875dce099e4456 + declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9 + digest-crc (0.7.0) sha256=64adc23a26a241044cbe6732477ca1b3c281d79e2240bcff275a37a5a0d78c07 + domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933 + dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8 + emoji_regex (3.2.3) sha256=ecd8be856b7691406c6bf3bb3a5e55d6ed683ffab98b4aa531bb90e1ddcc564b + excon (1.6.0) sha256=ebe2ab83c055ef8e117c62c91a3535b966a59a64868188219d49ab90cd83b65f + faraday (1.10.6) sha256=7ff4802a6b312876a2241b3e641ce0d5045e168dd871b422c35b505e5261ad4d + faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb + faraday-em_http (1.0.0) sha256=7a3d4c7079789121054f57e08cd4ef7e40ad1549b63101f38c7093a9d6c59689 + faraday-em_synchrony (1.0.1) sha256=bf3ce45dcf543088d319ab051f80985ea6d294930635b7a0b966563179f81750 + faraday-excon (1.1.0) sha256=b055c842376734d7f74350fe8611542ae2000c5387348d9ba9708109d6e40940 + faraday-httpclient (1.0.1) sha256=4c8ff1f0973ff835be8d043ef16aaf54f47f25b7578f6d916deee8399a04d33b + faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757 + faraday-net_http (1.0.2) sha256=63992efea42c925a20818cf3c0830947948541fdcf345842755510d266e4c682 + faraday-net_http_persistent (1.2.0) sha256=0b0cbc8f03dab943c3e1cc58d8b7beb142d9df068b39c718cd83e39260348335 + faraday-patron (1.0.0) sha256=dc2cd7b340bb3cc8e36bcb9e6e7eff43d134b6d526d5f3429c7a7680ddd38fa7 + faraday-rack (1.0.0) sha256=ef60ec969a2bb95b8dbf24400155aee64a00fc8ba6c6a4d3968562bcc92328c0 + faraday-retry (1.0.4) sha256=dc659233777fabf96c69c2ffe56c0a5d2c102af90321a42cc6c90157bcd716aa + faraday_middleware (1.2.1) sha256=d45b78c8ee864c4783fbc276f845243d4a7918a67301c052647bacabec0529e9 + fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20 + fastlane (2.237.0) sha256=bb1e867bc070fb328741b5e6e7606d5ba596941a9ecca0478f60ef22d1009db9 + fastlane-sirp (1.1.0) sha256=10bc94f9682efd8e1badfb31452a76dd8981f1f3a33717c765fde6d75b54d847 + gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939 + google-apis-androidpublisher_v3 (0.106.0) sha256=b90b5312b70f8f731def88f24a2b8fb791fe1b979a7c2c14a905cb6cae19cf3f + google-apis-core (0.18.0) sha256=96b057816feeeab448139ed5b5c78eab7fc2a9d8958f0fbc8217dedffad054ee + google-apis-iamcredentials_v1 (0.28.0) sha256=0a92ffe6cc39c569554af2a77a25dfc61519ed8bbb64ab04cffdd352dc5ef106 + google-apis-playcustomapp_v1 (0.18.0) sha256=44b277b9dee4a59ac5e9d98be1485edc5e382d2f9d73c79ae8908a455786a254 + google-apis-storage_v1 (0.65.0) sha256=4247aa542931691f6988ab61e45cdea2878cbfe897a98eec4af9cdcb678eb359 + google-cloud-core (1.9.0) sha256=ab55409f51488e8deefb6edcc1ce4771dfb5da2fe7b3bc075709a030c2b682a4 + google-cloud-env (2.2.2) sha256=94bed40e05a67e9468ce1cb38389fba9a90aa8fc62fc9e173204c1dca59e21e7 + google-cloud-errors (1.7.0) sha256=6e682f42d89aae08689f36f28495de629d756da076bafff0003bac7f8042ebb3 + google-cloud-storage (1.62.0) sha256=e2c3c08bf8fd40d50be92304084942203314d4fc0ee52028e99f9359c3ad1330 + google-logging-utils (0.2.0) sha256=675462b4ea5affa825a3442694ca2d75d0069455a1d0956127207498fca3df7b + googleauth (1.17.3) sha256=9a655455885c0c17a8b70537acd2a992a7c7842c43c06c6612dfb0d93e755d58 + highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479 + http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6 + httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8 + jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 + json (2.21.1) sha256=13a43df75d95641443f5702dff350f237164a9d811ff0f2c2800d4d980220583 + jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7 + logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 + mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9 + mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef + multi_json (1.21.1) sha256=e6126a31808e3b4d19f483c775ceac34df190dffa62adfb63a165ee14ba68080 + multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8 + mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751 + nanaimo (0.4.0) sha256=faf069551bab17f15169c1f74a1c73c220657e71b6e900919897a10d991d0723 + naturally (2.3.0) sha256=459923cf76c2e6613048301742363200c3c7e4904c324097d54a67401e179e01 + nkf (0.3.0) sha256=357a8dbeba38b727b75930f665146546076a394a1c243faf634ff176e3588895 + optparse (0.8.1) sha256=42bea10d53907ccff4f080a69991441d611fbf8733b60ed1ce9ee365ce03bd1a + os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f + ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 + plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42 + pstore (0.2.1) sha256=03904d0f2c66579e96d1e6704cdabc0c88df7ea8ed8782d9f3569f6f6c702c1a + public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623 + rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 + representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace + retriable (3.8.0) sha256=9f2f1b0207594c7817f17f671587b8ec7587387ac6cebda6c941a802bb98a8e5 + rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 + rouge (3.28.0) sha256=0d6de482c7624000d92697772ab14e48dca35629f8ddf3f4b21c99183fd70e20 + ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef + rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615 + security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7 + signet (0.22.0) sha256=b76d495ccb07ad35dbc89f3e920665a9d8ed717141955034005d7843dcfe4780 + simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b + terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea + terminal-table (3.0.2) sha256=f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91 + trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3 + tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48 + tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50 + tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542 + uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc + unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a + word_wrap (1.0.0) sha256=f556d4224c812e371000f12a6ee8102e0daa724a314c3f246afaad76d82accc7 + xcodeproj (1.28.1) sha256=6f12670f00739d9817ca27ac89d6ef01cc86050e22a0bc08a3131487e5b5cddc + xcpretty (0.4.1) sha256=b14c50e721f6589ee3d6f5353e2c2cfcd8541fa1ea16d6c602807dd7327f3892 + xcpretty-travis-formatter (1.0.1) sha256=aacc332f17cb7b2cba222994e2adc74223db88724fe76341483ad3098e232f93 + +BUNDLED WITH + 4.0.16 diff --git a/apps/mobile/eas.json b/apps/mobile/eas.json index 21575abc..9bdaea82 100644 --- a/apps/mobile/eas.json +++ b/apps/mobile/eas.json @@ -47,8 +47,7 @@ "store-production": { "android": { "track": "production", - "releaseStatus": "draft", - "changesNotSentForReview": true + "releaseStatus": "completed" } }, "store-ios": { diff --git a/apps/mobile/fastlane/Fastfile b/apps/mobile/fastlane/Fastfile new file mode 100644 index 00000000..b8a81b9b --- /dev/null +++ b/apps/mobile/fastlane/Fastfile @@ -0,0 +1,33 @@ +opt_out_usage + +default_platform(:ios) + +platform :ios do + desc "Submit an uploaded EAS build to App Review and release it automatically after approval" + lane :submit_review do + version = ENV.fetch("APP_STORE_VERSION") + build_number = ENV.fetch("APP_STORE_BUILD_NUMBER") + + api_key = app_store_connect_api_key( + key_id: ENV.fetch("APP_STORE_CONNECT_API_KEY_ID"), + issuer_id: ENV.fetch("APP_STORE_CONNECT_API_ISSUER_ID"), + key_content: ENV.fetch("APP_STORE_CONNECT_API_KEY_P8_BASE64"), + is_key_content_base64: true, + in_house: false + ) + + deliver( + api_key: api_key, + app_identifier: "org.edgeever.mobile", + app_version: version, + build_number: build_number, + submit_for_review: true, + automatic_release: true, + phased_release: false, + force: true, + skip_binary_upload: true, + skip_metadata: true, + skip_screenshots: true + ) + end +end diff --git a/docs/mobile-build.md b/docs/mobile-build.md index 183006b5..0e0a4229 100644 --- a/docs/mobile-build.md +++ b/docs/mobile-build.md @@ -115,9 +115,11 @@ bunx eas-cli build --platform ios --profile production The first command requires the Apple Account Holder to authenticate and may prompt for two-factor authentication. The production profile automatically increments the App Store build number. Routine delivery should use the separate -store-delivery workflow, which builds from an immutable formal Release tag and -uploads to App Store Connect/TestFlight. Apple credentials, App Store Connect -API keys, certificates, and provisioning profiles must never be committed. +store-delivery workflow, which builds from an immutable formal Release tag, +uploads to App Store Connect, and submits that exact build to App Review. +Approved builds are released automatically. Apple credentials, App Store +Connect API keys, certificates, and provisioning profiles must never be +committed. ## EAS diff --git a/docs/store-delivery.md b/docs/store-delivery.md index d6ff09dc..95faad35 100644 --- a/docs/store-delivery.md +++ b/docs/store-delivery.md @@ -6,8 +6,9 @@ GitHub Releases and mobile store delivery are separate operations: Google Play or App Store Connect. - `bun run publish:stores` dispatches a manual store-delivery workflow for one existing formal Release tag. -- Store publication remains a separate decision. Google Play production uploads - remain Drafts, and iOS uploads stop at App Store Connect/TestFlight. +- Store delivery is the authorization to submit. By default, Google Play uses + the Production track, while iOS continues from App Store Connect upload into + App Review. Approved builds are released automatically. ## Safety Model @@ -19,8 +20,7 @@ store build starts, it verifies that: - the audited range since the previous formal Release contains mobile runtime changes; - root and mobile app versions both match the Release tag; -- Android `versionCode` increased; -- a Google Play production request repeats the exact Release tag as confirmation. +- Android `versionCode` increased. A Release that reused the previous mobile binary is intentionally rejected. It does not represent a new store binary and should not be uploaded again. @@ -34,6 +34,9 @@ Configure these GitHub repository secrets: - `ANDROID_KEYSTORE_PASSWORD` - `ANDROID_KEY_ALIAS` - `ANDROID_KEY_PASSWORD` +- `APP_STORE_CONNECT_API_KEY_ID` +- `APP_STORE_CONNECT_API_ISSUER_ID` +- `APP_STORE_CONNECT_API_KEY_P8_BASE64` Upload the Google Play service-account key to the Android application's EAS Submit credentials. Configure the iOS distribution credentials and App Store @@ -42,9 +45,8 @@ repository. Create these GitHub Environments: -- `store-delivery` for Internal/Alpha/Beta and TestFlight delivery. -- `store-production` for Google Play production delivery. Configure required - reviewers on this environment. +- `store-delivery` for Android testing tracks and Apple App Review delivery. +- `store-production` for Google Play production delivery. EAS Submit requires the application to exist in each store and Google Play API submission requires the service account to have access to the application. @@ -54,7 +56,7 @@ Follow the official ## Commands -Deliver both platforms to Google Play Internal and App Store Connect/TestFlight: +Submit both platforms to Google Play Production and Apple App Review: ```sh bun run publish:stores -- --release v1.7.0 @@ -69,16 +71,6 @@ bun run publish:stores -- \ --android-track beta ``` -Prepare a Google Play production Draft: - -```sh -bun run publish:stores -- \ - --release v1.7.0 \ - --platform android \ - --android-track production \ - --confirm-production v1.7.0 -``` - Use `--dry-run` to print the GitHub workflow dispatch without starting it. ## Platform Behavior @@ -89,15 +81,18 @@ The self-hosted release runner builds a signed AAB from the selected tag, verifies its signature and R8 mapping, preserves both as GitHub Actions artifacts, and uploads the AAB through EAS Submit. -Internal, Alpha, and Beta profiles use a completed release on the selected -testing track. Production uses `releaseStatus: draft` and -`changesNotSentForReview: true`; completing review and rollout remains a manual -Google Play Console action. +Internal, Alpha, Beta, and Production profiles use a completed release on the +selected track. The default command targets Production; use +`--android-track internal`, `alpha`, or `beta` only when a testing delivery is +explicitly requested. ### App Store Connect EAS Build creates the signed iOS archive from the selected tag and increments the remote iOS build number. EAS Submit uploads that exact build to App Store Connect, where it becomes available to TestFlight after Apple finishes -processing it. The workflow does not select the build for an App Store version -or submit it for App Review. +processing it. Fastlane then selects the exact app version and build number, +submits it to App Review with an App Store Connect API key, and configures +automatic release after approval. Missing metadata, agreements, review +information, or credentials cause the workflow to fail without submitting a +different build. diff --git a/docs/store-delivery.zh-CN.md b/docs/store-delivery.zh-CN.md index 5bf12cde..c969377c 100644 --- a/docs/store-delivery.zh-CN.md +++ b/docs/store-delivery.zh-CN.md @@ -6,8 +6,9 @@ GitHub Release 与移动端商店交付是两个独立操作: App Store Connect。 - `bun run publish:stores` 针对一个已经存在的正式 Release tag,触发手动商店 交付工作流。 -- 商店正式发布仍然是独立决策。Google Play Production 上传会保持 Draft, - iOS 上传只到 App Store Connect/TestFlight。 +- 触发商店交付就代表已经授权正式提交。默认情况下,Google Play 使用 + Production 轨道;iOS 在上传 App Store Connect 后继续提交 App Review。审核 + 通过后自动发布。 ## 安全模型 @@ -17,8 +18,7 @@ GitHub Release 与移动端商店交付是两个独立操作: - Release 目标提交与 Git tag 指向同一个提交; - 与上一个正式 Release 相比,审计范围内确实包含移动端运行时代码变化; - 根版本和移动端 App 版本都与 Release tag 一致; -- Android `versionCode` 已递增; -- Google Play Production 请求再次输入了完全一致的 Release tag。 +- Android `versionCode` 已递增。 如果某个 Release 复用了上一版移动端二进制,工作流会主动拒绝。它不代表新的 商店二进制,不应重复上传。 @@ -32,6 +32,9 @@ GitHub Release 与移动端商店交付是两个独立操作: - `ANDROID_KEYSTORE_PASSWORD` - `ANDROID_KEY_ALIAS` - `ANDROID_KEY_PASSWORD` +- `APP_STORE_CONNECT_API_KEY_ID` +- `APP_STORE_CONNECT_API_ISSUER_ID` +- `APP_STORE_CONNECT_API_KEY_P8_BASE64` 将 Google Play 服务账号密钥上传到 Android 应用的 EAS Submit Credentials。 在 EAS 中配置 iOS 分发凭据和 App Store Connect API Key。凭据和私钥禁止提交 @@ -39,9 +42,8 @@ GitHub Release 与移动端商店交付是两个独立操作: 创建以下 GitHub Environments: -- `store-delivery`:用于 Internal/Alpha/Beta 和 TestFlight 交付。 -- `store-production`:用于 Google Play Production 交付,并为该 Environment - 配置 Required Reviewers。 +- `store-delivery`:用于 Android 测试轨道和 Apple App Review 交付。 +- `store-production`:用于 Google Play Production 交付。 EAS Submit 要求应用已经在对应商店中创建;Google Play API 提交还要求服务账号 拥有该应用的访问权限。配置方法参考官方 @@ -50,7 +52,7 @@ EAS Submit 要求应用已经在对应商店中创建;Google Play API 提交 ## 命令 -同时交付到 Google Play Internal 与 App Store Connect/TestFlight: +同时提交 Google Play Production 和 Apple App Review: ```sh bun run publish:stores -- --release v1.7.0 @@ -65,16 +67,6 @@ bun run publish:stores -- \ --android-track beta ``` -准备 Google Play Production Draft: - -```sh -bun run publish:stores -- \ - --release v1.7.0 \ - --platform android \ - --android-track production \ - --confirm-production v1.7.0 -``` - 使用 `--dry-run` 可以只输出将要触发的 GitHub 工作流,不实际启动。 ## 各平台行为 @@ -84,12 +76,14 @@ bun run publish:stores -- \ 自托管发布 Runner 会从指定 tag 构建签名 AAB,验证签名和 R8 Mapping,将两者 保留为 GitHub Actions Artifacts,然后通过 EAS Submit 上传 AAB。 -Internal、Alpha 和 Beta 配置会在对应测试轨道创建 Completed Release。 -Production 使用 `releaseStatus: draft` 和 `changesNotSentForReview: true`; -完成审核和发布仍需在 Google Play Console 中手动操作。 +Internal、Alpha、Beta 和 Production 配置都会在所选轨道创建 Completed +Release。默认命令直接使用 Production;只有明确要求测试交付时才使用 +`--android-track internal`、`alpha` 或 `beta`。 ### App Store Connect EAS Build 从指定 tag 创建签名 iOS Archive,并递增远端 iOS Build Number。 EAS Submit 将同一个构建上传到 App Store Connect;Apple 处理完成后,它会出现在 -TestFlight 中。工作流不会为 App Store 版本选择构建,也不会自动提交 App Review。 +TestFlight 中。随后 Fastlane 使用 App Store Connect API Key 精确选择相同的 +App Version 和 Build Number,提交 App Review,并设置为审核通过后自动发布。 +如果元数据、协议、审核信息或凭据不完整,工作流会失败,不会改为提交其他构建。 diff --git a/scripts/store-delivery.mjs b/scripts/store-delivery.mjs index b09101aa..35bbf461 100644 --- a/scripts/store-delivery.mjs +++ b/scripts/store-delivery.mjs @@ -12,8 +12,7 @@ const usage = `Usage: Options: --release Required formal GitHub Release tag --platform android, ios, or both (default: both) - --android-track internal, alpha, beta, or production (default: internal) - --confirm-production Required when the Android track is production + --android-track production, alpha, beta, or internal (default: production) --repository GitHub repository (default: ${DEFAULT_REPOSITORY}) --dry-run Print the workflow dispatch plan --help Show this help @@ -23,8 +22,7 @@ export const parseStoreDeliveryArgs = (argv) => { const options = { releaseTag: "", platform: "both", - androidTrack: "internal", - productionConfirmation: "", + androidTrack: "production", repository: DEFAULT_REPOSITORY, dryRun: false, help: false, @@ -33,7 +31,6 @@ export const parseStoreDeliveryArgs = (argv) => { ["--release", "releaseTag"], ["--platform", "platform"], ["--android-track", "androidTrack"], - ["--confirm-production", "productionConfirmation"], ["--repository", "repository"], ]); @@ -73,15 +70,6 @@ export const parseStoreDeliveryArgs = (argv) => { "--android-track must be internal, alpha, beta, or production.", ); } - if ( - options.androidTrack === "production" && - options.platform !== "ios" && - options.productionConfirmation !== options.releaseTag - ) { - throw new Error( - `Production delivery requires --confirm-production ${options.releaseTag}.`, - ); - } if (!/^[^/\s]+\/[^/\s]+$/.test(options.repository)) { throw new Error("--repository must use owner/name format."); } @@ -103,8 +91,6 @@ const run = (options) => { `platform=${options.platform}`, "-f", `android_track=${options.androidTrack}`, - "-f", - `production_confirmation=${options.productionConfirmation}`, ]; if (options.dryRun) { console.log(`gh ${args.join(" ")}`); diff --git a/scripts/store-delivery.test.mjs b/scripts/store-delivery.test.mjs index f84930e8..4ed5df59 100644 --- a/scripts/store-delivery.test.mjs +++ b/scripts/store-delivery.test.mjs @@ -2,14 +2,13 @@ import { describe, expect, test } from "bun:test"; import { parseStoreDeliveryArgs } from "./store-delivery.mjs"; describe("store delivery command", () => { - test("uses safe testing defaults", () => { + test("uses full production delivery defaults", () => { expect( parseStoreDeliveryArgs(["--release", "v1.7.0"]), ).toMatchObject({ releaseTag: "v1.7.0", platform: "both", - androidTrack: "internal", - productionConfirmation: "", + androidTrack: "production", }); }); @@ -29,17 +28,4 @@ describe("store delivery command", () => { parseStoreDeliveryArgs(["--release", "latest"]) ).toThrow("stable vX.Y.Z"); }); - - test("requires exact confirmation for Google Play production", () => { - expect(() => - parseStoreDeliveryArgs([ - "--release", - "v1.7.0", - "--platform", - "android", - "--android-track", - "production", - ]) - ).toThrow("--confirm-production v1.7.0"); - }); }); diff --git a/scripts/validate-store-delivery.mjs b/scripts/validate-store-delivery.mjs index 54447c93..65ced9eb 100644 --- a/scripts/validate-store-delivery.mjs +++ b/scripts/validate-store-delivery.mjs @@ -17,7 +17,6 @@ export const validateStoreDelivery = ({ changedFiles, platform, androidTrack, - productionConfirmation, }) => { const version = STABLE_RELEASE_TAG.exec(releaseTag)?.[1]; if (!version) { @@ -50,16 +49,6 @@ export const validateStoreDelivery = ({ `Android versionCode must increase beyond ${previousVersionCode}; received ${currentVersionCode}.`, ); } - if ( - platform !== "ios" && - androidTrack === "production" && - productionConfirmation !== releaseTag - ) { - throw new Error( - `Production delivery requires --confirm-production ${releaseTag}.`, - ); - } - return { version, versionCode: currentVersionCode, @@ -74,12 +63,11 @@ const run = () => { releaseTag, previousTag, platform = "both", - androidTrack = "internal", - productionConfirmation = "", + androidTrack = "production", ] = process.argv.slice(2); if (!releaseTag || !previousTag) { console.error( - "Usage: node scripts/validate-store-delivery.mjs [platform] [android-track] [production-confirmation]", + "Usage: node scripts/validate-store-delivery.mjs [platform] [android-track]", ); process.exit(2); } @@ -105,7 +93,6 @@ const run = () => { changedFiles, platform, androidTrack, - productionConfirmation, }); process.stdout.write(`version=${result.version}\n`); diff --git a/scripts/validate-store-delivery.test.mjs b/scripts/validate-store-delivery.test.mjs index f2a9fe91..da4c7c18 100644 --- a/scripts/validate-store-delivery.test.mjs +++ b/scripts/validate-store-delivery.test.mjs @@ -9,8 +9,7 @@ const validInput = { previousVersionCode: 56, changedFiles: ["apps/mobile/src/screens/WorkspaceScreen.tsx"], platform: "both", - androidTrack: "internal", - productionConfirmation: "", + androidTrack: "production", }; describe("store delivery validation", () => { @@ -42,20 +41,4 @@ describe("store delivery validation", () => { validateStoreDelivery({ ...validInput, currentVersionCode: 56 }) ).toThrow("versionCode must increase"); }); - - test("requires an exact confirmation for production delivery", () => { - expect(() => - validateStoreDelivery({ - ...validInput, - androidTrack: "production", - }) - ).toThrow("--confirm-production v1.7.0"); - expect( - validateStoreDelivery({ - ...validInput, - androidTrack: "production", - productionConfirmation: "v1.7.0", - }).version, - ).toBe("1.7.0"); - }); });