@aifabrix/builder 2.39.2 → 2.40.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 (116) hide show
  1. package/.cursor/rules/project-rules.mdc +6 -6
  2. package/README.md +2 -2
  3. package/babel.config.js +6 -0
  4. package/integration/hubspot/README.md +53 -141
  5. package/integration/hubspot/application.yaml +37 -0
  6. package/integration/hubspot/env.template +2 -11
  7. package/integration/hubspot/hubspot-deploy.json +1 -0
  8. package/integration/hubspot/test.js +5 -5
  9. package/lib/api/credentials.api.js +5 -5
  10. package/lib/api/deployments.api.js +2 -2
  11. package/lib/api/pipeline.api.js +17 -17
  12. package/lib/api/wizard.api.js +2 -2
  13. package/lib/app/config.js +11 -6
  14. package/lib/app/deploy-config.js +13 -16
  15. package/lib/app/deploy.js +29 -22
  16. package/lib/app/display.js +1 -1
  17. package/lib/app/dockerfile.js +11 -12
  18. package/lib/app/helpers.js +51 -13
  19. package/lib/app/index.js +14 -2
  20. package/lib/app/prompts.js +37 -45
  21. package/lib/app/push.js +8 -11
  22. package/lib/app/readme.js +16 -12
  23. package/lib/app/register.js +3 -3
  24. package/lib/app/run-helpers.js +31 -22
  25. package/lib/app/run.js +44 -5
  26. package/lib/app/show-display.js +104 -44
  27. package/lib/app/show.js +123 -43
  28. package/lib/build/index.js +11 -18
  29. package/lib/cli/setup-app.js +38 -28
  30. package/lib/cli/setup-auth.js +18 -15
  31. package/lib/cli/setup-credential-deployment.js +3 -1
  32. package/lib/cli/setup-external-system.js +35 -16
  33. package/lib/cli/setup-infra.js +45 -23
  34. package/lib/cli/setup-utility.js +79 -31
  35. package/lib/commands/app-logs.js +165 -10
  36. package/lib/commands/app.js +30 -26
  37. package/lib/commands/convert.js +202 -0
  38. package/lib/commands/credential-list.js +78 -17
  39. package/lib/commands/datasource.js +24 -24
  40. package/lib/commands/deployment-list.js +13 -6
  41. package/lib/commands/up-common.js +80 -42
  42. package/lib/commands/up-dataplane.js +15 -14
  43. package/lib/commands/up-miso.js +15 -14
  44. package/lib/commands/upload.js +163 -0
  45. package/lib/commands/wizard-core.js +5 -4
  46. package/lib/core/diff.js +84 -9
  47. package/lib/core/key-generator.js +9 -12
  48. package/lib/core/secrets-docker-env.js +2 -2
  49. package/lib/core/secrets.js +3 -2
  50. package/lib/core/templates.js +2 -2
  51. package/lib/datasource/deploy.js +2 -1
  52. package/lib/deployment/deployer.js +76 -48
  53. package/lib/external-system/delete.js +0 -1
  54. package/lib/external-system/deploy-helpers.js +5 -6
  55. package/lib/external-system/deploy.js +7 -2
  56. package/lib/external-system/download-helpers.js +4 -4
  57. package/lib/external-system/download.js +11 -10
  58. package/lib/external-system/generator.js +19 -17
  59. package/lib/external-system/test.js +10 -15
  60. package/lib/generator/builders.js +1 -1
  61. package/lib/generator/external-controller-manifest.js +26 -29
  62. package/lib/generator/external-schema-utils.js +6 -18
  63. package/lib/generator/external.js +32 -27
  64. package/lib/generator/github.js +1 -1
  65. package/lib/generator/helpers.js +12 -19
  66. package/lib/generator/index.js +15 -15
  67. package/lib/generator/parse-image.js +35 -0
  68. package/lib/generator/split-readme.js +105 -0
  69. package/lib/generator/split-variables.js +149 -0
  70. package/lib/generator/split.js +86 -246
  71. package/lib/generator/wizard.js +46 -69
  72. package/lib/schema/application-schema.json +4 -4
  73. package/lib/schema/deployment-rules.yaml +0 -4
  74. package/lib/schema/external-datasource.schema.json +5 -0
  75. package/lib/schema/external-system.schema.json +10 -0
  76. package/lib/utils/app-config-resolver.js +52 -0
  77. package/lib/utils/app-register-api.js +1 -1
  78. package/lib/utils/app-register-auth.js +1 -1
  79. package/lib/utils/app-register-config.js +16 -23
  80. package/lib/utils/app-register-display.js +22 -3
  81. package/lib/utils/app-register-validator.js +2 -2
  82. package/lib/utils/cli-utils.js +47 -3
  83. package/lib/utils/config-format.js +154 -0
  84. package/lib/utils/config-paths.js +19 -52
  85. package/lib/utils/config-tokens.js +1 -0
  86. package/lib/utils/docker-build.js +71 -94
  87. package/lib/utils/dockerfile-utils.js +1 -1
  88. package/lib/utils/env-copy.js +4 -4
  89. package/lib/utils/env-ports.js +2 -2
  90. package/lib/utils/error-formatter.js +1 -1
  91. package/lib/utils/error-formatters/validation-errors.js +1 -1
  92. package/lib/utils/external-readme.js +12 -5
  93. package/lib/utils/external-system-test-helpers.js +2 -0
  94. package/lib/utils/health-check.js +55 -66
  95. package/lib/utils/image-version.js +12 -21
  96. package/lib/utils/paths.js +39 -66
  97. package/lib/utils/port-resolver.js +8 -8
  98. package/lib/utils/schema-loader.js +22 -0
  99. package/lib/utils/schema-resolver.js +23 -33
  100. package/lib/utils/secrets-helpers.js +7 -7
  101. package/lib/utils/secrets-utils.js +10 -12
  102. package/lib/utils/template-helpers.js +13 -13
  103. package/lib/utils/token-manager.js +20 -2
  104. package/lib/utils/variable-transformer.js +2 -2
  105. package/lib/validation/validate-display.js +3 -4
  106. package/lib/validation/validate.js +33 -27
  107. package/lib/validation/validator.js +50 -30
  108. package/package.json +2 -1
  109. package/templates/README.md +1 -1
  110. package/templates/applications/README.md.hbs +3 -3
  111. package/templates/applications/miso-controller/env.template +3 -1
  112. package/templates/external-system/README.md.hbs +4 -4
  113. package/integration/hubspot/variables.yaml +0 -17
  114. /package/templates/applications/dataplane/{variables.yaml → application.yaml} +0 -0
  115. /package/templates/applications/keycloak/{variables.yaml → application.yaml} +0 -0
  116. /package/templates/applications/miso-controller/{variables.yaml → application.yaml} +0 -0
