publish-mobile-app
Skill2 starsChanged 21 days agoMITView on GitHub
Bootstrap an app-agnostic set of deploy scripts into an Expo React Native + web project and run the full lifecycle of publishing a mobile app to both the Apple App Store (TestFlight) and Google Play, plus store-screenshot capture.
- Reads credentials
In this repository15 other files it gives its agents.
- global/CLAUDE.mdCLAUDE.md
- black-octoberSkill
- branch-cascadeSkill
- build-orderSkill
- full-review-auditSkill
- meta-improveSkill
- newSkill
- pr-monitorSkill
- remindSkill
- reopen-prSkill
- research-spikeSkill
- setup-switch-loginSkill
- skill-syncSkill
- splice-youtube-quotesSkill
- switch-loginSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 14
All app identity is derived at runtime from the project's app.json (expo.name, expo.slug, expo.ios.bundleIdentifier, expo.android.package, …
- Very specific: line 79
If the app embeds an Apple Watch companion, npm run ios auto-chains the watch build after the phone build finishes, or you can run it …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Publish Mobile App
A skillset for every step of shipping a mobile app to both the Apple App
Store and Google Play. It generalizes a working set of deploy/screenshot scripts
(originally from an Expo React Native + Vite web monorepo) into reusable,
app-agnostic form, and ships an install.sh that drops them into any project and
wires the npm commands.
All app identity is derived at runtime from the project's app.json
(expo.name, expo.slug, expo.ios.bundleIdentifier, expo.android.package,
expo.ios.appleTeamId) and from .env — nothing app-specific is hardcoded. The
Android upload keystore is namespaced per app slug so two apps never collide on
one key.
Use when
- The developer wants to set up App Store / TestFlight and Google Play publishing
for an Expo React Native app (typically in a
mobile/subdirectory of a web monorepo), from scratch or on a new machine. - They ask to build and upload an iOS TestFlight build or a signed Android
.aab. - They want to capture App Store / Play Store marketing screenshots from a web
?shot=1mode. - They want to bump the app version / build numbers before a release.
Procedure
The lifecycle runs from install → .env → per-platform setup+upload →
screenshots → version bumps. The scripts are interactive and safe to re-run.
Install into the project. From the target project root:
/path/to/publish-mobile-app/install.sh --mobile-dir mobilePreview first with
--dry-run. This copies the deploy scripts into<project>/mobile/scripts/, the screenshot runner into<project>/scripts/, a.env.exampleinto<project>/mobile/(only if absent), and adds npm scripts (setup:env,setup:ios,setup:android,ios,upload:ios,upload:androidto the mobile package;shotsto the root package) without clobbering existing scripts. It is idempotent.Fill in
.env. From the mobile dir:cd mobile && npm run setup:env # creates .env from .env.exampleThen edit
.env. The account-level Apple values (APPLE_API_KEY_ID,APPLE_API_ISSUER_ID,APPLE_API_KEY_PATH,APPLE_TEAM_ID) are the same for every app and can be copied from a sibling project. The app-level Android keystore is unique per app — leave its password fields empty to have it generated automatically. See/path/to/publish-mobile-app/references/store-setup.mdfor obtaining the Apple API key. Confirmapp.jsonhasexpo.name,expo.slug,expo.ios.bundleIdentifier,expo.android.package, andexpo.ios.appleTeamId.iOS: setup + upload. iOS builds require macOS + Xcode.
npm run setup:ios # toolchain preflight, expo prebuild, optional signing npm run upload:ios # bump, archive, export, upload to TestFlightsetup:iosrunsdoctor-ios.sh(installs missing simulator platforms), generates the nativeios/project, and optionally walks through Xcode signing (needed only for device/TestFlight, not the Simulator).upload:iosbumps the version, cleans+prebuilds, generatesExportOptions.plistfrom the runtime Team ID + bundle id, archives withxcodebuild, and uploads withxcrun altool. The build will not go live until you submit it in App Store Connect.If the app embeds an Apple Watch companion,
npm run iosauto-chains the watch build after the phone build finishes, or you can run it standalone:npm run watch # build + install + launch the watch app on a paired watchOS Simulatorrun-watch.shdiscovers the workspace fromios/, defaults the watch scheme to<app scheme>Watch(override withWATCH_SCHEME=), pairs a watchOS Simulator to a booted iPhone, and installs/launches the companion. It is a no-op for apps without a watch target. The companion is phone-driven, so keep the iPhone app running for live data.Android: setup + upload.
npm run setup:android # create Play app, (optional) service account, keystore npm run upload:android # bump, prebuild, sign, build .aab, print upload stepssetup:androidopens the Play Console, documents the corrected Google Cloud service-account flow (optional; Play uploads here are manual), and generates the per-app upload keystore at~/.private_keys/android/<app-slug>-upload-keystore.jks, writing its random passwords back into.env.upload:androidbuilds a signed.aabintoreleases/and prints step-by-step Play Console upload instructions. Play uploads are manual — no Google service-account key is required.Store screenshots. With the app's
?shot=1web build served locally, from the project root:npm run shotsThe runner (
shots.mjs) drives?shot=1&screen=..&theme=..&accent=..&layout=..and writes on-spec PNGs tostore-screenshots/. The shot list is read from an optionalshots.config.json; the app-side shot mode is app-specific. See/path/to/publish-mobile-app/references/screenshots.md.Version bumps. Both upload scripts call
bump-version.shautomatically, but you can run it directly:cd mobile && bash scripts/bump-version.sh [major|minor|patch|overwrite]It updates
app.json(version,android.versionCode,ios.buildNumberin lockstep) andpackage.json.overwritekeeps the marketing version but increments the build number so TestFlight accepts a rebuild.
Scripts
Installed into <project>/mobile/scripts/ (screenshot runner into
<project>/scripts/):
/path/to/publish-mobile-app/scripts/setup-ios.sh— one-time iOS onboarding: toolchain preflight,expo prebuild, optional Xcode signing./path/to/publish-mobile-app/scripts/doctor-ios.sh— non-interactive iOS toolchain diagnostics; auto-installs missing iOS/watchOS simulator platforms./path/to/publish-mobile-app/scripts/run-ios.sh— dev entrypoint (npm run ios); first-run onboarding gate, thenexpo run:ios; auto-chains the watch build when present./path/to/publish-mobile-app/scripts/run-watch.sh— build/install/launch the Apple Watch companion on a paired watchOS Simulator (npm run watch); no-op without a watch target./path/to/publish-mobile-app/scripts/generate-export-options.sh— writesios/ExportOptions.plistfrom the runtime Team ID + bundle id./path/to/publish-mobile-app/scripts/upload-ios.sh— bump → prebuild → archive → export → upload to TestFlight./path/to/publish-mobile-app/scripts/setup-android-upload.sh— Play Console + optional service-account docs + per-app keystore generation./path/to/publish-mobile-app/scripts/upload-android.sh— bump → prebuild → sign → build.aab→ manual Play upload instructions./path/to/publish-mobile-app/scripts/fix-android-signing.js— patchesandroid/app/build.gradleto use the release signing config./path/to/publish-mobile-app/scripts/bump-version.sh— bumpsapp.json/package.jsonversion + build numbers./path/to/publish-mobile-app/scripts/shots.mjs— Playwright store-screenshot capture driving?shot=1web mode.
Templates and references:
/path/to/publish-mobile-app/templates/.env.example— account-level vs app-level deploy-credential template./path/to/publish-mobile-app/references/store-setup.md— Apple API key + corrected Google Play service-account/API flow + listing answers./path/to/publish-mobile-app/references/screenshots.md— the?shot=1pattern,shots.config.jsonshape, and store size/aspect specs.
Dependencies
expo/easCLI (vianpx expo) — prebuild and run.xcodebuild,xcrun altool— iOS archive/export/upload (macOS + Xcode only).keytool,openssl— Android upload keystore generation.node+python3— JSON edits inbump-version.shandinstall.sh.sips— image resizing (if you post-process screenshots).- Playwright — screenshot capture (
PLAYWRIGHT_PATHif it lives in the npx cache).
Notes / gotchas
- iOS is macOS-only.
doctor-ios.shaborts on non-Darwin and installs missing simulator platforms (SKIP_PLATFORM_DOWNLOAD=1to opt out). It also installs the watchOS platform when the app embeds an Apple Watch companion (detected viawithWatchAppinapp.jsonor a native*Watch*target). - The Android upload key is irreplaceable. It is generated once at
~/.private_keys/android/<app-slug>-upload-keystore.jks, its passwords are written to.env, and the scripts never overwrite an existing keystore. Back up both the file and.env. Losing the key means recovery only via Google Play support. - Play uploads are manual by design —
upload:androidbuilds and signs the.aaband prints Play Console steps. There is noGOOGLE_SERVICE_ACCOUNT_KEY_PATHvariable; the service-account docs are only for building your own automation later. - Neither store goes live automatically. iOS must be submitted from App Store Connect after processing; Android needs a production release created from the Production track after closed testing.
- Version/build lockstep:
bump-version.shkeepsios.buildNumberequal toandroid.versionCodeso anoverwriterebuild still uploads a unique build number (TestFlight rejects a reused version+build pair). - Monotonic versionCode:
bump-version.shwrites a.version-codehigh-water-mark file in the mobile dir so the next build's code always clears what's already uploaded, even if the marketing version was reset (Play/TestFlight only move forward). Add.version-codeto your mobile dir's.gitignore— it's machine-local build state, not source. - The screenshot harness is the least generalizable piece. The runner is
generic, but the
?shot=1mode andshots.config.json(screens, accents, selectors) are app-specific and must be supplied per app. install.shnever clobbers existing npm scripts — it warns and skips any key already present inpackage.json.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

