@aifabrix/builder 2.22.1 → 2.31.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.
Files changed (65) hide show
  1. package/jest.config.coverage.js +37 -0
  2. package/lib/api/pipeline.api.js +10 -9
  3. package/lib/app-deploy.js +36 -14
  4. package/lib/app-list.js +191 -71
  5. package/lib/app-prompts.js +77 -26
  6. package/lib/app-readme.js +123 -5
  7. package/lib/app-rotate-secret.js +101 -57
  8. package/lib/app-run-helpers.js +200 -172
  9. package/lib/app-run.js +137 -68
  10. package/lib/audit-logger.js +8 -7
  11. package/lib/build.js +161 -250
  12. package/lib/cli.js +73 -65
  13. package/lib/commands/login.js +45 -31
  14. package/lib/commands/logout.js +181 -0
  15. package/lib/commands/secrets-set.js +2 -2
  16. package/lib/commands/secure.js +61 -26
  17. package/lib/config.js +79 -45
  18. package/lib/datasource-deploy.js +89 -29
  19. package/lib/deployer.js +164 -129
  20. package/lib/diff.js +63 -21
  21. package/lib/environment-deploy.js +36 -19
  22. package/lib/external-system-deploy.js +134 -66
  23. package/lib/external-system-download.js +244 -171
  24. package/lib/external-system-test.js +199 -164
  25. package/lib/generator-external.js +145 -72
  26. package/lib/generator-helpers.js +49 -17
  27. package/lib/generator-split.js +105 -58
  28. package/lib/infra.js +101 -131
  29. package/lib/schema/application-schema.json +895 -896
  30. package/lib/schema/env-config.yaml +11 -4
  31. package/lib/template-validator.js +13 -4
  32. package/lib/utils/api.js +8 -8
  33. package/lib/utils/app-register-auth.js +36 -18
  34. package/lib/utils/app-run-containers.js +140 -0
  35. package/lib/utils/auth-headers.js +6 -6
  36. package/lib/utils/build-copy.js +60 -2
  37. package/lib/utils/build-helpers.js +94 -0
  38. package/lib/utils/cli-utils.js +177 -76
  39. package/lib/utils/compose-generator.js +12 -2
  40. package/lib/utils/config-tokens.js +151 -9
  41. package/lib/utils/deployment-errors.js +137 -69
  42. package/lib/utils/deployment-validation-helpers.js +103 -0
  43. package/lib/utils/docker-build.js +57 -0
  44. package/lib/utils/dockerfile-utils.js +13 -3
  45. package/lib/utils/env-copy.js +163 -94
  46. package/lib/utils/env-map.js +226 -86
  47. package/lib/utils/environment-checker.js +2 -2
  48. package/lib/utils/error-formatters/network-errors.js +0 -1
  49. package/lib/utils/external-system-display.js +14 -19
  50. package/lib/utils/external-system-env-helpers.js +107 -0
  51. package/lib/utils/external-system-test-helpers.js +144 -0
  52. package/lib/utils/health-check.js +10 -8
  53. package/lib/utils/infra-status.js +123 -0
  54. package/lib/utils/local-secrets.js +3 -2
  55. package/lib/utils/paths.js +228 -49
  56. package/lib/utils/schema-loader.js +125 -57
  57. package/lib/utils/token-manager.js +10 -7
  58. package/lib/utils/yaml-preserve.js +55 -16
  59. package/lib/validate.js +87 -89
  60. package/package.json +4 -4
  61. package/scripts/ci-fix.sh +19 -0
  62. package/scripts/ci-simulate.sh +19 -0
  63. package/templates/applications/miso-controller/test.yaml +1 -0
  64. package/templates/python/Dockerfile.hbs +8 -45
  65. package/templates/typescript/Dockerfile.hbs +8 -42
@@ -81,38 +81,15 @@ async function generateExternalSystemDeployJson(appName, appPath) {
81
81
  }
82
82
 
