@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
@@ -9,13 +9,10 @@
9
9
  * @version 2.0.0
10
10
  */
11
11
 
12
- const { exec } = require('child_process');
13
- const { promisify } = require('util');
14
12
  const config = require('../core/config');
15
13
  const devConfig = require('./dev-config');
16
14
  const containerUtils = require('./infra-containers');
17
-
18
- const execAsync = promisify(exec);
15
+ const { execWithDockerEnv } = require('./docker-exec');
19
16
 
20
17
  /**
21
18
  * Builds services config map from ports and config flags.
@@ -52,7 +49,7 @@ async function getServiceStatus(serviceName, serviceConfig, devId) {
52
49
  try {
53
50
  const containerName = await containerUtils.findContainer(serviceName, devId, { strict: true });
54
51
  const rawStatus = containerName
55
- ? (await execAsync(`docker inspect --format='{{.State.Status}}' ${containerName}`)).stdout.trim().replace(/['"]/g, '')
52
+ ? (await execWithDockerEnv(`docker inspect --format='{{.State.Status}}' ${containerName}`)).stdout.trim().replace(/['"]/g, '')
56
53
  : 'not running';
57
54
  return { status: rawStatus, port: serviceConfig.port, url: serviceConfig.url };
58
55
  } catch {
@@ -107,6 +104,9 @@ function getInfraContainerNames(devIdNum, devId) {
107
104
  /** Suffixes for init/helper containers to exclude from "Running Applications" (e.g. keycloak-db-init) */
108
105
  const INIT_CONTAINER_SUFFIXES = ['-db-init', '-init'];
109
106
 
107
+ /** Names like aifabrix-dev02-postgres belong to isolated developer stacks, not legacy dev-0 mode */
108
+ const DEV_PREFIXED_CONTAINER = /^aifabrix-dev\d+-/;
109
+
110
110
  /**
111
111
  * Extracts app name from container name
112
112
  * @param {string} containerName - Container name
@@ -115,6 +115,9 @@ const INIT_CONTAINER_SUFFIXES = ['-db-init', '-init'];
115
115
  * @returns {string|null} App name or null if not matched
116
116
  */
