create-lunar-kit 0.1.4 → 0.1.7

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/dist/index.js CHANGED
@@ -58,7 +58,15 @@ var renderLogo = () => {
58
58
  // src/commands/init.ts
59
59
  import fs from "fs-extra";
60
60
  import path from "path";
61
- import { LOCAL_COMPONENTS_PATH, LOCAL_SOURCE_PATH } from "@lunar-kit/core";
61
+ import { LOCAL_COMPONENTS_PATH, LOCAL_SOURCE_PATH, LOCAL_TEMPLATES_PATH } from "@lunar-kit/core";
62
+ function copyTemplate(templatePath, destPath) {
63
+ const content = fs.readFileSync(path.join(LOCAL_TEMPLATES_PATH, templatePath));
64
+ fs.writeFileSync(destPath, content);
65
+ }
66
+ function copySource(sourcePath, destPath) {
67
+ const content = fs.readFileSync(path.join(LOCAL_SOURCE_PATH, sourcePath));
68
+ fs.writeFileSync(destPath, content);
69
+ }
62
70
  async function createSrcStructure(projectPath, navigation) {
63
71
  const dirs = [
64
72
  "src/modules",
@@ -69,6 +77,9 @@ async function createSrcStructure(projectPath, navigation) {
69
77
  "src/stores",
70
78
  "src/types"
71
79
  ];
80
+ if (navigation === "react-navigation") {
81
+ dirs.push("src/screens");
82
+ }
72
83
  for (const dir of dirs) {
73
84
  await fs.ensureDir(path.join(projectPath, dir));
74
85
  }
@@ -84,328 +95,89 @@ async function createBarrelExport(dir, filename) {
84
95
  }
85
96
  async function setupAppEntry(projectPath, navigation) {
86
97
  if (navigation === "expo-router") {
87
- const appTsxPath = path.join(projectPath, "App.tsx");
88
- if (fs.existsSync(appTsxPath)) {
89
- await fs.remove(appTsxPath);
90
- }
91
- const indexPath = path.join(projectPath, "index.ts");
92
- if (fs.existsSync(indexPath)) {
93
- await fs.remove(indexPath);
94
- }
95
- const indexJsPath = path.join(projectPath, "index.js");
96
- if (fs.existsSync(indexJsPath)) {
97
- await fs.remove(indexJsPath);
98
+ for (const file of ["App.tsx", "index.ts", "index.js"]) {
99
+ const filePath = path.join(projectPath, file);
100
+ if (fs.existsSync(filePath)) await fs.remove(filePath);
98
101
  }
99
102
  const pkgPath = path.join(projectPath, "package.json");
100
103
  const pkg = await fs.readJson(pkgPath);
101
104
  pkg.main = "expo-router/entry";
102
105
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
103
106
  } else {
104
- const indexPath = path.join(projectPath, "index.ts");
105
- if (fs.existsSync(indexPath)) {
106
- await fs.remove(indexPath);
107
- }
108
- const indexJsPath = path.join(projectPath, "index.js");
109
- if (fs.existsSync(indexJsPath)) {
110
- await fs.remove(indexJsPath);
107
+ for (const file of ["index.ts", "index.js"]) {
108
+ const filePath = path.join(projectPath, file);
109
+ if (fs.existsSync(filePath)) await fs.remove(filePath);
111
110
  }
112
- const appContent = `import Main from './src/Main';
113
-
114
- export default function App() {
115
- return <Main />;
116
- }`;
117
- await fs.writeFile(path.join(projectPath, "App.tsx"), appContent);
111
+ copyTemplate("scaffolding/App.tsx", path.join(projectPath, "App.tsx"));
118
112
  const pkgPath = path.join(projectPath, "package.json");
119
113
  const pkg = await fs.readJson(pkgPath);
120
- if (pkg.main) {
121
- delete pkg.main;
122
- }
114
+ if (pkg.main) delete pkg.main;
123
115
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
124
116
  const appJsonPath = path.join(projectPath, "app.json");
125
117
  if (fs.existsSync(appJsonPath)) {
126
118
  const appJson = await fs.readJson(appJsonPath);
127
- if (!appJson.expo) {
128
- appJson.expo = {};
129
- }
119
+ if (!appJson.expo) appJson.expo = {};
130
120
  appJson.expo.entryPoint = "./App.tsx";
131
121
  await fs.writeJson(appJsonPath, appJson, { spaces: 2 });
132
122
  }
133
- const mainContent = `import './global.css';
134
- import { StatusBar } from 'expo-status-bar';
135
- import { View, Text } from 'react-native';
136
- import { Button } from './components/ui/button';
137
-
138
- export default function Main() {
139
- return (
140
- <View className="flex-1 items-center justify-center">
141
- <Text className="text-3xl font-bold text-slate-900 mb-2">
142
- \u{1F319} Lunar Kit
143
- </Text>
144
- <Text className="text-slate-600 mb-8">
145
- Your app is ready!
146
- </Text>
147
- <Button onPress={() => alert('Hello Lunar Kit!')}>
148
- Get Started
149
- </Button>
150
- <StatusBar style="auto" />
151
- </View>
152
- );
153
- }`;
154
- await fs.writeFile(path.join(projectPath, "src", "Main.tsx"), mainContent);
123
+ copyTemplate("scaffolding/Main.tsx", path.join(projectPath, "src", "Main.tsx"));
155
124
  }
156
125
  }
157
126
  async function setupExpoRouterSrc(projectPath) {
158
127
  const appDir = path.join(projectPath, "app");
159
128
  await fs.ensureDir(appDir);
160
- const layoutContent = `import React from 'react';
161
- import '../src/global.css';
162
- import { Stack } from 'expo-router';
163
- import { GestureHandlerRootView } from 'react-native-gesture-handler';
164
- import { ThemeProvider } from '@/providers/theme-provider';
165
- import { useColorScheme } from 'nativewind';
166
- import { useThemeColors } from '@/hooks/useThemeColors';
167
-
168
- export default function RootLayout() {
169
- const { colorScheme } = useColorScheme();
170
- const { colors } = useThemeColors()
171
- return (<GestureHandlerRootView style={{ flex: 1 }}>
172
- <ThemeProvider>
173
- <Stack screenOptions={{
174
- headerShown: false,
175
- contentStyle: {
176
- backgroundColor: colors.background,
177
-
178
- },
179
- }} key={colorScheme} />
180
- </ThemeProvider>
181
- </GestureHandlerRootView>
182
- );
183
- }`;
184
- await fs.writeFile(path.join(appDir, "_layout.tsx"), layoutContent);
185
- const indexContent = `import React from 'react';
186
- import { View, Text } from 'react-native';
187
- import { Button } from '@/components/ui/button';
188
-
189
- export default function IndexScreen() {
190
- return (
191
- <View className="flex-1 items-center justify-center">
192
- <Text className="text-3xl font-bold text-slate-900 mb-2">
193
- \u{1F319} Lunar Kit
194
- </Text>
195
- <Text className="text-slate-600 mb-8">
196
- Your app is ready!
197
- </Text>
198
- <Button onPress={() => alert('Hello Lunar Kit!')}>
199
- Get Started
200
- </Button>
201
- </View>
202
- );
203
- }`;
204
- await fs.writeFile(path.join(appDir, "index.tsx"), indexContent);
129
+ copyTemplate("expo-router/_layout.tsx", path.join(appDir, "_layout.tsx"));
130
+ copyTemplate("expo-router/index.tsx", path.join(appDir, "index.tsx"));
205
131
  }
206
132
  async function setupReactNavigationSrc(projectPath) {
207
- const navContent = `import { NavigationContainer } from '@react-navigation/native';
208
- import { createNativeStackNavigator } from '@react-navigation/native-stack';
209
- import HomeScreen from './screens/HomeScreen';
210
-
211
- const Stack = createNativeStackNavigator();
212
-
213
- export default function Navigation() {
214
- return (
215
- <NavigationContainer>
216
- <Stack.Navigator screenOptions={{ headerShown: false }}>
217
- <Stack.Screen name="Home" component={HomeScreen} />
218
- </Stack.Navigator>
219
- </NavigationContainer>
220
- );
221
- }`;
222
- await fs.writeFile(path.join(projectPath, "src", "Navigation.tsx"), navContent);
223
- const homeContent = `import React from 'react';
224
- import { View, Text } from 'react-native';
225
- import { Button } from '../components/ui/button';
226
-
227
- export default function HomeScreen() {
228
- return (
229
- <View className="flex-1 items-center justify-center">
230
- <Text className="text-3xl font-bold text-slate-900 mb-2">
231
- \u{1F319} Lunar Kit
232
- </Text>
233
- <Text className="text-slate-600 mb-8">
234
- Your app is ready!
235
- </Text>
236
- <Button onPress={() => alert('Hello Lunar Kit!')}>
237
- Get Started
238
- </Button>
239
- </View>
240
- );
241
- }`;
242
- await fs.writeFile(path.join(projectPath, "src", "screens", "HomeScreen.tsx"), homeContent);
243
- const mainContent = `import React from 'react';
244
- import './global.css';
245
- import { StatusBar } from 'expo-status-bar';
246
- import Navigation from './Navigation';
247
-
248
- export default function Main() {
249
- return (
250
- <>
251
- <Navigation />
252
- <StatusBar style="auto" />
253
- </>
254
- );
255
- }`;
256
- await fs.writeFile(path.join(projectPath, "src", "Main.tsx"), mainContent);
133
+ copyTemplate("react-navigation/Navigation.tsx", path.join(projectPath, "src", "Navigation.tsx"));
134
+ await fs.ensureDir(path.join(projectPath, "src", "screens"));
135
+ copyTemplate("react-navigation/HomeScreen.tsx", path.join(projectPath, "src", "screens", "HomeScreen.tsx"));
136
+ copyTemplate("scaffolding/Main.react-navigation.tsx", path.join(projectPath, "src", "Main.tsx"));
257
137
  }
258
138
  async function setupAuthSrc(projectPath, navigation) {
259
139
  const authDir = navigation === "expo-router" ? path.join(projectPath, "app", "(auth)") : path.join(projectPath, "src", "screens", "auth");
260
140
  await fs.ensureDir(authDir);
261
- const loginScreen = `import { View, Text } from 'react-native';
262
- import { Button } from '@/src/components/ui/button';
263
-
264
- export default function LoginScreen() {
265
- return (
266
- <View className="flex-1 items-center justify-center p-4">
267
- <Text className="text-2xl font-bold mb-4">Login</Text>
268
- <Button className="w-full">Sign In</Button>
269
- </View>
270
- );
271
- }`;
272
- await fs.writeFile(path.join(authDir, "login.tsx"), loginScreen);
141
+ copyTemplate("auth/login.tsx", path.join(authDir, "login.tsx"));
273
142
  }
274
143
  async function setupDarkModeSrc(projectPath, navigation) {
275
- const themeProvider = fs.readFileSync(
276
- path.join(LOCAL_SOURCE_PATH, "providers", "theme-provider.tsx")
277
- );
278
- const themeStore = fs.readFileSync(
279
- path.join(LOCAL_SOURCE_PATH, "stores", "theme.ts")
280
- );
144
+ await fs.ensureDir(path.join(projectPath, "src", "providers"));
145
+ copySource("providers/theme-provider.tsx", path.join(projectPath, "src", "providers", "theme-provider.tsx"));
146
+ await fs.ensureDir(path.join(projectPath, "src", "stores"));
147
+ copySource("stores/theme.ts", path.join(projectPath, "src", "stores", "theme.ts"));
281
148
  const storesIndexPath = path.join(projectPath, "src", "stores", "index.ts");
282
- let storesIndexContent = `// Auto-generated barrel export
149
+ const storesIndexContent = `// Auto-generated barrel export
283
150
  // This file is managed by Lunar Kit CLI
284
- `;
285
- storesIndexContent += `export * from './theme';
151
+ export * from './theme';
286
152
  `;
287
153
  await fs.writeFile(storesIndexPath, storesIndexContent);
288
- let toolbarContent;
289
- if (navigation === "expo-router") {
290
- toolbarContent = fs.readFileSync(
291
- path.join(LOCAL_SOURCE_PATH, "hooks", "useToolbar.tsx")
292
- );
293
- } else {
294
- toolbarContent = fs.readFileSync(
295
- path.join(LOCAL_SOURCE_PATH, "hooks", "useToolbar.react-navigation.tsx")
296
- );
297
- }
298
- const themeContent = fs.readFileSync(
299
- path.join(LOCAL_SOURCE_PATH, "hooks", "useTheme.ts")
300
- );
301
- const themeColor = fs.readFileSync(
302
- path.join(LOCAL_SOURCE_PATH, "hooks", "useThemeColors.ts")
303
- );
304
- const libTheme = fs.readFileSync(
305
- path.join(LOCAL_SOURCE_PATH, "lib", "theme.ts")
306
- );
307
- await fs.ensureDir(path.join(projectPath, "src", "providers"));
308
- await fs.writeFile(path.join(projectPath, "src", "providers", "theme-provider.tsx"), themeProvider);
309
- await fs.ensureDir(path.join(projectPath, "src", "stores"));
310
- await fs.writeFile(path.join(projectPath, "src", "stores", "theme.ts"), themeStore);
311
154
  await fs.ensureDir(path.join(projectPath, "src", "hooks"));
312
- await fs.writeFile(path.join(projectPath, "src", "hooks", "useToolbar.tsx"), toolbarContent);
313
- await fs.writeFile(path.join(projectPath, "src", "hooks", "useTheme.ts"), themeContent);
314
- await fs.writeFile(path.join(projectPath, "src", "hooks", "useThemeColors.ts"), themeColor);
155
+ const toolbarSource = navigation === "expo-router" ? "hooks/useToolbar.tsx" : "hooks/useToolbar.react-navigation.tsx";
156
+ copySource(toolbarSource, path.join(projectPath, "src", "hooks", "useToolbar.tsx"));
157
+ copySource("hooks/useTheme.ts", path.join(projectPath, "src", "hooks", "useTheme.ts"));
158
+ copySource("hooks/useThemeColors.ts", path.join(projectPath, "src", "hooks", "useThemeColors.ts"));
315
159
  await fs.ensureDir(path.join(projectPath, "src", "lib"));
316
- await fs.writeFile(path.join(projectPath, "src", "lib", "theme.ts"), libTheme);
160
+ copySource("lib/theme.ts", path.join(projectPath, "src", "lib", "theme.ts"));
317
161
  }
318
162
  async function setupFormsSrc(projectPath) {
319
- const hookContent = `import { useState } from 'react';
320
-
321
- export function useForm<T>(initialValues: T) {
322
- const [values, setValues] = useState<T>(initialValues);
323
- const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
324
-
325
- const handleChange = (name: keyof T, value: any) => {
326
- setValues(prev => ({ ...prev, [name]: value }));
327
- };
328
-
329
- const validate = (validationRules: Partial<Record<keyof T, (value: any) => string | undefined>>) => {
330
- const newErrors: Partial<Record<keyof T, string>> = {};
331
-
332
- Object.keys(validationRules).forEach((key) => {
333
- const error = validationRules[key as keyof T]?.(values[key as keyof T]);
334
- if (error) {
335
- newErrors[key as keyof T] = error;
336
- }
337
- });
338
-
339
- setErrors(newErrors);
340
- return Object.keys(newErrors).length === 0;
341
- };
342
-
343
- return { values, errors, handleChange, validate };
344
- }`;
345
- await fs.writeFile(path.join(projectPath, "src", "hooks", "useForm.ts"), hookContent);
163
+ await fs.ensureDir(path.join(projectPath, "src", "hooks"));
164
+ copyTemplate("forms/useForm.ts", path.join(projectPath, "src", "hooks", "useForm.ts"));
346
165
  }
347
166
  async function setupNativeWind(projectPath) {
348
- const globalCss = `@tailwind base;
349
- @tailwind components;
350
- @tailwind utilities;`;
351
- await fs.writeFile(path.join(projectPath, "src", "global.css"), globalCss);
352
- const tailwindConfig = fs.readFileSync(
353
- path.join(LOCAL_SOURCE_PATH, "tailwind.config.js")
354
- );
355
- await fs.writeFile(path.join(projectPath, "tailwind.config.js"), tailwindConfig);
356
- const metroConfig = `const { getDefaultConfig } = require('expo/metro-config');
357
- const { withNativeWind } = require('nativewind/metro');
358
-
359
- const config = getDefaultConfig(__dirname);
360
-
361
- module.exports = withNativeWind(config, {
362
- input: './src/global.css',
363
- });`;
364
- await fs.writeFile(path.join(projectPath, "metro.config.js"), metroConfig);
365
- const babelConfig = `module.exports = function (api) {
366
- api.cache(true);
367
- return {
368
- presets: [
369
- ['babel-preset-expo', { jsxImportSource: 'nativewind' }],
370
- 'nativewind/babel'
371
- ],
372
- };
373
- };`;
374
- await fs.writeFile(path.join(projectPath, "babel.config.js"), babelConfig);
375
- const nativewindEnv = `/// <reference types="nativewind/types" />`;
376
- await fs.writeFile(path.join(projectPath, "nativewind-env.d.ts"), nativewindEnv);
377
- const utilsContent = fs.readFileSync(
378
- path.join(LOCAL_SOURCE_PATH, "lib", "utils.ts")
379
- );
380
- const colorsContent = fs.readFileSync(
381
- path.join(LOCAL_SOURCE_PATH, "lib", "theme.ts")
382
- );
383
- await fs.writeFile(path.join(projectPath, "src", "lib", "utils.ts"), utilsContent);
384
- await fs.writeFile(path.join(projectPath, "src", "lib", "theme.ts"), colorsContent);
385
- const buttonContent = fs.readFileSync(
386
- path.join(LOCAL_COMPONENTS_PATH, "ui", "button.tsx")
387
- );
388
- const textContent = fs.readFileSync(
389
- path.join(LOCAL_COMPONENTS_PATH, "ui", "text.tsx")
390
- );
167
+ copyTemplate("config/global.css", path.join(projectPath, "src", "global.css"));
168
+ copyTemplate("config/metro.config.js", path.join(projectPath, "metro.config.js"));
169
+ copyTemplate("config/babel.config.js", path.join(projectPath, "babel.config.js"));
170
+ copyTemplate("config/nativewind-env.d.ts", path.join(projectPath, "nativewind-env.d.ts"));
171
+ copyTemplate("config/tsconfig.json", path.join(projectPath, "tsconfig.json"));
172
+ copySource("tailwind.config.js", path.join(projectPath, "tailwind.config.js"));
173
+ await fs.ensureDir(path.join(projectPath, "src", "lib"));
174
+ copySource("lib/utils.ts", path.join(projectPath, "src", "lib", "utils.ts"));
175
+ copySource("lib/theme.ts", path.join(projectPath, "src", "lib", "theme.ts"));
176
+ await fs.ensureDir(path.join(projectPath, "src", "components", "ui"));
177
+ const buttonContent = fs.readFileSync(path.join(LOCAL_COMPONENTS_PATH, "ui", "button.tsx"));
178
+ const textContent = fs.readFileSync(path.join(LOCAL_COMPONENTS_PATH, "ui", "text.tsx"));
391
179
  await fs.writeFile(path.join(projectPath, "src", "components", "ui", "button.tsx"), buttonContent);
392
180
  await fs.writeFile(path.join(projectPath, "src", "components", "ui", "text.tsx"), textContent);
393
- const tsconfigContent = `{
394
- "extends": "expo/tsconfig.base",
395
- "compilerOptions": {
396
- "strict": true,
397
- "baseUrl": ".",
398
- "paths": {
399
- "@/*": ["./src/*"],
400
- "@modules/*": ["./src/modules/*"],
401
- "@components/*": ["./src/components/*"],
402
- "@hooks/*": ["./src/hooks/*"],
403
- "@stores/*": ["./src/stores/*"],
404
- "@lib/*": ["./src/lib/*"]
405
- }
406
- }
407
- }`;
408
- await fs.writeFile(path.join(projectPath, "tsconfig.json"), tsconfigContent);
409
181
  }
410
182
  async function updatePackageJson(projectPath, navigation, features) {
411
183
  const pkgPath = path.join(projectPath, "package.json");
@@ -419,31 +191,34 @@ async function updatePackageJson(projectPath, navigation, features) {
419
191
  "@react-native-async-storage/async-storage": "^2.2.0",
420
192
  "lucide-react-native": "^0.562.0",
421
193
  "react-native-gesture-handler": "^2.28.0",
422
- "react-native-reanimated": "^4.1.6",
194
+ "react-native-reanimated": "~4.1.1",
423
195
  "react-native-safe-area-context": "^5.6.2",
424
- "react-native-worklets": "^0.5.1",
196
+ "react-native-screens": "~4.16.0",
197
+ "react-native-worklets": "0.5.1",
425
198
  "zustand": "^5.0.3"
426
199
  };
427
200
  if (navigation === "expo-router") {
428
- pkg.dependencies["expo-router"] = "^6.0.22";
429
- pkg.dependencies["react-native-screens"] = "~4.16.0";
430
- pkg.dependencies["react-native-safe-area-context"] = "^5.6.2";
201
+ pkg.dependencies["expo-router"] = "^6.0.23";
431
202
  }
432
203
  if (navigation === "react-navigation") {
433
204
  pkg.dependencies["@react-navigation/native"] = "^7.0.14";
434
205
  pkg.dependencies["@react-navigation/native-stack"] = "^7.1.10";
435
- pkg.dependencies["react-native-screens"] = "~4.4.0";
436
- pkg.dependencies["react-native-safe-area-context"] = "4.16.0";
206
+ }
207
+ if (features.includes("api")) {
208
+ pkg.dependencies["axios"] = "^1.9.0";
209
+ }
210
+ if (features.includes("env")) {
211
+ pkg.dependencies["expo-constants"] = "~18.0.8";
437
212
  }
438
213
  if (features.includes("forms")) {
439
- pkg.dependencies["react-hook-form"] = "^7.54.2";
214
+ pkg.dependencies["react-hook-form"] = "^7.71.1";
440
215
  pkg.dependencies["@hookform/resolvers"] = "^5.2.2";
441
216
  pkg.dependencies["zod"] = "^4.3.6";
442
217
  }
443
218
  pkg.devDependencies = {
444
219
  ...pkg.devDependencies,
445
220
  "tailwindcss": "3.4.17",
446
- "@expo/metro-config": "^0.20.3",
221
+ "@expo/metro-config": "^54.0.14",
447
222
  "react-native-css-interop": "^0.2.1"
448
223
  };
449
224
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
@@ -469,8 +244,55 @@ async function createConfig(projectPath, navigation, features, packageManager) {
469
244
  autoImport: true,
470
245
  autoBarrelExport: true
471
246
  };
247
+ if (features.includes("localization")) {
248
+ config.localization = {
249
+ defaultLocale: "en",
250
+ locales: ["en"],
251
+ localesDir: "src/locales"
252
+ };
253
+ }
472
254
  await fs.writeJson(path.join(projectPath, "kit.config.json"), config, { spaces: 2 });
473
255
  }
256
+ async function setupLocalizationSrc(projectPath) {
257
+ const localesDir = path.join(projectPath, "src", "locales");
258
+ await fs.ensureDir(localesDir);
259
+ copySource("locales/index.ts", path.join(localesDir, "index.ts"));
260
+ copySource("locales/en.ts", path.join(localesDir, "en.ts"));
261
+ await fs.ensureDir(path.join(projectPath, "src", "stores"));
262
+ copySource("locales/locale-store.ts", path.join(projectPath, "src", "stores", "locale.ts"));
263
+ const indexPath = path.join(localesDir, "index.ts");
264
+ let indexFile = await fs.readFile(indexPath, "utf-8");
265
+ indexFile += `
266
+ registerLocale('en', () => import('./en'));
267
+ `;
268
+ await fs.writeFile(indexPath, indexFile);
269
+ }
270
+ async function setupEnvConfig(projectPath) {
271
+ const envContent = `# App Environment
272
+ EXPO_PUBLIC_APP_ENV=development
273
+ EXPO_PUBLIC_API_URL=http://localhost:3000
274
+ `;
275
+ await fs.writeFile(path.join(projectPath, ".env"), envContent);
276
+ const envExampleContent = `# Copy this file to .env and fill in your values
277
+ EXPO_PUBLIC_APP_ENV=development
278
+ EXPO_PUBLIC_API_URL=http://localhost:3000
279
+ `;
280
+ await fs.writeFile(path.join(projectPath, ".env.example"), envExampleContent);
281
+ await fs.ensureDir(path.join(projectPath, "src", "lib"));
282
+ copyTemplate("env.ts", path.join(projectPath, "src", "lib", "env.ts"));
283
+ const gitignorePath = path.join(projectPath, ".gitignore");
284
+ if (fs.existsSync(gitignorePath)) {
285
+ let gitignore = await fs.readFile(gitignorePath, "utf-8");
286
+ if (!gitignore.includes(".env")) {
287
+ gitignore += "\n# Environment\n.env\n.env.local\n";
288
+ await fs.writeFile(gitignorePath, gitignore);
289
+ }
290
+ }
291
+ }
292
+ async function setupApiClient(projectPath) {
293
+ await fs.ensureDir(path.join(projectPath, "src", "lib"));
294
+ copyTemplate("api.ts", path.join(projectPath, "src", "lib", "api.ts"));
295
+ }
474
296
 
475
297
  // src/res/close.ts
476
298
  import chalk2 from "chalk";
@@ -525,11 +347,11 @@ program.name("create-lunar-kit").description("Create a new React Native app with
525
347
  name: "features",
526
348
  message: "Select features to include:",
527
349
  choices: [
528
- // TODO: need to add these features
529
- { title: "Authentication screens", value: "auth", selected: false },
530
- // { title: 'Dark mode support', value: 'dark-mode', selected: true },
531
- { title: "Form validation (react-hook-form)", value: "forms", selected: false }
532
- // { title: 'State management (Zustand)', value: 'state', selected: false },
350
+ { title: "\u{1F30D} Localization (i18n)", value: "localization", selected: false },
351
+ { title: "\u{1F510} Environment config (.env)", value: "env", selected: false },
352
+ { title: "\u{1F4E1} API client (axios)", value: "api", selected: false },
353
+ { title: "\u{1F511} Authentication screens", value: "auth", selected: false },
354
+ { title: "\u{1F4DD} Form validation (react-hook-form)", value: "forms", selected: false }
533
355
  ]
534
356
  },
535
357
  {
@@ -537,6 +359,7 @@ program.name("create-lunar-kit").description("Create a new React Native app with
537
359
  name: "packageManager",
538
360
  message: "Which package manager?",
539
361
  choices: [
362
+ { title: "bun", value: "bun" },
540
363
  { title: "pnpm", value: "pnpm" },
541
364
  { title: "npm", value: "npm" },
542
365
  { title: "yarn", value: "yarn" }
@@ -585,12 +408,24 @@ program.name("create-lunar-kit").description("Create a new React Native app with
585
408
  spinner.text = "Setting up forms...";
586
409
  await setupFormsSrc(projectPath);
587
410
  }
411
+ if (features.includes("localization")) {
412
+ spinner.text = "Setting up localization...";
413
+ await setupLocalizationSrc(projectPath);
414
+ }
415
+ if (features.includes("env")) {
416
+ spinner.text = "Setting up environment config...";
417
+ await setupEnvConfig(projectPath);
418
+ }
419
+ if (features.includes("api")) {
420
+ spinner.text = "Setting up API client...";
421
+ await setupApiClient(projectPath);
422
+ }
588
423
  await setupNativeWind(projectPath);
589
424
  spinner.text = "Updating dependencies...";
590
425
  await updatePackageJson(projectPath, navigation, features);
591
426
  await createConfig(projectPath, navigation, features, packageManager);
592
427
  spinner.text = `Installing dependencies with ${packageManager}...`;
593
- const installCmd = packageManager === "yarn" ? "yarn" : packageManager;
428
+ const installCmd = packageManager === "yarn" ? "yarn" : packageManager === "bun" ? "bun" : packageManager;
594
429
  const installArgs = packageManager === "npm" ? ["install"] : packageManager === "yarn" ? [] : ["install"];
595
430
  await execa(installCmd, installArgs, { cwd: projectPath });
596
431
  spinner.succeed(chalk3.green("Project created successfully! \u{1F389}"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lunar-kit",
3
- "version": "0.1.4",
3
+ "version": "0.1.7",
4
4
  "description": "Create a new React Native app with Lunar Kit and NativeWind pre-configured",
5
5
  "author": "Your Name",
6
6
  "license": "MIT",
@@ -9,12 +9,12 @@
9
9
  },
10
10
  "type": "module",
11
11
  "files": [
12
- "dist",
13
- "templates"
12
+ "dist"
14
13
  ],
15
14
  "scripts": {
16
15
  "build": "tsup",
17
- "dev": "tsup --watch"
16
+ "dev": "tsup --watch",
17
+ "prepublishOnly": "bun run build"
18
18
  },
19
19
  "keywords": [
20
20
  "react-native",
@@ -24,7 +24,7 @@
24
24
  "starter"
25
25
  ],
26
26
  "dependencies": {
27
- "@lunar-kit/core": "0.1.2",
27
+ "@lunar-kit/core": "0.1.4",
28
28
  "chalk": "^5.4.1",
29
29
  "commander": "^12.1.0",
30
30
  "execa": "^9.6.1",
@@ -1,21 +0,0 @@
1
- module.exports = {
2
- arrowParens: "always",
3
- bracketSameLine: true,
4
- bracketSpacing: true,
5
- embeddedLanguageFormatting: "auto",
6
- experimentalTernaries: false,
7
- htmlWhitespaceSensitivity: "css",
8
- insertPragma: false,
9
- jsxSingleQuote: true,
10
- printWidth: 80,
11
- proseWrap: "preserve",
12
- quoteProps: "as-needed",
13
- requirePragma: false,
14
- semi: false,
15
- singleAttributePerLine: false,
16
- singleQuote: true,
17
- tabWidth: 2,
18
- trailingComma: "es5",
19
- useTabs: false,
20
- vueIndentScriptAndStyle: false,
21
- };
@@ -1,21 +0,0 @@
1
- import './global.css';
2
- import { StatusBar } from 'expo-status-bar';
3
- import { View, Text } from 'react-native';
4
- import { Button } from './components/ui/button';
5
-
6
- export default function App() {
7
- return (
8
- <View className="flex-1 items-center justify-center bg-white">
9
- <Text className="text-3xl font-bold text-slate-900 mb-2">
10
- 🌙 Lunar Kit
11
- </Text>
12
- <Text className="text-slate-600 mb-8">
13
- Your app is ready!
14
- </Text>
15
- <Button onPress={() => alert('Hello Lunar Kit!')}>
16
- Get Started
17
- </Button>
18
- <StatusBar style="auto" />
19
- </View>
20
- );
21
- }
@@ -1,9 +0,0 @@
1
- module.exports = function (api) {
2
- api.cache(true);
3
- return {
4
- presets: [
5
- ["babel-preset-expo", { jsxImportSource: "nativewind" }],
6
- "nativewind/babel",
7
- ],
8
- };
9
- };
@@ -1,55 +0,0 @@
1
- import * as React from 'react';
2
- import { Pressable, Text } from 'react-native';
3
- import { cn } from '../../lib/utils';
4
-
5
- interface ButtonProps {
6
- children: React.ReactNode;
7
- variant?: 'default' | 'outline' | 'ghost';
8
- size?: 'default' | 'sm' | 'lg';
9
- onPress?: () => void;
10
- className?: string;
11
- }
12
-
13
- export function Button({
14
- children,
15
- variant = 'default',
16
- size = 'default',
17
- onPress,
18
- className,
19
- }: ButtonProps) {
20
- return (
21
- <Pressable
22
- onPress={onPress}
23
- className={cn(
24
- 'items-center justify-center rounded-md',
25
- {
26
- 'bg-slate-900': variant === 'default',
27
- 'border border-slate-200': variant === 'outline',
28
- 'bg-transparent': variant === 'ghost',
29
- },
30
- {
31
- 'h-10 px-4': size === 'default',
32
- 'h-9 px-3': size === 'sm',
33
- 'h-11 px-8': size === 'lg',
34
- },
35
- className
36
- )}
37
- >
38
- <Text
39
- className={cn(
40
- 'font-medium',
41
- {
42
- 'text-slate-50': variant === 'default',
43
- 'text-slate-900': variant === 'outline' || variant === 'ghost',
44
- },
45
- {
46
- 'text-sm': size === 'default' || size === 'sm',
47
- 'text-base': size === 'lg',
48
- }
49
- )}
50
- >
51
- {children}
52
- </Text>
53
- </Pressable>
54
- );
55
- }
@@ -1,3 +0,0 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
@@ -1,6 +0,0 @@
1
- import { clsx, type ClassValue } from 'clsx';
2
- import { twMerge } from 'tailwind-merge';
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs));
6
- }
@@ -1,6 +0,0 @@
1
- const { getDefaultConfig } = require("expo/metro-config");
2
- const { withNativeWind } = require('nativewind/metro');
3
-
4
- const config = getDefaultConfig(__dirname)
5
-
6
- module.exports = withNativeWind(config, { input: './global.css' })
@@ -1 +0,0 @@
1
- /// <reference types="nativewind/types" />
@@ -1,14 +0,0 @@
1
- /** @type {import('tailwindcss').Config} */
2
- module.exports = {
3
- content: [
4
- "./App.{js,jsx,ts,tsx}",
5
- "./components/**/*.{js,jsx,ts,tsx}",
6
- "../../components/**/*.{js,jsx,ts,tsx}"
7
- ],
8
- theme: {
9
- extend: {},
10
- },
11
- presets: [require("nativewind/preset")],
12
- plugins: [],
13
- }
14
-