create-fluxstack 1.1.0 → 1.4.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 (64) hide show
  1. package/app/server/backend-only.ts +5 -5
  2. package/app/server/index.ts +63 -54
  3. package/app/server/live/FluxStackConfig.ts +43 -39
  4. package/app/server/live/SystemMonitorIntegration.ts +2 -2
  5. package/app/server/live/register-components.ts +1 -1
  6. package/app/server/middleware/errorHandling.ts +6 -4
  7. package/app/server/routes/config.ts +145 -0
  8. package/app/server/routes/index.ts +5 -3
  9. package/config/app.config.ts +113 -0
  10. package/config/build.config.ts +24 -0
  11. package/config/database.config.ts +99 -0
  12. package/config/index.ts +68 -0
  13. package/config/logger.config.ts +27 -0
  14. package/config/runtime.config.ts +92 -0
  15. package/config/server.config.ts +46 -0
  16. package/config/services.config.ts +130 -0
  17. package/config/system.config.ts +105 -0
  18. package/core/build/index.ts +10 -4
  19. package/core/cli/generators/index.ts +5 -2
  20. package/core/cli/generators/plugin.ts +290 -0
  21. package/core/cli/index.ts +117 -15
  22. package/core/config/env.ts +37 -95
  23. package/core/config/runtime-config.ts +61 -58
  24. package/core/config/schema.ts +4 -0
  25. package/core/framework/server.ts +22 -10
  26. package/core/plugins/built-in/index.ts +7 -17
  27. package/core/plugins/built-in/swagger/index.ts +228 -228
  28. package/core/plugins/built-in/vite/index.ts +374 -358
  29. package/core/plugins/dependency-manager.ts +5 -5
  30. package/core/plugins/manager.ts +12 -12
  31. package/core/plugins/registry.ts +3 -3
  32. package/core/server/index.ts +0 -1
  33. package/core/server/live/ComponentRegistry.ts +34 -8
  34. package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
  35. package/core/server/live/websocket-plugin.ts +434 -434
  36. package/core/server/middleware/README.md +488 -0
  37. package/core/server/middleware/elysia-helpers.ts +227 -0
  38. package/core/server/middleware/index.ts +25 -9
  39. package/core/server/plugins/static-files-plugin.ts +231 -231
  40. package/core/utils/config-schema.ts +484 -0
  41. package/core/utils/env.ts +306 -0
  42. package/core/utils/helpers.ts +4 -4
  43. package/core/utils/logger/colors.ts +114 -0
  44. package/core/utils/logger/config.ts +35 -0
  45. package/core/utils/logger/formatter.ts +82 -0
  46. package/core/utils/logger/group-logger.ts +101 -0
  47. package/core/utils/logger/index.ts +199 -250
  48. package/core/utils/logger/stack-trace.ts +92 -0
  49. package/core/utils/logger/startup-banner.ts +92 -0
  50. package/core/utils/logger/winston-logger.ts +152 -0
  51. package/core/utils/version.ts +5 -0
  52. package/create-fluxstack.ts +118 -8
  53. package/fluxstack.config.ts +2 -2
  54. package/package.json +117 -115
  55. package/core/config/env-dynamic.ts +0 -326
  56. package/core/plugins/built-in/logger/index.ts +0 -180
  57. package/core/server/plugins/logger.ts +0 -47
  58. package/core/utils/env-runtime-v2.ts +0 -232
  59. package/core/utils/env-runtime.ts +0 -259
  60. package/core/utils/logger/formatters.ts +0 -222
  61. package/core/utils/logger/middleware.ts +0 -253
  62. package/core/utils/logger/performance.ts +0 -384
  63. package/core/utils/logger/transports.ts +0 -365
  64. package/core/utils/logger.ts +0 -106
@@ -1,280 +1,229 @@
1
1
  /**
2
- * FluxStack Logger
3
- * Enhanced logging system with multiple transports and formatters
2
+ * FluxStack Logger - Main Entry Point
3
+ * Unified logging system based on Winston with automatic module detection
4
4
  */
