diff --git a/ios/BUILD_STATE.md b/ios/BUILD_STATE.md new file mode 100644 index 0000000..622e694 --- /dev/null +++ b/ios/BUILD_STATE.md @@ -0,0 +1,47 @@ +# iOS Build State — ChronoMind + +## Current Build +**CURRENT_PROJECT_VERSION = 1** +Marketing version: 1.0.0 + +--- + +## Build History + +| Build | Status | Key Changes | +|-------|--------|-------------| +| 1 | Pending | Initial TestFlight release — fix theme duplicates, remove App Groups (temp), bundle ID → com.saravana.chronomind | + +--- + +## Bundle IDs (Registered in App Store Connect) + +| Target | Bundle ID | +|--------|-----------| +| ChronoMind | `com.saravana.chronomind` | +| ChronoMindWidgets | `com.saravana.chronomind.widgets` | +| ChronoMindWatch | `com.saravana.chronomind.watchkitapp` | +| ChronoMindMac | `com.saravana.chronomind.mac` | + +## App Store Connect API Key + +| Field | Value | +|-------|-------| +| Key ID | `PPATU9GL73` | +| Issuer ID | `1dbc2980-1621-4fb9-940b-e28257e6322c` | +| Key Path | `~/.appstoreconnect/private_keys/AuthKey_PPATU9GL73.p8` | + +## Pre-Release Checklist + +- [x] Bundle IDs registered (iOS + widgets) +- [ ] App record created in App Store Connect +- [ ] App Group `group.com.chronomind.shared` registered (needed for widget ↔ app data sharing) +- [ ] First TestFlight upload + +--- + +## Next Steps + +1. Create app record in App Store Connect (manual — API key needs Admin role) +2. Run `bash ios/release-testflight.sh` from home network +3. After first upload succeeds, register App Group and re-enable entitlements diff --git a/ios/ChronoMind.xcodeproj/project.pbxproj b/ios/ChronoMind.xcodeproj/project.pbxproj index 9e72d07..287d8b4 100644 --- a/ios/ChronoMind.xcodeproj/project.pbxproj +++ b/ios/ChronoMind.xcodeproj/project.pbxproj @@ -1173,7 +1173,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app; + PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -1203,7 +1203,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app; + PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -1222,7 +1222,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.tests; + PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.tests; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChronoMind.app/ChronoMind"; @@ -1244,7 +1244,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app.watchkitapp.complications; + PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.watchkitapp.complications; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; @@ -1263,7 +1263,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.tests; + PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.tests; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChronoMind.app/ChronoMind"; @@ -1279,10 +1279,10 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_GENERATION_MODE = GeneratedFile; INFOPLIST_KEY_CFBundleDisplayName = ChronoMind; - INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.chronomind.app; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.saravana.chronomind; INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES; MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app.watchkitapp; + PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.watchkitapp; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; @@ -1307,7 +1307,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.mac; + PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.mac; SDKROOT = macosx; }; name = Release; @@ -1327,7 +1327,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app.watchkitapp.complications; + PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.watchkitapp.complications; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; @@ -1351,7 +1351,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app.widgets; + PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.widgets; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1426,10 +1426,10 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_GENERATION_MODE = GeneratedFile; INFOPLIST_KEY_CFBundleDisplayName = ChronoMind; - INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.chronomind.app; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.saravana.chronomind; INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES; MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app.watchkitapp; + PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.watchkitapp; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; @@ -1521,7 +1521,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.mac; + PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.mac; SDKROOT = macosx; }; name = Debug; @@ -1542,7 +1542,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app.widgets; + PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.widgets; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/ios/ChronoMind/ChronoMind.entitlements b/ios/ChronoMind/ChronoMind.entitlements index cc45b00..6631ffa 100644 --- a/ios/ChronoMind/ChronoMind.entitlements +++ b/ios/ChronoMind/ChronoMind.entitlements @@ -2,9 +2,5 @@ - com.apple.security.application-groups - - group.com.chronomind.shared - diff --git a/ios/ChronoMindWidgets/ChronoMindWidgets.entitlements b/ios/ChronoMindWidgets/ChronoMindWidgets.entitlements index cc45b00..6631ffa 100644 --- a/ios/ChronoMindWidgets/ChronoMindWidgets.entitlements +++ b/ios/ChronoMindWidgets/ChronoMindWidgets.entitlements @@ -2,9 +2,5 @@ - com.apple.security.application-groups - - group.com.chronomind.shared - diff --git a/ios/README_TESTFLIGHT.md b/ios/README_TESTFLIGHT.md new file mode 100644 index 0000000..25b5638 --- /dev/null +++ b/ios/README_TESTFLIGHT.md @@ -0,0 +1,161 @@ +# ChronoMind — TestFlight Release Guide + +## One-Time Setup (Home Laptop) + +### 1. Install prerequisites + +```bash +# Xcode (from App Store or xcodereleases.com) +xcode-select --install + +# XcodeGen +brew install xcodegen +``` + +### 2. Clone sibling repos + +The iOS app depends on `ByteLystPlatformSDK` from the common platform repo. Both repos must be siblings: + +``` +~/code/mygh/ +├── learning_ai_clock/ # This repo +└── learning_ai_common_plat/ # ByteLystPlatformSDK lives here +``` + +```bash +cd ~/code/mygh +git clone https://github.com/saravanakumardb1/learning_ai_common_plat.git +``` + +### 3. App Store Connect API Key + +The key is already created. Copy it from your work machine: + +```bash +mkdir -p ~/.appstoreconnect/private_keys +# Copy AuthKey_PPATU9GL73.p8 to ~/.appstoreconnect/private_keys/ +chmod 600 ~/.appstoreconnect/private_keys/AuthKey_PPATU9GL73.p8 +``` + +| Field | Value | +|-------|-------| +| Key ID | `PPATU9GL73` | +| Issuer ID | `1dbc2980-1621-4fb9-940b-e28257e6322c` | + +### 4. Create app in App Store Connect (one-time) + +1. Go to [App Store Connect → My Apps](https://appstoreconnect.apple.com/apps) +2. Click **"+"** → **"New App"** +3. Fill in: + - **Platform:** iOS + - **Name:** `ChronoMind` + - **Primary Language:** English (U.S.) + - **Bundle ID:** `com.saravana.chronomind` (already registered) + - **SKU:** `chronomind` +4. Click **Create** + +--- + +## Release to TestFlight + +### Quick release (one command) + +```bash +cd ios +bash release-testflight.sh +``` + +This will: +1. Bump the build number in `project.yml` +2. Regenerate the Xcode project via `xcodegen` +3. Verify the debug build compiles +4. Archive a Release build +5. Export and upload to App Store Connect +6. Update `BUILD_STATE.md` +7. Commit and push + +### Re-upload a failed upload + +If the archive succeeded but upload failed: + +```bash +bash release-testflight.sh --skip-build +``` + +### Custom API key location + +```bash +ASC_KEY_ID=XXXX ASC_ISSUER_ID=yyyy ASC_KEY_PATH=/path/to/key.p8 bash release-testflight.sh +``` + +--- + +## Manual Steps (if script fails) + +### Generate Xcode project + +```bash +cd ios +xcodegen generate +``` + +### Build (simulator, no signing) + +```bash +xcodebuild build \ + -project ChronoMind.xcodeproj \ + -scheme ChronoMind \ + -configuration Debug \ + -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ + CODE_SIGN_IDENTITY=- CODE_SIGNING_ALLOWED=NO +``` + +### Archive + +```bash +xcodebuild archive \ + -project ChronoMind.xcodeproj \ + -scheme ChronoMind \ + -configuration Release \ + -archivePath /tmp/ChronoMind_1.xcarchive \ + -destination 'generic/platform=iOS' +``` + +### Export + Upload + +```bash +xcodebuild -exportArchive \ + -archivePath /tmp/ChronoMind_1.xcarchive \ + -exportPath /tmp/ChronoMind_export1 \ + -exportOptionsPlist ExportOptions.plist \ + -allowProvisioningUpdates \ + -authenticationKeyPath ~/.appstoreconnect/private_keys/AuthKey_PPATU9GL73.p8 \ + -authenticationKeyID PPATU9GL73 \ + -authenticationKeyIssuerID 1dbc2980-1621-4fb9-940b-e28257e6322c +``` + +--- + +## Troubleshooting + +| Error | Fix | +|-------|-----| +| `xcodegen not found` | `brew install xcodegen` | +| `ByteLystPlatformSDK not found` | Clone `learning_ai_common_plat` as sibling directory | +| `Authentication failed` | Check API key path, Key ID, and Issuer ID | +| `Error Downloading App Information` | Create the app record in App Store Connect first (see Setup §4) | +| `No signing certificate "iOS Distribution"` | Use `-allowProvisioningUpdates` with API key (script does this) | +| `App Groups capability` | Register App Group in developer portal, or remove entitlement for initial release | +| `duplicate 'init(hex:alpha:)'` | Remove duplicate `Color.init(hex:)` from `ChronoMindTheme.generated.swift` | + +--- + +## Project Info + +| Field | Value | +|-------|-------| +| Bundle ID | `com.saravana.chronomind` | +| Team ID | `748N7QPX7J` | +| Min iOS | 17.0 | +| Xcode | 16.0+ | +| Swift | 5.9 | diff --git a/ios/project.yml b/ios/project.yml index dc3ef3c..a44e901 100644 --- a/ios/project.yml +++ b/ios/project.yml @@ -1,6 +1,6 @@ name: ChronoMind options: - bundleIdPrefix: com.chronomind + bundleIdPrefix: com.saravana.chronomind deploymentTarget: iOS: "17.0" watchOS: "10.0" @@ -32,7 +32,7 @@ targets: - "**/.DS_Store" settings: base: - PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.app + PRODUCT_BUNDLE_IDENTIFIER: com.saravana.chronomind INFOPLIST_GENERATION_MODE: GeneratedFile MARKETING_VERSION: "1.0.0" CURRENT_PROJECT_VERSION: "1" @@ -52,9 +52,6 @@ targets: - package: ByteLystPlatformSDK entitlements: path: ChronoMind/ChronoMind.entitlements - properties: - com.apple.security.application-groups: - - group.com.chronomind.shared ChronoMindTests: type: bundle.unit-test @@ -68,7 +65,7 @@ targets: - target: ChronoMind settings: base: - PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.tests + PRODUCT_BUNDLE_IDENTIFIER: com.saravana.chronomind.tests GENERATE_INFOPLIST_FILE: true ChronoMindWidgets: @@ -84,7 +81,7 @@ targets: - path: ChronoMind/LiveActivity/TimerActivityAttributes.swift settings: base: - PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.app.widgets + PRODUCT_BUNDLE_IDENTIFIER: com.saravana.chronomind.widgets INFOPLIST_GENERATION_MODE: GeneratedFile GENERATE_INFOPLIST_FILE: true MARKETING_VERSION: "1.0.0" @@ -93,9 +90,6 @@ targets: INFOPLIST_KEY_CFBundleDisplayName: ChronoMind Widgets entitlements: path: ChronoMindWidgets/ChronoMindWidgets.entitlements - properties: - com.apple.security.application-groups: - - group.com.chronomind.shared ChronoMindWatch: type: application @@ -110,12 +104,12 @@ targets: - path: ChronoMind/Shared/AppGroup settings: base: - PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.app.watchkitapp + PRODUCT_BUNDLE_IDENTIFIER: com.saravana.chronomind.watchkitapp INFOPLIST_GENERATION_MODE: GeneratedFile GENERATE_INFOPLIST_FILE: true MARKETING_VERSION: "1.0.0" CURRENT_PROJECT_VERSION: "1" - INFOPLIST_KEY_WKCompanionAppBundleIdentifier: com.chronomind.app + INFOPLIST_KEY_WKCompanionAppBundleIdentifier: com.saravana.chronomind INFOPLIST_KEY_CFBundleDisplayName: ChronoMind INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp: true dependencies: @@ -138,7 +132,7 @@ targets: - path: ChronoMind/Shared/AppGroup settings: base: - PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.app.watchkitapp.complications + PRODUCT_BUNDLE_IDENTIFIER: com.saravana.chronomind.watchkitapp.complications INFOPLIST_GENERATION_MODE: GeneratedFile GENERATE_INFOPLIST_FILE: true MARKETING_VERSION: "1.0.0" @@ -168,7 +162,7 @@ targets: - package: ByteLystPlatformSDK settings: base: - PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.mac + PRODUCT_BUNDLE_IDENTIFIER: com.saravana.chronomind.mac INFOPLIST_GENERATION_MODE: GeneratedFile GENERATE_INFOPLIST_FILE: true MARKETING_VERSION: "1.0.0" diff --git a/ios/release-testflight.sh b/ios/release-testflight.sh new file mode 100755 index 0000000..db46c08 --- /dev/null +++ b/ios/release-testflight.sh @@ -0,0 +1,186 @@ +#!/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_.p8 + Then export ASC_KEY_ID= ASC_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 ""