@aifabrix/builder 2.44.5 → 2.45.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 (249) hide show
  1. package/.cursor/rules/cli-layout.mdc +8 -4
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/README.md +15 -23
  4. package/integration/hubspot-test/README.md +2 -0
  5. package/integration/hubspot-test/test.js +5 -3
  6. package/jest.projects.js +104 -2
  7. package/lib/api/controller-health.api.js +49 -0
  8. package/lib/api/dimension-values.api.js +82 -0
  9. package/lib/api/dimensions.api.js +114 -0
  10. package/lib/api/external-systems.api.js +1 -0
  11. package/lib/api/integration-clients.api.js +168 -0
  12. package/lib/api/types/dimension-values.types.js +28 -0
  13. package/lib/api/types/dimensions.types.js +31 -0
  14. package/lib/api/types/integration-clients.types.js +45 -0
  15. package/lib/api/validation-runner.js +46 -25
  16. package/lib/app/deploy-config.js +11 -1
  17. package/lib/app/deploy-status-display.js +3 -3
  18. package/lib/app/deploy.js +36 -14
  19. package/lib/app/display.js +15 -11
  20. package/lib/app/helpers.js +3 -3
  21. package/lib/app/index.js +3 -3
  22. package/lib/app/push.js +46 -23
  23. package/lib/app/register.js +7 -6
  24. package/lib/app/restart-display.js +126 -0
  25. package/lib/app/rotate-secret.js +7 -6
  26. package/lib/app/run-container-start.js +12 -6
  27. package/lib/app/run-env-compose.js +30 -1
  28. package/lib/app/run-helpers.js +58 -19
  29. package/lib/app/run-reload-sync.js +148 -0
  30. package/lib/app/run-resolve-image.js +51 -1
  31. package/lib/app/run.js +148 -74
  32. package/lib/app/show-display.js +7 -0
  33. package/lib/app/show.js +87 -5
  34. package/lib/build/index.js +83 -49
  35. package/lib/cli/doctor-check.js +117 -0
  36. package/lib/cli/index.js +8 -2
  37. package/lib/cli/infra-guided.js +460 -0
  38. package/lib/cli/installation-log-command.js +73 -0
  39. package/lib/cli/setup-app.js +31 -3
  40. package/lib/cli/setup-auth.js +98 -27
  41. package/lib/cli/setup-dev-path-commands.js +50 -3
  42. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  43. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  44. package/lib/cli/setup-infra.js +132 -118
  45. package/lib/cli/setup-integration-client.js +182 -0
  46. package/lib/cli/setup-parameters.js +21 -2
  47. package/lib/cli/setup-platform.js +102 -0
  48. package/lib/cli/setup-secrets.js +18 -6
  49. package/lib/cli/setup-utility-resolve.js +132 -0
  50. package/lib/cli/setup-utility.js +143 -84
  51. package/lib/commands/app-logs.js +81 -33
  52. package/lib/commands/auth-config.js +116 -18
  53. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  54. package/lib/commands/datasource-capability-output.js +29 -0
  55. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  56. package/lib/commands/datasource-capability.js +411 -0
  57. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  58. package/lib/commands/datasource.js +53 -13
  59. package/lib/commands/dev-down.js +3 -3
  60. package/lib/commands/dev-infra-gate.js +32 -0
  61. package/lib/commands/dev-init.js +13 -7
  62. package/lib/commands/dimension-value.js +179 -0
  63. package/lib/commands/dimension.js +330 -0
  64. package/lib/commands/integration-client.js +430 -0
  65. package/lib/commands/login-device.js +65 -30
  66. package/lib/commands/login.js +21 -10
  67. package/lib/commands/parameters-validate.js +78 -13
  68. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  69. package/lib/commands/repair-datasource-keys.js +10 -5
  70. package/lib/commands/repair-datasource.js +19 -7
  71. package/lib/commands/repair-env-template.js +4 -1
  72. package/lib/commands/repair-openapi-sync.js +172 -0
  73. package/lib/commands/repair-persist.js +102 -0
  74. package/lib/commands/repair-rbac-extract.js +27 -0
  75. package/lib/commands/repair-rbac-migrate.js +186 -0
  76. package/lib/commands/repair-rbac.js +214 -31
  77. package/lib/commands/repair-system-alignment.js +246 -0
  78. package/lib/commands/repair-system-permissions.js +168 -0
  79. package/lib/commands/repair.js +120 -338
  80. package/lib/commands/secure.js +1 -1
  81. package/lib/commands/setup-modes.js +468 -0
  82. package/lib/commands/setup-prompts.js +421 -0
  83. package/lib/commands/setup.js +254 -0
  84. package/lib/commands/teardown.js +277 -0
  85. package/lib/commands/up-common.js +113 -19
  86. package/lib/commands/up-dataplane.js +44 -19
  87. package/lib/commands/up-miso.js +18 -18
  88. package/lib/commands/upload.js +111 -23
  89. package/lib/commands/wizard-core-helpers.js +14 -11
  90. package/lib/commands/wizard-core.js +6 -5
  91. package/lib/commands/wizard-dataplane.js +2 -2
  92. package/lib/commands/wizard-entity-selection.js +4 -3
  93. package/lib/commands/wizard-headless.js +2 -1
  94. package/lib/commands/wizard.js +2 -1
  95. package/lib/constants/infra-compose-service-names.js +40 -0
  96. package/lib/core/audit-logger.js +1 -34
  97. package/lib/core/config-admin-email.js +56 -0
  98. package/lib/core/config-normalize.js +60 -0
  99. package/lib/core/config-registered-controller-urls.js +54 -0
  100. package/lib/core/config.js +33 -50
  101. package/lib/core/env-reader.js +16 -3
  102. package/lib/core/secrets-admin-env.js +101 -0
  103. package/lib/core/secrets-ensure-infra.js +34 -1
  104. package/lib/core/secrets-ensure.js +88 -66
  105. package/lib/core/secrets-env-content.js +428 -0
  106. package/lib/core/secrets-env-declarative-expand.js +170 -0
  107. package/lib/core/secrets-env-write.js +29 -1
  108. package/lib/core/secrets-load.js +252 -0
  109. package/lib/core/secrets-names.js +32 -0
  110. package/lib/core/secrets.js +17 -757
  111. package/lib/datasource/capability/basic-exposure.js +76 -0
  112. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  113. package/lib/datasource/capability/capability-key.js +34 -0
  114. package/lib/datasource/capability/capability-resolve.js +172 -0
  115. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  116. package/lib/datasource/capability/copy-operations.js +348 -0
  117. package/lib/datasource/capability/copy-test-payload.js +139 -0
  118. package/lib/datasource/capability/create-operations.js +235 -0
  119. package/lib/datasource/capability/dimension-operations.js +151 -0
  120. package/lib/datasource/capability/dimension-validate.js +219 -0
  121. package/lib/datasource/capability/json-pointer.js +31 -0
  122. package/lib/datasource/capability/reference-rewrite.js +51 -0
  123. package/lib/datasource/capability/relate-operations.js +325 -0
  124. package/lib/datasource/capability/relate-validate.js +219 -0
  125. package/lib/datasource/capability/remove-operations.js +275 -0
  126. package/lib/datasource/capability/run-capability-copy.js +152 -0
  127. package/lib/datasource/capability/run-capability-diff.js +135 -0
  128. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  129. package/lib/datasource/capability/run-capability-edit.js +377 -0
  130. package/lib/datasource/capability/run-capability-relate.js +193 -0
  131. package/lib/datasource/capability/run-capability-remove.js +105 -0
  132. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  133. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  134. package/lib/datasource/list.js +136 -23
  135. package/lib/datasource/log-viewer.js +2 -4
  136. package/lib/datasource/unified-validation-run.js +51 -16
  137. package/lib/datasource/validate.js +53 -1
  138. package/lib/deployment/deploy-poll-ui.js +60 -0
  139. package/lib/deployment/deployer-status.js +29 -3
  140. package/lib/deployment/deployer.js +48 -30
  141. package/lib/deployment/environment.js +7 -2
  142. package/lib/deployment/poll-interval.js +72 -0
  143. package/lib/deployment/push.js +11 -9
  144. package/lib/external-system/deploy.js +9 -2
  145. package/lib/external-system/download.js +61 -32
  146. package/lib/external-system/sync-deploy-manifest.js +33 -0
  147. package/lib/infrastructure/index.js +49 -19
  148. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  149. package/lib/internal/node-fs.js +2 -0
  150. package/lib/parameters/infra-kv-discovery.js +29 -4
  151. package/lib/parameters/infra-parameter-catalog.js +6 -3
  152. package/lib/parameters/infra-parameter-validate.js +67 -19
  153. package/lib/resolvers/datasource-resolver.js +53 -0
  154. package/lib/resolvers/dimension-file.js +52 -0
  155. package/lib/resolvers/manifest-resolver.js +133 -0
  156. package/lib/schema/application-schema.json +4 -0
  157. package/lib/schema/external-datasource.schema.json +183 -53
  158. package/lib/schema/external-system.schema.json +23 -10
  159. package/lib/schema/infra.parameter.yaml +26 -1
  160. package/lib/schema/wizard-config.schema.json +1 -1
  161. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  162. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  163. package/lib/utils/app-config-resolver.js +24 -1
  164. package/lib/utils/app-run-containers.js +2 -2
  165. package/lib/utils/applications-config-defaults.js +206 -0
  166. package/lib/utils/auth-config-validator.js +2 -12
  167. package/lib/utils/bash-secret-env.js +59 -0
  168. package/lib/utils/cli-secrets-error-format.js +78 -0
  169. package/lib/utils/cli-test-layout-chalk.js +31 -9
  170. package/lib/utils/cli-utils.js +4 -36
  171. package/lib/utils/compose-generate-docker-compose.js +111 -6
  172. package/lib/utils/compose-generator.js +17 -8
  173. package/lib/utils/controller-url.js +50 -7
  174. package/lib/utils/datasource-test-run-display.js +8 -0
  175. package/lib/utils/dev-hosts-helper.js +3 -2
  176. package/lib/utils/dev-init-ssh-merge.js +2 -1
  177. package/lib/utils/docker-build.js +17 -9
  178. package/lib/utils/docker-reload-mount.js +127 -0
  179. package/lib/utils/env-copy.js +99 -14
  180. package/lib/utils/env-template.js +5 -1
  181. package/lib/utils/external-readme.js +71 -2
  182. package/lib/utils/external-system-local-test-tty.js +3 -2
  183. package/lib/utils/external-system-readiness-core.js +45 -12
  184. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  185. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  186. package/lib/utils/external-system-readiness-display.js +10 -1
  187. package/lib/utils/file-upload.js +40 -3
  188. package/lib/utils/health-check-db-init.js +107 -0
  189. package/lib/utils/health-check-public-warn.js +69 -0
  190. package/lib/utils/health-check-url.js +28 -10
  191. package/lib/utils/health-check.js +139 -107
  192. package/lib/utils/help-builder.js +5 -1
  193. package/lib/utils/image-name.js +34 -7
  194. package/lib/utils/infra-optional-service-flags.js +69 -0
  195. package/lib/utils/installation-log-core.js +282 -0
  196. package/lib/utils/installation-log-record.js +237 -0
  197. package/lib/utils/installation-log.js +123 -0
  198. package/lib/utils/integration-file-backup.js +74 -0
  199. package/lib/utils/log-redaction.js +105 -0
  200. package/lib/utils/manifest-location.js +164 -0
  201. package/lib/utils/manifest-source-emit.js +162 -0
  202. package/lib/utils/mutagen-install.js +30 -3
  203. package/lib/utils/paths.js +308 -76
  204. package/lib/utils/postgres-wipe.js +212 -0
  205. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  206. package/lib/utils/remote-dev-auth.js +21 -5
  207. package/lib/utils/remote-docker-env.js +9 -1
  208. package/lib/utils/remote-secrets-loader.js +49 -4
  209. package/lib/utils/resolve-docker-image-ref.js +9 -3
  210. package/lib/utils/run-cli-flags.js +29 -0
  211. package/lib/utils/secrets-ancestor-paths.js +47 -0
  212. package/lib/utils/secrets-canonical.js +10 -3
  213. package/lib/utils/secrets-helpers.js +17 -10
  214. package/lib/utils/secrets-kv-refs.js +42 -0
  215. package/lib/utils/secrets-kv-scope.js +19 -2
  216. package/lib/utils/secrets-materialize-local.js +134 -0
  217. package/lib/utils/secrets-path.js +26 -13
  218. package/lib/utils/secrets-utils.js +20 -10
  219. package/lib/utils/system-builder-root.js +42 -0
  220. package/lib/utils/url-declarative-public-base.js +80 -12
  221. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  222. package/lib/utils/url-declarative-resolve-build.js +24 -388
  223. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  224. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  225. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  226. package/lib/utils/url-declarative-resolve.js +47 -7
  227. package/lib/utils/url-declarative-runtime-base-path.js +52 -0
  228. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  229. package/lib/utils/urls-local-registry-scan.js +103 -0
  230. package/lib/utils/urls-local-registry.js +158 -76
  231. package/lib/utils/validation-poll-ui.js +81 -0
  232. package/lib/utils/validation-run-poll.js +29 -5
  233. package/lib/utils/with-muted-logger.js +53 -0
  234. package/package.json +3 -1
  235. package/templates/applications/dataplane/application.yaml +5 -1
  236. package/templates/applications/dataplane/rbac.yaml +10 -10
  237. package/templates/applications/keycloak/env.template +8 -6
  238. package/templates/applications/miso-controller/application.yaml +9 -0
  239. package/templates/applications/miso-controller/env.template +27 -29
  240. package/templates/applications/miso-controller/rbac.yaml +9 -9
  241. package/templates/external-system/README.md.hbs +83 -123
  242. package/.npmrc.token +0 -1
  243. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  244. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  245. package/.nyc_output/processinfo/index.json +0 -1
  246. package/lib/api/service-users.api.js +0 -150
  247. package/lib/api/types/service-users.types.js +0 -65
  248. package/lib/cli/setup-service-user.js +0 -187
  249. package/lib/commands/service-user.js +0 -429
