@aifabrix/builder 2.43.0 → 2.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/README.md +1 -1
  3. package/anchor-docs/README.md +10 -0
  4. package/anchor-docs/_TEMPLATE +24 -0
  5. package/bin/aifabrix.js +13 -4
  6. package/integration/hubspot-test/README.md +31 -0
  7. package/integration/hubspot-test/create-hubspot.js +5 -5
  8. package/integration/hubspot-test/hubspot-test-datasource-company.json +58 -462
  9. package/integration/hubspot-test/hubspot-test-datasource-contact.json +61 -555
  10. package/integration/hubspot-test/hubspot-test-datasource-deal.json +63 -506
  11. package/integration/hubspot-test/hubspot-test-datasource-users.json +42 -83
  12. package/integration/hubspot-test/hubspot-test-deploy.json +3 -3
  13. package/integration/hubspot-test/test-dataplane-down-tests.js +1 -7
  14. package/integration/hubspot-test/test-dataplane-down.js +3 -3
  15. package/integration/hubspot-test/test.js +35 -43
  16. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  17. package/integration/roundtrip-test-local/README.md +144 -0
  18. package/integration/roundtrip-test-local/application.yaml +13 -0
  19. package/integration/roundtrip-test-local/env.template +15 -0
  20. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  21. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  22. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  23. package/integration/roundtrip-test-local2/README.md +144 -0
  24. package/integration/roundtrip-test-local2/application.yaml +13 -0
  25. package/integration/roundtrip-test-local2/env.template +15 -0
  26. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  27. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  28. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  29. package/integration/test/wizard.yaml +8 -0
  30. package/jest.config.default.js +10 -0
  31. package/jest.config.integration.fixtures.js +22 -0
  32. package/jest.config.integration.js +21 -18
  33. package/jest.config.isolated.js +10 -0
  34. package/jest.projects.js +288 -0
  35. package/lib/api/datasources-core.api.js +3 -3
  36. package/lib/api/dev-mtls-request.js +110 -0
  37. package/lib/api/dev-server-https.js +145 -0
  38. package/lib/api/dev.api.js +133 -144
  39. package/lib/api/index.js +0 -1
  40. package/lib/api/pipeline.api.js +67 -20
  41. package/lib/api/types/dev.types.js +4 -3
  42. package/lib/api/types/pipeline.types.js +8 -5
  43. package/lib/api/types/validation-run.types.js +56 -0
  44. package/lib/api/validation-run.api.js +99 -0
  45. package/lib/api/validation-runner.js +99 -0
  46. package/lib/app/config.js +1 -1
  47. package/lib/app/deploy-status-display.js +2 -2
  48. package/lib/app/deploy.js +7 -6
  49. package/lib/app/display.js +2 -1
  50. package/lib/app/dockerfile.js +3 -2
  51. package/lib/app/down.js +2 -1
  52. package/lib/app/helpers.js +6 -5
  53. package/lib/app/index.js +27 -8
  54. package/lib/app/list.js +7 -6
  55. package/lib/app/push.js +4 -3
  56. package/lib/app/register.js +16 -7
  57. package/lib/app/rotate-secret.js +14 -13
  58. package/lib/app/run-container-start.js +184 -0
  59. package/lib/app/run-docker-fallback.js +108 -0
  60. package/lib/app/run-env-compose.js +30 -42
  61. package/lib/app/run-helpers.js +49 -126
  62. package/lib/app/run-infra-requirements.js +30 -0
  63. package/lib/app/run-resolve-image.js +21 -0
  64. package/lib/app/run.js +74 -21
  65. package/lib/app/show-display.js +1 -1
  66. package/lib/app/show.js +1 -1
  67. package/lib/build/index.js +13 -10
  68. package/lib/cli/index.js +2 -0
  69. package/lib/cli/setup-app.help.js +67 -0
  70. package/lib/cli/setup-app.js +57 -121
  71. package/lib/cli/setup-app.test-commands.js +179 -0
  72. package/lib/cli/setup-auth.js +19 -5
  73. package/lib/cli/setup-credential-deployment.js +22 -8
  74. package/lib/cli/setup-dev-path-commands.js +124 -0
  75. package/lib/cli/setup-dev.js +170 -113
  76. package/lib/cli/setup-environment.js +7 -1
  77. package/lib/cli/setup-external-system.js +62 -22
  78. package/lib/cli/setup-infra.js +126 -47
  79. package/lib/cli/setup-parameters.js +32 -0
  80. package/lib/cli/setup-secrets.js +106 -8
  81. package/lib/cli/setup-service-user.js +1 -1
  82. package/lib/cli/setup-utility.js +36 -20
  83. package/lib/commands/app-down.js +5 -7
  84. package/lib/commands/app-install.js +14 -7
  85. package/lib/commands/app-logs.js +13 -10
  86. package/lib/commands/app-shell.js +4 -1
  87. package/lib/commands/app-test.js +25 -19
  88. package/lib/commands/app.js +22 -10
  89. package/lib/commands/auth-config.js +6 -6
  90. package/lib/commands/auth-status.js +4 -3
  91. package/lib/commands/credential-env.js +4 -3
  92. package/lib/commands/credential-list.js +5 -4
  93. package/lib/commands/credential-push.js +4 -3
  94. package/lib/commands/datasource-unified-test-cli.js +495 -0
  95. package/lib/commands/datasource-unified-test-cli.options.js +149 -0
  96. package/lib/commands/datasource-validation-cli.js +129 -0
  97. package/lib/commands/datasource.js +105 -98
  98. package/lib/commands/deployment-list.js +6 -5
  99. package/lib/commands/dev-cli-handlers.js +122 -18
  100. package/lib/commands/dev-down.js +4 -3
  101. package/lib/commands/dev-init.js +231 -116
  102. package/lib/commands/dev-show-display.js +473 -0
  103. package/lib/commands/login-credentials.js +3 -2
  104. package/lib/commands/login-device.js +4 -3
  105. package/lib/commands/login.js +5 -4
  106. package/lib/commands/logout.js +8 -7
  107. package/lib/commands/parameters-validate.js +54 -0
  108. package/lib/commands/repair-datasource.js +314 -68
  109. package/lib/commands/repair-env-template.js +2 -2
  110. package/lib/commands/repair.js +21 -3
  111. package/lib/commands/secrets-list.js +23 -12
  112. package/lib/commands/secrets-remove-all.js +220 -0
  113. package/lib/commands/secrets-remove.js +21 -12
  114. package/lib/commands/secrets-set.js +21 -12
  115. package/lib/commands/secrets-validate.js +4 -4
  116. package/lib/commands/secure.js +10 -9
  117. package/lib/commands/service-user.js +26 -25
  118. package/lib/commands/test-e2e-external.js +27 -1
  119. package/lib/commands/up-common.js +3 -2
  120. package/lib/commands/up-dataplane.js +29 -16
  121. package/lib/commands/up-miso.js +19 -29
  122. package/lib/commands/upload.js +138 -39
  123. package/lib/commands/wizard-core-helpers.js +1 -1
  124. package/lib/commands/wizard-dataplane.js +4 -3
  125. package/lib/commands/wizard-helpers.js +3 -3
  126. package/lib/commands/wizard.js +2 -2
  127. package/lib/core/admin-secrets.js +14 -5
  128. package/lib/core/audit-logger.js +12 -4
  129. package/lib/core/config-attach-extensions.js +46 -0
  130. package/lib/core/config-runtime-paths.js +29 -0
  131. package/lib/core/config.js +55 -56
  132. package/lib/core/diff.js +3 -2
  133. package/lib/core/ensure-encryption-key.js +1 -1
  134. package/lib/core/secrets-ensure-infra.js +77 -0
  135. package/lib/core/secrets-ensure.js +120 -64
  136. package/lib/core/secrets-env-write.js +35 -7
  137. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  138. package/lib/core/secrets.js +200 -37
  139. package/lib/core/templates-env.js +4 -3
  140. package/lib/datasource/abac-validator.js +1 -10
  141. package/lib/datasource/deploy.js +75 -53
  142. package/lib/datasource/field-reference-validator.js +9 -6
  143. package/lib/datasource/integration-context.js +63 -0
  144. package/lib/datasource/list.js +8 -7
  145. package/lib/datasource/log-viewer.js +84 -53
  146. package/lib/datasource/resolve-app.js +4 -4
  147. package/lib/datasource/test-e2e.js +95 -146
  148. package/lib/datasource/test-integration.js +114 -122
  149. package/lib/datasource/unified-validation-run-body.js +65 -0
  150. package/lib/datasource/unified-validation-run-post.js +23 -0
  151. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  152. package/lib/datasource/unified-validation-run.js +92 -0
  153. package/lib/datasource/validate.js +157 -13
  154. package/lib/deployment/deployer.js +4 -3
  155. package/lib/deployment/environment.js +7 -6
  156. package/lib/deployment/push.js +17 -8
  157. package/lib/external-system/delete.js +4 -3
  158. package/lib/external-system/deploy.js +131 -53
  159. package/lib/external-system/download-helpers.js +1 -1
  160. package/lib/external-system/download.js +7 -6
  161. package/lib/external-system/generator.js +92 -6
  162. package/lib/external-system/integration-test-dispatch.js +26 -0
  163. package/lib/external-system/test-execution.js +5 -1
  164. package/lib/external-system/test-helpers.js +0 -4
  165. package/lib/external-system/test-system-level-helpers.js +110 -0
  166. package/lib/external-system/test-system-level.js +83 -44
  167. package/lib/external-system/test.js +59 -8
  168. package/lib/generator/builders.js +23 -11
  169. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  170. package/lib/generator/external.js +16 -4
  171. package/lib/generator/helpers.js +58 -3
  172. package/lib/generator/index.js +4 -0
  173. package/lib/generator/split-readme.js +12 -7
  174. package/lib/generator/split-variables.js +2 -1
  175. package/lib/generator/split.js +1 -1
  176. package/lib/generator/wizard-readme.js +3 -3
  177. package/lib/generator/wizard.js +8 -8
  178. package/lib/infrastructure/compose.js +60 -6
  179. package/lib/infrastructure/helpers.js +201 -29
  180. package/lib/infrastructure/index.js +28 -17
  181. package/lib/infrastructure/services.js +21 -15
  182. package/lib/internal/fs-real-sync.js +104 -0
  183. package/lib/internal/node-fs.js +98 -0
  184. package/lib/parameters/database-secret-values.js +173 -0
  185. package/lib/parameters/infra-kv-discovery.js +121 -0
  186. package/lib/parameters/infra-parameter-catalog.js +458 -0
  187. package/lib/parameters/infra-parameter-validate.js +64 -0
  188. package/lib/schema/application-schema.json +37 -17
  189. package/lib/schema/datasource-test-run.schema.json +493 -0
  190. package/lib/schema/deployment-rules.yaml +102 -63
  191. package/lib/schema/external-datasource.schema.json +1200 -442
  192. package/lib/schema/external-system.schema.json +181 -5
  193. package/lib/schema/flag-map-validation-run.json +31 -0
  194. package/lib/schema/infra-parameter.schema.json +106 -0
  195. package/lib/schema/infra.parameter.yaml +421 -0
  196. package/lib/schema/type/credential-auth-templates.json +40 -0
  197. package/lib/schema/type/document-storage.json +213 -0
  198. package/lib/schema/type/message-service.json +123 -0
  199. package/lib/schema/type/vector-store.json +88 -0
  200. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  201. package/lib/utils/api-error-handler.js +2 -2
  202. package/lib/utils/api.js +49 -14
  203. package/lib/utils/app-register-api.js +3 -2
  204. package/lib/utils/app-register-auth.js +1 -1
  205. package/lib/utils/app-register-config.js +4 -4
  206. package/lib/utils/app-register-display.js +3 -2
  207. package/lib/utils/app-register-validator.js +3 -2
  208. package/lib/utils/app-run-containers.js +26 -22
  209. package/lib/utils/app-scoped-config.js +31 -0
  210. package/lib/utils/app-service-env-from-builder.js +164 -0
  211. package/lib/utils/build-copy.js +1 -1
  212. package/lib/utils/build-helpers.js +20 -20
  213. package/lib/utils/build-resolve-image.js +165 -0
  214. package/lib/utils/cli-layout-chalk.js +8 -0
  215. package/lib/utils/cli-test-layout-chalk.js +267 -0
  216. package/lib/utils/cli-utils.js +88 -11
  217. package/lib/utils/compose-db-passwords.js +138 -0
  218. package/lib/utils/compose-generate-docker-compose.js +216 -0
  219. package/lib/utils/compose-generator.js +197 -291
  220. package/lib/utils/compose-miso-env.js +18 -0
  221. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  222. package/lib/utils/config-paths.js +166 -7
  223. package/lib/utils/config-scoped-resources-preference.js +41 -0
  224. package/lib/utils/controller-deployment-outcome.js +68 -0
  225. package/lib/utils/credential-display.js +2 -2
  226. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  227. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  228. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  229. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  230. package/lib/utils/datasource-test-run-display.js +442 -0
  231. package/lib/utils/datasource-test-run-exit.js +58 -0
  232. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  233. package/lib/utils/datasource-test-run-report-version.js +51 -0
  234. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  235. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  236. package/lib/utils/datasource-validation-watch.js +266 -0
  237. package/lib/utils/declarative-url-ports.js +47 -0
  238. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  239. package/lib/utils/dev-ca-install.js +185 -23
  240. package/lib/utils/dev-cert-helper.js +266 -17
  241. package/lib/utils/dev-hosts-helper.js +307 -0
  242. package/lib/utils/dev-init-cert-hints.js +37 -0
  243. package/lib/utils/dev-init-health-messages.js +52 -0
  244. package/lib/utils/dev-init-resolve.js +86 -0
  245. package/lib/utils/dev-init-ssh-merge.js +65 -0
  246. package/lib/utils/dev-ssh-config-helper.js +196 -0
  247. package/lib/utils/dev-user-groups.js +93 -0
  248. package/lib/utils/docker-build.js +42 -17
  249. package/lib/utils/docker-exec.js +28 -0
  250. package/lib/utils/docker-manifest-public-port.js +116 -0
  251. package/lib/utils/docker-not-running-hint.js +52 -0
  252. package/lib/utils/docker.js +98 -11
  253. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  254. package/lib/utils/env-config-loader.js +10 -91
  255. package/lib/utils/env-copy.js +19 -10
  256. package/lib/utils/env-map.js +35 -8
  257. package/lib/utils/env-template.js +2 -2
  258. package/lib/utils/environment-scoped-resources.js +144 -0
  259. package/lib/utils/error-formatter.js +92 -13
  260. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  261. package/lib/utils/error-formatters/network-errors.js +2 -1
  262. package/lib/utils/error-formatters/permission-errors.js +2 -1
  263. package/lib/utils/error-formatters/validation-errors.js +2 -1
  264. package/lib/utils/external-readme.js +8 -1
  265. package/lib/utils/external-system-display.js +234 -136
  266. package/lib/utils/external-system-local-test-tty.js +389 -0
  267. package/lib/utils/external-system-readiness-core.js +377 -0
  268. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  269. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  270. package/lib/utils/external-system-readiness-display.js +186 -0
  271. package/lib/utils/external-system-test-helpers.js +24 -6
  272. package/lib/utils/external-system-validators.js +30 -12
  273. package/lib/utils/health-check-url.js +119 -0
  274. package/lib/utils/health-check.js +59 -25
  275. package/lib/utils/help-builder.js +11 -8
  276. package/lib/utils/image-version.js +4 -8
  277. package/lib/utils/infra-containers.js +4 -7
  278. package/lib/utils/infra-env-defaults.js +162 -0
  279. package/lib/utils/infra-status-display.js +167 -0
  280. package/lib/utils/infra-status.js +16 -8
  281. package/lib/utils/local-secrets.js +3 -4
  282. package/lib/utils/paths.js +134 -47
  283. package/lib/utils/port-resolver.js +10 -23
  284. package/lib/utils/redis-env-scope.js +62 -0
  285. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  286. package/lib/utils/remote-builder-validation.js +99 -0
  287. package/lib/utils/remote-dev-auth.js +117 -21
  288. package/lib/utils/remote-docker-env.js +67 -15
  289. package/lib/utils/remote-secrets-loader.js +13 -4
  290. package/lib/utils/resolve-docker-image-ref.js +124 -0
  291. package/lib/utils/schema-loader.js +22 -9
  292. package/lib/utils/secrets-bash-kv.js +25 -0
  293. package/lib/utils/secrets-generator.js +169 -49
  294. package/lib/utils/secrets-helpers.js +70 -59
  295. package/lib/utils/secrets-kv-scope.js +60 -0
  296. package/lib/utils/secrets-utils.js +32 -38
  297. package/lib/utils/secrets-validation.js +3 -1
  298. package/lib/utils/secrets-yaml-preserve.js +109 -0
  299. package/lib/utils/ssh-key-helper.js +4 -2
  300. package/lib/utils/template-helpers.js +2 -2
  301. package/lib/utils/test-log-writer.js +3 -3
  302. package/lib/utils/token-manager.js +1 -2
  303. package/lib/utils/url-declarative-public-base.js +188 -0
  304. package/lib/utils/url-declarative-resolve-build.js +493 -0
  305. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  306. package/lib/utils/url-declarative-resolve.js +220 -0
  307. package/lib/utils/url-declarative-token-parse.js +74 -0
  308. package/lib/utils/url-declarative-url-flags.js +50 -0
  309. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  310. package/lib/utils/url-public-path-prefix.js +34 -0
  311. package/lib/utils/urls-local-registry.js +220 -0
  312. package/lib/utils/validation-report-tty-kit.js +77 -0
  313. package/lib/utils/validation-run-poll.js +89 -0
  314. package/lib/utils/validation-run-post-retry.js +73 -0
  315. package/lib/utils/validation-run-request.js +98 -0
  316. package/lib/utils/variable-transformer.js +21 -4
  317. package/lib/utils/yaml-preserve.js +33 -14
  318. package/lib/validation/datasource-warnings.js +56 -0
  319. package/lib/validation/env-template-auth.js +1 -1
  320. package/lib/validation/external-manifest-validator.js +27 -7
  321. package/lib/validation/validate-display.js +37 -31
  322. package/lib/validation/validate.js +4 -13
  323. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  324. package/lib/validation/validator.js +22 -65
  325. package/lib/validation/wizard-config-validator.js +2 -1
  326. package/package.json +7 -3
  327. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  328. package/scripts/diagnose-cli.js +150 -0
  329. package/scripts/install-local.js +304 -55
  330. package/templates/README.md +15 -2
  331. package/templates/applications/dataplane/application.yaml +52 -2
  332. package/templates/applications/dataplane/env.template +75 -17
  333. package/templates/applications/dataplane/rbac.yaml +8 -0
  334. package/templates/applications/keycloak/application.yaml +9 -1
  335. package/templates/applications/keycloak/env.template +15 -6
  336. package/templates/applications/miso-controller/application.yaml +10 -2
  337. package/templates/applications/miso-controller/env.template +42 -12
  338. package/templates/applications/miso-controller/rbac.yaml +5 -0
  339. package/templates/external-system/README.md.hbs +20 -7
  340. package/templates/external-system/deploy.js.hbs +5 -5
  341. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  342. package/templates/infra/compose.yaml.hbs +20 -4
  343. package/templates/python/docker-compose.hbs +16 -0
  344. package/templates/typescript/docker-compose.hbs +16 -0
  345. package/lib/api/external-test.api.js +0 -111
  346. package/lib/schema/env-config.yaml +0 -60
