@lytjs/cli 6.4.0 → 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/lyt.cjs CHANGED
@@ -5,6 +5,26 @@ var fs = require('fs');
5
5
  var path = require('path');
6
6
  var child_process = require('child_process');
7
7
 
8
+ function _interopNamespace(e) {
9
+ if (e && e.__esModule) return e;
10
+ var n = Object.create(null);
11
+ if (e) {
12
+ Object.keys(e).forEach(function (k) {
13
+ if (k !== 'default') {
14
+ var d = Object.getOwnPropertyDescriptor(e, k);
15
+ Object.defineProperty(n, k, d.get ? d : {
16
+ enumerable: true,
17
+ get: function () { return e[k]; }
18
+ });
19
+ }
20
+ });
21
+ }
22
+ n.default = e;
23
+ return Object.freeze(n);
24
+ }
25
+
26
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
27
+
8
28
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
29
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
30
  }) : x)(function(x) {
@@ -62,8 +82,8 @@ function writeFile(filePath, content) {
62
82
  function readFile(filePath) {
63
83
  return fs.readFileSync(filePath, "utf-8");
64
84
  }
65
- function exists(path) {
66
- return fs.existsSync(path);
85
+ function exists(path2) {
86
+ return fs.existsSync(path2);
67
87
  }
68
88
  function isEmptyDir(dir) {
69
89
  if (!fs.existsSync(dir)) return true;
@@ -443,12 +463,19 @@ export const useCounterStore = defineStore('counter', () => {
443
463
  writeFile(path.join(targetDir, "src", "stores", "counter.ts"), counterStore);
444
464
  }
445
465
  if (isSsr) {
446
- const entryServer = `import { createSSRApp } from '@lytjs/core';
466
+ const entryServer = `import { createSSRApp, h } from '@lytjs/core';
467
+ import { renderToString } from '@lytjs/ssr';
447
468
  import App from './App.lyt';
448
469
 
449
470
  export async function render(url: string) {
450
- const app = createSSRApp(App);
451
- return app;
471
+ const app = createSSRApp({
472
+ render() {
473
+ return h(App);
474
+ }
475
+ });
476
+
477
+ const html = await renderToString(app);
478
+ return html;
452
479
  }
453
480
  `;
454
481
  writeFile(path.join(targetDir, "src/entry-server.ts"), entryServer);
@@ -462,36 +489,114 @@ app.mount('#app');
462
489
  const serverTs = `/**
463
490
  * LytJS SSR Server
464
491
  *
465
- * A minimal SSR server for development and production.
492
+ * Complete SSR server with Vite dev server and production build support.
493
+ * Supports streaming SSR, route prefetching, and static file serving.
466
494
  */
467
495
 
468
496
  import fs from 'fs';
469
497
  import path from 'path';
470
498
  import { fileURLToPath } from 'url';
499
+ import http from 'http';
471
500
 
472
501
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
473
502
  const isProduction = process.env.NODE_ENV === 'production';
503
+ const DIST_DIR = path.join(__dirname, 'dist');
504
+ const PORT = parseInt(process.env.PORT || '3000', 10);
505
+
506
+ interface RenderOptions {
507
+ url: string;
508
+ template: string;
509
+ manifest?: Record<string, string[]>;
510
+ }
511
+
512
+ async function renderPage({ url, template, manifest }: RenderOptions): Promise<string> {
513
+ let app: any;
514
+ let vite: any;
515
+
516
+ if (!isProduction) {
517
+ const { createServer: createViteServer } = await import('vite');
518
+ vite = await createViteServer({
519
+ server: { middlewareMode: true },
520
+ appType: 'custom',
521
+ });
522
+ app = (await vite.ssrLoadModule(path.join(__dirname, 'src/entry-server.ts'))).default;
523
+ } else {
524
+ app = (await import(path.join(DIST_DIR, 'server/entry-server.js'))).default;
525
+ }
526
+
527
+ const html = await app.render(url);
528
+ return template.replace('<!--app-html-->', html);
529
+ }
474
530
 
475
531
  async function createServer() {
476
- let resolve: any;
477
532
  let vite: any;
478
533
 
534
+ // Load index.html template
535
+ let template: string;
536
+
479
537
  if (!isProduction) {
480
538
  const { createServer: createViteServer } = await import('vite');
481
539
  vite = await createViteServer({
482
540
  server: { middlewareMode: true },
483
541
  appType: 'custom',
484
542
  });
485
- resolve = (id: string) => vite.resolveUrl(id);
543
+ template = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf-8');
486
544
  } else {
487
- resolve = (id: string) => id;
545
+ template = fs.readFileSync(path.join(DIST_DIR, 'client/index.html'), 'utf-8');
488
546
  }
489
547
 
490
- // TODO: Set up express/polka server and SSR rendering
491
- console.log('LytJS SSR server starting...');
548
+ const server = http.createServer(async (req, res) => {
549
+ const url = req.url || '/';
550
+
551
+ try {
552
+ if (!isProduction && url.startsWith('/@')) {
553
+ // Vite dev server requests
554
+ return;
555
+ }
556
+
557
+ // Static assets
558
+ if (url.startsWith('/assets/') || url.endsWith('.js') || url.endsWith('.css')) {
559
+ const filePath = isProduction
560
+ ? path.join(DIST_DIR, 'client', url)
561
+ : path.join(__dirname, url);
562
+
563
+ if (fs.existsSync(filePath)) {
564
+ const ext = path.extname(filePath);
565
+ const contentType = ext === '.css' ? 'text/css' : 'application/javascript';
566
+ res.writeHead(200, { 'Content-Type': contentType });
567
+ res.end(fs.readFileSync(filePath));
568
+ return;
569
+ }
570
+ }
571
+
572
+ // SSR rendering
573
+ const html = await renderPage({
574
+ url,
575
+ template,
576
+ manifest: isProduction
577
+ ? JSON.parse(fs.readFileSync(path.join(DIST_DIR, 'client/ssr-manifest.json'), 'utf-8'))
578
+ : undefined
579
+ });
580
+
581
+ res.writeHead(200, { 'Content-Type': 'text/html' });
582
+ res.end(html);
583
+ } catch (err: any) {
584
+ if (!isProduction && vite) {
585
+ vite.ssrFixStacktrace(err);
586
+ }
587
+ console.error('SSR Error:', err);
588
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
589
+ res.end('Internal Server Error');
590
+ }
591
+ });
592
+
593
+ server.listen(PORT, () => {
594
+ console.log(\`LytJS SSR server running at http://localhost:\${PORT}\`);
595
+ console.log(\`Mode: \${isProduction ? 'Production' : 'Development'}\`);
596
+ });
492
597
  }
493
598
 
494
- createServer();
599
+ createServer().catch(console.error);
495
600
  `;
496
601
  writeFile(path.join(targetDir, "server.ts"), serverTs);
497
602
  }
@@ -668,12 +773,13 @@ defineEmits<{
668
773
  }];
669
774
  },
670
775
  page(name, basePath) {
776
+ const pascalName = toPascalCase(name);
671
777
  const filePath = path.join(basePath, `${name}.lyt`);
672
778
  return [{
673
779
  filePath,
674
780
  content: `<template>
675
781
  <div class="page-${name}">
676
- <h1>${toPascalCase(name)}</h1>
782
+ <h1>${pascalName}</h1>
677
783
  </div>
678
784
  </template>
679
785
 
@@ -724,6 +830,157 @@ export const use${toPascalCase(name)}Store = defineStore('${name}', () => {
724
830
  reset,
725
831
  };
726
832
  });
833
+ `
834
+ }];
835
+ },
836
+ directive(name, basePath) {
837
+ const filePath = path.join(basePath, `${name}.ts`);
838
+ const camelCaseName = toCamelCase(name);
839
+ return [{
840
+ filePath,
841
+ content: `import type { Directive } from '@lytjs/core';
842
+
843
+ /**
844
+ * ${toPascalCase(name)} Directive
845
+ *
846
+ * @example
847
+ * \`\`\`vue
848
+ * <div v-${camelCaseName} />
849
+ * \`\`\`
850
+ */
851
+ export const v${toPascalCase(name)}: Directive = {
852
+ mounted(el, binding) {
853
+ // Directive mounted
854
+ },
855
+
856
+ updated(el, binding) {
857
+ // Directive updated
858
+ },
859
+
860
+ unmounted(el) {
861
+ // Directive unmounted
862
+ },
863
+ };
864
+ `
865
+ }];
866
+ },
867
+ composable(name, basePath) {
868
+ const filePath = path.join(basePath, `use${toPascalCase(name)}.ts`);
869
+ return [{
870
+ filePath,
871
+ content: `import { signal, computed } from '@lytjs/reactivity';
872
+
873
+ /**
874
+ * ${toPascalCase(name)} Composable
875
+ *
876
+ * @example
877
+ * \`\`\`typescript
878
+ * const { state, actions } = use${toPascalCase(name)}();
879
+ * \`\`\`
880
+ */
881
+ export function use${toPascalCase(name)}() {
882
+ // State
883
+ const isLoading = signal(false);
884
+ const error = signal<Error | null>(null);
885
+ const data = signal<any>(null);
886
+
887
+ // Computed
888
+ const hasData = computed(() => data.value !== null);
889
+
890
+ // Actions
891
+ async function fetch() {
892
+ isLoading.value = true;
893
+ error.value = null;
894
+ try {
895
+ // TODO: Fetch logic here
896
+ // data.value = await someApi();
897
+ } catch (e) {
898
+ error.value = e as Error;
899
+ } finally {
900
+ isLoading.value = false;
901
+ }
902
+ }
903
+
904
+ function reset() {
905
+ isLoading.value = false;
906
+ error.value = null;
907
+ data.value = null;
908
+ }
909
+
910
+ return {
911
+ isLoading,
912
+ error,
913
+ data,
914
+ hasData,
915
+ fetch,
916
+ reset,
917
+ };
918
+ }
919
+ `
920
+ }];
921
+ },
922
+ hook(name, basePath) {
923
+ const filePath = path.join(basePath, `use${toPascalCase(name)}.ts`);
924
+ return [{
925
+ filePath,
926
+ content: `import { signal, onMounted, onUnmounted } from '@lytjs/core';
927
+
928
+ /**
929
+ * ${toPascalCase(name)} Hook
930
+ */
931
+ export function use${toPascalCase(name)}() {
932
+ const state = signal(null);
933
+
934
+ onMounted(() => {
935
+ // Setup code on mount
936
+ });
937
+
938
+ onUnmounted(() => {
939
+ // Cleanup on unmount
940
+ });
941
+
942
+ return {
943
+ state,
944
+ };
945
+ }
946
+ `
947
+ }];
948
+ },
949
+ util(name, basePath) {
950
+ const filePath = path.join(basePath, `${name}.ts`);
951
+ return [{
952
+ filePath,
953
+ content: `/**
954
+ * ${toPascalCase(name)} Utility Functions
955
+ */
956
+
957
+ /**
958
+ * ${toPascalCase(name)} function
959
+ *
960
+ * @param input - The input value
961
+ * @returns The processed result
962
+ */
963
+ export function ${toCamelCase(name)}(input: any) {
964
+ // TODO: Implement function
965
+ return input;
966
+ }
967
+ `
968
+ }];
969
+ },
970
+ middleware(name, basePath) {
971
+ const filePath = path.join(basePath, `${name}.ts`);
972
+ return [{
973
+ filePath,
974
+ content: `import type { NavigationGuard } from '@lytjs/router';
975
+
976
+ /**
977
+ * ${toPascalCase(name)} Middleware
978
+ */
979
+ export const ${toCamelCase(name)}Middleware: NavigationGuard = (to, from, next) => {
980
+ // Middleware logic
981
+ console.log('Middleware:', to.path);
982
+ next();
983
+ };
727
984
  `
728
985
  }];
729
986
  }
@@ -731,6 +988,10 @@ export const use${toPascalCase(name)}Store = defineStore('${name}', () => {
731
988
  function toPascalCase(str) {
732
989
  return str.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
733
990
  }
991
+ function toCamelCase(str) {
992
+ const pascalCase = toPascalCase(str);
993
+ return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
994
+ }
734
995
  function resolveTargetDir(type) {
735
996
  const cwd = process.cwd();
736
997
  switch (type) {
@@ -740,6 +1001,16 @@ function resolveTargetDir(type) {
740
1001
  return path.join(cwd, "src", "pages");
741
1002
  case "store":
742
1003
  return path.join(cwd, "src", "stores");
1004
+ case "directive":
1005
+ return path.join(cwd, "src", "directives");
1006
+ case "composable":
1007
+ return path.join(cwd, "src", "composables");
1008
+ case "util":
1009
+ return path.join(cwd, "src", "utils");
1010
+ case "middleware":
1011
+ return path.join(cwd, "src", "middleware");
1012
+ case "hook":
1013
+ return path.join(cwd, "src", "hooks");
743
1014
  }
744
1015
  }
745
1016
  async function add(type, name, options = {}) {
@@ -766,6 +1037,548 @@ async function add(type, name, options = {}) {
766
1037
  logger.success(`Created ${type}: ${file.filePath}`);
767
1038
  }
768
1039
  }
1040
+ var TEMPLATES3 = {
1041
+ component: (data, withStyles, withTest, template, lang) => {
1042
+ const styleImport = withStyles ? `
1043
+ import './${data.kebabName}.styles.css';` : "";
1044
+ const tsOnly = lang === "ts";
1045
+ if (template === "sfc") {
1046
+ return `<template>
1047
+ <div class="${data.kebabName}">
1048
+ <slot>
1049
+ ${data.pascalName} Component
1050
+ </slot>
1051
+ </div>
1052
+ </template>
1053
+
1054
+ <script setup${tsOnly ? ' lang="ts"' : ""}>
1055
+ ${tsOnly ? `import { ref } from '@lytjs/reactivity';
1056
+ ` : ""}
1057
+ ${tsOnly ? `
1058
+ export interface ${data.pascalName}Props {
1059
+ className?: string;
1060
+ }
1061
+
1062
+ const props = defineProps<${data.pascalName}Props>();
1063
+ ` : ""}
1064
+
1065
+ const title = ref('${data.pascalName}');
1066
+ </script>
1067
+
1068
+ <style scoped>
1069
+ .${data.kebabName} {
1070
+ /* Component styles */
1071
+ }
1072
+ </style>
1073
+ `;
1074
+ }
1075
+ const testImport = withTest ? `
1076
+ import { describe, it, expect } from 'vitest';
1077
+ import { ${data.pascalName} } from './${data.kebabName}';
1078
+
1079
+ describe('${data.pascalName}', () => {
1080
+ it('should render', () => {
1081
+ // Add test here
1082
+ expect(true).toBe(true);
1083
+ });
1084
+ });` : "";
1085
+ const propsDecl = tsOnly ? `['className', 'children']` : `[]`;
1086
+ return `/**
1087
+ * ${data.pascalName} \u7EC4\u4EF6
1088
+ *
1089
+ * @description ${data.description}
1090
+ * @created ${data.date}
1091
+ */
1092
+
1093
+ import { h, defineComponent } from '@lytjs/core';${styleImport}
1094
+
1095
+ ${tsOnly ? `export interface ${data.pascalName}Props {
1096
+ className?: string;
1097
+ children?: any;
1098
+ }
1099
+
1100
+ ` : ""}${template === "functional" ? `export function ${data.pascalName}(${tsOnly ? `props: ${data.pascalName}Props` : "props"}) {
1101
+ const { className = '', children } = props;
1102
+
1103
+ return (
1104
+ <div className={\`${data.kebabName} \${className}\`}>
1105
+ {children || '${data.pascalName} Component'}
1106
+ </div>
1107
+ );
1108
+ }` : `export const ${data.pascalName} = defineComponent({
1109
+ name: '${data.pascalName}',
1110
+ props: ${propsDecl},
1111
+ setup(props) {
1112
+ const { className = '', children } = props;
1113
+
1114
+ return () => (
1115
+ <div className={\`${data.kebabName} \${className}\`}>
1116
+ {children || '${data.pascalName} Component'}
1117
+ </div>
1118
+ );
1119
+ },
1120
+ });`}
1121
+
1122
+ export default ${data.pascalName};${testImport}
1123
+ `;
1124
+ },
1125
+ page: (data, withStyles, withTest, template, lang) => {
1126
+ const styleImport = withStyles ? `
1127
+ import './${data.kebabName}.styles.css';` : "";
1128
+ const tsOnly = lang === "ts";
1129
+ if (template === "sfc") {
1130
+ return `<template>
1131
+ <div class="${data.kebabName}-page">
1132
+ <h1>{title}</h1>
1133
+ <p>Page content for ${data.pascalName}</p>
1134
+ <slot />
1135
+ </div>
1136
+ </template>
1137
+
1138
+ <script setup${tsOnly ? ' lang="ts"' : ""}>
1139
+ ${tsOnly ? `import { ref } from '@lytjs/reactivity';
1140
+ ` : ""}
1141
+ ${tsOnly ? `
1142
+ export interface ${data.pascalName}PageProps {
1143
+ title?: string;
1144
+ }
1145
+
1146
+ const props = defineProps<${data.pascalName}PageProps>();
1147
+ ` : ""}
1148
+
1149
+ const title = ref(props.title || '${data.pascalName}');
1150
+ </script>
1151
+
1152
+ <style scoped>
1153
+ .${data.kebabName}-page {
1154
+ padding: 2rem;
1155
+ }
1156
+ </style>
1157
+ `;
1158
+ }
1159
+ const testImport = withTest ? `
1160
+ import { describe, it, expect } from 'vitest';
1161
+ import { ${data.pascalName}Page } from './${data.kebabName}';
1162
+
1163
+ describe('${data.pascalName}Page', () => {
1164
+ it('should render', () => {
1165
+ // Add test here
1166
+ expect(true).toBe(true);
1167
+ });
1168
+ });` : "";
1169
+ return `/**
1170
+ * ${data.pascalName} \u9875\u9762
1171
+ *
1172
+ * @description ${data.description}
1173
+ * @created ${data.date}
1174
+ */
1175
+
1176
+ import { h, ${tsOnly ? "signal" : "signal"} } from '@lytjs/core';
1177
+ ${styleImport}
1178
+
1179
+ ${tsOnly ? `export interface ${data.pascalName}PageProps {
1180
+ title?: string;
1181
+ }
1182
+
1183
+ ` : ""}export function ${data.pascalName}Page(${tsOnly ? `props: ${data.pascalName}PageProps` : "props"}) {
1184
+ const { title = '${data.pascalName}' } = props;
1185
+
1186
+ return (
1187
+ <div className="${data.kebabName}-page">
1188
+ <h1>{title}</h1>
1189
+ <p>Page content for ${data.pascalName}</p>
1190
+ </div>
1191
+ );
1192
+ }
1193
+
1194
+ export default ${data.pascalName}Page;${testImport}
1195
+ `;
1196
+ },
1197
+ service: (data, _withStyles, _withTest, _template, lang) => {
1198
+ const tsOnly = lang === "ts";
1199
+ return `/**
1200
+ * ${data.pascalName} \u670D\u52A1
1201
+ *
1202
+ * @description ${data.description}
1203
+ * @created ${data.date}
1204
+ */
1205
+
1206
+ ${tsOnly ? `export interface ${data.pascalName}ServiceOptions {
1207
+ baseUrl?: string;
1208
+ timeout?: number;
1209
+ }
1210
+
1211
+ ` : ""}${tsOnly ? `export class ${data.pascalName}Service {
1212
+ private baseUrl: string;
1213
+ private timeout: number;
1214
+ ` : `export class ${data.pascalName}Service {
1215
+ `}
1216
+ constructor(${tsOnly ? `options: ${data.pascalName}ServiceOptions = {}` : "options = {}"}) {
1217
+ this.baseUrl = options.baseUrl || '/api';
1218
+ this.timeout = options.timeout || 30000;
1219
+ }
1220
+
1221
+ async getAll()${tsOnly ? ": Promise<any[]>" : ""} {
1222
+ const response = await fetch(\`\${this.baseUrl}/${data.kebabName}s\`, {
1223
+ method: 'GET',
1224
+ headers: { 'Content-Type': 'application/json' },
1225
+ signal: AbortSignal.timeout(this.timeout),
1226
+ });
1227
+ return response.json();
1228
+ }
1229
+
1230
+ async getById(id)${tsOnly ? ": Promise<any>" : ""} {
1231
+ const response = await fetch(\`\${this.baseUrl}/${data.kebabName}s/\${id}\`, {
1232
+ method: 'GET',
1233
+ headers: { 'Content-Type': 'application/json' },
1234
+ signal: AbortSignal.timeout(this.timeout),
1235
+ });
1236
+ return response.json();
1237
+ }
1238
+
1239
+ async create(${tsOnly ? "data: any" : "data"})${tsOnly ? ": Promise<any>" : ""} {
1240
+ const response = await fetch(\`\${this.baseUrl}/${data.kebabName}s\`, {
1241
+ method: 'POST',
1242
+ headers: { 'Content-Type': 'application/json' },
1243
+ body: JSON.stringify(data),
1244
+ signal: AbortSignal.timeout(this.timeout),
1245
+ });
1246
+ return response.json();
1247
+ }
1248
+
1249
+ async update(id)${tsOnly ? ": Promise<any>" : ""} {
1250
+ const response = await fetch(\`\${this.baseUrl}/${data.kebabName}s/\${id}\`, {
1251
+ method: 'PUT',
1252
+ headers: { 'Content-Type': 'application/json' },
1253
+ body: JSON.stringify(data),
1254
+ signal: AbortSignal.timeout(this.timeout),
1255
+ });
1256
+ return response.json();
1257
+ }
1258
+
1259
+ async delete(id)${tsOnly ? ": Promise<void>" : ""} {
1260
+ await fetch(\`\${this.baseUrl}/${data.kebabName}s/\${id}\`, {
1261
+ method: 'DELETE',
1262
+ signal: AbortSignal.timeout(this.timeout),
1263
+ });
1264
+ }
1265
+ }
1266
+
1267
+ export default ${data.pascalName}Service;
1268
+ `;
1269
+ },
1270
+ hook: (data, _withStyles, _withTest, _template, lang) => {
1271
+ const tsOnly = lang === "ts";
1272
+ return `/**
1273
+ * ${data.pascalName} Hook
1274
+ *
1275
+ * @description ${data.description}
1276
+ * @created ${data.date}
1277
+ */
1278
+
1279
+ import { signal, effect } from '@lytjs/reactivity';
1280
+
1281
+ ${tsOnly ? `export interface ${data.pascalName}Options {
1282
+ immediate?: boolean;
1283
+ }
1284
+
1285
+ export interface ${data.pascalName}Return {
1286
+ data: ReturnType<typeof signal>;
1287
+ loading: ReturnType<typeof signal>;
1288
+ error: ReturnType<typeof signal>;
1289
+ execute: () => Promise<void>;
1290
+ reset: () => void;
1291
+ }
1292
+
1293
+ ` : ""}export function use${data.pascalName}(${tsOnly ? `options: ${data.pascalName}Options = {}` : "options = {}"})${tsOnly ? `: ${data.pascalName}Return` : ""} {
1294
+ const { immediate = false } = options;
1295
+
1296
+ const data = signal<any>(null);
1297
+ const loading = signal(false);
1298
+ const error = signal<Error | null>(null);
1299
+
1300
+ async function execute() {
1301
+ loading.value = true;
1302
+ error.value = null;
1303
+
1304
+ try {
1305
+ const result = await new Promise(resolve => setTimeout(() => resolve(null), 100));
1306
+ data.value = result;
1307
+ } catch (e) {
1308
+ error.value = e as Error;
1309
+ } finally {
1310
+ loading.value = false;
1311
+ }
1312
+ }
1313
+
1314
+ function reset() {
1315
+ data.value = null;
1316
+ loading.value = false;
1317
+ error.value = null;
1318
+ }
1319
+
1320
+ if (immediate) {
1321
+ execute();
1322
+ }
1323
+
1324
+ return {
1325
+ data,
1326
+ loading,
1327
+ error,
1328
+ execute,
1329
+ reset,
1330
+ };
1331
+ }
1332
+
1333
+ export default use${data.pascalName};
1334
+ `;
1335
+ },
1336
+ store: (data, _withStyles, _withTest, _template, lang) => {
1337
+ const tsOnly = lang === "ts";
1338
+ return `/**
1339
+ * ${data.pascalName} Store
1340
+ *
1341
+ * @description ${data.description}
1342
+ * @created ${data.date}
1343
+ */
1344
+
1345
+ import { signal, computed } from '@lytjs/reactivity';
1346
+
1347
+ ${tsOnly ? `export interface ${data.pascalName}State {
1348
+ items: any[];
1349
+ selectedId: string | null;
1350
+ loading: boolean;
1351
+ error: Error | null;
1352
+ }
1353
+
1354
+ ` : ""}export function create${data.pascalName}Store() {
1355
+ const state = signal${tsOnly ? `<${data.pascalName}State>` : ""}({
1356
+ items: [],
1357
+ selectedId: null,
1358
+ loading: false,
1359
+ error: null,
1360
+ });
1361
+
1362
+ const selectedItem = computed(() => {
1363
+ const currentState = state.value;
1364
+ return currentState.items.find(item => item.id === currentState.selectedId);
1365
+ });
1366
+
1367
+ const itemCount = computed(() => state.value.items.length);
1368
+
1369
+ function setItems(items) {
1370
+ state.value = { ...state.value, items };
1371
+ }
1372
+
1373
+ function selectItem(id) {
1374
+ state.value = { ...state.value, selectedId: id };
1375
+ }
1376
+
1377
+ function addItem(item) {
1378
+ state.value = {
1379
+ ...state.value,
1380
+ items: [...state.value.items, item],
1381
+ };
1382
+ }
1383
+
1384
+ function updateItem(id, updates) {
1385
+ state.value = {
1386
+ ...state.value,
1387
+ items: state.value.items.map(item =>
1388
+ item.id === id ? { ...item, ...updates } : item
1389
+ ),
1390
+ };
1391
+ }
1392
+
1393
+ function removeItem(id) {
1394
+ state.value = {
1395
+ ...state.value,
1396
+ items: state.value.items.filter(item => item.id !== id),
1397
+ selectedId: state.value.selectedId === id ? null : state.value.selectedId,
1398
+ };
1399
+ }
1400
+
1401
+ function setLoading(loading) {
1402
+ state.value = { ...state.value, loading };
1403
+ }
1404
+
1405
+ function setError(error) {
1406
+ state.value = { ...state.value, error };
1407
+ }
1408
+
1409
+ function reset() {
1410
+ state.value = {
1411
+ items: [],
1412
+ selectedId: null,
1413
+ loading: false,
1414
+ error: null,
1415
+ };
1416
+ }
1417
+
1418
+ return {
1419
+ state,
1420
+ selectedItem,
1421
+ itemCount,
1422
+ setItems,
1423
+ selectItem,
1424
+ addItem,
1425
+ updateItem,
1426
+ removeItem,
1427
+ setLoading,
1428
+ setError,
1429
+ reset,
1430
+ };
1431
+ }
1432
+
1433
+ ${tsOnly ? `export type ${data.pascalName}Store = ReturnType<typeof create${data.pascalName}Store>;
1434
+ ` : ""}export default create${data.pascalName}Store;
1435
+ `;
1436
+ },
1437
+ layout: (data, withStyles, _withTest, _template, lang) => {
1438
+ const tsOnly = lang === "ts";
1439
+ const styleImport = withStyles ? `
1440
+ import './${data.kebabName}.styles.css';` : "";
1441
+ return `/**
1442
+ * ${data.pascalName} \u5E03\u5C40
1443
+ *
1444
+ * @description ${data.description}
1445
+ * @created ${data.date}
1446
+ */
1447
+
1448
+ import { h, defineComponent } from '@lytjs/core';${styleImport}
1449
+
1450
+ ${tsOnly ? `export interface ${data.pascalName}LayoutProps {
1451
+ children?: any;
1452
+ }
1453
+
1454
+ ` : ""}export const ${data.pascalName}Layout = defineComponent({
1455
+ name: '${data.pascalName}Layout',
1456
+ setup(props) {
1457
+ return () => (
1458
+ <div className="${data.kebabName}-layout">
1459
+ <header className="${data.kebabName}-header">
1460
+ <slot name="header">
1461
+ <h1>${data.pascalName}</h1>
1462
+ </slot>
1463
+ </header>
1464
+ <main className="${data.kebabName}-main">
1465
+ <slot />
1466
+ </main>
1467
+ <footer className="${data.kebabName}-footer">
1468
+ <slot name="footer" />
1469
+ </footer>
1470
+ </div>
1471
+ );
1472
+ },
1473
+ });
1474
+
1475
+ export default ${data.pascalName}Layout;
1476
+ `;
1477
+ },
1478
+ middleware: (data, _withStyles, _withTest, _template, lang) => {
1479
+ const tsOnly = lang === "ts";
1480
+ return `/**
1481
+ * ${data.pascalName} \u4E2D\u95F4\u4EF6
1482
+ *
1483
+ * @description ${data.description}
1484
+ * @created ${data.date}
1485
+ */
1486
+
1487
+ ${tsOnly ? `import type { Request, Response, NextFunction } from 'express';
1488
+ ` : ""}
1489
+ export function ${data.camelName}Middleware(${tsOnly ? `req: Request, res: Response, next: NextFunction` : "req, res, next"}) {
1490
+ try {
1491
+ console.log('[${data.pascalName}] Middleware executed');
1492
+ next();
1493
+ } catch (error) {
1494
+ next(error);
1495
+ }
1496
+ }
1497
+
1498
+ export default ${data.camelName}Middleware;
1499
+ `;
1500
+ }
1501
+ };
1502
+ function toPascalCase2(name) {
1503
+ return name.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1504
+ }
1505
+ function toCamelCase2(name) {
1506
+ const pascal = toPascalCase2(name);
1507
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
1508
+ }
1509
+ function toKebabCase(name) {
1510
+ return name.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
1511
+ }
1512
+ async function generate(options) {
1513
+ const {
1514
+ type,
1515
+ name,
1516
+ path: basePath = "./src",
1517
+ withStyles = false,
1518
+ withTest = false,
1519
+ description = "",
1520
+ template = "default",
1521
+ language = "ts"
1522
+ } = options;
1523
+ const templateData = {
1524
+ name,
1525
+ pascalName: toPascalCase2(name),
1526
+ kebabName: toKebabCase(name),
1527
+ camelName: toCamelCase2(name),
1528
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
1529
+ description: description || `${toPascalCase2(name)} ${type}`
1530
+ };
1531
+ const typeDirs = {
1532
+ component: "components",
1533
+ page: "pages",
1534
+ service: "services",
1535
+ hook: "hooks",
1536
+ store: "stores",
1537
+ layout: "layouts",
1538
+ middleware: "middleware"
1539
+ };
1540
+ const targetDir = path__namespace.join(
1541
+ process.cwd(),
1542
+ basePath,
1543
+ typeDirs[type] || "components"
1544
+ );
1545
+ await ensureDir(targetDir);
1546
+ const templateFn = TEMPLATES3[type];
1547
+ if (!templateFn) {
1548
+ logger.error(`Unknown type: ${type}`);
1549
+ logger.info("Available types: component, page, service, hook, store, layout, middleware");
1550
+ process.exit(1);
1551
+ }
1552
+ const extension = template === "sfc" ? "lyt" : language;
1553
+ const filename = `${templateData.kebabName}${type === "page" ? ".page" : ""}.${extension}`;
1554
+ const filePath = path__namespace.join(targetDir, filename);
1555
+ const content = templateFn(templateData, withStyles, withTest, template, language);
1556
+ await writeFile(filePath, content);
1557
+ logger.success(`Generated ${type}: ${filePath}`);
1558
+ if (withStyles && template !== "sfc") {
1559
+ const styleContent = `/**
1560
+ * ${templateData.pascalName} Styles
1561
+ */
1562
+
1563
+ .${templateData.kebabName} {
1564
+ /* Component styles */
1565
+ }
1566
+ `;
1567
+ const stylePath = path__namespace.join(targetDir, `${templateData.kebabName}.styles.css`);
1568
+ await writeFile(stylePath, styleContent);
1569
+ logger.success(`Generated styles: ${stylePath}`);
1570
+ }
1571
+ if (withTest && template !== "sfc") {
1572
+ logger.info("Test file included in generated component");
1573
+ }
1574
+ logger.info("\nNext steps:");
1575
+ logger.info(` cd ${targetDir}`);
1576
+ logger.info(` Import your ${type}: import { ${template === "sfc" ? "default" : templateData.pascalName} } from './${templateData.kebabName}'`);
1577
+ logger.info("\nAvailable options:");
1578
+ logger.info(" --template=sfc : Single File Component (.lyt)");
1579
+ logger.info(" --template=functional : Functional component");
1580
+ logger.info(" --language=js : JavaScript output");
1581
+ }
769
1582
  var PLUGIN_TEMPLATES = {
770
1583
  default: "Default plugin template with TypeScript",
771
1584
  minimal: "Minimal plugin without extra dependencies",
@@ -857,8 +1670,6 @@ const optionsSchema: ConfigSchema<${pluginName.replace(/-/g, "")}Options> = {
857
1670
  additionalProperties: false,
858
1671
  };
859
1672
 
860
- ${pluginName.replace(/-/g, "")}Options\`;
861
-
862
1673
  export interface ${pluginName.replace(/-/g, "")}Options {
863
1674
  debug?: boolean;
864
1675
  option1?: string;
@@ -1176,8 +1987,10 @@ async function runCli(rawArgs = process.argv.slice(2)) {
1176
1987
  });
1177
1988
  break;
1178
1989
  case "add":
1179
- if (!args[0] || !["component", "page", "store"].includes(args[0])) {
1180
- logger.error("Usage: lyt add <component|page|store> <name>");
1990
+ const addTypes = ["component", "page", "store", "directive", "composable", "util", "middleware", "hook"];
1991
+ if (!args[0] || !addTypes.includes(args[0])) {
1992
+ logger.error("Usage: lyt add <type> <name>");
1993
+ logger.info("Types: component, page, store, directive, composable, util, middleware, hook");
1181
1994
  logger.info("Example: lyt add component Button");
1182
1995
  process.exit(1);
1183
1996
  }
@@ -1185,6 +1998,24 @@ async function runCli(rawArgs = process.argv.slice(2)) {
1185
1998
  force: options.force
1186
1999
  });
1187
2000
  break;
2001
+ case "generate":
2002
+ case "g":
2003
+ const genTypes = ["component", "page", "service", "hook", "store"];
2004
+ if (!args[0] || !genTypes.includes(args[0])) {
2005
+ logger.error("Usage: lyt generate <type> <name>");
2006
+ logger.info("Types: component, page, service, hook, store");
2007
+ logger.info("Example: lyt generate component Button");
2008
+ process.exit(1);
2009
+ }
2010
+ await generate({
2011
+ type: args[0],
2012
+ name: args[1] || "Unnamed",
2013
+ path: options.path,
2014
+ withStyles: options.styles,
2015
+ withTest: options.test,
2016
+ withStorybook: options.storybook
2017
+ });
2018
+ break;
1188
2019
  case "plugin":
1189
2020
  if (!args[0]) {
1190
2021
  logger.error("Usage: lyt plugin <create|build|validate|templates>");
@@ -1280,10 +2111,11 @@ ${logger.bold("Commands:")}
1280
2111
  dev Start development server
1281
2112
  build Build for production
1282
2113
  test Run tests
1283
- add <type> <name> Generate a component, page, or store
2114
+ add <type> <name> Generate a component, page, store, directive, composable, etc.
2115
+ generate, g Advanced code generation (component, page, service, hook, store)
1284
2116
  plugin <subcmd> Plugin development commands
1285
2117
  help Show this help message
1286
-
2118
+
1287
2119
  ${logger.bold("Options:")}
1288
2120
  --version, -v Show version number
1289
2121
  --help Show help
@@ -1307,6 +2139,12 @@ ${logger.bold("Test Options:")}
1307
2139
  --coverage Generate coverage report
1308
2140
  --grep <pattern> Filter tests by pattern
1309
2141
 
2142
+ ${logger.bold("Generate Options:")}
2143
+ --path <dir> Output directory (default: ./src)
2144
+ --styles Generate CSS styles file
2145
+ --test Generate test file
2146
+ --storybook Generate Storybook story file
2147
+
1310
2148
  ${logger.bold("Plugin Options:")}
1311
2149
  --template <name> Use a specific plugin template (default, minimal, withConfig)
1312
2150
  --force Overwrite existing directory
@@ -1327,6 +2165,16 @@ ${logger.bold("Examples:")}
1327
2165
  lyt add component Button
1328
2166
  lyt add page About
1329
2167
  lyt add store user
2168
+ lyt directive click-outside
2169
+ lyt add composable fetch-data
2170
+ lyt add util format
2171
+ lyt add hook window-size
2172
+ lyt generate component Button --styles --test
2173
+ lyt generate page Dashboard --path ./src/pages
2174
+ lyt generate service User --path ./src/services
2175
+ lyt generate hook useCounter
2176
+ lyt generate store Auth --path ./src/stores
2177
+ lyt g page Login
1330
2178
  lyt plugin create my-plugin
1331
2179
  lyt plugin create my-plugin --template withConfig
1332
2180
  lyt plugin build
@@ -1348,6 +2196,7 @@ exports.detectPackageManager = detectPackageManager;
1348
2196
  exports.dev = dev;
1349
2197
  exports.ensureDir = ensureDir;
1350
2198
  exports.exists = exists;
2199
+ exports.generate = generate;
1351
2200
  exports.getAddCommand = getAddCommand;
1352
2201
  exports.getInstallCommand = getInstallCommand;
1353
2202
  exports.getRunCommand = getRunCommand;