@aifabrix/builder 2.46.0 → 2.47.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 (175) 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 +16 -4
  47. package/lib/cli/setup-app.js +54 -3
  48. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  49. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  50. package/lib/cli/setup-platform.js +1 -1
  51. package/lib/cli/setup-utility-help-after.js +4 -1
  52. package/lib/cli/setup-utility-resolve.js +8 -5
  53. package/lib/commands/app-logs.js +54 -26
  54. package/lib/commands/app.js +41 -0
  55. package/lib/commands/auth-status-dataplane-version.js +97 -61
  56. package/lib/commands/auth-status.js +2 -7
  57. package/lib/commands/channel.js +58 -0
  58. package/lib/commands/datasource.js +1 -1
  59. package/lib/commands/identity-apply-sync.js +49 -8
  60. package/lib/commands/identity-group-cli.js +66 -26
  61. package/lib/commands/identity-membership-role-cache.js +58 -37
  62. package/lib/commands/identity-shared.js +22 -1
  63. package/lib/commands/identity-user-cli.js +80 -34
  64. package/lib/commands/repair-auth-apply.js +30 -4
  65. package/lib/commands/repair-auth-config.js +8 -13
  66. package/lib/commands/repair-datasource-com-v2.js +15 -0
  67. package/lib/commands/repair-datasource-service-contract.js +206 -0
  68. package/lib/commands/repair-datasource.js +53 -18
  69. package/lib/commands/repair-env-template.js +12 -12
  70. package/lib/commands/repair-openapi-sync.js +26 -4
  71. package/lib/commands/repair-steps.js +28 -13
  72. package/lib/commands/repair-system-alignment.js +7 -4
  73. package/lib/commands/resource-type.js +371 -0
  74. package/lib/commands/setup-modes.js +1 -1
  75. package/lib/commands/setup-prompts-platform-mode.js +24 -21
  76. package/lib/commands/test-trust-external.js +39 -90
  77. package/lib/commands/up-common.js +33 -1
  78. package/lib/commands/up-dataplane.js +8 -4
  79. package/lib/commands/upload.js +24 -3
  80. package/lib/commands/verify-trust-command-action.js +4 -1
  81. package/lib/commands/verify-trust-external.js +12 -20
  82. package/lib/core/config-setup-platform-mode.js +3 -3
  83. package/lib/core/setup-platform-mode.js +41 -6
  84. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  85. package/lib/datasource/capability/capability-manifest.js +254 -0
  86. package/lib/datasource/capability/capability-resolve.js +3 -4
  87. package/lib/datasource/capability/copy-operations.js +5 -3
  88. package/lib/datasource/capability/remove-operations.js +2 -1
  89. package/lib/datasource/capability/run-capability-copy.js +32 -4
  90. package/lib/datasource/capability/run-capability-diff.js +3 -3
  91. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  92. package/lib/datasource/capability/run-capability-edit.js +4 -7
  93. package/lib/datasource/capability/run-capability-relate.js +2 -4
  94. package/lib/datasource/capability/run-capability-remove.js +2 -4
  95. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  96. package/lib/datasource/datasource-identifier.js +2 -1
  97. package/lib/datasource/datasource-validate-display.js +1 -1
  98. package/lib/datasource/field-reference-validator.js +24 -3
  99. package/lib/datasource/orphan-profiles-validator.js +98 -0
  100. package/lib/datasource/validate.js +41 -23
  101. package/lib/external-system/download-helpers.js +87 -0
  102. package/lib/external-system/download.js +61 -122
  103. package/lib/external-system/generator.js +1 -59
  104. package/lib/external-system/test.js +10 -15
  105. package/lib/generator/builders.js +11 -1
  106. package/lib/generator/external-controller-manifest.js +5 -2
  107. package/lib/generator/split.js +3 -1
  108. package/lib/generator/wizard.js +1 -28
  109. package/lib/identity/fk-reference.js +65 -0
  110. package/lib/identity/identity-apply-core.js +13 -58
  111. package/lib/identity/identity-apply-phases.js +5 -1
  112. package/lib/identity/identity-display.js +243 -0
  113. package/lib/identity/identity-response-helpers.js +52 -0
  114. package/lib/identity/identity-sync-errors.js +62 -0
  115. package/lib/identity/resolve-user-id.js +129 -0
  116. package/lib/identity/username-default.js +31 -0
  117. package/lib/lifecycle/certification-progress.js +2 -2
  118. package/lib/lifecycle/report-display-trust.js +70 -0
  119. package/lib/lifecycle/report-display.js +14 -13
  120. package/lib/resolvers/datasource-resolver.js +2 -3
  121. package/lib/resolvers/resource-type-file.js +159 -0
  122. package/lib/schema/application-schema.json +16 -0
  123. package/lib/schema/deployment-rules.yaml +2 -1
  124. package/lib/schema/external-datasource.schema.json +372 -58
  125. package/lib/schema/external-system.schema.json +331 -2
  126. package/lib/schema/type/document-storage.json +9 -5
  127. package/lib/utils/app-run-containers.js +25 -2
  128. package/lib/utils/authentication-shape.js +224 -0
  129. package/lib/utils/build-copy.js +36 -5
  130. package/lib/utils/build-resolve-image.js +66 -12
  131. package/lib/utils/celery-companion-apps.js +53 -0
  132. package/lib/utils/cli-utils.js +4 -0
  133. package/lib/utils/compose-celery-worker.js +23 -0
  134. package/lib/utils/compose-generate-docker-compose.js +9 -1
  135. package/lib/utils/configuration-env-resolver.js +36 -0
  136. package/lib/utils/credential-secrets-env-kv.js +216 -0
  137. package/lib/utils/credential-secrets-env.js +52 -262
  138. package/lib/utils/dataplane-resolver.js +4 -115
  139. package/lib/utils/declarative-url-ports.js +24 -1
  140. package/lib/utils/docker-build.js +1 -4
  141. package/lib/utils/external-env-template.js +10 -3
  142. package/lib/utils/external-system-validators.js +8 -2
  143. package/lib/utils/help-builder.js +2 -0
  144. package/lib/utils/paths.js +25 -0
  145. package/lib/utils/platform-controller-url.js +2 -1
  146. package/lib/utils/platform-kv-ref.js +57 -0
  147. package/lib/utils/runtime-configuration-download.js +189 -0
  148. package/lib/utils/runtime-configuration-values.js +119 -0
  149. package/lib/utils/schema-loader.js +19 -4
  150. package/lib/utils/worker-sidecars.js +142 -0
  151. package/lib/validation/external-manifest-validator.js +2 -0
  152. package/lib/validation/runtime-configuration-rules.js +98 -0
  153. package/lib/validation/validate-display-dimensions.js +154 -0
  154. package/lib/validation/validate-display-sections.js +276 -0
  155. package/lib/validation/validate-display-steps.js +67 -0
  156. package/lib/validation/validate-display.js +24 -402
  157. package/package.json +1 -1
  158. package/templates/applications/dataplane/application.yaml +5 -0
  159. package/templates/applications/dataplane/env.template +26 -13
  160. package/templates/applications/dataplane/rbac.yaml +108 -1
  161. package/templates/applications/miso-controller/application.yaml +2 -2
  162. package/templates/applications/miso-controller/env.template +37 -122
  163. package/templates/applications/miso-controller/rbac.yaml +28 -13
  164. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  165. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  166. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  167. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  168. package/templates/channels/slack/install.template.md.hbs +49 -0
  169. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  170. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  171. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  172. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  173. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  174. package/templates/python/docker-compose.hbs +56 -0
  175. package/templates/typescript/docker-compose.hbs +52 -0
