1- name : ios-testflight- release
1+ name : ios-release
22
33on :
44 workflow_dispatch :
5+ inputs :
6+ destination :
7+ description : Where to publish the build and App Store assets
8+ required : true
9+ default : testflight
10+ type : choice
11+ options :
12+ - testflight
13+ - app-store-assets
14+ - app-store
15+ submit_for_review :
16+ description : Submit the App Store version for review after upload
17+ required : true
18+ default : false
19+ type : boolean
20+ screenshot_style :
21+ description : Screenshot presentation to upload
22+ required : true
23+ default : framed
24+ type : choice
25+ options :
26+ - raw
27+ - framed
528
629concurrency :
7- group : ios-testflight- release-${{ github.ref }}
30+ group : ios-release-${{ github.ref }}
831 cancel-in-progress : false
932
1033permissions :
1134 contents : read
1235
1336jobs :
14- testflight :
37+ release :
1538 runs-on : [self-hosted, macOS]
1639 timeout-minutes : 60
1740 defaults :
3255 - name : Install dependencies
3356 run : pnpm install --frozen-lockfile
3457
58+ - name : Validate App Store metadata and public links
59+ if : inputs.destination != 'testflight'
60+ working-directory : standalone/webapp
61+ run : pnpm appstore:metadata:validate
62+
3563 - name : Install Ruby gems (fastlane + cocoapods)
3664 working-directory : standalone/webapp
3765 run : |
@@ -53,15 +81,49 @@ jobs:
5381
5482 - name : Capacitor sync (iOS)
5583 working-directory : standalone/webapp
84+ # Bake the live-update signing public key into the app so the updater
85+ # strictly rejects any bundle it can't verify. Unset (e.g. forks) → the
86+ # feature is simply disabled, not insecure.
87+ env :
88+ CAPGO_PUBLIC_KEY : ${{ vars.CAPGO_PUBLIC_KEY }}
89+ # Fail the build if the signing key is missing rather than shipping
90+ # OTA with signature enforcement silently disabled.
91+ CAPGO_REQUIRE_SIGNING : " true"
5692 run : pnpm exec cap sync ios
5793
5894 - name : Resolve marketing version and build number
95+ if : inputs.destination != 'app-store-assets'
5996 run : |
6097 MARKETING_VERSION=$(node -p "require('./standalone/webapp/package.json').version")
6198 echo "MARKETING_VERSION=$MARKETING_VERSION" >> "$GITHUB_ENV"
6299 echo "BUILD_NUMBER=$(date +'%Y%m%d%H%M')" >> "$GITHUB_ENV"
63100
101+ - name : Prepare licensed Apple product bezels
102+ if : inputs.destination != 'testflight' && inputs.screenshot_style == 'framed'
103+ working-directory : standalone/webapp
104+ run : pnpm run appstore:screenshots:prepare-frames
105+
106+ - name : Capture iPhone and iPad App Store screenshots
107+ if : inputs.destination != 'testflight'
108+ working-directory : standalone/webapp
109+ env :
110+ APP_STORE_SCREENSHOT_STYLE : ${{ inputs.screenshot_style }}
111+ run : bundle exec fastlane screenshots
112+
113+ - name : Upload App Store screenshot review
114+ if : inputs.destination != 'testflight'
115+ uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
116+ with :
117+ name : ios-app-store-screenshot-review
118+ path : |
119+ standalone/webapp/fastlane/screenshots/
120+ standalone/webapp/fastlane/screenshots-framed/
121+ standalone/webapp/fastlane/screenshots-review.html
122+ if-no-files-found : error
123+ retention-days : 14
124+
64125 - name : Build iOS app (Fastlane)
126+ if : inputs.destination != 'app-store-assets'
65127 working-directory : standalone/webapp
66128 env :
67129 BUILD_NUMBER : ${{ env.BUILD_NUMBER }}
@@ -72,18 +134,57 @@ jobs:
72134 API_KEY_ID : ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY_ID }}
73135 API_KEY_ISSUER_ID : ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
74136 API_KEY_PASSWORD : ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_PASSWORD }}
137+ # The lane rebuilds and re-syncs the web bundle before archiving, so the
138+ # signing key must be present here too — otherwise the archived binary
139+ # would accept unsigned live updates.
140+ CAPGO_PUBLIC_KEY : ${{ vars.CAPGO_PUBLIC_KEY }}
141+ CAPGO_REQUIRE_SIGNING : " true"
75142 run : bundle exec fastlane build
76143
77144 - name : Upload to TestFlight (Fastlane)
145+ if : inputs.destination == 'testflight'
78146 working-directory : standalone/webapp
79147 env :
80148 API_KEY_ID : ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY_ID }}
81149 API_KEY_ISSUER_ID : ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
82150 API_KEY_PASSWORD : ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_PASSWORD }}
83151 run : bundle exec fastlane release
84152
153+ - name : Upload App Store metadata and screenshots (Fastlane)
154+ if : inputs.destination == 'app-store-assets'
155+ working-directory : standalone/webapp
156+ env :
157+ APP_STORE_SCREENSHOT_STYLE : ${{ inputs.screenshot_style }}
158+ API_KEY_ID : ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY_ID }}
159+ API_KEY_ISSUER_ID : ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
160+ API_KEY_PASSWORD : ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_PASSWORD }}
161+ # Optional: App Review contact. Unset secrets are skipped, leaving the
162+ # existing App Store Connect values untouched.
163+ APP_REVIEW_FIRST_NAME : ${{ secrets.IOS_APP_REVIEW_FIRST_NAME }}
164+ APP_REVIEW_LAST_NAME : ${{ secrets.IOS_APP_REVIEW_LAST_NAME }}
165+ APP_REVIEW_EMAIL : ${{ secrets.IOS_APP_REVIEW_EMAIL }}
166+ APP_REVIEW_PHONE : ${{ secrets.IOS_APP_REVIEW_PHONE }}
167+ run : bundle exec fastlane store_assets
168+
169+ - name : Upload App Store build, metadata, and screenshots (Fastlane)
170+ if : inputs.destination == 'app-store'
171+ working-directory : standalone/webapp
172+ env :
173+ APP_STORE_SCREENSHOT_STYLE : ${{ inputs.screenshot_style }}
174+ API_KEY_ID : ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY_ID }}
175+ API_KEY_ISSUER_ID : ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
176+ API_KEY_PASSWORD : ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_PASSWORD }}
177+ SUBMIT_FOR_REVIEW : ${{ inputs.submit_for_review }}
178+ # Optional: App Review contact. Unset secrets are skipped, leaving the
179+ # existing App Store Connect values untouched.
180+ APP_REVIEW_FIRST_NAME : ${{ secrets.IOS_APP_REVIEW_FIRST_NAME }}
181+ APP_REVIEW_LAST_NAME : ${{ secrets.IOS_APP_REVIEW_LAST_NAME }}
182+ APP_REVIEW_EMAIL : ${{ secrets.IOS_APP_REVIEW_EMAIL }}
183+ APP_REVIEW_PHONE : ${{ secrets.IOS_APP_REVIEW_PHONE }}
184+ run : bundle exec fastlane release_app_store
185+
85186 - name : Upload IPA artifact
86- if : always()
187+ if : always() && inputs.destination != 'app-store-assets'
87188 uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
88189 with :
89190 name : ios-ipa
0 commit comments