83
83
  /**
84
- * Generates application-schema.json structure for external systems
85
- * Combines system and datasource JSONs into application-level deployment format
84
+ * Load system file and merge RBAC
86
85
  * @async
87
- * @function generateExternalSystemApplicationSchema
88
- * @param {string} appName - Application name
89
- * @returns {Promise<Object>} Application schema object
90
- * @throws {Error} If generation fails
86
+ * @param {string} appPath - Application path
87
+ * @param {string} schemaBasePath - Schema base path
88
+ * @param {string} systemFileName - System file name
89
+ * @returns {Promise<Object>} System JSON object
90
+ * @throws {Error} If file cannot be loaded
91
91
  */
92
- async function generateExternalSystemApplicationSchema(appName) {
93
- if (!appName || typeof appName !== 'string') {
94
- throw new Error('App name is required and must be a string');
95
- }
96
-
97
- const { appPath } = await detectAppType(appName);
98
- const variablesPath = path.join(appPath, 'variables.yaml');
99
-
100
- // Load variables.yaml
101
- const { parsed: variables } = loadVariables(variablesPath);
102
-
103
- if (!variables.externalIntegration) {
104
- throw new Error('externalIntegration block not found in variables.yaml');
105
- }
106
-
107
- // Load system file
108
- const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
109
- const systemFiles = variables.externalIntegration.systems || [];
110
-
111
- if (systemFiles.length === 0) {
112
- throw new Error('No system files specified in externalIntegration.systems');
113
- }
114
-
115
- const systemFileName = systemFiles[0];
92
+ async function loadSystemFile(appPath, schemaBasePath, systemFileName) {
116
93
  const systemFilePath = path.isAbsolute(schemaBasePath)
117
94
  ? path.join(schemaBasePath, systemFileName)
118
95
  : path.join(appPath, schemaBasePath, systemFileName);
@@ -125,7 +102,6 @@ async function generateExternalSystemApplicationSchema(appName) {
125
102
  const systemJson = JSON.parse(systemContent);
126
103
 
127
104
  // Load rbac.yaml from app directory and merge if present
128
- // Priority: roles/permissions in system JSON > rbac.yaml (if both exist, prefer JSON)
129
105
  const rbacPath = path.join(appPath, 'rbac.yaml');
130
106
  const rbac = loadRbac(rbacPath);
131
107
  if (rbac) {
@@ -137,8 +113,19 @@ async function generateExternalSystemApplicationSchema(appName) {
137
113
  }
138
114
  }
139
115
 
140
- // Load datasource files
141
- const datasourceFiles = variables.externalIntegration.dataSources || [];
116
+ return systemJson;
117
+ }
118
+
119
+ /**
120
+ * Load datasource files
121
+ * @async
122
+ * @param {string} appPath - Application path
123
+ * @param {string} schemaBasePath - Schema base path
124
+ * @param {Array<string>} datasourceFiles - Array of datasource file names
125
+ * @returns {Promise<Array<Object>>} Array of datasource JSON objects
126
+ * @throws {Error} If files cannot be loaded
127
+ */
128
+ async function loadDatasourceFiles(appPath, schemaBasePath, datasourceFiles) {
142
129
  const datasourceJsons = [];
143
130
 
144
131
  for (const datasourceFile of datasourceFiles) {
@@ -155,69 +142,155 @@ async function generateExternalSystemApplicationSchema(appName) {
155
142
  datasourceJsons.push(datasourceJson);
156
143
  }
157
144
 
158
- // Build application-schema.json structure
159
- const applicationSchema = {
160
- version: variables.externalIntegration.version || '1.0.0',
145
+ return datasourceJsons;
146
+ }
147
+
148
+ /**
149
+ * Build base application schema structure
150
+ * @param {Object} systemJson - System JSON object
151
+ * @param {Array<Object>} datasourceJsons - Array of datasource JSON objects
152
+ * @param {string} version - Schema version
153
+ * @returns {Object} Application schema object
154
+ */
155
+ function buildBaseSchema(systemJson, datasourceJsons, version) {
156
+ return {
157
+ version: version || '1.0.0',
161
158
  application: systemJson,
162
159
  dataSources: datasourceJsons
163
160
  };
161
+ }
164
162
 
165
- // Validate individual components against their schemas
166
- const externalSystemSchema = require('./schema/external-system.schema.json');
167
- const externalDatasourceSchema = require('./schema/external-datasource.schema.json');
168
-
169
- // For draft-2020-12 schemas, remove $schema to avoid AJV issues (similar to schema-loader.js)
170
- const datasourceSchemaToAdd = { ...externalDatasourceSchema };
171
- if (datasourceSchemaToAdd.$schema && datasourceSchemaToAdd.$schema.includes('2020-12')) {
172
- delete datasourceSchemaToAdd.$schema;
173
- }
163
+ /**
164
+ * Filter validation errors to handle schema inconsistencies
165
+ * @param {Array} errors - Validation errors
166
+ * @param {Object} schema - Schema object
167
+ * @returns {Array} Filtered errors
168
+ */
169
+ function filterValidationErrors(errors, schema) {
170
+ return errors.filter(err => {
171
+ if (err.keyword === 'additionalProperties' && err.params?.additionalProperty === 'authentication') {
172
+ const required = schema.required || [];
173
+ if (required.includes('authentication')) {
174
+ return false; // Ignore this error since authentication is required but not defined
175
+ }
176
+ }
177
+ return true;
178
+ });
179
+ }
174
180
 
175
- const ajv = new Ajv({ allErrors: true, strict: false, removeAdditional: false });
181
+ /**
182
+ * Format validation errors for display
183
+ * @param {Array} errors - Validation errors
184
+ * @returns {string} Formatted error message
185
+ */
186
+ function formatValidationErrors(errors) {
187
+ return errors.map(err => {
188
+ const path = err.instancePath || err.schemaPath;
189
+ return `${path} ${err.message}`;
190
+ }).join(', ');
191
+ }
176
192
 
177
- // Validate application (system) against external-system schema
193
+ /**
194
+ * Validate system against external-system schema
195
+ * @param {Object} systemJson - System JSON object
196
+ * @param {Object} externalSystemSchema - External system schema
197
+ * @param {Object} ajv - AJV instance
198
+ * @throws {Error} If validation fails
199
+ */
200
+ function validateSystemSchema(systemJson, externalSystemSchema, ajv) {
178
201
  const externalSystemSchemaId = externalSystemSchema.$id || 'https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-system.schema.json';
179
202
  ajv.addSchema(externalSystemSchema, externalSystemSchemaId);
180
203
  const validateSystem = ajv.compile(externalSystemSchema);
181
204
  const systemValid = validateSystem(systemJson);
182
205
 
183
206
  if (!systemValid) {
184
- // Filter out additionalProperties errors for required properties that aren't defined in schema
185
- // This handles schema inconsistencies where authentication is required but not defined in properties
186
- const filteredErrors = validateSystem.errors.filter(err => {
187
- if (err.keyword === 'additionalProperties' && err.params?.additionalProperty === 'authentication') {
188
- // Check if authentication is in required array
189
- const required = externalSystemSchema.required || [];
190
- if (required.includes('authentication')) {
191
- return false; // Ignore this error since authentication is required but not defined
192
- }
193
- }
194
- return true;
195
- });
196
-
207
+ const filteredErrors = filterValidationErrors(validateSystem.errors, externalSystemSchema);
197
208
  if (filteredErrors.length > 0) {
198
- const errors = filteredErrors.map(err => {
199
- const path = err.instancePath || err.schemaPath;
200
- return `${path} ${err.message}`;
201
- }).join(', ');
209
+ const errors = formatValidationErrors(filteredErrors);
202
210
  throw new Error(`System JSON does not match external-system schema: ${errors}`);
203
211
  }
204
212
  }
213
+ }
205
214
 
206
- // Validate each datasource against external-datasource schema
207
- const externalDatasourceSchemaId = datasourceSchemaToAdd.$id || 'https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-datasource.schema.json';
208
- ajv.addSchema(datasourceSchemaToAdd, externalDatasourceSchemaId);
209
- const validateDatasource = ajv.compile(datasourceSchemaToAdd);
215
+ /**
216
+ * Validate datasources against external-datasource schema
217
+ * @param {Array<Object>} datasourceJsons - Array of datasource JSON objects
218
+ * @param {Object} externalDatasourceSchema - External datasource schema
219
+ * @param {Object} ajv - AJV instance
220
+ * @throws {Error} If validation fails
221
+ */
222
+ function validateDatasourceSchemas(datasourceJsons, externalDatasourceSchema, ajv) {
223
+ const externalDatasourceSchemaId = externalDatasourceSchema.$id || 'https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-datasource.schema.json';
224
+ ajv.addSchema(externalDatasourceSchema, externalDatasourceSchemaId);
225
+ const validateDatasource = ajv.compile(externalDatasourceSchema);
210
226
 
211
227
  for (let i = 0; i < datasourceJsons.length; i++) {
212
228
  const datasourceValid = validateDatasource(datasourceJsons[i]);
213
229
  if (!datasourceValid) {
214
- const errors = validateDatasource.errors.map(err => {
215
- const path = err.instancePath || err.schemaPath;
216
- return `${path} ${err.message}`;
217
- }).join(', ');
230
+ const errors = formatValidationErrors(validateDatasource.errors);
218
231
  throw new Error(`Datasource ${i + 1} (${datasourceJsons[i].key || 'unknown'}) does not match external-datasource schema: ${errors}`);
219
232
  }
220
233
  }
234
+ }
235
+
236
+ /**
237
+ * Generates application-schema.json structure for external systems
238
+ * Combines system and datasource JSONs into application-level deployment format
239
+ * @async
240
+ * @function generateExternalSystemApplicationSchema
241
+ * @param {string} appName - Application name
242
+ * @returns {Promise<Object>} Application schema object
243
+ * @throws {Error} If generation fails
244
+ */
245
+ async function generateExternalSystemApplicationSchema(appName) {
246
+ if (!appName || typeof appName !== 'string') {
247
+ throw new Error('App name is required and must be a string');
248
+ }
249
+
250
+ const { appPath } = await detectAppType(appName);
251
+ const variablesPath = path.join(appPath, 'variables.yaml');
252
+
253
+ // Load variables.yaml
254
+ const { parsed: variables } = loadVariables(variablesPath);
255
+
256
+ if (!variables.externalIntegration) {
257
+ throw new Error('externalIntegration block not found in variables.yaml');
258
+ }
259
+
260
+ // Load system file and merge RBAC
261
+ const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
262
+ const systemFiles = variables.externalIntegration.systems || [];
263
+
264
+ if (systemFiles.length === 0) {
265
+ throw new Error('No system files specified in externalIntegration.systems');
266
+ }
267
+
268
+ const systemJson = await loadSystemFile(appPath, schemaBasePath, systemFiles[0]);
269
+
270
+ // Load datasource files
271
+ const datasourceFiles = variables.externalIntegration.dataSources || [];
272
+ const datasourceJsons = await loadDatasourceFiles(appPath, schemaBasePath, datasourceFiles);
273
+
274
+ // Build application-schema.json structure
275
+ const applicationSchema = buildBaseSchema(systemJson, datasourceJsons, variables.externalIntegration.version);
276
+
277
+ // Validate individual components against their schemas
278
+ const externalSystemSchema = require('./schema/external-system.schema.json');
279
+ const externalDatasourceSchema = require('./schema/external-datasource.schema.json');
280
+
281
+ // For draft-2020-12 schemas, remove $schema to avoid AJV issues
282
+ const datasourceSchemaToAdd = { ...externalDatasourceSchema };
283
+ if (datasourceSchemaToAdd.$schema && datasourceSchemaToAdd.$schema.includes('2020-12')) {
284
+ delete datasourceSchemaToAdd.$schema;
285
+ }
286
+
287
+ const ajv = new Ajv({ allErrors: true, strict: false, removeAdditional: false });
288
+
289
+ // Validate system against external-system schema
290
+ validateSystemSchema(systemJson, externalSystemSchema, ajv);
291
+
292
+ // Validate datasources against external-datasource schema
293
+ validateDatasourceSchemas(datasourceJsons, datasourceSchemaToAdd, ajv);
221
294
 
222
295
  return applicationSchema;
223
296
  }
@@ -63,17 +63,12 @@ function loadRbac(rbacPath) {
63
63
  }
64
64
 
65
65
  /**
66
- * Validates portalInput structure against schema requirements
67
- * @param {Object} portalInput - Portal input configuration to validate
66
+ * Validate required fields in portalInput
67
+ * @param {Object} portalInput - Portal input configuration
68
68
  * @param {string} variableName - Variable name for error messages
69
- * @throws {Error} If portalInput structure is invalid
69
+ * @throws {Error} If required fields are missing
70
70
  */
71
- function validatePortalInput(portalInput, variableName) {
72
- if (!portalInput || typeof portalInput !== 'object') {
73
- throw new Error(`Invalid portalInput for variable '${variableName}': must be an object`);
74
- }
75
-
76
- // Check required fields
71
+ function validateRequiredFields(portalInput, variableName) {
77
72
  if (!portalInput.field || typeof portalInput.field !== 'string') {
78
73
  throw new Error(`Invalid portalInput for variable '${variableName}': field is required and must be a string`);
79
74
  }
@@ -81,21 +76,48 @@ function validatePortalInput(portalInput, variableName) {
81
76
  if (!portalInput.label || typeof portalInput.label !== 'string') {
82
77
  throw new Error(`Invalid portalInput for variable '${variableName}': label is required and must be a string`);
83
78
  }
79
+ }
84
80
 
85
- // Validate field type
81
+ /**
82
+ * Validate field type in portalInput
83
+ * @param {Object} portalInput - Portal input configuration
84
+ * @param {string} variableName - Variable name for error messages
85
+ * @throws {Error} If field type is invalid
86
+ */
87
+ function validateFieldType(portalInput, variableName) {
86
88
  const validFieldTypes = ['password', 'text', 'textarea', 'select'];
87
89
  if (!validFieldTypes.includes(portalInput.field)) {
88
90
  throw new Error(`Invalid portalInput for variable '${variableName}': field must be one of: ${validFieldTypes.join(', ')}`);
89
91
  }
92
+ }
90
93
 
91
- // Validate select field requires options
94
+ /**
95
+ * Validate select field options
96
+ * @param {Object} portalInput - Portal input configuration
97
+ * @param {string} variableName - Variable name for error messages
98
+ * @throws {Error} If select field options are invalid
99
+ */
100
+ function validateSelectFieldOptions(portalInput, variableName) {
92
101
  if (portalInput.field === 'select') {
93
102
  if (!portalInput.options || !Array.isArray(portalInput.options) || portalInput.options.length === 0) {
94
103
  throw new Error(`Invalid portalInput for variable '${variableName}': select field requires a non-empty options array`);
95
104
  }
96
105
  }
97
106
 
98
- // Validate optional fields
107
+ if (portalInput.options !== undefined && portalInput.field !== 'select') {
108
+ if (Array.isArray(portalInput.options) && portalInput.options.length > 0) {
109
+ throw new Error(`Invalid portalInput for variable '${variableName}': options can only be used with select field type`);
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Validate optional fields in portalInput
116
+ * @param {Object} portalInput - Portal input configuration
117
+ * @param {string} variableName - Variable name for error messages
118
+ * @throws {Error} If optional fields are invalid
119
+ */
120
+ function validateOptionalFields(portalInput, variableName) {
99
121
  if (portalInput.placeholder !== undefined && typeof portalInput.placeholder !== 'string') {
100
122
  throw new Error(`Invalid portalInput for variable '${variableName}': placeholder must be a string`);
101
123
  }
@@ -109,13 +131,23 @@ function validatePortalInput(portalInput, variableName) {
109
131
  throw new Error(`Invalid portalInput for variable '${variableName}': validation must be an object`);
110
132
  }
111
133
  }
134
+ }
112
135
 
113
- if (portalInput.options !== undefined && portalInput.field !== 'select') {
114
- // Options should only be present for select fields
115
- if (Array.isArray(portalInput.options) && portalInput.options.length > 0) {
116
- throw new Error(`Invalid portalInput for variable '${variableName}': options can only be used with select field type`);
117
- }
136
+ /**
137
+ * Validates portalInput structure against schema requirements
138
+ * @param {Object} portalInput - Portal input configuration to validate
139
+ * @param {string} variableName - Variable name for error messages
140
+ * @throws {Error} If portalInput structure is invalid
141
+ */
142
+ function validatePortalInput(portalInput, variableName) {
143
+ if (!portalInput || typeof portalInput !== 'object') {
144
+ throw new Error(`Invalid portalInput for variable '${variableName}': must be an object`);
118
145
  }
146
+
147
+ validateRequiredFields(portalInput, variableName);
148
+ validateFieldType(portalInput, variableName);
149
+ validateSelectFieldOptions(portalInput, variableName);
150
+ validateOptionalFields(portalInput, variableName);
119
151
  }
120
152
 
121
153
  /**
@@ -84,98 +84,145 @@ function parseImageReference(imageString) {
84
84
  }
85
85
 
86
86
  /**
87
- * Extracts deployment JSON into variables.yaml structure
88
- * @function extractVariablesYaml
87
+ * Extract app section from deployment
89
88
  * @param {Object} deployment - Deployment JSON object
90
- * @returns {Object} Variables YAML structure
89
+ * @returns {Object|undefined} App section or undefined
91
90
  */
92
- function extractVariablesYaml(deployment) {
93
- if (!deployment || typeof deployment !== 'object') {
94
- throw new Error('Deployment object is required');
91
+ function extractAppSection(deployment) {
92
+ if (!deployment.key && !deployment.displayName && !deployment.description && !deployment.type) {
93
+ return undefined;
95
94
  }
96
95
 
97
- const variables = {};
96
+ const app = {};
97
+ if (deployment.key) app.key = deployment.key;
98
+ if (deployment.displayName) app.displayName = deployment.displayName;
99
+ if (deployment.description) app.description = deployment.description;
100
+ if (deployment.type) app.type = deployment.type;
101
+ return app;
102
+ }
98
103
 
99
- // Extract app information
100
- if (deployment.key || deployment.displayName || deployment.description || deployment.type) {
101
- variables.app = {};
102
- if (deployment.key) variables.app.key = deployment.key;
103
- if (deployment.displayName) variables.app.displayName = deployment.displayName;
104
- if (deployment.description) variables.app.description = deployment.description;
105
- if (deployment.type) variables.app.type = deployment.type;
104
+ /**
105
+ * Extract image section from deployment
106
+ * @param {Object} deployment - Deployment JSON object
107
+ * @returns {Object|undefined} Image section or undefined
108
+ */
109
+ function extractImageSection(deployment) {
110
+ if (!deployment.image) {
111
+ return undefined;
106
112
  }
107
113
 
108
- // Extract image information
109
- if (deployment.image) {
110
- const imageParts = parseImageReference(deployment.image);
111
- variables.image = {};
112
- if (imageParts.name) variables.image.name = imageParts.name;
113
- if (imageParts.registry) variables.image.registry = imageParts.registry;
114
- if (imageParts.tag) variables.image.tag = imageParts.tag;
115
- if (deployment.registryMode) variables.image.registryMode = deployment.registryMode;
116
- }
114
+ const imageParts = parseImageReference(deployment.image);
115
+ const image = {};
116
+ if (imageParts.name) image.name = imageParts.name;
117
+ if (imageParts.registry) image.registry = imageParts.registry;
118
+ if (imageParts.tag) image.tag = imageParts.tag;
119
+ if (deployment.registryMode) image.registryMode = deployment.registryMode;
120
+ return image;
121
+ }
117
122
 
118
- // Extract port
119
- if (deployment.port !== undefined) {
120
- variables.port = deployment.port;
123
+ /**
124
+ * Extract requirements section from deployment
125
+ * @param {Object} deployment - Deployment JSON object
126
+ * @returns {Object|undefined} Requirements section or undefined
127
+ */
128
+ function extractRequirementsSection(deployment) {
129
+ if (!deployment.requiresDatabase && !deployment.requiresRedis && !deployment.requiresStorage && !deployment.databases) {
130
+ return undefined;
121
131
  }
122
132
 
123
- // Extract requirements
124
- if (deployment.requiresDatabase || deployment.requiresRedis || deployment.requiresStorage || deployment.databases) {
125
- variables.requires = {};
126
- if (deployment.requiresDatabase !== undefined) variables.requires.database = deployment.requiresDatabase;
127
- if (deployment.requiresRedis !== undefined) variables.requires.redis = deployment.requiresRedis;
128
- if (deployment.requiresStorage !== undefined) variables.requires.storage = deployment.requiresStorage;
129
- if (deployment.databases) variables.requires.databases = deployment.databases;
130
- }
133
+ const requires = {};
134
+ if (deployment.requiresDatabase !== undefined) requires.database = deployment.requiresDatabase;
135
+ if (deployment.requiresRedis !== undefined) requires.redis = deployment.requiresRedis;
136
+ if (deployment.requiresStorage !== undefined) requires.storage = deployment.requiresStorage;
137
+ if (deployment.databases) requires.databases = deployment.databases;
138
+ return requires;
139
+ }
140
+
141
+ /**
142
+ * Extract optional sections from deployment
143
+ * @param {Object} deployment - Deployment JSON object
144
+ * @returns {Object} Object with optional sections
145
+ */
146
+ function extractOptionalSections(deployment) {
147
+ const optional = {};
131
148
 
132
- // Extract healthCheck
133
149
  if (deployment.healthCheck) {
134
- variables.healthCheck = deployment.healthCheck;
150
+ optional.healthCheck = deployment.healthCheck;
135
151
  }
136
-
137
- // Extract authentication
138
152
  if (deployment.authentication) {
139
- variables.authentication = { ...deployment.authentication };
153
+ optional.authentication = { ...deployment.authentication };
140
154
  }
141
-
142
- // Extract build
143
155
  if (deployment.build) {
144
- variables.build = deployment.build;
156
+ optional.build = deployment.build;
145
157
  }
146
-
147
- // Extract repository
148
158
  if (deployment.repository) {
149
- variables.repository = deployment.repository;
159
+ optional.repository = deployment.repository;
150
160
  }
151
-
152
- // Extract deployment config
153
161
  if (deployment.deployment) {
154
- variables.deployment = deployment.deployment;
162
+ optional.deployment = deployment.deployment;
155
163
  }
156
-
157
- // Extract other optional fields
158
164
  if (deployment.startupCommand) {
159
- variables.startupCommand = deployment.startupCommand;
165
+ optional.startupCommand = deployment.startupCommand;
160
166
  }
161
167
  if (deployment.runtimeVersion) {
162
- variables.runtimeVersion = deployment.runtimeVersion;
168
+ optional.runtimeVersion = deployment.runtimeVersion;
163
169
  }
164
170
  if (deployment.scaling) {
165
- variables.scaling = deployment.scaling;
171
+ optional.scaling = deployment.scaling;
166
172
  }
167
173
  if (deployment.frontDoorRouting) {
168
- variables.frontDoorRouting = deployment.frontDoorRouting;
174
+ optional.frontDoorRouting = deployment.frontDoorRouting;
169
175
  }
170
-
171
- // Extract roles and permissions (if present)
172
176
  if (deployment.roles) {
173
- variables.roles = deployment.roles;
177
+ optional.roles = deployment.roles;
174
178
  }
175
179
  if (deployment.permissions) {
176
- variables.permissions = deployment.permissions;
180
+ optional.permissions = deployment.permissions;
181
+ }
182
+
183
+ return optional;
184
+ }
185
+
186
+ /**
187
+ * Extracts deployment JSON into variables.yaml structure
188
+ * @function extractVariablesYaml
189
+ * @param {Object} deployment - Deployment JSON object
190
+ * @returns {Object} Variables YAML structure
191
+ */
192
+ function extractVariablesYaml(deployment) {
193
+ if (!deployment || typeof deployment !== 'object') {
194
+ throw new Error('Deployment object is required');
177
195
  }
178
196
 
197
+ const variables = {};
198
+
199
+ // Extract app section
200
+ const appSection = extractAppSection(deployment);
201
+ if (appSection) {
202
+ variables.app = appSection;
203
+ }
204
+
205
+ // Extract image section
206
+ const imageSection = extractImageSection(deployment);
207
+ if (imageSection) {
208
+ variables.image = imageSection;
209
+ }
210
+
211
+ // Extract port
212
+ if (deployment.port !== undefined) {
213
+ variables.port = deployment.port;
214
+ }
215
+
216
+ // Extract requirements section
217
+ const requirementsSection = extractRequirementsSection(deployment);
218
+ if (requirementsSection) {
219
+ variables.requires = requirementsSection;
220
+ }
221
+
222
+ // Extract optional sections
223
+ const optionalSections = extractOptionalSections(deployment);
224
+ Object.assign(variables, optionalSections);
225
+
179
226
  return variables;
180
227
  }
181
228