Skip to content
Agentmart
Submit a tool

expo-prebuild-and-config-plugins

Keeps ios/ and android/ generated from app.config.ts and tested config plugins.

Skill by zakariaf

IndexedMIT★ 11
Write a review

Chalk Labs

Need help deploying expo-prebuild-and-config-plugins?

Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.

Book a call

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/zakariaf-expo-prebuild-and-config-plugins

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Keeps ios/ and android/ generated from app.config.ts and tested config plugins. Use when adding a plugin, an Info.plist or AndroidManifest entry, or a prebuild step. Not for native code (native-modules-and-new-architecture).

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Node.js package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/expo-prebuild-and-config-plugins

What's inside

Skill name
expo-prebuild-and-config-plugins
Description
Keeps ios/ and android/ generated from app.config.ts and tested config plugins. Use when adding a plugin, an Info.plist or AndroidManifest entry, or a prebuild step. Not for native code (native-modules-and-new-architecture).
License
MIT
Instructions
189 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (40)

  • SKILL.md
  • auto-plugins.json · assets/
  • shared.json · assets/
  • app-config-plugin-list.ts · examples/
  • native-change-report.md · examples/
  • with-gradle-jvm-args.ts · examples/
  • app-config.md · references/
  • cng-and-native-folders.md · references/
  • config-plugins.md · references/
  • pitfalls-and-rules.md · references/
  • testing-and-proof.md · references/
  • check-lib.mjs · scripts/
  • check-native-generation.mjs · scripts/
  • check-native-snapshot.mjs · scripts/
  • cng-scan.mjs · scripts/lib/
  • source-scan.mjs · scripts/lib/
  • selftest.mjs · scripts/
  • native-generation.json · templates/contracts/
  • with-android-optional-features.test.ts · templates/plugins/
  • with-android-optional-features.ts · templates/plugins/
  • run-mod.test.ts · templates/test/config-plugins/
  • run-mod.ts · templates/test/config-plugins/
  • app.config.ts · tests/bases/app/
  • ci.yml · tests/bases/app/dot-github/workflows/
  • dot-gitignore · tests/bases/app/
  • jest.config.js · tests/bases/app/
  • package.json · tests/bases/app/
  • collect-artifacts.sh · tests/bases/app/scripts/build/
  • read-gradle-version.ts · tests/bases/app/scripts/release/
  • task-title.ts · tests/bases/app/src/features/tasks/
  • app.config.ts · tests/bases/snapshot/
  • native-config.json · tests/bases/snapshot/contracts/
  • introspect.json · tests/bases/snapshot/
  • package.json · tests/bases/snapshot/
  • CASES.json · tests/fixtures/check-native-generation/
  • SUITE.json · tests/fixtures/check-native-generation/
  • EXPECT.txt · tests/fixtures/check-native-generation/good/
  • CASES.json · tests/fixtures/check-native-snapshot/
  • SUITE.json · tests/fixtures/check-native-snapshot/
  • EXPECT.txt · tests/fixtures/check-native-snapshot/good/

SKILL.md (first lines)

# Expo prebuild and config plugins

Continuous Native Generation: `ios/` and `android/` are build output of `app.config.ts`, its config
plugins and the installed native packages. Every native change is a config key or one line in the
plugin list; local plugins are typed, idempotent and tested; the generated folders stay out of git
and out of every script's reach; a committed snapshot of the introspected native config makes each
native change visible in review. Two checkers prove it. Leaving this model is a recorded decision.

## Rules that must hold

1. **`app.config.ts` is the one config, and a pure function.** No `app.json`, `app.config.json` or
   second `app.config.*`; it returns the object synchronously, the same environment gives the same
   JSON, it holds no `null`, and it applies no plugin in code. Why: Expo reads `.ts` first and ignores
   the rest; a Promise is refused; the fingerprint hashes the config; `null` reaches the app as `{}`
   (all verified on SDK 57). (`single-config`, `config-evaluates`, `config-deterministic`,
   `config-null`, `config-mods`)
2. **`ios/` and `android/` are generated output.** `.gitignore` (and `.easignore`, which replaces it
   for EAS) ignores `/ios` and `/android`, anchored; git tracks nothing there; no npm script, `scripts/`
   file or CI step writes into them. Why: SDK 57's prebuild deletes them by default, EAS prebuilds
   only when they are absent, and an unanchored `ios/` also hides a local module's Swift code.
   (`native-ignored`, `native-tracked`, `native-ignore-scope`, `easignore-native`, `native-edit`)
