@highbeek/create-rnstarterkit 1.0.2-beta.14 → 1.0.2-beta.17
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
CHANGED
|
@@ -36,6 +36,27 @@ npx @highbeek/create-rnstarterkit myApp --preset indie
|
|
|
36
36
|
npx @highbeek/create-rnstarterkit myApp --preset minimal
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
### After scaffolding — React Native CLI projects
|
|
40
|
+
|
|
41
|
+
Native dependencies need linking via CocoaPods before you can run on iOS:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cd myApp
|
|
45
|
+
npm install # already run by the generator, skip if done
|
|
46
|
+
cd ios && pod install && cd ..
|
|
47
|
+
|
|
48
|
+
# Then run
|
|
49
|
+
npx react-native run-ios
|
|
50
|
+
npx react-native run-android
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### After scaffolding — Expo projects
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cd myApp
|
|
57
|
+
npx expo start
|
|
58
|
+
```
|
|
59
|
+
|
|
39
60
|
---
|
|
40
61
|
|
|
41
62
|
## Presets
|
|
@@ -69,6 +90,7 @@ When run without a preset, the CLI walks you through your stack:
|
|
|
69
90
|
? Sentry: Yes / No
|
|
70
91
|
? i18n (i18next): Yes / No
|
|
71
92
|
? Maestro E2E flows: Yes / No
|
|
93
|
+
? NativeWind (Tailwind): Yes / No
|
|
72
94
|
? CI (GitHub Actions): Yes / No
|
|
73
95
|
? Husky pre-commit hooks: Yes / No
|
|
74
96
|
```
|
|
@@ -161,9 +183,23 @@ Three implementations — all include Login/Register screens, protected routes,
|
|
|
161
183
|
| AsyncStorage | Default — async token persistence |
|
|
162
184
|
| MMKV | Synchronous, 10x faster, drop-in replacement |
|
|
163
185
|
|
|
186
|
+
### NativeWind (Tailwind CSS)
|
|
187
|
+
|
|
188
|
+
Adds `nativewind` + `tailwindcss` and wires everything up for your platform:
|
|
189
|
+
|
|
190
|
+
| Step | Expo | React Native CLI |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `tailwind.config.js` | content globs for `app/` + `src/` | content globs for `App.tsx` + `src/` |
|
|
193
|
+
| `global.css` | created, imported in `app/_layout.tsx` | created, imported in `index.js` |
|
|
194
|
+
| `metro.config.js` | wrapped with `withNativeWind` (expo/metro-config) | wrapped with `withNativeWind` (@react-native/metro-config) |
|
|
195
|
+
| `babel.config.js` | `jsxImportSource: 'nativewind'` in babel-preset-expo | `nativewind/babel` plugin added |
|
|
196
|
+
| `tsconfig.json` | `"nativewind/types"` added to types | same |
|
|
197
|
+
|
|
198
|
+
After generation, use `className` props directly on React Native components — no extra setup needed.
|
|
199
|
+
|
|
164
200
|
### Sentry
|
|
165
201
|
|
|
166
|
-
Adds `@sentry/react-native`, initialises in the app entry point, and exports `captureException` + `addBreadcrumb` helpers.
|
|
202
|
+
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.
|
|
167
203
|
|
|
168
204
|
### i18n
|
|
169
205
|
|
|
@@ -230,7 +266,7 @@ Templates live in `src/templates/`. Generator logic is in `src/generators/appGen
|
|
|
230
266
|
|
|
231
267
|
## Status
|
|
232
268
|
|
|
233
|
-
Current version: `1.0.2-beta.
|
|
269
|
+
Current version: `1.0.2-beta.17`
|
|
234
270
|
|
|
235
271
|
Beta release — core scaffolding, presets, and generators are working. Feedback and contributions welcome.
|
|
236
272
|
|
|
@@ -26,6 +26,7 @@ const PRESETS = {
|
|
|
26
26
|
sentry: false,
|
|
27
27
|
i18n: false,
|
|
28
28
|
maestro: false,
|
|
29
|
+
nativewind: false,
|
|
29
30
|
},
|
|
30
31
|
fintech: {
|
|
31
32
|
platform: "React Native CLI",
|
|
@@ -43,6 +44,7 @@ const PRESETS = {
|
|
|
43
44
|
sentry: true,
|
|
44
45
|
i18n: true,
|
|
45
46
|
maestro: true,
|
|
47
|
+
nativewind: false,
|
|
46
48
|
},
|
|
47
49
|
social: {
|
|
48
50
|
platform: "Expo",
|
|
@@ -60,6 +62,7 @@ const PRESETS = {
|
|
|
60
62
|
sentry: true,
|
|
61
63
|
i18n: true,
|
|
62
64
|
maestro: false,
|
|
65
|
+
nativewind: false,
|
|
63
66
|
},
|
|
64
67
|
indie: {
|
|
65
68
|
typescript: true,
|
|
@@ -76,6 +79,7 @@ const PRESETS = {
|
|
|
76
79
|
sentry: false,
|
|
77
80
|
i18n: false,
|
|
78
81
|
maestro: false,
|
|
82
|
+
nativewind: false,
|
|
79
83
|
},
|
|
80
84
|
};
|
|
81
85
|
// ---------------------------------------------------------------------------
|
|
@@ -194,6 +198,13 @@ async function runInteractivePrompt(prefilledName, presetDefaults) {
|
|
|
194
198
|
default: false,
|
|
195
199
|
when: () => presetDefaults?.maestro === undefined,
|
|
196
200
|
},
|
|
201
|
+
{
|
|
202
|
+
type: "confirm",
|
|
203
|
+
name: "nativewind",
|
|
204
|
+
message: "Use NativeWind (Tailwind CSS for React Native)?",
|
|
205
|
+
default: false,
|
|
206
|
+
when: () => presetDefaults?.nativewind === undefined,
|
|
207
|
+
},
|
|
197
208
|
{
|
|
198
209
|
type: "confirm",
|
|
199
210
|
name: "ci",
|
|
@@ -224,6 +235,7 @@ async function runInteractivePrompt(prefilledName, presetDefaults) {
|
|
|
224
235
|
sentry: answers.sentry ?? presetDefaults?.sentry ?? false,
|
|
225
236
|
i18n: answers.i18n ?? presetDefaults?.i18n ?? false,
|
|
226
237
|
maestro: answers.maestro ?? presetDefaults?.maestro ?? false,
|
|
238
|
+
nativewind: answers.nativewind ?? presetDefaults?.nativewind ?? false,
|
|
227
239
|
ci: answers.ci ?? presetDefaults?.ci ?? false,
|
|
228
240
|
husky: answers.husky ?? presetDefaults?.husky ?? true,
|
|
229
241
|
};
|
|
@@ -235,7 +247,7 @@ const program = new commander_1.Command();
|
|
|
235
247
|
program
|
|
236
248
|
.name("create-rnstarterkit")
|
|
237
249
|
.description("Scaffold a production-ready React Native app")
|
|
238
|
-
.version("1.0.2-beta.
|
|
250
|
+
.version("1.0.2-beta.17")
|
|
239
251
|
.argument("[projectName]", "Name of the project (alphanumeric)")
|
|
240
252
|
.option("--preset <name>", "Skip prompts with a preset: minimal | fintech | social | indie")
|
|
241
253
|
.action(async (projectName, cmdOptions) => {
|
|
@@ -263,6 +275,7 @@ program
|
|
|
263
275
|
sentry: presetDefaults.sentry ?? false,
|
|
264
276
|
i18n: presetDefaults.i18n ?? false,
|
|
265
277
|
maestro: presetDefaults.maestro ?? false,
|
|
278
|
+
nativewind: presetDefaults.nativewind ?? false,
|
|
266
279
|
ci: presetDefaults.ci ?? false,
|
|
267
280
|
husky: presetDefaults.husky ?? true,
|
|
268
281
|
};
|
|
@@ -7,6 +7,10 @@ exports.generateApp = generateApp;
|
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
9
|
const execa_1 = require("execa");
|
|
10
|
+
const CLI_REANIMATED_VERSION = "^4.2.0";
|
|
11
|
+
const CLI_WORKLETS_VERSION = "^0.7.0";
|
|
12
|
+
const EXPO_REANIMATED_VERSION = "~4.1.1";
|
|
13
|
+
const EXPO_WORKLETS_VERSION = "0.5.1";
|
|
10
14
|
async function generateApp(options) {
|
|
11
15
|
const { platform, projectName, auth, apiClient, absoluteImports, state, dataFetching, validation, storage, typescript, apiClientType, husky, sentry, i18n, maestro, } = options;
|
|
12
16
|
const templateRoot = await resolveTemplateRoot();
|
|
@@ -80,6 +84,8 @@ async function generateApp(options) {
|
|
|
80
84
|
await configureI18n(targetPath, platform);
|
|
81
85
|
if (maestro)
|
|
82
86
|
await configureMaestro(targetPath, { auth, ci: options.ci });
|
|
87
|
+
if (options.nativewind)
|
|
88
|
+
await configureNativeWind(targetPath, platform);
|
|
83
89
|
await stampVersion(targetPath, options);
|
|
84
90
|
if (options.ci)
|
|
85
91
|
await configureCi(targetPath);
|
|
@@ -96,6 +102,7 @@ async function generateApp(options) {
|
|
|
96
102
|
await fs_extra_1.default.rename(appJs, appTsx);
|
|
97
103
|
}
|
|
98
104
|
}
|
|
105
|
+
await validateGeneratedRuntimeCompatibility(targetPath);
|
|
99
106
|
await installDependencies(targetPath);
|
|
100
107
|
console.log("✅ Project created successfully!");
|
|
101
108
|
}
|
|
@@ -246,16 +253,19 @@ async function createStandardStructure(targetPath, platform, state) {
|
|
|
246
253
|
"hooks",
|
|
247
254
|
"utils",
|
|
248
255
|
"services",
|
|
249
|
-
"api",
|
|
250
256
|
"config",
|
|
251
257
|
"theme",
|
|
252
|
-
"providers",
|
|
253
258
|
"types",
|
|
254
259
|
];
|
|
255
|
-
//
|
|
260
|
+
// context/ only when Context API is the state solution
|
|
256
261
|
if (state === "Context API" || state === "None") {
|
|
257
262
|
commonDirectories.push("context");
|
|
258
263
|
}
|
|
264
|
+
// api/ only when an API client or auth module will populate it
|
|
265
|
+
// (auth modules and apiClient copyOptionalModule both write into api/)
|
|
266
|
+
// — handled by those modules; we don't pre-create an empty dir here
|
|
267
|
+
// providers/ is NOT pre-created — writeIfMissing creates it on demand
|
|
268
|
+
// when SWR / React Query / i18n providers are written into it
|
|
259
269
|
const cliOnlyDirectories = ["navigation", "screens"];
|
|
260
270
|
const directories = platform === "Expo"
|
|
261
271
|
? commonDirectories
|
|
@@ -509,11 +519,13 @@ async function configureStateAndAuthDependencies(targetPath, options) {
|
|
|
509
519
|
dependencies["@react-navigation/bottom-tabs"] = "^7.4.0";
|
|
510
520
|
dependencies["react-native-screens"] = "^4.16.0";
|
|
511
521
|
dependencies["react-native-gesture-handler"] = "^2.28.0";
|
|
512
|
-
dependencies["react-native-
|
|
522
|
+
dependencies["react-native-worklets"] = CLI_WORKLETS_VERSION;
|
|
523
|
+
dependencies["react-native-reanimated"] = CLI_REANIMATED_VERSION;
|
|
513
524
|
}
|
|
514
525
|
// Expo with auth uses expo-router tabs which require reanimated
|
|
515
526
|
if (options.auth && options.platform === "Expo") {
|
|
516
|
-
dependencies["react-native-
|
|
527
|
+
dependencies["react-native-worklets"] = EXPO_WORKLETS_VERSION;
|
|
528
|
+
dependencies["react-native-reanimated"] = EXPO_REANIMATED_VERSION;
|
|
517
529
|
}
|
|
518
530
|
// AsyncStorage: needed when explicitly selected OR when auth is enabled
|
|
519
531
|
// (auth modules use AsyncStorage for welcome screen persistence)
|
|
@@ -2032,7 +2044,7 @@ async function configureTsconfigAlias(targetPath, absoluteImports) {
|
|
|
2032
2044
|
if (absoluteImports) {
|
|
2033
2045
|
tsconfig.compilerOptions.baseUrl = ".";
|
|
2034
2046
|
tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
|
|
2035
|
-
tsconfig.compilerOptions.paths["@/*"] = ["
|
|
2047
|
+
tsconfig.compilerOptions.paths["@/*"] = ["./src/*"];
|
|
2036
2048
|
}
|
|
2037
2049
|
else if (tsconfig.compilerOptions.paths) {
|
|
2038
2050
|
delete tsconfig.compilerOptions.paths["@/*"];
|
|
@@ -2050,8 +2062,10 @@ async function writeCliBabelConfig(targetPath, options) {
|
|
|
2050
2062
|
if (options.useDotenv)
|
|
2051
2063
|
plugins.push("'module:react-native-dotenv'");
|
|
2052
2064
|
if (options.useAbsoluteImports) {
|
|
2053
|
-
plugins.push("['module-resolver', { root: ['./'], alias: { '@': './' } }]");
|
|
2065
|
+
plugins.push("['module-resolver', { root: ['./src'], alias: { '@': './src' } }]");
|
|
2054
2066
|
}
|
|
2067
|
+
// Must stay last when present.
|
|
2068
|
+
plugins.push("'react-native-worklets/plugin'");
|
|
2055
2069
|
const pluginsLine = plugins.length > 0 ? `,\n plugins: [${plugins.join(", ")}]` : "";
|
|
2056
2070
|
const content = `module.exports = {\n presets: ['module:@react-native/babel-preset']${pluginsLine},\n};\n`;
|
|
2057
2071
|
await fs_extra_1.default.writeFile(babelConfigPath, content, "utf8");
|
|
@@ -2083,6 +2097,31 @@ async function ensureDependencies(targetPath, patch) {
|
|
|
2083
2097
|
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2084
2098
|
}
|
|
2085
2099
|
}
|
|
2100
|
+
function parseMajorMinor(version) {
|
|
2101
|
+
const match = version.match(/(\d+)\.(\d+)/);
|
|
2102
|
+
if (!match)
|
|
2103
|
+
return null;
|
|
2104
|
+
return { major: Number(match[1]), minor: Number(match[2]) };
|
|
2105
|
+
}
|
|
2106
|
+
async function validateGeneratedRuntimeCompatibility(targetPath) {
|
|
2107
|
+
const packageJsonPath = path_1.default.join(targetPath, "package.json");
|
|
2108
|
+
if (!(await fs_extra_1.default.pathExists(packageJsonPath)))
|
|
2109
|
+
return;
|
|
2110
|
+
const pkg = await fs_extra_1.default.readJson(packageJsonPath);
|
|
2111
|
+
const rnVersion = pkg.dependencies?.["react-native"];
|
|
2112
|
+
const reanimatedVersion = pkg.dependencies?.["react-native-reanimated"];
|
|
2113
|
+
if (!rnVersion || !reanimatedVersion)
|
|
2114
|
+
return;
|
|
2115
|
+
const rn = parseMajorMinor(String(rnVersion));
|
|
2116
|
+
const reanimated = parseMajorMinor(String(reanimatedVersion));
|
|
2117
|
+
if (!rn || !reanimated)
|
|
2118
|
+
return;
|
|
2119
|
+
// Reanimated 3 is not compatible with RN >= 0.82.
|
|
2120
|
+
if (rn.major === 0 && rn.minor >= 82 && reanimated.major < 4) {
|
|
2121
|
+
throw new Error(`❌ Invalid dependency combination detected: react-native@${rnVersion} with react-native-reanimated@${reanimatedVersion}. ` +
|
|
2122
|
+
`Use react-native-reanimated@4+ (and react-native-worklets) for React Native 0.${rn.minor}.x.`);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2086
2125
|
async function writeIfMissing(filePath, content) {
|
|
2087
2126
|
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
|
|
2088
2127
|
if (!(await fs_extra_1.default.pathExists(filePath))) {
|
|
@@ -2261,6 +2300,112 @@ async function configureTesting(targetPath) {
|
|
|
2261
2300
|
}
|
|
2262
2301
|
}
|
|
2263
2302
|
}
|
|
2303
|
+
// ---------------------------------------------------------------------------
|
|
2304
|
+
// NativeWind (Tailwind CSS for React Native)
|
|
2305
|
+
// ---------------------------------------------------------------------------
|
|
2306
|
+
async function configureNativeWind(targetPath, platform) {
|
|
2307
|
+
// 1. Install dependencies
|
|
2308
|
+
await ensureDependencies(targetPath, {
|
|
2309
|
+
dependencies: {
|
|
2310
|
+
nativewind: "^4.1.23",
|
|
2311
|
+
tailwindcss: "^3.4.17",
|
|
2312
|
+
},
|
|
2313
|
+
});
|
|
2314
|
+
// 2. tailwind.config.js
|
|
2315
|
+
const contentGlobs = platform === "Expo"
|
|
2316
|
+
? `['./app/**/*.{js,jsx,ts,tsx}', './src/**/*.{js,jsx,ts,tsx}', './components/**/*.{js,jsx,ts,tsx}']`
|
|
2317
|
+
: `['./App.{js,jsx,ts,tsx}', './src/**/*.{js,jsx,ts,tsx}']`;
|
|
2318
|
+
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
2319
|
+
module.exports = {
|
|
2320
|
+
content: ${contentGlobs},
|
|
2321
|
+
presets: [require('nativewind/preset')],
|
|
2322
|
+
theme: {
|
|
2323
|
+
extend: {},
|
|
2324
|
+
},
|
|
2325
|
+
plugins: [],
|
|
2326
|
+
};
|
|
2327
|
+
`;
|
|
2328
|
+
await writeIfMissing(path_1.default.join(targetPath, "tailwind.config.js"), tailwindConfig);
|
|
2329
|
+
// Both platforms need global.css
|
|
2330
|
+
const globalCss = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n`;
|
|
2331
|
+
await writeIfMissing(path_1.default.join(targetPath, "global.css"), globalCss);
|
|
2332
|
+
const metroPath = path_1.default.join(targetPath, "metro.config.js");
|
|
2333
|
+
if (platform === "Expo") {
|
|
2334
|
+
// Expo: metro.config.js — wrap with withNativeWind using expo/metro-config
|
|
2335
|
+
const expoMetroContent = `const { getDefaultConfig } = require('expo/metro-config');
|
|
2336
|
+
const { withNativeWind } = require('nativewind/metro');
|
|
2337
|
+
|
|
2338
|
+
const config = getDefaultConfig(__dirname);
|
|
2339
|
+
|
|
2340
|
+
module.exports = withNativeWind(config, { input: './global.css' });
|
|
2341
|
+
`;
|
|
2342
|
+
await fs_extra_1.default.writeFile(metroPath, expoMetroContent, "utf8");
|
|
2343
|
+
// Expo: babel.config.js — jsxImportSource switches JSX transform to nativewind
|
|
2344
|
+
const expoBabelContent = `module.exports = function (api) {
|
|
2345
|
+
api.cache(true);
|
|
2346
|
+
return {
|
|
2347
|
+
presets: [
|
|
2348
|
+
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
|
|
2349
|
+
],
|
|
2350
|
+
};
|
|
2351
|
+
};
|
|
2352
|
+
`;
|
|
2353
|
+
await fs_extra_1.default.writeFile(path_1.default.join(targetPath, "babel.config.js"), expoBabelContent, "utf8");
|
|
2354
|
+
// Expo: inject global.css import at the top of app/_layout.tsx
|
|
2355
|
+
const layoutPath = path_1.default.join(targetPath, "app", "_layout.tsx");
|
|
2356
|
+
if (await fs_extra_1.default.pathExists(layoutPath)) {
|
|
2357
|
+
const layoutContent = await fs_extra_1.default.readFile(layoutPath, "utf8");
|
|
2358
|
+
if (!layoutContent.includes("global.css")) {
|
|
2359
|
+
await fs_extra_1.default.writeFile(layoutPath, `import '../global.css';\n${layoutContent}`, "utf8");
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
else {
|
|
2364
|
+
// CLI: metro.config.js — replace with withNativeWind using @react-native/metro-config
|
|
2365
|
+
const cliMetroContent = `const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
|
|
2366
|
+
const { withNativeWind } = require('nativewind/metro');
|
|
2367
|
+
|
|
2368
|
+
const config = mergeConfig(getDefaultConfig(__dirname), {});
|
|
2369
|
+
|
|
2370
|
+
module.exports = withNativeWind(config, { input: './global.css' });
|
|
2371
|
+
`;
|
|
2372
|
+
await fs_extra_1.default.writeFile(metroPath, cliMetroContent, "utf8");
|
|
2373
|
+
// CLI: babel.config.js — add nativewind/babel plugin
|
|
2374
|
+
const babelPath = path_1.default.join(targetPath, "babel.config.js");
|
|
2375
|
+
if (await fs_extra_1.default.pathExists(babelPath)) {
|
|
2376
|
+
let babelContent = await fs_extra_1.default.readFile(babelPath, "utf8");
|
|
2377
|
+
if (!babelContent.includes("nativewind/babel")) {
|
|
2378
|
+
babelContent = babelContent.replace(/plugins:\s*\[([^\]]*)\]/, (match, inner) => inner.trim()
|
|
2379
|
+
? match.replace(inner, `${inner.trimEnd()}, 'nativewind/babel'`)
|
|
2380
|
+
: `plugins: ['nativewind/babel']`);
|
|
2381
|
+
if (!babelContent.includes("nativewind/babel")) {
|
|
2382
|
+
babelContent = babelContent.replace(/module\.exports\s*=\s*\{/, `module.exports = {\n plugins: ['nativewind/babel'],`);
|
|
2383
|
+
}
|
|
2384
|
+
await fs_extra_1.default.writeFile(babelPath, babelContent, "utf8");
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
// CLI: inject global.css import at the top of index.js
|
|
2388
|
+
const indexPath = path_1.default.join(targetPath, "index.js");
|
|
2389
|
+
if (await fs_extra_1.default.pathExists(indexPath)) {
|
|
2390
|
+
const indexContent = await fs_extra_1.default.readFile(indexPath, "utf8");
|
|
2391
|
+
if (!indexContent.includes("global.css")) {
|
|
2392
|
+
await fs_extra_1.default.writeFile(indexPath, `import './global.css';\n${indexContent}`, "utf8");
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
// 4. Add nativewind types to tsconfig
|
|
2397
|
+
const tsconfigPath = path_1.default.join(targetPath, "tsconfig.json");
|
|
2398
|
+
if (await fs_extra_1.default.pathExists(tsconfigPath)) {
|
|
2399
|
+
const tsconfig = await fs_extra_1.default.readJson(tsconfigPath);
|
|
2400
|
+
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
|
|
2401
|
+
const types = tsconfig.compilerOptions.types || [];
|
|
2402
|
+
if (!types.includes("nativewind/types")) {
|
|
2403
|
+
tsconfig.compilerOptions.types = [...types, "nativewind/types"];
|
|
2404
|
+
}
|
|
2405
|
+
await fs_extra_1.default.writeJson(tsconfigPath, tsconfig, { spaces: 2 });
|
|
2406
|
+
}
|
|
2407
|
+
console.log("🎨 NativeWind configured — use className props on your React Native components.");
|
|
2408
|
+
}
|
|
2264
2409
|
async function stampVersion(targetPath, options) {
|
|
2265
2410
|
const packageJsonPath = path_1.default.join(targetPath, "package.json");
|
|
2266
2411
|
if (!(await fs_extra_1.default.pathExists(packageJsonPath)))
|
|
@@ -2294,6 +2439,7 @@ async function stampVersion(targetPath, options) {
|
|
|
2294
2439
|
sentry: options.sentry,
|
|
2295
2440
|
i18n: options.i18n,
|
|
2296
2441
|
maestro: options.maestro,
|
|
2442
|
+
nativewind: options.nativewind,
|
|
2297
2443
|
};
|
|
2298
2444
|
await fs_extra_1.default.writeJson(packageJsonPath, projectPkg, { spaces: 2 });
|
|
2299
2445
|
}
|
|
@@ -2669,6 +2815,8 @@ function isTextFile(filePath) {
|
|
|
2669
2815
|
".lock",
|
|
2670
2816
|
".xcprivacy",
|
|
2671
2817
|
".storyboard",
|
|
2818
|
+
".xcworkspacedata",
|
|
2819
|
+
".xcscheme",
|
|
2672
2820
|
]);
|
|
2673
2821
|
const base = path_1.default.basename(filePath).toLowerCase();
|
|
2674
2822
|
return textExtensions.has(extension) || base === "podfile" || base === "gemfile";
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"react": "19.2.3",
|
|
16
16
|
"react-native": "0.84.0",
|
|
17
17
|
"react-native-safe-area-context": "^5.5.2",
|
|
18
|
-
"react-native-
|
|
18
|
+
"react-native-worklets": "^0.7.0",
|
|
19
|
+
"react-native-reanimated": "^4.2.0"
|
|
19
20
|
},
|
|
20
21
|
"devDependencies": {
|
|
21
22
|
"@babel/core": "^7.25.2",
|
package/package.json
CHANGED