@aifabrix/builder 2.45.5 → 2.46.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 (231) hide show
  1. package/.cursor/rules/cli-layout.mdc +2 -0
  2. package/.cursor/rules/cli-product-neutral.mdc +43 -0
  3. package/.cursor/rules/project-rules.mdc +1 -0
  4. package/README.md +43 -17
  5. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  6. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  7. package/jest.config.default.js +2 -1
  8. package/jest.config.isolated.js +2 -1
  9. package/jest.global-live-fabrix-hooks.js +36 -0
  10. package/jest.isolated-projects.js +130 -0
  11. package/jest.projects.js +41 -136
  12. package/lib/api/api-response-errors.js +124 -0
  13. package/lib/api/governance-scenario-pack.api.js +159 -0
  14. package/lib/api/index.js +43 -3
  15. package/lib/api/lifecycle.api.js +77 -0
  16. package/lib/api/pipeline.api.js +3 -1
  17. package/lib/api/records-bulk.api.js +14 -1
  18. package/lib/api/validation-runner.js +3 -1
  19. package/lib/app/deploy-config.js +4 -1
  20. package/lib/app/down.js +20 -1
  21. package/lib/app/push.js +4 -5
  22. package/lib/app/register.js +7 -4
  23. package/lib/app/run-env-compose.js +84 -30
  24. package/lib/build/index.js +5 -0
  25. package/lib/certification/sync-system-certification.js +13 -6
  26. package/lib/cli/index.js +4 -0
  27. package/lib/cli/infra-guided-auth.js +109 -0
  28. package/lib/cli/infra-guided.js +84 -81
  29. package/lib/cli/setup-app.help.js +0 -31
  30. package/lib/cli/setup-app.js +28 -2
  31. package/lib/cli/setup-app.test-commands.js +5 -57
  32. package/lib/cli/setup-auth.js +25 -0
  33. package/lib/cli/setup-certification-commands.js +147 -0
  34. package/lib/cli/setup-certification.help.js +141 -0
  35. package/lib/cli/setup-environment.js +19 -2
  36. package/lib/cli/setup-external-system-help.js +62 -0
  37. package/lib/cli/setup-external-system.js +25 -3
  38. package/lib/cli/setup-infra.js +9 -1
  39. package/lib/cli/setup-platform.js +6 -1
  40. package/lib/cli/setup-secrets.js +33 -1
  41. package/lib/cli/setup-utility-help-after.js +22 -2
  42. package/lib/cli/setup-utility-repair.js +198 -0
  43. package/lib/cli/setup-utility-resolve.js +40 -4
  44. package/lib/cli/setup-utility-validate.js +197 -0
  45. package/lib/cli/setup-utility.js +4 -239
  46. package/lib/commands/auth-status-client-tokens.js +214 -0
  47. package/lib/commands/auth-status-dataplane-version.js +58 -20
  48. package/lib/commands/auth-status.js +41 -47
  49. package/lib/commands/datasource-test-trust-cli.js +27 -23
  50. package/lib/commands/datasource-unified-test-cli.js +6 -3
  51. package/lib/commands/datasource-unified-test-cli.options.js +5 -1
  52. package/lib/commands/datasource.js +7 -7
  53. package/lib/commands/dev-show-display.js +8 -1
  54. package/lib/commands/governance-cli-shared.js +94 -0
  55. package/lib/commands/governance-command-action.js +132 -0
  56. package/lib/commands/governance-download-external.js +34 -0
  57. package/lib/commands/governance-generate-external.js +71 -0
  58. package/lib/commands/governance-upload-external.js +36 -0
  59. package/lib/commands/governance-verify-external.js +64 -0
  60. package/lib/commands/governance.js +144 -0
  61. package/lib/commands/lifecycle-command-action.js +123 -0
  62. package/lib/commands/lifecycle-external.js +268 -0
  63. package/lib/commands/login.js +11 -2
  64. package/lib/commands/repair-auth-apply.js +127 -0
  65. package/lib/commands/repair-changed-files.js +41 -0
  66. package/lib/commands/repair-datasource-keys.js +4 -1
  67. package/lib/commands/repair-datasource-run.js +59 -0
  68. package/lib/commands/repair-datasource-test-payload.js +259 -0
  69. package/lib/commands/repair-datasource.js +1 -51
  70. package/lib/commands/repair-env-template.js +64 -13
  71. package/lib/commands/repair-external-run.js +216 -0
  72. package/lib/commands/repair-openapi-sync.js +23 -0
  73. package/lib/commands/repair-persist.js +4 -0
  74. package/lib/commands/repair-rbac.js +52 -1
  75. package/lib/commands/repair-steps.js +145 -0
  76. package/lib/commands/repair.js +29 -296
  77. package/lib/commands/secrets-get.js +184 -0
  78. package/lib/commands/secrets-remove-all.js +3 -1
  79. package/lib/commands/setup-image-refresh.js +146 -0
  80. package/lib/commands/setup-modes.js +158 -162
  81. package/lib/commands/setup-platform-auth.js +76 -0
  82. package/lib/commands/setup-prompts-admin.js +244 -0
  83. package/lib/commands/setup-prompts-platform-mode.js +67 -0
  84. package/lib/commands/setup-prompts-validators.js +36 -0
  85. package/lib/commands/setup-prompts.js +24 -76
  86. package/lib/commands/setup-run-context.js +45 -0
  87. package/lib/commands/setup-spinners.js +39 -0
  88. package/lib/commands/setup.js +27 -5
  89. package/lib/commands/teardown.js +39 -0
  90. package/lib/commands/test-e2e-external.js +24 -8
  91. package/lib/commands/test-governance-external.js +3 -24
  92. package/lib/commands/test-trust-external.js +20 -21
  93. package/lib/commands/up-common.js +25 -13
  94. package/lib/commands/up-dataplane.js +50 -13
  95. package/lib/commands/upload-publish-context.js +75 -0
  96. package/lib/commands/upload-sidecar-followups.js +89 -0
  97. package/lib/commands/upload.js +90 -108
  98. package/lib/commands/verify-governance-combined-external.js +61 -0
  99. package/lib/commands/verify-governance-command-action.js +64 -0
  100. package/lib/commands/verify-governance-db.js +102 -0
  101. package/lib/commands/verify-governance-external.js +77 -0
  102. package/lib/commands/verify-governance-result-builders.js +71 -0
  103. package/lib/commands/verify-operations-command-action.js +68 -0
  104. package/lib/commands/verify-operations-external.js +268 -0
  105. package/lib/commands/verify-operations-persist.js +75 -0
  106. package/lib/commands/verify-operations-step-plan.js +68 -0
  107. package/lib/commands/verify-operations-steps.js +111 -0
  108. package/lib/commands/verify-trust-command-action.js +70 -0
  109. package/lib/commands/verify-trust-external.js +67 -0
  110. package/lib/commands/wizard-config-normalizer.js +43 -23
  111. package/lib/commands/wizard-core.js +2 -0
  112. package/lib/core/admin-password-generator.js +38 -0
  113. package/lib/core/admin-secrets-bundle.js +111 -0
  114. package/lib/core/admin-secrets-env-overlay.js +62 -0
  115. package/lib/core/admin-secrets.js +32 -4
  116. package/lib/core/config-attach-extensions.js +3 -0
  117. package/lib/core/config-platform-controller.js +44 -0
  118. package/lib/core/config-setup-installation-profile.js +42 -0
  119. package/lib/core/config-setup-platform-mode.js +46 -0
  120. package/lib/core/config.js +46 -36
  121. package/lib/core/secrets-admin-env.js +6 -2
  122. package/lib/core/secrets-ensure.js +4 -0
  123. package/lib/core/secrets-env-content.js +54 -48
  124. package/lib/core/secrets-env-write.js +10 -1
  125. package/lib/core/secrets-infra-placeholder-sync.js +2 -0
  126. package/lib/core/setup-installation-profile.js +43 -0
  127. package/lib/core/setup-platform-mode.js +55 -0
  128. package/lib/datasource/agent-trust-debug-log.js +1 -1
  129. package/lib/datasource/agent-trust-run.js +5 -3
  130. package/lib/datasource/audit-evidence-extract-debug.js +30 -18
  131. package/lib/datasource/audit-evidence-extract.js +7 -4
  132. package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
  133. package/lib/datasource/bulk-loader-run-batches.js +15 -3
  134. package/lib/datasource/datasource-load-export-context.js +3 -0
  135. package/lib/datasource/log-cleaner.js +2 -2
  136. package/lib/datasource/log-viewer.js +8 -8
  137. package/lib/datasource/test-e2e.js +4 -1
  138. package/lib/datasource/unified-validation-run-body.js +3 -1
  139. package/lib/datasource/unified-validation-run.js +5 -2
  140. package/lib/deployment/environment-poll-status.js +138 -0
  141. package/lib/deployment/environment.js +42 -34
  142. package/lib/external-system/download.js +21 -20
  143. package/lib/external-system/generator.js +5 -1
  144. package/lib/external-system/integration-auth-context.js +45 -0
  145. package/lib/external-system/test-auth.js +12 -5
  146. package/lib/external-system/test.js +6 -6
  147. package/lib/generator/external-controller-manifest.js +15 -0
  148. package/lib/governance/governance-cli-errors.js +96 -0
  149. package/lib/governance/governance-download-display.js +38 -0
  150. package/lib/governance/governance-run-display.js +215 -25
  151. package/lib/infrastructure/admin-secrets-apply.js +84 -0
  152. package/lib/infrastructure/admin-secrets-ensure.js +130 -0
  153. package/lib/infrastructure/admin-secrets-resolve.js +95 -0
  154. package/lib/infrastructure/admin-secrets-sync.js +91 -0
  155. package/lib/infrastructure/helpers.js +23 -129
  156. package/lib/infrastructure/index.js +5 -6
  157. package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
  158. package/lib/lifecycle/certification-progress.js +231 -0
  159. package/lib/lifecycle/dataplane-ui-url.js +145 -0
  160. package/lib/lifecycle/exit-codes.js +24 -0
  161. package/lib/lifecycle/json-envelope.js +25 -0
  162. package/lib/lifecycle/lifecycle-governance-options.js +51 -0
  163. package/lib/lifecycle/logger-quiet.js +29 -0
  164. package/lib/lifecycle/open-api-published.js +59 -0
  165. package/lib/lifecycle/operations-readiness-metrics.js +101 -0
  166. package/lib/lifecycle/product-model.js +196 -0
  167. package/lib/lifecycle/report-display.js +471 -0
  168. package/lib/lifecycle/scenario-upload.js +191 -0
  169. package/lib/lifecycle/verify-step-progress.js +87 -0
  170. package/lib/parameters/infra-kv-discovery.js +27 -2
  171. package/lib/protection/auth-context.js +10 -4
  172. package/lib/schema/application-schema.json +1 -1
  173. package/lib/schema/datasource-test-run.schema.json +89 -6
  174. package/lib/schema/external-datasource.schema.json +2636 -2622
  175. package/lib/schema/external-system.schema.json +1089 -761
  176. package/lib/schema/infra.parameter.yaml +45 -0
  177. package/lib/schema/type/credential-field-registry.json +103 -97
  178. package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
  179. package/lib/utils/api.js +55 -31
  180. package/lib/utils/app-relative-path-env.js +112 -0
  181. package/lib/utils/cli-secrets-error-format.js +83 -21
  182. package/lib/utils/cli-utils.js +16 -4
  183. package/lib/utils/compose-generate-docker-compose.js +4 -0
  184. package/lib/utils/compose-traefik-ingress-base.js +16 -1
  185. package/lib/utils/config-secrets-path.js +46 -0
  186. package/lib/utils/controller-url.js +88 -1
  187. package/lib/utils/credential-secrets-env.js +137 -9
  188. package/lib/utils/dataplane-resolver.js +129 -2
  189. package/lib/utils/declarative-url-ports.js +3 -3
  190. package/lib/utils/deployment-auth-mode.js +41 -0
  191. package/lib/utils/developer-volume-id.js +33 -0
  192. package/lib/utils/env-copy.js +14 -9
  193. package/lib/utils/env-kv-orphan-lines.js +43 -0
  194. package/lib/utils/error-formatters/network-errors.js +13 -4
  195. package/lib/utils/external-env-template.js +1 -0
  196. package/lib/utils/external-system-readiness-core.js +10 -0
  197. package/lib/utils/external-system-readiness-display.js +80 -23
  198. package/lib/utils/health-check.js +4 -1
  199. package/lib/utils/help-builder.js +11 -8
  200. package/lib/utils/keycloak-realm-ready.js +101 -0
  201. package/lib/utils/load-cip-capacity-display-config.js +61 -15
  202. package/lib/utils/manifest-source-emit.js +2 -0
  203. package/lib/utils/pipeline-upload-auth-retry.js +82 -0
  204. package/lib/utils/platform-controller-url.js +156 -0
  205. package/lib/utils/postgres-platform-bootstrap.js +199 -0
  206. package/lib/utils/postgres-wipe.js +9 -2
  207. package/lib/utils/resolve-env-mode.js +47 -0
  208. package/lib/utils/secrets-generator.js +31 -25
  209. package/lib/utils/secrets-missing-error.js +164 -0
  210. package/lib/utils/service-url-label.js +81 -0
  211. package/lib/utils/setup-quiet-output.js +25 -0
  212. package/lib/utils/template-helpers.js +15 -0
  213. package/lib/utils/token-manager-client-credentials.js +201 -0
  214. package/lib/utils/token-manager-messages.js +8 -0
  215. package/lib/utils/token-manager-refresh.js +12 -2
  216. package/lib/utils/token-manager.js +86 -105
  217. package/lib/utils/upload-pipeline-ui.js +12 -5
  218. package/lib/utils/upload-sync-options.js +42 -0
  219. package/lib/utils/url-declarative-public-base.js +21 -3
  220. package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
  221. package/lib/utils/validation-poll-ui.js +8 -0
  222. package/lib/utils/validation-run-stage.js +45 -0
  223. package/package.json +1 -1
  224. package/templates/applications/dataplane/application.yaml +2 -1
  225. package/templates/applications/dataplane/env.template +24 -1
  226. package/templates/applications/miso-controller/env.template +16 -0
  227. package/templates/external-system/README.md.hbs +74 -17
  228. package/templates/python/docker-compose.hbs +19 -18
  229. package/templates/typescript/docker-compose.hbs +18 -17
  230. package/lib/commands/test-governance-command-action.js +0 -55
  231. package/lib/commands/test-trust-command-action.js +0 -118
