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.
- package/README.md +2 -2
- package/dist/assets/.arcane/governance/compliance-standards.md +238 -0
- package/dist/assets/.arcane/governance/mobile-release-standards.md +239 -0
- package/dist/assets/.claude/commands/spell-compliance.md +13 -0
- package/dist/assets/.claude/commands/spell-eas-store-deploy.md +13 -0
- package/dist/assets/.github/prompts/spell-compliance.prompt.md +129 -0
- package/dist/assets/.github/prompts/spell-eas-store-deploy.prompt.md +253 -0
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
{
|