@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
@@ -0,0 +1,102 @@
1
+ /**
2
+ * CLI registration for `aifabrix setup` and `aifabrix teardown`.
3
+ *
4
+ * `setup` is the one-shot platform installer. When no infrastructure is
5
+ * detected it runs a small wizard (admin email/password, AI tool keys) and
6
+ * then `up-infra` + `up-platform`. When infra is already up, it offers a
7
+ * mode menu (re-install, wipe data, clean install files, update images).
8
+ *
9
+ * `teardown` is the symmetrical inverse of `setup`: `down-infra -v` plus a
10
+ * full clean of the AI Fabrix system directory (everything except
11
+ * `config.yaml`).
12
+ *
13
+ * @fileoverview CLI command setup for setup/teardown
14
+ * @author AI Fabrix Team
15
+ * @version 2.0.0
16
+ */
17
+
18
+ 'use strict';
19
+
20
+ const { handleCommandError } = require('../utils/cli-utils');
21
+ const { handleSetup } = require('../commands/setup');
22
+ const { handleTeardown } = require('../commands/teardown');
23
+
24
+ const SETUP_HELP_AFTER = `
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.
27
+ - With infra running: shows a mode menu:
28
+ 1) Re-install — stop infra and remove all volumes, then up-infra + up-platform --force.
29
+ 2) Wipe data — drop every database and DB user, then up-infra + up-platform --force.
30
+ 3) Clean installation files — remove user-local secrets, then up-infra + up-platform --force.
31
+ 4) Update images — pull the latest infra and platform images, then up-infra + up-platform.
32
+
33
+ AI tool keys are read from the user-local file (~/.aifabrix/secrets.local.yaml) merged with the shared aifabrix-secrets file. If either source already provides keys, the AI tool prompt is skipped.
34
+
35
+ Use 'aifabrix teardown' to fully remove the local installation.
36
+ `;
37
+
38
+ const TEARDOWN_HELP_AFTER = `
39
+ What this command does:
40
+ - Runs down-infra -v (stops all infra + apps, removes every Docker volume).
41
+ - Removes every file and subfolder inside ~/.aifabrix/ EXCEPT config.yaml.
42
+ This includes secrets.local.yaml, admin-secrets.env, auth/token files,
43
+ and infra-dev*/ directories.
44
+
45
+ This is the symmetrical inverse of 'aifabrix setup'. Use --yes / -y to skip the confirmation prompt in CI.
46
+ `;
47
+
48
+ /**
49
+ * Register the setup command on the Commander program.
50
+ * @param {import('commander').Command} program
51
+ */
52
+ function registerSetup(program) {
53
+ program
54
+ .command('setup')
55
+ .description('Install or refresh the full AI Fabrix platform (infra + miso + dataplane)')
56
+ .option('-d, --developer <id>', 'Pin developer ID before fresh install (fresh path only; ignored when infra is already up)')
57
+ .option('-y, --yes', 'Skip destructive confirmation prompts (re-install, wipe data, teardown)')
58
+ .addHelpText('after', SETUP_HELP_AFTER)
59
+ .action(async(options) => {
60
+ try {
61
+ await handleSetup(options || {});
62
+ } catch (error) {
63
+ handleCommandError(error, 'setup');
64
+ process.exit(1);
65
+ }
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Register the teardown command on the Commander program.
71
+ * @param {import('commander').Command} program
72
+ */
73
+ function registerTeardown(program) {
74
+ program
75
+ .command('teardown')
76
+ .description('Tear down local AI Fabrix infra and clean ~/.aifabrix/ except config.yaml')
77
+ .option('-y, --yes', 'Skip the confirmation prompt')
78
+ .addHelpText('after', TEARDOWN_HELP_AFTER)
79
+ .action(async(options) => {
80
+ try {
81
+ await handleTeardown(options || {});
82
+ } catch (error) {
83
+ handleCommandError(error, 'teardown');
84
+ process.exit(1);
85
+ }
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Register all setup-platform commands on the Commander program.
91
+ * @param {import('commander').Command} program
92
+ */
93
+ function setupPlatformCommands(program) {
94
+ registerSetup(program);
95
+ registerTeardown(program);
96
+ }
97
+
98
+ module.exports = {
99
+ setupPlatformCommands,
100
+ registerSetup,
101
+ registerTeardown
102
+ };
@@ -24,8 +24,8 @@ Subcommands:
24
24
 
25
25
  Also: aifabrix secure Encrypt secrets.local.yaml (ISO 27001)
26
26
 
27
- Default "secret set" (no --shared): writes only to your user secrets.local.yaml next to
28
- config.yaml (typically ~/.aifabrix/secrets.local.yaml). Use --shared to write the
27
+ Default "secret set" (no --shared): writes only to your user secrets.local.yaml under the
28
+ resolved AI Fabrix home (config key aifabrix-home, else ~/.aifabrix). Use --shared to write the
29
29
  shared store from config aifabrix-secrets (another file or https), not that user file.
30
30
 
31
31
  Examples:
@@ -46,8 +46,8 @@ Examples:
46
46
 
47
47
  const SECRET_SET_HELP_AFTER = `
48
48
  Where the value is stored:
49
- No --shared User file secrets.local.yaml in your aifabrix config directory (same
50
- folder as config.yaml; often ~/.aifabrix/). Used for app/integration
49
+ No --shared User file secrets.local.yaml under the resolved AI Fabrix home
50
+ (config key aifabrix-home, else ~/.aifabrix/). Used for app/integration
51
51
  secrets and kv:// resolution on your machine.
52
52
  --shared The store set in config.yaml as aifabrix-secrets: a YAML file path or
53
53
  an https secrets API (never the user-only file above unless you point
@@ -70,6 +70,13 @@ Examples:
70
70
  `;
71
71
 
72
72
  const SECRET_REMOVE_ALL_HELP_AFTER = `
73
+ Where keys are removed:
74
+ Default All keys in your user secrets.local.yaml (under resolved aifabrix home,
75
+ often ~/.aifabrix/). This is NOT the shared store unless you deliberately
76
+ point aifabrix-secrets there.
77
+ --shared Every key in the shared store from config aifabrix-secrets (YAML file
78
+ path or https secrets API). Requires that setting; use secret set-secrets-file if needed.
79
+
73
80
  You will be asked to type "yes" to confirm unless you pass --yes.
74
81
 
75
82
  Examples:
@@ -103,9 +110,14 @@ function setupSecretRemoveAllCommand(secretCmd) {
103
110
  const { handleSecretsRemoveAll } = require('../commands/secrets-remove-all');
104
111
  secretCmd
105
112
  .command('remove-all')
106
- .description('Remove all secret keys (requires typing "yes" unless --yes)')
113
+ .description(
114
+ 'Remove every key from user secrets.local.yaml (add --shared for aifabrix-secrets store; confirm unless --yes)'
115
+ )
107
116
  .addHelpText('after', SECRET_REMOVE_ALL_HELP_AFTER)
108
- .option('--shared', 'Remove all from shared secrets (file or remote API)')
117
+ .option(
118
+ '--shared',
119
+ 'Remove every key from shared secrets (config aifabrix-secrets: YAML file or https API)'
120
+ )
109
121
  .option('-y, --yes', 'Skip confirmation prompt (non-interactive / scripts)')
110
122
  .action(async options => {
111
123
  try {
@@ -0,0 +1,132 @@
1
+ /**
2
+ * `aifabrix resolve <app>` command wiring (split from setup-utility for file/function size limits).
3
+ *
4
+ * @fileoverview Resolve 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 secrets = require('../core/secrets');
13
+ const logger = require('../utils/logger');
14
+ const { handleCommandError } = require('../utils/cli-utils');
15
+ const coreConfig = require('../core/config');
16
+ const { resolvePreferLocalEnvOutputPathFlag } = require('../utils/applications-config-defaults');
17
+ const { getResolveAppPath } = require('../utils/paths');
18
+
19
+ /**
20
+ * @param {string} appName
21
+ * @param {string} appPath
22
+ * @param {boolean} envOnly
23
+ * @param {string} envPath
24
+ * @param {{ skipValidation?: boolean }} options
25
+ * @param {{ getManifestSourcePayload: function }} manifestEmit
26
+ * @returns {Promise<void>}
27
+ */
28
+ async function runResolveJsonOutput(appName, appPath, envOnly, envPath, options, manifestEmit) {
29
+ /** @type {Record<string, unknown>} */
30
+ const payload = {
31
+ app: appName,
32
+ appPath,
33
+ envOnly,
34
+ envPath,
35
+ manifestSource: envOnly ? null : manifestEmit.getManifestSourcePayload(appName, appPath)
36
+ };
37
+ if (!envOnly && !options.skipValidation) {
38
+ const validate = require('../validation/validate');
39
+ payload.validation = await validate.validateAppOrFile(appName);
40
+ }
41
+ logger.log(JSON.stringify(payload, null, 2));
42
+ const v = payload.validation;
43
+ if (v && typeof v === 'object' && v.valid === false) {
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * @param {string} appName
50
+ * @param {string} envPath
51
+ * @param {boolean} envOnly
52
+ * @param {{ skipValidation?: boolean }} options
53
+ * @returns {Promise<void>}
54
+ */
55
+ async function runResolveHumanOutput(appName, envPath, envOnly, options) {
56
+ logger.log(`✔ Generated .env file: ${envPath}`);
57
+ logger.log(chalk.gray(' Note: up-platform / up-miso / up-dataplane / register / build resolve secrets in memory only.'));
58
+ logger.log(chalk.gray(` Re-run "aifabrix resolve ${appName}" whenever you need an on-disk .env again.`));
59
+ if (envOnly) {
60
+ logger.log(chalk.gray(' (env-only mode: validation skipped; no application config file)'));
61
+ return;
62
+ }
63
+ if (options.skipValidation) {
64
+ return;
65
+ }
66
+ const validate = require('../validation/validate');
67
+ const result = await validate.validateAppOrFile(appName);
68
+ validate.displayValidationResults(result);
69
+ if (!result.valid) {
70
+ logger.log(chalk.yellow('\n⚠ Validation found errors. Fix them before deploying.'));
71
+ process.exit(1);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * @param {string} appName
77
+ * @param {{ force?: boolean, skipValidation?: boolean, json?: boolean }} options
78
+ * @returns {Promise<void>}
79
+ */
80
+ async function runResolveAppAction(appName, options) {
81
+ const jsonMode = options.json === true;
82
+ const { appPath, envOnly } = await getResolveAppPath(appName);
83
+ const manifestEmit = require('../utils/manifest-source-emit');
84
+ manifestEmit.emitManifestMetadataLineIfTTY(logger, {
85
+ appKey: appName,
86
+ appPath,
87
+ envOnly,
88
+ json: jsonMode
89
+ });
90
+ const userCfg = await coreConfig.getConfig();
91
+ const preferLocalEnvOutputPath = await resolvePreferLocalEnvOutputPathFlag(userCfg, appName);
92
+ const envPath = await secrets.generateEnvFile(
93
+ appName,
94
+ undefined,
95
+ 'docker',
96
+ options.force,
97
+ {
98
+ appPath,
99
+ envOnly,
100
+ skipOutputPath: false,
101
+ preserveFromPath: null,
102
+ preferLocalEnvOutputPath
103
+ }
104
+ );
105
+ if (jsonMode) {
106
+ await runResolveJsonOutput(appName, appPath, envOnly, envPath, options, manifestEmit);
107
+ return;
108
+ }
109
+ await runResolveHumanOutput(appName, envPath, envOnly, options);
110
+ }
111
+
112
+ /**
113
+ * @param {import('commander').Command} program
114
+ * @returns {void}
115
+ */
116
+ function setupResolveCommand(program) {
117
+ program.command('resolve <app>')
118
+ .description('Generate .env from template; optional validate after')
119
+ .option('-f, --force', 'Generate missing secret keys in secrets file')
120
+ .option('--skip-validation', 'Skip file validation after generating .env')
121
+ .option('--json', 'Print JSON summary (envPath, manifestSource, optional validation)')
122
+ .action(async(appName, options) => {
123
+ try {
124
+ await runResolveAppAction(appName, options);
125
+ } catch (error) {
126
+ handleCommandError(error, 'resolve');
127
+ process.exit(1);
128
+ }
129
+ });
130
+ }
131
+
132
+ module.exports = { setupResolveCommand };
@@ -10,11 +10,11 @@ const { formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
10
10
  const path = require('path');
11
11
  const fs = require('fs');
12
12
  const chalk = require('chalk');
13
- const secrets = require('../core/secrets');
14
13
  const generator = require('../generator');
15
14
  const logger = require('../utils/logger');
16
15
  const { handleCommandError, logOfflinePathWhenType } = require('../utils/cli-utils');
17
16
  const { detectAppType, getDeployJsonPath, getResolveAppPath } = require('../utils/paths');
17
+ const { setupResolveCommand } = require('./setup-utility-resolve');
18
18
 
19
19
  const JSON_HELP_AFTER = `
20
20
  Example:
@@ -46,6 +46,8 @@ Examples:
46
46
 
47
47
  $ aifabrix repair hubspot-demo --doc
48
48
  Regenerate README.md from the current deployment manifest only (other drift fixes unchanged).
49
+
50
+ Before overwriting files, repair writes timestamped backups under integration/<systemKey>/backup/ (same as datasource capability copy). Use --no-backup to skip.
49
51
  `;
50
52
 
51
53
  /**
@@ -119,46 +121,20 @@ function logSplitJsonResult(result) {
119
121
  }
120
122
  }
121
123
 
122
- function setupResolveCommand(program) {
123
- program.command('resolve <app>')
124
- .description('Generate .env from template; optional validate after')
125
- .option('-f, --force', 'Generate missing secret keys in secrets file')
126
- .option('--skip-validation', 'Skip file validation after generating .env')
127
- .action(async(appName, options) => {
128
- try {
129
- const { appPath, envOnly } = await getResolveAppPath(appName);
130
- const envPath = await secrets.generateEnvFile(
131
- appName,
132
- undefined,
133
- 'docker',
134
- options.force,
135
- { appPath, envOnly, skipOutputPath: false, preserveFromPath: null }
136
- );
137
- logger.log(`✔ Generated .env file: ${envPath}`);
138
- if (envOnly) {
139
- logger.log(chalk.gray(' (env-only mode: validation skipped; no application config file)'));
140
- } else if (!options.skipValidation) {
141
- const validate = require('../validation/validate');
142
- const result = await validate.validateAppOrFile(appName);
143
- validate.displayValidationResults(result);
144
- if (!result.valid) {
145
- logger.log(chalk.yellow('\n⚠ Validation found errors. Fix them before deploying.'));
146
- process.exit(1);
147
- }
148
- }
149
- } catch (error) {
150
- handleCommandError(error, 'resolve');
151
- process.exit(1);
152
- }
153
- });
154
- }
155
-
156
124
  function setupJsonCommand(program) {
157
125
  program.command('json <app>')
158
126
  .description('Write deployment JSON to disk for version control')
159
127
  .addHelpText('after', JSON_HELP_AFTER)
160
128
  .action(async(appName, options) => {
161
129
  try {
130
+ const resolved = await getResolveAppPath(appName);
131
+ const { emitManifestMetadataLineIfTTY } = require('../utils/manifest-source-emit');
132
+ emitManifestMetadataLineIfTTY(logger, {
133
+ appKey: appName,
134
+ appPath: resolved.appPath,
135
+ envOnly: resolved.envOnly,
136
+ json: false
137
+ });
162
138
  const result = await generator.generateDeployJsonWithValidation(appName, options);
163
139
  if (result.success) {
164
140
  const fileName = result.path.includes('application-schema.json') ? 'application-schema.json' : 'deployment JSON';
@@ -193,50 +169,93 @@ function setupSplitJsonCommand(program) {
193
169
  });
194
170
  }
195
171
 
172
+ function buildRepairExternalIntegrationOptions(appName, options, format) {
173
+ const all = options.all === true;
174
+ return {
175
+ auth: options.auth,
176
+ doc: options.doc || all,
177
+ dryRun: options.dryRun,
178
+ format,
179
+ rbac: options.rbac || all,
180
+ expose: options.expose || all,
181
+ sync: options.sync || all,
182
+ api: options.api || all,
183
+ test: options.test || all,
184
+ backup: options.backup,
185
+ noBackup: options.noBackup
186
+ };
187
+ }
188
+
189
+ function logRepairResult(options, result) {
190
+ if (options.dryRun && result.updated && result.changes.length > 0) {
191
+ logger.log(chalk.yellow('\nWould apply:'));
192
+ result.changes.forEach(c => logger.log(chalk.gray(` ${c}`)));
193
+ return;
194
+ }
195
+ if (Array.isArray(result.backupPaths) && result.backupPaths.length > 0) {
196
+ result.backupPaths.forEach((p) => logger.log(chalk.gray(`Backup: ${p}`)));
197
+ }
198
+ if (result.updated) {
199
+ logger.log(formatSuccessParagraph('Repaired external integration config.'));
200
+ return;
201
+ }
202
+ if (Array.isArray(result.changes) && result.changes.length > 0) {
203
+ logger.log(formatSuccessParagraph('Repair actions completed.'));
204
+ result.changes.forEach(c => logger.log(chalk.gray(` ${c}`)));
205
+ return;
206
+ }
207
+ if (result.readmeRegenerated) {
208
+ logger.log(formatSuccessParagraph('Regenerated README.md from deployment manifest.'));
209
+ return;
210
+ }
211
+ logger.log(chalk.gray('No changes needed; config already matches files on disk.'));
212
+ }
213
+
214
+ /**
215
+ * Handler for `aifabrix repair <systemKey>`.
216
+ * @param {string} appName
217
+ * @param {object} options - Commander options
218
+ * @returns {Promise<void>}
219
+ */
220
+ async function handleRepairCommand(appName, options) {
221
+ const { repairExternalIntegration } = require('../commands/repair');
222
+ const { detectAppType } = require('../utils/paths');
223
+ const { appPath } = await detectAppType(appName);
224
+ logOfflinePathWhenType(appPath);
225
+ let format = 'yaml';
226
+ try {
227
+ const config = require('../core/config');
228
+ format = (await config.getFormat()) || format;
229
+ } catch (_) {
230
+ /* default yaml when config unavailable */
231
+ }
232
+ const result = await repairExternalIntegration(
233
+ appName,
234
+ buildRepairExternalIntegrationOptions(appName, options, format)
235
+ );
236
+ logRepairResult(options, result);
237
+ }
238
+
196
239
  function setupRepairCommand(program) {
197
240
  program.command('repair <systemKey>')
198
241
  .description('Fix external integration drift (files, RBAC, manifest, …)')
242
+ .option('--all', 'Run all repair actions (api, doc, expose, rbac, sync, test)')
243
+ .option(
244
+ '--api',
245
+ 'Validate and sync API contracts needed for OpenAPI/MCP (uses local OpenAPI specs at integration/<systemKey>/openapi/*.json when present)'
246
+ )
199
247
  .option('--auth <method>', 'Set authentication method (oauth2, aad, apikey, basic, queryParam, oidc, hmac, none); updates system file and env.template')
200
248
  .option('--doc', 'Regenerate README.md from deployment manifest')
201
- .option('--dry-run', 'Report changes only; do not write')
202
- .option('--rbac', 'Ensure RBAC permissions per datasource and add default Admin/Reader roles if none exist')
203
249
  .option('--expose', 'Set exposed.schema on each datasource from all fieldMappings.attributes keys (metadata.<key>); removes deprecated exposed.attributes if present')
250
+ .option('--rbac', 'Ensure RBAC permissions per datasource and add default Admin/Reader roles if none exist')
204
251
  .option('--sync', 'Add default sync section to datasources that lack it')
205
252
  .option('--test', 'Generate testPayload.payloadTemplate and testPayload.expectedResult from attributes')
253
+ .option('--no-backup', 'Skip timestamped copies under integration/<systemKey>/backup/')
254
+ .option('--dry-run', 'Report changes only; do not write')
206
255
  .addHelpText('after', REPAIR_HELP_AFTER)
207
256
  .action(async(appName, options) => {
208
257
  try {
209
- const { repairExternalIntegration } = require('../commands/repair');
210
- const { detectAppType } = require('../utils/paths');
211
- const { appPath } = await detectAppType(appName);
212
- logOfflinePathWhenType(appPath);
213
- let format = 'yaml';
214
- try {
215
- const config = require('../core/config');
216
- format = (await config.getFormat()) || format;
217
- } catch (_) {
218
- // use default yaml when config unavailable
219
- }
220
- const result = await repairExternalIntegration(appName, {
221
- auth: options.auth,
222
- doc: options.doc,
223
- dryRun: options.dryRun,
224
- format,
225
- rbac: options.rbac,
226
- expose: options.expose,
227
- sync: options.sync,
228
- test: options.test
229
- });
230
- if (options.dryRun && result.updated && result.changes.length > 0) {
231
- logger.log(chalk.yellow('\nWould apply:'));
232
- result.changes.forEach(c => logger.log(chalk.gray(` ${c}`)));
233
- } else if (result.updated) {
234
- logger.log(formatSuccessParagraph('Repaired external integration config.'));
235
- } else if (result.readmeRegenerated) {
236
- logger.log(formatSuccessParagraph('Regenerated README.md from deployment manifest.'));
237
- } else {
238
- logger.log(chalk.gray('No changes needed; config already matches files on disk.'));
239
- }
258
+ await handleRepairCommand(appName, options);
240
259
  } catch (error) {
241
260
  handleCommandError(error, 'repair');
242
261
  process.exit(1);
@@ -308,28 +327,56 @@ function setupSplitJsonConvertShowCommands(program) {
308
327
  setupShowCommand(program);
309
328
  }
310
329
 
311
- async function runValidateCommand(appOrFile, options) {
312
- const validate = require('../validation/validate');
313
- const opts = options.opts ? options.opts() : options;
330
+ function emitManifestAfterValidateIfApplicable(appOrFile, result, outFormat) {
331
+ if (outFormat === 'json' || !result?.appPath || !appOrFile || typeof appOrFile !== 'string') {
332
+ return;
333
+ }
334
+ try {
335
+ if (fs.existsSync(appOrFile) && fs.statSync(appOrFile).isFile()) {
336
+ return;
337
+ }
338
+ const { emitManifestMetadataLineIfTTY } = require('../utils/manifest-source-emit');
339
+ emitManifestMetadataLineIfTTY(logger, {
340
+ appKey: appOrFile,
341
+ appPath: result.appPath,
342
+ envOnly: false,
343
+ json: false
344
+ });
345
+ } catch {
346
+ /* ignore emit failures */
347
+ }
348
+ }
349
+
350
+ async function runValidateBatchBranch(validate, opts) {
314
351
  const integration = opts.integration === true;
315
352
  const builder = opts.builder === true;
353
+ if (!integration && !builder) {
354
+ return false;
355
+ }
316
356
  const outFormat = (opts.format || 'default').toLowerCase();
357
+ const batchResult = integration && builder
358
+ ? await validate.validateAll(opts)
359
+ : integration
360
+ ? await validate.validateAllIntegrations(opts)
361
+ : await validate.validateAllBuilderApps(opts);
362
+ if (outFormat === 'json') {
363
+ logger.log(JSON.stringify(batchResult, null, 2));
364
+ } else {
365
+ validate.displayBatchValidationResults(batchResult);
366
+ }
367
+ if (!batchResult.valid) process.exit(1);
368
+ return true;
369
+ }
317
370
 
318
- if (integration || builder) {
319
- const batchResult = integration && builder
320
- ? await validate.validateAll(opts)
321
- : integration
322
- ? await validate.validateAllIntegrations(opts)
323
- : await validate.validateAllBuilderApps(opts);
324
- if (outFormat === 'json') {
325
- logger.log(JSON.stringify(batchResult, null, 2));
326
- } else {
327
- validate.displayBatchValidationResults(batchResult);
328
- }
329
- if (!batchResult.valid) process.exit(1);
371
+ async function runValidateCommand(appOrFile, options) {
372
+ const validate = require('../validation/validate');
373
+ const opts = options.opts ? options.opts() : options;
374
+ if (await runValidateBatchBranch(validate, opts)) {
330
375
  return;
331
376
  }
332
377
 
378
+ const outFormat = (opts.format || 'default').toLowerCase();
379
+
333
380
  if (!appOrFile || typeof appOrFile !== 'string') {
334
381
  logger.log(chalk.red('App name or file path is required, or use --integration / --builder'));
335
382
  process.exit(1);
@@ -339,8 +386,20 @@ async function runValidateCommand(appOrFile, options) {
339
386
  ...opts,
340
387
  certSync: opts.certSync === true
341
388
  });
389
+ emitManifestAfterValidateIfApplicable(appOrFile, result, outFormat);
342
390
  if (outFormat === 'json') {
343
- logger.log(JSON.stringify(result, null, 2));
391
+ let payload = result;
392
+ try {
393
+ if (result?.appPath && appOrFile && typeof appOrFile === 'string') {
394
+ if (!(fs.existsSync(appOrFile) && fs.statSync(appOrFile).isFile())) {
395
+ const { getManifestSourcePayload } = require('../utils/manifest-source-emit');
396
+ payload = { ...result, manifestSource: getManifestSourcePayload(appOrFile, result.appPath) };
397
+ }
398
+ }
399
+ } catch {
400
+ /* keep plain result */
401
+ }
402
+ logger.log(JSON.stringify(payload, null, 2));
344
403
  } else {
345
404
  validate.displayValidationResults(result);
346
405
  }