package/lib/app/push.js CHANGED
@@ -1,4 +1,12 @@
1
- const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
+ const {
2
+ formatSuccessLine,
3
+ formatSuccessParagraph,
4
+ sectionTitle,
5
+ headerKeyValue,
6
+ metadata,
7
+ formatWarningLine,
8
+ formatNextActions
9
+ } = require('../utils/cli-test-layout-chalk');
2
10
  /**
3
11
  * Application Push Utilities
4
12
  *
@@ -9,9 +17,9 @@ const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test
9
17
  * @version 2.0.0
10
18
  */
11
19
 
12
- const chalk = require('chalk');
13
20
  const pushUtils = require('../deployment/push');
14
21
  const logger = require('../utils/logger');
22
+ const { detectAppType } = require('../utils/paths');
15
23
 
16
24
  /**
17
25
  * Validate application name format
@@ -150,6 +158,33 @@ async function authenticateWithRegistry(registry) {
150
158
  }
151
159
  }
152
160
 
161
+ /**
162
+ * Layout-aligned notice when push is skipped for external integrations.
163
+ * @param {string} appName
164
+ */
165
+ function logExternalIntegrationPushNotice(appName) {
166
+ logger.log('');
167
+ logger.log(sectionTitle('Push'));
168
+ logger.log(headerKeyValue('Application:', appName));
169
+ logger.log(formatWarningLine('External integrations have no application image to push.'));
170
+ logger.log(metadata('Use upload or deploy for integration artifacts.'));
171
+ logger.log(formatNextActions([`aifabrix upload ${appName}`, `aifabrix deploy ${appName}`]));
172
+ }
173
+
174
+ /**
175
+ * Header after validation passes for a regular (non-external) push.
176
+ * @param {string} appName
177
+ * @param {string} registry
178
+ * @param {string} imageName
179
+ */
180
+ function logPushCommandHeader(appName, registry, imageName) {
181
+ logger.log('');
182
+ logger.log(sectionTitle('Push'));
183
+ logger.log(headerKeyValue('Application:', appName));
184
+ logger.log(metadata(`Registry: ${registry}`));
185
+ logger.log(metadata(`Repository: ${imageName}`));
186
+ }
187
+
153
188
  /**
154
189
  * Pushes image tags to registry
155
190
  * @async
@@ -171,7 +206,9 @@ async function pushImageTags(imageName, registry, tags) {
171
206
  (errorMessage.includes('authentication') && errorMessage.includes('401'));
172
207
 
173
208
  if (isAuthError) {
174
- logger.log(chalk.yellow('⚠ Authentication expired, re-authenticating...'));
209
+ logger.log(
210
+ formatWarningLine('Registry authentication expired; re-authenticating, then retrying push.')
211
+ );
175
212
  await authenticateWithRegistry(registry);
176
213
  // Retry push after re-authentication
177
214
  await Promise.all(tags.map(async(tag) => {
@@ -190,9 +227,9 @@ async function pushImageTags(imageName, registry, tags) {
190
227
  * @param {Array<string>} tags - Image tags
191
228
  */