@@ -6,8 +6,8 @@
6
6
  * @version 2.0.0
7
7
  */
8
8
 
9
- const fs = require('fs');
10
9
  const path = require('path');
10
+ const { readDatasourceConfigFile } = require('./run-capability-copy');
11
11
  const { resolveValidateInputPath } = require('../validate');
12
12
  const { normalizeCapabilityKey } = require('./capability-key');
13
13
  const { extractCapabilitySliceForDiff } = require('./capability-diff-slice');
@@ -108,8 +108,8 @@ function resolveProfileKeys(opts) {
108
108
  function runCapabilityDiff(opts) {
109
109
  const pathA = resolveValidateInputPath(opts.fileA.trim());
110
110
  const pathB = resolveValidateInputPath(opts.fileB.trim());
111
- const docA = JSON.parse(fs.readFileSync(pathA, 'utf8'));
112
- const docB = JSON.parse(fs.readFileSync(pathB, 'utf8'));
111
+ const docA = readDatasourceConfigFile(pathA);
112
+ const docB = readDatasourceConfigFile(pathB);
113
113
 
114
114
  const { capA, capB } = resolveCapabilityKeys(opts);
115
115
  const { profA, profB } = resolveProfileKeys(opts);
@@ -8,7 +8,6 @@
8
8
 
9
9
  'use strict';
10
10
 
11
- const fs = require('fs');
12
11
  const { resolveControllerUrl } = require('../../utils/controller-url');
13
12
  const { normalizeControllerUrl } = require('../../core/config');
14
13
  const { getOrRefreshDeviceToken } = require('../../utils/token-manager');
@@ -19,7 +18,7 @@ const {
19
18
  } = require('../validate');
20
19
  const { applyCapabilityDimension } = require('./dimension-operations');
21
20
  const { validateDimensionSemantics } = require('./dimension-validate');
22
- const { writeBackup, atomicWriteJson } = require('./run-capability-copy');
21
+ const { writeBackup, atomicWriteJson, readDatasourceConfigFile } = require('./run-capability-copy');
23
22
  const { tryResolveDatasourceKeyToLocalPath, readJsonFile } = require('../../resolvers/datasource-resolver');
24
23
  const { tryFetchDatasourceConfig } = require('../../resolvers/manifest-resolver');
25
24
 
@@ -231,8 +230,7 @@ function applyBindingAndValidateSchema({ parsed, opts, semanticWarnings, remoteF
231
230
  async function runCapabilityDimension(rawOpts) {
232
231
  const opts = normalizeRunOptions(rawOpts);
233
232
  const resolvedPath = resolveValidateInputPath(opts.fileOrKey);
234
- const raw = fs.readFileSync(resolvedPath, 'utf8');
235
- const parsed = JSON.parse(raw);
233
+ const parsed = readDatasourceConfigFile(resolvedPath);
236
234
 
237
235
  const systemKeyForAuth = resolveSystemKeyForAuth(parsed);
238
236
  const { remoteTargetsByKey, remoteFetchMeta } = await loadRemoteTargetsByKey({
@@ -6,7 +6,6 @@
6
6
  * @version 2.0.0
7
7
  */
8
8
 
9
- const fs = require('fs');
10
9
  const { execSync } = require('child_process');
11
10
  const inquirer = require('inquirer');
12
11
  const {
@@ -19,7 +18,8 @@ const {
19
18
  resolveSingleOpsKey,
20
19
  resolveProfileKeyForLogical
21
20
  } = require('./capability-resolve');
22
- const { writeBackup, atomicWriteJson } = require('./run-capability-copy');
21
+ const { writeBackup, atomicWriteJson, readDatasourceConfigFile } = require('./run-capability-copy');
22
+ const { listCapabilityKeys } = require('./capability-manifest');
23
23
 
24
24
  /**
25
25
  * @typedef {object} RunCapabilityEditOpts
@@ -35,10 +35,7 @@ const { writeBackup, atomicWriteJson } = require('./run-capability-copy');
35
35
  * @returns {string[]}
36
36
  */
37
37
  function collectCapabilityKeys(doc) {
38
- const s = new Set();
39
- if (Array.isArray(doc.capabilities)) {
40
- doc.capabilities.forEach((c) => s.add(String(c)));
41
- }
38
+ const s = new Set(listCapabilityKeys(doc.capabilities));
42
39
  if (doc.openapi?.operations) {
43
40
  Object.keys(doc.openapi.operations).forEach((k) => s.add(k));
44
41
  }
@@ -351,7 +348,7 @@ async function runCapabilityEdit(opts) {
351
348
  throw new Error('capability edit requires an interactive terminal (TTY). Use a terminal or SSH with -t.');
352
349
  }
353
350
  const resolvedPath = resolveValidateInputPath(opts.fileOrKey.trim());
354
- const doc = JSON.parse(fs.readFileSync(resolvedPath, 'utf8'));
351
+ const doc = readDatasourceConfigFile(resolvedPath);
355
352
  const { capKey, section, profileKey } = await resolveEditTargets(doc, opts);
356
353
  const initial = getEditableJson(doc, capKey, section, profileKey);
357
354
  const body = await promptEditorJson(initial);
@@ -6,14 +6,13 @@
6
6
  * @version 2.0.0
7
7
  */
8
8
 
9
- const fs = require('fs');
10
9
  const {
11
10
  resolveValidateInputPath,
12
11
  validateDatasourceParsed
13
12
  } = require('../validate');
14
13
  const { applyCapabilityRelate } = require('./relate-operations');
15
14
  const { validateRelateSemantics } = require('./relate-validate');
16
- const { writeBackup, atomicWriteJson } = require('./run-capability-copy');
15
+ const { writeBackup, atomicWriteJson, readDatasourceConfigFile } = require('./run-capability-copy');
17
16
  const { tryResolveDatasourceKeyToLocalPath, readJsonFile } = require('../../resolvers/datasource-resolver');
18
17
  const { tryFetchDatasourceConfig } = require('../../resolvers/manifest-resolver');
19
18
 
@@ -134,8 +133,7 @@ function applyRelateAndValidateSchema(parsed, opts, semanticWarnings, remoteVali
134
133
  */
135
134
  async function runCapabilityRelate(opts) {
136
135
  const resolvedPath = resolveValidateInputPath(opts.fileOrKey.trim());
137
- const raw = fs.readFileSync(resolvedPath, 'utf8');
138
- const parsed = JSON.parse(raw);
136
+ const parsed = readDatasourceConfigFile(resolvedPath);
139
137
 
140
138
  const targetDocLocal = loadTargetDocLocal(opts.targetDatasource);
141
139
  const systemKeyForAuth = resolveSystemKeyForAuth(parsed);
@@ -6,14 +6,13 @@
6
6
  * @version 2.0.0
7
7
  */
8
8
 
9
- const fs = require('fs');
10
9
  const {
11
10
  resolveValidateInputPath,
12
11
  validateDatasourceParsed
13
12
  } = require('../validate');
14
13
  const { normalizeCapabilityKey } = require('./capability-key');
15
14
  const { applyCapabilityRemove } = require('./remove-operations');
16
- const { writeBackup, atomicWriteJson } = require('./run-capability-copy');
15
+ const { writeBackup, atomicWriteJson, readDatasourceConfigFile } = require('./run-capability-copy');
17
16
 
18
17
  /**
19
18
  * @param {{ valid: boolean, errors: string[] }} validation
@@ -67,8 +66,7 @@ function makeRemoveResult(resolvedPath, capability, result, validation, dryRun,
67
66
  */
68
67
  async function runCapabilityRemove(opts) {
69
68
  const resolvedPath = resolveValidateInputPath(opts.fileOrKey.trim());
70
- const raw = fs.readFileSync(resolvedPath, 'utf8');
71
- const parsed = JSON.parse(raw);
69
+ const parsed = readDatasourceConfigFile(resolvedPath);
72
70
 
73
71
  const capability = normalizeCapabilityKey(opts.capability, 'Capability (--capability)');
74
72
 
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  const { normalizeCapabilityKey } = require('./capability-key');
10
+ const { capabilitiesIncludes } = require('./capability-manifest');
10
11
 
11
12
  /**
12
13
  * Lists structural locations where a capability key should appear.
@@ -18,7 +19,7 @@ const { normalizeCapabilityKey } = require('./capability-key');
18
19
  function checkCapabilitySlices(parsed, rawKey) {
19
20
  const key = normalizeCapabilityKey(rawKey, 'capability');
20
21
  const missing = [];
21
- if (!Array.isArray(parsed.capabilities) || !parsed.capabilities.includes(key)) {
22
+ if (!capabilitiesIncludes(parsed.capabilities, key)) {
22
23
  missing.push(`capabilities[] (missing "${key}")`);
23
24
  }
24
25
  if (!parsed.openapi?.operations?.[key]) {
@@ -38,7 +38,8 @@ function datasourceIdentifierMatchesFile(identifier, fileRef, parsed) {
38
38
  if (stem === id) {
39
39
  return true;
40
40
  }
41
- if (`${stem}.json` === id.toLowerCase()) {
41
+ const lowerId = id.toLowerCase();
42
+ if (`${stem}.json` === lowerId || `${stem}.yaml` === lowerId || `${stem}.yml` === lowerId) {
42
43
  return true;
43
44
  }
44
45
  return false;
@@ -30,7 +30,7 @@ function logHeaderAndIdentity(result, trimmed, showMapping) {
30
30
  const { resolvedPath, summary } = result;
31
31
  logger.log('');
32
32
  logger.log(sectionTitle('Datasource validation'));
33
- logger.log(metadata('Offline — JSON schema and integration wiring'));
33
+ logger.log(metadata('Offline — YAML/JSON schema and integration wiring'));
34
34
  logger.log('');
35
35
  if (summary) {
36
36
  logger.log(` ${headerKeyValue('Key:', summary.key)}`);
@@ -10,6 +10,8 @@
10
10
  * @version 2.0.0
11
11
  */
12
12
 
13
+ const { validateOrphanExposedProfiles } = require('./orphan-profiles-validator');
14
+
13
15
  /**
14
16
  * Set of attribute names plus root dimension keys valid for field references.
15
17
  * Used for primaryKey (schema allows dimension keys or attributes) and other paths (attributes only).
@@ -40,16 +42,34 @@ function checkPrimaryKey(parsed, attributesAndDimensions, errors) {
40
42
  });
41
43
  }
42
44
 
45
+ /** @returns {Set<string>} */
46
+ function profileReferenceSet(parsed) {
47
+ if (parsed?.entityType === 'serviceContract') {
48
+ const schema = parsed?.exposed?.schema;
49
+ if (schema && typeof schema === 'object' && !Array.isArray(schema)) {
50
+ return new Set(
51
+ Object.keys(schema).filter(key => typeof key === 'string' && key !== '')
52
+ );
53
+ }
54
+ }
55
+ return new Set(Object.keys(parsed?.fieldMappings?.attributes ?? {}));
56
+ }
57
+
43
58
  /** @param {string[]} errors */
44
- function checkExposedProfiles(parsed, attrSet, errors) {
59
+ function checkExposedProfiles(parsed, _attrSet, errors) {
45
60
  const profiles = parsed?.exposed?.profiles;
46
61
  if (!profiles || typeof profiles !== 'object' || Array.isArray(profiles)) return;
62
+ const profileRefSet = profileReferenceSet(parsed);
63
+ const referenceLabel =
64
+ parsed?.entityType === 'serviceContract'
65
+ ? 'exposed.schema'
66
+ : 'fieldMappings.attributes';
47
67
  Object.entries(profiles).forEach(([profileName, fields]) => {
48
68
  if (!Array.isArray(fields)) return;
49
69
  fields.forEach((field, idx) => {
50
- if (typeof field === 'string' && !attrSet.has(field)) {
70
+ if (typeof field === 'string' && !profileRefSet.has(field)) {
51
71
  errors.push(
52
- `exposed.profiles.${profileName}[${idx}]: field '${field}' does not exist in fieldMappings.attributes. Add the attribute or remove the reference.`
72
+ `exposed.profiles.${profileName}[${idx}]: field '${field}' does not exist in ${referenceLabel}. Add the attribute or remove the reference.`
53
73
  );
54
74
  }
55
75
  });
@@ -121,6 +141,7 @@ function validateFieldReferences(parsed) {
121
141
 
122
142
  checkPrimaryKey(parsed, attributesAndDimensions, errors);
123
143
  checkExposedProfiles(parsed, attrSet, errors);
144
+ errors.push(...validateOrphanExposedProfiles(parsed));
124
145
  if (normalizedAttributes.length === 0) return errors;
125
146
 
126
147
  checkIndexingAndValidation(parsed, normalizedAttributes, errors);
@@ -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
  };