@aifabrix/builder 2.43.0 → 2.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/README.md +1 -1
  3. package/anchor-docs/README.md +10 -0
  4. package/anchor-docs/_TEMPLATE +24 -0
  5. package/bin/aifabrix.js +13 -4
  6. package/integration/hubspot-test/README.md +31 -0
  7. package/integration/hubspot-test/create-hubspot.js +5 -5
  8. package/integration/hubspot-test/hubspot-test-datasource-company.json +58 -462
  9. package/integration/hubspot-test/hubspot-test-datasource-contact.json +61 -555
  10. package/integration/hubspot-test/hubspot-test-datasource-deal.json +63 -506
  11. package/integration/hubspot-test/hubspot-test-datasource-users.json +42 -83
  12. package/integration/hubspot-test/hubspot-test-deploy.json +3 -3
  13. package/integration/hubspot-test/test-dataplane-down-tests.js +1 -7
  14. package/integration/hubspot-test/test-dataplane-down.js +3 -3
  15. package/integration/hubspot-test/test.js +35 -43
  16. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  17. package/integration/roundtrip-test-local/README.md +144 -0
  18. package/integration/roundtrip-test-local/application.yaml +13 -0
  19. package/integration/roundtrip-test-local/env.template +15 -0
  20. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  21. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  22. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  23. package/integration/roundtrip-test-local2/README.md +144 -0
  24. package/integration/roundtrip-test-local2/application.yaml +13 -0
  25. package/integration/roundtrip-test-local2/env.template +15 -0
  26. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  27. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  28. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  29. package/integration/test/wizard.yaml +8 -0
  30. package/jest.config.default.js +10 -0
  31. package/jest.config.integration.fixtures.js +22 -0
  32. package/jest.config.integration.js +21 -18
  33. package/jest.config.isolated.js +10 -0
  34. package/jest.projects.js +288 -0
  35. package/lib/api/datasources-core.api.js +3 -3
  36. package/lib/api/dev-mtls-request.js +110 -0
  37. package/lib/api/dev-server-https.js +145 -0
  38. package/lib/api/dev.api.js +133 -144
  39. package/lib/api/index.js +0 -1
  40. package/lib/api/pipeline.api.js +67 -20
  41. package/lib/api/types/dev.types.js +4 -3
  42. package/lib/api/types/pipeline.types.js +8 -5
  43. package/lib/api/types/validation-run.types.js +56 -0
  44. package/lib/api/validation-run.api.js +99 -0
  45. package/lib/api/validation-runner.js +99 -0
  46. package/lib/app/config.js +1 -1
  47. package/lib/app/deploy-status-display.js +2 -2
  48. package/lib/app/deploy.js +7 -6
  49. package/lib/app/display.js +2 -1
  50. package/lib/app/dockerfile.js +3 -2
  51. package/lib/app/down.js +2 -1
  52. package/lib/app/helpers.js +6 -5
  53. package/lib/app/index.js +27 -8
  54. package/lib/app/list.js +7 -6
  55. package/lib/app/push.js +4 -3
  56. package/lib/app/register.js +16 -7
  57. package/lib/app/rotate-secret.js +14 -13
  58. package/lib/app/run-container-start.js +184 -0
  59. package/lib/app/run-docker-fallback.js +108 -0
  60. package/lib/app/run-env-compose.js +30 -42
  61. package/lib/app/run-helpers.js +49 -126
  62. package/lib/app/run-infra-requirements.js +30 -0
  63. package/lib/app/run-resolve-image.js +21 -0
  64. package/lib/app/run.js +74 -21
  65. package/lib/app/show-display.js +1 -1
  66. package/lib/app/show.js +1 -1
  67. package/lib/build/index.js +13 -10
  68. package/lib/cli/index.js +2 -0
  69. package/lib/cli/setup-app.help.js +67 -0
  70. package/lib/cli/setup-app.js +57 -121
  71. package/lib/cli/setup-app.test-commands.js +179 -0
  72. package/lib/cli/setup-auth.js +19 -5
  73. package/lib/cli/setup-credential-deployment.js +22 -8
  74. package/lib/cli/setup-dev-path-commands.js +124 -0
  75. package/lib/cli/setup-dev.js +170 -113
  76. package/lib/cli/setup-environment.js +7 -1
  77. package/lib/cli/setup-external-system.js +62 -22
  78. package/lib/cli/setup-infra.js +126 -47
  79. package/lib/cli/setup-parameters.js +32 -0
  80. package/lib/cli/setup-secrets.js +106 -8
  81. package/lib/cli/setup-service-user.js +1 -1
  82. package/lib/cli/setup-utility.js +36 -20
  83. package/lib/commands/app-down.js +5 -7
  84. package/lib/commands/app-install.js +14 -7
  85. package/lib/commands/app-logs.js +13 -10
  86. package/lib/commands/app-shell.js +4 -1
  87. package/lib/commands/app-test.js +25 -19
  88. package/lib/commands/app.js +22 -10
  89. package/lib/commands/auth-config.js +6 -6
  90. package/lib/commands/auth-status.js +4 -3
  91. package/lib/commands/credential-env.js +4 -3
  92. package/lib/commands/credential-list.js +5 -4
  93. package/lib/commands/credential-push.js +4 -3
  94. package/lib/commands/datasource-unified-test-cli.js +495 -0
  95. package/lib/commands/datasource-unified-test-cli.options.js +149 -0
  96. package/lib/commands/datasource-validation-cli.js +129 -0
  97. package/lib/commands/datasource.js +105 -98
  98. package/lib/commands/deployment-list.js +6 -5
  99. package/lib/commands/dev-cli-handlers.js +122 -18
  100. package/lib/commands/dev-down.js +4 -3
  101. package/lib/commands/dev-init.js +231 -116
  102. package/lib/commands/dev-show-display.js +473 -0
  103. package/lib/commands/login-credentials.js +3 -2
  104. package/lib/commands/login-device.js +4 -3
  105. package/lib/commands/login.js +5 -4
  106. package/lib/commands/logout.js +8 -7
  107. package/lib/commands/parameters-validate.js +54 -0
  108. package/lib/commands/repair-datasource.js +314 -68
  109. package/lib/commands/repair-env-template.js +2 -2
  110. package/lib/commands/repair.js +21 -3
  111. package/lib/commands/secrets-list.js +23 -12
  112. package/lib/commands/secrets-remove-all.js +220 -0
  113. package/lib/commands/secrets-remove.js +21 -12
  114. package/lib/commands/secrets-set.js +21 -12
  115. package/lib/commands/secrets-validate.js +4 -4
  116. package/lib/commands/secure.js +10 -9
  117. package/lib/commands/service-user.js +26 -25
  118. package/lib/commands/test-e2e-external.js +27 -1
  119. package/lib/commands/up-common.js +3 -2
  120. package/lib/commands/up-dataplane.js +29 -16
  121. package/lib/commands/up-miso.js +19 -29
  122. package/lib/commands/upload.js +138 -39
  123. package/lib/commands/wizard-core-helpers.js +1 -1
  124. package/lib/commands/wizard-dataplane.js +4 -3
  125. package/lib/commands/wizard-helpers.js +3 -3
  126. package/lib/commands/wizard.js +2 -2
  127. package/lib/core/admin-secrets.js +14 -5
  128. package/lib/core/audit-logger.js +12 -4
  129. package/lib/core/config-attach-extensions.js +46 -0
  130. package/lib/core/config-runtime-paths.js +29 -0
  131. package/lib/core/config.js +55 -56
  132. package/lib/core/diff.js +3 -2
  133. package/lib/core/ensure-encryption-key.js +1 -1
  134. package/lib/core/secrets-ensure-infra.js +77 -0
  135. package/lib/core/secrets-ensure.js +120 -64
  136. package/lib/core/secrets-env-write.js +35 -7
  137. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  138. package/lib/core/secrets.js +200 -37
  139. package/lib/core/templates-env.js +4 -3
  140. package/lib/datasource/abac-validator.js +1 -10
  141. package/lib/datasource/deploy.js +75 -53
  142. package/lib/datasource/field-reference-validator.js +9 -6
  143. package/lib/datasource/integration-context.js +63 -0
  144. package/lib/datasource/list.js +8 -7
  145. package/lib/datasource/log-viewer.js +84 -53
  146. package/lib/datasource/resolve-app.js +4 -4
  147. package/lib/datasource/test-e2e.js +95 -146
  148. package/lib/datasource/test-integration.js +114 -122
  149. package/lib/datasource/unified-validation-run-body.js +65 -0
  150. package/lib/datasource/unified-validation-run-post.js +23 -0
  151. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  152. package/lib/datasource/unified-validation-run.js +92 -0
  153. package/lib/datasource/validate.js +157 -13
  154. package/lib/deployment/deployer.js +4 -3
  155. package/lib/deployment/environment.js +7 -6
  156. package/lib/deployment/push.js +17 -8
  157. package/lib/external-system/delete.js +4 -3
  158. package/lib/external-system/deploy.js +131 -53
  159. package/lib/external-system/download-helpers.js +1 -1
  160. package/lib/external-system/download.js +7 -6
  161. package/lib/external-system/generator.js +92 -6
  162. package/lib/external-system/integration-test-dispatch.js +26 -0
  163. package/lib/external-system/test-execution.js +5 -1
  164. package/lib/external-system/test-helpers.js +0 -4
  165. package/lib/external-system/test-system-level-helpers.js +110 -0
  166. package/lib/external-system/test-system-level.js +83 -44
  167. package/lib/external-system/test.js +59 -8
  168. package/lib/generator/builders.js +23 -11
  169. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  170. package/lib/generator/external.js +16 -4
  171. package/lib/generator/helpers.js +58 -3
  172. package/lib/generator/index.js +4 -0
  173. package/lib/generator/split-readme.js +12 -7
  174. package/lib/generator/split-variables.js +2 -1
  175. package/lib/generator/split.js +1 -1
  176. package/lib/generator/wizard-readme.js +3 -3
  177. package/lib/generator/wizard.js +8 -8
  178. package/lib/infrastructure/compose.js +60 -6
  179. package/lib/infrastructure/helpers.js +201 -29
  180. package/lib/infrastructure/index.js +28 -17
  181. package/lib/infrastructure/services.js +21 -15
  182. package/lib/internal/fs-real-sync.js +104 -0
  183. package/lib/internal/node-fs.js +98 -0
  184. package/lib/parameters/database-secret-values.js +173 -0
  185. package/lib/parameters/infra-kv-discovery.js +121 -0
  186. package/lib/parameters/infra-parameter-catalog.js +458 -0
  187. package/lib/parameters/infra-parameter-validate.js +64 -0
  188. package/lib/schema/application-schema.json +37 -17
  189. package/lib/schema/datasource-test-run.schema.json +493 -0
  190. package/lib/schema/deployment-rules.yaml +102 -63
  191. package/lib/schema/external-datasource.schema.json +1200 -442
  192. package/lib/schema/external-system.schema.json +181 -5
  193. package/lib/schema/flag-map-validation-run.json +31 -0
  194. package/lib/schema/infra-parameter.schema.json +106 -0
  195. package/lib/schema/infra.parameter.yaml +421 -0
  196. package/lib/schema/type/credential-auth-templates.json +40 -0
  197. package/lib/schema/type/document-storage.json +213 -0
  198. package/lib/schema/type/message-service.json +123 -0
  199. package/lib/schema/type/vector-store.json +88 -0
  200. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  201. package/lib/utils/api-error-handler.js +2 -2
  202. package/lib/utils/api.js +49 -14
  203. package/lib/utils/app-register-api.js +3 -2
  204. package/lib/utils/app-register-auth.js +1 -1
  205. package/lib/utils/app-register-config.js +4 -4
  206. package/lib/utils/app-register-display.js +3 -2
  207. package/lib/utils/app-register-validator.js +3 -2
  208. package/lib/utils/app-run-containers.js +26 -22
  209. package/lib/utils/app-scoped-config.js +31 -0
  210. package/lib/utils/app-service-env-from-builder.js +164 -0
  211. package/lib/utils/build-copy.js +1 -1
  212. package/lib/utils/build-helpers.js +20 -20
  213. package/lib/utils/build-resolve-image.js +165 -0
  214. package/lib/utils/cli-layout-chalk.js +8 -0
  215. package/lib/utils/cli-test-layout-chalk.js +267 -0
  216. package/lib/utils/cli-utils.js +88 -11
  217. package/lib/utils/compose-db-passwords.js +138 -0
  218. package/lib/utils/compose-generate-docker-compose.js +216 -0
  219. package/lib/utils/compose-generator.js +197 -291
  220. package/lib/utils/compose-miso-env.js +18 -0
  221. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  222. package/lib/utils/config-paths.js +166 -7
  223. package/lib/utils/config-scoped-resources-preference.js +41 -0
  224. package/lib/utils/controller-deployment-outcome.js +68 -0
  225. package/lib/utils/credential-display.js +2 -2
  226. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  227. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  228. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  229. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  230. package/lib/utils/datasource-test-run-display.js +442 -0
  231. package/lib/utils/datasource-test-run-exit.js +58 -0
  232. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  233. package/lib/utils/datasource-test-run-report-version.js +51 -0
  234. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  235. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  236. package/lib/utils/datasource-validation-watch.js +266 -0
  237. package/lib/utils/declarative-url-ports.js +47 -0
  238. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  239. package/lib/utils/dev-ca-install.js +185 -23
  240. package/lib/utils/dev-cert-helper.js +266 -17
  241. package/lib/utils/dev-hosts-helper.js +307 -0
  242. package/lib/utils/dev-init-cert-hints.js +37 -0
  243. package/lib/utils/dev-init-health-messages.js +52 -0
  244. package/lib/utils/dev-init-resolve.js +86 -0
  245. package/lib/utils/dev-init-ssh-merge.js +65 -0
  246. package/lib/utils/dev-ssh-config-helper.js +196 -0
  247. package/lib/utils/dev-user-groups.js +93 -0
  248. package/lib/utils/docker-build.js +42 -17
  249. package/lib/utils/docker-exec.js +28 -0
  250. package/lib/utils/docker-manifest-public-port.js +116 -0
  251. package/lib/utils/docker-not-running-hint.js +52 -0
  252. package/lib/utils/docker.js +98 -11
  253. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  254. package/lib/utils/env-config-loader.js +10 -91
  255. package/lib/utils/env-copy.js +19 -10
  256. package/lib/utils/env-map.js +35 -8
  257. package/lib/utils/env-template.js +2 -2
  258. package/lib/utils/environment-scoped-resources.js +144 -0
  259. package/lib/utils/error-formatter.js +92 -13
  260. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  261. package/lib/utils/error-formatters/network-errors.js +2 -1
  262. package/lib/utils/error-formatters/permission-errors.js +2 -1
  263. package/lib/utils/error-formatters/validation-errors.js +2 -1
  264. package/lib/utils/external-readme.js +8 -1
  265. package/lib/utils/external-system-display.js +234 -136
  266. package/lib/utils/external-system-local-test-tty.js +389 -0
  267. package/lib/utils/external-system-readiness-core.js +377 -0
  268. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  269. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  270. package/lib/utils/external-system-readiness-display.js +186 -0
  271. package/lib/utils/external-system-test-helpers.js +24 -6
  272. package/lib/utils/external-system-validators.js +30 -12
  273. package/lib/utils/health-check-url.js +119 -0
  274. package/lib/utils/health-check.js +59 -25
  275. package/lib/utils/help-builder.js +11 -8
  276. package/lib/utils/image-version.js +4 -8
  277. package/lib/utils/infra-containers.js +4 -7
  278. package/lib/utils/infra-env-defaults.js +162 -0
  279. package/lib/utils/infra-status-display.js +167 -0
  280. package/lib/utils/infra-status.js +16 -8
  281. package/lib/utils/local-secrets.js +3 -4
  282. package/lib/utils/paths.js +134 -47
  283. package/lib/utils/port-resolver.js +10 -23
  284. package/lib/utils/redis-env-scope.js +62 -0
  285. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  286. package/lib/utils/remote-builder-validation.js +99 -0
  287. package/lib/utils/remote-dev-auth.js +117 -21
  288. package/lib/utils/remote-docker-env.js +67 -15
  289. package/lib/utils/remote-secrets-loader.js +13 -4
  290. package/lib/utils/resolve-docker-image-ref.js +124 -0
  291. package/lib/utils/schema-loader.js +22 -9
  292. package/lib/utils/secrets-bash-kv.js +25 -0
  293. package/lib/utils/secrets-generator.js +169 -49
  294. package/lib/utils/secrets-helpers.js +70 -59
  295. package/lib/utils/secrets-kv-scope.js +60 -0
  296. package/lib/utils/secrets-utils.js +32 -38
  297. package/lib/utils/secrets-validation.js +3 -1
  298. package/lib/utils/secrets-yaml-preserve.js +109 -0
  299. package/lib/utils/ssh-key-helper.js +4 -2
  300. package/lib/utils/template-helpers.js +2 -2
  301. package/lib/utils/test-log-writer.js +3 -3
  302. package/lib/utils/token-manager.js +1 -2
  303. package/lib/utils/url-declarative-public-base.js +188 -0
  304. package/lib/utils/url-declarative-resolve-build.js +493 -0
  305. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  306. package/lib/utils/url-declarative-resolve.js +220 -0
  307. package/lib/utils/url-declarative-token-parse.js +74 -0
  308. package/lib/utils/url-declarative-url-flags.js +50 -0
  309. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  310. package/lib/utils/url-public-path-prefix.js +34 -0
  311. package/lib/utils/urls-local-registry.js +220 -0
  312. package/lib/utils/validation-report-tty-kit.js +77 -0
  313. package/lib/utils/validation-run-poll.js +89 -0
  314. package/lib/utils/validation-run-post-retry.js +73 -0
  315. package/lib/utils/validation-run-request.js +98 -0
  316. package/lib/utils/variable-transformer.js +21 -4
  317. package/lib/utils/yaml-preserve.js +33 -14
  318. package/lib/validation/datasource-warnings.js +56 -0
  319. package/lib/validation/env-template-auth.js +1 -1
  320. package/lib/validation/external-manifest-validator.js +27 -7
  321. package/lib/validation/validate-display.js +37 -31
  322. package/lib/validation/validate.js +4 -13
  323. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  324. package/lib/validation/validator.js +22 -65
  325. package/lib/validation/wizard-config-validator.js +2 -1
  326. package/package.json +7 -3
  327. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  328. package/scripts/diagnose-cli.js +150 -0
  329. package/scripts/install-local.js +304 -55
  330. package/templates/README.md +15 -2
  331. package/templates/applications/dataplane/application.yaml +52 -2
  332. package/templates/applications/dataplane/env.template +75 -17
  333. package/templates/applications/dataplane/rbac.yaml +8 -0
  334. package/templates/applications/keycloak/application.yaml +9 -1
  335. package/templates/applications/keycloak/env.template +15 -6
  336. package/templates/applications/miso-controller/application.yaml +10 -2
  337. package/templates/applications/miso-controller/env.template +42 -12
  338. package/templates/applications/miso-controller/rbac.yaml +5 -0
  339. package/templates/external-system/README.md.hbs +20 -7
  340. package/templates/external-system/deploy.js.hbs +5 -5
  341. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  342. package/templates/infra/compose.yaml.hbs +20 -4
  343. package/templates/python/docker-compose.hbs +16 -0
  344. package/templates/typescript/docker-compose.hbs +16 -0
  345. package/lib/api/external-test.api.js +0 -111
  346. package/lib/schema/env-config.yaml +0 -60
