Signing, builds, uploads and an originality check
apple signing status|ensure: bundle id capabilities, a distribution certificate from a key made locally (kept in .storebound/signing, never printed), App Store profiles installed for Xcode; Developer ID 403 explained. apple build / apple upload: xcodebuild archive + export with a generated ExportOptions.plist, next build number, App Store Connect API build upload (or altool), processing wait. Native, Capacitor, React Native, Flutter, Unity projects. play build / play upload: Gradle bundleRelease with the upload key passed through the environment, next versionCode read back from the AAB, testing tracks only. originality: offline Guideline 4.3 check (name, listing text, icon and screenshot perceptual hashes, project structure). New skill build-and-sign; zero new dependencies.
This commit is contained in:
parent
346d80e07e
commit
41319562d8
36 changed files with 2786 additions and 28 deletions
92
skills/build-and-sign/SKILL.md
Normal file
92
skills/build-and-sign/SKILL.md
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
---
|
||||
name: build-and-sign
|
||||
description: Sign, build and upload a mobile app or game from the terminal - App Store Connect API signing (bundle id capabilities, distribution certificate, provisioning profiles), xcodebuild archive and export with a generated ExportOptions.plist, IPA upload through the App Store Connect API or altool with processing wait, and the Android counterpart (Gradle bundleRelease with the upload key, AAB to a testing track). Native, Capacitor, React Native, Flutter and Unity-exported projects. Use when the user wants to build, sign, archive, export, upload or ship a new build, hits code signing or provisioning errors, or needs a build number / versionCode.
|
||||
---
|
||||
|
||||
# Build, sign and upload
|
||||
|
||||
The CLI is `storebound` (`storebound help`). Credentials only from env or `.storebound/credentials.json`
|
||||
(gitignored): `ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_P8` (Admin key for signing), `PLAY_SERVICE_ACCOUNT`, and the
|
||||
Android upload key `ANDROID_KEYSTORE`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD`.
|
||||
Never ask for them in chat, never print them. Shared agent rules: `docs/agents-core.md`.
|
||||
|
||||
**Every write is dry first, then asks.** Certificates, profiles and uploads change the owner's developer account:
|
||||
show the `--dry` output, run it for real only after a clear yes (the command asks again in the terminal; `--yes` only
|
||||
when the owner said so for this run). The MCP server offers only `apple_signing_status` (read-only) from this skill.
|
||||
|
||||
## iOS
|
||||
|
||||
1. **Read.** `storebound apple signing status`: is the bundle id registered (else `storebound apple bundle`), which
|
||||
capabilities it has vs `apple.signing.capabilities`, the distribution certificates of the team and whether this
|
||||
Mac holds a private key for one (a key file in `.storebound/signing/` or an identity in the keychain), App Store
|
||||
profiles per bundle id (app + `apple.signing.extraBundleIds` for extensions) and whether Xcode has them installed.
|
||||
2. **Pick the style.** `apple.signing.style`:
|
||||
- `automatic` (default): `xcodebuild -allowProvisioningUpdates` with the API key creates and fetches what it needs.
|
||||
Nothing to do in step 3 unless capabilities are missing. Easiest for one developer on one Mac.
|
||||
- `manual`: certificate and profiles from step 3, named in ExportOptions.plist. Best for CI and teams; set the app
|
||||
target to manual signing with the installed profile in Xcode (Signing & Capabilities) once.
|
||||
3. **Ensure.** `storebound apple signing ensure --dry`, show it, then without `--dry` after a yes:
|
||||
- adds missing capabilities (never removes one; extra ones are only reported). Adding a capability makes existing
|
||||
profiles invalid: `ensure` creates new ones in the same run.
|
||||
- creates an Apple Distribution certificate only when no valid one has its key on this machine. The RSA key is made
|
||||
locally, kept 0600 in `.storebound/signing/<id>.key` next to `<id>.cer`; it is never printed and never leaves the
|
||||
machine. `--keychain` imports both into the login keychain (or `--keychain <path>`) so xcodebuild can sign.
|
||||
Back the key up (encrypted). Apple allows 3 distribution certificates per team: at the limit, revoking one is
|
||||
the owner's decision (developer.apple.com > Certificates).
|
||||
- Developer ID certificates (Mac apps outside the App Store): only the **Account Holder** can create them; the API
|
||||
answers 403 for every other key, and storebound says so. The Account Holder makes it in Xcode > Settings >
|
||||
Accounts > Manage Certificates and exports a .p12.
|
||||
- creates or reuses an App Store profile per bundle id and installs it into
|
||||
`~/Library/Developer/Xcode/UserData/Provisioning Profiles` (Xcode 16+) and `~/Library/MobileDevice/Provisioning
|
||||
Profiles` (older Xcode). `--no-install` skips that.
|
||||
4. **Build.** `storebound apple build --dry --version 1.4.0` shows the project it found (Capacitor `ios/App`, React
|
||||
Native `ios/<Name>.xcworkspace`, Flutter `ios/Runner`, Unity `Unity-iPhone`, or `apple.build.workspace` /
|
||||
`project` / `scheme`), the build number (next free one from App Store Connect, or `--build-number N`), the
|
||||
ExportOptions.plist (`method` app-store-connect, `destination` export, team, style, profiles) and both xcodebuild
|
||||
commands. Then run it without `--dry` (minutes; full logs in `build/ios/xcodebuild-*.log`). It ends with the IPA
|
||||
path, version and build. **Note the build number**: later steps use exactly it.
|
||||
- Capacitor: `npx cap sync ios` after the web build first. React Native / CocoaPods: `pod install` first (the dry
|
||||
run warns when Pods/ is missing). Flutter: storebound runs `flutter build ios --config-only` with the build
|
||||
number. Unity: build the Xcode project from Unity first.
|
||||
- Capacitor needs a UTF-8 locale; storebound sets `LANG` / `LC_ALL` for xcodebuild.
|
||||
5. **Upload.** `storebound apple upload --dry` (default: the newest IPA in `build/ios/export`): bundle id, version,
|
||||
build, and a refusal when that build number is already on App Store Connect. Then without `--dry` after a yes. Default
|
||||
`--via api` is the App Store Connect API build upload (Build Uploads: reserve, upload the parts, commit with the
|
||||
MD5, poll the upload state; any OS). `--via altool` uses `xcrun altool --upload-package` with the API key. Apple
|
||||
lists both (and Xcode / Transporter); neither is deprecated. storebound then waits until the build is VALID
|
||||
(exit 0), INVALID / FAILED (exit 1, Apple emails the ITMS reason) or `--timeout` (default 60m, exit 7). Run long
|
||||
uploads in the background with a log: `storebound apple upload --yes > .storebound/upload.log 2>&1 &` (only
|
||||
after the owner's yes).
|
||||
6. **Next.** `storebound apple encryption --build N`, `storebound apple testflight distribute --build N --group Team
|
||||
--dry` (`testflight` skill), `storebound apple listing --version x.y.z --build N` (`appstore-first-release`).
|
||||
|
||||
## Android
|
||||
|
||||
1. **Upload key.** Generate once (`keytool -genkeypair -v -storetype PKCS12 -keystore upload.p12 -alias upload
|
||||
-keyalg RSA -keysize 4096 -validity 10950`), keep it outside git, put the four `ANDROID_*` values in env or
|
||||
`.storebound/credentials.json`. storebound hands them to Gradle as injected signing properties through the
|
||||
environment, never on the command line. Without them the project's own `signingConfigs.release` signs (e.g.
|
||||
Flutter's `key.properties`).
|
||||
2. **Build.** `storebound play build --dry` shows the Gradle project (`android/` for Capacitor, React Native and
|
||||
Flutter, the root for native, a Unity export with `launcher/` + `unityLibrary/`; or `play.build.dir` / `module` /
|
||||
`flavor`), the task (`:app:bundleRelease`; Flutter: `flutter build appbundle`), the next free versionCode from Play
|
||||
(or `--version-code N`) and where the key comes from. Then without `--dry`. The AAB lands in
|
||||
`build/android/<package>-<versionCode>.aab`; storebound reads the versionCode back from the bundle and warns when
|
||||
the build file ignored the one it passed (read `-PversionCode` there, or `android.injected.version.code`).
|
||||
3. **Upload.** `storebound play upload --track internal --dry` (read-only: checks the versionCode is free, shows
|
||||
what is on the track now and the release notes languages). Then without `--dry` after a yes. Tracks: internal,
|
||||
alpha (closed), beta (open). Production stays `storebound play release --version-code N --production draft` plus
|
||||
the owner's click in the Console (`play-first-release`, `release-management`). A never-published app only takes
|
||||
drafts: `--status draft`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- "No signing certificate iOS Distribution found": the certificate exists on the team but its key is on another Mac.
|
||||
`apple signing status` shows this as "without key"; import that Mac's .p12, or `ensure` makes a new one.
|
||||
- "Provisioning profile doesn't include the currently selected device / capability": a capability was added after the
|
||||
profile was made. `apple signing ensure` (profiles are recreated when they are not ACTIVE).
|
||||
- Xcode 16+ reads profiles only from `~/Library/Developer/Xcode/UserData/Provisioning Profiles`: profiles copied into
|
||||
the old folder alone are not seen.
|
||||
- ITMS-90189 / "bundle version must be higher": the build number is taken. `apple next-build` gives a free one;
|
||||
`apple build` uses it by default.
|
||||
- Play "Version code N has already been used": `play next-version-code`; `play build` uses it by default.
|
||||
Loading…
Add table
Add a link
Reference in a new issue