@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,129 @@
1
+ const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
2
+ /**
3
+ * @fileoverview Shared CLI handling for unified validation (DatasourceTestRun + exit matrix).
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 { computeExitCodeFromDatasourceTestRun, exitCodeForPollTimeout } = require('../utils/datasource-test-run-exit');
11
+ const { analyzeCapabilityScope } = require('../utils/datasource-test-run-capability-scope');
12
+ const ttyLog = require('../utils/datasource-test-run-tty-log');
13
+ const { logEnvelopeForInteractiveCli } = ttyLog;
14
+
15
+ /**
16
+ * Build unified CLI result from integration test return shape.
17
+ * @param {Object} r - runDatasourceTestIntegration result
18
+ * @returns {{ envelope: Object|null, apiError: Object|null, pollTimedOut: boolean, incompleteNoAsync: boolean }}
19
+ */
20
+ function unifiedCliResultFromIntegrationReturn(r) {
21
+ const meta = r.runMeta || {};
22
+ return {
23
+ apiError: meta.apiError || null,
24
+ pollTimedOut: meta.pollTimedOut === true,
25
+ incompleteNoAsync: meta.incompleteNoAsync === true,
26
+ envelope: r.datasourceTestRun || null
27
+ };
28
+ }
29
+
30
+ function logApiError(apiError) {
31
+ logger.error(
32
+ formatBlockingError('Dataplane request failed:'),
33
+ apiError.formattedError || apiError.error || 'Request failed'
34
+ );
35
+ if (apiError.status) {
36
+ logger.error(chalk.gray(` HTTP ${apiError.status}`));
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Print unified validation outcome and return exit code (plan §3.14 watch — no process.exit).
42
+ * @param {Object} result - From runUnifiedDatasourceValidation
43
+ * @param {Object} options - CLI flags
44
+ * @returns {number}
45
+ */
46
+ function finalizeUnifiedValidationResult(result, options = {}) {
47
+ if (result.apiError) {
48
+ logApiError(result.apiError);
49
+ return 3;
50
+ }
51
+ if (result.pollTimedOut) {
52
+ logger.error(formatBlockingError('Report incomplete: timeout'));
53
+ return exitCodeForPollTimeout(result.envelope);
54
+ }
55
+ if (result.incompleteNoAsync) {
56
+ logger.error(
57
+ chalk.red(
58
+ '✖ Report incomplete: async polling disabled (--no-async) but server returned partial/minimal report.'
59
+ )
60
+ );
61
+ return 3;
62
+ }
63
+
64
+ const envelope = result.envelope;
65
+ logEnvelopeForInteractiveCli(envelope, options);
66
+
67
+ let exitCode = computeExitCodeFromDatasourceTestRun(envelope, {
68
+ warningsAsErrors: options.warningsAsErrors === true,
69
+ requireCert: options.requireCert === true
70
+ });
71
+ const scope = analyzeCapabilityScope(envelope, options.requestedCapabilityKey);
72
+ if (options.strictCapabilityScope === true && scope.violated) {
73
+ exitCode = Math.max(exitCode, 1);
74
+ }
75
+ if (
76
+ exitCode === 2 &&
77
+ options.requireCert &&
78
+ !envelope.certificate
79
+ ) {
80
+ logger.error(formatBlockingError('Certification not returned; cannot verify.'));
81
+ }
82
+ return exitCode;
83
+ }
84
+
85
+ /**
86
+ * Handle unified validation result: logs, stdout JSON, process.exit.
87
+ * @param {Object} result - From runUnifiedDatasourceValidation
88
+ * @param {Object} options - CLI flags
89
+ * @returns {void} Exits process
90
+ */
91
+ function exitFromUnifiedValidationResult(result, options = {}) {
92
+ process.exit(finalizeUnifiedValidationResult(result, options));
93
+ }
94
+
95
+ /**
96
+ * Compute exit code after integration CLI display (no process.exit; plan §3.14 watch).
97
+ * @param {Object} integrationResult - From runDatasourceTestIntegration
98
+ * @param {Object} [exitOpts]
99
+ * @returns {number}
100
+ */
101
+ function finalizeAfterIntegrationDisplay(integrationResult, exitOpts = {}) {
102
+ const env = integrationResult.datasourceTestRun;
103
+ if (!env) {
104
+ return integrationResult.success ? 0 : 1;
105
+ }
106
+ return computeExitCodeFromDatasourceTestRun(env, {
107
+ warningsAsErrors: exitOpts.warningsAsErrors === true,
108
+ requireCert: exitOpts.requireCert === true
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Exit after integration CLI when not using raw unified output modes.
114
+ * @param {Object} integrationResult - From runDatasourceTestIntegration
115
+ * @param {Object} [exitOpts]
116
+ */
117
+ function exitAfterIntegrationDisplay(integrationResult, exitOpts = {}) {
118
+ process.exit(finalizeAfterIntegrationDisplay(integrationResult, exitOpts));
119
+ }
120
+
121
+ module.exports = {
122
+ exitFromUnifiedValidationResult,
123
+ finalizeUnifiedValidationResult,
124
+ emitReportVersionDiagnostics: ttyLog.emitReportVersionDiagnostics,
125
+ emitCapabilityScopeDiagnostics: ttyLog.emitCapabilityScopeDiagnostics,
126
+ unifiedCliResultFromIntegrationReturn,
127
+ finalizeAfterIntegrationDisplay,
128
+ exitAfterIntegrationDisplay
129
+ };
@@ -1,39 +1,103 @@
1
1
  /**
2
2
  * AI Fabrix Builder - Datasource Commands
3
3
  *
4
- * Handles datasource validation, listing, comparison, and deployment
5
- * Commands: datasource validate, datasource list, datasource diff, datasource upload
4
+ * Handles datasource validation, listing, comparison, deployment, and online validation runs.
5
+ * Subcommands `test`, `test-integration`, and `test-e2e` call the dataplane unified validation API; permissions are summarized in `docs/commands/permissions.md`.
6
6
  *
7
7
  * @fileoverview Datasource management commands for AI Fabrix Builder
8
8
  * @author AI Fabrix Team
9
9
  * @version 2.0.0
10
10
  */
11
11
 
12
+ const path = require('path');
12
13
  const chalk = require('chalk');
13
14
  const logger = require('../utils/logger');
15
+ const { sectionTitle, headerKeyValue, metadata, formatSuccessLine, formatBlockingError } = require('../utils/cli-test-layout-chalk');
14
16
  const { validateDatasourceFile } = require('../datasource/validate');
15
17
  const { listDatasources } = require('../datasource/list');
16
18
  const { compareDatasources } = require('../datasource/diff');
17
19
  const { deployDatasource } = require('../datasource/deploy');
18
- const { runDatasourceTestIntegration } = require('../datasource/test-integration');
19
- const { runDatasourceTestE2E } = require('../datasource/test-e2e');
20
- const { displayIntegrationTestResults, displayE2EResults } = require('../utils/external-system-display');
20
+ const { runLogViewer } = require('../datasource/log-viewer');
21
+ const {
22
+ setupDatasourceTestCommand,
23
+ setupDatasourceTestIntegrationCommand,
24
+ setupDatasourceTestE2ECommand
25
+ } = require('./datasource-unified-test-cli');
26
+
27
+ const DATASOURCE_HELP_AFTER = `
28
+ Subcommands:
29
+ validate <file-or-key> Validate datasource JSON (path or datasource key under integration/<app>/)
30
+ list List datasources (env from config)
31
+ upload <file-or-key> Deploy one datasource JSON to the dataplane (path or key; systemKey in file)
32
+ diff Compare two datasource JSON files
33
+ test <key> Structural/policy validation via unified dataplane API (DatasourceTestRun)
34
+ test-integration / test-e2e Integration or E2E run via the same unified validation API
35
+ log-integration / log-e2e Show saved test logs
36
+ `;
37
+
38
+ const DATASOURCE_VALIDATE_HELP_AFTER = `
39
+ Examples:
40
+ $ aifabrix datasource validate test-e2e-hubspot-users
41
+ $ aifabrix datasource validate integration/myapp/myapp-datasource-contacts.json
42
+ $ aifabrix datasource validate ./test-e2e-hubspot-datasource-users.json
43
+ $ aifabrix datasource validate /path/to/system-datasource-entity.json
44
+ $ af ds validate ../integration/hubspot/hubspot-datasource-deals.json
45
+ `;
46
+
47
+ const DATASOURCE_UPLOAD_HELP_AFTER = `
48
+ Examples:
49
+ $ aifabrix datasource upload test-e2e-hubspot-users
50
+ $ aifabrix datasource upload integration/myapp/myapp-datasource-contacts.json
51
+ $ aifabrix datasource upload ./test-e2e-hubspot-datasource-users.json
52
+ $ aifabrix datasource upload /path/to/system-datasource-entity.json
53
+ $ af ds upload ../integration/hubspot/hubspot-datasource-deals.json
54
+ `;
55
+
56
+ /**
57
+ * TTY layout for local datasource JSON validation (aligned with cli-test-layout-chalk).
58
+ * @param {{ valid: boolean, errors: string[], resolvedPath: string }} result
59
+ * @param {string} trimmed - original CLI argument
60
+ * @param {boolean} showMapping - show Key + File when key resolved to a path
61
+ */
62
+ function logDatasourceValidateOutcome(result, trimmed, showMapping) {
63
+ logger.log('');
64
+ logger.log(sectionTitle('Datasource validation'));
65
+ logger.log(metadata('Offline — JSON schema and integration wiring'));
66
+ logger.log('');
67
+ if (!result.valid) {
68
+ logger.log(headerKeyValue('File:', result.resolvedPath));
69
+ logger.log('');
70
+ logger.log(formatBlockingError('Datasource file has errors:'));
71
+ result.errors.forEach(error => logger.log(chalk.red(` • ${error}`)));
72
+ return;
73
+ }
74
+ if (showMapping) {
75
+ logger.log(headerKeyValue('Key:', trimmed));
76
+ logger.log(headerKeyValue('File:', result.resolvedPath));
77
+ } else {
78
+ logger.log(headerKeyValue('File:', result.resolvedPath));
79
+ }
80
+ logger.log('');
81
+ logger.log(formatSuccessLine('Datasource file is valid.'));
82
+ }
21
83
 
22
84
  function setupDatasourceValidateCommand(datasource) {
23
- datasource.command('validate <file>')
24
- .description('Validate external datasource JSON file')
25
- .action(async(file) => {
85
+ datasource.command('validate <file-or-key>')
86
+ .description('Validate datasource JSON (file path or datasource key under integration/<app>/)')
87
+ .addHelpText('after', DATASOURCE_VALIDATE_HELP_AFTER)
88
+ .action(async(fileOrKey) => {
26
89
  try {
27
- const result = await validateDatasourceFile(file);
28
- if (result.valid) {
29
- logger.log(chalk.green(`\n✓ Datasource file is valid: ${file}`));
30
- } else {
31
- logger.log(chalk.red(`\n✗ Datasource file has errors: ${file}`));
32
- result.errors.forEach(error => logger.log(chalk.red(` • ${error}`)));
90
+ const trimmed = fileOrKey.trim();
91
+ const result = await validateDatasourceFile(trimmed);
92
+ const resolvedPath = result.resolvedPath;
93
+ const argResolved = path.resolve(trimmed);
94
+ const showMapping = resolvedPath && argResolved !== resolvedPath && trimmed !== resolvedPath;
95
+ logDatasourceValidateOutcome(result, trimmed, showMapping);
96
+ if (!result.valid) {
33
97
  process.exit(1);
34
98
  }
35
99
  } catch (error) {
36
- logger.error(chalk.red('Validation failed:'), error.message);
100
+ logger.error(formatBlockingError('Validation failed:'), error.message);
37
101
  process.exit(1);
38
102
  }
39
103
  });
@@ -41,12 +105,12 @@ function setupDatasourceValidateCommand(datasource) {
41
105
 
42
106
  function setupDatasourceListCommand(datasource) {
43
107
  datasource.command('list')
44
- .description('List datasources from environment (uses environment from config.yaml)')
108
+ .description('List datasources for environment in config')
45
109
  .action(async() => {
46
110
  try {
47
111
  await listDatasources({});
48
112
  } catch (error) {
49
- logger.error(chalk.red('Failed to list datasources:'), error.message);
113
+ logger.error(formatBlockingError('Failed to list datasources:'), error.message);
50
114
  process.exit(1);
51
115
  }
52
116
  });
@@ -54,91 +118,70 @@ function setupDatasourceListCommand(datasource) {
54
118
 
55
119
  function setupDatasourceDiffCommand(datasource) {
56
120
  datasource.command('diff <file1> <file2>')
57
- .description('Compare two datasource configuration files (for dataplane)')
121
+ .description('Diff two datasource JSON files')
58
122
  .action(async(file1, file2) => {
59
123
  try {
60
124
  await compareDatasources(file1, file2);
61
125
  } catch (error) {
62
- logger.error(chalk.red('Diff failed:'), error.message);
126
+ logger.error(formatBlockingError('Diff failed:'), error.message);
63
127
  process.exit(1);
64
128
  }
65
129
  });
66
130
  }
67
131
 
68
132
  function setupDatasourceUploadCommand(datasource) {
69
- datasource.command('upload <myapp> <file>')
70
- .description('Upload datasource to dataplane')
71
- .action(async(myapp, file, options) => {
133
+ datasource.command('upload <file-or-key>')
134
+ .description('Deploy datasource JSON to dataplane (file path or datasource key under integration/<app>/)')
135
+ .addHelpText('after', DATASOURCE_UPLOAD_HELP_AFTER)
136
+ .action(async(fileOrKey, options) => {
72
137
  try {
73
- await deployDatasource(myapp, file, options);
138
+ await deployDatasource(fileOrKey, options);
74
139
  } catch (error) {
75
- logger.error(chalk.red('Upload failed:'), error.message);
140
+ logger.error(formatBlockingError('Upload failed:'), error.message);
76
141
  process.exit(1);
77
142
  }
78
143
  });
79
144
  }
80
145
 
81
- function setupDatasourceTestIntegrationCommand(datasource) {
82
- datasource.command('test-integration <datasourceKey>')
83
- .description('Run integration (config) test for one datasource via dataplane pipeline')
84
- .option('-a, --app <appKey>', 'App key (default: resolve from cwd if inside integration/<appKey>/)')
85
- .option('-p, --payload <file>', 'Path to custom test payload file')
86
- .option('-e, --env <env>', 'Environment: dev, tst, or pro')
87
- .option('--debug', 'Include debug output and write log to integration/<appKey>/logs/')
88
- .option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
146
+ function setupDatasourceLogE2ECommand(datasource) {
147
+ datasource.command('log-e2e <datasourceKey>')
148
+ .description('Show E2E test log (latest or --file)')
149
+ .option(
150
+ '-a, --app <app>',
151
+ 'Integration folder name (optional: resolve from cwd or datasource key if single match)'
152
+ )
153
+ .option('-f, --file <path>', 'Path to log file (default: latest in app logs folder)')
89
154
  .action(async(datasourceKey, options) => {
90
155
  try {
91
- const result = await runDatasourceTestIntegration(datasourceKey, {
156
+ await runLogViewer(datasourceKey, {
92
157
  app: options.app,
93
- payload: options.payload,
94
- environment: options.env,
95
- debug: options.debug,
96
- timeout: options.timeout
158
+ file: options.file,
159
+ logType: 'test-e2e'
97
160
  });
98
- displayIntegrationTestResults({
99
- systemKey: result.systemKey || 'unknown',
100
- datasourceResults: [result],
101
- success: result.success
102
- }, options.verbose);
103
- if (!result.success) process.exit(1);
104
161
  } catch (error) {
105
- logger.error(chalk.red(' Integration test failed:'), error.message);
162
+ logger.error(formatBlockingError('log-e2e failed:'), error.message);
106
163
  process.exit(1);
107
164
  }
108
165
  });
109
166
  }
110
167
 
111
- function setupDatasourceTestE2ECommand(datasource) {
112
- datasource.command('test-e2e <datasourceKey>')
113
- .description('Run E2E test for one datasource (config, credential, sync, data, CIP) via dataplane')
114
- .option('-a, --app <appKey>', 'App key (default: resolve from cwd if inside integration/<appKey>/)')
115
- .option('-e, --env <env>', 'Environment: dev, tst, or pro')
116
- .option('-v, --verbose', 'Show detailed step output and poll progress')
117
- .option('--debug', 'Include debug output and write log to integration/<appKey>/logs/')
118
- .option('--test-crud', 'Enable CRUD lifecycle test (body testCrud: true)')
119
- .option('--record-id <id>', 'Record ID for test (body recordId)')
120
- .option('--no-cleanup', 'Disable cleanup after test (body cleanup: false)')
121
- .option('--primary-key-value <value|@path>', 'Primary key value or path to JSON file (e.g. @pk.json) for body primaryKeyValue')
122
- .option('--no-async', 'Use sync mode (no polling); single POST, no asyncRun')
168
+ function setupDatasourceLogIntegrationCommand(datasource) {
169
+ datasource.command('log-integration <datasourceKey>')
170
+ .description('Show integration test log (latest or --file)')
171
+ .option(
172
+ '-a, --app <app>',
173
+ 'Integration folder name (optional: resolve from cwd or datasource key if single match)'
174
+ )
175
+ .option('-f, --file <path>', 'Path to log file (default: latest in app logs folder)')
123
176
  .action(async(datasourceKey, options) => {
124
177
  try {
125
- const data = await runDatasourceTestE2E(datasourceKey, {
178
+ await runLogViewer(datasourceKey, {
126
179
  app: options.app,
127
- environment: options.env,
128
- debug: options.debug,
129
- verbose: options.verbose,
130
- async: options.async !== false,
131
- testCrud: options.testCrud,
132
- recordId: options.recordId,
133
- cleanup: options.cleanup,
134
- primaryKeyValue: options.primaryKeyValue
180
+ file: options.file,
181
+ logType: 'test-integration'
135
182
  });
136
- displayE2EResults(data, options.verbose);
137
- const steps = data.steps || data.completedActions || [];
138
- const failed = data.success === false || steps.some(s => s.success === false || s.error);
139
- if (failed) process.exit(1);
140
183
  } catch (error) {
141
- logger.error(chalk.red(' E2E test failed:'), error.message);
184
+ logger.error(formatBlockingError('log-integration failed:'), error.message);
142
185
  process.exit(1);
143
186
  }
144
187
  });
@@ -149,13 +192,22 @@ function setupDatasourceTestE2ECommand(datasource) {
149
192
  * @param {Command} program - Commander program instance
150
193
  */
151
194
  function setupDatasourceCommands(program) {
152
- const datasource = program.command('datasource').description('Manage external data sources');
195
+ const datasource = program
196
+ .command('datasource')
197
+ .description('Datasource JSON: validate, list, deploy, test, logs')
198
+ .addHelpText('after', DATASOURCE_HELP_AFTER);
199
+ if (typeof datasource.alias === 'function') {
200
+ datasource.alias('ds');
201
+ }
153
202
  setupDatasourceValidateCommand(datasource);
154
203
  setupDatasourceListCommand(datasource);
155
204
  setupDatasourceDiffCommand(datasource);
156
205
  setupDatasourceUploadCommand(datasource);
206
+ setupDatasourceTestCommand(datasource);
157
207
  setupDatasourceTestIntegrationCommand(datasource);
158
208
  setupDatasourceTestE2ECommand(datasource);
209
+ setupDatasourceLogE2ECommand(datasource);
210
+ setupDatasourceLogIntegrationCommand(datasource);
159
211
  }
160
212
 
161
213
  module.exports = { setupDatasourceCommands };
@@ -1,3 +1,4 @@
1
+ const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * Deployment list commands – list deployments for environment or for an app
3
4
  * Uses GET .../deployments and GET .../applications/{appKey}/deployments.
@@ -97,7 +98,7 @@ async function runDeploymentList(options = {}) {
97
98
  );
98
99
  displayDeploymentList(extractDeployments(response), environment, authResult.controllerUrl);
99
100
  } catch (error) {
100
- logger.error(chalk.red(`❌ Failed to list deployments: ${error.message}`));
101
+ logger.error(formatBlockingError(`Failed to list deployments: ${error.message}`));
101
102
  process.exit(1);
102
103
  }
103
104
  }
@@ -133,13 +134,13 @@ function displayAppDeploymentList(deployments, appKey, environment, controllerUr
133
134
  async function resolveDeploymentListContext(options) {
134
135
  const controllerUrl = options.controller || (await resolveControllerUrl());
135
136
  if (!controllerUrl) {
136
- logger.error(chalk.red('Controller URL is required. Run "aifabrix login" first.'));
137
+ logger.error(formatBlockingError('Controller URL is required. Run "aifabrix login" first.'));
137
138
  process.exit(1);
138
139
  }
139
140
  const environment = options.environment || (await resolveEnvironment());
140
141
  const authResult = await getDeploymentListAuth(controllerUrl);
141
142
  if (!authResult || !authResult.token) {
142
- logger.error(chalk.red(`❌ No authentication token for controller: ${controllerUrl}`));
143
+ logger.error(formatBlockingError(`No authentication token for controller: ${controllerUrl}`));
143
144
  logger.error(chalk.gray('Run: aifabrix login'));
144
145
  process.exit(1);
145
146
  }
@@ -158,7 +159,7 @@ async function resolveDeploymentListContext(options) {
158
159
  */
159
160
  async function runAppDeploymentList(appKey, options = {}) {
160
161
  if (!appKey || typeof appKey !== 'string') {
161
- logger.error(chalk.red('Application key is required.'));
162
+ logger.error(formatBlockingError('Application key is required.'));
162
163
  process.exit(1);
163
164
  return;
164
165
  }
@@ -180,7 +181,7 @@ async function runAppDeploymentList(appKey, options = {}) {
180
181
  authResult.controllerUrl
181
182
  );
182
183
  } catch (error) {
183
- logger.error(chalk.red(`❌ Failed to list deployments for ${appKey}: ${error.message}`));
184
+ logger.error(formatBlockingError(`Failed to list deployments for ${appKey}: ${error.message}`));
184
185
  process.exit(1);
185
186
  }
186
187
  }
@@ -1,3 +1,4 @@
1
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * @fileoverview CLI action handlers for dev list/add/update/pin/delete (remote Builder Server)
3
4
  * @author AI Fabrix Team
@@ -9,6 +10,12 @@ const config = require('../core/config');
9
10
  const logger = require('../utils/logger');
10
11
  const devApi = require('../api/dev.api');
11
12
  const { getRemoteDevAuth } = require('../utils/remote-dev-auth');
13
+ const { isValidIpv4 } = require('../utils/dev-hosts-helper');
14
+ const {
15
+ parseDevGroupsOption,
16
+ validateDevGroups,
17
+ augmentDevUserGroupsServerError
18
+ } = require('../utils/dev-user-groups');
12
19
 
13
20
  const REMOTE_NOT_CONFIGURED_MSG = 'Remote server is not configured. Set remote-server and run "aifabrix dev init" first.';
14
21
 
@@ -39,7 +46,7 @@ async function handleDevList() {
39
46
  logger.log(chalk.yellow(REMOTE_NOT_CONFIGURED_MSG));
40
47
  return;
41
48
  }
42
- const users = await devApi.listUsers(auth.serverUrl, auth.clientCertPem);
49
+ const users = await devApi.listUsers(auth.serverUrl, auth.clientCertPem, auth.serverCaPem || undefined);
43
50
  if (users.length === 0) {
44
51
  logger.log(chalk.gray('No developers registered.'));
45
52
  return;
@@ -71,14 +78,23 @@ async function handleDevList() {
71
78
  async function handleDevAdd(options) {
72
79
  const auth = await getRemoteDevAuth();
73
80
  if (!auth) throw new Error(REMOTE_NOT_CONFIGURED_MSG);
74
- const groups = (options.groups || 'developer').split(',').map(s => s.trim()).filter(Boolean);
75
- const user = await devApi.createUser(auth.serverUrl, auth.clientCertPem, {
76
- developerId: options.developerId,
77
- name: options.name,
78
- email: options.email,
79
- groups: groups.length ? groups : ['developer']
80
- });
81
- logger.log(chalk.green(`✓ Developer ${user.id} created. Use "aifabrix dev pin ${user.id}" to create a PIN for onboarding.`));
81
+ let groups = parseDevGroupsOption(
82
+ options.groups !== undefined && options.groups !== null ? options.groups : 'developer'
83
+ );
84
+ if (groups.length === 0) groups = ['developer'];
85
+ validateDevGroups(groups);
86
+ try {
87
+ const user = await devApi.createUser(auth.serverUrl, auth.clientCertPem, {
88
+ developerId: options.developerId,
89
+ name: options.name,
90
+ email: options.email,
91
+ groups
92
+ }, auth.serverCaPem || undefined);
93
+ logger.log(formatSuccessLine(`Developer ${user.id} created. Use "aifabrix dev pin ${user.id}" to create a PIN for onboarding.`));
94
+ } catch (err) {
95
+ augmentDevUserGroupsServerError(err, groups);
96
+ throw err;
97
+ }
82
98
  }
83
99
 
84
100
  /**
@@ -95,27 +111,115 @@ async function handleDevUpdate(developerId, options) {
95
111
  const body = {};
96
112
  if (options.name) body.name = options.name;
97
113
  if (options.email) body.email = options.email;
98
- if (options.groups) body.groups = options.groups.split(',').map(s => s.trim()).filter(Boolean);
114
+ if (options.groups) {
115
+ const groups = parseDevGroupsOption(options.groups);
116
+ if (groups.length === 0) {
117
+ throw new Error('--groups must list at least one valid group');
118
+ }
119
+ validateDevGroups(groups);
120
+ body.groups = groups;
121
+ }
99
122
  if (Object.keys(body).length === 0) {
100
123
  throw new Error('Provide at least one of --name, --email, --groups');
101
124
  }
102
- await devApi.updateUser(auth.serverUrl, auth.clientCertPem, id, body);
103
- logger.log(chalk.green(`✓ Developer ${id} updated.`));
125
+ try {
126
+ await devApi.updateUser(auth.serverUrl, auth.clientCertPem, id, body, auth.serverCaPem || undefined);
127
+ logger.log(formatSuccessLine(`Developer ${id} updated.`));
128
+ } catch (err) {
129
+ augmentDevUserGroupsServerError(err, body.groups);
130
+ throw err;
131
+ }
132
+ }
133
+
134
+ function logPinHostsVariantLines(baseInit, id, hostsIp) {
135
+ logger.log(
136
+ chalk.cyan('2) Hosts file / administrator')
137
+ + chalk.gray(
138
+ ' — use when the hostname does not resolve and your organisation does not publish DNS for it. '
139
+ + 'Run in an elevated terminal so the hosts file can be updated, or add the entry manually:'
140
+ )
141
+ );
142
+ if (hostsIp) {
143
+ logger.log(` ${baseInit} --add-hosts --hosts-ip ${hostsIp}`);
144
+ } else {
145
+ logger.log(` ${baseInit} --add-hosts --hosts-ip <server-LAN-IPv4>`);
146
+ logger.log(
147
+ chalk.gray(
148
+ ` Replace <server-LAN-IPv4> with the Builder Server IPv4, or re-run: aifabrix dev pin ${id} --hosts-ip <ip>`
149
+ )
150
+ );
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Log copy-paste dev init commands for the developer (standard vs hosts-file).
156
+ * @param {Object} p
157
+ * @param {string} p.id - Developer ID
158
+ * @param {string} p.serverUrl - Builder Server base URL
159
+ * @param {string} p.pin - One-time PIN
160
+ * @param {string} p.expiresAt - PIN expiry (API string)
161
+ * @param {string} [p.hostsIp] - Optional IPv4 for the hosts variant
162
+ * @returns {void}
163
+ */
164
+ function logDevPinShareInstructions(p) {
165
+ const { id, serverUrl, pin, expiresAt, hostsIp } = p;
166
+ const baseInit = `aifabrix dev init --developer-id ${id} --server ${serverUrl} --pin ${pin}`;
167
+ const pinOnly = `aifabrix dev init --pin ${pin}`;
168
+ logger.log(formatSuccessLine(`PIN created for ${id}, expires ${expiresAt}.`));
169
+ logger.log('');
170
+ logger.log(
171
+ chalk.bold('Share with the developer — they run one command (the PIN is one-time):')
172
+ );
173
+ logger.log('');
174
+ logger.log(
175
+ chalk.cyan('1) Standard')
176
+ + chalk.gray(' — use when this machine can resolve the server hostname (DNS or hosts already set):')
177
+ );
178
+ logger.log(` ${baseInit}`);
179
+ logger.log('');
180
+ logPinHostsVariantLines(baseInit, id, hostsIp);
181
+ logger.log('');
182
+ logger.log(
183
+ chalk.cyan('3) Config already has remote-server + developer-id')
184
+ + chalk.gray(
185
+ ' — e.g. second machine / SSH session after settings were merged; same onboarding, PIN only:'
186
+ )
187
+ );
188
+ logger.log(` ${pinOnly}`);
189
+ logger.log('');
104
190
  }
105
191
 
106
192
  /**
107
193
  * Handle dev pin – create/regenerate PIN (remote only).
108
194
  * @param {string} [developerId] - Developer ID (optional; uses config if omitted)
195
+ * @param {Object} [options] - Commander options
196
+ * @param {string} [options.hostsIp] - From --hosts-ip (embeds IPv4 in the hosts variant)
109
197
  * @returns {Promise<void>}
110
198
  */
111
- async function handleDevPin(developerId) {
199
+ async function handleDevPin(developerId, options = {}) {
112
200
  const auth = await getRemoteDevAuth();
113
201
  if (!auth) throw new Error(REMOTE_NOT_CONFIGURED_MSG);
114
202
  const id = developerId || await config.getDeveloperId();
115
203
  if (!id) throw new Error('developerId is required (argument or set developer-id in config)');
116
- const res = await devApi.createPin(auth.serverUrl, auth.clientCertPem, id);
117
- logger.log(chalk.green(`✓ PIN created for ${id}, expires ${res.expiresAt}.`));
118
- logger.log(chalk.yellow(` Give this PIN once to the developer for: aifabrix dev init --developer-id ${id} --server ${auth.serverUrl} --pin ${res.pin}`));
204
+ const rawHostsIp = options.hostsIp ?? options['hosts-ip'];
205
+ let hostsIp = '';
206
+ if (rawHostsIp !== undefined && rawHostsIp !== null) {
207
+ const trimmed = String(rawHostsIp).trim();
208
+ if (trimmed !== '') {
209
+ if (!isValidIpv4(trimmed)) {
210
+ throw new Error('Invalid --hosts-ip (use an IPv4 address, e.g. 192.168.1.25)');
211
+ }
212
+ hostsIp = trimmed;
213
+ }
214
+ }
215
+ const res = await devApi.createPin(auth.serverUrl, auth.clientCertPem, id, auth.serverCaPem || undefined);
216
+ logDevPinShareInstructions({
217
+ id,
218
+ serverUrl: auth.serverUrl,
219
+ pin: res.pin,
220
+ expiresAt: res.expiresAt,
221
+ hostsIp: hostsIp || undefined
222
+ });
119
223
  }
120
224
 
121
225
  /**
@@ -128,8 +232,8 @@ async function handleDevDelete(developerId) {
128
232
  if (!auth) throw new Error(REMOTE_NOT_CONFIGURED_MSG);
129
233
  const id = developerId;
130
234
  if (!id) throw new Error('Developer ID is required (positional argument or --developer-id).');
131
- await devApi.deleteUser(auth.serverUrl, auth.clientCertPem, id);
132
- logger.log(chalk.green(`✓ Developer ${id} removed.`));
235
+ await devApi.deleteUser(auth.serverUrl, auth.clientCertPem, id, auth.serverCaPem || undefined);
236
+ logger.log(formatSuccessLine(`Developer ${id} removed.`));
133
237
  }
134
238
 
135
239
  module.exports = {