create-fluxstack 1.0.13 ā 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.
- package/.env.example +29 -29
- package/app/client/README.md +69 -69
- package/app/client/index.html +14 -13
- package/app/client/src/App.tsx +157 -524
- package/app/client/src/components/ErrorBoundary.tsx +107 -0
- package/app/client/src/components/ErrorDisplay.css +365 -0
- package/app/client/src/components/ErrorDisplay.tsx +258 -0
- package/app/client/src/components/FluxStackConfig.tsx +1321 -0
- package/app/client/src/components/HybridLiveCounter.tsx +140 -0
- package/app/client/src/components/LiveClock.tsx +286 -0
- package/app/client/src/components/MainLayout.tsx +390 -0
- package/app/client/src/components/SidebarNavigation.tsx +391 -0
- package/app/client/src/components/StateDemo.tsx +178 -0
- package/app/client/src/components/SystemMonitor.tsx +1038 -0
- package/app/client/src/components/Teste.tsx +104 -0
- package/app/client/src/components/UserProfile.tsx +809 -0
- package/app/client/src/hooks/useAuth.ts +39 -0
- package/app/client/src/hooks/useNotifications.ts +56 -0
- package/app/client/src/lib/eden-api.ts +189 -53
- package/app/client/src/lib/errors.ts +340 -0
- package/app/client/src/lib/hooks/useErrorHandler.ts +258 -0
- package/app/client/src/lib/index.ts +45 -0
- package/app/client/src/main.tsx +3 -2
- package/app/client/src/pages/ApiDocs.tsx +182 -0
- package/app/client/src/pages/Demo.tsx +174 -0
- package/app/client/src/pages/HybridLive.tsx +263 -0
- package/app/client/src/pages/Overview.tsx +155 -0
- package/app/client/src/store/README.md +43 -0
- package/app/client/src/store/index.ts +16 -0
- package/app/client/src/store/slices/uiSlice.ts +151 -0
- package/app/client/src/store/slices/userSlice.ts +161 -0
- package/app/client/src/test/README.md +257 -0
- package/app/client/src/test/setup.ts +70 -0
- package/app/client/src/test/types.ts +12 -0
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/client/tsconfig.app.json +44 -43
- package/app/client/tsconfig.json +7 -7
- package/app/client/tsconfig.node.json +25 -25
- package/app/client/zustand-setup.md +65 -0
- package/app/server/controllers/users.controller.ts +68 -68
- package/app/server/index.ts +9 -1
- package/app/server/live/CounterComponent.ts +191 -0
- package/app/server/live/FluxStackConfig.ts +529 -0
- package/app/server/live/LiveClockComponent.ts +214 -0
- package/app/server/live/SidebarNavigation.ts +156 -0
- package/app/server/live/SystemMonitor.ts +594 -0
- package/app/server/live/SystemMonitorIntegration.ts +151 -0
- package/app/server/live/TesteComponent.ts +87 -0
- package/app/server/live/UserProfileComponent.ts +135 -0
- package/app/server/live/register-components.ts +28 -0
- package/app/server/middleware/auth.ts +136 -0
- package/app/server/middleware/errorHandling.ts +250 -0
- package/app/server/middleware/index.ts +10 -0
- package/app/server/middleware/rateLimit.ts +193 -0
- package/app/server/middleware/requestLogging.ts +215 -0
- package/app/server/middleware/validation.ts +270 -0
- package/app/server/routes/index.ts +14 -2
- package/app/server/routes/upload.ts +92 -0
- package/app/server/routes/users.routes.ts +2 -9
- package/app/server/services/NotificationService.ts +302 -0
- package/app/server/services/UserService.ts +222 -0
- package/app/server/services/index.ts +46 -0
- package/core/cli/commands/plugin-deps.ts +263 -0
- package/core/cli/generators/README.md +339 -0
- package/core/cli/generators/component.ts +770 -0
- package/core/cli/generators/controller.ts +299 -0
- package/core/cli/generators/index.ts +144 -0
- package/core/cli/generators/interactive.ts +228 -0
- package/core/cli/generators/prompts.ts +83 -0
- package/core/cli/generators/route.ts +513 -0
- package/core/cli/generators/service.ts +465 -0
- package/core/cli/generators/template-engine.ts +154 -0
- package/core/cli/generators/types.ts +71 -0
- package/core/cli/generators/utils.ts +192 -0
- package/core/cli/index.ts +69 -0
- package/core/cli/plugin-discovery.ts +16 -85
- package/core/client/fluxstack.ts +17 -0
- package/core/client/hooks/index.ts +7 -0
- package/core/client/hooks/state-validator.ts +130 -0
- package/core/client/hooks/useAuth.ts +49 -0
- package/core/client/hooks/useChunkedUpload.ts +258 -0
- package/core/client/hooks/useHybridLiveComponent.ts +967 -0
- package/core/client/hooks/useWebSocket.ts +373 -0
- package/core/client/index.ts +47 -0
- package/core/client/state/createStore.ts +193 -0
- package/core/client/state/index.ts +15 -0
- package/core/config/env-dynamic.ts +1 -1
- package/core/config/env.ts +2 -1
- package/core/config/runtime-config.ts +3 -3
- package/core/config/schema.ts +84 -49
- package/core/framework/server.ts +30 -0
- package/core/index.ts +25 -0
- package/core/live/ComponentRegistry.ts +399 -0
- package/core/live/types.ts +164 -0
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +1201 -0
- package/core/plugins/built-in/live-components/index.ts +27 -0
- package/core/plugins/built-in/logger/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +1 -1
- package/core/plugins/built-in/static/index.ts +1 -1
- package/core/plugins/built-in/swagger/index.ts +1 -1
- package/core/plugins/built-in/vite/index.ts +1 -1
- package/core/plugins/dependency-manager.ts +384 -0
- package/core/plugins/index.ts +5 -1
- package/core/plugins/manager.ts +7 -3
- package/core/plugins/registry.ts +88 -10
- package/core/plugins/types.ts +11 -11
- package/core/server/framework.ts +43 -0
- package/core/server/index.ts +11 -1
- package/core/server/live/ComponentRegistry.ts +1017 -0
- package/core/server/live/FileUploadManager.ts +272 -0
- package/core/server/live/LiveComponentPerformanceMonitor.ts +930 -0
- package/core/server/live/SingleConnectionManager.ts +0 -0
- package/core/server/live/StateSignature.ts +644 -0
- package/core/server/live/WebSocketConnectionManager.ts +688 -0
- package/core/server/live/websocket-plugin.ts +435 -0
- package/core/server/middleware/errorHandling.ts +141 -0
- package/core/server/middleware/index.ts +16 -0
- package/core/server/plugins/static-files-plugin.ts +232 -0
- package/core/server/services/BaseService.ts +95 -0
- package/core/server/services/ServiceContainer.ts +144 -0
- package/core/server/services/index.ts +9 -0
- package/core/templates/create-project.ts +46 -2
- package/core/testing/index.ts +10 -0
- package/core/testing/setup.ts +74 -0
- package/core/types/build.ts +38 -14
- package/core/types/types.ts +319 -0
- package/core/utils/env-runtime.ts +7 -0
- package/core/utils/errors/handlers.ts +264 -39
- package/core/utils/errors/index.ts +528 -18
- package/core/utils/errors/middleware.ts +114 -0
- package/core/utils/logger/formatters.ts +222 -0
- package/core/utils/logger/index.ts +167 -48
- package/core/utils/logger/middleware.ts +253 -0
- package/core/utils/logger/performance.ts +384 -0
- package/core/utils/logger/transports.ts +365 -0
- package/create-fluxstack.ts +296 -296
- package/fluxstack.config.ts +17 -1
- package/package-template.json +66 -66
- package/package.json +31 -6
- package/public/README.md +16 -0
- package/vite.config.ts +29 -14
- package/.claude/settings.local.json +0 -74
- package/.github/workflows/ci-build-tests.yml +0 -480
- package/.github/workflows/dependency-management.yml +0 -324
- package/.github/workflows/release-validation.yml +0 -355
- package/.kiro/specs/fluxstack-architecture-optimization/design.md +0 -700
- package/.kiro/specs/fluxstack-architecture-optimization/requirements.md +0 -127
- package/.kiro/specs/fluxstack-architecture-optimization/tasks.md +0 -330
- package/CLAUDE.md +0 -200
- package/Dockerfile +0 -58
- package/Dockerfile.backend +0 -52
- package/Dockerfile.frontend +0 -54
- package/README-Docker.md +0 -85
- package/ai-context/00-QUICK-START.md +0 -86
- package/ai-context/README.md +0 -88
- package/ai-context/development/eden-treaty-guide.md +0 -362
- package/ai-context/development/patterns.md +0 -382
- package/ai-context/development/plugins-guide.md +0 -572
- package/ai-context/examples/crud-complete.md +0 -626
- package/ai-context/project/architecture.md +0 -399
- package/ai-context/project/overview.md +0 -213
- package/ai-context/recent-changes/eden-treaty-refactor.md +0 -281
- package/ai-context/recent-changes/type-inference-fix.md +0 -223
- package/ai-context/reference/environment-vars.md +0 -384
- package/ai-context/reference/troubleshooting.md +0 -407
- package/app/client/src/components/TestPage.tsx +0 -453
- package/bun.lock +0 -1063
- package/bunfig.toml +0 -16
- package/core/__tests__/integration.test.ts +0 -227
- package/core/build/index.ts +0 -186
- package/core/config/__tests__/config-loader.test.ts +0 -554
- package/core/config/__tests__/config-merger.test.ts +0 -657
- package/core/config/__tests__/env-converter.test.ts +0 -372
- package/core/config/__tests__/env-processor.test.ts +0 -431
- package/core/config/__tests__/env.test.ts +0 -452
- package/core/config/__tests__/integration.test.ts +0 -418
- package/core/config/__tests__/loader.test.ts +0 -331
- package/core/config/__tests__/schema.test.ts +0 -129
- package/core/config/__tests__/validator.test.ts +0 -318
- package/core/framework/__tests__/server.test.ts +0 -233
- package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
- package/core/plugins/__tests__/manager.test.ts +0 -398
- package/core/plugins/__tests__/monitoring.test.ts +0 -401
- package/core/plugins/__tests__/registry.test.ts +0 -335
- package/core/utils/__tests__/errors.test.ts +0 -139
- package/core/utils/__tests__/helpers.test.ts +0 -297
- package/core/utils/__tests__/logger.test.ts +0 -141
- package/create-test-app.ts +0 -156
- package/docker-compose.microservices.yml +0 -75
- package/docker-compose.simple.yml +0 -57
- package/docker-compose.yml +0 -71
- package/eslint.config.js +0 -23
- package/flux-cli.ts +0 -214
- package/nginx-lb.conf +0 -37
- package/publish.sh +0 -63
- package/run-clean.ts +0 -26
- package/run-env-tests.ts +0 -313
- package/tailwind.config.js +0 -34
- package/tests/__mocks__/api.ts +0 -56
- package/tests/fixtures/users.ts +0 -69
- package/tests/integration/api/users.routes.test.ts +0 -221
- package/tests/setup.ts +0 -29
- package/tests/unit/app/client/App-simple.test.tsx +0 -56
- package/tests/unit/app/client/App.test.tsx.skip +0 -237
- package/tests/unit/app/client/eden-api.test.ts +0 -186
- package/tests/unit/app/client/simple.test.tsx +0 -23
- package/tests/unit/app/controllers/users.controller.test.ts +0 -150
- package/tests/unit/core/create-project.test.ts.skip +0 -95
- package/tests/unit/core/framework.test.ts +0 -144
- package/tests/unit/core/plugins/logger.test.ts.skip +0 -268
- package/tests/unit/core/plugins/vite.test.ts.disabled +0 -188
- package/tests/utils/test-helpers.ts +0 -61
- package/vitest.config.ts +0 -50
- package/workspace.json +0 -6
|
@@ -0,0 +1,299 @@
|
|
|
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 ControllerGenerator implements Generator {
|
|
6
|
+
name = 'controller'
|
|
7
|
+
description = 'Generate a new controller with CRUD operations'
|
|
8
|
+
|
|
9
|
+
async generate(context: GeneratorContext, options: GeneratorOptions): Promise<void> {
|
|
10
|
+
const template = this.getTemplate(options.template)
|
|
11
|
+
|
|
12
|
+
// Execute before hook if present
|
|
13
|
+
if (template.hooks?.beforeGenerate) {
|
|
14
|
+
await template.hooks.beforeGenerate(context, options)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const files = await templateEngine.processTemplate(template, context, options)
|
|
18
|
+
|
|
19
|
+
if (options.dryRun) {
|
|
20
|
+
console.log(`\nš Would generate controller '${options.name}':\n`)
|
|
21
|
+
for (const file of files) {
|
|
22
|
+
console.log(`${file.action === 'create' ? 'š' : 'āļø'} ${file.path}`)
|
|
23
|
+
}
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
await templateEngine.generateFiles(files, options.dryRun)
|
|
28
|
+
|
|
29
|
+
// Execute after hook if present
|
|
30
|
+
if (template.hooks?.afterGenerate) {
|
|
31
|
+
const filePaths = files.map(f => f.path)
|
|
32
|
+
await template.hooks.afterGenerate(context, options, filePaths)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(`\nā
Generated controller '${options.name}' with ${files.length} files`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private getTemplate(templateName?: string): Template {
|
|
39
|
+
switch (templateName) {
|
|
40
|
+
case 'minimal':
|
|
41
|
+
return this.getMinimalTemplate()
|
|
42
|
+
case 'crud':
|
|
43
|
+
default:
|
|
44
|
+
return this.getCrudTemplate()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private getCrudTemplate(): Template {
|
|
49
|
+
return {
|
|
50
|
+
name: 'crud-controller',
|
|
51
|
+
description: 'Full CRUD controller with validation',
|
|
52
|
+
files: [
|
|
53
|
+
{
|
|
54
|
+
path: 'app/server/controllers/{{kebabName}}.controller.ts',
|
|
55
|
+
content: `import { Elysia, t } from 'elysia'
|
|
56
|
+
import { {{pascalName}}Service } from '../services/{{kebabName}}.service'
|
|
57
|
+
import { {{pascalName}}Schema, Create{{pascalName}}Schema, Update{{pascalName}}Schema } from '../schemas/{{kebabName}}.schema'
|
|
58
|
+
import { NotFoundError, ValidationError } from '../../../core/utils/errors'
|
|
59
|
+
|
|
60
|
+
export class {{pascalName}}Controller {
|
|
61
|
+
private service: {{pascalName}}Service
|
|
62
|
+
|
|
63
|
+
constructor() {
|
|
64
|
+
this.service = new {{pascalName}}Service()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
routes() {
|
|
68
|
+
return new Elysia({ prefix: '/{{kebabName}}s' })
|
|
69
|
+
.get('/', this.getAll.bind(this))
|
|
70
|
+
.get('/:id', this.getById.bind(this), {
|
|
71
|
+
params: t.Object({
|
|
72
|
+
id: t.String()
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
.post('/', this.create.bind(this), {
|
|
76
|
+
body: Create{{pascalName}}Schema
|
|
77
|
+
})
|
|
78
|
+
.put('/:id', this.update.bind(this), {
|
|
79
|
+
params: t.Object({
|
|
80
|
+
id: t.String()
|
|
81
|
+
}),
|
|
82
|
+
body: Update{{pascalName}}Schema
|
|
83
|
+
})
|
|
84
|
+
.delete('/:id', this.delete.bind(this), {
|
|
85
|
+
params: t.Object({
|
|
86
|
+
id: t.String()
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async getAll() {
|
|
92
|
+
try {
|
|
93
|
+
const {{camelName}}s = await this.service.findAll()
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
data: {{camelName}}s,
|
|
97
|
+
count: {{camelName}}s.length
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
throw new Error(\`Failed to fetch {{camelName}}s: \${error instanceof Error ? error.message : 'Unknown error'}\`)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async getById({ params }: { params: { id: string } }) {
|
|
105
|
+
try {
|
|
106
|
+
const {{camelName}} = await this.service.findById(params.id)
|
|
107
|
+
|
|
108
|
+
if (!{{camelName}}) {
|
|
109
|
+
throw new NotFoundError('{{pascalName}} not found')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
success: true,
|
|
114
|
+
data: {{camelName}}
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof NotFoundError) {
|
|
118
|
+
throw error
|
|
119
|
+
}
|
|
120
|
+
throw new Error(\`Failed to fetch {{camelName}}: \${error instanceof Error ? error.message : 'Unknown error'}\`)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async create({ body }: { body: any }) {
|
|
125
|
+
try {
|
|
126
|
+
const {{camelName}} = await this.service.create(body)
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
success: true,
|
|
130
|
+
data: {{camelName}},
|
|
131
|
+
message: '{{pascalName}} created successfully'
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error instanceof ValidationError) {
|
|
135
|
+
throw error
|
|
136
|
+
}
|
|
137
|
+
throw new Error(\`Failed to create {{camelName}}: \${error instanceof Error ? error.message : 'Unknown error'}\`)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async update({ params, body }: { params: { id: string }, body: any }) {
|
|
142
|
+
try {
|
|
143
|
+
const {{camelName}} = await this.service.update(params.id, body)
|
|
144
|
+
|
|
145
|
+
if (!{{camelName}}) {
|
|
146
|
+
throw new NotFoundError('{{pascalName}} not found')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
success: true,
|
|
151
|
+
data: {{camelName}},
|
|
152
|
+
message: '{{pascalName}} updated successfully'
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof NotFoundError || error instanceof ValidationError) {
|
|
156
|
+
throw error
|
|
157
|
+
}
|
|
158
|
+
throw new Error(\`Failed to update {{camelName}}: \${error instanceof Error ? error.message : 'Unknown error'}\`)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async delete({ params }: { params: { id: string } }) {
|
|
163
|
+
try {
|
|
164
|
+
const deleted = await this.service.delete(params.id)
|
|
165
|
+
|
|
166
|
+
if (!deleted) {
|
|
167
|
+
throw new NotFoundError('{{pascalName}} not found')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
success: true,
|
|
172
|
+
message: '{{pascalName}} deleted successfully'
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (error instanceof NotFoundError) {
|
|
176
|
+
throw error
|
|
177
|
+
}
|
|
178
|
+
throw new Error(\`Failed to delete {{camelName}}: \${error instanceof Error ? error.message : 'Unknown error'}\`)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
`
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
path: 'app/server/schemas/{{kebabName}}.schema.ts',
|
|
186
|
+
content: `import { t } from 'elysia'
|
|
187
|
+
|
|
188
|
+
export const {{pascalName}}Schema = t.Object({
|
|
189
|
+
id: t.String(),
|
|
190
|
+
name: t.String(),
|
|
191
|
+
description: t.Optional(t.String()),
|
|
192
|
+
createdAt: t.Date(),
|
|
193
|
+
updatedAt: t.Date()
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
export const Create{{pascalName}}Schema = t.Object({
|
|
197
|
+
name: t.String({
|
|
198
|
+
minLength: 1,
|
|
199
|
+
maxLength: 100,
|
|
200
|
+
error: 'Name must be between 1 and 100 characters'
|
|
201
|
+
}),
|
|
202
|
+
description: t.Optional(t.String({
|
|
203
|
+
maxLength: 500,
|
|
204
|
+
error: 'Description must be less than 500 characters'
|
|
205
|
+
}))
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
export const Update{{pascalName}}Schema = t.Partial(Create{{pascalName}}Schema)
|
|
209
|
+
|
|
210
|
+
export type {{pascalName}} = typeof {{pascalName}}Schema.static
|
|
211
|
+
export type Create{{pascalName}} = typeof Create{{pascalName}}Schema.static
|
|
212
|
+
export type Update{{pascalName}} = typeof Update{{pascalName}}Schema.static
|
|
213
|
+
`
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
hooks: {
|
|
217
|
+
afterGenerate: async (context, options, files) => {
|
|
218
|
+
context.logger.info(`Generated controller files:`)
|
|
219
|
+
files.forEach(file => {
|
|
220
|
+
context.logger.info(` - ${file}`)
|
|
221
|
+
})
|
|
222
|
+
context.logger.info(`\nNext steps:`)
|
|
223
|
+
context.logger.info(`1. Generate the corresponding service: flux generate service ${options.name}`)
|
|
224
|
+
context.logger.info(`2. Add the controller to your routes in app/server/routes/`)
|
|
225
|
+
context.logger.info(`3. Implement the business logic in the service`)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private getMinimalTemplate(): Template {
|
|
232
|
+
return {
|
|
233
|
+
name: 'minimal-controller',
|
|
234
|
+
description: 'Minimal controller with basic structure',
|
|
235
|
+
files: [
|
|
236
|
+
{
|
|
237
|
+
path: 'app/server/controllers/{{kebabName}}.controller.ts',
|
|
238
|
+
content: `import { Elysia } from 'elysia'
|
|
239
|
+
|
|
240
|
+
export class {{pascalName}}Controller {
|
|
241
|
+
routes() {
|
|
242
|
+
return new Elysia({ prefix: '/{{kebabName}}s' })
|
|
243
|
+
.get('/', this.index.bind(this))
|
|
244
|
+
.get('/:id', this.show.bind(this))
|
|
245
|
+
.post('/', this.create.bind(this))
|
|
246
|
+
.put('/:id', this.update.bind(this))
|
|
247
|
+
.delete('/:id', this.destroy.bind(this))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async index() {
|
|
251
|
+
// TODO: Implement list logic
|
|
252
|
+
return {
|
|
253
|
+
success: true,
|
|
254
|
+
data: [],
|
|
255
|
+
message: 'List {{camelName}}s'
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async show({ params }: { params: { id: string } }) {
|
|
260
|
+
// TODO: Implement show logic
|
|
261
|
+
return {
|
|
262
|
+
success: true,
|
|
263
|
+
data: { id: params.id },
|
|
264
|
+
message: 'Show {{camelName}}'
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async create({ body }: { body: any }) {
|
|
269
|
+
// TODO: Implement create logic
|
|
270
|
+
return {
|
|
271
|
+
success: true,
|
|
272
|
+
data: body,
|
|
273
|
+
message: '{{pascalName}} created'
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async update({ params, body }: { params: { id: string }, body: any }) {
|
|
278
|
+
// TODO: Implement update logic
|
|
279
|
+
return {
|
|
280
|
+
success: true,
|
|
281
|
+
data: { id: params.id, ...body },
|
|
282
|
+
message: '{{pascalName}} updated'
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async destroy({ params }: { params: { id: string } }) {
|
|
287
|
+
// TODO: Implement delete logic
|
|
288
|
+
return {
|
|
289
|
+
success: true,
|
|
290
|
+
message: '{{pascalName}} deleted'
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
`
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { CliCommand } from "../../plugins/types.js"
|
|
2
|
+
import { ControllerGenerator } from "./controller.js"
|
|
3
|
+
import { RouteGenerator } from "./route.js"
|
|
4
|
+
import { ComponentGenerator } from "./component.js"
|
|
5
|
+
import { ServiceGenerator } from "./service.js"
|
|
6
|
+
import type { GeneratorContext, GeneratorOptions } from "./types.js"
|
|
7
|
+
|
|
8
|
+
export interface Generator {
|
|
9
|
+
name: string
|
|
10
|
+
description: string
|
|
11
|
+
generate(context: GeneratorContext, options: GeneratorOptions): Promise<void>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class GeneratorRegistry {
|
|
15
|
+
private generators = new Map<string, Generator>()
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
this.registerBuiltInGenerators()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private registerBuiltInGenerators() {
|
|
22
|
+
this.register(new ControllerGenerator())
|
|
23
|
+
this.register(new RouteGenerator())
|
|
24
|
+
this.register(new ComponentGenerator())
|
|
25
|
+
this.register(new ServiceGenerator())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
register(generator: Generator): void {
|
|
29
|
+
this.generators.set(generator.name, generator)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get(name: string): Generator | undefined {
|
|
33
|
+
return this.generators.get(name)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getAll(): Generator[] {
|
|
37
|
+
return Array.from(this.generators.values())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
has(name: string): boolean {
|
|
41
|
+
return this.generators.has(name)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const generatorRegistry = new GeneratorRegistry()
|
|
46
|
+
|
|
47
|
+
// Export additional commands
|
|
48
|
+
export { interactiveGenerateCommand } from "./interactive.js"
|
|
49
|
+
|
|
50
|
+
// CLI command for code generation
|
|
51
|
+
export const generateCommand: CliCommand = {
|
|
52
|
+
name: 'generate',
|
|
53
|
+
description: 'Generate code from templates',
|
|
54
|
+
usage: 'flux generate <type> <name> [options]',
|
|
55
|
+
aliases: ['g', 'gen'],
|
|
56
|
+
category: 'Development',
|
|
57
|
+
examples: [
|
|
58
|
+
'flux generate controller user',
|
|
59
|
+
'flux generate component UserCard',
|
|
60
|
+
'flux generate service auth',
|
|
61
|
+
'flux generate route api/users'
|
|
62
|
+
],
|
|
63
|
+
arguments: [
|
|
64
|
+
{
|
|
65
|
+
name: 'type',
|
|
66
|
+
description: 'Type of code to generate',
|
|
67
|
+
required: true,
|
|
68
|
+
type: 'string',
|
|
69
|
+
choices: ['controller', 'route', 'component', 'service']
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'name',
|
|
73
|
+
description: 'Name of the generated item',
|
|
74
|
+
required: true,
|
|
75
|
+
type: 'string'
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
options: [
|
|
79
|
+
{
|
|
80
|
+
name: 'path',
|
|
81
|
+
short: 'p',
|
|
82
|
+
description: 'Custom path for generated files',
|
|
83
|
+
type: 'string'
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'template',
|
|
87
|
+
short: 't',
|
|
88
|
+
description: 'Template variant to use',
|
|
89
|
+
type: 'string'
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'force',
|
|
93
|
+
short: 'f',
|
|
94
|
+
description: 'Overwrite existing files',
|
|
95
|
+
type: 'boolean',
|
|
96
|
+
default: false
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'dry-run',
|
|
100
|
+
description: 'Show what would be generated without creating files',
|
|
101
|
+
type: 'boolean',
|
|
102
|
+
default: false
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
handler: async (args, options, context) => {
|
|
106
|
+
const [type, name] = args
|
|
107
|
+
|
|
108
|
+
const generator = generatorRegistry.get(type)
|
|
109
|
+
if (!generator) {
|
|
110
|
+
console.error(`ā Unknown generator type: ${type}`)
|
|
111
|
+
console.log('\nAvailable generators:')
|
|
112
|
+
for (const gen of generatorRegistry.getAll()) {
|
|
113
|
+
console.log(` ${gen.name.padEnd(12)} ${gen.description}`)
|
|
114
|
+
}
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const generatorContext: GeneratorContext = {
|
|
119
|
+
workingDir: context.workingDir,
|
|
120
|
+
config: context.config,
|
|
121
|
+
logger: context.logger,
|
|
122
|
+
utils: context.utils
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const generatorOptions: GeneratorOptions = {
|
|
126
|
+
name,
|
|
127
|
+
path: options.path,
|
|
128
|
+
template: options.template,
|
|
129
|
+
force: options.force,
|
|
130
|
+
dryRun: options['dry-run']
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
await generator.generate(generatorContext, generatorOptions)
|
|
135
|
+
|
|
136
|
+
if (!options['dry-run']) {
|
|
137
|
+
console.log(`ā
Successfully generated ${type}: ${name}`)
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error(`ā Failed to generate ${type}:`, error instanceof Error ? error.message : String(error))
|
|
141
|
+
throw error
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type { CliCommand } from "../../plugins/types.js"
|
|
2
|
+
import { generatorRegistry } from "./index.js"
|
|
3
|
+
import type { GeneratorContext, GeneratorOptions } from "./types.js"
|
|
4
|
+
import { promptSystem } from "./prompts.js"
|
|
5
|
+
|
|
6
|
+
export const interactiveGenerateCommand: CliCommand = {
|
|
7
|
+
name: 'generate:interactive',
|
|
8
|
+
description: 'Generate code interactively with prompts',
|
|
9
|
+
usage: 'flux generate:interactive',
|
|
10
|
+
aliases: ['gi', 'gen:i'],
|
|
11
|
+
category: 'Development',
|
|
12
|
+
examples: [
|
|
13
|
+
'flux generate:interactive',
|
|
14
|
+
'flux gi'
|
|
15
|
+
],
|
|
16
|
+
handler: async (args, options, context) => {
|
|
17
|
+
console.log('šÆ FluxStack Interactive Code Generator\n')
|
|
18
|
+
|
|
19
|
+
// Select generator type
|
|
20
|
+
const generators = generatorRegistry.getAll()
|
|
21
|
+
const generatorChoices = generators.map(gen => ({
|
|
22
|
+
name: `${gen.name} - ${gen.description}`,
|
|
23
|
+
value: gen.name
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
const selectedType = await promptSystem.select(
|
|
27
|
+
'What would you like to generate?',
|
|
28
|
+
generatorChoices
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const generator = generatorRegistry.get(selectedType)
|
|
32
|
+
if (!generator) {
|
|
33
|
+
console.error(`ā Generator not found: ${selectedType}`)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Get name
|
|
38
|
+
const name = await promptSystem.input(
|
|
39
|
+
`Enter the ${selectedType} name:`,
|
|
40
|
+
undefined,
|
|
41
|
+
(value) => {
|
|
42
|
+
if (!value.trim()) {
|
|
43
|
+
return 'Name is required'
|
|
44
|
+
}
|
|
45
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(value)) {
|
|
46
|
+
return 'Name must start with a letter and contain only letters, numbers, hyphens, and underscores'
|
|
47
|
+
}
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
// Get template variant (if applicable)
|
|
53
|
+
let template: string | undefined
|
|
54
|
+
if (selectedType === 'controller') {
|
|
55
|
+
template = await promptSystem.select(
|
|
56
|
+
'Choose controller template:',
|
|
57
|
+
[
|
|
58
|
+
{ name: 'CRUD - Full CRUD operations with validation', value: 'crud' },
|
|
59
|
+
{ name: 'Minimal - Basic structure only', value: 'minimal' }
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
} else if (selectedType === 'component') {
|
|
63
|
+
template = await promptSystem.select(
|
|
64
|
+
'Choose component template:',
|
|
65
|
+
[
|
|
66
|
+
{ name: 'Basic - Simple component', value: 'basic' },
|
|
67
|
+
{ name: 'Functional - Component with hooks', value: 'functional' },
|
|
68
|
+
{ name: 'Page - Page component with layout', value: 'page' },
|
|
69
|
+
{ name: 'Form - Form component with validation', value: 'form' },
|
|
70
|
+
{ name: 'Full - Complete with tests and stories', value: 'full' }
|
|
71
|
+
]
|
|
72
|
+
)
|
|
73
|
+
} else if (selectedType === 'service') {
|
|
74
|
+
template = await promptSystem.select(
|
|
75
|
+
'Choose service template:',
|
|
76
|
+
[
|
|
77
|
+
{ name: 'CRUD - Full service with repository', value: 'crud' },
|
|
78
|
+
{ name: 'Repository - Service with repository pattern', value: 'repository' },
|
|
79
|
+
{ name: 'Minimal - Basic structure only', value: 'minimal' }
|
|
80
|
+
]
|
|
81
|
+
)
|
|
82
|
+
} else if (selectedType === 'route') {
|
|
83
|
+
template = await promptSystem.select(
|
|
84
|
+
'Choose route template:',
|
|
85
|
+
[
|
|
86
|
+
{ name: 'CRUD - Full REST API routes', value: 'crud' },
|
|
87
|
+
{ name: 'Auth - Authentication routes', value: 'auth' },
|
|
88
|
+
{ name: 'Minimal - Basic routes only', value: 'minimal' }
|
|
89
|
+
]
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Get custom path (optional)
|
|
94
|
+
const useCustomPath = await promptSystem.confirm(
|
|
95
|
+
'Do you want to specify a custom path?',
|
|
96
|
+
false
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
let customPath: string | undefined
|
|
100
|
+
if (useCustomPath) {
|
|
101
|
+
customPath = await promptSystem.input(
|
|
102
|
+
'Enter custom path (relative to project root):'
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Confirm overwrite if files exist
|
|
107
|
+
const force = await promptSystem.confirm(
|
|
108
|
+
'Overwrite existing files if they exist?',
|
|
109
|
+
false
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// Show dry run first
|
|
113
|
+
console.log('\nš Preview of files to be generated:\n')
|
|
114
|
+
|
|
115
|
+
const generatorContext: GeneratorContext = {
|
|
116
|
+
workingDir: context.workingDir,
|
|
117
|
+
config: context.config,
|
|
118
|
+
logger: context.logger,
|
|
119
|
+
utils: context.utils
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const generatorOptions: GeneratorOptions = {
|
|
123
|
+
name,
|
|
124
|
+
path: customPath,
|
|
125
|
+
template,
|
|
126
|
+
force,
|
|
127
|
+
dryRun: true
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await generator.generate(generatorContext, generatorOptions)
|
|
132
|
+
|
|
133
|
+
const proceed = await promptSystem.confirm(
|
|
134
|
+
'\nProceed with generation?',
|
|
135
|
+
true
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if (!proceed) {
|
|
139
|
+
console.log('ā Generation cancelled')
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Generate for real
|
|
144
|
+
generatorOptions.dryRun = false
|
|
145
|
+
await generator.generate(generatorContext, generatorOptions)
|
|
146
|
+
|
|
147
|
+
console.log(`\nā
Successfully generated ${selectedType}: ${name}`)
|
|
148
|
+
|
|
149
|
+
// Ask if user wants to generate related files
|
|
150
|
+
await suggestRelatedGenerations(selectedType, name, generatorContext)
|
|
151
|
+
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error(`ā Failed to generate ${selectedType}:`, error instanceof Error ? error.message : String(error))
|
|
154
|
+
throw error
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function suggestRelatedGenerations(
|
|
160
|
+
generatedType: string,
|
|
161
|
+
name: string,
|
|
162
|
+
context: GeneratorContext
|
|
163
|
+
): Promise<void> {
|
|
164
|
+
const suggestions: Array<{ type: string; description: string }> = []
|
|
165
|
+
|
|
166
|
+
switch (generatedType) {
|
|
167
|
+
case 'controller':
|
|
168
|
+
suggestions.push(
|
|
169
|
+
{ type: 'service', description: `Generate ${name} service for business logic` },
|
|
170
|
+
{ type: 'route', description: `Generate ${name} routes to expose the controller` }
|
|
171
|
+
)
|
|
172
|
+
break
|
|
173
|
+
case 'service':
|
|
174
|
+
suggestions.push(
|
|
175
|
+
{ type: 'controller', description: `Generate ${name} controller to use this service` }
|
|
176
|
+
)
|
|
177
|
+
break
|
|
178
|
+
case 'route':
|
|
179
|
+
suggestions.push(
|
|
180
|
+
{ type: 'controller', description: `Generate ${name} controller for route handlers` }
|
|
181
|
+
)
|
|
182
|
+
break
|
|
183
|
+
case 'component':
|
|
184
|
+
// No automatic suggestions for components
|
|
185
|
+
break
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (suggestions.length === 0) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
console.log('\nš” Suggested next steps:')
|
|
193
|
+
for (const suggestion of suggestions) {
|
|
194
|
+
console.log(` ⢠${suggestion.description}`)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const generateRelated = await promptSystem.confirm(
|
|
198
|
+
'\nWould you like to generate related files now?',
|
|
199
|
+
false
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if (!generateRelated) {
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (const suggestion of suggestions) {
|
|
207
|
+
const shouldGenerate = await promptSystem.confirm(
|
|
208
|
+
`Generate ${suggestion.type} for ${name}?`,
|
|
209
|
+
true
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if (shouldGenerate) {
|
|
213
|
+
const generator = generatorRegistry.get(suggestion.type)
|
|
214
|
+
if (generator) {
|
|
215
|
+
try {
|
|
216
|
+
await generator.generate(context, {
|
|
217
|
+
name,
|
|
218
|
+
force: false,
|
|
219
|
+
dryRun: false
|
|
220
|
+
})
|
|
221
|
+
console.log(`ā
Generated ${suggestion.type}: ${name}`)
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.error(`ā Failed to generate ${suggestion.type}:`, error instanceof Error ? error.message : String(error))
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|