@aifabrix/builder 2.42.1 → 2.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/README.md +2 -2
  3. package/anchor-docs/README.md +10 -0
  4. package/anchor-docs/_TEMPLATE +24 -0
  5. package/bin/aifabrix.js +13 -4
  6. package/integration/hubspot-test/README.md +157 -0
  7. package/integration/{hubspot → hubspot-test}/application.json +6 -6
  8. package/integration/{hubspot → hubspot-test}/create-hubspot.js +10 -10
  9. package/integration/hubspot-test/env.template +4 -0
  10. package/integration/hubspot-test/hubspot-test-datasource-company.json +138 -0
  11. package/integration/hubspot-test/hubspot-test-datasource-contact.json +146 -0
  12. package/integration/hubspot-test/hubspot-test-datasource-deal.json +146 -0
  13. package/integration/hubspot-test/hubspot-test-datasource-users.json +76 -0
  14. package/integration/{hubspot/hubspot-deploy.json → hubspot-test/hubspot-test-deploy.json} +201 -24
  15. package/integration/{hubspot/hubspot-system.json → hubspot-test/hubspot-test-system.json} +8 -7
  16. package/integration/hubspot-test/rbac.json +166 -0
  17. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-credential-real.yaml +3 -3
  18. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-env-vars.yaml +2 -2
  19. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-add-datasource.yaml +1 -1
  20. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-create.yaml +1 -1
  21. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-select.yaml +1 -1
  22. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-known-platform.yaml +1 -1
  23. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
  24. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-mode.yaml +1 -1
  25. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-file.yaml +1 -1
  26. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-url.yaml +1 -1
  27. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-source.yaml +1 -1
  28. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-array-test.yaml +1 -1
  29. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
  30. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
  31. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-test.yaml +1 -1
  32. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-test.yaml +1 -1
  33. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +1 -1
  34. package/integration/{hubspot → hubspot-test}/test-dataplane-down-tests.js +1 -7
  35. package/integration/{hubspot → hubspot-test}/test-dataplane-down.js +3 -3
  36. package/integration/{hubspot → hubspot-test}/test.js +137 -102
  37. package/integration/{hubspot → hubspot-test}/wizard-hubspot-e2e.yaml +2 -2
  38. package/integration/{hubspot → hubspot-test}/wizard-hubspot-platform.yaml +1 -1
  39. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  40. package/integration/roundtrip-test-local/README.md +144 -0
  41. package/integration/roundtrip-test-local/application.yaml +13 -0
  42. package/integration/roundtrip-test-local/env.template +15 -0
  43. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  44. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  45. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  46. package/integration/roundtrip-test-local2/README.md +144 -0
  47. package/integration/roundtrip-test-local2/application.yaml +13 -0
  48. package/integration/roundtrip-test-local2/env.template +15 -0
  49. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  50. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  51. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  52. package/integration/test/wizard.yaml +8 -0
  53. package/jest.config.default.js +10 -0
  54. package/jest.config.integration.fixtures.js +22 -0
  55. package/jest.config.integration.js +21 -18
  56. package/jest.config.isolated.js +10 -0
  57. package/jest.projects.js +288 -0
  58. package/lib/api/datasources-core.api.js +3 -3
  59. package/lib/api/dev-mtls-request.js +110 -0
  60. package/lib/api/dev-server-https.js +145 -0
  61. package/lib/api/dev.api.js +133 -144
  62. package/lib/api/index.js +0 -1
  63. package/lib/api/pipeline.api.js +67 -20
  64. package/lib/api/service-users.api.js +111 -2
  65. package/lib/api/types/dev.types.js +4 -3
  66. package/lib/api/types/pipeline.types.js +8 -5
  67. package/lib/api/types/service-users.types.js +41 -0
  68. package/lib/api/types/validation-run.types.js +56 -0
  69. package/lib/api/validation-run.api.js +99 -0
  70. package/lib/api/validation-runner.js +99 -0
  71. package/lib/app/config.js +1 -1
  72. package/lib/app/deploy-status-display.js +2 -2
  73. package/lib/app/deploy.js +7 -6
  74. package/lib/app/display.js +2 -1
  75. package/lib/app/dockerfile.js +3 -2
  76. package/lib/app/down.js +2 -1
  77. package/lib/app/helpers.js +6 -5
  78. package/lib/app/index.js +27 -8
  79. package/lib/app/list.js +7 -6
  80. package/lib/app/push.js +4 -3
  81. package/lib/app/register.js +19 -8
  82. package/lib/app/rotate-secret.js +17 -13
  83. package/lib/app/run-container-start.js +184 -0
  84. package/lib/app/run-docker-fallback.js +108 -0
  85. package/lib/app/run-env-compose.js +30 -42
  86. package/lib/app/run-helpers.js +49 -126
  87. package/lib/app/run-infra-requirements.js +30 -0
  88. package/lib/app/run-resolve-image.js +21 -0
  89. package/lib/app/run.js +74 -21
  90. package/lib/app/show-display.js +1 -1
  91. package/lib/app/show.js +1 -1
  92. package/lib/build/index.js +13 -10
  93. package/lib/cli/index.js +2 -0
  94. package/lib/cli/setup-app.help.js +67 -0
  95. package/lib/cli/setup-app.js +59 -123
  96. package/lib/cli/setup-app.test-commands.js +179 -0
  97. package/lib/cli/setup-auth.js +36 -14
  98. package/lib/cli/setup-credential-deployment.js +22 -8
  99. package/lib/cli/setup-dev-path-commands.js +124 -0
  100. package/lib/cli/setup-dev.js +190 -103
  101. package/lib/cli/setup-environment.js +11 -20
  102. package/lib/cli/setup-external-system.js +62 -22
  103. package/lib/cli/setup-infra.js +139 -47
  104. package/lib/cli/setup-parameters.js +32 -0
  105. package/lib/cli/setup-secrets.js +147 -10
  106. package/lib/cli/setup-service-user.js +146 -20
  107. package/lib/cli/setup-utility.js +47 -19
  108. package/lib/commands/app-down.js +5 -7
  109. package/lib/commands/app-install.js +14 -7
  110. package/lib/commands/app-logs.js +13 -10
  111. package/lib/commands/app-shell.js +4 -1
  112. package/lib/commands/app-test.js +25 -19
  113. package/lib/commands/app.js +22 -10
  114. package/lib/commands/auth-config.js +10 -14
  115. package/lib/commands/auth-status.js +4 -3
  116. package/lib/commands/credential-env.js +4 -3
  117. package/lib/commands/credential-list.js +5 -4
  118. package/lib/commands/credential-push.js +4 -3
  119. package/lib/commands/datasource-unified-test-cli.js +495 -0
  120. package/lib/commands/datasource-unified-test-cli.options.js +149 -0
  121. package/lib/commands/datasource-validation-cli.js +129 -0
  122. package/lib/commands/datasource.js +123 -71
  123. package/lib/commands/deployment-list.js +6 -5
  124. package/lib/commands/dev-cli-handlers.js +122 -18
  125. package/lib/commands/dev-down.js +4 -3
  126. package/lib/commands/dev-init.js +231 -116
  127. package/lib/commands/dev-show-display.js +473 -0
  128. package/lib/commands/login-credentials.js +3 -2
  129. package/lib/commands/login-device.js +4 -3
  130. package/lib/commands/login.js +5 -4
  131. package/lib/commands/logout.js +8 -7
  132. package/lib/commands/parameters-validate.js +54 -0
  133. package/lib/commands/repair-datasource.js +314 -68
  134. package/lib/commands/repair-env-template.js +16 -10
  135. package/lib/commands/repair-rbac.js +25 -19
  136. package/lib/commands/repair.js +116 -32
  137. package/lib/commands/secrets-list.js +23 -12
  138. package/lib/commands/secrets-remove-all.js +220 -0
  139. package/lib/commands/secrets-remove.js +22 -13
  140. package/lib/commands/secrets-set.js +21 -12
  141. package/lib/commands/secrets-validate.js +20 -7
  142. package/lib/commands/secure.js +10 -9
  143. package/lib/commands/service-user.js +243 -13
  144. package/lib/commands/test-e2e-external.js +27 -1
  145. package/lib/commands/up-common.js +28 -2
  146. package/lib/commands/up-dataplane.js +31 -18
  147. package/lib/commands/up-miso.js +19 -29
  148. package/lib/commands/upload.js +138 -39
  149. package/lib/commands/wizard-core-helpers.js +1 -1
  150. package/lib/commands/wizard-dataplane.js +4 -3
  151. package/lib/commands/wizard-helpers.js +3 -3
  152. package/lib/commands/wizard.js +2 -2
  153. package/lib/core/admin-secrets.js +16 -5
  154. package/lib/core/audit-logger.js +12 -4
  155. package/lib/core/config-attach-extensions.js +46 -0
  156. package/lib/core/config-runtime-paths.js +29 -0
  157. package/lib/core/config.js +59 -58
  158. package/lib/core/diff.js +3 -2
  159. package/lib/core/ensure-encryption-key.js +2 -4
  160. package/lib/core/secrets-ensure-infra.js +77 -0
  161. package/lib/core/secrets-ensure.js +120 -64
  162. package/lib/core/secrets-env-write.js +35 -7
  163. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  164. package/lib/core/secrets.js +228 -42
  165. package/lib/core/templates-env.js +4 -3
  166. package/lib/core/templates.js +1 -1
  167. package/lib/datasource/abac-validator.js +148 -0
  168. package/lib/datasource/deploy.js +75 -53
  169. package/lib/datasource/field-reference-validator.js +77 -36
  170. package/lib/datasource/integration-context.js +63 -0
  171. package/lib/datasource/list.js +8 -7
  172. package/lib/datasource/log-viewer.js +252 -0
  173. package/lib/datasource/resolve-app.js +109 -0
  174. package/lib/datasource/test-e2e.js +95 -155
  175. package/lib/datasource/test-integration.js +121 -109
  176. package/lib/datasource/unified-validation-run-body.js +65 -0
  177. package/lib/datasource/unified-validation-run-post.js +23 -0
  178. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  179. package/lib/datasource/unified-validation-run.js +92 -0
  180. package/lib/datasource/validate.js +162 -15
  181. package/lib/deployment/deployer.js +4 -3
  182. package/lib/deployment/environment.js +7 -6
  183. package/lib/deployment/push.js +17 -8
  184. package/lib/external-system/delete.js +4 -3
  185. package/lib/external-system/deploy.js +131 -53
  186. package/lib/external-system/download-helpers.js +1 -1
  187. package/lib/external-system/download.js +7 -6
  188. package/lib/external-system/generator.js +104 -14
  189. package/lib/external-system/integration-test-dispatch.js +26 -0
  190. package/lib/external-system/test-execution.js +5 -1
  191. package/lib/external-system/test-helpers.js +0 -4
  192. package/lib/external-system/test-system-level-helpers.js +110 -0
  193. package/lib/external-system/test-system-level.js +83 -44
  194. package/lib/external-system/test.js +59 -8
  195. package/lib/generator/builders.js +23 -11
  196. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  197. package/lib/generator/external-controller-manifest.js +3 -3
  198. package/lib/generator/external.js +23 -11
  199. package/lib/generator/helpers.js +71 -12
  200. package/lib/generator/index.js +8 -4
  201. package/lib/generator/split-readme.js +12 -7
  202. package/lib/generator/split-variables.js +2 -1
  203. package/lib/generator/split.js +46 -11
  204. package/lib/generator/wizard-readme.js +3 -3
  205. package/lib/generator/wizard.js +16 -13
  206. package/lib/infrastructure/compose.js +60 -6
  207. package/lib/infrastructure/helpers.js +238 -51
  208. package/lib/infrastructure/index.js +64 -37
  209. package/lib/infrastructure/services.js +21 -15
  210. package/lib/internal/fs-real-sync.js +104 -0
  211. package/lib/internal/node-fs.js +98 -0
  212. package/lib/parameters/database-secret-values.js +173 -0
  213. package/lib/parameters/infra-kv-discovery.js +121 -0
  214. package/lib/parameters/infra-parameter-catalog.js +458 -0
  215. package/lib/parameters/infra-parameter-validate.js +64 -0
  216. package/lib/schema/application-schema.json +37 -17
  217. package/lib/schema/datasource-test-run.schema.json +493 -0
  218. package/lib/schema/deployment-rules.yaml +102 -63
  219. package/lib/schema/external-datasource.schema.json +1201 -433
  220. package/lib/schema/external-system.schema.json +181 -5
  221. package/lib/schema/flag-map-validation-run.json +31 -0
  222. package/lib/schema/infra-parameter.schema.json +106 -0
  223. package/lib/schema/infra.parameter.yaml +421 -0
  224. package/lib/schema/type/credential-auth-templates.json +40 -0
  225. package/lib/schema/type/document-storage.json +213 -0
  226. package/lib/schema/type/message-service.json +123 -0
  227. package/lib/schema/type/vector-store.json +88 -0
  228. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  229. package/lib/utils/api-error-handler.js +2 -2
  230. package/lib/utils/api.js +49 -14
  231. package/lib/utils/app-config-resolver.js +23 -1
  232. package/lib/utils/app-register-api.js +3 -2
  233. package/lib/utils/app-register-auth.js +1 -1
  234. package/lib/utils/app-register-config.js +4 -4
  235. package/lib/utils/app-register-display.js +3 -2
  236. package/lib/utils/app-register-validator.js +3 -2
  237. package/lib/utils/app-run-containers.js +26 -22
  238. package/lib/utils/app-scoped-config.js +31 -0
  239. package/lib/utils/app-service-env-from-builder.js +164 -0
  240. package/lib/utils/build-copy.js +1 -1
  241. package/lib/utils/build-helpers.js +20 -20
  242. package/lib/utils/build-resolve-image.js +165 -0
  243. package/lib/utils/cli-layout-chalk.js +8 -0
  244. package/lib/utils/cli-test-layout-chalk.js +267 -0
  245. package/lib/utils/cli-utils.js +88 -11
  246. package/lib/utils/compose-db-passwords.js +138 -0
  247. package/lib/utils/compose-generate-docker-compose.js +216 -0
  248. package/lib/utils/compose-generator.js +197 -291
  249. package/lib/utils/compose-miso-env.js +18 -0
  250. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  251. package/lib/utils/config-paths.js +209 -6
  252. package/lib/utils/config-scoped-resources-preference.js +41 -0
  253. package/lib/utils/controller-deployment-outcome.js +68 -0
  254. package/lib/utils/credential-display.js +2 -2
  255. package/lib/utils/credential-secrets-env.js +16 -1
  256. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  257. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  258. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  259. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  260. package/lib/utils/datasource-test-run-display.js +442 -0
  261. package/lib/utils/datasource-test-run-exit.js +58 -0
  262. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  263. package/lib/utils/datasource-test-run-report-version.js +51 -0
  264. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  265. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  266. package/lib/utils/datasource-validation-watch.js +266 -0
  267. package/lib/utils/declarative-url-ports.js +47 -0
  268. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  269. package/lib/utils/dev-ca-install.js +185 -23
  270. package/lib/utils/dev-cert-helper.js +266 -17
  271. package/lib/utils/dev-hosts-helper.js +307 -0
  272. package/lib/utils/dev-init-cert-hints.js +37 -0
  273. package/lib/utils/dev-init-health-messages.js +52 -0
  274. package/lib/utils/dev-init-resolve.js +86 -0
  275. package/lib/utils/dev-init-ssh-merge.js +65 -0
  276. package/lib/utils/dev-ssh-config-helper.js +196 -0
  277. package/lib/utils/dev-user-groups.js +93 -0
  278. package/lib/utils/docker-build.js +42 -17
  279. package/lib/utils/docker-exec.js +28 -0
  280. package/lib/utils/docker-manifest-public-port.js +116 -0
  281. package/lib/utils/docker-not-running-hint.js +52 -0
  282. package/lib/utils/docker.js +98 -11
  283. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  284. package/lib/utils/env-config-loader.js +10 -91
  285. package/lib/utils/env-copy.js +19 -10
  286. package/lib/utils/env-map.js +42 -11
  287. package/lib/utils/env-template.js +2 -2
  288. package/lib/utils/environment-scoped-resources.js +144 -0
  289. package/lib/utils/error-formatter.js +125 -9
  290. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  291. package/lib/utils/error-formatters/network-errors.js +2 -1
  292. package/lib/utils/error-formatters/permission-errors.js +2 -1
  293. package/lib/utils/error-formatters/validation-errors.js +2 -1
  294. package/lib/utils/external-env-template.js +180 -0
  295. package/lib/utils/external-readme.js +8 -1
  296. package/lib/utils/external-system-display.js +277 -136
  297. package/lib/utils/external-system-local-test-tty.js +389 -0
  298. package/lib/utils/external-system-readiness-core.js +377 -0
  299. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  300. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  301. package/lib/utils/external-system-readiness-display.js +186 -0
  302. package/lib/utils/external-system-test-helpers.js +24 -6
  303. package/lib/utils/external-system-validators.js +32 -14
  304. package/lib/utils/health-check-url.js +119 -0
  305. package/lib/utils/health-check.js +59 -25
  306. package/lib/utils/help-builder.js +14 -13
  307. package/lib/utils/image-version.js +4 -8
  308. package/lib/utils/infra-containers.js +4 -7
  309. package/lib/utils/infra-env-defaults.js +162 -0
  310. package/lib/utils/infra-status-display.js +167 -0
  311. package/lib/utils/infra-status.js +16 -8
  312. package/lib/utils/local-secrets.js +29 -7
  313. package/lib/utils/paths.js +136 -48
  314. package/lib/utils/port-resolver.js +10 -23
  315. package/lib/utils/redis-env-scope.js +62 -0
  316. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  317. package/lib/utils/remote-builder-validation.js +99 -0
  318. package/lib/utils/remote-dev-auth.js +117 -21
  319. package/lib/utils/remote-docker-env.js +67 -15
  320. package/lib/utils/remote-secrets-loader.js +13 -4
  321. package/lib/utils/resolve-docker-image-ref.js +124 -0
  322. package/lib/utils/schema-loader.js +22 -9
  323. package/lib/utils/secrets-bash-kv.js +25 -0
  324. package/lib/utils/secrets-generator.js +171 -51
  325. package/lib/utils/secrets-helpers.js +70 -59
  326. package/lib/utils/secrets-kv-scope.js +60 -0
  327. package/lib/utils/secrets-utils.js +35 -37
  328. package/lib/utils/secrets-validation.js +3 -1
  329. package/lib/utils/secrets-yaml-preserve.js +109 -0
  330. package/lib/utils/secure-file-permissions.js +91 -0
  331. package/lib/utils/ssh-key-helper.js +4 -2
  332. package/lib/utils/template-helpers.js +2 -2
  333. package/lib/utils/test-log-writer.js +3 -3
  334. package/lib/utils/token-manager.js +37 -5
  335. package/lib/utils/url-declarative-public-base.js +188 -0
  336. package/lib/utils/url-declarative-resolve-build.js +493 -0
  337. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  338. package/lib/utils/url-declarative-resolve.js +220 -0
  339. package/lib/utils/url-declarative-token-parse.js +74 -0
  340. package/lib/utils/url-declarative-url-flags.js +50 -0
  341. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  342. package/lib/utils/url-public-path-prefix.js +34 -0
  343. package/lib/utils/urls-local-registry.js +220 -0
  344. package/lib/utils/validation-report-tty-kit.js +77 -0
  345. package/lib/utils/validation-run-poll.js +89 -0
  346. package/lib/utils/validation-run-post-retry.js +73 -0
  347. package/lib/utils/validation-run-request.js +98 -0
  348. package/lib/utils/variable-transformer.js +21 -4
  349. package/lib/utils/yaml-preserve.js +78 -1
  350. package/lib/validation/datasource-warnings.js +56 -0
  351. package/lib/validation/env-template-auth.js +50 -2
  352. package/lib/validation/external-manifest-validator.js +35 -7
  353. package/lib/validation/validate-display.js +37 -31
  354. package/lib/validation/validate.js +9 -10
  355. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  356. package/lib/validation/validator.js +32 -78
  357. package/lib/validation/wizard-config-validator.js +2 -1
  358. package/package.json +11 -3
  359. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  360. package/scripts/diagnose-cli.js +150 -0
  361. package/scripts/install-local.js +304 -55
  362. package/templates/README.md +15 -2
  363. package/templates/applications/dataplane/application.yaml +52 -2
  364. package/templates/applications/dataplane/env.template +80 -18
  365. package/templates/applications/dataplane/rbac.yaml +8 -0
  366. package/templates/applications/keycloak/application.yaml +9 -1
  367. package/templates/applications/keycloak/env.template +15 -6
  368. package/templates/applications/miso-controller/application.yaml +10 -2
  369. package/templates/applications/miso-controller/env.template +55 -14
  370. package/templates/applications/miso-controller/rbac.yaml +5 -0
  371. package/templates/external-system/README.md.hbs +20 -7
  372. package/templates/external-system/deploy.js.hbs +5 -5
  373. package/templates/external-system/env.template.hbs +22 -0
  374. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  375. package/templates/infra/compose.yaml.hbs +20 -4
  376. package/templates/python/docker-compose.hbs +16 -0
  377. package/templates/typescript/docker-compose.hbs +16 -0
  378. package/integration/hubspot/README.md +0 -102
  379. package/integration/hubspot/env.template +0 -4
  380. package/integration/hubspot/hubspot-datasource-company.json +0 -541
  381. package/integration/hubspot/hubspot-datasource-contact.json +0 -639
  382. package/integration/hubspot/hubspot-datasource-deal.json +0 -588
  383. package/integration/hubspot/hubspot-datasource-users.json +0 -116
  384. package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
  385. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
  386. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
  387. package/lib/api/external-test.api.js +0 -111
  388. package/lib/schema/env-config.yaml +0 -43
  389. /package/integration/{hubspot → hubspot-test}/companies.json +0 -0
  390. /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-app-name.yaml +0 -0
  391. /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-missing-app.yaml +0 -0
  392. /package/integration/{hubspot → hubspot-test}/test-dataplane-down-helpers.js +0 -0
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Shared output for `aifabrix status`: title + configuration block.
3
+ * TLS/SSL (`infraTlsSslCell`) and `devNN` title match `dev-show-display.js`; environment, Traefik,
4
+ * and scoped-resources lines use uppercase ON/OFF-style labels for status output.
5
+ *
6
+ * @fileoverview Infra status CLI display helpers
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const config = require('../core/config');
14
+ const logger = require('./logger');
15
+
16
+ /** En dash for unset / empty values (same as dev-show-display). */
17
+ const EM = '\u2013';
18
+ const LABEL_W = 18;
19
+
20
+ /**
21
+ * @param {unknown} v
22
+ * @returns {boolean}
23
+ */
24
+ function isUnset(v) {
25
+ return v === null || v === undefined || v === '';
26
+ }
27
+
28
+ /**
29
+ * @param {unknown} v
30
+ * @returns {string}
31
+ */
32
+ function cell(v) {
33
+ return isUnset(v) ? EM : String(v);
34
+ }
35
+
36
+ /**
37
+ * @param {string} label
38
+ * @param {unknown} value
39
+ */
40
+ function logPaddedFieldRow(label, value) {
41
+ logger.log(` ${label.padEnd(LABEL_W)} ${cell(value)}`);
42
+ }
43
+
44
+ /**
45
+ * @param {string|null|undefined} remoteUrl
46
+ * @returns {string}
47
+ */
48
+ function hostFromRemoteUrl(remoteUrl) {
49
+ if (!remoteUrl || typeof remoteUrl !== 'string') {
50
+ return '';
51
+ }
52
+ try {
53
+ const u = new URL(remoteUrl.trim());
54
+ return u.hostname || '';
55
+ } catch {
56
+ return '';
57
+ }
58
+ }
59
+
60
+ /**
61
+ * @param {string} devIdStr
62
+ * @returns {string} e.g. dev02
63
+ */
64
+ function devProfileHandle(devIdStr) {
65
+ const s = String(devIdStr);
66
+ if (/^[0-9]+$/.test(s)) {
67
+ return `dev${s.padStart(2, '0')}`;
68
+ }
69
+ return `dev${s}`;
70
+ }
71
+
72
+ /**
73
+ * Infra / compose TLS mode from ~/.aifabrix `tlsEnabled` (not Docker TLS verify).
74
+ * @param {boolean} tlsEnabled
75
+ * @returns {string}
76
+ */
77
+ function infraTlsSslCell(tlsEnabled) {
78
+ return tlsEnabled ? 'ON 🔒' : 'OFF 🕐';
79
+ }
80
+
81
+ /**
82
+ * Traefik proxy row (`traefik: true` in config). Icons only (no ANSI color).
83
+ * @param {boolean} traefikEnabled
84
+ * @returns {string}
85
+ */
86
+ function statusTraefikProxyCell(traefikEnabled) {
87
+ return traefikEnabled ? 'ON 🟢' : 'OFF 🟡';
88
+ }
89
+
90
+ /**
91
+ * Environment for status (uppercase).
92
+ * @param {unknown} environment
93
+ * @returns {string}
94
+ */
95
+ function formatStatusEnvironment(environment) {
96
+ if (isUnset(environment)) {
97
+ return EM;
98
+ }
99
+ return String(environment).trim().toUpperCase();
100
+ }
101
+
102
+ /**
103
+ * Scoped resources for status: ON vs OFF (DEFAULT).
104
+ * @param {boolean} useScoped
105
+ * @returns {string}
106
+ */
107
+ function statusScopedResourcesCell(useScoped) {
108
+ return useScoped ? 'ON' : 'OFF (DEFAULT)';
109
+ }
110
+
111
+ /**
112
+ * Single-line title for `aifabrix status` (same dev profile + remote host pattern as dev show).
113
+ * @param {string} devIdStr
114
+ * @param {string|null|undefined} remoteServer
115
+ * @returns {string}
116
+ */
117
+ function formatInfraStatusTitleLine(devIdStr, remoteServer) {
118
+ const who = devProfileHandle(devIdStr);
119
+ const host = hostFromRemoteUrl(remoteServer);
120
+ if (host) {
121
+ return `📊 Infrastructure Status (${who} @ ${host})`;
122
+ }
123
+ return `📊 Infrastructure Status (${who})`;
124
+ }
125
+
126
+ /**
127
+ * @param {{ remoteServer: string|null|undefined, tlsEnabled: boolean, environment: unknown, useScoped: boolean, traefikEnabled: boolean }} p
128
+ */
129
+ function logInfraStatusConfigurationSummary(p) {
130
+ logger.log('⚙️ Configuration');
131
+ logPaddedFieldRow('Server', p.remoteServer);
132
+ logPaddedFieldRow('TLS/SSL', infraTlsSslCell(p.tlsEnabled));
133
+ logPaddedFieldRow('Traefik proxy', statusTraefikProxyCell(p.traefikEnabled));
134
+ logPaddedFieldRow('Environment', formatStatusEnvironment(p.environment));
135
+ logPaddedFieldRow('Scoped resources', statusScopedResourcesCell(p.useScoped));
136
+ logger.log('');
137
+ }
138
+
139
+ /**
140
+ * @returns {Promise<{ devIdStr: string, remoteServer: string|null|undefined, tlsEnabled: boolean, environment: unknown, useScoped: boolean, traefikEnabled: boolean }>}
141
+ */
142
+ async function loadInfraStatusSummary() {
143
+ const [rawDevId, environment, tlsEnabled, remoteServer, useScoped, traefikEnabled] = await Promise.all([
144
+ config.getDeveloperId(),
145
+ config.getCurrentEnvironment(),
146
+ config.getTlsEnabled(),
147
+ config.getRemoteServer(),
148
+ config.getUseEnvironmentScopedResources(),
149
+ config.getTraefikEnabled()
150
+ ]);
151
+ const devIdStr = rawDevId === null || rawDevId === undefined ? '0' : String(rawDevId);
152
+ return {
153
+ devIdStr,
154
+ environment,
155
+ tlsEnabled,
156
+ remoteServer,
157
+ useScoped,
158
+ traefikEnabled
159
+ };
160
+ }
161
+
162
+ module.exports = {
163
+ loadInfraStatusSummary,
164
+ formatInfraStatusTitleLine,
165
+ logInfraStatusConfigurationSummary,
166
+ logPaddedFieldRow
167
+ };
@@ -9,13 +9,10 @@
9
9
  * @version 2.0.0
