@aifabrix/builder 2.31.1 → 2.32.1

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 (118) hide show
  1. package/README.md +9 -9
  2. package/integration/hubspot/README.md +2 -2
  3. package/integration/hubspot/hubspot-deploy-company.json +17 -14
  4. package/integration/hubspot/hubspot-deploy-contact.json +19 -16
  5. package/integration/hubspot/hubspot-deploy-deal.json +21 -18
  6. package/lib/api/types/datasources.types.js +31 -5
  7. package/lib/api/types/wizard.types.js +142 -0
  8. package/lib/api/wizard.api.js +177 -0
  9. package/lib/{app-config.js → app/config.js} +4 -4
  10. package/lib/{app-deploy.js → app/deploy.js} +8 -8
  11. package/lib/app/display.js +90 -0
  12. package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
  13. package/lib/{app-down.js → app/down.js} +4 -4
  14. package/lib/app/helpers.js +218 -0
  15. package/lib/app/index.js +298 -0
  16. package/lib/{app-list.js → app/list.js} +6 -6
  17. package/lib/{app-push.js → app/push.js} +4 -4
  18. package/lib/{app-readme.js → app/readme.js} +34 -13
  19. package/lib/{app-register.js → app/register.js} +9 -9
  20. package/lib/{app-rotate-secret.js → app/rotate-secret.js} +10 -10
  21. package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
  22. package/lib/{app-run.js → app/run.js} +6 -6
  23. package/lib/{build.js → build/index.js} +59 -32
  24. package/lib/build/package.json +7 -0
  25. package/lib/cli.js +245 -179
  26. package/lib/commands/app.js +3 -3
  27. package/lib/commands/datasource.js +4 -4
  28. package/lib/commands/login-credentials.js +209 -0
  29. package/lib/commands/login-device.js +254 -0
  30. package/lib/commands/login.js +67 -378
  31. package/lib/commands/logout.js +1 -1
  32. package/lib/commands/secrets-set.js +1 -1
  33. package/lib/commands/secure.js +2 -2
  34. package/lib/commands/wizard.js +498 -0
  35. package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
  36. package/lib/{config.js → core/config.js} +28 -26
  37. package/lib/{diff.js → core/diff.js} +157 -72
  38. package/lib/{secrets.js → core/secrets.js} +86 -49
  39. package/lib/{templates.js → core/templates-env.js} +14 -222
  40. package/lib/core/templates.js +279 -0
  41. package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
  42. package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
  43. package/lib/datasource/list.js +223 -0
  44. package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
  45. package/lib/{deployer.js → deployment/deployer.js} +48 -18
  46. package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
  47. package/lib/{push.js → deployment/push.js} +1 -1
  48. package/lib/external-system/deploy-helpers.js +145 -0
  49. package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
  50. package/lib/external-system/download-helpers.js +114 -0
  51. package/lib/{external-system-download.js → external-system/download.js} +92 -135
  52. package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
  53. package/lib/external-system/test-auth.js +40 -0
  54. package/lib/external-system/test-execution.js +84 -0
  55. package/lib/external-system/test-helpers.js +109 -0
  56. package/lib/{external-system-test.js → external-system/test.js} +174 -192
  57. package/lib/{generator-builders.js → generator/builders.js} +87 -10
  58. package/lib/{generator-external.js → generator/external.js} +115 -52
  59. package/lib/{github-generator.js → generator/github.js} +116 -15
  60. package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
  61. package/lib/{generator.js → generator/index.js} +49 -22
  62. package/lib/{generator-split.js → generator/split.js} +108 -55
  63. package/lib/generator/wizard-prompts.js +357 -0
  64. package/lib/generator/wizard.js +490 -0
  65. package/lib/{infra.js → infrastructure/index.js} +49 -22
  66. package/lib/schema/external-datasource.schema.json +145 -133
  67. package/lib/schema/external-system.schema.json +42 -0
  68. package/lib/utils/api.js +9 -5
  69. package/lib/utils/app-register-api.js +60 -32
  70. package/lib/utils/app-register-auth.js +172 -47
  71. package/lib/utils/app-register-config.js +130 -59
  72. package/lib/utils/app-run-containers.js +29 -8
  73. package/lib/utils/build-helpers.js +1 -1
  74. package/lib/utils/cli-utils.js +78 -30
  75. package/lib/utils/compose-generator.js +145 -65
  76. package/lib/utils/config-paths.js +2 -0
  77. package/lib/utils/deployment-errors.js +1 -1
  78. package/lib/utils/device-code.js +99 -41
  79. package/lib/utils/env-config-loader.js +1 -1
  80. package/lib/utils/env-copy.js +21 -18
  81. package/lib/utils/env-endpoints.js +115 -67
  82. package/lib/utils/env-map.js +13 -14
  83. package/lib/utils/env-ports.js +45 -25
  84. package/lib/utils/env-template.js +84 -42
  85. package/lib/utils/error-formatter.js +26 -9
  86. package/lib/utils/error-formatters/error-parser.js +90 -4
  87. package/lib/utils/error-formatters/http-status-errors.js +54 -17
  88. package/lib/utils/error-formatters/network-errors.js +103 -26
  89. package/lib/utils/external-system-display.js +184 -90
  90. package/lib/utils/external-system-validators.js +164 -42
  91. package/lib/utils/file-upload.js +109 -0
  92. package/lib/utils/health-check.js +199 -83
  93. package/lib/utils/infra-containers.js +1 -1
  94. package/lib/utils/infra-status.js +66 -15
  95. package/lib/utils/local-secrets.js +45 -25
  96. package/lib/utils/paths.js +45 -33
  97. package/lib/utils/schema-loader.js +42 -25
  98. package/lib/utils/schema-resolver.js +123 -74
  99. package/lib/utils/secrets-encryption.js +62 -25
  100. package/lib/utils/secrets-helpers.js +126 -63
  101. package/lib/utils/secrets-path.js +1 -1
  102. package/lib/utils/secrets-url.js +1 -1
  103. package/lib/utils/token-manager-refresh.js +181 -0
  104. package/lib/utils/token-manager.js +76 -123
  105. package/lib/utils/variable-transformer.js +154 -77
  106. package/lib/utils/yaml-preserve.js +41 -47
  107. package/lib/{template-validator.js → validation/template.js} +54 -23
  108. package/lib/{validate.js → validation/validate.js} +205 -125
  109. package/lib/{validator.js → validation/validator.js} +58 -39
  110. package/package.json +31 -2
  111. package/templates/external-system/deploy.ps1.hbs +34 -0
  112. package/templates/external-system/deploy.sh.hbs +34 -0
  113. package/templates/external-system/external-datasource.json.hbs +31 -12
  114. package/lib/app.js +0 -467
  115. package/lib/datasource-list.js +0 -141
  116. /package/lib/{app-prompts.js → app/prompts.js} +0 -0
  117. /package/lib/{env-reader.js → core/env-reader.js} +0 -0
  118. /package/lib/{key-generator.js → core/key-generator.js} +0 -0