@@ -0,0 +1,196 @@
1
+ /**
2
+ * @fileoverview Merge a Builder dev SSH Host alias into ~/.ssh/config (Mutagen / interactive SSH).
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const nodeFsLib = require('../internal/node-fs');
8
+ const path = require('path');
9
+ const { ensureSshDir } = require('./ssh-key-helper');
10
+
11
+ /**
12
+ * Derive SSH config Host alias: sync user + real hostname (e.g. dev01.builder02.local).
13
+ * @param {string} sshUser - sync-ssh-user
14
+ * @param {string} sshHost - sync-ssh-host (HostName)
15
+ * @returns {string}
16
+ */
17
+ function getDevSshHostAlias(sshUser, sshHost) {
18
+ return `${sshUser}.${sshHost}`;
19
+ }
20
+
21
+ /**
22
+ * Parse ssh config into Host blocks (lines before the first Host are ignored).
23
+ * @param {string} content
24
+ * @returns {{ aliases: string[], body: string[] }[]}
25
+ */
26
+ function parseSshHostBlocks(content) {
27
+ const lines = (content || '').split(/\r?\n/);
28
+ const blocks = [];
29
+ let i = 0;
30
+ while (i < lines.length) {
31
+ const m = lines[i].match(/^Host\s+(.+?)\s*$/i);
32
+ if (!m) {
33
+ i++;
34
+ continue;
35
+ }
36
+ const aliases = m[1].trim().split(/\s+/).filter(Boolean);
37
+ i++;
38
+ const body = [];
39
+ while (i < lines.length && !/^Host\s/i.test(lines[i])) {
40
+ body.push(lines[i]);
41
+ i++;
42
+ }
43
+ blocks.push({ aliases, body });
44
+ }
45
+ return blocks;
46
+ }
47
+
48
+ /**
49
+ * First value for a directive inside a Host block body (e.g. HostName, User).
50
+ * @param {string[]} bodyLines
51
+ * @param {string} directive - e.g. HostName
52
+ * @returns {string|null}
53
+ */
54
+ function getHostBlockDirective(bodyLines, directive) {
55
+ const re = new RegExp(`^\\s+${directive}\\s+(.+)$`, 'i');
56
+ for (const line of bodyLines) {
57
+ const m = line.match(re);
58
+ if (m) return m[1].trim();
59
+ }
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * Find a Host block whose HostName and User match (exact trim match).
65
+ * @param {string} content
66
+ * @param {string} hostname
67
+ * @param {string} user
68
+ * @returns {{ aliases: string[], body: string[] }|null}
69
+ */
70
+ function findMatchingHostBlockForUserHost(content, hostname, user) {
71
+ for (const block of parseSshHostBlocks(content)) {
72
+ const hn = getHostBlockDirective(block.body, 'HostName');
73
+ const u = getHostBlockDirective(block.body, 'User');
74
+ if (hn === hostname && u === user) {
75
+ return block;
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * SSH Host alias to suggest for ssh(1) when a block already exists.
83
+ * @param {{ aliases: string[] }} matchedBlock
84
+ * @param {string} canonicalAlias - e.g. dev01.builder02.local
85
+ * @returns {string}
86
+ */
87
+ function resolveConnectAlias(matchedBlock, canonicalAlias) {
88
+ if (matchedBlock.aliases.includes(canonicalAlias)) {
89
+ return canonicalAlias;
90
+ }
91
+ return matchedBlock.aliases[0] || canonicalAlias;
92
+ }
93
+
94
+ /**
95
+ * Replace an existing Host block for hostAlias, or append a new block at EOF.
96
+ * @param {string} content - Existing ssh config (may be empty)
97
+ * @param {string} hostAlias - Host keyword value (single alias)
98
+ * @param {string} hostname - HostName directive
99
+ * @param {string} user - User directive
100
+ * @returns {string} Updated config
101
+ */
102
+ function upsertSshHostBlock(content, hostAlias, hostname, user) {
103
+ const lines = (content || '').split(/\r?\n/);
104
+ const out = [];
105
+ let i = 0;
106
+ while (i < lines.length) {
107
+ const line = lines[i];
108
+ const m = line.match(/^Host\s+(.+?)\s*$/i);
109
+ if (m) {
110
+ const aliases = m[1].trim().split(/\s+/).filter(Boolean);
111
+ if (aliases.includes(hostAlias)) {
112
+ i++;
113
+ while (i < lines.length && !/^Host\s/i.test(lines[i])) {
114
+ i++;
115
+ }
116
+ continue;
117
+ }
118
+ }
119
+ out.push(line);
120
+ i++;
121
+ }
122
+ const block = [
123
+ `Host ${hostAlias}`,
124
+ ` HostName ${hostname}`,
125
+ ` User ${user}`,
126
+ ' IdentitiesOnly yes'
127
+ ].join('\n');
128
+ const trimmed = out.join('\n').replace(/\s+$/, '');
129
+ const prefix = trimmed.length ? `${trimmed}\n\n` : '';
130
+ return `${prefix}${block}\n`;
131
+ }
132
+
133
+ /**
134
+ * @param {string} sshUser
135
+ * @param {string} sshHost
136
+ * @returns {{ error: string }|{ user: string, host: string, hostAlias: string }}
137
+ */
138
+ function parseEnsureSshInputs(sshUser, sshHost) {
139
+ if (!sshUser || typeof sshUser !== 'string' || !sshUser.trim()) {
140
+ return { error: 'missing ssh user' };
141
+ }
142
+ if (!sshHost || typeof sshHost !== 'string' || !sshHost.trim()) {
143
+ return { error: 'missing ssh host' };
144
+ }
145
+ const user = sshUser.trim();
146
+ const host = sshHost.trim();
147
+ return { user, host, hostAlias: getDevSshHostAlias(user, host) };
148
+ }
149
+
150
+ /**
151
+ * Ensure ~/.ssh/config contains a Host entry for interactive SSH / tooling.
152
+ * @param {string} sshUser - SSH user (sync-ssh-user)
153
+ * @param {string} sshHost - Real hostname (sync-ssh-host)
154
+ * @param {string} [sshDir] - SSH directory (default ~/.ssh)
155
+ * @returns {Promise<{ ok: boolean, configPath?: string, hostAlias?: string, error?: string, skippedDuplicate?: boolean }>}
156
+ */
157
+ async function ensureDevSshConfigBlock(sshUser, sshHost, sshDir) {
158
+ const parsed = parseEnsureSshInputs(sshUser, sshHost);
159
+ if ('error' in parsed) {
160
+ return { ok: false, error: parsed.error };
161
+ }
162
+ const { user, host, hostAlias } = parsed;
163
+ const dir = ensureSshDir(sshDir);
164
+ const configPath = path.join(dir, 'config');
165
+ let existing = '';
166
+ try {
167
+ existing = await nodeFsLib.nodeFs().promises.readFile(configPath, 'utf8');
168
+ } catch (e) {
169
+ if (e.code !== 'ENOENT') {
170
+ return { ok: false, error: e.message || String(e) };
171
+ }
172
+ }
173
+ const matched = findMatchingHostBlockForUserHost(existing, host, user);
174
+ if (matched) {
175
+ return {
176
+ ok: true,
177
+ configPath,
178
+ hostAlias: resolveConnectAlias(matched, hostAlias),
179
+ skippedDuplicate: true
180
+ };
181
+ }
182
+ const next = upsertSshHostBlock(existing, hostAlias, host, user);
183
+ if (next === existing) {
184
+ return { ok: true, configPath, hostAlias };
185
+ }
186
+ await nodeFsLib.nodeFs().promises.writeFile(configPath, next, { mode: 0o600 });
187
+ return { ok: true, configPath, hostAlias };
188
+ }
189
+
190
+ module.exports = {
191
+ getDevSshHostAlias,
192
+ findMatchingHostBlockForUserHost,
193
+ resolveConnectAlias,
194
+ upsertSshHostBlock,
195
+ ensureDevSshConfigBlock
196
+ };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Allowed dev user groups for Builder Server user APIs (must match server DTOs).
3
+ * @fileoverview
4
+ * @author AI Fabrix Team
5
+ * @version 2.0.0
6
+ */
7
+
8
+ const ALLOWED_DEV_GROUPS = Object.freeze([
9
+ 'admin',
10
+ 'secret-manager',
11
+ 'developer',
12
+ 'docker'
13
+ ]);
14
+
15
+ /**
16
+ * Parse --groups CLI value into normalized lowercase tokens.
17
+ * Uses commas and/or whitespace so PowerShell (which often turns `a,b,c` into
18
+ * separate argv tokens later joined as `a b c`) still works.
19
+ * @param {string|string[]} raw - e.g. "admin, developer,docker" or ["admin","developer"]
20
+ * @returns {string[]}
21
+ */
22
+ function parseDevGroupsOption(raw) {
23
+ if (raw === null || raw === undefined) {
24
+ return [];
25
+ }
26
+ if (Array.isArray(raw)) {
27
+ return raw
28
+ .map(s => String(s).trim().toLowerCase())
29
+ .filter(Boolean);
30
+ }
31
+ if (typeof raw !== 'string') {
32
+ return [];
33
+ }
34
+ return raw
35
+ .split(/[\s,]+/)
36
+ .map(s => s.trim().toLowerCase())
37
+ .filter(Boolean);
38
+ }
39
+
40
+ /**
41
+ * Ensure every group is allowed; throws with an actionable message if not.
42
+ * @param {string[]} groups - Normalized group names
43
+ * @throws {Error} When an unknown group is present
44
+ * @returns {string[]} Same array reference
45
+ */
46
+ function validateDevGroups(groups) {
47
+ if (!Array.isArray(groups)) {
48
+ throw new Error('groups must be an array');
49
+ }
50
+ const invalid = groups.filter(g => !ALLOWED_DEV_GROUPS.includes(g));
51
+ if (invalid.length > 0) {
52
+ throw new Error(
53
+ `Invalid group(s): ${invalid.join(', ')}. Each value must be one of: ${ALLOWED_DEV_GROUPS.join(', ')}`
54
+ );
55
+ }
56
+ return groups;
57
+ }
58
+
59
+ /**
60
+ * When the Builder Server rejects `groups` with an enum message that omits `docker`,
61
+ * but the CLI sent `docker`, append a short hint (the server must be upgraded).
62
+ * Mutates err.message when applicable.
63
+ * @param {Error} err - API error
64
+ * @param {string[]|undefined} requestedGroups - groups sent in the request body
65
+ * @returns {Error}
66
+ */
67
+ function augmentDevUserGroupsServerError(err, requestedGroups) {
68
+ if (!err || typeof err.message !== 'string') {
69
+ return err;
70
+ }
71
+ if (!Array.isArray(requestedGroups) || !requestedGroups.includes('docker')) {
72
+ return err;
73
+ }
74
+ const msg = err.message;
75
+ const looksLikeGroupEnum =
76
+ /each value in groups must be one of/i.test(msg) ||
77
+ (/groups/i.test(msg) && /must be one of the following values/i.test(msg));
78
+ if (!looksLikeGroupEnum) {
79
+ return err;
80
+ }
81
+ if (msg.toLowerCase().includes('docker')) {
82
+ return err;
83
+ }
84
+ err.message = `${msg} The Builder Server that handled this request does not accept the \`docker\` group yet; update that service (user DTO enum), or omit docker from --groups until it is deployed.`;
85
+ return err;
86
+ }
87
+
88
+ module.exports = {
89
+ ALLOWED_DEV_GROUPS,
90
+ parseDevGroupsOption,
91
+ validateDevGroups,
92
+ augmentDevUserGroupsServerError
93
+ };
@@ -1,3 +1,4 @@
1
+ const { formatSuccessLine } = require('./cli-test-layout-chalk');
1
2
  /**
2
3
  * Docker Build Utilities
3
4
  *
@@ -11,6 +12,7 @@
11
12
 
12
13
  const { spawn } = require('child_process');
13
14
  const ora = require('ora');
15
+ const { getDockerNotRunningErrorMessage } = require('./docker-not-running-hint');
14
16
 
15
17
  /**
16
18
  * Checks if error indicates Docker is not running or not installed
@@ -86,7 +88,7 @@ function handleDockerClose(code, ctx) {
86
88
  spinner.fail('Build failed');
87
89
  const errorMessage = stderrBuffer || stdoutBuffer || 'Docker build failed';
88
90
  if (isDockerNotAvailableError(errorMessage)) {
89
- reject(new Error('Docker is not running or not installed. Please start Docker Desktop and try again.'));
91
+ reject(new Error(getDockerNotRunningErrorMessage()));
90
92
  } else {
91
93
  const errorLines = errorMessage.split('\n').filter(line => line.trim());
92
94
  reject(new Error(`Docker build failed: ${errorLines.slice(-5).join('\n')}`));
@@ -94,17 +96,35 @@ function handleDockerClose(code, ctx) {
94
96
  }
95
97
  }
96
98
 
97
- function runDockerBuildProcess(buildOpts) {
98
- const { imageName, tag, dockerfilePath, contextPath, spinner, resolve, reject, env = {}, buildArgs = {} } = buildOpts;
99
- const spawnEnv = { ...process.env, ...env };
99
+ function buildDockerCliArgs(imageName, tag, dockerfilePath, contextPath, buildArgs, noCache) {
100
100
  const args = ['build', '-t', `${imageName}:${tag}`, '-f', dockerfilePath];
101
- // Pass NPM_TOKEN/PYPI_TOKEN etc. so private registry auth works during RUN npm install / pip install
102
101
  for (const [key, value] of Object.entries(buildArgs)) {
103
102
  if (value !== null && value !== undefined && String(value).length > 0) {
104
103
  args.push('--build-arg', `${key}=${String(value)}`);
105
104
  }
106
105
  }
106
+ if (noCache) {
107
+ args.push('--no-cache');
108
+ }
107
109
  args.push(contextPath);
110
+ return args;
111
+ }
112
+
113
+ function runDockerBuildProcess(buildOpts) {
114
+ const {
115
+ imageName,
116
+ tag,
117
+ dockerfilePath,
118
+ contextPath,
119
+ spinner,
120
+ resolve,
121
+ reject,
122
+ env = {},
123
+ buildArgs = {},
124
+ noCache = false
125
+ } = buildOpts;
126
+ const spawnEnv = { ...process.env, ...env };
127
+ const args = buildDockerCliArgs(imageName, tag, dockerfilePath, contextPath, buildArgs, noCache);
108
128
  const dockerProcess = spawn('docker', args, {
109
129
  shell: process.platform === 'win32',
110
130
  env: spawnEnv
@@ -135,7 +155,7 @@ function runDockerBuildProcess(buildOpts) {
135
155
  spinner.fail('Build failed');
136
156
  const msg = error.message || String(error);
137
157
  if (isDockerNotAvailableError(msg)) {
138
- reject(new Error('Docker is not running or not installed. Please start Docker Desktop and try again.'));
158
+ reject(new Error(getDockerNotRunningErrorMessage()));
139
159
  } else {
140
160
  reject(new Error(`Docker build failed: ${msg}`));
141
161
  }
@@ -149,10 +169,11 @@ function runDockerBuildProcess(buildOpts) {
149
169
  * @param {string} contextPath - Build context path
150
170
  * @param {string} tag - Image tag
151
171
  * @param {Object} [buildArgs={}] - Optional build args (e.g. NPM_TOKEN, PYPI_TOKEN) for private registries
172
+ * @param {boolean} [noCache=false] - When true, pass `docker build --no-cache` (full rebuild)
152
173
  * @returns {Promise<void>} Resolves when build completes
153
174
  * @throws {Error} If build fails
154
175
  */
155
- async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, buildArgs = {}) {
176
+ async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, buildArgs = {}, noCache = false) {
156
177
  const spinner = ora({ text: 'Starting Docker build...', spinner: 'dots' }).start();
157
178
  const fsSync = require('fs');
158
179
  const path = require('path');
@@ -187,7 +208,8 @@ async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, b
187
208
  resolve,
188
209
  reject,
189
210
  env: remoteEnv,
190
- buildArgs: resolvedBuildArgs
211
+ buildArgs: resolvedBuildArgs,
212
+ noCache
191
213
  });
192
214
  });
