create-fluxstack 1.0.13 → 1.0.15

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 (214) hide show
  1. package/.env.example +29 -29
  2. package/app/client/README.md +69 -69
  3. package/app/client/index.html +14 -13
  4. package/app/client/src/App.tsx +157 -524
  5. package/app/client/src/components/ErrorBoundary.tsx +107 -0
  6. package/app/client/src/components/ErrorDisplay.css +365 -0
  7. package/app/client/src/components/ErrorDisplay.tsx +258 -0
  8. package/app/client/src/components/FluxStackConfig.tsx +1321 -0
  9. package/app/client/src/components/HybridLiveCounter.tsx +140 -0
  10. package/app/client/src/components/LiveClock.tsx +286 -0
  11. package/app/client/src/components/MainLayout.tsx +390 -0
  12. package/app/client/src/components/SidebarNavigation.tsx +391 -0
  13. package/app/client/src/components/StateDemo.tsx +178 -0
  14. package/app/client/src/components/SystemMonitor.tsx +1038 -0
  15. package/app/client/src/components/Teste.tsx +104 -0
  16. package/app/client/src/components/UserProfile.tsx +809 -0
  17. package/app/client/src/hooks/useAuth.ts +39 -0
  18. package/app/client/src/hooks/useNotifications.ts +56 -0
  19. package/app/client/src/lib/eden-api.ts +189 -53
  20. package/app/client/src/lib/errors.ts +340 -0
  21. package/app/client/src/lib/hooks/useErrorHandler.ts +258 -0
  22. package/app/client/src/lib/index.ts +45 -0
  23. package/app/client/src/main.tsx +3 -2
  24. package/app/client/src/pages/ApiDocs.tsx +182 -0
  25. package/app/client/src/pages/Demo.tsx +174 -0
  26. package/app/client/src/pages/HybridLive.tsx +263 -0
  27. package/app/client/src/pages/Overview.tsx +155 -0
  28. package/app/client/src/store/README.md +43 -0
  29. package/app/client/src/store/index.ts +16 -0
  30. package/app/client/src/store/slices/uiSlice.ts +151 -0
  31. package/app/client/src/store/slices/userSlice.ts +161 -0
  32. package/app/client/src/test/README.md +257 -0
  33. package/app/client/src/test/setup.ts +70 -0
  34. package/app/client/src/test/types.ts +12 -0
  35. package/app/client/src/vite-env.d.ts +1 -1
  36. package/app/client/tsconfig.app.json +44 -43
  37. package/app/client/tsconfig.json +7 -7
  38. package/app/client/tsconfig.node.json +25 -25
  39. package/app/client/zustand-setup.md +65 -0
  40. package/app/server/controllers/users.controller.ts +68 -68
  41. package/app/server/index.ts +9 -1
  42. package/app/server/live/CounterComponent.ts +191 -0
  43. package/app/server/live/FluxStackConfig.ts +529 -0
  44. package/app/server/live/LiveClockComponent.ts +214 -0
  45. package/app/server/live/SidebarNavigation.ts +156 -0
  46. package/app/server/live/SystemMonitor.ts +594 -0
  47. package/app/server/live/SystemMonitorIntegration.ts +151 -0
  48. package/app/server/live/TesteComponent.ts +87 -0
  49. package/app/server/live/UserProfileComponent.ts +135 -0
  50. package/app/server/live/register-components.ts +28 -0
  51. package/app/server/middleware/auth.ts +136 -0
  52. package/app/server/middleware/errorHandling.ts +250 -0
  53. package/app/server/middleware/index.ts +10 -0
  54. package/app/server/middleware/rateLimit.ts +193 -0
  55. package/app/server/middleware/requestLogging.ts +215 -0
  56. package/app/server/middleware/validation.ts +270 -0
  57. package/app/server/routes/index.ts +14 -2
  58. package/app/server/routes/upload.ts +92 -0
  59. package/app/server/routes/users.routes.ts +2 -9
  60. package/app/server/services/NotificationService.ts +302 -0
  61. package/app/server/services/UserService.ts +222 -0
  62. package/app/server/services/index.ts +46 -0
  63. package/core/cli/commands/plugin-deps.ts +263 -0
  64. package/core/cli/generators/README.md +339 -0
  65. package/core/cli/generators/component.ts +770 -0
  66. package/core/cli/generators/controller.ts +299 -0
  67. package/core/cli/generators/index.ts +144 -0
  68. package/core/cli/generators/interactive.ts +228 -0
  69. package/core/cli/generators/prompts.ts +83 -0
  70. package/core/cli/generators/route.ts +513 -0
  71. package/core/cli/generators/service.ts +465 -0
  72. package/core/cli/generators/template-engine.ts +154 -0
  73. package/core/cli/generators/types.ts +71 -0
  74. package/core/cli/generators/utils.ts +192 -0
  75. package/core/cli/index.ts +69 -0
  76. package/core/cli/plugin-discovery.ts +16 -85
  77. package/core/client/fluxstack.ts +17 -0
  78. package/core/client/hooks/index.ts +7 -0
  79. package/core/client/hooks/state-validator.ts +130 -0
  80. package/core/client/hooks/useAuth.ts +49 -0
  81. package/core/client/hooks/useChunkedUpload.ts +258 -0
  82. package/core/client/hooks/useHybridLiveComponent.ts +967 -0
  83. package/core/client/hooks/useWebSocket.ts +373 -0
  84. package/core/client/index.ts +47 -0
  85. package/core/client/state/createStore.ts +193 -0
  86. package/core/client/state/index.ts +15 -0
  87. package/core/config/env-dynamic.ts +1 -1
  88. package/core/config/env.ts +2 -1
  89. package/core/config/runtime-config.ts +3 -3
  90. package/core/config/schema.ts +84 -49
  91. package/core/framework/server.ts +30 -0
  92. package/core/index.ts +25 -0
  93. package/core/live/ComponentRegistry.ts +399 -0
  94. package/core/live/types.ts +164 -0
  95. package/core/plugins/built-in/live-components/commands/create-live-component.ts +1201 -0
  96. package/core/plugins/built-in/live-components/index.ts +27 -0
  97. package/core/plugins/built-in/logger/index.ts +1 -1
  98. package/core/plugins/built-in/monitoring/index.ts +1 -1
  99. package/core/plugins/built-in/static/index.ts +1 -1
  100. package/core/plugins/built-in/swagger/index.ts +1 -1
  101. package/core/plugins/built-in/vite/index.ts +1 -1
  102. package/core/plugins/dependency-manager.ts +384 -0
  103. package/core/plugins/index.ts +5 -1
  104. package/core/plugins/manager.ts +7 -3
  105. package/core/plugins/registry.ts +88 -10
  106. package/core/plugins/types.ts +11 -11
  107. package/core/server/framework.ts +43 -0
  108. package/core/server/index.ts +11 -1
  109. package/core/server/live/ComponentRegistry.ts +1017 -0
  110. package/core/server/live/FileUploadManager.ts +272 -0
  111. package/core/server/live/LiveComponentPerformanceMonitor.ts +930 -0
  112. package/core/server/live/SingleConnectionManager.ts +0 -0
  113. package/core/server/live/StateSignature.ts +644 -0
  114. package/core/server/live/WebSocketConnectionManager.ts +688 -0
  115. package/core/server/live/websocket-plugin.ts +435 -0
  116. package/core/server/middleware/errorHandling.ts +141 -0
  117. package/core/server/middleware/index.ts +16 -0
  118. package/core/server/plugins/static-files-plugin.ts +232 -0
  119. package/core/server/services/BaseService.ts +95 -0
  120. package/core/server/services/ServiceContainer.ts +144 -0
  121. package/core/server/services/index.ts +9 -0
  122. package/core/templates/create-project.ts +196 -33
  123. package/core/testing/index.ts +10 -0
  124. package/core/testing/setup.ts +74 -0
  125. package/core/types/build.ts +38 -14
  126. package/core/types/types.ts +319 -0
  127. package/core/utils/env-runtime.ts +7 -0
  128. package/core/utils/errors/handlers.ts +264 -39
  129. package/core/utils/errors/index.ts +528 -18
  130. package/core/utils/errors/middleware.ts +114 -0
  131. package/core/utils/logger/formatters.ts +222 -0
  132. package/core/utils/logger/index.ts +167 -48
  133. package/core/utils/logger/middleware.ts +253 -0
  134. package/core/utils/logger/performance.ts +384 -0
  135. package/core/utils/logger/transports.ts +365 -0
  136. package/create-fluxstack.ts +296 -296
  137. package/fluxstack.config.ts +17 -1
  138. package/package-template.json +66 -66
  139. package/package.json +31 -6
  140. package/public/README.md +16 -0
  141. package/vite.config.ts +29 -14
  142. package/.claude/settings.local.json +0 -74
  143. package/.github/workflows/ci-build-tests.yml +0 -480
  144. package/.github/workflows/dependency-management.yml +0 -324
  145. package/.github/workflows/release-validation.yml +0 -355
  146. package/.kiro/specs/fluxstack-architecture-optimization/design.md +0 -700
  147. package/.kiro/specs/fluxstack-architecture-optimization/requirements.md +0 -127
  148. package/.kiro/specs/fluxstack-architecture-optimization/tasks.md +0 -330
  149. package/CLAUDE.md +0 -200
  150. package/Dockerfile +0 -58
  151. package/Dockerfile.backend +0 -52
  152. package/Dockerfile.frontend +0 -54
  153. package/README-Docker.md +0 -85
  154. package/ai-context/00-QUICK-START.md +0 -86
  155. package/ai-context/README.md +0 -88
  156. package/ai-context/development/eden-treaty-guide.md +0 -362
  157. package/ai-context/development/patterns.md +0 -382
  158. package/ai-context/development/plugins-guide.md +0 -572
  159. package/ai-context/examples/crud-complete.md +0 -626
  160. package/ai-context/project/architecture.md +0 -399
  161. package/ai-context/project/overview.md +0 -213
  162. package/ai-context/recent-changes/eden-treaty-refactor.md +0 -281
  163. package/ai-context/recent-changes/type-inference-fix.md +0 -223
  164. package/ai-context/reference/environment-vars.md +0 -384
  165. package/ai-context/reference/troubleshooting.md +0 -407
  166. package/app/client/src/components/TestPage.tsx +0 -453
  167. package/bun.lock +0 -1063
  168. package/bunfig.toml +0 -16
  169. package/core/__tests__/integration.test.ts +0 -227
  170. package/core/build/index.ts +0 -186
  171. package/core/config/__tests__/config-loader.test.ts +0 -554
  172. package/core/config/__tests__/config-merger.test.ts +0 -657
  173. package/core/config/__tests__/env-converter.test.ts +0 -372
  174. package/core/config/__tests__/env-processor.test.ts +0 -431
  175. package/core/config/__tests__/env.test.ts +0 -452
  176. package/core/config/__tests__/integration.test.ts +0 -418
  177. package/core/config/__tests__/loader.test.ts +0 -331
  178. package/core/config/__tests__/schema.test.ts +0 -129
  179. package/core/config/__tests__/validator.test.ts +0 -318
  180. package/core/framework/__tests__/server.test.ts +0 -233
  181. package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
  182. package/core/plugins/__tests__/manager.test.ts +0 -398
  183. package/core/plugins/__tests__/monitoring.test.ts +0 -401
  184. package/core/plugins/__tests__/registry.test.ts +0 -335
  185. package/core/utils/__tests__/errors.test.ts +0 -139
  186. package/core/utils/__tests__/helpers.test.ts +0 -297
  187. package/core/utils/__tests__/logger.test.ts +0 -141
  188. package/create-test-app.ts +0 -156
  189. package/docker-compose.microservices.yml +0 -75
  190. package/docker-compose.simple.yml +0 -57
  191. package/docker-compose.yml +0 -71
  192. package/eslint.config.js +0 -23
  193. package/flux-cli.ts +0 -214
  194. package/nginx-lb.conf +0 -37
  195. package/publish.sh +0 -63
  196. package/run-clean.ts +0 -26
  197. package/run-env-tests.ts +0 -313
  198. package/tailwind.config.js +0 -34
  199. package/tests/__mocks__/api.ts +0 -56
  200. package/tests/fixtures/users.ts +0 -69
  201. package/tests/integration/api/users.routes.test.ts +0 -221
  202. package/tests/setup.ts +0 -29
  203. package/tests/unit/app/client/App-simple.test.tsx +0 -56
  204. package/tests/unit/app/client/App.test.tsx.skip +0 -237
  205. package/tests/unit/app/client/eden-api.test.ts +0 -186
  206. package/tests/unit/app/client/simple.test.tsx +0 -23
  207. package/tests/unit/app/controllers/users.controller.test.ts +0 -150
  208. package/tests/unit/core/create-project.test.ts.skip +0 -95
  209. package/tests/unit/core/framework.test.ts +0 -144
  210. package/tests/unit/core/plugins/logger.test.ts.skip +0 -268
  211. package/tests/unit/core/plugins/vite.test.ts.disabled +0 -188
  212. package/tests/utils/test-helpers.ts +0 -61
  213. package/vitest.config.ts +0 -50
  214. package/workspace.json +0 -6