117
117
  function extractAppName(containerName, devIdNum, devId) {
118
+ if (devIdNum === 0 && DEV_PREFIXED_CONTAINER.test(containerName)) {
119
+ return null;
120
+ }
118
121
  const pattern = devIdNum === 0 ? /^aifabrix-(.+)$/ : new RegExp(`^aifabrix-dev${devId}-(.+)$`);
119
122
  const match = containerName.match(pattern);
120
123
  if (!match) return null;
@@ -176,7 +179,7 @@ async function getAppStatus() {
176
179
  try {
177
180
  const devIdNum = parseInt(devId, 10);
178
181
  const filterPattern = devIdNum === 0 ? 'aifabrix-' : `aifabrix-dev${devId}-`;
179
- const { stdout } = await execAsync(`docker ps --filter "name=${filterPattern}" --format "{{.Names}}\t{{.Ports}}\t{{.Status}}"`);
182
+ const { stdout } = await execWithDockerEnv(`docker ps --filter "name=${filterPattern}" --format "{{.Names}}\t{{.Ports}}\t{{.Status}}"`);
180
183
  const lines = stdout.trim().split('\n').filter(line => line.trim() !== '');
181
184
  const infraContainers = getInfraContainerNames(devIdNum, devId);
182
185
  for (const line of lines) {
@@ -211,9 +214,14 @@ async function listAppContainerNamesForDeveloper(devId, options = {}) {
211
214
  const includeExited = !!options.includeExited;
212
215
  try {
213
216
  const allFlag = includeExited ? ' -a' : '';
214
- const { stdout } = await execAsync(`docker ps${allFlag} --filter "name=${filterPattern}" --format "{{.Names}}"`);
217
+ const { stdout } = await execWithDockerEnv(`docker ps${allFlag} --filter "name=${filterPattern}" --format "{{.Names}}"`);
215
218
  const names = (stdout || '').trim().split('\n').filter(Boolean);
216
- return names.filter(n => !infraContainers.includes(n));
219
+ return names.filter(n => {
220
+ if (devIdNum === 0 && DEV_PREFIXED_CONTAINER.test(n)) {
221
+ return false;
222
+ }
223
+ return !infraContainers.includes(n);
224
+ });
217
225
  } catch {
218
226
  return [];
219
227
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Local Secrets Management Utilities
3
3
  *
4
- * Helper functions for managing local secrets in ~/.aifabrix/secrets.local.yaml
4
+ * Helper functions for managing local secrets in getPrimaryUserSecretsLocalPath() (config dir)
5
5
  *
6
6
  * @fileoverview Local secrets management utilities
7
7
  * @author AI Fabrix Team
@@ -36,8 +36,7 @@ async function resolveValueForWrite(key, value) {
36
36
  }
37
37
 
38
38
  /**
39
- * Saves a secret to ~/.aifabrix/secrets.local.yaml
40
- * Uses paths.getAifabrixHome() to respect config.yaml aifabrix-home override
39
+ * Saves a secret to the primary user secrets file (getPrimaryUserSecretsLocalPath)
41
40
  * Merges the key into the file (updates in place if key already exists, e.g. after rotate-secret).
42
41
  * Encrypts the value when a secrets-encryption key is configured (except for the bootstrap key).
43
42
  *
@@ -61,7 +60,7 @@ async function saveLocalSecret(key, value) {
61
60
  }
62
61
 
63
62
  const valueToWrite = await resolveValueForWrite(key, value);
64
- const secretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
63
+ const secretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
65
64
  mergeSecretsIntoFile(secretsPath, { [key]: valueToWrite });
66
65
  }
67
66
 
@@ -12,6 +12,11 @@
12
12
  const path = require('path');
13
13
  const fs = require('fs');
14
14
  const yaml = require('js-yaml');
15
+ const { nodeFs } = require('../internal/node-fs');
16
+ const {
17
+ getAifabrixRuntimeConfigDir,
18
+ resolveAifabrixHomeLikePath
19
+ } = require('./aifabrix-runtime-config-dir');
15
20
 
16
21
  function safeHomedir() {
17
22
  try {
@@ -29,30 +34,46 @@ function safeHomedir() {
29
34
  }
30
35
 
31
36
  /**
32
- * Returns the path to the config directory (same precedence as config.js so both read the same config).
33
- * Priority: AIFABRIX_CONFIG (dirname) AIFABRIX_HOME ~/.aifabrix.
37
+ * Returns the path to the config directory (same as {@link getAifabrixRuntimeConfigDir} / config.js).
38
+ * When `AIFABRIX_HOME` is `$HOME` but `config.yaml` is only under `$HOME/.aifabrix/`, returns the latter.
34
39
  * @returns {string} Absolute path to config directory
35
40
  */
36
41
  function getConfigDirForPaths() {
37
- const configFile = process.env.AIFABRIX_CONFIG && typeof process.env.AIFABRIX_CONFIG === 'string';
38
- if (configFile) {
39
- return path.dirname(path.resolve(process.env.AIFABRIX_CONFIG.trim()));
40
- }
41
- if (process.env.AIFABRIX_HOME && typeof process.env.AIFABRIX_HOME === 'string') {
42
- return path.resolve(process.env.AIFABRIX_HOME.trim());
43
- }
44
- return path.join(safeHomedir(), '.aifabrix');
42
+ return getAifabrixRuntimeConfigDir();
43
+ }
44
+
45
+ /**
46
+ * User-owned `secrets.local.yaml` beside effective `config.yaml` (see {@link getConfigDirForPaths}).
47
+ * When `AIFABRIX_HOME` is `$HOME` but config is only at `$HOME/.aifabrix/config.yaml`, uses that folder.
48
+ * Keeps `secret list` / `secret set` aligned with resolve merge (`loadPrimaryUserSecrets`).
49
+ *
50
+ * @returns {string} Absolute path to secrets.local.yaml
51
+ */
52
+ function getPrimaryUserSecretsLocalPath() {
53
+ return path.join(getConfigDirForPaths(), 'secrets.local.yaml');
54
+ }
55
+
56
+ /**
57
+ * Directory for CLI system state next to `config.yaml`: `admin-secrets.env`, `infra/` or `infra-dev{id}/`,
58
+ * `audit.log`, etc. Unlike {@link getAifabrixHome}, this follows the resolved config directory (e.g.
59
+ * `~/.aifabrix` when config lives there even if `aifabrix-home` / `AIFABRIX_HOME` is `$HOME`).
60
+ *
61
+ * @returns {string} Absolute path (same as {@link getConfigDirForPaths})
62
+ */
63
+ function getAifabrixSystemDir() {
64
+ return getConfigDirForPaths();
45
65
  }
46
66
 
47
67
  /**
48
68
  * Returns the base AI Fabrix directory.
49
- * Priority: AIFABRIX_HOME env → config.yaml `aifabrix-home` (from AIFABRIX_HOME or ~/.aifabrix) → ~/.aifabrix.
69
+ * Priority: AIFABRIX_HOME env → config.yaml `aifabrix-home` → ~/.aifabrix.
70
+ * Builder-server SSH provisioning sets `aifabrix-home` to the user POSIX home; dev `.bashrc` exports AIFABRIX_HOME from that key (default $HOME).
50
71
  *
51
72
  * @returns {string} Absolute path to the AI Fabrix home directory
52
73
  */
53
74
  function getAifabrixHome() {
54
75
  if (process.env.AIFABRIX_HOME && typeof process.env.AIFABRIX_HOME === 'string') {
55
- return path.resolve(process.env.AIFABRIX_HOME.trim());
76
+ return resolveAifabrixHomeLikePath(process.env.AIFABRIX_HOME.trim());
56
77
  }
57
78
  const isTestEnv = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
58
79
  if (!isTestEnv) {
@@ -64,7 +85,7 @@ function getAifabrixHome() {
64
85
  const config = yaml.load(content) || {};
65
86
  const homeOverride = config && typeof config['aifabrix-home'] === 'string' ? config['aifabrix-home'].trim() : '';
66
87
  if (homeOverride) {
67
- return path.resolve(homeOverride);
88
+ return resolveAifabrixHomeLikePath(homeOverride);
68
89
  }
69
90
  }
70
91
  } catch {
@@ -74,6 +95,40 @@ function getAifabrixHome() {
74
95
  return path.join(safeHomedir(), '.aifabrix');
75
96
  }
76
97
 
98
+ /**
99
+ * Default git / workspace root from env or config (optional; no fallback to aifabrix-home).
100
+ * Priority: AIFABRIX_WORK env (trim, resolve) → config.yaml `aifabrix-work` → null.
101
+ *
102
+ * @returns {string|null} Absolute path or null when unset
103
+ */
104
+ function getAifabrixWork() {
105
+ if (process.env.AIFABRIX_WORK && typeof process.env.AIFABRIX_WORK === 'string') {
106
+ const t = process.env.AIFABRIX_WORK.trim();
107
+ if (t) {
108
+ return resolveAifabrixHomeLikePath(t);
109
+ }
110
+ }
111
+ const isTestEnv = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
112
+ if (!isTestEnv) {
113
+ try {
114
+ const configDir = getConfigDirForPaths();
115
+ const configPath = path.join(configDir, 'config.yaml');
116
+ if (fs.existsSync(configPath)) {
117
+ const content = fs.readFileSync(configPath, 'utf8');
118
+ const config = yaml.load(content) || {};
119
+ const workOverride =
120
+ config && typeof config['aifabrix-work'] === 'string' ? config['aifabrix-work'].trim() : '';
121
+ if (workOverride) {
122
+ return resolveAifabrixHomeLikePath(workOverride);
123
+ }
124
+ }
125
+ } catch {
126
+ // ignore
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+
77
132
  // Cache project root to avoid repeated filesystem lookups
78
133
  let cachedProjectRoot = null;
79
134
 
@@ -92,7 +147,8 @@ function clearProjectRootCache() {
92
147
  */
93
148
  function hasPackageJson(dirPath) {
94
149
  const packageJsonPath = path.join(dirPath, 'package.json');
95
- return fs.existsSync(packageJsonPath);
150
+ // Real disk: Jest workers may retain jest.mock('fs') from other suites; project root must stay truthful.
151
+ return nodeFs().existsSync(packageJsonPath);
96
152
  }
97
153
 
98
154
  /**
@@ -215,22 +271,31 @@ function tryFindProjectRoot() {
215
271
  }
216
272
 
217
273
  function getProjectRoot() {
218
- // Return cached value if available and valid
274
+ // Prefer global.PROJECT_ROOT whenever it is valid so tests that override it (or restore it)
275
+ // are not defeated by a stale cachedProjectRoot from an earlier call in the same Jest worker.
276
+ const globalRoot = checkGlobalProjectRoot();
277
+ if (globalRoot) {
278
+ cachedProjectRoot = globalRoot;
279
+ return globalRoot;
280
+ }
219
281
  if (cachedProjectRoot && hasPackageJson(cachedProjectRoot)) {
220
282
  return cachedProjectRoot;
221
283
  }
222
-
223
284
  return tryFindProjectRoot();
224
285
  }
225
286
 
226
287
  /**
227
- * Returns the applications base directory. Dev 0: <home>/applications; Dev > 0: <home>/applications-dev-{id}
288
+ * Returns the applications base directory next to effective `config.yaml` (same root as infra, secrets, audit).
289
+ * Dev 0: `<configDir>/applications`; non-zero dev: `<configDir>/applications-dev-{id}`.
290
+ * Uses {@link getAifabrixSystemDir}, not raw {@link getAifabrixHome}, so builder-server layouts with
291
+ * `AIFABRIX_HOME=$HOME` and config under `~/.aifabrix/` keep apps under `.aifabrix` (not `$HOME/applications-dev-*`).
292
+ *
228
293
  * @param {number|string} developerId - Developer ID
229
294
  * @returns {string} Absolute path to applications base directory
230
295
  */
231
296
  function getApplicationsBaseDir(developerId) {
232
297
  const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
233
- const base = getAifabrixHome();
298
+ const base = getAifabrixSystemDir();
234
299
  if (idNum === 0) {
235
300
  return path.join(base, 'applications');
236
301
  }
@@ -249,7 +314,8 @@ function getDevDirectory(appName, developerId) {
249
314
  }
250
315
 
251
316
  /**
252
- * Gets the application path (builder or integration folder)
317
+ * Gets the application path (builder or integration folder).
318
+ * Matches getBuilderPath / getIntegrationPath: respects AIFABRIX_BUILDER_DIR and project-root vs cwd base.
253
319
  * @param {string} appName - Application name
254
320
  * @param {string} [appType] - Application type ('external' or other)
255
321
  * @returns {string} Absolute path to application directory
@@ -258,9 +324,10 @@ function getAppPath(appName, appType) {
258
324
  if (!appName || typeof appName !== 'string') {
259
325
  throw new Error('App name is required and must be a string');
260
326
  }
261
-
262
- const baseDir = appType === 'external' ? 'integration' : 'builder';
263
- return path.join(process.cwd(), baseDir, appName);
327
+ if (appType === 'external') {
328
+ return getIntegrationPath(appName);
329
+ }
330
+ return path.join(getBuilderRoot(), appName);
264
331
  }
265
332
 
266
333
  /**
@@ -299,28 +366,45 @@ function getBuilderRoot() {
299
366
  return path.join(getIntegrationBuilderBaseDir(), 'builder');
300
367
  }
301
368
 
369
+ /**
370
+ * True when name under root is a real directory (follows symlinks). False for broken symlinks, files, ENOENT.
371
+ * @param {string} root - Parent directory (must exist)
372
+ * @param {string} name - Entry from readdir
373
+ * @returns {boolean}
374
+ */
375
+ function isAppSubdirSync(root, name) {
376
+ if (!name || name.startsWith('.')) return false;
377
+ const fullPath = path.join(root, name);
378
+ try {
379
+ const st = nodeFs().statSync(fullPath);
380
+ return Boolean(st && typeof st.isDirectory === 'function' && st.isDirectory());
381
+ } catch {
382
+ return false;
383
+ }
384
+ }
385
+
302
386
  /**
303
387
  * Lists app names (directories) under integration root. Excludes dot-prefixed entries.
304
388
  * Returns [] if root does not exist.
305
389
  * @returns {string[]} Sorted list of app directory names
306
390
  */
307
391
  function listIntegrationAppNames() {
392
+ const disk = nodeFs();
308
393
  const root = getIntegrationRoot();
309
- if (!fs.existsSync(root)) {
394
+ if (!disk.existsSync(root)) {
395
+ return [];
396
+ }
397
+ let rootStat;
398
+ try {
399
+ rootStat = disk.statSync(root);
400
+ } catch {
310
401
  return [];
311
402
  }
312
- const stat = fs.statSync(root);
313
- if (!stat || typeof stat.isDirectory !== 'function' || !stat.isDirectory()) {
403
+ if (!rootStat || typeof rootStat.isDirectory !== 'function' || !rootStat.isDirectory()) {
314
404
  return [];
315
405
  }
316
- const entries = fs.readdirSync(root);
317
- return entries
318
- .filter(name => !name.startsWith('.'))
319
- .filter(name => {
320
- const fullPath = path.join(root, name);
321
- return fs.statSync(fullPath).isDirectory();
322
- })
323
- .sort();
406
+ const entries = disk.readdirSync(root);
407
+ return entries.filter((name) => isAppSubdirSync(root, name)).sort();
324
408
  }
325
409
 
326
410
  /**
@@ -329,22 +413,22 @@ function listIntegrationAppNames() {
329
413
  * @returns {string[]} Sorted list of app directory names
330
414
  */
331
415
  function listBuilderAppNames() {
416
+ const disk = nodeFs();
332
417
  const root = getBuilderRoot();
333
- if (!fs.existsSync(root)) {
418
+ if (!disk.existsSync(root)) {
419
+ return [];
420
+ }
421
+ let rootStat;
422
+ try {
423
+ rootStat = disk.statSync(root);
424
+ } catch {
334
425
  return [];
335
426
  }
336
- const stat = fs.statSync(root);
337
- if (!stat || typeof stat.isDirectory !== 'function' || !stat.isDirectory()) {
427
+ if (!rootStat || typeof rootStat.isDirectory !== 'function' || !rootStat.isDirectory()) {
338
428
  return [];
339
429
  }
340
- const entries = fs.readdirSync(root);
341
- return entries
342
- .filter(name => !name.startsWith('.'))
343
- .filter(name => {
344
- const fullPath = path.join(root, name);
345
- return fs.statSync(fullPath).isDirectory();
346
- })
347
- .sort();
430
+ const entries = disk.readdirSync(root);
431
+ return entries.filter((name) => isAppSubdirSync(root, name)).sort();
348
432
  }
349
433
 
350
434
  /**
@@ -387,7 +471,7 @@ function getBuilderPath(appName) {
387
471
  ? process.env.AIFABRIX_BUILDER_DIR.trim()
388
472
  : null;
389
473
  if (builderRoot) {
390
- return path.join(builderRoot, appName);
474
+ return path.join(path.resolve(builderRoot), appName);
391
475
  }
392
476
  const base = getIntegrationBuilderBaseDir();
393
477
  return path.join(base, 'builder', appName);
@@ -518,7 +602,7 @@ async function detectAppType(appName, _options = {}) {
518
602
 
519
603
  /**
520
604
  * Resolve-specific app path: prefer integration + env.template only (env-only mode).
521
- * If integration/<appName>/env.template exists, use that directory without requiring application.yaml.
605
+ * If integration/<systemKey>/env.template exists, use that directory without requiring application.yaml.
522
606
  * Otherwise fall back to detectAppType (integration or builder with full config).
523
607
  *
524
608
  * @param {string} appName - Application name
@@ -538,7 +622,7 @@ async function getResolveAppPath(appName) {
538
622
  return { appPath: result.appPath, envOnly: false };
539
623
  }
540
624
 
541
- /** Resolve appKey when cwd is inside integration/<appKey>/. */
625
+ /** Resolve app folder name when cwd is inside integration/<systemKey>/. */
542
626
  function resolveIntegrationAppKeyFromCwd() {
543
627
  const integrationNorm = path.resolve(path.join(getIntegrationBuilderBaseDir(), 'integration'));
544
628
  const cwd = path.resolve(process.cwd());
@@ -548,7 +632,10 @@ function resolveIntegrationAppKeyFromCwd() {
548
632
 
549
633
  module.exports = {
550
634
  getAifabrixHome,
635
+ getAifabrixWork,
551
636
  getConfigDirForPaths,
637
+ getAifabrixSystemDir,
638
+ getPrimaryUserSecretsLocalPath,
552
639
  getApplicationsBaseDir,
553
640
  getDevDirectory,
554
641
  getAppPath,
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Single source of truth for resolving application port from application config.
5
5
  * Use getContainerPort for container/Docker/deployment/registration; use getLocalPort
6
- * for local .env and dev-id–adjusted host port.
6
+ * for manifest listen port (same as port; local host uses port+10+dev*100 in secrets-helpers).
7
7
  *
8
8
  * @fileoverview Port resolution from variables (port, build.containerPort)
9
9
  * @author AI Fabrix Team
@@ -14,6 +14,7 @@
14
14
 
15
15
  const fs = require('fs');
16
16
  const yaml = require('js-yaml');
17
+ const { DECLARATIVE_URL_INFRA_DEFAULTS } = require('./infra-env-defaults');
17
18
 
18
19
  /**
19
20
  * Resolve container port from variables object.
@@ -21,10 +22,10 @@ const yaml = require('js-yaml');
21
22
  * When containerPort is empty or missing, main port is used (e.g. keycloak 8082:8080 vs miso 3000:3000).
22
23
  *
23
24
  * @param {Object} variables - Parsed application config (or subset with build, port)
24
- * @param {number} [defaultPort=3000] - Default when neither build.containerPort nor port is set
25
+ * @param {number} [defaultPort] - Default when neither build.containerPort nor port is set (infra fallback)
25
26
  * @returns {number} Resolved container port
26
27
  */
27
- function getContainerPort(variables, defaultPort = 3000) {
28
+ function getContainerPort(variables, defaultPort = DECLARATIVE_URL_INFRA_DEFAULTS.manifestPortFallback) {
28
29
  const v = variables || {};
29
30
  const containerPort = v.build?.containerPort;
30
31
  const useMain = containerPort === undefined || containerPort === null ||
@@ -36,20 +37,13 @@ function getContainerPort(variables, defaultPort = 3000) {
36
37
  }
37
38
 
38
39
  /**
39
- * Resolve local (development) port from variables object.
40
- * Precedence: build.localPort (when positive integer) → port → defaultPort.
41
- * Used for env-copy, env-ports, getLocalPortFromPath, run compose host port.
42
- *
40
+ * Resolve base application listen port (manifest `port` only; build.localPort removed).
43
41
  * @param {Object} variables - Parsed application config
44
- * @param {number} [defaultPort=3000] - Default when neither build.localPort nor port is set
45
- * @returns {number} Resolved local port
42
+ * @param {number} [defaultPort] - Default when port is unset (infra fallback)
43
+ * @returns {number} Listen port
46
44
  */
47
- function getLocalPort(variables, defaultPort = 3000) {
45
+ function getLocalPort(variables, defaultPort = DECLARATIVE_URL_INFRA_DEFAULTS.manifestPortFallback) {
48
46
  const v = variables || {};
49
- const localPort = v.build?.localPort;
50
- if (typeof localPort === 'number' && localPort > 0) {
51
- return localPort;
52
- }
53
47
  return v.port ?? defaultPort;
54
48
  }
55
49
 
@@ -94,22 +88,15 @@ function getContainerPortFromPath(variablesPath) {
94
88
  }
95
89
 
96
90
  /**
97
- * Resolve local port from application config path.
98
- * Same rule as getLocalPort: build.localPort (when positive integer) → port.
99
- * Returns null when file is missing or neither localPort nor port is set.
100
- *
91
+ * Resolve manifest port from application config path.
101
92
  * @param {string} variablesPath - Path to application config
102
- * @returns {number|null} Local port or null
93
+ * @returns {number|null} Port or null
103
94
  */
104
95
  function getLocalPortFromPath(variablesPath) {
105
96
  const v = loadVariablesFromPath(variablesPath);
106
97
  if (!v) {
107
98
  return null;
108
99
  }
109
- const localPort = v.build?.localPort;
110
- if (typeof localPort === 'number' && localPort > 0) {
111
- return localPort;
112
- }
113
100
  const p = v.port;
114
101
  return (p !== undefined && p !== null) ? p : null;
115
102
  }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Adjust Redis DB index in resolved .env content when environment-scoped resources are effective.
3
+ *
4
+ * @fileoverview REDIS_DB and redis:// URL path segment (plan 117)
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ /**
12
+ * Set pathname /dbIndex for redis(s) URLs.
13
+ *
14
+ * @param {string} urlStr - redis:// or rediss:// URL
15
+ * @param {number} dbIndex - logical DB (0–15 typical)
16
+ * @returns {string}
17
+ */
18
+ function setRedisUrlDbIndex(urlStr, dbIndex) {
19
+ if (!urlStr || typeof urlStr !== 'string') return urlStr;
20
+ try {
21
+ const u = new URL(urlStr.trim());
22
+ if (u.protocol !== 'redis:' && u.protocol !== 'rediss:') {
23
+ return urlStr;
24
+ }
25
+ u.pathname = `/${dbIndex}`;
26
+ return u.toString();
27
+ } catch {
28
+ return urlStr;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Apply Redis DB index to REDIS_DB= and REDIS_URL= lines when effective.
34
+ *
35
+ * @param {string} content - .env text
36
+ * @param {number|null} dbIndex - from redisDbIndexForScopedRunEnv; skip if null
37
+ * @returns {string}
38
+ */
39
+ function applyRedisDbIndexToEnvContent(content, dbIndex) {
40
+ if (dbIndex === null || dbIndex === undefined || typeof content !== 'string') {
41
+ return content;
42
+ }
43
+ const lines = content.split('\n');
44
+ const out = lines.map((line) => {
45
+ const trimmed = line.trim();
46
+ if (!trimmed || trimmed.startsWith('#')) return line;
47
+ const eq = trimmed.indexOf('=');
48
+ if (eq <= 0) return line;
49
+ const key = trimmed.slice(0, eq).trim();
50
+ const value = trimmed.slice(eq + 1);
51
+ if (key === 'REDIS_DB') {
52
+ return `${key}=${dbIndex}`;
53
+ }
54
+ if (key === 'REDIS_URL' && value && !value.startsWith('kv://')) {
55
+ return `${key}=${setRedisUrlDbIndex(value, dbIndex)}`;
56
+ }
57
+ return line;
58
+ });
59
+ return out.join('\n');
60
+ }
61
+
62
+ module.exports = { applyRedisDbIndexToEnvContent, setRedisUrlDbIndex };