193
215
  }
@@ -205,16 +227,20 @@ async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, b
205
227
  */
206
228
  async function executeBuild(imageName, dockerfilePath, contextPath, tag, options) {
207
229
  const buildArgs = (options && options.buildArgs) || {};
208
- await executeDockerBuild(imageName, dockerfilePath, contextPath, tag, buildArgs);
230
+ // Commander maps `--no-cache` to `cache: false` (boolean negation), not `noCache: true`.
231
+ const noCache = Boolean(
232
+ options &&
233
+ (options.cache === false || options.noCache === true || options['no-cache'] === true)
234
+ );
235
+ await executeDockerBuild(imageName, dockerfilePath, contextPath, tag, buildArgs, noCache);
209
236
 
210
237
  // Tag image if additional tag provided
211
238
  if (options && options.tag && options.tag !== 'latest') {
212
239
  const { promisify } = require('util');
213
240
  const { exec } = require('child_process');
214
241
  const run = promisify(exec);
215
- const { getRemoteDockerEnv } = require('./remote-docker-env');
216
- const remoteEnv = await getRemoteDockerEnv();
217
- const env = { ...process.env, ...remoteEnv };
242
+ const { getDockerExecEnv } = require('./remote-docker-env');
243
+ const env = await getDockerExecEnv();
218
244
  await run(`docker tag ${imageName}:${tag} ${imageName}:latest`, { env });
219
245
  }
220
246
  }