10
10
  */
11
11
 
12
- const { exec } = require('child_process');
13
- const { promisify } = require('util');
14
12
  const config = require('../core/config');
15
13
  const devConfig = require('./dev-config');
16
14
  const containerUtils = require('./infra-containers');
17
-
18
- const execAsync = promisify(exec);
15
+ const { execWithDockerEnv } = require('./docker-exec');
19
16
 
20
17
  /**
21
18
  * Builds services config map from ports and config flags.
@@ -52,7 +49,7 @@ async function getServiceStatus(serviceName, serviceConfig, devId) {
52
49
  try {
53
50
  const containerName = await containerUtils.findContainer(serviceName, devId, { strict: true });
54
51
  const rawStatus = containerName
55
- ? (await execAsync(`docker inspect --format='{{.State.Status}}' ${containerName}`)).stdout.trim().replace(/['"]/g, '')
52
+ ? (await execWithDockerEnv(`docker inspect --format='{{.State.Status}}' ${containerName}`)).stdout.trim().replace(/['"]/g, '')
56
53
  : 'not running';
57
54
  return { status: rawStatus, port: serviceConfig.port, url: serviceConfig.url };
58
55
  } catch {
@@ -107,6 +104,9 @@ function getInfraContainerNames(devIdNum, devId) {
107
104
  /** Suffixes for init/helper containers to exclude from "Running Applications" (e.g. keycloak-db-init) */
