create-fluxstack 1.1.0 → 1.4.0

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 (62) 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/index.ts +29 -12
  20. package/core/config/env.ts +37 -95
  21. package/core/config/runtime-config.ts +61 -58
  22. package/core/config/schema.ts +4 -0
  23. package/core/framework/server.ts +22 -10
  24. package/core/plugins/built-in/index.ts +7 -17
  25. package/core/plugins/built-in/swagger/index.ts +228 -228
  26. package/core/plugins/built-in/vite/index.ts +374 -358
  27. package/core/plugins/dependency-manager.ts +5 -5
  28. package/core/plugins/manager.ts +12 -12
  29. package/core/plugins/registry.ts +3 -3
  30. package/core/server/index.ts +0 -1
  31. package/core/server/live/ComponentRegistry.ts +34 -8
  32. package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
  33. package/core/server/live/websocket-plugin.ts +434 -434
  34. package/core/server/middleware/README.md +488 -0
  35. package/core/server/middleware/elysia-helpers.ts +227 -0
  36. package/core/server/middleware/index.ts +25 -9
  37. package/core/server/plugins/static-files-plugin.ts +231 -231
  38. package/core/utils/config-schema.ts +484 -0
  39. package/core/utils/env.ts +306 -0
  40. package/core/utils/helpers.ts +4 -4
  41. package/core/utils/logger/colors.ts +114 -0
  42. package/core/utils/logger/config.ts +35 -0
  43. package/core/utils/logger/formatter.ts +82 -0
  44. package/core/utils/logger/group-logger.ts +101 -0
  45. package/core/utils/logger/index.ts +199 -250
  46. package/core/utils/logger/stack-trace.ts +92 -0
  47. package/core/utils/logger/startup-banner.ts +92 -0
  48. package/core/utils/logger/winston-logger.ts +152 -0
  49. package/core/utils/version.ts +5 -0
  50. package/create-fluxstack.ts +1 -0
  51. package/fluxstack.config.ts +2 -2
  52. package/package.json +117 -115
  53. package/core/config/env-dynamic.ts +0 -326
  54. package/core/plugins/built-in/logger/index.ts +0 -180
  55. package/core/server/plugins/logger.ts +0 -47
  56. package/core/utils/env-runtime-v2.ts +0 -232
  57. package/core/utils/env-runtime.ts +0 -259
  58. package/core/utils/logger/formatters.ts +0 -222
  59. package/core/utils/logger/middleware.ts +0 -253
  60. package/core/utils/logger/performance.ts +0 -384
  61. package/core/utils/logger/transports.ts +0 -365
  62. package/core/utils/logger.ts +0 -106