@@ -11,13 +11,13 @@
11
11
 
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
- const _keyGenerator = require('./key-generator');
15
- const _validator = require('./validator');
16
- const builders = require('./generator-builders');
17
- const { detectAppType, getDeployJsonPath } = require('./utils/paths');
18
- const splitFunctions = require('./generator-split');
19
- const { loadVariables, loadEnvTemplate, loadRbac, parseEnvironmentVariables } = require('./generator-helpers');
20
- const { generateExternalSystemDeployJson, generateExternalSystemApplicationSchema } = require('./generator-external');
14
+ const _keyGenerator = require('../core/key-generator');
15
+ const _validator = require('../validation/validator');
16
+ const builders = require('./builders');
17
+ const { detectAppType, getDeployJsonPath } = require('../utils/paths');
18
+ const splitFunctions = require('./split');
19
+ const { loadVariables, loadEnvTemplate, loadRbac, parseEnvironmentVariables } = require('./helpers');
20
+ const { generateExternalSystemDeployJson, generateExternalSystemApplicationSchema } = require('./external');
21
21
 
22
22
  /**
23
23
  * Generates deployment JSON from application configuration files
@@ -35,30 +35,37 @@ const { generateExternalSystemDeployJson, generateExternalSystemApplicationSchem
35
35
  * const jsonPath = await generateDeployJson('myapp');
36
36
  * // Returns: './builder/myapp/myapp-deploy.json' or './integration/hubspot/hubspot-deploy.json'
37
37
  */
