@aifabrix/builder 2.42.1 → 2.44.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 (392) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/README.md +2 -2
  3. package/anchor-docs/README.md +10 -0
  4. package/anchor-docs/_TEMPLATE +24 -0
  5. package/bin/aifabrix.js +13 -4
  6. package/integration/hubspot-test/README.md +157 -0
  7. package/integration/{hubspot → hubspot-test}/application.json +6 -6
  8. package/integration/{hubspot → hubspot-test}/create-hubspot.js +10 -10
  9. package/integration/hubspot-test/env.template +4 -0
  10. package/integration/hubspot-test/hubspot-test-datasource-company.json +138 -0
  11. package/integration/hubspot-test/hubspot-test-datasource-contact.json +146 -0
  12. package/integration/hubspot-test/hubspot-test-datasource-deal.json +146 -0
  13. package/integration/hubspot-test/hubspot-test-datasource-users.json +76 -0
  14. package/integration/{hubspot/hubspot-deploy.json → hubspot-test/hubspot-test-deploy.json} +201 -24
  15. package/integration/{hubspot/hubspot-system.json → hubspot-test/hubspot-test-system.json} +8 -7
  16. package/integration/hubspot-test/rbac.json +166 -0
  17. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-credential-real.yaml +3 -3
  18. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-env-vars.yaml +2 -2
  19. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-add-datasource.yaml +1 -1
  20. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-create.yaml +1 -1
  21. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-select.yaml +1 -1
  22. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-known-platform.yaml +1 -1
  23. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
  24. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-mode.yaml +1 -1
  25. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-file.yaml +1 -1
  26. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-url.yaml +1 -1
  27. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-source.yaml +1 -1
  28. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-array-test.yaml +1 -1
  29. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
  30. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
  31. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-test.yaml +1 -1
  32. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-test.yaml +1 -1
  33. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +1 -1
  34. package/integration/{hubspot → hubspot-test}/test-dataplane-down-tests.js +1 -7
  35. package/integration/{hubspot → hubspot-test}/test-dataplane-down.js +3 -3
  36. package/integration/{hubspot → hubspot-test}/test.js +137 -102
  37. package/integration/{hubspot → hubspot-test}/wizard-hubspot-e2e.yaml +2 -2
  38. package/integration/{hubspot → hubspot-test}/wizard-hubspot-platform.yaml +1 -1
  39. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  40. package/integration/roundtrip-test-local/README.md +144 -0
  41. package/integration/roundtrip-test-local/application.yaml +13 -0
  42. package/integration/roundtrip-test-local/env.template +15 -0
  43. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  44. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  45. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  46. package/integration/roundtrip-test-local2/README.md +144 -0
  47. package/integration/roundtrip-test-local2/application.yaml +13 -0
  48. package/integration/roundtrip-test-local2/env.template +15 -0
  49. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  50. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  51. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  52. package/integration/test/wizard.yaml +8 -0
  53. package/jest.config.default.js +10 -0
  54. package/jest.config.integration.fixtures.js +22 -0
  55. package/jest.config.integration.js +21 -18
  56. package/jest.config.isolated.js +10 -0
  57. package/jest.projects.js +288 -0
  58. package/lib/api/datasources-core.api.js +3 -3
  59. package/lib/api/dev-mtls-request.js +110 -0
  60. package/lib/api/dev-server-https.js +145 -0
  61. package/lib/api/dev.api.js +133 -144
  62. package/lib/api/index.js +0 -1
  63. package/lib/api/pipeline.api.js +67 -20
  64. package/lib/api/service-users.api.js +111 -2
  65. package/lib/api/types/dev.types.js +4 -3
  66. package/lib/api/types/pipeline.types.js +8 -5
  67. package/lib/api/types/service-users.types.js +41 -0
  68. package/lib/api/types/validation-run.types.js +56 -0
  69. package/lib/api/validation-run.api.js +99 -0
  70. package/lib/api/validation-runner.js +99 -0
  71. package/lib/app/config.js +1 -1
  72. package/lib/app/deploy-status-display.js +2 -2
  73. package/lib/app/deploy.js +7 -6
  74. package/lib/app/display.js +2 -1
  75. package/lib/app/dockerfile.js +3 -2
  76. package/lib/app/down.js +2 -1
  77. package/lib/app/helpers.js +6 -5
  78. package/lib/app/index.js +27 -8
  79. package/lib/app/list.js +7 -6
  80. package/lib/app/push.js +4 -3
  81. package/lib/app/register.js +19 -8
  82. package/lib/app/rotate-secret.js +17 -13
  83. package/lib/app/run-container-start.js +184 -0
  84. package/lib/app/run-docker-fallback.js +108 -0
  85. package/lib/app/run-env-compose.js +30 -42
  86. package/lib/app/run-helpers.js +49 -126
  87. package/lib/app/run-infra-requirements.js +30 -0
  88. package/lib/app/run-resolve-image.js +21 -0
  89. package/lib/app/run.js +74 -21
  90. package/lib/app/show-display.js +1 -1
  91. package/lib/app/show.js +1 -1
  92. package/lib/build/index.js +13 -10
  93. package/lib/cli/index.js +2 -0
  94. package/lib/cli/setup-app.help.js +67 -0
  95. package/lib/cli/setup-app.js +59 -123
  96. package/lib/cli/setup-app.test-commands.js +179 -0
  97. package/lib/cli/setup-auth.js +36 -14
  98. package/lib/cli/setup-credential-deployment.js +22 -8
  99. package/lib/cli/setup-dev-path-commands.js +124 -0
  100. package/lib/cli/setup-dev.js +190 -103
  101. package/lib/cli/setup-environment.js +11 -20
  102. package/lib/cli/setup-external-system.js +62 -22
  103. package/lib/cli/setup-infra.js +139 -47
  104. package/lib/cli/setup-parameters.js +32 -0
  105. package/lib/cli/setup-secrets.js +147 -10
  106. package/lib/cli/setup-service-user.js +146 -20
  107. package/lib/cli/setup-utility.js +47 -19
  108. package/lib/commands/app-down.js +5 -7
  109. package/lib/commands/app-install.js +14 -7
  110. package/lib/commands/app-logs.js +13 -10
  111. package/lib/commands/app-shell.js +4 -1
  112. package/lib/commands/app-test.js +25 -19
  113. package/lib/commands/app.js +22 -10
  114. package/lib/commands/auth-config.js +10 -14
  115. package/lib/commands/auth-status.js +4 -3
  116. package/lib/commands/credential-env.js +4 -3
  117. package/lib/commands/credential-list.js +5 -4
  118. package/lib/commands/credential-push.js +4 -3
  119. package/lib/commands/datasource-unified-test-cli.js +495 -0
  120. package/lib/commands/datasource-unified-test-cli.options.js +149 -0
  121. package/lib/commands/datasource-validation-cli.js +129 -0
  122. package/lib/commands/datasource.js +123 -71
  123. package/lib/commands/deployment-list.js +6 -5
  124. package/lib/commands/dev-cli-handlers.js +122 -18
  125. package/lib/commands/dev-down.js +4 -3
  126. package/lib/commands/dev-init.js +231 -116
  127. package/lib/commands/dev-show-display.js +473 -0
  128. package/lib/commands/login-credentials.js +3 -2
  129. package/lib/commands/login-device.js +4 -3
  130. package/lib/commands/login.js +5 -4
  131. package/lib/commands/logout.js +8 -7
  132. package/lib/commands/parameters-validate.js +54 -0
  133. package/lib/commands/repair-datasource.js +314 -68
  134. package/lib/commands/repair-env-template.js +16 -10
  135. package/lib/commands/repair-rbac.js +25 -19
  136. package/lib/commands/repair.js +116 -32
  137. package/lib/commands/secrets-list.js +23 -12
  138. package/lib/commands/secrets-remove-all.js +220 -0
  139. package/lib/commands/secrets-remove.js +22 -13
  140. package/lib/commands/secrets-set.js +21 -12
  141. package/lib/commands/secrets-validate.js +20 -7
  142. package/lib/commands/secure.js +10 -9
  143. package/lib/commands/service-user.js +243 -13
  144. package/lib/commands/test-e2e-external.js +27 -1
  145. package/lib/commands/up-common.js +28 -2
  146. package/lib/commands/up-dataplane.js +31 -18
  147. package/lib/commands/up-miso.js +19 -29
  148. package/lib/commands/upload.js +138 -39
  149. package/lib/commands/wizard-core-helpers.js +1 -1
  150. package/lib/commands/wizard-dataplane.js +4 -3
  151. package/lib/commands/wizard-helpers.js +3 -3
  152. package/lib/commands/wizard.js +2 -2
  153. package/lib/core/admin-secrets.js +16 -5
  154. package/lib/core/audit-logger.js +12 -4
  155. package/lib/core/config-attach-extensions.js +46 -0
  156. package/lib/core/config-runtime-paths.js +29 -0
  157. package/lib/core/config.js +59 -58
  158. package/lib/core/diff.js +3 -2
  159. package/lib/core/ensure-encryption-key.js +2 -4
  160. package/lib/core/secrets-ensure-infra.js +77 -0
  161. package/lib/core/secrets-ensure.js +120 -64
  162. package/lib/core/secrets-env-write.js +35 -7
  163. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  164. package/lib/core/secrets.js +228 -42
  165. package/lib/core/templates-env.js +4 -3
  166. package/lib/core/templates.js +1 -1
  167. package/lib/datasource/abac-validator.js +148 -0
  168. package/lib/datasource/deploy.js +75 -53
  169. package/lib/datasource/field-reference-validator.js +77 -36
  170. package/lib/datasource/integration-context.js +63 -0
  171. package/lib/datasource/list.js +8 -7
  172. package/lib/datasource/log-viewer.js +252 -0
  173. package/lib/datasource/resolve-app.js +109 -0
  174. package/lib/datasource/test-e2e.js +95 -155
  175. package/lib/datasource/test-integration.js +121 -109
  176. package/lib/datasource/unified-validation-run-body.js +65 -0
  177. package/lib/datasource/unified-validation-run-post.js +23 -0
  178. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  179. package/lib/datasource/unified-validation-run.js +92 -0
  180. package/lib/datasource/validate.js +162 -15
  181. package/lib/deployment/deployer.js +4 -3
  182. package/lib/deployment/environment.js +7 -6
  183. package/lib/deployment/push.js +17 -8
  184. package/lib/external-system/delete.js +4 -3
  185. package/lib/external-system/deploy.js +131 -53
  186. package/lib/external-system/download-helpers.js +1 -1
  187. package/lib/external-system/download.js +7 -6
  188. package/lib/external-system/generator.js +104 -14
  189. package/lib/external-system/integration-test-dispatch.js +26 -0
  190. package/lib/external-system/test-execution.js +5 -1
  191. package/lib/external-system/test-helpers.js +0 -4
  192. package/lib/external-system/test-system-level-helpers.js +110 -0
  193. package/lib/external-system/test-system-level.js +83 -44
  194. package/lib/external-system/test.js +59 -8
  195. package/lib/generator/builders.js +23 -11
  196. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  197. package/lib/generator/external-controller-manifest.js +3 -3
  198. package/lib/generator/external.js +23 -11
  199. package/lib/generator/helpers.js +71 -12
  200. package/lib/generator/index.js +8 -4
  201. package/lib/generator/split-readme.js +12 -7
  202. package/lib/generator/split-variables.js +2 -1
  203. package/lib/generator/split.js +46 -11
  204. package/lib/generator/wizard-readme.js +3 -3
  205. package/lib/generator/wizard.js +16 -13
  206. package/lib/infrastructure/compose.js +60 -6
  207. package/lib/infrastructure/helpers.js +238 -51
  208. package/lib/infrastructure/index.js +64 -37
  209. package/lib/infrastructure/services.js +21 -15
  210. package/lib/internal/fs-real-sync.js +104 -0
  211. package/lib/internal/node-fs.js +98 -0
  212. package/lib/parameters/database-secret-values.js +173 -0
  213. package/lib/parameters/infra-kv-discovery.js +121 -0
  214. package/lib/parameters/infra-parameter-catalog.js +458 -0
  215. package/lib/parameters/infra-parameter-validate.js +64 -0
  216. package/lib/schema/application-schema.json +37 -17
  217. package/lib/schema/datasource-test-run.schema.json +493 -0
  218. package/lib/schema/deployment-rules.yaml +102 -63
  219. package/lib/schema/external-datasource.schema.json +1201 -433
  220. package/lib/schema/external-system.schema.json +181 -5
  221. package/lib/schema/flag-map-validation-run.json +31 -0
  222. package/lib/schema/infra-parameter.schema.json +106 -0
  223. package/lib/schema/infra.parameter.yaml +421 -0
  224. package/lib/schema/type/credential-auth-templates.json +40 -0
  225. package/lib/schema/type/document-storage.json +213 -0
  226. package/lib/schema/type/message-service.json +123 -0
  227. package/lib/schema/type/vector-store.json +88 -0
  228. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  229. package/lib/utils/api-error-handler.js +2 -2
  230. package/lib/utils/api.js +49 -14
  231. package/lib/utils/app-config-resolver.js +23 -1
  232. package/lib/utils/app-register-api.js +3 -2
  233. package/lib/utils/app-register-auth.js +1 -1
  234. package/lib/utils/app-register-config.js +4 -4
  235. package/lib/utils/app-register-display.js +3 -2
  236. package/lib/utils/app-register-validator.js +3 -2
  237. package/lib/utils/app-run-containers.js +26 -22
  238. package/lib/utils/app-scoped-config.js +31 -0
  239. package/lib/utils/app-service-env-from-builder.js +164 -0
  240. package/lib/utils/build-copy.js +1 -1
  241. package/lib/utils/build-helpers.js +20 -20
  242. package/lib/utils/build-resolve-image.js +165 -0
  243. package/lib/utils/cli-layout-chalk.js +8 -0
  244. package/lib/utils/cli-test-layout-chalk.js +267 -0
  245. package/lib/utils/cli-utils.js +88 -11
  246. package/lib/utils/compose-db-passwords.js +138 -0
  247. package/lib/utils/compose-generate-docker-compose.js +216 -0
  248. package/lib/utils/compose-generator.js +197 -291
  249. package/lib/utils/compose-miso-env.js +18 -0
  250. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  251. package/lib/utils/config-paths.js +209 -6
  252. package/lib/utils/config-scoped-resources-preference.js +41 -0
  253. package/lib/utils/controller-deployment-outcome.js +68 -0
  254. package/lib/utils/credential-display.js +2 -2
  255. package/lib/utils/credential-secrets-env.js +16 -1
  256. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  257. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  258. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  259. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  260. package/lib/utils/datasource-test-run-display.js +442 -0
  261. package/lib/utils/datasource-test-run-exit.js +58 -0
  262. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  263. package/lib/utils/datasource-test-run-report-version.js +51 -0
  264. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  265. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  266. package/lib/utils/datasource-validation-watch.js +266 -0
  267. package/lib/utils/declarative-url-ports.js +47 -0
  268. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  269. package/lib/utils/dev-ca-install.js +185 -23
  270. package/lib/utils/dev-cert-helper.js +266 -17
  271. package/lib/utils/dev-hosts-helper.js +307 -0
  272. package/lib/utils/dev-init-cert-hints.js +37 -0
  273. package/lib/utils/dev-init-health-messages.js +52 -0
  274. package/lib/utils/dev-init-resolve.js +86 -0
  275. package/lib/utils/dev-init-ssh-merge.js +65 -0
  276. package/lib/utils/dev-ssh-config-helper.js +196 -0
  277. package/lib/utils/dev-user-groups.js +93 -0
  278. package/lib/utils/docker-build.js +42 -17
  279. package/lib/utils/docker-exec.js +28 -0
  280. package/lib/utils/docker-manifest-public-port.js +116 -0
  281. package/lib/utils/docker-not-running-hint.js +52 -0
  282. package/lib/utils/docker.js +98 -11
  283. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  284. package/lib/utils/env-config-loader.js +10 -91
  285. package/lib/utils/env-copy.js +19 -10
  286. package/lib/utils/env-map.js +42 -11
  287. package/lib/utils/env-template.js +2 -2
  288. package/lib/utils/environment-scoped-resources.js +144 -0
  289. package/lib/utils/error-formatter.js +125 -9
  290. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  291. package/lib/utils/error-formatters/network-errors.js +2 -1
  292. package/lib/utils/error-formatters/permission-errors.js +2 -1
  293. package/lib/utils/error-formatters/validation-errors.js +2 -1
  294. package/lib/utils/external-env-template.js +180 -0
  295. package/lib/utils/external-readme.js +8 -1
  296. package/lib/utils/external-system-display.js +277 -136
  297. package/lib/utils/external-system-local-test-tty.js +389 -0
  298. package/lib/utils/external-system-readiness-core.js +377 -0
  299. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  300. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  301. package/lib/utils/external-system-readiness-display.js +186 -0
  302. package/lib/utils/external-system-test-helpers.js +24 -6
  303. package/lib/utils/external-system-validators.js +32 -14
  304. package/lib/utils/health-check-url.js +119 -0
  305. package/lib/utils/health-check.js +59 -25
  306. package/lib/utils/help-builder.js +14 -13
  307. package/lib/utils/image-version.js +4 -8
  308. package/lib/utils/infra-containers.js +4 -7
  309. package/lib/utils/infra-env-defaults.js +162 -0
  310. package/lib/utils/infra-status-display.js +167 -0
  311. package/lib/utils/infra-status.js +16 -8
  312. package/lib/utils/local-secrets.js +29 -7
  313. package/lib/utils/paths.js +136 -48
  314. package/lib/utils/port-resolver.js +10 -23
  315. package/lib/utils/redis-env-scope.js +62 -0
  316. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  317. package/lib/utils/remote-builder-validation.js +99 -0
  318. package/lib/utils/remote-dev-auth.js +117 -21
  319. package/lib/utils/remote-docker-env.js +67 -15
  320. package/lib/utils/remote-secrets-loader.js +13 -4
  321. package/lib/utils/resolve-docker-image-ref.js +124 -0
  322. package/lib/utils/schema-loader.js +22 -9
  323. package/lib/utils/secrets-bash-kv.js +25 -0
  324. package/lib/utils/secrets-generator.js +171 -51
  325. package/lib/utils/secrets-helpers.js +70 -59
  326. package/lib/utils/secrets-kv-scope.js +60 -0
  327. package/lib/utils/secrets-utils.js +35 -37
  328. package/lib/utils/secrets-validation.js +3 -1
  329. package/lib/utils/secrets-yaml-preserve.js +109 -0
  330. package/lib/utils/secure-file-permissions.js +91 -0
  331. package/lib/utils/ssh-key-helper.js +4 -2
  332. package/lib/utils/template-helpers.js +2 -2
  333. package/lib/utils/test-log-writer.js +3 -3
  334. package/lib/utils/token-manager.js +37 -5
  335. package/lib/utils/url-declarative-public-base.js +188 -0
  336. package/lib/utils/url-declarative-resolve-build.js +493 -0
  337. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  338. package/lib/utils/url-declarative-resolve.js +220 -0
  339. package/lib/utils/url-declarative-token-parse.js +74 -0
  340. package/lib/utils/url-declarative-url-flags.js +50 -0
  341. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  342. package/lib/utils/url-public-path-prefix.js +34 -0
  343. package/lib/utils/urls-local-registry.js +220 -0
  344. package/lib/utils/validation-report-tty-kit.js +77 -0
  345. package/lib/utils/validation-run-poll.js +89 -0
  346. package/lib/utils/validation-run-post-retry.js +73 -0
  347. package/lib/utils/validation-run-request.js +98 -0
  348. package/lib/utils/variable-transformer.js +21 -4
  349. package/lib/utils/yaml-preserve.js +78 -1
  350. package/lib/validation/datasource-warnings.js +56 -0
  351. package/lib/validation/env-template-auth.js +50 -2
  352. package/lib/validation/external-manifest-validator.js +35 -7
  353. package/lib/validation/validate-display.js +37 -31
  354. package/lib/validation/validate.js +9 -10
  355. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  356. package/lib/validation/validator.js +32 -78
  357. package/lib/validation/wizard-config-validator.js +2 -1
  358. package/package.json +11 -3
  359. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  360. package/scripts/diagnose-cli.js +150 -0
  361. package/scripts/install-local.js +304 -55
  362. package/templates/README.md +15 -2
  363. package/templates/applications/dataplane/application.yaml +52 -2
  364. package/templates/applications/dataplane/env.template +80 -18
  365. package/templates/applications/dataplane/rbac.yaml +8 -0
  366. package/templates/applications/keycloak/application.yaml +9 -1
  367. package/templates/applications/keycloak/env.template +15 -6
  368. package/templates/applications/miso-controller/application.yaml +10 -2
  369. package/templates/applications/miso-controller/env.template +55 -14
  370. package/templates/applications/miso-controller/rbac.yaml +5 -0
  371. package/templates/external-system/README.md.hbs +20 -7
  372. package/templates/external-system/deploy.js.hbs +5 -5
  373. package/templates/external-system/env.template.hbs +22 -0
  374. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  375. package/templates/infra/compose.yaml.hbs +20 -4
  376. package/templates/python/docker-compose.hbs +16 -0
  377. package/templates/typescript/docker-compose.hbs +16 -0
  378. package/integration/hubspot/README.md +0 -102
  379. package/integration/hubspot/env.template +0 -4
  380. package/integration/hubspot/hubspot-datasource-company.json +0 -541
  381. package/integration/hubspot/hubspot-datasource-contact.json +0 -639
  382. package/integration/hubspot/hubspot-datasource-deal.json +0 -588
  383. package/integration/hubspot/hubspot-datasource-users.json +0 -116
  384. package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
  385. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
  386. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
  387. package/lib/api/external-test.api.js +0 -111
  388. package/lib/schema/env-config.yaml +0 -43
  389. /package/integration/{hubspot → hubspot-test}/companies.json +0 -0
  390. /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-app-name.yaml +0 -0
  391. /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-missing-app.yaml +0 -0
  392. /package/integration/{hubspot → hubspot-test}/test-dataplane-down-helpers.js +0 -0
