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.
- package/app/server/backend-only.ts +5 -5
- package/app/server/index.ts +63 -54
- package/app/server/live/FluxStackConfig.ts +43 -39
- package/app/server/live/SystemMonitorIntegration.ts +2 -2
- package/app/server/live/register-components.ts +1 -1
- package/app/server/middleware/errorHandling.ts +6 -4
- package/app/server/routes/config.ts +145 -0
- package/app/server/routes/index.ts +5 -3
- package/config/app.config.ts +113 -0
- package/config/build.config.ts +24 -0
- package/config/database.config.ts +99 -0
- package/config/index.ts +68 -0
- package/config/logger.config.ts +27 -0
- package/config/runtime.config.ts +92 -0
- package/config/server.config.ts +46 -0
- package/config/services.config.ts +130 -0
- package/config/system.config.ts +105 -0
- package/core/build/index.ts +10 -4
- package/core/cli/index.ts +29 -12
- package/core/config/env.ts +37 -95
- package/core/config/runtime-config.ts +61 -58
- package/core/config/schema.ts +4 -0
- package/core/framework/server.ts +22 -10
- package/core/plugins/built-in/index.ts +7 -17
- package/core/plugins/built-in/swagger/index.ts +228 -228
- package/core/plugins/built-in/vite/index.ts +374 -358
- package/core/plugins/dependency-manager.ts +5 -5
- package/core/plugins/manager.ts +12 -12
- package/core/plugins/registry.ts +3 -3
- package/core/server/index.ts +0 -1
- package/core/server/live/ComponentRegistry.ts +34 -8
- package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
- package/core/server/live/websocket-plugin.ts +434 -434
- package/core/server/middleware/README.md +488 -0
- package/core/server/middleware/elysia-helpers.ts +227 -0
- package/core/server/middleware/index.ts +25 -9
- package/core/server/plugins/static-files-plugin.ts +231 -231
- package/core/utils/config-schema.ts +484 -0
- package/core/utils/env.ts +306 -0
- package/core/utils/helpers.ts +4 -4
- package/core/utils/logger/colors.ts +114 -0
- package/core/utils/logger/config.ts +35 -0
- package/core/utils/logger/formatter.ts +82 -0
- package/core/utils/logger/group-logger.ts +101 -0
- package/core/utils/logger/index.ts +199 -250
- package/core/utils/logger/stack-trace.ts +92 -0
- package/core/utils/logger/startup-banner.ts +92 -0
- package/core/utils/logger/winston-logger.ts +152 -0
- package/core/utils/version.ts +5 -0
- package/create-fluxstack.ts +1 -0
- package/fluxstack.config.ts +2 -2
- package/package.json +117 -115
- package/core/config/env-dynamic.ts +0 -326
- package/core/plugins/built-in/logger/index.ts +0 -180
- package/core/server/plugins/logger.ts +0 -47
- package/core/utils/env-runtime-v2.ts +0 -232
- package/core/utils/env-runtime.ts +0 -259
- package/core/utils/logger/formatters.ts +0 -222
- package/core/utils/logger/middleware.ts +0 -253
- package/core/utils/logger/performance.ts +0 -384
- package/core/utils/logger/transports.ts +0 -365
- 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
|
package/core/utils/helpers.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
+
}
|