arcane-cli 0.26.2 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,253 @@
1
+ ---
2
+ name: Spell — EAS Store Deploy
3
+ description: Deploy an Expo app to the Apple App Store and Google Play via EAS Build + EAS Submit — one-time setup, repeat deployments, and the known pitfalls hit dogfooding both flows for real.
4
+ argument-hint: Which store(s) to deploy to (ios, android, or both) and whether this is initial setup or a repeat deployment
5
+ agent: agent
6
+ ---
7
+
8
+ ## Executive Summary
9
+
10
+ - This spell documents the **EAS Build + EAS Submit** pipeline end to end for both the Apple App Store
11
+ and Google Play — initial one-time setup, repeat deployments, and known pitfalls, in that order, for
12
+ each store.
13
+ - Roughly 80% of the material (credentials, `eas.json` profile shape, build-number handling, OTA rules)
14
+ is shared between the two stores; only the store-console work genuinely diverges. Covered once in a
15
+ shared preamble, then two store-specific sections.
16
+ - **Deliberately scoped to EAS Build + EAS Submit** — no local Xcode, Xcode Cloud, or Fastlane coverage.
17
+ That scope is what keeps every step concrete and copy-pasteable.
18
+ - Every lesson here was hit for real across two dogfooding runs (a first-ever App Store submission, then
19
+ a from-zero Google Play listing to a live production release) — none is general knowledge restated.
20
+ - Durable, store-specific platform facts are documented once in
21
+ [`mobile-release-standards.md`](../../.arcane/governance/mobile-release-standards.md) and cited here
22
+ by `MR-nn` ID; verification discipline is documented once in
23
+ [`external-verification-standards.md`](../../.arcane/governance/external-verification-standards.md)
24
+ and cited by `EV-nn` ID. This spell does not restate either.
25
+
26
+ ---
27
+
28
+ ## Placeholders
29
+
30
+ Every placeholder below follows the same resolution rule: pull it from `.arcane.json`, the invoking
31
+ frontmatter, or the repo's own existing `app.json`/`eas.json`; if it is not resolvable from any of those,
32
+ **ask** — never assume. A wrong guess here produces a rejected submission or a build tied to the wrong
33
+ account, not a local mistake.
34
+
35
+ | Placeholder | What it is | Resolution rule |
36
+ | --- | --- | --- |
37
+ | `{IOS_BUNDLE_ID}` | The iOS bundle identifier. | Resolve from `app.json`'s `expo.ios.bundleIdentifier`; if unset, ask. |
38
+ | `{APPLE_TEAM_ID}` | The Apple Developer Team ID. | Resolve from `eas.json` or the Apple Developer account; if unset, ask. |
39
+ | `{ASC_APP_ID}` | The App Store Connect App ID (`ascAppId`). | Resolve from `eas.json`'s `submit.production.ios` block, or App Store Connect's own app record; if unset, ask. |
40
+ | `{APPLE_ID_EMAIL}` | The Apple ID email used to authenticate to App Store Connect. | Resolve from `eas.json`, or ask — never assume which Apple ID owns the account. |
41
+ | `{EAS_PROJECT_SLUG}` | The EAS project ID/slug. | Resolve from `app.json`'s `expo.extra.eas.projectId` or `eas init` output; if unset, ask. |
42
+ | `{ANDROID_PACKAGE}` | The Android application ID / package name. | Resolve from `app.json`'s `expo.android.package`; if unset, ask. |
43
+ | `{ORG_NAME}` | The Expo/EAS organization name, if the project belongs to one. | Resolve from `eas.json` or the EAS dashboard; if not applicable, omit. |
44
+
45
+ ## Context files
46
+
47
+ - [governance/mobile-release-standards.md](../../.arcane/governance/mobile-release-standards.md) —
48
+ every `MR-nn` rule cited below. Rules are **cited by ID only** in this spell, never restated. **If the
49
+ file is not installed**, this spell still runs in full — findings just cite each check as `MR-nn
50
+ (rationale unavailable — install the standards doc)` instead of linking to it.
51
+ - [governance/external-verification-standards.md](../../.arcane/governance/external-verification-standards.md) —
52
+ every `EV-nn` rule cited below, cited by ID only. **If the file is not installed**, this spell still
53
+ runs, and still re-reads actual state rather than trusting a console's own success signal — it just
54
+ cites those checks as `EV-nn (rationale unavailable — install the standards doc)`.
55
+
56
+ ---
57
+
58
+ ## Shared EAS Preamble (both stores)
59
+
60
+ This section is common setup both stores' deployments depend on. Do it once.
61
+
62
+ ### `eas.json` profile structure
63
+
64
+ ```json
65
+ {
66
+ "build": {
67
+ "production": {
68
+ "autoIncrement": true
69
+ }
70
+ },
71
+ "submit": {
72
+ "production": {
73
+ "ios": {
74
+ "appleId": "{APPLE_ID_EMAIL}",
75
+ "ascAppId": "{ASC_APP_ID}",
76
+ "appleTeamId": "{APPLE_TEAM_ID}"
77
+ },
78
+ "android": {
79
+ "serviceAccountKeyPath": "./path/to/service-account.json",
80
+ "track": "production"
81
+ }
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ - `appVersionSource: "local"` (in `app.json`'s `expo` block) plus `production.autoIncrement: true` means
88
+ build numbers auto-increment on every build — no manual version bump needed for a routine release.
89
+ - Credentials for both platforms can be **remote, EAS-managed** — no local Mac or Xcode required for iOS
90
+ signing (Distribution Certificate, Provisioning Profile, Push Notifications key are all generated and
91
+ held by EAS), and no local Android keystore file required either.
92
+
93
+ ### Cross-platform lessons (apply to every release, either store)
94
+
95
+ - **Never ship a native rebuild while a submission is in review** just to carry a JS-only change — it
96
+ can cost the queue position. Hold the change and ship it as an OTA update after the verdict lands.
97
+ - **A reviewer's first cold launch must already be correct.** A review build has to carry the work
98
+ natively; it cannot rely on an OTA update being fetched, because of the next rule.
99
+ - **OTA updates apply on the next launch, not the current one** — the client downloads in the background
100
+ and swaps on relaunch. Do not diagnose a "stuck" update until after two full quit-and-reopen cycles.
101
+ - **🔴 Bundler transform caches do not key on `EXPO_PUBLIC_*` env vars.** On a persistent (self-hosted)
102
+ build runner, the cache survives between runs, so an update published to one channel can ship
103
+ another channel's inlined config — for example, a production build silently pointing at a development
104
+ API. Pass `--clear-cache` on the publish lane, and verify by comparing the published `launchAsset`
105
+ hashes across channels: identical hashes for two channels that differ by at least one env var is proof
106
+ of the defect. **A green publish/build step is not evidence here (`EV-02`)** — assert against the
107
+ actual published asset.
108
+ - **When a feature is removed or renamed, both stores' screenshots and descriptions are in the blast
109
+ radius.** Store metadata outlives the code it depicts, and it's the first thing a reviewer opens.
110
+
111
+ ---
112
+
113
+ ## Apple App Store
114
+
115
+ ### Initial one-time setup
116
+
117
+ 1. Confirm Apple Developer Program enrollment is active.
118
+ 2. Create the App Store Connect app record: name, `{IOS_BUNDLE_ID}`, SKU, and user access.
119
+ 3. Set the `eas.json` build + submit profile structure shown in the preamble above, filling in
120
+ `{APPLE_ID_EMAIL}`, `{ASC_APP_ID}`, and `{APPLE_TEAM_ID}`.
121
+ 4. Generate remote, EAS-managed credentials (Distribution Certificate, Provisioning Profile, Push
122
+ Notifications key) — no local Mac or Xcode needed.
123
+ 5. Create an App Store Connect API key for non-interactive submits (choose EAS-managed vs.
124
+ manually-uploaded per the operator's preference).
125
+ 6. If App Store Connect already has this bundle ID linked and EAS's automatic capability sync fails
126
+ against it, toggle the affected capabilities manually in the Apple Developer console's Identifiers
127
+ page instead of retrying the automated sync.
128
+
129
+ ### Repeat deployments
130
+
131
+ ```bash
132
+ eas build --platform ios --profile production
133
+ eas submit --platform ios --profile production --latest
134
+ ```
135
+
136
+ Build numbers auto-increment per the preamble's `eas.json` config — no manual version bump needed.
137
+ Realistic wait times: build ~10-20 minutes; Apple's binary processing ~5-10 minutes before the build is
138
+ visible in TestFlight. A build not yet visible immediately after `eas submit` reports success is
139
+ ordinarily processing lag, not a failed submission — verify by checking TestFlight directly before
140
+ concluding otherwise (`EV-06`).
141
+
142
+ ### Known pitfalls
143
+
144
+ - **`Failed to patch capabilities: X ON, Y OFF`** — happens when an App Store Connect app is already
145
+ linked to the bundle ID and Apple rejects EAS's automatic capability sync. Fix: `EXPO_NO_CAPABILITY_SYNC=1`
146
+ as a build-time env var, combined with the manual capability toggle from setup step 6.
147
+ - **`Command must be re-run to pick up new updates configuration`** — EAS auto-installs `expo-updates`
148
+ mid-build the first time it's needed. Simply re-run the exact same `eas build` command; no
149
+ configuration change is required on your end.
150
+ - **A transient Apple "Internal Server Error" during App Store Connect API key creation** clears on a
151
+ simple retry — no code or configuration change needed.
152
+
153
+ ---
154
+
155
+ ## Google Play
156
+
157
+ Read this before anything else: **passing review on one Google Play track never promotes the binary to
158
+ any other track** (`MR-01`) — internal testing has no review at all, while closed, open, and production
159
+ each require one independently.
160
+
161
+ ### Initial one-time setup
162
+
163
+ 1. Create the Google Play Console app listing.
164
+ 2. **Set the country/region list on every track you plan to release to, before building that release** —
165
+ a fresh production track starts with zero countries and fails with a generic, un-actionable error
166
+ otherwise (`MR-02`).
167
+ 3. Reuse the App Store's listing copy and screenshots as a starting point — it keeps the two stores
168
+ consistent — but re-review each asset against the actually shipped binary before publishing; a
169
+ screenshot advertising a since-removed feature is a common miss from blind reuse. Image dimensions
170
+ differ between stores, so assets need re-export, not re-invention.
171
+ 4. Set up a Google Play service account and reference its key from `eas.json`'s
172
+ `submit.production.android.serviceAccountKeyPath`.
173
+ 5. Complete the IARC content-rating questionnaire one question at a time, and re-read the full form
174
+ after any change to the app's category (`MR-04`).
175
+ 6. Complete the data-safety declaration using "leaves the device" as the collection test, and confirm it
176
+ agrees with the App Store's own App Privacy answers for the same app (`MR-05`).
177
+ 7. Provide reviewer/demo-account access — one working account per app, described in prose for any
178
+ additional account types (`MR-07`).
179
+ 8. If Android developer verification is pending, correct the public developer address at its D-U-N-S
180
+ source and let it propagate **before** confirming it in the Play console — confirming first locks in
181
+ whatever address is currently on file (`MR-11`). Start this early: verification has a hard deadline,
182
+ and D&B propagation can take days to weeks.
183
+
184
+ ### Repeat deployments
185
+
186
+ ```bash
187
+ eas build --platform android --profile production
188
+ eas submit --platform android --profile production --latest
189
+ ```
190
+
191
+ **Saving a console edit is not submitting it** — Google Play stages edits until an explicit "send
192
+ changes for review" (or "save and publish") action, each with its own confirmation dialog (`MR-03`). A
193
+ workflow that stops at "Save" ships nothing.
194
+
195
+ Observed review latencies, for expectation-setting: internal testing is instant (no review); a first
196
+ closed-testing review is roughly one day; production review is quoted as "typically within 7 days," and
197
+ materially faster when the identical binary already cleared a closed-track review. Treat a release not
198
+ yet visible immediately after submission as this ordinary review latency, not a failure, absent a
199
+ console error stating otherwise (`EV-06`).
200
+
201
+ ### Known pitfalls
202
+
203
+ - **🔴 Play Console silently drops synthetic browser input.** Programmatic form input and coordinate
204
+ clicks frequently do not register — the control appears set, the save succeeds, and nothing persists
205
+ (seen on both app-access contact fields and country checkboxes). Before saving, read the page's own
206
+ state readout (a "Targeted (N)" filter chip, "Release notes provided for N languages," a track's
207
+ country-count summary); if the number disagrees with intent, hand the step to a human rather than
208
+ retrying the same automated input (`EV-01`/`EV-03`).
209
+ - **The "no deobfuscation file" warning** appears on every release and is benign and permanent until
210
+ mapping-file upload is wired up — it is not a sign of a broken build (`MR-08`).
211
+ - **🔴 The signing certificate an installed app presents can differ from every console page you'd
212
+ naturally check.** Play App Signing plus "automatic protection" can make anything keyed to a
213
+ `(package, SHA-1)` pair — Maps, Google Sign-In, App Links/`assetlinks.json` — fail on Play-delivered
214
+ installs while working on sideloads. Never infer the SHA-1; read it from `adb logcat` against an
215
+ actual Play-delivered install (`MR-09`, `EV-01`). Zero traffic in a provider's usage metrics is not
216
+ evidence the app isn't calling it — an auth failure can occur before any billable request is logged —
217
+ and a working browser-based OAuth flow proves nothing about certificate matching.
218
+ - **Android OAuth clients are keyed to one `(package, SHA-1)` pair each** — a Play-signed install, an
219
+ upload-signed sideload, and a `.dev` variant each need their own client. New clients also ship with
220
+ "Enable custom URI scheme" off, which fails with a visible `Error 400: invalid_request` until toggled
221
+ on (`MR-10`).
222
+ - **Audit what the built artifact actually declares** (`aapt2 dump permissions`, or an equivalent
223
+ manifest inspection) before submission — a dependency that's installed but no longer called still
224
+ contributes its permissions, and removing one means removing the dependency, re-verified against a
225
+ fresh build (`MR-12`).
226
+ - **Android notification icons need a dedicated monochrome, white-on-transparent small icon**, shipped
227
+ in a build — there's no OTA path for this. A "broken" icon symptom should be checked in the
228
+ notification shade, not the heads-up banner, since some OEM skins legitimately omit the small icon
229
+ from banners only (`MR-13`).
230
+ - **Android's native `AlertDialog` caps at three buttons.** A menu built on `Alert.alert` past that cap
231
+ silently loses the overflow — typically the *Cancel* row. Use a real bottom sheet instead for any menu
232
+ that could exceed three options (`MR-14`).
233
+ - **Locale lists differ between the stores.** Release notes are per-language; resolve locale coverage
234
+ against each store's own catalogue rather than copying the other store's list (`MR-06`).
235
+
236
+ ---
237
+
238
+ ## Rules
239
+
240
+ This spell defers entirely to
241
+ [governance/mobile-release-standards.md](../../.arcane/governance/mobile-release-standards.md) for what
242
+ each store-specific platform fact means and why, and to
243
+ [governance/external-verification-standards.md](../../.arcane/governance/external-verification-standards.md)
244
+ for how any claimed success is verified. Findings and steps in this spell cite rule IDs from both; they
245
+ do not re-explain them. Where the two disagree with what this spell says to do, the governance docs win.
246
+
247
+ ## Related
248
+
249
+ - **Consumes from:** whatever workflow builds and versions the app before deployment (in this
250
+ repository's own stack, `spell-implement`/`spell-ship`) — this spell deploys an already-built app to
251
+ the stores, it does not build the app's features.
252
+ - [Mobile Release Standards](../../.arcane/governance/mobile-release-standards.md)
253
+ - [External Verification Standards](../../.arcane/governance/external-verification-standards.md)
package/dist/index.js CHANGED
@@ -290,7 +290,9 @@ var PROFILE_CONFIGS = [
290
290
  "universal-agent-rules",
291
291
  "spell-authoring-standards",
292
292
  "external-verification-standards",
293
- "web-discoverability-standards"
293
+ "web-discoverability-standards",
294
+ "mobile-release-standards",
295
+ "compliance-standards"
294
296
  ]
295
297
  }
296
298
  ];
@@ -452,6 +454,16 @@ var COMPONENTS = [
452
454
  description: "Search-engine and AI-assistant discoverability rules for web properties",
453
455
  files: [".arcane/governance/web-discoverability-standards.md"]
454
456
  },
457
+ {
458
+ name: "mobile-release-standards",
459
+ description: "Durable App Store / Google Play console and release-track facts consumed by spell-eas-store-deploy",
460
+ files: [".arcane/governance/mobile-release-standards.md"]
461
+ },
462
+ {
463
+ name: "compliance-standards",
464
+ description: "GDPR/CCPA/SOC 2/HIPAA obligations and a tiered applicability guide, consumed by spell-compliance",
465
+ files: [".arcane/governance/compliance-standards.md"]
466
+ },
455
467
  // ─── Spells ────────────────────────────────────────────────────────────────
456
468
  // Split from the former monolithic `spell-prompts` + `claude-commands` pair
457
469
  // so a profile can select spells by capability (see ARC-0NN). Grouping lives
@@ -562,7 +574,11 @@ var COMPONENTS = [
562
574
  ".github/prompts/spell-make-discoverable.prompt.md",
563
575
  ".claude/commands/spell-make-discoverable.md",
564
576
  ".github/prompts/spell-scry.prompt.md",
565
- ".claude/commands/spell-scry.md"
577
+ ".claude/commands/spell-scry.md",
578
+ ".github/prompts/spell-eas-store-deploy.prompt.md",
579
+ ".claude/commands/spell-eas-store-deploy.md",
580
+ ".github/prompts/spell-compliance.prompt.md",
581
+ ".claude/commands/spell-compliance.md"
566
582
  ]
567
583
  },
568
584
  {