diff --git a/.github/workflows/app-store.yml b/.github/workflows/app-store.yml new file mode 100644 index 0000000..89b3a8f --- /dev/null +++ b/.github/workflows/app-store.yml @@ -0,0 +1,82 @@ +name: App Store Submission + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + upload-to-app-store: + name: Archive & Upload to App Store + runs-on: macos-15 + timeout-minutes: 60 + defaults: + run: + working-directory: tabatago-swift + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Select Xcode + run: sudo xcode-select -switch /Applications/Xcode.app + + - name: Cache SPM dependencies + uses: actions/cache@v4 + with: + path: ~/Library/Developer/Xcode/DerivedData/**/SourcePackages + key: spm-macos-${{ hashFiles('tabatago-swift/project.yml') }} + restore-keys: | + spm-macos- + + - name: Write App Store Connect API key + env: + API_KEY_P8: ${{ secrets.APP_STORE_CONNECT_API_KEY_P8 }} + run: | + printf '%s' "$API_KEY_P8" > "$RUNNER_TEMP/AuthKey.p8" + + - name: Archive + run: | + xcodebuild archive \ + -project TabataGo.xcodeproj \ + -scheme TabataGo \ + -configuration Release \ + -archivePath ./build/TabataGo.xcarchive \ + -allowProvisioningUpdates \ + -authenticationKeyPath "$RUNNER_TEMP/AuthKey.p8" \ + -authenticationKeyID "${{ secrets.APP_STORE_CONNECT_KEY_ID }}" \ + -authenticationKeyIssuerID "${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}" \ + || { echo "❌ Archive failed — check signing and API key permissions"; exit 1; } + + - name: Verify build settings + run: | + echo "Checking version and build number..." + xcodebuild -showBuildSettings \ + -project TabataGo.xcodeproj \ + -scheme TabataGo \ + -configuration Release \ + | grep -E "MARKETING_VERSION|CURRENT_PROJECT_VERSION" + + - name: Export IPA + run: | + xcodebuild -exportArchive \ + -archivePath ./build/TabataGo.xcarchive \ + -exportPath ./build/export \ + -exportOptionsPlist ExportOptions.plist \ + -allowProvisioningUpdates \ + -authenticationKeyPath "$RUNNER_TEMP/AuthKey.p8" \ + -authenticationKeyID "${{ secrets.APP_STORE_CONNECT_KEY_ID }}" \ + -authenticationKeyIssuerID "${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}" \ + || { echo "❌ Export failed — check ExportOptions.plist and provisioning"; exit 1; } + + # NOTE: The first upload automatically creates the app record in + # App Store Connect if one does not already exist. + - name: Upload to App Store + run: | + xcrun altool --upload-app \ + --type ios \ + --file ./build/export/TabataGo.ipa \ + --apiKey "${{ secrets.APP_STORE_CONNECT_KEY_ID }}" \ + --apiIssuer "${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}" \ + || { echo "❌ Upload failed — check API key permissions and app record"; exit 1; } diff --git a/docs/app-store-submission.md b/docs/app-store-submission.md new file mode 100644 index 0000000..90a910e --- /dev/null +++ b/docs/app-store-submission.md @@ -0,0 +1,71 @@ +# App Store Submission + +## Prerequisites + +Before triggering the CI pipeline, you need: + +1. **Apple Developer Program membership** (paid, $99/year) +2. **App Store Connect access** with Admin or App Manager role + +## Step 1: Create App Store Connect API Key + +1. Go to [App Store Connect → Users and Access → Integrations → App Store Connect API](https://appstoreconnect.apple.com/access/integrations/api) +2. Click **Generate API Key** (or "+" button) +3. Name it: `GitHub Actions CI` +4. Set access to: **App Manager** (required for auto-signing + upload) +5. Download the `.p8` file immediately (you cannot re-download it later) +6. Note the **Key ID** and **Issuer ID** displayed on the page + +## Step 2: Add GitHub Secrets + +Go to your GitHub repo → **Settings → Secrets and variables → Actions → New repository secret**. + +| Secret Name | Value | +|---|---| +| `APP_STORE_CONNECT_KEY_ID` | The Key ID from Step 1 (e.g., `ABC123XYZ`) | +| `APP_STORE_CONNECT_ISSUER_ID` | The Issuer ID from Step 1 (UUID format) | +| `APP_STORE_CONNECT_API_KEY_P8` | The **entire contents** of the `.p8` file, including `-----BEGIN PRIVATE KEY-----` and `-----END PRIVATE KEY-----` lines | + +## Step 3: Trigger the Submission + +Push a version tag to the repository: + +```bash +git tag v1.0.0 +git push origin v1.0.0 +``` + +Or run manually: +- Go to **Actions** tab → **App Store Submission** → **Run workflow** + +## What Happens + +1. GitHub Actions runner (macOS) checks out your code +2. Archives the app with Xcode auto-signing +3. Exports an App Store IPA +4. Uploads to App Store Connect +5. The first upload **automatically creates** the app record in App Store Connect (bundle ID: `com.tabatago.app`) + +## After Upload + +1. Go to [App Store Connect → Apps](https://appstoreconnect.apple.com/apps) +2. Select **TabataGo** +3. Complete the **App Information** (description, screenshots, keywords, etc.) +4. Complete the **Pricing and Availability** section +5. Go to the new build under **TestFlight** or **App Store** tab +6. Submit for review + +## Troubleshooting + +| Symptom | Likely Cause | +|---|---| +| "No accounts with iTunes Connect access" | API key doesn't have App Manager permissions — recreate the key with correct access | +| "No profiles found" | The bundle ID `com.tabatago.app` isn't registered yet — check Apple Developer portal | +| "Duplicate build number" | Build number already used — bump `CFBundleVersion` in `project.yml` and re-tag | +| "Authentication failed" | API key was revoked, expired, or secret is misspelled — verify secrets in repository settings | + +## Build Number Increments + +Each submission requires a unique build number. Edit `tabatago-swift/project.yml`: +- Change `CFBundleVersion: "2"` → `"3"` in all three targets (TabataGo, TabataGoWatch, TabataGoWatchWidget) +- Then regenerate the project: `cd tabatago-swift && xcodegen` diff --git a/tabatago-swift/ExportOptions.plist b/tabatago-swift/ExportOptions.plist new file mode 100644 index 0000000..86ac81b --- /dev/null +++ b/tabatago-swift/ExportOptions.plist @@ -0,0 +1,18 @@ + + + + + method + app-store + teamID + 2MJF39L8VY + signingStyle + automatic + uploadBitcode + + uploadSymbols + + manageAppVersionAndBuildNumber + + + diff --git a/tabatago-swift/project.yml b/tabatago-swift/project.yml index de69625..ef06f42 100644 --- a/tabatago-swift/project.yml +++ b/tabatago-swift/project.yml @@ -46,7 +46,7 @@ targets: properties: CFBundleDisplayName: TabataGo CFBundleShortVersionString: "1.0" - CFBundleVersion: "1" + CFBundleVersion: "2" UILaunchScreen: UIColorName: "" UIImageName: "" @@ -110,7 +110,7 @@ targets: properties: CFBundleDisplayName: TabataGo CFBundleShortVersionString: "1.0" - CFBundleVersion: "1" + CFBundleVersion: "2" WKApplication: true WKCompanionAppBundleIdentifier: com.tabatago.app NSHealthShareUsageDescription: "TabataGo reads your heart rate and calories during workouts." @@ -147,7 +147,7 @@ targets: properties: CFBundleDisplayName: TabataGoWidget CFBundleShortVersionString: "1.0" - CFBundleVersion: "1" + CFBundleVersion: "2" NSExtension: NSExtensionPointIdentifier: com.apple.widgetkit-extension settings: