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.
- 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/generators/index.ts +5 -2
- package/core/cli/generators/plugin.ts +290 -0
- package/core/cli/index.ts +117 -15
- 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 +118 -8
- 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
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime Environment Loader V2 - Simplified API
|
|
3
|
-
* Mais elegante com casting automático e acesso direto
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Enhanced environment variable loader with smart casting
|
|
8
|
-
*/
|
|
9
|
-
class SmartEnvLoader {
|
|
10
|
-
private envAccessor: () => Record<string, string | undefined>
|
|
11
|
-
|
|
12
|
-
constructor() {
|
|
13
|
-
this.envAccessor = this.createDynamicAccessor()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
private createDynamicAccessor(): () => Record<string, string | undefined> {
|
|
17
|
-
const globalScope = globalThis as any
|
|
18
|
-
|
|
19
|
-
return () => {
|
|
20
|
-
// Try Bun.env first (most reliable in Bun runtime)
|
|
21
|
-
if (globalScope['Bun'] && globalScope['Bun']['env']) {
|
|
22
|
-
return globalScope['Bun']['env']
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Fallback to process.env with dynamic access
|
|
26
|
-
if (globalScope['process'] && globalScope['process']['env']) {
|
|
27
|
-
return globalScope['process']['env']
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Final fallback
|
|
31
|
-
const proc = eval('typeof process !== "undefined" ? process : null')
|
|
32
|
-
return proc?.env || {}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Smart get with automatic type conversion based on default value
|
|
38
|
-
*/
|
|
39
|
-
get<T>(key: string, defaultValue?: T): T {
|
|
40
|
-
const env = this.envAccessor()
|
|
41
|
-
const value = env[key]
|
|
42
|
-
|
|
43
|
-
if (!value || value === '') {
|
|
44
|
-
return defaultValue as T
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Auto-detect type from default value
|
|
48
|
-
if (typeof defaultValue === 'number') {
|
|
49
|
-
const parsed = parseInt(value, 10)
|
|
50
|
-
return (isNaN(parsed) ? defaultValue : parsed) as T
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (typeof defaultValue === 'boolean') {
|
|
54
|
-
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase()) as T
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (Array.isArray(defaultValue)) {
|
|
58
|
-
return value.split(',').map(v => v.trim()).filter(Boolean) as T
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (typeof defaultValue === 'object' && defaultValue !== null) {
|
|
62
|
-
try {
|
|
63
|
-
return JSON.parse(value) as T
|
|
64
|
-
} catch {
|
|
65
|
-
return defaultValue
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return value as T
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Check if environment variable exists
|
|
74
|
-
*/
|
|
75
|
-
has(key: string): boolean {
|
|
76
|
-
const env = this.envAccessor()
|
|
77
|
-
return key in env && env[key] !== undefined && env[key] !== ''
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get all environment variables
|
|
82
|
-
*/
|
|
83
|
-
all(): Record<string, string> {
|
|
84
|
-
const env = this.envAccessor()
|
|
85
|
-
const result: Record<string, string> = {}
|
|
86
|
-
|
|
87
|
-
for (const [key, value] of Object.entries(env)) {
|
|
88
|
-
if (value !== undefined && value !== '') {
|
|
89
|
-
result[key] = value
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return result
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Create singleton instance
|
|
98
|
-
const smartEnv = new SmartEnvLoader()
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Simplified env API with smart casting
|
|
102
|
-
*/
|
|
103
|
-
export const env = {
|
|
104
|
-
/**
|
|
105
|
-
* Smart get - automatically casts based on default value type
|
|
106
|
-
* Usage:
|
|
107
|
-
* env.get('PORT', 3000) -> number
|
|
108
|
-
* env.get('DEBUG', false) -> boolean
|
|
109
|
-
* env.get('ORIGINS', ['*']) -> string[]
|
|
110
|
-
* env.get('HOST', 'localhost') -> string
|
|
111
|
-
*/
|
|
112
|
-
get: <T>(key: string, defaultValue?: T): T => smartEnv.get(key, defaultValue),
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Check if env var exists
|
|
116
|
-
*/
|
|
117
|
-
has: (key: string) => smartEnv.has(key),
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Get all env vars
|
|
121
|
-
*/
|
|
122
|
-
all: () => smartEnv.all(),
|
|
123
|
-
|
|
124
|
-
// Common environment variables as properties with smart defaults
|
|
125
|
-
get NODE_ENV() { return this.get('NODE_ENV', 'development') },
|
|
126
|
-
get PORT() { return this.get('PORT', 3000) },
|
|
127
|
-
get HOST() { return this.get('HOST', 'localhost') },
|
|
128
|
-
get DEBUG() { return this.get('DEBUG', false) },
|
|
129
|
-
get LOG_LEVEL() { return this.get('LOG_LEVEL', 'info') },
|
|
130
|
-
get DATABASE_URL() { return this.get('DATABASE_URL', '') },
|
|
131
|
-
get JWT_SECRET() { return this.get('JWT_SECRET', '') },
|
|
132
|
-
get CORS_ORIGINS() { return this.get('CORS_ORIGINS', ['*']) },
|
|
133
|
-
get VITE_PORT() { return this.get('VITE_PORT', 5173) },
|
|
134
|
-
get API_PREFIX() { return this.get('API_PREFIX', '/api') },
|
|
135
|
-
|
|
136
|
-
// App specific
|
|
137
|
-
get FLUXSTACK_APP_NAME() { return this.get('FLUXSTACK_APP_NAME', 'FluxStack') },
|
|
138
|
-
get FLUXSTACK_APP_VERSION() { return this.get('FLUXSTACK_APP_VERSION', '1.0.0') },
|
|
139
|
-
|
|
140
|
-
// Monitoring
|
|
141
|
-
get ENABLE_MONITORING() { return this.get('ENABLE_MONITORING', false) },
|
|
142
|
-
get ENABLE_SWAGGER() { return this.get('ENABLE_SWAGGER', true) },
|
|
143
|
-
get ENABLE_METRICS() { return this.get('ENABLE_METRICS', false) },
|
|
144
|
-
|
|
145
|
-
// Database
|
|
146
|
-
get DB_HOST() { return this.get('DB_HOST', 'localhost') },
|
|
147
|
-
get DB_PORT() { return this.get('DB_PORT', 5432) },
|
|
148
|
-
get DB_NAME() { return this.get('DB_NAME', '') },
|
|
149
|
-
get DB_USER() { return this.get('DB_USER', '') },
|
|
150
|
-
get DB_PASSWORD() { return this.get('DB_PASSWORD', '') },
|
|
151
|
-
get DB_SSL() { return this.get('DB_SSL', false) },
|
|
152
|
-
|
|
153
|
-
// SMTP
|
|
154
|
-
get SMTP_HOST() { return this.get('SMTP_HOST', '') },
|
|
155
|
-
get SMTP_PORT() { return this.get('SMTP_PORT', 587) },
|
|
156
|
-
get SMTP_USER() { return this.get('SMTP_USER', '') },
|
|
157
|
-
get SMTP_PASSWORD() { return this.get('SMTP_PASSWORD', '') },
|
|
158
|
-
get SMTP_SECURE() { return this.get('SMTP_SECURE', false) }
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Create namespaced environment access
|
|
163
|
-
* Usage: const db = createNamespace('DATABASE_')
|
|
164
|
-
* db.get('URL') -> reads DATABASE_URL
|
|
165
|
-
*/
|
|
166
|
-
export function createNamespace(prefix: string) {
|
|
167
|
-
return {
|
|
168
|
-
get: <T>(key: string, defaultValue?: T): T =>
|
|
169
|
-
smartEnv.get(`${prefix}${key}`, defaultValue),
|
|
170
|
-
|
|
171
|
-
has: (key: string) => smartEnv.has(`${prefix}${key}`),
|
|
172
|
-
|
|
173
|
-
all: () => {
|
|
174
|
-
const allEnv = smartEnv.all()
|
|
175
|
-
const namespaced: Record<string, string> = {}
|
|
176
|
-
|
|
177
|
-
for (const [key, value] of Object.entries(allEnv)) {
|
|
178
|
-
if (key.startsWith(prefix)) {
|
|
179
|
-
namespaced[key.slice(prefix.length)] = value
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return namespaced
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Environment validation
|
|
190
|
-
*/
|
|
191
|
-
export const validate = {
|
|
192
|
-
require(keys: string[]): void {
|
|
193
|
-
const missing = keys.filter(key => !smartEnv.has(key))
|
|
194
|
-
if (missing.length > 0) {
|
|
195
|
-
throw new Error(`Missing required environment variables: ${missing.join(', ')}`)
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
|
|
199
|
-
oneOf(key: string, validValues: string[]): void {
|
|
200
|
-
const value = smartEnv.get(key, '')
|
|
201
|
-
if (value && !validValues.includes(value)) {
|
|
202
|
-
throw new Error(`${key} must be one of: ${validValues.join(', ')}, got: ${value}`)
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Convenience functions
|
|
209
|
-
*/
|
|
210
|
-
export const helpers = {
|
|
211
|
-
isDevelopment: () => env.NODE_ENV === 'development',
|
|
212
|
-
isProduction: () => env.NODE_ENV === 'production',
|
|
213
|
-
isTest: () => env.NODE_ENV === 'test',
|
|
214
|
-
|
|
215
|
-
getDatabaseUrl: () => {
|
|
216
|
-
const url = env.DATABASE_URL
|
|
217
|
-
if (url) return url
|
|
218
|
-
|
|
219
|
-
const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = env
|
|
220
|
-
if (DB_HOST && DB_NAME) {
|
|
221
|
-
const auth = DB_USER ? `${DB_USER}:${DB_PASSWORD}@` : ''
|
|
222
|
-
return `postgres://${auth}${DB_HOST}:${DB_PORT}/${DB_NAME}`
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return null
|
|
226
|
-
},
|
|
227
|
-
|
|
228
|
-
getServerUrl: () => `http://${env.HOST}:${env.PORT}`,
|
|
229
|
-
getClientUrl: () => `http://${env.HOST}:${env.VITE_PORT}`
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export default env
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime Environment Loader V2 - Simplified API
|
|
3
|
-
* Mais elegante com casting automático e acesso direto
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Enhanced environment variable loader with smart casting
|
|
8
|
-
*/
|
|
9
|
-
class SmartEnvLoader {
|
|
10
|
-
private envAccessor: () => Record<string, string | undefined>
|
|
11
|
-
|
|
12
|
-
constructor() {
|
|
13
|
-
this.envAccessor = this.createDynamicAccessor()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
private createDynamicAccessor(): () => Record<string, string | undefined> {
|
|
17
|
-
const globalScope = globalThis as any
|
|
18
|
-
|
|
19
|
-
return () => {
|
|
20
|
-
// Try Bun.env first (most reliable in Bun runtime)
|
|
21
|
-
if (globalScope['Bun'] && globalScope['Bun']['env']) {
|
|
22
|
-
return globalScope['Bun']['env']
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Fallback to process.env with dynamic access
|
|
26
|
-
if (globalScope['process'] && globalScope['process']['env']) {
|
|
27
|
-
return globalScope['process']['env']
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Final fallback
|
|
31
|
-
const proc = eval('typeof process !== "undefined" ? process : null')
|
|
32
|
-
return proc?.env || {}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Smart get with automatic type conversion based on default value
|
|
38
|
-
*/
|
|
39
|
-
get<T>(key: string, defaultValue?: T): T {
|
|
40
|
-
const env = this.envAccessor()
|
|
41
|
-
const value = env[key]
|
|
42
|
-
|
|
43
|
-
if (!value || value === '') {
|
|
44
|
-
return defaultValue as T
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Auto-detect type from default value
|
|
48
|
-
if (typeof defaultValue === 'number') {
|
|
49
|
-
const parsed = parseInt(value, 10)
|
|
50
|
-
return (isNaN(parsed) ? defaultValue : parsed) as T
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (typeof defaultValue === 'boolean') {
|
|
54
|
-
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase()) as T
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (Array.isArray(defaultValue)) {
|
|
58
|
-
return value.split(',').map(v => v.trim()).filter(Boolean) as T
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (typeof defaultValue === 'object' && defaultValue !== null) {
|
|
62
|
-
try {
|
|
63
|
-
return JSON.parse(value) as T
|
|
64
|
-
} catch {
|
|
65
|
-
return defaultValue
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return value as T
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Check if environment variable exists
|
|
74
|
-
*/
|
|
75
|
-
has(key: string): boolean {
|
|
76
|
-
const env = this.envAccessor()
|
|
77
|
-
return key in env && env[key] !== undefined && env[key] !== ''
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get all environment variables
|
|
82
|
-
*/
|
|
83
|
-
all(): Record<string, string> {
|
|
84
|
-
const env = this.envAccessor()
|
|
85
|
-
const result: Record<string, string> = {}
|
|
86
|
-
|
|
87
|
-
for (const [key, value] of Object.entries(env)) {
|
|
88
|
-
if (value !== undefined && value !== '') {
|
|
89
|
-
result[key] = value
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return result
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Create singleton instance
|
|
98
|
-
const smartEnv = new SmartEnvLoader()
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Simplified env API with smart casting
|
|
102
|
-
*/
|
|
103
|
-
export const env = {
|
|
104
|
-
/**
|
|
105
|
-
* Smart get - automatically casts based on default value type
|
|
106
|
-
* Usage:
|
|
107
|
-
* env.get('PORT', 3000) -> number
|
|
108
|
-
* env.get('DEBUG', false) -> boolean
|
|
109
|
-
* env.get('ORIGINS', ['*']) -> string[]
|
|
110
|
-
* env.get('HOST', 'localhost') -> string
|
|
111
|
-
*/
|
|
112
|
-
get: <T>(key: string, defaultValue?: T): T => smartEnv.get(key, defaultValue),
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Check if env var exists
|
|
116
|
-
*/
|
|
117
|
-
has: (key: string) => smartEnv.has(key),
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Get number value
|
|
121
|
-
*/
|
|
122
|
-
num: (key: string, defaultValue?: number) => Number(smartEnv.get(key, defaultValue?.toString() || '0')),
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Get boolean value
|
|
126
|
-
*/
|
|
127
|
-
bool: (key: string, defaultValue?: boolean) => smartEnv.get(key, defaultValue?.toString() || 'false') === 'true',
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Get array value
|
|
131
|
-
*/
|
|
132
|
-
array: (key: string, defaultValue?: string[]) => smartEnv.get(key, defaultValue?.join(',') || '').split(',').filter(Boolean),
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Get all env vars
|
|
136
|
-
*/
|
|
137
|
-
all: () => smartEnv.all(),
|
|
138
|
-
|
|
139
|
-
// Common environment variables as properties with smart defaults
|
|
140
|
-
get NODE_ENV() { return this.get('NODE_ENV', 'development') },
|
|
141
|
-
get PORT() { return this.get('PORT', 3000) },
|
|
142
|
-
get HOST() { return this.get('HOST', 'localhost') },
|
|
143
|
-
get DEBUG() { return this.get('DEBUG', false) },
|
|
144
|
-
get LOG_LEVEL() { return this.get('LOG_LEVEL', 'info') },
|
|
145
|
-
get DATABASE_URL() { return this.get('DATABASE_URL', '') },
|
|
146
|
-
get JWT_SECRET() { return this.get('JWT_SECRET', '') },
|
|
147
|
-
get CORS_ORIGINS() { return this.get('CORS_ORIGINS', ['*']) },
|
|
148
|
-
get VITE_PORT() { return this.get('VITE_PORT', 5173) },
|
|
149
|
-
get API_PREFIX() { return this.get('API_PREFIX', '/api') },
|
|
150
|
-
|
|
151
|
-
// App specific
|
|
152
|
-
get FLUXSTACK_APP_NAME() { return this.get('FLUXSTACK_APP_NAME', 'FluxStack') },
|
|
153
|
-
get FLUXSTACK_APP_VERSION() { return this.get('FLUXSTACK_APP_VERSION', '1.0.0') },
|
|
154
|
-
|
|
155
|
-
// Monitoring
|
|
156
|
-
get ENABLE_MONITORING() { return this.get('ENABLE_MONITORING', false) },
|
|
157
|
-
get ENABLE_SWAGGER() { return this.get('ENABLE_SWAGGER', true) },
|
|
158
|
-
get ENABLE_METRICS() { return this.get('ENABLE_METRICS', false) },
|
|
159
|
-
|
|
160
|
-
// Database
|
|
161
|
-
get DB_HOST() { return this.get('DB_HOST', 'localhost') },
|
|
162
|
-
get DB_PORT() { return this.get('DB_PORT', 5432) },
|
|
163
|
-
get DB_NAME() { return this.get('DB_NAME', '') },
|
|
164
|
-
get DB_USER() { return this.get('DB_USER', '') },
|
|
165
|
-
get DB_PASSWORD() { return this.get('DB_PASSWORD', '') },
|
|
166
|
-
get DB_SSL() { return this.get('DB_SSL', false) },
|
|
167
|
-
|
|
168
|
-
// SMTP
|
|
169
|
-
get SMTP_HOST() { return this.get('SMTP_HOST', '') },
|
|
170
|
-
get SMTP_PORT() { return this.get('SMTP_PORT', 587) },
|
|
171
|
-
get SMTP_USER() { return this.get('SMTP_USER', '') },
|
|
172
|
-
get SMTP_PASSWORD() { return this.get('SMTP_PASSWORD', '') },
|
|
173
|
-
get SMTP_SECURE() { return this.get('SMTP_SECURE', false) }
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Create namespaced environment access
|
|
178
|
-
* Usage: const db = createNamespace('DATABASE_')
|
|
179
|
-
* db.get('URL') -> reads DATABASE_URL
|
|
180
|
-
*/
|
|
181
|
-
export function createNamespace(prefix: string) {
|
|
182
|
-
return {
|
|
183
|
-
get: <T>(key: string, defaultValue?: T): T =>
|
|
184
|
-
smartEnv.get(`${prefix}${key}`, defaultValue),
|
|
185
|
-
|
|
186
|
-
has: (key: string) => smartEnv.has(`${prefix}${key}`),
|
|
187
|
-
|
|
188
|
-
all: () => {
|
|
189
|
-
const allEnv = smartEnv.all()
|
|
190
|
-
const namespaced: Record<string, string> = {}
|
|
191
|
-
|
|
192
|
-
for (const [key, value] of Object.entries(allEnv)) {
|
|
193
|
-
if (key.startsWith(prefix)) {
|
|
194
|
-
namespaced[key.slice(prefix.length)] = value
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return namespaced
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Environment validation
|
|
205
|
-
*/
|
|
206
|
-
export const validate = {
|
|
207
|
-
require(keys: string[]): void {
|
|
208
|
-
const missing = keys.filter(key => !smartEnv.has(key))
|
|
209
|
-
if (missing.length > 0) {
|
|
210
|
-
throw new Error(`Missing required environment variables: ${missing.join(', ')}`)
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
|
|
214
|
-
oneOf(key: string, validValues: string[]): void {
|
|
215
|
-
const value = smartEnv.get(key, '')
|
|
216
|
-
if (value && !validValues.includes(value)) {
|
|
217
|
-
throw new Error(`${key} must be one of: ${validValues.join(', ')}, got: ${value}`)
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
|
|
221
|
-
validate(key: string, validator: (value: string) => boolean, errorMessage: string): void {
|
|
222
|
-
const value = smartEnv.get(key, '')
|
|
223
|
-
if (value && !validator(value)) {
|
|
224
|
-
throw new Error(`${key}: ${errorMessage}`)
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Convenience functions
|
|
231
|
-
*/
|
|
232
|
-
export const helpers = {
|
|
233
|
-
isDevelopment: () => env.NODE_ENV === 'development',
|
|
234
|
-
isProduction: () => env.NODE_ENV === 'production',
|
|
235
|
-
isTest: () => env.NODE_ENV === 'test',
|
|
236
|
-
|
|
237
|
-
getDatabaseUrl: () => {
|
|
238
|
-
const url = env.DATABASE_URL
|
|
239
|
-
if (url) return url
|
|
240
|
-
|
|
241
|
-
const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = env
|
|
242
|
-
if (DB_HOST && DB_NAME) {
|
|
243
|
-
const auth = DB_USER ? `${DB_USER}:${DB_PASSWORD}@` : ''
|
|
244
|
-
return `postgres://${auth}${DB_HOST}:${DB_PORT}/${DB_NAME}`
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return null
|
|
248
|
-
},
|
|
249
|
-
|
|
250
|
-
getServerUrl: () => `http://${env.HOST}:${env.PORT}`,
|
|
251
|
-
getClientUrl: () => `http://${env.HOST}:${env.VITE_PORT}`
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export default env
|
|
255
|
-
|
|
256
|
-
// Legacy exports for compatibility
|
|
257
|
-
export const runtimeEnv = env
|
|
258
|
-
export const envValidation = validate
|
|
259
|
-
export const createEnvNamespace = createNamespace
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FluxStack Logger Formatters
|
|
3
|
-
* Different formatting strategies for log output
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { LogEntry, LogLevel } from './transports'
|
|
7
|
-
|
|
8
|
-
export interface LogFormatter {
|
|
9
|
-
format(entry: LogEntry): string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Pretty formatter for development with colors and readable layout
|
|
14
|
-
*/
|
|
15
|
-
export class PrettyFormatter implements LogFormatter {
|
|
16
|
-
private colors: boolean
|
|
17
|
-
private timestamp: boolean
|
|
18
|
-
|
|
19
|
-
private colorMap = {
|
|
20
|
-
debug: '\x1b[36m', // cyan
|
|
21
|
-
info: '\x1b[32m', // green
|
|
22
|
-
warn: '\x1b[33m', // yellow
|
|
23
|
-
error: '\x1b[31m', // red
|
|
24
|
-
reset: '\x1b[0m',
|
|
25
|
-
gray: '\x1b[90m'
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
constructor(options: { colors?: boolean; timestamp?: boolean } = {}) {
|
|
29
|
-
this.colors = options.colors !== false
|
|
30
|
-
this.timestamp = options.timestamp !== false
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
format(entry: LogEntry): string {
|
|
34
|
-
const { timestamp, level, message, meta, context } = entry
|
|
35
|
-
|
|
36
|
-
let formatted = ''
|
|
37
|
-
|
|
38
|
-
// Add timestamp
|
|
39
|
-
if (this.timestamp) {
|
|
40
|
-
const color = this.colors ? this.colorMap.gray : ''
|
|
41
|
-
const reset = this.colors ? this.colorMap.reset : ''
|
|
42
|
-
formatted += `${color}[${timestamp}]${reset} `
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Add level with color and icon
|
|
46
|
-
const levelColor = this.colors ? this.colorMap[level] : ''
|
|
47
|
-
const reset = this.colors ? this.colorMap.reset : ''
|
|
48
|
-
const levelIcon = this.getLevelIcon(level)
|
|
49
|
-
const levelStr = level.toUpperCase().padEnd(5)
|
|
50
|
-
formatted += `${levelColor}${levelIcon} ${levelStr}${reset} `
|
|
51
|
-
|
|
52
|
-
// Add context if available
|
|
53
|
-
if (context && Object.keys(context).length > 0) {
|
|
54
|
-
const contextStr = Object.entries(context)
|
|
55
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
56
|
-
.join(' ')
|
|
57
|
-
const contextColor = this.colors ? this.colorMap.gray : ''
|
|
58
|
-
formatted += `${contextColor}[${contextStr}]${reset} `
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Add message
|
|
62
|
-
formatted += message
|
|
63
|
-
|
|
64
|
-
// Add meta if available
|
|
65
|
-
if (meta && typeof meta === 'object') {
|
|
66
|
-
const metaColor = this.colors ? this.colorMap.gray : ''
|
|
67
|
-
const metaStr = this.formatMeta(meta)
|
|
68
|
-
formatted += ` ${metaColor}${metaStr}${reset}`
|
|
69
|
-
} else if (meta !== undefined) {
|
|
70
|
-
formatted += ` ${meta}`
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return formatted
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private getLevelIcon(level: LogLevel): string {
|
|
77
|
-
switch (level) {
|
|
78
|
-
case 'debug': return '🔍'
|
|
79
|
-
case 'info': return 'ℹ️ '
|
|
80
|
-
case 'warn': return '⚠️ '
|
|
81
|
-
case 'error': return '❌'
|
|
82
|
-
default: return ' '
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private formatMeta(meta: any): string {
|
|
87
|
-
if (typeof meta === 'object' && meta !== null) {
|
|
88
|
-
// Pretty print objects with indentation
|
|
89
|
-
return JSON.stringify(meta, null, 2)
|
|
90
|
-
.split('\n')
|
|
91
|
-
.map((line, index) => index === 0 ? line : ` ${line}`)
|
|
92
|
-
.join('\n')
|
|
93
|
-
}
|
|
94
|
-
return String(meta)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* JSON formatter for production with structured output
|
|
100
|
-
*/
|
|
101
|
-
export class JSONFormatter implements LogFormatter {
|
|
102
|
-
private pretty: boolean
|
|
103
|
-
|
|
104
|
-
constructor(options: { pretty?: boolean } = {}) {
|
|
105
|
-
this.pretty = options.pretty || false
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
format(entry: LogEntry): string {
|
|
109
|
-
const jsonEntry = {
|
|
110
|
-
'@timestamp': entry.timestamp,
|
|
111
|
-
level: entry.level,
|
|
112
|
-
message: entry.message,
|
|
113
|
-
...(entry.context && { context: entry.context }),
|
|
114
|
-
...(entry.meta && { meta: entry.meta })
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return this.pretty
|
|
118
|
-
? JSON.stringify(jsonEntry, null, 2)
|
|
119
|
-
: JSON.stringify(jsonEntry)
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Simple formatter for basic text output
|
|
125
|
-
*/
|
|
126
|
-
export class SimpleFormatter implements LogFormatter {
|
|
127
|
-
private timestamp: boolean
|
|
128
|
-
|
|
129
|
-
constructor(options: { timestamp?: boolean } = {}) {
|
|
130
|
-
this.timestamp = options.timestamp !== false
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
format(entry: LogEntry): string {
|
|
134
|
-
const { timestamp, level, message, meta, context } = entry
|
|
135
|
-
|
|
136
|
-
let formatted = ''
|
|
137
|
-
|
|
138
|
-
// Add timestamp
|
|
139
|
-
if (this.timestamp) {
|
|
140
|
-
formatted += `[${timestamp}] `
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Add level
|
|
144
|
-
formatted += `${level.toUpperCase().padEnd(5)} `
|
|
145
|
-
|
|
146
|
-
// Add context if available
|
|
147
|
-
if (context && Object.keys(context).length > 0) {
|
|
148
|
-
const contextStr = Object.entries(context)
|
|
149
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
150
|
-
.join(' ')
|
|
151
|
-
formatted += `[${contextStr}] `
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Add message
|
|
155
|
-
formatted += message
|
|
156
|
-
|
|
157
|
-
// Add meta if available
|
|
158
|
-
if (meta && typeof meta === 'object') {
|
|
159
|
-
formatted += ` ${JSON.stringify(meta)}`
|
|
160
|
-
} else if (meta !== undefined) {
|
|
161
|
-
formatted += ` ${meta}`
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return formatted
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Syslog formatter for system logging
|
|
170
|
-
*/
|
|
171
|
-
export class SyslogFormatter implements LogFormatter {
|
|
172
|
-
private facility: number
|
|
173
|
-
private hostname: string
|
|
174
|
-
|
|
175
|
-
constructor(options: { facility?: number; hostname?: string } = {}) {
|
|
176
|
-
this.facility = options.facility || 16 // local0
|
|
177
|
-
this.hostname = options.hostname || require('os').hostname()
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
format(entry: LogEntry): string {
|
|
181
|
-
const { timestamp, level, message, meta, context } = entry
|
|
182
|
-
|
|
183
|
-
// Calculate priority (facility * 8 + severity)
|
|
184
|
-
const severity = this.getLevelSeverity(level)
|
|
185
|
-
const priority = this.facility * 8 + severity
|
|
186
|
-
|
|
187
|
-
// Format timestamp in RFC3339
|
|
188
|
-
const syslogTime = new Date(timestamp).toISOString()
|
|
189
|
-
|
|
190
|
-
// Build syslog message
|
|
191
|
-
let syslogMessage = `<${priority}>${syslogTime} ${this.hostname} fluxstack: `
|
|
192
|
-
|
|
193
|
-
// Add context if available
|
|
194
|
-
if (context && Object.keys(context).length > 0) {
|
|
195
|
-
const contextStr = Object.entries(context)
|
|
196
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
197
|
-
.join(' ')
|
|
198
|
-
syslogMessage += `[${contextStr}] `
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
syslogMessage += message
|
|
202
|
-
|
|
203
|
-
// Add meta if available
|
|
204
|
-
if (meta && typeof meta === 'object') {
|
|
205
|
-
syslogMessage += ` ${JSON.stringify(meta)}`
|
|
206
|
-
} else if (meta !== undefined) {
|
|
207
|
-
syslogMessage += ` ${meta}`
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return syslogMessage
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
private getLevelSeverity(level: LogLevel): number {
|
|
214
|
-
switch (level) {
|
|
215
|
-
case 'debug': return 7 // debug
|
|
216
|
-
case 'info': return 6 // info
|
|
217
|
-
case 'warn': return 4 // warning
|
|
218
|
-
case 'error': return 3 // error
|
|
219
|
-
default: return 6
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|