@@ -85,7 +85,7 @@ async function normalizeControllerUrl(options) {
85
85
  if (!controllerUrl) {
86
86
  controllerUrl = await resolveControllerUrl();
87
87
  }
88
- controllerUrl = String(controllerUrl).replace(/\/+$/, '');
88
+ controllerUrl = String(controllerUrl).trim().replace(/\/+$/, '');
89
89
  // Save controller URL to config
90
90
  await setControllerUrl(controllerUrl);
91
91
  if (!options.compact) {
@@ -170,6 +170,14 @@ async function handleDeviceCodeLoginFlow(controllerUrl, environment, options) {
170
170
  */
171
171
  async function refreshDataplaneVersionIfNeeded(controllerUrl, options, skipRefresh) {
172
172
  if (skipRefresh) return;
173
+ if (!options.compact) {
174
+ const { LOGIN_POST_REFRESH_BUDGET_MS } = require('./auth-status-dataplane-version');
175
+ logger.log(
176
+ chalk.gray(
177
+ `Syncing dataplane version cache (optional, up to ${Math.round(LOGIN_POST_REFRESH_BUDGET_MS / 1000)}s)...`
178
+ )
179
+ );
180
+ }
173
181
  await tryRefreshDataplaneVersionAfterLogin(controllerUrl, options);
174
182
  }
175
183
 
@@ -213,7 +221,8 @@ async function handleLogin(options) {
213
221
  const method = await determineAuthMethod(options.method);
214
222
  validateScopeOptions(method, options);
215
223
 
216
- const skipRefresh = options.skipDataplaneVersionRefresh === true;
224
+ const skipRefresh =
225
+ options.skipDataplaneVersionRefresh === true || options.skipVersionCache === true;
217
226
  const flow = await executeLoginMethod(method, controllerUrl, environment, options, skipRefresh);
218
227
  if (flow === 'device') return;
219
228
 
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Repair --auth: canonical authentication blocks and bearerToken preset.
3
+ *
4
+ * Applies canonical authentication blocks for repair --auth.
5
+ *
6
+ * @fileoverview Repair authentication method application
7
+ * @author AI Fabrix Team
8
+ * @version 1.0.0
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const { buildAuthenticationFromMethod } = require('../external-system/generator');
14
+
15
+ /** Repair CLI --auth values (normalized lowercase). */
16
+ const ALLOWED_REPAIR_AUTH = [
17
+ 'oauth2',
18
+ 'aad',
19
+ 'apikey',
20
+ 'bearertoken',
21
+ 'bearerkey',
22
+ 'basic',
23
+ 'queryparam',
24
+ 'oidc',
25
+ 'hmac',
26
+ 'none'
27
+ ];
28
+
29
+ const PRESERVED_VARIABLE_KEYS = ['baseUrl', 'testEndpoint'];
30
+
31
+ /**
32
+ * @param {string} authOption - Raw --auth value
33
+ * @returns {string} Normalized lowercase auth option
34
+ */
35
+ function normalizeRepairAuthOption(authOption) {
36
+ return String(authOption || '').trim().toLowerCase();
37
+ }
38
+
39
+ /**
40
+ * @param {string} authOption - Normalized --auth value
41
+ * @returns {boolean}
42
+ */
43
+ function isAllowedRepairAuth(authOption) {
44
+ return ALLOWED_REPAIR_AUTH.includes(normalizeRepairAuthOption(authOption));
45
+ }
46
+
47
+ /**
48
+ * Builds authentication block for repair --auth.
49
+ * @param {string} systemKey - System key
50
+ * @param {string} authOption - Normalized repair auth (e.g. bearertoken, apikey)
51
+ * @returns {{ method: string, variables: Object, security: Object }}
52
+ */
53
+ function buildAuthenticationForRepair(systemKey, authOption) {
54
+ const normalized = normalizeRepairAuthOption(authOption);
55
+ const schemaMethod =
56
+ normalized === 'queryparam'
57
+ ? 'queryParam'
58
+ : normalized === 'bearertoken' || normalized === 'bearerkey'
59
+ ? 'bearerToken'
60
+ : normalized;
61
+ return buildAuthenticationFromMethod(systemKey, schemaMethod);
62
+ }
63
+
64
+ /**
65
+ * Merges preserved connectivity fields from existing auth into template variables.
66
+ * @param {Object} mergedVariables - Target variables object (mutated)
67
+ * @param {Object} existingVars - Prior authentication.variables
68
+ */
69
+ function mergePreservedAuthVariables(mergedVariables, existingVars) {
70
+ if (!existingVars || typeof existingVars !== 'object') return;
71
+ for (const key of PRESERVED_VARIABLE_KEYS) {
72
+ if (existingVars[key] !== undefined && existingVars[key] !== null) {
73
+ mergedVariables[key] = existingVars[key];
74
+ }
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Human-readable label for repair change log.
80
+ * @param {string} authOption - Normalized repair auth
81
+ * @returns {string}
82
+ */
83
+ function repairAuthChangeLabel(authOption) {
84
+ const normalized = normalizeRepairAuthOption(authOption);
85
+ if (normalized === 'bearertoken' || normalized === 'bearerkey') return 'bearerToken';
86
+ return normalized;
87
+ }
88
+
89
+ const MISSING_TEST_ENDPOINT_WARNING =
90
+ 'authentication.variables.testEndpoint is missing for apikey/bearerToken auth — add a GET URL for credential/E2E tests (e.g. /crm/v3/objects/contacts?limit=1, or a path resolved against baseUrl).';
91
+
92
+ /**
93
+ * Warn when apikey auth (including bearerToken preset) has no testEndpoint.
94
+ * @param {Object} authentication - authentication block on system file
95
+ * @param {string[]} warnings - Mutable warnings list
96
+ */
97
+ function appendTestEndpointWarningIfMissing(authentication, warnings) {
98
+ const method = String(authentication?.method || '').toLowerCase();
99
+ if (method !== 'apikey' && method !== 'bearertoken') return;
100
+ const testEndpoint = String(authentication?.variables?.testEndpoint || '').trim();
101
+ if (testEndpoint) return;
102
+ if (warnings.includes(MISSING_TEST_ENDPOINT_WARNING)) return;
103
+ warnings.push(MISSING_TEST_ENDPOINT_WARNING);
104
+ }
105
+
106
+ /**
107
+ * Audit system authentication after repair (with or without --auth).
108
+ * @param {Object} systemParsed - Parsed system config
109
+ * @param {string[]} warnings - Mutable warnings list
110
+ */
111
+ function auditRepairAuthenticationWarnings(systemParsed, warnings) {
112
+ const authentication = systemParsed?.authentication || systemParsed?.auth;
113
+ if (!authentication || typeof authentication !== 'object') return;
114
+ appendTestEndpointWarningIfMissing(authentication, warnings);
115
+ }
116
+
117
+ module.exports = {
118
+ ALLOWED_REPAIR_AUTH,
119
+ MISSING_TEST_ENDPOINT_WARNING,
120
+ normalizeRepairAuthOption,
121
+ isAllowedRepairAuth,
122
+ buildAuthenticationForRepair,
123
+ mergePreservedAuthVariables,
124
+ repairAuthChangeLabel,
125
+ appendTestEndpointWarningIfMissing,
126
+ auditRepairAuthenticationWarnings
127
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Track integration files touched during repair for CLI summary output.
3
+ *
4
+ * @fileoverview Repair changed-file tracking
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+
13
+ /**
14
+ * Ensures backup/repair context has changed-file buckets.
15
+ * @param {Object} ctx - Repair backup context
16
+ * @returns {{ changedFiles: string[], wouldChangeFiles: string[] }}
17
+ */
18
+ function ensureChangedFileBuckets(ctx) {
19
+ if (!ctx) return { changedFiles: [], wouldChangeFiles: [] };
20
+ if (!Array.isArray(ctx.changedFiles)) ctx.changedFiles = [];
21
+ if (!Array.isArray(ctx.wouldChangeFiles)) ctx.wouldChangeFiles = [];
22
+ return { changedFiles: ctx.changedFiles, wouldChangeFiles: ctx.wouldChangeFiles };
23
+ }
24
+
25
+ /**
26
+ * Records a file path that repair wrote or would write (dry-run).
27
+ * @param {string} filePath - Path to file
28
+ * @param {Object} ctx - Repair context with dryRun, changedFiles, wouldChangeFiles
29
+ */
30
+ function trackRepairWrite(filePath, ctx) {
31
+ if (!filePath || !ctx) return;
32
+ const abs = path.resolve(filePath);
33
+ const { changedFiles, wouldChangeFiles } = ensureChangedFileBuckets(ctx);
34
+ const bucket = ctx.dryRun ? wouldChangeFiles : changedFiles;
35
+ if (!bucket.includes(abs)) bucket.push(abs);
36
+ }
37
+
38
+ module.exports = {
39
+ ensureChangedFileBuckets,
40
+ trackRepairWrite
41
+ };
@@ -16,6 +16,7 @@ const path = require('path');
16
16
  const fs = require('fs');
17
17
  const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
18
18
  const { backupIntegrationFile } = require('../utils/integration-file-backup');
19
+ const { trackRepairWrite } = require('./repair-changed-files');
19
20
 
20
21
  /**
21
22
  * Returns suffix from a canonical-format filename: <systemKey>-datasource-<suffix>.<ext>.
@@ -174,8 +175,9 @@ function normalizeDatasourceKeysAndFilenames(appPath, datasourceFiles, systemKey
174
175
 
175
176
  if (parsed.key !== canonicalKey) {
176
177
  parsed.key = canonicalKey;
178
+ const fullPath = path.join(appPath, fileName);
179
+ trackRepairWrite(fullPath, backupCtx);
177
180
  if (!dryRun) {
178
- const fullPath = path.join(appPath, fileName);
179
181
  backupIntegrationFile(fullPath, backupCtx);
180
182
  writeConfigFile(fullPath, parsed);
181
183
  }
@@ -185,6 +187,7 @@ function normalizeDatasourceKeysAndFilenames(appPath, datasourceFiles, systemKey
185
187
  if (fileName !== canonicalFileName) {
186
188
  const oldPath = path.join(appPath, fileName);
187
189
  const newPath = path.join(appPath, canonicalFileName);
190
+ trackRepairWrite(newPath, backupCtx);
188
191
  if (!dryRun && fs.existsSync(oldPath) && !fs.existsSync(newPath)) {
189
192
  backupIntegrationFile(oldPath, backupCtx);
190
193
  fs.renameSync(oldPath, newPath);
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Batch datasource file repair during external integration repair.
3
+ *
4
+ * @fileoverview Run repairDatasourceFile across integration datasource files
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+ const fs = require('fs');
13
+ const chalk = require('chalk');
14
+ const logger = require('../utils/logger');
15
+ const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
16
+ const { backupIntegrationFile } = require('../utils/integration-file-backup');
17
+ const { trackRepairWrite } = require('./repair-changed-files');
18
+ const { repairDatasourceFile } = require('./repair-datasource');
19
+
20
+ /**
21
+ * @param {string} appPath
22
+ * @param {string[]} datasourceFiles
23
+ * @param {Object} options
24
+ * @param {boolean} dryRun
25
+ * @param {string[]} changes
26
+ * @returns {boolean}
27
+ */
28
+ function runDatasourceRepairs(appPath, datasourceFiles, options, dryRun, changes) {
29
+ if (!datasourceFiles || datasourceFiles.length === 0) return false;
30
+ let updated = false;
31
+ for (const fileName of datasourceFiles) {
32
+ const filePath = path.join(appPath, fileName);
33
+ if (!fs.existsSync(filePath)) continue;
34
+ try {
35
+ const parsed = loadConfigFile(filePath);
36
+ const { updated: fileUpdated, changes: fileChanges } = repairDatasourceFile(parsed, {
37
+ expose: options.expose,
38
+ sync: options.sync,
39
+ test: options.test
40
+ });
41
+ if (fileUpdated) {
42
+ updated = true;
43
+ fileChanges.forEach(c => changes.push(`${fileName}: ${c}`));
44
+ if (!dryRun) {
45
+ backupIntegrationFile(filePath, options.backupCtx);
46
+ writeConfigFile(filePath, parsed);
47
+ }
48
+ trackRepairWrite(filePath, options.backupCtx);
49
+ }
50
+ } catch (err) {
51
+ logger.log(chalk.yellow(`⚠ Could not repair datasource ${fileName}: ${err.message}`));
52
+ }
53
+ }
54
+ return updated;
55
+ }
56
+
57
+ module.exports = {
58
+ runDatasourceRepairs
59
+ };
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Schema-aware testPayload repair (payloadTemplate / expectedResult).
3
+ *
4
+ * @fileoverview Helpers for repair-datasource testPayload generation
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ /**
12
+ * @param {string|string[]|undefined} type - JSON Schema type
13
+ * @returns {string}
14
+ */
15
+ function primarySchemaType(type) {
16
+ if (Array.isArray(type)) {
17
+ const nonNull = type.filter(t => t !== 'null');
18
+ return nonNull[0] || 'string';
19
+ }
20
+ return type || 'string';
21
+ }
22
+
23
+ /**
24
+ * @param {Object|null|undefined} schema - JSON Schema node
25
+ * @param {string} key - Property name
26
+ * @returns {Object|null}
27
+ */
28
+ function getSchemaProperty(schema, key) {
29
+ if (!schema || typeof schema !== 'object') return null;
30
+ const props = schema.properties;
31
+ if (props && typeof props === 'object' && Object.prototype.hasOwnProperty.call(props, key)) {
32
+ return props[key];
33
+ }
34
+ if (schema.additionalProperties === true) {
35
+ return { type: 'string' };
36
+ }
37
+ if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
38
+ return schema.additionalProperties;
39
+ }
40
+ return null;
41
+ }
42
+
43
+ /**
44
+ * @param {Object|null|undefined} rootSchema - metadataSchema root
45
+ * @param {string[]} pathParts - Dot path under raw payload (no raw. prefix)
46
+ * @returns {Object|null}
47
+ */
48
+ function getSchemaNodeAtPath(rootSchema, pathParts) {
49
+ if (!rootSchema || !pathParts.length) return rootSchema;
50
+ let node = rootSchema;
51
+ for (const part of pathParts) {
52
+ if (!part) continue;
53
+ const next = getSchemaProperty(node, part);
54
+ if (!next) return null;
55
+ node = next;
56
+ }
57
+ return node;
58
+ }
59
+
60
+ /**
61
+ * @param {Object|null|undefined} node - JSON Schema node
62
+ * @returns {*}
63
+ */
64
+ function placeholderFromSchemaNode(node) {
65
+ if (!node || typeof node !== 'object') return '';
66
+ const type = primarySchemaType(node.type);
67
+ if (node.format === 'date-time') return '2025-01-15T10:30:00Z';
68
+ if (node.format === 'date') return '2025-01-15';
69
+ if (node.format === 'uuid') return '00000000-0000-4000-8000-000000000001';
70
+ if (type === 'boolean') return false;
71
+ if (type === 'integer' || type === 'number') return 0;
72
+ if (type === 'array') return [];
73
+ if (type === 'object') {
74
+ if (node.properties && typeof node.properties === 'object') {
75
+ const out = {};
76
+ for (const [k, sub] of Object.entries(node.properties)) {
77
+ out[k] = placeholderFromSchemaNode(sub);
78
+ }
79
+ return out;
80
+ }
81
+ return {};
82
+ }
83
+ return '';
84
+ }
85
+
86
+ /**
87
+ * @param {string} type - Legacy attribute type
88
+ * @returns {*}
89
+ */
90
+ function placeholderForType(type) {
91
+ if (type === 'number' || type === 'integer') return 0;
92
+ if (type === 'boolean') return false;
93
+ if (type === 'array') return [];
94
+ if (type === 'object') return {};
95
+ return '';
96
+ }
97
+
98
+ /**
99
+ * @param {Object} parsed - Datasource config
100
+ * @param {string[]} pathParts - Path under raw payload
101
+ * @param {Object} [attrConfig] - Attribute config (optional type fallback)
102
+ * @returns {*}
103
+ */
104
+ function resolvePlaceholderForPath(parsed, pathParts, attrConfig) {
105
+ const node = getSchemaNodeAtPath(parsed?.metadataSchema, pathParts);
106
+ if (node) return placeholderFromSchemaNode(node);
107
+ return placeholderForType(attrConfig?.type || 'string');
108
+ }
109
+
110
+ /**
111
+ * @param {Object} obj - Target object (mutated)
112
+ * @param {string[]} pathParts - Path segments
113
+ * @param {*} value - Value to set
114
+ */
115
+ function setNestedValue(obj, pathParts, value) {
116
+ const parts = pathParts.filter(Boolean);
117
+ if (!parts.length) return;
118
+ let cur = obj;
119
+ for (let i = 0; i < parts.length - 1; i++) {
120
+ const p = parts[i];
121
+ if (!(p in cur) || typeof cur[p] !== 'object' || cur[p] === null || Array.isArray(cur[p])) {
122
+ cur[p] = {};
123
+ }
124
+ cur = cur[p];
125
+ }
126
+ const last = parts[parts.length - 1];
127
+ const existing = cur[last];
128
+ if (
129
+ existing &&
130
+ typeof existing === 'object' &&
131
+ !Array.isArray(existing) &&
132
+ value &&
133
+ typeof value === 'object' &&
134
+ !Array.isArray(value)
135
+ ) {
136
+ cur[last] = { ...existing, ...value };
137
+ return;
138
+ }
139
+ cur[last] = value;
140
+ }
141
+
142
+ /**
143
+ * @param {Object} parsed - Datasource config
144
+ * @param {Array<{ path: string, config: Object }>} pathEntries - Sorted deepest-first
145
+ * @param {Object} payloadTemplate - payloadTemplate (mutated)
146
+ */
147
+ function enrichObjectLeavesFromNestedPaths(parsed, pathEntries, payloadTemplate) {
148
+ const propertiesNode = getSchemaNodeAtPath(parsed?.metadataSchema, ['properties']);
149
+ for (const { path } of pathEntries) {
150
+ if (!path.startsWith('properties.')) continue;
151
+ const leaf = path.slice('properties.'.length);
152
+ if (!leaf || leaf.includes('.')) continue;
153
+ if (!payloadTemplate.properties || typeof payloadTemplate.properties !== 'object') {
154
+ payloadTemplate.properties = placeholderFromSchemaNode(propertiesNode);
155
+ }
156
+ if (payloadTemplate.properties[leaf] === undefined || payloadTemplate.properties[leaf] === '') {
157
+ const leafNode = getSchemaNodeAtPath(parsed?.metadataSchema, ['properties', leaf]);
158
+ payloadTemplate.properties[leaf] = leafNode
159
+ ? placeholderFromSchemaNode(leafNode)
160
+ : 'sample';
161
+ }
162
+ }
163
+ }
164
+
165
+ /**
166
+ * @param {string} expressionPath - Path from {{ ... }} token
167
+ * @returns {string|null}
168
+ */
169
+ function stripExpressionPathPrefix(expressionPath) {
170
+ const path = expressionPath.trim();
171
+ if (path.startsWith('raw.')) return path.slice(4);
172
+ if (path.startsWith('metadata.')) return path.slice(9);
173
+ return path;
174
+ }
175
+
176
+ /**
177
+ * @param {Object} parsed - Datasource config
178
+ * @param {Object} attrs - fieldMappings.attributes
179
+ * @returns {Array<{ attrKey: string, path: string|null, config: Object, expectedOnly: *|null }>}
180
+ */
181
+ function buildPathEntriesFromAttributes(parsed, attrs) {
182
+ const pathEntries = [];
183
+ for (const [attrKey, config] of Object.entries(attrs)) {
184
+ const match = config?.expression?.match(/\{\{\s*([^}|]+)/);
185
+ if (!match) {
186
+ const metaNode = parsed.metadataSchema?.properties?.[attrKey];
187
+ const expected = metaNode
188
+ ? placeholderFromSchemaNode(metaNode)
189
+ : placeholderForType(config?.type || 'string');
190
+ pathEntries.push({ attrKey, path: null, config, expectedOnly: expected });
191
+ continue;
192
+ }
193
+ pathEntries.push({
194
+ attrKey,
195
+ path: stripExpressionPathPrefix(match[1]),
196
+ config,
197
+ expectedOnly: null
198
+ });
199
+ }
200
+ return pathEntries.sort((a, b) => {
201
+ const depthA = a.path ? a.path.split('.').length : 0;
202
+ const depthB = b.path ? b.path.split('.').length : 0;
203
+ return depthB - depthA;
204
+ });
205
+ }
206
+
207
+ /**
208
+ * @param {Object} parsed - Datasource config
209
+ * @param {Array} pathEntries - From buildPathEntriesFromAttributes
210
+ * @returns {{ payloadTemplate: Object, expectedResult: Object }}
211
+ */
212
+ function buildTemplatesFromPathEntries(parsed, pathEntries) {
213
+ const payloadTemplate = {};
214
+ const expectedResult = {};
215
+ for (const entry of pathEntries) {
216
+ const { attrKey, path, config, expectedOnly } = entry;
217
+ const metaNode = parsed.metadataSchema?.properties?.[attrKey];
218
+ expectedResult[attrKey] = metaNode
219
+ ? placeholderFromSchemaNode(metaNode)
220
+ : (expectedOnly ?? placeholderForType(config?.type || 'string'));
221
+ if (!path) continue;
222
+ const parts = path.split('.');
223
+ setNestedValue(payloadTemplate, parts, resolvePlaceholderForPath(parsed, parts, config));
224
+ }
225
+ enrichObjectLeavesFromNestedPaths(parsed, pathEntries, payloadTemplate);
226
+ return { payloadTemplate, expectedResult };
227
+ }
228
+
229
+ /**
230
+ * Builds minimal payloadTemplate and expectedResult from attributes + metadataSchema.
231
+ *
232
+ * @param {Object} parsed - Parsed datasource (mutated)
233
+ * @param {string[]} changes - Change log
234
+ * @returns {boolean} True if testPayload was added or updated
235
+ */
236
+ function repairTestPayload(parsed, changes) {
237
+ const attrs = parsed?.fieldMappings?.attributes;
238
+ if (!attrs || typeof attrs !== 'object') return false;
239
+
240
+ const pathEntries = buildPathEntriesFromAttributes(parsed, attrs);
241
+ const { payloadTemplate, expectedResult } = buildTemplatesFromPathEntries(parsed, pathEntries);
242
+
243
+ if (!parsed.testPayload) parsed.testPayload = {};
244
+ parsed.testPayload.payloadTemplate = payloadTemplate;
245
+ parsed.testPayload.expectedResult = expectedResult;
246
+ changes.push(
247
+ 'Generated testPayload.payloadTemplate and testPayload.expectedResult from attributes and metadataSchema'
248
+ );
249
+ return true;
250
+ }
251
+
252
+ module.exports = {
253
+ primarySchemaType,
254
+ getSchemaNodeAtPath,
255
+ placeholderFromSchemaNode,
256
+ placeholderForType,
257
+ resolvePlaceholderForPath,
258
+ repairTestPayload
259
+ };
@@ -11,6 +11,7 @@
11
11
 
12
12
  const { repairOpenapiSection } = require('./repair-datasource-openapi');
13
13
  const { normalizeAutoRbacOperationKeys } = require('./repair-datasource-auto-rbac');
14
+ const { repairTestPayload } = require('./repair-datasource-test-payload');
14
15
 
15
16
  const DEFAULT_SYNC = {
16
17
  mode: 'pull',
@@ -358,25 +359,6 @@ function repairSyncSection(parsed, changes) {
358
359
  return true;
359
360
  }
360
361
 
361
- function placeholderForType(type) {
362
- if (type === 'number' || type === 'integer') return 0;
363
- if (type === 'boolean') return false;
364
- if (type === 'array') return [];
365
- if (type === 'object') return {};
366
- return '';
367
- }
368
-
369
- function setNested(obj, pathParts, value) {
370
- let cur = obj;
371
- for (let i = 0; i < pathParts.length - 1; i++) {
372
- const p = pathParts[i];
373
- if (!(p in cur) || typeof cur[p] !== 'object') cur[p] = {};
374
- cur = cur[p];
375
- }
376
- const last = pathParts[pathParts.length - 1];
377
- if (last) cur[last] = value;
378
- }
379
-
380
362
  /**
381
363
  * Removes unknown top-level keys from testPayload (v2.4.1 closed root).
382
364
  * @param {Object} parsed - Parsed datasource (mutated)
@@ -398,38 +380,6 @@ function sanitizeTestPayloadTopLevel(parsed, changes) {
398
380
  return true;
399
381
  }
400
382
 
401
- /**
402
- * Builds minimal payloadTemplate and expectedResult from attribute expression paths.
403
- *
404
- * @param {Object} parsed - Parsed datasource (mutated)
405
- * @param {string[]} changes - Change log
406
- * @returns {boolean} True if testPayload was added or updated
407
- */
408
- function repairTestPayload(parsed, changes) {
409
- const attrs = parsed?.fieldMappings?.attributes;
410
- if (!attrs || typeof attrs !== 'object') return false;
411
- const payloadTemplate = {};
412
- const expectedResult = {};
413
- for (const [key, config] of Object.entries(attrs)) {
414
- const type = config?.type || 'string';
415
- const placeholder = placeholderForType(type);
416
- expectedResult[key] = placeholder;
417
- const match = config?.expression?.match(/\{\{\s*([^}|]+)/);
418
- if (match) {
419
- let path = match[1].trim();
420
- if (path.startsWith('raw.')) {
421
- path = path.slice(4);
422
- }
423
- setNested(payloadTemplate, path.split('.'), placeholder);
424
- }
425
- }
426
- if (!parsed.testPayload) parsed.testPayload = {};
427
- parsed.testPayload.payloadTemplate = payloadTemplate;
428
- parsed.testPayload.expectedResult = expectedResult;
429
- changes.push('Generated testPayload.payloadTemplate and testPayload.expectedResult from attributes');
430
- return true;
431
- }
432
-
433
383
  /**
434
384
  * Runs all requested datasource repairs.
435
385
  *