@highbeek/create-rnstarterkit 1.0.2-beta.15 → 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
|
}
|
|
@@ -512,11 +519,13 @@ async function configureStateAndAuthDependencies(targetPath, options) {
|
|
|
512
519
|
dependencies["@react-navigation/bottom-tabs"] = "^7.4.0";
|
|
513
520
|
dependencies["react-native-screens"] = "^4.16.0";
|
|
514
521
|
dependencies["react-native-gesture-handler"] = "^2.28.0";
|
|
515
|
-
dependencies["react-native-
|
|
522
|
+
dependencies["react-native-worklets"] = CLI_WORKLETS_VERSION;
|
|
523
|
+
dependencies["react-native-reanimated"] = CLI_REANIMATED_VERSION;
|
|
516
524
|
}
|
|
517
525
|
// Expo with auth uses expo-router tabs which require reanimated
|
|
518
526
|
if (options.auth && options.platform === "Expo") {
|
|
519
|
-
dependencies["react-native-
|
|
527
|
+
dependencies["react-native-worklets"] = EXPO_WORKLETS_VERSION;
|
|
528
|
+
dependencies["react-native-reanimated"] = EXPO_REANIMATED_VERSION;
|
|
520
529
|
}
|
|
521
530
|
// AsyncStorage: needed when explicitly selected OR when auth is enabled
|
|
522
531
|
// (auth modules use AsyncStorage for welcome screen persistence)
|
|
@@ -2055,6 +2064,8 @@ async function writeCliBabelConfig(targetPath, options) {
|
|
|
2055
2064
|
if (options.useAbsoluteImports) {
|
|
2056
2065
|
plugins.push("['module-resolver', { root: ['./src'], alias: { '@': './src' } }]");
|
|
2057
2066
|
}
|
|
2067
|
+
// Must stay last when present.
|
|
2068
|
+
plugins.push("'react-native-worklets/plugin'");
|
|
2058
2069
|
const pluginsLine = plugins.length > 0 ? `,\n plugins: [${plugins.join(", ")}]` : "";
|
|
2059
2070
|
const content = `module.exports = {\n presets: ['module:@react-native/babel-preset']${pluginsLine},\n};\n`;
|
|
2060
2071
|
await fs_extra_1.default.writeFile(babelConfigPath, content, "utf8");
|
|
@@ -2086,6 +2097,31 @@ async function ensureDependencies(targetPath, patch) {
|
|
|
2086
2097
|
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2087
2098
|
}
|
|
2088
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
|
+
}
|
|
2089
2125
|
async function writeIfMissing(filePath, content) {
|
|
2090
2126
|
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
|
|
2091
2127
|
if (!(await fs_extra_1.default.pathExists(filePath))) {
|
|
@@ -2264,6 +2300,112 @@ async function configureTesting(targetPath) {
|
|
|
2264
2300
|
}
|
|
2265
2301
|
}
|
|
2266
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
|
+
}
|
|
2267
2409
|
async function stampVersion(targetPath, options) {
|
|
2268
2410
|
const packageJsonPath = path_1.default.join(targetPath, "package.json");
|
|
2269
2411
|
if (!(await fs_extra_1.default.pathExists(packageJsonPath)))
|
|
@@ -2297,6 +2439,7 @@ async function stampVersion(targetPath, options) {
|
|
|
2297
2439
|
sentry: options.sentry,
|
|
2298
2440
|
i18n: options.i18n,
|
|
2299
2441
|
maestro: options.maestro,
|
|
2442
|
+
nativewind: options.nativewind,
|
|
2300
2443
|
};
|
|
2301
2444
|
await fs_extra_1.default.writeJson(packageJsonPath, projectPkg, { spaces: 2 });
|
|
2302
2445
|
}
|
|
@@ -2672,6 +2815,8 @@ function isTextFile(filePath) {
|
|
|
2672
2815
|
".lock",
|
|
2673
2816
|
".xcprivacy",
|
|
2674
2817
|
".storyboard",
|
|
2818
|
+
".xcworkspacedata",
|
|
2819
|
+
".xcscheme",
|
|
2675
2820
|
]);
|
|
2676
2821
|
const base = path_1.default.basename(filePath).toLowerCase();
|
|
2677
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