@aifabrix/builder 2.40.2 → 2.42.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 (198) hide show
  1. package/.cursor/rules/docs-rules.mdc +30 -0
  2. package/README.md +7 -5
  3. package/integration/hubspot/README.md +8 -4
  4. package/integration/hubspot/application.json +54 -0
  5. package/integration/hubspot/create-hubspot.js +9 -136
  6. package/integration/hubspot/env.template +3 -4
  7. package/integration/hubspot/hubspot-datasource-company.json +343 -5
  8. package/integration/hubspot/hubspot-datasource-contact.json +413 -5
  9. package/integration/hubspot/hubspot-datasource-deal.json +341 -4
  10. package/integration/hubspot/hubspot-datasource-users.json +116 -0
  11. package/integration/hubspot/hubspot-deploy.json +1250 -108
  12. package/integration/hubspot/hubspot-system.json +15 -32
  13. package/integration/hubspot/test-dataplane-down-tests.js +17 -16
  14. package/integration/hubspot/test-dataplane-down.js +2 -2
  15. package/integration/hubspot/test.js +1 -1
  16. package/jest.config.manual.js +2 -1
  17. package/lib/api/credential.api.js +40 -0
  18. package/lib/api/dev.api.js +423 -0
  19. package/lib/api/external-test.api.js +111 -0
  20. package/lib/api/index.js +42 -19
  21. package/lib/api/pipeline.api.js +66 -120
  22. package/lib/api/types/credential.types.js +23 -0
  23. package/lib/api/types/dev.types.js +140 -0
  24. package/lib/api/types/pipeline.types.js +37 -0
  25. package/lib/api/wizard-platform.api.js +61 -0
  26. package/lib/api/wizard.api.js +34 -1
  27. package/lib/app/config.js +44 -11
  28. package/lib/app/down.js +2 -1
  29. package/lib/app/index.js +12 -1
  30. package/lib/app/prompts.js +44 -29
  31. package/lib/app/push.js +36 -12
  32. package/lib/app/readme.js +9 -6
  33. package/lib/app/run-env-compose.js +264 -0
  34. package/lib/app/run-helpers.js +121 -118
  35. package/lib/app/run.js +148 -28
  36. package/lib/app/show-display.js +1 -1
  37. package/lib/app/show.js +5 -2
  38. package/lib/build/index.js +11 -3
  39. package/lib/cli/setup-app.js +172 -15
  40. package/lib/cli/setup-credential-deployment.js +31 -6
  41. package/lib/cli/setup-dev.js +206 -16
  42. package/lib/cli/setup-environment.js +16 -6
  43. package/lib/cli/setup-external-system.js +89 -24
  44. package/lib/cli/setup-infra.js +82 -15
  45. package/lib/cli/setup-secrets.js +52 -5
  46. package/lib/cli/setup-utility.js +129 -24
  47. package/lib/commands/app-install.js +172 -0
  48. package/lib/commands/app-shell.js +75 -0
  49. package/lib/commands/app-test.js +282 -0
  50. package/lib/commands/app.js +1 -1
  51. package/lib/commands/credential-env.js +162 -0
  52. package/lib/commands/credential-list.js +17 -22
  53. package/lib/commands/credential-push.js +96 -0
  54. package/lib/commands/datasource.js +77 -6
  55. package/lib/commands/dev-cli-handlers.js +141 -0
  56. package/lib/commands/dev-down.js +114 -0
  57. package/lib/commands/dev-init.js +347 -0
  58. package/lib/commands/repair-auth-config.js +99 -0
  59. package/lib/commands/repair-datasource-keys.js +208 -0
  60. package/lib/commands/repair-datasource.js +235 -0
  61. package/lib/commands/repair-env-template.js +348 -0
  62. package/lib/commands/repair-internal.js +85 -0
  63. package/lib/commands/repair-rbac.js +158 -0
  64. package/lib/commands/repair.js +507 -0
  65. package/lib/commands/secrets-list.js +118 -0
  66. package/lib/commands/secrets-remove.js +97 -0
  67. package/lib/commands/secrets-set.js +30 -17
  68. package/lib/commands/secrets-validate.js +50 -0
  69. package/lib/commands/test-e2e-external.js +165 -0
  70. package/lib/commands/up-dataplane.js +2 -2
  71. package/lib/commands/up-miso.js +0 -25
  72. package/lib/commands/upload.js +96 -40
  73. package/lib/commands/wizard-core-helpers.js +226 -4
  74. package/lib/commands/wizard-core.js +67 -29
  75. package/lib/commands/wizard-dataplane.js +1 -1
  76. package/lib/commands/wizard-entity-selection.js +43 -0
  77. package/lib/commands/wizard-headless.js +44 -5
  78. package/lib/commands/wizard-helpers.js +7 -3
  79. package/lib/commands/wizard.js +86 -64
  80. package/lib/core/admin-secrets.js +96 -0
  81. package/lib/core/config.js +7 -1
  82. package/lib/core/secrets-ensure.js +378 -0
  83. package/lib/core/secrets-env-write.js +157 -0
  84. package/lib/core/secrets.js +176 -89
  85. package/lib/datasource/deploy.js +12 -3
  86. package/lib/datasource/field-reference-validator.js +91 -0
  87. package/lib/datasource/test-e2e.js +219 -0
  88. package/lib/datasource/test-integration.js +154 -0
  89. package/lib/datasource/validate.js +21 -3
  90. package/lib/deployment/deployer.js +7 -5
  91. package/lib/deployment/environment-config.js +137 -0
  92. package/lib/deployment/environment.js +21 -98
  93. package/lib/deployment/push.js +32 -2
  94. package/lib/external-system/download.js +188 -203
  95. package/lib/external-system/generator.js +204 -56
  96. package/lib/external-system/test-auth.js +7 -3
  97. package/lib/external-system/test-execution.js +2 -1
  98. package/lib/external-system/test-system-level.js +73 -0
  99. package/lib/external-system/test.js +56 -19
  100. package/lib/generator/external-controller-manifest.js +29 -2
  101. package/lib/generator/external-schema-utils.js +1 -1
  102. package/lib/generator/external.js +10 -3
  103. package/lib/generator/index.js +177 -25
  104. package/lib/generator/split-readme.js +1 -0
  105. package/lib/generator/split-variables.js +7 -1
  106. package/lib/generator/split.js +194 -54
  107. package/lib/generator/wizard-prompts-secondary.js +294 -0
  108. package/lib/generator/wizard-prompts.js +105 -106
  109. package/lib/generator/wizard-readme.js +88 -0
  110. package/lib/generator/wizard.js +155 -158
  111. package/lib/infrastructure/compose.js +11 -1
  112. package/lib/infrastructure/helpers.js +103 -20
  113. package/lib/infrastructure/index.js +98 -12
  114. package/lib/infrastructure/services.js +88 -22
  115. package/lib/schema/application-schema.json +32 -8
  116. package/lib/schema/external-datasource.schema.json +49 -26
  117. package/lib/schema/external-system.schema.json +509 -411
  118. package/lib/schema/wizard-config.schema.json +16 -0
  119. package/lib/utils/api.js +41 -13
  120. package/lib/utils/app-register-auth.js +25 -3
  121. package/lib/utils/auth-headers.js +8 -7
  122. package/lib/utils/cli-utils.js +20 -0
  123. package/lib/utils/compose-generator.js +77 -76
  124. package/lib/utils/compose-handlebars-helpers.js +54 -0
  125. package/lib/utils/compose-vector-helper.js +18 -0
  126. package/lib/utils/config-format-preference.js +51 -0
  127. package/lib/utils/config-format.js +36 -0
  128. package/lib/utils/config-paths.js +127 -2
  129. package/lib/utils/configuration-env-resolver.js +179 -0
  130. package/lib/utils/credential-display.js +83 -0
  131. package/lib/utils/credential-secrets-env.js +357 -0
  132. package/lib/utils/dataplane-pipeline-warning.js +28 -0
  133. package/lib/utils/deployment-validation-helpers.js +4 -4
  134. package/lib/utils/dev-ca-install.js +139 -0
  135. package/lib/utils/dev-cert-helper.js +122 -0
  136. package/lib/utils/device-code-helpers.js +224 -0
  137. package/lib/utils/device-code.js +37 -336
  138. package/lib/utils/docker-build.js +40 -8
  139. package/lib/utils/env-copy.js +103 -13
  140. package/lib/utils/env-map.js +35 -5
  141. package/lib/utils/env-template.js +6 -5
  142. package/lib/utils/error-formatters/http-status-errors.js +20 -2
  143. package/lib/utils/error-formatters/permission-errors.js +0 -1
  144. package/lib/utils/error-formatters/validation-errors.js +0 -1
  145. package/lib/utils/external-readme.js +56 -29
  146. package/lib/utils/external-system-display.js +59 -1
  147. package/lib/utils/external-system-test-helpers.js +21 -8
  148. package/lib/utils/external-system-validators.js +3 -0
  149. package/lib/utils/file-upload.js +20 -50
  150. package/lib/utils/help-builder.js +16 -2
  151. package/lib/utils/infra-status.js +80 -45
  152. package/lib/utils/local-secrets.js +7 -52
  153. package/lib/utils/mutagen-install.js +195 -0
  154. package/lib/utils/mutagen.js +146 -0
  155. package/lib/utils/paths.js +128 -37
  156. package/lib/utils/port-resolver.js +28 -16
  157. package/lib/utils/remote-dev-auth.js +38 -0
  158. package/lib/utils/remote-docker-env.js +43 -0
  159. package/lib/utils/remote-secrets-loader.js +60 -0
  160. package/lib/utils/secrets-canonical.js +93 -0
  161. package/lib/utils/secrets-generator.js +114 -6
  162. package/lib/utils/secrets-helpers.js +108 -114
  163. package/lib/utils/secrets-path.js +2 -2
  164. package/lib/utils/secrets-utils.js +52 -1
  165. package/lib/utils/secrets-validation.js +84 -0
  166. package/lib/utils/ssh-key-helper.js +116 -0
  167. package/lib/utils/test-log-writer.js +56 -0
  168. package/lib/utils/token-manager-messages.js +90 -0
  169. package/lib/utils/token-manager.js +29 -36
  170. package/lib/utils/variable-transformer.js +3 -3
  171. package/lib/validation/env-template-auth.js +157 -0
  172. package/lib/validation/env-template-kv.js +41 -0
  173. package/lib/validation/external-manifest-validator.js +25 -0
  174. package/lib/validation/external-system-auth-rules.js +86 -0
  175. package/lib/validation/validate-batch.js +149 -0
  176. package/lib/validation/validate-datasource-keys-api.js +33 -0
  177. package/lib/validation/validate-display.js +94 -16
  178. package/lib/validation/validate.js +25 -12
  179. package/lib/validation/validator.js +72 -9
  180. package/lib/validation/wizard-datasource-validation.js +50 -0
  181. package/package.json +8 -3
  182. package/scripts/install-local.js +34 -15
  183. package/templates/README.md +0 -1
  184. package/templates/applications/README.md.hbs +4 -4
  185. package/templates/applications/dataplane/application.yaml +6 -5
  186. package/templates/applications/dataplane/env.template +15 -10
  187. package/templates/applications/dataplane/rbac.yaml +2 -2
  188. package/templates/applications/keycloak/env.template +2 -0
  189. package/templates/applications/miso-controller/application.yaml +1 -0
  190. package/templates/applications/miso-controller/env.template +12 -10
  191. package/templates/external-system/README.md.hbs +65 -25
  192. package/templates/external-system/deploy.js.hbs +4 -2
  193. package/templates/external-system/external-datasource.yaml.hbs +217 -0
  194. package/templates/external-system/external-system.json.hbs +1 -18
  195. package/templates/infra/compose.yaml.hbs +6 -0
  196. package/templates/python/docker-compose.hbs +49 -23
  197. package/templates/typescript/docker-compose.hbs +48 -22
  198. package/integration/hubspot/application.yaml +0 -37
