@aifabrix/builder 2.45.5 → 2.46.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 (231) hide show
  1. package/.cursor/rules/cli-layout.mdc +2 -0
  2. package/.cursor/rules/cli-product-neutral.mdc +43 -0
  3. package/.cursor/rules/project-rules.mdc +1 -0
  4. package/README.md +43 -17
  5. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  6. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  7. package/jest.config.default.js +2 -1
  8. package/jest.config.isolated.js +2 -1
  9. package/jest.global-live-fabrix-hooks.js +36 -0
  10. package/jest.isolated-projects.js +130 -0
  11. package/jest.projects.js +41 -136
  12. package/lib/api/api-response-errors.js +124 -0
  13. package/lib/api/governance-scenario-pack.api.js +159 -0
  14. package/lib/api/index.js +43 -3
  15. package/lib/api/lifecycle.api.js +77 -0
  16. package/lib/api/pipeline.api.js +3 -1
  17. package/lib/api/records-bulk.api.js +14 -1
  18. package/lib/api/validation-runner.js +3 -1
  19. package/lib/app/deploy-config.js +4 -1
  20. package/lib/app/down.js +20 -1
  21. package/lib/app/push.js +4 -5
  22. package/lib/app/register.js +7 -4
  23. package/lib/app/run-env-compose.js +84 -30
  24. package/lib/build/index.js +5 -0
  25. package/lib/certification/sync-system-certification.js +13 -6
  26. package/lib/cli/index.js +4 -0
  27. package/lib/cli/infra-guided-auth.js +109 -0
  28. package/lib/cli/infra-guided.js +84 -81
  29. package/lib/cli/setup-app.help.js +0 -31
  30. package/lib/cli/setup-app.js +28 -2
  31. package/lib/cli/setup-app.test-commands.js +5 -57
  32. package/lib/cli/setup-auth.js +25 -0
  33. package/lib/cli/setup-certification-commands.js +147 -0
  34. package/lib/cli/setup-certification.help.js +141 -0
  35. package/lib/cli/setup-environment.js +19 -2
  36. package/lib/cli/setup-external-system-help.js +62 -0
  37. package/lib/cli/setup-external-system.js +25 -3
  38. package/lib/cli/setup-infra.js +9 -1
  39. package/lib/cli/setup-platform.js +6 -1
  40. package/lib/cli/setup-secrets.js +33 -1
  41. package/lib/cli/setup-utility-help-after.js +22 -2
  42. package/lib/cli/setup-utility-repair.js +198 -0
  43. package/lib/cli/setup-utility-resolve.js +40 -4
  44. package/lib/cli/setup-utility-validate.js +197 -0
  45. package/lib/cli/setup-utility.js +4 -239
  46. package/lib/commands/auth-status-client-tokens.js +214 -0
  47. package/lib/commands/auth-status-dataplane-version.js +58 -20
  48. package/lib/commands/auth-status.js +41 -47
  49. package/lib/commands/datasource-test-trust-cli.js +27 -23
  50. package/lib/commands/datasource-unified-test-cli.js +6 -3
  51. package/lib/commands/datasource-unified-test-cli.options.js +5 -1
  52. package/lib/commands/datasource.js +7 -7
  53. package/lib/commands/dev-show-display.js +8 -1
  54. package/lib/commands/governance-cli-shared.js +94 -0
  55. package/lib/commands/governance-command-action.js +132 -0
  56. package/lib/commands/governance-download-external.js +34 -0
  57. package/lib/commands/governance-generate-external.js +71 -0
  58. package/lib/commands/governance-upload-external.js +36 -0
  59. package/lib/commands/governance-verify-external.js +64 -0
  60. package/lib/commands/governance.js +144 -0
  61. package/lib/commands/lifecycle-command-action.js +123 -0
  62. package/lib/commands/lifecycle-external.js +268 -0
  63. package/lib/commands/login.js +11 -2
  64. package/lib/commands/repair-auth-apply.js +127 -0
  65. package/lib/commands/repair-changed-files.js +41 -0
  66. package/lib/commands/repair-datasource-keys.js +4 -1
  67. package/lib/commands/repair-datasource-run.js +59 -0
  68. package/lib/commands/repair-datasource-test-payload.js +259 -0
  69. package/lib/commands/repair-datasource.js +1 -51
  70. package/lib/commands/repair-env-template.js +64 -13
  71. package/lib/commands/repair-external-run.js +216 -0
  72. package/lib/commands/repair-openapi-sync.js +23 -0
  73. package/lib/commands/repair-persist.js +4 -0
  74. package/lib/commands/repair-rbac.js +52 -1
  75. package/lib/commands/repair-steps.js +145 -0
  76. package/lib/commands/repair.js +29 -296
  77. package/lib/commands/secrets-get.js +184 -0
  78. package/lib/commands/secrets-remove-all.js +3 -1
  79. package/lib/commands/setup-image-refresh.js +146 -0
  80. package/lib/commands/setup-modes.js +158 -162
  81. package/lib/commands/setup-platform-auth.js +76 -0
  82. package/lib/commands/setup-prompts-admin.js +244 -0
  83. package/lib/commands/setup-prompts-platform-mode.js +67 -0
  84. package/lib/commands/setup-prompts-validators.js +36 -0
  85. package/lib/commands/setup-prompts.js +24 -76
  86. package/lib/commands/setup-run-context.js +45 -0
  87. package/lib/commands/setup-spinners.js +39 -0
  88. package/lib/commands/setup.js +27 -5
  89. package/lib/commands/teardown.js +39 -0
  90. package/lib/commands/test-e2e-external.js +24 -8
  91. package/lib/commands/test-governance-external.js +3 -24
  92. package/lib/commands/test-trust-external.js +20 -21
  93. package/lib/commands/up-common.js +25 -13
  94. package/lib/commands/up-dataplane.js +50 -13
  95. package/lib/commands/upload-publish-context.js +75 -0
  96. package/lib/commands/upload-sidecar-followups.js +89 -0
  97. package/lib/commands/upload.js +90 -108
  98. package/lib/commands/verify-governance-combined-external.js +61 -0
  99. package/lib/commands/verify-governance-command-action.js +64 -0
  100. package/lib/commands/verify-governance-db.js +102 -0
  101. package/lib/commands/verify-governance-external.js +77 -0
  102. package/lib/commands/verify-governance-result-builders.js +71 -0
  103. package/lib/commands/verify-operations-command-action.js +68 -0
  104. package/lib/commands/verify-operations-external.js +268 -0
  105. package/lib/commands/verify-operations-persist.js +75 -0
  106. package/lib/commands/verify-operations-step-plan.js +68 -0
  107. package/lib/commands/verify-operations-steps.js +111 -0
  108. package/lib/commands/verify-trust-command-action.js +70 -0
  109. package/lib/commands/verify-trust-external.js +67 -0
  110. package/lib/commands/wizard-config-normalizer.js +43 -23
  111. package/lib/commands/wizard-core.js +2 -0
  112. package/lib/core/admin-password-generator.js +38 -0
  113. package/lib/core/admin-secrets-bundle.js +111 -0
  114. package/lib/core/admin-secrets-env-overlay.js +62 -0
  115. package/lib/core/admin-secrets.js +32 -4
  116. package/lib/core/config-attach-extensions.js +3 -0
  117. package/lib/core/config-platform-controller.js +44 -0
  118. package/lib/core/config-setup-installation-profile.js +42 -0
  119. package/lib/core/config-setup-platform-mode.js +46 -0
  120. package/lib/core/config.js +46 -36
  121. package/lib/core/secrets-admin-env.js +6 -2
  122. package/lib/core/secrets-ensure.js +4 -0
  123. package/lib/core/secrets-env-content.js +54 -48
  124. package/lib/core/secrets-env-write.js +10 -1
  125. package/lib/core/secrets-infra-placeholder-sync.js +2 -0
  126. package/lib/core/setup-installation-profile.js +43 -0
  127. package/lib/core/setup-platform-mode.js +55 -0
  128. package/lib/datasource/agent-trust-debug-log.js +1 -1
  129. package/lib/datasource/agent-trust-run.js +5 -3
  130. package/lib/datasource/audit-evidence-extract-debug.js +30 -18
  131. package/lib/datasource/audit-evidence-extract.js +7 -4
  132. package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
  133. package/lib/datasource/bulk-loader-run-batches.js +15 -3
  134. package/lib/datasource/datasource-load-export-context.js +3 -0
  135. package/lib/datasource/log-cleaner.js +2 -2
  136. package/lib/datasource/log-viewer.js +8 -8
  137. package/lib/datasource/test-e2e.js +4 -1
  138. package/lib/datasource/unified-validation-run-body.js +3 -1
  139. package/lib/datasource/unified-validation-run.js +5 -2
  140. package/lib/deployment/environment-poll-status.js +138 -0
  141. package/lib/deployment/environment.js +42 -34
  142. package/lib/external-system/download.js +21 -20
  143. package/lib/external-system/generator.js +5 -1
  144. package/lib/external-system/integration-auth-context.js +45 -0
  145. package/lib/external-system/test-auth.js +12 -5
  146. package/lib/external-system/test.js +6 -6
  147. package/lib/generator/external-controller-manifest.js +15 -0
  148. package/lib/governance/governance-cli-errors.js +96 -0
  149. package/lib/governance/governance-download-display.js +38 -0
  150. package/lib/governance/governance-run-display.js +215 -25
  151. package/lib/infrastructure/admin-secrets-apply.js +84 -0
  152. package/lib/infrastructure/admin-secrets-ensure.js +130 -0
  153. package/lib/infrastructure/admin-secrets-resolve.js +95 -0
  154. package/lib/infrastructure/admin-secrets-sync.js +91 -0
  155. package/lib/infrastructure/helpers.js +23 -129
  156. package/lib/infrastructure/index.js +5 -6
  157. package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
  158. package/lib/lifecycle/certification-progress.js +231 -0
  159. package/lib/lifecycle/dataplane-ui-url.js +145 -0
  160. package/lib/lifecycle/exit-codes.js +24 -0
  161. package/lib/lifecycle/json-envelope.js +25 -0
  162. package/lib/lifecycle/lifecycle-governance-options.js +51 -0
  163. package/lib/lifecycle/logger-quiet.js +29 -0
  164. package/lib/lifecycle/open-api-published.js +59 -0
  165. package/lib/lifecycle/operations-readiness-metrics.js +101 -0
  166. package/lib/lifecycle/product-model.js +196 -0
  167. package/lib/lifecycle/report-display.js +471 -0
  168. package/lib/lifecycle/scenario-upload.js +191 -0
  169. package/lib/lifecycle/verify-step-progress.js +87 -0
  170. package/lib/parameters/infra-kv-discovery.js +27 -2
  171. package/lib/protection/auth-context.js +10 -4
  172. package/lib/schema/application-schema.json +1 -1
  173. package/lib/schema/datasource-test-run.schema.json +89 -6
  174. package/lib/schema/external-datasource.schema.json +2636 -2622
  175. package/lib/schema/external-system.schema.json +1089 -761
  176. package/lib/schema/infra.parameter.yaml +45 -0
  177. package/lib/schema/type/credential-field-registry.json +103 -97
  178. package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
  179. package/lib/utils/api.js +55 -31
  180. package/lib/utils/app-relative-path-env.js +112 -0
  181. package/lib/utils/cli-secrets-error-format.js +83 -21
  182. package/lib/utils/cli-utils.js +16 -4
  183. package/lib/utils/compose-generate-docker-compose.js +4 -0
  184. package/lib/utils/compose-traefik-ingress-base.js +16 -1
  185. package/lib/utils/config-secrets-path.js +46 -0
  186. package/lib/utils/controller-url.js +88 -1
  187. package/lib/utils/credential-secrets-env.js +137 -9
  188. package/lib/utils/dataplane-resolver.js +129 -2
  189. package/lib/utils/declarative-url-ports.js +3 -3
  190. package/lib/utils/deployment-auth-mode.js +41 -0
  191. package/lib/utils/developer-volume-id.js +33 -0
  192. package/lib/utils/env-copy.js +14 -9
  193. package/lib/utils/env-kv-orphan-lines.js +43 -0
  194. package/lib/utils/error-formatters/network-errors.js +13 -4
  195. package/lib/utils/external-env-template.js +1 -0
  196. package/lib/utils/external-system-readiness-core.js +10 -0
  197. package/lib/utils/external-system-readiness-display.js +80 -23
  198. package/lib/utils/health-check.js +4 -1
  199. package/lib/utils/help-builder.js +11 -8
  200. package/lib/utils/keycloak-realm-ready.js +101 -0
  201. package/lib/utils/load-cip-capacity-display-config.js +61 -15
  202. package/lib/utils/manifest-source-emit.js +2 -0
  203. package/lib/utils/pipeline-upload-auth-retry.js +82 -0
  204. package/lib/utils/platform-controller-url.js +156 -0
  205. package/lib/utils/postgres-platform-bootstrap.js +199 -0
  206. package/lib/utils/postgres-wipe.js +9 -2
  207. package/lib/utils/resolve-env-mode.js +47 -0
  208. package/lib/utils/secrets-generator.js +31 -25
  209. package/lib/utils/secrets-missing-error.js +164 -0
  210. package/lib/utils/service-url-label.js +81 -0
  211. package/lib/utils/setup-quiet-output.js +25 -0
  212. package/lib/utils/template-helpers.js +15 -0
  213. package/lib/utils/token-manager-client-credentials.js +201 -0
  214. package/lib/utils/token-manager-messages.js +8 -0
  215. package/lib/utils/token-manager-refresh.js +12 -2
  216. package/lib/utils/token-manager.js +86 -105
  217. package/lib/utils/upload-pipeline-ui.js +12 -5
  218. package/lib/utils/upload-sync-options.js +42 -0
  219. package/lib/utils/url-declarative-public-base.js +21 -3
  220. package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
  221. package/lib/utils/validation-poll-ui.js +8 -0
  222. package/lib/utils/validation-run-stage.js +45 -0
  223. package/package.json +1 -1
  224. package/templates/applications/dataplane/application.yaml +2 -1
  225. package/templates/applications/dataplane/env.template +24 -1
  226. package/templates/applications/miso-controller/env.template +16 -0
  227. package/templates/external-system/README.md.hbs +74 -17
  228. package/templates/python/docker-compose.hbs +19 -18
  229. package/templates/typescript/docker-compose.hbs +18 -17
  230. package/lib/commands/test-governance-command-action.js +0 -55
  231. package/lib/commands/test-trust-command-action.js +0 -118
