create-lunar-kit 0.1.0
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.d.ts +1 -0
- package/dist/index.js +613 -0
- package/package.json +41 -0
- package/templates/base/.prettierrc.js +21 -0
- package/templates/base/App.tsx +21 -0
- package/templates/base/babel.config.js +9 -0
- package/templates/base/components/ui/button.tsx +55 -0
- package/templates/base/global.css +3 -0
- package/templates/base/lib/utils.ts +6 -0
- package/templates/base/metro.config.js +6 -0
- package/templates/base/nativewind-env.d.ts +1 -0
- package/templates/base/tailwind.config.js +14 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.4.49_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/esm_shims.js
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
var getFilename = () => fileURLToPath(import.meta.url);
|
|
7
|
+
var getDirname = () => path.dirname(getFilename());
|
|
8
|
+
var __dirname = /* @__PURE__ */ getDirname();
|
|
9
|
+
|
|
10
|
+
// src/index.ts
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
import prompts from "prompts";
|
|
13
|
+
import chalk3 from "chalk";
|
|
14
|
+
import ora from "ora";
|
|
15
|
+
import { execa } from "execa";
|
|
16
|
+
import fs2 from "fs-extra";
|
|
17
|
+
import path3 from "path";
|
|
18
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
19
|
+
|
|
20
|
+
// src/assets/logo.ts
|
|
21
|
+
import chalk from "chalk";
|
|
22
|
+
var renderLogo = () => {
|
|
23
|
+
console.log(
|
|
24
|
+
chalk.cyan(` ............. ............
|
|
25
|
+
................. .................
|
|
26
|
+
...... .......... .......... ......
|
|
27
|
+
...... ......... ........ .....
|
|
28
|
+
..... ............. .....
|
|
29
|
+
..... ........ .....
|
|
30
|
+
..... ....... .....
|
|
31
|
+
..... ........... .....
|
|
32
|
+
..... ...... ...... .....
|
|
33
|
+
..... ...... ...... .....
|
|
34
|
+
..... .............................. .....
|
|
35
|
+
.................................................
|
|
36
|
+
....................... .......................
|
|
37
|
+
................. ...... ...... ...............
|
|
38
|
+
........... ..... ...... ... ...... ..... ...........
|
|
39
|
+
........ ............ .... ............ ........
|
|
40
|
+
...... .......... ..... ......... ......
|
|
41
|
+
..... ....... ...... ....... .....
|
|
42
|
+
.... ...... ....... ..... ....
|
|
43
|
+
.... ....... .......... .. ....... .....
|
|
44
|
+
..... ......... .............. ......... .....
|
|
45
|
+
...... .......... ............ .......... .......
|
|
46
|
+
......... ..... ...... ....... ...... ..... ........
|
|
47
|
+
................. ..... ..... ................
|
|
48
|
+
................ ..... ......................
|
|
49
|
+
..................................................
|
|
50
|
+
................................................
|
|
51
|
+
..... ................... ......
|
|
52
|
+
..... ...... ...... .....
|
|
53
|
+
..... ...... ...... .....
|
|
54
|
+
..... ......... .....
|
|
55
|
+
..... ....... .....
|
|
56
|
+
..... ........... .....
|
|
57
|
+
..... ....... ....... .....
|
|
58
|
+
..... ......... ........ .....
|
|
59
|
+
................. .................
|
|
60
|
+
............. .............
|
|
61
|
+
`)
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// src/commands/init.ts
|
|
66
|
+
import fs from "fs-extra";
|
|
67
|
+
import path2 from "path";
|
|
68
|
+
var COMPONENTS_SOURCE = path2.join(__dirname, "../..", "src", "components");
|
|
69
|
+
var SOURCE = path2.join(__dirname, "../..", "src");
|
|
70
|
+
async function createSrcStructure(projectPath, navigation) {
|
|
71
|
+
const dirs = [
|
|
72
|
+
"src/modules",
|
|
73
|
+
"src/components/ui",
|
|
74
|
+
"src/lib",
|
|
75
|
+
"src/hooks",
|
|
76
|
+
"src/providers",
|
|
77
|
+
"src/stores",
|
|
78
|
+
"src/types"
|
|
79
|
+
];
|
|
80
|
+
for (const dir of dirs) {
|
|
81
|
+
await fs.ensureDir(path2.join(projectPath, dir));
|
|
82
|
+
}
|
|
83
|
+
await createBarrelExport(path2.join(projectPath, "src/components"), "index.ts");
|
|
84
|
+
await createBarrelExport(path2.join(projectPath, "src/hooks"), "index.ts");
|
|
85
|
+
await createBarrelExport(path2.join(projectPath, "src/stores"), "index.ts");
|
|
86
|
+
}
|
|
87
|
+
async function createBarrelExport(dir, filename) {
|
|
88
|
+
const content = `// Auto-generated barrel export
|
|
89
|
+
// This file is managed by Lunar Kit CLI
|
|
90
|
+
`;
|
|
91
|
+
await fs.writeFile(path2.join(dir, filename), content);
|
|
92
|
+
}
|
|
93
|
+
async function setupAppEntry(projectPath, navigation) {
|
|
94
|
+
if (navigation === "expo-router") {
|
|
95
|
+
const appTsxPath = path2.join(projectPath, "App.tsx");
|
|
96
|
+
if (fs.existsSync(appTsxPath)) {
|
|
97
|
+
await fs.remove(appTsxPath);
|
|
98
|
+
}
|
|
99
|
+
const indexPath = path2.join(projectPath, "index.ts");
|
|
100
|
+
if (fs.existsSync(indexPath)) {
|
|
101
|
+
await fs.remove(indexPath);
|
|
102
|
+
}
|
|
103
|
+
const indexJsPath = path2.join(projectPath, "index.js");
|
|
104
|
+
if (fs.existsSync(indexJsPath)) {
|
|
105
|
+
await fs.remove(indexJsPath);
|
|
106
|
+
}
|
|
107
|
+
const pkgPath = path2.join(projectPath, "package.json");
|
|
108
|
+
const pkg = await fs.readJson(pkgPath);
|
|
109
|
+
pkg.main = "expo-router/entry";
|
|
110
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
111
|
+
} else {
|
|
112
|
+
const indexPath = path2.join(projectPath, "index.ts");
|
|
113
|
+
if (fs.existsSync(indexPath)) {
|
|
114
|
+
await fs.remove(indexPath);
|
|
115
|
+
}
|
|
116
|
+
const indexJsPath = path2.join(projectPath, "index.js");
|
|
117
|
+
if (fs.existsSync(indexJsPath)) {
|
|
118
|
+
await fs.remove(indexJsPath);
|
|
119
|
+
}
|
|
120
|
+
const appContent = `import Main from './src/Main';
|
|
121
|
+
|
|
122
|
+
export default function App() {
|
|
123
|
+
return <Main />;
|
|
124
|
+
}`;
|
|
125
|
+
await fs.writeFile(path2.join(projectPath, "App.tsx"), appContent);
|
|
126
|
+
const pkgPath = path2.join(projectPath, "package.json");
|
|
127
|
+
const pkg = await fs.readJson(pkgPath);
|
|
128
|
+
if (pkg.main) {
|
|
129
|
+
delete pkg.main;
|
|
130
|
+
}
|
|
131
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
132
|
+
const appJsonPath = path2.join(projectPath, "app.json");
|
|
133
|
+
if (fs.existsSync(appJsonPath)) {
|
|
134
|
+
const appJson = await fs.readJson(appJsonPath);
|
|
135
|
+
if (!appJson.expo) {
|
|
136
|
+
appJson.expo = {};
|
|
137
|
+
}
|
|
138
|
+
appJson.expo.entryPoint = "./App.tsx";
|
|
139
|
+
await fs.writeJson(appJsonPath, appJson, { spaces: 2 });
|
|
140
|
+
}
|
|
141
|
+
const mainContent = `import './global.css';
|
|
142
|
+
import { StatusBar } from 'expo-status-bar';
|
|
143
|
+
import { View, Text } from 'react-native';
|
|
144
|
+
import { Button } from './components/ui/button';
|
|
145
|
+
|
|
146
|
+
export default function Main() {
|
|
147
|
+
return (
|
|
148
|
+
<View className="flex-1 items-center justify-center">
|
|
149
|
+
<Text className="text-3xl font-bold text-slate-900 mb-2">
|
|
150
|
+
\u{1F319} Lunar Kit
|
|
151
|
+
</Text>
|
|
152
|
+
<Text className="text-slate-600 mb-8">
|
|
153
|
+
Your app is ready!
|
|
154
|
+
</Text>
|
|
155
|
+
<Button onPress={() => alert('Hello Lunar Kit!')}>
|
|
156
|
+
Get Started
|
|
157
|
+
</Button>
|
|
158
|
+
<StatusBar style="auto" />
|
|
159
|
+
</View>
|
|
160
|
+
);
|
|
161
|
+
}`;
|
|
162
|
+
await fs.writeFile(path2.join(projectPath, "src", "Main.tsx"), mainContent);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function setupExpoRouterSrc(projectPath) {
|
|
166
|
+
const appDir = path2.join(projectPath, "app");
|
|
167
|
+
await fs.ensureDir(appDir);
|
|
168
|
+
const layoutContent = `import React from 'react';
|
|
169
|
+
import '../src/global.css';
|
|
170
|
+
import { Stack } from 'expo-router';
|
|
171
|
+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
172
|
+
import { ThemeProvider } from '@/providers/theme-provider';
|
|
173
|
+
import { useColorScheme } from 'nativewind';
|
|
174
|
+
import { useThemeColors } from '@/hooks/useThemeColors';
|
|
175
|
+
|
|
176
|
+
export default function RootLayout() {
|
|
177
|
+
const { colorScheme } = useColorScheme();
|
|
178
|
+
const { colors } = useThemeColors()
|
|
179
|
+
return (<GestureHandlerRootView style={{ flex: 1 }}>
|
|
180
|
+
<ThemeProvider>
|
|
181
|
+
<Stack screenOptions={{
|
|
182
|
+
headerShown: false,
|
|
183
|
+
contentStyle: {
|
|
184
|
+
backgroundColor: colors.background,
|
|
185
|
+
|
|
186
|
+
},
|
|
187
|
+
}} key={colorScheme} />
|
|
188
|
+
</ThemeProvider>
|
|
189
|
+
</GestureHandlerRootView>
|
|
190
|
+
);
|
|
191
|
+
}`;
|
|
192
|
+
await fs.writeFile(path2.join(appDir, "_layout.tsx"), layoutContent);
|
|
193
|
+
const indexContent = `import React from 'react';
|
|
194
|
+
import { View, Text } from 'react-native';
|
|
195
|
+
import { Button } from '@/components/ui/button';
|
|
196
|
+
|
|
197
|
+
export default function IndexScreen() {
|
|
198
|
+
return (
|
|
199
|
+
<View className="flex-1 items-center justify-center">
|
|
200
|
+
<Text className="text-3xl font-bold text-slate-900 mb-2">
|
|
201
|
+
\u{1F319} Lunar Kit
|
|
202
|
+
</Text>
|
|
203
|
+
<Text className="text-slate-600 mb-8">
|
|
204
|
+
Your app is ready!
|
|
205
|
+
</Text>
|
|
206
|
+
<Button onPress={() => alert('Hello Lunar Kit!')}>
|
|
207
|
+
Get Started
|
|
208
|
+
</Button>
|
|
209
|
+
</View>
|
|
210
|
+
);
|
|
211
|
+
}`;
|
|
212
|
+
await fs.writeFile(path2.join(appDir, "index.tsx"), indexContent);
|
|
213
|
+
}
|
|
214
|
+
async function setupReactNavigationSrc(projectPath) {
|
|
215
|
+
const navContent = `import { NavigationContainer } from '@react-navigation/native';
|
|
216
|
+
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|
217
|
+
import HomeScreen from './screens/HomeScreen';
|
|
218
|
+
|
|
219
|
+
const Stack = createNativeStackNavigator();
|
|
220
|
+
|
|
221
|
+
export default function Navigation() {
|
|
222
|
+
return (
|
|
223
|
+
<NavigationContainer>
|
|
224
|
+
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
|
225
|
+
<Stack.Screen name="Home" component={HomeScreen} />
|
|
226
|
+
</Stack.Navigator>
|
|
227
|
+
</NavigationContainer>
|
|
228
|
+
);
|
|
229
|
+
}`;
|
|
230
|
+
await fs.writeFile(path2.join(projectPath, "src", "Navigation.tsx"), navContent);
|
|
231
|
+
const homeContent = `import React from 'react';
|
|
232
|
+
import { View, Text } from 'react-native';
|
|
233
|
+
import { Button } from '../components/ui/button';
|
|
234
|
+
|
|
235
|
+
export default function HomeScreen() {
|
|
236
|
+
return (
|
|
237
|
+
<View className="flex-1 items-center justify-center">
|
|
238
|
+
<Text className="text-3xl font-bold text-slate-900 mb-2">
|
|
239
|
+
\u{1F319} Lunar Kit
|
|
240
|
+
</Text>
|
|
241
|
+
<Text className="text-slate-600 mb-8">
|
|
242
|
+
Your app is ready!
|
|
243
|
+
</Text>
|
|
244
|
+
<Button onPress={() => alert('Hello Lunar Kit!')}>
|
|
245
|
+
Get Started
|
|
246
|
+
</Button>
|
|
247
|
+
</View>
|
|
248
|
+
);
|
|
249
|
+
}`;
|
|
250
|
+
await fs.writeFile(path2.join(projectPath, "src", "screens", "HomeScreen.tsx"), homeContent);
|
|
251
|
+
const mainContent = `import React from 'react';
|
|
252
|
+
import './global.css';
|
|
253
|
+
import { StatusBar } from 'expo-status-bar';
|
|
254
|
+
import Navigation from './Navigation';
|
|
255
|
+
|
|
256
|
+
export default function Main() {
|
|
257
|
+
return (
|
|
258
|
+
<>
|
|
259
|
+
<Navigation />
|
|
260
|
+
<StatusBar style="auto" />
|
|
261
|
+
</>
|
|
262
|
+
);
|
|
263
|
+
}`;
|
|
264
|
+
await fs.writeFile(path2.join(projectPath, "src", "Main.tsx"), mainContent);
|
|
265
|
+
}
|
|
266
|
+
async function setupAuthSrc(projectPath, navigation) {
|
|
267
|
+
const authDir = navigation === "expo-router" ? path2.join(projectPath, "app", "(auth)") : path2.join(projectPath, "src", "screens", "auth");
|
|
268
|
+
await fs.ensureDir(authDir);
|
|
269
|
+
const loginScreen = `import { View, Text } from 'react-native';
|
|
270
|
+
import { Button } from '@/src/components/ui/button';
|
|
271
|
+
|
|
272
|
+
export default function LoginScreen() {
|
|
273
|
+
return (
|
|
274
|
+
<View className="flex-1 items-center justify-center p-4">
|
|
275
|
+
<Text className="text-2xl font-bold mb-4">Login</Text>
|
|
276
|
+
<Button className="w-full">Sign In</Button>
|
|
277
|
+
</View>
|
|
278
|
+
);
|
|
279
|
+
}`;
|
|
280
|
+
await fs.writeFile(path2.join(authDir, "login.tsx"), loginScreen);
|
|
281
|
+
}
|
|
282
|
+
async function setupDarkModeSrc(projectPath, navigation) {
|
|
283
|
+
const themeProvider = fs.readFileSync(
|
|
284
|
+
path2.join(SOURCE, "providers", "theme-provider.tsx")
|
|
285
|
+
);
|
|
286
|
+
const themeStore = fs.readFileSync(
|
|
287
|
+
path2.join(SOURCE, "stores", "theme.ts")
|
|
288
|
+
);
|
|
289
|
+
const storesIndexPath = path2.join(projectPath, "src", "stores", "index.ts");
|
|
290
|
+
let storesIndexContent = `// Auto-generated barrel export
|
|
291
|
+
// This file is managed by Lunar Kit CLI
|
|
292
|
+
`;
|
|
293
|
+
storesIndexContent += `export * from './theme';
|
|
294
|
+
`;
|
|
295
|
+
await fs.writeFile(storesIndexPath, storesIndexContent);
|
|
296
|
+
let toolbarContent;
|
|
297
|
+
if (navigation === "expo-router") {
|
|
298
|
+
toolbarContent = fs.readFileSync(
|
|
299
|
+
path2.join(SOURCE, "hooks", "useToolbar.tsx")
|
|
300
|
+
);
|
|
301
|
+
} else {
|
|
302
|
+
toolbarContent = fs.readFileSync(
|
|
303
|
+
path2.join(SOURCE, "hooks", "useToolbar.react-navigation.tsx")
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
const themeContent = fs.readFileSync(
|
|
307
|
+
path2.join(SOURCE, "hooks", "useTheme.ts")
|
|
308
|
+
);
|
|
309
|
+
const themeColor = fs.readFileSync(
|
|
310
|
+
path2.join(SOURCE, "hooks", "useThemeColors.ts")
|
|
311
|
+
);
|
|
312
|
+
const libTheme = fs.readFileSync(
|
|
313
|
+
path2.join(SOURCE, "lib", "theme.ts")
|
|
314
|
+
);
|
|
315
|
+
await fs.ensureDir(path2.join(projectPath, "src", "providers"));
|
|
316
|
+
await fs.writeFile(path2.join(projectPath, "src", "providers", "theme-provider.tsx"), themeProvider);
|
|
317
|
+
await fs.ensureDir(path2.join(projectPath, "src", "stores"));
|
|
318
|
+
await fs.writeFile(path2.join(projectPath, "src", "stores", "theme.ts"), themeStore);
|
|
319
|
+
await fs.ensureDir(path2.join(projectPath, "src", "hooks"));
|
|
320
|
+
await fs.writeFile(path2.join(projectPath, "src", "hooks", "useToolbar.tsx"), toolbarContent);
|
|
321
|
+
await fs.writeFile(path2.join(projectPath, "src", "hooks", "useTheme.ts"), themeContent);
|
|
322
|
+
await fs.writeFile(path2.join(projectPath, "src", "hooks", "useThemeColors.ts"), themeColor);
|
|
323
|
+
await fs.ensureDir(path2.join(projectPath, "src", "lib"));
|
|
324
|
+
await fs.writeFile(path2.join(projectPath, "src", "lib", "theme.ts"), libTheme);
|
|
325
|
+
}
|
|
326
|
+
async function setupFormsSrc(projectPath) {
|
|
327
|
+
const hookContent = `import { useState } from 'react';
|
|
328
|
+
|
|
329
|
+
export function useForm<T>(initialValues: T) {
|
|
330
|
+
const [values, setValues] = useState<T>(initialValues);
|
|
331
|
+
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
|
|
332
|
+
|
|
333
|
+
const handleChange = (name: keyof T, value: any) => {
|
|
334
|
+
setValues(prev => ({ ...prev, [name]: value }));
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const validate = (validationRules: Partial<Record<keyof T, (value: any) => string | undefined>>) => {
|
|
338
|
+
const newErrors: Partial<Record<keyof T, string>> = {};
|
|
339
|
+
|
|
340
|
+
Object.keys(validationRules).forEach((key) => {
|
|
341
|
+
const error = validationRules[key as keyof T]?.(values[key as keyof T]);
|
|
342
|
+
if (error) {
|
|
343
|
+
newErrors[key as keyof T] = error;
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
setErrors(newErrors);
|
|
348
|
+
return Object.keys(newErrors).length === 0;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
return { values, errors, handleChange, validate };
|
|
352
|
+
}`;
|
|
353
|
+
await fs.writeFile(path2.join(projectPath, "src", "hooks", "useForm.ts"), hookContent);
|
|
354
|
+
}
|
|
355
|
+
async function setupNativeWind(projectPath) {
|
|
356
|
+
const globalCss = `@tailwind base;
|
|
357
|
+
@tailwind components;
|
|
358
|
+
@tailwind utilities;`;
|
|
359
|
+
await fs.writeFile(path2.join(projectPath, "src", "global.css"), globalCss);
|
|
360
|
+
const tailwindConfig = fs.readFileSync(
|
|
361
|
+
path2.join(SOURCE, "tailwind.config.js")
|
|
362
|
+
);
|
|
363
|
+
await fs.writeFile(path2.join(projectPath, "tailwind.config.js"), tailwindConfig);
|
|
364
|
+
const metroConfig = `const { getDefaultConfig } = require('expo/metro-config');
|
|
365
|
+
const { withNativeWind } = require('nativewind/metro');
|
|
366
|
+
|
|
367
|
+
const config = getDefaultConfig(__dirname);
|
|
368
|
+
|
|
369
|
+
module.exports = withNativeWind(config, {
|
|
370
|
+
input: './src/global.css',
|
|
371
|
+
});`;
|
|
372
|
+
await fs.writeFile(path2.join(projectPath, "metro.config.js"), metroConfig);
|
|
373
|
+
const babelConfig = `module.exports = function (api) {
|
|
374
|
+
api.cache(true);
|
|
375
|
+
return {
|
|
376
|
+
presets: [
|
|
377
|
+
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
|
|
378
|
+
'nativewind/babel'
|
|
379
|
+
],
|
|
380
|
+
};
|
|
381
|
+
};`;
|
|
382
|
+
await fs.writeFile(path2.join(projectPath, "babel.config.js"), babelConfig);
|
|
383
|
+
const nativewindEnv = `/// <reference types="nativewind/types" />`;
|
|
384
|
+
await fs.writeFile(path2.join(projectPath, "nativewind-env.d.ts"), nativewindEnv);
|
|
385
|
+
const utilsContent = fs.readFileSync(
|
|
386
|
+
path2.join(SOURCE, "lib", "utils.ts")
|
|
387
|
+
);
|
|
388
|
+
const colorsContent = fs.readFileSync(
|
|
389
|
+
path2.join(SOURCE, "lib", "theme.ts")
|
|
390
|
+
);
|
|
391
|
+
await fs.writeFile(path2.join(projectPath, "src", "lib", "utils.ts"), utilsContent);
|
|
392
|
+
await fs.writeFile(path2.join(projectPath, "src", "lib", "theme.ts"), colorsContent);
|
|
393
|
+
const buttonContent = fs.readFileSync(
|
|
394
|
+
path2.join(COMPONENTS_SOURCE, "ui", "button.tsx")
|
|
395
|
+
);
|
|
396
|
+
const textContent = fs.readFileSync(
|
|
397
|
+
path2.join(COMPONENTS_SOURCE, "ui", "text.tsx")
|
|
398
|
+
);
|
|
399
|
+
await fs.writeFile(path2.join(projectPath, "src", "components", "ui", "button.tsx"), buttonContent);
|
|
400
|
+
await fs.writeFile(path2.join(projectPath, "src", "components", "ui", "text.tsx"), textContent);
|
|
401
|
+
const tsconfigContent = `{
|
|
402
|
+
"extends": "expo/tsconfig.base",
|
|
403
|
+
"compilerOptions": {
|
|
404
|
+
"strict": true,
|
|
405
|
+
"baseUrl": ".",
|
|
406
|
+
"paths": {
|
|
407
|
+
"@/*": ["./src/*"],
|
|
408
|
+
"@modules/*": ["./src/modules/*"],
|
|
409
|
+
"@components/*": ["./src/components/*"],
|
|
410
|
+
"@hooks/*": ["./src/hooks/*"],
|
|
411
|
+
"@stores/*": ["./src/stores/*"],
|
|
412
|
+
"@lib/*": ["./src/lib/*"]
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}`;
|
|
416
|
+
await fs.writeFile(path2.join(projectPath, "tsconfig.json"), tsconfigContent);
|
|
417
|
+
}
|
|
418
|
+
async function updatePackageJson(projectPath, navigation, features) {
|
|
419
|
+
const pkgPath = path2.join(projectPath, "package.json");
|
|
420
|
+
const pkg = await fs.readJson(pkgPath);
|
|
421
|
+
pkg.dependencies = {
|
|
422
|
+
...pkg.dependencies,
|
|
423
|
+
"nativewind": "^4.2.1",
|
|
424
|
+
"clsx": "^2.1.1",
|
|
425
|
+
"tailwind-merge": "^3.4.0",
|
|
426
|
+
"class-variance-authority": "^0.7.1",
|
|
427
|
+
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
428
|
+
"lucide-react-native": "^0.562.0",
|
|
429
|
+
"react-native-gesture-handler": "^2.28.0",
|
|
430
|
+
"react-native-reanimated": "^4.1.6",
|
|
431
|
+
"react-native-safe-area-context": "^5.6.2",
|
|
432
|
+
"react-native-worklets": "^0.5.1",
|
|
433
|
+
"zustand": "^5.0.3"
|
|
434
|
+
};
|
|
435
|
+
if (navigation === "expo-router") {
|
|
436
|
+
pkg.dependencies["expo-router"] = "^6.0.22";
|
|
437
|
+
pkg.dependencies["react-native-screens"] = "~4.16.0";
|
|
438
|
+
pkg.dependencies["react-native-safe-area-context"] = "^5.6.2";
|
|
439
|
+
}
|
|
440
|
+
if (navigation === "react-navigation") {
|
|
441
|
+
pkg.dependencies["@react-navigation/native"] = "^7.0.14";
|
|
442
|
+
pkg.dependencies["@react-navigation/native-stack"] = "^7.1.10";
|
|
443
|
+
pkg.dependencies["react-native-screens"] = "~4.4.0";
|
|
444
|
+
pkg.dependencies["react-native-safe-area-context"] = "4.16.0";
|
|
445
|
+
}
|
|
446
|
+
if (features.includes("forms")) {
|
|
447
|
+
pkg.dependencies["react-hook-form"] = "^7.54.2";
|
|
448
|
+
pkg.dependencies["@hookform/resolvers"] = "^5.2.2";
|
|
449
|
+
pkg.dependencies["zod"] = "^4.3.6";
|
|
450
|
+
}
|
|
451
|
+
pkg.devDependencies = {
|
|
452
|
+
...pkg.devDependencies,
|
|
453
|
+
"tailwindcss": "3.4.17",
|
|
454
|
+
"@expo/metro-config": "^0.20.3",
|
|
455
|
+
"react-native-css-interop": "^0.2.1"
|
|
456
|
+
};
|
|
457
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
458
|
+
}
|
|
459
|
+
async function createConfig(projectPath, navigation, features, packageManager) {
|
|
460
|
+
const config = {
|
|
461
|
+
navigation,
|
|
462
|
+
features,
|
|
463
|
+
architecture: "modular",
|
|
464
|
+
srcDir: "src",
|
|
465
|
+
modulesDir: "src/modules",
|
|
466
|
+
componentsDir: "src/components",
|
|
467
|
+
hooksDir: "src/hooks",
|
|
468
|
+
storesDir: "src/stores",
|
|
469
|
+
uiComponentsDir: "src/components/ui",
|
|
470
|
+
packageManager,
|
|
471
|
+
// DONE: Add this
|
|
472
|
+
namingConvention: {
|
|
473
|
+
files: "snake_case",
|
|
474
|
+
exports: "PascalCase",
|
|
475
|
+
hooks: "camelCase"
|
|
476
|
+
},
|
|
477
|
+
autoRouting: true,
|
|
478
|
+
autoImport: true,
|
|
479
|
+
autoBarrelExport: true
|
|
480
|
+
};
|
|
481
|
+
await fs.writeJson(path2.join(projectPath, "kit.config.json"), config, { spaces: 2 });
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// src/res/close.ts
|
|
485
|
+
import chalk2 from "chalk";
|
|
486
|
+
function closeInitProject(packageManager, name) {
|
|
487
|
+
console.log("\n" + chalk2.bold("Project structure:\n"));
|
|
488
|
+
console.log(chalk2.dim(`${name}/`));
|
|
489
|
+
console.log(chalk2.cyan(" src/"));
|
|
490
|
+
console.log(chalk2.cyan(" components/ui/ # UI components"));
|
|
491
|
+
console.log(chalk2.cyan(" screens/ # App screens"));
|
|
492
|
+
console.log(chalk2.cyan(" lib/ # Utilities"));
|
|
493
|
+
console.log(chalk2.cyan(" stores/ # State management"));
|
|
494
|
+
console.log(chalk2.cyan(" hooks/ # Custom hooks"));
|
|
495
|
+
console.log("\n" + chalk2.bold("Next steps:\n"));
|
|
496
|
+
console.log(chalk2.cyan(` cd ${name}`));
|
|
497
|
+
console.log(chalk2.cyan(` ${packageManager === "npm" ? "npm start" : `${packageManager} start`}`));
|
|
498
|
+
console.log("\n" + chalk2.bold("Generate code:\n"));
|
|
499
|
+
console.log(chalk2.cyan(" pnpm dlx lk g screen Profile"));
|
|
500
|
+
console.log(chalk2.cyan(" pnpm dlx lk g component UserCard"));
|
|
501
|
+
console.log("\n" + chalk2.bold("Add UI components:\n"));
|
|
502
|
+
console.log(chalk2.cyan(" pnpm dlx lk add card"));
|
|
503
|
+
console.log(chalk2.cyan(" pnpm dlx lk add input"));
|
|
504
|
+
console.log(chalk2.dim("\nHappy coding! \u{1F319}\n"));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/index.ts
|
|
508
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
509
|
+
var program = new Command();
|
|
510
|
+
program.name("create-lunar-kit").description("Create a new React Native app with Lunar Kit").argument("[project-name]", "Name of your project").action(async (projectName) => {
|
|
511
|
+
renderLogo();
|
|
512
|
+
console.log(chalk3.bold.cyan("\n\u{1F319} Create Lunar Kit App\n"));
|
|
513
|
+
const response = await prompts([
|
|
514
|
+
{
|
|
515
|
+
type: projectName ? null : "text",
|
|
516
|
+
name: "projectName",
|
|
517
|
+
message: "What is your project named?",
|
|
518
|
+
initial: "my-lunar-app",
|
|
519
|
+
validate: (value) => /^[a-z0-9-]+$/.test(value) || "Project name must be lowercase and use hyphens"
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
type: "select",
|
|
523
|
+
name: "navigation",
|
|
524
|
+
message: "Which navigation library?",
|
|
525
|
+
choices: [
|
|
526
|
+
{ title: "Expo Router (File-based routing)", value: "expo-router" },
|
|
527
|
+
{ title: "React Navigation (Stack-based)", value: "react-navigation" },
|
|
528
|
+
{ title: "None (I'll add it later)", value: "none" }
|
|
529
|
+
],
|
|
530
|
+
initial: 0
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
type: "multiselect",
|
|
534
|
+
name: "features",
|
|
535
|
+
message: "Select features to include:",
|
|
536
|
+
choices: [
|
|
537
|
+
// TODO: need to add these features
|
|
538
|
+
{ title: "Authentication screens", value: "auth", selected: false },
|
|
539
|
+
// { title: 'Dark mode support', value: 'dark-mode', selected: true },
|
|
540
|
+
{ title: "Form validation (react-hook-form)", value: "forms", selected: false }
|
|
541
|
+
// { title: 'State management (Zustand)', value: 'state', selected: false },
|
|
542
|
+
]
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
type: "select",
|
|
546
|
+
name: "packageManager",
|
|
547
|
+
message: "Which package manager?",
|
|
548
|
+
choices: [
|
|
549
|
+
{ title: "pnpm", value: "pnpm" },
|
|
550
|
+
{ title: "npm", value: "npm" },
|
|
551
|
+
{ title: "yarn", value: "yarn" }
|
|
552
|
+
],
|
|
553
|
+
initial: 0
|
|
554
|
+
}
|
|
555
|
+
]);
|
|
556
|
+
const name = projectName || response.projectName;
|
|
557
|
+
const { navigation, features, packageManager } = response;
|
|
558
|
+
if (!name) {
|
|
559
|
+
console.log(chalk3.red("Project name is required"));
|
|
560
|
+
process.exit(1);
|
|
561
|
+
}
|
|
562
|
+
const projectPath = path3.join(process.cwd(), name);
|
|
563
|
+
if (fs2.existsSync(projectPath)) {
|
|
564
|
+
console.log(chalk3.red(`Directory ${name} already exists`));
|
|
565
|
+
process.exit(1);
|
|
566
|
+
}
|
|
567
|
+
const spinner = ora("Creating project...").start();
|
|
568
|
+
try {
|
|
569
|
+
spinner.text = "Creating Expo app...";
|
|
570
|
+
const template = navigation === "expo-router" ? "blank-typescript" : "blank-typescript";
|
|
571
|
+
await execa("npx", [
|
|
572
|
+
"create-expo-app@latest",
|
|
573
|
+
name,
|
|
574
|
+
"--template",
|
|
575
|
+
template,
|
|
576
|
+
"--no-install"
|
|
577
|
+
]);
|
|
578
|
+
spinner.text = "Setting up project structure...";
|
|
579
|
+
await createSrcStructure(projectPath, navigation);
|
|
580
|
+
await setupAppEntry(projectPath, navigation);
|
|
581
|
+
if (navigation === "expo-router") {
|
|
582
|
+
spinner.text = "Setting up Expo Router...";
|
|
583
|
+
await setupExpoRouterSrc(projectPath);
|
|
584
|
+
} else if (navigation === "react-navigation") {
|
|
585
|
+
spinner.text = "Setting up React Navigation...";
|
|
586
|
+
await setupReactNavigationSrc(projectPath);
|
|
587
|
+
}
|
|
588
|
+
if (features.includes("auth")) {
|
|
589
|
+
spinner.text = "Setting up authentication...";
|
|
590
|
+
await setupAuthSrc(projectPath, navigation);
|
|
591
|
+
}
|
|
592
|
+
await setupDarkModeSrc(projectPath, navigation);
|
|
593
|
+
if (features.includes("forms")) {
|
|
594
|
+
spinner.text = "Setting up forms...";
|
|
595
|
+
await setupFormsSrc(projectPath);
|
|
596
|
+
}
|
|
597
|
+
await setupNativeWind(projectPath);
|
|
598
|
+
spinner.text = "Updating dependencies...";
|
|
599
|
+
await updatePackageJson(projectPath, navigation, features);
|
|
600
|
+
await createConfig(projectPath, navigation, features, packageManager);
|
|
601
|
+
spinner.text = `Installing dependencies with ${packageManager}...`;
|
|
602
|
+
const installCmd = packageManager === "yarn" ? "yarn" : packageManager;
|
|
603
|
+
const installArgs = packageManager === "npm" ? ["install"] : packageManager === "yarn" ? [] : ["install"];
|
|
604
|
+
await execa(installCmd, installArgs, { cwd: projectPath });
|
|
605
|
+
spinner.succeed(chalk3.green("Project created successfully! \u{1F389}"));
|
|
606
|
+
closeInitProject(packageManager, name);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
spinner.fail("Failed to create project");
|
|
609
|
+
console.error(error);
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-lunar-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create a new React Native app with Lunar Kit and NativeWind pre-configured",
|
|
5
|
+
"author": "Your Name",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-lunar-kit": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"templates"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"dev": "tsup --watch"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"react-native",
|
|
21
|
+
"expo",
|
|
22
|
+
"nativewind",
|
|
23
|
+
"create-app",
|
|
24
|
+
"starter"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@lunar-kit/core": "workspace:*",
|
|
28
|
+
"chalk": "^5.4.1",
|
|
29
|
+
"commander": "^12.1.0",
|
|
30
|
+
"execa": "^9.6.1",
|
|
31
|
+
"fs-extra": "^11.2.0",
|
|
32
|
+
"ora": "^8.1.1",
|
|
33
|
+
"prompts": "^2.4.2"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/fs-extra": "^11.0.4",
|
|
37
|
+
"@types/prompts": "^2.4.9",
|
|
38
|
+
"tsup": "^8.3.5",
|
|
39
|
+
"typescript": "^5.7.3"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="nativewind/types" />
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
|