@@ -244,13 +270,12 @@ async function executeDockerBuildWithTag(effectiveImageName, imageName, dockerfi
244
270
  const { promisify } = require('util');
245
271
  const { exec } = require('child_process');
246
272
  const run = promisify(exec);
247
- const { getRemoteDockerEnv } = require('./remote-docker-env');
248
- const remoteEnv = await getRemoteDockerEnv();
249
- const env = { ...process.env, ...remoteEnv };
273
+ const { getDockerExecEnv } = require('./remote-docker-env');
274
+ const env = await getDockerExecEnv();
250
275
  await run(`docker tag ${effectiveImageName}:${tag} ${imageName}:${tag}`, { env });
251
- logger.log(chalk.green(`✓ Tagged image: ${imageName}:${tag}`));
276
+ logger.log(formatSuccessLine(`Tagged image: ${imageName}:${tag}`));
252
277
  } catch (err) {
253
- logger.log(chalk.yellow(`⚠️ Warning: Could not create compatibility tag ${imageName}:${tag} - ${err.message}`));
278
+ logger.log(chalk.yellow(`⚠ Warning: Could not create compatibility tag ${imageName}:${tag} - ${err.message}`));
254
279
  }
255
280
  }
256
281
 
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Docker CLI exec with dev-config remote Docker (docker-endpoint + TLS) applied.
3
+ *
4
+ * @fileoverview Wraps child_process.exec with getDockerExecEnv for consistent remote daemon use
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { promisify } = require('util');
12
+ const { exec } = require('child_process');
13
+
14
+ const execAsync = promisify(exec);
15
+
16
+ /**
17
+ * Run a shell command with process.env merged with remote Docker client settings when docker-endpoint is set.
18
+ * @param {string} command
19
+ * @param {import('child_process').ExecOptions} [options]
20
+ * @returns {Promise<{ stdout: string, stderr: string }>}
21
+ */
22
+ async function execWithDockerEnv(command, options = {}) {
23
+ const { getDockerExecEnv } = require('./remote-docker-env');
24
+ const env = { ...(await getDockerExecEnv()), ...(options.env || {}) };
25
+ return execAsync(command, { ...options, env });
26
+ }
27
+
28
+ module.exports = { execWithDockerEnv };
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Docker .env generation: manifest `port` is the host-published port; env-config `*_PORT` is
3
+ * often internal. After resolveKvReferences, ${*_PUBLIC_PORT} may already be interpolated from
4
+ * schema before manifest port is applied — align *_PUBLIC_PORT with application.yaml `port`.
5
+ *
6
+ * Matching rule (no app-specific keys in callers): find env-config `*_HOST` whose value equals
7
+ * `application.yaml` `app.key` (Docker Compose service name), then update the paired *_PUBLIC_PORT.
8
+ *
9
+ * @fileoverview Manifest published port for docker env interpolation
10
+ * @author AI Fabrix Team
11
+ * @version 1.0.0
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ /**
17
+ * @param {string} s
18
+ * @returns {string}
19
+ */
20
+ function escapeRegExp(s) {
21
+ return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
22
+ }
23
+
24
+ /**
25
+ * @param {Object} envVars
26
+ * @param {string} appKey - application.yaml app.key (trimmed)
27
+ * @returns {string|null} e.g. KEYCLOAK_HOST
28
+ */
29
+ function findDockerHostKeyForAppKey(envVars, appKey) {
30
+ if (!envVars || !appKey) {
31
+ return null;
32
+ }
33
+ for (const [k, v] of Object.entries(envVars)) {
34
+ if (!/_HOST$/.test(k)) {
35
+ continue;
36
+ }
37
+ if (String(v) === appKey) {
38
+ return k;
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+
44
+ /**
45
+ * @param {Object} envVars
46
+ * @param {string} appKey
47
+ * @returns {string|null} e.g. KEYCLOAK_PUBLIC_PORT
48
+ */
49
+ function publicPortKeyForAppKey(envVars, appKey) {
50
+ const hostKey = findDockerHostKeyForAppKey(envVars, appKey);
51
+ if (!hostKey) {
52
+ return null;
53
+ }
54
+ return hostKey.replace(/_HOST$/, '_PUBLIC_PORT');
55
+ }
56
+
57
+ /**
58
+ * Set *_PUBLIC_PORT from manifest `port` + developer id when docker service name matches app.key.
59
+ * @param {Object} envVars - Mutated map from buildEnvVarMap
60
+ * @param {Object|null|undefined} appDoc - application.yaml root
61
+ * @returns {Promise<void>}
62
+ */
63
+ async function mergeDockerManifestPublishedPort(envVars, appDoc) {
64
+ if (!appDoc || !appDoc.app || typeof appDoc.app.key !== 'string') {
65
+ return;
66
+ }
67
+ const appKey = appDoc.app.key.trim();
68
+ if (!appKey) {
69
+ return;
70
+ }
71
+ const publicPortKey = publicPortKeyForAppKey(envVars, appKey);
72
+ if (!publicPortKey) {
73
+ return;
74
+ }
75
+ const rawPub = appDoc.port;
76
+ const pubBase = rawPub !== undefined && rawPub !== null ? Number(rawPub) : NaN;
77
+ if (!Number.isFinite(pubBase)) {
78
+ return;
79
+ }
80
+ const devIdNum = await require('./env-map').getDeveloperIdNumber(null);
81
+ envVars[publicPortKey] = String(devIdNum > 0 ? pubBase + devIdNum * 100 : pubBase);
82
+ }
83
+
84
+ /**
85
+ * Rewrite first line for the matched *_PUBLIC_PORT after interpolateEnvVars (early kv pass may have wrong value).
86
+ * @param {string} resolved
87
+ * @param {Object} envVars
88
+ * @param {Object|null|undefined} appDoc
89
+ * @returns {string}
90
+ */
91
+ function rewriteDockerManifestPublicPortEnvLine(resolved, envVars, appDoc) {
92
+ if (!resolved || !appDoc || !appDoc.app || typeof appDoc.app.key !== 'string') {
93
+ return resolved;
94
+ }
95
+ const appKey = appDoc.app.key.trim();
96
+ if (!appKey) {
97
+ return resolved;
98
+ }
99
+ const publicPortKey = publicPortKeyForAppKey(envVars, appKey);
100
+ if (!publicPortKey) {
101
+ return resolved;
102
+ }
103
+ const publicPort = envVars[publicPortKey];
104
+ if (publicPort === undefined || publicPort === null || publicPort === '') {
105
+ return resolved;
106
+ }
107
+ const re = new RegExp(`^${escapeRegExp(publicPortKey)}\\s*=\\s*.*$`, 'm');
108
+ return resolved.replace(re, `${publicPortKey}=${publicPort}`);
109
+ }
110
+
111
+ module.exports = {
112
+ findDockerHostKeyForAppKey,
113
+ publicPortKeyForAppKey,
114
+ mergeDockerManifestPublishedPort,
115
+ rewriteDockerManifestPublicPortEnvLine
116
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @fileoverview Platform-specific hints when the Docker CLI cannot reach the daemon.
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * Short sentence: how to get a working Docker daemon for this OS (no trailing period in fragment use).
11
+ * @returns {string}
12
+ */
13
+ function getDockerDaemonStartHintSentence() {
14
+ if (process.platform === 'linux') {
15
+ return (
16
+ 'Start the Docker daemon (e.g. sudo systemctl start docker), install Docker Engine if needed, ' +
17
+ 'and ensure your user can use the socket (e.g. sudo usermod -aG docker $USER, then log out and back in). ' +
18
+ 'On Windows or macOS, start Docker Desktop.'
19
+ );
20
+ }
21
+ if (process.platform === 'darwin') {
22
+ return 'Start Docker Desktop (or Colima / another runtime) and try again.';
23
+ }
24
+ return 'Start Docker Desktop and try again.';
25
+ }
26
+
27
+ /**
28
+ * Single-line error for thrown Error.message (build spawn failures, etc.).
29
+ * @returns {string}
30
+ */
31
+ function getDockerNotRunningErrorMessage() {
32
+ return `Docker is not running or not installed. ${getDockerDaemonStartHintSentence()}`;
33
+ }
34
+
35
+ /**
36
+ * When the user cannot use the local unix socket (no docker group) but Docker runs on the same or another host
37
+ * exposing the Engine API — the CLI talks to that API via DOCKER_HOST (docker-endpoint in ~/.aifabrix/config.yaml).
38
+ * @returns {string[]}
39
+ */
40
+ function getDockerApiOverTcpHintLines() {
41
+ return [
42
+ ' Without unix socket access: set docker-endpoint to the Docker Engine API (e.g. tcp://builder02.local:2376). Builder settings often merge this after aifabrix dev init or dev refresh.',
43
+ ' The CLI still runs docker/docker compose locally as a thin client to that API (not the Builder HTTP API). TLS: cert.pem, key.pem, ca.pem in ~/.aifabrix/certs/<developer-id>/ (match daemon client-auth).',
44
+ ' Run: aifabrix dev show'
45
+ ];
46
+ }
47
+
48
+ module.exports = {
49
+ getDockerDaemonStartHintSentence,
50
+ getDockerNotRunningErrorMessage,
51
+ getDockerApiOverTcpHintLines
52
+ };