@lunar-kit/cli 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 +1037 -0
- package/package.json +56 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,1037 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk7 from "chalk";
|
|
6
|
+
|
|
7
|
+
// src/commands/init.ts
|
|
8
|
+
import fs from "fs-extra";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import chalk2 from "chalk";
|
|
11
|
+
import ora from "ora";
|
|
12
|
+
import prompts from "prompts";
|
|
13
|
+
|
|
14
|
+
// src/assets/logo.ts
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
var renderLogo = () => {
|
|
17
|
+
console.log(
|
|
18
|
+
chalk.cyan(` ............. ............
|
|
19
|
+
................. .................
|
|
20
|
+
...... .......... .......... ......
|
|
21
|
+
...... ......... ........ .....
|
|
22
|
+
..... ............. .....
|
|
23
|
+
..... ........ .....
|
|
24
|
+
..... ....... .....
|
|
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
|
+
// src/commands/init.ts
|
|
60
|
+
async function initProject() {
|
|
61
|
+
renderLogo();
|
|
62
|
+
console.log(chalk2.bold("\u{1F319} Initializing Lunar Kit...\n"));
|
|
63
|
+
const response = await prompts([
|
|
64
|
+
{
|
|
65
|
+
type: "confirm",
|
|
66
|
+
name: "typescript",
|
|
67
|
+
message: "Would you like to use TypeScript?",
|
|
68
|
+
initial: true
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: "select",
|
|
72
|
+
name: "packageManager",
|
|
73
|
+
message: "Which package manager do you use?",
|
|
74
|
+
choices: [
|
|
75
|
+
{ title: "pnpm", value: "pnpm" },
|
|
76
|
+
{ title: "npm", value: "npm" },
|
|
77
|
+
{ title: "yarn", value: "yarn" }
|
|
78
|
+
],
|
|
79
|
+
initial: 0
|
|
80
|
+
}
|
|
81
|
+
]);
|
|
82
|
+
const spinner = ora("Setting up project structure...").start();
|
|
83
|
+
try {
|
|
84
|
+
const componentsDir = path.join(process.cwd(), "src", "components", "ui");
|
|
85
|
+
await fs.ensureDir(componentsDir);
|
|
86
|
+
spinner.text = "Created components/ui directory";
|
|
87
|
+
const libDir = path.join(process.cwd(), "lib");
|
|
88
|
+
await fs.ensureDir(libDir);
|
|
89
|
+
const utilsExt = response.typescript ? "ts" : "js";
|
|
90
|
+
const utilsPath = path.join(libDir, `utils.${utilsExt}`);
|
|
91
|
+
if (!fs.existsSync(utilsPath)) {
|
|
92
|
+
const utilsContent = `import { clsx, type ClassValue } from 'clsx';
|
|
93
|
+
import { twMerge } from 'tailwind-merge';
|
|
94
|
+
|
|
95
|
+
export function cn(...inputs: ClassValue[]) {
|
|
96
|
+
return twMerge(clsx(inputs));
|
|
97
|
+
}`;
|
|
98
|
+
await fs.writeFile(utilsPath, utilsContent);
|
|
99
|
+
spinner.text = "Created lib/utils";
|
|
100
|
+
}
|
|
101
|
+
const config = {
|
|
102
|
+
$schema: "https://lunar-kit.dev/schema.json",
|
|
103
|
+
style: "default",
|
|
104
|
+
rsc: false,
|
|
105
|
+
tsx: response.typescript,
|
|
106
|
+
packageManager: response.packageManager,
|
|
107
|
+
// DONE: Save package manager
|
|
108
|
+
tailwind: {
|
|
109
|
+
config: "tailwind.config.js",
|
|
110
|
+
css: "global.css",
|
|
111
|
+
baseColor: "slate",
|
|
112
|
+
cssVariables: true
|
|
113
|
+
},
|
|
114
|
+
aliases: {
|
|
115
|
+
components: "@/src/components",
|
|
116
|
+
utils: "@/src/lib/utils"
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
await fs.writeJson(path.join(process.cwd(), "kit.config.json"), config, {
|
|
120
|
+
spaces: 2
|
|
121
|
+
});
|
|
122
|
+
spinner.succeed(chalk2.green("Project initialized successfully!"));
|
|
123
|
+
console.log("\n" + chalk2.bold("Next steps:"));
|
|
124
|
+
console.log(chalk2.cyan("1. Make sure NativeWind is installed and configured"));
|
|
125
|
+
console.log(chalk2.cyan("2. Install required dependencies:"));
|
|
126
|
+
console.log(chalk2.white(` ${response.packageManager} add clsx tailwind-merge`));
|
|
127
|
+
console.log(chalk2.cyan("3. Add components:"));
|
|
128
|
+
console.log(chalk2.white(" lk add button"));
|
|
129
|
+
} catch (error) {
|
|
130
|
+
spinner.fail("Failed to initialize project");
|
|
131
|
+
console.error(error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/commands/add.ts
|
|
136
|
+
import fs3 from "fs-extra";
|
|
137
|
+
import path3 from "path";
|
|
138
|
+
import chalk3 from "chalk";
|
|
139
|
+
import ora2 from "ora";
|
|
140
|
+
import { execSync } from "child_process";
|
|
141
|
+
|
|
142
|
+
// src/utils/helpers.ts
|
|
143
|
+
import fs2 from "fs-extra";
|
|
144
|
+
import path2 from "path";
|
|
145
|
+
async function loadConfig() {
|
|
146
|
+
const configPath = path2.join(process.cwd(), "kit.config.json");
|
|
147
|
+
if (!fs2.existsSync(configPath)) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return await fs2.readJson(configPath);
|
|
151
|
+
}
|
|
152
|
+
function toSnakeCase(str) {
|
|
153
|
+
return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
154
|
+
}
|
|
155
|
+
function toPascalCase(str) {
|
|
156
|
+
return str.replace(/([-_]\w)/g, (g) => g[1].toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
|
|
157
|
+
}
|
|
158
|
+
function toCamelCase(str) {
|
|
159
|
+
const pascal = toPascalCase(str);
|
|
160
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/commands/add.ts
|
|
164
|
+
import {
|
|
165
|
+
LOCAL_REGISTRY_PATH,
|
|
166
|
+
LOCAL_COMPONENTS_PATH
|
|
167
|
+
} from "@lunar-kit/core";
|
|
168
|
+
async function getRegistryPath() {
|
|
169
|
+
if (await fs3.pathExists(LOCAL_REGISTRY_PATH)) {
|
|
170
|
+
return LOCAL_REGISTRY_PATH;
|
|
171
|
+
}
|
|
172
|
+
throw new Error("Local registry not found. Remote registry not yet implemented.");
|
|
173
|
+
}
|
|
174
|
+
async function getComponentsPath() {
|
|
175
|
+
if (await fs3.pathExists(LOCAL_COMPONENTS_PATH)) {
|
|
176
|
+
return LOCAL_COMPONENTS_PATH;
|
|
177
|
+
}
|
|
178
|
+
throw new Error("Local components not found.");
|
|
179
|
+
}
|
|
180
|
+
var installedComponents = /* @__PURE__ */ new Set();
|
|
181
|
+
async function installRegistryDependencies(dependencies, componentsDir, spinner, registryPath, componentsPath) {
|
|
182
|
+
for (const dep of dependencies) {
|
|
183
|
+
if (installedComponents.has(dep)) {
|
|
184
|
+
spinner.text = chalk3.dim(`Skipping ${dep} (already installed)`);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
spinner.text = `Installing registry dependency: ${chalk3.cyan(dep)}`;
|
|
188
|
+
const depRegistryPath = path3.join(registryPath, "ui", `${dep}.json`);
|
|
189
|
+
if (!await fs3.pathExists(depRegistryPath)) {
|
|
190
|
+
spinner.warn(`Registry dependency "${dep}" not found. Skipping...`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const depRegistry = await fs3.readJson(depRegistryPath);
|
|
194
|
+
if (depRegistry.registryDependencies?.length > 0) {
|
|
195
|
+
await installRegistryDependencies(
|
|
196
|
+
depRegistry.registryDependencies,
|
|
197
|
+
componentsDir,
|
|
198
|
+
spinner,
|
|
199
|
+
registryPath,
|
|
200
|
+
componentsPath
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
for (const file of depRegistry.files) {
|
|
204
|
+
const relativePath = file.path.replace("/src/components/", "");
|
|
205
|
+
const sourcePath = path3.join(componentsPath, relativePath);
|
|
206
|
+
const fileName = path3.basename(file.path);
|
|
207
|
+
const targetPath = path3.join(componentsDir, fileName);
|
|
208
|
+
if (await fs3.pathExists(targetPath)) {
|
|
209
|
+
spinner.text = chalk3.dim(`Skipping ${fileName} (already exists)`);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (!await fs3.pathExists(sourcePath)) {
|
|
213
|
+
spinner.warn(`Source file not found: ${sourcePath}`);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
await fs3.ensureDir(path3.dirname(targetPath));
|
|
217
|
+
await fs3.copy(sourcePath, targetPath);
|
|
218
|
+
spinner.text = `Copied ${chalk3.green(fileName)} (from ${dep})`;
|
|
219
|
+
}
|
|
220
|
+
installedComponents.add(dep);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function getPackageManager(config) {
|
|
224
|
+
if (config.packageManager) {
|
|
225
|
+
return config.packageManager;
|
|
226
|
+
}
|
|
227
|
+
if (fs3.existsSync(path3.join(process.cwd(), "pnpm-lock.yaml"))) {
|
|
228
|
+
return "pnpm";
|
|
229
|
+
}
|
|
230
|
+
if (fs3.existsSync(path3.join(process.cwd(), "yarn.lock"))) {
|
|
231
|
+
return "yarn";
|
|
232
|
+
}
|
|
233
|
+
if (fs3.existsSync(path3.join(process.cwd(), "package-lock.json"))) {
|
|
234
|
+
return "npm";
|
|
235
|
+
}
|
|
236
|
+
return "npm";
|
|
237
|
+
}
|
|
238
|
+
function getInstalledDependencies() {
|
|
239
|
+
const pkgPath = path3.join(process.cwd(), "package.json");
|
|
240
|
+
if (!fs3.existsSync(pkgPath)) {
|
|
241
|
+
return { dependencies: /* @__PURE__ */ new Set(), devDependencies: /* @__PURE__ */ new Set() };
|
|
242
|
+
}
|
|
243
|
+
const pkg = fs3.readJsonSync(pkgPath);
|
|
244
|
+
return {
|
|
245
|
+
dependencies: new Set(Object.keys(pkg.dependencies || {})),
|
|
246
|
+
devDependencies: new Set(Object.keys(pkg.devDependencies || {}))
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function filterUninstalledDeps(deps, installed) {
|
|
250
|
+
return deps.filter((dep) => {
|
|
251
|
+
const pkgName = dep.split("@")[0];
|
|
252
|
+
return !installed.has(pkgName);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
function installDependencies(deps, config, isDev = false) {
|
|
256
|
+
if (deps.length === 0) return;
|
|
257
|
+
const packageManager = getPackageManager(config);
|
|
258
|
+
const { dependencies, devDependencies } = getInstalledDependencies();
|
|
259
|
+
const installed = isDev ? devDependencies : dependencies;
|
|
260
|
+
const toInstall = filterUninstalledDeps(deps, installed);
|
|
261
|
+
if (toInstall.length === 0) {
|
|
262
|
+
console.log(chalk3.dim(`
|
|
263
|
+
\u2713 All ${isDev ? "dev " : ""}dependencies already installed`));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const alreadyInstalled = deps.filter((d) => !toInstall.includes(d));
|
|
267
|
+
if (alreadyInstalled.length > 0) {
|
|
268
|
+
console.log(chalk3.dim(`
|
|
269
|
+
\u2713 Already installed: ${alreadyInstalled.join(", ")}`));
|
|
270
|
+
}
|
|
271
|
+
const devFlag = isDev ? packageManager === "npm" ? "--save-dev" : "-D" : "";
|
|
272
|
+
const command = `${packageManager} add ${devFlag} ${toInstall.join(" ")}`;
|
|
273
|
+
console.log(chalk3.cyan(`
|
|
274
|
+
Installing ${isDev ? "dev " : ""}dependencies with ${packageManager}...`));
|
|
275
|
+
console.log(chalk3.white(`Installing: ${chalk3.green(toInstall.join(", "))}`));
|
|
276
|
+
console.log(chalk3.dim(command));
|
|
277
|
+
try {
|
|
278
|
+
execSync(command, {
|
|
279
|
+
stdio: "inherit",
|
|
280
|
+
cwd: process.cwd()
|
|
281
|
+
});
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.error(chalk3.red(`Failed to install dependencies`));
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function addComponent(componentName) {
|
|
288
|
+
const spinner = ora2(`Adding ${componentName}...`).start();
|
|
289
|
+
try {
|
|
290
|
+
const config = await loadConfig();
|
|
291
|
+
if (!config) {
|
|
292
|
+
spinner.fail("kit.config.json not found. Run `lunar init` first.");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const registryPath = await getRegistryPath();
|
|
296
|
+
const componentsPath = await getComponentsPath();
|
|
297
|
+
const componentsDir = path3.join(process.cwd(), config.uiComponentsDir || "src/components/ui");
|
|
298
|
+
if (!await fs3.pathExists(componentsDir)) {
|
|
299
|
+
spinner.text = `Creating directory: ${config.uiComponentsDir}`;
|
|
300
|
+
await fs3.ensureDir(componentsDir);
|
|
301
|
+
spinner.succeed(`Created ${chalk3.green(config.uiComponentsDir)}`);
|
|
302
|
+
spinner.start(`Adding ${componentName}...`);
|
|
303
|
+
}
|
|
304
|
+
const componentRegistryPath = path3.join(registryPath, "ui", `${componentName}.json`);
|
|
305
|
+
if (!await fs3.pathExists(componentRegistryPath)) {
|
|
306
|
+
spinner.fail(`Component "${componentName}" not found in registry.`);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const registry = await fs3.readJson(componentRegistryPath);
|
|
310
|
+
installedComponents.clear();
|
|
311
|
+
if (registry.registryDependencies?.length > 0) {
|
|
312
|
+
spinner.text = "Installing registry dependencies...";
|
|
313
|
+
await installRegistryDependencies(
|
|
314
|
+
registry.registryDependencies,
|
|
315
|
+
componentsDir,
|
|
316
|
+
spinner,
|
|
317
|
+
registryPath,
|
|
318
|
+
componentsPath
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
for (const file of registry.files) {
|
|
322
|
+
const relativePath = file.path.replace("/src/components/", "");
|
|
323
|
+
const sourcePath = path3.join(componentsPath, relativePath);
|
|
324
|
+
const fileName = path3.basename(file.path);
|
|
325
|
+
const targetPath = path3.join(componentsDir, fileName);
|
|
326
|
+
if (!await fs3.pathExists(sourcePath)) {
|
|
327
|
+
spinner.warn(`Source file not found: ${sourcePath}`);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
await fs3.ensureDir(path3.dirname(targetPath));
|
|
331
|
+
await fs3.copy(sourcePath, targetPath);
|
|
332
|
+
spinner.text = `Copied ${chalk3.green(fileName)}`;
|
|
333
|
+
}
|
|
334
|
+
spinner.succeed(`Successfully added ${chalk3.green(componentName)}!`);
|
|
335
|
+
console.log(chalk3.dim(`
|
|
336
|
+
Location: ${config.uiComponentsDir}/${componentName}.tsx`));
|
|
337
|
+
if (registry.dependencies?.length > 0) {
|
|
338
|
+
spinner.stop();
|
|
339
|
+
installDependencies(registry.dependencies, config, false);
|
|
340
|
+
}
|
|
341
|
+
if (registry.devDependencies && registry.devDependencies.length > 0) {
|
|
342
|
+
installDependencies(registry.devDependencies, config, true);
|
|
343
|
+
}
|
|
344
|
+
console.log(chalk3.green("\n\u2713 All done!"));
|
|
345
|
+
} catch (error) {
|
|
346
|
+
spinner.fail(`Failed to add ${componentName}`);
|
|
347
|
+
console.error(error);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/commands/module.ts
|
|
352
|
+
import fs5 from "fs-extra";
|
|
353
|
+
import path5 from "path";
|
|
354
|
+
import chalk4 from "chalk";
|
|
355
|
+
import ora3 from "ora";
|
|
356
|
+
import prompts2 from "prompts";
|
|
357
|
+
|
|
358
|
+
// src/utils/routing.ts
|
|
359
|
+
import fs4 from "fs-extra";
|
|
360
|
+
import path4 from "path";
|
|
361
|
+
async function addToRouting(config, modulePath, viewName, type = "module") {
|
|
362
|
+
if (config.navigation === "expo-router") {
|
|
363
|
+
await addToExpoRouter(config, modulePath, viewName, type);
|
|
364
|
+
} else if (config.navigation === "react-navigation") {
|
|
365
|
+
await addToReactNavigation(config, modulePath, viewName);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async function addToExpoRouter(config, modulePath, viewName, type = "module") {
|
|
369
|
+
const appDir = path4.join(process.cwd(), "app");
|
|
370
|
+
const pathParts = modulePath.split("/");
|
|
371
|
+
const routePath = modulePath.replace(/\\/g, "/");
|
|
372
|
+
const routeDir = path4.join(appDir, ...routePath.split("/").slice(0, -1));
|
|
373
|
+
const fileName = pathParts[pathParts.length - 1];
|
|
374
|
+
const importPath = type === "view" ? `@modules/${pathParts.slice(0, -1).join("/")}/view/${viewName.replace(".tsx", "")}` : `@modules/${modulePath}/view/${viewName.replace(".tsx", "")}`;
|
|
375
|
+
const routeContent = `export { default } from '${importPath}';`;
|
|
376
|
+
const routeFilePath = routePath.includes("/") ? path4.join(routeDir, `${fileName}.tsx`) : path4.join(appDir, `${fileName}.tsx`);
|
|
377
|
+
await fs4.ensureDir(path4.dirname(routeFilePath));
|
|
378
|
+
await fs4.writeFile(routeFilePath, routeContent);
|
|
379
|
+
}
|
|
380
|
+
async function addToReactNavigation(config, modulePath, viewName) {
|
|
381
|
+
const navDir = path4.join(process.cwd(), "src", "navigation");
|
|
382
|
+
await fs4.ensureDir(navDir);
|
|
383
|
+
const routesFile = path4.join(navDir, "routes.config.ts");
|
|
384
|
+
const componentName = toPascalCase(path4.basename(modulePath));
|
|
385
|
+
const importStatement = `import ${componentName}View from '@modules/${modulePath}/view/${viewName.replace(".tsx", "")}';`;
|
|
386
|
+
const routeEntry = ` { name: '${componentName}', component: ${componentName}View },`;
|
|
387
|
+
let content = "";
|
|
388
|
+
if (fs4.existsSync(routesFile)) {
|
|
389
|
+
content = await fs4.readFile(routesFile, "utf-8");
|
|
390
|
+
} else {
|
|
391
|
+
content = `// Auto-generated routes configuration
|
|
392
|
+
|
|
393
|
+
`;
|
|
394
|
+
}
|
|
395
|
+
if (!content.includes(importStatement)) {
|
|
396
|
+
const lastImportIndex = content.lastIndexOf("import");
|
|
397
|
+
if (lastImportIndex !== -1) {
|
|
398
|
+
const endOfLine = content.indexOf("\n", lastImportIndex);
|
|
399
|
+
content = content.slice(0, endOfLine + 1) + importStatement + "\n" + content.slice(endOfLine + 1);
|
|
400
|
+
} else {
|
|
401
|
+
content = importStatement + "\n\n" + content;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (!content.includes("export const mainRoutes")) {
|
|
405
|
+
content += `
|
|
406
|
+
export const mainRoutes = [
|
|
407
|
+
${routeEntry}
|
|
408
|
+
];
|
|
409
|
+
`;
|
|
410
|
+
} else if (!content.includes(routeEntry)) {
|
|
411
|
+
content = content.replace(
|
|
412
|
+
/export const mainRoutes = \[([\s\S]*?)\];/,
|
|
413
|
+
`export const mainRoutes = [$1 ${routeEntry}
|
|
414
|
+
];`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
await fs4.writeFile(routesFile, content);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/commands/module.ts
|
|
421
|
+
async function generateModule(modulePath) {
|
|
422
|
+
const spinner = ora3("Generating module...").start();
|
|
423
|
+
try {
|
|
424
|
+
const config = await loadConfig();
|
|
425
|
+
if (!config) {
|
|
426
|
+
spinner.fail("kit.config.json not found.");
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const fullPath = path5.join(process.cwd(), config.modulesDir, modulePath);
|
|
430
|
+
if (fs5.existsSync(fullPath)) {
|
|
431
|
+
spinner.fail(`Module ${modulePath} already exists`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const moduleName = path5.basename(modulePath);
|
|
435
|
+
const dirs = ["view", "components", "hooks", "store"];
|
|
436
|
+
for (const dir of dirs) {
|
|
437
|
+
await fs5.ensureDir(path5.join(fullPath, dir));
|
|
438
|
+
}
|
|
439
|
+
const viewName = `${toSnakeCase(moduleName)}_view`;
|
|
440
|
+
const viewContent = generateViewTemplate(toPascalCase(moduleName));
|
|
441
|
+
await fs5.writeFile(path5.join(fullPath, "view", `${viewName}.tsx`), viewContent);
|
|
442
|
+
const barrelContent = `// Auto-generated exports for ${moduleName} module
|
|
443
|
+
export { default as ${toPascalCase(moduleName)}View } from './view/${viewName}';
|
|
444
|
+
`;
|
|
445
|
+
await fs5.writeFile(path5.join(fullPath, "index.ts"), barrelContent);
|
|
446
|
+
await addToRouting(config, modulePath, viewName);
|
|
447
|
+
spinner.succeed(chalk4.green(`Module ${modulePath} created successfully!`));
|
|
448
|
+
console.log(chalk4.dim("\nGenerated:"));
|
|
449
|
+
console.log(chalk4.cyan(` ${config.modulesDir}/${modulePath}/view/${viewName}.tsx`));
|
|
450
|
+
console.log(chalk4.cyan(` ${config.modulesDir}/${modulePath}/index.ts`));
|
|
451
|
+
if (config.navigation === "expo-router") {
|
|
452
|
+
console.log(chalk4.cyan(` app/${moduleName}.tsx`));
|
|
453
|
+
}
|
|
454
|
+
} catch (error) {
|
|
455
|
+
spinner.fail("Failed to generate module");
|
|
456
|
+
console.error(error);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async function generateTabs(modulePath) {
|
|
460
|
+
const spinner = ora3("Generating tabs module...").start();
|
|
461
|
+
try {
|
|
462
|
+
const config = await loadConfig();
|
|
463
|
+
if (!config) {
|
|
464
|
+
spinner.fail("kit.config.json not found.");
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
spinner.stop();
|
|
468
|
+
const response = await prompts2([
|
|
469
|
+
{
|
|
470
|
+
type: "confirm",
|
|
471
|
+
name: "generateTabs",
|
|
472
|
+
message: "Do you want to generate initial tabs?",
|
|
473
|
+
initial: true
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
type: (prev) => prev ? "number" : null,
|
|
477
|
+
name: "tabCount",
|
|
478
|
+
message: "How many tabs?",
|
|
479
|
+
initial: 3,
|
|
480
|
+
min: 1,
|
|
481
|
+
max: 10
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
type: (prev, values) => values.generateTabs ? "list" : null,
|
|
485
|
+
name: "tabNames",
|
|
486
|
+
message: "Tab names (comma-separated):",
|
|
487
|
+
initial: "home,profile,settings",
|
|
488
|
+
separator: ","
|
|
489
|
+
}
|
|
490
|
+
]);
|
|
491
|
+
spinner.start("Creating tabs module...");
|
|
492
|
+
const fullModulePath = path5.join(process.cwd(), config.modulesDir, modulePath);
|
|
493
|
+
const moduleName = path5.basename(modulePath);
|
|
494
|
+
await fs5.ensureDir(path5.join(fullModulePath, "tabs"));
|
|
495
|
+
if (config.navigation !== "expo-router") {
|
|
496
|
+
const layoutContent = generateTabsLayoutTemplate(moduleName, response.tabNames || []);
|
|
497
|
+
await fs5.writeFile(path5.join(fullModulePath, "tabs_layout.tsx"), layoutContent);
|
|
498
|
+
}
|
|
499
|
+
if (response.generateTabs && response.tabNames) {
|
|
500
|
+
for (const tabName of response.tabNames) {
|
|
501
|
+
const tabPath = path5.join(fullModulePath, "tabs", tabName);
|
|
502
|
+
await generateTabModule(tabPath, tabName, config);
|
|
503
|
+
spinner.text = `Generated tab: ${tabName}`;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const exports = response.tabNames?.map(
|
|
507
|
+
(tab) => `export { default as ${toPascalCase(tab)}Tab } from './tabs/${tab}/view/${toSnakeCase(tab)}_view';`
|
|
508
|
+
).join("\n") || "";
|
|
509
|
+
let barrelContent = `// Auto-generated exports for ${moduleName} tabs module
|
|
510
|
+
`;
|
|
511
|
+
if (config.navigation !== "expo-router") {
|
|
512
|
+
barrelContent += `export { default as TabsLayout } from './tabs_layout';
|
|
513
|
+
`;
|
|
514
|
+
}
|
|
515
|
+
barrelContent += exports + "\n";
|
|
516
|
+
await fs5.writeFile(path5.join(fullModulePath, "index.ts"), barrelContent);
|
|
517
|
+
if (config.navigation === "expo-router") {
|
|
518
|
+
await createExpoRouterTabsLayout(config, modulePath, response.tabNames || []);
|
|
519
|
+
}
|
|
520
|
+
spinner.succeed(chalk4.green(`Tabs module ${modulePath} created successfully!`));
|
|
521
|
+
console.log(chalk4.dim("\nGenerated:"));
|
|
522
|
+
if (config.navigation !== "expo-router") {
|
|
523
|
+
console.log(chalk4.cyan(` ${config.modulesDir}/${modulePath}/tabs_layout.tsx`));
|
|
524
|
+
} else {
|
|
525
|
+
console.log(chalk4.cyan(` app/(${moduleName})/_layout.tsx`));
|
|
526
|
+
if (response.tabNames) {
|
|
527
|
+
response.tabNames.forEach((tab) => {
|
|
528
|
+
console.log(chalk4.cyan(` app/(${moduleName})/${tab}.tsx`));
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (response.tabNames) {
|
|
533
|
+
response.tabNames.forEach((tab) => {
|
|
534
|
+
console.log(chalk4.cyan(` ${config.modulesDir}/${modulePath}/tabs/${tab}/`));
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
} catch (error) {
|
|
538
|
+
spinner.fail("Failed to generate tabs module");
|
|
539
|
+
console.error(error);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
async function generateTabModule(tabPath, tabName, config) {
|
|
543
|
+
const dirs = ["view", "components", "hooks", "store"];
|
|
544
|
+
for (const dir of dirs) {
|
|
545
|
+
await fs5.ensureDir(path5.join(tabPath, dir));
|
|
546
|
+
}
|
|
547
|
+
const viewName = `${toSnakeCase(tabName)}_view`;
|
|
548
|
+
const viewContent = generateViewTemplate(toPascalCase(tabName));
|
|
549
|
+
await fs5.writeFile(path5.join(tabPath, "view", `${viewName}.tsx`), viewContent);
|
|
550
|
+
const barrelContent = `export { default as ${toPascalCase(tabName)}View } from './view/${viewName}';
|
|
551
|
+
`;
|
|
552
|
+
await fs5.writeFile(path5.join(tabPath, "index.ts"), barrelContent);
|
|
553
|
+
}
|
|
554
|
+
function generateViewTemplate(componentName) {
|
|
555
|
+
return `import { View, Text } from 'react-native';
|
|
556
|
+
|
|
557
|
+
export default function ${componentName}View() {
|
|
558
|
+
return (
|
|
559
|
+
<View className="flex-1 items-center justify-center">
|
|
560
|
+
<Text className="text-2xl font-bold">${componentName}</Text>
|
|
561
|
+
</View>
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
`;
|
|
565
|
+
}
|
|
566
|
+
function generateTabsLayoutTemplate(moduleName, tabs) {
|
|
567
|
+
const imports = tabs.map(
|
|
568
|
+
(tab) => `import ${toPascalCase(tab)}Tab from './tabs/${tab}/view/${toSnakeCase(tab)}_view';`
|
|
569
|
+
).join("\n");
|
|
570
|
+
const screens = tabs.map(
|
|
571
|
+
(tab) => ` <Tabs.Screen
|
|
572
|
+
name="${tab}"
|
|
573
|
+
component={${toPascalCase(tab)}Tab}
|
|
574
|
+
options={{ title: '${toPascalCase(tab)}' }}
|
|
575
|
+
/>`
|
|
576
|
+
).join("\n");
|
|
577
|
+
return `import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
|
578
|
+
${imports}
|
|
579
|
+
|
|
580
|
+
const Tabs = createBottomTabNavigator();
|
|
581
|
+
|
|
582
|
+
export default function ${toPascalCase(moduleName)}TabsLayout() {
|
|
583
|
+
return (
|
|
584
|
+
<Tabs.Navigator>
|
|
585
|
+
${screens}
|
|
586
|
+
</Tabs.Navigator>
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
`;
|
|
590
|
+
}
|
|
591
|
+
async function createExpoRouterTabsLayout(config, modulePath, tabs) {
|
|
592
|
+
const moduleName = path5.basename(modulePath);
|
|
593
|
+
const appDir = path5.join(process.cwd(), "app", `(${moduleName})`);
|
|
594
|
+
await fs5.ensureDir(appDir);
|
|
595
|
+
const layoutContent = `import { Tabs } from 'expo-router';
|
|
596
|
+
|
|
597
|
+
export default function ${toPascalCase(moduleName)}Layout() {
|
|
598
|
+
return (
|
|
599
|
+
<Tabs>
|
|
600
|
+
${tabs.map((tab) => ` <Tabs.Screen name="${tab}" options={{ title: '${toPascalCase(tab)}' }} />`).join("\n")}
|
|
601
|
+
</Tabs>
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
`;
|
|
605
|
+
await fs5.writeFile(path5.join(appDir, "_layout.tsx"), layoutContent);
|
|
606
|
+
for (const tab of tabs) {
|
|
607
|
+
const tabContent = `export { default } from '@modules/${modulePath}/tabs/${tab}/view/${toSnakeCase(tab)}_view';`;
|
|
608
|
+
await fs5.writeFile(path5.join(appDir, `${tab}.tsx`), tabContent);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// src/commands/generate-scoped.ts
|
|
613
|
+
import fs7 from "fs-extra";
|
|
614
|
+
import path6 from "path";
|
|
615
|
+
import chalk5 from "chalk";
|
|
616
|
+
import ora4 from "ora";
|
|
617
|
+
|
|
618
|
+
// src/utils/barrel.ts
|
|
619
|
+
import fs6 from "fs-extra";
|
|
620
|
+
async function updateBarrelExport(filePath, exportStatement) {
|
|
621
|
+
let content = "";
|
|
622
|
+
if (fs6.existsSync(filePath)) {
|
|
623
|
+
content = await fs6.readFile(filePath, "utf-8");
|
|
624
|
+
} else {
|
|
625
|
+
content = "// Auto-generated barrel export\n// This file is managed by Lunar Kit CLI\n\n";
|
|
626
|
+
}
|
|
627
|
+
if (!content.includes(exportStatement)) {
|
|
628
|
+
content += exportStatement + "\n";
|
|
629
|
+
await fs6.writeFile(filePath, content);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/commands/generate-scoped.ts
|
|
634
|
+
async function generateModuleView(fullPath) {
|
|
635
|
+
const spinner = ora4("Generating module view...").start();
|
|
636
|
+
try {
|
|
637
|
+
const config = await loadConfig();
|
|
638
|
+
if (!config) {
|
|
639
|
+
spinner.fail("kit.config.json not found.");
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const parts = fullPath.split("/");
|
|
643
|
+
if (parts.length < 2) {
|
|
644
|
+
spinner.fail("Invalid format. Use: module/view-name (e.g., welcome/splash)");
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const modulePath = parts.slice(0, -1).join("/");
|
|
648
|
+
const viewName = parts[parts.length - 1];
|
|
649
|
+
const moduleFullPath = path6.join(process.cwd(), config.modulesDir, modulePath);
|
|
650
|
+
if (!fs7.existsSync(moduleFullPath)) {
|
|
651
|
+
spinner.fail(`Module ${modulePath} does not exist. Create it first with: lunar g mod ${modulePath}`);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const viewDir = path6.join(moduleFullPath, "view");
|
|
655
|
+
await fs7.ensureDir(viewDir);
|
|
656
|
+
const createViewName = toSnakeCase(viewName);
|
|
657
|
+
const existingViewSuffix = ["_view", "View", "-view"].find((suffix) => createViewName.endsWith(suffix));
|
|
658
|
+
const finalViewName = existingViewSuffix ? createViewName.slice(0, -existingViewSuffix.length) : createViewName;
|
|
659
|
+
const fileName = `${toSnakeCase(finalViewName)}_view.tsx`;
|
|
660
|
+
const componentName = toPascalCase(`${finalViewName}_view`);
|
|
661
|
+
const viewContent = `import { View, Text } from 'react-native';
|
|
662
|
+
|
|
663
|
+
export default function ${componentName}() {
|
|
664
|
+
return (
|
|
665
|
+
<View className="flex-1 items-center justify-center">
|
|
666
|
+
<Text className="text-2xl font-bold">${componentName}</Text>
|
|
667
|
+
</View>
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
`;
|
|
671
|
+
const filePath = path6.join(viewDir, fileName);
|
|
672
|
+
await fs7.writeFile(filePath, viewContent);
|
|
673
|
+
await updateBarrelExport(
|
|
674
|
+
path6.join(moduleFullPath, "index.ts"),
|
|
675
|
+
`export { default as ${componentName} } from './view/${toSnakeCase(finalViewName)}_view';`
|
|
676
|
+
);
|
|
677
|
+
await addToRouting(config, `${modulePath}/${finalViewName}`, `${toSnakeCase(finalViewName)}_view`, "view");
|
|
678
|
+
spinner.succeed(chalk5.green(`View ${viewName} created in module ${modulePath}`));
|
|
679
|
+
console.log(chalk5.cyan(` ${config.modulesDir}/${modulePath}/view/${fileName}`));
|
|
680
|
+
} catch (error) {
|
|
681
|
+
spinner.fail("Failed to generate module view");
|
|
682
|
+
console.error(error);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async function generateModuleComponent(fullPath) {
|
|
686
|
+
const spinner = ora4("Generating module component...").start();
|
|
687
|
+
try {
|
|
688
|
+
const config = await loadConfig();
|
|
689
|
+
if (!config) {
|
|
690
|
+
spinner.fail("kit.config.json not found.");
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const parts = fullPath.split("/");
|
|
694
|
+
if (parts.length < 2) {
|
|
695
|
+
spinner.fail("Invalid format. Use: module/component-name (e.g., auth/login-button)");
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const modulePath = parts.slice(0, -1).join("/");
|
|
699
|
+
const componentName = parts[parts.length - 1];
|
|
700
|
+
const moduleFullPath = path6.join(process.cwd(), config.modulesDir, modulePath);
|
|
701
|
+
if (!fs7.existsSync(moduleFullPath)) {
|
|
702
|
+
spinner.fail(`Module ${modulePath} does not exist.`);
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const componentDir = path6.join(moduleFullPath, "components");
|
|
706
|
+
await fs7.ensureDir(componentDir);
|
|
707
|
+
const fileName = `${toSnakeCase(componentName)}.tsx`;
|
|
708
|
+
const exportName = toPascalCase(componentName);
|
|
709
|
+
const componentContent = `import { View, Text } from 'react-native';
|
|
710
|
+
|
|
711
|
+
interface ${exportName}Props {
|
|
712
|
+
// Add props here
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
export function ${exportName}({}: ${exportName}Props) {
|
|
716
|
+
return (
|
|
717
|
+
<View className="p-4">
|
|
718
|
+
<Text className="text-lg font-semibold">${exportName}</Text>
|
|
719
|
+
</View>
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
`;
|
|
723
|
+
const filePath = path6.join(componentDir, fileName);
|
|
724
|
+
await fs7.writeFile(filePath, componentContent);
|
|
725
|
+
await updateBarrelExport(
|
|
726
|
+
path6.join(moduleFullPath, "index.ts"),
|
|
727
|
+
`export { ${exportName} } from './components/${toSnakeCase(componentName)}';`
|
|
728
|
+
);
|
|
729
|
+
spinner.succeed(chalk5.green(`Component ${componentName} created in module ${modulePath}`));
|
|
730
|
+
console.log(chalk5.cyan(` ${config.modulesDir}/${modulePath}/components/${fileName}`));
|
|
731
|
+
} catch (error) {
|
|
732
|
+
spinner.fail("Failed to generate module component");
|
|
733
|
+
console.error(error);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
async function generateModuleStore(fullPath) {
|
|
737
|
+
const spinner = ora4("Generating module store...").start();
|
|
738
|
+
try {
|
|
739
|
+
const config = await loadConfig();
|
|
740
|
+
if (!config) {
|
|
741
|
+
spinner.fail("kit.config.json not found.");
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
const parts = fullPath.split("/");
|
|
745
|
+
if (parts.length < 2) {
|
|
746
|
+
spinner.fail("Invalid format. Use: module/store-name (e.g., auth/auth-store)");
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const modulePath = parts.slice(0, -1).join("/");
|
|
750
|
+
const storeName = parts[parts.length - 1];
|
|
751
|
+
const moduleFullPath = path6.join(process.cwd(), config.modulesDir, modulePath);
|
|
752
|
+
if (!fs7.existsSync(moduleFullPath)) {
|
|
753
|
+
spinner.fail(`Module ${modulePath} does not exist.`);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const storeDir = path6.join(moduleFullPath, "store");
|
|
757
|
+
await fs7.ensureDir(storeDir);
|
|
758
|
+
const fileName = `${toSnakeCase(storeName)}.ts`;
|
|
759
|
+
const hookName = `use${toPascalCase(storeName)}`;
|
|
760
|
+
const storeContent = `import { create } from 'zustand';
|
|
761
|
+
|
|
762
|
+
interface ${toPascalCase(storeName)}State {
|
|
763
|
+
// Add state here
|
|
764
|
+
count: number;
|
|
765
|
+
increment: () => void;
|
|
766
|
+
decrement: () => void;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
export const ${hookName} = create<${toPascalCase(storeName)}State>((set) => ({
|
|
770
|
+
count: 0,
|
|
771
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
772
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
773
|
+
}));
|
|
774
|
+
`;
|
|
775
|
+
const filePath = path6.join(storeDir, fileName);
|
|
776
|
+
await fs7.writeFile(filePath, storeContent);
|
|
777
|
+
await updateBarrelExport(
|
|
778
|
+
path6.join(moduleFullPath, "index.ts"),
|
|
779
|
+
`export { ${hookName} } from './store/${toSnakeCase(storeName)}';`
|
|
780
|
+
);
|
|
781
|
+
spinner.succeed(chalk5.green(`Store ${storeName} created in module ${modulePath}`));
|
|
782
|
+
console.log(chalk5.cyan(` ${config.modulesDir}/${modulePath}/store/${fileName}`));
|
|
783
|
+
} catch (error) {
|
|
784
|
+
spinner.fail("Failed to generate module store");
|
|
785
|
+
console.error(error);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
async function generateModuleHook(fullPath) {
|
|
789
|
+
const spinner = ora4("Generating module hook...").start();
|
|
790
|
+
try {
|
|
791
|
+
const config = await loadConfig();
|
|
792
|
+
if (!config) {
|
|
793
|
+
spinner.fail("kit.config.json not found.");
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const parts = fullPath.split("/");
|
|
797
|
+
if (parts.length < 2) {
|
|
798
|
+
spinner.fail("Invalid format. Use: module/hook-name (e.g., auth/use-auth)");
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const modulePath = parts.slice(0, -1).join("/");
|
|
802
|
+
const hookName = parts[parts.length - 1];
|
|
803
|
+
const moduleFullPath = path6.join(process.cwd(), config.modulesDir, modulePath);
|
|
804
|
+
if (!fs7.existsSync(moduleFullPath)) {
|
|
805
|
+
spinner.fail(`Module ${modulePath} does not exist.`);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const hookDir = path6.join(moduleFullPath, "hooks");
|
|
809
|
+
await fs7.ensureDir(hookDir);
|
|
810
|
+
const fileName = toCamelCase(hookName.startsWith("use") ? hookName : `use-${hookName}`) + ".ts";
|
|
811
|
+
const exportName = toCamelCase(fileName.replace(".ts", ""));
|
|
812
|
+
const hookContent = `import { useState, useEffect } from 'react';
|
|
813
|
+
|
|
814
|
+
export function ${exportName}() {
|
|
815
|
+
const [value, setValue] = useState(null);
|
|
816
|
+
|
|
817
|
+
useEffect(() => {
|
|
818
|
+
// Add side effects here
|
|
819
|
+
}, []);
|
|
820
|
+
|
|
821
|
+
return { value, setValue };
|
|
822
|
+
}
|
|
823
|
+
`;
|
|
824
|
+
const filePath = path6.join(hookDir, fileName);
|
|
825
|
+
await fs7.writeFile(filePath, hookContent);
|
|
826
|
+
await updateBarrelExport(
|
|
827
|
+
path6.join(moduleFullPath, "index.ts"),
|
|
828
|
+
`export { ${exportName} } from './hooks/${fileName.replace(".ts", "")}';`
|
|
829
|
+
);
|
|
830
|
+
spinner.succeed(chalk5.green(`Hook ${hookName} created in module ${modulePath}`));
|
|
831
|
+
console.log(chalk5.cyan(` ${config.modulesDir}/${modulePath}/hooks/${fileName}`));
|
|
832
|
+
} catch (error) {
|
|
833
|
+
spinner.fail("Failed to generate module hook");
|
|
834
|
+
console.error(error);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/commands/generate-global.ts
|
|
839
|
+
import fs8 from "fs-extra";
|
|
840
|
+
import path7 from "path";
|
|
841
|
+
import chalk6 from "chalk";
|
|
842
|
+
import ora5 from "ora";
|
|
843
|
+
async function generateGlobalHook(hookName) {
|
|
844
|
+
const spinner = ora5("Generating global hook...").start();
|
|
845
|
+
try {
|
|
846
|
+
const config = await loadConfig();
|
|
847
|
+
if (!config) {
|
|
848
|
+
spinner.fail("kit.config.json not found.");
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
const hooksDir = path7.join(process.cwd(), config.hooksDir);
|
|
852
|
+
await fs8.ensureDir(hooksDir);
|
|
853
|
+
const fileName = toCamelCase(hookName.startsWith("use") ? hookName : `use${toPascalCase(hookName)}`) + ".ts";
|
|
854
|
+
const exportName = fileName.replace(".ts", "");
|
|
855
|
+
const hookContent = `import { useState, useEffect } from 'react';
|
|
856
|
+
|
|
857
|
+
export function ${exportName}() {
|
|
858
|
+
const [value, setValue] = useState(null);
|
|
859
|
+
|
|
860
|
+
useEffect(() => {
|
|
861
|
+
// Add side effects here
|
|
862
|
+
}, []);
|
|
863
|
+
|
|
864
|
+
return { value, setValue };
|
|
865
|
+
}
|
|
866
|
+
`;
|
|
867
|
+
const filePath = path7.join(hooksDir, fileName);
|
|
868
|
+
await fs8.writeFile(filePath, hookContent);
|
|
869
|
+
await updateBarrelExport(
|
|
870
|
+
path7.join(hooksDir, "index.ts"),
|
|
871
|
+
`export { ${exportName} } from './${fileName.replace(".ts", "")}';`
|
|
872
|
+
);
|
|
873
|
+
spinner.succeed(chalk6.green(`Hook ${hookName} created`));
|
|
874
|
+
console.log(chalk6.cyan(` ${config.hooksDir}/${fileName}`));
|
|
875
|
+
} catch (error) {
|
|
876
|
+
spinner.fail("Failed to generate hook");
|
|
877
|
+
console.error(error);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
async function generateGlobalStore(storeName) {
|
|
881
|
+
const spinner = ora5("Generating global store...").start();
|
|
882
|
+
try {
|
|
883
|
+
const config = await loadConfig();
|
|
884
|
+
if (!config) {
|
|
885
|
+
spinner.fail("kit.config.json not found.");
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const storesDir = path7.join(process.cwd(), config.storesDir);
|
|
889
|
+
await fs8.ensureDir(storesDir);
|
|
890
|
+
const fileName = `${toSnakeCase(storeName)}.ts`;
|
|
891
|
+
const hookName = `use${toPascalCase(storeName)}Store`;
|
|
892
|
+
const storeContent = `import { create } from 'zustand';
|
|
893
|
+
|
|
894
|
+
interface ${toPascalCase(storeName)}State {
|
|
895
|
+
// Add state here
|
|
896
|
+
count: number;
|
|
897
|
+
increment: () => void;
|
|
898
|
+
decrement: () => void;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
export const ${hookName} = create<${toPascalCase(storeName)}State>((set) => ({
|
|
902
|
+
count: 0,
|
|
903
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
904
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
905
|
+
}));
|
|
906
|
+
`;
|
|
907
|
+
const filePath = path7.join(storesDir, fileName);
|
|
908
|
+
await fs8.writeFile(filePath, storeContent);
|
|
909
|
+
await updateBarrelExport(
|
|
910
|
+
path7.join(storesDir, "index.ts"),
|
|
911
|
+
`export { ${hookName} } from './${fileName.replace(".ts", "")}';`
|
|
912
|
+
);
|
|
913
|
+
spinner.succeed(chalk6.green(`Store ${storeName} created`));
|
|
914
|
+
console.log(chalk6.cyan(` ${config.storesDir}/${fileName}`));
|
|
915
|
+
} catch (error) {
|
|
916
|
+
spinner.fail("Failed to generate store");
|
|
917
|
+
console.error(error);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
async function generateGlobalComponent(componentName) {
|
|
921
|
+
const spinner = ora5("Generating global component...").start();
|
|
922
|
+
try {
|
|
923
|
+
const config = await loadConfig();
|
|
924
|
+
if (!config) {
|
|
925
|
+
spinner.fail("kit.config.json not found.");
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
const componentsBaseDir = path7.join(process.cwd(), config.componentsDir);
|
|
929
|
+
await fs8.ensureDir(componentsBaseDir);
|
|
930
|
+
const componentDir = path7.join(componentsBaseDir, toSnakeCase(componentName));
|
|
931
|
+
await fs8.ensureDir(componentDir);
|
|
932
|
+
const fileName = `${toSnakeCase(componentName)}.tsx`;
|
|
933
|
+
const exportName = toPascalCase(componentName);
|
|
934
|
+
const componentContent = `import { View, Text } from 'react-native';
|
|
935
|
+
|
|
936
|
+
interface ${exportName}Props {
|
|
937
|
+
// Add props here
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
export function ${exportName}({}: ${exportName}Props) {
|
|
941
|
+
return (
|
|
942
|
+
<View className="p-4">
|
|
943
|
+
<Text className="text-lg font-semibold">${exportName}</Text>
|
|
944
|
+
</View>
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
`;
|
|
948
|
+
const filePath = path7.join(componentDir, fileName);
|
|
949
|
+
await fs8.writeFile(filePath, componentContent);
|
|
950
|
+
await updateBarrelExport(
|
|
951
|
+
path7.join(componentsBaseDir, "index.ts"),
|
|
952
|
+
`export { ${exportName} } from './${toSnakeCase(componentName)}/${toSnakeCase(componentName)}';`
|
|
953
|
+
);
|
|
954
|
+
spinner.succeed(chalk6.green(`Component ${componentName} created`));
|
|
955
|
+
console.log(chalk6.cyan(` ${config.componentsDir}/${toSnakeCase(componentName)}/${fileName}`));
|
|
956
|
+
} catch (error) {
|
|
957
|
+
spinner.fail("Failed to generate component");
|
|
958
|
+
console.error(error);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// src/index.ts
|
|
963
|
+
var program = new Command();
|
|
964
|
+
program.name("lunar").description("CLI for Lunar Kit - Universal React Native UI components").version("0.1.0");
|
|
965
|
+
program.command("init").description("Initialize Lunar Kit in your project").action(async () => {
|
|
966
|
+
await initProject();
|
|
967
|
+
});
|
|
968
|
+
program.command("add <component>").description("Add a UI component to your project").action(async (component) => {
|
|
969
|
+
await addComponent(component);
|
|
970
|
+
});
|
|
971
|
+
var generate = program.command("generate").alias("g").description("Generate modules, screens, components, stores, or hooks");
|
|
972
|
+
generate.command("mod <path>").description("Generate a new module (e.g., auth/login)").action(async (path8) => {
|
|
973
|
+
await generateModule(path8);
|
|
974
|
+
});
|
|
975
|
+
generate.command("tabs <path>").description("Generate a tabs module (e.g., shop/main)").action(async (path8) => {
|
|
976
|
+
await generateTabs(path8);
|
|
977
|
+
});
|
|
978
|
+
generate.command("mod:view <path>").alias("mod:vi").description("Generate view in module (e.g., auth/login-screen)").action(async (path8) => {
|
|
979
|
+
await generateModuleView(path8);
|
|
980
|
+
});
|
|
981
|
+
generate.command("mod:component <path>").alias("mod:co").description("Generate component in module (e.g., auth/login-button)").action(async (path8) => {
|
|
982
|
+
await generateModuleComponent(path8);
|
|
983
|
+
});
|
|
984
|
+
generate.command("mod:store <path>").alias("mod:st").description("Generate store in module (e.g., auth/auth-store)").action(async (path8) => {
|
|
985
|
+
await generateModuleStore(path8);
|
|
986
|
+
});
|
|
987
|
+
generate.command("mod:hook <path>").alias("mod:ho").description("Generate hook in module (e.g., auth/use-auth)").action(async (path8) => {
|
|
988
|
+
await generateModuleHook(path8);
|
|
989
|
+
});
|
|
990
|
+
generate.command("component <name>").alias("co").description("Generate global component").action(async (name) => {
|
|
991
|
+
await generateGlobalComponent(name);
|
|
992
|
+
});
|
|
993
|
+
generate.command("hook <name>").alias("ho").description("Generate global hook").action(async (name) => {
|
|
994
|
+
await generateGlobalHook(name);
|
|
995
|
+
});
|
|
996
|
+
generate.command("store <name>").alias("st").description("Generate global store").action(async (name) => {
|
|
997
|
+
await generateGlobalStore(name);
|
|
998
|
+
});
|
|
999
|
+
program.configureHelp({
|
|
1000
|
+
formatHelp: () => {
|
|
1001
|
+
return `
|
|
1002
|
+
${chalk7.bold("Usage:")} lunar <command> [options]
|
|
1003
|
+
|
|
1004
|
+
${chalk7.bold("Commands:")}
|
|
1005
|
+
${chalk7.green("init")} Initialize Lunar Kit in your project
|
|
1006
|
+
${chalk7.green("add")} <component> Add a UI component to your project
|
|
1007
|
+
${chalk7.green("generate|g")} [options] <schematic> [path] Generate a Lunar Kit element.
|
|
1008
|
+
${chalk7.dim("Schematics available:")}
|
|
1009
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
1010
|
+
\u2502 ${chalk7.bold("name")} \u2502 ${chalk7.bold("alias")} \u2502 ${chalk7.bold("description")} \u2502
|
|
1011
|
+
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
|
|
1012
|
+
\u2502 ${chalk7.cyan("Module")} \u2502 \u2502 \u2502
|
|
1013
|
+
\u2502 mod \u2502 mod \u2502 Generate a module \u2502
|
|
1014
|
+
\u2502 tabs \u2502 tabs \u2502 Generate a tabs module \u2502
|
|
1015
|
+
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
|
|
1016
|
+
\u2502 ${chalk7.cyan("Module-Scoped")} \u2502 \u2502 \u2502
|
|
1017
|
+
\u2502 mod:view \u2502 mod:vi \u2502 Generate a view in module \u2502
|
|
1018
|
+
\u2502 mod:component \u2502 mod:co \u2502 Generate a component in module \u2502
|
|
1019
|
+
\u2502 mod:store \u2502 mod:st \u2502 Generate a store in module \u2502
|
|
1020
|
+
\u2502 mod:hook \u2502 mod:ho \u2502 Generate a hook in module \u2502
|
|
1021
|
+
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
|
|
1022
|
+
\u2502 ${chalk7.cyan("Global")} \u2502 \u2502 \u2502
|
|
1023
|
+
\u2502 component \u2502 co \u2502 Generate a global component \u2502
|
|
1024
|
+
\u2502 hook \u2502 ho \u2502 Generate a global hook \u2502
|
|
1025
|
+
\u2502 store \u2502 st \u2502 Generate a global store \u2502
|
|
1026
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
1027
|
+
|
|
1028
|
+
${chalk7.bold("Examples:")}
|
|
1029
|
+
${chalk7.dim("$ lunar init")}
|
|
1030
|
+
${chalk7.dim("$ lunar add button")}
|
|
1031
|
+
${chalk7.dim("$ lunar g mod auth/login")}
|
|
1032
|
+
${chalk7.dim("$ lunar g mod:vi auth/login-screen")}
|
|
1033
|
+
${chalk7.dim("$ lunar g tabs shop")}
|
|
1034
|
+
`;
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lunar-kit/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for Lunar Kit",
|
|
5
|
+
"bin": {
|
|
6
|
+
"lk": "./dist/index.js",
|
|
7
|
+
"lunar": "./dist/index.js",
|
|
8
|
+
"lunar-kit": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup",
|
|
13
|
+
"dev": "tsup --watch",
|
|
14
|
+
"prepublishOnly": "pnpm build"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@lunar-kit/core": "workspace:*",
|
|
18
|
+
"axios": "^1.13.2",
|
|
19
|
+
"chalk": "^5.6.2",
|
|
20
|
+
"commander": "^14.0.2",
|
|
21
|
+
"fs-extra": "^11.3.3",
|
|
22
|
+
"ora": "^9.1.0",
|
|
23
|
+
"prompts": "^2.4.2"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/fs-extra": "^11.0.4",
|
|
27
|
+
"@types/prompts": "^2.4.9",
|
|
28
|
+
"tsup": "^8.5.1",
|
|
29
|
+
"typescript": "~5.9.3"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"keywords": [
|
|
35
|
+
"react-native",
|
|
36
|
+
"cli",
|
|
37
|
+
"lunar-kit",
|
|
38
|
+
"component-generator",
|
|
39
|
+
"expo",
|
|
40
|
+
"ui-components"
|
|
41
|
+
],
|
|
42
|
+
"author": "Your Name <your@email.com>",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/yourusername/lunar-kit.git",
|
|
47
|
+
"directory": "packages/cli"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/yourusername/lunar-kit#readme",
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/yourusername/lunar-kit/issues"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.0.0"
|
|
55
|
+
}
|
|
56
|
+
}
|