@aifabrix/builder 2.44.6 → 2.45.5

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 (257) hide show
  1. package/.cursor/rules/cli-layout.mdc +7 -3
  2. package/.cursor/rules/docs-rules.mdc +3 -1
  3. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  4. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  5. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
  6. package/jest.projects.js +100 -1
  7. package/lib/api/agent-metadata-validation.api.js +93 -0
  8. package/lib/api/audit-api-helpers.js +68 -0
  9. package/lib/api/audit.api.js +119 -0
  10. package/lib/api/auth-cache.api.js +44 -0
  11. package/lib/api/certificates.api.js +4 -4
  12. package/lib/api/credential.api.js +2 -2
  13. package/lib/api/dataplane-health.api.js +137 -0
  14. package/lib/api/dataplane-sync.api.js +27 -0
  15. package/lib/api/datasources-core.api.js +21 -18
  16. package/lib/api/datasources-extended.api.js +22 -22
  17. package/lib/api/dimensions.api.js +3 -0
  18. package/lib/api/external-systems.api.js +13 -13
  19. package/lib/api/governance-scenarios.api.js +39 -0
  20. package/lib/api/groups.api.js +79 -0
  21. package/lib/api/index.js +45 -2
  22. package/lib/api/pipeline.api.js +4 -4
  23. package/lib/api/protection.api.js +157 -0
  24. package/lib/api/records-bulk.api.js +30 -0
  25. package/lib/api/records-search-parse.js +49 -0
  26. package/lib/api/records-search.api.js +29 -0
  27. package/lib/api/types/agent-metadata-validation.types.js +67 -0
  28. package/lib/api/types/audit.types.js +72 -0
  29. package/lib/api/types/dataplane-health.types.js +27 -0
  30. package/lib/api/types/dimensions.types.js +2 -0
  31. package/lib/api/types/governance-scenarios.types.js +39 -0
  32. package/lib/api/types/protection.types.js +69 -0
  33. package/lib/api/types/records-bulk.types.js +35 -0
  34. package/lib/api/types/records-search.types.js +28 -0
  35. package/lib/api/user-groups.api.js +50 -0
  36. package/lib/api/users.api.js +95 -0
  37. package/lib/api/validation-run.api.js +3 -3
  38. package/lib/api/validation-runner.js +3 -3
  39. package/lib/api/wizard-platform.api.js +3 -3
  40. package/lib/api/wizard.api.js +21 -21
  41. package/lib/app/deploy.js +34 -19
  42. package/lib/app/helpers.js +3 -3
  43. package/lib/app/index.js +3 -3
  44. package/lib/app/register.js +7 -6
  45. package/lib/app/restart-display.js +52 -21
  46. package/lib/app/rotate-secret.js +7 -6
  47. package/lib/app/run-helpers.js +15 -8
  48. package/lib/app/run.js +57 -9
  49. package/lib/app/show-display.js +7 -0
  50. package/lib/app/show.js +87 -5
  51. package/lib/build/index.js +9 -5
  52. package/lib/cli/index.js +4 -2
  53. package/lib/cli/infra-guided.js +62 -28
  54. package/lib/cli/installation-log-command.js +73 -0
  55. package/lib/cli/setup-app.help.js +50 -2
  56. package/lib/cli/setup-app.js +44 -25
  57. package/lib/cli/setup-app.test-commands.js +80 -21
  58. package/lib/cli/setup-auth.js +124 -52
  59. package/lib/cli/setup-external-system.js +37 -3
  60. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  61. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  62. package/lib/cli/setup-infra.js +60 -119
  63. package/lib/cli/setup-platform.js +1 -1
  64. package/lib/cli/setup-utility-help-after.js +45 -0
  65. package/lib/cli/setup-utility-resolve.js +132 -0
  66. package/lib/cli/setup-utility.js +148 -116
  67. package/lib/commands/app-logs.js +81 -33
  68. package/lib/commands/auth-config.js +116 -18
  69. package/lib/commands/auth-status-dataplane-version.js +231 -0
  70. package/lib/commands/auth-status-display.js +94 -0
  71. package/lib/commands/auth-status.js +70 -6
  72. package/lib/commands/credential-push.js +10 -5
  73. package/lib/commands/datasource-load-export-cli.js +178 -0
  74. package/lib/commands/datasource-test-trust-cli.js +114 -0
  75. package/lib/commands/datasource-unified-test-cli.js +25 -17
  76. package/lib/commands/datasource-unified-test-cli.options.js +18 -3
  77. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
  78. package/lib/commands/datasource-verify-audit-cli.js +96 -0
  79. package/lib/commands/datasource.js +68 -1
  80. package/lib/commands/dimension-value.js +43 -13
  81. package/lib/commands/dimension.js +38 -7
  82. package/lib/commands/identity-apply-sync.js +131 -0
  83. package/lib/commands/identity-group-cli.js +146 -0
  84. package/lib/commands/identity-membership-role-cache.js +175 -0
  85. package/lib/commands/identity-shared.js +94 -0
  86. package/lib/commands/identity-user-cli.js +158 -0
  87. package/lib/commands/identity.js +43 -0
  88. package/lib/commands/login.js +46 -16
  89. package/lib/commands/protection-cli-leaves.js +286 -0
  90. package/lib/commands/protection.js +50 -0
  91. package/lib/commands/repair.js +13 -3
  92. package/lib/commands/setup-modes.js +19 -6
  93. package/lib/commands/setup-prompts.js +41 -8
  94. package/lib/commands/setup.js +114 -9
  95. package/lib/commands/teardown.js +54 -5
  96. package/lib/commands/test-e2e-external.js +40 -9
  97. package/lib/commands/test-governance-cli-options.js +47 -0
  98. package/lib/commands/test-governance-command-action.js +55 -0
  99. package/lib/commands/test-governance-external.js +88 -0
  100. package/lib/commands/test-trust-cli-options.js +32 -0
  101. package/lib/commands/test-trust-command-action.js +118 -0
  102. package/lib/commands/test-trust-external.js +127 -0
  103. package/lib/commands/up-common.js +48 -14
  104. package/lib/commands/up-dataplane.js +21 -18
  105. package/lib/commands/up-miso.js +12 -8
  106. package/lib/commands/upload.js +38 -15
  107. package/lib/commands/wizard-core.js +3 -0
  108. package/lib/core/audit-logger.js +1 -34
  109. package/lib/core/config-admin-email.js +56 -0
  110. package/lib/core/config-device-dataplane.js +185 -0
  111. package/lib/core/config-normalize.js +60 -0
  112. package/lib/core/config-registered-controller-urls.js +54 -0
  113. package/lib/core/config.js +33 -50
  114. package/lib/core/secrets-ensure-infra.js +1 -1
  115. package/lib/core/secrets-env-content.js +86 -90
  116. package/lib/core/secrets-env-declarative-expand.js +170 -0
  117. package/lib/core/secrets-env-write.js +2 -0
  118. package/lib/core/secrets-load.js +106 -102
  119. package/lib/datasource/agent-trust-debug-log.js +45 -0
  120. package/lib/datasource/agent-trust-fetch.js +115 -0
  121. package/lib/datasource/agent-trust-map.js +101 -0
  122. package/lib/datasource/agent-trust-mock.js +44 -0
  123. package/lib/datasource/agent-trust-run.js +93 -0
  124. package/lib/datasource/agent-trust-timeout.js +25 -0
  125. package/lib/datasource/audit-evidence-display.js +80 -0
  126. package/lib/datasource/audit-evidence-extract-debug.js +103 -0
  127. package/lib/datasource/audit-evidence-extract.js +177 -0
  128. package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
  129. package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
  130. package/lib/datasource/audit-evidence-poll.js +109 -0
  131. package/lib/datasource/audit-evidence-run.js +177 -0
  132. package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
  133. package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
  134. package/lib/datasource/audit-evidence-verify.js +108 -0
  135. package/lib/datasource/bulk-loader-run-batches.js +88 -0
  136. package/lib/datasource/bulk-loader-service-retry.js +52 -0
  137. package/lib/datasource/bulk-loader-service.js +127 -0
  138. package/lib/datasource/capability/run-capability-dimension.js +2 -1
  139. package/lib/datasource/datasource-exporter-resolve.js +82 -0
  140. package/lib/datasource/datasource-exporter-service.js +144 -0
  141. package/lib/datasource/datasource-identifier.js +148 -0
  142. package/lib/datasource/datasource-load-export-context.js +92 -0
  143. package/lib/datasource/integration-context.js +6 -4
  144. package/lib/datasource/local-data-paths.js +145 -0
  145. package/lib/datasource/log-cleaner.js +241 -0
  146. package/lib/datasource/log-viewer.js +111 -6
  147. package/lib/datasource/record-file-parser.js +85 -0
  148. package/lib/datasource/record-mapper.js +129 -0
  149. package/lib/datasource/resolve-app.js +74 -55
  150. package/lib/datasource/test-e2e.js +26 -2
  151. package/lib/datasource/unified-validation-run-resolve.js +45 -12
  152. package/lib/datasource/unified-validation-run.js +43 -17
  153. package/lib/datasource/validate.js +37 -20
  154. package/lib/external-system/deploy.js +5 -1
  155. package/lib/external-system/test-system-level.js +25 -4
  156. package/lib/external-system/test.js +3 -1
  157. package/lib/governance/governance-pack-loader.js +70 -0
  158. package/lib/governance/governance-run-display.js +119 -0
  159. package/lib/identity/identity-apply-core.js +108 -0
  160. package/lib/identity/identity-apply-phases.js +244 -0
  161. package/lib/identity/identity-apply-service.js +77 -0
  162. package/lib/identity/identity-csv-parser.js +114 -0
  163. package/lib/internal/fs-real-sync.js +29 -1
  164. package/lib/internal/node-fs.js +2 -0
  165. package/lib/protection/auth-context.js +45 -0
  166. package/lib/protection/convert-batch.js +90 -0
  167. package/lib/protection/load.js +41 -0
  168. package/lib/protection/paths.js +158 -0
  169. package/lib/protection/preflight-datasource-ready.js +46 -0
  170. package/lib/protection/protection-create-probe-lines.js +142 -0
  171. package/lib/protection/protection-create-probes.js +95 -0
  172. package/lib/protection/protection-create-scaffold.js +281 -0
  173. package/lib/protection/protection-display-helpers.js +445 -0
  174. package/lib/protection/protection-display.js +215 -0
  175. package/lib/protection/protection-preset-registry.js +97 -0
  176. package/lib/protection/report-exit.js +62 -0
  177. package/lib/protection/resolve.js +202 -0
  178. package/lib/protection/run-commands-validate.js +130 -0
  179. package/lib/protection/run-commands.js +216 -0
  180. package/lib/protection/run-protection-create-helpers.js +134 -0
  181. package/lib/protection/run-protection-create-probes-flow.js +43 -0
  182. package/lib/protection/run-protection-create.js +191 -0
  183. package/lib/protection/scope.js +22 -0
  184. package/lib/protection/sync-after-upload.js +64 -0
  185. package/lib/protection/upload-batch.js +127 -0
  186. package/lib/protection/validate-batch.js +113 -0
  187. package/lib/protection/validate-local.js +65 -0
  188. package/lib/resolvers/dimension-file.js +29 -4
  189. package/lib/schema/application-schema.json +4 -0
  190. package/lib/schema/external-datasource.schema.json +47 -13
  191. package/lib/schema/flag-map-validation-run.json +2 -2
  192. package/lib/schema/infra.parameter.yaml +10 -0
  193. package/lib/schema/protection.schema.json +374 -0
  194. package/lib/schema/type/credential-field-registry.json +636 -0
  195. package/lib/schema/wizard-config.schema.json +1 -1
  196. package/lib/utils/agent-trust-run-display.js +231 -0
  197. package/lib/utils/agent-trust-run-exit.js +59 -0
  198. package/lib/utils/agent-trust-wait-ui.js +94 -0
  199. package/lib/utils/app-config-resolver.js +24 -1
  200. package/lib/utils/applications-config-defaults.js +206 -0
  201. package/lib/utils/auth-config-validator.js +2 -12
  202. package/lib/utils/bash-secret-env.js +1 -1
  203. package/lib/utils/cli-exit-codes.js +30 -0
  204. package/lib/utils/cli-sync-options.js +33 -0
  205. package/lib/utils/compose-generate-docker-compose.js +111 -6
  206. package/lib/utils/compose-generator.js +17 -8
  207. package/lib/utils/controller-url.js +50 -7
  208. package/lib/utils/credential-push-ui.js +90 -0
  209. package/lib/utils/dataplane-cli-version-gate.js +202 -0
  210. package/lib/utils/dataplane-cli-version-help.js +74 -0
  211. package/lib/utils/datasource-export-display.js +137 -0
  212. package/lib/utils/datasource-load-display.js +174 -0
  213. package/lib/utils/datasource-load-export-ui.js +120 -0
  214. package/lib/utils/datasource-test-run-capability-scope.js +3 -3
  215. package/lib/utils/datasource-test-run-display.js +3 -3
  216. package/lib/utils/datasource-test-run-tty-log.js +5 -1
  217. package/lib/utils/env-copy.js +99 -14
  218. package/lib/utils/env-template.js +5 -1
  219. package/lib/utils/health-check-url.js +18 -15
  220. package/lib/utils/health-check.js +7 -5
  221. package/lib/utils/help-builder.js +30 -24
  222. package/lib/utils/infra-optional-service-flags.js +69 -0
  223. package/lib/utils/installation-log-core.js +282 -0
  224. package/lib/utils/installation-log-record.js +237 -0
  225. package/lib/utils/installation-log.js +123 -0
  226. package/lib/utils/log-redaction.js +105 -0
  227. package/lib/utils/manifest-location.js +164 -0
  228. package/lib/utils/manifest-source-emit.js +184 -0
  229. package/lib/utils/paths.js +238 -89
  230. package/lib/utils/remote-secrets-loader.js +7 -1
  231. package/lib/utils/run-cli-flags.js +29 -0
  232. package/lib/utils/secrets-canonical.js +10 -3
  233. package/lib/utils/secrets-path.js +3 -4
  234. package/lib/utils/secrets-utils.js +20 -10
  235. package/lib/utils/semver-compare.js +127 -0
  236. package/lib/utils/system-builder-root.js +10 -2
  237. package/lib/utils/upload-pipeline-ui.js +91 -0
  238. package/lib/utils/url-declarative-public-base.js +80 -12
  239. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  240. package/lib/utils/url-declarative-resolve-build.js +24 -393
  241. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  242. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  243. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  244. package/lib/utils/url-declarative-resolve.js +47 -7
  245. package/lib/utils/url-declarative-runtime-base-path.js +21 -1
  246. package/lib/utils/urls-local-registry-scan.js +103 -0
  247. package/lib/utils/urls-local-registry.js +161 -90
  248. package/lib/utils/validation-poll-ui.js +14 -7
  249. package/lib/validation/wizard-config-validator.js +58 -19
  250. package/package.json +3 -1
  251. package/templates/applications/dataplane/application.yaml +4 -0
  252. package/templates/applications/dataplane/env.template +13 -1
  253. package/templates/applications/dataplane/rbac.yaml +4 -0
  254. package/templates/applications/miso-controller/application.yaml +2 -0
  255. package/templates/applications/miso-controller/env.template +27 -29
  256. package/templates/external-system/README.md.hbs +56 -8
  257. package/.npmrc.token +0 -1
