@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
@@ -23,7 +23,7 @@ const { handleTeardown } = require('../commands/teardown');
23
23
 
24
24
  const SETUP_HELP_AFTER = `
25
25
  What this command does:
26
- - With no infra running: prompts for admin email/password (used for Postgres, pgAdmin, Keycloak). The email is saved as adminEmail in config.yaml so the next setup can show it as the default (you can still change it). Prompts for an AI tool (OpenAI or Azure OpenAI) only when keys are not already set, then runs up-infra and up-platform.
26
+ - With no infra running: prompts for installation profile (dev = one password for infra + Keycloak + platform UI; pro = autogen or manual split passwords). Credentials are stored in admin-secrets.env (encrypted secure:// values), not in secrets.local.yaml unless you pass --syncAdminKv on up-infra. The admin email is saved in config.yaml for the next run. Prompts for an AI tool (OpenAI or Azure OpenAI) only when keys are not already set, then runs up-infra and up-platform.
27
27
  - With infra running: shows a mode menu:
28
28
  1) Re-install — stop infra and remove all volumes, then up-infra + up-platform --force.
29
29
  2) Wipe data — drop every database and DB user, then up-infra + up-platform --force.
@@ -54,6 +54,11 @@ function registerSetup(program) {
54
54
  .command('setup')
55
55
  .description('Install or refresh the full AI Fabrix platform (infra + miso + dataplane)')
56
56
  .option('-d, --developer <id>', 'Pin developer ID before fresh install (fresh path only; ignored when infra is already up)')
57
+ .option('--platform <mode>', 'Platform mode: single (direct ports) or full (Traefik front door + multi-env)')
58
+ .option('--installation <profile>', 'Installation profile: dev (one password) or pro (autogen/manual)')
59
+ .option('--pro-password-mode <mode>', 'Pro profile only: autogen or manual')
60
+ .option('--admin-email <email>', 'Admin email (non-interactive fresh install)')
61
+ .option('--admin-password <password>', 'Admin password for dev or pro manual-single (non-interactive)')
57
62
  .option('-y, --yes', 'Skip destructive confirmation prompts (re-install, wipe data, teardown)')
58
63
  .addHelpText('after', SETUP_HELP_AFTER)
59
64
  .action(async(options) => {
@@ -10,6 +10,7 @@ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
10
10
  const { handleCommandError } = require('../utils/cli-utils');
11
11
  const { handleSecretsSet } = require('../commands/secrets-set');
12
12
  const { handleSecretsList } = require('../commands/secrets-list');
13
+ const { handleSecretsGet } = require('../commands/secrets-get');
13
14
  const { handleSecretsRemove } = require('../commands/secrets-remove');
14
15
  const { handleSecretsValidate } = require('../commands/secrets-validate');
15
16
  const { handleSecure } = require('../commands/secure');
@@ -18,7 +19,7 @@ const logger = require('../utils/logger');
18
19
 
19
20
  const SECRET_GROUP_HELP_AFTER = `
20
21
  Subcommands:
21
- list, set, remove, remove-all User secrets.local.yaml (add --shared for shared/remote)
22
+ list, get, set, remove, remove-all User secrets.local.yaml (add --shared for shared/remote)
22
23
  set-secrets-file Point config at a shared secrets file or https URL
23
24
  validate YAML structure (+ optional --naming)
24
25
 
@@ -38,6 +39,17 @@ Examples:
38
39
  Shared over https: key BASH_NPM_TOKEN --shared → NPM_TOKEN available in terminal (exported).
39
40
  `;
40
41
 
42
+ const SECRET_GET_HELP_AFTER = `
43
+ Examples:
44
+ $ aifabrix secret get hubspot-demo/token --shared --exists
45
+ $ aifabrix secret get myapp/clientSecret
46
+ $ aifabrix secret get hubspot/apiKey --shared --json
47
+
48
+ --exists Script mode: exit 0 when the key is present and non-empty; no output.
49
+ Default Local user secrets.local.yaml only (same target as secret set without --shared).
50
+ --shared aifabrix-secrets store only (team file or remote API; same as secret set --shared).
51
+ `;
52
+
41
53
  const SECRET_LIST_HELP_AFTER = `
42
54
  Examples:
43
55
  $ aifabrix secret list
@@ -168,6 +180,25 @@ function setupSecureCommand(program) {
168
180
  });
