@aifabrix/builder 2.45.5 → 2.47.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 (355) hide show
  1. package/.cursor/rules/cli-layout.mdc +2 -0
  2. package/.cursor/rules/cli-product-neutral.mdc +43 -0
  3. package/.cursor/rules/project-rules.mdc +1 -0
  4. package/.npmrc.token +1 -0
  5. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  7. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  8. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  9. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  10. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  13. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  14. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  15. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  16. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  17. package/.temp/validation/summary.txt +5 -0
  18. package/README.md +43 -17
  19. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  20. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  21. package/jest.config.default.js +2 -1
  22. package/jest.config.isolated.js +2 -1
  23. package/jest.global-live-fabrix-hooks.js +36 -0
  24. package/jest.isolated-projects.js +146 -0
  25. package/jest.projects.js +65 -136
  26. package/lib/api/api-response-errors.js +124 -0
  27. package/lib/api/dataplane-health.api.js +19 -7
  28. package/lib/api/governance-scenario-pack.api.js +159 -0
  29. package/lib/api/index.js +43 -3
  30. package/lib/api/lifecycle.api.js +98 -0
  31. package/lib/api/pipeline.api.js +3 -1
  32. package/lib/api/records-bulk.api.js +14 -1
  33. package/lib/api/resource-types.api.js +107 -0
  34. package/lib/api/runtime-configuration.api.js +72 -0
  35. package/lib/api/types/resource-types.types.js +45 -0
  36. package/lib/api/user-groups.api.js +9 -9
  37. package/lib/api/users.api.js +8 -8
  38. package/lib/api/validation-runner.js +3 -1
  39. package/lib/api/wizard.api.js +10 -2
  40. package/lib/app/deploy-config.js +4 -1
  41. package/lib/app/down.js +78 -27
  42. package/lib/app/index.js +2 -2
  43. package/lib/app/push.js +4 -5
  44. package/lib/app/register.js +7 -4
  45. package/lib/app/run-env-compose.js +84 -30
  46. package/lib/app/run-resolve-image.js +2 -1
  47. package/lib/app/run-stop-containers.js +113 -0
  48. package/lib/app/run.js +1 -31
  49. package/lib/app/show-display.js +35 -0
  50. package/lib/app/show.js +32 -1
  51. package/lib/build/index.js +22 -5
  52. package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
  53. package/lib/certification/sync-system-certification.js +13 -6
  54. package/lib/channels/add-channel.js +348 -0
  55. package/lib/channels/channel-artifact-generator.js +141 -0
  56. package/lib/channels/channel-audit.js +101 -0
  57. package/lib/channels/channel-auth-context.js +37 -0
  58. package/lib/channels/channel-credential.js +200 -0
  59. package/lib/channels/channel-display-name.js +64 -0
  60. package/lib/channels/channel-external-system.js +106 -0
  61. package/lib/channels/channel-integration-generator.js +274 -0
  62. package/lib/channels/channel-probe.js +89 -0
  63. package/lib/channels/channel-runtime-resolver.js +152 -0
  64. package/lib/channels/channel-status-display.js +141 -0
  65. package/lib/channels/channel-targets.js +115 -0
  66. package/lib/cli/index.js +20 -4
  67. package/lib/cli/infra-guided-auth.js +109 -0
  68. package/lib/cli/infra-guided.js +84 -81
  69. package/lib/cli/setup-app.help.js +0 -31
  70. package/lib/cli/setup-app.js +82 -5
  71. package/lib/cli/setup-app.test-commands.js +5 -57
  72. package/lib/cli/setup-auth.js +25 -0
  73. package/lib/cli/setup-certification-commands.js +147 -0
  74. package/lib/cli/setup-certification.help.js +141 -0
  75. package/lib/cli/setup-environment.js +19 -2
  76. package/lib/cli/setup-external-system-help.js +62 -0
  77. package/lib/cli/setup-external-system.js +25 -3
  78. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  79. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  80. package/lib/cli/setup-infra.js +9 -1
  81. package/lib/cli/setup-platform.js +6 -1
  82. package/lib/cli/setup-secrets.js +33 -1
  83. package/lib/cli/setup-utility-help-after.js +25 -2
  84. package/lib/cli/setup-utility-repair.js +198 -0
  85. package/lib/cli/setup-utility-resolve.js +44 -5
  86. package/lib/cli/setup-utility-validate.js +197 -0
  87. package/lib/cli/setup-utility.js +4 -239
  88. package/lib/commands/app-logs.js +54 -26
  89. package/lib/commands/app.js +41 -0
  90. package/lib/commands/auth-status-client-tokens.js +214 -0
  91. package/lib/commands/auth-status-dataplane-version.js +142 -68
  92. package/lib/commands/auth-status.js +43 -54
  93. package/lib/commands/channel.js +58 -0
  94. package/lib/commands/datasource-test-trust-cli.js +27 -23
  95. package/lib/commands/datasource-unified-test-cli.js +6 -3
  96. package/lib/commands/datasource-unified-test-cli.options.js +5 -1
  97. package/lib/commands/datasource.js +8 -8
  98. package/lib/commands/dev-show-display.js +8 -1
  99. package/lib/commands/governance-cli-shared.js +94 -0
  100. package/lib/commands/governance-command-action.js +132 -0
  101. package/lib/commands/governance-download-external.js +34 -0
  102. package/lib/commands/governance-generate-external.js +71 -0
  103. package/lib/commands/governance-upload-external.js +36 -0
  104. package/lib/commands/governance-verify-external.js +64 -0
  105. package/lib/commands/governance.js +144 -0
  106. package/lib/commands/identity-apply-sync.js +49 -8
  107. package/lib/commands/identity-group-cli.js +66 -26
  108. package/lib/commands/identity-membership-role-cache.js +58 -37
  109. package/lib/commands/identity-shared.js +22 -1
  110. package/lib/commands/identity-user-cli.js +80 -34
  111. package/lib/commands/lifecycle-command-action.js +123 -0
  112. package/lib/commands/lifecycle-external.js +268 -0
  113. package/lib/commands/login.js +11 -2
  114. package/lib/commands/repair-auth-apply.js +153 -0
  115. package/lib/commands/repair-auth-config.js +8 -13
  116. package/lib/commands/repair-changed-files.js +41 -0
  117. package/lib/commands/repair-datasource-com-v2.js +15 -0
  118. package/lib/commands/repair-datasource-keys.js +4 -1
  119. package/lib/commands/repair-datasource-run.js +59 -0
  120. package/lib/commands/repair-datasource-service-contract.js +206 -0
  121. package/lib/commands/repair-datasource-test-payload.js +259 -0
  122. package/lib/commands/repair-datasource.js +49 -64
  123. package/lib/commands/repair-env-template.js +76 -25
  124. package/lib/commands/repair-external-run.js +216 -0
  125. package/lib/commands/repair-openapi-sync.js +49 -4
  126. package/lib/commands/repair-persist.js +4 -0
  127. package/lib/commands/repair-rbac.js +52 -1
  128. package/lib/commands/repair-steps.js +160 -0
  129. package/lib/commands/repair-system-alignment.js +7 -4
  130. package/lib/commands/repair.js +29 -296
  131. package/lib/commands/resource-type.js +371 -0
  132. package/lib/commands/secrets-get.js +184 -0
  133. package/lib/commands/secrets-remove-all.js +3 -1
  134. package/lib/commands/setup-image-refresh.js +146 -0
  135. package/lib/commands/setup-modes.js +158 -162
  136. package/lib/commands/setup-platform-auth.js +76 -0
  137. package/lib/commands/setup-prompts-admin.js +244 -0
  138. package/lib/commands/setup-prompts-platform-mode.js +70 -0
  139. package/lib/commands/setup-prompts-validators.js +36 -0
  140. package/lib/commands/setup-prompts.js +24 -76
  141. package/lib/commands/setup-run-context.js +45 -0
  142. package/lib/commands/setup-spinners.js +39 -0
  143. package/lib/commands/setup.js +27 -5
  144. package/lib/commands/teardown.js +39 -0
  145. package/lib/commands/test-e2e-external.js +24 -8
  146. package/lib/commands/test-governance-external.js +3 -24
  147. package/lib/commands/test-trust-external.js +48 -100
  148. package/lib/commands/up-common.js +57 -13
  149. package/lib/commands/up-dataplane.js +56 -15
  150. package/lib/commands/upload-publish-context.js +75 -0
  151. package/lib/commands/upload-sidecar-followups.js +89 -0
  152. package/lib/commands/upload.js +113 -110
  153. package/lib/commands/verify-governance-combined-external.js +61 -0
  154. package/lib/commands/verify-governance-command-action.js +64 -0
  155. package/lib/commands/verify-governance-db.js +102 -0
  156. package/lib/commands/verify-governance-external.js +77 -0
  157. package/lib/commands/verify-governance-result-builders.js +71 -0
  158. package/lib/commands/verify-operations-command-action.js +68 -0
  159. package/lib/commands/verify-operations-external.js +268 -0
  160. package/lib/commands/verify-operations-persist.js +75 -0
  161. package/lib/commands/verify-operations-step-plan.js +68 -0
  162. package/lib/commands/verify-operations-steps.js +111 -0
  163. package/lib/commands/verify-trust-command-action.js +73 -0
  164. package/lib/commands/verify-trust-external.js +59 -0
  165. package/lib/commands/wizard-config-normalizer.js +43 -23
  166. package/lib/commands/wizard-core.js +2 -0
  167. package/lib/core/admin-password-generator.js +38 -0
  168. package/lib/core/admin-secrets-bundle.js +111 -0
  169. package/lib/core/admin-secrets-env-overlay.js +62 -0
  170. package/lib/core/admin-secrets.js +32 -4
  171. package/lib/core/config-attach-extensions.js +3 -0
  172. package/lib/core/config-platform-controller.js +44 -0
  173. package/lib/core/config-setup-installation-profile.js +42 -0
  174. package/lib/core/config-setup-platform-mode.js +46 -0
  175. package/lib/core/config.js +46 -36
  176. package/lib/core/secrets-admin-env.js +6 -2
  177. package/lib/core/secrets-ensure.js +4 -0
  178. package/lib/core/secrets-env-content.js +54 -48
  179. package/lib/core/secrets-env-write.js +10 -1
  180. package/lib/core/secrets-infra-placeholder-sync.js +2 -0
  181. package/lib/core/setup-installation-profile.js +43 -0
  182. package/lib/core/setup-platform-mode.js +90 -0
  183. package/lib/datasource/agent-trust-debug-log.js +1 -1
  184. package/lib/datasource/agent-trust-run.js +5 -3
  185. package/lib/datasource/audit-evidence-extract-debug.js +30 -18
  186. package/lib/datasource/audit-evidence-extract.js +7 -4
  187. package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
  188. package/lib/datasource/bulk-loader-run-batches.js +15 -3
  189. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  190. package/lib/datasource/capability/capability-manifest.js +254 -0
  191. package/lib/datasource/capability/capability-resolve.js +3 -4
  192. package/lib/datasource/capability/copy-operations.js +5 -3
  193. package/lib/datasource/capability/remove-operations.js +2 -1
  194. package/lib/datasource/capability/run-capability-copy.js +32 -4
  195. package/lib/datasource/capability/run-capability-diff.js +3 -3
  196. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  197. package/lib/datasource/capability/run-capability-edit.js +4 -7
  198. package/lib/datasource/capability/run-capability-relate.js +2 -4
  199. package/lib/datasource/capability/run-capability-remove.js +2 -4
  200. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  201. package/lib/datasource/datasource-identifier.js +2 -1
  202. package/lib/datasource/datasource-load-export-context.js +3 -0
  203. package/lib/datasource/datasource-validate-display.js +1 -1
  204. package/lib/datasource/field-reference-validator.js +24 -3
  205. package/lib/datasource/log-cleaner.js +2 -2
  206. package/lib/datasource/log-viewer.js +8 -8
  207. package/lib/datasource/orphan-profiles-validator.js +98 -0
  208. package/lib/datasource/test-e2e.js +4 -1
  209. package/lib/datasource/unified-validation-run-body.js +3 -1
  210. package/lib/datasource/unified-validation-run.js +5 -2
  211. package/lib/datasource/validate.js +41 -23
  212. package/lib/deployment/environment-poll-status.js +138 -0
  213. package/lib/deployment/environment.js +42 -34
  214. package/lib/external-system/download-helpers.js +87 -0
  215. package/lib/external-system/download.js +78 -138
  216. package/lib/external-system/generator.js +1 -55
  217. package/lib/external-system/integration-auth-context.js +45 -0
  218. package/lib/external-system/test-auth.js +12 -5
  219. package/lib/external-system/test.js +16 -21
  220. package/lib/generator/builders.js +11 -1
  221. package/lib/generator/external-controller-manifest.js +18 -0
  222. package/lib/generator/split.js +3 -1
  223. package/lib/generator/wizard.js +1 -28
  224. package/lib/governance/governance-cli-errors.js +96 -0
  225. package/lib/governance/governance-download-display.js +38 -0
  226. package/lib/governance/governance-run-display.js +215 -25
  227. package/lib/identity/fk-reference.js +65 -0
  228. package/lib/identity/identity-apply-core.js +13 -58
  229. package/lib/identity/identity-apply-phases.js +5 -1
  230. package/lib/identity/identity-display.js +243 -0
  231. package/lib/identity/identity-response-helpers.js +52 -0
  232. package/lib/identity/identity-sync-errors.js +62 -0
  233. package/lib/identity/resolve-user-id.js +129 -0
  234. package/lib/identity/username-default.js +31 -0
  235. package/lib/infrastructure/admin-secrets-apply.js +84 -0
  236. package/lib/infrastructure/admin-secrets-ensure.js +130 -0
  237. package/lib/infrastructure/admin-secrets-resolve.js +95 -0
  238. package/lib/infrastructure/admin-secrets-sync.js +91 -0
  239. package/lib/infrastructure/helpers.js +23 -129
  240. package/lib/infrastructure/index.js +5 -6
  241. package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
  242. package/lib/lifecycle/certification-progress.js +231 -0
  243. package/lib/lifecycle/dataplane-ui-url.js +145 -0
  244. package/lib/lifecycle/exit-codes.js +24 -0
  245. package/lib/lifecycle/json-envelope.js +25 -0
  246. package/lib/lifecycle/lifecycle-governance-options.js +51 -0
  247. package/lib/lifecycle/logger-quiet.js +29 -0
  248. package/lib/lifecycle/open-api-published.js +59 -0
  249. package/lib/lifecycle/operations-readiness-metrics.js +101 -0
  250. package/lib/lifecycle/product-model.js +196 -0
  251. package/lib/lifecycle/report-display-trust.js +70 -0
  252. package/lib/lifecycle/report-display.js +472 -0
  253. package/lib/lifecycle/scenario-upload.js +191 -0
  254. package/lib/lifecycle/verify-step-progress.js +87 -0
  255. package/lib/parameters/infra-kv-discovery.js +27 -2
  256. package/lib/protection/auth-context.js +10 -4
  257. package/lib/resolvers/datasource-resolver.js +2 -3
  258. package/lib/resolvers/resource-type-file.js +159 -0
  259. package/lib/schema/application-schema.json +17 -1
  260. package/lib/schema/datasource-test-run.schema.json +89 -6
  261. package/lib/schema/deployment-rules.yaml +2 -1
  262. package/lib/schema/external-datasource.schema.json +2947 -2619
  263. package/lib/schema/external-system.schema.json +1418 -761
  264. package/lib/schema/infra.parameter.yaml +45 -0
  265. package/lib/schema/type/credential-field-registry.json +103 -97
  266. package/lib/schema/type/document-storage.json +9 -5
  267. package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
  268. package/lib/utils/api.js +55 -31
  269. package/lib/utils/app-relative-path-env.js +112 -0
  270. package/lib/utils/app-run-containers.js +25 -2
  271. package/lib/utils/authentication-shape.js +224 -0
  272. package/lib/utils/build-copy.js +36 -5
  273. package/lib/utils/build-resolve-image.js +66 -12
  274. package/lib/utils/celery-companion-apps.js +53 -0
  275. package/lib/utils/cli-secrets-error-format.js +83 -21
  276. package/lib/utils/cli-utils.js +20 -4
  277. package/lib/utils/compose-celery-worker.js +23 -0
  278. package/lib/utils/compose-generate-docker-compose.js +13 -1
  279. package/lib/utils/compose-traefik-ingress-base.js +16 -1
  280. package/lib/utils/config-secrets-path.js +46 -0
  281. package/lib/utils/configuration-env-resolver.js +36 -0
  282. package/lib/utils/controller-url.js +88 -1
  283. package/lib/utils/credential-secrets-env-kv.js +216 -0
  284. package/lib/utils/credential-secrets-env.js +175 -257
  285. package/lib/utils/dataplane-resolver.js +18 -2
  286. package/lib/utils/declarative-url-ports.js +27 -4
  287. package/lib/utils/deployment-auth-mode.js +41 -0
  288. package/lib/utils/developer-volume-id.js +33 -0
  289. package/lib/utils/docker-build.js +1 -4
  290. package/lib/utils/env-copy.js +14 -9
  291. package/lib/utils/env-kv-orphan-lines.js +43 -0
  292. package/lib/utils/error-formatters/network-errors.js +13 -4
  293. package/lib/utils/external-env-template.js +11 -3
  294. package/lib/utils/external-system-readiness-core.js +10 -0
  295. package/lib/utils/external-system-readiness-display.js +80 -23
  296. package/lib/utils/external-system-validators.js +8 -2
  297. package/lib/utils/health-check.js +4 -1
  298. package/lib/utils/help-builder.js +13 -8
  299. package/lib/utils/keycloak-realm-ready.js +101 -0
  300. package/lib/utils/load-cip-capacity-display-config.js +61 -15
  301. package/lib/utils/manifest-source-emit.js +2 -0
  302. package/lib/utils/paths.js +25 -0
  303. package/lib/utils/pipeline-upload-auth-retry.js +82 -0
  304. package/lib/utils/platform-controller-url.js +157 -0
  305. package/lib/utils/platform-kv-ref.js +57 -0
  306. package/lib/utils/postgres-platform-bootstrap.js +199 -0
  307. package/lib/utils/postgres-wipe.js +9 -2
  308. package/lib/utils/resolve-env-mode.js +47 -0
  309. package/lib/utils/runtime-configuration-download.js +189 -0
  310. package/lib/utils/runtime-configuration-values.js +119 -0
  311. package/lib/utils/schema-loader.js +19 -4
  312. package/lib/utils/secrets-generator.js +31 -25
  313. package/lib/utils/secrets-missing-error.js +164 -0
  314. package/lib/utils/service-url-label.js +81 -0
  315. package/lib/utils/setup-quiet-output.js +25 -0
  316. package/lib/utils/template-helpers.js +15 -0
  317. package/lib/utils/token-manager-client-credentials.js +201 -0
  318. package/lib/utils/token-manager-messages.js +8 -0
  319. package/lib/utils/token-manager-refresh.js +12 -2
  320. package/lib/utils/token-manager.js +86 -105
  321. package/lib/utils/upload-pipeline-ui.js +12 -5
  322. package/lib/utils/upload-sync-options.js +42 -0
  323. package/lib/utils/url-declarative-public-base.js +21 -3
  324. package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
  325. package/lib/utils/validation-poll-ui.js +8 -0
  326. package/lib/utils/validation-run-stage.js +45 -0
  327. package/lib/utils/worker-sidecars.js +142 -0
  328. package/lib/validation/external-manifest-validator.js +2 -0
  329. package/lib/validation/runtime-configuration-rules.js +98 -0
  330. package/lib/validation/validate-display-dimensions.js +154 -0
  331. package/lib/validation/validate-display-sections.js +276 -0
  332. package/lib/validation/validate-display-steps.js +67 -0
  333. package/lib/validation/validate-display.js +24 -402
  334. package/package.json +1 -1
  335. package/templates/applications/dataplane/application.yaml +7 -1
  336. package/templates/applications/dataplane/env.template +50 -14
  337. package/templates/applications/dataplane/rbac.yaml +108 -1
  338. package/templates/applications/miso-controller/application.yaml +2 -2
  339. package/templates/applications/miso-controller/env.template +53 -122
  340. package/templates/applications/miso-controller/rbac.yaml +28 -13
  341. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  342. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  343. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  344. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  345. package/templates/channels/slack/install.template.md.hbs +49 -0
  346. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  347. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  348. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  349. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  350. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  351. package/templates/external-system/README.md.hbs +74 -17
  352. package/templates/python/docker-compose.hbs +75 -18
  353. package/templates/typescript/docker-compose.hbs +70 -17
  354. package/lib/commands/test-governance-command-action.js +0 -55
  355. package/lib/commands/test-trust-command-action.js +0 -118