108
105
  const INIT_CONTAINER_SUFFIXES = ['-db-init', '-init'];
109
106
 
107
+ /** Names like aifabrix-dev02-postgres belong to isolated developer stacks, not legacy dev-0 mode */
108
+ const DEV_PREFIXED_CONTAINER = /^aifabrix-dev\d+-/;
109
+
110
110
  /**
111
111
  * Extracts app name from container name
112
112
  * @param {string} containerName - Container name
@@ -115,6 +115,9 @@ const INIT_CONTAINER_SUFFIXES = ['-db-init', '-init'];
115
115
  * @returns {string|null} App name or null if not matched
116
116
  */
117
117
  function extractAppName(containerName, devIdNum, devId) {
118
+ if (devIdNum === 0 && DEV_PREFIXED_CONTAINER.test(containerName)) {
119
+ return null;
120
+ }
118
121
  const pattern = devIdNum === 0 ? /^aifabrix-(.+)$/ : new RegExp(`^aifabrix-dev${devId}-(.+)$`);
119
122
  const match = containerName.match(pattern);
120
123
  if (!match) return null;
@@ -176,7 +179,7 @@ async function getAppStatus() {
176
179
  try {
177
180
  const devIdNum = parseInt(devId, 10);
178
181
  const filterPattern = devIdNum === 0 ? 'aifabrix-' : `aifabrix-dev${devId}-`;
179
- const { stdout } = await execAsync(`docker ps --filter "name=${filterPattern}" --format "{{.Names}}\t{{.Ports}}\t{{.Status}}"`);
182
+ const { stdout } = await execWithDockerEnv(`docker ps --filter "name=${filterPattern}" --format "{{.Names}}\t{{.Ports}}\t{{.Status}}"`);
180
183
  const lines = stdout.trim().split('\n').filter(line => line.trim() !== '');
181
184
  const infraContainers = getInfraContainerNames(devIdNum, devId);
182
185
  for (const line of lines) {
@@ -211,9 +214,14 @@ async function listAppContainerNamesForDeveloper(devId, options = {}) {
211
214
  const includeExited = !!options.includeExited;
212
215
  try {
213
216
  const allFlag = includeExited ? ' -a' : '';
214
- const { stdout } = await execAsync(`docker ps${allFlag} --filter "name=${filterPattern}" --format "{{.Names}}"`);
217
+ const { stdout } = await execWithDockerEnv(`docker ps${allFlag} --filter "name=${filterPattern}" --format "{{.Names}}"`);
215
218
  const names = (stdout || '').trim().split('\n').filter(Boolean);
216
- return names.filter(n => !infraContainers.includes(n));
219
+ return names.filter(n => {
220
+ if (devIdNum === 0 && DEV_PREFIXED_CONTAINER.test(n)) {
221
+ return false;
222
+ }
223
+ return !infraContainers.includes(n);
224
+ });
217
225
  } catch {
218
226
  return [];
219
227
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Local Secrets Management Utilities
3
3
  *
4
- * Helper functions for managing local secrets in ~/.aifabrix/secrets.local.yaml
4
+ * Helper functions for managing local secrets in getPrimaryUserSecretsLocalPath() (config dir)
5
5
  *
6
6
  * @fileoverview Local secrets management utilities
7
7
  * @author AI Fabrix Team
@@ -15,10 +15,30 @@ const logger = require('../utils/logger');
15
15
  const pathsUtil = require('./paths');
16
16
  const { mergeSecretsIntoFile } = require('./secrets-generator');
17
17
 
18
+ /** Bootstrap key name; never encrypt this key's value when writing (key is stored in config). */
19
+ const ENCRYPTION_KEY_VAULT = 'secrets-encryptionKeyVault';
20
+
21
+ /**
22
+ * Resolves value to write: encrypted (secure://) when encryption key is set and key is not the bootstrap key.
23
+ * @async
24
+ * @param {string} key - Secret key name
25
+ * @param {string} value - Secret value
26
+ * @returns {Promise<string>} Value to write (plaintext or secure://...)
27
+ */
28
+ async function resolveValueForWrite(key, value) {
29
+ const config = require('../core/config');
30
+ const encryptionKey = await config.getSecretsEncryptionKey();
31
+ if (!encryptionKey || key === ENCRYPTION_KEY_VAULT) {
32
+ return typeof value === 'string' ? value : String(value);
33
+ }
34
+ const { encryptSecret } = require('./secrets-encryption');
35
+ return encryptSecret(typeof value === 'string' ? value : String(value), encryptionKey);
36
+ }
37
+
18
38
  /**
19
- * Saves a secret to ~/.aifabrix/secrets.local.yaml
20
- * Uses paths.getAifabrixHome() to respect config.yaml aifabrix-home override
21
- * Merges the key into the file (updates in place if key already exists, e.g. after rotate-secret)
39
+ * Saves a secret to the primary user secrets file (getPrimaryUserSecretsLocalPath)
40
+ * Merges the key into the file (updates in place if key already exists, e.g. after rotate-secret).
41
+ * Encrypts the value when a secrets-encryption key is configured (except for the bootstrap key).
22
42
  *
23
43
  * @async
24
44
  * @function saveLocalSecret
@@ -39,8 +59,9 @@ async function saveLocalSecret(key, value) {
39
59
  throw new Error('Secret value is required');
40
60
  }
41
61
 
42
- const secretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
43
- mergeSecretsIntoFile(secretsPath, { [key]: value });
62
+ const valueToWrite = await resolveValueForWrite(key, value);
63
+ const secretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
64
+ mergeSecretsIntoFile(secretsPath, { [key]: valueToWrite });
44
65
  }
45
66
 
46
67
  /**
@@ -121,8 +142,9 @@ function _loadExistingSecrets(resolvedPath) {
121
142
  async function saveSecret(key, value, secretsPath) {
122
143
  validateSaveSecretParams(key, value, secretsPath);
123
144
 
145
+ const valueToWrite = await resolveValueForWrite(key, value);
124
146
  const resolvedPath = resolveAndPrepareSecretsPath(secretsPath);
125
- mergeSecretsIntoFile(resolvedPath, { [key]: value });
147
+ mergeSecretsIntoFile(resolvedPath, { [key]: valueToWrite });
126
148
  }
127
149
 
128
150
  /**
@@ -12,6 +12,11 @@
12
12
  const path = require('path');
13
13
  const fs = require('fs');
14
14
  const yaml = require('js-yaml');
15
+ const { nodeFs } = require('../internal/node-fs');
16
+ const {
17
+ getAifabrixRuntimeConfigDir,
18
+ resolveAifabrixHomeLikePath
19
+ } = require('./aifabrix-runtime-config-dir');
15
20
 
16
21
  function safeHomedir() {
17
22
  try {
@@ -29,30 +34,46 @@ function safeHomedir() {
29
34
  }
30
35
 
31
36
  /**
32
- * Returns the path to the config directory (same precedence as config.js so both read the same config).
33
- * Priority: AIFABRIX_CONFIG (dirname) AIFABRIX_HOME ~/.aifabrix.
37
+ * Returns the path to the config directory (same as {@link getAifabrixRuntimeConfigDir} / config.js).
38
+ * When `AIFABRIX_HOME` is `$HOME` but `config.yaml` is only under `$HOME/.aifabrix/`, returns the latter.
34
39
  * @returns {string} Absolute path to config directory
35
40
  */
36
41
  function getConfigDirForPaths() {
37
- const configFile = process.env.AIFABRIX_CONFIG && typeof process.env.AIFABRIX_CONFIG === 'string';
38
- if (configFile) {
39
- return path.dirname(path.resolve(process.env.AIFABRIX_CONFIG.trim()));
40
- }
41
- if (process.env.AIFABRIX_HOME && typeof process.env.AIFABRIX_HOME === 'string') {
42
- return path.resolve(process.env.AIFABRIX_HOME.trim());
43
- }
44
- return path.join(safeHomedir(), '.aifabrix');
42
+ return getAifabrixRuntimeConfigDir();
43
+ }
44
+
45
+ /**
46
+ * User-owned `secrets.local.yaml` beside effective `config.yaml` (see {@link getConfigDirForPaths}).
47
+ * When `AIFABRIX_HOME` is `$HOME` but config is only at `$HOME/.aifabrix/config.yaml`, uses that folder.
48
+ * Keeps `secret list` / `secret set` aligned with resolve merge (`loadPrimaryUserSecrets`).
49
+ *
50
+ * @returns {string} Absolute path to secrets.local.yaml
51
+ */
52
+ function getPrimaryUserSecretsLocalPath() {
53
+ return path.join(getConfigDirForPaths(), 'secrets.local.yaml');
54
+ }
55
+
56
+ /**
57
+ * Directory for CLI system state next to `config.yaml`: `admin-secrets.env`, `infra/` or `infra-dev{id}/`,
58
+ * `audit.log`, etc. Unlike {@link getAifabrixHome}, this follows the resolved config directory (e.g.
59
+ * `~/.aifabrix` when config lives there even if `aifabrix-home` / `AIFABRIX_HOME` is `$HOME`).
60
+ *
61
+ * @returns {string} Absolute path (same as {@link getConfigDirForPaths})
62
+ */
63
+ function getAifabrixSystemDir() {
64
+ return getConfigDirForPaths();
45
65
  }
46
66
 
47
67
  /**
48
68
  * Returns the base AI Fabrix directory.
49
- * Priority: AIFABRIX_HOME env → config.yaml `aifabrix-home` (from AIFABRIX_HOME or ~/.aifabrix) → ~/.aifabrix.
69
+ * Priority: AIFABRIX_HOME env → config.yaml `aifabrix-home` → ~/.aifabrix.
70
+ * Builder-server SSH provisioning sets `aifabrix-home` to the user POSIX home; dev `.bashrc` exports AIFABRIX_HOME from that key (default $HOME).
50
71
  *
51
72
  * @returns {string} Absolute path to the AI Fabrix home directory
52
73
  */
53
74
  function getAifabrixHome() {
54
75
  if (process.env.AIFABRIX_HOME && typeof process.env.AIFABRIX_HOME === 'string') {
55
- return path.resolve(process.env.AIFABRIX_HOME.trim());
76
+ return resolveAifabrixHomeLikePath(process.env.AIFABRIX_HOME.trim());
56
77
  }
57
78
  const isTestEnv = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
58
79
  if (!isTestEnv) {
@@ -64,7 +85,7 @@ function getAifabrixHome() {
64
85
  const config = yaml.load(content) || {};
65
86
  const homeOverride = config && typeof config['aifabrix-home'] === 'string' ? config['aifabrix-home'].trim() : '';
66
87
  if (homeOverride) {
67
- return path.resolve(homeOverride);
88
+ return resolveAifabrixHomeLikePath(homeOverride);
68
89
  }
69
90
  }
70
91
  } catch {
@@ -74,6 +95,40 @@ function getAifabrixHome() {
74
95
  return path.join(safeHomedir(), '.aifabrix');
75
96
  }
76
97
 
98
+ /**
99
+ * Default git / workspace root from env or config (optional; no fallback to aifabrix-home).
100
+ * Priority: AIFABRIX_WORK env (trim, resolve) → config.yaml `aifabrix-work` → null.
101
+ *
102
+ * @returns {string|null} Absolute path or null when unset
103
+ */
104
+ function getAifabrixWork() {
105
+ if (process.env.AIFABRIX_WORK && typeof process.env.AIFABRIX_WORK === 'string') {
106
+ const t = process.env.AIFABRIX_WORK.trim();
107
+ if (t) {
108
+ return resolveAifabrixHomeLikePath(t);
109
+ }
110
+ }
111
+ const isTestEnv = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
112
+ if (!isTestEnv) {
113
+ try {
114
+ const configDir = getConfigDirForPaths();
115
+ const configPath = path.join(configDir, 'config.yaml');
116
+ if (fs.existsSync(configPath)) {
117
+ const content = fs.readFileSync(configPath, 'utf8');
118
+ const config = yaml.load(content) || {};
119
+ const workOverride =
120
+ config && typeof config['aifabrix-work'] === 'string' ? config['aifabrix-work'].trim() : '';
121
+ if (workOverride) {
122
+ return resolveAifabrixHomeLikePath(workOverride);
123
+ }
124
+ }
125
+ } catch {
126
+ // ignore
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+
77
132
  // Cache project root to avoid repeated filesystem lookups
78
133
  let cachedProjectRoot = null;
79
134
 
@@ -92,7 +147,8 @@ function clearProjectRootCache() {
92
147
  */
93
148
  function hasPackageJson(dirPath) {
94
149
  const packageJsonPath = path.join(dirPath, 'package.json');
95
- return fs.existsSync(packageJsonPath);
150
+ // Real disk: Jest workers may retain jest.mock('fs') from other suites; project root must stay truthful.
151
+ return nodeFs().existsSync(packageJsonPath);
96
152
  }
97
153
 
98
154
  /**
@@ -215,22 +271,31 @@ function tryFindProjectRoot() {
215
271
  }
216
272
 
217
273
  function getProjectRoot() {
218
- // Return cached value if available and valid
274
+ // Prefer global.PROJECT_ROOT whenever it is valid so tests that override it (or restore it)
275
+ // are not defeated by a stale cachedProjectRoot from an earlier call in the same Jest worker.
276
+ const globalRoot = checkGlobalProjectRoot();
277
+ if (globalRoot) {
278
+ cachedProjectRoot = globalRoot;
279
+ return globalRoot;
280
+ }
219
281
  if (cachedProjectRoot && hasPackageJson(cachedProjectRoot)) {
220
282
  return cachedProjectRoot;
221
283
  }
222
-
223
284
  return tryFindProjectRoot();
224
285
  }
225
286
 
226
287
  /**
227
- * Returns the applications base directory. Dev 0: <home>/applications; Dev > 0: <home>/applications-dev-{id}
288
+ * Returns the applications base directory next to effective `config.yaml` (same root as infra, secrets, audit).
289
+ * Dev 0: `<configDir>/applications`; non-zero dev: `<configDir>/applications-dev-{id}`.
290
+ * Uses {@link getAifabrixSystemDir}, not raw {@link getAifabrixHome}, so builder-server layouts with
291
+ * `AIFABRIX_HOME=$HOME` and config under `~/.aifabrix/` keep apps under `.aifabrix` (not `$HOME/applications-dev-*`).
292
+ *
228
293
  * @param {number|string} developerId - Developer ID
229
294
  * @returns {string} Absolute path to applications base directory
230
295
  */
231
296
  function getApplicationsBaseDir(developerId) {
232
297
  const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
233
- const base = getAifabrixHome();
298
+ const base = getAifabrixSystemDir();
234
299
  if (idNum === 0) {
235
300
  return path.join(base, 'applications');
236
301
  }
@@ -249,7 +314,8 @@ function getDevDirectory(appName, developerId) {
249
314
  }
250
315
 
251
316
  /**
252
- * Gets the application path (builder or integration folder)
317
+ * Gets the application path (builder or integration folder).
318
+ * Matches getBuilderPath / getIntegrationPath: respects AIFABRIX_BUILDER_DIR and project-root vs cwd base.
253
319
  * @param {string} appName - Application name
254
320
  * @param {string} [appType] - Application type ('external' or other)
255
321
  * @returns {string} Absolute path to application directory
@@ -258,9 +324,10 @@ function getAppPath(appName, appType) {
258
324
  if (!appName || typeof appName !== 'string') {
259
325
  throw new Error('App name is required and must be a string');
260
326
  }
261
-
262
- const baseDir = appType === 'external' ? 'integration' : 'builder';
263
- return path.join(process.cwd(), baseDir, appName);
327
+ if (appType === 'external') {
328
+ return getIntegrationPath(appName);
329
+ }
330
+ return path.join(getBuilderRoot(), appName);
264
331
  }
265
332
 
266
333
  /**
@@ -299,28 +366,45 @@ function getBuilderRoot() {
299
366
  return path.join(getIntegrationBuilderBaseDir(), 'builder');
300
367
  }
301
368
 
369
+ /**
370
+ * True when name under root is a real directory (follows symlinks). False for broken symlinks, files, ENOENT.
371
+ * @param {string} root - Parent directory (must exist)
372
+ * @param {string} name - Entry from readdir
373
+ * @returns {boolean}
374
+ */
375
+ function isAppSubdirSync(root, name) {
376
+ if (!name || name.startsWith('.')) return false;
377
+ const fullPath = path.join(root, name);
378
+ try {
379
+ const st = nodeFs().statSync(fullPath);
380
+ return Boolean(st && typeof st.isDirectory === 'function' && st.isDirectory());
381
+ } catch {
382
+ return false;
383
+ }
384
+ }
385
+
302
386
  /**
303
387
  * Lists app names (directories) under integration root. Excludes dot-prefixed entries.
304
388
  * Returns [] if root does not exist.
305
389
  * @returns {string[]} Sorted list of app directory names
306
390
  */
307
391
  function listIntegrationAppNames() {
392
+ const disk = nodeFs();
308
393
  const root = getIntegrationRoot();
309
- if (!fs.existsSync(root)) {
394
+ if (!disk.existsSync(root)) {
395
+ return [];
396
+ }
397
+ let rootStat;
398
+ try {
399
+ rootStat = disk.statSync(root);
400
+ } catch {
310
401
  return [];
311
402
  }
312
- const stat = fs.statSync(root);
313
- if (!stat || typeof stat.isDirectory !== 'function' || !stat.isDirectory()) {
403
+ if (!rootStat || typeof rootStat.isDirectory !== 'function' || !rootStat.isDirectory()) {
314
404
  return [];
315
405
  }
316
- const entries = fs.readdirSync(root);
317
- return entries
318
- .filter(name => !name.startsWith('.'))
319
- .filter(name => {
320
- const fullPath = path.join(root, name);
321
- return fs.statSync(fullPath).isDirectory();
322
- })
323
- .sort();
406
+ const entries = disk.readdirSync(root);
407
+ return entries.filter((name) => isAppSubdirSync(root, name)).sort();
324
408
  }
325
409
 
326
410
  /**
@@ -329,22 +413,22 @@ function listIntegrationAppNames() {
329
413
  * @returns {string[]} Sorted list of app directory names
330
414
  */
331
415
  function listBuilderAppNames() {
416
+ const disk = nodeFs();
332
417
  const root = getBuilderRoot();
333
- if (!fs.existsSync(root)) {
418
+ if (!disk.existsSync(root)) {
419
+ return [];
420
+ }
421
+ let rootStat;
422
+ try {
423
+ rootStat = disk.statSync(root);
424
+ } catch {
334
425
  return [];
335
426
  }
336
- const stat = fs.statSync(root);
337
- if (!stat || typeof stat.isDirectory !== 'function' || !stat.isDirectory()) {
427
+ if (!rootStat || typeof rootStat.isDirectory !== 'function' || !rootStat.isDirectory()) {
338
428
  return [];
339
429
  }
340
- const entries = fs.readdirSync(root);
341
- return entries
342
- .filter(name => !name.startsWith('.'))
343
- .filter(name => {
344
- const fullPath = path.join(root, name);
345
- return fs.statSync(fullPath).isDirectory();
346
- })
347
- .sort();
430
+ const entries = disk.readdirSync(root);
431
+ return entries.filter((name) => isAppSubdirSync(root, name)).sort();
348
432
  }
349
433
 
350
434
  /**
@@ -387,7 +471,7 @@ function getBuilderPath(appName) {
387
471
  ? process.env.AIFABRIX_BUILDER_DIR.trim()
388
472
  : null;
389
473
  if (builderRoot) {
390
- return path.join(builderRoot, appName);
474
+ return path.join(path.resolve(builderRoot), appName);
391
475
  }
392
476
  const base = getIntegrationBuilderBaseDir();
393
477
  return path.join(base, 'builder', appName);
@@ -430,7 +514,7 @@ function getDeployJsonPath(appName, appType, preferNew = false) {
430
514
  // If neither exists, return new naming (for generation)
431
515
  return newPath;
432
516
  }
433
- const { resolveApplicationConfigPath } = require('./app-config-resolver');
517
+ const { resolveApplicationConfigPath, resolveRbacPath } = require('./app-config-resolver');
434
518
  const { loadConfigFile } = require('./config-format');
435
519
  /**
436
520
  * Checks if app type is external from variables object
@@ -518,7 +602,7 @@ async function detectAppType(appName, _options = {}) {
518
602
 
519
603
  /**
520
604
  * Resolve-specific app path: prefer integration + env.template only (env-only mode).
521
- * If integration/<appName>/env.template exists, use that directory without requiring application.yaml.
605
+ * If integration/<systemKey>/env.template exists, use that directory without requiring application.yaml.
522
606
  * Otherwise fall back to detectAppType (integration or builder with full config).
523
607
  *
524
608
  * @param {string} appName - Application name
@@ -538,7 +622,7 @@ async function getResolveAppPath(appName) {
538
622
  return { appPath: result.appPath, envOnly: false };
539
623
  }
540
624
 
541
- /** Resolve appKey when cwd is inside integration/<appKey>/. */
625
+ /** Resolve app folder name when cwd is inside integration/<systemKey>/. */
542
626
  function resolveIntegrationAppKeyFromCwd() {
543
627
  const integrationNorm = path.resolve(path.join(getIntegrationBuilderBaseDir(), 'integration'));
544
628
  const cwd = path.resolve(process.cwd());
@@ -548,7 +632,10 @@ function resolveIntegrationAppKeyFromCwd() {
548
632
 
549
633
  module.exports = {
550
634
  getAifabrixHome,
635
+ getAifabrixWork,
551
636
  getConfigDirForPaths,
637
+ getAifabrixSystemDir,
638
+ getPrimaryUserSecretsLocalPath,
552
639
  getApplicationsBaseDir,
553
640
  getDevDirectory,
554
641
  getAppPath,
@@ -562,6 +649,7 @@ module.exports = {
562
649
  resolveBuildContext,
563
650
  getDeployJsonPath,
564
651
  resolveApplicationConfigPath,
652
+ resolveRbacPath,
565
653
  detectAppType,
566
654
  getResolveAppPath,
567
655
  resolveIntegrationAppKeyFromCwd,