@aifabrix/builder 2.46.0 → 2.47.1

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 (191) hide show
  1. package/.npmrc.token +1 -0
  2. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  3. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  4. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  5. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  7. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  8. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  9. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  10. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  13. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  14. package/.temp/validation/summary.txt +5 -0
  15. package/jest.isolated-projects.js +18 -2
  16. package/jest.projects.js +24 -0
  17. package/lib/api/dataplane-health.api.js +19 -7
  18. package/lib/api/lifecycle.api.js +22 -1
  19. package/lib/api/resource-types.api.js +107 -0
  20. package/lib/api/runtime-configuration.api.js +72 -0
  21. package/lib/api/types/resource-types.types.js +45 -0
  22. package/lib/api/user-groups.api.js +9 -9
  23. package/lib/api/users.api.js +8 -8
  24. package/lib/api/wizard.api.js +10 -2
  25. package/lib/app/down.js +58 -26
  26. package/lib/app/index.js +2 -2
  27. package/lib/app/run-resolve-image.js +2 -1
  28. package/lib/app/run-stop-containers.js +113 -0
  29. package/lib/app/run.js +1 -31
  30. package/lib/app/show-display.js +35 -0
  31. package/lib/app/show.js +32 -1
  32. package/lib/build/index.js +17 -5
  33. package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
  34. package/lib/channels/add-channel.js +348 -0
  35. package/lib/channels/channel-artifact-generator.js +141 -0
  36. package/lib/channels/channel-audit.js +101 -0
  37. package/lib/channels/channel-auth-context.js +37 -0
  38. package/lib/channels/channel-credential.js +200 -0
  39. package/lib/channels/channel-display-name.js +64 -0
  40. package/lib/channels/channel-external-system.js +106 -0
  41. package/lib/channels/channel-integration-generator.js +274 -0
  42. package/lib/channels/channel-probe.js +89 -0
  43. package/lib/channels/channel-runtime-resolver.js +152 -0
  44. package/lib/channels/channel-status-display.js +141 -0
  45. package/lib/channels/channel-targets.js +115 -0
  46. package/lib/cli/index.js +18 -4
  47. package/lib/cli/infra-guided-auth.js +36 -12
  48. package/lib/cli/setup-app.js +54 -3
  49. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  50. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  51. package/lib/cli/setup-npm.js +59 -0
  52. package/lib/cli/setup-platform.js +1 -1
  53. package/lib/cli/setup-secrets.js +1 -1
  54. package/lib/cli/setup-utility-help-after.js +4 -1
  55. package/lib/cli/setup-utility-resolve.js +8 -5
  56. package/lib/commands/app-logs.js +54 -26
  57. package/lib/commands/app.js +41 -0
  58. package/lib/commands/auth-status-dataplane-version.js +97 -61
  59. package/lib/commands/auth-status.js +2 -7
  60. package/lib/commands/channel.js +58 -0
  61. package/lib/commands/datasource.js +1 -1
  62. package/lib/commands/identity-apply-sync.js +49 -8
  63. package/lib/commands/identity-group-cli.js +66 -26
  64. package/lib/commands/identity-membership-role-cache.js +58 -37
  65. package/lib/commands/identity-shared.js +22 -1
  66. package/lib/commands/identity-user-cli.js +80 -34
  67. package/lib/commands/login-device.js +11 -3
  68. package/lib/commands/login.js +8 -3
  69. package/lib/commands/npm-config.js +103 -0
  70. package/lib/commands/repair-auth-apply.js +30 -4
  71. package/lib/commands/repair-auth-config.js +8 -13
  72. package/lib/commands/repair-datasource-com-v2.js +15 -0
  73. package/lib/commands/repair-datasource-service-contract.js +206 -0
  74. package/lib/commands/repair-datasource.js +53 -18
  75. package/lib/commands/repair-env-template.js +12 -12
  76. package/lib/commands/repair-openapi-sync.js +26 -4
  77. package/lib/commands/repair-steps.js +28 -13
  78. package/lib/commands/repair-system-alignment.js +7 -4
  79. package/lib/commands/resource-type.js +371 -0
  80. package/lib/commands/setup-modes.js +1 -1
  81. package/lib/commands/setup-prompts-platform-mode.js +24 -21
  82. package/lib/commands/test-trust-external.js +39 -90
  83. package/lib/commands/up-common.js +33 -1
  84. package/lib/commands/up-dataplane.js +8 -4
  85. package/lib/commands/upload.js +24 -3
  86. package/lib/commands/verify-trust-command-action.js +4 -1
  87. package/lib/commands/verify-trust-external.js +12 -20
  88. package/lib/core/config-setup-platform-mode.js +3 -3
  89. package/lib/core/setup-platform-mode.js +41 -6
  90. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  91. package/lib/datasource/capability/capability-manifest.js +254 -0
  92. package/lib/datasource/capability/capability-resolve.js +3 -4
  93. package/lib/datasource/capability/copy-operations.js +5 -3
  94. package/lib/datasource/capability/remove-operations.js +2 -1
  95. package/lib/datasource/capability/run-capability-copy.js +32 -4
  96. package/lib/datasource/capability/run-capability-diff.js +3 -3
  97. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  98. package/lib/datasource/capability/run-capability-edit.js +4 -7
  99. package/lib/datasource/capability/run-capability-relate.js +2 -4
  100. package/lib/datasource/capability/run-capability-remove.js +2 -4
  101. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  102. package/lib/datasource/datasource-identifier.js +2 -1
  103. package/lib/datasource/datasource-validate-display.js +1 -1
  104. package/lib/datasource/field-reference-validator.js +24 -3
  105. package/lib/datasource/orphan-profiles-validator.js +98 -0
  106. package/lib/datasource/validate.js +41 -23
  107. package/lib/external-system/download-helpers.js +87 -0
  108. package/lib/external-system/download.js +61 -122
  109. package/lib/external-system/generator.js +1 -59
  110. package/lib/external-system/test.js +10 -15
  111. package/lib/generator/builders.js +11 -1
  112. package/lib/generator/external-controller-manifest.js +5 -2
  113. package/lib/generator/split.js +3 -1
  114. package/lib/generator/wizard.js +1 -28
  115. package/lib/identity/fk-reference.js +65 -0
  116. package/lib/identity/identity-apply-core.js +13 -58
  117. package/lib/identity/identity-apply-phases.js +5 -1
  118. package/lib/identity/identity-display.js +243 -0
  119. package/lib/identity/identity-response-helpers.js +52 -0
  120. package/lib/identity/identity-sync-errors.js +62 -0
  121. package/lib/identity/resolve-user-id.js +129 -0
  122. package/lib/identity/username-default.js +31 -0
  123. package/lib/lifecycle/certification-progress.js +2 -2
  124. package/lib/lifecycle/report-display-trust.js +70 -0
  125. package/lib/lifecycle/report-display.js +14 -13
  126. package/lib/resolvers/datasource-resolver.js +2 -3
  127. package/lib/resolvers/resource-type-file.js +159 -0
  128. package/lib/schema/application-schema.json +16 -0
  129. package/lib/schema/deployment-rules.yaml +2 -1
  130. package/lib/schema/external-datasource.schema.json +372 -58
  131. package/lib/schema/external-system.schema.json +331 -2
  132. package/lib/schema/type/document-storage.json +9 -5
  133. package/lib/utils/api.js +2 -1
  134. package/lib/utils/app-run-containers.js +25 -2
  135. package/lib/utils/authentication-shape.js +224 -0
  136. package/lib/utils/build-copy.js +36 -5
  137. package/lib/utils/build-resolve-image.js +66 -12
  138. package/lib/utils/celery-companion-apps.js +53 -0
  139. package/lib/utils/cli-utils.js +4 -0
  140. package/lib/utils/compose-celery-worker.js +23 -0
  141. package/lib/utils/compose-generate-docker-compose.js +9 -1
  142. package/lib/utils/configuration-env-resolver.js +36 -0
  143. package/lib/utils/controller-url.js +4 -1
  144. package/lib/utils/credential-secrets-env-kv.js +216 -0
  145. package/lib/utils/credential-secrets-env.js +52 -262
  146. package/lib/utils/dataplane-resolver.js +4 -115
  147. package/lib/utils/declarative-url-ports.js +24 -1
  148. package/lib/utils/dev-platform-tls.js +148 -0
  149. package/lib/utils/docker-build.js +1 -4
  150. package/lib/utils/external-env-template.js +10 -3
  151. package/lib/utils/external-system-validators.js +8 -2
  152. package/lib/utils/health-check-request.js +114 -0
  153. package/lib/utils/health-check-url.js +3 -1
  154. package/lib/utils/health-check.js +1 -113
  155. package/lib/utils/help-builder.js +3 -0
  156. package/lib/utils/keycloak-realm-ready.js +5 -1
  157. package/lib/utils/localhost-public-host.js +31 -0
  158. package/lib/utils/npm-user-config.js +145 -0
  159. package/lib/utils/paths.js +25 -0
  160. package/lib/utils/platform-controller-url.js +58 -4
  161. package/lib/utils/platform-kv-ref.js +57 -0
  162. package/lib/utils/runtime-configuration-download.js +189 -0
  163. package/lib/utils/runtime-configuration-values.js +119 -0
  164. package/lib/utils/schema-loader.js +19 -4
  165. package/lib/utils/url-declarative-public-base.js +33 -25
  166. package/lib/utils/worker-sidecars.js +142 -0
  167. package/lib/validation/external-manifest-validator.js +2 -0
  168. package/lib/validation/runtime-configuration-rules.js +98 -0
  169. package/lib/validation/validate-display-dimensions.js +154 -0
  170. package/lib/validation/validate-display-sections.js +276 -0
  171. package/lib/validation/validate-display-steps.js +67 -0
  172. package/lib/validation/validate-display.js +24 -402
  173. package/package.json +1 -1
  174. package/templates/applications/dataplane/application.yaml +5 -0
  175. package/templates/applications/dataplane/env.template +26 -13
  176. package/templates/applications/dataplane/rbac.yaml +108 -1
  177. package/templates/applications/miso-controller/application.yaml +2 -2
  178. package/templates/applications/miso-controller/env.template +37 -122
  179. package/templates/applications/miso-controller/rbac.yaml +28 -13
  180. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  181. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  182. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  183. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  184. package/templates/channels/slack/install.template.md.hbs +49 -0
  185. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  186. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  187. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  188. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  189. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  190. package/templates/python/docker-compose.hbs +56 -0
  191. package/templates/typescript/docker-compose.hbs +52 -0
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Validate exposed.profiles keys against capabilities (346 §13 — no orphan profiles).
3
+ *
4
+ * @fileoverview Orphan exposed profile validation for datasource manifests
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { listCapabilityKeys } = require('./capability/capability-manifest');
12
+
13
+ const FORBIDDEN_ORPHAN_PROFILE_KEYS = new Set(['minimal', 'analytics', 'metadata']);
14
+
15
+ /**
16
+ * @param {string} name
17
+ * @returns {string}
18
+ */
19
+ function normalizeProfileKey(name) {
20
+ return String(name).trim().toLowerCase();
21
+ }
22
+
23
+ /**
24
+ * @param {object} parsed
25
+ * @returns {Set<string>}
26
+ */
27
+ function operationKeysFromConfig(parsed) {
28
+ const keys = new Set();
29
+ const openapiOps = parsed?.openapi?.operations;
30
+ if (openapiOps && typeof openapiOps === 'object') {
31
+ Object.keys(openapiOps).forEach((k) => keys.add(normalizeProfileKey(k)));
32
+ }
33
+ const cipOps = parsed?.execution?.cip?.operations;
34
+ if (cipOps && typeof cipOps === 'object') {
35
+ Object.keys(cipOps).forEach((k) => keys.add(normalizeProfileKey(k)));
36
+ }
37
+ return keys;
38
+ }
39
+
40
+ /**
41
+ * @param {object} parsed
42
+ * @returns {Set<string>}
43
+ */
44
+ function allowedExposedProfileKeys(parsed) {
45
+ const allowed = new Set(['default']);
46
+ const capabilityKeys = listCapabilityKeys(parsed?.capabilities).map(normalizeProfileKey);
47
+ if (capabilityKeys.length > 0) {
48
+ capabilityKeys.forEach((k) => allowed.add(k));
49
+ } else {
50
+ operationKeysFromConfig(parsed).forEach((k) => allowed.add(k));
51
+ }
52
+ return allowed;
53
+ }
54
+
55
+ /**
56
+ * @param {object} parsed - Parsed datasource manifest
57
+ * @returns {string[]} Error messages; empty when valid
58
+ */
59
+ function validateOrphanExposedProfiles(parsed) {
60
+ const errors = [];
61
+ const profiles = parsed?.exposed?.profiles;
62
+ if (!profiles || typeof profiles !== 'object' || Array.isArray(profiles)) {
63
+ return errors;
64
+ }
65
+
66
+ const allowed = allowedExposedProfileKeys(parsed);
67
+ const orphans = Object.keys(profiles).filter((name) => {
68
+ const normalized = normalizeProfileKey(name);
69
+ return FORBIDDEN_ORPHAN_PROFILE_KEYS.has(normalized) || !allowed.has(normalized);
70
+ });
71
+
72
+ if (orphans.length === 0) {
73
+ return errors;
74
+ }
75
+
76
+ const decorative = orphans.filter((name) =>
77
+ FORBIDDEN_ORPHAN_PROFILE_KEYS.has(normalizeProfileKey(name))
78
+ );
79
+ if (decorative.length > 0) {
80
+ errors.push(
81
+ `exposed.profiles: orphan exposed profiles are forbidden: ${decorative.join(', ')}. ` +
82
+ 'Use exposed.profiles.default and one profile per capabilities[].key only.'
83
+ );
84
+ return errors;
85
+ }
86
+
87
+ errors.push(
88
+ `exposed.profiles: orphan profile keys must match capabilities[].key or default: ${orphans.join(', ')}. ` +
89
+ `Allowed profile keys: ${[...allowed].sort().join(', ')}.`
90
+ );
91
+ return errors;
92
+ }
93
+
94
+ module.exports = {
95
+ FORBIDDEN_ORPHAN_PROFILE_KEYS,
96
+ allowedExposedProfileKeys,
97
+ validateOrphanExposedProfiles
98
+ };
@@ -10,6 +10,7 @@
10
10
 
