@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
@@ -1,3 +1,4 @@
1
+ const { formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * dev down – stop Mutagen sync sessions and optionally app containers for this developer.
3
4
  *
@@ -38,7 +39,7 @@ async function stopMutagenSessions(developerId) {
38
39
  const toTerminate = sessions.filter(name => name.startsWith(prefix));
39
40
  for (const name of toTerminate) {
40
41
  await execAsync(`"${mutagenPath}" sync terminate "${name}"`, { timeout: 5000 });
41
- logger.log(chalk.green(` Stopped sync session: ${name}`));
42
+ logger.log(chalk.green(` Stopped sync session: ${name}`));
42
43
  }
43
44
  if (toTerminate.length === 0 && sessions.length > 0) {
44
45
  logger.log(chalk.gray('No sync sessions for this developer.'));
@@ -98,7 +99,7 @@ async function handleDevDown(options = {}) {
98
99
  if (!appName) continue;
99
100
  try {
100
101
  await appLib.downApp(appName, {});
101
- logger.log(chalk.green(` Stopped app: ${appName}`));
102
+ logger.log(chalk.green(` Stopped app: ${appName}`));
102
103
  } catch (err) {
103
104
  logger.log(chalk.yellow(` ⚠ Could not stop ${appName}: ${err.message}`));
104
105
  }
@@ -108,7 +109,7 @@ async function handleDevDown(options = {}) {
108
109
  }
109
110
  }
110
111
 
111
- logger.log(chalk.green('\n✓ dev down complete.\n'));
112
+ logger.log(formatSuccessParagraph('dev down complete.\n'));
112
113
  }
113
114
 
114
115
  module.exports = { handleDevDown };
@@ -1,6 +1,7 @@
1
+ const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
- * @fileoverview aifabrix dev init – onboard with Builder Server (issue-cert, save cert, get settings, add SSH key).
3
- * Auth: first call (issue-cert) uses no client cert; all other calls (getSettings, addSshKey, and every other dev API) send the client certificate.
3
+ * @fileoverview aifabrix dev init – onboard with Builder Server (issue-cert, save cert, get settings, add SSH key, SSH config alias).
4
+ * Auth: first call (issue-cert) uses no client cert; other calls send the client cert (mTLS on https, X-Client-Cert header on http for getSettings/addSshKey).
4
5
  * @author AI Fabrix Team
5
6
  * @version 2.0.0
6
7
  */
@@ -10,69 +11,138 @@ const path = require('path');
10
11
  const chalk = require('chalk');
11
12
  const config = require('../core/config');
12
13
  const { getConfigDirForPaths } = require('../utils/paths');
13
- const { generateCSR, getCertDir, readClientCertPem, readClientKeyPem, getCertValidNotAfter } = require('../utils/dev-cert-helper');
14
+ const {
15
+ generateCSR,
16
+ getCertDir,
17
+ readClientCertPem,
18
+ readClientKeyPem,
19
+ getCertValidNotAfter,
20
+ normalizePemNewlines,
21
+ mergeCaPemBlocks
22
+ } = require('../utils/dev-cert-helper');
14
23
  const { getOrCreatePublicKeyContent } = require('../utils/ssh-key-helper');
15
24
  const devApi = require('../api/dev.api');
16
25
  const logger = require('../utils/logger');
17
26
  const {
18
27
  isSslUntrustedError,
28
+ isSslHostnameMismatchError,
19
29
  fetchInstallCa,
20
30
  installCaPlatform,
21
- promptInstallCa
31
+ promptInstallCa,
32
+ isLinuxCaSudoRequiredError
22
33
  } = require('../utils/dev-ca-install');
34
+ const { runOptionalHostsSetup } = require('../utils/dev-hosts-helper');
35
+ const { mergeDevSshConfigAfterInit } = require('../utils/dev-init-ssh-merge');
36
+ const { resolveInitOptions } = require('../utils/dev-init-resolve');
37
+ const { displayDevConfig } = require('./dev-show-display');
38
+ const {
39
+ isHealthHttpServerError,
40
+ formatEnsureServerTrustedFailure
41
+ } = require('../utils/dev-init-health-messages');
42
+ const { getBadRequestHint, logCertTroubleshootingHint } = require('../utils/dev-init-cert-hints');
23
43
 
24
44
  /**
25
- * Ensure the Builder Server is trusted: run health check; on SSL untrusted error,
26
- * optionally fetch and install CA, then retry.
45
+ * Install dev CA into the OS trust store. On Linux without sudo, log and return false so the caller can use in-process PEM.
46
+ * @param {Buffer} caBuf - CA PEM buffer
47
+ * @param {string} baseUrlForInstall - Builder Server base URL (for install-ca-help paths)
48
+ * @returns {Promise<boolean>} true if the OS store was updated
49
+ */
50
+ async function tryInstallDevCaToOsStoreOrWarnLinuxSudo(caBuf, baseUrlForInstall) {
51
+ try {
52
+ await installCaPlatform(caBuf, baseUrlForInstall);
53
+ return true;
54
+ } catch (installErr) {
55
+ if (isLinuxCaSudoRequiredError(installErr)) {
56
+ logger.log(
57
+ chalk.yellow(
58
+ ' ⚠ Could not install the development CA into the system trust store (sudo/root required). ' +
59
+ 'Continuing with the downloaded CA for this CLI session; browsers and curl may still warn until you install it manually.\n' +
60
+ ' ' +
61
+ (installErr.message || String(installErr))
62
+ )
63
+ );
64
+ return false;
65
+ }
66
+ throw installErr;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Prompt, fetch dev CA, install into OS store, verify health with PEM for Node TLS.
27
72
  * @param {string} baseUrl - Builder Server base URL
28
- * @param {Object} options - Commander options (yes, y, no-install-ca)
29
- * @returns {Promise<void>}
73
+ * @param {Object} options - Commander options
74
+ * @returns {Promise<string>} Dev root CA PEM
30
75
  */
31
- async function ensureServerTrusted(baseUrl, options) {
76
+ async function installDevCaAndRetryHealth(baseUrl, options) {
32
77
  const skipInstall = options['no-install-ca'];
33
78
  const autoInstall = options.yes || options.y;
34
- try {
35
- await devApi.getHealth(baseUrl);
36
- } catch (err) {
37
- if (!isSslUntrustedError(err)) throw err;
38
- const manualUrl = `${baseUrl.replace(/\/+$/, '')}/install-ca`;
39
- if (skipInstall) {
79
+ const manualUrl = `${baseUrl.replace(/\/+$/, '')}/install-ca`;
80
+ if (skipInstall) {
81
+ throw new Error(`Server certificate not trusted. Install CA manually: ${manualUrl}`);
82
+ }
83
+ if (!autoInstall) {
84
+ const install = await promptInstallCa();
85
+ if (!install) {
40
86
  throw new Error(`Server certificate not trusted. Install CA manually: ${manualUrl}`);
41
87
  }
42
- if (!autoInstall) {
43
- const install = await promptInstallCa();
44
- if (!install) {
45
- throw new Error(`Server certificate not trusted. Install CA manually: ${manualUrl}`);
46
- }
88
+ }
89
+ logger.log(chalk.gray(' Downloading and installing CA...'));
90
+ const caBuf = await fetchInstallCa(baseUrl);
91
+ const caPemStr = caBuf.toString('utf8').trim();
92
+ const installedToOsStore = await tryInstallDevCaToOsStoreOrWarnLinuxSudo(caBuf, baseUrl);
93
+ logger.log(
94
+ chalk.gray(
95
+ installedToOsStore ? ' CA installed. Retrying...' : ' Retrying with downloaded CA (in-process trust)...'
96
+ )
97
+ );
98
+ try {
99
+ await devApi.getHealth(baseUrl, caPemStr);
100
+ } catch (healthErr) {
101
+ if (isHealthHttpServerError(healthErr)) {
102
+ logger.log(
103
+ chalk.yellow(
104
+ ` ⚠ GET /health returned HTTP ${healthErr.status} (${healthErr.message || 'Server Error'}). ` +
105
+ 'TLS is verified; continuing with certificate onboarding.'
106
+ )
107
+ );
108
+ } else {
109
+ throw healthErr;
47
110
  }
48
- logger.log(chalk.gray(' Downloading and installing CA...'));
49
- const caPem = await fetchInstallCa(baseUrl);
50
- await installCaPlatform(caPem, baseUrl);
51
- logger.log(chalk.gray(' CA installed. Retrying...'));
52
- await devApi.getHealth(baseUrl);
53
111
  }
112
+ return caPemStr;
54
113
  }
55
114
 
56
115
  /**
57
- * Validate init options and return normalized baseUrl and devId.
58
- * @param {Object} options - Commander options
59
- * @returns {{ baseUrl: string, devId: string }}
116
+ * Ensure the Builder Server is trusted: run health check; on SSL untrusted error,
117
+ * optionally fetch and install CA, then retry using the PEM in-process (Node does not use Windows user ROOT for fetch).
118
+ * @param {string} baseUrl - Builder Server base URL
119
+ * @param {Object} options - Commander options (yes, y, no-install-ca)
120
+ * @returns {Promise<string|null>} Dev root CA PEM to pass to dev API for TLS, or null if no install-ca path was used
60
121
  */
61
- function validateInitOptions(options) {
62
- const devId = options.developerId || options['developer-id'];
63
- const server = options.server;
64
- const pin = options.pin;
65
-
66
- if (!devId || typeof devId !== 'string' || !/^[0-9]+$/.test(devId)) {
67
- throw new Error('--developer-id is required and must be a non-empty digit string (e.g. 01)');
68
- }
69
- if (!server || typeof server !== 'string' || !server.trim()) {
70
- throw new Error('--server is required and must be the Builder Server base URL (e.g. https://dev.aifabrix.dev)');
71
- }
72
- if (!pin || typeof pin !== 'string' || !pin.trim()) {
73
- throw new Error('--pin is required (one-time PIN from your admin)');
122
+ async function ensureServerTrusted(baseUrl, options) {
123
+ try {
124
+ await devApi.getHealth(baseUrl);
125
+ return null;
126
+ } catch (err) {
127
+ if (isSslHostnameMismatchError(err)) {
128
+ throw new Error(
129
+ `TLS hostname does not match the server certificate for ${baseUrl}. Open the site in a browser only after accepting a warning, or the certificate may list a different DNS name. Use a URL whose hostname is in the certificate (SAN), or reissue the server certificate for this host.`
130
+ );
131
+ }
132
+ if (!isSslUntrustedError(err)) {
133
+ if (isHealthHttpServerError(err)) {
134
+ logger.log(
135
+ chalk.yellow(
136
+ ` ⚠ GET /health returned HTTP ${err.status} (${err.message || 'Server Error'}). ` +
137
+ 'Continuing certificate onboarding; fix server health if later steps fail.'
138
+ )
139
+ );
140
+ return null;
141
+ }
142
+ throw err;
143
+ }
144
+ return installDevCaAndRetryHealth(baseUrl, options);
74
145
  }
75
- return { baseUrl: server.trim().replace(/\/+$/, ''), devId };
76
146
  }
77
147
 
78
148
  /**
@@ -81,15 +151,16 @@ function validateInitOptions(options) {
81
151
  * @param {string} devId - Developer ID
82
152
  * @param {string} pin - One-time PIN
83
153
  * @param {string} csrPem - PEM CSR
154
+ * @param {string} [serverCaPem] - Dev root CA for Node TLS (after install-ca)
84
155
  * @returns {Promise<Object>} IssueCertResponseDto
85
156
  */
86
- async function requestCertificate(baseUrl, devId, pin, csrPem) {
157
+ async function requestCertificate(baseUrl, devId, pin, csrPem, serverCaPem) {
87
158
  try {
88
159
  return await devApi.issueCert(baseUrl, {
89
160
  developerId: devId,
90
161
  pin: pin.trim(),
91
162
  csr: csrPem
92
- });
163
+ }, serverCaPem);
93
164
  } catch (err) {
94
165
  if (err.status === 401) {
95
166
  throw new Error('Invalid or expired PIN. Ask your admin for a new PIN (aifabrix dev pin <developerId>).');
@@ -104,17 +175,6 @@ async function requestCertificate(baseUrl, devId, pin, csrPem) {
104
175
  }
105
176
  }
106
177
 
107
- /**
108
- * Normalize PEM string: turn literal \n (backslash-n) into real newlines so Docker/OpenSSL accept it.
109
- * Some servers return JSON with escaped newlines in the PEM string.
110
- * @param {string} pem - PEM string (certificate or CA)
111
- * @returns {string} PEM with real newlines
112
- */
113
- function normalizePemNewlines(pem) {
114
- if (typeof pem !== 'string') return pem;
115
- return pem.replace(/\\n/g, '\n');
116
- }
117
-
118
178
  /**
119
179
  * Save certificate, key, and optional CA to cert dir; set developer-id in config.
120
180
  * Remote Docker requires ca.pem in the cert dir; if the server provides it (e.g. issue-cert
@@ -135,34 +195,12 @@ async function saveCertAndConfig(configDir, devId, certificatePem, keyPem, caPem
135
195
  if (caPem && typeof caPem === 'string' && caPem.trim()) {
136
196
  const caNormalized = normalizePemNewlines(caPem.trim());
137
197
  await fs.writeFile(path.join(certDir, 'ca.pem'), caNormalized, { mode: 0o600 });
138
- logger.log(chalk.green(' Certificate and CA saved to ') + chalk.cyan(path.join(certDir, 'cert.pem')));
198
+ logger.log(chalk.green(' Certificate and CA saved to ') + chalk.cyan(path.join(certDir, 'cert.pem')));
139
199
  } else {
140
- logger.log(chalk.green(' Certificate saved to ') + chalk.cyan(path.join(certDir, 'cert.pem')));
200
+ logger.log(chalk.green(' Certificate saved to ') + chalk.cyan(path.join(certDir, 'cert.pem')));
141
201
  }
142
202
  await config.setDeveloperId(devId);
143
- logger.log(chalk.green(' Developer ID set to ') + chalk.cyan(devId));
144
- }
145
-
146
- /**
147
- * Message for 400 Bad Request: nginx often forwards X-Client-Cert with literal newlines.
148
- * @returns {string} Hint for server-side nginx fix
149
- */
150
- function getBadRequestHint() {
151
- return 'Bad Request (400) often means the server\'s nginx is forwarding the client certificate with literal newlines in X-Client-Cert. On the server, use nginx njs to escape newlines (see .cursor/plans/builder-cli.md §5).';
152
- }
153
-
154
- /**
155
- * Log a one-line hint for cert troubleshooting (curl test and docs).
156
- * @param {string} configDir - Config directory
157
- * @param {string} devId - Developer ID
158
- * @param {string} baseUrl - Builder Server base URL
159
- */
160
- function logCertTroubleshootingHint(configDir, devId, baseUrl) {
161
- const certDir = getCertDir(configDir, devId);
162
- const certPath = path.join(certDir, 'cert.pem');
163
- const keyPath = path.join(certDir, 'key.pem');
164
- logger.log(chalk.gray(` Test with: curl -v --cert ${certPath} --key ${keyPath} ${baseUrl}/api/dev/settings`));
165
- logger.log(chalk.gray(' See .cursor/plans/builder-cli.md §5 for 200 vs 401 vs 400 and nginx/server fix.'));
203
+ logger.log(chalk.green(' Developer ID set to ') + chalk.cyan(devId));
166
204
  }
167
205
 
168
206
  /**
@@ -172,14 +210,14 @@ function logCertTroubleshootingHint(configDir, devId, baseUrl) {
172
210
  * @param {string} clientKeyPem - Client private key PEM (for mTLS)
173
211
  * @param {string} devId - Developer ID
174
212
  */
175
- async function registerSshKey(baseUrl, clientCertPem, clientKeyPem, devId) {
213
+ async function registerSshKey(baseUrl, clientCertPem, clientKeyPem, devId, serverCaPem) {
176
214
  const publicKey = getOrCreatePublicKeyContent();
177
215
  try {
178
216
  await devApi.addSshKey(baseUrl, clientCertPem, devId, {
179
217
  publicKey,
180
218
  label: 'aifabrix-init'
181
- }, clientKeyPem);
182
- logger.log(chalk.green(' SSH key registered'));
219
+ }, clientKeyPem, serverCaPem);
220
+ logger.log(chalk.green(' SSH key registered'));
183
221
  } catch (err) {
184
222
  if (err.status === 409) {
185
223
  logger.log(chalk.yellow(' ⚠ SSH key already registered'));
@@ -198,13 +236,13 @@ async function registerSshKey(baseUrl, clientCertPem, clientKeyPem, devId) {
198
236
  * @param {string} devId - Developer ID
199
237
  * @private
200
238
  */
201
- async function _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, configDir, devId) {
239
+ async function _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, configDir, devId, serverCaPem) {
202
240
  logger.log(chalk.gray(' Registering SSH key for Mutagen sync...'));
203
241
  try {
204
242
  if (keyPem && typeof keyPem === 'string') {
205
243
  logger.log(chalk.gray(' Using client certificate for TLS'));
206
244
  }
207
- await registerSshKey(baseUrl, issueResponse.certificate, keyPem, devId);
245
+ await registerSshKey(baseUrl, issueResponse.certificate, keyPem, devId, serverCaPem);
208
246
  } catch (err) {
209
247
  const msg = err.status === 400 ? getBadRequestHint() : (err.message || String(err));
210
248
  logger.log(chalk.yellow(' ⚠ Could not register SSH key: ' + msg));
@@ -218,12 +256,13 @@ async function _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, config
218
256
  * @param {string} devId - Developer ID
219
257
  * @param {Object} issueResponse - IssueCert response (certificate, settings)
220
258
  * @param {string} keyPem - Client key PEM
259
+ * @param {string} [serverCaPem] - Dev root CA for Node TLS
221
260
  */
222
- async function applySettingsFromServer(baseUrl, devId, issueResponse, keyPem) {
261
+ async function applySettingsFromServer(baseUrl, devId, issueResponse, keyPem, serverCaPem) {
223
262
  const configDir = getConfigDirForPaths();
224
263
  if (issueResponse.settings && typeof issueResponse.settings === 'object') {
225
264
  await config.mergeRemoteSettings(issueResponse.settings);
226
- logger.log(chalk.green(' Config updated from server (issue-cert response)'));
265
+ logger.log(chalk.green(' Config updated from server (issue-cert response)'));
227
266
  return;
228
267
  }
229
268
  logger.log(chalk.gray(' Fetching settings...'));
@@ -231,9 +270,9 @@ async function applySettingsFromServer(baseUrl, devId, issueResponse, keyPem) {
231
270
  if (keyPem && typeof keyPem === 'string') {
232
271
  logger.log(chalk.gray(' Using client certificate for TLS'));
233
272
  }
234
- const settings = await devApi.getSettings(baseUrl, issueResponse.certificate, keyPem);
273
+ const settings = await devApi.getSettings(baseUrl, issueResponse.certificate, keyPem, serverCaPem);
235
274
  await config.mergeRemoteSettings(settings);
236
- logger.log(chalk.green(' Config updated from server'));
275
+ logger.log(chalk.green(' Config updated from server'));
237
276
  } catch (err) {
238
277
  const msg = err.status === 400 ? getBadRequestHint() : (err.message || String(err));
239
278
  logger.log(chalk.yellow(' ⚠ Could not fetch settings (server may not support cert yet): ' + msg));
@@ -241,36 +280,94 @@ async function applySettingsFromServer(baseUrl, devId, issueResponse, keyPem) {
241
280
  }
242
281
  }
243
282
 
283
+ /**
284
+ * @param {Object} options - Commander options
285
+ * @param {string} baseUrl
286
+ * @param {string} devId - Developer ID (--developer-id) for devNN.hosts line
287
+ */
288
+ async function maybeAddHostsDuringInit(options, baseUrl, devId) {
289
+ if (!options.addHosts && !options['add-hosts']) return;
290
+ const hostsIp = options.hostsIp || options['hosts-ip'];
291
+ await runOptionalHostsSetup({
292
+ baseUrl,
293
+ developerId: devId,
294
+ hostsIp: typeof hostsIp === 'string' ? hostsIp : undefined,
295
+ skipConfirm: Boolean(options.yes || options.y),
296
+ logger
297
+ });
298
+ }
299
+
300
+ /**
301
+ * @param {string} baseUrl
302
+ * @param {Object} options
303
+ * @returns {Promise<string|null>}
304
+ */
305
+ async function resolveServerCaPemForTls(baseUrl, options) {
306
+ try {
307
+ return await ensureServerTrusted(baseUrl, options);
308
+ } catch (err) {
309
+ throw new Error(formatEnsureServerTrustedFailure(baseUrl, err));
310
+ }
311
+ }
312
+
313
+ /**
314
+ * @param {{ hostAlias: string|null, syncUser: string, syncHost: string|null }} p
315
+ */
316
+ function logOnboardingFinished({ hostAlias, syncUser, syncHost }) {
317
+ logger.log(formatSuccessParagraph('Onboarding complete. You can use remote Docker and Mutagen sync.'));
318
+ if (hostAlias) {
319
+ logger.log(
320
+ chalk.gray(' You can also open an SSH session on the builder with ') +
321
+ chalk.cyan(`ssh ${hostAlias}`) +
322
+ chalk.gray(' (uses your registered SSH key; see ~/.ssh/config).')
323
+ );
324
+ } else if (syncHost && syncUser) {
325
+ logger.log(
326
+ chalk.gray(' You can SSH to the builder with ') +
327
+ chalk.cyan(`ssh ${syncUser}@${syncHost}`) +
328
+ chalk.gray(' when your key is authorized there.')
329
+ );
330
+ }
331
+ logger.log('');
332
+ }
333
+
244
334
  /**
245
335
  * Run dev init: validate PIN via issue-cert, save certificate, fetch settings, add SSH key.
246
336
  * @param {Object} options - Commander options (devId, server, pin)
247
337
  * @returns {Promise<void>}
248
338
  */
249
339
  async function runDevInit(options) {
250
- const { baseUrl, devId } = validateInitOptions(options);
340
+ const { baseUrl, devId } = await resolveInitOptions(options);
341
+
342
+ // Save developer-id and remote-server before TLS / issue-cert so ~/.aifabrix/config.yaml
343
+ // matches the CLI even if onboarding fails later (PIN, network, etc.). User can retry init.
344
+ await config.setDeveloperId(devId);
345
+ await config.setRemoteServer(baseUrl);
346
+ process.env.AIFABRIX_DEVELOPERID = devId;
347
+
251
348
  logger.log(chalk.blue('\n🔐 Onboarding with Builder Server...\n'));
252
349
 
253
- try {
254
- await ensureServerTrusted(baseUrl, options);
255
- } catch (err) {
256
- throw new Error(`Cannot reach Builder Server at ${baseUrl}. Check URL and network. ${err.message}`);
257
- }
350
+ await maybeAddHostsDuringInit(options, baseUrl, devId);
351
+ const serverCaPemForTls = await resolveServerCaPemForTls(baseUrl, options);
258
352
 
259
353
  logger.log(chalk.gray(' Generating certificate request...'));
260
354
  const { csrPem, keyPem } = generateCSR(devId);
261
355
 
262
356
  logger.log(chalk.gray(' Requesting certificate (issue-cert)...'));
263
- const issueResponse = await requestCertificate(baseUrl, devId, options.pin, csrPem);
357
+ const issueResponse = await requestCertificate(baseUrl, devId, options.pin, csrPem, serverCaPemForTls || undefined);
264
358
 
265
359
  const configDir = getConfigDirForPaths();
266
- const caPem = issueResponse.caCertificate || issueResponse.ca;
360
+ const caPem = mergeCaPemBlocks(
361
+ serverCaPemForTls,
362
+ issueResponse.caCertificate,
363
+ issueResponse.ca
364
+ );
267
365
  await saveCertAndConfig(configDir, devId, issueResponse.certificate, keyPem, caPem);
268
366
 
269
- await config.setRemoteServer(baseUrl);
270
-
271
- await applySettingsFromServer(baseUrl, devId, issueResponse, keyPem);
272
- await _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, configDir, devId);
273
- logger.log(chalk.green('\n✓ Onboarding complete. You can use remote Docker and Mutagen sync.\n'));
367
+ await applySettingsFromServer(baseUrl, devId, issueResponse, keyPem, serverCaPemForTls || undefined);
368
+ await _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, configDir, devId, serverCaPemForTls || undefined);
369
+ const sshInfo = await mergeDevSshConfigAfterInit(baseUrl, devId);
370
+ logOnboardingFinished(sshInfo);
274
371
  }
275
372
 
276
373
  /** Days before cert expiry at which we auto-refresh on dev refresh. */
@@ -291,25 +388,47 @@ function shouldRefreshDevCert(certDir) {
291
388
 
292
389
  /**
293
390
  * Refresh developer certificate: create PIN (with current cert), issue new cert, save and apply settings.
294
- * @param {{ serverUrl: string, clientCertPem: string }} auth - Current auth from getRemoteDevAuth
391
+ * @param {{ serverUrl: string, clientCertPem: string, serverCaPem?: string|null }} auth - Current auth from getRemoteDevAuth
295
392
  * @returns {Promise<void>}
296
393
  */
297
394
  async function runCertificateRefresh(auth) {
298
395
  const devId = await config.getDeveloperId();
299
396
  if (!devId) throw new Error('developer-id not set in config.');
300
397
  const configDir = getConfigDirForPaths();
398
+ const serverCaPem = auth.serverCaPem || undefined;
301
399
  logger.log(chalk.blue('\n🔄 Refreshing certificate (create PIN + issue-cert)...\n'));
302
- const pinRes = await devApi.createPin(auth.serverUrl, auth.clientCertPem, devId);
400
+ const pinRes = await devApi.createPin(auth.serverUrl, auth.clientCertPem, devId, serverCaPem);
303
401
  const pin = pinRes.pin;
304
402
  if (!pin || typeof pin !== 'string') throw new Error('Server did not return a PIN.');
305
403
  logger.log(chalk.gray(' Generating new certificate request...'));
306
404
  const { csrPem, keyPem } = generateCSR(devId);
307
405
  logger.log(chalk.gray(' Requesting new certificate (issue-cert)...'));
308
- const issueResponse = await requestCertificate(auth.serverUrl, devId, pin, csrPem);
309
- const caPem = issueResponse.caCertificate || issueResponse.ca;
406
+ const issueResponse = await requestCertificate(auth.serverUrl, devId, pin, csrPem, serverCaPem);
407
+ const caPem = mergeCaPemBlocks(serverCaPem, issueResponse.caCertificate, issueResponse.ca);
310
408
  await saveCertAndConfig(configDir, devId, issueResponse.certificate, keyPem, caPem);
311
- await applySettingsFromServer(auth.serverUrl, devId, issueResponse, keyPem);
312
- logger.log(chalk.green('Certificate refreshed and config updated from server.\n'));
409
+ await applySettingsFromServer(auth.serverUrl, devId, issueResponse, keyPem, serverCaPem);
410
+ logger.log(formatSuccessLine('Certificate refreshed and config updated from server.\n'));
411
+ }
412
+
413
+ /**
414
+ * GET /api/dev/settings and merge into local config (used by dev refresh when cert is still valid).
415
+ * @param {{ serverUrl: string, serverCaPem?: string|null }} auth
416
+ * @param {string} clientCertPem
417
+ * @param {string|null|undefined} clientKeyPem
418
+ * @param {string} devId
419
+ * @returns {Promise<void>}
420
+ */
421
+ async function fetchAndMergeRemoteSettings(auth, clientCertPem, clientKeyPem, devId) {
422
+ logger.log(chalk.blue('\n🔄 Fetching settings from Builder Server...\n'));
423
+ const settings = await devApi.getSettings(
424
+ auth.serverUrl,
425
+ clientCertPem,
426
+ clientKeyPem || undefined,
427
+ auth.serverCaPem || undefined
428
+ );
429
+ await config.mergeRemoteSettings(settings);
430
+ logger.log(formatSuccessLine('Config updated from server.\n'));
431
+ await displayDevConfig(devId);
313
432
  }
314
433
 
315
434
  /**
@@ -326,22 +445,18 @@ async function runDevRefresh(options = {}) {
326
445
  throw new Error('Remote server is not configured. Set remote-server and run "aifabrix dev init" first.');
327
446
  }
328
447
  const devId = await config.getDeveloperId();
329
- const configDir = getConfigDirForPaths();
330
- const certDir = getCertDir(configDir, devId);
448
+ const certDir = getCertDir(getConfigDirForPaths(), devId);
331
449
  const clientCertPem = readClientCertPem(certDir);
332
450
  const clientKeyPem = readClientKeyPem(certDir);
333
451
  if (!clientCertPem) {
334
452
  throw new Error('Client certificate not found. Run "aifabrix dev init" first.');
335
453
  }
336
- const forceCertRefresh = Boolean(options.cert);
337
- if (forceCertRefresh || shouldRefreshDevCert(certDir)) {
454
+ if (Boolean(options.cert) || shouldRefreshDevCert(certDir)) {
338
455
  await runCertificateRefresh(auth);
456
+ await displayDevConfig(devId);
339
457
  return;
340
458
  }
341
- logger.log(chalk.blue('\n🔄 Fetching settings from Builder Server...\n'));
342
- const settings = await devApi.getSettings(auth.serverUrl, clientCertPem, clientKeyPem || undefined);
343
- await config.mergeRemoteSettings(settings);
344
- logger.log(chalk.green('✓ Config updated from server. Run "aifabrix dev config" to verify.\n'));
459
+ await fetchAndMergeRemoteSettings(auth, clientCertPem, clientKeyPem, devId);
345
460
  }
346
461
 
347
462
  module.exports = { runDevInit, runDevRefresh };