- Add release-testflight.sh — one-command archive + upload to TestFlight - Add README_TESTFLIGHT.md — setup guide for home laptop - Add BUILD_STATE.md — build tracking - Migrate bundle IDs: com.chronomind.app → com.saravana.chronomind (original was taken) - Remove App Groups entitlement temporarily (not registered in dev portal yet) - Fix duplicate theme enums in generated file
187 lines
8.5 KiB
Bash
Executable File
187 lines
8.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ── ChronoMind — TestFlight Release Script ──────────────────────────
|
|
# Run from: learning_ai_clock/ios/
|
|
# Prerequisites: Xcode 16+, xcodegen, App Store Connect API key
|
|
# Usage: bash release-testflight.sh
|
|
# bash release-testflight.sh --skip-build # re-upload existing archive
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
cd "$SCRIPT_DIR"
|
|
|
|
# ── Configuration ────────────────────────────────────────────────────
|
|
SCHEME="ChronoMind"
|
|
PROJECT="ChronoMind.xcodeproj"
|
|
BUNDLE_ID="com.saravana.chronomind"
|
|
TEAM_ID="748N7QPX7J"
|
|
EXPORT_OPTIONS="ExportOptions.plist"
|
|
|
|
# App Store Connect API Key (create at https://appstoreconnect.apple.com/access/integrations/api)
|
|
ASC_KEY_ID="${ASC_KEY_ID:-PPATU9GL73}"
|
|
ASC_ISSUER_ID="${ASC_ISSUER_ID:-1dbc2980-1621-4fb9-940b-e28257e6322c}"
|
|
ASC_KEY_PATH="${ASC_KEY_PATH:-$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8}"
|
|
|
|
# ── Helpers ──────────────────────────────────────────────────────────
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
|
step() { echo -e "\n${CYAN}▸ $1${NC}"; }
|
|
ok() { echo -e "${GREEN}✅ $1${NC}"; }
|
|
warn() { echo -e "${YELLOW}⚠️ $1${NC}"; }
|
|
fail() { echo -e "${RED}❌ $1${NC}"; exit 1; }
|
|
|
|
# ── Parse args ───────────────────────────────────────────────────────
|
|
SKIP_BUILD=false
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--skip-build) SKIP_BUILD=true ;;
|
|
--help|-h) echo "Usage: bash release-testflight.sh [--skip-build]"; exit 0 ;;
|
|
esac
|
|
done
|
|
|
|
# ── Pre-flight checks ───────────────────────────────────────────────
|
|
step "Pre-flight checks"
|
|
|
|
command -v xcodebuild >/dev/null 2>&1 || fail "xcodebuild not found — install Xcode"
|
|
command -v xcodegen >/dev/null 2>&1 || fail "xcodegen not found — brew install xcodegen"
|
|
|
|
[ -f "$ASC_KEY_PATH" ] || fail "API key not found at $ASC_KEY_PATH
|
|
Create one at https://appstoreconnect.apple.com/access/integrations/api
|
|
Save as ~/.appstoreconnect/private_keys/AuthKey_<KEY_ID>.p8
|
|
Then export ASC_KEY_ID=<KEY_ID> ASC_ISSUER_ID=<ISSUER_ID>"
|
|
|
|
[ -f "$EXPORT_OPTIONS" ] || fail "ExportOptions.plist not found in ios/"
|
|
[ -f "project.yml" ] || fail "project.yml not found — are you in the ios/ directory?"
|
|
|
|
# Check sibling SDK exists
|
|
SDK_PATH="../../learning_ai_common_plat/packages/swift-platform-sdk/Package.swift"
|
|
[ -f "$SDK_PATH" ] || fail "ByteLystPlatformSDK not found at $SDK_PATH
|
|
Ensure learning_ai_common_plat is cloned as a sibling directory"
|
|
|
|
ok "All pre-flight checks passed"
|
|
|
|
# ── Read current build number ────────────────────────────────────────
|
|
step "Reading current build number"
|
|
|
|
CURRENT_BUILD=$(grep -m1 "CURRENT_PROJECT_VERSION" project.yml | awk -F'"' '{print $2}')
|
|
[ -z "$CURRENT_BUILD" ] && fail "Could not read CURRENT_PROJECT_VERSION from project.yml"
|
|
|
|
NEXT_BUILD=$((CURRENT_BUILD + 1))
|
|
echo " Current: $CURRENT_BUILD → Next: $NEXT_BUILD"
|
|
|
|
if [ "$SKIP_BUILD" = true ]; then
|
|
ARCHIVE_PATH="/tmp/${SCHEME}_${CURRENT_BUILD}.xcarchive"
|
|
[ -d "$ARCHIVE_PATH" ] || fail "No archive found at $ARCHIVE_PATH — run without --skip-build first"
|
|
warn "Skipping build — using existing archive at $ARCHIVE_PATH"
|
|
NEXT_BUILD=$CURRENT_BUILD
|
|
else
|
|
# ── Bump build number ────────────────────────────────────────────
|
|
step "Bumping build number to $NEXT_BUILD"
|
|
|
|
sed -i '' "s/CURRENT_PROJECT_VERSION: \"$CURRENT_BUILD\"/CURRENT_PROJECT_VERSION: \"$NEXT_BUILD\"/g" project.yml
|
|
ok "project.yml updated"
|
|
|
|
# ── Regenerate Xcode project ─────────────────────────────────────
|
|
step "Generating Xcode project"
|
|
xcodegen generate
|
|
ok "Xcode project generated"
|
|
|
|
# ── Verify build ─────────────────────────────────────────────────
|
|
step "Verifying debug build"
|
|
xcodebuild build \
|
|
-project "$PROJECT" \
|
|
-scheme "$SCHEME" \
|
|
-configuration Debug \
|
|
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
|
|
CODE_SIGN_IDENTITY=- CODE_SIGNING_ALLOWED=NO \
|
|
2>&1 | tail -1
|
|
|
|
# ── Archive ──────────────────────────────────────────────────────
|
|
ARCHIVE_PATH="/tmp/${SCHEME}_${NEXT_BUILD}.xcarchive"
|
|
|
|
step "Archiving build $NEXT_BUILD"
|
|
xcodebuild archive \
|
|
-project "$PROJECT" \
|
|
-scheme "$SCHEME" \
|
|
-configuration Release \
|
|
-archivePath "$ARCHIVE_PATH" \
|
|
-destination 'generic/platform=iOS' \
|
|
2>&1 | tail -1
|
|
|
|
[ -d "$ARCHIVE_PATH" ] || fail "Archive not found at $ARCHIVE_PATH"
|
|
ok "Archive succeeded: $ARCHIVE_PATH"
|
|
|
|
# ── Commit ───────────────────────────────────────────────────────
|
|
step "Committing build number bump"
|
|
cd "$SCRIPT_DIR/.."
|
|
git add ios/project.yml ios/ChronoMind.xcodeproj/
|
|
git commit -m "chore(ios): bump build number to $NEXT_BUILD for TestFlight release" || warn "Nothing to commit"
|
|
cd "$SCRIPT_DIR"
|
|
fi
|
|
|
|
# ── Export + Upload ──────────────────────────────────────────────────
|
|
EXPORT_PATH="/tmp/${SCHEME}_export${NEXT_BUILD}"
|
|
|
|
step "Exporting and uploading build $NEXT_BUILD to TestFlight"
|
|
rm -rf "$EXPORT_PATH"
|
|
|
|
xcodebuild -exportArchive \
|
|
-archivePath "$ARCHIVE_PATH" \
|
|
-exportPath "$EXPORT_PATH" \
|
|
-exportOptionsPlist "$EXPORT_OPTIONS" \
|
|
-allowProvisioningUpdates \
|
|
-authenticationKeyPath "$ASC_KEY_PATH" \
|
|
-authenticationKeyID "$ASC_KEY_ID" \
|
|
-authenticationKeyIssuerID "$ASC_ISSUER_ID" \
|
|
2>&1 | tail -5
|
|
|
|
# ── Verify upload ────────────────────────────────────────────────────
|
|
if [ -f "$EXPORT_PATH/${SCHEME}.ipa" ]; then
|
|
ok "Upload succeeded! Build $NEXT_BUILD sent to TestFlight"
|
|
else
|
|
# Check if upload succeeded despite no local IPA (destination: upload)
|
|
if xcodebuild -exportArchive -archivePath "$ARCHIVE_PATH" -exportPath "$EXPORT_PATH" -exportOptionsPlist "$EXPORT_OPTIONS" 2>&1 | grep -q "Upload succeeded"; then
|
|
ok "Upload succeeded! Build $NEXT_BUILD sent to TestFlight"
|
|
fi
|
|
fi
|
|
|
|
# ── Update BUILD_STATE.md ────────────────────────────────────────────
|
|
step "Updating BUILD_STATE.md"
|
|
|
|
BUILD_STATE="BUILD_STATE.md"
|
|
if [ ! -f "$BUILD_STATE" ]; then
|
|
cat > "$BUILD_STATE" << 'HEREDOC'
|
|
# iOS Build State — ChronoMind
|
|
|
|
## Current Build
|
|
**CURRENT_PROJECT_VERSION = 1**
|
|
Marketing version: 1.0.0
|
|
|
|
---
|
|
|
|
## Build History
|
|
|
|
| Build | Status | Key Changes |
|
|
|-------|--------|-------------|
|
|
HEREDOC
|
|
fi
|
|
|
|
# Update current build number
|
|
sed -i '' "s/CURRENT_PROJECT_VERSION = [0-9]*/CURRENT_PROJECT_VERSION = $NEXT_BUILD/" "$BUILD_STATE"
|
|
|
|
ok "BUILD_STATE.md updated to build $NEXT_BUILD"
|
|
|
|
# ── Final commit + push ─────────────────────────────────────────────
|
|
step "Final commit and push"
|
|
cd "$SCRIPT_DIR/.."
|
|
git add ios/BUILD_STATE.md
|
|
git commit -m "docs(ios): update BUILD_STATE.md for build $NEXT_BUILD" || warn "Nothing to commit"
|
|
git push origin main || warn "Push failed — push manually later"
|
|
cd "$SCRIPT_DIR"
|
|
|
|
echo ""
|
|
echo -e "${GREEN}════════════════════════════════════════════════════════${NC}"
|
|
echo -e "${GREEN} ChronoMind build $NEXT_BUILD uploaded to TestFlight! ${NC}"
|
|
echo -e "${GREEN} It should appear in ~15-30 minutes after processing. ${NC}"
|
|
echo -e "${GREEN}════════════════════════════════════════════════════════${NC}"
|
|
echo ""
|