@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
@@ -46,6 +46,41 @@ function isCommentOrEmptyLine(line) {
46
46
  /** Regex for kv:// path (allows slashes, e.g. kv://hubspot/clientId) */
47
47
  const KV_REF_PATTERN = /kv:\/\/([a-zA-Z0-9_\-/]+)/g;
48
48
 
49
+ const { resolveBashKvFromProcessEnv } = require('./secrets-bash-kv');
50
+
51
+ /**
52
+ * Last-resort when infra.parameter.yaml cannot be read (e.g. Jest suites that mock `fs`;
53
+ * catalog uses `node:fs`, which is often the same mocked instance). Must stay in sync with
54
+ * `generator.type: emptyAllowed` keys in lib/schema/infra.parameter.yaml.
55
+ */
56
+ const EMPTY_ALLOWED_KV_FALLBACK = new Set(['redis-passwordKeyVault']);
57
+
58
+ /**
59
+ * Infra catalog keys with generator `emptyAllowed` may be absent from the secrets file;
60
+ * they resolve like an explicit empty string (e.g. local Redis has no password).
61
+ * @param {string} pathStr - kv path segment (flat key or path with slashes)
62
+ * @returns {boolean}
63
+ */
64
+ function isKvKeyAllowedEmptyWhenAbsent(pathStr) {
65
+ if (!pathStr || typeof pathStr !== 'string') return false;
66
+ try {
67
+ const { getInfraParameterCatalog } = require('../parameters/infra-parameter-catalog');
68
+ return getInfraParameterCatalog().isKeyAllowedEmpty(pathStr);
69
+ } catch {
70
+ try {
71
+ const {
72
+ readRelaxedEmptyAllowedKeySet,
73
+ DEFAULT_CATALOG_PATH
74
+ } = require('../parameters/infra-parameter-catalog');
75
+ const set = readRelaxedEmptyAllowedKeySet(DEFAULT_CATALOG_PATH);
76
+ if (set && set.has(pathStr)) return true;
77
+ } catch {
78
+ /* ignore */
79
+ }
80
+ }
81
+ return EMPTY_ALLOWED_KV_FALLBACK.has(pathStr);
82
+ }
83
+
49
84
  /**
50
85
  * Find object key that matches part case-insensitively.
51
86
  * @param {Object} obj - Object to search
@@ -98,19 +133,30 @@ function getValueByPath(secrets, pathStr) {
98
133
  return getValueByNestedPath(secrets, pathStr.split('/'));
99
134
  }
100
135
 
136
+ const { getValueByPathWithEnvScope, mergeSecretsWithPrefixedCopies } =
137
+ require('./secrets-kv-scope').createScopedKvHelpers(getValueByPath);
138
+
139
+ function resolveKvRefValue(secretsMap, pathStr, envKey, effective) {
140
+ const v = getValueByPathWithEnvScope(secretsMap, pathStr, envKey, effective);
141
+ if (v !== undefined && v !== null) return v;
142
+ return resolveBashKvFromProcessEnv(pathStr);
143
+ }
144
+
101
145
  /**
102
146
  * Collect missing kv:// secrets referenced in content (skips commented and empty lines).
103
147
  * Supports path-style refs (e.g. kv://hubspot/clientId). Returns unique refs.
104
148
  * @function collectMissingSecrets
105
149
  * @param {string} content - Text content
106
150
  * @param {Object} secrets - Available secrets (flat or nested)
151
+ * @param {{ effective?: boolean, envKey?: string }|null} [scopedKv] - Optional env-scoped kv resolution
107
152
  * @returns {string[]} Array of missing kv://<path> references (unique)
108
153
  */
109
- function collectMissingSecrets(content, secrets) {
154
+ function collectMissingSecrets(content, secrets, scopedKv = null) {
155
+ const effective = Boolean(scopedKv && scopedKv.effective && scopedKv.envKey);
156
+ const secretsMap = effective ? mergeSecretsWithPrefixedCopies(secrets, scopedKv.envKey) : secrets;
110
157
  const seen = new Set();
111
158
  const missing = [];
112
- const lines = content.split('\n');
113
- for (const line of lines) {
159
+ for (const line of content.split('\n')) {
114
160
  if (isCommentOrEmptyLine(line)) continue;
115
161
  let match;
116
162
  KV_REF_PATTERN.lastIndex = 0;
@@ -118,8 +164,8 @@ function collectMissingSecrets(content, secrets) {
118
164
  const pathStr = match[1];
119
165
  if (seen.has(pathStr)) continue;
120
166
  seen.add(pathStr);
121
- const value = getValueByPath(secrets, pathStr);
122
- if (value === undefined || value === null) {
167
+ const value = resolveKvRefValue(secretsMap, pathStr, scopedKv?.envKey, effective);
168
+ if ((value === undefined || value === null) && !isKvKeyAllowedEmptyWhenAbsent(pathStr)) {
123
169
  missing.push(`kv://${pathStr}`);
124
170
  }
125
171
  }
@@ -157,14 +203,20 @@ function formatMissingSecretsFileInfo(secretsFilePaths) {
157
203
  * @param {string} content - Text content containing kv:// references
158
204
  * @param {Object} secrets - Secrets map (flat or nested)
159
205
  * @param {Object} envVars - Environment variables map for nested interpolation
206
+ * @param {{ effective?: boolean, envKey?: string }|null} [scopedKv] - Optional env-scoped kv resolution
160
207
  * @returns {string} Content with kv:// references replaced
161
208
  */
162
- function replaceKvInContent(content, secrets, envVars) {
209
+ function replaceKvInContent(content, secrets, envVars, scopedKv = null) {
210
+ const effective = Boolean(scopedKv && scopedKv.effective && scopedKv.envKey);
211
+ const secretsMap = effective ? mergeSecretsWithPrefixedCopies(secrets, scopedKv.envKey) : secrets;
163
212
  const lines = content.split('\n');
164
213
  const result = lines.map(line => {
165
214
  if (isCommentOrEmptyLine(line)) return line;
166
215
  return line.replace(KV_REF_PATTERN, (match, pathStr) => {
167
- let value = getValueByPath(secrets, pathStr);
216
+ let value = resolveKvRefValue(secretsMap, pathStr, scopedKv?.envKey, effective);
217
+ if ((value === undefined || value === null) && isKvKeyAllowedEmptyWhenAbsent(pathStr)) {
218
+ value = '';
219
+ }
168
220
  if (typeof value === 'string') {
169
221
  value = value.replace(/\$\{([A-Z_]+)\}/g, (m, envVar) => {
170
222
  return envVars[envVar] || m;
@@ -247,44 +299,23 @@ function getPortFromEnvContent(envContent) {
247
299
  }
248
300
 
249
301
  /**
250
- * Applies developer-id adjustment to port
251
- * @function applyDeveloperIdAdjustment
252
- * @param {number} baseAppPort - Base application port
253
- * @param {number} devIdNum - Developer ID number
254
- * @returns {number} Adjusted port
255
- */
256
- function applyDeveloperIdAdjustment(baseAppPort, devIdNum) {
257
- return devIdNum === 0 ? baseAppPort : (baseAppPort + (devIdNum * 100));
258
- }
259
-
260
- /**
261
- * Calculate application port following override chain and developer-id adjustment
262
- * Override chain: env-config.yaml → config.yaml → application.yaml port
302
+ * Resolve manifest listen port (container port) for the app from override chain.
263
303
  * @async
264
- * @function calculateAppPort
265
304
  * @param {string} [variablesPath] - Path to application config
266
- * @param {Object} localEnv - Local environment config from env-config.yaml and config.yaml
305
+ * @param {Object} localEnv - Merged local env from defaults + config.yaml
267
306
  * @param {string} envContent - Environment content for fallback
268
- * @param {number} devIdNum - Developer ID number
269
- * @returns {Promise<number>} Final application port with developer-id adjustment
307
+ * @returns {Promise<number>} Base listen port (before +10 / +dev*100 local host math)
270
308
  */
271
- async function calculateAppPort(variablesPath, localEnv, envContent, devIdNum) {
272
- // Start with env-config value
309
+ async function resolveManifestListenPort(variablesPath, localEnv, envContent) {
273
310
  let baseAppPort = getPortFromLocalEnv(localEnv);
274
-
275
- // Override with application config port (strongest)
276
311
  const variablesPort = getPortFromVariablesFile(variablesPath);
277
312
  if (variablesPort !== null) {
278
313
  baseAppPort = variablesPort;
279
314
  }
280
-
281
- // Fallback to env content if still no port
282
315
  if (baseAppPort === null || baseAppPort === undefined) {
283
316
  baseAppPort = getPortFromEnvContent(envContent);
284
317
  }
285
-
286
- // Apply developer-id adjustment
287
- return applyDeveloperIdAdjustment(baseAppPort, devIdNum);
318
+ return baseAppPort;
288
319
  }
289
320
 
290
321
  /**
@@ -325,16 +356,6 @@ function getPortVarFromEnvTemplatePath(variablesPath) {
325
356
  }
326
357
  }
327
358
 
328
- /**
329
- * Adjust infra-related ports in resolved .env content for local environment.
330
- * Own case: when we generate .env for envOutputPath (not reload), we use localPort (application.yaml build.localPort or port).
331
- * Sets PORT and the template port var (e.g. MISO_PORT) to localPort so the generated .env is correct for local use.
332
- * @async
333
- * @function adjustLocalEnvPortsInContent
334
- * @param {string} envContent - Resolved .env content
335
- * @param {string} [variablesPath] - Path to application config (to read port and template port var)
336
- * @returns {Promise<string>} Updated content with local ports
337
- */
338
359
  /**
339
360
  * Gets developer ID number
340
361
  * @async
@@ -418,12 +439,13 @@ async function buildEnvVarsForInterpolation(devIdNum) {
418
439
  async function adjustLocalEnvPortsInContent(envContent, variablesPath) {
419
440
  const devIdNum = await getDeveloperIdNumber();
420
441
  const localEnv = await getLocalEnvWithOverrides();
442
+ const { localHostPort } = require('./declarative-url-ports');
421
443
 
422
- const baseAppPort = await calculateAppPort(variablesPath, localEnv, envContent, 0);
423
- const appPort = await calculateAppPort(variablesPath, localEnv, envContent, devIdNum);
444
+ const baseListen = await resolveManifestListenPort(variablesPath, localEnv, envContent);
445
+ const appPort = localHostPort(baseListen, devIdNum);
424
446
 
425
447
  let updated = updatePortVariable(envContent, appPort);
426
- updated = updateLocalhostUrls(updated, baseAppPort, appPort);
448
+ updated = updateLocalhostUrls(updated, baseListen, appPort);
427
449
  updated = await rewriteInfraEndpoints(updated, 'local');
428
450
 
429
451
  const envVars = await buildEnvVarsForInterpolation(devIdNum);
@@ -437,18 +459,6 @@ async function adjustLocalEnvPortsInContent(envContent, variablesPath) {
437
459
  return updated;
438
460
  }
439
461
 
440
- /**
441
- * Ensure secrets map is non-empty or throw a friendly guidance error
442
- * @function ensureNonEmptySecrets
443
- * @param {Object} secrets - Secrets map
444
- * @throws {Error} If secrets is empty
445
- */
446
- function ensureNonEmptySecrets(secrets) {
447
- if (Object.keys(secrets || {}).length === 0) {
448
- throw new Error('No secrets file found. Please create ~/.aifabrix/secrets.local.yaml or configure aifabrix-secrets in config.yaml');
449
- }
450
- }
451
-
452
462
  /**
453
463
  * Validate secrets against the env template (skips commented and empty lines)
454
464
  * @function validateSecrets
@@ -465,6 +475,8 @@ module.exports = {
465
475
  loadEnvConfig,
466
476
  interpolateEnvVars,
467
477
  collectMissingSecrets,
478
+ resolveBashKvFromProcessEnv,
479
+ mergeSecretsWithPrefixedCopies,
468
480
  formatMissingSecretsFileInfo,
469
481
  replaceKvInContent,
470
482
  resolveServicePortsInEnvContent,
@@ -473,7 +485,6 @@ module.exports = {
473
485
  adjustLocalEnvPortsInContent,
474
486
  readYamlAtPath,
475
487
  applyCanonicalSecretsOverride,
476
- ensureNonEmptySecrets,
477
488
  validateSecrets,
478
489
  rewriteInfraEndpoints,
479
490
  getEnvHosts
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Env-scoped kv:// resolution helpers (prefixed keys + in-memory aliases).
3
+ *
4
+ * @fileoverview Split from secrets-helpers for file size limits
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ /**
12
+ * @param {Function} getValueByPath - From secrets-helpers
13
+ * @returns {{ getValueByPathWithEnvScope: Function, mergeSecretsWithPrefixedCopies: Function }}
14
+ */
15
+ function createScopedKvHelpers(getValueByPath) {
16
+ /**
17
+ * @param {Object} secrets
18
+ * @param {string} pathStr
19
+ * @param {string|null|undefined} envKey
20
+ * @param {boolean} effective
21
+ * @returns {*}
22
+ */
23
+ function getValueByPathWithEnvScope(secrets, pathStr, envKey, effective) {
24
+ if (!effective || !envKey) {
25
+ return getValueByPath(secrets, pathStr);
26
+ }
27
+ const prefix = `${String(envKey).toLowerCase()}-`;
28
+ const prefixedPath = prefix + pathStr;
29
+ const fromPrefixed = getValueByPath(secrets, prefixedPath);
30
+ if (fromPrefixed !== undefined && fromPrefixed !== null) {
31
+ return fromPrefixed;
32
+ }
33
+ return getValueByPath(secrets, pathStr);
34
+ }
35
+
36
+ /**
37
+ * @param {Object} secrets
38
+ * @param {string} envKey
39
+ * @returns {Object}
40
+ */
41
+ function mergeSecretsWithPrefixedCopies(secrets, envKey) {
42
+ if (!secrets || typeof secrets !== 'object' || !envKey) {
43
+ return secrets;
44
+ }
45
+ const prefix = `${String(envKey).toLowerCase()}-`;
46
+ const merged = { ...secrets };
47
+ for (const [k, v] of Object.entries(secrets)) {
48
+ if (typeof k !== 'string' || !k || k.startsWith(prefix)) continue;
49
+ const pk = prefix + k;
50
+ if (merged[pk] === undefined && v !== undefined && v !== null) {
51
+ merged[pk] = v;
52
+ }
53
+ }
54
+ return merged;
55
+ }
56
+
57
+ return { getValueByPathWithEnvScope, mergeSecretsWithPrefixedCopies };
58
+ }
59
+
60
+ module.exports = { createScopedKvHelpers };
@@ -19,25 +19,18 @@ const { getContainerPort } = require('./port-resolver');
19
19
  const { loadYamlTolerantOfDuplicateKeys } = require('./secrets-generator');
20
20
 
21
21
  /**
22
- * Parses secrets YAML content with fallback for duplicate keys.
22
+ * Parses secrets YAML content with fallback for duplicate keys and broken concat files
23
+ * (e.g. empty map `{}` then appended key lines — invalid without `---`).
23
24
  * @param {string} content - Raw file content
24
25
  * @returns {Object} Parsed secrets object
25
26
  */
26
27
  function parseSecretsContent(content) {
27
- try {
28
- return yaml.load(content);
29
- } catch (yamlErr) {
30
- const msg = yamlErr.message || '';
31
- if (msg.includes('duplicate') || msg.includes('duplicated mapping')) {
32
- return loadYamlTolerantOfDuplicateKeys(content);
33
- }
34
- throw yamlErr;
35
- }
28
+ return loadYamlTolerantOfDuplicateKeys(content);
36
29
  }
37
30
 
38
31
  /**
39
32
  * Loads secrets from file with cascading lookup support
40
- * First checks ~/.aifabrix/secrets.local.yaml, then aifabrix-secrets from config.yaml
33
+ * First checks the primary user secrets file (see getPrimaryUserSecretsLocalPath in paths.js), then aifabrix-secrets from config.yaml
41
34
  *
42
35
  * @async
43
36
  * @function loadSecretsFromFile
@@ -51,7 +44,7 @@ async function loadSecretsFromFile(filePath) {
51
44
 
52
45
  try {
53
46
  const content = fs.readFileSync(filePath, 'utf8');
54
- const secrets = yaml.load(content);
47
+ const secrets = parseSecretsContent(content);
55
48
 
56
49
  if (!secrets || typeof secrets !== 'object') {
57
50
  return {};
@@ -65,17 +58,15 @@ async function loadSecretsFromFile(filePath) {
65
58
  }
66
59
 
67
60
  /**
68
- * Loads user secrets from the primary config directory (AIFABRIX_HOME or ~/.aifabrix).
61
+ * Loads user secrets from getPrimaryUserSecretsLocalPath() (same dir as config.yaml resolution).
69
62
  * Used as the master source when merging with project/public secrets: user values win,
70
63
  * missing keys are filled from the public (aifabrix-secrets) file.
71
- * Does not use config.yaml aifabrix-home so the merge always sees the actual user file.
72
64
  *
73
65
  * @function loadPrimaryUserSecrets
74
66
  * @returns {Object} Loaded secrets object or empty object
75
67
  */
76
68
  function loadPrimaryUserSecrets() {
77
- const primaryDir = pathsUtil.getConfigDirForPaths();
78
- const userSecretsPath = path.join(primaryDir, 'secrets.local.yaml');
69
+ const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
79
70
  if (!fs.existsSync(userSecretsPath)) {
80
71
  return {};
81
72
  }
@@ -98,32 +89,12 @@ function loadPrimaryUserSecrets() {
98
89
  }
99
90
 
100
91
  /**
101
- * Loads user secrets from ~/.aifabrix/secrets.local.yaml
102
- * Uses paths.getAifabrixHome() to respect config.yaml aifabrix-home override
92
+ * Same as {@link loadPrimaryUserSecrets} (legacy name; kept for callers).
103
93
  * @function loadUserSecrets
104
94
  * @returns {Object} Loaded secrets object or empty object
105
95
  */
106
96
  function loadUserSecrets() {
107
- const userSecretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
108
- if (!fs.existsSync(userSecretsPath)) {
109
- return {};
110
- }
111
- ensureSecureFilePermissions(userSecretsPath);
112
-
113
- try {
114
- const content = fs.readFileSync(userSecretsPath, 'utf8');
115
- const secrets = parseSecretsContent(content);
116
- if (!secrets || typeof secrets !== 'object') {
117
- throw new Error(`Invalid secrets file format: ${userSecretsPath}`);
118
- }
119
- return secrets;
120
- } catch (error) {
121
- if (error.message.includes('Invalid secrets file format')) {
122
- throw error;
123
- }
124
- logger.warn(`Warning: Could not read secrets file ${userSecretsPath}: ${error.message}`);
125
- return {};
126
- }
97
+ return loadPrimaryUserSecrets();
127
98
  }
128
99
 
129
100
  /**
@@ -155,6 +126,28 @@ function loadDefaultSecrets() {
155
126
  }
156
127
  }
157
128
 
129
+ /**
130
+ * Creates the primary user secrets file if missing (empty map) for first-run installs.
131
+ * Uses the same directory as {@link loadPrimaryUserSecrets} (config dir / ~/.aifabrix).
132
+ *
133
+ * @function ensurePrimaryUserSecretsFileExists
134
+ */
135
+ function ensurePrimaryUserSecretsFileExists() {
136
+ const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
137
+ const primaryDir = path.dirname(userSecretsPath);
138
+ if (fs.existsSync(userSecretsPath)) {
139
+ return;
140
+ }
141
+ if (!fs.existsSync(primaryDir)) {
142
+ fs.mkdirSync(primaryDir, { recursive: true, mode: 0o700 });
143
+ }
144
+ const header =
145
+ '# Local secrets for AI Fabrix CLI (kv:// references in env.template resolve here)\n' +
146
+ '# Keys are appended by `aifabrix resolve` / ensure — do not prefix with JSON `{}`.\n';
147
+ fs.writeFileSync(userSecretsPath, header, { mode: 0o600 });
148
+ ensureSecureFilePermissions(userSecretsPath);
149
+ }
150
+
158
151
  /**
159
152
  * Builds a map of hostname to service name from environment config
160
153
  * @function buildHostnameToServiceMap
@@ -214,6 +207,7 @@ module.exports = {
214
207
  loadPrimaryUserSecrets,
215
208
  loadUserSecrets,
216
209
  loadDefaultSecrets,
210
+ ensurePrimaryUserSecretsFileExists,
217
211
  buildHostnameToServiceMap,
218
212
  resolveUrlPort
219
213
  };
@@ -63,7 +63,9 @@ function validateSecretsFile(filePath, options = {}) {
63
63
  if (!filePath || typeof filePath !== 'string') {
64
64
  return { valid: false, errors: ['Path is required'], path: '' };
65
65
  }
66
- const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
66
+ // Always normalize to an OS-resolved absolute path. This makes behavior consistent
67
+ // on Windows when callers pass POSIX-style absolute paths like "/nonexistent/foo".
68
+ const resolvedPath = path.resolve(filePath);
67
69
  if (!fs.existsSync(resolvedPath)) {
68
70
  return { valid: false, errors: [`File not found: ${resolvedPath}`], path: resolvedPath };
69
71
  }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * YAML merge utilities that preserve comments/blank lines for flat secrets files.
3
+ *
4
+ * Intended for `secrets.local.yaml` which is typically a simple `key: value` mapping
5
+ * with user-owned comments. When the file isn't flat (complex YAML), callers should
6
+ * fall back to a full YAML rewrite.
7
+ *
8
+ * @fileoverview Comment-preserving YAML merge helpers
9
+ */
10
+
11
+ const keyLineRe = /^(\s*)([^#:\n]+):\s*(.*)$/;
12
+
13
+ function isCommentOrBlank(line) {
14
+ const t = line.trim();
15
+ return !t || t.startsWith('#');
16
+ }
17
+
18
+ function parseFlatKeyLine(line) {
19
+ const m = line.match(keyLineRe);
20
+ if (!m) return null;
21
+ const indent = m[1] || '';
22
+ const key = (m[2] || '').trim();
23
+ const rest = m[3] || '';
24
+ if (!key) return null;
25
+ return { indent, key, rest };
26
+ }
27
+
28
+ function splitInlineComment(rest) {
29
+ const idx = rest.indexOf(' #');
30
+ return { inlineComment: idx >= 0 ? rest.slice(idx) : '' };
31
+ }
32
+
33
+ function formatYamlScalarForFlatLine(yaml, dumpOpts, value) {
34
+ const dumped = yaml.dump({ _k: value }, dumpOpts);
35
+ const lines = dumped.split(/\r?\n/).filter(Boolean);
36
+ if (lines.length !== 1) return null;
37
+ const idx = lines[0].indexOf(':');
38
+ if (idx < 0) return null;
39
+ return lines[0].slice(idx + 1).trimStart();
40
+ }
41
+
42
+ function appendMissingKeys(out, seen, desiredSecrets, yaml, dumpOpts) {
43
+ for (const key of Object.keys(desiredSecrets)) {
44
+ if (seen.has(key)) continue;
45
+ const scalar = formatYamlScalarForFlatLine(yaml, dumpOpts, desiredSecrets[key]);
46
+ if (scalar === null) return null;
47
+ out.push(`${key}: ${scalar}`.trimEnd());
48
+ seen.add(key);
49
+ }
50
+ return out;
51
+ }
52
+
53
+ function mergeExistingLinesPreservingComments(lines, desiredSecrets, yaml, dumpOpts) {
54
+ const out = [];
55
+ const seen = new Set();
56
+ const encountered = new Set();
57
+
58
+ for (const line of lines) {
59
+ if (isCommentOrBlank(line)) {
60
+ out.push(line);
61
+ continue;
62
+ }
63
+ const parsed = parseFlatKeyLine(line);
64
+ if (!parsed) return null;
65
+ const { indent, key, rest } = parsed;
66
+
67
+ if (encountered.has(key)) continue;
68
+ encountered.add(key);
69
+
70
+ if (!Object.prototype.hasOwnProperty.call(desiredSecrets, key)) continue;
71
+
72
+ const { inlineComment } = splitInlineComment(rest);
73
+ const scalar = formatYamlScalarForFlatLine(yaml, dumpOpts, desiredSecrets[key]);
74
+ if (scalar === null) return null;
75
+ seen.add(key);
76
+ out.push(`${indent}${key}: ${scalar}${inlineComment}`.trimEnd());
77
+ }
78
+
79
+ return { out, seen };
80
+ }
81
+
82
+ /**
83
+ * Merge a desired flat secrets object into existing file content while preserving comments/blank lines.
84
+ * Supports deletes: keys present in existing content but absent in desired are removed.
85
+ *
86
+ * Returns null when content cannot be treated as a flat key-value file.
87
+ *
88
+ * @param {string} existingContent
89
+ * @param {Record<string, any>} desiredSecrets
90
+ * @param {{ yaml: any, dumpOpts: any }} yamlCtx
91
+ * @returns {string|null}
92
+ */
93
+ function mergeFlatSecretsYamlPreservingComments(existingContent, desiredSecrets, yamlCtx) {
94
+ if (typeof existingContent !== 'string') return null;
95
+ if (!desiredSecrets || typeof desiredSecrets !== 'object') return null;
96
+ if (!yamlCtx || !yamlCtx.yaml) return null;
97
+
98
+ const { yaml, dumpOpts } = yamlCtx;
99
+ const lines = existingContent.split(/\r?\n/);
100
+ const merged = mergeExistingLinesPreservingComments(lines, desiredSecrets, yaml, dumpOpts);
101
+ if (!merged) return null;
102
+
103
+ const appended = appendMissingKeys(merged.out, merged.seen, desiredSecrets, yaml, dumpOpts);
104
+ if (appended === null) return null;
105
+ return appended.join('\n');
106
+ }
107
+
108
+ module.exports = { mergeFlatSecretsYamlPreservingComments };
109
+
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  const fs = require('fs');
8
+ const { nodeFs } = require('../internal/node-fs');
8
9
  const path = require('path');
9
10
  const os = require('os');
10
11
  const { execSync } = require('child_process');
@@ -40,9 +41,10 @@ function getDefaultEd25519PrivateKeyPath() {
40
41
  * @returns {string} Resolved SSH dir path
41
42
  */
42
43
  function ensureSshDir(sshDir) {
44
+ const syncFs = nodeFs();
43
45
  const dir = sshDir || getDefaultSshDir();
44
- if (!fs.existsSync(dir)) {
45
- fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
46
+ if (!syncFs.existsSync(dir)) {
47
+ syncFs.mkdirSync(dir, { recursive: true, mode: 0o700 });
46
48
  }
47
49
  return dir;
48
50
  }
@@ -35,7 +35,7 @@ async function loadTemplateVariables(templateName) {
35
35
  } catch (error) {
36
36
  // Template application.yaml not found or invalid, continue without it
37
37
  if (error.code !== 'ENOENT') {
38
- logger.warn(chalk.yellow(`⚠️ Warning: Could not load template application.yaml: ${error.message}`));
38
+ logger.warn(chalk.yellow(`⚠ Warning: Could not load template application.yaml: ${error.message}`));
39
39
  }
40
40
  return null;
41
41
  }
@@ -125,7 +125,7 @@ async function updateTemplateVariables(appPath, appName, options, config) {
125
125
  writeConfigFile(variablesPath, variables);
126
126
  } catch (error) {
127
127
  if (error.message && !error.message.includes('not found')) {
128
- logger.warn(chalk.yellow(`⚠️ Warning: Could not update application config: ${error.message}`));
128
+ logger.warn(chalk.yellow(`⚠ Warning: Could not update application config: ${error.message}`));
129
129
  }
130
130
  }
131
131
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Test log writer - writes debug logs to integration/<appKey>/logs/
2
+ * Test log writer - writes debug logs to integration/<systemKey>/logs/
3
3
  * Sanitization (tokens, secrets) is done by dataplane before responses are returned.
4
4
  *
5
5
  * @fileoverview Write test request/response logs for debugging
@@ -29,9 +29,9 @@ function sanitizeForLog(obj, seen = new Set()) {
29
29
  }
30
30
 
31
31
  /**
32
- * Write test log to integration/<appKey>/logs/<logType>-<timestamp>.json
32
+ * Write test log to integration/<systemKey>/logs/<logType>-<timestamp>.json
33
33
  * @async
34
- * @param {string} appKey - Application key (used for path)
34
+ * @param {string} appKey - Integration folder name under integration/ (used for path)
35
35
  * @param {Object} data - Log data (request, response) - will be sanitized
36
36
  * @param {string} [logType] - Log type prefix (default: test-integration)
37
37
  * @param {string} [integrationBaseDir] - Base dir for integration (default: cwd/integration)
@@ -10,7 +10,6 @@
10
10
  */
11
11
 
12
12
  const fs = require('fs');
13
- const path = require('path');
14
13
  const yaml = require('js-yaml');
15
14
  const config = require('../core/config');
16
15
  const logger = require('./logger');
@@ -25,7 +24,7 @@ const { warnRefreshFailureOnce, warnRefreshTokenExpiredOnce } = require('./token
25
24
  const DATAPLANE_APP_KEY = 'dataplane';
26
25
 
27
26
  function getSecretsFilePath() {
28
- return path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
27
+ return pathsUtil.getPrimaryUserSecretsLocalPath();
29
28
  }
30
29
 
31
30
  /**