@aifabrix/builder 2.0.0 → 2.0.3

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.
Files changed (61) hide show
  1. package/README.md +5 -3
  2. package/bin/aifabrix.js +9 -3
  3. package/jest.config.integration.js +30 -0
  4. package/lib/app-config.js +157 -0
  5. package/lib/app-deploy.js +233 -82
  6. package/lib/app-dockerfile.js +112 -0
  7. package/lib/app-prompts.js +244 -0
  8. package/lib/app-push.js +172 -0
  9. package/lib/app-run.js +235 -144
  10. package/lib/app.js +208 -274
  11. package/lib/audit-logger.js +2 -0
  12. package/lib/build.js +177 -125
  13. package/lib/cli.js +76 -86
  14. package/lib/commands/app.js +414 -0
  15. package/lib/commands/login.js +304 -0
  16. package/lib/config.js +78 -0
  17. package/lib/deployer.js +225 -81
  18. package/lib/env-reader.js +45 -30
  19. package/lib/generator.js +308 -191
  20. package/lib/github-generator.js +67 -7
  21. package/lib/infra.js +156 -61
  22. package/lib/push.js +105 -10
  23. package/lib/schema/application-schema.json +30 -2
  24. package/lib/schema/env-config.yaml +9 -1
  25. package/lib/schema/infrastructure-schema.json +589 -0
  26. package/lib/secrets.js +229 -24
  27. package/lib/template-validator.js +205 -0
  28. package/lib/templates.js +305 -170
  29. package/lib/utils/api.js +329 -0
  30. package/lib/utils/cli-utils.js +97 -0
  31. package/lib/utils/compose-generator.js +185 -0
  32. package/lib/utils/docker-build.js +173 -0
  33. package/lib/utils/dockerfile-utils.js +131 -0
  34. package/lib/utils/environment-checker.js +125 -0
  35. package/lib/utils/error-formatter.js +61 -0
  36. package/lib/utils/health-check.js +187 -0
  37. package/lib/utils/logger.js +53 -0
  38. package/lib/utils/template-helpers.js +223 -0
  39. package/lib/utils/variable-transformer.js +271 -0
  40. package/lib/validator.js +27 -112
  41. package/package.json +14 -10
  42. package/templates/README.md +75 -3
  43. package/templates/applications/keycloak/Dockerfile +36 -0
  44. package/templates/applications/keycloak/env.template +32 -0
  45. package/templates/applications/keycloak/rbac.yaml +37 -0
  46. package/templates/applications/keycloak/variables.yaml +56 -0
  47. package/templates/applications/miso-controller/Dockerfile +125 -0
  48. package/templates/applications/miso-controller/env.template +129 -0
  49. package/templates/applications/miso-controller/rbac.yaml +214 -0
  50. package/templates/applications/miso-controller/variables.yaml +56 -0
  51. package/templates/github/release.yaml.hbs +5 -26
  52. package/templates/github/steps/npm.hbs +24 -0
  53. package/templates/infra/compose.yaml +6 -6
  54. package/templates/python/docker-compose.hbs +19 -12
  55. package/templates/python/main.py +80 -0
  56. package/templates/python/requirements.txt +4 -0
  57. package/templates/typescript/Dockerfile.hbs +2 -2
  58. package/templates/typescript/docker-compose.hbs +19 -12
  59. package/templates/typescript/index.ts +116 -0
  60. package/templates/typescript/package.json +26 -0
  61. package/templates/typescript/tsconfig.json +24 -0
package/lib/deployer.js CHANGED
@@ -12,6 +12,7 @@
12
12
  const axios = require('axios');
13
13
  const chalk = require('chalk');
14
14
  const auditLogger = require('./audit-logger');
15
+ const logger = require('./utils/logger');
15
16
 
