Section 12/171 menit
12. TestFlight dan App Store Connect Integration
12. TestFlight dan App Store Connect Integration
Build Number Management
Xcode Cloud otomatis generate CI_BUILD_NUMBER yang increment dengan setiap build. Gunakan ini sebagai CFBundleVersion:
swift
#!/bin/zsh
# ci_scripts/ci_pre_xcodebuild.sh
if [[ "$CI_XCODEBUILD_ACTION" == "archive" ]]; then
# Set build number dari CI_BUILD_NUMBER (auto-increment)
agvtool new-version -all "$CI_BUILD_NUMBER"
echo "Build number set to: $CI_BUILD_NUMBER"
fi
Release Notes Otomatis
Generate release notes dari git log untuk TestFlight:
swift
#!/bin/zsh
# ci_scripts/ci_post_xcodebuild.sh
if [[ "$CI_XCODEBUILD_ACTION" == "archive" ]]; then
# Ambil commit messages sejak tag terakhir
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [[ -n "$LAST_TAG" ]]; then
COMMITS=$(git log "$LAST_TAG..HEAD" --pretty=format:"• %s" --no-merges | head -20)
else
COMMITS=$(git log --pretty=format:"• %s" --no-merges -10)
fi
# TestFlight release notes per locale
mkdir -p "$CI_PRIMARY_REPOSITORY_PATH/release_notes"
cat > "$CI_PRIMARY_REPOSITORY_PATH/release_notes/en-US.txt" << EOF
Build #${CI_BUILD_NUMBER} — Branch: ${CI_BRANCH}
Changes:
${COMMITS}
EOF
echo "Release notes generated"
fi
App Store Connect API untuk Automasi
Untuk automasi lanjutan, gunakan App Store Connect API melalui Python atau Ruby di ci_scripts:
swift
#!/usr/bin/env python3
# ci_scripts/upload_metadata.py
# Jalankan dari ci_post_xcodebuild.sh
import os
import json
import urllib.request
import urllib.parse
import jwt
import time
# App Store Connect API credentials dari environment variables
KEY_ID = os.environ.get("ASC_KEY_ID")
ISSUER_ID = os.environ.get("ASC_ISSUER_ID")
PRIVATE_KEY = os.environ.get("ASC_PRIVATE_KEY", "").replace("\\n", "\n")
def generate_token():
header = {"alg": "ES256", "kid": KEY_ID, "typ": "JWT"}
payload = {
"iss": ISSUER_ID,
"iat": int(time.time()),
"exp": int(time.time()) + 1200,
"aud": "appstoreconnect-v1"
}
return jwt.encode(payload, PRIVATE_KEY, algorithm="ES256", headers=header)
def get_latest_build(app_id: str, version: str):
token = generate_token()
url = f"https://api.appstoreconnect.apple.com/v1/builds?filter[app]={app_id}&filter[preReleaseVersion.version]={version}&limit=1"
req = urllib.request.Request(url)
req.add_header("Authorization", f"Bearer {token}")
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req) as response:
return json.loads(response.read())
if __name__ == "__main__":
app_id = os.environ.get("APP_STORE_CONNECT_APP_ID")
version = os.environ.get("APP_VERSION", "1.0.0")
builds = get_latest_build(app_id, version)
print(f"Latest build: {json.dumps(builds, indent=2)}")