create-fluxstack 1.0.12 → 1.0.14

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 (215) 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/loader.ts +8 -32
  90. package/core/config/runtime-config.ts +3 -3
  91. package/core/config/schema.ts +84 -49
  92. package/core/framework/server.ts +30 -0
  93. package/core/index.ts +25 -0
  94. package/core/live/ComponentRegistry.ts +399 -0
  95. package/core/live/types.ts +164 -0
  96. package/core/plugins/built-in/live-components/commands/create-live-component.ts +1201 -0
  97. package/core/plugins/built-in/live-components/index.ts +27 -0
  98. package/core/plugins/built-in/logger/index.ts +1 -1
  99. package/core/plugins/built-in/monitoring/index.ts +1 -1
  100. package/core/plugins/built-in/static/index.ts +1 -1
  101. package/core/plugins/built-in/swagger/index.ts +1 -1
  102. package/core/plugins/built-in/vite/index.ts +1 -1
  103. package/core/plugins/dependency-manager.ts +384 -0
  104. package/core/plugins/index.ts +5 -1
  105. package/core/plugins/manager.ts +7 -3
  106. package/core/plugins/registry.ts +88 -10
  107. package/core/plugins/types.ts +11 -11
  108. package/core/server/framework.ts +43 -0
  109. package/core/server/index.ts +11 -1
  110. package/core/server/live/ComponentRegistry.ts +1017 -0
  111. package/core/server/live/FileUploadManager.ts +272 -0
  112. package/core/server/live/LiveComponentPerformanceMonitor.ts +930 -0
  113. package/core/server/live/SingleConnectionManager.ts +0 -0
  114. package/core/server/live/StateSignature.ts +644 -0
  115. package/core/server/live/WebSocketConnectionManager.ts +688 -0
  116. package/core/server/live/websocket-plugin.ts +435 -0
  117. package/core/server/middleware/errorHandling.ts +141 -0
  118. package/core/server/middleware/index.ts +16 -0
  119. package/core/server/plugins/static-files-plugin.ts +232 -0
  120. package/core/server/services/BaseService.ts +95 -0
  121. package/core/server/services/ServiceContainer.ts +144 -0
  122. package/core/server/services/index.ts +9 -0
  123. package/core/templates/create-project.ts +46 -2
  124. package/core/testing/index.ts +10 -0
  125. package/core/testing/setup.ts +74 -0
  126. package/core/types/build.ts +38 -14
  127. package/core/types/types.ts +319 -0
  128. package/core/utils/env-runtime.ts +7 -0
  129. package/core/utils/errors/handlers.ts +264 -39
  130. package/core/utils/errors/index.ts +528 -18
  131. package/core/utils/errors/middleware.ts +114 -0
  132. package/core/utils/logger/formatters.ts +222 -0
  133. package/core/utils/logger/index.ts +167 -48
  134. package/core/utils/logger/middleware.ts +253 -0
  135. package/core/utils/logger/performance.ts +384 -0
  136. package/core/utils/logger/transports.ts +365 -0
  137. package/create-fluxstack.ts +296 -296
  138. package/fluxstack.config.ts +17 -1
  139. package/package-template.json +66 -66
  140. package/package.json +31 -6
  141. package/public/README.md +16 -0
  142. package/vite.config.ts +29 -14
  143. package/.claude/settings.local.json +0 -74
  144. package/.github/workflows/ci-build-tests.yml +0 -480
  145. package/.github/workflows/dependency-management.yml +0 -324
  146. package/.github/workflows/release-validation.yml +0 -355
  147. package/.kiro/specs/fluxstack-architecture-optimization/design.md +0 -700
  148. package/.kiro/specs/fluxstack-architecture-optimization/requirements.md +0 -127
  149. package/.kiro/specs/fluxstack-architecture-optimization/tasks.md +0 -330
  150. package/CLAUDE.md +0 -200
  151. package/Dockerfile +0 -58
  152. package/Dockerfile.backend +0 -52
  153. package/Dockerfile.frontend +0 -54
  154. package/README-Docker.md +0 -85
  155. package/ai-context/00-QUICK-START.md +0 -86
  156. package/ai-context/README.md +0 -88
  157. package/ai-context/development/eden-treaty-guide.md +0 -362
  158. package/ai-context/development/patterns.md +0 -382
  159. package/ai-context/development/plugins-guide.md +0 -572
  160. package/ai-context/examples/crud-complete.md +0 -626
  161. package/ai-context/project/architecture.md +0 -399
  162. package/ai-context/project/overview.md +0 -213
  163. package/ai-context/recent-changes/eden-treaty-refactor.md +0 -281
  164. package/ai-context/recent-changes/type-inference-fix.md +0 -223
  165. package/ai-context/reference/environment-vars.md +0 -384
  166. package/ai-context/reference/troubleshooting.md +0 -407
  167. package/app/client/src/components/TestPage.tsx +0 -453
  168. package/bun.lock +0 -1063
  169. package/bunfig.toml +0 -16
  170. package/core/__tests__/integration.test.ts +0 -227
  171. package/core/build/index.ts +0 -186
  172. package/core/config/__tests__/config-loader.test.ts +0 -591
  173. package/core/config/__tests__/config-merger.test.ts +0 -657
  174. package/core/config/__tests__/env-converter.test.ts +0 -372
  175. package/core/config/__tests__/env-processor.test.ts +0 -431
  176. package/core/config/__tests__/env.test.ts +0 -452
  177. package/core/config/__tests__/integration.test.ts +0 -418
  178. package/core/config/__tests__/loader.test.ts +0 -331
  179. package/core/config/__tests__/schema.test.ts +0 -129
  180. package/core/config/__tests__/validator.test.ts +0 -318
  181. package/core/framework/__tests__/server.test.ts +0 -233
  182. package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
  183. package/core/plugins/__tests__/manager.test.ts +0 -398
  184. package/core/plugins/__tests__/monitoring.test.ts +0 -401
  185. package/core/plugins/__tests__/registry.test.ts +0 -335
  186. package/core/utils/__tests__/errors.test.ts +0 -139
  187. package/core/utils/__tests__/helpers.test.ts +0 -297
  188. package/core/utils/__tests__/logger.test.ts +0 -141
  189. package/create-test-app.ts +0 -156
  190. package/docker-compose.microservices.yml +0 -75
  191. package/docker-compose.simple.yml +0 -57
  192. package/docker-compose.yml +0 -71
  193. package/eslint.config.js +0 -23
  194. package/flux-cli.ts +0 -214
  195. package/nginx-lb.conf +0 -37
  196. package/publish.sh +0 -63
  197. package/run-clean.ts +0 -26
  198. package/run-env-tests.ts +0 -313
  199. package/tailwind.config.js +0 -34
  200. package/tests/__mocks__/api.ts +0 -56
  201. package/tests/fixtures/users.ts +0 -69
  202. package/tests/integration/api/users.routes.test.ts +0 -221
  203. package/tests/setup.ts +0 -29
  204. package/tests/unit/app/client/App-simple.test.tsx +0 -56
  205. package/tests/unit/app/client/App.test.tsx.skip +0 -237
  206. package/tests/unit/app/client/eden-api.test.ts +0 -186
  207. package/tests/unit/app/client/simple.test.tsx +0 -23
  208. package/tests/unit/app/controllers/users.controller.test.ts +0 -150
  209. package/tests/unit/core/create-project.test.ts.skip +0 -95
  210. package/tests/unit/core/framework.test.ts +0 -144
  211. package/tests/unit/core/plugins/logger.test.ts.skip +0 -268
  212. package/tests/unit/core/plugins/vite.test.ts.disabled +0 -188
  213. package/tests/utils/test-helpers.ts +0 -61
  214. package/vitest.config.ts +0 -50
  215. 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
+ }