@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,254 @@
1
+ /**
2
+ * COM v2 capability object helpers for datasource manifests.
3
+ *
4
+ * @fileoverview capability-manifest
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const FORBIDDEN_ROOT_KEYS = new Set([
12
+ 'context',
13
+ 'origin',
14
+ 'generatedBy',
15
+ 'composition',
16
+ 'generation',
17
+ 'enterpriseResolver'
18
+ ]);
19
+
20
+ const DEFAULT_RISK_BY_KEY = {
21
+ list: 'low',
22
+ get: 'low',
23
+ search: 'low',
24
+ create: 'medium',
25
+ update: 'medium',
26
+ delete: 'high'
27
+ };
28
+
29
+ const RESOURCE_TYPE_MAP = {
30
+ conversation: 'message',
31
+ user: 'employee',
32
+ person: 'employee',
33
+ record: 'document',
34
+ folder: 'document',
35
+ country: 'organization',
36
+ report: 'document',
37
+ calendar: 'event',
38
+ subscription: 'event'
39
+ };
40
+
41
+ /**
42
+ * @param {unknown} entry
43
+ * @returns {string}
44
+ */
45
+ function capabilityKeyFromEntry(entry) {
46
+ if (typeof entry === 'string' && entry.trim()) {
47
+ return entry.trim();
48
+ }
49
+ if (entry && typeof entry === 'object' && typeof entry.key === 'string' && entry.key.trim()) {
50
+ return entry.key.trim();
51
+ }
52
+ return '';
53
+ }
54
+
55
+ /**
56
+ * @param {unknown} capabilities
57
+ * @returns {string[]}
58
+ */
59
+ function listCapabilityKeys(capabilities) {
60
+ if (!Array.isArray(capabilities)) {
61
+ return [];
62
+ }
63
+ return capabilities.map(capabilityKeyFromEntry).filter(Boolean);
64
+ }
65
+
66
+ /**
67
+ * @param {unknown} capabilities
68
+ * @param {string} key
69
+ * @returns {boolean}
70
+ */
71
+ function capabilitiesIncludes(capabilities, key) {
72
+ const target = String(key).toLowerCase();
73
+ return listCapabilityKeys(capabilities).some((item) => item.toLowerCase() === target);
74
+ }
75
+
76
+ /**
77
+ * @param {string} key
78
+ * @param {string} [displayName]
79
+ * @returns {object}
80
+ */
81
+ function buildCapabilityObject(key, displayName) {
82
+ const normalized = String(key || '').trim();
83
+ const label = displayName && String(displayName).trim() ? String(displayName).trim() : 'records';
84
+ return {
85
+ key: normalized,
86
+ description: `${normalized} operation on ${label}`,
87
+ riskLevel: DEFAULT_RISK_BY_KEY[normalized.toLowerCase()] || 'medium',
88
+ approvalRequired: false
89
+ };
90
+ }
91
+
92
+ /**
93
+ * @param {object} parsed
94
+ * @returns {object[]}
95
+ */
96
+ function normalizeCapabilitiesToObjects(parsed) {
97
+ const caps = parsed?.capabilities;
98
+ const displayName = parsed?.displayName || parsed?.key || 'records';
99
+ if (!Array.isArray(caps) || caps.length === 0) {
100
+ return [];
101
+ }
102
+ const out = [];
103
+ const seen = new Set();
104
+ caps.forEach((item) => {
105
+ if (typeof item === 'string' && item.trim()) {
106
+ const key = item.trim();
107
+ if (seen.has(key)) return;
108
+ seen.add(key);
109
+ out.push(buildCapabilityObject(key, displayName));
110
+ return;
111
+ }
112
+ if (!item || typeof item !== 'object') return;
113
+ const key = capabilityKeyFromEntry(item);
114
+ if (!key || seen.has(key)) return;
115
+ seen.add(key);
116
+ const row = buildCapabilityObject(key, displayName);
117
+ if (typeof item.description === 'string' && item.description.trim()) {
118
+ row.description = item.description.trim();
119
+ }
120
+ if (typeof item.riskLevel === 'string' && item.riskLevel.trim()) {
121
+ row.riskLevel = item.riskLevel.trim();
122
+ }
123
+ if (item.approvalRequired === true) {
124
+ row.approvalRequired = true;
125
+ }
126
+ out.push(row);
127
+ });
128
+ return out;
129
+ }
130
+
131
+ /**
132
+ * @param {object} node
133
+ * @returns {void}
134
+ */
135
+ function stripSemanticExtras(node) {
136
+ if (!node || typeof node !== 'object') return;
137
+ const semantic = node.semantic;
138
+ if (semantic && typeof semantic === 'object') {
139
+ delete semantic.categories;
140
+ delete semantic.synonyms;
141
+ if (Object.keys(semantic).length === 0) {
142
+ delete node.semantic;
143
+ }
144
+ }
145
+ Object.values(node).forEach((value) => {
146
+ if (value && typeof value === 'object') {
147
+ if (Array.isArray(value)) {
148
+ value.forEach((item) => stripSemanticExtras(item));
149
+ } else {
150
+ stripSemanticExtras(value);
151
+ }
152
+ }
153
+ });
154
+ }
155
+
156
+ /**
157
+ * @param {object} parsed
158
+ * @param {string[]} changes
159
+ * @returns {boolean}
160
+ */
161
+ function removeForbiddenComRootKeys(parsed, changes) {
162
+ let updated = false;
163
+ FORBIDDEN_ROOT_KEYS.forEach((key) => {
164
+ if (Object.prototype.hasOwnProperty.call(parsed, key)) {
165
+ delete parsed[key];
166
+ changes.push(`Removed forbidden COM v2 root key: ${key}`);
167
+ updated = true;
168
+ }
169
+ });
170
+ return updated;
171
+ }
172
+
173
+ /**
174
+ * @param {object} parsed
175
+ * @param {string[]} changes
176
+ * @returns {boolean}
177
+ */
178
+ function stripFieldMappingSemanticExtras(parsed, changes) {
179
+ if (!parsed.fieldMappings) {
180
+ return false;
181
+ }
182
+ const before = JSON.stringify(parsed.fieldMappings);
183
+ stripSemanticExtras(parsed.fieldMappings);
184
+ if (JSON.stringify(parsed.fieldMappings) === before) {
185
+ return false;
186
+ }
187
+ changes.push('Removed fieldMappings.*.semantic.categories/synonyms (COM v2)');
188
+ return true;
189
+ }
190
+
191
+ /**
192
+ * @param {object} parsed
193
+ * @param {string[]} changes
194
+ * @returns {boolean}
195
+ */
196
+ function applyResourceTypeCatalogMap(parsed, changes) {
197
+ const resourceType = parsed.resourceType;
198
+ if (typeof resourceType !== 'string') {
199
+ return false;
200
+ }
201
+ const mapped = RESOURCE_TYPE_MAP[resourceType.trim()];
202
+ if (!mapped) {
203
+ return false;
204
+ }
205
+ parsed.resourceType = mapped;
206
+ changes.push(`Mapped resourceType ${resourceType} → ${mapped}`);
207
+ return true;
208
+ }
209
+
210
+ /**
211
+ * @param {object} parsed
212
+ * @param {string[]} changes
213
+ * @returns {boolean}
214
+ */
215
+ function migrateCapabilitiesToComObjects(parsed, changes) {
216
+ const caps = parsed.capabilities;
217
+ const needsObjectMigration = Array.isArray(caps)
218
+ && caps.some((item) => typeof item === 'string' || (item && typeof item === 'object' && !item.key));
219
+ const hasStringCapabilities = Array.isArray(caps) && caps.length > 0 && typeof caps[0] === 'string';
220
+ if (!needsObjectMigration && !hasStringCapabilities) {
221
+ return false;
222
+ }
223
+ parsed.capabilities = normalizeCapabilitiesToObjects(parsed);
224
+ changes.push('Migrated capabilities[] to COM v2 capability objects');
225
+ return true;
226
+ }
227
+
228
+ /**
229
+ * @param {object} parsed
230
+ * @param {string[]} changes
231
+ * @returns {boolean}
232
+ */
233
+ function repairComV2Manifest(parsed, changes) {
234
+ if (!parsed || typeof parsed !== 'object') {
235
+ return false;
236
+ }
237
+ return [
238
+ removeForbiddenComRootKeys(parsed, changes),
239
+ stripFieldMappingSemanticExtras(parsed, changes),
240
+ applyResourceTypeCatalogMap(parsed, changes),
241
+ migrateCapabilitiesToComObjects(parsed, changes)
242
+ ].some(Boolean);
243
+ }
244
+
245
+ module.exports = {
246
+ FORBIDDEN_ROOT_KEYS,
247
+ capabilityKeyFromEntry,
248
+ listCapabilityKeys,
249
+ capabilitiesIncludes,
250
+ buildCapabilityObject,
251
+ normalizeCapabilitiesToObjects,
252
+ stripSemanticExtras,
253
+ repairComV2Manifest
254
+ };
@@ -6,6 +6,8 @@
6
6
  * @version 2.0.0
