Skip to content

Bump release to v0.13.0 #5

Bump release to v0.13.0

Bump release to v0.13.0 #5

Workflow file for this run

name: Release iOS
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
version:
description: "Release version (for example 1.2.3 or v1.2.3)"
required: true
type: string
permissions:
contents: read
jobs:
preflight:
name: Preflight
runs-on: ubuntu-24.04
outputs:
version: ${{ steps.release_meta.outputs.version }}
tag: ${{ steps.release_meta.outputs.tag }}
is_prerelease: ${{ steps.release_meta.outputs.is_prerelease }}
ref: ${{ github.sha }}
steps:
- name: Checkout
uses: actions/checkout@v6
- id: release_meta
name: Resolve release version
shell: bash
run: |
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
raw="${{ github.event.inputs.version }}"
else
raw="${GITHUB_REF_NAME}"
fi
version="${raw#v}"
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
echo "Invalid release version: $raw" >&2
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT"
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
else
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
fi
build-ios:
name: Build & Upload to TestFlight
needs: [preflight]
runs-on: macos-14
env:
RELEASE_VERSION: ${{ needs.preflight.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.ref }}
fetch-depth: 0
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: package.json
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version-file: package.json
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Align package versions to release version
run: node scripts/update-release-package-versions.ts "$RELEASE_VERSION"
- name: Update iOS version in Xcode project
run: node scripts/update-ios-version.ts "$RELEASE_VERSION" --build-number "$GITHUB_RUN_NUMBER"
- name: Build mobile web bundle
run: bun run --cwd apps/mobile build
- name: Sync Capacitor iOS
run: bunx cap sync ios --deployment
working-directory: apps/mobile
- name: Install Apple certificate and provisioning profile
env:
IOS_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }}
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }}
run: |
set -euo pipefail
# Validate required secrets
for secret_name in IOS_CERTIFICATE_P12 IOS_CERTIFICATE_PASSWORD IOS_PROVISIONING_PROFILE; do
if [[ -z "${!secret_name}" ]]; then
echo "Missing required secret: $secret_name" >&2
exit 1
fi
done
# Create temporary keychain
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
KEYCHAIN_PASSWORD="$(openssl rand -hex 16)"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Import distribution certificate
CERT_PATH="$RUNNER_TEMP/certificate.p12"
echo "$IOS_CERTIFICATE_P12" | base64 --decode > "$CERT_PATH"
security import "$CERT_PATH" \
-P "$IOS_CERTIFICATE_PASSWORD" \
-A -t cert -f pkcs12 \
-k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychain -d user -s "$KEYCHAIN_PATH"
# Install provisioning profile
PROFILE_PATH="$RUNNER_TEMP/profile.mobileprovision"
echo "$IOS_PROVISIONING_PROFILE" | base64 --decode > "$PROFILE_PATH"
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp "$PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/
- name: Build iOS archive
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
set -euo pipefail
xcodebuild archive \
-project apps/mobile/ios/App/App.xcodeproj \
-scheme App \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath "$RUNNER_TEMP/App.xcarchive" \
CODE_SIGN_STYLE=Manual \
DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \
CODE_SIGN_IDENTITY="iPhone Distribution" \
PROVISIONING_PROFILE_SPECIFIER="${{ secrets.IOS_PROVISIONING_PROFILE_NAME }}" \
-allowProvisioningUpdates \
COMPILER_INDEX_STORE_ENABLE=NO
- name: Generate ExportOptions.plist
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
cat > "$RUNNER_TEMP/ExportOptions.plist" <<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>destination</key>
<string>upload</string>
<key>teamID</key>
<string>${APPLE_TEAM_ID}</string>
<key>uploadSymbols</key>
<true/>
<key>signingStyle</key>
<string>manual</string>
<key>provisioningProfiles</key>
<dict>
<key>com.openknots.okcode.mobile</key>
<string>${{ secrets.IOS_PROVISIONING_PROFILE_NAME }}</string>
</dict>
</dict>
</plist>
PLIST
- name: Export IPA
run: |
set -euo pipefail
xcodebuild -exportArchive \
-archivePath "$RUNNER_TEMP/App.xcarchive" \
-exportPath "$RUNNER_TEMP/export" \
-exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist"
- name: Write App Store Connect API key
env:
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
run: |
set -euo pipefail
KEY_DIR="$HOME/private_keys"
mkdir -p "$KEY_DIR"
printf '%s' "$APPLE_API_KEY" > "$KEY_DIR/AuthKey_${APPLE_API_KEY_ID}.p8"
- name: Upload to TestFlight
env:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
run: |
set -euo pipefail
IPA_FILE=$(find "$RUNNER_TEMP/export" -name "*.ipa" -print -quit)
if [[ -z "$IPA_FILE" ]]; then
echo "No IPA file found in export directory" >&2
ls -la "$RUNNER_TEMP/export/"
exit 1
fi
echo "Uploading $IPA_FILE to TestFlight..."
xcrun altool --upload-app \
-f "$IPA_FILE" \
-t ios \
--apiKey "$APPLE_API_KEY_ID" \
--apiIssuer "$APPLE_API_ISSUER"
echo "Upload to TestFlight complete!"
- name: Cleanup keychain
if: always()
run: |
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
if [[ -f "$KEYCHAIN_PATH" ]]; then
security delete-keychain "$KEYCHAIN_PATH" || true
fi
rm -f "$HOME/private_keys/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8" || true