create-fluxstack 1.9.1 → 1.10.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 (49) hide show
  1. package/LIVE_COMPONENTS_REVIEW.md +781 -0
  2. package/app/client/src/App.tsx +39 -43
  3. package/app/client/src/lib/eden-api.ts +2 -7
  4. package/app/client/src/live/FileUploadExample.tsx +359 -0
  5. package/app/client/src/live/MinimalLiveClock.tsx +47 -0
  6. package/app/client/src/live/QuickUploadTest.tsx +193 -0
  7. package/app/client/src/main.tsx +10 -10
  8. package/app/client/src/vite-env.d.ts +1 -1
  9. package/app/client/tsconfig.app.json +45 -44
  10. package/app/client/tsconfig.node.json +25 -25
  11. package/app/server/index.ts +30 -103
  12. package/app/server/live/LiveFileUploadComponent.ts +77 -0
  13. package/app/server/live/register-components.ts +19 -19
  14. package/core/build/bundler.ts +4 -1
  15. package/core/build/index.ts +124 -4
  16. package/core/build/live-components-generator.ts +68 -1
  17. package/core/cli/index.ts +163 -35
  18. package/core/client/LiveComponentsProvider.tsx +3 -9
  19. package/core/client/hooks/AdaptiveChunkSizer.ts +215 -0
  20. package/core/client/hooks/useChunkedUpload.ts +112 -61
  21. package/core/client/hooks/useHybridLiveComponent.ts +80 -26
  22. package/core/client/hooks/useTypedLiveComponent.ts +133 -0
  23. package/core/client/hooks/useWebSocket.ts +4 -16
  24. package/core/client/index.ts +20 -2
  25. package/core/framework/server.ts +181 -8
  26. package/core/live/ComponentRegistry.ts +5 -1
  27. package/core/plugins/built-in/index.ts +8 -5
  28. package/core/plugins/built-in/live-components/commands/create-live-component.ts +55 -63
  29. package/core/plugins/built-in/vite/index.ts +75 -187
  30. package/core/plugins/built-in/vite/vite-dev.ts +88 -0
  31. package/core/plugins/registry.ts +54 -2
  32. package/core/plugins/types.ts +86 -2
  33. package/core/server/index.ts +1 -2
  34. package/core/server/live/ComponentRegistry.ts +14 -5
  35. package/core/server/live/FileUploadManager.ts +22 -25
  36. package/core/server/live/auto-generated-components.ts +29 -26
  37. package/core/server/live/websocket-plugin.ts +19 -5
  38. package/core/server/plugins/static-files-plugin.ts +49 -240
  39. package/core/server/plugins/swagger.ts +33 -33
  40. package/core/types/build.ts +1 -0
  41. package/core/types/plugin.ts +9 -1
  42. package/core/types/types.ts +137 -0
  43. package/core/utils/logger/startup-banner.ts +20 -4
  44. package/core/utils/version.ts +1 -1
  45. package/eslint.config.js +23 -23
  46. package/package.json +3 -3
  47. package/plugins/crypto-auth/server/middlewares.ts +19 -19
  48. package/tsconfig.json +52 -52
  49. package/workspace.json +5 -5
@@ -1,22 +1,25 @@
1
1
  import type { FluxStack, PluginContext, RequestContext } from "@/core/plugins/types"
2
- import { createServer, type ViteDevServer } from 'vite'
3
2
  import { FLUXSTACK_VERSION } from "@/core/utils/version"
4
3
  import { clientConfig } from '@/config/client.config'
4
+ import { isDevelopment } from "@/core/utils/helpers"
5
+ import { join } from "path"
6
+ import { statSync, existsSync } from "fs"
5
7
 
6
8
  type Plugin = FluxStack.Plugin
7
9
 
8
- let viteServer: ViteDevServer | null = null
9
-
10
10
  // Default configuration values (uses clientConfig from /config)
