@aifabrix/builder 2.43.0 → 2.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/README.md +1 -1
  3. package/anchor-docs/README.md +10 -0
  4. package/anchor-docs/_TEMPLATE +24 -0
  5. package/bin/aifabrix.js +13 -4
  6. package/integration/hubspot-test/README.md +31 -0
  7. package/integration/hubspot-test/create-hubspot.js +5 -5
  8. package/integration/hubspot-test/hubspot-test-datasource-company.json +58 -462
  9. package/integration/hubspot-test/hubspot-test-datasource-contact.json +61 -555
  10. package/integration/hubspot-test/hubspot-test-datasource-deal.json +63 -506
  11. package/integration/hubspot-test/hubspot-test-datasource-users.json +42 -83
  12. package/integration/hubspot-test/hubspot-test-deploy.json +3 -3
  13. package/integration/hubspot-test/test-dataplane-down-tests.js +1 -7
  14. package/integration/hubspot-test/test-dataplane-down.js +3 -3
  15. package/integration/hubspot-test/test.js +35 -43
  16. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  17. package/integration/roundtrip-test-local/README.md +144 -0
  18. package/integration/roundtrip-test-local/application.yaml +13 -0
  19. package/integration/roundtrip-test-local/env.template +15 -0
  20. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  21. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  22. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  23. package/integration/roundtrip-test-local2/README.md +144 -0
  24. package/integration/roundtrip-test-local2/application.yaml +13 -0
  25. package/integration/roundtrip-test-local2/env.template +15 -0
  26. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  27. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  28. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  29. package/integration/test/wizard.yaml +8 -0
  30. package/jest.config.default.js +10 -0
  31. package/jest.config.integration.fixtures.js +22 -0
  32. package/jest.config.integration.js +21 -18
  33. package/jest.config.isolated.js +10 -0
  34. package/jest.projects.js +288 -0
  35. package/lib/api/datasources-core.api.js +3 -3
  36. package/lib/api/dev-mtls-request.js +110 -0
  37. package/lib/api/dev-server-https.js +145 -0
  38. package/lib/api/dev.api.js +133 -144
  39. package/lib/api/index.js +0 -1
  40. package/lib/api/pipeline.api.js +67 -20
  41. package/lib/api/types/dev.types.js +4 -3
  42. package/lib/api/types/pipeline.types.js +8 -5
  43. package/lib/api/types/validation-run.types.js +56 -0
  44. package/lib/api/validation-run.api.js +99 -0
  45. package/lib/api/validation-runner.js +99 -0
  46. package/lib/app/config.js +1 -1
  47. package/lib/app/deploy-status-display.js +2 -2
  48. package/lib/app/deploy.js +7 -6
  49. package/lib/app/display.js +2 -1
  50. package/lib/app/dockerfile.js +3 -2
  51. package/lib/app/down.js +2 -1
  52. package/lib/app/helpers.js +6 -5
  53. package/lib/app/index.js +27 -8
  54. package/lib/app/list.js +7 -6
  55. package/lib/app/push.js +4 -3
  56. package/lib/app/register.js +16 -7
  57. package/lib/app/rotate-secret.js +14 -13
  58. package/lib/app/run-container-start.js +184 -0
  59. package/lib/app/run-docker-fallback.js +108 -0
  60. package/lib/app/run-env-compose.js +30 -42
  61. package/lib/app/run-helpers.js +49 -126
  62. package/lib/app/run-infra-requirements.js +30 -0
  63. package/lib/app/run-resolve-image.js +21 -0
  64. package/lib/app/run.js +74 -21
  65. package/lib/app/show-display.js +1 -1
  66. package/lib/app/show.js +1 -1
  67. package/lib/build/index.js +13 -10
  68. package/lib/cli/index.js +2 -0
  69. package/lib/cli/setup-app.help.js +67 -0
  70. package/lib/cli/setup-app.js +57 -121
  71. package/lib/cli/setup-app.test-commands.js +179 -0
  72. package/lib/cli/setup-auth.js +19 -5
  73. package/lib/cli/setup-credential-deployment.js +22 -8
  74. package/lib/cli/setup-dev-path-commands.js +124 -0
  75. package/lib/cli/setup-dev.js +170 -113
  76. package/lib/cli/setup-environment.js +7 -1
  77. package/lib/cli/setup-external-system.js +62 -22
  78. package/lib/cli/setup-infra.js +126 -47
  79. package/lib/cli/setup-parameters.js +32 -0
  80. package/lib/cli/setup-secrets.js +106 -8
  81. package/lib/cli/setup-service-user.js +1 -1
  82. package/lib/cli/setup-utility.js +36 -20
  83. package/lib/commands/app-down.js +5 -7
  84. package/lib/commands/app-install.js +14 -7
  85. package/lib/commands/app-logs.js +13 -10
  86. package/lib/commands/app-shell.js +4 -1
  87. package/lib/commands/app-test.js +25 -19
  88. package/lib/commands/app.js +22 -10
  89. package/lib/commands/auth-config.js +6 -6
  90. package/lib/commands/auth-status.js +4 -3
  91. package/lib/commands/credential-env.js +4 -3
  92. package/lib/commands/credential-list.js +5 -4
  93. package/lib/commands/credential-push.js +4 -3
  94. package/lib/commands/datasource-unified-test-cli.js +495 -0
  95. package/lib/commands/datasource-unified-test-cli.options.js +149 -0
  96. package/lib/commands/datasource-validation-cli.js +129 -0
  97. package/lib/commands/datasource.js +105 -98
  98. package/lib/commands/deployment-list.js +6 -5
  99. package/lib/commands/dev-cli-handlers.js +122 -18
  100. package/lib/commands/dev-down.js +4 -3
  101. package/lib/commands/dev-init.js +231 -116
  102. package/lib/commands/dev-show-display.js +473 -0
  103. package/lib/commands/login-credentials.js +3 -2
  104. package/lib/commands/login-device.js +4 -3
  105. package/lib/commands/login.js +5 -4
  106. package/lib/commands/logout.js +8 -7
  107. package/lib/commands/parameters-validate.js +54 -0
  108. package/lib/commands/repair-datasource.js +314 -68
  109. package/lib/commands/repair-env-template.js +2 -2
  110. package/lib/commands/repair.js +21 -3
  111. package/lib/commands/secrets-list.js +23 -12
  112. package/lib/commands/secrets-remove-all.js +220 -0
  113. package/lib/commands/secrets-remove.js +21 -12
  114. package/lib/commands/secrets-set.js +21 -12
  115. package/lib/commands/secrets-validate.js +4 -4
  116. package/lib/commands/secure.js +10 -9
  117. package/lib/commands/service-user.js +26 -25
  118. package/lib/commands/test-e2e-external.js +27 -1
  119. package/lib/commands/up-common.js +3 -2
  120. package/lib/commands/up-dataplane.js +29 -16
  121. package/lib/commands/up-miso.js +19 -29
  122. package/lib/commands/upload.js +138 -39
  123. package/lib/commands/wizard-core-helpers.js +1 -1
  124. package/lib/commands/wizard-dataplane.js +4 -3
  125. package/lib/commands/wizard-helpers.js +3 -3
  126. package/lib/commands/wizard.js +2 -2
  127. package/lib/core/admin-secrets.js +14 -5
  128. package/lib/core/audit-logger.js +12 -4
  129. package/lib/core/config-attach-extensions.js +46 -0
  130. package/lib/core/config-runtime-paths.js +29 -0
  131. package/lib/core/config.js +55 -56
  132. package/lib/core/diff.js +3 -2
  133. package/lib/core/ensure-encryption-key.js +1 -1
  134. package/lib/core/secrets-ensure-infra.js +77 -0
  135. package/lib/core/secrets-ensure.js +120 -64
  136. package/lib/core/secrets-env-write.js +35 -7
  137. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  138. package/lib/core/secrets.js +200 -37
  139. package/lib/core/templates-env.js +4 -3
  140. package/lib/datasource/abac-validator.js +1 -10
  141. package/lib/datasource/deploy.js +75 -53
  142. package/lib/datasource/field-reference-validator.js +9 -6
  143. package/lib/datasource/integration-context.js +63 -0
  144. package/lib/datasource/list.js +8 -7
  145. package/lib/datasource/log-viewer.js +84 -53
  146. package/lib/datasource/resolve-app.js +4 -4
  147. package/lib/datasource/test-e2e.js +95 -146
  148. package/lib/datasource/test-integration.js +114 -122
  149. package/lib/datasource/unified-validation-run-body.js +65 -0
  150. package/lib/datasource/unified-validation-run-post.js +23 -0
  151. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  152. package/lib/datasource/unified-validation-run.js +92 -0
  153. package/lib/datasource/validate.js +157 -13
  154. package/lib/deployment/deployer.js +4 -3
  155. package/lib/deployment/environment.js +7 -6
  156. package/lib/deployment/push.js +17 -8
  157. package/lib/external-system/delete.js +4 -3
  158. package/lib/external-system/deploy.js +131 -53
  159. package/lib/external-system/download-helpers.js +1 -1
  160. package/lib/external-system/download.js +7 -6
  161. package/lib/external-system/generator.js +92 -6
  162. package/lib/external-system/integration-test-dispatch.js +26 -0
  163. package/lib/external-system/test-execution.js +5 -1
  164. package/lib/external-system/test-helpers.js +0 -4
  165. package/lib/external-system/test-system-level-helpers.js +110 -0
  166. package/lib/external-system/test-system-level.js +83 -44
  167. package/lib/external-system/test.js +59 -8
  168. package/lib/generator/builders.js +23 -11
  169. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  170. package/lib/generator/external.js +16 -4
  171. package/lib/generator/helpers.js +58 -3
  172. package/lib/generator/index.js +4 -0
  173. package/lib/generator/split-readme.js +12 -7
  174. package/lib/generator/split-variables.js +2 -1
  175. package/lib/generator/split.js +1 -1
  176. package/lib/generator/wizard-readme.js +3 -3
  177. package/lib/generator/wizard.js +8 -8
  178. package/lib/infrastructure/compose.js +60 -6
  179. package/lib/infrastructure/helpers.js +201 -29
  180. package/lib/infrastructure/index.js +28 -17
  181. package/lib/infrastructure/services.js +21 -15
  182. package/lib/internal/fs-real-sync.js +104 -0
  183. package/lib/internal/node-fs.js +98 -0
  184. package/lib/parameters/database-secret-values.js +173 -0
  185. package/lib/parameters/infra-kv-discovery.js +121 -0
  186. package/lib/parameters/infra-parameter-catalog.js +458 -0
  187. package/lib/parameters/infra-parameter-validate.js +64 -0
  188. package/lib/schema/application-schema.json +37 -17
  189. package/lib/schema/datasource-test-run.schema.json +493 -0
  190. package/lib/schema/deployment-rules.yaml +102 -63
  191. package/lib/schema/external-datasource.schema.json +1200 -442
  192. package/lib/schema/external-system.schema.json +181 -5
  193. package/lib/schema/flag-map-validation-run.json +31 -0
  194. package/lib/schema/infra-parameter.schema.json +106 -0
  195. package/lib/schema/infra.parameter.yaml +421 -0
  196. package/lib/schema/type/credential-auth-templates.json +40 -0
  197. package/lib/schema/type/document-storage.json +213 -0
  198. package/lib/schema/type/message-service.json +123 -0
  199. package/lib/schema/type/vector-store.json +88 -0
  200. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  201. package/lib/utils/api-error-handler.js +2 -2
  202. package/lib/utils/api.js +49 -14
  203. package/lib/utils/app-register-api.js +3 -2
  204. package/lib/utils/app-register-auth.js +1 -1
  205. package/lib/utils/app-register-config.js +4 -4
  206. package/lib/utils/app-register-display.js +3 -2
  207. package/lib/utils/app-register-validator.js +3 -2
  208. package/lib/utils/app-run-containers.js +26 -22
  209. package/lib/utils/app-scoped-config.js +31 -0
  210. package/lib/utils/app-service-env-from-builder.js +164 -0
  211. package/lib/utils/build-copy.js +1 -1
  212. package/lib/utils/build-helpers.js +20 -20
  213. package/lib/utils/build-resolve-image.js +165 -0
  214. package/lib/utils/cli-layout-chalk.js +8 -0
  215. package/lib/utils/cli-test-layout-chalk.js +267 -0
  216. package/lib/utils/cli-utils.js +88 -11
  217. package/lib/utils/compose-db-passwords.js +138 -0
  218. package/lib/utils/compose-generate-docker-compose.js +216 -0
  219. package/lib/utils/compose-generator.js +197 -291
  220. package/lib/utils/compose-miso-env.js +18 -0
  221. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  222. package/lib/utils/config-paths.js +166 -7
  223. package/lib/utils/config-scoped-resources-preference.js +41 -0
  224. package/lib/utils/controller-deployment-outcome.js +68 -0
  225. package/lib/utils/credential-display.js +2 -2
  226. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  227. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  228. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  229. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  230. package/lib/utils/datasource-test-run-display.js +442 -0
  231. package/lib/utils/datasource-test-run-exit.js +58 -0
  232. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  233. package/lib/utils/datasource-test-run-report-version.js +51 -0
  234. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  235. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  236. package/lib/utils/datasource-validation-watch.js +266 -0
  237. package/lib/utils/declarative-url-ports.js +47 -0
  238. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  239. package/lib/utils/dev-ca-install.js +185 -23
  240. package/lib/utils/dev-cert-helper.js +266 -17
  241. package/lib/utils/dev-hosts-helper.js +307 -0
  242. package/lib/utils/dev-init-cert-hints.js +37 -0
  243. package/lib/utils/dev-init-health-messages.js +52 -0
  244. package/lib/utils/dev-init-resolve.js +86 -0
  245. package/lib/utils/dev-init-ssh-merge.js +65 -0
  246. package/lib/utils/dev-ssh-config-helper.js +196 -0
  247. package/lib/utils/dev-user-groups.js +93 -0
  248. package/lib/utils/docker-build.js +42 -17
  249. package/lib/utils/docker-exec.js +28 -0
  250. package/lib/utils/docker-manifest-public-port.js +116 -0
  251. package/lib/utils/docker-not-running-hint.js +52 -0
  252. package/lib/utils/docker.js +98 -11
  253. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  254. package/lib/utils/env-config-loader.js +10 -91
  255. package/lib/utils/env-copy.js +19 -10
  256. package/lib/utils/env-map.js +35 -8
  257. package/lib/utils/env-template.js +2 -2
  258. package/lib/utils/environment-scoped-resources.js +144 -0
  259. package/lib/utils/error-formatter.js +92 -13
  260. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  261. package/lib/utils/error-formatters/network-errors.js +2 -1
  262. package/lib/utils/error-formatters/permission-errors.js +2 -1
  263. package/lib/utils/error-formatters/validation-errors.js +2 -1
  264. package/lib/utils/external-readme.js +8 -1
  265. package/lib/utils/external-system-display.js +234 -136
  266. package/lib/utils/external-system-local-test-tty.js +389 -0
  267. package/lib/utils/external-system-readiness-core.js +377 -0
  268. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  269. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  270. package/lib/utils/external-system-readiness-display.js +186 -0
  271. package/lib/utils/external-system-test-helpers.js +24 -6
  272. package/lib/utils/external-system-validators.js +30 -12
  273. package/lib/utils/health-check-url.js +119 -0
  274. package/lib/utils/health-check.js +59 -25
  275. package/lib/utils/help-builder.js +11 -8
  276. package/lib/utils/image-version.js +4 -8
  277. package/lib/utils/infra-containers.js +4 -7
  278. package/lib/utils/infra-env-defaults.js +162 -0
  279. package/lib/utils/infra-status-display.js +167 -0
  280. package/lib/utils/infra-status.js +16 -8
  281. package/lib/utils/local-secrets.js +3 -4
  282. package/lib/utils/paths.js +134 -47
  283. package/lib/utils/port-resolver.js +10 -23
  284. package/lib/utils/redis-env-scope.js +62 -0
  285. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  286. package/lib/utils/remote-builder-validation.js +99 -0
  287. package/lib/utils/remote-dev-auth.js +117 -21
  288. package/lib/utils/remote-docker-env.js +67 -15
  289. package/lib/utils/remote-secrets-loader.js +13 -4
  290. package/lib/utils/resolve-docker-image-ref.js +124 -0
  291. package/lib/utils/schema-loader.js +22 -9
  292. package/lib/utils/secrets-bash-kv.js +25 -0
  293. package/lib/utils/secrets-generator.js +169 -49
  294. package/lib/utils/secrets-helpers.js +70 -59
  295. package/lib/utils/secrets-kv-scope.js +60 -0
  296. package/lib/utils/secrets-utils.js +32 -38
  297. package/lib/utils/secrets-validation.js +3 -1
  298. package/lib/utils/secrets-yaml-preserve.js +109 -0
  299. package/lib/utils/ssh-key-helper.js +4 -2
  300. package/lib/utils/template-helpers.js +2 -2
  301. package/lib/utils/test-log-writer.js +3 -3
  302. package/lib/utils/token-manager.js +1 -2
  303. package/lib/utils/url-declarative-public-base.js +188 -0
  304. package/lib/utils/url-declarative-resolve-build.js +493 -0
  305. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  306. package/lib/utils/url-declarative-resolve.js +220 -0
  307. package/lib/utils/url-declarative-token-parse.js +74 -0
  308. package/lib/utils/url-declarative-url-flags.js +50 -0
  309. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  310. package/lib/utils/url-public-path-prefix.js +34 -0
  311. package/lib/utils/urls-local-registry.js +220 -0
  312. package/lib/utils/validation-report-tty-kit.js +77 -0
  313. package/lib/utils/validation-run-poll.js +89 -0
  314. package/lib/utils/validation-run-post-retry.js +73 -0
  315. package/lib/utils/validation-run-request.js +98 -0
  316. package/lib/utils/variable-transformer.js +21 -4
  317. package/lib/utils/yaml-preserve.js +33 -14
  318. package/lib/validation/datasource-warnings.js +56 -0
  319. package/lib/validation/env-template-auth.js +1 -1
  320. package/lib/validation/external-manifest-validator.js +27 -7
  321. package/lib/validation/validate-display.js +37 -31
  322. package/lib/validation/validate.js +4 -13
  323. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  324. package/lib/validation/validator.js +22 -65
  325. package/lib/validation/wizard-config-validator.js +2 -1
  326. package/package.json +7 -3
  327. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  328. package/scripts/diagnose-cli.js +150 -0
  329. package/scripts/install-local.js +304 -55
  330. package/templates/README.md +15 -2
  331. package/templates/applications/dataplane/application.yaml +52 -2
  332. package/templates/applications/dataplane/env.template +75 -17
  333. package/templates/applications/dataplane/rbac.yaml +8 -0
  334. package/templates/applications/keycloak/application.yaml +9 -1
  335. package/templates/applications/keycloak/env.template +15 -6
  336. package/templates/applications/miso-controller/application.yaml +10 -2
  337. package/templates/applications/miso-controller/env.template +42 -12
  338. package/templates/applications/miso-controller/rbac.yaml +5 -0
  339. package/templates/external-system/README.md.hbs +20 -7
  340. package/templates/external-system/deploy.js.hbs +5 -5
  341. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  342. package/templates/infra/compose.yaml.hbs +20 -4
  343. package/templates/python/docker-compose.hbs +16 -0
  344. package/templates/typescript/docker-compose.hbs +16 -0
  345. package/lib/api/external-test.api.js +0 -111
  346. package/lib/schema/env-config.yaml +0 -60
