@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
@@ -1,18 +1,17 @@
1
1
  /**
2
2
  * Datasource repair helpers: align dimensions, metadataSchema, exposed, sync, testPayload
3
- * with fieldMappings.attributes as source of truth.
3
+ * with fieldMappings.attributes as source of truth (external-datasource schema v2.4+).
4
4
  *
5
5
  * @fileoverview Repair datasource files for external integration
6
6
  * @author AI Fabrix Team
7
- * @version 2.0.0
7
+ * @version 2.2.0
8
8
  */
9
9
 
10
10
  'use strict';
11
11
 
12
12
  const DEFAULT_SYNC = {
13
13
  mode: 'pull',
14
- batchSize: 500,
15
- maxParallelRequests: 5
14
+ batchSize: 500
16
15
  };
17
16
 
18
17
  const MINIMAL_METADATA_SCHEMA = {
@@ -20,6 +19,61 @@ const MINIMAL_METADATA_SCHEMA = {
20
19
  additionalProperties: true
21
20
  };
22
21
 
22
+ /** Plan 346 / v2.4.1 closed root for testPayload */
23
+ const TEST_PAYLOAD_TOP_LEVEL_ALLOW = new Set([
24
+ 'mode',
25
+ 'primaryKey',
26
+ 'scenarios',
27
+ 'fk',
28
+ 'actors',
29
+ 'payloadTemplate',
30
+ 'expectedResult'
31
+ ]);
32
+
33
+ /**
34
+ * @param {string|undefined} entityType - entityType from datasource
35
+ * @returns {boolean}
36
+ */
37
+ function isNoneEntityType(entityType) {
38
+ return entityType === 'none';
39
+ }
40
+
41
+ /**
42
+ * @param {string|undefined} entityType - entityType from datasource
43
+ * @returns {boolean}
44
+ */
45
+ function isStorageEntityType(entityType) {
46
+ return entityType === 'recordStorage' || entityType === 'documentStorage';
47
+ }
48
+
49
+ /**
50
+ * Ensures metadataSchema.properties.externalId for recordStorage/documentStorage (schema v2.4).
51
+ * @param {Object} parsed - Parsed datasource (mutated)
52
+ * @param {string[]} changes - Change log
53
+ * @returns {boolean} True if externalId was added or corrected
54
+ */
55
+ function ensureStorageExternalIdMetadataProperty(parsed, changes) {
56
+ if (!isStorageEntityType(parsed?.entityType)) return false;
57
+ if (!parsed.metadataSchema || typeof parsed.metadataSchema !== 'object') return false;
58
+ if (!parsed.metadataSchema.properties || typeof parsed.metadataSchema.properties !== 'object') {
59
+ parsed.metadataSchema.properties = {};
60
+ }
61
+ const props = parsed.metadataSchema.properties;
62
+ const ext = props.externalId;
63
+ const ok = ext
64
+ && typeof ext === 'object'
65
+ && ext.type === 'string'
66
+ && ext.index === true;
67
+ if (ok) return false;
68
+ props.externalId = {
69
+ ...(typeof ext === 'object' && ext !== null ? ext : {}),
70
+ type: 'string',
71
+ index: true
72
+ };
73
+ changes.push('Ensured metadataSchema.properties.externalId (type string, index true) for storage entityType');
74
+ return true;
75
+ }
76
+
23
77
  /**
24
78
  * Returns the set of attribute keys from fieldMappings.attributes.
25
79
  * @param {Object} parsed - Parsed datasource object
@@ -32,12 +86,24 @@ function getAttributeKeys(parsed) {
32
86
  }
33
87
 
34
88
  /**
35
- * Extracts paths from attribute expressions (e.g. {{ metadata.email }}).
89
+ * @param {string} path - Normalized path (raw. prefix already stripped)
90
+ * @param {string[]} paths
91
+ * @param {Set<string>} topLevelKeys
92
+ * @param {Set<string>} referencedSchemaPropertyNames
93
+ */
94
+ function accumulatePathSegments(path, paths, topLevelKeys, referencedSchemaPropertyNames) {
95
+ paths.push(path);
96
+ const segments = path.split('.');
97
+ const first = segments[0];
98
+ if (first) topLevelKeys.add(first);
99
+ if (first === 'metadata' && segments.length >= 2 && segments[1]) {
100
+ referencedSchemaPropertyNames.add(segments[1]);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Extracts paths from attribute expressions (e.g. {{ metadata.email }}, {{ raw.id }}).
36
106
  * Skips record_ref: expressions.
37
- * - topLevelKeys: first segment of each path (e.g. "metadata" from "metadata.id").
38
- * - referencedSchemaPropertyNames: for paths like "metadata.xxx" or "metadata.xxx.yyy", the name "xxx"
39
- * (the first property under metadata). Used to prune metadataSchema.properties so we only keep
40
- * properties that are referenced; we do not compare against "metadata" or the schema would be wiped.
41
107
  *
42
108
  * @param {Object} attributes - fieldMappings.attributes object
43
109
  * @returns {{ paths: string[], topLevelKeys: Set<string>, referencedSchemaPropertyNames: Set<string> }}
@@ -46,111 +112,246 @@ function parsePathsFromExpressions(attributes) {
46
112
  const paths = [];
47
113
  const topLevelKeys = new Set();
48
114
  const referencedSchemaPropertyNames = new Set();
49
- if (!attributes || typeof attributes !== 'object') return { paths, topLevelKeys, referencedSchemaPropertyNames };
115
+ if (!attributes || typeof attributes !== 'object') {
116
+ return { paths, topLevelKeys, referencedSchemaPropertyNames };
117
+ }
50
118
  for (const attr of Object.values(attributes)) {
51
119
  const expr = attr?.expression;
52
120
  if (typeof expr !== 'string') continue;
53
121
  if (/^\s*record_ref:/i.test(expr.trim())) continue;
54
122
  const match = expr.match(/\{\{\s*([^}]+)\s*\}\}/);
55
123
  if (!match) continue;
56
- const path = match[1].trim().split('|')[0].trim();
57
- if (path) {
58
- paths.push(path);
59
- const segments = path.split('.');
60
- const first = segments[0];
61
- if (first) topLevelKeys.add(first);
62
- if (first === 'metadata' && segments.length >= 2 && segments[1]) {
63
- referencedSchemaPropertyNames.add(segments[1]);
64
- }
65
- }
124
+ let path = match[1].trim().split('|')[0].trim();
125
+ if (path.startsWith('raw.')) path = path.slice(4);
126
+ if (path) accumulatePathSegments(path, paths, topLevelKeys, referencedSchemaPropertyNames);
66
127
  }
67
128
  return { paths, topLevelKeys, referencedSchemaPropertyNames };
68
129
  }
69
130
 
70
131
  /**
71
- * Removes dimension entries whose value is metadata.<attr> and attr is not in fieldMappings.attributes.
72
- * Mutates parsed.fieldMappings.dimensions.
73
- *
132
+ * @param {Object} binding - dimension binding
133
+ * @param {string} dimKey - dimension key
134
+ * @param {string[]} changes - Change log
135
+ * @returns {boolean}
136
+ */
137
+ function removeOperatorWithoutActor(binding, dimKey, changes) {
138
+ if (binding.operator && !binding.actor) {
139
+ delete binding.operator;
140
+ changes.push(`Removed 'operator' from dimension '${dimKey}' (no actor)`);
141
+ return true;
142
+ }
143
+ return false;
144
+ }
145
+
146
+ /**
147
+ * @param {string} dimKey - dimension key
148
+ * @param {Object} binding - dimension binding
149
+ * @param {string[]} changes - Change log
150
+ * @returns {boolean}
151
+ */
152
+ function repairFkDimensionBinding(dimKey, binding, changes) {
153
+ let updated = false;
154
+ if (binding.field !== undefined) {
155
+ delete binding.field;
156
+ changes.push(`Removed invalid 'field' from FK dimension '${dimKey}'`);
157
+ updated = true;
158
+ }
159
+ return removeOperatorWithoutActor(binding, dimKey, changes) || updated;
160
+ }
161
+
162
+ /**
163
+ * @param {string} dimKey - dimension key
164
+ * @param {Object} binding - dimension binding
165
+ * @param {string[]} changes - Change log
166
+ * @returns {boolean}
167
+ */
168
+ function repairLocalDimensionBinding(dimKey, binding, changes) {
169
+ let updated = false;
170
+ if (binding.via !== undefined) {
171
+ delete binding.via;
172
+ changes.push(`Removed invalid 'via' from local dimension '${dimKey}'`);
173
+ updated = true;
174
+ }
175
+ return removeOperatorWithoutActor(binding, dimKey, changes) || updated;
176
+ }
177
+
178
+ /**
179
+ * Normalizes dimension bindings: FK vs local invariants; strip operator without actor.
180
+ * @param {Object} parsed - Parsed datasource (mutated)
181
+ * @param {string[]} changes - Change log
182
+ * @returns {boolean}
183
+ */
184
+ function repairDimensionBindingShape(parsed, changes) {
185
+ const dims = parsed?.dimensions;
186
+ if (!dims || typeof dims !== 'object') return false;
187
+ let updated = false;
188
+ for (const [dimKey, binding] of Object.entries(dims)) {
189
+ if (!binding || typeof binding !== 'object') continue;
190
+ const t = binding.type;
191
+ if (t === 'fk') {
192
+ updated = repairFkDimensionBinding(dimKey, binding, changes) || updated;
193
+ } else if (t === 'local' || t === undefined) {
194
+ updated = repairLocalDimensionBinding(dimKey, binding, changes) || updated;
195
+ } else {
196
+ updated = removeOperatorWithoutActor(binding, dimKey, changes) || updated;
197
+ }
198
+ }
199
+ return updated;
200
+ }
201
+
202
+ /**
203
+ * Removes root local dimension bindings whose field is not in fieldMappings.attributes.
204
+ * Skips FK bindings.
74
205
  * @param {Object} parsed - Parsed datasource (mutated)
75
- * @param {string[]} changes - Array to append change descriptions to
206
+ * @param {string[]} changes - Change log
76
207
  * @returns {boolean} True if any dimension was removed
77
208
  */
78
- function repairDimensionsFromAttributes(parsed, changes) {
79
- const dims = parsed?.fieldMappings?.dimensions;
209
+ function repairRootDimensionsFromAttributes(parsed, changes) {
210
+ const dims = parsed?.dimensions;
80
211
  if (!dims || typeof dims !== 'object') return false;
81
212
  const attributeKeys = getAttributeKeys(parsed);
82
213
  let updated = false;
83
- for (const [dimKey, value] of Object.entries(dims)) {
84
- if (typeof value !== 'string') continue;
85
- if (!value.startsWith('metadata.')) continue;
86
- const attr = value.slice('metadata.'.length).trim();
87
- if (!attr || attributeKeys.has(attr)) continue;
214
+ for (const [dimKey, binding] of Object.entries(dims)) {
215
+ if (!binding || typeof binding !== 'object') continue;
216
+ if (binding.type === 'fk') continue;
217
+ if (binding.type !== undefined && binding.type !== 'local') continue;
218
+ const field = binding.field;
219
+ if (typeof field !== 'string') continue;
220
+ if (attributeKeys.has(field)) continue;
88
221
  delete dims[dimKey];
89
- changes.push(`Removed dimension '${dimKey}': ${value} not in fieldMappings.attributes`);
222
+ changes.push(`Removed root dimension '${dimKey}': field '${field}' not in fieldMappings.attributes`);
90
223
  updated = true;
91
224
  }
92
225
  return updated;
93
226
  }
94
227
 
95
228
  /**
96
- * Ensures metadataSchema exists (minimal stub if missing). If present, prunes top-level
97
- * properties not referenced by any attribute expression. Uses the first property name under
98
- * "metadata" in paths (e.g. metadata.id "id") so we do not remove schema properties that
99
- * are referenced. If no metadata.xxx paths exist, we do not prune (keep all properties).
229
+ * Adds metadataSchema property stubs for metadata.* paths referenced in attributes.
230
+ * @param {Object} parsed - Parsed datasource (mutated)
231
+ * @param {Set<string>} referencedSchemaPropertyNames - Property names under metadata
232
+ * @param {string[]} changes - Change log
233
+ * @returns {boolean}
234
+ */
235
+ function ensureMetadataSchemaPropertyStubs(parsed, referencedSchemaPropertyNames, changes) {
236
+ if (!referencedSchemaPropertyNames || referencedSchemaPropertyNames.size === 0) return false;
237
+ if (!parsed.metadataSchema || typeof parsed.metadataSchema !== 'object') return false;
238
+ if (!parsed.metadataSchema.properties || typeof parsed.metadataSchema.properties !== 'object') {
239
+ parsed.metadataSchema.properties = {};
240
+ }
241
+ const props = parsed.metadataSchema.properties;
242
+ const added = [];
243
+ for (const name of referencedSchemaPropertyNames) {
244
+ if (!name || props[name] !== undefined) continue;
245
+ props[name] = { type: 'string' };
246
+ added.push(name);
247
+ }
248
+ if (added.length === 0) return false;
249
+ changes.push(`Added metadataSchema.properties stubs for [${added.join(', ')}]`);
250
+ return true;
251
+ }
252
+
253
+ /**
254
+ * @param {Object} parsed - Parsed datasource (mutated)
255
+ * @param {Set<string>} referencedSchemaPropertyNames - metadata.* names from expressions
256
+ * @param {string[]} changes - Change log
257
+ * @returns {boolean}
258
+ */
259
+ function pruneMetadataSchemaPropertiesByAttributeRefs(parsed, referencedSchemaPropertyNames, changes) {
260
+ const props = parsed.metadataSchema?.properties;
261
+ if (
262
+ !props || typeof props !== 'object'
263
+ || referencedSchemaPropertyNames.size === 0
264
+ ) {
265
+ return false;
266
+ }
267
+ const toRemove = Object.keys(props).filter(k => {
268
+ if (referencedSchemaPropertyNames.has(k)) return false;
269
+ if (isStorageEntityType(parsed.entityType) && k === 'externalId') return false;
270
+ return true;
271
+ });
272
+ if (toRemove.length === 0) return false;
273
+ toRemove.forEach(k => delete props[k]);
274
+ changes.push(`Pruned metadataSchema.properties: removed [${toRemove.join(', ')}] (not referenced by attributes)`);
275
+ return true;
276
+ }
277
+
278
+ /**
279
+ * Ensures metadataSchema exists (minimal stub if missing). Prunes top-level properties not
280
+ * referenced by metadata.* paths in expressions (after stripping raw. prefix).
281
+ * Skipped entirely for entityType 'none'.
100
282
  *
101
283
  * @param {Object} parsed - Parsed datasource (mutated)
102
- * @param {string[]} changes - Array to append change descriptions to
284
+ * @param {string[]} changes - Change log
103
285
  * @returns {boolean} True if schema was added or pruned
104
286
  */
105
287
  function repairMetadataSchemaFromAttributes(parsed, changes) {
288
+ if (isNoneEntityType(parsed?.entityType)) {
289
+ return false;
290
+ }
106
291
  const { referencedSchemaPropertyNames } = parsePathsFromExpressions(parsed?.fieldMappings?.attributes ?? {});
292
+ let updated = false;
293
+
107
294
  if (!parsed.metadataSchema || typeof parsed.metadataSchema !== 'object') {
108
- parsed.metadataSchema = { ...MINIMAL_METADATA_SCHEMA };
295
+ parsed.metadataSchema = { ...MINIMAL_METADATA_SCHEMA, properties: {} };
109
296
  changes.push('Added minimal metadataSchema (was missing)');
110
- return true;
297
+ ensureMetadataSchemaPropertyStubs(parsed, referencedSchemaPropertyNames, changes);
298
+ updated = true;
299
+ } else {
300
+ if (pruneMetadataSchemaPropertiesByAttributeRefs(parsed, referencedSchemaPropertyNames, changes)) {
301
+ updated = true;
302
+ }
303
+ if (ensureMetadataSchemaPropertyStubs(parsed, referencedSchemaPropertyNames, changes)) {
304
+ updated = true;
305
+ }
111
306
  }
112
- const props = parsed.metadataSchema.properties;
113
- if (!props || typeof props !== 'object') return false;
114
- if (referencedSchemaPropertyNames.size === 0) return false;
115
- const toRemove = Object.keys(props).filter(k => !referencedSchemaPropertyNames.has(k));
116
- if (toRemove.length === 0) return false;
117
- toRemove.forEach(k => delete props[k]);
118
- changes.push(`Pruned metadataSchema.properties: removed [${toRemove.join(', ')}] (not referenced by attributes)`);
119
- return true;
307
+ if (ensureStorageExternalIdMetadataProperty(parsed, changes)) {
308
+ updated = true;
309
+ }
310
+ return updated;
120
311
  }
121
312
 
122
313
  /**
123
- * Sets exposed.attributes to the list of fieldMappings.attributes keys (sorted).
124
- * Only when options.expose is true; caller should gate.
314
+ * Sets exposed.schema from fieldMappings.attributes keys (metadata.<key> leaves). v2.4 canonical.
315
+ * Removes deprecated exposed.attributes when present so output matches schema.
125
316
  *
126
317
  * @param {Object} parsed - Parsed datasource (mutated)
127
- * @param {string[]} changes - Array to append change descriptions to
318
+ * @param {string[]} changes - Change log
128
319
  * @returns {boolean} True if exposed was updated
129
320
  */
130
321
  function repairExposeFromAttributes(parsed, changes) {
131
322
  const keys = Array.from(getAttributeKeys(parsed)).filter(Boolean).sort();
132
323
  if (keys.length === 0) return false;
133
324
  if (!parsed.exposed) parsed.exposed = {};
134
- const prev = parsed.exposed.attributes;
135
- const same = Array.isArray(prev) && prev.length === keys.length && prev.every((v, i) => v === keys[i]);
136
- if (same) return false;
137
- parsed.exposed.attributes = keys;
138
- changes.push(`Set exposed.attributes to [${keys.join(', ')}]`);
325
+ const schema = {};
326
+ keys.forEach(k => {
327
+ schema[k] = `metadata.${k}`;
328
+ });
329
+ const prev = parsed.exposed.schema;
330
+ const same = prev && typeof prev === 'object'
331
+ && keys.length === Object.keys(prev).length
332
+ && keys.every(k => prev[k] === schema[k]);
333
+ if (same && parsed.exposed.attributes === undefined) return false;
334
+ parsed.exposed.schema = schema;
335
+ if (parsed.exposed.attributes !== undefined) {
336
+ delete parsed.exposed.attributes;
337
+ }
338
+ changes.push(`Set exposed.schema for [${keys.join(', ')}]`);
139
339
  return true;
140
340
  }
141
341
 
142
342
  /**
143
- * Adds default sync section if missing or empty.
343
+ * Adds default sync section if missing or empty. Not applied for entityType 'none'.
144
344
  *
145
345
  * @param {Object} parsed - Parsed datasource (mutated)
146
- * @param {string[]} changes - Array to append change descriptions to
346
+ * @param {string[]} changes - Change log
147
347
  * @returns {boolean} True if sync was added
148
348
  */
149
349
  function repairSyncSection(parsed, changes) {
350
+ if (isNoneEntityType(parsed?.entityType)) return false;
150
351
  const sync = parsed.sync;
151
352
  if (sync && typeof sync === 'object' && Object.keys(sync).length > 0) return false;
152
353
  parsed.sync = { ...DEFAULT_SYNC };
153
- changes.push('Added default sync section (mode: pull, batchSize: 500, maxParallelRequests: 5)');
354
+ changes.push('Added default sync section (mode: pull, batchSize: 500)');
154
355
  return true;
155
356
  }
156
357
 
@@ -173,11 +374,32 @@ function setNested(obj, pathParts, value) {
173
374
  if (last) cur[last] = value;
174
375
  }
175
376
 
377
+ /**
378
+ * Removes unknown top-level keys from testPayload (v2.4.1 closed root).
379
+ * @param {Object} parsed - Parsed datasource (mutated)
380
+ * @param {string[]} changes - Change log
381
+ * @returns {boolean}
382
+ */
383
+ function sanitizeTestPayloadTopLevel(parsed, changes) {
384
+ const tp = parsed?.testPayload;
385
+ if (!tp || typeof tp !== 'object' || Array.isArray(tp)) return false;
386
+ const removed = [];
387
+ for (const key of Object.keys(tp)) {
388
+ if (!TEST_PAYLOAD_TOP_LEVEL_ALLOW.has(key)) {
389
+ delete tp[key];
390
+ removed.push(key);
391
+ }
392
+ }
393
+ if (removed.length === 0) return false;
394
+ changes.push(`Removed unknown testPayload keys: [${removed.join(', ')}]`);
395
+ return true;
396
+ }
397
+
176
398
  /**
177
399
  * Builds minimal payloadTemplate and expectedResult from attribute expression paths.
178
400
  *
179
401
  * @param {Object} parsed - Parsed datasource (mutated)
180
- * @param {string[]} changes - Array to append change descriptions to
402
+ * @param {string[]} changes - Change log
181
403
  * @returns {boolean} True if testPayload was added or updated
182
404
  */
183
405
  function repairTestPayload(parsed, changes) {
@@ -191,7 +413,10 @@ function repairTestPayload(parsed, changes) {
191
413
  expectedResult[key] = placeholder;
192
414
  const match = config?.expression?.match(/\{\{\s*([^}|]+)/);
193
415
  if (match) {
194
- const path = match[1].trim();
416
+ let path = match[1].trim();
417
+ if (path.startsWith('raw.')) {
418
+ path = path.slice(4);
419
+ }
195
420
  setNested(payloadTemplate, path.split('.'), placeholder);
196
421
  }
197
422
  }
@@ -203,7 +428,7 @@ function repairTestPayload(parsed, changes) {
203
428
  }
204
429
 
205
430
  /**
206
- * Runs all requested datasource repairs. Core: dimensions + metadataSchema. Optional: expose, sync, testPayload.
431
+ * Runs all requested datasource repairs.
207
432
  *
208
433
  * @param {Object} parsed - Parsed datasource object (mutated)
209
434
  * @param {Object} options - { expose?: boolean, sync?: boolean, test?: boolean }
@@ -213,23 +438,44 @@ function repairTestPayload(parsed, changes) {
213
438
  function repairDatasourceFile(parsed, options = {}, changes = []) {
214
439
  const out = Array.isArray(changes) ? changes : [];
215
440
  let updated = false;
216
- updated = repairDimensionsFromAttributes(parsed, out) || updated;
217
- updated = repairMetadataSchemaFromAttributes(parsed, out) || updated;
218
- if (options.expose) updated = repairExposeFromAttributes(parsed, out) || updated;
219
- if (options.sync) updated = repairSyncSection(parsed, out) || updated;
220
- if (options.test) updated = repairTestPayload(parsed, out) || updated;
441
+ const none = isNoneEntityType(parsed?.entityType);
442
+
443
+ if (!none) {
444
+ updated = repairDimensionBindingShape(parsed, out) || updated;
445
+ updated = repairRootDimensionsFromAttributes(parsed, out) || updated;
446
+ updated = repairMetadataSchemaFromAttributes(parsed, out) || updated;
447
+ }
448
+
449
+ if (options.expose) {
450
+ updated = repairExposeFromAttributes(parsed, out) || updated;
451
+ }
452
+
453
+ if (!none && options.sync) {
454
+ updated = repairSyncSection(parsed, out) || updated;
455
+ }
456
+
457
+ if (options.test) {
458
+ sanitizeTestPayloadTopLevel(parsed, out);
459
+ updated = repairTestPayload(parsed, out) || updated;
460
+ updated = sanitizeTestPayloadTopLevel(parsed, out) || updated;
461
+ }
462
+
221
463
  return { updated, changes: out };
222
464
  }
223
465
 
224
466
  module.exports = {
225
467
  getAttributeKeys,
226
468
  parsePathsFromExpressions,
227
- repairDimensionsFromAttributes,
469
+ repairDimensionBindingShape,
470
+ repairRootDimensionsFromAttributes,
228
471
  repairMetadataSchemaFromAttributes,
229
472
  repairExposeFromAttributes,
230
473
  repairSyncSection,
231
474
  repairTestPayload,
475
+ sanitizeTestPayloadTopLevel,
232
476
  repairDatasourceFile,
233
477
  DEFAULT_SYNC,
234
- MINIMAL_METADATA_SCHEMA
478
+ MINIMAL_METADATA_SCHEMA,
479
+ TEST_PAYLOAD_TOP_LEVEL_ALLOW,
480
+ isNoneEntityType
235
481
  };
@@ -15,7 +15,7 @@ const { generateExternalEnvTemplateContent } = require('../utils/external-env-te
15
15
 
16
16
  /**
17
17
  * Normalizes a keyvault config entry to canonical KV_* name and path-style value.
18
- * Path format: kv://<system-key>/<variable> (e.g. kv://microsoft-teams/clientId).
18
+ * Path format: kv://<systemKey>/<variable> (e.g. kv://microsoft-teams/clientId).
19
19
  * @param {Object} entry - Config entry with name, value, location
20
20
  * @param {string} prefix - KV prefix (e.g. MICROSOFT_TEAMS)
21
21
  * @param {string} systemKey - System key (e.g. microsoft-teams) for path namespace
@@ -66,7 +66,7 @@ function addFromConfiguration(effective, config, prefix, seenNames, systemKey) {
66
66
 
67
67
  /**
68
68
  * Adds effective config entries from authentication.security.
69
- * Path format: kv://<system-key>/<variable> (e.g. kv://microsoft-teams/clientId).
69
+ * Path format: kv://<systemKey>/<variable> (e.g. kv://microsoft-teams/clientId).
70
70
  * @param {Array} effective - Mutable result array
71
71
  * @param {Object} systemParsed - Parsed system config
72
72
  * @param {string} prefix - KV prefix
@@ -12,6 +12,7 @@
12
12
  /* eslint-disable max-lines -- Repair flow with auth, normalization, and steps */
13
13
 
14
14
  'use strict';
15
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
15
16
 
16
17
  const path = require('path');
17
18
  const fs = require('fs');
@@ -32,6 +33,22 @@ const { normalizeDatasourceKeysAndFilenames } = require('./repair-datasource-key
32
33
  /** Allowed authentication methods for repair --auth (matches external-system schema) */
33
34
  const ALLOWED_AUTH = ['oauth2', 'aad', 'apikey', 'basic', 'queryParam', 'oidc', 'hmac', 'none'];
34
35
 
36
+ /**
37
+ * README "Files" section should match integration config format on disk (YAML vs JSON).
38
+ * @param {string} appPath - Integration directory
39
+ * @returns {string} '.yaml' or '.json'
40
+ */
41
+ function inferExternalReadmeFileExt(appPath) {
42
+ try {
43
+ const configPath = resolveApplicationConfigPath(appPath);
44
+ const ext = path.extname(configPath).toLowerCase();
45
+ if (ext === '.yaml' || ext === '.yml') return '.yaml';
46
+ } catch {
47
+ /* use default */
48
+ }
49
+ return '.json';
50
+ }
51
+
35
52
  /**
36
53
  * Extracts roles and permissions from system object for rbac.yaml
37
54
  * @param {Object} system - Parsed system config
@@ -296,7 +313,7 @@ function createRbacFromSystemIfNeeded(appPath, systemFilePath, systemParsed, dry
296
313
  }
297
314
 
298
315
  /**
299
- * Runs datasource repair for each file (dimensions, metadataSchema, optional expose/sync/test).
316
+ * Runs datasource repair for each file (v2.4 root dimensions, metadataSchema; optional expose/sync/test).
300
317
  * @param {string} appPath - Application path
301
318
  * @param {string[]} datasourceFiles - Datasource file names
302
319
  * @param {Object} options - { expose?: boolean, sync?: boolean, test?: boolean }
@@ -359,7 +376,8 @@ async function regenerateReadmeIfRequested(appName, appPath, options, changes) {
359
376
  if (!fs.existsSync(deployJsonPath)) return false;
360
377
  try {
361
378
  const deployment = JSON.parse(fs.readFileSync(deployJsonPath, 'utf8'));
362
- const readmeContent = generateReadmeFromDeployJson(deployment);
379
+ const fileExt = inferExternalReadmeFileExt(appPath);
380
+ const readmeContent = generateReadmeFromDeployJson(deployment, { fileExt });
363
381
  const readmePath = path.join(appPath, 'README.md');
364
382
  if (!options.dryRun) {
365
383
  fs.writeFileSync(readmePath, readmeContent, { mode: 0o644, encoding: 'utf8' });
@@ -378,7 +396,7 @@ function persistChangesAndRegenerate(configPath, variables, appName, appPath, ch
378
396
  } else {
379
397
  writeConfigFile(configPath, variables);
380
398
  }
381
- logger.log(chalk.green(`✓ Updated ${path.basename(configPath)}`));
399
+ logger.log(formatSuccessLine(`Updated ${path.basename(configPath)}`));
382
400
  changes.forEach(c => logger.log(chalk.gray(` ${c}`)));
383
401
  return regenerateManifest(appName, appPath, changes);
384
402
  }
@@ -11,7 +11,7 @@ const chalk = require('chalk');
11
11
  const logger = require('../utils/logger');
12
12
  const { getAifabrixSecretsPath } = require('../core/config');
13
13
  const pathsUtil = require('../utils/paths');
14
- const { isRemoteSecretsUrl, getRemoteDevAuth } = require('../utils/remote-dev-auth');
14
+ const remoteDevAuth = require('../utils/remote-dev-auth');
15
15
  const devApi = require('../api/dev.api');
16
16
 
17
17
  const REMOTE_NOT_CONFIGURED_MSG = 'Remote server is not configured. Set remote-server and run "aifabrix dev init" first.';
@@ -38,8 +38,8 @@ function listKeysAndValuesFromFile(filePath) {
38
38
  }
39
39
  }
40
40
 
41
- const KEY_COL_WIDTH = 45;
42
- const TABLE_SEPARATOR_LENGTH = 120;
41
+ const KEY_COL_WIDTH = 55;
42
+ const TABLE_SEPARATOR_LENGTH = 130;
43
43
 
44
44
  /**
45
45
  * Log a list of secret keys and values as a table (header, column headers, separator, rows).
@@ -70,19 +70,26 @@ function logKeyValueList(emptyMessage, title, items) {
70
70
  * @returns {Promise<void>}
71
71
  */
72
72
  async function listSharedSecrets(generalSecretsPath) {
73
- if (isRemoteSecretsUrl(generalSecretsPath)) {
74
- const auth = await getRemoteDevAuth();
73
+ const target = await remoteDevAuth.resolveSharedSecretsEndpoint(generalSecretsPath);
74
+ if (remoteDevAuth.isRemoteSecretsUrl(target)) {
75
+ const auth = await remoteDevAuth.getRemoteDevAuth();
75
76
  if (!auth) {
76
77
  throw new Error(REMOTE_NOT_CONFIGURED_MSG);
77
78
  }
78
- const items = await devApi.listSecrets(auth.serverUrl, auth.clientCertPem);
79
+ const items = await devApi.listSecrets(
80
+ auth.serverUrl,
81
+ auth.clientCertPem,
82
+ auth.serverCaPem || undefined,
83
+ target
84
+ );
79
85
  const keyValues = items.map(i => ({ key: i.name || i.key || '', value: (i.value !== null && i.value !== undefined) ? String(i.value) : '' }));
80
- logKeyValueList('No shared secrets (remote).', 'Shared secrets (remote)', keyValues);
86
+ const { title, emptyMessage } = remoteDevAuth.getSharedSecretsRemoteListLabels(target);
87
+ logKeyValueList(emptyMessage, title, keyValues);
81
88
  return;
82
89
  }
83
- const resolvedPath = path.isAbsolute(generalSecretsPath)
84
- ? generalSecretsPath
85
- : path.resolve(process.cwd(), generalSecretsPath);
90
+ const resolvedPath = path.isAbsolute(target)
91
+ ? target
92
+ : path.resolve(process.cwd(), target);
86
93
  const keyValues = listKeysAndValuesFromFile(resolvedPath);
87
94
  const fileTitle = `Shared secrets (file: ${resolvedPath})`;
88
95
  logKeyValueList('No shared secrets in file.', fileTitle, keyValues);
@@ -90,9 +97,13 @@ async function listSharedSecrets(generalSecretsPath) {
90
97
 
91
98
  /** List user secrets and log key and value. */
92
99
  function listUserSecrets() {
93
- const userSecretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
100
+ const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
94
101
  const keyValues = listKeysAndValuesFromFile(userSecretsPath);
95
- logKeyValueList('No user secrets.', 'User secrets', keyValues);
102
+ logKeyValueList(
103
+ `No user secrets (file: ${userSecretsPath}). Use "aifabrix secret set <KEY> <value>" or resolve/up-infra to populate.`,
104
+ 'User secrets',
105
+ keyValues
106
+ );
96
107
  }
97
108
 
98
109
  /**