@aifabrix/builder 2.43.0 → 2.44.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/.cursor/rules/cli-layout.mdc +75 -0
  3. package/.cursor/rules/project-rules.mdc +8 -0
  4. package/.npmrc.token +1 -0
  5. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +1 -0
  6. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +1 -0
  7. package/.nyc_output/processinfo/index.json +1 -0
  8. package/README.md +1 -1
  9. package/anchor-docs/README.md +10 -0
  10. package/anchor-docs/_TEMPLATE +24 -0
  11. package/bin/aifabrix.js +13 -4
  12. package/integration/hubspot-test/README.md +31 -0
  13. package/integration/hubspot-test/create-hubspot.js +5 -5
  14. package/integration/hubspot-test/hubspot-test-datasource-company.json +58 -462
  15. package/integration/hubspot-test/hubspot-test-datasource-contact.json +61 -555
  16. package/integration/hubspot-test/hubspot-test-datasource-deal.json +63 -506
  17. package/integration/hubspot-test/hubspot-test-datasource-users.json +42 -83
  18. package/integration/hubspot-test/hubspot-test-deploy.json +3 -3
  19. package/integration/hubspot-test/test-dataplane-down-tests.js +1 -7
  20. package/integration/hubspot-test/test-dataplane-down.js +3 -3
  21. package/integration/hubspot-test/test.js +35 -43
  22. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  23. package/integration/roundtrip-test-local/README.md +144 -0
  24. package/integration/roundtrip-test-local/application.yaml +13 -0
  25. package/integration/roundtrip-test-local/env.template +15 -0
  26. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  27. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  28. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  29. package/integration/roundtrip-test-local2/README.md +144 -0
  30. package/integration/roundtrip-test-local2/application.yaml +13 -0
  31. package/integration/roundtrip-test-local2/env.template +15 -0
  32. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  33. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  34. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  35. package/integration/test/wizard.yaml +8 -0
  36. package/jest.config.default.js +10 -0
  37. package/jest.config.integration.fixtures.js +22 -0
  38. package/jest.config.integration.js +21 -18
  39. package/jest.config.isolated.js +10 -0
  40. package/jest.projects.js +301 -0
  41. package/lib/api/certificates.api.js +62 -0
  42. package/lib/api/datasources-core.api.js +3 -3
  43. package/lib/api/dev-mtls-request.js +110 -0
  44. package/lib/api/dev-server-https.js +145 -0
  45. package/lib/api/dev.api.js +133 -144
  46. package/lib/api/index.js +11 -3
  47. package/lib/api/pipeline.api.js +67 -20
  48. package/lib/api/types/certificates.types.js +48 -0
  49. package/lib/api/types/dev.types.js +4 -3
  50. package/lib/api/types/pipeline.types.js +8 -5
  51. package/lib/api/types/validation-run.types.js +56 -0
  52. package/lib/api/validation-run.api.js +111 -0
  53. package/lib/api/validation-runner.js +109 -0
  54. package/lib/app/certification-show-enrich.js +129 -0
  55. package/lib/app/certification-verify-rows.js +60 -0
  56. package/lib/app/config.js +1 -1
  57. package/lib/app/deploy-status-display.js +2 -2
  58. package/lib/app/deploy.js +7 -6
  59. package/lib/app/display.js +2 -1
  60. package/lib/app/dockerfile.js +3 -2
  61. package/lib/app/down.js +2 -1
  62. package/lib/app/helpers.js +6 -5
  63. package/lib/app/index.js +27 -8
  64. package/lib/app/list.js +7 -6
  65. package/lib/app/push.js +4 -3
  66. package/lib/app/register.js +16 -7
  67. package/lib/app/rotate-secret.js +14 -13
  68. package/lib/app/run-container-start.js +184 -0
  69. package/lib/app/run-docker-fallback.js +108 -0
  70. package/lib/app/run-env-compose.js +30 -42
  71. package/lib/app/run-helpers.js +49 -126
  72. package/lib/app/run-infra-requirements.js +30 -0
  73. package/lib/app/run-resolve-image.js +21 -0
  74. package/lib/app/run.js +74 -21
  75. package/lib/app/show-display.js +44 -1
  76. package/lib/app/show.js +93 -9
  77. package/lib/build/index.js +13 -10
  78. package/lib/certification/cli-cert-sync-skip.js +21 -0
  79. package/lib/certification/merge-certification-from-artifact.js +185 -0
  80. package/lib/certification/post-unified-cert-sync.js +33 -0
  81. package/lib/certification/sync-after-external-command.js +52 -0
  82. package/lib/certification/sync-system-certification.js +197 -0
  83. package/lib/cli/index.js +2 -0
  84. package/lib/cli/setup-app.help.js +67 -0
  85. package/lib/cli/setup-app.js +61 -121
  86. package/lib/cli/setup-app.test-commands.js +195 -0
  87. package/lib/cli/setup-auth.js +19 -5
  88. package/lib/cli/setup-credential-deployment.js +22 -8
  89. package/lib/cli/setup-dev-path-commands.js +124 -0
  90. package/lib/cli/setup-dev.js +170 -113
  91. package/lib/cli/setup-environment.js +7 -1
  92. package/lib/cli/setup-external-system.js +84 -23
  93. package/lib/cli/setup-infra.js +126 -47
  94. package/lib/cli/setup-parameters.js +32 -0
  95. package/lib/cli/setup-secrets.js +137 -18
  96. package/lib/cli/setup-service-user.js +1 -1
  97. package/lib/cli/setup-utility.js +54 -22
  98. package/lib/commands/app-down.js +5 -7
  99. package/lib/commands/app-install.js +14 -7
  100. package/lib/commands/app-logs.js +13 -10
  101. package/lib/commands/app-shell.js +4 -1
  102. package/lib/commands/app-test.js +25 -19
  103. package/lib/commands/app.js +32 -11
  104. package/lib/commands/auth-config.js +6 -6
  105. package/lib/commands/auth-status.js +4 -3
  106. package/lib/commands/credential-env.js +4 -3
  107. package/lib/commands/credential-list.js +5 -4
  108. package/lib/commands/credential-push.js +4 -3
  109. package/lib/commands/datasource-unified-test-cli.js +428 -0
  110. package/lib/commands/datasource-unified-test-cli.options.js +191 -0
  111. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +106 -0
  112. package/lib/commands/datasource-validation-cli.js +143 -0
  113. package/lib/commands/datasource.js +125 -95
  114. package/lib/commands/deployment-list.js +6 -5
  115. package/lib/commands/dev-cli-handlers.js +122 -18
  116. package/lib/commands/dev-down.js +4 -3
  117. package/lib/commands/dev-init.js +231 -116
  118. package/lib/commands/dev-show-display.js +473 -0
  119. package/lib/commands/login-credentials.js +3 -2
  120. package/lib/commands/login-device.js +4 -3
  121. package/lib/commands/login.js +5 -4
  122. package/lib/commands/logout.js +8 -7
  123. package/lib/commands/parameters-validate.js +54 -0
  124. package/lib/commands/repair-datasource.js +314 -68
  125. package/lib/commands/repair-env-template.js +2 -2
  126. package/lib/commands/repair.js +21 -3
  127. package/lib/commands/secrets-list.js +23 -12
  128. package/lib/commands/secrets-remove-all.js +220 -0
  129. package/lib/commands/secrets-remove.js +21 -12
  130. package/lib/commands/secrets-set.js +21 -12
  131. package/lib/commands/secrets-validate.js +4 -4
  132. package/lib/commands/secure.js +10 -9
  133. package/lib/commands/service-user.js +26 -25
  134. package/lib/commands/test-e2e-external.js +27 -1
  135. package/lib/commands/up-common.js +3 -2
  136. package/lib/commands/up-dataplane.js +29 -16
  137. package/lib/commands/up-miso.js +19 -29
  138. package/lib/commands/upload.js +149 -39
  139. package/lib/commands/wizard-core-helpers.js +1 -1
  140. package/lib/commands/wizard-dataplane.js +4 -3
  141. package/lib/commands/wizard-helpers.js +3 -3
  142. package/lib/commands/wizard.js +2 -2
  143. package/lib/core/admin-secrets.js +14 -5
  144. package/lib/core/audit-logger.js +12 -4
  145. package/lib/core/config-attach-extensions.js +46 -0
  146. package/lib/core/config-runtime-paths.js +29 -0
  147. package/lib/core/config.js +55 -56
  148. package/lib/core/diff.js +3 -2
  149. package/lib/core/ensure-encryption-key.js +1 -1
  150. package/lib/core/secrets-ensure-infra.js +77 -0
  151. package/lib/core/secrets-ensure.js +120 -64
  152. package/lib/core/secrets-env-write.js +35 -7
  153. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  154. package/lib/core/secrets.js +200 -37
  155. package/lib/core/templates-env.js +4 -3
  156. package/lib/datasource/abac-validator.js +1 -10
  157. package/lib/datasource/deploy.js +75 -53
  158. package/lib/datasource/field-reference-validator.js +9 -6
  159. package/lib/datasource/integration-context.js +63 -0
  160. package/lib/datasource/list.js +8 -7
  161. package/lib/datasource/log-viewer.js +189 -67
  162. package/lib/datasource/resolve-app.js +4 -4
  163. package/lib/datasource/test-e2e.js +113 -146
  164. package/lib/datasource/test-integration.js +114 -122
  165. package/lib/datasource/unified-validation-run-body.js +68 -0
  166. package/lib/datasource/unified-validation-run-post.js +23 -0
  167. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  168. package/lib/datasource/unified-validation-run.js +93 -0
  169. package/lib/datasource/validate.js +157 -13
  170. package/lib/deployment/deployer.js +4 -3
  171. package/lib/deployment/environment.js +7 -6
  172. package/lib/deployment/push.js +17 -8
  173. package/lib/external-system/delete.js +4 -3
  174. package/lib/external-system/deploy.js +166 -53
  175. package/lib/external-system/download-helpers.js +1 -1
  176. package/lib/external-system/download.js +7 -6
  177. package/lib/external-system/generator.js +92 -6
  178. package/lib/external-system/integration-test-dispatch.js +26 -0
  179. package/lib/external-system/test-execution.js +5 -1
  180. package/lib/external-system/test-helpers.js +0 -4
  181. package/lib/external-system/test-system-level-helpers.js +110 -0
  182. package/lib/external-system/test-system-level.js +83 -44
  183. package/lib/external-system/test.js +59 -8
  184. package/lib/generator/builders.js +23 -11
  185. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  186. package/lib/generator/external.js +16 -4
  187. package/lib/generator/helpers.js +58 -3
  188. package/lib/generator/index.js +4 -0
  189. package/lib/generator/split-readme.js +12 -7
  190. package/lib/generator/split-variables.js +2 -1
  191. package/lib/generator/split.js +1 -1
  192. package/lib/generator/wizard-readme.js +3 -3
  193. package/lib/generator/wizard.js +8 -8
  194. package/lib/infrastructure/compose.js +70 -7
  195. package/lib/infrastructure/helpers-docker-check.js +67 -0
  196. package/lib/infrastructure/helpers.js +203 -42
  197. package/lib/infrastructure/index.js +31 -18
  198. package/lib/infrastructure/services.js +21 -67
  199. package/lib/internal/fs-real-sync.js +104 -0
  200. package/lib/internal/node-fs.js +98 -0
  201. package/lib/parameters/database-secret-values.js +173 -0
  202. package/lib/parameters/infra-kv-discovery.js +121 -0
  203. package/lib/parameters/infra-parameter-catalog.js +458 -0
  204. package/lib/parameters/infra-parameter-validate.js +64 -0
  205. package/lib/schema/application-schema.json +37 -17
  206. package/lib/schema/datasource-test-run.schema.json +493 -0
  207. package/lib/schema/deployment-rules.yaml +102 -63
  208. package/lib/schema/external-datasource.schema.json +1200 -442
  209. package/lib/schema/external-system.schema.json +203 -5
  210. package/lib/schema/flag-map-validation-run.json +31 -0
  211. package/lib/schema/infra-parameter.schema.json +106 -0
  212. package/lib/schema/infra.parameter.yaml +421 -0
  213. package/lib/schema/type/credential-auth-templates.json +40 -0
  214. package/lib/schema/type/document-storage.json +226 -0
  215. package/lib/schema/type/message-service.json +123 -0
  216. package/lib/schema/type/vector-store.json +88 -0
  217. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  218. package/lib/utils/api-error-handler.js +2 -2
  219. package/lib/utils/api.js +77 -17
  220. package/lib/utils/app-register-api.js +3 -2
  221. package/lib/utils/app-register-auth.js +1 -1
  222. package/lib/utils/app-register-config.js +4 -4
  223. package/lib/utils/app-register-display.js +3 -2
  224. package/lib/utils/app-register-validator.js +3 -2
  225. package/lib/utils/app-run-containers.js +26 -22
  226. package/lib/utils/app-scoped-config.js +31 -0
  227. package/lib/utils/app-service-env-from-builder.js +164 -0
  228. package/lib/utils/build-copy.js +1 -1
  229. package/lib/utils/build-helpers.js +20 -20
  230. package/lib/utils/build-resolve-image.js +165 -0
  231. package/lib/utils/cli-layout-chalk.js +8 -0
  232. package/lib/utils/cli-test-layout-chalk.js +267 -0
  233. package/lib/utils/cli-utils.js +88 -11
  234. package/lib/utils/compose-db-passwords.js +138 -0
  235. package/lib/utils/compose-generate-docker-compose.js +216 -0
  236. package/lib/utils/compose-generator.js +197 -291
  237. package/lib/utils/compose-miso-env.js +18 -0
  238. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  239. package/lib/utils/config-paths.js +166 -7
  240. package/lib/utils/config-scoped-resources-preference.js +41 -0
  241. package/lib/utils/configuration-env-resolver.js +11 -8
  242. package/lib/utils/controller-deployment-outcome.js +68 -0
  243. package/lib/utils/credential-display.js +2 -2
  244. package/lib/utils/credential-secrets-env.js +5 -5
  245. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  246. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  247. package/lib/utils/datasource-test-run-certificate-tty.js +82 -0
  248. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  249. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  250. package/lib/utils/datasource-test-run-display.js +459 -0
  251. package/lib/utils/datasource-test-run-exit.js +83 -0
  252. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  253. package/lib/utils/datasource-test-run-report-version.js +51 -0
  254. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  255. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  256. package/lib/utils/datasource-validation-watch.js +266 -0
  257. package/lib/utils/declarative-url-ports.js +47 -0
  258. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  259. package/lib/utils/dev-ca-install.js +185 -23
  260. package/lib/utils/dev-cert-helper.js +266 -17
  261. package/lib/utils/dev-hosts-helper.js +307 -0
  262. package/lib/utils/dev-init-cert-hints.js +37 -0
  263. package/lib/utils/dev-init-health-messages.js +52 -0
  264. package/lib/utils/dev-init-resolve.js +86 -0
  265. package/lib/utils/dev-init-ssh-merge.js +65 -0
  266. package/lib/utils/dev-ssh-config-helper.js +196 -0
  267. package/lib/utils/dev-user-groups.js +93 -0
  268. package/lib/utils/docker-build.js +42 -17
  269. package/lib/utils/docker-exec.js +28 -0
  270. package/lib/utils/docker-manifest-public-port.js +116 -0
  271. package/lib/utils/docker-not-running-hint.js +52 -0
  272. package/lib/utils/docker.js +98 -11
  273. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  274. package/lib/utils/env-config-loader.js +10 -91
  275. package/lib/utils/env-copy.js +19 -10
  276. package/lib/utils/env-map.js +35 -8
  277. package/lib/utils/env-template.js +2 -2
  278. package/lib/utils/environment-scoped-resources.js +144 -0
  279. package/lib/utils/error-formatter.js +92 -13
  280. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  281. package/lib/utils/error-formatters/network-errors.js +2 -1
  282. package/lib/utils/error-formatters/permission-errors.js +2 -1
  283. package/lib/utils/error-formatters/validation-errors.js +2 -1
  284. package/lib/utils/external-readme.js +8 -1
  285. package/lib/utils/external-system-display.js +242 -136
  286. package/lib/utils/external-system-local-test-tty.js +389 -0
  287. package/lib/utils/external-system-readiness-core.js +377 -0
  288. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  289. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  290. package/lib/utils/external-system-readiness-display.js +186 -0
  291. package/lib/utils/external-system-system-test-tty-overview.js +120 -0
  292. package/lib/utils/external-system-system-test-tty.js +417 -0
  293. package/lib/utils/external-system-test-helpers.js +24 -6
  294. package/lib/utils/external-system-validators.js +30 -12
  295. package/lib/utils/health-check-url.js +119 -0
  296. package/lib/utils/health-check.js +59 -25
  297. package/lib/utils/help-builder.js +11 -8
  298. package/lib/utils/image-version.js +4 -8
  299. package/lib/utils/infra-containers.js +4 -7
  300. package/lib/utils/infra-env-defaults.js +162 -0
  301. package/lib/utils/infra-status-display.js +167 -0
  302. package/lib/utils/infra-status.js +16 -8
  303. package/lib/utils/local-secrets.js +3 -4
  304. package/lib/utils/paths.js +148 -47
  305. package/lib/utils/port-resolver.js +10 -23
  306. package/lib/utils/redis-env-scope.js +62 -0
  307. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  308. package/lib/utils/remote-builder-validation.js +99 -0
  309. package/lib/utils/remote-dev-auth.js +117 -21
  310. package/lib/utils/remote-docker-env.js +67 -15
  311. package/lib/utils/remote-secrets-loader.js +13 -4
  312. package/lib/utils/resolve-docker-image-ref.js +124 -0
  313. package/lib/utils/schema-loader.js +22 -9
  314. package/lib/utils/secrets-bash-kv.js +25 -0
  315. package/lib/utils/secrets-generator.js +169 -49
  316. package/lib/utils/secrets-helpers.js +70 -59
  317. package/lib/utils/secrets-kv-scope.js +60 -0
  318. package/lib/utils/secrets-utils.js +32 -38
  319. package/lib/utils/secrets-validation.js +3 -1
  320. package/lib/utils/secrets-yaml-preserve.js +109 -0
  321. package/lib/utils/ssh-key-helper.js +4 -2
  322. package/lib/utils/template-helpers.js +2 -2
  323. package/lib/utils/test-log-writer.js +3 -3
  324. package/lib/utils/token-manager.js +1 -2
  325. package/lib/utils/url-declarative-public-base.js +188 -0
  326. package/lib/utils/url-declarative-resolve-build.js +493 -0
  327. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  328. package/lib/utils/url-declarative-resolve.js +220 -0
  329. package/lib/utils/url-declarative-token-parse.js +74 -0
  330. package/lib/utils/url-declarative-url-flags.js +50 -0
  331. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  332. package/lib/utils/url-public-path-prefix.js +34 -0
  333. package/lib/utils/urls-local-registry.js +220 -0
  334. package/lib/utils/validation-report-tty-kit.js +77 -0
  335. package/lib/utils/validation-run-poll.js +112 -0
  336. package/lib/utils/validation-run-post-retry.js +85 -0
  337. package/lib/utils/validation-run-request.js +116 -0
  338. package/lib/utils/variable-transformer.js +21 -4
  339. package/lib/utils/yaml-preserve.js +33 -14
  340. package/lib/validation/datasource-warnings.js +56 -0
  341. package/lib/validation/env-template-auth.js +1 -1
  342. package/lib/validation/external-manifest-validator.js +27 -7
  343. package/lib/validation/validate-display.js +37 -31
  344. package/lib/validation/validate-external-cert-sync.js +23 -0
  345. package/lib/validation/validate.js +8 -14
  346. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  347. package/lib/validation/validator.js +22 -65
  348. package/lib/validation/wizard-config-validator.js +2 -1
  349. package/package.json +9 -4
  350. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  351. package/scripts/diagnose-cli.js +150 -0
  352. package/scripts/install-local.js +307 -55
  353. package/scripts/pnpm-global-remove.js +48 -0
  354. package/templates/README.md +15 -2
  355. package/templates/applications/dataplane/application.yaml +52 -2
  356. package/templates/applications/dataplane/env.template +79 -17
  357. package/templates/applications/dataplane/rbac.yaml +8 -0
  358. package/templates/applications/keycloak/application.yaml +9 -1
  359. package/templates/applications/keycloak/env.template +15 -6
  360. package/templates/applications/miso-controller/application.yaml +10 -2
  361. package/templates/applications/miso-controller/env.template +42 -12
  362. package/templates/applications/miso-controller/rbac.yaml +5 -0
  363. package/templates/external-system/README.md.hbs +20 -7
  364. package/templates/external-system/deploy.js.hbs +5 -5
  365. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  366. package/templates/infra/compose.yaml.hbs +33 -16
  367. package/templates/infra/servers.json.hbs +3 -1
  368. package/templates/python/docker-compose.hbs +16 -0
  369. package/templates/typescript/docker-compose.hbs +16 -0
  370. package/lib/api/external-test.api.js +0 -111
  371. package/lib/schema/env-config.yaml +0 -60
