@aifabrix/builder 2.44.4 → 2.44.6

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 (214) hide show
  1. package/.cursor/rules/cli-layout.mdc +1 -1
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/.npmrc.token +1 -1
  4. package/README.md +15 -23
  5. package/integration/hubspot-test/README.md +2 -0
  6. package/integration/hubspot-test/test.js +5 -3
  7. package/jest.projects.js +68 -17
  8. package/lib/api/controller-health.api.js +49 -0
  9. package/lib/api/dimension-values.api.js +82 -0
  10. package/lib/api/dimensions.api.js +114 -0
  11. package/lib/api/external-systems.api.js +1 -0
  12. package/lib/api/integration-clients.api.js +168 -0
  13. package/lib/api/types/dimension-values.types.js +28 -0
  14. package/lib/api/types/dimensions.types.js +31 -0
  15. package/lib/api/types/integration-clients.types.js +45 -0
  16. package/lib/api/types/wizard.types.js +2 -1
  17. package/lib/api/validation-runner.js +46 -25
  18. package/lib/app/deploy-config.js +11 -1
  19. package/lib/app/deploy-status-display.js +3 -3
  20. package/lib/app/deploy.js +36 -14
  21. package/lib/app/display.js +15 -11
  22. package/lib/app/push.js +46 -23
  23. package/lib/app/register.js +1 -1
  24. package/lib/app/restart-display.js +95 -0
  25. package/lib/app/rotate-secret.js +1 -1
  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 +44 -12
  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 +99 -73
  32. package/lib/build/index.js +75 -45
  33. package/lib/cli/doctor-check.js +117 -0
  34. package/lib/cli/index.js +8 -2
  35. package/lib/cli/infra-guided.js +445 -0
  36. package/lib/cli/setup-app.help.js +1 -1
  37. package/lib/cli/setup-app.js +20 -2
  38. package/lib/cli/setup-app.test-commands.js +9 -5
  39. package/lib/cli/setup-auth.js +26 -0
  40. package/lib/cli/setup-dev-path-commands.js +50 -3
  41. package/lib/cli/setup-infra.js +138 -61
  42. package/lib/cli/setup-integration-client.js +182 -0
  43. package/lib/cli/setup-parameters.js +21 -2
  44. package/lib/cli/setup-platform.js +102 -0
  45. package/lib/cli/setup-secrets.js +18 -6
  46. package/lib/cli/setup-utility.js +97 -33
  47. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  48. package/lib/commands/datasource-capability-output.js +29 -0
  49. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  50. package/lib/commands/datasource-capability.js +411 -0
  51. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  52. package/lib/commands/datasource.js +53 -13
  53. package/lib/commands/dev-down.js +3 -3
  54. package/lib/commands/dev-infra-gate.js +32 -0
  55. package/lib/commands/dev-init.js +13 -7
  56. package/lib/commands/dimension-value.js +179 -0
  57. package/lib/commands/dimension.js +330 -0
  58. package/lib/commands/integration-client.js +430 -0
  59. package/lib/commands/login-device.js +65 -30
  60. package/lib/commands/login.js +21 -10
  61. package/lib/commands/parameters-validate.js +78 -13
  62. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  63. package/lib/commands/repair-datasource-keys.js +10 -5
  64. package/lib/commands/repair-datasource.js +19 -7
  65. package/lib/commands/repair-env-template.js +4 -1
  66. package/lib/commands/repair-openapi-sync.js +172 -0
  67. package/lib/commands/repair-persist.js +102 -0
  68. package/lib/commands/repair-rbac-extract.js +27 -0
  69. package/lib/commands/repair-rbac-migrate.js +186 -0
  70. package/lib/commands/repair-rbac.js +225 -19
  71. package/lib/commands/repair-system-alignment.js +246 -0
  72. package/lib/commands/repair-system-permissions.js +168 -0
  73. package/lib/commands/repair.js +120 -354
  74. package/lib/commands/secure.js +1 -1
  75. package/lib/commands/setup-modes.js +455 -0
  76. package/lib/commands/setup-prompts.js +388 -0
  77. package/lib/commands/setup.js +149 -0
  78. package/lib/commands/teardown.js +228 -0
  79. package/lib/commands/test-e2e-external.js +4 -3
  80. package/lib/commands/up-common.js +97 -12
  81. package/lib/commands/up-dataplane.js +33 -11
  82. package/lib/commands/up-miso.js +7 -11
  83. package/lib/commands/upload.js +109 -23
  84. package/lib/commands/wizard-core-helpers.js +14 -11
  85. package/lib/commands/wizard-core.js +58 -15
  86. package/lib/commands/wizard-dataplane.js +2 -2
  87. package/lib/commands/wizard-entity-selection.js +72 -14
  88. package/lib/commands/wizard-headless.js +7 -3
  89. package/lib/commands/wizard-helpers.js +13 -1
  90. package/lib/commands/wizard.js +210 -61
  91. package/lib/constants/infra-compose-service-names.js +40 -0
  92. package/lib/core/env-reader.js +16 -3
  93. package/lib/core/secrets-admin-env.js +101 -0
  94. package/lib/core/secrets-ensure-infra.js +34 -1
  95. package/lib/core/secrets-ensure.js +88 -66
  96. package/lib/core/secrets-env-content.js +432 -0
  97. package/lib/core/secrets-env-write.js +27 -1
  98. package/lib/core/secrets-load.js +248 -0
  99. package/lib/core/secrets-names.js +32 -0
  100. package/lib/core/secrets.js +17 -757
  101. package/lib/datasource/capability/basic-exposure.js +76 -0
  102. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  103. package/lib/datasource/capability/capability-key.js +34 -0
  104. package/lib/datasource/capability/capability-resolve.js +172 -0
  105. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  106. package/lib/datasource/capability/copy-operations.js +348 -0
  107. package/lib/datasource/capability/copy-test-payload.js +139 -0
  108. package/lib/datasource/capability/create-operations.js +235 -0
  109. package/lib/datasource/capability/dimension-operations.js +151 -0
  110. package/lib/datasource/capability/dimension-validate.js +219 -0
  111. package/lib/datasource/capability/json-pointer.js +31 -0
  112. package/lib/datasource/capability/reference-rewrite.js +51 -0
  113. package/lib/datasource/capability/relate-operations.js +325 -0
  114. package/lib/datasource/capability/relate-validate.js +219 -0
  115. package/lib/datasource/capability/remove-operations.js +275 -0
  116. package/lib/datasource/capability/run-capability-copy.js +152 -0
  117. package/lib/datasource/capability/run-capability-diff.js +135 -0
  118. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  119. package/lib/datasource/capability/run-capability-edit.js +377 -0
  120. package/lib/datasource/capability/run-capability-relate.js +193 -0
  121. package/lib/datasource/capability/run-capability-remove.js +105 -0
  122. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  123. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  124. package/lib/datasource/list.js +136 -23
  125. package/lib/datasource/log-viewer.js +2 -4
  126. package/lib/datasource/unified-validation-run.js +51 -16
  127. package/lib/datasource/validate.js +53 -1
  128. package/lib/deployment/deploy-poll-ui.js +60 -0
  129. package/lib/deployment/deployer-status.js +29 -3
  130. package/lib/deployment/deployer.js +48 -30
  131. package/lib/deployment/environment.js +7 -2
  132. package/lib/deployment/poll-interval.js +72 -0
  133. package/lib/deployment/push.js +11 -9
  134. package/lib/external-system/deploy.js +4 -1
  135. package/lib/external-system/download.js +61 -32
  136. package/lib/external-system/sync-deploy-manifest.js +33 -0
  137. package/lib/generator/wizard-prompts.js +7 -1
  138. package/lib/generator/wizard.js +34 -0
  139. package/lib/infrastructure/index.js +49 -19
  140. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  141. package/lib/parameters/infra-kv-discovery.js +29 -4
  142. package/lib/parameters/infra-parameter-catalog.js +6 -3
  143. package/lib/parameters/infra-parameter-validate.js +67 -19
  144. package/lib/resolvers/datasource-resolver.js +53 -0
  145. package/lib/resolvers/dimension-file.js +52 -0
  146. package/lib/resolvers/manifest-resolver.js +133 -0
  147. package/lib/schema/external-datasource.schema.json +183 -53
  148. package/lib/schema/external-system.schema.json +23 -10
  149. package/lib/schema/infra.parameter.yaml +26 -11
  150. package/lib/schema/wizard-config.schema.json +2 -2
  151. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  152. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  153. package/lib/utils/app-run-containers.js +2 -2
  154. package/lib/utils/bash-secret-env.js +59 -0
  155. package/lib/utils/cli-secrets-error-format.js +78 -0
  156. package/lib/utils/cli-test-layout-chalk.js +31 -9
  157. package/lib/utils/cli-utils.js +4 -36
  158. package/lib/utils/datasource-test-run-display.js +8 -0
  159. package/lib/utils/dev-hosts-helper.js +3 -2
  160. package/lib/utils/dev-init-ssh-merge.js +2 -1
  161. package/lib/utils/docker-build.js +17 -9
  162. package/lib/utils/docker-reload-mount.js +127 -0
  163. package/lib/utils/external-readme.js +117 -4
  164. package/lib/utils/external-system-local-test-tty.js +3 -2
  165. package/lib/utils/external-system-readiness-core.js +45 -12
  166. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  167. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  168. package/lib/utils/external-system-readiness-display.js +10 -1
  169. package/lib/utils/file-upload.js +40 -3
  170. package/lib/utils/health-check-db-init.js +107 -0
  171. package/lib/utils/health-check-public-warn.js +69 -0
  172. package/lib/utils/health-check-url.js +19 -4
  173. package/lib/utils/health-check.js +135 -105
  174. package/lib/utils/help-builder.js +5 -1
  175. package/lib/utils/image-name.js +34 -7
  176. package/lib/utils/integration-file-backup.js +74 -0
  177. package/lib/utils/mutagen-install.js +30 -3
  178. package/lib/utils/paths.js +108 -25
  179. package/lib/utils/postgres-wipe.js +212 -0
  180. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  181. package/lib/utils/remote-dev-auth.js +21 -5
  182. package/lib/utils/remote-docker-env.js +9 -1
  183. package/lib/utils/remote-secrets-loader.js +42 -3
  184. package/lib/utils/resolve-docker-image-ref.js +9 -3
  185. package/lib/utils/secrets-ancestor-paths.js +47 -0
  186. package/lib/utils/secrets-helpers.js +17 -10
  187. package/lib/utils/secrets-kv-refs.js +42 -0
  188. package/lib/utils/secrets-kv-scope.js +19 -2
  189. package/lib/utils/secrets-materialize-local.js +134 -0
  190. package/lib/utils/secrets-path.js +24 -10
  191. package/lib/utils/secrets-utils.js +2 -2
  192. package/lib/utils/system-builder-root.js +34 -0
  193. package/lib/utils/url-declarative-resolve-build.js +6 -1
  194. package/lib/utils/url-declarative-runtime-base-path.js +32 -0
  195. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  196. package/lib/utils/urls-local-registry.js +73 -20
  197. package/lib/utils/validation-poll-ui.js +81 -0
  198. package/lib/utils/validation-run-poll.js +29 -5
  199. package/lib/utils/with-muted-logger.js +53 -0
  200. package/package.json +1 -1
  201. package/templates/applications/dataplane/application.yaml +1 -1
  202. package/templates/applications/dataplane/rbac.yaml +10 -10
  203. package/templates/applications/keycloak/env.template +8 -6
  204. package/templates/applications/miso-controller/application.yaml +7 -0
  205. package/templates/applications/miso-controller/env.template +7 -7
  206. package/templates/applications/miso-controller/rbac.yaml +9 -9
  207. package/templates/external-system/README.md.hbs +89 -102
  208. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  209. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  210. package/.nyc_output/processinfo/index.json +0 -1
  211. package/lib/api/service-users.api.js +0 -150
  212. package/lib/api/types/service-users.types.js +0 -65
  213. package/lib/cli/setup-service-user.js +0 -187
  214. package/lib/commands/service-user.js +0 -429
