@highbeek/create-rnstarterkit 1.0.2-beta.7 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +268 -0
- package/dist/bin/create-rnstarterkit.js +205 -7
- package/dist/src/generators/appGenerator.js +1944 -127
- package/dist/src/generators/codeGenerator.js +289 -0
- package/dist/templates/cli-base/App.tsx +199 -21
- package/dist/templates/cli-base/assets/images/icon.png +0 -0
- package/dist/templates/cli-base/assets/images/partial-react-logo.png +0 -0
- package/dist/templates/cli-base/assets/images/react-logo.png +0 -0
- package/dist/templates/cli-base/babel.config.js +1 -0
- package/dist/templates/cli-base/ios/BaseApp.xcodeproj/project.pbxproj +6 -0
- package/dist/templates/cli-base/ios/Podfile +5 -0
- package/dist/templates/cli-base/jest.config.js +4 -0
- package/dist/templates/cli-base/package.json +7 -4
- package/dist/templates/cli-base/tsconfig.json +1 -0
- package/dist/templates/expo-base/app/_layout.tsx +7 -3
- package/dist/templates/expo-base/app/home.tsx +37 -0
- package/dist/templates/expo-base/app/index.tsx +166 -5
- package/dist/templates/expo-base/app.json +1 -2
- package/dist/templates/expo-base/package.json +5 -2
- package/dist/templates/optional/auth-context/components/layout/ScreenLayout.tsx +46 -0
- package/dist/templates/optional/auth-context/navigation/ProtectedStack.tsx +33 -5
- package/dist/templates/optional/auth-context/screens/HomeScreen.tsx +4 -3
- package/dist/templates/optional/auth-context/screens/LoginScreen.tsx +41 -6
- package/dist/templates/optional/auth-context/screens/ProfileScreen.tsx +4 -3
- package/dist/templates/optional/auth-context/screens/RegisterScreen.tsx +41 -6
- package/dist/templates/optional/auth-context/screens/SettingsScreen.tsx +4 -3
- package/dist/templates/optional/auth-context/screens/WelcomeScreen.tsx +174 -0
- package/dist/templates/optional/auth-redux/components/layout/ScreenLayout.tsx +46 -0
- package/dist/templates/optional/auth-redux/navigation/ProtectedStack.tsx +39 -2
- package/dist/templates/optional/auth-redux/screens/HomeScreen.tsx +4 -3
- package/dist/templates/optional/auth-redux/screens/LoginScreen.tsx +42 -7
- package/dist/templates/optional/auth-redux/screens/ProfileScreen.tsx +7 -10
- package/dist/templates/optional/auth-redux/screens/RegisterScreen.tsx +61 -11
- package/dist/templates/optional/auth-redux/screens/SettingsScreen.tsx +6 -9
- package/dist/templates/optional/auth-redux/screens/WelcomeScreen.tsx +174 -0
- package/dist/templates/optional/auth-zustand/components/layout/ScreenLayout.tsx +46 -0
- package/dist/templates/optional/auth-zustand/navigation/ProtectedStack.tsx +34 -6
- package/dist/templates/optional/auth-zustand/screens/HomeScreen.tsx +4 -3
- package/dist/templates/optional/auth-zustand/screens/LoginScreen.tsx +41 -6
- package/dist/templates/optional/auth-zustand/screens/ProfileScreen.tsx +4 -3
- package/dist/templates/optional/auth-zustand/screens/RegisterScreen.tsx +41 -6
- package/dist/templates/optional/auth-zustand/screens/SettingsScreen.tsx +4 -3
- package/dist/templates/optional/auth-zustand/screens/WelcomeScreen.tsx +174 -0
- package/dist/templates/optional/ci/.github/workflows/ci.yml +32 -0
- package/dist/templates/optional/error-boundary/components/ErrorBoundary.tsx +83 -0
- package/dist/templates/optional/formik/components/formik/FormikInput.tsx +45 -0
- package/dist/templates/optional/formik/components/formik/LoginForm.tsx +60 -0
- package/dist/templates/optional/formik/schemas/auth.schema.ts +17 -0
- package/dist/templates/optional/i18n/src/i18n/hooks/useAppTranslation.ts +28 -0
- package/dist/templates/optional/i18n/src/i18n/i18n.ts +30 -0
- package/dist/templates/optional/i18n/src/i18n/locales/en.json +32 -0
- package/dist/templates/optional/i18n/src/i18n/locales/es.json +32 -0
- package/dist/templates/optional/maestro/.maestro/flows/01_welcome.yaml +5 -0
- package/dist/templates/optional/maestro-auth/.maestro/flows/01_welcome.yaml +5 -0
- package/dist/templates/optional/maestro-auth/.maestro/flows/02_login.yaml +13 -0
- package/dist/templates/optional/maestro-auth/.maestro/flows/03_logout.yaml +16 -0
- package/dist/templates/optional/mmkv/utils/storage.ts +17 -0
- package/dist/templates/optional/react-hook-form/components/rhf/LoginForm.tsx +63 -0
- package/dist/templates/optional/react-hook-form/components/rhf/RHFInput.tsx +50 -0
- package/dist/templates/optional/react-hook-form/schemas/auth.schema.ts +29 -0
- package/dist/templates/optional/react-query/hooks/useAppMutation.ts +16 -0
- package/dist/templates/optional/react-query/hooks/useAppQuery.ts +12 -0
- package/dist/templates/optional/react-query/services/queryClient.ts +14 -0
- package/dist/templates/optional/redux/store/hooks.ts +6 -0
- package/dist/templates/optional/redux/store/store.ts +11 -0
- package/dist/templates/optional/sentry/src/utils/sentry.ts +24 -0
- package/dist/templates/optional/swr/hooks/useSWRFetch.ts +14 -0
- package/dist/templates/optional/swr/providers/SWRProvider.tsx +21 -0
- package/dist/templates/optional/tsconfig.json +17 -0
- package/dist/templates/optional/zustand/store/appStore.ts +13 -0
- package/package.json +40 -5
- package/dist/templates/expo-base/components/ui/collapsible.tsx +0 -45
- package/dist/templates/expo-base/components/ui/icon-symbol.ios.tsx +0 -32
- package/dist/templates/expo-base/components/ui/icon-symbol.tsx +0 -41
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ibukun Agboola
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# RNStarterKit
|
|
2
|
+
|
|
3
|
+
**A CLI that scaffolds production-ready React Native apps with clean architecture and sensible defaults.**
|
|
4
|
+
|
|
5
|
+
Not a replacement for Expo or React Native CLI — it sits on top of them and structures your project correctly from day one.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @highbeek/create-rnstarterkit myApp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Why This Exists
|
|
14
|
+
|
|
15
|
+
Every serious React Native project ends up making the same decisions:
|
|
16
|
+
|
|
17
|
+
- Where do API calls live?
|
|
18
|
+
- How is auth state managed and persisted?
|
|
19
|
+
- What's the folder structure when the app scales?
|
|
20
|
+
- How are environment variables typed?
|
|
21
|
+
|
|
22
|
+
This tool answers those questions upfront. You get a complete, typed, linted project with your chosen stack — ready to build on, not fight against.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx @highbeek/create-rnstarterkit myApp
|
|
30
|
+
|
|
31
|
+
# Skip prompts with a preset
|
|
32
|
+
npx @highbeek/create-rnstarterkit myApp --preset fintech
|
|
33
|
+
npx @highbeek/create-rnstarterkit myApp --preset social
|
|
34
|
+
npx @highbeek/create-rnstarterkit myApp --preset indie
|
|
35
|
+
npx @highbeek/create-rnstarterkit myApp --preset minimal
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### After scaffolding — React Native CLI projects
|
|
39
|
+
|
|
40
|
+
Native dependencies need linking via CocoaPods before you can run on iOS:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd myApp
|
|
44
|
+
npm install
|
|
45
|
+
cd ios && pod install && cd ..
|
|
46
|
+
|
|
47
|
+
# Then run
|
|
48
|
+
npx react-native run-ios
|
|
49
|
+
npx react-native run-android
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### After scaffolding — Expo projects
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cd myApp
|
|
56
|
+
npx expo start
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Presets
|
|
62
|
+
|
|
63
|
+
Presets bundle the most common stack combinations so you can skip all prompts:
|
|
64
|
+
|
|
65
|
+
| Preset | Platform | State | Auth | Data | Storage | Extras |
|
|
66
|
+
|---|---|---|---|---|---|---|
|
|
67
|
+
| `minimal` | Expo | None | No | None | None | Bare structure |
|
|
68
|
+
| `indie` | Expo | Context API | Yes | React Query | AsyncStorage | Husky |
|
|
69
|
+
| `social` | Expo | Zustand | Yes | SWR | AsyncStorage | Sentry, i18n, CI |
|
|
70
|
+
| `fintech` | RN CLI | Redux Toolkit | Yes | React Query | MMKV | Sentry, i18n, Maestro, CI |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Interactive Setup
|
|
75
|
+
|
|
76
|
+
When run without a preset, the CLI walks you through your stack:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
? Project name:
|
|
80
|
+
? Platform: Expo / React Native CLI
|
|
81
|
+
? TypeScript: Yes / No
|
|
82
|
+
? Absolute imports (@/*): Yes / No
|
|
83
|
+
? State management: None / Context API / Redux Toolkit / Zustand
|
|
84
|
+
? Data fetching: None / React Query / SWR
|
|
85
|
+
? Validation: Formik / React Hook Form / Yup
|
|
86
|
+
? Storage: AsyncStorage / MMKV / None
|
|
87
|
+
? Auth scaffold: Yes / No
|
|
88
|
+
? API client: Yes / No → Fetch / Axios
|
|
89
|
+
? Sentry: Yes / No
|
|
90
|
+
? i18n (i18next): Yes / No
|
|
91
|
+
? Maestro E2E flows: Yes / No
|
|
92
|
+
? NativeWind (Tailwind): Yes / No
|
|
93
|
+
? CI (GitHub Actions): Yes / No
|
|
94
|
+
? Husky pre-commit hooks: Yes / No
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Generator Commands
|
|
100
|
+
|
|
101
|
+
After scaffolding, use the `generate` subcommand to add code that matches your project's setup:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx @highbeek/create-rnstarterkit generate screen Dashboard
|
|
105
|
+
npx @highbeek/create-rnstarterkit generate component Avatar
|
|
106
|
+
npx @highbeek/create-rnstarterkit generate hook useDebounce
|
|
107
|
+
npx @highbeek/create-rnstarterkit generate slice cart
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Generators are context-aware — they read your project's config from `package.json` and produce files with the correct imports for your state manager, platform, and auth setup.
|
|
111
|
+
|
|
112
|
+
| Command | Output |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `generate screen Name` | `src/screens/NameScreen.tsx` with correct state hook |
|
|
115
|
+
| `generate component Name` | `src/components/Name.tsx` with typed props + StyleSheet |
|
|
116
|
+
| `generate hook useName` | `src/hooks/useName.ts` with loading/error boilerplate |
|
|
117
|
+
| `generate slice name` | `src/store/nameSlice.ts` + auto-patches `store.ts` |
|
|
118
|
+
|
|
119
|
+
> `generate slice` only works in Redux Toolkit projects. It automatically adds the reducer to your store.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## What Gets Generated
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
<project>/
|
|
127
|
+
├── src/
|
|
128
|
+
│ ├── api/ # API client + endpoints
|
|
129
|
+
│ ├── components/ # Shared UI components + ErrorBoundary
|
|
130
|
+
│ ├── config/ # Typed env config
|
|
131
|
+
│ ├── context/ # Context API providers (if selected)
|
|
132
|
+
│ ├── hooks/ # Shared custom hooks
|
|
133
|
+
│ ├── i18n/ # i18next config + locales (if selected)
|
|
134
|
+
│ ├── navigation/ # Navigation setup (RN CLI)
|
|
135
|
+
│ ├── providers/ # App-level providers
|
|
136
|
+
│ ├── screens/ # App screens
|
|
137
|
+
│ ├── services/ # Query clients, external services
|
|
138
|
+
│ ├── store/ # Redux / Zustand store (if selected)
|
|
139
|
+
│ ├── theme/ # Color tokens, spacing, typography
|
|
140
|
+
│ ├── types/ # Shared TypeScript types
|
|
141
|
+
│ └── utils/ # Shared utilities + Sentry (if selected)
|
|
142
|
+
├── .env.example # Environment variable template
|
|
143
|
+
├── .husky/ # Pre-commit hooks (if selected)
|
|
144
|
+
├── .maestro/ # E2E flows (if selected)
|
|
145
|
+
├── .vscode/ # Editor config + extension recommendations
|
|
146
|
+
└── .github/workflows/ # CI pipeline (if selected)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Optional Modules
|
|
152
|
+
|
|
153
|
+
### Auth Scaffold
|
|
154
|
+
|
|
155
|
+
Three implementations — all include Login/Register screens, protected routes, token persistence, and a ScreenLayout wrapper:
|
|
156
|
+
|
|
157
|
+
| Option | State | Best For |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| Context API | Built-in React | Most apps |
|
|
160
|
+
| Redux Toolkit | `@reduxjs/toolkit` | Complex global state |
|
|
161
|
+
| Zustand | `zustand` | Minimal, no boilerplate |
|
|
162
|
+
|
|
163
|
+
### API Client
|
|
164
|
+
|
|
165
|
+
| Option | Description |
|
|
166
|
+
|---|---|
|
|
167
|
+
| Fetch | Zero deps — typed wrapper with request/response interceptors |
|
|
168
|
+
| Axios | Full Axios instance with error normalisation + typed helpers |
|
|
169
|
+
|
|
170
|
+
### Data Fetching
|
|
171
|
+
|
|
172
|
+
| Option | Setup |
|
|
173
|
+
|---|---|
|
|
174
|
+
| React Query | QueryClient pre-configured with retry + stale time defaults |
|
|
175
|
+
| SWR | SWRConfig provider + typed `useSWRFetch` hook |
|
|
176
|
+
|
|
177
|
+
### Storage
|
|
178
|
+
|
|
179
|
+
| Option | Description |
|
|
180
|
+
|---|---|
|
|
181
|
+
| AsyncStorage | Default — async token persistence |
|
|
182
|
+
| MMKV | Synchronous, 10x faster, drop-in replacement |
|
|
183
|
+
|
|
184
|
+
### NativeWind (Tailwind CSS)
|
|
185
|
+
|
|
186
|
+
Adds `nativewind` + `tailwindcss` and wires everything up for your platform:
|
|
187
|
+
|
|
188
|
+
| Step | Expo | React Native CLI |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| `tailwind.config.js` | content globs for `app/` + `src/` | content globs for `App.tsx` + `src/` |
|
|
191
|
+
| `global.css` | created, imported in `app/_layout.tsx` | created, imported in `index.js` |
|
|
192
|
+
| `metro.config.js` | wrapped with `withNativeWind` (expo/metro-config) | wrapped with `withNativeWind` (@react-native/metro-config) |
|
|
193
|
+
| `babel.config.js` | `jsxImportSource: 'nativewind'` in babel-preset-expo | `nativewind/babel` plugin added |
|
|
194
|
+
| `tsconfig.json` | `"nativewind/types"` added to types | same |
|
|
195
|
+
|
|
196
|
+
After generation, use `className` props directly on React Native components — no extra setup needed.
|
|
197
|
+
|
|
198
|
+
### Sentry
|
|
199
|
+
|
|
200
|
+
Adds `@sentry/react-native`, initialises in the app entry point, and exports `captureException` + `addBreadcrumb` helpers. The DSN is read from your environment variables — set `EXPO_PUBLIC_SENTRY_DSN` (Expo) or `SENTRY_DSN` (RN CLI) in your `.env` file.
|
|
201
|
+
|
|
202
|
+
### i18n
|
|
203
|
+
|
|
204
|
+
Adds `i18next` + `react-i18next` with:
|
|
205
|
+
- Language auto-detection via `react-native-localize`
|
|
206
|
+
- English + Spanish locale files as a starting point
|
|
207
|
+
- Typed `useAppTranslation` hook
|
|
208
|
+
|
|
209
|
+
### Maestro E2E
|
|
210
|
+
|
|
211
|
+
Generates `.maestro/flows/` with welcome, login, and logout flows. Automatically adds a Maestro job to the CI workflow when both are selected.
|
|
212
|
+
|
|
213
|
+
### CI
|
|
214
|
+
|
|
215
|
+
GitHub Actions workflow with lint and test jobs on push/PR to `main` and `develop`.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Tooling (Always Included)
|
|
220
|
+
|
|
221
|
+
| Tool | Config |
|
|
222
|
+
|---|---|
|
|
223
|
+
| TypeScript | `strict: true`, platform-native tsconfig |
|
|
224
|
+
| ESLint | `@react-native` ruleset, `--max-warnings 0` |
|
|
225
|
+
| Prettier | `format` script wired to `package.json` |
|
|
226
|
+
| Jest | RTL setup, `transformIgnorePatterns` for RN modules |
|
|
227
|
+
| VSCode | `extensions.json` + `settings.json` (format on save) |
|
|
228
|
+
| `.env.example` | Environment variable template + `.gitignore` entry |
|
|
229
|
+
| Theme | `colors.ts`, `spacing.ts`, `typography.ts` tokens |
|
|
230
|
+
| ErrorBoundary | Class component with reset, ready to wrap your app |
|
|
231
|
+
| Navigation types | `AuthStackParamList`, `MainTabParamList` (RN CLI + auth) |
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Architecture
|
|
236
|
+
|
|
237
|
+
**Typed from the start.** TypeScript strict mode on by default. API responses, environment variables, and navigation params are all typed.
|
|
238
|
+
|
|
239
|
+
**Thin screens, fat hooks.** Screens handle UI. Business logic, API calls, and state live in hooks and services. Screens don't call APIs directly.
|
|
240
|
+
|
|
241
|
+
**Non-destructive generation.** `writeIfMissing` is used throughout — the generator never overwrites files you've already customised.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Contributing
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
git clone https://github.com/highbeek/rnstarterkit
|
|
249
|
+
cd rnstarterkit
|
|
250
|
+
npm install
|
|
251
|
+
npm run build
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full development workflow, testing, and PR guidelines.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Status
|
|
259
|
+
|
|
260
|
+
Current version: `1.0.0`
|
|
261
|
+
|
|
262
|
+
Core scaffolding, presets, and generators are stable. Feedback and contributions welcome.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## License
|
|
267
|
+
|
|
268
|
+
MIT
|
|
@@ -4,15 +4,95 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
7
8
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
9
|
const appGenerator_1 = require("../src/generators/appGenerator");
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const codeGenerator_1 = require("../src/generators/codeGenerator");
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Presets — opinionated bundles that skip the 12-question prompt
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const PRESETS = {
|
|
15
|
+
minimal: {
|
|
16
|
+
typescript: true,
|
|
17
|
+
absoluteImports: false,
|
|
18
|
+
state: "None",
|
|
19
|
+
auth: false,
|
|
20
|
+
dataFetching: "None",
|
|
21
|
+
validation: [],
|
|
22
|
+
storage: "None",
|
|
23
|
+
apiClient: false,
|
|
24
|
+
ci: false,
|
|
25
|
+
husky: false,
|
|
26
|
+
sentry: false,
|
|
27
|
+
i18n: false,
|
|
28
|
+
maestro: false,
|
|
29
|
+
nativewind: false,
|
|
30
|
+
},
|
|
31
|
+
fintech: {
|
|
32
|
+
platform: "React Native CLI",
|
|
33
|
+
typescript: true,
|
|
34
|
+
absoluteImports: true,
|
|
35
|
+
state: "Redux Toolkit",
|
|
36
|
+
auth: true,
|
|
37
|
+
dataFetching: "React Query",
|
|
38
|
+
validation: [],
|
|
39
|
+
storage: "MMKV",
|
|
40
|
+
apiClient: true,
|
|
41
|
+
apiClientType: "Fetch",
|
|
42
|
+
ci: true,
|
|
43
|
+
husky: true,
|
|
44
|
+
sentry: true,
|
|
45
|
+
i18n: true,
|
|
46
|
+
maestro: true,
|
|
47
|
+
nativewind: false,
|
|
48
|
+
},
|
|
49
|
+
social: {
|
|
50
|
+
platform: "Expo",
|
|
51
|
+
typescript: true,
|
|
52
|
+
absoluteImports: true,
|
|
53
|
+
state: "Zustand",
|
|
54
|
+
auth: true,
|
|
55
|
+
dataFetching: "SWR",
|
|
56
|
+
validation: [],
|
|
57
|
+
storage: "AsyncStorage",
|
|
58
|
+
apiClient: true,
|
|
59
|
+
apiClientType: "Fetch",
|
|
60
|
+
ci: true,
|
|
61
|
+
husky: true,
|
|
62
|
+
sentry: true,
|
|
63
|
+
i18n: true,
|
|
64
|
+
maestro: false,
|
|
65
|
+
nativewind: false,
|
|
66
|
+
},
|
|
67
|
+
indie: {
|
|
68
|
+
platform: "Expo",
|
|
69
|
+
typescript: true,
|
|
70
|
+
absoluteImports: true,
|
|
71
|
+
state: "Context API",
|
|
72
|
+
auth: true,
|
|
73
|
+
dataFetching: "React Query",
|
|
74
|
+
validation: [],
|
|
75
|
+
storage: "AsyncStorage",
|
|
76
|
+
apiClient: true,
|
|
77
|
+
apiClientType: "Fetch",
|
|
78
|
+
ci: false,
|
|
79
|
+
husky: true,
|
|
80
|
+
sentry: false,
|
|
81
|
+
i18n: false,
|
|
82
|
+
maestro: false,
|
|
83
|
+
nativewind: false,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Interactive prompt
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
async function runInteractivePrompt(prefilledName, presetDefaults) {
|
|
11
90
|
const answers = await inquirer_1.default.prompt([
|
|
12
91
|
{
|
|
13
92
|
type: "input",
|
|
14
93
|
name: "projectName",
|
|
15
94
|
message: "Project name:",
|
|
95
|
+
when: () => !prefilledName,
|
|
16
96
|
validate: (input) => /^[A-Za-z0-9]+$/.test(input)
|
|
17
97
|
? true
|
|
18
98
|
: "Project name must be alphanumeric",
|
|
@@ -22,18 +102,21 @@ async function main() {
|
|
|
22
102
|
name: "platform",
|
|
23
103
|
message: "Choose platform",
|
|
24
104
|
choices: ["Expo", "React Native CLI"],
|
|
105
|
+
when: () => !presetDefaults?.platform,
|
|
25
106
|
},
|
|
26
107
|
{
|
|
27
108
|
type: "confirm",
|
|
28
109
|
name: "typescript",
|
|
29
110
|
message: "Use TypeScript?",
|
|
30
111
|
default: true,
|
|
112
|
+
when: () => presetDefaults?.typescript === undefined,
|
|
31
113
|
},
|
|
32
114
|
{
|
|
33
115
|
type: "confirm",
|
|
34
116
|
name: "absoluteImports",
|
|
35
117
|
message: "Use absolute import alias (@/*)?",
|
|
36
118
|
default: true,
|
|
119
|
+
when: () => presetDefaults?.absoluteImports === undefined,
|
|
37
120
|
},
|
|
38
121
|
{
|
|
39
122
|
type: "rawlist",
|
|
@@ -41,6 +124,7 @@ async function main() {
|
|
|
41
124
|
message: "State management?",
|
|
42
125
|
choices: ["None", "Context API", "Redux Toolkit", "Zustand"],
|
|
43
126
|
default: "Context API",
|
|
127
|
+
when: () => !presetDefaults?.state,
|
|
44
128
|
},
|
|
45
129
|
{
|
|
46
130
|
type: "rawlist",
|
|
@@ -48,6 +132,7 @@ async function main() {
|
|
|
48
132
|
message: "Data fetching library?",
|
|
49
133
|
choices: ["None", "React Query", "SWR"],
|
|
50
134
|
default: "None",
|
|
135
|
+
when: () => !presetDefaults?.dataFetching,
|
|
51
136
|
},
|
|
52
137
|
{
|
|
53
138
|
type: "checkbox",
|
|
@@ -55,6 +140,7 @@ async function main() {
|
|
|
55
140
|
message: "Validation libraries?",
|
|
56
141
|
choices: ["Formik", "React Hook Form", "Yup"],
|
|
57
142
|
default: [],
|
|
143
|
+
when: () => !presetDefaults?.validation?.length,
|
|
58
144
|
validate: (selected) => {
|
|
59
145
|
if (selected.includes("Formik") && !selected.includes("Yup")) {
|
|
60
146
|
return "Formik requires Yup in this starter. Select Yup as well.";
|
|
@@ -68,18 +154,21 @@ async function main() {
|
|
|
68
154
|
message: "Storage method?",
|
|
69
155
|
choices: ["AsyncStorage", "MMKV", "None"],
|
|
70
156
|
default: "AsyncStorage",
|
|
157
|
+
when: () => !presetDefaults?.storage,
|
|
71
158
|
},
|
|
72
159
|
{
|
|
73
160
|
type: "confirm",
|
|
74
161
|
name: "auth",
|
|
75
162
|
message: "Include auth scaffold?",
|
|
76
163
|
default: false,
|
|
164
|
+
when: () => presetDefaults?.auth === undefined,
|
|
77
165
|
},
|
|
78
166
|
{
|
|
79
167
|
type: "confirm",
|
|
80
168
|
name: "apiClient",
|
|
81
169
|
message: "Include API client scaffold?",
|
|
82
170
|
default: true,
|
|
171
|
+
when: () => presetDefaults?.apiClient === undefined,
|
|
83
172
|
},
|
|
84
173
|
{
|
|
85
174
|
type: "rawlist",
|
|
@@ -87,19 +176,128 @@ async function main() {
|
|
|
87
176
|
message: "API client transport?",
|
|
88
177
|
choices: ["Fetch", "Axios"],
|
|
89
178
|
default: "Fetch",
|
|
90
|
-
when: (
|
|
179
|
+
when: (a) => presetDefaults?.apiClientType === undefined && a.apiClient,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
type: "confirm",
|
|
183
|
+
name: "sentry",
|
|
184
|
+
message: "Include Sentry crash reporting?",
|
|
185
|
+
default: false,
|
|
186
|
+
when: () => presetDefaults?.sentry === undefined,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
type: "confirm",
|
|
190
|
+
name: "i18n",
|
|
191
|
+
message: "Include i18n (i18next + react-i18next)?",
|
|
192
|
+
default: false,
|
|
193
|
+
when: () => presetDefaults?.i18n === undefined,
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
type: "confirm",
|
|
197
|
+
name: "maestro",
|
|
198
|
+
message: "Include Maestro E2E test flows?",
|
|
199
|
+
default: false,
|
|
200
|
+
when: () => presetDefaults?.maestro === undefined,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
type: "confirm",
|
|
204
|
+
name: "nativewind",
|
|
205
|
+
message: "Use NativeWind (Tailwind CSS for React Native)?",
|
|
206
|
+
default: false,
|
|
207
|
+
when: () => presetDefaults?.nativewind === undefined,
|
|
91
208
|
},
|
|
92
209
|
{
|
|
93
210
|
type: "confirm",
|
|
94
211
|
name: "ci",
|
|
95
|
-
message: "Include CI setup?",
|
|
212
|
+
message: "Include CI setup (GitHub Actions)?",
|
|
96
213
|
default: false,
|
|
214
|
+
when: () => presetDefaults?.ci === undefined,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
type: "confirm",
|
|
218
|
+
name: "husky",
|
|
219
|
+
message: "Set up Husky pre-commit hooks (lint-staged)?",
|
|
220
|
+
default: true,
|
|
221
|
+
when: () => presetDefaults?.husky === undefined,
|
|
97
222
|
},
|
|
98
223
|
]);
|
|
99
|
-
|
|
224
|
+
return {
|
|
225
|
+
projectName: prefilledName ?? answers.projectName,
|
|
226
|
+
platform: answers.platform ?? presetDefaults?.platform ?? "Expo",
|
|
227
|
+
typescript: answers.typescript ?? presetDefaults?.typescript ?? true,
|
|
228
|
+
absoluteImports: answers.absoluteImports ?? presetDefaults?.absoluteImports ?? true,
|
|
229
|
+
state: answers.state ?? presetDefaults?.state ?? "None",
|
|
230
|
+
auth: answers.auth ?? presetDefaults?.auth ?? false,
|
|
231
|
+
dataFetching: answers.dataFetching ?? presetDefaults?.dataFetching ?? "None",
|
|
232
|
+
validation: answers.validation ?? presetDefaults?.validation ?? [],
|
|
233
|
+
storage: answers.storage ?? presetDefaults?.storage ?? "AsyncStorage",
|
|
234
|
+
apiClient: answers.apiClient ?? presetDefaults?.apiClient ?? false,
|
|
235
|
+
apiClientType: answers.apiClientType ?? presetDefaults?.apiClientType,
|
|
236
|
+
sentry: answers.sentry ?? presetDefaults?.sentry ?? false,
|
|
237
|
+
i18n: answers.i18n ?? presetDefaults?.i18n ?? false,
|
|
238
|
+
maestro: answers.maestro ?? presetDefaults?.maestro ?? false,
|
|
239
|
+
nativewind: answers.nativewind ?? presetDefaults?.nativewind ?? false,
|
|
240
|
+
ci: answers.ci ?? presetDefaults?.ci ?? false,
|
|
241
|
+
husky: answers.husky ?? presetDefaults?.husky ?? true,
|
|
242
|
+
};
|
|
100
243
|
}
|
|
101
|
-
|
|
102
|
-
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// CLI wiring
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
const program = new commander_1.Command();
|
|
248
|
+
program
|
|
249
|
+
.name("create-rnstarterkit")
|
|
250
|
+
.description("Scaffold a production-ready React Native app")
|
|
251
|
+
.version("1.0.0")
|
|
252
|
+
.argument("[projectName]", "Name of the project (alphanumeric)")
|
|
253
|
+
.option("--preset <name>", "Skip prompts with a preset: minimal | fintech | social | indie")
|
|
254
|
+
.action(async (projectName, cmdOptions) => {
|
|
255
|
+
console.log("🚀 Welcome to RNStarterKit!");
|
|
256
|
+
const presetKey = cmdOptions.preset;
|
|
257
|
+
if (presetKey && !(presetKey in PRESETS)) {
|
|
258
|
+
console.error(`❌ Unknown preset "${presetKey}". Available: ${Object.keys(PRESETS).join(", ")}`);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
const presetDefaults = presetKey ? PRESETS[presetKey] : undefined;
|
|
262
|
+
// Preset + name supplied → fully non-interactive
|
|
263
|
+
if (presetDefaults && projectName) {
|
|
264
|
+
const options = {
|
|
265
|
+
projectName,
|
|
266
|
+
platform: presetDefaults.platform ?? "Expo",
|
|
267
|
+
typescript: presetDefaults.typescript ?? true,
|
|
268
|
+
absoluteImports: presetDefaults.absoluteImports ?? true,
|
|
269
|
+
state: presetDefaults.state ?? "None",
|
|
270
|
+
auth: presetDefaults.auth ?? false,
|
|
271
|
+
dataFetching: presetDefaults.dataFetching ?? "None",
|
|
272
|
+
validation: presetDefaults.validation ?? [],
|
|
273
|
+
storage: presetDefaults.storage ?? "AsyncStorage",
|
|
274
|
+
apiClient: presetDefaults.apiClient ?? false,
|
|
275
|
+
apiClientType: presetDefaults.apiClientType,
|
|
276
|
+
sentry: presetDefaults.sentry ?? false,
|
|
277
|
+
i18n: presetDefaults.i18n ?? false,
|
|
278
|
+
maestro: presetDefaults.maestro ?? false,
|
|
279
|
+
nativewind: presetDefaults.nativewind ?? false,
|
|
280
|
+
ci: presetDefaults.ci ?? false,
|
|
281
|
+
husky: presetDefaults.husky ?? true,
|
|
282
|
+
};
|
|
283
|
+
console.log(`⚡ Using preset: ${presetKey}`);
|
|
284
|
+
await (0, appGenerator_1.generateApp)(options);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
const options = await runInteractivePrompt(projectName, presetDefaults);
|
|
288
|
+
await (0, appGenerator_1.generateApp)(options);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
// generate subcommand
|
|
292
|
+
program
|
|
293
|
+
.command("generate <type> <name>")
|
|
294
|
+
.description("Generate code inside an existing rnstarterkit project\n" +
|
|
295
|
+
" Types: screen | component | hook | slice")
|
|
296
|
+
.action(async (type, name) => {
|
|
297
|
+
await (0, codeGenerator_1.generateCode)(type, name);
|
|
298
|
+
});
|
|
299
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
300
|
+
console.error("❌ Failed.");
|
|
103
301
|
console.error(error);
|
|
104
302
|
process.exit(1);
|
|
105
303
|
});
|