@aifabrix/builder 2.44.4 → 2.44.6

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 (214) hide show
  1. package/.cursor/rules/cli-layout.mdc +1 -1
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/.npmrc.token +1 -1
  4. package/README.md +15 -23
  5. package/integration/hubspot-test/README.md +2 -0
  6. package/integration/hubspot-test/test.js +5 -3
  7. package/jest.projects.js +68 -17
  8. package/lib/api/controller-health.api.js +49 -0
  9. package/lib/api/dimension-values.api.js +82 -0
  10. package/lib/api/dimensions.api.js +114 -0
  11. package/lib/api/external-systems.api.js +1 -0
  12. package/lib/api/integration-clients.api.js +168 -0
  13. package/lib/api/types/dimension-values.types.js +28 -0
  14. package/lib/api/types/dimensions.types.js +31 -0
  15. package/lib/api/types/integration-clients.types.js +45 -0
  16. package/lib/api/types/wizard.types.js +2 -1
  17. package/lib/api/validation-runner.js +46 -25
  18. package/lib/app/deploy-config.js +11 -1
  19. package/lib/app/deploy-status-display.js +3 -3
  20. package/lib/app/deploy.js +36 -14
  21. package/lib/app/display.js +15 -11
  22. package/lib/app/push.js +46 -23
  23. package/lib/app/register.js +1 -1
  24. package/lib/app/restart-display.js +95 -0
  25. package/lib/app/rotate-secret.js +1 -1
  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 +44 -12
  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 +99 -73
  32. package/lib/build/index.js +75 -45
  33. package/lib/cli/doctor-check.js +117 -0
  34. package/lib/cli/index.js +8 -2
  35. package/lib/cli/infra-guided.js +445 -0
  36. package/lib/cli/setup-app.help.js +1 -1
  37. package/lib/cli/setup-app.js +20 -2
  38. package/lib/cli/setup-app.test-commands.js +9 -5
  39. package/lib/cli/setup-auth.js +26 -0
  40. package/lib/cli/setup-dev-path-commands.js +50 -3
  41. package/lib/cli/setup-infra.js +138 -61
  42. package/lib/cli/setup-integration-client.js +182 -0
  43. package/lib/cli/setup-parameters.js +21 -2
  44. package/lib/cli/setup-platform.js +102 -0
  45. package/lib/cli/setup-secrets.js +18 -6
  46. package/lib/cli/setup-utility.js +97 -33
  47. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  48. package/lib/commands/datasource-capability-output.js +29 -0
  49. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  50. package/lib/commands/datasource-capability.js +411 -0
  51. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  52. package/lib/commands/datasource.js +53 -13
  53. package/lib/commands/dev-down.js +3 -3
  54. package/lib/commands/dev-infra-gate.js +32 -0
  55. package/lib/commands/dev-init.js +13 -7
  56. package/lib/commands/dimension-value.js +179 -0
  57. package/lib/commands/dimension.js +330 -0
  58. package/lib/commands/integration-client.js +430 -0
  59. package/lib/commands/login-device.js +65 -30
  60. package/lib/commands/login.js +21 -10
  61. package/lib/commands/parameters-validate.js +78 -13
  62. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  63. package/lib/commands/repair-datasource-keys.js +10 -5
  64. package/lib/commands/repair-datasource.js +19 -7
  65. package/lib/commands/repair-env-template.js +4 -1
  66. package/lib/commands/repair-openapi-sync.js +172 -0
  67. package/lib/commands/repair-persist.js +102 -0
  68. package/lib/commands/repair-rbac-extract.js +27 -0
  69. package/lib/commands/repair-rbac-migrate.js +186 -0
  70. package/lib/commands/repair-rbac.js +225 -19
  71. package/lib/commands/repair-system-alignment.js +246 -0
  72. package/lib/commands/repair-system-permissions.js +168 -0
  73. package/lib/commands/repair.js +120 -354
  74. package/lib/commands/secure.js +1 -1
  75. package/lib/commands/setup-modes.js +455 -0
  76. package/lib/commands/setup-prompts.js +388 -0
  77. package/lib/commands/setup.js +149 -0
  78. package/lib/commands/teardown.js +228 -0
  79. package/lib/commands/test-e2e-external.js +4 -3
  80. package/lib/commands/up-common.js +97 -12
  81. package/lib/commands/up-dataplane.js +33 -11
  82. package/lib/commands/up-miso.js +7 -11
  83. package/lib/commands/upload.js +109 -23
  84. package/lib/commands/wizard-core-helpers.js +14 -11
  85. package/lib/commands/wizard-core.js +58 -15
  86. package/lib/commands/wizard-dataplane.js +2 -2
  87. package/lib/commands/wizard-entity-selection.js +72 -14
  88. package/lib/commands/wizard-headless.js +7 -3
  89. package/lib/commands/wizard-helpers.js +13 -1
  90. package/lib/commands/wizard.js +210 -61
  91. package/lib/constants/infra-compose-service-names.js +40 -0
  92. package/lib/core/env-reader.js +16 -3
  93. package/lib/core/secrets-admin-env.js +101 -0
  94. package/lib/core/secrets-ensure-infra.js +34 -1
  95. package/lib/core/secrets-ensure.js +88 -66
  96. package/lib/core/secrets-env-content.js +432 -0
  97. package/lib/core/secrets-env-write.js +27 -1
  98. package/lib/core/secrets-load.js +248 -0
  99. package/lib/core/secrets-names.js +32 -0
  100. package/lib/core/secrets.js +17 -757
  101. package/lib/datasource/capability/basic-exposure.js +76 -0
  102. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  103. package/lib/datasource/capability/capability-key.js +34 -0
  104. package/lib/datasource/capability/capability-resolve.js +172 -0
  105. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  106. package/lib/datasource/capability/copy-operations.js +348 -0
  107. package/lib/datasource/capability/copy-test-payload.js +139 -0
  108. package/lib/datasource/capability/create-operations.js +235 -0
  109. package/lib/datasource/capability/dimension-operations.js +151 -0
  110. package/lib/datasource/capability/dimension-validate.js +219 -0
  111. package/lib/datasource/capability/json-pointer.js +31 -0
  112. package/lib/datasource/capability/reference-rewrite.js +51 -0
  113. package/lib/datasource/capability/relate-operations.js +325 -0
  114. package/lib/datasource/capability/relate-validate.js +219 -0
  115. package/lib/datasource/capability/remove-operations.js +275 -0
  116. package/lib/datasource/capability/run-capability-copy.js +152 -0
  117. package/lib/datasource/capability/run-capability-diff.js +135 -0
  118. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  119. package/lib/datasource/capability/run-capability-edit.js +377 -0
  120. package/lib/datasource/capability/run-capability-relate.js +193 -0
  121. package/lib/datasource/capability/run-capability-remove.js +105 -0
  122. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  123. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  124. package/lib/datasource/list.js +136 -23
  125. package/lib/datasource/log-viewer.js +2 -4
  126. package/lib/datasource/unified-validation-run.js +51 -16
  127. package/lib/datasource/validate.js +53 -1
  128. package/lib/deployment/deploy-poll-ui.js +60 -0
  129. package/lib/deployment/deployer-status.js +29 -3
  130. package/lib/deployment/deployer.js +48 -30
  131. package/lib/deployment/environment.js +7 -2
  132. package/lib/deployment/poll-interval.js +72 -0
  133. package/lib/deployment/push.js +11 -9
  134. package/lib/external-system/deploy.js +4 -1
  135. package/lib/external-system/download.js +61 -32
  136. package/lib/external-system/sync-deploy-manifest.js +33 -0
  137. package/lib/generator/wizard-prompts.js +7 -1
  138. package/lib/generator/wizard.js +34 -0
  139. package/lib/infrastructure/index.js +49 -19
  140. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  141. package/lib/parameters/infra-kv-discovery.js +29 -4
  142. package/lib/parameters/infra-parameter-catalog.js +6 -3
  143. package/lib/parameters/infra-parameter-validate.js +67 -19
  144. package/lib/resolvers/datasource-resolver.js +53 -0
  145. package/lib/resolvers/dimension-file.js +52 -0
  146. package/lib/resolvers/manifest-resolver.js +133 -0
  147. package/lib/schema/external-datasource.schema.json +183 -53
  148. package/lib/schema/external-system.schema.json +23 -10
  149. package/lib/schema/infra.parameter.yaml +26 -11
  150. package/lib/schema/wizard-config.schema.json +2 -2
  151. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  152. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  153. package/lib/utils/app-run-containers.js +2 -2
  154. package/lib/utils/bash-secret-env.js +59 -0
  155. package/lib/utils/cli-secrets-error-format.js +78 -0
  156. package/lib/utils/cli-test-layout-chalk.js +31 -9
  157. package/lib/utils/cli-utils.js +4 -36
  158. package/lib/utils/datasource-test-run-display.js +8 -0
  159. package/lib/utils/dev-hosts-helper.js +3 -2
  160. package/lib/utils/dev-init-ssh-merge.js +2 -1
  161. package/lib/utils/docker-build.js +17 -9
  162. package/lib/utils/docker-reload-mount.js +127 -0
  163. package/lib/utils/external-readme.js +117 -4
  164. package/lib/utils/external-system-local-test-tty.js +3 -2
  165. package/lib/utils/external-system-readiness-core.js +45 -12
  166. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  167. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  168. package/lib/utils/external-system-readiness-display.js +10 -1
  169. package/lib/utils/file-upload.js +40 -3
  170. package/lib/utils/health-check-db-init.js +107 -0
  171. package/lib/utils/health-check-public-warn.js +69 -0
  172. package/lib/utils/health-check-url.js +19 -4
  173. package/lib/utils/health-check.js +135 -105
  174. package/lib/utils/help-builder.js +5 -1
  175. package/lib/utils/image-name.js +34 -7
  176. package/lib/utils/integration-file-backup.js +74 -0
  177. package/lib/utils/mutagen-install.js +30 -3
  178. package/lib/utils/paths.js +108 -25
  179. package/lib/utils/postgres-wipe.js +212 -0
  180. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  181. package/lib/utils/remote-dev-auth.js +21 -5
  182. package/lib/utils/remote-docker-env.js +9 -1
  183. package/lib/utils/remote-secrets-loader.js +42 -3
  184. package/lib/utils/resolve-docker-image-ref.js +9 -3
  185. package/lib/utils/secrets-ancestor-paths.js +47 -0
  186. package/lib/utils/secrets-helpers.js +17 -10
  187. package/lib/utils/secrets-kv-refs.js +42 -0
  188. package/lib/utils/secrets-kv-scope.js +19 -2
  189. package/lib/utils/secrets-materialize-local.js +134 -0
  190. package/lib/utils/secrets-path.js +24 -10
  191. package/lib/utils/secrets-utils.js +2 -2
  192. package/lib/utils/system-builder-root.js +34 -0
  193. package/lib/utils/url-declarative-resolve-build.js +6 -1
  194. package/lib/utils/url-declarative-runtime-base-path.js +32 -0
  195. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  196. package/lib/utils/urls-local-registry.js +73 -20
  197. package/lib/utils/validation-poll-ui.js +81 -0
  198. package/lib/utils/validation-run-poll.js +29 -5
  199. package/lib/utils/with-muted-logger.js +53 -0
  200. package/package.json +1 -1
  201. package/templates/applications/dataplane/application.yaml +1 -1
  202. package/templates/applications/dataplane/rbac.yaml +10 -10
  203. package/templates/applications/keycloak/env.template +8 -6
  204. package/templates/applications/miso-controller/application.yaml +7 -0
  205. package/templates/applications/miso-controller/env.template +7 -7
  206. package/templates/applications/miso-controller/rbac.yaml +9 -9
  207. package/templates/external-system/README.md.hbs +89 -102
  208. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  209. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  210. package/.nyc_output/processinfo/index.json +0 -1
  211. package/lib/api/service-users.api.js +0 -150
  212. package/lib/api/types/service-users.types.js +0 -65
  213. package/lib/cli/setup-service-user.js +0 -187
  214. package/lib/commands/service-user.js +0 -429