@@ -12,207 +12,13 @@
12
12
 
13
13
  'use strict';
14
14
 
15
- const path = require('path');
16
15
  const fs = require('fs');
17
- const chalk = require('chalk');
18
16
  const { detectAppType } = require('../utils/paths');
19
- const { resolveApplicationConfigPath, resolveRbacPath } = require('../utils/app-config-resolver');
20
- const { loadConfigFile, writeConfigFile, isYamlPath } = require('../utils/config-format');
21
- const logger = require('../utils/logger');
22
- const { repairEnvTemplate, normalizeSystemFileAuthAndConfig } = require('./repair-env-template');
23
- const { repairDatasourceFile } = require('./repair-datasource');
24
- const { mergeRbacFromDatasources, extractRbacFromSystem, migrateSystemRbacIntoRbacFile } = require('./repair-rbac');
17
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
18
+ const { loadConfigFile, isYamlPath } = require('../utils/config-format');
25
19
  const { discoverIntegrationFiles, buildEffectiveDatasourceFiles } = require('./repair-internal');
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');
38
-
39
- /** Allowed authentication methods for repair --auth (matches external-system schema) */
40
- const ALLOWED_AUTH = ['oauth2', 'aad', 'apikey', 'basic', 'queryParam', 'oidc', 'hmac', 'none'];
41
-
42
- function createRbacFromSystemIfNeeded(opts) {
43
- const { appPath, systemFilePath, systemParsed, dryRun, changes, format, backupCtx } = opts;
44
- if (resolveRbacPath(appPath)) return false;
45
- const rbacFromSystem = extractRbacFromSystem(systemParsed);
46
- if (!rbacFromSystem) return false;
47
- if (!dryRun) {
48
- const rbacFormat = format === 'json' ? 'json' : 'yaml';
49
- const defaultRbacPath = path.join(appPath, rbacFormat === 'json' ? 'rbac.json' : 'rbac.yaml');
50
- writeConfigFile(defaultRbacPath, rbacFromSystem, rbacFormat);
51
- delete systemParsed.roles;
52
- delete systemParsed.permissions;
53
- backupIntegrationFile(systemFilePath, backupCtx);
54
- writeConfigFile(systemFilePath, systemParsed);
55
- }
56
- changes.push('Created rbac.yaml from system roles/permissions');
57
- changes.push('Removed roles/permissions from system file (now in rbac file)');
58
- return true;
59
- }
60
-
61
- /**
62
- * Runs datasource repair for each file (v2.4 root dimensions, metadataSchema; optional expose/sync/test).
63
- * @param {string} appPath - Application path
64
- * @param {string[]} datasourceFiles - Datasource file names
65
- * @param {Object} options - { expose?: boolean, sync?: boolean, test?: boolean }
66
- * @param {boolean} dryRun - If true, do not write
67
- * @param {string[]} changes - Array to append change descriptions to
68
- * @returns {boolean} True if any datasource was updated
69
- */
70
- function runDatasourceRepairs(appPath, datasourceFiles, options, dryRun, changes) {
71
- if (!datasourceFiles || datasourceFiles.length === 0) return false;
72
- let updated = false;
73
- for (const fileName of datasourceFiles) {
74
- const filePath = path.join(appPath, fileName);
75
- if (!fs.existsSync(filePath)) continue;
76
- try {
77
- const parsed = loadConfigFile(filePath);
78
- const { updated: fileUpdated, changes: fileChanges } = repairDatasourceFile(parsed, {
79
- expose: options.expose,
80
- sync: options.sync,
81
- test: options.test
82
- });
83
- if (fileUpdated) {
84
- updated = true;
85
- fileChanges.forEach(c => changes.push(`${fileName}: ${c}`));
86
- if (!dryRun) {
87
- backupIntegrationFile(filePath, options.backupCtx);
88
- writeConfigFile(filePath, parsed);
89
- }
90
- }
91
- } catch (err) {
92
- logger.log(chalk.yellow(`⚠ Could not repair datasource ${fileName}: ${err.message}`));
93
- }
94
- }
95
- return updated;
96
- }
97
-
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 };
109
- }
20
+ const { runRepairExternalIntegration } = require('./repair-external-run');
110
21
 
