@aifabrix/builder 2.44.3 → 2.44.5
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/.npmrc.token +1 -1
- package/integration/roundtrip-test-local/README.md +1 -2
- package/integration/roundtrip-test-local2/README.md +1 -2
- package/jest.projects.js +31 -15
- package/lib/api/certificates.api.js +21 -3
- package/lib/api/types/wizard.types.js +2 -1
- package/lib/certification/post-unified-cert-sync.js +13 -2
- package/lib/certification/sync-after-external-command.js +6 -3
- package/lib/certification/sync-system-certification.js +60 -14
- package/lib/cli/setup-app.help.js +1 -1
- package/lib/cli/setup-app.test-commands.js +75 -39
- package/lib/cli/setup-infra.js +6 -2
- package/lib/cli/setup-utility.js +20 -1
- package/lib/commands/datasource-unified-test-cli.js +81 -46
- package/lib/commands/datasource-unified-test-cli.options.js +4 -2
- package/lib/commands/datasource.js +3 -31
- package/lib/commands/repair-datasource-keys.js +1 -1
- package/lib/commands/repair-datasource-openapi.js +57 -0
- package/lib/commands/repair-datasource.js +5 -0
- package/lib/commands/repair-internal.js +2 -4
- package/lib/commands/repair-rbac.js +25 -2
- package/lib/commands/repair.js +2 -19
- package/lib/commands/test-e2e-external.js +9 -9
- package/lib/commands/up-common.js +25 -0
- package/lib/commands/upload.js +18 -4
- package/lib/commands/wizard-core.js +53 -11
- package/lib/commands/wizard-dataplane.js +14 -6
- package/lib/commands/wizard-entity-selection.js +71 -14
- package/lib/commands/wizard-headless.js +5 -2
- package/lib/commands/wizard-helpers.js +13 -1
- package/lib/commands/wizard.js +208 -60
- package/lib/datasource/datasource-validate-display.js +162 -0
- package/lib/datasource/datasource-validate-summary.js +194 -0
- package/lib/datasource/test-e2e.js +65 -37
- package/lib/datasource/unified-validation-run-body.js +1 -2
- package/lib/datasource/validate.js +14 -6
- package/lib/external-system/test.js +12 -8
- package/lib/generator/external-controller-manifest.js +12 -2
- package/lib/generator/wizard-prompts.js +7 -1
- package/lib/generator/wizard.js +34 -0
- package/lib/schema/cip-capacity-display.fallback.json +7 -0
- package/lib/schema/datasource-test-run.schema.json +79 -1
- package/lib/schema/external-datasource.schema.json +94 -2
- package/lib/schema/flag-map-validation-run.json +1 -2
- package/lib/schema/type/document-storage.json +83 -3
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/configuration-env-resolver.js +38 -0
- package/lib/utils/dataplane-resolver.js +3 -2
- package/lib/utils/datasource-test-run-capacity-operations.js +149 -0
- package/lib/utils/datasource-test-run-debug-display.js +143 -1
- package/lib/utils/datasource-test-run-display.js +46 -33
- package/lib/utils/datasource-test-run-tty-log.js +6 -2
- package/lib/utils/datasource-test-run-tty-meta-lines.js +123 -0
- package/lib/utils/error-formatter.js +32 -2
- package/lib/utils/external-readme.js +47 -3
- package/lib/utils/external-system-readiness-core.js +39 -0
- package/lib/utils/external-system-readiness-deploy-display.js +2 -3
- package/lib/utils/external-system-readiness-display-internals.js +3 -2
- package/lib/utils/external-system-system-test-tty.js +33 -9
- package/lib/utils/external-system-validators.js +62 -5
- package/lib/utils/load-cip-capacity-display-config.js +130 -0
- package/lib/utils/paths.js +10 -3
- package/lib/utils/schema-resolver.js +98 -2
- package/lib/utils/urls-local-registry.js +52 -10
- package/lib/utils/validation-run-poll.js +15 -4
- package/lib/utils/validation-run-request.js +4 -6
- package/lib/validation/dimension-display-helpers.js +60 -0
- package/lib/validation/validate-display-log-helpers.js +39 -0
- package/lib/validation/validate-display.js +89 -83
- package/package.json +1 -1
- package/templates/applications/miso-controller/env.template +6 -6
- package/templates/external-system/README.md.hbs +58 -32
|
@@ -275,7 +275,8 @@ async function handleTypeDetection(dataplaneUrl, authConfig, openapiSpec) {
|
|
|
275
275
|
* @param {string} [options.systemIdOrKey] - System ID or key (optional)
|
|
276
276
|
* @param {string} [options.sourceType] - Source type (use 'known-platform' to call platforms config endpoint)
|
|
277
277
|
* @param {string} [options.platformKey] - Platform key for known-platform (e.g. 'hubspot')
|
|
278
|
-
* @param {string} [options.appName] -
|
|
278
|
+
* @param {string} [options.appName] - Integration app key; for create-system, used as systemIdOrKey
|
|
279
|
+
* when not set so kv paths match the folder (avoids spec-title keys like "companies")
|
|
279
280
|
* @returns {Promise<Object>} Generated configuration
|
|
280
281
|
*/
|
|
281
282
|
async function callGenerateApi(dataplaneUrl, authConfig, options, prefs) {
|
|
@@ -291,13 +292,19 @@ async function callGenerateApi(dataplaneUrl, authConfig, options, prefs) {
|
|
|
291
292
|
});
|
|
292
293
|
return await getPlatformConfig(dataplaneUrl, authConfig, options.platformKey, platformPayload);
|
|
293
294
|
}
|
|
295
|
+
// Headless/interactive create-system uses appName as integration folder key; OpenAPI title alone
|
|
296
|
+
// can yield a wrong system key (e.g. title "Companies" → "companies"). Prefer appName when set.
|
|
297
|
+
let systemIdOrKeyForPayload = options.systemIdOrKey;
|
|
298
|
+
if (options.mode === 'create-system' && options.appName) {
|
|
299
|
+
systemIdOrKeyForPayload = systemIdOrKeyForPayload || options.appName;
|
|
300
|
+
}
|
|
294
301
|
const configPayload = buildConfigPayload({
|
|
295
302
|
openapiSpec: options.openapiSpec,
|
|
296
303
|
detectedType: options.detectedType,
|
|
297
304
|
mode: options.mode,
|
|
298
305
|
prefs,
|
|
299
306
|
credentialIdOrKey: options.credentialIdOrKey,
|
|
300
|
-
systemIdOrKey:
|
|
307
|
+
systemIdOrKey: systemIdOrKeyForPayload,
|
|
301
308
|
entityName: options.entityName,
|
|
302
309
|
systemDisplayName: options.systemDisplayName
|
|
303
310
|
});
|
|
@@ -410,6 +417,39 @@ async function tryUpdateReadmeFromDeploymentDocs(appPath, appName, dataplaneUrl,
|
|
|
410
417
|
}
|
|
411
418
|
}
|
|
412
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Writes rbac.yaml / rbac.json from datasource resourceType + capabilities (same as `af repair --rbac`).
|
|
422
|
+
* @param {Object} generatedFiles - Result of generateWizardFiles (appPath, systemFilePath, datasourceFilePaths)
|
|
423
|
+
* @param {string} format - Project format: yaml | json
|
|
424
|
+
*/
|
|
425
|
+
function logWizardFileSaveFooter(appName, generatedFiles) {
|
|
426
|
+
logger.log(chalk.green('\n\u2713 Wizard completed successfully!'));
|
|
427
|
+
logger.log(chalk.green(`\nFiles created in: ${generatedFiles.appPath}`));
|
|
428
|
+
logger.log(chalk.blue('\nNext steps:'));
|
|
429
|
+
logger.log(chalk.gray(` 1. Review the generated files in integration/${appName}/`));
|
|
430
|
+
logger.log(chalk.gray(' 2. Update env.template with your authentication details'));
|
|
431
|
+
logger.log(chalk.gray(` 3. Deploy using: node deploy.js or aifabrix deploy ${appName}`));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function mergeRbacAfterWizardFilesWritten(generatedFiles, format) {
|
|
435
|
+
const { mergeRbacFromDatasources, extractRbacFromSystem } = require('./repair-rbac');
|
|
436
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
437
|
+
const systemParsedForRbac = loadConfigFile(generatedFiles.systemFilePath);
|
|
438
|
+
const datasourceFileNames = (generatedFiles.datasourceFilePaths || []).map((p) => path.basename(p));
|
|
439
|
+
const rbacChanges = [];
|
|
440
|
+
const rbacUpdated = mergeRbacFromDatasources(
|
|
441
|
+
generatedFiles.appPath,
|
|
442
|
+
systemParsedForRbac,
|
|
443
|
+
datasourceFileNames,
|
|
444
|
+
extractRbacFromSystem,
|
|
445
|
+
{ format: format === 'json' ? 'json' : 'yaml', dryRun: false, changes: rbacChanges }
|
|
446
|
+
);
|
|
447
|
+
if (rbacUpdated && rbacChanges.length) {
|
|
448
|
+
rbacChanges.forEach((c) => logger.log(chalk.gray(` RBAC: ${c}`)));
|
|
449
|
+
logger.log(chalk.green(' RBAC file updated from datasource capabilities (enableRBAC).'));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
413
453
|
/**
|
|
414
454
|
* Handle file saving step
|
|
415
455
|
* @async
|
|
@@ -418,17 +458,24 @@ async function tryUpdateReadmeFromDeploymentDocs(appPath, appName, dataplaneUrl,
|
|
|
418
458
|
* @param {Object} systemConfig - System configuration
|
|
419
459
|
* @param {Object[]} datasourceConfigs - Datasource configurations
|
|
420
460
|
* @param {string} systemKey - System key
|
|
421
|
-
* @param {string}
|
|
422
|
-
* @param {Object} authConfig - Authentication configuration
|
|
461
|
+
* @param {{ dataplaneUrl: string, authConfig: Object, enableRBAC?: boolean }} ctx - Dataplane auth + optional RBAC generation
|
|
423
462
|
* @returns {Promise<Object>} Generated files information
|
|
424
463
|
*/
|
|
425
|
-
async function handleFileSaving(appName, systemConfig, datasourceConfigs, systemKey,
|
|
464
|
+
async function handleFileSaving(appName, systemConfig, datasourceConfigs, systemKey, ctx) {
|
|
465
|
+
const { dataplaneUrl, authConfig, enableRBAC = false } = ctx || {};
|
|
426
466
|
logger.log(chalk.blue('\n\uD83D\uDCCB Step 7: Save Files'));
|
|
427
467
|
const spinner = ora('Saving files...').start();
|
|
428
468
|
try {
|
|
429
469
|
const config = require('../core/config');
|
|
430
470
|
const format = (await config.getFormat()) || 'yaml';
|
|
431
471
|
const generatedFiles = await generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, { aiGeneratedReadme: null, format });
|
|
472
|
+
if (enableRBAC && generatedFiles.appPath && generatedFiles.systemFilePath) {
|
|
473
|
+
try {
|
|
474
|
+
mergeRbacAfterWizardFilesWritten(generatedFiles, format);
|
|
475
|
+
} catch (e) {
|
|
476
|
+
logger.log(chalk.yellow(` Could not generate RBAC file: ${e.message}`));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
432
479
|
if (systemKey && dataplaneUrl && authConfig && generatedFiles.appPath) {
|
|
433
480
|
try {
|
|
434
481
|
await tryUpdateReadmeFromDeploymentDocs(generatedFiles.appPath, appName, dataplaneUrl, authConfig, systemKey);
|
|
@@ -437,12 +484,7 @@ async function handleFileSaving(appName, systemConfig, datasourceConfigs, system
|
|
|
437
484
|
}
|
|
438
485
|
}
|
|
439
486
|
spinner.stop();
|
|
440
|
-
|
|
441
|
-
logger.log(chalk.green(`\nFiles created in: ${generatedFiles.appPath}`));
|
|
442
|
-
logger.log(chalk.blue('\nNext steps:'));
|
|
443
|
-
logger.log(chalk.gray(` 1. Review the generated files in integration/${appName}/`));
|
|
444
|
-
logger.log(chalk.gray(' 2. Update env.template with your authentication details'));
|
|
445
|
-
logger.log(chalk.gray(` 3. Deploy using: node deploy.js or aifabrix deploy ${appName}`));
|
|
487
|
+
logWizardFileSaveFooter(appName, generatedFiles);
|
|
446
488
|
return generatedFiles;
|
|
447
489
|
} catch (error) {
|
|
448
490
|
spinner.stop();
|
|
@@ -73,10 +73,12 @@ function createDataplaneNotFoundError() {
|
|
|
73
73
|
* @returns {Promise<string>} Dataplane URL
|
|
74
74
|
* @throws {Error} If dataplane URL cannot be retrieved
|
|
75
75
|
*/
|
|
76
|
-
async function tryFallbackDataplaneUrl(controllerUrl, environment, authConfig) {
|
|
76
|
+
async function tryFallbackDataplaneUrl(controllerUrl, environment, authConfig, silent) {
|
|
77
77
|
try {
|
|
78
78
|
const fallbackUrl = await getDataplaneUrl(controllerUrl, 'dataplane', environment, authConfig);
|
|
79
|
-
|
|
79
|
+
if (!silent) {
|
|
80
|
+
logger.log(formatSuccessLine(`Dataplane URL: ${fallbackUrl}`));
|
|
81
|
+
}
|
|
80
82
|
return fallbackUrl;
|
|
81
83
|
} catch (fallbackError) {
|
|
82
84
|
if (isNotFoundError(fallbackError)) {
|
|
@@ -93,19 +95,25 @@ async function tryFallbackDataplaneUrl(controllerUrl, environment, authConfig) {
|
|
|
93
95
|
* @param {string} controllerUrl - Controller URL
|
|
94
96
|
* @param {string} environment - Environment key
|
|
95
97
|
* @param {Object} authConfig - Authentication configuration
|
|
98
|
+
* @param {{ silent?: boolean }} [opts] - When silent, skip progress/success lines (e.g. cert sync right after a run).
|
|
96
99
|
* @returns {Promise<string>} Dataplane URL
|
|
97
100
|
* @throws {Error} If dataplane URL cannot be discovered
|
|
98
101
|
*/
|
|
99
|
-
async function discoverDataplaneUrl(controllerUrl, environment, authConfig) {
|
|
100
|
-
|
|
102
|
+
async function discoverDataplaneUrl(controllerUrl, environment, authConfig, opts = {}) {
|
|
103
|
+
const silent = opts.silent === true;
|
|
104
|
+
if (!silent) {
|
|
105
|
+
logger.log(infoLine('🌐 Getting dataplane URL from controller...'));
|
|
106
|
+
}
|
|
101
107
|
try {
|
|
102
108
|
const dataplaneAppKey = await findDataplaneServiceAppKey(controllerUrl, environment, authConfig);
|
|
103
109
|
if (dataplaneAppKey) {
|
|
104
110
|
const dataplaneUrl = await getDataplaneUrl(controllerUrl, dataplaneAppKey, environment, authConfig);
|
|
105
|
-
|
|
111
|
+
if (!silent) {
|
|
112
|
+
logger.log(formatSuccessLine(`Dataplane URL: ${dataplaneUrl}`));
|
|
113
|
+
}
|
|
106
114
|
return dataplaneUrl;
|
|
107
115
|
}
|
|
108
|
-
return await tryFallbackDataplaneUrl(controllerUrl, environment, authConfig);
|
|
116
|
+
return await tryFallbackDataplaneUrl(controllerUrl, environment, authConfig, silent);
|
|
109
117
|
} catch (error) {
|
|
110
118
|
if (error.message.includes('Could not discover dataplane URL')) {
|
|
111
119
|
throw error;
|
|
@@ -10,30 +10,87 @@ const { discoverEntities } = require('../api/wizard.api');
|
|
|
10
10
|
const { validateEntityNameForOpenApi } = require('../validation/wizard-datasource-validation');
|
|
11
11
|
const { promptForEntitySelection } = require('../generator/wizard-prompts');
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* If wizard.yaml entity name matches discover-entities list, use it; else warn.
|
|
15
|
+
* @param {string} trimmed - Trimmed entity name from prefill
|
|
16
|
+
* @param {Array<{name: string}>} entities - Discovered entities
|
|
17
|
+
* @returns {string|null} Resolved name or null to prompt
|
|
18
|
+
*/
|
|
19
|
+
function resolvePrefillEntityName(trimmed, entities) {
|
|
20
|
+
const prefillCheck = validateEntityNameForOpenApi(trimmed, entities);
|
|
21
|
+
if (prefillCheck.valid) {
|
|
22
|
+
logger.log(chalk.gray(
|
|
23
|
+
`Using entity from wizard.yaml (${trimmed}). Skipping entity prompts.`
|
|
24
|
+
));
|
|
25
|
+
logger.log(chalk.green(`\u2713 Selected entity: ${trimmed}`));
|
|
26
|
+
return trimmed;
|
|
27
|
+
}
|
|
28
|
+
logger.log(chalk.yellow(
|
|
29
|
+
`Warning: wizard.yaml source.entityName '${trimmed}' is not in the discover-entities list; choose manually.`
|
|
30
|
+
));
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Prompt for entity and validate against list.
|
|
36
|
+
* @param {Array<{name: string}>} entities - Discovered entities
|
|
37
|
+
* @returns {Promise<string>} Valid entity name
|
|
38
|
+
*/
|
|
39
|
+
async function promptForValidatedEntity(entities) {
|
|
40
|
+
const entityName = await promptForEntitySelection(entities);
|
|
41
|
+
const validation = validateEntityNameForOpenApi(entityName, entities);
|
|
42
|
+
if (!validation.valid) {
|
|
43
|
+
throw new Error(`Invalid entity '${entityName}'. Available: ${entities.map(e => e.name).join(', ')}`);
|
|
44
|
+
}
|
|
45
|
+
logger.log(chalk.green(`\u2713 Selected entity: ${entityName}`));
|
|
46
|
+
return entityName;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Discover entities and select one (single-entity shortcut, prefill, or prompt).
|
|
51
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
52
|
+
* @param {Object} authConfig - Authentication configuration
|
|
53
|
+
* @param {Object} openapiSpec - OpenAPI specification
|
|
54
|
+
* @param {string} [prefillEntityName] - From wizard.yaml `source.entityName` when valid
|
|
55
|
+
* @returns {Promise<string|null>} Selected entity name or null (skip)
|
|
56
|
+
*/
|
|
57
|
+
async function discoverAndSelectEntity(dataplaneUrl, authConfig, openapiSpec, prefillEntityName) {
|
|
58
|
+
const response = await discoverEntities(dataplaneUrl, authConfig, openapiSpec);
|
|
59
|
+
const entities = response?.data?.entities;
|
|
60
|
+
if (!Array.isArray(entities) || entities.length === 0) return null;
|
|
61
|
+
|
|
62
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 4.5: Select Entity'));
|
|
63
|
+
|
|
64
|
+
if (entities.length === 1) {
|
|
65
|
+
const only = entities[0].name;
|
|
66
|
+
logger.log(chalk.green(`\u2713 Only one entity discovered; using: ${only}`));
|
|
67
|
+
return only;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const trimmed =
|
|
71
|
+
typeof prefillEntityName === 'string' ? prefillEntityName.trim() : '';
|
|
72
|
+
if (trimmed) {
|
|
73
|
+
const resolved = resolvePrefillEntityName(trimmed, entities);
|
|
74
|
+
if (resolved) return resolved;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return promptForValidatedEntity(entities);
|
|
78
|
+
}
|
|
79
|
+
|
|
13
80
|
/**
|
|
14
81
|
* Handle entity selection step (OpenAPI multi-entity).
|
|
15
|
-
* Calls discover-entities;
|
|
82
|
+
* Calls discover-entities; prompts unless prefill or a single entity applies.
|
|
16
83
|
* @async
|
|
17
84
|
* @param {string} dataplaneUrl - Dataplane URL
|
|
18
85
|
* @param {Object} authConfig - Authentication configuration
|
|
19
86
|
* @param {Object} openapiSpec - OpenAPI specification
|
|
87
|
+
* @param {string} [prefillEntityName] - From wizard.yaml `source.entityName` when valid
|
|
20
88
|
* @returns {Promise<string|null>} Selected entity name or null (skip)
|
|
21
89
|
*/
|
|
22
|
-
async function handleEntitySelection(dataplaneUrl, authConfig, openapiSpec) {
|
|
90
|
+
async function handleEntitySelection(dataplaneUrl, authConfig, openapiSpec, prefillEntityName) {
|
|
23
91
|
if (!openapiSpec || typeof openapiSpec !== 'object') return null;
|
|
24
92
|
try {
|
|
25
|
-
|
|
26
|
-
const entities = response?.data?.entities;
|
|
27
|
-
if (!Array.isArray(entities) || entities.length === 0) return null;
|
|
28
|
-
|
|
29
|
-
logger.log(chalk.blue('\n\uD83D\uDCCB Step 4.5: Select Entity'));
|
|
30
|
-
const entityName = await promptForEntitySelection(entities);
|
|
31
|
-
const validation = validateEntityNameForOpenApi(entityName, entities);
|
|
32
|
-
if (!validation.valid) {
|
|
33
|
-
throw new Error(`Invalid entity '${entityName}'. Available: ${entities.map(e => e.name).join(', ')}`);
|
|
34
|
-
}
|
|
35
|
-
logger.log(chalk.green(`\u2713 Selected entity: ${entityName}`));
|
|
36
|
-
return entityName;
|
|
93
|
+
return await discoverAndSelectEntity(dataplaneUrl, authConfig, openapiSpec, prefillEntityName);
|
|
37
94
|
} catch (error) {
|
|
38
95
|
logger.log(chalk.yellow(`Warning: Entity discovery failed, using default: ${error.message}`));
|
|
39
96
|
return null;
|
|
@@ -111,8 +111,11 @@ async function executeWizardFromConfig(wizardConfig, dataplaneUrl, authConfig, o
|
|
|
111
111
|
systemConfig,
|
|
112
112
|
datasourceConfigs,
|
|
113
113
|
systemKey || appName,
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
{
|
|
115
|
+
dataplaneUrl,
|
|
116
|
+
authConfig,
|
|
117
|
+
enableRBAC: Boolean(preferences?.enableRBAC)
|
|
118
|
+
}
|
|
116
119
|
);
|
|
117
120
|
}
|
|
118
121
|
|
|
@@ -47,6 +47,12 @@ function buildSourceForSave(source) {
|
|
|
47
47
|
out.token = source.token ? '(set)' : undefined;
|
|
48
48
|
}
|
|
49
49
|
if (source.type === 'known-platform' && source.platform) out.platform = source.platform;
|
|
50
|
+
if (
|
|
51
|
+
(source.type === 'openapi-file' || source.type === 'openapi-url') &&
|
|
52
|
+
source.entityName
|
|
53
|
+
) {
|
|
54
|
+
out.entityName = source.entityName;
|
|
55
|
+
}
|
|
50
56
|
return out;
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -75,7 +81,13 @@ function buildWizardStateForSave(opts) {
|
|
|
75
81
|
function formatSourceLine(source) {
|
|
76
82
|
if (!source) return null;
|
|
77
83
|
const s = source;
|
|
78
|
-
|
|
84
|
+
let line =
|
|
85
|
+
s.type +
|
|
86
|
+
(s.filePath ? ` (${s.filePath})` : s.url ? ` (${s.url})` : s.platform ? ` (${s.platform})` : '');
|
|
87
|
+
if (s.entityName && (s.type === 'openapi-file' || s.type === 'openapi-url')) {
|
|
88
|
+
line += ` [entity: ${s.entityName}]`;
|
|
89
|
+
}
|
|
90
|
+
return line;
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
/**
|
package/lib/commands/wizard.js
CHANGED
|
@@ -26,6 +26,7 @@ const {
|
|
|
26
26
|
validateAndCheckAppDirectory,
|
|
27
27
|
formatDataplaneRejectedTokenMessage,
|
|
28
28
|
extractSessionId,
|
|
29
|
+
handleSourceSelection,
|
|
29
30
|
handleOpenApiParsing,
|
|
30
31
|
handleCredentialSelection,
|
|
31
32
|
handleTypeDetection,
|
|
@@ -50,6 +51,149 @@ const {
|
|
|
50
51
|
} = require('./wizard-helpers');
|
|
51
52
|
const { humanizeAppKey } = require('../generator/wizard-prompts-secondary');
|
|
52
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Map resolved source type/data onto wizard state.source.
|
|
56
|
+
* @param {Object} state - Mutable wizard state
|
|
57
|
+
* @param {string} sourceType - Source type key
|
|
58
|
+
* @param {unknown} sourceData - Raw source payload from prompts
|
|
59
|
+
*/
|
|
60
|
+
function applySourceSelectionToState(state, sourceType, sourceData) {
|
|
61
|
+
state.source = { type: sourceType };
|
|
62
|
+
if (sourceType === 'openapi-file') state.source.filePath = sourceData;
|
|
63
|
+
else if (sourceType === 'openapi-url') state.source.url = sourceData;
|
|
64
|
+
else if (sourceType === 'mcp-server') state.source.serverUrl = JSON.parse(sourceData).serverUrl;
|
|
65
|
+
else if (sourceType === 'known-platform') state.source.platform = sourceData;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Interactive or prefill source selection (Step 2).
|
|
70
|
+
* @returns {Promise<{ sourceType: string, sourceData: unknown }>}
|
|
71
|
+
*/
|
|
72
|
+
async function resolveWizardSourcePhase(dataplaneUrl, sessionId, authConfig, platforms, prefill) {
|
|
73
|
+
if (prefill?.source?.type) {
|
|
74
|
+
logger.log(chalk.gray(
|
|
75
|
+
`Using source from wizard.yaml (${prefill.source.type}). Skipping source prompts.`
|
|
76
|
+
));
|
|
77
|
+
return handleSourceSelection(dataplaneUrl, sessionId, authConfig, prefill.source);
|
|
78
|
+
}
|
|
79
|
+
return handleInteractiveSourceSelection(dataplaneUrl, sessionId, authConfig, platforms);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Credential selection with optional prefill (Step 3).
|
|
84
|
+
* @returns {Promise<string>} credentialIdOrKey for generation step
|
|
85
|
+
*/
|
|
86
|
+
async function resolveWizardCredentialPhase(dataplaneUrl, authConfig, prefill, state) {
|
|
87
|
+
if (prefill?.credential) {
|
|
88
|
+
state.credential = prefill.credential;
|
|
89
|
+
return handleCredentialSelection(dataplaneUrl, authConfig, prefill.credential);
|
|
90
|
+
}
|
|
91
|
+
const credentialAction = await promptForCredentialAction();
|
|
92
|
+
const configCredential = await resolveCredentialConfig(dataplaneUrl, authConfig, credentialAction);
|
|
93
|
+
state.credential = configCredential;
|
|
94
|
+
return handleCredentialSelection(dataplaneUrl, authConfig, configCredential);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Collect user intent and UI preferences (Step 5), with optional wizard.yaml prefill.
|
|
99
|
+
* @returns {Promise<{ userIntent: string, preferences: Object, hasPrefillIntent: boolean }>}
|
|
100
|
+
*/
|
|
101
|
+
async function collectIntentAndPreferences(preferencesPrefill) {
|
|
102
|
+
const prefillPrefs = preferencesPrefill;
|
|
103
|
+
const hasPrefillIntent =
|
|
104
|
+
prefillPrefs &&
|
|
105
|
+
typeof prefillPrefs.intent === 'string' &&
|
|
106
|
+
prefillPrefs.intent.trim().length > 0;
|
|
107
|
+
|
|
108
|
+
if (hasPrefillIntent) {
|
|
109
|
+
logger.log(chalk.gray(
|
|
110
|
+
'Using preferences from wizard.yaml (intent and toggles). Skipping preference prompts.'
|
|
111
|
+
));
|
|
112
|
+
const level = prefillPrefs.fieldOnboardingLevel;
|
|
113
|
+
const validLevel = level === 'standard' || level === 'minimal' ? level : 'full';
|
|
114
|
+
return {
|
|
115
|
+
userIntent: prefillPrefs.intent.trim(),
|
|
116
|
+
preferences: {
|
|
117
|
+
fieldOnboardingLevel: validLevel,
|
|
118
|
+
mcp: Boolean(prefillPrefs.enableMCP),
|
|
119
|
+
abac: Boolean(prefillPrefs.enableABAC),
|
|
120
|
+
rbac: Boolean(prefillPrefs.enableRBAC)
|
|
121
|
+
},
|
|
122
|
+
hasPrefillIntent: true
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const userIntent = await promptForUserIntent();
|
|
127
|
+
const preferences = await promptForUserPreferences();
|
|
128
|
+
return { userIntent, preferences, hasPrefillIntent: false };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Run Step 5 (generate), Step 6–7 (review), and save files; updates state.preferences.
|
|
133
|
+
* @param {Object} payload
|
|
134
|
+
* @param {string} payload.appKey - Application key
|
|
135
|
+
* @param {string} payload.dataplaneUrl - Dataplane URL
|
|
136
|
+
* @param {Object} payload.authConfig - Auth configuration
|
|
137
|
+
* @param {string} payload.sessionId - Wizard session ID
|
|
138
|
+
* @param {Object} payload.state - Mutable wizard state
|
|
139
|
+
* @param {Object} payload.flowOpts - Flow options (mode, systemIdOrKey, debug, prefill, etc.)
|
|
140
|
+
* @param {Object} payload.genInput - Generation inputs (openapiSpec, detectedType, credential…)
|
|
141
|
+
* @returns {Promise<Object|null>} Updated state or null if review cancelled
|
|
142
|
+
*/
|
|
143
|
+
async function completeWizardGenerateReviewSave(payload) {
|
|
144
|
+
const {
|
|
145
|
+
appKey,
|
|
146
|
+
dataplaneUrl,
|
|
147
|
+
authConfig,
|
|
148
|
+
sessionId,
|
|
149
|
+
state,
|
|
150
|
+
flowOpts,
|
|
151
|
+
genInput
|
|
152
|
+
} = payload;
|
|
153
|
+
const { mode, systemIdOrKey, debug, prefill } = flowOpts;
|
|
154
|
+
const genResult = await handleInteractiveConfigGeneration({
|
|
155
|
+
dataplaneUrl,
|
|
156
|
+
authConfig,
|
|
157
|
+
mode,
|
|
158
|
+
openapiSpec: genInput.openapiSpec,
|
|
159
|
+
detectedType: genInput.detectedType,
|
|
160
|
+
credentialIdOrKey: genInput.credentialIdOrKey,
|
|
161
|
+
systemIdOrKey,
|
|
162
|
+
sourceType: genInput.sourceType,
|
|
163
|
+
sourceData: genInput.sourceData,
|
|
164
|
+
entityName: genInput.entityName,
|
|
165
|
+
appName: appKey,
|
|
166
|
+
debug,
|
|
167
|
+
systemDisplayName: flowOpts.systemDisplayName,
|
|
168
|
+
preferencesPrefill: prefill?.preferences
|
|
169
|
+
});
|
|
170
|
+
const { systemConfig, datasourceConfigs, systemKey, preferences: savedPrefs } = genResult;
|
|
171
|
+
state.preferences = savedPrefs || {};
|
|
172
|
+
|
|
173
|
+
const finalConfigs = await handleConfigurationReview(
|
|
174
|
+
dataplaneUrl,
|
|
175
|
+
authConfig,
|
|
176
|
+
sessionId,
|
|
177
|
+
systemConfig,
|
|
178
|
+
datasourceConfigs,
|
|
179
|
+
{ appKey, debug }
|
|
180
|
+
);
|
|
181
|
+
if (!finalConfigs) return null;
|
|
182
|
+
|
|
183
|
+
await handleFileSaving(
|
|
184
|
+
appKey,
|
|
185
|
+
finalConfigs.systemConfig,
|
|
186
|
+
finalConfigs.datasourceConfigs,
|
|
187
|
+
systemKey || appKey,
|
|
188
|
+
{
|
|
189
|
+
dataplaneUrl,
|
|
190
|
+
authConfig,
|
|
191
|
+
enableRBAC: Boolean(savedPrefs?.enableRBAC)
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
return state;
|
|
195
|
+
}
|
|
196
|
+
|
|
53
197
|
/**
|
|
54
198
|
* Create wizard session with given mode and optional systemIdOrKey (no prompts)
|
|
55
199
|
* @async
|
|
@@ -125,12 +269,15 @@ async function handleInteractiveSourceSelection(dataplaneUrl, sessionId, authCon
|
|
|
125
269
|
* @param {Object} options.detectedType - Detected type info
|
|
126
270
|
* @param {string} [options.credentialIdOrKey] - Credential ID or key (optional)
|
|
127
271
|
* @param {string} [options.systemIdOrKey] - System ID or key (optional)
|
|
272
|
+
* @param {Object} [options.preferencesPrefill] - From wizard.yaml `preferences` (skip Step 5 prompts when intent is set)
|
|
128
273
|
* @returns {Promise<Object>} Generated configuration and preferences { systemConfig, datasourceConfigs, systemKey, preferences }
|
|
129
274
|
*/
|
|
130
275
|
async function handleInteractiveConfigGeneration(options) {
|
|
131
276
|
logger.log(chalk.blue('\n\uD83D\uDCCB Step 5: User Preferences'));
|
|
132
|
-
|
|
133
|
-
const preferences = await
|
|
277
|
+
|
|
278
|
+
const { userIntent, preferences, hasPrefillIntent } = await collectIntentAndPreferences(
|
|
279
|
+
options.preferencesPrefill
|
|
280
|
+
);
|
|
134
281
|
|
|
135
282
|
const configPrefs = {
|
|
136
283
|
intent: userIntent,
|
|
@@ -138,6 +285,9 @@ async function handleInteractiveConfigGeneration(options) {
|
|
|
138
285
|
enableMCP: preferences.mcp,
|
|
139
286
|
enableABAC: preferences.abac,
|
|
140
287
|
enableRBAC: preferences.rbac,
|
|
288
|
+
enableOpenAPIGeneration: hasPrefillIntent
|
|
289
|
+
? options.preferencesPrefill?.enableOpenAPIGeneration !== false
|
|
290
|
+
: true,
|
|
141
291
|
debug: options.debug === true
|
|
142
292
|
};
|
|
143
293
|
|
|
@@ -222,62 +372,43 @@ async function handleConfigurationReview(dataplaneUrl, authConfig, sessionId, sy
|
|
|
222
372
|
* @returns {Promise<Object>} Collected state (source, credential, preferences) for wizard.yaml save
|
|
223
373
|
*/
|
|
224
374
|
async function doWizardSteps(appKey, dataplaneUrl, authConfig, sessionId, flowOpts, state) {
|
|
225
|
-
const {
|
|
226
|
-
|
|
227
|
-
|
|
375
|
+
const { platforms, prefill } = flowOpts;
|
|
376
|
+
|
|
377
|
+
const { sourceType, sourceData } = await resolveWizardSourcePhase(
|
|
378
|
+
dataplaneUrl,
|
|
379
|
+
sessionId,
|
|
380
|
+
authConfig,
|
|
381
|
+
platforms,
|
|
382
|
+
prefill
|
|
228
383
|
);
|
|
229
|
-
state
|
|
230
|
-
if (sourceType === 'openapi-file') state.source.filePath = sourceData;
|
|
231
|
-
else if (sourceType === 'openapi-url') state.source.url = sourceData;
|
|
232
|
-
else if (sourceType === 'mcp-server') state.source.serverUrl = JSON.parse(sourceData).serverUrl;
|
|
233
|
-
else if (sourceType === 'known-platform') state.source.platform = sourceData;
|
|
384
|
+
applySourceSelectionToState(state, sourceType, sourceData);
|
|
234
385
|
|
|
235
386
|
const openapiSpec = await handleOpenApiParsing(dataplaneUrl, authConfig, sourceType, sourceData);
|
|
236
|
-
const
|
|
237
|
-
const configCredential = await resolveCredentialConfig(dataplaneUrl, authConfig, credentialAction);
|
|
238
|
-
state.credential = configCredential;
|
|
239
|
-
const credentialIdOrKey = await handleCredentialSelection(dataplaneUrl, authConfig, configCredential);
|
|
387
|
+
const credentialIdOrKey = await resolveWizardCredentialPhase(dataplaneUrl, authConfig, prefill, state);
|
|
240
388
|
|
|
241
389
|
const detectedType = await handleTypeDetection(dataplaneUrl, authConfig, openapiSpec);
|
|
390
|
+
const prefillEntityName = prefill?.source?.entityName;
|
|
242
391
|
const entityName = openapiSpec && sourceType !== 'known-platform'
|
|
243
|
-
? await handleEntitySelection(dataplaneUrl, authConfig, openapiSpec) : null;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
detectedType,
|
|
250
|
-
credentialIdOrKey,
|
|
251
|
-
systemIdOrKey,
|
|
252
|
-
sourceType,
|
|
253
|
-
sourceData,
|
|
254
|
-
entityName: entityName || undefined,
|
|
255
|
-
appName: appKey,
|
|
256
|
-
debug,
|
|
257
|
-
systemDisplayName: flowOpts.systemDisplayName
|
|
258
|
-
});
|
|
259
|
-
const { systemConfig, datasourceConfigs, systemKey, preferences: savedPrefs } = genResult;
|
|
260
|
-
state.preferences = savedPrefs || {};
|
|
261
|
-
|
|
262
|
-
const finalConfigs = await handleConfigurationReview(
|
|
392
|
+
? await handleEntitySelection(dataplaneUrl, authConfig, openapiSpec, prefillEntityName) : null;
|
|
393
|
+
if (entityName && state.source) {
|
|
394
|
+
state.source.entityName = entityName;
|
|
395
|
+
}
|
|
396
|
+
return completeWizardGenerateReviewSave({
|
|
397
|
+
appKey,
|
|
263
398
|
dataplaneUrl,
|
|
264
399
|
authConfig,
|
|
265
400
|
sessionId,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
dataplaneUrl,
|
|
278
|
-
authConfig
|
|
279
|
-
);
|
|
280
|
-
return state;
|
|
401
|
+
state,
|
|
402
|
+
flowOpts,
|
|
403
|
+
genInput: {
|
|
404
|
+
openapiSpec,
|
|
405
|
+
detectedType,
|
|
406
|
+
credentialIdOrKey,
|
|
407
|
+
sourceType,
|
|
408
|
+
sourceData,
|
|
409
|
+
entityName: entityName || undefined
|
|
410
|
+
}
|
|
411
|
+
});
|
|
281
412
|
}
|
|
282
413
|
|
|
283
414
|
async function runWizardStepsAfterSession(appKey, dataplaneUrl, authConfig, sessionId, flowOpts) {
|
|
@@ -317,7 +448,7 @@ async function runWizardStepsAfterSession(appKey, dataplaneUrl, authConfig, sess
|
|
|
317
448
|
* @returns {Promise<void>} Resolves when wizard flow completes
|
|
318
449
|
*/
|
|
319
450
|
async function executeWizardFlow(appKey, dataplaneUrl, authConfig, flowOpts = {}) {
|
|
320
|
-
const { mode, systemIdOrKey, configPath, debug, systemDisplayName } = flowOpts;
|
|
451
|
+
const { mode, systemIdOrKey, configPath, debug, systemDisplayName, prefill } = flowOpts;
|
|
321
452
|
|
|
322
453
|
if (debug) {
|
|
323
454
|
logger.log(chalk.gray(`[DEBUG] Wizard debug mode enabled for app: ${appKey}`));
|
|
@@ -333,7 +464,8 @@ async function executeWizardFlow(appKey, dataplaneUrl, authConfig, flowOpts = {}
|
|
|
333
464
|
platforms,
|
|
334
465
|
configPath,
|
|
335
466
|
debug: flowOpts.debug,
|
|
336
|
-
systemDisplayName
|
|
467
|
+
systemDisplayName,
|
|
468
|
+
prefill
|
|
337
469
|
});
|
|
338
470
|
if (!state) return;
|
|
339
471
|
|
|
@@ -348,6 +480,11 @@ async function executeWizardFlow(appKey, dataplaneUrl, authConfig, flowOpts = {}
|
|
|
348
480
|
* @param {string} [appName] - App name (for log message)
|
|
349
481
|
* @returns {Promise<Object|null>} Loaded config or null
|
|
350
482
|
*/
|
|
483
|
+
/**
|
|
484
|
+
* Load wizard.yaml when present. Returns full valid config, or invalid-but-parsed config for interactive prefill.
|
|
485
|
+
*
|
|
486
|
+
* @returns {Promise<{ valid: true, config: Object } | { valid: false, config: Object, errors: string[] } | null>}
|
|
487
|
+
*/
|
|
351
488
|
async function loadWizardConfigIfExists(configPath, appName) {
|
|
352
489
|
if (!configPath) return null;
|
|
353
490
|
const displayPath = appName ? `integration/${appName}/wizard.yaml` : configPath;
|
|
@@ -360,10 +497,16 @@ async function loadWizardConfigIfExists(configPath, appName) {
|
|
|
360
497
|
const result = await validateWizardConfig(configPath, { validateFilePaths: false });
|
|
361
498
|
if (result.valid && result.config) {
|
|
362
499
|
logger.log(chalk.green(`Loaded saved state from ${displayPath}. Resuming with saved choices.`));
|
|
363
|
-
return result.config;
|
|
500
|
+
return { valid: true, config: result.config };
|
|
364
501
|
}
|
|
365
502
|
if (result.errors?.length) {
|
|
366
|
-
logger.log(chalk.yellow(
|
|
503
|
+
logger.log(chalk.yellow(
|
|
504
|
+
`Loaded ${displayPath} but it does not fully validate; prefilling from file where possible.`
|
|
505
|
+
));
|
|
506
|
+
result.errors.forEach(err => logger.log(chalk.gray(` • ${err}`)));
|
|
507
|
+
}
|
|
508
|
+
if (result.config) {
|
|
509
|
+
return { valid: false, config: result.config, errors: result.errors || [] };
|
|
367
510
|
}
|
|
368
511
|
} catch (e) {
|
|
369
512
|
logger.log(chalk.gray(`Could not load wizard config from ${displayPath}: ${e.message}`));
|
|
@@ -470,15 +613,16 @@ async function handleWizardWithSavedConfig(options, loadedConfig, displayPath) {
|
|
|
470
613
|
}
|
|
471
614
|
|
|
472
615
|
async function handleWizardInteractive(options) {
|
|
616
|
+
const prefill = options.wizardPrefill;
|
|
473
617
|
const allowAddDatasource = !options.app;
|
|
474
618
|
const mode = allowAddDatasource ? await promptForMode(undefined, true) : 'create-system';
|
|
475
619
|
const resolved = mode === 'create-system'
|
|
476
|
-
? await resolveCreateNewPath(options, null)
|
|
477
|
-
: await resolveAddDatasourcePath(options, null);
|
|
620
|
+
? await resolveCreateNewPath(options, prefill || null)
|
|
621
|
+
: await resolveAddDatasourcePath(options, prefill || null);
|
|
478
622
|
if (!resolved) return;
|
|
479
623
|
const { appKey, configPath, dataplaneUrl, authConfig } = resolved;
|
|
480
624
|
const systemIdOrKey = mode === 'add-datasource' ? resolved.systemIdOrKey : undefined;
|
|
481
|
-
const systemDisplayName = options.systemDisplayName || options.displayName ||
|
|
625
|
+
const systemDisplayName = options.systemDisplayName || options.displayName || prefill?.systemDisplayName ||
|
|
482
626
|
(mode === 'create-system' ? humanizeAppKey(appKey) : undefined);
|
|
483
627
|
try {
|
|
484
628
|
await executeWizardFlow(appKey, dataplaneUrl, authConfig, {
|
|
@@ -486,7 +630,8 @@ async function handleWizardInteractive(options) {
|
|
|
486
630
|
systemIdOrKey,
|
|
487
631
|
configPath,
|
|
488
632
|
debug: options.debug,
|
|
489
|
-
systemDisplayName
|
|
633
|
+
systemDisplayName,
|
|
634
|
+
prefill: prefill || undefined
|
|
490
635
|
});
|
|
491
636
|
logger.log(chalk.gray(`To change settings, edit integration/${appKey}/wizard.yaml and run: aifabrix wizard ${appKey}`));
|
|
492
637
|
} catch (error) {
|
|
@@ -504,9 +649,12 @@ async function handleWizard(options = {}) {
|
|
|
504
649
|
return await handleWizardSilent(options);
|
|
505
650
|
}
|
|
506
651
|
logger.log(chalk.blue('\n\uD83E\uDDD9 AI Fabrix External System Wizard\n'));
|
|
507
|
-
const
|
|
508
|
-
if (
|
|
509
|
-
return await handleWizardWithSavedConfig(options,
|
|
652
|
+
const loadResult = await loadWizardConfigIfExists(options.configPath, options.app);
|
|
653
|
+
if (loadResult?.valid && loadResult.config) {
|
|
654
|
+
return await handleWizardWithSavedConfig(options, loadResult.config, displayPath);
|
|
655
|
+
}
|
|
656
|
+
if (loadResult?.config && loadResult.valid === false) {
|
|
657
|
+
return await handleWizardInteractive({ ...options, wizardPrefill: loadResult.config });
|
|
510
658
|
}
|
|
511
659
|
return await handleWizardInteractive(options);
|
|
512
660
|
}
|