@@ -1,3 +1,4 @@
1
+ const { formatSuccessLine } = require('./cli-test-layout-chalk');
1
2
  /**
2
3
  * Health Check Utilities
3
4
  *
@@ -9,13 +10,41 @@
9
10
  */
10
11
 
11
12
  const http = require('http');
13
+ const https = require('https');
12
14
  const net = require('net');
13
15
  const chalk = require('chalk');
14
- const { exec } = require('child_process');
15
- const { promisify } = require('util');
16
16
  const logger = require('./logger');
17
+ const { execWithDockerEnv } = require('./docker-exec');
18
+ const { computeTraefikHealthCheckUrl } = require('./health-check-url');
17
19
 
18
- const execAsync = promisify(exec);
20
+ /**
21
+ * Compute the health check URL for an app.
22
+ *
23
+ * - Default (no Traefik front-door): http://localhost:<port><healthPath>
24
+ * - With Traefik + app frontDoorRouting.enabled: <publicBase><frontDoorRouting.pattern><healthPath>
25
+ *
26
+ * @async
27
+ * @param {string} appName
28
+ * @param {number} healthCheckPort
29
+ * @param {Object|null} appConfig
30
+ * @param {Object} opts
31
+ * @param {Object} [opts.runOptions] - runApp options (may include env + effectiveEnvironmentScopedResources)
32
+ * @returns {Promise<string>}
33
+ */
34
+ async function computeHealthCheckUrl(appName, healthCheckPort, appConfig, _opts = {}) {
35
+ const healthCheckPath = appConfig?.healthCheck?.path || '/health';
36
+
37
+ // Traefik front-door branch: probe via resolved public host + path (e.g. https://dev02.builder02.local/auth/health/ready)
38
+ try {
39
+ const traefikUrl = await computeTraefikHealthCheckUrl(appName, healthCheckPort, appConfig);
40
+ if (traefikUrl) return traefikUrl;
41
+ } catch {
42
+ // If any resolver step fails, fall back to localhost probing.
43
+ }
44
+
45
+ // Default: probe local published port.
46
+ return `http://localhost:${healthCheckPort}${healthCheckPath}`;
47
+ }
19
48
 