@@ -1,18 +1,17 @@
1
1
  /**
2
2
  * Datasource repair helpers: align dimensions, metadataSchema, exposed, sync, testPayload
3
- * with fieldMappings.attributes as source of truth.
3
+ * with fieldMappings.attributes as source of truth (external-datasource schema v2.4+).
4
4
  *
5
5
  * @fileoverview Repair datasource files for external integration
6
6
  * @author AI Fabrix Team
7
- * @version 2.0.0
7
+ * @version 2.2.0
8
8
  */
9
9
 
10
10
  'use strict';
11
11
 
12
12
  const DEFAULT_SYNC = {
13
13
  mode: 'pull',
14
- batchSize: 500,
15
- maxParallelRequests: 5
14
+ batchSize: 500
16
15
  };
17
16
 
18
17
  const MINIMAL_METADATA_SCHEMA = {
@@ -20,6 +19,61 @@ const MINIMAL_METADATA_SCHEMA = {
20
19
  additionalProperties: true
21
20
  };
22
21
 
22
+ /** Plan 346 / v2.4.1 closed root for testPayload */
23
+ const TEST_PAYLOAD_TOP_LEVEL_ALLOW = new Set([
24
+ 'mode',
25
+ 'primaryKey',
26
+ 'scenarios',
27
+ 'fk',
28
+ 'actors',
29
+ 'payloadTemplate',
30
+ 'expectedResult'
31
+ ]);
32
+
33
+ /**
34
+ * @param {string|undefined} entityType - entityType from datasource
35
+ * @returns {boolean}
36
+ */
37
+ function isNoneEntityType(entityType) {
38
+ return entityType === 'none';
39
+ }
40
+
41
+ /**
42
+ * @param {string|undefined} entityType - entityType from datasource
43
+ * @returns {boolean}
44
+ */
45
+ function isStorageEntityType(entityType) {
46
+ return entityType === 'recordStorage' || entityType === 'documentStorage';
47
+ }
48
+
49
+ /**
50
+ * Ensures metadataSchema.properties.externalId for recordStorage/documentStorage (schema v2.4).
51
+ * @param {Object} parsed - Parsed datasource (mutated)
52
+ * @param {string[]} changes - Change log
53
+ * @returns {boolean} True if externalId was added or corrected
54
+ */
55
+ function ensureStorageExternalIdMetadataProperty(parsed, changes) {
56
+ if (!isStorageEntityType(parsed?.entityType)) return false;
57
+ if (!parsed.metadataSchema || typeof parsed.metadataSchema !== 'object') return false;
58
+ if (!parsed.metadataSchema.properties || typeof parsed.metadataSchema.properties !== 'object') {
59
+ parsed.metadataSchema.properties = {};
60
+ }
61
+ const props = parsed.metadataSchema.properties;
62
+ const ext = props.externalId;
63
+ const ok = ext
64
+ && typeof ext === 'object'
65
+ && ext.type === 'string'
66
+ && ext.index === true;
67
+ if (ok) return false;
68
+ props.externalId = {
69
+ ...(typeof ext === 'object' && ext !== null ? ext : {}),
70
+ type: 'string',
71
+ index: true
72
+ };
73
+ changes.push('Ensured metadataSchema.properties.externalId (type string, index true) for storage entityType');
74
+ return true;
75
+ }
76
+
23
77
  /**
24
78
  * Returns the set of attribute keys from fieldMappings.attributes.
25
79
  * @param {Object} parsed - Parsed datasource object
@@ -32,12 +86,24 @@ function getAttributeKeys(parsed) {
32
86
  }
33
87
 
34
88
  /**
35
- * Extracts paths from attribute expressions (e.g. {{ metadata.email }}).
89
+ * @param {string} path - Normalized path (raw. prefix already stripped)
90
+ * @param {string[]} paths
91
+ * @param {Set<string>} topLevelKeys
92
+ * @param {Set<string>} referencedSchemaPropertyNames
93
+ */
94
+ function accumulatePathSegments(path, paths, topLevelKeys, referencedSchemaPropertyNames) {
95
+ paths.push(path);
96
+ const segments = path.split('.');
97
+ const first = segments[0];
98
+ if (first) topLevelKeys.add(first);
99
+ if (first === 'metadata' && segments.length >= 2 && segments[1]) {
100
+ referencedSchemaPropertyNames.add(segments[1]);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Extracts paths from attribute expressions (e.g. {{ metadata.email }}, {{ raw.id }}).
36
106
  * Skips record_ref: expressions.
37
- * - topLevelKeys: first segment of each path (e.g. "metadata" from "metadata.id").
38
- * - referencedSchemaPropertyNames: for paths like "metadata.xxx" or "metadata.xxx.yyy", the name "xxx"
39
- * (the first property under metadata). Used to prune metadataSchema.properties so we only keep
40
- * properties that are referenced; we do not compare against "metadata" or the schema would be wiped.
41
107
  *
42
108
  * @param {Object} attributes - fieldMappings.attributes object
43
109
  * @returns {{ paths: string[], topLevelKeys: Set<string>, referencedSchemaPropertyNames: Set<string> }}
@@ -46,111 +112,246 @@ function parsePathsFromExpressions(attributes) {
46
112
  const paths = [];
47
113
  const topLevelKeys = new Set();
48
114
  const referencedSchemaPropertyNames = new Set();
49
- if (!attributes || typeof attributes !== 'object') return { paths, topLevelKeys, referencedSchemaPropertyNames };
115
+ if (!attributes || typeof attributes !== 'object') {
116
+ return { paths, topLevelKeys, referencedSchemaPropertyNames };
117
+ }
50
118
  for (const attr of Object.values(attributes)) {
51
119
  const expr = attr?.expression;
52
120
  if (typeof expr !== 'string') continue;
53
121
  if (/^\s*record_ref:/i.test(expr.trim())) continue;
54
122
  const match = expr.match(/\{\{\s*([^}]+)\s*\}\}/);
55
123
  if (!match) continue;
56
- const path = match[1].trim().split('|')[0].trim();
57
- if (path) {
58
- paths.push(path);
59
- const segments = path.split('.');
60
- const first = segments[0];
61
- if (first) topLevelKeys.add(first);
62
- if (first === 'metadata' && segments.length >= 2 && segments[1]) {
63
- referencedSchemaPropertyNames.add(segments[1]);
64
- }
65
- }
124
+ let path = match[1].trim().split('|')[0].trim();
125
+ if (path.startsWith('raw.')) path = path.slice(4);
126
+ if (path) accumulatePathSegments(path, paths, topLevelKeys, referencedSchemaPropertyNames);
66
127
  }
67
128
  return { paths, topLevelKeys, referencedSchemaPropertyNames };
68
129
  }
69
130
 
70
131
  /**
71
- * Removes dimension entries whose value is metadata.<attr> and attr is not in fieldMappings.attributes.
72
- * Mutates parsed.fieldMappings.dimensions.
73
- *
132
+ * @param {Object} binding - dimension binding
133
+ * @param {string} dimKey - dimension key
134
+ * @param {string[]} changes - Change log
135
+ * @returns {boolean}
136
+ */
137
+ function removeOperatorWithoutActor(binding, dimKey, changes) {
138
+ if (binding.operator && !binding.actor) {
139
+ delete binding.operator;
140
+ changes.push(`Removed 'operator' from dimension '${dimKey}' (no actor)`);
141
+ return true;
142
+ }
143
+ return false;
144
+ }
145
+
146
+ /**
147
+ * @param {string} dimKey - dimension key
148
+ * @param {Object} binding - dimension binding
149
+ * @param {string[]} changes - Change log
150
+ * @returns {boolean}
151
+ */
152
+ function repairFkDimensionBinding(dimKey, binding, changes) {
153
+ let updated = false;
154
+ if (binding.field !== undefined) {
155
+ delete binding.field;
156
+ changes.push(`Removed invalid 'field' from FK dimension '${dimKey}'`);
157
+ updated = true;
158
+ }
159
+ return removeOperatorWithoutActor(binding, dimKey, changes) || updated;
160
+ }
161
+
162
+ /**
163
+ * @param {string} dimKey - dimension key
164
+ * @param {Object} binding - dimension binding
165
+ * @param {string[]} changes - Change log
166
+ * @returns {boolean}
167
+ */
168
+ function repairLocalDimensionBinding(dimKey, binding, changes) {
169
+ let updated = false;
170
+ if (binding.via !== undefined) {
171
+ delete binding.via;
172
+ changes.push(`Removed invalid 'via' from local dimension '${dimKey}'`);
173
+ updated = true;
174
+ }
175
+ return removeOperatorWithoutActor(binding, dimKey, changes) || updated;
176
+ }
177
+
178
+ /**
179
+ * Normalizes dimension bindings: FK vs local invariants; strip operator without actor.
180
+ * @param {Object} parsed - Parsed datasource (mutated)
181
+ * @param {string[]} changes - Change log
182
+ * @returns {boolean}
183
+ */
184
+ function repairDimensionBindingShape(parsed, changes) {
185
+ const dims = parsed?.dimensions;
186
+ if (!dims || typeof dims !== 'object') return false;
187
+ let updated = false;
188
+ for (const [dimKey, binding] of Object.entries(dims)) {
189
+ if (!binding || typeof binding !== 'object') continue;
190
+ const t = binding.type;
191
+ if (t === 'fk') {
192
+ updated = repairFkDimensionBinding(dimKey, binding, changes) || updated;
193
+ } else if (t === 'local' || t === undefined) {
194
+ updated = repairLocalDimensionBinding(dimKey, binding, changes) || updated;
195
+ } else {
196
+ updated = removeOperatorWithoutActor(binding, dimKey, changes) || updated;
197
+ }
198
+ }
199
+ return updated;
200
+ }
201
+
202
+ /**
203
+ * Removes root local dimension bindings whose field is not in fieldMappings.attributes.
204
+ * Skips FK bindings.
74
205
  * @param {Object} parsed - Parsed datasource (mutated)
75
- * @param {string[]} changes - Array to append change descriptions to
206
+ * @param {string[]} changes - Change log
76
207
  * @returns {boolean} True if any dimension was removed
77
208
  */
78
- function repairDimensionsFromAttributes(parsed, changes) {
79
- const dims = parsed?.fieldMappings?.dimensions;
209
+ function repairRootDimensionsFromAttributes(parsed, changes) {
210
+ const dims = parsed?.dimensions;
80
211
  if (!dims || typeof dims !== 'object') return false;
81
212
  const attributeKeys = getAttributeKeys(parsed);
82
213
  let updated = false;
83
- for (const [dimKey, value] of Object.entries(dims)) {
84
- if (typeof value !== 'string') continue;
85
- if (!value.startsWith('metadata.')) continue;
86
- const attr = value.slice('metadata.'.length).trim();
87
- if (!attr || attributeKeys.has(attr)) continue;
214
+ for (const [dimKey, binding] of Object.entries(dims)) {
215
+ if (!binding || typeof binding !== 'object') continue;
216
+ if (binding.type === 'fk') continue;
217
+ if (binding.type !== undefined && binding.type !== 'local') continue;
218
+ const field = binding.field;
219
+ if (typeof field !== 'string') continue;
220
+ if (attributeKeys.has(field)) continue;
88
221
  delete dims[dimKey];
89
- changes.push(`Removed dimension '${dimKey}': ${value} not in fieldMappings.attributes`);
222
+ changes.push(`Removed root dimension '${dimKey}': field '${field}' not in fieldMappings.attributes`);
90
223
  updated = true;
91
224
  }
92
225
  return updated;
93
226
  }
94
227
 
95
228
  /**
96
- * Ensures metadataSchema exists (minimal stub if missing). If present, prunes top-level
97
- * properties not referenced by any attribute expression. Uses the first property name under
98
- * "metadata" in paths (e.g. metadata.id "id") so we do not remove schema properties that
99
- * are referenced. If no metadata.xxx paths exist, we do not prune (keep all properties).
229
+ * Adds metadataSchema property stubs for metadata.* paths referenced in attributes.
230
+ * @param {Object} parsed - Parsed datasource (mutated)
231
+ * @param {Set<string>} referencedSchemaPropertyNames - Property names under metadata
232
+ * @param {string[]} changes - Change log
233
+ * @returns {boolean}
234
+ */
235
+ function ensureMetadataSchemaPropertyStubs(parsed, referencedSchemaPropertyNames, changes) {
236
+ if (!referencedSchemaPropertyNames || referencedSchemaPropertyNames.size === 0) return false;
237
+ if (!parsed.metadataSchema || typeof parsed.metadataSchema !== 'object') return false;
238
+ if (!parsed.metadataSchema.properties || typeof parsed.metadataSchema.properties !== 'object') {
239
+ parsed.metadataSchema.properties = {};
240
+ }
241
+ const props = parsed.metadataSchema.properties;
242
+ const added = [];
243
+ for (const name of referencedSchemaPropertyNames) {
244
+ if (!name || props[name] !== undefined) continue;
245
+ props[name] = { type: 'string' };
246
+ added.push(name);
247
+ }
248
+ if (added.length === 0) return false;
249
+ changes.push(`Added metadataSchema.properties stubs for [${added.join(', ')}]`);
250
+ return true;
251
+ }
252
+
253
+ /**
254
+ * @param {Object} parsed - Parsed datasource (mutated)
255
+ * @param {Set<string>} referencedSchemaPropertyNames - metadata.* names from expressions
256
+ * @param {string[]} changes - Change log
257
+ * @returns {boolean}
258
+ */
259
+ function pruneMetadataSchemaPropertiesByAttributeRefs(parsed, referencedSchemaPropertyNames, changes) {
260
+ const props = parsed.metadataSchema?.properties;
261
+ if (
262
+ !props || typeof props !== 'object'
263
+ || referencedSchemaPropertyNames.size === 0
264
+ ) {
265
+ return false;
266
+ }
267
+ const toRemove = Object.keys(props).filter(k => {
268
+ if (referencedSchemaPropertyNames.has(k)) return false;
269
+ if (isStorageEntityType(parsed.entityType) && k === 'externalId') return false;
270
+ return true;
271
+ });
272
+ if (toRemove.length === 0) return false;
273
+ toRemove.forEach(k => delete props[k]);
274
+ changes.push(`Pruned metadataSchema.properties: removed [${toRemove.join(', ')}] (not referenced by attributes)`);
275
+ return true;
276
+ }
277
+
278
+ /**
279
+ * Ensures metadataSchema exists (minimal stub if missing). Prunes top-level properties not
280
+ * referenced by metadata.* paths in expressions (after stripping raw. prefix).
281
+ * Skipped entirely for entityType 'none'.
100
282
  *
101
283
  * @param {Object} parsed - Parsed datasource (mutated)
102
- * @param {string[]} changes - Array to append change descriptions to
284
+ * @param {string[]} changes - Change log
103
285
  * @returns {boolean} True if schema was added or pruned
104
286
  */
105
287
  function repairMetadataSchemaFromAttributes(parsed, changes) {
288
+ if (isNoneEntityType(parsed?.entityType)) {
289
+ return false;
290
+ }
106
291
  const { referencedSchemaPropertyNames } = parsePathsFromExpressions(parsed?.fieldMappings?.attributes ?? {});
292
+ let updated = false;
293
+
107
294
  if (!parsed.metadataSchema || typeof parsed.metadataSchema !== 'object') {
108
- parsed.metadataSchema = { ...MINIMAL_METADATA_SCHEMA };
295
+ parsed.metadataSchema = { ...MINIMAL_METADATA_SCHEMA, properties: {} };
109
296
  changes.push('Added minimal metadataSchema (was missing)');
110
- return true;
297
+ ensureMetadataSchemaPropertyStubs(parsed, referencedSchemaPropertyNames, changes);
298
+ updated = true;
299
+ } else {
300
+ if (pruneMetadataSchemaPropertiesByAttributeRefs(parsed, referencedSchemaPropertyNames, changes)) {
301
+ updated = true;
302
+ }
303
+ if (ensureMetadataSchemaPropertyStubs(parsed, referencedSchemaPropertyNames, changes)) {
304
+ updated = true;
305
+ }
111
306
  }
112
- const props = parsed.metadataSchema.properties;
113
- if (!props || typeof props !== 'object') return false;
114
- if (referencedSchemaPropertyNames.size === 0) return false;
115
- const toRemove = Object.keys(props).filter(k => !referencedSchemaPropertyNames.has(k));
116
- if (toRemove.length === 0) return false;
117
- toRemove.forEach(k => delete props[k]);
118
- changes.push(`Pruned metadataSchema.properties: removed [${toRemove.join(', ')}] (not referenced by attributes)`);
119
- return true;
307
+ if (ensureStorageExternalIdMetadataProperty(parsed, changes)) {
308
+ updated = true;
309
+ }
310
+ return updated;
120
311
  }
121
312
 
122
313
  /**
123
- * Sets exposed.attributes to the list of fieldMappings.attributes keys (sorted).
124
- * Only when options.expose is true; caller should gate.
314
+ * Sets exposed.schema from fieldMappings.attributes keys (metadata.<key> leaves). v2.4 canonical.
315
+ * Removes deprecated exposed.attributes when present so output matches schema.
125
316
  *
126
317
  * @param {Object} parsed - Parsed datasource (mutated)
127
- * @param {string[]} changes - Array to append change descriptions to
318
+ * @param {string[]} changes - Change log
128
319
  * @returns {boolean} True if exposed was updated
129
320
  */
130
321
  function repairExposeFromAttributes(parsed, changes) {
131
322
  const keys = Array.from(getAttributeKeys(parsed)).filter(Boolean).sort();
132
323
  if (keys.length === 0) return false;
133
324
  if (!parsed.exposed) parsed.exposed = {};
134
- const prev = parsed.exposed.attributes;
135
- const same = Array.isArray(prev) && prev.length === keys.length && prev.every((v, i) => v === keys[i]);
136
- if (same) return false;
137
- parsed.exposed.attributes = keys;
138
- changes.push(`Set exposed.attributes to [${keys.join(', ')}]`);
325
+ const schema = {};
326
+ keys.forEach(k => {
327
+ schema[k] = `metadata.${k}`;
328
+ });
329
+ const prev = parsed.exposed.schema;
330
+ const same = prev && typeof prev === 'object'
331
+ && keys.length === Object.keys(prev).length
332
+ && keys.every(k => prev[k] === schema[k]);
333
+ if (same && parsed.exposed.attributes === undefined) return false;
334
+ parsed.exposed.schema = schema;
335
+ if (parsed.exposed.attributes !== undefined) {
336
+ delete parsed.exposed.attributes;
337
+ }
338
+ changes.push(`Set exposed.schema for [${keys.join(', ')}]`);
139
339
  return true;
140
340
  }
141
341
 
142
342
  /**
143
- * Adds default sync section if missing or empty.
343
+ * Adds default sync section if missing or empty. Not applied for entityType 'none'.
144
344
  *
145
345
  * @param {Object} parsed - Parsed datasource (mutated)
146
- * @param {string[]} changes - Array to append change descriptions to
346
+ * @param {string[]} changes - Change log
147
347
  * @returns {boolean} True if sync was added
148
348
  */
149
349
  function repairSyncSection(parsed, changes) {
350
+ if (isNoneEntityType(parsed?.entityType)) return false;
150
351
  const sync = parsed.sync;
151
352
  if (sync && typeof sync === 'object' && Object.keys(sync).length > 0) return false;
152
353
  parsed.sync = { ...DEFAULT_SYNC };
153
- changes.push('Added default sync section (mode: pull, batchSize: 500, maxParallelRequests: 5)');
354
+ changes.push('Added default sync section (mode: pull, batchSize: 500)');
154
355
  return true;
155
356
  }
156
357
 
@@ -173,11 +374,32 @@ function setNested(obj, pathParts, value) {
173
374
  if (last) cur[last] = value;
174
375
  }
175
376
 
377
+ /**
378
+ * Removes unknown top-level keys from testPayload (v2.4.1 closed root).
379
+ * @param {Object} parsed - Parsed datasource (mutated)
380
+ * @param {string[]} changes - Change log
381
+ * @returns {boolean}
382
+ */
383
+ function sanitizeTestPayloadTopLevel(parsed, changes) {
384
+ const tp = parsed?.testPayload;
385
+ if (!tp || typeof tp !== 'object' || Array.isArray(tp)) return false;
386
+ const removed = [];
387
+ for (const key of Object.keys(tp)) {
388
+ if (!TEST_PAYLOAD_TOP_LEVEL_ALLOW.has(key)) {
389
+ delete tp[key];
390
+ removed.push(key);
391
+ }
392
+ }
393
+ if (removed.length === 0) return false;
394
+ changes.push(`Removed unknown testPayload keys: [${removed.join(', ')}]`);
395
+ return true;
396
+ }
397
+
176
398
  /**
177
399
  * Builds minimal payloadTemplate and expectedResult from attribute expression paths.
178
400
  *
179
401
  * @param {Object} parsed - Parsed datasource (mutated)
180
- * @param {string[]} changes - Array to append change descriptions to
402
+ * @param {string[]} changes - Change log
181
403
  * @returns {boolean} True if testPayload was added or updated
182
404
  */
183
405
  function repairTestPayload(parsed, changes) {
@@ -191,7 +413,10 @@ function repairTestPayload(parsed, changes) {
191
413
  expectedResult[key] = placeholder;
192
414
  const match = config?.expression?.match(/\{\{\s*([^}|]+)/);
193
415
  if (match) {
194
- const path = match[1].trim();
416
+ let path = match[1].trim();
417
+ if (path.startsWith('raw.')) {
418
+ path = path.slice(4);
419
+ }
195
420
  setNested(payloadTemplate, path.split('.'), placeholder);
196
421
  }
197
422
  }
@@ -203,7 +428,7 @@ function repairTestPayload(parsed, changes) {
203
428
  }
204
429
 
205
430
  /**
206
- * Runs all requested datasource repairs. Core: dimensions + metadataSchema. Optional: expose, sync, testPayload.
431
+ * Runs all requested datasource repairs.
207
432
  *
208
433
  * @param {Object} parsed - Parsed datasource object (mutated)
209
434
  * @param {Object} options - { expose?: boolean, sync?: boolean, test?: boolean }
@@ -213,23 +438,44 @@ function repairTestPayload(parsed, changes) {
213
438
  function repairDatasourceFile(parsed, options = {}, changes = []) {
214
439
  const out = Array.isArray(changes) ? changes : [];
215
440
  let updated = false;
216
- updated = repairDimensionsFromAttributes(parsed, out) || updated;
217
- updated = repairMetadataSchemaFromAttributes(parsed, out) || updated;
218
- if (options.expose) updated = repairExposeFromAttributes(parsed, out) || updated;
219
- if (options.sync) updated = repairSyncSection(parsed, out) || updated;
220
- if (options.test) updated = repairTestPayload(parsed, out) || updated;
441
+ const none = isNoneEntityType(parsed?.entityType);
442
+
443
+ if (!none) {
444
+ updated = repairDimensionBindingShape(parsed, out) || updated;
445
+ updated = repairRootDimensionsFromAttributes(parsed, out) || updated;
446
+ updated = repairMetadataSchemaFromAttributes(parsed, out) || updated;
447
+ }
448
+
449
+ if (options.expose) {
450
+ updated = repairExposeFromAttributes(parsed, out) || updated;
451
+ }
452
+
453
+ if (!none && options.sync) {
454
+ updated = repairSyncSection(parsed, out) || updated;
455
+ }
456
+
457
+ if (options.test) {
458
+ sanitizeTestPayloadTopLevel(parsed, out);
459
+ updated = repairTestPayload(parsed, out) || updated;
460
+ updated = sanitizeTestPayloadTopLevel(parsed, out) || updated;
461
+ }
462
+
221
463
  return { updated, changes: out };
222
464
  }
223
465
 
224
466
  module.exports = {
225
467
  getAttributeKeys,
226
468
  parsePathsFromExpressions,
227
- repairDimensionsFromAttributes,
469
+ repairDimensionBindingShape,
470
+ repairRootDimensionsFromAttributes,
228
471
  repairMetadataSchemaFromAttributes,
229
472
  repairExposeFromAttributes,
230
473
  repairSyncSection,
231
474
  repairTestPayload,
475
+ sanitizeTestPayloadTopLevel,
232
476
  repairDatasourceFile,
233
477
  DEFAULT_SYNC,
234
- MINIMAL_METADATA_SCHEMA
478
+ MINIMAL_METADATA_SCHEMA,
479
+ TEST_PAYLOAD_TOP_LEVEL_ALLOW,
480
+ isNoneEntityType
235
481
  };
@@ -11,10 +11,11 @@ const path = require('path');
11
11
  const fs = require('fs');
12
12
  const { systemKeyToKvPrefix, kvEnvKeyToPath, securityKeyToVar } = require('../utils/credential-secrets-env');
13
13
  const { extractEnvTemplate } = require('../generator/split');
14
+ const { generateExternalEnvTemplateContent } = require('../utils/external-env-template');
14
15
 
15
16
  /**
16
17
  * Normalizes a keyvault config entry to canonical KV_* name and path-style value.
17
- * Path format: kv://<system-key>/<variable> (e.g. kv://microsoft-teams/clientId).
18
+ * Path format: kv://<systemKey>/<variable> (e.g. kv://microsoft-teams/clientId).
18
19
  * @param {Object} entry - Config entry with name, value, location
19
20
  * @param {string} prefix - KV prefix (e.g. MICROSOFT_TEAMS)
20
21
  * @param {string} systemKey - System key (e.g. microsoft-teams) for path namespace
@@ -65,7 +66,7 @@ function addFromConfiguration(effective, config, prefix, seenNames, systemKey) {
65
66
 
66
67
  /**
67
68
  * Adds effective config entries from authentication.security.
68
- * Path format: kv://<system-key>/<variable> (e.g. kv://microsoft-teams/clientId).
69
+ * Path format: kv://<systemKey>/<variable> (e.g. kv://microsoft-teams/clientId).
69
70
  * @param {Array} effective - Mutable result array
70
71
  * @param {Object} systemParsed - Parsed system config
71
72
  * @param {string} prefix - KV prefix
@@ -126,20 +127,25 @@ function buildExpectedByKey(effective) {
126
127
  }
127
128
 
128
129
  /**
129
- * Creates env.template when missing. Returns true if created (and change pushed).
130
+ * Creates env.template when missing using the Handlebars template (Authentication + Configuration sections).
130
131
  * @param {string} envPath - Path to env.template
131
- * @param {Map<string, string>} expectedByKey - Expected key->line map
132
+ * @param {Map<string, string>} expectedByKey - Expected key->line map (fallback when no systemParsed)
132
133
  * @param {boolean} dryRun - If true, do not write
133
134
  * @param {string[]} changes - Array to append to
135
+ * @param {Object} [systemParsed] - Parsed system config for template context (preferred)
134
136
  * @returns {boolean}
135
137
  */
136
- function createEnvTemplateIfMissing(envPath, expectedByKey, dryRun, changes) {
138
+ function createEnvTemplateIfMissing(envPath, expectedByKey, dryRun, changes, systemParsed) {
137
139
  if (fs.existsSync(envPath)) return false;
138
- const lines = Array.from(expectedByKey.values());
139
- const content = lines.join('\n');
140
- if (!content) return false;
140
+ if (expectedByKey.size === 0) return false;
141
+ const content = systemParsed
142
+ ? generateExternalEnvTemplateContent(systemParsed)
143
+ : Array.from(expectedByKey.values()).join('\n');
144
+ if (!content || !content.trim()) return false;
145
+ const hasKeyValueLine = /^[A-Z_][A-Z0-9_]*=/m.test(content);
146
+ if (!hasKeyValueLine) return false;
141
147
  if (!dryRun) {
142
- fs.writeFileSync(envPath, content + '\n', { mode: 0o644, encoding: 'utf8' });
148
+ fs.writeFileSync(envPath, content + (content.endsWith('\n') ? '' : '\n'), { mode: 0o644, encoding: 'utf8' });
143
149
  }
144
150
  changes.push('Created env.template from system configuration');
145
151
  return true;
@@ -236,7 +242,7 @@ function repairEnvTemplate(appPath, systemParsed, systemKey, dryRun, changes) {
236
242
  const expectedByKey = buildExpectedByKey(effective);
237
243
  const envPath = path.join(appPath, 'env.template');
238
244
 
239
- if (createEnvTemplateIfMissing(envPath, expectedByKey, dryRun, changes)) {
245
+ if (createEnvTemplateIfMissing(envPath, expectedByKey, dryRun, changes, systemParsed)) {
240
246
  return true;
241
247
  }
242
248
  if (!fs.existsSync(envPath)) {
@@ -10,10 +10,10 @@
10
10
 
11
11
  const path = require('path');
12
12
  const fs = require('fs');
13
- const yaml = require('js-yaml');
14
13
  const chalk = require('chalk');
15
14
  const logger = require('../utils/logger');
16
- const { loadConfigFile } = require('../utils/config-format');
15
+ const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
16
+ const { resolveRbacPath } = require('../utils/app-config-resolver');
17
17
 
18
18
  const DEFAULT_CAPABILITIES = ['list', 'get', 'create', 'update', 'delete'];
19
19
 
@@ -55,26 +55,30 @@ function collectPermissionNames(appPath, datasourceFiles) {
55
55
  }
56
56
 
57
57
  /**
58
- * Loads existing rbac or creates empty structure. Uses extractRbacFromSystem when provided.
58
+ * Loads existing RBAC file (rbac.yaml, rbac.yml, or rbac.json) or creates empty structure.
59
+ * Uses extractRbacFromSystem when no file exists. New file path respects format (rbac.json when format is 'json').
60
+ *
59
61
  * @param {string} appPath - Application path
60
62
  * @param {Object} [systemParsed] - Parsed system for extractRbacFromSystem
61
63
  * @param {Function} extractRbacFromSystem - (system) => rbac or null
62
- * @returns {{ rbac: Object, rbacPath: string, rbacYmlPath: string }}
64
+ * @param {string} [format] - 'json' or 'yaml'; used only when creating a new file (default 'yaml')
65
+ * @returns {{ rbac: Object, rbacPath: string }} rbacPath is resolved path or path.join(appPath, 'rbac.{json|yaml}') for new file
63
66
  */
64
- function loadOrCreateRbac(appPath, systemParsed, extractRbacFromSystem) {
65
- const rbacPath = path.join(appPath, 'rbac.yaml');
66
- const rbacYmlPath = path.join(appPath, 'rbac.yml');
67
+ function loadOrCreateRbac(appPath, systemParsed, extractRbacFromSystem, format) {
68
+ const resolvedPath = resolveRbacPath(appPath);
67
69
  let rbac;
68
- if (fs.existsSync(rbacPath)) {
69
- rbac = loadConfigFile(rbacPath);
70
- } else if (fs.existsSync(rbacYmlPath)) {
71
- rbac = loadConfigFile(rbacYmlPath);
70
+ let rbacPath;
71
+ if (resolvedPath) {
72
+ rbac = loadConfigFile(resolvedPath);
73
+ rbacPath = resolvedPath;
72
74
  } else {
73
75
  rbac = extractRbacFromSystem(systemParsed) || { roles: [], permissions: [] };
74
76
  if (!Array.isArray(rbac.roles)) rbac.roles = [];
75
77
  if (!Array.isArray(rbac.permissions)) rbac.permissions = [];
78
+ const ext = (format === 'json') ? 'rbac.json' : 'rbac.yaml';
79
+ rbacPath = path.join(appPath, ext);
76
80
  }
77
- return { rbac, rbacPath, rbacYmlPath };
81
+ return { rbac, rbacPath };
78
82
  }
79
83
 
80
84
  /**
@@ -123,31 +127,33 @@ function ensureDefaultRoles(rbac, systemKey, displayName, changes) {
123
127
  if (p.name && listGetPerms.includes(p.name) && !p.roles.includes(readerValue)) p.roles.push(readerValue);
124
128
  if (!p.roles.includes(adminValue)) p.roles.push(adminValue);
125
129
  }
126
- changes.push('Added default Admin and Reader roles to rbac.yaml');
130
+ changes.push('Added default Admin and Reader roles to rbac file');
127
131
  return true;
128
132
  }
129
133
 
130
134
  /**
131
135
  * Merges RBAC from datasources: ensures permission per resourceType:capability, adds Admin/Reader roles if none.
136
+ * When creating a new RBAC file, uses rbac.json if format is 'json', otherwise rbac.yaml.
137
+ *
132
138
  * @param {string} appPath - Application path
133
139
  * @param {Object} systemParsed - Parsed system (key, displayName)
134
140
  * @param {string[]} datasourceFiles - Datasource file names
135
141
  * @param {Function} extractRbacFromSystem - (system) => rbac or null
136
- * @param {boolean} dryRun - If true, do not write
137
- * @param {string[]} changes - Array to append change descriptions to
142
+ * @param {{ format?: string, dryRun: boolean, changes: string[] }} options - format ('json'|'yaml'), dryRun, changes array
138
143
  * @returns {boolean} True if rbac was updated (or would be in dry-run)
139
144
  */
140
- function mergeRbacFromDatasources(appPath, systemParsed, datasourceFiles, extractRbacFromSystem, dryRun, changes) {
145
+ function mergeRbacFromDatasources(appPath, systemParsed, datasourceFiles, extractRbacFromSystem, options) {
146
+ const { format = 'yaml', dryRun, changes } = options;
147
+ const rbacFormat = format === 'json' ? 'json' : 'yaml';
141
148
  const permissionNames = collectPermissionNames(appPath, datasourceFiles);
142
149
  if (permissionNames.size === 0) return false;
143
150
  const systemKey = systemParsed?.key || 'system';
144
151
  const displayName = systemParsed?.displayName || systemKey;
145
- const { rbac, rbacPath, rbacYmlPath } = loadOrCreateRbac(appPath, systemParsed, extractRbacFromSystem);
152
+ const { rbac, rbacPath } = loadOrCreateRbac(appPath, systemParsed, extractRbacFromSystem, rbacFormat);
146
153
  let updated = addMissingPermissions(rbac, permissionNames, changes);
147
154
  updated = ensureDefaultRoles(rbac, systemKey, displayName, changes) || updated;
148
155
  if (updated && !dryRun) {
149
- const outPath = fs.existsSync(rbacPath) ? rbacPath : (fs.existsSync(rbacYmlPath) ? rbacYmlPath : rbacPath);
150
- fs.writeFileSync(outPath, yaml.dump(rbac, { indent: 2, lineWidth: -1 }), { mode: 0o644, encoding: 'utf8' });
156
+ writeConfigFile(rbacPath, rbac);
151
157
  }
152
158
  return updated;
153
159
  }