@@ -1,6 +1,14 @@
1
- const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
1
+ const {
2
+ formatSuccessLine,
3
+ formatWarningLine,
4
+ formatProgress,
5
+ sectionTitle,
6
+ headerKeyValue,
7
+ metadata
8
+ } = require('../utils/cli-test-layout-chalk');
2
9
  /**
3
- * Upload external system to dataplane (single pipeline upload: validate → publish → controller register).
10
+ * Upload external system to dataplane (sync local `integration/<systemKey>/openapi/*.json` when present,
11
+ * then single pipeline upload: validate → publish → controller register).
4
12
  *
5
13
  * @fileoverview Upload command handler for aifabrix upload <systemKey>
6
14
  * @author AI Fabrix Team
@@ -10,6 +18,8 @@ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
10
18
  const path = require('path');
11
19
  const chalk = require('chalk');
12
20
  const logger = require('../utils/logger');
21
+
22
+ const SEP = chalk.gray('────────────────────────────────────────');
13
23
  const { resolveControllerUrl } = require('../utils/controller-url');
14
24
  const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
15
25
  const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
@@ -38,6 +48,7 @@ const {
38
48
  } = require('../utils/external-system-readiness-display');
39
49
  const { maybeSyncSystemCertificationFromDataplane } = require('../certification/sync-system-certification');
40
50
  const { cliOptsSkipCertSync } = require('../certification/cli-cert-sync-skip');
51
+ const { maybeSyncOpenApiFilesForMcp } = require('./repair-openapi-sync');
41
52
 
42
53
  /**
43
54
  * Validates system-key format (same as download).
@@ -86,7 +97,7 @@ async function resolveDataplaneAndAuth(systemKey, opts = {}) {
86
97
  }
87
98
 
88
99
  if (!silent) {
89
- logger.log(chalk.gray('Resolving dataplane URL...'));
100
+ logger.log(metadata('Resolving dataplane URL...'));
90
101
  }
91
102
  const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig, { silent });
92
103
  return { dataplaneUrl, authConfig, environment };
@@ -168,7 +179,9 @@ async function pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey,
168
179
  });
169
180
  if (pushResult.pushed > 0) {
170
181
  const keyList = pushResult.keys?.length ? ` (${pushResult.keys.join(', ')})` : '';
171
- logger.log(chalk.green(`Pushed ${pushResult.pushed} credential secret(s) to dataplane${keyList}.`));
182
+ logger.log(
183
+ formatSuccessLine(`Pushed ${pushResult.pushed} credential secret(s) to dataplane${keyList}.`)
184
+ );
172
185
  } else {
173
186
  logger.log(chalk.yellow('Secret push skipped'));
174
187
  }
@@ -223,7 +236,10 @@ const UPLOAD_PROBE_TEST_DATA = {};
223
236
  * @param {number|undefined} probeTimeoutMs
224
237
  */
