@aifabrix/builder 2.43.0 → 2.44.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 (346) hide show
  1. package/.cursor/rules/anchor-docs.mdc +15 -0
  2. package/README.md +1 -1
  3. package/anchor-docs/README.md +10 -0
  4. package/anchor-docs/_TEMPLATE +24 -0
  5. package/bin/aifabrix.js +13 -4
  6. package/integration/hubspot-test/README.md +31 -0
  7. package/integration/hubspot-test/create-hubspot.js +5 -5
  8. package/integration/hubspot-test/hubspot-test-datasource-company.json +58 -462
  9. package/integration/hubspot-test/hubspot-test-datasource-contact.json +61 -555
  10. package/integration/hubspot-test/hubspot-test-datasource-deal.json +63 -506
  11. package/integration/hubspot-test/hubspot-test-datasource-users.json +42 -83
  12. package/integration/hubspot-test/hubspot-test-deploy.json +3 -3
  13. package/integration/hubspot-test/test-dataplane-down-tests.js +1 -7
  14. package/integration/hubspot-test/test-dataplane-down.js +3 -3
  15. package/integration/hubspot-test/test.js +35 -43
  16. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
  17. package/integration/roundtrip-test-local/README.md +144 -0
  18. package/integration/roundtrip-test-local/application.yaml +13 -0
  19. package/integration/roundtrip-test-local/env.template +15 -0
  20. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
  21. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
  22. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
  23. package/integration/roundtrip-test-local2/README.md +144 -0
  24. package/integration/roundtrip-test-local2/application.yaml +13 -0
  25. package/integration/roundtrip-test-local2/env.template +15 -0
  26. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
  27. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
  28. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
  29. package/integration/test/wizard.yaml +8 -0
  30. package/jest.config.default.js +10 -0
  31. package/jest.config.integration.fixtures.js +22 -0
  32. package/jest.config.integration.js +21 -18
  33. package/jest.config.isolated.js +10 -0
  34. package/jest.projects.js +288 -0
  35. package/lib/api/datasources-core.api.js +3 -3
  36. package/lib/api/dev-mtls-request.js +110 -0
  37. package/lib/api/dev-server-https.js +145 -0
  38. package/lib/api/dev.api.js +133 -144
  39. package/lib/api/index.js +0 -1
  40. package/lib/api/pipeline.api.js +67 -20
  41. package/lib/api/types/dev.types.js +4 -3
  42. package/lib/api/types/pipeline.types.js +8 -5
  43. package/lib/api/types/validation-run.types.js +56 -0
  44. package/lib/api/validation-run.api.js +99 -0
  45. package/lib/api/validation-runner.js +99 -0
  46. package/lib/app/config.js +1 -1
  47. package/lib/app/deploy-status-display.js +2 -2
  48. package/lib/app/deploy.js +7 -6
  49. package/lib/app/display.js +2 -1
  50. package/lib/app/dockerfile.js +3 -2
  51. package/lib/app/down.js +2 -1
  52. package/lib/app/helpers.js +6 -5
  53. package/lib/app/index.js +27 -8
  54. package/lib/app/list.js +7 -6
  55. package/lib/app/push.js +4 -3
  56. package/lib/app/register.js +16 -7
  57. package/lib/app/rotate-secret.js +14 -13
  58. package/lib/app/run-container-start.js +184 -0
  59. package/lib/app/run-docker-fallback.js +108 -0
  60. package/lib/app/run-env-compose.js +30 -42
  61. package/lib/app/run-helpers.js +49 -126
  62. package/lib/app/run-infra-requirements.js +30 -0
  63. package/lib/app/run-resolve-image.js +21 -0
  64. package/lib/app/run.js +74 -21
  65. package/lib/app/show-display.js +1 -1
  66. package/lib/app/show.js +1 -1
  67. package/lib/build/index.js +13 -10
  68. package/lib/cli/index.js +2 -0
  69. package/lib/cli/setup-app.help.js +67 -0
  70. package/lib/cli/setup-app.js +57 -121
  71. package/lib/cli/setup-app.test-commands.js +179 -0
  72. package/lib/cli/setup-auth.js +19 -5
  73. package/lib/cli/setup-credential-deployment.js +22 -8
  74. package/lib/cli/setup-dev-path-commands.js +124 -0
  75. package/lib/cli/setup-dev.js +170 -113
  76. package/lib/cli/setup-environment.js +7 -1
  77. package/lib/cli/setup-external-system.js +62 -22
  78. package/lib/cli/setup-infra.js +126 -47
  79. package/lib/cli/setup-parameters.js +32 -0
  80. package/lib/cli/setup-secrets.js +106 -8
  81. package/lib/cli/setup-service-user.js +1 -1
  82. package/lib/cli/setup-utility.js +36 -20
  83. package/lib/commands/app-down.js +5 -7
  84. package/lib/commands/app-install.js +14 -7
  85. package/lib/commands/app-logs.js +13 -10
  86. package/lib/commands/app-shell.js +4 -1
  87. package/lib/commands/app-test.js +25 -19
  88. package/lib/commands/app.js +22 -10
  89. package/lib/commands/auth-config.js +6 -6
  90. package/lib/commands/auth-status.js +4 -3
  91. package/lib/commands/credential-env.js +4 -3
  92. package/lib/commands/credential-list.js +5 -4
  93. package/lib/commands/credential-push.js +4 -3
  94. package/lib/commands/datasource-unified-test-cli.js +495 -0
  95. package/lib/commands/datasource-unified-test-cli.options.js +149 -0
  96. package/lib/commands/datasource-validation-cli.js +129 -0
  97. package/lib/commands/datasource.js +105 -98
  98. package/lib/commands/deployment-list.js +6 -5
  99. package/lib/commands/dev-cli-handlers.js +122 -18
  100. package/lib/commands/dev-down.js +4 -3
  101. package/lib/commands/dev-init.js +231 -116
  102. package/lib/commands/dev-show-display.js +473 -0
  103. package/lib/commands/login-credentials.js +3 -2
  104. package/lib/commands/login-device.js +4 -3
  105. package/lib/commands/login.js +5 -4
  106. package/lib/commands/logout.js +8 -7
  107. package/lib/commands/parameters-validate.js +54 -0
  108. package/lib/commands/repair-datasource.js +314 -68
  109. package/lib/commands/repair-env-template.js +2 -2
  110. package/lib/commands/repair.js +21 -3
  111. package/lib/commands/secrets-list.js +23 -12
  112. package/lib/commands/secrets-remove-all.js +220 -0
  113. package/lib/commands/secrets-remove.js +21 -12
  114. package/lib/commands/secrets-set.js +21 -12
  115. package/lib/commands/secrets-validate.js +4 -4
  116. package/lib/commands/secure.js +10 -9
  117. package/lib/commands/service-user.js +26 -25
  118. package/lib/commands/test-e2e-external.js +27 -1
  119. package/lib/commands/up-common.js +3 -2
  120. package/lib/commands/up-dataplane.js +29 -16
  121. package/lib/commands/up-miso.js +19 -29
  122. package/lib/commands/upload.js +138 -39
  123. package/lib/commands/wizard-core-helpers.js +1 -1
  124. package/lib/commands/wizard-dataplane.js +4 -3
  125. package/lib/commands/wizard-helpers.js +3 -3
  126. package/lib/commands/wizard.js +2 -2
  127. package/lib/core/admin-secrets.js +14 -5
  128. package/lib/core/audit-logger.js +12 -4
  129. package/lib/core/config-attach-extensions.js +46 -0
  130. package/lib/core/config-runtime-paths.js +29 -0
  131. package/lib/core/config.js +55 -56
  132. package/lib/core/diff.js +3 -2
  133. package/lib/core/ensure-encryption-key.js +1 -1
  134. package/lib/core/secrets-ensure-infra.js +77 -0
  135. package/lib/core/secrets-ensure.js +120 -64
  136. package/lib/core/secrets-env-write.js +35 -7
  137. package/lib/core/secrets-infra-placeholder-sync.js +61 -0
  138. package/lib/core/secrets.js +200 -37
  139. package/lib/core/templates-env.js +4 -3
  140. package/lib/datasource/abac-validator.js +1 -10
  141. package/lib/datasource/deploy.js +75 -53
  142. package/lib/datasource/field-reference-validator.js +9 -6
  143. package/lib/datasource/integration-context.js +63 -0
  144. package/lib/datasource/list.js +8 -7
  145. package/lib/datasource/log-viewer.js +84 -53
  146. package/lib/datasource/resolve-app.js +4 -4
  147. package/lib/datasource/test-e2e.js +95 -146
  148. package/lib/datasource/test-integration.js +114 -122
  149. package/lib/datasource/unified-validation-run-body.js +65 -0
  150. package/lib/datasource/unified-validation-run-post.js +23 -0
  151. package/lib/datasource/unified-validation-run-resolve.js +43 -0
  152. package/lib/datasource/unified-validation-run.js +92 -0
  153. package/lib/datasource/validate.js +157 -13
  154. package/lib/deployment/deployer.js +4 -3
  155. package/lib/deployment/environment.js +7 -6
  156. package/lib/deployment/push.js +17 -8
  157. package/lib/external-system/delete.js +4 -3
  158. package/lib/external-system/deploy.js +131 -53
  159. package/lib/external-system/download-helpers.js +1 -1
  160. package/lib/external-system/download.js +7 -6
  161. package/lib/external-system/generator.js +92 -6
  162. package/lib/external-system/integration-test-dispatch.js +26 -0
  163. package/lib/external-system/test-execution.js +5 -1
  164. package/lib/external-system/test-helpers.js +0 -4
  165. package/lib/external-system/test-system-level-helpers.js +110 -0
  166. package/lib/external-system/test-system-level.js +83 -44
  167. package/lib/external-system/test.js +59 -8
  168. package/lib/generator/builders.js +23 -11
  169. package/lib/generator/deploy-manifest-azure-kv.js +81 -0
  170. package/lib/generator/external.js +16 -4
  171. package/lib/generator/helpers.js +58 -3
  172. package/lib/generator/index.js +4 -0
  173. package/lib/generator/split-readme.js +12 -7
  174. package/lib/generator/split-variables.js +2 -1
  175. package/lib/generator/split.js +1 -1
  176. package/lib/generator/wizard-readme.js +3 -3
  177. package/lib/generator/wizard.js +8 -8
  178. package/lib/infrastructure/compose.js +60 -6
  179. package/lib/infrastructure/helpers.js +201 -29
  180. package/lib/infrastructure/index.js +28 -17
  181. package/lib/infrastructure/services.js +21 -15
  182. package/lib/internal/fs-real-sync.js +104 -0
  183. package/lib/internal/node-fs.js +98 -0
  184. package/lib/parameters/database-secret-values.js +173 -0
  185. package/lib/parameters/infra-kv-discovery.js +121 -0
  186. package/lib/parameters/infra-parameter-catalog.js +458 -0
  187. package/lib/parameters/infra-parameter-validate.js +64 -0
  188. package/lib/schema/application-schema.json +37 -17
  189. package/lib/schema/datasource-test-run.schema.json +493 -0
  190. package/lib/schema/deployment-rules.yaml +102 -63
  191. package/lib/schema/external-datasource.schema.json +1200 -442
  192. package/lib/schema/external-system.schema.json +181 -5
  193. package/lib/schema/flag-map-validation-run.json +31 -0
  194. package/lib/schema/infra-parameter.schema.json +106 -0
  195. package/lib/schema/infra.parameter.yaml +421 -0
  196. package/lib/schema/type/credential-auth-templates.json +40 -0
  197. package/lib/schema/type/document-storage.json +213 -0
  198. package/lib/schema/type/message-service.json +123 -0
  199. package/lib/schema/type/vector-store.json +88 -0
  200. package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
  201. package/lib/utils/api-error-handler.js +2 -2
  202. package/lib/utils/api.js +49 -14
  203. package/lib/utils/app-register-api.js +3 -2
  204. package/lib/utils/app-register-auth.js +1 -1
  205. package/lib/utils/app-register-config.js +4 -4
  206. package/lib/utils/app-register-display.js +3 -2
  207. package/lib/utils/app-register-validator.js +3 -2
  208. package/lib/utils/app-run-containers.js +26 -22
  209. package/lib/utils/app-scoped-config.js +31 -0
  210. package/lib/utils/app-service-env-from-builder.js +164 -0
  211. package/lib/utils/build-copy.js +1 -1
  212. package/lib/utils/build-helpers.js +20 -20
  213. package/lib/utils/build-resolve-image.js +165 -0
  214. package/lib/utils/cli-layout-chalk.js +8 -0
  215. package/lib/utils/cli-test-layout-chalk.js +267 -0
  216. package/lib/utils/cli-utils.js +88 -11
  217. package/lib/utils/compose-db-passwords.js +138 -0
  218. package/lib/utils/compose-generate-docker-compose.js +216 -0
  219. package/lib/utils/compose-generator.js +197 -291
  220. package/lib/utils/compose-miso-env.js +18 -0
  221. package/lib/utils/compose-traefik-ingress-base.js +158 -0
  222. package/lib/utils/config-paths.js +166 -7
  223. package/lib/utils/config-scoped-resources-preference.js +41 -0
  224. package/lib/utils/controller-deployment-outcome.js +68 -0
  225. package/lib/utils/credential-display.js +2 -2
  226. package/lib/utils/dataplane-pipeline-warning.js +4 -3
  227. package/lib/utils/datasource-test-run-capability-scope.js +43 -0
  228. package/lib/utils/datasource-test-run-debug-display.js +137 -0
  229. package/lib/utils/datasource-test-run-debug-slice.js +93 -0
  230. package/lib/utils/datasource-test-run-display.js +442 -0
  231. package/lib/utils/datasource-test-run-exit.js +58 -0
  232. package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
  233. package/lib/utils/datasource-test-run-report-version.js +51 -0
  234. package/lib/utils/datasource-test-run-schema-sync.js +59 -0
  235. package/lib/utils/datasource-test-run-tty-log.js +81 -0
  236. package/lib/utils/datasource-validation-watch.js +266 -0
  237. package/lib/utils/declarative-url-ports.js +47 -0
  238. package/lib/utils/derive-env-key-from-client-id.js +41 -0
  239. package/lib/utils/dev-ca-install.js +185 -23
  240. package/lib/utils/dev-cert-helper.js +266 -17
  241. package/lib/utils/dev-hosts-helper.js +307 -0
  242. package/lib/utils/dev-init-cert-hints.js +37 -0
  243. package/lib/utils/dev-init-health-messages.js +52 -0
  244. package/lib/utils/dev-init-resolve.js +86 -0
  245. package/lib/utils/dev-init-ssh-merge.js +65 -0
  246. package/lib/utils/dev-ssh-config-helper.js +196 -0
  247. package/lib/utils/dev-user-groups.js +93 -0
  248. package/lib/utils/docker-build.js +42 -17
  249. package/lib/utils/docker-exec.js +28 -0
  250. package/lib/utils/docker-manifest-public-port.js +116 -0
  251. package/lib/utils/docker-not-running-hint.js +52 -0
  252. package/lib/utils/docker.js +98 -11
  253. package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
  254. package/lib/utils/env-config-loader.js +10 -91
  255. package/lib/utils/env-copy.js +19 -10
  256. package/lib/utils/env-map.js +35 -8
  257. package/lib/utils/env-template.js +2 -2
  258. package/lib/utils/environment-scoped-resources.js +144 -0
  259. package/lib/utils/error-formatter.js +92 -13
  260. package/lib/utils/error-formatters/http-status-errors.js +6 -5
  261. package/lib/utils/error-formatters/network-errors.js +2 -1
  262. package/lib/utils/error-formatters/permission-errors.js +2 -1
  263. package/lib/utils/error-formatters/validation-errors.js +2 -1
  264. package/lib/utils/external-readme.js +8 -1
  265. package/lib/utils/external-system-display.js +234 -136
  266. package/lib/utils/external-system-local-test-tty.js +389 -0
  267. package/lib/utils/external-system-readiness-core.js +377 -0
  268. package/lib/utils/external-system-readiness-deploy-display.js +270 -0
  269. package/lib/utils/external-system-readiness-display-internals.js +150 -0
  270. package/lib/utils/external-system-readiness-display.js +186 -0
  271. package/lib/utils/external-system-test-helpers.js +24 -6
  272. package/lib/utils/external-system-validators.js +30 -12
  273. package/lib/utils/health-check-url.js +119 -0
  274. package/lib/utils/health-check.js +59 -25
  275. package/lib/utils/help-builder.js +11 -8
  276. package/lib/utils/image-version.js +4 -8
  277. package/lib/utils/infra-containers.js +4 -7
  278. package/lib/utils/infra-env-defaults.js +162 -0
  279. package/lib/utils/infra-status-display.js +167 -0
  280. package/lib/utils/infra-status.js +16 -8
  281. package/lib/utils/local-secrets.js +3 -4
  282. package/lib/utils/paths.js +134 -47
  283. package/lib/utils/port-resolver.js +10 -23
  284. package/lib/utils/redis-env-scope.js +62 -0
  285. package/lib/utils/register-aifabrix-shell-env.js +204 -0
  286. package/lib/utils/remote-builder-validation.js +99 -0
  287. package/lib/utils/remote-dev-auth.js +117 -21
  288. package/lib/utils/remote-docker-env.js +67 -15
  289. package/lib/utils/remote-secrets-loader.js +13 -4
  290. package/lib/utils/resolve-docker-image-ref.js +124 -0
  291. package/lib/utils/schema-loader.js +22 -9
  292. package/lib/utils/secrets-bash-kv.js +25 -0
  293. package/lib/utils/secrets-generator.js +169 -49
  294. package/lib/utils/secrets-helpers.js +70 -59
  295. package/lib/utils/secrets-kv-scope.js +60 -0
  296. package/lib/utils/secrets-utils.js +32 -38
  297. package/lib/utils/secrets-validation.js +3 -1
  298. package/lib/utils/secrets-yaml-preserve.js +109 -0
  299. package/lib/utils/ssh-key-helper.js +4 -2
  300. package/lib/utils/template-helpers.js +2 -2
  301. package/lib/utils/test-log-writer.js +3 -3
  302. package/lib/utils/token-manager.js +1 -2
  303. package/lib/utils/url-declarative-public-base.js +188 -0
  304. package/lib/utils/url-declarative-resolve-build.js +493 -0
  305. package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
  306. package/lib/utils/url-declarative-resolve.js +220 -0
  307. package/lib/utils/url-declarative-token-parse.js +74 -0
  308. package/lib/utils/url-declarative-url-flags.js +50 -0
  309. package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
  310. package/lib/utils/url-public-path-prefix.js +34 -0
  311. package/lib/utils/urls-local-registry.js +220 -0
  312. package/lib/utils/validation-report-tty-kit.js +77 -0
  313. package/lib/utils/validation-run-poll.js +89 -0
  314. package/lib/utils/validation-run-post-retry.js +73 -0
  315. package/lib/utils/validation-run-request.js +98 -0
  316. package/lib/utils/variable-transformer.js +21 -4
  317. package/lib/utils/yaml-preserve.js +33 -14
  318. package/lib/validation/datasource-warnings.js +56 -0
  319. package/lib/validation/env-template-auth.js +1 -1
  320. package/lib/validation/external-manifest-validator.js +27 -7
  321. package/lib/validation/validate-display.js +37 -31
  322. package/lib/validation/validate.js +4 -13
  323. package/lib/validation/validator-unresolved-placeholders.js +98 -0
  324. package/lib/validation/validator.js +22 -65
  325. package/lib/validation/wizard-config-validator.js +2 -1
  326. package/package.json +7 -3
  327. package/scripts/check-datasource-test-run-schema-sync.js +34 -0
  328. package/scripts/diagnose-cli.js +150 -0
  329. package/scripts/install-local.js +304 -55
  330. package/templates/README.md +15 -2
  331. package/templates/applications/dataplane/application.yaml +52 -2
  332. package/templates/applications/dataplane/env.template +75 -17
  333. package/templates/applications/dataplane/rbac.yaml +8 -0
  334. package/templates/applications/keycloak/application.yaml +9 -1
  335. package/templates/applications/keycloak/env.template +15 -6
  336. package/templates/applications/miso-controller/application.yaml +10 -2
  337. package/templates/applications/miso-controller/env.template +42 -12
  338. package/templates/applications/miso-controller/rbac.yaml +5 -0
  339. package/templates/external-system/README.md.hbs +20 -7
  340. package/templates/external-system/deploy.js.hbs +5 -5
  341. package/templates/external-system/external-datasource.yaml.hbs +197 -118
  342. package/templates/infra/compose.yaml.hbs +20 -4
  343. package/templates/python/docker-compose.hbs +16 -0
  344. package/templates/typescript/docker-compose.hbs +16 -0
  345. package/lib/api/external-test.api.js +0 -111
  346. package/lib/schema/env-config.yaml +0 -60
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @fileoverview POST + optional poll for unified validation run (keeps main module small).
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const { postValidationRunAndOptionalPoll } = require('../api/validation-runner');
8
+
9
+ /**
10
+ * @param {Object} opts
11
+ * @param {string} opts.dataplaneUrl
12
+ * @param {Object} opts.authConfig
13
+ * @param {Object} opts.body
14
+ * @param {number} opts.timeoutMs
15
+ * @param {boolean} opts.useAsync
16
+ * @param {boolean} opts.noAsync
17
+ * @returns {Promise<{ envelope: Object|null, apiError: Object|null, pollTimedOut: boolean, incompleteNoAsync: boolean }>}
18
+ */
19
+ /* eslint-disable max-lines-per-function, max-statements, complexity -- POST + poll orchestration */
20
+ // Re-exported thin wrapper; single implementation lives in lib/api/validation-runner.js
21
+ /* eslint-enable max-lines-per-function, max-statements, complexity */
22
+
23
+ module.exports = { postValidationRunAndOptionalPoll };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @fileoverview Resolve datasource file path + parsed config for unified validation.
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const path = require('path');
8
+ const { getIntegrationPath } = require('../utils/paths');
9
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
10
+ const { loadConfigFile } = require('../utils/config-format');
11
+
12
+ /**
13
+ * @param {string} appKey
14
+ * @param {string} datasourceKey
15
+ * @param {Function} findDatasourceFileByKey - From integration-context
16
+ * @returns {{ datasource: Object, datasourcePath: string }}
17
+ */
18
+ function loadDatasourceForApp(appKey, datasourceKey, findDatasourceFileByKey) {
19
+ const appPath = getIntegrationPath(appKey);
20
+ const config = loadConfigFile(resolveApplicationConfigPath(appPath));
21
+ const schemaBasePath = config.externalIntegration?.schemaBasePath || './';
22
+ const datasourceFiles = config.externalIntegration?.dataSources || [];
23
+ let datasourceFile = datasourceFiles.find(f => {
24
+ const base = path.basename(f, path.extname(f));
25
+ return base === datasourceKey || base.includes(datasourceKey);
26
+ });
27
+ if (!datasourceFile) {
28
+ datasourceFile = findDatasourceFileByKey(appPath, schemaBasePath, datasourceFiles, datasourceKey);
29
+ }
30
+ if (!datasourceFile) {
31
+ throw new Error(`Datasource '${datasourceKey}' not found in application config`);
32
+ }
33
+ const datasourcePath = path.isAbsolute(schemaBasePath)
34
+ ? path.join(schemaBasePath, datasourceFile)
35
+ : path.join(appPath, schemaBasePath, datasourceFile);
36
+ const datasource = loadConfigFile(datasourcePath);
37
+ if (datasource.key !== datasourceKey) {
38
+ throw new Error(`Datasource key mismatch: file has '${datasource.key}', expected '${datasourceKey}'`);
39
+ }
40
+ return { datasource, datasourcePath };
41
+ }
42
+
43
+ module.exports = { loadDatasourceForApp };
@@ -0,0 +1,92 @@
1
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
2
+ /**
3
+ * @fileoverview Run one persisted datasource through the dataplane unified validation flow (POST + optional poll via `lib/api/validation-run.api.js`).
4
+ *
5
+ * **CLI:** `aifabrix datasource test`, `test-integration`, and `test-e2e` use this module with different `runType` values.
6
+ * **User-facing permissions:** see `docs/commands/permissions.md` (Dataplane scopes may differ from the minimum noted on the HTTP helper JSDoc).
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ const chalk = require('chalk');
12
+ const { resolveAppKeyForDatasource } = require('./resolve-app');
13
+ const { setupIntegrationTestAuth } = require('../external-system/test-auth');
14
+ const { getConfig } = require('../core/config');
15
+ const { getSystemKeyFromAppKey, findDatasourceFileByKey } = require('./integration-context');
16
+ const { loadDatasourceForApp } = require('./unified-validation-run-resolve');
17
+ const { buildUnifiedValidationBody } = require('./unified-validation-run-body');
18
+ const { postValidationRunAndOptionalPoll } = require('./unified-validation-run-post');
19
+ const { publishDatasourceViaPipeline } = require('../api/pipeline.api');
20
+ const { requireBearerForDataplanePipeline } = require('../utils/token-manager');
21
+ const { formatApiError } = require('../utils/api-error-handler');
22
+ const logger = require('../utils/logger');
23
+
24
+ /**
25
+ * Resolve datasource JSON path and loaded object (same rules as test-integration).
26
+ * Re-export for tests.
27
+ */
28
+ function loadDatasourceForAppExport(appKey, datasourceKey) {
29
+ return loadDatasourceForApp(appKey, datasourceKey, findDatasourceFileByKey);
30
+ }
31
+
32
+ /**
33
+ * Run unified validation for one persisted datasource.
34
+ * @async
35
+ * @param {string} datasourceKey
36
+ * @param {Object} options
37
+ * @param {boolean} [options.sync] - Publish local datasource JSON via pipeline before POST validation/run
38
+ * @returns {Promise<{ envelope: Object|null, apiError: Object|null, pollTimedOut: boolean, incompleteNoAsync: boolean }>}
39
+ */
40
+ async function runUnifiedDatasourceValidation(datasourceKey, options) {
41
+ if (!datasourceKey || typeof datasourceKey !== 'string') {
42
+ throw new Error('Datasource key is required');
43
+ }
44
+ const runType = options.runType || 'test';
45
+ const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
46
+ const systemKey = await getSystemKeyFromAppKey(appKey);
47
+ const { datasource } = loadDatasourceForAppExport(appKey, datasourceKey);
48
+
49
+ const configObj = await getConfig();
50
+ const { authConfig, dataplaneUrl } = await setupIntegrationTestAuth(appKey, options, configObj);
51
+
52
+ if (options.sync === true) {
53
+ requireBearerForDataplanePipeline(authConfig);
54
+ logger.log(chalk.cyan('Syncing local config to dataplane…'));
55
+ const publishResponse = await publishDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig, datasource);
56
+ if (!publishResponse || publishResponse.success === false) {
57
+ const msg =
58
+ (publishResponse && (publishResponse.formattedError || publishResponse.error)) ||
59
+ formatApiError(publishResponse, dataplaneUrl) ||
60
+ 'Publish failed';
61
+ throw new Error(`Sync failed: ${msg}`);
62
+ }
63
+ logger.log(formatSuccessLine('Sync complete'));
64
+ }
65
+
66
+ const useAsync = options.noAsync ? false : options.async !== false;
67
+ const timeoutMs = parseInt(String(options.timeout || '30000'), 10) || 30000;
68
+
69
+ const body = await buildUnifiedValidationBody({
70
+ systemKey,
71
+ datasourceKey,
72
+ runType,
73
+ datasource,
74
+ payloadPath: options.payload,
75
+ useAsync,
76
+ options
77
+ });
78
+
79
+ return postValidationRunAndOptionalPoll({
80
+ dataplaneUrl,
81
+ authConfig,
82
+ body,
83
+ timeoutMs,
84
+ useAsync,
85
+ noAsync: options.noAsync === true || options.async === false
86
+ });
87
+ }
88
+
89
+ module.exports = {
90
+ runUnifiedDatasourceValidation,
91
+ loadDatasourceForApp: loadDatasourceForAppExport
92
+ };
@@ -8,33 +8,172 @@
8
8
  * @version 2.0.0
