@aifabrix/builder 2.32.2 → 2.32.3
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/lib/api/index.js +10 -5
- package/lib/api/wizard.api.js +52 -21
- package/lib/app/list.js +62 -28
- package/lib/app/prompts.js +9 -5
- package/lib/app/rotate-secret.js +2 -1
- package/lib/cli.js +33 -4
- package/lib/commands/auth-status.js +262 -0
- package/lib/commands/login-device.js +17 -12
- package/lib/commands/login.js +16 -9
- package/lib/commands/wizard.js +63 -69
- package/lib/datasource/deploy.js +5 -2
- package/lib/external-system/deploy.js +3 -2
- package/lib/external-system/download.js +2 -1
- package/lib/external-system/test-auth.js +2 -1
- package/lib/schema/application-schema.json +1 -1
- package/lib/schema/external-datasource.schema.json +301 -15
- package/lib/schema/external-system.schema.json +1 -1
- package/lib/utils/api.js +20 -7
- package/lib/utils/app-register-display.js +2 -1
- package/lib/utils/cli-utils.js +3 -1
- package/lib/utils/controller-url.js +67 -0
- package/lib/utils/env-map.js +2 -1
- package/lib/utils/error-formatter.js +100 -28
- package/lib/utils/token-manager.js +60 -0
- package/lib/validation/validator.js +2 -1
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +2 -2
- package/templates/external-system/external-system.json.hbs +1 -1
|
@@ -10,45 +10,116 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* @function formatSingleError
|
|
16
|
-
* @param {Object} error - Raw validation error from Ajv
|
|
17
|
-
* @returns {string} Formatted error message
|
|
13
|
+
* Maps common regex patterns to human-readable descriptions
|
|
14
|
+
* @type {Object.<string, string>}
|
|
18
15
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const PATTERN_DESCRIPTIONS = {
|
|
17
|
+
'^[a-z0-9-]+$': 'lowercase letters, numbers, and hyphens only',
|
|
18
|
+
'^[a-z0-9-:]+$': 'lowercase letters, numbers, hyphens, and colons only (e.g., "entity:action")',
|
|
19
|
+
'^[a-z-]+$': 'lowercase letters and hyphens only',
|
|
20
|
+
'^[A-Z_][A-Z0-9_]*$': 'uppercase letters, numbers, and underscores (must start with letter or underscore)',
|
|
21
|
+
'^[a-zA-Z0-9_-]+$': 'letters, numbers, hyphens, and underscores only',
|
|
22
|
+
'^(http|https)://.*$': 'valid HTTP or HTTPS URL',
|
|
23
|
+
'^/[a-z0-9/-]*$': 'URL path starting with / (lowercase letters, numbers, hyphens, slashes)'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Gets a human-readable description of a regex pattern
|
|
28
|
+
* @function getPatternDescription
|
|
29
|
+
* @param {string} pattern - The regex pattern
|
|
30
|
+
* @returns {string} Human-readable description
|
|
31
|
+
*/
|
|
32
|
+
function getPatternDescription(pattern) {
|
|
33
|
+
return PATTERN_DESCRIPTIONS[pattern] || `must match pattern: ${pattern}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extracts the field name from an error's instancePath
|
|
38
|
+
* @function getFieldName
|
|
39
|
+
* @param {Object} error - Validation error object
|
|
40
|
+
* @returns {string} Formatted field name
|
|
41
|
+
*/
|
|
42
|
+
function getFieldName(error) {
|
|
21
43
|
const instancePath = error.instancePath || '';
|
|
22
44
|
const path = instancePath ? instancePath.slice(1) : '';
|
|
23
|
-
|
|
45
|
+
return path ? `Field "${path}"` : 'Configuration';
|
|
46
|
+
}
|
|
24
47
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Formats a pattern validation error with the actual invalid value
|
|
50
|
+
* @function formatPatternError
|
|
51
|
+
* @param {string} field - Field name
|
|
52
|
+
* @param {Object} error - Validation error object
|
|
53
|
+
* @returns {string} Formatted error message
|
|
54
|
+
*/
|
|
55
|
+
function formatPatternError(field, error) {
|
|
56
|
+
const invalidValue = error.data !== undefined ? `"${error.data}"` : 'value';
|
|
57
|
+
const patternDesc = getPatternDescription(error.params?.pattern);
|
|
58
|
+
return `${field}: Invalid value ${invalidValue} - ${patternDesc}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Creates error message formatters for each validation keyword
|
|
63
|
+
* @function createKeywordFormatters
|
|
64
|
+
* @param {string} field - Field name
|
|
65
|
+
* @param {Object} error - Validation error object
|
|
66
|
+
* @returns {Object} Object mapping keywords to formatted messages
|
|
67
|
+
*/
|
|
68
|
+
function createKeywordFormatters(field, error) {
|
|
69
|
+
const params = error.params || {};
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
required: params.missingProperty
|
|
73
|
+
? `${field}: Missing required property "${params.missingProperty}"`
|
|
29
74
|
: `${field}: Missing required property`,
|
|
30
|
-
|
|
31
|
-
|
|
75
|
+
|
|
76
|
+
type: params.type
|
|
77
|
+
? `${field}: Expected ${params.type}, got ${typeof error.data}`
|
|
32
78
|
: `${field}: Type error`,
|
|
33
|
-
|
|
34
|
-
|
|
79
|
+
|
|
80
|
+
minimum: params.limit !== undefined
|
|
81
|
+
? `${field}: Value must be at least ${params.limit}`
|
|
35
82
|
: `${field}: Value below minimum`,
|
|
36
|
-
|
|
37
|
-
|
|
83
|
+
|
|
84
|
+
maximum: params.limit !== undefined
|
|
85
|
+
? `${field}: Value must be at most ${params.limit}`
|
|
38
86
|
: `${field}: Value above maximum`,
|
|
39
|
-
|
|
40
|
-
|
|
87
|
+
|
|
88
|
+
minLength: params.limit !== undefined
|
|
89
|
+
? `${field}: Must be at least ${params.limit} characters`
|
|
41
90
|
: `${field}: Too short`,
|
|
42
|
-
|
|
43
|
-
|
|
91
|
+
|
|
92
|
+
maxLength: params.limit !== undefined
|
|
93
|
+
? `${field}: Must be at most ${params.limit} characters`
|
|
44
94
|
: `${field}: Too long`,
|
|
45
|
-
|
|
46
|
-
enum:
|
|
47
|
-
? `${field}: Must be one of: ${
|
|
95
|
+
|
|
96
|
+
enum: params.allowedValues && params.allowedValues.length > 0
|
|
97
|
+
? `${field}: Must be one of: ${params.allowedValues.join(', ')}`
|
|
48
98
|
: `${field}: Must be one of: unknown`
|
|
49
99
|
};
|
|
100
|
+
}
|
|
50
101
|
|
|
51
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Formats a single validation error into a developer-friendly message
|
|
104
|
+
*
|
|
105
|
+
* @function formatSingleError
|
|
106
|
+
* @param {Object} error - Raw validation error from Ajv
|
|
107
|
+
* @returns {string} Formatted error message
|
|
108
|
+
*/
|
|
109
|
+
function formatSingleError(error) {
|
|
110
|
+
const field = getFieldName(error);
|
|
111
|
+
|
|
112
|
+
// Handle pattern errors with special formatting
|
|
113
|
+
if (error.keyword === 'pattern') {
|
|
114
|
+
return formatPatternError(field, error);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Use object lookup for keyword-specific messages
|
|
118
|
+
const formatters = createKeywordFormatters(field, error);
|
|
119
|
+
const message = formatters[error.keyword];
|
|
120
|
+
|
|
121
|
+
// Return keyword message or fallback to generic message
|
|
122
|
+
return message || `${field}: ${error.message || 'Validation error'}`;
|
|
52
123
|
}
|
|
53
124
|
|
|
54
125
|
/**
|
|
@@ -73,6 +144,7 @@ function formatValidationErrors(errors) {
|
|
|
73
144
|
|
|
74
145
|
module.exports = {
|
|
75
146
|
formatSingleError,
|
|
76
|
-
formatValidationErrors
|
|
147
|
+
formatValidationErrors,
|
|
148
|
+
getPatternDescription,
|
|
149
|
+
PATTERN_DESCRIPTIONS
|
|
77
150
|
};
|
|
78
|
-
|
|
@@ -357,6 +357,64 @@ async function extractClientCredentials(authConfig, appKey, envKey, _options = {
|
|
|
357
357
|
throw new Error('Invalid authentication type');
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
/**
|
|
361
|
+
* Force refresh device token for controller (regardless of local expiry time)
|
|
362
|
+
* Used when server returns 401 even though local token hasn't expired
|
|
363
|
+
* @param {string} controllerUrl - Controller URL
|
|
364
|
+
* @returns {Promise<{token: string, controller: string}|null>} Token and controller URL, or null if not available
|
|
365
|
+
*/
|
|
366
|
+
async function forceRefreshDeviceToken(controllerUrl) {
|
|
367
|
+
// Try to get existing token to get refresh token
|
|
368
|
+
const tokenInfo = await getDeviceToken(controllerUrl);
|
|
369
|
+
|
|
370
|
+
if (!tokenInfo) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Must have refresh token to force refresh
|
|
375
|
+
if (!tokenInfo.refreshToken) {
|
|
376
|
+
logger.warn('Cannot refresh: no refresh token available. Please login again using: aifabrix login');
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
const refreshed = await refreshDeviceToken(controllerUrl, tokenInfo.refreshToken);
|
|
382
|
+
return {
|
|
383
|
+
token: refreshed.token,
|
|
384
|
+
controller: controllerUrl
|
|
385
|
+
};
|
|
386
|
+
} catch (error) {
|
|
387
|
+
const errorMessage = error.message || String(error);
|
|
388
|
+
if (errorMessage.includes('Refresh token has expired')) {
|
|
389
|
+
logger.warn(`Refresh token expired: ${errorMessage}`);
|
|
390
|
+
} else {
|
|
391
|
+
logger.warn(`Failed to refresh device token: ${errorMessage}`);
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Get device-only authentication for services that require user-level authentication.
|
|
399
|
+
* Used for interactive commands like wizard that don't support client credentials.
|
|
400
|
+
* @async
|
|
401
|
+
* @function getDeviceOnlyAuth
|
|
402
|
+
* @param {string} controllerUrl - Controller URL
|
|
403
|
+
* @returns {Promise<Object>} Auth config with device token
|
|
404
|
+
* @throws {Error} If device token is not available
|
|
405
|
+
*/
|
|
406
|
+
async function getDeviceOnlyAuth(controllerUrl) {
|
|
407
|
+
const deviceToken = await getOrRefreshDeviceToken(controllerUrl);
|
|
408
|
+
if (deviceToken && deviceToken.token) {
|
|
409
|
+
return {
|
|
410
|
+
type: 'bearer',
|
|
411
|
+
token: deviceToken.token,
|
|
412
|
+
controller: deviceToken.controller
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
throw new Error('Device token authentication required. Run "aifabrix login" to authenticate.');
|
|
416
|
+
}
|
|
417
|
+
|
|
360
418
|
module.exports = {
|
|
361
419
|
getDeviceToken,
|
|
362
420
|
getClientToken,
|
|
@@ -367,7 +425,9 @@ module.exports = {
|
|
|
367
425
|
loadClientCredentials,
|
|
368
426
|
getOrRefreshClientToken,
|
|
369
427
|
getOrRefreshDeviceToken,
|
|
428
|
+
forceRefreshDeviceToken,
|
|
370
429
|
getDeploymentAuth,
|
|
430
|
+
getDeviceOnlyAuth,
|
|
371
431
|
extractClientCredentials
|
|
372
432
|
};
|
|
373
433
|
|
|
@@ -296,7 +296,8 @@ function validateDeploymentJson(deployment) {
|
|
|
296
296
|
};
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
-
|
|
299
|
+
// verbose: true includes the actual data value in error objects for better error messages
|
|
300
|
+
const ajv = new Ajv({ allErrors: true, strict: false, verbose: true });
|
|
300
301
|
// Register external schemas with their $id (GitHub raw URLs)
|
|
301
302
|
// Create copies to avoid modifying the original schemas
|
|
302
303
|
const externalSystemSchemaCopy = { ...externalSystemSchema };
|
package/package.json
CHANGED
|
@@ -18,8 +18,8 @@ npm install -g @aifabrix/builder
|
|
|
18
18
|
# Check your environment
|
|
19
19
|
aifabrix doctor
|
|
20
20
|
|
|
21
|
-
# Login to controller
|
|
22
|
-
aifabrix login --method device --environment dev --
|
|
21
|
+
# Login to controller (offline tokens are default)
|
|
22
|
+
aifabrix login --method device --environment dev --controller http://localhost:3000
|
|
23
23
|
|
|
24
24
|
# Register your application (gets you credentials automatically)
|
|
25
25
|
aifabrix app register {{appName}} --environment dev
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"name": "{{name}}",
|
|
40
40
|
"value": "{{value}}",
|
|
41
41
|
"description": "{{description}}"{{#if Groups}},
|
|
42
|
-
"
|
|
42
|
+
"groups": [{{#each Groups}}"{{this}}"{{#unless @last}}, {{/unless}}{{/each}}]{{/if}}
|
|
43
43
|
}{{#unless @last}},{{/unless}}
|
|
44
44
|
{{/each}}
|
|
45
45
|
]{{/if}}{{#if permissions}},
|