@aifabrix/builder 2.44.4 → 2.44.6

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 (214) hide show
  1. package/.cursor/rules/cli-layout.mdc +1 -1
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/.npmrc.token +1 -1
  4. package/README.md +15 -23
  5. package/integration/hubspot-test/README.md +2 -0
  6. package/integration/hubspot-test/test.js +5 -3
  7. package/jest.projects.js +68 -17
  8. package/lib/api/controller-health.api.js +49 -0
  9. package/lib/api/dimension-values.api.js +82 -0
  10. package/lib/api/dimensions.api.js +114 -0
  11. package/lib/api/external-systems.api.js +1 -0
  12. package/lib/api/integration-clients.api.js +168 -0
  13. package/lib/api/types/dimension-values.types.js +28 -0
  14. package/lib/api/types/dimensions.types.js +31 -0
  15. package/lib/api/types/integration-clients.types.js +45 -0
  16. package/lib/api/types/wizard.types.js +2 -1
  17. package/lib/api/validation-runner.js +46 -25
  18. package/lib/app/deploy-config.js +11 -1
  19. package/lib/app/deploy-status-display.js +3 -3
  20. package/lib/app/deploy.js +36 -14
  21. package/lib/app/display.js +15 -11
  22. package/lib/app/push.js +46 -23
  23. package/lib/app/register.js +1 -1
  24. package/lib/app/restart-display.js +95 -0
  25. package/lib/app/rotate-secret.js +1 -1
  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 +44 -12
  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 +99 -73
  32. package/lib/build/index.js +75 -45
  33. package/lib/cli/doctor-check.js +117 -0
  34. package/lib/cli/index.js +8 -2
  35. package/lib/cli/infra-guided.js +445 -0
  36. package/lib/cli/setup-app.help.js +1 -1
  37. package/lib/cli/setup-app.js +20 -2
  38. package/lib/cli/setup-app.test-commands.js +9 -5
  39. package/lib/cli/setup-auth.js +26 -0
  40. package/lib/cli/setup-dev-path-commands.js +50 -3
  41. package/lib/cli/setup-infra.js +138 -61
  42. package/lib/cli/setup-integration-client.js +182 -0
  43. package/lib/cli/setup-parameters.js +21 -2
  44. package/lib/cli/setup-platform.js +102 -0
  45. package/lib/cli/setup-secrets.js +18 -6
  46. package/lib/cli/setup-utility.js +97 -33
  47. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  48. package/lib/commands/datasource-capability-output.js +29 -0
  49. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  50. package/lib/commands/datasource-capability.js +411 -0
  51. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  52. package/lib/commands/datasource.js +53 -13
  53. package/lib/commands/dev-down.js +3 -3
  54. package/lib/commands/dev-infra-gate.js +32 -0
  55. package/lib/commands/dev-init.js +13 -7
  56. package/lib/commands/dimension-value.js +179 -0
  57. package/lib/commands/dimension.js +330 -0
  58. package/lib/commands/integration-client.js +430 -0
  59. package/lib/commands/login-device.js +65 -30
  60. package/lib/commands/login.js +21 -10
  61. package/lib/commands/parameters-validate.js +78 -13
  62. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  63. package/lib/commands/repair-datasource-keys.js +10 -5
  64. package/lib/commands/repair-datasource.js +19 -7
  65. package/lib/commands/repair-env-template.js +4 -1
  66. package/lib/commands/repair-openapi-sync.js +172 -0
  67. package/lib/commands/repair-persist.js +102 -0
  68. package/lib/commands/repair-rbac-extract.js +27 -0
  69. package/lib/commands/repair-rbac-migrate.js +186 -0
  70. package/lib/commands/repair-rbac.js +225 -19
  71. package/lib/commands/repair-system-alignment.js +246 -0
  72. package/lib/commands/repair-system-permissions.js +168 -0
  73. package/lib/commands/repair.js +120 -354
  74. package/lib/commands/secure.js +1 -1
  75. package/lib/commands/setup-modes.js +455 -0
  76. package/lib/commands/setup-prompts.js +388 -0
  77. package/lib/commands/setup.js +149 -0
  78. package/lib/commands/teardown.js +228 -0
  79. package/lib/commands/test-e2e-external.js +4 -3
  80. package/lib/commands/up-common.js +97 -12
  81. package/lib/commands/up-dataplane.js +33 -11
  82. package/lib/commands/up-miso.js +7 -11
  83. package/lib/commands/upload.js +109 -23
  84. package/lib/commands/wizard-core-helpers.js +14 -11
  85. package/lib/commands/wizard-core.js +58 -15
  86. package/lib/commands/wizard-dataplane.js +2 -2
  87. package/lib/commands/wizard-entity-selection.js +72 -14
  88. package/lib/commands/wizard-headless.js +7 -3
  89. package/lib/commands/wizard-helpers.js +13 -1
  90. package/lib/commands/wizard.js +210 -61
  91. package/lib/constants/infra-compose-service-names.js +40 -0
  92. package/lib/core/env-reader.js +16 -3
  93. package/lib/core/secrets-admin-env.js +101 -0
  94. package/lib/core/secrets-ensure-infra.js +34 -1
  95. package/lib/core/secrets-ensure.js +88 -66
  96. package/lib/core/secrets-env-content.js +432 -0
  97. package/lib/core/secrets-env-write.js +27 -1
  98. package/lib/core/secrets-load.js +248 -0
  99. package/lib/core/secrets-names.js +32 -0
  100. package/lib/core/secrets.js +17 -757
  101. package/lib/datasource/capability/basic-exposure.js +76 -0
  102. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  103. package/lib/datasource/capability/capability-key.js +34 -0
  104. package/lib/datasource/capability/capability-resolve.js +172 -0
  105. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  106. package/lib/datasource/capability/copy-operations.js +348 -0
  107. package/lib/datasource/capability/copy-test-payload.js +139 -0
  108. package/lib/datasource/capability/create-operations.js +235 -0
  109. package/lib/datasource/capability/dimension-operations.js +151 -0
  110. package/lib/datasource/capability/dimension-validate.js +219 -0
  111. package/lib/datasource/capability/json-pointer.js +31 -0
  112. package/lib/datasource/capability/reference-rewrite.js +51 -0
  113. package/lib/datasource/capability/relate-operations.js +325 -0
  114. package/lib/datasource/capability/relate-validate.js +219 -0
  115. package/lib/datasource/capability/remove-operations.js +275 -0
  116. package/lib/datasource/capability/run-capability-copy.js +152 -0
  117. package/lib/datasource/capability/run-capability-diff.js +135 -0
  118. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  119. package/lib/datasource/capability/run-capability-edit.js +377 -0
  120. package/lib/datasource/capability/run-capability-relate.js +193 -0
  121. package/lib/datasource/capability/run-capability-remove.js +105 -0
  122. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  123. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  124. package/lib/datasource/list.js +136 -23
  125. package/lib/datasource/log-viewer.js +2 -4
  126. package/lib/datasource/unified-validation-run.js +51 -16
  127. package/lib/datasource/validate.js +53 -1
  128. package/lib/deployment/deploy-poll-ui.js +60 -0
  129. package/lib/deployment/deployer-status.js +29 -3
  130. package/lib/deployment/deployer.js +48 -30
  131. package/lib/deployment/environment.js +7 -2
  132. package/lib/deployment/poll-interval.js +72 -0
  133. package/lib/deployment/push.js +11 -9
  134. package/lib/external-system/deploy.js +4 -1
  135. package/lib/external-system/download.js +61 -32
  136. package/lib/external-system/sync-deploy-manifest.js +33 -0
  137. package/lib/generator/wizard-prompts.js +7 -1
  138. package/lib/generator/wizard.js +34 -0
  139. package/lib/infrastructure/index.js +49 -19
  140. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  141. package/lib/parameters/infra-kv-discovery.js +29 -4
  142. package/lib/parameters/infra-parameter-catalog.js +6 -3
  143. package/lib/parameters/infra-parameter-validate.js +67 -19
  144. package/lib/resolvers/datasource-resolver.js +53 -0
  145. package/lib/resolvers/dimension-file.js +52 -0
  146. package/lib/resolvers/manifest-resolver.js +133 -0
  147. package/lib/schema/external-datasource.schema.json +183 -53
  148. package/lib/schema/external-system.schema.json +23 -10
  149. package/lib/schema/infra.parameter.yaml +26 -11
  150. package/lib/schema/wizard-config.schema.json +2 -2
  151. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  152. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  153. package/lib/utils/app-run-containers.js +2 -2
  154. package/lib/utils/bash-secret-env.js +59 -0
  155. package/lib/utils/cli-secrets-error-format.js +78 -0
  156. package/lib/utils/cli-test-layout-chalk.js +31 -9
  157. package/lib/utils/cli-utils.js +4 -36
  158. package/lib/utils/datasource-test-run-display.js +8 -0
  159. package/lib/utils/dev-hosts-helper.js +3 -2
  160. package/lib/utils/dev-init-ssh-merge.js +2 -1
  161. package/lib/utils/docker-build.js +17 -9
  162. package/lib/utils/docker-reload-mount.js +127 -0
  163. package/lib/utils/external-readme.js +117 -4
  164. package/lib/utils/external-system-local-test-tty.js +3 -2
  165. package/lib/utils/external-system-readiness-core.js +45 -12
  166. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  167. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  168. package/lib/utils/external-system-readiness-display.js +10 -1
  169. package/lib/utils/file-upload.js +40 -3
  170. package/lib/utils/health-check-db-init.js +107 -0
  171. package/lib/utils/health-check-public-warn.js +69 -0
  172. package/lib/utils/health-check-url.js +19 -4
  173. package/lib/utils/health-check.js +135 -105
  174. package/lib/utils/help-builder.js +5 -1
  175. package/lib/utils/image-name.js +34 -7
  176. package/lib/utils/integration-file-backup.js +74 -0
  177. package/lib/utils/mutagen-install.js +30 -3
  178. package/lib/utils/paths.js +108 -25
  179. package/lib/utils/postgres-wipe.js +212 -0
  180. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  181. package/lib/utils/remote-dev-auth.js +21 -5
  182. package/lib/utils/remote-docker-env.js +9 -1
  183. package/lib/utils/remote-secrets-loader.js +42 -3
  184. package/lib/utils/resolve-docker-image-ref.js +9 -3
  185. package/lib/utils/secrets-ancestor-paths.js +47 -0
  186. package/lib/utils/secrets-helpers.js +17 -10
  187. package/lib/utils/secrets-kv-refs.js +42 -0
  188. package/lib/utils/secrets-kv-scope.js +19 -2
  189. package/lib/utils/secrets-materialize-local.js +134 -0
  190. package/lib/utils/secrets-path.js +24 -10
  191. package/lib/utils/secrets-utils.js +2 -2
  192. package/lib/utils/system-builder-root.js +34 -0
  193. package/lib/utils/url-declarative-resolve-build.js +6 -1
  194. package/lib/utils/url-declarative-runtime-base-path.js +32 -0
  195. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  196. package/lib/utils/urls-local-registry.js +73 -20
  197. package/lib/utils/validation-poll-ui.js +81 -0
  198. package/lib/utils/validation-run-poll.js +29 -5
  199. package/lib/utils/with-muted-logger.js +53 -0
  200. package/package.json +1 -1
  201. package/templates/applications/dataplane/application.yaml +1 -1
  202. package/templates/applications/dataplane/rbac.yaml +10 -10
  203. package/templates/applications/keycloak/env.template +8 -6
  204. package/templates/applications/miso-controller/application.yaml +7 -0
  205. package/templates/applications/miso-controller/env.template +7 -7
  206. package/templates/applications/miso-controller/rbac.yaml +9 -9
  207. package/templates/external-system/README.md.hbs +89 -102
  208. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  209. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  210. package/.nyc_output/processinfo/index.json +0 -1
  211. package/lib/api/service-users.api.js +0 -150
  212. package/lib/api/types/service-users.types.js +0 -65
  213. package/lib/cli/setup-service-user.js +0 -187
  214. package/lib/commands/service-user.js +0 -429
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Helper functions for aligning external integration system/datasource config.
3
+ *
4
+ * Extracted from repair.js to keep file size under 500 lines.
5
+ *
6
+ * @fileoverview External integration repair helpers (system/datasource alignment)
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+ const fs = require('fs');
13
+ const chalk = require('chalk');
14
+
15
+ const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
16
+ const { systemKeyToKvPrefix, securityKeyToVar } = require('../utils/credential-secrets-env');
17
+ const { backupIntegrationFile } = require('../utils/integration-file-backup');
18
+ const logger = require('../utils/logger');
19
+
20
+ /**
21
+ * Loads first system file and returns parsed object with key
22
+ * @param {string} appPath - Application path
23
+ * @param {string} systemFileName - System file name
24
+ * @returns {Object} Parsed system config
25
+ */
26
+ function loadFirstSystemFile(appPath, systemFileName) {
27
+ const systemPath = path.join(appPath, systemFileName);
28
+ if (!fs.existsSync(systemPath)) {
29
+ throw new Error(`System file not found: ${systemPath}`);
30
+ }
31
+ return loadConfigFile(systemPath);
32
+ }
33
+
34
+ function resolveSystemContext(appPath, systemFiles) {
35
+ const systemFilePath = path.join(appPath, systemFiles[0]);
36
+ const systemParsed = loadFirstSystemFile(appPath, systemFiles[0]);
37
+ const systemKey = systemParsed.key ||
38
+ path.basename(systemFiles[0], path.extname(systemFiles[0])).replace(/-system$/, '');
39
+ return { systemFilePath, systemParsed, systemKey };
40
+ }
41
+
42
+ function ensureExternalIntegrationBlock(variables, systemFiles, datasourceFiles, changes) {
43
+ const extInt = variables.externalIntegration;
44
+ let updated = false;
45
+ if (!extInt) {
46
+ variables.externalIntegration = {
47
+ schemaBasePath: './',
48
+ systems: systemFiles,
49
+ dataSources: datasourceFiles,
50
+ autopublish: true,
51
+ version: '1.0.0'
52
+ };
53
+ changes.push('Created externalIntegration block from discovered files');
54
+ updated = true;
55
+ } else {
56
+ const prevSystems = extInt.systems || [];
57
+ const prevDataSources = extInt.dataSources || [];
58
+ if (JSON.stringify(prevSystems) !== JSON.stringify(systemFiles)) {
59
+ changes.push(`systems: [${prevSystems.join(', ')}] → [${systemFiles.join(', ')}]`);
60
+ extInt.systems = systemFiles;
61
+ updated = true;
62
+ }
63
+ if (JSON.stringify(prevDataSources) !== JSON.stringify(datasourceFiles)) {
64
+ changes.push(`dataSources: [${prevDataSources.join(', ')}] → [${datasourceFiles.join(', ')}]`);
65
+ extInt.dataSources = datasourceFiles;
66
+ updated = true;
67
+ }
68
+ }
69
+ return updated;
70
+ }
71
+
72
+ function alignAppKeyWithSystem(variables, systemKey, _systemParsed, changes) {
73
+ const appKey = variables.app?.key;
74
+ if (!appKey || appKey === systemKey) return false;
75
+ if (!variables.app) variables.app = {};
76
+ variables.app.key = systemKey;
77
+ changes.push(`app.key: ${appKey} → ${systemKey}`);
78
+ return true;
79
+ }
80
+
81
+ /**
82
+ * Aligns datasource systemKey values to match the system key.
83
+ * Updates each datasource file whose systemKey differs from the system key.
84
+ *
85
+ * @param {string} appPath - Application directory path
86
+ * @param {string[]} datasourceFiles - Datasource file names
87
+ * @param {string} systemKey - Expected system key
88
+ * @param {boolean} dryRun - If true, report changes but do not write
89
+ * @param {string[]} changes - Array to append change descriptions to
90
+ * @returns {boolean} True if any file was updated (or would be in dry-run)
91
+ */
92
+ function alignDatasourceSystemKeys(appPath, datasourceFiles, systemKey, dryRun, changes, backupCtx) {
93
+ if (!datasourceFiles || datasourceFiles.length === 0) return false;
94
+ let updated = false;
95
+ for (const datasourceFile of datasourceFiles) {
96
+ const datasourcePath = path.join(appPath, datasourceFile);
97
+ if (!fs.existsSync(datasourcePath)) continue;
98
+ const parsed = loadConfigFile(datasourcePath);
99
+ const old = parsed.systemKey;
100
+ if (old !== systemKey) {
101
+ parsed.systemKey = systemKey;
102
+ if (!dryRun) {
103
+ backupIntegrationFile(datasourcePath, backupCtx);
104
+ writeConfigFile(datasourcePath, parsed);
105
+ }
106
+ changes.push(`${datasourceFile}: systemKey ${old} → ${systemKey}`);
107
+ updated = true;
108
+ }
109
+ }
110
+ return updated;
111
+ }
112
+
113
+ /**
114
+ * Derives a datasource key from filename when the file has no key.
115
+ * - hubspot-datasource-company.json → hubspot-company
116
+ * - datasource-companies.json → {systemKey}-companies (e.g. test-hubspot-companies)
117
+ *
118
+ * @param {string} fileName - Datasource file name
119
+ * @param {string} systemKey - System key (e.g. test-hubspot), used for datasource-*.json style names
120
+ * @returns {string}
121
+ */
122
+ function deriveDatasourceKeyFromFileName(fileName, systemKey) {
123
+ const base = path.basename(fileName, path.extname(fileName));
124
+ if (/^datasource-/.test(base)) {
125
+ const suffix = base.slice('datasource-'.length);
126
+ return systemKey && typeof systemKey === 'string' ? `${systemKey}-${suffix}` : base;
127
+ }
128
+ return base.replace(/-datasource-/, '-');
129
+ }
130
+
131
+ /**
132
+ * Aligns system file dataSources array to match datasource keys from discovered files.
133
+ * The system file holds logical keys (not filenames): each key comes from that datasource
134
+ * file's "key" property, or is derived from the filename when missing (e.g. datasource-companies.json → {systemKey}-companies).
135
+ *
136
+ * @param {string} appPath - Application directory path
137
+ * @param {Object} systemParsed - Parsed system config (mutated)
138
+ * @param {string[]} datasourceFiles - Datasource file names
139
+ * @param {string} systemKey - System key for deriving key when missing
140
+ * @param {boolean} dryRun - If true, report changes but do not write
141
+ * @param {string[]} changes - Array to append change descriptions to
142
+ * @returns {boolean} True if dataSources was updated (or would be in dry-run)
143
+ */
144
+ function alignSystemFileDataSources(appPath, systemParsed, datasourceFiles, systemKey, _dryRun, changes) {
145
+ const keys = [];
146
+ for (const fileName of datasourceFiles) {
147
+ const filePath = path.join(appPath, fileName);
148
+ if (!fs.existsSync(filePath)) continue;
149
+ try {
150
+ const parsed = loadConfigFile(filePath);
151
+ const key = parsed && typeof parsed.key === 'string' && parsed.key.trim()
152
+ ? parsed.key.trim()
153
+ : deriveDatasourceKeyFromFileName(fileName, systemKey);
154
+ keys.push(key);
155
+ } catch (err) {
156
+ logger.log(chalk.yellow(`⚠ Could not load datasource file ${fileName}: ${err.message}; using derived key`));
157
+ keys.push(deriveDatasourceKeyFromFileName(fileName, systemKey));
158
+ }
159
+ }
160
+ const prev = Array.isArray(systemParsed.dataSources) ? [...systemParsed.dataSources] : [];
161
+ if (JSON.stringify(prev) === JSON.stringify(keys)) return false;
162
+ systemParsed.dataSources = keys;
163
+ changes.push(`dataSources: [${prev.join(', ') || '(none)'}] → [${keys.join(', ')}] (keys from each datasource file's "key" or filename)`);
164
+ return true;
165
+ }
166
+
167
+ /**
168
+ * Builds the set of auth variable names (UPPERCASE, no underscores) from authentication.variables and authentication.security.
169
+ * Used to detect configuration entries that belong only in the authentication section.
170
+ * Canonical keys per method are in lib/schema/external-system.schema.json $defs.authenticationVariablesByMethod
171
+ * (e.g. oauth2: variables baseUrl, tokenUrl, scope; security clientId, clientSecret).
172
+ *
173
+ * @param {Object} systemParsed - Parsed system config
174
+ * @param {string} _systemKey - System key (unused; for API consistency)
175
+ * @returns {Set<string>}
176
+ */
177
+ function buildAuthVarNames(systemParsed, _systemKey) {
178
+ const names = new Set();
179
+ const auth = systemParsed.authentication;
180
+ if (auth && typeof auth.variables === 'object') {
181
+ for (const k of Object.keys(auth.variables)) {
182
+ names.add(String(k).toUpperCase().replace(/_/g, ''));
183
+ }
184
+ }
185
+ if (auth && typeof auth.security === 'object') {
186
+ for (const k of Object.keys(auth.security)) {
187
+ names.add(securityKeyToVar(k));
188
+ }
189
+ }
190
+ return names;
191
+ }
192
+
193
+ /**
194
+ * Derives the normalized auth variable part from a config entry name (for matching against authNames).
195
+ * E.g. KV_HUBSPOT_CLIENTID → CLIENTID, BASEURL → BASEURL.
196
+ * @param {string} name - Entry name
197
+ * @param {string} systemKey - System key for KV_ prefix
198
+ * @returns {string}
199
+ */
200
+ function normalizedAuthPartFromConfigName(name, systemKey) {
201
+ const n = String(name).trim();
202
+ if (!n) return '';
203
+ const prefix = systemKeyToKvPrefix(systemKey);
204
+ const kvPrefix = `KV_${prefix}_`;
205
+ if (n.toUpperCase().startsWith(kvPrefix)) {
206
+ const rest = n.slice(kvPrefix.length);
207
+ return rest.toUpperCase().replace(/_/g, '');
208
+ }
209
+ // Old-style or other KV_* names: treat last segment as var (e.g. KV_HUBSPOT_CLIENTID → CLIENTID)
210
+ if (n.toUpperCase().startsWith('KV_')) {
211
+ const parts = n.split('_').filter(Boolean);
212
+ if (parts.length >= 2) return parts[parts.length - 1].toUpperCase();
213
+ }
214
+ return n.toUpperCase().replace(/_/g, '');
215
+ }
216
+
217
+ function removeAuthVarsFromConfiguration(systemParsed, systemKey, _dryRun, changes) {
218
+ const config = systemParsed.configuration;
219
+ if (!Array.isArray(config)) return false;
220
+ const authNames = buildAuthVarNames(systemParsed, systemKey);
221
+ if (authNames.size === 0) return false;
222
+ const removed = [];
223
+ const filtered = config.filter((entry) => {
224
+ if (!entry || !entry.name) return true;
225
+ const authPart = normalizedAuthPartFromConfigName(entry.name, systemKey);
226
+ if (authNames.has(authPart)) {
227
+ removed.push(entry.name);
228
+ return false;
229
+ }
230
+ return true;
231
+ });
232
+ if (removed.length === 0) return false;
233
+ systemParsed.configuration = filtered;
234
+ changes.push(`Removed authentication variable(s) from configuration: ${removed.join(', ')}`);
235
+ return true;
236
+ }
237
+
238
+ module.exports = {
239
+ alignAppKeyWithSystem,
240
+ alignDatasourceSystemKeys,
241
+ alignSystemFileDataSources,
242
+ ensureExternalIntegrationBlock,
243
+ removeAuthVarsFromConfiguration,
244
+ resolveSystemContext
245
+ };
246
+
@@ -0,0 +1,168 @@
1
+ /**
2
+ * @fileoverview Repair external system permissions[] for OpenAPI autoRbac operations
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const path = require('path');
10
+ const fs = require('fs');
11
+ const { loadConfigFile } = require('../utils/config-format');
12
+
13
+ function _safeString(v) {
14
+ return typeof v === 'string' && v.trim() ? v.trim() : '';
15
+ }
16
+
17
+ function _normalizeOperationKey(opKey) {
18
+ const s = _safeString(opKey);
19
+ if (!s) return '';
20
+ const withHyphens = s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
21
+ const cleaned = withHyphens.replace(/[^a-z0-9-]+/g, '-');
22
+ return cleaned.replace(/-+/g, '-').replace(/^-+|-+$/g, '');
23
+ }
24
+
25
+ function _ensureDefaultRoles(systemParsed, systemKey) {
26
+ if (Array.isArray(systemParsed.roles) && systemParsed.roles.length > 0) return false;
27
+ const key = _safeString(systemKey) || _safeString(systemParsed?.key) || 'system';
28
+ const display = _safeString(systemParsed?.displayName) || key;
29
+ systemParsed.roles = [
30
+ {
31
+ name: `${display} Admin`,
32
+ value: `${key}-admin`,
33
+ description: `Full access to all ${display} operations`,
34
+ groups: []
35
+ },
36
+ {
37
+ name: `${display} Reader`,
38
+ value: `${key}-reader`,
39
+ description: `Read-only access to all ${display} data`,
40
+ groups: []
41
+ }
42
+ ];
43
+ return true;
44
+ }
45
+
46
+ function _pickRoleValue(systemParsed, suffix, fallback) {
47
+ const roles = Array.isArray(systemParsed.roles) ? systemParsed.roles : [];
48
+ const vals = roles
49
+ .map((r) => r && typeof r === 'object' ? _safeString(r.value) : '')
50
+ .filter(Boolean);
51
+ return vals.find((v) => v.endsWith(suffix)) || vals[0] || fallback;
52
+ }
53
+
54
+ function _defaultRolesForPermissionName(permName, { adminValue, readerValue }) {
55
+ const roles = [];
56
+ const cap = typeof permName === 'string' && permName.includes(':') ? permName.split(':')[1] : '';
57
+ const isRead = cap === 'list' || cap === 'get';
58
+ if (isRead && readerValue) roles.push(readerValue);
59
+ if (adminValue) roles.push(adminValue);
60
+ return roles;
61
+ }
62
+
63
+ function _permissionNamesFromDatasource(parsed) {
64
+ const resourceType = _safeString(parsed && parsed.resourceType) || 'document';
65
+ const openapi = parsed && parsed.openapi;
66
+ if (!openapi || typeof openapi !== 'object') return [];
67
+ if (openapi.autoRbac !== true) return [];
68
+ const ops = openapi.operations;
69
+ if (!ops || typeof ops !== 'object' || Array.isArray(ops)) return [];
70
+ return Object.keys(ops)
71
+ .map((k) => _safeString(k))
72
+ .filter(Boolean)
73
+ .map((opKey) => _normalizeOperationKey(opKey) || opKey)
74
+ .filter(Boolean)
75
+ .map((opKey) => `${resourceType}:${opKey}`);
76
+ }
77
+
78
+ function _collectAutoRbacOperationPermissions(appPath, datasourceFiles) {
79
+ const permissionNames = new Set();
80
+ for (const fileName of datasourceFiles || []) {
81
+ const filePath = path.join(appPath, fileName);
82
+ if (!fs.existsSync(filePath)) continue;
83
+ let parsed;
84
+ try {
85
+ parsed = loadConfigFile(filePath);
86
+ } catch {
87
+ continue;
88
+ }
89
+ for (const perm of _permissionNamesFromDatasource(parsed)) {
90
+ permissionNames.add(perm);
91
+ }
92
+ }
93
+ return permissionNames;
94
+ }
95
+
96
+ function _getExistingPermissionNameSet(systemParsed) {
97
+ const asArray = Array.isArray(systemParsed.permissions) ? systemParsed.permissions : [];
98
+ return new Set(
99
+ asArray
100
+ .map((p) => (typeof p === 'string' ? p : p && typeof p === 'object' ? p.name : null))
101
+ .filter((n) => typeof n === 'string' && n.trim())
102
+ );
103
+ }
104
+
105
+ function _backfillEmptyPermissionRoles(systemParsed, dryRun, adminValue, readerValue, changes) {
106
+ if (dryRun) return false;
107
+ if (!Array.isArray(systemParsed.permissions)) return false;
108
+ let updated = false;
109
+ for (const p of systemParsed.permissions) {
110
+ if (!p || typeof p !== 'object') continue;
111
+ if (typeof p.name !== 'string' || !p.name.trim()) continue;
112
+ if (!Array.isArray(p.roles)) p.roles = [];
113
+ if (p.roles.length > 0) continue;
114
+ p.roles = _defaultRolesForPermissionName(p.name, { adminValue, readerValue });
115
+ if (p.roles.length > 0) {
116
+ changes.push(`RBAC: defaulted empty roles for system permission ${p.name}`);
117
+ updated = true;
118
+ }
119
+ }
120
+ return updated;
121
+ }
122
+
123
+ function _addMissingSystemPermission(systemParsed, perm, dryRun, adminValue, readerValue) {
124
+ if (dryRun) return;
125
+ if (!Array.isArray(systemParsed.permissions)) systemParsed.permissions = [];
126
+ systemParsed.permissions.push({
127
+ name: perm,
128
+ roles: _defaultRolesForPermissionName(perm, { adminValue, readerValue }),
129
+ description: `Permission: ${perm}`
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Ensure system.permissions includes autoRbac-derived permission names.
135
+ * @param {string} appPath
136
+ * @param {Object} systemParsed - parsed *-system.json/yaml (mutated)
137
+ * @param {string[]} datasourceFiles
138
+ * @param {boolean} dryRun
139
+ * @param {string[]} changes
140
+ * @returns {boolean} updated
141
+ */
142
+ function ensureSystemPermissionsForAutoRbac(appPath, systemParsed, datasourceFiles, dryRun, changes) {
143
+ if (!systemParsed || typeof systemParsed !== 'object') return false;
144
+ const desired = _collectAutoRbacOperationPermissions(appPath, datasourceFiles);
145
+ if (desired.size === 0) return false;
146
+
147
+ const rolesAdded = _ensureDefaultRoles(systemParsed, systemParsed?.key);
148
+ if (rolesAdded) {
149
+ changes.push('Added default Admin/Reader roles (required for RBAC permissions)');
150
+ }
151
+ const adminValue = _pickRoleValue(systemParsed, '-admin', 'admin');
152
+ const readerValue = _pickRoleValue(systemParsed, '-reader', null);
153
+
154
+ const existing = _getExistingPermissionNameSet(systemParsed);
155
+ let updated = _backfillEmptyPermissionRoles(systemParsed, dryRun, adminValue, readerValue, changes);
156
+
157
+ for (const perm of desired) {
158
+ if (existing.has(perm)) continue;
159
+ _addMissingSystemPermission(systemParsed, perm, dryRun, adminValue, readerValue);
160
+ changes.push(`Added system permission: ${perm} (autoRbac)`);
161
+ existing.add(perm);
162
+ updated = true;
163
+ }
164
+ return updated;
165
+ }
166
+
167
+ module.exports = { ensureSystemPermissionsForAutoRbac };
168
+