5
5
 
6
- import { ConsoleTransport, FileTransport, JSONTransport } from './transports'
7
- import { PrettyFormatter, JSONFormatter, SimpleFormatter } from './formatters'
8
- import type { LogTransport, LogEntry, LogLevel } from './transports'
9
- import type { LogFormatter } from './formatters'
6
+ import chalk from 'chalk'
7
+ import { getCallerInfo, formatCallerInfo } from './stack-trace'
8
+ import { getColorForModule } from './colors'
9
+ import { getLoggerForModule } from './winston-logger'
10
+ import {
11
+ formatMessage,
12
+ formatSection,
13
+ formatImportant,
14
+ formatOperationStart,
15
+ formatOperationSuccess
16
+ } from './formatter'
17
+
18
+ // Re-export types and utilities
19
+ export { LOGGER_CONFIG } from './config'
20
+ export type { LoggerConfig } from './config'
21
+ export type { CallerInfo } from './stack-trace'
22
+ export { clearColorCache } from './colors'
23
+ export { clearCallerCache } from './stack-trace'
24
+ export { clearLoggerCache } from './winston-logger'
25
+
26
+ // Re-export banner utilities for custom banners
27
+ export { displayStartupBanner, type StartupInfo } from './startup-banner'
10
28
 
11
- export type { LogLevel, LogTransport, LogEntry } from './transports'
12
- export type { LogFormatter } from './formatters'
13
-
14
- export interface Logger {
15
- debug(message: string, meta?: any): void
16
- info(message: string, meta?: any): void
17
- warn(message: string, meta?: any): void
18
- error(message: string, meta?: any): void
19
-
20
- // Contextual logging
21
- child(context: any): Logger
22
-
23
- // Performance logging
24
- time(label: string): void
25
- timeEnd(label: string): void
26
-
27
- // Request logging
28
- request(method: string, path: string, status?: number, duration?: number): void
29
-
30
- // Transport management
31
- addTransport(transport: LogTransport): void
32
- removeTransport(name: string): void
33
-
34
- // Cleanup
35
- close(): Promise<void>
29
+ /**
30
+ * Core log function that handles all log levels
31
+ */
32
+ function logMessage(level: 'debug' | 'info' | 'warn' | 'error', message: unknown, ...args: unknown[]): void {
33
+ const { file: callerFile, line: callerLine, function: callerFunction } = getCallerInfo()
34
+ const logger = getLoggerForModule(callerFile)
35
+ const moduleColor = getColorForModule(callerFile)
36
+
37
+ // Format caller context
38
+ const context = formatCallerInfo(
39
+ { file: callerFile, line: callerLine, function: callerFunction },
40
+ (str) => moduleColor(str)
41
+ )
42
+
43
+ // Format the message
44
+ const finalMessage = formatMessage(message, args)
45
+
46
+ // Log with appropriate level
47
+ logger.log({
48
+ level,
49
+ message: `${context} ${finalMessage}`
50
+ })
36
51
  }
37
52
 
38
- export interface LoggerConfig {
39
- level?: LogLevel
40
- transports?: LogTransport[]
41
- defaultMeta?: any
53
+ /**
54
+ * Log info message
55
+ */
56
+ export function LOG(message: unknown, ...args: unknown[]): void {
57
+ logMessage('info', message, ...args)
42
58
  }
43
59
 
44
- class FluxStackLogger implements Logger {
45
- private static instance: FluxStackLogger | null = null
46
- private logLevel: LogLevel
47
- private context: any = {}
48
- private timers: Map<string, number> = new Map()
49
- private transports: Map<string, LogTransport> = new Map()
50
- private defaultMeta: any = {}
51
-
52
- constructor(config?: LoggerConfig) {
53
- this.logLevel = config?.level || (process.env.LOG_LEVEL as LogLevel) || 'info'
54
- this.context = {}
55
- this.defaultMeta = config?.defaultMeta || {}
56
-
57
- // Setup default transports if none provided
58
- if (config?.transports) {
59
- config.transports.forEach(transport => {
60
- this.transports.set(transport.name, transport)
61
- })
62
- } else {
63
- this.setupDefaultTransports()
64
- }
65
- }
66
-
67
- static getInstance(config?: LoggerConfig): FluxStackLogger {
68
- if (FluxStackLogger.instance === null) {
69
- FluxStackLogger.instance = new FluxStackLogger(config)
70
- }
71
- return FluxStackLogger.instance
72
- }
73
-
74
- private setupDefaultTransports(): void {
75
- const isDevelopment = process.env.NODE_ENV !== 'production'
76
-
77
- if (isDevelopment) {
78
- // Development: Pretty console output
79
- this.transports.set('console', new ConsoleTransport({
80
- level: this.logLevel,
81
- colors: true,
82
- timestamp: true
83
- }))
84
- } else {
85
- // Production: JSON output for structured logging
86
- this.transports.set('json', new JSONTransport({
87
- level: this.logLevel,
88
- pretty: false
89
- }))
90
-
91
- // Also add file transport for production
92
- this.transports.set('file', new FileTransport({
93
- level: this.logLevel,
94
- filename: 'logs/fluxstack.log',
95
- maxSize: 10 * 1024 * 1024, // 10MB
96
- maxFiles: 5,
97
- compress: true
98
- }))
99
- }
100
- }
101
-
102
- private shouldLog(level: LogLevel): boolean {
103
- const levels: Record<LogLevel, number> = {
104
- debug: 0,
105
- info: 1,
106
- warn: 2,
107
- error: 3
108
- }
109
-
110
- return levels[level] >= levels[this.logLevel]
111
- }
112
-
113
- private async writeToTransports(level: LogLevel, message: string, meta?: any): Promise<void> {
114
- if (!this.shouldLog(level)) return
115
-
116
- const entry: LogEntry = {
117
- timestamp: new Date().toISOString(),
118
- level,
119
- message,
120
- meta: { ...this.defaultMeta, ...meta },
121
- context: Object.keys(this.context).length > 0 ? this.context : undefined
122
- }
123
-
124
- // Write to all transports
125
- const writePromises = Array.from(this.transports.values()).map(async transport => {
126
- try {
127
- await transport.write(entry)
128
- } catch (error) {
129
- // Fallback to console if transport fails
130
- console.error(`Transport ${transport.name} failed:`, error)
131
- }
132
- })
133
-
134
- await Promise.all(writePromises)
135
- }
136
-
137
- debug(message: string, meta?: any): void {
138
- this.writeToTransports('debug', message, meta).catch(err => {
139
- console.error('Logger error:', err)
140
- })
141
- }
60
+ /**
61
+ * Log warning message
62
+ */
63
+ export function WARN(message: unknown, ...args: unknown[]): void {
64
+ logMessage('warn', message, ...args)
65
+ }
142
66
 
143
- info(message: string, meta?: any): void {
144
- this.writeToTransports('info', message, meta).catch(err => {
145
- console.error('Logger error:', err)
146
- })
147
- }
67
+ /**
68
+ * Log error message
69
+ */
70
+ export function ERROR(message: unknown, ...args: unknown[]): void {
71
+ logMessage('error', message, ...args)
72
+ }
148
73
 
149
- warn(message: string, meta?: any): void {
150
- this.writeToTransports('warn', message, meta).catch(err => {
151
- console.error('Logger error:', err)
152
- })
153
- }
74
+ /**
75
+ * Log debug message
76
+ */
77
+ export function DEBUG(message: unknown, ...args: unknown[]): void {
78
+ logMessage('debug', message, ...args)
79
+ }
154
80
 
155
- error(message: string, meta?: any): void {
156
- this.writeToTransports('error', message, meta).catch(err => {
157
- console.error('Logger error:', err)
158
- })
81
+ /**
82
+ * Log operation start
83
+ */
84
+ export function START(operation: string, details?: unknown): void {
85
+ const message = formatOperationStart(operation)
86
+ if (details) {
87
+ logMessage('info', message, details)
88
+ } else {
89
+ logMessage('info', message)
159
90
  }
91
+ }
160
92
 
161
- // Contextual logging
162
- child(context: any): FluxStackLogger {
163
- const childLogger = new FluxStackLogger({
164
- level: this.logLevel,
165
- transports: Array.from(this.transports.values()),
166
- defaultMeta: this.defaultMeta
167
- })
168
- childLogger.context = { ...this.context, ...context }
169
- return childLogger
93
+ /**
94
+ * Log operation success
95
+ */
96
+ export function SUCCESS(operation: string, details?: unknown): void {
97
+ const message = formatOperationSuccess(operation)
98
+ if (details) {
99
+ logMessage('info', message, details)
100
+ } else {
101
+ logMessage('info', message)
170
102
  }
103
+ }
171
104
 
172
- // Performance logging
173
- time(label: string): void {
174
- this.timers.set(label, Date.now())
175
- }
105
+ /**
106
+ * Log important information
107
+ */
108
+ export function IMPORTANT(title: string, message: unknown): void {
109
+ const formattedTitle = chalk.bold.cyan(formatImportant(title))
110
+ logMessage('info', `${formattedTitle}\n${formatMessage(message)}`)
111
+ }
176
112
 
177
- timeEnd(label: string): void {
178
- const startTime = this.timers.get(label)
179
- if (startTime) {
180
- const duration = Date.now() - startTime
181
- this.info(`Timer ${label}: ${duration}ms`)
182
- this.timers.delete(label)
183
- }
184
- }
113
+ /**
114
+ * Log a section (group of related logs)
115
+ */
116
+ export function SECTION(sectionName: string, callback: () => void): void {
117
+ const sectionTitle = chalk.bold.cyan(formatSection(sectionName))
118
+ logMessage('info', sectionTitle)
119
+ callback()
120
+ logMessage('info', chalk.bold.cyan(formatSection(`FIM: ${sectionName}`)))
121
+ }
185
122
 
186
- // HTTP request logging
187
- request(method: string, path: string, status?: number, duration?: number): void {
188
- const meta: any = { method, path }
189
- if (status) meta.status = status
190
- if (duration) meta.duration = duration
191
-
192
- const statusStr = status ? ` ${status}` : ''
193
- const durationStr = duration ? ` (${duration}ms)` : ''
194
- this.info(`${method} ${path}${statusStr}${durationStr}`, meta)
195
- }
123
+ /**
124
+ * HTTP request logging (compatibility with Elysia)
125
+ */
126
+ export function request(method: string, path: string, status?: number, duration?: number): void {
127
+ const statusStr = status ? ` ${status}` : ''
128
+ const durationStr = duration ? ` (${duration}ms)` : ''
129
+ LOG(`${method} ${path}${statusStr}${durationStr}`)
130
+ }
196
131
 
197
- // Transport management
198
- addTransport(transport: LogTransport): void {
199
- this.transports.set(transport.name, transport)
200
- }
132
+ /**
133
+ * Plugin logging (compatibility with plugin system)
134
+ */
135
+ export function plugin(pluginName: string, message: string, meta?: unknown): void {
136
+ DEBUG(`[${pluginName}] ${message}`, meta)
137
+ }
201
138
 
202
- removeTransport(name: string): void {
203
- const transport = this.transports.get(name)
204
- if (transport && transport.close) {
205
- const closeResult = transport.close()
206
- if (closeResult instanceof Promise) {
207
- closeResult.catch(console.error)
208
- }
209
- }
210
- this.transports.delete(name)
211
- }
139
+ /**
140
+ * Framework logging (compatibility with framework)
141
+ */
142
+ export function framework(message: string, meta?: unknown): void {
143
+ LOG(`[FluxStack] ${message}`, meta)
144
+ }
212
145
 
213
- // Cleanup
214
- async close(): Promise<void> {
215
- const closePromises = Array.from(this.transports.values())
216
- .filter(transport => transport.close)
217
- .map(transport => transport.close!())
218
-
219
- await Promise.all(closePromises)
220
- this.transports.clear()
221
- }
146
+ /**
147
+ * Performance timing
148
+ */
149
+ const timers = new Map<string, number>()
222
150
 
223
- // Plugin logging
224
- plugin(pluginName: string, message: string, meta?: any): void {
225
- this.debug(`[${pluginName}] ${message}`, { plugin: pluginName, ...meta })
226
- }
151
+ export function time(label: string): void {
152
+ timers.set(label, Date.now())
153
+ }
227
154
 
228
- // Framework logging
229
- framework(message: string, meta?: any): void {
230
- this.info(`[FluxStack] ${message}`, { component: 'framework', ...meta })
155
+ export function timeEnd(label: string): void {
156
+ const startTime = timers.get(label)
157
+ if (startTime) {
158
+ const duration = Date.now() - startTime
159
+ LOG(`Timer ${label}: ${duration}ms`)
160
+ timers.delete(label)
161
+ } else {
162
+ WARN(`Timer not found: ${label}`)
231
163
  }
232
164
  }
233
165
 
234
- // Export transport and formatter classes
235
- export { ConsoleTransport, FileTransport, JSONTransport } from './transports'
236
- export { PrettyFormatter, JSONFormatter, SimpleFormatter } from './formatters'
237
-
238
- // Export performance utilities
239
- export {
240
- RequestLogger,
241
- PerformanceLogger,
242
- createRequestLoggingMiddleware
243
- } from './performance'
244
- export type { RequestContext, PerformanceTimer } from './performance'
245
-
246
- // Export middleware utilities
247
- export {
248
- createElysiaLoggerMiddleware,
249
- createDatabaseLoggerMiddleware,
250
- createPluginLoggerMiddleware,
251
- createBuildLoggerMiddleware
252
- } from './middleware'
166
+ /**
167
+ * Clear all caches (useful for testing)
168
+ */
169
+ export function clearCache(): void {
170
+ const { clearColorCache } = require('./colors')
171
+ const { clearCallerCache } = require('./stack-trace')
172
+ const { clearLoggerCache } = require('./winston-logger')
173
+
174
+ clearColorCache()
175
+ clearCallerCache()
176
+ clearLoggerCache()
177
+ }
253
178
 
254
- // Export singleton instance
255
- export const logger = FluxStackLogger.getInstance()
179
+ /**
180
+ * Legacy compatibility - logger object with methods
181
+ */
182
+ export const logger = {
183
+ debug: DEBUG,
184
+ info: LOG,
185
+ warn: WARN,
186
+ error: ERROR,
187
+ request,
188
+ plugin,
189
+ framework,
190
+ time,
191
+ timeEnd
192
+ }
256
193
 
257
- // Export convenience functions
194
+ /**
195
+ * Convenience log object (similar to old implementation)
196
+ */
258
197
  export const log = {
259
- debug: (message: string, meta?: any) => logger.debug(message, meta),
260
- info: (message: string, meta?: any) => logger.info(message, meta),
261
- warn: (message: string, meta?: any) => logger.warn(message, meta),
262
- error: (message: string, meta?: any) => logger.error(message, meta),
263
- request: (method: string, path: string, status?: number, duration?: number) =>
264
- logger.request(method, path, status, duration),
265
- plugin: (pluginName: string, message: string, meta?: any) =>
266
- logger.plugin(pluginName, message, meta),
267
- framework: (message: string, meta?: any) =>
268
- logger.framework(message, meta),
269
- child: (context: any) => logger.child(context),
270
- time: (label: string) => logger.time(label),
271
- timeEnd: (label: string) => logger.timeEnd(label),
272
- addTransport: (transport: LogTransport) => logger.addTransport(transport),
273
- removeTransport: (name: string) => logger.removeTransport(name),
274
- close: () => logger.close()
198
+ debug: DEBUG,
199
+ info: LOG,
200
+ warn: WARN,
201
+ error: ERROR,
202
+ request,
203
+ plugin,
204
+ framework,
205
+ time,
206
+ timeEnd
275
207
  }
276
208
 
277
- // Factory function for creating configured loggers
278
- export function createLogger(config: LoggerConfig): Logger {
279
- return new FluxStackLogger(config)
280
- }
209
+ /**
210
+ * Default export for easy importing
211
+ */
212
+ export default {
213
+ LOG,
214
+ WARN,
215
+ ERROR,
216
+ DEBUG,
217
+ START,
218
+ SUCCESS,
219
+ IMPORTANT,
220
+ SECTION,
221
+ request,
222
+ plugin,
223
+ framework,
224
+ time,
225
+ timeEnd,
226
+ logger,
227
+ log,
228
+ clearCache
229
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * FluxStack Logger - Stack Trace Analysis
3
+ * Extracts caller information from stack traces
4
+ */
5
+
6
+ import { relative } from 'path'
7
+
8
+ export interface CallerInfo {
9
+ file: string
10
+ line: number
11
+ function: string
12
+ }
13
+
14
+ // Cache for caller information
15
+ const callerCache = new Map<string, CallerInfo>()
16
+ const MAX_CACHE_SIZE = 1000
17
+
18
+ /**
19
+ * Get information about the caller (file, line, function)
20
+ */
21
+ export function getCallerInfo(): CallerInfo {
22
+ const originalFunc = Error.prepareStackTrace
23
+ let callerInfo: CallerInfo = { file: 'unknown', line: 0, function: 'unknown' }
24
+
25
+ try {
26
+ const err = new Error()
27
+ let stack: NodeJS.CallSite[] = []
28
+
29
+ Error.prepareStackTrace = (_: Error, stackTraces: NodeJS.CallSite[]) => stackTraces
30
+ stack = err.stack as unknown as NodeJS.CallSite[]
31
+
32
+ // Find the first frame that is not from the logger
33
+ for (let i = 2; i < stack.length; i++) {
34
+ const callSite = stack[i]
35
+ const fileName = callSite.getFileName()
36
+
37
+ if (
38
+ fileName &&
39
+ !fileName.includes('logger') &&
40
+ !fileName.includes('node_modules') &&
41
+ !fileName.includes('bun:') &&
42
+ !fileName.includes('<anonymous>')
43
+ ) {
44
+ const relativeFile = relative(process.cwd(), fileName)
45
+ const lineNumber = callSite.getLineNumber() || 0
46
+ const cacheKey = `${relativeFile}:${lineNumber}`
47
+
48
+ // Check cache
49
+ if (callerCache.has(cacheKey)) {
50
+ return callerCache.get(cacheKey)!
51
+ }
52
+
53
+ callerInfo = {
54
+ file: relativeFile,
55
+ line: lineNumber,
56
+ function: callSite.getFunctionName() || 'anonymous'
57
+ }
58
+
59
+ // Cache the result
60
+ if (callerCache.size >= MAX_CACHE_SIZE) {
61
+ // Remove oldest entry
62
+ const firstKey = callerCache.keys().next().value
63
+ callerCache.delete(firstKey)
64
+ }
65
+
66
+ callerCache.set(cacheKey, callerInfo)
67
+ break
68
+ }
69
+ }
70
+ } catch (error) {
71
+ // Silently fail - return default caller info
72
+ }
73
+
74
+ Error.prepareStackTrace = originalFunc
75
+ return callerInfo
76
+ }
77
+
78
+ /**
79
+ * Clear caller cache (useful for testing)
80
+ */
81
+ export function clearCallerCache(): void {
82
+ callerCache.clear()
83
+ }
84
+
85
+ /**
86
+ * Format caller info for display
87
+ */
88
+ export function formatCallerInfo(info: CallerInfo, colorFn: (str: string) => string): string {
89
+ const fileInfo = colorFn(`[${info.file}:${info.line}]`)
90
+ const functionInfo = info.function !== 'unknown' ? colorFn(`[${info.function}]`) : ''
91
+ return functionInfo ? `${fileInfo} ${functionInfo}` : fileInfo
92
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * FluxStack Logger - Startup Banner
3
+ * Clean and beautiful startup display
4
+ *
5
+ * Developers can customize the banner by:
6
+ * 1. Setting showBanner: false in server config
7
+ * 2. Using displayStartupBanner() in app.listen() callback
8
+ * 3. Creating completely custom banners with chalk
9
+ */
10
+
11
+ import chalk from 'chalk'
12
+ import { LOG } from './index'
13
+ import { FLUXSTACK_VERSION } from '../version'
14
+
15
+ export interface StartupInfo {
16
+ port: number
17
+ apiPrefix?: string
18
+ environment: string
19
+ pluginCount?: number
20
+ vitePort?: number
21
+ viteEmbedded?: boolean // true when Vite runs programmatically with backend
22
+ swaggerPath?: string
23
+ }
24
+
25
+ /**
26
+ * Display clean startup banner
27
+ */
28
+ export function displayStartupBanner(info: StartupInfo): void {
29
+ const {
30
+ port,
31
+ apiPrefix = '/api',
32
+ environment,
33
+ pluginCount = 0,
34
+ vitePort,
35
+ viteEmbedded = false,
36
+ swaggerPath
37
+ } = info
38
+
39
+ console.log('\n' + chalk.cyan.bold('⚡ FluxStack') + chalk.gray(` v${FLUXSTACK_VERSION}\n`))
40
+
41
+ // Server info
42
+ console.log(chalk.bold('🚀 Server'))
43
+ console.log(` ${chalk.gray('→')} http://localhost:${port}`)
44
+ console.log(` ${chalk.gray('→')} API: http://localhost:${port}${apiPrefix}`)
45
+ console.log(` ${chalk.gray('→')} Health: http://localhost:${port}${apiPrefix}/health`)
46
+
47
+ // Frontend info (only if Vite is running standalone, NOT embedded)
48
+ if (vitePort && !viteEmbedded) {
49
+ console.log('')
50
+ console.log(chalk.bold('âš›ī¸ Frontend'))
51
+ console.log(` ${chalk.gray('→')} http://localhost:${vitePort}`)
52
+ }
53
+
54
+ // Swagger docs (if enabled)
55
+ if (swaggerPath) {
56
+ console.log('')
57
+ console.log(chalk.bold('📋 Documentation'))
58
+ console.log(` ${chalk.gray('→')} Swagger: http://localhost:${port}${swaggerPath}`)
59
+ }
60
+
61
+ // Environment and plugins
62
+ console.log('')
63
+ console.log(chalk.bold('â„šī¸ Info'))
64
+ console.log(` ${chalk.gray('→')} Environment: ${chalk.green(environment)}`)
65
+ console.log(` ${chalk.gray('→')} Plugins: ${chalk.yellow(pluginCount)}`)
66
+
67
+ // Show Vite embedded status when applicable
68
+ if (viteEmbedded && vitePort) {
69
+ console.log(` ${chalk.gray('→')} Vite: ${chalk.magenta('embedded')} ${chalk.gray(`(port ${vitePort})`)}`)
70
+ }
71
+
72
+ console.log('\n' + chalk.green('✨ Ready!') + chalk.gray(' Press Ctrl+C to stop\n'))
73
+ }
74
+
75
+ /**
76
+ * Display simple plugin loaded message
77
+ */
78
+ export function logPluginLoaded(name: string, version?: string): void {
79
+ const versionStr = version ? chalk.gray(`v${version}`) : ''
80
+ LOG(`${chalk.green('✓')} Plugin loaded: ${chalk.cyan(name)} ${versionStr}`)
81
+ }
82
+
83
+ /**
84
+ * Display plugin count summary
85
+ */
86
+ export function logPluginsSummary(count: number): void {
87
+ if (count === 0) {
88
+ LOG(chalk.yellow('⚠ No plugins loaded'))
89
+ } else {
90
+ LOG(chalk.green(`✓ ${count} plugin${count > 1 ? 's' : ''} loaded successfully`))
91
+ }
92
+ }