16
17
  /**
17
18
  * Validates and sanitizes controller URL
@@ -25,13 +26,13 @@ function validateControllerUrl(url) {
25
26
  throw new Error('Controller URL is required and must be a string');
26
27
  }
27
28
 
28
- // Must use HTTPS for security
29
- if (!url.startsWith('https://')) {
30
- throw new Error('Controller URL must use HTTPS (https://)');
29
+ // Must use HTTPS for security (allow http://localhost for local development)
30
+ if (!url.startsWith('https://') && !url.startsWith('http://localhost')) {
31
+ throw new Error('Controller URL must use HTTPS (https://) or http://localhost');
31
32
  }
32
33
 
33
34
  // Basic URL format validation
34
- const urlPattern = /^https:\/\/[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*(:[0-9]+)?(\/.*)?$/;
35
+ const urlPattern = /^(https?):\/\/[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*(localhost)?(:[0-9]+)?(\/.*)?$/;
35
36
  if (!urlPattern.test(url)) {
36
37
  throw new Error('Invalid controller URL format');
37
38
  }
@@ -41,36 +42,79 @@ function validateControllerUrl(url) {
41
42
  }
42
43
 
43
44
  /**
44
- * Sends deployment manifest to Miso Controller
45
+ * Creates authentication headers for Client Credentials flow
45
46
  *
46
- * @async
47
- * @param {string} url - Controller URL
48
- * @param {Object} manifest - Deployment manifest
49
- * @param {Object} options - Deployment options (timeout, retries, etc.)
50
- * @returns {Promise<Object>} Deployment result from controller
51
- * @throws {Error} If deployment fails
47
+ * @param {string} clientId - Application client ID
48
+ * @param {string} clientSecret - Application client secret
49
+ * @returns {Object} Headers object with authentication
50
+ * @throws {Error} If credentials are missing
52
51
  */
