skills/ballee/xcode-cloud-cicd/SKILL.md
Xcode Cloud CI/CD setup for Flutter iOS apps. Covers ci_post_clone.sh scripts, workflow configuration, TestFlight deployment, environment secrets, App Store distribution, and API management. Use when setting up CI/CD, troubleshooting builds, deploying to TestFlight/App Store, or managing workflows via API.
npx skillsauth add javeedishaq/ai-workflow-orchestrator skills/ballee/xcode-cloud-cicdInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive guide for setting up Xcode Cloud CI/CD for the Ballee Flutter iOS app.
Xcode Cloud provides native CI/CD for Apple platforms with:
┌─────────────────────────────────────────────────────────────────┐
│ XCODE CLOUD BUILD │
├─────────────────────────────────────────────────────────────────┤
│ 1. Clone Repository │
│ 2. Run ci_post_clone.sh (install Flutter, dependencies) │
│ 3. Build Flutter iOS app │
│ 4. Archive with automatic code signing │
│ 5. Export IPA │
│ 6. Upload to App Store Connect │
│ 7. Distribute to TestFlight │
└─────────────────────────────────────────────────────────────────┘
App Created: App must exist in App Store Connect
co.balleeA86CXY8H75 (Akson Engineering Sàrl)Certificates:
API Key (for local uploads):
LGU934Y2XR69a6de96-5d75-47e3-e053-5b8c7c11a4d1~/.appstoreconnect/private_keys/AuthKey_LGU934Y2XR.p8antoineschaller/balleeios/ci_scripts/ci_post_clone.sh script presentios/Podfile with correct target nameA Python CLI tool is available for managing Xcode Cloud via App Store Connect API:
# Location
apps/mobile/ios/scripts/xcode_cloud_cli.py
# Prerequisites
pip3 install pyjwt cryptography requests
# Commands
./xcode_cloud_cli.py list-products # List Xcode Cloud products
./xcode_cloud_cli.py list-workflows # List all workflows
./xcode_cloud_cli.py workflow-info <id> # Get workflow details
./xcode_cloud_cli.py delete-workflow <id> # Delete a workflow
./xcode_cloud_cli.py trigger <id> # Trigger a build
./xcode_cloud_cli.py build-status <id> # Check build status
The ci_post_clone.sh script runs after Xcode Cloud clones the repository. It installs Flutter and builds the iOS app.
apps/mobile/ios/ci_scripts/ci_post_clone.sh
#!/bin/sh
set -e
# ci_post_clone.sh for Ballee Flutter app
# This script runs after Xcode Cloud clones the repository
# Documentation: https://developer.apple.com/documentation/xcode/writing-custom-build-scripts
echo "=========================================="
echo "CI Post Clone Script - Ballee"
echo "=========================================="
# Navigate to mobile app root
cd "$CI_PRIMARY_REPOSITORY_PATH/apps/mobile"
# ========================================
# Install Flutter
# ========================================
echo "Installing Flutter..."
# Clone Flutter SDK to temp location
FLUTTER_DIR="$HOME/flutter"
if [ ! -d "$FLUTTER_DIR" ]; then
git clone https://github.com/flutter/flutter.git -b stable "$FLUTTER_DIR"
fi
# Add Flutter to PATH
export PATH="$PATH:$FLUTTER_DIR/bin"
# Pre-download iOS artifacts
flutter precache --ios
# Accept licenses
flutter doctor --android-licenses || true
echo "Flutter version:"
flutter --version
# ========================================
# Install Ruby & CocoaPods (if needed)
# ========================================
echo "Setting up Ruby environment..."
# Xcode Cloud has Ruby but may need CocoaPods
if ! command -v pod &> /dev/null; then
echo "Installing CocoaPods..."
gem install cocoapods
fi
# ========================================
# Install Flutter Dependencies
# ========================================
echo "Installing Flutter dependencies..."
flutter pub get
# ========================================
# Generate Code (Freezed, Riverpod)
# ========================================
echo "Running build_runner..."
dart run build_runner build --delete-conflicting-outputs
# ========================================
# Build iOS (Release)
# ========================================
echo "Building Flutter iOS..."
flutter build ios --release --no-codesign
# ========================================
# Install CocoaPods Dependencies
# ========================================
echo "Installing CocoaPods dependencies..."
cd ios
pod install --repo-update
echo "=========================================="
echo "CI Post Clone Complete"
echo "=========================================="
chmod +x apps/mobile/ios/ci_scripts/ci_post_clone.sh
| Variable | Description |
|----------|-------------|
| CI_PRIMARY_REPOSITORY_PATH | Path to cloned repository |
| CI_WORKSPACE | Xcode workspace path |
| CI_PRODUCT | Product being built |
| CI_BRANCH | Git branch name |
| CI_TAG | Git tag (if triggered by tag) |
| CI_COMMIT | Git commit SHA |
apps/mobile/ios/Ballee.xcworkspace in XcodeStart Conditions:
main branchdev branchActions:
Start Conditions:
main branchActions:
Post-Actions:
Start Conditions:
v* (e.g., v1.0.0)Actions:
Post-Actions:
# Conceptual representation - configure in Xcode UI
workflows:
- name: "TestFlight Internal"
triggers:
- type: branch
branch: main
environment:
xcode: latest_release
macos: latest_release
actions:
- action: build
platform: iOS
scheme: Ballee
configuration: Release
post_actions:
- action: testflight_internal
groups:
- "Internal Testers"
| Variable | Value | Secret |
|----------|-------|--------|
| FLUTTER_VERSION | 3.24.0 (or stable) | No |
| SUPABASE_URL | Your Supabase URL | No |
| SUPABASE_ANON_KEY | Your anon key | Yes |
# Environment variables are automatically available
echo "Building for branch: $CI_BRANCH"
echo "Commit: $CI_COMMIT"
# Custom variables
if [ -n "$FLUTTER_VERSION" ]; then
git -C "$FLUTTER_DIR" checkout "$FLUTTER_VERSION"
fi
# Create .env file for app
cat > .env << EOF
SUPABASE_URL=$SUPABASE_URL
SUPABASE_ANON_KEY=$SUPABASE_ANON_KEY
EOF
Secrets are:
Configure in Xcode Cloud workflow:
| Group | Purpose | Review Required | |-------|---------|-----------------| | Internal Testing | Team members | No | | External Testing | Beta testers | Yes (first build) |
Xcode Cloud auto-increments build numbers. To customize:
# In ci_post_clone.sh
BUILD_NUMBER=$CI_BUILD_NUMBER
# Update Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" ios/Ballee/Info.plist
For local builds and custom export configurations:
<?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>teamID</key>
<string>A86CXY8H75</string>
<key>uploadBitcode</key>
<false/>
<key>uploadSymbols</key>
<true/>
<key>signingStyle</key>
<string>automatic</string>
<key>destination</key>
<string>upload</string>
</dict>
</plist>
Start Conditions:
release/*Post-Actions:
#!/bin/bash
set -e
# Navigate to mobile app
cd apps/mobile
# Clean previous builds
rm -rf build/ios/archive build/ios/export
# Build Flutter
flutter build ios --release --no-codesign
# Archive
cd ios
xcodebuild -workspace Ballee.xcworkspace \
-scheme Ballee \
-configuration Release \
-archivePath ../build/ios/archive/Ballee.xcarchive \
-destination 'generic/platform=iOS' \
CODE_SIGN_STYLE=Automatic \
DEVELOPMENT_TEAM=A86CXY8H75 \
archive
# Export IPA
xcodebuild -exportArchive \
-archivePath ../build/ios/archive/Ballee.xcarchive \
-exportOptionsPlist ExportOptions.plist \
-exportPath ../build/ios/export \
-allowProvisioningUpdates
# Upload to App Store Connect
xcrun altool --upload-app \
--type ios \
-f ../build/ios/export/Ballee.ipa \
--apiKey LGU934Y2XR \
--apiIssuer 69a6de96-5d75-47e3-e053-5b8c7c11a4d1
xcrun altool --validate-app \
--type ios \
-f build/ios/export/Ballee.ipa \
--apiKey LGU934Y2XR \
--apiIssuer 69a6de96-5d75-47e3-e053-5b8c7c11a4d1
# For macOS apps only
xcrun notarytool submit build/macos/Ballee.app.zip \
--key ~/.appstoreconnect/private_keys/AuthKey_LGU934Y2XR.p8 \
--key-id LGU934Y2XR \
--issuer 69a6de96-5d75-47e3-e053-5b8c7c11a4d1 \
--wait
Cause: ci_post_clone.sh not executed or Flutter not in PATH.
Fix:
# Ensure script is executable
chmod +x ios/ci_scripts/ci_post_clone.sh
# Verify script location (must be ios/ci_scripts/)
ls -la ios/ci_scripts/
Cause: Distribution certificate not available.
Fix:
Cause: Profile was generated before distribution certificate.
Fix:
# Use -allowProvisioningUpdates to regenerate
xcodebuild -exportArchive \
-archivePath build.xcarchive \
-exportOptionsPlist ExportOptions.plist \
-exportPath export \
-allowProvisioningUpdates
Cause: CocoaPods cache or version mismatch.
Fix:
# In ci_post_clone.sh
cd ios
rm -rf Pods Podfile.lock
pod install --repo-update
Cause: Uploading build with same version+build number.
Fix:
# Auto-increment in ci_post_clone.sh
NEW_BUILD_NUMBER=$(date +%Y%m%d%H%M)
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $NEW_BUILD_NUMBER" ios/Ballee/Info.plist
Cause: Development-signed frameworks in release build.
Fix:
# Clean and rebuild
flutter clean
flutter build ios --release --no-codesign
cd ios && pod install --repo-update
# Then archive with correct signing
# Simulate Xcode Cloud environment
export CI_PRIMARY_REPOSITORY_PATH="/Users/antoineschaller/GitHub/ballee"
export CI_BRANCH="main"
export CI_COMMIT="abc123"
# Run script
cd apps/mobile
./ios/ci_scripts/ci_post_clone.sh
Version: 1.2.3 (CFBundleShortVersionString)
Build: 2024010112 (CFBundleVersion) - auto-incremented by date/time
| Branch | Workflow | Distribution |
|--------|----------|--------------|
| dev | Build only | None |
| main | Archive | TestFlight Internal |
| v* tags | Archive | TestFlight External |
| release/* tags | Archive | App Store |
To speed up builds, cache Flutter between runs:
# Check if Flutter exists from previous build
if [ -d "$HOME/.flutter_cache/flutter" ]; then
export PATH="$PATH:$HOME/.flutter_cache/flutter/bin"
else
git clone https://github.com/flutter/flutter.git "$HOME/.flutter_cache/flutter"
export PATH="$PATH:$HOME/.flutter_cache/flutter/bin"
fi
Enable concurrent builds for faster feedback on multiple PRs.
Configure Slack/email notifications for:
# Full clean build
flutter clean && flutter pub get && flutter build ios --release
# Archive
xcodebuild -workspace ios/Ballee.xcworkspace -scheme Ballee -configuration Release archive
# Upload
xcrun altool --upload-app --type ios -f Ballee.ipa --apiKey KEY_ID --apiIssuer ISSUER_ID
| Script | When | Purpose |
|--------|------|---------|
| ci_post_clone.sh | After clone | Install dependencies |
| ci_pre_xcodebuild.sh | Before build | Pre-build setup |
| ci_post_xcodebuild.sh | After build | Post-build processing |
# Download from App Store Connect
# Keys > App Store Connect API > Generate API Key
# Save to ~/.appstoreconnect/private_keys/AuthKey_{KEY_ID}.p8
The App Store Connect API allows programmatic management of Xcode Cloud workflows, builds, and products.
| Setting | Value |
|---------|-------|
| Key ID | LGU934Y2XR |
| Issuer ID | 69a6de96-5d75-47e3-e053-5b8c7c11a4d1 |
| Key Path | ~/.appstoreconnect/private_keys/AuthKey_LGU934Y2XR.p8 |
| Base URL | https://api.appstoreconnect.apple.com/v1 |
A Python CLI tool is available for managing Xcode Cloud:
# Location
apps/mobile/ios/scripts/xcode_cloud_cli.py
# Prerequisites
pip3 install pyjwt cryptography requests
# Make executable
chmod +x apps/mobile/ios/scripts/xcode_cloud_cli.py
# List all products (apps) with Xcode Cloud enabled
./xcode_cloud_cli.py list-products
# List all workflows
./xcode_cloud_cli.py list-workflows
# Get workflow details
./xcode_cloud_cli.py workflow-info <workflow-id>
# Delete a workflow (with confirmation)
./xcode_cloud_cli.py delete-workflow <workflow-id>
# Delete without confirmation
./xcode_cloud_cli.py delete-workflow <workflow-id> --force
# Trigger a build
./xcode_cloud_cli.py trigger <workflow-id>
# Check build status
./xcode_cloud_cli.py build-status <build-id>
| Endpoint | Method | Description |
|----------|--------|-------------|
| /ciProducts | GET | List Xcode Cloud products |
| /ciProducts/{id}/workflows | GET | List workflows for a product |
| /ciWorkflows/{id} | GET | Get workflow details |
| /ciWorkflows/{id} | DELETE | Delete a workflow |
| /ciBuildRuns | POST | Trigger a new build |
| /ciBuildRuns/{id} | GET | Get build status |
# 1. List all workflows
./xcode_cloud_cli.py list-workflows
# Output:
# ID Name Active
# 81B37E9E-D5BD-482A-9252-761C0093FF9C Ballee workflow Yes
# 24FB36D3-8241-47EE-A819-9D1810C2927F Development Build No
# 2. Delete the unused workflow
./xcode_cloud_cli.py delete-workflow 24FB36D3-8241-47EE-A819-9D1810C2927F
The API uses JWT (JSON Web Token) for authentication. The CLI handles this automatically, but for reference:
import jwt
import time
payload = {
'iss': ISSUER_ID,
'iat': int(time.time()),
'exp': int(time.time()) + 1200, # 20 min expiry
'aud': 'appstoreconnect-v1'
}
token = jwt.encode(
payload,
private_key,
algorithm='ES256',
headers={'kid': KEY_ID}
)
# Generate JWT (requires openssl)
JWT=$(python3 -c "
import jwt, time
with open('$HOME/.appstoreconnect/private_keys/AuthKey_LGU934Y2XR.p8') as f:
key = f.read()
print(jwt.encode({
'iss': '69a6de96-5d75-47e3-e053-5b8c7c11a4d1',
'iat': int(time.time()),
'exp': int(time.time()) + 1200,
'aud': 'appstoreconnect-v1'
}, key, algorithm='ES256', headers={'kid': 'LGU934Y2XR'}))
")
# List workflows
curl -H "Authorization: Bearer $JWT" \
"https://api.appstoreconnect.apple.com/v1/ciWorkflows"
# Delete a workflow
curl -X DELETE -H "Authorization: Bearer $JWT" \
"https://api.appstoreconnect.apple.com/v1/ciWorkflows/{WORKFLOW_ID}"
TestFlight deployment is configured in App Store Connect workflow settings (not via code).
If no groups exist:
For the build to succeed with TestFlight, ensure these are set in Xcode Cloud:
| Variable | Description | Where to Set |
|----------|-------------|--------------|
| SUPABASE_URL | Supabase project URL | Workflow > Environment |
| SUPABASE_ANON_KEY | Supabase anonymous key | Workflow > Environment (Secret) |
| SENTRY_DSN | Sentry error tracking | Workflow > Environment (Secret) |
| MIXPANEL_TOKEN | Analytics token | Workflow > Environment (Secret) |
Recommended settings for "Ballee workflow":
Start Conditions:
- Branch: main (or specific release branches)
- Auto-cancel: Enabled (only latest commit builds)
Environment:
- Xcode: Latest Release
- macOS: Latest Release
- Environment Variables: (as above)
Archive:
- Scheme: Ballee
- Platform: iOS
- Configuration: Release
Post-Actions:
- TestFlight Internal Testing
- Group: Internal Testers
- Notify: Yes
After a successful build:
The /ciBuildRuns endpoint doesn't support GET_COLLECTION. Get builds per workflow instead:
./xcode_cloud_cli.py workflow-info <workflow-id>
Ensure the .p8 file exists:
ls ~/.appstoreconnect/private_keys/AuthKey_LGU934Y2XR.p8
Or set the environment variable:
export APP_STORE_CONNECT_API_KEY_CONTENT=$(base64 < ~/.appstoreconnect/private_keys/AuthKey_LGU934Y2XR.p8)
The API may show multiple products (e.g., ballee and Ballee). Use list-products to identify the correct one and check workflows for each.
tools
# Test Patterns Testing patterns for reliable, maintainable, and fast tests. > **Template Usage:** Customize for your test framework (Vitest, Jest, Playwright, etc.) and assertion library. ## Test Structure ```typescript // user.test.ts import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { userService } from '@/services/user.service'; import { createTestUser, cleanupTestData } from '@/tests/helpers'; describe('UserService', () => { let testUserId: string; befor
tools
# State Management Patterns Client-side state management patterns for modern applications. > **Template Usage:** Customize for your state library (React Query, Zustand, Jotai, Redux, etc.). ## State Categories | Type | Description | Solution | |------|-------------|----------| | **Server State** | Data from API/database | React Query, SWR | | **Client State** | UI state, user preferences | Zustand, Jotai, useState | | **Form State** | Form inputs, validation | React Hook Form, Formik | | **U
development
# Service Patterns Service layer patterns for clean architecture with proper error handling, logging, and type safety. > **Template Usage:** Customize for your ORM (Prisma, Drizzle, TypeORM, etc.) and logging solution. ## Result Type Pattern Never throw exceptions from services. Always return a Result type. ```typescript // lib/result.ts export type Result<T, E = Error> = | { success: true; data: T } | { success: false; error: E }; export function ok<T>(data: T): Result<T, never> { r
testing
# Row-Level Security Patterns Database security patterns for multi-tenant and user-scoped data. > **Template Usage:** Customize for your database (PostgreSQL, Supabase, etc.) and auth system. ## RLS Fundamentals ### Enable RLS on Tables ```sql -- Enable RLS (required before policies take effect) ALTER TABLE users ENABLE ROW LEVEL SECURITY; ALTER TABLE posts ENABLE ROW LEVEL SECURITY; ALTER TABLE comments ENABLE ROW LEVEL SECURITY; -- Force RLS for table owners too (recommended) ALTER TABLE