@lytjs/cli 6.4.0 → 6.6.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.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
2
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync as readdirSync$1 } 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,12 +61,12 @@ 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;
68
- const files = readdirSync(dir);
69
+ const files = readdirSync$1(dir);
69
70
  return files.length === 0;
70
71
  }
71
72
  function detectPackageManager(cwd = process.cwd()) {
@@ -168,7 +169,7 @@ function generateProjectFiles(targetDir, projectName, template) {
168
169
  },
169
170
  devDependencies: {
170
171
  "@lytjs/plugin-vite": "^6.0.0",
171
- "vite": "^5.0.0"
172
+ vite: "^5.0.0"
172
173
  }
173
174
  };
174
175
  if (!isMinimal) {
@@ -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
  }
@@ -639,9 +725,10 @@ async function test(options = {}) {
639
725
  var TEMPLATES2 = {
640
726
  component(name, basePath) {
641
727
  const filePath = join(basePath, `${name}.lyt`);
642
- return [{
643
- filePath,
644
- content: `<template>
728
+ return [
729
+ {
730
+ filePath,
731
+ content: `<template>
645
732
  <div class="${name}">
646
733
  <slot />
647
734
  </div>
@@ -663,15 +750,18 @@ defineEmits<{
663
750
  }
664
751
  </style>
665
752
  `
666
- }];
753
+ }
754
+ ];
667
755
  },
668
756
  page(name, basePath) {
757
+ const pascalName = toPascalCase(name);
669
758
  const filePath = join(basePath, `${name}.lyt`);
670
- return [{
671
- filePath,
672
- content: `<template>
759
+ return [
760
+ {
761
+ filePath,
762
+ content: `<template>
673
763
  <div class="page-${name}">
674
- <h1>${toPascalCase(name)}</h1>
764
+ <h1>${pascalName}</h1>
675
765
  </div>
676
766
  </template>
677
767
 
@@ -685,13 +775,15 @@ defineEmits<{
685
775
  }
686
776
  </style>
687
777
  `
688
- }];
778
+ }
779
+ ];
689
780
  },
690
781
  store(name, basePath) {
691
782
  const filePath = join(basePath, `${name}.ts`);
692
- return [{
693
- filePath,
694
- content: `import { defineStore } from '@lytjs/store';
783
+ return [
784
+ {
785
+ filePath,
786
+ content: `import { defineStore } from '@lytjs/store';
695
787
  import { signal, computed } from '@lytjs/reactivity';
696
788
 
697
789
  export const use${toPascalCase(name)}Store = defineStore('${name}', () => {
@@ -723,12 +815,178 @@ export const use${toPascalCase(name)}Store = defineStore('${name}', () => {
723
815
  };
724
816
  });
725
817
  `
726
- }];
818
+ }
819
+ ];
820
+ },
821
+ directive(name, basePath) {
822
+ const filePath = join(basePath, `${name}.ts`);
823
+ const camelCaseName = toCamelCase(name);
824
+ return [
825
+ {
826
+ filePath,
827
+ content: `import type { Directive } from '@lytjs/core';
828
+
829
+ /**
830
+ * ${toPascalCase(name)} Directive
831
+ *
832
+ * @example
833
+ * \`\`\`vue
834
+ * <div v-${camelCaseName} />
835
+ * \`\`\`
836
+ */
837
+ export const v${toPascalCase(name)}: Directive = {
838
+ mounted(el, binding) {
839
+ // Directive mounted
840
+ },
841
+
842
+ updated(el, binding) {
843
+ // Directive updated
844
+ },
845
+
846
+ unmounted(el) {
847
+ // Directive unmounted
848
+ },
849
+ };
850
+ `
851
+ }
852
+ ];
853
+ },
854
+ composable(name, basePath) {
855
+ const filePath = join(basePath, `use${toPascalCase(name)}.ts`);
856
+ return [
857
+ {
858
+ filePath,
859
+ content: `import { signal, computed } from '@lytjs/reactivity';
860
+
861
+ /**
862
+ * ${toPascalCase(name)} Composable
863
+ *
864
+ * @example
865
+ * \`\`\`typescript
866
+ * const { state, actions } = use${toPascalCase(name)}();
867
+ * \`\`\`
868
+ */
869
+ export function use${toPascalCase(name)}() {
870
+ // State
871
+ const isLoading = signal(false);
872
+ const error = signal<Error | null>(null);
873
+ const data = signal<any>(null);
874
+
875
+ // Computed
876
+ const hasData = computed(() => data.value !== null);
877
+
878
+ // Actions
879
+ async function fetch() {
880
+ isLoading.value = true;
881
+ error.value = null;
882
+ try {
883
+ // TODO: Fetch logic here
884
+ // data.value = await someApi();
885
+ } catch (e) {
886
+ error.value = e as Error;
887
+ } finally {
888
+ isLoading.value = false;
889
+ }
890
+ }
891
+
892
+ function reset() {
893
+ isLoading.value = false;
894
+ error.value = null;
895
+ data.value = null;
896
+ }
897
+
898
+ return {
899
+ isLoading,
900
+ error,
901
+ data,
902
+ hasData,
903
+ fetch,
904
+ reset,
905
+ };
906
+ }
907
+ `
908
+ }
909
+ ];
910
+ },
911
+ hook(name, basePath) {
912
+ const filePath = join(basePath, `use${toPascalCase(name)}.ts`);
913
+ return [
914
+ {
915
+ filePath,
916
+ content: `import { signal, onMounted, onUnmounted } from '@lytjs/core';
917
+
918
+ /**
919
+ * ${toPascalCase(name)} Hook
920
+ */
921
+ export function use${toPascalCase(name)}() {
922
+ const state = signal(null);
923
+
924
+ onMounted(() => {
925
+ // Setup code on mount
926
+ });
927
+
928
+ onUnmounted(() => {
929
+ // Cleanup on unmount
930
+ });
931
+
932
+ return {
933
+ state,
934
+ };
935
+ }
936
+ `
937
+ }
938
+ ];
939
+ },
940
+ util(name, basePath) {
941
+ const filePath = join(basePath, `${name}.ts`);
942
+ return [
943
+ {
944
+ filePath,
945
+ content: `/**
946
+ * ${toPascalCase(name)} Utility Functions
947
+ */
948
+
949
+ /**
950
+ * ${toPascalCase(name)} function
951
+ *
952
+ * @param input - The input value
953
+ * @returns The processed result
954
+ */
955
+ export function ${toCamelCase(name)}(input: any) {
956
+ // TODO: Implement function
957
+ return input;
958
+ }
959
+ `
960
+ }
961
+ ];
962
+ },
963
+ middleware(name, basePath) {
964
+ const filePath = join(basePath, `${name}.ts`);
965
+ return [
966
+ {
967
+ filePath,
968
+ content: `import type { NavigationGuard } from '@lytjs/router';
969
+
970
+ /**
971
+ * ${toPascalCase(name)} Middleware
972
+ */
973
+ export const ${toCamelCase(name)}Middleware: NavigationGuard = (to, from, next) => {
974
+ // Middleware logic
975
+ console.log('Middleware:', to.path);
976
+ next();
977
+ };
978
+ `
979
+ }
980
+ ];
727
981
  }
728
982
  };
729
983
  function toPascalCase(str) {
730
984
  return str.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
731
985
  }
986
+ function toCamelCase(str) {
987
+ const pascalCase = toPascalCase(str);
988
+ return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
989
+ }
732
990
  function resolveTargetDir(type) {
733
991
  const cwd = process.cwd();
734
992
  switch (type) {
@@ -738,6 +996,16 @@ function resolveTargetDir(type) {
738
996
  return join(cwd, "src", "pages");
739
997
  case "store":
740
998
  return join(cwd, "src", "stores");
999
+ case "directive":
1000
+ return join(cwd, "src", "directives");
1001
+ case "composable":
1002
+ return join(cwd, "src", "composables");
1003
+ case "util":
1004
+ return join(cwd, "src", "utils");
1005
+ case "middleware":
1006
+ return join(cwd, "src", "middleware");
1007
+ case "hook":
1008
+ return join(cwd, "src", "hooks");
741
1009
  }
742
1010
  }
743
1011
  async function add(type, name, options = {}) {
@@ -764,6 +1032,546 @@ async function add(type, name, options = {}) {
764
1032
  logger.success(`Created ${type}: ${file.filePath}`);
765
1033
  }
766
1034
  }
1035
+ var TEMPLATES3 = {
1036
+ component: (data, withStyles, withTest, template, lang) => {
1037
+ const styleImport = withStyles ? `
1038
+ import './${data.kebabName}.styles.css';` : "";
1039
+ const tsOnly = lang === "ts";
1040
+ if (template === "sfc") {
1041
+ return `<template>
1042
+ <div class="${data.kebabName}">
1043
+ <slot>
1044
+ ${data.pascalName} Component
1045
+ </slot>
1046
+ </div>
1047
+ </template>
1048
+
1049
+ <script setup${tsOnly ? ' lang="ts"' : ""}>
1050
+ ${tsOnly ? `import { ref } from '@lytjs/reactivity';
1051
+ ` : ""}
1052
+ ${tsOnly ? `
1053
+ export interface ${data.pascalName}Props {
1054
+ className?: string;
1055
+ }
1056
+
1057
+ const props = defineProps<${data.pascalName}Props>();
1058
+ ` : ""}
1059
+
1060
+ const title = ref('${data.pascalName}');
1061
+ </script>
1062
+
1063
+ <style scoped>
1064
+ .${data.kebabName} {
1065
+ /* Component styles */
1066
+ }
1067
+ </style>
1068
+ `;
1069
+ }
1070
+ const testImport = withTest ? `
1071
+ import { describe, it, expect } from 'vitest';
1072
+ import { ${data.pascalName} } from './${data.kebabName}';
1073
+
1074
+ describe('${data.pascalName}', () => {
1075
+ it('should render', () => {
1076
+ // Add test here
1077
+ expect(true).toBe(true);
1078
+ });
1079
+ });` : "";
1080
+ const propsDecl = tsOnly ? `['className', 'children']` : `[]`;
1081
+ return `/**
1082
+ * ${data.pascalName} \u7EC4\u4EF6
1083
+ *
1084
+ * @description ${data.description}
1085
+ * @created ${data.date}
1086
+ */
1087
+
1088
+ import { h, defineComponent } from '@lytjs/core';${styleImport}
1089
+
1090
+ ${tsOnly ? `export interface ${data.pascalName}Props {
1091
+ className?: string;
1092
+ children?: any;
1093
+ }
1094
+
1095
+ ` : ""}${template === "functional" ? `export function ${data.pascalName}(${tsOnly ? `props: ${data.pascalName}Props` : "props"}) {
1096
+ const { className = '', children } = props;
1097
+
1098
+ return (
1099
+ <div className={\`${data.kebabName} \${className}\`}>
1100
+ {children || '${data.pascalName} Component'}
1101
+ </div>
1102
+ );
1103
+ }` : `export const ${data.pascalName} = defineComponent({
1104
+ name: '${data.pascalName}',
1105
+ props: ${propsDecl},
1106
+ setup(props) {
1107
+ const { className = '', children } = props;
1108
+
1109
+ return () => (
1110
+ <div className={\`${data.kebabName} \${className}\`}>
1111
+ {children || '${data.pascalName} Component'}
1112
+ </div>
1113
+ );
1114
+ },
1115
+ });`}
1116
+
1117
+ export default ${data.pascalName};${testImport}
1118
+ `;
1119
+ },
1120
+ page: (data, withStyles, withTest, template, lang) => {
1121
+ const styleImport = withStyles ? `
1122
+ import './${data.kebabName}.styles.css';` : "";
1123
+ const tsOnly = lang === "ts";
1124
+ if (template === "sfc") {
1125
+ return `<template>
1126
+ <div class="${data.kebabName}-page">
1127
+ <h1>{title}</h1>
1128
+ <p>Page content for ${data.pascalName}</p>
1129
+ <slot />
1130
+ </div>
1131
+ </template>
1132
+
1133
+ <script setup${tsOnly ? ' lang="ts"' : ""}>
1134
+ ${tsOnly ? `import { ref } from '@lytjs/reactivity';
1135
+ ` : ""}
1136
+ ${tsOnly ? `
1137
+ export interface ${data.pascalName}PageProps {
1138
+ title?: string;
1139
+ }
1140
+
1141
+ const props = defineProps<${data.pascalName}PageProps>();
1142
+ ` : ""}
1143
+
1144
+ const title = ref(props.title || '${data.pascalName}');
1145
+ </script>
1146
+
1147
+ <style scoped>
1148
+ .${data.kebabName}-page {
1149
+ padding: 2rem;
1150
+ }
1151
+ </style>
1152
+ `;
1153
+ }
1154
+ const testImport = withTest ? `
1155
+ import { describe, it, expect } from 'vitest';
1156
+ import { ${data.pascalName}Page } from './${data.kebabName}';
1157
+
1158
+ describe('${data.pascalName}Page', () => {
1159
+ it('should render', () => {
1160
+ // Add test here
1161
+ expect(true).toBe(true);
1162
+ });
1163
+ });` : "";
1164
+ return `/**
1165
+ * ${data.pascalName} \u9875\u9762
1166
+ *
1167
+ * @description ${data.description}
1168
+ * @created ${data.date}
1169
+ */
1170
+
1171
+ import { h, ${tsOnly ? "signal" : "signal"} } from '@lytjs/core';
1172
+ ${styleImport}
1173
+
1174
+ ${tsOnly ? `export interface ${data.pascalName}PageProps {
1175
+ title?: string;
1176
+ }
1177
+
1178
+ ` : ""}export function ${data.pascalName}Page(${tsOnly ? `props: ${data.pascalName}PageProps` : "props"}) {
1179
+ const { title = '${data.pascalName}' } = props;
1180
+
1181
+ return (
1182
+ <div className="${data.kebabName}-page">
1183
+ <h1>{title}</h1>
1184
+ <p>Page content for ${data.pascalName}</p>
1185
+ </div>
1186
+ );
1187
+ }
1188
+
1189
+ export default ${data.pascalName}Page;${testImport}
1190
+ `;
1191
+ },
1192
+ service: (data, _withStyles, _withTest, _template, lang) => {
1193
+ const tsOnly = lang === "ts";
1194
+ return `/**
1195
+ * ${data.pascalName} \u670D\u52A1
1196
+ *
1197
+ * @description ${data.description}
1198
+ * @created ${data.date}
1199
+ */
1200
+
1201
+ ${tsOnly ? `export interface ${data.pascalName}ServiceOptions {
1202
+ baseUrl?: string;
1203
+ timeout?: number;
1204
+ }
1205
+
1206
+ ` : ""}${tsOnly ? `export class ${data.pascalName}Service {
1207
+ private baseUrl: string;
1208
+ private timeout: number;
1209
+ ` : `export class ${data.pascalName}Service {
1210
+ `}
1211
+ constructor(${tsOnly ? `options: ${data.pascalName}ServiceOptions = {}` : "options = {}"}) {
1212
+ this.baseUrl = options.baseUrl || '/api';
1213
+ this.timeout = options.timeout || 30000;
1214
+ }
1215
+
1216
+ async getAll()${tsOnly ? ": Promise<any[]>" : ""} {
1217
+ const response = await fetch(\`\${this.baseUrl}/${data.kebabName}s\`, {
1218
+ method: 'GET',
1219
+ headers: { 'Content-Type': 'application/json' },
1220
+ signal: AbortSignal.timeout(this.timeout),
1221
+ });
1222
+ return response.json();
1223
+ }
1224
+
1225
+ async getById(id)${tsOnly ? ": Promise<any>" : ""} {
1226
+ const response = await fetch(\`\${this.baseUrl}/${data.kebabName}s/\${id}\`, {
1227
+ method: 'GET',
1228
+ headers: { 'Content-Type': 'application/json' },
1229
+ signal: AbortSignal.timeout(this.timeout),
1230
+ });
1231
+ return response.json();
1232
+ }
1233
+
1234
+ async create(${tsOnly ? "data: any" : "data"})${tsOnly ? ": Promise<any>" : ""} {
1235
+ const response = await fetch(\`\${this.baseUrl}/${data.kebabName}s\`, {
1236
+ method: 'POST',
1237
+ headers: { 'Content-Type': 'application/json' },
1238
+ body: JSON.stringify(data),
1239
+ signal: AbortSignal.timeout(this.timeout),
1240
+ });
1241
+ return response.json();
1242
+ }
1243
+
1244
+ async update(id)${tsOnly ? ": Promise<any>" : ""} {
1245
+ const response = await fetch(\`\${this.baseUrl}/${data.kebabName}s/\${id}\`, {
1246
+ method: 'PUT',
1247
+ headers: { 'Content-Type': 'application/json' },
1248
+ body: JSON.stringify(data),
1249
+ signal: AbortSignal.timeout(this.timeout),
1250
+ });
1251
+ return response.json();
1252
+ }
1253
+
1254
+ async delete(id)${tsOnly ? ": Promise<void>" : ""} {
1255
+ await fetch(\`\${this.baseUrl}/${data.kebabName}s/\${id}\`, {
1256
+ method: 'DELETE',
1257
+ signal: AbortSignal.timeout(this.timeout),
1258
+ });
1259
+ }
1260
+ }
1261
+
1262
+ export default ${data.pascalName}Service;
1263
+ `;
1264
+ },
1265
+ hook: (data, _withStyles, _withTest, _template, lang) => {
1266
+ const tsOnly = lang === "ts";
1267
+ return `/**
1268
+ * ${data.pascalName} Hook
1269
+ *
1270
+ * @description ${data.description}
1271
+ * @created ${data.date}
1272
+ */
1273
+
1274
+ import { signal, effect } from '@lytjs/reactivity';
1275
+
1276
+ ${tsOnly ? `export interface ${data.pascalName}Options {
1277
+ immediate?: boolean;
1278
+ }
1279
+
1280
+ export interface ${data.pascalName}Return {
1281
+ data: ReturnType<typeof signal>;
1282
+ loading: ReturnType<typeof signal>;
1283
+ error: ReturnType<typeof signal>;
1284
+ execute: () => Promise<void>;
1285
+ reset: () => void;
1286
+ }
1287
+
1288
+ ` : ""}export function use${data.pascalName}(${tsOnly ? `options: ${data.pascalName}Options = {}` : "options = {}"})${tsOnly ? `: ${data.pascalName}Return` : ""} {
1289
+ const { immediate = false } = options;
1290
+
1291
+ const data = signal<any>(null);
1292
+ const loading = signal(false);
1293
+ const error = signal<Error | null>(null);
1294
+
1295
+ async function execute() {
1296
+ loading.value = true;
1297
+ error.value = null;
1298
+
1299
+ try {
1300
+ const result = await new Promise(resolve => setTimeout(() => resolve(null), 100));
1301
+ data.value = result;
1302
+ } catch (e) {
1303
+ error.value = e as Error;
1304
+ } finally {
1305
+ loading.value = false;
1306
+ }
1307
+ }
1308
+
1309
+ function reset() {
1310
+ data.value = null;
1311
+ loading.value = false;
1312
+ error.value = null;
1313
+ }
1314
+
1315
+ if (immediate) {
1316
+ execute();
1317
+ }
1318
+
1319
+ return {
1320
+ data,
1321
+ loading,
1322
+ error,
1323
+ execute,
1324
+ reset,
1325
+ };
1326
+ }
1327
+
1328
+ export default use${data.pascalName};
1329
+ `;
1330
+ },
1331
+ store: (data, _withStyles, _withTest, _template, lang) => {
1332
+ const tsOnly = lang === "ts";
1333
+ return `/**
1334
+ * ${data.pascalName} Store
1335
+ *
1336
+ * @description ${data.description}
1337
+ * @created ${data.date}
1338
+ */
1339
+
1340
+ import { signal, computed } from '@lytjs/reactivity';
1341
+
1342
+ ${tsOnly ? `export interface ${data.pascalName}State {
1343
+ items: any[];
1344
+ selectedId: string | null;
1345
+ loading: boolean;
1346
+ error: Error | null;
1347
+ }
1348
+
1349
+ ` : ""}export function create${data.pascalName}Store() {
1350
+ const state = signal${tsOnly ? `<${data.pascalName}State>` : ""}({
1351
+ items: [],
1352
+ selectedId: null,
1353
+ loading: false,
1354
+ error: null,
1355
+ });
1356
+
1357
+ const selectedItem = computed(() => {
1358
+ const currentState = state.value;
1359
+ return currentState.items.find(item => item.id === currentState.selectedId);
1360
+ });
1361
+
1362
+ const itemCount = computed(() => state.value.items.length);
1363
+
1364
+ function setItems(items) {
1365
+ state.value = { ...state.value, items };
1366
+ }
1367
+
1368
+ function selectItem(id) {
1369
+ state.value = { ...state.value, selectedId: id };
1370
+ }
1371
+
1372
+ function addItem(item) {
1373
+ state.value = {
1374
+ ...state.value,
1375
+ items: [...state.value.items, item],
1376
+ };
1377
+ }
1378
+
1379
+ function updateItem(id, updates) {
1380
+ state.value = {
1381
+ ...state.value,
1382
+ items: state.value.items.map(item =>
1383
+ item.id === id ? { ...item, ...updates } : item
1384
+ ),
1385
+ };
1386
+ }
1387
+
1388
+ function removeItem(id) {
1389
+ state.value = {
1390
+ ...state.value,
1391
+ items: state.value.items.filter(item => item.id !== id),
1392
+ selectedId: state.value.selectedId === id ? null : state.value.selectedId,
1393
+ };
1394
+ }
1395
+
1396
+ function setLoading(loading) {
1397
+ state.value = { ...state.value, loading };
1398
+ }
1399
+
1400
+ function setError(error) {
1401
+ state.value = { ...state.value, error };
1402
+ }
1403
+
1404
+ function reset() {
1405
+ state.value = {
1406
+ items: [],
1407
+ selectedId: null,
1408
+ loading: false,
1409
+ error: null,
1410
+ };
1411
+ }
1412
+
1413
+ return {
1414
+ state,
1415
+ selectedItem,
1416
+ itemCount,
1417
+ setItems,
1418
+ selectItem,
1419
+ addItem,
1420
+ updateItem,
1421
+ removeItem,
1422
+ setLoading,
1423
+ setError,
1424
+ reset,
1425
+ };
1426
+ }
1427
+
1428
+ ${tsOnly ? `export type ${data.pascalName}Store = ReturnType<typeof create${data.pascalName}Store>;
1429
+ ` : ""}export default create${data.pascalName}Store;
1430
+ `;
1431
+ },
1432
+ layout: (data, withStyles, _withTest, _template, lang) => {
1433
+ const tsOnly = lang === "ts";
1434
+ const styleImport = withStyles ? `
1435
+ import './${data.kebabName}.styles.css';` : "";
1436
+ return `/**
1437
+ * ${data.pascalName} \u5E03\u5C40
1438
+ *
1439
+ * @description ${data.description}
1440
+ * @created ${data.date}
1441
+ */
1442
+
1443
+ import { h, defineComponent } from '@lytjs/core';${styleImport}
1444
+
1445
+ ${tsOnly ? `export interface ${data.pascalName}LayoutProps {
1446
+ children?: any;
1447
+ }
1448
+
1449
+ ` : ""}export const ${data.pascalName}Layout = defineComponent({
1450
+ name: '${data.pascalName}Layout',
1451
+ setup(props) {
1452
+ return () => (
1453
+ <div className="${data.kebabName}-layout">
1454
+ <header className="${data.kebabName}-header">
1455
+ <slot name="header">
1456
+ <h1>${data.pascalName}</h1>
1457
+ </slot>
1458
+ </header>
1459
+ <main className="${data.kebabName}-main">
1460
+ <slot />
1461
+ </main>
1462
+ <footer className="${data.kebabName}-footer">
1463
+ <slot name="footer" />
1464
+ </footer>
1465
+ </div>
1466
+ );
1467
+ },
1468
+ });
1469
+
1470
+ export default ${data.pascalName}Layout;
1471
+ `;
1472
+ },
1473
+ middleware: (data, _withStyles, _withTest, _template, lang) => {
1474
+ const tsOnly = lang === "ts";
1475
+ return `/**
1476
+ * ${data.pascalName} \u4E2D\u95F4\u4EF6
1477
+ *
1478
+ * @description ${data.description}
1479
+ * @created ${data.date}
1480
+ */
1481
+
1482
+ ${tsOnly ? `import type { Request, Response, NextFunction } from 'express';
1483
+ ` : ""}
1484
+ export function ${data.camelName}Middleware(${tsOnly ? `req: Request, res: Response, next: NextFunction` : "req, res, next"}) {
1485
+ try {
1486
+ console.log('[${data.pascalName}] Middleware executed');
1487
+ next();
1488
+ } catch (error) {
1489
+ next(error);
1490
+ }
1491
+ }
1492
+
1493
+ export default ${data.camelName}Middleware;
1494
+ `;
1495
+ }
1496
+ };
1497
+ function toPascalCase2(name) {
1498
+ return name.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1499
+ }
1500
+ function toCamelCase2(name) {
1501
+ const pascal = toPascalCase2(name);
1502
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
1503
+ }
1504
+ function toKebabCase(name) {
1505
+ return name.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
1506
+ }
1507
+ async function generate(options) {
1508
+ const {
1509
+ type,
1510
+ name,
1511
+ path: basePath = "./src",
1512
+ withStyles = false,
1513
+ withTest = false,
1514
+ description = "",
1515
+ template = "default",
1516
+ language = "ts"
1517
+ } = options;
1518
+ const templateData = {
1519
+ name,
1520
+ pascalName: toPascalCase2(name),
1521
+ kebabName: toKebabCase(name),
1522
+ camelName: toCamelCase2(name),
1523
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
1524
+ description: description || `${toPascalCase2(name)} ${type}`
1525
+ };
1526
+ const typeDirs = {
1527
+ component: "components",
1528
+ page: "pages",
1529
+ service: "services",
1530
+ hook: "hooks",
1531
+ store: "stores",
1532
+ layout: "layouts",
1533
+ middleware: "middleware"
1534
+ };
1535
+ const targetDir = path.join(process.cwd(), basePath, typeDirs[type] || "components");
1536
+ await ensureDir(targetDir);
1537
+ const templateFn = TEMPLATES3[type];
1538
+ if (!templateFn) {
1539
+ logger.error(`Unknown type: ${type}`);
1540
+ logger.info("Available types: component, page, service, hook, store, layout, middleware");
1541
+ process.exit(1);
1542
+ }
1543
+ const extension = template === "sfc" ? "lyt" : language;
1544
+ const filename = `${templateData.kebabName}${type === "page" ? ".page" : ""}.${extension}`;
1545
+ const filePath = path.join(targetDir, filename);
1546
+ const content = templateFn(templateData, withStyles, withTest, template, language);
1547
+ await writeFile(filePath, content);
1548
+ logger.success(`Generated ${type}: ${filePath}`);
1549
+ if (withStyles && template !== "sfc") {
1550
+ const styleContent = `/**
1551
+ * ${templateData.pascalName} Styles
1552
+ */
1553
+
1554
+ .${templateData.kebabName} {
1555
+ /* Component styles */
1556
+ }
1557
+ `;
1558
+ const stylePath = path.join(targetDir, `${templateData.kebabName}.styles.css`);
1559
+ await writeFile(stylePath, styleContent);
1560
+ logger.success(`Generated styles: ${stylePath}`);
1561
+ }
1562
+ if (withTest && template !== "sfc") {
1563
+ logger.info("Test file included in generated component");
1564
+ }
1565
+ logger.info("\nNext steps:");
1566
+ logger.info(` cd ${targetDir}`);
1567
+ logger.info(
1568
+ ` Import your ${type}: import { ${template === "sfc" ? "default" : templateData.pascalName} } from './${templateData.kebabName}'`
1569
+ );
1570
+ logger.info("\nAvailable options:");
1571
+ logger.info(" --template=sfc : Single File Component (.lyt)");
1572
+ logger.info(" --template=functional : Functional component");
1573
+ logger.info(" --language=js : JavaScript output");
1574
+ }
767
1575
  var PLUGIN_TEMPLATES = {
768
1576
  default: "Default plugin template with TypeScript",
769
1577
  minimal: "Minimal plugin without extra dependencies",
@@ -772,45 +1580,53 @@ var PLUGIN_TEMPLATES = {
772
1580
  function getTemplateContent(template, pluginName) {
773
1581
  const packageName = `@lytjs/plugin-${pluginName}`;
774
1582
  const files = {};
775
- files["package.json"] = JSON.stringify({
776
- name: packageName,
777
- version: "0.1.0",
778
- description: `LytJS plugin: ${pluginName}`,
779
- main: "dist/index.cjs",
780
- module: "dist/index.mjs",
781
- types: "dist/index.d.ts",
782
- exports: {
783
- ".": {
784
- import: "./dist/index.mjs",
785
- require: "./dist/index.cjs",
786
- types: "./dist/index.d.ts"
1583
+ files["package.json"] = JSON.stringify(
1584
+ {
1585
+ name: packageName,
1586
+ version: "0.1.0",
1587
+ description: `LytJS plugin: ${pluginName}`,
1588
+ main: "dist/index.cjs",
1589
+ module: "dist/index.mjs",
1590
+ types: "dist/index.d.ts",
1591
+ exports: {
1592
+ ".": {
1593
+ import: "./dist/index.mjs",
1594
+ require: "./dist/index.cjs",
1595
+ types: "./dist/index.d.ts"
1596
+ }
1597
+ },
1598
+ files: ["dist"],
1599
+ scripts: {
1600
+ build: "tsup",
1601
+ dev: "tsup --watch",
1602
+ test: "vitest",
1603
+ lint: "eslint src",
1604
+ prepublishOnly: "npm run build"
1605
+ },
1606
+ keywords: ["lytjs", "plugin"],
1607
+ license: "MIT",
1608
+ peerDependencies: {
1609
+ "@lytjs/core": ">=6.0.0"
787
1610
  }
788
1611
  },
789
- files: ["dist"],
790
- scripts: {
791
- build: "tsup",
792
- dev: "tsup --watch",
793
- test: "vitest",
794
- lint: "eslint src",
795
- "prepublishOnly": "npm run build"
1612
+ null,
1613
+ 2
1614
+ );
1615
+ files["tsconfig.json"] = JSON.stringify(
1616
+ {
1617
+ extends: "@lytjs/core/tsconfig.json",
1618
+ compilerOptions: {
1619
+ outDir: "./dist",
1620
+ rootDir: "./src",
1621
+ declaration: true,
1622
+ declarationMap: true
1623
+ },
1624
+ include: ["src"],
1625
+ exclude: ["node_modules", "dist", "tests"]
796
1626
  },
797
- keywords: ["lytjs", "plugin"],
798
- license: "MIT",
799
- peerDependencies: {
800
- "@lytjs/core": ">=6.0.0"
801
- }
802
- }, null, 2);
803
- files["tsconfig.json"] = JSON.stringify({
804
- extends: "@lytjs/core/tsconfig.json",
805
- compilerOptions: {
806
- outDir: "./dist",
807
- rootDir: "./src",
808
- declaration: true,
809
- declarationMap: true
810
- },
811
- include: ["src"],
812
- exclude: ["node_modules", "dist", "tests"]
813
- }, null, 2);
1627
+ null,
1628
+ 2
1629
+ );
814
1630
  files["tsup.config.ts"] = `import { defineConfig } from 'tsup';
815
1631
 
816
1632
  export default defineConfig({
@@ -855,8 +1671,6 @@ const optionsSchema: ConfigSchema<${pluginName.replace(/-/g, "")}Options> = {
855
1671
  additionalProperties: false,
856
1672
  };
857
1673
 
858
- ${pluginName.replace(/-/g, "")}Options\`;
859
-
860
1674
  export interface ${pluginName.replace(/-/g, "")}Options {
861
1675
  debug?: boolean;
862
1676
  option1?: string;
@@ -1024,7 +1838,16 @@ async function buildPlugin(options = {}) {
1024
1838
  logger.info("Building plugin...");
1025
1839
  try {
1026
1840
  const buildCmd = "tsup";
1027
- const args = ["--entry", "src/index.ts", "--outDir", outDir, "--format", "esm,cjs", "--dts", "--sourcemap"];
1841
+ const args = [
1842
+ "--entry",
1843
+ "src/index.ts",
1844
+ "--outDir",
1845
+ outDir,
1846
+ "--format",
1847
+ "esm,cjs",
1848
+ "--dts",
1849
+ "--sourcemap"
1850
+ ];
1028
1851
  if (options.minify) {
1029
1852
  args.push("--minify");
1030
1853
  }
@@ -1090,8 +1913,8 @@ async function validatePlugin(options = {}) {
1090
1913
  if (!existsSync(tsupConfigPath)) {
1091
1914
  warnings.push("tsup.config.ts not found (recommended for builds)");
1092
1915
  }
1093
- let hasErrors = errors.length > 0;
1094
- let hasWarnings = warnings.length > 0;
1916
+ const hasErrors = errors.length > 0;
1917
+ const hasWarnings = warnings.length > 0;
1095
1918
  if (hasErrors) {
1096
1919
  logger.error("Validation failed with errors:");
1097
1920
  for (const err of errors) {
@@ -1120,7 +1943,7 @@ async function validatePlugin(options = {}) {
1120
1943
  }
1121
1944
  function isEmptyDir2(dir) {
1122
1945
  if (!existsSync(dir)) return true;
1123
- const files = __require("fs").readdirSync(dir);
1946
+ const files = readdirSync(dir);
1124
1947
  return files.length === 0;
1125
1948
  }
1126
1949
  function listPluginTemplates() {
@@ -1139,7 +1962,7 @@ async function runCli(rawArgs = process.argv.slice(2)) {
1139
1962
  return;
1140
1963
  }
1141
1964
  if (options.version || command === "version" || command === "-v" || command === "--version") {
1142
- console.log(`LytJS CLI v${VERSION}`);
1965
+ console.warn(`LytJS CLI v${VERSION}`);
1143
1966
  return;
1144
1967
  }
1145
1968
  switch (command) {
@@ -1173,9 +1996,20 @@ async function runCli(rawArgs = process.argv.slice(2)) {
1173
1996
  grep: options.grep
1174
1997
  });
1175
1998
  break;
1176
- case "add":
1177
- if (!args[0] || !["component", "page", "store"].includes(args[0])) {
1178
- logger.error("Usage: lyt add <component|page|store> <name>");
1999
+ case "add": {
2000
+ const addTypes = [
2001
+ "component",
2002
+ "page",
2003
+ "store",
2004
+ "directive",
2005
+ "composable",
2006
+ "util",
2007
+ "middleware",
2008
+ "hook"
2009
+ ];
2010
+ if (!args[0] || !addTypes.includes(args[0])) {
2011
+ logger.error("Usage: lyt add <type> <name>");
2012
+ logger.info("Types: component, page, store, directive, composable, util, middleware, hook");
1179
2013
  logger.info("Example: lyt add component Button");
1180
2014
  process.exit(1);
1181
2015
  }
@@ -1183,7 +2017,27 @@ async function runCli(rawArgs = process.argv.slice(2)) {
1183
2017
  force: options.force
1184
2018
  });
1185
2019
  break;
1186
- case "plugin":
2020
+ }
2021
+ case "generate":
2022
+ case "g": {
2023
+ const genTypes = ["component", "page", "service", "hook", "store"];
2024
+ if (!args[0] || !genTypes.includes(args[0])) {
2025
+ logger.error("Usage: lyt generate <type> <name>");
2026
+ logger.info("Types: component, page, service, hook, store");
2027
+ logger.info("Example: lyt generate component Button");
2028
+ process.exit(1);
2029
+ }
2030
+ await generate({
2031
+ type: args[0],
2032
+ name: args[1] || "Unnamed",
2033
+ path: options.path,
2034
+ withStyles: options.styles,
2035
+ withTest: options.test,
2036
+ withStorybook: options.storybook
2037
+ });
2038
+ break;
2039
+ }
2040
+ case "plugin": {
1187
2041
  if (!args[0]) {
1188
2042
  logger.error("Usage: lyt plugin <create|build|validate|templates>");
1189
2043
  logger.info("Example: lyt plugin create my-plugin");
@@ -1220,6 +2074,7 @@ async function runCli(rawArgs = process.argv.slice(2)) {
1220
2074
  process.exit(1);
1221
2075
  }
1222
2076
  break;
2077
+ }
1223
2078
  default:
1224
2079
  if (command) {
1225
2080
  logger.error(`Unknown command: ${command}`);
@@ -1266,7 +2121,7 @@ function parseArgs(args) {
1266
2121
  return { command, args: positional, options };
1267
2122
  }
1268
2123
  function showHelp() {
1269
- console.log(`
2124
+ console.warn(`
1270
2125
  ${logger.bold("LytJS CLI")} v${VERSION}
1271
2126
 
1272
2127
  ${logger.bold("Usage:")}
@@ -1278,10 +2133,11 @@ ${logger.bold("Commands:")}
1278
2133
  dev Start development server
1279
2134
  build Build for production
1280
2135
  test Run tests
1281
- add <type> <name> Generate a component, page, or store
2136
+ add <type> <name> Generate a component, page, store, directive, composable, etc.
2137
+ generate, g Advanced code generation (component, page, service, hook, store)
1282
2138
  plugin <subcmd> Plugin development commands
1283
2139
  help Show this help message
1284
-
2140
+
1285
2141
  ${logger.bold("Options:")}
1286
2142
  --version, -v Show version number
1287
2143
  --help Show help
@@ -1305,6 +2161,12 @@ ${logger.bold("Test Options:")}
1305
2161
  --coverage Generate coverage report
1306
2162
  --grep <pattern> Filter tests by pattern
1307
2163
 
2164
+ ${logger.bold("Generate Options:")}
2165
+ --path <dir> Output directory (default: ./src)
2166
+ --styles Generate CSS styles file
2167
+ --test Generate test file
2168
+ --storybook Generate Storybook story file
2169
+
1308
2170
  ${logger.bold("Plugin Options:")}
1309
2171
  --template <name> Use a specific plugin template (default, minimal, withConfig)
1310
2172
  --force Overwrite existing directory
@@ -1325,6 +2187,16 @@ ${logger.bold("Examples:")}
1325
2187
  lyt add component Button
1326
2188
  lyt add page About
1327
2189
  lyt add store user
2190
+ lyt directive click-outside
2191
+ lyt add composable fetch-data
2192
+ lyt add util format
2193
+ lyt add hook window-size
2194
+ lyt generate component Button --styles --test
2195
+ lyt generate page Dashboard --path ./src/pages
2196
+ lyt generate service User --path ./src/services
2197
+ lyt generate hook useCounter
2198
+ lyt generate store Auth --path ./src/stores
2199
+ lyt g page Login
1328
2200
  lyt plugin create my-plugin
1329
2201
  lyt plugin create my-plugin --template withConfig
1330
2202
  lyt plugin build
@@ -1337,6 +2209,6 @@ if (__require.main === module) {
1337
2209
  runCli().catch(console.error);
1338
2210
  }
1339
2211
 
1340
- export { add, build, buildPlugin, create, createPlugin, detectPackageManager, dev, ensureDir, exists, getAddCommand, getInstallCommand, getRunCommand, listPluginTemplates, listTemplates, logger, readFile, runCli, test, validatePlugin, writeFile };
2212
+ export { add, build, buildPlugin, create, createPlugin, detectPackageManager, dev, ensureDir, exists, generate, getAddCommand, getInstallCommand, getRunCommand, listPluginTemplates, listTemplates, logger, readFile, runCli, test, validatePlugin, writeFile };
1341
2213
  //# sourceMappingURL=lyt.mjs.map
1342
2214
  //# sourceMappingURL=lyt.mjs.map