create-fluxstack 1.0.22 → 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 +6 -26
- 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/bundler.ts +53 -5
- package/core/build/flux-plugins-generator.ts +315 -0
- package/core/build/index.ts +11 -7
- package/core/build/live-components-generator.ts +231 -0
- package/core/build/optimizer.ts +2 -54
- package/core/cli/index.ts +31 -13
- package/core/config/env.ts +38 -94
- package/core/config/runtime-config.ts +61 -58
- package/core/config/schema.ts +1 -0
- package/core/framework/server.ts +55 -11
- package/core/plugins/built-in/index.ts +7 -17
- package/core/plugins/built-in/static/index.ts +24 -10
- 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 +57 -14
- 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 +9 -3
- 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 +6 -12
- package/package.json +117 -114
- package/plugins/crypto-auth/README.md +238 -0
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +325 -0
- package/plugins/crypto-auth/client/components/AuthProvider.tsx +190 -0
- package/plugins/crypto-auth/client/components/LoginButton.tsx +155 -0
- package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +109 -0
- package/plugins/crypto-auth/client/components/SessionInfo.tsx +242 -0
- package/plugins/crypto-auth/client/components/index.ts +15 -0
- package/plugins/crypto-auth/client/index.ts +12 -0
- package/plugins/crypto-auth/index.ts +230 -0
- package/plugins/crypto-auth/package.json +65 -0
- package/plugins/crypto-auth/plugin.json +29 -0
- package/plugins/crypto-auth/server/AuthMiddleware.ts +237 -0
- package/plugins/crypto-auth/server/CryptoAuthService.ts +293 -0
- package/plugins/crypto-auth/server/index.ts +9 -0
- package/vite.config.ts +16 -0
- 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,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serviço de Autenticação Criptográfica
|
|
3
|
+
* Implementa autenticação baseada em Ed25519
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ed25519 } from '@noble/curves/ed25519'
|
|
7
|
+
import { sha256 } from '@noble/hashes/sha256'
|
|
8
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
|
9
|
+
export interface Logger {
|
|
10
|
+
debug(message: string, meta?: any): void
|
|
11
|
+
info(message: string, meta?: any): void
|
|
12
|
+
warn(message: string, meta?: any): void
|
|
13
|
+
error(message: string, meta?: any): void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SessionData {
|
|
17
|
+
sessionId: string
|
|
18
|
+
publicKey: string
|
|
19
|
+
createdAt: Date
|
|
20
|
+
lastUsed: Date
|
|
21
|
+
isAdmin: boolean
|
|
22
|
+
permissions: string[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AuthResult {
|
|
26
|
+
success: boolean
|
|
27
|
+
sessionId?: string
|
|
28
|
+
error?: string
|
|
29
|
+
user?: {
|
|
30
|
+
sessionId: string
|
|
31
|
+
isAdmin: boolean
|
|
32
|
+
isSuperAdmin: boolean
|
|
33
|
+
permissions: string[]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CryptoAuthConfig {
|
|
38
|
+
sessionTimeout: number
|
|
39
|
+
maxTimeDrift: number
|
|
40
|
+
adminKeys: string[]
|
|
41
|
+
logger?: Logger
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class CryptoAuthService {
|
|
45
|
+
private sessions: Map<string, SessionData> = new Map()
|
|
46
|
+
private config: CryptoAuthConfig
|
|
47
|
+
private logger?: Logger
|
|
48
|
+
|
|
49
|
+
constructor(config: CryptoAuthConfig) {
|
|
50
|
+
this.config = config
|
|
51
|
+
this.logger = config.logger
|
|
52
|
+
|
|
53
|
+
// Limpar sessões expiradas a cada 5 minutos
|
|
54
|
+
setInterval(() => {
|
|
55
|
+
this.cleanupExpiredSessions()
|
|
56
|
+
}, 5 * 60 * 1000)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Inicializar uma nova sessão
|
|
61
|
+
*/
|
|
62
|
+
async initializeSession(data: { publicKey?: string }): Promise<AuthResult> {
|
|
63
|
+
try {
|
|
64
|
+
let publicKey: string
|
|
65
|
+
|
|
66
|
+
if (data.publicKey) {
|
|
67
|
+
// Validar chave pública fornecida
|
|
68
|
+
if (!this.isValidPublicKey(data.publicKey)) {
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: "Chave pública inválida"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
publicKey = data.publicKey
|
|
75
|
+
} else {
|
|
76
|
+
// Gerar novo par de chaves
|
|
77
|
+
const privateKey = ed25519.utils.randomPrivateKey()
|
|
78
|
+
publicKey = bytesToHex(ed25519.getPublicKey(privateKey))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const sessionId = publicKey
|
|
82
|
+
const isAdmin = this.config.adminKeys.includes(publicKey)
|
|
83
|
+
|
|
84
|
+
const sessionData: SessionData = {
|
|
85
|
+
sessionId,
|
|
86
|
+
publicKey,
|
|
87
|
+
createdAt: new Date(),
|
|
88
|
+
lastUsed: new Date(),
|
|
89
|
+
isAdmin,
|
|
90
|
+
permissions: isAdmin ? ['admin', 'read', 'write'] : ['read']
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.sessions.set(sessionId, sessionData)
|
|
94
|
+
|
|
95
|
+
this.logger?.info("Nova sessão inicializada", {
|
|
96
|
+
sessionId: sessionId.substring(0, 8) + "...",
|
|
97
|
+
isAdmin,
|
|
98
|
+
permissions: sessionData.permissions
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
sessionId,
|
|
104
|
+
user: {
|
|
105
|
+
sessionId,
|
|
106
|
+
isAdmin,
|
|
107
|
+
isSuperAdmin: isAdmin,
|
|
108
|
+
permissions: sessionData.permissions
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
this.logger?.error("Erro ao inicializar sessão", { error })
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
error: "Erro interno ao inicializar sessão"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validar uma sessão com assinatura
|
|
122
|
+
*/
|
|
123
|
+
async validateSession(data: {
|
|
124
|
+
sessionId: string
|
|
125
|
+
timestamp: number
|
|
126
|
+
nonce: string
|
|
127
|
+
signature: string
|
|
128
|
+
message?: string
|
|
129
|
+
}): Promise<AuthResult> {
|
|
130
|
+
try {
|
|
131
|
+
const { sessionId, timestamp, nonce, signature, message = "" } = data
|
|
132
|
+
|
|
133
|
+
// Verificar se a sessão existe
|
|
134
|
+
const session = this.sessions.get(sessionId)
|
|
135
|
+
if (!session) {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
error: "Sessão não encontrada"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Verificar se a sessão não expirou
|
|
143
|
+
const now = Date.now()
|
|
144
|
+
const sessionAge = now - session.lastUsed.getTime()
|
|
145
|
+
if (sessionAge > this.config.sessionTimeout) {
|
|
146
|
+
this.sessions.delete(sessionId)
|
|
147
|
+
return {
|
|
148
|
+
success: false,
|
|
149
|
+
error: "Sessão expirada"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Verificar drift de tempo
|
|
154
|
+
const timeDrift = Math.abs(now - timestamp)
|
|
155
|
+
if (timeDrift > this.config.maxTimeDrift) {
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
error: "Timestamp inválido"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Construir mensagem para verificação
|
|
163
|
+
const messageToVerify = `${sessionId}:${timestamp}:${nonce}:${message}`
|
|
164
|
+
const messageHash = sha256(new TextEncoder().encode(messageToVerify))
|
|
165
|
+
|
|
166
|
+
// Verificar assinatura
|
|
167
|
+
const publicKeyBytes = hexToBytes(session.publicKey)
|
|
168
|
+
const signatureBytes = hexToBytes(signature)
|
|
169
|
+
|
|
170
|
+
const isValidSignature = ed25519.verify(signatureBytes, messageHash, publicKeyBytes)
|
|
171
|
+
|
|
172
|
+
if (!isValidSignature) {
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
error: "Assinatura inválida"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Atualizar último uso da sessão
|
|
180
|
+
session.lastUsed = new Date()
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
success: true,
|
|
184
|
+
sessionId,
|
|
185
|
+
user: {
|
|
186
|
+
sessionId,
|
|
187
|
+
isAdmin: session.isAdmin,
|
|
188
|
+
isSuperAdmin: session.isAdmin,
|
|
189
|
+
permissions: session.permissions
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
this.logger?.error("Erro ao validar sessão", { error })
|
|
194
|
+
return {
|
|
195
|
+
success: false,
|
|
196
|
+
error: "Erro interno ao validar sessão"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Obter informações da sessão
|
|
203
|
+
*/
|
|
204
|
+
async getSessionInfo(sessionId: string): Promise<SessionData | null> {
|
|
205
|
+
const session = this.sessions.get(sessionId)
|
|
206
|
+
if (!session) {
|
|
207
|
+
return null
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Verificar se não expirou
|
|
211
|
+
const now = Date.now()
|
|
212
|
+
const sessionAge = now - session.lastUsed.getTime()
|
|
213
|
+
if (sessionAge > this.config.sessionTimeout) {
|
|
214
|
+
this.sessions.delete(sessionId)
|
|
215
|
+
return null
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { ...session }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Destruir uma sessão
|
|
223
|
+
*/
|
|
224
|
+
async destroySession(sessionId: string): Promise<void> {
|
|
225
|
+
this.sessions.delete(sessionId)
|
|
226
|
+
this.logger?.info("Sessão destruída", {
|
|
227
|
+
sessionId: sessionId.substring(0, 8) + "..."
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Verificar se uma chave pública é válida
|
|
233
|
+
*/
|
|
234
|
+
private isValidPublicKey(publicKey: string): boolean {
|
|
235
|
+
try {
|
|
236
|
+
if (publicKey.length !== 64) {
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const bytes = hexToBytes(publicKey)
|
|
241
|
+
return bytes.length === 32
|
|
242
|
+
} catch {
|
|
243
|
+
return false
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Limpar sessões expiradas
|
|
249
|
+
*/
|
|
250
|
+
private cleanupExpiredSessions(): void {
|
|
251
|
+
const now = Date.now()
|
|
252
|
+
let cleanedCount = 0
|
|
253
|
+
|
|
254
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
255
|
+
const sessionAge = now - session.lastUsed.getTime()
|
|
256
|
+
if (sessionAge > this.config.sessionTimeout) {
|
|
257
|
+
this.sessions.delete(sessionId)
|
|
258
|
+
cleanedCount++
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (cleanedCount > 0) {
|
|
263
|
+
this.logger?.debug(`Limpeza de sessões: ${cleanedCount} sessões expiradas removidas`)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Obter estatísticas das sessões
|
|
269
|
+
*/
|
|
270
|
+
getStats() {
|
|
271
|
+
const now = Date.now()
|
|
272
|
+
let activeSessions = 0
|
|
273
|
+
let adminSessions = 0
|
|
274
|
+
|
|
275
|
+
for (const session of this.sessions.values()) {
|
|
276
|
+
const sessionAge = now - session.lastUsed.getTime()
|
|
277
|
+
if (sessionAge <= this.config.sessionTimeout) {
|
|
278
|
+
activeSessions++
|
|
279
|
+
if (session.isAdmin) {
|
|
280
|
+
adminSessions++
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
totalSessions: this.sessions.size,
|
|
287
|
+
activeSessions,
|
|
288
|
+
adminSessions,
|
|
289
|
+
sessionTimeout: this.config.sessionTimeout,
|
|
290
|
+
adminKeys: this.config.adminKeys.length
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exportações do servidor de autenticação
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { CryptoAuthService } from './CryptoAuthService'
|
|
6
|
+
export type { SessionData, AuthResult, CryptoAuthConfig } from './CryptoAuthService'
|
|
7
|
+
|
|
8
|
+
export { AuthMiddleware } from './AuthMiddleware'
|
|
9
|
+
export type { AuthMiddlewareConfig, AuthMiddlewareResult } from './AuthMiddleware'
|
package/vite.config.ts
CHANGED
|
@@ -23,6 +23,22 @@ export default defineConfig({
|
|
|
23
23
|
},
|
|
24
24
|
})
|
|
25
25
|
],
|
|
26
|
+
define: {
|
|
27
|
+
__DEFINES__: JSON.stringify({}),
|
|
28
|
+
global: 'globalThis',
|
|
29
|
+
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
|
30
|
+
__HMR_CONFIG_NAME__: JSON.stringify('vite.config.ts'),
|
|
31
|
+
__BASE__: JSON.stringify('/'),
|
|
32
|
+
__SERVER_HOST__: JSON.stringify('localhost'),
|
|
33
|
+
__HMR_PROTOCOL__: JSON.stringify('ws'),
|
|
34
|
+
__HMR_PORT__: JSON.stringify(5173),
|
|
35
|
+
__HMR_HOSTNAME__: JSON.stringify('localhost'),
|
|
36
|
+
__HMR_BASE__: JSON.stringify('/'),
|
|
37
|
+
__HMR_DIRECT_TARGET__: JSON.stringify('localhost:5173'),
|
|
38
|
+
__HMR_ENABLE_OVERLAY__: JSON.stringify(true),
|
|
39
|
+
__HMR_TIMEOUT__: JSON.stringify(30000),
|
|
40
|
+
__WS_TOKEN__: JSON.stringify(''),
|
|
41
|
+
},
|
|
26
42
|
root: 'app/client',
|
|
27
43
|
server: {
|
|
28
44
|
port: 5173,
|
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dynamic Environment Configuration Adapter for FluxStack
|
|
3
|
-
* Integrates runtime env loader with existing configuration system
|
|
4
|
-
* Solves Bun build issue by using dynamic environment access
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { env, runtimeEnv, envValidation } from '../utils/env-runtime'
|
|
8
|
-
import type { FluxStackConfig, LogLevel, BuildTarget, LogFormat } from './schema'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Enhanced Environment Processor that uses dynamic env access
|
|
12
|
-
* Replaces the original EnvironmentProcessor from env.ts
|
|
13
|
-
*/
|
|
14
|
-
export class DynamicEnvironmentProcessor {
|
|
15
|
-
private precedenceMap: Map<string, any> = new Map()
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Process environment variables using dynamic runtime access
|
|
19
|
-
* This prevents Bun from fixing env values during build
|
|
20
|
-
*/
|
|
21
|
-
processEnvironmentVariables(): Partial<FluxStackConfig> {
|
|
22
|
-
const config: any = {}
|
|
23
|
-
|
|
24
|
-
// App configuration
|
|
25
|
-
this.setConfigValue(config, 'app.name',
|
|
26
|
-
env.get('FLUXSTACK_APP_NAME') || env.get('APP_NAME'), 'string')
|
|
27
|
-
this.setConfigValue(config, 'app.version',
|
|
28
|
-
env.get('FLUXSTACK_APP_VERSION') || env.get('APP_VERSION'), 'string')
|
|
29
|
-
this.setConfigValue(config, 'app.description',
|
|
30
|
-
env.get('FLUXSTACK_APP_DESCRIPTION') || env.get('APP_DESCRIPTION'), 'string')
|
|
31
|
-
|
|
32
|
-
// Server configuration
|
|
33
|
-
this.setConfigValue(config, 'server.port',
|
|
34
|
-
env.get('PORT') || env.get('FLUXSTACK_PORT'), 'number')
|
|
35
|
-
this.setConfigValue(config, 'server.host',
|
|
36
|
-
env.get('HOST') || env.get('FLUXSTACK_HOST'), 'string')
|
|
37
|
-
this.setConfigValue(config, 'server.apiPrefix',
|
|
38
|
-
env.get('FLUXSTACK_API_PREFIX') || env.get('API_PREFIX'), 'string')
|
|
39
|
-
|
|
40
|
-
// CORS configuration
|
|
41
|
-
this.setConfigValue(config, 'server.cors.origins',
|
|
42
|
-
env.get('CORS_ORIGINS') || env.get('FLUXSTACK_CORS_ORIGINS'), 'array')
|
|
43
|
-
this.setConfigValue(config, 'server.cors.methods',
|
|
44
|
-
env.get('CORS_METHODS') || env.get('FLUXSTACK_CORS_METHODS'), 'array')
|
|
45
|
-
this.setConfigValue(config, 'server.cors.headers',
|
|
46
|
-
env.get('CORS_HEADERS') || env.get('FLUXSTACK_CORS_HEADERS'), 'array')
|
|
47
|
-
this.setConfigValue(config, 'server.cors.credentials',
|
|
48
|
-
env.get('CORS_CREDENTIALS') || env.get('FLUXSTACK_CORS_CREDENTIALS'), 'boolean')
|
|
49
|
-
this.setConfigValue(config, 'server.cors.maxAge',
|
|
50
|
-
env.get('CORS_MAX_AGE') || env.get('FLUXSTACK_CORS_MAX_AGE'), 'number')
|
|
51
|
-
|
|
52
|
-
// Client configuration
|
|
53
|
-
this.setConfigValue(config, 'client.port',
|
|
54
|
-
env.get('VITE_PORT') || env.get('CLIENT_PORT') || env.get('FLUXSTACK_CLIENT_PORT'), 'number')
|
|
55
|
-
this.setConfigValue(config, 'client.proxy.target',
|
|
56
|
-
env.get('VITE_API_URL') || env.get('API_URL') || env.get('FLUXSTACK_PROXY_TARGET'), 'string')
|
|
57
|
-
this.setConfigValue(config, 'client.build.sourceMaps',
|
|
58
|
-
env.get('FLUXSTACK_CLIENT_SOURCEMAPS'), 'boolean')
|
|
59
|
-
this.setConfigValue(config, 'client.build.minify',
|
|
60
|
-
env.get('FLUXSTACK_CLIENT_MINIFY'), 'boolean')
|
|
61
|
-
|
|
62
|
-
// Build configuration
|
|
63
|
-
this.setConfigValue(config, 'build.target',
|
|
64
|
-
env.get('BUILD_TARGET') || env.get('FLUXSTACK_BUILD_TARGET'), 'buildTarget')
|
|
65
|
-
this.setConfigValue(config, 'build.outDir',
|
|
66
|
-
env.get('BUILD_OUTDIR') || env.get('FLUXSTACK_BUILD_OUTDIR'), 'string')
|
|
67
|
-
this.setConfigValue(config, 'build.sourceMaps',
|
|
68
|
-
env.get('BUILD_SOURCEMAPS') || env.get('FLUXSTACK_BUILD_SOURCEMAPS'), 'boolean')
|
|
69
|
-
this.setConfigValue(config, 'build.clean',
|
|
70
|
-
env.get('BUILD_CLEAN') || env.get('FLUXSTACK_BUILD_CLEAN'), 'boolean')
|
|
71
|
-
|
|
72
|
-
// Build optimization
|
|
73
|
-
this.setConfigValue(config, 'build.optimization.minify',
|
|
74
|
-
env.get('BUILD_MINIFY') || env.get('FLUXSTACK_BUILD_MINIFY'), 'boolean')
|
|
75
|
-
this.setConfigValue(config, 'build.optimization.treeshake',
|
|
76
|
-
env.get('BUILD_TREESHAKE') || env.get('FLUXSTACK_BUILD_TREESHAKE'), 'boolean')
|
|
77
|
-
this.setConfigValue(config, 'build.optimization.compress',
|
|
78
|
-
env.get('BUILD_COMPRESS') || env.get('FLUXSTACK_BUILD_COMPRESS'), 'boolean')
|
|
79
|
-
this.setConfigValue(config, 'build.optimization.splitChunks',
|
|
80
|
-
env.get('BUILD_SPLIT_CHUNKS') || env.get('FLUXSTACK_BUILD_SPLIT_CHUNKS'), 'boolean')
|
|
81
|
-
this.setConfigValue(config, 'build.optimization.bundleAnalyzer',
|
|
82
|
-
env.get('BUILD_ANALYZER') || env.get('FLUXSTACK_BUILD_ANALYZER'), 'boolean')
|
|
83
|
-
|
|
84
|
-
// Logging configuration
|
|
85
|
-
this.setConfigValue(config, 'logging.level',
|
|
86
|
-
env.get('LOG_LEVEL') || env.get('FLUXSTACK_LOG_LEVEL'), 'logLevel')
|
|
87
|
-
this.setConfigValue(config, 'logging.format',
|
|
88
|
-
env.get('LOG_FORMAT') || env.get('FLUXSTACK_LOG_FORMAT'), 'logFormat')
|
|
89
|
-
|
|
90
|
-
// Monitoring configuration
|
|
91
|
-
this.setConfigValue(config, 'monitoring.enabled',
|
|
92
|
-
env.get('MONITORING_ENABLED') || env.get('FLUXSTACK_MONITORING_ENABLED'), 'boolean')
|
|
93
|
-
this.setConfigValue(config, 'monitoring.metrics.enabled',
|
|
94
|
-
env.get('METRICS_ENABLED') || env.get('FLUXSTACK_METRICS_ENABLED'), 'boolean')
|
|
95
|
-
this.setConfigValue(config, 'monitoring.metrics.collectInterval',
|
|
96
|
-
env.get('METRICS_INTERVAL') || env.get('FLUXSTACK_METRICS_INTERVAL'), 'number')
|
|
97
|
-
this.setConfigValue(config, 'monitoring.profiling.enabled',
|
|
98
|
-
env.get('PROFILING_ENABLED') || env.get('FLUXSTACK_PROFILING_ENABLED'), 'boolean')
|
|
99
|
-
this.setConfigValue(config, 'monitoring.profiling.sampleRate',
|
|
100
|
-
env.get('PROFILING_SAMPLE_RATE') || env.get('FLUXSTACK_PROFILING_SAMPLE_RATE'), 'number')
|
|
101
|
-
|
|
102
|
-
// Database configuration
|
|
103
|
-
this.setConfigValue(config, 'database.url', env.get('DATABASE_URL'), 'string')
|
|
104
|
-
this.setConfigValue(config, 'database.host', env.get('DATABASE_HOST'), 'string')
|
|
105
|
-
this.setConfigValue(config, 'database.port', env.get('DATABASE_PORT'), 'number')
|
|
106
|
-
this.setConfigValue(config, 'database.database', env.get('DATABASE_NAME'), 'string')
|
|
107
|
-
this.setConfigValue(config, 'database.user', env.get('DATABASE_USER'), 'string')
|
|
108
|
-
this.setConfigValue(config, 'database.password', env.get('DATABASE_PASSWORD'), 'string')
|
|
109
|
-
this.setConfigValue(config, 'database.ssl', env.get('DATABASE_SSL'), 'boolean')
|
|
110
|
-
this.setConfigValue(config, 'database.poolSize', env.get('DATABASE_POOL_SIZE'), 'number')
|
|
111
|
-
|
|
112
|
-
// Auth configuration
|
|
113
|
-
this.setConfigValue(config, 'auth.secret', env.get('JWT_SECRET'), 'string')
|
|
114
|
-
this.setConfigValue(config, 'auth.expiresIn', env.get('JWT_EXPIRES_IN'), 'string')
|
|
115
|
-
this.setConfigValue(config, 'auth.algorithm', env.get('JWT_ALGORITHM'), 'string')
|
|
116
|
-
this.setConfigValue(config, 'auth.issuer', env.get('JWT_ISSUER'), 'string')
|
|
117
|
-
|
|
118
|
-
// Email configuration
|
|
119
|
-
this.setConfigValue(config, 'email.host', env.get('SMTP_HOST'), 'string')
|
|
120
|
-
this.setConfigValue(config, 'email.port', env.get('SMTP_PORT'), 'number')
|
|
121
|
-
this.setConfigValue(config, 'email.user', env.get('SMTP_USER'), 'string')
|
|
122
|
-
this.setConfigValue(config, 'email.password', env.get('SMTP_PASSWORD'), 'string')
|
|
123
|
-
this.setConfigValue(config, 'email.secure', env.get('SMTP_SECURE'), 'boolean')
|
|
124
|
-
this.setConfigValue(config, 'email.from', env.get('SMTP_FROM'), 'string')
|
|
125
|
-
|
|
126
|
-
// Storage configuration
|
|
127
|
-
this.setConfigValue(config, 'storage.uploadPath', env.get('UPLOAD_PATH'), 'string')
|
|
128
|
-
this.setConfigValue(config, 'storage.maxFileSize', env.get('MAX_FILE_SIZE'), 'number')
|
|
129
|
-
this.setConfigValue(config, 'storage.provider', env.get('STORAGE_PROVIDER'), 'string')
|
|
130
|
-
|
|
131
|
-
// Plugin configuration
|
|
132
|
-
this.setConfigValue(config, 'plugins.enabled',
|
|
133
|
-
env.get('FLUXSTACK_PLUGINS_ENABLED'), 'array')
|
|
134
|
-
this.setConfigValue(config, 'plugins.disabled',
|
|
135
|
-
env.get('FLUXSTACK_PLUGINS_DISABLED'), 'array')
|
|
136
|
-
|
|
137
|
-
return this.cleanEmptyObjects(config)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private setConfigValue(
|
|
141
|
-
config: any,
|
|
142
|
-
path: string,
|
|
143
|
-
value: string | undefined,
|
|
144
|
-
type: string
|
|
145
|
-
): void {
|
|
146
|
-
if (value === undefined || value === '') return
|
|
147
|
-
|
|
148
|
-
const convertedValue = this.convertValue(value, type)
|
|
149
|
-
if (convertedValue !== undefined) {
|
|
150
|
-
this.setNestedProperty(config, path, convertedValue)
|
|
151
|
-
|
|
152
|
-
// Track precedence
|
|
153
|
-
this.precedenceMap.set(path, {
|
|
154
|
-
source: 'environment',
|
|
155
|
-
path,
|
|
156
|
-
value: convertedValue,
|
|
157
|
-
priority: 3
|
|
158
|
-
})
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
private convertValue(value: string, type: string): any {
|
|
163
|
-
switch (type) {
|
|
164
|
-
case 'string':
|
|
165
|
-
return value
|
|
166
|
-
case 'number':
|
|
167
|
-
const num = parseInt(value, 10)
|
|
168
|
-
return isNaN(num) ? undefined : num
|
|
169
|
-
case 'boolean':
|
|
170
|
-
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase())
|
|
171
|
-
case 'array':
|
|
172
|
-
return value.split(',').map(v => v.trim()).filter(Boolean)
|
|
173
|
-
case 'logLevel':
|
|
174
|
-
const level = value.toLowerCase() as LogLevel
|
|
175
|
-
return ['debug', 'info', 'warn', 'error'].includes(level) ? level : 'info'
|
|
176
|
-
case 'buildTarget':
|
|
177
|
-
const target = value.toLowerCase() as BuildTarget
|
|
178
|
-
return ['bun', 'node', 'docker'].includes(target) ? target : 'bun'
|
|
179
|
-
case 'logFormat':
|
|
180
|
-
const format = value.toLowerCase() as LogFormat
|
|
181
|
-
return ['json', 'pretty'].includes(format) ? format : 'pretty'
|
|
182
|
-
case 'object':
|
|
183
|
-
try {
|
|
184
|
-
return JSON.parse(value)
|
|
185
|
-
} catch {
|
|
186
|
-
return {}
|
|
187
|
-
}
|
|
188
|
-
default:
|
|
189
|
-
return value
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
private setNestedProperty(obj: any, path: string, value: any): void {
|
|
194
|
-
const keys = path.split('.')
|
|
195
|
-
let current = obj
|
|
196
|
-
|
|
197
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
198
|
-
const key = keys[i]
|
|
199
|
-
if (!(key in current) || typeof current[key] !== 'object') {
|
|
200
|
-
current[key] = {}
|
|
201
|
-
}
|
|
202
|
-
current = current[key]
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
current[keys[keys.length - 1]] = value
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
private cleanEmptyObjects(obj: any): any {
|
|
209
|
-
if (typeof obj !== 'object' || obj === null) return obj
|
|
210
|
-
|
|
211
|
-
const cleaned: any = {}
|
|
212
|
-
|
|
213
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
214
|
-
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
215
|
-
const cleanedValue = this.cleanEmptyObjects(value)
|
|
216
|
-
if (Object.keys(cleanedValue).length > 0) {
|
|
217
|
-
cleaned[key] = cleanedValue
|
|
218
|
-
}
|
|
219
|
-
} else if (value !== undefined && value !== null) {
|
|
220
|
-
cleaned[key] = value
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return cleaned
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
getPrecedenceInfo(): Map<string, any> {
|
|
228
|
-
return new Map(this.precedenceMap)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
clearPrecedence(): void {
|
|
232
|
-
this.precedenceMap.clear()
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Enhanced environment info with dynamic access
|
|
238
|
-
*/
|
|
239
|
-
export function getDynamicEnvironmentInfo() {
|
|
240
|
-
const nodeEnv = env.get('NODE_ENV', 'development') as 'development' | 'production' | 'test'
|
|
241
|
-
|
|
242
|
-
return {
|
|
243
|
-
name: nodeEnv,
|
|
244
|
-
isDevelopment: nodeEnv === 'development',
|
|
245
|
-
isProduction: nodeEnv === 'production',
|
|
246
|
-
isTest: nodeEnv === 'test',
|
|
247
|
-
nodeEnv
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Runtime configuration loader that uses dynamic env access
|
|
253
|
-
*/
|
|
254
|
-
export function loadConfigFromDynamicEnv(): Partial<FluxStackConfig> {
|
|
255
|
-
const processor = new DynamicEnvironmentProcessor()
|
|
256
|
-
return processor.processEnvironmentVariables()
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Utility functions for backward compatibility
|
|
261
|
-
*/
|
|
262
|
-
export function isDevelopment(): boolean {
|
|
263
|
-
return getDynamicEnvironmentInfo().isDevelopment
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
export function isProduction(): boolean {
|
|
267
|
-
return getDynamicEnvironmentInfo().isProduction
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export function isTest(): boolean {
|
|
271
|
-
return getDynamicEnvironmentInfo().isTest
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Validate critical environment variables for production
|
|
276
|
-
*/
|
|
277
|
-
export function validateProductionEnv(): void {
|
|
278
|
-
if (isProduction()) {
|
|
279
|
-
const requiredVars = ['NODE_ENV']
|
|
280
|
-
const missingVars = requiredVars.filter(key => !env.has(key))
|
|
281
|
-
|
|
282
|
-
if (missingVars.length > 0) {
|
|
283
|
-
throw new Error(`Missing required production environment variables: ${missingVars.join(', ')}`)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Validate LOG_LEVEL for production
|
|
287
|
-
const logLevel = env.get('LOG_LEVEL')
|
|
288
|
-
if (logLevel === 'debug') {
|
|
289
|
-
console.warn('⚠️ Production environment should not use debug logging')
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Create environment-aware configuration
|
|
296
|
-
*/
|
|
297
|
-
export function createDynamicConfig(): Partial<FluxStackConfig> {
|
|
298
|
-
const envInfo = getDynamicEnvironmentInfo()
|
|
299
|
-
const envConfig = loadConfigFromDynamicEnv()
|
|
300
|
-
|
|
301
|
-
// Add environment-specific defaults
|
|
302
|
-
const config: any = { ...envConfig }
|
|
303
|
-
|
|
304
|
-
// Ensure proper defaults based on environment
|
|
305
|
-
if (envInfo.isDevelopment) {
|
|
306
|
-
config.logging = {
|
|
307
|
-
level: env.get('LOG_LEVEL', 'debug'),
|
|
308
|
-
format: env.get('LOG_FORMAT', 'pretty'),
|
|
309
|
-
...config.logging
|
|
310
|
-
}
|
|
311
|
-
} else if (envInfo.isProduction) {
|
|
312
|
-
config.logging = {
|
|
313
|
-
level: env.get('LOG_LEVEL', 'warn'),
|
|
314
|
-
format: env.get('LOG_FORMAT', 'json'),
|
|
315
|
-
...config.logging
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return config
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Export singleton instance
|
|
323
|
-
export const dynamicEnvironmentProcessor = new DynamicEnvironmentProcessor()
|
|
324
|
-
|
|
325
|
-
// Export runtime environment access
|
|
326
|
-
export { env, runtimeEnv, envValidation } from '../utils/env-runtime'
|