@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/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
3
+ import * as path from 'path';
3
4
  import { join, resolve, dirname } from 'path';
4
5
  import { execSync, spawn } from 'child_process';
5
6
 
@@ -60,8 +61,8 @@ function writeFile(filePath, content) {
60
61
  function readFile(filePath) {
61
62
  return readFileSync(filePath, "utf-8");
62
63
  }
63
- function exists(path) {
64
- return existsSync(path);
64
+ function exists(path2) {
65
+ return existsSync(path2);
65
66
  }
66
67
  function isEmptyDir(dir) {
67
68
  if (!existsSync(dir)) return true;
@@ -441,12 +442,19 @@ export const useCounterStore = defineStore('counter', () => {
441
442
  writeFile(join(targetDir, "src", "stores", "counter.ts"), counterStore);
442
443
  }
443
444
  if (isSsr) {
444
- const entryServer = `import { createSSRApp } from '@lytjs/core';
445
+ const entryServer = `import { createSSRApp, h } from '@lytjs/core';
446
+ import { renderToString } from '@lytjs/ssr';
445
447
  import App from './App.lyt';
446
448
 
447
449
  export async function render(url: string) {
448
- const app = createSSRApp(App);
449
- return app;
450
+ const app = createSSRApp({
451
+ render() {
452
+ return h(App);
453
+ }
454
+ });
455
+
456
+ const html = await renderToString(app);
457
+ return html;
450
458
  }
451
459
  `;
452
460
  writeFile(join(targetDir, "src/entry-server.ts"), entryServer);
@@ -460,36 +468,114 @@ app.mount('#app');
460
468
  const serverTs = `/**
461
469
  * LytJS SSR Server
462
470
  *
463
- * A minimal SSR server for development and production.
471
+ * Complete SSR server with Vite dev server and production build support.
472
+ * Supports streaming SSR, route prefetching, and static file serving.
464
473
  */
465
474
 
466
475
  import fs from 'fs';
467
476
  import path from 'path';
468
477
  import { fileURLToPath } from 'url';
478
+ import http from 'http';
469
479
 
470
480
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
471
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
+ }
472
509
 
473
510
  async function createServer() {
474
- let resolve: any;
475
511
  let vite: any;
476
512
 
513
+ // Load index.html template
514
+ let template: string;
515
+
477
516
  if (!isProduction) {
478
517
  const { createServer: createViteServer } = await import('vite');
479
518
  vite = await createViteServer({
480
519
  server: { middlewareMode: true },
481
520
  appType: 'custom',
482
521
  });
483
- resolve = (id: string) => vite.resolveUrl(id);
522
+ template = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf-8');
484
523
  } else {
485
- resolve = (id: string) => id;
524
+ template = fs.readFileSync(path.join(DIST_DIR, 'client/index.html'), 'utf-8');
486
525
  }
487
526
 
488
- // TODO: Set up express/polka server and SSR rendering
489
- console.log('LytJS SSR server starting...');
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
+ });
490
576
  }
491
577
 
492
- createServer();
578
+ createServer().catch(console.error);
493
579
  `;
494
580
  writeFile(join(targetDir, "server.ts"), serverTs);
495
581
  }
@@ -666,12 +752,13 @@ defineEmits<{
666
752
  }];
667
753
  },
668
754
  page(name, basePath) {
755
+ const pascalName = toPascalCase(name);
669
756
  const filePath = join(basePath, `${name}.lyt`);
670
757
  return [{
671
758
  filePath,
672
759
  content: `<template>
673
760
  <div class="page-${name}">
674
- <h1>${toPascalCase(name)}</h1>
761
+ <h1>${pascalName}</h1>
675
762
  </div>
676
763
  </template>
677
764
 
@@ -722,6 +809,157 @@ export const use${toPascalCase(name)}Store = defineStore('${name}', () => {
722
809
  reset,
723
810
  };
724
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
+ };
725
963
  `
726
964
  }];
727
965
  }
@@ -729,6 +967,10 @@ export const use${toPascalCase(name)}Store = defineStore('${name}', () => {
729
967
  function toPascalCase(str) {
730
968
  return str.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
731
969
  }
970
+ function toCamelCase(str) {
971
+ const pascalCase = toPascalCase(str);
972
+ return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
973
+ }
732
974
  function resolveTargetDir(type) {
733
975
  const cwd = process.cwd();
734
976
  switch (type) {
@@ -738,6 +980,16 @@ function resolveTargetDir(type) {
738
980
  return join(cwd, "src", "pages");
739
981
  case "store":
740
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");
741
993
  }
742
994
  }
743
995
  async function add(type, name, options = {}) {
@@ -764,6 +1016,548 @@ async function add(type, name, options = {}) {
764
1016
  logger.success(`Created ${type}: ${file.filePath}`);
765
1017
  }
766
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
+ }
767
1561
  var PLUGIN_TEMPLATES = {
768
1562
  default: "Default plugin template with TypeScript",
769
1563
  minimal: "Minimal plugin without extra dependencies",
@@ -855,8 +1649,6 @@ const optionsSchema: ConfigSchema<${pluginName.replace(/-/g, "")}Options> = {
855
1649
  additionalProperties: false,
856
1650
  };
857
1651
 
858
- ${pluginName.replace(/-/g, "")}Options\`;
859
-
860
1652
  export interface ${pluginName.replace(/-/g, "")}Options {
861
1653
  debug?: boolean;
862
1654
  option1?: string;
@@ -1174,8 +1966,10 @@ async function runCli(rawArgs = process.argv.slice(2)) {
1174
1966
  });
1175
1967
  break;
1176
1968
  case "add":
1177
- if (!args[0] || !["component", "page", "store"].includes(args[0])) {
1178
- logger.error("Usage: lyt add <component|page|store> <name>");
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");
1179
1973
  logger.info("Example: lyt add component Button");
1180
1974
  process.exit(1);
1181
1975
  }
@@ -1183,6 +1977,24 @@ async function runCli(rawArgs = process.argv.slice(2)) {
1183
1977
  force: options.force
1184
1978
  });
1185
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;
1186
1998
  case "plugin":
1187
1999
  if (!args[0]) {
1188
2000
  logger.error("Usage: lyt plugin <create|build|validate|templates>");
@@ -1278,10 +2090,11 @@ ${logger.bold("Commands:")}
1278
2090
  dev Start development server
1279
2091
  build Build for production
1280
2092
  test Run tests
1281
- add <type> <name> Generate a component, page, or store
2093
+ add <type> <name> Generate a component, page, store, directive, composable, etc.
2094
+ generate, g Advanced code generation (component, page, service, hook, store)
1282
2095
  plugin <subcmd> Plugin development commands
1283
2096
  help Show this help message
1284
-
2097
+
1285
2098
  ${logger.bold("Options:")}
1286
2099
  --version, -v Show version number
1287
2100
  --help Show help
@@ -1305,6 +2118,12 @@ ${logger.bold("Test Options:")}
1305
2118
  --coverage Generate coverage report
1306
2119
  --grep <pattern> Filter tests by pattern
1307
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
+
1308
2127
  ${logger.bold("Plugin Options:")}
1309
2128
  --template <name> Use a specific plugin template (default, minimal, withConfig)
1310
2129
  --force Overwrite existing directory
@@ -1325,6 +2144,16 @@ ${logger.bold("Examples:")}
1325
2144
  lyt add component Button
1326
2145
  lyt add page About
1327
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
1328
2157
  lyt plugin create my-plugin
1329
2158
  lyt plugin create my-plugin --template withConfig
1330
2159
  lyt plugin build
@@ -1337,6 +2166,6 @@ if (__require.main === module) {
1337
2166
  runCli().catch(console.error);
1338
2167
  }
1339
2168
 
1340
- export { add, build, buildPlugin, create, createPlugin, detectPackageManager, dev, ensureDir, exists, getAddCommand, getInstallCommand, getRunCommand, listPluginTemplates, listTemplates, logger, readFile, runCli, test, validatePlugin, writeFile };
2169
+ export { add, build, buildPlugin, create, createPlugin, detectPackageManager, dev, ensureDir, exists, generate, getAddCommand, getInstallCommand, getRunCommand, listPluginTemplates, listTemplates, logger, readFile, runCli, test, validatePlugin, writeFile };
1341
2170
  //# sourceMappingURL=index.mjs.map
1342
2171
  //# sourceMappingURL=index.mjs.map