7
7
  */
8
8
 
9
+ const { capabilitiesIncludes } = require('./capability-manifest');
10
+
9
11
  /**
10
12
  * @param {object|undefined} operations
11
13
  * @param {string} logicalKey
@@ -146,10 +148,7 @@ function resolveProfileKeyForLogical(doc, logicalKey) {
146
148
  * @returns {boolean}
147
149
  */
148
150
  function capabilityLogicalExists(doc, name) {
149
- if (
150
- Array.isArray(doc.capabilities) &&
151
- doc.capabilities.some((c) => String(c).toLowerCase() === String(name).toLowerCase())
152
- ) {
151
+ if (capabilitiesIncludes(doc.capabilities, name)) {
153
152
  return true;
154
153
  }
155
154
  if (findMatchingOpsKeys(doc.openapi?.operations, name).length > 0) {
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  const { rewriteCapabilityReferences } = require('./reference-rewrite');
10
+ const { capabilityKeyFromEntry } = require('./capability-manifest');
10
11
  const { jsonPointerPath } = require('./json-pointer');
11
12
  const { buildBasicExposureProfileArray } = require('./basic-exposure');
12
13
  const {
@@ -53,9 +54,10 @@ function removeCapability(doc, logicalKey) {
53
54
  const profileKey = resolveProfileKeyForLogical(doc, logicalKey);
54
55
 
55
56
  if (Array.isArray(doc.capabilities)) {
56
- doc.capabilities = doc.capabilities.filter(
57
- (c) => String(c).toLowerCase() !== String(logicalKey).toLowerCase()
58
- );
57
+ doc.capabilities = doc.capabilities.filter((c) => {
58
+ const key = capabilityKeyFromEntry(c);
59
+ return key.toLowerCase() !== String(logicalKey).toLowerCase();
60
+ });
59
61
  }
60
62
  if (openapiKey && oo) {
61
63
  delete oo[openapiKey];
@@ -17,6 +17,7 @@ const {
17
17
  findMatchingOpsKeys,
18
18
  resolveProfileKeyForLogical
19
19
  } = require('./capability-resolve');
20
+ const { capabilityKeyFromEntry } = require('./capability-manifest');
20
21
 
21
22
  /**
22
23
  * @param {unknown} capabilities
@@ -28,7 +29,7 @@ function findCapabilityIndex(capabilities, logicalName) {
28
29
  return -1;
29
30
  }
30
31
  return capabilities.findIndex(
31
- (c) => String(c).toLowerCase() === String(logicalName).toLowerCase()
32
+ (c) => capabilityKeyFromEntry(c).toLowerCase() === String(logicalName).toLowerCase()
32
33
  );
33
34
  }
34
35
 
@@ -8,6 +8,7 @@
8
8
 
9
9
  const fs = require('node:fs');
10
10
  const path = require('path');
11
+ const { loadConfigFile, writeConfigFile } = require('../../utils/config-format');
11
12
  const { writeBackup } = require('../../utils/integration-file-backup');
12
13
  const {
13
14
  resolveValidateInputPath,
@@ -46,6 +47,33 @@ function resolveCapabilityCopyInputPath(identifier) {
46
47
  return resolveValidateInputPath(trimmed);
47
48
  }
48
49
 
50
+ /**
51
+ * Read a datasource config file (.json, .yaml, or .yml).
52
+ *
53
+ * @param {string} filePath
54
+ * @returns {object}
55
+ */
56
+ function readDatasourceConfigFile(filePath) {
57
+ return loadConfigFile(filePath);
58
+ }
59
+
60
+ /**
61
+ * @param {string} filePath
62
+ * @returns {'json'|'yaml'}
63
+ */
64
+ function inferDatasourceConfigFormat(filePath) {
65
+ const ext = path.extname(filePath).toLowerCase();
66
+ if (ext === '.json') {
67
+ return 'json';
68
+ }
69
+ if (ext === '.yaml' || ext === '.yml') {
70
+ return 'yaml';
71
+ }
72
+ throw new Error(
73
+ `Unsupported datasource config extension: ${ext || '(none)'}. Use .json, .yaml, or .yml`
74
+ );
75
+ }
76
+
49
77
  /**
50
78
  * @param {string} filePath
51
79
  * @param {object} obj
@@ -53,9 +81,9 @@ function resolveCapabilityCopyInputPath(identifier) {
53
81
  */
54
82
  function atomicWriteJson(filePath, obj) {
55
83
  const dir = path.dirname(filePath);
84
+ const format = inferDatasourceConfigFormat(filePath);
56
85
  const tmp = path.join(dir, `.${path.basename(filePath)}.${process.pid}.tmp`);
57
- const payload = `${JSON.stringify(obj, null, 2)}\n`;
58
- fs.writeFileSync(tmp, payload, 'utf8');
86
+ writeConfigFile(tmp, obj, format);
59
87
  fs.renameSync(tmp, filePath);
60
88
  }
61
89
 
@@ -89,8 +117,7 @@ function atomicWriteJson(filePath, obj) {
89
117
  */
90
118
  async function runCapabilityCopy(opts) {
91
119
  const resolvedPath = resolveCapabilityCopyInputPath(opts.fileOrKey.trim());
92
- const raw = fs.readFileSync(resolvedPath, 'utf8');
93
- const parsed = JSON.parse(raw);
120
+ const parsed = readDatasourceConfigFile(resolvedPath);
94
121
 
95
122
  const to = normalizeCapabilityKey(opts.as, 'Target (--as)');
96
123
  const fromRaw = opts.from !== undefined && opts.from !== null ? String(opts.from).trim() : '';
@@ -148,5 +175,6 @@ async function runCapabilityCopy(opts) {
148
175
  module.exports = {
149
176
  runCapabilityCopy,
150
177
  writeBackup,
178
+ readDatasourceConfigFile,
151
179
  atomicWriteJson
152
180
  };
@@ -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);