@aifabrix/builder 2.10.1 → 2.20.0

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.
Files changed (47) hide show
  1. package/.cursor/rules/project-rules.mdc +194 -0
  2. package/README.md +12 -0
  3. package/integration/hubspot/README.md +2 -2
  4. package/integration/hubspot/hubspot-deploy.json +12 -4
  5. package/lib/api/applications.api.js +164 -0
  6. package/lib/api/auth.api.js +304 -0
  7. package/lib/api/datasources-core.api.js +87 -0
  8. package/lib/api/datasources-extended.api.js +117 -0
  9. package/lib/api/datasources.api.js +13 -0
  10. package/lib/api/deployments.api.js +126 -0
  11. package/lib/api/environments.api.js +245 -0
  12. package/lib/api/external-systems.api.js +251 -0
  13. package/lib/api/index.js +221 -0
  14. package/lib/api/pipeline.api.js +234 -0
  15. package/lib/api/types/applications.types.js +136 -0
  16. package/lib/api/types/auth.types.js +218 -0
  17. package/lib/api/types/datasources.types.js +272 -0
  18. package/lib/api/types/deployments.types.js +184 -0
  19. package/lib/api/types/environments.types.js +197 -0
  20. package/lib/api/types/external-systems.types.js +244 -0
  21. package/lib/api/types/pipeline.types.js +125 -0
  22. package/lib/app-list.js +5 -7
  23. package/lib/app-register.js +70 -403
  24. package/lib/app-rotate-secret.js +4 -10
  25. package/lib/commands/login.js +19 -12
  26. package/lib/datasource-deploy.js +7 -30
  27. package/lib/datasource-list.js +9 -6
  28. package/lib/deployer.js +103 -135
  29. package/lib/environment-deploy.js +15 -26
  30. package/lib/external-system-deploy.js +12 -39
  31. package/lib/external-system-download.js +5 -13
  32. package/lib/external-system-test.js +9 -12
  33. package/lib/utils/api-error-handler.js +11 -453
  34. package/lib/utils/app-register-api.js +66 -0
  35. package/lib/utils/app-register-auth.js +72 -0
  36. package/lib/utils/app-register-config.js +205 -0
  37. package/lib/utils/app-register-display.js +69 -0
  38. package/lib/utils/app-register-validator.js +143 -0
  39. package/lib/utils/deployment-errors.js +88 -6
  40. package/lib/utils/device-code.js +1 -1
  41. package/lib/utils/error-formatters/error-parser.js +150 -0
  42. package/lib/utils/error-formatters/http-status-errors.js +189 -0
  43. package/lib/utils/error-formatters/network-errors.js +46 -0
  44. package/lib/utils/error-formatters/permission-errors.js +94 -0
  45. package/lib/utils/error-formatters/validation-errors.js +133 -0
  46. package/package.json +1 -1
  47. package/templates/applications/README.md.hbs +1 -1
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Error Parser Utilities
3
+ *
4
+ * Parses error responses and determines error types
5
+ *
6
+ * @fileoverview Error parsing utilities
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ const { formatPermissionError } = require('./permission-errors');
12
+ const { formatValidationError } = require('./validation-errors');
13
+ const {
14
+ formatAuthenticationError,
15
+ formatServerError,
16
+ formatConflictError,
17
+ formatNotFoundError,
18
+ formatGenericError
19
+ } = require('./http-status-errors');
20
+ const { formatNetworkError } = require('./network-errors');
21
+
22
+ /**
23
+ * Parses error response into error data object
24
+ * @param {string|Object} errorResponse - Error response (string or parsed JSON)
25
+ * @returns {Object} Parsed error data object
26
+ */
27
+ function parseErrorData(errorResponse) {
28
+ if (errorResponse === undefined || errorResponse === null) {
29
+ return { message: 'Unknown error occurred' };
30
+ }
31
+ if (typeof errorResponse === 'string') {
32
+ try {
33
+ return JSON.parse(errorResponse);
34
+ } catch {
35
+ return { message: errorResponse };
36
+ }
37
+ }
38
+ return typeof errorResponse === 'object' ? errorResponse : { message: String(errorResponse) };
39
+ }
40
+
41
+ /**
42
+ * Creates error result object
43
+ * @param {string} type - Error type
44
+ * @param {string} message - Error message
45
+ * @param {string} formatted - Formatted error message
46
+ * @param {Object} data - Error data
47
+ * @returns {Object} Error result object
48
+ */
49
+ function createErrorResult(type, message, formatted, data) {
50
+ return { type, message, formatted, data };
51
+ }
52
+
53
+ /**
54
+ * Gets error message from error data
55
+ * @param {Object} errorData - Error data object
56
+ * @param {string} defaultMessage - Default message if not found
57
+ * @returns {string} Error message
58
+ */
59
+ function getErrorMessage(errorData, defaultMessage) {
60
+ return errorData.detail || errorData.title || errorData.errorDescription ||
61
+ errorData.message || errorData.error || defaultMessage;
62
+ }
63
+
64
+ /**
65
+ * Handles 400 validation error
66
+ * @param {Object} errorData - Error data object
67
+ * @returns {Object} Error result object
68
+ */
69
+ function handleValidationError(errorData) {
70
+ const errorMessage = getErrorMessage(errorData, 'Validation error');
71
+ return createErrorResult('validation', errorMessage, formatValidationError(errorData), errorData);
72
+ }
73
+
74
+ /**
75
+ * Handles specific 4xx client error codes
76
+ * @param {number} statusCode - HTTP status code
77
+ * @param {Object} errorData - Error data object
78
+ * @returns {Object|null} Error result object or null if not handled
79
+ */
80
+ function handleSpecificClientErrors(statusCode, errorData) {
81
+ switch (statusCode) {
82
+ case 403:
83
+ return createErrorResult('permission', 'Permission denied', formatPermissionError(errorData), errorData);
84
+ case 401:
85
+ return createErrorResult('authentication', 'Authentication failed', formatAuthenticationError(errorData), errorData);
86
+ case 400:
87
+ case 422:
88
+ return handleValidationError(errorData);
89
+ case 404:
90
+ return createErrorResult('notfound', getErrorMessage(errorData, 'Not found'), formatNotFoundError(errorData), errorData);
91
+ case 409:
92
+ return createErrorResult('conflict', getErrorMessage(errorData, 'Conflict'), formatConflictError(errorData), errorData);
93
+ default:
94
+ return null;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Handles HTTP status code errors
100
+ * @param {number} statusCode - HTTP status code
101
+ * @param {Object} errorData - Error data object
102
+ * @returns {Object|null} Error result object or null if not handled
103
+ */
104
+ function handleStatusCodeError(statusCode, errorData) {
105
+ // Handle 4xx client errors
106
+ if (statusCode >= 400 && statusCode < 500) {
107
+ return handleSpecificClientErrors(statusCode, errorData);
108
+ }
109
+ // Handle 5xx server errors
110
+ if (statusCode >= 500) {
111
+ return createErrorResult('server', 'Server error', formatServerError(errorData), errorData);
112
+ }
113
+ return null;
114
+ }
115
+
116
+ /**
117
+ * Parses error response and determines error type
118
+ * @param {string|Object} errorResponse - Error response (string or parsed JSON)
119
+ * @param {number} statusCode - HTTP status code
120
+ * @param {boolean} isNetworkError - Whether this is a network error
121
+ * @returns {Object} Parsed error object with type, message, and formatted output
122
+ */
123
+ function parseErrorResponse(errorResponse, statusCode, isNetworkError) {
124
+ let errorData = parseErrorData(errorResponse);
125
+
126
+ // Handle nested response structure (some APIs wrap errors in data field)
127
+ if (errorData.data && typeof errorData.data === 'object') {
128
+ errorData = errorData.data;
129
+ }
130
+
131
+ // Handle network errors
132
+ if (isNetworkError) {
133
+ const errorMessage = errorData.message || errorResponse || 'Network error';
134
+ return createErrorResult('network', errorMessage, formatNetworkError(errorMessage, errorData), errorData);
135
+ }
136
+
137
+ // Handle HTTP status codes
138
+ const statusError = handleStatusCodeError(statusCode, errorData);
139
+ if (statusError) {
140
+ return statusError;
141
+ }
142
+
143
+ // Generic error
144
+ return createErrorResult('generic', errorData.message || errorData.error || 'Unknown error', formatGenericError(errorData, statusCode), errorData);
145
+ }
146
+
147
+ module.exports = {
148
+ parseErrorResponse
149
+ };
150
+
@@ -0,0 +1,189 @@
1
+ /**
2
+ * HTTP Status Error Formatters
3
+ *
4
+ * Formats HTTP status code errors (401, 404, 409, 500+)
5
+ *
6
+ * @fileoverview HTTP status error formatting utilities
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ const chalk = require('chalk');
12
+
13
+ /**
14
+ * Formats authentication error
15
+ * @param {Object} errorData - Error response data
16
+ * @returns {string} Formatted authentication error message
17
+ */
18
+ function formatAuthenticationError(errorData) {
19
+ const lines = [];
20
+ lines.push(chalk.red('❌ Authentication Failed\n'));
21
+ if (errorData.message) {
22
+ lines.push(chalk.yellow(errorData.message));
23
+ } else {
24
+ lines.push(chalk.yellow('Invalid credentials or token expired.'));
25
+ }
26
+ lines.push('');
27
+ lines.push(chalk.gray('Run: aifabrix login'));
28
+ if (errorData.correlationId) {
29
+ lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
30
+ }
31
+ return lines.join('\n');
32
+ }
33
+
34
+ /**
35
+ * Formats server error (500+)
36
+ * @param {Object} errorData - Error response data
37
+ * @returns {string} Formatted server error message
38
+ */
39
+ function formatServerError(errorData) {
40
+ const lines = [];
41
+ lines.push(chalk.red('❌ Server Error\n'));
42
+
43
+ // Check for RFC 7807 Problem Details format (detail field)
44
+ if (errorData.detail) {
45
+ lines.push(chalk.yellow(errorData.detail));
46
+ } else if (errorData.message) {
47
+ lines.push(chalk.yellow(errorData.message));
48
+ } else {
49
+ lines.push(chalk.yellow('An internal server error occurred.'));
50
+ }
51
+ lines.push('');
52
+ lines.push(chalk.gray('Please try again later or contact support.'));
53
+
54
+ if (errorData.correlationId) {
55
+ lines.push('');
56
+ lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
57
+ }
58
+
59
+ return lines.join('\n');
60
+ }
61
+
62
+ /**
63
+ * Formats conflict error (409)
64
+ * @param {Object} errorData - Error response data
65
+ * @returns {string} Formatted conflict error message
66
+ */
67
+ function formatConflictError(errorData) {
68
+ const lines = [];
69
+ lines.push(chalk.red('❌ Conflict\n'));
70
+
71
+ // Check if it's an application already exists error
72
+ const detail = errorData.detail || errorData.message || '';
73
+ if (detail.toLowerCase().includes('application already exists')) {
74
+ lines.push(chalk.yellow('This application already exists in this environment.'));
75
+ lines.push('');
76
+ lines.push(chalk.gray('Options:'));
77
+ lines.push(chalk.gray(' • Use a different environment'));
78
+ lines.push(chalk.gray(' • Check existing applications: aifabrix app list -e <environment>'));
79
+ lines.push(chalk.gray(' • Update the existing application if needed'));
80
+ } else if (detail) {
81
+ lines.push(chalk.yellow(detail));
82
+ } else if (errorData.message) {
83
+ lines.push(chalk.yellow(errorData.message));
84
+ } else {
85
+ lines.push(chalk.yellow('A conflict occurred. The resource may already exist.'));
86
+ }
87
+
88
+ if (errorData.correlationId) {
89
+ lines.push('');
90
+ lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
91
+ }
92
+
93
+ return lines.join('\n');
94
+ }
95
+
96
+ /**
97
+ * Gets actionable guidance options based on error detail
98
+ * @param {string} detail - Error detail message
99
+ * @returns {Array<string>} Array of guidance options
100
+ */
101
+ function getNotFoundGuidance(detail) {
102
+ const lowerDetail = detail.toLowerCase();
103
+ if (lowerDetail.includes('environment')) {
104
+ return [
105
+ ' • Check the environment key spelling',
106
+ ' • List available environments: aifabrix app list -e <environment>',
107
+ ' • Verify you have access to this environment'
108
+ ];
109
+ }
110
+ if (lowerDetail.includes('application')) {
111
+ return [
112
+ ' • Check the application key spelling',
113
+ ' • List applications: aifabrix app list -e <environment>',
114
+ ' • Verify the application exists in this environment'
115
+ ];
116
+ }
117
+ return [
118
+ ' • Check the resource identifier',
119
+ ' • Verify the resource exists',
120
+ ' • Check your permissions'
121
+ ];
122
+ }
123
+
124
+ /**
125
+ * Formats not found error (404)
126
+ * @param {Object} errorData - Error response data
127
+ * @returns {string} Formatted not found error message
128
+ */
129
+ function formatNotFoundError(errorData) {
130
+ const lines = [];
131
+ lines.push(chalk.red('❌ Not Found\n'));
132
+
133
+ const detail = errorData.detail || errorData.message || '';
134
+ if (detail) {
135
+ lines.push(chalk.yellow(detail));
136
+ lines.push('');
137
+ }
138
+
139
+ lines.push(chalk.gray('Options:'));
140
+ const guidance = getNotFoundGuidance(detail);
141
+ guidance.forEach(option => {
142
+ lines.push(chalk.gray(option));
143
+ });
144
+
145
+ if (errorData.correlationId) {
146
+ lines.push('');
147
+ lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
148
+ }
149
+
150
+ return lines.join('\n');
151
+ }
152
+
153
+ /**
154
+ * Formats generic error
155
+ * @param {Object} errorData - Error response data
156
+ * @param {number} statusCode - HTTP status code
157
+ * @returns {string} Formatted error message
158
+ */
159
+ function formatGenericError(errorData, statusCode) {
160
+ const lines = [];
161
+ lines.push(chalk.red(`❌ Error (HTTP ${statusCode})\n`));
162
+
163
+ // Check for RFC 7807 Problem Details format (detail field)
164
+ if (errorData.detail) {
165
+ lines.push(chalk.yellow(errorData.detail));
166
+ } else if (errorData.message) {
167
+ lines.push(chalk.yellow(errorData.message));
168
+ } else if (errorData.error) {
169
+ lines.push(chalk.yellow(errorData.error));
170
+ } else {
171
+ lines.push(chalk.yellow('An error occurred while processing your request.'));
172
+ }
173
+
174
+ if (errorData.correlationId) {
175
+ lines.push('');
176
+ lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
177
+ }
178
+
179
+ return lines.join('\n');
180
+ }
181
+
182
+ module.exports = {
183
+ formatAuthenticationError,
184
+ formatServerError,
185
+ formatConflictError,
186
+ formatNotFoundError,
187
+ formatGenericError
188
+ };
189
+
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Network Error Formatters
3
+ *
4
+ * Formats network-related errors (connection refused, timeouts, etc.)
5
+ *
6
+ * @fileoverview Network error formatting utilities
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ const chalk = require('chalk');
12
+
13
+ /**
14
+ * Formats network error
15
+ * @param {string} errorMessage - Error message
16
+ * @param {Object} errorData - Error response data (optional)
17
+ * @returns {string} Formatted network error message
18
+ */
19
+ function formatNetworkError(errorMessage, errorData) {
20
+ const lines = [];
21
+ lines.push(chalk.red('❌ Network Error\n'));
22
+
23
+ if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('Cannot connect')) {
24
+ lines.push(chalk.yellow('Cannot connect to controller.'));
25
+ lines.push(chalk.gray('Check if the controller is running.'));
26
+ } else if (errorMessage.includes('ENOTFOUND') || errorMessage.includes('hostname not found')) {
27
+ lines.push(chalk.yellow('Controller hostname not found.'));
28
+ lines.push(chalk.gray('Check your controller URL.'));
29
+ } else if (errorMessage.includes('timeout') || errorMessage.includes('timed out')) {
30
+ lines.push(chalk.yellow('Request timed out.'));
31
+ lines.push(chalk.gray('The controller may be overloaded.'));
32
+ } else {
33
+ lines.push(chalk.yellow(errorMessage));
34
+ }
35
+
36
+ if (errorData && errorData.correlationId) {
37
+ lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
38
+ }
39
+
40
+ return lines.join('\n');
41
+ }
42
+
43
+ module.exports = {
44
+ formatNetworkError
45
+ };
46
+
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Permission Error Formatters
3
+ *
4
+ * Formats permission-related errors (403) with missing and required permissions
5
+ *
6
+ * @fileoverview Permission error formatting utilities
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ const chalk = require('chalk');
12
+
13
+ /**
14
+ * Extracts missing permissions from error data
15
+ * @param {Object} errorData - Error response data
16
+ * @returns {Array<string>} Array of missing permissions
17
+ */
18
+ function extractMissingPermissions(errorData) {
19
+ if (errorData.missingPermissions && Array.isArray(errorData.missingPermissions)) {
20
+ return errorData.missingPermissions;
21
+ }
22
+ if (errorData.missing && errorData.missing.permissions && Array.isArray(errorData.missing.permissions)) {
23
+ return errorData.missing.permissions;
24
+ }
25
+ return [];
26
+ }
27
+
28
+ /**
29
+ * Extracts required permissions from error data
30
+ * @param {Object} errorData - Error response data
31
+ * @returns {Array<string>} Array of required permissions
32
+ */
33
+ function extractRequiredPermissions(errorData) {
34
+ if (errorData.requiredPermissions && Array.isArray(errorData.requiredPermissions)) {
35
+ return errorData.requiredPermissions;
36
+ }
37
+ if (errorData.required && errorData.required.permissions && Array.isArray(errorData.required.permissions)) {
38
+ return errorData.required.permissions;
39
+ }
40
+ return [];
41
+ }
42
+
43
+ /**
44
+ * Adds permission list to lines array
45
+ * @param {Array<string>} lines - Lines array to append to
46
+ * @param {Array<string>} perms - Permissions array
47
+ * @param {string} label - Label for the permissions list
48
+ */
49
+ function addPermissionList(lines, perms, label) {
50
+ if (perms.length > 0) {
51
+ lines.push(chalk.yellow(`${label}:`));
52
+ perms.forEach(perm => {
53
+ lines.push(chalk.gray(` - ${perm}`));
54
+ });
55
+ lines.push('');
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Formats permission error with missing and required permissions
61
+ * @param {Object} errorData - Error response data
62
+ * @returns {string} Formatted permission error message
63
+ */
64
+ function formatPermissionError(errorData) {
65
+ const lines = [];
66
+ lines.push(chalk.red('❌ Permission Denied\n'));
67
+
68
+ if (errorData.detail) {
69
+ lines.push(chalk.yellow(errorData.detail));
70
+ lines.push('');
71
+ }
72
+
73
+ const missingPerms = extractMissingPermissions(errorData);
74
+ addPermissionList(lines, missingPerms, 'Missing permissions');
75
+
76
+ const requiredPerms = extractRequiredPermissions(errorData);
77
+ addPermissionList(lines, requiredPerms, 'Required permissions');
78
+
79
+ const requestUrl = errorData.instance || errorData.url;
80
+ const method = errorData.method || 'POST';
81
+ if (requestUrl) {
82
+ lines.push(chalk.gray(`Request: ${method} ${requestUrl}`));
83
+ }
84
+ if (errorData.correlationId) {
85
+ lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
86
+ }
87
+
88
+ return lines.join('\n');
89
+ }
90
+
91
+ module.exports = {
92
+ formatPermissionError
93
+ };
94
+
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Validation Error Formatters
3
+ *
4
+ * Formats validation errors (400, 422) with field-level details
5
+ *
6
+ * @fileoverview Validation error formatting utilities
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ const chalk = require('chalk');
12
+
13
+ /**
14
+ * Adds main error message to lines array
15
+ * @param {Array<string>} lines - Lines array to append to
16
+ * @param {Object} errorData - Error response data
17
+ */
18
+ function addValidationErrorMessage(lines, errorData) {
19
+ // Handle RFC 7807 Problem Details format
20
+ // Priority: detail > title > errorDescription > message > error
21
+ if (errorData.detail) {
22
+ lines.push(chalk.yellow(errorData.detail));
23
+ lines.push('');
24
+ } else if (errorData.title) {
25
+ lines.push(chalk.yellow(errorData.title));
26
+ lines.push('');
27
+ } else if (errorData.errorDescription) {
28
+ lines.push(chalk.yellow(errorData.errorDescription));
29
+ if (errorData.error) {
30
+ lines.push(chalk.gray(`Error code: ${errorData.error}`));
31
+ }
32
+ lines.push('');
33
+ } else if (errorData.message) {
34
+ lines.push(chalk.yellow(errorData.message));
35
+ lines.push('');
36
+ } else if (errorData.error) {
37
+ lines.push(chalk.yellow(errorData.error));
38
+ lines.push('');
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Formats errors array and adds to lines
44
+ * @param {Array<string>} lines - Lines array to append to
45
+ * @param {Array<Object>} errors - Array of error objects
46
+ */
47
+ function addValidationErrorsList(lines, errors) {
48
+ if (!errors || !Array.isArray(errors) || errors.length === 0) {
49
+ return;
50
+ }
51
+ lines.push(chalk.yellow('Validation errors:'));
52
+ errors.forEach(err => {
53
+ const field = err.field || err.path || err.loc?.join('.') || 'validation';
54
+ const message = err.msg || err.message || 'Invalid value';
55
+ const value = err.value !== undefined ? ` (value: ${JSON.stringify(err.value)})` : '';
56
+ if (field === 'validation' || field === 'unknown') {
57
+ lines.push(chalk.gray(` • ${message}${value}`));
58
+ } else {
59
+ lines.push(chalk.gray(` • ${field}: ${message}${value}`));
60
+ }
61
+ });
62
+ lines.push('');
63
+ }
64
+
65
+ /**
66
+ * Formats configuration errors and adds to lines
67
+ * @param {Array<string>} lines - Lines array to append to
68
+ * @param {Object} configuration - Configuration error object
69
+ */
70
+ function addConfigurationErrors(lines, configuration) {
71
+ if (!configuration || !configuration.errors) {
72
+ return;
73
+ }
74
+ lines.push(chalk.yellow('Configuration errors:'));
75
+ if (Array.isArray(configuration.errors)) {
76
+ configuration.errors.forEach(err => {
77
+ const field = err.field || err.path || 'configuration';
78
+ const message = err.message || 'Invalid value';
79
+ lines.push(chalk.gray(` • ${field}: ${message}`));
80
+ });
81
+ } else if (typeof configuration.errors === 'object') {
82
+ Object.keys(configuration.errors).forEach(key => {
83
+ const message = configuration.errors[key];
84
+ lines.push(chalk.gray(` • configuration.${key}: ${message}`));
85
+ });
86
+ }
87
+ lines.push('');
88
+ }
89
+
90
+ /**
91
+ * Adds actionable guidance tips to lines
92
+ * @param {Array<string>} lines - Lines array to append to
93
+ * @param {boolean} hasErrors - Whether there are validation errors
94
+ */
95
+ function addValidationGuidance(lines, hasErrors) {
96
+ if (!hasErrors) {
97
+ return;
98
+ }
99
+ lines.push(chalk.gray('Tips:'));
100
+ lines.push(chalk.gray(' • Check your variables.yaml file for the correct field values'));
101
+ lines.push(chalk.gray(' • Verify field names match the expected schema'));
102
+ lines.push(chalk.gray(' • Ensure required fields are present and valid'));
103
+ lines.push('');
104
+ }
105
+
106
+ /**
107
+ * Formats validation error with field-level details
108
+ * Handles unified error API response format (RFC 7807 Problem Details)
109
+ * @param {Object} errorData - Error response data
110
+ * @returns {string} Formatted validation error message
111
+ */
112
+ function formatValidationError(errorData) {
113
+ const lines = [];
114
+ lines.push(chalk.red('❌ Validation Error\n'));
115
+
116
+ addValidationErrorMessage(lines, errorData);
117
+ addValidationErrorsList(lines, errorData.errors);
118
+ addConfigurationErrors(lines, errorData.configuration);
119
+ addValidationGuidance(lines, errorData.errors && errorData.errors.length > 0);
120
+
121
+ if (errorData.instance) {
122
+ lines.push(chalk.gray(`Endpoint: ${errorData.instance}`));
123
+ }
124
+ if (errorData.correlationId) {
125
+ lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
126
+ }
127
+ return lines.join('\n');
128
+ }
129
+
130
+ module.exports = {
131
+ formatValidationError
132
+ };
133
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.10.1",
3
+ "version": "2.20.0",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -19,7 +19,7 @@ npm install -g @aifabrix/builder
19
19
  aifabrix doctor
20
20
 
21
21
  # Login to controller
22
- aifabrix login --method device --environment dev
22
+ aifabrix login --method device --environment dev --offline --controller http://localhost:3000
23
23
 
24
24
  # Register your application (gets you credentials automatically)
25
25
  aifabrix app register {{appName}} --environment dev