101 lines
7.5 KiB
Markdown
101 lines
7.5 KiB
Markdown
---
|
|
name: testflight
|
|
description: Run TestFlight for a mobile app or game through the App Store Connect API - beta groups (internal / external, public links), testers from a CSV that never enters git, adding a build by its exact number to groups, "What to Test" per locale from the release notes, Test Information, and the Beta App Review submission the owner confirms. Use when the user wants testers on a build, sets up beta groups, writes What to Test, or asks why external testers do not see a build.
|
|
---
|
|
|
|
# TestFlight
|
|
|
|
CLI: `storebound apple testflight <status | groups | testers | what-to-test | info | distribute>` (or `bunx storebound`
|
|
in a project that has it). Every write has `--dry`; run it first and show the plan. API references are cited at the top
|
|
of `cli/lib/testflight.ts` (developer.apple.com/documentation/appstoreconnectapi: beta groups, beta testers, beta build
|
|
localizations, beta app localizations, beta app review submissions).
|
|
|
|
## Prerequisites
|
|
- The build is uploaded and processed (`storebound apple status`, `VALID`), with export compliance answered
|
|
(`storebound apple encryption --build N`, export-compliance skill). A build missing it shows
|
|
`MISSING_EXPORT_COMPLIANCE` and nobody can test it.
|
|
- API key role Admin or App Manager.
|
|
|
|
## 1. Groups (config, no emails)
|
|
```ts
|
|
apple: {
|
|
testflight: {
|
|
groups: [
|
|
{ name: 'Team', internal: true, allBuilds: true }, // App Store Connect users of your team, no review
|
|
{ name: 'Friends' }, // external, by email, Beta App Review first
|
|
{ name: 'Public', publicLink: true, publicLinkLimit: 500 },
|
|
],
|
|
description: { en: 'Match tiles, clear 60 levels.', tr: '...' }, // Beta App Description per language id
|
|
reviewNotes: 'No login needed. IAPs are sandbox purchases.',
|
|
},
|
|
},
|
|
```
|
|
`storebound apple testflight groups` (lists them and what would be created), then `groups sync`. Groups are matched by name; storebound never renames,
|
|
changes or deletes a group.
|
|
|
|
## 2. Testers (CSV in .storebound/, never in git)
|
|
- Columns: `email,first_name,last_name,groups` (groups separated by `;`). Default path `.storebound/testers.csv`
|
|
(gitignored); the config refuses a path outside `.storebound/`. Never paste tester emails into chat, docs, issues or
|
|
commits; storebound masks them in every log line (`j***@example.com`).
|
|
- `storebound apple testflight testers --dry` shows: new testers, testers added to more groups, (with `--remove`)
|
|
testers who leave a managed group. The real run **asks first**: Apple emails each new tester an invitation once the
|
|
group has a build. `--invite` (re)sends invitations to testers still `NOT_INVITED` / `INVITED` (asks again).
|
|
- Internal groups only take App Store Connect users (Users and Access); add people there first.
|
|
- Testers are an owner action: not an MCP tool. An agent prepares the CSV format and the dry run, the owner runs it.
|
|
|
|
## 3. What to Test
|
|
- `storebound whats-new` drafts release notes into `store/appstore.md` (`### whatsNew` per locale). For testers you can
|
|
add a `### whatToTest` block per locale: it wins over `whatsNew` for TestFlight.
|
|
- `storebound apple testflight what-to-test --build N --dry`, then for real. `--text "..." --locale en-US` sets one
|
|
text instead. Pick the build by the exact number you uploaded, never "the latest"; `--version x.y.z` when the same
|
|
build number exists for two versions.
|
|
- Write it for testers: what changed, what to try, known issues. Short; it is read on a phone. Apple documents no
|
|
maximum; storebound stops at 4000 characters (assumption, see the unverified list in docs/project.md).
|
|
|
|
## 4. Test Information (needed for external testing)
|
|
`storebound apple testflight info --dry`: Beta App Description per locale (`apple.testflight.description`), feedback
|
|
email (`app.contactEmail`), privacy policy and marketing URL, and the Beta App Review contact (from
|
|
`.storebound/credentials.json` `reviewContact`) plus notes (4000 characters max). Apple requires a description on every
|
|
beta app localization before a Beta App Review submission, and fields cannot be cleared once set.
|
|
|
|
## 5. Distribute
|
|
- Internal: `storebound apple testflight distribute --build N --group Team --dry`, then for real (MCP:
|
|
`apple_testflight_distribute`, internal groups only). Groups with `allBuilds: true` get every build automatically:
|
|
distribute reports "already available to <group> (internal, all builds)" and sends nothing (App Store Connect
|
|
refuses adding a build to such a group). Internal groups without all builds get the build like external ones.
|
|
A re-run adds nothing (it reads each group's builds first).
|
|
- A group in `apple.testflight.groups` that is not on App Store Connect yet: `distribute` creates it first (the
|
|
groups sync for the missing `--group` names only; it never removes a group) and the dry run shows it. Inviting
|
|
testers stays the owner's step (`apple testflight testers`). A name in neither lists the groups that exist.
|
|
- External: same command with an external group; it asks first. External testers only get the build after **Beta App
|
|
Review**: add `--submit-review` (asks again) or submit in App Store Connect. The first build of a version is
|
|
reviewed; later builds of the same version are often approved quickly. Beta review states: `WAITING_FOR_REVIEW`,
|
|
`IN_REVIEW`, `APPROVED`, `REJECTED` (`storebound apple testflight status` shows internal / external build states).
|
|
- The Beta App Review submission is public-facing and the owner's call: never through MCP; an agent explains and the
|
|
owner confirms in the terminal.
|
|
|
|
## 6. Wait for processing
|
|
`storebound apple testflight wait --build N [--timeout 60m] [--json]` polls the build you just uploaded (20 s, then
|
|
x1.5, at most every 2 minutes) and prints a line whenever its state changes: "not listed yet" for the first minutes,
|
|
`PROCESSING`, then `VALID`. Exit codes: 0 VALID, 1 FAILED / INVALID (read Apple's email, fix, upload a new build
|
|
number), 7 timeout (run it again). `--dry` looks once and does not wait. On VALID it names the next step, e.g. the
|
|
export compliance answer when the build shows `MISSING_EXPORT_COMPLIANCE`. MCP: `apple_testflight_wait` (read-only).
|
|
In CI, run it in the background with a log and report its lines.
|
|
|
|
## 7. Expire old builds (asks first)
|
|
`storebound apple testflight expire --keep 5 --dry` (or `--older-than 60`, or both: beyond the newest 5 AND older than
|
|
60 days; `--version 1.2.0` limits it to one version). The newest build always stays; builds attached to an App Store
|
|
version (in review, live or being prepared) and builds still processing are never expired. The dry run lists every
|
|
build with "expire" / "keep" and why; the real run asks first (`--yes` in scripts the owner runs) because an expired
|
|
build cannot be brought back and testers lose it at once. Not an MCP tool. API: `PATCH /v1/builds/{id}` with
|
|
`expired: true` (cited in `cli/lib/build-ops.ts`).
|
|
|
|
## Gotchas
|
|
- Builds expire after 90 days in TestFlight; upload a new one (`storebound apple next-build`).
|
|
- Up to 100 internal testers and 10,000 external testers per app (TestFlight overview in App Store Connect Help:
|
|
https://developer.apple.com/help/app-store-connect/test-a-beta-version/testflight-overview).
|
|
- A public link (`publicLink: true`) lets anyone with it join, up to `publicLinkLimit` (1-10000). Share it only where
|
|
the owner wants; it still needs the approved build.
|
|
- External testers of an app with IAP or subscriptions buy with sandbox accounts automatically (no charge); say so in What to Test.
|
|
- Rejected beta review: read the message in App Store Connect > TestFlight, fix, upload a new build, submit again
|
|
(review-rejections skill for the tone of the reply).
|