@@ -8,53 +8,144 @@
8
8
  * @version 2.0.0
9
9
  */
10
10
 
11
+ const chalk = require('chalk');
11
12
  const { formatBlockingError, formatSuccessLine } = require('../utils/cli-test-layout-chalk');
12
13
  const {
13
14
  setControllerUrl,
14
15
  setCurrentEnvironment,
15
- getControllerUrl
16
+ getControllerUrl,
17
+ getRegisteredControllerUrls,
18
+ getConfig
16
19
  } = require('../core/config');
17
20
  const {
18
21
  validateControllerUrl,
19
22
  validateEnvironment,
20
23
  checkUserLoggedIn
21
24
  } = require('../utils/auth-config-validator');
22
- const { getControllerUrlFromLoggedInUser } = require('../utils/controller-url');
25
+ const { hasStoredDeviceTokenForController } = require('../utils/controller-url');
23
26
  const logger = require('../utils/logger');
24
27
 
28
+ /**
29
+ * True when user ran --set-controller with no URL (pick from config.yaml).
30
+ * @param {unknown} setController - Commander option value
31
+ * @returns {boolean}
32
+ */
33
+ function isInteractiveControllerPick(setController) {
34
+ return (
35
+ setController === true ||
36
+ (typeof setController === 'string' && setController.trim() === '')
37
+ );
38
+ }
39
+
40
+ /**
41
+ * True when a non-empty controller URL string was passed.
42
+ * @param {unknown} setController - Commander option value
43
+ * @returns {boolean}
44
+ */
45
+ function hasExplicitControllerUrl(setController) {
46
+ return typeof setController === 'string' && setController.trim() !== '';
47
+ }
48
+
49
+ /**
50
+ * Normalize controller URL for equality checks (trailing slashes).
51
+ * @param {string|null|undefined} url - URL or empty
52
+ * @returns {string}
53
+ */
54
+ function normalizeForCompare(url) {
55
+ if (!url || typeof url !== 'string') return '';
56
+ return url.trim().replace(/\/+$/, '');
57
+ }
58
+
59
+ function throwNoRegisteredControllers() {
60
+ const msg =
61
+ 'No controllers are registered in config. Run "aifabrix login" first, or set a controller with "aifabrix auth --set-controller <url>".';
62
+ logger.error(formatBlockingError(msg));
63
+ throw new Error(msg);
64
+ }
65
+
66
+ function throwNonInteractiveControllerPick() {
67
+ const msg =
68
+ 'Cannot choose a controller without a URL in non-interactive mode. Run: aifabrix auth --set-controller <url>';
69
+ logger.error(formatBlockingError(msg));
70
+ throw new Error(msg);
71
+ }
72
+
73
+ /**
74
+ * Pick default controller from URLs in config (`controller` + `device` keys), or set the only one.
75
+ * @async
76
+ * @returns {Promise<void>}
77
+ */
78
+ async function handleSelectRegisteredController() {
79
+ const urls = await getRegisteredControllerUrls();
80
+
81
+ if (urls.length === 0) {
82
+ throwNoRegisteredControllers();
83
+ }
84
+
85
+ if (!process.stdin.isTTY) {
86
+ throwNonInteractiveControllerPick();
87
+ }
88
+
89
+ if (urls.length === 1) {
90
+ const sole = urls[0];
91
+ const current = await getControllerUrl();
92
+ if (normalizeForCompare(current) === normalizeForCompare(sole)) {
93
+ logger.log(formatSuccessLine(`Default controller is already set to ${sole}.`));
94
+ logger.log(
95
+ chalk.white('To add another controller, run: aifabrix auth --set-controller <url>')
96
+ );
97
+ return;
98
+ }
99
+ await handleSetController(sole);
100
+ return;
101
+ }
102
+
103
+ const inquirer = require('inquirer');
104
+ const { controllerUrl } = await inquirer.prompt([
105
+ {
106
+ type: 'list',
107
+ name: 'controllerUrl',
108
+ message: 'Select default controller:',
109
+ choices: urls
110
+ }
111
+ ]);
112
+ await handleSetController(controllerUrl);
113
+ }
114
+
25
115
  /**
26
116
  * Handle set-controller command
27
- * Allows setting the default controller when no credentials are stored, or when already logged in to that controller.
28
- * If credentials exist for a different controller, throws with a clear message.
117
+ * Allows setting the default controller when there are no device tokens, or when a token exists
118
+ * for the target URL (including switching among multiple logged-in controllers). If device tokens
119
+ * exist only for other controller URLs, throws with a clear message.
29
120
  *
30
121
  * @async
31
122
  * @function handleSetController
32
123
  * @param {string} url - Controller URL to set
33
124
  * @returns {Promise<void>}
34
- * @throws {Error} If validation fails or credentials exist for another controller
125
+ * @throws {Error} If validation fails or credentials exist only for other controllers
35
126
  */
