@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
@@ -1,4 +1,4 @@
1
- const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
1
+ const { formatSuccessLine, formatProgress, metadata } = require('../utils/cli-test-layout-chalk');
2
2
  /**
3
3
  * AI Fabrix Builder Deployment Module
4
4
  *
@@ -22,6 +22,8 @@ const {
22
22
  convertToPipelineAuthConfig,
23
23
  processDeploymentStatusResponse
24
24
  } = require('./deployer-status');
25
+ const { resolvePollIntervalFromController } = require('./poll-interval');
26
+ const { createDeployPollHandlers } = require('./deploy-poll-ui');
25
27
 
26
28
  /**
27
29
  * For external systems, send full manifest (application + inline system + full dataSources).
@@ -47,7 +49,10 @@ function transformExternalManifestForPipeline(manifest) {
47
49
  * @returns {Promise<Object>} Object with validationData, pipelineAuthConfig, and useBearerOnly
48
50
  */
49
51
  async function buildValidationData(manifest, validatedEnvKey, authConfig, options) {
50
- const repositoryUrl = options.repositoryUrl || `https://github.com/aifabrix/${manifest.key}`;
52
+ const repositoryUrl =
53
+ options.repositoryUrl ||
54
+ manifest?.repository?.repositoryUrl ||
55
+ `https://github.com/aifabrix/${manifest.key}`;
51
56
 
52
57
  if (authConfig.type === 'bearer' && authConfig.token && !authConfig.clientId) {
53
58
  const pipelineAuthConfig = { type: 'bearer', token: authConfig.token };
@@ -295,10 +300,19 @@ async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, authCon
295
300
  const validatedEnvKey = validateEnvironmentKey(envKey);
296
301
  const pipelineAuthConfig = convertToPipelineAuthConfig(authConfig);
297
302
 
303
+ const onPollProgress = typeof options.onPollProgress === 'function' ? options.onPollProgress : null;
304
+
298
305
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
299
306
  try {
300
307
  const response = await getPipelineDeployment(controllerUrl, validatedEnvKey, deploymentId, pipelineAuthConfig);
301
- const deploymentData = await processDeploymentStatusResponse(response, attempt, maxAttempts, interval, deploymentId);
308
+ const deploymentData = await processDeploymentStatusResponse(
309
+ response,
310
+ attempt,
311
+ maxAttempts,
312
+ interval,
313
+ deploymentId,
314
+ onPollProgress
315
+ );
302
316
  if (deploymentData) {
303
317
  return deploymentData;
304
318
  }
@@ -354,7 +368,7 @@ async function sendDeployment(url, validatedEnvKey, manifest, authConfig, option
354
368
  await ensureBearerTokenValid(url, authConfig);
355
369
 
356
370
  // Step 1: Validate deployment
357
- logger.log(chalk.blue('šŸ” Validating deployment configuration...'));
371
+ logger.log(formatProgress('Validating deployment configuration...'));
358
372
  const validateResult = await validateDeployment(url, validatedEnvKey, manifest, authConfig, {
359
373
  repositoryUrl: options.repositoryUrl,
360
374
  controllerId: options.controllerId,
@@ -368,11 +382,13 @@ async function sendDeployment(url, validatedEnvKey, manifest, authConfig, option
368
382
 
369
383
  logger.log(formatSuccessLine('Validation successful'));
370
384
  if (validateResult.draftDeploymentId) {
371
- logger.log(chalk.gray(` Draft Deployment ID: ${validateResult.draftDeploymentId}`));
385
+ logger.log(metadata(`Draft Deployment ID: ${validateResult.draftDeploymentId}`));
372
386
  }
373
387
 
374
- // Step 2: Deploy using validateToken
375
- logger.log(chalk.blue('\nšŸš€ Deploying application...'));
388
+ // Step 2: Deploy using validateToken (when not polling, show a clear line; otherwise poll phase uses ora)
389
+ if (options.poll === false) {
390
+ logger.log(formatProgress('Deploying application...'));
391
+ }
376
392
  const result = await sendDeploymentRequest(url, validatedEnvKey, validateResult.validateToken, authConfig, {
377
393
  imageTag: options.imageTag || 'latest',
378
394
  timeout: options.timeout || 30000,
@@ -386,35 +402,37 @@ async function sendDeployment(url, validatedEnvKey, manifest, authConfig, option
386
402
  return result;
387
403
  }
388
404
 
389
- /**
390
- * Polls deployment status if enabled
391
- * @async
392
- * @param {Object} result - Deployment result
393
- * @param {string} url - Controller URL
394
- * @param {string} validatedEnvKey - Validated environment key
395
- * @param {Object} authConfig - Authentication configuration
396
- * @param {Object} options - Deployment options
397
- * @returns {Promise<Object>} Deployment result with status
398
- */
405
+ /** Poll pipeline deployment to terminal status (TTY: ora spinner). */
399
406
  async function pollDeployment(result, url, validatedEnvKey, authConfig, options) {
400
407
  if (!options.poll || !result.deploymentId) {
401
408
  return result;
402
409
  }
403
410
 
404
- logger.log(chalk.blue(`\nā³ Polling deployment status (${options.pollInterval || 5000}ms intervals)...`));
405
- const status = await pollDeploymentStatus(
406
- result.deploymentId,
407
- url,
408
- validatedEnvKey,
409
- authConfig,
410
- {
411
- interval: options.pollInterval || 5000,
412
- maxAttempts: options.pollMaxAttempts || 60
413
- }
414
- );
411
+ // Separate validation/draft block from deploy polling (matches guided CLI spacing)
412
+ logger.log('');
415
413
 
416
- result.status = status;
417
- return result;
414
+ const resolvedInterval = await resolvePollIntervalFromController(url, options.pollInterval);
415
+ const maxAttempts = options.pollMaxAttempts || 60;
416
+ const pollUi = createDeployPollHandlers(maxAttempts, { silent: options.silentPoll === true });
417
+
418
+ try {
419
+ const status = await pollDeploymentStatus(
420
+ result.deploymentId,
421
+ url,
422
+ validatedEnvKey,
423
+ authConfig,
424
+ {
425
+ interval: resolvedInterval,
426
+ maxAttempts,
427
+ onPollProgress: pollUi.onPollProgress
428
+ }
429
+ );
430
+
431
+ result.status = status;
432
+ return result;
433
+ } finally {
434
+ pollUi.finish();
435
+ }
418
436
  }
419
437
 
420
438
  /**
@@ -378,14 +378,19 @@ async function executeEnvironmentDeployment(validatedControllerUrl, envKey, auth
378
378
  async function pollDeploymentStatusIfEnabled(result, validatedControllerUrl, envKey, authConfig, options) {
379
379
  const shouldPoll = options.poll !== false && !options.noPoll;
380
380
  if (shouldPoll && result.deploymentId) {
381
+ const { resolvePollIntervalFromController } = require('./poll-interval');
382
+ const pollInterval = await resolvePollIntervalFromController(
383
+ validatedControllerUrl,
384
+ options.pollInterval
385
+ );
381
386
  const pollResult = await pollEnvironmentStatus(
382
387
  result.deploymentId,
383
388
  validatedControllerUrl,
384
389
  envKey,
385
390
  authConfig,
386
391
  {
387
- pollInterval: 5000,
388
- maxAttempts: 60
392
+ pollInterval,
393
+ maxAttempts: options.pollMaxAttempts || 60
389
394
  }
390
395
  );
391
396
  result.status = pollResult.status;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Pipeline deployment polling intervals based on controller DEPLOYMENT mode.
3
+ *
4
+ * Miso-controller exposes `deploymentType` on GET /api/v1/health (azure | azure-mock | local | database).
5
+ * Local/database installs finish quickly; use a shorter poll interval unless the user overrides.
6
+ *
7
+ * @fileoverview Resolve poll interval from controller deployment mode
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ const FAST_POLL_MS = 1000;
13
+ const STANDARD_POLL_MS = 5000;
14
+
15
+ /**
16
+ * @param {string} [deploymentType] - From controller health: deploymentType
17
+ * @returns {boolean}
18
+ */
19
+ function isFastPollingDeploymentType(deploymentType) {
20
+ if (!deploymentType || typeof deploymentType !== 'string') {
21
+ return false;
22
+ }
23
+ const t = deploymentType.trim().toLowerCase();
24
+ return t === 'local' || t === 'database';
25
+ }
26
+
27
+ /**
28
+ * @param {string} [deploymentType] - Controller deploymentType
29
+ * @param {number|string|undefined|null} explicitPollInterval - User/config override (ms)
30
+ * @returns {number}
31
+ */
32
+ function resolvePollIntervalMs(deploymentType, explicitPollInterval) {
33
+ const n =
34
+ explicitPollInterval !== undefined && explicitPollInterval !== null
35
+ ? Number(explicitPollInterval)
36
+ : NaN;
37
+ if (Number.isFinite(n) && n > 0) {
38
+ return n;
39
+ }
40
+ return isFastPollingDeploymentType(deploymentType) ? FAST_POLL_MS : STANDARD_POLL_MS;
41
+ }
42
+
43
+ /**
44
+ * Resolve poll interval: honor explicit ms when valid; else probe controller health for deploymentType.
45
+ * @param {string} controllerUrl - Miso controller base URL
46
+ * @param {number|string|undefined|null} explicitPollInterval - Optional CLI/config override
47
+ * @returns {Promise<number>}
48
+ */
49
+ async function resolvePollIntervalFromController(controllerUrl, explicitPollInterval) {
50
+ const n =
51
+ explicitPollInterval !== undefined && explicitPollInterval !== null
52
+ ? Number(explicitPollInterval)
53
+ : NaN;
54
+ if (Number.isFinite(n) && n > 0) {
55
+ return n;
56
+ }
57
+ try {
58
+ const { getControllerDeploymentType } = require('../api/controller-health.api');
59
+ const deploymentType = await getControllerDeploymentType(controllerUrl);
60
+ return resolvePollIntervalMs(deploymentType, undefined);
61
+ } catch {
62
+ return STANDARD_POLL_MS;
63
+ }
64
+ }
65
+
66
+ module.exports = {
67
+ FAST_POLL_MS,
68
+ STANDARD_POLL_MS,
69
+ isFastPollingDeploymentType,
70
+ resolvePollIntervalMs,
71
+ resolvePollIntervalFromController
72
+ };
@@ -1,4 +1,4 @@
1
- const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
1
+ const { formatSuccessLine, formatProgress } = require('../utils/cli-test-layout-chalk');
2
2
  /**
3
3
  * AI Fabrix Builder Push Utilities
4
4
  *
@@ -150,8 +150,9 @@ function validateRegistryURL(registryUrl) {
150
150
  async function checkACRAuthentication(registry) {
151
151
  try {
152
152
  const registryName = extractRegistryName(registry);
153
- // On Windows, use shell option to ensure proper command resolution
154
- const options = process.platform === 'win32' ? { shell: true } : {};
153
+ const { getDockerExecEnv } = require('../utils/remote-docker-env');
154
+ const env = await getDockerExecEnv();
155
+ const options = process.platform === 'win32' ? { shell: true, env } : { env };
155
156
  await execAsync(`az acr show --name ${registryName}`, { ...options, timeout: AZ_ACR_SHOW_TIMEOUT_MS });
156
157
  return true;
157
158
  } catch (error) {
@@ -167,9 +168,10 @@ async function checkACRAuthentication(registry) {
167
168
  async function authenticateACR(registry) {
168
169
  try {
169
170
  const registryName = extractRegistryName(registry);
170
- logger.log(chalk.blue(`Authenticating with ${registry}...`));
171
- // On Windows, use shell option to ensure proper command resolution
172
- const options = process.platform === 'win32' ? { shell: true } : {};
171
+ logger.log(formatProgress(`Authenticating with ${registry}…`));
172
+ const { getDockerExecEnv } = require('../utils/remote-docker-env');
173
+ const env = await getDockerExecEnv();
174
+ const options = process.platform === 'win32' ? { shell: true, env } : { env };
173
175
  await execAsync(`az acr login --name ${registryName}`, { ...options, timeout: AZ_ACR_LOGIN_TIMEOUT_MS });
174
176
  logger.log(formatSuccessLine(`Authenticated with ${registry}`));
175
177
  } catch (error) {
@@ -192,7 +194,7 @@ async function authenticateACR(registry) {
192
194
  */
193
195
  async function authenticateExternalRegistry(registry, username, password) {
194
196
  try {
195
- logger.log(chalk.blue(`Authenticating with ${registry}...`));
197
+ logger.log(formatProgress(`Authenticating with ${registry}…`));
196
198
 
197
199
  // Use cross-platform approach: write password to stdin directly
198
200
  // This works on Windows, Linux, and macOS
@@ -262,7 +264,7 @@ async function tagImage(sourceImage, targetImage) {
262
264
  try {
263
265
  const { getDockerExecEnv } = require('../utils/remote-docker-env');
264
266
  const env = await getDockerExecEnv();
265
- logger.log(chalk.blue(`Tagging ${sourceImage} as ${targetImage}...`));
267
+ logger.log(formatProgress(`Tagging ${sourceImage} → ${targetImage}…`));
266
268
  await execAsync(`docker tag ${sourceImage} ${targetImage}`, { env });
267
269
  logger.log(formatSuccessLine(`Tagged: ${targetImage}`));
268
270
  } catch (error) {
@@ -280,7 +282,7 @@ async function pushImage(imageWithTag, registry = null) {
280
282
  try {
281
283
  const { getDockerExecEnv } = require('../utils/remote-docker-env');
282
284
  const env = await getDockerExecEnv();
283
- logger.log(chalk.blue(`Pushing ${imageWithTag}...`));
285
+ logger.log(formatProgress(`Pushing ${imageWithTag}…`));
284
286
  await execAsync(`docker push ${imageWithTag}`, { env });
285
287
  logger.log(formatSuccessLine(`Pushed: ${imageWithTag}`));
286
288
  } catch (error) {
@@ -29,6 +29,7 @@ const { validateExternalSystemComplete } = require('../validation/validate');
29
29
  const { displayValidationResults } = require('../validation/validate-display');
30
30
  const { maybeSyncSystemCertificationFromDataplane } = require('../certification/sync-system-certification');
31
31
  const { cliOptsSkipCertSync } = require('../certification/cli-cert-sync-skip');
32
+ const { syncDeployJsonFromSources } = require('./sync-deploy-manifest');
32
33
 
33
34
  /**
34
35
  * Lists datasources for a system and loads system record for docs URLs.
@@ -152,7 +153,7 @@ function logImmediateControllerDeploymentOutcome(deploymentOutcome) {
152
153
  logger.log(formatSuccessParagraph('Controller deployment OK'));
153
154
  return;
154
155
  }
155
- logger.log(chalk.red('\nāœ– Controller deployment did not complete successfully'));
156
+ logger.log(chalk.red('āœ– Controller deployment did not complete successfully'));
156
157
  const parts = [deploymentOutcome.error, deploymentOutcome.message].filter(Boolean);
157
158
  if (parts.length > 0) {
158
159
  for (const line of parts) {
@@ -265,6 +266,8 @@ async function deployExternalSystem(appName, options = {}) {
265
266
 
266
267
  logger.log(formatSuccessLine('Local validation passed, proceeding with deployment...'));
267
268
 
269
+ await syncDeployJsonFromSources(appName);
270
+
268
271
  const manifest = await generateControllerManifest(appName, options);
269
272
 
270
273
  const { environment, controllerUrl, authConfig } = await prepareDeploymentConfig(appName, options);
@@ -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
+ formatProgress,
8
+ formatWarningLine
9
+ } = require('../utils/cli-test-layout-chalk');
2
10
  /**
3
11
  * External System Download Module
4
12
  *
@@ -24,7 +32,6 @@ const fsSync = require('fs');
24
32
  const path = require('path');
25
33
  const readline = require('readline');
26
34
  const yaml = require('js-yaml');
27
- const chalk = require('chalk');
28
35
  const { getExternalSystemConfig } = require('../api/external-systems.api');
29
36
  const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
30
37
  const { getConfig } = require('../core/config');
@@ -128,7 +135,7 @@ async function setupAuthenticationAndDataplane(systemKey, _options, _config) {
128
135
  }
129
136
 
130
137
  const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
131
- logger.log(chalk.blue('🌐 Resolving dataplane URL...'));
138
+ logger.log(formatProgress('Resolving dataplane URL…'));
132
139
  const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
133
140
  logger.log(formatSuccessLine(`Dataplane URL: ${dataplaneUrl}`));
134
141
 
@@ -148,7 +155,7 @@ async function setupAuthenticationAndDataplane(systemKey, _options, _config) {
148
155
  * @throws {Error} If download fails
149
156
  */
150
157
  async function downloadFullManifest(dataplaneUrl, systemKey, authConfig) {
151
- logger.log(chalk.blue(`šŸ“” Downloading full manifest: ${systemKey}`));
158
+ logger.log(formatProgress(`Downloading manifest for ${systemKey}…`));
152
159
  const response = await getExternalSystemConfig(dataplaneUrl, systemKey, authConfig);
153
160
 
154
161
  if (!response.success || !response.data) {
@@ -268,14 +275,18 @@ function validateSystemKeyFormat(systemKey) {
268
275
  * @param {string} dataplaneUrl - Dataplane URL
269
276
  */
270
277
  function handleDryRun(systemKey, dataplaneUrl) {
271
- logger.log(chalk.yellow('šŸ” Dry run mode - would download from:'));
272
- logger.log(chalk.gray(` ${dataplaneUrl}/api/v1/external/systems/${systemKey}/config`));
273
- logger.log(chalk.yellow('\nWould create (via split-json):'));
274
- logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-deploy.json`));
275
- logger.log(chalk.gray(` integration/${systemKey}/application.yaml`));
276
- logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-system.yaml`));
277
- logger.log(chalk.gray(` integration/${systemKey}/env.template`));
278
- logger.log(chalk.gray(` integration/${systemKey}/README.md`));
278
+ logger.log(formatWarningLine('Dry run mode: no files written.'));
279
+ logger.log(metadata(`Would fetch: ${dataplaneUrl}/api/v1/external/systems/${systemKey}/config`));
280
+ logger.log(metadata('Would create:'));
281
+ [
282
+ `integration/${systemKey}/${systemKey}-deploy.json`,
283
+ `integration/${systemKey}/application.yaml`,
284
+ `integration/${systemKey}/${systemKey}-system.yaml`,
285
+ `integration/${systemKey}/env.template`,
286
+ `integration/${systemKey}/README.md`
287
+ ].forEach(rel => {
288
+ logger.log(metadata(` - ${rel}`));
289
+ });
279
290
  }
280
291
 
281
292
  /**
@@ -285,7 +296,7 @@ function handleDryRun(systemKey, dataplaneUrl) {
285
296
  * @returns {string} System type
286
297
  */
287
298
  function validateAndLogDownloadedData(application, dataSources) {
288
- logger.log(chalk.blue('šŸ” Validating downloaded data...'));
299
+ logger.log(formatProgress('Validating downloaded data…'));
289
300
  validateDownloadedData(application, dataSources);
290
301
  const systemType = validateSystemType(application);
291
302
  logger.log(formatSuccessLine(`System type: ${systemType}`));
@@ -300,7 +311,7 @@ function validateAndLogDownloadedData(application, dataSources) {
300
311
  function promptReplaceReadme() {
301
312
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
302
313
  return new Promise(resolve => {
303
- rl.question(chalk.yellow('README.md already exists. Do you want to replace it? (yes/no) '), answer => {
314
+ rl.question(`${metadata('README.md already exists. Replace it?')} (yes/no) `, answer => {
304
315
  rl.close();
305
316
  const normalized = (answer || '').trim().toLowerCase();
306
317
  resolve(normalized === 'yes' || normalized === 'y');
@@ -326,7 +337,7 @@ async function resolveDownloadSplitOptions(finalPath, options = {}) {
326
337
  opts.overwriteReadme = true;
327
338
  } else {
328
339
  opts.overwriteReadme = await promptReplaceReadme();
329
- if (!opts.overwriteReadme) logger.log(chalk.gray(' Keeping existing README.md'));
340
+ if (!opts.overwriteReadme) logger.log(metadata('Keeping existing README.md'));
330
341
  }
331
342
  }
332
343
  return opts;
@@ -362,7 +373,8 @@ async function processDownloadedSystem(systemKey, manifest, splitOptions = {}) {
362
373
  const { application, dataSources, version } = manifest;
363
374
  const finalPath = getIntegrationPath(systemKey);
364
375
 
365
- logger.log(chalk.blue(`šŸ“ Creating directory: ${finalPath}`));
376
+ logger.log(formatProgress('Creating integration directory…'));
377
+ logger.log(metadata(finalPath));
366
378
  await fs.mkdir(finalPath, { recursive: true });
367
379
 
368
380
  const deployJson = buildDeployJsonFromManifest(application, dataSources, version);
@@ -370,7 +382,7 @@ async function processDownloadedSystem(systemKey, manifest, splitOptions = {}) {
370
382
  await fs.writeFile(deployJsonPath, JSON.stringify(deployJson, null, 2), 'utf8');
371
383
  logger.log(formatSuccessLine(`Created: ${path.relative(process.cwd(), deployJsonPath)}`));
372
384
 
373
- logger.log(chalk.blue('šŸ“‚ Splitting deploy JSON into component files...'));
385
+ logger.log(formatProgress('Splitting deploy JSON into component files…'));
374
386
  const splitResult = await generator.splitDeployJson(deployJsonPath, finalPath, splitOptions);
375
387
  await applyRetemplateToSystemFile(systemKey, splitResult.systemFile);
376
388
 
@@ -391,10 +403,37 @@ async function processDownloadedSystem(systemKey, manifest, splitOptions = {}) {
391
403
  * @param {number} datasourceCount - Number of datasources
392
404
  */
393
405
  function displayDownloadSuccess(systemKey, finalPath, datasourceCount) {
394
- logger.log(formatSuccessParagraph('External system downloaded successfully!'));
395
- logger.log(chalk.blue(`Location: ${finalPath}`));
396
- logger.log(chalk.blue(`System: ${systemKey}`));
397
- logger.log(chalk.blue(`Datasources: ${datasourceCount}`));
406
+ logger.log(
407
+ formatSuccessParagraph(
408
+ `Downloaded ${systemKey} (${datasourceCount} datasource${datasourceCount === 1 ? '' : 's'})`
409
+ )
410
+ );
411
+ logger.log(headerKeyValue('Location:', finalPath));
412
+ }
413
+
414
+ /**
415
+ * After YAML split, convert integration files to JSON when --format json.
416
+ * @async
417
+ * @param {string} systemKey
418
+ * @returns {Promise<void>}
419
+ */
420
+ async function runConvertToJsonIfRequested(systemKey) {
421
+ const { runConvert } = require('../commands/convert');
422
+ try {
423
+ await runConvert(systemKey, { format: 'json', force: true });
424
+ logger.log(formatSuccessLine('Converted component files to JSON'));
425
+ } catch (convertErr) {
426
+ throw new Error(`Download succeeded but convert to JSON failed: ${convertErr.message}`);
427
+ }
428
+ }
429
+
430
+ /**
431
+ * @param {string} systemKey
432
+ */
433
+ function logDownloadCommandHeader(systemKey) {
434
+ logger.log('');
435
+ logger.log(sectionTitle('Download'));
436
+ logger.log(headerKeyValue('System:', systemKey));
398
437
  }
399
438
 
400
439
  /**
@@ -411,23 +450,13 @@ function displayDownloadSuccess(systemKey, finalPath, datasourceCount) {
411
450
  * @returns {Promise<void>} Resolves when download completes
412
451
  * @throws {Error} If download fails
413
452
  */
414
- async function runConvertToJsonIfRequested(systemKey) {
415
- const { runConvert } = require('../commands/convert');
416
- try {
417
- await runConvert(systemKey, { format: 'json', force: true });
418
- logger.log(formatSuccessLine('Converted component files to JSON'));
419
- } catch (convertErr) {
420
- throw new Error(`Download succeeded but convert to JSON failed: ${convertErr.message}`);
421
- }
422
- }
423
-
424
453
  async function downloadExternalSystem(systemKey, options = {}) {
425
454
  validateSystemKeyFormat(systemKey);
426
455
 
427
456
  const format = (options.format || 'yaml').toLowerCase();
428
457
 
429
458
  try {
430
- logger.log(chalk.blue(`\nšŸ“„ Downloading external system: ${systemKey}`));
459
+ logDownloadCommandHeader(systemKey);
431
460
 
432
461
  const config = await getConfig();
433
462
  const { authConfig, dataplaneUrl } = await setupAuthenticationAndDataplane(systemKey, options, config);
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Regenerate *-deploy.json on disk for an integration (same as `aifabrix json <systemKey>`).
3
+ *
4
+ * @fileoverview Sync deployment manifest file before upload/deploy
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
12
+ const generator = require('../generator');
13
+ const logger = require('../utils/logger');
14
+
15
+ /**
16
+ * Writes integration/<systemKey>/<systemKey>-deploy.json from current application sources.
17
+ * Equivalent to running {@link generator.generateDeployJson} / `aifabrix json <systemKey>` for externals.
18
+ *
19
+ * @param {string} systemKey - External system key (integration folder name)
20
+ * @param {{ quiet?: boolean }} [opts] - quiet: omit success log line
21
+ * @returns {Promise<string>} Absolute path to the written deploy JSON file
22
+ */
23
+ async function syncDeployJsonFromSources(systemKey, opts = {}) {
24
+ const deployPath = await generator.generateDeployJson(systemKey, {});
25
+ if (!opts.quiet) {
26
+ logger.log(formatSuccessLine(`Updated deployment manifest: ${deployPath}`));
27
+ }
28
+ return deployPath;
29
+ }
30
+
31
+ module.exports = {
32
+ syncDeployJsonFromSources
33
+ };
@@ -324,17 +324,22 @@ async function promptForExistingCredentialInput() {
324
324
  * @function promptForUserIntent
325
325
  * @returns {Promise<string>} User intent
326
326
  */
327
+ const WIZARD_INTENT_MAX_LENGTH = 1000;
328
+
327
329
  async function promptForUserIntent() {
328
330
  const { intent } = await inquirer.prompt([
329
331
  {
330
332
  type: 'input',
331
333
  name: 'intent',
332
- message: 'Describe your primary use case (any text):',
334
+ message: `Describe your primary use case (max ${WIZARD_INTENT_MAX_LENGTH} characters):`,
333
335
  default: 'general integration',
334
336
  validate: (input) => {
335
337
  if (!input || typeof input !== 'string' || input.trim().length === 0) {
336
338
  return 'Intent is required';
337
339
  }
340
+ if (input.length > WIZARD_INTENT_MAX_LENGTH) {
341
+ return `Intent must be ${WIZARD_INTENT_MAX_LENGTH} characters or fewer`;
342
+ }
338
343
  return true;
339
344
  }
340
345
  }
@@ -437,6 +442,7 @@ async function promptForRunWithSavedConfig() {
437
442
  const secondary = require('./wizard-prompts-secondary');
438
443
 
439
444
  module.exports = {
445
+ WIZARD_INTENT_MAX_LENGTH,
440
446
  promptForMode,
441
447
  promptForSystemIdOrKey,
442
448
  promptForExistingSystem,
@@ -32,6 +32,35 @@ function toKeySegment(str) {
32
32
  return sanitized || 'default';
33
33
  }
34
34
 
35
+ /**
36
+ * Align authentication.security kv:// namespaces and credentialKey with the final system key.
37
+ * Dataplane normalizes this before respond; when appName overrides a spec-derived key (e.g.
38
+ * OpenAPI title "Companies"), the builder still must rewrite nested auth so it matches env.template.
39
+ *
40
+ * @param {Object|null|undefined} authentication - authentication block from dataplane
41
+ * @param {string} systemKey - Final external system key (integration app name)
42
+ * @param {string} [authDisplayName] - Credential display name (typically title-cased app name)
43
+ */
44
+ function normalizeAuthenticationToSystemKey(authentication, systemKey, authDisplayName) {
45
+ if (!authentication || typeof authentication !== 'object') return;
46
+ authentication.credentialKey = `${systemKey}-cred`;
47
+ const security = authentication.security;
48
+ if (security && typeof security === 'object') {
49
+ for (const k of Object.keys(security)) {
50
+ const v = security[k];
51
+ if (typeof v === 'string' && v.startsWith('kv://')) {
52
+ const rest = v.slice(5);
53
+ const idx = rest.indexOf('/');
54
+ const suffix = idx >= 0 ? rest.slice(idx + 1) : '';
55
+ security[k] = suffix ? `kv://${systemKey}/${suffix}` : `kv://${systemKey}`;
56
+ }
57
+ }
58
+ }
59
+ if (authDisplayName) {
60
+ authentication.displayName = authDisplayName;
61
+ }
62
+ }
63
+
35
64
  /**
36
65
  * Generate files from dataplane-generated wizard configurations
37
66
  * @async
@@ -182,6 +211,11 @@ async function prepareWizardContext(appName, systemConfig, datasourceConfigs) {
182
211
  const originalSystemKey = systemConfig.key || finalSystemKey;
183
212
  const appDisplayName = appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
184
213
  const updatedSystemConfig = { ...systemConfig, key: finalSystemKey, displayName: appDisplayName };
214
+ normalizeAuthenticationToSystemKey(
215
+ updatedSystemConfig.authentication,
216
+ finalSystemKey,
217
+ appDisplayName
218
+ );
185
219
  const originalPrefix = `${originalSystemKey}-`;
186
220
  const updatedDatasourceConfigs = datasourceConfigs.map(ds => {
187
221
  let newKey;