appCN
← Components

appCN CLI

@app-cn/cli is a thin, RN-aware wrapper around the shadcn CLI. Two commands: init (one-shot project setup) and add (install a component).

init

One-shot setup for a fresh Expo or bare React Native app. Detects your package manager, installs NativeWind + Reanimated + gesture-handler, patches tailwind/babel/metro/global.css, and registers the @app-cn shadcn registry in components.json.

npx @app-cn/cli@latest init

Detects Expo vs bare RN and your package manager. Safe to re-run.

Idempotency contract

Re-running init on an already-configured project produces zero diffs. If a file already contains the appCN setup, the CLI leaves it alone. If a file exists but conflicts (e.g. a hand-rolled metro.config.js that does not wrap with withNativeWind), the CLI prints the required diff and aborts — it never silently mutates Metro or app entry files.

add

Install a single appCN component into the current project. Delegates to shadcn after preflighting the registry and ensuring components.json knows about @app-cn.

npx @app-cn/cli@latest add button

Replace `button` with any slug from the components index.

Under the hood, appcn add <slug> spawns <pm> dlx shadcn@latest add @app-cn/<slug> so the resulting file writes are identical to running shadcn directly. The CLI adds RN-specific preflight: registry HEAD check, components.json patch, and clearer error messages.

Environment

Override the registry base URL for local development or self-hosted forks.

# .env
APPCN_REGISTRY_URL=http://localhost:3000/r

Defaults to https://appcn.vercel.app/r. The CLI uses this for the HEAD check; the URL written into components.json uses the same value so consumers point at the registry they actually fetched from.

What init writes

The exact diff init produces on a fresh `npx create-expo-app` project.

  • tailwind.config.js — adds presets: [require("@app-cn/ui/tailwind-preset")] and content globs covering ./app, ./components.
  • babel.config.js — adds the nativewind/babel preset and ensures react-native-worklets/plugin (Reanimated 4) or react-native-reanimated/plugin (Reanimated 3) is last in the plugin list.
  • metro.config.js — wraps getDefaultConfig with withNativeWind(..., { input: "./global.css" }). Aborts if an existing file does not match the expected shape.
  • global.css — Tailwind directives and the appCN CSS variables. Prints the one-line import to add to your app entry; never auto-edits _layout.tsx.
  • components.json — deep-merges registries["@app-cn"]: "https://appcn.vercel.app/r/{name}.json" and default aliases. Prompts on conflict; never overwrites existing keys without confirmation.
  • tsconfig.json — ensures paths["@/*"] resolves to ./* for shadcn-style imports.

Why a custom CLI?

Couldn't you just point people at shadcn directly?

You can — that's the shadcn (URL) tab on every component page. The CLI exists for one job: init. React Native + Expo have a non-trivial setup surface (NativeWind babel + metro + global CSS + Reanimated plugin ordering) that shadcn cannot do for you because it is React Native-agnostic. After init, the difference between appcn add and shadcn add @app-cn/... is just which tool runs the HEAD check.