@aifabrix/builder 2.32.3 → 2.33.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/.cursor/rules/project-rules.mdc +8 -0
- package/README.md +36 -8
- package/bin/aifabrix.js +6 -8
- package/integration/hubspot/README.md +12 -11
- package/integration/hubspot/companies.json +2048 -0
- package/integration/hubspot/create-hubspot.js +665 -0
- package/integration/hubspot/{hubspot-deploy-company.json → hubspot-datasource-company.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-contact.json → hubspot-datasource-contact.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-deal.json → hubspot-datasource-deal.json} +1 -1
- package/integration/hubspot/hubspot-deploy.json +832 -81
- package/integration/hubspot/hubspot-system.json +99 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-credential-real.yaml +20 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +9 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-add-datasource.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-app-name.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-create.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-select.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-known-platform.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-app.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-mode.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-file.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-url.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-source.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-array-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +5 -0
- package/integration/hubspot/test-dataplane-down-helpers.js +246 -0
- package/integration/hubspot/test-dataplane-down-tests.js +419 -0
- package/integration/hubspot/test-dataplane-down.js +157 -0
- package/integration/hubspot/test.js +1517 -0
- package/integration/hubspot/variables.yaml +4 -4
- package/integration/hubspot/wizard-hubspot-e2e.yaml +16 -0
- package/integration/hubspot/wizard-hubspot-platform.yaml +8 -0
- package/lib/api/applications.api.js +1 -0
- package/lib/api/index.js +6 -2
- package/lib/api/types/wizard.types.js +176 -38
- package/lib/api/wizard.api.js +161 -23
- package/lib/app/deploy.js +116 -54
- package/lib/app/display.js +6 -5
- package/lib/app/dockerfile.js +2 -1
- package/lib/app/list.js +17 -10
- package/lib/app/readme.js +41 -112
- package/lib/app/register.js +44 -9
- package/lib/app/rotate-secret.js +48 -31
- package/lib/cli.js +219 -70
- package/lib/commands/app.js +4 -9
- package/lib/commands/auth-config.js +125 -0
- package/lib/commands/auth-status.js +7 -8
- package/lib/commands/datasource.js +3 -6
- package/lib/commands/login-credentials.js +4 -4
- package/lib/commands/login-device.js +26 -17
- package/lib/commands/login.js +12 -10
- package/lib/commands/wizard-config-normalizer.js +92 -0
- package/lib/commands/wizard-core.js +515 -0
- package/lib/commands/wizard-dataplane.js +122 -0
- package/lib/commands/wizard-headless.js +115 -0
- package/lib/commands/wizard.js +110 -332
- package/lib/core/config.js +46 -0
- package/lib/core/secrets.js +3 -22
- package/lib/core/templates-env.js +1 -1
- package/lib/datasource/deploy.js +59 -23
- package/lib/datasource/list.js +108 -19
- package/lib/deployment/deployer.js +25 -0
- package/lib/deployment/environment.js +10 -13
- package/lib/external-system/delete.js +151 -0
- package/lib/external-system/deploy.js +53 -378
- package/lib/external-system/download-helpers.js +45 -65
- package/lib/external-system/download.js +33 -13
- package/lib/external-system/generator.js +11 -7
- package/lib/external-system/test-auth.js +4 -3
- package/lib/generator/builders.js +3 -1
- package/lib/generator/external-controller-manifest.js +157 -0
- package/lib/generator/external-schema-utils.js +236 -0
- package/lib/generator/external.js +55 -3
- package/lib/generator/index.js +22 -10
- package/lib/generator/wizard-prompts.js +33 -10
- package/lib/generator/wizard.js +69 -86
- package/lib/infrastructure/compose.js +100 -0
- package/lib/infrastructure/helpers.js +139 -0
- package/lib/infrastructure/index.js +52 -311
- package/lib/infrastructure/services.js +168 -0
- package/lib/schema/application-schema.json +23 -4
- package/lib/schema/external-datasource.schema.json +2 -2
- package/lib/schema/wizard-config.schema.json +234 -0
- package/lib/utils/api.js +102 -52
- package/lib/utils/app-existence.js +42 -0
- package/lib/utils/app-register-config.js +7 -2
- package/lib/utils/auth-config-validator.js +92 -0
- package/lib/utils/command-header.js +43 -0
- package/lib/utils/compose-generator.js +113 -70
- package/lib/utils/controller-url.js +65 -17
- package/lib/utils/dataplane-health.js +115 -0
- package/lib/utils/dataplane-resolver.js +29 -0
- package/lib/utils/dev-config.js +6 -2
- package/lib/utils/env-copy.js +2 -1
- package/lib/utils/env-ports.js +2 -1
- package/lib/utils/env-template.js +1 -1
- package/lib/utils/error-formatter.js +49 -0
- package/lib/utils/error-formatters/network-errors.js +13 -3
- package/lib/utils/external-readme.js +125 -0
- package/lib/utils/help-builder.js +190 -0
- package/lib/utils/infra-status.js +13 -3
- package/lib/utils/paths.js +17 -2
- package/lib/utils/port-resolver.js +111 -0
- package/lib/utils/secrets-helpers.js +3 -15
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/token-manager.js +9 -4
- package/lib/utils/variable-transformer.js +7 -2
- package/lib/validation/external-manifest-validator.js +202 -0
- package/lib/validation/validate-display.js +406 -0
- package/lib/validation/validate.js +159 -123
- package/lib/validation/validator.js +36 -3
- package/lib/validation/wizard-config-validator.js +267 -0
- package/package.json +4 -2
- package/templates/applications/README.md.hbs +18 -16
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/applications/miso-controller/rbac.yaml +7 -7
- package/templates/external-system/README.md.hbs +99 -0
- package/templates/github/ci.yaml.hbs +44 -1
- package/templates/github/release.yaml.hbs +44 -0
- package/templates/infra/compose.yaml.hbs +35 -0
- package/templates/python/docker-compose.hbs +26 -0
- package/templates/typescript/docker-compose.hbs +26 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Wizard core functions - shared between interactive and headless modes
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
/* eslint-disable max-lines */
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const ora = require('ora');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs').promises;
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const { getDeploymentAuth, getDeviceOnlyAuth } = require('../utils/token-manager');
|
|
13
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
14
|
+
const { normalizeWizardConfigs } = require('./wizard-config-normalizer');
|
|
15
|
+
const {
|
|
16
|
+
createWizardSession,
|
|
17
|
+
updateWizardSession,
|
|
18
|
+
parseOpenApi,
|
|
19
|
+
credentialSelection,
|
|
20
|
+
detectType,
|
|
21
|
+
generateConfig,
|
|
22
|
+
validateWizardConfig,
|
|
23
|
+
getDeploymentDocs,
|
|
24
|
+
testMcpConnection
|
|
25
|
+
} = require('../api/wizard.api');
|
|
26
|
+
const { generateWizardFiles } = require('../generator/wizard');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Validate app name and check if directory exists
|
|
30
|
+
* @async
|
|
31
|
+
* @function validateAndCheckAppDirectory
|
|
32
|
+
* @param {string} appName - Application name
|
|
33
|
+
* @param {boolean} [interactive=true] - Whether to prompt for confirmation
|
|
34
|
+
* @returns {Promise<boolean>} True if should continue, false if cancelled
|
|
35
|
+
*/
|
|
36
|
+
async function validateAndCheckAppDirectory(appName, interactive = true) {
|
|
37
|
+
if (!/^[a-z0-9-_]+$/.test(appName)) {
|
|
38
|
+
throw new Error('Application name must contain only lowercase letters, numbers, hyphens, and underscores');
|
|
39
|
+
}
|
|
40
|
+
const appPath = path.join(process.cwd(), 'integration', appName);
|
|
41
|
+
try {
|
|
42
|
+
await fs.access(appPath);
|
|
43
|
+
if (interactive) {
|
|
44
|
+
const { overwrite } = await require('inquirer').prompt([{
|
|
45
|
+
type: 'confirm', name: 'overwrite',
|
|
46
|
+
message: `Directory ${appPath} already exists. Overwrite?`, default: false
|
|
47
|
+
}]);
|
|
48
|
+
if (!overwrite) {
|
|
49
|
+
logger.log(chalk.yellow('Wizard cancelled.')); return false;
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
logger.log(chalk.yellow(`Warning: Directory ${appPath} exists. Overwriting...`));
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error.code !== 'ENOENT') throw error;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Extract session ID from response data
|
|
62
|
+
* @function extractSessionId
|
|
63
|
+
* @param {Object} responseData - Response data from API
|
|
64
|
+
* @returns {string} Session ID
|
|
65
|
+
*/
|
|
66
|
+
function extractSessionId(responseData) {
|
|
67
|
+
let sessionId = responseData?.data?.sessionId || responseData?.sessionId ||
|
|
68
|
+
responseData?.data?.session_id || responseData?.session_id;
|
|
69
|
+
if (sessionId && typeof sessionId === 'object') {
|
|
70
|
+
sessionId = sessionId.id || sessionId.sessionId || sessionId.session_id;
|
|
71
|
+
}
|
|
72
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
73
|
+
throw new Error(`Session ID not found: ${JSON.stringify(responseData, null, 2)}`);
|
|
74
|
+
}
|
|
75
|
+
return sessionId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Handle mode selection step - create wizard session
|
|
80
|
+
* @async
|
|
81
|
+
* @function handleModeSelection
|
|
82
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
83
|
+
* @param {Object} authConfig - Authentication configuration
|
|
84
|
+
* @param {string} [configMode] - Mode from config file
|
|
85
|
+
* @param {string} [systemIdOrKey] - System ID or key
|
|
86
|
+
* @returns {Promise<Object>} Object with mode and sessionId
|
|
87
|
+
*/
|
|
88
|
+
async function handleModeSelection(dataplaneUrl, authConfig, configMode = null, systemIdOrKey = null) {
|
|
89
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 1: Create Session'));
|
|
90
|
+
const mode = configMode || 'create-system';
|
|
91
|
+
const sessionResponse = await createWizardSession(dataplaneUrl, authConfig, mode, systemIdOrKey);
|
|
92
|
+
if (!sessionResponse.success || !sessionResponse.data) {
|
|
93
|
+
const errorMsg = sessionResponse.formattedError || sessionResponse.error ||
|
|
94
|
+
sessionResponse.errorData?.detail || 'Unknown error';
|
|
95
|
+
throw new Error(`Failed to create wizard session: ${errorMsg}`);
|
|
96
|
+
}
|
|
97
|
+
const sessionId = extractSessionId(sessionResponse.data);
|
|
98
|
+
logger.log(chalk.green(`\u2713 Session created: ${sessionId}`));
|
|
99
|
+
return { mode, sessionId };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Handle source selection step
|
|
104
|
+
* @async
|
|
105
|
+
* @function handleSourceSelection
|
|
106
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
107
|
+
* @param {string} sessionId - Wizard session ID
|
|
108
|
+
* @param {Object} authConfig - Authentication configuration
|
|
109
|
+
* @param {Object} [configSource] - Source config from wizard.yaml
|
|
110
|
+
* @returns {Promise<Object>} Object with sourceType and sourceData
|
|
111
|
+
*/
|
|
112
|
+
async function handleSourceSelection(dataplaneUrl, sessionId, authConfig, configSource = null) {
|
|
113
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 2: Parse OpenAPI'));
|
|
114
|
+
let sourceType, sourceData = null;
|
|
115
|
+
const updateData = { currentStep: 1 };
|
|
116
|
+
if (configSource) {
|
|
117
|
+
sourceType = configSource.type;
|
|
118
|
+
if (sourceType === 'openapi-file') sourceData = configSource.filePath;
|
|
119
|
+
else if (sourceType === 'openapi-url') {
|
|
120
|
+
sourceData = configSource.url;
|
|
121
|
+
updateData.openapiSpec = null;
|
|
122
|
+
} else if (sourceType === 'mcp-server') {
|
|
123
|
+
sourceData = JSON.stringify({ serverUrl: configSource.serverUrl, token: configSource.token });
|
|
124
|
+
updateData.mcpServerUrl = configSource.serverUrl;
|
|
125
|
+
} else if (sourceType === 'known-platform') sourceData = configSource.platform;
|
|
126
|
+
}
|
|
127
|
+
const updateResponse = await updateWizardSession(dataplaneUrl, sessionId, authConfig, updateData);
|
|
128
|
+
if (!updateResponse.success) {
|
|
129
|
+
throw new Error(`Source selection failed: ${updateResponse.error || updateResponse.formattedError}`);
|
|
130
|
+
}
|
|
131
|
+
return { sourceType, sourceData };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Parse OpenAPI file or URL
|
|
136
|
+
* @async
|
|
137
|
+
* @function parseOpenApiSource
|
|
138
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
139
|
+
* @param {Object} authConfig - Authentication configuration
|
|
140
|
+
* @param {string} sourceType - Source type (openapi-file or openapi-url)
|
|
141
|
+
* @param {string} sourceData - Source data (file path or URL)
|
|
142
|
+
* @returns {Promise<Object|null>} OpenAPI spec or null
|
|
143
|
+
*/
|
|
144
|
+
async function parseOpenApiSource(dataplaneUrl, authConfig, sourceType, sourceData) {
|
|
145
|
+
const isUrl = sourceType === 'openapi-url';
|
|
146
|
+
const spinner = ora(`Parsing OpenAPI ${isUrl ? 'URL' : 'file'}...`).start();
|
|
147
|
+
try {
|
|
148
|
+
const parseResponse = await parseOpenApi(dataplaneUrl, authConfig, sourceData, isUrl);
|
|
149
|
+
spinner.stop();
|
|
150
|
+
if (!parseResponse.success) {
|
|
151
|
+
throw new Error(`OpenAPI parsing failed: ${parseResponse.error || parseResponse.formattedError}`);
|
|
152
|
+
}
|
|
153
|
+
logger.log(chalk.green(`\u2713 OpenAPI ${isUrl ? 'URL' : 'file'} parsed successfully`));
|
|
154
|
+
return parseResponse.data?.spec;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
spinner.stop();
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Test MCP server connection
|
|
163
|
+
* @async
|
|
164
|
+
* @function testMcpServerConnection
|
|
165
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
166
|
+
* @param {Object} authConfig - Authentication configuration
|
|
167
|
+
* @param {string} sourceData - MCP server details JSON string
|
|
168
|
+
* @returns {Promise<null>} Always returns null
|
|
169
|
+
*/
|
|
170
|
+
async function testMcpServerConnection(dataplaneUrl, authConfig, sourceData) {
|
|
171
|
+
const mcpDetails = JSON.parse(sourceData);
|
|
172
|
+
const spinner = ora('Testing MCP server connection...').start();
|
|
173
|
+
try {
|
|
174
|
+
const testResponse = await testMcpConnection(dataplaneUrl, authConfig, mcpDetails.serverUrl, mcpDetails.token);
|
|
175
|
+
spinner.stop();
|
|
176
|
+
if (!testResponse.success || !testResponse.data?.connected) {
|
|
177
|
+
throw new Error(`MCP connection failed: ${testResponse.data?.error || 'Unable to connect'}`);
|
|
178
|
+
}
|
|
179
|
+
logger.log(chalk.green('\u2713 MCP server connection successful'));
|
|
180
|
+
} catch (error) {
|
|
181
|
+
spinner.stop();
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Handle OpenAPI parsing step
|
|
189
|
+
* @async
|
|
190
|
+
* @function handleOpenApiParsing
|
|
191
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
192
|
+
* @param {Object} authConfig - Authentication configuration
|
|
193
|
+
* @param {string} sourceType - Source type
|
|
194
|
+
* @param {string} sourceData - Source data
|
|
195
|
+
* @returns {Promise<Object|null>} OpenAPI spec or null
|
|
196
|
+
*/
|
|
197
|
+
async function handleOpenApiParsing(dataplaneUrl, authConfig, sourceType, sourceData) {
|
|
198
|
+
if (sourceType === 'openapi-file' || sourceType === 'openapi-url') {
|
|
199
|
+
return await parseOpenApiSource(dataplaneUrl, authConfig, sourceType, sourceData);
|
|
200
|
+
}
|
|
201
|
+
if (sourceType === 'mcp-server') {
|
|
202
|
+
return await testMcpServerConnection(dataplaneUrl, authConfig, sourceData);
|
|
203
|
+
}
|
|
204
|
+
if (sourceType === 'known-platform' && sourceData) {
|
|
205
|
+
const platformKey = String(sourceData).toUpperCase().replace(/[^A-Z0-9]/g, '_');
|
|
206
|
+
const filePath = process.env[`${platformKey}_OPENAPI_FILE`];
|
|
207
|
+
const url = process.env[`${platformKey}_OPENAPI_URL`];
|
|
208
|
+
if (filePath) return await parseOpenApiSource(dataplaneUrl, authConfig, 'openapi-file', filePath);
|
|
209
|
+
if (url) return await parseOpenApiSource(dataplaneUrl, authConfig, 'openapi-url', url);
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Handle credential selection step
|
|
216
|
+
* @async
|
|
217
|
+
* @function handleCredentialSelection
|
|
218
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
219
|
+
* @param {Object} authConfig - Authentication configuration
|
|
220
|
+
* @param {Object} [configCredential] - Credential config from wizard.yaml
|
|
221
|
+
* @returns {Promise<string|null>} Credential ID/key or null if skipped
|
|
222
|
+
*/
|
|
223
|
+
async function handleCredentialSelection(dataplaneUrl, authConfig, configCredential = null) {
|
|
224
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 3: Credential Selection (Optional)'));
|
|
225
|
+
const selectionData = configCredential ? {
|
|
226
|
+
action: configCredential.action,
|
|
227
|
+
credentialConfig: configCredential.config,
|
|
228
|
+
credentialIdOrKey: configCredential.credentialIdOrKey
|
|
229
|
+
} : { action: 'skip' };
|
|
230
|
+
if (selectionData.action === 'skip') {
|
|
231
|
+
logger.log(chalk.gray(' Skipping credential selection'));
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const spinner = ora('Processing credential selection...').start();
|
|
235
|
+
try {
|
|
236
|
+
const response = await credentialSelection(dataplaneUrl, authConfig, selectionData);
|
|
237
|
+
spinner.stop();
|
|
238
|
+
if (!response.success) {
|
|
239
|
+
logger.log(chalk.yellow(`Warning: Credential selection failed: ${response.error}`));
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const actionText = selectionData.action === 'create' ? 'created' : 'selected';
|
|
243
|
+
logger.log(chalk.green(`\u2713 Credential ${actionText}`));
|
|
244
|
+
return response.data?.credentialIdOrKey || null;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
spinner.stop();
|
|
247
|
+
logger.log(chalk.yellow(`Warning: Credential selection failed: ${error.message}`));
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Handle type detection step
|
|
254
|
+
* @async
|
|
255
|
+
* @function handleTypeDetection
|
|
256
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
257
|
+
* @param {Object} authConfig - Authentication configuration
|
|
258
|
+
* @param {Object} openapiSpec - OpenAPI specification
|
|
259
|
+
* @returns {Promise<Object|null>} Detected type or null
|
|
260
|
+
*/
|
|
261
|
+
async function handleTypeDetection(dataplaneUrl, authConfig, openapiSpec) {
|
|
262
|
+
if (!openapiSpec) return null;
|
|
263
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 4: Detect Type'));
|
|
264
|
+
const spinner = ora('Detecting API type...').start();
|
|
265
|
+
try {
|
|
266
|
+
const detectResponse = await detectType(dataplaneUrl, authConfig, openapiSpec);
|
|
267
|
+
spinner.stop();
|
|
268
|
+
if (detectResponse.success && detectResponse.data) {
|
|
269
|
+
const detectedType = detectResponse.data;
|
|
270
|
+
const recommendedType = detectedType.recommendedType || detectedType.apiType || 'unknown';
|
|
271
|
+
logger.log(chalk.green(`\u2713 API type detected: ${recommendedType}`));
|
|
272
|
+
return detectedType;
|
|
273
|
+
}
|
|
274
|
+
} catch (error) {
|
|
275
|
+
spinner.stop();
|
|
276
|
+
logger.log(chalk.yellow(`Warning: Type detection failed: ${error.message}`));
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Build configuration preferences from configPrefs object
|
|
283
|
+
* @function buildConfigPreferences
|
|
284
|
+
* @param {Object} [configPrefs] - Preferences from wizard.yaml
|
|
285
|
+
* @returns {Object} Configuration preferences object
|
|
286
|
+
*/
|
|
287
|
+
function buildConfigPreferences(configPrefs) {
|
|
288
|
+
return {
|
|
289
|
+
intent: configPrefs?.intent || 'general integration',
|
|
290
|
+
fieldOnboardingLevel: configPrefs?.fieldOnboardingLevel || 'full',
|
|
291
|
+
enableOpenAPIGeneration: configPrefs?.enableOpenAPIGeneration !== false,
|
|
292
|
+
userPreferences: {
|
|
293
|
+
enableMCP: configPrefs?.enableMCP || false,
|
|
294
|
+
enableABAC: configPrefs?.enableABAC || false,
|
|
295
|
+
enableRBAC: configPrefs?.enableRBAC || false
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Build configuration payload for API call
|
|
302
|
+
* @function buildConfigPayload
|
|
303
|
+
* @param {Object} params - Parameters object
|
|
304
|
+
* @param {Object} params.openapiSpec - OpenAPI specification
|
|
305
|
+
* @param {Object} params.detectedType - Detected type info
|
|
306
|
+
* @param {string} params.mode - Selected mode
|
|
307
|
+
* @param {Object} params.prefs - Configuration preferences
|
|
308
|
+
* @param {string} [params.credentialIdOrKey] - Credential ID or key
|
|
309
|
+
* @param {string} [params.systemIdOrKey] - System ID or key
|
|
310
|
+
* @returns {Object} Configuration payload
|
|
311
|
+
*/
|
|
312
|
+
function buildConfigPayload({ openapiSpec, detectedType, mode, prefs, credentialIdOrKey, systemIdOrKey }) {
|
|
313
|
+
const detectedTypeValue = detectedType?.recommendedType || detectedType?.apiType || detectedType?.selectedType || 'record-based';
|
|
314
|
+
const payload = {
|
|
315
|
+
openapiSpec,
|
|
316
|
+
detectedType: detectedTypeValue,
|
|
317
|
+
intent: prefs.intent,
|
|
318
|
+
mode,
|
|
319
|
+
fieldOnboardingLevel: prefs.fieldOnboardingLevel,
|
|
320
|
+
enableOpenAPIGeneration: prefs.enableOpenAPIGeneration,
|
|
321
|
+
userPreferences: prefs.userPreferences
|
|
322
|
+
};
|
|
323
|
+
if (credentialIdOrKey) payload.credentialIdOrKey = credentialIdOrKey;
|
|
324
|
+
if (systemIdOrKey) payload.systemIdOrKey = systemIdOrKey;
|
|
325
|
+
return payload;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Extract configuration from API response
|
|
330
|
+
* @function extractConfigurationFromResponse
|
|
331
|
+
* @param {Object} generateResponse - API response
|
|
332
|
+
* @returns {Object} Extracted configuration
|
|
333
|
+
*/
|
|
334
|
+
function extractConfigurationFromResponse(generateResponse) {
|
|
335
|
+
const systemConfig = generateResponse.data?.systemConfig;
|
|
336
|
+
const datasourceConfigs = generateResponse.data?.datasourceConfigs ||
|
|
337
|
+
(generateResponse.data?.datasourceConfig ? [generateResponse.data.datasourceConfig] : []);
|
|
338
|
+
if (!systemConfig) throw new Error('System configuration not found');
|
|
339
|
+
return { systemConfig, datasourceConfigs, systemKey: generateResponse.data?.systemKey };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Handle configuration generation step
|
|
344
|
+
* @async
|
|
345
|
+
* @function handleConfigurationGeneration
|
|
346
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
347
|
+
* @param {Object} authConfig - Authentication configuration
|
|
348
|
+
* @param {Object} options - Configuration options
|
|
349
|
+
* @param {string} options.mode - Selected mode
|
|
350
|
+
* @param {Object} options.openapiSpec - OpenAPI specification
|
|
351
|
+
* @param {Object} options.detectedType - Detected type info
|
|
352
|
+
* @param {Object} [options.configPrefs] - Preferences from wizard.yaml
|
|
353
|
+
* @param {string} [options.credentialIdOrKey] - Credential ID or key (optional)
|
|
354
|
+
* @param {string} [options.systemIdOrKey] - System ID or key (optional)
|
|
355
|
+
* @returns {Promise<Object>} Generated configuration
|
|
356
|
+
*/
|
|
357
|
+
async function handleConfigurationGeneration(dataplaneUrl, authConfig, options) {
|
|
358
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 5: Generate Configuration'));
|
|
359
|
+
const prefs = buildConfigPreferences(options.configPrefs);
|
|
360
|
+
const spinner = ora('Generating configuration via AI (10-30 seconds)...').start();
|
|
361
|
+
try {
|
|
362
|
+
const configPayload = buildConfigPayload({
|
|
363
|
+
openapiSpec: options.openapiSpec,
|
|
364
|
+
detectedType: options.detectedType,
|
|
365
|
+
mode: options.mode,
|
|
366
|
+
prefs,
|
|
367
|
+
credentialIdOrKey: options.credentialIdOrKey,
|
|
368
|
+
systemIdOrKey: options.systemIdOrKey
|
|
369
|
+
});
|
|
370
|
+
const generateResponse = await generateConfig(dataplaneUrl, authConfig, configPayload);
|
|
371
|
+
spinner.stop();
|
|
372
|
+
if (!generateResponse.success) {
|
|
373
|
+
throw new Error(`Configuration generation failed: ${generateResponse.error || generateResponse.formattedError}`);
|
|
374
|
+
}
|
|
375
|
+
const result = extractConfigurationFromResponse(generateResponse);
|
|
376
|
+
const normalized = normalizeWizardConfigs(result.systemConfig, result.datasourceConfigs);
|
|
377
|
+
logger.log(chalk.green('\u2713 Configuration generated successfully'));
|
|
378
|
+
return {
|
|
379
|
+
systemConfig: normalized.systemConfig,
|
|
380
|
+
datasourceConfigs: normalized.datasourceConfigs,
|
|
381
|
+
systemKey: result.systemKey
|
|
382
|
+
};
|
|
383
|
+
} catch (error) {
|
|
384
|
+
spinner.stop();
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Validate wizard configuration
|
|
391
|
+
* @async
|
|
392
|
+
* @function validateWizardConfiguration
|
|
393
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
394
|
+
* @param {Object} authConfig - Authentication configuration
|
|
395
|
+
* @param {Object} systemConfig - System configuration
|
|
396
|
+
* @param {Object[]} datasourceConfigs - Datasource configurations
|
|
397
|
+
*/
|
|
398
|
+
// eslint-disable-next-line max-statements
|
|
399
|
+
async function validateWizardConfiguration(dataplaneUrl, authConfig, systemConfig, datasourceConfigs) {
|
|
400
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 6: Validate Configuration'));
|
|
401
|
+
const spinner = ora('Validating configuration...').start();
|
|
402
|
+
try {
|
|
403
|
+
const configs = Array.isArray(datasourceConfigs) ? datasourceConfigs : [datasourceConfigs];
|
|
404
|
+
const warnings = [];
|
|
405
|
+
for (const datasourceConfig of configs) {
|
|
406
|
+
const validateResponse = await validateWizardConfig(dataplaneUrl, authConfig, systemConfig, datasourceConfig);
|
|
407
|
+
const isValid = validateResponse.success && (validateResponse.data?.valid || validateResponse.data?.isValid);
|
|
408
|
+
if (!isValid) {
|
|
409
|
+
const errors = validateResponse.data?.errors || validateResponse.errorData?.errors || [];
|
|
410
|
+
const errorDetail = validateResponse.errorData?.detail || validateResponse.errorData?.message;
|
|
411
|
+
const errorMsg = errors.length > 0 ? errors.map(e => e.message || e).join(', ') : errorDetail || validateResponse.error || 'Validation failed';
|
|
412
|
+
spinner.stop();
|
|
413
|
+
throw new Error(`Configuration validation failed: ${errorMsg}`);
|
|
414
|
+
}
|
|
415
|
+
if (validateResponse.data?.warnings?.length > 0) warnings.push(...validateResponse.data.warnings);
|
|
416
|
+
}
|
|
417
|
+
spinner.stop();
|
|
418
|
+
logger.log(chalk.green('\u2713 Configuration validated successfully'));
|
|
419
|
+
if (warnings.length > 0) {
|
|
420
|
+
logger.log(chalk.yellow('\n\u26A0 Warnings:'));
|
|
421
|
+
warnings.forEach(w => logger.log(chalk.yellow(` - ${w.message || w}`)));
|
|
422
|
+
}
|
|
423
|
+
} catch (error) {
|
|
424
|
+
spinner.stop();
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Handle file saving step
|
|
431
|
+
* @async
|
|
432
|
+
* @function handleFileSaving
|
|
433
|
+
* @param {string} appName - Application name
|
|
434
|
+
* @param {Object} systemConfig - System configuration
|
|
435
|
+
* @param {Object[]} datasourceConfigs - Datasource configurations
|
|
436
|
+
* @param {string} systemKey - System key
|
|
437
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
438
|
+
* @param {Object} authConfig - Authentication configuration
|
|
439
|
+
* @returns {Promise<Object>} Generated files information
|
|
440
|
+
*/
|
|
441
|
+
async function handleFileSaving(appName, systemConfig, datasourceConfigs, systemKey, dataplaneUrl, authConfig) {
|
|
442
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 7: Save Files'));
|
|
443
|
+
const spinner = ora('Saving files...').start();
|
|
444
|
+
try {
|
|
445
|
+
let aiGeneratedReadme = null;
|
|
446
|
+
if (systemKey && dataplaneUrl && authConfig) {
|
|
447
|
+
try {
|
|
448
|
+
const docsResponse = await getDeploymentDocs(dataplaneUrl, authConfig, systemKey);
|
|
449
|
+
if (docsResponse.success && docsResponse.data?.content) aiGeneratedReadme = docsResponse.data.content;
|
|
450
|
+
} catch (e) {
|
|
451
|
+
logger.log(chalk.gray(` Could not fetch AI-generated README: ${e.message}`));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const generatedFiles = await generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, { aiGeneratedReadme });
|
|
455
|
+
spinner.stop();
|
|
456
|
+
logger.log(chalk.green('\n\u2713 Wizard completed successfully!'));
|
|
457
|
+
logger.log(chalk.green(`\nFiles created in: ${generatedFiles.appPath}`));
|
|
458
|
+
logger.log(chalk.blue('\nNext steps:'));
|
|
459
|
+
logger.log(chalk.gray(` 1. Review the generated files in integration/${appName}/`));
|
|
460
|
+
logger.log(chalk.gray(' 2. Update env.template with your authentication details'));
|
|
461
|
+
logger.log(chalk.gray(` 3. Deploy using: ./deploy.sh or aifabrix deploy ${appName}`));
|
|
462
|
+
return generatedFiles;
|
|
463
|
+
} catch (error) {
|
|
464
|
+
spinner.stop();
|
|
465
|
+
throw error;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Setup dataplane URL and authentication
|
|
471
|
+
* @async
|
|
472
|
+
* @function setupDataplaneAndAuth
|
|
473
|
+
* @param {Object} options - Command options
|
|
474
|
+
* @param {string} appName - Application name
|
|
475
|
+
* @returns {Promise<Object>} Object with dataplaneUrl and authConfig
|
|
476
|
+
*/
|
|
477
|
+
async function setupDataplaneAndAuth(options, appName) {
|
|
478
|
+
const { resolveEnvironment } = require('../core/config');
|
|
479
|
+
const environment = await resolveEnvironment();
|
|
480
|
+
const controllerUrl = await resolveControllerUrl();
|
|
481
|
+
let authConfig;
|
|
482
|
+
try {
|
|
483
|
+
// For wizard mode creating new external systems, use device-only auth
|
|
484
|
+
// since the app doesn't exist yet. Device token is sufficient for
|
|
485
|
+
// discovering the dataplane URL and running the wizard.
|
|
486
|
+
authConfig = await getDeviceOnlyAuth(controllerUrl);
|
|
487
|
+
} catch (error) {
|
|
488
|
+
// Fallback to getDeploymentAuth if device-only auth fails
|
|
489
|
+
// (e.g., for add-datasource mode where app might exist)
|
|
490
|
+
try {
|
|
491
|
+
authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
|
|
492
|
+
} catch (fallbackError) {
|
|
493
|
+
throw new Error(`Authentication failed: ${error.message}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
497
|
+
let dataplaneUrl;
|
|
498
|
+
try {
|
|
499
|
+
dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
throw new Error(
|
|
502
|
+
`${error.message}\n\n` +
|
|
503
|
+
'The dataplane URL is automatically discovered from the controller.\n' +
|
|
504
|
+
'If discovery fails, ensure you are logged in and the controller is accessible:\n' +
|
|
505
|
+
' aifabrix login'
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
return { dataplaneUrl, authConfig };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
module.exports = {
|
|
512
|
+
validateAndCheckAppDirectory, extractSessionId, handleModeSelection, handleSourceSelection, handleOpenApiParsing,
|
|
513
|
+
handleCredentialSelection, handleTypeDetection, handleConfigurationGeneration, validateWizardConfiguration,
|
|
514
|
+
handleFileSaving, setupDataplaneAndAuth
|
|
515
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Wizard dataplane URL discovery utilities
|
|
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 { getDataplaneUrl } = require('../datasource/deploy');
|
|
10
|
+
const { listEnvironmentApplications } = require('../api/environments.api');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Find dataplane service application key from environment applications list
|
|
14
|
+
* @async
|
|
15
|
+
* @function findDataplaneServiceAppKey
|
|
16
|
+
* @param {string} controllerUrl - Controller URL
|
|
17
|
+
* @param {string} environment - Environment key
|
|
18
|
+
* @param {Object} authConfig - Authentication configuration
|
|
19
|
+
* @returns {Promise<string|null>} Dataplane service appKey or null if not found
|
|
20
|
+
*/
|
|
21
|
+
// eslint-disable-next-line complexity
|
|
22
|
+
async function findDataplaneServiceAppKey(controllerUrl, environment, authConfig) {
|
|
23
|
+
try {
|
|
24
|
+
const response = await listEnvironmentApplications(controllerUrl, environment, authConfig, { pageSize: 100 });
|
|
25
|
+
if (!response.success || !response.data) return null;
|
|
26
|
+
const applications = response.data.data || response.data || [];
|
|
27
|
+
for (const app of applications) {
|
|
28
|
+
const appKey = app.key || app.id;
|
|
29
|
+
if (!appKey) continue;
|
|
30
|
+
const keyLower = appKey.toLowerCase();
|
|
31
|
+
const appType = app.configuration?.type || app.type;
|
|
32
|
+
const nameLower = (app.displayName || app.name || '').toLowerCase();
|
|
33
|
+
if (keyLower === 'dataplane' || keyLower.includes('dataplane') || (appType === 'service' && nameLower.includes('dataplane'))) {
|
|
34
|
+
return appKey;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
logger.log(chalk.gray(` Could not list applications: ${error.message}`));
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if error is a "Not Found" error
|
|
46
|
+
* @param {Error} error - Error to check
|
|
47
|
+
* @returns {boolean} True if error is a "Not Found" error
|
|
48
|
+
*/
|
|
49
|
+
function isNotFoundError(error) {
|
|
50
|
+
return error.message.includes('Not Found') ||
|
|
51
|
+
error.message.includes('not found') ||
|
|
52
|
+
error.message.includes('Application not found');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create error message for missing dataplane service
|
|
57
|
+
* @returns {Error} Error with helpful message
|
|
58
|
+
*/
|
|
59
|
+
function createDataplaneNotFoundError() {
|
|
60
|
+
return new Error(
|
|
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.'
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Try to get dataplane URL using fallback app key
|
|
68
|
+
* @async
|
|
69
|
+
* @param {string} controllerUrl - Controller URL
|
|
70
|
+
* @param {string} environment - Environment key
|
|
71
|
+
* @param {Object} authConfig - Authentication configuration
|
|
72
|
+
* @returns {Promise<string>} Dataplane URL
|
|
73
|
+
* @throws {Error} If dataplane URL cannot be retrieved
|
|
74
|
+
*/
|
|
75
|
+
async function tryFallbackDataplaneUrl(controllerUrl, environment, authConfig) {
|
|
76
|
+
try {
|
|
77
|
+
const fallbackUrl = await getDataplaneUrl(controllerUrl, 'dataplane', environment, authConfig);
|
|
78
|
+
logger.log(chalk.green(`\u2713 Dataplane URL: ${fallbackUrl}`));
|
|
79
|
+
return fallbackUrl;
|
|
80
|
+
} catch (fallbackError) {
|
|
81
|
+
if (isNotFoundError(fallbackError)) {
|
|
82
|
+
throw createDataplaneNotFoundError();
|
|
83
|
+
}
|
|
84
|
+
throw fallbackError;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Discover dataplane URL from controller
|
|
90
|
+
* @async
|
|
91
|
+
* @function discoverDataplaneUrl
|
|
92
|
+
* @param {string} controllerUrl - Controller URL
|
|
93
|
+
* @param {string} environment - Environment key
|
|
94
|
+
* @param {Object} authConfig - Authentication configuration
|
|
95
|
+
* @returns {Promise<string>} Dataplane URL
|
|
96
|
+
* @throws {Error} If dataplane URL cannot be discovered
|
|
97
|
+
*/
|
|
98
|
+
async function discoverDataplaneUrl(controllerUrl, environment, authConfig) {
|
|
99
|
+
logger.log(chalk.blue('\uD83C\uDF10 Getting dataplane URL from controller...'));
|
|
100
|
+
try {
|
|
101
|
+
const dataplaneAppKey = await findDataplaneServiceAppKey(controllerUrl, environment, authConfig);
|
|
102
|
+
if (dataplaneAppKey) {
|
|
103
|
+
const dataplaneUrl = await getDataplaneUrl(controllerUrl, dataplaneAppKey, environment, authConfig);
|
|
104
|
+
logger.log(chalk.green(`\u2713 Dataplane URL: ${dataplaneUrl}`));
|
|
105
|
+
return dataplaneUrl;
|
|
106
|
+
}
|
|
107
|
+
return await tryFallbackDataplaneUrl(controllerUrl, environment, authConfig);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (error.message.includes('Could not discover dataplane URL')) {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
if (isNotFoundError(error) && error.message.includes('Failed to get application')) {
|
|
113
|
+
throw createDataplaneNotFoundError();
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`Failed to discover dataplane URL: ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = {
|
|
120
|
+
discoverDataplaneUrl,
|
|
121
|
+
findDataplaneServiceAppKey
|
|
122
|
+
};
|