@@ -0,0 +1,465 @@
1
+ import type { Generator } from "./index.js"
2
+ import type { GeneratorContext, GeneratorOptions, Template } from "./types.js"
3
+ import { templateEngine } from "./template-engine.js"
4
+
5
+ export class ServiceGenerator implements Generator {
6
+ name = 'service'
7
+ description = 'Generate a new service with business logic'
8
+
9
+ async generate(context: GeneratorContext, options: GeneratorOptions): Promise<void> {
10
+ const template = this.getTemplate(options.template)
11
+
12
+ if (template.hooks?.beforeGenerate) {
13
+ await template.hooks.beforeGenerate(context, options)
14
+ }
15
+
16
+ const files = await templateEngine.processTemplate(template, context, options)
17
+
18
+ if (options.dryRun) {
19
+ console.log(`\n📋 Would generate service '${options.name}':\n`)
20
+ for (const file of files) {
21
+ console.log(`${file.action === 'create' ? '📄' : '✏️'} ${file.path}`)
22
+ }
23
+ return
24
+ }
25
+
26
+ await templateEngine.generateFiles(files, options.dryRun)
27
+
28
+ if (template.hooks?.afterGenerate) {
29
+ const filePaths = files.map(f => f.path)
30
+ await template.hooks.afterGenerate(context, options, filePaths)
31
+ }
32
+
33
+ console.log(`\n✅ Generated service '${options.name}' with ${files.length} files`)
34
+ }
35
+
36
+ private getTemplate(templateName?: string): Template {
37
+ switch (templateName) {
38
+ case 'minimal':
39
+ return this.getMinimalTemplate()
40
+ case 'repository':
41
+ return this.getRepositoryTemplate()
42
+ case 'crud':
43
+ default:
44
+ return this.getCrudTemplate()
45
+ }
46
+ }
47
+
48
+ private getCrudTemplate(): Template {
49
+ return {
50
+ name: 'crud-service',
51
+ description: 'Full CRUD service with validation and error handling',
52
+ files: [
53
+ {
54
+ path: 'app/server/services/{{kebabName}}.service.ts',
55
+ content: `import { {{pascalName}}, Create{{pascalName}}, Update{{pascalName}} } from '../schemas/{{kebabName}}.schema'
56
+ import { {{pascalName}}Repository } from '../repositories/{{kebabName}}.repository'
57
+ import { ValidationError, NotFoundError } from '../../../core/utils/errors'
58
+ import { logger } from '../../../core/utils/logger'
59
+
60
+ export class {{pascalName}}Service {
61
+ private repository: {{pascalName}}Repository
62
+
63
+ constructor() {
64
+ this.repository = new {{pascalName}}Repository()
65
+ }
66
+
67
+ async findAll(): Promise<{{pascalName}}[]> {
68
+ try {
69
+ logger.debug('Fetching all {{camelName}}s')
70
+ const {{camelName}}s = await this.repository.findAll()
71
+ logger.info(\`Found \${{{camelName}}s.length} {{camelName}}s\`)
72
+ return {{camelName}}s
73
+ } catch (error) {
74
+ logger.error('Failed to fetch {{camelName}}s', { error })
75
+ throw new Error('Failed to fetch {{camelName}}s')
76
+ }
77
+ }
78
+
79
+ async findById(id: string): Promise<{{pascalName}} | null> {
80
+ try {
81
+ this.validateId(id)
82
+
83
+ logger.debug(\`Fetching {{camelName}} with id: \${id}\`)
84
+ const {{camelName}} = await this.repository.findById(id)
85
+
86
+ if (!{{camelName}}) {
87
+ logger.warn(\`{{pascalName}} not found with id: \${id}\`)
88
+ return null
89
+ }
90
+
91
+ logger.info(\`Found {{camelName}}: \${{{camelName}}.name}\`)
92
+ return {{camelName}}
93
+ } catch (error) {
94
+ if (error instanceof ValidationError) {
95
+ throw error
96
+ }
97
+ logger.error(\`Failed to fetch {{camelName}} with id: \${id}\`, { error })
98
+ throw new Error('Failed to fetch {{camelName}}')
99
+ }
100
+ }
101
+
102
+ async create(data: Create{{pascalName}}): Promise<{{pascalName}}> {
103
+ try {
104
+ this.validateCreateData(data)
105
+
106
+ logger.debug('Creating new {{camelName}}', { data })
107
+
108
+ // Check for duplicates
109
+ const existing = await this.repository.findByName(data.name)
110
+ if (existing) {
111
+ throw new ValidationError('{{pascalName}} with this name already exists')
112
+ }
113
+
114
+ const {{camelName}} = await this.repository.create({
115
+ ...data,
116
+ id: this.generateId(),
117
+ createdAt: new Date(),
118
+ updatedAt: new Date()
119
+ })
120
+
121
+ logger.info(\`Created {{camelName}}: \${{{camelName}}.name}\`, { id: {{camelName}}.id })
122
+ return {{camelName}}
123
+ } catch (error) {
124
+ if (error instanceof ValidationError) {
125
+ throw error
126
+ }
127
+ logger.error('Failed to create {{camelName}}', { error, data })
128
+ throw new Error('Failed to create {{camelName}}')
129
+ }
130
+ }
131
+
132
+ async update(id: string, data: Update{{pascalName}}): Promise<{{pascalName}} | null> {
133
+ try {
134
+ this.validateId(id)
135
+ this.validateUpdateData(data)
136
+
137
+ logger.debug(\`Updating {{camelName}} with id: \${id}\`, { data })
138
+
139
+ const existing = await this.repository.findById(id)
140
+ if (!existing) {
141
+ logger.warn(\`{{pascalName}} not found for update with id: \${id}\`)
142
+ return null
143
+ }
144
+
145
+ // Check for name conflicts if name is being updated
146
+ if (data.name && data.name !== existing.name) {
147
+ const nameConflict = await this.repository.findByName(data.name)
148
+ if (nameConflict && nameConflict.id !== id) {
149
+ throw new ValidationError('{{pascalName}} with this name already exists')
150
+ }
151
+ }
152
+
153
+ const updated{{pascalName}} = await this.repository.update(id, {
154
+ ...data,
155
+ updatedAt: new Date()
156
+ })
157
+
158
+ logger.info(\`Updated {{camelName}}: \${updated{{pascalName}}?.name}\`, { id })
159
+ return updated{{pascalName}}
160
+ } catch (error) {
161
+ if (error instanceof ValidationError) {
162
+ throw error
163
+ }
164
+ logger.error(\`Failed to update {{camelName}} with id: \${id}\`, { error, data })
165
+ throw new Error('Failed to update {{camelName}}')
166
+ }
167
+ }
168
+
169
+ async delete(id: string): Promise<boolean> {
170
+ try {
171
+ this.validateId(id)
172
+
173
+ logger.debug(\`Deleting {{camelName}} with id: \${id}\`)
174
+
175
+ const existing = await this.repository.findById(id)
176
+ if (!existing) {
177
+ logger.warn(\`{{pascalName}} not found for deletion with id: \${id}\`)
178
+ return false
179
+ }
180
+
181
+ const deleted = await this.repository.delete(id)
182
+
183
+ if (deleted) {
184
+ logger.info(\`Deleted {{camelName}}: \${existing.name}\`, { id })
185
+ }
186
+
187
+ return deleted
188
+ } catch (error) {
189
+ if (error instanceof ValidationError) {
190
+ throw error
191
+ }
192
+ logger.error(\`Failed to delete {{camelName}} with id: \${id}\`, { error })
193
+ throw new Error('Failed to delete {{camelName}}')
194
+ }
195
+ }
196
+
197
+ async search(query: string): Promise<{{pascalName}}[]> {
198
+ try {
199
+ if (!query || query.trim().length === 0) {
200
+ throw new ValidationError('Search query cannot be empty')
201
+ }
202
+
203
+ logger.debug(\`Searching {{camelName}}s with query: \${query}\`)
204
+ const results = await this.repository.search(query.trim())
205
+ logger.info(\`Found \${results.length} {{camelName}}s matching query: \${query}\`)
206
+
207
+ return results
208
+ } catch (error) {
209
+ if (error instanceof ValidationError) {
210
+ throw error
211
+ }
212
+ logger.error(\`Failed to search {{camelName}}s with query: \${query}\`, { error })
213
+ throw new Error('Failed to search {{camelName}}s')
214
+ }
215
+ }
216
+
217
+ private validateId(id: string): void {
218
+ if (!id || typeof id !== 'string' || id.trim().length === 0) {
219
+ throw new ValidationError('Invalid ID provided')
220
+ }
221
+ }
222
+
223
+ private validateCreateData(data: Create{{pascalName}}): void {
224
+ if (!data) {
225
+ throw new ValidationError('{{pascalName}} data is required')
226
+ }
227
+
228
+ if (!data.name || typeof data.name !== 'string' || data.name.trim().length === 0) {
229
+ throw new ValidationError('{{pascalName}} name is required')
230
+ }
231
+
232
+ if (data.name.length > 100) {
233
+ throw new ValidationError('{{pascalName}} name must be less than 100 characters')
234
+ }
235
+
236
+ if (data.description && data.description.length > 500) {
237
+ throw new ValidationError('{{pascalName}} description must be less than 500 characters')
238
+ }
239
+ }
240
+
241
+ private validateUpdateData(data: Update{{pascalName}}): void {
242
+ if (!data || Object.keys(data).length === 0) {
243
+ throw new ValidationError('Update data is required')
244
+ }
245
+
246
+ if (data.name !== undefined) {
247
+ if (!data.name || typeof data.name !== 'string' || data.name.trim().length === 0) {
248
+ throw new ValidationError('{{pascalName}} name cannot be empty')
249
+ }
250
+
251
+ if (data.name.length > 100) {
252
+ throw new ValidationError('{{pascalName}} name must be less than 100 characters')
253
+ }
254
+ }
255
+
256
+ if (data.description !== undefined && data.description && data.description.length > 500) {
257
+ throw new ValidationError('{{pascalName}} description must be less than 500 characters')
258
+ }
259
+ }
260
+
261
+ private generateId(): string {
262
+ return \`{{kebabName}}_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`
263
+ }
264
+ }
265
+ `
266
+ },
267
+ {
268
+ path: 'app/server/repositories/{{kebabName}}.repository.ts',
269
+ content: `import { {{pascalName}} } from '../schemas/{{kebabName}}.schema'
270
+
271
+ // In-memory storage for demo purposes
272
+ // Replace with your preferred database implementation
273
+ let {{camelName}}Store: {{pascalName}}[] = []
274
+
275
+ export class {{pascalName}}Repository {
276
+ async findAll(): Promise<{{pascalName}}[]> {
277
+ return [...{{camelName}}Store]
278
+ }
279
+
280
+ async findById(id: string): Promise<{{pascalName}} | null> {
281
+ const {{camelName}} = {{camelName}}Store.find(item => item.id === id)
282
+ return {{camelName}} || null
283
+ }
284
+
285
+ async findByName(name: string): Promise<{{pascalName}} | null> {
286
+ const {{camelName}} = {{camelName}}Store.find(item =>
287
+ item.name.toLowerCase() === name.toLowerCase()
288
+ )
289
+ return {{camelName}} || null
290
+ }
291
+
292
+ async create(data: {{pascalName}}): Promise<{{pascalName}}> {
293
+ {{camelName}}Store.push(data)
294
+ return data
295
+ }
296
+
297
+ async update(id: string, data: Partial<{{pascalName}}>): Promise<{{pascalName}} | null> {
298
+ const index = {{camelName}}Store.findIndex(item => item.id === id)
299
+
300
+ if (index === -1) {
301
+ return null
302
+ }
303
+
304
+ {{camelName}}Store[index] = { ...{{camelName}}Store[index], ...data }
305
+ return {{camelName}}Store[index]
306
+ }
307
+
308
+ async delete(id: string): Promise<boolean> {
309
+ const index = {{camelName}}Store.findIndex(item => item.id === id)
310
+
311
+ if (index === -1) {
312
+ return false
313
+ }
314
+
315
+ {{camelName}}Store.splice(index, 1)
316
+ return true
317
+ }
318
+
319
+ async search(query: string): Promise<{{pascalName}}[]> {
320
+ const lowerQuery = query.toLowerCase()
321
+ return {{camelName}}Store.filter(item =>
322
+ item.name.toLowerCase().includes(lowerQuery) ||
323
+ (item.description && item.description.toLowerCase().includes(lowerQuery))
324
+ )
325
+ }
326
+
327
+ async count(): Promise<number> {
328
+ return {{camelName}}Store.length
329
+ }
330
+
331
+ async clear(): Promise<void> {
332
+ {{camelName}}Store = []
333
+ }
334
+ }
335
+ `
336
+ }
337
+ ],
338
+ hooks: {
339
+ afterGenerate: async (context, options, files) => {
340
+ context.logger.info(`Generated service files:`)
341
+ files.forEach(file => {
342
+ context.logger.info(` - ${file}`)
343
+ })
344
+ context.logger.info(`\nNext steps:`)
345
+ context.logger.info(`1. Replace the in-memory repository with your database implementation`)
346
+ context.logger.info(`2. Add any additional business logic methods`)
347
+ context.logger.info(`3. Configure proper error handling and logging`)
348
+ }
349
+ }
350
+ }
351
+ }
352
+
353
+ private getMinimalTemplate(): Template {
354
+ return {
355
+ name: 'minimal-service',
356
+ description: 'Minimal service with basic structure',
357
+ files: [
358
+ {
359
+ path: 'app/server/services/{{kebabName}}.service.ts',
360
+ content: `export class {{pascalName}}Service {
361
+ async findAll() {
362
+ // TODO: Implement find all logic
363
+ return []
364
+ }
365
+
366
+ async findById(id: string) {
367
+ // TODO: Implement find by id logic
368
+ return null
369
+ }
370
+
371
+ async create(data: any) {
372
+ // TODO: Implement create logic
373
+ return data
374
+ }
375
+
376
+ async update(id: string, data: any) {
377
+ // TODO: Implement update logic
378
+ return { id, ...data }
379
+ }
380
+
381
+ async delete(id: string) {
382
+ // TODO: Implement delete logic
383
+ return true
384
+ }
385
+ }
386
+ `
387
+ }
388
+ ]
389
+ }
390
+ }
391
+
392
+ private getRepositoryTemplate(): Template {
393
+ return {
394
+ name: 'repository-service',
395
+ description: 'Service with repository pattern',
396
+ files: [
397
+ {
398
+ path: 'app/server/services/{{kebabName}}.service.ts',
399
+ content: `import { {{pascalName}}Repository } from '../repositories/{{kebabName}}.repository'
400
+
401
+ export class {{pascalName}}Service {
402
+ private repository: {{pascalName}}Repository
403
+
404
+ constructor() {
405
+ this.repository = new {{pascalName}}Repository()
406
+ }
407
+
408
+ async findAll() {
409
+ return await this.repository.findAll()
410
+ }
411
+
412
+ async findById(id: string) {
413
+ return await this.repository.findById(id)
414
+ }
415
+
416
+ async create(data: any) {
417
+ // Add business logic here
418
+ return await this.repository.create(data)
419
+ }
420
+
421
+ async update(id: string, data: any) {
422
+ // Add business logic here
423
+ return await this.repository.update(id, data)
424
+ }
425
+
426
+ async delete(id: string) {
427
+ return await this.repository.delete(id)
428
+ }
429
+ }
430
+ `
431
+ },
432
+ {
433
+ path: 'app/server/repositories/{{kebabName}}.repository.ts',
434
+ content: `export class {{pascalName}}Repository {
435
+ async findAll() {
436
+ // TODO: Implement database query
437
+ return []
438
+ }
439
+
440
+ async findById(id: string) {
441
+ // TODO: Implement database query
442
+ return null
443
+ }
444
+
445
+ async create(data: any) {
446
+ // TODO: Implement database insert
447
+ return data
448
+ }
449
+
450
+ async update(id: string, data: any) {
451
+ // TODO: Implement database update
452
+ return { id, ...data }
453
+ }
454
+
455
+ async delete(id: string) {
456
+ // TODO: Implement database delete
457
+ return true
458
+ }
459
+ }
460
+ `
461
+ }
462
+ ]
463
+ }
464
+ }
465
+ }
@@ -0,0 +1,154 @@
1
+ import type { GeneratorContext, GeneratorOptions, Template, TemplateFile, GeneratedFile, TemplateProcessor } from "./types.js"
2
+ import { join, dirname } from "path"
3
+ import { mkdir, writeFile, readFile, stat } from "fs/promises"
4
+ import { existsSync } from "fs"
5
+
6
+ export class TemplateEngine {
7
+ private processor: TemplateProcessor
8
+
9
+ constructor() {
10
+ this.processor = this.createTemplateProcessor()
11
+ }
12
+
13
+ async processTemplate(
14
+ template: Template,
15
+ context: GeneratorContext,
16
+ options: GeneratorOptions
17
+ ): Promise<GeneratedFile[]> {
18
+ const variables = await this.collectVariables(template, context, options)
19
+ const files: GeneratedFile[] = []
20
+
21
+ for (const templateFile of template.files) {
22
+ // Check condition if specified
23
+ if (templateFile.condition && !templateFile.condition(variables)) {
24
+ continue
25
+ }
26
+
27
+ const processedPath = this.processor(templateFile.path, variables)
28
+ const processedContent = this.processor(templateFile.content, variables)
29
+ const fullPath = join(context.workingDir, processedPath)
30
+
31
+ const exists = existsSync(fullPath)
32
+ let action: 'create' | 'overwrite' | 'skip' = 'create'
33
+
34
+ if (exists) {
35
+ if (options.force) {
36
+ action = 'overwrite'
37
+ } else {
38
+ action = 'skip'
39
+ context.logger.warn(`File already exists: ${processedPath}`)
40
+ }
41
+ }
42
+
43
+ files.push({
44
+ path: fullPath,
45
+ content: processedContent,
46
+ exists,
47
+ action
48
+ })
49
+ }
50
+
51
+ return files
52
+ }
53
+
54
+ async generateFiles(files: GeneratedFile[], dryRun: boolean = false): Promise<void> {
55
+ for (const file of files) {
56
+ if (file.action === 'skip') {
57
+ continue
58
+ }
59
+
60
+ if (dryRun) {
61
+ console.log(`${file.action === 'create' ? '📄' : '✏️'} ${file.path}`)
62
+ continue
63
+ }
64
+
65
+ // Ensure directory exists
66
+ const dir = dirname(file.path)
67
+ await mkdir(dir, { recursive: true })
68
+
69
+ // Write file
70
+ await writeFile(file.path, file.content, 'utf-8')
71
+ }
72
+ }
73
+
74
+ private async collectVariables(
75
+ template: Template,
76
+ context: GeneratorContext,
77
+ options: GeneratorOptions
78
+ ): Promise<Record<string, any>> {
79
+ const variables: Record<string, any> = {
80
+ // Built-in variables
81
+ name: options.name,
82
+ Name: this.capitalize(options.name),
83
+ NAME: options.name.toUpperCase(),
84
+ kebabName: this.toKebabCase(options.name),
85
+ camelName: this.toCamelCase(options.name),
86
+ pascalName: this.toPascalCase(options.name),
87
+ snakeName: this.toSnakeCase(options.name),
88
+ timestamp: new Date().toISOString(),
89
+ date: new Date().toLocaleDateString(),
90
+ year: new Date().getFullYear(),
91
+ author: 'FluxStack Developer',
92
+ projectName: context.config.app?.name || 'fluxstack-app',
93
+ ...options
94
+ }
95
+
96
+ // Process template-specific variables
97
+ if (template.variables) {
98
+ for (const variable of template.variables) {
99
+ if (!(variable.name in variables)) {
100
+ if (variable.required && !variable.default) {
101
+ // In a real implementation, you'd use a proper prompt library
102
+ // For now, we'll use the default or throw an error
103
+ if (variable.default !== undefined) {
104
+ variables[variable.name] = variable.default
105
+ } else {
106
+ throw new Error(`Required variable '${variable.name}' not provided`)
107
+ }
108
+ } else {
109
+ variables[variable.name] = variable.default
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ return variables
116
+ }
117
+
118
+ private createTemplateProcessor(): TemplateProcessor {
119
+ return (template: string, variables: Record<string, any>) => {
120
+ return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
121
+ return variables[key] !== undefined ? String(variables[key]) : match
122
+ })
123
+ }
124
+ }
125
+
126
+ // String transformation utilities
127
+ private capitalize(str: string): string {
128
+ return str.charAt(0).toUpperCase() + str.slice(1)
129
+ }
130
+
131
+ private toCamelCase(str: string): string {
132
+ return str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : '')
133
+ }
134
+
135
+ private toPascalCase(str: string): string {
136
+ return this.capitalize(this.toCamelCase(str))
137
+ }
138
+
139
+ private toKebabCase(str: string): string {
140
+ return str
141
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
142
+ .replace(/[\s_]+/g, '-')
143
+ .toLowerCase()
144
+ }
145
+
146
+ private toSnakeCase(str: string): string {
147
+ return str
148
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
149
+ .replace(/[\s-]+/g, '_')
150
+ .toLowerCase()
151
+ }
152
+ }
153
+
154
+ export const templateEngine = new TemplateEngine()
@@ -0,0 +1,71 @@
1
+ import type { FluxStackConfig } from "../../config/schema"
2
+ import type { Logger } from "../../utils/logger/index"
3
+ import type { PluginUtils } from "../../plugins/types"
4
+
5
+ export interface GeneratorContext {
6
+ workingDir: string
7
+ config: FluxStackConfig
8
+ logger: Logger
9
+ utils: PluginUtils
10
+ }
11
+
12
+ export interface GeneratorOptions {
13
+ name: string
14
+ path?: string
15
+ template?: string
16
+ force?: boolean
17
+ dryRun?: boolean
18
+ [key: string]: any
19
+ }
20
+
21
+ export interface TemplateVariable {
22
+ name: string
23
+ description: string
24
+ type: 'string' | 'boolean' | 'choice'
25
+ required?: boolean
26
+ default?: any
27
+ choices?: string[]
28
+ prompt?: string
29
+ }
30
+
31
+ export interface Template {
32
+ name: string
33
+ description: string
34
+ files: TemplateFile[]
35
+ variables?: TemplateVariable[]
36
+ hooks?: {
37
+ beforeGenerate?: (context: GeneratorContext, options: GeneratorOptions) => Promise<void>
38
+ afterGenerate?: (context: GeneratorContext, options: GeneratorOptions, files: string[]) => Promise<void>
39
+ }
40
+ }
41
+
42
+ export interface TemplateFile {
43
+ path: string
44
+ content: string
45
+ condition?: (variables: Record<string, any>) => boolean
46
+ }
47
+
48
+ export interface GeneratedFile {
49
+ path: string
50
+ content: string
51
+ exists: boolean
52
+ action: 'create' | 'overwrite' | 'skip'
53
+ }
54
+
55
+ export interface GenerationResult {
56
+ success: boolean
57
+ files: GeneratedFile[]
58
+ errors?: string[]
59
+ warnings?: string[]
60
+ }
61
+
62
+ // Utility types for template processing
63
+ export type TemplateProcessor = (template: string, variables: Record<string, any>) => string
64
+
65
+ export interface PromptConfig {
66
+ type: 'input' | 'confirm' | 'select' | 'multiselect'
67
+ message: string
68
+ default?: any
69
+ choices?: Array<{ name: string; value: any; description?: string }>
70
+ validate?: (value: any) => boolean | string
71
+ }