@highbeek/create-rnstarterkit 1.0.2-beta.12 → 1.0.2-beta.13
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/README.md +141 -154
- package/dist/bin/create-rnstarterkit.js +185 -7
- package/dist/src/generators/appGenerator.js +160 -4
- package/dist/src/generators/codeGenerator.js +288 -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/sentry/src/utils/sentry.ts +24 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# RNStarterKit
|
|
2
2
|
|
|
3
|
-
**A CLI
|
|
3
|
+
**A CLI that scaffolds production-ready React Native apps with clean architecture and sensible defaults.**
|
|
4
4
|
|
|
5
|
-
Not a replacement for Expo or React Native CLI — it sits on top of them and structures your project
|
|
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
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npx @highbeek/create-rnstarterkit myApp
|
|
@@ -12,221 +12,199 @@ npx @highbeek/create-rnstarterkit myApp
|
|
|
12
12
|
|
|
13
13
|
## Why This Exists
|
|
14
14
|
|
|
15
|
-
Every serious React Native project
|
|
15
|
+
Every serious React Native project ends up making the same decisions:
|
|
16
16
|
|
|
17
17
|
- Where do API calls live?
|
|
18
18
|
- How is auth state managed and persisted?
|
|
19
19
|
- What's the folder structure when the app scales?
|
|
20
20
|
- How are environment variables typed?
|
|
21
|
-
- Where does validation logic go?
|
|
22
21
|
|
|
23
|
-
This tool answers those questions upfront. You get a
|
|
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.
|
|
24
23
|
|
|
25
24
|
---
|
|
26
25
|
|
|
27
|
-
##
|
|
26
|
+
## Quick Start
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
```bash
|
|
29
|
+
# Interactive setup
|
|
30
|
+
npx @highbeek/create-rnstarterkit myApp
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
screens/ # Additional screens
|
|
37
|
-
navigation/ # React Navigation or Expo Router setup
|
|
38
|
-
api/ # Axios/Fetch client + interceptors (optional)
|
|
39
|
-
services/ # React Query client setup (optional)
|
|
40
|
-
store/ # Redux / Zustand store (optional)
|
|
41
|
-
config/ # Typed environment config
|
|
42
|
-
hooks/ # Shared custom hooks
|
|
43
|
-
utils/ # Shared utilities
|
|
44
|
-
components/ # Shared UI components
|
|
45
|
-
constants/ # App-wide constants
|
|
46
|
-
types/ # TypeScript types and interfaces
|
|
47
|
-
assets/
|
|
48
|
-
images/
|
|
49
|
-
fonts/
|
|
32
|
+
# Skip prompts with a preset
|
|
33
|
+
npx @highbeek/create-rnstarterkit myApp --preset fintech
|
|
34
|
+
npx @highbeek/create-rnstarterkit myApp --preset social
|
|
35
|
+
npx @highbeek/create-rnstarterkit myApp --preset indie
|
|
36
|
+
npx @highbeek/create-rnstarterkit myApp --preset minimal
|
|
50
37
|
```
|
|
51
38
|
|
|
52
39
|
---
|
|
53
40
|
|
|
54
|
-
##
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
npx @highbeek/create-rnstarterkit myApp
|
|
58
|
-
```
|
|
41
|
+
## Presets
|
|
59
42
|
|
|
60
|
-
|
|
43
|
+
Presets bundle the most common stack combinations so you can skip all prompts:
|
|
61
44
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
45
|
+
| Preset | Platform | State | Auth | Data | Storage | Extras |
|
|
46
|
+
|---|---|---|---|---|---|---|
|
|
47
|
+
| `minimal` | Expo | None | No | None | None | Bare structure |
|
|
48
|
+
| `indie` | Either | Context API | Yes | React Query | AsyncStorage | Husky |
|
|
49
|
+
| `social` | Expo | Zustand | Yes | SWR | AsyncStorage | Sentry, i18n, CI |
|
|
50
|
+
| `fintech` | RN CLI | Redux Toolkit | Yes | React Query | MMKV | Sentry, i18n, Maestro, CI |
|
|
66
51
|
|
|
67
52
|
---
|
|
68
53
|
|
|
69
54
|
## Interactive Setup
|
|
70
55
|
|
|
71
|
-
|
|
56
|
+
When run without a preset, the CLI walks you through your stack:
|
|
72
57
|
|
|
73
58
|
```
|
|
74
|
-
? Project name:
|
|
75
|
-
? Platform:
|
|
76
|
-
? TypeScript:
|
|
77
|
-
? Absolute imports (@/*):
|
|
78
|
-
? State management:
|
|
79
|
-
? Data fetching:
|
|
80
|
-
?
|
|
81
|
-
? Storage:
|
|
82
|
-
?
|
|
83
|
-
?
|
|
84
|
-
?
|
|
59
|
+
? Project name:
|
|
60
|
+
? Platform: Expo / React Native CLI
|
|
61
|
+
? TypeScript: Yes / No
|
|
62
|
+
? Absolute imports (@/*): Yes / No
|
|
63
|
+
? State management: None / Context API / Redux Toolkit / Zustand
|
|
64
|
+
? Data fetching: None / React Query / SWR
|
|
65
|
+
? Validation: Formik / React Hook Form / Yup
|
|
66
|
+
? Storage: AsyncStorage / MMKV / None
|
|
67
|
+
? Auth scaffold: Yes / No
|
|
68
|
+
? API client: Yes / No → Fetch / Axios
|
|
69
|
+
? Sentry: Yes / No
|
|
70
|
+
? i18n (i18next): Yes / No
|
|
71
|
+
? Maestro E2E flows: Yes / No
|
|
72
|
+
? CI (GitHub Actions): Yes / No
|
|
73
|
+
? Husky pre-commit hooks: Yes / No
|
|
85
74
|
```
|
|
86
75
|
|
|
87
76
|
---
|
|
88
77
|
|
|
89
|
-
##
|
|
90
|
-
|
|
91
|
-
### Platform Templates
|
|
92
|
-
|
|
93
|
-
| Feature | Expo | React Native CLI |
|
|
94
|
-
|---|---|---|
|
|
95
|
-
| File-based routing | expo-router | — |
|
|
96
|
-
| React Navigation | — | Native Stack v7 |
|
|
97
|
-
| TypeScript | Default | Default |
|
|
98
|
-
| Babel absolute imports | Optional | Optional |
|
|
99
|
-
| ESLint + Prettier | Configured | Configured |
|
|
100
|
-
| Jest | Configured | Configured |
|
|
101
|
-
| Native iOS/Android | Managed | Full native project |
|
|
78
|
+
## Generator Commands
|
|
102
79
|
|
|
103
|
-
|
|
80
|
+
After scaffolding, use the `generate` subcommand to add code that matches your project's setup:
|
|
104
81
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
All three include:
|
|
114
|
-
- Login + Register screens
|
|
115
|
-
- Protected route stack
|
|
116
|
-
- Token persistence via AsyncStorage
|
|
117
|
-
- ScreenLayout wrapper component
|
|
82
|
+
```bash
|
|
83
|
+
# Run from inside your generated project
|
|
84
|
+
npx @highbeek/create-rnstarterkit generate screen Dashboard
|
|
85
|
+
npx @highbeek/create-rnstarterkit generate component Avatar
|
|
86
|
+
npx @highbeek/create-rnstarterkit generate hook useDebounce
|
|
87
|
+
npx @highbeek/create-rnstarterkit generate slice cart
|
|
88
|
+
```
|
|
118
89
|
|
|
119
|
-
|
|
90
|
+
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.
|
|
120
91
|
|
|
121
|
-
|
|
92
|
+
| Command | Output |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `generate screen Name` | `src/screens/NameScreen.tsx` with correct state hook |
|
|
95
|
+
| `generate component Name` | `src/components/Name.tsx` with typed props + StyleSheet |
|
|
96
|
+
| `generate hook useName` | `src/hooks/useName.ts` with loading/error boilerplate |
|
|
97
|
+
| `generate slice name` | `src/store/nameSlice.ts` + auto-patches `store.ts` |
|
|
122
98
|
|
|
123
|
-
|
|
99
|
+
> `generate slice` only works in Redux Toolkit projects. It automatically adds the reducer to your store.
|
|
124
100
|
|
|
125
|
-
|
|
101
|
+
---
|
|
126
102
|
|
|
127
|
-
|
|
103
|
+
## What Gets Generated
|
|
128
104
|
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
105
|
+
```
|
|
106
|
+
<project>/
|
|
107
|
+
├── src/
|
|
108
|
+
│ ├── api/ # API client + endpoints
|
|
109
|
+
│ ├── components/ # Shared UI components + ErrorBoundary
|
|
110
|
+
│ ├── config/ # Typed env config
|
|
111
|
+
│ ├── context/ # Context API providers (if selected)
|
|
112
|
+
│ ├── hooks/ # Shared custom hooks
|
|
113
|
+
│ ├── i18n/ # i18next config + locales (if selected)
|
|
114
|
+
│ ├── navigation/ # Navigation setup (RN CLI)
|
|
115
|
+
│ ├── providers/ # App-level providers
|
|
116
|
+
│ ├── screens/ # App screens
|
|
117
|
+
│ ├── services/ # Query clients, external services
|
|
118
|
+
│ ├── store/ # Redux / Zustand store (if selected)
|
|
119
|
+
│ ├── theme/ # Color tokens, spacing, typography
|
|
120
|
+
│ ├── types/ # Shared TypeScript types
|
|
121
|
+
│ └── utils/ # Shared utilities + Sentry (if selected)
|
|
122
|
+
├── .env.example # Environment variable template
|
|
123
|
+
├── .husky/ # Pre-commit hooks (if selected)
|
|
124
|
+
├── .maestro/ # E2E flows (if selected)
|
|
125
|
+
├── .vscode/ # Editor config + extension recommendations
|
|
126
|
+
└── .github/workflows/ # CI pipeline (if selected)
|
|
134
127
|
```
|
|
135
128
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
### Data Fetching (Optional)
|
|
129
|
+
---
|
|
139
130
|
|
|
140
|
-
|
|
131
|
+
## Optional Modules
|
|
141
132
|
|
|
142
|
-
|
|
133
|
+
### Auth Scaffold
|
|
143
134
|
|
|
144
|
-
|
|
135
|
+
Three implementations — all include Login/Register screens, protected routes, token persistence, and a ScreenLayout wrapper:
|
|
145
136
|
|
|
146
|
-
| Option |
|
|
137
|
+
| Option | State | Best For |
|
|
147
138
|
|---|---|---|
|
|
148
|
-
|
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
| Zustand | zustand | Minimal, fast, no boilerplate |
|
|
139
|
+
| Context API | Built-in React | Most apps |
|
|
140
|
+
| Redux Toolkit | `@reduxjs/toolkit` | Complex global state |
|
|
141
|
+
| Zustand | `zustand` | Minimal, no boilerplate |
|
|
152
142
|
|
|
153
|
-
###
|
|
143
|
+
### API Client
|
|
154
144
|
|
|
155
|
-
| Option |
|
|
145
|
+
| Option | Description |
|
|
156
146
|
|---|---|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
147
|
+
| Fetch | Zero deps — typed wrapper with request/response interceptors |
|
|
148
|
+
| Axios | Full Axios instance with error normalisation + typed helpers |
|
|
159
149
|
|
|
160
|
-
###
|
|
150
|
+
### Data Fetching
|
|
161
151
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
152
|
+
| Option | Setup |
|
|
153
|
+
|---|---|
|
|
154
|
+
| React Query | QueryClient pre-configured with retry + stale time defaults |
|
|
155
|
+
| SWR | SWRConfig provider + typed `useSWRFetch` hook |
|
|
166
156
|
|
|
167
|
-
|
|
157
|
+
### Storage
|
|
168
158
|
|
|
169
|
-
|
|
159
|
+
| Option | Description |
|
|
160
|
+
|---|---|
|
|
161
|
+
| AsyncStorage | Default — async token persistence |
|
|
162
|
+
| MMKV | Synchronous, 10x faster, drop-in replacement |
|
|
170
163
|
|
|
171
|
-
|
|
164
|
+
### Sentry
|
|
172
165
|
|
|
173
|
-
|
|
166
|
+
Adds `@sentry/react-native`, initialises in the app entry point, and exports `captureException` + `addBreadcrumb` helpers. Replace `__YOUR_DSN__` in `src/utils/sentry.ts` with your project DSN.
|
|
174
167
|
|
|
175
|
-
|
|
176
|
-
features/
|
|
177
|
-
auth/
|
|
178
|
-
screens/
|
|
179
|
-
LoginScreen.tsx
|
|
180
|
-
RegisterScreen.tsx
|
|
181
|
-
hooks/
|
|
182
|
-
useLogin.ts
|
|
183
|
-
api/
|
|
184
|
-
authApi.ts
|
|
185
|
-
```
|
|
168
|
+
### i18n
|
|
186
169
|
|
|
187
|
-
|
|
170
|
+
Adds `i18next` + `react-i18next` with:
|
|
171
|
+
- Language auto-detection via `react-native-localize`
|
|
172
|
+
- English + Spanish locale files as a starting point
|
|
173
|
+
- Typed `useAppTranslation` hook
|
|
188
174
|
|
|
189
|
-
|
|
175
|
+
### Maestro E2E
|
|
190
176
|
|
|
191
|
-
|
|
177
|
+
Generates `.maestro/flows/` with welcome, login, and logout flows. Automatically adds a Maestro job to the CI workflow when both are selected.
|
|
192
178
|
|
|
193
|
-
|
|
179
|
+
### CI
|
|
194
180
|
|
|
195
|
-
|
|
181
|
+
GitHub Actions workflow with lint and test jobs on push/PR to `main` and `develop`.
|
|
196
182
|
|
|
197
183
|
---
|
|
198
184
|
|
|
199
|
-
##
|
|
200
|
-
|
|
201
|
-
**In Progress**
|
|
185
|
+
## Tooling (Always Included)
|
|
202
186
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
**Not Planned (Intentionally)**
|
|
216
|
-
|
|
217
|
-
- Custom UI component library — use your own or NativeBase, Tamagui, etc.
|
|
218
|
-
- Analytics or tracking — too opinionated, too many options
|
|
219
|
-
- Monorepo setup — out of scope for v1
|
|
187
|
+
| Tool | Config |
|
|
188
|
+
|---|---|
|
|
189
|
+
| TypeScript | `strict: true`, platform-native tsconfig |
|
|
190
|
+
| ESLint | `@react-native` ruleset, `--max-warnings 0` |
|
|
191
|
+
| Prettier | `format` script wired to `package.json` |
|
|
192
|
+
| Jest | RTL setup, `transformIgnorePatterns` for RN modules |
|
|
193
|
+
| VSCode | `extensions.json` + `settings.json` (format on save) |
|
|
194
|
+
| `.env.example` | Environment variable template + `.gitignore` entry |
|
|
195
|
+
| Theme | `colors.ts`, `spacing.ts`, `typography.ts` tokens |
|
|
196
|
+
| ErrorBoundary | Class component with reset, ready to wrap your app |
|
|
197
|
+
| Navigation types | `AuthStackParamList`, `MainTabParamList` (RN CLI + auth) |
|
|
220
198
|
|
|
221
199
|
---
|
|
222
200
|
|
|
223
|
-
##
|
|
201
|
+
## Architecture
|
|
224
202
|
|
|
225
|
-
|
|
203
|
+
**Typed from the start.** TypeScript strict mode on by default. API responses, environment variables, and navigation params are all typed.
|
|
226
204
|
|
|
227
|
-
|
|
205
|
+
**Thin screens, fat hooks.** Screens handle UI. Business logic, API calls, and state live in hooks and services. Screens don't call APIs directly.
|
|
228
206
|
|
|
229
|
-
|
|
207
|
+
**Non-destructive generation.** `writeIfMissing` is used throughout — the generator never overwrites files you've already customised.
|
|
230
208
|
|
|
231
209
|
---
|
|
232
210
|
|
|
@@ -237,15 +215,24 @@ git clone https://github.com/highbeek/rnstarterkit
|
|
|
237
215
|
cd rnstarterkit
|
|
238
216
|
npm install
|
|
239
217
|
npm run build
|
|
218
|
+
|
|
219
|
+
# Test locally
|
|
220
|
+
node dist/bin/create-rnstarterkit.js myTestApp
|
|
221
|
+
node dist/bin/create-rnstarterkit.js myTestApp --preset fintech
|
|
222
|
+
|
|
223
|
+
# Test generators (from inside a generated project)
|
|
224
|
+
node /path/to/rnstarterkit/dist/bin/create-rnstarterkit.js generate screen Dashboard
|
|
240
225
|
```
|
|
241
226
|
|
|
242
|
-
Templates live in `src/templates/`.
|
|
227
|
+
Templates live in `src/templates/`. Generator logic is in `src/generators/appGenerator.ts`. The `generate` subcommand is in `src/generators/codeGenerator.ts`.
|
|
243
228
|
|
|
244
|
-
|
|
229
|
+
---
|
|
245
230
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
231
|
+
## Status
|
|
232
|
+
|
|
233
|
+
Current version: `1.0.2-beta.13`
|
|
234
|
+
|
|
235
|
+
Beta release — core scaffolding, presets, and generators are working. Feedback and contributions welcome.
|
|
249
236
|
|
|
250
237
|
---
|
|
251
238
|
|
|
@@ -4,15 +4,90 @@ 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
|
+
},
|
|
30
|
+
fintech: {
|
|
31
|
+
platform: "React Native CLI",
|
|
32
|
+
typescript: true,
|
|
33
|
+
absoluteImports: true,
|
|
34
|
+
state: "Redux Toolkit",
|
|
35
|
+
auth: true,
|
|
36
|
+
dataFetching: "React Query",
|
|
37
|
+
validation: [],
|
|
38
|
+
storage: "MMKV",
|
|
39
|
+
apiClient: true,
|
|
40
|
+
apiClientType: "Fetch",
|
|
41
|
+
ci: true,
|
|
42
|
+
husky: true,
|
|
43
|
+
sentry: true,
|
|
44
|
+
i18n: true,
|
|
45
|
+
maestro: true,
|
|
46
|
+
},
|
|
47
|
+
social: {
|
|
48
|
+
platform: "Expo",
|
|
49
|
+
typescript: true,
|
|
50
|
+
absoluteImports: true,
|
|
51
|
+
state: "Zustand",
|
|
52
|
+
auth: true,
|
|
53
|
+
dataFetching: "SWR",
|
|
54
|
+
validation: [],
|
|
55
|
+
storage: "AsyncStorage",
|
|
56
|
+
apiClient: true,
|
|
57
|
+
apiClientType: "Fetch",
|
|
58
|
+
ci: true,
|
|
59
|
+
husky: true,
|
|
60
|
+
sentry: true,
|
|
61
|
+
i18n: true,
|
|
62
|
+
maestro: false,
|
|
63
|
+
},
|
|
64
|
+
indie: {
|
|
65
|
+
typescript: true,
|
|
66
|
+
absoluteImports: true,
|
|
67
|
+
state: "Context API",
|
|
68
|
+
auth: true,
|
|
69
|
+
dataFetching: "React Query",
|
|
70
|
+
validation: [],
|
|
71
|
+
storage: "AsyncStorage",
|
|
72
|
+
apiClient: true,
|
|
73
|
+
apiClientType: "Fetch",
|
|
74
|
+
ci: false,
|
|
75
|
+
husky: true,
|
|
76
|
+
sentry: false,
|
|
77
|
+
i18n: false,
|
|
78
|
+
maestro: false,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Interactive prompt
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
async function runInteractivePrompt(prefilledName, presetDefaults) {
|
|
11
85
|
const answers = await inquirer_1.default.prompt([
|
|
12
86
|
{
|
|
13
87
|
type: "input",
|
|
14
88
|
name: "projectName",
|
|
15
89
|
message: "Project name:",
|
|
90
|
+
when: () => !prefilledName,
|
|
16
91
|
validate: (input) => /^[A-Za-z0-9]+$/.test(input)
|
|
17
92
|
? true
|
|
18
93
|
: "Project name must be alphanumeric",
|
|
@@ -22,18 +97,21 @@ async function main() {
|
|
|
22
97
|
name: "platform",
|
|
23
98
|
message: "Choose platform",
|
|
24
99
|
choices: ["Expo", "React Native CLI"],
|
|
100
|
+
when: () => !presetDefaults?.platform,
|
|
25
101
|
},
|
|
26
102
|
{
|
|
27
103
|
type: "confirm",
|
|
28
104
|
name: "typescript",
|
|
29
105
|
message: "Use TypeScript?",
|
|
30
106
|
default: true,
|
|
107
|
+
when: () => presetDefaults?.typescript === undefined,
|
|
31
108
|
},
|
|
32
109
|
{
|
|
33
110
|
type: "confirm",
|
|
34
111
|
name: "absoluteImports",
|
|
35
112
|
message: "Use absolute import alias (@/*)?",
|
|
36
113
|
default: true,
|
|
114
|
+
when: () => presetDefaults?.absoluteImports === undefined,
|
|
37
115
|
},
|
|
38
116
|
{
|
|
39
117
|
type: "rawlist",
|
|
@@ -41,6 +119,7 @@ async function main() {
|
|
|
41
119
|
message: "State management?",
|
|
42
120
|
choices: ["None", "Context API", "Redux Toolkit", "Zustand"],
|
|
43
121
|
default: "Context API",
|
|
122
|
+
when: () => !presetDefaults?.state,
|
|
44
123
|
},
|
|
45
124
|
{
|
|
46
125
|
type: "rawlist",
|
|
@@ -48,6 +127,7 @@ async function main() {
|
|
|
48
127
|
message: "Data fetching library?",
|
|
49
128
|
choices: ["None", "React Query", "SWR"],
|
|
50
129
|
default: "None",
|
|
130
|
+
when: () => !presetDefaults?.dataFetching,
|
|
51
131
|
},
|
|
52
132
|
{
|
|
53
133
|
type: "checkbox",
|
|
@@ -55,6 +135,7 @@ async function main() {
|
|
|
55
135
|
message: "Validation libraries?",
|
|
56
136
|
choices: ["Formik", "React Hook Form", "Yup"],
|
|
57
137
|
default: [],
|
|
138
|
+
when: () => !presetDefaults?.validation?.length,
|
|
58
139
|
validate: (selected) => {
|
|
59
140
|
if (selected.includes("Formik") && !selected.includes("Yup")) {
|
|
60
141
|
return "Formik requires Yup in this starter. Select Yup as well.";
|
|
@@ -68,18 +149,21 @@ async function main() {
|
|
|
68
149
|
message: "Storage method?",
|
|
69
150
|
choices: ["AsyncStorage", "MMKV", "None"],
|
|
70
151
|
default: "AsyncStorage",
|
|
152
|
+
when: () => !presetDefaults?.storage,
|
|
71
153
|
},
|
|
72
154
|
{
|
|
73
155
|
type: "confirm",
|
|
74
156
|
name: "auth",
|
|
75
157
|
message: "Include auth scaffold?",
|
|
76
158
|
default: false,
|
|
159
|
+
when: () => presetDefaults?.auth === undefined,
|
|
77
160
|
},
|
|
78
161
|
{
|
|
79
162
|
type: "confirm",
|
|
80
163
|
name: "apiClient",
|
|
81
164
|
message: "Include API client scaffold?",
|
|
82
165
|
default: true,
|
|
166
|
+
when: () => presetDefaults?.apiClient === undefined,
|
|
83
167
|
},
|
|
84
168
|
{
|
|
85
169
|
type: "rawlist",
|
|
@@ -87,25 +171,119 @@ async function main() {
|
|
|
87
171
|
message: "API client transport?",
|
|
88
172
|
choices: ["Fetch", "Axios"],
|
|
89
173
|
default: "Fetch",
|
|
90
|
-
when: (
|
|
174
|
+
when: (a) => presetDefaults?.apiClientType === undefined && a.apiClient,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
type: "confirm",
|
|
178
|
+
name: "sentry",
|
|
179
|
+
message: "Include Sentry crash reporting?",
|
|
180
|
+
default: false,
|
|
181
|
+
when: () => presetDefaults?.sentry === undefined,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
type: "confirm",
|
|
185
|
+
name: "i18n",
|
|
186
|
+
message: "Include i18n (i18next + react-i18next)?",
|
|
187
|
+
default: false,
|
|
188
|
+
when: () => presetDefaults?.i18n === undefined,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
type: "confirm",
|
|
192
|
+
name: "maestro",
|
|
193
|
+
message: "Include Maestro E2E test flows?",
|
|
194
|
+
default: false,
|
|
195
|
+
when: () => presetDefaults?.maestro === undefined,
|
|
91
196
|
},
|
|
92
197
|
{
|
|
93
198
|
type: "confirm",
|
|
94
199
|
name: "ci",
|
|
95
|
-
message: "Include CI setup?",
|
|
200
|
+
message: "Include CI setup (GitHub Actions)?",
|
|
96
201
|
default: false,
|
|
202
|
+
when: () => presetDefaults?.ci === undefined,
|
|
97
203
|
},
|
|
98
204
|
{
|
|
99
205
|
type: "confirm",
|
|
100
206
|
name: "husky",
|
|
101
207
|
message: "Set up Husky pre-commit hooks (lint-staged)?",
|
|
102
208
|
default: true,
|
|
209
|
+
when: () => presetDefaults?.husky === undefined,
|
|
103
210
|
},
|
|
104
211
|
]);
|
|
105
|
-
|
|
212
|
+
return {
|
|
213
|
+
projectName: prefilledName ?? answers.projectName,
|
|
214
|
+
platform: answers.platform ?? presetDefaults?.platform ?? "Expo",
|
|
215
|
+
typescript: answers.typescript ?? presetDefaults?.typescript ?? true,
|
|
216
|
+
absoluteImports: answers.absoluteImports ?? presetDefaults?.absoluteImports ?? true,
|
|
217
|
+
state: answers.state ?? presetDefaults?.state ?? "None",
|
|
218
|
+
auth: answers.auth ?? presetDefaults?.auth ?? false,
|
|
219
|
+
dataFetching: answers.dataFetching ?? presetDefaults?.dataFetching ?? "None",
|
|
220
|
+
validation: answers.validation ?? presetDefaults?.validation ?? [],
|
|
221
|
+
storage: answers.storage ?? presetDefaults?.storage ?? "AsyncStorage",
|
|
222
|
+
apiClient: answers.apiClient ?? presetDefaults?.apiClient ?? false,
|
|
223
|
+
apiClientType: answers.apiClientType ?? presetDefaults?.apiClientType,
|
|
224
|
+
sentry: answers.sentry ?? presetDefaults?.sentry ?? false,
|
|
225
|
+
i18n: answers.i18n ?? presetDefaults?.i18n ?? false,
|
|
226
|
+
maestro: answers.maestro ?? presetDefaults?.maestro ?? false,
|
|
227
|
+
ci: answers.ci ?? presetDefaults?.ci ?? false,
|
|
228
|
+
husky: answers.husky ?? presetDefaults?.husky ?? true,
|
|
229
|
+
};
|
|
106
230
|
}
|
|
107
|
-
|
|
108
|
-
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// CLI wiring
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
const program = new commander_1.Command();
|
|
235
|
+
program
|
|
236
|
+
.name("create-rnstarterkit")
|
|
237
|
+
.description("Scaffold a production-ready React Native app")
|
|
238
|
+
.version("1.0.2-beta.12")
|
|
239
|
+
.argument("[projectName]", "Name of the project (alphanumeric)")
|
|
240
|
+
.option("--preset <name>", "Skip prompts with a preset: minimal | fintech | social | indie")
|
|
241
|
+
.action(async (projectName, cmdOptions) => {
|
|
242
|
+
console.log("🚀 Welcome to RNStarterKit!");
|
|
243
|
+
const presetKey = cmdOptions.preset;
|
|
244
|
+
if (presetKey && !(presetKey in PRESETS)) {
|
|
245
|
+
console.error(`❌ Unknown preset "${presetKey}". Available: ${Object.keys(PRESETS).join(", ")}`);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
const presetDefaults = presetKey ? PRESETS[presetKey] : undefined;
|
|
249
|
+
// Preset + name supplied → fully non-interactive
|
|
250
|
+
if (presetDefaults && projectName) {
|
|
251
|
+
const options = {
|
|
252
|
+
projectName,
|
|
253
|
+
platform: presetDefaults.platform ?? "Expo",
|
|
254
|
+
typescript: presetDefaults.typescript ?? true,
|
|
255
|
+
absoluteImports: presetDefaults.absoluteImports ?? true,
|
|
256
|
+
state: presetDefaults.state ?? "None",
|
|
257
|
+
auth: presetDefaults.auth ?? false,
|
|
258
|
+
dataFetching: presetDefaults.dataFetching ?? "None",
|
|
259
|
+
validation: presetDefaults.validation ?? [],
|
|
260
|
+
storage: presetDefaults.storage ?? "AsyncStorage",
|
|
261
|
+
apiClient: presetDefaults.apiClient ?? false,
|
|
262
|
+
apiClientType: presetDefaults.apiClientType,
|
|
263
|
+
sentry: presetDefaults.sentry ?? false,
|
|
264
|
+
i18n: presetDefaults.i18n ?? false,
|
|
265
|
+
maestro: presetDefaults.maestro ?? false,
|
|
266
|
+
ci: presetDefaults.ci ?? false,
|
|
267
|
+
husky: presetDefaults.husky ?? true,
|
|
268
|
+
};
|
|
269
|
+
console.log(`⚡ Using preset: ${presetKey}`);
|
|
270
|
+
await (0, appGenerator_1.generateApp)(options);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const options = await runInteractivePrompt(projectName, presetDefaults);
|
|
274
|
+
await (0, appGenerator_1.generateApp)(options);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
// generate subcommand
|
|
278
|
+
program
|
|
279
|
+
.command("generate <type> <name>")
|
|
280
|
+
.description("Generate code inside an existing rnstarterkit project\n" +
|
|
281
|
+
" Types: screen | component | hook | slice")
|
|
282
|
+
.action(async (type, name) => {
|
|
283
|
+
await (0, codeGenerator_1.generateCode)(type, name);
|
|
284
|
+
});
|
|
285
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
286
|
+
console.error("❌ Failed.");
|
|
109
287
|
console.error(error);
|
|
110
288
|
process.exit(1);
|
|
111
289
|
});
|