3. **A config key before a plugin.** `ios.infoPlist`, `ios.entitlements`, `locales`,
   `android.permissions`/`blockedPermissions`, `ios.deploymentTarget` and expo-build-properties
   (SDK versions, `manifestQueries`, cleartext, frameworks) cover most needs; a plugin only for what
   none covers. Why: keys are validated, introspectable and maintained by Expo across upgrades.
   (`builtin-first`)
4. **Nothing reaches the native projects implicitly.** Every installed package whose plugin prebuild
   applies on its own (`assets/auto-plugins.json`) is listed, with the options the app means, in every
   variant; no plugin is listed twice; a permission a plugin adds whatever its options (expo-file-system's
   storage pair, which every app gets through `expo`) is removed only by `android.blockedPermissions`
   (exact case, `WRITE_EXTERNAL_STORAGE`): block it unless the app needs it (shared-storage access).
   Why: the defaults add push and Sign in with Apple entitlements, permissions and English purpose
   strings (seen in the reference app), and a run-once plugin silently drops a second entry's options
   (verified). (`auto-plugin-listed`, `plugin-duplicate`)
5. **Local plugins live in `plugins/with-<capability>.ts`, listed by path.** Default export (a typed
   `ConfigPlugin<Options | undefined>`, tagged `@public`), Expo's helpers from `'expo/config-plugins.js'`
   on SDK 57 (bare from SDK 58), never `@expo/config-plugins`. Why: `npx expo config` then shows the
   file; the bare specifier fails with `ERR_MODULE_NOT_FOUND` once Node imports the file as an ES
   module (verified). (`plugin-entry`, `plugin-import`)
6. **Typed mods first; text mods merge; dangerous mods are justified.** A text mod edits through
   `CodeGenerator.mergeContents` or behind an `includes` check (inline or in a `plugins/lib/` helper);
   `withDangerousMod` carries `// dangerous-mod: <reason>`, writes files only inside it (or helpers
   it calls), and writes them whole or guards an append. Why: only typed mods are
   introspected; dangerous mods run first and break silently when the SDK template changes.
   (`dangerous-mod`, `text-mod-idempotent`)
7. **Every plugin is idempotent and validates its options.** Applying it twice equals once; a bare
   entry or a bad option throws a message showing the right entry. Why: `prebuild --no-clean` and
   `expo run:*` rerun mods onto existing files (verified: an appending plugin left 1, 2, 3 copies).
8. **Every local plugin has a sibling test that imports it and that Jest runs, through Expo's own mod chain.** `runMod`
   (`test/config-plugins/run-mod.ts`), a fresh config per test, a second run, bad options, the other
   platform untouched; the Jest config's test roots include `plugins/`. Why: Expo ships no plugin
   test harness, the `with*` helpers mutate the config they get, and an unreached test passes silently.
   (`plugin-test`)
9. **One owner per native key, and a deliberate order.** Mods run newest first: the plugin listed
   first in `plugins` writes last and wins a shared key (verified). Two plugins on one file are tested
   together. Why: otherwise a reorder silently changes the app.
10. **Native changes are proven by introspection and reviewed as a snapshot.** `contracts/native-config.json`
    is regenerated with `--write` only after a deliberate change and committed with it. Why: the
    generated folders never appear in a pull request; the snapshot shows a new permission or entitlement
    from any plugin or package. (`native-snapshot`, `native-drift`)
11. **Prebuild is clean in scripts and CI, and before every local build.** Never `--no-clean` in a
    script; `npx expo prebuild --clean` before `expo run:*` or a local release build after any config,
    plugin, package or variant change. Why: `expo run:*` builds an existing folder as it is (verified);
    a layered prebuild depends on earlier runs. (`prebuild-clean`)
12. **Leaving CNG is a recorded decision.** `contracts/native-generation.json` `"mode": "committed"`
    with a `reason` and `decidedOn`, both folders committed, no prebuild in any script; native keys
    then change by hand. Why: committed folders and prebuild-only keys drift apart (expo-doctor reports
    it). (`native-mode`)

## Workflow