@@ -0,0 +1,306 @@
1
+ /**
2
+ * ⚡ FluxStack Unified Environment Loader
3
+ *
4
+ * Single source of truth for environment variables with:
5
+ * - Automatic type casting
6
+ * - Build-safe dynamic access (prevents Bun inlining)
7
+ * - Simple, intuitive API
8
+ * - TypeScript type inference
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { env } from '@/core/utils/env'
13
+ *
14
+ * const port = env.PORT // number (3000)
15
+ * const debug = env.DEBUG // boolean (false)
16
+ * const origins = env.CORS_ORIGINS // string[] (['*'])
17
+ *
18
+ * // Custom vars with smart casting
19
+ * const timeout = env.get('TIMEOUT', 5000) // number
20
+ * const enabled = env.get('FEATURE_X', false) // boolean
21
+ * const tags = env.get('TAGS', ['api']) // string[]
22
+ * ```
23
+ */
24
+
25
+ /**
26
+ * Smart environment loader with dynamic access
27
+ * Uses Bun.env (runtime) → process.env (fallback) → eval (last resort)
28
+ */
29
+ class EnvLoader {
30
+ private cache = new Map<string, any>()
31
+ private accessor: () => Record<string, string | undefined>
32
+
33
+ constructor() {
34
+ this.accessor = this.createAccessor()
35
+ }
36
+
37
+ /**
38
+ * Create dynamic accessor to prevent build-time inlining
39
+ */
40
+ private createAccessor(): () => Record<string, string | undefined> {
41
+ const global = globalThis as any
42
+
43
+ return () => {
44
+ // Try Bun.env first (most reliable in Bun)
45
+ if (global['Bun']?.['env']) {
46
+ return global['Bun']['env']
47
+ }
48
+
49
+ // Fallback to process.env
50
+ if (global['process']?.['env']) {
51
+ return global['process']['env']
52
+ }
53
+
54
+ // Last resort: eval to bypass static analysis
55
+ try {
56
+ const proc = eval('typeof process !== "undefined" ? process : null')
57
+ return proc?.env || {}
58
+ } catch {
59
+ return {}
60
+ }
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Get environment variable with automatic type casting
66
+ * Type is inferred from defaultValue
67
+ */
68
+ get<T>(key: string, defaultValue?: T): T {
69
+ // Check cache first
70
+ const cacheKey = `${key}:${typeof defaultValue}`
71
+ if (this.cache.has(cacheKey)) {
72
+ return this.cache.get(cacheKey)
73
+ }
74
+
75
+ const env = this.accessor()
76
+ const value = env[key]
77
+
78
+ if (!value || value === '') {
79
+ this.cache.set(cacheKey, defaultValue as T)
80
+ return defaultValue as T
81
+ }
82
+
83
+ // Auto-detect type from defaultValue
84
+ let result: any = value
85
+
86
+ if (typeof defaultValue === 'number') {
87
+ const parsed = Number(value)
88
+ result = isNaN(parsed) ? defaultValue : parsed
89
+ } else if (typeof defaultValue === 'boolean') {
90
+ result = ['true', '1', 'yes', 'on'].includes(value.toLowerCase())
91
+ } else if (Array.isArray(defaultValue)) {
92
+ result = value.split(',').map(v => v.trim()).filter(Boolean)
93
+ } else if (typeof defaultValue === 'object' && defaultValue !== null) {
94
+ try {
95
+ result = JSON.parse(value)
96
+ } catch {
97
+ result = defaultValue
98
+ }
99
+ }
100
+
101
+ this.cache.set(cacheKey, result)
102
+ return result as T
103
+ }
104
+
105
+ /**
106
+ * Check if environment variable exists and has a value
107
+ */
108
+ has(key: string): boolean {
109
+ const env = this.accessor()
110
+ const value = env[key]
111
+ return value !== undefined && value !== ''
112
+ }
113
+
114
+ /**
115
+ * Get all environment variables
116
+ */
117
+ all(): Record<string, string> {
118
+ const env = this.accessor()
119
+ const result: Record<string, string> = {}
120
+
121
+ for (const [key, value] of Object.entries(env)) {
122
+ if (value !== undefined && value !== '') {
123
+ result[key] = value
124
+ }
125
+ }
126
+
127
+ return result
128
+ }
129
+
130
+ /**
131
+ * Require specific environment variables (throws if missing)
132
+ */
133
+ require(keys: string[]): void {
134
+ const missing = keys.filter(key => !this.has(key))
135
+ if (missing.length > 0) {
136
+ throw new Error(
137
+ `Missing required environment variables: ${missing.join(', ')}\n` +
138
+ `Please set them in your .env file or environment.`
139
+ )
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Validate environment variable value
145
+ */
146
+ validate(key: string, validValues: string[]): void {
147
+ const value = this.get(key, '')
148
+ if (value && !validValues.includes(value)) {
149
+ throw new Error(
150
+ `Invalid value for ${key}: "${value}"\n` +
151
+ `Valid values are: ${validValues.join(', ')}`
152
+ )
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Clear cache (useful for testing)
158
+ */
159
+ clearCache(): void {
160
+ this.cache.clear()
161
+ }
162
+ }
163
+
164
+ // Singleton instance
165
+ const loader = new EnvLoader()
166
+
167
+ /**
168
+ * Unified environment variables API
169
+ */
170
+ export const env = {
171
+ /**
172
+ * Get environment variable with smart type casting
173
+ * @example env.get('PORT', 3000) → number
174
+ */
175
+ get: <T>(key: string, defaultValue?: T): T => loader.get(key, defaultValue),
176
+
177
+ /**
178
+ * Check if environment variable exists
179
+ */
180
+ has: (key: string): boolean => loader.has(key),
181
+
182
+ /**
183
+ * Get all environment variables
184
+ */
185
+ all: (): Record<string, string> => loader.all(),
186
+
187
+ /**
188
+ * Require environment variables (throws if missing)
189
+ */
190
+ require: (keys: string[]): void => loader.require(keys),
191
+
192
+ /**
193
+ * Validate environment variable value
194
+ */
195
+ validate: (key: string, validValues: string[]): void => loader.validate(key, validValues),
196
+
197
+ /**
198
+ * Clear cache (for testing)
199
+ */
200
+ clearCache: (): void => loader.clearCache(),
201
+
202
+ // Common environment variables with smart defaults
203
+ get NODE_ENV() { return this.get('NODE_ENV', 'development') as 'development' | 'production' | 'test' },
204
+ get PORT() { return this.get('PORT', 3000) },
205
+ get HOST() { return this.get('HOST', 'localhost') },
206
+ get DEBUG() { return this.get('DEBUG', false) },
207
+ get LOG_LEVEL() { return this.get('LOG_LEVEL', 'info') as 'debug' | 'info' | 'warn' | 'error' },
208
+ get LOG_FORMAT() { return this.get('LOG_FORMAT', 'pretty') as 'json' | 'pretty' },
209
+
210
+ // API
211
+ get API_PREFIX() { return this.get('API_PREFIX', '/api') },
212
+ get VITE_PORT() { return this.get('VITE_PORT', 5173) },
213
+
214
+ // CORS
215
+ get CORS_ORIGINS() { return this.get('CORS_ORIGINS', ['*']) },
216
+ get CORS_METHODS() { return this.get('CORS_METHODS', ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']) },
217
+ get CORS_HEADERS() { return this.get('CORS_HEADERS', ['Content-Type', 'Authorization']) },
218
+ get CORS_CREDENTIALS() { return this.get('CORS_CREDENTIALS', false) },
219
+ get CORS_MAX_AGE() { return this.get('CORS_MAX_AGE', 86400) },
220
+
221
+ // App
222
+ get FLUXSTACK_APP_NAME() { return this.get('FLUXSTACK_APP_NAME', 'FluxStack') },
223
+ get FLUXSTACK_APP_VERSION() { return this.get('FLUXSTACK_APP_VERSION', '1.0.0') },
224
+
225
+ // Features
226
+ get ENABLE_MONITORING() { return this.get('ENABLE_MONITORING', false) },
227
+ get ENABLE_SWAGGER() { return this.get('ENABLE_SWAGGER', true) },
228
+ get ENABLE_METRICS() { return this.get('ENABLE_METRICS', false) },
229
+
230
+ // Database
231
+ get DATABASE_URL() { return this.get('DATABASE_URL', '') },
232
+ get DB_HOST() { return this.get('DB_HOST', 'localhost') },
233
+ get DB_PORT() { return this.get('DB_PORT', 5432) },
234
+ get DB_NAME() { return this.get('DB_NAME', '') },
235
+ get DB_USER() { return this.get('DB_USER', '') },
236
+ get DB_PASSWORD() { return this.get('DB_PASSWORD', '') },
237
+ get DB_SSL() { return this.get('DB_SSL', false) },
238
+
239
+ // Auth
240
+ get JWT_SECRET() { return this.get('JWT_SECRET', '') },
241
+ get JWT_EXPIRES_IN() { return this.get('JWT_EXPIRES_IN', '24h') },
242
+ get JWT_ALGORITHM() { return this.get('JWT_ALGORITHM', 'HS256') },
243
+
244
+ // Email
245
+ get SMTP_HOST() { return this.get('SMTP_HOST', '') },
246
+ get SMTP_PORT() { return this.get('SMTP_PORT', 587) },
247
+ get SMTP_USER() { return this.get('SMTP_USER', '') },
248
+ get SMTP_PASSWORD() { return this.get('SMTP_PASSWORD', '') },
249
+ get SMTP_SECURE() { return this.get('SMTP_SECURE', false) },
250
+ }
251
+
252
+ /**
253
+ * Environment helpers
254
+ */
255
+ export const helpers = {
256
+ isDevelopment: (): boolean => env.NODE_ENV === 'development',
257
+ isProduction: (): boolean => env.NODE_ENV === 'production',
258
+ isTest: (): boolean => env.NODE_ENV === 'test',
259
+
260
+ getServerUrl: (): string => `http://${env.HOST}:${env.PORT}`,
261
+ getClientUrl: (): string => `http://${env.HOST}:${env.VITE_PORT}`,
262
+
263
+ getDatabaseUrl: (): string | null => {
264
+ if (env.DATABASE_URL) return env.DATABASE_URL
265
+
266
+ const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = env
267
+ if (DB_HOST && DB_NAME) {
268
+ const auth = DB_USER ? `${DB_USER}:${DB_PASSWORD}@` : ''
269
+ return `postgres://${auth}${DB_HOST}:${DB_PORT}/${DB_NAME}`
270
+ }
271
+
272
+ return null
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Create namespaced environment access
278
+ * @example
279
+ * const db = createNamespace('DATABASE_')
280
+ * db.get('URL') // reads DATABASE_URL
281
+ */
282
+ export function createNamespace(prefix: string) {
283
+ return {
284
+ get: <T>(key: string, defaultValue?: T): T =>
285
+ env.get(`${prefix}${key}`, defaultValue),
286
+
287
+ has: (key: string): boolean =>
288
+ env.has(`${prefix}${key}`),
289
+
290
+ all: (): Record<string, string> => {
291
+ const allEnv = env.all()
292
+ const namespaced: Record<string, string> = {}
293
+
294
+ for (const [key, value] of Object.entries(allEnv)) {
295
+ if (key.startsWith(prefix)) {
296
+ namespaced[key.slice(prefix.length)] = value
297
+ }
298
+ }
299
+
300
+ return namespaced
301
+ }
302
+ }
303
+ }
304
+
305
+ // Default export
306
+ export default env
@@ -87,20 +87,20 @@ export const throttle = <T extends (...args: any[]) => any>(
87
87
  }
88
88
 
89
89
  export const isProduction = (): boolean => {
90
- // Import here to avoid circular dependency
91
- const { env } = require('./env-runtime-v2')
90
+ // Import here to avoid circular dependency
91
+ const { env } = require('./env')
92
92
  return env.NODE_ENV === 'production'
93
93
  }
94
94
 
95
95
  export const isDevelopment = (): boolean => {
96
96
  // Import here to avoid circular dependency
97
- const { env } = require('./env-runtime-v2')
97
+ const { env } = require('./env')
98
98
  return env.NODE_ENV === 'development' || !env.NODE_ENV
99
99
  }
100
100
 
101
101
  export const isTest = (): boolean => {
102
102
  // Import here to avoid circular dependency
103
- const { env } = require('./env-runtime-v2')
103
+ const { env } = require('./env')
104
104
  return env.NODE_ENV === 'test'
105
105
  }
106
106
 
@@ -0,0 +1,114 @@
1
+ /**
2
+ * FluxStack Logger - Color Management
3
+ * Generates unique colors for each module
4
+ */
5
+
6
+ import chalk from 'chalk'
7
+
8
+ // Cache for module colors
9
+ const moduleColors = new Map<string, chalk.Chalk>()
10
+
11
+ /**
12
+ * Pre-defined colors for common modules
13
+ */
14
+ const COMMON_MODULE_COLORS: Record<string, string> = {
15
+ api: '#4CAF50', // Green
16
+ db: '#2196F3', // Blue
17
+ auth: '#FF9800', // Orange
18
+ user: '#9C27B0', // Purple
19
+ config: '#00BCD4', // Cyan
20
+ utils: '#607D8B', // Blue Gray
21
+ routes: '#E91E63', // Pink
22
+ controllers: '#3F51B5', // Indigo
23
+ models: '#009688', // Teal
24
+ services: '#FF5722', // Deep Orange
25
+ plugins: '#673AB7', // Deep Purple
26
+ middleware: '#795548', // Brown
27
+ live: '#00E676', // Green Accent
28
+ websocket: '#00B0FF', // Light Blue Accent
29
+ build: '#FFC107', // Amber
30
+ cli: '#CDDC39' // Lime
31
+ }
32
+
33
+ /**
34
+ * Symbols for different log levels
35
+ */
36
+ export const LOG_SYMBOLS = {
37
+ error: chalk.red('✖'),
38
+ warn: chalk.yellow('⚠'),
39
+ info: chalk.blue('ℹ'),
40
+ debug: chalk.magenta('⬤'),
41
+ default: chalk.gray('•')
42
+ } as const
43
+
44
+ /**
45
+ * Colors for different log levels
46
+ */
47
+ export const LEVEL_COLORS = {
48
+ error: chalk.bold.red,
49
+ warn: chalk.bold.yellow,
50
+ info: chalk.bold.blue,
51
+ debug: chalk.bold.magenta,
52
+ default: chalk.bold.gray
53
+ } as const
54
+
55
+ /**
56
+ * Generate a unique color for a module based on its name
57
+ */
58
+ export function getColorForModule(moduleName: string): chalk.Chalk {
59
+ // Check cache first
60
+ if (moduleColors.has(moduleName)) {
61
+ return moduleColors.get(moduleName)!
62
+ }
63
+
64
+ // Check if module name contains a common module keyword
65
+ for (const [key, hexColor] of Object.entries(COMMON_MODULE_COLORS)) {
66
+ if (moduleName.toLowerCase().includes(key)) {
67
+ const color = chalk.hex(hexColor)
68
+ moduleColors.set(moduleName, color)
69
+ return color
70
+ }
71
+ }
72
+
73
+ // Generate color from module name hash
74
+ let hash = 0
75
+ for (let i = 0; i < moduleName.length; i++) {
76
+ hash = moduleName.charCodeAt(i) + ((hash << 5) - hash)
77
+ }
78
+
79
+ // Generate pleasant color using HSL
80
+ const h = Math.abs(hash) % 360
81
+ const s = 65 + (Math.abs(hash) % 20) // Saturation 65-85%
82
+ const l = 45 + (Math.abs(hash) % 15) // Lightness 45-60%
83
+
84
+ const hexColor = hslToHex(h, s, l)
85
+ const color = chalk.hex(hexColor)
86
+
87
+ moduleColors.set(moduleName, color)
88
+ return color
89
+ }
90
+
91
+ /**
92
+ * Convert HSL to Hex color
93
+ */
94
+ function hslToHex(h: number, s: number, l: number): string {
95
+ s /= 100
96
+ l /= 100
97
+
98
+ const k = (n: number) => (n + h / 30) % 12
99
+ const a = s * Math.min(l, 1 - l)
100
+ const f = (n: number) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)))
101
+
102
+ const r = Math.round(255 * f(0))
103
+ const g = Math.round(255 * f(8))
104
+ const b = Math.round(255 * f(4))
105
+
106
+ return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
107
+ }
108
+
109
+ /**
110
+ * Clear color cache (useful for testing)
111
+ */
112
+ export function clearColorCache(): void {
113
+ moduleColors.clear()
114
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * FluxStack Logger Configuration
3
+ * Re-export from declarative config
4
+ */
5
+
6
+ import { loggerConfig } from '@/config/logger.config'
7
+
8
+ export interface LoggerConfig {
9
+ level: 'debug' | 'info' | 'warn' | 'error'
10
+ dateFormat: string
11
+ logToFile: boolean
12
+ maxSize: string
13
+ maxFiles: string
14
+ objectDepth: number
15
+ enableColors: boolean
16
+ enableStackTrace: boolean
17
+ }
18
+
19
+ /**
20
+ * Get logger configuration from declarative config
21
+ */
22
+ export function getLoggerConfig(): LoggerConfig {
23
+ return {
24
+ level: loggerConfig.level,
25
+ dateFormat: loggerConfig.dateFormat,
26
+ logToFile: loggerConfig.logToFile,
27
+ maxSize: loggerConfig.maxSize,
28
+ maxFiles: loggerConfig.maxFiles,
29
+ objectDepth: loggerConfig.objectDepth,
30
+ enableColors: loggerConfig.enableColors,
31
+ enableStackTrace: loggerConfig.enableStackTrace
32
+ }
33
+ }
34
+
35
+ export const LOGGER_CONFIG = getLoggerConfig()
@@ -0,0 +1,82 @@
1
+ /**
2
+ * FluxStack Logger - Message Formatter
3
+ * Formats log messages with proper object inspection
4
+ */
5
+
6
+ import { inspect } from 'util'
7
+ import { LOGGER_CONFIG } from './config'
8
+
9
+ /**
10
+ * Format a log message with proper object inspection
11
+ */
12
+ export function formatMessage(message: unknown, args: unknown[] = []): string {
13
+ const inspectOptions = {
14
+ depth: LOGGER_CONFIG.objectDepth,
15
+ colors: LOGGER_CONFIG.enableColors,
16
+ compact: false,
17
+ breakLength: 100
18
+ }
19
+
20
+ // Format the main message
21
+ let formattedMessage: string
22
+
23
+ if (typeof message === 'string') {
24
+ formattedMessage = message
25
+ } else if (message instanceof Error) {
26
+ // Special handling for Error objects
27
+ formattedMessage = `${message.name}: ${message.message}\n${message.stack || ''}`
28
+ } else if (typeof message === 'object' && message !== null) {
29
+ // Use util.inspect for better object formatting
30
+ formattedMessage = inspect(message, inspectOptions)
31
+ } else {
32
+ formattedMessage = String(message)
33
+ }
34
+
35
+ // Format additional arguments (skip undefined values)
36
+ if (args.length > 0) {
37
+ const formattedArgs = args
38
+ .filter(arg => arg !== undefined) // Skip undefined values
39
+ .map(arg => {
40
+ if (typeof arg === 'object' && arg !== null) {
41
+ return inspect(arg, inspectOptions)
42
+ }
43
+ return String(arg)
44
+ })
45
+ .join(' ')
46
+
47
+ // Only add formatted args if there are any after filtering
48
+ if (formattedArgs.length > 0) {
49
+ return `${formattedMessage} ${formattedArgs}`
50
+ }
51
+ }
52
+
53
+ return formattedMessage
54
+ }
55
+
56
+ /**
57
+ * Format a section title
58
+ */
59
+ export function formatSection(title: string): string {
60
+ return `=== ${title.toUpperCase()} ===`
61
+ }
62
+
63
+ /**
64
+ * Format an important message
65
+ */
66
+ export function formatImportant(title: string): string {
67
+ return `■ ${title.toUpperCase()} ■`
68
+ }
69
+
70
+ /**
71
+ * Format operation start
72
+ */
73
+ export function formatOperationStart(operation: string): string {
74
+ return `▶ INICIANDO: ${operation}`
75
+ }
76
+
77
+ /**
78
+ * Format operation success
79
+ */
80
+ export function formatOperationSuccess(operation: string): string {
81
+ return `✓ CONCLUÍDO: ${operation}`
82
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * FluxStack Logger - Grouped/Collapsible Logs
3
+ * Creates beautiful grouped console output
4
+ */
5
+
6
+ import chalk from 'chalk'
7
+
8
+ export interface GroupOptions {
9
+ title: string
10
+ icon?: string
11
+ collapsed?: boolean
12
+ color?: 'cyan' | 'green' | 'yellow' | 'blue' | 'magenta' | 'gray'
13
+ }
14
+
15
+ /**
16
+ * Start a log group (collapsible in browsers, indented in terminal)
17
+ */
18
+ export function startGroup(options: GroupOptions): void {
19
+ const { title, icon = '📦', collapsed = false, color = 'cyan' } = options
20
+
21
+ // Check if we're in a browser-like environment (has console.group)
22
+ if (typeof console.groupCollapsed === 'function' && typeof console.group === 'function') {
23
+ const coloredTitle = chalk[color].bold(`${icon} ${title}`)
24
+
25
+ if (collapsed) {
26
+ console.groupCollapsed(coloredTitle)
27
+ } else {
28
+ console.group(coloredTitle)
29
+ }
30
+ } else {
31
+ // Terminal fallback - use box drawing
32
+ const coloredTitle = chalk[color].bold(`${icon} ${title}`)
33
+ console.log('\n' + coloredTitle)
34
+ console.log(chalk.gray('─'.repeat(Math.min(title.length + 4, 60))))
35
+ }
36
+ }
37
+
38
+ /**
39
+ * End a log group
40
+ */
41
+ export function endGroup(): void {
42
+ if (typeof console.groupEnd === 'function') {
43
+ console.groupEnd()
44
+ } else {
45
+ // Terminal fallback - just add spacing
46
+ console.log('')
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Log within a group
52
+ */
53
+ export function logInGroup(message: string, icon?: string): void {
54
+ const prefix = icon ? `${icon} ` : ' '
55
+ console.log(chalk.gray(prefix) + message)
56
+ }
57
+
58
+ /**
59
+ * Helper: Auto-group function that handles start/end automatically
60
+ */
61
+ export async function withGroup<T>(
62
+ options: GroupOptions,
63
+ callback: () => T | Promise<T>
64
+ ): Promise<T> {
65
+ startGroup(options)
66
+ try {
67
+ const result = await callback()
68
+ return result
69
+ } finally {
70
+ endGroup()
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Helper: Create a summary line for groups
76
+ */
77
+ export function groupSummary(count: number, itemName: string, icon: string = '✓'): void {
78
+ // itemName should already include proper pluralization
79
+ const message = `${icon} ${count} ${itemName}`
80
+ console.log(chalk.green.bold(message))
81
+ }
82
+
83
+ /**
84
+ * Create a boxed section for important info
85
+ */
86
+ export function logBox(title: string, content: string[], options: { color?: 'cyan' | 'green' | 'yellow' } = {}): void {
87
+ const { color = 'cyan' } = options
88
+ const maxWidth = Math.max(title.length, ...content.map(c => c.length)) + 4
89
+
90
+ console.log('')
91
+ console.log(chalk[color]('┌' + '─'.repeat(maxWidth) + '┐'))
92
+ console.log(chalk[color]('│ ') + chalk.bold(title.padEnd(maxWidth - 2)) + chalk[color](' │'))
93
+ console.log(chalk[color]('├' + '─'.repeat(maxWidth) + '┤'))
94
+
95
+ for (const line of content) {
96
+ console.log(chalk[color]('│ ') + line.padEnd(maxWidth - 2) + chalk[color](' │'))
97
+ }
98
+
99
+ console.log(chalk[color]('└' + '─'.repeat(maxWidth) + '┘'))
100
+ console.log('')
101
+ }