@aifabrix/builder 2.43.0 → 2.44.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 (371) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/.cursor/rules/cli-layout.mdc +75 -0
  3. package/.cursor/rules/project-rules.mdc +8 -0
  4. package/.npmrc.token +1 -0
  5. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +1 -0
  6. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +1 -0
  7. package/.nyc_output/processinfo/index.json +1 -0
  8. package/README.md +1 -1
  9. package/anchor-docs/README.md +10 -0
  10. package/anchor-docs/_TEMPLATE +24 -0
  11. package/bin/aifabrix.js +13 -4
  12. package/integration/hubspot-test/README.md +31 -0
  13. package/integration/hubspot-test/create-hubspot.js +5 -5
  14. package/integration/hubspot-test/hubspot-test-datasource-company.json +58 -462
  15. package/integration/hubspot-test/hubspot-test-datasource-contact.json +61 -555
  16. package/integration/hubspot-test/hubspot-test-datasource-deal.json +63 -506
  17. package/integration/hubspot-test/hubspot-test-datasource-users.json +42 -83
  18. package/integration/hubspot-test/hubspot-test-deploy.json +3 -3
  19. package/integration/hubspot-test/test-dataplane-down-tests.js +1 -7
  20. package/integration/hubspot-test/test-dataplane-down.js +3 -3
  21. package/integration/hubspot-test/test.js +35 -43
  22. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  23. package/integration/roundtrip-test-local/README.md +144 -0
  24. package/integration/roundtrip-test-local/application.yaml +13 -0
  25. package/integration/roundtrip-test-local/env.template +15 -0
  26. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  27. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  28. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  29. package/integration/roundtrip-test-local2/README.md +144 -0
  30. package/integration/roundtrip-test-local2/application.yaml +13 -0
  31. package/integration/roundtrip-test-local2/env.template +15 -0
  32. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  33. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  34. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  35. package/integration/test/wizard.yaml +8 -0
  36. package/jest.config.default.js +10 -0
  37. package/jest.config.integration.fixtures.js +22 -0
  38. package/jest.config.integration.js +21 -18
  39. package/jest.config.isolated.js +10 -0
  40. package/jest.projects.js +301 -0
  41. package/lib/api/certificates.api.js +62 -0
  42. package/lib/api/datasources-core.api.js +3 -3
  43. package/lib/api/dev-mtls-request.js +110 -0
  44. package/lib/api/dev-server-https.js +145 -0
  45. package/lib/api/dev.api.js +133 -144
  46. package/lib/api/index.js +11 -3
  47. package/lib/api/pipeline.api.js +67 -20
  48. package/lib/api/types/certificates.types.js +48 -0
  49. package/lib/api/types/dev.types.js +4 -3
  50. package/lib/api/types/pipeline.types.js +8 -5
  51. package/lib/api/types/validation-run.types.js +56 -0
  52. package/lib/api/validation-run.api.js +111 -0
  53. package/lib/api/validation-runner.js +109 -0
  54. package/lib/app/certification-show-enrich.js +129 -0
  55. package/lib/app/certification-verify-rows.js +60 -0
  56. package/lib/app/config.js +1 -1
  57. package/lib/app/deploy-status-display.js +2 -2
  58. package/lib/app/deploy.js +7 -6
  59. package/lib/app/display.js +2 -1
  60. package/lib/app/dockerfile.js +3 -2
  61. package/lib/app/down.js +2 -1
  62. package/lib/app/helpers.js +6 -5
  63. package/lib/app/index.js +27 -8
  64. package/lib/app/list.js +7 -6
  65. package/lib/app/push.js +4 -3
  66. package/lib/app/register.js +16 -7
  67. package/lib/app/rotate-secret.js +14 -13
  68. package/lib/app/run-container-start.js +184 -0
  69. package/lib/app/run-docker-fallback.js +108 -0
  70. package/lib/app/run-env-compose.js +30 -42
  71. package/lib/app/run-helpers.js +49 -126
  72. package/lib/app/run-infra-requirements.js +30 -0
  73. package/lib/app/run-resolve-image.js +21 -0
  74. package/lib/app/run.js +74 -21
  75. package/lib/app/show-display.js +44 -1
  76. package/lib/app/show.js +93 -9
  77. package/lib/build/index.js +13 -10
  78. package/lib/certification/cli-cert-sync-skip.js +21 -0
  79. package/lib/certification/merge-certification-from-artifact.js +185 -0
  80. package/lib/certification/post-unified-cert-sync.js +33 -0
  81. package/lib/certification/sync-after-external-command.js +52 -0
  82. package/lib/certification/sync-system-certification.js +197 -0
  83. package/lib/cli/index.js +2 -0
  84. package/lib/cli/setup-app.help.js +67 -0
  85. package/lib/cli/setup-app.js +61 -121
  86. package/lib/cli/setup-app.test-commands.js +195 -0
  87. package/lib/cli/setup-auth.js +19 -5
  88. package/lib/cli/setup-credential-deployment.js +22 -8
  89. package/lib/cli/setup-dev-path-commands.js +124 -0
  90. package/lib/cli/setup-dev.js +170 -113
  91. package/lib/cli/setup-environment.js +7 -1
  92. package/lib/cli/setup-external-system.js +84 -23
  93. package/lib/cli/setup-infra.js +126 -47
  94. package/lib/cli/setup-parameters.js +32 -0
  95. package/lib/cli/setup-secrets.js +137 -18
  96. package/lib/cli/setup-service-user.js +1 -1
  97. package/lib/cli/setup-utility.js +54 -22
  98. package/lib/commands/app-down.js +5 -7
  99. package/lib/commands/app-install.js +14 -7
  100. package/lib/commands/app-logs.js +13 -10
  101. package/lib/commands/app-shell.js +4 -1
  102. package/lib/commands/app-test.js +25 -19
  103. package/lib/commands/app.js +32 -11
  104. package/lib/commands/auth-config.js +6 -6
  105. package/lib/commands/auth-status.js +4 -3
  106. package/lib/commands/credential-env.js +4 -3
  107. package/lib/commands/credential-list.js +5 -4
  108. package/lib/commands/credential-push.js +4 -3
  109. package/lib/commands/datasource-unified-test-cli.js +428 -0
  110. package/lib/commands/datasource-unified-test-cli.options.js +191 -0
  111. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +106 -0
  112. package/lib/commands/datasource-validation-cli.js +143 -0
  113. package/lib/commands/datasource.js +125 -95
  114. package/lib/commands/deployment-list.js +6 -5
  115. package/lib/commands/dev-cli-handlers.js +122 -18
  116. package/lib/commands/dev-down.js +4 -3
  117. package/lib/commands/dev-init.js +231 -116
  118. package/lib/commands/dev-show-display.js +473 -0
  119. package/lib/commands/login-credentials.js +3 -2
  120. package/lib/commands/login-device.js +4 -3
  121. package/lib/commands/login.js +5 -4
  122. package/lib/commands/logout.js +8 -7
  123. package/lib/commands/parameters-validate.js +54 -0
  124. package/lib/commands/repair-datasource.js +314 -68
  125. package/lib/commands/repair-env-template.js +2 -2
  126. package/lib/commands/repair.js +21 -3
  127. package/lib/commands/secrets-list.js +23 -12
  128. package/lib/commands/secrets-remove-all.js +220 -0
  129. package/lib/commands/secrets-remove.js +21 -12
  130. package/lib/commands/secrets-set.js +21 -12
  131. package/lib/commands/secrets-validate.js +4 -4
  132. package/lib/commands/secure.js +10 -9
  133. package/lib/commands/service-user.js +26 -25
  134. package/lib/commands/test-e2e-external.js +27 -1
  135. package/lib/commands/up-common.js +3 -2
  136. package/lib/commands/up-dataplane.js +29 -16
  137. package/lib/commands/up-miso.js +19 -29
  138. package/lib/commands/upload.js +149 -39
  139. package/lib/commands/wizard-core-helpers.js +1 -1
  140. package/lib/commands/wizard-dataplane.js +4 -3
  141. package/lib/commands/wizard-helpers.js +3 -3
  142. package/lib/commands/wizard.js +2 -2
  143. package/lib/core/admin-secrets.js +14 -5
  144. package/lib/core/audit-logger.js +12 -4
  145. package/lib/core/config-attach-extensions.js +46 -0
  146. package/lib/core/config-runtime-paths.js +29 -0
  147. package/lib/core/config.js +55 -56
  148. package/lib/core/diff.js +3 -2
  149. package/lib/core/ensure-encryption-key.js +1 -1
  150. package/lib/core/secrets-ensure-infra.js +77 -0
  151. package/lib/core/secrets-ensure.js +120 -64
  152. package/lib/core/secrets-env-write.js +35 -7
  153. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  154. package/lib/core/secrets.js +200 -37
  155. package/lib/core/templates-env.js +4 -3
  156. package/lib/datasource/abac-validator.js +1 -10
  157. package/lib/datasource/deploy.js +75 -53
  158. package/lib/datasource/field-reference-validator.js +9 -6
  159. package/lib/datasource/integration-context.js +63 -0
  160. package/lib/datasource/list.js +8 -7
  161. package/lib/datasource/log-viewer.js +189 -67
  162. package/lib/datasource/resolve-app.js +4 -4
  163. package/lib/datasource/test-e2e.js +113 -146
  164. package/lib/datasource/test-integration.js +114 -122
  165. package/lib/datasource/unified-validation-run-body.js +68 -0
  166. package/lib/datasource/unified-validation-run-post.js +23 -0
  167. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  168. package/lib/datasource/unified-validation-run.js +93 -0
  169. package/lib/datasource/validate.js +157 -13
  170. package/lib/deployment/deployer.js +4 -3
  171. package/lib/deployment/environment.js +7 -6
  172. package/lib/deployment/push.js +17 -8
  173. package/lib/external-system/delete.js +4 -3
  174. package/lib/external-system/deploy.js +166 -53
  175. package/lib/external-system/download-helpers.js +1 -1
  176. package/lib/external-system/download.js +7 -6
  177. package/lib/external-system/generator.js +92 -6
  178. package/lib/external-system/integration-test-dispatch.js +26 -0
  179. package/lib/external-system/test-execution.js +5 -1
  180. package/lib/external-system/test-helpers.js +0 -4
  181. package/lib/external-system/test-system-level-helpers.js +110 -0
  182. package/lib/external-system/test-system-level.js +83 -44
  183. package/lib/external-system/test.js +59 -8
  184. package/lib/generator/builders.js +23 -11
  185. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  186. package/lib/generator/external.js +16 -4
  187. package/lib/generator/helpers.js +58 -3
  188. package/lib/generator/index.js +4 -0
  189. package/lib/generator/split-readme.js +12 -7
  190. package/lib/generator/split-variables.js +2 -1
  191. package/lib/generator/split.js +1 -1
  192. package/lib/generator/wizard-readme.js +3 -3
  193. package/lib/generator/wizard.js +8 -8
  194. package/lib/infrastructure/compose.js +70 -7
  195. package/lib/infrastructure/helpers-docker-check.js +67 -0
  196. package/lib/infrastructure/helpers.js +203 -42
  197. package/lib/infrastructure/index.js +31 -18
  198. package/lib/infrastructure/services.js +21 -67
  199. package/lib/internal/fs-real-sync.js +104 -0
  200. package/lib/internal/node-fs.js +98 -0
  201. package/lib/parameters/database-secret-values.js +173 -0
  202. package/lib/parameters/infra-kv-discovery.js +121 -0
  203. package/lib/parameters/infra-parameter-catalog.js +458 -0
  204. package/lib/parameters/infra-parameter-validate.js +64 -0
  205. package/lib/schema/application-schema.json +37 -17
  206. package/lib/schema/datasource-test-run.schema.json +493 -0
  207. package/lib/schema/deployment-rules.yaml +102 -63
  208. package/lib/schema/external-datasource.schema.json +1200 -442
  209. package/lib/schema/external-system.schema.json +203 -5
  210. package/lib/schema/flag-map-validation-run.json +31 -0
  211. package/lib/schema/infra-parameter.schema.json +106 -0
  212. package/lib/schema/infra.parameter.yaml +421 -0
  213. package/lib/schema/type/credential-auth-templates.json +40 -0
  214. package/lib/schema/type/document-storage.json +226 -0
  215. package/lib/schema/type/message-service.json +123 -0
  216. package/lib/schema/type/vector-store.json +88 -0
  217. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  218. package/lib/utils/api-error-handler.js +2 -2
  219. package/lib/utils/api.js +77 -17
  220. package/lib/utils/app-register-api.js +3 -2
  221. package/lib/utils/app-register-auth.js +1 -1
  222. package/lib/utils/app-register-config.js +4 -4
  223. package/lib/utils/app-register-display.js +3 -2
  224. package/lib/utils/app-register-validator.js +3 -2
  225. package/lib/utils/app-run-containers.js +26 -22
  226. package/lib/utils/app-scoped-config.js +31 -0
  227. package/lib/utils/app-service-env-from-builder.js +164 -0
  228. package/lib/utils/build-copy.js +1 -1
  229. package/lib/utils/build-helpers.js +20 -20
  230. package/lib/utils/build-resolve-image.js +165 -0
  231. package/lib/utils/cli-layout-chalk.js +8 -0
  232. package/lib/utils/cli-test-layout-chalk.js +267 -0
  233. package/lib/utils/cli-utils.js +88 -11
  234. package/lib/utils/compose-db-passwords.js +138 -0
  235. package/lib/utils/compose-generate-docker-compose.js +216 -0
  236. package/lib/utils/compose-generator.js +197 -291
  237. package/lib/utils/compose-miso-env.js +18 -0
  238. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  239. package/lib/utils/config-paths.js +166 -7
  240. package/lib/utils/config-scoped-resources-preference.js +41 -0
  241. package/lib/utils/configuration-env-resolver.js +11 -8
  242. package/lib/utils/controller-deployment-outcome.js +68 -0
  243. package/lib/utils/credential-display.js +2 -2
  244. package/lib/utils/credential-secrets-env.js +5 -5
  245. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  246. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  247. package/lib/utils/datasource-test-run-certificate-tty.js +82 -0
  248. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  249. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  250. package/lib/utils/datasource-test-run-display.js +459 -0
  251. package/lib/utils/datasource-test-run-exit.js +83 -0
  252. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  253. package/lib/utils/datasource-test-run-report-version.js +51 -0
  254. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  255. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  256. package/lib/utils/datasource-validation-watch.js +266 -0
  257. package/lib/utils/declarative-url-ports.js +47 -0
  258. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  259. package/lib/utils/dev-ca-install.js +185 -23
  260. package/lib/utils/dev-cert-helper.js +266 -17
  261. package/lib/utils/dev-hosts-helper.js +307 -0
  262. package/lib/utils/dev-init-cert-hints.js +37 -0
  263. package/lib/utils/dev-init-health-messages.js +52 -0
  264. package/lib/utils/dev-init-resolve.js +86 -0
  265. package/lib/utils/dev-init-ssh-merge.js +65 -0
  266. package/lib/utils/dev-ssh-config-helper.js +196 -0
  267. package/lib/utils/dev-user-groups.js +93 -0
  268. package/lib/utils/docker-build.js +42 -17
  269. package/lib/utils/docker-exec.js +28 -0
  270. package/lib/utils/docker-manifest-public-port.js +116 -0
  271. package/lib/utils/docker-not-running-hint.js +52 -0
  272. package/lib/utils/docker.js +98 -11
  273. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  274. package/lib/utils/env-config-loader.js +10 -91
  275. package/lib/utils/env-copy.js +19 -10
  276. package/lib/utils/env-map.js +35 -8
  277. package/lib/utils/env-template.js +2 -2
  278. package/lib/utils/environment-scoped-resources.js +144 -0
  279. package/lib/utils/error-formatter.js +92 -13
  280. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  281. package/lib/utils/error-formatters/network-errors.js +2 -1
  282. package/lib/utils/error-formatters/permission-errors.js +2 -1
  283. package/lib/utils/error-formatters/validation-errors.js +2 -1
  284. package/lib/utils/external-readme.js +8 -1
  285. package/lib/utils/external-system-display.js +242 -136
  286. package/lib/utils/external-system-local-test-tty.js +389 -0
  287. package/lib/utils/external-system-readiness-core.js +377 -0
  288. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  289. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  290. package/lib/utils/external-system-readiness-display.js +186 -0
  291. package/lib/utils/external-system-system-test-tty-overview.js +120 -0
  292. package/lib/utils/external-system-system-test-tty.js +417 -0
  293. package/lib/utils/external-system-test-helpers.js +24 -6
  294. package/lib/utils/external-system-validators.js +30 -12
  295. package/lib/utils/health-check-url.js +119 -0
  296. package/lib/utils/health-check.js +59 -25
  297. package/lib/utils/help-builder.js +11 -8
  298. package/lib/utils/image-version.js +4 -8
  299. package/lib/utils/infra-containers.js +4 -7
  300. package/lib/utils/infra-env-defaults.js +162 -0
  301. package/lib/utils/infra-status-display.js +167 -0
  302. package/lib/utils/infra-status.js +16 -8
  303. package/lib/utils/local-secrets.js +3 -4
  304. package/lib/utils/paths.js +148 -47
  305. package/lib/utils/port-resolver.js +10 -23
  306. package/lib/utils/redis-env-scope.js +62 -0
  307. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  308. package/lib/utils/remote-builder-validation.js +99 -0
  309. package/lib/utils/remote-dev-auth.js +117 -21
  310. package/lib/utils/remote-docker-env.js +67 -15
  311. package/lib/utils/remote-secrets-loader.js +13 -4
  312. package/lib/utils/resolve-docker-image-ref.js +124 -0
  313. package/lib/utils/schema-loader.js +22 -9
  314. package/lib/utils/secrets-bash-kv.js +25 -0
  315. package/lib/utils/secrets-generator.js +169 -49
  316. package/lib/utils/secrets-helpers.js +70 -59
  317. package/lib/utils/secrets-kv-scope.js +60 -0
  318. package/lib/utils/secrets-utils.js +32 -38
  319. package/lib/utils/secrets-validation.js +3 -1
  320. package/lib/utils/secrets-yaml-preserve.js +109 -0
  321. package/lib/utils/ssh-key-helper.js +4 -2
  322. package/lib/utils/template-helpers.js +2 -2
  323. package/lib/utils/test-log-writer.js +3 -3
  324. package/lib/utils/token-manager.js +1 -2
  325. package/lib/utils/url-declarative-public-base.js +188 -0
  326. package/lib/utils/url-declarative-resolve-build.js +493 -0
  327. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  328. package/lib/utils/url-declarative-resolve.js +220 -0
  329. package/lib/utils/url-declarative-token-parse.js +74 -0
  330. package/lib/utils/url-declarative-url-flags.js +50 -0
  331. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  332. package/lib/utils/url-public-path-prefix.js +34 -0
  333. package/lib/utils/urls-local-registry.js +220 -0
  334. package/lib/utils/validation-report-tty-kit.js +77 -0
  335. package/lib/utils/validation-run-poll.js +112 -0
  336. package/lib/utils/validation-run-post-retry.js +85 -0
  337. package/lib/utils/validation-run-request.js +116 -0
  338. package/lib/utils/variable-transformer.js +21 -4
  339. package/lib/utils/yaml-preserve.js +33 -14
  340. package/lib/validation/datasource-warnings.js +56 -0
  341. package/lib/validation/env-template-auth.js +1 -1
  342. package/lib/validation/external-manifest-validator.js +27 -7
  343. package/lib/validation/validate-display.js +37 -31
  344. package/lib/validation/validate-external-cert-sync.js +23 -0
  345. package/lib/validation/validate.js +8 -14
  346. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  347. package/lib/validation/validator.js +22 -65
  348. package/lib/validation/wizard-config-validator.js +2 -1
  349. package/package.json +9 -4
  350. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  351. package/scripts/diagnose-cli.js +150 -0
  352. package/scripts/install-local.js +307 -55
  353. package/scripts/pnpm-global-remove.js +48 -0
  354. package/templates/README.md +15 -2
  355. package/templates/applications/dataplane/application.yaml +52 -2
  356. package/templates/applications/dataplane/env.template +79 -17
  357. package/templates/applications/dataplane/rbac.yaml +8 -0
  358. package/templates/applications/keycloak/application.yaml +9 -1
  359. package/templates/applications/keycloak/env.template +15 -6
  360. package/templates/applications/miso-controller/application.yaml +10 -2
  361. package/templates/applications/miso-controller/env.template +42 -12
  362. package/templates/applications/miso-controller/rbac.yaml +5 -0
  363. package/templates/external-system/README.md.hbs +20 -7
  364. package/templates/external-system/deploy.js.hbs +5 -5
  365. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  366. package/templates/infra/compose.yaml.hbs +33 -16
  367. package/templates/infra/servers.json.hbs +3 -1
  368. package/templates/python/docker-compose.hbs +16 -0
  369. package/templates/typescript/docker-compose.hbs +16 -0
  370. package/lib/api/external-test.api.js +0 -111
  371. package/lib/schema/env-config.yaml +0 -60