@@ -8,7 +8,7 @@
8
8
  * @version 2.0.0
9
9
  */
10
10
 
11
- const chalk = require('chalk');
11
+ const { formatBlockingError, formatSuccessLine } = require('../utils/cli-test-layout-chalk');
12
12
  const {
13
13
  setControllerUrl,
14
14
  setCurrentEnvironment,
@@ -42,14 +42,14 @@ async function handleSetController(url) {
42
42
  if (!loggedInControllerUrl) {
43
43
  // No stored credentials: allow setting controller so "aifabrix login" opens the right place
44
44
  await setControllerUrl(url);
45
- logger.log(chalk.green(`✓ Controller URL set to: ${url}`));
45
+ logger.log(formatSuccessLine(`Controller URL set to: ${url}`));
46
46
  return;
47
47
  }
48
48
 
49
49
  const normalizedLoggedIn = loggedInControllerUrl.trim().replace(/\/+$/, '');
50
50
  if (normalizedLoggedIn === normalizedUrl) {
51
51
  await setControllerUrl(url);
52
- logger.log(chalk.green(`✓ Controller URL set to: ${url}`));
52
+ logger.log(formatSuccessLine(`Controller URL set to: ${url}`));
53
53
  return;
54
54
  }
55
55
 
@@ -58,7 +58,7 @@ async function handleSetController(url) {
58
58
  '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
59
  );
60
60
  } catch (error) {
61
- logger.error(chalk.red(`✗ Failed to set controller URL: ${error.message}`));
61
+ logger.error(formatBlockingError(`Failed to set controller URL: ${error.message}`));
62
62
  throw error;
63
63
  }
64
64
  }
