@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.
- package/README.md +9 -9
- package/integration/hubspot/README.md +2 -2
- package/integration/hubspot/hubspot-deploy-company.json +17 -14
- package/integration/hubspot/hubspot-deploy-contact.json +19 -16
- package/integration/hubspot/hubspot-deploy-deal.json +21 -18
- package/lib/api/types/datasources.types.js +31 -5
- package/lib/api/types/wizard.types.js +142 -0
- package/lib/api/wizard.api.js +177 -0
- package/lib/{app-config.js → app/config.js} +4 -4
- package/lib/{app-deploy.js → app/deploy.js} +8 -8
- package/lib/app/display.js +90 -0
- package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
- package/lib/{app-down.js → app/down.js} +4 -4
- package/lib/app/helpers.js +218 -0
- package/lib/app/index.js +298 -0
- package/lib/{app-list.js → app/list.js} +6 -6
- package/lib/{app-push.js → app/push.js} +4 -4
- package/lib/{app-readme.js → app/readme.js} +34 -13
- package/lib/{app-register.js → app/register.js} +9 -9
- package/lib/{app-rotate-secret.js → app/rotate-secret.js} +10 -10
- package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
- package/lib/{app-run.js → app/run.js} +6 -6
- package/lib/{build.js → build/index.js} +59 -32
- package/lib/build/package.json +7 -0
- package/lib/cli.js +245 -179
- package/lib/commands/app.js +3 -3
- package/lib/commands/datasource.js +4 -4
- package/lib/commands/login-credentials.js +209 -0
- package/lib/commands/login-device.js +254 -0
- package/lib/commands/login.js +67 -378
- package/lib/commands/logout.js +1 -1
- package/lib/commands/secrets-set.js +1 -1
- package/lib/commands/secure.js +2 -2
- package/lib/commands/wizard.js +498 -0
- package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
- package/lib/{config.js → core/config.js} +28 -26
- package/lib/{diff.js → core/diff.js} +157 -72
- package/lib/{secrets.js → core/secrets.js} +86 -49
- package/lib/{templates.js → core/templates-env.js} +14 -222
- package/lib/core/templates.js +279 -0
- package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
- package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
- package/lib/datasource/list.js +223 -0
- package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
- package/lib/{deployer.js → deployment/deployer.js} +48 -18
- package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
- package/lib/{push.js → deployment/push.js} +1 -1
- package/lib/external-system/deploy-helpers.js +145 -0
- package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
- package/lib/external-system/download-helpers.js +114 -0
- package/lib/{external-system-download.js → external-system/download.js} +92 -135
- package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
- package/lib/external-system/test-auth.js +40 -0
- package/lib/external-system/test-execution.js +84 -0
- package/lib/external-system/test-helpers.js +109 -0
- package/lib/{external-system-test.js → external-system/test.js} +174 -192
- package/lib/{generator-builders.js → generator/builders.js} +87 -10
- package/lib/{generator-external.js → generator/external.js} +115 -52
- package/lib/{github-generator.js → generator/github.js} +116 -15
- package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
- package/lib/{generator.js → generator/index.js} +49 -22
- package/lib/{generator-split.js → generator/split.js} +108 -55
- package/lib/generator/wizard-prompts.js +357 -0
- package/lib/generator/wizard.js +490 -0
- package/lib/{infra.js → infrastructure/index.js} +49 -22
- package/lib/schema/external-datasource.schema.json +145 -133
- package/lib/schema/external-system.schema.json +42 -0
- package/lib/utils/api.js +9 -5
- package/lib/utils/app-register-api.js +60 -32
- package/lib/utils/app-register-auth.js +172 -47
- package/lib/utils/app-register-config.js +130 -59
- package/lib/utils/app-run-containers.js +29 -8
- package/lib/utils/build-helpers.js +1 -1
- package/lib/utils/cli-utils.js +78 -30
- package/lib/utils/compose-generator.js +145 -65
- package/lib/utils/config-paths.js +2 -0
- package/lib/utils/deployment-errors.js +1 -1
- package/lib/utils/device-code.js +99 -41
- package/lib/utils/env-config-loader.js +1 -1
- package/lib/utils/env-copy.js +21 -18
- package/lib/utils/env-endpoints.js +115 -67
- package/lib/utils/env-map.js +13 -14
- package/lib/utils/env-ports.js +45 -25
- package/lib/utils/env-template.js +84 -42
- package/lib/utils/error-formatter.js +26 -9
- package/lib/utils/error-formatters/error-parser.js +90 -4
- package/lib/utils/error-formatters/http-status-errors.js +54 -17
- package/lib/utils/error-formatters/network-errors.js +103 -26
- package/lib/utils/external-system-display.js +184 -90
- package/lib/utils/external-system-validators.js +164 -42
- package/lib/utils/file-upload.js +109 -0
- package/lib/utils/health-check.js +199 -83
- package/lib/utils/infra-containers.js +1 -1
- package/lib/utils/infra-status.js +66 -15
- package/lib/utils/local-secrets.js +45 -25
- package/lib/utils/paths.js +45 -33
- package/lib/utils/schema-loader.js +42 -25
- package/lib/utils/schema-resolver.js +123 -74
- package/lib/utils/secrets-encryption.js +62 -25
- package/lib/utils/secrets-helpers.js +126 -63
- package/lib/utils/secrets-path.js +1 -1
- package/lib/utils/secrets-url.js +1 -1
- package/lib/utils/token-manager-refresh.js +181 -0
- package/lib/utils/token-manager.js +76 -123
- package/lib/utils/variable-transformer.js +154 -77
- package/lib/utils/yaml-preserve.js +41 -47
- package/lib/{template-validator.js → validation/template.js} +54 -23
- package/lib/{validate.js → validation/validate.js} +205 -125
- package/lib/{validator.js → validation/validator.js} +58 -39
- package/package.json +31 -2
- package/templates/external-system/deploy.ps1.hbs +34 -0
- package/templates/external-system/deploy.sh.hbs +34 -0
- package/templates/external-system/external-datasource.json.hbs +31 -12
- package/lib/app.js +0 -467
- package/lib/datasource-list.js +0 -141
- /package/lib/{app-prompts.js → app/prompts.js} +0 -0
- /package/lib/{env-reader.js → core/env-reader.js} +0 -0
- /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('
|
|
15
|
-
const _validator = require('
|
|
16
|
-
const builders = require('./
|
|
17
|
-
const { detectAppType, getDeployJsonPath } = require('
|
|
18
|
-
const splitFunctions = require('./
|
|
19
|
-
const { loadVariables, loadEnvTemplate, loadRbac, parseEnvironmentVariables } = require('./
|
|
20
|
-
const { generateExternalSystemDeployJson, generateExternalSystemApplicationSchema } = require('./
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
334
|
-
|
|
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
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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(
|
|
356
|
-
await
|
|
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(
|
|
399
|
+
const variablesPath = path.join(outputDir, 'variables.yaml');
|
|
361
400
|
const variablesYaml = yaml.dump(variables, { indent: 2, lineWidth: -1 });
|
|
362
|
-
await
|
|
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(
|
|
406
|
+
const rbacPath = path.join(outputDir, 'rbac.yml');
|
|
368
407
|
const rbacYaml = yaml.dump(rbac, { indent: 2, lineWidth: -1 });
|
|
369
|
-
await
|
|
408
|
+
await writeComponentFile(rbacPath, rbacYaml);
|
|
370
409
|
results.rbac = rbacPath;
|
|
371
410
|
}
|
|
372
411
|
|
|
373
412
|
// Write README.md
|
|
374
|
-
const readmePath = path.join(
|
|
375
|
-
await
|
|
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
|
+
|