225
238
  async function maybeRunUploadProbe(dataplaneUrl, systemKey, authConfig, probeTimeoutMs) {
226
- logger.log(chalk.blue('\nRunning runtime checks (--probe)...'));
239
+ logger.log('');
240
+ logger.log(sectionTitle('Runtime checks (--probe)'));
241
+ logger.log(SEP);
242
+ logger.log(formatProgress('Running runtime checks...'));
227
243
  const timeoutMs = probeTimeoutMs === undefined || probeTimeoutMs === null ? 120000 : probeTimeoutMs;
228
244
  try {
229
245
  const pr = await testSystemViaPipeline(
@@ -246,10 +262,13 @@ async function maybeRunUploadProbe(dataplaneUrl, systemKey, authConfig, probeTim
246
262
 
247
263
  /**
248
264
  * Local validation, manifest, payload, and configuration resolution.
265
+ * Does not write *-deploy.json; run `aifabrix json <systemKey>` to regenerate the deployment manifest from sources.
266
+ *
249
267
  * @param {string} systemKey
268
+ * @param {{ dryRun?: boolean }} [_opts] - Reserved for callers (e.g. dry-run); manifest is always built from current disk files
250
269
  * @returns {Promise<{ manifest: Object, payload: Object }>}
251
270
  */
252
- async function buildValidatedUploadManifestPayload(systemKey) {
271
+ async function buildValidatedUploadManifestPayload(systemKey, _opts = {}) {
253
272
  const validationResult = await validateExternalSystemComplete(systemKey, { type: 'external' });
254
273
  throwIfValidationFailed(validationResult);
255
274
  logger.log(formatSuccessLine('Local validation passed'));
@@ -260,24 +279,52 @@ async function buildValidatedUploadManifestPayload(systemKey) {
260
279
  }
261
280
 
262
281
  /**
263
- * Upload path after dry-run check.
282
+ * Upload local `integration/<systemKey>/openapi/*.json` specs when present so pipeline can resolve
283
+ * each datasource `openapi.documentKey` (same behavior as repair --api OpenAPI sync).
284
+ *
264
285
  * @param {string} systemKey
265
- * @param {Object} options
266
286
  * @param {Object} manifest
267
- * @param {Object} payload
287
+ * @returns {Promise<void>}
268
288
  */
269
- async function runUploadPublishAndSummary(systemKey, options, manifest, payload) {
270
- const { dataplaneUrl, authConfig, environment } = await resolveDataplaneAndAuth(systemKey);
271
- requireBearerForDataplanePipeline(authConfig);
272
- logger.log(chalk.gray('Target:'));
273
- logger.log(chalk.gray(`Environment: ${environment}`));
274
- logger.log(chalk.gray(`Dataplane: ${dataplaneUrl}`));
275
- logDataplanePipelineWarning();
276
- if (options.verbose) {
277
- await maybeRunVerboseServerValidation(dataplaneUrl, authConfig, payload);
289
+ async function logAndSyncLocalOpenApiForUpload(systemKey, manifest) {
290
+ const appPath = getIntegrationPath(systemKey);
291
+ const ei = manifest && manifest.externalIntegration;
292
+ const datasourceFiles = ei && Array.isArray(ei.dataSources) ? ei.dataSources : [];
293
+ const openapiSyncLines = await maybeSyncOpenApiFilesForMcp({
294
+ enabled: true,
295
+ dryRun: false,
296
+ appPath,
297
+ systemKey,
298
+ datasourceFiles
299
+ });
300
+ for (const line of openapiSyncLines) {
301
+ logger.log(line.startsWith('Skipped') ? formatWarningLine(line) : formatSuccessLine(line));
278
302
  }
279
- await pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey, payload);
280
- const rawRes = await runUploadValidatePublish(dataplaneUrl, authConfig, payload);
303
+ }
304
+
305
+ /**
306
+ * @param {Object} ctx
307
+ * @param {string} ctx.systemKey
308
+ * @param {Object} ctx.options
309
+ * @param {Object} ctx.manifest
310
+ * @param {Object} ctx.payload
311
+ * @param {string} ctx.environment
312
+ * @param {string} ctx.dataplaneUrl
313
+ * @param {Object} ctx.authConfig
314
+ * @param {Object} ctx.rawRes
315
+ * @returns {Promise<void>}
316
+ */
317
+ async function handlePublicationAndFollowups(ctx) {
318
+ const {
319
+ systemKey,
320
+ options,
321
+ manifest,
322
+ payload,
323
+ environment,
324
+ dataplaneUrl,
325
+ authConfig,
326
+ rawRes
327
+ } = ctx;
281
328
  const publication = unwrapPublicationResult(rawRes);
282
329
  if (!publication) {
283
330
  throw new Error(
@@ -296,7 +343,6 @@ async function runUploadPublishAndSummary(systemKey, options, manifest, payload)
296
343
  if (options.probe) {
297
344
  await maybeRunUploadProbe(dataplaneUrl, systemKey, authConfig, options.probeTimeout);
298
345
  }
299
-
300
346
  const dsKeys = (payload.dataSources || []).map((ds) => ds && ds.key).filter(Boolean);
301
347
  await maybeSyncSystemCertificationFromDataplane({
302
348
  label: 'upload',
@@ -308,6 +354,41 @@ async function runUploadPublishAndSummary(systemKey, options, manifest, payload)
308
354
  });
309
355
  }
310
356
 
357
+ /**
358
+ * Upload path after dry-run check.
359
+ * @param {string} systemKey
360
+ * @param {Object} options
361
+ * @param {Object} manifest
362
+ * @param {Object} payload
363
+ */
364
+ async function runUploadPublishAndSummary(systemKey, options, manifest, payload) {
365
+ const { dataplaneUrl, authConfig, environment } = await resolveDataplaneAndAuth(systemKey);
366
+ requireBearerForDataplanePipeline(authConfig);
367
+ logger.log('');
368
+ logger.log(sectionTitle('Target'));
369
+ logger.log(SEP);
370
+ logger.log(headerKeyValue('Environment:', environment));
371
+ logger.log(headerKeyValue('Dataplane:', dataplaneUrl));
372
+ logDataplanePipelineWarning();
373
+ if (options.verbose) {
374
+ await maybeRunVerboseServerValidation(dataplaneUrl, authConfig, payload);
375
+ }
376
+ await pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey, payload);
377
+ await logAndSyncLocalOpenApiForUpload(systemKey, manifest);
378
+
379
+ const rawRes = await runUploadValidatePublish(dataplaneUrl, authConfig, payload);
380
+ await handlePublicationAndFollowups({
381
+ systemKey,
382
+ options,
383
+ manifest,
384
+ payload,
385
+ environment,
386
+ dataplaneUrl,
387
+ authConfig,
388
+ rawRes
389
+ });
390
+ }
391
+
311
392
  /**
312
393
  * Uploads external system: publishes to dataplane and registers with controller (draft).
313
394
  * @param {string} systemKey - External system key (integration/<systemKey>/)
@@ -322,8 +403,13 @@ async function runUploadPublishAndSummary(systemKey, options, manifest, payload)
322
403
  */
323
404
  async function uploadExternalSystem(systemKey, options = {}) {
324
405
  validateSystemKeyFormat(systemKey);
325
- logger.log(chalk.blue(`\nUploading external system: ${chalk.bold(systemKey)}`));
326
- const { manifest, payload } = await buildValidatedUploadManifestPayload(systemKey);
406
+ logger.log('');
407
+ logger.log(sectionTitle('Upload external system'));
408
+ logger.log(SEP);
409
+ logger.log(headerKeyValue('System:', systemKey));
410
+ const { manifest, payload } = await buildValidatedUploadManifestPayload(systemKey, {
411
+ dryRun: !!options.dryRun
412
+ });
327
413
  if (options.dryRun) {
328
414
  logger.log(chalk.yellow('Dry run: would upload payload (no API calls).'));
329
415
  logger.log(
@@ -15,6 +15,7 @@ const { getIntegrationPath } = require('../utils/paths');
15
15
  const { parseOpenApi, testMcpConnection, credentialSelection } = require('../api/wizard.api');
16
16
  const { listCredentials } = require('../api/credentials.api');
17
17
  const { listExternalSystems, getExternalSystem } = require('../api/external-systems.api');
18
+ const { formatSuccessLine } = require('../utils/cli-layout-chalk');
18
19
 
19
20
  /**
20
21
  * Parse OpenAPI file or URL
@@ -35,7 +36,7 @@ async function parseOpenApiSource(dataplaneUrl, authConfig, sourceType, sourceDa
35
36
  if (!parseResponse.success) {
36
37
  throw new Error(`OpenAPI parsing failed: ${parseResponse.error || parseResponse.formattedError}`);
37
38
  }
38
- logger.log(chalk.green(`\u2713 OpenAPI ${isUrl ? 'URL' : 'file'} parsed successfully`));
39
+ logger.log(formatSuccessLine(`OpenAPI ${isUrl ? 'URL' : 'file'} parsed successfully`));
39
40
  return parseResponse.data?.spec;
40
41
  } catch (error) {
41
42
  spinner.stop();
@@ -61,7 +62,7 @@ async function testMcpServerConnection(dataplaneUrl, authConfig, sourceData) {
61
62
  if (!testResponse.success || !testResponse.data?.connected) {
62
63
  throw new Error(`MCP connection failed: ${testResponse.data?.error || 'Unable to connect'}`);
63
64
  }
64
- logger.log(chalk.green('\u2713 MCP server connection successful'));
65
+ logger.log(formatSuccessLine('MCP server connection successful'));
65
66
  } catch (error) {
66
67
  spinner.stop();
67
68
  throw error;
@@ -145,7 +146,7 @@ async function runCredentialSelectionLoop(dataplaneUrl, authConfig, selectionDat
145
146
  }
146
147
  if (response.success) {
147
148
  const actionText = selectionData.action === 'create' ? 'created' : 'selected';
148
- logger.log(chalk.green(`\u2713 Credential ${actionText}`));
149
+ logger.log(formatSuccessLine(`Credential ${actionText}`));
149
150
  return response.data?.credentialIdOrKey || null;
150
151
  }
151
152
  const errorMsg = response.error || response.formattedError || response.message || 'Unknown error';
@@ -308,18 +309,19 @@ function throwConfigGenerationError(generateResponse, options = {}) {
308
309
  }
309
310
 
310
311
  /**
311
- * Write debug log to integration/<systemKey>/debug.log
312
+ * Write debug log to integration/<systemKey>/logs/debug.log
312
313
  * @async
313
314
  * @param {string} appName - Application name
314
315
  * @param {string} content - Debug log content
315
316
  */
316
317
  async function writeDebugLog(appName, content) {
317
318
  try {
318
- const dir = getIntegrationPath(appName);
319
+ const appDir = getIntegrationPath(appName);
320
+ const dir = path.join(appDir, 'logs');
319
321
  await fs.mkdir(dir, { recursive: true });
320
322
  const debugPath = path.join(dir, 'debug.log');
321
323
  await fs.writeFile(debugPath, content, 'utf8');
322
- logger.log(chalk.gray(` Debug log saved to integration/${appName}/debug.log`));
324
+ logger.log(chalk.gray(` Debug log saved to integration/${appName}/logs/debug.log`));
323
325
  } catch (e) {
324
326
  logger.warn(`Could not save debug.log: ${e.message}`);
325
327
  }
@@ -336,13 +338,14 @@ async function writeDebugLog(appName, content) {
336
338
  async function writeDebugManifest(appName, systemConfig, datasourceConfig) {
337
339
  const saved = [];
338
340
  try {
339
- const dir = getIntegrationPath(appName);
341
+ const appDir = getIntegrationPath(appName);
342
+ const dir = path.join(appDir, 'logs');
340
343
  await fs.mkdir(dir, { recursive: true });
341
344
  if (systemConfig && typeof systemConfig === 'object') {
342
345
  const systemPath = path.join(dir, 'debug-system.yaml');
343
346
  await fs.writeFile(systemPath, yaml.dump(systemConfig, { lineWidth: -1 }), 'utf8');
344
347
  saved.push('debug-system.yaml');
345
- logger.log(chalk.gray(` Debug manifest saved to integration/${appName}/debug-system.yaml`));
348
+ logger.log(chalk.gray(` Debug manifest saved to integration/${appName}/logs/debug-system.yaml`));
346
349
  }
347
350
  if (datasourceConfig !== undefined && datasourceConfig !== null) {
348
351
  const configs = Array.isArray(datasourceConfig) ? datasourceConfig : [datasourceConfig];
@@ -351,7 +354,7 @@ async function writeDebugManifest(appName, systemConfig, datasourceConfig) {
351
354
  const toWrite = configs.length === 1 ? configs[0] : configs;
352
355
  await fs.writeFile(datasourcePath, yaml.dump(toWrite, { lineWidth: -1 }), 'utf8');
353
356
  saved.push('debug-datasource.yaml');
354
- logger.log(chalk.gray(` Debug manifest saved to integration/${appName}/debug-datasource.yaml`));
357
+ logger.log(chalk.gray(` Debug manifest saved to integration/${appName}/logs/debug-datasource.yaml`));
355
358
  }
356
359
  }
357
360
  } catch (e) {
@@ -381,7 +384,7 @@ async function saveDebugManifestOnErrorAndThrow(generateResponse, opts) {
381
384
  const savedManifest = await writeDebugManifest(appName, systemConfig, datasourceConfig);
382
385
  if (debugLog || savedManifest.length > 0) {
383
386
  const files = [debugLog && 'debug.log', ...savedManifest].filter(Boolean).join(', ');
384
- debugManifestHint = `Debug manifest saved to integration/${appName}/ (${files}). Review the log and fix the manifest manually, then run: aifabrix wizard ${appName}`;
387
+ debugManifestHint = `Debug manifest saved to integration/${appName}/logs/ (${files}). Review the log and fix the manifest manually, then run: aifabrix wizard ${appName}`;
385
388
  }
386
389
  }
387
390
  throwConfigGenerationError(generateResponse, { debugManifestHint });
@@ -398,7 +401,7 @@ async function throwValidationFailureWithDebug(validateResponse, systemConfig, c
398
401
  );
399
402
  if (!debugLog && savedManifest.length === 0) throw new Error(`Configuration validation failed: ${errorMsg}`);
400
403
  const files = [debugLog && 'debug.log', ...savedManifest].filter(Boolean).join(', ');
401
- throw new Error(`Configuration validation failed: ${errorMsg}\n\nDebug manifest saved to integration/${options.appName}/ (${files}). Review the log and fix the manifest manually, then run: aifabrix wizard ${options.appName}`);
404
+ throw new Error(`Configuration validation failed: ${errorMsg}\n\nDebug manifest saved to integration/${options.appName}/logs/ (${files}). Review the log and fix the manifest manually, then run: aifabrix wizard ${options.appName}`);
402
405
  }
403
406
 
404
407
  /**
@@ -13,6 +13,7 @@ const logger = require('../utils/logger');
13
13
  const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
14
14
  const { resolveControllerUrl } = require('../utils/controller-url');
15
15
  const { normalizeWizardConfigs } = require('./wizard-config-normalizer');
16
+ const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-layout-chalk');
16
17
  const {
17
18
  createWizardSession,
18
19
  updateWizardSession,
@@ -143,7 +144,7 @@ async function handleModeSelection(dataplaneUrl, authConfig, configMode = null,
143
144
  throw new Error(fullMsg);
144
145
  }
145
146
  const sessionId = extractSessionId(sessionResponse.data);
146
- logger.log(chalk.green(`\u2713 Session created: ${sessionId}`));
147
+ logger.log(formatSuccessLine(`Session created: ${sessionId}`));
147
148
  return { mode, sessionId };
148
149
  }
149
150
 
@@ -250,7 +251,7 @@ async function handleTypeDetection(dataplaneUrl, authConfig, openapiSpec) {
250
251
  if (detectResponse.success && detectResponse.data) {
251
252
  const detectedType = detectResponse.data;
252
253
  const recommendedType = detectedType.recommendedType || detectedType.apiType || 'unknown';
253
- logger.log(chalk.green(`\u2713 API type detected: ${recommendedType}`));
254
+ logger.log(formatSuccessLine(`API type detected: ${recommendedType}`));
254
255
  return detectedType;
255
256
  }
256
257
  } catch (error) {
@@ -275,7 +276,8 @@ async function handleTypeDetection(dataplaneUrl, authConfig, openapiSpec) {
275
276
  * @param {string} [options.systemIdOrKey] - System ID or key (optional)
276
277
  * @param {string} [options.sourceType] - Source type (use 'known-platform' to call platforms config endpoint)
277
278
  * @param {string} [options.platformKey] - Platform key for known-platform (e.g. 'hubspot')
278
- * @param {string} [options.appName] - App name for writing debug.log when debug=true
279
+ * @param {string} [options.appName] - Integration app key; for create-system, used as systemIdOrKey
280
+ * when not set so kv paths match the folder (avoids spec-title keys like "companies")
279
281
  * @returns {Promise<Object>} Generated configuration
280
282
  */
281
283
  async function callGenerateApi(dataplaneUrl, authConfig, options, prefs) {
@@ -291,13 +293,19 @@ async function callGenerateApi(dataplaneUrl, authConfig, options, prefs) {
291
293
  });
292
294
  return await getPlatformConfig(dataplaneUrl, authConfig, options.platformKey, platformPayload);
293
295
  }
296
+ // Headless/interactive create-system uses appName as integration folder key; OpenAPI title alone
297
+ // can yield a wrong system key (e.g. title "Companies" → "companies"). Prefer appName when set.
298
+ let systemIdOrKeyForPayload = options.systemIdOrKey;
299
+ if (options.mode === 'create-system' && options.appName) {
300
+ systemIdOrKeyForPayload = systemIdOrKeyForPayload || options.appName;
301
+ }
294
302
  const configPayload = buildConfigPayload({
295
303
  openapiSpec: options.openapiSpec,
296
304
  detectedType: options.detectedType,
297
305
  mode: options.mode,
298
306
  prefs,
299
307
  credentialIdOrKey: options.credentialIdOrKey,
300
- systemIdOrKey: options.systemIdOrKey,
308
+ systemIdOrKey: systemIdOrKeyForPayload,
301
309
  entityName: options.entityName,
302
310
  systemDisplayName: options.systemDisplayName
303
311
  });
@@ -322,7 +330,7 @@ async function handleConfigurationGeneration(dataplaneUrl, authConfig, options)
322
330
  await writeDebugLog(options.appName, debugLog);
323
331
  }
324
332
  }
325
- logger.log(chalk.green('\u2713 Configuration generated successfully'));
333
+ logger.log(formatSuccessLine('Configuration generated successfully'));
326
334
  return { systemConfig: normalized.systemConfig, datasourceConfigs: normalized.datasourceConfigs, systemKey: result.systemKey };
327
335
  } catch (error) {
328
336
  spinner.stop();
@@ -361,7 +369,7 @@ async function validateWizardConfiguration(dataplaneUrl, authConfig, systemConfi
361
369
  if (validateResponse.data?.warnings?.length > 0) warnings.push(...validateResponse.data.warnings);
362
370
  }
363
371
  spinner.stop();
364
- logger.log(chalk.green('\u2713 Configuration validated successfully'));
372
+ logger.log(formatSuccessLine('Configuration validated successfully'));
365
373
  if (warnings.length > 0) logger.log(chalk.yellow('\n\u26A0 Warnings:\n' + warnings.map(w => ` - ${w.message || w}`).join('\n')));
366
374
  } catch (error) {
367
375
  spinner.stop();
@@ -410,6 +418,39 @@ async function tryUpdateReadmeFromDeploymentDocs(appPath, appName, dataplaneUrl,
410
418
  }
411
419
  }
412
420
 
421
+ /**
422
+ * Writes rbac.yaml / rbac.json from datasource resourceType + capabilities (same as `af repair --rbac`).
423
+ * @param {Object} generatedFiles - Result of generateWizardFiles (appPath, systemFilePath, datasourceFilePaths)
424
+ * @param {string} format - Project format: yaml | json
425
+ */
426
+ function logWizardFileSaveFooter(appName, generatedFiles) {
427
+ logger.log(formatSuccessParagraph('Wizard completed successfully!'));
428
+ logger.log(chalk.green(`\nFiles created in: ${generatedFiles.appPath}`));
429
+ logger.log(chalk.blue('\nNext steps:'));
430
+ logger.log(chalk.gray(` 1. Review the generated files in integration/${appName}/`));
431
+ logger.log(chalk.gray(' 2. Update env.template with your authentication details'));
432
+ logger.log(chalk.gray(` 3. Deploy using: node deploy.js or aifabrix deploy ${appName}`));
433
+ }
434
+
435
+ function mergeRbacAfterWizardFilesWritten(generatedFiles, format) {
436
+ const { mergeRbacFromDatasources, extractRbacFromSystem } = require('./repair-rbac');
437
+ const { loadConfigFile } = require('../utils/config-format');
438
+ const systemParsedForRbac = loadConfigFile(generatedFiles.systemFilePath);
439
+ const datasourceFileNames = (generatedFiles.datasourceFilePaths || []).map((p) => path.basename(p));
440
+ const rbacChanges = [];
441
+ const rbacUpdated = mergeRbacFromDatasources(
442
+ generatedFiles.appPath,
443
+ systemParsedForRbac,
444
+ datasourceFileNames,
445
+ extractRbacFromSystem,
446
+ { format: format === 'json' ? 'json' : 'yaml', dryRun: false, changes: rbacChanges }
447
+ );
448
+ if (rbacUpdated && rbacChanges.length) {
449
+ rbacChanges.forEach((c) => logger.log(chalk.gray(` RBAC: ${c}`)));
450
+ logger.log(chalk.green(' RBAC file updated from datasource capabilities (enableRBAC).'));
451
+ }
452
+ }
453
+
413
454
  /**
414
455
  * Handle file saving step
415
456
  * @async
@@ -418,17 +459,24 @@ async function tryUpdateReadmeFromDeploymentDocs(appPath, appName, dataplaneUrl,
418
459
  * @param {Object} systemConfig - System configuration
419
460
  * @param {Object[]} datasourceConfigs - Datasource configurations
420
461
  * @param {string} systemKey - System key
421
- * @param {string} dataplaneUrl - Dataplane URL
422
- * @param {Object} authConfig - Authentication configuration
462
+ * @param {{ dataplaneUrl: string, authConfig: Object, enableRBAC?: boolean }} ctx - Dataplane auth + optional RBAC generation
423
463
  * @returns {Promise<Object>} Generated files information
424
464
  */
425
- async function handleFileSaving(appName, systemConfig, datasourceConfigs, systemKey, dataplaneUrl, authConfig) {
465
+ async function handleFileSaving(appName, systemConfig, datasourceConfigs, systemKey, ctx) {
466
+ const { dataplaneUrl, authConfig, enableRBAC = false } = ctx || {};
426
467
  logger.log(chalk.blue('\n\uD83D\uDCCB Step 7: Save Files'));
427
468
  const spinner = ora('Saving files...').start();
428
469
  try {
429
470
  const config = require('../core/config');
430
471
  const format = (await config.getFormat()) || 'yaml';
431
472
  const generatedFiles = await generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, { aiGeneratedReadme: null, format });
473
+ if (enableRBAC && generatedFiles.appPath && generatedFiles.systemFilePath) {
474
+ try {
475
+ mergeRbacAfterWizardFilesWritten(generatedFiles, format);
476
+ } catch (e) {
477
+ logger.log(chalk.yellow(` Could not generate RBAC file: ${e.message}`));
478
+ }
479
+ }
432
480
  if (systemKey && dataplaneUrl && authConfig && generatedFiles.appPath) {
433
481
  try {
434
482
  await tryUpdateReadmeFromDeploymentDocs(generatedFiles.appPath, appName, dataplaneUrl, authConfig, systemKey);
@@ -437,12 +485,7 @@ async function handleFileSaving(appName, systemConfig, datasourceConfigs, system
437
485
  }
438
486
  }
439
487
  spinner.stop();
440
- logger.log(chalk.green('\n\u2713 Wizard completed successfully!'));
441
- logger.log(chalk.green(`\nFiles created in: ${generatedFiles.appPath}`));
442
- logger.log(chalk.blue('\nNext steps:'));
443
- logger.log(chalk.gray(` 1. Review the generated files in integration/${appName}/`));
444
- logger.log(chalk.gray(' 2. Update env.template with your authentication details'));
445
- logger.log(chalk.gray(` 3. Deploy using: node deploy.js or aifabrix deploy ${appName}`));
488
+ logWizardFileSaveFooter(appName, generatedFiles);
446
489
  return generatedFiles;
447
490
  } catch (error) {
448
491
  spinner.stop();
@@ -6,7 +6,7 @@
6
6
 
7
7
  const chalk = require('chalk');
8
8
  const logger = require('../utils/logger');
9
- const { infoLine, formatSuccessLine } = require('../utils/cli-test-layout-chalk');
9
+ const { formatProgress, formatSuccessLine } = require('../utils/cli-test-layout-chalk');
10
10
  const { getDataplaneUrl } = require('../datasource/deploy');
11
11
  const { listEnvironmentApplications } = require('../api/environments.api');
12
12
 
@@ -102,7 +102,7 @@ async function tryFallbackDataplaneUrl(controllerUrl, environment, authConfig, s
102
102
  async function discoverDataplaneUrl(controllerUrl, environment, authConfig, opts = {}) {
103
103
  const silent = opts.silent === true;
104
104
  if (!silent) {
105
- logger.log(infoLine('🌐 Getting dataplane URL from controller...'));
105
+ logger.log(formatProgress('Getting dataplane URL from controller...'));
106
106
  }
107
107
  try {
108
108
  const dataplaneAppKey = await findDataplaneServiceAppKey(controllerUrl, environment, authConfig);
@@ -9,31 +9,89 @@ const logger = require('../utils/logger');
9
9
  const { discoverEntities } = require('../api/wizard.api');
10
10
  const { validateEntityNameForOpenApi } = require('../validation/wizard-datasource-validation');
11
11
  const { promptForEntitySelection } = require('../generator/wizard-prompts');
12
+ const { formatSuccessLine } = require('../utils/cli-layout-chalk');
13
+
14
+ /**
15
+ * If wizard.yaml entity name matches discover-entities list, use it; else warn.
16
+ * @param {string} trimmed - Trimmed entity name from prefill
17
+ * @param {Array<{name: string}>} entities - Discovered entities
18
+ * @returns {string|null} Resolved name or null to prompt
19
+ */
20
+ function resolvePrefillEntityName(trimmed, entities) {
21
+ const prefillCheck = validateEntityNameForOpenApi(trimmed, entities);
22
+ if (prefillCheck.valid) {
23
+ logger.log(chalk.gray(
24
+ `Using entity from wizard.yaml (${trimmed}). Skipping entity prompts.`
25
+ ));
26
+ logger.log(formatSuccessLine(`Selected entity: ${trimmed}`));
27
+ return trimmed;
28
+ }
29
+ logger.log(chalk.yellow(
30
+ `Warning: wizard.yaml source.entityName '${trimmed}' is not in the discover-entities list; choose manually.`
31
+ ));
32
+ return null;
33
+ }
34
+
35
+ /**
36
+ * Prompt for entity and validate against list.
37
+ * @param {Array<{name: string}>} entities - Discovered entities
38
+ * @returns {Promise<string>} Valid entity name
39
+ */
40
+ async function promptForValidatedEntity(entities) {
41
+ const entityName = await promptForEntitySelection(entities);
42
+ const validation = validateEntityNameForOpenApi(entityName, entities);
43
+ if (!validation.valid) {
44
+ throw new Error(`Invalid entity '${entityName}'. Available: ${entities.map(e => e.name).join(', ')}`);
45
+ }
46
+ logger.log(formatSuccessLine(`Selected entity: ${entityName}`));
47
+ return entityName;
48
+ }
49
+
50
+ /**
51
+ * Discover entities and select one (single-entity shortcut, prefill, or prompt).
52
+ * @param {string} dataplaneUrl - Dataplane URL
53
+ * @param {Object} authConfig - Authentication configuration
54
+ * @param {Object} openapiSpec - OpenAPI specification
55
+ * @param {string} [prefillEntityName] - From wizard.yaml `source.entityName` when valid
56
+ * @returns {Promise<string|null>} Selected entity name or null (skip)
57
+ */
58
+ async function discoverAndSelectEntity(dataplaneUrl, authConfig, openapiSpec, prefillEntityName) {
59
+ const response = await discoverEntities(dataplaneUrl, authConfig, openapiSpec);
60
+ const entities = response?.data?.entities;
61
+ if (!Array.isArray(entities) || entities.length === 0) return null;
62
+
63
+ logger.log(chalk.blue('\n\uD83D\uDCCB Step 4.5: Select Entity'));
64
+
65
+ if (entities.length === 1) {
66
+ const only = entities[0].name;
67
+ logger.log(formatSuccessLine(`Only one entity discovered; using: ${only}`));
68
+ return only;
69
+ }
70
+
71
+ const trimmed =
72
+ typeof prefillEntityName === 'string' ? prefillEntityName.trim() : '';
73
+ if (trimmed) {
74
+ const resolved = resolvePrefillEntityName(trimmed, entities);
75
+ if (resolved) return resolved;
76
+ }
77
+
78
+ return promptForValidatedEntity(entities);
79
+ }
12
80
 
13
81
  /**
14
82
  * Handle entity selection step (OpenAPI multi-entity).
15
- * Calls discover-entities; if entities found, prompts user to select one.
83
+ * Calls discover-entities; prompts unless prefill or a single entity applies.
16
84
  * @async
17
85
  * @param {string} dataplaneUrl - Dataplane URL
18
86
  * @param {Object} authConfig - Authentication configuration
19
87
  * @param {Object} openapiSpec - OpenAPI specification
88
+ * @param {string} [prefillEntityName] - From wizard.yaml `source.entityName` when valid
20
89
  * @returns {Promise<string|null>} Selected entity name or null (skip)
21
90
  */
22
- async function handleEntitySelection(dataplaneUrl, authConfig, openapiSpec) {
91
+ async function handleEntitySelection(dataplaneUrl, authConfig, openapiSpec, prefillEntityName) {
23
92
  if (!openapiSpec || typeof openapiSpec !== 'object') return null;
24
93
  try {
25
- const response = await discoverEntities(dataplaneUrl, authConfig, openapiSpec);
26
- const entities = response?.data?.entities;
27
- if (!Array.isArray(entities) || entities.length === 0) return null;
28
-
29
- logger.log(chalk.blue('\n\uD83D\uDCCB Step 4.5: Select Entity'));
30
- const entityName = await promptForEntitySelection(entities);
31
- const validation = validateEntityNameForOpenApi(entityName, entities);
32
- if (!validation.valid) {
33
- throw new Error(`Invalid entity '${entityName}'. Available: ${entities.map(e => e.name).join(', ')}`);
34
- }
35
- logger.log(chalk.green(`\u2713 Selected entity: ${entityName}`));
36
- return entityName;
94
+ return await discoverAndSelectEntity(dataplaneUrl, authConfig, openapiSpec, prefillEntityName);
37
95
  } catch (error) {
38
96
  logger.log(chalk.yellow(`Warning: Entity discovery failed, using default: ${error.message}`));
39
97
  return null;
@@ -6,6 +6,7 @@
6
6
 
7
7
  const chalk = require('chalk');
8
8
  const logger = require('../utils/logger');
9
+ const { formatSuccessLine } = require('../utils/cli-layout-chalk');
9
10
  const {
10
11
  validateWizardConfig: validateWizardConfigFile,
11
12
  displayValidationResults
@@ -111,8 +112,11 @@ async function executeWizardFromConfig(wizardConfig, dataplaneUrl, authConfig, o
111
112
  systemConfig,
112
113
  datasourceConfigs,
113
114
  systemKey || appName,
114
- dataplaneUrl,
115
- authConfig
115
+ {
116
+ dataplaneUrl,
117
+ authConfig,
118
+ enableRBAC: Boolean(preferences?.enableRBAC)
119
+ }
116
120
  );
117
121
  }
118
122
 
@@ -138,7 +142,7 @@ async function handleWizardHeadless(options) {
138
142
  displayValidationResults(validationResult);
139
143
  throw new Error('Wizard configuration validation failed');
140
144
  }
141
- logger.log(chalk.green('\u2713 Configuration file validated'));
145
+ logger.log(formatSuccessLine('Configuration file validated'));
142
146
 
143
147
  const wizardConfig = validationResult.config;
144
148
  const appName = wizardConfig.appName;
@@ -47,6 +47,12 @@ function buildSourceForSave(source) {
47
47
  out.token = source.token ? '(set)' : undefined;
48
48
  }
49
49
  if (source.type === 'known-platform' && source.platform) out.platform = source.platform;
50
+ if (
51
+ (source.type === 'openapi-file' || source.type === 'openapi-url') &&
52
+ source.entityName
53
+ ) {
54
+ out.entityName = source.entityName;
55
+ }
50
56
  return out;
51
57
  }
52
58
 
@@ -75,7 +81,13 @@ function buildWizardStateForSave(opts) {
75
81
  function formatSourceLine(source) {
76
82
  if (!source) return null;
77
83
  const s = source;
78
- return s.type + (s.filePath ? ` (${s.filePath})` : s.url ? ` (${s.url})` : s.platform ? ` (${s.platform})` : '');
84
+ let line =
85
+ s.type +
86
+ (s.filePath ? ` (${s.filePath})` : s.url ? ` (${s.url})` : s.platform ? ` (${s.platform})` : '');
87
+ if (s.entityName && (s.type === 'openapi-file' || s.type === 'openapi-url')) {
88
+ line += ` [entity: ${s.entityName}]`;
89
+ }
90
+ return line;
79
91
  }
80
92
 
81
93
  /**