@aifabrix/builder 2.33.6 → 2.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/integration/hubspot/README.md +47 -0
- package/integration/hubspot/env.template +6 -1
- package/integration/hubspot/hubspot-deploy.json +0 -6
- package/integration/hubspot/hubspot-system.json +0 -6
- package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
- package/integration/hubspot/test-dataplane-down-helpers.js +4 -1
- package/integration/hubspot/test-dataplane-down-tests.js +5 -33
- package/integration/hubspot/test-dataplane-down.js +60 -8
- package/integration/hubspot/test.js +53 -19
- package/integration/hubspot/wizard-hubspot-e2e.yaml +1 -1
- package/lib/api/wizard.api.js +24 -1
- package/lib/app/config.js +0 -1
- package/lib/app/show-display.js +210 -0
- package/lib/app/show.js +642 -0
- package/lib/cli.js +28 -7
- package/lib/commands/app.js +15 -0
- package/lib/commands/wizard-core-helpers.js +278 -0
- package/lib/commands/wizard-core.js +26 -145
- package/lib/commands/wizard-headless.js +2 -2
- package/lib/commands/wizard-helpers.js +152 -0
- package/lib/commands/wizard.js +276 -68
- package/lib/generator/index.js +32 -0
- package/lib/generator/wizard-prompts.js +124 -45
- package/lib/schema/env-config.yaml +1 -3
- package/lib/utils/cli-utils.js +40 -1
- package/lib/utils/token-manager.js +30 -22
- package/lib/validation/wizard-config-validator.js +35 -0
- package/package.json +2 -2
|
@@ -0,0 +1,152 @@
|
|
|
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 (fieldOnboardingLevel, mcp, abac, rbac)
|
|
16
|
+
* @returns {Object} Preferences for wizard-config schema
|
|
17
|
+
*/
|
|
18
|
+
function buildPreferencesForSave(intent, preferences) {
|
|
19
|
+
const level = preferences?.fieldOnboardingLevel;
|
|
20
|
+
const validLevel = level === 'standard' || level === 'minimal' ? level : 'full';
|
|
21
|
+
return {
|
|
22
|
+
intent: intent || 'general integration',
|
|
23
|
+
fieldOnboardingLevel: validLevel,
|
|
24
|
+
enableOpenAPIGeneration: true,
|
|
25
|
+
enableMCP: Boolean(preferences?.mcp),
|
|
26
|
+
enableABAC: Boolean(preferences?.abac),
|
|
27
|
+
enableRBAC: Boolean(preferences?.rbac)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build source object for wizard.yaml (no secrets)
|
|
33
|
+
* @param {Object} [source] - Source state
|
|
34
|
+
* @returns {Object|undefined}
|
|
35
|
+
*/
|
|
36
|
+
function buildSourceForSave(source) {
|
|
37
|
+
if (!source) return undefined;
|
|
38
|
+
const out = { type: source.type };
|
|
39
|
+
if (source.type === 'openapi-file' && source.filePath) out.filePath = source.filePath;
|
|
40
|
+
if (source.type === 'openapi-url' && source.url) out.url = source.url;
|
|
41
|
+
if (source.type === 'mcp-server' && source.serverUrl) {
|
|
42
|
+
out.serverUrl = source.serverUrl;
|
|
43
|
+
out.token = source.token ? '(set)' : undefined;
|
|
44
|
+
}
|
|
45
|
+
if (source.type === 'known-platform' && source.platform) out.platform = source.platform;
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Build partial wizard state for saving to wizard.yaml (no secrets)
|
|
51
|
+
* @param {Object} opts - Collected state
|
|
52
|
+
* @returns {Object} Serializable wizard config shape
|
|
53
|
+
*/
|
|
54
|
+
function buildWizardStateForSave(opts) {
|
|
55
|
+
const state = {
|
|
56
|
+
appName: opts.appKey,
|
|
57
|
+
mode: opts.mode,
|
|
58
|
+
source: buildSourceForSave(opts.source)
|
|
59
|
+
};
|
|
60
|
+
if (opts.mode === 'add-datasource' && opts.systemIdOrKey) state.systemIdOrKey = opts.systemIdOrKey;
|
|
61
|
+
if (opts.credential) state.credential = opts.credential;
|
|
62
|
+
if (opts.preferences) state.preferences = opts.preferences;
|
|
63
|
+
return state;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Format source config as a short line for display
|
|
68
|
+
* @param {Object} [source] - Source config
|
|
69
|
+
* @returns {string|null}
|
|
70
|
+
*/
|
|
71
|
+
function formatSourceLine(source) {
|
|
72
|
+
if (!source) return null;
|
|
73
|
+
const s = source;
|
|
74
|
+
return s.type + (s.filePath ? ` (${s.filePath})` : s.url ? ` (${s.url})` : s.platform ? ` (${s.platform})` : '');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Format preferences config as a short line for display
|
|
79
|
+
* @param {Object} [preferences] - Preferences config
|
|
80
|
+
* @returns {string|null}
|
|
81
|
+
*/
|
|
82
|
+
function formatPreferencesLine(preferences) {
|
|
83
|
+
if (!preferences || (!preferences.intent && (preferences.enableMCP === undefined || preferences.enableMCP === null) && !preferences.fieldOnboardingLevel)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const p = preferences;
|
|
87
|
+
const parts = [
|
|
88
|
+
p.fieldOnboardingLevel && `level=${p.fieldOnboardingLevel}`,
|
|
89
|
+
p.intent && `intent=${p.intent}`,
|
|
90
|
+
p.enableMCP && 'MCP',
|
|
91
|
+
p.enableABAC && 'ABAC',
|
|
92
|
+
p.enableRBAC && 'RBAC'
|
|
93
|
+
].filter(Boolean);
|
|
94
|
+
return parts.length ? parts.join(', ') : '(defaults)';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Show a short summary of loaded wizard config (for resume flow)
|
|
99
|
+
* @param {Object} config - Loaded wizard config
|
|
100
|
+
* @param {string} displayPath - Path to show (e.g. integration/test/wizard.yaml)
|
|
101
|
+
*/
|
|
102
|
+
function showWizardConfigSummary(config, displayPath) {
|
|
103
|
+
logger.log(chalk.blue('\n📋 Saved config summary'));
|
|
104
|
+
logger.log(chalk.gray(` From: ${displayPath}`));
|
|
105
|
+
if (config.appName) logger.log(chalk.gray(` App: ${config.appName}`));
|
|
106
|
+
if (config.mode) logger.log(chalk.gray(` Mode: ${config.mode}`));
|
|
107
|
+
const srcLine = formatSourceLine(config.source);
|
|
108
|
+
if (srcLine) logger.log(chalk.gray(` Source: ${srcLine}`));
|
|
109
|
+
if (config.credential) logger.log(chalk.gray(` Credential: ${config.credential.action || 'skip'}`));
|
|
110
|
+
const prefs = formatPreferencesLine(config.preferences);
|
|
111
|
+
if (prefs) logger.log(chalk.gray(` Preferences: ${prefs}`));
|
|
112
|
+
logger.log('');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Ensure integration/<appKey> directory exists
|
|
117
|
+
* @param {string} appKey - Application key
|
|
118
|
+
* @returns {Promise<string>} Resolved config path (integration/<appKey>/wizard.yaml)
|
|
119
|
+
*/
|
|
120
|
+
async function ensureIntegrationDir(appKey) {
|
|
121
|
+
const dir = path.join(process.cwd(), 'integration', appKey);
|
|
122
|
+
await fs.mkdir(dir, { recursive: true });
|
|
123
|
+
return path.join(dir, 'wizard.yaml');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** External system types that support add-datasource (excludes webapp/application) */
|
|
127
|
+
const EXTERNAL_SYSTEM_TYPES = ['openapi', 'mcp', 'custom'];
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Returns true if the system is an external system that supports add-datasource (not a webapp).
|
|
131
|
+
* @param {Object} sys - System object from getExternalSystem (may have type, systemType, or kind)
|
|
132
|
+
* @returns {boolean}
|
|
133
|
+
*/
|
|
134
|
+
function isExternalSystemForAddDatasource(sys) {
|
|
135
|
+
const type = (sys?.type || sys?.systemType || sys?.kind || '').toLowerCase();
|
|
136
|
+
if (!type) return true;
|
|
137
|
+
if (EXTERNAL_SYSTEM_TYPES.includes(type)) return true;
|
|
138
|
+
if (['webapp', 'application', 'app'].includes(type)) return false;
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
buildPreferencesForSave,
|
|
144
|
+
buildSourceForSave,
|
|
145
|
+
buildWizardStateForSave,
|
|
146
|
+
formatSourceLine,
|
|
147
|
+
formatPreferencesLine,
|
|
148
|
+
showWizardConfigSummary,
|
|
149
|
+
ensureIntegrationDir,
|
|
150
|
+
EXTERNAL_SYSTEM_TYPES,
|
|
151
|
+
isExternalSystemForAddDatasource
|
|
152
|
+
};
|
package/lib/commands/wizard.js
CHANGED
|
@@ -13,11 +13,13 @@ const {
|
|
|
13
13
|
promptForOpenApiFile,
|
|
14
14
|
promptForOpenApiUrl,
|
|
15
15
|
promptForMcpServer,
|
|
16
|
+
promptForCredentialAction,
|
|
16
17
|
promptForKnownPlatform,
|
|
17
18
|
promptForUserIntent,
|
|
18
19
|
promptForUserPreferences,
|
|
19
20
|
promptForConfigReview,
|
|
20
|
-
promptForAppName
|
|
21
|
+
promptForAppName,
|
|
22
|
+
promptForRunWithSavedConfig
|
|
21
23
|
} = require('../generator/wizard-prompts');
|
|
22
24
|
const {
|
|
23
25
|
validateAndCheckAppDirectory,
|
|
@@ -31,7 +33,17 @@ const {
|
|
|
31
33
|
setupDataplaneAndAuth
|
|
32
34
|
} = require('./wizard-core');
|
|
33
35
|
const { handleWizardHeadless } = require('./wizard-headless');
|
|
34
|
-
const { createWizardSession, updateWizardSession } = require('../api/wizard.api');
|
|
36
|
+
const { createWizardSession, updateWizardSession, getWizardPlatforms } = require('../api/wizard.api');
|
|
37
|
+
const { getExternalSystem } = require('../api/external-systems.api');
|
|
38
|
+
const { writeWizardConfig, wizardConfigExists, validateWizardConfig } = require('../validation/wizard-config-validator');
|
|
39
|
+
const { appendWizardError } = require('../utils/cli-utils');
|
|
40
|
+
const {
|
|
41
|
+
buildPreferencesForSave,
|
|
42
|
+
buildWizardStateForSave,
|
|
43
|
+
showWizardConfigSummary,
|
|
44
|
+
ensureIntegrationDir,
|
|
45
|
+
isExternalSystemForAddDatasource
|
|
46
|
+
} = require('./wizard-helpers');
|
|
35
47
|
|
|
36
48
|
/**
|
|
37
49
|
* Extract session ID from response data
|
|
@@ -53,22 +65,18 @@ function extractSessionId(responseData) {
|
|
|
53
65
|
}
|
|
54
66
|
|
|
55
67
|
/**
|
|
56
|
-
*
|
|
68
|
+
* Create wizard session with given mode and optional systemIdOrKey (no prompts)
|
|
57
69
|
* @async
|
|
58
|
-
* @function
|
|
70
|
+
* @function createSessionFromParams
|
|
59
71
|
* @param {string} dataplaneUrl - Dataplane URL
|
|
60
72
|
* @param {Object} authConfig - Authentication configuration
|
|
73
|
+
* @param {string} mode - Mode ('create-system' | 'add-datasource')
|
|
74
|
+
* @param {string} [systemIdOrKey] - System ID or key (for add-datasource)
|
|
61
75
|
* @param {string} [appName] - Application name (for 401 hint)
|
|
62
|
-
* @returns {Promise<
|
|
76
|
+
* @returns {Promise<string>} Session ID
|
|
63
77
|
*/
|
|
64
|
-
async function
|
|
65
|
-
|
|
66
|
-
const mode = await promptForMode();
|
|
67
|
-
let systemIdOrKey = null;
|
|
68
|
-
if (mode === 'add-datasource') {
|
|
69
|
-
systemIdOrKey = await promptForSystemIdOrKey();
|
|
70
|
-
}
|
|
71
|
-
const sessionResponse = await createWizardSession(dataplaneUrl, authConfig, mode, systemIdOrKey);
|
|
78
|
+
async function createSessionFromParams(dataplaneUrl, authConfig, mode, systemIdOrKey, appName) {
|
|
79
|
+
const sessionResponse = await createWizardSession(dataplaneUrl, authConfig, mode, systemIdOrKey || null);
|
|
72
80
|
if (!sessionResponse.success || !sessionResponse.data) {
|
|
73
81
|
const errorMsg = sessionResponse.formattedError || sessionResponse.error ||
|
|
74
82
|
sessionResponse.errorData?.detail || sessionResponse.message ||
|
|
@@ -79,8 +87,7 @@ async function handleInteractiveModeSelection(dataplaneUrl, authConfig, appName)
|
|
|
79
87
|
: `Failed to create wizard session: ${errorMsg}`;
|
|
80
88
|
throw new Error(fullMsg);
|
|
81
89
|
}
|
|
82
|
-
|
|
83
|
-
return { mode, sessionId, systemIdOrKey };
|
|
90
|
+
return extractSessionId(sessionResponse.data);
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
/**
|
|
@@ -90,11 +97,12 @@ async function handleInteractiveModeSelection(dataplaneUrl, authConfig, appName)
|
|
|
90
97
|
* @param {string} dataplaneUrl - Dataplane URL
|
|
91
98
|
* @param {string} sessionId - Wizard session ID
|
|
92
99
|
* @param {Object} authConfig - Authentication configuration
|
|
100
|
+
* @param {Array<{key: string, displayName?: string}>} [platforms] - Known platforms from dataplane (empty = hide "Known platform")
|
|
93
101
|
* @returns {Promise<Object>} Object with sourceType and sourceData
|
|
94
102
|
*/
|
|
95
|
-
async function handleInteractiveSourceSelection(dataplaneUrl, sessionId, authConfig) {
|
|
103
|
+
async function handleInteractiveSourceSelection(dataplaneUrl, sessionId, authConfig, platforms = []) {
|
|
96
104
|
logger.log(chalk.blue('\n\uD83D\uDCCB Step 2: Source Selection'));
|
|
97
|
-
const sourceType = await promptForSourceType();
|
|
105
|
+
const sourceType = await promptForSourceType(platforms);
|
|
98
106
|
let sourceData = null;
|
|
99
107
|
const updateData = { currentStep: 1 };
|
|
100
108
|
|
|
@@ -108,7 +116,7 @@ async function handleInteractiveSourceSelection(dataplaneUrl, sessionId, authCon
|
|
|
108
116
|
sourceData = JSON.stringify(mcpDetails);
|
|
109
117
|
updateData.mcpServerUrl = mcpDetails.url || null;
|
|
110
118
|
} else if (sourceType === 'known-platform') {
|
|
111
|
-
sourceData = await promptForKnownPlatform();
|
|
119
|
+
sourceData = await promptForKnownPlatform(platforms);
|
|
112
120
|
}
|
|
113
121
|
|
|
114
122
|
const updateResponse = await updateWizardSession(dataplaneUrl, sessionId, authConfig, updateData);
|
|
@@ -131,7 +139,7 @@ async function handleInteractiveSourceSelection(dataplaneUrl, sessionId, authCon
|
|
|
131
139
|
* @param {Object} options.detectedType - Detected type info
|
|
132
140
|
* @param {string} [options.credentialIdOrKey] - Credential ID or key (optional)
|
|
133
141
|
* @param {string} [options.systemIdOrKey] - System ID or key (optional)
|
|
134
|
-
* @returns {Promise<Object>} Generated configuration
|
|
142
|
+
* @returns {Promise<Object>} Generated configuration and preferences { systemConfig, datasourceConfigs, systemKey, preferences }
|
|
135
143
|
*/
|
|
136
144
|
async function handleInteractiveConfigGeneration(options) {
|
|
137
145
|
logger.log(chalk.blue('\n\uD83D\uDCCB Step 5: User Preferences'));
|
|
@@ -140,12 +148,13 @@ async function handleInteractiveConfigGeneration(options) {
|
|
|
140
148
|
|
|
141
149
|
const configPrefs = {
|
|
142
150
|
intent: userIntent,
|
|
151
|
+
fieldOnboardingLevel: preferences.fieldOnboardingLevel || 'full',
|
|
143
152
|
enableMCP: preferences.mcp,
|
|
144
153
|
enableABAC: preferences.abac,
|
|
145
154
|
enableRBAC: preferences.rbac
|
|
146
155
|
};
|
|
147
156
|
|
|
148
|
-
|
|
157
|
+
const result = await handleConfigurationGeneration(options.dataplaneUrl, options.authConfig, {
|
|
149
158
|
mode: options.mode,
|
|
150
159
|
openapiSpec: options.openapiSpec,
|
|
151
160
|
detectedType: options.detectedType,
|
|
@@ -153,6 +162,11 @@ async function handleInteractiveConfigGeneration(options) {
|
|
|
153
162
|
credentialIdOrKey: options.credentialIdOrKey,
|
|
154
163
|
systemIdOrKey: options.systemIdOrKey
|
|
155
164
|
});
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
...result,
|
|
168
|
+
preferences: buildPreferencesForSave(userIntent, preferences)
|
|
169
|
+
};
|
|
156
170
|
}
|
|
157
171
|
|
|
158
172
|
/**
|
|
@@ -183,32 +197,37 @@ async function handleConfigurationReview(dataplaneUrl, authConfig, systemConfig,
|
|
|
183
197
|
}
|
|
184
198
|
|
|
185
199
|
/**
|
|
186
|
-
*
|
|
200
|
+
* Run steps 2–7 after session is created (source, credential, type, generate, review, save).
|
|
201
|
+
* On any error, saves partial wizard.yaml with all collected state so far, appends to error.log, then rethrows.
|
|
187
202
|
* @async
|
|
188
|
-
* @
|
|
189
|
-
* @param {string} appName - Application name
|
|
203
|
+
* @param {string} appKey - Application key
|
|
190
204
|
* @param {string} dataplaneUrl - Dataplane URL
|
|
191
205
|
* @param {Object} authConfig - Authentication configuration
|
|
192
|
-
* @
|
|
206
|
+
* @param {string} sessionId - Wizard session ID
|
|
207
|
+
* @param {Object} flowOpts - Mode, systemIdOrKey, platforms, configPath
|
|
208
|
+
* @returns {Promise<Object>} Collected state (source, credential, preferences) for wizard.yaml save
|
|
193
209
|
*/
|
|
194
|
-
async function
|
|
195
|
-
|
|
196
|
-
const {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
210
|
+
async function doWizardSteps(appKey, dataplaneUrl, authConfig, sessionId, flowOpts, state) {
|
|
211
|
+
const { mode, systemIdOrKey, platforms } = flowOpts;
|
|
212
|
+
const { sourceType, sourceData } = await handleInteractiveSourceSelection(
|
|
213
|
+
dataplaneUrl, sessionId, authConfig, platforms
|
|
214
|
+
);
|
|
215
|
+
state.source = { type: sourceType };
|
|
216
|
+
if (sourceType === 'openapi-file') state.source.filePath = sourceData;
|
|
217
|
+
else if (sourceType === 'openapi-url') state.source.url = sourceData;
|
|
218
|
+
else if (sourceType === 'mcp-server') state.source.serverUrl = JSON.parse(sourceData).serverUrl;
|
|
219
|
+
else if (sourceType === 'known-platform') state.source.platform = sourceData;
|
|
200
220
|
|
|
201
|
-
// Parse OpenAPI (part of step 2)
|
|
202
221
|
const openapiSpec = await handleOpenApiParsing(dataplaneUrl, authConfig, sourceType, sourceData);
|
|
222
|
+
const credentialAction = await promptForCredentialAction();
|
|
223
|
+
const configCredential = credentialAction.action === 'skip'
|
|
224
|
+
? { action: 'skip' }
|
|
225
|
+
: { action: credentialAction.action, credentialIdOrKey: credentialAction.credentialIdOrKey };
|
|
226
|
+
state.credential = configCredential;
|
|
227
|
+
const credentialIdOrKey = await handleCredentialSelection(dataplaneUrl, authConfig, configCredential);
|
|
203
228
|
|
|
204
|
-
// Step 3: Credential Selection (optional)
|
|
205
|
-
const credentialIdOrKey = await handleCredentialSelection(dataplaneUrl, authConfig);
|
|
206
|
-
|
|
207
|
-
// Step 4: Detect Type
|
|
208
229
|
const detectedType = await handleTypeDetection(dataplaneUrl, authConfig, openapiSpec);
|
|
209
|
-
|
|
210
|
-
// Step 5: Generate Configuration
|
|
211
|
-
const { systemConfig, datasourceConfigs, systemKey } = await handleInteractiveConfigGeneration({
|
|
230
|
+
const genResult = await handleInteractiveConfigGeneration({
|
|
212
231
|
dataplaneUrl,
|
|
213
232
|
authConfig,
|
|
214
233
|
mode,
|
|
@@ -217,60 +236,249 @@ async function executeWizardFlow(appName, dataplaneUrl, authConfig) {
|
|
|
217
236
|
credentialIdOrKey,
|
|
218
237
|
systemIdOrKey
|
|
219
238
|
});
|
|
239
|
+
const { systemConfig, datasourceConfigs, systemKey, preferences: savedPrefs } = genResult;
|
|
240
|
+
state.preferences = savedPrefs || {};
|
|
220
241
|
|
|
221
|
-
// Step 6-7: Review & Validate
|
|
222
242
|
const finalConfigs = await handleConfigurationReview(dataplaneUrl, authConfig, systemConfig, datasourceConfigs);
|
|
223
|
-
if (!finalConfigs) return;
|
|
243
|
+
if (!finalConfigs) return null;
|
|
224
244
|
|
|
225
|
-
// Step 7: Save Files
|
|
226
245
|
await handleFileSaving(
|
|
227
|
-
|
|
246
|
+
appKey,
|
|
228
247
|
finalConfigs.systemConfig,
|
|
229
248
|
finalConfigs.datasourceConfigs,
|
|
230
|
-
systemKey ||
|
|
249
|
+
systemKey || appKey,
|
|
231
250
|
dataplaneUrl,
|
|
232
251
|
authConfig
|
|
233
252
|
);
|
|
253
|
+
return state;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function runWizardStepsAfterSession(appKey, dataplaneUrl, authConfig, sessionId, flowOpts) {
|
|
257
|
+
const { mode, systemIdOrKey, configPath } = flowOpts;
|
|
258
|
+
const state = { appKey, mode, systemIdOrKey: mode === 'add-datasource' ? systemIdOrKey : undefined };
|
|
259
|
+
|
|
260
|
+
const savePartialOnError = async(err) => {
|
|
261
|
+
state.preferences = state.preferences || {};
|
|
262
|
+
if (configPath) {
|
|
263
|
+
try {
|
|
264
|
+
await writeWizardConfig(configPath, buildWizardStateForSave(state));
|
|
265
|
+
} catch (e) {
|
|
266
|
+
logger.warn(`Could not save partial wizard.yaml: ${e.message}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
await appendWizardError(appKey, err);
|
|
270
|
+
err.wizardResumeMessage = `To resume: aifabrix wizard ${appKey}\nSee integration/${appKey}/error.log for details.`;
|
|
271
|
+
err.wizardPartialSaved = true;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
return await doWizardSteps(appKey, dataplaneUrl, authConfig, sessionId, flowOpts, state);
|
|
276
|
+
} catch (err) {
|
|
277
|
+
await savePartialOnError(err);
|
|
278
|
+
throw err;
|
|
279
|
+
}
|
|
234
280
|
}
|
|
235
281
|
|
|
236
282
|
/**
|
|
237
|
-
*
|
|
283
|
+
* Execute wizard flow steps (interactive mode). Mode and systemIdOrKey are already set; creates session then runs steps 2–7.
|
|
238
284
|
* @async
|
|
239
|
-
* @function
|
|
285
|
+
* @function executeWizardFlow
|
|
286
|
+
* @param {string} appKey - Application/integration key (folder name under integration/)
|
|
287
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
288
|
+
* @param {Object} authConfig - Authentication configuration
|
|
289
|
+
* @param {Object} flowOpts - Flow options (mode, systemIdOrKey, configPath)
|
|
290
|
+
* @returns {Promise<void>} Resolves when wizard flow completes
|
|
291
|
+
*/
|
|
292
|
+
async function executeWizardFlow(appKey, dataplaneUrl, authConfig, flowOpts = {}) {
|
|
293
|
+
const { mode, systemIdOrKey, configPath } = flowOpts;
|
|
294
|
+
|
|
295
|
+
logger.log(chalk.blue('\n\uD83D\uDCCB Step 1: Create Session'));
|
|
296
|
+
const sessionId = await createSessionFromParams(dataplaneUrl, authConfig, mode, systemIdOrKey, appKey);
|
|
297
|
+
logger.log(chalk.green('\u2713 Session created'));
|
|
298
|
+
|
|
299
|
+
const platforms = await getWizardPlatforms(dataplaneUrl, authConfig);
|
|
300
|
+
const state = await runWizardStepsAfterSession(appKey, dataplaneUrl, authConfig, sessionId, {
|
|
301
|
+
mode,
|
|
302
|
+
systemIdOrKey,
|
|
303
|
+
platforms,
|
|
304
|
+
configPath
|
|
305
|
+
});
|
|
306
|
+
if (!state) return;
|
|
307
|
+
|
|
308
|
+
if (configPath) {
|
|
309
|
+
await writeWizardConfig(configPath, buildWizardStateForSave(state));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Load wizard config from configPath if it exists (for prefill)
|
|
315
|
+
* @param {string} [configPath] - Path to wizard.yaml (e.g. integration/<app>/wizard.yaml)
|
|
316
|
+
* @param {string} [appName] - App name (for log message)
|
|
317
|
+
* @returns {Promise<Object|null>} Loaded config or null
|
|
318
|
+
*/
|
|
319
|
+
async function loadWizardConfigIfExists(configPath, appName) {
|
|
320
|
+
if (!configPath) return null;
|
|
321
|
+
const displayPath = appName ? `integration/${appName}/wizard.yaml` : configPath;
|
|
322
|
+
try {
|
|
323
|
+
const exists = await wizardConfigExists(configPath);
|
|
324
|
+
if (!exists) {
|
|
325
|
+
logger.log(chalk.gray(`No saved state at ${displayPath}; starting from step 1.`));
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
const result = await validateWizardConfig(configPath, { validateFilePaths: false });
|
|
329
|
+
if (result.valid && result.config) {
|
|
330
|
+
logger.log(chalk.green(`Loaded saved state from ${displayPath}. Resuming with saved choices.`));
|
|
331
|
+
return result.config;
|
|
332
|
+
}
|
|
333
|
+
if (result.errors?.length) {
|
|
334
|
+
logger.log(chalk.yellow(`Loaded ${displayPath} but it has errors; prompting for missing fields.`));
|
|
335
|
+
}
|
|
336
|
+
} catch (e) {
|
|
337
|
+
logger.log(chalk.gray(`Could not load wizard config from ${displayPath}: ${e.message}`));
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Resolve appKey, configPath, dataplane and auth for "create-system" mode
|
|
240
344
|
* @param {Object} options - Command options
|
|
241
|
-
* @param {
|
|
242
|
-
* @
|
|
243
|
-
* @param {string} [options.environment] - Environment key
|
|
244
|
-
* @param {string} [options.dataplane] - Dataplane URL (overrides controller lookup)
|
|
245
|
-
* @param {string} [options.config] - Path to wizard.yaml config file (headless mode)
|
|
246
|
-
* @returns {Promise<void>} Resolves when wizard completes
|
|
247
|
-
* @throws {Error} If wizard fails
|
|
345
|
+
* @param {Object} [loadedConfig] - Loaded wizard config
|
|
346
|
+
* @returns {Promise<Object|null>} { appKey, configPath, dataplaneUrl, authConfig } or null if cancelled
|
|
248
347
|
*/
|
|
249
|
-
async function
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
348
|
+
async function resolveCreateNewPath(options, loadedConfig) {
|
|
349
|
+
const appName = options.app || loadedConfig?.appName || (await promptForAppName(loadedConfig?.appName));
|
|
350
|
+
const shouldContinue = await validateAndCheckAppDirectory(appName, true);
|
|
351
|
+
if (!shouldContinue) return null;
|
|
352
|
+
const appKey = appName;
|
|
353
|
+
const configPath = await ensureIntegrationDir(appKey);
|
|
354
|
+
const { dataplaneUrl, authConfig } = await setupDataplaneAndAuth(options, appName);
|
|
355
|
+
return { appKey, configPath, dataplaneUrl, authConfig };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Resolve appKey, configPath, dataplane and auth for "add-datasource" mode (validates system)
|
|
360
|
+
* @param {Object} options - Command options
|
|
361
|
+
* @param {Object} [loadedConfig] - Loaded wizard config
|
|
362
|
+
* @returns {Promise<Object>} { appKey, configPath, dataplaneUrl, authConfig, systemIdOrKey }
|
|
363
|
+
*/
|
|
364
|
+
async function resolveAddDatasourcePath(options, loadedConfig) {
|
|
365
|
+
let systemIdOrKey = loadedConfig?.systemIdOrKey || (await promptForSystemIdOrKey(loadedConfig?.systemIdOrKey));
|
|
366
|
+
const { dataplaneUrl, authConfig } = await setupDataplaneAndAuth(options, systemIdOrKey || 'wizard');
|
|
367
|
+
let systemResponse;
|
|
368
|
+
for (;;) {
|
|
369
|
+
try {
|
|
370
|
+
systemResponse = await getExternalSystem(dataplaneUrl, systemIdOrKey, authConfig);
|
|
371
|
+
const sys = systemResponse?.data || systemResponse;
|
|
372
|
+
if (sys && (systemResponse?.data || systemResponse?.success)) {
|
|
373
|
+
if (!isExternalSystemForAddDatasource(sys)) {
|
|
374
|
+
logger.log(chalk.red('Cannot add datasource to a webapp. Please enter an external system ID or key.'));
|
|
375
|
+
systemIdOrKey = await promptForSystemIdOrKey(systemIdOrKey);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
} catch (err) {
|
|
381
|
+
logger.log(chalk.red(`System not found or error: ${err.message}`));
|
|
382
|
+
}
|
|
383
|
+
systemIdOrKey = await promptForSystemIdOrKey(systemIdOrKey);
|
|
253
384
|
}
|
|
385
|
+
const sys = systemResponse?.data || systemResponse;
|
|
386
|
+
const appKey = sys?.key || sys?.systemKey || systemIdOrKey;
|
|
387
|
+
const configPath = await ensureIntegrationDir(appKey);
|
|
388
|
+
return { appKey, configPath, dataplaneUrl, authConfig, systemIdOrKey };
|
|
389
|
+
}
|
|
254
390
|
|
|
255
|
-
|
|
391
|
+
/**
|
|
392
|
+
* On wizard error: append to error.log, save partial wizard.yaml, set resume message
|
|
393
|
+
* @param {string} appKey - Application key
|
|
394
|
+
* @param {string} [configPath] - Path to wizard.yaml
|
|
395
|
+
* @param {string} mode - Wizard mode
|
|
396
|
+
* @param {string} [systemIdOrKey] - System ID (add-datasource)
|
|
397
|
+
* @param {Error} error - The error
|
|
398
|
+
*/
|
|
399
|
+
async function handleWizardError(appKey, configPath, mode, systemIdOrKey, error) {
|
|
400
|
+
await appendWizardError(appKey, error);
|
|
401
|
+
if (!error.wizardPartialSaved && configPath) {
|
|
402
|
+
const partial = buildWizardStateForSave({
|
|
403
|
+
appKey,
|
|
404
|
+
mode,
|
|
405
|
+
systemIdOrKey: mode === 'add-datasource' ? systemIdOrKey : undefined
|
|
406
|
+
});
|
|
407
|
+
try {
|
|
408
|
+
await writeWizardConfig(configPath, partial);
|
|
409
|
+
} catch (e) {
|
|
410
|
+
logger.warn(`Could not save partial wizard.yaml: ${e.message}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
error.wizardResumeMessage = `To resume: aifabrix wizard ${appKey}\nSee integration/${appKey}/error.log for details.`;
|
|
414
|
+
}
|
|
256
415
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
416
|
+
/**
|
|
417
|
+
* Handle wizard command (mode-first, load/save wizard.yaml, error.log on failure)
|
|
418
|
+
* @async
|
|
419
|
+
* @function handleWizard
|
|
420
|
+
* @param {Object} options - Command options
|
|
421
|
+
* @param {string} [options.app] - Application name (from positional or -a)
|
|
422
|
+
* @param {string} [options.config] - Path to wizard.yaml (headless mode)
|
|
423
|
+
* @param {string} [options.configPath] - Resolved path integration/<app>/wizard.yaml for load/save
|
|
424
|
+
* @returns {Promise<void>} Resolves when wizard completes
|
|
425
|
+
* @throws {Error} If wizard fails (wizardResumeMessage set when appKey known)
|
|
426
|
+
*/
|
|
427
|
+
async function handleWizardSilent(options) {
|
|
428
|
+
if (!options.configPath) {
|
|
429
|
+
throw new Error('--silent requires an app name (e.g. aifabrix wizard test --silent)');
|
|
430
|
+
}
|
|
431
|
+
const result = await validateWizardConfig(options.configPath, { validateFilePaths: false });
|
|
432
|
+
if (!result.valid || !result.config) {
|
|
433
|
+
const displayPath = options.app ? `integration/${options.app}/wizard.yaml` : '';
|
|
434
|
+
const errMsg = result.errors?.length ? result.errors.join('; ') : 'Invalid or missing wizard.yaml';
|
|
435
|
+
throw new Error(`Cannot run --silent: ${displayPath} is invalid or missing. ${errMsg}`);
|
|
261
436
|
}
|
|
437
|
+
return await handleWizardHeadless({ ...options, config: options.configPath });
|
|
438
|
+
}
|
|
262
439
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
440
|
+
async function handleWizardWithSavedConfig(options, loadedConfig, displayPath) {
|
|
441
|
+
showWizardConfigSummary(loadedConfig, displayPath);
|
|
442
|
+
const runWithSaved = await promptForRunWithSavedConfig();
|
|
443
|
+
if (!runWithSaved) {
|
|
444
|
+
logger.log(chalk.gray(`To change settings, edit ${displayPath} and run: aifabrix wizard ${options.app}`));
|
|
266
445
|
return;
|
|
267
446
|
}
|
|
447
|
+
logger.log(chalk.gray(`Running with saved config from ${displayPath}...\n`));
|
|
448
|
+
return await handleWizardHeadless({ ...options, config: options.configPath });
|
|
449
|
+
}
|
|
268
450
|
|
|
269
|
-
|
|
270
|
-
const
|
|
451
|
+
async function handleWizardInteractive(options) {
|
|
452
|
+
const mode = await promptForMode();
|
|
453
|
+
const resolved = mode === 'create-system'
|
|
454
|
+
? await resolveCreateNewPath(options, null)
|
|
455
|
+
: await resolveAddDatasourcePath(options, null);
|
|
456
|
+
if (!resolved) return;
|
|
457
|
+
const { appKey, configPath, dataplaneUrl, authConfig } = resolved;
|
|
458
|
+
const systemIdOrKey = mode === 'add-datasource' ? resolved.systemIdOrKey : undefined;
|
|
459
|
+
try {
|
|
460
|
+
await executeWizardFlow(appKey, dataplaneUrl, authConfig, { mode, systemIdOrKey, configPath });
|
|
461
|
+
logger.log(chalk.gray(`To change settings, edit integration/${appKey}/wizard.yaml and run: aifabrix wizard ${appKey}`));
|
|
462
|
+
} catch (error) {
|
|
463
|
+
await handleWizardError(appKey, configPath, mode, systemIdOrKey, error);
|
|
464
|
+
throw error;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
271
467
|
|
|
272
|
-
|
|
273
|
-
|
|
468
|
+
async function handleWizard(options = {}) {
|
|
469
|
+
if (options.config) {
|
|
470
|
+
return await handleWizardHeadless(options);
|
|
471
|
+
}
|
|
472
|
+
const displayPath = options.app ? `integration/${options.app}/wizard.yaml` : '';
|
|
473
|
+
if (options.silent && options.app) {
|
|
474
|
+
return await handleWizardSilent(options);
|
|
475
|
+
}
|
|
476
|
+
logger.log(chalk.blue('\n\uD83E\uDDD9 AI Fabrix External System Wizard\n'));
|
|
477
|
+
const loadedConfig = await loadWizardConfigIfExists(options.configPath, options.app);
|
|
478
|
+
if (loadedConfig) {
|
|
479
|
+
return await handleWizardWithSavedConfig(options, loadedConfig, displayPath);
|
|
480
|
+
}
|
|
481
|
+
return await handleWizardInteractive(options);
|
|
274
482
|
}
|
|
275
483
|
|
|
276
484
|
module.exports = { handleWizard, handleWizardHeadless };
|
package/lib/generator/index.js
CHANGED
|
@@ -91,6 +91,37 @@ function buildAndValidateDeployment(appName, variables, envTemplate, rbac) {
|
|
|
91
91
|
return deployment;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Builds deployment manifest in memory (no file write, no schema validation).
|
|
96
|
+
* Same structure as generateDeployJson output; used by "aifabrix show" offline.
|
|
97
|
+
* @async
|
|
98
|
+
* @function buildDeploymentManifestInMemory
|
|
99
|
+
* @param {string} appName - Application name
|
|
100
|
+
* @param {Object} [options] - Options (e.g. type for external)
|
|
101
|
+
* @returns {Promise<{ deployment: Object, appPath: string }>} Manifest and app path
|
|
102
|
+
* @throws {Error} If variables.yaml/env.template missing or generation fails
|
|
103
|
+
*/
|
|
104
|
+
async function buildDeploymentManifestInMemory(appName, options = {}) {
|
|
105
|
+
if (!appName || typeof appName !== 'string') {
|
|
106
|
+
throw new Error('App name is required and must be a string');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const { isExternal, appPath, appType } = await detectAppType(appName, options);
|
|
110
|
+
|
|
111
|
+
if (isExternal) {
|
|
112
|
+
const manifest = await generateControllerManifest(appName);
|
|
113
|
+
return { deployment: manifest, appPath };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const { variables, envTemplate, rbac } = loadDeploymentConfigFiles(appPath, appType, appName);
|
|
117
|
+
const configuration = parseEnvironmentVariables(envTemplate, variables);
|
|
118
|
+
const deployment = builders.buildManifestStructure(appName, variables, null, configuration, rbac);
|
|
119
|
+
const deploymentKey = _keyGenerator.generateDeploymentKeyFromJson(deployment);
|
|
120
|
+
deployment.deploymentKey = deploymentKey;
|
|
121
|
+
|
|
122
|
+
return { deployment, appPath };
|
|
123
|
+
}
|
|
124
|
+
|
|
94
125
|
async function generateDeployJson(appName, options = {}) {
|
|
95
126
|
if (!appName || typeof appName !== 'string') {
|
|
96
127
|
throw new Error('App name is required and must be a string');
|
|
@@ -153,6 +184,7 @@ async function generateDeployJsonWithValidation(appName, options = {}) {
|
|
|
153
184
|
module.exports = {
|
|
154
185
|
generateDeployJson,
|
|
155
186
|
generateDeployJsonWithValidation,
|
|
187
|
+
buildDeploymentManifestInMemory,
|
|
156
188
|
generateExternalSystemApplicationSchema,
|
|
157
189
|
splitExternalApplicationSchema,
|
|
158
190
|
parseEnvironmentVariables,
|