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 +132 -297
- package/package.json +5 -5
- package/templates/base/.prettierrc.js +0 -21
- package/templates/base/App.tsx +0 -21
- package/templates/base/babel.config.js +0 -9
- package/templates/base/components/ui/button.tsx +0 -55
- package/templates/base/global.css +0 -3
- package/templates/base/lib/utils.ts +0 -6
- package/templates/base/metro.config.js +0 -6
- package/templates/base/nativewind-env.d.ts +0 -1
- package/templates/base/tailwind.config.js +0 -14
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
|
|
88
|
-
|
|
89
|
-
await fs.remove(
|
|
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
|
|
105
|
-
|
|
106
|
-
await fs.remove(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
);
|
|
278
|
-
|
|
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
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
160
|
+
copySource("lib/theme.ts", path.join(projectPath, "src", "lib", "theme.ts"));
|
|
317
161
|
}
|
|
318
162
|
async function setupFormsSrc(projectPath) {
|
|
319
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
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": "
|
|
194
|
+
"react-native-reanimated": "~4.1.1",
|
|
423
195
|
"react-native-safe-area-context": "^5.6.2",
|
|
424
|
-
"react-native-
|
|
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.
|
|
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
|
-
|
|
436
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
529
|
-
{ title: "
|
|
530
|
-
|
|
531
|
-
{ title: "
|
|
532
|
-
|
|
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.
|
|
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.
|
|
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
|
-
};
|
package/templates/base/App.tsx
DELETED
|
@@ -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,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 +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
|
-
|