@aifabrix/builder 2.43.0 → 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 (346) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/README.md +1 -1
  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 +31 -0
  7. package/integration/hubspot-test/create-hubspot.js +5 -5
  8. package/integration/hubspot-test/hubspot-test-datasource-company.json +58 -462
  9. package/integration/hubspot-test/hubspot-test-datasource-contact.json +61 -555
  10. package/integration/hubspot-test/hubspot-test-datasource-deal.json +63 -506
  11. package/integration/hubspot-test/hubspot-test-datasource-users.json +42 -83
  12. package/integration/hubspot-test/hubspot-test-deploy.json +3 -3
  13. package/integration/hubspot-test/test-dataplane-down-tests.js +1 -7
  14. package/integration/hubspot-test/test-dataplane-down.js +3 -3
  15. package/integration/hubspot-test/test.js +35 -43
  16. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  17. package/integration/roundtrip-test-local/README.md +144 -0
  18. package/integration/roundtrip-test-local/application.yaml +13 -0
  19. package/integration/roundtrip-test-local/env.template +15 -0
  20. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  21. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  22. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  23. package/integration/roundtrip-test-local2/README.md +144 -0
  24. package/integration/roundtrip-test-local2/application.yaml +13 -0
  25. package/integration/roundtrip-test-local2/env.template +15 -0
  26. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  27. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  28. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  29. package/integration/test/wizard.yaml +8 -0
  30. package/jest.config.default.js +10 -0
  31. package/jest.config.integration.fixtures.js +22 -0
  32. package/jest.config.integration.js +21 -18
  33. package/jest.config.isolated.js +10 -0
  34. package/jest.projects.js +288 -0
  35. package/lib/api/datasources-core.api.js +3 -3
  36. package/lib/api/dev-mtls-request.js +110 -0
  37. package/lib/api/dev-server-https.js +145 -0
  38. package/lib/api/dev.api.js +133 -144
  39. package/lib/api/index.js +0 -1
  40. package/lib/api/pipeline.api.js +67 -20
  41. package/lib/api/types/dev.types.js +4 -3
  42. package/lib/api/types/pipeline.types.js +8 -5
  43. package/lib/api/types/validation-run.types.js +56 -0
  44. package/lib/api/validation-run.api.js +99 -0
  45. package/lib/api/validation-runner.js +99 -0
  46. package/lib/app/config.js +1 -1
  47. package/lib/app/deploy-status-display.js +2 -2
  48. package/lib/app/deploy.js +7 -6
  49. package/lib/app/display.js +2 -1
  50. package/lib/app/dockerfile.js +3 -2
  51. package/lib/app/down.js +2 -1
  52. package/lib/app/helpers.js +6 -5
  53. package/lib/app/index.js +27 -8
  54. package/lib/app/list.js +7 -6
  55. package/lib/app/push.js +4 -3
  56. package/lib/app/register.js +16 -7
  57. package/lib/app/rotate-secret.js +14 -13
  58. package/lib/app/run-container-start.js +184 -0
  59. package/lib/app/run-docker-fallback.js +108 -0
  60. package/lib/app/run-env-compose.js +30 -42
  61. package/lib/app/run-helpers.js +49 -126
  62. package/lib/app/run-infra-requirements.js +30 -0
  63. package/lib/app/run-resolve-image.js +21 -0
  64. package/lib/app/run.js +74 -21
  65. package/lib/app/show-display.js +1 -1
  66. package/lib/app/show.js +1 -1
  67. package/lib/build/index.js +13 -10
  68. package/lib/cli/index.js +2 -0
  69. package/lib/cli/setup-app.help.js +67 -0
  70. package/lib/cli/setup-app.js +57 -121
  71. package/lib/cli/setup-app.test-commands.js +179 -0
  72. package/lib/cli/setup-auth.js +19 -5
  73. package/lib/cli/setup-credential-deployment.js +22 -8
  74. package/lib/cli/setup-dev-path-commands.js +124 -0
  75. package/lib/cli/setup-dev.js +170 -113
  76. package/lib/cli/setup-environment.js +7 -1
  77. package/lib/cli/setup-external-system.js +62 -22
  78. package/lib/cli/setup-infra.js +126 -47
  79. package/lib/cli/setup-parameters.js +32 -0
  80. package/lib/cli/setup-secrets.js +106 -8
  81. package/lib/cli/setup-service-user.js +1 -1
  82. package/lib/cli/setup-utility.js +36 -20
  83. package/lib/commands/app-down.js +5 -7
  84. package/lib/commands/app-install.js +14 -7
  85. package/lib/commands/app-logs.js +13 -10
  86. package/lib/commands/app-shell.js +4 -1
  87. package/lib/commands/app-test.js +25 -19
  88. package/lib/commands/app.js +22 -10
  89. package/lib/commands/auth-config.js +6 -6
  90. package/lib/commands/auth-status.js +4 -3
  91. package/lib/commands/credential-env.js +4 -3
  92. package/lib/commands/credential-list.js +5 -4
  93. package/lib/commands/credential-push.js +4 -3
  94. package/lib/commands/datasource-unified-test-cli.js +495 -0
  95. package/lib/commands/datasource-unified-test-cli.options.js +149 -0
  96. package/lib/commands/datasource-validation-cli.js +129 -0
  97. package/lib/commands/datasource.js +105 -98
  98. package/lib/commands/deployment-list.js +6 -5
  99. package/lib/commands/dev-cli-handlers.js +122 -18
  100. package/lib/commands/dev-down.js +4 -3
  101. package/lib/commands/dev-init.js +231 -116
  102. package/lib/commands/dev-show-display.js +473 -0
  103. package/lib/commands/login-credentials.js +3 -2
  104. package/lib/commands/login-device.js +4 -3
  105. package/lib/commands/login.js +5 -4
  106. package/lib/commands/logout.js +8 -7
  107. package/lib/commands/parameters-validate.js +54 -0
  108. package/lib/commands/repair-datasource.js +314 -68
  109. package/lib/commands/repair-env-template.js +2 -2
  110. package/lib/commands/repair.js +21 -3
  111. package/lib/commands/secrets-list.js +23 -12
  112. package/lib/commands/secrets-remove-all.js +220 -0
  113. package/lib/commands/secrets-remove.js +21 -12
  114. package/lib/commands/secrets-set.js +21 -12
  115. package/lib/commands/secrets-validate.js +4 -4
  116. package/lib/commands/secure.js +10 -9
  117. package/lib/commands/service-user.js +26 -25
  118. package/lib/commands/test-e2e-external.js +27 -1
  119. package/lib/commands/up-common.js +3 -2
  120. package/lib/commands/up-dataplane.js +29 -16
  121. package/lib/commands/up-miso.js +19 -29
  122. package/lib/commands/upload.js +138 -39
  123. package/lib/commands/wizard-core-helpers.js +1 -1
  124. package/lib/commands/wizard-dataplane.js +4 -3
  125. package/lib/commands/wizard-helpers.js +3 -3
  126. package/lib/commands/wizard.js +2 -2
  127. package/lib/core/admin-secrets.js +14 -5
  128. package/lib/core/audit-logger.js +12 -4
  129. package/lib/core/config-attach-extensions.js +46 -0
  130. package/lib/core/config-runtime-paths.js +29 -0
  131. package/lib/core/config.js +55 -56
  132. package/lib/core/diff.js +3 -2
  133. package/lib/core/ensure-encryption-key.js +1 -1
  134. package/lib/core/secrets-ensure-infra.js +77 -0
  135. package/lib/core/secrets-ensure.js +120 -64
  136. package/lib/core/secrets-env-write.js +35 -7
  137. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  138. package/lib/core/secrets.js +200 -37
  139. package/lib/core/templates-env.js +4 -3
  140. package/lib/datasource/abac-validator.js +1 -10
  141. package/lib/datasource/deploy.js +75 -53
  142. package/lib/datasource/field-reference-validator.js +9 -6
  143. package/lib/datasource/integration-context.js +63 -0
  144. package/lib/datasource/list.js +8 -7
  145. package/lib/datasource/log-viewer.js +84 -53
  146. package/lib/datasource/resolve-app.js +4 -4
  147. package/lib/datasource/test-e2e.js +95 -146
  148. package/lib/datasource/test-integration.js +114 -122
  149. package/lib/datasource/unified-validation-run-body.js +65 -0
  150. package/lib/datasource/unified-validation-run-post.js +23 -0
  151. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  152. package/lib/datasource/unified-validation-run.js +92 -0
  153. package/lib/datasource/validate.js +157 -13
  154. package/lib/deployment/deployer.js +4 -3
  155. package/lib/deployment/environment.js +7 -6
  156. package/lib/deployment/push.js +17 -8
  157. package/lib/external-system/delete.js +4 -3
  158. package/lib/external-system/deploy.js +131 -53
  159. package/lib/external-system/download-helpers.js +1 -1
  160. package/lib/external-system/download.js +7 -6
  161. package/lib/external-system/generator.js +92 -6
  162. package/lib/external-system/integration-test-dispatch.js +26 -0
  163. package/lib/external-system/test-execution.js +5 -1
  164. package/lib/external-system/test-helpers.js +0 -4
  165. package/lib/external-system/test-system-level-helpers.js +110 -0
  166. package/lib/external-system/test-system-level.js +83 -44
  167. package/lib/external-system/test.js +59 -8
  168. package/lib/generator/builders.js +23 -11
  169. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  170. package/lib/generator/external.js +16 -4
  171. package/lib/generator/helpers.js +58 -3
  172. package/lib/generator/index.js +4 -0
  173. package/lib/generator/split-readme.js +12 -7
  174. package/lib/generator/split-variables.js +2 -1
  175. package/lib/generator/split.js +1 -1
  176. package/lib/generator/wizard-readme.js +3 -3
  177. package/lib/generator/wizard.js +8 -8
  178. package/lib/infrastructure/compose.js +60 -6
  179. package/lib/infrastructure/helpers.js +201 -29
  180. package/lib/infrastructure/index.js +28 -17
  181. package/lib/infrastructure/services.js +21 -15
  182. package/lib/internal/fs-real-sync.js +104 -0
  183. package/lib/internal/node-fs.js +98 -0
  184. package/lib/parameters/database-secret-values.js +173 -0
  185. package/lib/parameters/infra-kv-discovery.js +121 -0
  186. package/lib/parameters/infra-parameter-catalog.js +458 -0
  187. package/lib/parameters/infra-parameter-validate.js +64 -0
  188. package/lib/schema/application-schema.json +37 -17
  189. package/lib/schema/datasource-test-run.schema.json +493 -0
  190. package/lib/schema/deployment-rules.yaml +102 -63
  191. package/lib/schema/external-datasource.schema.json +1200 -442
  192. package/lib/schema/external-system.schema.json +181 -5
  193. package/lib/schema/flag-map-validation-run.json +31 -0
  194. package/lib/schema/infra-parameter.schema.json +106 -0
  195. package/lib/schema/infra.parameter.yaml +421 -0
  196. package/lib/schema/type/credential-auth-templates.json +40 -0
  197. package/lib/schema/type/document-storage.json +213 -0
  198. package/lib/schema/type/message-service.json +123 -0
  199. package/lib/schema/type/vector-store.json +88 -0
  200. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  201. package/lib/utils/api-error-handler.js +2 -2
  202. package/lib/utils/api.js +49 -14
  203. package/lib/utils/app-register-api.js +3 -2
  204. package/lib/utils/app-register-auth.js +1 -1
  205. package/lib/utils/app-register-config.js +4 -4
  206. package/lib/utils/app-register-display.js +3 -2
  207. package/lib/utils/app-register-validator.js +3 -2
  208. package/lib/utils/app-run-containers.js +26 -22
  209. package/lib/utils/app-scoped-config.js +31 -0
  210. package/lib/utils/app-service-env-from-builder.js +164 -0
  211. package/lib/utils/build-copy.js +1 -1
  212. package/lib/utils/build-helpers.js +20 -20
  213. package/lib/utils/build-resolve-image.js +165 -0
  214. package/lib/utils/cli-layout-chalk.js +8 -0
  215. package/lib/utils/cli-test-layout-chalk.js +267 -0
  216. package/lib/utils/cli-utils.js +88 -11
  217. package/lib/utils/compose-db-passwords.js +138 -0
  218. package/lib/utils/compose-generate-docker-compose.js +216 -0
  219. package/lib/utils/compose-generator.js +197 -291
  220. package/lib/utils/compose-miso-env.js +18 -0
  221. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  222. package/lib/utils/config-paths.js +166 -7
  223. package/lib/utils/config-scoped-resources-preference.js +41 -0
  224. package/lib/utils/controller-deployment-outcome.js +68 -0
  225. package/lib/utils/credential-display.js +2 -2
  226. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  227. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  228. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  229. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  230. package/lib/utils/datasource-test-run-display.js +442 -0
  231. package/lib/utils/datasource-test-run-exit.js +58 -0
  232. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  233. package/lib/utils/datasource-test-run-report-version.js +51 -0
  234. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  235. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  236. package/lib/utils/datasource-validation-watch.js +266 -0
  237. package/lib/utils/declarative-url-ports.js +47 -0
  238. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  239. package/lib/utils/dev-ca-install.js +185 -23
  240. package/lib/utils/dev-cert-helper.js +266 -17
  241. package/lib/utils/dev-hosts-helper.js +307 -0
  242. package/lib/utils/dev-init-cert-hints.js +37 -0
  243. package/lib/utils/dev-init-health-messages.js +52 -0
  244. package/lib/utils/dev-init-resolve.js +86 -0
  245. package/lib/utils/dev-init-ssh-merge.js +65 -0
  246. package/lib/utils/dev-ssh-config-helper.js +196 -0
  247. package/lib/utils/dev-user-groups.js +93 -0
  248. package/lib/utils/docker-build.js +42 -17
  249. package/lib/utils/docker-exec.js +28 -0
  250. package/lib/utils/docker-manifest-public-port.js +116 -0
  251. package/lib/utils/docker-not-running-hint.js +52 -0
  252. package/lib/utils/docker.js +98 -11
  253. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  254. package/lib/utils/env-config-loader.js +10 -91
  255. package/lib/utils/env-copy.js +19 -10
  256. package/lib/utils/env-map.js +35 -8
  257. package/lib/utils/env-template.js +2 -2
  258. package/lib/utils/environment-scoped-resources.js +144 -0
  259. package/lib/utils/error-formatter.js +92 -13
  260. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  261. package/lib/utils/error-formatters/network-errors.js +2 -1
  262. package/lib/utils/error-formatters/permission-errors.js +2 -1
  263. package/lib/utils/error-formatters/validation-errors.js +2 -1
  264. package/lib/utils/external-readme.js +8 -1
  265. package/lib/utils/external-system-display.js +234 -136
  266. package/lib/utils/external-system-local-test-tty.js +389 -0
  267. package/lib/utils/external-system-readiness-core.js +377 -0
  268. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  269. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  270. package/lib/utils/external-system-readiness-display.js +186 -0
  271. package/lib/utils/external-system-test-helpers.js +24 -6
  272. package/lib/utils/external-system-validators.js +30 -12
  273. package/lib/utils/health-check-url.js +119 -0
  274. package/lib/utils/health-check.js +59 -25
  275. package/lib/utils/help-builder.js +11 -8
  276. package/lib/utils/image-version.js +4 -8
  277. package/lib/utils/infra-containers.js +4 -7
  278. package/lib/utils/infra-env-defaults.js +162 -0
  279. package/lib/utils/infra-status-display.js +167 -0
  280. package/lib/utils/infra-status.js +16 -8
  281. package/lib/utils/local-secrets.js +3 -4
  282. package/lib/utils/paths.js +134 -47
  283. package/lib/utils/port-resolver.js +10 -23
  284. package/lib/utils/redis-env-scope.js +62 -0
  285. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  286. package/lib/utils/remote-builder-validation.js +99 -0
  287. package/lib/utils/remote-dev-auth.js +117 -21
  288. package/lib/utils/remote-docker-env.js +67 -15
  289. package/lib/utils/remote-secrets-loader.js +13 -4
  290. package/lib/utils/resolve-docker-image-ref.js +124 -0
  291. package/lib/utils/schema-loader.js +22 -9
  292. package/lib/utils/secrets-bash-kv.js +25 -0
  293. package/lib/utils/secrets-generator.js +169 -49
  294. package/lib/utils/secrets-helpers.js +70 -59
  295. package/lib/utils/secrets-kv-scope.js +60 -0
  296. package/lib/utils/secrets-utils.js +32 -38
  297. package/lib/utils/secrets-validation.js +3 -1
  298. package/lib/utils/secrets-yaml-preserve.js +109 -0
  299. package/lib/utils/ssh-key-helper.js +4 -2
  300. package/lib/utils/template-helpers.js +2 -2
  301. package/lib/utils/test-log-writer.js +3 -3
  302. package/lib/utils/token-manager.js +1 -2
  303. package/lib/utils/url-declarative-public-base.js +188 -0
  304. package/lib/utils/url-declarative-resolve-build.js +493 -0
  305. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  306. package/lib/utils/url-declarative-resolve.js +220 -0
  307. package/lib/utils/url-declarative-token-parse.js +74 -0
  308. package/lib/utils/url-declarative-url-flags.js +50 -0
  309. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  310. package/lib/utils/url-public-path-prefix.js +34 -0
  311. package/lib/utils/urls-local-registry.js +220 -0
  312. package/lib/utils/validation-report-tty-kit.js +77 -0
  313. package/lib/utils/validation-run-poll.js +89 -0
  314. package/lib/utils/validation-run-post-retry.js +73 -0
  315. package/lib/utils/validation-run-request.js +98 -0
  316. package/lib/utils/variable-transformer.js +21 -4
  317. package/lib/utils/yaml-preserve.js +33 -14
  318. package/lib/validation/datasource-warnings.js +56 -0
  319. package/lib/validation/env-template-auth.js +1 -1
  320. package/lib/validation/external-manifest-validator.js +27 -7
  321. package/lib/validation/validate-display.js +37 -31
  322. package/lib/validation/validate.js +4 -13
  323. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  324. package/lib/validation/validator.js +22 -65
  325. package/lib/validation/wizard-config-validator.js +2 -1
  326. package/package.json +7 -3
  327. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  328. package/scripts/diagnose-cli.js +150 -0
  329. package/scripts/install-local.js +304 -55
  330. package/templates/README.md +15 -2
  331. package/templates/applications/dataplane/application.yaml +52 -2
  332. package/templates/applications/dataplane/env.template +75 -17
  333. package/templates/applications/dataplane/rbac.yaml +8 -0
  334. package/templates/applications/keycloak/application.yaml +9 -1
  335. package/templates/applications/keycloak/env.template +15 -6
  336. package/templates/applications/miso-controller/application.yaml +10 -2
  337. package/templates/applications/miso-controller/env.template +42 -12
  338. package/templates/applications/miso-controller/rbac.yaml +5 -0
  339. package/templates/external-system/README.md.hbs +20 -7
  340. package/templates/external-system/deploy.js.hbs +5 -5
  341. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  342. package/templates/infra/compose.yaml.hbs +20 -4
  343. package/templates/python/docker-compose.hbs +16 -0
  344. package/templates/typescript/docker-compose.hbs +16 -0
  345. package/lib/api/external-test.api.js +0 -111
  346. package/lib/schema/env-config.yaml +0 -60
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Resolve Docker repository path and tag from application config and optional CLI overrides.
3
+ * Precedence: --image (full ref), --registry CLI, image.registry in manifest, else unqualified name.
4
+ *
5
+ * For refs like localhost:5000/repo without an explicit tag, prefer --image with :tag (parse ambiguity).
6
+ *
7
+ * @fileoverview Shared Docker image reference resolution for run, compose, and version checks
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const { parseImageOverride } = require('./parse-image-ref');
15
+
16
+ /**
17
+ * Repository path without host (same rules as compose-generator getImageName).
18
+ * @param {Object} appConfig - Application configuration
19
+ * @param {string} appName - Application name fallback
20
+ * @returns {string}
21
+ */
22
+ function getRepositoryPathFromConfig(appConfig, appName) {
23
+ if (!appConfig || typeof appConfig !== 'object') {
24
+ return appName;
25
+ }
26
+ if (typeof appConfig.image === 'string') {
27
+ return appConfig.image.split(':')[0];
28
+ }
29
+ if (appConfig.image?.name) {
30
+ return appConfig.image.name;
31
+ }
32
+ if (appConfig.app?.key) {
33
+ return appConfig.app.key;
34
+ }
35
+ return appName;
36
+ }
37
+
38
+ /**
39
+ * @param {Object} [appConfig]
40
+ * @returns {string}
41
+ */
42
+ function imageTagFromConfig(appConfig) {
43
+ return (appConfig && appConfig.image && appConfig.image.tag) || 'latest';
44
+ }
45
+
46
+ /**
47
+ * Trim and strip trailing slashes from a registry host/prefix. Empty/whitespace → ''.
48
+ * @param {string|undefined|null|number} registry - Registry host or prefix
49
+ * @returns {string}
50
+ */
51
+ function normalizeDockerRegistryPrefix(registry) {
52
+ if (registry === null || registry === undefined) {
53
+ return '';
54
+ }
55
+ if (typeof registry !== 'string') {
56
+ return normalizeDockerRegistryPrefix(String(registry));
57
+ }
58
+ const t = registry.trim();
59
+ if (!t) {
60
+ return '';
61
+ }
62
+ return t.replace(/\/+$/, '');
63
+ }
64
+
65
+ /**
66
+ * Effective image repository (may include registry prefix) and tag for Docker.
67
+ * @param {string} appName - Application name
68
+ * @param {Object} appConfig - Loaded application manifest
69
+ * @param {Object} [runOptions] - Run/deploy options
70
+ * @param {string} [runOptions.image] - Full image ref override
71
+ * @param {string} [runOptions.registry] - CLI registry prefix (wins over manifest)
72
+ * @returns {{ imageName: string, imageTag: string }}
73
+ */
74
+ function resolveDockerImageRef(appName, appConfig, runOptions = {}) {
75
+ const opts = runOptions || {};
76
+ if (opts.image) {
77
+ const parsed = parseImageOverride(opts.image);
78
+ return {
79
+ imageName: parsed ? parsed.name : getRepositoryPathFromConfig(appConfig, appName),
80
+ imageTag: parsed ? parsed.tag : imageTagFromConfig(appConfig)
81
+ };
82
+ }
83
+
84
+ const baseRepo = getRepositoryPathFromConfig(appConfig, appName);
85
+ const imageTag = imageTagFromConfig(appConfig);
86
+ const prefix =
87
+ normalizeDockerRegistryPrefix(opts.registry) ||
88
+ normalizeDockerRegistryPrefix(appConfig?.image?.registry ?? '');
89
+ if (prefix) {
90
+ return { imageName: `${prefix}/${baseRepo}`, imageTag };
91
+ }
92
+ return { imageName: baseRepo, imageTag };
93
+ }
94
+
95
+ /**
96
+ * Full image string for compose when manifest/CLI registry applies; else null (use template defaults).
97
+ * @param {string} appName - Application name
98
+ * @param {Object} appConfig - Application configuration
99
+ * @param {Object} [options] - Run options (image, imageOverride, tag, registry)
100
+ * @returns {string|null}
101
+ */
102
+ function resolveComposeImageOverrideString(appName, appConfig, options = {}) {
103
+ if (options.image) return options.image;
104
+ if (options.imageOverride) return options.imageOverride;
105
+ const runOpts = { registry: options.registry, image: undefined };
106
+ if (options.tag) {
107
+ const { imageName } = resolveDockerImageRef(appName, appConfig, runOpts);
108
+ return `${imageName}:${options.tag}`;
109
+ }
110
+ const { imageName, imageTag } = resolveDockerImageRef(appName, appConfig, runOpts);
111
+ const shortName = getRepositoryPathFromConfig(appConfig, appName);
112
+ const shortTag = imageTagFromConfig(appConfig);
113
+ if (imageName === shortName && imageTag === shortTag) {
114
+ return null;
115
+ }
116
+ return `${imageName}:${imageTag}`;
117
+ }
118
+
119
+ module.exports = {
120
+ resolveDockerImageRef,
121
+ resolveComposeImageOverrideString,
122
+ normalizeDockerRegistryPrefix,
123
+ getRepositoryPathFromConfig
124
+ };
@@ -9,9 +9,10 @@
9
9
  * @version 2.0.0
