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.
- package/LIVE_COMPONENTS_REVIEW.md +781 -0
- package/app/client/src/App.tsx +39 -43
- package/app/client/src/lib/eden-api.ts +2 -7
- package/app/client/src/live/FileUploadExample.tsx +359 -0
- package/app/client/src/live/MinimalLiveClock.tsx +47 -0
- package/app/client/src/live/QuickUploadTest.tsx +193 -0
- package/app/client/src/main.tsx +10 -10
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/client/tsconfig.app.json +45 -44
- package/app/client/tsconfig.node.json +25 -25
- package/app/server/index.ts +30 -103
- package/app/server/live/LiveFileUploadComponent.ts +77 -0
- package/app/server/live/register-components.ts +19 -19
- package/core/build/bundler.ts +4 -1
- package/core/build/index.ts +124 -4
- package/core/build/live-components-generator.ts +68 -1
- package/core/cli/index.ts +163 -35
- package/core/client/LiveComponentsProvider.tsx +3 -9
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -0
- package/core/client/hooks/useChunkedUpload.ts +112 -61
- package/core/client/hooks/useHybridLiveComponent.ts +80 -26
- package/core/client/hooks/useTypedLiveComponent.ts +133 -0
- package/core/client/hooks/useWebSocket.ts +4 -16
- package/core/client/index.ts +20 -2
- package/core/framework/server.ts +181 -8
- package/core/live/ComponentRegistry.ts +5 -1
- package/core/plugins/built-in/index.ts +8 -5
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +55 -63
- package/core/plugins/built-in/vite/index.ts +75 -187
- package/core/plugins/built-in/vite/vite-dev.ts +88 -0
- package/core/plugins/registry.ts +54 -2
- package/core/plugins/types.ts +86 -2
- package/core/server/index.ts +1 -2
- package/core/server/live/ComponentRegistry.ts +14 -5
- package/core/server/live/FileUploadManager.ts +22 -25
- package/core/server/live/auto-generated-components.ts +29 -26
- package/core/server/live/websocket-plugin.ts +19 -5
- package/core/server/plugins/static-files-plugin.ts +49 -240
- package/core/server/plugins/swagger.ts +33 -33
- package/core/types/build.ts +1 -0
- package/core/types/plugin.ts +9 -1
- package/core/types/types.ts +137 -0
- package/core/utils/logger/startup-banner.ts +20 -4
- package/core/utils/version.ts +1 -1
- package/eslint.config.js +23 -23
- package/package.json +3 -3
- package/plugins/crypto-auth/server/middlewares.ts +19 -19
- package/tsconfig.json +52 -52
- 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:
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
62
|
+
// Static fallback handler (runs last)
|
|
63
|
+
const staticFallback = (c: any) => {
|
|
64
|
+
const req = c.request
|
|
65
|
+
if (!req) return
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
70
|
+
// Determine base directory using path discovery
|
|
71
|
+
let baseDir: string
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/core/plugins/registry.ts
CHANGED
|
@@ -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
|
/**
|
package/core/plugins/types.ts
CHANGED
|
@@ -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
|
/**
|
package/core/server/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// FluxStack framework exports
|
|
2
2
|
export { FluxStackFramework } from "../framework/server"
|
|
3
|
-
export { vitePlugin } from "../plugins/built-in
|
|
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
|
-
|
|
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
|
-
//
|
|
190
|
-
|
|
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)
|