package/lib/app/down.js CHANGED
@@ -18,6 +18,13 @@ const logger = require('../utils/logger');
18
18
  const config = require('../core/config');
19
19
  const helpers = require('./run-helpers');
20
20
  const { validateAppName } = require('./push');
21
+ const buildCopy = require('../utils/build-copy');
22
+ const {
23
+ getCeleryCompanionAppKeys,
24
+ loadBuilderApplicationConfig
25
+ } = require('../utils/celery-companion-apps');
26
+ const fsSync = require('fs');
27
+ const path = require('path');
21
28
 
22
29
  /**
23
30
  * Executes a shell command asynchronously.
@@ -48,6 +55,8 @@ function execAsync(command, options = {}) {
48
55
  * @param {number|string} developerId - Developer ID
49
56
  * @returns {string} Docker volume name
50
57
  */
58
+ const { collectDeveloperVolumeIdSuffixes } = require('../utils/developer-volume-id');
59
+
51
60
  function getAppVolumeName(appName, developerId) {
52
61
  const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
53
62
  if (idNum === 0) {
@@ -56,6 +65,69 @@ function getAppVolumeName(appName, developerId) {
56
65
  return `aifabrix_dev${developerId}_${appName}_data`;
57
66
  }
58
67
 
68
+ /**
69
+ * Docker volume name candidates for an app (handles dev6 vs dev06 naming drift).
70
+ *
71
+ * @param {string} appName
72
+ * @param {string|number} developerId
73
+ * @returns {string[]}
74
+ */
75
+ function getAppVolumeNameCandidates(appName, developerId) {
76
+ const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
77
+ if (idNum === 0) {
78
+ return [`aifabrix_${appName}_data`, `aifabrix_dev0_${appName}_data`];
79
+ }
80
+ const suffixes = collectDeveloperVolumeIdSuffixes(developerId);
81
+ return suffixes.map((suffix) => `aifabrix_dev${suffix}_${appName}_data`);
82
+ }
83
+
84
+ function buildVolumeRemovalCandidates(appName, developerId) {
85
+ const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
86
+ const primaryName = getAppVolumeName(appName, developerId);
87
+ const legacyDev0Name = idNum === 0 ? `aifabrix_dev0_${appName}_data` : null;
88
+ return Array.from(new Set([primaryName, legacyDev0Name].filter(Boolean)));
89
+ }
90
+
91
+ async function removeVolumeIfPresent(volumeName) {
92
+ logger.log(chalk.yellow(`Removing volume ${volumeName}...`));
93
+ try {
94
+ await execAsync(`docker volume rm -f ${volumeName}`);
95
+ logger.log(formatSuccessLine(`Volume ${volumeName} removed`));
96
+ } catch (volErr) {
97
+ logger.log(chalk.gray(`Volume ${volumeName} not found or already removed`));
98
+ }
99
+ }
100
+
101
+ async function removeAppVolumes(appName, developerId) {
102
+ for (const volumeName of buildVolumeRemovalCandidates(appName, developerId)) {
103
+ await removeVolumeIfPresent(volumeName);
104
+ }
105
+ }
106
+
107
+ async function stopAppContainers(appName, companionKeys, developerId) {
108
+ const devDir = buildCopy.getDevDirectory(appName, developerId);
109
+ const composePath = path.join(devDir, 'docker-compose.yaml');
110
+
111
+ if (companionKeys.length > 0 && fsSync.existsSync(composePath)) {
112
+ try {
113
+ await execAsync(`docker compose -f "${composePath}" down`);
114
+ return;
115
+ } catch (composeErr) {
116
+ logger.log(chalk.gray(`Compose down: ${composeErr.message}; stopping containers individually`));
117
+ for (const companionKey of companionKeys) {
118
+ await helpers.stopAndRemoveContainer(companionKey, developerId, false);
119
+ }
120
+ await helpers.stopAndRemoveContainer(appName, developerId, false);
121
+ return;
122
+ }
123
+ }
124
+
125
+ await helpers.stopAndRemoveContainer(appName, developerId, false);
126
+ for (const companionKey of companionKeys) {
127
+ await helpers.stopAndRemoveContainer(companionKey, developerId, false);
128
+ }
129
+ }
130
+
59
131
  /**
60
132
  * Stops and removes a specific application's container.
61
133
  * If --volumes is passed, attempts to remove the app's named Docker volume.
@@ -75,42 +147,20 @@ function getAppVolumeName(appName, developerId) {
75
147
  */
76
148
  async function downApp(appName, options = {}) {
77
149
  try {
78
- // Input validation
79
150
  if (!appName || typeof appName !== 'string') {
80
151
  throw new Error('Application name is required and must be a string');
81
152
  }
82
153
  validateAppName(appName);
83
154
 
84
155
  const volumes = !!options.volumes;
85
-
86
- // Load developer ID
87
156
  const developerId = await config.getDeveloperId();
157
+ const appConfig = loadBuilderApplicationConfig(appName);
158
+ const companionKeys = getCeleryCompanionAppKeys(appName, appConfig);
88
159
 
89
- // Stop and remove container (idempotent handling in helper logs when not running)
90
- await helpers.stopAndRemoveContainer(appName, developerId, false);
160
+ await stopAppContainers(appName, companionKeys, developerId);
91
161
 
92
- // Optionally remove named Docker volume (ignore if it doesn't exist)
93
162
  if (volumes) {
94
- const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
95
- // Primary expected name
96
- const primaryName = getAppVolumeName(appName, developerId);
97
- // Legacy name used when devId \"0\" was treated as string in templates
98
- const legacyDev0Name = idNum === 0 ? `aifabrix_dev0_${appName}_data` : null;
99
- // Build list of candidates, unique and non-empty
100
- const candidates = Array.from(
101
- new Set([primaryName, legacyDev0Name].filter(Boolean))
102
- );
103
-
104
- for (const volumeName of candidates) {
105
- logger.log(chalk.yellow(`Removing volume ${volumeName}...`));
106
- try {
107
- await execAsync(`docker volume rm -f ${volumeName}`);
108
- logger.log(formatSuccessLine(`Volume ${volumeName} removed`));
109
- } catch (volErr) {
110
- // Swallow errors for missing volume; provide neutral message
111
- logger.log(chalk.gray(`Volume ${volumeName} not found or already removed`));
112
- }
113
- }
163
+ await removeAppVolumes(appName, developerId);
114
164
  }
115
165
  } catch (error) {
116
166
  // Provide meaningful error while avoiding sensitive info
@@ -120,6 +170,7 @@ async function downApp(appName, options = {}) {
120
170
 
121
171
  module.exports = {
122
172
  downApp,
123
- getAppVolumeName
173
+ getAppVolumeName,
174
+ getAppVolumeNameCandidates
124
175
  };
125
176
 
package/lib/app/index.js CHANGED
@@ -93,7 +93,7 @@ function validateAppNameAndSetup(appName, options) {
93
93
 
94
94
  const initialType = options.type || 'external';
95
95
  const baseDir = getBaseDirForAppType(initialType);
96
- const appPath = paths.getAppPath(appName, initialType);
96
+ const appPath = paths.getAppCreatePath(appName, initialType);
97
97
 
98
98
  return { initialType, baseDir, appPath };
99
99
  }
@@ -124,7 +124,7 @@ async function handleTemplateSetup(options) {
124
124
  */
125
125
  async function prepareFinalAppPath(appName, config, initialAppPath) {
126
126
  const finalBaseDir = getBaseDirForAppType(config.type);
127
- const finalAppPath = paths.getAppPath(appName, config.type);
127
+ const finalAppPath = paths.getAppCreatePath(appName, config.type);
128
128
 
129
129
  // If path changed, validate the new path
130
130
  if (finalAppPath !== initialAppPath) {
package/lib/app/push.js CHANGED
@@ -19,7 +19,7 @@ const {
19
19
 
20
20
  const pushUtils = require('../deployment/push');
21
21
  const logger = require('../utils/logger');
22
- const { detectAppType } = require('../utils/paths');
22
+ const paths = require('../utils/paths');
23
23
 
24
24
  /**
25
25
  * Validate application name format
@@ -92,11 +92,10 @@ function extractImageName(config, appName) {
92
92
  * @throws {Error} If configuration cannot be loaded
93
93
  */
94
94
  async function loadPushConfig(appName, options) {
95
- const { detectAppType, resolveApplicationConfigPath } = require('../utils/paths');
96
95
  const { loadConfigFile } = require('../utils/config-format');
97
- const { appPath } = await detectAppType(appName);
98
96
  try {
99
- const configPath = resolveApplicationConfigPath(appPath);
97
+ const { appPath } = await paths.detectAppType(appName);
98
+ const configPath = paths.resolveApplicationConfigPath(appPath);
100
99
  const config = loadConfigFile(configPath);
101
100
  const effective = getEffectiveConfig(config);
102
101
  const registry = options.registry || effective.image?.registry;
@@ -242,7 +241,7 @@ function displayPushResults(registry, imageName, tags) {
242
241
  */
243
242
  async function pushApp(appName, options = {}) {
244
243
  try {
245
- const { isExternal } = await detectAppType(appName);
244
+ const { isExternal } = await paths.detectAppType(appName);
246
245
  if (isExternal) {
247
246
  logExternalIntegrationPushNotice(appName);
248
247
  return;
@@ -25,6 +25,8 @@ const { callRegisterApi } = require('../utils/app-register-api');
25
25
  const { displayRegistrationResults, getEnvironmentPrefix } = require('../utils/app-register-display');
26
26
  const pathsUtil = require('../utils/paths');
27
27
  const { refreshUrlsLocalRegistryFromBuilder } = require('../utils/urls-local-registry');
28
+ const { resolveControllerUrl } = require('../utils/controller-url');
29
+ const config = require('../core/config');
28
30
 
29
31
  /**
30
32
  * Build registration data payload from app configuration
@@ -152,9 +154,6 @@ async function applyLocalhostPortAdjustment(appConfig, apiUrl, options) {
152
154
  */
153
155
  async function registerApplication(appKey, options = {}) {
154
156
  logger.log(chalk.blue('📋 Registering application...\n'));
155
-
156
- const { resolveControllerUrl } = require('../utils/controller-url');
157
- const config = require('../core/config');
158
157
  await config.ensureSecretsEncryptionKey();
159
158
  const { resolveEnvironment } = config;
160
159
 
@@ -168,7 +167,11 @@ async function registerApplication(appKey, options = {}) {
168
167
  const appConfig = await extractAppConfiguration(finalVariables, appKey, options);
169
168
  await validateAppRegistrationData(appConfig, appKey);
170
169
 
171
- const [controllerUrl, environmentKey] = await Promise.all([resolveControllerUrl(), resolveEnvironment()]);
170
+ const controllerUrl = await resolveControllerUrl();
171
+ const environmentKey =
172
+ options && options.envKey
173
+ ? String(options.envKey).trim().toLowerCase()
174
+ : await resolveEnvironment();
172
175
  const authConfig = await checkAuthentication(controllerUrl, environmentKey);
173
176
  const environment = registerApplicationSchema.environmentId(environmentKey);
174
177
 
@@ -14,9 +14,28 @@ const fs = require('fs').promises;
14
14
  const fsSync = require('fs');
15
15
  const pathsUtil = require('../utils/paths');
16
16
  const adminSecrets = require('../core/admin-secrets');
17
+ const config = require('../core/config');
17
18
  const secretsEnsure = require('../core/secrets-ensure');
18
19
  const secretsEnvWrite = require('../core/secrets-env-write');
19
20
  const { getContainerPort } = require('../utils/port-resolver');
21
+ const { applyAdminSecretsPlatformOverlay } = require('../core/admin-secrets-env-overlay');
22
+
23
+ /**
24
+ * Merge app env with admin-secrets; overlay ensures platform keys come from admin file.
25
+ * @param {Object.<string, string>} merged
26
+ * @param {Object.<string, string>} adminObj
27
+ */
28
+ function mergeRunEnvWithAdminOverlay(merged, adminObj) {
29
+ applyAdminSecretsPlatformOverlay(merged, adminObj);
30
+ }
31
+
32
+ async function applyOnboardingPlatformModeIfNeeded(appName, merged) {
33
+ if (appName !== 'miso-controller') {
34
+ return;
35
+ }
36
+ const mode = await config.getSetupPlatformMode();
37
+ merged.ONBOARDING_FULL_PLATFORM = mode === 'full' ? 'true' : 'false';
38
+ }
20
39
 
21
40
  /**
22
41
  * Backfill only secrets needed for `aifabrix run <app>`: kv:// keys from that app's env.template
@@ -194,31 +213,72 @@ async function readPasswordFromPgpassFile(pgpassPath) {
194
213
  }
195
214
 
196
215
  /**
197
- * Read POSTGRES_PASSWORD from `pgpass` next to the active infra compose (same as `resolveInfraStatePaths()`).
198
- * Does not use `getAifabrixHome()` alone, so a stale pgpass under the wrong base directory (e.g. when
199
- * `aifabrix-home` points at $HOME but compose lives under the config dir) cannot override admin-secrets.
216
+ * Read POSTGRES_PASSWORD from pgpass-style files beside the active infra compose
217
+ * (`resolveInfraStatePaths()`). Tries legacy `pgpass`, then `.pgpass.bootstrap` written at `up-infra`
218
+ * so db-init matches the password Postgres was started with when admin-secrets later drifted.
200
219
  * @param {number|string} developerId - Developer ID
201
220
  * @returns {Promise<string|undefined>} Password or undefined
202
221
  */
203
222
  async function readPostgresPasswordFromPgpass(developerId) {
204
- const { resolveInfraStatePaths } = require('../infrastructure/helpers');
223
+ const { resolveInfraStatePaths, PGPASS_BOOTSTRAP_BASENAME } = require('../infrastructure/helpers');
205
224
  const { infraDir } = resolveInfraStatePaths(developerId);
206
- const pgpassPath = path.join(infraDir, 'pgpass');
207
- if (!fsSync.existsSync(pgpassPath)) {
208
- return undefined;
209
- }
210
- try {
211
- const pwd = await readPasswordFromPgpassFile(pgpassPath);
212
- return pwd;
213
- } catch {
214
- return undefined;
225
+ const candidates = ['pgpass', PGPASS_BOOTSTRAP_BASENAME];
226
+ for (const name of candidates) {
227
+ const pgpassPath = path.join(infraDir, name);
228
+ if (!fsSync.existsSync(pgpassPath)) {
229
+ continue;
230
+ }
231
+ try {
232
+ const pwd = await readPasswordFromPgpassFile(pgpassPath);
233
+ if (pwd !== undefined && pwd !== '') {
234
+ return pwd;
235
+ }
236
+ } catch {
237
+ // try next candidate
238
+ }
215
239
  }
240
+ return undefined;
241
+ }
242
+
243
+ /**
244
+ * @returns {Function}
245
+ */
246
+ function resolveEnsureAdminSecretsFn() {
247
+ const infra = require('../infrastructure');
248
+ return typeof infra.ensureAdminSecrets === 'function'
249
+ ? infra.ensureAdminSecrets
250
+ : require('../infrastructure/helpers').ensureAdminSecrets;
251
+ }
252
+
253
+ /**
254
+ * @param {Object} merged
255
+ * @param {number|string|undefined} developerId
256
+ * @returns {Promise<void>}
257
+ */
258
+ async function applyPostgresPasswordFromPgpass(merged, developerId) {
259
+ if (developerId === undefined) return;
260
+ const pgpassPwd = await readPostgresPasswordFromPgpass(developerId);
261
+ if (pgpassPwd !== undefined) merged.POSTGRES_PASSWORD = pgpassPwd;
262
+ }
263
+
264
+ /**
265
+ * @param {string} runEnvPath
266
+ * @param {string} runEnvAdminPath
267
+ * @param {Object} appOnly
268
+ * @param {Object} dbInitOnly
269
+ * @returns {Promise<void>}
270
+ */
271
+ async function writeRunEnvFiles(runEnvPath, runEnvAdminPath, appOnly, dbInitOnly) {
272
+ const toContent = adminSecrets.envObjectToContent;
273
+ await fs.writeFile(runEnvPath, toContent(appOnly), { mode: 0o600 });
274
+ await fs.writeFile(runEnvAdminPath, toContent(dbInitOnly), { mode: 0o600 });
216
275
  }
217
276
 
218
277
  /**
219
278
  * Build two run env files: .env.run (app-only, no admin secrets) and .env.run.admin (start-only, for db-init).
220
279
  * Admin password is never set in the app container; .env.run.admin is used only for start and then deleted.
221
- * When `pgpass` exists beside the active infra compose, POSTGRES_PASSWORD is taken from it so db-init matches Postgres.
280
+ * When `pgpass` or `.pgpass.bootstrap` exists beside the active infra compose, POSTGRES_PASSWORD is taken
281
+ * from it so db-init matches Postgres.
222
282
  * @async
223
283
  * @param {string} appName - Application name
224
284
  * @param {Object} appConfig - Application configuration
@@ -227,12 +287,8 @@ async function readPostgresPasswordFromPgpass(developerId) {
227
287
  * @returns {Promise<{ runEnvPath: string, runEnvAdminPath: string }>} Paths to .env.run and .env.run.admin
228
288
  */
229
289
  async function buildMergedRunEnvAndWrite(appName, appConfig, devDir, developerId, scopeOpts = null) {
230
- const infra = require('../infrastructure');
231
- const ensureAdminSecretsFn = typeof infra.ensureAdminSecrets === 'function'
232
- ? infra.ensureAdminSecrets
233
- : require('../infrastructure/helpers').ensureAdminSecrets;
234
290
  await ensureRunSecretsForApp(appName);
235
- await ensureAdminSecretsFn();
291
+ await resolveEnsureAdminSecretsFn()();
236
292
  const adminObj = await adminSecrets.readAndDecryptAdminSecrets();
237
293
  const runEnvKey = scopeOpts && scopeOpts.runEnvKey ? String(scopeOpts.runEnvKey).toLowerCase() : 'dev';
238
294
  const appObj = await secretsEnvWrite.resolveAndGetEnvMap(appName, {
@@ -242,22 +298,20 @@ async function buildMergedRunEnvAndWrite(appName, appConfig, devDir, developerId
242
298
  runEnvKey
243
299
  });
244
300
  const merged = { ...adminObj, ...appObj };
245
- if (developerId !== undefined) {
246
- const pgpassPwd = await readPostgresPasswordFromPgpass(developerId);
247
- if (pgpassPwd !== undefined) merged.POSTGRES_PASSWORD = pgpassPwd;
248
- }
301
+ mergeRunEnvWithAdminOverlay(merged, adminObj);
302
+ await applyOnboardingPlatformModeIfNeeded(appName, merged);
303
+ await applyPostgresPasswordFromPgpass(merged, developerId);
249
304
  injectDatabaseNamesAndUsers(merged, appConfig, scopeOpts);
250
305
  injectContainerPortForRun(merged, appConfig, appName);
251
306
 
252
307
  const runEnvPath = path.join(devDir, '.env.run');
253
308
  const runEnvAdminPath = path.join(devDir, '.env.run.admin');
254
-
255
- const appOnly = buildAppOnlyEnv(merged);
256
- const dbInitOnly = buildDbInitOnlyEnv(merged);
257
-
258
- await fs.writeFile(runEnvPath, adminSecrets.envObjectToContent(appOnly), { mode: 0o600 });
259
- await fs.writeFile(runEnvAdminPath, adminSecrets.envObjectToContent(dbInitOnly), { mode: 0o600 });
260
-
309
+ await writeRunEnvFiles(
310
+ runEnvPath,
311
+ runEnvAdminPath,
312
+ buildAppOnlyEnv(merged),
313
+ buildDbInitOnlyEnv(merged)
314
+ );
261
315
  return { runEnvPath, runEnvAdminPath };
262
316
  }
263
317
 
@@ -10,6 +10,7 @@ const config = require('../core/config');
10
10
  const { resolveDockerImageRef } = require('../utils/resolve-docker-image-ref');
11
11
  const { checkImageExists } = require('../utils/app-run-containers');
12
12
  const { buildDevImageRepositoryPath } = require('../utils/image-name');
13
+ const { wantsLocalDockerImageDiscovery } = require('../utils/build-resolve-image');
13
14
 
14
15
  /**
15
16
  * @param {string} appName
@@ -41,7 +42,7 @@ async function resolveRunImageWithLocalFallback(appName, appConfig, runOptions =
41
42
 
42
43
  const baseRef = resolveDockerImageRef(appName, appConfig, opts);
43
44
 
44
- if (opts.base === true) {
45
+ if (opts.base === true || !wantsLocalDockerImageDiscovery(appConfig)) {
45
46
  return baseRef;
46
47
  }
47
48
 
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Stop running app containers (and celery companions) before `aifabrix run`.
3
+ *
4
+ * @fileoverview Pre-run container teardown for port reuse
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { formatWarningLine } = require('../utils/cli-test-layout-chalk');
12
+ const config = require('../core/config');
13
+ const logger = require('../utils/logger');
14
+ const containerHelpers = require('../utils/app-run-containers');
15
+ const helpers = require('./run-helpers');
16
+ const { computeEffectiveEnvironmentScopedResources } = require('../utils/environment-scoped-resources');
17
+ const { getCeleryCompanionAppKeys } = require('../utils/celery-companion-apps');
18
+
19
+ /**
20
+ * @param {Object} appConfig
21
+ * @param {Object} options
22
+ * @returns {Promise<{ effectiveEnvironmentScopedResources: true, env: string }|null>}
23
+ */
24
+ async function resolveRunScopeOpts(appConfig, options) {
25
+ const runEnvKey = (options.env || 'dev').toLowerCase();
26
+ const userCfg = await config.getConfig();
27
+ const effectiveEnvironmentScopedResources = computeEffectiveEnvironmentScopedResources(
28
+ Boolean(userCfg.useEnvironmentScopedResources),
29
+ appConfig.environmentScopedResources === true,
30
+ runEnvKey
31
+ );
32
+ if (!effectiveEnvironmentScopedResources) {
33
+ return null;
34
+ }
35
+ return { effectiveEnvironmentScopedResources: true, env: runEnvKey };
36
+ }
37
+
38
+ /**
39
+ * @param {string[]} companionKeys
40
+ * @param {string|number} developerId
41
+ * @param {boolean} debug
42
+ * @param {Object|null} scopeOpts
43
+ * @returns {Promise<string[]>}
44
+ */
45
+ async function findRunningCompanionKeys(companionKeys, developerId, debug, scopeOpts) {
46
+ const running = [];
47
+ for (const companionKey of companionKeys) {
48
+ if (await helpers.checkContainerRunning(companionKey, developerId, debug, scopeOpts)) {
49
+ running.push(companionKey);
50
+ }
51
+ }
52
+ return running;
53
+ }
54
+
55
+ /**
56
+ * @param {string[]} companionKeys
57
+ * @param {string|number} developerId
58
+ * @param {boolean} debug
59
+ * @param {Object|null} scopeOpts
60
+ * @returns {Promise<void>}
61
+ */
62
+ async function stopCompanionContainers(companionKeys, developerId, debug, scopeOpts) {
63
+ for (const companionKey of companionKeys) {
64
+ const companionName = containerHelpers.getContainerName(companionKey, developerId, scopeOpts);
65
+ logger.log(formatWarningLine(`Stopping companion container ${companionName}`));
66
+ await helpers.stopAndRemoveContainer(companionKey, developerId, debug, scopeOpts);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Check if container is running and stop it (and celery companions) if needed.
72
+ *
73
+ * @async
74
+ * @param {string} appName
75
+ * @param {Object} appConfig
76
+ * @param {Object} options
77
+ * @param {boolean} debug
78
+ * @returns {Promise<void>}
79
+ */
80
+ async function checkAndStopContainer(appName, appConfig, options, debug) {
81
+ const developerId = appConfig.developerId;
82
+ const scopeOpts = await resolveRunScopeOpts(appConfig, options);
83
+ const companionKeys = getCeleryCompanionAppKeys(appName, appConfig);
84
+ const companionsToStop = await findRunningCompanionKeys(
85
+ companionKeys,
86
+ developerId,
87
+ debug,
88
+ scopeOpts
89
+ );
90
+ const containerRunning = await helpers.checkContainerRunning(
91
+ appName,
92
+ developerId,
93
+ debug,
94
+ scopeOpts
95
+ );
96
+ if (!containerRunning && companionsToStop.length === 0) {
97
+ return;
98
+ }
99
+ await stopCompanionContainers(companionsToStop, developerId, debug, scopeOpts);
100
+ if (!containerRunning) {
101
+ return;
102
+ }
103
+ const containerName = containerHelpers.getContainerName(appName, developerId, scopeOpts);
104
+ logger.log(formatWarningLine(`Container ${containerName} is already running`));
105
+ await helpers.stopAndRemoveContainer(appName, developerId, debug, scopeOpts);
106
+ }
107
+
108
+ module.exports = {
109
+ checkAndStopContainer,
110
+ resolveRunScopeOpts,
111
+ findRunningCompanionKeys,
112
+ stopCompanionContainers
113
+ };
package/lib/app/run.js CHANGED
@@ -27,6 +27,7 @@ const containerHelpers = require('../utils/app-run-containers');
27
27
  const helpers = require('./run-helpers');
28
28
  const { ensureReloadSync, logReloadDevSummary } = require('./run-reload-sync');
29
29
  const { logRestartDevMountSummary } = require('./restart-display');
30
+ const { checkAndStopContainer } = require('./run-stop-containers');
30
31
  const { execWithDockerEnv } = require('../utils/docker-exec');
31
32
  const { isRunCliNoProxy } = require('../utils/run-cli-flags');
32
33
 
@@ -111,37 +112,6 @@ async function validateAppForRun(appName, _debug) {
111
112
  return true;
112
113
  }
113
114
 
114
- /**
115
- * Check if container is running and stop it if needed
116
- * @async
117
- * @param {string} appName - Application name
118
- * @param {number|string} developerId - Developer ID
119
- * @param {boolean} debug - Debug flag
120
- * @returns {Promise<void>}
121
- */
122
- async function checkAndStopContainer(appName, appConfig, options, debug) {
123
- const developerId = appConfig.developerId;
124
- const runEnvKey = (options.env || 'dev').toLowerCase();
125
- const userCfg = await config.getConfig();
126
- const { computeEffectiveEnvironmentScopedResources } = require('../utils/environment-scoped-resources');
127
- const effectiveEnvironmentScopedResources = computeEffectiveEnvironmentScopedResources(
128
- Boolean(userCfg.useEnvironmentScopedResources),
129
- appConfig.environmentScopedResources === true,
130
- runEnvKey
131
- );
132
- const scopeOpts = effectiveEnvironmentScopedResources
133
- ? { effectiveEnvironmentScopedResources: true, env: runEnvKey }
134
- : null;
135
- const containerRunning = await helpers.checkContainerRunning(appName, developerId, debug, scopeOpts);
136
- if (!containerRunning) {
137
- return;
138
- }
139
-
140
- const containerName = containerHelpers.getContainerName(appName, developerId, scopeOpts);
141
- logger.log(formatWarningLine(`Container ${containerName} is already running`));
142
- await helpers.stopAndRemoveContainer(appName, developerId, debug, scopeOpts);
143
- }
144
-
145
115
  /**
146
116
  * Calculate host port and validate it's available
147
117
  * @async
@@ -49,6 +49,40 @@ function logApplicationOptional(a) {
49
49
  });
50
50
  }
51
51
 
52
+ function localRunProfileDiffers(application, localRunProfile) {
53
+ if (!localRunProfile || !application) return false;
54
+ if (localRunProfile.url && localRunProfile.url !== application.url) return true;
55
+ if (localRunProfile.internalUrl && localRunProfile.internalUrl !== application.internalUrl) {
56
+ return true;
57
+ }
58
+ return (
59
+ localRunProfile.port !== undefined &&
60
+ localRunProfile.port !== null &&
61
+ localRunProfile.port !== application.port
62
+ );
63
+ }
64
+
65
+ function logLocalRunProfileSection(summary) {
66
+ const local = summary.localRunProfile;
67
+ if (!local || summary.source !== 'online' || !localRunProfileDiffers(summary.application, local)) {
68
+ return;
69
+ }
70
+ logger.log('');
71
+ logger.log(chalk.gray(' Local run profile (docker manifest url://):'));
72
+ if (local.url && local.url !== summary.application.url) {
73
+ logger.log(chalk.gray(` URL: ${local.url}`));
74
+ }
75
+ if (local.internalUrl && local.internalUrl !== summary.application.internalUrl) {
76
+ logger.log(chalk.gray(` Internal URL: ${local.internalUrl}`));
77
+ }
78
+ if (local.port !== undefined && local.port !== summary.application.port) {
79
+ logger.log(chalk.gray(` Port: ${local.port}`));
80
+ }
81
+ logger.log(
82
+ chalk.gray(' Controller registry values above are what identity sync and pipeline use.')
83
+ );
84
+ }
85
+
52
86
  function logApplicationFields(a) {
53
87
  logApplicationRequired(a);
54
88
  logApplicationOptional(a);
@@ -96,6 +130,7 @@ function logApplicationSection(a, summary) {
96
130
  } else {
97
131
  logApplicationFields(a);
98
132
  }
133
+ logLocalRunProfileSection(summary);
99
134
  if (summary.runReloadDefault && !summary.isExternal) {
100
135
  logger.log(` ${'Run default:'.padEnd(16)} reload on (config)`);
101
136
  }
package/lib/app/show.js CHANGED
@@ -54,10 +54,22 @@ function attachRunDefaultsFromUserConfig(summary, appKey, userCfg) {
54
54
 
55
55
  /**
56
56
  * Overlay `application.url` / `application.internalUrl` from declarative url:// rules (same profile as run `.env`, default `docker`).
57
- * Used for offline and **online** show so controller metadata does not stale local proxy/TLS hints.
57
+ * Offline: replaces URL fields for local run hints. Online: keeps controller registry on `application` and adds `localRunProfile` when it differs.
58
58
  * @param {Object} summary
59
59
  * @param {string} appKey
60
60
  */
61
+ function portFromShowUrl(url) {
62
+ if (!url || typeof url !== 'string') return undefined;
63
+ try {
64
+ const parsed = new URL(url);
65
+ if (!parsed.port) return undefined;
66
+ const port = Number(parsed.port);
67
+ return Number.isFinite(port) ? port : undefined;
68
+ } catch {
69
+ return undefined;
70
+ }
71
+ }
72
+
61
73
  async function attachDeclarativeUrlsToShowApplication(summary, appKey) {
62
74
  if (!summary || summary.isExternal) return;
63
75
  const t = summary.application && summary.application.type;
@@ -68,8 +80,24 @@ async function attachDeclarativeUrlsToShowApplication(summary, appKey) {
68
80
  const variablesPath = resolveApplicationConfigPath(appPath);
69
81
  const urls = await resolveDeclarativeShowUrlsForApp(appKey, appPath, variablesPath, 'docker');
70
82
  if (!urls) return;
83
+
84
+ const localPort = portFromShowUrl(urls.publicUrl);
85
+ const localRunProfile = {
86
+ url: urls.publicUrl,
87
+ internalUrl: urls.internalUrl,
88
+ port: localPort ?? summary.application.port
89
+ };
90
+
91
+ if (summary.source === 'online') {
92
+ summary.localRunProfile = localRunProfile;
93
+ return;
94
+ }
95
+
71
96
  summary.application.url = urls.publicUrl;
72
97
  summary.application.internalUrl = urls.internalUrl;
98
+ if (localPort !== undefined) {
99
+ summary.application.port = localPort;
100
+ }
73
101
  } catch {
74
102
  /* display-only */
75
103
  }
@@ -830,6 +858,9 @@ function outputOnlineJson(summary, permissionsOnly, manifestSource) {
830
858
  out.runReloadDefault = true;
831
859
  }
832
860
  out.runProxyDefault = Boolean(summary.runProxyDefault);
861
+ if (summary.localRunProfile) {
862
+ out.localRunProfile = summary.localRunProfile;
863
+ }
833
864
  if (summary.externalSystem !== undefined && summary.externalSystem !== null) {
834
865
  out.externalSystem = summary.externalSystem && summary.externalSystem.error
835
866
  ? { error: summary.externalSystem.error }