@aifabrix/builder 2.33.5 → 2.36.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/README.md +5 -0
- package/integration/test-hubspot/wizard.yaml +8 -0
- package/lib/api/wizard.api.js +24 -1
- package/lib/app/run-helpers.js +7 -2
- package/lib/app/run.js +2 -2
- package/lib/app/show-display.js +184 -0
- package/lib/app/show.js +642 -0
- package/lib/cli.js +38 -9
- package/lib/commands/auth-status.js +58 -2
- package/lib/commands/up-miso.js +25 -16
- package/lib/commands/wizard-core-helpers.js +278 -0
- package/lib/commands/wizard-core.js +74 -161
- package/lib/commands/wizard-headless.js +2 -2
- package/lib/commands/wizard-helpers.js +143 -0
- package/lib/commands/wizard.js +282 -69
- package/lib/datasource/list.js +6 -3
- package/lib/generator/index.js +32 -0
- package/lib/generator/wizard-prompts.js +111 -44
- package/lib/infrastructure/services.js +6 -3
- package/lib/utils/app-register-auth.js +9 -2
- package/lib/utils/cli-utils.js +40 -1
- package/lib/utils/error-formatters/http-status-errors.js +8 -0
- package/lib/utils/error-formatters/permission-errors.js +44 -1
- package/lib/utils/infra-containers.js +19 -16
- package/lib/utils/infra-status.js +12 -3
- package/lib/validation/wizard-config-validator.js +35 -0
- package/package.json +2 -2
|
@@ -3,27 +3,34 @@
|
|
|
3
3
|
* @author AI Fabrix Team
|
|
4
4
|
* @version 2.0.0
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
const chalk = require('chalk');
|
|
8
8
|
const ora = require('ora');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const fs = require('fs').promises;
|
|
11
11
|
const logger = require('../utils/logger');
|
|
12
|
-
const { getDeploymentAuth
|
|
12
|
+
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
13
13
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
14
14
|
const { normalizeWizardConfigs } = require('./wizard-config-normalizer');
|
|
15
15
|
const {
|
|
16
16
|
createWizardSession,
|
|
17
17
|
updateWizardSession,
|
|
18
|
-
parseOpenApi,
|
|
19
|
-
credentialSelection,
|
|
20
18
|
detectType,
|
|
21
19
|
generateConfig,
|
|
22
20
|
validateWizardConfig,
|
|
23
|
-
getDeploymentDocs
|
|
24
|
-
testMcpConnection
|
|
21
|
+
getDeploymentDocs
|
|
25
22
|
} = require('../api/wizard.api');
|
|
26
23
|
const { generateWizardFiles } = require('../generator/wizard');
|
|
24
|
+
const {
|
|
25
|
+
parseOpenApiSource,
|
|
26
|
+
testMcpServerConnection,
|
|
27
|
+
normalizeCredentialSelectionInput,
|
|
28
|
+
runCredentialSelectionLoop,
|
|
29
|
+
buildConfigPreferences,
|
|
30
|
+
buildConfigPayload,
|
|
31
|
+
extractConfigurationFromResponse,
|
|
32
|
+
throwConfigGenerationError
|
|
33
|
+
} = require('./wizard-core-helpers');
|
|
27
34
|
|
|
28
35
|
/**
|
|
29
36
|
* Validate app name and check if directory exists
|
|
@@ -75,6 +82,32 @@ function extractSessionId(responseData) {
|
|
|
75
82
|
return sessionId;
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Full error message when dataplane returns 401 (controller accepts token, dataplane rejects it).
|
|
87
|
+
* Avoids misleading "token invalid or expired" / "run login" text from the API formatter.
|
|
88
|
+
* @param {string} [dataplaneUrl] - Dataplane URL (for context)
|
|
89
|
+
* @param {string} [appName] - Application name for credential hint
|
|
90
|
+
* @param {string} [apiMessage] - Raw message from API (e.g. "Invalid token or insufficient permissions")
|
|
91
|
+
* @returns {string} Full message explaining the actual problem
|
|
92
|
+
*/
|
|
93
|
+
function formatDataplaneRejectedTokenMessage(dataplaneUrl = '', appName = null, apiMessage = '') {
|
|
94
|
+
const app = appName || '<app>';
|
|
95
|
+
const where = dataplaneUrl ? ` the dataplane at ${dataplaneUrl}` : ' the dataplane';
|
|
96
|
+
const apiLine = apiMessage ? `\n\nResponse: ${apiMessage}` : '';
|
|
97
|
+
return (
|
|
98
|
+
'Failed to create wizard session.' +
|
|
99
|
+
apiLine +
|
|
100
|
+
`\n\nYour token is valid for the controller (aifabrix auth status shows you as authenticated), but${where} rejected the request.` +
|
|
101
|
+
' This usually means:\n' +
|
|
102
|
+
' • The dataplane is configured to accept only client credentials, not device tokens, or\n' +
|
|
103
|
+
' • There is a permission or configuration issue on the dataplane side.\n\n' +
|
|
104
|
+
'What you can do:\n' +
|
|
105
|
+
` • Add client credentials to ~/.aifabrix/secrets.local.yaml as "${app}-client-idKeyVault" and "${app}-client-secretKeyVault" if the dataplane accepts them.\n` +
|
|
106
|
+
' • Contact your administrator to have the dataplane accept your token or to get the required client credentials.\n' +
|
|
107
|
+
' • Run "aifabrix doctor" for environment diagnostics.'
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
78
111
|
/**
|
|
79
112
|
* Handle mode selection step - create wizard session
|
|
80
113
|
* @async
|
|
@@ -92,7 +125,11 @@ async function handleModeSelection(dataplaneUrl, authConfig, configMode = null,
|
|
|
92
125
|
if (!sessionResponse.success || !sessionResponse.data) {
|
|
93
126
|
const errorMsg = sessionResponse.formattedError || sessionResponse.error ||
|
|
94
127
|
sessionResponse.errorData?.detail || 'Unknown error';
|
|
95
|
-
|
|
128
|
+
const apiMessage = sessionResponse.errorData?.message || sessionResponse.errorData?.detail || sessionResponse.error || '';
|
|
129
|
+
const fullMsg = sessionResponse.status === 401
|
|
130
|
+
? formatDataplaneRejectedTokenMessage(dataplaneUrl, null, apiMessage)
|
|
131
|
+
: `Failed to create wizard session: ${errorMsg}`;
|
|
132
|
+
throw new Error(fullMsg);
|
|
96
133
|
}
|
|
97
134
|
const sessionId = extractSessionId(sessionResponse.data);
|
|
98
135
|
logger.log(chalk.green(`\u2713 Session created: ${sessionId}`));
|
|
@@ -131,59 +168,6 @@ async function handleSourceSelection(dataplaneUrl, sessionId, authConfig, config
|
|
|
131
168
|
return { sourceType, sourceData };
|
|
132
169
|
}
|
|
133
170
|
|
|
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
171
|
/**
|
|
188
172
|
* Handle OpenAPI parsing step
|
|
189
173
|
* @async
|
|
@@ -212,41 +196,28 @@ async function handleOpenApiParsing(dataplaneUrl, authConfig, sourceType, source
|
|
|
212
196
|
}
|
|
213
197
|
|
|
214
198
|
/**
|
|
215
|
-
* Handle credential selection step
|
|
199
|
+
* Handle credential selection step.
|
|
200
|
+
* Validation is done by the dataplane (POST /api/v1/wizard/credential-selection).
|
|
201
|
+
* When action is 'select' and the API fails (e.g. credential not found), and allowRetry is true,
|
|
202
|
+
* we re-prompt for credential ID/key or allow the user to skip (empty = skip).
|
|
216
203
|
* @async
|
|
217
204
|
* @function handleCredentialSelection
|
|
218
205
|
* @param {string} dataplaneUrl - Dataplane URL
|
|
219
206
|
* @param {Object} authConfig - Authentication configuration
|
|
220
|
-
* @param {Object} [configCredential] - Credential config from wizard.yaml
|
|
221
|
-
* @
|
|
207
|
+
* @param {Object} [configCredential] - Credential config from wizard.yaml or prompt
|
|
208
|
+
* @param {Object} [options] - Options
|
|
209
|
+
* @param {boolean} [options.allowRetry=true] - If true (interactive), re-prompt on failure for 'select'; if false (headless), do not re-prompt
|
|
210
|
+
* @returns {Promise<string|null>} Credential ID/key or null if skipped / failed
|
|
222
211
|
*/
|
|
223
|
-
async function handleCredentialSelection(dataplaneUrl, authConfig, configCredential = null) {
|
|
212
|
+
async function handleCredentialSelection(dataplaneUrl, authConfig, configCredential = null, options = {}) {
|
|
213
|
+
const allowRetry = options.allowRetry !== false;
|
|
224
214
|
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' };
|
|
215
|
+
const selectionData = normalizeCredentialSelectionInput(configCredential);
|
|
230
216
|
if (selectionData.action === 'skip') {
|
|
231
217
|
logger.log(chalk.gray(' Skipping credential selection'));
|
|
232
218
|
return null;
|
|
233
219
|
}
|
|
234
|
-
|
|
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
|
-
}
|
|
220
|
+
return await runCredentialSelectionLoop(dataplaneUrl, authConfig, selectionData, allowRetry);
|
|
250
221
|
}
|
|
251
222
|
|
|
252
223
|
/**
|
|
@@ -278,67 +249,6 @@ async function handleTypeDetection(dataplaneUrl, authConfig, openapiSpec) {
|
|
|
278
249
|
return null;
|
|
279
250
|
}
|
|
280
251
|
|
|
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
252
|
/**
|
|
343
253
|
* Handle configuration generation step
|
|
344
254
|
* @async
|
|
@@ -354,6 +264,7 @@ function extractConfigurationFromResponse(generateResponse) {
|
|
|
354
264
|
* @param {string} [options.systemIdOrKey] - System ID or key (optional)
|
|
355
265
|
* @returns {Promise<Object>} Generated configuration
|
|
356
266
|
*/
|
|
267
|
+
|
|
357
268
|
async function handleConfigurationGeneration(dataplaneUrl, authConfig, options) {
|
|
358
269
|
logger.log(chalk.blue('\n\uD83D\uDCCB Step 5: Generate Configuration'));
|
|
359
270
|
const prefs = buildConfigPreferences(options.configPrefs);
|
|
@@ -370,7 +281,7 @@ async function handleConfigurationGeneration(dataplaneUrl, authConfig, options)
|
|
|
370
281
|
const generateResponse = await generateConfig(dataplaneUrl, authConfig, configPayload);
|
|
371
282
|
spinner.stop();
|
|
372
283
|
if (!generateResponse.success) {
|
|
373
|
-
|
|
284
|
+
throwConfigGenerationError(generateResponse);
|
|
374
285
|
}
|
|
375
286
|
const result = extractConfigurationFromResponse(generateResponse);
|
|
376
287
|
const normalized = normalizeWizardConfigs(result.systemConfig, result.datasourceConfigs);
|
|
@@ -478,20 +389,13 @@ async function setupDataplaneAndAuth(options, appName) {
|
|
|
478
389
|
const { resolveEnvironment } = require('../core/config');
|
|
479
390
|
const environment = await resolveEnvironment();
|
|
480
391
|
const controllerUrl = await resolveControllerUrl();
|
|
392
|
+
// Prefer device token; use client token or client credentials when available.
|
|
393
|
+
// Some dataplanes accept only client credentials; getDeploymentAuth tries all.
|
|
481
394
|
let authConfig;
|
|
482
395
|
try {
|
|
483
|
-
|
|
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);
|
|
396
|
+
authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
|
|
487
397
|
} catch (error) {
|
|
488
|
-
|
|
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
|
-
}
|
|
398
|
+
throw new Error(`Authentication failed: ${error.message}`);
|
|
495
399
|
}
|
|
496
400
|
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
497
401
|
let dataplaneUrl;
|
|
@@ -509,7 +413,16 @@ async function setupDataplaneAndAuth(options, appName) {
|
|
|
509
413
|
}
|
|
510
414
|
|
|
511
415
|
module.exports = {
|
|
512
|
-
validateAndCheckAppDirectory,
|
|
513
|
-
|
|
514
|
-
|
|
416
|
+
validateAndCheckAppDirectory,
|
|
417
|
+
extractSessionId,
|
|
418
|
+
formatDataplaneRejectedTokenMessage,
|
|
419
|
+
handleModeSelection,
|
|
420
|
+
handleSourceSelection,
|
|
421
|
+
handleOpenApiParsing,
|
|
422
|
+
handleCredentialSelection,
|
|
423
|
+
handleTypeDetection,
|
|
424
|
+
handleConfigurationGeneration,
|
|
425
|
+
validateWizardConfiguration,
|
|
426
|
+
handleFileSaving,
|
|
427
|
+
setupDataplaneAndAuth
|
|
515
428
|
};
|
|
@@ -45,8 +45,8 @@ async function executeWizardFromConfig(wizardConfig, dataplaneUrl, authConfig) {
|
|
|
45
45
|
// Parse OpenAPI
|
|
46
46
|
const openapiSpec = await handleOpenApiParsing(dataplaneUrl, authConfig, sourceType, sourceData);
|
|
47
47
|
|
|
48
|
-
// Step 3: Credential Selection
|
|
49
|
-
const credentialIdOrKey = await handleCredentialSelection(dataplaneUrl, authConfig, credential);
|
|
48
|
+
// Step 3: Credential Selection (no retry prompt in headless)
|
|
49
|
+
const credentialIdOrKey = await handleCredentialSelection(dataplaneUrl, authConfig, credential, { allowRetry: false });
|
|
50
50
|
|
|
51
51
|
// Step 4: Detect Type
|
|
52
52
|
const detectedType = await handleTypeDetection(dataplaneUrl, authConfig, openapiSpec);
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Wizard command helpers - pure and I/O helpers for wizard flow
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs').promises;
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const logger = require('../utils/logger');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build preferences object for wizard.yaml (schema shape: intent, fieldOnboardingLevel, enableOpenAPIGeneration, enableMCP, enableABAC, enableRBAC)
|
|
14
|
+
* @param {string} intent - User intent
|
|
15
|
+
* @param {Object} preferences - From promptForUserPreferences (mcp, abac, rbac)
|
|
16
|
+
* @returns {Object} Preferences for wizard-config schema
|
|
17
|
+
*/
|
|
18
|
+
function buildPreferencesForSave(intent, preferences) {
|
|
19
|
+
return {
|
|
20
|
+
intent: intent || 'general integration',
|
|
21
|
+
fieldOnboardingLevel: 'full',
|
|
22
|
+
enableOpenAPIGeneration: true,
|
|
23
|
+
enableMCP: Boolean(preferences?.mcp),
|
|
24
|
+
enableABAC: Boolean(preferences?.abac),
|
|
25
|
+
enableRBAC: Boolean(preferences?.rbac)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build source object for wizard.yaml (no secrets)
|
|
31
|
+
* @param {Object} [source] - Source state
|
|
32
|
+
* @returns {Object|undefined}
|
|
33
|
+
*/
|
|
34
|
+
function buildSourceForSave(source) {
|
|
35
|
+
if (!source) return undefined;
|
|
36
|
+
const out = { type: source.type };
|
|
37
|
+
if (source.type === 'openapi-file' && source.filePath) out.filePath = source.filePath;
|
|
38
|
+
if (source.type === 'openapi-url' && source.url) out.url = source.url;
|
|
39
|
+
if (source.type === 'mcp-server' && source.serverUrl) {
|
|
40
|
+
out.serverUrl = source.serverUrl;
|
|
41
|
+
out.token = source.token ? '(set)' : undefined;
|
|
42
|
+
}
|
|
43
|
+
if (source.type === 'known-platform' && source.platform) out.platform = source.platform;
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build partial wizard state for saving to wizard.yaml (no secrets)
|
|
49
|
+
* @param {Object} opts - Collected state
|
|
50
|
+
* @returns {Object} Serializable wizard config shape
|
|
51
|
+
*/
|
|
52
|
+
function buildWizardStateForSave(opts) {
|
|
53
|
+
const state = {
|
|
54
|
+
appName: opts.appKey,
|
|
55
|
+
mode: opts.mode,
|
|
56
|
+
source: buildSourceForSave(opts.source)
|
|
57
|
+
};
|
|
58
|
+
if (opts.mode === 'add-datasource' && opts.systemIdOrKey) state.systemIdOrKey = opts.systemIdOrKey;
|
|
59
|
+
if (opts.credential) state.credential = opts.credential;
|
|
60
|
+
if (opts.preferences) state.preferences = opts.preferences;
|
|
61
|
+
return state;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Format source config as a short line for display
|
|
66
|
+
* @param {Object} [source] - Source config
|
|
67
|
+
* @returns {string|null}
|
|
68
|
+
*/
|
|
69
|
+
function formatSourceLine(source) {
|
|
70
|
+
if (!source) return null;
|
|
71
|
+
const s = source;
|
|
72
|
+
return s.type + (s.filePath ? ` (${s.filePath})` : s.url ? ` (${s.url})` : s.platform ? ` (${s.platform})` : '');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Format preferences config as a short line for display
|
|
77
|
+
* @param {Object} [preferences] - Preferences config
|
|
78
|
+
* @returns {string|null}
|
|
79
|
+
*/
|
|
80
|
+
function formatPreferencesLine(preferences) {
|
|
81
|
+
if (!preferences || (!preferences.intent && (preferences.enableMCP === undefined || preferences.enableMCP === null))) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const p = preferences;
|
|
85
|
+
return [p.intent && `intent=${p.intent}`, p.enableMCP && 'MCP', p.enableABAC && 'ABAC', p.enableRBAC && 'RBAC'].filter(Boolean).join(', ') || '(defaults)';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Show a short summary of loaded wizard config (for resume flow)
|
|
90
|
+
* @param {Object} config - Loaded wizard config
|
|
91
|
+
* @param {string} displayPath - Path to show (e.g. integration/test/wizard.yaml)
|
|
92
|
+
*/
|
|
93
|
+
function showWizardConfigSummary(config, displayPath) {
|
|
94
|
+
logger.log(chalk.blue('\n📋 Saved config summary'));
|
|
95
|
+
logger.log(chalk.gray(` From: ${displayPath}`));
|
|
96
|
+
if (config.appName) logger.log(chalk.gray(` App: ${config.appName}`));
|
|
97
|
+
if (config.mode) logger.log(chalk.gray(` Mode: ${config.mode}`));
|
|
98
|
+
const srcLine = formatSourceLine(config.source);
|
|
99
|
+
if (srcLine) logger.log(chalk.gray(` Source: ${srcLine}`));
|
|
100
|
+
if (config.credential) logger.log(chalk.gray(` Credential: ${config.credential.action || 'skip'}`));
|
|
101
|
+
const prefs = formatPreferencesLine(config.preferences);
|
|
102
|
+
if (prefs) logger.log(chalk.gray(` Preferences: ${prefs}`));
|
|
103
|
+
logger.log('');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Ensure integration/<appKey> directory exists
|
|
108
|
+
* @param {string} appKey - Application key
|
|
109
|
+
* @returns {Promise<string>} Resolved config path (integration/<appKey>/wizard.yaml)
|
|
110
|
+
*/
|
|
111
|
+
async function ensureIntegrationDir(appKey) {
|
|
112
|
+
const dir = path.join(process.cwd(), 'integration', appKey);
|
|
113
|
+
await fs.mkdir(dir, { recursive: true });
|
|
114
|
+
return path.join(dir, 'wizard.yaml');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** External system types that support add-datasource (excludes webapp/application) */
|
|
118
|
+
const EXTERNAL_SYSTEM_TYPES = ['openapi', 'mcp', 'custom'];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Returns true if the system is an external system that supports add-datasource (not a webapp).
|
|
122
|
+
* @param {Object} sys - System object from getExternalSystem (may have type, systemType, or kind)
|
|
123
|
+
* @returns {boolean}
|
|
124
|
+
*/
|
|
125
|
+
function isExternalSystemForAddDatasource(sys) {
|
|
126
|
+
const type = (sys?.type || sys?.systemType || sys?.kind || '').toLowerCase();
|
|
127
|
+
if (!type) return true;
|
|
128
|
+
if (EXTERNAL_SYSTEM_TYPES.includes(type)) return true;
|
|
129
|
+
if (['webapp', 'application', 'app'].includes(type)) return false;
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
buildPreferencesForSave,
|
|
135
|
+
buildSourceForSave,
|
|
136
|
+
buildWizardStateForSave,
|
|
137
|
+
formatSourceLine,
|
|
138
|
+
formatPreferencesLine,
|
|
139
|
+
showWizardConfigSummary,
|
|
140
|
+
ensureIntegrationDir,
|
|
141
|
+
EXTERNAL_SYSTEM_TYPES,
|
|
142
|
+
isExternalSystemForAddDatasource
|
|
143
|
+
};
|