20
49
  /**
21
50
  * Checks if db-init container exists and waits for it to complete
@@ -33,7 +62,7 @@ const execAsync = promisify(exec);
33
62
  */
34
63
  async function checkDbInitContainerExists(dbInitContainer) {
35
64
  try {
36
- const { stdout } = await execAsync(`docker ps -a --filter "name=${dbInitContainer}" --format "{{.Names}}"`);
65
+ const { stdout } = await execWithDockerEnv(`docker ps -a --filter "name=${dbInitContainer}" --format "{{.Names}}"`);
37
66
  return stdout.trim() === dbInitContainer;
38
67
  } catch {
39
68
  return false;
@@ -48,7 +77,7 @@ async function checkDbInitContainerExists(dbInitContainer) {
48
77
  * @returns {Promise<string>} Exit code
49
78
  */
50
79
  async function getContainerExitCode(dbInitContainer) {
51
- const { stdout: exitCode } = await execAsync(`docker inspect --format='{{.State.ExitCode}}' ${dbInitContainer}`);
80
+ const { stdout: exitCode } = await execWithDockerEnv(`docker inspect --format='{{.State.ExitCode}}' ${dbInitContainer}`);
52
81
  return exitCode.trim();
53
82
  }
54
83
 
@@ -60,11 +89,11 @@ async function getContainerExitCode(dbInitContainer) {
60
89
  * @returns {Promise<boolean>} True if handled (container already exited)
61
90
  */
62
91
  async function handleExitedContainer(dbInitContainer) {
63
- const { stdout: status } = await execAsync(`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`);
92
+ const { stdout: status } = await execWithDockerEnv(`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`);
64
93
  if (status.trim() === 'exited') {
65
94
  const exitCode = await getContainerExitCode(dbInitContainer);
66
95
  if (exitCode === '0') {
67
- logger.log(chalk.green('Database initialization already completed'));
96
+ logger.log(formatSuccessLine('Database initialization already completed'));
68
97
  } else {
69
98
  logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode}`));
70
99
  }
@@ -82,11 +111,11 @@ async function handleExitedContainer(dbInitContainer) {
82
111
  */
83
112
  async function waitForContainerExit(dbInitContainer, maxAttempts) {
84
113
  for (let attempts = 0; attempts < maxAttempts; attempts++) {
85
- const { stdout: currentStatus } = await execAsync(`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`);
114
+ const { stdout: currentStatus } = await execWithDockerEnv(`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`);
86
115
  if (currentStatus.trim() === 'exited') {
87
116
  const exitCode = await getContainerExitCode(dbInitContainer);
88
117
  if (exitCode === '0') {
89
- logger.log(chalk.green('Database initialization completed'));
118
+ logger.log(formatSuccessLine('Database initialization completed'));
90
119
  } else {
91
120
  logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode}`));
92
121
  }
@@ -135,7 +164,7 @@ async function getPortFromDockerInspect(appName, debug) {
135
164
  if (debug) {
136
165
  logger.log(chalk.gray(`[DEBUG] Executing: ${inspectCmd}`));
137
166
  }
138
- const { stdout: portMapping } = await execAsync(inspectCmd);
167
+ const { stdout: portMapping } = await execWithDockerEnv(inspectCmd);
139
168
  const ports = portMapping.trim().split('\n').filter(p => p && p !== '');
140
169
  if (ports.length > 0) {
141
170
  const port = parseInt(ports[0], 10);
@@ -162,7 +191,7 @@ async function getPortFromDockerPs(appName, debug) {
162
191
  if (debug) {
163
192
  logger.log(chalk.gray(`[DEBUG] Fallback: Executing: ${psCmd}`));
164
193
  }
165
- const { stdout: psOutput } = await execAsync(psCmd);
194
+ const { stdout: psOutput } = await execWithDockerEnv(psCmd);
166
195
  const portMatch = psOutput.match(/:(\d+)->/);
167
196
  if (!portMatch) {
168
197
  return null;
@@ -244,6 +273,7 @@ function handleHealthResponse(res, data, debug, resolve) {
244
273
  function doHealthCheckRequest(healthCheckUrl, debug, resolve, reject) {
245
274
  try {
246
275
  const urlObj = new URL(healthCheckUrl);
276
+ const transport = urlObj.protocol === 'https:' ? https : http;
247
277
  const options = {
248
278
  hostname: urlObj.hostname,
249
279
  port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
@@ -254,10 +284,9 @@ function doHealthCheckRequest(healthCheckUrl, debug, resolve, reject) {
254
284
  logger.log(chalk.gray(`[DEBUG] Health check request: ${healthCheckUrl}`));
255
285
  logger.log(chalk.gray(`[DEBUG] Request options: ${JSON.stringify(options, null, 2)}`));
256
286
  }
257
- // eslint-disable-next-line prefer-const
258
- let timeoutId;
259
- const req = http.request(options, (res) => {
260
- clearTimeout(timeoutId);
287
+ const timer = { id: null };
288
+ const req = transport.request(options, (res) => {
289
+ clearTimeout(timer.id);
261
290
  let data = '';
262
291
  if (debug) {
263
292
  logger.log(chalk.gray(`[DEBUG] Response status code: ${res.statusCode}`));
@@ -268,13 +297,13 @@ function doHealthCheckRequest(healthCheckUrl, debug, resolve, reject) {
268
297
  });
269
298
  res.on('end', () => handleHealthResponse(res, data, debug, resolve));
270
299
  });
271
- timeoutId = setTimeout(() => {
300
+ timer.id = setTimeout(() => {
272
301
  if (debug) logger.log(chalk.gray('[DEBUG] Health check request timeout after 5 seconds'));
273
302
  req.destroy();
274
303
  resolve(false);
275
304
  }, 5000);
276
305
  req.on('error', (error) => {
277
- clearTimeout(timeoutId);
306
+ clearTimeout(timer.id);
278
307
  if (debug) logger.log(chalk.gray(`[DEBUG] Health check request error: ${error.message}`));
279
308
  resolve(false);
280
309
  });
@@ -341,16 +370,14 @@ async function determineHealthCheckPort(port, appName, debug) {
341
370
  * @returns {Object} Health check configuration
342
371
  */
343
372
  function buildHealthCheckConfig(healthCheckPort, config, timeout, debug) {
344
- const healthCheckPath = config?.healthCheck?.path || '/health';
345
- const healthCheckUrl = `http://localhost:${healthCheckPort}${healthCheckPath}`;
346
373
  const maxAttempts = timeout / 2;
347
374
 
375
+ // URL is computed later (may use Traefik public base).
348
376
  if (debug) {
349
- logger.log(chalk.gray(`[DEBUG] Health check URL: ${healthCheckUrl}`));
350
377
  logger.log(chalk.gray(`[DEBUG] Timeout: ${timeout} seconds, Max attempts: ${maxAttempts}`));
351
378
  }
352
379
 
353
- return { healthCheckUrl, maxAttempts };
380
+ return { maxAttempts };
354
381
  }
355
382
 
356
383
  /**
@@ -370,7 +397,7 @@ async function performHealthCheckAttempt(healthCheckUrl, attempt, maxAttempts, d
370
397
  }
371
398
  const healthCheckPassed = await checkHealthEndpoint(healthCheckUrl, debug);
372
399
  if (healthCheckPassed) {
373
- logger.log(chalk.green('Application is healthy'));
400
+ logger.log(formatSuccessLine('Application is healthy'));
374
401
  if (debug) {
375
402
  logger.log(chalk.gray(`[DEBUG] Health check passed after ${attempt + 1} attempt(s)`));
376
403
  }
@@ -384,11 +411,15 @@ async function performHealthCheckAttempt(healthCheckUrl, attempt, maxAttempts, d
384
411
  return false;
385
412
  }
386
413
 
387
- async function waitForHealthCheck(appName, timeout = 90, port = null, config = null, debug = false) {
414
+ async function waitForHealthCheck(appName, timeout = 90, port = null, config = null, debug = false, runOptions = {}) {
388
415
  await waitForDbInit(appName);
389
416
 
390
417
  const healthCheckPort = await determineHealthCheckPort(port, appName, debug);
391
- const { healthCheckUrl, maxAttempts } = buildHealthCheckConfig(healthCheckPort, config, timeout, debug);
418
+ const { maxAttempts } = buildHealthCheckConfig(healthCheckPort, config, timeout, debug);
419
+ const healthCheckUrl = await computeHealthCheckUrl(appName, healthCheckPort, config, { runOptions });
420
+ if (debug) {
421
+ logger.log(chalk.gray(`[DEBUG] Health check URL: ${healthCheckUrl}`));
422
+ }
392
423
 
393
424
  for (let attempts = 0; attempts < maxAttempts; attempts++) {
394
425
  const passed = await performHealthCheckAttempt(healthCheckUrl, attempts, maxAttempts, debug);
@@ -426,6 +457,9 @@ async function checkPortAvailable(port) {
426
457
  module.exports = {
427
458
  waitForHealthCheck,
428
459
  checkHealthEndpoint,
429
- checkPortAvailable
460
+ checkPortAvailable,
461
+ computeHealthCheckUrl,
462
+ // Re-exported for tests and shared usage.
463
+ normalizeFrontDoorPatternForHealth: require('./health-check-url').normalizeFrontDoorPatternForHealth
430
464
  };
431
465
 
@@ -12,7 +12,7 @@ const { Help } = require('commander');
12
12
 
13
13
  /**
14
14
  * Command categories and order. Each command can have an optional `term` override
15
- * for the help line (e.g. "down [app]"); otherwise the command name is used.
15
+ * for the help line (e.g. "down [service|app]"); otherwise the command name is used.
16
16
  *
17
17
  * @type {Array<{ name: string, commands: Array<{ name: string, term?: string }> }>}
18
18
  */
@@ -24,10 +24,10 @@ const CATEGORIES = [
24
24
  { name: 'up-platform' },
25
25
  { name: 'up-miso' },
26
26
  { name: 'up-dataplane' },
27
- { name: 'down-infra', term: 'down-infra [app]' },
27
+ { name: 'down-infra', term: 'down-infra [service|app]' },
28
28
  { name: 'doctor' },
29
29
  { name: 'status' },
30
- { name: 'restart', term: 'restart <service>' }
30
+ { name: 'restart', term: 'restart <service|app>' }
31
31
  ]
32
32
  },
33
33
  {
@@ -82,18 +82,19 @@ const CATEGORIES = [
82
82
  { name: 'json', term: 'json <app>' },
83
83
  { name: 'split-json', term: 'split-json <app>' },
84
84
  { name: 'convert', term: 'convert <app>' },
85
- { name: 'show', term: 'show <appKey>' },
85
+ { name: 'show', term: 'show <app>' },
86
86
  { name: 'validate', term: 'validate <appOrFile>' },
87
+ { name: 'parameters', term: 'parameters validate' },
87
88
  { name: 'diff', term: 'diff <file1> <file2>' }
88
89
  ]
89
90
  },
90
91
  {
91
92
  name: 'External Systems',
92
93
  commands: [
93
- { name: 'download', term: 'download <system-key>' },
94
- { name: 'upload', term: 'upload <system-key>' },
95
- { name: 'delete', term: 'delete <system-key>' },
96
- { name: 'repair', term: 'repair <app>' },
94
+ { name: 'download', term: 'download <systemKey>' },
95
+ { name: 'upload', term: 'upload <systemKey>' },
96
+ { name: 'delete', term: 'delete <systemKey>' },
97
+ { name: 'repair', term: 'repair <systemKey>' },
97
98
  { name: 'datasource' },
98
99
  { name: 'test', term: 'test <app>' },
99
100
  { name: 'test-e2e', term: 'test-e2e <app>' },
@@ -182,6 +183,8 @@ function formatCommandCategories(helper, program) {
182
183
  out.push('Help:');
183
184
  out.push(` ${'help [command]'.padEnd(pad)}display help for command`);
184
185
  out.push('');
186
+ out.push(` ${''.padEnd(pad)}Tip: aifabrix <command> --help for flags and examples`);
187
+ out.push('');
185
188
  return out;
186
189
  }
187
190
 
@@ -9,15 +9,12 @@
9
9
  * @version 2.0.0
10
10
  */
11
11
 
12
- const { exec } = require('child_process');
13
- const { promisify } = require('util');
14
12
  const { getBuilderPath } = require('./paths');
15
13
  const { resolveApplicationConfigPath } = require('./app-config-resolver');
16
14
  const { loadConfigFile, writeConfigFile } = require('./config-format');
17
- const composeGenerator = require('./compose-generator');
18
15
  const containerHelpers = require('./app-run-containers');
19
-
20
- const execAsync = promisify(exec);
16
+ const { resolveDockerImageRef } = require('./resolve-docker-image-ref');
17
+ const { execWithDockerEnv } = require('./docker-exec');
21
18
 
22
19
  const OCI_VERSION_LABEL = 'org.opencontainers.image.version';
23
20
  const SEMVER_REGEX = /^v?(\d+\.\d+\.\d+)(?:-[-.\w]+)?(?:\+[-.\w]+)?$/i;
@@ -38,7 +35,7 @@ async function getVersionFromImage(imageName, imageTag) {
38
35
 
39
36
  try {
40
37
  const labelFormat = `{{index .Config.Labels "${OCI_VERSION_LABEL}"}}`;
41
- const { stdout } = await execAsync(
38
+ const { stdout } = await execWithDockerEnv(
42
39
  `docker inspect --format '${labelFormat}' "${fullImage}" 2>/dev/null || true`,
43
40
  { timeout: 10000 }
44
41
  );
@@ -139,8 +136,7 @@ async function resolveVersionForApp(appName, variables, options = {}) {
139
136
  return { version, fromImage: false, updated: false };
140
137
  }
141
138
 
142
- const imageName = composeGenerator.getImageName(variables, appName);
143
- const imageTag = variables?.image?.tag || 'latest';
139
+ const { imageName, imageTag } = resolveDockerImageRef(appName, variables, {});
144
140
  const imageExists = await containerHelpers.checkImageExists(imageName, imageTag);
145
141
 
146
142
  if (!imageExists) {
@@ -9,11 +9,8 @@
9
9
  * @version 2.0.0
10
10
  */
11
11
 
12
- const { exec } = require('child_process');
13
- const { promisify } = require('util');
14
12
  const config = require('../core/config');
15
-
16
- const execAsync = promisify(exec);
13
+ const { execWithDockerEnv } = require('./docker-exec');
17
14
 
18
15
  /**
19
16
  * Finds container by name pattern
@@ -42,7 +39,7 @@ async function findContainer(serviceName, devId = null, options = {}) {
42
39
  ];
43
40
 
44
41
  for (const pattern of patternsToTry) {
45
- const { stdout } = await execAsync(`docker ps --filter "name=${pattern}" --format "{{.Names}}"`);
42
+ const { stdout } = await execWithDockerEnv(`docker ps --filter "name=${pattern}" --format "{{.Names}}"`);
46
43
  const names = stdout
47
44
  .split('\n')
48
45
  .map(s => s.trim())
@@ -75,7 +72,7 @@ async function checkServiceWithHealthCheck(serviceName, devId = null, options =
75
72
  if (!containerName) {
76
73
  return 'unknown';
77
74
  }
78
- const { stdout } = await execAsync(`docker inspect --format='{{.State.Health.Status}}' ${containerName}`);
75
+ const { stdout } = await execWithDockerEnv(`docker inspect --format='{{.State.Health.Status}}' ${containerName}`);
79
76
  const status = stdout.trim().replace(/['"]/g, '');
80
77
  // Accept both 'healthy' and 'starting' as healthy (starting means it's initializing)
81
78
  return (status === 'healthy' || status === 'starting') ? 'healthy' : status;
@@ -99,7 +96,7 @@ async function checkServiceWithoutHealthCheck(serviceName, devId = null, options
99
96
  if (!containerName) {
100
97
  return 'unknown';
101
98
  }
102
- const { stdout } = await execAsync(`docker inspect --format='{{.State.Status}}' ${containerName}`);
99
+ const { stdout } = await execWithDockerEnv(`docker inspect --format='{{.State.Status}}' ${containerName}`);
103
100
  const status = stdout.trim().replace(/['"]/g, '');
104
101
  // Treat 'running' or 'healthy' as 'healthy' for services without health checks
105
102
  return (status === 'running' || status === 'healthy') ? 'healthy' : 'unhealthy';
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Canonical env defaults for docker/local interpolation (formerly lib/schema/env-config.yaml).
3
+ *
4
+ * @fileoverview Infra-only vs app-service layers merged for backward compatibility.
5
+ * True infra: DB, Redis, runtime. App rows (MISO_*, DATAPLANE_*, …) are a separate object
6
+ * so policy and future YAML-only sourcing stay clear — see APP_SERVICE_ENV_DEFAULTS.
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const { buildAppServiceEnvOverlay } = require('./app-service-env-from-builder');
14
+
15
+ /**
16
+ * @param {string|null|undefined} projectRoot
17
+ * @returns {string}
18
+ */
19
+ function resolveProjectRootForEnvDefaults(projectRoot) {
20
+ if (projectRoot !== undefined && projectRoot !== null) {
21
+ return projectRoot;
22
+ }
23
+ try {
24
+ const { getProjectRoot } = require('./paths');
25
+ if (typeof getProjectRoot === 'function') {
26
+ return getProjectRoot();
27
+ }
28
+ } catch {
29
+ // ignore
30
+ }
31
+ return process.cwd();
32
+ }
33
+
34
+ /** Docker profile: databases, cache, process env only (no application services). */
35
+ const INFRA_ENV_DEFAULTS_DOCKER = Object.freeze({
36
+ DB_HOST: 'postgres',
37
+ DB_PORT: 5432,
38
+ REDIS_HOST: 'redis',
39
+ REDIS_PORT: 6379,
40
+ NODE_ENV: 'production',
41
+ PYTHONUNBUFFERED: 1,
42
+ PYTHONDONTWRITEBYTECODE: 1,
43
+ PYTHONIOENCODING: 'utf-8'
44
+ });
45
+
46
+ /** Local profile: same infra shape as docker with localhost hosts. */
47
+ const INFRA_ENV_DEFAULTS_LOCAL = Object.freeze({
48
+ DB_HOST: 'localhost',
49
+ DB_PORT: 5432,
50
+ REDIS_HOST: 'localhost',
51
+ REDIS_PORT: 6379,
52
+ NODE_ENV: 'development',
53
+ PYTHONUNBUFFERED: 1,
54
+ PYTHONDONTWRITEBYTECODE: 1,
55
+ PYTHONIOENCODING: 'utf-8'
56
+ });
57
+
58
+ /**
59
+ * Default *_HOST / *_PORT / *_PUBLIC_PORT for known stack apps.
60
+ * Prefer application.yaml + url:// for URLs; these support ${VAR} interpolation until templates migrate.
61
+ */
62
+ const APP_SERVICE_ENV_DEFAULTS_DOCKER = Object.freeze({
63
+ MISO_HOST: 'miso-controller',
64
+ MISO_PORT: 3000,
65
+ MISO_PUBLIC_PORT: 3000,
66
+ KEYCLOAK_HOST: 'keycloak',
67
+ KEYCLOAK_PORT: 8080,
68
+ KEYCLOAK_PUBLIC_PORT: 8082,
69
+ MORI_HOST: 'mori-controller',
70
+ MORI_PORT: 3004,
71
+ OPENWEBUI_HOST: 'openwebui',
72
+ OPENWEBUI_PORT: 3003,
73
+ FLOWISE_HOST: 'flowise',
74
+ FLOWISE_PORT: 3002,
75
+ DATAPLANE_HOST: 'dataplane',
76
+ DATAPLANE_PORT: 3001,
77
+ DATAPLANE_PUBLIC_PORT: 3001
78
+ });
79
+
80
+ const APP_SERVICE_ENV_DEFAULTS_LOCAL = Object.freeze({
81
+ MISO_HOST: 'localhost',
82
+ MISO_PORT: 3010,
83
+ MISO_PUBLIC_PORT: 3010,
84
+ KEYCLOAK_HOST: 'localhost',
85
+ KEYCLOAK_PORT: 8082,
86
+ KEYCLOAK_PUBLIC_PORT: 8082,
87
+ MORI_HOST: 'localhost',
88
+ MORI_PORT: 3014,
89
+ OPENWEBUI_HOST: 'localhost',
90
+ OPENWEBUI_PORT: 3013,
91
+ FLOWISE_HOST: 'localhost',
92
+ FLOWISE_PORT: 3012,
93
+ DATAPLANE_HOST: 'localhost',
94
+ DATAPLANE_PORT: 3011,
95
+ DATAPLANE_PUBLIC_PORT: 3011
96
+ });
97
+
98
+ /**
99
+ * @param {Record<string, unknown>} a
100
+ * @param {Record<string, unknown>} b
101
+ * @returns {Record<string, unknown>}
102
+ */
103
+ function shallowMergeEnv(a, b) {
104
+ return { ...a, ...b };
105
+ }
106
+
107
+ /**
108
+ * Merged env-config shape (infra + static app fallbacks + per-app builder manifest overlay).
109
+ * @param {string|null|undefined} [projectRoot] - When omitted, uses paths getProjectRoot or process.cwd().
110
+ * @returns {Object}
111
+ */
112
+ function buildMergedDefaultEnvConfig(projectRoot) {
113
+ const root = resolveProjectRootForEnvDefaults(projectRoot);
114
+ const overlay = buildAppServiceEnvOverlay(root);
115
+ return {
116
+ environments: {
117
+ docker: shallowMergeEnv(
118
+ shallowMergeEnv(
119
+ { ...INFRA_ENV_DEFAULTS_DOCKER },
120
+ { ...APP_SERVICE_ENV_DEFAULTS_DOCKER }
121
+ ),
122
+ overlay.docker
123
+ ),
124
+ local: shallowMergeEnv(
125
+ shallowMergeEnv(
126
+ { ...INFRA_ENV_DEFAULTS_LOCAL },
127
+ { ...APP_SERVICE_ENV_DEFAULTS_LOCAL }
128
+ ),
129
+ overlay.local
130
+ )
131
+ }
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Deep clone of default env-config (docker/local hosts and ports).
137
+ * @param {string|null|undefined} [projectRoot] - Optional workspace root for builder/ scan.
138
+ * @returns {Object}
139
+ */
140
+ function getDefaultEnvConfig(projectRoot) {
141
+ return JSON.parse(JSON.stringify(buildMergedDefaultEnvConfig(projectRoot)));
142
+ }
143
+
144
+ /**
145
+ * Infra-only defaults for declarative url:// resolution and registry fallbacks.
146
+ * Application manifests should set `port` / `frontDoorRouting`; these apply when omitted.
147
+ */
148
+ const DECLARATIVE_URL_INFRA_DEFAULTS = Object.freeze({
149
+ manifestPortFallback: 3000,
150
+ frontDoorPatternWhenUnspecified: '/',
151
+ inactiveVdirPublicEnvReplacement: '/'
152
+ });
153
+
154
+ module.exports = {
155
+ getDefaultEnvConfig,
156
+ buildMergedDefaultEnvConfig,
157
+ INFRA_ENV_DEFAULTS_DOCKER,
158
+ INFRA_ENV_DEFAULTS_LOCAL,
159
+ APP_SERVICE_ENV_DEFAULTS_DOCKER,
160
+ APP_SERVICE_ENV_DEFAULTS_LOCAL,
161
+ DECLARATIVE_URL_INFRA_DEFAULTS
162
+ };
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Shared output for `aifabrix status`: title + configuration block.
3
+ * TLS/SSL (`infraTlsSslCell`) and `devNN` title match `dev-show-display.js`; environment, Traefik,
4
+ * and scoped-resources lines use uppercase ON/OFF-style labels for status output.
5
+ *
6
+ * @fileoverview Infra status CLI display helpers
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const config = require('../core/config');
14
+ const logger = require('./logger');
15
+
16
+ /** En dash for unset / empty values (same as dev-show-display). */
17
+ const EM = '\u2013';
18
+ const LABEL_W = 18;
19
+
20
+ /**
21
+ * @param {unknown} v
22
+ * @returns {boolean}
23
+ */
24
+ function isUnset(v) {
25
+ return v === null || v === undefined || v === '';
26
+ }
27
+
28
+ /**
29
+ * @param {unknown} v
30
+ * @returns {string}
31
+ */
32
+ function cell(v) {
33
+ return isUnset(v) ? EM : String(v);
34
+ }
35
+
36
+ /**
37
+ * @param {string} label
38
+ * @param {unknown} value
39
+ */
40
+ function logPaddedFieldRow(label, value) {
41
+ logger.log(` ${label.padEnd(LABEL_W)} ${cell(value)}`);
42
+ }
43
+
44
+ /**
45
+ * @param {string|null|undefined} remoteUrl
46
+ * @returns {string}
47
+ */
48
+ function hostFromRemoteUrl(remoteUrl) {
49
+ if (!remoteUrl || typeof remoteUrl !== 'string') {
50
+ return '';
51
+ }
52
+ try {
53
+ const u = new URL(remoteUrl.trim());
54
+ return u.hostname || '';
55
+ } catch {
56
+ return '';
57
+ }
58
+ }
59
+
60
+ /**
61
+ * @param {string} devIdStr
62
+ * @returns {string} e.g. dev02
63
+ */
64
+ function devProfileHandle(devIdStr) {
65
+ const s = String(devIdStr);
66
+ if (/^[0-9]+$/.test(s)) {
67
+ return `dev${s.padStart(2, '0')}`;
68
+ }
69
+ return `dev${s}`;
70
+ }
71
+
72
+ /**
73
+ * Infra / compose TLS mode from ~/.aifabrix `tlsEnabled` (not Docker TLS verify).
74
+ * @param {boolean} tlsEnabled
75
+ * @returns {string}
76
+ */
77
+ function infraTlsSslCell(tlsEnabled) {
78
+ return tlsEnabled ? 'ON 🔒' : 'OFF 🕐';
79
+ }
80
+
81
+ /**
82
+ * Traefik proxy row (`traefik: true` in config). Icons only (no ANSI color).
83
+ * @param {boolean} traefikEnabled
84
+ * @returns {string}
85
+ */
86
+ function statusTraefikProxyCell(traefikEnabled) {
87
+ return traefikEnabled ? 'ON 🟢' : 'OFF 🟡';
88
+ }
89
+
90
+ /**
91
+ * Environment for status (uppercase).
92
+ * @param {unknown} environment
93
+ * @returns {string}
94
+ */
95
+ function formatStatusEnvironment(environment) {
96
+ if (isUnset(environment)) {
97
+ return EM;
98
+ }
99
+ return String(environment).trim().toUpperCase();
100
+ }
101
+
102
+ /**
103
+ * Scoped resources for status: ON vs OFF (DEFAULT).
104
+ * @param {boolean} useScoped
105
+ * @returns {string}
106
+ */
107
+ function statusScopedResourcesCell(useScoped) {
108
+ return useScoped ? 'ON' : 'OFF (DEFAULT)';
109
+ }
110
+
111
+ /**
112
+ * Single-line title for `aifabrix status` (same dev profile + remote host pattern as dev show).
113
+ * @param {string} devIdStr
114
+ * @param {string|null|undefined} remoteServer
115
+ * @returns {string}
116
+ */
117
+ function formatInfraStatusTitleLine(devIdStr, remoteServer) {
118
+ const who = devProfileHandle(devIdStr);
119
+ const host = hostFromRemoteUrl(remoteServer);
120
+ if (host) {
121
+ return `📊 Infrastructure Status (${who} @ ${host})`;
122
+ }
123
+ return `📊 Infrastructure Status (${who})`;
124
+ }
125
+
126
+ /**
127
+ * @param {{ remoteServer: string|null|undefined, tlsEnabled: boolean, environment: unknown, useScoped: boolean, traefikEnabled: boolean }} p
128
+ */
129
+ function logInfraStatusConfigurationSummary(p) {
130
+ logger.log('⚙️ Configuration');
131
+ logPaddedFieldRow('Server', p.remoteServer);
132
+ logPaddedFieldRow('TLS/SSL', infraTlsSslCell(p.tlsEnabled));
133
+ logPaddedFieldRow('Traefik proxy', statusTraefikProxyCell(p.traefikEnabled));
134
+ logPaddedFieldRow('Environment', formatStatusEnvironment(p.environment));
135
+ logPaddedFieldRow('Scoped resources', statusScopedResourcesCell(p.useScoped));
136
+ logger.log('');
137
+ }
138
+
139
+ /**
140
+ * @returns {Promise<{ devIdStr: string, remoteServer: string|null|undefined, tlsEnabled: boolean, environment: unknown, useScoped: boolean, traefikEnabled: boolean }>}
141
+ */
142
+ async function loadInfraStatusSummary() {
143
+ const [rawDevId, environment, tlsEnabled, remoteServer, useScoped, traefikEnabled] = await Promise.all([
144
+ config.getDeveloperId(),
145
+ config.getCurrentEnvironment(),
146
+ config.getTlsEnabled(),
147
+ config.getRemoteServer(),
148
+ config.getUseEnvironmentScopedResources(),
149
+ config.getTraefikEnabled()
150
+ ]);
151
+ const devIdStr = rawDevId === null || rawDevId === undefined ? '0' : String(rawDevId);
152
+ return {
153
+ devIdStr,
154
+ environment,
155
+ tlsEnabled,
156
+ remoteServer,
157
+ useScoped,
158
+ traefikEnabled
159
+ };
160
+ }
161
+
162
+ module.exports = {
163
+ loadInfraStatusSummary,
164
+ formatInfraStatusTitleLine,
165
+ logInfraStatusConfigurationSummary,
166
+ logPaddedFieldRow
167
+ };