11
11
  const DEFAULTS = {
12
12
  enabled: true,
13
13
  port: clientConfig.vite.port,
14
- host: "localhost",
14
+ host: clientConfig.vite.host,
15
15
  checkInterval: 2000,
16
16
  maxRetries: 10,
17
17
  timeout: 5000,
18
18
  proxyPaths: [] as string[],
19
- excludePaths: [] as string[]
19
+ excludePaths: [] as string[],
20
+ // Static file serving (production) - uses clientConfig
21
+ publicDir: clientConfig.build.outDir,
22
+ indexFile: "index.html"
20
23
  }
21
24
 
22
25
  /**
@@ -50,90 +53,94 @@ export const vitePlugin: Plugin = {
50
53
  return
51
54
  }
52
55
 
53
- const vitePort = DEFAULTS.port || clientConfig.vite.port || 5173
54
- const viteHost = DEFAULTS.host || "localhost"
56
+ // Production mode: setup static file serving
57
+ if (!isDevelopment()) {
58
+ context.logger.debug("Production mode: static file serving enabled", {
59
+ publicDir: DEFAULTS.publicDir
60
+ })
55
61
 
56
- // Import group logger utilities
57
- const { startGroup, endGroup, logInGroup } = await import('@/core/utils/logger/group-logger')
62
+ // Static fallback handler (runs last)
63
+ const staticFallback = (c: any) => {
64
+ const req = c.request
65
+ if (!req) return
58
66
 
59
- try {
60
- // Start Vite dev server programmatically (silently)
61
- viteServer = await createServer({
62
- configFile: './vite.config.ts',
63
- // Don't override root - let vite.config.ts handle it
64
- server: {
65
- port: vitePort,
66
- host: viteHost,
67
- strictPort: true
68
- },
69
- logLevel: 'silent' // Suppress all Vite logs
70
- })
67
+ const url = new URL(req.url)
68
+ let pathname = decodeURIComponent(url.pathname)
71
69
 
72
- await viteServer.listen()
70
+ // Determine base directory using path discovery
71
+ let baseDir: string
73
72
 
74
- context.logger.debug(`Vite server started on ${viteHost}:${vitePort} (internal proxy)`)
75
- context.logger.debug('Hot reload coordination active')
73
+ // Production: try paths in order of preference
74
+ if (existsSync('client')) {
75
+ // Found client/ in current directory (running from dist/)
76
+ baseDir = 'client'
77
+ } else if (existsSync('dist/client')) {
78
+ // Found dist/client/ (running from project root)
79
+ baseDir = 'dist/client'
80
+ } else {
81
+ // Fallback to configured path
82
+ baseDir = DEFAULTS.publicDir
83
+ }
76
84
 
77
- // Store Vite config in context for later use
78
- ; (context as any).viteConfig = {
79
- port: vitePort,
80
- host: viteHost,
81
- server: viteServer
85
+ // Root or empty path index.html
86
+ if (pathname === '/' || pathname === '') {
87
+ pathname = `/${DEFAULTS.indexFile}`
82
88
  }
83
89
 
84
- // Setup cleanup on process exit
85
- const cleanup = async () => {
86
- if (viteServer) {
87
- context.logger.debug('🛑 Stopping Vite server...')
88
- await viteServer.close()
89
- viteServer = null
90
+ const filePath = join(baseDir, pathname)
91
+
92
+ try {
93
+ const info = statSync(filePath)
94
+
95
+ // File exists → serve it
96
+ if (info.isFile()) {
97
+ return Bun.file(filePath)
98
+ }
99
+ } catch (_) {
100
+ // File not found → continue
90
101
  }
91
- }
92
102
 
93
- process.on('SIGINT', cleanup)
94
- process.on('SIGTERM', cleanup)
95
- process.on('exit', cleanup)
96
-
97
- } catch (error) {
98
- // Check if error is related to port already in use
99
- const errorMessage = error instanceof Error ? error.message : String(error)
100
- const isPortInUse = errorMessage.includes('EADDRINUSE') ||
101
- errorMessage.includes('address already in use') ||
102
- errorMessage.includes('Port') && errorMessage.includes('is in use')
103
-
104
- if (isPortInUse) {
105
- endGroup()
106
- console.log('') // Separator line
107
- context.logger.error(`❌ Failed to start Vite: Port ${vitePort} is already in use`)
108
- context.logger.info(`💡 Try one of these solutions:`)
109
- context.logger.info(` 1. Stop the process using port ${vitePort}`)
110
- context.logger.info(` 2. Change VITE_PORT in your .env file`)
111
- context.logger.info(` 3. Kill the process: ${process.platform === 'win32' ? `netstat -ano | findstr :${vitePort}` : `lsof -ti:${vitePort} | xargs kill -9`}`)
112
- process.exit(1)
113
- } else {
114
- context.logger.error('❌ Failed to start Vite server:', errorMessage)
115
- context.logger.debug('Full error:', error)
116
- context.logger.debug('⚠️ Falling back to monitoring mode...')
117
-
118
- // Fallback to monitoring if programmatic start fails
119
- ; (context as any).viteConfig = {
120
- port: vitePort,
121
- host: viteHost
103
+ // SPA fallback: serve index.html for non-file routes
104
+ const indexPath = join(baseDir, DEFAULTS.indexFile)
105
+ try {
106
+ statSync(indexPath) // Ensure index exists
107
+ return Bun.file(indexPath)
108
+ } catch (_) {
109
+ // Index not found let request continue (404)
122
110
  }
123
- monitorVite(context, viteHost, vitePort)
124
111
  }
112
+
113
+ // Register as catch-all fallback (runs after all other routes)
114
+ context.app.all('*', staticFallback)
115
+ return
125
116
  }
117
+
118
+ // Development mode: import and setup Vite dev server
119
+ const { setupViteDev } = await import('./vite-dev')
120
+ await setupViteDev(context)
126
121
  },
127
122
 
128
123
  onServerStart: async (context: PluginContext) => {
129
- const viteConfig = (context as any).viteConfig
124
+ if (!DEFAULTS.enabled) return
125
+
126
+ if (!isDevelopment()) {
127
+ context.logger.debug(`Static files ready`, {
128
+ publicDir: DEFAULTS.publicDir,
129
+ indexFile: DEFAULTS.indexFile
130
+ })
131
+ return
132
+ }
130
133
 
131
- if (DEFAULTS.enabled && viteConfig) {
134
+ const viteConfig = (context as any).viteConfig
135
+ if (viteConfig) {
132
136
  context.logger.debug(`Vite integration active - monitoring ${viteConfig.host}:${viteConfig.port}`)
133
137
  }
134
138
  },
135
139
 
136
140
  onBeforeRoute: async (requestContext: RequestContext) => {
141
+ // Production mode: static serving handled by catch-all route in setup
142
+ if (!isDevelopment()) return
143
+
137
144
  // Skip API routes and swagger - let them be handled by backend
138
145
  if (requestContext.path.startsWith("/api") || requestContext.path.startsWith("/swagger")) {
139
146
  return
@@ -175,7 +182,6 @@ export const vitePlugin: Plugin = {
175
182
  } catch (viteError) {
176
183
  // If Vite fails, let the request continue to normal routing (will become 404)
177
184
  // Only log if explicitly enabled for debugging
178
- const { clientConfig } = await import('@/config/client.config')
179
185
  if (clientConfig.vite.enableLogging) {
180
186
  console.warn(`Vite proxy error: ${viteError}`)
181
187
  }
@@ -214,7 +220,6 @@ export const vitePlugin: Plugin = {
214
220
  } catch (viteError) {
215
221
  // If Vite fails, let the request continue to normal routing (will become 404)
216
222
  // Only log if explicitly enabled for debugging
217
- const { clientConfig } = await import('@/config/client.config')
218
223
  if (clientConfig.vite.enableLogging) {
219
224
  console.warn(`Vite proxy error: ${viteError}`)
220
225
  }
@@ -222,122 +227,5 @@ export const vitePlugin: Plugin = {
222
227
  }
223
228
  }
224
229
 
225
- // Monitor Vite server status with automatic port detection
226
- async function monitorVite(
227
- context: PluginContext,
228
- host: string,
229
- initialPort: number
230
- ) {
231
- let retries = 0
232
- let isConnected = false
233
- let actualPort = initialPort
234
- let portDetected = false
235
-
236
- const checkVite = async () => {
237
- try {
238
- // If we haven't found the correct port yet, try to detect it
239
- if (!portDetected) {
240
- const detectedPort = await detectVitePort(host, initialPort)
241
- if (detectedPort !== null) {
242
- actualPort = detectedPort
243
- portDetected = true
244
- // Update the context with the detected port
245
- if ((context as any).viteConfig) {
246
- ; (context as any).viteConfig.port = actualPort
247
- }
248
- }
249
- }
250
-
251
- const isRunning = await checkViteRunning(host, actualPort, DEFAULTS.timeout)
252
-
253
- if (isRunning && !isConnected) {
254
- isConnected = true
255
- retries = 0
256
- if (actualPort !== initialPort) {
257
- context.logger.debug(`✓ Vite server detected on ${host}:${actualPort} (auto-detected from port ${initialPort})`)
258
- } else {
259
- context.logger.debug(`✓ Vite server detected on ${host}:${actualPort}`)
260
- }
261
- context.logger.debug("Hot reload coordination active")
262
- } else if (!isRunning && isConnected) {
263
- isConnected = false
264
- context.logger.warn(`✗ Vite server disconnected from ${host}:${actualPort}`)
265
- // Reset port detection when disconnected
266
- portDetected = false
267
- actualPort = initialPort
268
- } else if (!isRunning) {
269
- retries++
270
- if (retries <= DEFAULTS.maxRetries) {
271
- if (portDetected) {
272
- context.logger.debug(`Waiting for Vite server on ${host}:${actualPort}... (${retries}/${DEFAULTS.maxRetries})`)
273
- } else {
274
- context.logger.debug(`Detecting Vite server port... (${retries}/${DEFAULTS.maxRetries})`)
275
- }
276
- } else if (retries === DEFAULTS.maxRetries + 1) {
277
- context.logger.warn(`Vite server not found after ${DEFAULTS.maxRetries} attempts. Development features may be limited.`)
278
- }
279
- }
280
- } catch (error) {
281
- if (isConnected) {
282
- context.logger.error('Error checking Vite server status', { error })
283
- }
284
- }
285
-
286
- // Continue monitoring
287
- setTimeout(checkVite, DEFAULTS.checkInterval)
288
- }
289
-
290
- // Start monitoring after a brief delay
291
- setTimeout(checkVite, 1000)
292
- }
293
-
294
- // Auto-detect Vite port by trying common ports
295
- async function detectVitePort(host: string, startPort: number): Promise<number | null> {
296
- // Try the initial port first, then common alternatives
297
- const portsToTry = [
298
- startPort,
299
- startPort + 1,
300
- startPort + 2,
301
- startPort + 3,
302
- 5174, // Common Vite alternative
303
- 5175,
304
- 5176,
305
- 3000, // Sometimes Vite might use this
306
- 4173 // Another common alternative
307
- ]
308
-
309
- for (const port of portsToTry) {
310
- try {
311
- const isRunning = await checkViteRunning(host, port, 1000)
312
- if (isRunning) {
313
- return port
314
- }
315
- } catch (error) {
316
- // Continue trying other ports
317
- }
318
- }
319
-
320
- return null
321
- }
322
-
323
- // Check if Vite is running
324
- async function checkViteRunning(host: string, port: number, timeout: number = 1000): Promise<boolean> {
325
- try {
326
- const controller = new AbortController()
327
- const timeoutId = setTimeout(() => controller.abort(), timeout)
328
-
329
- const response = await fetch(`http://${host}:${port}`, {
330
- signal: controller.signal,
331
- method: 'HEAD' // Use HEAD to minimize data transfer
332
- })
333
-
334
- clearTimeout(timeoutId)
335
- return response.status >= 200 && response.status < 500
336
- } catch (error) {
337
- return false
338
- }
339
- }
340
-
341
- // Note: Proxy logic is now handled directly in the onBeforeRoute hook above
342
230
 
343
231
  export default vitePlugin
@@ -0,0 +1,88 @@
1
+ import type { PluginContext } from "@/core/plugins/types"
2
+ import { clientConfig } from '@/config/client.config'
3
+
4
+ // Dynamic import type for vite
5
+ type ViteDevServer = Awaited<ReturnType<typeof import('vite')['createServer']>>
6
+
7
+ // Store vite server instance
8
+ let viteServer: ViteDevServer | null = null
9
+
10
+ // Default configuration values
11
+ const DEFAULTS = {
12
+ port: clientConfig.vite.port,
13
+ host: clientConfig.vite.host
14
+ }
15
+
16
+ /**
17
+ * Setup Vite development server
18
+ * This file is only imported in development mode
19
+ */
20
+ export async function setupViteDev(context: PluginContext): Promise<void> {
21
+ const vitePort = DEFAULTS.port || clientConfig.vite.port || 5173
22
+ const viteHost = DEFAULTS.host || "localhost"
23
+
24
+ // Import group logger utilities
25
+ const { endGroup } = await import('@/core/utils/logger/group-logger')
26
+
27
+ try {
28
+ // Dynamic import of vite
29
+ const { createServer } = await import('vite')
30
+
31
+ // Start Vite dev server programmatically (silently)
32
+ viteServer = await createServer({
33
+ configFile: './vite.config.ts',
34
+ server: {
35
+ port: vitePort,
36
+ host: viteHost,
37
+ strictPort: true
38
+ },
39
+ logLevel: 'silent'
40
+ })
41
+
42
+ await viteServer.listen()
43
+
44
+ context.logger.debug(`Vite server started on ${viteHost}:${vitePort} (internal proxy)`)
45
+ context.logger.debug('Hot reload coordination active')
46
+
47
+ // Store Vite config in context for later use
48
+ ;(context as any).viteConfig = {
49
+ port: vitePort,
50
+ host: viteHost,
51
+ server: viteServer
52
+ }
53
+
54
+ // Setup cleanup on process exit
55
+ const cleanup = async () => {
56
+ if (viteServer) {
57
+ context.logger.debug('🛑 Stopping Vite server...')
58
+ await viteServer.close()
59
+ viteServer = null
60
+ }
61
+ }
62
+
63
+ process.on('SIGINT', cleanup)
64
+ process.on('SIGTERM', cleanup)
65
+ process.on('exit', cleanup)
66
+
67
+ } catch (error) {
68
+ // Check if error is related to port already in use
69
+ const errorMessage = error instanceof Error ? error.message : String(error)
70
+ const isPortInUse = errorMessage.includes('EADDRINUSE') ||
71
+ errorMessage.includes('address already in use') ||
72
+ errorMessage.includes('Port') && errorMessage.includes('is in use')
73
+
74
+ if (isPortInUse) {
75
+ endGroup()
76
+ console.log('') // Separator line
77
+ context.logger.error(`❌ Failed to start Vite: Port ${vitePort} is already in use`)
78
+ context.logger.info(`💡 Try one of these solutions:`)
79
+ context.logger.info(` 1. Stop the process using port ${vitePort}`)
80
+ context.logger.info(` 2. Change VITE_PORT in your .env file`)
81
+ context.logger.info(` 3. Kill the process: ${process.platform === 'win32' ? `netstat -ano | findstr :${vitePort}` : `lsof -ti:${vitePort} | xargs kill -9`}`)
82
+ process.exit(1)
83
+ } else {
84
+ context.logger.error('❌ Failed to start Vite server:', errorMessage)
85
+ context.logger.debug('Full error:', error)
86
+ }
87
+ }
88
+ }
@@ -56,7 +56,7 @@ export class PluginRegistry {
56
56
  }