9
9
  */
10
10
 
11
+ const path = require('path');
11
12
  const fs = require('fs');
13
+ const pathsUtil = require('../utils/paths');
12
14
  const { loadExternalDataSourceSchema } = require('../utils/schema-loader');
13
15
  const { formatValidationErrors } = require('../utils/error-formatter');
14
16
  const { validateFieldReferences } = require('./field-reference-validator');
15
17
  const { validateAbac } = require('./abac-validator');
16
18
 
19
+ const EXCLUDE_JSON_NAMES = new Set(['application.json', 'rbac.json', 'package.json', 'package-lock.json']);
20
+
21
+ /**
22
+ * Resolve identifier to an existing file path, or null.
23
+ * If `fs.statSync` is unavailable (partial Jest mocks), `existsSync` alone implies a file.
24
+ * @param {string} identifier
25
+ * @returns {string|null} Absolute path
26
+ */
27
+ function tryResolveAsExistingFile(identifier) {
28
+ const candidates = [...new Set([identifier, path.resolve(identifier)])];
29
+ for (const p of candidates) {
30
+ try {
31
+ if (!fs.existsSync(p)) continue;
32
+ try {
33
+ const st = fs.statSync(p);
34
+ if (st && typeof st.isFile === 'function' && st.isFile()) {
35
+ return path.resolve(p);
36
+ }
37
+ } catch {
38
+ return path.resolve(p);
39
+ }
40
+ } catch {
41
+ // ignore
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+
47
+ /**
48
+ * Pick integration app folder for a datasource `key` (longest matching prefix wins).
49
+ * @param {string} datasourceKey
50
+ * @param {string[]} appNames
51
+ * @returns {string|null}
52
+ */
53
+ function pickIntegrationAppForDatasourceKey(datasourceKey, appNames) {
54
+ const matches = appNames.filter((app) => datasourceKey === app || datasourceKey.startsWith(`${app}-`));
55
+ if (matches.length === 0) return null;
56
+ matches.sort((a, b) => b.length - a.length);
57
+ return matches[0];
58
+ }
59
+
60
+ /**
61
+ * @param {string} fileName
62
+ * @returns {boolean}
63
+ */
64
+ function shouldScanJsonDatasourceCandidate(fileName) {
65
+ const lower = fileName.toLowerCase();
66
+ if (!lower.endsWith('.json')) return false;
67
+ if (EXCLUDE_JSON_NAMES.has(fileName)) return false;
68
+ if (lower.endsWith('-deploy.json') || lower.endsWith('-system.json')) return false;
69
+ return true;
70
+ }
71
+
72
+ /**
73
+ * @param {string} fileName
74
+ * @returns {boolean}
75
+ */
76
+ function isDatasourceNamedJson(fileName) {
77
+ return fileName.toLowerCase().includes('datasource');
78
+ }
79
+
80
+ /**
81
+ * @param {string} dir
82
+ * @param {string[]} scanOrder
83
+ * @param {string} datasourceKey
84
+ * @returns {string[]}
85
+ */
86
+ function collectJsonPathsMatchingDatasourceKey(dir, scanOrder, datasourceKey) {
87
+ const hits = [];
88
+ for (const name of scanOrder) {
89
+ const full = path.join(dir, name);
90
+ let parsed;
91
+ try {
92
+ parsed = JSON.parse(fs.readFileSync(full, 'utf8'));
93
+ } catch {
94
+ continue;
95
+ }
96
+ if (parsed && typeof parsed === 'object' && parsed.key === datasourceKey) {
97
+ hits.push(full);
98
+ }
99
+ }
100
+ return hits;
101
+ }
102
+
103
+ /**
104
+ * Find integration JSON whose parsed `key` equals datasourceKey.
105
+ * @param {string} datasourceKey
106
+ * @returns {string} Absolute file path
107
+ * @throws {Error}
108
+ */
109
+ function resolvePathFromIntegrationDatasourceKey(datasourceKey) {
110
+ const appNames = pathsUtil.listIntegrationAppNames();
111
+ const appName = pickIntegrationAppForDatasourceKey(datasourceKey, appNames);
112
+ if (!appName) {
113
+ throw new Error(
114
+ `No integration/<app>/ folder matches datasource key "${datasourceKey}". ` +
115
+ 'Pass a path to the JSON file, or use a key whose prefix matches an app under integration/ ' +
116
+ '(e.g. app test-e2e-hubspot for key test-e2e-hubspot-users).'
117
+ );
118
+ }
119
+ const dir = pathsUtil.getIntegrationPath(appName);
120
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
121
+ throw new Error(`Integration folder not found: ${dir}`);
122
+ }
123
+ const entries = fs.readdirSync(dir);
124
+ const jsonFiles = entries.filter(shouldScanJsonDatasourceCandidate);
125
+ const prefer = jsonFiles.filter(isDatasourceNamedJson);
126
+ const scanOrder = [...prefer, ...jsonFiles.filter((f) => !prefer.includes(f))];
127
+ const hits = collectJsonPathsMatchingDatasourceKey(dir, scanOrder, datasourceKey);
128
+ if (hits.length === 0) {
129
+ throw new Error(
130
+ `No datasource JSON with key "${datasourceKey}" under integration/${appName}/. ` +
131
+ `Checked: ${scanOrder.length ? scanOrder.join(', ') : '(no JSON candidates)'}`
132
+ );
133
+ }
134
+ if (hits.length > 1) {
135
+ throw new Error(`Ambiguous: multiple files declare key "${datasourceKey}": ${hits.join(', ')}`);
136
+ }
137
+ return hits[0];
138
+ }
139
+
140
+ /**
141
+ * @param {string} identifier - File path or datasource `key`
142
+ * @returns {string} Absolute path to JSON file
143
+ * @throws {Error}
144
+ */
145
+ function resolveValidateInputPath(identifier) {
146
+ const trimmed = String(identifier || '').trim();
147
+ if (!trimmed) {
148
+ throw new Error('File path is required and must be a string');
149
+ }
150
+ const asFile = tryResolveAsExistingFile(trimmed);
151
+ if (asFile) return asFile;
152
+ if (trimmed.toLowerCase().endsWith('.json')) {
153
+ throw new Error(`File not found: ${trimmed}`);
154
+ }
155
+ return resolvePathFromIntegrationDatasourceKey(trimmed);
156
+ }
157
+
17
158
  /**
18
159
  * Validates a datasource file against external-datasource schema
19
160
  *
20
161
  * @async
21
162
  * @function validateDatasourceFile
22
- * @param {string} filePath - Path to the datasource JSON file
23
- * @returns {Promise<Object>} Validation result with errors and warnings
163
+ * @param {string} filePathOrKey - Path to the datasource JSON file, or datasource `key` resolved under integration/<app>/
164
+ * @returns {Promise<Object>} Validation result with errors, warnings, and `resolvedPath` (absolute JSON path)
24
165
  * @throws {Error} If file cannot be read or parsed
25
166
  *
26
167
  * @example
27
168
  * const result = await validateDatasourceFile('./hubspot-deal.json');
28
- * // Returns: { valid: true, errors: [], warnings: [] }
169
+ * // Returns: { valid: true, errors: [], warnings: [], resolvedPath: '...' }
29
170
  */
30
- async function validateDatasourceFile(filePath) {
31
- if (!filePath || typeof filePath !== 'string') {
171
+ async function validateDatasourceFile(filePathOrKey) {
172
+ if (!filePathOrKey || typeof filePathOrKey !== 'string') {
32
173
  throw new Error('File path is required and must be a string');
33
174
  }
34
175
 
35
- if (!fs.existsSync(filePath)) {
36
- throw new Error(`File not found: ${filePath}`);
37
- }
176
+ const filePath = resolveValidateInputPath(filePathOrKey.trim());
38
177
 
39
178
  const content = fs.readFileSync(filePath, 'utf8');
40
179
  let parsed;
@@ -45,7 +184,8 @@ async function validateDatasourceFile(filePath) {
45
184
  return {
46
185
  valid: false,
47
186
  errors: [`Invalid JSON syntax: ${error.message}`],
48
- warnings: []
187
+ warnings: [],
188
+ resolvedPath: filePath
49
189
  };
50
190
  }
51
191
 
@@ -56,7 +196,8 @@ async function validateDatasourceFile(filePath) {
56
196
  return {
57
197
  valid: false,
58
198
  errors: formatValidationErrors(validate.errors),
59
- warnings: []
199
+ warnings: [],
200
+ resolvedPath: filePath
60
201
  };
61
202
  }
62
203
 
@@ -67,18 +208,21 @@ async function validateDatasourceFile(filePath) {
67
208
  return {
68
209
  valid: false,
69
210
  errors: postSchemaErrors,
70
- warnings: []
211
+ warnings: [],
212
+ resolvedPath: filePath
71
213
  };
72
214
  }
73
215
 
74
216
  return {
75
217
  valid: true,
76
218
  errors: [],
77
- warnings: []
219
+ warnings: [],
220
+ resolvedPath: filePath
78
221
  };
79
222
  }
80
223
 
81
224
  module.exports = {
82
- validateDatasourceFile
225
+ validateDatasourceFile,
226
+ resolveValidateInputPath
83
227
  };
84
228
 
@@ -1,3 +1,4 @@
1
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * AI Fabrix Builder Deployment Module
3
4
  *
@@ -139,7 +140,7 @@ async function validateDeployment(url, envKey, manifest, authConfig, options = {
139
140
  const shouldRetry = attempt < maxRetries && error.status && error.status >= 500;
140
141
  if (shouldRetry) {
141
142
  const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
142
- logger.log(chalk.yellow(`⚠️ Validation attempt ${attempt} failed, retrying in ${delay}ms...`));
143
+ logger.log(chalk.yellow(`⚠ Validation attempt ${attempt} failed, retrying in ${delay}ms...`));
143
144
  await new Promise(resolve => setTimeout(resolve, delay));
144
145
  } else {
145
146
  throw error;
@@ -266,7 +267,7 @@ async function sendDeploymentRequest(url, envKey, validateToken, authConfig, opt
266
267
  lastError = error;
267
268
  if (attempt < maxRetries) {
268
269
  const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
269
- logger.log(chalk.yellow(`⚠️ Deployment attempt ${attempt} failed, retrying in ${delay}ms...`));
270
+ logger.log(chalk.yellow(`⚠ Deployment attempt ${attempt} failed, retrying in ${delay}ms...`));
270
271
  await new Promise(resolve => setTimeout(resolve, delay));
271
272
  }
272
273
  }
@@ -365,7 +366,7 @@ async function sendDeployment(url, validatedEnvKey, manifest, authConfig, option
365
366
  throw new Error('Validation failed: validateToken not received');
366
367
  }
367
368
 
368
- logger.log(chalk.green('Validation successful'));
369
+ logger.log(formatSuccessLine('Validation successful'));
369
370
  if (validateResult.draftDeploymentId) {
370
371
  logger.log(chalk.gray(` Draft Deployment ID: ${validateResult.draftDeploymentId}`));
371
372
  }
@@ -1,3 +1,4 @@
1
+ const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * AI Fabrix Builder Environment Deployment Module
3
4
  *
@@ -276,16 +277,16 @@ async function pollEnvironmentStatus(deploymentId, controllerUrl, envKey, authCo
276
277
  * @param {Object} result - Deployment result
277
278
  */
278
279
  function displayDeploymentResults(result) {
279
- logger.log(chalk.green('\n✅ Environment deployed successfully'));
280
+ logger.log(formatSuccessParagraph('Environment deployed successfully'));
280
281
  logger.log(chalk.blue(` Environment: ${result.environment}`));
281
- logger.log(chalk.blue(` Status: ${result.status === 'ready' ? ' ready' : result.status}`));
282
+ logger.log(chalk.blue(` Status: ${result.status === 'ready' ? ' ready' : result.status}`));
282
283
  if (result.url) {
283
284
  logger.log(chalk.blue(` URL: ${result.url}`));
284
285
  }
285
286
  if (result.deploymentId) {
286
287
  logger.log(chalk.blue(` Deployment ID: ${result.deploymentId}`));
287
288
  }
288
- logger.log(chalk.green('\n✓ Environment is ready for application deployments'));
289
+ logger.log(formatSuccessParagraph('Environment is ready for application deployments'));
289
290
  }
290
291
 
291
292
  /**
@@ -340,8 +341,8 @@ async function prepareEnvironmentDeployment(envKey, controllerUrl, options) {
340
341
  // Get authentication (device token)
341
342
  logger.log(chalk.blue(`\n📋 Deploying environment '${envKey}' to ${controllerUrl}...`));
342
343
  const authConfig = await getEnvironmentAuth(controllerUrl);
343
- logger.log(chalk.green('Environment validated'));
344
- logger.log(chalk.green('Authentication successful'));
344
+ logger.log(formatSuccessLine('Environment validated'));
345
+ logger.log(formatSuccessLine('Authentication successful'));
345
346
 
346
347
  return authConfig;
347
348
  }
@@ -412,7 +413,7 @@ async function deployEnvironment(envKey, options = {}) {
412
413
  // Error handling is done in sendEnvironmentDeployment and pollEnvironmentStatus
413
414
  // Re-throw with context
414
415
  if (error._logged !== true) {
415
- logger.error(chalk.red(`\n Environment deployment failed: ${error.message}`));
416
+ logger.error(chalk.red(`\n Environment deployment failed: ${error.message}`));
416
417
  }
417
418
  throw error;
418
419
  }
@@ -1,3 +1,4 @@
1
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
1
2
  /**
2
3
  * AI Fabrix Builder Push Utilities
3
4
  *
@@ -170,7 +171,7 @@ async function authenticateACR(registry) {
170
171
  // On Windows, use shell option to ensure proper command resolution
171
172
  const options = process.platform === 'win32' ? { shell: true } : {};
172
173
  await execAsync(`az acr login --name ${registryName}`, { ...options, timeout: AZ_ACR_LOGIN_TIMEOUT_MS });
173
- logger.log(chalk.green(`✓ Authenticated with ${registry}`));
174
+ logger.log(formatSuccessLine(`Authenticated with ${registry}`));
174
175
  } catch (error) {
175
176
  const msg = error.message || String(error);
176
177
  if (msg.includes('ETIMEDOUT') || msg.includes('timeout')) {
@@ -196,7 +197,9 @@ async function authenticateExternalRegistry(registry, username, password) {
196
197
  // Use cross-platform approach: write password to stdin directly
197
198
  // This works on Windows, Linux, and macOS
198
199
  const { spawn } = require('child_process');
199
- const dockerLogin = spawn('docker', ['login', registry, '-u', username, '--password-stdin']);
200
+ const { getDockerExecEnv } = require('../utils/remote-docker-env');
201
+ const dockerEnv = await getDockerExecEnv();
202
+ const dockerLogin = spawn('docker', ['login', registry, '-u', username, '--password-stdin'], { env: dockerEnv });
200
203
 
201
204
  return new Promise((resolve, reject) => {
202
205
  let errorOutput = '';
@@ -214,7 +217,7 @@ async function authenticateExternalRegistry(registry, username, password) {
214
217
 
215
218
  dockerLogin.on('close', (code) => {
216
219
  if (code === 0) {
217
- logger.log(chalk.green(`✓ Authenticated with ${registry}`));
220
+ logger.log(formatSuccessLine(`Authenticated with ${registry}`));
218
221
  resolve();
219
222
  } else {
220
223
  reject(new Error(`Docker login failed: ${errorOutput || `Exit code ${code}`}`));
@@ -238,8 +241,10 @@ async function authenticateExternalRegistry(registry, username, password) {
238
241
  */
239
242
  async function checkLocalImageExists(imageName, tag = 'latest') {
240
243
  try {
244
+ const { getDockerExecEnv } = require('../utils/remote-docker-env');
245
+ const env = await getDockerExecEnv();
241
246
  // Use Docker's native filtering for cross-platform compatibility (Windows-safe)
242
- const { stdout } = await execAsync(`docker images --format "{{.Repository}}:{{.Tag}}" --filter "reference=${imageName}:${tag}"`);
247
+ const { stdout } = await execAsync(`docker images --format "{{.Repository}}:{{.Tag}}" --filter "reference=${imageName}:${tag}"`, { env });
243
248
  const lines = stdout.trim().split('\n').filter(line => line.trim() !== '');
244
249
  return lines.some(line => line.trim() === `${imageName}:${tag}`);
245
250
  } catch (error) {
@@ -255,9 +260,11 @@ async function checkLocalImageExists(imageName, tag = 'latest') {
255
260
  */
256
261
  async function tagImage(sourceImage, targetImage) {
257
262
  try {
263
+ const { getDockerExecEnv } = require('../utils/remote-docker-env');
264
+ const env = await getDockerExecEnv();
258
265
  logger.log(chalk.blue(`Tagging ${sourceImage} as ${targetImage}...`));
259
- await execAsync(`docker tag ${sourceImage} ${targetImage}`);
260
- logger.log(chalk.green(`✓ Tagged: ${targetImage}`));
266
+ await execAsync(`docker tag ${sourceImage} ${targetImage}`, { env });
267
+ logger.log(formatSuccessLine(`Tagged: ${targetImage}`));
261
268
  } catch (error) {
262
269
  throw new Error(`Failed to tag image: ${error.message}`);
263
270
  }
@@ -271,9 +278,11 @@ async function tagImage(sourceImage, targetImage) {
271
278
  */
272
279
  async function pushImage(imageWithTag, registry = null) {
273
280
  try {
281
+ const { getDockerExecEnv } = require('../utils/remote-docker-env');
282
+ const env = await getDockerExecEnv();
274
283
  logger.log(chalk.blue(`Pushing ${imageWithTag}...`));
275
- await execAsync(`docker push ${imageWithTag}`);
276
- logger.log(chalk.green(`✓ Pushed: ${imageWithTag}`));
284
+ await execAsync(`docker push ${imageWithTag}`, { env });
285
+ logger.log(formatSuccessLine(`Pushed: ${imageWithTag}`));
277
286
  } catch (error) {
278
287
  const errorMessage = error.message || error.stderr || String(error);
279
288
  const isAuthError = errorMessage.includes('authentication required') ||
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  'use strict';
12
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
12
13
 
13
14
  const chalk = require('chalk');
14
15
  const inquirer = require('inquirer');
@@ -97,7 +98,7 @@ async function confirmDeletion(systemKey, datasources, options) {
97
98
  return true;
98
99
  }
99
100
 
100
- logger.log(chalk.yellow(`\n⚠️ Warning: Deleting external system '${systemKey}' will also delete all associated datasources:`));
101
+ logger.log(chalk.yellow(`\n Warning: Deleting external system '${systemKey}' will also delete all associated datasources:`));
101
102
  if (datasources.length > 0) {
102
103
  datasources.forEach(ds => logger.log(chalk.yellow(` - ${ds}`)));
103
104
  } else {
@@ -141,8 +142,8 @@ async function deleteExternalSystemCommand(systemKey, options = {}) {
141
142
  throw new Error(response?.error || response?.formattedError || `Failed to delete external system '${systemKey}'`);
142
143
  }
143
144
 
144
- logger.log(chalk.green(`✓ External system '${systemKey}' deleted successfully`));
145
- logger.log(chalk.green('All associated datasources have been removed'));
145
+ logger.log(formatSuccessLine(`External system '${systemKey}' deleted successfully`));
146
+ logger.log(formatSuccessLine('All associated datasources have been removed'));
146
147
  }
147
148
 
148
149
  module.exports = {