111
- /**
112
- * Apply --auth: replace system file authentication with canonical block for the given method.
113
- * Preserves existing authentication.variables (e.g. baseUrl, tokenUrl) from the current system file.
114
- * @param {Object} ctx - Context with systemParsed, systemKey, auth, dryRun, changes
115
- * @returns {boolean} True if auth was replaced
116
- */
117
- function applyAuthMethod(ctx) {
118
- if (!ctx.auth || typeof ctx.auth !== 'string') return false;
119
- const method = ctx.auth.trim().toLowerCase();
120
- if (!ALLOWED_AUTH.includes(method)) {
121
- throw new Error(
122
- `Invalid --auth "${ctx.auth}". Allowed methods: ${ALLOWED_AUTH.join(', ')}`
123
- );
124
- }
125
- const existingAuth = ctx.systemParsed.authentication || ctx.systemParsed.auth || {};
126
- const { buildAuthenticationFromMethod } = require('../external-system/generator');
127
- const newAuth = buildAuthenticationFromMethod(ctx.systemKey, method);
128
- const existingVars =
129
- existingAuth.variables && typeof existingAuth.variables === 'object'
130
- ? existingAuth.variables
131
- : {};
132
- // When switching auth method, keep only connectivity fields from the wizard draft.
133
- // Do not let OAuth-era variables (tokenUrl, scope, headerName, …) override the new method.
134
- const mergedVariables = { ...newAuth.variables };
135
- for (const key of ['baseUrl', 'testEndpoint']) {
136
- if (existingVars[key] !== undefined && existingVars[key] !== null) {
137
- mergedVariables[key] = existingVars[key];
138
- }
139
- }
140
- ctx.systemParsed.authentication = {
141
- ...newAuth,
142
- variables: mergedVariables
143
- };
144
- if (existingAuth.displayName !== undefined) {
145
- ctx.systemParsed.authentication.displayName = existingAuth.displayName;
146
- }
147
- ctx.changes.push(`Set authentication method to ${method}`);
148
- return true;
149
- }
150
-
151
- /**
152
- * Runs all repair steps (integration block, system dataSources, auth/config, app key, datasource keys, rbac, env.template).
153
- * @param {Object} ctx - Context with appPath, configPath, variables, systemFilePath, systemParsed, systemKey, systemFiles, datasourceFiles, dryRun, changes, auth?, backupCtx?
154
- * @returns {{ updated: boolean, appKeyFixed: boolean, datasourceKeysFixed: boolean, rbacFileCreated: boolean, envTemplateRepaired: boolean }}
155
- */
156
- function runRepairSteps(ctx) {
157
- const bc = ctx.backupCtx;
158
- let updated = ensureExternalIntegrationBlock(
159
- ctx.variables, ctx.systemFiles, ctx.datasourceFiles, ctx.changes
160
- );
161
- const authReplaced = applyAuthMethod(ctx);
162
- updated = updated || authReplaced;
163
- const systemAuthConfigNormalized = normalizeSystemFileAuthAndConfig(
164
- ctx.systemParsed, ctx.systemKey, ctx.changes
165
- );
166
- const systemDataSourcesAligned = alignSystemFileDataSources(
167
- ctx.appPath, ctx.systemParsed, ctx.datasourceFiles, ctx.systemKey, ctx.dryRun, ctx.changes
168
- );
169
- const authVarsRemoved = removeAuthVarsFromConfiguration(
170
- ctx.systemParsed, ctx.systemKey, ctx.dryRun, ctx.changes
171
- );
172
- const systemFileUpdated =
173
- authReplaced || systemAuthConfigNormalized || systemDataSourcesAligned || authVarsRemoved;
174
- if (systemFileUpdated && !ctx.dryRun) {
175
- backupIntegrationFile(ctx.systemFilePath, bc);
176
- writeConfigFile(ctx.systemFilePath, ctx.systemParsed);
177
- }
178
- updated = updated || systemFileUpdated;
179
- const appKeyFixed = alignAppKeyWithSystem(
180
- ctx.variables, ctx.systemKey, ctx.systemParsed, ctx.changes
181
- );
182
- const datasourceKeysFixed = alignDatasourceSystemKeys(
183
- ctx.appPath, ctx.datasourceFiles, ctx.systemKey, ctx.dryRun, ctx.changes, bc
184
- );
185
- const rbacFileCreated = createRbacFromSystemIfNeeded({
186
- appPath: ctx.appPath,
187
- systemFilePath: ctx.systemFilePath,
188
- systemParsed: ctx.systemParsed,
189
- dryRun: ctx.dryRun,
190
- changes: ctx.changes,
191
- format: ctx.format,
192
- backupCtx: bc
193
- });
194
- const envTemplateRepaired = repairEnvTemplate(
195
- ctx.appPath, ctx.systemParsed, ctx.systemKey, ctx.dryRun, ctx.changes, bc
196
- );
197
- updated = updated || appKeyFixed || datasourceKeysFixed || rbacFileCreated || envTemplateRepaired;
198
- return {
199
- updated,
200
- appKeyFixed,
201
- datasourceKeysFixed,
202
- rbacFileCreated,
203
- envTemplateRepaired
204
- };
205
- }
206
-
207
- /**
208
- * Repairs external integration config: syncs files, aligns keys, creates rbac, regenerates manifest
209
- * @async
210
- * @param {string} appName - Application/integration name
211
- * @param {Object} [options] - Options
212
- * @param {boolean} [options.dryRun] - If true, only report changes; do not write
213
- * @param {boolean} [options.doc] - If true, regenerate README.md from deployment manifest
214
- * @returns {Promise<{ updated: boolean, changes: string[], systemFiles: string[], datasourceFiles: string[], appKeyFixed?: boolean, datasourceKeysFixed?: boolean, rbacFileCreated?: boolean, envTemplateRepaired?: boolean, manifestRegenerated?: boolean, readmeRegenerated?: boolean }>}
215
- */
216
22
  /**
217
23
  * Loads application config and discovers integration files; validates at least one system file exists.
218
24
  * @param {string} appPath - Application path
@@ -226,43 +32,13 @@ function loadConfigAndDiscover(appPath, configPath) {
226
32
  originalYamlContent = fs.readFileSync(configPath, 'utf8');
227
33
  }
228
34
  const { systemFiles, datasourceFiles: discoveredDatasourceFiles } = discoverIntegrationFiles(appPath);
229
- if (systemFiles.length === 0) {
35
+ if (!systemFiles || systemFiles.length === 0) {
230
36
  throw new Error(`No system file found in ${appPath}. Expected *-system.yaml or *-system.json`);
231
37
  }
232
38
  const datasourceFiles = buildEffectiveDatasourceFiles(appPath, discoveredDatasourceFiles, variables.externalIntegration?.dataSources);
233
39
  return { variables, originalYamlContent, systemFiles, datasourceFiles };
234
40
  }
235
41
 
236
- /**
237
- * Builds the repair result object from steps and flags.
238
- * @param {Object} steps - Result of runRepairSteps
239
- * @param {boolean} anyUpdated - Whether any repair made changes
240
- * @param {boolean} manifestRegenerated - Whether manifest was regenerated
241
- * @param {boolean} readmeRegenerated - Whether README was regenerated
242
- * @param {{ changes: string[], systemFiles: string[], datasourceFiles: string[] }} ctx - changes and file lists
243
- * @returns {Object} Combined result object
244
- */
245
- function buildRepairResult(steps, anyUpdated, manifestRegenerated, readmeRegenerated, ctx) {
246
- return Object.assign(
247
- {
248
- updated: anyUpdated,
249
- actionsPerformed: ctx.actionsPerformed === true,
250
- changes: ctx.changes,
251
- systemFiles: ctx.systemFiles,
252
- datasourceFiles: ctx.datasourceFiles,
253
- backupPaths: ctx.backupPaths || []
254
- },
255
- {
256
- appKeyFixed: steps.appKeyFixed,
257
- datasourceKeysFixed: steps.datasourceKeysFixed,
258
- rbacFileCreated: steps.rbacFileCreated,
259
- envTemplateRepaired: steps.envTemplateRepaired,
260
- manifestRegenerated,
261
- readmeRegenerated
262
- }
263
- );
264
- }
265
-
266
42
  /**
267
43
  * Validates repair inputs and resolves app path and config path.
268
44
  * @param {string} appName - Application name
@@ -291,83 +67,40 @@ function createRepairBackupContext(options, dryRun) {
291
67
  const noBackup = options.noBackup === true || options.backup === false;
292
68
  const backupPaths = [];
293
69
  const backedUpFiles = new Set();
294
- const backupCtx = { dryRun, noBackup, backupPaths, backedUpFiles };
70
+ const backupCtx = {
71
+ dryRun,
72
+ noBackup,
73
+ backupPaths,
74
+ backedUpFiles,
75
+ changedFiles: [],
76
+ wouldChangeFiles: []
77
+ };
295
78
  return { backupPaths, backupCtx };
296
79
  }
297
80
 
298
- async function maybeRunOpenApiSyncForMcp({ options, dryRun, appPath, systemKey, datasourceFiles, changes }) {
299
- if (!options.api) return false;
300
- try {
301
- const openapiLines = await maybeSyncOpenApiFilesForMcp({
302
- enabled: true,
303
- dryRun,
304
- appPath,
305
- systemKey,
306
- datasourceFiles
307
- });
308
- openapiLines.forEach((l) => changes.push(l));
309
- return openapiLines.length > 0;
310
- } catch (err) {
311
- changes.push(`OpenAPI upload for MCP failed: ${err.message}`);
312
- return false;
313
- }
314
- }
315
-
81
+ /**
82
+ * Repairs external integration config: syncs files, aligns keys, creates rbac, regenerates manifest
83
+ * @async
84
+ * @param {string} appName - Application/integration name
85
+ * @param {Object} [options] - Options
86
+ * @param {boolean} [options.dryRun] - If true, only report changes; do not write
87
+ * @param {boolean} [options.doc] - If true, regenerate README.md from deployment manifest
88
+ * @returns {Promise<Object>}
89
+ */
316
90
  async function repairExternalIntegration(appName, options = {}) {
317
91
  const { appPath, configPath, dryRun, authOption } = await validateAndResolveRepairPaths(appName, options);
318
92
  const { backupPaths, backupCtx } = createRepairBackupContext(options, dryRun);
319
- const { variables, originalYamlContent, systemFiles, datasourceFiles: initialDatasourceFiles } = loadConfigAndDiscover(appPath, configPath);
320
- const changes = [];
321
- const { systemFilePath, systemParsed, systemKey } = resolveSystemContext(appPath, systemFiles);
322
- const rbacFmt = options.format === 'json' ? 'json' : 'yaml';
323
- const { updated: keysNormalized, datasourceFiles } = normalizeDatasourceKeysAndFilenames(appPath, initialDatasourceFiles, systemKey, { variables, dryRun, changes, backupCtx });
324
- const steps = runRepairSteps({
93
+ const loaded = loadConfigAndDiscover(appPath, configPath);
94
+ return runRepairExternalIntegration({
95
+ appName,
96
+ options,
325
97
  appPath,
326
98
  configPath,
327
- variables,
328
- systemFilePath,
329
- systemParsed,
330
- systemKey,
331
- systemFiles,
332
- datasourceFiles,
333
- dryRun,
334
- changes,
335
- auth: authOption,
336
- format: rbacFmt,
337
- backupCtx
338
- });
339
- const datasourceRepairUpdated = runDatasourceRepairs(appPath, datasourceFiles, {
340
- expose: Boolean(options.expose),
341
- sync: Boolean(options.sync),
342
- test: Boolean(options.test),
343
- backupCtx
344
- }, dryRun, changes);
345
- const openapiActionsPerformed = await maybeRunOpenApiSyncForMcp({
346
- options,
347
99
  dryRun,
348
- appPath,
349
- systemKey,
350
- datasourceFiles,
351
- changes
352
- });
353
- const { rbacMigratedFromSystem, rbacMergeUpdated } = maybeRepairRbac(
354
- appPath,
355
- systemFilePath,
356
- systemParsed,
357
- datasourceFiles,
358
- { rbac: Boolean(options.rbac), rbacFmt, dryRun, changes, backupCtx }
359
- );
360
- const anyLocalUpdated = keysNormalized || steps.updated || datasourceRepairUpdated || rbacMigratedFromSystem || rbacMergeUpdated;
361
- const manifestRegenerated = (anyLocalUpdated && !dryRun) ? await persistChangesAndRegenerate(
362
- { configPath, variables, appName, appPath, changes, originalYamlContent, backupCtx }
363
- ) : false;
364
- const readmeRegenerated = await regenerateReadmeIfRequested(appName, appPath, { ...options, backupCtx }, changes);
365
- return buildRepairResult(steps, anyLocalUpdated, manifestRegenerated, readmeRegenerated, {
366
- actionsPerformed: openapiActionsPerformed,
367
- changes,
368
- systemFiles,
369
- datasourceFiles,
370
- backupPaths
100
+ authOption,
101
+ backupPaths,
102
+ backupCtx,
103
+ ...loaded
371
104
  });
372
105
  }
373
106
 
@@ -0,0 +1,184 @@
1
+ /**
2
+ * @fileoverview aifabrix secret get – read one secret (local user file or shared-only)
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const path = require('path');
8
+ const { getAifabrixSecretsPath } = require('../core/config');
9
+ const { decryptSecretsObject } = require('../core/secrets-load');
10
+ const { loadPrimaryUserSecrets } = require('../utils/secrets-utils');
11
+ const { loadConfiguredSharedSecretsStore } = require('../utils/remote-secrets-loader');
12
+ const remoteDevAuth = require('../utils/remote-dev-auth');
13
+ const devApi = require('../api/dev.api');
14
+ const logger = require('../utils/logger');
15
+
16
+ const REMOTE_NOT_CONFIGURED_MSG =
17
+ 'Remote server is not configured. Set remote-server and run "aifabrix dev init" first.';
18
+
19
+ /**
20
+ * Human-readable label for which store(s) were queried (paths included for file stores).
21
+ * @param {{ shared?: boolean }} options
22
+ * @returns {Promise<string>}
23
+ */
24
+ async function resolveSecretsScopeLabel(options) {
25
+ const useSharedOnly = Boolean(options.shared || options['shared']);
26
+ const generalSecretsPath = await getAifabrixSecretsPath();
27
+
28
+ if (useSharedOnly) {
29
+ if (!generalSecretsPath) {
30
+ return 'shared secrets (aifabrix-secrets not configured; run aifabrix secret set-secrets-file <path>)';
31
+ }
32
+ const target = await remoteDevAuth.resolveSharedSecretsEndpoint(generalSecretsPath);
33
+ if (remoteDevAuth.isRemoteSecretsUrl(target)) {
34
+ const host = remoteDevAuth.getSharedSecretsRemoteHostname(target) || target;
35
+ return `shared secrets API (${host})`;
36
+ }
37
+ const resolved = path.isAbsolute(target) ? target : path.resolve(process.cwd(), target);
38
+ return `shared secrets file ${resolved}`;
39
+ }
40
+
41
+ const pathsUtil = require('../utils/paths');
42
+ const userPath = pathsUtil.getPrimaryUserSecretsLocalPath();
43
+ return `local secrets file ${userPath}`;
44
+ }
45
+
46
+ /**
47
+ * @param {unknown} value
48
+ * @returns {boolean}
49
+ */
50
+ function isSecretPresent(value) {
51
+ if (value === undefined || value === null) {
52
+ return false;
53
+ }
54
+ return String(value).trim() !== '';
55
+ }
56
+
57
+ /**
58
+ * @param {Array<{ name?: string, key?: string, value?: unknown }>} items
59
+ * @returns {Record<string, string>}
60
+ */
61
+ function remoteSecretItemsToMap(items) {
62
+ const raw = {};
63
+ for (const item of items) {
64
+ const name = item?.name || item?.key;
65
+ if (typeof name === 'string' && item.value !== undefined) {
66
+ raw[name] = String(item.value);
67
+ }
68
+ }
69
+ return raw;
70
+ }
71
+
72
+ /**
73
+ * @param {string} target
74
+ * @returns {Promise<Record<string, string>>}
75
+ */
76
+ async function loadRemoteSharedSecretsDecrypted(target) {
77
+ const auth = await remoteDevAuth.getRemoteDevAuth();
78
+ if (!auth) {
79
+ throw new Error(REMOTE_NOT_CONFIGURED_MSG);
80
+ }
81
+ const items = await devApi.listSecrets(
82
+ auth.serverUrl,
83
+ auth.clientCertPem,
84
+ auth.serverCaPem || undefined,
85
+ target
86
+ );
87
+ const label = remoteDevAuth.getSharedSecretsRemoteHostname(target) || 'shared secrets API';
88
+ return decryptSecretsObject(remoteSecretItemsToMap(items), { defaultSourceLabel: label });
89
+ }
90
+
91
+ /**
92
+ * Load shared secrets (file or remote), decrypted.
93
+ * @returns {Promise<Record<string, string>>}
94
+ */
95
+ async function loadSharedSecretsDecrypted() {
96
+ const generalSecretsPath = await getAifabrixSecretsPath();
97
+ if (!generalSecretsPath) {
98
+ throw new Error('Shared secrets not configured. Set aifabrix-secrets in config.yaml.');
99
+ }
100
+
101
+ const target = await remoteDevAuth.resolveSharedSecretsEndpoint(generalSecretsPath);
102
+ if (remoteDevAuth.isRemoteSecretsUrl(target)) {
103
+ return loadRemoteSharedSecretsDecrypted(target);
104
+ }
105
+
106
+ const raw = await loadConfiguredSharedSecretsStore();
107
+ if (!raw || typeof raw !== 'object') {
108
+ return {};
109
+ }
110
+ const resolvedPath = path.isAbsolute(target) ? target : path.resolve(process.cwd(), target);
111
+ return decryptSecretsObject(raw, { defaultSourceLabel: resolvedPath });
112
+ }
113
+
114
+ /**
115
+ * Load and decrypt the primary user secrets file only (not aifabrix-secrets / shared).
116
+ * @returns {Promise<Record<string, string>>}
117
+ */
118
+ async function loadLocalUserSecretsDecrypted() {
119
+ const pathsUtil = require('../utils/paths');
120
+ const userPath = pathsUtil.getPrimaryUserSecretsLocalPath();
121
+ const raw = loadPrimaryUserSecrets();
122
+ return decryptSecretsObject(raw || {}, { defaultSourceLabel: userPath });
123
+ }
124
+
125
+ /**
126
+ * Load secrets for lookup: local user file (default) or shared store only (--shared).
127
+ * @param {{ shared?: boolean }} options
128
+ * @returns {Promise<Record<string, string>>}
129
+ */
130
+ async function loadSecretsForGet(options) {
131
+ if (options.shared || options['shared']) {
132
+ return loadSharedSecretsDecrypted();
133
+ }
134
+ return loadLocalUserSecretsDecrypted();
135
+ }
136
+
137
+ /**
138
+ * Handle secret get command.
139
+ * @param {string} key - Secret key (no kv:// prefix)
140
+ * @param {Object} options
141
+ * @param {boolean} [options.shared] - Shared store only (aifabrix-secrets); default is local user file only
142
+ * @param {boolean} [options.exists] - Exit 0 when present; no stdout (script-friendly)
143
+ * @param {boolean} [options.json] - JSON { key, exists, value }
144
+ * @returns {Promise<{ key: string, exists: boolean, value?: string }>}
145
+ */
146
+ async function handleSecretsGet(key, options = {}) {
147
+ if (!key || typeof key !== 'string') {
148
+ throw new Error('Secret key is required and must be a string');
149
+ }
150
+ if (key.startsWith('kv://')) {
151
+ throw new Error(
152
+ 'Secret key must not start with kv://. Use the key path without the prefix (e.g. hubspot-demo/token).'
153
+ );
154
+ }
155
+
156
+ const secrets = await loadSecretsForGet(options);
157
+ const value = secrets[key];
158
+ const present = isSecretPresent(value);
159
+
160
+ if (!present) {
161
+ const scope = await resolveSecretsScopeLabel(options);
162
+ throw new Error(`Secret '${key}' is missing or empty in ${scope}.`);
163
+ }
164
+
165
+ if (options.exists || options['exists']) {
166
+ return { key, exists: true };
167
+ }
168
+
169
+ if (options.json || options['json']) {
170
+ logger.log(JSON.stringify({ key, exists: true, value: String(value) }));
171
+ return { key, exists: true, value: String(value) };
172
+ }
173
+
174
+ logger.log(String(value));
175
+ return { key, exists: true, value: String(value) };
176
+ }
177
+
178
+ module.exports = {
179
+ handleSecretsGet,
180
+ isSecretPresent,
181
+ loadSecretsForGet,
182
+ loadLocalUserSecretsDecrypted,
183
+ resolveSecretsScopeLabel
184
+ };
@@ -56,6 +56,8 @@ function listKeysFromYamlFile(filePath) {
56
56
  * @param {string} filePath - Absolute path
57
57
  */
58
58
  function writeEmptySecretsFile(filePath) {
59
+ const { assertWritableSecretsPathForTests } = require('../utils/aifabrix-test-runtime-guard');
60
+ assertWritableSecretsPathForTests(filePath);
59
61
  const yamlContent = yaml.dump({}, { indent: 2, lineWidth: -1, noRefs: true, sortKeys: false });
60
62
  fs.writeFileSync(filePath, yamlContent, { mode: 0o600 });
61
63
  }
@@ -217,4 +219,4 @@ async function handleSecretsRemoveAll(options) {
217
219
  await removeAllSharedSecrets(generalSecretsPath, options);
218
220
  }
219
221
 
220
- module.exports = { handleSecretsRemoveAll };
222
+ module.exports = { handleSecretsRemoveAll, writeEmptySecretsFile };