57
57
 
58
58
  this.plugins.set(plugin.name, plugin)
59
-
59
+
60
60
  if (manifest) {
61
61
  this.manifests.set(plugin.name, manifest)
62
62
  }
@@ -74,12 +74,58 @@ export class PluginRegistry {
74
74
  version: plugin.version,
75
75
  dependencies: plugin.dependencies
76
76
  })
77
+
78
+ // Execute onPluginRegister hooks on all registered plugins
79
+ await this.executePluginRegisterHooks(plugin)
80
+ }
81
+
82
+ /**
83
+ * Execute onPluginRegister hooks on all plugins
84
+ */
85
+ private async executePluginRegisterHooks(registeredPlugin: FluxStackPlugin): Promise<void> {
86
+ for (const plugin of this.plugins.values()) {
87
+ if (plugin.onPluginRegister && typeof plugin.onPluginRegister === 'function') {
88
+ try {
89
+ await plugin.onPluginRegister({
90
+ pluginName: registeredPlugin.name,
91
+ pluginVersion: registeredPlugin.version,
92
+ timestamp: Date.now(),
93
+ data: { plugin: registeredPlugin }
94
+ })
95
+ } catch (error) {
96
+ this.logger?.error(`Plugin '${plugin.name}' onPluginRegister hook failed`, {
97
+ error: error instanceof Error ? error.message : String(error)
98
+ })
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Execute onPluginUnregister hooks on all plugins
106
+ */
107
+ private async executePluginUnregisterHooks(unregisteredPluginName: string, version?: string): Promise<void> {
108
+ for (const plugin of this.plugins.values()) {
109
+ if (plugin.onPluginUnregister && typeof plugin.onPluginUnregister === 'function') {
110
+ try {
111
+ await plugin.onPluginUnregister({
112
+ pluginName: unregisteredPluginName,
113
+ pluginVersion: version,
114
+ timestamp: Date.now()
115
+ })
116
+ } catch (error) {
117
+ this.logger?.error(`Plugin '${plugin.name}' onPluginUnregister hook failed`, {
118
+ error: error instanceof Error ? error.message : String(error)
119
+ })
120
+ }
121
+ }
122
+ }
77
123
  }
78
124
 
79
125
  /**
80
126
  * Unregister a plugin from the registry
81
127
  */
82
- unregister(name: string): void {
128
+ async unregister(name: string): Promise<void> {
83
129
  if (!this.plugins.has(name)) {
84
130
  throw new FluxStackError(
85
131
  `Plugin '${name}' is not registered`,
@@ -98,12 +144,18 @@ export class PluginRegistry {
98
144
  )
99
145
  }
100
146
 
147
+ const plugin = this.plugins.get(name)
148
+ const version = plugin?.version
149
+
101
150
  this.plugins.delete(name)
102
151
  this.manifests.delete(name)
103
152
  this.dependencies.delete(name)
104
153
  this.loadOrder = this.loadOrder.filter(pluginName => pluginName !== name)
105
154
 
106
155
  this.logger?.debug(`Plugin '${name}' unregistered successfully`)
156
+
157
+ // Execute onPluginUnregister hooks on all remaining plugins
158
+ await this.executePluginUnregisterHooks(name, version)
107
159
  }
108
160
 
109
161
  /**
@@ -1,16 +1,35 @@
1
1
  import type { FluxStackConfig } from "@/core/config/schema"
2
2
  import type { Logger } from "@/core/utils/logger/index"
3
3
 
4
- export type PluginHook =
4
+ export type PluginHook =
5
+ // Lifecycle hooks
5
6
  | 'setup'
7
+ | 'onConfigLoad'
8
+ | 'onBeforeServerStart'
6
9
  | 'onServerStart'
10
+ | 'onAfterServerStart'
11
+ | 'onBeforeServerStop'
7
12
  | 'onServerStop'
13
+ // Request/Response pipeline hooks
8
14
  | 'onRequest'
9
15
  | 'onBeforeRoute'
16
+ | 'onAfterRoute'
17
+ | 'onBeforeResponse'
10
18
  | 'onResponse'
19
+ | 'onRequestValidation'
20
+ | 'onResponseTransform'
21
+ // Error handling hooks
11
22
  | 'onError'
23
+ // Build pipeline hooks
24
+ | 'onBeforeBuild'
12
25
  | 'onBuild'
26
+ | 'onBuildAsset'
13
27
  | 'onBuildComplete'
28
+ | 'onBuildError'
29
+ // Plugin system hooks
30
+ | 'onPluginRegister'
31
+ | 'onPluginUnregister'
32
+ | 'onPluginError'
14
33
 
15
34
  export type PluginPriority = 'highest' | 'high' | 'normal' | 'low' | 'lowest' | number
16
35
 
@@ -68,6 +87,49 @@ export interface BuildContext {
68
87
  config: FluxStackConfig
69
88
  }
70
89
 
90
+ export interface ConfigLoadContext {
91
+ config: FluxStackConfig
92
+ envVars: Record<string, string | undefined>
93
+ configPath?: string
94
+ }
95
+
96
+ export interface RouteContext extends RequestContext {
97
+ route?: string
98
+ handler?: Function
99
+ params: Record<string, string>
100
+ }
101
+
102
+ export interface ValidationContext extends RequestContext {
103
+ errors: Array<{ field: string; message: string; code: string }>
104
+ isValid: boolean
105
+ }
106
+
107
+ export interface TransformContext extends ResponseContext {
108
+ transformed: boolean
109
+ originalResponse?: Response
110
+ }
111
+
112
+ export interface BuildAssetContext {
113
+ assetPath: string
114
+ assetType: 'js' | 'css' | 'html' | 'image' | 'font' | 'other'
115
+ size: number
116
+ content?: string | Buffer
117
+ }
118
+
119
+ export interface BuildErrorContext {
120
+ error: Error
121
+ file?: string
122
+ line?: number
123
+ column?: number
124
+ }
125
+
126
+ export interface PluginEventContext {
127
+ pluginName: string
128
+ pluginVersion?: string
129
+ timestamp: number
130
+ data?: any
131
+ }
132
+
71
133
  export interface PluginConfigSchema {
72
134
  type: 'object'
73
135
  properties: Record<string, any>
@@ -85,17 +147,39 @@ export namespace FluxStack {
85
147
  priority?: number | PluginPriority
86
148
  category?: string
87
149
  tags?: string[]
88
-
150
+
89
151
  // Lifecycle hooks
90
152
  setup?: (context: PluginContext) => void | Promise<void>
153
+ onConfigLoad?: (context: ConfigLoadContext) => void | Promise<void>
154
+ onBeforeServerStart?: (context: PluginContext) => void | Promise<void>
91
155
  onServerStart?: (context: PluginContext) => void | Promise<void>
156
+ onAfterServerStart?: (context: PluginContext) => void | Promise<void>
157
+ onBeforeServerStop?: (context: PluginContext) => void | Promise<void>
92
158
  onServerStop?: (context: PluginContext) => void | Promise<void>
159
+
160
+ // Request/Response pipeline hooks
93
161
  onRequest?: (context: RequestContext) => void | Promise<void>
94
162
  onBeforeRoute?: (context: RequestContext) => void | Promise<void>
163
+ onAfterRoute?: (context: RouteContext) => void | Promise<void>
164
+ onBeforeResponse?: (context: ResponseContext) => void | Promise<void>
95
165
  onResponse?: (context: ResponseContext) => void | Promise<void>
166
+ onRequestValidation?: (context: ValidationContext) => void | Promise<void>
167
+ onResponseTransform?: (context: TransformContext) => void | Promise<void>
168
+
169
+ // Error handling hooks
96
170
  onError?: (context: ErrorContext) => void | Promise<void>
171
+
172
+ // Build pipeline hooks
173
+ onBeforeBuild?: (context: BuildContext) => void | Promise<void>
97
174
  onBuild?: (context: BuildContext) => void | Promise<void>
175
+ onBuildAsset?: (context: BuildAssetContext) => void | Promise<void>
98
176
  onBuildComplete?: (context: BuildContext) => void | Promise<void>
177
+ onBuildError?: (context: BuildErrorContext) => void | Promise<void>
178
+
179
+ // Plugin system hooks
180
+ onPluginRegister?: (context: PluginEventContext) => void | Promise<void>
181
+ onPluginUnregister?: (context: PluginEventContext) => void | Promise<void>
182
+ onPluginError?: (context: PluginEventContext & { error: Error }) => void | Promise<void>
99
183
 
100
184
  // Configuration
101
185
  /**
@@ -1,7 +1,6 @@
1
1
  // FluxStack framework exports
2
2
  export { FluxStackFramework } from "../framework/server"
3
- export { vitePlugin } from "../plugins/built-in/vite"
4
- export { staticPlugin } from "../plugins/built-in/static"
3
+ export { vitePlugin, staticPlugin } from "../plugins/built-in"
5
4
  export { swaggerPlugin } from "../plugins/built-in/swagger"
6
5
  export { PluginRegistry } from "../plugins/registry"
7
6
  export * from "../types"
@@ -154,7 +154,11 @@ export class ComponentRegistry {
154
154
  const { startGroup, endGroup, logInGroup, groupSummary } = await import('@/core/utils/logger/group-logger')
155
155
 
156
156
  if (!fs.existsSync(componentsPath)) {
157
- console.log(`⚠️ Components path not found: ${componentsPath}`)
157
+ // In production, components are already bundled - no need to auto-discover
158
+ const { appConfig } = await import('@/config/app.config')
159
+ if (appConfig.env !== 'production') {
160
+ console.log(`⚠️ Components path not found: ${componentsPath}`)
161
+ }
158
162
  return
159
163
  }
160
164
 
@@ -186,10 +190,8 @@ export class ComponentRegistry {
186
190
  }
187
191
  }
188
192
 
189
- // Display components in a compact single line format
190
- if (components.length > 0) {
191
- console.log(`\nLive Components (${components.length}): ${components.join(', ')}\n`)
192
- }
193
+ // Components are now displayed in the startup banner
194
+ // No need to log here to avoid duplication
193
195
  } catch (error) {
194
196
  console.error('❌ Auto-discovery failed:', error)
195
197
  }
@@ -729,6 +731,13 @@ export class ComponentRegistry {
729
731
  }
730
732
  }
731
733
 
734
+ // Get registered component names
735
+ getRegisteredComponentNames(): string[] {
736
+ const definitionNames = Array.from(this.definitions.keys())
737
+ const autoDiscoveredNames = Array.from(this.autoDiscoveredComponents.keys())
738
+ return [...new Set([...definitionNames, ...autoDiscoveredNames])]
739
+ }
740
+
732
741
  // Get component by ID
733
742
  getComponent(componentId: string): LiveComponent | undefined {
734
743
  return this.components.get(componentId)