create-fluxstack 1.8.1 โ†’ 1.9.1

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 (136) hide show
  1. package/.env.example +19 -0
  2. package/README.md +653 -272
  3. package/app/client/SIMPLIFICATION.md +140 -0
  4. package/app/client/frontend-only.ts +1 -1
  5. package/app/client/src/App.tsx +148 -283
  6. package/app/client/src/index.css +5 -20
  7. package/app/client/src/lib/eden-api.ts +53 -220
  8. package/app/client/src/main.tsx +2 -3
  9. package/app/server/controllers/users.controller.ts +57 -31
  10. package/app/server/index.ts +5 -2
  11. package/app/server/live/register-components.ts +18 -7
  12. package/app/server/routes/env-test.ts +53 -2
  13. package/app/server/routes/index.ts +1 -8
  14. package/app/server/routes/users.routes.ts +192 -91
  15. package/config/fluxstack.config.ts +2 -2
  16. package/config/plugins.config.ts +22 -1
  17. package/core/build/bundler.ts +199 -55
  18. package/core/build/flux-plugins-generator.ts +5 -5
  19. package/core/build/index.ts +4 -0
  20. package/core/build/live-components-generator.ts +15 -12
  21. package/core/cli/command-registry.ts +4 -14
  22. package/core/cli/commands/plugin-deps.ts +8 -8
  23. package/core/cli/generators/component.ts +3 -3
  24. package/core/cli/generators/controller.ts +4 -4
  25. package/core/cli/generators/index.ts +8 -8
  26. package/core/cli/generators/interactive.ts +4 -4
  27. package/core/cli/generators/plugin.ts +9 -9
  28. package/core/cli/generators/prompts.ts +1 -1
  29. package/core/cli/generators/route.ts +27 -11
  30. package/core/cli/generators/service.ts +5 -5
  31. package/core/cli/generators/template-engine.ts +1 -1
  32. package/core/cli/generators/types.ts +1 -1
  33. package/core/cli/index.ts +258 -193
  34. package/core/cli/plugin-discovery.ts +3 -3
  35. package/core/client/hooks/index.ts +2 -2
  36. package/core/client/hooks/state-validator.ts +1 -1
  37. package/core/client/hooks/useAuth.ts +1 -1
  38. package/core/client/hooks/useChunkedUpload.ts +1 -1
  39. package/core/client/hooks/useHybridLiveComponent.ts +1 -1
  40. package/core/client/hooks/useWebSocket.ts +1 -1
  41. package/core/config/env.ts +1 -1
  42. package/core/config/runtime-config.ts +5 -5
  43. package/core/config/schema.ts +9 -0
  44. package/core/framework/server.ts +30 -15
  45. package/core/framework/types.ts +2 -2
  46. package/core/live/ComponentRegistry.ts +1 -1
  47. package/core/plugins/built-in/live-components/commands/create-live-component.ts +1 -1
  48. package/core/plugins/built-in/live-components/index.ts +1 -1
  49. package/core/plugins/built-in/monitoring/index.ts +65 -161
  50. package/core/plugins/built-in/static/index.ts +18 -47
  51. package/core/plugins/built-in/swagger/index.ts +301 -231
  52. package/core/plugins/built-in/vite/index.ts +74 -109
  53. package/core/plugins/config.ts +2 -2
  54. package/core/plugins/dependency-manager.ts +2 -2
  55. package/core/plugins/discovery.ts +1 -1
  56. package/core/plugins/executor.ts +2 -2
  57. package/core/plugins/manager.ts +19 -4
  58. package/core/plugins/module-resolver.ts +1 -1
  59. package/core/plugins/registry.ts +3 -3
  60. package/core/plugins/types.ts +147 -5
  61. package/core/server/framework.ts +2 -2
  62. package/core/server/live/ComponentRegistry.ts +9 -26
  63. package/core/server/live/FileUploadManager.ts +1 -1
  64. package/core/server/live/auto-generated-components.ts +26 -0
  65. package/core/server/live/websocket-plugin.ts +211 -19
  66. package/core/server/middleware/errorHandling.ts +1 -1
  67. package/core/server/middleware/index.ts +4 -4
  68. package/core/server/plugins/database.ts +1 -2
  69. package/core/server/plugins/static-files-plugin.ts +259 -231
  70. package/core/server/plugins/swagger.ts +1 -1
  71. package/core/server/services/BaseService.ts +1 -1
  72. package/core/server/services/ServiceContainer.ts +1 -1
  73. package/core/server/services/index.ts +4 -4
  74. package/core/server/standalone.ts +16 -1
  75. package/core/testing/index.ts +1 -1
  76. package/core/testing/setup.ts +1 -1
  77. package/core/types/build.ts +21 -0
  78. package/core/utils/logger/startup-banner.ts +7 -33
  79. package/core/utils/version.ts +1 -1
  80. package/create-fluxstack.ts +73 -30
  81. package/package.json +3 -2
  82. package/plugins/crypto-auth/index.ts +52 -47
  83. package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
  84. package/plugins/crypto-auth/server/middlewares/helpers.ts +16 -1
  85. package/tsconfig.json +2 -1
  86. package/vitest.config.ts +11 -2
  87. package/app/client/src/App.css +0 -883
  88. package/app/client/src/components/ErrorBoundary.tsx +0 -107
  89. package/app/client/src/components/ErrorDisplay.css +0 -365
  90. package/app/client/src/components/ErrorDisplay.tsx +0 -258
  91. package/app/client/src/components/FluxStackConfig.tsx +0 -1321
  92. package/app/client/src/components/HybridLiveCounter.tsx +0 -140
  93. package/app/client/src/components/LiveClock.tsx +0 -286
  94. package/app/client/src/components/MainLayout.tsx +0 -388
  95. package/app/client/src/components/SidebarNavigation.tsx +0 -391
  96. package/app/client/src/components/StateDemo.tsx +0 -178
  97. package/app/client/src/components/SystemMonitor.tsx +0 -1044
  98. package/app/client/src/components/UserProfile.tsx +0 -809
  99. package/app/client/src/hooks/useAuth.ts +0 -39
  100. package/app/client/src/hooks/useNotifications.ts +0 -56
  101. package/app/client/src/lib/errors.ts +0 -340
  102. package/app/client/src/lib/hooks/useErrorHandler.ts +0 -258
  103. package/app/client/src/lib/index.ts +0 -45
  104. package/app/client/src/pages/ApiDocs.tsx +0 -182
  105. package/app/client/src/pages/CryptoAuthPage.tsx +0 -394
  106. package/app/client/src/pages/Demo.tsx +0 -174
  107. package/app/client/src/pages/HybridLive.tsx +0 -263
  108. package/app/client/src/pages/Overview.tsx +0 -155
  109. package/app/client/src/store/README.md +0 -43
  110. package/app/client/src/store/index.ts +0 -16
  111. package/app/client/src/store/slices/uiSlice.ts +0 -151
  112. package/app/client/src/store/slices/userSlice.ts +0 -161
  113. package/app/client/src/test/README.md +0 -257
  114. package/app/client/src/test/setup.ts +0 -70
  115. package/app/client/src/test/types.ts +0 -12
  116. package/app/server/live/CounterComponent.ts +0 -191
  117. package/app/server/live/FluxStackConfig.ts +0 -534
  118. package/app/server/live/SidebarNavigation.ts +0 -157
  119. package/app/server/live/SystemMonitor.ts +0 -595
  120. package/app/server/live/SystemMonitorIntegration.ts +0 -151
  121. package/app/server/live/UserProfileComponent.ts +0 -141
  122. package/app/server/middleware/auth.ts +0 -136
  123. package/app/server/middleware/errorHandling.ts +0 -252
  124. package/app/server/middleware/index.ts +0 -10
  125. package/app/server/middleware/rateLimit.ts +0 -193
  126. package/app/server/middleware/requestLogging.ts +0 -215
  127. package/app/server/middleware/validation.ts +0 -270
  128. package/app/server/routes/config.ts +0 -145
  129. package/app/server/routes/crypto-auth-demo.routes.ts +0 -167
  130. package/app/server/routes/example-with-crypto-auth.routes.ts +0 -235
  131. package/app/server/routes/exemplo-posts.routes.ts +0 -161
  132. package/app/server/routes/upload.ts +0 -92
  133. package/app/server/services/NotificationService.ts +0 -302
  134. package/app/server/services/UserService.ts +0 -222
  135. package/app/server/services/index.ts +0 -46
  136. package/app/server/types/index.ts +0 -1
