Skip to content

TestFlight

TestFlight #36

Workflow file for this run

name: TestFlight
on:
workflow_dispatch:
env:
PROJECT_PATH: DialectListener.xcodeproj
SCHEME: DialectListener
CONFIGURATION: Release
APP_BUNDLE_ID: ${{ vars.APP_BUNDLE_ID || 'com.dustland.DialectListener' }}
MARKETING_VERSION: '1.0.12'
jobs:
build-and-upload:
name: Build and upload to TestFlight
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode_26.3.app
- name: Validate required secrets
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
ASC_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
ASC_PRIVATE_KEY: ${{ secrets.APP_STORE_CONNECT_API_PRIVATE_KEY }}
IOS_CERTIFICATE_BASE64: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_BASE64 }}
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_PASSWORD }}
IOS_PROFILE_BASE64: ${{ secrets.IOS_APPSTORE_PROFILE_BASE64 }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
run: |
set -euo pipefail
missing=()
[ -n "$APPLE_TEAM_ID" ] || missing+=("APPLE_TEAM_ID")
[ -n "$ASC_KEY_ID" ] || missing+=("APP_STORE_CONNECT_API_KEY_ID")
[ -n "$ASC_ISSUER_ID" ] || missing+=("APP_STORE_CONNECT_API_ISSUER_ID")
[ -n "$ASC_PRIVATE_KEY" ] || missing+=("APP_STORE_CONNECT_API_PRIVATE_KEY")
[ -n "$IOS_CERTIFICATE_BASE64" ] || missing+=("IOS_DISTRIBUTION_CERTIFICATE_BASE64")
[ -n "$IOS_CERTIFICATE_PASSWORD" ] || missing+=("IOS_DISTRIBUTION_CERTIFICATE_PASSWORD")
[ -n "$IOS_PROFILE_BASE64" ] || missing+=("IOS_APPSTORE_PROFILE_BASE64")
[ -n "$OPENROUTER_API_KEY" ] || missing+=("OPENROUTER_API_KEY")
if [ "${#missing[@]}" -gt 0 ]; then
printf 'Missing required GitHub Actions secrets:\n'
printf ' - %s\n' "${missing[@]}"
exit 1
fi
- name: Write App Store Connect API key
env:
ASC_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
ASC_PRIVATE_KEY: ${{ secrets.APP_STORE_CONNECT_API_PRIVATE_KEY }}
run: |
mkdir -p "$HOME/.appstoreconnect/private_keys"
mkdir -p "$RUNNER_TEMP/private_keys"
printf '%s' "$ASC_PRIVATE_KEY" > "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8"
cp "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8" "$RUNNER_TEMP/private_keys/AuthKey_${ASC_KEY_ID}.p8"
chmod 600 "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8"
chmod 600 "$RUNNER_TEMP/private_keys/AuthKey_${ASC_KEY_ID}.p8"
- name: Install signing certificate and provisioning profile
env:
IOS_CERTIFICATE_BASE64: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_BASE64 }}
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_PASSWORD }}
IOS_PROFILE_BASE64: ${{ secrets.IOS_APPSTORE_PROFILE_BASE64 }}
run: |
set -euo pipefail
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
KEYCHAIN_PASSWORD="$(uuidgen)"
CERTIFICATE_PATH="$RUNNER_TEMP/distribution.p12"
PROFILE_PATH="$RUNNER_TEMP/Dialect_Listener_App_Store.mobileprovision"
printf '%s' "$IOS_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH"
printf '%s' "$IOS_PROFILE_BASE64" | base64 --decode > "$PROFILE_PATH"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERTIFICATE_PATH" -P "$IOS_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security list-keychain -d user -s "$KEYCHAIN_PATH"
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
PROFILE_UUID="$(/usr/libexec/PlistBuddy -c 'Print :UUID' /dev/stdin <<< "$(security cms -D -i "$PROFILE_PATH")")"
cp "$PROFILE_PATH" "$HOME/Library/MobileDevice/Provisioning Profiles/$PROFILE_UUID.mobileprovision"
- name: Archive
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
ASC_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
run: |
set -euo pipefail
xcodebuild clean archive \
-project "$PROJECT_PATH" \
-scheme "$SCHEME" \
-configuration "$CONFIGURATION" \
-destination "generic/platform=iOS" \
-archivePath "$RUNNER_TEMP/$SCHEME.xcarchive" \
-allowProvisioningUpdates \
-authenticationKeyPath "$RUNNER_TEMP/private_keys/AuthKey_${ASC_KEY_ID}.p8" \
-authenticationKeyID "$ASC_KEY_ID" \
-authenticationKeyIssuerID "$ASC_ISSUER_ID" \
DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \
PRODUCT_BUNDLE_IDENTIFIER="$APP_BUNDLE_ID" \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="Apple Distribution" \
PROVISIONING_PROFILE_SPECIFIER="Dialect Listener App Store" \
OPENROUTER_API_KEY="$OPENROUTER_API_KEY" \
MARKETING_VERSION="$MARKETING_VERSION" \
CURRENT_PROJECT_VERSION="$GITHUB_RUN_NUMBER"
- name: Export IPA
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
ASC_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
run: |
set -euo pipefail
xcodebuild -exportArchive \
-archivePath "$RUNNER_TEMP/$SCHEME.xcarchive" \
-exportPath "$RUNNER_TEMP/export" \
-exportOptionsPlist ExportOptions.plist \
-allowProvisioningUpdates \
-authenticationKeyPath "$RUNNER_TEMP/private_keys/AuthKey_${ASC_KEY_ID}.p8" \
-authenticationKeyID "$ASC_KEY_ID" \
-authenticationKeyIssuerID "$ASC_ISSUER_ID" \
DEVELOPMENT_TEAM="$APPLE_TEAM_ID"
- name: Upload IPA artifact
uses: actions/upload-artifact@v4
with:
name: DialectListener-ipa
path: ${{ runner.temp }}/export/*.ipa
if-no-files-found: error
- name: Upload to TestFlight
env:
ASC_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
run: |
set -euo pipefail
IPA_PATH="$(find "$RUNNER_TEMP/export" -name '*.ipa' -print -quit)"
xcrun altool --upload-app \
--file "$IPA_PATH" \
--type ios \
--apiKey "$ASC_KEY_ID" \
--apiIssuer "$ASC_ISSUER_ID"