@@ -95,9 +95,9 @@ async function handleSetEnvironment(environment) {
95
95
  // Save environment
96
96
  await setCurrentEnvironment(environment);
97
97
 
98
- logger.log(chalk.green(`✓ Environment set to: ${environment}`));
98
+ logger.log(formatSuccessLine(`Environment set to: ${environment}`));
99
99
  } catch (error) {
100
- logger.error(chalk.red(`✗ Failed to set environment: ${error.message}`));
100
+ logger.error(formatBlockingError(`Failed to set environment: ${error.message}`));
101
101
  throw error;
102
102
  }
103
103
  }
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  const chalk = require('chalk');
12
+ const { failureGlyph, successGlyph } = require('../utils/cli-test-layout-chalk');
12
13
  const logger = require('../utils/logger');
13
14
  const config = require('../core/config');
14
15
  const { getConfig } = config;
@@ -194,7 +195,7 @@ function displayUserInfo(user) {
194
195
  * @param {Object} tokenInfo - Token information
195
196
  */
196
197
  function displayTokenInfo(tokenInfo) {
197
- const statusIcon = tokenInfo.authenticated ? chalk.green('✓') : chalk.red('✗');
198
+ const statusIcon = tokenInfo.authenticated ? successGlyph() : failureGlyph();
198
199
  const statusText = tokenInfo.authenticated ? 'Authenticated' : 'Not authenticated';
199
200
 
200
201
  logger.log(` Status: ${statusIcon} ${statusText}`);
@@ -244,7 +245,7 @@ function displayDataplaneSection(dataplaneUrl, dataplaneConnected) {
244
245
  logger.log('');
245
246
  if (dataplaneUrl) {
246
247
  logger.log(`Dataplane: ${chalk.cyan(dataplaneUrl)}`);
247
- const statusIcon = dataplaneConnected ? chalk.green('✓') : chalk.red('✗');
248
+ const statusIcon = dataplaneConnected ? successGlyph() : failureGlyph();
248
249
  const statusText = dataplaneConnected ? 'Connected' : 'Not reachable';
249
250
  displayOpenApiDocs(null, dataplaneUrl);
250
251
  logger.log('');
@@ -297,7 +298,7 @@ function displayStatus(controllerUrl, environment, tokenInfo, dataplaneInfo) {
297
298
  logger.log(` Environment: ${chalk.cyan(environment || 'Not specified')}\n`);
298
299
 
299
300
  if (!tokenInfo) {
300
- logger.log(` Status: ${chalk.red('Not authenticated')}`);
301
+ logger.log(` Status: ${failureGlyph()} ${chalk.red('Not authenticated')}`);
301
302
  logger.log(` Token Type: ${chalk.gray('None')}\n`);
302
303
  logger.log(chalk.yellow('💡 Run "aifabrix login" to authenticate\n'));
303
304
  return;
@@ -1,6 +1,7 @@
1
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * Credential env command – prompts for KV_* values and writes .env.
3
- * Used by `aifabrix credential env <system-key>`.
4
+ * Used by `aifabrix credential env <systemKey>`.
4
5
  *
5
6
  * @fileoverview Credential env command – interactive credential capture to .env
6
7
  * @author AI Fabrix Team
@@ -120,7 +121,7 @@ function buildEnvContent(templateContent, promptValues) {
120
121
  /**
121
122
  * Runs credential env command: prompt for KV_* values and write .env.
122
123
  * @async
123
- * @param {string} systemKey - External system key (integration/<system-key>/)
124
+ * @param {string} systemKey - External system key (integration/<systemKey>/)
124
125
  * @returns {Promise<string>} Path to written .env file
125
126
  * @throws {Error} If env.template missing or write fails
126
127
  */
@@ -155,7 +156,7 @@ async function runCredentialEnv(systemKey) {
155
156
  const dir = path.dirname(envPath);
156
157
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
157
158
  fs.writeFileSync(envPath, content, { mode: 0o600 });
158
- logger.log(chalk.green(`✓ Wrote ${envPath}`));
159
+ logger.log(formatSuccessLine(`Wrote ${envPath}`));
159
160
  return envPath;
160
161
  }
161
162
 
@@ -1,3 +1,4 @@
1
+ const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * Credential list command – list credentials from Dataplane
3
4
  * GET /api/v1/credential. Used by `aifabrix credential list`.
@@ -78,12 +79,12 @@ function displayCredentialList(list, baseUrl) {
78
79
  async function ensureControllerAndAuth(options = {}) {
79
80
  const controllerUrl = options.controller || (await resolveControllerUrl());
80
81
  if (!controllerUrl) {
81
- logger.error(chalk.red('Controller URL is required. Run "aifabrix login" first.'));
82
+ logger.error(formatBlockingError('Controller URL is required. Run "aifabrix login" first.'));
82
83
  process.exit(1);
83
84
  }
84
85
  const authResult = await getCredentialListAuth(controllerUrl);
85
86
  if (!authResult || !authResult.token) {
86
- logger.error(chalk.red(`❌ No authentication token for controller: ${controllerUrl}`));
87
+ logger.error(formatBlockingError(`No authentication token for controller: ${controllerUrl}`));
87
88
  logger.error(chalk.gray('Run: aifabrix login'));
88
89
  process.exit(1);
89
90
  }
@@ -129,7 +130,7 @@ async function resolveDataplaneUrlOrExit(controllerUrl, authConfig) {
129
130
  try {
130
131
  return await resolveCredentialListDataplaneUrl(controllerUrl, authConfig);
131
132
  } catch (err) {
132
- logger.error(chalk.red(`❌ Could not resolve Dataplane URL: ${err.message}`));
133
+ logger.error(formatBlockingError(`Could not resolve Dataplane URL: ${err.message}`));
133
134
  process.exit(1);
134
135
  }
135
136
  }
@@ -152,7 +153,7 @@ async function runCredentialList(options = {}) {
152
153
  try {
153
154
  await fetchAndDisplayCredentials(dataplaneUrl, authConfig, listOptions);
154
155
  } catch (error) {
155
- logger.error(chalk.red(`❌ Failed to list credentials: ${error.message}`));
156
+ logger.error(formatBlockingError(`Failed to list credentials: ${error.message}`));
156
157
  process.exit(1);
157
158
  }
158
159
  }
@@ -1,6 +1,7 @@
1
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * Credential push command – pushes KV_* from .env to dataplane.
3
- * Used by `aifabrix credential push <system-key>`.
4
+ * Used by `aifabrix credential push <systemKey>`.
4
5
  *
5
6
  * @fileoverview Credential push command – push credential secrets to dataplane
6
7
  * @author AI Fabrix Team
@@ -55,7 +56,7 @@ async function buildPayload(systemKey) {
55
56
  function logPushResult(pushResult) {
56
57
  if (pushResult.pushed > 0) {
57
58
  const keyList = pushResult.keys?.length ? ` (${pushResult.keys.join(', ')})` : '';
58
- logger.log(chalk.green(`✓ Pushed ${pushResult.pushed} credential secret(s) to dataplane${keyList}.`));
59
+ logger.log(formatSuccessLine(`Pushed ${pushResult.pushed} credential secret(s) to dataplane${keyList}.`));
59
60
  } else if (pushResult.skipped) {
60
61
  logger.log(chalk.yellow('No credential secrets to push (empty .env or no KV_* vars with values).'));
61
62
  } else {
@@ -75,7 +76,7 @@ async function runCredentialPush(systemKey) {
75
76
  const authConfig = await getDeploymentAuth(controllerUrl, environment, systemKey);
76
77
 
77
78
  if (!authConfig.token && !authConfig.clientId) {
78
- throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register <system-key>" first.');
79
+ throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register <systemKey>" first.');
79
80
  }
80
81
 
81
82
  requireBearerForDataplanePipeline(authConfig);
@@ -0,0 +1,495 @@
1
+ const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
2
+ /**
3
+ * @fileoverview CLI wiring for `datasource test`, `test-integration`, and `test-e2e` (unified validation + watch).
4
+ * @author AI Fabrix Team
5
+ * @version 2.0.0
6
+ */
7
+
8
+ const chalk = require('chalk');
9
+ const logger = require('../utils/logger');
10
+ const { runDatasourceTestIntegration } = require('../datasource/test-integration');
11
+ const { runDatasourceTestE2E } = require('../datasource/test-e2e');
12
+ const { runUnifiedDatasourceValidation } = require('../datasource/unified-validation-run');
13
+ const { displayIntegrationTestResults, displayE2EResults } = require('../utils/external-system-display');
14
+ const path = require('path');
15
+ const { getIntegrationPath } = require('../utils/paths');
16
+ const { writeTestLog } = require('../utils/test-log-writer');
17
+ const { includeDebugForRequest } = require('../utils/validation-run-request');
18
+ const {
19
+ exitFromUnifiedValidationResult,
20
+ finalizeUnifiedValidationResult,
21
+ unifiedCliResultFromIntegrationReturn,
22
+ exitAfterIntegrationDisplay,
23
+ finalizeAfterIntegrationDisplay,
24
+ emitCapabilityScopeDiagnostics
25
+ } = require('./datasource-validation-cli');
26
+ const { resolveAppKeyForDatasource } = require('../datasource/resolve-app');
27
+ const { runDatasourceValidationWatchLoop } = require('../utils/datasource-validation-watch');
28
+ const { computeExitCodeFromDatasourceTestRun } = require('../utils/datasource-test-run-exit');
29
+ const { analyzeCapabilityScope } = require('../utils/datasource-test-run-capability-scope');
30
+ const {
31
+ resolveDebugDisplayMode,
32
+ formatDatasourceTestRunDebugBlock
33
+ } = require('../utils/datasource-test-run-debug-display');
34
+ const { formatCapabilityFocusSection } = require('../utils/datasource-test-run-display');
35
+ const {
36
+ datasourceTestHelpAfter,
37
+ datasourceTestIntegrationHelpAfter,
38
+ datasourceTestE2eHelpAfter,
39
+ attachDatasourceWatchOptions,
40
+ attachDatasourceTestCommonOptions
41
+ } = require('./datasource-unified-test-cli.options');
42
+
43
+ function integrationBaseDirForLogs(appKey) {
44
+ return path.dirname(getIntegrationPath(appKey));
45
+ }
46
+
47
+ async function writeDatasourceTestDebugLogIfRequested(appKey, datasourceKey, result, options) {
48
+ if (!options || !options.debug) return;
49
+ const requestMeta = {
50
+ datasourceKey,
51
+ runType: 'test',
52
+ includeDebug: includeDebugForRequest(options.debug)
53
+ };
54
+ const envelope = result && typeof result === 'object' ? result.envelope : null;
55
+ const apiError = result && typeof result === 'object' ? result.apiError : null;
56
+ const errorMessage = apiError
57
+ ? apiError.formattedError || apiError.error || 'Request failed'
58
+ : null;
59
+ const data = errorMessage
60
+ ? { request: requestMeta, error: errorMessage }
61
+ : { request: requestMeta, response: envelope };
62
+ const logPath = await writeTestLog(
63
+ appKey,
64
+ data,
65
+ 'test',
66
+ integrationBaseDirForLogs(appKey)
67
+ );
68
+ logger.log(chalk.gray(` Debug log: ${logPath}`));
69
+ }
70
+
71
+ function logDatasourceTestRunDebugAppendix(envelope, debugOpt) {
72
+ const mode = resolveDebugDisplayMode(debugOpt);
73
+ if (!mode || !envelope) return;
74
+ const block = formatDatasourceTestRunDebugBlock(envelope, mode, process.stdout.isTTY);
75
+ if (block) logger.log(block);
76
+ }
77
+
78
+ function logE2eCapabilityFocusFromEnvelope(env, capabilityOpt) {
79
+ if (!env) return;
80
+ const capKey =
81
+ capabilityOpt !== undefined && capabilityOpt !== null
82
+ ? String(capabilityOpt).trim()
83
+ : '';
84
+ if (!capKey) return;
85
+ const sec = formatCapabilityFocusSection(env, capKey);
86
+ if (sec) logger.log(sec);
87
+ }
88
+
89
+ /**
90
+ * Exit code from envelope only (no stderr diagnostics; use when TTY output already ran emit via logEnvelope).
91
+ * @param {Object|null|undefined} env
92
+ * @param {Object} options
93
+ * @returns {number|null} null if no envelope
94
+ */
95
+ function exitCodeFromDatasourceTestRunEnvelope(env, options) {
96
+ if (!env || typeof env !== 'object') return null;
97
+ let code = computeExitCodeFromDatasourceTestRun(env, {
98
+ warningsAsErrors: false,
99
+ requireCert: false
100
+ });
101
+ const scope = analyzeCapabilityScope(env, options.capability);
102
+ if (options.strictCapabilityScope === true && scope.violated) {
103
+ code = Math.max(code, 1);
104
+ }
105
+ return code;
106
+ }
107
+
108
+ /**
109
+ * Legacy E2E display + exit code (no process.exit; watch mode).
110
+ * @param {Object} data
111
+ * @param {Object} options
112
+ * @returns {number}
113
+ */
114
+ function finalizeDatasourceTestE2ELegacyPath(data, options) {
115
+ displayE2EResults(data, options.verbose);
116
+ logDatasourceTestRunDebugAppendix(data.datasourceTestRun, options.debug);
117
+ logE2eCapabilityFocusFromEnvelope(data.datasourceTestRun, options.capability);
118
+ const env = data.datasourceTestRun;
119
+ if (env) {
120
+ emitCapabilityScopeDiagnostics(env, { requestedCapabilityKey: options.capability });
121
+ const code = exitCodeFromDatasourceTestRunEnvelope(env, options);
122
+ return code === null ? 1 : code;
123
+ }
124
+ const steps = data.steps || data.completedActions || [];
125
+ const failed = data.success === false || steps.some(s => s.success === false || s.error);
126
+ return failed ? 1 : 0;
127
+ }
128
+
129
+ /**
130
+ * Human TTY for single-datasource E2E when DatasourceTestRun envelope is present.
131
+ * @param {string} datasourceKey
132
+ * @param {Object} env
133
+ * @param {Object} options
134
+ */
135
+ function displayDatasourceTestE2EEnvelopeResults(datasourceKey, env, options) {
136
+ const success = env.status !== 'fail';
137
+ displayIntegrationTestResults(
138
+ {
139
+ systemKey: env.systemKey || 'unknown',
140
+ success,
141
+ datasourceResults: [{ key: datasourceKey, success, datasourceTestRun: env }]
142
+ },
143
+ options.verbose,
144
+ {
145
+ debug: options.debug,
146
+ runType: 'e2e',
147
+ requestedCapabilityKey: options.capability
148
+ }
149
+ );
150
+ logE2eCapabilityFocusFromEnvelope(env, options.capability);
151
+ }
152
+
153
+ function buildDatasourceTestRunOpts(options) {
154
+ return {
155
+ app: options.app,
156
+ environment: options.env,
157
+ runType: 'test',
158
+ payload: options.payload,
159
+ debug: options.debug,
160
+ verbose: options.verbose,
161
+ timeout: options.timeout,
162
+ async: options.async !== false,
163
+ noAsync: options.async === false,
164
+ sync: options.sync === true
165
+ };
166
+ }
167
+
168
+ function buildDatasourceTestDisplayOpts(options) {
169
+ return {
170
+ json: options.json,
171
+ summary: options.summary,
172
+ warningsAsErrors: options.warningsAsErrors,
173
+ requireCert: options.requireCert,
174
+ debug: options.debug
175
+ };
176
+ }
177
+
178
+ async function runDatasourceUnifiedTestOnceForWatch(datasourceKey, runOpts, displayOpts) {
179
+ try {
180
+ const result = await runUnifiedDatasourceValidation(datasourceKey, runOpts);
181
+ return {
182
+ exitCode: finalizeUnifiedValidationResult(result, displayOpts),
183
+ envelope: result.envelope
184
+ };
185
+ } catch (err) {
186
+ logger.error(formatBlockingError('Datasource test failed:'), err.message);
187
+ return { exitCode: 4, envelope: null };
188
+ }
189
+ }
190
+
191
+ async function datasourceTestCommandAction(datasourceKey, options) {
192
+ const runOpts = buildDatasourceTestRunOpts(options);
193
+ const displayOpts = buildDatasourceTestDisplayOpts(options);
194
+ try {
195
+ if (options.watch) {
196
+ const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
197
+ await runDatasourceValidationWatchLoop({
198
+ appKey,
199
+ extraPaths: options.watchPath || [],
200
+ includeApplicationYaml: options.watchApplicationYaml === true,
201
+ watchCi: options.watchCi === true,
202
+ watchFullDiff: options.watchFullDiff === true,
203
+ runOnce: async() => {
204
+ const result = await runUnifiedDatasourceValidation(datasourceKey, runOpts);
205
+ await writeDatasourceTestDebugLogIfRequested(appKey, datasourceKey, result, options);
206
+ return {
207
+ exitCode: finalizeUnifiedValidationResult(result, displayOpts),
208
+ envelope: result.envelope
209
+ };
210
+ }
211
+ });
212
+ return;
213
+ }
214
+ const result = await runUnifiedDatasourceValidation(datasourceKey, runOpts);
215
+ if (options.debug) {
216
+ try {
217
+ const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
218
+ await writeDatasourceTestDebugLogIfRequested(appKey, datasourceKey, result, options);
219
+ } catch (e) {
220
+ logger.warn(chalk.yellow(`⚠ Could not write debug log: ${e.message}`));
221
+ }
222
+ }
223
+ exitFromUnifiedValidationResult(result, displayOpts);
224
+ } catch (error) {
225
+ logger.error(formatBlockingError('Datasource test failed:'), error.message);
226
+ process.exit(4);
227
+ }
228
+ }
229
+
230
+ function chainDatasourceTestCommand(datasource) {
231
+ const cmd = datasource
232
+ .command('test <datasourceKey>')
233
+ .description('Structural/policy validation for one datasource (unified dataplane API, runType=test)');
234
+ attachDatasourceTestCommonOptions(cmd, {
235
+ includeNoAsync: true,
236
+ verboseHelp: 'Set explain=true on validation request',
237
+ timeoutHelp: 'Aggregate timeout for POST + polls'
238
+ });
239
+ return cmd.addHelpText('after', datasourceTestHelpAfter());
240
+ }
241
+
242
+ function setupDatasourceTestCommand(datasource) {
243
+ // watch flags are already attached by attachDatasourceTestCommonOptions()
244
+ chainDatasourceTestCommand(datasource).action(datasourceTestCommandAction);
245
+ }
246
+
247
+ function buildIntegrationTestOpts(options) {
248
+ return {
249
+ app: options.app,
250
+ payload: options.payload,
251
+ environment: options.env,
252
+ debug: options.debug,
253
+ verbose: options.verbose,
254
+ timeout: options.timeout,
255
+ sync: options.sync === true
256
+ };
257
+ }
258
+
259
+ function buildIntegrationUnifiedDisplayOpts(options) {
260
+ return {
261
+ json: options.json,
262
+ summary: options.summary,
263
+ warningsAsErrors: options.warningsAsErrors,
264
+ requireCert: options.requireCert,
265
+ debug: options.debug
266
+ };
267
+ }
268
+
269
+ async function runIntegrationOnceForWatch(datasourceKey, integOpts, options, unifiedDisplayOpts) {
270
+ try {
271
+ const result = await runDatasourceTestIntegration(datasourceKey, integOpts);
272
+ const unifiedModes =
273
+ options.json || options.summary || options.warningsAsErrors || options.requireCert;
274
+ if (unifiedModes) {
275
+ const uni = unifiedCliResultFromIntegrationReturn(result);
276
+ const exitCode = finalizeUnifiedValidationResult(uni, unifiedDisplayOpts);
277
+ return { exitCode, envelope: uni.envelope };
278
+ }
279
+ displayIntegrationTestResults(
280
+ {
281
+ systemKey: result.systemKey || 'unknown',
282
+ datasourceResults: [result],
283
+ success: result.success
284
+ },
285
+ options.verbose,
286
+ { debug: options.debug, runType: 'integration' }
287
+ );
288
+ const exitCode = finalizeAfterIntegrationDisplay(result, {});
289
+ return { exitCode, envelope: result.datasourceTestRun || null };
290
+ } catch (err) {
291
+ logger.error(formatBlockingError('Integration test failed:'), err.message);
292
+ return { exitCode: 4, envelope: null };
293
+ }
294
+ }
295
+
296
+ async function integrationTestCommandAction(datasourceKey, options) {
297
+ const integOpts = buildIntegrationTestOpts(options);
298
+ const unifiedDisplayOpts = buildIntegrationUnifiedDisplayOpts(options);
299
+ try {
300
+ if (options.watch) {
301
+ const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
302
+ await runDatasourceValidationWatchLoop({
303
+ appKey,
304
+ extraPaths: options.watchPath || [],
305
+ includeApplicationYaml: options.watchApplicationYaml === true,
306
+ watchCi: options.watchCi === true,
307
+ watchFullDiff: options.watchFullDiff === true,
308
+ runOnce: () => runIntegrationOnceForWatch(datasourceKey, integOpts, options, unifiedDisplayOpts)
309
+ });
310
+ return;
311
+ }
312
+ const result = await runDatasourceTestIntegration(datasourceKey, integOpts);
313
+ const unifiedModes =
314
+ options.json || options.summary || options.warningsAsErrors || options.requireCert;
315
+ if (unifiedModes) {
316
+ exitFromUnifiedValidationResult(unifiedCliResultFromIntegrationReturn(result), unifiedDisplayOpts);
317
+ return;
318
+ }
319
+ displayIntegrationTestResults(
320
+ {
321
+ systemKey: result.systemKey || 'unknown',
322
+ datasourceResults: [result],
323
+ success: result.success
324
+ },
325
+ options.verbose,
326
+ { debug: options.debug, runType: 'integration' }
327
+ );
328
+ exitAfterIntegrationDisplay(result, {});
329
+ } catch (error) {
330
+ logger.error(formatBlockingError('Integration test failed:'), error.message);
331
+ process.exit(4);
332
+ }
333
+ }
334
+
335
+ function chainDatasourceTestIntegrationCommand(datasource) {
336
+ const cmd = datasource
337
+ .command('test-integration <datasourceKey>')
338
+ .description('Integration test one datasource (unified validation API, runType=integration)');
339
+ attachDatasourceTestCommonOptions(cmd, {
340
+ includeNoAsync: false,
341
+ debugHelp: 'includeDebug + log under integration/<systemKey>/logs/; TTY appendix: summary, full, or raw'
342
+ });
343
+ return cmd.addHelpText('after', datasourceTestIntegrationHelpAfter());
344
+ }
345
+
346
+ function setupDatasourceTestIntegrationCommand(datasource) {
347
+ // watch flags are already attached by attachDatasourceTestCommonOptions()
348
+ chainDatasourceTestIntegrationCommand(datasource).action(integrationTestCommandAction);
349
+ }
350
+
351
+ /**
352
+ * @param {string} datasourceKey
353
+ * @param {Object} options
354
+ * @returns {Promise<{ exitCode: number, envelope: Object|null }>}
355
+ */
356
+ async function runDatasourceTestE2ECliOnce(datasourceKey, options) {
357
+ const data = await runDatasourceTestE2E(datasourceKey, {
358
+ app: options.app,
359
+ environment: options.env,
360
+ debug: options.debug,
361
+ verbose: options.verbose,
362
+ async: options.async !== false,
363
+ testCrud: options.testCrud,
364
+ recordId: options.recordId,
365
+ cleanup: options.cleanup,
366
+ primaryKeyValue: options.primaryKeyValue,
367
+ timeout: options.timeout,
368
+ capabilityKey: options.capability,
369
+ sync: options.sync === true
370
+ });
371
+ const unifiedModes =
372
+ options.json || options.summary || options.warningsAsErrors || options.requireCert;
373
+ if (unifiedModes && data.datasourceTestRun) {
374
+ const exitCode = finalizeUnifiedValidationResult(
375
+ {
376
+ apiError: null,
377
+ pollTimedOut: false,
378
+ incompleteNoAsync: false,
379
+ envelope: data.datasourceTestRun
380
+ },
381
+ {
382
+ json: options.json,
383
+ summary: options.summary,
384
+ warningsAsErrors: options.warningsAsErrors,
385
+ requireCert: options.requireCert,
386
+ debug: options.debug,
387
+ requestedCapabilityKey: options.capability,
388
+ strictCapabilityScope: options.strictCapabilityScope === true
389
+ }
390
+ );
391
+ return { exitCode, envelope: data.datasourceTestRun };
392
+ }
393
+ const env = data.datasourceTestRun;
394
+ if (env && !unifiedModes) {
395
+ displayDatasourceTestE2EEnvelopeResults(datasourceKey, env, options);
396
+ const exitCode = exitCodeFromDatasourceTestRunEnvelope(env, options);
397
+ return { exitCode: exitCode === null ? 1 : exitCode, envelope: env };
398
+ }
399
+ const exitCode = finalizeDatasourceTestE2ELegacyPath(data, options);
400
+ return { exitCode, envelope: data.datasourceTestRun || null };
401
+ }
402
+
403
+ async function runDatasourceTestE2ECliAction(datasourceKey, options) {
404
+ const { exitCode } = await runDatasourceTestE2ECliOnce(datasourceKey, options);
405
+ process.exit(exitCode);
406
+ }
407
+
408
+ async function e2eTestCommandAction(datasourceKey, capabilityKey, options) {
409
+ const optObj = options && typeof options === 'object' ? options : {};
410
+ const capFromArg = typeof capabilityKey === 'string' ? capabilityKey.trim() : '';
411
+ const capFromFlag = optObj.capability !== undefined && optObj.capability !== null
412
+ ? String(optObj.capability).trim()
413
+ : '';
414
+ const requestedCapability = capFromArg || capFromFlag;
415
+ if (capFromArg && capFromFlag && capFromArg !== capFromFlag) {
416
+ logger.warn(
417
+ chalk.yellow('⚠ Capability mismatch:'),
418
+ `using positional '${capFromArg}' instead of --capability '${capFromFlag}'.`
419
+ );
420
+ }
421
+ const mergedOptions = { ...optObj, capability: requestedCapability || undefined };
422
+ try {
423
+ if (mergedOptions.watch) {
424
+ const { appKey } = await resolveAppKeyForDatasource(datasourceKey, mergedOptions.app);
425
+ await runDatasourceValidationWatchLoop({
426
+ appKey,
427
+ extraPaths: mergedOptions.watchPath || [],
428
+ includeApplicationYaml: mergedOptions.watchApplicationYaml === true,
429
+ watchCi: mergedOptions.watchCi === true,
430
+ watchFullDiff: mergedOptions.watchFullDiff === true,
431
+ runOnce: async() => {
432
+ try {
433
+ return await runDatasourceTestE2ECliOnce(datasourceKey, mergedOptions);
434
+ } catch (err) {
435
+ logger.error(formatBlockingError('E2E test failed:'), err.message);
436
+ return { exitCode: 3, envelope: null };
437
+ }
438
+ }
439
+ });
440
+ return;
441
+ }
442
+ await runDatasourceTestE2ECliAction(datasourceKey, mergedOptions);
443
+ } catch (error) {
444
+ logger.error(formatBlockingError('E2E test failed:'), error.message);
445
+ process.exit(3);
446
+ }
447
+ }
448
+
449
+ function chainDatasourceTestE2ECommand(datasource) {
450
+ const cmd = datasource
451
+ .command('test-e2e <datasourceKey> [capabilityKey]')
452
+ .description('E2E test one datasource (unified validation API, runType=e2e)');
453
+ attachDatasourceTestCommonOptions(cmd, {
454
+ includeNoAsync: true,
455
+ includePayload: false,
456
+ appHelp: 'Integration folder name (default: resolve from cwd if inside integration/<systemKey>/)',
457
+ verboseHelp: 'Audit / explain-oriented request flags where applicable',
458
+ debugHelp: 'includeDebug + log under integration/<systemKey>/logs/; TTY appendix: summary, full, or raw',
459
+ timeoutHelp: 'Aggregate timeout for POST + polls (default 15m)',
460
+ timeoutDefault: String(15 * 60 * 1000)
461
+ });
462
+ return cmd
463
+ .option('--test-crud', 'Enable CRUD lifecycle test (e2eOptions.testCrud)')
464
+ .option('--record-id <id>', 'Record ID for test (e2eOptions.recordId)')
465
+ .option('--no-cleanup', 'Disable cleanup after test (e2eOptions.cleanup: false)')
466
+ .option(
467
+ '--primary-key-value <value|@path>',
468
+ 'Primary key value or path to JSON file (e.g. @pk.json) for e2eOptions.primaryKeyValue'
469
+ )
470
+ .option('--capability <key>', 'Capability drill-down (deprecated; use positional [capabilityKey])')
471
+ .option(
472
+ '--strict-capability-scope',
473
+ 'Exit 1 if a capability drill-down is requested but the report lists more than one capabilities[] row (plan §2.3)'
474
+ )
475
+ .addHelpText('after', datasourceTestE2eHelpAfter());
476
+ }
477
+
478
+ function setupDatasourceTestE2ECommand(datasource) {
479
+ // watch flags are already attached by attachDatasourceTestCommonOptions()
480
+ chainDatasourceTestE2ECommand(datasource).action(e2eTestCommandAction);
481
+ }
482
+
483
+ module.exports = {
484
+ setupDatasourceTestCommand,
485
+ setupDatasourceTestIntegrationCommand,
486
+ setupDatasourceTestE2ECommand,
487
+ attachDatasourceWatchOptions,
488
+ /** @internal Exported for Jest: CLI action coverage without Commander. */
489
+ runDatasourceUnifiedTestOnceForWatch,
490
+ datasourceTestCommandAction,
491
+ runIntegrationOnceForWatch,
492
+ integrationTestCommandAction,
493
+ runDatasourceTestE2ECliOnce,
494
+ e2eTestCommandAction
495
+ };