create-fluxstack 1.0.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/.env +30 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/app/client/README.md +69 -0
- package/app/client/frontend-only.ts +12 -0
- package/app/client/index.html +13 -0
- package/app/client/public/vite.svg +1 -0
- package/app/client/src/App.css +883 -0
- package/app/client/src/App.tsx +669 -0
- package/app/client/src/assets/react.svg +1 -0
- package/app/client/src/components/TestPage.tsx +453 -0
- package/app/client/src/index.css +51 -0
- package/app/client/src/lib/eden-api.ts +110 -0
- package/app/client/src/main.tsx +10 -0
- package/app/client/src/vite-env.d.ts +1 -0
- package/app/client/tsconfig.app.json +43 -0
- package/app/client/tsconfig.json +7 -0
- package/app/client/tsconfig.node.json +25 -0
- package/app/server/app.ts +10 -0
- package/app/server/backend-only.ts +15 -0
- package/app/server/controllers/users.controller.ts +69 -0
- package/app/server/index.ts +104 -0
- package/app/server/routes/index.ts +25 -0
- package/app/server/routes/users.routes.ts +121 -0
- package/app/server/types/index.ts +1 -0
- package/app/shared/types/index.ts +18 -0
- package/bun.lock +1053 -0
- package/core/__tests__/integration.test.ts +227 -0
- package/core/build/index.ts +186 -0
- package/core/cli/command-registry.ts +334 -0
- package/core/cli/index.ts +394 -0
- package/core/cli/plugin-discovery.ts +200 -0
- package/core/client/standalone.ts +57 -0
- package/core/config/__tests__/config-loader.test.ts +591 -0
- package/core/config/__tests__/config-merger.test.ts +657 -0
- package/core/config/__tests__/env-converter.test.ts +372 -0
- package/core/config/__tests__/env-processor.test.ts +431 -0
- package/core/config/__tests__/env.test.ts +452 -0
- package/core/config/__tests__/integration.test.ts +418 -0
- package/core/config/__tests__/loader.test.ts +331 -0
- package/core/config/__tests__/schema.test.ts +129 -0
- package/core/config/__tests__/validator.test.ts +318 -0
- package/core/config/env-dynamic.ts +326 -0
- package/core/config/env.ts +597 -0
- package/core/config/index.ts +317 -0
- package/core/config/loader.ts +546 -0
- package/core/config/runtime-config.ts +322 -0
- package/core/config/schema.ts +694 -0
- package/core/config/validator.ts +540 -0
- package/core/framework/__tests__/server.test.ts +233 -0
- package/core/framework/client.ts +132 -0
- package/core/framework/index.ts +8 -0
- package/core/framework/server.ts +501 -0
- package/core/framework/types.ts +63 -0
- package/core/plugins/__tests__/built-in.test.ts.disabled +366 -0
- package/core/plugins/__tests__/manager.test.ts +398 -0
- package/core/plugins/__tests__/monitoring.test.ts +401 -0
- package/core/plugins/__tests__/registry.test.ts +335 -0
- package/core/plugins/built-in/index.ts +142 -0
- package/core/plugins/built-in/logger/index.ts +180 -0
- package/core/plugins/built-in/monitoring/README.md +193 -0
- package/core/plugins/built-in/monitoring/index.ts +912 -0
- package/core/plugins/built-in/static/index.ts +289 -0
- package/core/plugins/built-in/swagger/index.ts +229 -0
- package/core/plugins/built-in/vite/index.ts +316 -0
- package/core/plugins/config.ts +348 -0
- package/core/plugins/discovery.ts +350 -0
- package/core/plugins/executor.ts +351 -0
- package/core/plugins/index.ts +195 -0
- package/core/plugins/manager.ts +583 -0
- package/core/plugins/registry.ts +424 -0
- package/core/plugins/types.ts +254 -0
- package/core/server/framework.ts +123 -0
- package/core/server/index.ts +8 -0
- package/core/server/plugins/database.ts +182 -0
- package/core/server/plugins/logger.ts +47 -0
- package/core/server/plugins/swagger.ts +34 -0
- package/core/server/standalone.ts +91 -0
- package/core/templates/create-project.ts +455 -0
- package/core/types/api.ts +169 -0
- package/core/types/build.ts +174 -0
- package/core/types/config.ts +68 -0
- package/core/types/index.ts +127 -0
- package/core/types/plugin.ts +94 -0
- package/core/utils/__tests__/errors.test.ts +139 -0
- package/core/utils/__tests__/helpers.test.ts +297 -0
- package/core/utils/__tests__/logger.test.ts +141 -0
- package/core/utils/env-runtime-v2.ts +232 -0
- package/core/utils/env-runtime.ts +252 -0
- package/core/utils/errors/codes.ts +115 -0
- package/core/utils/errors/handlers.ts +63 -0
- package/core/utils/errors/index.ts +81 -0
- package/core/utils/helpers.ts +180 -0
- package/core/utils/index.ts +18 -0
- package/core/utils/logger/index.ts +161 -0
- package/core/utils/logger.ts +106 -0
- package/core/utils/monitoring/index.ts +212 -0
- package/create-fluxstack.ts +231 -0
- package/package.json +43 -0
- package/tsconfig.json +51 -0
- package/vite.config.ts +42 -0
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Environment Configuration System for FluxStack
|
|
3
|
+
* Handles environment variable processing and precedence
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FluxStackConfig, LogLevel, BuildTarget, LogFormat } from './schema'
|
|
7
|
+
|
|
8
|
+
export interface EnvironmentInfo {
|
|
9
|
+
name: string
|
|
10
|
+
isDevelopment: boolean
|
|
11
|
+
isProduction: boolean
|
|
12
|
+
isTest: boolean
|
|
13
|
+
nodeEnv: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ConfigPrecedence {
|
|
17
|
+
source: 'default' | 'file' | 'environment' | 'override'
|
|
18
|
+
path: string
|
|
19
|
+
value: any
|
|
20
|
+
priority: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get current environment information
|
|
25
|
+
*/
|
|
26
|
+
export function getEnvironmentInfo(): EnvironmentInfo {
|
|
27
|
+
const nodeEnv = process.env.NODE_ENV || 'development'
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
name: nodeEnv,
|
|
31
|
+
isDevelopment: nodeEnv === 'development',
|
|
32
|
+
isProduction: nodeEnv === 'production',
|
|
33
|
+
isTest: nodeEnv === 'test',
|
|
34
|
+
nodeEnv
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Environment variable type conversion utilities
|
|
40
|
+
*/
|
|
41
|
+
export class EnvConverter {
|
|
42
|
+
static toNumber(value: string | undefined, defaultValue: number): number {
|
|
43
|
+
if (!value) return defaultValue
|
|
44
|
+
const parsed = parseInt(value, 10)
|
|
45
|
+
return isNaN(parsed) ? defaultValue : parsed
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static toBoolean(value: string | undefined, defaultValue: boolean): boolean {
|
|
49
|
+
if (!value) return defaultValue
|
|
50
|
+
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase())
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static toArray(value: string | undefined, defaultValue: string[] = []): string[] {
|
|
54
|
+
if (!value) return defaultValue
|
|
55
|
+
return value.split(',').map(v => v.trim()).filter(Boolean)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static toLogLevel(value: string | undefined, defaultValue: LogLevel): LogLevel {
|
|
59
|
+
if (!value) return defaultValue
|
|
60
|
+
const level = value.toLowerCase() as LogLevel
|
|
61
|
+
return ['debug', 'info', 'warn', 'error'].includes(level) ? level : defaultValue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static toBuildTarget(value: string | undefined, defaultValue: BuildTarget): BuildTarget {
|
|
65
|
+
if (!value) return defaultValue
|
|
66
|
+
const target = value.toLowerCase() as BuildTarget
|
|
67
|
+
return ['bun', 'node', 'docker'].includes(target) ? target : defaultValue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static toLogFormat(value: string | undefined, defaultValue: LogFormat): LogFormat {
|
|
71
|
+
if (!value) return defaultValue
|
|
72
|
+
const format = value.toLowerCase() as LogFormat
|
|
73
|
+
return ['json', 'pretty'].includes(format) ? format : defaultValue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static toObject<T = any>(value: string | undefined, defaultValue: T): T {
|
|
77
|
+
if (!value) return defaultValue
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(value)
|
|
80
|
+
} catch {
|
|
81
|
+
return defaultValue
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Environment variable processor with precedence handling
|
|
88
|
+
*/
|
|
89
|
+
export class EnvironmentProcessor {
|
|
90
|
+
private precedenceMap: Map<string, ConfigPrecedence> = new Map()
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Process environment variables with type conversion and precedence tracking
|
|
94
|
+
*/
|
|
95
|
+
processEnvironmentVariables(): Partial<FluxStackConfig> {
|
|
96
|
+
const config: any = {}
|
|
97
|
+
|
|
98
|
+
// App configuration
|
|
99
|
+
this.setConfigValue(config, 'app.name',
|
|
100
|
+
process.env.FLUXSTACK_APP_NAME || process.env.APP_NAME, 'string')
|
|
101
|
+
this.setConfigValue(config, 'app.version',
|
|
102
|
+
process.env.FLUXSTACK_APP_VERSION || process.env.APP_VERSION, 'string')
|
|
103
|
+
this.setConfigValue(config, 'app.description',
|
|
104
|
+
process.env.FLUXSTACK_APP_DESCRIPTION || process.env.APP_DESCRIPTION, 'string')
|
|
105
|
+
|
|
106
|
+
// Server configuration
|
|
107
|
+
this.setConfigValue(config, 'server.port',
|
|
108
|
+
process.env.PORT || process.env.FLUXSTACK_PORT, 'number')
|
|
109
|
+
this.setConfigValue(config, 'server.host',
|
|
110
|
+
process.env.HOST || process.env.FLUXSTACK_HOST, 'string')
|
|
111
|
+
this.setConfigValue(config, 'server.apiPrefix',
|
|
112
|
+
process.env.FLUXSTACK_API_PREFIX || process.env.API_PREFIX, 'string')
|
|
113
|
+
|
|
114
|
+
// CORS configuration
|
|
115
|
+
this.setConfigValue(config, 'server.cors.origins',
|
|
116
|
+
process.env.CORS_ORIGINS || process.env.FLUXSTACK_CORS_ORIGINS, 'array')
|
|
117
|
+
this.setConfigValue(config, 'server.cors.methods',
|
|
118
|
+
process.env.CORS_METHODS || process.env.FLUXSTACK_CORS_METHODS, 'array')
|
|
119
|
+
this.setConfigValue(config, 'server.cors.headers',
|
|
120
|
+
process.env.CORS_HEADERS || process.env.FLUXSTACK_CORS_HEADERS, 'array')
|
|
121
|
+
this.setConfigValue(config, 'server.cors.credentials',
|
|
122
|
+
process.env.CORS_CREDENTIALS || process.env.FLUXSTACK_CORS_CREDENTIALS, 'boolean')
|
|
123
|
+
this.setConfigValue(config, 'server.cors.maxAge',
|
|
124
|
+
process.env.CORS_MAX_AGE || process.env.FLUXSTACK_CORS_MAX_AGE, 'number')
|
|
125
|
+
|
|
126
|
+
// Client configuration
|
|
127
|
+
this.setConfigValue(config, 'client.port',
|
|
128
|
+
process.env.VITE_PORT || process.env.CLIENT_PORT || process.env.FLUXSTACK_CLIENT_PORT, 'number')
|
|
129
|
+
this.setConfigValue(config, 'client.proxy.target',
|
|
130
|
+
process.env.VITE_API_URL || process.env.API_URL || process.env.FLUXSTACK_PROXY_TARGET, 'string')
|
|
131
|
+
this.setConfigValue(config, 'client.build.sourceMaps',
|
|
132
|
+
process.env.FLUXSTACK_CLIENT_SOURCEMAPS, 'boolean')
|
|
133
|
+
this.setConfigValue(config, 'client.build.minify',
|
|
134
|
+
process.env.FLUXSTACK_CLIENT_MINIFY, 'boolean')
|
|
135
|
+
|
|
136
|
+
// Build configuration
|
|
137
|
+
this.setConfigValue(config, 'build.target',
|
|
138
|
+
process.env.BUILD_TARGET || process.env.FLUXSTACK_BUILD_TARGET, 'buildTarget')
|
|
139
|
+
this.setConfigValue(config, 'build.outDir',
|
|
140
|
+
process.env.BUILD_OUTDIR || process.env.FLUXSTACK_BUILD_OUTDIR, 'string')
|
|
141
|
+
this.setConfigValue(config, 'build.sourceMaps',
|
|
142
|
+
process.env.BUILD_SOURCEMAPS || process.env.FLUXSTACK_BUILD_SOURCEMAPS, 'boolean')
|
|
143
|
+
this.setConfigValue(config, 'build.clean',
|
|
144
|
+
process.env.BUILD_CLEAN || process.env.FLUXSTACK_BUILD_CLEAN, 'boolean')
|
|
145
|
+
|
|
146
|
+
// Build optimization
|
|
147
|
+
this.setConfigValue(config, 'build.optimization.minify',
|
|
148
|
+
process.env.BUILD_MINIFY || process.env.FLUXSTACK_BUILD_MINIFY, 'boolean')
|
|
149
|
+
this.setConfigValue(config, 'build.optimization.treeshake',
|
|
150
|
+
process.env.BUILD_TREESHAKE || process.env.FLUXSTACK_BUILD_TREESHAKE, 'boolean')
|
|
151
|
+
this.setConfigValue(config, 'build.optimization.compress',
|
|
152
|
+
process.env.BUILD_COMPRESS || process.env.FLUXSTACK_BUILD_COMPRESS, 'boolean')
|
|
153
|
+
this.setConfigValue(config, 'build.optimization.splitChunks',
|
|
154
|
+
process.env.BUILD_SPLIT_CHUNKS || process.env.FLUXSTACK_BUILD_SPLIT_CHUNKS, 'boolean')
|
|
155
|
+
this.setConfigValue(config, 'build.optimization.bundleAnalyzer',
|
|
156
|
+
process.env.BUILD_ANALYZER || process.env.FLUXSTACK_BUILD_ANALYZER, 'boolean')
|
|
157
|
+
|
|
158
|
+
// Logging configuration
|
|
159
|
+
this.setConfigValue(config, 'logging.level',
|
|
160
|
+
process.env.LOG_LEVEL || process.env.FLUXSTACK_LOG_LEVEL, 'logLevel')
|
|
161
|
+
this.setConfigValue(config, 'logging.format',
|
|
162
|
+
process.env.LOG_FORMAT || process.env.FLUXSTACK_LOG_FORMAT, 'logFormat')
|
|
163
|
+
|
|
164
|
+
// Monitoring configuration
|
|
165
|
+
this.setConfigValue(config, 'monitoring.enabled',
|
|
166
|
+
process.env.MONITORING_ENABLED || process.env.FLUXSTACK_MONITORING_ENABLED, 'boolean')
|
|
167
|
+
this.setConfigValue(config, 'monitoring.metrics.enabled',
|
|
168
|
+
process.env.METRICS_ENABLED || process.env.FLUXSTACK_METRICS_ENABLED, 'boolean')
|
|
169
|
+
this.setConfigValue(config, 'monitoring.metrics.collectInterval',
|
|
170
|
+
process.env.METRICS_INTERVAL || process.env.FLUXSTACK_METRICS_INTERVAL, 'number')
|
|
171
|
+
this.setConfigValue(config, 'monitoring.profiling.enabled',
|
|
172
|
+
process.env.PROFILING_ENABLED || process.env.FLUXSTACK_PROFILING_ENABLED, 'boolean')
|
|
173
|
+
this.setConfigValue(config, 'monitoring.profiling.sampleRate',
|
|
174
|
+
process.env.PROFILING_SAMPLE_RATE || process.env.FLUXSTACK_PROFILING_SAMPLE_RATE, 'number')
|
|
175
|
+
|
|
176
|
+
// Database configuration
|
|
177
|
+
this.setConfigValue(config, 'database.url', process.env.DATABASE_URL, 'string')
|
|
178
|
+
this.setConfigValue(config, 'database.host', process.env.DATABASE_HOST, 'string')
|
|
179
|
+
this.setConfigValue(config, 'database.port', process.env.DATABASE_PORT, 'number')
|
|
180
|
+
this.setConfigValue(config, 'database.database', process.env.DATABASE_NAME, 'string')
|
|
181
|
+
this.setConfigValue(config, 'database.user', process.env.DATABASE_USER, 'string')
|
|
182
|
+
this.setConfigValue(config, 'database.password', process.env.DATABASE_PASSWORD, 'string')
|
|
183
|
+
this.setConfigValue(config, 'database.ssl', process.env.DATABASE_SSL, 'boolean')
|
|
184
|
+
this.setConfigValue(config, 'database.poolSize', process.env.DATABASE_POOL_SIZE, 'number')
|
|
185
|
+
|
|
186
|
+
// Auth configuration
|
|
187
|
+
this.setConfigValue(config, 'auth.secret', process.env.JWT_SECRET, 'string')
|
|
188
|
+
this.setConfigValue(config, 'auth.expiresIn', process.env.JWT_EXPIRES_IN, 'string')
|
|
189
|
+
this.setConfigValue(config, 'auth.algorithm', process.env.JWT_ALGORITHM, 'string')
|
|
190
|
+
this.setConfigValue(config, 'auth.issuer', process.env.JWT_ISSUER, 'string')
|
|
191
|
+
|
|
192
|
+
// Email configuration
|
|
193
|
+
this.setConfigValue(config, 'email.host', process.env.SMTP_HOST, 'string')
|
|
194
|
+
this.setConfigValue(config, 'email.port', process.env.SMTP_PORT, 'number')
|
|
195
|
+
this.setConfigValue(config, 'email.user', process.env.SMTP_USER, 'string')
|
|
196
|
+
this.setConfigValue(config, 'email.password', process.env.SMTP_PASSWORD, 'string')
|
|
197
|
+
this.setConfigValue(config, 'email.secure', process.env.SMTP_SECURE, 'boolean')
|
|
198
|
+
this.setConfigValue(config, 'email.from', process.env.SMTP_FROM, 'string')
|
|
199
|
+
|
|
200
|
+
// Storage configuration
|
|
201
|
+
this.setConfigValue(config, 'storage.uploadPath', process.env.UPLOAD_PATH, 'string')
|
|
202
|
+
this.setConfigValue(config, 'storage.maxFileSize', process.env.MAX_FILE_SIZE, 'number')
|
|
203
|
+
this.setConfigValue(config, 'storage.provider', process.env.STORAGE_PROVIDER, 'string')
|
|
204
|
+
|
|
205
|
+
// Plugin configuration
|
|
206
|
+
this.setConfigValue(config, 'plugins.enabled',
|
|
207
|
+
process.env.FLUXSTACK_PLUGINS_ENABLED, 'array')
|
|
208
|
+
this.setConfigValue(config, 'plugins.disabled',
|
|
209
|
+
process.env.FLUXSTACK_PLUGINS_DISABLED, 'array')
|
|
210
|
+
|
|
211
|
+
return this.cleanEmptyObjects(config)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private setConfigValue(
|
|
215
|
+
config: any,
|
|
216
|
+
path: string,
|
|
217
|
+
value: string | undefined,
|
|
218
|
+
type: string
|
|
219
|
+
): void {
|
|
220
|
+
if (value === undefined) return
|
|
221
|
+
|
|
222
|
+
const convertedValue = this.convertValue(value, type)
|
|
223
|
+
if (convertedValue !== undefined) {
|
|
224
|
+
this.setNestedProperty(config, path, convertedValue)
|
|
225
|
+
|
|
226
|
+
// Track precedence
|
|
227
|
+
this.precedenceMap.set(path, {
|
|
228
|
+
source: 'environment',
|
|
229
|
+
path,
|
|
230
|
+
value: convertedValue,
|
|
231
|
+
priority: 3 // Environment variables have high priority
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private convertValue(value: string, type: string): any {
|
|
237
|
+
switch (type) {
|
|
238
|
+
case 'string':
|
|
239
|
+
return value
|
|
240
|
+
case 'number':
|
|
241
|
+
return EnvConverter.toNumber(value, 0)
|
|
242
|
+
case 'boolean':
|
|
243
|
+
const boolValue = EnvConverter.toBoolean(value, false)
|
|
244
|
+
return boolValue
|
|
245
|
+
case 'array':
|
|
246
|
+
return EnvConverter.toArray(value)
|
|
247
|
+
case 'logLevel':
|
|
248
|
+
return EnvConverter.toLogLevel(value, 'info')
|
|
249
|
+
case 'buildTarget':
|
|
250
|
+
return EnvConverter.toBuildTarget(value, 'bun')
|
|
251
|
+
case 'logFormat':
|
|
252
|
+
return EnvConverter.toLogFormat(value, 'pretty')
|
|
253
|
+
case 'object':
|
|
254
|
+
return EnvConverter.toObject(value, {})
|
|
255
|
+
default:
|
|
256
|
+
return value
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private setNestedProperty(obj: any, path: string, value: any): void {
|
|
261
|
+
const keys = path.split('.')
|
|
262
|
+
let current = obj
|
|
263
|
+
|
|
264
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
265
|
+
const key = keys[i]
|
|
266
|
+
if (!(key in current) || typeof current[key] !== 'object') {
|
|
267
|
+
current[key] = {}
|
|
268
|
+
}
|
|
269
|
+
current = current[key]
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
current[keys[keys.length - 1]] = value
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private cleanEmptyObjects(obj: any): any {
|
|
276
|
+
if (typeof obj !== 'object' || obj === null) return obj
|
|
277
|
+
|
|
278
|
+
const cleaned: any = {}
|
|
279
|
+
|
|
280
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
281
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
282
|
+
const cleanedValue = this.cleanEmptyObjects(value)
|
|
283
|
+
if (Object.keys(cleanedValue).length > 0) {
|
|
284
|
+
cleaned[key] = cleanedValue
|
|
285
|
+
}
|
|
286
|
+
} else if (value !== undefined && value !== null) {
|
|
287
|
+
cleaned[key] = value
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return cleaned
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Get precedence information for configuration values
|
|
296
|
+
*/
|
|
297
|
+
getPrecedenceInfo(): Map<string, ConfigPrecedence> {
|
|
298
|
+
return new Map(this.precedenceMap)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Clear precedence tracking
|
|
303
|
+
*/
|
|
304
|
+
clearPrecedence(): void {
|
|
305
|
+
this.precedenceMap.clear()
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Configuration merger with precedence handling
|
|
311
|
+
*/
|
|
312
|
+
export class ConfigMerger {
|
|
313
|
+
private precedenceOrder = ['default', 'file', 'environment', 'override']
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Merge configurations with precedence handling
|
|
317
|
+
* Higher precedence values override lower ones
|
|
318
|
+
*/
|
|
319
|
+
merge(...configs: Array<{ config: Partial<FluxStackConfig>, source: string }>): FluxStackConfig {
|
|
320
|
+
let result: any = {}
|
|
321
|
+
const precedenceMap: Map<string, ConfigPrecedence> = new Map()
|
|
322
|
+
|
|
323
|
+
// Process configs in precedence order
|
|
324
|
+
for (const { config, source } of configs) {
|
|
325
|
+
this.deepMergeWithPrecedence(result, config, source, precedenceMap)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return result as FluxStackConfig
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private deepMergeWithPrecedence(
|
|
332
|
+
target: any,
|
|
333
|
+
source: any,
|
|
334
|
+
sourceName: string,
|
|
335
|
+
precedenceMap: Map<string, ConfigPrecedence>,
|
|
336
|
+
currentPath = ''
|
|
337
|
+
): void {
|
|
338
|
+
if (!source || typeof source !== 'object') return
|
|
339
|
+
|
|
340
|
+
for (const [key, value] of Object.entries(source)) {
|
|
341
|
+
const fullPath = currentPath ? `${currentPath}.${key}` : key
|
|
342
|
+
const sourcePriority = this.precedenceOrder.indexOf(sourceName)
|
|
343
|
+
|
|
344
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
345
|
+
// Ensure target has the nested object
|
|
346
|
+
if (!(key in target) || typeof target[key] !== 'object') {
|
|
347
|
+
target[key] = {}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Recursively merge nested objects
|
|
351
|
+
this.deepMergeWithPrecedence(target[key], value, sourceName, precedenceMap, fullPath)
|
|
352
|
+
} else {
|
|
353
|
+
// Check precedence before overriding
|
|
354
|
+
const existingPrecedence = precedenceMap.get(fullPath)
|
|
355
|
+
|
|
356
|
+
if (!existingPrecedence || sourcePriority >= existingPrecedence.priority) {
|
|
357
|
+
target[key] = value
|
|
358
|
+
precedenceMap.set(fullPath, {
|
|
359
|
+
source: sourceName as any,
|
|
360
|
+
path: fullPath,
|
|
361
|
+
value,
|
|
362
|
+
priority: sourcePriority
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Environment-specific configuration applier
|
|
372
|
+
*/
|
|
373
|
+
export class EnvironmentConfigApplier {
|
|
374
|
+
/**
|
|
375
|
+
* Apply environment-specific configuration overrides
|
|
376
|
+
*/
|
|
377
|
+
applyEnvironmentConfig(
|
|
378
|
+
baseConfig: FluxStackConfig,
|
|
379
|
+
environment: string
|
|
380
|
+
): FluxStackConfig {
|
|
381
|
+
const envConfig = baseConfig.environments?.[environment]
|
|
382
|
+
|
|
383
|
+
if (!envConfig) {
|
|
384
|
+
return baseConfig
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const merger = new ConfigMerger()
|
|
388
|
+
return merger.merge(
|
|
389
|
+
{ config: baseConfig, source: 'base' },
|
|
390
|
+
{ config: envConfig, source: `environment:${environment}` }
|
|
391
|
+
)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Get available environments from configuration
|
|
396
|
+
*/
|
|
397
|
+
getAvailableEnvironments(config: FluxStackConfig): string[] {
|
|
398
|
+
return config.environments ? Object.keys(config.environments) : []
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Validate environment-specific configuration
|
|
403
|
+
*/
|
|
404
|
+
validateEnvironmentConfig(
|
|
405
|
+
config: FluxStackConfig,
|
|
406
|
+
environment: string
|
|
407
|
+
): { valid: boolean; errors: string[] } {
|
|
408
|
+
const envConfig = config.environments?.[environment]
|
|
409
|
+
|
|
410
|
+
if (!envConfig) {
|
|
411
|
+
return { valid: true, errors: [] }
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const errors: string[] = []
|
|
415
|
+
|
|
416
|
+
// Check for conflicting configurations
|
|
417
|
+
if (envConfig.server?.port === config.server.port && environment !== 'development') {
|
|
418
|
+
errors.push(`Environment ${environment} uses same port as base configuration`)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Check for missing required overrides in production
|
|
422
|
+
if (environment === 'production') {
|
|
423
|
+
if (!envConfig.logging?.level || envConfig.logging.level === 'debug') {
|
|
424
|
+
errors.push('Production environment should not use debug logging')
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (!envConfig.monitoring?.enabled) {
|
|
428
|
+
errors.push('Production environment should enable monitoring')
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
valid: errors.length === 0,
|
|
434
|
+
errors
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Singleton instances for global use
|
|
440
|
+
export const environmentProcessor = new EnvironmentProcessor()
|
|
441
|
+
export const configMerger = new ConfigMerger()
|
|
442
|
+
export const environmentConfigApplier = new EnvironmentConfigApplier()
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Utility functions for backward compatibility
|
|
446
|
+
*/
|
|
447
|
+
export function isDevelopment(): boolean {
|
|
448
|
+
return getEnvironmentInfo().isDevelopment
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function isProduction(): boolean {
|
|
452
|
+
return getEnvironmentInfo().isProduction
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export function isTest(): boolean {
|
|
456
|
+
return getEnvironmentInfo().isTest
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get environment-specific configuration recommendations
|
|
461
|
+
*/
|
|
462
|
+
export function getEnvironmentRecommendations(environment: string): Partial<FluxStackConfig> {
|
|
463
|
+
switch (environment) {
|
|
464
|
+
case 'development':
|
|
465
|
+
return {
|
|
466
|
+
logging: {
|
|
467
|
+
level: 'debug' as const,
|
|
468
|
+
format: 'pretty' as const,
|
|
469
|
+
transports: [{ type: 'console' as const, level: 'debug' as const, format: 'pretty' as const }]
|
|
470
|
+
},
|
|
471
|
+
build: {
|
|
472
|
+
target: 'bun' as const,
|
|
473
|
+
outDir: 'dist',
|
|
474
|
+
clean: true,
|
|
475
|
+
optimization: {
|
|
476
|
+
minify: false,
|
|
477
|
+
compress: false,
|
|
478
|
+
treeshake: false,
|
|
479
|
+
splitChunks: false,
|
|
480
|
+
bundleAnalyzer: false
|
|
481
|
+
},
|
|
482
|
+
sourceMaps: true
|
|
483
|
+
},
|
|
484
|
+
monitoring: {
|
|
485
|
+
enabled: false,
|
|
486
|
+
metrics: {
|
|
487
|
+
enabled: false,
|
|
488
|
+
collectInterval: 60000,
|
|
489
|
+
httpMetrics: true,
|
|
490
|
+
systemMetrics: true,
|
|
491
|
+
customMetrics: false
|
|
492
|
+
},
|
|
493
|
+
profiling: {
|
|
494
|
+
enabled: false,
|
|
495
|
+
sampleRate: 0.1,
|
|
496
|
+
memoryProfiling: false,
|
|
497
|
+
cpuProfiling: false
|
|
498
|
+
},
|
|
499
|
+
exporters: []
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
case 'production':
|
|
504
|
+
return {
|
|
505
|
+
logging: {
|
|
506
|
+
level: 'warn' as const,
|
|
507
|
+
format: 'json' as const,
|
|
508
|
+
transports: [
|
|
509
|
+
{ type: 'console' as const, level: 'warn' as const, format: 'json' as const },
|
|
510
|
+
{ type: 'file' as const, level: 'warn' as const, format: 'json' as const, options: { filename: 'app.log' } }
|
|
511
|
+
]
|
|
512
|
+
},
|
|
513
|
+
build: {
|
|
514
|
+
target: 'bun' as const,
|
|
515
|
+
outDir: 'dist',
|
|
516
|
+
clean: true,
|
|
517
|
+
optimization: {
|
|
518
|
+
minify: true,
|
|
519
|
+
compress: true,
|
|
520
|
+
treeshake: true,
|
|
521
|
+
splitChunks: true,
|
|
522
|
+
bundleAnalyzer: false
|
|
523
|
+
},
|
|
524
|
+
sourceMaps: false
|
|
525
|
+
},
|
|
526
|
+
monitoring: {
|
|
527
|
+
enabled: true,
|
|
528
|
+
metrics: {
|
|
529
|
+
enabled: true,
|
|
530
|
+
collectInterval: 30000,
|
|
531
|
+
httpMetrics: true,
|
|
532
|
+
systemMetrics: true,
|
|
533
|
+
customMetrics: false
|
|
534
|
+
},
|
|
535
|
+
profiling: {
|
|
536
|
+
enabled: true,
|
|
537
|
+
sampleRate: 0.01,
|
|
538
|
+
memoryProfiling: true,
|
|
539
|
+
cpuProfiling: true
|
|
540
|
+
},
|
|
541
|
+
exporters: ['prometheus']
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
case 'test':
|
|
546
|
+
return {
|
|
547
|
+
logging: {
|
|
548
|
+
level: 'error' as const,
|
|
549
|
+
format: 'json' as const,
|
|
550
|
+
transports: [{ type: 'console' as const, level: 'error' as const, format: 'json' as const }]
|
|
551
|
+
},
|
|
552
|
+
server: {
|
|
553
|
+
port: 0, // Random port
|
|
554
|
+
host: 'localhost',
|
|
555
|
+
apiPrefix: '/api',
|
|
556
|
+
cors: {
|
|
557
|
+
origins: ['*'],
|
|
558
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
559
|
+
headers: ['Content-Type', 'Authorization'],
|
|
560
|
+
credentials: false,
|
|
561
|
+
maxAge: 86400
|
|
562
|
+
},
|
|
563
|
+
middleware: []
|
|
564
|
+
},
|
|
565
|
+
client: {
|
|
566
|
+
port: 0,
|
|
567
|
+
proxy: { target: 'http://localhost:3000' },
|
|
568
|
+
build: {
|
|
569
|
+
target: 'es2020' as const,
|
|
570
|
+
outDir: 'dist/client',
|
|
571
|
+
sourceMaps: false,
|
|
572
|
+
minify: false
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
monitoring: {
|
|
576
|
+
enabled: false,
|
|
577
|
+
metrics: {
|
|
578
|
+
enabled: false,
|
|
579
|
+
collectInterval: 60000,
|
|
580
|
+
httpMetrics: true,
|
|
581
|
+
systemMetrics: true,
|
|
582
|
+
customMetrics: false
|
|
583
|
+
},
|
|
584
|
+
profiling: {
|
|
585
|
+
enabled: false,
|
|
586
|
+
sampleRate: 0.1,
|
|
587
|
+
memoryProfiling: false,
|
|
588
|
+
cpuProfiling: false
|
|
589
|
+
},
|
|
590
|
+
exporters: []
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
default:
|
|
595
|
+
return {}
|
|
596
|
+
}
|
|
597
|
+
}
|