@@ -1,114 +1,215 @@
1
- import { Elysia, t } from "elysia"
2
- import { UsersController } from "../controllers/users.controller"
3
-
4
- export const usersRoutes = new Elysia({ prefix: "/users" })
5
- .get("/", () => UsersController.getUsers(), {
6
- response: t.Object({
7
- users: t.Array(t.Object({
8
- id: t.Number(),
9
- name: t.String(),
10
- email: t.String(),
11
- createdAt: t.Date()
12
- }))
13
- }),
1
+ import { Elysia, t } from 'elysia'
2
+ import { UsersController } from '@/app/server/controllers/users.controller'
3
+
4
+ // ===== Request/Response Schemas =====
5
+
6
+ const UserSchema = t.Object({
7
+ id: t.Number(),
8
+ name: t.String(),
9
+ email: t.String()
10
+ }, {
11
+ description: 'User object'
12
+ })
13
+
14
+ const CreateUserRequestSchema = t.Object({
15
+ name: t.String({ minLength: 2, description: 'User name (minimum 2 characters)' }),
16
+ email: t.String({ format: 'email', description: 'Valid email address' })
17
+ }, {
18
+ description: 'Request body for creating a new user'
19
+ })
20
+
21
+ const CreateUserResponseSchema = t.Union([
22
+ t.Object({
23
+ success: t.Literal(true),
24
+ user: UserSchema,
25
+ message: t.Optional(t.String())
26
+ }),
27
+ t.Object({
28
+ success: t.Literal(false),
29
+ error: t.String()
30
+ })
31
+ ], {
32
+ description: 'Response after attempting to create a user'
33
+ })
34
+
35
+ const GetUsersResponseSchema = t.Object({
36
+ success: t.Boolean(),
37
+ users: t.Array(UserSchema),
38
+ count: t.Number()
39
+ }, {
40
+ description: 'List of all users'
41
+ })
42
+
43
+ const GetUserResponseSchema = t.Union([
44
+ t.Object({
45
+ success: t.Literal(true),
46
+ user: UserSchema
47
+ }),
48
+ t.Object({
49
+ success: t.Literal(false),
50
+ error: t.String()
51
+ })
52
+ ], {
53
+ description: 'Single user or error'
54
+ })
55
+
56
+ const DeleteUserResponseSchema = t.Union([
57
+ t.Object({
58
+ success: t.Literal(true),
59
+ message: t.String()
60
+ }),
61
+ t.Object({
62
+ success: t.Literal(false),
63
+ message: t.String()
64
+ })
65
+ ], {
66
+ description: 'Result of delete operation'
67
+ })
68
+
69
+ const ErrorResponseSchema = t.Object({
70
+ error: t.String()
71
+ }, {
72
+ description: 'Error response'
73
+ })
74
+
75
+ /**
76
+ * Users API Routes
77
+ */
78
+ export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
79
+ // GET /users - Get all users
80
+ .get('/', async () => {
81
+ return await UsersController.getUsers()
82
+ }, {
14
83
  detail: {
15
- tags: ['Users'],
16
- summary: 'List Users',
17
- description: 'Retrieve a list of all users in the system'
18
- }
84
+ summary: 'Get All Users',
85
+ description: 'Retrieves a list of all registered users',
86
+ tags: ['Users', 'CRUD']
87
+ },
88
+ response: GetUsersResponseSchema
19
89
  })
20
-
21
- .get("/:id", async ({ params: { id }, set }) => {
22
- const userId = parseInt(id)
23
- const result = await UsersController.getUserById(userId)
24
-
90
+
91
+ // GET /users/:id - Get user by ID
92
+ .get('/:id', async ({ params, set }) => {
93
+ const id = parseInt(params.id)
94
+
95
+ // Handle invalid ID
96
+ if (isNaN(id)) {
97
+ set.status = 400
98
+ return { error: 'ID invรกlido' }
99
+ }
100
+
101
+ const result = await UsersController.getUserById(id)
102
+
25
103
  if (!result) {
26
104
  set.status = 404
27
- return { error: "Usuรกrio nรฃo encontrado" }
105
+ return { error: 'Usuรกrio nรฃo encontrado' }
28
106
  }
29
-
107
+
30
108
  return result
31
109
  }, {
32
- params: t.Object({
33
- id: t.String()
34
- }),
35
110
  detail: {
36
- tags: ['Users'],
37
111
  summary: 'Get User by ID',
38
- description: 'Retrieve a specific user by their ID'
112
+ description: 'Retrieves a single user by their unique identifier',
113
+ tags: ['Users', 'CRUD']
114
+ },
115
+ params: t.Object({
116
+ id: t.String({ description: 'User ID' })
117
+ }),
118
+ response: {
119
+ 200: GetUserResponseSchema,
120
+ 400: ErrorResponseSchema,
121
+ 404: ErrorResponseSchema
39
122
  }
40
123
  })
41
-
42
- .post("/", async ({ body, set }) => {
43
- try {
44
- return await UsersController.createUser(body as any)
45
- } catch (error) {
124
+
125
+ // POST /users - Create new user
126
+ .post('/', async ({ body, set }) => {
127
+ // Validate required fields
128
+ if (!body.name || !body.email) {
129
+ set.status = 400
130
+ return {
131
+ success: false,
132
+ error: 'Nome e email sรฃo obrigatรณrios'
133
+ }
134
+ }
135
+
136
+ // Validate name length
137
+ if (body.name.length < 2) {
138
+ set.status = 400
139
+ return {
140
+ success: false,
141
+ error: 'Nome deve ter pelo menos 2 caracteres'
142
+ }
143
+ }
144
+
145
+ // Validate email format
146
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
147
+ if (!emailRegex.test(body.email)) {
46
148
  set.status = 400
47
- return {
48
- success: false,
49
- error: "Dados invรกlidos",
50
- details: error instanceof Error ? error.message : 'Unknown error'
149
+ return {
150
+ success: false,
151
+ error: 'Email invรกlido'
51
152
  }
52
153
  }
154
+
155
+ const result = await UsersController.createUser(body)
156
+
157
+ // If email is duplicate, still return 200 but with success: false
158
+ if (!result.success) {
159
+ return result
160
+ }
161
+
162
+ return result
53
163
  }, {
54
- body: t.Object({
55
- name: t.String({ minLength: 2 }),
56
- email: t.String({ format: "email" })
57
- }),
58
- response: t.Object({
59
- success: t.Boolean(),
60
- user: t.Optional(t.Object({
61
- id: t.Number(),
62
- name: t.String(),
63
- email: t.String(),
64
- createdAt: t.Date()
65
- })),
66
- message: t.Optional(t.String())
67
- }),
68
164
  detail: {
69
- tags: ['Users'],
70
- summary: 'Create User',
71
- description: 'Create a new user with name and email'
165
+ summary: 'Create New User',
166
+ description: 'Creates a new user with name and email. Email must be unique.',
167
+ tags: ['Users', 'CRUD']
72
168
  },
73
- error({ code, error, set }) {
74
- switch (code) {
75
- case 'VALIDATION':
76
- set.status = 400
77
- return {
78
- success: false,
79
- error: "Dados invรกlidos",
80
- details: error.message
81
- }
82
- default:
83
- set.status = 500
84
- return {
85
- success: false,
86
- error: "Erro interno do servidor"
87
- }
88
- }
169
+ body: CreateUserRequestSchema,
170
+ response: {
171
+ 200: CreateUserResponseSchema,
172
+ 400: t.Object({
173
+ success: t.Literal(false),
174
+ error: t.String()
175
+ })
89
176
  }
90
177
  })
91
-
92
- .delete("/:id", ({ params: { id } }) => {
93
- const userId = parseInt(id)
94
- return UsersController.deleteUser(userId)
178
+
179
+ // DELETE /users/:id - Delete user
180
+ .delete('/:id', async ({ params, set }) => {
181
+ const id = parseInt(params.id)
182
+
183
+ if (isNaN(id)) {
184
+ set.status = 400
185
+ return {
186
+ success: false,
187
+ message: 'ID invรกlido'
188
+ }
189
+ }
190
+
191
+ const result = await UsersController.deleteUser(id)
192
+
193
+ if (!result.success) {
194
+ // Don't set 404 status, just return success: false
195
+ return result
196
+ }
197
+
198
+ return result
95
199
  }, {
96
- params: t.Object({
97
- id: t.String()
98
- }),
99
- response: t.Object({
100
- success: t.Boolean(),
101
- user: t.Optional(t.Object({
102
- id: t.Number(),
103
- name: t.String(),
104
- email: t.String(),
105
- createdAt: t.Date()
106
- })),
107
- message: t.Optional(t.String())
108
- }),
109
200
  detail: {
110
- tags: ['Users'],
111
201
  summary: 'Delete User',
112
- description: 'Delete a user by their ID'
202
+ description: 'Deletes a user by their ID',
203
+ tags: ['Users', 'CRUD']
204
+ },
205
+ params: t.Object({
206
+ id: t.String({ description: 'User ID to delete' })
207
+ }),
208
+ response: {
209
+ 200: DeleteUserResponseSchema,
210
+ 400: t.Object({
211
+ success: t.Literal(false),
212
+ message: t.String()
213
+ })
113
214
  }
114
- })
215
+ })
@@ -4,8 +4,8 @@
4
4
  * @deprecated Use the configuration from the root fluxstack.config.ts instead
5
5
  */
6
6
 
7
- import { getConfigSync, createLegacyConfig } from '../core/config'
8
- import type { FluxStackConfig } from '../core/config'
7
+ import { getConfigSync, createLegacyConfig } from '@/core/config'
8
+ import type { FluxStackConfig } from '@/core/config'
9
9
 
10
10
  // Load the new configuration
11
11
  const newConfig = getConfigSync()
@@ -28,7 +28,8 @@ const pluginsConfigSchema = {
28
28
  // Logger plugin (handled by logger.config.ts)
29
29
  loggerEnabled: config.boolean('LOGGER_PLUGIN_ENABLED', true),
30
30
 
31
- // Swagger plugin (enable/disable via runtime.config.ts)
31
+ // Swagger plugin
32
+ swaggerEnabled: config.boolean('SWAGGER_ENABLED', true),
32
33
  swaggerTitle: config.string('SWAGGER_TITLE', 'FluxStack API'),
33
34
  swaggerVersion: config.string('SWAGGER_VERSION', FLUXSTACK_VERSION),
34
35
  swaggerDescription: config.string(
@@ -37,6 +38,26 @@ const pluginsConfigSchema = {
37
38
  ),
38
39
  swaggerPath: config.string('SWAGGER_PATH', '/swagger'),
39
40
 
41
+ // Swagger advanced options
42
+ swaggerExcludePaths: config.array('SWAGGER_EXCLUDE_PATHS', []),
43
+
44
+ // Swagger servers (comma-separated list of URLs)
45
+ // Format: "url1|description1,url2|description2"
46
+ // Example: "https://api.prod.com|Production,https://api.staging.com|Staging"
47
+ swaggerServers: config.string('SWAGGER_SERVERS', ''),
48
+
49
+ // Swagger UI options
50
+ swaggerPersistAuthorization: config.boolean('SWAGGER_PERSIST_AUTH', true),
51
+ swaggerDisplayRequestDuration: config.boolean('SWAGGER_DISPLAY_DURATION', true),
52
+ swaggerEnableFilter: config.boolean('SWAGGER_ENABLE_FILTER', true),
53
+ swaggerShowExtensions: config.boolean('SWAGGER_SHOW_EXTENSIONS', true),
54
+ swaggerTryItOutEnabled: config.boolean('SWAGGER_TRY_IT_OUT', true),
55
+
56
+ // Swagger authentication (Basic Auth)
57
+ swaggerAuthEnabled: config.boolean('SWAGGER_AUTH_ENABLED', false),
58
+ swaggerAuthUsername: config.string('SWAGGER_AUTH_USERNAME', 'admin'),
59
+ swaggerAuthPassword: config.string('SWAGGER_AUTH_PASSWORD', ''),
60
+
40
61
  // Static files plugin
41
62
  staticFilesEnabled: config.boolean('STATIC_FILES_ENABLED', true),
42
63
  staticPublicDir: config.string('STATIC_PUBLIC_DIR', 'public'),
@@ -79,34 +79,18 @@ export class Bundler {
79
79
  let liveComponentsGenerator: any = null
80
80
 
81
81
  try {
82
- // ๐Ÿš€ PRE-BUILD: Auto-generate Live Components registration
83
- const generatorModule = await import('./live-components-generator')
84
- liveComponentsGenerator = generatorModule.liveComponentsGenerator
85
- const discoveredComponents = await liveComponentsGenerator.preBuild()
86
-
87
- // ๐Ÿ”Œ PRE-BUILD: Auto-generate FluxStack Plugins registration
88
- const pluginsGeneratorModule = await import('./flux-plugins-generator')
89
- const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
90
- const discoveredPlugins = await fluxPluginsGenerator.preBuild()
91
-
82
+ // Run pre-build steps
83
+ liveComponentsGenerator = await this.runPreBuildSteps()
84
+
92
85
  // Ensure output directory exists
93
- if (!existsSync(this.config.outDir)) {
94
- mkdirSync(this.config.outDir, { recursive: true })
95
- }
86
+ this.ensureOutputDirectory()
96
87
 
97
- const external = [
98
- "@tailwindcss/vite",
99
- "tailwindcss",
100
- "lightningcss",
101
- "vite",
102
- "@vitejs/plugin-react",
103
- ...(this.config.external || []),
104
- ...(options.external || [])
105
- ]
88
+ // Get external dependencies
89
+ const external = this.getExternalDependencies(options)
106
90
 
107
91
  const buildArgs = [
108
- "bun", "build",
109
- entryPoint,
92
+ "bun", "build",
93
+ entryPoint,
110
94
  "--outdir", this.config.outDir,
111
95
  "--target", this.config.target,
112
96
  ...external.flatMap(ext => ["--external", ext])
@@ -132,20 +116,12 @@ export class Bundler {
132
116
  const exitCode = await buildProcess.exited
133
117
  const duration = Date.now() - startTime
134
118
 
135
- // ๐Ÿงน POST-BUILD: Handle auto-generated registration file
136
- // (liveComponentsGenerator already available from above)
137
-
138
119
  if (exitCode === 0) {
139
120
  buildLogger.success(`Server bundle completed in ${buildLogger.formatDuration(duration)}`)
140
-
141
- // Keep generated files for production (they're now baked into bundle)
142
- await liveComponentsGenerator.postBuild(false)
143
-
144
- // Cleanup plugins registry
145
- const pluginsGeneratorModule = await import('./flux-plugins-generator')
146
- const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
147
- await fluxPluginsGenerator.postBuild(false)
148
-
121
+
122
+ // Run post-build cleanup
123
+ await this.runPostBuildCleanup(liveComponentsGenerator)
124
+
149
125
  return {
150
126
  success: true,
151
127
  duration,
@@ -154,15 +130,10 @@ export class Bundler {
154
130
  }
155
131
  } else {
156
132
  buildLogger.error("Server bundle failed")
157
-
158
- // Restore original files since build failed
159
- await liveComponentsGenerator.postBuild(false)
160
-
161
- // Restore plugins registry
162
- const pluginsGeneratorModule = await import('./flux-plugins-generator')
163
- const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
164
- await fluxPluginsGenerator.postBuild(false)
165
-
133
+
134
+ // Run post-build cleanup
135
+ await this.runPostBuildCleanup(liveComponentsGenerator)
136
+
166
137
  const stderr = await new Response(buildProcess.stderr).text()
167
138
  return {
168
139
  success: false,
@@ -172,21 +143,139 @@ export class Bundler {
172
143
  }
173
144
  } catch (error) {
174
145
  const duration = Date.now() - startTime
175
-
146
+
147
+ // ๐Ÿงน CLEANUP: Restore original files on error
148
+ try {
149
+ await this.runPostBuildCleanup(liveComponentsGenerator)
150
+ } catch (cleanupError) {
151
+ buildLogger.warn(`Failed to cleanup generated files: ${cleanupError}`)
152
+ }
153
+
154
+ return {
155
+ success: false,
156
+ duration,
157
+ error: error instanceof Error ? error.message : "Unknown error"
158
+ }
159
+ }
160
+ }
161
+
162
+ async compileToExecutable(entryPoint: string, outputName: string = "app", options: BundleOptions = {}): Promise<BundleResult> {
163
+ buildLogger.section('Executable Build', '๐Ÿ“ฆ')
164
+
165
+ const startTime = Date.now()
166
+ let liveComponentsGenerator: any = null
167
+
168
+ try {
169
+ // Run pre-build steps
170
+ liveComponentsGenerator = await this.runPreBuildSteps()
171
+
172
+ // Ensure output directory exists
173
+ this.ensureOutputDirectory()
174
+
175
+ const outputPath = join(this.config.outDir, outputName)
176
+
177
+ // Get external dependencies
178
+ const external = this.getExternalDependencies(options)
179
+
180
+ const buildArgs = [
181
+ "bun", "build",
182
+ entryPoint,
183
+ "--compile",
184
+ "--outfile", outputPath,
185
+ "--target", this.config.target,
186
+ ...external.flatMap(ext => ["--external", ext])
187
+ ]
188
+
189
+ if (this.config.sourceMaps) {
190
+ buildArgs.push("--sourcemap")
191
+ }
192
+
193
+ if (this.config.minify) {
194
+ buildArgs.push("--minify")
195
+ }
196
+
197
+ // Add Windows-specific options if provided
198
+ if (options.executable?.windows) {
199
+ const winOpts = options.executable.windows
200
+ if (winOpts.hideConsole) {
201
+ buildArgs.push("--windows-hide-console")
202
+ }
203
+ if (winOpts.icon) {
204
+ buildArgs.push("--windows-icon", winOpts.icon)
205
+ }
206
+ if (winOpts.title) {
207
+ buildArgs.push("--windows-title", winOpts.title)
208
+ }
209
+ if (winOpts.publisher) {
210
+ buildArgs.push("--windows-publisher", winOpts.publisher)
211
+ }
212
+ if (winOpts.version) {
213
+ buildArgs.push("--windows-version", winOpts.version)
214
+ }
215
+ if (winOpts.description) {
216
+ buildArgs.push("--windows-description", winOpts.description)
217
+ }
218
+ if (winOpts.copyright) {
219
+ buildArgs.push("--windows-copyright", winOpts.copyright)
220
+ }
221
+ }
222
+
223
+ // Add custom build arguments if provided
224
+ if (options.executable?.customArgs) {
225
+ buildArgs.push(...options.executable.customArgs)
226
+ }
227
+
228
+ buildLogger.step(`Compiling ${entryPoint} to ${outputPath}...`)
229
+
230
+ const buildProcess = spawn({
231
+ cmd: buildArgs,
232
+ stdout: "pipe",
233
+ stderr: "pipe",
234
+ env: {
235
+ ...process.env,
236
+ NODE_ENV: 'production',
237
+ ...options.env
238
+ }
239
+ })
240
+
241
+ const exitCode = await buildProcess.exited
242
+ const duration = Date.now() - startTime
243
+
244
+ if (exitCode === 0) {
245
+ buildLogger.success(`Executable compiled in ${buildLogger.formatDuration(duration)}`)
246
+
247
+ // Run post-build cleanup
248
+ await this.runPostBuildCleanup(liveComponentsGenerator)
249
+
250
+ return {
251
+ success: true,
252
+ duration,
253
+ outputPath,
254
+ entryPoint: outputPath
255
+ }
256
+ } else {
257
+ buildLogger.error("Executable compilation failed")
258
+
259
+ // Run post-build cleanup
260
+ await this.runPostBuildCleanup(liveComponentsGenerator)
261
+
262
+ const stderr = await new Response(buildProcess.stderr).text()
263
+ return {
264
+ success: false,
265
+ duration,
266
+ error: stderr || "Executable compilation failed"
267
+ }
268
+ }
269
+ } catch (error) {
270
+ const duration = Date.now() - startTime
271
+
176
272
  // ๐Ÿงน CLEANUP: Restore original files on error
177
273
  try {
178
- if (liveComponentsGenerator) {
179
- await liveComponentsGenerator.postBuild(false)
180
- }
181
-
182
- // Cleanup plugins registry
183
- const pluginsGeneratorModule = await import('./flux-plugins-generator')
184
- const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
185
- await fluxPluginsGenerator.postBuild(false)
274
+ await this.runPostBuildCleanup(liveComponentsGenerator)
186
275
  } catch (cleanupError) {
187
276
  buildLogger.warn(`Failed to cleanup generated files: ${cleanupError}`)
188
277
  }
189
-
278
+
190
279
  return {
191
280
  success: false,
192
281
  duration,
@@ -195,6 +284,61 @@ export class Bundler {
195
284
  }
196
285
  }
197
286
 
287
+ /**
288
+ * Get list of external dependencies that should not be bundled
289
+ */
290
+ private getExternalDependencies(options: BundleOptions = {}): string[] {
291
+ return [
292
+ "@tailwindcss/vite",
293
+ "tailwindcss",
294
+ "lightningcss",
295
+ "vite",
296
+ "@vitejs/plugin-react",
297
+ "rollup",
298
+ ...(this.config.external || []),
299
+ ...(options.external || [])
300
+ ]
301
+ }
302
+
303
+ /**
304
+ * Ensure output directory exists
305
+ */
306
+ private ensureOutputDirectory(): void {
307
+ if (!existsSync(this.config.outDir)) {
308
+ mkdirSync(this.config.outDir, { recursive: true })
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Run pre-build steps (Live Components and Plugins generation)
314
+ */
315
+ private async runPreBuildSteps(): Promise<any> {
316
+ // ๐Ÿš€ PRE-BUILD: Auto-generate Live Components registration
317
+ const generatorModule = await import('./live-components-generator')
318
+ const liveComponentsGenerator = generatorModule.liveComponentsGenerator
319
+ await liveComponentsGenerator.preBuild()
320
+
321
+ // ๐Ÿ”Œ PRE-BUILD: Auto-generate FluxStack Plugins registration
322
+ const pluginsGeneratorModule = await import('./flux-plugins-generator')
323
+ const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
324
+ await fluxPluginsGenerator.preBuild()
325
+
326
+ return liveComponentsGenerator
327
+ }
328
+
329
+ /**
330
+ * Run post-build cleanup
331
+ */
332
+ private async runPostBuildCleanup(liveComponentsGenerator: any): Promise<void> {
333
+ if (liveComponentsGenerator) {
334
+ await liveComponentsGenerator.postBuild(false)
335
+ }
336
+
337
+ const pluginsGeneratorModule = await import('./flux-plugins-generator')
338
+ const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
339
+ await fluxPluginsGenerator.postBuild(false)
340
+ }
341
+
198
342
  private async getClientAssets(): Promise<string[]> {
199
343
  // This would analyze the build output to get asset information
200
344
  // For now, return empty array - can be enhanced later
@@ -95,12 +95,12 @@ export class FluxPluginsGenerator {
95
95
  private findPluginEntryFile(pluginDir: string): string | null {
96
96
  const possibleFiles = [
97
97
  'index.ts',
98
- 'index.js',
98
+ 'index',
99
99
  'plugin.ts',
100
- 'plugin.js',
100
+ 'plugin',
101
101
  'src/index.ts',
102
- 'src/index.js',
103
- 'dist/index.js'
102
+ 'src/index',
103
+ 'dist/index'
104
104
  ]
105
105
 
106
106
  for (const file of possibleFiles) {
@@ -130,7 +130,7 @@ export class FluxPluginsGenerator {
130
130
  const imports = plugins
131
131
  .map(plugin => {
132
132
  const importName = this.getImportName(plugin.pluginName)
133
- const importPath = plugin.entryFile === 'index.ts' || plugin.entryFile === 'index.js'
133
+ const importPath = plugin.entryFile === 'index.ts' || plugin.entryFile === 'index'
134
134
  ? plugin.relativePath
135
135
  : `${plugin.relativePath}/${plugin.entryFile.replace(/\.(ts|js)$/, '')}`
136
136
  return `import ${importName} from "${importPath}"`
@@ -47,6 +47,10 @@ export class FluxStackBuilder {
47
47
  return await this.bundler.bundleServer("app/server/index.ts")
48
48
  }
49
49
 
50
+ async buildExecutable(outputName: string = "CLauncher", options?: import("../types/build").BundleOptions) {
51
+ return await this.bundler.compileToExecutable("app/server/index.ts", outputName, options)
52
+ }
53
+
50
54
  async createDockerFiles() {
51
55
  buildLogger.section('Docker Configuration', '๐Ÿณ')
52
56