@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
@@ -1,3 +1,4 @@
1
+ const { formatSuccessLine } = require('./cli-test-layout-chalk');
1
2
  /**
2
3
  * AI Fabrix Builder - App Run Container Helpers
3
4
  *
@@ -9,12 +10,13 @@
9
10
  * @version 2.0.0
10
11
  */
11
12
 
12
- const { exec } = require('child_process');
13
- const { promisify } = require('util');
14
13
  const chalk = require('chalk');
15
14
  const logger = require('./logger');
16
-
17
- const execAsync = promisify(exec);
15
+ const { execWithDockerEnv } = require('./docker-exec');
16
+ const {
17
+ resolveRunContainerName,
18
+ buildDefaultLocalContainerName
19
+ } = require('./environment-scoped-resources');
18
20
 
19
21
  /**
20
22
  * Checks if Docker image exists for the application
@@ -30,7 +32,7 @@ async function checkImageExists(imageName, tag = 'latest', debug = false) {
30
32
  if (debug) {
31
33
  logger.log(chalk.gray(`[DEBUG] Executing: ${cmd}`));
32
34
  }
33
- const { stdout } = await execAsync(cmd);
35
+ const { stdout } = await execWithDockerEnv(cmd);
34
36
  const lines = stdout.trim().split('\n').filter(line => line.trim() !== '');
35
37
  const exists = lines.some(line => line.trim() === fullImageName);
36
38
  if (debug) {
@@ -57,11 +59,15 @@ async function checkImageExists(imageName, tag = 'latest', debug = false) {
57
59
  * @function getContainerName
58
60
  * @param {string} appName - Application name
59
61
  * @param {number|string} developerId - Developer ID
62
+ * @param {{ effectiveEnvironmentScopedResources?: boolean, env?: string }|null} [scopeOpts] - When set (run --env dev|tst + gates), uses env-scoped name
60
63
  * @returns {string} Container name
61
64
  */
62
- function getContainerName(appName, developerId) {
65
+ function getContainerName(appName, developerId, scopeOpts = null) {
63
66
  const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
64
- return idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${developerId}-${appName}`;
67
+ if (scopeOpts && scopeOpts.effectiveEnvironmentScopedResources && scopeOpts.env) {
68
+ return resolveRunContainerName(appName, developerId, true, scopeOpts.env);
69
+ }
70
+ return buildDefaultLocalContainerName(appName, developerId, idNum);
65
71
  }
66
72
 
67
73
  /**
@@ -72,21 +78,21 @@ function getContainerName(appName, developerId) {
72
78
  */
73
79
  async function logContainerDebugInfo(containerName) {
74
80
  const statusCmd = `docker ps --filter "name=${containerName}" --format "{{.Status}}"`;
75
- const { stdout: status } = await execAsync(statusCmd);
81
+ const { stdout: status } = await execWithDockerEnv(statusCmd);
76
82
  const portsCmd = `docker ps --filter "name=${containerName}" --format "{{.Ports}}"`;
77
- const { stdout: ports } = await execAsync(portsCmd);
83
+ const { stdout: ports } = await execWithDockerEnv(portsCmd);
78
84
  logger.log(chalk.gray(`[DEBUG] Container status: ${status.trim()}`));
79
85
  logger.log(chalk.gray(`[DEBUG] Container ports: ${ports.trim()}`));
80
86
  }
81
87
 
82
- async function checkContainerRunning(appName, developerId, debug = false) {
88
+ async function checkContainerRunning(appName, developerId, debug = false, scopeOpts = null) {
83
89
  try {
84
- const containerName = getContainerName(appName, developerId);
90
+ const containerName = getContainerName(appName, developerId, scopeOpts);
85
91
  const cmd = `docker ps --filter "name=${containerName}" --format "{{.Names}}"`;
86
92
  if (debug) {
87
93
  logger.log(chalk.gray(`[DEBUG] Executing: ${cmd}`));
88
94
  }
89
- const { stdout } = await execAsync(cmd);
95
+ const { stdout } = await execWithDockerEnv(cmd);
90
96
  const isRunning = stdout.trim() === containerName;
91
97
  if (debug) {
92
98
  logger.log(chalk.gray(`[DEBUG] Container ${containerName} running: ${isRunning}`));
@@ -109,28 +115,26 @@ async function checkContainerRunning(appName, developerId, debug = false) {
109
115
  * @param {number|string} developerId - Developer ID (0 = default infra, > 0 = developer-specific; string allowed)
110
116
  * @param {boolean} [debug=false] - Enable debug logging
111
117
  */
112
- async function stopAndRemoveContainer(appName, developerId, debug = false) {
118
+ async function stopAndRemoveContainer(appName, developerId, debug = false, scopeOpts = null) {
113
119
  try {
114
- const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
115
- const containerName = idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${developerId}-${appName}`;
120
+ const containerName = getContainerName(appName, developerId, scopeOpts);
116
121
  logger.log(chalk.yellow(`Stopping existing container ${containerName}...`));
117
122
  const stopCmd = `docker stop ${containerName}`;
118
123
  if (debug) {
119
124
  logger.log(chalk.gray(`[DEBUG] Executing: ${stopCmd}`));
120
125
  }
121
- await execAsync(stopCmd);
126
+ await execWithDockerEnv(stopCmd);
122
127
  const rmCmd = `docker rm ${containerName}`;
123
128
  if (debug) {
124
129
  logger.log(chalk.gray(`[DEBUG] Executing: ${rmCmd}`));
125
130
  }
126
- await execAsync(rmCmd);
127
- logger.log(chalk.green(`✓ Container ${containerName} stopped and removed`));
131
+ await execWithDockerEnv(rmCmd);
132
+ logger.log(formatSuccessLine(`Container ${containerName} stopped and removed`));
128
133
  } catch (error) {
129
134
  if (debug) {
130
135
  logger.log(chalk.gray(`[DEBUG] Stop/remove container error: ${error.message}`));
131
136
  }
132
- const idNum2 = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
133
- const containerName = idNum2 === 0 ? `aifabrix-${appName}` : `aifabrix-dev${developerId}-${appName}`;
137
+ const containerName = getContainerName(appName, developerId, scopeOpts);
134
138
  logger.log(chalk.gray(`Container ${containerName} was not running`));
135
139
  }
136
140
  }