package/lib/app/run.js CHANGED
@@ -10,15 +10,23 @@
10
10
  */
11
11
 
12
12
  const chalk = require('chalk');
13
+ const {
14
+ formatWarningLine,
15
+ sectionTitle,
16
+ headerKeyValue,
17
+ metadata,
18
+ formatNextActions
19
+ } = require('../utils/cli-test-layout-chalk');
13
20
  const config = require('../core/config');
14
21
  const logger = require('../utils/logger');
15
22
  const pathsUtil = require('../utils/paths');
16
23
  const { checkPortAvailable, waitForHealthCheck } = require('../utils/health-check');
17
24
  const composeGenerator = require('../utils/compose-generator');
18
25
  const containerHelpers = require('../utils/app-run-containers');
19
- const mutagen = require('../utils/mutagen');
20
26
  // Helper functions extracted to reduce file size and complexity
21
27
  const helpers = require('./run-helpers');
28
+ const { ensureReloadSync, logReloadDevSummary } = require('./run-reload-sync');
29
+ const { logRestartDevMountSummary } = require('./restart-display');
22
30
  const { execWithDockerEnv } = require('../utils/docker-exec');
23
31
 
24
32
  /**
@@ -73,8 +81,16 @@ async function validateAppForRun(appName, _debug) {
73
81
  try {
74
82
  const { isExternal, baseDir } = await detectAppType(appName);
75
83
  if (baseDir !== 'builder' || isExternal) {
76
- logger.log(chalk.yellow('⚠ External systems don\'t run as Docker containers.'));
77
- logger.log(chalk.blue('Use "aifabrix build" to deploy to dataplane, then test via OpenAPI endpoints.'));
84
+ logger.log('');
85
+ logger.log(sectionTitle('Run'));
86
+ logger.log(headerKeyValue('Application:', appName));
87
+ logger.log(
88
+ formatWarningLine('External integrations are not started as local Docker containers.')
89
+ );
90
+ logger.log(
91
+ metadata('Build and deploy the integration, then use its APIs from your environment.')
92
+ );
93
+ logger.log(formatNextActions([`aifabrix build ${appName}`]));
78
94
  return false;
79
95
  }
80
96
  } catch (error) {
@@ -114,7 +130,7 @@ async function checkAndStopContainer(appName, appConfig, options, debug) {
114
130
  }
115
131
 
116
132
  const containerName = containerHelpers.getContainerName(appName, developerId, scopeOpts);
117
- logger.log(chalk.yellow(`Container ${containerName} is already running`));
133
+ logger.log(formatWarningLine(`Container ${containerName} is already running`));
118
134
  await helpers.stopAndRemoveContainer(appName, developerId, debug, scopeOpts);
119
135
  }
120
136
 
@@ -149,48 +165,6 @@ async function calculateHostPort(appConfig, options, debug) {
149
165
  return hostPort;
150
166
  }
151
167
 
152
- /**
153
- * When run --reload in dev with remote: ensure Mutagen sync session; return remote path for compose mount.
154
- * Uses codePath (resolved build.context) as Mutagen local path so one config field drives both local and remote.
155
- * When Docker endpoint, remote-server, or sync-ssh-host is localhost/127.0.0.1, returns null (no sync; use local path).
156
- *
157
- * @param {string} appName - Application name
158
- * @param {string} developerId - Developer ID
159
- * @param {boolean} debug - Debug flag
160
- * @param {string} codePath - Resolved build.context (absolute path to app code)
161
- * @param {string} [remoteSyncPath] - Optional relative path under user-mutagen-folder (from build.remoteSyncPath); when unset, defaults to dev/<appKey>
162
- * @returns {Promise<string|null>} Remote path for -v mount or null if not remote/reload or already on server (localhost)
163
- * @throws {Error} If --reload but remote not configured, or Mutagen install fails
164
- */
165
- async function ensureReloadSync(appName, developerId, debug, codePath, remoteSyncPath) {
166
- const endpoint = await config.getDockerEndpoint();
167
- const serverUrl = await config.getRemoteServer();
168
- if (!endpoint && !serverUrl) return null;
169
- const syncSshHost = await config.getSyncSshHost();
170
- if (isLocalhostEndpoint(endpoint) || isLocalhostEndpoint(serverUrl) || isLocalhostHost(syncSshHost || '')) {
171
- if (debug) logger.log(chalk.gray('[DEBUG] Docker/remote/sync host is localhost; skipping Mutagen, using local path'));
172
- return null;
173
- }
174
- const [userMutagenFolder, syncSshUser] = await Promise.all([
175
- config.getUserMutagenFolder(),
176
- config.getSyncSshUser()
177
- ]);
178
- if (!userMutagenFolder || !syncSshUser || !syncSshHost) {
179
- throw new Error(
180
- 'run --reload requires remote server sync settings. Run "aifabrix dev init" or set user-mutagen-folder, sync-ssh-user, sync-ssh-host in config.'
181
- );
182
- }
183
- const mutagenPath = await mutagen.ensureMutagenPath(logger.log);
184
- const remotePath = mutagen.getRemotePath(userMutagenFolder, appName, remoteSyncPath);
185
- const sshUrl = mutagen.getSyncSshUrl(syncSshUser, syncSshHost, remotePath);
186
- const sessionName = mutagen.getSessionName(developerId, appName);
187
- const localPath = (codePath && typeof codePath === 'string') ? codePath : pathsUtil.getBuilderPath(appName);
188
- if (debug) logger.log(chalk.gray(`[DEBUG] Mutagen sync: ${sessionName} ${localPath} <-> ${sshUrl}`));
189
- logger.log(chalk.blue('Ensuring Mutagen sync session...'));
190
- await mutagen.ensureSyncSession(mutagenPath, sessionName, localPath, sshUrl);
191
- return remotePath;
192
- }
193
-
194
168
  /**
195
169
  * Load and configure application
196
170
  * @async
@@ -238,12 +212,21 @@ async function startAppContainer(appName, tempComposePath, hostPort, appConfig,
238
212
  devMountPath,
239
213
  misoEnvironment
240
214
  });
241
- await helpers.displayRunStatus(appName, hostPort, appConfig, opts.runScopeOpts || null);
215
+ await helpers.displayRunStatus(
216
+ appName,
217
+ hostPort,
218
+ appConfig,
219
+ opts.runScopeOpts || null,
220
+ runOptions || {}
221
+ );
242
222
  } catch (error) {
243
- logger.log(chalk.yellow(`\n⚠️ Compose file preserved at: ${tempComposePath}`));
244
- logger.log(chalk.yellow(' Review the file to debug issues'));
223
+ logger.log('');
224
+ logger.log(formatWarningLine(`Compose file preserved at ${tempComposePath}`));
225
+ logger.log(metadata(' Review the compose file, fix the issue, then run again.'));
245
226
  if (runEnvPath || runEnvAdminPath) {
246
- logger.log(chalk.yellow(' Run .env file(s) (contain secrets) were not deleted; remove them manually if desired.'));
227
+ logger.log(
228
+ metadata(' Run .env file(s) contain secrets and were not deleted; remove manually if needed.')
229
+ );
247
230
  }
248
231
  if (debug) {
249
232
  logger.log(chalk.gray(`[DEBUG] Error during container start: ${error.message}`));
@@ -271,30 +254,46 @@ async function resolveRunOptions(appName, appConfig, options, envKey, debug, eff
271
254
  const codePath = pathsUtil.resolveBuildContext(builderPath, appConfig.build?.context || '.');
272
255
  if (options.reload && envKey === 'dev') {
273
256
  const remoteSyncPath = appConfig.build?.remoteSyncPath;
274
- const remotePath = await ensureReloadSync(appName, appConfig.developerId, debug, codePath, remoteSyncPath);
275
- runOptions.devMountPath = remotePath || codePath;
257
+ const reloadSummary = await ensureReloadSync(appName, appConfig.developerId, debug, codePath, remoteSyncPath);
258
+ runOptions.reloadSyncSummary = reloadSummary;
259
+ runOptions.devMountPath =
260
+ reloadSummary.transport === 'mutagen' ? reloadSummary.remotePath : codePath;
276
261
  }
277
262
  return runOptions;
278
263
  }
279
264
 
280
265
  /**
281
- * Prepare run: validate app, load config, check prereqs, stop existing container, resolve port and reload, prepare env.
282
- * @param {string} appName - Application name
283
- * @param {Object} options - Run options
284
- * @param {boolean} debug - Debug flag
285
- * @returns {Promise<{ appConfig: Object, tempComposePath: string, hostPort: number }|null>} Prepared run context or null if should not continue
266
+ * When Traefik is enabled in user config, pass through for health checks (DNS + localhost order).
267
+ * @param {Object} runOptions
268
+ * @param {Object} userCfg
286
269
  */
