@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
|
@@ -12,35 +12,41 @@ const fs = require('fs').promises;
|
|
|
12
12
|
* Prompt for wizard mode selection
|
|
13
13
|
* @async
|
|
14
14
|
* @function promptForMode
|
|
15
|
+
* @param {string} [defaultMode] - Default value ('create-system' | 'add-datasource')
|
|
15
16
|
* @returns {Promise<string>} Selected mode ('create-system' | 'add-datasource')
|
|
16
17
|
*/
|
|
17
|
-
async function promptForMode() {
|
|
18
|
+
async function promptForMode(defaultMode) {
|
|
19
|
+
const choices = [
|
|
20
|
+
{ name: 'Create a new external system', value: 'create-system' },
|
|
21
|
+
{ name: 'Add datasource to existing system', value: 'add-datasource' }
|
|
22
|
+
];
|
|
18
23
|
const { mode } = await inquirer.prompt([
|
|
19
24
|
{
|
|
20
25
|
type: 'list',
|
|
21
26
|
name: 'mode',
|
|
22
27
|
message: 'What would you like to do?',
|
|
23
|
-
choices
|
|
24
|
-
|
|
25
|
-
{ name: 'Add datasource to existing system', value: 'add-datasource' }
|
|
26
|
-
]
|
|
28
|
+
choices,
|
|
29
|
+
default: defaultMode && choices.some(c => c.value === defaultMode) ? defaultMode : undefined
|
|
27
30
|
}
|
|
28
31
|
]);
|
|
29
32
|
return mode;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
|
-
* Prompt for existing system ID or key (for add-datasource mode)
|
|
36
|
+
* Prompt for existing system ID or key (for add-datasource mode).
|
|
37
|
+
* Only external systems (OpenAPI, MCP, custom) support add-datasource; webapps do not.
|
|
34
38
|
* @async
|
|
35
39
|
* @function promptForSystemIdOrKey
|
|
40
|
+
* @param {string} [defaultValue] - Default value (e.g. from loaded wizard.yaml)
|
|
36
41
|
* @returns {Promise<string>} System ID or key
|
|
37
42
|
*/
|
|
38
|
-
async function promptForSystemIdOrKey() {
|
|
43
|
+
async function promptForSystemIdOrKey(defaultValue) {
|
|
39
44
|
const { systemIdOrKey } = await inquirer.prompt([
|
|
40
45
|
{
|
|
41
46
|
type: 'input',
|
|
42
47
|
name: 'systemIdOrKey',
|
|
43
|
-
message: 'Enter the existing system ID or key:',
|
|
48
|
+
message: 'Enter the existing external system ID or key (not a webapp):',
|
|
49
|
+
default: defaultValue,
|
|
44
50
|
validate: (input) => {
|
|
45
51
|
if (!input || typeof input !== 'string' || input.trim().length === 0) {
|
|
46
52
|
return 'System ID or key is required';
|
|
@@ -55,20 +61,24 @@ async function promptForSystemIdOrKey() {
|
|
|
55
61
|
* Prompt for source type selection
|
|
56
62
|
* @async
|
|
57
63
|
* @function promptForSourceType
|
|
64
|
+
* @param {Array<{key: string, displayName?: string}>} [platforms] - If provided and non-empty, include "Known platform"; otherwise omit it
|
|
58
65
|
* @returns {Promise<string>} Selected source type
|
|
59
66
|
*/
|
|
60
|
-
async function promptForSourceType() {
|
|
67
|
+
async function promptForSourceType(platforms = []) {
|
|
68
|
+
const choices = [
|
|
69
|
+
{ name: 'OpenAPI file (local file)', value: 'openapi-file' },
|
|
70
|
+
{ name: 'OpenAPI URL (remote URL)', value: 'openapi-url' },
|
|
71
|
+
{ name: 'MCP server', value: 'mcp-server' }
|
|
72
|
+
];
|
|
73
|
+
if (Array.isArray(platforms) && platforms.length > 0) {
|
|
74
|
+
choices.push({ name: 'Known platform (pre-configured)', value: 'known-platform' });
|
|
75
|
+
}
|
|
61
76
|
const { sourceType } = await inquirer.prompt([
|
|
62
77
|
{
|
|
63
78
|
type: 'list',
|
|
64
79
|
name: 'sourceType',
|
|
65
80
|
message: 'What is your source type?',
|
|
66
|
-
choices
|
|
67
|
-
{ name: 'OpenAPI file (local file)', value: 'openapi-file' },
|
|
68
|
-
{ name: 'OpenAPI URL (remote URL)', value: 'openapi-url' },
|
|
69
|
-
{ name: 'MCP server', value: 'mcp-server' },
|
|
70
|
-
{ name: 'Known platform (pre-configured)', value: 'known-platform' }
|
|
71
|
-
]
|
|
81
|
+
choices
|
|
72
82
|
}
|
|
73
83
|
]);
|
|
74
84
|
return sourceType;
|
|
@@ -176,11 +186,74 @@ async function promptForMcpServer() {
|
|
|
176
186
|
};
|
|
177
187
|
}
|
|
178
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Prompt for credential action (skip / create new / use existing).
|
|
191
|
+
* Choose Skip if you don't have credentials yet; you can add them later in env.template.
|
|
192
|
+
* @async
|
|
193
|
+
* @function promptForCredentialAction
|
|
194
|
+
* @returns {Promise<Object>} Object with action ('skip'|'create'|'select') and optional credentialIdOrKey
|
|
195
|
+
*/
|
|
196
|
+
async function promptForCredentialAction() {
|
|
197
|
+
const { action } = await inquirer.prompt([
|
|
198
|
+
{
|
|
199
|
+
type: 'list',
|
|
200
|
+
name: 'action',
|
|
201
|
+
message: 'Credential (optional; choose Skip if you don\'t have credentials yet):',
|
|
202
|
+
choices: [
|
|
203
|
+
{ name: 'Skip - configure credentials later', value: 'skip' },
|
|
204
|
+
{ name: 'Create new', value: 'create' },
|
|
205
|
+
{ name: 'Use existing', value: 'select' }
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
]);
|
|
209
|
+
if (action === 'select') {
|
|
210
|
+
const { credentialIdOrKey } = await inquirer.prompt([
|
|
211
|
+
{
|
|
212
|
+
type: 'input',
|
|
213
|
+
name: 'credentialIdOrKey',
|
|
214
|
+
message: 'Enter credential ID or key (must exist on the dataplane):',
|
|
215
|
+
validate: (input) => {
|
|
216
|
+
if (!input || typeof input !== 'string' || input.trim().length === 0) {
|
|
217
|
+
return 'Credential ID or key is required (or choose Skip at the previous step)';
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
]);
|
|
223
|
+
return { action, credentialIdOrKey: credentialIdOrKey.trim() };
|
|
224
|
+
}
|
|
225
|
+
return { action };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Re-prompt for credential ID/key when validation failed (e.g. not found on dataplane).
|
|
230
|
+
* Empty input means skip.
|
|
231
|
+
* @async
|
|
232
|
+
* @function promptForCredentialIdOrKeyRetry
|
|
233
|
+
* @param {string} [previousError] - Error message from dataplane (e.g. "Credential not found")
|
|
234
|
+
* @returns {Promise<Object>} { credentialIdOrKey: string } or { skip: true } if user leaves empty
|
|
235
|
+
*/
|
|
236
|
+
async function promptForCredentialIdOrKeyRetry(previousError) {
|
|
237
|
+
const msg = previousError
|
|
238
|
+
? `Credential not found or invalid (${String(previousError).slice(0, 60)}). Enter ID/key or leave empty to skip:`
|
|
239
|
+
: 'Enter credential ID or key (or leave empty to skip):';
|
|
240
|
+
const { credentialIdOrKey } = await inquirer.prompt([
|
|
241
|
+
{
|
|
242
|
+
type: 'input',
|
|
243
|
+
name: 'credentialIdOrKey',
|
|
244
|
+
message: msg,
|
|
245
|
+
default: ''
|
|
246
|
+
}
|
|
247
|
+
]);
|
|
248
|
+
const trimmed = (credentialIdOrKey && credentialIdOrKey.trim()) || '';
|
|
249
|
+
return trimmed ? { credentialIdOrKey: trimmed } : { skip: true };
|
|
250
|
+
}
|
|
251
|
+
|
|
179
252
|
/**
|
|
180
253
|
* Prompt for known platform selection
|
|
181
254
|
* @async
|
|
182
255
|
* @function promptForKnownPlatform
|
|
183
|
-
* @param {string
|
|
256
|
+
* @param {Array<{key: string, displayName?: string}>} [platforms] - List of available platforms (if provided)
|
|
184
257
|
* @returns {Promise<string>} Selected platform key
|
|
185
258
|
*/
|
|
186
259
|
async function promptForKnownPlatform(platforms = []) {
|
|
@@ -236,10 +309,21 @@ async function promptForUserIntent() {
|
|
|
236
309
|
* Prompt for user preferences
|
|
237
310
|
* @async
|
|
238
311
|
* @function promptForUserPreferences
|
|
239
|
-
* @returns {Promise<Object>} User preferences object
|
|
312
|
+
* @returns {Promise<Object>} User preferences object (fieldOnboardingLevel, mcp, abac, rbac)
|
|
240
313
|
*/
|
|
241
314
|
async function promptForUserPreferences() {
|
|
242
315
|
const answers = await inquirer.prompt([
|
|
316
|
+
{
|
|
317
|
+
type: 'list',
|
|
318
|
+
name: 'fieldOnboardingLevel',
|
|
319
|
+
message: 'Field onboarding level:',
|
|
320
|
+
choices: [
|
|
321
|
+
{ name: 'full - All fields mapped and indexed', value: 'full' },
|
|
322
|
+
{ name: 'standard - Core and important fields only', value: 'standard' },
|
|
323
|
+
{ name: 'minimal - Essential fields only', value: 'minimal' }
|
|
324
|
+
],
|
|
325
|
+
default: 'full'
|
|
326
|
+
},
|
|
243
327
|
{
|
|
244
328
|
type: 'confirm',
|
|
245
329
|
name: 'mcp',
|
|
@@ -260,6 +344,7 @@ async function promptForUserPreferences() {
|
|
|
260
344
|
}
|
|
261
345
|
]);
|
|
262
346
|
return {
|
|
347
|
+
fieldOnboardingLevel: answers.fieldOnboardingLevel,
|
|
263
348
|
mcp: answers.mcp,
|
|
264
349
|
abac: answers.abac,
|
|
265
350
|
rbac: answers.rbac
|
|
@@ -297,7 +382,6 @@ async function promptForConfigReview(systemConfig, datasourceConfigs) {
|
|
|
297
382
|
message: 'What would you like to do?',
|
|
298
383
|
choices: [
|
|
299
384
|
{ name: 'Accept and save', value: 'accept' },
|
|
300
|
-
{ name: 'Edit configuration manually', value: 'edit' },
|
|
301
385
|
{ name: 'Cancel', value: 'cancel' }
|
|
302
386
|
]
|
|
303
387
|
}
|
|
@@ -307,32 +391,6 @@ async function promptForConfigReview(systemConfig, datasourceConfigs) {
|
|
|
307
391
|
return { action: 'cancel' };
|
|
308
392
|
}
|
|
309
393
|
|
|
310
|
-
if (action === 'edit') {
|
|
311
|
-
const { editedConfig } = await inquirer.prompt([
|
|
312
|
-
{
|
|
313
|
-
type: 'editor',
|
|
314
|
-
name: 'editedConfig',
|
|
315
|
-
message: 'Edit the configuration (JSON format):',
|
|
316
|
-
default: JSON.stringify({ systemConfig, datasourceConfigs }, null, 2),
|
|
317
|
-
validate: (input) => {
|
|
318
|
-
try {
|
|
319
|
-
JSON.parse(input);
|
|
320
|
-
return true;
|
|
321
|
-
} catch (error) {
|
|
322
|
-
return `Invalid JSON: ${error.message}`;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
]);
|
|
327
|
-
|
|
328
|
-
const parsed = JSON.parse(editedConfig);
|
|
329
|
-
return {
|
|
330
|
-
action: 'edit',
|
|
331
|
-
systemConfig: parsed.systemConfig,
|
|
332
|
-
datasourceConfigs: parsed.datasourceConfigs
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
|
|
336
394
|
return { action: 'accept' };
|
|
337
395
|
}
|
|
338
396
|
|
|
@@ -364,6 +422,24 @@ async function promptForAppName(defaultName) {
|
|
|
364
422
|
return appName.trim();
|
|
365
423
|
}
|
|
366
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Prompt: Run with saved config? (Y/n). Used when resuming from existing wizard.yaml.
|
|
427
|
+
* @async
|
|
428
|
+
* @function promptForRunWithSavedConfig
|
|
429
|
+
* @returns {Promise<boolean>} True to run with saved config, false to exit
|
|
430
|
+
*/
|
|
431
|
+
async function promptForRunWithSavedConfig() {
|
|
432
|
+
const { run } = await inquirer.prompt([
|
|
433
|
+
{
|
|
434
|
+
type: 'confirm',
|
|
435
|
+
name: 'run',
|
|
436
|
+
message: 'Run with saved config?',
|
|
437
|
+
default: true
|
|
438
|
+
}
|
|
439
|
+
]);
|
|
440
|
+
return run;
|
|
441
|
+
}
|
|
442
|
+
|
|
367
443
|
module.exports = {
|
|
368
444
|
promptForMode,
|
|
369
445
|
promptForSystemIdOrKey,
|
|
@@ -371,10 +447,13 @@ module.exports = {
|
|
|
371
447
|
promptForOpenApiFile,
|
|
372
448
|
promptForOpenApiUrl,
|
|
373
449
|
promptForMcpServer,
|
|
450
|
+
promptForCredentialAction,
|
|
451
|
+
promptForCredentialIdOrKeyRetry,
|
|
374
452
|
promptForKnownPlatform,
|
|
375
453
|
promptForUserIntent,
|
|
376
454
|
promptForUserPreferences,
|
|
377
455
|
promptForConfigReview,
|
|
378
|
-
promptForAppName
|
|
456
|
+
promptForAppName,
|
|
457
|
+
promptForRunWithSavedConfig
|
|
379
458
|
};
|
|
380
459
|
|
|
@@ -20,7 +20,7 @@ environments:
|
|
|
20
20
|
KEYCLOAK_PORT: 8082 # Internal port (container-to-container). KEYCLOAK_PUBLIC_PORT calculated automatically.
|
|
21
21
|
KEYCLOAK_PUBLIC_PORT: 8082
|
|
22
22
|
DATAPLANE_HOST: dataplane
|
|
23
|
-
DATAPLANE_PORT: 3001
|
|
23
|
+
DATAPLANE_PORT: 3001 # Internal port (container-to-container). DATAPLANE_PUBLIC_PORT calculated automatically.
|
|
24
24
|
NODE_ENV: production
|
|
25
25
|
PYTHONUNBUFFERED: 1
|
|
26
26
|
PYTHONDONTWRITEBYTECODE: 1
|
|
@@ -33,10 +33,8 @@ environments:
|
|
|
33
33
|
REDIS_PORT: 6379
|
|
34
34
|
MISO_HOST: localhost
|
|
35
35
|
MISO_PORT: 3010
|
|
36
|
-
MISO_PUBLIC_PORT: 3010
|
|
37
36
|
KEYCLOAK_HOST: localhost
|
|
38
37
|
KEYCLOAK_PORT: 8082
|
|
39
|
-
KEYCLOAK_PUBLIC_PORT: 8082
|
|
40
38
|
DATAPLANE_HOST: localhost
|
|
41
39
|
DATAPLANE_PORT: 3011
|
|
42
40
|
NODE_ENV: development
|
package/lib/utils/cli-utils.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* @version 2.0.0
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs').promises;
|
|
11
13
|
const logger = require('./logger');
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -289,10 +291,47 @@ function logError(command, errorMessages) {
|
|
|
289
291
|
function handleCommandError(error, command) {
|
|
290
292
|
const errorMessages = formatError(error);
|
|
291
293
|
logError(command, errorMessages);
|
|
294
|
+
if (error.wizardResumeMessage) {
|
|
295
|
+
logger.log(error.wizardResumeMessage);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Strip ANSI escape codes for plain-text logging (ESC [...] m) */
|
|
300
|
+
// eslint-disable-next-line no-control-regex -- intentional: match ANSI CSI sequences
|
|
301
|
+
const ANSI_CODE_RE = /\x1b\[[\d;]*m/g;
|
|
302
|
+
function stripAnsi(str) {
|
|
303
|
+
return typeof str === 'string' ? str.replace(ANSI_CODE_RE, '') : str;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Appends a wizard error to integration/<appKey>/error.log (timestamp + message only; no stack or secrets).
|
|
308
|
+
* Uses full formatted message (with validation details) when error.formatted is set, stripped of ANSI.
|
|
309
|
+
* Does not throw; logs and ignores write failures.
|
|
310
|
+
* @param {string} appKey - Application/integration key (e.g. app name or system key)
|
|
311
|
+
* @param {Error} error - The error that occurred
|
|
312
|
+
* @returns {Promise<void>}
|
|
313
|
+
*/
|
|
314
|
+
async function appendWizardError(appKey, error) {
|
|
315
|
+
if (!appKey || typeof appKey !== 'string' || !/^[a-z0-9-_]+$/.test(appKey)) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const dir = path.join(process.cwd(), 'integration', appKey);
|
|
319
|
+
const logPath = path.join(dir, 'error.log');
|
|
320
|
+
const rawMessage = (error && error.message) ? String(error.message) : String(error);
|
|
321
|
+
const fullPlain = (error && error.formatted) ? stripAnsi(error.formatted) : null;
|
|
322
|
+
const message = fullPlain && fullPlain.length > rawMessage.length ? fullPlain : rawMessage;
|
|
323
|
+
const line = `${new Date().toISOString()} ${message}\n`;
|
|
324
|
+
try {
|
|
325
|
+
await fs.mkdir(dir, { recursive: true });
|
|
326
|
+
await fs.appendFile(logPath, line, 'utf8');
|
|
327
|
+
} catch (e) {
|
|
328
|
+
logger.warn(`Could not write wizard error.log: ${e.message}`);
|
|
329
|
+
}
|
|
292
330
|
}
|
|
293
331
|
|
|
294
332
|
module.exports = {
|
|
295
333
|
validateCommand,
|
|
296
|
-
handleCommandError
|
|
334
|
+
handleCommandError,
|
|
335
|
+
appendWizardError
|
|
297
336
|
};
|
|
298
337
|
|
|
@@ -25,8 +25,9 @@ function getSecretsFilePath() {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Load client credentials from secrets.local.yaml
|
|
29
|
-
* Reads using pattern: <app-name>-client-idKeyVault and <app-name>-client-secretKeyVault
|
|
28
|
+
* Load client credentials from secrets.local.yaml or process.env (e.g. integration/hubspot/.env).
|
|
29
|
+
* Reads secrets file using pattern: <app-name>-client-idKeyVault and <app-name>-client-secretKeyVault.
|
|
30
|
+
* If not found, checks process.env.CLIENTID and process.env.CLIENTSECRET (set when .env is loaded).
|
|
30
31
|
* @param {string} appName - Application name
|
|
31
32
|
* @returns {Promise<{clientId: string, clientSecret: string}|null>} Credentials or null if not found
|
|
32
33
|
*/
|
|
@@ -37,31 +38,38 @@ async function loadClientCredentials(appName) {
|
|
|
37
38
|
|
|
38
39
|
try {
|
|
39
40
|
const secretsFile = getSecretsFilePath();
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
if (fs.existsSync(secretsFile)) {
|
|
42
|
+
const content = fs.readFileSync(secretsFile, 'utf8');
|
|
43
|
+
const secrets = yaml.load(content) || {};
|
|
44
|
+
|
|
45
|
+
const clientIdKey = `${appName}-client-idKeyVault`;
|
|
46
|
+
const clientSecretKey = `${appName}-client-secretKeyVault`;
|
|
47
|
+
|
|
48
|
+
const clientId = secrets[clientIdKey];
|
|
49
|
+
const clientSecret = secrets[clientSecretKey];
|
|
50
|
+
|
|
51
|
+
if (clientId && clientSecret) {
|
|
52
|
+
return {
|
|
53
|
+
clientId: String(clientId),
|
|
54
|
+
clientSecret: String(clientSecret)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
55
57
|
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
logger.warn(`Failed to load credentials from secrets.local.yaml: ${error.message}`);
|
|
60
|
+
}
|
|
56
61
|
|
|
62
|
+
// Fallback: use CLIENTID/CLIENTSECRET from process.env (e.g. from integration/hubspot/.env)
|
|
63
|
+
const envClientId = process.env.CLIENTID || process.env.CLIENT_ID;
|
|
64
|
+
const envClientSecret = process.env.CLIENTSECRET || process.env.CLIENT_SECRET;
|
|
65
|
+
if (envClientId && envClientSecret) {
|
|
57
66
|
return {
|
|
58
|
-
clientId:
|
|
59
|
-
clientSecret:
|
|
67
|
+
clientId: String(envClientId).trim(),
|
|
68
|
+
clientSecret: String(envClientSecret).trim()
|
|
60
69
|
};
|
|
61
|
-
} catch (error) {
|
|
62
|
-
logger.warn(`Failed to load credentials from secrets.local.yaml: ${error.message}`);
|
|
63
|
-
return null;
|
|
64
70
|
}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
/**
|
|
@@ -114,6 +114,39 @@ async function loadWizardConfig(configPath) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Write wizard configuration to wizard.yaml (creates directory if needed).
|
|
119
|
+
* @async
|
|
120
|
+
* @function writeWizardConfig
|
|
121
|
+
* @param {string} configPath - Path to wizard.yaml file
|
|
122
|
+
* @param {Object} config - Configuration object to write (will be dumped as YAML)
|
|
123
|
+
* @returns {Promise<void>}
|
|
124
|
+
* @throws {Error} If write fails
|
|
125
|
+
*/
|
|
126
|
+
async function writeWizardConfig(configPath, config) {
|
|
127
|
+
const resolvedPath = path.resolve(configPath);
|
|
128
|
+
const dir = path.dirname(resolvedPath);
|
|
129
|
+
await fs.mkdir(dir, { recursive: true });
|
|
130
|
+
const content = yaml.dump(config, { lineWidth: -1 });
|
|
131
|
+
await fs.writeFile(resolvedPath, content, 'utf8');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if wizard.yaml exists at path
|
|
136
|
+
* @async
|
|
137
|
+
* @function wizardConfigExists
|
|
138
|
+
* @param {string} configPath - Path to wizard.yaml file
|
|
139
|
+
* @returns {Promise<boolean>}
|
|
140
|
+
*/
|
|
141
|
+
async function wizardConfigExists(configPath) {
|
|
142
|
+
try {
|
|
143
|
+
await fs.access(path.resolve(configPath));
|
|
144
|
+
return true;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return error.code === 'ENOENT' ? false : Promise.reject(error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
117
150
|
/**
|
|
118
151
|
* Validate wizard configuration against schema
|
|
119
152
|
* @function validateWizardConfigSchema
|
|
@@ -258,6 +291,8 @@ function displayValidationResults(result) {
|
|
|
258
291
|
|
|
259
292
|
module.exports = {
|
|
260
293
|
loadWizardConfig,
|
|
294
|
+
writeWizardConfig,
|
|
295
|
+
wizardConfigExists,
|
|
261
296
|
validateWizardConfig,
|
|
262
297
|
validateWizardConfigSchema,
|
|
263
298
|
resolveEnvVar,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aifabrix/builder",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.36.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:coverage": "
|
|
13
|
+
"test:coverage": "cross-env RUN_COVERAGE=1 node tests/scripts/test-wrapper.js",
|
|
14
14
|
"test:coverage:nyc": "nyc --reporter=text --reporter=lcov --reporter=html jest --config jest.config.coverage.js --runInBand",
|
|
15
15
|
"test:watch": "jest --watch",
|
|
16
16
|
"test:integration": "jest --config jest.config.integration.js --runInBand",
|