36
127
  async function handleSetController(url) {
37
128
  try {
38
129
  validateControllerUrl(url);
39
130
  const normalizedUrl = url.trim().replace(/\/+$/, '');
40
131
 
41
- const loggedInControllerUrl = await getControllerUrlFromLoggedInUser();
42
- if (!loggedInControllerUrl) {
43
- // No stored credentials: allow setting controller so "aifabrix login" opens the right place
44
- await setControllerUrl(url);
45
- logger.log(formatSuccessLine(`Controller URL set to: ${url}`));
46
- return;
47
- }
132
+ const userConfig = await getConfig();
133
+ const device =
134
+ userConfig.device && typeof userConfig.device === 'object' ? userConfig.device : {};
135
+ const deviceKeys = Object.keys(device);
136
+ const hasTokenForTarget = await hasStoredDeviceTokenForController(normalizedUrl);
48
137
 
49
- const normalizedLoggedIn = loggedInControllerUrl.trim().replace(/\/+$/, '');
50
- if (normalizedLoggedIn === normalizedUrl) {
138
+ if (deviceKeys.length === 0 || hasTokenForTarget) {
51
139
  await setControllerUrl(url);
52
140
  logger.log(formatSuccessLine(`Controller URL set to: ${url}`));
53
141
  return;
54
142
  }
55
143
 
144
+ const otherKey =
145
+ deviceKeys.find((k) => normalizeForCompare(k) !== normalizeForCompare(normalizedUrl)) ||
146
+ deviceKeys[0];
56
147
  throw new Error(
57
- `You have credentials for another controller (${loggedInControllerUrl}).\n` +
148
+ `You have credentials for another controller (${otherKey.trim().replace(/\/+$/, '')}).\n` +
58
149
  'To use a different controller either run "aifabrix login" with that controller, or run "aifabrix logout" first to clear credentials, then set the new controller with "aifabrix auth --set-controller <url>".'
59
150
  );
60
151
  } catch (error) {
@@ -107,20 +198,27 @@ async function handleSetEnvironment(environment) {
107
198
  * @async
108
199
  * @function handleAuthConfig
109
200
  * @param {Object} options - Command options
110
- * @param {string} [options.setController] - Controller URL to set
201
+ * @param {string|boolean} [options.setController] - Controller URL, or true when flag has no value
111
202
  * @param {string} [options.setEnvironment] - Environment to set
112
203
  * @returns {Promise<void>}
113
204
  * @throws {Error} If command fails
114
205
  */
115
206
  async function handleAuthConfig(options) {
116
- if (!options.setController && !options.setEnvironment) {
207
+ const pick = isInteractiveControllerPick(options.setController);
208
+ const hasUrl = hasExplicitControllerUrl(options.setController);
209
+
210
+ if (!pick && !hasUrl && !options.setEnvironment) {
117
211
  throw new Error(
118
212
  'No action specified. Use "aifabrix auth --set-controller <url>" or "aifabrix auth --set-environment <env>".'
119
213
  );
120
214
  }
121
- if (options.setController) {
215
+
216
+ if (pick) {
217
+ await handleSelectRegisteredController();
218
+ } else if (hasUrl) {
122
219
  await handleSetController(options.setController);
123
220
  }
221
+
124
222
  if (options.setEnvironment) {
125
223
  await handleSetEnvironment(options.setEnvironment);
126
224
  }
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Auth-status dataplane version refresh + compatibility resolution (plan 142.0).
3
+ *
4
+ * Extracted from `auth-status.js` to keep that file under the 500-line limit
5
+ * (`.cursor/rules/project-rules.mdc → File and Function Size Limits`). The
6
+ * helpers here are pure-ish (only depend on the dataplane health API and the
7
+ * `device.<controllerUrl>` cache module) so they are easy to mock in unit
8
+ * tests.
9
+ *
10
+ * @fileoverview Resolve dataplane version + Builder CLI compatibility for auth status
11
+ * @author AI Fabrix Team
12
+ * @version 2.0.0
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ const { fetchDataplaneGeneralHealth } = require('../api/dataplane-health.api');
18
+ const {
19
+ updateDeviceDataplaneVersions,
20
+ getDeviceDataplaneVersions
21
+ } = require('../core/config-device-dataplane');
22
+ const {
23
+ getInstalledCliVersion
24
+ } = require('../utils/dataplane-cli-version-gate');
25
+ const { isAtLeast, isValidSemver } = require('../utils/semver-compare');
26
+
27
+ /**
28
+ * Per plan 142.0: invalid semver from the dataplane is logged but does **not**
29
+ * block the CLI; missing min version means no enforcement.
30
+ *
31
+ * @param {string} cliVersion
32
+ * @param {string|undefined} minBuilderCliVersion
33
+ * @returns {boolean}
34
+ */
35
+ function computeCompatibility(cliVersion, minBuilderCliVersion) {
36
+ if (!minBuilderCliVersion) return true;
37
+ if (!isValidSemver(minBuilderCliVersion) || !isValidSemver(cliVersion)) {
38
+ return true;
39
+ }
40
+ return isAtLeast(cliVersion, minBuilderCliVersion);
41
+ }
42
+
43
+ /**
44
+ * Read `device.<controllerUrl>` cache without throwing.
45
+ * @private
46
+ * @async
47
+ * @param {string} controllerUrl
48
+ * @returns {Promise<Object|null>}
49
+ */
50
+ async function safeReadCache(controllerUrl) {
51
+ if (!controllerUrl) return null;
52
+ try {
53
+ return await getDeviceDataplaneVersions(controllerUrl);
54
+ } catch (_error) {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Refresh dataplane health, persist `device.<controllerUrl>` version cache,
61
+ * and derive the compatibility verdict against the installed Builder CLI.
62
+ * Falls back to the cached entry when the live probe fails so `auth status`
63
+ * still shows the last-known values offline.
64
+ *
65
+ * @async
66
+ * @param {string} controllerUrl - Controller URL (cache key)
67
+ * @param {string} dataplaneUrl - Dataplane base URL for the health probe
68
+ * @returns {Promise<{
69
+ * dataplaneVersion: string|undefined,
70
+ * minBuilderCliVersion: string|undefined,
71
+ * cliVersion: string,
72
+ * compatible: boolean
73
+ * }>}
74
+ */
75
+ async function refreshDataplaneVersionInfo(controllerUrl, dataplaneUrl) {
76
+ const cliVersion = getInstalledCliVersion();
77
+ let dataplaneVersion;
78
+ let minBuilderCliVersion;
79
+
80
+ try {
81
+ const snapshot = await fetchDataplaneGeneralHealth(dataplaneUrl);
82
+ if (snapshot) {
83
+ dataplaneVersion = snapshot.version;
84
+ minBuilderCliVersion = snapshot.minBuilderCliVersion;
85
+ try {
86
+ await updateDeviceDataplaneVersions(controllerUrl, {
87
+ version: dataplaneVersion,
88
+ minBuilderCliVersion: minBuilderCliVersion || null
89
+ });
90
+ } catch (_persistError) {
91
+ // cache write failure must not break the display
92
+ }
93
+ }
94
+ } catch (_fetchError) {
95
+ // ignore — fall through to cached values
96
+ }
97
+
98
+ if (!dataplaneVersion && !minBuilderCliVersion) {
99
+ const cached = await safeReadCache(controllerUrl);
100
+ if (cached) {
101
+ dataplaneVersion = cached.version;
102
+ minBuilderCliVersion = cached.minBuilderCliVersion;
103
+ }
104
+ }
105
+
106
+ const compatible = computeCompatibility(cliVersion, minBuilderCliVersion);
107
+ return { dataplaneVersion, minBuilderCliVersion, cliVersion, compatible };
108
+ }
109
+
110
+ /**
111
+ * Load cached versions only (no live probe). Used when the dataplane is
112
+ * unreachable so `auth status` can still surface a known compatibility floor.
113
+ *
114
+ * @async
115
+ * @param {string} controllerUrl
116
+ * @returns {Promise<{
117
+ * dataplaneVersion: string|undefined,
118
+ * minBuilderCliVersion: string|undefined,
119
+ * cliVersion: string,
120
+ * compatible: boolean
121
+ * }>}
122
+ */
123
+ async function loadCachedVersionInfo(controllerUrl) {
124
+ const cliVersion = getInstalledCliVersion();
125
+ const cached = await safeReadCache(controllerUrl);
126
+ const dataplaneVersion = cached ? cached.version : undefined;
127
+ const minBuilderCliVersion = cached ? cached.minBuilderCliVersion : undefined;
128
+ return {
129
+ dataplaneVersion,
130
+ minBuilderCliVersion,
131
+ cliVersion,
132
+ compatible: computeCompatibility(cliVersion, minBuilderCliVersion)
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Resolve bearer auth after login (device token preferred, else client app token).
138
+ * @private
139
+ * @async
140
+ * @param {string} controllerUrl
141
+ * @param {string} environment
142
+ * @param {Object} loginOptions
143
+ * @returns {Promise<Object|null>}
144
+ */
145
+ async function resolveAuthConfigAfterLogin(controllerUrl, environment, loginOptions) {
146
+ const { getOrRefreshDeviceToken, getDeploymentAuth } = require('../utils/token-manager');
147
+ const device = await getOrRefreshDeviceToken(controllerUrl);
148
+ if (device && device.token) {
149
+ return { type: 'bearer', token: device.token };
150
+ }
151
+ if (!loginOptions.app) {
152
+ return null;
153
+ }
154
+ try {
155
+ return await getDeploymentAuth(controllerUrl, environment, loginOptions.app);
156
+ } catch (_authError) {
157
+ return null;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Resolve dataplane URL after login (mirrors auth-status silent resolver).
163
+ * @private
164
+ * @async
165
+ * @param {string} controllerUrl
166
+ * @param {string} environment
167
+ * @param {Object} authConfig
168
+ * @returns {Promise<string|null>}
169
+ */
170
+ async function resolveDataplaneUrlAfterLogin(controllerUrl, environment, authConfig) {
171
+ const { findDataplaneServiceAppKey } = require('./wizard-dataplane');
172
+ const { getDataplaneUrl } = require('../datasource/deploy');
173
+ try {
174
+ const dataplaneAppKey = await findDataplaneServiceAppKey(
175
+ controllerUrl,
176
+ environment,
177
+ authConfig
178
+ );
179
+ if (dataplaneAppKey) {
180
+ return await getDataplaneUrl(controllerUrl, dataplaneAppKey, environment, authConfig);
181
+ }
182
+ return await getDataplaneUrl(controllerUrl, 'dataplane', environment, authConfig);
183
+ } catch (_resolveError) {
184
+ return null;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Best-effort health refresh after login when dataplane URL is discoverable.
190
+ * Never throws — cache failures must not block login success.
191
+ *
192
+ * @async
193
+ * @param {string} controllerUrl - Controller URL (device cache key)
194
+ * @param {Object} [loginOptions]
195
+ * @param {string} [loginOptions.app] - App name (credentials login) for client-token auth
196
+ * @returns {Promise<void>}
197
+ */
198
+ async function tryRefreshDataplaneVersionAfterLogin(controllerUrl, loginOptions = {}) {
199
+ if (!controllerUrl || typeof controllerUrl !== 'string') {
200
+ return;
201
+ }
202
+ try {
203
+ const { resolveEnvironment } = require('../core/config');
204
+ const environment = await resolveEnvironment();
205
+ const authConfig = await resolveAuthConfigAfterLogin(
206
+ controllerUrl,
207
+ environment,
208
+ loginOptions
209
+ );
210
+ if (!authConfig) {
211
+ return;
212
+ }
213
+ const dataplaneUrl = await resolveDataplaneUrlAfterLogin(
214
+ controllerUrl,
215
+ environment,
216
+ authConfig
217
+ );
218
+ if (dataplaneUrl) {
219
+ await refreshDataplaneVersionInfo(controllerUrl, dataplaneUrl);
220
+ }
221
+ } catch (_error) {
222
+ // best-effort only
223
+ }
224
+ }
225
+
226
+ module.exports = {
227
+ refreshDataplaneVersionInfo,
228
+ loadCachedVersionInfo,
229
+ computeCompatibility,
230
+ tryRefreshDataplaneVersionAfterLogin
231
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Auth-status dataplane version block + blocking upgrade UX (plan 142.0).
3
+ *
4
+ * Kept in a separate module so `auth-status.js` stays under the 500-line
5
+ * limit and so the same display helpers can be reused by future dataplane
6
+ * gate surfaces (`upload`, `wizard`, `deploy`, …).
7
+ *
8
+ * @fileoverview TTY rendering for dataplane / Builder CLI compatibility
9
+ * @author AI Fabrix Team
10
+ * @version 2.0.0
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const chalk = require('chalk');
16
+ const logger = require('../utils/logger');
17
+ const {
18
+ successGlyph,
19
+ failureGlyph
20
+ } = require('../utils/cli-test-layout-chalk');
21
+ const {
22
+ formatCliVersionGateError
23
+ } = require('../utils/dataplane-cli-version-help');
24
+
25
+ /**
26
+ * Format a single key/value version line indented under the dataplane block.
27
+ * @private
28
+ * @param {string} label
29
+ * @param {string|undefined|null} value
30
+ * @returns {string}
31
+ */
32
+ function versionLine(label, value) {
33
+ const display = value && String(value).trim() ? chalk.cyan(value) : chalk.gray('—');
34
+ return ` ${label}: ${display}`;
35
+ }
36
+
37
+ /**
38
+ * Render the dataplane version subsection on `aifabrix auth status`.
39
+ *
40
+ * Designed to be called immediately after the existing connection/status line,
41
+ * so the output reads as a single grouped block per layout.md §HEADER BLOCK.
42
+ *
43
+ * @param {Object} params
44
+ * @param {boolean} params.connected - Whether dataplane reachability probe passed
45
+ * @param {string|undefined} params.dataplaneVersion - From `device.<controllerUrl>.dataplane-version`
46
+ * @param {string|undefined} params.minBuilderCliVersion - From `device.<controllerUrl>.dataplane-min-cli-version`
47
+ * @param {string} params.cliVersion - Installed Builder CLI version
48
+ * @param {boolean} params.compatible - true when CLI ≄ minimum (or no minimum enforced)
49
+ * @returns {void}
50
+ */
51
+ function displayDataplaneVersionSection({
52
+ connected,
53
+ dataplaneVersion,
54
+ minBuilderCliVersion,
55
+ cliVersion,
56
+ compatible
57
+ }) {
58
+ if (!connected && !dataplaneVersion) {
59
+ // Nothing to add — keep auth status quiet when the dataplane probe failed
60
+ // and no cached version is known (the reachability line already told the user).
61
+ return;
62
+ }
63
+ logger.log(versionLine('Dataplane version', dataplaneVersion));
64
+ logger.log(versionLine('Min Builder CLI', minBuilderCliVersion));
65
+ logger.log(versionLine('This CLI', cliVersion));
66
+ if (!minBuilderCliVersion) {
67
+ logger.log(` Compatibility: ${chalk.gray('Not enforced')}`);
68
+ return;
69
+ }
70
+ if (compatible) {
71
+ logger.log(` Compatibility: ${successGlyph()} ${chalk.green('OK')}`);
72
+ } else {
73
+ logger.log(` Compatibility: ${failureGlyph()} ${chalk.red('Upgrade required')}`);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Render the blocking upgrade block (red āœ– + Next actions) under the
79
+ * dataplane subsection when the CLI is incompatible.
80
+ *
81
+ * @param {Object} params
82
+ * @param {string} params.required - Required minimum semver
83
+ * @param {string} params.installed - Currently installed semver
84
+ * @returns {void}
85
+ */
86
+ function displayCliUpgradeRequired({ required, installed }) {
87
+ logger.log('');
88
+ logger.log(formatCliVersionGateError(required, installed));
89
+ }
90
+
91
+ module.exports = {
92
+ displayDataplaneVersionSection,
93
+ displayCliUpgradeRequired
94
+ };
@@ -19,6 +19,19 @@ const { resolveControllerUrl } = require('../utils/controller-url');
19
19
  const { findDataplaneServiceAppKey } = require('./wizard-dataplane');
20
20
  const { getDataplaneUrl } = require('../datasource/deploy');
21
21
  const { checkDataplaneHealth } = require('../utils/dataplane-health');
22
+ const {
23
+ displayDataplaneVersionSection,
24
+ displayCliUpgradeRequired
25
+ } = require('./auth-status-display');
26
+ const {
27
+ refreshDataplaneVersionInfo,
28
+ loadCachedVersionInfo
29
+ } = require('./auth-status-dataplane-version');
30
+ const {
31
+ EXIT_NOT_AUTHENTICATED,
32
+ EXIT_CLI_VERSION_INCOMPATIBLE,
33
+ EXIT_OK
34
+ } = require('../utils/cli-exit-codes');
22
35
 
23
36
  /**
24
37
  * Format expiration date for display
@@ -290,6 +303,10 @@ function displayOpenApiDocs(controllerUrl, dataplaneUrl) {
290
303
  * @param {Object} [dataplaneInfo] - Optional dataplane URL and health
291
304
  * @param {string|null} [dataplaneInfo.url] - Dataplane URL
292
305
  * @param {boolean} [dataplaneInfo.connected] - Whether dataplane is reachable
306
+ * @param {string|undefined} [dataplaneInfo.dataplaneVersion]
307
+ * @param {string|undefined} [dataplaneInfo.minBuilderCliVersion]
308
+ * @param {string} [dataplaneInfo.cliVersion]
309
+ * @param {boolean} [dataplaneInfo.compatible]
293
310
  */
294
311
  function displayStatus(controllerUrl, environment, tokenInfo, dataplaneInfo) {
295
312
  logger.log(chalk.bold('\nšŸ” Authentication Status\n'));
@@ -308,18 +325,61 @@ function displayStatus(controllerUrl, environment, tokenInfo, dataplaneInfo) {
308
325
 
309
326
  if (tokenInfo.authenticated && dataplaneInfo) {
310
327
  displayDataplaneSection(dataplaneInfo.url, dataplaneInfo.connected);
328
+ displayDataplaneVersionSection({
329
+ connected: dataplaneInfo.connected,
330
+ dataplaneVersion: dataplaneInfo.dataplaneVersion,
331
+ minBuilderCliVersion: dataplaneInfo.minBuilderCliVersion,
332
+ cliVersion: dataplaneInfo.cliVersion,
333
+ compatible: dataplaneInfo.compatible
334
+ });
335
+ if (dataplaneInfo.minBuilderCliVersion && dataplaneInfo.compatible === false) {
336
+ displayCliUpgradeRequired({
337
+ required: dataplaneInfo.minBuilderCliVersion,
338
+ installed: dataplaneInfo.cliVersion
339
+ });
340
+ }
311
341
  }
312
342
 
313
343
  logger.log('');
314
344
  }
315
345
 
346
+ /**
347
+ * Resolve the dataplane block for auth status (URL, reachability, versions,
348
+ * compatibility). Returns null when no dataplane is discovered.
349
+ *
350
+ * @async
351
+ * @function buildDataplaneInfo
352
+ * @param {string} controllerUrl
353
+ * @param {string} environment
354
+ * @param {Object} authConfig - Bearer auth config built from a valid token
355
+ * @returns {Promise<Object|null>}
356
+ */
357
+ async function buildDataplaneInfo(controllerUrl, environment, authConfig) {
358
+ const dataplaneUrl = await resolveDataplaneUrlSilent(controllerUrl, environment, authConfig);
359
+ if (!dataplaneUrl) {
360
+ return { url: null, connected: false };
361
+ }
362
+ const connected = await checkDataplaneHealth(dataplaneUrl, 5000);
363
+ const versionInfo = connected
364
+ ? await refreshDataplaneVersionInfo(controllerUrl, dataplaneUrl)
365
+ : await loadCachedVersionInfo(controllerUrl);
366
+ return {
367
+ url: dataplaneUrl,
368
+ connected,
369
+ dataplaneVersion: versionInfo.dataplaneVersion,
370
+ minBuilderCliVersion: versionInfo.minBuilderCliVersion,
371
+ cliVersion: versionInfo.cliVersion,
372
+ compatible: versionInfo.compatible
373
+ };
374
+ }
375
+
316
376
  /**
317
377
  * Handle auth status command
318
378
  * Controller and environment come from config.yaml (set via aifabrix login or aifabrix auth config).
319
379
  * @async
320
380
  * @function handleAuthStatus
321
381
  * @param {Object} options - Command options
322
- * @param {boolean} [options.validate] - If true, exit with code 1 when not authenticated
382
+ * @param {boolean} [options.validate] - If true, exit with code 1 when not authenticated, or code 3 when CLI is older than dataplane minimum
323
383
  * @returns {Promise<void>} Resolves when status is displayed
324
384
  */
325
385
  async function handleAuthStatus(options = {}) {
@@ -338,15 +398,19 @@ async function handleAuthStatus(options = {}) {
338
398
  let dataplaneInfo = null;
339
399
  if (tokenInfo && tokenInfo.authenticated && tokenInfo.token) {
340
400
  const authConfig = { type: 'bearer', token: tokenInfo.token };
341
- const dataplaneUrl = await resolveDataplaneUrlSilent(controllerUrl, environment, authConfig);
342
- const connected = dataplaneUrl ? await checkDataplaneHealth(dataplaneUrl, 5000) : false;
343
- dataplaneInfo = { url: dataplaneUrl, connected };
401
+ dataplaneInfo = await buildDataplaneInfo(controllerUrl, environment, authConfig);
344
402
  }
345
403
 
346
404
  displayStatus(controllerUrl, environment, tokenInfo, dataplaneInfo);
347
405
 
348
- if (options.validate && (!tokenInfo || !tokenInfo.authenticated)) {
349
- process.exit(1);
406
+ if (options.validate) {
407
+ if (!tokenInfo || !tokenInfo.authenticated) {
408
+ process.exit(EXIT_NOT_AUTHENTICATED);
409
+ }
410
+ if (dataplaneInfo && dataplaneInfo.minBuilderCliVersion && dataplaneInfo.compatible === false) {
411
+ process.exit(EXIT_CLI_VERSION_INCOMPATIBLE);
412
+ }
413
+ process.exit(EXIT_OK);
350
414
  }
351
415
  }
352
416
 
@@ -16,6 +16,7 @@ const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../uti
16
16
  const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
17
17
  const { getIntegrationPath } = require('../utils/paths');
18
18
  const { pushCredentialSecrets } = require('../utils/credential-secrets-env');
19
+ const { runWithCredentialPushSpinner } = require('../utils/credential-push-ui');
19
20
  const { generateControllerManifest } = require('../generator/external-controller-manifest');
20
21
 
21
22
  /**
@@ -84,11 +85,15 @@ async function runCredentialPush(systemKey) {
84
85
  const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
85
86
 
86
87
  const payload = await buildPayload(systemKey);
87
- const pushResult = await pushCredentialSecrets(dataplaneUrl, authConfig, {
88
- envFilePath,
89
- appName: systemKey,
90
- payload
91
- });
88
+ const pushResult = await runWithCredentialPushSpinner(
89
+ () =>
90
+ pushCredentialSecrets(dataplaneUrl, authConfig, {
91
+ envFilePath,
92
+ appName: systemKey,
93
+ payload
94
+ }),
95
+ { systemKey }
96
+ );
92
97
 
93
98
  logPushResult(pushResult);
94
99
  return { pushed: pushResult.pushed || 0 };