feat(ios): add TestFlight release script, README, BUILD_STATE + bundle ID migration
- 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
This commit is contained in:
parent
1ea3965492
commit
e61858db15
47
ios/BUILD_STATE.md
Normal file
47
ios/BUILD_STATE.md
Normal file
@ -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
|
||||||
@ -1173,7 +1173,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
@ -1203,7 +1203,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
@ -1222,7 +1222,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@loader_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.tests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.tests;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChronoMind.app/ChronoMind";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChronoMind.app/ChronoMind";
|
||||||
@ -1244,7 +1244,7 @@
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app.watchkitapp.complications;
|
PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.watchkitapp.complications;
|
||||||
SDKROOT = watchos;
|
SDKROOT = watchos;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
TARGETED_DEVICE_FAMILY = 4;
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
@ -1263,7 +1263,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@loader_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.tests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.tests;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChronoMind.app/ChronoMind";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChronoMind.app/ChronoMind";
|
||||||
@ -1279,10 +1279,10 @@
|
|||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_GENERATION_MODE = GeneratedFile;
|
INFOPLIST_GENERATION_MODE = GeneratedFile;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = ChronoMind;
|
INFOPLIST_KEY_CFBundleDisplayName = ChronoMind;
|
||||||
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.chronomind.app;
|
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.saravana.chronomind;
|
||||||
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES;
|
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES;
|
||||||
MARKETING_VERSION = 1.0.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app.watchkitapp;
|
PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.watchkitapp;
|
||||||
SDKROOT = watchos;
|
SDKROOT = watchos;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
TARGETED_DEVICE_FAMILY = 4;
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
@ -1307,7 +1307,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.mac;
|
PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.mac;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@ -1327,7 +1327,7 @@
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app.watchkitapp.complications;
|
PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.watchkitapp.complications;
|
||||||
SDKROOT = watchos;
|
SDKROOT = watchos;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
TARGETED_DEVICE_FAMILY = 4;
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
@ -1351,7 +1351,7 @@
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app.widgets;
|
PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.widgets;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
@ -1426,10 +1426,10 @@
|
|||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_GENERATION_MODE = GeneratedFile;
|
INFOPLIST_GENERATION_MODE = GeneratedFile;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = ChronoMind;
|
INFOPLIST_KEY_CFBundleDisplayName = ChronoMind;
|
||||||
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.chronomind.app;
|
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.saravana.chronomind;
|
||||||
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES;
|
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES;
|
||||||
MARKETING_VERSION = 1.0.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app.watchkitapp;
|
PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.watchkitapp;
|
||||||
SDKROOT = watchos;
|
SDKROOT = watchos;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
TARGETED_DEVICE_FAMILY = 4;
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
@ -1521,7 +1521,7 @@
|
|||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.mac;
|
PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.mac;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@ -1542,7 +1542,7 @@
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.chronomind.app.widgets;
|
PRODUCT_BUNDLE_IDENTIFIER = com.saravana.chronomind.widgets;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,9 +2,5 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.application-groups</key>
|
|
||||||
<array>
|
|
||||||
<string>group.com.chronomind.shared</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -2,9 +2,5 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.application-groups</key>
|
|
||||||
<array>
|
|
||||||
<string>group.com.chronomind.shared</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
161
ios/README_TESTFLIGHT.md
Normal file
161
ios/README_TESTFLIGHT.md
Normal file
@ -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 |
|
||||||
@ -1,6 +1,6 @@
|
|||||||
name: ChronoMind
|
name: ChronoMind
|
||||||
options:
|
options:
|
||||||
bundleIdPrefix: com.chronomind
|
bundleIdPrefix: com.saravana.chronomind
|
||||||
deploymentTarget:
|
deploymentTarget:
|
||||||
iOS: "17.0"
|
iOS: "17.0"
|
||||||
watchOS: "10.0"
|
watchOS: "10.0"
|
||||||
@ -32,7 +32,7 @@ targets:
|
|||||||
- "**/.DS_Store"
|
- "**/.DS_Store"
|
||||||
settings:
|
settings:
|
||||||
base:
|
base:
|
||||||
PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.app
|
PRODUCT_BUNDLE_IDENTIFIER: com.saravana.chronomind
|
||||||
INFOPLIST_GENERATION_MODE: GeneratedFile
|
INFOPLIST_GENERATION_MODE: GeneratedFile
|
||||||
MARKETING_VERSION: "1.0.0"
|
MARKETING_VERSION: "1.0.0"
|
||||||
CURRENT_PROJECT_VERSION: "1"
|
CURRENT_PROJECT_VERSION: "1"
|
||||||
@ -52,9 +52,6 @@ targets:
|
|||||||
- package: ByteLystPlatformSDK
|
- package: ByteLystPlatformSDK
|
||||||
entitlements:
|
entitlements:
|
||||||
path: ChronoMind/ChronoMind.entitlements
|
path: ChronoMind/ChronoMind.entitlements
|
||||||
properties:
|
|
||||||
com.apple.security.application-groups:
|
|
||||||
- group.com.chronomind.shared
|
|
||||||
|
|
||||||
ChronoMindTests:
|
ChronoMindTests:
|
||||||
type: bundle.unit-test
|
type: bundle.unit-test
|
||||||
@ -68,7 +65,7 @@ targets:
|
|||||||
- target: ChronoMind
|
- target: ChronoMind
|
||||||
settings:
|
settings:
|
||||||
base:
|
base:
|
||||||
PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.tests
|
PRODUCT_BUNDLE_IDENTIFIER: com.saravana.chronomind.tests
|
||||||
GENERATE_INFOPLIST_FILE: true
|
GENERATE_INFOPLIST_FILE: true
|
||||||
|
|
||||||
ChronoMindWidgets:
|
ChronoMindWidgets:
|
||||||
@ -84,7 +81,7 @@ targets:
|
|||||||
- path: ChronoMind/LiveActivity/TimerActivityAttributes.swift
|
- path: ChronoMind/LiveActivity/TimerActivityAttributes.swift
|
||||||
settings:
|
settings:
|
||||||
base:
|
base:
|
||||||
PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.app.widgets
|
PRODUCT_BUNDLE_IDENTIFIER: com.saravana.chronomind.widgets
|
||||||
INFOPLIST_GENERATION_MODE: GeneratedFile
|
INFOPLIST_GENERATION_MODE: GeneratedFile
|
||||||
GENERATE_INFOPLIST_FILE: true
|
GENERATE_INFOPLIST_FILE: true
|
||||||
MARKETING_VERSION: "1.0.0"
|
MARKETING_VERSION: "1.0.0"
|
||||||
@ -93,9 +90,6 @@ targets:
|
|||||||
INFOPLIST_KEY_CFBundleDisplayName: ChronoMind Widgets
|
INFOPLIST_KEY_CFBundleDisplayName: ChronoMind Widgets
|
||||||
entitlements:
|
entitlements:
|
||||||
path: ChronoMindWidgets/ChronoMindWidgets.entitlements
|
path: ChronoMindWidgets/ChronoMindWidgets.entitlements
|
||||||
properties:
|
|
||||||
com.apple.security.application-groups:
|
|
||||||
- group.com.chronomind.shared
|
|
||||||
|
|
||||||
ChronoMindWatch:
|
ChronoMindWatch:
|
||||||
type: application
|
type: application
|
||||||
@ -110,12 +104,12 @@ targets:
|
|||||||
- path: ChronoMind/Shared/AppGroup
|
- path: ChronoMind/Shared/AppGroup
|
||||||
settings:
|
settings:
|
||||||
base:
|
base:
|
||||||
PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.app.watchkitapp
|
PRODUCT_BUNDLE_IDENTIFIER: com.saravana.chronomind.watchkitapp
|
||||||
INFOPLIST_GENERATION_MODE: GeneratedFile
|
INFOPLIST_GENERATION_MODE: GeneratedFile
|
||||||
GENERATE_INFOPLIST_FILE: true
|
GENERATE_INFOPLIST_FILE: true
|
||||||
MARKETING_VERSION: "1.0.0"
|
MARKETING_VERSION: "1.0.0"
|
||||||
CURRENT_PROJECT_VERSION: "1"
|
CURRENT_PROJECT_VERSION: "1"
|
||||||
INFOPLIST_KEY_WKCompanionAppBundleIdentifier: com.chronomind.app
|
INFOPLIST_KEY_WKCompanionAppBundleIdentifier: com.saravana.chronomind
|
||||||
INFOPLIST_KEY_CFBundleDisplayName: ChronoMind
|
INFOPLIST_KEY_CFBundleDisplayName: ChronoMind
|
||||||
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp: true
|
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp: true
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -138,7 +132,7 @@ targets:
|
|||||||
- path: ChronoMind/Shared/AppGroup
|
- path: ChronoMind/Shared/AppGroup
|
||||||
settings:
|
settings:
|
||||||
base:
|
base:
|
||||||
PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.app.watchkitapp.complications
|
PRODUCT_BUNDLE_IDENTIFIER: com.saravana.chronomind.watchkitapp.complications
|
||||||
INFOPLIST_GENERATION_MODE: GeneratedFile
|
INFOPLIST_GENERATION_MODE: GeneratedFile
|
||||||
GENERATE_INFOPLIST_FILE: true
|
GENERATE_INFOPLIST_FILE: true
|
||||||
MARKETING_VERSION: "1.0.0"
|
MARKETING_VERSION: "1.0.0"
|
||||||
@ -168,7 +162,7 @@ targets:
|
|||||||
- package: ByteLystPlatformSDK
|
- package: ByteLystPlatformSDK
|
||||||
settings:
|
settings:
|
||||||
base:
|
base:
|
||||||
PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.mac
|
PRODUCT_BUNDLE_IDENTIFIER: com.saravana.chronomind.mac
|
||||||
INFOPLIST_GENERATION_MODE: GeneratedFile
|
INFOPLIST_GENERATION_MODE: GeneratedFile
|
||||||
GENERATE_INFOPLIST_FILE: true
|
GENERATE_INFOPLIST_FILE: true
|
||||||
MARKETING_VERSION: "1.0.0"
|
MARKETING_VERSION: "1.0.0"
|
||||||
|
|||||||
186
ios/release-testflight.sh
Executable file
186
ios/release-testflight.sh
Executable file
@ -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_<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 ""
|
||||||
Loading…
Reference in New Issue
Block a user