@@ -10,24 +10,17 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const yaml = require('js-yaml');
13
+ const { loadConfigFile } = require('../utils/config-format');
13
14
 
14
15
  /**
15
- * Loads variables.yaml file
16
- * @param {string} variablesPath - Path to variables.yaml
17
- * @returns {Object} Parsed variables
18
- * @throws {Error} If file not found or invalid YAML
16
+ * Loads application config file (application.yaml, application.json, or legacy path) via converter.
17
+ * @param {string} configPath - Path to application config file
18
+ * @returns {Object} Object with parsed config: { parsed }
19
+ * @throws {Error} If file not found or invalid YAML/JSON
19
20
  */
20
- function loadVariables(variablesPath) {
21
- if (!fs.existsSync(variablesPath)) {
22
- throw new Error(`variables.yaml not found: ${variablesPath}`);
23
- }
24
-
25
- const variablesContent = fs.readFileSync(variablesPath, 'utf8');
26
- try {
27
- return { content: variablesContent, parsed: yaml.load(variablesContent) };
28
- } catch (error) {
29
- throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
30
- }
21
+ function loadVariables(configPath) {
22
+ const parsed = loadConfigFile(configPath);
23
+ return { parsed };
31
24
  }
32
25
 
33
26
  /**
@@ -151,16 +144,16 @@ function validatePortalInput(portalInput, variableName) {
151
144
  }
152
145
 
153
146
  /**
154
- * Parses environment variables from env.template and merges portalInput from variables.yaml
147
+ * Parses environment variables from env.template and merges portalInput from application config
155
148
  * @param {string} envTemplate - Content of env.template file
156
- * @param {Object|null} [variablesConfig=null] - Optional configuration from variables.yaml
149
+ * @param {Object|null} [variablesConfig=null] - Optional configuration from application.yaml
157
150
  * @returns {Array<Object>} Configuration array with merged portalInput
158
151
  * @throws {Error} If portalInput structure is invalid
159
152
  */
160
153
  /**
161
154
  * Creates a map of portalInput configurations from variables config
162
155
  * @function createPortalInputMap
163
- * @param {Object|null} variablesConfig - Configuration from variables.yaml
156
+ * @param {Object|null} variablesConfig - Configuration from application.yaml
164
157
  * @returns {Map} Map of variable names to portalInput configurations
165
158
  */
166
159
  function createPortalInputMap(variablesConfig) {
@@ -250,7 +243,7 @@ function createConfigItem(key, value, location, required, portalInputMap) {
250
243
  required
251
244
  };
252
245
 
253
- // Merge portalInput if it exists in variables.yaml
246
+ // Merge portalInput if it exists in application config
254
247
  if (portalInputMap.has(key)) {
255
248
  configItem.portalInput = portalInputMap.get(key);
256
249
  }
@@ -2,7 +2,7 @@
2
2
  * AI Fabrix Builder Deployment JSON Generator
3
3
  *
4
4
  * This module generates deployment JSON manifests for Miso Controller.
5
- * Combines variables.yaml, env.template, and rbac.yaml into deployment configuration.
5
+ * Combines application.yaml, env.template, and rbac.yaml into deployment configuration.
6
6
  *
7
7
  * @fileoverview Deployment JSON generation for AI Fabrix Builder
8
8
  * @author AI Fabrix Team
@@ -13,7 +13,8 @@ const fs = require('fs');
13
13
  const path = require('path');
14
14
  const _validator = require('../validation/validator');
15
15
  const builders = require('./builders');
16
- const { detectAppType, getDeployJsonPath } = require('../utils/paths');
16
+ const { detectAppType, getDeployJsonPath, resolveApplicationConfigPath } = require('../utils/paths');
17
+ const { logOfflinePathWhenType } = require('../utils/cli-utils');
17
18
  const splitFunctions = require('./split');
18
19
  const { loadVariables, loadEnvTemplate, loadRbac, parseEnvironmentVariables } = require('./helpers');
19
20
  const { generateExternalSystemApplicationSchema, splitExternalApplicationSchema } = require('./external');
@@ -24,13 +25,13 @@ const { resolveVersionForApp } = require('../utils/image-version');
24
25
  * Generates deployment JSON from application configuration files
25
26
  * Creates <app-name>-deploy.json for regular apps (consistent naming)
26
27
  * For external systems, generates application-schema.json
27
- * For regular apps, generates deployment manifest from variables.yaml, env.template, rbac.yaml
28
+ * For regular apps, generates deployment manifest from application.yaml, env.template, rbac.yaml
28
29
  *
29
30
  * @async
30
31
  * @function generateDeployJson
31
32
  * @param {string} appName - Name of the application
32
33
  * @param {Object} [options] - Generation options
33
- * @param {string} [options.type] - Forced application type (external)
34
+ *
34
35
  * @returns {Promise<string>} Path to generated deployment JSON file
35
36
  * @throws {Error} If generation fails or configuration is invalid
36
37
  *
@@ -46,7 +47,7 @@ const { resolveVersionForApp } = require('../utils/image-version');
46
47
  * @returns {Object} Loaded configuration files
47
48
  */
48
49
  function loadDeploymentConfigFiles(appPath, appType, appName) {
49
- const variablesPath = path.join(appPath, 'variables.yaml');
50
+ const variablesPath = resolveApplicationConfigPath(appPath);
50
51
  const templatePath = path.join(appPath, 'env.template');
51
52
  const rbacPath = path.join(appPath, 'rbac.yaml');
52
53
  const jsonPath = getDeployJsonPath(appName, appType, true); // Use new naming
@@ -69,7 +70,7 @@ function loadDeploymentConfigFiles(appPath, appType, appName) {
69
70
  * @throws {Error} If validation fails
70
71
  */
71
72
  function buildAndValidateDeployment(appName, variables, envTemplate, rbac) {
72
- // Parse environment variables from template and merge portalInput from variables.yaml
73
+ // Parse environment variables from template and merge portalInput from application config
73
74
  const configuration = parseEnvironmentVariables(envTemplate, variables);
74
75
 
75
76
  // Build deployment manifest (Controller computes deploymentKey from schema)
@@ -93,17 +94,17 @@ function buildAndValidateDeployment(appName, variables, envTemplate, rbac) {
93
94
  * @param {string} appName - Application name
94
95
  * @param {Object} [options] - Options (e.g. type for external)
95
96
  * @returns {Promise<{ deployment: Object, appPath: string }>} Manifest and app path
96
- * @throws {Error} If variables.yaml/env.template missing or generation fails
97
+ * @throws {Error} If application config/env.template missing or generation fails
97
98
  */
98
99
  async function buildDeploymentManifestInMemory(appName, options = {}) {
99
100
  if (!appName || typeof appName !== 'string') {
100
101
  throw new Error('App name is required and must be a string');
101
102
  }
102
103
 
103
- const { isExternal, appPath, appType } = await detectAppType(appName, options);
104
+ const { isExternal, appPath, appType } = await detectAppType(appName);
104
105
 
105
106
  if (isExternal) {
106
- const manifest = await generateControllerManifest(appName);
107
+ const manifest = await generateControllerManifest(appName, options);
107
108
  return { deployment: manifest, appPath };
108
109
  }
109
110
 
@@ -124,13 +125,13 @@ async function generateDeployJson(appName, options = {}) {
124
125
  throw new Error('App name is required and must be a string');
125
126
  }
126
127
 
127
- // Detect app type and get correct path (integration or builder)
128
- const { isExternal, appPath, appType } = await detectAppType(appName, options);
128
+ // Detect app type and get correct path (integration first, then builder)
129
+ const { isExternal, appPath, appType } = await detectAppType(appName);
130
+ logOfflinePathWhenType(appPath);
129
131
 
130
132
  // Check if app type is external
131
133
  if (isExternal) {
132
- // Generate controller-compatible manifest format
133
- const manifest = await generateControllerManifest(appName);
134
+ const manifest = await generateControllerManifest(appName, options);
134
135
 
135
136
  // Determine system key for file naming
136
137
  const systemKey = manifest.key || appName;
@@ -161,8 +162,7 @@ async function generateDeployJsonWithValidation(appName, options = {}) {
161
162
  const jsonContent = fs.readFileSync(jsonPath, 'utf8');
162
163
  const deployment = JSON.parse(jsonContent);
163
164
 
164
- // Detect if this is an external system
165
- const { isExternal } = await detectAppType(appName, options);
165
+ const { isExternal } = await detectAppType(appName);
166
166
 
167
167
  // For external systems, skip deployment JSON validation (they use external system JSON structure)
168
168
  if (isExternal) {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Parses image reference string into components.
3
+ * @fileoverview Image reference parser for deployment JSON
4
+ * @author AI Fabrix Team
5
+ * @version 2.0.0
6
+ */
7
+
8
+ /**
9
+ * Parses image reference string into components
10
+ * @function parseImageReference
11
+ * @param {string} imageString - Full image string (e.g., "registry/name:tag")
12
+ * @returns {Object} Object with registry, name, and tag
13
+ */
14
+ function parseImageReference(imageString) {
15
+ if (!imageString || typeof imageString !== 'string') {
16
+ return { registry: null, name: null, tag: 'latest' };
17
+ }
18
+
19
+ const parts = imageString.split('/');
20
+ let registry = null;
21
+ let nameAndTag = imageString;
22
+
23
+ if (parts.length > 1 && parts[0].includes('.')) {
24
+ registry = parts[0];
25
+ nameAndTag = parts.slice(1).join('/');
26
+ }
27
+
28
+ const tagIndex = nameAndTag.lastIndexOf(':');
29
+ const name = tagIndex !== -1 ? nameAndTag.substring(0, tagIndex) : nameAndTag;
30
+ const tag = tagIndex !== -1 ? nameAndTag.substring(tagIndex + 1) : 'latest';
31
+
32
+ return { registry, name, tag };
33
+ }
34
+
35
+ module.exports = { parseImageReference };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * README generation from deployment JSON for split flow.
3
+ * @fileoverview Builds readme config and generates README from deployment JSON
4
+ * @author AI Fabrix Team
5
+ * @version 2.0.0
6
+ */
7
+
8
+ const { generateReadmeMd } = require('../app/readme');
9
+ const { parseImageReference } = require('./parse-image');
10
+
11
+ /**
12
+ * Builds config for external-system README from deployment
13
+ * @param {Object} deployment - Deployment JSON object
14
+ * @returns {{ appName: string, config: Object }}
15
+ */
16
+ function buildReadmeConfigForExternal(deployment) {
17
+ const system = deployment.system;
18
+ const appName = system.key || deployment.key || 'external-system';
19
+ const dataSources = deployment.dataSources || deployment.datasources || [];
20
+ return {
21
+ appName,
22
+ config: {
23
+ type: 'external',
24
+ systemKey: appName,
25
+ systemType: system.type || 'openapi',
26
+ systemDisplayName: system.displayName || appName,
27
+ systemDescription: system.description || `External system integration for ${appName}`,
28
+ datasourceCount: dataSources.length,
29
+ datasources: dataSources
30
+ }
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Builds config for application README from deployment
36
+ * @param {Object} deployment - Deployment JSON object
37
+ * @returns {{ appName: string, config: Object }}
38
+ */
39
+ function buildReadmeConfigForApp(deployment) {
40
+ const appName = deployment.key || 'application';
41
+ const imageParts = parseImageReference(deployment.image || '');
42
+ const port = deployment.port !== undefined ? deployment.port : 3000;
43
+ const imageName = imageParts.name || appName;
44
+ const registry = imageParts.registry || 'myacr.azurecr.io';
45
+
46
+ const config = {
47
+ type: deployment.type || 'webapp',
48
+ displayName: deployment.displayName,
49
+ description: deployment.description,
50
+ port,
51
+ build: { localPort: port },
52
+ image: { name: imageName, registry },
53
+ registry,
54
+ database: deployment.requiresDatabase,
55
+ requires: {
56
+ database: deployment.requiresDatabase,
57
+ redis: deployment.requiresRedis,
58
+ storage: deployment.requiresStorage
59
+ },
60
+ redis: deployment.requiresRedis,
61
+ storage: deployment.requiresStorage,
62
+ authentication: !!deployment.authentication
63
+ };
64
+
65
+ if (config.type === 'external') {
66
+ config.systemKey = appName;
67
+ config.systemType = deployment.systemType || 'openapi';
68
+ config.systemDisplayName = deployment.displayName || appName;
69
+ config.systemDescription = deployment.description || `External system integration for ${appName}`;
70
+ config.datasourceCount = 0;
71
+ config.datasources = deployment.dataSources || deployment.datasources || [];
72
+ }
73
+
74
+ return { appName, config };
75
+ }
76
+
77
+ /**
78
+ * Builds application config shape from deployment JSON for README template context.
79
+ * @param {Object} deployment - Deployment JSON object
80
+ * @returns {{ appName: string, config: Object }}
81
+ */
82
+ function buildReadmeConfigFromDeployment(deployment) {
83
+ if (deployment.system && typeof deployment.system === 'object') {
84
+ return buildReadmeConfigForExternal(deployment);
85
+ }
86
+ return buildReadmeConfigForApp(deployment);
87
+ }
88
+
89
+ /**
90
+ * Generates README.md content from deployment JSON.
91
+ * @param {Object} deployment - Deployment JSON object
92
+ * @returns {string} README.md content
93
+ */
94
+ function generateReadmeFromDeployJson(deployment) {
95
+ if (!deployment || typeof deployment !== 'object') {
96
+ throw new Error('Deployment object is required');
97
+ }
98
+ const { appName, config } = buildReadmeConfigFromDeployment(deployment);
99
+ return generateReadmeMd(appName, config);
100
+ }
101
+
102
+ module.exports = {
103
+ buildReadmeConfigFromDeployment,
104
+ generateReadmeFromDeployJson
105
+ };
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Variables YAML extraction for deployment JSON split.
3
+ * Extracts application.yaml (variables) structure from deployment JSON.
4
+ *
5
+ * @fileoverview Split variables extraction for deployment JSON reverse conversion
6
+ * @author AI Fabrix Team
7
+ * @version 2.0.0
8
+ */
9
+
10
+ const { parseImageReference } = require('./parse-image');
11
+
12
+ function extractAppSection(deployment) {
13
+ if (!deployment.key && !deployment.displayName && !deployment.description && !deployment.type) {
14
+ return undefined;
15
+ }
16
+ const app = {};
17
+ if (deployment.key) app.key = deployment.key;
18
+ if (deployment.displayName) app.displayName = deployment.displayName;
19
+ if (deployment.description) app.description = deployment.description;
20
+ if (deployment.type) app.type = deployment.type;
21
+ if (deployment.version) app.version = deployment.version;
22
+ return app;
23
+ }
24
+
25
+ function extractImageSection(deployment) {
26
+ if (!deployment.image) return undefined;
27
+ const imageParts = parseImageReference(deployment.image);
28
+ const image = {};
29
+ if (imageParts.name) image.name = imageParts.name;
30
+ if (imageParts.registry) image.registry = imageParts.registry;
31
+ if (imageParts.tag) image.tag = imageParts.tag;
32
+ if (deployment.registryMode) image.registryMode = deployment.registryMode;
33
+ return image;
34
+ }
35
+
36
+ function extractRequirementsSection(deployment) {
37
+ if (!deployment.requiresDatabase && !deployment.requiresRedis && !deployment.requiresStorage && !deployment.databases) {
38
+ return undefined;
39
+ }
40
+ const requires = {};
41
+ if (deployment.requiresDatabase !== undefined) requires.database = deployment.requiresDatabase;
42
+ if (deployment.requiresRedis !== undefined) requires.redis = deployment.requiresRedis;
43
+ if (deployment.requiresStorage !== undefined) requires.storage = deployment.requiresStorage;
44
+ if (deployment.databases) requires.databases = deployment.databases;
45
+ return requires;
46
+ }
47
+
48
+ function extractOptionalSection(deployment, sectionName, optional) {
49
+ if (!deployment[sectionName]) return;
50
+ optional[sectionName] = sectionName === 'authentication'
51
+ ? { ...deployment[sectionName] }
52
+ : deployment[sectionName];
53
+ }
54
+
55
+ function extractOptionalSections(deployment) {
56
+ const optional = {};
57
+ const names = [
58
+ 'healthCheck', 'authentication', 'build', 'repository', 'deployment',
59
+ 'startupCommand', 'runtimeVersion', 'scaling', 'frontDoorRouting'
60
+ ];
61
+ for (const sectionName of names) {
62
+ extractOptionalSection(deployment, sectionName, optional);
63
+ }
64
+ return optional;
65
+ }
66
+
67
+ /**
68
+ * Portal-only configuration for application.yaml (name + portalInput per entry).
69
+ * @param {Array} configuration - Configuration array from deployment JSON
70
+ * @returns {Array<{ name: string, portalInput: Object }>}
71
+ */
72
+ function extractPortalInputConfiguration(configuration) {
73
+ if (!Array.isArray(configuration) || configuration.length === 0) return [];
74
+ return configuration
75
+ .filter(item => item && item.portalInput && typeof item.name === 'string')
76
+ .map(item => ({ name: item.name, portalInput: item.portalInput }));
77
+ }
78
+
79
+ /**
80
+ * Datasource filename for externalIntegration.dataSources.
81
+ * @param {string} systemKey - System key
82
+ * @param {Object} datasource - Datasource from deployment.dataSources
83
+ * @param {number} index - Index
84
+ * @returns {string} Filename e.g. test-hubspot-datasource-companies-data.yaml
85
+ */
86
+ function getExternalDatasourceFileName(systemKey, datasource, index) {
87
+ const key = datasource.key || '';
88
+ let suffix;
89
+ if (key.startsWith(`${systemKey}-deploy-`)) suffix = key.slice(`${systemKey}-deploy-`.length);
90
+ else if (key.startsWith(`${systemKey}-`)) suffix = key.slice(systemKey.length + 1);
91
+ else if (key) suffix = key;
92
+ else suffix = datasource.entityType || datasource.entityKey || `entity${index + 1}`;
93
+ return `${systemKey}-datasource-${suffix}.yaml`;
94
+ }
95
+
96
+ function extractVariablesYamlForExternal(deployment) {
97
+ const system = deployment.system;
98
+ const systemKey = system.key || 'external-system';
99
+ const dataSourcesList = deployment.dataSources || deployment.datasources || [];
100
+ const variables = {
101
+ app: {
102
+ key: systemKey,
103
+ displayName: system.displayName || systemKey,
104
+ description: system.description || `External system integration for ${systemKey}`,
105
+ type: 'external'
106
+ },
107
+ externalIntegration: {
108
+ schemaBasePath: './',
109
+ systems: [`${systemKey}-system.yaml`],
110
+ dataSources: dataSourcesList.map((ds, i) => getExternalDatasourceFileName(systemKey, ds, i)),
111
+ autopublish: true,
112
+ version: '1.0.0'
113
+ }
114
+ };
115
+ const portalOnlyConfig = extractPortalInputConfiguration(deployment.configuration);
116
+ if (portalOnlyConfig.length > 0) variables.configuration = portalOnlyConfig;
117
+ return variables;
118
+ }
119
+
120
+ /**
121
+ * Extracts deployment JSON into application config (variables) structure.
122
+ * @param {Object} deployment - Deployment JSON object
123
+ * @returns {Object} Variables YAML structure
124
+ */
125
+ function extractVariablesYaml(deployment) {
126
+ if (!deployment || typeof deployment !== 'object') {
127
+ throw new Error('Deployment object is required');
128
+ }
129
+ if (deployment.system && typeof deployment.system === 'object') {
130
+ return extractVariablesYamlForExternal(deployment);
131
+ }
132
+ const variables = {};
133
+ const appSection = extractAppSection(deployment);
134
+ if (appSection) variables.app = appSection;
135
+ const imageSection = extractImageSection(deployment);
136
+ if (imageSection) variables.image = imageSection;
137
+ if (deployment.port !== undefined) variables.port = deployment.port;
138
+ const requirementsSection = extractRequirementsSection(deployment);
139
+ if (requirementsSection) variables.requires = requirementsSection;
140
+ Object.assign(variables, extractOptionalSections(deployment));
141
+ const portalOnlyConfig = extractPortalInputConfiguration(deployment.configuration);
142
+ if (portalOnlyConfig.length > 0) variables.configuration = portalOnlyConfig;
143
+ return variables;
144
+ }
145
+
146
+ module.exports = {
147
+ extractVariablesYaml,
148
+ getExternalDatasourceFileName
149
+ };