10
10
  */
11
11
 
12
- const fs = require('fs');
12
+ const fsRealSync = require('../internal/fs-real-sync');
13
13
  const path = require('path');
14
14
  const Ajv = require('ajv');
15
+ const addFormats = require('ajv-formats');
15
16
 
16
17
  // Cache for compiled validators
17
18
  // These are reset when module is reloaded (for testing)
@@ -46,11 +47,14 @@ function loadExternalSystemSchema() {
46
47
 
47
48
  const schemaPath = path.join(__dirname, '..', 'schema', 'external-system.schema.json');
48
49
 
49
- if (!fs.existsSync(schemaPath)) {
50
- throw new Error(`External system schema not found: ${schemaPath}`);
50
+ if (!fsRealSync.existsSync(schemaPath)) {
51
+ throw new Error(
52
+ `External system schema not found: ${schemaPath}. ` +
53
+ 'Ensure the file exists (tracked under lib/schema/); run git checkout HEAD -- lib/schema/external-system.schema.json if your tree is incomplete.'
54
+ );
51
55
  }
52
56
 
53
- const schemaContent = fs.readFileSync(schemaPath, 'utf8');
57
+ const schemaContent = fsRealSync.readFileSync(schemaPath, 'utf8');
54
58
  let schema;
55
59
 
56
60
  try {
@@ -60,6 +64,7 @@ function loadExternalSystemSchema() {
60
64
  }
61
65
 
62
66
  const ajv = new Ajv({ allErrors: true, strict: false });
67
+ addFormats(ajv);
63
68
  externalSystemValidator = ajv.compile(schema);
64
69
 
65
70
  return externalSystemValidator;
@@ -84,11 +89,14 @@ function loadExternalDataSourceSchema() {
84
89
 
85
90
  const schemaPath = path.join(__dirname, '..', 'schema', 'external-datasource.schema.json');
86
91
 
87
- if (!fs.existsSync(schemaPath)) {
88
- throw new Error(`External datasource schema not found: ${schemaPath}`);
92
+ if (!fsRealSync.existsSync(schemaPath)) {
93
+ throw new Error(
94
+ `External datasource schema not found: ${schemaPath}. ` +
95
+ 'Ensure the file exists (tracked under lib/schema/); run git checkout HEAD -- lib/schema/external-datasource.schema.json if your tree is incomplete.'
96
+ );
89
97
  }
90
98
 
91
- const schemaContent = fs.readFileSync(schemaPath, 'utf8');
99
+ const schemaContent = fsRealSync.readFileSync(schemaPath, 'utf8');
92
100
  let schema;
93
101
 
94
102
  try {
@@ -106,6 +114,11 @@ function loadExternalDataSourceSchema() {
106
114
  }
107
115
 
108
116
  const ajv = new Ajv({ allErrors: true, strict: false, strictSchema: false });
117
+ addFormats(ajv);
118
+ // external-datasource.schema.json references these by $id (aifabrix://schema/type/*)
119
+ ajv.addSchema(require('../schema/type/document-storage.json'));
120
+ ajv.addSchema(require('../schema/type/message-service.json'));
121
+ ajv.addSchema(require('../schema/type/vector-store.json'));
109
122
  externalDataSourceValidator = ajv.compile(schemaToCompile);
110
123
 
111
124
  return externalDataSourceValidator;
@@ -250,10 +263,10 @@ function readAndParseFileContent(filePath, content) {
250
263
  let fileContent = content;
251
264
 
252
265
  if (!fileContent) {
253
- if (!fs.existsSync(filePath)) {
266
+ if (!fsRealSync.existsSync(filePath)) {
254
267
  throw new Error(`File not found: ${filePath}`);
255
268
  }
256
- fileContent = fs.readFileSync(filePath, 'utf8');
269
+ fileContent = fsRealSync.readFileSync(filePath, 'utf8');
257
270
  }
258
271
 
259
272
  try {
@@ -0,0 +1,25 @@
1
+ /**
2
+ * kv://BASH_<NAME> fallback: use process.env.<NAME>, then process.env.BASH_<NAME> (shared BASH_ keys).
3
+ * @fileoverview
4
+ */
5
+ 'use strict';
6
+
7
+ /**
8
+ * @param {string} pathStr - Flat kv path (no slashes)
9
+ * @returns {string|undefined}
10
+ */
11
+ function resolveBashKvFromProcessEnv(pathStr) {
12
+ if (!pathStr || typeof pathStr !== 'string' || pathStr.includes('/')) return undefined;
13
+ if (!pathStr.startsWith('BASH_')) return undefined;
14
+ const suffix = pathStr.slice(5);
15
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(suffix)) return undefined;
16
+ const pick = k => {
17
+ const raw = process.env[k];
18
+ if (raw === undefined || raw === null) return undefined;
19
+ const t = String(raw).trim();
20
+ return t.length > 0 ? t : undefined;
21
+ };
22
+ return pick(suffix) ?? pick(pathStr);
23
+ }
24
+
25
+ module.exports = { resolveBashKvFromProcessEnv };
@@ -16,6 +16,17 @@ const yaml = require('js-yaml');
16
16
  const crypto = require('crypto');
17
17
  const logger = require('./logger');
18
18
  const pathsUtil = require('./paths');
19
+ const { mergeFlatSecretsYamlPreservingComments } = require('./secrets-yaml-preserve');
20
+ const {
21
+ generateDatabasePasswordValueForKey,
22
+ generateDatabaseUrlValueForKey,
23
+ parseDatabaseSecretKey
24
+ } = require('../parameters/database-secret-values');
25
+
26
+ /** Lazy require so tests can jest.spyOn getInfraParameterCatalog after module load. */
27
+ function infraParameterCatalogModule() {
28
+ return require('../parameters/infra-parameter-catalog');
29
+ }
19
30
 
20
31
  /**
21
32
  * Parse key-value pairs from YAML-like lines (last occurrence wins per key).
@@ -44,7 +55,9 @@ function parseYamlKeyValueLines(content) {
44
55
  /**
45
56
  * Parse YAML content tolerating duplicate keys (last occurrence wins).
46
57
  * Use for secrets files that may have been appended to repeatedly.
47
- * Tries yaml.load first; on "duplicate key" error falls back to line-by-line parse.
58
+ * Tries yaml.load first; on duplicate-key errors or other parse failures (e.g. `{}` on line 1
59
+ * then `key: value` lines — invalid multi-document without `---`) falls back to line-by-line
60
+ * parse for flat key: value secrets files.
48
61
  *
49
62
  * @param {string} content - Raw YAML content
50
63
  * @returns {Object} Parsed object (last value wins for duplicate keys)
@@ -56,13 +69,11 @@ function loadYamlTolerantOfDuplicateKeys(content) {
56
69
  try {
57
70
  const parsed = yaml.load(content);
58
71
  return parsed && typeof parsed === 'object' ? parsed : {};
59
- } catch (err) {
60
- const msg = err.message || '';
61
- if (!msg.includes('duplicate') && !msg.includes('duplicated mapping')) {
62
- throw err;
63
- }
72
+ } catch {
73
+ // Duplicate keys, `{}` + appended YAML (invalid multi-doc), or other bad merges —
74
+ // flat secrets files recover via line parse (last key wins).
75
+ return parseYamlKeyValueLines(content);
64
76
  }
65
- return parseYamlKeyValueLines(content);
66
77
  }
67
78
 
68
79
  /**
@@ -104,18 +115,37 @@ function findMissingSecretKeys(envTemplate, existingSecrets) {
104
115
  return missingKeys;
105
116
  }
106
117
 
118
+ /**
119
+ * Resolve app directory for database key generation (application.yaml lookup).
120
+ * @param {string} key - Secret key
121
+ * @returns {string|null}
122
+ */
123
+ function resolveAppDirForDatabaseKey(key) {
124
+ const parsed = parseDatabaseSecretKey(key);
125
+ if (!parsed) return null;
126
+ try {
127
+ const builderPath = pathsUtil.getBuilderPath(parsed.appKey);
128
+ if (fs.existsSync(builderPath)) return builderPath;
129
+ } catch {
130
+ /* ignore */
131
+ }
132
+ try {
133
+ const intPath = pathsUtil.getIntegrationPath(parsed.appKey);
134
+ if (fs.existsSync(intPath)) return intPath;
135
+ } catch {
136
+ /* ignore */
137
+ }
138
+ return null;
139
+ }
140
+
107
141
  /**
108
142
  * Generate database password value for a key (databases-*-passwordKeyVault)
109
143
  * @param {string} key - Secret key name
110
144
  * @returns {string|null} Password string or null if key does not match
111
145
  */
112
146
  function generateDbPasswordValue(key) {
113
- const dbPasswordMatch = key.match(/^databases-([a-z0-9-_]+)-\d+-passwordKeyVault$/i);
114
- if (!dbPasswordMatch) return null;
115
- const appName = dbPasswordMatch[1];
116
- if (appName === 'miso-controller') return 'miso_pass123';
117
- const dbName = appName.replace(/-/g, '_');
118
- return `${dbName}_pass123`;
147
+ const appDir = resolveAppDirForDatabaseKey(key);
148
+ return generateDatabasePasswordValueForKey(key, appDir);
119
149
  }
120
150
 
121
151
  /**
@@ -124,44 +154,98 @@ function generateDbPasswordValue(key) {
124
154
  * @returns {string|null} URL string or null if key does not match
125
155
  */
126
156
  function generateDbUrlValue(key) {
127
- const dbUrlMatch = key.match(/^databases-([a-z0-9-_]+)-\d+-urlKeyVault$/i);
128
- if (!dbUrlMatch) return null;
129
- const appName = dbUrlMatch[1];
130
- if (appName === 'miso-controller') {
131
- return 'postgresql://miso_user:miso_pass123@${DB_HOST}:${DB_PORT}/miso';
157
+ const appDir = resolveAppDirForDatabaseKey(key);
158
+ return generateDatabaseUrlValueForKey(key, appDir);
159
+ }
160
+
161
+ /**
162
+ * @param {string} key - Secret key
163
+ * @param {Record<string, string>|undefined} placeholderContext - Merged infra.parameter.yaml defaults + CLI overrides
164
+ * @returns {string|undefined} Value if catalog handled key; undefined to use legacy rules
165
+ */
166
+ function tryGenerateSecretValueFromCatalog(key, placeholderContext) {
167
+ let catalogEntry;
168
+ try {
169
+ catalogEntry = infraParameterCatalogModule().getInfraParameterCatalog().findEntryForKey(key);
170
+ } catch (err) {
171
+ logger.warn(`infra parameter catalog unavailable (${err.message}); using legacy generators.`);
172
+ return undefined;
173
+ }
174
+ if (!catalogEntry || !catalogEntry.generator) return undefined;
175
+ const g = catalogEntry.generator.type;
176
+ if (g === 'databasePassword') {
177
+ const v = generateDbPasswordValue(key);
178
+ return v !== null ? v : undefined;
179
+ }
180
+ if (g === 'databaseUrl') {
181
+ const v = generateDbUrlValue(key);
182
+ return v !== null ? v : undefined;
132
183
  }
133
- const dbName = appName.replace(/-/g, '_');
134
- return `postgresql://${dbName}_user:${dbName}_pass123@\${DB_HOST}:\${DB_PORT}/${dbName}`;
184
+ if (
185
+ g === 'randomBytes32' ||
186
+ g === 'randomAlphanumeric' ||
187
+ g === 'password' ||
188
+ g === 'emptyString' ||
189
+ g === 'emptyAllowed' ||
190
+ g === 'literal'
191
+ ) {
192
+ return infraParameterCatalogModule().generateValueFromCatalogEntry(key, catalogEntry, crypto, placeholderContext);
193
+ }
194
+ return undefined;
135
195
  }
136
196
 
137
197
  /**
138
- * Generates secret value based on key name
139
- * @function generateSecretValue
140
- * @param {string} key - Secret key name
141
- * @returns {string} Generated secret value
198
+ * Last-resort generation when infra.parameter.yaml cannot be loaded (e.g. tests, broken install).
199
+ * @param {string} key
200
+ * @returns {string}
142
201
  */
143
- function generateSecretValue(key) {
202
+ function legacySecretValueWhenCatalogUnavailable(key) {
144
203
  const keyLower = key.toLowerCase();
145
-
146
204
  if (keyLower.includes('password')) {
147
205
  const dbPassword = generateDbPasswordValue(key);
148
206
  if (dbPassword !== null) return dbPassword;
149
207
  return crypto.randomBytes(32).toString('base64');
150
208
  }
151
-
152
209
  if (keyLower.includes('url') || keyLower.includes('uri')) {
153
210
  const dbUrl = generateDbUrlValue(key);
154
211
  if (dbUrl !== null) return dbUrl;
155
212
  return '';
156
213
  }
157
-
158
214
  if (keyLower.includes('key') || keyLower.includes('secret') || keyLower.includes('token')) {
159
215
  return crypto.randomBytes(32).toString('base64');
160
216
  }
161
-
162
217
  return '';
163
218
  }
164
219
 
220
+ /**
221
+ * @param {string} key - Secret key name
222
+ * @param {Record<string, string>|undefined} placeholderContext - Optional merged catalog defaults + CLI (e.g. up-infra)
223
+ * @returns {string} Generated secret value
224
+ */
225
+ function generateSecretValue(key, placeholderContext) {
226
+ const fromCatalog = tryGenerateSecretValueFromCatalog(key, placeholderContext);
227
+ if (fromCatalog !== undefined) return fromCatalog;
228
+
229
+ const dbPassword = generateDbPasswordValue(key);
230
+ if (dbPassword !== null) return dbPassword;
231
+ const dbUrl = generateDbUrlValue(key);
232
+ if (dbUrl !== null) return dbUrl;
233
+
234
+ try {
235
+ infraParameterCatalogModule().getInfraParameterCatalog();
236
+ } catch {
237
+ logger.warn(
238
+ `Secret key "${key}": infra parameter catalog unavailable; using legacy name heuristics.`
239
+ );
240
+ return legacySecretValueWhenCatalogUnavailable(key);
241
+ }
242
+
243
+ throw new Error(
244
+ `Cannot generate secret for key "${key}": no matching rule in infra.parameter.yaml. ` +
245
+ 'Add a catalog entry or run `aifabrix parameters validate` after fixing env.template references.'
246
+ );
247
+ }
248
+
165
249
  /**
166
250
  * Loads existing secrets from file
167
251
  * @function loadExistingSecrets
@@ -196,6 +280,23 @@ function saveSecretsFile(resolvedPath, secrets) {
196
280
  fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
197
281
  }
198
282
 
283
+ if (fs.existsSync(resolvedPath)) {
284
+ try {
285
+ const existing = fs.readFileSync(resolvedPath, 'utf8');
286
+ const mergedContent = mergeFlatSecretsYamlPreservingComments(existing, secrets, {
287
+ yaml,
288
+ dumpOpts: YAML_DUMP_OPTS
289
+ });
290
+ if (mergedContent !== null) {
291
+ fs.writeFileSync(resolvedPath, mergedContent + (mergedContent.endsWith('\n') ? '' : '\n'), { mode: 0o600 });
292
+ return;
293
+ }
294
+ } catch (err) {
295
+ logger.warn(`Could not preserve comments when writing secrets file: ${err.message}; rewriting YAML.`);
296
+ // fall through to dump overwrite
297
+ }
298
+ }
299
+
199
300
  const yamlContent = yaml.dump(secrets, {
200
301
  indent: 2,
201
302
  lineWidth: -1,
@@ -208,6 +309,20 @@ function saveSecretsFile(resolvedPath, secrets) {
208
309
 
209
310
  const YAML_DUMP_OPTS = { indent: 2, lineWidth: -1, noRefs: true, sortKeys: false };
210
311
 
312
+ /**
313
+ * @param {string} resolvedPath
314
+ * @returns {string}
315
+ */
316
+ function readSecretsFileUtf8OrWarnEmpty(resolvedPath) {
317
+ try {
318
+ const raw = fs.readFileSync(resolvedPath, 'utf8');
319
+ return typeof raw === 'string' ? raw : '';
320
+ } catch (err) {
321
+ logger.warn(`Could not read existing secrets file: ${err.message}; appending new keys only.`);
322
+ return '';
323
+ }
324
+ }
325
+
211
326
  /**
212
327
  * Merges secret keys into the secrets file (load existing, merge, overwrite file).
213
328
  * Use when setting or updating keys so that existing keys are updated in place instead of duplicated.
@@ -253,12 +368,13 @@ function appendSecretsToFile(resolvedPath, secrets) {
253
368
  return;
254
369
  }
255
370
 
256
- let existing = '';
371
+ const existing = readSecretsFileUtf8OrWarnEmpty(resolvedPath);
257
372
  try {
258
- const raw = fs.readFileSync(resolvedPath, 'utf8');
259
- existing = typeof raw === 'string' ? raw : '';
260
- } catch (err) {
261
- logger.warn(`Could not read existing secrets file: ${err.message}; appending new keys only.`);
373
+ yaml.load(existing);
374
+ } catch {
375
+ const mergedObj = { ...loadExistingSecrets(resolvedPath), ...secrets };
376
+ saveSecretsFile(resolvedPath, mergedObj);
377
+ return;
262
378
  }
263
379
  const separator = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
264
380
  fs.writeFileSync(resolvedPath, existing + separator + appendContent, { mode: 0o600 });
@@ -296,7 +412,7 @@ async function generateMissingSecrets(envTemplate, secretsPath) {
296
412
 
297
413
  appendSecretsToFile(resolvedPath, newSecrets);
298
414
 
299
- logger.log(`✓ Generated ${missingKeys.length} missing secret key(s): ${missingKeys.join(', ')}`);
415
+ logger.log(`✔ Generated ${missingKeys.length} missing secret key(s): ${missingKeys.join(', ')}`);
300
416
  return missingKeys;
301
417
  }
302
418
 
@@ -314,6 +430,15 @@ async function generateMissingSecrets(envTemplate, secretsPath) {
314
430
  * await createDefaultSecrets('~/.aifabrix/secrets.yaml');
315
431
  * // Default secrets file is created
316
432
  */
433
+ /** Minimal seed keys for first-time secrets file; values match infra.parameter.yaml generators. */
434
+ const DEFAULT_SECRETS_SEED_KEYS = [
435
+ 'postgres-passwordKeyVault',
436
+ 'redis-passwordKeyVault',
437
+ 'redis-url',
438
+ 'keycloak-admin-passwordKeyVault',
439
+ 'keycloak-web-server-url'
440
+ ];
441
+
317
442
  async function createDefaultSecrets(secretsPath) {
318
443
  const resolvedPath = secretsPath.startsWith('~')
319
444
  ? path.join(os.homedir(), secretsPath.slice(1))
@@ -324,22 +449,17 @@ async function createDefaultSecrets(secretsPath) {
324
449
  fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
325
450
  }
326
451
 
327
- const defaultSecrets = `# Local Development Secrets
328
- # Production uses Azure KeyVault
329
-
330
- # Database Secrets
331
- postgres-passwordKeyVault: "admin123"
332
-
333
- # Redis Secrets
334
- redis-passwordKeyVault: ""
335
- redis-url: "redis://\${REDIS_HOST}:\${REDIS_PORT}"
452
+ const seeded = {};
453
+ for (const k of DEFAULT_SECRETS_SEED_KEYS) {
454
+ seeded[k] = generateSecretValue(k);
455
+ }
336
456
 
337
- # Keycloak Secrets
338
- keycloak-admin-passwordKeyVault: "admin123"
339
- keycloak-server-url: "http://\${KEYCLOAK_HOST}:\${KEYCLOAK_PORT}"
340
- `;
457
+ const header =
458
+ '# Local development secrets (catalog-aligned seed). Run aifabrix up-infra and aifabrix resolve <app> --force for full keys.\n' +
459
+ '# Production uses Azure Key Vault.\n\n';
460
+ const body = yaml.dump(seeded, { indent: 2, lineWidth: -1, noRefs: true, sortKeys: false });
341
461
 
342
- fs.writeFileSync(resolvedPath, defaultSecrets, { mode: 0o600 });
462
+ fs.writeFileSync(resolvedPath, header + body, { mode: 0o600 });
343
463
  }
344
464
 
345
465
  module.exports = {