Skip to content
Back to skills

Expo Project Bootstrap

ASecurity

Creates a new Expo app from empty - SDK 57 stack, Expo Router, root configs, gates, variants, icons - proven by a checker. Use when bootstrapping, setting up or scaffolding an app or repo. Not for adding packages (dependency-management).

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 18, 2026
developmenttypescriptrustgojavakotlinreactnodegit

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 19 files and shows the line behind each finding

Scanned October 18, 2026

npx -y skills add zakariaf/ReactNative-Skills --skill expo-project-bootstrap --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Expo Project Bootstrap?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Expo Project Bootstrap
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zakariaf-expo-project-bootstrap/badge)](https://www.skillsdirectory.com/skills/zakariaf-expo-project-bootstrap)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: expo-project-bootstrap
description: Creates a new Expo app from empty - SDK 57 stack, Expo Router, root configs, gates, variants, icons - proven by a checker. Use when bootstrapping, setting up or scaffolding an app or repo. Not for adding packages (dependency-management).
---

# Expo project bootstrap

Turns an empty (or docs-only) folder into an Expo app with the canonical stack: one app at the repo
root with Expo Router in `src/app/`, every root config, the gate scripts, the dependency policy,
`app.config.ts` with three variants for iOS and Android, a starter route and screen, and placeholder
art. Everything comes from templates that were installed and run end to end; every gate is green on
day one, and a checker proves the skeleton stays that way. The multi-package form is fenced under
"When multi-package" in `references/project-layout.md`.

## Rules that must hold

1. **Generate the skeleton with `scaffold-app.mjs`; never type a config by hand.** Each template was
   installed and run (tsc, ESLint, Prettier, Jest, knip, `expo config`); a hand-typed config drifts
   from the gates on the first character, and the guardrail then fails for a reason nobody sees.
2. **Bootstrap at the repo root, inside a git repository, and leave pre-existing folders where they
   are.** `npm install` installs the git hooks only in a repository, and agents load settings from
   the root. Folders that predate the app (docs, design exports) go into `.prettierignore`, ESLint's
   `PRE_EXISTING` and the root tsconfig `exclude`, never moved or reformatted. (`format-ignore`,
   `lint-ignore`, `tsconfig-program`)
3. **Never overwrite a file that differs from its template without reading both.** The generator
   reports a conflict instead of writing; `.gitignore` gains missing lines, `.claude/settings.json`
   keeps the developer's keys and allow rules, `CLAUDE.md` gains the `@AGENTS.md` import. Extra deny
   or ask rules or hooks are a question for the developer, because the guardrail compares them exactly.
4. **Start from this skeleton, not from `create-expo-app`'s demo.** `app.json`, the demo tabs and
   components, `scripts/reset-project.js`, a second routes folder and a second tool config
   (`eslint.config.js` from `npx expo lint`, `prettier.config.*`) are leftovers: they ship literal
   strings, or a config that Expo merges or that its tool loads before the canonical one. Move what
   is needed, delete the rest; never ignore them. (`template-leftover`)
5. **One identity, three variants, both platforms.** The production id is the reverse-DNS id the
   developer registers in App Store Connect and Google Play, valid as an iOS bundle id and an Android
   package (lower case, dot-separated, each part starting with a letter, no Kotlin or Java keyword);
   `development` and `preview` append `.dev` and `.preview`, so all three install side by side.
   `app.config.ts` sets `ios.bundleIdentifier`, `android.package` and the adaptive icon from it, and
   every icon it names exists. A `com.example` id is allowed only until the first store build. (`app-id`, `platforms`,
   `app-files`)
6. **Install exactly the policy's versions, with a committed lockfile.** Specifiers come from
   `contracts/dependency-policy.json`: Expo SDK modules keep the `~` that `npx expo install` writes,
   everything else is exact at the policy's version, React and React Native included. `overrides` pins all ten
   `@typescript-eslint/*` packages at the `typescript-eslint` pin: a second copy crashes every ESLint
   run with `Cannot redefine plugin "@typescript-eslint"`. (`pin-exact`, `root-overrides`,
   `one-react`, `lockfile`)
7. **Install only what the skeleton uses.** knip fails on an unused dependency, and a package
   installed early ages without review; every later package arrives with the skill that first imports
   it, through dependency-management. (`banned` keeps the policy's banned and doNotAdd lists out.)
8. **Keep the `.npmrc` policy whole; a dated exclude lives only until its `expires=` date.** npm keeps
   the last value of a key, so a later `min-release-age=0` silently switches the policy off; an
   exclude outside a dated block, or past its date, is a problem. (`npmrc-policy`, `npmrc-exclude`)
9. **Approve install scripts one package at a time, after reading them.** `strict-allow-scripts` stops
   an install on an unreviewed script; an approval is pinned to the reviewed version. Never
   `--dangerously-allow-all-scripts`. (`install-scripts`)
10. **Never stub a missing gate script to make `npm run verify` pass.** `i18n:verify` waits for
    i18n-strings-and-catalogs; report it as pending. A gate that passes without checking hides every
    later failure. (`tooling-files` for the scripts the skeleton does own)
11. **Agents get one contract.** `AGENTS.md` (start green with `npm run -s check:fast`, stop and ask,
    never bypass a hook, devices always named) and `CLAUDE.md` = `@AGENTS.md`. An app nested in a
    larger repository may take them from there (`"agentInstructions": "parent-repo"`; only up to that
    repository's top level counts). (`agents`)
12. **Stop and ask for human steps.** Xcode and its licence, Android SDK licences, `sudo`, store
    accounts, signing credentials and trusting the folder in the agent are the developer's; ask once,
    name the step and the default that applies meanwhile. Never read or create a signing key.
13. **The first commit stages exactly the generated files, with a `Gate-Change:` trailer, through the
    hooks; nothing is pushed unless the developer asks.** Never `git add -A`: the folder may hold the
    developer's own uncommitted work.

The checker also guards what the other skills own inside the skeleton, so a bootstrap cannot drift
from them: the canonical scripts (`root-scripts`), Prettier and TypeScript options (`prettier-config`,
`tsconfig-base`), the gate wiring (`knip-config`, `lefthook`, `quality-gates`, `gate-scope`,
`stryker-config`, `claude-settings`), the app config (`app-config`, `metro-cache`, `app-env`) and
unfilled template tokens (`placeholder`). Each rule's reason is in `references/root-files.md`.

## Workflow

1. **Toolchain.** Read [references/toolchain-setup.md](references/toolchain-setup.md) and run its
   "Checks before installing" (Node 26.4.0, npm 11.17.0, git; Xcode and the Android SDK only from the
   first native build). Ask the developer for any human step that is missing.
2. **Identity.** Ask the developer for the display name and the store id (`com.<org>.<app>`); the slug
   and scheme default from the name. Read [references/project-layout.md](references/project-layout.md)
   so the tree, the left-out parts and the pre-existing entries are clear. Run `git status`; `git init`
   only if the folder is not a repository yet; never discard work you did not make.
3. **Plan without writing:** `node ${CLAUDE_SKILL_DIR}/scripts/scaffold-app.mjs . --app-id com.acme.tasks --name "Tasks"`.
   Read the plan: the pre-existing entries it will ignore, the merges, every `conflict` and `leftover`.
4. **Resolve** each conflict with [references/root-files.md](references/root-files.md) ("Merging an
   existing repo"): edit the file to the template content, or rerun with `--replace <file>` when it is
   only an old draft; ask the developer when a settings rule would be dropped. Delete each leftover
   after moving what the app still needs (an `app.json` value goes into `app.config.ts`).
5. **Write:** the same command with `--write`. Rerun the dry run: it must print `0 new, 0 merged` and
   `RESULT: PASS`.
6. **Install** exactly as [references/first-install-and-verify.md](references/first-install-and-verify.md)
   says ("Install and approve install scripts"): `npm install`, read each pending script, approve one
   package at a time until `npm approve-scripts --allow-scripts-pending` lists none; `npm ls react
   react-native @typescript-eslint/eslint-plugin` shows one version each.
7. **Gates:** every gate one by one, with no format step first ("The gate runs":
   `export:audit` is the only one that bundles through Metro, then `audit:licenses` reads its source
   maps) and the Expo config for the three variants on both platforms ("The Expo config for every
   variant"). Fix the cause of a failure in the files; a gate is never loosened.
8. **Check:** `node ${CLAUDE_SKILL_DIR}/scripts/check-skeleton.mjs .`; fix every `FAIL` line (each
   names the file, the rule and the fix) and rerun until `RESULT: PASS`.
9. **Commit** as "The first commit" says (exact paths, `Gate-Change: initial quality gates`, hooks run).
10. **Report** in the shape of [examples/bootstrap-report.md](examples/bootstrap-report.md): outcome,
    at most one request, what to look at, the pending gate in plain words, then the numbers. Hand off
    in the order of "What the bootstrap leaves out" (project-layout.md).

## Definition of done

- [ ] `scaffold-app.mjs . --app-id <id> --name <name>` prints `0 new, 0 merged` and `RESULT: PASS`
      (no conflict, no leftover); pre-existing folders are untouched and ignored by Prettier, ESLint
      and the root tsconfig.
- [ ] `package-lock.json` is committed; `npm approve-scripts --allow-scripts-pending` lists no
      package; `npm ls react react-native @typescript-eslint/eslint-plugin` shows one version each.
- [ ] `npm run -s check:fast`, `npm run -s lint`, `npm run -s typecheck`, `npm run -s test:coverage`
      (thresholds met), `npm run -s check:unused`, `npm run -s export:audit` (iOS and Android
      bundles), `npm run -s audit:licenses`, `node scripts/quality/check-quality-gates.ts` and
      `npm run -s check:deps` pass; `npx lefthook validate` prints `All good`.
- [ ] `npx expo config --type public` resolves for `development`, `preview` and `production` (each
      with the same `APP_VARIANT` and `EXPO_PUBLIC_APP_VARIANT`), with three ids, names, schemes and
      icons on iOS and Android; `APP_VARIANT=production` alone fails.
- [ ] The first commit went through the hooks with a `Gate-Change:` trailer; nothing was pushed
      without the developer.
- [ ] The report names the pending gate (`i18n:verify`, i18n-strings-and-catalogs) and the next
      skills in order.
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-skeleton.mjs .` prints `RESULT: PASS` (a `com.example`
      id adds one `NOTE ... [app-id]` line; there are no SKIP lines).

## Anti-patterns

- **Running `create-expo-app` and trimming the demo by hand.** Leftovers survive (`app.json`, the
  tabs, `src/components/`), and the configs miss the gates. Generate from this skill; on top of an
  existing template app, the plan lists every leftover.
- **Installing the whole stack on day one.** knip fails on a dozen unused dependencies; each package
  arrives with its first user, at the policy's version.
- **Dropping an `overrides` row, or excluding `typescript-eslint` from the release age.** A fresh
  install then takes whatever npm published that morning; on 2026-09-29 that was a one-day-old
  `@typescript-eslint` 8.71.0 next to 8.70.1, and ESLint crashed on every run.
- **Deleting `package-lock.json` or loosening `.npmrc` when an install fails with `ETARGET`.** The
  version is younger than the release age; use dependency-management's dated-exception procedure.
- **Approving install scripts with `--dangerously-allow-all-scripts`, or `npm audit fix --force`.**
  Read each script; `npm audit` is information (the force fix moves `expo` off the SDK).
- **Adding a stub `verify-catalogs.ts` so `npm run verify` goes green.** Report the pending gate.
- **Editing `quality-gates.json` to match a changed config.** The guardrail exists to catch exactly
  that; restore the config, or change both with the developer and a `Gate-Change:` trailer.
- **Reformatting or linting the developer's pre-existing folders**, or ignoring app code to silence a
  gate: only folders that are not app code go in the ignore lists.
- **Committing `ios/` or `android/`.** They are generated by `npx expo prebuild` (Continuous Native
  Generation); native changes go through `app.config.ts` and config plugins.
- **Selecting Xcode with `sudo xcode-select`, or building on "the booted simulator".** Use
  `DEVELOPER_DIR`, and name every simulator, emulator and device.

## Files in this skill

| File | What it is | Read/run when |
|---|---|---|
| [references/toolchain-setup.md](references/toolchain-setup.md) | Node, npm, git, Xcode by `DEVELOPER_DIR`, CocoaPods, JDK 17 and Android SDK 36, human steps, checks, prebuild facts for both platforms | Workflow step 1 |
| [references/project-layout.md](references/project-layout.md) | The tree, what is left out and which skill adds it, who maintains each file, pre-existing entries, leftovers, When multi-package | Workflow step 2, and when a skill asks where a file goes |
| [references/root-files.md](references/root-files.md) | Every root file and the reasons behind it; merging an existing repo | Workflow step 4, and when a check names a config |
| [references/first-install-and-verify.md](references/first-install-and-verify.md) | Install and approvals, the gate runs, the variant configs, other skills' checkers on a fresh skeleton, the first commit, pending gates, traps, the verified run | Workflow steps 6 to 9 |
| [examples/bootstrap-report.md](examples/bootstrap-report.md) | A complete report after a bootstrap | Workflow step 10 |
| `scripts/scaffold-app.mjs` | Generator: dry-run plan, `--write`, `--replace <file>`, `--compare`; rules `conflict`, `leftover`, `differs` | Workflow steps 3 to 5 |
| `scripts/check-skeleton.mjs` | Checker for the whole skeleton (the rule ids above; when multi-package also `workspace-layout`, `package-manifest`, `app-lockstep`, `root-runtime`) | Workflow step 8, and after any change to a root config |
| `scripts/lib/bootstrap-plan.mjs` | Template mapping, rendering, merges, pre-existing entries, leftovers, placeholder art | Read only to change the generator |
| `scripts/lib/repo-read.mjs` | JSONC, `.npmrc` policy and dated-exclude readers | Read only to change the checker |
| `scripts/lib/ship-placeholders.mjs` | The placeholder values the ship gates refuse (synced; do not edit here) | Read to see why `com.example` is only a NOTE here |
| `scripts/check-lib.mjs` | Shared script helper (synced; do not edit here) | Never by hand |
| `scripts/selftest.mjs` | Proves both scripts: generator cases, and the checker on a skeleton generated at test time (single app, multi-package, nested) with a planted bug per rule | After changing a script or a template |
| `templates/` | Every file of the skeleton: the canonical root configs, `contracts/`, `scripts/` (gates, hooks, git, deps, audit, clock, metro), `src/` (env types, app-variant, starter layout, route and screen), `app.config.ts`, `metro.config.js`, `AGENTS.md`, `CLAUDE.md`, `dot-nvmrc` (a `dot-` file name becomes `.`; synced copies are listed in `assets/shared.json`) | The generator writes them; read one when resolving a conflict |
| `assets/skeleton-package.json` | The canonical skeleton manifest (scripts, dev tools, overrides, approvals; synced) | The generator renders `package.json` from it |
| `assets/claude-settings.json` | The canonical `.claude/settings.json` (synced) | The generator writes or merges it |
| `assets/app-dependencies.json` | The skeleton's runtime packages and the dev tools it leaves out until first use | When the skeleton's dependency set changes |
| `assets/template-placeholders.json` | Placeholder values for the template harness | Maintainers only |
| `assets/shared.json` | The shared files copied into this skill | When adding a shared file |
| `tests/fixtures/` | Generator cases and the planted bugs for the checker (`mutation.json` on the generated skeleton) | When adding a rule |

## Related skills

- `react-native-conventions-index` - the build order after the bootstrap.
- `dependency-management` - every package after the bootstrap, the policy file, dated exceptions.
- `quality-gates` - what each gate checks and how to fix a failing one.
- `typescript-and-lint-rules` - the tsconfig set, the ESLint config, Prettier, pre-existing entries.
- `architecture-and-boundaries` - where files go; the composition root.
- `app-startup-and-bootstrap` - replaces the starter `_layout.tsx` with the root skeleton.
- `navigation-and-routing` - the route contract and the not-found route.
- `scaffold-feature-screen` - every screen after the starter, the first one in its place.
- `app-variants-and-environment` - variants, `EXPO_PUBLIC_*`, the Metro cache key.
- `unit-and-component-tests` - Jest projects, render helpers, coverage, Stryker.
- `git-commits-and-reporting` - the commit format, the `Gate-Change:` trailer, the report.
- `icons-app-icon-and-splash` - real art in place of the placeholder icons.
- `eas-build-and-submit` - `eas.json` and the first build; `ios-simulator-build`, `android-emulator-build` for local builds.
- `expo-sdk-upgrade` - moving the whole app to the next Expo SDK.

Files in this skill

  • SKILL.md16.6 KB
  • assets/app-dependencies.json1.6 KB
  • assets/claude-settings.json2.2 KB
  • assets/shared.json3.1 KB
  • assets/skeleton-package.json3.2 KB
  • assets/template-placeholders.json120 B
  • examples/bootstrap-report.md2.3 KB
  • references/first-install-and-verify.md15.9 KB
  • references/project-layout.md16.3 KB
  • references/root-files.md19.7 KB
  • references/toolchain-setup.md8.2 KB
  • scripts/check-skeleton.mjs56.1 KB
  • scripts/lib/bootstrap-plan.mjs28.7 KB
  • scripts/lib/repo-read.mjs4.6 KB
  • scripts/lib/ship-placeholders.mjs8 KB
  • scripts/scaffold-app.mjs6.2 KB
  • scripts/selftest.mjs8.3 KB
  • templates/AGENTS.md3 KB
  • templates/CLAUDE.md11 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.