11
11
  const path = require('path');
12
12
  const fs = require('fs');
13
+ const { loadConfigFile } = require('../utils/config-format');
13
14
  const pathsUtil = require('../utils/paths');
14
15
  const {
15
16
  datasourceIdentifierMatchesFile,
@@ -22,7 +23,16 @@ const { validateFieldReferences } = require('./field-reference-validator');
22
23
  const { validateAbac } = require('./abac-validator');
23
24
  const { buildDatasourceValidateSummary } = require('./datasource-validate-summary');
24
25
 
25
- const EXCLUDE_JSON_NAMES = new Set(['application.json', 'rbac.json', 'package.json', 'package-lock.json']);
26
+ const DATASOURCE_CONFIG_EXTENSIONS = new Set(['.json', '.yaml', '.yml']);
27
+ const EXCLUDE_CONFIG_NAMES = new Set([
28
+ 'application.json',
29
+ 'application.yaml',
30
+ 'application.yml',
31
+ 'rbac.json',
32
+ 'rbac.yaml',
33
+ 'package.json',
34
+ 'package-lock.json'
35
+ ]);
26
36
 
27
37
  /**
28
38
  * Resolve identifier to an existing file path, or null.
@@ -64,11 +74,14 @@ function pickIntegrationAppForDatasourceKey(datasourceKey, appNames) {
64
74
  * @param {string} fileName
65
75
  * @returns {boolean}
66
76
  */
67
- function shouldScanJsonDatasourceCandidate(fileName) {
77
+ function shouldScanDatasourceConfigCandidate(fileName) {
68
78
  const lower = fileName.toLowerCase();
69
- if (!lower.endsWith('.json')) return false;
70
- if (EXCLUDE_JSON_NAMES.has(fileName)) return false;
79
+ const ext = path.extname(lower);
80
+ if (!DATASOURCE_CONFIG_EXTENSIONS.has(ext)) return false;
81
+ if (EXCLUDE_CONFIG_NAMES.has(fileName)) return false;
71
82
  if (lower.endsWith('-deploy.json') || lower.endsWith('-system.json')) return false;
83
+ if (lower.endsWith('-deploy.yaml') || lower.endsWith('-deploy.yml')) return false;
84
+ if (lower.endsWith('-system.yaml') || lower.endsWith('-system.yml')) return false;
72
85
  return true;
73
86
  }
74
87
 
@@ -86,13 +99,13 @@ function isDatasourceNamedJson(fileName) {
86
99
  * @param {string} datasourceKey
87
100
  * @returns {string[]}
88
101
  */
89
- function collectJsonPathsMatchingDatasourceKey(dir, scanOrder, datasourceKey) {
102
+ function collectConfigPathsMatchingDatasourceKey(dir, scanOrder, datasourceKey) {
90
103
  const hits = [];
91
104
  for (const name of scanOrder) {
92
105
  const full = path.join(dir, name);
93
106
  let parsed;
94
107
  try {
95
- parsed = JSON.parse(fs.readFileSync(full, 'utf8'));
108
+ parsed = loadConfigFile(full);
96
109
  } catch {
97
110
  continue;
98
111
  }
@@ -115,14 +128,14 @@ function resolvePathFromIntegrationFolderScan(appName, datasourceKey) {
115
128
  throw new Error(`Integration folder not found: ${dir}`);
116
129
  }
117
130
  const entries = fs.readdirSync(dir);
118
- const jsonFiles = entries.filter(shouldScanJsonDatasourceCandidate);
119
- const prefer = jsonFiles.filter(isDatasourceNamedJson);
120
- const scanOrder = [...prefer, ...jsonFiles.filter((f) => !prefer.includes(f))];
121
- const hits = collectJsonPathsMatchingDatasourceKey(dir, scanOrder, datasourceKey);
131
+ const configFiles = entries.filter(shouldScanDatasourceConfigCandidate);
132
+ const prefer = configFiles.filter(isDatasourceNamedJson);
133
+ const scanOrder = [...prefer, ...configFiles.filter((f) => !prefer.includes(f))];
134
+ const hits = collectConfigPathsMatchingDatasourceKey(dir, scanOrder, datasourceKey);
122
135
  if (hits.length === 0) {
123
136
  throw new Error(
124
- `No datasource JSON matching "${datasourceKey}" under integration/${appName}/. ` +
125
- `Checked: ${scanOrder.length ? scanOrder.join(', ') : '(no JSON candidates)'}`
137
+ `No datasource config matching "${datasourceKey}" under integration/${appName}/. ` +
138
+ `Checked: ${scanOrder.length ? scanOrder.join(', ') : '(no JSON/YAML candidates)'}`
126
139
  );
127
140
  }
128
141
  if (hits.length > 1) {
@@ -167,7 +180,8 @@ function resolveValidateInputPath(identifier) {
167
180
  }
168
181
  const asFile = tryResolveAsExistingFile(trimmed);
169
182
  if (asFile) return asFile;
170
- if (trimmed.toLowerCase().endsWith('.json')) {
183
+ const ext = path.extname(trimmed).toLowerCase();
184
+ if (DATASOURCE_CONFIG_EXTENSIONS.has(ext)) {
171
185
  throw new Error(`File not found: ${trimmed}`);
172
186
  }
173
187
  return resolvePathFromIntegrationDatasourceKey(trimmed);
@@ -187,6 +201,18 @@ function resolveValidateInputPath(identifier) {
187
201
  * const result = await validateDatasourceFile('./hubspot-deal.json');
188
202
  * // Returns: { valid: true, errors: [], warnings: [], resolvedPath: '...' }
189
203
  */
204
+ function buildParseFailureResult(filePath, error) {
205
+ const message =
206
+ error && error.message ? String(error.message) : 'Invalid config file';
207
+ return {
208
+ valid: false,
209
+ errors: [message],
210
+ warnings: [],
211
+ resolvedPath: filePath,
212
+ summary: null
213
+ };
214
+ }
215
+
190
216
  async function validateDatasourceFile(filePathOrKey) {
191
217
  if (!filePathOrKey || typeof filePathOrKey !== 'string') {
192
218
  throw new Error('File path is required and must be a string');
@@ -194,19 +220,11 @@ async function validateDatasourceFile(filePathOrKey) {
194
220
 
195
221
  const filePath = resolveValidateInputPath(filePathOrKey.trim());
196
222
 
197
- const content = fs.readFileSync(filePath, 'utf8');
198
223
  let parsed;
199
-
200
224
  try {
201
- parsed = JSON.parse(content);
225
+ parsed = loadConfigFile(filePath);
202
226
  } catch (error) {
203
- return {
204
- valid: false,
205
- errors: [`Invalid JSON syntax: ${error.message}`],
206
- warnings: [],
207
- resolvedPath: filePath,
208
- summary: null
209
- };
227
+ return buildParseFailureResult(filePath, error);
210
228
  }
211
229
 
212
230
  const summary = buildDatasourceValidateSummary(parsed);
@@ -8,8 +8,90 @@
8
8
  * @version 2.0.0
9
9
  */
10
10
 
11
+ const {
12
+ formatSuccessLine,
13
+ formatSuccessParagraph,
14
+ sectionTitle,
15
+ headerKeyValue
16
+ } = require('../utils/cli-test-layout-chalk');
17
+ const logger = require('../utils/logger');
11
18
  const { generateExternalReadmeContent } = require('../utils/external-readme');
12
19
 
20
+ /**
21
+ * Validates system type from downloaded application
22
+ * @param {Object} application - External system configuration
23
+ * @returns {string} System type (openapi, mcp, custom)
24
+ */
25
+ function validateSystemType(application) {
26
+ if (!application || typeof application !== 'object') {
27
+ throw new Error('Application configuration is required');
28
+ }
29
+ const validTypes = ['openapi', 'mcp', 'custom'];
30
+ const systemType = application.type;
31
+ if (!systemType || !validTypes.includes(systemType)) {
32
+ throw new Error(`Invalid system type: ${systemType}. Must be one of: ${validTypes.join(', ')}`);
33
+ }
34
+ return systemType;
35
+ }
36
+
37
+ function validateApplicationConfig(application) {
38
+ if (!application || typeof application !== 'object') {
39
+ throw new Error('Application configuration is required');
40
+ }
41
+ if (!application.key || typeof application.key !== 'string') {
42
+ throw new Error('Application key is required');
43
+ }
44
+ }
45
+
46
+ function validateDatasourceConfig(datasource, applicationKey) {
47
+ if (!datasource.key || typeof datasource.key !== 'string') {
48
+ throw new Error('Datasource key is required for all datasources');
49
+ }
50
+ if (!datasource.systemKey || typeof datasource.systemKey !== 'string') {
51
+ throw new Error('Datasource systemKey is required for all datasources');
52
+ }
53
+ if (datasource.systemKey !== applicationKey) {
54
+ throw new Error(
55
+ `Datasource systemKey (${datasource.systemKey}) does not match application key (${applicationKey})`
56
+ );
57
+ }
58
+ }
59
+
60
+ function validateDownloadedData(application, dataSources) {
61
+ validateApplicationConfig(application);
62
+ if (!Array.isArray(dataSources)) {
63
+ throw new Error('DataSources must be an array');
64
+ }
65
+ for (const datasource of dataSources) {
66
+ validateDatasourceConfig(datasource, application.key);
67
+ }
68
+ }
69
+
70
+ function logDownloadCommandHeader(systemKey) {
71
+ logger.log('');
72
+ logger.log(sectionTitle('Download'));
73
+ logger.log(headerKeyValue('System:', systemKey));
74
+ }
75
+
76
+ function displayDownloadSuccess(systemKey, finalPath, datasourceCount) {
77
+ logger.log(
78
+ formatSuccessParagraph(
79
+ `Downloaded ${systemKey} (${datasourceCount} datasource${datasourceCount === 1 ? '' : 's'})`
80
+ )
81
+ );
82
+ logger.log(headerKeyValue('Location:', finalPath));
83
+ }
84
+
85
+ async function runConvertToJsonIfRequested(systemKey) {
86
+ const { runConvert } = require('../commands/convert');
87
+ try {
88
+ await runConvert(systemKey, { format: 'json', force: true });
89
+ logger.log(formatSuccessLine('Converted component files to JSON'));
90
+ } catch (convertErr) {
91
+ throw new Error(`Download succeeded but convert to JSON failed: ${convertErr.message}`);
92
+ }
93
+ }
94
+
13
95
  /**
14
96
  * Generates application.yaml content for downloaded system
15
97
  * @param {string} systemKey - System key
@@ -90,6 +172,11 @@ function generateReadme(systemKey, application, dataSources) {
90
172
  }
91
173
 
92
174
  module.exports = {
175
+ validateSystemType,
176
+ validateDownloadedData,
177
+ logDownloadCommandHeader,
178
+ displayDownloadSuccess,
179
+ runConvertToJsonIfRequested,
93
180
  generateVariablesYaml,
94
181
  generateReadme
95
182
  };
@@ -1,8 +1,5 @@
1
1
  const {
2
2
  formatSuccessLine,
3
- formatSuccessParagraph,
4
- sectionTitle,
5
- headerKeyValue,
6
3
  metadata,
7
4
  formatProgress,
8
5
  formatWarningLine
@@ -36,83 +33,18 @@ const { getExternalSystemConfig } = require('../api/external-systems.api');
36
33
  const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
37
34
  const { getConfig } = require('../core/config');
38
35
  const { getIntegrationPath } = require('../utils/paths');
39
- const { retemplateConfigurationForDownload } = require('../utils/configuration-env-resolver');
36
+ const { retemplateConfigurationForDownload, retemplateAllVariableDefinitions } = require('../utils/configuration-env-resolver');
37
+ const { syncRuntimeConfigurationFromDataplane } = require('../utils/runtime-configuration-download');
40
38
  const logger = require('../utils/logger');
41
39
  const { resolveControllerUrl } = require('../utils/controller-url');
42
40
  const generator = require('../generator');
43
-
44
- /**
45
- * Validates system type from downloaded application
46
- * @param {Object} application - External system configuration
47
- * @returns {string} System type (openapi, mcp, custom)
48
- * @throws {Error} If system type is invalid
49
- */
50
- function validateSystemType(application) {
51
- if (!application || typeof application !== 'object') {
52
- throw new Error('Application configuration is required');
53
- }
54
-
55
- const validTypes = ['openapi', 'mcp', 'custom'];
56
- const systemType = application.type;
57
-
58
- if (!systemType || !validTypes.includes(systemType)) {
59
- throw new Error(`Invalid system type: ${systemType}. Must be one of: ${validTypes.join(', ')}`);
60
- }
61
-
62
- return systemType;
63
- }
64
-
65
- /**
66
- * Validates downloaded data structure before writing files
67
- * @param {Object} application - External system configuration
68
- * @param {Array} dataSources - Array of datasource configurations
69
- * @throws {Error} If validation fails
70
- */
71
- /**
72
- * Validates application configuration
73
- * @function validateApplicationConfig
74
- * @param {Object} application - Application configuration
75
- * @throws {Error} If validation fails
76
- */
77
- function validateApplicationConfig(application) {
78
- if (!application || typeof application !== 'object') {
79
- throw new Error('Application configuration is required');
80
- }
81
- if (!application.key || typeof application.key !== 'string') {
82
- throw new Error('Application key is required');
83
- }
84
- }
85
-
86
- /**
87
- * Validates datasource configuration
88
- * @function validateDatasourceConfig
89
- * @param {Object} datasource - Datasource configuration
90
- * @param {string} applicationKey - Application key
91
- * @throws {Error} If validation fails
92
- */
93
- function validateDatasourceConfig(datasource, applicationKey) {
94
- if (!datasource.key || typeof datasource.key !== 'string') {
95
- throw new Error('Datasource key is required for all datasources');
96
- }
97
- if (!datasource.systemKey || typeof datasource.systemKey !== 'string') {
98
- throw new Error('Datasource systemKey is required for all datasources');
99
- }
100
- if (datasource.systemKey !== applicationKey) {
101
- throw new Error(`Datasource systemKey (${datasource.systemKey}) does not match application key (${applicationKey})`);
102
- }
103
- }
104
-
105
- function validateDownloadedData(application, dataSources) {
106
- validateApplicationConfig(application);
107
-
108
- if (!Array.isArray(dataSources)) {
109
- throw new Error('DataSources must be an array');
110
- }
111
-
112
- for (const datasource of dataSources) {
113
- validateDatasourceConfig(datasource, application.key);
114
- }
115
- }
41
+ const {
42
+ validateSystemType,
43
+ validateDownloadedData,
44
+ logDownloadCommandHeader,
45
+ displayDownloadSuccess,
46
+ runConvertToJsonIfRequested
47
+ } = require('./download-helpers');
116
48
 
117
49
  /**
118
50
  * Setup authentication and get dataplane URL
@@ -354,10 +286,9 @@ async function applyRetemplateToSystemFile(systemKey, systemFilePath) {
354
286
  const systemContent = await fs.readFile(systemFilePath, 'utf8');
355
287
  const systemObj = yaml.load(systemContent);
356
288
  if (!Array.isArray(systemObj?.configuration)) return;
357
- const applied = await retemplateConfigurationForDownload(systemKey, systemObj.configuration);
358
- if (applied) {
359
- await fs.writeFile(systemFilePath, yaml.dump(systemObj, { indent: 2, lineWidth: -1 }), 'utf8');
360
- }
289
+ retemplateAllVariableDefinitions(systemObj.configuration);
290
+ await retemplateConfigurationForDownload(systemKey, systemObj.configuration);
291
+ await fs.writeFile(systemFilePath, yaml.dump(systemObj, { indent: 2, lineWidth: -1 }), 'utf8');
361
292
  }
362
293
 
363
294
  /**
@@ -397,43 +328,54 @@ async function processDownloadedSystem(systemKey, manifest, splitOptions = {}) {
397
328
  }
398
329
 
399
330
  /**
400
- * Display download success message
401
- * @param {string} systemKey - System key
402
- * @param {string} finalPath - Final destination path
403
- * @param {number} datasourceCount - Number of datasources
404
- */
405
- function displayDownloadSuccess(systemKey, finalPath, datasourceCount) {
406
- logger.log(
407
- formatSuccessParagraph(
408
- `Downloaded ${systemKey} (${datasourceCount} datasource${datasourceCount === 1 ? '' : 's'})`
409
- )
410
- );
411
- logger.log(headerKeyValue('Location:', finalPath));
412
- }
413
-
414
- /**
415
- * After YAML split, convert integration files to JSON when --format json.
416
- * @async
417
- * @param {string} systemKey
331
+ * Post-split download steps: governance packs, optional JSON convert, runtime config sync.
332
+ * @param {Object} params
333
+ * @param {string} params.systemKey
334
+ * @param {Object} params.manifest
335
+ * @param {string} params.finalPath
336
+ * @param {string} params.dataplaneUrl
337
+ * @param {Object} params.authConfig
338
+ * @param {string} params.format
418
339
  * @returns {Promise<void>}
419
340
  */
420
- async function runConvertToJsonIfRequested(systemKey) {
421
- const { runConvert } = require('../commands/convert');
341
+ async function finalizeDownloadArtifacts({
342
+ systemKey,
343
+ manifest,
344
+ finalPath,
345
+ dataplaneUrl,
346
+ authConfig,
347
+ format
348
+ }) {
349
+ const { downloadGovernanceScenarioPacksFromManifest } = require('../lifecycle/scenario-upload');
350
+ await downloadGovernanceScenarioPacksFromManifest(
351
+ systemKey,
352
+ manifest,
353
+ dataplaneUrl,
354
+ authConfig
355
+ );
356
+ if (format === 'json') {
357
+ await runConvertToJsonIfRequested(systemKey);
358
+ }
422
359
  try {
423
- await runConvert(systemKey, { format: 'json', force: true });
424
- logger.log(formatSuccessLine('Converted component files to JSON'));
425
- } catch (convertErr) {
426
- throw new Error(`Download succeeded but convert to JSON failed: ${convertErr.message}`);
360
+ const configSync = await syncRuntimeConfigurationFromDataplane({
361
+ dataplaneUrl,
362
+ authConfig,
363
+ systemKey,
364
+ integrationPath: finalPath,
365
+ dataSources: manifest.dataSources
366
+ });
367
+ if (configSync.merged > 0) {
368
+ const nameList = configSync.names.length ? ` (${configSync.names.join(', ')})` : '';
369
+ logger.log(
370
+ formatSuccessLine(
371
+ `Merged ${configSync.merged} runtime configuration variable(s) into env.template${nameList}.`
372
+ )
373
+ );
374
+ }
375
+ } catch (syncErr) {
376
+ logger.log(formatWarningLine(`Runtime configuration sync skipped: ${syncErr.message}`));
427
377
  }
428
- }
429
-
430
- /**
431
- * @param {string} systemKey
432
- */
433
- function logDownloadCommandHeader(systemKey) {
434
- logger.log('');
435
- logger.log(sectionTitle('Download'));
436
- logger.log(headerKeyValue('System:', systemKey));
378
+ displayDownloadSuccess(systemKey, finalPath, manifest.dataSources.length);
437
379
  }
438
380
 
439
381
  /**
@@ -468,17 +410,14 @@ async function downloadExternalSystem(systemKey, options = {}) {
468
410
  manifest,
469
411
  await resolveDownloadSplitOptions(finalPath, options)
470
412
  );
471
- const { downloadGovernanceScenarioPacksFromManifest } = require('../lifecycle/scenario-upload');
472
- await downloadGovernanceScenarioPacksFromManifest(
413
+ await finalizeDownloadArtifacts({
473
414
  systemKey,
474
415
  manifest,
416
+ finalPath,
475
417
  dataplaneUrl,
476
- authConfig
477
- );
478
- if (format === 'json') {
479
- await runConvertToJsonIfRequested(systemKey);
480
- }
481
- displayDownloadSuccess(systemKey, finalPath, manifest.dataSources.length);
418
+ authConfig,
419
+ format
420
+ });
482
421
  } catch (error) {
483
422
  throw new Error(`Failed to download external system: ${error.message}`);
484
423
  }
@@ -16,70 +16,12 @@ const handlebars = require('handlebars');
16
16
  const logger = require('../utils/logger');
17
17
  const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
18
18
  const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
19
- const { getKvPathSegmentForSecurityKey } = require('../utils/credential-secrets-env');
19
+ const { buildAuthenticationFromMethod } = require('../utils/authentication-shape');
20
20
 
21
21
  // Register Handlebars helper for equality check
22
22
  handlebars.registerHelper('eq', (a, b) => a === b);
23
23
  handlebars.registerHelper('json', (obj) => JSON.stringify(obj));
24
24
 
25
- /**
26
- * Build authentication object per schema authenticationVariablesByMethod.
27
- * Security values use canonical kv://<systemKey>/<segment> paths (segment from getKvPathSegmentForSecurityKey).
28
- * @param {string} systemKey - External system key
29
- * @param {string} authType - Auth method (oauth2, aad, apikey, bearerToken, basic, queryParam, oidc, hmac, none)
30
- * @returns {{ method: string, variables: Object, security: Object }} Authentication object
31
- */
32
- function buildAuthenticationFromMethod(systemKey, authType) {
33
- const kvPath = (securityKey) => {
34
- const segment = getKvPathSegmentForSecurityKey(securityKey);
35
- return segment ? `kv://${systemKey}/${segment}` : null;
36
- };
37
- const method = authType || 'apikey';
38
- const base = 'https://api.example.com';
39
-
40
- const authMap = {
41
- oauth2: {
42
- variables: { baseUrl: base, tokenUrl: `${base}/oauth/token`, authorizationUrl: `${base}/oauth/authorize` },
43
- security: { clientId: kvPath('clientId'), clientSecret: kvPath('clientSecret') }
44
- },
45
- aad: {
46
- variables: { baseUrl: base, tokenUrl: 'https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token', tenantId: '{tenant-id}' },
47
- security: { clientId: kvPath('clientId'), clientSecret: kvPath('clientSecret') }
48
- },
49
- apikey: {
50
- variables: { baseUrl: base, headerName: 'X-API-Key' },
51
- security: { apiKey: kvPath('apiKey') }
52
- },
53
- bearerToken: {
54
- variables: { baseUrl: base, headerName: 'Authorization', prefix: 'Bearer' },
55
- security: { token: kvPath('token') }
56
- },
57
- basic: {
58
- variables: { baseUrl: base },
59
- security: { username: kvPath('username'), password: kvPath('password') }
60
- },
61
- queryParam: {
62
- variables: { baseUrl: base, paramName: 'api_key' },
63
- security: { paramValue: kvPath('paramValue') }
64
- },
65
- oidc: {
66
- variables: { openIdConfigUrl: 'https://example.com/.well-known/openid-configuration', clientId: 'app-id' },
67
- security: {}
68
- },
69
- hmac: {
70
- variables: { baseUrl: base, algorithm: 'sha256', signatureHeader: 'X-Signature' },
71
- security: { signingSecret: kvPath('signingSecret') }
72
- },
73
- none: {
74
- variables: {},
75
- security: {}
76
- }
77
- };
78
-
79
- const auth = authMap[method] || authMap.apikey;
80
- return { method, variables: auth.variables, security: auth.security };
81
- }
82
-
83
25
  /** Target extension for format */
84
26
  const FORMAT_EXT = { yaml: '.yaml', json: '.json' };
85
27