@aifabrix/builder 2.44.5 → 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 (207) 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 +48 -2
  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/validation-runner.js +46 -25
  17. package/lib/app/deploy-config.js +11 -1
  18. package/lib/app/deploy-status-display.js +3 -3
  19. package/lib/app/deploy.js +36 -14
  20. package/lib/app/display.js +15 -11
  21. package/lib/app/push.js +46 -23
  22. package/lib/app/register.js +1 -1
  23. package/lib/app/restart-display.js +95 -0
  24. package/lib/app/rotate-secret.js +1 -1
  25. package/lib/app/run-container-start.js +12 -6
  26. package/lib/app/run-env-compose.js +30 -1
  27. package/lib/app/run-helpers.js +44 -12
  28. package/lib/app/run-reload-sync.js +148 -0
  29. package/lib/app/run-resolve-image.js +51 -1
  30. package/lib/app/run.js +99 -73
  31. package/lib/build/index.js +75 -45
  32. package/lib/cli/doctor-check.js +117 -0
  33. package/lib/cli/index.js +8 -2
  34. package/lib/cli/infra-guided.js +445 -0
  35. package/lib/cli/setup-app.js +20 -2
  36. package/lib/cli/setup-auth.js +26 -0
  37. package/lib/cli/setup-dev-path-commands.js +50 -3
  38. package/lib/cli/setup-infra.js +134 -61
  39. package/lib/cli/setup-integration-client.js +182 -0
  40. package/lib/cli/setup-parameters.js +21 -2
  41. package/lib/cli/setup-platform.js +102 -0
  42. package/lib/cli/setup-secrets.js +18 -6
  43. package/lib/cli/setup-utility.js +78 -33
  44. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  45. package/lib/commands/datasource-capability-output.js +29 -0
  46. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  47. package/lib/commands/datasource-capability.js +411 -0
  48. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  49. package/lib/commands/datasource.js +53 -13
  50. package/lib/commands/dev-down.js +3 -3
  51. package/lib/commands/dev-infra-gate.js +32 -0
  52. package/lib/commands/dev-init.js +13 -7
  53. package/lib/commands/dimension-value.js +179 -0
  54. package/lib/commands/dimension.js +330 -0
  55. package/lib/commands/integration-client.js +430 -0
  56. package/lib/commands/login-device.js +65 -30
  57. package/lib/commands/login.js +21 -10
  58. package/lib/commands/parameters-validate.js +78 -13
  59. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  60. package/lib/commands/repair-datasource-keys.js +10 -5
  61. package/lib/commands/repair-datasource.js +19 -7
  62. package/lib/commands/repair-env-template.js +4 -1
  63. package/lib/commands/repair-openapi-sync.js +172 -0
  64. package/lib/commands/repair-persist.js +102 -0
  65. package/lib/commands/repair-rbac-extract.js +27 -0
  66. package/lib/commands/repair-rbac-migrate.js +186 -0
  67. package/lib/commands/repair-rbac.js +214 -31
  68. package/lib/commands/repair-system-alignment.js +246 -0
  69. package/lib/commands/repair-system-permissions.js +168 -0
  70. package/lib/commands/repair.js +120 -338
  71. package/lib/commands/secure.js +1 -1
  72. package/lib/commands/setup-modes.js +455 -0
  73. package/lib/commands/setup-prompts.js +388 -0
  74. package/lib/commands/setup.js +149 -0
  75. package/lib/commands/teardown.js +228 -0
  76. package/lib/commands/up-common.js +79 -19
  77. package/lib/commands/up-dataplane.js +33 -11
  78. package/lib/commands/up-miso.js +7 -11
  79. package/lib/commands/upload.js +109 -23
  80. package/lib/commands/wizard-core-helpers.js +14 -11
  81. package/lib/commands/wizard-core.js +6 -5
  82. package/lib/commands/wizard-dataplane.js +2 -2
  83. package/lib/commands/wizard-entity-selection.js +4 -3
  84. package/lib/commands/wizard-headless.js +2 -1
  85. package/lib/commands/wizard.js +2 -1
  86. package/lib/constants/infra-compose-service-names.js +40 -0
  87. package/lib/core/env-reader.js +16 -3
  88. package/lib/core/secrets-admin-env.js +101 -0
  89. package/lib/core/secrets-ensure-infra.js +34 -1
  90. package/lib/core/secrets-ensure.js +88 -66
  91. package/lib/core/secrets-env-content.js +432 -0
  92. package/lib/core/secrets-env-write.js +27 -1
  93. package/lib/core/secrets-load.js +248 -0
  94. package/lib/core/secrets-names.js +32 -0
  95. package/lib/core/secrets.js +17 -757
  96. package/lib/datasource/capability/basic-exposure.js +76 -0
  97. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  98. package/lib/datasource/capability/capability-key.js +34 -0
  99. package/lib/datasource/capability/capability-resolve.js +172 -0
  100. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  101. package/lib/datasource/capability/copy-operations.js +348 -0
  102. package/lib/datasource/capability/copy-test-payload.js +139 -0
  103. package/lib/datasource/capability/create-operations.js +235 -0
  104. package/lib/datasource/capability/dimension-operations.js +151 -0
  105. package/lib/datasource/capability/dimension-validate.js +219 -0
  106. package/lib/datasource/capability/json-pointer.js +31 -0
  107. package/lib/datasource/capability/reference-rewrite.js +51 -0
  108. package/lib/datasource/capability/relate-operations.js +325 -0
  109. package/lib/datasource/capability/relate-validate.js +219 -0
  110. package/lib/datasource/capability/remove-operations.js +275 -0
  111. package/lib/datasource/capability/run-capability-copy.js +152 -0
  112. package/lib/datasource/capability/run-capability-diff.js +135 -0
  113. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  114. package/lib/datasource/capability/run-capability-edit.js +377 -0
  115. package/lib/datasource/capability/run-capability-relate.js +193 -0
  116. package/lib/datasource/capability/run-capability-remove.js +105 -0
  117. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  118. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  119. package/lib/datasource/list.js +136 -23
  120. package/lib/datasource/log-viewer.js +2 -4
  121. package/lib/datasource/unified-validation-run.js +51 -16
  122. package/lib/datasource/validate.js +53 -1
  123. package/lib/deployment/deploy-poll-ui.js +60 -0
  124. package/lib/deployment/deployer-status.js +29 -3
  125. package/lib/deployment/deployer.js +48 -30
  126. package/lib/deployment/environment.js +7 -2
  127. package/lib/deployment/poll-interval.js +72 -0
  128. package/lib/deployment/push.js +11 -9
  129. package/lib/external-system/deploy.js +4 -1
  130. package/lib/external-system/download.js +61 -32
  131. package/lib/external-system/sync-deploy-manifest.js +33 -0
  132. package/lib/infrastructure/index.js +49 -19
  133. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  134. package/lib/parameters/infra-kv-discovery.js +29 -4
  135. package/lib/parameters/infra-parameter-catalog.js +6 -3
  136. package/lib/parameters/infra-parameter-validate.js +67 -19
  137. package/lib/resolvers/datasource-resolver.js +53 -0
  138. package/lib/resolvers/dimension-file.js +52 -0
  139. package/lib/resolvers/manifest-resolver.js +133 -0
  140. package/lib/schema/external-datasource.schema.json +183 -53
  141. package/lib/schema/external-system.schema.json +23 -10
  142. package/lib/schema/infra.parameter.yaml +26 -11
  143. package/lib/schema/wizard-config.schema.json +1 -1
  144. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  145. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  146. package/lib/utils/app-run-containers.js +2 -2
  147. package/lib/utils/bash-secret-env.js +59 -0
  148. package/lib/utils/cli-secrets-error-format.js +78 -0
  149. package/lib/utils/cli-test-layout-chalk.js +31 -9
  150. package/lib/utils/cli-utils.js +4 -36
  151. package/lib/utils/datasource-test-run-display.js +8 -0
  152. package/lib/utils/dev-hosts-helper.js +3 -2
  153. package/lib/utils/dev-init-ssh-merge.js +2 -1
  154. package/lib/utils/docker-build.js +17 -9
  155. package/lib/utils/docker-reload-mount.js +127 -0
  156. package/lib/utils/external-readme.js +71 -2
  157. package/lib/utils/external-system-local-test-tty.js +3 -2
  158. package/lib/utils/external-system-readiness-core.js +45 -12
  159. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  160. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  161. package/lib/utils/external-system-readiness-display.js +10 -1
  162. package/lib/utils/file-upload.js +40 -3
  163. package/lib/utils/health-check-db-init.js +107 -0
  164. package/lib/utils/health-check-public-warn.js +69 -0
  165. package/lib/utils/health-check-url.js +19 -4
  166. package/lib/utils/health-check.js +135 -105
  167. package/lib/utils/help-builder.js +5 -1
  168. package/lib/utils/image-name.js +34 -7
  169. package/lib/utils/integration-file-backup.js +74 -0
  170. package/lib/utils/mutagen-install.js +30 -3
  171. package/lib/utils/paths.js +108 -25
  172. package/lib/utils/postgres-wipe.js +212 -0
  173. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  174. package/lib/utils/remote-dev-auth.js +21 -5
  175. package/lib/utils/remote-docker-env.js +9 -1
  176. package/lib/utils/remote-secrets-loader.js +42 -3
  177. package/lib/utils/resolve-docker-image-ref.js +9 -3
  178. package/lib/utils/secrets-ancestor-paths.js +47 -0
  179. package/lib/utils/secrets-helpers.js +17 -10
  180. package/lib/utils/secrets-kv-refs.js +42 -0
  181. package/lib/utils/secrets-kv-scope.js +19 -2
  182. package/lib/utils/secrets-materialize-local.js +134 -0
  183. package/lib/utils/secrets-path.js +24 -10
  184. package/lib/utils/secrets-utils.js +2 -2
  185. package/lib/utils/system-builder-root.js +34 -0
  186. package/lib/utils/url-declarative-resolve-build.js +6 -1
  187. package/lib/utils/url-declarative-runtime-base-path.js +32 -0
  188. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  189. package/lib/utils/urls-local-registry.js +23 -12
  190. package/lib/utils/validation-poll-ui.js +81 -0
  191. package/lib/utils/validation-run-poll.js +29 -5
  192. package/lib/utils/with-muted-logger.js +53 -0
  193. package/package.json +1 -1
  194. package/templates/applications/dataplane/application.yaml +1 -1
  195. package/templates/applications/dataplane/rbac.yaml +10 -10
  196. package/templates/applications/keycloak/env.template +8 -6
  197. package/templates/applications/miso-controller/application.yaml +7 -0
  198. package/templates/applications/miso-controller/env.template +1 -1
  199. package/templates/applications/miso-controller/rbac.yaml +9 -9
  200. package/templates/external-system/README.md.hbs +83 -123
  201. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  202. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  203. package/.nyc_output/processinfo/index.json +0 -1
  204. package/lib/api/service-users.api.js +0 -150
  205. package/lib/api/types/service-users.types.js +0 -65
  206. package/lib/cli/setup-service-user.js +0 -187
  207. package/lib/commands/service-user.js +0 -429
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @fileoverview Dimensions API type definitions (Controller)
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ /**
8
+ * Dimension create/update payload (Controller catalog)
9
+ * @typedef {Object} DimensionCreateRequest
10
+ * @property {string} key
11
+ * @property {string} displayName
12
+ * @property {string} [description]
13
+ * @property {'string'|'number'|'boolean'} dataType
14
+ * @property {boolean} [isRequired]
15
+ */
16
+
17
+ /**
18
+ * Dimension entity
19
+ * @typedef {Object} Dimension
20
+ * @property {string} id
21
+ * @property {string} key
22
+ * @property {string} displayName
23
+ * @property {string|null} [description]
24
+ * @property {'string'|'number'|'boolean'} dataType
25
+ * @property {boolean} isRequired
26
+ * @property {string|null} [createdBy]
27
+ * @property {string|null} [updatedBy]
28
+ * @property {string} [createdAt]
29
+ * @property {string} [updatedAt]
30
+ */
31
+
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @fileoverview Integration clients API type definitions (Controller camelCase)
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ /**
8
+ * Integration client create request body (builder → controller)
9
+ * @typedef {Object} IntegrationClientCreateRequest
10
+ * @property {string} key - Stable key (lowercase alphanumeric + hyphens)
11
+ * @property {string} displayName - Human-readable name
12
+ * @property {string[]} redirectUris - Allowed redirect URIs for OAuth2 (min 1)
13
+ * @property {string[]} [groupNames] - Optional group names (RBAC); omit or empty for OAuth-only clients
14
+ * @property {string} [description] - Optional description
15
+ * @property {string} [keycloakClientId] - Optional fixed Keycloak client id
16
+ */
17
+
18
+ /**
19
+ * Create response (clientSecret is one-time-only)
20
+ * @typedef {Object} IntegrationClientCreateResponseData
21
+ * @property {Object} [integrationClient] - Created record
22
+ * @property {string} [integrationClient.id] - Integration client id
23
+ * @property {string} [integrationClient.key] - Key
24
+ * @property {string} [integrationClient.displayName] - Display name
25
+ * @property {string} [integrationClient.keycloakClientId] - OAuth client id in Keycloak
26
+ * @property {string} [integrationClient.status] - Status
27
+ * @property {string} [clientSecret] - One-time secret
28
+ */
29
+
30
+ /**
31
+ * Single integration client in list response
32
+ * @typedef {Object} ListIntegrationClientItem
33
+ * @property {string} id - Integration client id
34
+ * @property {string} [key] - Key
35
+ * @property {string} [displayName] - Display name
36
+ * @property {string} [keycloakClientId] - OAuth client id
37
+ * @property {string} [status] - Status (e.g. active)
38
+ */
39
+
40
+ /**
41
+ * Regenerate secret response
42
+ * @typedef {Object} RegenerateIntegrationClientSecretResponse
43
+ * @property {Object} [data] - Nested data
44
+ * @property {string} [data.clientSecret] - New secret (one-time)
45
+ */
@@ -9,6 +9,8 @@
9
9
  const { extractTestRunId } = require('./validation-run.api');
