ci: add release dry-run workflow and release-mode compile checks
Add a weekly + manual release-dryrun workflow that exercises the full store pipeline without publishing: Android validates the AAB against Google Play (validate_only), iOS builds a fully signed archive via match (readonly) and skips the App Store upload. Both Fastlane lanes gain a dry_run option that reuses the real release path so the dry-run stays faithful to it. Also add release-mode compile checks (flutter build apk/ios --release) to CI and PR checks to catch release-only breakage, pinning the JDK to match the release toolchain. Trim ci.yml from a redundant 3-OS matrix to two purpose-built jobs (analyze/test once on Linux, iOS build on macOS). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
17bbb20b94
commit
7f4abdefea
5 changed files with 175 additions and 11 deletions
34
.github/workflows/ci.yml
vendored
34
.github/workflows/ci.yml
vendored
|
|
@ -3,14 +3,20 @@ on:
|
|||
push:
|
||||
branches: [main]
|
||||
jobs:
|
||||
test:
|
||||
name: Check and test on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
# Linux runner: analyze + test once (both are OS-independent) and compile a
|
||||
# release APK to catch release-only breakage. Debug-signed via the build.gradle
|
||||
# fallback (ALLOW_DEBUG_SIGNED_RELEASE) — no signing secrets needed.
|
||||
android:
|
||||
name: Analyze, test, and build Android
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
# Match the JDK the release workflows build with, so this compile check
|
||||
# faithfully catches what would break a real release.
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: 3.44.1
|
||||
|
|
@ -19,3 +25,19 @@ jobs:
|
|||
- run: flutter pub get
|
||||
- run: flutter analyze
|
||||
- run: flutter test
|
||||
- run: flutter build apk --release
|
||||
env:
|
||||
ALLOW_DEBUG_SIGNED_RELEASE: '1'
|
||||
# macOS runner: release-compile iOS (no codesign, no secrets) — the only thing
|
||||
# that must run on macOS. analyze/test already covered on the Linux job.
|
||||
ios:
|
||||
name: Build iOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: 3.44.1
|
||||
cache: true
|
||||
- run: flutter pub get
|
||||
- run: flutter build ios --release --no-codesign
|
||||
|
|
|
|||
13
.github/workflows/pr-check.yml
vendored
13
.github/workflows/pr-check.yml
vendored
|
|
@ -8,6 +8,12 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
# Match the JDK the release workflows build with, so the release-mode APK
|
||||
# check below faithfully catches what would break a real release.
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: 3.44.1
|
||||
|
|
@ -16,3 +22,10 @@ jobs:
|
|||
- run: flutter pub get
|
||||
- run: flutter analyze
|
||||
- run: flutter test
|
||||
# Release-mode compile check (no signing secrets needed — Android falls back
|
||||
# to debug signing via ALLOW_DEBUG_SIGNED_RELEASE). Catches release-only
|
||||
# breakage that debug builds and tests miss (AOT compile, icon tree-shaking,
|
||||
# plugin/Gradle release incompatibility).
|
||||
- run: flutter build apk --release
|
||||
env:
|
||||
ALLOW_DEBUG_SIGNED_RELEASE: '1'
|
||||
|
|
|
|||
118
.github/workflows/release-dryrun.yml
vendored
Normal file
118
.github/workflows/release-dryrun.yml
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# Dry-runs the full store release pipeline without publishing, to catch
|
||||
# credential, signing, dependency, and store-validation breakage before an
|
||||
# actual release. Mirrors store-release.yml but stops short of publishing:
|
||||
# Android validates the AAB against Google Play (validate_only); iOS builds a
|
||||
# fully signed archive via match (readonly) and skips the App Store upload.
|
||||
name: Release dry-run
|
||||
on:
|
||||
# Run on demand before cutting a release.
|
||||
workflow_dispatch:
|
||||
# Weekly heartbeat so cert expiry, secret rotation, and dependency drift
|
||||
# surface on their own between releases.
|
||||
schedule:
|
||||
- cron: '0 9 * * 1' # Mondays 09:00 UTC
|
||||
# Setup environment variables for use in all jobs.
|
||||
env:
|
||||
# Gemfile path used for caching dependencies.
|
||||
BUNDLE_GEMFILE: ${{ github.workspace }}/android/Gemfile
|
||||
CLIENT_ID: ${{ secrets.CLIENT_ID }}
|
||||
SECRET: ${{ secrets.SECRET }}
|
||||
LC_ALL: en_US.UTF-8
|
||||
LANG: en_US.UTF-8
|
||||
jobs:
|
||||
android:
|
||||
name: Dry-run Android release
|
||||
runs-on: ubuntu-latest
|
||||
environment: release-android
|
||||
env:
|
||||
UPLOAD_KEYSTORE: ${{ secrets.UPLOAD_KEYSTORE }}
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
KEY_FILE_PATH: android/key.properties
|
||||
PLAY_STORE_CREDENTIALS: ${{ secrets.PLAY_STORE_CREDENTIALS }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Frosty repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Install Ruby and install/cache dependencies with bundler in order to use fastlane.
|
||||
- name: Set up Ruby and Bundler
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.2
|
||||
bundler-cache: true
|
||||
|
||||
# Setup Java so that we can build the appbundle.
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
# Setup and cache Flutter to install packages and build.
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: 3.44.1
|
||||
cache: true
|
||||
|
||||
# Decode and create the upload-keystore.jks file, used for signing the app.
|
||||
- name: Decode and create upload-keystore.jks
|
||||
run: echo $UPLOAD_KEYSTORE | base64 --decode > $GITHUB_WORKSPACE/android/upload-keystore.jks
|
||||
|
||||
# Create the key.properties file, used for signing the app.
|
||||
- name: Create key.properties
|
||||
run: |
|
||||
echo storePassword=$STORE_PASSWORD >> $KEY_FILE_PATH
|
||||
echo keyPassword=$KEY_PASSWORD >> $KEY_FILE_PATH
|
||||
echo keyAlias=upload >> $KEY_FILE_PATH
|
||||
echo storeFile=$GITHUB_WORKSPACE/android/upload-keystore.jks >> $KEY_FILE_PATH
|
||||
|
||||
# Build the Android app bundle (AAB) and pass the environment variables.
|
||||
- name: Build the Android AAB
|
||||
run: flutter build appbundle --dart-define CLIENT_ID=$CLIENT_ID --dart-define SECRET=$SECRET
|
||||
|
||||
# Validate the AAB and credentials against Google Play without publishing.
|
||||
- name: Validate release with fastlane (no publish)
|
||||
run: cd android && bundle exec fastlane deploy dry_run:true
|
||||
ios:
|
||||
name: Dry-run iOS release
|
||||
runs-on: macos-latest
|
||||
environment: release-ios
|
||||
env:
|
||||
APP_STORE_KEY_ID: ${{ secrets.APP_STORE_KEY_ID }}
|
||||
APP_STORE_ISSUER_ID: ${{ secrets.APP_STORE_ISSUER_ID }}
|
||||
APP_STORE_CREDENTIALS: ${{ secrets.APP_STORE_CREDENTIALS }}
|
||||
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
steps:
|
||||
- name: Checkout Frosty repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Always pick the newest production Xcode on the runner
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: latest-stable
|
||||
|
||||
# Install Ruby and install/cache dependencies with bundler in order to use fastlane.
|
||||
- name: Set up Ruby and Bundler
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.2
|
||||
bundler-cache: true
|
||||
|
||||
# Setup and cache Flutter to install packages and build.
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: 3.44.1
|
||||
cache: true
|
||||
|
||||
# Setup the iOS project with environment variables without signing (building and signing will happen in fastlane).
|
||||
- name: Config the iOS build
|
||||
run: flutter build ios --no-codesign --config-only --dart-define CLIENT_ID=$CLIENT_ID --dart-define SECRET=$SECRET
|
||||
|
||||
# Authenticate ASC API key, resolve signing via match (readonly), and build a
|
||||
# fully signed archive without uploading to the App Store.
|
||||
- name: Sign and build with fastlane (no publish)
|
||||
run: cd ios && bundle exec fastlane release dry_run:true
|
||||
|
|
@ -17,11 +17,14 @@ default_platform(:android)
|
|||
|
||||
platform :android do
|
||||
desc "Deploy a new version to the Google Play"
|
||||
lane :deploy do
|
||||
lane :deploy do |options|
|
||||
# Upload to the play store ussing the provided credentials and AAB from flutter build.
|
||||
# With dry_run:true (release dry-run workflow), validate_only authenticates and
|
||||
# validates the AAB against Google Play without committing the release.
|
||||
upload_to_play_store(
|
||||
json_key_data: Base64.decode64(ENV["PLAY_STORE_CREDENTIALS"]),
|
||||
aab: "../build/app/outputs/bundle/release/app-release.aab"
|
||||
aab: "../build/app/outputs/bundle/release/app-release.aab",
|
||||
validate_only: options[:dry_run] || false
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ default_platform(:ios)
|
|||
|
||||
platform :ios do
|
||||
desc 'Push a new release build to the App Store'
|
||||
lane :release do
|
||||
lane :release do |options|
|
||||
dry_run = options[:dry_run] || false
|
||||
|
||||
# Setup for CI. Uses a keychain to prevent the CI from hanging due to password prompts.
|
||||
setup_ci
|
||||
|
||||
|
|
@ -29,8 +31,9 @@ platform :ios do
|
|||
is_key_content_base64: true
|
||||
)
|
||||
|
||||
# Manage code signing (with match).
|
||||
sync_code_signing
|
||||
# Manage code signing (with match). readonly on a dry run so we only verify the
|
||||
# existing cert/profile resolve and never regenerate or mutate the certificates repo.
|
||||
sync_code_signing(readonly: dry_run)
|
||||
|
||||
# Automatic signing is used during development, but fastlane needs to use manual signing.
|
||||
update_code_signing_settings(
|
||||
|
|
@ -45,6 +48,11 @@ platform :ios do
|
|||
output_directory: '../build/ios/archive'
|
||||
)
|
||||
|
||||
# On a dry run, stop here: the signed archive above exercises every step that
|
||||
# actually fails (ASC key, match certs/profiles, signing, release build). The
|
||||
# App Store upload itself is the only remaining unverified step.
|
||||
next if dry_run
|
||||
|
||||
# Upload the app to the App Store.
|
||||
upload_to_app_store(
|
||||
submit_for_review: true,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue