@fluixi/core 1.0.0-alpha.53

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.
Files changed (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +611 -0
  3. package/dist/cdn/core.global.js +3 -0
  4. package/dist/cli/generate.cjs +1814 -0
  5. package/dist/cli/generate.d.ts +7 -0
  6. package/dist/cli/generate.d.ts.map +1 -0
  7. package/dist/cli/generate.js +2580 -0
  8. package/dist/cli/generate.mjs +1814 -0
  9. package/dist/cli/index.cjs +1814 -0
  10. package/dist/cli/index.d.ts +7 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/index.js +2580 -0
  13. package/dist/cli/index.mjs +1814 -0
  14. package/dist/cli/run.cjs +2 -0
  15. package/dist/cli/run.d.ts +3 -0
  16. package/dist/cli/run.d.ts.map +1 -0
  17. package/dist/cli/run.js +62 -0
  18. package/dist/cli/run.mjs +2 -0
  19. package/dist/fluixi.d.ts +5 -0
  20. package/dist/src/cdn/chunk-PFM5O5UE.mjs +1 -0
  21. package/dist/src/cdn/core.cjs +1 -0
  22. package/dist/src/cdn/core.d.ts +16 -0
  23. package/dist/src/cdn/core.d.ts.map +1 -0
  24. package/dist/src/cdn/core.js +15 -0
  25. package/dist/src/cdn/core.mjs +1 -0
  26. package/dist/src/cdn/router-next-WNWCFVC2.mjs +1 -0
  27. package/dist/src/index.cjs +1 -0
  28. package/dist/src/index.d.ts +7 -0
  29. package/dist/src/index.d.ts.map +1 -0
  30. package/dist/src/index.js +26 -0
  31. package/dist/src/index.mjs +1 -0
  32. package/dist/src/jsx-dev-runtime.cjs +1 -0
  33. package/dist/src/jsx-dev-runtime.d.ts +2 -0
  34. package/dist/src/jsx-dev-runtime.d.ts.map +1 -0
  35. package/dist/src/jsx-dev-runtime.js +1 -0
  36. package/dist/src/jsx-dev-runtime.mjs +1 -0
  37. package/dist/src/jsx-runtime.cjs +1 -0
  38. package/dist/src/jsx-runtime.d.ts +7 -0
  39. package/dist/src/jsx-runtime.d.ts.map +1 -0
  40. package/dist/src/jsx-runtime.js +17 -0
  41. package/dist/src/jsx-runtime.mjs +1 -0
  42. package/dist/src/lib/client/chunk-D3VTWVNJ.mjs +1 -0
  43. package/dist/src/lib/client/index.cjs +1 -0
  44. package/dist/src/lib/client/index.d.ts +54 -0
  45. package/dist/src/lib/client/index.d.ts.map +1 -0
  46. package/dist/src/lib/client/index.js +142 -0
  47. package/dist/src/lib/client/index.mjs +1 -0
  48. package/dist/src/lib/client/router-next-UYIYMRJL.mjs +1 -0
  49. package/dist/src/lib/context/context.cjs +1 -0
  50. package/dist/src/lib/context/context.d.ts +337 -0
  51. package/dist/src/lib/context/context.d.ts.map +1 -0
  52. package/dist/src/lib/context/context.js +462 -0
  53. package/dist/src/lib/context/context.mjs +1 -0
  54. package/dist/src/lib/context/index.cjs +1 -0
  55. package/dist/src/lib/context/index.d.ts +3 -0
  56. package/dist/src/lib/context/index.d.ts.map +1 -0
  57. package/dist/src/lib/context/index.js +5 -0
  58. package/dist/src/lib/context/index.mjs +1 -0
  59. package/dist/src/lib/control-flow.cjs +1 -0
  60. package/dist/src/lib/control-flow.d.ts +4 -0
  61. package/dist/src/lib/control-flow.d.ts.map +1 -0
  62. package/dist/src/lib/control-flow.js +1 -0
  63. package/dist/src/lib/control-flow.mjs +1 -0
  64. package/dist/src/lib/core.cjs +1 -0
  65. package/dist/src/lib/core.d.ts +90 -0
  66. package/dist/src/lib/core.d.ts.map +1 -0
  67. package/dist/src/lib/core.js +181 -0
  68. package/dist/src/lib/core.mjs +1 -0
  69. package/dist/src/lib/directives/lit.cjs +1 -0
  70. package/dist/src/lib/directives/lit.d.ts +2 -0
  71. package/dist/src/lib/directives/lit.d.ts.map +1 -0
  72. package/dist/src/lib/directives/lit.js +31 -0
  73. package/dist/src/lib/directives/lit.mjs +0 -0
  74. package/dist/src/lib/env/index.cjs +1 -0
  75. package/dist/src/lib/env/index.d.ts +39 -0
  76. package/dist/src/lib/env/index.d.ts.map +1 -0
  77. package/dist/src/lib/env/index.js +50 -0
  78. package/dist/src/lib/env/index.mjs +1 -0
  79. package/dist/src/lib/i18n/index.cjs +1 -0
  80. package/dist/src/lib/i18n/index.d.ts +79 -0
  81. package/dist/src/lib/i18n/index.d.ts.map +1 -0
  82. package/dist/src/lib/i18n/index.js +127 -0
  83. package/dist/src/lib/i18n/index.mjs +1 -0
  84. package/dist/src/lib/index.cjs +1 -0
  85. package/dist/src/lib/index.d.ts +83 -0
  86. package/dist/src/lib/index.d.ts.map +1 -0
  87. package/dist/src/lib/index.js +147 -0
  88. package/dist/src/lib/index.mjs +1 -0
  89. package/dist/src/lib/isomorphic.cjs +1 -0
  90. package/dist/src/lib/isomorphic.d.ts +69 -0
  91. package/dist/src/lib/isomorphic.d.ts.map +1 -0
  92. package/dist/src/lib/isomorphic.js +92 -0
  93. package/dist/src/lib/isomorphic.mjs +1 -0
  94. package/dist/src/lib/jsx-runtime/index.cjs +1 -0
  95. package/dist/src/lib/jsx-runtime/index.d.ts +13 -0
  96. package/dist/src/lib/jsx-runtime/index.d.ts.map +1 -0
  97. package/dist/src/lib/jsx-runtime/index.js +23 -0
  98. package/dist/src/lib/jsx-runtime/index.mjs +1 -0
  99. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.cjs +1 -0
  100. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.d.ts +2 -0
  101. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.d.ts.map +1 -0
  102. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.js +1 -0
  103. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.mjs +1 -0
  104. package/dist/src/lib/plugins/fluixi-routes-plugin.cjs +13 -0
  105. package/dist/src/lib/plugins/fluixi-routes-plugin.d.ts +21 -0
  106. package/dist/src/lib/plugins/fluixi-routes-plugin.d.ts.map +1 -0
  107. package/dist/src/lib/plugins/fluixi-routes-plugin.js +102 -0
  108. package/dist/src/lib/plugins/fluixi-routes-plugin.mjs +13 -0
  109. package/dist/src/lib/plugins/index.cjs +13 -0
  110. package/dist/src/lib/plugins/index.d.ts +4 -0
  111. package/dist/src/lib/plugins/index.d.ts.map +1 -0
  112. package/dist/src/lib/plugins/index.js +3 -0
  113. package/dist/src/lib/plugins/index.mjs +13 -0
  114. package/dist/src/lib/plugins/route-codegen.cjs +8 -0
  115. package/dist/src/lib/plugins/route-codegen.d.ts +13 -0
  116. package/dist/src/lib/plugins/route-codegen.d.ts.map +1 -0
  117. package/dist/src/lib/plugins/route-codegen.js +81 -0
  118. package/dist/src/lib/plugins/route-codegen.mjs +8 -0
  119. package/dist/src/lib/plugins/route-scanner.cjs +1 -0
  120. package/dist/src/lib/plugins/route-scanner.d.ts +39 -0
  121. package/dist/src/lib/plugins/route-scanner.d.ts.map +1 -0
  122. package/dist/src/lib/plugins/route-scanner.js +216 -0
  123. package/dist/src/lib/plugins/route-scanner.mjs +1 -0
  124. package/dist/src/lib/plugins/vite.cjs +1 -0
  125. package/dist/src/lib/plugins/vite.d.ts +3 -0
  126. package/dist/src/lib/plugins/vite.d.ts.map +1 -0
  127. package/dist/src/lib/plugins/vite.js +1 -0
  128. package/dist/src/lib/plugins/vite.mjs +1 -0
  129. package/dist/src/lib/render/component.cjs +1 -0
  130. package/dist/src/lib/render/component.d.ts +328 -0
  131. package/dist/src/lib/render/component.d.ts.map +1 -0
  132. package/dist/src/lib/render/component.js +520 -0
  133. package/dist/src/lib/render/component.mjs +1 -0
  134. package/dist/src/lib/render/index.cjs +1 -0
  135. package/dist/src/lib/render/index.d.ts +113 -0
  136. package/dist/src/lib/render/index.d.ts.map +1 -0
  137. package/dist/src/lib/render/index.js +201 -0
  138. package/dist/src/lib/render/index.mjs +1 -0
  139. package/dist/src/lib/render/lazy.cjs +1 -0
  140. package/dist/src/lib/render/lazy.d.ts +28 -0
  141. package/dist/src/lib/render/lazy.d.ts.map +1 -0
  142. package/dist/src/lib/render/lazy.js +62 -0
  143. package/dist/src/lib/render/lazy.mjs +1 -0
  144. package/dist/src/lib/render/suspense.cjs +1 -0
  145. package/dist/src/lib/render/suspense.d.ts +25 -0
  146. package/dist/src/lib/render/suspense.d.ts.map +1 -0
  147. package/dist/src/lib/render/suspense.js +139 -0
  148. package/dist/src/lib/render/suspense.mjs +1 -0
  149. package/dist/src/lib/router-next/data.cjs +1 -0
  150. package/dist/src/lib/router-next/data.d.ts +101 -0
  151. package/dist/src/lib/router-next/data.d.ts.map +1 -0
  152. package/dist/src/lib/router-next/data.js +179 -0
  153. package/dist/src/lib/router-next/data.mjs +1 -0
  154. package/dist/src/lib/router-next/index.cjs +1 -0
  155. package/dist/src/lib/router-next/index.d.ts +156 -0
  156. package/dist/src/lib/router-next/index.d.ts.map +1 -0
  157. package/dist/src/lib/router-next/index.js +464 -0
  158. package/dist/src/lib/router-next/index.mjs +1 -0
  159. package/dist/src/lib/server/hydration.cjs +1 -0
  160. package/dist/src/lib/server/hydration.d.ts +238 -0
  161. package/dist/src/lib/server/hydration.d.ts.map +1 -0
  162. package/dist/src/lib/server/hydration.js +384 -0
  163. package/dist/src/lib/server/hydration.mjs +1 -0
  164. package/dist/src/lib/server/index.cjs +16 -0
  165. package/dist/src/lib/server/index.d.ts +91 -0
  166. package/dist/src/lib/server/index.d.ts.map +1 -0
  167. package/dist/src/lib/server/index.js +141 -0
  168. package/dist/src/lib/server/index.mjs +16 -0
  169. package/dist/src/lib/server/reactive.cjs +1 -0
  170. package/dist/src/lib/server/reactive.d.ts +298 -0
  171. package/dist/src/lib/server/reactive.d.ts.map +1 -0
  172. package/dist/src/lib/server/reactive.js +472 -0
  173. package/dist/src/lib/server/reactive.mjs +1 -0
  174. package/dist/src/lib/theme/index.cjs +1 -0
  175. package/dist/src/lib/theme/index.d.ts +51 -0
  176. package/dist/src/lib/theme/index.d.ts.map +1 -0
  177. package/dist/src/lib/theme/index.js +79 -0
  178. package/dist/src/lib/theme/index.mjs +1 -0
  179. package/dist/src/lib/utils/index.cjs +1 -0
  180. package/dist/src/lib/utils/index.d.ts +379 -0
  181. package/dist/src/lib/utils/index.d.ts.map +1 -0
  182. package/dist/src/lib/utils/index.js +527 -0
  183. package/dist/src/lib/utils/index.mjs +1 -0
  184. package/dist/src/rx.cjs +1 -0
  185. package/dist/src/rx.d.ts +2 -0
  186. package/dist/src/rx.d.ts.map +1 -0
  187. package/dist/src/rx.js +101 -0
  188. package/dist/src/rx.mjs +0 -0
  189. package/dist/src/server.cjs +16 -0
  190. package/dist/src/server.d.ts +64 -0
  191. package/dist/src/server.d.ts.map +1 -0
  192. package/dist/src/server.js +63 -0
  193. package/dist/src/server.mjs +16 -0
  194. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  195. package/package.json +128 -0
  196. package/src/index.ts +31 -0
  197. package/types/fluixi.d.ts +5 -0
  198. package/types/vinxi.d.ts +50 -0
@@ -0,0 +1,2580 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview Fluixi CLI - Command-line interface for project and component generation
4
+ * @module @fluixi/core/cli
5
+ */
6
+ import { program } from 'commander';
7
+ import inquirer from 'inquirer';
8
+ import chalk from 'chalk';
9
+ import fs from 'fs-extra';
10
+ import path from 'path';
11
+ // const __filename = fileURLToPath(import.meta.url);
12
+ // const __dirname = path.dirname(__filename);
13
+ /**
14
+ * CLI version
15
+ */
16
+ const VERSION = '0.0.1';
17
+ /**
18
+ * Generates a basic component template
19
+ * @param {string} name - Component name
20
+ * @returns {string} Component code
21
+ */
22
+ function generateBasicComponent(name, pattern = 'functional') {
23
+ if (pattern === 'fluixilement') {
24
+ return `/**
25
+ * @fileoverview ${name} component
26
+ */
27
+
28
+ import { html } from 'lit';
29
+ import { customElement } from 'lit/decorators.js';
30
+ import { FluixiElement } from '@fluixi/core';
31
+
32
+ /**
33
+ * ${name} component using FluixiElement pattern
34
+ *
35
+ * @example
36
+ * \`\`\`html
37
+ * <${name.toLowerCase()}></${name.toLowerCase()}>
38
+ * \`\`\`
39
+ */
40
+ @customElement('${name.toLowerCase()}')
41
+ export class ${name} extends FluixiElement {
42
+ view() {
43
+ return html\`
44
+ <div class="${name.toLowerCase()}">
45
+ <h2>${name}</h2>
46
+ <p>Component created with Fluixi CLI (FluixiElement pattern)</p>
47
+ </div>
48
+ \`;
49
+ }
50
+ }
51
+
52
+ export default ${name};
53
+ `;
54
+ }
55
+ if (pattern === 'withsignals') {
56
+ return `/**
57
+ * @fileoverview ${name} component
58
+ */
59
+
60
+ import { html, LitElement } from 'lit';
61
+ import { customElement } from 'lit/decorators.js';
62
+ import { WithSignals } from '@fluixi/core';
63
+
64
+ /**
65
+ * ${name} component using WithSignals mixin
66
+ *
67
+ * @example
68
+ * \`\`\`html
69
+ * <${name.toLowerCase()}></${name.toLowerCase()}>
70
+ * \`\`\`
71
+ */
72
+ @customElement('${name.toLowerCase()}')
73
+ export class ${name} extends WithSignals(LitElement) {
74
+ render() {
75
+ return html\`
76
+ <div class="${name.toLowerCase()}">
77
+ <h2>${name}</h2>
78
+ <p>Component created with Fluixi CLI (WithSignals pattern)</p>
79
+ </div>
80
+ \`;
81
+ }
82
+ }
83
+
84
+ export default ${name};
85
+ `;
86
+ }
87
+ if (pattern === 'controller') {
88
+ return `/**
89
+ * @fileoverview ${name} component
90
+ */
91
+
92
+ import { html, LitElement } from 'lit';
93
+ import { customElement } from 'lit/decorators.js';
94
+ import { SignalController } from '@fluixi/core';
95
+
96
+ /**
97
+ * ${name} component using manual controller pattern
98
+ *
99
+ * @example
100
+ * \`\`\`html
101
+ * <${name.toLowerCase()}></${name.toLowerCase()}>
102
+ * \`\`\`
103
+ */
104
+ @customElement('${name.toLowerCase()}')
105
+ export class ${name} extends LitElement {
106
+ private signals = new SignalController(this);
107
+
108
+ render() {
109
+ return html\`
110
+ <div class="${name.toLowerCase()}">
111
+ <h2>${name}</h2>
112
+ <p>Component created with Fluixi CLI (Controller pattern)</p>
113
+ </div>
114
+ \`;
115
+ }
116
+ }
117
+
118
+ export default ${name};
119
+ `;
120
+ }
121
+ if (pattern === 'litelement') {
122
+ return `/**
123
+ * @fileoverview ${name} component
124
+ */
125
+
126
+ import { html, css, LitElement } from 'lit';
127
+ import { customElement, property } from 'lit/decorators.js';
128
+
129
+ /**
130
+ * ${name} component using plain LitElement
131
+ *
132
+ * @example
133
+ * \`\`\`html
134
+ * <${name.toLowerCase()}></${name.toLowerCase()}>
135
+ * \`\`\`
136
+ */
137
+ @customElement('${name.toLowerCase()}')
138
+ export class ${name} extends LitElement {
139
+ static styles = css\`
140
+ :host {
141
+ display: block;
142
+ padding: 1rem;
143
+ }
144
+
145
+ .${name.toLowerCase()} {
146
+ font-family: sans-serif;
147
+ }
148
+ \`;
149
+
150
+ render() {
151
+ return html\`
152
+ <div class="${name.toLowerCase()}">
153
+ <h2>${name}</h2>
154
+ <p>Component created with Fluixi CLI (LitElement pattern)</p>
155
+ </div>
156
+ \`;
157
+ }
158
+ }
159
+
160
+ export default ${name};
161
+ `;
162
+ }
163
+ // Functional pattern (default)
164
+ return `/**
165
+ * @fileoverview ${name} component
166
+ */
167
+
168
+ import { html, Component, ComponentProps } from '@fluixi/core';
169
+
170
+ /**
171
+ * Props for ${name} component
172
+ */
173
+ export interface ${name}Props extends ComponentProps {
174
+ // Add your props here
175
+ }
176
+
177
+ /**
178
+ * ${name} component (functional pattern)
179
+ * @param {${name}Props} props - Component props
180
+ * @returns {TemplateResult} Rendered template
181
+ *
182
+ * @example
183
+ * \`\`\`typescript
184
+ * const MyPage = () => {
185
+ * return html\`
186
+ * <div>
187
+ * \${${name}({})}
188
+ * </div>
189
+ * \`;
190
+ * };
191
+ * \`\`\`
192
+ */
193
+ export const ${name}: Component<${name}Props> = (props) => {
194
+ return html\`
195
+ <div class="${name.toLowerCase()}">
196
+ <h2>${name}</h2>
197
+ <p>Component created with Fluixi CLI</p>
198
+ </div>
199
+ \`;
200
+ };
201
+
202
+ export default ${name};
203
+ `;
204
+ }
205
+ /**
206
+ * Generates a component with state
207
+ * @param {string} name - Component name
208
+ * @param {ComponentPattern} pattern - Component pattern to use
209
+ * @returns {string} Component code
210
+ */
211
+ function generateComponentWithState(name, pattern = 'functional') {
212
+ if (pattern === 'litelement') {
213
+ return `/**
214
+ * @fileoverview ${name} component with state
215
+ */
216
+
217
+ import { html, css, LitElement } from 'lit';
218
+ import { customElement, property, state } from 'lit/decorators.js';
219
+
220
+ /**
221
+ * ${name} component with state using plain LitElement
222
+ *
223
+ * @example
224
+ * \`\`\`html
225
+ * <${name.toLowerCase()}></${name.toLowerCase()}>
226
+ * \`\`\`
227
+ */
228
+ @customElement('${name.toLowerCase()}')
229
+ export class ${name} extends LitElement {
230
+ static styles = css\`
231
+ :host {
232
+ display: block;
233
+ padding: 1rem;
234
+ }
235
+
236
+ .${name.toLowerCase()} {
237
+ font-family: sans-serif;
238
+ }
239
+
240
+ .counter {
241
+ display: flex;
242
+ gap: 1rem;
243
+ align-items: center;
244
+ margin: 1rem 0;
245
+ }
246
+
247
+ button {
248
+ padding: 0.5rem 1rem;
249
+ font-size: 1rem;
250
+ cursor: pointer;
251
+ }
252
+
253
+ .count {
254
+ font-size: 2rem;
255
+ font-weight: bold;
256
+ min-width: 3rem;
257
+ text-align: center;
258
+ }
259
+ \`;
260
+
261
+ @state()
262
+ private count = 0;
263
+
264
+ @state()
265
+ private message = '';
266
+
267
+ connectedCallback() {
268
+ super.connectedCallback();
269
+ this.updateMessage();
270
+ }
271
+
272
+ private updateMessage() {
273
+ this.message = \`Count is \${this.count}\`;
274
+ }
275
+
276
+ private increment() {
277
+ this.count++;
278
+ this.updateMessage();
279
+ }
280
+
281
+ private decrement() {
282
+ this.count--;
283
+ this.updateMessage();
284
+ }
285
+
286
+ private reset() {
287
+ this.count = 0;
288
+ this.updateMessage();
289
+ }
290
+
291
+ render() {
292
+ return html\`
293
+ <div class="${name.toLowerCase()}">
294
+ <h2>${name} Counter</h2>
295
+ <p>\${this.message}</p>
296
+ <div class="counter">
297
+ <button @click=\${this.decrement}>-</button>
298
+ <span class="count">\${this.count}</span>
299
+ <button @click=\${this.increment}>+</button>
300
+ </div>
301
+ <button @click=\${this.reset}>Reset</button>
302
+ </div>
303
+ \`;
304
+ }
305
+ }
306
+
307
+ export default ${name};
308
+ `;
309
+ }
310
+ if (pattern === 'fluixilement') {
311
+ return `/**
312
+ * @fileoverview ${name} component with reactive state
313
+ */
314
+
315
+ import { html } from 'lit';
316
+ import { customElement } from 'lit/decorators.js';
317
+ import { FluixiElement } from '@fluixi/core';
318
+ import { createSignal, createEffect } from '@fluixi/reactive';
319
+
320
+ /**
321
+ * ${name} component with reactive state using FluixiElement
322
+ *
323
+ * @example
324
+ * \`\`\`html
325
+ * <${name.toLowerCase()} initial-value="10"></${name.toLowerCase()}>
326
+ * \`\`\`
327
+ */
328
+ @customElement('${name.toLowerCase()}')
329
+ export class ${name} extends FluixiElement {
330
+ private count = createSignal(0);
331
+ private message = createSignal('');
332
+
333
+ connectedCallback() {
334
+ super.connectedCallback();
335
+
336
+ // Effect to update message when count changes
337
+ this.runInScope(() => {
338
+ createEffect(() => {
339
+ const current = this.count[0]();
340
+ this.message[1](\`Count is \${current}\`);
341
+ });
342
+ });
343
+ }
344
+
345
+ private increment = () => this.count[1](c => c + 1);
346
+ private decrement = () => this.count[1](c => c - 1);
347
+ private reset = () => this.count[1](0);
348
+
349
+ view() {
350
+ return html\`
351
+ <div class="${name.toLowerCase()}">
352
+ <h2>${name}</h2>
353
+ <div class="counter">
354
+ <p class="count">\${this.count[0]()}</p>
355
+ <p class="message">\${this.message[0]()}</p>
356
+ <div class="controls">
357
+ <button @click=\${this.decrement}>-</button>
358
+ <button @click=\${this.reset}>Reset</button>
359
+ <button @click=\${this.increment}>+</button>
360
+ </div>
361
+ </div>
362
+ </div>
363
+ \`;
364
+ }
365
+ }
366
+
367
+ export default ${name};
368
+ `;
369
+ }
370
+ if (pattern === 'withsignals') {
371
+ return `/**
372
+ * @fileoverview ${name} component with reactive state
373
+ */
374
+
375
+ import { html, LitElement } from 'lit';
376
+ import { customElement } from 'lit/decorators.js';
377
+ import { WithSignals } from '@fluixi/core';
378
+ import { createSignal, createEffect } from '@fluixi/reactive';
379
+
380
+ /**
381
+ * ${name} component with reactive state using WithSignals mixin
382
+ *
383
+ * @example
384
+ * \`\`\`html
385
+ * <${name.toLowerCase()}></${name.toLowerCase()}>
386
+ * \`\`\`
387
+ */
388
+ @customElement('${name.toLowerCase()}')
389
+ export class ${name} extends WithSignals(LitElement) {
390
+ private count = createSignal(0);
391
+ private message = createSignal('');
392
+
393
+ connectedCallback() {
394
+ super.connectedCallback();
395
+
396
+ // Effect to update message when count changes
397
+ this.runInReactiveScope(() => {
398
+ createEffect(() => {
399
+ const current = this.count[0]();
400
+ this.message[1](\`Count is \${current}\`);
401
+ });
402
+ });
403
+ }
404
+
405
+ private increment = () => this.count[1](c => c + 1);
406
+ private decrement = () => this.count[1](c => c - 1);
407
+ private reset = () => this.count[1](0);
408
+
409
+ render() {
410
+ return html\`
411
+ <div class="${name.toLowerCase()}">
412
+ <h2>${name}</h2>
413
+ <div class="counter">
414
+ <p class="count">\${this.count[0]()}</p>
415
+ <p class="message">\${this.message[0]()}</p>
416
+ <div class="controls">
417
+ <button @click=\${this.decrement}>-</button>
418
+ <button @click=\${this.reset}>Reset</button>
419
+ <button @click=\${this.increment}>+</button>
420
+ </div>
421
+ </div>
422
+ </div>
423
+ \`;
424
+ }
425
+ }
426
+
427
+ export default ${name};
428
+ `;
429
+ }
430
+ if (pattern === 'controller') {
431
+ return `/**
432
+ * @fileoverview ${name} component with reactive state
433
+ */
434
+
435
+ import { html, LitElement } from 'lit';
436
+ import { customElement } from 'lit/decorators.js';
437
+ import { SignalController } from '@fluixi/core';
438
+ import { createSignal, createEffect } from '@fluixi/reactive';
439
+
440
+ /**
441
+ * ${name} component with reactive state using manual controller
442
+ *
443
+ * @example
444
+ * \`\`\`html
445
+ * <${name.toLowerCase()}></${name.toLowerCase()}>
446
+ * \`\`\`
447
+ */
448
+ @customElement('${name.toLowerCase()}')
449
+ export class ${name} extends LitElement {
450
+ private signals = new SignalController(this);
451
+ private count = createSignal(0);
452
+ private message = createSignal('');
453
+
454
+ connectedCallback() {
455
+ super.connectedCallback();
456
+
457
+ // Effect to update message when count changes
458
+ this.signals.runInScope(() => {
459
+ createEffect(() => {
460
+ const current = this.count[0]();
461
+ this.message[1](\`Count is \${current}\`);
462
+ });
463
+ });
464
+ }
465
+
466
+ private increment = () => this.count[1](c => c + 1);
467
+ private decrement = () => this.count[1](c => c - 1);
468
+ private reset = () => this.count[1](0);
469
+
470
+ render() {
471
+ return html\`
472
+ <div class="${name.toLowerCase()}">
473
+ <h2>${name}</h2>
474
+ <div class="counter">
475
+ <p class="count">\${this.count[0]()}</p>
476
+ <p class="message">\${this.message[0]()}</p>
477
+ <div class="controls">
478
+ <button @click=\${this.decrement}>-</button>
479
+ <button @click=\${this.reset}>Reset</button>
480
+ <button @click=\${this.increment}>+</button>
481
+ </div>
482
+ </div>
483
+ </div>
484
+ \`;
485
+ }
486
+ }
487
+
488
+ export default ${name};
489
+ `;
490
+ }
491
+ // Functional pattern (default)
492
+ return `/**
493
+ * @fileoverview ${name} component with reactive state
494
+ */
495
+
496
+ import { html, Component, ComponentProps } from '@fluixi/core';
497
+ import { createSignal, createEffect } from '@fluixi/reactive';
498
+
499
+ /**
500
+ * Props for ${name} component
501
+ */
502
+ export interface ${name}Props extends ComponentProps {
503
+ initialValue?: number;
504
+ }
505
+
506
+ /**
507
+ * ${name} component with reactive state management
508
+ * @param {${name}Props} props - Component props
509
+ * @returns {TemplateResult} Rendered template
510
+ *
511
+ * @example
512
+ * \`\`\`typescript
513
+ * const MyPage = () => {
514
+ * return html\`
515
+ * <div>
516
+ * \${${name}({ initialValue: 10 })}
517
+ * </div>
518
+ * \`;
519
+ * };
520
+ * \`\`\`
521
+ */
522
+ export const ${name}: Component<${name}Props> = (props) => {
523
+ const [count, setCount] = createSignal(props.initialValue ?? 0);
524
+ const [message, setMessage] = createSignal('');
525
+
526
+ // Effect to update message when count changes
527
+ createEffect(() => {
528
+ const current = count();
529
+ setMessage(\`Count is \${current}\`);
530
+ });
531
+
532
+ const increment = () => setCount(count() + 1);
533
+ const decrement = () => setCount(count() - 1);
534
+ const reset = () => setCount(props.initialValue ?? 0);
535
+
536
+ return html\`
537
+ <div class="${name.toLowerCase()}">
538
+ <h2>${name}</h2>
539
+ <div class="counter">
540
+ <p class="count">\${count()}</p>
541
+ <p class="message">\${message()}</p>
542
+ <div class="controls">
543
+ <button @click=\${decrement}>-</button>
544
+ <button @click=\${reset}>Reset</button>
545
+ <button @click=\${increment}>+</button>
546
+ </div>
547
+ </div>
548
+ </div>
549
+ \`;
550
+ };
551
+
552
+ export default ${name};
553
+ `;
554
+ }
555
+ /**
556
+ * Generates a component with async resource fetching
557
+ * @param {string} name - Component name
558
+ * @param {ComponentPattern} pattern - Component pattern to use
559
+ * @returns {string} Component code
560
+ */
561
+ function generateComponentWithResource(name, pattern = 'functional') {
562
+ if (pattern === 'litelement') {
563
+ return `/**
564
+ * @fileoverview ${name} component with async resource fetching
565
+ */
566
+
567
+ import { html, css, LitElement } from 'lit';
568
+ import { customElement, state } from 'lit/decorators.js';
569
+
570
+ interface ${name}Data {
571
+ id: string;
572
+ title: string;
573
+ description: string;
574
+ }
575
+
576
+ /**
577
+ * ${name} component with resource fetching using LitElement
578
+ *
579
+ * @example
580
+ * \`\`\`html
581
+ * <${name.toLowerCase()} resource-id="123"></${name.toLowerCase()}>
582
+ * \`\`\`
583
+ */
584
+ @customElement('${name.toLowerCase()}')
585
+ export class ${name} extends LitElement {
586
+ static styles = css\`
587
+ :host {
588
+ display: block;
589
+ padding: 1rem;
590
+ }
591
+
592
+ .loading, .error {
593
+ padding: 1rem;
594
+ margin: 1rem 0;
595
+ }
596
+
597
+ .error {
598
+ color: #dc3545;
599
+ background: #f8d7da;
600
+ border: 1px solid #f5c6cb;
601
+ }
602
+
603
+ button {
604
+ padding: 0.5rem 1rem;
605
+ cursor: pointer;
606
+ }
607
+ \`;
608
+
609
+ @state()
610
+ private data: ${name}Data | null = null;
611
+
612
+ @state()
613
+ private loading = false;
614
+
615
+ @state()
616
+ private error: Error | null = null;
617
+
618
+ connectedCallback() {
619
+ super.connectedCallback();
620
+ this.fetchData();
621
+ }
622
+
623
+ private async fetchData() {
624
+ this.loading = true;
625
+ this.error = null;
626
+
627
+ try {
628
+ const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
629
+ if (!response.ok) throw new Error('Failed to fetch');
630
+ const json = await response.json();
631
+ this.data = {
632
+ id: json.id,
633
+ title: json.title,
634
+ description: json.body,
635
+ };
636
+ } catch (err) {
637
+ this.error = err as Error;
638
+ } finally {
639
+ this.loading = false;
640
+ }
641
+ }
642
+
643
+ render() {
644
+ return html\`
645
+ <div class="${name.toLowerCase()}">
646
+ <h2>${name}</h2>
647
+
648
+ \${this.loading ? html\`<div class="loading">Loading...</div>\` : ''}
649
+
650
+ \${this.error ? html\`
651
+ <div class="error">
652
+ <p>Error: \${this.error.message}</p>
653
+ <button @click=\${this.fetchData}>Retry</button>
654
+ </div>
655
+ \` : ''}
656
+
657
+ \${this.data && !this.loading && !this.error ? html\`
658
+ <div class="content">
659
+ <h3>\${this.data.title}</h3>
660
+ <p>\${this.data.description}</p>
661
+ <button @click=\${this.fetchData}>Refresh</button>
662
+ </div>
663
+ \` : ''}
664
+ </div>
665
+ \`;
666
+ }
667
+ }
668
+
669
+ export default ${name};
670
+ `;
671
+ }
672
+ return `/**
673
+ * @fileoverview ${name} component with async resource fetching
674
+ */
675
+
676
+ import { html, Component, ComponentProps, createResource, Show } from '@fluixi/core';
677
+
678
+ /**
679
+ * Props for ${name} component
680
+ */
681
+ export interface ${name}Props extends ComponentProps {
682
+ resourceId?: string | number;
683
+ }
684
+
685
+ /**
686
+ * Data type returned by the resource
687
+ */
688
+ interface ${name}Data {
689
+ id: string | number;
690
+ title: string;
691
+ description: string;
692
+ // Add more fields as needed
693
+ }
694
+
695
+ /**
696
+ * Fetches data for the component
697
+ * @param {string | number} id - Resource ID
698
+ * @returns {Promise<${name}Data>} Fetched data
699
+ */
700
+ async function fetch${name}Data(id: string | number): Promise<${name}Data> {
701
+ // Replace with your actual API call
702
+ const response = await fetch(\`/api/${name.toLowerCase()}/\${id}\`);
703
+
704
+ if (!response.ok) {
705
+ throw new Error('Failed to fetch data');
706
+ }
707
+
708
+ return response.json();
709
+ }
710
+
711
+ /**
712
+ * ${name} component with async resource fetching
713
+ * @param {${name}Props} props - Component props
714
+ * @returns {TemplateResult} Rendered template
715
+ *
716
+ * @example
717
+ * \`\`\`typescript
718
+ * const MyPage = () => {
719
+ * return html\`
720
+ * <div>
721
+ * \${${name}({ resourceId: '123' })}
722
+ * </div>
723
+ * \`;
724
+ * };
725
+ * \`\`\`
726
+ */
727
+ export const ${name}: Component<${name}Props> = (props) => {
728
+ const [data, { refetch }] = createResource(
729
+ () => props.resourceId ?? '1',
730
+ fetch${name}Data,
731
+ {
732
+ onError: (error) => console.error('Failed to load ${name}:', error),
733
+ }
734
+ );
735
+
736
+ return html\`
737
+ <div class="${name.toLowerCase()}">
738
+ <h2>${name}</h2>
739
+
740
+ \${Show({
741
+ when: data.loading,
742
+ children: html\`<div class="loading">Loading...</div>\`,
743
+ })}
744
+
745
+ \${Show({
746
+ when: data.error,
747
+ children: html\`
748
+ <div class="error">
749
+ <p>Error: \${data.error?.message}</p>
750
+ <button @click=\${refetch}>Retry</button>
751
+ </div>
752
+ \`,
753
+ })}
754
+
755
+ \${Show({
756
+ when: () => data() && !data.loading && !data.error,
757
+ children: () => {
758
+ const resource = data()!;
759
+ return html\`
760
+ <div class="content">
761
+ <h3>\${resource.title}</h3>
762
+ <p>\${resource.description}</p>
763
+ <button @click=\${refetch}>Refresh</button>
764
+ </div>
765
+ \`;
766
+ },
767
+ })}
768
+ </div>
769
+ \`;
770
+ };
771
+
772
+ export default ${name};
773
+ `;
774
+ }
775
+ /**
776
+ * Generates a component with router integration
777
+ * @param {string} name - Component name
778
+ * @param {ComponentPattern} pattern - Component pattern to use
779
+ * @returns {string} Component code
780
+ */
781
+ function generateComponentWithRouter(name, pattern = 'functional') {
782
+ if (pattern === 'litelement') {
783
+ return `/**
784
+ * @fileoverview ${name} component with router integration
785
+ */
786
+
787
+ import { html, css, LitElement } from 'lit';
788
+ import { customElement, property, state } from 'lit/decorators.js';
789
+
790
+ /**
791
+ * ${name} component with router integration using LitElement
792
+ *
793
+ * @example
794
+ * \`\`\`html
795
+ * <${name.toLowerCase()}></${name.toLowerCase()}>
796
+ * \`\`\`
797
+ */
798
+ @customElement('${name.toLowerCase()}')
799
+ export class ${name} extends LitElement {
800
+ static styles = css\`
801
+ :host {
802
+ display: block;
803
+ padding: 1rem;
804
+ }
805
+
806
+ nav {
807
+ display: flex;
808
+ gap: 1rem;
809
+ margin: 1rem 0;
810
+ padding: 1rem;
811
+ background: #f5f5f5;
812
+ }
813
+
814
+ nav a {
815
+ padding: 0.5rem 1rem;
816
+ text-decoration: none;
817
+ color: #333;
818
+ background: white;
819
+ border-radius: 4px;
820
+ }
821
+
822
+ nav a:hover {
823
+ background: #007bff;
824
+ color: white;
825
+ }
826
+
827
+ .route-info {
828
+ margin: 1rem 0;
829
+ padding: 1rem;
830
+ background: #e9ecef;
831
+ }
832
+ \`;
833
+
834
+ @state()
835
+ private currentPath = window.location.pathname;
836
+
837
+ connectedCallback() {
838
+ super.connectedCallback();
839
+ window.addEventListener('popstate', this.handlePopState);
840
+ }
841
+
842
+ disconnectedCallback() {
843
+ super.disconnectedCallback();
844
+ window.removeEventListener('popstate', this.handlePopState);
845
+ }
846
+
847
+ private handlePopState = () => {
848
+ this.currentPath = window.location.pathname;
849
+ };
850
+
851
+ private navigate(path: string) {
852
+ window.history.pushState({}, '', path);
853
+ this.currentPath = path;
854
+ }
855
+
856
+ render() {
857
+ return html\`
858
+ <div class="${name.toLowerCase()}">
859
+ <h2>${name} - Router Component</h2>
860
+
861
+ <nav>
862
+ <a href="/" @click=\${(e: Event) => {
863
+ e.preventDefault();
864
+ this.navigate('/');
865
+ }}>Home</a>
866
+ <a href="/about" @click=\${(e: Event) => {
867
+ e.preventDefault();
868
+ this.navigate('/about');
869
+ }}>About</a>
870
+ <a href="/contact" @click=\${(e: Event) => {
871
+ e.preventDefault();
872
+ this.navigate('/contact');
873
+ }}>Contact</a>
874
+ </nav>
875
+
876
+ <div class="route-info">
877
+ <p><strong>Current Route:</strong> \${this.currentPath}</p>
878
+ <p>Router integration active</p>
879
+ </div>
880
+
881
+ <div class="content">
882
+ \${this.renderRoute()}
883
+ </div>
884
+ </div>
885
+ \`;
886
+ }
887
+
888
+ private renderRoute() {
889
+ switch (this.currentPath) {
890
+ case '/':
891
+ return html\`<h3>Home Page</h3><p>Welcome to the home page!</p>\`;
892
+ case '/about':
893
+ return html\`<h3>About Page</h3><p>Learn more about us.</p>\`;
894
+ case '/contact':
895
+ return html\`<h3>Contact Page</h3><p>Get in touch with us.</p>\`;
896
+ default:
897
+ return html\`<h3>404 Not Found</h3><p>Page not found.</p>\`;
898
+ }
899
+ }
900
+ }
901
+
902
+ export default ${name};
903
+ `;
904
+ }
905
+ return `/**
906
+ * @fileoverview ${name} component with router integration
907
+ */
908
+
909
+ import {
910
+ html,
911
+ Component,
912
+ ComponentProps,
913
+ useParams,
914
+ useNavigate,
915
+ useLocation,
916
+ createSignal,
917
+ } from '@fluixi/core';
918
+
919
+ /**
920
+ * Props for ${name} component
921
+ */
922
+ export interface ${name}Props extends ComponentProps {
923
+ // Add your props here
924
+ }
925
+
926
+ /**
927
+ * ${name} component with router integration
928
+ * @param {${name}Props} props - Component props
929
+ * @returns {TemplateResult} Rendered template
930
+ *
931
+ * @example
932
+ * \`\`\`typescript
933
+ * // In your routes configuration:
934
+ * const routes = [
935
+ * {
936
+ * path: '/${name.toLowerCase()}/:id',
937
+ * component: ${name}
938
+ * }
939
+ * ];
940
+ * \`\`\`
941
+ */
942
+ export const ${name}: Component<${name}Props> = (props) => {
943
+ const params = useParams();
944
+ const navigate = useNavigate();
945
+ const location = useLocation();
946
+
947
+ const [selectedTab, setSelectedTab] = createSignal('overview');
948
+
949
+ const goBack = () => {
950
+ navigate(-1);
951
+ };
952
+
953
+ const navigateToTab = (tab: string) => {
954
+ setSelectedTab(tab);
955
+ navigate(\`\${location().pathname}?tab=\${tab}\`);
956
+ };
957
+
958
+ return html\`
959
+ <div class="${name.toLowerCase()}">
960
+ <div class="header">
961
+ <button @click=\${goBack}>← Back</button>
962
+ <h2>${name}</h2>
963
+ <p>ID: \${params.id || 'N/A'}</p>
964
+ </div>
965
+
966
+ <div class="tabs">
967
+ <button
968
+ class=\${selectedTab() === 'overview' ? 'active' : ''}
969
+ @click=\${() => navigateToTab('overview')}
970
+ >
971
+ Overview
972
+ </button>
973
+ <button
974
+ class=\${selectedTab() === 'details' ? 'active' : ''}
975
+ @click=\${() => navigateToTab('details')}
976
+ >
977
+ Details
978
+ </button>
979
+ <button
980
+ class=\${selectedTab() === 'settings' ? 'active' : ''}
981
+ @click=\${() => navigateToTab('settings')}
982
+ >
983
+ Settings
984
+ </button>
985
+ </div>
986
+
987
+ <div class="content">
988
+ <p>Current tab: \${selectedTab()}</p>
989
+ <p>Full path: \${location().pathname}</p>
990
+ </div>
991
+ </div>
992
+ \`;
993
+ };
994
+
995
+ export default ${name};
996
+ `;
997
+ }
998
+ /**
999
+ * Generates a full-featured component
1000
+ * @param {string} name - Component name
1001
+ * @returns {string} Component code
1002
+ */
1003
+ function generateFullComponent(name) {
1004
+ return `/**
1005
+ * @fileoverview ${name} - Full-featured component with state, resources, and router
1006
+ */
1007
+
1008
+ import {
1009
+ html,
1010
+ Component,
1011
+ ComponentProps,
1012
+ createSignal,
1013
+ createEffect,
1014
+ createResource,
1015
+ createContext,
1016
+ useContext,
1017
+ useParams,
1018
+ useNavigate,
1019
+ Show,
1020
+ For,
1021
+ onMount,
1022
+ onUnmount,
1023
+ } from '@fluixi/core';
1024
+
1025
+ /**
1026
+ * Props for ${name} component
1027
+ */
1028
+ export interface ${name}Props extends ComponentProps {
1029
+ initialData?: ${name}Data[];
1030
+ }
1031
+
1032
+ /**
1033
+ * Data interface for ${name}
1034
+ */
1035
+ export interface ${name}Data {
1036
+ id: string | number;
1037
+ title: string;
1038
+ description: string;
1039
+ createdAt: string;
1040
+ }
1041
+
1042
+ /**
1043
+ * ${name} context type
1044
+ */
1045
+ interface ${name}Context {
1046
+ selectedId: () => string | null;
1047
+ selectItem: (id: string) => void;
1048
+ }
1049
+
1050
+ /**
1051
+ * Context for ${name} component
1052
+ */
1053
+ const ${name}Context = createContext<${name}Context | null>(null, '${name}Context');
1054
+
1055
+ /**
1056
+ * Fetches items for ${name}
1057
+ * @returns {Promise<${name}Data[]>} Fetched items
1058
+ */
1059
+ async function fetch${name}Items(): Promise<${name}Data[]> {
1060
+ // Replace with your actual API call
1061
+ const response = await fetch('/api/${name.toLowerCase()}');
1062
+
1063
+ if (!response.ok) {
1064
+ throw new Error('Failed to fetch items');
1065
+ }
1066
+
1067
+ return response.json();
1068
+ }
1069
+
1070
+ /**
1071
+ * ${name} component - Full-featured implementation
1072
+ * @param {${name}Props} props - Component props
1073
+ * @returns {TemplateResult} Rendered template
1074
+ *
1075
+ * @example
1076
+ * \`\`\`typescript
1077
+ * const App = () => {
1078
+ * return html\`
1079
+ * <div>
1080
+ * \${${name}({ initialData: [] })}
1081
+ * </div>
1082
+ * \`;
1083
+ * };
1084
+ * \`\`\`
1085
+ */
1086
+ export const ${name}: Component<${name}Props> = (props) => {
1087
+ const params = useParams();
1088
+ const navigate = useNavigate();
1089
+
1090
+ // State
1091
+ const [selectedId, setSelectedId] = createSignal<string | null>(params.id || null);
1092
+ const [filter, setFilter] = createSignal('');
1093
+ const [sortBy, setSortBy] = createSignal<'title' | 'date'>('title');
1094
+
1095
+ // Resource
1096
+ const [items, { refetch, mutate }] = createResource(
1097
+ fetch${name}Items,
1098
+ {
1099
+ initialValue: props.initialData || [],
1100
+ onError: (error) => console.error('Failed to load items:', error),
1101
+ }
1102
+ );
1103
+
1104
+ // Computed filtered and sorted items
1105
+ const filteredItems = () => {
1106
+ const all = items() || [];
1107
+ const filterText = filter().toLowerCase();
1108
+
1109
+ let filtered = all.filter(item =>
1110
+ item.title.toLowerCase().includes(filterText) ||
1111
+ item.description.toLowerCase().includes(filterText)
1112
+ );
1113
+
1114
+ if (sortBy() === 'title') {
1115
+ filtered.sort((a, b) => a.title.localeCompare(b.title));
1116
+ } else {
1117
+ filtered.sort((a, b) =>
1118
+ new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
1119
+ );
1120
+ }
1121
+
1122
+ return filtered;
1123
+ };
1124
+
1125
+ // Effects
1126
+ createEffect(() => {
1127
+ if (params.id) {
1128
+ setSelectedId(params.id);
1129
+ }
1130
+ });
1131
+
1132
+ // Lifecycle
1133
+ onMount(() => {
1134
+ console.log('${name} mounted');
1135
+ });
1136
+
1137
+ onUnmount(() => {
1138
+ console.log('${name} unmounted');
1139
+ });
1140
+
1141
+ // Handlers
1142
+ const selectItem = (id: string) => {
1143
+ setSelectedId(id);
1144
+ navigate(\`/${name.toLowerCase()}/\${id}\`);
1145
+ };
1146
+
1147
+ const addNewItem = async () => {
1148
+ // Optimistic update
1149
+ const newItem: ${name}Data = {
1150
+ id: Date.now(),
1151
+ title: 'New Item',
1152
+ description: 'New item description',
1153
+ createdAt: new Date().toISOString(),
1154
+ };
1155
+
1156
+ mutate(prev => [...(prev || []), newItem]);
1157
+
1158
+ // Sync with server
1159
+ try {
1160
+ await fetch('/api/${name.toLowerCase()}', {
1161
+ method: 'POST',
1162
+ headers: { 'Content-Type': 'application/json' },
1163
+ body: JSON.stringify(newItem),
1164
+ });
1165
+ refetch();
1166
+ } catch (error) {
1167
+ console.error('Failed to add item:', error);
1168
+ }
1169
+ };
1170
+
1171
+ const deleteItem = async (id: string | number) => {
1172
+ // Optimistic update
1173
+ mutate(prev => prev?.filter(item => item.id !== id));
1174
+
1175
+ // Sync with server
1176
+ try {
1177
+ await fetch(\`/api/${name.toLowerCase()}/\${id}\`, {
1178
+ method: 'DELETE',
1179
+ });
1180
+ } catch (error) {
1181
+ console.error('Failed to delete item:', error);
1182
+ refetch();
1183
+ }
1184
+ };
1185
+
1186
+ // Context value
1187
+ const contextValue: ${name}Context = {
1188
+ selectedId,
1189
+ selectItem,
1190
+ };
1191
+
1192
+ return html\`
1193
+ <div class="${name.toLowerCase()}">
1194
+ <div class="header">
1195
+ <h2>${name}</h2>
1196
+ <button @click=\${refetch}>Refresh</button>
1197
+ <button @click=\${addNewItem}>Add New</button>
1198
+ </div>
1199
+
1200
+ <div class="controls">
1201
+ <input
1202
+ type="text"
1203
+ placeholder="Filter items..."
1204
+ .value=\${filter()}
1205
+ @input=\${(e: Event) => setFilter((e.target as HTMLInputElement).value)}
1206
+ />
1207
+ <select
1208
+ .value=\${sortBy()}
1209
+ @change=\${(e: Event) => setSortBy((e.target as HTMLSelectElement).value as any)}
1210
+ >
1211
+ <option value="title">Sort by Title</option>
1212
+ <option value="date">Sort by Date</option>
1213
+ </select>
1214
+ </div>
1215
+
1216
+ \${Show({
1217
+ when: items.loading,
1218
+ children: html\`<div class="loading">Loading...</div>\`,
1219
+ })}
1220
+
1221
+ \${Show({
1222
+ when: items.error,
1223
+ children: html\`
1224
+ <div class="error">
1225
+ <p>Error: \${items.error?.message}</p>
1226
+ <button @click=\${refetch}>Retry</button>
1227
+ </div>
1228
+ \`,
1229
+ })}
1230
+
1231
+ \${Show({
1232
+ when: () => !items.loading && !items.error,
1233
+ children: html\`
1234
+ <div class="items">
1235
+ \${For({
1236
+ each: filteredItems,
1237
+ children: (item, index) => html\`
1238
+ <div
1239
+ class=\${selectedId() === String(item.id) ? 'item selected' : 'item'}
1240
+ @click=\${() => selectItem(String(item.id))}
1241
+ >
1242
+ <h3>\${item.title}</h3>
1243
+ <p>\${item.description}</p>
1244
+ <span class="date">\${new Date(item.createdAt).toLocaleDateString()}</span>
1245
+ <button
1246
+ @click=\${(e: Event) => {
1247
+ e.stopPropagation();
1248
+ deleteItem(item.id);
1249
+ }}
1250
+ >
1251
+ Delete
1252
+ </button>
1253
+ </div>
1254
+ \`,
1255
+ })}
1256
+ </div>
1257
+ \`,
1258
+ })}
1259
+
1260
+ \${Show({
1261
+ when: () => filteredItems().length === 0 && !items.loading,
1262
+ children: html\`<p class="empty">No items found</p>\`,
1263
+ })}
1264
+ </div>
1265
+ \`;
1266
+ };
1267
+
1268
+ export default ${name};
1269
+ `;
1270
+ }
1271
+ /**
1272
+ * Generates a JSX component
1273
+ */
1274
+ function generateJSXComponent(name, type = 'basic') {
1275
+ const kebabName = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
1276
+ if (type === 'basic') {
1277
+ return `/** @jsx h */
1278
+ import { h, Fragment } from '@fluixi/core/jsx-runtime';
1279
+ import { createSignal } from '@fluixi/reactive/signal';
1280
+ import './${kebabName}.css';
1281
+
1282
+ export interface ${name}Props {
1283
+ title?: string;
1284
+ }
1285
+
1286
+ export function ${name}({ title = 'Hello' }: ${name}Props) {
1287
+ return (
1288
+ <div class="${kebabName}">
1289
+ <h1>{title}</h1>
1290
+ <p>This is a Fluixi JSX component</p>
1291
+ </div>
1292
+ );
1293
+ }
1294
+
1295
+ export default ${name};
1296
+ `;
1297
+ }
1298
+ else if (type === 'with-state') {
1299
+ return `/** @jsx h */
1300
+ import { h, Fragment } from '@fluixi/core/jsx-runtime';
1301
+ import { createSignal } from '@fluixi/reactive/signal';
1302
+ import './${kebabName}.css';
1303
+
1304
+ export interface ${name}Props {
1305
+ initialCount?: number;
1306
+ }
1307
+
1308
+ export function ${name}({ initialCount = 0 }: ${name}Props) {
1309
+ const [count, setCount] = createSignal(initialCount);
1310
+
1311
+ const increment = () => setCount(count() + 1);
1312
+ const decrement = () => setCount(count() - 1);
1313
+ const reset = () => setCount(initialCount);
1314
+
1315
+ return (
1316
+ <div class="${kebabName}">
1317
+ <h2>Counter Component</h2>
1318
+ <div class="counter">
1319
+ <button onClick={decrement}>-</button>
1320
+ <span class="count">{count()}</span>
1321
+ <button onClick={increment}>+</button>
1322
+ </div>
1323
+ <button onClick={reset}>Reset</button>
1324
+ </div>
1325
+ );
1326
+ }
1327
+
1328
+ export default ${name};
1329
+ `;
1330
+ }
1331
+ return generateJSXComponent(name, 'basic');
1332
+ }
1333
+ /**
1334
+ * Generates a React Bridge component
1335
+ */
1336
+ function generateReactBridgeComponent(name, type = 'basic') {
1337
+ const kebabName = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
1338
+ if (type === 'basic') {
1339
+ return `import React from 'react';
1340
+ import { useSignal } from '@fluixi/core/react-bridge';
1341
+ import './${kebabName}.css';
1342
+
1343
+ export interface ${name}Props {
1344
+ title?: string;
1345
+ }
1346
+
1347
+ export function ${name}({ title = 'Hello' }: ${name}Props) {
1348
+ return (
1349
+ <div className="${kebabName}">
1350
+ <h1>{title}</h1>
1351
+ <p>This is a Fluixi React Bridge component</p>
1352
+ </div>
1353
+ );
1354
+ }
1355
+
1356
+ export default ${name};
1357
+ `;
1358
+ }
1359
+ else if (type === 'with-state') {
1360
+ return `import React from 'react';
1361
+ import { useSignal, useFluixiEffect } from '@fluixi/core/react-bridge';
1362
+ import './${kebabName}.css';
1363
+
1364
+ export interface ${name}Props {
1365
+ initialCount?: number;
1366
+ }
1367
+
1368
+ export function ${name}({ initialCount = 0 }: ${name}Props) {
1369
+ const [count, setCount] = useSignal(initialCount);
1370
+
1371
+ useFluixiEffect(() => {
1372
+ console.log('Count changed:', count());
1373
+ });
1374
+
1375
+ const increment = () => setCount(count() + 1);
1376
+ const decrement = () => setCount(count() - 1);
1377
+ const reset = () => setCount(initialCount);
1378
+
1379
+ return (
1380
+ <div className="${kebabName}">
1381
+ <h2>Counter Component</h2>
1382
+ <div className="counter">
1383
+ <button onClick={decrement}>-</button>
1384
+ <span className="count">{count()}</span>
1385
+ <button onClick={increment}>+</button>
1386
+ </div>
1387
+ <button onClick={reset}>Reset</button>
1388
+ </div>
1389
+ );
1390
+ }
1391
+
1392
+ export default ${name};
1393
+ `;
1394
+ }
1395
+ return generateReactBridgeComponent(name, 'basic');
1396
+ }
1397
+ /**
1398
+ * Creates a component file
1399
+ * @param {string} componentPath - Path to create the component
1400
+ * @param {string} name - Component name
1401
+ * @param {ComponentType} type - Component type
1402
+ */
1403
+ async function createComponent(componentPath, name, type = 'basic', pattern = 'functional', mode = 'lit-html') {
1404
+ const componentDir = path.dirname(componentPath);
1405
+ // Ensure component directory exists
1406
+ await fs.mkdir(componentDir, { recursive: true });
1407
+ let componentCode;
1408
+ // Generate based on mode
1409
+ if (mode === 'jsx') {
1410
+ componentCode = generateJSXComponent(name, type);
1411
+ }
1412
+ else if (mode === 'react-bridge') {
1413
+ componentCode = generateReactBridgeComponent(name, type);
1414
+ }
1415
+ else {
1416
+ // lit-html mode (original generators)
1417
+ switch (type) {
1418
+ case 'basic':
1419
+ componentCode = generateBasicComponent(name, pattern);
1420
+ break;
1421
+ case 'with-state':
1422
+ componentCode = generateComponentWithState(name, pattern);
1423
+ break;
1424
+ case 'with-resource':
1425
+ componentCode = generateComponentWithResource(name, pattern);
1426
+ break;
1427
+ case 'with-router':
1428
+ componentCode = generateComponentWithRouter(name, pattern);
1429
+ break;
1430
+ case 'full':
1431
+ componentCode = generateFullComponent(name);
1432
+ break;
1433
+ default:
1434
+ componentCode = generateBasicComponent(name, pattern);
1435
+ }
1436
+ }
1437
+ // Write component file
1438
+ await fs.writeFile(componentPath, componentCode, 'utf-8');
1439
+ // Create test file
1440
+ const testPath = componentPath.replace(/\.tsx?$/, '.spec.ts');
1441
+ const testCode = generateTestFile(name);
1442
+ await fs.writeFile(testPath, testCode, 'utf-8');
1443
+ // Create style file
1444
+ const stylePath = componentPath.replace(/\.tsx?$/, '.css');
1445
+ const styleCode = generateStyleFile(name);
1446
+ await fs.writeFile(stylePath, styleCode, 'utf-8');
1447
+ console.log(chalk.green(`✓ Created component: ${name}`));
1448
+ console.log(chalk.gray(` - ${componentPath}`));
1449
+ console.log(chalk.gray(` - ${testPath}`));
1450
+ console.log(chalk.gray(` - ${stylePath}`));
1451
+ }
1452
+ /**
1453
+ * Generates a test file for a component
1454
+ * @param {string} name - Component name
1455
+ * @returns {string} Test code
1456
+ */
1457
+ function generateTestFile(name) {
1458
+ return `/**
1459
+ * @fileoverview Tests for ${name} component
1460
+ */
1461
+
1462
+ import { describe, it, expect } from 'vitest';
1463
+ import { render } from '@fluixi/core';
1464
+ import { ${name} } from './${name}';
1465
+
1466
+ describe('${name}', () => {
1467
+ it('should render successfully', () => {
1468
+ const container = document.createElement('div');
1469
+ const dispose = render(() => ${name}({}), container);
1470
+
1471
+ expect(container.querySelector('.${name.toLowerCase()}')).toBeTruthy();
1472
+
1473
+ dispose();
1474
+ });
1475
+
1476
+ it('should display component title', () => {
1477
+ const container = document.createElement('div');
1478
+ const dispose = render(() => ${name}({}), container);
1479
+
1480
+ const heading = container.querySelector('h2');
1481
+ expect(heading?.textContent).toBe('${name}');
1482
+
1483
+ dispose();
1484
+ });
1485
+ });
1486
+ `;
1487
+ }
1488
+ /**
1489
+ * Generates a style file for a component
1490
+ * @param {string} name - Component name
1491
+ * @returns {string} CSS code
1492
+ */
1493
+ function generateStyleFile(name) {
1494
+ return `.${name.toLowerCase()} {
1495
+ padding: 1rem;
1496
+ border: 1px solid #e0e0e0;
1497
+ border-radius: 8px;
1498
+ }
1499
+
1500
+ .${name.toLowerCase()} h2 {
1501
+ margin: 0 0 1rem 0;
1502
+ color: #333;
1503
+ }
1504
+
1505
+ .${name.toLowerCase()} p {
1506
+ margin: 0.5rem 0;
1507
+ color: #666;
1508
+ }
1509
+
1510
+ .${name.toLowerCase()} button {
1511
+ padding: 0.5rem 1rem;
1512
+ margin: 0.25rem;
1513
+ border: none;
1514
+ border-radius: 4px;
1515
+ background: #007bff;
1516
+ color: white;
1517
+ cursor: pointer;
1518
+ font-size: 0.875rem;
1519
+ }
1520
+
1521
+ .${name.toLowerCase()} button:hover {
1522
+ background: #0056b3;
1523
+ }
1524
+
1525
+ .${name.toLowerCase()} button:active {
1526
+ transform: scale(0.98);
1527
+ }
1528
+
1529
+ .${name.toLowerCase()} .loading,
1530
+ .${name.toLowerCase()} .error,
1531
+ .${name.toLowerCase()} .empty {
1532
+ padding: 2rem;
1533
+ text-align: center;
1534
+ color: #666;
1535
+ }
1536
+
1537
+ .${name.toLowerCase()} .error {
1538
+ color: #dc3545;
1539
+ }
1540
+ `;
1541
+ }
1542
+ /**
1543
+ * Interactive component generation
1544
+ */
1545
+ async function interactiveComponentGeneration() {
1546
+ const answers = await inquirer.prompt([
1547
+ {
1548
+ type: 'input',
1549
+ name: 'name',
1550
+ message: 'Component name:',
1551
+ validate: (input) => {
1552
+ if (!input)
1553
+ return 'Component name is required';
1554
+ if (!/^[A-Z][a-zA-Z0-9]*$/.test(input)) {
1555
+ return 'Component name must start with uppercase letter';
1556
+ }
1557
+ return true;
1558
+ },
1559
+ },
1560
+ {
1561
+ type: 'list',
1562
+ name: 'mode',
1563
+ message: 'Component mode:',
1564
+ choices: [
1565
+ {
1566
+ name: 'Lit-HTML (Recommended - native Fluixi with lit-html)',
1567
+ value: 'lit-html',
1568
+ },
1569
+ {
1570
+ name: 'JSX (Native JSX runtime for Fluixi)',
1571
+ value: 'jsx',
1572
+ },
1573
+ {
1574
+ name: 'React Bridge (React compatibility with Fluixi signals)',
1575
+ value: 'react-bridge',
1576
+ },
1577
+ ],
1578
+ default: 'lit-html',
1579
+ },
1580
+ {
1581
+ type: 'list',
1582
+ name: 'pattern',
1583
+ message: 'Component pattern:',
1584
+ choices: [
1585
+ {
1586
+ name: 'FluixiElement (Recommended - extends FluixiElement base class)',
1587
+ value: 'fluixilement',
1588
+ },
1589
+ {
1590
+ name: 'LitElement (Plain LitElement class component)',
1591
+ value: 'litelement',
1592
+ },
1593
+ {
1594
+ name: 'WithSignals (Mixin - adds signals to LitElement)',
1595
+ value: 'withsignals',
1596
+ },
1597
+ {
1598
+ name: 'Controller (Manual - uses SignalController)',
1599
+ value: 'controller',
1600
+ },
1601
+ {
1602
+ name: 'Functional (Function-based component)',
1603
+ value: 'functional',
1604
+ },
1605
+ ],
1606
+ default: 'fluixilement',
1607
+ when: (answers) => answers.mode === 'lit-html',
1608
+ },
1609
+ {
1610
+ type: 'list',
1611
+ name: 'type',
1612
+ message: 'Component features:',
1613
+ choices: [
1614
+ { name: 'Basic (Simple component)', value: 'basic' },
1615
+ { name: 'With State (Reactive state management)', value: 'with-state' },
1616
+ { name: 'With Resource (Async data fetching)', value: 'with-resource' },
1617
+ { name: 'With Router (Router integration)', value: 'with-router' },
1618
+ { name: 'Full (All features)', value: 'full' },
1619
+ ],
1620
+ },
1621
+ {
1622
+ type: 'input',
1623
+ name: 'path',
1624
+ message: 'Component path (relative to src):',
1625
+ default: (answers) => {
1626
+ const ext = answers.mode === 'lit-html' ? 'ts' : 'tsx';
1627
+ return `components/${answers.name}/${answers.name}.${ext}`;
1628
+ },
1629
+ },
1630
+ ]);
1631
+ const componentPath = path.join(process.cwd(), 'src', answers.path);
1632
+ await createComponent(componentPath, answers.name, answers.type, answers.pattern || 'functional', answers.mode || 'lit-html');
1633
+ }
1634
+ /**
1635
+ * Generates project structure
1636
+ * @param {string} projectName - Project name
1637
+ * @param {ProjectTemplate} template - Template type
1638
+ * @param {ComponentMode} mode - Default component mode
1639
+ * @param {any} config - Advanced configuration options
1640
+ */
1641
+ async function generateProject(projectName, template, mode = 'lit-html', config = {}) {
1642
+ const projectPath = path.join(process.cwd(), projectName);
1643
+ console.log(chalk.blue(`\nCreating Fluixi project: ${projectName}`));
1644
+ console.log(chalk.gray(`Template: ${template}\n`));
1645
+ // Create project directory
1646
+ await fs.ensureDir(projectPath);
1647
+ // Create directory structure
1648
+ await fs.ensureDir(path.join(projectPath, 'src'));
1649
+ await fs.ensureDir(path.join(projectPath, 'src/components'));
1650
+ await fs.ensureDir(path.join(projectPath, 'src/pages'));
1651
+ await fs.ensureDir(path.join(projectPath, 'src/styles'));
1652
+ await fs.ensureDir(path.join(projectPath, 'public'));
1653
+ // Generate package.json with conditional dependencies based on mode
1654
+ const packageJson = {
1655
+ name: projectName,
1656
+ version: '0.0.1',
1657
+ type: 'module',
1658
+ scripts: {
1659
+ dev: 'vite',
1660
+ build: 'tsc && vite build',
1661
+ preview: 'vite preview',
1662
+ test: 'vitest',
1663
+ },
1664
+ dependencies: {
1665
+ '@fluixi/core': 'workspace:*',
1666
+ '@fluixi/reactive': 'workspace:*',
1667
+ lit: '^3.3.1',
1668
+ },
1669
+ devDependencies: {
1670
+ typescript: '^5.0.0',
1671
+ vite: '^5.0.0',
1672
+ vitest: '^1.0.0',
1673
+ },
1674
+ };
1675
+ // Add React dependencies if using react-bridge mode
1676
+ if (mode === 'react-bridge') {
1677
+ packageJson.dependencies.react = '^18.0.0';
1678
+ packageJson.dependencies['react-dom'] = '^18.0.0';
1679
+ packageJson.devDependencies['@types/react'] = '^18.0.0';
1680
+ packageJson.devDependencies['@types/react-dom'] = '^18.0.0';
1681
+ }
1682
+ // Add advanced configuration dependencies
1683
+ if (config.cssPreprocessor === 'scss') {
1684
+ packageJson.devDependencies.sass = '^1.69.0';
1685
+ }
1686
+ else if (config.cssPreprocessor === 'less') {
1687
+ packageJson.devDependencies.less = '^4.2.0';
1688
+ }
1689
+ else if (config.cssPreprocessor === 'postcss') {
1690
+ packageJson.devDependencies.postcss = '^8.4.31';
1691
+ packageJson.devDependencies.autoprefixer = '^10.4.16';
1692
+ }
1693
+ if (config.includeESLint) {
1694
+ packageJson.devDependencies.eslint = '^8.53.0';
1695
+ packageJson.devDependencies['@typescript-eslint/eslint-plugin'] = '^6.10.0';
1696
+ packageJson.devDependencies['@typescript-eslint/parser'] = '^6.10.0';
1697
+ packageJson.scripts.lint = 'eslint src --ext ts,tsx';
1698
+ }
1699
+ if (config.includePrettier) {
1700
+ packageJson.devDependencies.prettier = '^3.1.0';
1701
+ packageJson.scripts.format = 'prettier --write "src/**/*.{ts,tsx,css}"';
1702
+ }
1703
+ if (config.includeSSR) {
1704
+ packageJson.dependencies['@lit-labs/ssr'] = '^3.3.1';
1705
+ packageJson.dependencies['@lit-labs/ssr-client'] = '^1.1.7';
1706
+ }
1707
+ if (config.author) {
1708
+ packageJson.author = config.author;
1709
+ }
1710
+ if (config.description) {
1711
+ packageJson.description = config.description;
1712
+ }
1713
+ if (config.license) {
1714
+ packageJson.license = config.license;
1715
+ }
1716
+ await fs.writeJSON(path.join(projectPath, 'package.json'), packageJson, {
1717
+ spaces: 2,
1718
+ });
1719
+ // Generate tsconfig.json with conditional JSX settings
1720
+ const tsConfig = {
1721
+ compilerOptions: {
1722
+ target: 'ES2020',
1723
+ module: 'ESNext',
1724
+ lib: ['ES2020', 'DOM', 'DOM.Iterable'],
1725
+ moduleResolution: 'bundler',
1726
+ strict: true,
1727
+ skipLibCheck: true,
1728
+ esModuleInterop: true,
1729
+ resolveJsonModule: true,
1730
+ },
1731
+ include: ['src'],
1732
+ };
1733
+ // Configure JSX based on mode
1734
+ if (mode === 'jsx') {
1735
+ tsConfig.compilerOptions.jsx = 'react-jsx';
1736
+ tsConfig.compilerOptions.jsxImportSource = '@fluixi/core/jsx-runtime';
1737
+ }
1738
+ else if (mode === 'react-bridge') {
1739
+ tsConfig.compilerOptions.jsx = 'react-jsx';
1740
+ }
1741
+ else {
1742
+ tsConfig.compilerOptions.jsx = 'preserve';
1743
+ }
1744
+ await fs.writeJSON(path.join(projectPath, 'tsconfig.json'), tsConfig, {
1745
+ spaces: 2,
1746
+ });
1747
+ // Generate vite.config.ts
1748
+ const viteConfig = `import { defineConfig } from 'vite';
1749
+
1750
+ export default defineConfig({
1751
+ server: {
1752
+ port: 3000,
1753
+ },
1754
+ build: {
1755
+ target: 'esnext',
1756
+ },
1757
+ });
1758
+ `;
1759
+ await fs.writeFile(path.join(projectPath, 'vite.config.ts'), viteConfig);
1760
+ // Generate index.html
1761
+ const indexHtml = `<!DOCTYPE html>
1762
+ <html lang="en">
1763
+ <head>
1764
+ <meta charset="UTF-8" />
1765
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1766
+ <title>${projectName}</title>
1767
+ </head>
1768
+ <body>
1769
+ <div id="app"></div>
1770
+ <script type="module" src="/src/main.ts"></script>
1771
+ </body>
1772
+ </html>
1773
+ `;
1774
+ await fs.writeFile(path.join(projectPath, 'index.html'), indexHtml);
1775
+ // Generate main.ts based on mode
1776
+ let mainTs;
1777
+ const appExt = mode === 'lit-html' ? 'ts' : 'tsx';
1778
+ if (mode === 'jsx') {
1779
+ mainTs = `import { render } from '@fluixi/core';
1780
+ import { App } from './App';
1781
+ import './styles/main.css';
1782
+
1783
+ const root = document.getElementById('app');
1784
+
1785
+ if (root) {
1786
+ render(App, root);
1787
+ }
1788
+ `;
1789
+ }
1790
+ else if (mode === 'react-bridge') {
1791
+ mainTs = `import React from 'react';
1792
+ import { createRoot } from 'react-dom/client';
1793
+ import { App } from './App';
1794
+ import './styles/main.css';
1795
+
1796
+ const root = document.getElementById('app');
1797
+
1798
+ if (root) {
1799
+ const reactRoot = createRoot(root);
1800
+ reactRoot.render(<App />);
1801
+ }
1802
+ `;
1803
+ }
1804
+ else {
1805
+ mainTs = `import { render, html } from '@fluixi/core';
1806
+ import { App } from './App';
1807
+ import './styles/main.css';
1808
+
1809
+ const root = document.getElementById('app');
1810
+
1811
+ if (root) {
1812
+ render(App, root);
1813
+ }
1814
+ `;
1815
+ }
1816
+ await fs.writeFile(path.join(projectPath, 'src/main.ts'), mainTs);
1817
+ // Generate App component based on mode
1818
+ const appTs = generateAppComponent(template, mode);
1819
+ await fs.writeFile(path.join(projectPath, `src/App.${appExt}`), appTs);
1820
+ // Generate example components if requested
1821
+ if (config.exampleComponents && config.exampleComponents.length > 0) {
1822
+ console.log(chalk.blue('\nGenerating example components...'));
1823
+ for (const component of config.exampleComponents) {
1824
+ await generateExampleComponent(projectPath, component, mode);
1825
+ }
1826
+ }
1827
+ // Generate example pages if requested
1828
+ if (config.examplePages && config.examplePages.length > 0) {
1829
+ console.log(chalk.blue('\nGenerating example pages...'));
1830
+ for (const page of config.examplePages) {
1831
+ await generateExamplePage(projectPath, page, mode);
1832
+ }
1833
+ }
1834
+ // Generate ESLint config
1835
+ if (config.includeESLint) {
1836
+ const eslintConfig = {
1837
+ parser: '@typescript-eslint/parser',
1838
+ extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
1839
+ parserOptions: {
1840
+ ecmaVersion: 2020,
1841
+ sourceType: 'module',
1842
+ },
1843
+ rules: {},
1844
+ };
1845
+ await fs.writeJSON(path.join(projectPath, '.eslintrc.json'), eslintConfig, {
1846
+ spaces: 2,
1847
+ });
1848
+ }
1849
+ // Generate Prettier config
1850
+ if (config.includePrettier) {
1851
+ const prettierConfig = {
1852
+ semi: true,
1853
+ trailingComma: 'es5',
1854
+ singleQuote: true,
1855
+ printWidth: 80,
1856
+ tabWidth: 2,
1857
+ };
1858
+ await fs.writeJSON(path.join(projectPath, '.prettierrc.json'), prettierConfig, {
1859
+ spaces: 2,
1860
+ });
1861
+ }
1862
+ // Generate main.css
1863
+ const mainCss = `* {
1864
+ margin: 0;
1865
+ padding: 0;
1866
+ box-sizing: border-box;
1867
+ }
1868
+
1869
+ body {
1870
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
1871
+ line-height: 1.6;
1872
+ color: #333;
1873
+ }
1874
+
1875
+ #app {
1876
+ max-width: 1200px;
1877
+ margin: 0 auto;
1878
+ padding: 2rem;
1879
+ }
1880
+ `;
1881
+ await fs.writeFile(path.join(projectPath, 'src/styles/main.css'), mainCss);
1882
+ // Generate README
1883
+ const readme = `# ${projectName}
1884
+
1885
+ A Fluixi application.
1886
+
1887
+ ## Getting Started
1888
+
1889
+ \`\`\`bash
1890
+ # Install dependencies
1891
+ npm install
1892
+
1893
+ # Start development server
1894
+ npm run dev
1895
+
1896
+ # Build for production
1897
+ npm run build
1898
+
1899
+ # Run tests
1900
+ npm test
1901
+ \`\`\`
1902
+
1903
+ ## Project Structure
1904
+
1905
+ \`\`\`
1906
+ ${projectName}/
1907
+ ├── src/
1908
+ │ ├── components/ # Reusable components
1909
+ │ ├── pages/ # Page components
1910
+ │ ├── styles/ # Global styles
1911
+ │ ├── App.ts # Root component
1912
+ │ └── main.ts # Application entry point
1913
+ ├── public/ # Static assets
1914
+ ├── index.html # HTML template
1915
+ ├── package.json
1916
+ ├── tsconfig.json
1917
+ └── vite.config.ts
1918
+ \`\`\`
1919
+
1920
+ ## Learn More
1921
+
1922
+ - [Fluixi Documentation](https://github.com/your-org/fluixi)
1923
+ - [Lit Documentation](https://lit.dev)
1924
+ `;
1925
+ await fs.writeFile(path.join(projectPath, 'README.md'), readme);
1926
+ console.log(chalk.green(`\n✓ Project created successfully!\n`));
1927
+ console.log(chalk.blue('Next steps:'));
1928
+ console.log(chalk.gray(` cd ${projectName}`));
1929
+ console.log(chalk.gray(` npm install`));
1930
+ console.log(chalk.gray(` npm run dev\n`));
1931
+ }
1932
+ /**
1933
+ * Generates the main App component
1934
+ * @param {ProjectTemplate} template - Template type
1935
+ * @param {ComponentMode} mode - Component mode
1936
+ * @returns {string} Component code
1937
+ */
1938
+ function generateAppComponent(template, mode = 'lit-html') {
1939
+ if (mode === 'jsx') {
1940
+ return generateJSXAppComponent(template);
1941
+ }
1942
+ else if (mode === 'react-bridge') {
1943
+ return generateReactBridgeAppComponent(template);
1944
+ }
1945
+ // Default lit-html mode
1946
+ if (template === 'full') {
1947
+ return `import { html, createRouter, Router, Link, Component } from '@fluixi/core';
1948
+
1949
+ const Home = () => html\`
1950
+ <div>
1951
+ <h1>Home Page</h1>
1952
+ <p>Welcome to your Fluixi application!</p>
1953
+ </div>
1954
+ \`;
1955
+
1956
+ const About = () => html\`
1957
+ <div>
1958
+ <h1>About Page</h1>
1959
+ <p>This is a full-featured Fluixi application.</p>
1960
+ </div>
1961
+ \`;
1962
+
1963
+ const routes = [
1964
+ { path: '/', component: Home },
1965
+ { path: '/about', component: About },
1966
+ ];
1967
+
1968
+ export const App: Component = () => {
1969
+ const router = createRouter(routes);
1970
+
1971
+ return Router({
1972
+ router,
1973
+ children: html\`
1974
+ <div class="app">
1975
+ <nav>
1976
+ \${Link({ href: '/', children: 'Home' })}
1977
+ \${Link({ href: '/about', children: 'About' })}
1978
+ </nav>
1979
+ <main id="content"></main>
1980
+ </div>
1981
+ \`,
1982
+ });
1983
+ };
1984
+ `;
1985
+ }
1986
+ return `import { html, createSignal, Component } from '@fluixi/core';
1987
+
1988
+ export const App: Component = () => {
1989
+ const [count, setCount] = createSignal(0);
1990
+
1991
+ return html\`
1992
+ <div class="app">
1993
+ <h1>Welcome to Fluixi</h1>
1994
+ <p>A reactive framework for lit-html</p>
1995
+ <div class="counter">
1996
+ <p>Count: \${count()}</p>
1997
+ <button @click=\${() => setCount(count() + 1)}>Increment</button>
1998
+ </div>
1999
+ </div>
2000
+ \`;
2001
+ };
2002
+ export default App;
2003
+ `;
2004
+ }
2005
+ /**
2006
+ * Generates JSX App component
2007
+ */
2008
+ function generateJSXAppComponent(template) {
2009
+ return `/** @jsx h */
2010
+ import { h, Fragment } from '@fluixi/core/jsx-runtime';
2011
+ import { createSignal } from '@fluixi/reactive/signal';
2012
+ import './styles/main.css';
2013
+
2014
+ export function App() {
2015
+ const [count, setCount] = createSignal(0);
2016
+
2017
+ return (
2018
+ <div class="app">
2019
+ <header>
2020
+ <h1>Welcome to Fluixi</h1>
2021
+ <p>A reactive framework powered by signals</p>
2022
+ </header>
2023
+
2024
+ <main>
2025
+ <div class="counter">
2026
+ <h2>Counter Example</h2>
2027
+ <div class="controls">
2028
+ <button onClick={() => setCount(count() - 1)}>-</button>
2029
+ <span class="count">{count()}</span>
2030
+ <button onClick={() => setCount(count() + 1)}>+</button>
2031
+ </div>
2032
+ </div>
2033
+ </main>
2034
+
2035
+ <footer>
2036
+ <p>Built with Fluixi JSX</p>
2037
+ </footer>
2038
+ </div>
2039
+ );
2040
+ }
2041
+
2042
+ export default App;
2043
+ `;
2044
+ }
2045
+ /**
2046
+ * Generates React Bridge App component
2047
+ */
2048
+ function generateReactBridgeAppComponent(template) {
2049
+ return `import React from 'react';
2050
+ import { useSignal, useFluixiEffect } from '@fluixi/core/react-bridge';
2051
+ import './styles/main.css';
2052
+
2053
+ export function App() {
2054
+ const [count, setCount] = useSignal(0);
2055
+
2056
+ useFluixiEffect(() => {
2057
+ console.log('Count changed:', count());
2058
+ });
2059
+
2060
+ return (
2061
+ <div className="app">
2062
+ <header>
2063
+ <h1>Welcome to Fluixi</h1>
2064
+ <p>A reactive framework with React compatibility</p>
2065
+ </header>
2066
+
2067
+ <main>
2068
+ <div className="counter">
2069
+ <h2>Counter Example</h2>
2070
+ <div className="controls">
2071
+ <button onClick={() => setCount(count() - 1)}>-</button>
2072
+ <span className="count">{count()}</span>
2073
+ <button onClick={() => setCount(count() + 1)}>+</button>
2074
+ </div>
2075
+ </div>
2076
+ </main>
2077
+
2078
+ <footer>
2079
+ <p>Built with Fluixi React Bridge</p>
2080
+ </footer>
2081
+ </div>
2082
+ );
2083
+ }
2084
+
2085
+ export default App;
2086
+ `;
2087
+ }
2088
+ /**
2089
+ * Generate example component
2090
+ */
2091
+ async function generateExampleComponent(projectPath, componentName, mode) {
2092
+ const componentDir = path.join(projectPath, 'src/components', componentName);
2093
+ await fs.ensureDir(componentDir);
2094
+ const ext = mode === 'lit-html' ? 'ts' : 'tsx';
2095
+ const componentPath = path.join(componentDir, `${componentName}.${ext}`);
2096
+ let componentCode = '';
2097
+ if (componentName === 'counter') {
2098
+ componentCode =
2099
+ mode === 'jsx'
2100
+ ? generateJSXComponent('Counter', 'with-state')
2101
+ : mode === 'react-bridge'
2102
+ ? generateReactBridgeComponent('Counter', 'with-state')
2103
+ : generateComponentWithState('Counter', 'functional');
2104
+ }
2105
+ else if (componentName === 'todolist') {
2106
+ componentCode = generateTodoListComponent(mode);
2107
+ }
2108
+ else if (componentName === 'usercard') {
2109
+ componentCode = generateUserCardComponent(mode);
2110
+ }
2111
+ else if (componentName === 'datafetcher') {
2112
+ componentCode = generateDataFetcherComponent(mode);
2113
+ }
2114
+ await fs.writeFile(componentPath, componentCode);
2115
+ // Generate CSS
2116
+ const cssPath = path.join(componentDir, `${componentName}.css`);
2117
+ await fs.writeFile(cssPath, generateStyleFile(componentName));
2118
+ console.log(chalk.gray(` ✓ ${componentName} component created`));
2119
+ }
2120
+ /**
2121
+ * Generate example page
2122
+ */
2123
+ async function generateExamplePage(projectPath, pageName, mode) {
2124
+ const pageDir = path.join(projectPath, 'src/pages', pageName);
2125
+ await fs.ensureDir(pageDir);
2126
+ const ext = mode === 'lit-html' ? 'ts' : 'tsx';
2127
+ const pagePath = path.join(pageDir, `${pageName}.${ext}`);
2128
+ let pageCode = '';
2129
+ if (mode === 'jsx') {
2130
+ pageCode = `/** @jsx h */
2131
+ import { h } from '@fluixi/core/jsx-runtime';
2132
+ import './${pageName}.css';
2133
+
2134
+ export function ${pageName.charAt(0).toUpperCase() + pageName.slice(1)}Page() {
2135
+ return (
2136
+ <div class="${pageName}-page">
2137
+ <h1>${pageName.charAt(0).toUpperCase() + pageName.slice(1)} Page</h1>
2138
+ <p>Welcome to the ${pageName} page.</p>
2139
+ </div>
2140
+ );
2141
+ }
2142
+
2143
+ export default ${pageName.charAt(0).toUpperCase() + pageName.slice(1)}Page;
2144
+ `;
2145
+ }
2146
+ else if (mode === 'react-bridge') {
2147
+ pageCode = `import React from 'react';
2148
+ import './${pageName}.css';
2149
+
2150
+ export function ${pageName.charAt(0).toUpperCase() + pageName.slice(1)}Page() {
2151
+ return (
2152
+ <div className="${pageName}-page">
2153
+ <h1>${pageName.charAt(0).toUpperCase() + pageName.slice(1)} Page</h1>
2154
+ <p>Welcome to the ${pageName} page.</p>
2155
+ </div>
2156
+ );
2157
+ }
2158
+
2159
+ export default ${pageName.charAt(0).toUpperCase() + pageName.slice(1)}Page;
2160
+ `;
2161
+ }
2162
+ else {
2163
+ pageCode = `import { html } from 'lit';
2164
+ import './${pageName}.css';
2165
+
2166
+ export const ${pageName.charAt(0).toUpperCase() + pageName.slice(1)}Page = () => html\`
2167
+ <div class="${pageName}-page">
2168
+ <h1>${pageName.charAt(0).toUpperCase() + pageName.slice(1)} Page</h1>
2169
+ <p>Welcome to the ${pageName} page.</p>
2170
+ </div>
2171
+ \`;
2172
+
2173
+ export default ${pageName.charAt(0).toUpperCase() + pageName.slice(1)}Page;
2174
+ `;
2175
+ }
2176
+ await fs.writeFile(pagePath, pageCode);
2177
+ // Generate CSS
2178
+ const cssPath = path.join(pageDir, `${pageName}.css`);
2179
+ await fs.writeFile(cssPath, `.${pageName}-page { padding: 2rem; }`);
2180
+ console.log(chalk.gray(` ✓ ${pageName} page created`));
2181
+ }
2182
+ /**
2183
+ * Generate TodoList component
2184
+ */
2185
+ function generateTodoListComponent(mode) {
2186
+ if (mode === 'jsx') {
2187
+ return `/** @jsx h */
2188
+ import { h } from '@fluixi/core/jsx-runtime';
2189
+ import { createSignal } from '@fluixi/reactive/signal';
2190
+ import './todolist.css';
2191
+
2192
+ export function TodoList() {
2193
+ const [todos, setTodos] = createSignal<string[]>([]);
2194
+ const [input, setInput] = createSignal('');
2195
+
2196
+ const addTodo = () => {
2197
+ if (input().trim()) {
2198
+ setTodos([...todos(), input()]);
2199
+ setInput('');
2200
+ }
2201
+ };
2202
+
2203
+ return (
2204
+ <div class="todolist">
2205
+ <h2>Todo List</h2>
2206
+ <div class="input-group">
2207
+ <input
2208
+ type="text"
2209
+ value={input()}
2210
+ onInput={(e) => setInput((e.target as HTMLInputElement).value)}
2211
+ placeholder="Add a todo..."
2212
+ />
2213
+ <button onClick={addTodo}>Add</button>
2214
+ </div>
2215
+ <ul>
2216
+ {todos().map((todo, index) => (
2217
+ <li key={index}>{todo}</li>
2218
+ ))}
2219
+ </ul>
2220
+ </div>
2221
+ );
2222
+ }
2223
+
2224
+ export default TodoList;
2225
+ `;
2226
+ }
2227
+ return `import { html } from 'lit';
2228
+ import { createSignal } from '@fluixi/reactive/signal';
2229
+ import './todolist.css';
2230
+
2231
+ export const TodoList = () => {
2232
+ const [todos, setTodos] = createSignal<string[]>([]);
2233
+ const [input, setInput] = createSignal('');
2234
+
2235
+ const addTodo = () => {
2236
+ if (input().trim()) {
2237
+ setTodos([...todos(), input()]);
2238
+ setInput('');
2239
+ }
2240
+ };
2241
+
2242
+ return html\`
2243
+ <div class="todolist">
2244
+ <h2>Todo List</h2>
2245
+ <div class="input-group">
2246
+ <input
2247
+ type="text"
2248
+ .value=\${input()}
2249
+ @input=\${(e: Event) => setInput((e.target as HTMLInputElement).value)}
2250
+ placeholder="Add a todo..."
2251
+ />
2252
+ <button @click=\${addTodo}>Add</button>
2253
+ </div>
2254
+ <ul>
2255
+ \${todos().map((todo) => html\`<li>\${todo}</li>\`)}
2256
+ </ul>
2257
+ </div>
2258
+ \`;
2259
+ };
2260
+
2261
+ export default TodoList;
2262
+ `;
2263
+ }
2264
+ /**
2265
+ * Generate UserCard component
2266
+ */
2267
+ function generateUserCardComponent(mode) {
2268
+ if (mode === 'jsx') {
2269
+ return `/** @jsx h */
2270
+ import { h } from '@fluixi/core/jsx-runtime';
2271
+ import './usercard.css';
2272
+
2273
+ export interface UserCardProps {
2274
+ name: string;
2275
+ email: string;
2276
+ avatar?: string;
2277
+ }
2278
+
2279
+ export function UserCard({ name, email, avatar }: UserCardProps) {
2280
+ return (
2281
+ <div class="usercard">
2282
+ <img src={avatar || 'https://via.placeholder.com/100'} alt={name} />
2283
+ <h3>{name}</h3>
2284
+ <p>{email}</p>
2285
+ </div>
2286
+ );
2287
+ }
2288
+
2289
+ export default UserCard;
2290
+ `;
2291
+ }
2292
+ return `import { html } from 'lit';
2293
+ import './usercard.css';
2294
+
2295
+ export interface UserCardProps {
2296
+ name: string;
2297
+ email: string;
2298
+ avatar?: string;
2299
+ }
2300
+
2301
+ export const UserCard = (props: UserCardProps) => html\`
2302
+ <div class="usercard">
2303
+ <img src="\${props.avatar || 'https://via.placeholder.com/100'}" alt="\${props.name}" />
2304
+ <h3>\${props.name}</h3>
2305
+ <p>\${props.email}</p>
2306
+ </div>
2307
+ \`;
2308
+
2309
+ export default UserCard;
2310
+ `;
2311
+ }
2312
+ /**
2313
+ * Generate DataFetcher component
2314
+ */
2315
+ function generateDataFetcherComponent(mode) {
2316
+ return `import { html } from 'lit';
2317
+ import { createResource } from '@fluixi/core/resources';
2318
+ import { Show } from '@fluixi/core/control-flow';
2319
+ import './datafetcher.css';
2320
+
2321
+ async function fetchData() {
2322
+ const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
2323
+ return response.json();
2324
+ }
2325
+
2326
+ export const DataFetcher = () => {
2327
+ const [data] = createResource(fetchData);
2328
+
2329
+ return html\`
2330
+ <div class="datafetcher">
2331
+ <h2>Data Fetcher</h2>
2332
+ \${Show({
2333
+ when: () => data.loading,
2334
+ children: html\`<p>Loading...</p>\`,
2335
+ })}
2336
+ \${Show({
2337
+ when: () => data.error,
2338
+ children: html\`<p>Error: \${data.error?.message}</p>\`,
2339
+ })}
2340
+ \${Show({
2341
+ when: () => data(),
2342
+ children: html\`
2343
+ <div>
2344
+ <h3>\${data()?.title}</h3>
2345
+ <p>\${data()?.body}</p>
2346
+ </div>
2347
+ \`,
2348
+ })}
2349
+ </div>
2350
+ \`;
2351
+ };
2352
+
2353
+ export default DataFetcher;
2354
+ `;
2355
+ }
2356
+ /**
2357
+ * Interactive project generation
2358
+ */
2359
+ async function interactiveProjectGeneration() {
2360
+ const initialAnswers = await inquirer.prompt([
2361
+ {
2362
+ type: 'input',
2363
+ name: 'name',
2364
+ message: 'Project name:',
2365
+ validate: (input) => {
2366
+ if (!input)
2367
+ return 'Project name is required';
2368
+ if (!/^[a-z][a-z0-9-]*$/.test(input)) {
2369
+ return 'Project name must start with lowercase letter and contain only lowercase letters, numbers, and hyphens';
2370
+ }
2371
+ return true;
2372
+ },
2373
+ },
2374
+ {
2375
+ type: 'list',
2376
+ name: 'template',
2377
+ message: 'Project template:',
2378
+ choices: [
2379
+ { name: 'Minimal (Basic setup)', value: 'minimal' },
2380
+ { name: 'Standard (With router and examples)', value: 'standard' },
2381
+ { name: 'Full (Complete setup with all features)', value: 'full' },
2382
+ ],
2383
+ },
2384
+ {
2385
+ type: 'list',
2386
+ name: 'mode',
2387
+ message: 'Default component mode:',
2388
+ choices: [
2389
+ { name: 'Lit-HTML (Native Fluixi)', value: 'lit-html' },
2390
+ { name: 'JSX (Native JSX runtime)', value: 'jsx' },
2391
+ { name: 'React Bridge (React compatibility)', value: 'react-bridge' },
2392
+ ],
2393
+ default: 'lit-html',
2394
+ },
2395
+ {
2396
+ type: 'confirm',
2397
+ name: 'advancedConfig',
2398
+ message: 'Configure advanced project settings?',
2399
+ default: false,
2400
+ },
2401
+ ]);
2402
+ let finalAnswers = { ...initialAnswers };
2403
+ // Advanced configuration
2404
+ if (initialAnswers.advancedConfig) {
2405
+ const advancedAnswers = await inquirer.prompt([
2406
+ {
2407
+ type: 'confirm',
2408
+ name: 'includeRouter',
2409
+ message: 'Include router setup?',
2410
+ default: initialAnswers.template !== 'minimal',
2411
+ },
2412
+ {
2413
+ type: 'confirm',
2414
+ name: 'includeSSR',
2415
+ message: 'Include Server-Side Rendering (SSR) support?',
2416
+ default: false,
2417
+ },
2418
+ {
2419
+ type: 'confirm',
2420
+ name: 'includeStateManagement',
2421
+ message: 'Include state management utilities?',
2422
+ default: initialAnswers.template === 'full',
2423
+ },
2424
+ {
2425
+ type: 'confirm',
2426
+ name: 'includeTesting',
2427
+ message: 'Setup testing framework (Vitest)?',
2428
+ default: true,
2429
+ },
2430
+ {
2431
+ type: 'confirm',
2432
+ name: 'includeESLint',
2433
+ message: 'Include ESLint configuration?',
2434
+ default: true,
2435
+ },
2436
+ {
2437
+ type: 'confirm',
2438
+ name: 'includePrettier',
2439
+ message: 'Include Prettier configuration?',
2440
+ default: true,
2441
+ },
2442
+ {
2443
+ type: 'list',
2444
+ name: 'cssPreprocessor',
2445
+ message: 'CSS preprocessor:',
2446
+ choices: [
2447
+ { name: 'None (Plain CSS)', value: 'none' },
2448
+ { name: 'SCSS/SASS', value: 'scss' },
2449
+ { name: 'Less', value: 'less' },
2450
+ { name: 'PostCSS', value: 'postcss' },
2451
+ ],
2452
+ default: 'none',
2453
+ },
2454
+ {
2455
+ type: 'checkbox',
2456
+ name: 'exampleComponents',
2457
+ message: 'Generate example components:',
2458
+ choices: [
2459
+ {
2460
+ name: 'Counter (Basic state management)',
2461
+ value: 'counter',
2462
+ checked: true,
2463
+ },
2464
+ { name: 'TodoList (CRUD operations)', value: 'todolist' },
2465
+ { name: 'UserCard (Props & styling)', value: 'usercard' },
2466
+ { name: 'DataFetcher (Async resources)', value: 'datafetcher' },
2467
+ ],
2468
+ },
2469
+ {
2470
+ type: 'checkbox',
2471
+ name: 'examplePages',
2472
+ message: 'Generate example pages:',
2473
+ choices: [
2474
+ { name: 'Home', value: 'home', checked: true },
2475
+ { name: 'About', value: 'about' },
2476
+ { name: 'Dashboard', value: 'dashboard' },
2477
+ { name: 'NotFound (404)', value: 'notfound', checked: true },
2478
+ ],
2479
+ },
2480
+ {
2481
+ type: 'input',
2482
+ name: 'author',
2483
+ message: 'Author name (optional):',
2484
+ default: '',
2485
+ },
2486
+ {
2487
+ type: 'input',
2488
+ name: 'description',
2489
+ message: 'Project description (optional):',
2490
+ default: 'A Fluixi application',
2491
+ },
2492
+ {
2493
+ type: 'input',
2494
+ name: 'license',
2495
+ message: 'License:',
2496
+ default: 'MIT',
2497
+ },
2498
+ ]);
2499
+ finalAnswers = { ...finalAnswers, ...advancedAnswers };
2500
+ }
2501
+ await generateProject(finalAnswers.name, finalAnswers.template, finalAnswers.mode, finalAnswers);
2502
+ console.log(chalk.yellow('\nNote: Component mode preference saved.'));
2503
+ console.log(chalk.gray(`Default mode: ${finalAnswers.mode}`));
2504
+ }
2505
+ // CLI Commands
2506
+ program
2507
+ .name('fluixi')
2508
+ .description('Fluixi CLI - Generate projects and components')
2509
+ .version(VERSION);
2510
+ program
2511
+ .command('new [project-name]')
2512
+ .description('Create a new Fluixi project')
2513
+ .option('-t, --template <template>', 'Project template (minimal|standard|full)', 'standard')
2514
+ .action(async (projectName, options) => {
2515
+ try {
2516
+ if (projectName) {
2517
+ await generateProject(projectName, options?.template || 'standard');
2518
+ }
2519
+ else {
2520
+ await interactiveProjectGeneration();
2521
+ }
2522
+ }
2523
+ catch (error) {
2524
+ console.error(chalk.red('Error creating project:'), error);
2525
+ process.exit(1);
2526
+ }
2527
+ });
2528
+ program
2529
+ .command('generate <type> [name]')
2530
+ .alias('g')
2531
+ .description('Generate a component or other artifact')
2532
+ .option('-t, --type <type>', 'Component type (basic|with-state|with-resource|with-router|full)')
2533
+ .option('-p, --path <path>', 'Component path')
2534
+ .option('-m, --mode <mode>', 'Component mode (lit-html|jsx|react-bridge)')
2535
+ .action(async (type, name, options) => {
2536
+ try {
2537
+ if (type === 'component' || type === 'c') {
2538
+ if (name && options?.path) {
2539
+ const componentType = options.type || 'basic';
2540
+ const componentMode = options.mode || 'lit-html';
2541
+ const componentPath = path.join(process.cwd(), 'src', options.path);
2542
+ await createComponent(componentPath, name, componentType, 'functional', componentMode);
2543
+ }
2544
+ else {
2545
+ await interactiveComponentGeneration();
2546
+ }
2547
+ }
2548
+ else {
2549
+ console.error(chalk.red(`Unknown type: ${type}`));
2550
+ console.log(chalk.gray('Available types: component (or c)'));
2551
+ process.exit(1);
2552
+ }
2553
+ }
2554
+ catch (error) {
2555
+ console.error(chalk.red('Error generating component:'), error);
2556
+ process.exit(1);
2557
+ }
2558
+ });
2559
+ program
2560
+ .command('info')
2561
+ .description('Display Fluixi information')
2562
+ .action(() => {
2563
+ console.log(chalk.blue('\n╔═══════════════════════════════════╗'));
2564
+ console.log(chalk.blue('║ Fluixi CLI v' + VERSION + ' ║'));
2565
+ console.log(chalk.blue('╚═══════════════════════════════════╝\n'));
2566
+ console.log(chalk.gray('A reactive framework for lit-html\n'));
2567
+ console.log('Commands:');
2568
+ console.log(chalk.cyan(' new [project-name] ') + 'Create a new project');
2569
+ console.log(chalk.cyan(' generate <type> [name] ') + 'Generate a component');
2570
+ console.log(chalk.cyan(' info ') + 'Show this information');
2571
+ console.log(chalk.cyan(' help ') + 'Show help\n');
2572
+ console.log('Examples:');
2573
+ console.log(chalk.gray(' fluixi new my-app'));
2574
+ console.log(chalk.gray(' fluixi generate component MyComponent'));
2575
+ console.log(chalk.gray(' fluixi generate component MyComponent --mode jsx'));
2576
+ console.log(chalk.gray(' fluixi generate component MyComponent --mode react-bridge'));
2577
+ console.log(chalk.gray(' fluixi g c MyComponent -t with-state -p components/MyComponent/MyComponent.ts\n'));
2578
+ });
2579
+ // Parse CLI arguments
2580
+ program.parse();