@@ -3,19 +3,21 @@
3
3
  * @author AI Fabrix Team
4
4
  * @version 2.0.0
5
5
  */
6
+ /* eslint-disable max-lines */
6
7
 
7
8
  const chalk = require('chalk');
8
9
  const ora = require('ora');
9
10
  const path = require('path');
10
11
  const fs = require('fs').promises;
11
12
  const logger = require('../utils/logger');
12
- const { getDeploymentAuth } = require('../utils/token-manager');
13
+ const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
13
14
  const { resolveControllerUrl } = require('../utils/controller-url');
14
15
  const { normalizeWizardConfigs } = require('./wizard-config-normalizer');
15
16
  const {
16
17
  createWizardSession,
17
18
  updateWizardSession,
18
19
  detectType,
20
+ getPlatformConfig,
19
21
  generateConfig,
20
22
  validateWizardConfig,
21
23
  getDeploymentDocs,
@@ -29,9 +31,17 @@ const {
29
31
  runCredentialSelectionLoop,
30
32
  buildConfigPreferences,
31
33
  buildConfigPayload,
34
+ buildPlatformConfigPayload,
32
35
  extractConfigurationFromResponse,
33
- throwConfigGenerationError
36
+ writeDebugLog,
37
+ saveDebugManifestOnErrorAndThrow,
38
+ throwValidationFailureWithDebug,
39
+ resolveCredentialConfig,
40
+ fetchSystemsListForAddDatasource,
41
+ resolveExternalSystemForAddDatasource
34
42
  } = require('./wizard-core-helpers');
43
+ const { validateDatasourceKeysBeforePlatformConfig } = require('../validation/validate-datasource-keys-api');
44
+ const { handleEntitySelection } = require('./wizard-entity-selection');
35
45
 
36
46
  /**
37
47
  * Validate app name and check if directory exists
@@ -263,35 +273,56 @@ async function handleTypeDetection(dataplaneUrl, authConfig, openapiSpec) {
263
273
  * @param {Object} [options.configPrefs] - Preferences from wizard.yaml
264
274
  * @param {string} [options.credentialIdOrKey] - Credential ID or key (optional)
265
275
  * @param {string} [options.systemIdOrKey] - System ID or key (optional)
276
+ * @param {string} [options.sourceType] - Source type (use 'known-platform' to call platforms config endpoint)
277
+ * @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
266
279
  * @returns {Promise<Object>} Generated configuration
267
280
  */
281
+ async function callGenerateApi(dataplaneUrl, authConfig, options, prefs) {
282
+ if (options.sourceType === 'known-platform' && options.platformKey) {
283
+ await validateDatasourceKeysBeforePlatformConfig(
284
+ dataplaneUrl, authConfig, options.platformKey, options.datasourceKeys
285
+ );
286
+ const platformPayload = buildPlatformConfigPayload({
287
+ credentialIdOrKey: options.credentialIdOrKey,
288
+ datasourceKeys: options.datasourceKeys,
289
+ configurationValues: options.configurationValues,
290
+ debug: prefs.debug
291
+ });
292
+ return await getPlatformConfig(dataplaneUrl, authConfig, options.platformKey, platformPayload);
293
+ }
294
+ const configPayload = buildConfigPayload({
295
+ openapiSpec: options.openapiSpec,
296
+ detectedType: options.detectedType,
297
+ mode: options.mode,
298
+ prefs,
299
+ credentialIdOrKey: options.credentialIdOrKey,
300
+ systemIdOrKey: options.systemIdOrKey,
301
+ entityName: options.entityName
302
+ });
303
+ return await generateConfig(dataplaneUrl, authConfig, configPayload);
304
+ }
268
305
 
269
306
  async function handleConfigurationGeneration(dataplaneUrl, authConfig, options) {
270
307
  logger.log(chalk.blue('\n\uD83D\uDCCB Step 5: Generate Configuration'));
271
308
  const prefs = buildConfigPreferences(options.configPrefs);
272
309
  const spinner = ora('Generating configuration via AI (10-30 seconds)...').start();
273
310
  try {
274
- const configPayload = buildConfigPayload({
275
- openapiSpec: options.openapiSpec,
276
- detectedType: options.detectedType,
277
- mode: options.mode,
278
- prefs,
279
- credentialIdOrKey: options.credentialIdOrKey,
280
- systemIdOrKey: options.systemIdOrKey
281
- });
282
- const generateResponse = await generateConfig(dataplaneUrl, authConfig, configPayload);
311
+ const generateResponse = await callGenerateApi(dataplaneUrl, authConfig, options, prefs);
283
312
  spinner.stop();
284
313
  if (!generateResponse.success) {
285
- throwConfigGenerationError(generateResponse);
314
+ await saveDebugManifestOnErrorAndThrow(generateResponse, { enableDebug: prefs.debug, appName: options.appName });
286
315
  }
287
316
  const result = extractConfigurationFromResponse(generateResponse);
288
317
  const normalized = normalizeWizardConfigs(result.systemConfig, result.datasourceConfigs);
318
+ if (prefs.debug && options.appName) {
319
+ const debugLog = generateResponse.data?.debugLog;
320
+ if (debugLog && typeof debugLog === 'string') {
321
+ await writeDebugLog(options.appName, debugLog);
322
+ }
323
+ }
289
324
  logger.log(chalk.green('\u2713 Configuration generated successfully'));
290
- return {
291
- systemConfig: normalized.systemConfig,
292
- datasourceConfigs: normalized.datasourceConfigs,
293
- systemKey: result.systemKey
294
- };
325
+ return { systemConfig: normalized.systemConfig, datasourceConfigs: normalized.datasourceConfigs, systemKey: result.systemKey };
295
326
  } catch (error) {
296
327
  spinner.stop();
297
328
  throw error;
@@ -306,9 +337,11 @@ async function handleConfigurationGeneration(dataplaneUrl, authConfig, options)
306
337
  * @param {Object} authConfig - Authentication configuration
307
338
  * @param {Object} systemConfig - System configuration
308
339
  * @param {Object[]} datasourceConfigs - Datasource configurations
340
+ * @param {Object} [options] - Optional options
341
+ * @param {boolean} [options.debug] - When true, save debug manifest on validation failure
342
+ * @param {string} [options.appName] - App name for writing debug files
309
343
  */
310
- // eslint-disable-next-line max-statements
311
- async function validateWizardConfiguration(dataplaneUrl, authConfig, systemConfig, datasourceConfigs) {
344
+ async function validateWizardConfiguration(dataplaneUrl, authConfig, systemConfig, datasourceConfigs, options = {}) {
312
345
  logger.log(chalk.blue('\n\uD83D\uDCCB Step 6: Validate Configuration'));
313
346
  const spinner = ora('Validating configuration...').start();
314
347
  try {
@@ -322,16 +355,13 @@ async function validateWizardConfiguration(dataplaneUrl, authConfig, systemConfi
322
355
  const errorDetail = validateResponse.errorData?.detail || validateResponse.errorData?.message;
323
356
  const errorMsg = errors.length > 0 ? errors.map(e => e.message || e).join(', ') : errorDetail || validateResponse.error || 'Validation failed';
324
357
  spinner.stop();
325
- throw new Error(`Configuration validation failed: ${errorMsg}`);
358
+ await throwValidationFailureWithDebug(validateResponse, systemConfig, configs, errorMsg, options);
326
359
  }
327
360
  if (validateResponse.data?.warnings?.length > 0) warnings.push(...validateResponse.data.warnings);
328
361
  }
329
362
  spinner.stop();
330
363
  logger.log(chalk.green('\u2713 Configuration validated successfully'));
331
- if (warnings.length > 0) {
332
- logger.log(chalk.yellow('\n\u26A0 Warnings:'));
333
- warnings.forEach(w => logger.log(chalk.yellow(` - ${w.message || w}`)));
334
- }
364
+ if (warnings.length > 0) logger.log(chalk.yellow('\n\u26A0 Warnings:\n' + warnings.map(w => ` - ${w.message || w}`).join('\n')));
335
365
  } catch (error) {
336
366
  spinner.stop();
337
367
  throw error;
@@ -370,7 +400,9 @@ async function tryUpdateReadmeFromDeploymentDocs(appPath, appName, dataplaneUrl,
370
400
  ? await postDeploymentDocs(dataplaneUrl, authConfig, systemKey, body)
371
401
  : await getDeploymentDocs(dataplaneUrl, authConfig, systemKey);
372
402
  const content = docsResponse?.data?.content ?? docsResponse?.content;
373
- if (content && typeof content === 'string') {
403
+ // Only overwrite README when the API returns substantial content; otherwise keep the template README
404
+ const MIN_README_LENGTH = 400;
405
+ if (content && typeof content === 'string' && content.trim().length >= MIN_README_LENGTH) {
374
406
  const readmePath = path.join(appPath, 'README.md');
375
407
  await fs.writeFile(readmePath, content, 'utf8');
376
408
  logger.log(chalk.gray(' Updated README.md from deployment-docs API (application config + deploy JSON).'));
@@ -393,7 +425,9 @@ async function handleFileSaving(appName, systemConfig, datasourceConfigs, system
393
425
  logger.log(chalk.blue('\n\uD83D\uDCCB Step 7: Save Files'));
394
426
  const spinner = ora('Saving files...').start();
395
427
  try {
396
- const generatedFiles = await generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, { aiGeneratedReadme: null });
428
+ const config = require('../core/config');
429
+ const format = (await config.getFormat()) || 'yaml';
430
+ const generatedFiles = await generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, { aiGeneratedReadme: null, format });
397
431
  if (systemKey && dataplaneUrl && authConfig && generatedFiles.appPath) {
398
432
  try {
399
433
  await tryUpdateReadmeFromDeploymentDocs(generatedFiles.appPath, appName, dataplaneUrl, authConfig, systemKey);
@@ -427,14 +461,14 @@ async function setupDataplaneAndAuth(options, appName) {
427
461
  const { resolveEnvironment } = require('../core/config');
428
462
  const environment = await resolveEnvironment();
429
463
  const controllerUrl = await resolveControllerUrl();
430
- // Prefer device token; use client token or client credentials when available.
431
- // Some dataplanes accept only client credentials; getDeploymentAuth tries all.
464
+ // getDeploymentAuth returns token-based auth only (Bearer); never client-credentials for app endpoints.
432
465
  let authConfig;
433
466
  try {
434
467
  authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
435
468
  } catch (error) {
436
469
  throw new Error(`Authentication failed: ${error.message}`);
437
470
  }
471
+ requireBearerForDataplanePipeline(authConfig);
438
472
  const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
439
473
  let dataplaneUrl;
440
474
  try {
@@ -459,8 +493,12 @@ module.exports = {
459
493
  handleOpenApiParsing,
460
494
  handleCredentialSelection,
461
495
  handleTypeDetection,
496
+ handleEntitySelection,
462
497
  handleConfigurationGeneration,
463
498
  validateWizardConfiguration,
464
499
  handleFileSaving,
465
- setupDataplaneAndAuth
500
+ setupDataplaneAndAuth,
501
+ resolveCredentialConfig,
502
+ fetchSystemsListForAddDatasource,
503
+ resolveExternalSystemForAddDatasource
466
504
  };
@@ -59,7 +59,7 @@ function isNotFoundError(error) {
59
59
  function createDataplaneNotFoundError() {
60
60
  return new Error(
61
61
  'Could not discover dataplane URL from controller. No dataplane service application found in this environment. ' +
62
- 'Please provide the dataplane URL using --dataplane <url> flag.'
62
+ 'Ensure the dataplane service is registered in the controller.'
63
63
  );
64
64
  }
65
65
 
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @fileoverview Entity selection step for OpenAPI multi-entity wizard flow
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const chalk = require('chalk');
8
+ const logger = require('../utils/logger');
9
+ const { discoverEntities } = require('../api/wizard.api');
10
+ const { validateEntityNameForOpenApi } = require('../validation/wizard-datasource-validation');
11
+ const { promptForEntitySelection } = require('../generator/wizard-prompts');
12
+
13
+ /**
14
+ * Handle entity selection step (OpenAPI multi-entity).
15
+ * Calls discover-entities; if entities found, prompts user to select one.
16
+ * @async
17
+ * @param {string} dataplaneUrl - Dataplane URL
18
+ * @param {Object} authConfig - Authentication configuration
19
+ * @param {Object} openapiSpec - OpenAPI specification
20
+ * @returns {Promise<string|null>} Selected entity name or null (skip)
21
+ */
22
+ async function handleEntitySelection(dataplaneUrl, authConfig, openapiSpec) {
23
+ if (!openapiSpec || typeof openapiSpec !== 'object') return null;
24
+ 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;
37
+ } catch (error) {
38
+ logger.log(chalk.yellow(`Warning: Entity discovery failed, using default: ${error.message}`));
39
+ return null;
40
+ }
41
+ }
42
+
43
+ module.exports = { handleEntitySelection };
@@ -22,6 +22,29 @@ const {
22
22
  handleFileSaving,
23
23
  setupDataplaneAndAuth
24
24
  } = require('./wizard-core');
25
+ const { discoverEntities } = require('../api/wizard.api');
26
+ const { validateEntityNameForOpenApi } = require('../validation/wizard-datasource-validation');
27
+
28
+ /**
29
+ * Validate entityName for headless config (throws if invalid)
30
+ * @async
31
+ * @param {string} entityName - Entity from source
32
+ * @param {Object} openapiSpec - OpenAPI spec
33
+ * @param {string} sourceType - Source type
34
+ * @param {string} dataplaneUrl - Dataplane URL
35
+ * @param {Object} authConfig - Auth config
36
+ * @throws {Error} If entityName invalid
37
+ */
38
+ async function validateHeadlessEntityName(entityName, openapiSpec, sourceType, dataplaneUrl, authConfig) {
39
+ if (!entityName || !openapiSpec || sourceType === 'known-platform') return;
40
+ const discoverResponse = await discoverEntities(dataplaneUrl, authConfig, openapiSpec);
41
+ const entities = discoverResponse?.data?.entities || [];
42
+ const validation = validateEntityNameForOpenApi(entityName, entities);
43
+ if (!validation.valid) {
44
+ const available = entities.map(e => e.name).join(', ');
45
+ throw new Error(`Invalid entityName '${entityName}'. Available from discover-entities: ${available || 'none'}`);
46
+ }
47
+ }
25
48
 
26
49
  /**
27
50
  * Execute wizard flow from config file (headless mode)
@@ -30,11 +53,15 @@ const {
30
53
  * @param {Object} wizardConfig - Validated wizard configuration
31
54
  * @param {string} dataplaneUrl - Dataplane URL
32
55
  * @param {Object} authConfig - Authentication configuration
56
+ * @param {Object} [opts] - Optional overrides
57
+ * @param {boolean} [opts.debug] - Enable debug (CLI --debug); overrides wizard.yaml preferences.debug
33
58
  * @returns {Promise<void>} Resolves when wizard flow completes
34
59
  * @throws {Error} If wizard flow fails
35
60
  */
36
- async function executeWizardFromConfig(wizardConfig, dataplaneUrl, authConfig) {
61
+ async function executeWizardFromConfig(wizardConfig, dataplaneUrl, authConfig, opts = {}) {
37
62
  const { appName, mode, systemIdOrKey, source, credential, preferences } = wizardConfig;
63
+ const configPrefs = { ...preferences };
64
+ if (opts.debug === true) configPrefs.debug = true;
38
65
 
39
66
  // Step 1: Create Session
40
67
  const { sessionId } = await handleModeSelection(dataplaneUrl, authConfig, mode, systemIdOrKey);
@@ -51,18 +78,27 @@ async function executeWizardFromConfig(wizardConfig, dataplaneUrl, authConfig) {
51
78
  // Step 4: Detect Type
52
79
  const detectedType = await handleTypeDetection(dataplaneUrl, authConfig, openapiSpec);
53
80
 
81
+ await validateHeadlessEntityName(source?.entityName, openapiSpec, sourceType, dataplaneUrl, authConfig);
82
+
54
83
  // Step 5: Generate Configuration
55
84
  const { systemConfig, datasourceConfigs, systemKey } = await handleConfigurationGeneration(dataplaneUrl, authConfig, {
56
85
  mode,
57
86
  openapiSpec,
58
87
  detectedType,
59
- configPrefs: preferences,
88
+ configPrefs,
60
89
  credentialIdOrKey,
61
- systemIdOrKey
90
+ systemIdOrKey,
91
+ sourceType,
92
+ platformKey: sourceType === 'known-platform' ? sourceData : undefined,
93
+ datasourceKeys: source?.datasourceKeys,
94
+ configurationValues: source?.configurationValues,
95
+ entityName: source?.entityName,
96
+ appName
62
97
  });
63
98
 
64
99
  // Step 6: Validate Configuration
65
- await validateWizardConfiguration(dataplaneUrl, authConfig, systemConfig, datasourceConfigs);
100
+ const debugOpts = opts.debug ? { debug: true, appName } : {};
101
+ await validateWizardConfiguration(dataplaneUrl, authConfig, systemConfig, datasourceConfigs, debugOpts);
66
102
 
67
103
  // Step 7: Save Files
68
104
  await handleFileSaving(
@@ -87,6 +123,9 @@ async function executeWizardFromConfig(wizardConfig, dataplaneUrl, authConfig) {
87
123
  async function handleWizardHeadless(options) {
88
124
  logger.log(chalk.blue('\n\uD83E\uDDD9 AI Fabrix External System Wizard (Headless Mode)\n'));
89
125
  logger.log(chalk.gray(`Reading configuration from: ${options.config}`));
126
+ if (options.debug) {
127
+ logger.log(chalk.gray('[DEBUG] Wizard debug mode enabled'));
128
+ }
90
129
 
91
130
  // Validate wizard config file
92
131
  const validationResult = await validateWizardConfigFile(options.config);
@@ -109,7 +148,7 @@ async function handleWizardHeadless(options) {
109
148
  const { dataplaneUrl, authConfig } = await setupDataplaneAndAuth(options, appName);
110
149
 
111
150
  // Execute wizard flow from config
112
- await executeWizardFromConfig(wizardConfig, dataplaneUrl, authConfig);
151
+ await executeWizardFromConfig(wizardConfig, dataplaneUrl, authConfig, { debug: options.debug });
113
152
  }
114
153
 
115
154
  module.exports = { handleWizardHeadless, executeWizardFromConfig };
@@ -10,15 +10,17 @@ const chalk = require('chalk');
10
10
  const logger = require('../utils/logger');
11
11
 
12
12
  /**
13
- * Build preferences object for wizard.yaml (schema shape: intent, fieldOnboardingLevel, enableOpenAPIGeneration, enableMCP, enableABAC, enableRBAC)
13
+ * Build preferences object for wizard.yaml (schema shape: intent, fieldOnboardingLevel, enableOpenAPIGeneration, enableMCP, enableABAC, enableRBAC, debug)
14
14
  * @param {string} intent - User intent
15
15
  * @param {Object} preferences - From promptForUserPreferences (fieldOnboardingLevel, mcp, abac, rbac)
16
+ * @param {Object} [opts] - Optional overrides
17
+ * @param {boolean} [opts.debug] - Enable debug (CLI --debug); saved to wizard.yaml for headless runs
16
18
  * @returns {Object} Preferences for wizard-config schema
17
19
  */
18
- function buildPreferencesForSave(intent, preferences) {
20
+ function buildPreferencesForSave(intent, preferences, opts = {}) {
19
21
  const level = preferences?.fieldOnboardingLevel;
20
22
  const validLevel = level === 'standard' || level === 'minimal' ? level : 'full';
21
- return {
23
+ const result = {
22
24
  intent: intent || 'general integration',
23
25
  fieldOnboardingLevel: validLevel,
24
26
  enableOpenAPIGeneration: true,
@@ -26,6 +28,8 @@ function buildPreferencesForSave(intent, preferences) {
26
28
  enableABAC: Boolean(preferences?.abac),
27
29
  enableRBAC: Boolean(preferences?.rbac)
28
30
  };
31
+ if (opts.debug === true || preferences?.debug === true) result.debug = true;
32
+ return result;
29
33
  }
30
34
 
31
35
  /**
@@ -3,12 +3,13 @@
3
3
  * @author AI Fabrix Team
4
4
  * @version 2.0.0
5
5
  */
6
+ /* eslint-disable max-lines */
6
7
 
7
8
  const chalk = require('chalk');
8
9
  const logger = require('../utils/logger');
9
10
  const {
10
11
  promptForMode,
11
- promptForSystemIdOrKey,
12
+ promptForExistingSystem,
12
13
  promptForSourceType,
13
14
  promptForOpenApiFile,
14
15
  promptForOpenApiUrl,
@@ -24,46 +25,30 @@ const {
24
25
  const {
25
26
  validateAndCheckAppDirectory,
26
27
  formatDataplaneRejectedTokenMessage,
28
+ extractSessionId,
27
29
  handleOpenApiParsing,
28
30
  handleCredentialSelection,
29
31
  handleTypeDetection,
32
+ handleEntitySelection,
30
33
  handleConfigurationGeneration,
31
34
  validateWizardConfiguration,
32
35
  handleFileSaving,
33
- setupDataplaneAndAuth
36
+ setupDataplaneAndAuth,
37
+ resolveCredentialConfig,
38
+ fetchSystemsListForAddDatasource,
39
+ resolveExternalSystemForAddDatasource
34
40
  } = require('./wizard-core');
35
41
  const { handleWizardHeadless } = require('./wizard-headless');
36
- const { createWizardSession, updateWizardSession, getWizardPlatforms } = require('../api/wizard.api');
37
- const { getExternalSystem } = require('../api/external-systems.api');
42
+ const { createWizardSession, updateWizardSession, getWizardPlatforms, getPreview } = require('../api/wizard.api');
38
43
  const { writeWizardConfig, wizardConfigExists, validateWizardConfig } = require('../validation/wizard-config-validator');
39
44
  const { appendWizardError } = require('../utils/cli-utils');
40
45
  const {
41
46
  buildPreferencesForSave,
42
47
  buildWizardStateForSave,
43
48
  showWizardConfigSummary,
44
- ensureIntegrationDir,
45
- isExternalSystemForAddDatasource
49
+ ensureIntegrationDir
46
50
  } = require('./wizard-helpers');
47
51
 
48
- /**
49
- * Extract session ID from response data
50
- * @function extractSessionId
51
- * @param {Object} responseData - Response data from API
52
- * @returns {string} Session ID
53
- * @throws {Error} If session ID not found or invalid
54
- */
55
- function extractSessionId(responseData) {
56
- let sessionId = responseData?.data?.sessionId || responseData?.sessionId ||
57
- responseData?.data?.session_id || responseData?.session_id;
58
- if (sessionId && typeof sessionId === 'object') {
59
- sessionId = sessionId.id || sessionId.sessionId || sessionId.session_id;
60
- }
61
- if (!sessionId || typeof sessionId !== 'string') {
62
- throw new Error(`Session ID not found: ${JSON.stringify(responseData, null, 2)}`);
63
- }
64
- return sessionId;
65
- }
66
-
67
52
  /**
68
53
  * Create wizard session with given mode and optional systemIdOrKey (no prompts)
69
54
  * @async
@@ -151,7 +136,8 @@ async function handleInteractiveConfigGeneration(options) {
151
136
  fieldOnboardingLevel: preferences.fieldOnboardingLevel || 'full',
152
137
  enableMCP: preferences.mcp,
153
138
  enableABAC: preferences.abac,
154
- enableRBAC: preferences.rbac
139
+ enableRBAC: preferences.rbac,
140
+ debug: options.debug === true
155
141
  };
156
142
 
157
143
  const result = await handleConfigurationGeneration(options.dataplaneUrl, options.authConfig, {
@@ -160,28 +146,51 @@ async function handleInteractiveConfigGeneration(options) {
160
146
  detectedType: options.detectedType,
161
147
  configPrefs,
162
148
  credentialIdOrKey: options.credentialIdOrKey,
163
- systemIdOrKey: options.systemIdOrKey
149
+ systemIdOrKey: options.systemIdOrKey,
150
+ sourceType: options.sourceType,
151
+ platformKey: options.sourceType === 'known-platform' ? options.sourceData : undefined,
152
+ entityName: options.entityName,
153
+ appName: options.appName
164
154
  });
165
155
 
166
156
  return {
167
157
  ...result,
168
- preferences: buildPreferencesForSave(userIntent, preferences)
158
+ preferences: buildPreferencesForSave(userIntent, preferences, { debug: options.debug })
169
159
  };
170
160
  }
171
161
 
172
162
  /**
173
163
  * Handle configuration review and validation step (interactive mode only)
164
+ * Fetches preview summary from dataplane; falls back to YAML dump if preview unavailable.
174
165
  * @async
175
166
  * @function handleConfigurationReview
176
167
  * @param {string} dataplaneUrl - Dataplane URL
177
168
  * @param {Object} authConfig - Authentication configuration
169
+ * @param {string} sessionId - Wizard session ID
178
170
  * @param {Object} systemConfig - System configuration
179
171
  * @param {Object[]} datasourceConfigs - Datasource configurations
180
- * @returns {Promise<Object>} Final configurations
172
+ * @param {Object} [opts] - Optional options
173
+ * @param {string} [opts.appKey] - App key for debug manifest path
174
+ * @param {boolean} [opts.debug] - When true, save debug manifest on validation failure
175
+ * @returns {Promise<Object|null>} Final configurations or null if cancelled
181
176
  */
182
- async function handleConfigurationReview(dataplaneUrl, authConfig, systemConfig, datasourceConfigs) {
177
+ async function handleConfigurationReview(dataplaneUrl, authConfig, sessionId, systemConfig, datasourceConfigs, opts = {}) {
183
178
  logger.log(chalk.blue('\n\uD83D\uDCCB Step 6-7: Review & Validate'));
184
- const reviewResult = await promptForConfigReview(systemConfig, datasourceConfigs);
179
+
180
+ let preview = null;
181
+ try {
182
+ const previewResponse = await getPreview(dataplaneUrl, sessionId, authConfig);
183
+ if (previewResponse?.success && previewResponse?.data) {
184
+ preview = previewResponse.data;
185
+ }
186
+ } catch {
187
+ // Fall back to YAML display
188
+ }
189
+ if (!preview) {
190
+ logger.warn('Preview unavailable, showing full configuration.');
191
+ }
192
+
193
+ const reviewResult = await promptForConfigReview({ preview, systemConfig, datasourceConfigs });
185
194
 
186
195
  if (reviewResult.action === 'cancel') {
187
196
  logger.log(chalk.yellow('Wizard cancelled.'));
@@ -191,7 +200,10 @@ async function handleConfigurationReview(dataplaneUrl, authConfig, systemConfig,
191
200
  const finalSystemConfig = reviewResult.systemConfig || systemConfig;
192
201
  const finalDatasourceConfigs = reviewResult.datasourceConfigs || datasourceConfigs;
193
202
 
194
- await validateWizardConfiguration(dataplaneUrl, authConfig, finalSystemConfig, finalDatasourceConfigs);
203
+ await validateWizardConfiguration(dataplaneUrl, authConfig, finalSystemConfig, finalDatasourceConfigs, {
204
+ debug: opts.debug,
205
+ appName: opts.appKey
206
+ });
195
207
 
196
208
  return { systemConfig: finalSystemConfig, datasourceConfigs: finalDatasourceConfigs };
197
209
  }
@@ -208,7 +220,7 @@ async function handleConfigurationReview(dataplaneUrl, authConfig, systemConfig,
208
220
  * @returns {Promise<Object>} Collected state (source, credential, preferences) for wizard.yaml save
209
221
  */
210
222
  async function doWizardSteps(appKey, dataplaneUrl, authConfig, sessionId, flowOpts, state) {
211
- const { mode, systemIdOrKey, platforms } = flowOpts;
223
+ const { mode, systemIdOrKey, platforms, debug } = flowOpts;
212
224
  const { sourceType, sourceData } = await handleInteractiveSourceSelection(
213
225
  dataplaneUrl, sessionId, authConfig, platforms
214
226
  );
@@ -220,13 +232,13 @@ async function doWizardSteps(appKey, dataplaneUrl, authConfig, sessionId, flowOp
220
232
 
221
233
  const openapiSpec = await handleOpenApiParsing(dataplaneUrl, authConfig, sourceType, sourceData);
222
234
  const credentialAction = await promptForCredentialAction();
223
- const configCredential = credentialAction.action === 'skip'
224
- ? { action: 'skip' }
225
- : { action: credentialAction.action, credentialIdOrKey: credentialAction.credentialIdOrKey };
235
+ const configCredential = await resolveCredentialConfig(dataplaneUrl, authConfig, credentialAction);
226
236
  state.credential = configCredential;
227
237
  const credentialIdOrKey = await handleCredentialSelection(dataplaneUrl, authConfig, configCredential);
228
238
 
229
239
  const detectedType = await handleTypeDetection(dataplaneUrl, authConfig, openapiSpec);
240
+ const entityName = openapiSpec && sourceType !== 'known-platform'
241
+ ? await handleEntitySelection(dataplaneUrl, authConfig, openapiSpec) : null;
230
242
  const genResult = await handleInteractiveConfigGeneration({
231
243
  dataplaneUrl,
232
244
  authConfig,
@@ -234,12 +246,24 @@ async function doWizardSteps(appKey, dataplaneUrl, authConfig, sessionId, flowOp
234
246
  openapiSpec,
235
247
  detectedType,
236
248
  credentialIdOrKey,
237
- systemIdOrKey
249
+ systemIdOrKey,
250
+ sourceType,
251
+ sourceData,
252
+ entityName: entityName || undefined,
253
+ appName: appKey,
254
+ debug
238
255
  });
239
256
  const { systemConfig, datasourceConfigs, systemKey, preferences: savedPrefs } = genResult;
240
257
  state.preferences = savedPrefs || {};
241
258
 
242
- const finalConfigs = await handleConfigurationReview(dataplaneUrl, authConfig, systemConfig, datasourceConfigs);
259
+ const finalConfigs = await handleConfigurationReview(
260
+ dataplaneUrl,
261
+ authConfig,
262
+ sessionId,
263
+ systemConfig,
264
+ datasourceConfigs,
265
+ { appKey, debug }
266
+ );
243
267
  if (!finalConfigs) return null;
244
268
 
245
269
  await handleFileSaving(
@@ -290,18 +314,22 @@ async function runWizardStepsAfterSession(appKey, dataplaneUrl, authConfig, sess
290
314
  * @returns {Promise<void>} Resolves when wizard flow completes
291
315
  */
292
316
  async function executeWizardFlow(appKey, dataplaneUrl, authConfig, flowOpts = {}) {
293
- const { mode, systemIdOrKey, configPath } = flowOpts;
317
+ const { mode, systemIdOrKey, configPath, debug } = flowOpts;
294
318
 
319
+ if (debug) {
320
+ logger.log(chalk.gray(`[DEBUG] Wizard debug mode enabled for app: ${appKey}`));
321
+ }
295
322
  logger.log(chalk.blue('\n\uD83D\uDCCB Step 1: Create Session'));
296
323
  const sessionId = await createSessionFromParams(dataplaneUrl, authConfig, mode, systemIdOrKey, appKey);
297
324
  logger.log(chalk.green('\u2713 Session created'));
298
325
 
299
- const platforms = await getWizardPlatforms(dataplaneUrl, authConfig);
326
+ const platforms = mode === 'add-datasource' ? [] : await getWizardPlatforms(dataplaneUrl, authConfig);
300
327
  const state = await runWizardStepsAfterSession(appKey, dataplaneUrl, authConfig, sessionId, {
301
328
  mode,
302
329
  systemIdOrKey,
303
330
  platforms,
304
- configPath
331
+ configPath,
332
+ debug: flowOpts.debug
305
333
  });
306
334
  if (!state) return;
307
335
 
@@ -362,26 +390,15 @@ async function resolveCreateNewPath(options, loadedConfig) {
362
390
  * @returns {Promise<Object>} { appKey, configPath, dataplaneUrl, authConfig, systemIdOrKey }
363
391
  */
364
392
  async function resolveAddDatasourcePath(options, loadedConfig) {
365
- let systemIdOrKey = loadedConfig?.systemIdOrKey || (await promptForSystemIdOrKey(loadedConfig?.systemIdOrKey));
366
- const { dataplaneUrl, authConfig } = await setupDataplaneAndAuth(options, systemIdOrKey || 'wizard');
367
- let systemResponse;
368
- for (;;) {
369
- try {
370
- systemResponse = await getExternalSystem(dataplaneUrl, systemIdOrKey, authConfig);
371
- const sys = systemResponse?.data || systemResponse;
372
- if (sys && (systemResponse?.data || systemResponse?.success)) {
373
- if (!isExternalSystemForAddDatasource(sys)) {
374
- logger.log(chalk.red('Cannot add datasource to a webapp. Please enter an external system ID or key.'));
375
- systemIdOrKey = await promptForSystemIdOrKey(systemIdOrKey);
376
- continue;
377
- }
378
- break;
379
- }
380
- } catch (err) {
381
- logger.log(chalk.red(`System not found or error: ${err.message}`));
382
- }
383
- systemIdOrKey = await promptForSystemIdOrKey(systemIdOrKey);
384
- }
393
+ const { dataplaneUrl, authConfig } = await setupDataplaneAndAuth(
394
+ options,
395
+ loadedConfig?.systemIdOrKey || loadedConfig?.appKey || 'wizard'
396
+ );
397
+ const systemsList = await fetchSystemsListForAddDatasource(dataplaneUrl, authConfig);
398
+ const initialSystemIdOrKey = loadedConfig?.systemIdOrKey || (await promptForExistingSystem(systemsList, loadedConfig?.systemIdOrKey));
399
+ const { systemResponse, systemIdOrKey } = await resolveExternalSystemForAddDatasource(
400
+ dataplaneUrl, authConfig, systemsList, initialSystemIdOrKey
401
+ );
385
402
  const sys = systemResponse?.data || systemResponse;
386
403
  const appKey = sys?.key || sys?.systemKey || systemIdOrKey;
387
404
  const configPath = await ensureIntegrationDir(appKey);
@@ -449,7 +466,8 @@ async function handleWizardWithSavedConfig(options, loadedConfig, displayPath) {
449
466
  }
450
467
 
451
468
  async function handleWizardInteractive(options) {
452
- const mode = await promptForMode();
469
+ const allowAddDatasource = !options.app;
470
+ const mode = allowAddDatasource ? await promptForMode(undefined, true) : 'create-system';
453
471
  const resolved = mode === 'create-system'
454
472
  ? await resolveCreateNewPath(options, null)
455
473
  : await resolveAddDatasourcePath(options, null);
@@ -457,7 +475,12 @@ async function handleWizardInteractive(options) {
457
475
  const { appKey, configPath, dataplaneUrl, authConfig } = resolved;
458
476
  const systemIdOrKey = mode === 'add-datasource' ? resolved.systemIdOrKey : undefined;
459
477
  try {
460
- await executeWizardFlow(appKey, dataplaneUrl, authConfig, { mode, systemIdOrKey, configPath });
478
+ await executeWizardFlow(appKey, dataplaneUrl, authConfig, {
479
+ mode,
480
+ systemIdOrKey,
481
+ configPath,
482
+ debug: options.debug
483
+ });
461
484
  logger.log(chalk.gray(`To change settings, edit integration/${appKey}/wizard.yaml and run: aifabrix wizard ${appKey}`));
462
485
  } catch (error) {
463
486
  await handleWizardError(appKey, configPath, mode, systemIdOrKey, error);
@@ -480,5 +503,4 @@ async function handleWizard(options = {}) {
480
503
  }
481
504
  return await handleWizardInteractive(options);
482
505
  }
483
-
484
506
  module.exports = { handleWizard, handleWizardHeadless };