@aifabrix/builder 2.32.3 → 2.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/project-rules.mdc +8 -0
- package/README.md +36 -8
- package/bin/aifabrix.js +6 -8
- package/integration/hubspot/README.md +8 -7
- 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/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 +29 -21
- package/lib/datasource/list.js +8 -6
- 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 +32 -50
- 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/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/infra/compose.yaml.hbs +35 -0
- package/templates/python/docker-compose.hbs +26 -0
- package/templates/typescript/docker-compose.hbs +26 -0
package/lib/commands/wizard.js
CHANGED
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Wizard command handler - interactive external system creation
|
|
2
|
+
* @fileoverview Wizard command handler - interactive and headless external system creation
|
|
3
3
|
* @author AI Fabrix Team
|
|
4
4
|
* @version 2.0.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const chalk = require('chalk');
|
|
8
|
-
const ora = require('ora');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const fs = require('fs').promises;
|
|
11
8
|
const logger = require('../utils/logger');
|
|
12
|
-
const config = require('../core/config');
|
|
13
|
-
const { getDeviceOnlyAuth } = require('../utils/token-manager');
|
|
14
|
-
const { getDataplaneUrl } = require('../datasource/deploy');
|
|
15
|
-
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
16
|
-
const {
|
|
17
|
-
createWizardSession,
|
|
18
|
-
updateWizardSession,
|
|
19
|
-
parseOpenApi,
|
|
20
|
-
detectType,
|
|
21
|
-
generateConfig,
|
|
22
|
-
validateWizardConfig,
|
|
23
|
-
getDeploymentDocs
|
|
24
|
-
} = require('../api/wizard.api');
|
|
25
9
|
const {
|
|
26
10
|
promptForMode,
|
|
11
|
+
promptForSystemIdOrKey,
|
|
27
12
|
promptForSourceType,
|
|
28
13
|
promptForOpenApiFile,
|
|
29
14
|
promptForOpenApiUrl,
|
|
@@ -34,99 +19,90 @@ const {
|
|
|
34
19
|
promptForConfigReview,
|
|
35
20
|
promptForAppName
|
|
36
21
|
} = require('../generator/wizard-prompts');
|
|
37
|
-
const {
|
|
22
|
+
const {
|
|
23
|
+
validateAndCheckAppDirectory,
|
|
24
|
+
handleOpenApiParsing,
|
|
25
|
+
handleCredentialSelection,
|
|
26
|
+
handleTypeDetection,
|
|
27
|
+
handleConfigurationGeneration,
|
|
28
|
+
validateWizardConfiguration,
|
|
29
|
+
handleFileSaving,
|
|
30
|
+
setupDataplaneAndAuth
|
|
31
|
+
} = require('./wizard-core');
|
|
32
|
+
const { handleWizardHeadless } = require('./wizard-headless');
|
|
33
|
+
const { createWizardSession, updateWizardSession } = require('../api/wizard.api');
|
|
38
34
|
|
|
39
35
|
/**
|
|
40
|
-
*
|
|
41
|
-
* @
|
|
42
|
-
* @
|
|
43
|
-
* @
|
|
44
|
-
* @
|
|
45
|
-
* @throws {Error} If validation fails
|
|
36
|
+
* Extract session ID from response data
|
|
37
|
+
* @function extractSessionId
|
|
38
|
+
* @param {Object} responseData - Response data from API
|
|
39
|
+
* @returns {string} Session ID
|
|
40
|
+
* @throws {Error} If session ID not found or invalid
|
|
46
41
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
function extractSessionId(responseData) {
|
|
43
|
+
let sessionId = responseData?.data?.sessionId || responseData?.sessionId ||
|
|
44
|
+
responseData?.data?.session_id || responseData?.session_id;
|
|
45
|
+
if (sessionId && typeof sessionId === 'object') {
|
|
46
|
+
sessionId = sessionId.id || sessionId.sessionId || sessionId.session_id;
|
|
50
47
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
await fs.access(appPath);
|
|
54
|
-
const { overwrite } = await require('inquirer').prompt([{
|
|
55
|
-
type: 'confirm',
|
|
56
|
-
name: 'overwrite',
|
|
57
|
-
message: `Directory ${appPath} already exists. Overwrite?`,
|
|
58
|
-
default: false
|
|
59
|
-
}]);
|
|
60
|
-
if (!overwrite) {
|
|
61
|
-
logger.log(chalk.yellow('Wizard cancelled.'));
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
} catch (error) {
|
|
65
|
-
if (error.code !== 'ENOENT') {
|
|
66
|
-
throw error;
|
|
67
|
-
}
|
|
48
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
49
|
+
throw new Error(`Session ID not found: ${JSON.stringify(responseData, null, 2)}`);
|
|
68
50
|
}
|
|
69
|
-
return
|
|
51
|
+
return sessionId;
|
|
70
52
|
}
|
|
71
53
|
|
|
72
54
|
/**
|
|
73
|
-
* Handle mode selection step
|
|
55
|
+
* Handle interactive mode selection step
|
|
74
56
|
* @async
|
|
75
|
-
* @function
|
|
57
|
+
* @function handleInteractiveModeSelection
|
|
76
58
|
* @param {string} dataplaneUrl - Dataplane URL
|
|
77
59
|
* @param {Object} authConfig - Authentication configuration
|
|
78
60
|
* @returns {Promise<Object>} Object with mode and sessionId
|
|
79
|
-
* @throws {Error} If mode selection fails
|
|
80
61
|
*/
|
|
81
|
-
async function
|
|
82
|
-
logger.log(chalk.blue('\n
|
|
62
|
+
async function handleInteractiveModeSelection(dataplaneUrl, authConfig) {
|
|
63
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 1: Mode Selection'));
|
|
83
64
|
const mode = await promptForMode();
|
|
84
|
-
|
|
65
|
+
let systemIdOrKey = null;
|
|
66
|
+
if (mode === 'add-datasource') {
|
|
67
|
+
systemIdOrKey = await promptForSystemIdOrKey();
|
|
68
|
+
}
|
|
69
|
+
const sessionResponse = await createWizardSession(dataplaneUrl, authConfig, mode, systemIdOrKey);
|
|
85
70
|
if (!sessionResponse.success || !sessionResponse.data) {
|
|
86
|
-
const errorMsg = sessionResponse.formattedError ||
|
|
87
|
-
sessionResponse.
|
|
88
|
-
sessionResponse.errorData?.detail ||
|
|
89
|
-
sessionResponse.message ||
|
|
71
|
+
const errorMsg = sessionResponse.formattedError || sessionResponse.error ||
|
|
72
|
+
sessionResponse.errorData?.detail || sessionResponse.message ||
|
|
90
73
|
(sessionResponse.status ? `HTTP ${sessionResponse.status}` : 'Unknown error');
|
|
91
74
|
throw new Error(`Failed to create wizard session: ${errorMsg}`);
|
|
92
75
|
}
|
|
93
|
-
const sessionId = sessionResponse.data
|
|
94
|
-
|
|
95
|
-
throw new Error('Session ID not found in response');
|
|
96
|
-
}
|
|
97
|
-
return { mode, sessionId };
|
|
76
|
+
const sessionId = extractSessionId(sessionResponse.data);
|
|
77
|
+
return { mode, sessionId, systemIdOrKey };
|
|
98
78
|
}
|
|
99
79
|
|
|
100
80
|
/**
|
|
101
|
-
* Handle source selection step
|
|
81
|
+
* Handle interactive source selection step
|
|
102
82
|
* @async
|
|
103
|
-
* @function
|
|
83
|
+
* @function handleInteractiveSourceSelection
|
|
104
84
|
* @param {string} dataplaneUrl - Dataplane URL
|
|
105
85
|
* @param {string} sessionId - Wizard session ID
|
|
106
86
|
* @param {Object} authConfig - Authentication configuration
|
|
107
87
|
* @returns {Promise<Object>} Object with sourceType and sourceData
|
|
108
|
-
* @throws {Error} If source selection fails
|
|
109
88
|
*/
|
|
110
|
-
async function
|
|
111
|
-
logger.log(chalk.blue('\n
|
|
89
|
+
async function handleInteractiveSourceSelection(dataplaneUrl, sessionId, authConfig) {
|
|
90
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 2: Source Selection'));
|
|
112
91
|
const sourceType = await promptForSourceType();
|
|
113
92
|
let sourceData = null;
|
|
114
93
|
const updateData = { currentStep: 1 };
|
|
115
94
|
|
|
116
95
|
if (sourceType === 'openapi-file') {
|
|
117
|
-
|
|
118
|
-
sourceData = filePath;
|
|
96
|
+
sourceData = await promptForOpenApiFile();
|
|
119
97
|
} else if (sourceType === 'openapi-url') {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
updateData.openapiSpec = null; // Will be set after parsing
|
|
98
|
+
sourceData = await promptForOpenApiUrl();
|
|
99
|
+
updateData.openapiSpec = null;
|
|
123
100
|
} else if (sourceType === 'mcp-server') {
|
|
124
101
|
const mcpDetails = await promptForMcpServer();
|
|
125
102
|
sourceData = JSON.stringify(mcpDetails);
|
|
126
103
|
updateData.mcpServerUrl = mcpDetails.url || null;
|
|
127
104
|
} else if (sourceType === 'known-platform') {
|
|
128
|
-
|
|
129
|
-
sourceData = platform;
|
|
105
|
+
sourceData = await promptForKnownPlatform();
|
|
130
106
|
}
|
|
131
107
|
|
|
132
108
|
const updateResponse = await updateWizardSession(dataplaneUrl, sessionId, authConfig, updateData);
|
|
@@ -138,185 +114,53 @@ async function handleSourceSelection(dataplaneUrl, sessionId, authConfig) {
|
|
|
138
114
|
}
|
|
139
115
|
|
|
140
116
|
/**
|
|
141
|
-
*
|
|
142
|
-
* @async
|
|
143
|
-
* @function parseOpenApiFile
|
|
144
|
-
* @param {string} dataplaneUrl - Dataplane URL
|
|
145
|
-
* @param {Object} authConfig - Authentication configuration
|
|
146
|
-
* @param {string} sourceData - Source data (file path)
|
|
147
|
-
* @returns {Promise<Object>} OpenAPI spec
|
|
148
|
-
* @throws {Error} If parsing fails
|
|
149
|
-
*/
|
|
150
|
-
async function parseOpenApiFile(dataplaneUrl, authConfig, sourceData) {
|
|
151
|
-
logger.log(chalk.blue('\n📋 Step 3: Parsing OpenAPI File'));
|
|
152
|
-
const spinner = ora('Parsing OpenAPI file...').start();
|
|
153
|
-
try {
|
|
154
|
-
const parseResponse = await parseOpenApi(dataplaneUrl, authConfig, sourceData);
|
|
155
|
-
spinner.stop();
|
|
156
|
-
if (!parseResponse.success) {
|
|
157
|
-
throw new Error(`OpenAPI parsing failed: ${parseResponse.error || parseResponse.formattedError}`);
|
|
158
|
-
}
|
|
159
|
-
logger.log(chalk.green('✓ OpenAPI file parsed successfully'));
|
|
160
|
-
return parseResponse.data?.spec;
|
|
161
|
-
} catch (error) {
|
|
162
|
-
spinner.stop();
|
|
163
|
-
throw error;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Handle OpenAPI parsing step
|
|
117
|
+
* Handle interactive configuration generation step
|
|
169
118
|
* @async
|
|
170
|
-
* @function
|
|
171
|
-
* @param {
|
|
172
|
-
* @param {
|
|
173
|
-
* @param {
|
|
174
|
-
* @param {string}
|
|
175
|
-
* @
|
|
176
|
-
* @
|
|
119
|
+
* @function handleInteractiveConfigGeneration
|
|
120
|
+
* @param {Object} options - Configuration options
|
|
121
|
+
* @param {string} options.dataplaneUrl - Dataplane URL
|
|
122
|
+
* @param {Object} options.authConfig - Authentication configuration
|
|
123
|
+
* @param {string} options.mode - Selected mode
|
|
124
|
+
* @param {Object} options.openapiSpec - OpenAPI specification
|
|
125
|
+
* @param {Object} options.detectedType - Detected type info
|
|
126
|
+
* @param {string} [options.credentialIdOrKey] - Credential ID or key (optional)
|
|
127
|
+
* @param {string} [options.systemIdOrKey] - System ID or key (optional)
|
|
128
|
+
* @returns {Promise<Object>} Generated configuration
|
|
177
129
|
*/
|
|
178
|
-
async function
|
|
179
|
-
|
|
180
|
-
return await parseOpenApiFile(dataplaneUrl, authConfig, sourceData);
|
|
181
|
-
}
|
|
182
|
-
if (sourceType === 'openapi-url') {
|
|
183
|
-
logger.log(chalk.blue('\n📋 Step 3: Parsing OpenAPI URL'));
|
|
184
|
-
logger.log(chalk.green('✓ OpenAPI URL processed'));
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Handle type detection step
|
|
192
|
-
* @async
|
|
193
|
-
* @function handleTypeDetection
|
|
194
|
-
* @param {string} dataplaneUrl - Dataplane URL
|
|
195
|
-
* @param {Object} authConfig - Authentication configuration
|
|
196
|
-
* @param {Object} openApiSpec - OpenAPI specification
|
|
197
|
-
* @returns {Promise<Object|null>} Detected type or null
|
|
198
|
-
*/
|
|
199
|
-
async function handleTypeDetection(dataplaneUrl, authConfig, openApiSpec) {
|
|
200
|
-
if (!openApiSpec) {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
logger.log(chalk.blue('\n📋 Step 4: Detecting API Type'));
|
|
205
|
-
const spinner = ora('Detecting API type...').start();
|
|
206
|
-
try {
|
|
207
|
-
const detectResponse = await detectType(dataplaneUrl, authConfig, openApiSpec);
|
|
208
|
-
spinner.stop();
|
|
209
|
-
if (detectResponse.success && detectResponse.data) {
|
|
210
|
-
const detectedType = detectResponse.data;
|
|
211
|
-
logger.log(chalk.green(`✓ API type detected: ${detectedType.apiType || 'unknown'}`));
|
|
212
|
-
if (detectedType.category) {
|
|
213
|
-
logger.log(chalk.gray(` Category: ${detectedType.category}`));
|
|
214
|
-
}
|
|
215
|
-
return detectedType;
|
|
216
|
-
}
|
|
217
|
-
} catch (error) {
|
|
218
|
-
spinner.stop();
|
|
219
|
-
logger.log(chalk.yellow(`⚠ Type detection failed: ${error.message}`));
|
|
220
|
-
}
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Handle configuration generation step
|
|
226
|
-
* @async
|
|
227
|
-
* @function handleConfigurationGeneration
|
|
228
|
-
* @param {string} dataplaneUrl - Dataplane URL
|
|
229
|
-
* @param {Object} authConfig - Authentication configuration
|
|
230
|
-
* @param {string} mode - Selected mode
|
|
231
|
-
* @param {string} sourceType - Source type
|
|
232
|
-
* @param {Object} openApiSpec - OpenAPI specification
|
|
233
|
-
* @returns {Promise<Object>} Generated configuration with systemConfig, datasourceConfigs, and systemKey
|
|
234
|
-
* @throws {Error} If generation fails
|
|
235
|
-
*/
|
|
236
|
-
async function handleConfigurationGeneration(dataplaneUrl, authConfig, mode, sourceType, openApiSpec) {
|
|
237
|
-
logger.log(chalk.blue('\n📋 Step 5: User Preferences'));
|
|
130
|
+
async function handleInteractiveConfigGeneration(options) {
|
|
131
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 5: User Preferences'));
|
|
238
132
|
const userIntent = await promptForUserIntent();
|
|
239
133
|
const preferences = await promptForUserPreferences();
|
|
240
134
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const systemConfig = generateResponse.data?.systemConfig;
|
|
257
|
-
const datasourceConfigs = generateResponse.data?.datasourceConfigs || [];
|
|
258
|
-
const systemKey = generateResponse.data?.systemKey;
|
|
259
|
-
|
|
260
|
-
if (!systemConfig) {
|
|
261
|
-
throw new Error('System configuration not found in generation response');
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
logger.log(chalk.green('✓ Configuration generated successfully'));
|
|
265
|
-
return { systemConfig, datasourceConfigs, systemKey };
|
|
266
|
-
} catch (error) {
|
|
267
|
-
spinner.stop();
|
|
268
|
-
throw error;
|
|
269
|
-
}
|
|
135
|
+
const configPrefs = {
|
|
136
|
+
intent: userIntent,
|
|
137
|
+
enableMCP: preferences.mcp,
|
|
138
|
+
enableABAC: preferences.abac,
|
|
139
|
+
enableRBAC: preferences.rbac
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return await handleConfigurationGeneration(options.dataplaneUrl, options.authConfig, {
|
|
143
|
+
mode: options.mode,
|
|
144
|
+
openapiSpec: options.openapiSpec,
|
|
145
|
+
detectedType: options.detectedType,
|
|
146
|
+
configPrefs,
|
|
147
|
+
credentialIdOrKey: options.credentialIdOrKey,
|
|
148
|
+
systemIdOrKey: options.systemIdOrKey
|
|
149
|
+
});
|
|
270
150
|
}
|
|
271
151
|
|
|
272
152
|
/**
|
|
273
|
-
*
|
|
274
|
-
* @async
|
|
275
|
-
* @function validateWizardConfiguration
|
|
276
|
-
* @param {string} dataplaneUrl - Dataplane URL
|
|
277
|
-
* @param {Object} authConfig - Authentication configuration
|
|
278
|
-
* @param {Object} systemConfig - System configuration
|
|
279
|
-
* @param {Object[]} datasourceConfigs - Datasource configurations
|
|
280
|
-
* @throws {Error} If validation fails
|
|
281
|
-
*/
|
|
282
|
-
async function validateWizardConfiguration(dataplaneUrl, authConfig, systemConfig, datasourceConfigs) {
|
|
283
|
-
const validateSpinner = ora('Validating configuration...').start();
|
|
284
|
-
try {
|
|
285
|
-
const validateResponse = await validateWizardConfig(dataplaneUrl, authConfig, systemConfig, datasourceConfigs);
|
|
286
|
-
validateSpinner.stop();
|
|
287
|
-
if (!validateResponse.success || !validateResponse.data?.valid) {
|
|
288
|
-
const errors = validateResponse.data?.errors || [];
|
|
289
|
-
const errorMsg = errors.length > 0
|
|
290
|
-
? errors.map(e => e.message || e).join(', ')
|
|
291
|
-
: validateResponse.error || validateResponse.formattedError || 'Validation failed';
|
|
292
|
-
throw new Error(`Configuration validation failed: ${errorMsg}`);
|
|
293
|
-
}
|
|
294
|
-
logger.log(chalk.green('✓ Configuration validated successfully'));
|
|
295
|
-
if (validateResponse.data?.warnings && validateResponse.data.warnings.length > 0) {
|
|
296
|
-
logger.log(chalk.yellow('\n⚠ Warnings:'));
|
|
297
|
-
validateResponse.data.warnings.forEach(warning => {
|
|
298
|
-
logger.log(chalk.yellow(` - ${warning.message || warning}`));
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
} catch (error) {
|
|
302
|
-
validateSpinner.stop();
|
|
303
|
-
throw error;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Handle configuration review and validation step
|
|
153
|
+
* Handle configuration review and validation step (interactive mode only)
|
|
309
154
|
* @async
|
|
310
155
|
* @function handleConfigurationReview
|
|
311
156
|
* @param {string} dataplaneUrl - Dataplane URL
|
|
312
157
|
* @param {Object} authConfig - Authentication configuration
|
|
313
158
|
* @param {Object} systemConfig - System configuration
|
|
314
159
|
* @param {Object[]} datasourceConfigs - Datasource configurations
|
|
315
|
-
* @returns {Promise<Object>} Final configurations
|
|
316
|
-
* @throws {Error} If validation fails
|
|
160
|
+
* @returns {Promise<Object>} Final configurations
|
|
317
161
|
*/
|
|
318
162
|
async function handleConfigurationReview(dataplaneUrl, authConfig, systemConfig, datasourceConfigs) {
|
|
319
|
-
logger.log(chalk.blue('\n
|
|
163
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 6-7: Review & Validate'));
|
|
320
164
|
const reviewResult = await promptForConfigReview(systemConfig, datasourceConfigs);
|
|
321
165
|
|
|
322
166
|
if (reviewResult.action === 'cancel') {
|
|
@@ -324,97 +168,55 @@ async function handleConfigurationReview(dataplaneUrl, authConfig, systemConfig,
|
|
|
324
168
|
return null;
|
|
325
169
|
}
|
|
326
170
|
|
|
327
|
-
// Use edited configs if user edited them
|
|
328
171
|
const finalSystemConfig = reviewResult.systemConfig || systemConfig;
|
|
329
172
|
const finalDatasourceConfigs = reviewResult.datasourceConfigs || datasourceConfigs;
|
|
330
173
|
|
|
331
|
-
// Validate configuration
|
|
332
174
|
await validateWizardConfiguration(dataplaneUrl, authConfig, finalSystemConfig, finalDatasourceConfigs);
|
|
333
175
|
|
|
334
176
|
return { systemConfig: finalSystemConfig, datasourceConfigs: finalDatasourceConfigs };
|
|
335
177
|
}
|
|
336
178
|
|
|
337
179
|
/**
|
|
338
|
-
*
|
|
339
|
-
* @async
|
|
340
|
-
* @function handleFileSaving
|
|
341
|
-
* @param {string} appName - Application name
|
|
342
|
-
* @param {Object} systemConfig - System configuration
|
|
343
|
-
* @param {Object[]} datasourceConfigs - Datasource configurations
|
|
344
|
-
* @param {string} systemKey - System key
|
|
345
|
-
* @param {string} dataplaneUrl - Dataplane URL
|
|
346
|
-
* @param {Object} authConfig - Authentication configuration
|
|
347
|
-
* @returns {Promise<Object>} Generated files information
|
|
348
|
-
* @throws {Error} If file saving fails
|
|
349
|
-
*/
|
|
350
|
-
async function handleFileSaving(appName, systemConfig, datasourceConfigs, systemKey, dataplaneUrl, authConfig) {
|
|
351
|
-
logger.log(chalk.blue('\n📋 Step 8: Saving Files'));
|
|
352
|
-
const saveSpinner = ora('Saving files...').start();
|
|
353
|
-
try {
|
|
354
|
-
let aiGeneratedReadme = null;
|
|
355
|
-
if (systemKey && dataplaneUrl && authConfig) {
|
|
356
|
-
try {
|
|
357
|
-
const docsResponse = await getDeploymentDocs(dataplaneUrl, authConfig, systemKey);
|
|
358
|
-
if (docsResponse.success && docsResponse.data?.content) {
|
|
359
|
-
aiGeneratedReadme = docsResponse.data.content;
|
|
360
|
-
logger.log(chalk.gray(' ✓ Fetched AI-generated README.md from dataplane'));
|
|
361
|
-
}
|
|
362
|
-
} catch (error) {
|
|
363
|
-
logger.log(chalk.gray(` ⚠ Could not fetch AI-generated README: ${error.message}`));
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
const generatedFiles = await generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, { aiGeneratedReadme });
|
|
367
|
-
saveSpinner.stop();
|
|
368
|
-
logger.log(chalk.green('\n✓ Wizard completed successfully!'));
|
|
369
|
-
logger.log(chalk.green(`\nFiles created in: ${generatedFiles.appPath}`));
|
|
370
|
-
logger.log(chalk.blue('\nNext steps:'));
|
|
371
|
-
[` 1. Review the generated files in integration/${appName}/`, ' 2. Update env.template with your authentication details', ` 3. Deploy using: ./deploy.sh or .\\deploy.ps1 (or aifabrix deploy ${appName})`].forEach(step => logger.log(chalk.gray(step)));
|
|
372
|
-
return generatedFiles;
|
|
373
|
-
} catch (error) {
|
|
374
|
-
saveSpinner.stop();
|
|
375
|
-
throw error;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Execute wizard flow steps
|
|
180
|
+
* Execute wizard flow steps (interactive mode)
|
|
381
181
|
* @async
|
|
382
182
|
* @function executeWizardFlow
|
|
383
183
|
* @param {string} appName - Application name
|
|
384
184
|
* @param {string} dataplaneUrl - Dataplane URL
|
|
385
185
|
* @param {Object} authConfig - Authentication configuration
|
|
386
186
|
* @returns {Promise<void>} Resolves when wizard flow completes
|
|
387
|
-
* @throws {Error} If wizard flow fails
|
|
388
187
|
*/
|
|
389
188
|
async function executeWizardFlow(appName, dataplaneUrl, authConfig) {
|
|
390
|
-
// Step 1: Mode Selection
|
|
391
|
-
const { mode, sessionId } = await
|
|
189
|
+
// Step 1: Mode Selection
|
|
190
|
+
const { mode, sessionId, systemIdOrKey } = await handleInteractiveModeSelection(dataplaneUrl, authConfig);
|
|
392
191
|
|
|
393
|
-
// Step 2: Source Selection
|
|
394
|
-
const { sourceType, sourceData } = await
|
|
192
|
+
// Step 2: Source Selection
|
|
193
|
+
const { sourceType, sourceData } = await handleInteractiveSourceSelection(dataplaneUrl, sessionId, authConfig);
|
|
395
194
|
|
|
396
|
-
//
|
|
397
|
-
const
|
|
195
|
+
// Parse OpenAPI (part of step 2)
|
|
196
|
+
const openapiSpec = await handleOpenApiParsing(dataplaneUrl, authConfig, sourceType, sourceData);
|
|
398
197
|
|
|
399
|
-
// Step
|
|
400
|
-
await
|
|
198
|
+
// Step 3: Credential Selection (optional)
|
|
199
|
+
const credentialIdOrKey = await handleCredentialSelection(dataplaneUrl, authConfig);
|
|
401
200
|
|
|
402
|
-
// Step
|
|
403
|
-
const
|
|
201
|
+
// Step 4: Detect Type
|
|
202
|
+
const detectedType = await handleTypeDetection(dataplaneUrl, authConfig, openapiSpec);
|
|
203
|
+
|
|
204
|
+
// Step 5: Generate Configuration
|
|
205
|
+
const { systemConfig, datasourceConfigs, systemKey } = await handleInteractiveConfigGeneration({
|
|
404
206
|
dataplaneUrl,
|
|
405
207
|
authConfig,
|
|
406
208
|
mode,
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
209
|
+
openapiSpec,
|
|
210
|
+
detectedType,
|
|
211
|
+
credentialIdOrKey,
|
|
212
|
+
systemIdOrKey
|
|
213
|
+
});
|
|
410
214
|
|
|
411
|
-
// Step 7: Review & Validate
|
|
215
|
+
// Step 6-7: Review & Validate
|
|
412
216
|
const finalConfigs = await handleConfigurationReview(dataplaneUrl, authConfig, systemConfig, datasourceConfigs);
|
|
413
|
-
if (!finalConfigs)
|
|
414
|
-
return; // User cancelled
|
|
415
|
-
}
|
|
217
|
+
if (!finalConfigs) return;
|
|
416
218
|
|
|
417
|
-
// Step
|
|
219
|
+
// Step 7: Save Files
|
|
418
220
|
await handleFileSaving(
|
|
419
221
|
appName,
|
|
420
222
|
finalConfigs.systemConfig,
|
|
@@ -434,11 +236,17 @@ async function executeWizardFlow(appName, dataplaneUrl, authConfig) {
|
|
|
434
236
|
* @param {string} [options.controller] - Controller URL
|
|
435
237
|
* @param {string} [options.environment] - Environment key
|
|
436
238
|
* @param {string} [options.dataplane] - Dataplane URL (overrides controller lookup)
|
|
239
|
+
* @param {string} [options.config] - Path to wizard.yaml config file (headless mode)
|
|
437
240
|
* @returns {Promise<void>} Resolves when wizard completes
|
|
438
241
|
* @throws {Error} If wizard fails
|
|
439
242
|
*/
|
|
440
243
|
async function handleWizard(options = {}) {
|
|
441
|
-
|
|
244
|
+
// Check if headless mode (config file provided)
|
|
245
|
+
if (options.config) {
|
|
246
|
+
return await handleWizardHeadless(options);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
logger.log(chalk.blue('\n\uD83E\uDDD9 AI Fabrix External System Wizard\n'));
|
|
442
250
|
|
|
443
251
|
// Get or prompt for app name
|
|
444
252
|
let appName = options.app;
|
|
@@ -447,7 +255,7 @@ async function handleWizard(options = {}) {
|
|
|
447
255
|
}
|
|
448
256
|
|
|
449
257
|
// Validate app name and check directory
|
|
450
|
-
const shouldContinue = await validateAndCheckAppDirectory(appName);
|
|
258
|
+
const shouldContinue = await validateAndCheckAppDirectory(appName, true);
|
|
451
259
|
if (!shouldContinue) {
|
|
452
260
|
return;
|
|
453
261
|
}
|
|
@@ -459,34 +267,4 @@ async function handleWizard(options = {}) {
|
|
|
459
267
|
await executeWizardFlow(appName, dataplaneUrl, authConfig);
|
|
460
268
|
}
|
|
461
269
|
|
|
462
|
-
|
|
463
|
-
* Setup dataplane URL and authentication
|
|
464
|
-
* @async
|
|
465
|
-
* @function setupDataplaneAndAuth
|
|
466
|
-
* @param {Object} options - Command options
|
|
467
|
-
* @param {string} appName - Application name
|
|
468
|
-
* @returns {Promise<Object>} Object with dataplaneUrl and authConfig
|
|
469
|
-
* @throws {Error} If setup fails
|
|
470
|
-
*/
|
|
471
|
-
async function setupDataplaneAndAuth(options, appName) {
|
|
472
|
-
const configData = await config.getConfig();
|
|
473
|
-
const environment = options.environment || 'dev';
|
|
474
|
-
const controllerUrl = await resolveControllerUrl(options, configData);
|
|
475
|
-
// Wizard requires device token authentication (user-level), not client credentials
|
|
476
|
-
const authConfig = await getDeviceOnlyAuth(controllerUrl);
|
|
477
|
-
|
|
478
|
-
let dataplaneUrl = options.dataplane;
|
|
479
|
-
if (!dataplaneUrl) {
|
|
480
|
-
logger.log(chalk.blue('🌐 Getting dataplane URL from controller...'));
|
|
481
|
-
try {
|
|
482
|
-
dataplaneUrl = await getDataplaneUrl(controllerUrl, 'dataplane', environment, authConfig);
|
|
483
|
-
logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
|
|
484
|
-
} catch (error) {
|
|
485
|
-
const example = `aifabrix wizard -a ${appName} --dataplane https://dataplane.example.com -e ${environment} -c ${controllerUrl}`;
|
|
486
|
-
throw new Error(`${error.message}\n\n💡 For new applications, provide the dataplane URL using:\n --dataplane <dataplane-url>\n\n Example: ${example}`);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
return { dataplaneUrl, authConfig };
|
|
491
|
-
}
|
|
492
|
-
module.exports = { handleWizard };
|
|
270
|
+
module.exports = { handleWizard, handleWizardHeadless };
|
package/lib/core/config.js
CHANGED
|
@@ -95,6 +95,8 @@ function applyConfigDefaults(config) {
|
|
|
95
95
|
if (typeof config.device !== 'object' || config.device === null) {
|
|
96
96
|
config.device = {};
|
|
97
97
|
}
|
|
98
|
+
// Ensure controller field exists (but don't set defaults)
|
|
99
|
+
// It will be set by login or auth config commands
|
|
98
100
|
return config;
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -107,6 +109,7 @@ function getDefaultConfig() {
|
|
|
107
109
|
return {
|
|
108
110
|
'developer-id': '0',
|
|
109
111
|
environment: 'dev',
|
|
112
|
+
controller: undefined,
|
|
110
113
|
environments: {},
|
|
111
114
|
device: {}
|
|
112
115
|
};
|
|
@@ -239,6 +242,17 @@ async function getCurrentEnvironment() {
|
|
|
239
242
|
return config.environment || 'dev';
|
|
240
243
|
}
|
|
241
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Resolve environment from configuration
|
|
247
|
+
* Uses config.environment, defaults to 'dev'
|
|
248
|
+
* @async
|
|
249
|
+
* @function resolveEnvironment
|
|
250
|
+
* @returns {Promise<string>} Environment key
|
|
251
|
+
*/
|
|
252
|
+
async function resolveEnvironment() {
|
|
253
|
+
return await getCurrentEnvironment();
|
|
254
|
+
}
|
|
255
|
+
|
|
242
256
|
async function setCurrentEnvironment(environment) {
|
|
243
257
|
if (!environment || typeof environment !== 'string') {
|
|
244
258
|
throw new Error('Environment must be a non-empty string');
|
|
@@ -248,6 +262,35 @@ async function setCurrentEnvironment(environment) {
|
|
|
248
262
|
await saveConfig(config);
|
|
249
263
|
}
|
|
250
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Set controller URL in configuration
|
|
267
|
+
* @async
|
|
268
|
+
* @function setControllerUrl
|
|
269
|
+
* @param {string} controllerUrl - Controller URL to save
|
|
270
|
+
* @returns {Promise<void>}
|
|
271
|
+
* @throws {Error} If controller URL is invalid
|
|
272
|
+
*/
|
|
273
|
+
async function setControllerUrl(controllerUrl) {
|
|
274
|
+
if (!controllerUrl || typeof controllerUrl !== 'string') {
|
|
275
|
+
throw new Error('Controller URL is required and must be a string');
|
|
276
|
+
}
|
|
277
|
+
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
278
|
+
const config = await getConfig();
|
|
279
|
+
config.controller = normalizedUrl;
|
|
280
|
+
await saveConfig(config);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get controller URL from configuration
|
|
285
|
+
* @async
|
|
286
|
+
* @function getControllerUrl
|
|
287
|
+
* @returns {Promise<string|null>} Controller URL or null if not set
|
|
288
|
+
*/
|
|
289
|
+
async function getControllerUrl() {
|
|
290
|
+
const config = await getConfig();
|
|
291
|
+
return config.controller || null;
|
|
292
|
+
}
|
|
293
|
+
|
|
251
294
|
function isTokenExpired(expiresAt) {
|
|
252
295
|
if (!expiresAt) return true;
|
|
253
296
|
const expirationTime = new Date(expiresAt).getTime();
|
|
@@ -354,6 +397,7 @@ const exportsObj = {
|
|
|
354
397
|
loadDeveloperId,
|
|
355
398
|
getCurrentEnvironment,
|
|
356
399
|
setCurrentEnvironment,
|
|
400
|
+
resolveEnvironment,
|
|
357
401
|
isTokenExpired,
|
|
358
402
|
shouldRefreshToken,
|
|
359
403
|
encryptTokenValue,
|
|
@@ -363,6 +407,8 @@ const exportsObj = {
|
|
|
363
407
|
getSecretsPath,
|
|
364
408
|
setSecretsPath,
|
|
365
409
|
normalizeControllerUrl,
|
|
410
|
+
setControllerUrl,
|
|
411
|
+
getControllerUrl,
|
|
366
412
|
CONFIG_DIR,
|
|
367
413
|
CONFIG_FILE
|
|
368
414
|
};
|