@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
@@ -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
  ];
@@ -85,6 +87,27 @@ function createHomeAndSecretsPathFunctions(getConfigFn, saveConfigFn) {
85
87
  }
86
88
  await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-home', trimmed, 'Home path must be a non-empty string');
87
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
+ );
110
+ },
88
111
  async getAifabrixSecretsPath() {
89
112
  return getPathConfig(getConfigFn, 'aifabrix-secrets');
90
113
  },
@@ -102,16 +125,60 @@ function createHomeAndSecretsPathFunctions(getConfigFn, saveConfigFn) {
102
125
  };
103
126
  }
104
127
 
105
- /** Default env-config path when aifabrix-env-config is not set (builder schema). */
106
- function getDefaultEnvConfigPath() {
107
- return path.join(__dirname, '..', 'schema', 'env-config.yaml');
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));
108
168
  }
109
169
 
110
170
  function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
111
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
+ */
112
176
  async getAifabrixEnvConfigPath() {
113
177
  const value = await getPathConfig(getConfigFn, 'aifabrix-env-config');
114
- return value || getDefaultEnvConfigPath();
178
+ if (!value || typeof value !== 'string') {
179
+ return null;
180
+ }
181
+ return resolveEnvConfigPathToAbsolute(value, getConfigFn);
115
182
  },
116
183
  async setAifabrixEnvConfigPath(envConfigPath) {
117
184
  if (typeof envConfigPath !== 'string') {
@@ -126,11 +193,64 @@ function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
126
193
  },
127
194
  async getAifabrixBuilderDir() {
128
195
  const envConfigPath = await getPathConfig(getConfigFn, 'aifabrix-env-config');
129
- 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);
130
221
  }
131
222
  };
132
223
  }
