@aifabrix/builder 2.42.1 → 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 (392) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/README.md +2 -2
  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 +157 -0
  7. package/integration/{hubspot → hubspot-test}/application.json +6 -6
  8. package/integration/{hubspot → hubspot-test}/create-hubspot.js +10 -10
  9. package/integration/hubspot-test/env.template +4 -0
  10. package/integration/hubspot-test/hubspot-test-datasource-company.json +138 -0
  11. package/integration/hubspot-test/hubspot-test-datasource-contact.json +146 -0
  12. package/integration/hubspot-test/hubspot-test-datasource-deal.json +146 -0
  13. package/integration/hubspot-test/hubspot-test-datasource-users.json +76 -0
  14. package/integration/{hubspot/hubspot-deploy.json → hubspot-test/hubspot-test-deploy.json} +201 -24
  15. package/integration/{hubspot/hubspot-system.json → hubspot-test/hubspot-test-system.json} +8 -7
  16. package/integration/hubspot-test/rbac.json +166 -0
  17. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-credential-real.yaml +3 -3
  18. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-env-vars.yaml +2 -2
  19. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-add-datasource.yaml +1 -1
  20. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-create.yaml +1 -1
  21. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-select.yaml +1 -1
  22. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-known-platform.yaml +1 -1
  23. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
  24. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-mode.yaml +1 -1
  25. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-file.yaml +1 -1
  26. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-url.yaml +1 -1
  27. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-source.yaml +1 -1
  28. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-array-test.yaml +1 -1
  29. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
  30. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
  31. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-test.yaml +1 -1
  32. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-test.yaml +1 -1
  33. package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +1 -1
  34. package/integration/{hubspot → hubspot-test}/test-dataplane-down-tests.js +1 -7
  35. package/integration/{hubspot → hubspot-test}/test-dataplane-down.js +3 -3
  36. package/integration/{hubspot → hubspot-test}/test.js +137 -102
  37. package/integration/{hubspot → hubspot-test}/wizard-hubspot-e2e.yaml +2 -2
  38. package/integration/{hubspot → hubspot-test}/wizard-hubspot-platform.yaml +1 -1
  39. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  40. package/integration/roundtrip-test-local/README.md +144 -0
  41. package/integration/roundtrip-test-local/application.yaml +13 -0
  42. package/integration/roundtrip-test-local/env.template +15 -0
  43. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  44. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  45. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  46. package/integration/roundtrip-test-local2/README.md +144 -0
  47. package/integration/roundtrip-test-local2/application.yaml +13 -0
  48. package/integration/roundtrip-test-local2/env.template +15 -0
  49. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  50. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  51. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  52. package/integration/test/wizard.yaml +8 -0
  53. package/jest.config.default.js +10 -0
  54. package/jest.config.integration.fixtures.js +22 -0
  55. package/jest.config.integration.js +21 -18
  56. package/jest.config.isolated.js +10 -0
  57. package/jest.projects.js +288 -0
  58. package/lib/api/datasources-core.api.js +3 -3
  59. package/lib/api/dev-mtls-request.js +110 -0
  60. package/lib/api/dev-server-https.js +145 -0
  61. package/lib/api/dev.api.js +133 -144
  62. package/lib/api/index.js +0 -1
  63. package/lib/api/pipeline.api.js +67 -20
  64. package/lib/api/service-users.api.js +111 -2
  65. package/lib/api/types/dev.types.js +4 -3
  66. package/lib/api/types/pipeline.types.js +8 -5
  67. package/lib/api/types/service-users.types.js +41 -0
  68. package/lib/api/types/validation-run.types.js +56 -0
  69. package/lib/api/validation-run.api.js +99 -0
  70. package/lib/api/validation-runner.js +99 -0
  71. package/lib/app/config.js +1 -1
  72. package/lib/app/deploy-status-display.js +2 -2
  73. package/lib/app/deploy.js +7 -6
  74. package/lib/app/display.js +2 -1
  75. package/lib/app/dockerfile.js +3 -2
  76. package/lib/app/down.js +2 -1
  77. package/lib/app/helpers.js +6 -5
  78. package/lib/app/index.js +27 -8
  79. package/lib/app/list.js +7 -6
  80. package/lib/app/push.js +4 -3
  81. package/lib/app/register.js +19 -8
  82. package/lib/app/rotate-secret.js +17 -13
  83. package/lib/app/run-container-start.js +184 -0
  84. package/lib/app/run-docker-fallback.js +108 -0
  85. package/lib/app/run-env-compose.js +30 -42
  86. package/lib/app/run-helpers.js +49 -126
  87. package/lib/app/run-infra-requirements.js +30 -0
  88. package/lib/app/run-resolve-image.js +21 -0
  89. package/lib/app/run.js +74 -21
  90. package/lib/app/show-display.js +1 -1
  91. package/lib/app/show.js +1 -1
  92. package/lib/build/index.js +13 -10
  93. package/lib/cli/index.js +2 -0
  94. package/lib/cli/setup-app.help.js +67 -0
  95. package/lib/cli/setup-app.js +59 -123
  96. package/lib/cli/setup-app.test-commands.js +179 -0
  97. package/lib/cli/setup-auth.js +36 -14
  98. package/lib/cli/setup-credential-deployment.js +22 -8
  99. package/lib/cli/setup-dev-path-commands.js +124 -0
  100. package/lib/cli/setup-dev.js +190 -103
  101. package/lib/cli/setup-environment.js +11 -20
  102. package/lib/cli/setup-external-system.js +62 -22
  103. package/lib/cli/setup-infra.js +139 -47
  104. package/lib/cli/setup-parameters.js +32 -0
  105. package/lib/cli/setup-secrets.js +147 -10
  106. package/lib/cli/setup-service-user.js +146 -20
  107. package/lib/cli/setup-utility.js +47 -19
  108. package/lib/commands/app-down.js +5 -7
  109. package/lib/commands/app-install.js +14 -7
  110. package/lib/commands/app-logs.js +13 -10
  111. package/lib/commands/app-shell.js +4 -1
  112. package/lib/commands/app-test.js +25 -19
  113. package/lib/commands/app.js +22 -10
  114. package/lib/commands/auth-config.js +10 -14
  115. package/lib/commands/auth-status.js +4 -3
  116. package/lib/commands/credential-env.js +4 -3
  117. package/lib/commands/credential-list.js +5 -4
  118. package/lib/commands/credential-push.js +4 -3
  119. package/lib/commands/datasource-unified-test-cli.js +495 -0
  120. package/lib/commands/datasource-unified-test-cli.options.js +149 -0
  121. package/lib/commands/datasource-validation-cli.js +129 -0
  122. package/lib/commands/datasource.js +123 -71
  123. package/lib/commands/deployment-list.js +6 -5
  124. package/lib/commands/dev-cli-handlers.js +122 -18
  125. package/lib/commands/dev-down.js +4 -3
  126. package/lib/commands/dev-init.js +231 -116
  127. package/lib/commands/dev-show-display.js +473 -0
  128. package/lib/commands/login-credentials.js +3 -2
  129. package/lib/commands/login-device.js +4 -3
  130. package/lib/commands/login.js +5 -4
  131. package/lib/commands/logout.js +8 -7
  132. package/lib/commands/parameters-validate.js +54 -0
  133. package/lib/commands/repair-datasource.js +314 -68
  134. package/lib/commands/repair-env-template.js +16 -10
  135. package/lib/commands/repair-rbac.js +25 -19
  136. package/lib/commands/repair.js +116 -32
  137. package/lib/commands/secrets-list.js +23 -12
  138. package/lib/commands/secrets-remove-all.js +220 -0
  139. package/lib/commands/secrets-remove.js +22 -13
  140. package/lib/commands/secrets-set.js +21 -12
  141. package/lib/commands/secrets-validate.js +20 -7
  142. package/lib/commands/secure.js +10 -9
  143. package/lib/commands/service-user.js +243 -13
  144. package/lib/commands/test-e2e-external.js +27 -1
  145. package/lib/commands/up-common.js +28 -2
  146. package/lib/commands/up-dataplane.js +31 -18
  147. package/lib/commands/up-miso.js +19 -29
  148. package/lib/commands/upload.js +138 -39
  149. package/lib/commands/wizard-core-helpers.js +1 -1
  150. package/lib/commands/wizard-dataplane.js +4 -3
  151. package/lib/commands/wizard-helpers.js +3 -3
  152. package/lib/commands/wizard.js +2 -2
  153. package/lib/core/admin-secrets.js +16 -5
  154. package/lib/core/audit-logger.js +12 -4
  155. package/lib/core/config-attach-extensions.js +46 -0
  156. package/lib/core/config-runtime-paths.js +29 -0
  157. package/lib/core/config.js +59 -58
  158. package/lib/core/diff.js +3 -2
  159. package/lib/core/ensure-encryption-key.js +2 -4
  160. package/lib/core/secrets-ensure-infra.js +77 -0
  161. package/lib/core/secrets-ensure.js +120 -64
  162. package/lib/core/secrets-env-write.js +35 -7
  163. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  164. package/lib/core/secrets.js +228 -42
  165. package/lib/core/templates-env.js +4 -3
  166. package/lib/core/templates.js +1 -1
  167. package/lib/datasource/abac-validator.js +148 -0
  168. package/lib/datasource/deploy.js +75 -53
  169. package/lib/datasource/field-reference-validator.js +77 -36
  170. package/lib/datasource/integration-context.js +63 -0
  171. package/lib/datasource/list.js +8 -7
  172. package/lib/datasource/log-viewer.js +252 -0
  173. package/lib/datasource/resolve-app.js +109 -0
  174. package/lib/datasource/test-e2e.js +95 -155
  175. package/lib/datasource/test-integration.js +121 -109
  176. package/lib/datasource/unified-validation-run-body.js +65 -0
  177. package/lib/datasource/unified-validation-run-post.js +23 -0
  178. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  179. package/lib/datasource/unified-validation-run.js +92 -0
  180. package/lib/datasource/validate.js +162 -15
  181. package/lib/deployment/deployer.js +4 -3
  182. package/lib/deployment/environment.js +7 -6
  183. package/lib/deployment/push.js +17 -8
  184. package/lib/external-system/delete.js +4 -3
  185. package/lib/external-system/deploy.js +131 -53
  186. package/lib/external-system/download-helpers.js +1 -1
  187. package/lib/external-system/download.js +7 -6
  188. package/lib/external-system/generator.js +104 -14
  189. package/lib/external-system/integration-test-dispatch.js +26 -0
  190. package/lib/external-system/test-execution.js +5 -1
  191. package/lib/external-system/test-helpers.js +0 -4
  192. package/lib/external-system/test-system-level-helpers.js +110 -0
  193. package/lib/external-system/test-system-level.js +83 -44
  194. package/lib/external-system/test.js +59 -8
  195. package/lib/generator/builders.js +23 -11
  196. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  197. package/lib/generator/external-controller-manifest.js +3 -3
  198. package/lib/generator/external.js +23 -11
  199. package/lib/generator/helpers.js +71 -12
  200. package/lib/generator/index.js +8 -4
  201. package/lib/generator/split-readme.js +12 -7
  202. package/lib/generator/split-variables.js +2 -1
  203. package/lib/generator/split.js +46 -11
  204. package/lib/generator/wizard-readme.js +3 -3
  205. package/lib/generator/wizard.js +16 -13
  206. package/lib/infrastructure/compose.js +60 -6
  207. package/lib/infrastructure/helpers.js +238 -51
  208. package/lib/infrastructure/index.js +64 -37
  209. package/lib/infrastructure/services.js +21 -15
  210. package/lib/internal/fs-real-sync.js +104 -0
  211. package/lib/internal/node-fs.js +98 -0
  212. package/lib/parameters/database-secret-values.js +173 -0
  213. package/lib/parameters/infra-kv-discovery.js +121 -0
  214. package/lib/parameters/infra-parameter-catalog.js +458 -0
  215. package/lib/parameters/infra-parameter-validate.js +64 -0
  216. package/lib/schema/application-schema.json +37 -17
  217. package/lib/schema/datasource-test-run.schema.json +493 -0
  218. package/lib/schema/deployment-rules.yaml +102 -63
  219. package/lib/schema/external-datasource.schema.json +1201 -433
  220. package/lib/schema/external-system.schema.json +181 -5
  221. package/lib/schema/flag-map-validation-run.json +31 -0
  222. package/lib/schema/infra-parameter.schema.json +106 -0
  223. package/lib/schema/infra.parameter.yaml +421 -0
  224. package/lib/schema/type/credential-auth-templates.json +40 -0
  225. package/lib/schema/type/document-storage.json +213 -0
  226. package/lib/schema/type/message-service.json +123 -0
  227. package/lib/schema/type/vector-store.json +88 -0
  228. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  229. package/lib/utils/api-error-handler.js +2 -2
  230. package/lib/utils/api.js +49 -14
  231. package/lib/utils/app-config-resolver.js +23 -1
  232. package/lib/utils/app-register-api.js +3 -2
  233. package/lib/utils/app-register-auth.js +1 -1
  234. package/lib/utils/app-register-config.js +4 -4
  235. package/lib/utils/app-register-display.js +3 -2
  236. package/lib/utils/app-register-validator.js +3 -2
  237. package/lib/utils/app-run-containers.js +26 -22
  238. package/lib/utils/app-scoped-config.js +31 -0
  239. package/lib/utils/app-service-env-from-builder.js +164 -0
  240. package/lib/utils/build-copy.js +1 -1
  241. package/lib/utils/build-helpers.js +20 -20
  242. package/lib/utils/build-resolve-image.js +165 -0
  243. package/lib/utils/cli-layout-chalk.js +8 -0
  244. package/lib/utils/cli-test-layout-chalk.js +267 -0
  245. package/lib/utils/cli-utils.js +88 -11
  246. package/lib/utils/compose-db-passwords.js +138 -0
  247. package/lib/utils/compose-generate-docker-compose.js +216 -0
  248. package/lib/utils/compose-generator.js +197 -291
  249. package/lib/utils/compose-miso-env.js +18 -0
  250. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  251. package/lib/utils/config-paths.js +209 -6
  252. package/lib/utils/config-scoped-resources-preference.js +41 -0
  253. package/lib/utils/controller-deployment-outcome.js +68 -0
  254. package/lib/utils/credential-display.js +2 -2
  255. package/lib/utils/credential-secrets-env.js +16 -1
  256. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  257. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  258. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  259. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  260. package/lib/utils/datasource-test-run-display.js +442 -0
  261. package/lib/utils/datasource-test-run-exit.js +58 -0
  262. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  263. package/lib/utils/datasource-test-run-report-version.js +51 -0
  264. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  265. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  266. package/lib/utils/datasource-validation-watch.js +266 -0
  267. package/lib/utils/declarative-url-ports.js +47 -0
  268. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  269. package/lib/utils/dev-ca-install.js +185 -23
  270. package/lib/utils/dev-cert-helper.js +266 -17
  271. package/lib/utils/dev-hosts-helper.js +307 -0
  272. package/lib/utils/dev-init-cert-hints.js +37 -0
  273. package/lib/utils/dev-init-health-messages.js +52 -0
  274. package/lib/utils/dev-init-resolve.js +86 -0
  275. package/lib/utils/dev-init-ssh-merge.js +65 -0
  276. package/lib/utils/dev-ssh-config-helper.js +196 -0
  277. package/lib/utils/dev-user-groups.js +93 -0
  278. package/lib/utils/docker-build.js +42 -17
  279. package/lib/utils/docker-exec.js +28 -0
  280. package/lib/utils/docker-manifest-public-port.js +116 -0
  281. package/lib/utils/docker-not-running-hint.js +52 -0
  282. package/lib/utils/docker.js +98 -11
  283. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  284. package/lib/utils/env-config-loader.js +10 -91
  285. package/lib/utils/env-copy.js +19 -10
  286. package/lib/utils/env-map.js +42 -11
  287. package/lib/utils/env-template.js +2 -2
  288. package/lib/utils/environment-scoped-resources.js +144 -0
  289. package/lib/utils/error-formatter.js +125 -9
  290. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  291. package/lib/utils/error-formatters/network-errors.js +2 -1
  292. package/lib/utils/error-formatters/permission-errors.js +2 -1
  293. package/lib/utils/error-formatters/validation-errors.js +2 -1
  294. package/lib/utils/external-env-template.js +180 -0
  295. package/lib/utils/external-readme.js +8 -1
  296. package/lib/utils/external-system-display.js +277 -136
  297. package/lib/utils/external-system-local-test-tty.js +389 -0
  298. package/lib/utils/external-system-readiness-core.js +377 -0
  299. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  300. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  301. package/lib/utils/external-system-readiness-display.js +186 -0
  302. package/lib/utils/external-system-test-helpers.js +24 -6
  303. package/lib/utils/external-system-validators.js +32 -14
  304. package/lib/utils/health-check-url.js +119 -0
  305. package/lib/utils/health-check.js +59 -25
  306. package/lib/utils/help-builder.js +14 -13
  307. package/lib/utils/image-version.js +4 -8
  308. package/lib/utils/infra-containers.js +4 -7
  309. package/lib/utils/infra-env-defaults.js +162 -0
  310. package/lib/utils/infra-status-display.js +167 -0
  311. package/lib/utils/infra-status.js +16 -8
  312. package/lib/utils/local-secrets.js +29 -7
  313. package/lib/utils/paths.js +136 -48
  314. package/lib/utils/port-resolver.js +10 -23
  315. package/lib/utils/redis-env-scope.js +62 -0
  316. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  317. package/lib/utils/remote-builder-validation.js +99 -0
  318. package/lib/utils/remote-dev-auth.js +117 -21
  319. package/lib/utils/remote-docker-env.js +67 -15
  320. package/lib/utils/remote-secrets-loader.js +13 -4
  321. package/lib/utils/resolve-docker-image-ref.js +124 -0
  322. package/lib/utils/schema-loader.js +22 -9
  323. package/lib/utils/secrets-bash-kv.js +25 -0
  324. package/lib/utils/secrets-generator.js +171 -51
  325. package/lib/utils/secrets-helpers.js +70 -59
  326. package/lib/utils/secrets-kv-scope.js +60 -0
  327. package/lib/utils/secrets-utils.js +35 -37
  328. package/lib/utils/secrets-validation.js +3 -1
  329. package/lib/utils/secrets-yaml-preserve.js +109 -0
  330. package/lib/utils/secure-file-permissions.js +91 -0
  331. package/lib/utils/ssh-key-helper.js +4 -2
  332. package/lib/utils/template-helpers.js +2 -2
  333. package/lib/utils/test-log-writer.js +3 -3
  334. package/lib/utils/token-manager.js +37 -5
  335. package/lib/utils/url-declarative-public-base.js +188 -0
  336. package/lib/utils/url-declarative-resolve-build.js +493 -0
  337. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  338. package/lib/utils/url-declarative-resolve.js +220 -0
  339. package/lib/utils/url-declarative-token-parse.js +74 -0
  340. package/lib/utils/url-declarative-url-flags.js +50 -0
  341. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  342. package/lib/utils/url-public-path-prefix.js +34 -0
  343. package/lib/utils/urls-local-registry.js +220 -0
  344. package/lib/utils/validation-report-tty-kit.js +77 -0
  345. package/lib/utils/validation-run-poll.js +89 -0
  346. package/lib/utils/validation-run-post-retry.js +73 -0
  347. package/lib/utils/validation-run-request.js +98 -0
  348. package/lib/utils/variable-transformer.js +21 -4
  349. package/lib/utils/yaml-preserve.js +78 -1
  350. package/lib/validation/datasource-warnings.js +56 -0
  351. package/lib/validation/env-template-auth.js +50 -2
  352. package/lib/validation/external-manifest-validator.js +35 -7
  353. package/lib/validation/validate-display.js +37 -31
  354. package/lib/validation/validate.js +9 -10
  355. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  356. package/lib/validation/validator.js +32 -78
  357. package/lib/validation/wizard-config-validator.js +2 -1
  358. package/package.json +11 -3
  359. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  360. package/scripts/diagnose-cli.js +150 -0
  361. package/scripts/install-local.js +304 -55
  362. package/templates/README.md +15 -2
  363. package/templates/applications/dataplane/application.yaml +52 -2
  364. package/templates/applications/dataplane/env.template +80 -18
  365. package/templates/applications/dataplane/rbac.yaml +8 -0
  366. package/templates/applications/keycloak/application.yaml +9 -1
  367. package/templates/applications/keycloak/env.template +15 -6
  368. package/templates/applications/miso-controller/application.yaml +10 -2
  369. package/templates/applications/miso-controller/env.template +55 -14
  370. package/templates/applications/miso-controller/rbac.yaml +5 -0
  371. package/templates/external-system/README.md.hbs +20 -7
  372. package/templates/external-system/deploy.js.hbs +5 -5
  373. package/templates/external-system/env.template.hbs +22 -0
  374. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  375. package/templates/infra/compose.yaml.hbs +20 -4
  376. package/templates/python/docker-compose.hbs +16 -0
  377. package/templates/typescript/docker-compose.hbs +16 -0
  378. package/integration/hubspot/README.md +0 -102
  379. package/integration/hubspot/env.template +0 -4
  380. package/integration/hubspot/hubspot-datasource-company.json +0 -541
  381. package/integration/hubspot/hubspot-datasource-contact.json +0 -639
  382. package/integration/hubspot/hubspot-datasource-deal.json +0 -588
  383. package/integration/hubspot/hubspot-datasource-users.json +0 -116
  384. package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
  385. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
  386. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
  387. package/lib/api/external-test.api.js +0 -111
  388. package/lib/schema/env-config.yaml +0 -43
  389. /package/integration/{hubspot → hubspot-test}/companies.json +0 -0
  390. /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-app-name.yaml +0 -0
  391. /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-missing-app.yaml +0 -0
  392. /package/integration/{hubspot → hubspot-test}/test-dataplane-down-helpers.js +0 -0
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Log viewer for E2E and integration test logs - format and display JSON logs.
3
+ * @fileoverview Read and format test-e2e / test-integration debug logs for terminal
4
+ * @author AI Fabrix Team
5
+ * @version 2.0.0
6
+ */
7
+ /* eslint-disable max-statements, complexity, max-depth -- Formatter functions; display branches by design */
8
+
9
+ const path = require('path');
10
+ const fs = require('fs').promises;
11
+ const chalk = require('chalk');
12
+ const logger = require('../utils/logger');
13
+ const { resolveAppKeyForDatasource } = require('./resolve-app');
14
+ const { getIntegrationPath } = require('../utils/paths');
15
+ const { sectionTitle, headerKeyValue, formatBlockingError, successGlyph, failureGlyph } = require('../utils/cli-test-layout-chalk');
16
+
17
+ /**
18
+ * Get the path to the latest log file in a directory matching a glob-like pattern
19
+ * @param {string} logsDir - Directory containing log files
20
+ * @param {string} pattern - Prefix pattern (e.g. 'test-e2e' matches test-e2e-*.json)
21
+ * @returns {Promise<string|null>} Full path to latest file or null if none
22
+ */
23
+ async function getLatestLogPath(logsDir, pattern) {
24
+ let entries;
25
+ try {
26
+ entries = await fs.readdir(logsDir, { withFileTypes: true });
27
+ } catch (err) {
28
+ if (err.code === 'ENOENT') return null;
29
+ throw err;
30
+ }
31
+ const prefix = pattern.replace(/\*$/, '');
32
+ const files = entries
33
+ .filter(e => e.isFile() && e.name.startsWith(prefix) && e.name.endsWith('.json'))
34
+ .map(e => path.join(logsDir, e.name));
35
+ if (files.length === 0) return null;
36
+ const withStats = await Promise.all(
37
+ files.map(async f => ({ path: f, mtime: (await fs.stat(f)).mtimeMs }))
38
+ );
39
+ withStats.sort((a, b) => b.mtime - a.mtime);
40
+ return withStats[0].path;
41
+ }
42
+
43
+ /**
44
+ * Truncate string for display
45
+ * @param {string} s - String
46
+ * @param {number} maxLen - Max length
47
+ * @returns {string}
48
+ */
49
+ function truncate(s, maxLen = 60) {
50
+ if (typeof s !== 'string') return String(s);
51
+ return s.length <= maxLen ? s : `${s.slice(0, maxLen - 1)}…`;
52
+ }
53
+
54
+ /**
55
+ * Format E2E log content for terminal display
56
+ * @param {Object} data - Parsed log JSON (request, response, error)
57
+ * @param {string} [fileName] - Log file name for header
58
+ */
59
+ function logE2ERequestSection(data) {
60
+ const req = data.request || {};
61
+ logger.log('');
62
+ logger.log(sectionTitle('Request:'));
63
+ logger.log(headerKeyValue('sourceIdOrKey:', String(req.sourceIdOrKey ?? '—')));
64
+ if (req.includeDebug !== undefined) logger.log(chalk.gray(` includeDebug: ${req.includeDebug}`));
65
+ if (req.cleanup !== undefined) logger.log(chalk.gray(` cleanup: ${req.cleanup}`));
66
+ if (req.primaryKeyValue !== undefined) {
67
+ logger.log(chalk.gray(` primaryKeyValue: ${truncate(JSON.stringify(req.primaryKeyValue))}`));
68
+ }
69
+ return req;
70
+ }
71
+
72
+ function logE2EStepsSection(steps) {
73
+ if (!Array.isArray(steps) || steps.length === 0) return;
74
+ logger.log(sectionTitle('Steps:'));
75
+ for (const step of steps) {
76
+ const name = step.name || step.step || 'unknown';
77
+ const ok = step.success !== false && !step.error;
78
+ logger.log(` ${ok ? successGlyph() : failureGlyph()} ${chalk.white(name)}`);
79
+ if (step.error) logger.log(chalk.red(` ${step.error}`));
80
+ if (step.message && ok) logger.log(chalk.gray(` ${step.message}`));
81
+ if ((name === 'sync' || step.step === 'sync') && step.evidence && step.evidence.jobs) {
82
+ for (const job of step.evidence.jobs) {
83
+ const audit = job.audit || {};
84
+ const parts = [];
85
+ if (job.recordsProcessed !== undefined && job.recordsProcessed !== null) parts.push(`${job.recordsProcessed} processed`);
86
+ if (job.totalRecords !== undefined && job.totalRecords !== null) parts.push(`total: ${job.totalRecords}`);
87
+ if (
88
+ (audit.inserted !== undefined && audit.inserted !== null) ||
89
+ (audit.updated !== undefined && audit.updated !== null) ||
90
+ (audit.deleted !== undefined && audit.deleted !== null)
91
+ ) {
92
+ parts.push(`(inserted: ${audit.inserted ?? 0}, updated: ${audit.updated ?? 0}, deleted: ${audit.deleted ?? 0})`);
93
+ }
94
+ if (parts.length) logger.log(chalk.gray(` Job: ${parts.join(' ')}`));
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ function logE2EAuditTraceSection(req, res) {
101
+ if (!res.auditLog || !Array.isArray(res.auditLog) || res.auditLog.length === 0) return;
102
+ logger.log('');
103
+ logger.log(sectionTitle('CIP execution trace(s):'));
104
+ logger.log(chalk.gray(`${res.auditLog.length}`));
105
+ const baseUrl = (req.dataplaneUrl || '').toString().replace(/\/$/, '');
106
+ const sourceIdOrKey = req.sourceIdOrKey || '';
107
+ res.auditLog.slice(0, 3).forEach((trace, i) => {
108
+ const id = trace.executionId || trace.id || trace.traceId;
109
+ if (!id) return;
110
+ const idStr = String(id);
111
+ logger.log(chalk.gray(` ${i + 1}. executionId: ${idStr}`));
112
+ if (baseUrl && sourceIdOrKey) {
113
+ const executionUrl = `${baseUrl}/api/v1/external/${sourceIdOrKey}/executions/${idStr}`;
114
+ logger.log(chalk.gray(` Link: ${executionUrl}`));
115
+ }
116
+ });
117
+ }
118
+
119
+ function logE2EResponseSection(req, data) {
120
+ if (data.error) {
121
+ logger.log(formatBlockingError(`Error: ${data.error}`));
122
+ return;
123
+ }
124
+ const res = data.response || {};
125
+ logger.log('');
126
+ logger.log(sectionTitle('Response:'));
127
+ logger.log(chalk.gray(` success: ${res.success}`));
128
+ if (res.status) logger.log(chalk.gray(` status: ${res.status}`));
129
+ if (res.error) logger.log(chalk.red(` error: ${res.error}`));
130
+ const steps = res.steps || res.completedActions || [];
131
+ logE2EStepsSection(steps);
132
+ logE2EAuditTraceSection(req, res);
133
+ }
134
+
135
+ function formatE2ELog(data, fileName) {
136
+ logger.log('');
137
+ logger.log(sectionTitle('E2E log'));
138
+ if (fileName) logger.log(chalk.gray(fileName));
139
+ const req = logE2ERequestSection(data);
140
+ logE2EResponseSection(req, data);
141
+ }
142
+
143
+ /**
144
+ * Format integration test log content for terminal display
145
+ * @param {Object} data - Parsed log JSON
146
+ * @param {string} [fileName] - Log file name for header
147
+ */
148
+ function formatIntegrationLog(data, fileName) {
149
+ logger.log('');
150
+ logger.log(sectionTitle('Integration log'));
151
+ if (fileName) logger.log(chalk.gray(fileName));
152
+ const req = data.request || {};
153
+ logger.log('');
154
+ logger.log(sectionTitle('Request:'));
155
+ logger.log(chalk.gray(` systemKey: ${req.systemKey ?? '—'}, datasourceKey: ${req.datasourceKey ?? '—'}`));
156
+ if (req.includeDebug !== undefined) logger.log(chalk.gray(` includeDebug: ${req.includeDebug}`));
157
+ if (data.error) {
158
+ logger.log(formatBlockingError(`Error: ${data.error}`));
159
+ return;
160
+ }
161
+ const res = data.response || {};
162
+ logger.log('');
163
+ logger.log(sectionTitle('Response:'));
164
+ logger.log(chalk.gray(` success: ${res.success}`));
165
+ if (res.error) logger.log(chalk.red(` error: ${res.error}`));
166
+ const vr = res.validationResults || {};
167
+ logger.log(sectionTitle('Validation:'));
168
+ logger.log(chalk.gray(` isValid: ${vr.isValid}`));
169
+ if (vr.errors && vr.errors.length) vr.errors.forEach(e => logger.log(chalk.red(` - ${e}`)));
170
+ const fmr = res.fieldMappingResults || {};
171
+ if (Object.keys(fmr).length) {
172
+ logger.log(sectionTitle('Field mapping:'));
173
+ logger.log(chalk.gray(` mappingCount: ${fmr.mappingCount ?? '—'}`));
174
+ if (fmr.dimensions) logger.log(chalk.gray(` dimensions: ${Object.keys(fmr.dimensions).join(', ')}`));
175
+ }
176
+ const etr = res.endpointTestResults || {};
177
+ if (Object.keys(etr).length) {
178
+ logger.log(sectionTitle('Endpoint:'));
179
+ logger.log(chalk.gray(` endpointConfigured: ${etr.endpointConfigured}`));
180
+ }
181
+ if (res.normalizedOutput || res.normalizedMetadata) {
182
+ const out = res.normalizedOutput || res.normalizedMetadata;
183
+ const keys = typeof out === 'object' && out !== null ? Object.keys(out) : [];
184
+ logger.log(sectionTitle('Normalized output:') + ' ' + chalk.gray(keys.length ? `${keys.length} fields` : '—'));
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Format log content by type
190
+ * @param {Object} parsed - Parsed JSON log
191
+ * @param {'test-e2e'|'test-integration'} logType - Log type
192
+ * @param {string} [fileName] - File name for header
193
+ */
194
+ function formatLogContent(parsed, logType, fileName) {
195
+ if (logType === 'test-e2e') {
196
+ formatE2ELog(parsed, fileName);
197
+ } else {
198
+ formatIntegrationLog(parsed, fileName);
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Run log viewer: resolve log file, read, parse, format and print
204
+ * @async
205
+ * @param {string} datasourceKey - Datasource key (used when no --file)
206
+ * @param {Object} options - Options
207
+ * @param {string} [options.app] - App key (optional, resolved from key if omitted)
208
+ * @param {string} [options.file] - Path to log file (overrides app resolution)
209
+ * @param {'test-e2e'|'test-integration'} options.logType - Log type
210
+ * @throws {Error} When file not found or invalid JSON
211
+ */
212
+ /* eslint-disable-next-line max-statements -- Resolve path, read, parse, format */
213
+ async function runLogViewer(datasourceKey, options) {
214
+ const { app, file, logType } = options;
215
+ let logPath;
216
+ let fileName;
217
+ if (file && typeof file === 'string' && file.trim()) {
218
+ logPath = path.isAbsolute(file) ? file : path.resolve(process.cwd(), file.trim());
219
+ fileName = path.basename(logPath);
220
+ } else {
221
+ if (!datasourceKey || typeof datasourceKey !== 'string') {
222
+ throw new Error('Datasource key is required when --file is not provided');
223
+ }
224
+ const { appKey } = await resolveAppKeyForDatasource(datasourceKey.trim(), app);
225
+ const appPath = getIntegrationPath(appKey);
226
+ const logsDir = path.join(appPath, 'logs');
227
+ const pattern = logType === 'test-e2e' ? 'test-e2e-' : 'test-integration-';
228
+ logPath = await getLatestLogPath(logsDir, pattern);
229
+ if (!logPath) {
230
+ throw new Error(
231
+ `No ${logType} log found in ${logsDir}. Run the test with --debug first.`
232
+ );
233
+ }
234
+ fileName = path.basename(logPath);
235
+ }
236
+ const content = await fs.readFile(logPath, 'utf8');
237
+ let parsed;
238
+ try {
239
+ parsed = JSON.parse(content);
240
+ } catch (err) {
241
+ throw new Error(`Invalid JSON in ${logPath}: ${err.message}`);
242
+ }
243
+ formatLogContent(parsed, logType, fileName);
244
+ }
245
+
246
+ module.exports = {
247
+ getLatestLogPath,
248
+ formatLogContent,
249
+ formatE2ELog,
250
+ formatIntegrationLog,
251
+ runLogViewer
252
+ };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Resolve app key for datasource commands from explicit --app, cwd, scan, or key parse.
3
+ * @fileoverview App resolution for datasource test-e2e, test-integration, log-e2e, log-integration
4
+ * @author AI Fabrix Team
5
+ * @version 2.0.0
6
+ */
7
+
8
+ const path = require('path');
9
+ const fs = require('fs');
10
+ const {
11
+ getIntegrationPath,
12
+ listIntegrationAppNames,
13
+ resolveIntegrationAppKeyFromCwd
14
+ } = require('../utils/paths');
15
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
16
+ const { loadConfigFile } = require('../utils/config-format');
17
+
18
+ /**
19
+ * For one app, check if any of its datasource files has the given key
20
+ * @param {string} appKey - Integration folder name (system key context)
21
+ * @param {string} datasourceKey - Datasource key to match
22
+ * @returns {boolean} True if this app has a datasource with that key
23
+ */
24
+ function appHasDatasourceKey(appKey, datasourceKey) {
25
+ const appPath = getIntegrationPath(appKey);
26
+ let config;
27
+ try {
28
+ const configPath = resolveApplicationConfigPath(appPath);
29
+ config = loadConfigFile(configPath);
30
+ } catch {
31
+ return false;
32
+ }
33
+ const schemaBasePath = config.externalIntegration?.schemaBasePath || './';
34
+ const datasourceFiles = config.externalIntegration?.dataSources || [];
35
+ for (const f of datasourceFiles) {
36
+ if (!f || typeof f !== 'string') continue;
37
+ const fullPath = path.isAbsolute(schemaBasePath)
38
+ ? path.join(schemaBasePath, f)
39
+ : path.join(appPath, schemaBasePath, f);
40
+ if (!fs.existsSync(fullPath)) continue;
41
+ try {
42
+ const parsed = loadConfigFile(fullPath);
43
+ if (parsed && parsed.key === datasourceKey) return true;
44
+ } catch {
45
+ // skip unreadable or invalid files
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+
51
+ /**
52
+ * Resolve app key for a datasource: explicit --app, cwd, scan by key, or parse key convention.
53
+ * @async
54
+ * @param {string} datasourceKey - Datasource key (e.g. hubspot-test-company)
55
+ * @param {string} [explicitApp] - Explicit integration folder from --app
56
+ * @returns {Promise<{appKey: string}>} Resolved app key
57
+ * @throws {Error} When app cannot be determined or multiple apps match
58
+ */
59
+ /* eslint-disable-next-line max-statements -- Resolution order: explicit, cwd, scan, parse */
60
+ async function resolveAppKeyForDatasource(datasourceKey, explicitApp) {
61
+ if (!datasourceKey || typeof datasourceKey !== 'string') {
62
+ throw new Error('Datasource key is required');
63
+ }
64
+ const key = String(datasourceKey).trim();
65
+ if (key.length === 0) {
66
+ throw new Error('Datasource key cannot be empty');
67
+ }
68
+
69
+ if (explicitApp && typeof explicitApp === 'string' && explicitApp.trim()) {
70
+ const appPath = getIntegrationPath(explicitApp.trim());
71
+ if (fs.existsSync(appPath)) {
72
+ return { appKey: explicitApp.trim() };
73
+ }
74
+ }
75
+
76
+ const fromCwd = resolveIntegrationAppKeyFromCwd();
77
+ if (fromCwd && appHasDatasourceKey(fromCwd, key)) {
78
+ return { appKey: fromCwd };
79
+ }
80
+
81
+ const appNames = listIntegrationAppNames();
82
+ const matches = appNames.filter(appName => appHasDatasourceKey(appName, key));
83
+ if (matches.length === 1) {
84
+ return { appKey: matches[0] };
85
+ }
86
+ if (matches.length > 1) {
87
+ throw new Error(
88
+ `More than one app has this datasource; add --app <app>. Apps: ${matches.join(', ')}`
89
+ );
90
+ }
91
+
92
+ const segments = key.split('-');
93
+ if (segments.length >= 2) {
94
+ const candidate = segments.slice(0, -1).join('-');
95
+ const candidatePath = getIntegrationPath(candidate);
96
+ if (fs.existsSync(candidatePath) && appHasDatasourceKey(candidate, key)) {
97
+ return { appKey: candidate };
98
+ }
99
+ }
100
+
101
+ throw new Error(
102
+ 'Could not determine app context. Use --app <app> or run from integration/<systemKey>/ directory.'
103
+ );
104
+ }
105
+
106
+ module.exports = {
107
+ resolveAppKeyForDatasource,
108
+ appHasDatasourceKey
109
+ };
@@ -1,43 +1,34 @@
1
1
  /**
2
- * Datasource E2E test - run full E2E test via dataplane external API
3
- * @fileoverview Datasource E2E test logic (config, credential, sync, data, CIP)
2
+ * Datasource E2E test unified POST /api/v1/validation/run (runType=e2e).
3
+ * @fileoverview Datasource E2E via DatasourceTestRun envelope
4
4
  * @author AI Fabrix Team
5
5
  * @version 2.0.0
6
6
  */
7
- /* eslint-disable max-statements -- Auth setup, API call, polling, debug log */
8
7
 
9
8
  const path = require('path');
10
9
  const fs = require('fs').promises;
11
10
  const chalk = require('chalk');
12
11
  const logger = require('../utils/logger');
13
- const { getIntegrationPath, resolveIntegrationAppKeyFromCwd } = require('../utils/paths');
14
- const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
15
- const { resolveControllerUrl } = require('../utils/controller-url');
16
- const { getDeviceOnlyAuth } = require('../utils/token-manager');
17
- const { testDatasourceE2E, getE2ETestRun } = require('../api/external-test.api');
12
+ const { getIntegrationPath } = require('../utils/paths');
13
+ const { resolveAppKeyForDatasource } = require('./resolve-app');
14
+ const { infoLine } = require('../utils/cli-test-layout-chalk');
15
+ const { runUnifiedDatasourceValidation } = require('./unified-validation-run');
16
+ const { includeDebugForRequest } = require('../utils/validation-run-request');
17
+ const { e2eShapeFromEnvelope } = require('../utils/datasource-test-run-legacy-adapter');
18
18
  const { writeTestLog } = require('../utils/test-log-writer');
19
19
 
20
- const DEFAULT_POLL_INTERVAL_MS = 2500;
21
20
  const DEFAULT_POLL_TIMEOUT_MS = 15 * 60 * 1000;
22
21
 
23
- /**
24
- * Resolve appKey for datasource test-e2e
25
- * @param {string} [appKey] - Explicit app key from --app
26
- * @returns {string}
27
- */
28
- function resolveAppKey(appKey) {
29
- if (appKey) return appKey;
30
- const fromCwd = resolveIntegrationAppKeyFromCwd();
31
- if (fromCwd) return fromCwd;
32
- throw new Error(
33
- 'Could not determine app context. Use --app <appKey> or run from integration/<appKey>/ directory.'
34
- );
22
+ function logE2eDatasourceBanner(datasourceKey, verbose) {
23
+ if (!verbose) return;
24
+ logger.log('');
25
+ logger.log(infoLine(`🧪 Running E2E test for datasource: ${datasourceKey}`));
35
26
  }
36
27
 
37
28
  /**
38
29
  * Resolve primaryKeyValue for request body: string as-is, or read and parse JSON from @path
39
- * @param {string} [value] - Literal value or path prefixed with @ (e.g. @pk.json)
40
- * @returns {Promise<string|Object|null>} Resolved value for body.primaryKeyValue, or null if absent
30
+ * @param {string} [value]
31
+ * @returns {Promise<string|Object|null>}
41
32
  */
42
33
  async function resolvePrimaryKeyValue(value) {
43
34
  if (value === null || value === undefined || value === '') return null;
@@ -50,170 +41,119 @@ async function resolvePrimaryKeyValue(value) {
50
41
  return str;
51
42
  }
52
43
 
53
- /**
54
- * Build E2E request body from options
55
- * @param {Object} options - Command options
56
- * @returns {Promise<Object>} Request body
57
- */
58
- async function buildE2EBody(options) {
59
- const body = {};
60
- if (options.debug) body.includeDebug = true;
61
- if (options.testCrud === true) body.testCrud = true;
62
- if (options.recordId !== undefined && options.recordId !== null && options.recordId !== '') body.recordId = String(options.recordId);
63
- if (options.cleanup === false) body.cleanup = false;
64
- else if (options.cleanup === true) body.cleanup = true;
65
- const pk = await resolvePrimaryKeyValue(options.primaryKeyValue);
66
- if (pk !== null && pk !== undefined) body.primaryKeyValue = pk;
67
- return body;
44
+ function e2eIntegrationLogDir(appKey) {
45
+ return path.dirname(getIntegrationPath(appKey));
68
46
  }
69
47
 
70
48
  /**
71
- * Poll E2E test run until completed or failed
72
- * @param {string} dataplaneUrl - Dataplane URL
73
- * @param {string} sourceIdOrKey - Source ID or key
74
- * @param {string} testRunId - Test run ID
75
- * @param {Object} authConfig - Auth config
76
- * @param {Object} opts - Poll options
77
- * @param {number} [opts.intervalMs] - Poll interval (ms)
78
- * @param {number} [opts.timeoutMs] - Max wait (ms)
79
- * @param {boolean} [opts.verbose] - Log each poll
80
- * @returns {Promise<Object>} Final poll result (status completed or failed)
49
+ * Throw when unified run failed, timed out, or needs async; optionally write debug log.
50
+ * @returns {Promise<void>}
81
51
  */
82
- async function pollE2ETestRun(dataplaneUrl, sourceIdOrKey, testRunId, authConfig, opts = {}) {
83
- const intervalMs = opts.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
84
- const timeoutMs = opts.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
85
- const verbose = opts.verbose === true;
86
- const deadline = Date.now() + timeoutMs;
87
- let last;
88
- while (Date.now() < deadline) {
89
- last = await getE2ETestRun(dataplaneUrl, sourceIdOrKey, testRunId, authConfig);
90
- if (last.status === 'completed' || last.status === 'failed') {
91
- return last;
52
+ async function throwIfUnifiedE2EBlocked(unifiedResult, appKey, options, requestMeta) {
53
+ if (unifiedResult.apiError) {
54
+ const msg =
55
+ unifiedResult.apiError.formattedError ||
56
+ unifiedResult.apiError.error ||
57
+ 'E2E request failed';
58
+ if (options.debug) {
59
+ await writeTestLog(
60
+ appKey,
61
+ { request: requestMeta, error: msg },
62
+ 'test-e2e',
63
+ e2eIntegrationLogDir(appKey)
64
+ );
92
65
  }
93
- if (verbose) {
94
- const steps = last.completedActions || [];
95
- logger.log(chalk.gray(` Polling… status: ${last.status}, ${steps.length} step(s) completed`));
66
+ throw new Error(msg);
67
+ }
68
+ if (unifiedResult.pollTimedOut) {
69
+ const err = new Error('Report incomplete: timeout');
70
+ if (options.debug) {
71
+ await writeTestLog(
72
+ appKey,
73
+ { request: requestMeta, error: err.message },
74
+ 'test-e2e',
75
+ e2eIntegrationLogDir(appKey)
76
+ );
96
77
  }
97
- await new Promise(r => setTimeout(r, intervalMs));
78
+ throw err;
79
+ }
80
+ if (unifiedResult.incompleteNoAsync) {
81
+ throw new Error(
82
+ 'Report incomplete: async polling disabled (--no-async) but server returned partial report.'
83
+ );
98
84
  }
99
- throw new Error(
100
- `E2E test run did not complete within ${timeoutMs / 1000}s (run ID: ${testRunId})`
101
- );
102
85
  }
103
86
 
104
87
  /**
105
- * Run E2E test for one datasource (Bearer token or API key required; no client credentials).
106
- * Default: async start + polling until completed/failed. Use options.async === false for sync.
107
- *
88
+ * Run E2E test for one datasource (unified validation API; deployment auth like test-integration).
108
89
  * @async
109
- * @param {string} datasourceKey - Datasource key (used as sourceIdOrKey)
110
- * @param {Object} options - Options
111
- * @param {string} [options.app] - App key (or resolve from cwd)
112
- * @param {string} [options.environment] - Environment (dev, tst, pro)
113
- * @param {boolean} [options.debug] - Include debug, write log file
114
- * @param {boolean} [options.verbose] - Verbose output (e.g. poll progress)
115
- * @param {boolean} [options.async] - If false, use sync mode (no polling). Default true.
116
- * @param {boolean} [options.testCrud] - Set body testCrud true
117
- * @param {string} [options.recordId] - Set body recordId
118
- * @param {boolean} [options.cleanup] - Set body cleanup (default true)
119
- * @param {string} [options.primaryKeyValue] - Set body primaryKeyValue (string or @path to JSON)
120
- * @param {number} [options.pollIntervalMs] - Poll interval in ms (default 2500)
121
- * @param {number} [options.pollTimeoutMs] - Poll timeout in ms (default 15 min)
122
- * @returns {Promise<Object>} E2E test result (steps, success, error, etc.)
90
+ * @param {string} datasourceKey
91
+ * @param {Object} options
92
+ * @param {boolean} [options.sync] - Publish local datasource JSON before validation when true
93
+ * @returns {Promise<Object>} Shape compatible with displayE2EResults (steps, success, status)
123
94
  */
124
95
  async function runDatasourceTestE2E(datasourceKey, options = {}) {
125
96
  if (!datasourceKey || typeof datasourceKey !== 'string') {
126
97
  throw new Error('Datasource key is required');
127
98
  }
128
- const appKey = resolveAppKey(options.app);
129
- const controllerUrl = await resolveControllerUrl();
130
- const { resolveEnvironment } = require('../core/config');
131
- const environment = options.environment || await resolveEnvironment();
132
- const authConfig = await getDeviceOnlyAuth(controllerUrl);
133
- const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
99
+ const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
134
100
 
135
- logger.log(chalk.blue(`\n🧪 Running E2E test for datasource: ${datasourceKey}`));
101
+ logE2eDatasourceBanner(datasourceKey, options.verbose);
102
+
103
+ const pk = await resolvePrimaryKeyValue(options.primaryKeyValue);
104
+ const timeoutRaw =
105
+ options.timeout !== undefined && options.timeout !== null && options.timeout !== ''
106
+ ? parseInt(String(options.timeout), 10)
107
+ : options.pollTimeoutMs;
108
+ const timeoutMs =
109
+ Number.isFinite(timeoutRaw) && timeoutRaw > 0 ? timeoutRaw : DEFAULT_POLL_TIMEOUT_MS;
136
110
 
137
- const body = await buildE2EBody(options);
138
- const useAsync = options.async !== false;
139
111
  const requestMeta = {
140
- sourceIdOrKey: datasourceKey,
141
- includeDebug: options.debug,
112
+ datasourceKey,
113
+ runType: 'e2e',
114
+ includeDebug: includeDebugForRequest(options.debug),
142
115
  testCrud: options.testCrud,
143
116
  recordId: options.recordId,
144
117
  cleanup: options.cleanup,
145
- primaryKeyValue: options.primaryKeyValue !== undefined && options.primaryKeyValue !== null
118
+ primaryKeyValue: pk !== undefined && pk !== null
146
119
  };
147
120
 
148
- const execOpts = {
149
- dataplaneUrl,
150
- datasourceKey,
151
- authConfig,
152
- body,
153
- useAsync,
121
+ const unifiedResult = await runUnifiedDatasourceValidation(datasourceKey, {
122
+ app: options.app,
123
+ environment: options.environment,
124
+ runType: 'e2e',
125
+ debug: options.debug,
154
126
  verbose: options.verbose,
155
- pollIntervalMs: options.pollIntervalMs,
156
- pollTimeoutMs: options.pollTimeoutMs
157
- };
158
- let data;
159
- try {
160
- data = await executeE2EWithOptionalPoll(execOpts);
161
- } catch (error) {
162
- if (options.debug) {
163
- const appPath = getIntegrationPath(appKey);
164
- const integrationDir = path.dirname(appPath);
165
- await writeTestLog(appKey, { request: requestMeta, error: error.message }, 'test-e2e', integrationDir);
166
- }
167
- throw error;
168
- }
127
+ async: options.async !== false,
128
+ noAsync: options.async === false,
129
+ testCrud: options.testCrud,
130
+ recordId: options.recordId,
131
+ cleanup: options.cleanup,
132
+ primaryKeyValue: pk,
133
+ capabilityKey: options.capabilityKey,
134
+ timeout: timeoutMs,
135
+ sync: options.sync === true
136
+ });
169
137
 
170
- if (options.debug) {
171
- const appPath = getIntegrationPath(appKey);
172
- const integrationDir = path.dirname(appPath);
173
- const logPath = await writeTestLog(appKey, { request: requestMeta, response: data }, 'test-e2e', integrationDir);
174
- logger.log(chalk.gray(` Debug log: ${logPath}`));
175
- }
138
+ await throwIfUnifiedE2EBlocked(unifiedResult, appKey, options, requestMeta);
176
139
 
177
- return data;
178
- }
140
+ const display = e2eShapeFromEnvelope(unifiedResult.envelope);
141
+ Object.assign(display, { datasourceTestRun: unifiedResult.envelope });
179
142
 
180
- /**
181
- * Call E2E API and optionally poll until completed. On throw, caller should log if debug.
182
- * @param {Object} opts - Options
183
- * @param {string} opts.dataplaneUrl - Dataplane URL
184
- * @param {string} opts.datasourceKey - Source key
185
- * @param {Object} opts.authConfig - Auth config
186
- * @param {Object} opts.body - Request body
187
- * @param {boolean} opts.useAsync - Whether to use async + poll
188
- * @param {boolean} opts.verbose - Verbose poll progress
189
- * @param {number} [opts.pollIntervalMs] - Override poll interval (ms)
190
- * @param {number} [opts.pollTimeoutMs] - Override poll timeout (ms)
191
- * @returns {Promise<Object>} Final result data
192
- */
193
- /* eslint-disable-next-line max-params -- single opts object; destructuring in body */
194
- async function executeE2EWithOptionalPoll(opts) {
195
- const { dataplaneUrl, datasourceKey, authConfig, body, useAsync, verbose, pollIntervalMs, pollTimeoutMs } = opts;
196
- const response = await testDatasourceE2E(dataplaneUrl, datasourceKey, authConfig, body, {
197
- asyncRun: useAsync
198
- });
199
- let data = response.data || response;
200
- if (useAsync && data !== null && data !== undefined && data.testRunId) {
201
- data = await pollE2ETestRun(
202
- dataplaneUrl,
203
- datasourceKey,
204
- data.testRunId,
205
- authConfig,
206
- {
207
- intervalMs: pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
208
- timeoutMs: pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS,
209
- verbose
210
- }
143
+ if (options.debug) {
144
+ const logPath = await writeTestLog(
145
+ appKey,
146
+ { request: requestMeta, response: unifiedResult.envelope },
147
+ 'test-e2e',
148
+ e2eIntegrationLogDir(appKey)
211
149
  );
150
+ logger.log(chalk.gray(` Debug log: ${logPath}`));
212
151
  }
213
- return data;
152
+
153
+ return display;
214
154
  }
215
155
 
216
156
  module.exports = {
217
157
  runDatasourceTestE2E,
218
- resolveAppKey
158
+ resolvePrimaryKeyValue
219
159
  };