@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
@@ -9,292 +9,38 @@
9
9
  * @author AI Fabrix Team
10
10
  * @version 2.0.0
11
11
  */
12
- /* eslint-disable max-lines -- Repair flow with auth, normalization, and steps */
13
12
 
14
13
  'use strict';
15
- const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
16
14
 
17
15
  const path = require('path');
18
16
  const fs = require('fs');
19
17
  const chalk = require('chalk');
20
- const { detectAppType, getDeployJsonPath } = require('../utils/paths');
18
+ const { detectAppType } = require('../utils/paths');
21
19
  const { resolveApplicationConfigPath, resolveRbacPath } = require('../utils/app-config-resolver');
22
- const { loadConfigFile, writeConfigFile, writeYamlPreservingComments, isYamlPath } = require('../utils/config-format');
23
- const { systemKeyToKvPrefix, securityKeyToVar } = require('../utils/credential-secrets-env');
20
+ const { loadConfigFile, writeConfigFile, isYamlPath } = require('../utils/config-format');
24
21
  const logger = require('../utils/logger');
25
- const generator = require('../generator');
26
22
  const { repairEnvTemplate, normalizeSystemFileAuthAndConfig } = require('./repair-env-template');
27
- const { generateReadmeFromDeployJson } = require('../generator/split-readme');
28
23
  const { repairDatasourceFile } = require('./repair-datasource');
29
- const { mergeRbacFromDatasources } = require('./repair-rbac');
24
+ const { mergeRbacFromDatasources, extractRbacFromSystem, migrateSystemRbacIntoRbacFile } = require('./repair-rbac');
30
25
  const { discoverIntegrationFiles, buildEffectiveDatasourceFiles } = require('./repair-internal');
31
26
  const { normalizeDatasourceKeysAndFilenames } = require('./repair-datasource-keys');
27
+ const { backupIntegrationFile } = require('../utils/integration-file-backup');
28
+ const { maybeSyncOpenApiFilesForMcp } = require('./repair-openapi-sync');
29
+ const {
30
+ alignAppKeyWithSystem,
31
+ alignDatasourceSystemKeys,
32
+ alignSystemFileDataSources,
33
+ ensureExternalIntegrationBlock,
34
+ removeAuthVarsFromConfiguration,
35
+ resolveSystemContext
36
+ } = require('./repair-system-alignment');
37
+ const { persistChangesAndRegenerate, regenerateReadmeIfRequested } = require('./repair-persist');
32
38
 
33
39
  /** Allowed authentication methods for repair --auth (matches external-system schema) */
34
40
  const ALLOWED_AUTH = ['oauth2', 'aad', 'apikey', 'basic', 'queryParam', 'oidc', 'hmac', 'none'];
