@tamyla/clodo-framework 2.0.18 → 2.0.19
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/CHANGELOG.md +9 -0
- package/dist/modules/security.js +24 -22
- package/dist/orchestration/multi-domain-orchestrator.js +33 -0
- package/dist/security/SecurityCLI.js +9 -66
- package/dist/security/index.js +7 -6
- package/dist/service-management/ServiceOrchestrator.js +14 -19
- package/dist/utils/config/unified-config-manager.js +448 -0
- package/dist/utils/deployment/index.js +1 -1
- package/dist/utils/deployment/wrangler-config-manager.js +363 -0
- package/package.json +1 -1
- package/dist/config/ConfigurationManager.js +0 -159
- package/dist/config/CustomerConfigCLI.js +0 -226
- package/dist/config/customer-config-loader.js +0 -247
- package/dist/security/DeploymentManager.js +0 -208
- package/dist/service-management/handlers/ConfigMutator.js +0 -130
- package/dist/utils/deployment/config-persistence.js +0 -347
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unified Configuration Manager
|
|
5
|
+
* Single source of truth for customer .env configuration operations
|
|
6
|
+
*
|
|
7
|
+
* Consolidates functionality from:
|
|
8
|
+
* - CustomerConfigLoader (loading configs)
|
|
9
|
+
* - ConfigPersistenceManager (saving configs)
|
|
10
|
+
*
|
|
11
|
+
* Eliminates duplicate .env parsing and provides clean, unified API
|
|
12
|
+
*
|
|
13
|
+
* @module UnifiedConfigManager
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
16
|
+
import { resolve, join } from 'path';
|
|
17
|
+
import { getDirname } from '../esm-helper.js';
|
|
18
|
+
import { createLogger } from '../index.js';
|
|
19
|
+
const __dirname = getDirname(import.meta.url, 'src/utils/config');
|
|
20
|
+
const logger = createLogger('UnifiedConfigManager');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* UnifiedConfigManager
|
|
24
|
+
* Manages customer configuration loading, saving, and operations
|
|
25
|
+
*/
|
|
26
|
+
export class UnifiedConfigManager {
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.configDir = options.configDir || resolve(__dirname, '..', '..', '..', 'config', 'customers');
|
|
29
|
+
this.verbose = options.verbose || false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Load customer configuration from .env file
|
|
34
|
+
* Returns null if not found or is a template
|
|
35
|
+
*
|
|
36
|
+
* @param {string} customer - Customer name
|
|
37
|
+
* @param {string} environment - Environment (development, staging, production)
|
|
38
|
+
* @returns {Object|null} - Parsed config or null
|
|
39
|
+
*/
|
|
40
|
+
loadCustomerConfig(customer, environment) {
|
|
41
|
+
const configPath = resolve(this.configDir, customer, `${environment}.env`);
|
|
42
|
+
if (!existsSync(configPath)) {
|
|
43
|
+
if (this.verbose) {
|
|
44
|
+
logger.info(`Config not found: ${configPath}`);
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const envVars = this._parseEnvFile(configPath);
|
|
50
|
+
|
|
51
|
+
// Check if this is a template (not real data)
|
|
52
|
+
if (this.isTemplateConfig(envVars)) {
|
|
53
|
+
if (this.verbose) {
|
|
54
|
+
logger.info(`Skipping template config for ${customer}/${environment}`);
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Convert to standard format
|
|
60
|
+
return this.parseToStandardFormat(envVars, customer, environment);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
logger.error(`Failed to load config for ${customer}/${environment}:`, error.message);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Load customer config safely - never throws, always returns object
|
|
69
|
+
*
|
|
70
|
+
* @param {string} customer - Customer name
|
|
71
|
+
* @param {string} environment - Environment
|
|
72
|
+
* @returns {Object} - Parsed config or defaults
|
|
73
|
+
*/
|
|
74
|
+
loadCustomerConfigSafe(customer, environment) {
|
|
75
|
+
const config = this.loadCustomerConfig(customer, environment);
|
|
76
|
+
if (!config) {
|
|
77
|
+
// Return minimal defaults
|
|
78
|
+
return {
|
|
79
|
+
customer: customer,
|
|
80
|
+
environment: environment,
|
|
81
|
+
serviceName: customer,
|
|
82
|
+
serviceType: 'generic',
|
|
83
|
+
domainName: null,
|
|
84
|
+
cloudflareToken: process.env.CLOUDFLARE_API_TOKEN || null,
|
|
85
|
+
cloudflareAccountId: null,
|
|
86
|
+
cloudflareZoneId: null,
|
|
87
|
+
envVars: {}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return config;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Parse env vars into standard format matching InputCollector output
|
|
95
|
+
* Ensures compatibility between stored configs and collected inputs
|
|
96
|
+
*
|
|
97
|
+
* @param {Object} envVars - Environment variables from .env file
|
|
98
|
+
* @param {string} customer - Customer name
|
|
99
|
+
* @param {string} environment - Environment
|
|
100
|
+
* @returns {Object} - Standardized configuration object
|
|
101
|
+
*/
|
|
102
|
+
parseToStandardFormat(envVars, customer, environment) {
|
|
103
|
+
return {
|
|
104
|
+
customer: customer,
|
|
105
|
+
serviceName: envVars.SERVICE_NAME || customer,
|
|
106
|
+
serviceType: envVars.SERVICE_TYPE || 'generic',
|
|
107
|
+
domainName: envVars.DOMAIN || envVars.CUSTOMER_DOMAIN,
|
|
108
|
+
cloudflareToken: envVars.CLOUDFLARE_API_TOKEN || process.env.CLOUDFLARE_API_TOKEN,
|
|
109
|
+
cloudflareAccountId: envVars.CLOUDFLARE_ACCOUNT_ID,
|
|
110
|
+
cloudflareZoneId: envVars.CLOUDFLARE_ZONE_ID,
|
|
111
|
+
environment: environment,
|
|
112
|
+
// Additional fields
|
|
113
|
+
displayName: envVars.DISPLAY_NAME || customer,
|
|
114
|
+
description: envVars.DESCRIPTION,
|
|
115
|
+
workerName: envVars.WORKER_NAME,
|
|
116
|
+
databaseName: envVars.DATABASE_NAME || envVars.D1_DATABASE_NAME,
|
|
117
|
+
deploymentUrl: envVars.DEPLOYMENT_URL || envVars.API_DOMAIN,
|
|
118
|
+
healthCheckPath: envVars.HEALTH_CHECK_PATH || '/health',
|
|
119
|
+
apiBasePath: envVars.API_BASE_PATH || '/api/v1',
|
|
120
|
+
logLevel: envVars.LOG_LEVEL || 'info',
|
|
121
|
+
nodeCompatibility: envVars.NODE_COMPATIBILITY || 'v18',
|
|
122
|
+
// Keep raw env vars for access
|
|
123
|
+
envVars: envVars
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Save deployment configuration to customer .env file
|
|
129
|
+
*
|
|
130
|
+
* @param {string} customer - Customer name
|
|
131
|
+
* @param {string} environment - Environment
|
|
132
|
+
* @param {Object} deploymentData - Complete deployment data
|
|
133
|
+
* @param {Object} deploymentData.coreInputs - Tier 1 core inputs
|
|
134
|
+
* @param {Object} deploymentData.confirmations - Tier 2 confirmations
|
|
135
|
+
* @param {Object} deploymentData.result - Deployment result (optional)
|
|
136
|
+
* @returns {string} - Path to saved file
|
|
137
|
+
*/
|
|
138
|
+
async saveCustomerConfig(customer, environment, deploymentData) {
|
|
139
|
+
if (!customer || !environment) {
|
|
140
|
+
throw new Error('Customer and environment are required');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Create customer directory if it doesn't exist
|
|
144
|
+
const customerDir = join(this.configDir, customer);
|
|
145
|
+
if (!existsSync(customerDir)) {
|
|
146
|
+
mkdirSync(customerDir, {
|
|
147
|
+
recursive: true
|
|
148
|
+
});
|
|
149
|
+
logger.info(`Created customer directory: ${customerDir}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Generate .env file content
|
|
153
|
+
const envContent = this._generateEnvContent({
|
|
154
|
+
customer,
|
|
155
|
+
environment,
|
|
156
|
+
...deploymentData
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Write to customer environment file
|
|
160
|
+
const envFile = join(customerDir, `${environment}.env`);
|
|
161
|
+
writeFileSync(envFile, envContent, 'utf8');
|
|
162
|
+
logger.info(`Configuration saved: ${envFile}`);
|
|
163
|
+
return envFile;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* List all configured customers
|
|
168
|
+
*
|
|
169
|
+
* @returns {Array<string>} - List of customer names
|
|
170
|
+
*/
|
|
171
|
+
listCustomers() {
|
|
172
|
+
if (!existsSync(this.configDir)) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
const entries = readdirSync(this.configDir);
|
|
176
|
+
|
|
177
|
+
// Filter to only directories
|
|
178
|
+
const customers = entries.filter(entry => {
|
|
179
|
+
const fullPath = join(this.configDir, entry);
|
|
180
|
+
return statSync(fullPath).isDirectory();
|
|
181
|
+
});
|
|
182
|
+
return customers.sort();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Display customer configuration for review
|
|
187
|
+
*
|
|
188
|
+
* @param {string} customer - Customer name
|
|
189
|
+
* @param {string} environment - Environment
|
|
190
|
+
*/
|
|
191
|
+
displayCustomerConfig(customer, environment) {
|
|
192
|
+
const config = this.loadCustomerConfig(customer, environment);
|
|
193
|
+
if (!config) {
|
|
194
|
+
console.log(`\n⚠️ No configuration found for ${customer}/${environment}`);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
console.log(`\n📋 Configuration for ${customer} (${environment})`);
|
|
198
|
+
console.log('='.repeat(60));
|
|
199
|
+
console.log('\n🏢 Customer Identity:');
|
|
200
|
+
console.log(` Customer: ${config.customer}`);
|
|
201
|
+
console.log(` Service Name: ${config.serviceName}`);
|
|
202
|
+
console.log(` Service Type: ${config.serviceType}`);
|
|
203
|
+
if (config.domainName) {
|
|
204
|
+
console.log('\n🌐 Domain Configuration:');
|
|
205
|
+
console.log(` Domain: ${config.domainName}`);
|
|
206
|
+
console.log(` Deployment URL: ${config.deploymentUrl || 'Not set'}`);
|
|
207
|
+
}
|
|
208
|
+
if (config.cloudflareAccountId) {
|
|
209
|
+
console.log('\n☁️ Cloudflare Configuration:');
|
|
210
|
+
console.log(` Account ID: ${config.cloudflareAccountId}`);
|
|
211
|
+
console.log(` Zone ID: ${config.cloudflareZoneId || 'Not set'}`);
|
|
212
|
+
}
|
|
213
|
+
if (config.databaseName) {
|
|
214
|
+
console.log('\n🗄️ Database Configuration:');
|
|
215
|
+
console.log(` Database: ${config.databaseName}`);
|
|
216
|
+
}
|
|
217
|
+
console.log('\n' + '='.repeat(60));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Check if configuration is a template (not real data)
|
|
222
|
+
*
|
|
223
|
+
* @param {Object} envVars - Environment variables
|
|
224
|
+
* @returns {boolean} - True if template
|
|
225
|
+
*/
|
|
226
|
+
isTemplateConfig(envVars) {
|
|
227
|
+
return envVars.CUSTOMER_NAME?.includes('{{') || envVars.CLOUDFLARE_ACCOUNT_ID === '00000000000000000000000000000000' || !envVars.CLOUDFLARE_ACCOUNT_ID || !envVars.DOMAIN;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Check if customer configuration exists
|
|
232
|
+
*
|
|
233
|
+
* @param {string} customer - Customer name
|
|
234
|
+
* @param {string} environment - Environment
|
|
235
|
+
* @returns {boolean} - True if exists
|
|
236
|
+
*/
|
|
237
|
+
configExists(customer, environment) {
|
|
238
|
+
const configPath = resolve(this.configDir, customer, `${environment}.env`);
|
|
239
|
+
return existsSync(configPath);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get missing fields from configuration
|
|
244
|
+
*
|
|
245
|
+
* @param {Object} config - Configuration object
|
|
246
|
+
* @param {Array<string>} requiredFields - Required field names
|
|
247
|
+
* @returns {Array<string>} - Missing field names
|
|
248
|
+
*/
|
|
249
|
+
getMissingFields(config, requiredFields = []) {
|
|
250
|
+
const missing = [];
|
|
251
|
+
for (const field of requiredFields) {
|
|
252
|
+
if (!config[field] || config[field] === null || config[field] === '') {
|
|
253
|
+
missing.push(field);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return missing;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Merge stored config with collected inputs
|
|
261
|
+
* Collected inputs take precedence over stored config
|
|
262
|
+
*
|
|
263
|
+
* @param {Object} storedConfig - Config from .env file
|
|
264
|
+
* @param {Object} collectedInputs - Inputs from InputCollector
|
|
265
|
+
* @returns {Object} - Merged configuration
|
|
266
|
+
*/
|
|
267
|
+
mergeConfigs(storedConfig, collectedInputs) {
|
|
268
|
+
return {
|
|
269
|
+
...storedConfig,
|
|
270
|
+
...collectedInputs,
|
|
271
|
+
// Merge envVars separately
|
|
272
|
+
envVars: {
|
|
273
|
+
...(storedConfig.envVars || {}),
|
|
274
|
+
...(collectedInputs.envVars || {})
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Parse .env file into key-value pairs
|
|
281
|
+
* PRIVATE - Consolidated implementation from both old managers
|
|
282
|
+
*
|
|
283
|
+
* @param {string} filePath - Path to .env file
|
|
284
|
+
* @returns {Object} - Parsed environment variables
|
|
285
|
+
*/
|
|
286
|
+
_parseEnvFile(filePath) {
|
|
287
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
288
|
+
const result = {};
|
|
289
|
+
content.split('\n').forEach(line => {
|
|
290
|
+
line = line.trim();
|
|
291
|
+
|
|
292
|
+
// Skip empty lines and comments
|
|
293
|
+
if (!line || line.startsWith('#')) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Parse KEY=VALUE
|
|
298
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
299
|
+
if (match) {
|
|
300
|
+
const key = match[1].trim();
|
|
301
|
+
let value = match[2].trim();
|
|
302
|
+
|
|
303
|
+
// Remove quotes if present
|
|
304
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
305
|
+
value = value.slice(1, -1);
|
|
306
|
+
}
|
|
307
|
+
result[key] = value;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
return result;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Generate .env file content from deployment data
|
|
315
|
+
* PRIVATE - From ConfigPersistenceManager
|
|
316
|
+
*
|
|
317
|
+
* @param {Object} data - Deployment data
|
|
318
|
+
* @returns {string} - .env file content
|
|
319
|
+
*/
|
|
320
|
+
_generateEnvContent(data) {
|
|
321
|
+
const {
|
|
322
|
+
customer,
|
|
323
|
+
environment,
|
|
324
|
+
coreInputs = {},
|
|
325
|
+
confirmations = {},
|
|
326
|
+
result = {}
|
|
327
|
+
} = data;
|
|
328
|
+
const timestamp = new Date().toISOString();
|
|
329
|
+
const lines = [`# Deployment Configuration - ${customer} (${environment})`, `# Last Updated: ${timestamp}`, `# Auto-generated by Clodo Framework deployment`, '', '# ============================================', '# Core Customer Identity', '# ============================================', `CUSTOMER_ID=${customer}`, `CUSTOMER_NAME=${customer}`, `ENVIRONMENT=${environment}`, ''];
|
|
330
|
+
|
|
331
|
+
// Cloudflare Configuration
|
|
332
|
+
if (coreInputs.cloudflareAccountId || coreInputs.cloudflareZoneId) {
|
|
333
|
+
lines.push('# ============================================');
|
|
334
|
+
lines.push('# Cloudflare Configuration');
|
|
335
|
+
lines.push('# ============================================');
|
|
336
|
+
if (coreInputs.cloudflareAccountId) {
|
|
337
|
+
lines.push(`CLOUDFLARE_ACCOUNT_ID=${coreInputs.cloudflareAccountId}`);
|
|
338
|
+
}
|
|
339
|
+
if (coreInputs.cloudflareZoneId) {
|
|
340
|
+
lines.push(`CLOUDFLARE_ZONE_ID=${coreInputs.cloudflareZoneId}`);
|
|
341
|
+
}
|
|
342
|
+
if (coreInputs.cloudflareToken) {
|
|
343
|
+
lines.push(`CLOUDFLARE_API_TOKEN=${coreInputs.cloudflareToken}`);
|
|
344
|
+
}
|
|
345
|
+
lines.push('');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Service Configuration
|
|
349
|
+
lines.push('# ============================================');
|
|
350
|
+
lines.push('# Service Configuration');
|
|
351
|
+
lines.push('# ============================================');
|
|
352
|
+
if (coreInputs.serviceName) {
|
|
353
|
+
lines.push(`SERVICE_NAME=${coreInputs.serviceName}`);
|
|
354
|
+
}
|
|
355
|
+
if (coreInputs.serviceType) {
|
|
356
|
+
lines.push(`SERVICE_TYPE=${coreInputs.serviceType}`);
|
|
357
|
+
}
|
|
358
|
+
if (confirmations.displayName) {
|
|
359
|
+
lines.push(`DISPLAY_NAME=${confirmations.displayName}`);
|
|
360
|
+
}
|
|
361
|
+
if (confirmations.description) {
|
|
362
|
+
lines.push(`DESCRIPTION=${confirmations.description}`);
|
|
363
|
+
}
|
|
364
|
+
lines.push('');
|
|
365
|
+
|
|
366
|
+
// Domain Configuration
|
|
367
|
+
if (coreInputs.domainName || confirmations.deploymentUrl) {
|
|
368
|
+
lines.push('# ============================================');
|
|
369
|
+
lines.push('# Domain Configuration');
|
|
370
|
+
lines.push('# ============================================');
|
|
371
|
+
if (coreInputs.domainName) {
|
|
372
|
+
lines.push(`DOMAIN=${coreInputs.domainName}`);
|
|
373
|
+
lines.push(`CUSTOMER_DOMAIN=${coreInputs.domainName}`);
|
|
374
|
+
}
|
|
375
|
+
if (confirmations.deploymentUrl) {
|
|
376
|
+
lines.push(`DEPLOYMENT_URL=${confirmations.deploymentUrl}`);
|
|
377
|
+
lines.push(`API_DOMAIN=${confirmations.deploymentUrl}`);
|
|
378
|
+
}
|
|
379
|
+
lines.push('');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Database Configuration
|
|
383
|
+
if (result.databaseName || confirmations.databaseName) {
|
|
384
|
+
lines.push('# ============================================');
|
|
385
|
+
lines.push('# Database Configuration');
|
|
386
|
+
lines.push('# ============================================');
|
|
387
|
+
const dbName = result.databaseName || confirmations.databaseName;
|
|
388
|
+
lines.push(`DATABASE_NAME=${dbName}`);
|
|
389
|
+
lines.push(`D1_DATABASE_NAME=${dbName}`);
|
|
390
|
+
if (result.databaseId) {
|
|
391
|
+
lines.push(`D1_DATABASE_ID=${result.databaseId}`);
|
|
392
|
+
}
|
|
393
|
+
lines.push('');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Worker Configuration
|
|
397
|
+
if (confirmations.workerName || result.workerUrl) {
|
|
398
|
+
lines.push('# ============================================');
|
|
399
|
+
lines.push('# Worker Configuration');
|
|
400
|
+
lines.push('# ============================================');
|
|
401
|
+
if (confirmations.workerName) {
|
|
402
|
+
lines.push(`WORKER_NAME=${confirmations.workerName}`);
|
|
403
|
+
}
|
|
404
|
+
if (result.workerUrl) {
|
|
405
|
+
lines.push(`WORKER_URL=${result.workerUrl}`);
|
|
406
|
+
}
|
|
407
|
+
lines.push('');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Deployment Result
|
|
411
|
+
if (result.url || result.deploymentId) {
|
|
412
|
+
lines.push('# ============================================');
|
|
413
|
+
lines.push('# Deployment Information');
|
|
414
|
+
lines.push('# ============================================');
|
|
415
|
+
if (result.url) {
|
|
416
|
+
lines.push(`DEPLOYMENT_URL=${result.url}`);
|
|
417
|
+
}
|
|
418
|
+
if (result.deploymentId) {
|
|
419
|
+
lines.push(`DEPLOYMENT_ID=${result.deploymentId}`);
|
|
420
|
+
}
|
|
421
|
+
if (result.timestamp) {
|
|
422
|
+
lines.push(`DEPLOYED_AT=${result.timestamp}`);
|
|
423
|
+
}
|
|
424
|
+
lines.push('');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Additional Configuration
|
|
428
|
+
lines.push('# ============================================');
|
|
429
|
+
lines.push('# Additional Configuration');
|
|
430
|
+
lines.push('# ============================================');
|
|
431
|
+
lines.push(`HEALTH_CHECK_PATH=${confirmations.healthCheckPath || '/health'}`);
|
|
432
|
+
lines.push(`API_BASE_PATH=${confirmations.apiBasePath || '/api/v1'}`);
|
|
433
|
+
lines.push(`LOG_LEVEL=${confirmations.logLevel || 'info'}`);
|
|
434
|
+
lines.push(`NODE_COMPATIBILITY=${confirmations.nodeCompatibility || 'v18'}`);
|
|
435
|
+
lines.push('');
|
|
436
|
+
return lines.join('\n');
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Factory function for convenience
|
|
442
|
+
*/
|
|
443
|
+
export function createUnifiedConfigManager(options = {}) {
|
|
444
|
+
return new UnifiedConfigManager(options);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Export singleton instance for convenience
|
|
448
|
+
export const unifiedConfigManager = new UnifiedConfigManager();
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
|
|
4
4
|
export { ConfigurationCacheManager } from './config-cache.js';
|
|
5
5
|
export { EnhancedSecretManager } from './secret-generator.js';
|
|
6
|
-
export {
|
|
6
|
+
export { UnifiedConfigManager, unifiedConfigManager } from '../config/unified-config-manager.js';
|
|
7
7
|
export { askUser, askYesNo, askChoice, closePrompts } from '../interactive-prompts.js';
|