Beta groups from apple.testflight.groups (created when missing, never changed), testers from a CSV in .storebound/ (masked in logs, confirmation before Apple emails invitations, --remove / --invite), What to Test per locale from the App Store listing (whatToTest or whatsNew), Test Information and Beta App Review details, builds by exact number to groups, Beta App Review submission behind --submit-review and a confirmation. Pure planners in cli/lib/testflight.ts, shared CSV / table / JSON:API helpers. MCP: apple_testflight_status, _groups, _what_to_test, _info, _distribute (internal groups only). Skill testflight.
79 lines
7 KiB
Markdown
79 lines
7 KiB
Markdown
---
|
|
name: appstore-first-release
|
|
description: Step-by-step first (and later) App Store release of a mobile game - App Store Connect API key, bundle id, app record, signing and xcodebuild archive/upload with the API key, TestFlight, version + listing text in many locales, screenshots, age rating, App Privacy, review details, in-app purchases with the first version, and submitting for review. Use when the user wants to publish, release, upload or set up an iOS game/app on the App Store or TestFlight, or hits App Store Connect API errors.
|
|
---
|
|
|
|
# App Store: first release
|
|
|
|
The CLI is `storebound` (on PATH while the plugin is enabled; otherwise `bunx storebound` in a project that has it,
|
|
or `bun <toolchain>/cli/index.ts`; agents can also use the `storebound mcp` tools).
|
|
Config: `storebound.config.ts`; credentials: `ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_P8` (env or
|
|
`.storebound/credentials.json`, gitignored). All commands are idempotent and have `--dry`. Run `storebound check` first.
|
|
|
|
**Ask the owner before submitting for review, releasing, or canceling a submission.** The final "Submit for Review"
|
|
of a first version is the owner's click. Shared agent rules: `docs/agents-core.md` of the toolchain.
|
|
|
|
## Steps (API = storebound, UI = App Store Connect / Xcode by hand)
|
|
|
|
1. **UI · API key.** App Store Connect > Users and Access > Integrations > App Store Connect API > Team Keys > +
|
|
(Admin or App Manager). Download `AuthKey_<KEYID>.p8` once, keep it in `~/.appstoreconnect/private_keys/`, note the
|
|
Key ID and Issuer ID. The same key signs builds (`-allowProvisioningUpdates`).
|
|
2. **API · Bundle id.** `storebound apple bundle` registers it (Certificates, Identifiers & Profiles).
|
|
3. **UI · App record.** Apps > + > New App: iOS, name, primary language, the bundle id, SKU. **No API for this.**
|
|
Then `storebound apple status` finds it (put the Apple ID in `apple.appId` to skip the lookup).
|
|
4. **Local · Info.plist.** `ITSAppUsesNonExemptEncryption` with the true answer (`export-compliance` skill; `NO` only
|
|
when the game uses nothing beyond OS / HTTPS encryption) so builds arrive answered;
|
|
`NSUserTrackingUsageDescription` if ads use the IDFA; privacy policy reachable inside the app (guideline 5.1.1).
|
|
5. **Local · Build + upload** (Mac, no Xcode account login needed, the API key does signing):
|
|
```sh
|
|
K="-allowProvisioningUpdates -authenticationKeyPath $ASC_P8 -authenticationKeyID $ASC_KEY_ID -authenticationKeyIssuerID $ASC_ISSUER_ID"
|
|
xcodebuild -project ios/App/App.xcodeproj -scheme App -configuration Release -destination generic/platform=iOS \
|
|
-archivePath build/App.xcarchive MARKETING_VERSION=1.0.0 CURRENT_PROJECT_VERSION=3 $K archive
|
|
xcodebuild -exportArchive -archivePath build/App.xcarchive -exportOptionsPlist ExportOptions.plist -exportPath build/export $K
|
|
```
|
|
ExportOptions.plist: `method` app-store-connect, `destination` upload, `teamID` <your team>, `signingStyle`
|
|
automatic. `MARKETING_VERSION` / `CURRENT_PROJECT_VERSION` overrides beat the project values. Take the build
|
|
number from `storebound apple next-build` right before archiving and **note it**: every later step selects that
|
|
exact build, never "the latest". Processing takes 5-30 min (`storebound apple status` shows VALID).
|
|
6. **API · TestFlight.** Put an internal group with `allBuilds: true` in `apple.testflight.groups` and run
|
|
`storebound apple testflight groups`: every new build reaches the team automatically. External testers, What to
|
|
Test, Test Information and Beta App Review: the testflight skill. Sandbox testers stay UI: Users and Access > Sandbox.
|
|
7. **API · Version + text.** Fill `store/appstore.md`, then `storebound apple listing --version 1.0.0 --build 3`
|
|
(version, copyright, MANUAL release, the build with exactly that number, name / subtitle / privacy URL,
|
|
description / keywords / promo / URLs). Then `storebound apple release --phased on`: manual release + phased
|
|
release is the default for every version (`release-management`).
|
|
8. **API · Screenshots.** `store-screenshots` skill, then
|
|
`storebound apple screenshots > .storebound/asc-shots.log 2>&1 &` (6.9" + 6.5" per locale, MD5-idempotent).
|
|
9. **API · App info.** `storebound apple appinfo`: categories, age rating declaration, content rights, Free price,
|
|
all territories. The **first** availability record of a new app may be refused by the public API (an error on
|
|
POST /v2/appAvailabilities); then set it once in App Store Connect > Pricing and Availability and re-run (later runs only read it).
|
|
10. **API · Review details.** `storebound apple review-details` (contact from `.storebound/credentials.json`
|
|
`reviewContact`, or `--contact-from <Apple ID of another app of the team>`; notes from the config).
|
|
11. **API + UI · In-app purchases.** `store-iap-setup` skill. First IAPs go to review **with** this version.
|
|
12. **UI · App Privacy.** `store-declarations` skill (nutrition labels), then **Publish**.
|
|
13. **Preflight.** `storebound validate --apple` until it shows no blocker (`store-preflight`).
|
|
14. **UI · Submit.** Version page > Add for Review (with the IAPs in the same draft submission) > Submit for Review
|
|
(owner). Release type MANUAL means the owner presses "Release" after approval.
|
|
15. **Verify + wait.** `storebound apple status`: version WAITING_FOR_REVIEW, IAPs WAITING_FOR_REVIEW, one review
|
|
submission. `storebound apple status --wait-review` polls until approved (exit 0) or rejected (exit 1,
|
|
`review-rejections`). Approved: the owner releases (`release-management`).
|
|
|
|
## Gotchas
|
|
|
|
- The app record cannot be created via API; the bundle id can.
|
|
- First consumable / non-consumable IAPs must be submitted together with an app version, only from the IAP page
|
|
("Add for Review" puts them into the draft submission). The API (`inAppPurchaseSubmissions`) answers
|
|
`FIRST_*_MUST_BE_SUBMITTED_ON_VERSION`. If the version was already submitted without them: cancel the review
|
|
submission (`storebound apple status --cancel-submission <id>`, PATCH canceled: true), wait a minute, add the IAPs
|
|
from their page, then resubmit.
|
|
- App Privacy (data types, purposes, linked, tracking) is UI-only and must be **published**, not just saved.
|
|
- Export compliance unanswered = "Missing Compliance": `storebound apple encryption` (`export-compliance`).
|
|
- ITMS-91053 / 91061 privacy manifest emails after upload: `privacy-manifest` skill.
|
|
- Screenshot sizes: 6.9" 1320 x 2868 (`APP_IPHONE_67`) is required; 6.5" 1284 x 2778 (`APP_IPHONE_65`) recommended.
|
|
PNG without alpha. Up to 10 per set. Apple processes uploads async; failures show as FAILED later.
|
|
- App Store icon (1024) must have **no alpha channel** (`storebound icons` writes rgb24).
|
|
- Limits: name 30, subtitle 30, promotional text 170, keywords 100 bytes (UTF-8, no spaces after commas),
|
|
description 4000. No "What's New" on a first version.
|
|
- Guideline 5.1.1: the privacy policy link must be inside the app too (Settings > Privacy), not only in the listing.
|
|
- `xcodebuild` with Capacitor needs a UTF-8 locale (`export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8`).
|
|
- 500 / 503 from the API happen; storebound retries with backoff.
|