package/lib/app/down.js CHANGED
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  'use strict';
13
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
13
14
 
14
15
  const chalk = require('chalk');
15
16
  const { exec } = require('child_process');
@@ -104,7 +105,7 @@ async function downApp(appName, options = {}) {
104
105
  logger.log(chalk.yellow(`Removing volume ${volumeName}...`));
105
106
  try {
106
107
  await execAsync(`docker volume rm -f ${volumeName}`);
107
- logger.log(chalk.green(`✓ Volume ${volumeName} removed`));
108
+ logger.log(formatSuccessLine(`Volume ${volumeName} removed`));
108
109
  } catch (volErr) {
109
110
  // Swallow errors for missing volume; provide neutral message
110
111
  logger.log(chalk.gray(`Volume ${volumeName} not found or already removed`));
@@ -1,3 +1,4 @@
1
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * Application Helper Utilities
3
4
  *
@@ -21,7 +22,7 @@ const { getIntegrationPath, getBuilderPath } = require('../utils/paths');
21
22
  *
22
23
  * @async
23
24
  * @param {string} appName - Application or external system name
24
- * @throws {Error} If integration/<appName> or builder/<appName> already exists
25
+ * @throws {Error} If integration/<systemKey> or builder/<appKey> already exists
25
26
  */
26
27
  async function validateAppOrExternalNameNotExists(appName) {
27
28
  const integrationPath = getIntegrationPath(appName);
@@ -104,7 +105,7 @@ async function handleGitHubWorkflows(options, config) {
104
105
  }
105
106
  );
106
107
 
107
- logger.log(chalk.green('Generated GitHub Actions workflows:'));
108
+ logger.log(formatSuccessLine('Generated GitHub Actions workflows:'));
108
109
  workflowFiles.forEach(file => logger.log(chalk.gray(` - ${file}`)));
109
110
  }
110
111
 
@@ -157,7 +158,7 @@ async function processTemplateFiles(template, appPath, appName, options, config)
157
158
 
158
159
  await validateTemplate(template);
159
160
  const copiedFiles = await copyTemplateFiles(template, appPath);
160
- logger.log(chalk.green(`✓ Copied ${copiedFiles.length} file(s) from template '${template}'`));
161
+ logger.log(formatSuccessLine(`Copied ${copiedFiles.length} file(s) from template '${template}'`));
161
162
  const { updateTemplateVariables } = require('../utils/template-helpers');
162
163
  await updateTemplateVariables(appPath, appName, options, config);
163
164
  }