192
229
  function displayPushResults(registry, imageName, tags) {
193
- logger.log(formatSuccessParagraph(`Successfully pushed ${tags.length} tag(s) to ${registry}`));
194
- logger.log(chalk.gray(`Image: ${registry}/${imageName}:*`));
195
- logger.log(chalk.gray(`Tags: ${tags.join(', ')}`));
230
+ logger.log(formatSuccessParagraph(`Pushed ${tags.length} tag(s) to ${registry}`));
231
+ logger.log(headerKeyValue('Image:', `${registry}/${imageName}`));
232
+ logger.log(headerKeyValue('Tags:', tags.join(', ')));
196
233
  }
197
234
 
198
235
  /**
@@ -204,38 +241,25 @@ function displayPushResults(registry, imageName, tags) {
204
241
  * @returns {Promise<void>} Resolves when push is complete
205
242
  */
206
243
  async function pushApp(appName, options = {}) {
207
- // Check if app type is external - skip push
208
- const { detectAppType } = require('../utils/paths');
209
244
  try {
210
245
  const { isExternal } = await detectAppType(appName);
211
246
  if (isExternal) {
212
- logger.log(chalk.yellow('⚠ External systems don\'t require Docker images. Skipping push...'));
247
+ logExternalIntegrationPushNotice(appName);
213
248
  return;
214
249
  }
215
- } catch (error) {
250
+ } catch (_error) {
216
251
  // If detection fails, continue with normal push
217
252
  // (detectAppType throws if app doesn't exist, which is fine for push command)
218
253
  }
219
254
  try {
220
- // Validate app name
221
255
  validateAppName(appName);
222
-
223
- // Load configuration
224
256
  const { registry, imageName } = await loadPushConfig(appName, options);
225
-
226
- // Validate push configuration
227
257
  await validatePushConfig(registry, imageName, appName);
228
-
229
- // Authenticate with registry
258
+ logPushCommandHeader(appName, registry, imageName);
230
259
  await authenticateWithRegistry(registry);
231
-
232
- // Push image tags
233
260
  const tags = options.tag ? options.tag.split(',').map(t => t.trim()) : ['latest'];
234
261
  await pushImageTags(imageName, registry, tags);
235
-
236
- // Display results
237
262
  displayPushResults(registry, imageName, tags);
238
-
239
263
  } catch (error) {
240
264
  throw new Error(`Failed to push application: ${error.message}`);
241
265
  }
@@ -245,4 +269,3 @@ module.exports = {
245
269
  pushApp,
246
270
  validateAppName
247
271
  };
248
-
@@ -94,19 +94,20 @@ async function saveLocalCredentials(responseData, apiUrl) {
94
94
  await saveLocalSecret(clientIdKey, responseData.credentials.clientId);
95
95
  await saveLocalSecret(clientSecretKey, responseData.credentials.clientSecret);
96
96
 
97
- // Update env.template
97
+ // Update env.template (kv:// refs only; no resolved values touch disk)
98
98
  await updateEnvTemplate(registeredAppKey, clientIdKey, clientSecretKey, apiUrl);
99
99
 
100
- // Regenerate .env file with updated credentials
100
+ // Resolve in-memory so any missing-secret / kv:// error surfaces here, but never
101
+ // materialize <appPath>/.env or envOutputPath — that is only done by `aifabrix resolve`.
101
102
  try {
102
- await generateEnvFile(registeredAppKey, null, 'local');
103
- logger.log(formatSuccessLine('.env file updated with new credentials'));
103
+ await generateEnvFile(registeredAppKey, null, 'local', true, { noWrite: true });
104
104
  } catch (error) {
105
- logger.warn(chalk.yellow(`⚠ Could not regenerate .env file: ${error.message}`));
105
+ logger.warn(chalk.yellow(`⚠ Could not validate env resolution: ${error.message}`));
106
106
  }
107
107
 
108
108
  logger.log(formatSuccessParagraph('Credentials saved to ~/.aifabrix/secrets.local.yaml'));
109
- logger.log(formatSuccessLine('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
109
+ logger.log(formatSuccessLine('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL'));
110
+ logger.log(formatSuccessLine('Run "aifabrix resolve ' + registeredAppKey + '" to materialize an on-disk .env\n'));
110
111
  } catch (error) {
111
112
  logger.warn(chalk.yellow(`⚠ Could not save credentials locally: ${error.message}`));
112
113
  }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * After `docker restart`, describe dev workspace mounts for developer clarity.
3
+ *
4
+ * @fileoverview Bind mount vs remote-engine hints (aligns with run --reload messaging)
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const logger = require('../utils/logger');
12
+ const config = require('../core/config');
13
+ const { execWithDockerEnv } = require('../utils/docker-exec');
14
+ const { sectionTitle, headerKeyValue, metadata } = require('../utils/cli-test-layout-chalk');
15
+ const { isReloadBindMountOnEngineHost } = require('../utils/docker-reload-mount');
16
+ const { isApplicationsReloadDefaultOn } = require('../utils/applications-config-defaults');
17
+
18
+ /**
19
+ * @param {unknown} mounts - docker inspect .Mounts
20
+ * @returns {{ Type: string, Source: string, Destination: string }|null}
21
+ */
22
+ function findAppBindMount(mounts) {
23
+ if (!Array.isArray(mounts)) {
24
+ return null;
25
+ }
26
+ const hit = mounts.find((m) => m && m.Type === 'bind' && m.Destination === '/app');
27
+ return hit && typeof hit.Source === 'string' ? hit : null;
28
+ }
29
+
30
+ /**
31
+ * @param {string} stdout
32
+ * @returns {unknown|null}
33
+ */
34
+ function parseInspectMountsStdout(stdout) {
35
+ try {
36
+ return JSON.parse(String(stdout || '').trim());
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * @param {string} containerName
44
+ * @returns {Promise<unknown|null>}
45
+ */
46
+ async function fetchContainerMountsJson(containerName) {
47
+ try {
48
+ const { stdout } = await execWithDockerEnv(
49
+ `docker inspect --format '{{json .Mounts}}' ${containerName}`,
50
+ { maxBuffer: 2 * 1024 * 1024 }
51
+ );
52
+ return parseInspectMountsStdout(stdout);
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Log saved reload default for the app (same source as `aifabrix run` dev persist / `aifabrix show`).
60
+ * @param {string|null|undefined} appName - Application key (e.g. miso-controller)
61
+ * @returns {Promise<void>}
62
+ */
63
+ async function logReloadConfigSummaryForRestart(appName) {
64
+ if (!appName || typeof appName !== 'string') {
65
+ return;
66
+ }
67
+ const userCfg = await config.getConfig();
68
+ const reloadOn = isApplicationsReloadDefaultOn(userCfg, appName);
69
+ logger.log('');
70
+ logger.log(sectionTitle('Reload (config)'));
71
+ logger.log(
72
+ headerKeyValue(
73
+ 'Next dev run:',
74
+ reloadOn ? 'reload on (applications.<app>.reload in config)' : 'reload off'
75
+ )
76
+ );
77
+ logger.log(
78
+ metadata(
79
+ 'Persisted when you last ran this app in dev with or without --reload. Use aifabrix show <app> to inspect.'
80
+ )
81
+ );
82
+ logger.log('');
83
+ }
84
+
85
+ /**
86
+ * Log workspace transport after a successful container restart (mounts unchanged).
87
+ * @param {string} containerName - Docker container name
88
+ * @param {string|null} [appName] - Application key; when set, logs reload default from config after mount info
89
+ * @returns {Promise<void>}
90
+ */
91
+ async function logRestartDevMountSummary(containerName, appName = null) {
92
+ if (!containerName || typeof containerName !== 'string') {
93
+ await logReloadConfigSummaryForRestart(appName);
94
+ return;
95
+ }
96
+ const mounts = await fetchContainerMountsJson(containerName);
97
+ const appBind = findAppBindMount(mounts);
98
+ if (appBind) {
99
+ const endpoint = await config.getDockerEndpoint();
100
+ const localEngine = isReloadBindMountOnEngineHost(endpoint);
101
+
102
+ logger.log('');
103
+ logger.log(sectionTitle('Dev workspace (unchanged by restart)'));
104
+ if (localEngine) {
105
+ logger.log(headerKeyValue('Transport:', 'Direct bind mount on the Docker host (no Mutagen).'));
106
+ logger.log(headerKeyValue('Host path → container:', `${appBind.Source} → /app`));
107
+ logger.log(metadata('Edits under the host path are visible inside the container immediately.'));
108
+ } else {
109
+ logger.log(
110
+ headerKeyValue(
111
+ 'Transport:',
112
+ 'Bind mount on the Docker engine (see path below; Mutagen may sync to this path when using --reload).'
113
+ )
114
+ );
115
+ logger.log(headerKeyValue('Engine path → container:', `${appBind.Source} → /app`));
116
+ }
117
+ logger.log('');
118
+ }
119
+ await logReloadConfigSummaryForRestart(appName);
120
+ }
121
+
122
+ module.exports = {
123
+ findAppBindMount,
124
+ logReloadConfigSummaryForRestart,
125
+ logRestartDevMountSummary
126
+ };
@@ -281,20 +281,21 @@ async function saveCredentialsLocally(appKey, credentials, actualControllerUrl)
281
281
  await saveLocalSecret(clientIdKey, credentials.clientId);
282
282
  await saveLocalSecret(clientSecretKey, credentials.clientSecret);
283
283
 
284
- // Update env.template if localhost
284
+ // Update env.template if localhost (kv:// refs only; no resolved values touch disk)
285
285
  if (isLocalhost(actualControllerUrl)) {
286
286
  await updateEnvTemplate(appKey, clientIdKey, clientSecretKey, actualControllerUrl);
287
287
 
288
- // Regenerate .env file with updated credentials
288
+ // Resolve in-memory so any missing-secret / kv:// error surfaces here, but never
289
+ // materialize <appPath>/.env or envOutputPath — that is only done by `aifabrix resolve`.
289
290
  try {
290
- await generateEnvFile(appKey, null, 'local');
291
- logger.log(formatSuccessLine('.env file updated with new credentials'));
291
+ await generateEnvFile(appKey, null, 'local', true, { noWrite: true });
292
292
  } catch (error) {
293
- logger.warn(chalk.yellow(`⚠ Could not regenerate .env file: ${error.message}`));
293
+ logger.warn(chalk.yellow(`⚠ Could not validate env resolution: ${error.message}`));
294
294
  }
295
295
 
296
296
  logger.log(formatSuccessParagraph('Credentials saved to ~/.aifabrix/secrets.local.yaml'));
297
- logger.log(formatSuccessLine('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
297
+ logger.log(formatSuccessLine('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL'));
298
+ logger.log(formatSuccessLine('Run "aifabrix resolve ' + appKey + '" to materialize an on-disk .env\n'));
298
299
  } else {
299
300
  logger.log(formatSuccessParagraph('Credentials saved to ~/.aifabrix/secrets.local.yaml\n'));
300
301
  }
@@ -5,7 +5,11 @@
5
5
  */
6
6
 
7
7
  'use strict';
8
- const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
8
+ const {
9
+ formatSuccessLine,
10
+ formatProgress,
11
+ formatWarningLine
12
+ } = require('../utils/cli-test-layout-chalk');
9
13
 
10
14
  const fs = require('fs').promises;
11
15
  const chalk = require('chalk');
@@ -31,8 +35,8 @@ const execAsync = promisify(exec);
31
35
  async function emitAndRunDockerFallback(appName, appConfig, port, opts) {
32
36
  const { debug, runEnvPath, runOptions, misoEnvironment } = opts;
33
37
  logger.log(
34
- chalk.yellow(
35
- 'Docker Compose not found; using docker run (apps without database/redis only). ' +
38
+ formatWarningLine(
39
+ 'Docker Compose not found; using docker run (apps without Postgres/Redis only). ' +
36
40
  'Install docker-compose-plugin for full compose support.'
37
41
  )
38
42
  );
@@ -116,7 +120,7 @@ async function waitForHealthyAndCleanupEnvFiles(appName, port, appConfig, o) {
116
120
  const displayUrl = (healthUrl && typeof healthUrl === 'string')
117
121
  ? healthUrl
118
122
  : `http://localhost:${port}${healthCheckPath}`;
119
- logger.log(chalk.blue(`Waiting for application to be healthy at ${displayUrl}...`));
123
+ logger.log(formatProgress(`Waiting for healthy: ${displayUrl}…`));
120
124
  await healthCheck.waitForHealthCheck(appName, 90, port, appConfig, debug, ro);
121
125
 
122
126
  for (const p of [runEnvPath, runEnvAdminPath]) {
@@ -124,7 +128,9 @@ async function waitForHealthyAndCleanupEnvFiles(appName, port, appConfig, o) {
124
128
  try {
125
129
  await fs.unlink(p);
126
130
  } catch (err) {
127
- if (err.code !== 'ENOENT') logger.log(chalk.yellow(`Warning: could not remove run .env: ${err.message}`));
131
+ if (err.code !== 'ENOENT') {
132
+ logger.log(formatWarningLine(`Could not remove run .env: ${err.message}`));
133
+ }
128
134
  }
129
135
  }
130
136
  }
@@ -148,7 +154,7 @@ async function startContainer(appName, composePath, port, appConfig = null, opts
148
154
  devMountPath = null,
149
155
  misoEnvironment = 'dev'
150
156
  } = opts;
151
- logger.log(chalk.blue(`Starting ${appName}...`));
157
+ logger.log(formatProgress(`Starting ${appName}…`));
152
158
 
153
159
  let composeCmdBase;
154
160
  let usedDockerRunFallback = false;
@@ -14,9 +14,36 @@ 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 secretsEnsure = require('../core/secrets-ensure');
17
18
  const secretsEnvWrite = require('../core/secrets-env-write');
18
19
  const { getContainerPort } = require('../utils/port-resolver');
19
20
 
21
+ /**
22
+ * Backfill only secrets needed for `aifabrix run <app>`: kv:// keys from that app's env.template
23
+ * (catalog defaults via placeholderContext). Does not run full `ensureInfraSecrets`.
24
+ *
25
+ * Docker Postgres admin password for compose/db-init comes from **admin-secrets.env** (see
26
+ * `ensureAdminSecrets` / `readAndDecryptAdminSecrets`), not from `postgres-passwordKeyVault` on every run.
27
+ * The kv key is used when **generating** admin-secrets (e.g. first `up-infra`) via `generateAdminSecretsEnv`;
28
+ * we do not duplicate it here when an admin file already exists.
29
+ *
30
+ * @async
31
+ * @param {string} appName - Builder application key
32
+ * @returns {Promise<void>}
33
+ */
34
+ async function ensureRunSecretsForApp(appName) {
35
+ const placeholderContext = secretsEnsure.buildInfraPlaceholderContext({});
36
+ const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
37
+ const templatePath = path.join(pathsUtil.getBuilderPath(appName), 'env.template');
38
+ if (fsSync.existsSync(templatePath)) {
39
+ await secretsEnsure.ensureSecretsFromEnvTemplate(templatePath, {
40
+ placeholderContext,
41
+ preferredFilePath: userSecretsPath,
42
+ useMergedSecretsForMissingKeys: true
43
+ });
44
+ }
45
+ }
46
+
20
47
  /**
21
48
  * Clean applications directory: remove generated docker-compose.yaml and .env.* files.
22
49
  * @param {string|number} developerId - Developer ID
@@ -204,6 +231,7 @@ async function buildMergedRunEnvAndWrite(appName, appConfig, devDir, developerId
204
231
  const ensureAdminSecretsFn = typeof infra.ensureAdminSecrets === 'function'
205
232
  ? infra.ensureAdminSecrets
206
233
  : require('../infrastructure/helpers').ensureAdminSecrets;
234
+ await ensureRunSecretsForApp(appName);
207
235
  await ensureAdminSecretsFn();
208
236
  const adminObj = await adminSecrets.readAndDecryptAdminSecrets();
209
237
  const runEnvKey = scopeOpts && scopeOpts.runEnvKey ? String(scopeOpts.runEnvKey).toLowerCase() : 'dev';
@@ -248,5 +276,6 @@ function assertNoPasswordLiteralsInCompose(composeContent) {
248
276
  module.exports = {
249
277
  cleanApplicationsDir,
250
278
  buildMergedRunEnvAndWrite,
251
- assertNoPasswordLiteralsInCompose
279
+ assertNoPasswordLiteralsInCompose,
280
+ ensureRunSecretsForApp
252
281
  };
@@ -1,4 +1,12 @@
1
- const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
+ const {
2
+ formatSuccessLine,
3
+ formatSuccessParagraph,
4
+ formatProgress,
5
+ formatNextActions,
6
+ headerKeyValue,
7
+ metadata,
8
+ infoLine
9
+ } = require('../utils/cli-test-layout-chalk');
2
10
  /**
3
11
  * AI Fabrix Builder - App Run Helpers
4
12
  *
@@ -28,6 +36,9 @@ const { resolveRunImage } = require('./run-resolve-image');
28
36
  const { startContainer } = require('./run-container-start');
29
37
  const { resolveEnvOutputPath, writeEnvOutputForReload, writeEnvOutputForLocal } = require('../utils/env-copy');
30
38
  const { resolveVersionForApp } = require('../utils/image-version');
39
+ const healthCheckUtil = require('../utils/health-check');
40
+ const { computeTraefikPublicAppUrl } = require('../utils/health-check-url');
41
+ const { isFrontDoorRoutingEnabledInDoc } = require('../utils/url-declarative-vdir-inactive-env');
31
42
 
32
43
  /** Template apps (keycloak, miso-controller, dataplane) - never update application config when running */
33
44
  const TEMPLATE_APP_KEYS = ['keycloak', 'miso-controller', 'dataplane'];
@@ -62,7 +73,7 @@ function checkBuilderDirectory(appName) {
62
73
 
63
74
  /**
64
75
  * Load and validate config file exists
65
- * Uses paths.getBuilderPath so AIFABRIX_BUILDER_DIR (e.g. from up-miso) is respected.
76
+ * Uses paths.getBuilderPath (cwd / material `builder/` only; no `AIFABRIX_BUILDER_DIR` override).
66
77
  * @param {string} appName - Application name
67
78
  * @returns {Object} Application configuration
68
79
  * @throws {Error} If config file not found
@@ -179,7 +190,7 @@ async function checkPrerequisites(appName, appConfig, debug = false, skipInfraCh
179
190
  logger.log(chalk.gray(`[DEBUG] Image name: ${imageName}, tag: ${imageTag}`));
180
191
  }
181
192
 
182
- logger.log(chalk.blue(`Checking if image ${fullImageName} exists...`));
193
+ logger.log(formatProgress(`Checking image ${fullImageName}…`));
183
194
  const imageExists = await checkImageExists(imageName, imageTag, debug);
184
195
  if (!imageExists) {
185
196
  const isTemplateApp = TEMPLATE_APP_KEYS.includes(appName);
@@ -208,12 +219,12 @@ async function checkInfraHealthOrThrow(debug, appConfig) {
208
219
  const requirements = getAppInfraRequirements(appConfig);
209
220
  if (requirements && !requirements.needsPostgres && !requirements.needsRedis) {
210
221
  logger.log(
211
- chalk.blue('Skipping infrastructure check (application does not require database or redis)...')
222
+ infoLine('Skipping Postgres/Redis health check (not required by this application).')
212
223
  );
213
224
  return;
214
225
  }
215
226
 
216
- logger.log(chalk.blue('Checking infrastructure health...'));
227
+ logger.log(formatProgress('Checking infrastructure health'));
217
228
  const infra = require('../infrastructure');
218
229
  const healthOptions =
219
230
  requirements === null
@@ -280,7 +291,7 @@ function calculateComposePort(options, appConfig, developerId) {
280
291
  * @returns {Promise<string>} Path to compose file
281
292
  */
282
293
  async function generateComposeFile(appName, appConfig, composeOptions, devDir) {
283
- logger.log(chalk.blue('Generating Docker Compose configuration...'));
294
+ logger.log(formatProgress('Generating Docker Compose configuration'));
284
295
  const composeContent = await composeGenerator.generateDockerCompose(appName, appConfig, composeOptions);
285
296
  runEnvCompose.assertNoPasswordLiteralsInCompose(composeContent);
286
297
  const tempComposePath = path.join(devDir, 'docker-compose.yaml');
@@ -289,12 +300,15 @@ async function generateComposeFile(appName, appConfig, composeOptions, devDir) {
289
300
  }
290
301
 
291
302
  /**
292
- * Writes .env to envOutputPath when application.yaml build.envOutputPath is set.
303
+ * Writes `build.envOutputPath` after each `aifabrix run`: **local**-flavored env for the host/IDE
304
+ * when not using `--reload`; with **`--reload`**, merges container `.env.run` into the existing file
305
+ * (preserves resolve comments) without appending run-only keys missing from the template.
306
+ *
293
307
  * @async
294
308
  * @param {string} appName - Application name
295
309
  * @param {Object} appConfig - Application configuration
296
- * @param {string} runEnvPath - Path to .env.run
297
- * @param {Object} options - Run options (reload flag)
310
+ * @param {string} runEnvPath - Path to `.env.run`
311
+ * @param {Object} options - Run options (`reload`, `skipEnvOutputPath`)
298
312
  */
299
313
  async function writeEnvOutputIfConfigured(appName, appConfig, runEnvPath, options) {
300
314
  if (options && options.skipEnvOutputPath === true) return;
@@ -308,8 +322,8 @@ async function writeEnvOutputIfConfigured(appName, appConfig, runEnvPath, option
308
322
  if (!fsSync.existsSync(outputDir)) {
309
323
  await fs.mkdir(outputDir, { recursive: true });
310
324
  }
311
- if (options.reload) {
312
- await writeEnvOutputForReload(outputPath, runEnvPath);
325
+ if (options && options.reload === true) {
326
+ await writeEnvOutputForReload(outputPath, runEnvPath, appName);
313
327
  } else {
314
328
  await writeEnvOutputForLocal(appName, outputPath);
315
329
  }
@@ -338,7 +352,7 @@ async function prepareEnvironment(appName, appConfig, options) {
338
352
  );
339
353
 
340
354
  runEnvCompose.cleanApplicationsDir(developerId);
341
- logger.log(chalk.blue('Building merged .env (admin + app secrets)...'));
355
+ logger.log(formatProgress('Building merged .env (admin + app secrets)'));
342
356
  const { runEnvPath, runEnvAdminPath } = await runEnvCompose.buildMergedRunEnvAndWrite(
343
357
  appName,
344
358
  appConfig,
@@ -352,7 +366,8 @@ async function prepareEnvironment(appName, appConfig, options) {
352
366
  envFilePath: runEnvPath,
353
367
  dbInitEnvFilePath: runEnvAdminPath,
354
368
  effectiveEnvironmentScopedResources,
355
- env: runEnvKey
369
+ env: runEnvKey,
370
+ omitAppTraefikLabels: userCfg.traefik === false
356
371
  };
357
372
  composeOptions.port = calculateComposePort(composeOptions, appConfig, developerId);
358
373
  const composePath = await generateComposeFile(appName, appConfig, composeOptions, devDir);
@@ -367,15 +382,39 @@ async function prepareEnvironment(appName, appConfig, options) {
367
382
  * @param {string} appName - Application name
368
383
  * @param {number} port - Application port
369
384
  * @param {Object} appConfig - Application configuration (with developerId property)
385
+ * @param {Object|null} [runScopeOpts] - Optional env-scoped container naming
386
+ * @param {Object} [runOptions] - Run options (traefikEnabled / probeViaTraefik for public URL)
370
387
  */
371
- async function displayRunStatus(appName, port, appConfig, runScopeOpts = null) {
388
+ async function displayRunStatus(appName, port, appConfig, runScopeOpts = null, runOptions = {}) {
372
389
  const containerName = containerHelpers.getContainerName(appName, appConfig.developerId, runScopeOpts);
373
- const healthCheckPath = appConfig?.healthCheck?.path || '/health';
374
- const healthCheckUrl = `http://localhost:${port}${healthCheckPath}`;
390
+ const ro = runOptions && typeof runOptions === 'object' ? runOptions : {};
391
+ const frontDoorOn = isFrontDoorRoutingEnabledInDoc(appConfig);
392
+ const wantsPublic =
393
+ frontDoorOn && Boolean(ro.probeViaTraefik === true || ro.traefikEnabled === true);
394
+
395
+ let primaryAppUrl = `http://localhost:${port}`;
396
+ if (wantsPublic) {
397
+ const pub = await computeTraefikPublicAppUrl(appName, port, appConfig);
398
+ if (pub) primaryAppUrl = pub;
399
+ }
400
+
401
+ const healthTipUrl = await healthCheckUtil.computeHealthCheckUrl(appName, port, appConfig, {
402
+ runOptions: ro
403
+ });
375
404
 
376
- logger.log(formatSuccessParagraph(`App running at http://localhost:${port}`));
377
- logger.log(chalk.blue(`Health check: ${healthCheckUrl}`));
378
- logger.log(chalk.gray(`Container: ${containerName}`));
405
+ logger.log(formatSuccessParagraph(`App running at ${primaryAppUrl}`));
406
+ if (wantsPublic && primaryAppUrl.indexOf('localhost') === -1) {
407
+ logger.log(metadata(`Local direct: http://localhost:${port}`));
408
+ }
409
+ logger.log(headerKeyValue('Health check:', healthTipUrl));
410
+ logger.log(headerKeyValue('Container:', containerName));
411
+ logger.log('');
412
+ logger.log(
413
+ formatNextActions([
414
+ `Validate errors: aifabrix logs ${appName} -l error`,
415
+ `Follow output: aifabrix logs ${appName} -f -t 100`
416
+ ])
417
+ );
379
418
  }
380
419
 
381
420
  module.exports = {