Version bump to 1.0.20 #27
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: write | |
| jobs: | |
| quality: | |
| name: Lint and test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: "24" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run lint | |
| run: npm run lint | |
| - name: Run tests | |
| run: npm test | |
| build_ios: | |
| name: Build iOS | |
| runs-on: macos-26 | |
| needs: quality | |
| env: | |
| APP_VARIANT: production | |
| CYD_API_ENV: prod | |
| IOS_DISTRIBUTION: app_store | |
| APP_STORE_ANNUAL_PRODUCT_ID: premium_annual | |
| ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }} | |
| ASC_API_ISSUER_ID: ${{ secrets.ASC_API_ISSUER_ID }} | |
| ASC_API_PRIVATE_KEY_BASE64: ${{ secrets.ASC_API_PRIVATE_KEY_BASE64 }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| - name: Select Xcode 26 | |
| run: | | |
| # Prefer Xcode 26.3 because 26.4.1 currently crashes the Swift frontend | |
| # while compiling ExpoModulesCore during archive on GitHub's macOS 26 image. | |
| if [ -d /Applications/Xcode_26.3.app ]; then | |
| BEST=/Applications/Xcode_26.3.app | |
| elif [ -d /Applications/Xcode_26.3.0.app ]; then | |
| BEST=/Applications/Xcode_26.3.0.app | |
| else | |
| BEST="" | |
| fi | |
| # If 26.3 is unavailable, fall back to the latest stable (non-beta) | |
| # Xcode 26 on the runner. We resolve symlinks because some aliases | |
| # don't contain "beta" in their name but point to a beta install. | |
| for app in $(ls -d /Applications/Xcode_26*.app 2>/dev/null | sort -V); do | |
| if [ -n "$BEST" ]; then | |
| break | |
| fi | |
| REAL=$(readlink -f "$app" 2>/dev/null || realpath "$app") | |
| if basename "$REAL" | grep -qi 'beta'; then | |
| echo "Skipping $app (resolves to beta: $REAL)" | |
| continue | |
| fi | |
| BEST="$app" | |
| done | |
| if [ -z "$BEST" ]; then | |
| echo "::error::No non-beta Xcode 26 found on this runner." | |
| ls -ld /Applications/Xcode*.app | |
| exit 1 | |
| fi | |
| echo "Selecting $BEST" | |
| sudo xcode-select -s "$BEST/Contents/Developer" | |
| xcodebuild -version | |
| - name: Install iOS platform | |
| run: xcodebuild -downloadPlatform iOS | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: "24" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Generate native projects (skip pod install) | |
| run: npx expo prebuild --clean --no-install | |
| - name: Configure Xcode Node binary | |
| run: | | |
| echo "NODE_BINARY=$(command -v node)" > ios/.xcode.env.local | |
| - name: Cache CocoaPods | |
| uses: actions/cache@v5 | |
| with: | |
| path: ios/Pods | |
| key: pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }} | |
| restore-keys: | | |
| pods-${{ runner.os }}- | |
| - name: Install CocoaPods dependencies | |
| run: cd ios && pod install | |
| - name: Restore App Store Connect API private key | |
| run: python3 ./scripts/ci/restore-asc-private-key.py | |
| - name: Build iOS locally with Xcode | |
| run: | | |
| set -euo pipefail | |
| ARCHIVE_PATH="$RUNNER_TEMP/Cyd.xcarchive" | |
| EXPORT_DIR="$RUNNER_TEMP/ios-export" | |
| EXPORT_OPTS="$RUNNER_TEMP/ExportOptions.plist" | |
| cat > "$EXPORT_OPTS" <<'PLIST' | |
| <?xml version="1.0" encoding="UTF-8"?> | |
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
| <plist version="1.0"> | |
| <dict> | |
| <key>method</key> | |
| <string>app-store-connect</string> | |
| <key>signingStyle</key> | |
| <string>automatic</string> | |
| <key>teamID</key> | |
| <string>G762K6CH36</string> | |
| <key>stripSwiftSymbols</key> | |
| <true/> | |
| <key>uploadSymbols</key> | |
| <true/> | |
| <key>manageAppVersionAndBuildNumber</key> | |
| <true/> | |
| </dict> | |
| </plist> | |
| PLIST | |
| xcodebuild \ | |
| -workspace ios/Cyd.xcworkspace \ | |
| -scheme Cyd \ | |
| -configuration Release \ | |
| -destination 'generic/platform=iOS' \ | |
| -archivePath "$ARCHIVE_PATH" \ | |
| -allowProvisioningUpdates \ | |
| -authenticationKeyPath "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_API_KEY_ID}.p8" \ | |
| -authenticationKeyID "$ASC_API_KEY_ID" \ | |
| -authenticationKeyIssuerID "$ASC_API_ISSUER_ID" \ | |
| CODE_SIGN_IDENTITY=- \ | |
| AD_HOC_CODE_SIGNING_ALLOWED=YES \ | |
| archive | |
| xcodebuild \ | |
| -exportArchive \ | |
| -archivePath "$ARCHIVE_PATH" \ | |
| -exportPath "$EXPORT_DIR" \ | |
| -exportOptionsPlist "$EXPORT_OPTS" \ | |
| -allowProvisioningUpdates \ | |
| -authenticationKeyPath "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_API_KEY_ID}.p8" \ | |
| -authenticationKeyID "$ASC_API_KEY_ID" \ | |
| -authenticationKeyIssuerID "$ASC_API_ISSUER_ID" | |
| IOS_BUILD_FILE=$(ls -t "$EXPORT_DIR"/*.ipa | head -1) | |
| if [ -z "$IOS_BUILD_FILE" ]; then | |
| echo "No .ipa file found after local iOS build" | |
| exit 1 | |
| fi | |
| cp "$IOS_BUILD_FILE" cyd-mobile.ipa | |
| - name: Upload iOS artifact | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: ios-ipa-${{ github.ref_name }} | |
| path: cyd-mobile.ipa | |
| if-no-files-found: error | |
| build_ios_alt: | |
| name: Build iOS (alt store) | |
| runs-on: macos-26 | |
| needs: quality | |
| env: | |
| APP_VARIANT: production | |
| CYD_API_ENV: prod | |
| IOS_DISTRIBUTION: alt | |
| APP_STORE_ANNUAL_PRODUCT_ID: premium_annual | |
| ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }} | |
| ASC_API_ISSUER_ID: ${{ secrets.ASC_API_ISSUER_ID }} | |
| ASC_API_PRIVATE_KEY_BASE64: ${{ secrets.ASC_API_PRIVATE_KEY_BASE64 }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| - name: Select Xcode 26 | |
| run: | | |
| # Prefer Xcode 26.3 because 26.4.1 currently crashes the Swift frontend | |
| # while compiling ExpoModulesCore during archive on GitHub's macOS 26 image. | |
| if [ -d /Applications/Xcode_26.3.app ]; then | |
| BEST=/Applications/Xcode_26.3.app | |
| elif [ -d /Applications/Xcode_26.3.0.app ]; then | |
| BEST=/Applications/Xcode_26.3.0.app | |
| else | |
| BEST="" | |
| fi | |
| # If 26.3 is unavailable, fall back to the latest stable (non-beta) | |
| # Xcode 26 on the runner. We resolve symlinks because some aliases | |
| # don't contain "beta" in their name but point to a beta install. | |
| for app in $(ls -d /Applications/Xcode_26*.app 2>/dev/null | sort -V); do | |
| if [ -n "$BEST" ]; then | |
| break | |
| fi | |
| REAL=$(readlink -f "$app" 2>/dev/null || realpath "$app") | |
| if basename "$REAL" | grep -qi 'beta'; then | |
| echo "Skipping $app (resolves to beta: $REAL)" | |
| continue | |
| fi | |
| BEST="$app" | |
| done | |
| if [ -z "$BEST" ]; then | |
| echo "::error::No non-beta Xcode 26 found on this runner." | |
| ls -ld /Applications/Xcode*.app | |
| exit 1 | |
| fi | |
| echo "Selecting $BEST" | |
| sudo xcode-select -s "$BEST/Contents/Developer" | |
| xcodebuild -version | |
| - name: Install iOS platform | |
| run: xcodebuild -downloadPlatform iOS | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: "24" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Generate native projects (skip pod install) | |
| run: npx expo prebuild --clean --no-install | |
| - name: Configure Xcode Node binary | |
| run: | | |
| echo "NODE_BINARY=$(command -v node)" > ios/.xcode.env.local | |
| - name: Cache CocoaPods | |
| uses: actions/cache@v5 | |
| with: | |
| path: ios/Pods | |
| key: pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }} | |
| restore-keys: | | |
| pods-${{ runner.os }}- | |
| - name: Install CocoaPods dependencies | |
| run: cd ios && pod install | |
| - name: Restore App Store Connect API private key | |
| run: python3 ./scripts/ci/restore-asc-private-key.py | |
| - name: Build iOS alt store locally with Xcode | |
| run: | | |
| set -euo pipefail | |
| ARCHIVE_PATH="$RUNNER_TEMP/CydAlt.xcarchive" | |
| EXPORT_DIR="$RUNNER_TEMP/ios-alt-export" | |
| EXPORT_OPTS="$RUNNER_TEMP/ExportOptionsAlt.plist" | |
| cat > "$EXPORT_OPTS" <<'PLIST' | |
| <?xml version="1.0" encoding="UTF-8"?> | |
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
| <plist version="1.0"> | |
| <dict> | |
| <key>method</key> | |
| <string>app-store-connect</string> | |
| <key>signingStyle</key> | |
| <string>automatic</string> | |
| <key>teamID</key> | |
| <string>G762K6CH36</string> | |
| <key>stripSwiftSymbols</key> | |
| <true/> | |
| <key>uploadSymbols</key> | |
| <true/> | |
| <key>manageAppVersionAndBuildNumber</key> | |
| <true/> | |
| </dict> | |
| </plist> | |
| PLIST | |
| xcodebuild \ | |
| -workspace ios/Cyd.xcworkspace \ | |
| -scheme Cyd \ | |
| -configuration Release \ | |
| -destination 'generic/platform=iOS' \ | |
| -archivePath "$ARCHIVE_PATH" \ | |
| -allowProvisioningUpdates \ | |
| -authenticationKeyPath "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_API_KEY_ID}.p8" \ | |
| -authenticationKeyID "$ASC_API_KEY_ID" \ | |
| -authenticationKeyIssuerID "$ASC_API_ISSUER_ID" \ | |
| CODE_SIGN_IDENTITY=- \ | |
| AD_HOC_CODE_SIGNING_ALLOWED=YES \ | |
| archive | |
| xcodebuild \ | |
| -exportArchive \ | |
| -archivePath "$ARCHIVE_PATH" \ | |
| -exportPath "$EXPORT_DIR" \ | |
| -exportOptionsPlist "$EXPORT_OPTS" \ | |
| -allowProvisioningUpdates \ | |
| -authenticationKeyPath "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_API_KEY_ID}.p8" \ | |
| -authenticationKeyID "$ASC_API_KEY_ID" \ | |
| -authenticationKeyIssuerID "$ASC_API_ISSUER_ID" | |
| IOS_BUILD_FILE=$(ls -t "$EXPORT_DIR"/*.ipa | head -1) | |
| if [ -z "$IOS_BUILD_FILE" ]; then | |
| echo "No .ipa file found after alt iOS build" | |
| exit 1 | |
| fi | |
| cp "$IOS_BUILD_FILE" cyd-mobile-alt.ipa | |
| - name: Upload iOS alt artifact | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: ios-alt-ipa-${{ github.ref_name }} | |
| path: cyd-mobile-alt.ipa | |
| if-no-files-found: error | |
| submit_ios: | |
| name: Submit iOS to App Store | |
| runs-on: macos-latest | |
| needs: [build_ios, build_android] | |
| env: | |
| ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }} | |
| ASC_API_ISSUER_ID: ${{ secrets.ASC_API_ISSUER_ID }} | |
| ASC_API_PRIVATE_KEY_BASE64: ${{ secrets.ASC_API_PRIVATE_KEY_BASE64 }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| - name: Download iOS artifact | |
| uses: actions/download-artifact@v5 | |
| with: | |
| name: ios-ipa-${{ github.ref_name }} | |
| path: dist/ios | |
| - name: Restore App Store Connect API private key | |
| run: python3 ./scripts/ci/restore-asc-private-key.py | |
| - name: Submit iOS build | |
| run: ./scripts/ci/submit-ios.sh dist/ios/cyd-mobile.ipa | |
| submit_ios_alt: | |
| name: Submit iOS (alt store) to App Store | |
| runs-on: macos-latest | |
| needs: [build_ios_alt, build_android] | |
| env: | |
| ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }} | |
| ASC_API_ISSUER_ID: ${{ secrets.ASC_API_ISSUER_ID }} | |
| ASC_API_PRIVATE_KEY_BASE64: ${{ secrets.ASC_API_PRIVATE_KEY_BASE64 }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| - name: Download iOS alt artifact | |
| uses: actions/download-artifact@v5 | |
| with: | |
| name: ios-alt-ipa-${{ github.ref_name }} | |
| path: dist/ios-alt | |
| - name: Restore App Store Connect API private key | |
| run: python3 ./scripts/ci/restore-asc-private-key.py | |
| - name: Submit iOS alt build | |
| run: ./scripts/ci/submit-ios.sh dist/ios-alt/cyd-mobile-alt.ipa | |
| build_android: | |
| name: Build Android | |
| runs-on: ubuntu-latest-android-builder | |
| needs: quality | |
| env: | |
| APP_VARIANT: production | |
| CYD_API_ENV: prod | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: "24" | |
| cache: "npm" | |
| - name: Setup Java | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: "17" | |
| - name: Setup Android SDK | |
| uses: android-actions/setup-android@v3 | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Generate native iOS/Android projects | |
| run: npm run prebuild | |
| - name: Decode upload keystore | |
| run: | | |
| echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > android/app/upload.jks | |
| - name: Configure release signing | |
| run: | | |
| python3 ./scripts/ci/configure-android-signing.py | |
| echo "--- Signing config in build.gradle ---" | |
| grep -A5 'signingConfigs' android/app/build.gradle | head -25 | |
| - name: Tune Gradle for CI runner | |
| run: | | |
| cat >> android/gradle.properties <<EOF | |
| # Upload keystore credentials | |
| UPLOAD_STORE_PASSWORD=$ANDROID_KEYSTORE_PASSWORD | |
| UPLOAD_KEY_ALIAS=$ANDROID_KEY_ALIAS | |
| UPLOAD_KEY_PASSWORD=$ANDROID_KEY_PASSWORD | |
| EOF | |
| cat >> android/gradle.properties <<'EOF' | |
| # CI overrides for 8-core / 32GB runner | |
| org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 | |
| org.gradle.workers.max=8 | |
| org.gradle.caching=true | |
| org.gradle.daemon=false | |
| EOF | |
| - name: Cache Gradle dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: gradle-${{ runner.os }}-${{ hashFiles('android/gradle/wrapper/gradle-wrapper.properties', 'android/build.gradle', 'android/app/build.gradle') }} | |
| restore-keys: | | |
| gradle-${{ runner.os }}- | |
| - name: Build Android locally with Gradle | |
| run: | | |
| export ANDROID_HOME="$ANDROID_SDK_ROOT" | |
| cd android | |
| chmod +x ./gradlew | |
| ./gradlew bundleRelease assembleRelease --build-cache --parallel --max-workers=8 | |
| cd .. | |
| ANDROID_BUNDLE_FILE=android/app/build/outputs/bundle/release/app-release.aab | |
| ANDROID_APK_FILE=android/app/build/outputs/apk/release/app-release.apk | |
| if [ ! -f "$ANDROID_BUNDLE_FILE" ]; then | |
| echo "No .aab file found after local Android build" | |
| exit 1 | |
| fi | |
| if [ ! -f "$ANDROID_APK_FILE" ]; then | |
| echo "No .apk file found after local Android build" | |
| exit 1 | |
| fi | |
| cp "$ANDROID_BUNDLE_FILE" cyd-mobile.aab | |
| cp "$ANDROID_APK_FILE" cyd-mobile.apk | |
| - name: Upload Android artifact | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: android-aab-${{ github.ref_name }} | |
| path: cyd-mobile.aab | |
| if-no-files-found: error | |
| - name: Upload Android APK artifact | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: android-apk-${{ github.ref_name }} | |
| path: cyd-mobile.apk | |
| if-no-files-found: error | |
| upload_release_assets: | |
| name: Upload APK to GitHub Release | |
| runs-on: ubuntu-latest | |
| needs: [build_android] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| - name: Download Android APK artifact | |
| uses: actions/download-artifact@v5 | |
| with: | |
| name: android-apk-${{ github.ref_name }} | |
| path: dist/android | |
| - name: Upload release assets | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: | | |
| dist/android/cyd-mobile.apk | |
| fail_on_unmatched_files: true | |
| submit_android: | |
| name: Submit Android to Google Play | |
| runs-on: ubuntu-latest | |
| needs: [build_ios, build_android] | |
| env: | |
| GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64 }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: "24" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Download Android artifact | |
| uses: actions/download-artifact@v5 | |
| with: | |
| name: android-aab-${{ github.ref_name }} | |
| path: dist/android | |
| - name: Restore Google Play service account key | |
| run: python3 ./scripts/ci/restore-google-play-service-account.py | |
| - name: Submit Android build | |
| run: node ./scripts/ci/submit-android.js dist/android/cyd-mobile.aab |