169
181
  }
170
182
 
183
+ function setupSecretGetCommand(secretCmd) {
184
+ secretCmd
185
+ .command('get <key>')
186
+ .description('Get one secret (local user file by default; --shared for team store; --exists for scripts)')
187
+ .addHelpText('after', SECRET_GET_HELP_AFTER)
188
+ .option('--shared', 'Read from shared secrets (config aifabrix-secrets or remote API)')
189
+ .option('--exists', 'Exit 0 when key is present and non-empty; print nothing (for scripts)')
190
+ .option('--json', 'Output JSON { key, exists, value }')
191
+ .action(async(key, options) => {
192
+ try {
193
+ await config.ensureSecretsEncryptionKey();
194
+ await handleSecretsGet(key, options);
195
+ } catch (error) {
196
+ handleCommandError(error, 'secret get');
197
+ process.exit(1);
198
+ }
199
+ });
200
+ }
201
+
171
202
  function setupSecretListCommand(secretCmd) {
172
203
  secretCmd
173
204
  .command('list')
@@ -196,6 +227,7 @@ function setupSecretsCommands(program) {
196
227
  .addHelpText('after', SECRET_GROUP_HELP_AFTER);
197
228
 
198
229
  setupSecretListCommand(secretCmd);
230
+ setupSecretGetCommand(secretCmd);
199
231
 
200
232
  secretCmd
201
233
  .command('set <key> <value>')
@@ -26,8 +26,11 @@ Examples:
26
26
  $ aifabrix repair hubspot-demo --dry-run
27
27
  Show what would change without writing files.
28
28
 
29
+ $ aifabrix repair hubspot-demo --auth bearerToken
30
+ Private-app style apikey: Authorization + Bearer prefix; preserves baseUrl/testEndpoint when present. Warns if testEndpoint is missing.
31
+
29
32
  $ aifabrix repair hubspot-demo --auth oauth2
30
- Set authentication method; updates the system file and env.template (oauth2, aad, apikey, basic, …).
33
+ Set authentication method; updates the system file and env.template (oauth2, aad, apikey, bearerToken, basic, …).
31
34
 
32
35
  $ aifabrix repair hubspot-demo --rbac --expose --sync --test
33
36
  Optional datasource fixes: RBAC roles/permissions, exposed.schema from attributes, default sync block, testPayload stubs.
@@ -38,8 +41,25 @@ Examples:
38
41
  Before overwriting files, repair writes timestamped backups under integration/<systemKey>/backup/ (same as datasource capability copy). Use --no-backup to skip.
39
42
  `;
40
43
 
44
+ const RESOLVE_HELP_AFTER = `
45
+ Examples:
46
+ $ aifabrix resolve hubspot-demo
47
+ External integration: resolves integration/<systemKey>/env.template to .env using local host ports (no Docker rewrite).
48
+
49
+ $ aifabrix resolve dataplane --fresh
50
+ Regenerate the full .env from env.template (no merge with an existing file). Use after adding many new variables or when a normal resolve did not pick up new template lines.
51
+
52
+ $ aifabrix secret set hubspot-demo/token "<your-token>"
53
+ Store a secret when resolve reports a missing kv:// reference.
54
+
55
+ If a key is not required by your system file, comment out its line in env.template instead of creating a secret.
56
+ $ aifabrix repair hubspot-demo
57
+ Align env.template with authentication.security and comment stale OAuth KV lines after --auth bearerToken.
58
+ `;
59
+
41
60
  module.exports = {
42
61
  JSON_HELP_AFTER,
43
62
  VALIDATE_HELP_AFTER,
44
- REPAIR_HELP_AFTER
63
+ REPAIR_HELP_AFTER,
64
+ RESOLVE_HELP_AFTER
45
65
  };
@@ -0,0 +1,198 @@
1
+ /**
2
+ * `aifabrix repair` command wiring and CLI output (split from setup-utility for size limits).
3
+ *
4
+ * @fileoverview Repair CLI command
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const chalk = require('chalk');
12
+ const logger = require('../utils/logger');
13
+ const { handleCommandError, logOfflinePathWhenType } = require('../utils/cli-utils');
14
+ const { detectAppType } = require('../utils/paths');
15
+ const { formatSuccessParagraph, formatWarningLine } = require('../utils/cli-test-layout-chalk');
16
+ const { REPAIR_HELP_AFTER } = require('./setup-utility-help-after');
17
+
18
+ /**
19
+ * @param {string} appName
20
+ * @param {object} options
21
+ * @param {string} format
22
+ * @returns {object}
23
+ */
24
+ function buildRepairExternalIntegrationOptions(appName, options, format) {
25
+ const all = options.all === true;
26
+ return {
27
+ auth: options.auth,
28
+ doc: options.doc || all,
29
+ dryRun: options.dryRun,
30
+ format,
31
+ rbac: options.rbac || all,
32
+ expose: options.expose || all,
33
+ sync: options.sync || all,
34
+ api: options.api || all,
35
+ test: options.test || all,
36
+ backup: options.backup,
37
+ noBackup: options.noBackup
38
+ };
39
+ }
40
+
41
+ /**
42
+ * @param {string[]} changedFiles
43
+ * @param {boolean} dryRun
44
+ */
45
+ function logRepairChangedFiles(changedFiles, dryRun) {
46
+ if (!Array.isArray(changedFiles) || changedFiles.length === 0) return;
47
+ const title = dryRun ? '\nWould change files:' : '\nChanged files:';
48
+ logger.log(chalk.white(title));
49
+ changedFiles.forEach((filePath) => logger.log(chalk.cyan(` ${filePath}`)));
50
+ }
51
+
52
+ /**
53
+ * @param {string[]} warnings
54
+ */
55
+ function logRepairWarnings(warnings) {
56
+ if (!Array.isArray(warnings) || warnings.length === 0) return;
57
+ warnings.forEach((msg) => logger.log(formatWarningLine(msg)));
58
+ }
59
+
60
+ /**
61
+ * @param {string[]} changedFiles
62
+ * @param {string[]} warnings
63
+ */
64
+ function logRepairNextActions(changedFiles, warnings) {
65
+ const hasFiles = Array.isArray(changedFiles) && changedFiles.length > 0;
66
+ const hasWarnings = Array.isArray(warnings) && warnings.length > 0;
67
+ if (!hasFiles && !hasWarnings) return;
68
+ logger.log(chalk.white('\nNext actions:'));
69
+ if (hasFiles) {
70
+ logger.log(chalk.gray(' • Open the paths above in your editor and review the diff'));
71
+ logger.log(chalk.gray(' • Run: aifabrix validate <systemKey>'));
72
+ }
73
+ if (hasWarnings) {
74
+ logger.log(chalk.gray(' • Fix warnings in the system file (especially authentication.variables.testEndpoint)'));
75
+ logger.log(chalk.gray(' • Run: aifabrix json <systemKey> && aifabrix upload <systemKey>'));
76
+ }
77
+ }
78
+
79
+ /**
80
+ * @param {object} options
81
+ * @param {object} result
82
+ * @param {string[]} changedFiles
83
+ */
84
+ function logRepairDryRunOutcome(options, result, changedFiles) {
85
+ logger.log(chalk.yellow('\nWould apply:'));
86
+ result.changes.forEach((c) => logger.log(chalk.gray(` ${c}`)));
87
+ logRepairChangedFiles(changedFiles, true);
88
+ logRepairWarnings(result.warnings);
89
+ logRepairNextActions(changedFiles, result.warnings);
90
+ }
91
+
92
+ /**
93
+ * @param {object} result
94
+ * @param {string[]} changedFiles
95
+ */
96
+ function logRepairSuccessOutcome(result, changedFiles) {
97
+ if (result.updated) {
98
+ logger.log(formatSuccessParagraph('Repaired external integration config.'));
99
+ logRepairChangedFiles(changedFiles, false);
100
+ logRepairNextActions(changedFiles, result.warnings);
101
+ return;
102
+ }
103
+ if (Array.isArray(result.changes) && result.changes.length > 0) {
104
+ logger.log(formatSuccessParagraph('Repair actions completed.'));
105
+ result.changes.forEach((c) => logger.log(chalk.gray(` ${c}`)));
106
+ logRepairChangedFiles(changedFiles, false);
107
+ logRepairNextActions(changedFiles, result.warnings);
108
+ return;
109
+ }
110
+ if (result.readmeRegenerated) {
111
+ logger.log(formatSuccessParagraph('Regenerated README.md from deployment manifest.'));
112
+ return;
113
+ }
114
+ if (Array.isArray(result.warnings) && result.warnings.length > 0) {
115
+ logRepairNextActions(changedFiles, result.warnings);
116
+ return;
117
+ }
118
+ logger.log(chalk.gray('No changes needed; config already matches files on disk.'));
119
+ }
120
+
121
+ /**
122
+ * @param {object} options
123
+ * @param {object} result
124
+ */
125
+ function logRepairResult(options, result) {
126
+ const changedFiles = result.changedFiles;
127
+ if (options.dryRun && result.updated && result.changes.length > 0) {
128
+ logRepairDryRunOutcome(options, result, changedFiles);
129
+ return;
130
+ }
131
+ if (Array.isArray(result.backupPaths) && result.backupPaths.length > 0) {
132
+ result.backupPaths.forEach((p) => logger.log(chalk.gray(`Backup: ${p}`)));
133
+ }
134
+ logRepairWarnings(result.warnings);
135
+ logRepairSuccessOutcome(result, changedFiles);
136
+ }
137
+
138
+ /**
139
+ * @param {string} appName
140
+ * @param {object} options
141
+ * @returns {Promise<void>}
142
+ */
143
+ async function handleRepairCommand(appName, options) {
144
+ const { repairExternalIntegration } = require('../commands/repair');
145
+ const { appPath } = await detectAppType(appName);
146
+ logOfflinePathWhenType(appPath);
147
+ let format = 'yaml';
148
+ try {
149
+ const config = require('../core/config');
150
+ format = (await config.getFormat()) || format;
151
+ } catch (_) {
152
+ /* default yaml when config unavailable */
153
+ }
154
+ const result = await repairExternalIntegration(
155
+ appName,
156
+ buildRepairExternalIntegrationOptions(appName, options, format)
157
+ );
158
+ logRepairResult(options, result);
159
+ }
160
+
161
+ /**
162
+ * @param {import('commander').Command} program
163
+ */
164
+ function setupRepairCommand(program) {
165
+ program.command('repair <systemKey>')
166
+ .description('Fix external integration drift (files, RBAC, manifest, …)')
167
+ .option('--all', 'Run all repair actions (api, doc, expose, rbac, sync, test)')
168
+ .option(
169
+ '--api',
170
+ 'Validate and sync API contracts needed for OpenAPI/MCP (uses local OpenAPI specs at integration/<systemKey>/openapi/*.json when present)'
171
+ )
172
+ .option(
173
+ '--auth <method>',
174
+ 'Set authentication method (oauth2, aad, apikey, bearerToken, basic, queryParam, oidc, hmac, none); updates system file and env.template'
175
+ )
176
+ .option('--doc', 'Regenerate README.md from deployment manifest')
177
+ .option('--expose', 'Set exposed.schema on each datasource from all fieldMappings.attributes keys (metadata.<key>); removes deprecated exposed.attributes if present')
178
+ .option('--rbac', 'Ensure RBAC permissions per datasource and add default Admin/Reader roles if none exist')
179
+ .option('--sync', 'Add default sync section to datasources that lack it')
180
+ .option('--test', 'Generate testPayload.payloadTemplate and testPayload.expectedResult from attributes')
181
+ .option('--no-backup', 'Skip timestamped copies under integration/<systemKey>/backup/')
182
+ .option('--dry-run', 'Report changes only; do not write')
183
+ .addHelpText('after', REPAIR_HELP_AFTER)
184
+ .action(async(appName, options) => {
185
+ try {
186
+ await handleRepairCommand(appName, options);
187
+ } catch (error) {
188
+ handleCommandError(error, 'repair');
189
+ process.exit(1);
190
+ }
191
+ });
192
+ }
193
+
194
+ module.exports = {
195
+ setupRepairCommand,
196
+ buildRepairExternalIntegrationOptions,
197
+ logRepairResult
198
+ };
@@ -8,13 +8,18 @@
8
8
 
9
9
  'use strict';
10
10
 
11
+ const path = require('path');
11
12
  const chalk = require('chalk');
12
13
  const secrets = require('../core/secrets');
14
+ const secretsEnsure = require('../core/secrets-ensure');
13
15
  const logger = require('../utils/logger');
14
16
  const { handleCommandError } = require('../utils/cli-utils');
15
17
  const coreConfig = require('../core/config');
18
+ const pathsUtil = require('../utils/paths');
16
19
  const { resolvePreferLocalEnvOutputPathFlag } = require('../utils/applications-config-defaults');
17
20
  const { getResolveAppPath } = require('../utils/paths');
21
+ const { resolveGenerateEnvEnvironment } = require('../utils/resolve-env-mode');
22
+ const { RESOLVE_HELP_AFTER } = require('./setup-utility-help-after');
18
23
 
19
24
  /**
20
25
  * @param {string} appName
@@ -50,10 +55,17 @@ async function runResolveJsonOutput(appName, appPath, envOnly, envPath, options,
50
55
  * @param {string} envPath
51
56
  * @param {boolean} envOnly
52
57
  * @param {{ skipValidation?: boolean }} options
58
+ * @param {'local'|'docker'} environment
53
59
  * @returns {Promise<void>}
54
60
  */
55
- async function runResolveHumanOutput(appName, envPath, envOnly, options) {
61
+ async function runResolveHumanOutput(appName, envPath, envOnly, options, environment) {
56
62
  logger.log(`✔ Generated .env file: ${envPath}`);
63
+ if (options.fresh) {
64
+ logger.log(chalk.gray(' Wrote full resolved content (--fresh; did not merge with an existing .env).'));
65
+ }
66
+ if (environment === 'local') {
67
+ logger.log(chalk.gray(' Resolved for local use (host ports; no Docker port rewrite).'));
68
+ }
57
69
  logger.log(chalk.gray(' Note: up-platform / up-miso / up-dataplane / register / build resolve secrets in memory only.'));
58
70
  logger.log(chalk.gray(` Re-run "aifabrix resolve ${appName}" whenever you need an on-disk .env again.`));
59
71
  if (envOnly) {
@@ -77,9 +89,26 @@ async function runResolveHumanOutput(appName, envPath, envOnly, options) {
77
89
  * @param {{ force?: boolean, skipValidation?: boolean, json?: boolean }} options
78
90
  * @returns {Promise<void>}
79
91
  */
92
+ async function ensureResolveSecretsFromEnvTemplate(appPath, appName) {
93
+ const templatePath = path.join(appPath, 'env.template');
94
+ let emptyValuesForCredentials = false;
95
+ try {
96
+ const appType = await pathsUtil.detectAppType(appName);
97
+ emptyValuesForCredentials = appType?.baseDir === 'integration';
98
+ } catch {
99
+ emptyValuesForCredentials = false;
100
+ }
101
+ await secretsEnsure.ensureSecretsFromEnvTemplate(templatePath, {
102
+ preferredFilePath: pathsUtil.getPrimaryUserSecretsLocalPath(),
103
+ placeholderContext: secretsEnsure.buildInfraPlaceholderContext({}),
104
+ emptyValuesForCredentials
105
+ });
106
+ }
107
+
80
108
  async function runResolveAppAction(appName, options) {
81
109
  const jsonMode = options.json === true;
82
110
  const { appPath, envOnly } = await getResolveAppPath(appName);
111
+ await ensureResolveSecretsFromEnvTemplate(appPath, appName);
83
112
  const manifestEmit = require('../utils/manifest-source-emit');
84
113
  manifestEmit.emitManifestMetadataLineIfTTY(logger, {
85
114
  appKey: appName,
@@ -89,24 +118,26 @@ async function runResolveAppAction(appName, options) {
89
118
  });
90
119
  const userCfg = await coreConfig.getConfig();
91
120
  const preferLocalEnvOutputPath = await resolvePreferLocalEnvOutputPathFlag(userCfg, appName);
121
+ const environment = resolveGenerateEnvEnvironment(appPath, appName);
92
122
  const envPath = await secrets.generateEnvFile(
93
123
  appName,
94
124
  undefined,
95
- 'docker',
125
+ environment,
96
126
  options.force,
97
127
  {
98
128
  appPath,
99
129
  envOnly,
100
130
  skipOutputPath: false,
101
131
  preserveFromPath: null,
102
- preferLocalEnvOutputPath
132
+ preferLocalEnvOutputPath,
133
+ freshEnv: options.fresh === true
103
134
  }
104
135
  );
105
136
  if (jsonMode) {
106
137
  await runResolveJsonOutput(appName, appPath, envOnly, envPath, options, manifestEmit);
107
138
  return;
108
139
  }
109
- await runResolveHumanOutput(appName, envPath, envOnly, options);
140
+ await runResolveHumanOutput(appName, envPath, envOnly, options, environment);
110
141
  }
111
142
 
112
143
  /**
@@ -116,7 +147,12 @@ async function runResolveAppAction(appName, options) {
116
147
  function setupResolveCommand(program) {
117
148
  program.command('resolve <appKey|systemKey>')
118
149
  .description('Generate .env from template (builder/<appKey>/ or integration/<systemKey>/); optional validate after')
150
+ .addHelpText('after', RESOLVE_HELP_AFTER)
119
151
  .option('-f, --force', 'Generate missing secret keys in secrets file')
152
+ .option(
153
+ '--fresh',
154
+ 'Replace .env from env.template (no merge with existing .env or envOutputPath; same as deleting .env first)'
155
+ )
120
156
  .option('--skip-validation', 'Skip file validation after generating .env')
121
157
  .option('--json', 'Print JSON summary (envPath, manifestSource, optional validation)')
122
158
  .action(async(appName, options) => {
@@ -0,0 +1,197 @@
1
+ /**
2
+ * `aifabrix validate` and `diff` command wiring (split from setup-utility for size limits).
3
+ *
4
+ * @fileoverview Validate and diff CLI commands
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const chalk = require('chalk');
13
+ const logger = require('../utils/logger');
14
+ const { handleCommandError } = require('../utils/cli-utils');
15
+ const { VALIDATE_HELP_AFTER } = require('./setup-utility-help-after');
16
+
17
+ /**
18
+ * @param {string} appOrFile
19
+ * @param {object} result
20
+ * @param {string} outFormat
21
+ */
22
+ function emitManifestAfterValidateIfApplicable(appOrFile, result, outFormat) {
23
+ if (outFormat === 'json' || !result?.appPath || !appOrFile || typeof appOrFile !== 'string') {
24
+ return;
25
+ }
26
+ try {
27
+ if (fs.existsSync(appOrFile) && fs.statSync(appOrFile).isFile()) {
28
+ return;
29
+ }
30
+ const { emitManifestMetadataLineIfTTY } = require('../utils/manifest-source-emit');
31
+ emitManifestMetadataLineIfTTY(logger, {
32
+ appKey: appOrFile,
33
+ appPath: result.appPath,
34
+ envOnly: false,
35
+ json: false
36
+ });
37
+ } catch {
38
+ /* ignore emit failures */
39
+ }
40
+ }
41
+
42
+ /**
43
+ * @param {object} validate
44
+ * @param {object} opts
45
+ * @returns {Promise<boolean>}
46
+ */
47
+ async function runValidateBatchBranch(validate, opts) {
48
+ const integration = opts.integration === true;
49
+ const builder = opts.builder === true;
50
+ if (!integration && !builder) {
51
+ return false;
52
+ }
53
+ const outFormat = (opts.format || 'default').toLowerCase();
54
+ const batchResult = integration && builder
55
+ ? await validate.validateAll(opts)
56
+ : integration
57
+ ? await validate.validateAllIntegrations(opts)
58
+ : await validate.validateAllBuilderApps(opts);
59
+ if (outFormat === 'json') {
60
+ logger.log(JSON.stringify(batchResult, null, 2));
61
+ } else {
62
+ validate.displayBatchValidationResults(batchResult);
63
+ }
64
+ if (!batchResult.valid) process.exit(1);
65
+ return true;
66
+ }
67
+
68
+ /**
69
+ * @param {string} appOrFile
70
+ * @param {object} opts
71
+ * @returns {Promise<void>}
72
+ */
73
+ async function runProtectionValidateBranch(appOrFile, opts) {
74
+ const { validateProtectionBatchCommand } = require('../protection/validate-batch');
75
+ const code = await validateProtectionBatchCommand(
76
+ {
77
+ env: opts.env,
78
+ json: (opts.format || '').toLowerCase() === 'json',
79
+ warningsAsErrors: opts.warningsAsErrors === true,
80
+ simulate: opts.simulate === true,
81
+ verbose: opts.verbose === true
82
+ },
83
+ logger
84
+ );
85
+ if (code !== 0) {
86
+ process.exit(code);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * @param {object} result
92
+ * @param {string} appOrFile
93
+ * @returns {object}
94
+ */
95
+ function buildValidateJsonPayload(result, appOrFile) {
96
+ let payload = result;
97
+ try {
98
+ if (result?.appPath && appOrFile && typeof appOrFile === 'string') {
99
+ if (!(fs.existsSync(appOrFile) && fs.statSync(appOrFile).isFile())) {
100
+ const { getManifestSourcePayload } = require('../utils/manifest-source-emit');
101
+ payload = { ...result, manifestSource: getManifestSourcePayload(appOrFile, result.appPath) };
102
+ }
103
+ }
104
+ } catch {
105
+ /* keep plain result */
106
+ }
107
+ return payload;
108
+ }
109
+
110
+ /**
111
+ * @param {string} appOrFile
112
+ * @param {object} result
113
+ * @param {string} outFormat
114
+ * @param {object} validate
115
+ */
116
+ function displaySingleValidateResult(appOrFile, result, outFormat, validate) {
117
+ emitManifestAfterValidateIfApplicable(appOrFile, result, outFormat);
118
+ if (outFormat === 'json') {
119
+ logger.log(JSON.stringify(buildValidateJsonPayload(result, appOrFile), null, 2));
120
+ return;
121
+ }
122
+ validate.displayValidationResults(result);
123
+ }
124
+
125
+ /**
126
+ * @param {string} appOrFile
127
+ * @param {object} options
128
+ * @returns {Promise<void>}
129
+ */
130
+ async function runValidateCommand(appOrFile, options) {
131
+ const validate = require('../validation/validate');
132
+ const opts = options.opts ? options.opts() : options;
133
+ const { isProtectionScope } = require('../protection/scope');
134
+ if (isProtectionScope(appOrFile)) {
135
+ await runProtectionValidateBranch(appOrFile, opts);
136
+ return;
137
+ }
138
+ if (await runValidateBatchBranch(validate, opts)) {
139
+ return;
140
+ }
141
+ const outFormat = (opts.format || 'default').toLowerCase();
142
+ if (!appOrFile || typeof appOrFile !== 'string') {
143
+ logger.log(chalk.red('App name or file path is required, or use --integration / --builder'));
144
+ process.exit(1);
145
+ }
146
+ const result = await validate.validateAppOrFile(appOrFile, {
147
+ ...opts,
148
+ certSync: opts.certSync === true
149
+ });
150
+ displaySingleValidateResult(appOrFile, result, outFormat, validate);
151
+ if (!result.valid) process.exit(1);
152
+ }
153
+
154
+ /**
155
+ * @param {import('commander').Command} program
156
+ */
157
+ function setupValidateDiffCommands(program) {
158
+ program.command('validate [appKey|systemKey|file]')
159
+ .description('Validate appKey, systemKey, file path, .protection batch, or all under integration/ and builder/')
160
+ .addHelpText('after', VALIDATE_HELP_AFTER)
161
+ .option('--format <format>', 'Output format: json | default (human-readable)')
162
+ .option('--integration', 'Validate all applications under integration/')
163
+ .option('--builder', 'Validate all applications under builder/')
164
+ .option('--warnings-as-errors', 'For .protection: pass strict=true to dataplane validate')
165
+ .option('--simulate', 'For .protection: run simulate after validate')
166
+ .option(
167
+ '--cert-sync',
168
+ 'After successful validation of an external integration, refresh the system file certification block from the dataplane (requires login)'
169
+ )
170
+ .action((appOrFile, options) => {
171
+ runValidateCommand(appOrFile, options).catch((error) => {
172
+ handleCommandError(error, 'validate');
173
+ process.exit(1);
174
+ });
175
+ });
176
+
177
+ program.command('diff <file1> <file2>')
178
+ .description('Diff two config files (optional schema validate)')
179
+ .option('--no-validate', 'Skip schema validation (type check still applied)')
180
+ .action(async(file1, file2, cmd) => {
181
+ try {
182
+ const diff = require('../core/diff');
183
+ const opts = cmd.opts();
184
+ const result = await diff.compareFiles(file1, file2, { validate: opts.validate !== false });
185
+ diff.formatDiffOutput(result);
186
+ if (!result.identical) process.exit(1);
187
+ } catch (error) {
188
+ handleCommandError(error, 'diff');
189
+ process.exit(1);
190
+ }
191
+ });
192
+ }
193
+
194
+ module.exports = {
195
+ setupValidateDiffCommands,
196
+ runValidateCommand
197
+ };