@@ -251,6 +251,35 @@ function collectBlockScalarLines(lines, startIndex, keyIndentLen) {
251
251
  return { value: parts.join(' '), endIndex: i };
252
252
  }
253
253
 
254
+ /**
255
+ * Processes a single block-scalar key line and its continuation lines.
256
+ * @param {string} line - Line matching BLOCK_SCALAR_PATTERN
257
+ * @param {string[]} lines - All lines
258
+ * @param {number} lineIndex - Index of current line
259
+ * @param {string} encryptionKey - Encryption key
260
+ * @param {Object} stats - Mutable stats object
261
+ * @returns {{ resultLine: string, nextIndex: number }|null} Result or null if not a block scalar
262
+ */
263
+ function processBlockScalarLine(line, lines, lineIndex, encryptionKey, stats) {
264
+ const blockMatch = line.match(BLOCK_SCALAR_PATTERN);
265
+ if (!blockMatch) {
266
+ return null;
267
+ }
268
+ const [, indent, key, blockIndicator, trailingWhitespace, comment] = blockMatch;
269
+ const keyIndentLen = indent.length;
270
+ const folded = blockIndicator.startsWith('>');
271
+ const { value: rawValue, endIndex } = collectBlockScalarLines(lines, lineIndex + 1, keyIndentLen);
272
+ const value = folded ? rawValue.replace(/\s+/g, ' ').trim() : rawValue;
273
+ stats.total++;
274
+ const needEncrypt = shouldEncryptValue(value);
275
+ const outValue = needEncrypt ? encryptSecret(value, encryptionKey) : value;
276
+ if (needEncrypt) {
277
+ stats.encrypted++;
278
+ }
279
+ const resultLine = `${indent}${key}: ${outValue}${trailingWhitespace || ''}${comment || ''}`;
280
+ return { resultLine, nextIndex: endIndex };
281
+ }
282
+
254
283
  function encryptYamlValues(content, encryptionKey) {
255
284
  const lines = content.split(/\r?\n/);
256
285
  const encryptedLines = [];
@@ -267,20 +296,10 @@ function encryptYamlValues(content, encryptionKey) {
267
296
  continue;
268
297
  }
269
298
 
270
- const blockMatch = line.match(BLOCK_SCALAR_PATTERN);
271
- if (blockMatch) {
272
- const [, indent, key, blockIndicator, trailingWhitespace, comment] = blockMatch;
273
- const keyIndentLen = indent.length;
274
- const folded = blockIndicator.startsWith('>');
275
- const { value: rawValue, endIndex } = collectBlockScalarLines(lines, i + 1, keyIndentLen);
276
- const value = folded ? rawValue.replace(/\s+/g, ' ').trim() : rawValue;
277
- stats.total++;
278
- const outValue = shouldEncryptValue(value) ? encryptSecret(value, encryptionKey) : value;
279
- if (shouldEncryptValue(value)) {
280
- stats.encrypted++;
281
- }
282
- encryptedLines.push(`${indent}${key}: ${outValue}${trailingWhitespace || ''}${comment || ''}`);
283
- i = endIndex;
299
+ const blockResult = processBlockScalarLine(line, lines, i, encryptionKey, stats);
300
+ if (blockResult) {
301
+ encryptedLines.push(blockResult.resultLine);
302
+ i = blockResult.nextIndex;
284
303
  continue;
285
304
  }
286
305
 
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Optional v2.4.x datasource validation warnings (beyond JSON Schema).
3
+ *
4
+ * @fileoverview Post-schema warnings for external datasource configs
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ const STORAGE_ENTITIES = new Set(['recordStorage', 'documentStorage']);
10
+
11
+ function warnMissingDimensions(parsed, warnings) {
12
+ const entityType = parsed.entityType;
13
+ if (!STORAGE_ENTITIES.has(entityType)) {
14
+ return;
15
+ }
16
+ const dims = parsed.dimensions;
17
+ if (!dims || typeof dims !== 'object' || Array.isArray(dims) || Object.keys(dims).length === 0) {
18
+ warnings.push(
19
+ `Datasource "${parsed.key || '(unknown)'}": dimensions missing or empty for entityType=${entityType} (recommended for ABAC; see schema 2.4.x notes).`
20
+ );
21
+ }
22
+ }
23
+
24
+ function warnFkWithoutActor(parsed, warnings) {
25
+ const dimensions = parsed.dimensions;
26
+ if (!dimensions || typeof dimensions !== 'object' || Array.isArray(dimensions)) {
27
+ return;
28
+ }
29
+ for (const [dimKey, binding] of Object.entries(dimensions)) {
30
+ if (!binding || typeof binding !== 'object' || binding.type !== 'fk') {
31
+ continue;
32
+ }
33
+ if (!Object.prototype.hasOwnProperty.call(binding, 'actor')) {
34
+ warnings.push(
35
+ `Datasource "${parsed.key || '(unknown)'}": dimension "${dimKey}" uses type=fk without actor; set actor (displayName, email, userId, groups, roles) for predictable ABAC binding.`
36
+ );
37
+ }
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Collects non-fatal warnings for an external datasource document (already parsed).
43
+ * @param {Object} parsed - Parsed datasource JSON
44
+ * @returns {string[]} Warning messages (empty if none)
45
+ */
46
+ function collectExternalDatasourceWarnings(parsed) {
47
+ const warnings = [];
48
+ if (!parsed || typeof parsed !== 'object') {
49
+ return warnings;
50
+ }
51
+ warnMissingDimensions(parsed, warnings);
52
+ warnFkWithoutActor(parsed, warnings);
53
+ return warnings;
54
+ }
55
+
56
+ module.exports = { collectExternalDatasourceWarnings };
@@ -184,7 +184,7 @@ async function validateAuthSecurityPathConsistency(appPath, errors, _warnings) {
184
184
  const canonicalPath = canonicalSegment ? `kv://${systemKey}/${canonicalSegment}` : null;
185
185
  if (canonicalPath && value !== canonicalPath) {
186
186
  errors.push(
187
- `authentication.security.${key} has path ${value}; canonical path is ${canonicalPath}. Run \`aifabrix repair <app>\` to normalize.`
187
+ `authentication.security.${key} has path ${value}; canonical path is ${canonicalPath}. Run \`aifabrix repair <systemKey>\` to normalize.`
188
188
  );
189
189
  }
190
190
  }
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  const Ajv = require('ajv');
13
+ const addFormats = require('ajv-formats');
13
14
  const fs = require('fs');
14
15
  const path = require('path');
15
16
  const { formatValidationErrors } = require('../utils/error-formatter');
@@ -28,6 +29,7 @@ async function setupAjvWithSchemas() {
28
29
  strict: false,
29
30
  removeAdditional: false
30
31
  });
32
+ addFormats(ajv);
31
33
 
32
34
  // Load raw schema objects (not compiled validators)
33
35
  const externalSystemSchemaPath = path.join(__dirname, '..', 'schema', 'external-system.schema.json');
@@ -43,11 +45,20 @@ async function setupAjvWithSchemas() {
43
45
  externalDatasourceSchema = schemaCopy;
44
46
  }
45
47
 
46
- const externalSystemSchemaId = externalSystemSchema.$id || 'https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-system.schema.json';
47
- const externalDatasourceSchemaId = externalDatasourceSchema.$id || 'https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-datasource.schema.json';
48
+ if (!externalSystemSchema.$id || typeof externalSystemSchema.$id !== 'string') {
49
+ throw new Error('External system schema is missing required $id');
50
+ }
51
+ if (!externalDatasourceSchema.$id || typeof externalDatasourceSchema.$id !== 'string') {
52
+ throw new Error('External datasource schema is missing required $id');
53
+ }
48
54
 
49
- ajv.addSchema(externalSystemSchema, externalSystemSchemaId);
50
- ajv.addSchema(externalDatasourceSchema, externalDatasourceSchemaId);
55
+ // external-datasource.schema.json references these by $id (aifabrix://schema/type/*)
56
+ ajv.addSchema(require('../schema/type/document-storage.json'));
57
+ ajv.addSchema(require('../schema/type/message-service.json'));
58
+ ajv.addSchema(require('../schema/type/vector-store.json'));
59
+
60
+ ajv.addSchema(externalSystemSchema, externalSystemSchema.$id);
61
+ ajv.addSchema(externalDatasourceSchema, externalDatasourceSchema.$id);
51
62
 
52
63
  return { ajv, externalSystemSchema, externalDatasourceSchema };
53
64
  }
@@ -66,7 +77,10 @@ function validateManifestStructure(manifest, ajv, applicationSchema, errors) {
66
77
  const manifestValid = validateManifest(manifest);
67
78
 
68
79
  if (!manifestValid) {
69
- const manifestErrors = formatValidationErrors(validateManifest.errors);
80
+ const manifestErrors = formatValidationErrors(validateManifest.errors, {
81
+ rootData: manifest,
82
+ deploymentManifest: manifest
83
+ });
70
84
  errors.push(...manifestErrors.map(err => `Manifest validation: ${err}`));
71
85
  }
72
86
  }
@@ -86,7 +100,10 @@ function validateInlineSystem(manifest, ajv, externalSystemSchema, errors) {
86
100
  const systemValid = validateSystem(manifest.system);
87
101
 
88
102
  if (!systemValid) {
89
- const systemErrors = formatValidationErrors(validateSystem.errors);
103
+ const systemErrors = formatValidationErrors(validateSystem.errors, {
104
+ rootData: manifest.system,
105
+ deploymentManifest: manifest.system
106
+ });
90
107
  errors.push(...systemErrors.map(err => `System validation: ${err}`));
91
108
  }
92
109
  } else if (manifest.type === 'external') {
@@ -113,7 +130,10 @@ function validateDatasources(manifest, ajv, externalDatasourceSchema, errors, wa
113
130
  manifest.dataSources.forEach((datasource, index) => {
114
131
  const datasourceValid = validateDatasource(datasource);
115
132
  if (!datasourceValid) {
116
- const datasourceErrors = formatValidationErrors(validateDatasource.errors);
133
+ const datasourceErrors = formatValidationErrors(validateDatasource.errors, {
134
+ rootData: datasource,
135
+ deploymentManifest: datasource
136
+ });
117
137
  errors.push(...datasourceErrors.map(err => `Datasource ${index + 1} (${datasource.key || 'unknown'}): ${err}`));
118
138
  } else {
119
139
  const fieldRefErrors = validateFieldReferences(datasource);
@@ -1,3 +1,4 @@
1
+ const { formatBlockingError, formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * Validation Display Utilities
3
4
  *
@@ -25,9 +26,9 @@ function displayApplicationValidation(application) {
25
26
 
26
27
  logger.log(chalk.blue('\nApplication:'));
27
28
  if (application.valid) {
28
- logger.log(chalk.green(' Application configuration is valid'));
29
+ logger.log(chalk.green(' Application configuration is valid'));
29
30
  } else {
30
- logger.log(chalk.red(' Application configuration has errors:'));
31
+ logger.log(chalk.red(' Application configuration has errors:'));
31
32
  if (application.errors && application.errors.length > 0) {
32
33
  application.errors.forEach(error => {
33
34
  logger.log(chalk.red(` • ${error}`));
@@ -51,12 +52,17 @@ function extractDimensionsFromDatasource(filePath) {
51
52
  try {
52
53
  const parsed = loadConfigFile(filePath);
53
54
 
54
- // Check fieldMappings.dimensions (primary location)
55
- const dimensions = parsed.fieldMappings?.dimensions || {};
55
+ const rootFlat = {};
56
+ const root = parsed.dimensions;
57
+ if (root && typeof root === 'object' && !Array.isArray(root)) {
58
+ for (const [dimKey, binding] of Object.entries(root)) {
59
+ if (binding && typeof binding === 'object' && typeof binding.field === 'string') {
60
+ rootFlat[dimKey] = `metadata.${binding.field}`;
61
+ }
62
+ }
63
+ }
56
64
  const abacDimensions = parsed.abac?.dimensions || {};
57
-
58
- // Merge both sources (abac.dimensions can override fieldMappings.dimensions)
59
- const allDimensions = { ...dimensions, ...abacDimensions };
65
+ const allDimensions = { ...rootFlat, ...abacDimensions };
60
66
  const dimensionKeys = Object.keys(allDimensions);
61
67
 
62
68
  return {
@@ -82,9 +88,9 @@ function displayExternalFilesValidation(externalFiles) {
82
88
  logger.log(chalk.blue('\nExternal Integration Files:'));
83
89
  externalFiles.forEach(file => {
84
90
  if (file.valid) {
85
- logger.log(chalk.green(` ${file.file} (${file.type})`));
91
+ logger.log(chalk.green(` ${file.file} (${file.type})`));
86
92
  } else {
87
- logger.log(chalk.red(` ${file.file} (${file.type}):`));
93
+ logger.log(chalk.red(` ${file.file} (${file.type}):`));
88
94
  file.errors.forEach(error => {
89
95
  logger.log(chalk.red(` • ${error}`));
90
96
  });
@@ -126,7 +132,7 @@ function displayDimensionsValidation(externalFiles) {
126
132
 
127
133
  if (dimensionsInfo.hasDimensions) {
128
134
  anyDatasourceHasDimensions = true;
129
- logger.log(chalk.green(` ${file.file}`));
135
+ logger.log(chalk.green(` ${file.file}`));
130
136
  dimensionsInfo.dimensionKeys.forEach(key => {
131
137
  const mapping = dimensionsInfo.dimensions[key];
132
138
  logger.log(chalk.gray(` ${key} → ${mapping}`));
@@ -156,9 +162,9 @@ function displayRbacValidation(rbac) {
156
162
 
157
163
  logger.log(chalk.blue('\nRBAC Configuration:'));
158
164
  if (rbac.valid) {
159
- logger.log(chalk.green(' RBAC configuration is valid'));
165
+ logger.log(chalk.green(' RBAC configuration is valid'));
160
166
  } else {
161
- logger.log(chalk.red(' RBAC configuration has errors:'));
167
+ logger.log(chalk.red(' RBAC configuration has errors:'));
162
168
  rbac.errors.forEach(error => {
163
169
  logger.log(chalk.red(` • ${error}`));
164
170
  });
@@ -183,9 +189,9 @@ function displayFileValidation(result) {
183
189
  logger.log(chalk.blue(`\nFile: ${result.file}`));
184
190
  logger.log(chalk.blue(`Type: ${result.type}`));
185
191
  if (result.valid) {
186
- logger.log(chalk.green(' File is valid'));
192
+ logger.log(chalk.green(' File is valid'));
187
193
  } else {
188
- logger.log(chalk.red(' File has errors:'));
194
+ logger.log(chalk.red(' File has errors:'));
189
195
  result.errors.forEach(error => {
190
196
  logger.log(chalk.red(` • ${error}`));
191
197
  });
@@ -222,9 +228,9 @@ function displayAggregatedWarnings(warnings) {
222
228
  function displayApplicationStep(application) {
223
229
  logger.log(chalk.blue('\nApplication:'));
224
230
  if (application.valid) {
225
- logger.log(chalk.green(' Application configuration is valid'));
231
+ logger.log(chalk.green(' Application configuration is valid'));
226
232
  } else {
227
- logger.log(chalk.red(' Application configuration has errors:'));
233
+ logger.log(chalk.red(' Application configuration has errors:'));
228
234
  application.errors.forEach(error => {
229
235
  logger.log(chalk.red(` • ${error}`));
230
236
  });
@@ -253,9 +259,9 @@ function displayComponentsStep(components) {
253
259
  if (hasFiles) {
254
260
  components.files.forEach(file => {
255
261
  if (file.valid) {
256
- logger.log(chalk.green(` ${file.file} (${file.type})`));
262
+ logger.log(chalk.green(` ${file.file} (${file.type})`));
257
263
  } else {
258
- logger.log(chalk.red(` ${file.file} (${file.type})`));
264
+ logger.log(chalk.red(` ${file.file} (${file.type})`));
259
265
  }
260
266
  });
261
267
  }
@@ -282,7 +288,7 @@ function displayDimensionsStep(datasourceFiles) {
282
288
  try {
283
289
  const dimensionsInfo = extractDimensionsFromDatasource(file.path || file.file);
284
290
  if (dimensionsInfo.hasDimensions) {
285
- logger.log(chalk.green(` ${file.file}`));
291
+ logger.log(chalk.green(` ${file.file}`));
286
292
  dimensionsInfo.dimensionKeys.forEach(key => {
287
293
  const mapping = dimensionsInfo.dimensions[key];
288
294
  logger.log(chalk.gray(` ${key} → ${mapping}`));
@@ -308,15 +314,15 @@ function displayManifestStep(manifest, componentFiles) {
308
314
  if (manifest.skipped) {
309
315
  logger.log(chalk.yellow(' ⊘ Skipped (fix errors above first)'));
310
316
  } else if (manifest.valid) {
311
- logger.log(chalk.green(' Full deployment manifest is valid'));
317
+ logger.log(chalk.green(' Full deployment manifest is valid'));
312
318
  if (componentFiles) {
313
319
  const datasourceFiles = componentFiles.filter(f => f.type === 'datasource' || f.type === 'external-datasource');
314
- logger.log(chalk.green(' System configuration valid'));
315
- logger.log(chalk.green(` ${datasourceFiles.length} datasource(s) valid`));
316
- logger.log(chalk.green(' Schema validation passed'));
320
+ logger.log(chalk.green(' System configuration valid'));
321
+ logger.log(chalk.green(` ${datasourceFiles.length} datasource(s) valid`));
322
+ logger.log(chalk.green(' Schema validation passed'));
317
323
  }
318
324
  } else {
319
- logger.log(chalk.red(' Full deployment manifest validation failed:'));
325
+ logger.log(chalk.red(' Full deployment manifest validation failed:'));
320
326
  const errs = manifest.errors && manifest.errors.length > 0 ? manifest.errors : [];
321
327
  if (errs.length > 0) {
322
328
  errs.forEach(error => {
@@ -341,9 +347,9 @@ function displayManifestStep(manifest, componentFiles) {
341
347
  */
342
348
  function displayStepByStepValidation(result) {
343
349
  if (result.valid) {
344
- logger.log(chalk.green('\n✓ Validation passed!'));
350
+ logger.log(formatSuccessParagraph('Validation passed!'));
345
351
  } else {
346
- logger.log(chalk.red('\n Validation failed!'));
352
+ logger.log(chalk.red('\n Validation failed!'));
347
353
  }
348
354
 
349
355
  displayApplicationStep(result.steps.application);
@@ -388,7 +394,7 @@ function displayBatchValidationResults(batchResult) {
388
394
  results.forEach(item => {
389
395
  logger.log(chalk.blue(`\n--- ${item.appName} ---`));
390
396
  if (item.error) {
391
- logger.log(chalk.red(` ${item.error}`));
397
+ logger.log(chalk.red(` ${item.error}`));
392
398
  } else if (item.result) {
393
399
  displayValidationResults(item.result);
394
400
  }
@@ -398,10 +404,10 @@ function displayBatchValidationResults(batchResult) {
398
404
  const failed = results.length - passed;
399
405
  logger.log(chalk.blue('\n--- Summary ---'));
400
406
  if (failed === 0) {
401
- logger.log(chalk.green(`✓ ${passed} passed, 0 failed`));
407
+ logger.log(formatSuccessLine(`${passed} passed, 0 failed`));
402
408
  logger.log(chalk.green('Overall: Passed'));
403
409
  } else {
404
- logger.log(chalk.red(`✗ ${passed} passed, ${failed} failed`));
410
+ logger.log(formatBlockingError(`${passed} passed, ${failed} failed`));
405
411
  logger.log(chalk.red('Overall: Failed'));
406
412
  }
407
413
  }
@@ -445,9 +451,9 @@ function displayValidationResults(result) {
445
451
 
446
452
  // Legacy format (for regular apps)
447
453
  if (result.valid) {
448
- logger.log(chalk.green('\n✓ Validation passed!'));
454
+ logger.log(formatSuccessParagraph('Validation passed!'));
449
455
  } else {
450
- logger.log(chalk.red('\n Validation failed!'));
456
+ logger.log(chalk.red('\n Validation failed!'));
451
457
  }
452
458
 
453
459
  displayApplicationValidation(result.application);
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @fileoverview Optional certification sync after external validate (keeps validate.js under max-lines).
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * @param {string} appName
11
+ * @param {Object} options
12
+ * @param {function(string, Object): Promise<Object>} validateExternalSystemComplete
13
+ */
14
+ async function runExternalValidateWithOptionalCertSync(appName, options, validateExternalSystemComplete) {
15
+ const result = await validateExternalSystemComplete(appName, options);
16
+ if (result.valid && options.certSync === true) {
17
+ const { trySyncCertificationFromDataplaneForExternalApp } = require('../certification/sync-after-external-command');
18
+ await trySyncCertificationFromDataplaneForExternalApp(appName, 'validate');
19
+ }
20
+ return result;
21
+ }
22
+
23
+ module.exports = { runExternalValidateWithOptionalCertSync };
@@ -28,6 +28,7 @@ const {
28
28
  validateOAuth2GrantTypeAndAuthorizationUrl,
29
29
  validateConfigurationNoStandardAuthVariables
30
30
  } = require('./external-system-auth-rules');
31
+ const { runExternalValidateWithOptionalCertSync } = require('./validate-external-cert-sync');
31
32
 
32
33
  /**
33
34
  * Validates a file path (detects type and validates)
@@ -104,15 +105,6 @@ function aggregateValidationResults(appValidation, externalValidations, rbacVali
104
105
  };
105
106
  }
106
107
 
107
- /**
108
- * Validates a single external file against its schema
109
- *
110
- * @async
111
- * @function validateExternalFile
112
- * @param {string} filePath - Path to the file
113
- * @param {string} type - File type: 'system' | 'datasource'
114
- * @returns {Promise<Object>} Validation result
115
- */
116
108
  /**
117
109
  * Parses external system/datasource file content (JSON or YAML).
118
110
  * @function parseJsonFileContent
@@ -179,6 +171,7 @@ function validateRoleReferences(parsed, errors) {
179
171
  });
180
172
  }
181
173
 
174
+ /** @async */
182
175
  async function validateExternalFile(filePath, type) {
183
176
  if (!fs.existsSync(filePath)) {
184
177
  throw new Error(`File not found: ${filePath}`);
@@ -203,9 +196,9 @@ async function validateExternalFile(filePath, type) {
203
196
  }
204
197
 
205
198
  if (normalizedType === 'datasource') {
206
- const fieldRefErrors = validateFieldReferences(parseResult.parsed);
207
- const abacErrors = validateAbac(parseResult.parsed);
208
- errors.push(...fieldRefErrors, ...abacErrors);
199
+ const { collectExternalDatasourceWarnings } = require('./datasource-warnings');
200
+ errors.push(...validateFieldReferences(parseResult.parsed), ...validateAbac(parseResult.parsed));
201
+ warnings.push(...collectExternalDatasourceWarnings(parseResult.parsed));
209
202
  }
210
203
 
211
204
  return {
@@ -470,7 +463,9 @@ async function validateAppOrFile(appOrFile, options = {}) {
470
463
  const { appPath, isExternal } = await detectAppType(appName);
471
464
  logOfflinePathWhenType(appPath);
472
465
 
473
- if (isExternal) return await validateExternalSystemComplete(appName, options);
466
+ if (isExternal) {
467
+ return await runExternalValidateWithOptionalCertSync(appName, options, validateExternalSystemComplete);
468
+ }
474
469
 
475
470
  const appValidation = await validator.validateApplication(appName, options);
476
471
  const rbacValidation = await validateRbacForExternalSystem(isExternal, appName);
@@ -497,4 +492,3 @@ module.exports = {
497
492
  validateAll: (opts = {}) => batch.validateAll(validateAppOrFile, opts),
498
493
  buildBatchResult: batch.buildBatchResult
499
494
  };
500
-
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Scan deployment/config objects for unresolved ${VAR} placeholders (validator helper).
3
+ *
4
+ * @fileoverview Extracted from validator.js for ESLint max-lines
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ /** Pattern matching ${VAR} style unresolved variables in strings */
12
+ const UNRESOLVED_VAR_REGEX = /\$\{[^}]+\}/g;
13
+
14
+ /** Allowed manifest placeholders in frontDoorRouting.host (expanded at run/resolve time; plan 122). */
15
+ const FRONT_DOOR_HOST_ALLOWED = /\$\{DEV_USERNAME\}|\$\{REMOTE_HOST\}/g;
16
+
17
+ /**
18
+ * First ${...} still present after stripping allowed DEV_USERNAME / REMOTE_HOST segments, or null.
19
+ * @param {string} str
20
+ * @returns {string|null}
21
+ */
22
+ function getFrontDoorHostFirstForbiddenPlaceholder(str) {
23
+ let t = String(str).trim();
24
+ t = t.replace(FRONT_DOOR_HOST_ALLOWED, '');
25
+ t = t.replace(/^\.+|\.+$/g, '').trim();
26
+ const m = t.match(UNRESOLVED_VAR_REGEX);
27
+ return m ? m[0] : null;
28
+ }
29
+
30
+ /**
31
+ * @param {string} obj
32
+ * @param {string} prefix
33
+ * @returns {string[]}
34
+ */
35
+ function collectStringUnresolved(obj, prefix) {
36
+ if (prefix === 'frontDoorRouting.host') {
37
+ const bad = getFrontDoorHostFirstForbiddenPlaceholder(obj);
38
+ return bad ? [`${prefix}: ${bad}`] : [];
39
+ }
40
+ const matches = obj.match(UNRESOLVED_VAR_REGEX);
41
+ if (matches && matches.length > 0) {
42
+ const pathLabel = prefix || 'value';
43
+ return [`${pathLabel}: ${matches[0]}`];
44
+ }
45
+ return [];
46
+ }
47
+
48
+ /**
49
+ * Recursively finds all string values in obj that contain ${...} placeholders.
50
+ *
51
+ * @param {Object} obj - Object to scan (e.g. deployment manifest)
52
+ * @param {string} [prefix=''] - Path prefix for error reporting
53
+ * @returns {string[]} List of paths with example placeholder (e.g. "port: ${PORT}")
54
+ */
55
+ function findUnresolvedVariablesInObject(obj, prefix = '') {
56
+ const found = [];
57
+ if (obj === null || obj === undefined) {
58
+ return found;
59
+ }
60
+ if (typeof obj === 'string') {
61
+ return collectStringUnresolved(obj, prefix);
62
+ }
63
+ if (Array.isArray(obj)) {
64
+ obj.forEach((item, i) => {
65
+ found.push(...findUnresolvedVariablesInObject(item, `${prefix}[${i}]`));
66
+ });
67
+ return found;
68
+ }
69
+ if (typeof obj === 'object') {
70
+ for (const [key, value] of Object.entries(obj)) {
71
+ const path = prefix ? `${prefix}.${key}` : key;
72
+ found.push(...findUnresolvedVariablesInObject(value, path));
73
+ }
74
+ return found;
75
+ }
76
+ return found;
77
+ }
78
+
79
+ /**
80
+ * Validates that deployment manifest contains no unresolved ${...} variables.
81
+ * @param {Object} deployment - Deployment manifest object
82
+ * @throws {Error} If any ${...} placeholders are found
83
+ */
84
+ function validateNoUnresolvedVariablesInDeployment(deployment) {
85
+ const unresolved = findUnresolvedVariablesInObject(deployment);
86
+ if (unresolved.length > 0) {
87
+ const examples = [...new Set(unresolved)].slice(0, 5).join(', ');
88
+ throw new Error(
89
+ `Deployment manifest contains unresolved variables (e.g. ${examples}). ` +
90
+ 'Use secret variables (kv://) in env.template for sensitive values, and set the application port as a number in application.yaml.'
91
+ );
92
+ }
93
+ }
94
+
95
+ module.exports = {
96
+ findUnresolvedVariablesInObject,
97
+ validateNoUnresolvedVariablesInDeployment
98
+ };