35
41
 
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
-
52
- /**
53
- * Extracts roles and permissions from system object for rbac.yaml
54
- * @param {Object} system - Parsed system config
55
- * @returns {Object|null} RBAC object or null
56
- */
57
- function extractRbacFromSystem(system) {
58
- if (!system || typeof system !== 'object') return null;
59
- const hasRoles = system.roles && Array.isArray(system.roles) && system.roles.length > 0;
60
- const hasPermissions = system.permissions && Array.isArray(system.permissions) && system.permissions.length > 0;
61
- if (!hasRoles && !hasPermissions) return null;
62
- const rbac = {};
63
- if (hasRoles) rbac.roles = system.roles;
64
- if (hasPermissions) rbac.permissions = system.permissions;
65
- return rbac;
66
- }
67
-
68
- /**
69
- * Loads first system file and returns parsed object with key
70
- * @param {string} appPath - Application path
71
- * @param {string} systemFileName - System file name
72
- * @returns {Object} Parsed system config
73
- */
74
- function loadFirstSystemFile(appPath, systemFileName) {
75
- const systemPath = path.join(appPath, systemFileName);
76
- if (!fs.existsSync(systemPath)) {
77
- throw new Error(`System file not found: ${systemPath}`);
78
- }
79
- return loadConfigFile(systemPath);
80
- }
81
-
82
- function resolveSystemContext(appPath, systemFiles) {
83
- const systemFilePath = path.join(appPath, systemFiles[0]);
84
- const systemParsed = loadFirstSystemFile(appPath, systemFiles[0]);
85
- const systemKey = systemParsed.key ||
86
- path.basename(systemFiles[0], path.extname(systemFiles[0])).replace(/-system$/, '');
87
- return { systemFilePath, systemParsed, systemKey };
88
- }
89
-
90
- function ensureExternalIntegrationBlock(variables, systemFiles, datasourceFiles, changes) {
91
- const extInt = variables.externalIntegration;
92
- let updated = false;
93
- if (!extInt) {
94
- variables.externalIntegration = {
95
- schemaBasePath: './',
96
- systems: systemFiles,
97
- dataSources: datasourceFiles,
98
- autopublish: true,
99
- version: '1.0.0'
100
- };
101
- changes.push('Created externalIntegration block from discovered files');
102
- updated = true;
103
- } else {
104
- const prevSystems = extInt.systems || [];
105
- const prevDataSources = extInt.dataSources || [];
106
- if (JSON.stringify(prevSystems) !== JSON.stringify(systemFiles)) {
107
- changes.push(`systems: [${prevSystems.join(', ')}] → [${systemFiles.join(', ')}]`);
108
- extInt.systems = systemFiles;
109
- updated = true;
110
- }
111
- if (JSON.stringify(prevDataSources) !== JSON.stringify(datasourceFiles)) {
112
- changes.push(`dataSources: [${prevDataSources.join(', ')}] → [${datasourceFiles.join(', ')}]`);
113
- extInt.dataSources = datasourceFiles;
114
- updated = true;
115
- }
116
- }
117
- return updated;
118
- }
119
-
120
- function alignAppKeyWithSystem(variables, systemKey, systemParsed, changes) {
121
- const appKey = variables.app?.key;
122
- if (!appKey || appKey === systemKey) return false;
123
- if (!variables.app) variables.app = {};
124
- variables.app.key = systemKey;
125
- changes.push(`app.key: ${appKey} → ${systemKey}`);
126
- return true;
127
- }
128
-
129
- /**
130
- * Aligns datasource systemKey values to match the system key.
131
- * Updates each datasource file whose systemKey differs from the system key.
132
- *
133
- * @param {string} appPath - Application directory path
134
- * @param {string[]} datasourceFiles - Datasource file names
135
- * @param {string} systemKey - Expected system key
136
- * @param {boolean} dryRun - If true, report changes but do not write
137
- * @param {string[]} changes - Array to append change descriptions to
138
- * @returns {boolean} True if any file was updated (or would be in dry-run)
139
- */
140
- function alignDatasourceSystemKeys(appPath, datasourceFiles, systemKey, dryRun, changes) {
141
- if (!datasourceFiles || datasourceFiles.length === 0) return false;
142
- let updated = false;
143
- for (const datasourceFile of datasourceFiles) {
144
- const datasourcePath = path.join(appPath, datasourceFile);
145
- if (!fs.existsSync(datasourcePath)) continue;
146
- const parsed = loadConfigFile(datasourcePath);
147
- const old = parsed.systemKey;
148
- if (old !== systemKey) {
149
- parsed.systemKey = systemKey;
150
- if (!dryRun) {
151
- writeConfigFile(datasourcePath, parsed);
152
- }
153
- changes.push(`${datasourceFile}: systemKey ${old} → ${systemKey}`);
154
- updated = true;
155
- }
156
- }
157
- return updated;
158
- }
159
-
160
- /**
161
- * Derives a datasource key from filename when the file has no key.
162
- * - hubspot-datasource-company.json → hubspot-company
163
- * - datasource-companies.json → {systemKey}-companies (e.g. test-hubspot-companies)
164
- *
165
- * @param {string} fileName - Datasource file name
166
- * @param {string} systemKey - System key (e.g. test-hubspot), used for datasource-*.json style names
167
- * @returns {string}
168
- */
169
- function deriveDatasourceKeyFromFileName(fileName, systemKey) {
170
- const base = path.basename(fileName, path.extname(fileName));
171
- if (/^datasource-/.test(base)) {
172
- const suffix = base.slice('datasource-'.length);
173
- return systemKey && typeof systemKey === 'string' ? `${systemKey}-${suffix}` : base;
174
- }
175
- return base.replace(/-datasource-/, '-');
176
- }
177
-
178
- /**
179
- * Aligns system file dataSources array to match datasource keys from discovered files.
180
- * The system file holds logical keys (not filenames): each key comes from that datasource
181
- * file's "key" property, or is derived from the filename when missing (e.g. datasource-companies.json → {systemKey}-companies).
182
- *
183
- * @param {string} appPath - Application directory path
184
- * @param {Object} systemParsed - Parsed system config (mutated)
185
- * @param {string[]} datasourceFiles - Datasource file names
186
- * @param {string} systemKey - System key for deriving key when missing
187
- * @param {boolean} dryRun - If true, report changes but do not write
188
- * @param {string[]} changes - Array to append change descriptions to
189
- * @returns {boolean} True if dataSources was updated (or would be in dry-run)
190
- */
191
- function alignSystemFileDataSources(appPath, systemParsed, datasourceFiles, systemKey, dryRun, changes) {
192
- const keys = [];
193
- for (const fileName of datasourceFiles) {
194
- const filePath = path.join(appPath, fileName);
195
- if (!fs.existsSync(filePath)) continue;
196
- try {
197
- const parsed = loadConfigFile(filePath);
198
- const key = parsed && typeof parsed.key === 'string' && parsed.key.trim()
199
- ? parsed.key.trim()
200
- : deriveDatasourceKeyFromFileName(fileName, systemKey);
201
- keys.push(key);
202
- } catch (err) {
203
- logger.log(chalk.yellow(`⚠ Could not load datasource file ${fileName}: ${err.message}; using derived key`));
204
- keys.push(deriveDatasourceKeyFromFileName(fileName, systemKey));
205
- }
206
- }
207
- const prev = Array.isArray(systemParsed.dataSources) ? [...systemParsed.dataSources] : [];
208
- if (JSON.stringify(prev) === JSON.stringify(keys)) return false;
209
- systemParsed.dataSources = keys;
210
- changes.push(`dataSources: [${prev.join(', ') || '(none)'}] → [${keys.join(', ')}] (keys from each datasource file's "key" or filename)`);
211
- return true;
212
- }
213
-
214
- /**
215
- * Builds the set of auth variable names (UPPERCASE, no underscores) from authentication.variables and authentication.security.
216
- * Used to detect configuration entries that belong only in the authentication section.
217
- * Canonical keys per method are in lib/schema/external-system.schema.json $defs.authenticationVariablesByMethod
218
- * (e.g. oauth2: variables baseUrl, tokenUrl, scope; security clientId, clientSecret).
219
- *
220
- * @param {Object} systemParsed - Parsed system config
221
- * @param {string} _systemKey - System key (unused; for API consistency)
222
- * @returns {Set<string>}
223
- */
224
- function buildAuthVarNames(systemParsed, _systemKey) {
225
- const names = new Set();
226
- const auth = systemParsed.authentication;
227
- if (auth && typeof auth.variables === 'object') {
228
- for (const k of Object.keys(auth.variables)) {
229
- names.add(String(k).toUpperCase().replace(/_/g, ''));
230
- }
231
- }
232
- if (auth && typeof auth.security === 'object') {
233
- for (const k of Object.keys(auth.security)) {
234
- names.add(securityKeyToVar(k));
235
- }
236
- }
237
- return names;
238
- }
239
-
240
- /**
241
- * Removes from system configuration any entry that represents standard auth variables
242
- * (BASEURL, CLIENTID, CLIENTSECRET, TOKENURL, APIKEY, USERNAME, PASSWORD, etc.).
243
- * These are supplied from the selected credential at runtime; the configuration array
244
- * should contain only custom variables. Removes both plain and keyvault auth entries.
245
- *
246
- * @param {Object} systemParsed - Parsed system config (mutated)
247
- * @param {string} systemKey - System key for naming consistency
248
- * @param {boolean} dryRun - If true, report changes but do not write
249
- * @param {string[]} changes - Array to append change descriptions to
250
- * @returns {boolean} True if any entry was removed
251
- */
252
- /**
253
- * Derives the normalized auth variable part from a config entry name (for matching against authNames).
254
- * E.g. KV_HUBSPOT_CLIENTID → CLIENTID, BASEURL → BASEURL.
255
- * @param {string} name - Entry name
256
- * @param {string} systemKey - System key for KV_ prefix
257
- * @returns {string}
258
- */
259
- function normalizedAuthPartFromConfigName(name, systemKey) {
260
- const n = String(name).trim();
261
- if (!n) return '';
262
- const prefix = systemKeyToKvPrefix(systemKey);
263
- const kvPrefix = `KV_${prefix}_`;
264
- if (n.toUpperCase().startsWith(kvPrefix)) {
265
- const rest = n.slice(kvPrefix.length);
266
- return rest.toUpperCase().replace(/_/g, '');
267
- }
268
- // Old-style or other KV_* names: treat last segment as var (e.g. KV_HUBSPOT_CLIENTID → CLIENTID)
269
- if (n.toUpperCase().startsWith('KV_')) {
270
- const parts = n.split('_').filter(Boolean);
271
- if (parts.length >= 2) return parts[parts.length - 1].toUpperCase();
272
- }
273
- return n.toUpperCase().replace(/_/g, '');
274
- }
275
-
276
- function removeAuthVarsFromConfiguration(systemParsed, systemKey, dryRun, changes) {
277
- const config = systemParsed.configuration;
278
- if (!Array.isArray(config)) return false;
279
- const authNames = buildAuthVarNames(systemParsed, systemKey);
280
- if (authNames.size === 0) return false;
281
- const removed = [];
282
- const filtered = config.filter((entry) => {
283
- if (!entry || !entry.name) return true;
284
- const authPart = normalizedAuthPartFromConfigName(entry.name, systemKey);
285
- if (authNames.has(authPart)) {
286
- removed.push(entry.name);
287
- return false;
288
- }
289
- return true;
290
- });
291
- if (removed.length === 0) return false;
292
- systemParsed.configuration = filtered;
293
- changes.push(`Removed authentication variable(s) from configuration: ${removed.join(', ')}`);
294
- return true;
295
- }
296
-
297
- function createRbacFromSystemIfNeeded(appPath, systemFilePath, systemParsed, dryRun, changes, format) {
42
+ function createRbacFromSystemIfNeeded(opts) {
43
+ const { appPath, systemFilePath, systemParsed, dryRun, changes, format, backupCtx } = opts;
298
44
  if (resolveRbacPath(appPath)) return false;
299
45
  const rbacFromSystem = extractRbacFromSystem(systemParsed);
300
46
  if (!rbacFromSystem) return false;
@@ -304,6 +50,7 @@ function createRbacFromSystemIfNeeded(appPath, systemFilePath, systemParsed, dry
304
50
  writeConfigFile(defaultRbacPath, rbacFromSystem, rbacFormat);
305
51
  delete systemParsed.roles;
306
52
  delete systemParsed.permissions;
53
+ backupIntegrationFile(systemFilePath, backupCtx);
307
54
  writeConfigFile(systemFilePath, systemParsed);
308
55
  }
309
56
  changes.push('Created rbac.yaml from system roles/permissions');
@@ -337,6 +84,7 @@ function runDatasourceRepairs(appPath, datasourceFiles, options, dryRun, changes
337
84
  updated = true;
338
85
  fileChanges.forEach(c => changes.push(`${fileName}: ${c}`));
339
86
  if (!dryRun) {
87
+ backupIntegrationFile(filePath, options.backupCtx);
340
88
  writeConfigFile(filePath, parsed);
341
89
  }
342
90
  }
@@ -347,57 +95,17 @@ function runDatasourceRepairs(appPath, datasourceFiles, options, dryRun, changes
347
95
  return updated;
348
96
  }
349
97
 
350
- async function regenerateManifest(appName, appPath, changes) {
351
- try {
352
- const deployPath = await generator.generateDeployJson(appName, { appPath });
353
- changes.push(`Regenerated ${path.basename(deployPath)}`);
354
- return true;
355
- } catch (err) {
356
- logger.log(chalk.yellow(`⚠ Manifest regeneration skipped: ${err.message}`));
357
- return false;
358
- }
359
- }
360
-
361
- /**
362
- * Regenerates README.md from deployment manifest when options.doc is set.
363
- * @param {string} appName - Application name
364
- * @param {string} appPath - Application path
365
- * @param {Object} options - Options (doc, dryRun)
366
- * @param {string[]} changes - Array to append change messages to
367
- * @returns {Promise<boolean>} True if README was regenerated
368
- */
369
- async function regenerateReadmeIfRequested(appName, appPath, options, changes) {
370
- if (!options.doc) return false;
371
- const deployJsonPath = getDeployJsonPath(appName, 'external', true);
372
- if (!fs.existsSync(deployJsonPath) && !options.dryRun) {
373
- await regenerateManifest(appName, appPath, changes);
374
- }
375
- if (!fs.existsSync(deployJsonPath)) return false;
376
- try {
377
- const deployment = JSON.parse(fs.readFileSync(deployJsonPath, 'utf8'));
378
- const fileExt = inferExternalReadmeFileExt(appPath);
379
- const readmeContent = generateReadmeFromDeployJson(deployment, { fileExt });
380
- const readmePath = path.join(appPath, 'README.md');
381
- if (!options.dryRun) {
382
- fs.writeFileSync(readmePath, readmeContent, { mode: 0o644, encoding: 'utf8' });
383
- }
384
- changes.push('Regenerated README.md from deployment manifest');
385
- return true;
386
- } catch (err) {
387
- logger.log(chalk.yellow(`⚠ Could not regenerate README: ${err.message}`));
388
- return false;
389
- }
390
- }
391
-
392
- function persistChangesAndRegenerate(configPath, variables, appName, appPath, changes, originalYamlContent) {
393
- if (originalYamlContent !== null && originalYamlContent !== undefined && typeof originalYamlContent === 'string' && isYamlPath(configPath)) {
394
- writeYamlPreservingComments(configPath, originalYamlContent, variables);
395
- } else {
396
- writeConfigFile(configPath, variables);
397
- }
398
- logger.log(formatSuccessLine(`Updated ${path.basename(configPath)}`));
399
- changes.forEach(c => logger.log(chalk.gray(` ${c}`)));
400
- return regenerateManifest(appName, appPath, changes);
98
+ function maybeRepairRbac(appPath, systemFilePath, systemParsed, datasourceFiles, options) {
99
+ if (!options.rbac) return { rbacMigratedFromSystem: false, rbacMergeUpdated: false };
100
+ const { dryRun, changes, backupCtx, rbacFmt } = options;
101
+ const rbacMigratedFromSystem = migrateSystemRbacIntoRbacFile(appPath, systemFilePath, systemParsed, { dryRun, changes, backupCtx });
102
+ const rbacMergeUpdated = mergeRbacFromDatasources(appPath, systemParsed, datasourceFiles, extractRbacFromSystem, {
103
+ format: rbacFmt,
104
+ dryRun,
105
+ changes,
106
+ backupCtx
107
+ });
108
+ return { rbacMigratedFromSystem, rbacMergeUpdated };
401
109
  }
402
110
 
403
111
  /**
@@ -432,10 +140,11 @@ function applyAuthMethod(ctx) {
432
140
 
433
141
  /**
434
142
  * Runs all repair steps (integration block, system dataSources, auth/config, app key, datasource keys, rbac, env.template).
435
- * @param {Object} ctx - Context with appPath, configPath, variables, systemFilePath, systemParsed, systemKey, systemFiles, datasourceFiles, dryRun, changes, auth?
143
+ * @param {Object} ctx - Context with appPath, configPath, variables, systemFilePath, systemParsed, systemKey, systemFiles, datasourceFiles, dryRun, changes, auth?, backupCtx?
436
144
  * @returns {{ updated: boolean, appKeyFixed: boolean, datasourceKeysFixed: boolean, rbacFileCreated: boolean, envTemplateRepaired: boolean }}
437
145
  */
438
146
  function runRepairSteps(ctx) {
147
+ const bc = ctx.backupCtx;
439
148
  let updated = ensureExternalIntegrationBlock(
440
149
  ctx.variables, ctx.systemFiles, ctx.datasourceFiles, ctx.changes
441
150
  );
@@ -450,21 +159,30 @@ function runRepairSteps(ctx) {
450
159
  const authVarsRemoved = removeAuthVarsFromConfiguration(
451
160
  ctx.systemParsed, ctx.systemKey, ctx.dryRun, ctx.changes
452
161
  );
453
- if ((authReplaced || systemAuthConfigNormalized || systemDataSourcesAligned || authVarsRemoved) && !ctx.dryRun) {
162
+ const systemFileUpdated =
163
+ authReplaced || systemAuthConfigNormalized || systemDataSourcesAligned || authVarsRemoved;
164
+ if (systemFileUpdated && !ctx.dryRun) {
165
+ backupIntegrationFile(ctx.systemFilePath, bc);
454
166
  writeConfigFile(ctx.systemFilePath, ctx.systemParsed);
455
167
  }
456
- updated = updated || systemAuthConfigNormalized || systemDataSourcesAligned || authVarsRemoved;
168
+ updated = updated || systemFileUpdated;
457
169
  const appKeyFixed = alignAppKeyWithSystem(
458
170
  ctx.variables, ctx.systemKey, ctx.systemParsed, ctx.changes
459
171
  );
460
172
  const datasourceKeysFixed = alignDatasourceSystemKeys(
461
- ctx.appPath, ctx.datasourceFiles, ctx.systemKey, ctx.dryRun, ctx.changes
462
- );
463
- const rbacFileCreated = createRbacFromSystemIfNeeded(
464
- ctx.appPath, ctx.systemFilePath, ctx.systemParsed, ctx.dryRun, ctx.changes, ctx.format
173
+ ctx.appPath, ctx.datasourceFiles, ctx.systemKey, ctx.dryRun, ctx.changes, bc
465
174
  );
175
+ const rbacFileCreated = createRbacFromSystemIfNeeded({
176
+ appPath: ctx.appPath,
177
+ systemFilePath: ctx.systemFilePath,
178
+ systemParsed: ctx.systemParsed,
179
+ dryRun: ctx.dryRun,
180
+ changes: ctx.changes,
181
+ format: ctx.format,
182
+ backupCtx: bc
183
+ });
466
184
  const envTemplateRepaired = repairEnvTemplate(
467
- ctx.appPath, ctx.systemParsed, ctx.systemKey, ctx.dryRun, ctx.changes
185
+ ctx.appPath, ctx.systemParsed, ctx.systemKey, ctx.dryRun, ctx.changes, bc
468
186
  );
469
187
  updated = updated || appKeyFixed || datasourceKeysFixed || rbacFileCreated || envTemplateRepaired;
470
188
  return {
@@ -516,7 +234,14 @@ function loadConfigAndDiscover(appPath, configPath) {
516
234
  */
517
235
  function buildRepairResult(steps, anyUpdated, manifestRegenerated, readmeRegenerated, ctx) {
518
236
  return Object.assign(
519
- { updated: anyUpdated, changes: ctx.changes, systemFiles: ctx.systemFiles, datasourceFiles: ctx.datasourceFiles },
237
+ {
238
+ updated: anyUpdated,
239
+ actionsPerformed: ctx.actionsPerformed === true,
240
+ changes: ctx.changes,
241
+ systemFiles: ctx.systemFiles,
242
+ datasourceFiles: ctx.datasourceFiles,
243
+ backupPaths: ctx.backupPaths || []
244
+ },
520
245
  {
521
246
  appKeyFixed: steps.appKeyFixed,
522
247
  datasourceKeysFixed: steps.datasourceKeysFixed,
@@ -547,20 +272,45 @@ async function validateAndResolveRepairPaths(appName, options) {
547
272
  return { appPath, configPath, dryRun, authOption };
548
273
  }
549
274
 
275
+ /**
276
+ * @param {Object} options - CLI options (noBackup, backup)
277
+ * @param {boolean} dryRun
278
+ * @returns {{ backupPaths: string[], backupCtx: Object }}
279
+ */
280
+ function createRepairBackupContext(options, dryRun) {
281
+ const noBackup = options.noBackup === true || options.backup === false;
282
+ const backupPaths = [];
283
+ const backedUpFiles = new Set();
284
+ const backupCtx = { dryRun, noBackup, backupPaths, backedUpFiles };
285
+ return { backupPaths, backupCtx };
286
+ }
287
+
288
+ async function maybeRunOpenApiSyncForMcp({ options, dryRun, appPath, systemKey, datasourceFiles, changes }) {
289
+ if (!options.api) return false;
290
+ try {
291
+ const openapiLines = await maybeSyncOpenApiFilesForMcp({
292
+ enabled: true,
293
+ dryRun,
294
+ appPath,
295
+ systemKey,
296
+ datasourceFiles
297
+ });
298
+ openapiLines.forEach((l) => changes.push(l));
299
+ return openapiLines.length > 0;
300
+ } catch (err) {
301
+ changes.push(`OpenAPI upload for MCP failed: ${err.message}`);
302
+ return false;
303
+ }
304
+ }
305
+
550
306
  async function repairExternalIntegration(appName, options = {}) {
551
307
  const { appPath, configPath, dryRun, authOption } = await validateAndResolveRepairPaths(appName, options);
552
-
308
+ const { backupPaths, backupCtx } = createRepairBackupContext(options, dryRun);
553
309
  const { variables, originalYamlContent, systemFiles, datasourceFiles: initialDatasourceFiles } = loadConfigAndDiscover(appPath, configPath);
554
310
  const changes = [];
555
311
  const { systemFilePath, systemParsed, systemKey } = resolveSystemContext(appPath, systemFiles);
556
- const { updated: keysNormalized, datasourceFiles } = normalizeDatasourceKeysAndFilenames(
557
- appPath,
558
- initialDatasourceFiles,
559
- systemKey,
560
- variables,
561
- dryRun,
562
- changes
563
- );
312
+ const rbacFmt = options.format === 'json' ? 'json' : 'yaml';
313
+ const { updated: keysNormalized, datasourceFiles } = normalizeDatasourceKeysAndFilenames(appPath, initialDatasourceFiles, systemKey, { variables, dryRun, changes, backupCtx });
564
314
  const steps = runRepairSteps({
565
315
  appPath,
566
316
  configPath,
@@ -573,26 +323,42 @@ async function repairExternalIntegration(appName, options = {}) {
573
323
  dryRun,
574
324
  changes,
575
325
  auth: authOption,
576
- format: options.format === 'json' ? 'json' : 'yaml'
326
+ format: rbacFmt,
327
+ backupCtx
577
328
  });
578
329
  const datasourceRepairUpdated = runDatasourceRepairs(appPath, datasourceFiles, {
579
330
  expose: Boolean(options.expose),
580
331
  sync: Boolean(options.sync),
581
- test: Boolean(options.test)
332
+ test: Boolean(options.test),
333
+ backupCtx
582
334
  }, dryRun, changes);
583
- const rbacMergeUpdated = options.rbac
584
- ? mergeRbacFromDatasources(appPath, systemParsed, datasourceFiles, extractRbacFromSystem, {
585
- format: options.format === 'json' ? 'json' : 'yaml',
586
- dryRun,
587
- changes
588
- })
589
- : false;
590
- const anyUpdated = keysNormalized || steps.updated || datasourceRepairUpdated || rbacMergeUpdated;
591
- const manifestRegenerated = (anyUpdated && !dryRun)
592
- ? await persistChangesAndRegenerate(configPath, variables, appName, appPath, changes, originalYamlContent)
593
- : false;
594
- const readmeRegenerated = await regenerateReadmeIfRequested(appName, appPath, options, changes);
595
- return buildRepairResult(steps, anyUpdated, manifestRegenerated, readmeRegenerated, { changes, systemFiles, datasourceFiles });
335
+ const openapiActionsPerformed = await maybeRunOpenApiSyncForMcp({
336
+ options,
337
+ dryRun,
338
+ appPath,
339
+ systemKey,
340
+ datasourceFiles,
341
+ changes
342
+ });
343
+ const { rbacMigratedFromSystem, rbacMergeUpdated } = maybeRepairRbac(
344
+ appPath,
345
+ systemFilePath,
346
+ systemParsed,
347
+ datasourceFiles,
348
+ { rbac: Boolean(options.rbac), rbacFmt, dryRun, changes, backupCtx }
349
+ );
350
+ const anyLocalUpdated = keysNormalized || steps.updated || datasourceRepairUpdated || rbacMigratedFromSystem || rbacMergeUpdated;
351
+ const manifestRegenerated = (anyLocalUpdated && !dryRun) ? await persistChangesAndRegenerate(
352
+ { configPath, variables, appName, appPath, changes, originalYamlContent, backupCtx }
353
+ ) : false;
354
+ const readmeRegenerated = await regenerateReadmeIfRequested(appName, appPath, { ...options, backupCtx }, changes);
355
+ return buildRepairResult(steps, anyLocalUpdated, manifestRegenerated, readmeRegenerated, {
356
+ actionsPerformed: openapiActionsPerformed,
357
+ changes,
358
+ systemFiles,
359
+ datasourceFiles,
360
+ backupPaths
361
+ });
596
362
  }
597
363
 
598
364
  module.exports = {
@@ -179,7 +179,7 @@ async function processSecretsFiles(secretsFiles, encryptionKey) {
179
179
  totalValues += result.total;
180
180
 
181
181
  if (result.encrypted > 0) {
182
- logger.log(chalk.green(` Encrypted ${result.encrypted} of ${result.total} values`));
182
+ logger.log(chalk.green(' ') + formatSuccessLine(`Encrypted ${result.encrypted} of ${result.total} values`));
183
183
  } else {
184
184
  logger.log(chalk.gray(` - All values already encrypted (${result.total} total)`));
185
185
  }