@@ -190,7 +191,7 @@ async function updateVariablesForAppFlag(appPath, appName) {
190
191
  writeConfigFile(variablesPath, variables);
191
192
  } catch (error) {
192
193
  if (!error.message.includes('not found')) {
193
- logger.warn(chalk.yellow(`⚠️ Warning: Could not update application config: ${error.message}`));
194
+ logger.warn(chalk.yellow(`⚠ Warning: Could not update application config: ${error.message}`));
194
195
  }
195
196
  }
196
197
  }
@@ -239,7 +240,7 @@ async function setupAppFiles(appName, appPath, config, options) {
239
240
 
240
241
  const language = await getLanguageForAppFiles(config.language || options.language, appPath);
241
242
  const copiedFiles = await copyAppFiles(language, appsPath);
242
- logger.log(chalk.green(`✓ Copied ${copiedFiles.length} application file(s) to apps/${appName}/`));
243
+ logger.log(formatSuccessLine(`Copied ${copiedFiles.length} application file(s) to apps/${appName}/`));
243
244
  }
244
245
 
245
246
  module.exports = {
package/lib/app/index.js CHANGED
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  const fs = require('fs').promises;
13
- const { readExistingEnv } = require('../core/env-reader');
13
+ const envReaderModule = require('../core/env-reader');
14
14
  const build = require('../build');
15
15
  const appRun = require('./run');
16
16
  const { promptForOptions } = require('./prompts');
@@ -34,6 +34,29 @@ const {
34
34
  const path = require('path');
35
35
  const secretsEnsure = require('../core/secrets-ensure');
36
36
 
37
+ /**
38
+ * Ensures secrets from env.template. On EACCES (e.g. secrets path under a read-only tree), uses getPrimaryUserSecretsLocalPath().
39
+ * @param {string} envTemplatePath - Path to env.template
40
+ * @returns {Promise<void>}
41
+ */
42
+ async function ensureSecretsFromNewAppEnvTemplate(envTemplatePath) {
43
+ try {
44
+ await secretsEnsure.ensureSecretsFromEnvTemplate(envTemplatePath, {});
45
+ } catch (err) {
46
+ if (err.code === 'ENOENT') {
47
+ return;
48
+ }
49
+ if (err.code === 'EACCES' || (err.message && String(err.message).includes('EACCES'))) {
50
+ const { getPrimaryUserSecretsLocalPath } = require('../utils/paths');
51
+ await secretsEnsure.ensureSecretsFromEnvTemplate(envTemplatePath, {
52
+ preferredFilePath: getPrimaryUserSecretsLocalPath()
53
+ });
54
+ return;
55
+ }
56
+ throw err;
57
+ }
58
+ }
59
+
37
60
  /**
38
61
  * Creates new application with scaffolded configuration files
39
62
  * Prompts for configuration options and generates builder/ folder structure
@@ -125,19 +148,15 @@ async function generateApplicationFiles(finalAppPath, appName, config, options)
125
148
  await fs.mkdir(finalAppPath, { recursive: true });
126
149
  await processTemplateFiles(options.template, finalAppPath, appName, options, config);
127
150
 
128
- const existingEnv = await readExistingEnv(process.cwd());
151
+ const existingEnv = await envReaderModule.readExistingEnv(finalAppPath);
129
152
  const envConversionMessage = existingEnv
130
- ? '\n Found existing .env file - sensitive values will be converted to kv:// references'
153
+ ? '\n Found existing .env file - sensitive values will be converted to kv:// references'
131
154
  : '';
132
155
 
133
156
  await generateConfigFiles(finalAppPath, appName, config, existingEnv);
134
157
 
135
158
  const envTemplatePath = path.join(finalAppPath, 'env.template');
136
- try {
137
- await secretsEnsure.ensureSecretsFromEnvTemplate(envTemplatePath, {});
138
- } catch (err) {
139
- if (err.code !== 'ENOENT') throw err;
140
- }
159
+ await ensureSecretsFromNewAppEnvTemplate(envTemplatePath);
141
160
 
142
161
  // Generate external system files if type is external
143
162
  if (config.type === 'external') {
package/lib/app/list.js CHANGED
@@ -1,3 +1,4 @@
1
+ const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * AI Fabrix Builder - App List Command
3
4
  *
@@ -85,7 +86,7 @@ function extractApplications(response) {
85
86
  extractWrappedPaginatedItems(apiResponse);
86
87
 
87
88
  if (!applications) {
88
- logger.error(chalk.red('Invalid response: expected data array or items array'));
89
+ logger.error(formatBlockingError('Invalid response: expected data array or items array'));
89
90
  logger.error(chalk.gray('\nAPI response type:'), typeof apiResponse);
90
91
  logger.error(chalk.gray('API response:'), JSON.stringify(apiResponse, null, 2));
91
92
  logger.error(chalk.gray('\nFull response for debugging:'));
@@ -166,11 +167,11 @@ function displayApplications(applications, environment, controllerUrl) {
166
167
  applications.forEach((app) => {
167
168
  const isExternal = app.configuration?.type === 'external';
168
169
  const externalIcon = isExternal ? '🔗 ' : '';
169
- const hasPipeline = app.configuration?.pipeline?.isActive ? '' : '';
170
+ const hasPipeline = app.configuration?.pipeline?.isActive ? '' : '';
170
171
  const urlAndPort = formatUrlAndPort(app);
171
172
  logger.log(`${externalIcon}${hasPipeline} ${chalk.cyan(app.key)} - ${app.displayName} (${app.status || 'unknown'})${urlAndPort}`);
172
173
  });
173
- logger.log(chalk.gray(' To show details for an app: aifabrix app show <appKey>\n'));
174
+ logger.log(chalk.gray(' To show details for an app: aifabrix app show <app>\n'));
174
175
  }
175
176
 
176
177
  /**
@@ -246,7 +247,7 @@ async function getListAuthToken(controllerUrl, config) {
246
247
  const authResult = await tryGetTokenFromController(controllerUrl);
247
248
  if (!authResult || !authResult.token) {
248
249
  // No token found for explicitly provided controller URL
249
- logger.error(chalk.red(`❌ No authentication token found for controller: ${controllerUrl}`));
250
+ logger.error(formatBlockingError(`No authentication token found for controller: ${controllerUrl}`));
250
251
  logger.error(chalk.gray('Please login to this controller using: aifabrix login'));
251
252
  process.exit(1);
252
253
  // Return to prevent further execution in tests where process.exit is mocked
@@ -300,7 +301,7 @@ async function listApplications(options = {}) {
300
301
 
301
302
  const controllerUrl = options.controller || (await resolveControllerUrl());
302
303
  if (!controllerUrl) {
303
- logger.error(chalk.red('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml'));
304
+ logger.error(formatBlockingError('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml'));
304
305
  process.exit(1);
305
306
  return;
306
307
  }
@@ -321,7 +322,7 @@ async function listApplications(options = {}) {
321
322
  const applications = handleListResponse(response, actualControllerUrl);
322
323
  displayApplications(applications, environment, actualControllerUrl);
323
324
  } catch (error) {
324
- logger.error(chalk.red(`❌ Failed to list applications from controller: ${actualControllerUrl}`));
325
+ logger.error(formatBlockingError(`Failed to list applications from controller: ${actualControllerUrl}`));
325
326
  logger.error(chalk.gray(`Error: ${error.message}`));
326
327
  process.exit(1);
327
328
  }
package/lib/app/push.js CHANGED
@@ -1,3 +1,4 @@
1
+ const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * Application Push Utilities
3
4
  *
@@ -143,7 +144,7 @@ async function validatePushConfig(registry, imageName, appName) {
143
144
  */
144
145
  async function authenticateWithRegistry(registry) {
145
146
  if (await pushUtils.checkACRAuthentication(registry)) {
146
- logger.log(chalk.green(`✓ Already authenticated with ${registry}`));
147
+ logger.log(formatSuccessLine(`Already authenticated with ${registry}`));
147
148
  } else {
148
149
  await pushUtils.authenticateACR(registry);
149
150
  }
@@ -189,7 +190,7 @@ async function pushImageTags(imageName, registry, tags) {
189
190
  * @param {Array<string>} tags - Image tags
190
191
  */
191
192
  function displayPushResults(registry, imageName, tags) {
192
- logger.log(chalk.green(`\n✓ Successfully pushed ${tags.length} tag(s) to ${registry}`));
193
+ logger.log(formatSuccessParagraph(`Successfully pushed ${tags.length} tag(s) to ${registry}`));
193
194
  logger.log(chalk.gray(`Image: ${registry}/${imageName}:*`));
194
195
  logger.log(chalk.gray(`Tags: ${tags.join(', ')}`));
195
196
  }
@@ -208,7 +209,7 @@ async function pushApp(appName, options = {}) {
208
209
  try {
209
210
  const { isExternal } = await detectAppType(appName);
210
211
  if (isExternal) {
211
- logger.log(chalk.yellow('⚠️ External systems don\'t require Docker images. Skipping push...'));
212
+ logger.log(chalk.yellow(' External systems don\'t require Docker images. Skipping push...'));
212
213
  return;
213
214
  }
214
215
  } catch (error) {
@@ -1,3 +1,4 @@
1
+ const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * AI Fabrix Builder - App Register Command
3
4
  *
@@ -22,6 +23,8 @@ const {
22
23
  const { checkAuthentication } = require('../utils/app-register-auth');
23
24
  const { callRegisterApi } = require('../utils/app-register-api');
24
25
  const { displayRegistrationResults, getEnvironmentPrefix } = require('../utils/app-register-display');
26
+ const pathsUtil = require('../utils/paths');
27
+ const { refreshUrlsLocalRegistryFromBuilder } = require('../utils/urls-local-registry');
25
28
 
26
29
  /**
27
30
  * Build registration data payload from app configuration
@@ -62,8 +65,8 @@ function buildRegistrationData(appConfig, options) {
62
65
  registrationData.image = imageValue;
63
66
  }
64
67
 
65
- // URL: always set when we have port so controller DB has it. Precedence: --url, variables (app.url, deployment.dataplaneUrl, deployment.appUrl), else http://localhost:{localPort|port}
66
- const portForUrl = appConfig.localPort ?? appConfig.port;
68
+ // URL: default uses manifest listen port; host mapping uses url:// in env.template at resolve time
69
+ const portForUrl = appConfig.port;
67
70
  if (portForUrl) {
68
71
  registrationData.url = options.url || appConfig.url || `http://localhost:${portForUrl}`;
69
72
  }
@@ -97,15 +100,15 @@ async function saveLocalCredentials(responseData, apiUrl) {
97
100
  // Regenerate .env file with updated credentials
98
101
  try {
99
102
  await generateEnvFile(registeredAppKey, null, 'local');
100
- logger.log(chalk.green('.env file updated with new credentials'));
103
+ logger.log(formatSuccessLine('.env file updated with new credentials'));
101
104
  } catch (error) {
102
- logger.warn(chalk.yellow(`⚠️ Could not regenerate .env file: ${error.message}`));
105
+ logger.warn(chalk.yellow(`⚠ Could not regenerate .env file: ${error.message}`));
103
106
  }
104
107
 
105
- logger.log(chalk.green('\n✓ Credentials saved to ~/.aifabrix/secrets.local.yaml'));
106
- logger.log(chalk.green('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
108
+ logger.log(formatSuccessParagraph('Credentials saved to ~/.aifabrix/secrets.local.yaml'));
109
+ logger.log(formatSuccessLine('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
107
110
  } catch (error) {
108
- logger.warn(chalk.yellow(`⚠️ Could not save credentials locally: ${error.message}`));
111
+ logger.warn(chalk.yellow(`⚠ Could not save credentials locally: ${error.message}`));
109
112
  }
110
113
  }
111
114
 
@@ -179,6 +182,12 @@ async function registerApplication(appKey, options = {}) {
179
182
  registrationData
180
183
  );
181
184
 
185
+ try {
186
+ refreshUrlsLocalRegistryFromBuilder(pathsUtil.getProjectRoot());
187
+ } catch (error) {
188
+ logger.warn(chalk.yellow(`⚠ Could not refresh URLs registry: ${error.message}`));
189
+ }
190
+
182
191
  // Save credentials and display results (pass display name we sent so output shows it when API returns key as displayName)
183
192
  await saveLocalCredentials(responseData, authConfig.apiUrl);
184
193
  displayRegistrationResults(responseData, authConfig.apiUrl, environment, registrationData.displayName);
@@ -1,3 +1,4 @@
1
+ const { formatBlockingError, formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * AI Fabrix Builder - App Rotate Secret Command
3
4
  *
@@ -70,7 +71,7 @@ function validateEnvironment(environment) {
70
71
  async function resolveControllerAndEnvironment() {
71
72
  const controllerUrl = await resolveControllerUrl();
72
73
  if (!controllerUrl) {
73
- logger.error(chalk.red('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml'));
74
+ logger.error(formatBlockingError('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml'));
74
75
  process.exit(1);
75
76
  }
76
77
  const environment = await resolveEnvironment();
@@ -135,7 +136,7 @@ function extractCredentials(response) {
135
136
  */
136
137
  function validateResponse(response) {
137
138
  if (!response.data || typeof response.data !== 'object') {
138
- logger.error(chalk.red('Invalid response: missing data'));
139
+ logger.error(formatBlockingError('Invalid response: missing data'));
139
140
  logger.error(chalk.gray('\nAPI response type:'), typeof response.data);
140
141
  logger.error(chalk.gray('API response:'), JSON.stringify(response.data, null, 2));
141
142
  logger.error(chalk.gray('\nFull response for debugging:'));
@@ -146,7 +147,7 @@ function validateResponse(response) {
146
147
  const result = extractCredentials(response);
147
148
 
148
149
  if (!result) {
149
- logger.error(chalk.red('Invalid response: missing or invalid credentials'));
150
+ logger.error(formatBlockingError('Invalid response: missing or invalid credentials'));
150
151
  logger.error(chalk.gray('\nAPI response type:'), typeof response.data);
151
152
  logger.error(chalk.gray('API response:'), JSON.stringify(response.data, null, 2));
152
153
  logger.error(chalk.gray('\nFull response for debugging:'));
@@ -166,7 +167,7 @@ function validateResponse(response) {
166
167
  * @param {string} [message] - Optional message from API
167
168
  */
168
169
  function displayRotationResults(appKey, environment, credentials, apiUrl, message) {
169
- logger.log(chalk.green('Secret rotated successfully!\n'));
170
+ logger.log(formatSuccessLine('Secret rotated successfully!\n'));
170
171
  logger.log(chalk.bold('📋 Application Details:'));
171
172
  logger.log(` Key: ${appKey}`);
172
173
  logger.log(` Environment: ${environment}`);
@@ -206,7 +207,7 @@ async function getTokenFromUrl(controllerUrl) {
206
207
  };
207
208
  }
208
209
  } catch (error) {
209
- logger.error(chalk.red(`❌ Failed to authenticate with controller: ${controllerUrl}`));
210
+ logger.error(formatBlockingError(`Failed to authenticate with controller: ${controllerUrl}`));
210
211
  logger.error(chalk.gray(`Error: ${error.message}`));
211
212
  process.exit(1);
212
213
  }
@@ -287,18 +288,18 @@ async function saveCredentialsLocally(appKey, credentials, actualControllerUrl)
287
288
  // Regenerate .env file with updated credentials
288
289
  try {
289
290
  await generateEnvFile(appKey, null, 'local');
290
- logger.log(chalk.green('.env file updated with new credentials'));
291
+ logger.log(formatSuccessLine('.env file updated with new credentials'));
291
292
  } catch (error) {
292
- logger.warn(chalk.yellow(`⚠️ Could not regenerate .env file: ${error.message}`));
293
+ logger.warn(chalk.yellow(`⚠ Could not regenerate .env file: ${error.message}`));
293
294
  }
294
295
 
295
- logger.log(chalk.green('\n✓ Credentials saved to ~/.aifabrix/secrets.local.yaml'));
296
- logger.log(chalk.green('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
296
+ logger.log(formatSuccessParagraph('Credentials saved to ~/.aifabrix/secrets.local.yaml'));
297
+ logger.log(formatSuccessLine('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
297
298
  } else {
298
- logger.log(chalk.green('\n✓ Credentials saved to ~/.aifabrix/secrets.local.yaml\n'));
299
+ logger.log(formatSuccessParagraph('Credentials saved to ~/.aifabrix/secrets.local.yaml\n'));
299
300
  }
300
301
  } catch (error) {
301
- logger.warn(chalk.yellow(`⚠️ Could not save credentials locally: ${error.message}`));
302
+ logger.warn(chalk.yellow(`⚠ Could not save credentials locally: ${error.message}`));
302
303
  }
303
304
  }
304
305
 
@@ -337,7 +338,7 @@ async function executeRotation(appKey, actualControllerUrl, environment, token)
337
338
  * @throws {Error} If rotation fails
338
339
  */
339
340
  async function rotateSecret(appKey, _options = {}) {
340
- logger.log(chalk.yellow('⚠️ This will invalidate the old ClientSecret!\n'));
341
+ logger.log(chalk.yellow(' This will invalidate the old ClientSecret!\n'));
341
342
 
342
343
  const { ensureSecretsEncryptionKey } = require('../core/config');
343
344
  await ensureSecretsEncryptionKey();
@@ -349,7 +350,7 @@ async function rotateSecret(appKey, _options = {}) {
349
350
  try {
350
351
  await executeRotation(appKey, actualControllerUrl, environment, token);
351
352
  } catch (error) {
352
- logger.error(chalk.red(`❌ Failed to rotate secret via controller: ${actualControllerUrl}`));
353
+ logger.error(formatBlockingError(`Failed to rotate secret via controller: ${actualControllerUrl}`));
353
354
  logger.error(chalk.gray(`Error: ${error.message}`));
354
355
  process.exit(1);
355
356
  }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Docker Compose up and docker-run fallback for `aifabrix run`.
3
+ *
4
+ * @fileoverview Extracted from run-helpers to keep file size within limits
5
+ */
6
+
7
+ 'use strict';
8
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
9
+
10
+ const fs = require('fs').promises;
11
+ const chalk = require('chalk');
12
+ const { exec } = require('child_process');
13
+ const { promisify } = require('util');
14
+ const logger = require('../utils/logger');
15
+ const dockerUtils = require('../utils/docker');
16
+ const containerHelpers = require('../utils/app-run-containers');
17
+ const healthCheck = require('../utils/health-check');
18
+ const runDockerFallback = require('./run-docker-fallback');
19
+ const { resolveRunImage } = require('./run-resolve-image');
20
+
21
+ const execAsync = promisify(exec);
22
+
23
+ /**
24
+ * Logs and runs docker when Compose CLI is missing (narrow eligibility).
25
+ * @async
26
+ * @param {string} appName
27
+ * @param {Object} appConfig
28
+ * @param {number} port
29
+ * @param {Object} opts
30
+ */
31
+ async function emitAndRunDockerFallback(appName, appConfig, port, opts) {
32
+ const { debug, runEnvPath, runOptions, misoEnvironment } = opts;
33
+ logger.log(
34
+ chalk.yellow(
35
+ 'Docker Compose not found; using docker run (apps without database/redis only). ' +
36
+ 'Install docker-compose-plugin for full compose support.'
37
+ )
38
+ );
39
+ const { imageName, imageTag } = resolveRunImage(appName, appConfig, runOptions);
40
+ await runDockerFallback.executeDockerRunUp({
41
+ appName,
42
+ appConfig,
43
+ hostPort: port,
44
+ fullImage: `${imageName}:${imageTag}`,
45
+ runEnvPath,
46
+ misoEnvironment,
47
+ debug
48
+ });
49
+ }
50
+
51
+ /**
52
+ * Prepares env for docker compose child process (UID/GID + remote docker).
53
+ * @async
54
+ * @param {boolean} debug
55
+ * @returns {Promise<Object>}
56
+ */
57
+ async function prepareContainerEnv(debug) {
58
+ const { getDockerExecEnv } = require('../utils/remote-docker-env');
59
+ const env = await getDockerExecEnv();
60
+
61
+ if (typeof process.getuid === 'function' && typeof process.getgid === 'function') {
62
+ env.AIFABRIX_UID = String(process.getuid());
63
+ env.AIFABRIX_GID = String(process.getgid());
64
+ } else {
65
+ env.AIFABRIX_UID = '1000';
66
+ env.AIFABRIX_GID = '1000';
67
+ }
68
+
69
+ if (debug) {
70
+ logger.log(chalk.gray('[DEBUG] Container env prepared (secrets via env_file)'));
71
+ }
72
+
73
+ return env;
74
+ }
75
+
76
+ /**
77
+ * @async
78
+ * @param {string} composeCmdBase
79
+ * @param {string} composePath
80
+ * @param {Object} env
81
+ * @param {boolean} debug
82
+ */
83
+ async function executeComposeUp(composeCmdBase, composePath, env, debug) {
84
+ const composeCmd = `${composeCmdBase} -f "${composePath}" up -d`;
85
+ if (debug) {
86
+ logger.log(chalk.gray(`[DEBUG] Executing: ${composeCmd}`));
87
+ logger.log(chalk.gray(`[DEBUG] Compose file: ${composePath}`));
88
+ }
89
+ await execAsync(composeCmd, { env });
90
+ }
91
+
92
+ /**
93
+ * Log status, wait for HTTP health, delete run .env files.
94
+ * @async
95
+ * @param {string} appName
96
+ * @param {number} port
97
+ * @param {Object} appConfig
98
+ * @param {{ debug: boolean, runEnvPath: string|null, runEnvAdminPath: string|null }} o
99
+ */
100
+ async function waitForHealthyAndCleanupEnvFiles(appName, port, appConfig, o) {
101
+ const { debug, runEnvPath, runEnvAdminPath, runOptions = {} } = o;
102
+ const ro = runOptions || {};
103
+ const scopeOpts =
104
+ ro.effectiveEnvironmentScopedResources === true && ro.env
105
+ ? { effectiveEnvironmentScopedResources: true, env: String(ro.env).toLowerCase() }
106
+ : null;
107
+ const containerName = containerHelpers.getContainerName(appName, appConfig.developerId, scopeOpts);
108
+ logger.log(formatSuccessLine(`Container ${containerName} started`));
109
+ await containerHelpers.logContainerStatus(containerName, debug);
110
+
111
+ const healthCheckPath = appConfig?.healthCheck?.path || '/health';
112
+ const healthUrl = (typeof healthCheck.computeHealthCheckUrl === 'function')
113
+ ? await healthCheck.computeHealthCheckUrl(appName, port, appConfig, { runOptions: ro })
114
+ : null;
115
+ // Keep a stable fallback in the message if URL computation fails for any reason.
116
+ const displayUrl = (healthUrl && typeof healthUrl === 'string')
117
+ ? healthUrl
118
+ : `http://localhost:${port}${healthCheckPath}`;
119
+ logger.log(chalk.blue(`Waiting for application to be healthy at ${displayUrl}...`));
120
+ await healthCheck.waitForHealthCheck(appName, 90, port, appConfig, debug, ro);
121
+
122
+ for (const p of [runEnvPath, runEnvAdminPath]) {
123
+ if (p && typeof p === 'string') {
124
+ try {
125
+ await fs.unlink(p);
126
+ } catch (err) {
127
+ if (err.code !== 'ENOENT') logger.log(chalk.yellow(`Warning: could not remove run .env: ${err.message}`));
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Starts the container and waits for health check. Deletes run .env files after success (ISO 27K).
135
+ * @async
136
+ * @param {string} appName
137
+ * @param {string} composePath
138
+ * @param {number} port
139
+ * @param {Object} appConfig
140
+ * @param {Object} [opts]
141
+ */
142
+ async function startContainer(appName, composePath, port, appConfig = null, opts = {}) {
143
+ const {
144
+ debug = false,
145
+ runEnvPath = null,
146
+ runEnvAdminPath = null,
147
+ runOptions = {},
148
+ devMountPath = null,
149
+ misoEnvironment = 'dev'
150
+ } = opts;
151
+ logger.log(chalk.blue(`Starting ${appName}...`));
152
+
153
+ let composeCmdBase;
154
+ let usedDockerRunFallback = false;
155
+ try {
156
+ composeCmdBase = await dockerUtils.ensureDockerAndCompose();
157
+ } catch (composeErr) {
158
+ if (runDockerFallback.canUseDockerRunWithoutCompose(appConfig, { devMountPath })) {
159
+ await emitAndRunDockerFallback(appName, appConfig, port, {
160
+ debug,
161
+ runEnvPath,
162
+ runOptions,
163
+ misoEnvironment
164
+ });
165
+ usedDockerRunFallback = true;
166
+ } else {
167
+ throw composeErr;
168
+ }
169
+ }
170
+
171
+ if (!usedDockerRunFallback) {
172
+ const env = await prepareContainerEnv(debug);
173
+ await executeComposeUp(composeCmdBase, composePath, env, debug);
174
+ }
175
+
176
+ await waitForHealthyAndCleanupEnvFiles(appName, port, appConfig, {
177
+ debug,
178
+ runEnvPath,
179
+ runEnvAdminPath,
180
+ runOptions
181
+ });
182
+ }
183
+
184
+ module.exports = { startContainer, prepareContainerEnv, executeComposeUp };
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Fallback `docker run` when Docker Compose CLI is missing — narrow case only.
3
+ *
4
+ * @fileoverview Apps that declare no Postgres/Redis in application.yaml can start
5
+ * with plain Docker; same eligibility as skipping local infra health.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const chalk = require('chalk');
11
+ const logger = require('../utils/logger');
12
+ const { execWithDockerEnv } = require('../utils/docker-exec');
13
+ const { getContainerPort } = require('../utils/port-resolver');
14
+ const { getAppInfraRequirements } = require('./run-infra-requirements');
15
+
16
+ /**
17
+ * Shell-single-quote a string for use inside a POSIX sh -c style command.
18
+ * @param {string} value
19
+ * @returns {string}
20
+ */
21
+ function shellSingleQuote(value) {
22
+ const s = String(value);
23
+ return '\'' + s.replace(/'/g, '\'\\\'\'') + '\'';
24
+ }
25
+
26
+ /**
27
+ * Named volume for /mnt/data (must match templates/typescript/docker-compose.hbs).
28
+ * @param {string} appName
29
+ * @param {string|number} developerId
30
+ * @returns {string}
31
+ */
32
+ function storageVolumeName(appName, developerId) {
33
+ const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
34
+ if (idNum === 0) {
35
+ return `aifabrix_${appName}_data`;
36
+ }
37
+ return `aifabrix_dev${developerId}_${appName}_data`;
38
+ }
39
+
40
+ /**
41
+ * True when safe to emulate compose with `docker run` if Compose is unavailable.
42
+ * @param {Object} appConfig
43
+ * @param {{ devMountPath?: string|null }} startOpts
44
+ * @returns {boolean}
45
+ */
46
+ function canUseDockerRunWithoutCompose(appConfig, startOpts) {
47
+ const req = getAppInfraRequirements(appConfig);
48
+ if (!req || req.needsPostgres || req.needsRedis) {
49
+ return false;
50
+ }
51
+ if (appConfig.frontDoorRouting && appConfig.frontDoorRouting.enabled === true) {
52
+ return false;
53
+ }
54
+ if (startOpts.devMountPath) {
55
+ return false;
56
+ }
57
+ return true;
58
+ }
59
+
60
+ /**
61
+ * @param {Object} o
62
+ * @param {string} o.appName
63
+ * @param {Object} o.appConfig
64
+ * @param {number} o.hostPort
65
+ * @param {string} o.fullImage image:tag
66
+ * @param {string|null} o.runEnvPath
67
+ * @param {string} o.misoEnvironment
68
+ * @param {boolean} o.debug
69
+ * @returns {Promise<void>}
70
+ */
71
+ async function executeDockerRunUp(o) {
72
+ const { appName, appConfig, hostPort, fullImage, runEnvPath, misoEnvironment, debug } = o;
73
+ const idNum = typeof appConfig.developerId === 'string' ? parseInt(appConfig.developerId, 10) : appConfig.developerId;
74
+ const containerName = idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${appConfig.developerId}-${appName}`;
75
+ const containerPort = getContainerPort(appConfig, 3000);
76
+
77
+ const needsStorage =
78
+ appConfig.requires?.storage === true ||
79
+ appConfig.services?.storage === true;
80
+
81
+ const parts = [
82
+ 'docker run -d',
83
+ `--name ${shellSingleQuote(containerName)}`,
84
+ '--restart unless-stopped',
85
+ `-p ${hostPort}:${containerPort}`,
86
+ `-e MISO_ENVIRONMENT=${shellSingleQuote(misoEnvironment)}`
87
+ ];
88
+
89
+ if (needsStorage) {
90
+ parts.push(`-v ${shellSingleQuote(storageVolumeName(appName, appConfig.developerId))}:/mnt/data`);
91
+ }
92
+ if (runEnvPath && typeof runEnvPath === 'string') {
93
+ parts.push(`--env-file ${shellSingleQuote(runEnvPath)}`);
94
+ }
95
+ parts.push(shellSingleQuote(fullImage));
96
+
97
+ const cmd = parts.join(' ');
98
+ if (debug) {
99
+ logger.log(chalk.gray(`[DEBUG] Docker run fallback: ${cmd.replace(/env-file [^ ]+/, 'env-file <redacted>')}`));
100
+ }
101
+ await execWithDockerEnv(cmd);
102
+ }
103
+
104
+ module.exports = {
105
+ canUseDockerRunWithoutCompose,
106
+ executeDockerRunUp,
107
+ storageVolumeName
108
+ };