@lytjs/cli 5.0.6 → 6.5.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/create.cjs +607 -0
- package/dist/create.cjs.map +1 -0
- package/dist/create.d.mts +2 -0
- package/dist/create.d.ts +2 -0
- package/dist/create.mjs +605 -0
- package/dist/create.mjs.map +1 -0
- package/dist/index.cjs +2212 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +208 -0
- package/dist/index.d.ts +208 -0
- package/dist/index.mjs +1999 -886
- package/dist/index.mjs.map +1 -0
- package/dist/lyt.cjs +2212 -0
- package/dist/lyt.cjs.map +1 -0
- package/dist/lyt.d.mts +1 -0
- package/dist/lyt.d.ts +1 -0
- package/dist/lyt.mjs +2171 -0
- package/dist/lyt.mjs.map +1 -0
- package/lyt-cli.js +3 -0
- package/package.json +34 -31
- package/README.md +0 -227
- package/dist/bin/cli.cjs +0 -1058
- package/dist/bin/cli.js +0 -2
- package/dist/bin/cli.mjs +0 -1058
- package/dist/index.js +0 -1058
- package/dist/types/bin/cli.d.ts +0 -8
- package/dist/types/bin/cli.d.ts.map +0 -1
- package/dist/types/build.d.ts +0 -30
- package/dist/types/build.d.ts.map +0 -1
- package/dist/types/create.d.ts +0 -19
- package/dist/types/create.d.ts.map +0 -1
- package/dist/types/dev.d.ts +0 -24
- package/dist/types/dev.d.ts.map +0 -1
- package/dist/types/generate.d.ts +0 -14
- package/dist/types/generate.d.ts.map +0 -1
- package/dist/types/hmr.d.ts +0 -55
- package/dist/types/hmr.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -20
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/scaffold.d.ts +0 -67
- package/dist/types/scaffold.d.ts.map +0 -1
- package/dist/types/utils.d.ts +0 -92
- package/dist/types/utils.d.ts.map +0 -1
package/dist/lyt.mjs
ADDED
|
@@ -0,0 +1,2171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { join, resolve, dirname } from 'path';
|
|
5
|
+
import { execSync, spawn } from 'child_process';
|
|
6
|
+
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// src/utils/logger.ts
|
|
15
|
+
var colors = {
|
|
16
|
+
reset: "\x1B[0m",
|
|
17
|
+
bright: "\x1B[1m",
|
|
18
|
+
dim: "\x1B[2m",
|
|
19
|
+
red: "\x1B[31m",
|
|
20
|
+
green: "\x1B[32m",
|
|
21
|
+
yellow: "\x1B[33m",
|
|
22
|
+
blue: "\x1B[34m",
|
|
23
|
+
cyan: "\x1B[36m"
|
|
24
|
+
};
|
|
25
|
+
function colorize(text, color) {
|
|
26
|
+
if (!isColorSupported()) return text;
|
|
27
|
+
return `${colors[color]}${text}${colors.reset}`;
|
|
28
|
+
}
|
|
29
|
+
var logger = {
|
|
30
|
+
info(message) {
|
|
31
|
+
console.log(colorize("\u2139 ", "blue") + message);
|
|
32
|
+
},
|
|
33
|
+
success(message) {
|
|
34
|
+
console.log(colorize("\u2714 ", "green") + message);
|
|
35
|
+
},
|
|
36
|
+
warning(message) {
|
|
37
|
+
console.log(colorize("\u26A0 ", "yellow") + message);
|
|
38
|
+
},
|
|
39
|
+
error(message) {
|
|
40
|
+
console.error(colorize("\u2716 ", "red") + message);
|
|
41
|
+
},
|
|
42
|
+
dim(message) {
|
|
43
|
+
console.log(colorize(message, "dim"));
|
|
44
|
+
},
|
|
45
|
+
bold(message) {
|
|
46
|
+
return colorize(message, "bright");
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
function isColorSupported() {
|
|
50
|
+
return process.stdout.isTTY && process.env.NO_COLOR !== "1";
|
|
51
|
+
}
|
|
52
|
+
function ensureDir(dir) {
|
|
53
|
+
if (!existsSync(dir)) {
|
|
54
|
+
mkdirSync(dir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function writeFile(filePath, content) {
|
|
58
|
+
ensureDir(dirname(filePath));
|
|
59
|
+
writeFileSync(filePath, content, "utf-8");
|
|
60
|
+
}
|
|
61
|
+
function readFile(filePath) {
|
|
62
|
+
return readFileSync(filePath, "utf-8");
|
|
63
|
+
}
|
|
64
|
+
function exists(path2) {
|
|
65
|
+
return existsSync(path2);
|
|
66
|
+
}
|
|
67
|
+
function isEmptyDir(dir) {
|
|
68
|
+
if (!existsSync(dir)) return true;
|
|
69
|
+
const files = readdirSync(dir);
|
|
70
|
+
return files.length === 0;
|
|
71
|
+
}
|
|
72
|
+
function detectPackageManager(cwd = process.cwd()) {
|
|
73
|
+
if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
74
|
+
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
75
|
+
if (existsSync(join(cwd, "package-lock.json"))) return "npm";
|
|
76
|
+
try {
|
|
77
|
+
execSync("pnpm --version", { stdio: "ignore" });
|
|
78
|
+
return "pnpm";
|
|
79
|
+
} catch {
|
|
80
|
+
return "npm";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function getInstallCommand(pm) {
|
|
84
|
+
switch (pm) {
|
|
85
|
+
case "pnpm":
|
|
86
|
+
return "pnpm install";
|
|
87
|
+
case "yarn":
|
|
88
|
+
return "yarn";
|
|
89
|
+
case "npm":
|
|
90
|
+
return "npm install";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function getRunCommand(pm, script) {
|
|
94
|
+
switch (pm) {
|
|
95
|
+
case "pnpm":
|
|
96
|
+
return `pnpm run ${script}`;
|
|
97
|
+
case "yarn":
|
|
98
|
+
return `yarn ${script}`;
|
|
99
|
+
case "npm":
|
|
100
|
+
return `npm run ${script}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function getAddCommand(pm, dep, dev2 = false) {
|
|
104
|
+
const devFlag = dev2 ? " -D" : "";
|
|
105
|
+
switch (pm) {
|
|
106
|
+
case "pnpm":
|
|
107
|
+
return `pnpm add${devFlag} ${dep}`;
|
|
108
|
+
case "yarn":
|
|
109
|
+
return `yarn add${devFlag} ${dep}`;
|
|
110
|
+
case "npm":
|
|
111
|
+
return `npm install${devFlag} ${dep}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
var TEMPLATES = {
|
|
115
|
+
default: "Default template with TypeScript and Vite",
|
|
116
|
+
minimal: "Minimal template without extra dependencies",
|
|
117
|
+
ssr: "SSR-enabled template",
|
|
118
|
+
router: "Template with Router integration",
|
|
119
|
+
store: "Template with Store integration",
|
|
120
|
+
full: "Full-featured template with Router, Store, and UI components"
|
|
121
|
+
};
|
|
122
|
+
async function create(projectName, options = {}) {
|
|
123
|
+
if (!projectName) {
|
|
124
|
+
logger.error("Please provide a project name.");
|
|
125
|
+
logger.info("Usage: lyt create <project-name>");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
const targetDir = resolve(process.cwd(), projectName);
|
|
129
|
+
if (exists(targetDir) && !isEmptyDir(targetDir) && !options.force) {
|
|
130
|
+
logger.error(`Directory "${projectName}" already exists and is not empty.`);
|
|
131
|
+
logger.info("Use --force to overwrite.");
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
logger.info(`Creating a new LytJS project in ${targetDir}...`);
|
|
135
|
+
ensureDir(targetDir);
|
|
136
|
+
generateProjectFiles(targetDir, projectName, options.template || "default");
|
|
137
|
+
logger.info("Installing dependencies...");
|
|
138
|
+
const pm = detectPackageManager();
|
|
139
|
+
try {
|
|
140
|
+
execSync(getInstallCommand(pm), { cwd: targetDir, stdio: "inherit" });
|
|
141
|
+
logger.success("Dependencies installed successfully!");
|
|
142
|
+
} catch (_error) {
|
|
143
|
+
logger.warning("Failed to install dependencies automatically.");
|
|
144
|
+
logger.info(`Please run "${getInstallCommand(pm)}" manually.`);
|
|
145
|
+
}
|
|
146
|
+
logger.success(`Project "${projectName}" created successfully!`);
|
|
147
|
+
logger.info("");
|
|
148
|
+
logger.bold("Next steps:");
|
|
149
|
+
logger.info(` cd ${projectName}`);
|
|
150
|
+
logger.info(` ${pm === "npm" ? "npm run" : pm} dev`);
|
|
151
|
+
}
|
|
152
|
+
function generateProjectFiles(targetDir, projectName, template) {
|
|
153
|
+
const isMinimal = template === "minimal";
|
|
154
|
+
const isSsr = template === "ssr";
|
|
155
|
+
const isRouter = template === "router" || template === "full";
|
|
156
|
+
const isStore = template === "store" || template === "full";
|
|
157
|
+
const isFull = template === "full";
|
|
158
|
+
const packageJson = {
|
|
159
|
+
name: projectName,
|
|
160
|
+
version: "0.0.0",
|
|
161
|
+
type: "module",
|
|
162
|
+
scripts: {
|
|
163
|
+
dev: "vite",
|
|
164
|
+
build: "vite build",
|
|
165
|
+
preview: "vite preview"
|
|
166
|
+
},
|
|
167
|
+
dependencies: {
|
|
168
|
+
"@lytjs/core": "^6.0.0"
|
|
169
|
+
},
|
|
170
|
+
devDependencies: {
|
|
171
|
+
"@lytjs/plugin-vite": "^6.0.0",
|
|
172
|
+
"vite": "^5.0.0"
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
if (!isMinimal) {
|
|
176
|
+
packageJson.scripts.test = "vitest";
|
|
177
|
+
packageJson.devDependencies.vitest = "^1.0.0";
|
|
178
|
+
}
|
|
179
|
+
if (isSsr) {
|
|
180
|
+
packageJson.dependencies["@lytjs/server"] = "^6.0.0";
|
|
181
|
+
packageJson.scripts["build:client"] = "vite build --ssrManifest";
|
|
182
|
+
packageJson.scripts["build:server"] = "vite build --ssr src/entry-server.ts";
|
|
183
|
+
packageJson.scripts["build"] = "npm run build:client && npm run build:server";
|
|
184
|
+
packageJson.scripts["preview"] = "node server";
|
|
185
|
+
}
|
|
186
|
+
if (isRouter) {
|
|
187
|
+
packageJson.dependencies["@lytjs/router"] = "^1.0.0";
|
|
188
|
+
}
|
|
189
|
+
if (isStore) {
|
|
190
|
+
packageJson.dependencies["@lytjs/store"] = "^1.0.0";
|
|
191
|
+
}
|
|
192
|
+
if (isFull) {
|
|
193
|
+
packageJson.dependencies["@lytjs/ui"] = "^0.4.0";
|
|
194
|
+
}
|
|
195
|
+
writeFile(join(targetDir, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
196
|
+
let viteConfig;
|
|
197
|
+
if (isSsr) {
|
|
198
|
+
viteConfig = `import { defineConfig } from 'vite';
|
|
199
|
+
import lytjs from '@lytjs/plugin-vite';
|
|
200
|
+
|
|
201
|
+
export default defineConfig({
|
|
202
|
+
plugins: [lytjs()],
|
|
203
|
+
build: {
|
|
204
|
+
ssrManifest: true,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
`;
|
|
208
|
+
} else {
|
|
209
|
+
viteConfig = `import { defineConfig } from 'vite';
|
|
210
|
+
import lytjs from '@lytjs/plugin-vite';
|
|
211
|
+
|
|
212
|
+
export default defineConfig({
|
|
213
|
+
plugins: [lytjs()],
|
|
214
|
+
});
|
|
215
|
+
`;
|
|
216
|
+
}
|
|
217
|
+
writeFile(join(targetDir, "vite.config.ts"), viteConfig);
|
|
218
|
+
const indexHtml = `<!DOCTYPE html>
|
|
219
|
+
<html lang="en">
|
|
220
|
+
<head>
|
|
221
|
+
<meta charset="UTF-8" />
|
|
222
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
223
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
224
|
+
<title>${projectName}</title>
|
|
225
|
+
</head>
|
|
226
|
+
<body>
|
|
227
|
+
<div id="app"></div>
|
|
228
|
+
<script type="module" src="/src/main.ts"></script>
|
|
229
|
+
</body>
|
|
230
|
+
</html>
|
|
231
|
+
`;
|
|
232
|
+
writeFile(join(targetDir, "index.html"), indexHtml);
|
|
233
|
+
let mainTs;
|
|
234
|
+
if (isSsr) {
|
|
235
|
+
mainTs = `import { createApp } from '@lytjs/core';
|
|
236
|
+
import App from './App.lyt';
|
|
237
|
+
import { createSSRApp } from '@lytjs/server';
|
|
238
|
+
${isRouter ? "import { createRouter, createWebHistory } from '@lytjs/router';" : ""}
|
|
239
|
+
${isStore ? "import { createPinia } from '@lytjs/store';" : ""}
|
|
240
|
+
|
|
241
|
+
const app = createSSRApp(App);
|
|
242
|
+
${isStore ? "app.use(createPinia());" : ""}
|
|
243
|
+
${isRouter ? `
|
|
244
|
+
const router = createRouter({
|
|
245
|
+
history: createWebHistory(),
|
|
246
|
+
routes: [
|
|
247
|
+
{ path: '/', component: () => import('./pages/Home.lyt') },
|
|
248
|
+
{ path: '/about', component: () => import('./pages/About.lyt') },
|
|
249
|
+
],
|
|
250
|
+
});
|
|
251
|
+
app.use(router);
|
|
252
|
+
` : ""}
|
|
253
|
+
app.mount('#app');
|
|
254
|
+
`;
|
|
255
|
+
} else if (isRouter || isStore) {
|
|
256
|
+
mainTs = `import { createApp } from '@lytjs/core';
|
|
257
|
+
import App from './App.lyt';
|
|
258
|
+
${isRouter ? "import { createRouter, createWebHistory } from '@lytjs/router';" : ""}
|
|
259
|
+
${isStore ? "import { createPinia } from '@lytjs/store';" : ""}
|
|
260
|
+
|
|
261
|
+
const app = createApp(App);
|
|
262
|
+
${isStore ? "app.use(createPinia());" : ""}
|
|
263
|
+
${isRouter ? `
|
|
264
|
+
const router = createRouter({
|
|
265
|
+
history: createWebHistory(),
|
|
266
|
+
routes: [
|
|
267
|
+
{ path: '/', component: () => import('./pages/Home.lyt') },
|
|
268
|
+
{ path: '/about', component: () => import('./pages/About.lyt') },
|
|
269
|
+
],
|
|
270
|
+
});
|
|
271
|
+
app.use(router);
|
|
272
|
+
` : ""}
|
|
273
|
+
app.mount('#app');
|
|
274
|
+
`;
|
|
275
|
+
} else {
|
|
276
|
+
mainTs = `import { createApp } from '@lytjs/core';
|
|
277
|
+
import App from './App.lyt';
|
|
278
|
+
|
|
279
|
+
createApp(App).mount('#app');
|
|
280
|
+
`;
|
|
281
|
+
}
|
|
282
|
+
writeFile(join(targetDir, "src/main.ts"), mainTs);
|
|
283
|
+
let appLyt;
|
|
284
|
+
if (isMinimal) {
|
|
285
|
+
appLyt = `<template>
|
|
286
|
+
<div class="app">
|
|
287
|
+
<h1>{{ title }}</h1>
|
|
288
|
+
</div>
|
|
289
|
+
</template>
|
|
290
|
+
|
|
291
|
+
<script setup>
|
|
292
|
+
const title = 'Hello LytJS!';
|
|
293
|
+
</script>
|
|
294
|
+
|
|
295
|
+
<style scoped>
|
|
296
|
+
.app {
|
|
297
|
+
text-align: center;
|
|
298
|
+
}
|
|
299
|
+
</style>
|
|
300
|
+
`;
|
|
301
|
+
} else if (isRouter) {
|
|
302
|
+
appLyt = `<template>
|
|
303
|
+
<div class="app">
|
|
304
|
+
<nav class="nav">
|
|
305
|
+
<router-link to="/">Home</router-link>
|
|
306
|
+
<router-link to="/about">About</router-link>
|
|
307
|
+
</nav>
|
|
308
|
+
<router-view />
|
|
309
|
+
</div>
|
|
310
|
+
</template>
|
|
311
|
+
|
|
312
|
+
<script setup lang="ts">
|
|
313
|
+
import { RouterLink, RouterView } from '@lytjs/router';
|
|
314
|
+
</script>
|
|
315
|
+
|
|
316
|
+
<style scoped>
|
|
317
|
+
.app {
|
|
318
|
+
text-align: center;
|
|
319
|
+
padding: 2rem;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.nav {
|
|
323
|
+
margin-bottom: 2rem;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.nav a {
|
|
327
|
+
margin: 0 1rem;
|
|
328
|
+
color: #42b883;
|
|
329
|
+
text-decoration: none;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.nav a:hover {
|
|
333
|
+
text-decoration: underline;
|
|
334
|
+
}
|
|
335
|
+
</style>
|
|
336
|
+
`;
|
|
337
|
+
} else {
|
|
338
|
+
appLyt = `<template>
|
|
339
|
+
<div class="app">
|
|
340
|
+
<h1>{{ title }}</h1>
|
|
341
|
+
<p>Welcome to your LytJS app!</p>
|
|
342
|
+
</div>
|
|
343
|
+
</template>
|
|
344
|
+
|
|
345
|
+
<script setup>
|
|
346
|
+
const title = 'Hello LytJS!';
|
|
347
|
+
</script>
|
|
348
|
+
|
|
349
|
+
<style scoped>
|
|
350
|
+
.app {
|
|
351
|
+
text-align: center;
|
|
352
|
+
padding: 2rem;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
h1 {
|
|
356
|
+
color: #42b883;
|
|
357
|
+
}
|
|
358
|
+
</style>
|
|
359
|
+
`;
|
|
360
|
+
}
|
|
361
|
+
writeFile(join(targetDir, "src/App.lyt"), appLyt);
|
|
362
|
+
if (isRouter) {
|
|
363
|
+
const homePage = `<template>
|
|
364
|
+
<div class="home">
|
|
365
|
+
<h1>Home</h1>
|
|
366
|
+
${isStore ? `
|
|
367
|
+
<p>Count: {{ count }}</p>
|
|
368
|
+
<button @click="increment">Increment</button>
|
|
369
|
+
<button @click="decrement">Decrement</button>
|
|
370
|
+
` : ""}
|
|
371
|
+
<p>Welcome to the Home page!</p>
|
|
372
|
+
</div>
|
|
373
|
+
</template>
|
|
374
|
+
|
|
375
|
+
<script setup lang="ts">
|
|
376
|
+
${isStore ? `import { useCounterStore } from '../stores/counter';
|
|
377
|
+
const counterStore = useCounterStore();
|
|
378
|
+
const { count, increment, decrement } = counterStore;
|
|
379
|
+
` : ""}
|
|
380
|
+
</script>
|
|
381
|
+
|
|
382
|
+
<style scoped>
|
|
383
|
+
.home {
|
|
384
|
+
padding: 1rem;
|
|
385
|
+
}
|
|
386
|
+
</style>
|
|
387
|
+
`;
|
|
388
|
+
ensureDir(join(targetDir, "src", "pages"));
|
|
389
|
+
writeFile(join(targetDir, "src", "pages", "Home.lyt"), homePage);
|
|
390
|
+
const aboutPage = `<template>
|
|
391
|
+
<div class="about">
|
|
392
|
+
<h1>About</h1>
|
|
393
|
+
<p>This is the About page!</p>
|
|
394
|
+
</div>
|
|
395
|
+
</template>
|
|
396
|
+
|
|
397
|
+
<script setup lang="ts">
|
|
398
|
+
</script>
|
|
399
|
+
|
|
400
|
+
<style scoped>
|
|
401
|
+
.about {
|
|
402
|
+
padding: 1rem;
|
|
403
|
+
}
|
|
404
|
+
</style>
|
|
405
|
+
`;
|
|
406
|
+
writeFile(join(targetDir, "src", "pages", "About.lyt"), aboutPage);
|
|
407
|
+
}
|
|
408
|
+
if (isStore) {
|
|
409
|
+
const counterStore = `import { defineStore } from '@lytjs/store';
|
|
410
|
+
import { signal, computed } from '@lytjs/reactivity';
|
|
411
|
+
|
|
412
|
+
export const useCounterStore = defineStore('counter', () => {
|
|
413
|
+
// State
|
|
414
|
+
const count = signal(0);
|
|
415
|
+
|
|
416
|
+
// Getters
|
|
417
|
+
const doubleCount = computed(() => count.value * 2);
|
|
418
|
+
|
|
419
|
+
// Actions
|
|
420
|
+
function increment() {
|
|
421
|
+
count.value++;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function decrement() {
|
|
425
|
+
count.value--;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function reset() {
|
|
429
|
+
count.value = 0;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
count,
|
|
434
|
+
doubleCount,
|
|
435
|
+
increment,
|
|
436
|
+
decrement,
|
|
437
|
+
reset,
|
|
438
|
+
};
|
|
439
|
+
});
|
|
440
|
+
`;
|
|
441
|
+
ensureDir(join(targetDir, "src", "stores"));
|
|
442
|
+
writeFile(join(targetDir, "src", "stores", "counter.ts"), counterStore);
|
|
443
|
+
}
|
|
444
|
+
if (isSsr) {
|
|
445
|
+
const entryServer = `import { createSSRApp, h } from '@lytjs/core';
|
|
446
|
+
import { renderToString } from '@lytjs/ssr';
|
|
447
|
+
import App from './App.lyt';
|
|
448
|
+
|
|
449
|
+
export async function render(url: string) {
|
|
450
|
+
const app = createSSRApp({
|
|
451
|
+
render() {
|
|
452
|
+
return h(App);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
const html = await renderToString(app);
|
|
457
|
+
return html;
|
|
458
|
+
}
|
|
459
|
+
`;
|
|
460
|
+
writeFile(join(targetDir, "src/entry-server.ts"), entryServer);
|
|
461
|
+
const entryClient = `import { createApp } from '@lytjs/core';
|
|
462
|
+
import App from './App.lyt';
|
|
463
|
+
|
|
464
|
+
const app = createApp(App);
|
|
465
|
+
app.mount('#app');
|
|
466
|
+
`;
|
|
467
|
+
writeFile(join(targetDir, "src/entry-client.ts"), entryClient);
|
|
468
|
+
const serverTs = `/**
|
|
469
|
+
* LytJS SSR Server
|
|
470
|
+
*
|
|
471
|
+
* Complete SSR server with Vite dev server and production build support.
|
|
472
|
+
* Supports streaming SSR, route prefetching, and static file serving.
|
|
473
|
+
*/
|
|
474
|
+
|
|
475
|
+
import fs from 'fs';
|
|
476
|
+
import path from 'path';
|
|
477
|
+
import { fileURLToPath } from 'url';
|
|
478
|
+
import http from 'http';
|
|
479
|
+
|
|
480
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
481
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
482
|
+
const DIST_DIR = path.join(__dirname, 'dist');
|
|
483
|
+
const PORT = parseInt(process.env.PORT || '3000', 10);
|
|
484
|
+
|
|
485
|
+
interface RenderOptions {
|
|
486
|
+
url: string;
|
|
487
|
+
template: string;
|
|
488
|
+
manifest?: Record<string, string[]>;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async function renderPage({ url, template, manifest }: RenderOptions): Promise<string> {
|
|
492
|
+
let app: any;
|
|
493
|
+
let vite: any;
|
|
494
|
+
|
|
495
|
+
if (!isProduction) {
|
|
496
|
+
const { createServer: createViteServer } = await import('vite');
|
|
497
|
+
vite = await createViteServer({
|
|
498
|
+
server: { middlewareMode: true },
|
|
499
|
+
appType: 'custom',
|
|
500
|
+
});
|
|
501
|
+
app = (await vite.ssrLoadModule(path.join(__dirname, 'src/entry-server.ts'))).default;
|
|
502
|
+
} else {
|
|
503
|
+
app = (await import(path.join(DIST_DIR, 'server/entry-server.js'))).default;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const html = await app.render(url);
|
|
507
|
+
return template.replace('<!--app-html-->', html);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async function createServer() {
|
|
511
|
+
let vite: any;
|
|
512
|
+
|
|
513
|
+
// Load index.html template
|
|
514
|
+
let template: string;
|
|
515
|
+
|
|
516
|
+
if (!isProduction) {
|
|
517
|
+
const { createServer: createViteServer } = await import('vite');
|
|
518
|
+
vite = await createViteServer({
|
|
519
|
+
server: { middlewareMode: true },
|
|
520
|
+
appType: 'custom',
|
|
521
|
+
});
|
|
522
|
+
template = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf-8');
|
|
523
|
+
} else {
|
|
524
|
+
template = fs.readFileSync(path.join(DIST_DIR, 'client/index.html'), 'utf-8');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const server = http.createServer(async (req, res) => {
|
|
528
|
+
const url = req.url || '/';
|
|
529
|
+
|
|
530
|
+
try {
|
|
531
|
+
if (!isProduction && url.startsWith('/@')) {
|
|
532
|
+
// Vite dev server requests
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Static assets
|
|
537
|
+
if (url.startsWith('/assets/') || url.endsWith('.js') || url.endsWith('.css')) {
|
|
538
|
+
const filePath = isProduction
|
|
539
|
+
? path.join(DIST_DIR, 'client', url)
|
|
540
|
+
: path.join(__dirname, url);
|
|
541
|
+
|
|
542
|
+
if (fs.existsSync(filePath)) {
|
|
543
|
+
const ext = path.extname(filePath);
|
|
544
|
+
const contentType = ext === '.css' ? 'text/css' : 'application/javascript';
|
|
545
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
546
|
+
res.end(fs.readFileSync(filePath));
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// SSR rendering
|
|
552
|
+
const html = await renderPage({
|
|
553
|
+
url,
|
|
554
|
+
template,
|
|
555
|
+
manifest: isProduction
|
|
556
|
+
? JSON.parse(fs.readFileSync(path.join(DIST_DIR, 'client/ssr-manifest.json'), 'utf-8'))
|
|
557
|
+
: undefined
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
561
|
+
res.end(html);
|
|
562
|
+
} catch (err: any) {
|
|
563
|
+
if (!isProduction && vite) {
|
|
564
|
+
vite.ssrFixStacktrace(err);
|
|
565
|
+
}
|
|
566
|
+
console.error('SSR Error:', err);
|
|
567
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
568
|
+
res.end('Internal Server Error');
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
server.listen(PORT, () => {
|
|
573
|
+
console.log(\`LytJS SSR server running at http://localhost:\${PORT}\`);
|
|
574
|
+
console.log(\`Mode: \${isProduction ? 'Production' : 'Development'}\`);
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
createServer().catch(console.error);
|
|
579
|
+
`;
|
|
580
|
+
writeFile(join(targetDir, "server.ts"), serverTs);
|
|
581
|
+
}
|
|
582
|
+
const tsConfig = {
|
|
583
|
+
compilerOptions: {
|
|
584
|
+
target: "ES2020",
|
|
585
|
+
useDefineForClassFields: true,
|
|
586
|
+
module: "ESNext",
|
|
587
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
588
|
+
skipLibCheck: true,
|
|
589
|
+
moduleResolution: "bundler",
|
|
590
|
+
allowImportingTsExtensions: true,
|
|
591
|
+
resolveJsonModule: true,
|
|
592
|
+
isolatedModules: true,
|
|
593
|
+
noEmit: true,
|
|
594
|
+
strict: true,
|
|
595
|
+
noUnusedLocals: true,
|
|
596
|
+
noUnusedParameters: true,
|
|
597
|
+
noFallthroughCasesInSwitch: true
|
|
598
|
+
},
|
|
599
|
+
include: ["src/**/*.ts", "src/**/*.lyt"],
|
|
600
|
+
references: [{ path: "./tsconfig.node.json" }]
|
|
601
|
+
};
|
|
602
|
+
writeFile(join(targetDir, "tsconfig.json"), JSON.stringify(tsConfig, null, 2));
|
|
603
|
+
const tsConfigNode = {
|
|
604
|
+
compilerOptions: {
|
|
605
|
+
composite: true,
|
|
606
|
+
skipLibCheck: true,
|
|
607
|
+
module: "ESNext",
|
|
608
|
+
moduleResolution: "bundler",
|
|
609
|
+
allowSyntheticDefaultImports: true
|
|
610
|
+
},
|
|
611
|
+
include: ["vite.config.ts"]
|
|
612
|
+
};
|
|
613
|
+
writeFile(join(targetDir, "tsconfig.node.json"), JSON.stringify(tsConfigNode, null, 2));
|
|
614
|
+
const gitignore = `# Logs
|
|
615
|
+
logs
|
|
616
|
+
*.log
|
|
617
|
+
npm-debug.log*
|
|
618
|
+
yarn-debug.log*
|
|
619
|
+
yarn-error.log*
|
|
620
|
+
pnpm-debug.log*
|
|
621
|
+
lerna-debug.log*
|
|
622
|
+
|
|
623
|
+
node_modules
|
|
624
|
+
dist
|
|
625
|
+
dist-ssr
|
|
626
|
+
*.local
|
|
627
|
+
|
|
628
|
+
# Editor directories and files
|
|
629
|
+
.vscode/*
|
|
630
|
+
!.vscode/extensions.json
|
|
631
|
+
.idea
|
|
632
|
+
.DS_Store
|
|
633
|
+
*.suo
|
|
634
|
+
*.ntvs*
|
|
635
|
+
*.njsproj
|
|
636
|
+
*.sln
|
|
637
|
+
*.sw?
|
|
638
|
+
`;
|
|
639
|
+
writeFile(join(targetDir, ".gitignore"), gitignore);
|
|
640
|
+
}
|
|
641
|
+
function listTemplates() {
|
|
642
|
+
logger.bold("Available templates:");
|
|
643
|
+
for (const [name, description] of Object.entries(TEMPLATES)) {
|
|
644
|
+
logger.info(` ${name.padEnd(10)} - ${description}`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
async function dev(options = {}) {
|
|
648
|
+
if (!exists(join(process.cwd(), "package.json"))) {
|
|
649
|
+
logger.error("No package.json found. Are you in a LytJS project directory?");
|
|
650
|
+
process.exit(1);
|
|
651
|
+
}
|
|
652
|
+
const pm = detectPackageManager();
|
|
653
|
+
const runCmd = getRunCommand(pm, "dev");
|
|
654
|
+
logger.info(`Starting development server with ${pm}...`);
|
|
655
|
+
const devArgs = [];
|
|
656
|
+
if (options.port) devArgs.push("--port", String(options.port));
|
|
657
|
+
if (options.host) devArgs.push("--host", options.host);
|
|
658
|
+
if (options.open) devArgs.push("--open");
|
|
659
|
+
const cmdParts = runCmd.split(" ");
|
|
660
|
+
const cmd = cmdParts[0] ?? "pnpm";
|
|
661
|
+
const cmdArgs = [...cmdParts.slice(1), ...devArgs];
|
|
662
|
+
const child = spawn(cmd, cmdArgs, {
|
|
663
|
+
stdio: "inherit",
|
|
664
|
+
shell: true
|
|
665
|
+
});
|
|
666
|
+
child.on("error", (error) => {
|
|
667
|
+
logger.error(`Failed to start dev server: ${error.message}`);
|
|
668
|
+
process.exit(1);
|
|
669
|
+
});
|
|
670
|
+
child.on("exit", (code) => {
|
|
671
|
+
process.exit(code || 0);
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
async function build(options = {}) {
|
|
675
|
+
if (!exists(join(process.cwd(), "package.json"))) {
|
|
676
|
+
logger.error("No package.json found. Are you in a LytJS project directory?");
|
|
677
|
+
process.exit(1);
|
|
678
|
+
}
|
|
679
|
+
const pm = detectPackageManager();
|
|
680
|
+
logger.info("Building for production...");
|
|
681
|
+
const args = ["vite", "build"];
|
|
682
|
+
if (options.outDir) args.push("--outDir", options.outDir);
|
|
683
|
+
if (options.ssr) args.push("--ssr");
|
|
684
|
+
if (options.minify === false) args.push("--minify", "false");
|
|
685
|
+
const child = spawn(pm === "npm" ? "npx" : pm, args, {
|
|
686
|
+
stdio: "inherit",
|
|
687
|
+
shell: true
|
|
688
|
+
});
|
|
689
|
+
child.on("error", (error) => {
|
|
690
|
+
logger.error(`Build failed: ${error.message}`);
|
|
691
|
+
process.exit(1);
|
|
692
|
+
});
|
|
693
|
+
child.on("exit", (code) => {
|
|
694
|
+
if (code === 0) {
|
|
695
|
+
logger.success("Build completed successfully!");
|
|
696
|
+
} else {
|
|
697
|
+
logger.error(`Build failed with exit code ${code}`);
|
|
698
|
+
}
|
|
699
|
+
process.exit(code || 0);
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
async function test(options = {}) {
|
|
703
|
+
if (!exists(join(process.cwd(), "package.json"))) {
|
|
704
|
+
logger.error("No package.json found. Are you in a LytJS project directory?");
|
|
705
|
+
process.exit(1);
|
|
706
|
+
}
|
|
707
|
+
const pm = detectPackageManager();
|
|
708
|
+
logger.info("Running tests...");
|
|
709
|
+
const args = ["vitest"];
|
|
710
|
+
if (options.watch === false) args.push("run");
|
|
711
|
+
if (options.coverage) args.push("--coverage");
|
|
712
|
+
if (options.grep) args.push("--grep", options.grep);
|
|
713
|
+
const child = spawn(pm === "npm" ? "npx" : pm, args, {
|
|
714
|
+
stdio: "inherit",
|
|
715
|
+
shell: true
|
|
716
|
+
});
|
|
717
|
+
child.on("error", (error) => {
|
|
718
|
+
logger.error(`Tests failed: ${error.message}`);
|
|
719
|
+
process.exit(1);
|
|
720
|
+
});
|
|
721
|
+
child.on("exit", (code) => {
|
|
722
|
+
process.exit(code || 0);
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
var TEMPLATES2 = {
|
|
726
|
+
component(name, basePath) {
|
|
727
|
+
const filePath = join(basePath, `${name}.lyt`);
|
|
728
|
+
return [{
|
|
729
|
+
filePath,
|
|
730
|
+
content: `<template>
|
|
731
|
+
<div class="${name}">
|
|
732
|
+
<slot />
|
|
733
|
+
</div>
|
|
734
|
+
</template>
|
|
735
|
+
|
|
736
|
+
<script setup lang="ts">
|
|
737
|
+
defineProps<{
|
|
738
|
+
/** Component props */
|
|
739
|
+
}>();
|
|
740
|
+
|
|
741
|
+
defineEmits<{
|
|
742
|
+
/** Component events */
|
|
743
|
+
}>();
|
|
744
|
+
</script>
|
|
745
|
+
|
|
746
|
+
<style scoped>
|
|
747
|
+
.${name} {
|
|
748
|
+
/* styles */
|
|
749
|
+
}
|
|
750
|
+
</style>
|
|
751
|
+
`
|
|
752
|
+
}];
|
|
753
|
+
},
|
|
754
|
+
page(name, basePath) {
|
|
755
|
+
const pascalName = toPascalCase(name);
|
|
756
|
+
const filePath = join(basePath, `${name}.lyt`);
|
|
757
|
+
return [{
|
|
758
|
+
filePath,
|
|
759
|
+
content: `<template>
|
|
760
|
+
<div class="page-${name}">
|
|
761
|
+
<h1>${pascalName}</h1>
|
|
762
|
+
</div>
|
|
763
|
+
</template>
|
|
764
|
+
|
|
765
|
+
<script setup lang="ts">
|
|
766
|
+
// Page logic here
|
|
767
|
+
</script>
|
|
768
|
+
|
|
769
|
+
<style scoped>
|
|
770
|
+
.page-${name} {
|
|
771
|
+
padding: 1rem;
|
|
772
|
+
}
|
|
773
|
+
</style>
|
|
774
|
+
`
|
|
775
|
+
}];
|
|
776
|
+
},
|
|
777
|
+
store(name, basePath) {
|
|
778
|
+
const filePath = join(basePath, `${name}.ts`);
|
|
779
|
+
return [{
|
|
780
|
+
filePath,
|
|
781
|
+
content: `import { defineStore } from '@lytjs/store';
|
|
782
|
+
import { signal, computed } from '@lytjs/reactivity';
|
|
783
|
+
|
|
784
|
+
export const use${toPascalCase(name)}Store = defineStore('${name}', () => {
|
|
785
|
+
// State
|
|
786
|
+
const count = signal(0);
|
|
787
|
+
|
|
788
|
+
// Getters
|
|
789
|
+
const doubleCount = computed(() => count.value * 2);
|
|
790
|
+
|
|
791
|
+
// Actions
|
|
792
|
+
function increment() {
|
|
793
|
+
count.value++;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function decrement() {
|
|
797
|
+
count.value--;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function reset() {
|
|
801
|
+
count.value = 0;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
return {
|
|
805
|
+
count,
|
|
806
|
+
doubleCount,
|
|
807
|
+
increment,
|
|
808
|
+
decrement,
|
|
809
|
+
reset,
|
|
810
|
+
};
|
|
811
|
+
});
|
|
812
|
+
`
|
|
813
|
+
}];
|
|
814
|
+
},
|
|
815
|
+
directive(name, basePath) {
|
|
816
|
+
const filePath = join(basePath, `${name}.ts`);
|
|
817
|
+
const camelCaseName = toCamelCase(name);
|
|
818
|
+
return [{
|
|
819
|
+
filePath,
|
|
820
|
+
content: `import type { Directive } from '@lytjs/core';
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* ${toPascalCase(name)} Directive
|
|
824
|
+
*
|
|
825
|
+
* @example
|
|
826
|
+
* \`\`\`vue
|
|
827
|
+
* <div v-${camelCaseName} />
|
|
828
|
+
* \`\`\`
|
|
829
|
+
*/
|
|
830
|
+
export const v${toPascalCase(name)}: Directive = {
|
|
831
|
+
mounted(el, binding) {
|
|
832
|
+
// Directive mounted
|
|
833
|
+
},
|
|
834
|
+
|
|
835
|
+
updated(el, binding) {
|
|
836
|
+
// Directive updated
|
|
837
|
+
},
|
|
838
|
+
|
|
839
|
+
unmounted(el) {
|
|
840
|
+
// Directive unmounted
|
|
841
|
+
},
|
|
842
|
+
};
|
|
843
|
+
`
|
|
844
|
+
}];
|
|
845
|
+
},
|
|
846
|
+
composable(name, basePath) {
|
|
847
|
+
const filePath = join(basePath, `use${toPascalCase(name)}.ts`);
|
|
848
|
+
return [{
|
|
849
|
+
filePath,
|
|
850
|
+
content: `import { signal, computed } from '@lytjs/reactivity';
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* ${toPascalCase(name)} Composable
|
|
854
|
+
*
|
|
855
|
+
* @example
|
|
856
|
+
* \`\`\`typescript
|
|
857
|
+
* const { state, actions } = use${toPascalCase(name)}();
|
|
858
|
+
* \`\`\`
|
|
859
|
+
*/
|
|
860
|
+
export function use${toPascalCase(name)}() {
|
|
861
|
+
// State
|
|
862
|
+
const isLoading = signal(false);
|
|
863
|
+
const error = signal<Error | null>(null);
|
|
864
|
+
const data = signal<any>(null);
|
|
865
|
+
|
|
866
|
+
// Computed
|
|
867
|
+
const hasData = computed(() => data.value !== null);
|
|
868
|
+
|
|
869
|
+
// Actions
|
|
870
|
+
async function fetch() {
|
|
871
|
+
isLoading.value = true;
|
|
872
|
+
error.value = null;
|
|
873
|
+
try {
|
|
874
|
+
// TODO: Fetch logic here
|
|
875
|
+
// data.value = await someApi();
|
|
876
|
+
} catch (e) {
|
|
877
|
+
error.value = e as Error;
|
|
878
|
+
} finally {
|
|
879
|
+
isLoading.value = false;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function reset() {
|
|
884
|
+
isLoading.value = false;
|
|
885
|
+
error.value = null;
|
|
886
|
+
data.value = null;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return {
|
|
890
|
+
isLoading,
|
|
891
|
+
error,
|
|
892
|
+
data,
|
|
893
|
+
hasData,
|
|
894
|
+
fetch,
|
|
895
|
+
reset,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
`
|
|
899
|
+
}];
|
|
900
|
+
},
|
|
901
|
+
hook(name, basePath) {
|
|
902
|
+
const filePath = join(basePath, `use${toPascalCase(name)}.ts`);
|
|
903
|
+
return [{
|
|
904
|
+
filePath,
|
|
905
|
+
content: `import { signal, onMounted, onUnmounted } from '@lytjs/core';
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* ${toPascalCase(name)} Hook
|
|
909
|
+
*/
|
|
910
|
+
export function use${toPascalCase(name)}() {
|
|
911
|
+
const state = signal(null);
|
|
912
|
+
|
|
913
|
+
onMounted(() => {
|
|
914
|
+
// Setup code on mount
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
onUnmounted(() => {
|
|
918
|
+
// Cleanup on unmount
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
return {
|
|
922
|
+
state,
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
`
|
|
926
|
+
}];
|
|
927
|
+
},
|
|
928
|
+
util(name, basePath) {
|
|
929
|
+
const filePath = join(basePath, `${name}.ts`);
|
|
930
|
+
return [{
|
|
931
|
+
filePath,
|
|
932
|
+
content: `/**
|
|
933
|
+
* ${toPascalCase(name)} Utility Functions
|
|
934
|
+
*/
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* ${toPascalCase(name)} function
|
|
938
|
+
*
|
|
939
|
+
* @param input - The input value
|
|
940
|
+
* @returns The processed result
|
|
941
|
+
*/
|
|
942
|
+
export function ${toCamelCase(name)}(input: any) {
|
|
943
|
+
// TODO: Implement function
|
|
944
|
+
return input;
|
|
945
|
+
}
|
|
946
|
+
`
|
|
947
|
+
}];
|
|
948
|
+
},
|
|
949
|
+
middleware(name, basePath) {
|
|
950
|
+
const filePath = join(basePath, `${name}.ts`);
|
|
951
|
+
return [{
|
|
952
|
+
filePath,
|
|
953
|
+
content: `import type { NavigationGuard } from '@lytjs/router';
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* ${toPascalCase(name)} Middleware
|
|
957
|
+
*/
|
|
958
|
+
export const ${toCamelCase(name)}Middleware: NavigationGuard = (to, from, next) => {
|
|
959
|
+
// Middleware logic
|
|
960
|
+
console.log('Middleware:', to.path);
|
|
961
|
+
next();
|
|
962
|
+
};
|
|
963
|
+
`
|
|
964
|
+
}];
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
function toPascalCase(str) {
|
|
968
|
+
return str.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
969
|
+
}
|
|
970
|
+
function toCamelCase(str) {
|
|
971
|
+
const pascalCase = toPascalCase(str);
|
|
972
|
+
return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
|
|
973
|
+
}
|
|
974
|
+
function resolveTargetDir(type) {
|
|
975
|
+
const cwd = process.cwd();
|
|
976
|
+
switch (type) {
|
|
977
|
+
case "component":
|
|
978
|
+
return join(cwd, "src", "components");
|
|
979
|
+
case "page":
|
|
980
|
+
return join(cwd, "src", "pages");
|
|
981
|
+
case "store":
|
|
982
|
+
return join(cwd, "src", "stores");
|
|
983
|
+
case "directive":
|
|
984
|
+
return join(cwd, "src", "directives");
|
|
985
|
+
case "composable":
|
|
986
|
+
return join(cwd, "src", "composables");
|
|
987
|
+
case "util":
|
|
988
|
+
return join(cwd, "src", "utils");
|
|
989
|
+
case "middleware":
|
|
990
|
+
return join(cwd, "src", "middleware");
|
|
991
|
+
case "hook":
|
|
992
|
+
return join(cwd, "src", "hooks");
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
async function add(type, name, options = {}) {
|
|
996
|
+
const targetDir = resolveTargetDir(type);
|
|
997
|
+
const fullPath = resolve(targetDir);
|
|
998
|
+
if (!exists(join(process.cwd(), "package.json"))) {
|
|
999
|
+
logger.error("No package.json found. Are you in a LytJS project directory?");
|
|
1000
|
+
process.exit(1);
|
|
1001
|
+
}
|
|
1002
|
+
const template = TEMPLATES2[type];
|
|
1003
|
+
if (!template) {
|
|
1004
|
+
logger.error(`Unknown type: ${type}. Supported types: component, page, store`);
|
|
1005
|
+
process.exit(1);
|
|
1006
|
+
}
|
|
1007
|
+
const files = template(name, fullPath);
|
|
1008
|
+
for (const file of files) {
|
|
1009
|
+
if (exists(file.filePath) && !options.force) {
|
|
1010
|
+
logger.warning(`File already exists: ${file.filePath}`);
|
|
1011
|
+
logger.info("Use --force to overwrite.");
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
ensureDir(resolve(file.filePath, ".."));
|
|
1015
|
+
writeFile(file.filePath, file.content);
|
|
1016
|
+
logger.success(`Created ${type}: ${file.filePath}`);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
var TEMPLATES3 = {
|
|
1020
|
+
component: (data, withStyles, withTest, template, lang) => {
|
|
1021
|
+
const styleImport = withStyles ? `
|
|
1022
|
+
import './${data.kebabName}.styles.css';` : "";
|
|
1023
|
+
const tsOnly = lang === "ts";
|
|
1024
|
+
if (template === "sfc") {
|
|
1025
|
+
return `<template>
|
|
1026
|
+
<div class="${data.kebabName}">
|
|
1027
|
+
<slot>
|
|
1028
|
+
${data.pascalName} Component
|
|
1029
|
+
</slot>
|
|
1030
|
+
</div>
|
|
1031
|
+
</template>
|
|
1032
|
+
|
|
1033
|
+
<script setup${tsOnly ? ' lang="ts"' : ""}>
|
|
1034
|
+
${tsOnly ? `import { ref } from '@lytjs/reactivity';
|
|
1035
|
+
` : ""}
|
|
1036
|
+
${tsOnly ? `
|
|
1037
|
+
export interface ${data.pascalName}Props {
|
|
1038
|
+
className?: string;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const props = defineProps<${data.pascalName}Props>();
|
|
1042
|
+
` : ""}
|
|
1043
|
+
|
|
1044
|
+
const title = ref('${data.pascalName}');
|
|
1045
|
+
</script>
|
|
1046
|
+
|
|
1047
|
+
<style scoped>
|
|
1048
|
+
.${data.kebabName} {
|
|
1049
|
+
/* Component styles */
|
|
1050
|
+
}
|
|
1051
|
+
</style>
|
|
1052
|
+
`;
|
|
1053
|
+
}
|
|
1054
|
+
const testImport = withTest ? `
|
|
1055
|
+
import { describe, it, expect } from 'vitest';
|
|
1056
|
+
import { ${data.pascalName} } from './${data.kebabName}';
|
|
1057
|
+
|
|
1058
|
+
describe('${data.pascalName}', () => {
|
|
1059
|
+
it('should render', () => {
|
|
1060
|
+
// Add test here
|
|
1061
|
+
expect(true).toBe(true);
|
|
1062
|
+
});
|
|
1063
|
+
});` : "";
|
|
1064
|
+
const propsDecl = tsOnly ? `['className', 'children']` : `[]`;
|
|
1065
|
+
return `/**
|
|
1066
|
+
* ${data.pascalName} \u7EC4\u4EF6
|
|
1067
|
+
*
|
|
1068
|
+
* @description ${data.description}
|
|
1069
|
+
* @created ${data.date}
|
|
1070
|
+
*/
|
|
1071
|
+
|
|
1072
|
+
import { h, defineComponent } from '@lytjs/core';${styleImport}
|
|
1073
|
+
|
|
1074
|
+
${tsOnly ? `export interface ${data.pascalName}Props {
|
|
1075
|
+
className?: string;
|
|
1076
|
+
children?: any;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
` : ""}${template === "functional" ? `export function ${data.pascalName}(${tsOnly ? `props: ${data.pascalName}Props` : "props"}) {
|
|
1080
|
+
const { className = '', children } = props;
|
|
1081
|
+
|
|
1082
|
+
return (
|
|
1083
|
+
<div className={\`${data.kebabName} \${className}\`}>
|
|
1084
|
+
{children || '${data.pascalName} Component'}
|
|
1085
|
+
</div>
|
|
1086
|
+
);
|
|
1087
|
+
}` : `export const ${data.pascalName} = defineComponent({
|
|
1088
|
+
name: '${data.pascalName}',
|
|
1089
|
+
props: ${propsDecl},
|
|
1090
|
+
setup(props) {
|
|
1091
|
+
const { className = '', children } = props;
|
|
1092
|
+
|
|
1093
|
+
return () => (
|
|
1094
|
+
<div className={\`${data.kebabName} \${className}\`}>
|
|
1095
|
+
{children || '${data.pascalName} Component'}
|
|
1096
|
+
</div>
|
|
1097
|
+
);
|
|
1098
|
+
},
|
|
1099
|
+
});`}
|
|
1100
|
+
|
|
1101
|
+
export default ${data.pascalName};${testImport}
|
|
1102
|
+
`;
|
|
1103
|
+
},
|
|
1104
|
+
page: (data, withStyles, withTest, template, lang) => {
|
|
1105
|
+
const styleImport = withStyles ? `
|
|
1106
|
+
import './${data.kebabName}.styles.css';` : "";
|
|
1107
|
+
const tsOnly = lang === "ts";
|
|
1108
|
+
if (template === "sfc") {
|
|
1109
|
+
return `<template>
|
|
1110
|
+
<div class="${data.kebabName}-page">
|
|
1111
|
+
<h1>{title}</h1>
|
|
1112
|
+
<p>Page content for ${data.pascalName}</p>
|
|
1113
|
+
<slot />
|
|
1114
|
+
</div>
|
|
1115
|
+
</template>
|
|
1116
|
+
|
|
1117
|
+
<script setup${tsOnly ? ' lang="ts"' : ""}>
|
|
1118
|
+
${tsOnly ? `import { ref } from '@lytjs/reactivity';
|
|
1119
|
+
` : ""}
|
|
1120
|
+
${tsOnly ? `
|
|
1121
|
+
export interface ${data.pascalName}PageProps {
|
|
1122
|
+
title?: string;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
const props = defineProps<${data.pascalName}PageProps>();
|
|
1126
|
+
` : ""}
|
|
1127
|
+
|
|
1128
|
+
const title = ref(props.title || '${data.pascalName}');
|
|
1129
|
+
</script>
|
|
1130
|
+
|
|
1131
|
+
<style scoped>
|
|
1132
|
+
.${data.kebabName}-page {
|
|
1133
|
+
padding: 2rem;
|
|
1134
|
+
}
|
|
1135
|
+
</style>
|
|
1136
|
+
`;
|
|
1137
|
+
}
|
|
1138
|
+
const testImport = withTest ? `
|
|
1139
|
+
import { describe, it, expect } from 'vitest';
|
|
1140
|
+
import { ${data.pascalName}Page } from './${data.kebabName}';
|
|
1141
|
+
|
|
1142
|
+
describe('${data.pascalName}Page', () => {
|
|
1143
|
+
it('should render', () => {
|
|
1144
|
+
// Add test here
|
|
1145
|
+
expect(true).toBe(true);
|
|
1146
|
+
});
|
|
1147
|
+
});` : "";
|
|
1148
|
+
return `/**
|
|
1149
|
+
* ${data.pascalName} \u9875\u9762
|
|
1150
|
+
*
|
|
1151
|
+
* @description ${data.description}
|
|
1152
|
+
* @created ${data.date}
|
|
1153
|
+
*/
|
|
1154
|
+
|
|
1155
|
+
import { h, ${tsOnly ? "signal" : "signal"} } from '@lytjs/core';
|
|
1156
|
+
${styleImport}
|
|
1157
|
+
|
|
1158
|
+
${tsOnly ? `export interface ${data.pascalName}PageProps {
|
|
1159
|
+
title?: string;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
` : ""}export function ${data.pascalName}Page(${tsOnly ? `props: ${data.pascalName}PageProps` : "props"}) {
|
|
1163
|
+
const { title = '${data.pascalName}' } = props;
|
|
1164
|
+
|
|
1165
|
+
return (
|
|
1166
|
+
<div className="${data.kebabName}-page">
|
|
1167
|
+
<h1>{title}</h1>
|
|
1168
|
+
<p>Page content for ${data.pascalName}</p>
|
|
1169
|
+
</div>
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
export default ${data.pascalName}Page;${testImport}
|
|
1174
|
+
`;
|
|
1175
|
+
},
|
|
1176
|
+
service: (data, _withStyles, _withTest, _template, lang) => {
|
|
1177
|
+
const tsOnly = lang === "ts";
|
|
1178
|
+
return `/**
|
|
1179
|
+
* ${data.pascalName} \u670D\u52A1
|
|
1180
|
+
*
|
|
1181
|
+
* @description ${data.description}
|
|
1182
|
+
* @created ${data.date}
|
|
1183
|
+
*/
|
|
1184
|
+
|
|
1185
|
+
${tsOnly ? `export interface ${data.pascalName}ServiceOptions {
|
|
1186
|
+
baseUrl?: string;
|
|
1187
|
+
timeout?: number;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
` : ""}${tsOnly ? `export class ${data.pascalName}Service {
|
|
1191
|
+
private baseUrl: string;
|
|
1192
|
+
private timeout: number;
|
|
1193
|
+
` : `export class ${data.pascalName}Service {
|
|
1194
|
+
`}
|
|
1195
|
+
constructor(${tsOnly ? `options: ${data.pascalName}ServiceOptions = {}` : "options = {}"}) {
|
|
1196
|
+
this.baseUrl = options.baseUrl || '/api';
|
|
1197
|
+
this.timeout = options.timeout || 30000;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
async getAll()${tsOnly ? ": Promise<any[]>" : ""} {
|
|
1201
|
+
const response = await fetch(\`\${this.baseUrl}/${data.kebabName}s\`, {
|
|
1202
|
+
method: 'GET',
|
|
1203
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1204
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
1205
|
+
});
|
|
1206
|
+
return response.json();
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
async getById(id)${tsOnly ? ": Promise<any>" : ""} {
|
|
1210
|
+
const response = await fetch(\`\${this.baseUrl}/${data.kebabName}s/\${id}\`, {
|
|
1211
|
+
method: 'GET',
|
|
1212
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1213
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
1214
|
+
});
|
|
1215
|
+
return response.json();
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
async create(${tsOnly ? "data: any" : "data"})${tsOnly ? ": Promise<any>" : ""} {
|
|
1219
|
+
const response = await fetch(\`\${this.baseUrl}/${data.kebabName}s\`, {
|
|
1220
|
+
method: 'POST',
|
|
1221
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1222
|
+
body: JSON.stringify(data),
|
|
1223
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
1224
|
+
});
|
|
1225
|
+
return response.json();
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
async update(id)${tsOnly ? ": Promise<any>" : ""} {
|
|
1229
|
+
const response = await fetch(\`\${this.baseUrl}/${data.kebabName}s/\${id}\`, {
|
|
1230
|
+
method: 'PUT',
|
|
1231
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1232
|
+
body: JSON.stringify(data),
|
|
1233
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
1234
|
+
});
|
|
1235
|
+
return response.json();
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
async delete(id)${tsOnly ? ": Promise<void>" : ""} {
|
|
1239
|
+
await fetch(\`\${this.baseUrl}/${data.kebabName}s/\${id}\`, {
|
|
1240
|
+
method: 'DELETE',
|
|
1241
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
export default ${data.pascalName}Service;
|
|
1247
|
+
`;
|
|
1248
|
+
},
|
|
1249
|
+
hook: (data, _withStyles, _withTest, _template, lang) => {
|
|
1250
|
+
const tsOnly = lang === "ts";
|
|
1251
|
+
return `/**
|
|
1252
|
+
* ${data.pascalName} Hook
|
|
1253
|
+
*
|
|
1254
|
+
* @description ${data.description}
|
|
1255
|
+
* @created ${data.date}
|
|
1256
|
+
*/
|
|
1257
|
+
|
|
1258
|
+
import { signal, effect } from '@lytjs/reactivity';
|
|
1259
|
+
|
|
1260
|
+
${tsOnly ? `export interface ${data.pascalName}Options {
|
|
1261
|
+
immediate?: boolean;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
export interface ${data.pascalName}Return {
|
|
1265
|
+
data: ReturnType<typeof signal>;
|
|
1266
|
+
loading: ReturnType<typeof signal>;
|
|
1267
|
+
error: ReturnType<typeof signal>;
|
|
1268
|
+
execute: () => Promise<void>;
|
|
1269
|
+
reset: () => void;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
` : ""}export function use${data.pascalName}(${tsOnly ? `options: ${data.pascalName}Options = {}` : "options = {}"})${tsOnly ? `: ${data.pascalName}Return` : ""} {
|
|
1273
|
+
const { immediate = false } = options;
|
|
1274
|
+
|
|
1275
|
+
const data = signal<any>(null);
|
|
1276
|
+
const loading = signal(false);
|
|
1277
|
+
const error = signal<Error | null>(null);
|
|
1278
|
+
|
|
1279
|
+
async function execute() {
|
|
1280
|
+
loading.value = true;
|
|
1281
|
+
error.value = null;
|
|
1282
|
+
|
|
1283
|
+
try {
|
|
1284
|
+
const result = await new Promise(resolve => setTimeout(() => resolve(null), 100));
|
|
1285
|
+
data.value = result;
|
|
1286
|
+
} catch (e) {
|
|
1287
|
+
error.value = e as Error;
|
|
1288
|
+
} finally {
|
|
1289
|
+
loading.value = false;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
function reset() {
|
|
1294
|
+
data.value = null;
|
|
1295
|
+
loading.value = false;
|
|
1296
|
+
error.value = null;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
if (immediate) {
|
|
1300
|
+
execute();
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
return {
|
|
1304
|
+
data,
|
|
1305
|
+
loading,
|
|
1306
|
+
error,
|
|
1307
|
+
execute,
|
|
1308
|
+
reset,
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
export default use${data.pascalName};
|
|
1313
|
+
`;
|
|
1314
|
+
},
|
|
1315
|
+
store: (data, _withStyles, _withTest, _template, lang) => {
|
|
1316
|
+
const tsOnly = lang === "ts";
|
|
1317
|
+
return `/**
|
|
1318
|
+
* ${data.pascalName} Store
|
|
1319
|
+
*
|
|
1320
|
+
* @description ${data.description}
|
|
1321
|
+
* @created ${data.date}
|
|
1322
|
+
*/
|
|
1323
|
+
|
|
1324
|
+
import { signal, computed } from '@lytjs/reactivity';
|
|
1325
|
+
|
|
1326
|
+
${tsOnly ? `export interface ${data.pascalName}State {
|
|
1327
|
+
items: any[];
|
|
1328
|
+
selectedId: string | null;
|
|
1329
|
+
loading: boolean;
|
|
1330
|
+
error: Error | null;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
` : ""}export function create${data.pascalName}Store() {
|
|
1334
|
+
const state = signal${tsOnly ? `<${data.pascalName}State>` : ""}({
|
|
1335
|
+
items: [],
|
|
1336
|
+
selectedId: null,
|
|
1337
|
+
loading: false,
|
|
1338
|
+
error: null,
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
const selectedItem = computed(() => {
|
|
1342
|
+
const currentState = state.value;
|
|
1343
|
+
return currentState.items.find(item => item.id === currentState.selectedId);
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
const itemCount = computed(() => state.value.items.length);
|
|
1347
|
+
|
|
1348
|
+
function setItems(items) {
|
|
1349
|
+
state.value = { ...state.value, items };
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function selectItem(id) {
|
|
1353
|
+
state.value = { ...state.value, selectedId: id };
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function addItem(item) {
|
|
1357
|
+
state.value = {
|
|
1358
|
+
...state.value,
|
|
1359
|
+
items: [...state.value.items, item],
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function updateItem(id, updates) {
|
|
1364
|
+
state.value = {
|
|
1365
|
+
...state.value,
|
|
1366
|
+
items: state.value.items.map(item =>
|
|
1367
|
+
item.id === id ? { ...item, ...updates } : item
|
|
1368
|
+
),
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function removeItem(id) {
|
|
1373
|
+
state.value = {
|
|
1374
|
+
...state.value,
|
|
1375
|
+
items: state.value.items.filter(item => item.id !== id),
|
|
1376
|
+
selectedId: state.value.selectedId === id ? null : state.value.selectedId,
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
function setLoading(loading) {
|
|
1381
|
+
state.value = { ...state.value, loading };
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function setError(error) {
|
|
1385
|
+
state.value = { ...state.value, error };
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function reset() {
|
|
1389
|
+
state.value = {
|
|
1390
|
+
items: [],
|
|
1391
|
+
selectedId: null,
|
|
1392
|
+
loading: false,
|
|
1393
|
+
error: null,
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
return {
|
|
1398
|
+
state,
|
|
1399
|
+
selectedItem,
|
|
1400
|
+
itemCount,
|
|
1401
|
+
setItems,
|
|
1402
|
+
selectItem,
|
|
1403
|
+
addItem,
|
|
1404
|
+
updateItem,
|
|
1405
|
+
removeItem,
|
|
1406
|
+
setLoading,
|
|
1407
|
+
setError,
|
|
1408
|
+
reset,
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
${tsOnly ? `export type ${data.pascalName}Store = ReturnType<typeof create${data.pascalName}Store>;
|
|
1413
|
+
` : ""}export default create${data.pascalName}Store;
|
|
1414
|
+
`;
|
|
1415
|
+
},
|
|
1416
|
+
layout: (data, withStyles, _withTest, _template, lang) => {
|
|
1417
|
+
const tsOnly = lang === "ts";
|
|
1418
|
+
const styleImport = withStyles ? `
|
|
1419
|
+
import './${data.kebabName}.styles.css';` : "";
|
|
1420
|
+
return `/**
|
|
1421
|
+
* ${data.pascalName} \u5E03\u5C40
|
|
1422
|
+
*
|
|
1423
|
+
* @description ${data.description}
|
|
1424
|
+
* @created ${data.date}
|
|
1425
|
+
*/
|
|
1426
|
+
|
|
1427
|
+
import { h, defineComponent } from '@lytjs/core';${styleImport}
|
|
1428
|
+
|
|
1429
|
+
${tsOnly ? `export interface ${data.pascalName}LayoutProps {
|
|
1430
|
+
children?: any;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
` : ""}export const ${data.pascalName}Layout = defineComponent({
|
|
1434
|
+
name: '${data.pascalName}Layout',
|
|
1435
|
+
setup(props) {
|
|
1436
|
+
return () => (
|
|
1437
|
+
<div className="${data.kebabName}-layout">
|
|
1438
|
+
<header className="${data.kebabName}-header">
|
|
1439
|
+
<slot name="header">
|
|
1440
|
+
<h1>${data.pascalName}</h1>
|
|
1441
|
+
</slot>
|
|
1442
|
+
</header>
|
|
1443
|
+
<main className="${data.kebabName}-main">
|
|
1444
|
+
<slot />
|
|
1445
|
+
</main>
|
|
1446
|
+
<footer className="${data.kebabName}-footer">
|
|
1447
|
+
<slot name="footer" />
|
|
1448
|
+
</footer>
|
|
1449
|
+
</div>
|
|
1450
|
+
);
|
|
1451
|
+
},
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
export default ${data.pascalName}Layout;
|
|
1455
|
+
`;
|
|
1456
|
+
},
|
|
1457
|
+
middleware: (data, _withStyles, _withTest, _template, lang) => {
|
|
1458
|
+
const tsOnly = lang === "ts";
|
|
1459
|
+
return `/**
|
|
1460
|
+
* ${data.pascalName} \u4E2D\u95F4\u4EF6
|
|
1461
|
+
*
|
|
1462
|
+
* @description ${data.description}
|
|
1463
|
+
* @created ${data.date}
|
|
1464
|
+
*/
|
|
1465
|
+
|
|
1466
|
+
${tsOnly ? `import type { Request, Response, NextFunction } from 'express';
|
|
1467
|
+
` : ""}
|
|
1468
|
+
export function ${data.camelName}Middleware(${tsOnly ? `req: Request, res: Response, next: NextFunction` : "req, res, next"}) {
|
|
1469
|
+
try {
|
|
1470
|
+
console.log('[${data.pascalName}] Middleware executed');
|
|
1471
|
+
next();
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
next(error);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
export default ${data.camelName}Middleware;
|
|
1478
|
+
`;
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
1481
|
+
function toPascalCase2(name) {
|
|
1482
|
+
return name.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
1483
|
+
}
|
|
1484
|
+
function toCamelCase2(name) {
|
|
1485
|
+
const pascal = toPascalCase2(name);
|
|
1486
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
1487
|
+
}
|
|
1488
|
+
function toKebabCase(name) {
|
|
1489
|
+
return name.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
1490
|
+
}
|
|
1491
|
+
async function generate(options) {
|
|
1492
|
+
const {
|
|
1493
|
+
type,
|
|
1494
|
+
name,
|
|
1495
|
+
path: basePath = "./src",
|
|
1496
|
+
withStyles = false,
|
|
1497
|
+
withTest = false,
|
|
1498
|
+
description = "",
|
|
1499
|
+
template = "default",
|
|
1500
|
+
language = "ts"
|
|
1501
|
+
} = options;
|
|
1502
|
+
const templateData = {
|
|
1503
|
+
name,
|
|
1504
|
+
pascalName: toPascalCase2(name),
|
|
1505
|
+
kebabName: toKebabCase(name),
|
|
1506
|
+
camelName: toCamelCase2(name),
|
|
1507
|
+
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
1508
|
+
description: description || `${toPascalCase2(name)} ${type}`
|
|
1509
|
+
};
|
|
1510
|
+
const typeDirs = {
|
|
1511
|
+
component: "components",
|
|
1512
|
+
page: "pages",
|
|
1513
|
+
service: "services",
|
|
1514
|
+
hook: "hooks",
|
|
1515
|
+
store: "stores",
|
|
1516
|
+
layout: "layouts",
|
|
1517
|
+
middleware: "middleware"
|
|
1518
|
+
};
|
|
1519
|
+
const targetDir = path.join(
|
|
1520
|
+
process.cwd(),
|
|
1521
|
+
basePath,
|
|
1522
|
+
typeDirs[type] || "components"
|
|
1523
|
+
);
|
|
1524
|
+
await ensureDir(targetDir);
|
|
1525
|
+
const templateFn = TEMPLATES3[type];
|
|
1526
|
+
if (!templateFn) {
|
|
1527
|
+
logger.error(`Unknown type: ${type}`);
|
|
1528
|
+
logger.info("Available types: component, page, service, hook, store, layout, middleware");
|
|
1529
|
+
process.exit(1);
|
|
1530
|
+
}
|
|
1531
|
+
const extension = template === "sfc" ? "lyt" : language;
|
|
1532
|
+
const filename = `${templateData.kebabName}${type === "page" ? ".page" : ""}.${extension}`;
|
|
1533
|
+
const filePath = path.join(targetDir, filename);
|
|
1534
|
+
const content = templateFn(templateData, withStyles, withTest, template, language);
|
|
1535
|
+
await writeFile(filePath, content);
|
|
1536
|
+
logger.success(`Generated ${type}: ${filePath}`);
|
|
1537
|
+
if (withStyles && template !== "sfc") {
|
|
1538
|
+
const styleContent = `/**
|
|
1539
|
+
* ${templateData.pascalName} Styles
|
|
1540
|
+
*/
|
|
1541
|
+
|
|
1542
|
+
.${templateData.kebabName} {
|
|
1543
|
+
/* Component styles */
|
|
1544
|
+
}
|
|
1545
|
+
`;
|
|
1546
|
+
const stylePath = path.join(targetDir, `${templateData.kebabName}.styles.css`);
|
|
1547
|
+
await writeFile(stylePath, styleContent);
|
|
1548
|
+
logger.success(`Generated styles: ${stylePath}`);
|
|
1549
|
+
}
|
|
1550
|
+
if (withTest && template !== "sfc") {
|
|
1551
|
+
logger.info("Test file included in generated component");
|
|
1552
|
+
}
|
|
1553
|
+
logger.info("\nNext steps:");
|
|
1554
|
+
logger.info(` cd ${targetDir}`);
|
|
1555
|
+
logger.info(` Import your ${type}: import { ${template === "sfc" ? "default" : templateData.pascalName} } from './${templateData.kebabName}'`);
|
|
1556
|
+
logger.info("\nAvailable options:");
|
|
1557
|
+
logger.info(" --template=sfc : Single File Component (.lyt)");
|
|
1558
|
+
logger.info(" --template=functional : Functional component");
|
|
1559
|
+
logger.info(" --language=js : JavaScript output");
|
|
1560
|
+
}
|
|
1561
|
+
var PLUGIN_TEMPLATES = {
|
|
1562
|
+
default: "Default plugin template with TypeScript",
|
|
1563
|
+
minimal: "Minimal plugin without extra dependencies",
|
|
1564
|
+
withConfig: "Plugin template with configuration schema"
|
|
1565
|
+
};
|
|
1566
|
+
function getTemplateContent(template, pluginName) {
|
|
1567
|
+
const packageName = `@lytjs/plugin-${pluginName}`;
|
|
1568
|
+
const files = {};
|
|
1569
|
+
files["package.json"] = JSON.stringify({
|
|
1570
|
+
name: packageName,
|
|
1571
|
+
version: "0.1.0",
|
|
1572
|
+
description: `LytJS plugin: ${pluginName}`,
|
|
1573
|
+
main: "dist/index.cjs",
|
|
1574
|
+
module: "dist/index.mjs",
|
|
1575
|
+
types: "dist/index.d.ts",
|
|
1576
|
+
exports: {
|
|
1577
|
+
".": {
|
|
1578
|
+
import: "./dist/index.mjs",
|
|
1579
|
+
require: "./dist/index.cjs",
|
|
1580
|
+
types: "./dist/index.d.ts"
|
|
1581
|
+
}
|
|
1582
|
+
},
|
|
1583
|
+
files: ["dist"],
|
|
1584
|
+
scripts: {
|
|
1585
|
+
build: "tsup",
|
|
1586
|
+
dev: "tsup --watch",
|
|
1587
|
+
test: "vitest",
|
|
1588
|
+
lint: "eslint src",
|
|
1589
|
+
"prepublishOnly": "npm run build"
|
|
1590
|
+
},
|
|
1591
|
+
keywords: ["lytjs", "plugin"],
|
|
1592
|
+
license: "MIT",
|
|
1593
|
+
peerDependencies: {
|
|
1594
|
+
"@lytjs/core": ">=6.0.0"
|
|
1595
|
+
}
|
|
1596
|
+
}, null, 2);
|
|
1597
|
+
files["tsconfig.json"] = JSON.stringify({
|
|
1598
|
+
extends: "@lytjs/core/tsconfig.json",
|
|
1599
|
+
compilerOptions: {
|
|
1600
|
+
outDir: "./dist",
|
|
1601
|
+
rootDir: "./src",
|
|
1602
|
+
declaration: true,
|
|
1603
|
+
declarationMap: true
|
|
1604
|
+
},
|
|
1605
|
+
include: ["src"],
|
|
1606
|
+
exclude: ["node_modules", "dist", "tests"]
|
|
1607
|
+
}, null, 2);
|
|
1608
|
+
files["tsup.config.ts"] = `import { defineConfig } from 'tsup';
|
|
1609
|
+
|
|
1610
|
+
export default defineConfig({
|
|
1611
|
+
entry: { index: 'src/index.ts' },
|
|
1612
|
+
format: ['esm', 'cjs'],
|
|
1613
|
+
dts: true,
|
|
1614
|
+
sourcemap: true,
|
|
1615
|
+
clean: true,
|
|
1616
|
+
splitting: false,
|
|
1617
|
+
treeshake: true,
|
|
1618
|
+
minify: false,
|
|
1619
|
+
external: ['@lytjs/core'],
|
|
1620
|
+
});
|
|
1621
|
+
`;
|
|
1622
|
+
if (template === "withConfig") {
|
|
1623
|
+
files["src/index.ts"] = `/**
|
|
1624
|
+
* @lytjs/plugin-${pluginName}
|
|
1625
|
+
*
|
|
1626
|
+
* A LytJS plugin with configuration schema support.
|
|
1627
|
+
*/
|
|
1628
|
+
|
|
1629
|
+
import { definePlugin } from '@lytjs/core';
|
|
1630
|
+
import type { ConfigSchema } from '@lytjs/core';
|
|
1631
|
+
|
|
1632
|
+
/**
|
|
1633
|
+
* Plugin options schema
|
|
1634
|
+
*/
|
|
1635
|
+
const optionsSchema: ConfigSchema<${pluginName.replace(/-/g, "")}Options> = {
|
|
1636
|
+
type: 'object',
|
|
1637
|
+
properties: {
|
|
1638
|
+
debug: {
|
|
1639
|
+
type: 'boolean',
|
|
1640
|
+
description: 'Enable debug mode',
|
|
1641
|
+
default: false,
|
|
1642
|
+
},
|
|
1643
|
+
option1: {
|
|
1644
|
+
type: 'string',
|
|
1645
|
+
description: 'First option',
|
|
1646
|
+
default: 'default value',
|
|
1647
|
+
},
|
|
1648
|
+
},
|
|
1649
|
+
additionalProperties: false,
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1652
|
+
export interface ${pluginName.replace(/-/g, "")}Options {
|
|
1653
|
+
debug?: boolean;
|
|
1654
|
+
option1?: string;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
/**
|
|
1658
|
+
* Create the plugin with configuration schema
|
|
1659
|
+
*/
|
|
1660
|
+
export function create${pluginName.replace(/-/g, "").replace(/^\w/, (c) => c.toUpperCase())}(options?: ${pluginName.replace(/-/g, "")}Options) {
|
|
1661
|
+
return definePlugin({
|
|
1662
|
+
name: '${packageName}',
|
|
1663
|
+
version: '0.1.0',
|
|
1664
|
+
description: 'A LytJS plugin',
|
|
1665
|
+
schema: optionsSchema,
|
|
1666
|
+
install: (app, opts) => {
|
|
1667
|
+
const config = opts || {};
|
|
1668
|
+
console.log('[${packageName}] Installing with config:', config);
|
|
1669
|
+
},
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
/**
|
|
1674
|
+
* Direct plugin export (without schema)
|
|
1675
|
+
*/
|
|
1676
|
+
export default create${pluginName.replace(/-/g, "").replace(/^\w/, (c) => c.toUpperCase())}();
|
|
1677
|
+
`;
|
|
1678
|
+
} else {
|
|
1679
|
+
files["src/index.ts"] = `/**
|
|
1680
|
+
* @lytjs/plugin-${pluginName}
|
|
1681
|
+
*
|
|
1682
|
+
* A LytJS plugin.
|
|
1683
|
+
*/
|
|
1684
|
+
|
|
1685
|
+
import { definePlugin } from '@lytjs/core';
|
|
1686
|
+
|
|
1687
|
+
/**
|
|
1688
|
+
* Create the plugin
|
|
1689
|
+
*/
|
|
1690
|
+
export function create${pluginName.replace(/-/g, "").replace(/^\w/, (c) => c.toUpperCase())}() {
|
|
1691
|
+
return definePlugin({
|
|
1692
|
+
name: '${packageName}',
|
|
1693
|
+
version: '0.1.0',
|
|
1694
|
+
description: 'A LytJS plugin',
|
|
1695
|
+
install: (app) => {
|
|
1696
|
+
console.log('[${packageName}] Plugin installed');
|
|
1697
|
+
},
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/**
|
|
1702
|
+
* Direct plugin export
|
|
1703
|
+
*/
|
|
1704
|
+
export default create${pluginName.replace(/-/g, "").replace(/^\w/, (c) => c.toUpperCase())}();
|
|
1705
|
+
`;
|
|
1706
|
+
}
|
|
1707
|
+
if (template === "minimal") {
|
|
1708
|
+
files["src/types.ts"] = `/**
|
|
1709
|
+
* Plugin types
|
|
1710
|
+
*/
|
|
1711
|
+
|
|
1712
|
+
export interface PluginOptions {
|
|
1713
|
+
// Define your plugin options here
|
|
1714
|
+
}
|
|
1715
|
+
`;
|
|
1716
|
+
}
|
|
1717
|
+
files["README.md"] = `# @lytjs/plugin-${pluginName}
|
|
1718
|
+
|
|
1719
|
+
A LytJS plugin.
|
|
1720
|
+
|
|
1721
|
+
## Installation
|
|
1722
|
+
|
|
1723
|
+
\`\`\`bash
|
|
1724
|
+
npm install @lytjs/plugin-${pluginName}
|
|
1725
|
+
\`\`\`
|
|
1726
|
+
|
|
1727
|
+
## Usage
|
|
1728
|
+
|
|
1729
|
+
\`\`\`typescript
|
|
1730
|
+
import { createApp } from '@lytjs/core';
|
|
1731
|
+
import myPlugin from '@lytjs/plugin-${pluginName}';
|
|
1732
|
+
|
|
1733
|
+
const app = createApp(App);
|
|
1734
|
+
app.use(myPlugin);
|
|
1735
|
+
\`\`\`
|
|
1736
|
+
|
|
1737
|
+
## API
|
|
1738
|
+
|
|
1739
|
+
### createPlugin(options?)
|
|
1740
|
+
|
|
1741
|
+
Create the plugin with options.
|
|
1742
|
+
|
|
1743
|
+
## License
|
|
1744
|
+
|
|
1745
|
+
MIT
|
|
1746
|
+
`;
|
|
1747
|
+
files[".gitignore"] = `# Logs
|
|
1748
|
+
logs
|
|
1749
|
+
*.log
|
|
1750
|
+
npm-debug.log*
|
|
1751
|
+
yarn-debug.log*
|
|
1752
|
+
pnpm-debug.log*
|
|
1753
|
+
|
|
1754
|
+
node_modules
|
|
1755
|
+
dist
|
|
1756
|
+
*.local
|
|
1757
|
+
|
|
1758
|
+
# IDE
|
|
1759
|
+
.vscode
|
|
1760
|
+
.idea
|
|
1761
|
+
*.sw?
|
|
1762
|
+
`;
|
|
1763
|
+
return files;
|
|
1764
|
+
}
|
|
1765
|
+
async function createPlugin(name, options = {}) {
|
|
1766
|
+
const targetDir = resolve(process.cwd(), name);
|
|
1767
|
+
const template = options.template || "default";
|
|
1768
|
+
if (template !== "default" && template !== "minimal" && template !== "withConfig") {
|
|
1769
|
+
logger.error(`Unknown template: ${template}`);
|
|
1770
|
+
logger.info("Available templates: default, minimal, withConfig");
|
|
1771
|
+
process.exit(1);
|
|
1772
|
+
}
|
|
1773
|
+
if (existsSync(targetDir) && !isEmptyDir2(targetDir) && !options.force) {
|
|
1774
|
+
logger.error(`Directory "${name}" already exists and is not empty.`);
|
|
1775
|
+
logger.info("Use --force to overwrite.");
|
|
1776
|
+
process.exit(1);
|
|
1777
|
+
}
|
|
1778
|
+
logger.info(`Creating a new LytJS plugin in ${targetDir}...`);
|
|
1779
|
+
ensureDir(targetDir);
|
|
1780
|
+
const files = getTemplateContent(template, name);
|
|
1781
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
1782
|
+
const fullPath = join(targetDir, filePath);
|
|
1783
|
+
const fileDir = resolve(fullPath, "..");
|
|
1784
|
+
if (!existsSync(fileDir)) {
|
|
1785
|
+
mkdirSync(fileDir, { recursive: true });
|
|
1786
|
+
}
|
|
1787
|
+
writeFileSync(fullPath, content, "utf-8");
|
|
1788
|
+
logger.success(`Created: ${filePath}`);
|
|
1789
|
+
}
|
|
1790
|
+
if (!options.skipInstall) {
|
|
1791
|
+
logger.info("Installing dependencies...");
|
|
1792
|
+
try {
|
|
1793
|
+
execSync("pnpm install", { cwd: targetDir, stdio: "inherit" });
|
|
1794
|
+
logger.success("Dependencies installed!");
|
|
1795
|
+
} catch (_error) {
|
|
1796
|
+
logger.warning("Failed to install dependencies automatically.");
|
|
1797
|
+
logger.info('Please run "pnpm install" manually.');
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
logger.success(`Plugin "${name}" created successfully!`);
|
|
1801
|
+
logger.info("");
|
|
1802
|
+
logger.bold("Next steps:");
|
|
1803
|
+
logger.info(` cd ${name}`);
|
|
1804
|
+
logger.info(" pnpm dev # Start development");
|
|
1805
|
+
logger.info(" pnpm build # Build for production");
|
|
1806
|
+
logger.info(" pnpm test # Run tests");
|
|
1807
|
+
}
|
|
1808
|
+
async function buildPlugin(options = {}) {
|
|
1809
|
+
const cwd = process.cwd();
|
|
1810
|
+
const outDir = options.outDir || "dist";
|
|
1811
|
+
const pkgPath = join(cwd, "package.json");
|
|
1812
|
+
if (!existsSync(pkgPath)) {
|
|
1813
|
+
logger.error("No package.json found. Are you in a plugin directory?");
|
|
1814
|
+
process.exit(1);
|
|
1815
|
+
}
|
|
1816
|
+
logger.info("Building plugin...");
|
|
1817
|
+
try {
|
|
1818
|
+
const buildCmd = "tsup";
|
|
1819
|
+
const args = ["--entry", "src/index.ts", "--outDir", outDir, "--format", "esm,cjs", "--dts", "--sourcemap"];
|
|
1820
|
+
if (options.minify) {
|
|
1821
|
+
args.push("--minify");
|
|
1822
|
+
}
|
|
1823
|
+
execSync(`${buildCmd} ${args.join(" ")}`, { cwd, stdio: "inherit" });
|
|
1824
|
+
logger.success("Build completed!");
|
|
1825
|
+
logger.info(`Output: ${join(cwd, outDir)}`);
|
|
1826
|
+
} catch (_error) {
|
|
1827
|
+
logger.error("Build failed. Make sure tsup is installed: pnpm add -D tsup");
|
|
1828
|
+
process.exit(1);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
async function validatePlugin(options = {}) {
|
|
1832
|
+
const cwd = process.cwd();
|
|
1833
|
+
logger.info("Validating plugin...");
|
|
1834
|
+
const errors = [];
|
|
1835
|
+
const warnings = [];
|
|
1836
|
+
const pkgPath = join(cwd, "package.json");
|
|
1837
|
+
if (!existsSync(pkgPath)) {
|
|
1838
|
+
errors.push("package.json not found");
|
|
1839
|
+
} else {
|
|
1840
|
+
try {
|
|
1841
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1842
|
+
if (!pkg.name) errors.push("package.json: name is required");
|
|
1843
|
+
if (!pkg.version) errors.push("package.json: version is required");
|
|
1844
|
+
if (!pkg.main) errors.push("package.json: main is required");
|
|
1845
|
+
if (!pkg.module) errors.push("package.json: module is required");
|
|
1846
|
+
if (!pkg.types) errors.push("package.json: types is required");
|
|
1847
|
+
if (!pkg.exports) {
|
|
1848
|
+
warnings.push("package.json: exports field is recommended");
|
|
1849
|
+
}
|
|
1850
|
+
if (!pkg.peerDependencies || !pkg.peerDependencies["@lytjs/core"]) {
|
|
1851
|
+
errors.push("package.json: @lytjs/core peerDependency is required");
|
|
1852
|
+
}
|
|
1853
|
+
if (!pkg.keywords || !pkg.keywords.includes("lytjs")) {
|
|
1854
|
+
warnings.push('package.json: "lytjs" keyword is recommended');
|
|
1855
|
+
}
|
|
1856
|
+
} catch (err) {
|
|
1857
|
+
errors.push(`package.json: Invalid JSON - ${err}`);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
const srcPath = join(cwd, "src");
|
|
1861
|
+
if (!existsSync(srcPath)) {
|
|
1862
|
+
errors.push("src directory not found");
|
|
1863
|
+
} else {
|
|
1864
|
+
const indexPath = join(srcPath, "index.ts");
|
|
1865
|
+
if (!existsSync(indexPath)) {
|
|
1866
|
+
errors.push("src/index.ts not found");
|
|
1867
|
+
} else {
|
|
1868
|
+
const content = readFileSync(indexPath, "utf-8");
|
|
1869
|
+
if (!content.includes("definePlugin") && !content.includes("EnhancedPlugin")) {
|
|
1870
|
+
warnings.push("src/index.ts: Consider using definePlugin or EnhancedPlugin");
|
|
1871
|
+
}
|
|
1872
|
+
if (!content.includes("export")) {
|
|
1873
|
+
warnings.push("src/index.ts: No exports found");
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
const tsconfigPath = join(cwd, "tsconfig.json");
|
|
1878
|
+
if (!existsSync(tsconfigPath)) {
|
|
1879
|
+
warnings.push("tsconfig.json not found (recommended)");
|
|
1880
|
+
}
|
|
1881
|
+
const tsupConfigPath = join(cwd, "tsup.config.ts");
|
|
1882
|
+
if (!existsSync(tsupConfigPath)) {
|
|
1883
|
+
warnings.push("tsup.config.ts not found (recommended for builds)");
|
|
1884
|
+
}
|
|
1885
|
+
let hasErrors = errors.length > 0;
|
|
1886
|
+
let hasWarnings = warnings.length > 0;
|
|
1887
|
+
if (hasErrors) {
|
|
1888
|
+
logger.error("Validation failed with errors:");
|
|
1889
|
+
for (const err of errors) {
|
|
1890
|
+
logger.error(` - ${err}`);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
if (hasWarnings && !options.strict) {
|
|
1894
|
+
logger.warning("Warnings:");
|
|
1895
|
+
for (const warn of warnings) {
|
|
1896
|
+
logger.warning(` - ${warn}`);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
if (!hasErrors && !hasWarnings) {
|
|
1900
|
+
logger.success("Plugin validation passed!");
|
|
1901
|
+
} else if (hasWarnings && !hasErrors) {
|
|
1902
|
+
logger.success("Plugin validation passed (with warnings)");
|
|
1903
|
+
if (options.strict) {
|
|
1904
|
+
process.exit(1);
|
|
1905
|
+
}
|
|
1906
|
+
} else {
|
|
1907
|
+
if (options.warningsAsErrors && hasWarnings) {
|
|
1908
|
+
logger.error("Strict mode: treating warnings as errors");
|
|
1909
|
+
}
|
|
1910
|
+
process.exit(1);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
function isEmptyDir2(dir) {
|
|
1914
|
+
if (!existsSync(dir)) return true;
|
|
1915
|
+
const files = __require("fs").readdirSync(dir);
|
|
1916
|
+
return files.length === 0;
|
|
1917
|
+
}
|
|
1918
|
+
function listPluginTemplates() {
|
|
1919
|
+
logger.bold("Available plugin templates:");
|
|
1920
|
+
for (const [name, description] of Object.entries(PLUGIN_TEMPLATES)) {
|
|
1921
|
+
logger.info(` ${name.padEnd(12)} - ${description}`);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
// src/commands/run.ts
|
|
1926
|
+
var VERSION = "6.0.0";
|
|
1927
|
+
async function runCli(rawArgs = process.argv.slice(2)) {
|
|
1928
|
+
const { command, args, options } = parseArgs(rawArgs);
|
|
1929
|
+
if (options.help || command === "help") {
|
|
1930
|
+
showHelp();
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
if (options.version || command === "version" || command === "-v" || command === "--version") {
|
|
1934
|
+
console.log(`LytJS CLI v${VERSION}`);
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
switch (command) {
|
|
1938
|
+
case "create":
|
|
1939
|
+
await create(args[0] || "my-lytjs-app", {
|
|
1940
|
+
template: options.template,
|
|
1941
|
+
force: options.force
|
|
1942
|
+
});
|
|
1943
|
+
break;
|
|
1944
|
+
case "templates":
|
|
1945
|
+
listTemplates();
|
|
1946
|
+
break;
|
|
1947
|
+
case "dev":
|
|
1948
|
+
await dev({
|
|
1949
|
+
port: options.port ? parseInt(options.port, 10) : void 0,
|
|
1950
|
+
host: options.host,
|
|
1951
|
+
open: options.open
|
|
1952
|
+
});
|
|
1953
|
+
break;
|
|
1954
|
+
case "build":
|
|
1955
|
+
await build({
|
|
1956
|
+
outDir: options.outDir,
|
|
1957
|
+
ssr: options.ssr,
|
|
1958
|
+
minify: options.minify !== "false"
|
|
1959
|
+
});
|
|
1960
|
+
break;
|
|
1961
|
+
case "test":
|
|
1962
|
+
await test({
|
|
1963
|
+
watch: options.watch !== "false",
|
|
1964
|
+
coverage: options.coverage,
|
|
1965
|
+
grep: options.grep
|
|
1966
|
+
});
|
|
1967
|
+
break;
|
|
1968
|
+
case "add":
|
|
1969
|
+
const addTypes = ["component", "page", "store", "directive", "composable", "util", "middleware", "hook"];
|
|
1970
|
+
if (!args[0] || !addTypes.includes(args[0])) {
|
|
1971
|
+
logger.error("Usage: lyt add <type> <name>");
|
|
1972
|
+
logger.info("Types: component, page, store, directive, composable, util, middleware, hook");
|
|
1973
|
+
logger.info("Example: lyt add component Button");
|
|
1974
|
+
process.exit(1);
|
|
1975
|
+
}
|
|
1976
|
+
await add(args[0], args[1] || "Unnamed", {
|
|
1977
|
+
force: options.force
|
|
1978
|
+
});
|
|
1979
|
+
break;
|
|
1980
|
+
case "generate":
|
|
1981
|
+
case "g":
|
|
1982
|
+
const genTypes = ["component", "page", "service", "hook", "store"];
|
|
1983
|
+
if (!args[0] || !genTypes.includes(args[0])) {
|
|
1984
|
+
logger.error("Usage: lyt generate <type> <name>");
|
|
1985
|
+
logger.info("Types: component, page, service, hook, store");
|
|
1986
|
+
logger.info("Example: lyt generate component Button");
|
|
1987
|
+
process.exit(1);
|
|
1988
|
+
}
|
|
1989
|
+
await generate({
|
|
1990
|
+
type: args[0],
|
|
1991
|
+
name: args[1] || "Unnamed",
|
|
1992
|
+
path: options.path,
|
|
1993
|
+
withStyles: options.styles,
|
|
1994
|
+
withTest: options.test,
|
|
1995
|
+
withStorybook: options.storybook
|
|
1996
|
+
});
|
|
1997
|
+
break;
|
|
1998
|
+
case "plugin":
|
|
1999
|
+
if (!args[0]) {
|
|
2000
|
+
logger.error("Usage: lyt plugin <create|build|validate|templates>");
|
|
2001
|
+
logger.info("Example: lyt plugin create my-plugin");
|
|
2002
|
+
process.exit(1);
|
|
2003
|
+
}
|
|
2004
|
+
const subCommand = args[0];
|
|
2005
|
+
switch (subCommand) {
|
|
2006
|
+
case "create":
|
|
2007
|
+
await createPlugin(args[1] || "my-plugin", {
|
|
2008
|
+
template: options.template,
|
|
2009
|
+
force: options.force,
|
|
2010
|
+
skipInstall: options.skipInstall
|
|
2011
|
+
});
|
|
2012
|
+
break;
|
|
2013
|
+
case "build":
|
|
2014
|
+
await buildPlugin({
|
|
2015
|
+
outDir: options.outDir,
|
|
2016
|
+
minify: options.minify,
|
|
2017
|
+
sourcemap: options.sourcemap
|
|
2018
|
+
});
|
|
2019
|
+
break;
|
|
2020
|
+
case "validate":
|
|
2021
|
+
await validatePlugin({
|
|
2022
|
+
strict: options.strict,
|
|
2023
|
+
warningsAsErrors: options.warningsAsErrors
|
|
2024
|
+
});
|
|
2025
|
+
break;
|
|
2026
|
+
case "templates":
|
|
2027
|
+
listPluginTemplates();
|
|
2028
|
+
break;
|
|
2029
|
+
default:
|
|
2030
|
+
logger.error(`Unknown plugin sub-command: ${subCommand}`);
|
|
2031
|
+
logger.info("Supported sub-commands: create, build, validate, templates");
|
|
2032
|
+
process.exit(1);
|
|
2033
|
+
}
|
|
2034
|
+
break;
|
|
2035
|
+
default:
|
|
2036
|
+
if (command) {
|
|
2037
|
+
logger.error(`Unknown command: ${command}`);
|
|
2038
|
+
logger.info('Run "lyt --help" for usage information.');
|
|
2039
|
+
process.exit(1);
|
|
2040
|
+
} else {
|
|
2041
|
+
showHelp();
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
function parseArgs(args) {
|
|
2046
|
+
let command = args[0] ?? "";
|
|
2047
|
+
const positional = [];
|
|
2048
|
+
const options = {};
|
|
2049
|
+
if (command.startsWith("--") || command.startsWith("-")) {
|
|
2050
|
+
command = "";
|
|
2051
|
+
}
|
|
2052
|
+
const startIndex = command ? 1 : 0;
|
|
2053
|
+
for (let i = startIndex; i < args.length; i++) {
|
|
2054
|
+
const arg = args[i] ?? "";
|
|
2055
|
+
if (arg.startsWith("--")) {
|
|
2056
|
+
const parts = arg.slice(2).split("=");
|
|
2057
|
+
const key = parts[0];
|
|
2058
|
+
const value = parts[1];
|
|
2059
|
+
if (value !== void 0 && key) {
|
|
2060
|
+
options[key] = value;
|
|
2061
|
+
} else if (key && i + 1 < args.length && !(args[i + 1] ?? "").startsWith("-")) {
|
|
2062
|
+
const nextArg = args[++i];
|
|
2063
|
+
if (nextArg) {
|
|
2064
|
+
options[key] = nextArg;
|
|
2065
|
+
}
|
|
2066
|
+
} else if (key) {
|
|
2067
|
+
options[key] = true;
|
|
2068
|
+
}
|
|
2069
|
+
} else if (arg.startsWith("-")) {
|
|
2070
|
+
const key = arg.slice(1);
|
|
2071
|
+
if (key) {
|
|
2072
|
+
options[key] = true;
|
|
2073
|
+
}
|
|
2074
|
+
} else {
|
|
2075
|
+
positional.push(arg);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
return { command, args: positional, options };
|
|
2079
|
+
}
|
|
2080
|
+
function showHelp() {
|
|
2081
|
+
console.log(`
|
|
2082
|
+
${logger.bold("LytJS CLI")} v${VERSION}
|
|
2083
|
+
|
|
2084
|
+
${logger.bold("Usage:")}
|
|
2085
|
+
lyt <command> [options]
|
|
2086
|
+
|
|
2087
|
+
${logger.bold("Commands:")}
|
|
2088
|
+
create <name> Create a new LytJS project
|
|
2089
|
+
templates List available templates
|
|
2090
|
+
dev Start development server
|
|
2091
|
+
build Build for production
|
|
2092
|
+
test Run tests
|
|
2093
|
+
add <type> <name> Generate a component, page, store, directive, composable, etc.
|
|
2094
|
+
generate, g Advanced code generation (component, page, service, hook, store)
|
|
2095
|
+
plugin <subcmd> Plugin development commands
|
|
2096
|
+
help Show this help message
|
|
2097
|
+
|
|
2098
|
+
${logger.bold("Options:")}
|
|
2099
|
+
--version, -v Show version number
|
|
2100
|
+
--help Show help
|
|
2101
|
+
|
|
2102
|
+
${logger.bold("Create Options:")}
|
|
2103
|
+
--template <name> Use a specific template
|
|
2104
|
+
--force Overwrite existing directory
|
|
2105
|
+
|
|
2106
|
+
${logger.bold("Dev Options:")}
|
|
2107
|
+
--port <number> Specify port (default: 5173)
|
|
2108
|
+
--host <host> Specify host (default: localhost)
|
|
2109
|
+
--open Open browser on start
|
|
2110
|
+
|
|
2111
|
+
${logger.bold("Build Options:")}
|
|
2112
|
+
--outDir <dir> Output directory (default: dist)
|
|
2113
|
+
--ssr Build for SSR
|
|
2114
|
+
--minify false Disable minification
|
|
2115
|
+
|
|
2116
|
+
${logger.bold("Test Options:")}
|
|
2117
|
+
--watch false Run tests once (no watch mode)
|
|
2118
|
+
--coverage Generate coverage report
|
|
2119
|
+
--grep <pattern> Filter tests by pattern
|
|
2120
|
+
|
|
2121
|
+
${logger.bold("Generate Options:")}
|
|
2122
|
+
--path <dir> Output directory (default: ./src)
|
|
2123
|
+
--styles Generate CSS styles file
|
|
2124
|
+
--test Generate test file
|
|
2125
|
+
--storybook Generate Storybook story file
|
|
2126
|
+
|
|
2127
|
+
${logger.bold("Plugin Options:")}
|
|
2128
|
+
--template <name> Use a specific plugin template (default, minimal, withConfig)
|
|
2129
|
+
--force Overwrite existing directory
|
|
2130
|
+
--skipInstall Skip installing dependencies
|
|
2131
|
+
--outDir <dir> Output directory (default: dist)
|
|
2132
|
+
--minify Minify output
|
|
2133
|
+
--sourcemap Generate sourcemaps
|
|
2134
|
+
--strict Strict validation mode
|
|
2135
|
+
--warningsAsErrors Treat warnings as errors
|
|
2136
|
+
|
|
2137
|
+
${logger.bold("Examples:")}
|
|
2138
|
+
lyt create my-app
|
|
2139
|
+
lyt create my-app --template minimal
|
|
2140
|
+
lyt create my-app --template router
|
|
2141
|
+
lyt create my-app --template full
|
|
2142
|
+
lyt dev --port 3000
|
|
2143
|
+
lyt build --ssr
|
|
2144
|
+
lyt add component Button
|
|
2145
|
+
lyt add page About
|
|
2146
|
+
lyt add store user
|
|
2147
|
+
lyt directive click-outside
|
|
2148
|
+
lyt add composable fetch-data
|
|
2149
|
+
lyt add util format
|
|
2150
|
+
lyt add hook window-size
|
|
2151
|
+
lyt generate component Button --styles --test
|
|
2152
|
+
lyt generate page Dashboard --path ./src/pages
|
|
2153
|
+
lyt generate service User --path ./src/services
|
|
2154
|
+
lyt generate hook useCounter
|
|
2155
|
+
lyt generate store Auth --path ./src/stores
|
|
2156
|
+
lyt g page Login
|
|
2157
|
+
lyt plugin create my-plugin
|
|
2158
|
+
lyt plugin create my-plugin --template withConfig
|
|
2159
|
+
lyt plugin build
|
|
2160
|
+
lyt plugin validate
|
|
2161
|
+
`);
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
// src/index.ts
|
|
2165
|
+
if (__require.main === module) {
|
|
2166
|
+
runCli().catch(console.error);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
export { add, build, buildPlugin, create, createPlugin, detectPackageManager, dev, ensureDir, exists, generate, getAddCommand, getInstallCommand, getRunCommand, listPluginTemplates, listTemplates, logger, readFile, runCli, test, validatePlugin, writeFile };
|
|
2170
|
+
//# sourceMappingURL=lyt.mjs.map
|
|
2171
|
+
//# sourceMappingURL=lyt.mjs.map
|