53
- async function sendDeploymentRequest(url, manifest, options = {}) {
54
- const endpoint = `${url}/api/pipeline/deploy`;
55
- const timeout = options.timeout || 30000;
56
- const maxRetries = options.maxRetries || 3;
52
+ function createClientCredentialsHeaders(clientId, clientSecret) {
53
+ if (!clientId || !clientSecret) {
54
+ throw new Error('Client ID and Client Secret are required for authentication');
55
+ }
56
+ return {
57
+ 'x-client-id': clientId,
58
+ 'x-client-secret': clientSecret
59
+ };
60
+ }
57
61
 
58
- const requestConfig = {
62
+ /**
63
+ * Validates environment key
64
+ * @param {string} envKey - Environment key to validate
65
+ * @throws {Error} If environment key is invalid
66
+ */
67
+ function validateEnvironmentKey(envKey) {
68
+ if (!envKey || typeof envKey !== 'string') {
69
+ throw new Error('Environment key is required and must be a string');
70
+ }
71
+
72
+ const validEnvironments = ['miso', 'dev', 'tst', 'pro'];
73
+ if (!validEnvironments.includes(envKey.toLowerCase())) {
74
+ throw new Error(`Invalid environment key: ${envKey}. Must be one of: ${validEnvironments.join(', ')}`);
75
+ }
76
+
77
+ return envKey.toLowerCase();
78
+ }
79
+
80
+ /**
81
+ * Creates deployment request configuration
82
+ * @function createDeploymentRequestConfig
83
+ * @param {string} clientId - Client ID
84
+ * @param {string} clientSecret - Client Secret
85
+ * @param {number} timeout - Request timeout
86
+ * @returns {Object} Request configuration
87
+ */
88
+ function createDeploymentRequestConfig(clientId, clientSecret, timeout) {
89
+ return {
59
90
  headers: {
60
91
  'Content-Type': 'application/json',
61
- 'User-Agent': 'aifabrix-builder/2.0.0'
92
+ 'User-Agent': 'aifabrix-builder/2.0.0',
93
+ ...createClientCredentialsHeaders(clientId, clientSecret)
62
94
  },
63
95
  timeout,
64
- validateStatus: (status) => status < 500 // Don't throw on 4xx errors
96
+ validateStatus: (status) => status < 500
65
97
  };
98
+ }
66
99
 
100
+ /**
101
+ * Handles deployment request retry logic
102
+ * @async
103
+ * @function handleDeploymentRetry
104
+ * @param {string} endpoint - API endpoint
105
+ * @param {Object} manifest - Deployment manifest
106
+ * @param {Object} requestConfig - Request configuration
107
+ * @param {number} maxRetries - Maximum retry attempts
108
+ * @returns {Promise<Object>} Deployment result
109
+ * @throws {Error} If deployment fails after retries
110
+ */
111
+ async function handleDeploymentRetry(endpoint, manifest, requestConfig, maxRetries) {
67
112
  let lastError;
68
113
 
69
114
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
70
115
  try {
71
116
  const response = await axios.post(endpoint, manifest, requestConfig);
72
117
 
73
- // Check for HTTP errors
74
118
  if (response.status >= 400) {
75
119
  const error = new Error(`Controller returned error: ${response.status} ${response.statusText}`);
76
120
  error.status = response.status;
@@ -84,41 +128,76 @@ async function sendDeploymentRequest(url, manifest, options = {}) {
84
128
  }
85
129
 
86
130
  return response.data;
87
-
88
131
  } catch (error) {
89
132
  lastError = error;
90
133
 
91
- // Log retry attempt
92
134
  if (attempt < maxRetries) {
93
- const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000); // Exponential backoff, max 5s
94
- console.log(chalk.yellow(`⚠️ Deployment attempt ${attempt} failed, retrying in ${delay}ms...`));
135
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
136
+ logger.log(chalk.yellow(`⚠️ Deployment attempt ${attempt} failed, retrying in ${delay}ms...`));
95
137
  await new Promise(resolve => setTimeout(resolve, delay));
96
138
  }
97
139
  }
98
140
  }
99
141
 
100
- // All retries failed
101
142
  throw new Error(`Deployment failed after ${maxRetries} attempts: ${lastError.message}`);
102
143
  }
103
144
 
145
+ /**
146
+ * Sends deployment manifest to Miso Controller with Client Credentials authentication
147
+ *
148
+ * @async
149
+ * @param {string} url - Controller URL
150
+ * @param {string} envKey - Environment key (dev, tst, pro)
151
+ * @param {Object} manifest - Deployment manifest
152
+ * @param {string} clientId - Client ID for authentication
153
+ * @param {string} clientSecret - Client Secret for authentication
154
+ * @param {Object} options - Deployment options (timeout, retries, etc.)
155
+ * @returns {Promise<Object>} Deployment result from controller
156
+ * @throws {Error} If deployment fails
157
+ */
158
+ async function sendDeploymentRequest(url, envKey, manifest, clientId, clientSecret, options = {}) {
159
+ const validatedEnvKey = validateEnvironmentKey(envKey);
160
+ const endpoint = `${url}/api/v1/pipeline/${validatedEnvKey}/deploy`;
161
+ const timeout = options.timeout || 30000;
162
+ const maxRetries = options.maxRetries || 3;
163
+ const requestConfig = createDeploymentRequestConfig(clientId, clientSecret, timeout);
164
+
165
+ return handleDeploymentRetry(endpoint, manifest, requestConfig, maxRetries);
166
+ }
167
+
168
+ /**
169
+ * Checks if deployment status is terminal
170
+ * @function isTerminalStatus
171
+ * @param {string} status - Deployment status
172
+ * @returns {boolean} True if status is terminal
173
+ */
174
+ function isTerminalStatus(status) {
175
+ return status === 'completed' || status === 'failed' || status === 'cancelled';
176
+ }
177
+
104
178
  /**
105
179
  * Polls deployment status from controller
106
180
  *
107
181
  * @async
108
182
  * @param {string} deploymentId - Deployment ID to poll
109
183
  * @param {string} controllerUrl - Controller URL
184
+ * @param {string} envKey - Environment key
185
+ * @param {string} clientId - Client ID for authentication
186
+ * @param {string} clientSecret - Client Secret for authentication
110
187
  * @param {Object} options - Polling options (interval, maxAttempts, etc.)
111
188
  * @returns {Promise<Object>} Deployment status
112
189
  */
113
- async function pollDeploymentStatus(deploymentId, controllerUrl, options = {}) {
190
+ async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, clientId, clientSecret, options = {}) {
114
191
  const interval = options.interval || 5000;
115
- const maxAttempts = options.maxAttempts || 60; // 5 minutes max
192
+ const maxAttempts = options.maxAttempts || 60;
116
193
 
117
- const statusEndpoint = `${controllerUrl}/api/pipeline/status/${deploymentId}`;
194
+ const validatedEnvKey = validateEnvironmentKey(envKey);
195
+ const statusEndpoint = `${controllerUrl}/api/v1/environments/${validatedEnvKey}/deployments/${deploymentId}`;
118
196
 
119
197
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
120
198
  try {
121
199
  const response = await axios.get(statusEndpoint, {
200
+ headers: createClientCredentialsHeaders(clientId, clientSecret),
122
201
  timeout: 10000,
123
202
  validateStatus: (status) => status < 500
124
203
  });
@@ -126,15 +205,12 @@ async function pollDeploymentStatus(deploymentId, controllerUrl, options = {}) {
126
205
  if (response.status === 200) {
127
206
  const status = response.data.status;
128
207
 
129
- // Terminal states
130
- if (status === 'completed' || status === 'failed' || status === 'cancelled') {
208
+ if (isTerminalStatus(status)) {
131
209
  return response.data;
132
210
  }
133
211
 
134
- // Log progress
135
- console.log(chalk.blue(` Status: ${status} (attempt ${attempt + 1}/${maxAttempts})`));
212
+ logger.log(chalk.blue(` Status: ${status} (attempt ${attempt + 1}/${maxAttempts})`));
136
213
 
137
- // Wait before next poll
138
214
  if (attempt < maxAttempts - 1) {
139
215
  await new Promise(resolve => setTimeout(resolve, interval));
140
216
  }
@@ -174,75 +250,141 @@ function handleDeploymentError(error) {
174
250
  }
175
251
 
176
252
  /**
177
- * Deploys application to Miso Controller
253
+ * Sends deployment request to controller
254
+ * @async
255
+ * @param {string} url - Controller URL
256
+ * @param {string} validatedEnvKey - Validated environment key
257
+ * @param {Object} manifest - Deployment manifest
258
+ * @param {string} clientId - Client ID
259
+ * @param {string} clientSecret - Client Secret
260
+ * @param {Object} options - Deployment options
261
+ * @returns {Promise<Object>} Deployment result
262
+ */
263
+ async function sendDeployment(url, validatedEnvKey, manifest, clientId, clientSecret, options) {
264
+ logger.log(chalk.blue(`📤 Sending deployment request to ${url}/api/v1/pipeline/${validatedEnvKey}/deploy...`));
265
+ const result = await sendDeploymentRequest(url, validatedEnvKey, manifest, clientId, clientSecret, {
266
+ timeout: options.timeout || 30000,
267
+ maxRetries: options.maxRetries || 3
268
+ });
269
+
270
+ if (result.deploymentId) {
271
+ auditLogger.logDeploymentSuccess(manifest.key, result.deploymentId, url);
272
+ }
273
+
274
+ return result;
275
+ }
276
+
277
+ /**
278
+ * Polls deployment status if enabled
279
+ * @async
280
+ * @param {Object} result - Deployment result
281
+ * @param {string} url - Controller URL
282
+ * @param {string} validatedEnvKey - Validated environment key
283
+ * @param {string} clientId - Client ID
284
+ * @param {string} clientSecret - Client Secret
285
+ * @param {Object} options - Deployment options
286
+ * @returns {Promise<Object>} Deployment result with status
287
+ */
288
+ async function pollDeployment(result, url, validatedEnvKey, clientId, clientSecret, options) {
289
+ if (!options.poll || !result.deploymentId) {
290
+ return result;
291
+ }
292
+
293
+ logger.log(chalk.blue(`\n⏳ Polling deployment status (${options.pollInterval || 5000}ms intervals)...`));
294
+ const status = await pollDeploymentStatus(
295
+ result.deploymentId,
296
+ url,
297
+ validatedEnvKey,
298
+ clientId,
299
+ clientSecret,
300
+ {
301
+ interval: options.pollInterval || 5000,
302
+ maxAttempts: options.pollMaxAttempts || 60
303
+ }
304
+ );
305
+
306
+ result.status = status;
307
+ return result;
308
+ }
309
+
310
+ /**
311
+ * Handles deployment errors and maps them to user-friendly messages
312
+ * @param {Error} error - Error object
313
+ * @param {string} manifestKey - Manifest key for audit logging
314
+ * @param {string} url - Controller URL for audit logging
315
+ * @throws {Error} User-friendly error message
316
+ */
317
+ function handleDeploymentErrors(error, manifestKey, url) {
318
+ auditLogger.logDeploymentFailure(manifestKey, url, error);
319
+
320
+ const safeError = handleDeploymentError(error);
321
+
322
+ if (safeError.status === 401 || safeError.status === 403) {
323
+ throw new Error('Authentication failed. Check your client ID and client secret.');
324
+ }
325
+
326
+ if (safeError.status === 400) {
327
+ throw new Error('Invalid deployment manifest. Please check your configuration.');
328
+ }
329
+
330
+ if (safeError.status === 404) {
331
+ throw new Error('Controller endpoint not found. Check the controller URL and environment.');
332
+ }
333
+
334
+ if (safeError.code === 'ECONNREFUSED') {
335
+ throw new Error('Cannot connect to controller. Check if the controller is running.');
336
+ }
337
+
338
+ if (safeError.code === 'ENOTFOUND') {
339
+ throw new Error('Controller hostname not found. Check your controller URL.');
340
+ }
341
+
342
+ if (safeError.timeout) {
343
+ throw new Error('Request timed out. The controller may be overloaded.');
344
+ }
345
+
346
+ throw new Error(safeError.message);
347
+ }
348
+
349
+ /**
350
+ * Deploys application to Miso Controller with Client Credentials authentication
178
351
  * Main orchestrator for the deployment process
179
352
  *
180
353
  * @async
181
354
  * @param {Object} manifest - Deployment manifest
182
355
  * @param {string} controllerUrl - Controller URL
356
+ * @param {string} envKey - Environment key (dev, tst, pro)
357
+ * @param {string} clientId - Client ID for authentication
358
+ * @param {string} clientSecret - Client Secret for authentication
183
359
  * @param {Object} options - Deployment options
184
360
  * @returns {Promise<Object>} Deployment result
185
361
  * @throws {Error} If deployment fails
186
362
  */
187
- async function deployToController(manifest, controllerUrl, options = {}) {
363
+ async function deployToController(manifest, controllerUrl, envKey, clientId, clientSecret, options = {}) {
364
+ // Validate inputs
365
+ if (!envKey) {
366
+ throw new Error('Environment key is required');
367
+ }
368
+ if (!clientId || !clientSecret) {
369
+ throw new Error('Client ID and Client Secret are required for authentication');
370
+ }
371
+
188
372
  // Validate and sanitize controller URL
189
373
  const url = validateControllerUrl(controllerUrl);
374
+ const validatedEnvKey = validateEnvironmentKey(envKey);
190
375
 
191
376
  // Log deployment attempt for audit
192
377
  auditLogger.logDeploymentAttempt(manifest.key, url, options);
193
378
 
194
379
  try {
195
380
  // Send deployment request
196
- console.log(chalk.blue(`📤 Sending deployment request to ${url}...`));
197
- const result = await sendDeploymentRequest(url, manifest, {
198
- timeout: options.timeout || 30000,
199
- maxRetries: options.maxRetries || 3
200
- });
201
-
202
- // Log success
203
- if (result.deploymentId) {
204
- auditLogger.logDeploymentSuccess(manifest.key, result.deploymentId, url);
205
- }
381
+ const result = await sendDeployment(url, validatedEnvKey, manifest, clientId, clientSecret, options);
206
382
 
207
383
  // Poll for deployment status if enabled
208
- if (options.poll && result.deploymentId) {
209
- console.log(chalk.blue(`\n⏳ Polling deployment status (${options.pollInterval || 5000}ms intervals)...`));
210
- const status = await pollDeploymentStatus(
211
- result.deploymentId,
212
- url,
213
- {
214
- interval: options.pollInterval || 5000,
215
- maxAttempts: options.pollMaxAttempts || 60
216
- }
217
- );
218
- result.status = status;
219
- }
220
-
221
- return result;
384
+ return await pollDeployment(result, url, validatedEnvKey, clientId, clientSecret, options);
222
385
 
223
386
  } catch (error) {
224
- // Log failure for audit
225
- auditLogger.logDeploymentFailure(manifest.key, url, error);
226
-
227
- // Handle and re-throw with safe error
228
- const safeError = handleDeploymentError(error);
229
-
230
- // Provide user-friendly error messages
231
- if (safeError.status === 401 || safeError.status === 403) {
232
- throw new Error('Authentication failed. Check your deployment key.');
233
- } else if (safeError.status === 400) {
234
- throw new Error('Invalid deployment manifest. Please check your configuration.');
235
- } else if (safeError.status === 404) {
236
- throw new Error('Controller endpoint not found. Check the controller URL.');
237
- } else if (safeError.code === 'ECONNREFUSED') {
238
- throw new Error('Cannot connect to controller. Check if the controller is running.');
239
- } else if (safeError.code === 'ENOTFOUND') {
240
- throw new Error('Controller hostname not found. Check your controller URL.');
241
- } else if (safeError.timeout) {
242
- throw new Error('Request timed out. The controller may be overloaded.');
243
- }
244
-
245
- throw new Error(safeError.message);
387
+ handleDeploymentErrors(error, manifest.key, url);
246
388
  }
247
389
  }
248
390
 
@@ -251,6 +393,8 @@ module.exports = {
251
393
  sendDeploymentRequest,
252
394
  pollDeploymentStatus,
253
395
  validateControllerUrl,
254
- handleDeploymentError
396
+ handleDeploymentError,
397
+ createClientCredentialsHeaders,
398
+ validateEnvironmentKey
255
399
  };
256
400
 
package/lib/env-reader.js CHANGED
@@ -177,7 +177,48 @@ function sanitizeEnvValue(value) {
177
177
  }
178
178
 
179
179
  /**
180
- * Generate environment template with security considerations
180
+ * Filters existing environment variables for template
181
+ * @function filterExistingEnvVars
182
+ * @param {Object} convertedEnv - Converted environment variables
183
+ * @returns {string[]} Filtered environment variable lines
184
+ */
185
+ function filterExistingEnvVars(convertedEnv) {
186
+ const excludedPrefixes = [
187
+ 'NODE_ENV', 'PORT', 'APP_NAME', 'LOG_LEVEL',
188
+ 'DB_', 'DATABASE_', 'REDIS_', 'STORAGE_',
189
+ 'JWT_', 'AUTH_', 'SESSION_'
190
+ ];
191
+
192
+ return Object.entries(convertedEnv)
193
+ .filter(([key]) => !excludedPrefixes.some(prefix => key.startsWith(prefix)))
194
+ .map(([key, value]) => `${key}=${value}`);
195
+ }
196
+
197
+ /**
198
+ * Validates and processes environment variables
199
+ * @function validateAndProcessEnvVars
200
+ * @param {Object} existingEnv - Existing environment variables
201
+ * @returns {Object} Validation result with warnings
202
+ */
203
+ function validateAndProcessEnvVars(existingEnv) {
204
+ const warnings = [];
205
+
206
+ Object.entries(existingEnv).forEach(([key, value]) => {
207
+ if (!validateEnvKey(key)) {
208
+ warnings.push(`Invalid environment variable name: ${key}`);
209
+ }
210
+
211
+ const sanitizedValue = sanitizeEnvValue(value);
212
+ if (sanitizedValue !== value) {
213
+ warnings.push(`Sanitized value for ${key} (removed special characters)`);
214
+ }
215
+ });
216
+
217
+ return { warnings };
218
+ }
219
+
220
+ /**
221
+ * Generates environment template with security considerations
181
222
  * @param {Object} config - Application configuration
182
223
  * @param {Object} existingEnv - Existing environment variables
183
224
  * @returns {Promise<Object>} Template generation result
@@ -190,41 +231,15 @@ async function generateEnvTemplate(config, existingEnv = {}) {
190
231
  };
191
232
 
192
233
  try {
193
- // Convert existing environment variables
194
234
  const convertedEnv = convertToEnvTemplate(existingEnv, {});
195
-
196
- // Extract secrets for secrets.yaml
197
235
  result.secrets = generateSecretsFromEnv(existingEnv);
236
+ const validation = validateAndProcessEnvVars(existingEnv);
237
+ result.warnings = validation.warnings;
198
238
 
199
- // Validate environment variables
200
- Object.entries(existingEnv).forEach(([key, value]) => {
201
- if (!validateEnvKey(key)) {
202
- result.warnings.push(`Invalid environment variable name: ${key}`);
203
- }
204
-
205
- const sanitizedValue = sanitizeEnvValue(value);
206
- if (sanitizedValue !== value) {
207
- result.warnings.push(`Sanitized value for ${key} (removed special characters)`);
208
- }
209
- });
210
-
211
- // Generate template content
212
239
  const { generateEnvTemplate: generateTemplate } = require('./templates');
213
240
  const baseTemplate = generateTemplate(config);
214
241
 
215
- // Add existing environment variables to the template
216
- const existingEnvSection = [];
217
- Object.entries(convertedEnv).forEach(([key, value]) => {
218
- if (!key.startsWith('NODE_ENV') && !key.startsWith('PORT') &&
219
- !key.startsWith('APP_NAME') && !key.startsWith('LOG_LEVEL') &&
220
- !key.startsWith('DB_') && !key.startsWith('DATABASE_') &&
221
- !key.startsWith('REDIS_') && !key.startsWith('STORAGE_') &&
222
- !key.startsWith('JWT_') && !key.startsWith('AUTH_') &&
223
- !key.startsWith('SESSION_')) {
224
- existingEnvSection.push(`${key}=${value}`);
225
- }
226
- });
227
-
242
+ const existingEnvSection = filterExistingEnvVars(convertedEnv);
228
243
  if (existingEnvSection.length > 0) {
229
244
  result.template = baseTemplate + '\n\n# Existing Environment Variables\n' + existingEnvSection.join('\n');
230
245
  } else {