Skip to content

chore: bump version to 1.1.16 for pipeline testing (#63) #49

chore: bump version to 1.1.16 for pipeline testing (#63)

chore: bump version to 1.1.16 for pipeline testing (#63) #49

Workflow file for this run

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: 'public_beta'
type: choice
options:
- public_beta
- internal
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 (Public Beta)
needs: version-check
if: needs.version-check.outputs.should_release == 'true'
runs-on: macos-26
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_26.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 || 'public_beta' }}
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 'public_beta' for automatic releases (push to main)
# Can be set to 'internal' for manual workflow_dispatch if needed
fastlane beta channel:$RELEASE_CHANNEL
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
env:
RELEASE_CHANNEL: ${{ github.event.inputs.release_channel || 'public_beta' }}
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 || 'public_beta' }}
${{ (github.event.inputs.release_channel || 'public_beta') == '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 || 'public_beta' }}
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