10
10
  const { postValidationRunWithTransportRetry } = require('../utils/validation-run-post-retry');
11
11
  const { pollValidationRunUntilComplete } = require('../utils/validation-run-poll');
12
+ const { createValidationPollHandlers } = require('../utils/validation-poll-ui');
13
+ const logger = require('../utils/logger');
12
14
 
13
15
  /**
14
16
  * POST /api/v1/validation/run and (when async) poll GET until reportCompleteness is full.
@@ -20,6 +22,8 @@ const { pollValidationRunUntilComplete } = require('../utils/validation-run-poll
20
22
  * @param {number} opts.timeoutMs
21
23
  * @param {boolean} opts.useAsync
22
24
  * @param {boolean} opts.noAsync
25
+ * @param {boolean} [opts.verbosePoll] - Throttled poll lines when no TTY poll UI (see validation-run-poll)
26
+ * @param {Function} [opts.onPollProgress] - Extra hook: `(envelope, attemptIndex, meta)` during poll
23
27
  * @returns {Promise<{ envelope: Object|null, apiError: Object|null, pollTimedOut: boolean, incompleteNoAsync: boolean }>}
24
28
  */
25
29
  /* eslint-disable max-lines-per-function, max-statements, complexity -- POST + poll orchestration */
@@ -54,31 +58,48 @@ async function postValidationRunAndOptionalPoll(opts) {
54
58
  if (needsPoll && testRunId) {
55
59
  const elapsed = Date.now() - started;
56
60
  const remaining = Math.max(0, timeoutMs - elapsed);
57
- const pollResult = await pollValidationRunUntilComplete({
58
- dataplaneUrl,
59
- authConfig,
60
- testRunId,
61
- budgetMs: remaining,
62
- verbosePoll: verbosePoll === true,
63
- pollRequestTimeoutMs:
64
- Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : undefined
65
- });
66
- if (!pollResult.lastApiResult || !pollResult.lastApiResult.success) {
67
- return {
68
- envelope: pollResult.envelope,
69
- apiError: pollResult.lastApiResult,
70
- pollTimedOut: pollResult.timedOut,
71
- incompleteNoAsync: false
72
- };
73
- }
74
- envelope = pollResult.envelope;
75
- if (pollResult.timedOut) {
76
- return {
77
- envelope,
78
- apiError: null,
79
- pollTimedOut: true,
80
- incompleteNoAsync: false
81
- };
61
+ const deadlineMs = Date.now() + remaining;
62
+
63
+ logger.log('');
64
+ const pollUi = createValidationPollHandlers(deadlineMs);
65
+ const mergeOnPollProgress =
66
+ typeof opts.onPollProgress === 'function'
67
+ ? (envelope, attemptIndex, meta) => {
68
+ pollUi.onPollProgress(envelope, attemptIndex, meta);
69
+ opts.onPollProgress(envelope, attemptIndex, meta);
70
+ }
71
+ : pollUi.onPollProgress;
72
+
73
+ try {
74
+ const pollResult = await pollValidationRunUntilComplete({
75
+ dataplaneUrl,
76
+ authConfig,
77
+ testRunId,
78
+ budgetMs: remaining,
79
+ verbosePoll: verbosePoll === true,
80
+ pollRequestTimeoutMs:
81
+ Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : undefined,
82
+ onPollProgress: mergeOnPollProgress
83
+ });
84
+ if (!pollResult.lastApiResult || !pollResult.lastApiResult.success) {
85
+ return {
86
+ envelope: pollResult.envelope,
87
+ apiError: pollResult.lastApiResult,
88
+ pollTimedOut: pollResult.timedOut,
89
+ incompleteNoAsync: false
90
+ };
91
+ }
92
+ envelope = pollResult.envelope;
93
+ if (pollResult.timedOut) {
94
+ return {
95
+ envelope,
96
+ apiError: null,
97
+ pollTimedOut: true,
98
+ incompleteNoAsync: false
99
+ };
100
+ }
101
+ } finally {
102
+ pollUi.finish();
82
103
  }
83
104
  }
84
105
 
@@ -62,11 +62,21 @@ async function extractDeploymentConfig(options, _variables) {
62
62
  const controllerUrl = await resolveControllerUrl();
63
63
  const envKey = await resolveEnvironment();
64
64
 
65
+ const pollIntervalExplicit =
66
+ options.pollInterval !== undefined && options.pollInterval !== null
67
+ ? Number(options.pollInterval)
68
+ : undefined;
69
+
65
70
  return {
66
71
  controllerUrl,
67
72
  envKey,
68
73
  poll: options.poll !== false,
69
- pollInterval: options.pollInterval || 5000,
74
+ // When true, polling still happens but is not rendered (no nested ora spinner).
75
+ // Used by guided installers which already show a top-level spinner.
76
+ silentPoll: options.silentPoll === true,
77
+ repositoryUrl: options.repositoryUrl,
78
+ // Omit default here so deployer can resolve 2s vs 5s from controller deploymentType (GET /api/v1/health)
79
+ pollInterval: Number.isFinite(pollIntervalExplicit) ? pollIntervalExplicit : undefined,
70
80
  pollMaxAttempts: options.pollMaxAttempts || 60
71
81
  };
72
82
  }
@@ -5,8 +5,8 @@
5
5
  * @version 2.0.0
6
6
  */
7
7
 
8
- const chalk = require('chalk');
9
8
  const logger = require('../utils/logger');
9
+ const { formatSuccessLine, metadata } = require('../utils/cli-test-layout-chalk');
10
10
  const { getApplicationStatus } = require('../api/applications.api');
11
11
 
12
12
  /**
@@ -69,9 +69,9 @@ async function displayAppUrlFromController(controllerUrl, envKey, appKey, authCo
69
69
  url = buildAppUrlFromControllerAndPort(controllerUrl, port);
70
70
  }
71
71
  if (url) {
72
- logger.log(chalk.green(`App running at ${url}`));
72
+ logger.log(formatSuccessLine(`App running at ${url}`));
73
73
  } else {
74
- logger.log(chalk.blue('App deployed. Get URL from controller dashboard.'));
74
+ logger.log(metadata('App deployed. Resolve URL from the controller dashboard.'));
75
75
  }
76
76
  }
77
77
 
package/lib/app/deploy.js CHANGED
@@ -1,4 +1,10 @@
1
- const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
+ const {
2
+ formatSuccessLine,
3
+ formatSuccessParagraph,
4
+ sectionTitle,
5
+ headerKeyValue,
6
+ metadata
7
+ } = require('../utils/cli-test-layout-chalk');
2
8
  /**
3
9
  * AI Fabrix Builder Application Deployment Module
4
10
  *
@@ -13,6 +19,8 @@ const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test
13
19
  const fs = require('fs').promises;
14
20
  const chalk = require('chalk');
15
21
  const pushUtils = require('../deployment/push');
22
+
23
+ const SEP = chalk.gray('────────────────────────────────────────');
16
24
  const logger = require('../utils/logger');
17
25
  const { detectAppType, getBuilderPath, getIntegrationPath } = require('../utils/paths');
18
26
  const { checkApplicationExists } = require('../utils/app-existence');
@@ -156,7 +164,10 @@ async function pushApp(appName, options = {}) {
156
164
  * @throws {Error} If generation or validation fails
157
165
  */
158
166
  async function generateAndValidateManifest(appName, options = {}) {
159
- logger.log(chalk.blue(`\n📋 Generating deployment manifest for ${appName}...`));
167
+ logger.log('');
168
+ logger.log(sectionTitle('Deployment manifest'));
169
+ logger.log(SEP);
170
+ logger.log(metadata(`Generating manifest for ${appName}...`));
160
171
  const generator = require('../generator');
161
172
 
162
173
  // generateDeployJson already validates against schema and throws on error
@@ -175,10 +186,10 @@ async function generateAndValidateManifest(appName, options = {}) {
175
186
  */
176
187
  function displayDeploymentInfo(manifest, manifestPath) {
177
188
  logger.log(formatSuccessLine(`Manifest generated: ${manifestPath}`));
178
- logger.log(chalk.blue(` Key: ${manifest.key}`));
179
- logger.log(chalk.blue(` Display Name: ${manifest.displayName}`));
180
- logger.log(chalk.blue(` Image: ${manifest.image}`));
181
- logger.log(chalk.blue(` Port: ${manifest.port}`));
189
+ logger.log(headerKeyValue('Key:', manifest.key));
190
+ logger.log(headerKeyValue('Display Name:', manifest.displayName));
191
+ logger.log(headerKeyValue('Image:', manifest.image));
192
+ logger.log(headerKeyValue('Port:', String(manifest.port)));
182
193
  }
183
194
 
184
195
  /**
@@ -189,7 +200,11 @@ function displayDeploymentInfo(manifest, manifestPath) {
189
200
  * @returns {Promise<Object>} Deployment result
190
201
  */
191
202
  async function executeDeployment(manifest, deploymentConfig) {
192
- logger.log(chalk.blue(`\n🚀 Deploying to ${deploymentConfig.controllerUrl} (environment: ${deploymentConfig.envKey})...`));
203
+ logger.log('');
204
+ logger.log(sectionTitle('Deploy to controller'));
205
+ logger.log(SEP);
206
+ logger.log(headerKeyValue('URL:', deploymentConfig.controllerUrl));
207
+ logger.log(headerKeyValue('Environment:', deploymentConfig.envKey));
193
208
  const deployer = require('../deployment/deployer');
194
209
  return await deployer.deployToController(
195
210
  manifest,
@@ -199,7 +214,9 @@ async function executeDeployment(manifest, deploymentConfig) {
199
214
  {
200
215
  poll: deploymentConfig.poll,
201
216
  pollInterval: deploymentConfig.pollInterval,
202
- pollMaxAttempts: deploymentConfig.pollMaxAttempts
217
+ pollMaxAttempts: deploymentConfig.pollMaxAttempts,
218
+ repositoryUrl: deploymentConfig.repositoryUrl,
219
+ silentPoll: deploymentConfig.silentPoll === true
203
220
  }
204
221
  );
205
222
  }
@@ -209,17 +226,22 @@ async function executeDeployment(manifest, deploymentConfig) {
209
226
  * @param {Object} result - Deployment result
210
227
  */
211
228
  function displayDeploymentResults(result) {
212
- logger.log(chalk.green('\n✅ Deployment initiated successfully'));
229
+ logger.log(formatSuccessParagraph('Deployment initiated successfully'));
213
230
  if (result.deploymentUrl) {
214
- logger.log(chalk.blue(` URL: ${result.deploymentUrl}`));
231
+ logger.log(headerKeyValue('URL:', result.deploymentUrl));
215
232
  }
216
233
  if (result.deploymentId) {
217
- logger.log(chalk.blue(` Deployment ID: ${result.deploymentId}`));
234
+ logger.log(headerKeyValue('Deployment ID:', result.deploymentId));
218
235
  }
219
236
  if (result.status) {
220
- const statusIcon = result.status.status === 'completed' ? '✔' :
221
- result.status.status === 'failed' ? '✖' : '⏳';
222
- logger.log(chalk.blue(` Status: ${statusIcon} ${result.status.status}`));
237
+ const st = result.status.status;
238
+ const statusText =
239
+ st === 'completed'
240
+ ? formatSuccessLine(st)
241
+ : st === 'failed' || st === 'cancelled'
242
+ ? chalk.red(`✖ ${st}`)
243
+ : chalk.yellow(`⏳ ${st}`);
244
+ logger.log(`${chalk.gray('Status:')} ${statusText}`);
223
245
  }
224
246
  }
225
247
 
@@ -1,4 +1,4 @@
1
- const { formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
+ const { formatNextActions, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
2
2
  /**
3
3
  * Application Display Utilities
4
4
  *
@@ -24,11 +24,13 @@ const logger = require('../utils/logger');
24
24
  function displayExternalSystemSuccess(appName, config, location) {
25
25
  logger.log(chalk.blue('Type: External System'));
26
26
  logger.log(chalk.blue(`System Key: ${config.systemKey || appName}`));
27
- logger.log(chalk.green('\nNext steps:'));
28
- logger.log(chalk.white('1. Edit external system JSON files in ' + location));
29
- logger.log(chalk.white('2. Run: aifabrix validate ' + appName));
30
- logger.log(chalk.white('3. Run: aifabrix login'));
31
- logger.log(chalk.white('4. Run: aifabrix deploy ' + appName));
27
+ logger.log('');
28
+ logger.log(formatNextActions([
29
+ `Edit external system JSON files in ${location}`,
30
+ `Run: aifabrix validate ${appName}`,
31
+ 'Run: aifabrix login',
32
+ `Run: aifabrix deploy ${appName}`
33
+ ]));
32
34
  }
33
35
 
34
36
  /**
@@ -49,11 +51,13 @@ function displayWebappSuccess(appName, config, envConversionMessage) {
49
51
 
50
52
  logger.log(chalk.gray(envConversionMessage));
51
53
 
52
- logger.log(chalk.green('\nNext steps:'));
53
- logger.log(chalk.white('1. Copy env.template to .env and fill in your values'));
54
- logger.log(chalk.white('2. Run: aifabrix up-infra'));
55
- logger.log(chalk.white('3. Run: aifabrix build ' + appName));
56
- logger.log(chalk.white('4. Run: aifabrix run ' + appName));
54
+ logger.log('');
55
+ logger.log(formatNextActions([
56
+ 'Copy env.template to .env and fill in your values',
57
+ 'Run: aifabrix up-infra',
58
+ `Run: aifabrix build ${appName}`,
59
+ `Run: aifabrix run ${appName}`
60
+ ]));
57
61
  }
58
62
 
59
63
  /**
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
-
@@ -99,7 +99,7 @@ async function saveLocalCredentials(responseData, apiUrl) {
99
99
 
100
100
  // Regenerate .env file with updated credentials
101
101
  try {
102
- await generateEnvFile(registeredAppKey, null, 'local');
102
+ await generateEnvFile(registeredAppKey, null, 'local', true);
103
103
  logger.log(formatSuccessLine('.env file updated with new credentials'));
104
104
  } catch (error) {
105
105
  logger.warn(chalk.yellow(`⚠ Could not regenerate .env file: ${error.message}`));
@@ -0,0 +1,95 @@
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
+
17
+ /**
18
+ * @param {unknown} mounts - docker inspect .Mounts
19
+ * @returns {{ Type: string, Source: string, Destination: string }|null}
20
+ */
21
+ function findAppBindMount(mounts) {
22
+ if (!Array.isArray(mounts)) {
23
+ return null;
24
+ }
25
+ const hit = mounts.find((m) => m && m.Type === 'bind' && m.Destination === '/app');
26
+ return hit && typeof hit.Source === 'string' ? hit : null;
27
+ }
28
+
29
+ /**
30
+ * @param {string} stdout
31
+ * @returns {unknown|null}
32
+ */
33
+ function parseInspectMountsStdout(stdout) {
34
+ try {
35
+ return JSON.parse(String(stdout || '').trim());
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * @param {string} containerName
43
+ * @returns {Promise<unknown|null>}
44
+ */
45
+ async function fetchContainerMountsJson(containerName) {
46
+ try {
47
+ const { stdout } = await execWithDockerEnv(
48
+ `docker inspect --format '{{json .Mounts}}' ${containerName}`,
49
+ { maxBuffer: 2 * 1024 * 1024 }
50
+ );
51
+ return parseInspectMountsStdout(stdout);
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Log workspace transport after a successful container restart (mounts unchanged).
59
+ * @param {string} containerName - Docker container name
60
+ * @returns {Promise<void>}
61
+ */
62
+ async function logRestartDevMountSummary(containerName) {
63
+ if (!containerName || typeof containerName !== 'string') {
64
+ return;
65
+ }
66
+ const mounts = await fetchContainerMountsJson(containerName);
67
+ const appBind = findAppBindMount(mounts);
68
+ if (!appBind) {
69
+ return;
70
+ }
71
+ const endpoint = await config.getDockerEndpoint();
72
+ const localEngine = isReloadBindMountOnEngineHost(endpoint);
73
+
74
+ logger.log('');
75
+ logger.log(sectionTitle('Dev workspace (unchanged by restart)'));
76
+ if (localEngine) {
77
+ logger.log(headerKeyValue('Transport:', 'Direct bind mount on the Docker host (no Mutagen).'));
78
+ logger.log(headerKeyValue('Host path → container:', `${appBind.Source} → /app`));
79
+ logger.log(metadata('Edits under the host path are visible inside the container immediately.'));
80
+ } else {
81
+ logger.log(
82
+ headerKeyValue(
83
+ 'Transport:',
84
+ 'Bind mount on the Docker engine (see path below; Mutagen may sync to this path when using --reload).'
85
+ )
86
+ );
87
+ logger.log(headerKeyValue('Engine path → container:', `${appBind.Source} → /app`));
88
+ }
89
+ logger.log('');
90
+ }
91
+
92
+ module.exports = {
93
+ findAppBindMount,
94
+ logRestartDevMountSummary
95
+ };
@@ -287,7 +287,7 @@ async function saveCredentialsLocally(appKey, credentials, actualControllerUrl)
287
287
 
288
288
  // Regenerate .env file with updated credentials
289
289
  try {
290
- await generateEnvFile(appKey, null, 'local');
290
+ await generateEnvFile(appKey, null, 'local', true);
291
291
  logger.log(formatSuccessLine('.env file updated with new credentials'));
292
292
  } catch (error) {
293
293
  logger.warn(chalk.yellow(`⚠ Could not regenerate .env file: ${error.message}`));
@@ -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;