@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
@@ -9,276 +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, extractRbacFromSystem } = 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
- * Loads first system file and returns parsed object with key
54
- * @param {string} appPath - Application path
55
- * @param {string} systemFileName - System file name
56
- * @returns {Object} Parsed system config
57
- */
58
- function loadFirstSystemFile(appPath, systemFileName) {
59
- const systemPath = path.join(appPath, systemFileName);
60
- if (!fs.existsSync(systemPath)) {
61
- throw new Error(`System file not found: ${systemPath}`);
62
- }
63
- return loadConfigFile(systemPath);
64
- }
65
-
66
- function resolveSystemContext(appPath, systemFiles) {
67
- const systemFilePath = path.join(appPath, systemFiles[0]);
68
- const systemParsed = loadFirstSystemFile(appPath, systemFiles[0]);
69
- const systemKey = systemParsed.key ||
70
- path.basename(systemFiles[0], path.extname(systemFiles[0])).replace(/-system$/, '');
71
- return { systemFilePath, systemParsed, systemKey };
72
- }
73
-
74
- function ensureExternalIntegrationBlock(variables, systemFiles, datasourceFiles, changes) {
75
- const extInt = variables.externalIntegration;
76
- let updated = false;
77
- if (!extInt) {
78
- variables.externalIntegration = {
79
- schemaBasePath: './',
80
- systems: systemFiles,
81
- dataSources: datasourceFiles,
82
- autopublish: true,
83
- version: '1.0.0'
84
- };
85
- changes.push('Created externalIntegration block from discovered files');
86
- updated = true;
87
- } else {
88
- const prevSystems = extInt.systems || [];
89
- const prevDataSources = extInt.dataSources || [];
90
- if (JSON.stringify(prevSystems) !== JSON.stringify(systemFiles)) {
91
- changes.push(`systems: [${prevSystems.join(', ')}] → [${systemFiles.join(', ')}]`);
92
- extInt.systems = systemFiles;
93
- updated = true;
94
- }
95
- if (JSON.stringify(prevDataSources) !== JSON.stringify(datasourceFiles)) {
96
- changes.push(`dataSources: [${prevDataSources.join(', ')}] → [${datasourceFiles.join(', ')}]`);
97
- extInt.dataSources = datasourceFiles;
98
- updated = true;
99
- }
100
- }
101
- return updated;
102
- }
103
-
104
- function alignAppKeyWithSystem(variables, systemKey, systemParsed, changes) {
105
- const appKey = variables.app?.key;
106
- if (!appKey || appKey === systemKey) return false;
107
- if (!variables.app) variables.app = {};
108
- variables.app.key = systemKey;
109
- changes.push(`app.key: ${appKey} → ${systemKey}`);
110
- return true;
111
- }
112
-
113
- /**
114
- * Aligns datasource systemKey values to match the system key.
115
- * Updates each datasource file whose systemKey differs from the system key.
116
- *
117
- * @param {string} appPath - Application directory path
118
- * @param {string[]} datasourceFiles - Datasource file names
119
- * @param {string} systemKey - Expected system key
120
- * @param {boolean} dryRun - If true, report changes but do not write
121
- * @param {string[]} changes - Array to append change descriptions to
122
- * @returns {boolean} True if any file was updated (or would be in dry-run)
123
- */
124
- function alignDatasourceSystemKeys(appPath, datasourceFiles, systemKey, dryRun, changes) {
125
- if (!datasourceFiles || datasourceFiles.length === 0) return false;
126
- let updated = false;
127
- for (const datasourceFile of datasourceFiles) {
128
- const datasourcePath = path.join(appPath, datasourceFile);
129
- if (!fs.existsSync(datasourcePath)) continue;
130
- const parsed = loadConfigFile(datasourcePath);
131
- const old = parsed.systemKey;
132
- if (old !== systemKey) {
133
- parsed.systemKey = systemKey;
134
- if (!dryRun) {
135
- writeConfigFile(datasourcePath, parsed);
136
- }
137
- changes.push(`${datasourceFile}: systemKey ${old} → ${systemKey}`);
138
- updated = true;
139
- }
140
- }
141
- return updated;
142
- }
143
-
144
- /**
145
- * Derives a datasource key from filename when the file has no key.
146
- * - hubspot-datasource-company.json → hubspot-company
147
- * - datasource-companies.json → {systemKey}-companies (e.g. test-hubspot-companies)
148
- *
149
- * @param {string} fileName - Datasource file name
150
- * @param {string} systemKey - System key (e.g. test-hubspot), used for datasource-*.json style names
151
- * @returns {string}
152
- */
153
- function deriveDatasourceKeyFromFileName(fileName, systemKey) {
154
- const base = path.basename(fileName, path.extname(fileName));
155
- if (/^datasource-/.test(base)) {
156
- const suffix = base.slice('datasource-'.length);
157
- return systemKey && typeof systemKey === 'string' ? `${systemKey}-${suffix}` : base;
158
- }
159
- return base.replace(/-datasource-/, '-');
160
- }
161
-
162
- /**
163
- * Aligns system file dataSources array to match datasource keys from discovered files.
164
- * The system file holds logical keys (not filenames): each key comes from that datasource
165
- * file's "key" property, or is derived from the filename when missing (e.g. datasource-companies.json → {systemKey}-companies).
166
- *
167
- * @param {string} appPath - Application directory path
168
- * @param {Object} systemParsed - Parsed system config (mutated)
169
- * @param {string[]} datasourceFiles - Datasource file names
170
- * @param {string} systemKey - System key for deriving key when missing
171
- * @param {boolean} dryRun - If true, report changes but do not write
172
- * @param {string[]} changes - Array to append change descriptions to
173
- * @returns {boolean} True if dataSources was updated (or would be in dry-run)
174
- */
175
- function alignSystemFileDataSources(appPath, systemParsed, datasourceFiles, systemKey, dryRun, changes) {
176
- const keys = [];
177
- for (const fileName of datasourceFiles) {
178
- const filePath = path.join(appPath, fileName);
179
- if (!fs.existsSync(filePath)) continue;
180
- try {
181
- const parsed = loadConfigFile(filePath);
182
- const key = parsed && typeof parsed.key === 'string' && parsed.key.trim()
183
- ? parsed.key.trim()
184
- : deriveDatasourceKeyFromFileName(fileName, systemKey);
185
- keys.push(key);
186
- } catch (err) {
187
- logger.log(chalk.yellow(`⚠ Could not load datasource file ${fileName}: ${err.message}; using derived key`));
188
- keys.push(deriveDatasourceKeyFromFileName(fileName, systemKey));
189
- }
190
- }
191
- const prev = Array.isArray(systemParsed.dataSources) ? [...systemParsed.dataSources] : [];
192
- if (JSON.stringify(prev) === JSON.stringify(keys)) return false;
193
- systemParsed.dataSources = keys;
194
- changes.push(`dataSources: [${prev.join(', ') || '(none)'}] → [${keys.join(', ')}] (keys from each datasource file's "key" or filename)`);
195
- return true;
196
- }
197
-
198
- /**
199
- * Builds the set of auth variable names (UPPERCASE, no underscores) from authentication.variables and authentication.security.
200
- * Used to detect configuration entries that belong only in the authentication section.
201
- * Canonical keys per method are in lib/schema/external-system.schema.json $defs.authenticationVariablesByMethod
202
- * (e.g. oauth2: variables baseUrl, tokenUrl, scope; security clientId, clientSecret).
203
- *
204
- * @param {Object} systemParsed - Parsed system config
205
- * @param {string} _systemKey - System key (unused; for API consistency)
206
- * @returns {Set<string>}
207
- */
208
- function buildAuthVarNames(systemParsed, _systemKey) {
209
- const names = new Set();
210
- const auth = systemParsed.authentication;
211
- if (auth && typeof auth.variables === 'object') {
212
- for (const k of Object.keys(auth.variables)) {
213
- names.add(String(k).toUpperCase().replace(/_/g, ''));
214
- }
215
- }
216
- if (auth && typeof auth.security === 'object') {
217
- for (const k of Object.keys(auth.security)) {
218
- names.add(securityKeyToVar(k));
219
- }
220
- }
221
- return names;
222
- }
223
-
224
- /**
225
- * Removes from system configuration any entry that represents standard auth variables
226
- * (BASEURL, CLIENTID, CLIENTSECRET, TOKENURL, APIKEY, USERNAME, PASSWORD, etc.).
227
- * These are supplied from the selected credential at runtime; the configuration array
228
- * should contain only custom variables. Removes both plain and keyvault auth entries.
229
- *
230
- * @param {Object} systemParsed - Parsed system config (mutated)
231
- * @param {string} systemKey - System key for naming consistency
232
- * @param {boolean} dryRun - If true, report changes but do not write
233
- * @param {string[]} changes - Array to append change descriptions to
234
- * @returns {boolean} True if any entry was removed
235
- */
236
- /**
237
- * Derives the normalized auth variable part from a config entry name (for matching against authNames).
238
- * E.g. KV_HUBSPOT_CLIENTID → CLIENTID, BASEURL → BASEURL.
239
- * @param {string} name - Entry name
240
- * @param {string} systemKey - System key for KV_ prefix
241
- * @returns {string}
242
- */
243
- function normalizedAuthPartFromConfigName(name, systemKey) {
244
- const n = String(name).trim();
245
- if (!n) return '';
246
- const prefix = systemKeyToKvPrefix(systemKey);
247
- const kvPrefix = `KV_${prefix}_`;
248
- if (n.toUpperCase().startsWith(kvPrefix)) {
249
- const rest = n.slice(kvPrefix.length);
250
- return rest.toUpperCase().replace(/_/g, '');
251
- }
252
- // Old-style or other KV_* names: treat last segment as var (e.g. KV_HUBSPOT_CLIENTID → CLIENTID)
253
- if (n.toUpperCase().startsWith('KV_')) {
254
- const parts = n.split('_').filter(Boolean);
255
- if (parts.length >= 2) return parts[parts.length - 1].toUpperCase();
256
- }
257
- return n.toUpperCase().replace(/_/g, '');
258
- }
259
-
260
- function removeAuthVarsFromConfiguration(systemParsed, systemKey, dryRun, changes) {
261
- const config = systemParsed.configuration;
262
- if (!Array.isArray(config)) return false;
263
- const authNames = buildAuthVarNames(systemParsed, systemKey);
264
- if (authNames.size === 0) return false;
265
- const removed = [];
266
- const filtered = config.filter((entry) => {
267
- if (!entry || !entry.name) return true;
268
- const authPart = normalizedAuthPartFromConfigName(entry.name, systemKey);
269
- if (authNames.has(authPart)) {
270
- removed.push(entry.name);
271
- return false;
272
- }
273
- return true;
274
- });
275
- if (removed.length === 0) return false;
276
- systemParsed.configuration = filtered;
277
- changes.push(`Removed authentication variable(s) from configuration: ${removed.join(', ')}`);
278
- return true;
279
- }
280
-
281
- function createRbacFromSystemIfNeeded(appPath, systemFilePath, systemParsed, dryRun, changes, format) {
42
+ function createRbacFromSystemIfNeeded(opts) {
43
+ const { appPath, systemFilePath, systemParsed, dryRun, changes, format, backupCtx } = opts;
282
44
  if (resolveRbacPath(appPath)) return false;
283
45
  const rbacFromSystem = extractRbacFromSystem(systemParsed);
284
46
  if (!rbacFromSystem) return false;
@@ -288,6 +50,7 @@ function createRbacFromSystemIfNeeded(appPath, systemFilePath, systemParsed, dry
288
50
  writeConfigFile(defaultRbacPath, rbacFromSystem, rbacFormat);
289
51
  delete systemParsed.roles;
290
52
  delete systemParsed.permissions;
53
+ backupIntegrationFile(systemFilePath, backupCtx);
291
54
  writeConfigFile(systemFilePath, systemParsed);
292
55
  }
293
56
  changes.push('Created rbac.yaml from system roles/permissions');
@@ -321,6 +84,7 @@ function runDatasourceRepairs(appPath, datasourceFiles, options, dryRun, changes
321
84
  updated = true;
322
85
  fileChanges.forEach(c => changes.push(`${fileName}: ${c}`));
323
86
  if (!dryRun) {
87
+ backupIntegrationFile(filePath, options.backupCtx);
324
88
  writeConfigFile(filePath, parsed);
325
89
  }
326
90
  }
@@ -331,57 +95,17 @@ function runDatasourceRepairs(appPath, datasourceFiles, options, dryRun, changes
331
95
  return updated;
332
96
  }
333
97
 
334
- async function regenerateManifest(appName, appPath, changes) {
335
- try {
336
- const deployPath = await generator.generateDeployJson(appName, { appPath });
337
- changes.push(`Regenerated ${path.basename(deployPath)}`);
338
- return true;
339
- } catch (err) {
340
- logger.log(chalk.yellow(`⚠ Manifest regeneration skipped: ${err.message}`));
341
- return false;
342
- }
343
- }
344
-
345
- /**
346
- * Regenerates README.md from deployment manifest when options.doc is set.
347
- * @param {string} appName - Application name
348
- * @param {string} appPath - Application path
349
- * @param {Object} options - Options (doc, dryRun)
350
- * @param {string[]} changes - Array to append change messages to
351
- * @returns {Promise<boolean>} True if README was regenerated
352
- */
353
- async function regenerateReadmeIfRequested(appName, appPath, options, changes) {
354
- if (!options.doc) return false;
355
- const deployJsonPath = getDeployJsonPath(appName, 'external', true);
356
- if (!fs.existsSync(deployJsonPath) && !options.dryRun) {
357
- await regenerateManifest(appName, appPath, changes);
358
- }
359
- if (!fs.existsSync(deployJsonPath)) return false;
360
- try {
361
- const deployment = JSON.parse(fs.readFileSync(deployJsonPath, 'utf8'));
362
- const fileExt = inferExternalReadmeFileExt(appPath);
363
- const readmeContent = generateReadmeFromDeployJson(deployment, { fileExt });
364
- const readmePath = path.join(appPath, 'README.md');
365
- if (!options.dryRun) {
366
- fs.writeFileSync(readmePath, readmeContent, { mode: 0o644, encoding: 'utf8' });
367
- }
368
- changes.push('Regenerated README.md from deployment manifest');
369
- return true;
370
- } catch (err) {
371
- logger.log(chalk.yellow(`⚠ Could not regenerate README: ${err.message}`));
372
- return false;
373
- }
374
- }
375
-
376
- function persistChangesAndRegenerate(configPath, variables, appName, appPath, changes, originalYamlContent) {
377
- if (originalYamlContent !== null && originalYamlContent !== undefined && typeof originalYamlContent === 'string' && isYamlPath(configPath)) {
378
- writeYamlPreservingComments(configPath, originalYamlContent, variables);
379
- } else {
380
- writeConfigFile(configPath, variables);
381
- }
382
- logger.log(formatSuccessLine(`Updated ${path.basename(configPath)}`));
383
- changes.forEach(c => logger.log(chalk.gray(` ${c}`)));
384
- 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 };
385
109
  }
386
110
 
387
111
  /**
@@ -416,10 +140,11 @@ function applyAuthMethod(ctx) {
416
140
 
417
141
  /**
418
142
  * Runs all repair steps (integration block, system dataSources, auth/config, app key, datasource keys, rbac, env.template).
419
- * @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?
420
144
  * @returns {{ updated: boolean, appKeyFixed: boolean, datasourceKeysFixed: boolean, rbacFileCreated: boolean, envTemplateRepaired: boolean }}
421
145
  */
422
146
  function runRepairSteps(ctx) {
147
+ const bc = ctx.backupCtx;
423
148
  let updated = ensureExternalIntegrationBlock(
424
149
  ctx.variables, ctx.systemFiles, ctx.datasourceFiles, ctx.changes
425
150
  );
@@ -434,21 +159,30 @@ function runRepairSteps(ctx) {
434
159
  const authVarsRemoved = removeAuthVarsFromConfiguration(
435
160
  ctx.systemParsed, ctx.systemKey, ctx.dryRun, ctx.changes
436
161
  );
437
- 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);
438
166
  writeConfigFile(ctx.systemFilePath, ctx.systemParsed);
439
167
  }
440
- updated = updated || systemAuthConfigNormalized || systemDataSourcesAligned || authVarsRemoved;
168
+ updated = updated || systemFileUpdated;
441
169
  const appKeyFixed = alignAppKeyWithSystem(
442
170
  ctx.variables, ctx.systemKey, ctx.systemParsed, ctx.changes
443
171
  );
444
172
  const datasourceKeysFixed = alignDatasourceSystemKeys(
445
- ctx.appPath, ctx.datasourceFiles, ctx.systemKey, ctx.dryRun, ctx.changes
446
- );
447
- const rbacFileCreated = createRbacFromSystemIfNeeded(
448
- ctx.appPath, ctx.systemFilePath, ctx.systemParsed, ctx.dryRun, ctx.changes, ctx.format
173
+ ctx.appPath, ctx.datasourceFiles, ctx.systemKey, ctx.dryRun, ctx.changes, bc
449
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
+ });
450
184
  const envTemplateRepaired = repairEnvTemplate(
451
- ctx.appPath, ctx.systemParsed, ctx.systemKey, ctx.dryRun, ctx.changes
185
+ ctx.appPath, ctx.systemParsed, ctx.systemKey, ctx.dryRun, ctx.changes, bc
452
186
  );