1. **Place the change.** Read [references/app-config.md](references/app-config.md) ("Built-in keys
   before plugins", "Auto-applied plugins"). A key covers it: edit `app.config.ts` and go to step 6.
   A new native package: dependency-management installs it, then list its plugin here (rule 4).
2. **First time in a repo:** read [references/cng-and-native-folders.md](references/cng-and-native-folders.md);
   copy `templates/contracts/native-generation.json` to `contracts/`; anchor `/ios` and `/android` in
   `.gitignore`; make sure the Jest config's test roots include `'<rootDir>/plugins/**/'`
   (unit-and-component-tests owns `jest.config.js`); copy `templates/test/config-plugins/run-mod.ts`
   and its test. Then `node ${CLAUDE_SKILL_DIR}/scripts/check-native-snapshot.mjs . --write` and read
   every key of `contracts/native-config.json`: it is the audit of what prebuild writes today.
3. **Write the plugin** when no key covers the need: read
   [references/config-plugins.md](references/config-plugins.md); copy
   `templates/plugins/with-android-optional-features.ts` and its test as the shape (or keep them when
   the app needs optional hardware); [examples/with-gradle-jvm-args.ts](examples/with-gradle-jvm-args.ts)
   shows a gradle.properties mod. Pure transform, option validation, one typed mod per file.
4. **Test it** per [references/testing-and-proof.md](references/testing-and-proof.md) (levels 1 and 2):
   `npx jest --ci --selectProjects unit --runTestsByPath plugins/<file>.test.ts`, and
   `npx jest --listTests | grep plugins/` lists it.
5. **List it** in `app.config.ts` by path, with its options and a comment naming the owning skill, the
   way [examples/app-config-plugin-list.ts](examples/app-config-plugin-list.ts) does. Variant-dependent
   options are computed in the config (app-variants-and-environment owns the variants).
6. **Prove it:** `APP_VARIANT=<v> EXPO_PUBLIC_APP_VARIANT=<v> npx expo config --type introspect --json`
   for each variant; `node ${CLAUDE_SKILL_DIR}/scripts/check-native-snapshot.mjs .` and read every
   `native-drift` line; when each one is intended, rerun with `--write`. Text or dangerous mods also
   get `npx expo prebuild --clean --no-install --platform <p>`, then the same with `--no-clean`, and a
   look at the generated file (the change appears once).
7. **Run the app's gates:** `npm run typecheck`, `npx eslint . --max-warnings 0`, `npx jest --ci`.
8. **Check:** `node ${CLAUDE_SKILL_DIR}/scripts/check-native-generation.mjs .` and the snapshot check.
   Each `FAIL` names the file, the rule and the fix; [references/pitfalls-and-rules.md](references/pitfalls-and-rules.md)
   explains every rule and error text. Fix and rerun until both print `RESULT: PASS`.
9. **Leaving or returning to CNG:** stop and ask the developer first; it changes how every later
   native change is made. Then follow "When to leave CNG" or "Coming back to CNG" in
   [references/cng-and-native-folders.md](references/cng-and-native-folders.md).
10. **Report** like [examples/native-change-report.md](examples/native-change-report.md): what changed,
    the snapshot diff, the proof commands and every `RESULT` line, and what a build still has to show.

## Definition of done

- [ ] `app.config.ts` is the only app config; it evaluates for every variant, twice the same, without `null`.
- [ ] Every native change is a config key or one entry in `plugins`; every installed auto-applied package is listed; no entry twice.
- [ ] Local plugins sit in `plugins/`, listed by path, default-export a typed plugin, import `expo/config-plugins.js` (SDK 57), use typed mods, guard text mods, justify dangerous ones.
- [ ] Each plugin's sibling test passes, applies the plugin twice, covers bad options, and `npx jest --listTests` lists it.
- [ ] Introspection reviewed for each variant; `contracts/native-config.json` regenerated on purpose and committed with the change.
- [ ] Text or dangerous mods: a clean and then a `--no-clean` prebuild show the change once.
- [ ] `.gitignore` ignores `/ios` and `/android` (anchored); git tracks nothing there; no script writes there or runs `--no-clean`; or the committed mode is recorded with its reason.
- [ ] Expected SKIP line only while the app has no Jest config: `SKIP jest.config.js [plugin-test] not yet created (unit-and-component-tests creates it)`. `note:` lines are information, not failures.
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-native-snapshot.mjs .` prints `RESULT: PASS`
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-native-generation.mjs .` prints `RESULT: PASS`

From zakariaf/ReactNative-Skills, MIT.

More from zakariaf/ReactNative-Skills

Signals

Stars
11
Forks
2
Open issues
0
Last commit (this folder)
Oct 17, 2026 (14 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from zakariaf

Done for you

Rather not set it up yourself?

Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.

See how we build AI agents →Read the case studies

Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.

launches shipped
50+
launches shipped
media placements
500+
media placements
founders on every account
2
founders on every account

Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block

Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.

Get expo-prebuild-and-config-plugins running in your business

Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.

Goes to the Chalk Labs founders, not the newsletter. Privacy

Need help deploying expo-prebuild-and-config-plugins?

CallGet help
Cite this page

expo-prebuild-and-config-plugins skill. Agent Mart. https://agentmart.dev/skills/zakariaf-expo-prebuild-and-config-plugins (last updated 2026-10-24)