Scaffold
Skill Shankulkarni/RN-Lib-Claude/plugins/rn-lib-claude/skills/scaffold
Ship production-grade RN libraries faster — scaffold, audit, and publish with Claude Code.
npx -y skills add Shankulkarni/RN-Lib-Claude --skill scaffoldAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when creating a new React Native library from scratch — sets up create-react-native-library, bob config, TurboModule / Fabric view (New Architecture) or JS-only library, Expo example app, peer deps, ESLint, Prettier, Husky, and Changesets.
SKILL.md
12.8 KB, ~3.6k tokens by cl100k_base, as published. Nobody here has run it
Library Scaffolding
Stack
create-react-native-library + react-native-builder-bob, TypeScript strict, ESLint v9, Prettier, Husky + lint-staged, Changesets, Expo example app.
Reference: https://oss.callstack.com/react-native-builder-bob/create
Before You Start
Prerequisites
- bun must be installed. If not:
curl -fsSL https://bun.sh/install | bash - Node.js >= 18
Gather from user:
- Library name (e.g.
rn-country-picker→ package@scope/rn-country-picker) - npm scope (e.g.
@myorg) or unscoped - Type? See decision guide below.
- Animations? If yes, add
react-native-reanimated+react-native-gesture-handleras peer deps. (native and JS-only)
Type decision guide
| I need to... | Use |
|---|---|
| Call native platform APIs (camera, sensors, storage, crypto) | turbo-module |
| Expose native business logic or background processing | turbo-module |
| Render a native UI component (map, video player, native picker) | fabric-view |
| Wrap an existing native SDK's view | fabric-view |
| Build UI components, hooks, utilities — no native code | library |
| Country/state/city pickers, formatters, validators, headless logic | library |
Step 1: Scaffold
For a TurboModule (native API/logic):
bunx create-react-native-library@latest rn-my-library \
--slug rn-my-library \
--description "My library description" \
--author-name "Author Name" \
--author-email "[email protected]" \
--author-url "https://github.com/author" \
--repo-url "https://github.com/author/rn-my-library" \
--type turbo-module \
--languages kotlin-objc \
--example expo \
--tools eslint jest
For a Fabric view (native UI component):
bunx create-react-native-library@latest rn-my-library \
--slug rn-my-library \
--description "My library description" \
--author-name "Author Name" \
--author-email "[email protected]" \
--author-url "https://github.com/author" \
--repo-url "https://github.com/author/rn-my-library" \
--type fabric-view \
--languages kotlin-objc \
--example expo \
--tools eslint jest
For a JS-only library (TypeScript, no native code):
bunx create-react-native-library@latest rn-my-library \
--slug rn-my-library \
--description "My library description" \
--author-name "Author Name" \
--author-email "[email protected]" \
--author-url "https://github.com/author" \
--repo-url "https://github.com/author/rn-my-library" \
--type library \
--languages js \
--example expo \
--tools eslint jest
Flag reference
| Flag | turbo-module | fabric-view | library |
|---|---|---|---|
--type | turbo-module | fabric-view | library |
--languages | kotlin-objc | kotlin-objc | js |
--example | expo | expo | expo |
--tools | eslint jest | eslint jest | eslint jest |
| Native folders | android/ ios/ | android/ ios/ | none |
| Codegen spec | required | required | none |
--languages controls the native language (Kotlin/ObjC), NOT TypeScript. TypeScript is always generated. For a JS-only library, --languages js means "no native code" — it does not mean JavaScript instead of TypeScript.
--author-url must be a valid URL (cannot be empty) for all types.
Skip lefthook/release-it in --tools — we use Husky + Changesets instead.
If the target directory already exists
create-react-native-library crashes with ERR_INVALID_ARG_TYPE when the target directory already exists (even if it is nearly empty). Scaffold to a temporary name, then move:
bunx create-react-native-library@latest rn-my-library-tmp \
--slug rn-my-library \
... (all other flags) ...
cp -r rn-my-library-tmp/. rn-my-library/
rm -rf rn-my-library-tmp
Interactive fallback
If non-interactive fails, run without flags and select:
- Type → TurboModule, Fabric view, or Library
- Languages → Kotlin & Objective-C (native) or JavaScript (JS-only)
- Example → Expo
- Tools → ESLint, Jest (uncheck lefthook, release-it)
bunx create-react-native-library@latest rn-my-library
Step 2: Remove Yarn, Switch to Bun
The scaffold generates Yarn config. Remove it and switch to bun:
rm -rf .yarn .yarnrc.yml
bun install
Step 3: bob Config
create-react-native-library v0.62+ configures bob directly in package.json under the "react-native-builder-bob" key. The default config is correct:
"react-native-builder-bob": {
"source": "src",
"output": "lib",
"targets": [
["module", { "esm": true }],
["typescript", { "project": "tsconfig.build.json" }]
]
}
Note: Modern bob only uses module (ESM) output. There is no commonjs target — bundlers handle CJS conversion.
Step 4: package.json Conventions
Update the generated package.json:
{
"name": "rn-my-library",
"version": "0.1.0",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
"exports": {
".": {
"source": "./src/index.tsx",
"types": "./lib/typescript/src/index.d.ts",
"default": "./lib/module/index.js"
},
"./package.json": "./package.json"
},
"files": [
"src",
"lib",
"android", // native only — remove for JS-only library
"ios", // native only — remove for JS-only library
"cpp", // native only — remove for JS-only library
"*.podspec", // native only — remove for JS-only library
"react-native.config.js", // native only — remove for JS-only library
"!ios/build",
"!android/build",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__",
"!**/.*"
],
"peerDependencies": {
"react": ">=18.0.0",
"react-native": ">=0.76.0"
},
"scripts": {
"build": "bob build",
"clean": "del-cli lib",
"prepare": "bob build && husky",
"typecheck": "tsc",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"format": "prettier --write \"**/*.{js,ts,tsx,json,md}\"",
"format:check": "prettier --check \"**/*.{js,ts,tsx,json,md}\"",
"check": "bun run typecheck && bun run lint && bun run format:check",
"test": "jest",
"changeset": "changeset",
"version": "changeset version",
"release": "bun run build && changeset publish"
}
}
Important: The prepare script must be bob build && husky — husky init (Step 8) will overwrite this to just husky. Always restore bob build && prefix after running husky init.
Animation peer deps (only if library uses them)
"peerDependencies": {
"react": ">=18.0.0",
"react-native": ">=0.76.0",
"react-native-reanimated": ">=3.0.0",
"react-native-gesture-handler": ">=2.0.0"
},
"peerDependenciesMeta": {
"react-native-reanimated": { "optional": true },
"react-native-gesture-handler": { "optional": true }
}
lint-staged config
Add to package.json:
"lint-staged": {
"*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md}": ["prettier --write"]
}
Step 5: Prettier Config
Add to package.json (replaces any generated .prettierrc):
"prettier": {
"singleQuote": true,
"semi": false,
"trailingComma": "es5",
"useTabs": true
}
Step 6: ESLint
The generated eslint.config.mjs uses @react-native/eslint-config which requires many peer plugins. Install all of them:
bun add -d --ignore-scripts \
eslint-plugin-eslint-comments \
eslint-plugin-react \
eslint-plugin-react-hooks \
eslint-plugin-react-native \
eslint-plugin-jest \
@react-native/eslint-plugin \
@typescript-eslint/parser \
@typescript-eslint/eslint-plugin
These are required peers of @react-native/eslint-config — without them, eslint will fail with "plugin not found" errors.
Use --ignore-scripts because the prepare script (bob build && husky) will fail at this point — husky is not installed yet. Use --ignore-scripts for all bun add calls until after Step 8.
Step 7: Changesets Init
Install @changesets/cli first — bunx changeset init cannot find the binary until the package is installed:
bun add -d --ignore-scripts @changesets/cli
bunx changeset init
Edit .changeset/config.json — set access to public:
{
"changelog": "@changesets/cli/changelog",
"commit": false,
"baseBranch": "main",
"access": "public"
}
Step 8: Husky + lint-staged
bun add -d --ignore-scripts husky@^9 lint-staged
bunx husky init
Important: husky init overwrites the prepare script to just "husky". You MUST restore it:
# Fix prepare script (husky init removes bob build)
npm pkg set scripts.prepare="bob build && husky"
Set the pre-commit hook to run lint-staged:
echo "bunx lint-staged" > .husky/pre-commit
Note: husky@10 does not exist. Always use husky@^9.
Step 9: Format All Files
After scaffolding, the generated files (especially example app) use spaces and semicolons. Run format to apply the project's prettier config:
bun run format
This ensures all files match the tabs + no semicolons + single quotes convention.
Step 10: Expo Example App — Dev Client (native only)
For TurboModule and Fabric view libraries, the example app cannot use Expo Go — it requires a dev client build. After scaffolding:
- Add
expo-dev-clienttoexample/package.jsondependencies:cd example && bun add expo-dev-client - Update the
startscript inexample/package.json:"start": "expo start --dev-client" - Verify
example/metro.config.jsexists withwatchFolderspointing to the repo root (seeexample-appskill).
JS-only libraries skip this step — they run in Expo Go without modification.
Step 12: Codegen (native only — skip for JS-only library)
The scaffold adds codegenConfig to package.json for native types. Verify the type field matches:
For TurboModule:
"codegenConfig": {
"name": "RNMyLibrarySpec",
"type": "modules",
"jsSrcsDir": "src",
"android": { "javaPackageName": "com.mylibrary" }
}
For Fabric view:
"codegenConfig": {
"name": "RNMyLibrarySpec",
"type": "components",
"jsSrcsDir": "src",
"android": { "javaPackageName": "com.mylibrary" }
}
For JS-only library: no codegenConfig needed. Skip this step entirely.
Read the codegen skill for full native implementation details.
Step 13: src/index.ts
// Export everything public. Nothing else.
export { MyComponent } from './components/MyComponent'
export type { MyComponentProps } from './components/MyComponent'
Step 14: Verify
bun run check
This runs typecheck + lint + format:check. All three must pass.
Post-Scaffold Checklist
-
bun run checkpasses clean -
bun run buildproduceslib/with module + types output -
.changeset/directory exists -
.husky/pre-commitcontainsbunx lint-staged -
preparescript isbob build && husky(not justhusky) - README.md documents every export
- JS-only: example app runs in Expo Go (
cd example && bun run start) - Native:
expo-dev-clientin example dependencies,startscript uses--dev-client - Native: example app builds (
cd example && bun run ios)
Common Gotchas
| Problem | Cause | Fix |
|---|---|---|
husky@^10.0.0 failed to resolve | husky v10 doesn't exist | Use husky@^9 |
eslint-plugin-X not found | Missing peer deps of @react-native/eslint-config | Install all 8 plugins from Step 6 |
prepare only runs husky | husky init overwrites it | Restore to bob build && husky |
| Example app has wrong formatting | Generated with spaces/semicolons | Run bun run format |
--author-url error | Empty string passed | Must be a valid URL |
--tools missing error | Required flag since v0.62 | Pass --tools eslint jest |
lib/commonjs not found | Modern bob uses ESM only | Check for lib/module instead |
Prompted for lefthook/release-it | New tool choices in current CLI | Uncheck both — use Husky + Changesets instead |
ERR_INVALID_ARG_TYPE on scaffold | Target directory already exists | Scaffold to a temp name, then cp -r tmp/. target/ && rm -rf tmp |
bun add fails with "husky: command not found" | prepare runs before husky is installed | Use --ignore-scripts on all bun add calls in Steps 6–8 |
bunx changeset init → "could not determine executable" | @changesets/cli not installed yet | Install @changesets/cli first, then run bunx changeset init |
User asks for --languages ts | --languages is for native code, not TypeScript | Always use --languages js for JS-only; TS is always included |
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.