453
187
  updated = updated || appKeyFixed || datasourceKeysFixed || rbacFileCreated || envTemplateRepaired;
454
188
  return {
@@ -500,7 +234,14 @@ function loadConfigAndDiscover(appPath, configPath) {
500
234
  */
501
235
  function buildRepairResult(steps, anyUpdated, manifestRegenerated, readmeRegenerated, ctx) {
502
236
  return Object.assign(
503
- { 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
+ },
504
245
  {
505
246
  appKeyFixed: steps.appKeyFixed,
506
247
  datasourceKeysFixed: steps.datasourceKeysFixed,
@@ -531,20 +272,45 @@ async function validateAndResolveRepairPaths(appName, options) {
531
272
  return { appPath, configPath, dryRun, authOption };
532
273
  }
533
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
+
534
306
  async function repairExternalIntegration(appName, options = {}) {
535
307
  const { appPath, configPath, dryRun, authOption } = await validateAndResolveRepairPaths(appName, options);
536
-
308
+ const { backupPaths, backupCtx } = createRepairBackupContext(options, dryRun);
537
309
  const { variables, originalYamlContent, systemFiles, datasourceFiles: initialDatasourceFiles } = loadConfigAndDiscover(appPath, configPath);
538
310
  const changes = [];
539
311
  const { systemFilePath, systemParsed, systemKey } = resolveSystemContext(appPath, systemFiles);
540
- const { updated: keysNormalized, datasourceFiles } = normalizeDatasourceKeysAndFilenames(
541
- appPath,
542
- initialDatasourceFiles,
543
- systemKey,
544
- variables,
545
- dryRun,
546
- changes
547
- );
312
+ const rbacFmt = options.format === 'json' ? 'json' : 'yaml';
313
+ const { updated: keysNormalized, datasourceFiles } = normalizeDatasourceKeysAndFilenames(appPath, initialDatasourceFiles, systemKey, { variables, dryRun, changes, backupCtx });
548
314
  const steps = runRepairSteps({
549
315
  appPath,
550
316
  configPath,
@@ -557,26 +323,42 @@ async function repairExternalIntegration(appName, options = {}) {
557
323
  dryRun,
558
324
  changes,
559
325
  auth: authOption,
560
- format: options.format === 'json' ? 'json' : 'yaml'
326
+ format: rbacFmt,
327
+ backupCtx
561
328
  });
562
329
  const datasourceRepairUpdated = runDatasourceRepairs(appPath, datasourceFiles, {
563
330
  expose: Boolean(options.expose),
564
331
  sync: Boolean(options.sync),
565
- test: Boolean(options.test)
332
+ test: Boolean(options.test),
333
+ backupCtx
566
334
  }, dryRun, changes);
567
- const rbacMergeUpdated = options.rbac
568
- ? mergeRbacFromDatasources(appPath, systemParsed, datasourceFiles, extractRbacFromSystem, {
569
- format: options.format === 'json' ? 'json' : 'yaml',
570
- dryRun,
571
- changes
572
- })
573
- : false;
574
- const anyUpdated = keysNormalized || steps.updated || datasourceRepairUpdated || rbacMergeUpdated;
575
- const manifestRegenerated = (anyUpdated && !dryRun)
576
- ? await persistChangesAndRegenerate(configPath, variables, appName, appPath, changes, originalYamlContent)
577
- : false;
578
- const readmeRegenerated = await regenerateReadmeIfRequested(appName, appPath, options, changes);
579
- 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
+ });
580
362
  }
581
363
 
582
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
  }