chore: bump version to 1.1.5 #44
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: iOS Release Pipeline | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - 'V2er/Config/Version.xcconfig' | |
| workflow_dispatch: | |
| inputs: | |
| force_release: | |
| description: 'Force release even if version unchanged' | |
| required: false | |
| default: false | |
| type: boolean | |
| release_channel: | |
| description: 'TestFlight release channel' | |
| required: false | |
| default: 'internal' | |
| type: choice | |
| options: | |
| - internal | |
| - public_beta | |
| env: | |
| DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer | |
| jobs: | |
| version-check: | |
| name: Check Version and Create Tag | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_release: ${{ steps.check.outputs.should_release }} | |
| new_tag: ${{ steps.check.outputs.new_tag }} | |
| version: ${{ steps.check.outputs.version }} | |
| build: ${{ steps.check.outputs.build }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Check version and create tag if needed | |
| id: check | |
| run: | | |
| # Get current version from Version.xcconfig (works on Linux) | |
| CURRENT_VERSION=$(grep '^MARKETING_VERSION = ' V2er/Config/Version.xcconfig | sed 's/.*MARKETING_VERSION = //' | xargs) | |
| CURRENT_BUILD=$(grep '^CURRENT_PROJECT_VERSION = ' V2er/Config/Version.xcconfig | sed 's/.*CURRENT_PROJECT_VERSION = //' | xargs) | |
| echo "Current version: $CURRENT_VERSION (build $CURRENT_BUILD)" | |
| # Check if tag already exists | |
| TAG_NAME="v$CURRENT_VERSION" | |
| if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then | |
| if [[ "${{ github.event.inputs.force_release }}" == "true" ]]; then | |
| echo "Tag $TAG_NAME exists but force_release is true" | |
| # Delete existing tag for force release | |
| git tag -d "$TAG_NAME" 2>/dev/null || true | |
| git push origin --delete "$TAG_NAME" 2>/dev/null || true | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "Tag $TAG_NAME already exists, skipping release" | |
| echo "should_release=false" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| echo "Tag $TAG_NAME does not exist, will create it" | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| fi | |
| echo "new_tag=$TAG_NAME" >> $GITHUB_OUTPUT | |
| echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT | |
| echo "build=$CURRENT_BUILD" >> $GITHUB_OUTPUT | |
| - name: Create and push tag | |
| if: steps.check.outputs.should_release == 'true' | |
| run: | | |
| git config --local user.email "[email protected]" | |
| git config --local user.name "GitHub Action" | |
| TAG_NAME="${{ steps.check.outputs.new_tag }}" | |
| VERSION="${{ steps.check.outputs.version }}" | |
| BUILD="${{ steps.check.outputs.build }}" | |
| # Create annotated tag | |
| git tag -a "$TAG_NAME" -m "Release version $VERSION (build $BUILD)" | |
| # Push tag | |
| git push origin "$TAG_NAME" | |
| echo "✅ Successfully created tag: $TAG_NAME" | |
| build-and-release: | |
| name: Build and Release to TestFlight (Internal Testing) | |
| needs: version-check | |
| if: needs.version-check.outputs.should_release == 'true' | |
| runs-on: macos-latest | |
| steps: | |
| - name: Checkout repository at tag | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| fetch-depth: 0 | |
| ref: ${{ needs.version-check.outputs.new_tag }} | |
| - name: Select Xcode version | |
| run: sudo xcode-select -s /Applications/Xcode_16.0.app/Contents/Developer | |
| - name: Setup Ruby | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: '3.2' | |
| bundler-cache: false | |
| - name: Install Fastlane | |
| run: | | |
| gem install fastlane -v 2.228.0 | |
| gem install xcpretty | |
| - name: Setup SSH for Match repository | |
| uses: webfactory/[email protected] | |
| with: | |
| ssh-private-key: ${{ secrets.DEPLOY_KEY }} | |
| - name: Setup Keychain for Code Signing | |
| run: | | |
| # Create a temporary keychain for the build | |
| KEYCHAIN_NAME="build-temp.keychain" | |
| KEYCHAIN_PASSWORD="temp-password-$(date +%s)" | |
| # Create keychain | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" | |
| # Set it as default | |
| security default-keychain -s "$KEYCHAIN_NAME" | |
| # Unlock the keychain | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" | |
| # Set keychain timeout to 1 hour (3600 seconds) to prevent locking during build | |
| security set-keychain-settings -t 3600 -l "$KEYCHAIN_NAME" | |
| # Add keychain to search list | |
| security list-keychains -d user -s "$KEYCHAIN_NAME" $(security list-keychains -d user | sed 's/"//g') | |
| echo "MATCH_KEYCHAIN_NAME=$KEYCHAIN_NAME" >> $GITHUB_ENV | |
| echo "MATCH_KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV | |
| - name: Create App Store Connect API Key | |
| env: | |
| APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} | |
| APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} | |
| APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }} | |
| run: | | |
| # Debug: Check if environment variables are set | |
| if [ -z "$APP_STORE_CONNECT_KEY_ID" ]; then | |
| echo "ERROR: APP_STORE_CONNECT_KEY_ID is not set" | |
| exit 1 | |
| fi | |
| echo "✅ APP_STORE_CONNECT_KEY_ID is set" | |
| if [ -z "$APP_STORE_CONNECT_ISSUER_ID" ]; then | |
| echo "ERROR: APP_STORE_CONNECT_ISSUER_ID is not set" | |
| exit 1 | |
| fi | |
| echo "✅ APP_STORE_CONNECT_ISSUER_ID is set" | |
| if [ -z "$APP_STORE_CONNECT_API_KEY_BASE64" ]; then | |
| echo "ERROR: APP_STORE_CONNECT_API_KEY_BASE64 is not set" | |
| exit 1 | |
| fi | |
| echo "✅ APP_STORE_CONNECT_API_KEY_BASE64 is set" | |
| # Debug: Check the content characteristics | |
| echo "Debug: Checking base64 string characteristics..." | |
| echo "Length: $(echo -n "$APP_STORE_CONNECT_API_KEY_BASE64" | wc -c)" | |
| echo "First 10 chars: $(echo -n "$APP_STORE_CONNECT_API_KEY_BASE64" | head -c 10)..." | |
| echo "Last 10 chars: ...$(echo -n "$APP_STORE_CONNECT_API_KEY_BASE64" | tail -c 10)" | |
| # Check if it contains valid base64 characters | |
| if echo "$APP_STORE_CONNECT_API_KEY_BASE64" | grep -qE '^[A-Za-z0-9+/]*={0,2}$'; then | |
| echo "✅ String contains valid base64 characters" | |
| else | |
| echo "⚠️ String may contain invalid base64 characters" | |
| # Show which characters are invalid | |
| echo "$APP_STORE_CONNECT_API_KEY_BASE64" | sed 's/[A-Za-z0-9+/=]//g' | od -c | |
| # Check for URL encoding | |
| if echo "$APP_STORE_CONNECT_API_KEY_BASE64" | grep -q '%'; then | |
| echo "🔍 Detected URL encoding (contains % character)" | |
| echo " The secret appears to be URL-encoded. This is incorrect." | |
| fi | |
| fi | |
| # Create directory for API key | |
| mkdir -p ~/.appstoreconnect/private_keys | |
| # Decode base64 with better error handling | |
| # Try different approaches to handle potential formatting issues | |
| KEY_PATH=~/.appstoreconnect/private_keys/AuthKey_${APP_STORE_CONNECT_KEY_ID}.p8 | |
| # Try to decode the base64 string | |
| DECODE_SUCCESS=false | |
| # Method 1: Direct echo and decode | |
| echo "Trying method 1: base64 -d..." | |
| if echo "$APP_STORE_CONNECT_API_KEY_BASE64" | base64 -d > "$KEY_PATH" 2>/dev/null; then | |
| echo "✅ Successfully decoded API key using base64 -d" | |
| DECODE_SUCCESS=true | |
| fi | |
| # Method 2: Try with --decode flag (macOS) | |
| if [ "$DECODE_SUCCESS" = false ]; then | |
| echo "Trying method 2: base64 --decode..." | |
| if echo "$APP_STORE_CONNECT_API_KEY_BASE64" | base64 --decode > "$KEY_PATH" 2>/dev/null; then | |
| echo "✅ Successfully decoded API key using base64 --decode" | |
| DECODE_SUCCESS=true | |
| fi | |
| fi | |
| # Method 3: Remove potential whitespace/newlines and try again | |
| if [ "$DECODE_SUCCESS" = false ]; then | |
| echo "Trying method 3: removing whitespace first..." | |
| if echo "$APP_STORE_CONNECT_API_KEY_BASE64" | tr -d '\n\r ' | base64 -d > "$KEY_PATH" 2>/dev/null; then | |
| echo "✅ Successfully decoded API key after removing whitespace" | |
| DECODE_SUCCESS=true | |
| fi | |
| fi | |
| # Method 4: Try assuming it's not base64 encoded at all (raw .p8 content) | |
| if [ "$DECODE_SUCCESS" = false ]; then | |
| echo "Trying method 4: treating as raw .p8 content..." | |
| if echo "$APP_STORE_CONNECT_API_KEY_BASE64" > "$KEY_PATH" 2>/dev/null; then | |
| # Check if it looks like a valid .p8 file (should start with -----BEGIN PRIVATE KEY-----) | |
| if grep -q "BEGIN PRIVATE KEY" "$KEY_PATH"; then | |
| echo "✅ Secret appears to be raw .p8 content, not base64 encoded" | |
| DECODE_SUCCESS=true | |
| else | |
| rm -f "$KEY_PATH" | |
| fi | |
| fi | |
| fi | |
| if [ "$DECODE_SUCCESS" = false ]; then | |
| echo "ERROR: Failed to decode APP_STORE_CONNECT_API_KEY_BASE64" | |
| echo "The secret might be:" | |
| echo "1. Empty or containing only whitespace" | |
| echo "2. Incorrectly base64 encoded" | |
| echo "3. Already in .p8 format (not base64)" | |
| echo "" | |
| echo "To fix this, re-create the secret with:" | |
| echo " cat AuthKey_XXXXXX.p8 | base64 | tr -d '\\n' > base64_key.txt" | |
| echo "Then copy the contents of base64_key.txt to the secret" | |
| exit 1 | |
| fi | |
| # Verify the file was created and has content | |
| if [ ! -f "$KEY_PATH" ]; then | |
| echo "ERROR: API key file was not created" | |
| exit 1 | |
| fi | |
| if [ ! -s "$KEY_PATH" ]; then | |
| echo "ERROR: API key file is empty" | |
| exit 1 | |
| fi | |
| # Set proper permissions | |
| chmod 600 "$KEY_PATH" | |
| echo "✅ API key file created successfully at $KEY_PATH" | |
| # Set environment variables for Fastlane | |
| echo "APP_STORE_CONNECT_API_KEY_KEY_ID=$APP_STORE_CONNECT_KEY_ID" >> $GITHUB_ENV | |
| echo "APP_STORE_CONNECT_API_KEY_ISSUER_ID=$APP_STORE_CONNECT_ISSUER_ID" >> $GITHUB_ENV | |
| echo "APP_STORE_CONNECT_API_KEY_KEY=$KEY_PATH" >> $GITHUB_ENV | |
| - name: Run Fastlane Match | |
| env: | |
| MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} | |
| MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} | |
| TEAM_ID: ${{ secrets.TEAM_ID }} | |
| run: | | |
| fastlane match appstore --readonly | |
| - name: Build and Upload to TestFlight | |
| timeout-minutes: 60 | |
| env: | |
| MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} | |
| MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} | |
| TEAM_ID: ${{ secrets.TEAM_ID }} | |
| RELEASE_CHANNEL: ${{ github.event.inputs.release_channel || 'internal' }} | |
| run: | | |
| # Enable verbose output for debugging | |
| export FASTLANE_VERBOSE=true | |
| # Ensure we're using the correct locale for Fastlane | |
| export LC_ALL=en_US.UTF-8 | |
| export LANG=en_US.UTF-8 | |
| # Run the beta lane with release channel parameter | |
| # Default to 'internal' for automatic releases (push to main) | |
| # Can be set to 'public_beta' for manual workflow_dispatch | |
| fastlane beta channel:$RELEASE_CHANNEL | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v1 | |
| env: | |
| RELEASE_CHANNEL: ${{ github.event.inputs.release_channel || 'internal' }} | |
| with: | |
| tag_name: ${{ needs.version-check.outputs.new_tag }} | |
| name: Release ${{ needs.version-check.outputs.version }} | |
| body: | | |
| ## 🚀 Version ${{ needs.version-check.outputs.version }} | |
| Build: ${{ needs.version-check.outputs.build }} | |
| ### TestFlight Distribution | |
| **Channel**: ${{ github.event.inputs.release_channel || 'internal' }} | |
| ${{ github.event.inputs.release_channel == 'public_beta' && '📧 **Public Beta**: This build has been submitted to TestFlight for public beta testing. External testers will receive email notifications when the build is available after beta review approval.' || '👥 **Internal Testing**: This build is available to internal testers immediately after processing. No beta review required.' }} | |
| ### What's New | |
| - See [commit history](https://github.com/${{ github.repository }}/commits/${{ needs.version-check.outputs.new_tag }}) for changes | |
| --- | |
| *This release was automatically created by GitHub Actions* | |
| draft: false | |
| prerelease: false | |
| - name: Post release notification | |
| if: success() | |
| env: | |
| RELEASE_CHANNEL: ${{ github.event.inputs.release_channel || 'internal' }} | |
| run: | | |
| CHANNEL_DISPLAY=$([ "$RELEASE_CHANNEL" = "public_beta" ] && echo "Public Beta" || echo "Internal Testing") | |
| echo "✅ Successfully released version ${{ needs.version-check.outputs.version }} to TestFlight $CHANNEL_DISPLAY!" | |
| echo "🏷️ Tag: ${{ needs.version-check.outputs.new_tag }}" | |
| echo "🔢 Build: ${{ needs.version-check.outputs.build }}" | |
| echo "📦 Channel: $RELEASE_CHANNEL" | |
| if [ "$RELEASE_CHANNEL" = "public_beta" ]; then | |
| echo "📧 External testers will be notified after beta review approval" | |
| else | |
| echo "👥 Internal testers can access the build immediately after processing" | |
| fi |