@@ -144,9 +148,9 @@ async function stopAndRemoveContainer(appName, developerId, debug = false) {
144
148
  async function logContainerStatus(containerName, debug) {
145
149
  if (debug) {
146
150
  const statusCmd = `docker ps --filter "name=${containerName}" --format "{{.Status}}"`;
147
- const { stdout: status } = await execAsync(statusCmd);
151
+ const { stdout: status } = await execWithDockerEnv(statusCmd);
148
152
  const portsCmd = `docker ps --filter "name=${containerName}" --format "{{.Ports}}"`;
149
- const { stdout: ports } = await execAsync(portsCmd);
153
+ const { stdout: ports } = await execWithDockerEnv(portsCmd);
150
154
  logger.log(chalk.gray(`[DEBUG] Container status: ${status.trim()}`));
151
155
  logger.log(chalk.gray(`[DEBUG] Container ports: ${ports.trim()}`));
152
156
  }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Read per-application environmentScopedResources from application.yaml (or json).
3
+ *
4
+ * @fileoverview Plan 117 application gate
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { loadConfigFile } = require('./config-format');
12
+ const { resolveApplicationConfigPath } = require('./app-config-resolver');
13
+
14
+ /**
15
+ * @param {string|null|undefined} appPath - Builder app directory
16
+ * @returns {boolean} True when application.yaml sets environmentScopedResources: true
17
+ */
18
+ function readAppEnvironmentScopedFlagForAppPath(appPath) {
19
+ if (!appPath || typeof appPath !== 'string') {
20
+ return false;
21
+ }
22
+ try {
23
+ const cfgPath = resolveApplicationConfigPath(appPath);
24
+ const cfg = loadConfigFile(cfgPath);
25
+ return cfg.environmentScopedResources === true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ module.exports = { readAppEnvironmentScopedFlagForAppPath };
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Derive PREFIX_HOST, PREFIX_PORT, PREFIX_PUBLIC_PORT from each builder app application.yaml.
3
+ * Merged after infra + legacy static fallbacks so workspace manifests override hardcoded defaults.
4
+ *
5
+ * @fileoverview Plan 126 — app connectivity from YAML when present
6
+ * @author AI Fabrix Team
7
+ * @version 1.0.0
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const path = require('path');
13
+ const yaml = require('js-yaml');
14
+ const fsRealSync = require('../internal/fs-real-sync');
15
+ const { localHostPort } = require('./declarative-url-ports');
16
+
17
+ /**
18
+ * Maps application.yaml `app.key` to env var prefix (MISO_HOST, DATAPLANE_PORT, …).
19
+ * Generic keys not listed are skipped (no guessed PREFIX).
20
+ */
21
+ const APP_KEY_TO_ENV_PREFIX = Object.freeze({
22
+ dataplane: 'DATAPLANE',
23
+ keycloak: 'KEYCLOAK',
24
+ 'miso-controller': 'MISO',
25
+ 'mori-controller': 'MORI',
26
+ openwebui: 'OPENWEBUI',
27
+ flowise: 'FLOWISE'
28
+ });
29
+
30
+ /**
31
+ * @param {object|null|undefined} doc
32
+ * @returns {number|null}
33
+ */
34
+ function manifestPortOrNull(doc) {
35
+ if (!doc || typeof doc !== 'object') {
36
+ return null;
37
+ }
38
+ if (typeof doc.port === 'number' && doc.port > 0) {
39
+ return doc.port;
40
+ }
41
+ if (typeof doc.port === 'string' && /^\d+$/.test(doc.port.trim())) {
42
+ return parseInt(doc.port.trim(), 10);
43
+ }
44
+ return null;
45
+ }
46
+
47
+ /**
48
+ * In-container listen port (same rules as port-resolver; no require to avoid infra↔resolver cycle).
49
+ * @param {object} doc
50
+ * @param {number} manifestPort
51
+ * @returns {number}
52
+ */
53
+ function containerListenFromDoc(doc, manifestPort) {
54
+ const cp = doc.build && doc.build.containerPort;
55
+ const useMain =
56
+ cp === undefined ||
57
+ cp === null ||
58
+ (typeof cp === 'string' && cp.trim() === '');
59
+ if (!useMain && typeof cp === 'number' && cp > 0) {
60
+ return cp;
61
+ }
62
+ if (!useMain && typeof cp === 'string' && /^\d+$/.test(cp.trim())) {
63
+ return parseInt(cp.trim(), 10);
64
+ }
65
+ return manifestPort;
66
+ }
67
+
68
+ /**
69
+ * Local workstation ports: manifest-only when published ≠ container (e.g. Keycloak); else port+10 rule.
70
+ * @param {object} doc
71
+ * @returns {{ port: number, publicPort: number }}
72
+ */
73
+ function localWorkstationPortsForDoc(doc) {
74
+ const manifest = manifestPortOrNull(doc);
75
+ if (manifest === null) {
76
+ return { port: 0, publicPort: 0 };
77
+ }
78
+ const container = containerListenFromDoc(doc, manifest);
79
+ if (container === manifest) {
80
+ const p = localHostPort(manifest, 0);
81
+ return { port: p, publicPort: p };
82
+ }
83
+ return { port: manifest, publicPort: manifest };
84
+ }
85
+
86
+ /**
87
+ * @param {Record<string, unknown>} overlayDocker
88
+ * @param {Record<string, unknown>} overlayLocal
89
+ * @param {object} doc
90
+ * @param {string} folderName
91
+ */
92
+ function mergeDocIntoOverlay(overlayDocker, overlayLocal, doc, folderName) {
93
+ const appKey = (doc.app && doc.app.key) || folderName;
94
+ const prefix = APP_KEY_TO_ENV_PREFIX[appKey];
95
+ if (!prefix) {
96
+ return;
97
+ }
98
+ const manifestPort = manifestPortOrNull(doc);
99
+ if (manifestPort === null || manifestPort <= 0) {
100
+ return;
101
+ }
102
+ const containerPort = containerListenFromDoc(doc, manifestPort);
103
+ const localP = localWorkstationPortsForDoc(doc);
104
+ overlayDocker[`${prefix}_HOST`] = appKey;
105
+ overlayDocker[`${prefix}_PORT`] = containerPort;
106
+ overlayDocker[`${prefix}_PUBLIC_PORT`] = manifestPort;
107
+ overlayLocal[`${prefix}_HOST`] = 'localhost';
108
+ overlayLocal[`${prefix}_PORT`] = localP.port;
109
+ overlayLocal[`${prefix}_PUBLIC_PORT`] = localP.publicPort;
110
+ }
111
+
112
+ /**
113
+ * @param {string} projectRoot
114
+ * @returns {Array<{ doc: object, folderName: string }>}
115
+ */
116
+ function listBuilderApplicationDocs(projectRoot) {
117
+ const builderDir = path.join(projectRoot, 'builder');
118
+ if (!fsRealSync.existsSync(builderDir) || !fsRealSync.statSync(builderDir).isDirectory()) {
119
+ return [];
120
+ }
121
+ const out = [];
122
+ for (const ent of fsRealSync.readdirSync(builderDir, { withFileTypes: true })) {
123
+ if (!ent.isDirectory()) {
124
+ continue;
125
+ }
126
+ const cfg = path.join(builderDir, ent.name, 'application.yaml');
127
+ if (!fsRealSync.existsSync(cfg)) {
128
+ continue;
129
+ }
130
+ let doc;
131
+ try {
132
+ doc = yaml.load(fsRealSync.readFileSync(cfg, 'utf8'));
133
+ } catch {
134
+ continue;
135
+ }
136
+ if (doc && typeof doc === 'object') {
137
+ out.push({ doc, folderName: ent.name });
138
+ }
139
+ }
140
+ return out;
141
+ }
142
+
143
+ /**
144
+ * @param {string|null|undefined} projectRoot
145
+ * @returns {{ docker: Record<string, unknown>, local: Record<string, unknown> }}
146
+ */
147
+ function buildAppServiceEnvOverlay(projectRoot) {
148
+ const overlayDocker = {};
149
+ const overlayLocal = {};
150
+ if (!projectRoot || !fsRealSync.existsSync(projectRoot)) {
151
+ return { docker: overlayDocker, local: overlayLocal };
152
+ }
153
+ for (const { doc, folderName } of listBuilderApplicationDocs(projectRoot)) {
154
+ mergeDocIntoOverlay(overlayDocker, overlayLocal, doc, folderName);
155
+ }
156
+ return { docker: overlayDocker, local: overlayLocal };
157
+ }
158
+
159
+ module.exports = {
160
+ APP_KEY_TO_ENV_PREFIX,
161
+ buildAppServiceEnvOverlay,
162
+ localWorkstationPortsForDoc,
163
+ manifestPortOrNull
164
+ };
@@ -28,7 +28,7 @@ const paths = require('./paths');
28
28
  *
29
29
  * @example
30
30
  * const devPath = await copyBuilderToDevDirectory('myapp', 1);
31
- * // Returns: '~/.aifabrix/applications-dev-1'
31
+ * // Returns: '<configDir>/applications-dev-1' (e.g. ~/.aifabrix/... when config lives there)
32
32
  */
33
33
  async function copyBuilderToDevDirectory(appName, developerId) {
34
34
  const builderPath = paths.getBuilderPath(appName);
@@ -9,10 +9,10 @@
9
9
  * @version 2.0.0
10
10
  */
11
11
 
12
+ const { formatSuccessLine } = require('./cli-test-layout-chalk');
12
13
  const path = require('path');
13
14
  const dockerfileUtils = require('./dockerfile-utils');
14
15
  const logger = require('./logger');
15
- const chalk = require('chalk');
16
16
 
17
17
  /**
18
18
  * Determine Dockerfile path (template, custom, or generate)
@@ -26,23 +26,30 @@ async function determineDockerfile(appName, options, generateDockerfileFn) {
26
26
  // Use dev directory if provided, otherwise fall back to builder directory
27
27
  const searchPath = options.devDir || path.join(process.cwd(), 'builder', appName);
28
28
 
29
+ // Prefer application.yaml `build.dockerfile` over a stale Dockerfile copied into ~/.aifabrix/.../Dockerfile
30
+ const customDockerfile = dockerfileUtils.checkProjectDockerfile(
31
+ searchPath,
32
+ appName,
33
+ options.buildConfig,
34
+ options.contextPath,
35
+ options.forceTemplate
36
+ );
37
+ if (customDockerfile) {
38
+ logger.log(formatSuccessLine(`Using custom Dockerfile: ${options.buildConfig.dockerfile}`));
39
+ return customDockerfile;
40
+ }
41
+
29
42
  const templateDockerfile = dockerfileUtils.checkTemplateDockerfile(searchPath, appName, options.forceTemplate);
30
43
  if (templateDockerfile) {
31
44
  const relativePath = path.relative(process.cwd(), templateDockerfile);
32
- logger.log(chalk.green(`✓ Using existing Dockerfile: ${relativePath}`));
45
+ logger.log(formatSuccessLine(`Using existing Dockerfile: ${relativePath}`));
33
46
  return templateDockerfile;
34
47
  }
35
48
 
36
- const customDockerfile = dockerfileUtils.checkProjectDockerfile(searchPath, appName, options.buildConfig, options.contextPath, options.forceTemplate);
37
- if (customDockerfile) {
38
- logger.log(chalk.green(`✓ Using custom Dockerfile: ${options.buildConfig.dockerfile}`));
39
- return customDockerfile;
40
- }
41
-
42
49
  // Generate Dockerfile in dev directory if provided
43
50
  const dockerfilePath = await generateDockerfileFn(appName, options.language, options.config, options.buildConfig, options.devDir);
44
51
  const relativePath = path.relative(process.cwd(), dockerfilePath);
45
- logger.log(chalk.green(`✓ Generated Dockerfile from template: ${relativePath}`));
52
+ logger.log(formatSuccessLine(`Generated Dockerfile from template: ${relativePath}`));
46
53
  return dockerfilePath;
47
54
  }
48
55
 
@@ -56,6 +63,8 @@ async function determineDockerfile(appName, options, generateDockerfileFn) {
56
63
  async function loadAndValidateConfig(appName) {
57
64
  const { loadVariablesYaml } = require('../build');
58
65
  const validator = require('../validation/validator');
66
+ const config = require('../core/config');
67
+ const { resolveBuildImageRepositoryName } = require('./build-resolve-image');
59
68
 
60
69
  const variables = await loadVariablesYaml(appName);
61
70
 
@@ -65,17 +74,8 @@ async function loadAndValidateConfig(appName) {
65
74
  throw new Error(`Configuration validation failed:\n${validation.errors.join('\n')}`);
66
75
  }
67
76
 
68
- // Extract image name
69
- let imageName;
70
- if (typeof variables.image === 'string') {
71
- imageName = variables.image.split(':')[0];
72
- } else if (variables.image?.name) {
73
- imageName = variables.image.name;
74
- } else if (variables.app?.key) {
75
- imageName = variables.app.key;
76
- } else {
77
- imageName = appName;
78
- }
77
+ const developerId = await config.getDeveloperId();
78
+ const imageName = await resolveBuildImageRepositoryName(appName, variables, developerId);
79
79
 
80
80
  // Extract build config
81
81
  const buildConfig = variables.build || {};
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Resolve Docker image repository for `aifabrix build` from manifest or local Docker.
3
+ *
4
+ * @fileoverview When application.yaml omits image, discover repository from `docker images`
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const chalk = require('chalk');
12
+ const logger = require('./logger');
13
+ const { execWithDockerEnv } = require('./docker-exec');
14
+ const {
15
+ resolveDockerImageRef,
16
+ getRepositoryPathFromConfig
17
+ } = require('./resolve-docker-image-ref');
18
+
19
+ /**
20
+ * True when build should try local Docker to infer the repository path.
21
+ * @param {Object} variables - Loaded application manifest
22
+ * @returns {boolean}
23
+ */
24
+ function wantsLocalDockerImageDiscovery(variables) {
25
+ const img = variables && variables.image;
26
+ if (img === undefined || img === null) {
27
+ return true;
28
+ }
29
+ if (typeof img === 'string') {
30
+ return !img.trim();
31
+ }
32
+ if (typeof img !== 'object') {
33
+ return false;
34
+ }
35
+ return Object.keys(img).length === 0;
36
+ }
37
+
38
+ /**
39
+ * @param {string} repoPath
40
+ * @returns {string}
41
+ */
42
+ function repositoryTail(repoPath) {
43
+ if (!repoPath || typeof repoPath !== 'string') {
44
+ return '';
45
+ }
46
+ const parts = repoPath.split('/');
47
+ return parts[parts.length - 1] || repoPath;
48
+ }
49
+
50
+ /**
51
+ * Strip dev-scoped or -extra suffix from a Docker repository path.
52
+ * @param {string} repo
53
+ * @returns {string}
54
+ */
55
+ function baseRepositoryFromDevScoped(repo) {
56
+ const devMatch = repo.match(/^(.*)-dev\d+$/);
57
+ if (devMatch) {
58
+ return devMatch[1];
59
+ }
60
+ if (repo.endsWith('-extra')) {
61
+ return repo.slice(0, -'-extra'.length);
62
+ }
63
+ return repo;
64
+ }
65
+
66
+ /**
67
+ * @param {string} appName
68
+ * @param {string} repository - Docker repository (no tag)
69
+ * @returns {boolean}
70
+ */
71
+ function repoMatchesAppName(appName, repository) {
72
+ const base = baseRepositoryFromDevScoped(repository);
73
+ if (repository === appName || base === appName) {
74
+ return true;
75
+ }
76
+ if (repositoryTail(base) === appName) {
77
+ return true;
78
+ }
79
+ if (repositoryTail(repository) === appName) {
80
+ return true;
81
+ }
82
+ return false;
83
+ }
84
+
85
+ /**
86
+ * @param {string} appName
87
+ * @param {string[]} lines - docker images output lines repository:tag
88
+ * @param {string|number} developerId
89
+ * @returns {string|null}
90
+ */
91
+ function pickBaseFromDockerLines(appName, lines, developerId) {
92
+ const candidates = [];
93
+ for (const line of lines) {
94
+ if (!line || line.includes('<none>')) {
95
+ continue;
96
+ }
97
+ const colon = line.lastIndexOf(':');
98
+ const repository = colon > 0 ? line.slice(0, colon) : line;
99
+ if (!repository) {
100
+ continue;
101
+ }
102
+ if (!repoMatchesAppName(appName, repository)) {
103
+ continue;
104
+ }
105
+ candidates.push({
106
+ repository,
107
+ base: baseRepositoryFromDevScoped(repository)
108
+ });
109
+ }
110
+ if (!candidates.length) {
111
+ return null;
112
+ }
113
+ const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : Number(developerId);
114
+ if (Number.isFinite(idNum) && idNum > 0) {
115
+ const suffix = `-dev${idNum}`;
116
+ const preferred = candidates.find((c) => c.repository.endsWith(suffix));
117
+ if (preferred) {
118
+ return preferred.base;
119
+ }
120
+ }
121
+ return candidates[0].base;
122
+ }
123
+
124
+ /**
125
+ * Infer repository path from local Docker images when manifest has no image block.
126
+ * @param {string} appName
127
+ * @param {string|number} developerId
128
+ * @returns {Promise<string|null>}
129
+ */
130
+ async function discoverLocalBaseImageRepository(appName, developerId) {
131
+ try {
132
+ const { stdout } = await execWithDockerEnv(
133
+ 'docker images --format "{{.Repository}}:{{.Tag}}"'
134
+ );
135
+ const lines = stdout.trim().split('\n').filter(Boolean);
136
+ return pickBaseFromDockerLines(appName, lines, developerId);
137
+ } catch {
138
+ return null;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Repository path (may include registry prefix) used for `docker build -t`.
144
+ * @param {string} appName
145
+ * @param {Object} variables - application manifest
146
+ * @param {string|number} developerId
147
+ * @returns {Promise<string>}
148
+ */
149
+ async function resolveBuildImageRepositoryName(appName, variables, developerId) {
150
+ if (!wantsLocalDockerImageDiscovery(variables)) {
151
+ return resolveDockerImageRef(appName, variables, {}).imageName;
152
+ }
153
+ const discovered = await discoverLocalBaseImageRepository(appName, developerId);
154
+ if (discovered) {
155
+ logger.log(chalk.blue(`Using image repository from local Docker: ${discovered}`));
156
+ return discovered;
157
+ }
158
+ return getRepositoryPathFromConfig(variables, appName);
159
+ }
160
+
161
+ module.exports = {
162
+ resolveBuildImageRepositoryName,
163
+ wantsLocalDockerImageDiscovery,
164
+ pickBaseFromDockerLines
165
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @fileoverview Re-export of CLI layout chalk helpers (same API as cli-test-layout-chalk).
3
+ * Prefer this path for non-test modules; see `.cursor/plans/layout.md` contributor appendix.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ module.exports = require('./cli-test-layout-chalk');