133
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
+
134
254
  function createRemoteConfigGetters(getConfigFn) {
135
255
  return {
136
256
  async getRemoteServer() {
@@ -139,6 +259,25 @@ function createRemoteConfigGetters(getConfigFn) {
139
259
  async getDockerEndpoint() {
140
260
  return getPathConfig(getConfigFn, 'docker-endpoint');
141
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
+ },
142
281
  async getUserMutagenFolder() {
143
282
  return getPathConfig(getConfigFn, 'user-mutagen-folder');
144
283
  },
@@ -168,6 +307,24 @@ function createRemoteConfigSetters(getConfigFn, saveConfigFn) {
168
307
  const config = await getConfigFn();
169
308
  config['docker-endpoint'] = value || undefined;
170
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);
171
328
  }
172
329
  };
173
330
  }
@@ -198,7 +355,8 @@ function applySecretsUrlFromRemote(config) {
198
355
  const secretsPath = config['aifabrix-secrets'];
199
356
  if (!remoteServer || !secretsPath || isHttpUrl(secretsPath)) return;
200
357
  const base = typeof remoteServer === 'string' ? remoteServer.trim().replace(/\/+$/, '') : '';
201
- if (base) config['aifabrix-secrets'] = `${base}/api/dev/secrets`;
358
+ if (!base) return;
359
+ config['aifabrix-secrets'] = `${base}/api/dev/secrets`;
202
360
  }
203
361
 
204
362
  function applySyncAndDockerFromHost(config) {
@@ -211,6 +369,7 @@ function applySyncAndDockerFromHost(config) {
211
369
  async function mergeRemoteSettingsImpl(getConfigFn, saveConfigFn, settings) {
212
370
  if (!settings || typeof settings !== 'object') return;
213
371
  const config = await getConfigFn();
372
+ delete config['aifabrix-secrets-path'];
214
373
  for (const key of SETTINGS_RESPONSE_KEYS) {
215
374
  const raw = settings[key];
216
375
  if (raw === undefined || raw === null) continue;
@@ -257,7 +416,7 @@ module.exports = {
257
416
  getPathConfig,
258
417
  setPathConfig,
259
418
  createPathConfigFunctions,
260
- getDefaultEnvConfigPath,
419
+ resolveEnvConfigPathToAbsolute,
261
420
  SETTINGS_RESPONSE_KEYS
262
421
  };
263
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 };
@@ -10,7 +10,7 @@
10
10
  const path = require('path');
11
11
  const fs = require('fs');
12
12
  const { getIntegrationPath } = require('./paths');
13
- const { parseEnvToMap, resolveKvValue } = require('./credential-secrets-env');
13
+ const { parseEnvToMap } = require('./credential-secrets-env');
14
14
  const { loadSecrets, resolveKvReferences } = require('../core/secrets');
15
15
  const { loadEnvTemplate } = require('./secrets-helpers');
16
16
  const { getActualSecretsPath } = require('./secrets-path');
@@ -81,13 +81,13 @@ function substituteVarPlaceholders(value, envMap, systemKey) {
81
81
 
82
82
  /**
83
83
  * Resolves configuration array values in place by location: variable → {{VAR}} from envMap;
84
- * keyvault → kv:// from secrets. Does not log or expose secret values.
84
+ * keyvault → leaves kv:// as-is (secret value pushed separately by CLI). Does not log or expose secret values.
85
85
  *
86
86
  * @param {Array<{ name?: string, value?: string, location?: string }>} configArray - Configuration array (mutated)
87
87
  * @param {Object.<string, string>} envMap - Resolved env map from buildResolvedEnvMapForIntegration
88
- * @param {Object} secrets - Loaded secrets for kv:// resolution
88
+ * @param {Object} secrets - Loaded secrets (unused for keyvault config values; kept for backward compatibility)
89
89
  * @param {string} [systemKey] - System key for error messages
90
- * @throws {Error} If variable env is missing or keyvault secret unresolved (message never contains secret values)
90
+ * @throws {Error} If variable env is missing or keyvault value is not a kv:// reference (message never contains secret values)
91
91
  */
92
92
  function resolveConfigurationValues(configArray, envMap, secrets, systemKey) {
93
93
  if (!Array.isArray(configArray)) return;
@@ -101,11 +101,14 @@ function resolveConfigurationValues(configArray, envMap, secrets, systemKey) {
101
101
  }
102
102
  item.value = substituteVarPlaceholders(item.value, envMap, systemKey);
103
103
  } else if (location === 'keyvault') {
104
- const resolved = resolveKvValue(secrets, item.value);
105
- if (resolved === null || resolved === undefined) {
106
- throw new Error(`Unresolved keyvault reference for configuration '${item.name || 'unknown'}'.${hint}`);
104
+ if (!item.value.trim().startsWith('kv://')) {
105
+ throw new Error(
106
+ `Configuration entry '${item.name || 'unknown'}' has location 'keyvault' but value is not kv://. ` +
107
+ `Set value to a kv:// reference and push the secret with KV_* env vars.${hint}`
108
+ );
107
109
  }
108
- item.value = resolved;
110
+ // Intentionally do not resolve kv:// here. Upload keeps kv:// references in config and
111
+ // pushes secret values separately via the credential secret API.
109
112
  }
110
113
  }
111
114
  }
@@ -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
 
@@ -132,7 +132,7 @@ function kvEnvKeyToPath(envKey, systemKey) {
132
132
  * @param {Object.<string, string>} envMap - Key-value map from .env
133
133
  * @returns {Array<{ key: string, value: string }>} Items (key = kv://..., value = raw)
134
134
  */
135
- function collectKvEnvVarsAsSecretItems(envMap) {
135
+ function collectKvEnvVarsAsSecretItems(envMap, systemKey) {
136
136
  if (!envMap || typeof envMap !== 'object') {
137
137
  return [];
138
138
  }
@@ -144,7 +144,7 @@ function collectKvEnvVarsAsSecretItems(envMap) {
144
144
  if (value.startsWith('kv://') && isValidKvPath(value)) {
145
145
  kvPath = value;
146
146
  }
147
- if (!kvPath) kvPath = kvEnvKeyToPath(envKey);
147
+ if (!kvPath) kvPath = kvEnvKeyToPath(envKey, systemKey) || kvEnvKeyToPath(envKey);
148
148
  if (!kvPath) continue;
149
149
  items.push({ key: kvPath, value });
150
150
  }
@@ -223,12 +223,12 @@ function isValidKvPath(key) {
223
223
  * @param {Object} secrets - Loaded secrets
224
224
  * @param {Map<string, string>} itemsByKey - Mutable map to add items to
225
225
  */
226
- function buildItemsFromEnv(envFilePath, secrets, itemsByKey) {
226
+ function buildItemsFromEnv(envFilePath, secrets, itemsByKey, systemKey) {
227
227
  if (!envFilePath || typeof envFilePath !== 'string' || !fs.existsSync(envFilePath)) return;
228
228
  try {
229
229
  const content = fs.readFileSync(envFilePath, 'utf8');
230
230
  const envMap = parseEnvToMap(content);
231
- const fromEnv = collectKvEnvVarsAsSecretItems(envMap);
231
+ const fromEnv = collectKvEnvVarsAsSecretItems(envMap, systemKey);
232
232
  for (const { key, value } of fromEnv) {
233
233
  const resolved = resolveKvValue(secrets, value);
234
234
  // Skip placeholder: value that equals the kv path (e.g. from env.template) must not be pushed as the secret
@@ -320,7 +320,7 @@ async function pushCredentialSecrets(dataplaneUrl, authConfig, options = {}) {
320
320
  secrets = {};
321
321
  }
322
322
  const itemsByKey = new Map();
323
- buildItemsFromEnv(envFilePath, secrets, itemsByKey);
323
+ buildItemsFromEnv(envFilePath, secrets, itemsByKey, appName);
324
324
  buildItemsFromPayload(payload, secrets, itemsByKey);
325
325
 
326
326
  const items = Array.from(itemsByKey.entries())
@@ -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
+ };