287
- async function prepareAppRun(appName, options, debug) {
288
- const envKey = (options.env || 'dev').toLowerCase();
270
+ function applyTraefikFlagToRunOptions(runOptions, userCfg) {
271
+ if (userCfg && userCfg.traefik === true) {
272
+ runOptions.traefikEnabled = true;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * @param {string} envKey
278
+ * @throws {Error} If env is not dev, tst, or pro
279
+ */
280
+ function assertValidRunEnvKey(envKey) {
289
281
  if (envKey !== 'dev' && envKey !== 'tst' && envKey !== 'pro') {
290
282
  throw new Error('--env must be dev, tst, or pro');
291
283
  }
292
- const shouldContinue = await validateAppForRun(appName, debug);
293
- if (!shouldContinue) {
294
- return null;
295
- }
296
- const appConfig = await loadAndConfigureApp(appName, debug);
297
- const userCfg = await config.getConfig();
284
+ }
285
+
286
+ /**
287
+ * Image resolution, prereqs, port, compose env — after app config and user config are loaded.
288
+ * @param {string} appName
289
+ * @param {Object} appConfig
290
+ * @param {Object} options - Original run options
291
+ * @param {string} envKey
292
+ * @param {Object} userCfg - getConfig() result
293
+ * @param {boolean} debug
294
+ * @returns {Promise<Object>} Prepared run context (same shape as prepareAppRun return)
295
+ */
296
+ async function computeRunPreparationCore(appName, appConfig, options, envKey, userCfg, debug) {
298
297
  const { computeEffectiveEnvironmentScopedResources } = require('../utils/environment-scoped-resources');
299
298
  const effectiveEnvironmentScopedResources = computeEffectiveEnvironmentScopedResources(
300
299
  Boolean(userCfg.useEnvironmentScopedResources),
@@ -304,17 +303,24 @@ async function prepareAppRun(appName, options, debug) {
304
303
  const runScopeOpts = effectiveEnvironmentScopedResources
305
304
  ? { effectiveEnvironmentScopedResources: true, env: envKey }
306
305
  : null;
307
- await helpers.checkPrerequisites(appName, appConfig, debug, options.skipInfraCheck === true, options);
308
- await checkAndStopContainer(appName, appConfig, options, debug);
309
- const hostPort = await calculateHostPort(appConfig, options, debug);
306
+ const { resolveRunImageWithLocalFallback } = require('./run-resolve-image');
307
+ const resolvedRef = await resolveRunImageWithLocalFallback(appName, appConfig, options);
308
+ const effectiveRunOptions = {
309
+ ...options,
310
+ image: `${resolvedRef.imageName}:${resolvedRef.imageTag}`
311
+ };
312
+ await helpers.checkPrerequisites(appName, appConfig, debug, effectiveRunOptions.skipInfraCheck === true, effectiveRunOptions);
313
+ await checkAndStopContainer(appName, appConfig, effectiveRunOptions, debug);
314
+ const hostPort = await calculateHostPort(appConfig, effectiveRunOptions, debug);
310
315
  const runOptions = await resolveRunOptions(
311
316
  appName,
312
317
  appConfig,
313
- options,
318
+ effectiveRunOptions,
314
319
  envKey,
315
320
  debug,
316
321
  effectiveEnvironmentScopedResources
317
322
  );
323
+ applyTraefikFlagToRunOptions(runOptions, userCfg);
318
324
  const { composePath: tempComposePath, runEnvPath, runEnvAdminPath } = await helpers.prepareEnvironment(appName, appConfig, runOptions);
319
325
  const result = {
320
326
  appConfig,
@@ -329,6 +335,28 @@ async function prepareAppRun(appName, options, debug) {
329
335
  return result;
330
336
  }
331
337
 
338
+ /**
339
+ * Prepare run: validate app, load config, check prereqs, stop existing container, resolve port and reload, prepare env.
340
+ * @param {string} appName - Application name
341
+ * @param {Object} options - Run options
342
+ * @param {boolean} debug - Debug flag
343
+ * @returns {Promise<{ appConfig: Object, tempComposePath: string, hostPort: number }|null>} Prepared run context or null if should not continue
344
+ */
345
+ async function prepareAppRun(appName, options, debug) {
346
+ const envKey = (options.env || 'dev').toLowerCase();
347
+ assertValidRunEnvKey(envKey);
348
+ const shouldContinue = await validateAppForRun(appName, debug);
349
+ if (!shouldContinue) {
350
+ return null;
351
+ }
352
+ logger.log('');
353
+ logger.log(sectionTitle('Run'));
354
+ logger.log(headerKeyValue('Application:', appName));
355
+ const appConfig = await loadAndConfigureApp(appName, debug);
356
+ const userCfg = await config.getConfig();
357
+ return computeRunPreparationCore(appName, appConfig, options, envKey, userCfg, debug);
358
+ }
359
+
332
360
  /**
333
361
  * Runs the application locally using Docker
334
362
  * Starts container with proper port mapping and environment
@@ -360,10 +388,7 @@ async function runApp(appName, options = {}) {
360
388
  if (debug) {
361
389
  logger.log(chalk.gray(`[DEBUG] Compose file generated: ${prepared.tempComposePath}`));
362
390
  }
363
- if (options.reload && prepared.devMountPath) {
364
- logger.log(chalk.gray('With --reload: workspace mounted from host at /app (container runs as your user for write access).'));
365
- logger.log(chalk.gray(` Host path: ${prepared.devMountPath}`));
366
- }
391
+ logReloadDevSummary(Boolean(options.reload), prepared.mergedRunOptions.reloadSyncSummary);
367
392
  await startAppContainer(appName, prepared.tempComposePath, prepared.hostPort, prepared.appConfig, {
368
393
  debug,
369
394
  runEnvPath: prepared.runEnvPath,
@@ -401,6 +426,7 @@ async function restartApp(appName) {
401
426
  const containerName = containerHelpers.getContainerName(appName, developerId);
402
427
  try {
403
428
  await execWithDockerEnv(`docker restart ${containerName}`);
429
+ await logRestartDevMountSummary(containerName);
404
430
  } catch (error) {
405
431
  const msg = (error.stderr || error.stdout || error.message || '').toLowerCase();
406
432
  if (msg.includes('no such container') || msg.includes('is not running')) {
@@ -1,4 +1,13 @@
1
- const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
+ const {
2
+ formatSuccessLine,
3
+ formatSuccessParagraph,
4
+ formatProgress,
5
+ formatWarningLine,
6
+ sectionTitle,
7
+ headerKeyValue,
8
+ metadata,
9
+ formatNextActions
10
+ } = require('../utils/cli-test-layout-chalk');
2
11
  /**
3
12
  * AI Fabrix Builder Build Functions
4
13
  *
@@ -18,7 +27,6 @@ const paths = require('../utils/paths');
18
27
  const { detectAppType, getProjectRoot } = require('../utils/paths');
19
28
  const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
20
29
  const { loadConfigFile } = require('../utils/config-format');
21
- const chalk = require('chalk');
22
30
  const secrets = require('../core/secrets');
23
31
  const config = require('../core/config');
24
32
  const logger = require('../utils/logger');
@@ -28,6 +36,7 @@ const buildCopy = require('../utils/build-copy');
28
36
  const { buildDevImageName } = require('../utils/image-name');
29
37
  const buildHelpers = require('../utils/build-helpers');
30
38
  const secretsEnvWrite = require('../core/secrets-env-write');
39
+ const { ensureRunSecretsForApp } = require('../app/run-env-compose');
31
40
 
32
41
  /**
33
42
  * Loads application config for an application
@@ -192,7 +201,7 @@ async function postBuildTasks(appName, buildConfig) {
192
201
  // Note: processEnvVariables is already called by generateEnvFile to generate local .env
193
202
  // at the envOutputPath, so we don't need to manually copy the docker .env file
194
203
  } catch (error) {
195
- logger.log(chalk.yellow(`⚠ Warning: Could not generate .env file: ${error.message}`));
204
+ logger.log(formatWarningLine(`Could not generate .env file: ${error.message}`));
196
205
  }
197
206
  }
198
207
 
@@ -205,8 +214,11 @@ async function postBuildTasks(appName, buildConfig) {
205
214
  async function checkExternalAppType(appName) {
206
215
  const variables = await loadVariablesYaml(appName);
207
216
  if (variables.app && variables.app.type === 'external') {
208
- logger.log(chalk.blue(`External system: ${appName}`));
209
- logger.log(chalk.gray('To regenerate deployment JSON, run: aifabrix json ' + appName));
217
+ logger.log('');
218
+ logger.log(sectionTitle('External integration'));
219
+ logger.log(headerKeyValue('Application:', appName));
220
+ logger.log(metadata('No Docker image is built for external systems.'));
221
+ logger.log(formatNextActions([`aifabrix json ${appName}`]));
210
222
  return true;
211
223
  }
212
224
  return false;
@@ -269,9 +281,9 @@ async function prepareDevDirectory(appName, buildConfig, options) {
269
281
  const developerId = await config.getDeveloperId();
270
282
  const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
271
283
  const directoryName = idNum === 0 ? 'applications' : `dev-${developerId}`;
272
- logger.log(chalk.blue(`Copying files to developer-specific directory (${directoryName})...`));
284
+ logger.log(formatProgress(`Copying workspace (${directoryName})…`));
273
285
  const devDir = await buildCopy.copyBuilderToDevDirectory(appName, developerId);
274
- logger.log(formatSuccessLine(`Files copied to: ${devDir}`));
286
+ logger.log(formatSuccessLine(`Workspace ready: ${devDir}`));
275
287
 
276
288
  const { config: appConfig, imageName } = await buildHelpers.loadAndValidateConfig(appName);
277
289
  const effectiveImageName = buildDevImageName(imageName, developerId);
@@ -297,8 +309,11 @@ function prepareBuildContext(buildConfig, devDir) {
297
309
  // Check if context is using old format (../appName) - these are incompatible with dev directory structure
298
310
  if (buildConfig.context && buildConfig.context.startsWith('../') && buildConfig.context !== '../..') {
299
311
  // Old format detected - always use devDir instead
300
- logger.log(chalk.yellow(`⚠ Warning: Build context uses old format: ${buildConfig.context}`));
301
- logger.log(chalk.yellow(` Using dev directory instead: ${devDir}`));
312
+ logger.log(
313
+ formatWarningLine(
314
+ `Build context uses legacy path (${buildConfig.context}). Using dev workspace: ${devDir}`
315
+ )
316
+ );
302
317
  contextPath = devDir;
303
318
  } else if (buildConfig.context && buildConfig.context !== '../..') {
304
319
  // Resolve relative context path from dev directory
@@ -370,6 +385,53 @@ async function handleDockerfileGeneration(appName, params, options, buildHelpers
370
385
  }, generateDockerfile);
371
386
  }
372
387
 
388
+ /**
389
+ * Resolve Docker build-args from resolved env map and merged BASH_* secrets.
390
+ * @param {string} appName
391
+ * @returns {Promise<Object.<string, string>>}
392
+ */
393
+ async function resolveDockerBuildArgsForApp(appName) {
394
+ const envMap = await secretsEnvWrite.resolveAndGetEnvMap(appName, { environment: 'docker' });
395
+ const { getBashPrefixedProcessEnvOverlay } = require('../utils/bash-secret-env');
396
+ const bashOverlay = await getBashPrefixedProcessEnvOverlay(null, appName);
397
+ const buildArgs = {};
398
+ if (envMap.NPM_TOKEN) buildArgs.NPM_TOKEN = envMap.NPM_TOKEN;
399
+ if (envMap.PYPI_TOKEN) buildArgs.PYPI_TOKEN = envMap.PYPI_TOKEN;
400
+ for (const [k, v] of Object.entries(bashOverlay)) {
401
+ if (v && buildArgs[k] === undefined) buildArgs[k] = v;
402
+ }
403
+ return buildArgs;
404
+ }
405
+
406
+ /**
407
+ * Standard Docker build path after CLI header is printed (non-external apps).
408
+ * @param {string} appName
409
+ * @param {Object} options
410
+ * @returns {Promise<string>} Built image ref e.g. name:tag
411
+ */
412
+ async function runStandardDockerBuild(appName, options) {
413
+ const { buildConfig } = await buildHelpers.loadAndValidateConfig(appName);
414
+ const { devDir, effectiveImageName, imageName, appConfig } = await prepareDevDirectory(appName, buildConfig, options);
415
+ const contextPath = prepareBuildContext(buildConfig, devDir);
416
+ const dockerfilePath = await handleDockerfileGeneration(appName, {
417
+ devDir,
418
+ buildConfig,
419
+ contextPath,
420
+ appConfig
421
+ }, options, buildHelpers);
422
+ await ensureRunSecretsForApp(appName);
423
+ const buildArgs = await resolveDockerBuildArgsForApp(appName);
424
+ const buildOptions = { ...options, buildArgs };
425
+ const tag = options.tag || 'latest';
426
+ if (typeof tag === 'string' && tag.includes(',')) {
427
+ throw new Error('Use a single image tag per build (comma-separated multiple tags are not supported).');
428
+ }
429
+ await dockerBuild.executeDockerBuildWithTag(effectiveImageName, imageName, dockerfilePath, contextPath, tag, buildOptions);
430
+ await postBuildTasks(appName, buildConfig);
431
+ logger.log(formatSuccessParagraph('Build completed successfully!'));
432
+ return `${effectiveImageName}:${tag}`;
433
+ }
434
+
373
435
  /**
374
436
  * Builds a container image for the specified application
375
437
  * Auto-detects runtime and generates Dockerfile if needed
@@ -395,42 +457,10 @@ async function buildApp(appName, options = {}) {
395
457
  }
396
458
 
397
459
  try {
398
- logger.log(chalk.blue(`\n🔨 Building application: ${appName}`));
399
-
400
- // 1. Load and validate configuration
401
- const { buildConfig } = await buildHelpers.loadAndValidateConfig(appName);
402
-
403
- // 2. Prepare dev directory and copy files
404
- const { devDir, effectiveImageName, imageName, appConfig } = await prepareDevDirectory(appName, buildConfig, options);
405
-
406
- // 3. Prepare build context
407
- const contextPath = prepareBuildContext(buildConfig, devDir);
408
-
409
- // 4. Handle Dockerfile generation
410
- const dockerfilePath = await handleDockerfileGeneration(appName, {
411
- devDir,
412
- buildConfig,
413
- contextPath,
414
- appConfig
415
- }, options, buildHelpers);
416
-
417
- // 5. Resolve NPM_TOKEN/PYPI_TOKEN for private registries and pass as build-args
418
- const envMap = await secretsEnvWrite.resolveAndGetEnvMap(appName, { environment: 'docker' });
419
- const buildArgs = {};
420
- if (envMap.NPM_TOKEN) buildArgs.NPM_TOKEN = envMap.NPM_TOKEN;
421
- if (envMap.PYPI_TOKEN) buildArgs.PYPI_TOKEN = envMap.PYPI_TOKEN;
422
- const buildOptions = { ...options, buildArgs };
423
-
424
- // 6. Execute Docker build
425
- const tag = options.tag || 'latest';
426
- await dockerBuild.executeDockerBuildWithTag(effectiveImageName, imageName, dockerfilePath, contextPath, tag, buildOptions);
427
-
428
- // 7. Post-build tasks
429
- await postBuildTasks(appName, buildConfig);
430
-
431
- logger.log(formatSuccessParagraph('Build completed successfully!'));
432
- return `${imageName}:${tag}`;
433
-
460
+ logger.log('');
461
+ logger.log(sectionTitle('Build'));
462
+ logger.log(headerKeyValue('Application:', appName));
463
+ return await runStandardDockerBuild(appName, options);
434
464
  } catch (error) {
435
465
  throw new Error(`Build failed: ${error.message}`);
436
466
  }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @fileoverview `aifabrix doctor` action: environment validation + optional infra health.
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const chalk = require('chalk');
8
+ const {
9
+ sectionTitle,
10
+ formatBulletSection,
11
+ formatDatasourceListRow,
12
+ formatWarningLine,
13
+ metadata
14
+ } = require('../utils/cli-test-layout-chalk');
15
+ const validator = require('../validation/validator');
16
+ const config = require('../core/config');
17
+ const infra = require('../infrastructure');
18
+ const logger = require('../utils/logger');
19
+
20
+ /**
21
+ * @param {string} label
22
+ * @param {'ok'|'warning'|'fail'} variant
23
+ * @param {{ ok?: string, warn?: string, fail?: string }} text
24
+ * @returns {string}
25
+ */
26
+ function formatDoctorEnvSummaryLine(label, variant, text) {
27
+ const base = chalk.gray(`${label}:`);
28
+ if (variant === 'ok') return `${base} ${chalk.green('✔')} ${chalk.white(text.ok || '')}`;
29
+ if (variant === 'warning') return `${base} ${chalk.yellow('⚠')} ${chalk.white(text.warn || '')}`;
30
+ return `${base} ${chalk.red('✖')} ${chalk.white(text.fail || '')}`;
31
+ }
32
+
33
+ /**
34
+ * @param {string} statusRaw
35
+ * @returns {'ok'|'warn'|'fail'}
36
+ */
37
+ function doctorInfraRowAggregate(statusRaw) {
38
+ const s = String(statusRaw).trim().toLowerCase();
39
+ if (s === 'healthy') return 'ok';
40
+ if (s === 'unknown') return 'warn';
41
+ return 'fail';
42
+ }
43
+
44
+ /**
45
+ * @param {Object} result - `validator.checkEnvironment()` payload
46
+ */
47
+ function logDoctorEnvironmentSection(result) {
48
+ logger.log('');
49
+ logger.log(sectionTitle('Environment check'));
50
+ logger.log('');
51
+ logger.log(
52
+ formatDoctorEnvSummaryLine(
53
+ 'Docker',
54
+ result.docker === 'ok' ? 'ok' : 'fail',
55
+ { ok: 'Running', fail: 'Not available' }
56
+ )
57
+ );
58
+ logger.log(
59
+ formatDoctorEnvSummaryLine(
60
+ 'Ports',
61
+ result.ports === 'ok' ? 'ok' : 'warning',
62
+ { ok: 'Available', warn: 'Some ports in use' }
63
+ )
64
+ );
65
+ logger.log(
66
+ formatDoctorEnvSummaryLine(
67
+ 'Secrets',
68
+ result.secrets === 'ok' ? 'ok' : 'fail',
69
+ { ok: 'Configured', fail: 'Missing' }
70
+ )
71
+ );
72
+ if (result.recommendations.length > 0) {
73
+ logger.log('');
74
+ logger.log(formatBulletSection('Recommendations:', result.recommendations));
75
+ }
76
+ }
77
+
78
+ /**
79
+ * @param {Object} result - `validator.checkEnvironment()` payload
80
+ */
81
+ async function logDoctorInfraHealthSection(result) {
82
+ if (result.docker !== 'ok') {
83
+ logger.log('');
84
+ logger.log(metadata('Infrastructure health skipped (Docker not available).'));
85
+ return;
86
+ }
87
+ try {
88
+ const cfg = await config.getConfig();
89
+ const health = await infra.checkInfraHealth(null, {
90
+ pgadmin: cfg.pgadmin !== false,
91
+ redisCommander: cfg.redisCommander !== false,
92
+ traefik: !!cfg.traefik
93
+ });
94
+ logger.log('');
95
+ logger.log(sectionTitle('Infrastructure health'));
96
+ Object.entries(health).forEach(([service, status]) => {
97
+ const agg = doctorInfraRowAggregate(status);
98
+ logger.log(formatDatasourceListRow(agg, `${service}: ${status}`, null));
99
+ });
100
+ } catch (_err) {
101
+ logger.log('');
102
+ logger.log(formatWarningLine('Infrastructure is not running or health could not be read.'));
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Runs the doctor checks and prints the TTY summary.
108
+ * @returns {Promise<void>}
109
+ */
110
+ async function runDoctorCheck() {
111
+ const result = await validator.checkEnvironment();
112
+ logDoctorEnvironmentSection(result);
113
+ await logDoctorInfraHealthSection(result);
114
+ logger.log('');
115
+ }
116
+
117
+ module.exports = { runDoctorCheck };
package/lib/cli/index.js CHANGED
@@ -21,8 +21,11 @@ const { setupParametersCommands } = require('./setup-parameters');
21
21
  const { setupExternalSystemCommands } = require('./setup-external-system');
22
22
  const { setupAppCommands: setupAppManagementCommands } = require('../commands/app');
23
23
  const { setupDatasourceCommands } = require('../commands/datasource');
24
+ const { setupDimensionCommands } = require('../commands/dimension');
25
+ const { setupDimensionValueCommands } = require('../commands/dimension-value');
24
26
  const { setupCredentialDeploymentCommands } = require('./setup-credential-deployment');
25
- const { setupServiceUserCommands } = require('./setup-service-user');
27
+ const { setupIntegrationClientCommands } = require('./setup-integration-client');
28
+ const { setupPlatformCommands } = require('./setup-platform');
26
29
 
27
30
  /**
28
31
  * Sets up all CLI commands on the Commander program instance
@@ -35,13 +38,16 @@ function setupCommands(program) {
35
38
  setupEnvironmentCommands(program);
36
39
  setupAppManagementCommands(program);
37
40
  setupDatasourceCommands(program);
41
+ setupDimensionCommands(program);
42
+ setupDimensionValueCommands(program);
38
43
  setupUtilityCommands(program);
39
44
  setupCredentialDeploymentCommands(program);
40
- setupServiceUserCommands(program);
45
+ setupIntegrationClientCommands(program);
41
46
  setupExternalSystemCommands(program);
42
47
  setupDevCommands(program);
43
48
  setupSecretsCommands(program);
44
49
  setupParametersCommands(program);
50
+ setupPlatformCommands(program);
45
51
  }
46
52
 
47
53
  module.exports = {