@aifabrix/builder 2.42.0 → 2.42.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 +1 -1
- package/integration/hubspot/README.md +3 -1
- package/lib/api/wizard.api.js +2 -1
- package/lib/app/index.js +2 -2
- package/lib/app/prompts.js +2 -2
- package/lib/app/readme.js +3 -1
- package/lib/cli/setup-app.js +3 -3
- package/lib/commands/auth-config.js +22 -12
- package/lib/commands/repair.js +12 -1
- package/lib/commands/secrets-set.js +6 -0
- package/lib/commands/up-dataplane.js +90 -6
- package/lib/commands/wizard-core-helpers.js +5 -2
- package/lib/commands/wizard-core.js +2 -1
- package/lib/commands/wizard-headless.js +6 -1
- package/lib/commands/wizard.js +13 -6
- package/lib/external-system/download-helpers.js +3 -1
- package/lib/generator/external-schema-utils.js +3 -1
- package/lib/generator/wizard-prompts-secondary.js +39 -7
- package/lib/generator/wizard-readme.js +4 -1
- package/lib/generator/wizard.js +59 -47
- package/lib/schema/wizard-config.schema.json +7 -1
- package/lib/utils/external-readme.js +33 -1
- package/package.json +2 -2
- package/templates/external-system/README.md.hbs +18 -5
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ Create and deploy an external system (e.g. HubSpot): wizard or manual setup, the
|
|
|
82
82
|
|
|
83
83
|
**Example: HubSpot**
|
|
84
84
|
|
|
85
|
-
- Create: `aifabrix create hubspot
|
|
85
|
+
- Create: `aifabrix create hubspot` (external is the default; or `aifabrix wizard` for guided setup). For a web app use `aifabrix create my-app --type webapp`.
|
|
86
86
|
- Configure auth and datasources under `integration/hubspot-test/`.
|
|
87
87
|
- Validate: `aifabrix validate hubspot-test`
|
|
88
88
|
- Deploy: `aifabrix deploy hubspot-test`
|
|
@@ -26,9 +26,11 @@ Optional: `rbac.yaml` – Roles and permissions merged into the system when pres
|
|
|
26
26
|
### 1. Create External System
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
aifabrix create hubspot
|
|
29
|
+
aifabrix create hubspot
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
(External is the default type. Use `aifabrix create <name> --type webapp` for a builder app.)
|
|
33
|
+
|
|
32
34
|
Or use the interactive wizard:
|
|
33
35
|
|
|
34
36
|
```bash
|
package/lib/api/wizard.api.js
CHANGED
|
@@ -219,8 +219,9 @@ async function getPlatformConfig(dataplaneUrl, authConfig, platformKey, config)
|
|
|
219
219
|
* @param {string} config.detectedType - Detected API type (required, e.g., 'record-based')
|
|
220
220
|
* @param {string} config.intent - User intent (required, any descriptive text)
|
|
221
221
|
* @param {string} config.mode - Wizard mode (required, 'create-system' | 'add-datasource')
|
|
222
|
-
* @param {string} [config.systemIdOrKey] - Existing system ID/key (required for add-datasource)
|
|
222
|
+
* @param {string} [config.systemIdOrKey] - Existing system ID/key (required for add-datasource; must be application/system key, not entity/datasource key)
|
|
223
223
|
* @param {string} [config.credentialIdOrKey] - Credential ID or key
|
|
224
|
+
* @param {string|null} [config.systemDisplayName] - System-level display name for credential (e.g. 'Hubspot Demo'); when OpenAPI title is entity-specific, pass system name here
|
|
224
225
|
* @param {string} [config.fieldOnboardingLevel] - Field onboarding level ('full' | 'standard' | 'minimal')
|
|
225
226
|
* @param {boolean} [config.enableOpenAPIGeneration] - Enable OpenAPI operation generation
|
|
226
227
|
* @param {Object} [config.userPreferences] - User preferences
|
package/lib/app/index.js
CHANGED
|
@@ -68,7 +68,7 @@ function validateAppNameAndSetup(appName, options) {
|
|
|
68
68
|
throw new Error('Application name is required');
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const initialType = options.type || '
|
|
71
|
+
const initialType = options.type || 'external';
|
|
72
72
|
const baseDir = getBaseDirForAppType(initialType);
|
|
73
73
|
const appPath = getAppPath(appName, initialType);
|
|
74
74
|
|
|
@@ -179,7 +179,7 @@ async function logApplicationCreation(appName, config, options) {
|
|
|
179
179
|
async function createApp(appName, options = {}) {
|
|
180
180
|
try {
|
|
181
181
|
const { appPath } = validateAppNameAndSetup(appName, options);
|
|
182
|
-
await validateAppCreation(appName, options, appPath, getBaseDirForAppType(options.type || '
|
|
182
|
+
await validateAppCreation(appName, options, appPath, getBaseDirForAppType(options.type || 'external'));
|
|
183
183
|
|
|
184
184
|
const mergedOptions = await handleTemplateSetup(options);
|
|
185
185
|
const config = await promptForOptions(appName, mergedOptions);
|
package/lib/app/prompts.js
CHANGED
|
@@ -426,8 +426,8 @@ function mergePromptAnswers(appName, options, answers) {
|
|
|
426
426
|
* @returns {Promise<Object>} Complete configuration
|
|
427
427
|
*/
|
|
428
428
|
async function promptForOptions(appName, options) {
|
|
429
|
-
// Get app type from options (default to
|
|
430
|
-
const appType = options.type || '
|
|
429
|
+
// Get app type from options (default to external)
|
|
430
|
+
const appType = options.type || 'external';
|
|
431
431
|
|
|
432
432
|
// Build questions based on app type
|
|
433
433
|
let questions = [];
|
package/lib/app/readme.js
CHANGED
|
@@ -150,6 +150,7 @@ function generateReadmeMd(appName, config) {
|
|
|
150
150
|
const datasources = Array.isArray(config.datasources) && config.datasources.length > 0
|
|
151
151
|
? config.datasources
|
|
152
152
|
: buildExternalDatasourcePlaceholders(systemKey, config.datasourceCount, fileExt);
|
|
153
|
+
const authType = config.authentication?.type || config.authentication?.method || config.authType;
|
|
153
154
|
return generateExternalReadmeContent({
|
|
154
155
|
appName,
|
|
155
156
|
systemKey,
|
|
@@ -157,7 +158,8 @@ function generateReadmeMd(appName, config) {
|
|
|
157
158
|
displayName: config.systemDisplayName,
|
|
158
159
|
description: config.systemDescription,
|
|
159
160
|
fileExt: config.fileExt,
|
|
160
|
-
datasources
|
|
161
|
+
datasources,
|
|
162
|
+
authType
|
|
161
163
|
});
|
|
162
164
|
}
|
|
163
165
|
const context = buildReadmeContext(appName, config);
|
package/lib/cli/setup-app.js
CHANGED
|
@@ -84,14 +84,14 @@ async function handleCreateCommand(appName, options) {
|
|
|
84
84
|
const wizardOptions = { app: appName, ...options };
|
|
85
85
|
const normalizedOptions = normalizeExternalOptions(options);
|
|
86
86
|
|
|
87
|
-
const isExternalType = options.type === 'external';
|
|
87
|
+
const isExternalType = options.type === 'external' || !options.type;
|
|
88
88
|
const isNonInteractive = process.stdin && process.stdin.isTTY === false;
|
|
89
89
|
|
|
90
90
|
if (isExternalType && !options.wizard && isNonInteractive) {
|
|
91
91
|
validateNonInteractiveExternalOptions(normalizedOptions);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const shouldUseWizard = options.wizard && (options.type === 'external' ||
|
|
94
|
+
const shouldUseWizard = options.wizard && (options.type === 'external' || !options.type);
|
|
95
95
|
if (shouldUseWizard) {
|
|
96
96
|
const { handleWizard } = require('../commands/wizard');
|
|
97
97
|
await handleWizard(wizardOptions);
|
|
@@ -110,7 +110,7 @@ function setupCreateCommand(program) {
|
|
|
110
110
|
.option('-a, --authentication', 'Requires authentication/RBAC')
|
|
111
111
|
.option('-l, --language <lang>', 'Runtime language (typescript/python)')
|
|
112
112
|
.option('-t, --template <name>', 'Template to use (e.g., miso-controller, keycloak)')
|
|
113
|
-
.option('--type <type>', 'Application type (webapp, api, service, functionapp, external)', '
|
|
113
|
+
.option('--type <type>', 'Application type (webapp, api, service, functionapp, external)', 'external')
|
|
114
114
|
.option('--app', 'Generate minimal application files (package.json, index.ts or requirements.txt, main.py)')
|
|
115
115
|
.option('-g, --github', 'Generate GitHub Actions workflows')
|
|
116
116
|
.option('--github-steps <steps>', 'Extra GitHub workflow steps (comma-separated, e.g., npm,test)')
|
|
@@ -19,34 +19,44 @@ const {
|
|
|
19
19
|
validateEnvironment,
|
|
20
20
|
checkUserLoggedIn
|
|
21
21
|
} = require('../utils/auth-config-validator');
|
|
22
|
+
const { getControllerUrlFromLoggedInUser } = require('../utils/controller-url');
|
|
22
23
|
const logger = require('../utils/logger');
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Handle set-controller command
|
|
27
|
+
* Allows setting the default controller when no credentials are stored, or when already logged in to that controller.
|
|
28
|
+
* If credentials exist for a different controller, throws with a clear message.
|
|
29
|
+
*
|
|
26
30
|
* @async
|
|
27
31
|
* @function handleSetController
|
|
28
32
|
* @param {string} url - Controller URL to set
|
|
29
33
|
* @returns {Promise<void>}
|
|
30
|
-
* @throws {Error} If validation fails
|
|
34
|
+
* @throws {Error} If validation fails or credentials exist for another controller
|
|
31
35
|
*/
|
|
32
36
|
async function handleSetController(url) {
|
|
33
37
|
try {
|
|
34
|
-
// Validate URL format
|
|
35
38
|
validateControllerUrl(url);
|
|
39
|
+
const normalizedUrl = url.trim().replace(/\/+$/, '');
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
);
|
|
41
|
+
const loggedInControllerUrl = await getControllerUrlFromLoggedInUser();
|
|
42
|
+
if (!loggedInControllerUrl) {
|
|
43
|
+
// No stored credentials: allow setting controller so "aifabrix login" opens the right place
|
|
44
|
+
await setControllerUrl(url);
|
|
45
|
+
logger.log(chalk.green(`✓ Controller URL set to: ${url}`));
|
|
46
|
+
return;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
const normalizedLoggedIn = loggedInControllerUrl.trim().replace(/\/+$/, '');
|
|
50
|
+
if (normalizedLoggedIn === normalizedUrl) {
|
|
51
|
+
await setControllerUrl(url);
|
|
52
|
+
logger.log(chalk.green(`✓ Controller URL set to: ${url}`));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
48
55
|
|
|
49
|
-
|
|
56
|
+
throw new Error(
|
|
57
|
+
`You have credentials for another controller (${loggedInControllerUrl}).\n` +
|
|
58
|
+
`To use ${url} either run "aifabrix login" with that controller, or run "aifabrix logout" first to clear credentials, then set the new controller with --set-controller.`
|
|
59
|
+
);
|
|
50
60
|
} catch (error) {
|
|
51
61
|
logger.error(chalk.red(`✗ Failed to set controller URL: ${error.message}`));
|
|
52
62
|
throw error;
|
package/lib/commands/repair.js
CHANGED
|
@@ -351,6 +351,7 @@ function persistChangesAndRegenerate(configPath, variables, appName, appPath, ch
|
|
|
351
351
|
|
|
352
352
|
/**
|
|
353
353
|
* Apply --auth: replace system file authentication with canonical block for the given method.
|
|
354
|
+
* Preserves existing authentication.variables (e.g. baseUrl, tokenUrl) from the current system file.
|
|
354
355
|
* @param {Object} ctx - Context with systemParsed, systemKey, auth, dryRun, changes
|
|
355
356
|
* @returns {boolean} True if auth was replaced
|
|
356
357
|
*/
|
|
@@ -362,8 +363,18 @@ function applyAuthMethod(ctx) {
|
|
|
362
363
|
`Invalid --auth "${ctx.auth}". Allowed methods: ${ALLOWED_AUTH.join(', ')}`
|
|
363
364
|
);
|
|
364
365
|
}
|
|
366
|
+
const existingAuth = ctx.systemParsed.authentication || ctx.systemParsed.auth || {};
|
|
365
367
|
const { buildAuthenticationFromMethod } = require('../external-system/generator');
|
|
366
|
-
|
|
368
|
+
const newAuth = buildAuthenticationFromMethod(ctx.systemKey, method);
|
|
369
|
+
const existingVars = existingAuth.variables && typeof existingAuth.variables === 'object' ? existingAuth.variables : {};
|
|
370
|
+
const mergedVariables = { ...newAuth.variables, ...existingVars };
|
|
371
|
+
ctx.systemParsed.authentication = {
|
|
372
|
+
...newAuth,
|
|
373
|
+
variables: Object.keys(mergedVariables).length ? mergedVariables : newAuth.variables
|
|
374
|
+
};
|
|
375
|
+
if (existingAuth.displayName !== undefined) {
|
|
376
|
+
ctx.systemParsed.authentication.displayName = existingAuth.displayName;
|
|
377
|
+
}
|
|
367
378
|
ctx.changes.push(`Set authentication method to ${method}`);
|
|
368
379
|
return true;
|
|
369
380
|
}
|
|
@@ -60,6 +60,12 @@ async function handleSecretsSet(key, value, options) {
|
|
|
60
60
|
throw new Error('Secret key is required and must be a string');
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
if (key.startsWith('kv://')) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'Secret key must not start with kv://. Use the key path without the prefix (e.g. my-app/clientSecret or hubspot/apiKey).'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
63
69
|
if (value === undefined || value === null || value === '') {
|
|
64
70
|
throw new Error('Secret value is required');
|
|
65
71
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* @version 2.0.0
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
const readline = require('readline');
|
|
14
15
|
const chalk = require('chalk');
|
|
15
16
|
const pathsUtil = require('../utils/paths');
|
|
16
17
|
const { loadConfigFile } = require('../utils/config-format');
|
|
@@ -18,13 +19,87 @@ const logger = require('../utils/logger');
|
|
|
18
19
|
const config = require('../core/config');
|
|
19
20
|
const { checkAuthentication } = require('../utils/app-register-auth');
|
|
20
21
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
21
|
-
const { resolveEnvironment } = require('../core/config');
|
|
22
|
+
const { resolveEnvironment, setControllerUrl } = require('../core/config');
|
|
22
23
|
const { registerApplication } = require('../app/register');
|
|
23
24
|
const { rotateSecret } = require('../app/rotate-secret');
|
|
24
25
|
const { checkApplicationExists } = require('../utils/app-existence');
|
|
26
|
+
const { checkHealthEndpoint } = require('../utils/health-check');
|
|
27
|
+
const { validateControllerUrl } = require('../utils/auth-config-validator');
|
|
25
28
|
const app = require('../app');
|
|
26
29
|
const { ensureAppFromTemplate, validateEnvOutputPathFolderOrNull } = require('./up-common');
|
|
27
30
|
|
|
31
|
+
const CONTROLLER_HEALTH_PATH = '/health';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if controller is reachable (health endpoint).
|
|
35
|
+
* @param {string} baseUrl - Controller base URL (no trailing slash)
|
|
36
|
+
* @returns {Promise<boolean>} True if healthy
|
|
37
|
+
*/
|
|
38
|
+
async function isControllerHealthy(baseUrl) {
|
|
39
|
+
const healthUrl = `${baseUrl.replace(/\/+$/, '')}${CONTROLLER_HEALTH_PATH}`;
|
|
40
|
+
try {
|
|
41
|
+
return await checkHealthEndpoint(healthUrl, false);
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Prompt user for controller URL when current controller is not available.
|
|
49
|
+
* @param {string} currentUrl - Current controller URL that failed health check
|
|
50
|
+
* @returns {Promise<string|null>} New URL or null if user aborted (empty input)
|
|
51
|
+
*/
|
|
52
|
+
function promptForControllerUrl(currentUrl) {
|
|
53
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
rl.question(
|
|
56
|
+
chalk.yellow(`Controller at ${currentUrl} is not available. Enter controller URL (or press Enter to abort): `),
|
|
57
|
+
(answer) => {
|
|
58
|
+
rl.close();
|
|
59
|
+
const trimmed = (answer || '').trim();
|
|
60
|
+
resolve(trimmed === '' ? null : trimmed);
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolve controller URL and ensure it is healthy; if not, prompt once for new URL.
|
|
68
|
+
* @returns {Promise<string>} Controller URL to use
|
|
69
|
+
* @throws {Error} If controller unavailable and user aborts or new URL still unhealthy
|
|
70
|
+
*/
|
|
71
|
+
async function resolveControllerUrlWithHealthCheck() {
|
|
72
|
+
let controllerUrl = await resolveControllerUrl();
|
|
73
|
+
controllerUrl = controllerUrl.replace(/\/+$/, '');
|
|
74
|
+
|
|
75
|
+
let healthy = await isControllerHealthy(controllerUrl);
|
|
76
|
+
if (healthy) {
|
|
77
|
+
return controllerUrl;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
logger.log(chalk.yellow(`\nController at ${controllerUrl} is not responding (health check failed).\n`));
|
|
81
|
+
const newUrl = await promptForControllerUrl(controllerUrl);
|
|
82
|
+
if (!newUrl) {
|
|
83
|
+
throw new Error('Controller URL is required. Run "aifabrix up-dataplane" again and enter a valid controller URL, or set it with: aifabrix auth config --set-controller <url>');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
validateControllerUrl(newUrl);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
throw new Error(`Invalid controller URL: ${err.message}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await setControllerUrl(newUrl);
|
|
93
|
+
const normalizedNew = newUrl.trim().replace(/\/+$/, '');
|
|
94
|
+
healthy = await isControllerHealthy(normalizedNew);
|
|
95
|
+
if (!healthy) {
|
|
96
|
+
throw new Error(`Controller at ${normalizedNew} is not responding. Ensure the controller is running and reachable, then run "aifabrix up-dataplane" again.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
logger.log(chalk.green(`✓ Using controller: ${normalizedNew}`));
|
|
100
|
+
return normalizedNew;
|
|
101
|
+
}
|
|
102
|
+
|
|
28
103
|
/**
|
|
29
104
|
* Register or rotate dataplane: if app exists in controller, rotate secret; otherwise register.
|
|
30
105
|
* @async
|
|
@@ -45,6 +120,17 @@ async function registerOrRotateDataplane(options, controllerUrl, environmentKey,
|
|
|
45
120
|
}
|
|
46
121
|
}
|
|
47
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Deploy dataplane app (send manifest to controller).
|
|
125
|
+
* @param {Object} options - Commander options (registry, registryMode, image)
|
|
126
|
+
* @returns {Promise<void>}
|
|
127
|
+
*/
|
|
128
|
+
async function deployDataplaneToController(options) {
|
|
129
|
+
const imageOverride = options.image || (options.registry ? buildDataplaneImageRef(options.registry) : undefined);
|
|
130
|
+
const deployOpts = { imageOverride, image: imageOverride, registryMode: options.registryMode };
|
|
131
|
+
await app.deployApp('dataplane', deployOpts);
|
|
132
|
+
}
|
|
133
|
+
|
|
48
134
|
/**
|
|
49
135
|
* Build full image ref from registry and dataplane config (registry/name:tag)
|
|
50
136
|
* @param {string} registry - Registry URL
|
|
@@ -85,7 +171,8 @@ async function handleUpDataplane(options = {}) {
|
|
|
85
171
|
}
|
|
86
172
|
logger.log(chalk.blue('Starting up-dataplane (register/rotate, deploy, then run dataplane locally)...\n'));
|
|
87
173
|
|
|
88
|
-
const
|
|
174
|
+
const controllerUrl = await resolveControllerUrlWithHealthCheck();
|
|
175
|
+
const environmentKey = await resolveEnvironment();
|
|
89
176
|
const authConfig = await checkAuthentication(controllerUrl, environmentKey, { throwOnFailure: true });
|
|
90
177
|
|
|
91
178
|
const cfg = await config.getConfig();
|
|
@@ -103,10 +190,7 @@ async function handleUpDataplane(options = {}) {
|
|
|
103
190
|
|
|
104
191
|
await registerOrRotateDataplane(options, controllerUrl, environmentKey, authConfig);
|
|
105
192
|
|
|
106
|
-
|
|
107
|
-
const deployOpts = { imageOverride, image: imageOverride, registryMode: options.registryMode };
|
|
108
|
-
|
|
109
|
-
await app.deployApp('dataplane', deployOpts);
|
|
193
|
+
await deployDataplaneToController(options);
|
|
110
194
|
logger.log('');
|
|
111
195
|
await app.runApp('dataplane', { skipEnvOutputPath: true });
|
|
112
196
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @author AI Fabrix Team
|
|
4
4
|
* @version 2.0.0
|
|
5
5
|
*/
|
|
6
|
+
/* eslint-disable max-lines -- 502 lines; wizard payload and helpers */
|
|
6
7
|
|
|
7
8
|
const chalk = require('chalk');
|
|
8
9
|
const ora = require('ora');
|
|
@@ -181,11 +182,12 @@ function buildConfigPreferences(configPrefs) {
|
|
|
181
182
|
* @param {string} params.mode - Selected mode
|
|
182
183
|
* @param {Object} params.prefs - Configuration preferences
|
|
183
184
|
* @param {string} [params.credentialIdOrKey] - Credential ID or key
|
|
184
|
-
* @param {string} [params.systemIdOrKey] - System ID or key
|
|
185
|
+
* @param {string} [params.systemIdOrKey] - System ID or key (application/system key, not datasource/entity key)
|
|
185
186
|
* @param {string} [params.entityName] - Entity name for multi-entity OpenAPI (from discover-entities)
|
|
187
|
+
* @param {string|null} [params.systemDisplayName] - System-level display name for credential (e.g. 'Hubspot Demo')
|
|
186
188
|
* @returns {Object} Configuration payload
|
|
187
189
|
*/
|
|
188
|
-
function buildConfigPayload({ openapiSpec, detectedType, mode, prefs, credentialIdOrKey, systemIdOrKey, entityName }) {
|
|
190
|
+
function buildConfigPayload({ openapiSpec, detectedType, mode, prefs, credentialIdOrKey, systemIdOrKey, entityName, systemDisplayName }) {
|
|
189
191
|
const detectedTypeValue = detectedType?.recommendedType || detectedType?.apiType || detectedType?.selectedType || 'record-based';
|
|
190
192
|
const payload = {
|
|
191
193
|
openapiSpec,
|
|
@@ -199,6 +201,7 @@ function buildConfigPayload({ openapiSpec, detectedType, mode, prefs, credential
|
|
|
199
201
|
if (credentialIdOrKey) payload.credentialIdOrKey = credentialIdOrKey;
|
|
200
202
|
if (systemIdOrKey) payload.systemIdOrKey = systemIdOrKey;
|
|
201
203
|
if (entityName) payload.entityName = entityName;
|
|
204
|
+
if (systemDisplayName) payload.systemDisplayName = systemDisplayName;
|
|
202
205
|
if (prefs.debug) payload.debug = true;
|
|
203
206
|
return payload;
|
|
204
207
|
}
|
|
@@ -298,7 +298,8 @@ async function callGenerateApi(dataplaneUrl, authConfig, options, prefs) {
|
|
|
298
298
|
prefs,
|
|
299
299
|
credentialIdOrKey: options.credentialIdOrKey,
|
|
300
300
|
systemIdOrKey: options.systemIdOrKey,
|
|
301
|
-
entityName: options.entityName
|
|
301
|
+
entityName: options.entityName,
|
|
302
|
+
systemDisplayName: options.systemDisplayName
|
|
302
303
|
});
|
|
303
304
|
return await generateConfig(dataplaneUrl, authConfig, configPayload);
|
|
304
305
|
}
|
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
} = require('./wizard-core');
|
|
25
25
|
const { discoverEntities } = require('../api/wizard.api');
|
|
26
26
|
const { validateEntityNameForOpenApi } = require('../validation/wizard-datasource-validation');
|
|
27
|
+
const { humanizeAppKey } = require('../generator/wizard-prompts-secondary');
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Validate entityName for headless config (throws if invalid)
|
|
@@ -80,6 +81,9 @@ async function executeWizardFromConfig(wizardConfig, dataplaneUrl, authConfig, o
|
|
|
80
81
|
|
|
81
82
|
await validateHeadlessEntityName(source?.entityName, openapiSpec, sourceType, dataplaneUrl, authConfig);
|
|
82
83
|
|
|
84
|
+
const systemDisplayName = wizardConfig.systemDisplayName ||
|
|
85
|
+
(mode === 'create-system' ? humanizeAppKey(appName) : undefined);
|
|
86
|
+
|
|
83
87
|
// Step 5: Generate Configuration
|
|
84
88
|
const { systemConfig, datasourceConfigs, systemKey } = await handleConfigurationGeneration(dataplaneUrl, authConfig, {
|
|
85
89
|
mode,
|
|
@@ -93,7 +97,8 @@ async function executeWizardFromConfig(wizardConfig, dataplaneUrl, authConfig, o
|
|
|
93
97
|
datasourceKeys: source?.datasourceKeys,
|
|
94
98
|
configurationValues: source?.configurationValues,
|
|
95
99
|
entityName: source?.entityName,
|
|
96
|
-
appName
|
|
100
|
+
appName,
|
|
101
|
+
systemDisplayName
|
|
97
102
|
});
|
|
98
103
|
|
|
99
104
|
// Step 6: Validate Configuration
|
package/lib/commands/wizard.js
CHANGED
|
@@ -48,6 +48,7 @@ const {
|
|
|
48
48
|
showWizardConfigSummary,
|
|
49
49
|
ensureIntegrationDir
|
|
50
50
|
} = require('./wizard-helpers');
|
|
51
|
+
const { humanizeAppKey } = require('../generator/wizard-prompts-secondary');
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
54
|
* Create wizard session with given mode and optional systemIdOrKey (no prompts)
|
|
@@ -150,7 +151,8 @@ async function handleInteractiveConfigGeneration(options) {
|
|
|
150
151
|
sourceType: options.sourceType,
|
|
151
152
|
platformKey: options.sourceType === 'known-platform' ? options.sourceData : undefined,
|
|
152
153
|
entityName: options.entityName,
|
|
153
|
-
appName: options.appName
|
|
154
|
+
appName: options.appName,
|
|
155
|
+
systemDisplayName: options.systemDisplayName
|
|
154
156
|
});
|
|
155
157
|
|
|
156
158
|
return {
|
|
@@ -190,7 +192,7 @@ async function handleConfigurationReview(dataplaneUrl, authConfig, sessionId, sy
|
|
|
190
192
|
logger.warn('Preview unavailable, showing full configuration.');
|
|
191
193
|
}
|
|
192
194
|
|
|
193
|
-
const reviewResult = await promptForConfigReview({ preview, systemConfig, datasourceConfigs });
|
|
195
|
+
const reviewResult = await promptForConfigReview({ preview, systemConfig, datasourceConfigs, appKey: opts.appKey });
|
|
194
196
|
|
|
195
197
|
if (reviewResult.action === 'cancel') {
|
|
196
198
|
logger.log(chalk.yellow('Wizard cancelled.'));
|
|
@@ -251,7 +253,8 @@ async function doWizardSteps(appKey, dataplaneUrl, authConfig, sessionId, flowOp
|
|
|
251
253
|
sourceData,
|
|
252
254
|
entityName: entityName || undefined,
|
|
253
255
|
appName: appKey,
|
|
254
|
-
debug
|
|
256
|
+
debug,
|
|
257
|
+
systemDisplayName: flowOpts.systemDisplayName
|
|
255
258
|
});
|
|
256
259
|
const { systemConfig, datasourceConfigs, systemKey, preferences: savedPrefs } = genResult;
|
|
257
260
|
state.preferences = savedPrefs || {};
|
|
@@ -314,7 +317,7 @@ async function runWizardStepsAfterSession(appKey, dataplaneUrl, authConfig, sess
|
|
|
314
317
|
* @returns {Promise<void>} Resolves when wizard flow completes
|
|
315
318
|
*/
|
|
316
319
|
async function executeWizardFlow(appKey, dataplaneUrl, authConfig, flowOpts = {}) {
|
|
317
|
-
const { mode, systemIdOrKey, configPath, debug } = flowOpts;
|
|
320
|
+
const { mode, systemIdOrKey, configPath, debug, systemDisplayName } = flowOpts;
|
|
318
321
|
|
|
319
322
|
if (debug) {
|
|
320
323
|
logger.log(chalk.gray(`[DEBUG] Wizard debug mode enabled for app: ${appKey}`));
|
|
@@ -329,7 +332,8 @@ async function executeWizardFlow(appKey, dataplaneUrl, authConfig, flowOpts = {}
|
|
|
329
332
|
systemIdOrKey,
|
|
330
333
|
platforms,
|
|
331
334
|
configPath,
|
|
332
|
-
debug: flowOpts.debug
|
|
335
|
+
debug: flowOpts.debug,
|
|
336
|
+
systemDisplayName
|
|
333
337
|
});
|
|
334
338
|
if (!state) return;
|
|
335
339
|
|
|
@@ -474,12 +478,15 @@ async function handleWizardInteractive(options) {
|
|
|
474
478
|
if (!resolved) return;
|
|
475
479
|
const { appKey, configPath, dataplaneUrl, authConfig } = resolved;
|
|
476
480
|
const systemIdOrKey = mode === 'add-datasource' ? resolved.systemIdOrKey : undefined;
|
|
481
|
+
const systemDisplayName = options.systemDisplayName || options.displayName ||
|
|
482
|
+
(mode === 'create-system' ? humanizeAppKey(appKey) : undefined);
|
|
477
483
|
try {
|
|
478
484
|
await executeWizardFlow(appKey, dataplaneUrl, authConfig, {
|
|
479
485
|
mode,
|
|
480
486
|
systemIdOrKey,
|
|
481
487
|
configPath,
|
|
482
|
-
debug: options.debug
|
|
488
|
+
debug: options.debug,
|
|
489
|
+
systemDisplayName
|
|
483
490
|
});
|
|
484
491
|
logger.log(chalk.gray(`To change settings, edit integration/${appKey}/wizard.yaml and run: aifabrix wizard ${appKey}`));
|
|
485
492
|
} catch (error) {
|
|
@@ -77,13 +77,15 @@ function generateReadme(systemKey, application, dataSources) {
|
|
|
77
77
|
};
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
const authType = application.authentication?.type || application.authentication?.method;
|
|
80
81
|
return generateExternalReadmeContent({
|
|
81
82
|
appName: systemKey,
|
|
82
83
|
systemKey,
|
|
83
84
|
systemType: application.type,
|
|
84
85
|
displayName: application.displayName,
|
|
85
86
|
description: application.description,
|
|
86
|
-
datasources
|
|
87
|
+
datasources,
|
|
88
|
+
authType
|
|
87
89
|
});
|
|
88
90
|
}
|
|
89
91
|
|
|
@@ -196,13 +196,15 @@ async function writeSplitExternalSchemaFiles({ outputDir, systemKey, application
|
|
|
196
196
|
await writeYamlFile(rbacPath, rbac, { indent: 2, lineWidth: -1 });
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
const authType = application.authentication?.type || application.authentication?.method;
|
|
199
200
|
const readmeContent = generateExternalReadmeContent({
|
|
200
201
|
appName: systemKey,
|
|
201
202
|
systemKey,
|
|
202
203
|
systemType: application.type,
|
|
203
204
|
displayName: application.displayName,
|
|
204
205
|
description: application.description,
|
|
205
|
-
datasources: dataSources
|
|
206
|
+
datasources: dataSources,
|
|
207
|
+
authType
|
|
206
208
|
});
|
|
207
209
|
const readmePath = path.join(outputDir, 'README.md');
|
|
208
210
|
await fs.promises.writeFile(readmePath, readmeContent, 'utf8');
|
|
@@ -107,6 +107,16 @@ async function promptForEntitySelection(entities = []) {
|
|
|
107
107
|
/** @param {*} o - Value to stringify */
|
|
108
108
|
const _s = (o) => (o === null || o === undefined || o === '' ? '—' : String(o));
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Humanize app key for display name (must stay in sync with prepareWizardContext in lib/generator/wizard.js).
|
|
112
|
+
* @param {string} appKey - Application key (e.g. hubspot-demo)
|
|
113
|
+
* @returns {string} Display name (e.g. Hubspot Demo)
|
|
114
|
+
*/
|
|
115
|
+
function humanizeAppKey(appKey) {
|
|
116
|
+
if (!appKey || typeof appKey !== 'string') return appKey || '';
|
|
117
|
+
return appKey.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
118
|
+
}
|
|
119
|
+
|
|
110
120
|
/** @param {Object} sys - systemSummary */
|
|
111
121
|
function _formatSystem(sys) {
|
|
112
122
|
return [
|
|
@@ -149,8 +159,11 @@ function _formatFieldMappings(fm) {
|
|
|
149
159
|
/**
|
|
150
160
|
* Derive a preview summary from systemConfig and datasourceConfigs when the dataplane
|
|
151
161
|
* preview API does not return summaries. Ensures a compact summary is always shown.
|
|
162
|
+
* When appKey is provided, system key/displayName and datasource keys are overridden
|
|
163
|
+
* to match what prepareWizardContext will write (so the preview matches saved files).
|
|
152
164
|
* @param {Object} systemConfig - System configuration
|
|
153
165
|
* @param {Object[]} datasourceConfigs - Array of datasource configurations
|
|
166
|
+
* @param {string} [appKey] - Optional app key; when set, overrides system key/displayName and rewrites datasource keys
|
|
154
167
|
* @returns {Object} Preview object compatible with formatPreviewSummary
|
|
155
168
|
*/
|
|
156
169
|
function _buildDatasourceSummary(ds) {
|
|
@@ -171,8 +184,9 @@ function _buildDatasourceSummary(ds) {
|
|
|
171
184
|
}
|
|
172
185
|
|
|
173
186
|
function _buildSystemSummary(sys) {
|
|
174
|
-
const baseUrl = sys.openapi?.servers?.[0]?.url ?? sys.baseUrl ?? sys.openapi?.baseUrl ??
|
|
175
|
-
|
|
187
|
+
const baseUrl = sys.openapi?.servers?.[0]?.url ?? sys.baseUrl ?? sys.openapi?.baseUrl ??
|
|
188
|
+
sys.authentication?.variables?.baseUrl ?? null;
|
|
189
|
+
const authType = sys.authentication?.type ?? sys.authentication?.method ?? sys.authenticationType ?? null;
|
|
176
190
|
const endpointCount = sys.openapi?.endpoints?.length ??
|
|
177
191
|
(sys.openapi?.operations ? Object.keys(sys.openapi.operations || {}).length : null);
|
|
178
192
|
return {
|
|
@@ -191,7 +205,7 @@ function _buildFieldMappingsSummary(ds0) {
|
|
|
191
205
|
return mappedFields.length > 0 ? { mappingCount: mappedFields.length, mappedFields, unmappedFields: [] } : null;
|
|
192
206
|
}
|
|
193
207
|
|
|
194
|
-
function derivePreviewFromConfig(systemConfig, datasourceConfigs) {
|
|
208
|
+
function derivePreviewFromConfig(systemConfig, datasourceConfigs, appKey) {
|
|
195
209
|
const sys = systemConfig || {};
|
|
196
210
|
const dsList = Array.isArray(datasourceConfigs) ? datasourceConfigs : (datasourceConfigs ? [datasourceConfigs] : []);
|
|
197
211
|
|
|
@@ -199,7 +213,23 @@ function derivePreviewFromConfig(systemConfig, datasourceConfigs) {
|
|
|
199
213
|
systemSummary: _buildSystemSummary(sys),
|
|
200
214
|
fieldMappingsSummary: _buildFieldMappingsSummary(dsList[0] || {})
|
|
201
215
|
};
|
|
202
|
-
|
|
216
|
+
let datasourceSummaries = dsList.map(_buildDatasourceSummary);
|
|
217
|
+
|
|
218
|
+
if (appKey && typeof appKey === 'string') {
|
|
219
|
+
result.systemSummary.key = appKey;
|
|
220
|
+
result.systemSummary.displayName = humanizeAppKey(appKey);
|
|
221
|
+
const originalSystemKey = sys.key || appKey;
|
|
222
|
+
const originalPrefix = `${originalSystemKey}-`;
|
|
223
|
+
datasourceSummaries = datasourceSummaries.map((summary, i) => {
|
|
224
|
+
const ds = dsList[i] || {};
|
|
225
|
+
const dsKey = ds.key || '';
|
|
226
|
+
const newKey = dsKey && dsKey.startsWith(originalPrefix)
|
|
227
|
+
? `${appKey}-${dsKey.substring(originalPrefix.length)}`
|
|
228
|
+
: `${appKey}-${ds.entityType || ds.entityKey || (dsKey && dsKey.split('-').pop()) || 'default'}`;
|
|
229
|
+
return { ...summary, key: newKey };
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
203
233
|
if (datasourceSummaries.length === 1) {
|
|
204
234
|
result.datasourceSummary = datasourceSummaries[0];
|
|
205
235
|
} else if (datasourceSummaries.length > 1) {
|
|
@@ -247,11 +277,12 @@ function formatPreviewSummary(preview) {
|
|
|
247
277
|
* @param {Object|null} [opts.preview] - Preview data from getPreview (null = fallback to YAML)
|
|
248
278
|
* @param {Object} opts.systemConfig - System configuration
|
|
249
279
|
* @param {Object[]} opts.datasourceConfigs - Array of datasource configurations
|
|
280
|
+
* @param {string} [opts.appKey] - App key; when set and using fallback preview, overrides system key/displayName and datasource keys
|
|
250
281
|
* @returns {Promise<Object>} Object with action ('accept'|'cancel') and optionally edited configs
|
|
251
282
|
*/
|
|
252
|
-
async function promptForConfigReview({ preview, systemConfig, datasourceConfigs }) {
|
|
283
|
+
async function promptForConfigReview({ preview, systemConfig, datasourceConfigs, appKey }) {
|
|
253
284
|
const hasSummary = preview && (preview.systemSummary || preview.datasourceSummary || (preview.datasourceSummaries && preview.datasourceSummaries.length > 0));
|
|
254
|
-
const summaryToShow = hasSummary ? preview : derivePreviewFromConfig(systemConfig, datasourceConfigs);
|
|
285
|
+
const summaryToShow = hasSummary ? preview : derivePreviewFromConfig(systemConfig, datasourceConfigs, appKey);
|
|
255
286
|
const canShowSummary = summaryToShow.systemSummary || summaryToShow.datasourceSummary ||
|
|
256
287
|
(summaryToShow.datasourceSummaries && summaryToShow.datasourceSummaries.length > 0);
|
|
257
288
|
|
|
@@ -290,5 +321,6 @@ module.exports = {
|
|
|
290
321
|
promptForEntitySelection,
|
|
291
322
|
promptForConfigReview,
|
|
292
323
|
derivePreviewFromConfig,
|
|
293
|
-
formatPreviewSummary
|
|
324
|
+
formatPreviewSummary,
|
|
325
|
+
humanizeAppKey
|
|
294
326
|
};
|
|
@@ -68,6 +68,8 @@ async function generateReadme(options) {
|
|
|
68
68
|
};
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
const auth = systemConfig.authentication || systemConfig.auth || {};
|
|
72
|
+
const authType = auth.type || auth.method || auth.authType;
|
|
71
73
|
const readmeContent = generateExternalReadmeContent({
|
|
72
74
|
appName,
|
|
73
75
|
systemKey,
|
|
@@ -75,7 +77,8 @@ async function generateReadme(options) {
|
|
|
75
77
|
displayName: systemConfig.displayName,
|
|
76
78
|
description: systemConfig.description,
|
|
77
79
|
fileExt: ext,
|
|
78
|
-
datasources
|
|
80
|
+
datasources,
|
|
81
|
+
authType
|
|
79
82
|
});
|
|
80
83
|
|
|
81
84
|
await fs.writeFile(readmePath, readmeContent, 'utf8');
|
package/lib/generator/wizard.js
CHANGED
|
@@ -15,7 +15,7 @@ const chalk = require('chalk');
|
|
|
15
15
|
const logger = require('../utils/logger');
|
|
16
16
|
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
17
17
|
const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
|
|
18
|
-
const { systemKeyToKvPrefix } = require('../utils/credential-secrets-env');
|
|
18
|
+
const { systemKeyToKvPrefix, securityKeyToVar, isValidKvPath } = require('../utils/credential-secrets-env');
|
|
19
19
|
const { generateReadme } = require('./wizard-readme');
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -297,72 +297,84 @@ async function generateOrUpdateVariablesYaml(params) {
|
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
/**
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
* @param {string} prefix - KV prefix (e.g. HUBSPOT)
|
|
304
|
-
*/
|
|
305
|
-
function addApiKeyAuthLines(lines, prefix) {
|
|
306
|
-
lines.push('# API Key Authentication');
|
|
307
|
-
lines.push(`KV_${prefix}_APIKEY=`);
|
|
308
|
-
lines.push('');
|
|
300
|
+
/** Path-style kv value: kv://systemKey/key (camelCase key). */
|
|
301
|
+
function toPathStyleKv(systemKey, key) {
|
|
302
|
+
return `kv://${systemKey}/${key}`;
|
|
309
303
|
}
|
|
310
304
|
|
|
311
305
|
/**
|
|
312
|
-
* Adds
|
|
306
|
+
* Adds env.template lines from authentication.security (path-style kv:// values).
|
|
313
307
|
* @param {Array<string>} lines - Lines array to append to
|
|
314
|
-
* @param {Object}
|
|
315
|
-
* @param {string}
|
|
308
|
+
* @param {Object} security - authentication.security object
|
|
309
|
+
* @param {string} systemKey - System key for path namespace
|
|
310
|
+
* @param {string} prefix - KV prefix (e.g. HUBSPOT)
|
|
316
311
|
*/
|
|
317
|
-
function
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
312
|
+
function addLinesFromSecurity(lines, security, systemKey, prefix) {
|
|
313
|
+
if (!security || typeof security !== 'object') return;
|
|
314
|
+
for (const key of Object.keys(security)) {
|
|
315
|
+
const envName = `KV_${prefix}_${securityKeyToVar(key)}`;
|
|
316
|
+
let value = security[key];
|
|
317
|
+
if (typeof value === 'string' && value.trim().startsWith('kv://') && isValidKvPath(value.trim())) {
|
|
318
|
+
value = value.trim();
|
|
319
|
+
} else {
|
|
320
|
+
value = toPathStyleKv(systemKey, key);
|
|
321
|
+
}
|
|
322
|
+
lines.push(`${envName}=${value}`);
|
|
323
|
+
}
|
|
324
|
+
if (Object.keys(security).length > 0) lines.push('');
|
|
323
325
|
}
|
|
324
326
|
|
|
325
|
-
/**
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
327
|
+
/** Fallback security keys by auth method (camelCase) for path-style kv://. */
|
|
328
|
+
const FALLBACK_SECURITY_BY_AUTH = {
|
|
329
|
+
oauth2: ['clientId', 'clientSecret'],
|
|
330
|
+
oauth: ['clientId', 'clientSecret'],
|
|
331
|
+
aad: ['clientId', 'clientSecret'],
|
|
332
|
+
apikey: ['apiKey'],
|
|
333
|
+
apiKey: ['apiKey'],
|
|
334
|
+
basic: ['username', 'password'],
|
|
335
|
+
queryParam: ['paramValue'],
|
|
336
|
+
oidc: [],
|
|
337
|
+
hmac: ['signingSecret'],
|
|
338
|
+
bearer: ['bearerToken'],
|
|
339
|
+
token: ['bearerToken'],
|
|
340
|
+
none: []
|
|
341
|
+
};
|
|
335
342
|
|
|
336
343
|
/**
|
|
337
|
-
* Adds
|
|
344
|
+
* Adds auth lines by type with path-style kv:// values (fallback when security is absent).
|
|
338
345
|
* @param {Array<string>} lines - Lines array to append to
|
|
346
|
+
* @param {string} authType - Authentication type
|
|
347
|
+
* @param {string} systemKey - System key
|
|
339
348
|
* @param {string} prefix - KV prefix
|
|
340
349
|
*/
|
|
341
|
-
function
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
350
|
+
function addFallbackAuthLines(lines, authType, systemKey, prefix) {
|
|
351
|
+
const keys = FALLBACK_SECURITY_BY_AUTH[authType] || FALLBACK_SECURITY_BY_AUTH.apikey;
|
|
352
|
+
if (keys.length === 0) return;
|
|
353
|
+
const labels = { oauth2: 'OAuth2', aad: 'Azure AD', apikey: 'API Key', basic: 'Basic', queryParam: 'Query Param', hmac: 'HMAC', bearer: 'Bearer Token' };
|
|
354
|
+
const label = labels[authType] || authType;
|
|
355
|
+
lines.push(`# ${label} Authentication`);
|
|
356
|
+
for (const key of keys) {
|
|
357
|
+
lines.push(`KV_${prefix}_${securityKeyToVar(key)}=${toPathStyleKv(systemKey, key)}`);
|
|
358
|
+
}
|
|
345
359
|
lines.push('');
|
|
346
360
|
}
|
|
347
361
|
|
|
348
362
|
/**
|
|
349
|
-
* Adds authentication lines
|
|
363
|
+
* Adds authentication lines: from authentication.security when present, else by auth type with path-style kv://.
|
|
350
364
|
* @param {Array<string>} lines - Lines array to append to
|
|
351
|
-
* @param {Object} auth - Authentication configuration
|
|
352
|
-
* @param {string} authType -
|
|
353
|
-
* @param {string} systemKey - System key
|
|
365
|
+
* @param {Object} auth - Authentication configuration (may have security, type/method)
|
|
366
|
+
* @param {string} authType - Normalized auth type
|
|
367
|
+
* @param {string} systemKey - System key for KV path namespace
|
|
354
368
|
*/
|
|
355
369
|
function addAuthenticationLines(lines, auth, authType, systemKey) {
|
|
356
370
|
const prefix = systemKeyToKvPrefix(systemKey);
|
|
357
371
|
if (!prefix) return;
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
} else
|
|
363
|
-
|
|
364
|
-
} else if (authType === 'basic') {
|
|
365
|
-
addBasicAuthLines(lines, prefix);
|
|
372
|
+
const security = auth?.security;
|
|
373
|
+
if (security && typeof security === 'object' && Object.keys(security).length > 0) {
|
|
374
|
+
lines.push('# Authentication (from security)');
|
|
375
|
+
addLinesFromSecurity(lines, security, systemKey, prefix);
|
|
376
|
+
} else {
|
|
377
|
+
addFallbackAuthLines(lines, authType, systemKey, prefix);
|
|
366
378
|
}
|
|
367
379
|
}
|
|
368
380
|
|
|
@@ -396,7 +408,7 @@ async function generateEnvTemplate(appPath, systemConfig, finalSystemKey) {
|
|
|
396
408
|
const lines = ['# Environment variables for external system integration', '# Use KV_* for credential push (aifabrix credential push)', ''];
|
|
397
409
|
|
|
398
410
|
const auth = systemConfig?.authentication || systemConfig?.auth || {};
|
|
399
|
-
const authType = auth.type || auth.authType || 'apikey';
|
|
411
|
+
const authType = (auth.type || auth.method || auth.authType || 'apikey').toLowerCase();
|
|
400
412
|
|
|
401
413
|
addAuthenticationLines(lines, auth, authType, systemKey);
|
|
402
414
|
addBaseUrlLines(lines, systemConfig);
|
|
@@ -20,11 +20,17 @@
|
|
|
20
20
|
},
|
|
21
21
|
"systemIdOrKey": {
|
|
22
22
|
"type": "string",
|
|
23
|
-
"description": "
|
|
23
|
+
"description": "Application/system key (e.g. hubspot-demo), not a datasource or entity key. Required when mode='add-datasource'. Must be the system key from the dataplane, not an entity key (e.g. 'companies').",
|
|
24
24
|
"pattern": "^[a-z0-9-]+$",
|
|
25
25
|
"minLength": 1,
|
|
26
26
|
"maxLength": 50
|
|
27
27
|
},
|
|
28
|
+
"systemDisplayName": {
|
|
29
|
+
"type": ["string", "null"],
|
|
30
|
+
"title": "Systemdisplayname",
|
|
31
|
+
"description": "System-level display name for the credential (e.g. 'Hubspot Demo'). When the OpenAPI title is entity-specific (e.g. 'Companies'), pass the system name here so authentication.displayName is system-level.",
|
|
32
|
+
"maxLength": 200
|
|
33
|
+
},
|
|
28
34
|
"source": {
|
|
29
35
|
"type": "object",
|
|
30
36
|
"description": "Source configuration for the wizard",
|
|
@@ -91,6 +91,33 @@ function normalizeDatasources(datasources, systemKey, fileExt = '.json') {
|
|
|
91
91
|
);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Builds secret path entries for README "Secrets" section per auth type.
|
|
96
|
+
* Path is the key for `aifabrix secret set <key> <value>` (no kv:// prefix; key format systemKey/secretKey in camelCase).
|
|
97
|
+
* @param {string} systemKey - System key
|
|
98
|
+
* @param {string} [authType] - Authentication type (oauth2, aad, apikey, basic, queryParam, hmac, bearer, token, none)
|
|
99
|
+
* @returns {Array<{path: string, description: string}>} secretPaths for template (path = key for secret set, no kv://)
|
|
100
|
+
*/
|
|
101
|
+
function buildSecretPaths(systemKey, authType) {
|
|
102
|
+
if (!systemKey || typeof systemKey !== 'string') return [];
|
|
103
|
+
const t = (authType && typeof authType === 'string') ? authType.toLowerCase() : 'apikey';
|
|
104
|
+
const map = {
|
|
105
|
+
oauth2: [{ path: `${systemKey}/clientId`, description: 'Client ID' }, { path: `${systemKey}/clientSecret`, description: 'Client Secret' }],
|
|
106
|
+
oauth: [{ path: `${systemKey}/clientId`, description: 'Client ID' }, { path: `${systemKey}/clientSecret`, description: 'Client Secret' }],
|
|
107
|
+
aad: [{ path: `${systemKey}/clientId`, description: 'Client ID' }, { path: `${systemKey}/clientSecret`, description: 'Client Secret' }],
|
|
108
|
+
apikey: [{ path: `${systemKey}/apiKey`, description: 'API Key' }],
|
|
109
|
+
apiKey: [{ path: `${systemKey}/apiKey`, description: 'API Key' }],
|
|
110
|
+
basic: [{ path: `${systemKey}/username`, description: 'Username' }, { path: `${systemKey}/password`, description: 'Password' }],
|
|
111
|
+
queryparam: [{ path: `${systemKey}/paramValue`, description: 'Query parameter value' }],
|
|
112
|
+
hmac: [{ path: `${systemKey}/signingSecret`, description: 'Signing secret' }],
|
|
113
|
+
bearer: [{ path: `${systemKey}/bearerToken`, description: 'Bearer token' }],
|
|
114
|
+
token: [{ path: `${systemKey}/bearerToken`, description: 'Bearer token' }],
|
|
115
|
+
oidc: [],
|
|
116
|
+
none: []
|
|
117
|
+
};
|
|
118
|
+
return map[t] || map.apikey;
|
|
119
|
+
}
|
|
120
|
+
|
|
94
121
|
/**
|
|
95
122
|
* Builds the external system README template context
|
|
96
123
|
* @function buildExternalReadmeContext
|
|
@@ -102,6 +129,8 @@ function normalizeDatasources(datasources, systemKey, fileExt = '.json') {
|
|
|
102
129
|
* @param {string} [params.description] - Description
|
|
103
130
|
* @param {Array} [params.datasources] - Datasource objects
|
|
104
131
|
* @param {string} [params.fileExt] - File extension for config files (e.g. '.json', '.yaml'); default '.json'
|
|
132
|
+
* @param {string} [params.authType] - Authentication type for Secrets section (oauth2, aad, apikey, basic, etc.)
|
|
133
|
+
* @param {Object} [params.authentication] - Full authentication object (authType used if authType not set)
|
|
105
134
|
* @returns {Object} Template context
|
|
106
135
|
*/
|
|
107
136
|
function buildExternalReadmeContext(params = {}) {
|
|
@@ -112,6 +141,8 @@ function buildExternalReadmeContext(params = {}) {
|
|
|
112
141
|
const systemType = params.systemType || 'openapi';
|
|
113
142
|
const fileExt = params.fileExt !== undefined ? params.fileExt : '.json';
|
|
114
143
|
const datasources = normalizeDatasources(params.datasources, systemKey, fileExt);
|
|
144
|
+
const authType = params.authType || params.authentication?.type || params.authentication?.method || params.authentication?.authType;
|
|
145
|
+
const secretPaths = buildSecretPaths(systemKey, authType);
|
|
115
146
|
|
|
116
147
|
return {
|
|
117
148
|
appName,
|
|
@@ -122,7 +153,8 @@ function buildExternalReadmeContext(params = {}) {
|
|
|
122
153
|
fileExt: fileExt && fileExt.startsWith('.') ? fileExt : `.${fileExt || 'json'}`,
|
|
123
154
|
datasourceCount: datasources.length,
|
|
124
155
|
hasDatasources: datasources.length > 0,
|
|
125
|
-
datasources
|
|
156
|
+
datasources,
|
|
157
|
+
secretPaths
|
|
126
158
|
};
|
|
127
159
|
}
|
|
128
160
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aifabrix/builder",
|
|
3
|
-
"version": "2.42.
|
|
3
|
+
"version": "2.42.1",
|
|
4
4
|
"description": "AI Fabrix Local Fabric & Deployment SDK",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "node tests/scripts/test-wrapper.js",
|
|
12
12
|
"test:ci": "bash tests/scripts/ci-simulate.sh",
|
|
13
|
-
"test:same-as-github": "
|
|
13
|
+
"test:same-as-github": "npm run build:ci",
|
|
14
14
|
"test:coverage": "cross-env RUN_COVERAGE=1 node tests/scripts/test-wrapper.js",
|
|
15
15
|
"test:coverage:nyc": "nyc --reporter=text --reporter=lcov --reporter=html jest --config jest.config.coverage.js --runInBand",
|
|
16
16
|
"test:watch": "jest --watch",
|
|
@@ -46,6 +46,18 @@ Edit files in `integration/{{appName}}/`:
|
|
|
46
46
|
- **Field mappings**: `{{systemKey}}-datasource-*-datasource{{fileExt}}` (dimensions, attributes, operations)
|
|
47
47
|
- **Credential and configuration**: `env.template` (security settings and configuration variables)
|
|
48
48
|
|
|
49
|
+
{{#if secretPaths}}{{#if secretPaths.length}}
|
|
50
|
+
### Secrets
|
|
51
|
+
|
|
52
|
+
Secrets are resolved from `.aifabrix` or key vault. Set them with:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
{{#each secretPaths}}
|
|
56
|
+
aifabrix secret set {{path}} <your value> # {{description}}
|
|
57
|
+
{{/each}}
|
|
58
|
+
```
|
|
59
|
+
{{/if}}{{/if}}
|
|
60
|
+
|
|
49
61
|
### 3. Validate Configuration
|
|
50
62
|
|
|
51
63
|
```bash
|
|
@@ -61,11 +73,12 @@ aifabrix repair {{appName}}
|
|
|
61
73
|
```
|
|
62
74
|
|
|
63
75
|
Options:
|
|
64
|
-
--
|
|
65
|
-
--
|
|
66
|
-
--
|
|
67
|
-
--
|
|
68
|
-
--
|
|
76
|
+
--auth <method> Set authentication method (oauth2, aad, apikey, basic, queryParam, oidc, hmac, none); updates system file and env.template
|
|
77
|
+
--dry-run Report changes only; do not write
|
|
78
|
+
--rbac Ensure RBAC permissions per datasource and add default Admin/Reader roles if none exist
|
|
79
|
+
--expose Set exposed.attributes on each datasource to all fieldMappings.attributes keys
|
|
80
|
+
--sync Add default sync section to datasources that lack it
|
|
81
|
+
--test Generate testPayload.payloadTemplate and testPayload.expectedResult from attributes
|
|
69
82
|
|
|
70
83
|
### 5. Upload to dataplane
|
|
71
84
|
|