@aifabrix/builder 2.44.5 → 2.45.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 (249) hide show
  1. package/.cursor/rules/cli-layout.mdc +8 -4
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/README.md +15 -23
  4. package/integration/hubspot-test/README.md +2 -0
  5. package/integration/hubspot-test/test.js +5 -3
  6. package/jest.projects.js +104 -2
  7. package/lib/api/controller-health.api.js +49 -0
  8. package/lib/api/dimension-values.api.js +82 -0
  9. package/lib/api/dimensions.api.js +114 -0
  10. package/lib/api/external-systems.api.js +1 -0
  11. package/lib/api/integration-clients.api.js +168 -0
  12. package/lib/api/types/dimension-values.types.js +28 -0
  13. package/lib/api/types/dimensions.types.js +31 -0
  14. package/lib/api/types/integration-clients.types.js +45 -0
  15. package/lib/api/validation-runner.js +46 -25
  16. package/lib/app/deploy-config.js +11 -1
  17. package/lib/app/deploy-status-display.js +3 -3
  18. package/lib/app/deploy.js +36 -14
  19. package/lib/app/display.js +15 -11
  20. package/lib/app/helpers.js +3 -3
  21. package/lib/app/index.js +3 -3
  22. package/lib/app/push.js +46 -23
  23. package/lib/app/register.js +7 -6
  24. package/lib/app/restart-display.js +126 -0
  25. package/lib/app/rotate-secret.js +7 -6
  26. package/lib/app/run-container-start.js +12 -6
  27. package/lib/app/run-env-compose.js +30 -1
  28. package/lib/app/run-helpers.js +58 -19
  29. package/lib/app/run-reload-sync.js +148 -0
  30. package/lib/app/run-resolve-image.js +51 -1
  31. package/lib/app/run.js +148 -74
  32. package/lib/app/show-display.js +7 -0
  33. package/lib/app/show.js +87 -5
  34. package/lib/build/index.js +83 -49
  35. package/lib/cli/doctor-check.js +117 -0
  36. package/lib/cli/index.js +8 -2
  37. package/lib/cli/infra-guided.js +460 -0
  38. package/lib/cli/installation-log-command.js +73 -0
  39. package/lib/cli/setup-app.js +31 -3
  40. package/lib/cli/setup-auth.js +98 -27
  41. package/lib/cli/setup-dev-path-commands.js +50 -3
  42. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  43. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  44. package/lib/cli/setup-infra.js +132 -118
  45. package/lib/cli/setup-integration-client.js +182 -0
  46. package/lib/cli/setup-parameters.js +21 -2
  47. package/lib/cli/setup-platform.js +102 -0
  48. package/lib/cli/setup-secrets.js +18 -6
  49. package/lib/cli/setup-utility-resolve.js +132 -0
  50. package/lib/cli/setup-utility.js +143 -84
  51. package/lib/commands/app-logs.js +81 -33
  52. package/lib/commands/auth-config.js +116 -18
  53. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  54. package/lib/commands/datasource-capability-output.js +29 -0
  55. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  56. package/lib/commands/datasource-capability.js +411 -0
  57. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  58. package/lib/commands/datasource.js +53 -13
  59. package/lib/commands/dev-down.js +3 -3
  60. package/lib/commands/dev-infra-gate.js +32 -0
  61. package/lib/commands/dev-init.js +13 -7
  62. package/lib/commands/dimension-value.js +179 -0
  63. package/lib/commands/dimension.js +330 -0
  64. package/lib/commands/integration-client.js +430 -0
  65. package/lib/commands/login-device.js +65 -30
  66. package/lib/commands/login.js +21 -10
  67. package/lib/commands/parameters-validate.js +78 -13
  68. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  69. package/lib/commands/repair-datasource-keys.js +10 -5
  70. package/lib/commands/repair-datasource.js +19 -7
  71. package/lib/commands/repair-env-template.js +4 -1
  72. package/lib/commands/repair-openapi-sync.js +172 -0
  73. package/lib/commands/repair-persist.js +102 -0
  74. package/lib/commands/repair-rbac-extract.js +27 -0
  75. package/lib/commands/repair-rbac-migrate.js +186 -0
  76. package/lib/commands/repair-rbac.js +214 -31
  77. package/lib/commands/repair-system-alignment.js +246 -0
  78. package/lib/commands/repair-system-permissions.js +168 -0
  79. package/lib/commands/repair.js +120 -338
  80. package/lib/commands/secure.js +1 -1
  81. package/lib/commands/setup-modes.js +468 -0
  82. package/lib/commands/setup-prompts.js +421 -0
  83. package/lib/commands/setup.js +254 -0
  84. package/lib/commands/teardown.js +277 -0
  85. package/lib/commands/up-common.js +113 -19
  86. package/lib/commands/up-dataplane.js +44 -19
  87. package/lib/commands/up-miso.js +18 -18
  88. package/lib/commands/upload.js +111 -23
  89. package/lib/commands/wizard-core-helpers.js +14 -11
  90. package/lib/commands/wizard-core.js +6 -5
  91. package/lib/commands/wizard-dataplane.js +2 -2
  92. package/lib/commands/wizard-entity-selection.js +4 -3
  93. package/lib/commands/wizard-headless.js +2 -1
  94. package/lib/commands/wizard.js +2 -1
  95. package/lib/constants/infra-compose-service-names.js +40 -0
  96. package/lib/core/audit-logger.js +1 -34
  97. package/lib/core/config-admin-email.js +56 -0
  98. package/lib/core/config-normalize.js +60 -0
  99. package/lib/core/config-registered-controller-urls.js +54 -0
  100. package/lib/core/config.js +33 -50
  101. package/lib/core/env-reader.js +16 -3
  102. package/lib/core/secrets-admin-env.js +101 -0
  103. package/lib/core/secrets-ensure-infra.js +34 -1
  104. package/lib/core/secrets-ensure.js +88 -66
  105. package/lib/core/secrets-env-content.js +428 -0
  106. package/lib/core/secrets-env-declarative-expand.js +170 -0
  107. package/lib/core/secrets-env-write.js +29 -1
  108. package/lib/core/secrets-load.js +252 -0
  109. package/lib/core/secrets-names.js +32 -0
  110. package/lib/core/secrets.js +17 -757
  111. package/lib/datasource/capability/basic-exposure.js +76 -0
  112. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  113. package/lib/datasource/capability/capability-key.js +34 -0
  114. package/lib/datasource/capability/capability-resolve.js +172 -0
  115. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  116. package/lib/datasource/capability/copy-operations.js +348 -0
  117. package/lib/datasource/capability/copy-test-payload.js +139 -0
  118. package/lib/datasource/capability/create-operations.js +235 -0
  119. package/lib/datasource/capability/dimension-operations.js +151 -0
  120. package/lib/datasource/capability/dimension-validate.js +219 -0
  121. package/lib/datasource/capability/json-pointer.js +31 -0
  122. package/lib/datasource/capability/reference-rewrite.js +51 -0
  123. package/lib/datasource/capability/relate-operations.js +325 -0
  124. package/lib/datasource/capability/relate-validate.js +219 -0
  125. package/lib/datasource/capability/remove-operations.js +275 -0
  126. package/lib/datasource/capability/run-capability-copy.js +152 -0
  127. package/lib/datasource/capability/run-capability-diff.js +135 -0
  128. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  129. package/lib/datasource/capability/run-capability-edit.js +377 -0
  130. package/lib/datasource/capability/run-capability-relate.js +193 -0
  131. package/lib/datasource/capability/run-capability-remove.js +105 -0
  132. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  133. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  134. package/lib/datasource/list.js +136 -23
  135. package/lib/datasource/log-viewer.js +2 -4
  136. package/lib/datasource/unified-validation-run.js +51 -16
  137. package/lib/datasource/validate.js +53 -1
  138. package/lib/deployment/deploy-poll-ui.js +60 -0
  139. package/lib/deployment/deployer-status.js +29 -3
  140. package/lib/deployment/deployer.js +48 -30
  141. package/lib/deployment/environment.js +7 -2
  142. package/lib/deployment/poll-interval.js +72 -0
  143. package/lib/deployment/push.js +11 -9
  144. package/lib/external-system/deploy.js +9 -2
  145. package/lib/external-system/download.js +61 -32
  146. package/lib/external-system/sync-deploy-manifest.js +33 -0
  147. package/lib/infrastructure/index.js +49 -19
  148. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  149. package/lib/internal/node-fs.js +2 -0
  150. package/lib/parameters/infra-kv-discovery.js +29 -4
  151. package/lib/parameters/infra-parameter-catalog.js +6 -3
  152. package/lib/parameters/infra-parameter-validate.js +67 -19
  153. package/lib/resolvers/datasource-resolver.js +53 -0
  154. package/lib/resolvers/dimension-file.js +52 -0
  155. package/lib/resolvers/manifest-resolver.js +133 -0
  156. package/lib/schema/application-schema.json +4 -0
  157. package/lib/schema/external-datasource.schema.json +183 -53
  158. package/lib/schema/external-system.schema.json +23 -10
  159. package/lib/schema/infra.parameter.yaml +26 -1
  160. package/lib/schema/wizard-config.schema.json +1 -1
  161. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  162. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  163. package/lib/utils/app-config-resolver.js +24 -1
  164. package/lib/utils/app-run-containers.js +2 -2
  165. package/lib/utils/applications-config-defaults.js +206 -0
  166. package/lib/utils/auth-config-validator.js +2 -12
  167. package/lib/utils/bash-secret-env.js +59 -0
  168. package/lib/utils/cli-secrets-error-format.js +78 -0
  169. package/lib/utils/cli-test-layout-chalk.js +31 -9
  170. package/lib/utils/cli-utils.js +4 -36
  171. package/lib/utils/compose-generate-docker-compose.js +111 -6
  172. package/lib/utils/compose-generator.js +17 -8
  173. package/lib/utils/controller-url.js +50 -7
  174. package/lib/utils/datasource-test-run-display.js +8 -0
  175. package/lib/utils/dev-hosts-helper.js +3 -2
  176. package/lib/utils/dev-init-ssh-merge.js +2 -1
  177. package/lib/utils/docker-build.js +17 -9
  178. package/lib/utils/docker-reload-mount.js +127 -0
  179. package/lib/utils/env-copy.js +99 -14
  180. package/lib/utils/env-template.js +5 -1
  181. package/lib/utils/external-readme.js +71 -2
  182. package/lib/utils/external-system-local-test-tty.js +3 -2
  183. package/lib/utils/external-system-readiness-core.js +45 -12
  184. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  185. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  186. package/lib/utils/external-system-readiness-display.js +10 -1
  187. package/lib/utils/file-upload.js +40 -3
  188. package/lib/utils/health-check-db-init.js +107 -0
  189. package/lib/utils/health-check-public-warn.js +69 -0
  190. package/lib/utils/health-check-url.js +28 -10
  191. package/lib/utils/health-check.js +139 -107
  192. package/lib/utils/help-builder.js +5 -1
  193. package/lib/utils/image-name.js +34 -7
  194. package/lib/utils/infra-optional-service-flags.js +69 -0
  195. package/lib/utils/installation-log-core.js +282 -0
  196. package/lib/utils/installation-log-record.js +237 -0
  197. package/lib/utils/installation-log.js +123 -0
  198. package/lib/utils/integration-file-backup.js +74 -0
  199. package/lib/utils/log-redaction.js +105 -0
  200. package/lib/utils/manifest-location.js +164 -0
  201. package/lib/utils/manifest-source-emit.js +162 -0
  202. package/lib/utils/mutagen-install.js +30 -3
  203. package/lib/utils/paths.js +308 -76
  204. package/lib/utils/postgres-wipe.js +212 -0
  205. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  206. package/lib/utils/remote-dev-auth.js +21 -5
  207. package/lib/utils/remote-docker-env.js +9 -1
  208. package/lib/utils/remote-secrets-loader.js +49 -4
  209. package/lib/utils/resolve-docker-image-ref.js +9 -3
  210. package/lib/utils/run-cli-flags.js +29 -0
  211. package/lib/utils/secrets-ancestor-paths.js +47 -0
  212. package/lib/utils/secrets-canonical.js +10 -3
  213. package/lib/utils/secrets-helpers.js +17 -10
  214. package/lib/utils/secrets-kv-refs.js +42 -0
  215. package/lib/utils/secrets-kv-scope.js +19 -2
  216. package/lib/utils/secrets-materialize-local.js +134 -0
  217. package/lib/utils/secrets-path.js +26 -13
  218. package/lib/utils/secrets-utils.js +20 -10
  219. package/lib/utils/system-builder-root.js +42 -0
  220. package/lib/utils/url-declarative-public-base.js +80 -12
  221. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  222. package/lib/utils/url-declarative-resolve-build.js +24 -388
  223. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  224. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  225. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  226. package/lib/utils/url-declarative-resolve.js +47 -7
  227. package/lib/utils/url-declarative-runtime-base-path.js +52 -0
  228. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  229. package/lib/utils/urls-local-registry-scan.js +103 -0
  230. package/lib/utils/urls-local-registry.js +158 -76
  231. package/lib/utils/validation-poll-ui.js +81 -0
  232. package/lib/utils/validation-run-poll.js +29 -5
  233. package/lib/utils/with-muted-logger.js +53 -0
  234. package/package.json +3 -1
  235. package/templates/applications/dataplane/application.yaml +5 -1
  236. package/templates/applications/dataplane/rbac.yaml +10 -10
  237. package/templates/applications/keycloak/env.template +8 -6
  238. package/templates/applications/miso-controller/application.yaml +9 -0
  239. package/templates/applications/miso-controller/env.template +27 -29
  240. package/templates/applications/miso-controller/rbac.yaml +9 -9
  241. package/templates/external-system/README.md.hbs +83 -123
  242. package/.npmrc.token +0 -1
  243. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  244. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  245. package/.nyc_output/processinfo/index.json +0 -1
  246. package/lib/api/service-users.api.js +0 -150
  247. package/lib/api/types/service-users.types.js +0 -65
  248. package/lib/cli/setup-service-user.js +0 -187
  249. package/lib/commands/service-user.js +0 -429
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Tear down AI Fabrix infra Docker resources when compose.yaml / admin-secrets
3
+ * are missing from disk (e.g. after manual deletes). Matches compose naming from
4
+ * `templates/infra/compose.yaml.hbs` and `lib/infrastructure/compose.js`.
5
+ *
6
+ * @fileoverview Orphan Docker teardown for developer-scoped infra stacks
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const dockerUtils = require('../utils/docker');
12
+ const logger = require('../utils/logger');
13
+ const { execAsyncWithCwd } = require('./services');
14
+ const { getInfraProjectName } = require('./helpers');
15
+
16
+ /**
17
+ * Bridge network name for this developer (must match compose generator).
18
+ * @param {string|number} devId - Developer ID
19
+ * @returns {string}
20
+ */
21
+ function getInfraBridgeNetworkName(devId) {
22
+ const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
23
+ return idNum === 0 ? 'infra-aifabrix-network' : `infra-dev${devId}-aifabrix-network`;
24
+ }
25
+
26
+ /**
27
+ * Known infra service container names (postgres, redis, optional UIs).
28
+ * @param {string|number} devId - Developer ID
29
+ * @returns {string[]}
30
+ */
31
+ function getInfraServiceContainerNames(devId) {
32
+ const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
33
+ if (idNum === 0) {
34
+ return ['aifabrix-postgres', 'aifabrix-redis', 'aifabrix-pgadmin', 'aifabrix-redis-commander', 'aifabrix-traefik'];
35
+ }
36
+ return [
37
+ `aifabrix-dev${devId}-postgres`,
38
+ `aifabrix-dev${devId}-redis`,
39
+ `aifabrix-dev${devId}-pgadmin`,
40
+ `aifabrix-dev${devId}-redis-commander`,
41
+ `aifabrix-dev${devId}-traefik`
42
+ ];
43
+ }
44
+
45
+ /**
46
+ * Named volumes declared in infra compose (explicit `name:` entries).
47
+ * @param {string|number} devId - Developer ID
48
+ * @returns {string[]}
49
+ */
50
+ function getInfraNamedVolumeCandidates(devId) {
51
+ const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
52
+ if (idNum === 0) {
53
+ return ['infra_postgres_data', 'infra_redis_data', 'infra_pgadmin_data'];
54
+ }
55
+ return [
56
+ `infra_dev${devId}_postgres_data`,
57
+ `infra_dev${devId}_redis_data`,
58
+ `infra_dev${devId}_pgadmin_data`
59
+ ];
60
+ }
61
+
62
+ /**
63
+ * `docker compose down` using only the Compose project name (works when the
64
+ * compose file was removed but the project still exists in Docker).
65
+ *
66
+ * @param {string|number} devId - Developer ID
67
+ * @param {boolean} withVolumes - Pass `-v` to remove named volumes
68
+ * @returns {Promise<void>}
69
+ */
70
+ async function tryComposeProjectDown(devId, withVolumes) {
71
+ const composeCmd = await dockerUtils.getComposeCommand();
72
+ const projectName = getInfraProjectName(devId);
73
+ const volFlag = withVolumes ? ' -v' : '';
74
+ await execAsyncWithCwd(`${composeCmd} -p "${projectName}" down${volFlag}`);
75
+ }
76
+
77
+ /**
78
+ * @param {string} name - Container name
79
+ * @returns {Promise<void>}
80
+ */
81
+ async function dockerRmForceOne(name) {
82
+ try {
83
+ await execAsyncWithCwd(`docker rm -f "${name}"`);
84
+ logger.log(`Stopped and removed container: ${name}`);
85
+ } catch {
86
+ logger.log(`Container ${name} not running or already removed`);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * @param {string} vol - Volume name
92
+ * @returns {Promise<void>}
93
+ */
94
+ async function dockerVolumeRmForceOne(vol) {
95
+ try {
96
+ await execAsyncWithCwd(`docker volume rm -f "${vol}"`);
97
+ logger.log(`Removed volume: ${vol}`);
98
+ } catch {
99
+ logger.log(`Volume ${vol} not found or already removed`);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Remove containers on the developer infra bridge network (covers strays).
105
+ * @param {string} networkName - Docker network name
106
+ * @returns {Promise<void>}
107
+ */
108
+ async function removeContainersOnNetwork(networkName) {
109
+ let stdout = '';
110
+ try {
111
+ ({ stdout } = await execAsyncWithCwd(
112
+ `docker network inspect "${networkName}" --format '{{json .Containers}}'`
113
+ ));
114
+ } catch {
115
+ return;
116
+ }
117
+ const raw = String(stdout || '').trim();
118
+ if (!raw || raw === 'null' || raw === '{}') return;
119
+ let containers;
120
+ try {
121
+ containers = JSON.parse(raw);
122
+ } catch {
123
+ return;
124
+ }
125
+ for (const endpoint of Object.values(containers)) {
126
+ const name = endpoint && typeof endpoint.Name === 'string' ? endpoint.Name.replace(/^\//, '') : '';
127
+ if (name) await dockerRmForceOne(name);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Remove bridge network if present.
133
+ * @param {string} networkName - Docker network name
134
+ * @returns {Promise<void>}
135
+ */
136
+ async function removeNetworkIfPresent(networkName) {
137
+ try {
138
+ await execAsyncWithCwd(`docker network rm "${networkName}"`);
139
+ logger.log(`Removed network: ${networkName}`);
140
+ } catch {
141
+ logger.log(`Network ${networkName} not removed (still in use or already gone)`);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Last-resort teardown: remove known infra containers, optional volumes, then network.
147
+ *
148
+ * @param {string|number} devId - Developer ID
149
+ * @param {{ removeVolumes?: boolean }} [opts]
150
+ * @returns {Promise<void>}
151
+ */
152
+ async function stopInfraDockerStackOrphaned(devId, opts = {}) {
153
+ const removeVolumes = opts.removeVolumes !== false;
154
+ const networkName = getInfraBridgeNetworkName(devId);
155
+
156
+ for (const name of getInfraServiceContainerNames(devId)) {
157
+ await dockerRmForceOne(name);
158
+ }
159
+
160
+ await removeContainersOnNetwork(networkName);
161
+
162
+ if (removeVolumes) {
163
+ for (const vol of getInfraNamedVolumeCandidates(devId)) {
164
+ await dockerVolumeRmForceOne(vol);
165
+ }
166
+ }
167
+
168
+ await removeNetworkIfPresent(networkName);
169
+ }
170
+
171
+ module.exports = {
172
+ getInfraBridgeNetworkName,
173
+ getInfraServiceContainerNames,
174
+ getInfraNamedVolumeCandidates,
175
+ tryComposeProjectDown,
176
+ stopInfraDockerStackOrphaned
177
+ };
@@ -72,6 +72,7 @@ function buildBoundFs() {
72
72
  mkdirSync: snap.mkdirSync,
73
73
  readdirSync: snap.readdirSync,
74
74
  statSync: snap.statSync,
75
+ renameSync: snap.renameSync,
75
76
  watch: (...args) => live.watch(...args),
76
77
  promises: boundPromises
77
78
  };
@@ -83,6 +84,7 @@ function buildBoundFs() {
83
84
  mkdirSync: bindSync(rf, 'mkdirSync'),
84
85
  readdirSync: bindSync(rf, 'readdirSync'),
85
86
  statSync: bindSync(rf, 'statSync'),
87
+ renameSync: bindSync(rf, 'renameSync'),
86
88
  watch: (...args) => live.watch(...args),
87
89
  promises: boundPromises
88
90
  };
@@ -30,6 +30,23 @@ function extractKvKeysFromEnvContent(content) {
30
30
  return [...keys];
31
31
  }
32
32
 
33
+ /**
34
+ * List builder app directories only (no integration/* apps).
35
+ * Used by `parameters validate` so sample integrations do not require catalog entries.
36
+ * @param {object} pathsUtil - paths module
37
+ * @returns {{ appKey: string, dir: string }[]}
38
+ */
39
+ function listBuilderAppDirsForDiscovery(pathsUtil) {
40
+ const out = [];
41
+ for (const name of pathsUtil.listBuilderAppNames()) {
42
+ const dir = pathsUtil.getBuilderPath(name);
43
+ if (fsRealSync.existsSync(dir)) {
44
+ out.push({ appKey: name, dir });
45
+ }
46
+ }
47
+ return out;
48
+ }
49
+
33
50
  /**
34
51
  * List app directories for discovery (builder first, then integration-only apps).
35
52
  * @param {object} pathsUtil - paths module
@@ -99,13 +116,20 @@ function discoverKvKeysFromEnvTemplatesForHook(pathsUtil, hook, catalog) {
99
116
  }
100
117
 
101
118
  /**
102
- * Full key list for ensureInfraSecrets: catalog exact upInfra + standard miso DB + derived DB + template hooks.
103
- * @param {{ getEnsureOnKeys: Function, keyMatchesEnsureHook: Function }} catalog
119
+ * Keys `ensureInfraSecrets` (`up-infra`) may write locally:
120
+ * - {@link standardUpInfraEnsureKeys} in infra.parameter.yaml (explicit bootstrap + Redis/Postgres core)
121
+ * - `databases-{app}-{i}-*` derived from each workspace app `requires.databases`
122
+ * - `kv://` names from env.template lines whose catalog entry includes `upInfra`
123
+ *
124
+ * Does **not** union every `parameters[].ensureOn: upInfra` catalog entry—those would create dozens of
125
+ * unused Azure/BASH/app placeholders (often emptyString locally). Apps pull those via `resolve` / run when needed.
126
+ *
127
+ * @param {{ getStandardUpInfraBootstrapKeys: Function, keyMatchesEnsureHook: Function }} catalog
104
128
  * @param {object} pathsUtil - paths module
105
129
  * @returns {string[]}
106
130
  */
107
131
  function getAllInfraEnsureKeys(catalog, pathsUtil) {
108
- const set = new Set(catalog.getEnsureOnKeys('upInfra'));
132
+ const set = new Set();
109
133
  for (const k of catalog.getStandardUpInfraBootstrapKeys()) set.add(k);
110
134
  for (const k of deriveDatabaseKvKeysFromWorkspace(pathsUtil)) set.add(k);
111
135
  for (const k of discoverKvKeysFromEnvTemplatesForHook(pathsUtil, 'upInfra', catalog)) set.add(k);
@@ -117,5 +141,6 @@ module.exports = {
117
141
  deriveDatabaseKvKeysFromWorkspace,
118
142
  discoverKvKeysFromEnvTemplatesForHook,
119
143
  getAllInfraEnsureKeys,
120
- listAppDirsForDiscovery
144
+ listAppDirsForDiscovery,
145
+ listBuilderAppDirsForDiscovery
121
146
  };
@@ -133,7 +133,10 @@ function createCatalogApi(doc, exact, patterns) {
133
133
 
134
134
  function isKeyAllowedEmpty(key) {
135
135
  const entry = findEntryForKey(key);
136
- return Boolean(entry && entry.generator && entry.generator.type === 'emptyAllowed');
136
+ const gen = entry && entry.generator;
137
+ // emptyAllowed: e.g. Redis password when no requirepass.
138
+ // emptyString: optional user-supplied keys (OpenAI / Azure OpenAI) — absent resolves like ''.
139
+ return Boolean(gen && (gen.type === 'emptyAllowed' || gen.type === 'emptyString'));
137
140
  }
138
141
 
139
142
  function keyMatchesEnsureHook(key, hook) {
@@ -236,7 +239,7 @@ function readRelaxedUpInfraEnsureKeyList(catalogPath = DEFAULT_CATALOG_PATH) {
236
239
  }
237
240
 
238
241
  /**
239
- * YAML-only: keys whose generator type is emptyAllowed (for ensure backfill behavior).
242
+ * YAML-only: keys whose generator type is emptyAllowed or emptyString (optional / absent OK).
240
243
  *
241
244
  * @param {string} [catalogPath]
242
245
  * @returns {Set<string>|null}
@@ -254,7 +257,7 @@ function readRelaxedEmptyAllowedKeySet(catalogPath = DEFAULT_CATALOG_PATH) {
254
257
  typeof entry.key === 'string' &&
255
258
  entry.key.trim() &&
256
259
  entry.generator &&
257
- entry.generator.type === 'emptyAllowed'
260
+ (entry.generator.type === 'emptyAllowed' || entry.generator.type === 'emptyString')
258
261
  ) {
259
262
  set.add(entry.key.trim());
260
263
  }
@@ -6,38 +6,86 @@
6
6
 
7
7
  const { nodeFs } = require('../internal/node-fs');
8
8
  const path = require('path');
9
- const { listAppDirsForDiscovery, extractKvKeysFromEnvContent } = require('./infra-kv-discovery');
9
+ const {
10
+ listBuilderAppDirsForDiscovery,
11
+ extractKvKeysFromEnvContent
12
+ } = require('./infra-kv-discovery');
10
13
 
11
- /**
12
- * Validate that every kv:// key in workspace env.template files has catalog coverage.
13
- * @param {{ findEntryForKey: Function }} catalog - Loaded catalog API
14
- * @param {object} pathsUtil - paths module
15
- * @returns {{ valid: boolean, errors: string[] }}
16
- */
17
- function validateWorkspaceKvRefsAgainstCatalog(catalog, pathsUtil) {
18
- const errors = [];
14
+ function _scanEnvTemplate(fs, cwd, envPath) {
15
+ const rel = path.relative(cwd, envPath) || envPath;
16
+ try {
17
+ const content = fs.readFileSync(envPath, 'utf8');
18
+ return { rel, keys: extractKvKeysFromEnvContent(content), readError: null };
19
+ } catch (e) {
20
+ return { rel, keys: [], readError: e };
21
+ }
22
+ }
23
+
24
+ function _scanBuilderEnvTemplates(catalog, pathsUtil) {
19
25
  const cwd = process.cwd();
20
26
  const fs = nodeFs();
27
+ const errors = [];
28
+ const scannedApps = [];
29
+ const scannedEnvTemplates = [];
30
+ const kvKeysUnique = new Set();
21
31
 
22
- for (const { dir } of listAppDirsForDiscovery(pathsUtil)) {
32
+ for (const { dir } of listBuilderAppDirsForDiscovery(pathsUtil)) {
33
+ scannedApps.push(path.relative(cwd, dir) || dir);
23
34
  const envPath = path.join(dir, 'env.template');
24
35
  if (!fs.existsSync(envPath)) continue;
25
- let content;
26
- try {
27
- content = fs.readFileSync(envPath, 'utf8');
28
- } catch (e) {
29
- errors.push(`Could not read ${envPath}: ${e.message}`);
36
+ const scan = _scanEnvTemplate(fs, cwd, envPath);
37
+ scannedEnvTemplates.push(scan.rel);
38
+ if (scan.readError) {
39
+ errors.push({
40
+ key: '__read_error__',
41
+ envTemplatePath: scan.rel,
42
+ message: scan.readError.message
43
+ });
30
44
  continue;
31
45
  }
32
- const rel = path.relative(cwd, envPath) || envPath;
33
- for (const k of extractKvKeysFromEnvContent(content)) {
46
+ for (const k of scan.keys) {
47
+ kvKeysUnique.add(k);
34
48
  if (!catalog.findEntryForKey(k)) {
35
- errors.push(`Unknown kv:// key "${k}" in ${rel} — extend lib/schema/infra.parameter.yaml`);
49
+ errors.push({ key: k, envTemplatePath: scan.rel });
36
50
  }
37
51
  }
38
52
  }
39
53
 
40
- return { valid: errors.length === 0, errors };
54
+ return { errors, scannedApps, scannedEnvTemplates, kvKeysUnique };
55
+ }
56
+
57
+ /**
58
+ * Validate that every kv:// key under builder app env.template files has catalog coverage.
59
+ * Integration apps under integration/ are not scanned (they often use ad-hoc kv keys).
60
+ * @param {{ findEntryForKey: Function }} catalog - Loaded catalog API
61
+ * @param {object} pathsUtil - paths module
62
+ * @returns {{
63
+ * valid: boolean,
64
+ * errors: { key: string, envTemplatePath: string, message?: string }[],
65
+ * summary: {
66
+ * scannedApps: string[],
67
+ * scannedEnvTemplates: string[],
68
+ * kvKeysUnique: string[],
69
+ * kvKeysCount: number
70
+ * }
71
+ * }}
72
+ */
73
+ function validateWorkspaceKvRefsAgainstCatalog(catalog, pathsUtil) {
74
+ const { errors, scannedApps, scannedEnvTemplates, kvKeysUnique } = _scanBuilderEnvTemplates(
75
+ catalog,
76
+ pathsUtil
77
+ );
78
+
79
+ return {
80
+ valid: errors.length === 0,
81
+ errors,
82
+ summary: {
83
+ scannedApps: scannedApps.sort(),
84
+ scannedEnvTemplates: scannedEnvTemplates.sort(),
85
+ kvKeysUnique: [...kvKeysUnique].sort(),
86
+ kvKeysCount: kvKeysUnique.size
87
+ }
88
+ };
41
89
  }
42
90
 
43
91
  /**
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Shared datasource resolver utilities.
3
+ *
4
+ * Intentionally CLI-agnostic (no commander/options), so it can be reused by commands and tests.
5
+ *
6
+ * @fileoverview Datasource path + JSON loading helpers
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const { resolveValidateInputPath, resolvePathFromIntegrationDatasourceKey } = require('../datasource/validate');
15
+
16
+ /**
17
+ * @param {string} fileOrKey
18
+ * @returns {string} Absolute path to datasource JSON file
19
+ */
20
+ function resolveDatasourceJsonPath(fileOrKey) {
21
+ return resolveValidateInputPath(String(fileOrKey || '').trim());
22
+ }
23
+
24
+ /**
25
+ * Attempt to resolve a datasource key under integration/<app>/ without throwing.
26
+ *
27
+ * @param {string} datasourceKey
28
+ * @returns {{ ok: true, path: string } | { ok: false, error: string }}
29
+ */
30
+ function tryResolveDatasourceKeyToLocalPath(datasourceKey) {
31
+ try {
32
+ const p = resolvePathFromIntegrationDatasourceKey(String(datasourceKey || '').trim());
33
+ return { ok: true, path: p };
34
+ } catch (e) {
35
+ return { ok: false, error: e?.message || String(e) };
36
+ }
37
+ }
38
+
39
+ /**
40
+ * @param {string} jsonPath
41
+ * @returns {any}
42
+ */
43
+ function readJsonFile(jsonPath) {
44
+ const raw = fs.readFileSync(jsonPath, 'utf8');
45
+ return JSON.parse(raw);
46
+ }
47
+
48
+ module.exports = {
49
+ resolveDatasourceJsonPath,
50
+ tryResolveDatasourceKeyToLocalPath,
51
+ readJsonFile
52
+ };
53
+
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Dimension file helpers for `aifabrix dimension create --file`.
3
+ *
4
+ * @fileoverview Dimension file parsing
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ /**
15
+ * @typedef {Object} DimensionCreateInput
16
+ * @property {string} key
17
+ * @property {string} displayName
18
+ * @property {string} [description]
19
+ * @property {'string'|'number'|'boolean'} dataType
20
+ * @property {boolean} [isRequired]
21
+ * @property {Array<{ value: string, displayName?: string, description?: string }>} [values]
22
+ */
23
+
24
+ /**
25
+ * @param {string} filePath
26
+ * @returns {DimensionCreateInput}
27
+ */
28
+ function readDimensionCreateFile(filePath) {
29
+ const p = path.resolve(String(filePath || '').trim());
30
+ if (!p) {
31
+ throw new Error('--file is required');
32
+ }
33
+ if (!fs.existsSync(p)) {
34
+ throw new Error(`File not found: ${p}`);
35
+ }
36
+ const raw = fs.readFileSync(p, 'utf8');
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(raw);
40
+ } catch (e) {
41
+ throw new Error(`Invalid JSON in ${p}: ${e.message}`);
42
+ }
43
+ if (!parsed || typeof parsed !== 'object') {
44
+ throw new Error(`Dimension file must be a JSON object: ${p}`);
45
+ }
46
+ return parsed;
47
+ }
48
+
49
+ module.exports = {
50
+ readDimensionCreateFile
51
+ };
52
+
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Shared remote manifest lookup helpers (dataplane).
3
+ *
4
+ * This module does not decide *when* to fetch; callers can treat failures as
5
+ * \"not authenticated\" or \"remote unavailable\" depending on their UX needs.
6
+ *
7
+ * @fileoverview Remote manifest fetch helpers
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const { resolveDataplaneAndAuth } = require('../commands/upload');
15
+ const { getExternalSystemConfig } = require('../api/external-systems.api');
16
+ const { getDatasourceConfig } = require('../api/datasources-core.api');
17
+
18
+ /**
19
+ * @param {any} maybeEnvelope
20
+ * @returns {{ isFailure: boolean, status: number, errorType: string|undefined, message: string|undefined }}
21
+ */
22
+ function unwrapApiFailure(maybeEnvelope) {
23
+ if (!maybeEnvelope || typeof maybeEnvelope !== 'object') {
24
+ return { isFailure: false, status: 0, errorType: undefined, message: undefined };
25
+ }
26
+
27
+ if (maybeEnvelope.success !== false) {
28
+ return { isFailure: false, status: 0, errorType: undefined, message: undefined };
29
+ }
30
+
31
+ return {
32
+ isFailure: true,
33
+ status: Number(maybeEnvelope.status) || 0,
34
+ errorType: maybeEnvelope.errorType,
35
+ message: maybeEnvelope.error || maybeEnvelope.formattedError || maybeEnvelope.message
36
+ };
37
+ }
38
+
39
+ /**
40
+ * @param {{ status: number, errorType: string|undefined, message: string|undefined }} failure
41
+ * @returns {string|undefined}
42
+ */
43
+ function failureCodeFrom(failure) {
44
+ if (failure.errorType === 'notfound' || failure.status === 404) return 'not_found';
45
+ if (/Authentication required\./i.test(String(failure.message || ''))) return 'not_authenticated';
46
+ return undefined;
47
+ }
48
+
49
+ /**
50
+ * Fetch running manifest for one external system key (includes dataSources[]).
51
+ *
52
+ * @param {string} systemKey
53
+ * @param {{ silent?: boolean }} [opts]
54
+ * @returns {Promise<{ ok: true, dataplaneUrl: string, authConfig: Object, manifest: any } | { ok: false, error: string, code?: string }>}
55
+ */
56
+ async function tryFetchRunningManifest(systemKey, opts = {}) {
57
+ try {
58
+ const { dataplaneUrl, authConfig } = await resolveDataplaneAndAuth(String(systemKey || '').trim(), {
59
+ silent: opts.silent === true
60
+ });
61
+ const res = await getExternalSystemConfig(dataplaneUrl, systemKey, authConfig);
62
+ const manifest = res?.data?.data ?? res?.data ?? res;
63
+ return { ok: true, dataplaneUrl, authConfig, manifest };
64
+ } catch (e) {
65
+ const msg = e?.message || String(e);
66
+ const code = /Authentication required\./i.test(msg) ? 'not_authenticated' : undefined;
67
+ return { ok: false, error: msg, code };
68
+ }
69
+ }
70
+
71
+ /**
72
+ * @param {any} runningManifest
73
+ * @param {string} datasourceKey
74
+ * @returns {any|null}
75
+ */
76
+ function findDatasourceInRunningManifest(runningManifest, datasourceKey) {
77
+ const dsKey = String(datasourceKey || '').trim();
78
+ const dataSources = runningManifest?.dataSources;
79
+ if (!Array.isArray(dataSources)) return null;
80
+ return dataSources.find((d) => d && d.key === dsKey) || null;
81
+ }
82
+
83
+ module.exports = {
84
+ tryFetchRunningManifest,
85
+ findDatasourceInRunningManifest,
86
+ /**
87
+ * Fetch one datasource config by key/id using dataplane auth resolved from a systemKey.
88
+ *
89
+ * This is useful for cross-system FK target validation because datasource keys are globally unique.
90
+ *
91
+ * @param {string} systemKey - any system key usable for auth resolution
92
+ * @param {string} datasourceKey - datasource key to fetch config for
93
+ * @param {{ silent?: boolean }} [opts]
94
+ * @returns {Promise<{ ok: true, datasourceConfig: any } | { ok: false, error: string, code?: string }>}
95
+ */
96
+ async tryFetchDatasourceConfig(systemKey, datasourceKey, opts = {}) {
97
+ try {
98
+ const trimmedSystemKey = String(systemKey || '').trim();
99
+ const trimmedDatasourceKey = String(datasourceKey || '').trim();
100
+
101
+ const { dataplaneUrl, authConfig } = await resolveDataplaneAndAuth(trimmedSystemKey, {
102
+ silent: opts.silent === true
103
+ });
104
+
105
+ const res = await getDatasourceConfig(dataplaneUrl, trimmedDatasourceKey, authConfig);
106
+ const outerFailure = unwrapApiFailure(res);
107
+ if (outerFailure.isFailure) {
108
+ return {
109
+ ok: false,
110
+ error: outerFailure.message || `Failed to fetch datasource config: ${trimmedDatasourceKey}`,
111
+ code: failureCodeFrom(outerFailure)
112
+ };
113
+ }
114
+
115
+ const cfg = res?.data?.data ?? res?.data ?? res;
116
+ const innerFailure = unwrapApiFailure(cfg);
117
+ if (innerFailure.isFailure) {
118
+ return {
119
+ ok: false,
120
+ error: innerFailure.message || `Failed to fetch datasource config: ${trimmedDatasourceKey}`,
121
+ code: failureCodeFrom(innerFailure)
122
+ };
123
+ }
124
+
125
+ return { ok: true, datasourceConfig: cfg };
126
+ } catch (e) {
127
+ const msg = e?.message || String(e);
128
+ const code = /Authentication required\./i.test(msg) ? 'not_authenticated' : undefined;
129
+ return { ok: false, error: msg, code };
130
+ }
131
+ }
132
+ };
133
+
@@ -534,6 +534,10 @@
534
534
  "pattern": "^[a-zA-Z.$\\{\\}_-]+$",
535
535
  "default": true
536
536
  },
537
+ "internalDockerUseOriginOnly": {
538
+ "type": "boolean",
539
+ "description": "When true, declarative url://*-internal full URLs in Docker profile use http://service:containerPort only (omit frontDoorRouting.pattern). Use when the ingress path (e.g. /miso) is not part of in-container routes. Omit or false to keep pattern on internal URLs (e.g. Keycloak /auth)."
540
+ },
537
541
  "certStore": {
538
542
  "type": "string",
539
543
  "description": "Certificate store name for wildcard certificates. Optional - only needed when using a pre-configured certificate store in Traefik.",