@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
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Traefik ingress path/host/TLS and StripPrefix derivation helpers for compose generation.
3
+ *
4
+ * @fileoverview Traefik PathPrefix + host expansion (shared with health path resolution)
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ const {
10
+ buildEnvScopedTraefikPath
11
+ } = require('./environment-scoped-resources');
12
+ const { parseDeveloperIdNum } = require('./declarative-url-ports');
13
+
14
+ /**
15
+ * Derives base path from routing pattern by removing trailing wildcards
16
+ * @param {string} pattern - URL pattern (e.g., '/app/*', '/api/v1/*')
17
+ * @returns {string} Base path for routing
18
+ */
19
+ function derivePathFromPattern(pattern) {
20
+ if (!pattern || typeof pattern !== 'string') {
21
+ return '/';
22
+ }
23
+ const trimmed = pattern.trim();
24
+ if (trimmed === '/' || trimmed === '') {
25
+ return '/';
26
+ }
27
+ const withoutWildcards = trimmed.replace(/\*+$/g, '');
28
+ const withoutTrailingSlashes = withoutWildcards.replace(/\/+$/g, '');
29
+ return withoutTrailingSlashes || '/';
30
+ }
31
+
32
+ /**
33
+ * Resolve Traefik TLS from application.yaml `frontDoorRouting.tls` (boolean or string).
34
+ * String "false" disables TLS; placeholders (e.g. ${TLS_ENABLED}) are treated as enabled.
35
+ * @param {unknown} tls - Raw tls value
36
+ * @returns {boolean}
37
+ */
38
+ function resolveTraefikTlsEnabled(tls) {
39
+ if (tls === false || tls === 'false') {
40
+ return false;
41
+ }
42
+ return true;
43
+ }
44
+
45
+ /**
46
+ * Developer label for frontDoorRouting.host only. Id 0 / missing / empty → no subdomain (empty string).
47
+ * Non-zero → dev01, dev02, … (same padding as buildDevUsername).
48
+ *
49
+ * @param {string|number|null|undefined} devId
50
+ * @returns {string}
51
+ */
52
+ function buildDevUsernameForFrontDoorHost(devId) {
53
+ const n = parseDeveloperIdNum(devId);
54
+ if (n === 0) {
55
+ return '';
56
+ }
57
+ const s = String(n);
58
+ const padded = s.length === 1 ? s.padStart(2, '0') : s;
59
+ return `dev${padded}`;
60
+ }
61
+
62
+ /**
63
+ * Expand frontDoorRouting.host placeholders (Traefik labels + url:// base). Same rules as declarative URL resolver.
64
+ * Normalizes ${DEV_USERNAME}${REMOTE_HOST} to insert a dot. Trims stray leading/trailing dots (e.g. id 0 + `.${REMOTE_HOST}` → bare remote hostname).
65
+ *
66
+ * @param {string} template
67
+ * @param {string|number|null|undefined} developerIdRaw
68
+ * @param {string|null|undefined} remoteServer
69
+ * @returns {string}
70
+ */
71
+ function expandFrontDoorHostPlaceholders(template, developerIdRaw, remoteServer) {
72
+ let t = String(template || '');
73
+ t = t.replace(/\$\{DEV_USERNAME\}\$\{REMOTE_HOST\}/g, '${DEV_USERNAME}.${REMOTE_HOST}');
74
+ const devU = buildDevUsernameForFrontDoorHost(developerIdRaw);
75
+ t = t.replace(/\$\{DEV_USERNAME\}/g, devU);
76
+ let remoteHost = '';
77
+ try {
78
+ if (remoteServer && String(remoteServer).trim()) {
79
+ remoteHost = new URL(String(remoteServer).trim()).hostname;
80
+ }
81
+ } catch {
82
+ remoteHost = '';
83
+ }
84
+ t = t.replace(/\$\{REMOTE_HOST\}/g, remoteHost);
85
+ t = t.replace(/^\.+/g, '').replace(/\.{2,}/g, '.').replace(/\.+$/g, '').trim();
86
+ return t;
87
+ }
88
+
89
+ /**
90
+ * Traefik PathPrefix / host / TLS from frontDoorRouting (public ingress). Does not include StripPrefix — that follows
91
+ * the in-container health path: private URL is `http://<service>:<port>` plus the probe path; `/dev`, `/tst`, `/auth`,
92
+ * etc. are public path segments only.
93
+ *
94
+ * @param {Object} config - Application configuration (application.yaml shape)
95
+ * @param {string|number} devId - Developer id for host expansion
96
+ * @param {Object|null} scopeOpts - Env-scoped Traefik path (effectiveEnvironmentScopedResources, runEnvKey)
97
+ * @param {string|null|undefined} remoteServer - For ${REMOTE_HOST}
98
+ * @returns {{ enabled: false } | { enabled: true, host: string, path: string, tls: boolean, certStore: string|null }}
99
+ */
100
+ function buildTraefikIngressBase(config, devId, scopeOpts, remoteServer) {
101
+ const frontDoor = config.frontDoorRouting;
102
+ if (!frontDoor || frontDoor.enabled !== true) {
103
+ return { enabled: false };
104
+ }
105
+ if (!frontDoor.host || typeof frontDoor.host !== 'string') {
106
+ throw new Error('frontDoorRouting.host is required when frontDoorRouting.enabled is true');
107
+ }
108
+ const host = expandFrontDoorHostPlaceholders(frontDoor.host, devId, remoteServer);
109
+ const basePath = derivePathFromPattern(frontDoor.pattern);
110
+ let pathOut = basePath;
111
+ if (
112
+ scopeOpts &&
113
+ scopeOpts.effectiveEnvironmentScopedResources &&
114
+ scopeOpts.runEnvKey &&
115
+ (scopeOpts.runEnvKey === 'dev' || scopeOpts.runEnvKey === 'tst')
116
+ ) {
117
+ pathOut = buildEnvScopedTraefikPath(basePath, scopeOpts.runEnvKey);
118
+ }
119
+ return {
120
+ enabled: true,
121
+ host,
122
+ path: pathOut,
123
+ tls: resolveTraefikTlsEnabled(frontDoor.tls),
124
+ certStore: frontDoor.certStore || null
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Whether Traefik should apply StripPrefix so the backend sees the same path as the Docker health probe.
130
+ * When the resolved compose health path already lies under the public PathPrefix (e.g. /auth/health/ready), forward
131
+ * the full path. When the probe is root-only (e.g. /health) but PathPrefix is /miso, strip the prefix.
132
+ *
133
+ * @param {string} traefikPath - PathPrefix value (slashes normalized, no trailing slash except '/')
134
+ * @param {string} resolvedHealthPath - Output of resolveHealthCheckPathWithFrontDoorVdir with compose opts
135
+ * @returns {boolean} true when StripPrefix middleware labels should be emitted
136
+ */
137
+ function computeTraefikStripPathPrefix(traefikPath, resolvedHealthPath) {
138
+ const healthRaw = String(resolvedHealthPath || '/').trim();
139
+ const health = healthRaw.replace(/\/+$/, '') || '/';
140
+ const prefixRaw = String(traefikPath || '/').trim();
141
+ const prefix = prefixRaw.replace(/\/+$/, '') || '/';
142
+ if (prefix === '/' || prefix === '') {
143
+ return false;
144
+ }
145
+ if (health === prefix || health.startsWith(`${prefix}/`)) {
146
+ return false;
147
+ }
148
+ return true;
149
+ }
150
+
151
+ module.exports = {
152
+ derivePathFromPattern,
153
+ resolveTraefikTlsEnabled,
154
+ buildDevUsernameForFrontDoorHost,
155
+ expandFrontDoorHostPlaceholders,
156
+ buildTraefikIngressBase,
157
+ computeTraefikStripPathPrefix
158
+ };
@@ -8,6 +8,7 @@
8
8
  * @version 2.0.0
9
9
  */
10
10
 
11
+ const fs = require('fs');
11
12
  const path = require('path');
12
13
 
13
14
  /**
@@ -21,6 +22,7 @@ const SETTINGS_RESPONSE_KEYS = [
21
22
  'aifabrix-env-config',
22
23
  'remote-server',
23
24
  'docker-endpoint',
25
+ 'docker-tls-skip-verify',
24
26
  'sync-ssh-user',
25
27
  'sync-ssh-host'
26
28
  ];
@@ -56,38 +58,199 @@ async function setPathConfig(getConfigFn, saveConfigFn, key, value, errorMsg) {
56
58
  await saveConfigFn(config);
57
59
  }
58
60
 
61
+ /**
62
+ * Clear a path config key (set to undefined so getPathConfig returns null).
63
+ * @param {Function} getConfigFn - Function to get config
64
+ * @param {Function} saveConfigFn - Function to save config
65
+ * @param {string} key - Configuration key
66
+ * @returns {Promise<void>}
67
+ */
68
+ async function clearPathConfig(getConfigFn, saveConfigFn, key) {
69
+ const config = await getConfigFn();
70
+ config[key] = undefined;
71
+ await saveConfigFn(config);
72
+ }
73
+
59
74
  function createHomeAndSecretsPathFunctions(getConfigFn, saveConfigFn) {
60
75
  return {
61
76
  async getAifabrixHomeOverride() {
62
77
  return getPathConfig(getConfigFn, 'aifabrix-home');
63
78
  },
64
79
  async setAifabrixHomeOverride(homePath) {
65
- await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-home', homePath, 'Home path is required and must be a string');
80
+ if (typeof homePath !== 'string') {
81
+ throw new Error('Home path is required and must be a string');
82
+ }
83
+ const trimmed = homePath.trim();
84
+ if (trimmed === '') {
85
+ await clearPathConfig(getConfigFn, saveConfigFn, 'aifabrix-home');
86
+ return;
87
+ }
88
+ await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-home', trimmed, 'Home path must be a non-empty string');
89
+ },
90
+ async getAifabrixWorkOverride() {
91
+ return getPathConfig(getConfigFn, 'aifabrix-work');
92
+ },
93
+ async setAifabrixWorkOverride(workPath) {
94
+ if (typeof workPath !== 'string') {
95
+ throw new Error('Work path is required and must be a string');
96
+ }
97
+ const trimmed = workPath.trim();
98
+ if (trimmed === '') {
99
+ await clearPathConfig(getConfigFn, saveConfigFn, 'aifabrix-work');
100
+ return;
101
+ }
102
+ const resolved = path.resolve(trimmed);
103
+ await setPathConfig(
104
+ getConfigFn,
105
+ saveConfigFn,
106
+ 'aifabrix-work',
107
+ resolved,
108
+ 'Work path must be a non-empty string'
109
+ );
66
110
  },
67
111
  async getAifabrixSecretsPath() {
68
112
  return getPathConfig(getConfigFn, 'aifabrix-secrets');
69
113
  },
70
114
  async setAifabrixSecretsPath(secretsPath) {
71
- await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-secrets', secretsPath, 'Secrets path is required and must be a string');
115
+ if (typeof secretsPath !== 'string') {
116
+ throw new Error('Secrets path is required and must be a string');
117
+ }
118
+ const trimmed = secretsPath.trim();
119
+ if (trimmed === '') {
120
+ await clearPathConfig(getConfigFn, saveConfigFn, 'aifabrix-secrets');
121
+ return;
122
+ }
123
+ await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-secrets', trimmed, 'Secrets path must be a non-empty string');
72
124
  }
73
125
  };
74
126
  }
75
127
 
128
+ /**
129
+ * Resolve configured `aifabrix-env-config` to an absolute path.
130
+ * Relative paths are resolved against the workspace root first: `aifabrix-work` from the same config,
131
+ * then {@link module:lib/utils/paths.getAifabrixWork} (env `AIFABRIX_WORK` + on-disk yaml). If neither
132
+ * is set, falls back to `aifabrix-home` from config, then {@link module:lib/utils/paths.getAifabrixHome}.
133
+ * Never uses the process current working directory alone as the anchor.
134
+ *
135
+ * @async
136
+ * @param {string} raw - Non-empty path string from config (may be relative)
137
+ * @param {Function} getConfigFn - Async config loader
138
+ * @returns {Promise<string>} Normalized absolute path
139
+ */
140
+ async function resolveEnvConfigPathToAbsolute(raw, getConfigFn) {
141
+ const trimmed = String(raw || '').trim();
142
+ if (!trimmed) {
143
+ throw new Error('Env config path must be a non-empty string');
144
+ }
145
+ if (path.isAbsolute(trimmed)) {
146
+ return path.normalize(path.resolve(trimmed));
147
+ }
148
+ const pathsMod = require('./paths');
149
+
150
+ const workFromConfig = await getPathConfig(getConfigFn, 'aifabrix-work');
151
+ let workBase =
152
+ workFromConfig && String(workFromConfig).trim() !== ''
153
+ ? path.resolve(String(workFromConfig).trim())
154
+ : null;
155
+ if (!workBase) {
156
+ workBase = pathsMod.getAifabrixWork();
157
+ }
158
+ if (workBase && String(workBase).trim() !== '') {
159
+ return path.normalize(path.resolve(String(workBase).trim(), trimmed));
160
+ }
161
+
162
+ const homeFromConfig = await getPathConfig(getConfigFn, 'aifabrix-home');
163
+ const base =
164
+ homeFromConfig && String(homeFromConfig).trim() !== ''
165
+ ? path.resolve(String(homeFromConfig).trim())
166
+ : pathsMod.getAifabrixHome();
167
+ return path.normalize(path.resolve(base, trimmed));
168
+ }
169
+
76
170
  function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
77
171
  return {
172
+ /**
173
+ * Legacy `aifabrix-env-config` path when still set in config (infra defaults are in code).
174
+ * @returns {Promise<string|null>}
175
+ */
78
176
  async getAifabrixEnvConfigPath() {
79
- return getPathConfig(getConfigFn, 'aifabrix-env-config');
177
+ const value = await getPathConfig(getConfigFn, 'aifabrix-env-config');
178
+ if (!value || typeof value !== 'string') {
179
+ return null;
180
+ }
181
+ return resolveEnvConfigPathToAbsolute(value, getConfigFn);
80
182
  },
81
183
  async setAifabrixEnvConfigPath(envConfigPath) {
82
- await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-env-config', envConfigPath, 'Env config path is required and must be a string');
184
+ if (typeof envConfigPath !== 'string') {
185
+ throw new Error('Env config path is required and must be a string');
186
+ }
187
+ const trimmed = envConfigPath.trim();
188
+ if (trimmed === '') {
189
+ await clearPathConfig(getConfigFn, saveConfigFn, 'aifabrix-env-config');
190
+ return;
191
+ }
192
+ await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-env-config', trimmed, 'Env config path must be a non-empty string');
83
193
  },
84
194
  async getAifabrixBuilderDir() {
85
195
  const envConfigPath = await getPathConfig(getConfigFn, 'aifabrix-env-config');
86
- return envConfigPath && typeof envConfigPath === 'string' ? path.dirname(envConfigPath) : null;
196
+ if (envConfigPath && typeof envConfigPath === 'string') {
197
+ const absolute = await resolveEnvConfigPathToAbsolute(envConfigPath.trim(), getConfigFn);
198
+ return path.dirname(absolute);
199
+ }
200
+ const pathsMod = require('./paths');
201
+ const tryDir = (base) => {
202
+ if (!base) {
203
+ return null;
204
+ }
205
+ const b = path.join(base, 'builder');
206
+ try {
207
+ if (fs.existsSync(b) && fs.statSync(b).isDirectory()) {
208
+ return b;
209
+ }
210
+ } catch {
211
+ /* ignore */
212
+ }
213
+ return null;
214
+ };
215
+ const fromProject = tryDir(pathsMod.getProjectRoot());
216
+ if (fromProject) {
217
+ return fromProject;
218
+ }
219
+ const work = pathsMod.getAifabrixWork();
220
+ return tryDir(work);
87
221
  }
88
222
  };
89
223
  }
90
224
 
225
+ /**
226
+ * Whether remote Docker TLS should skip server certificate verification (dev / self-signed daemon).
227
+ * Env AIFABRIX_DOCKER_TLS_SKIP_VERIFY=1|true forces skip when set.
228
+ * @param {*} raw - Config or settings value
229
+ * @returns {boolean}
230
+ */
231
+ function isDockerTlsSkipVerifyTruthy(raw) {
232
+ if (raw === true || raw === 1) return true;
233
+ if (typeof raw === 'string') {
234
+ const s = raw.trim().toLowerCase();
235
+ return s === 'true' || s === '1' || s === 'yes';
236
+ }
237
+ return false;
238
+ }
239
+
240
+ /**
241
+ * Explicit opt-out of TLS verify skip in config (docker-tls-skip-verify: false).
242
+ * @param {*} raw - Config value
243
+ * @returns {boolean}
244
+ */
245
+ function isDockerTlsSkipVerifyExplicitlyFalse(raw) {
246
+ if (raw === false) return true;
247
+ if (typeof raw === 'string') {
248
+ const s = raw.trim().toLowerCase();
249
+ return s === 'false' || s === '0' || s === 'no';
250
+ }
251
+ return false;
252
+ }
253
+
91
254
  function createRemoteConfigGetters(getConfigFn) {
92
255
  return {
93
256
  async getRemoteServer() {
@@ -96,6 +259,25 @@ function createRemoteConfigGetters(getConfigFn) {
96
259
  async getDockerEndpoint() {
97
260
  return getPathConfig(getConfigFn, 'docker-endpoint');
98
261
  },
262
+ /**
263
+ * When true, Docker CLI may use DOCKER_TLS_VERIFY=0 only when ca.pem is absent (no trust anchor).
264
+ * If ca.pem exists (e.g. after issue-cert), the daemon is always verified regardless of this flag.
265
+ */
266
+ async getDockerTlsSkipVerify() {
267
+ const envRaw = process.env.AIFABRIX_DOCKER_TLS_SKIP_VERIFY;
268
+ if (envRaw !== undefined && String(envRaw).trim() !== '') {
269
+ return isDockerTlsSkipVerifyTruthy(String(envRaw).trim());
270
+ }
271
+ const config = await getConfigFn();
272
+ const flag = config['docker-tls-skip-verify'];
273
+ if (isDockerTlsSkipVerifyExplicitlyFalse(flag)) {
274
+ return false;
275
+ }
276
+ if (isDockerTlsSkipVerifyTruthy(flag)) {
277
+ return true;
278
+ }
279
+ return false;
280
+ },
99
281
  async getUserMutagenFolder() {
100
282
  return getPathConfig(getConfigFn, 'user-mutagen-folder');
101
283
  },
@@ -125,6 +307,24 @@ function createRemoteConfigSetters(getConfigFn, saveConfigFn) {
125
307
  const config = await getConfigFn();
126
308
  config['docker-endpoint'] = value || undefined;
127
309
  await saveConfigFn(config);
310
+ },
311
+ async setDockerTlsSkipVerify(value) {
312
+ const config = await getConfigFn();
313
+ if (value === null || value === undefined) {
314
+ config['docker-tls-skip-verify'] = undefined;
315
+ } else if (typeof value === 'boolean') {
316
+ config['docker-tls-skip-verify'] = value;
317
+ } else if (typeof value === 'string') {
318
+ const s = value.trim().toLowerCase();
319
+ if (s === '' || s === 'false' || s === '0' || s === 'no') {
320
+ config['docker-tls-skip-verify'] = false;
321
+ } else {
322
+ config['docker-tls-skip-verify'] = isDockerTlsSkipVerifyTruthy(value);
323
+ }
324
+ } else {
325
+ throw new Error('docker-tls-skip-verify must be a boolean or string');
326
+ }
327
+ await saveConfigFn(config);
128
328
  }
129
329
  };
130
330
  }
@@ -155,7 +355,8 @@ function applySecretsUrlFromRemote(config) {
155
355
  const secretsPath = config['aifabrix-secrets'];
156
356
  if (!remoteServer || !secretsPath || isHttpUrl(secretsPath)) return;
157
357
  const base = typeof remoteServer === 'string' ? remoteServer.trim().replace(/\/+$/, '') : '';
158
- if (base) config['aifabrix-secrets'] = `${base}/api/dev/secrets`;
358
+ if (!base) return;
359
+ config['aifabrix-secrets'] = `${base}/api/dev/secrets`;
159
360
  }
160
361
 
161
362
  function applySyncAndDockerFromHost(config) {
@@ -168,6 +369,7 @@ function applySyncAndDockerFromHost(config) {
168
369
  async function mergeRemoteSettingsImpl(getConfigFn, saveConfigFn, settings) {
169
370
  if (!settings || typeof settings !== 'object') return;
170
371
  const config = await getConfigFn();
372
+ delete config['aifabrix-secrets-path'];
171
373
  for (const key of SETTINGS_RESPONSE_KEYS) {
172
374
  const raw = settings[key];
173
375
  if (raw === undefined || raw === null) continue;
@@ -214,6 +416,7 @@ module.exports = {
214
416
  getPathConfig,
215
417
  setPathConfig,
216
418
  createPathConfigFunctions,
419
+ resolveEnvConfigPathToAbsolute,
217
420
  SETTINGS_RESPONSE_KEYS
218
421
  };
219
422
 
@@ -0,0 +1,41 @@
1
+ /**
2
+ * User preference: useEnvironmentScopedResources in ~/.aifabrix/config.yaml
3
+ *
4
+ * @fileoverview Gate for environment-scoped resource resolution (plan 117)
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ /**
12
+ * @param {Function} getConfigFn - async () => config object
13
+ * @param {Function} saveConfigFn - async (config) => void
14
+ * @returns {{ getUseEnvironmentScopedResources: Function, setUseEnvironmentScopedResources: Function }}
15
+ */
16
+ function createScopedResourcesPreferenceFunctions(getConfigFn, saveConfigFn) {
17
+ return {
18
+ /**
19
+ * @returns {Promise<boolean>}
20
+ */
21
+ async getUseEnvironmentScopedResources() {
22
+ const cfg = await getConfigFn();
23
+ return Boolean(cfg.useEnvironmentScopedResources);
24
+ },
25
+
26
+ /**
27
+ * @param {boolean} value - Activate (true) or passivate (false) user gate
28
+ * @returns {Promise<void>}
29
+ */
30
+ async setUseEnvironmentScopedResources(value) {
31
+ if (typeof value !== 'boolean') {
32
+ throw new Error('useEnvironmentScopedResources must be a boolean');
33
+ }
34
+ const cfg = await getConfigFn();
35
+ cfg.useEnvironmentScopedResources = value;
36
+ await saveConfigFn(cfg);
37
+ }
38
+ };
39
+ }
40
+
41
+ module.exports = { createScopedResourcesPreferenceFunctions };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Interprets deployToController / poll result for CLI messaging (status, errors).
3
+ *
4
+ * @fileoverview Controller pipeline deployment outcome parsing
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ /**
10
+ * @typedef {Object} ControllerDeploymentOutcome
11
+ * @property {boolean} ok - False only for terminal failure-like statuses from controller
12
+ * @property {string|null} statusLabel - Raw status string when present
13
+ * @property {string|null} message - Optional deployment message from API
14
+ * @property {string|null} error - Optional error string from API
15
+ */
16
+
17
+ /**
18
+ * @param {unknown} value - Candidate string field
19
+ * @returns {string|null} Trimmed non-empty string or null
20
+ */
21
+ function nonEmptyTrimmed(value) {
22
+ if (value === undefined || value === null) {
23
+ return null;
24
+ }
25
+ const t = String(value).trim();
26
+ return t ? t : null;
27
+ }
28
+
29
+ /**
30
+ * @param {Object} block - status object from API
31
+ * @returns {string|null}
32
+ */
33
+ function resolveRawStatusLabel(block) {
34
+ if (typeof block.status === 'string') {
35
+ return block.status;
36
+ }
37
+ if (typeof block.deploymentStatus === 'string') {
38
+ return block.deploymentStatus;
39
+ }
40
+ return null;
41
+ }
42
+
43
+ /**
44
+ * @param {Object|null|undefined} result - deployToController return value
45
+ * @returns {ControllerDeploymentOutcome}
46
+ */
47
+ function parseControllerDeploymentOutcome(result) {
48
+ const block = result && result.status && typeof result.status === 'object' ? result.status : null;
49
+ if (!block) {
50
+ return { ok: true, statusLabel: null, message: null, error: null };
51
+ }
52
+ const raw = resolveRawStatusLabel(block);
53
+ const message = nonEmptyTrimmed(block.message);
54
+ const error = nonEmptyTrimmed(block.error);
55
+ if (!raw) {
56
+ return { ok: true, statusLabel: null, message, error };
57
+ }
58
+ const s = raw.toLowerCase();
59
+ const failed = s === 'failed' || s === 'cancelled' || s === 'error';
60
+ return {
61
+ ok: !failed,
62
+ statusLabel: raw,
63
+ message,
64
+ error
65
+ };
66
+ }
67
+
68
+ module.exports = { parseControllerDeploymentOutcome };
@@ -11,9 +11,9 @@ const chalk = require('chalk');
11
11
 
12
12
  /** @type {{ verified: string, pending: string, failed: string, expired: string }} */
13
13
  const STATUS_ICONS = {
14
- verified: ' ',
14
+ verified: ' ',
15
15
  pending: ' ○',
16
- failed: ' ',
16
+ failed: ' ',
17
17
  expired: ' ⊘'
18
18
  };
19
19
 
@@ -92,6 +92,17 @@ function kvPathInferred(segments) {
92
92
  return (namespace && pathVar) ? `kv://${namespace}/${pathVar}` : null;
93
93
  }
94
94
 
95
+ /**
96
+ * Returns the path segment used in kv://&lt;systemKey&gt;/&lt;segment&gt; for a given security key.
97
+ * Uses the same derivation as env key → path (securityKeyToVar + varSegmentsToCamelCase).
98
+ * @param {string} securityKey - Security key (e.g. 'apiKey', 'clientId', 'clientSecret')
99
+ * @returns {string} Canonical path segment (e.g. 'apiKey', 'clientId')
100
+ */
101
+ function getKvPathSegmentForSecurityKey(securityKey) {
102
+ if (!securityKey || typeof securityKey !== 'string') return '';
103
+ return varSegmentsToCamelCase([securityKeyToVar(securityKey)]);
104
+ }
105
+
95
106
  /**
96
107
  * Converts KV_* env key to kv:// path in format kv://&lt;system-key&gt;/&lt;variable&gt;.
97
108
  * System-key uses hyphens (e.g. microsoft-teams); variable is camelCase (e.g. clientId).
@@ -220,7 +231,10 @@ function buildItemsFromEnv(envFilePath, secrets, itemsByKey) {
220
231
  const fromEnv = collectKvEnvVarsAsSecretItems(envMap);
221
232
  for (const { key, value } of fromEnv) {
222
233
  const resolved = resolveKvValue(secrets, value);
223
- if (resolved !== null && resolved !== undefined && isValidKvPath(key)) itemsByKey.set(key, resolved);
234
+ // Skip placeholder: value that equals the kv path (e.g. from env.template) must not be pushed as the secret
235
+ if (resolved !== null && resolved !== undefined && isValidKvPath(key) && resolved.trim() !== key.trim()) {
236
+ itemsByKey.set(key, resolved);
237
+ }
224
238
  }
225
239
  } catch {
226
240
  // Best-effort: continue without .env items
@@ -349,6 +363,7 @@ module.exports = {
349
363
  collectKvRefsFromPayload,
350
364
  pushCredentialSecrets,
351
365
  kvEnvKeyToPath,
366
+ getKvPathSegmentForSecurityKey,
352
367
  systemKeyToKvPrefix,
353
368
  securityKeyToVar,
354
369
  isValidKvPath,
@@ -7,19 +7,20 @@
7
7
  * @version 2.0.0
8
8
  */
9
9
 
10
- const chalk = require('chalk');
11
10
  const logger = require('./logger');
11
+ const { metadata } = require('./cli-test-layout-chalk');
12
12
 
13
13
  /** Message shown when CLI is about to call Dataplane pipeline upload or publish APIs. */
14
14
  const DATAPLANE_PIPELINE_WARNING =
15
15
  'Configuration will be sent to the Dataplane pipeline API. Ensure you are targeting the correct environment and have the required permissions.';
16
16
 
17
17
  /**
18
- * Log the Dataplane pipeline warning (yellow) to the console.
18
+ * Log the Dataplane pipeline notice (non-warning) to the console.
19
19
  * Call before uploadApplicationViaPipeline or publishDatasourceViaPipeline.
20
20
  */
21
21
  function logDataplanePipelineWarning() {
22
- logger.log(chalk.yellow(`⚠ ${DATAPLANE_PIPELINE_WARNING}`));
22
+ // Informational: this is expected behavior for upload/publish flows.
23
+ logger.log(metadata(`Dataplane pipeline: ${DATAPLANE_PIPELINE_WARNING}`));
23
24
  }
24
25
 
25
26
  module.exports = {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @fileoverview Single-capability CLI contract when --capability is set.
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ /**
8
+ * @param {Object|null|undefined} envelope - DatasourceTestRun-like
9
+ * @param {string|undefined|null} requestedCapabilityKey - From CLI --capability
10
+ * @returns {{ violated: boolean, message?: string, count?: number }}
11
+ */
12
+ function analyzeCapabilityScope(envelope, requestedCapabilityKey) {
13
+ const key =
14
+ requestedCapabilityKey !== undefined &&
15
+ requestedCapabilityKey !== null &&
16
+ String(requestedCapabilityKey).trim() !== ''
17
+ ? String(requestedCapabilityKey).trim()
18
+ : '';
19
+ if (!key) {
20
+ return { violated: false };
21
+ }
22
+ const caps =
23
+ envelope && typeof envelope === 'object' && Array.isArray(envelope.capabilities)
24
+ ? envelope.capabilities
25
+ : [];
26
+ if (caps.length <= 1) {
27
+ return { violated: false };
28
+ }
29
+ const keys = caps.map(c =>
30
+ c && c.key !== undefined && c.key !== null ? String(c.key) : '?'
31
+ );
32
+ const preview = keys.slice(0, 8).join(', ');
33
+ const suffix = keys.length > 8 ? ', …' : '';
34
+ return {
35
+ violated: true,
36
+ count: caps.length,
37
+ message: `Capabilities scope: with --capability "${key}", expected a single row in capabilities[]; server returned ${caps.length} (${preview}${suffix}).`
38
+ };
39
+ }
40
+
41
+ module.exports = {
42
+ analyzeCapabilityScope
43
+ };