38
- async function generateDeployJson(appName) {
39
- if (!appName || typeof appName !== 'string') {
40
- throw new Error('App name is required and must be a string');
41
- }
42
-
43
- // Detect app type and get correct path (integration or builder)
44
- const { isExternal, appPath, appType } = await detectAppType(appName);
38
+ /**
39
+ * Loads configuration files for deployment generation
40
+ * @function loadDeploymentConfigFiles
41
+ * @param {string} appPath - Application path
42
+ * @param {string} appType - Application type
43
+ * @returns {Object} Loaded configuration files
44
+ */
45
+ function loadDeploymentConfigFiles(appPath, appType, appName) {
45
46
  const variablesPath = path.join(appPath, 'variables.yaml');
46
-
47
- // Check if app type is external
48
- if (isExternal) {
49
- return await generateExternalSystemDeployJson(appName, appPath);
50
- }
51
-
52
- // Regular app: generate deployment manifest
53
47
  const templatePath = path.join(appPath, 'env.template');
54
48
  const rbacPath = path.join(appPath, 'rbac.yaml');
55
49
  const jsonPath = getDeployJsonPath(appName, appType, true); // Use new naming
56
50
 
57
- // Load configuration files
58
51
  const { parsed: variables } = loadVariables(variablesPath);
59
52
  const envTemplate = loadEnvTemplate(templatePath);
60
53
  const rbac = loadRbac(rbacPath);
61
54
 
55
+ return { variables, envTemplate, rbac, jsonPath };
56
+ }
57
+
58
+ /**
59
+ * Builds and validates deployment manifest
60
+ * @function buildAndValidateDeployment
61
+ * @param {string} appName - Application name
62
+ * @param {Object} variables - Variables configuration
63
+ * @param {Object} envTemplate - Environment template
64
+ * @param {Object} rbac - RBAC configuration
65
+ * @returns {Object} Deployment manifest with deploymentKey
66
+ * @throws {Error} If validation fails
67
+ */
68
+ function buildAndValidateDeployment(appName, variables, envTemplate, rbac) {
62
69
  // Parse environment variables from template and merge portalInput from variables.yaml
63
70
  const configuration = parseEnvironmentVariables(envTemplate, variables);
64
71
 
@@ -78,6 +85,26 @@ async function generateDeployJson(appName) {
78
85
  throw new Error(`Generated deployment JSON does not match schema:\n${errorMessages}`);
79
86
  }
80
87
 
88
+ return deployment;
89
+ }
90
+
91
+ async function generateDeployJson(appName) {
92
+ if (!appName || typeof appName !== 'string') {
93
+ throw new Error('App name is required and must be a string');
94
+ }
95
+
96
+ // Detect app type and get correct path (integration or builder)
97
+ const { isExternal, appPath, appType } = await detectAppType(appName);
98
+
99
+ // Check if app type is external
100
+ if (isExternal) {
101
+ return await generateExternalSystemDeployJson(appName, appPath);
102
+ }
103
+
104
+ // Regular app: generate deployment manifest
105
+ const { variables, envTemplate, rbac, jsonPath } = loadDeploymentConfigFiles(appPath, appType, appName);
106
+ const deployment = buildAndValidateDeployment(appName, variables, envTemplate, rbac);
107
+
81
108
  // Write deployment JSON
82
109
  const jsonContent = JSON.stringify(deployment, null, 2);
83
110
  fs.writeFileSync(jsonPath, jsonContent, { mode: 0o644 });
@@ -143,41 +143,42 @@ function extractRequirementsSection(deployment) {
143
143
  * @param {Object} deployment - Deployment JSON object
144
144
  * @returns {Object} Object with optional sections
145
145
  */
146
+ /**
147
+ * Extracts a single optional section if present
148
+ * @function extractOptionalSection
149
+ * @param {Object} deployment - Deployment object
150
+ * @param {string} sectionName - Section name to extract
151
+ * @param {Object} optional - Optional sections object to update
152
+ */
153
+ function extractOptionalSection(deployment, sectionName, optional) {
154
+ if (deployment[sectionName]) {
155
+ if (sectionName === 'authentication') {
156
+ optional[sectionName] = { ...deployment[sectionName] };
157
+ } else {
158
+ optional[sectionName] = deployment[sectionName];
159
+ }
160
+ }
161
+ }
162
+
146
163
  function extractOptionalSections(deployment) {
147
164
  const optional = {};
148
165
 
149
- if (deployment.healthCheck) {
150
- optional.healthCheck = deployment.healthCheck;
151
- }
152
- if (deployment.authentication) {
153
- optional.authentication = { ...deployment.authentication };
154
- }
155
- if (deployment.build) {
156
- optional.build = deployment.build;
157
- }
158
- if (deployment.repository) {
159
- optional.repository = deployment.repository;
160
- }
161
- if (deployment.deployment) {
162
- optional.deployment = deployment.deployment;
163
- }
164
- if (deployment.startupCommand) {
165
- optional.startupCommand = deployment.startupCommand;
166
- }
167
- if (deployment.runtimeVersion) {
168
- optional.runtimeVersion = deployment.runtimeVersion;
169
- }
170
- if (deployment.scaling) {
171
- optional.scaling = deployment.scaling;
172
- }
173
- if (deployment.frontDoorRouting) {
174
- optional.frontDoorRouting = deployment.frontDoorRouting;
175
- }
176
- if (deployment.roles) {
177
- optional.roles = deployment.roles;
178
- }
179
- if (deployment.permissions) {
180
- optional.permissions = deployment.permissions;
166
+ const optionalSectionNames = [
167
+ 'healthCheck',
168
+ 'authentication',
169
+ 'build',
170
+ 'repository',
171
+ 'deployment',
172
+ 'startupCommand',
173
+ 'runtimeVersion',
174
+ 'scaling',
175
+ 'frontDoorRouting',
176
+ 'roles',
177
+ 'permissions'
178
+ ];
179
+
180
+ for (const sectionName of optionalSectionNames) {
181
+ extractOptionalSection(deployment, sectionName, optional);
181
182
  }
182
183
 
183
184
  return optional;
@@ -311,73 +312,125 @@ function generateReadmeFromDeployJson(deployment) {
311
312
  * @returns {Promise<Object>} Object with paths to generated files
312
313
  * @throws {Error} If JSON file not found or invalid
313
314
  */
314
- async function splitDeployJson(deployJsonPath, outputDir = null) {
315
+ /**
316
+ * Validates deployment JSON path
317
+ * @function validateDeployJsonPath
318
+ * @param {string} deployJsonPath - Deployment JSON path
319
+ * @throws {Error} If path is invalid
320
+ */
321
+ function validateDeployJsonPath(deployJsonPath) {
315
322
  if (!deployJsonPath || typeof deployJsonPath !== 'string') {
316
323
  throw new Error('Deployment JSON path is required and must be a string');
317
324
  }
318
-
319
- // Validate file exists
320
325
  const fsSync = require('fs');
321
326
  if (!fsSync.existsSync(deployJsonPath)) {
322
327
  throw new Error(`Deployment JSON file not found: ${deployJsonPath}`);
323
328
  }
329
+ }
324
330
 
325
- // Determine output directory
331
+ /**
332
+ * Prepares output directory
333
+ * @async
334
+ * @function prepareOutputDirectory
335
+ * @param {string} deployJsonPath - Deployment JSON path
336
+ * @param {string|null} outputDir - Optional output directory
337
+ * @returns {Promise<string>} Final output directory path
338
+ */
339
+ async function prepareOutputDirectory(deployJsonPath, outputDir) {
326
340
  const finalOutputDir = outputDir || path.dirname(deployJsonPath);
327
-
328
- // Ensure output directory exists
341
+ const fsSync = require('fs');
329
342
  if (!fsSync.existsSync(finalOutputDir)) {
330
343
  await fs.mkdir(finalOutputDir, { recursive: true });
331
344
  }
345
+ return finalOutputDir;
346
+ }
332
347
 
333
- // Load and parse deployment JSON
334
- let deployment;
348
+ /**
349
+ * Loads and parses deployment JSON
350
+ * @async
351
+ * @function loadDeploymentJson
352
+ * @param {string} deployJsonPath - Deployment JSON path
353
+ * @returns {Promise<Object>} Parsed deployment object
354
+ */
355
+ async function loadDeploymentJson(deployJsonPath) {
335
356
  try {
336
357
  const jsonContent = await fs.readFile(deployJsonPath, 'utf8');
337
- deployment = JSON.parse(jsonContent);
358
+ return JSON.parse(jsonContent);
338
359
  } catch (error) {
339
360
  if (error.code === 'ENOENT') {
340
361
  throw new Error(`Deployment JSON file not found: ${deployJsonPath}`);
341
362
  }
342
363
  throw new Error(`Invalid JSON syntax in deployment file: ${error.message}`);
343
364
  }
365
+ }
344
366
 
345
- // Extract components
346
- const envTemplate = extractEnvTemplate(deployment.configuration || []);
347
- const variables = extractVariablesYaml(deployment);
348
- const rbac = extractRbacYaml(deployment);
349
- const readme = generateReadmeFromDeployJson(deployment);
367
+ /**
368
+ * Writes a component file
369
+ * @async
370
+ * @function writeComponentFile
371
+ * @param {string} filePath - File path
372
+ * @param {string} content - File content
373
+ * @returns {Promise<void>}
374
+ */
375
+ async function writeComponentFile(filePath, content) {
376
+ await fs.writeFile(filePath, content, { mode: 0o644, encoding: 'utf8' });
377
+ }
350
378
 
351
- // Write component files
379
+ /**
380
+ * Writes all component files
381
+ * @async
382
+ * @function writeComponentFiles
383
+ * @param {string} outputDir - Output directory
384
+ * @param {string} envTemplate - Environment template content
385
+ * @param {Object} variables - Variables object
386
+ * @param {Object|null} rbac - RBAC object or null
387
+ * @param {string} readme - README content
388
+ * @returns {Promise<Object>} Results object with file paths
389
+ */
390
+ async function writeComponentFiles(outputDir, envTemplate, variables, rbac, readme) {
352
391
  const results = {};
353
392
 
354
393
  // Write env.template
355
- const envTemplatePath = path.join(finalOutputDir, 'env.template');
356
- await fs.writeFile(envTemplatePath, envTemplate, { mode: 0o644, encoding: 'utf8' });
394
+ const envTemplatePath = path.join(outputDir, 'env.template');
395
+ await writeComponentFile(envTemplatePath, envTemplate);
357
396
  results.envTemplate = envTemplatePath;
358
397
 
359
398
  // Write variables.yaml
360
- const variablesPath = path.join(finalOutputDir, 'variables.yaml');
399
+ const variablesPath = path.join(outputDir, 'variables.yaml');
361
400
  const variablesYaml = yaml.dump(variables, { indent: 2, lineWidth: -1 });
362
- await fs.writeFile(variablesPath, variablesYaml, { mode: 0o644, encoding: 'utf8' });
401
+ await writeComponentFile(variablesPath, variablesYaml);
363
402
  results.variables = variablesPath;
364
403
 
365
404
  // Write rbac.yml (only if roles/permissions exist)
366
405
  if (rbac) {
367
- const rbacPath = path.join(finalOutputDir, 'rbac.yml');
406
+ const rbacPath = path.join(outputDir, 'rbac.yml');
368
407
  const rbacYaml = yaml.dump(rbac, { indent: 2, lineWidth: -1 });
369
- await fs.writeFile(rbacPath, rbacYaml, { mode: 0o644, encoding: 'utf8' });
408
+ await writeComponentFile(rbacPath, rbacYaml);
370
409
  results.rbac = rbacPath;
371
410
  }
372
411
 
373
412
  // Write README.md
374
- const readmePath = path.join(finalOutputDir, 'README.md');
375
- await fs.writeFile(readmePath, readme, { mode: 0o644, encoding: 'utf8' });
413
+ const readmePath = path.join(outputDir, 'README.md');
414
+ await writeComponentFile(readmePath, readme);
376
415
  results.readme = readmePath;
377
416
 
378
417
  return results;
379
418
  }
380
419
 
420
+ async function splitDeployJson(deployJsonPath, outputDir = null) {
421
+ validateDeployJsonPath(deployJsonPath);
422
+ const finalOutputDir = await prepareOutputDirectory(deployJsonPath, outputDir);
423
+ const deployment = await loadDeploymentJson(deployJsonPath);
424
+
425
+ // Extract components
426
+ const envTemplate = extractEnvTemplate(deployment.configuration || []);
427
+ const variables = extractVariablesYaml(deployment);
428
+ const rbac = extractRbacYaml(deployment);
429
+ const readme = generateReadmeFromDeployJson(deployment);
430
+
431
+ return await writeComponentFiles(finalOutputDir, envTemplate, variables, rbac, readme);
432
+ }
433
+
381
434
  module.exports = {
382
435
  splitDeployJson,
383
436
  extractEnvTemplate,
@@ -0,0 +1,357 @@
1
+ /**
2
+ * @fileoverview Wizard prompt utilities for interactive external system creation
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const inquirer = require('inquirer');
8
+ const path = require('path');
9
+ const fs = require('fs').promises;
10
+
11
+ /**
12
+ * Prompt for wizard mode selection
13
+ * @async
14
+ * @function promptForMode
15
+ * @returns {Promise<string>} Selected mode ('create-system' | 'add-datasource')
16
+ */
17
+ async function promptForMode() {
18
+ const { mode } = await inquirer.prompt([
19
+ {
20
+ type: 'list',
21
+ name: 'mode',
22
+ message: 'What would you like to do?',
23
+ choices: [
24
+ { name: 'Create a new external system', value: 'create-system' },
25
+ { name: 'Add datasource to existing system', value: 'add-datasource' }
26
+ ]
27
+ }
28
+ ]);
29
+ return mode;
30
+ }
31
+
32
+ /**
33
+ * Prompt for source type selection
34
+ * @async
35
+ * @function promptForSourceType
36
+ * @returns {Promise<string>} Selected source type
37
+ */
38
+ async function promptForSourceType() {
39
+ const { sourceType } = await inquirer.prompt([
40
+ {
41
+ type: 'list',
42
+ name: 'sourceType',
43
+ message: 'What is your source type?',
44
+ choices: [
45
+ { name: 'OpenAPI file (local file)', value: 'openapi-file' },
46
+ { name: 'OpenAPI URL (remote URL)', value: 'openapi-url' },
47
+ { name: 'MCP server', value: 'mcp-server' },
48
+ { name: 'Known platform (pre-configured)', value: 'known-platform' }
49
+ ]
50
+ }
51
+ ]);
52
+ return sourceType;
53
+ }
54
+
55
+ /**
56
+ * Prompt for OpenAPI file path
57
+ * @async
58
+ * @function promptForOpenApiFile
59
+ * @returns {Promise<string>} File path
60
+ */
61
+ async function promptForOpenApiFile() {
62
+ const { filePath } = await inquirer.prompt([
63
+ {
64
+ type: 'input',
65
+ name: 'filePath',
66
+ message: 'Enter the path to your OpenAPI file:',
67
+ validate: async(input) => {
68
+ if (!input || typeof input !== 'string' || input.trim().length === 0) {
69
+ return 'File path is required';
70
+ }
71
+ const resolvedPath = path.resolve(input);
72
+ try {
73
+ const stats = await fs.stat(resolvedPath);
74
+ if (!stats.isFile()) {
75
+ return 'Path must be a file';
76
+ }
77
+ return true;
78
+ } catch (error) {
79
+ return `File not found: ${input}`;
80
+ }
81
+ }
82
+ }
83
+ ]);
84
+ return path.resolve(filePath);
85
+ }
86
+
87
+ /**
88
+ * Prompt for OpenAPI URL
89
+ * @async
90
+ * @function promptForOpenApiUrl
91
+ * @returns {Promise<string>} URL
92
+ */
93
+ async function promptForOpenApiUrl() {
94
+ const { url } = await inquirer.prompt([
95
+ {
96
+ type: 'input',
97
+ name: 'url',
98
+ message: 'Enter the OpenAPI URL:',
99
+ validate: (input) => {
100
+ if (!input || typeof input !== 'string' || input.trim().length === 0) {
101
+ return 'URL is required';
102
+ }
103
+ try {
104
+ new URL(input);
105
+ return true;
106
+ } catch (error) {
107
+ return 'Invalid URL format';
108
+ }
109
+ }
110
+ }
111
+ ]);
112
+ return url.trim();
113
+ }
114
+
115
+ /**
116
+ * Prompt for MCP server details
117
+ * @async
118
+ * @function promptForMcpServer
119
+ * @returns {Promise<Object>} Object with serverUrl and token
120
+ */
121
+ async function promptForMcpServer() {
122
+ const answers = await inquirer.prompt([
123
+ {
124
+ type: 'input',
125
+ name: 'serverUrl',
126
+ message: 'Enter MCP server URL:',
127
+ validate: (input) => {
128
+ if (!input || typeof input !== 'string' || input.trim().length === 0) {
129
+ return 'Server URL is required';
130
+ }
131
+ try {
132
+ new URL(input);
133
+ return true;
134
+ } catch (error) {
135
+ return 'Invalid URL format';
136
+ }
137
+ }
138
+ },
139
+ {
140
+ type: 'password',
141
+ name: 'token',
142
+ message: 'Enter MCP server authentication token:',
143
+ validate: (input) => {
144
+ if (!input || typeof input !== 'string' || input.trim().length === 0) {
145
+ return 'Token is required';
146
+ }
147
+ return true;
148
+ }
149
+ }
150
+ ]);
151
+ return {
152
+ serverUrl: answers.serverUrl.trim(),
153
+ token: answers.token
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Prompt for known platform selection
159
+ * @async
160
+ * @function promptForKnownPlatform
161
+ * @param {string[]} [platforms] - List of available platforms (if provided)
162
+ * @returns {Promise<string>} Selected platform key
163
+ */
164
+ async function promptForKnownPlatform(platforms = []) {
165
+ // Default platforms if none provided
166
+ const defaultPlatforms = [
167
+ { name: 'HubSpot', value: 'hubspot' },
168
+ { name: 'Salesforce', value: 'salesforce' },
169
+ { name: 'Zendesk', value: 'zendesk' },
170
+ { name: 'Slack', value: 'slack' },
171
+ { name: 'Microsoft 365', value: 'microsoft365' }
172
+ ];
173
+
174
+ const choices = platforms.length > 0
175
+ ? platforms.map(p => ({ name: p.displayName || p.key, value: p.key }))
176
+ : defaultPlatforms;
177
+
178
+ const { platform } = await inquirer.prompt([
179
+ {
180
+ type: 'list',
181
+ name: 'platform',
182
+ message: 'Select a platform:',
183
+ choices
184
+ }
185
+ ]);
186
+ return platform;
187
+ }
188
+
189
+ /**
190
+ * Prompt for user intent
191
+ * @async
192
+ * @function promptForUserIntent
193
+ * @returns {Promise<string>} User intent
194
+ */
195
+ async function promptForUserIntent() {
196
+ const { intent } = await inquirer.prompt([
197
+ {
198
+ type: 'list',
199
+ name: 'intent',
200
+ message: 'What is your primary use case?',
201
+ choices: [
202
+ { name: 'Sales-focused (CRM, leads, deals)', value: 'sales-focused' },
203
+ { name: 'Support-focused (tickets, customers)', value: 'support-focused' },
204
+ { name: 'Marketing-focused (campaigns, contacts)', value: 'marketing-focused' },
205
+ { name: 'General integration', value: 'general' }
206
+ ],
207
+ default: 'general'
208
+ }
209
+ ]);
210
+ return intent;
211
+ }
212
+
213
+ /**
214
+ * Prompt for user preferences
215
+ * @async
216
+ * @function promptForUserPreferences
217
+ * @returns {Promise<Object>} User preferences object
218
+ */
219
+ async function promptForUserPreferences() {
220
+ const answers = await inquirer.prompt([
221
+ {
222
+ type: 'confirm',
223
+ name: 'mcp',
224
+ message: 'Enable MCP (Model Context Protocol)?',
225
+ default: false
226
+ },
227
+ {
228
+ type: 'confirm',
229
+ name: 'abac',
230
+ message: 'Enable ABAC (Attribute-Based Access Control)?',
231
+ default: false
232
+ },
233
+ {
234
+ type: 'confirm',
235
+ name: 'rbac',
236
+ message: 'Enable RBAC (Role-Based Access Control)?',
237
+ default: false
238
+ }
239
+ ]);
240
+ return {
241
+ mcp: answers.mcp,
242
+ abac: answers.abac,
243
+ rbac: answers.rbac
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Prompt for configuration review and editing
249
+ * @async
250
+ * @function promptForConfigReview
251
+ * @param {Object} systemConfig - System configuration
252
+ * @param {Object[]} datasourceConfigs - Array of datasource configurations
253
+ * @returns {Promise<Object>} Object with review decision and optionally edited configs
254
+ */
255
+ async function promptForConfigReview(systemConfig, datasourceConfigs) {
256
+ // eslint-disable-next-line no-console
257
+ console.log('\n📋 Generated Configuration:');
258
+ // eslint-disable-next-line no-console
259
+ console.log('\nSystem Configuration:');
260
+ // eslint-disable-next-line no-console
261
+ console.log(JSON.stringify(systemConfig, null, 2));
262
+ // eslint-disable-next-line no-console
263
+ console.log('\nDatasource Configurations:');
264
+ datasourceConfigs.forEach((ds, index) => {
265
+ // eslint-disable-next-line no-console
266
+ console.log(`\nDatasource ${index + 1}:`);
267
+ // eslint-disable-next-line no-console
268
+ console.log(JSON.stringify(ds, null, 2));
269
+ });
270
+
271
+ const { action } = await inquirer.prompt([
272
+ {
273
+ type: 'list',
274
+ name: 'action',
275
+ message: 'What would you like to do?',
276
+ choices: [
277
+ { name: 'Accept and save', value: 'accept' },
278
+ { name: 'Edit configuration manually', value: 'edit' },
279
+ { name: 'Cancel', value: 'cancel' }
280
+ ]
281
+ }
282
+ ]);
283
+
284
+ if (action === 'cancel') {
285
+ return { action: 'cancel' };
286
+ }
287
+
288
+ if (action === 'edit') {
289
+ const { editedConfig } = await inquirer.prompt([
290
+ {
291
+ type: 'editor',
292
+ name: 'editedConfig',
293
+ message: 'Edit the configuration (JSON format):',
294
+ default: JSON.stringify({ systemConfig, datasourceConfigs }, null, 2),
295
+ validate: (input) => {
296
+ try {
297
+ JSON.parse(input);
298
+ return true;
299
+ } catch (error) {
300
+ return `Invalid JSON: ${error.message}`;
301
+ }
302
+ }
303
+ }
304
+ ]);
305
+
306
+ const parsed = JSON.parse(editedConfig);
307
+ return {
308
+ action: 'edit',
309
+ systemConfig: parsed.systemConfig,
310
+ datasourceConfigs: parsed.datasourceConfigs
311
+ };
312
+ }
313
+
314
+ return { action: 'accept' };
315
+ }
316
+
317
+ /**
318
+ * Prompt for application name
319
+ * @async
320
+ * @function promptForAppName
321
+ * @param {string} [defaultName] - Default application name
322
+ * @returns {Promise<string>} Application name
323
+ */
324
+ async function promptForAppName(defaultName) {
325
+ const { appName } = await inquirer.prompt([
326
+ {
327
+ type: 'input',
328
+ name: 'appName',
329
+ message: 'Enter application name:',
330
+ default: defaultName,
331
+ validate: (input) => {
332
+ if (!input || typeof input !== 'string' || input.trim().length === 0) {
333
+ return 'Application name is required';
334
+ }
335
+ if (!/^[a-z0-9-_]+$/.test(input)) {
336
+ return 'Application name must contain only lowercase letters, numbers, hyphens, and underscores';
337
+ }
338
+ return true;
339
+ }
340
+ }
341
+ ]);
342
+ return appName.trim();
343
+ }
344
+
345
+ module.exports = {
346
+ promptForMode,
347
+ promptForSourceType,
348
+ promptForOpenApiFile,
349
+ promptForOpenApiUrl,
350
+ promptForMcpServer,
351
+ promptForKnownPlatform,
352
+ promptForUserIntent,
353
+ promptForUserPreferences,
354
+ promptForConfigReview,
355
+ promptForAppName
356
+ };
357
+