@aifabrix/builder 2.33.0 → 2.33.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/integration/hubspot/README.md +7 -7
- package/lib/api/index.js +6 -2
- package/lib/datasource/deploy.js +31 -3
- package/lib/datasource/list.js +102 -15
- package/lib/utils/api.js +70 -2
- package/lib/utils/error-formatters/network-errors.js +13 -3
- package/package.json +1 -1
- package/templates/github/ci.yaml.hbs +44 -1
- package/templates/github/release.yaml.hbs +44 -0
|
@@ -52,15 +52,15 @@ aifabrix validate hubspot
|
|
|
52
52
|
aifabrix login --controller http://localhost:3100 --method device --environment dev
|
|
53
53
|
|
|
54
54
|
# Register application
|
|
55
|
-
aifabrix app register hubspot
|
|
55
|
+
aifabrix app register hubspot
|
|
56
56
|
|
|
57
57
|
# Deploy entire system
|
|
58
|
-
aifabrix deploy hubspot
|
|
58
|
+
aifabrix deploy hubspot
|
|
59
59
|
|
|
60
60
|
# Or deploy individual datasources for testing
|
|
61
|
-
aifabrix datasource deploy hubspot-company --
|
|
62
|
-
aifabrix datasource deploy hubspot-contact --
|
|
63
|
-
aifabrix datasource deploy hubspot-deal --
|
|
61
|
+
aifabrix datasource deploy hubspot-company --file integration/hubspot/hubspot-datasource-company.json
|
|
62
|
+
aifabrix datasource deploy hubspot-contact --file integration/hubspot/hubspot-datasource-contact.json
|
|
63
|
+
aifabrix datasource deploy hubspot-deal --file integration/hubspot/hubspot-datasource-deal.json
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
## Field Mappings
|
|
@@ -122,10 +122,10 @@ RBAC permissions are auto-generated: `hubspot.company.list`, `hubspot.company.ge
|
|
|
122
122
|
|
|
123
123
|
```bash
|
|
124
124
|
# List datasources
|
|
125
|
-
aifabrix datasource list
|
|
125
|
+
aifabrix datasource list
|
|
126
126
|
|
|
127
127
|
# Validate specific datasource
|
|
128
|
-
aifabrix datasource validate hubspot-company
|
|
128
|
+
aifabrix datasource validate hubspot-company
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
## Documentation
|
package/lib/api/index.js
CHANGED
|
@@ -22,10 +22,14 @@ class ApiClient {
|
|
|
22
22
|
* @param {string} [authConfig.clientSecret] - Client secret
|
|
23
23
|
*/
|
|
24
24
|
constructor(baseUrl, authConfig = {}) {
|
|
25
|
-
if (
|
|
25
|
+
if (baseUrl === null || baseUrl === undefined || typeof baseUrl !== 'string') {
|
|
26
26
|
throw new Error('baseUrl is required and must be a string');
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
const trimmedUrl = baseUrl.trim().replace(/\/$/, ''); // Trim and remove trailing slash
|
|
29
|
+
if (!trimmedUrl) {
|
|
30
|
+
throw new Error('baseUrl cannot be empty. Please provide a valid URL.');
|
|
31
|
+
}
|
|
32
|
+
this.baseUrl = trimmedUrl;
|
|
29
33
|
this.authConfig = authConfig;
|
|
30
34
|
}
|
|
31
35
|
|
package/lib/datasource/deploy.js
CHANGED
|
@@ -118,10 +118,27 @@ async function setupDeploymentAuth(controllerUrl, environment, appKey) {
|
|
|
118
118
|
logger.log(chalk.green('✓ Authentication successful'));
|
|
119
119
|
|
|
120
120
|
logger.log(chalk.blue('🌐 Resolving dataplane URL...'));
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
let dataplaneUrl;
|
|
122
|
+
try {
|
|
123
|
+
dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
124
|
+
logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
|
|
125
|
+
} catch (error) {
|
|
126
|
+
logger.error(chalk.red('❌ Failed to resolve dataplane URL:'), error.message);
|
|
127
|
+
logger.error(chalk.gray('\nThe dataplane URL is automatically discovered from the controller.'));
|
|
128
|
+
logger.error(chalk.gray('If discovery fails, ensure you are logged in and the controller is accessible:'));
|
|
129
|
+
logger.error(chalk.gray(' aifabrix login'));
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Validate dataplane URL
|
|
134
|
+
if (!dataplaneUrl || !dataplaneUrl.trim()) {
|
|
135
|
+
logger.error(chalk.red('❌ Dataplane URL is empty.'));
|
|
136
|
+
logger.error(chalk.gray('The dataplane URL could not be discovered from the controller.'));
|
|
137
|
+
logger.error(chalk.gray('Ensure the dataplane service is registered in the controller.'));
|
|
138
|
+
throw new Error('Dataplane URL is empty');
|
|
139
|
+
}
|
|
123
140
|
|
|
124
|
-
return { authConfig, dataplaneUrl };
|
|
141
|
+
return { authConfig, dataplaneUrl: dataplaneUrl.trim() };
|
|
125
142
|
}
|
|
126
143
|
|
|
127
144
|
/**
|
|
@@ -143,6 +160,17 @@ async function publishDatasourceToDataplane(dataplaneUrl, systemKey, authConfig,
|
|
|
143
160
|
const formattedError = publishResponse.formattedError || formatApiError(publishResponse);
|
|
144
161
|
logger.error(chalk.red('❌ Publish failed:'));
|
|
145
162
|
logger.error(formattedError);
|
|
163
|
+
|
|
164
|
+
// Show dataplane URL and endpoint information
|
|
165
|
+
if (publishResponse.errorData && publishResponse.errorData.endpointUrl) {
|
|
166
|
+
logger.error(chalk.gray(`\nEndpoint URL: ${publishResponse.errorData.endpointUrl}`));
|
|
167
|
+
} else if (dataplaneUrl) {
|
|
168
|
+
logger.error(chalk.gray(`\nDataplane URL: ${dataplaneUrl}`));
|
|
169
|
+
logger.error(chalk.gray(`System Key: ${systemKey}`));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
logger.error(chalk.gray('\nFull response for debugging:'));
|
|
173
|
+
logger.error(chalk.gray(JSON.stringify(publishResponse, null, 2)));
|
|
146
174
|
throw new Error(`Dataplane publish failed: ${formattedError}`);
|
|
147
175
|
}
|
|
148
176
|
|
package/lib/datasource/list.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Datasource List Command
|
|
3
3
|
*
|
|
4
|
-
* Lists datasources from an environment via
|
|
4
|
+
* Lists datasources from an environment via dataplane API.
|
|
5
|
+
* Gets dataplane URL from controller, then lists datasources from dataplane.
|
|
5
6
|
*
|
|
6
7
|
* @fileoverview Datasource listing for AI Fabrix Builder
|
|
7
8
|
* @author AI Fabrix Team
|
|
@@ -11,7 +12,8 @@
|
|
|
11
12
|
const chalk = require('chalk');
|
|
12
13
|
const { getConfig, resolveEnvironment } = require('../core/config');
|
|
13
14
|
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
14
|
-
const {
|
|
15
|
+
const { listDatasources: listDatasourcesFromDataplane } = require('../api/datasources-core.api');
|
|
16
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
15
17
|
const { formatApiError } = require('../utils/api-error-handler');
|
|
16
18
|
const logger = require('../utils/logger');
|
|
17
19
|
|
|
@@ -103,14 +105,19 @@ function extractDatasources(response) {
|
|
|
103
105
|
* @function displayDatasources
|
|
104
106
|
* @param {Array} datasources - Array of datasource objects
|
|
105
107
|
* @param {string} environment - Environment key
|
|
108
|
+
* @param {string} dataplaneUrl - Dataplane URL for header display
|
|
106
109
|
*/
|
|
107
|
-
function displayDatasources(datasources, environment) {
|
|
110
|
+
function displayDatasources(datasources, environment, dataplaneUrl) {
|
|
111
|
+
const environmentName = environment || 'dev';
|
|
112
|
+
const header = `Datasources in ${environmentName} environment${dataplaneUrl ? ` (${dataplaneUrl})` : ''}`;
|
|
113
|
+
|
|
108
114
|
if (datasources.length === 0) {
|
|
109
|
-
logger.log(chalk.
|
|
115
|
+
logger.log(chalk.bold(`\n📋 ${header}:\n`));
|
|
116
|
+
logger.log(chalk.gray(' No datasources found in this environment.\n'));
|
|
110
117
|
return;
|
|
111
118
|
}
|
|
112
119
|
|
|
113
|
-
logger.log(chalk.
|
|
120
|
+
logger.log(chalk.bold(`\n📋 ${header}:\n`));
|
|
114
121
|
logger.log(chalk.gray('Key'.padEnd(30) + 'Display Name'.padEnd(30) + 'System Key'.padEnd(20) + 'Version'.padEnd(15) + 'Status'));
|
|
115
122
|
logger.log(chalk.gray('-'.repeat(120)));
|
|
116
123
|
|
|
@@ -169,7 +176,7 @@ async function getDeviceTokenFromConfig(config) {
|
|
|
169
176
|
* @param {string|null} controllerUrl - Controller URL
|
|
170
177
|
*/
|
|
171
178
|
function validateDatasourceListingAuth(token, controllerUrl) {
|
|
172
|
-
if (!token || !controllerUrl) {
|
|
179
|
+
if (!token || !controllerUrl || (typeof controllerUrl === 'string' && !controllerUrl.trim())) {
|
|
173
180
|
logger.error(chalk.red('❌ Not logged in. Run: aifabrix login'));
|
|
174
181
|
logger.error(chalk.gray(' Use device code flow: aifabrix login --method device --controller <url>'));
|
|
175
182
|
process.exit(1);
|
|
@@ -181,14 +188,92 @@ function validateDatasourceListingAuth(token, controllerUrl) {
|
|
|
181
188
|
* @function handleDatasourceApiError
|
|
182
189
|
* @param {Object} response - API response
|
|
183
190
|
*/
|
|
184
|
-
function handleDatasourceApiError(response) {
|
|
191
|
+
function handleDatasourceApiError(response, dataplaneUrl = null) {
|
|
185
192
|
const formattedError = response.formattedError || formatApiError(response);
|
|
186
193
|
logger.error(formattedError);
|
|
194
|
+
|
|
195
|
+
// Show endpoint URL from error data if available (more specific than dataplane URL)
|
|
196
|
+
if (response.errorData && response.errorData.endpointUrl) {
|
|
197
|
+
logger.error(chalk.gray(`\nEndpoint URL: ${response.errorData.endpointUrl}`));
|
|
198
|
+
} else if (response.errorData && response.errorData.controllerUrl) {
|
|
199
|
+
logger.error(chalk.gray(`\nDataplane URL: ${response.errorData.controllerUrl}`));
|
|
200
|
+
} else if (dataplaneUrl) {
|
|
201
|
+
logger.error(chalk.gray(`\nDataplane URL: ${dataplaneUrl}`));
|
|
202
|
+
}
|
|
203
|
+
|
|
187
204
|
logger.error(chalk.gray('\nFull response for debugging:'));
|
|
188
205
|
logger.error(chalk.gray(JSON.stringify(response, null, 2)));
|
|
189
206
|
process.exit(1);
|
|
190
207
|
}
|
|
191
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Validates and trims controller URL
|
|
211
|
+
* @function validateControllerUrl
|
|
212
|
+
* @param {string} controllerUrl - Controller URL to validate
|
|
213
|
+
* @returns {string} Trimmed controller URL
|
|
214
|
+
*/
|
|
215
|
+
function validateControllerUrl(controllerUrl) {
|
|
216
|
+
const trimmed = controllerUrl.trim();
|
|
217
|
+
if (!trimmed) {
|
|
218
|
+
logger.error(chalk.red('❌ Controller URL is empty.'));
|
|
219
|
+
logger.error(chalk.gray(` Controller URL from config: ${JSON.stringify(controllerUrl)}`));
|
|
220
|
+
logger.error(chalk.gray(' Run: aifabrix login --method device --controller <url>'));
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
return trimmed;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Resolves and validates dataplane URL from controller
|
|
228
|
+
* @async
|
|
229
|
+
* @function resolveAndValidateDataplaneUrl
|
|
230
|
+
* @param {string} controllerUrl - Controller URL
|
|
231
|
+
* @param {string} environment - Environment key
|
|
232
|
+
* @param {Object} authConfig - Authentication configuration
|
|
233
|
+
* @returns {Promise<string>} Validated dataplane URL
|
|
234
|
+
*/
|
|
235
|
+
async function resolveAndValidateDataplaneUrl(controllerUrl, environment, authConfig) {
|
|
236
|
+
let dataplaneUrl;
|
|
237
|
+
try {
|
|
238
|
+
// discoverDataplaneUrl already logs progress and success messages
|
|
239
|
+
dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
logger.error(chalk.red('❌ Failed to resolve dataplane URL:'), error.message);
|
|
242
|
+
logger.error(chalk.gray('\nThe dataplane URL is automatically discovered from the controller.'));
|
|
243
|
+
logger.error(chalk.gray('If discovery fails, ensure you are logged in and the controller is accessible:'));
|
|
244
|
+
logger.error(chalk.gray(' aifabrix login'));
|
|
245
|
+
process.exit(1);
|
|
246
|
+
// eslint-disable-next-line no-unreachable
|
|
247
|
+
throw error; // Never reached in production, but needed for tests when process.exit is mocked
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!dataplaneUrl || typeof dataplaneUrl !== 'string' || !dataplaneUrl.trim()) {
|
|
251
|
+
logger.error(chalk.red('❌ Dataplane URL is empty.'));
|
|
252
|
+
logger.error(chalk.gray('The dataplane URL could not be discovered from the controller.'));
|
|
253
|
+
logger.error(chalk.gray('Ensure the dataplane service is registered in the controller.'));
|
|
254
|
+
process.exit(1);
|
|
255
|
+
// eslint-disable-next-line no-unreachable
|
|
256
|
+
throw new Error('Dataplane URL is empty'); // Never reached in production, but needed for tests
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return dataplaneUrl.trim();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Sets up authentication configuration for dataplane API calls
|
|
264
|
+
* @function setupAuthConfig
|
|
265
|
+
* @param {string} token - Authentication token
|
|
266
|
+
* @param {string} controllerUrl - Controller URL
|
|
267
|
+
* @returns {Object} Authentication configuration
|
|
268
|
+
*/
|
|
269
|
+
function setupAuthConfig(token, controllerUrl) {
|
|
270
|
+
return {
|
|
271
|
+
type: 'bearer',
|
|
272
|
+
token: token,
|
|
273
|
+
controller: controllerUrl
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
192
277
|
async function listDatasources(_options) {
|
|
193
278
|
const config = await getConfig();
|
|
194
279
|
|
|
@@ -199,22 +284,24 @@ async function listDatasources(_options) {
|
|
|
199
284
|
const authInfo = await getDeviceTokenFromConfig(config);
|
|
200
285
|
validateDatasourceListingAuth(authInfo?.token, authInfo?.controllerUrl);
|
|
201
286
|
|
|
202
|
-
// Call controller API using centralized API client
|
|
203
|
-
// Note: validateDatasourceListingAuth will exit if auth is missing, so this check is defensive
|
|
204
287
|
if (!authInfo || !authInfo.token || !authInfo.controllerUrl) {
|
|
205
|
-
validateDatasourceListingAuth(null, null);
|
|
206
|
-
return;
|
|
288
|
+
validateDatasourceListingAuth(null, null);
|
|
289
|
+
return;
|
|
207
290
|
}
|
|
208
|
-
|
|
209
|
-
const
|
|
291
|
+
|
|
292
|
+
const controllerUrl = validateControllerUrl(authInfo.controllerUrl);
|
|
293
|
+
const authConfig = setupAuthConfig(authInfo.token, controllerUrl);
|
|
294
|
+
const dataplaneUrl = await resolveAndValidateDataplaneUrl(controllerUrl, environment, authConfig);
|
|
295
|
+
|
|
296
|
+
const response = await listDatasourcesFromDataplane(dataplaneUrl, authConfig);
|
|
210
297
|
|
|
211
298
|
if (!response.success || !response.data) {
|
|
212
|
-
handleDatasourceApiError(response);
|
|
299
|
+
handleDatasourceApiError(response, dataplaneUrl);
|
|
213
300
|
return; // Ensure we don't continue after exit
|
|
214
301
|
}
|
|
215
302
|
|
|
216
303
|
const datasources = extractDatasources(response);
|
|
217
|
-
displayDatasources(datasources, environment);
|
|
304
|
+
displayDatasources(datasources, environment, dataplaneUrl);
|
|
218
305
|
}
|
|
219
306
|
|
|
220
307
|
module.exports = {
|
package/lib/utils/api.js
CHANGED
|
@@ -131,6 +131,32 @@ async function handleSuccessResponse(response, url, options, duration) {
|
|
|
131
131
|
return { success: true, data: text, status: response.status };
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Validates that a URL is not empty or missing
|
|
136
|
+
* @function validateUrl
|
|
137
|
+
* @param {string} url - URL to validate
|
|
138
|
+
* @param {string} [urlType='URL'] - Type of URL for error message (e.g., 'Dataplane URL', 'Controller URL')
|
|
139
|
+
* @returns {void}
|
|
140
|
+
* @throws {Error} If URL is empty, null, undefined, whitespace-only, or malformed
|
|
141
|
+
*/
|
|
142
|
+
function validateUrl(url, urlType = 'URL') {
|
|
143
|
+
if (!url || typeof url !== 'string') {
|
|
144
|
+
throw new Error(`${urlType} is required and must be a string (received: ${JSON.stringify(url)})`);
|
|
145
|
+
}
|
|
146
|
+
const trimmedUrl = url.trim();
|
|
147
|
+
if (!trimmedUrl) {
|
|
148
|
+
throw new Error(`${urlType} cannot be empty. Please provide a valid URL.`);
|
|
149
|
+
}
|
|
150
|
+
// Check for common invalid URL patterns
|
|
151
|
+
if (trimmedUrl === 'undefined' || trimmedUrl === 'null' || trimmedUrl === 'NaN') {
|
|
152
|
+
throw new Error(`${urlType} is invalid: "${trimmedUrl}". Please provide a valid URL.`);
|
|
153
|
+
}
|
|
154
|
+
// Basic URL format validation - must start with http:// or https://
|
|
155
|
+
if (!trimmedUrl.match(/^https?:\/\//i)) {
|
|
156
|
+
throw new Error(`${urlType} must be a valid HTTP/HTTPS URL (received: "${trimmedUrl}")`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
134
160
|
/**
|
|
135
161
|
* Handles network error from API call
|
|
136
162
|
* @async
|
|
@@ -142,7 +168,34 @@ async function handleSuccessResponse(response, url, options, duration) {
|
|
|
142
168
|
* @returns {Promise<Object>} Error response object
|
|
143
169
|
*/
|
|
144
170
|
async function handleNetworkError(error, url, options, duration) {
|
|
145
|
-
|
|
171
|
+
// Enhance error message with URL information if URL is missing or invalid
|
|
172
|
+
let errorMessage = error.message;
|
|
173
|
+
if (errorMessage && (errorMessage.includes('cannot be empty') || errorMessage.includes('is required'))) {
|
|
174
|
+
// Add URL context to validation errors
|
|
175
|
+
if (!url || !url.trim()) {
|
|
176
|
+
errorMessage = `${errorMessage} (URL was: ${JSON.stringify(url)})`;
|
|
177
|
+
} else {
|
|
178
|
+
errorMessage = `${errorMessage} (URL was: ${url})`;
|
|
179
|
+
}
|
|
180
|
+
} else if (!url || !url.trim()) {
|
|
181
|
+
// If URL is empty but error doesn't mention it, add context
|
|
182
|
+
errorMessage = `Invalid or missing URL. ${errorMessage} (URL was: ${JSON.stringify(url)})`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const parsedError = parseErrorResponse(errorMessage, 0, true);
|
|
186
|
+
|
|
187
|
+
// Extract controller URL from full URL for error data
|
|
188
|
+
let controllerUrl = null;
|
|
189
|
+
const endpointUrl = url;
|
|
190
|
+
if (url && typeof url === 'string' && url.trim()) {
|
|
191
|
+
try {
|
|
192
|
+
const urlObj = new URL(url);
|
|
193
|
+
controllerUrl = `${urlObj.protocol}//${urlObj.host}`;
|
|
194
|
+
} catch {
|
|
195
|
+
// If URL parsing fails, use the full URL as endpoint
|
|
196
|
+
controllerUrl = null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
146
199
|
|
|
147
200
|
await logApiPerformance({
|
|
148
201
|
url,
|
|
@@ -157,10 +210,17 @@ async function handleNetworkError(error, url, options, duration) {
|
|
|
157
210
|
}
|
|
158
211
|
});
|
|
159
212
|
|
|
213
|
+
// Include both controller URL and full endpoint URL in error data
|
|
214
|
+
const errorData = {
|
|
215
|
+
...parsedError.data,
|
|
216
|
+
controllerUrl: controllerUrl,
|
|
217
|
+
endpointUrl: endpointUrl
|
|
218
|
+
};
|
|
219
|
+
|
|
160
220
|
return {
|
|
161
221
|
success: false,
|
|
162
222
|
error: parsedError.message,
|
|
163
|
-
errorData:
|
|
223
|
+
errorData: errorData,
|
|
164
224
|
errorType: parsedError.type,
|
|
165
225
|
formattedError: parsedError.formatted,
|
|
166
226
|
network: true
|
|
@@ -175,6 +235,14 @@ async function handleNetworkError(error, url, options, duration) {
|
|
|
175
235
|
* @returns {Promise<Object>} Response object with success flag
|
|
176
236
|
*/
|
|
177
237
|
async function makeApiCall(url, options = {}) {
|
|
238
|
+
// Validate URL before attempting request
|
|
239
|
+
try {
|
|
240
|
+
validateUrl(url, 'API endpoint URL');
|
|
241
|
+
} catch (error) {
|
|
242
|
+
const duration = 0;
|
|
243
|
+
return await handleNetworkError(error, url || '', options, duration);
|
|
244
|
+
}
|
|
245
|
+
|
|
178
246
|
const startTime = Date.now();
|
|
179
247
|
const fetchOptions = { ...options };
|
|
180
248
|
if (!fetchOptions.signal) {
|
|
@@ -40,7 +40,10 @@ function addControllerUrlHeader(lines, errorData) {
|
|
|
40
40
|
* @param {Object} errorData - Error response data
|
|
41
41
|
*/
|
|
42
42
|
function addControllerUrlToMessage(lines, errorData) {
|
|
43
|
-
|
|
43
|
+
// Prefer showing full endpoint URL if available
|
|
44
|
+
if (errorData && errorData.endpointUrl) {
|
|
45
|
+
lines.push(chalk.gray(`Endpoint URL: ${errorData.endpointUrl}`));
|
|
46
|
+
} else if (errorData && errorData.controllerUrl) {
|
|
44
47
|
lines.push(chalk.gray(`Controller URL: ${errorData.controllerUrl}`));
|
|
45
48
|
}
|
|
46
49
|
}
|
|
@@ -77,8 +80,15 @@ function formatHostnameNotFoundError(lines, errorData) {
|
|
|
77
80
|
*/
|
|
78
81
|
function formatTimeoutError(lines, errorData) {
|
|
79
82
|
lines.push(chalk.yellow('Request timed out.'));
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
|
|
84
|
+
// Show full endpoint URL if available, otherwise show controller URL
|
|
85
|
+
if (errorData && errorData.endpointUrl) {
|
|
86
|
+
lines.push(chalk.gray(`Endpoint URL: ${errorData.endpointUrl}`));
|
|
87
|
+
} else if (errorData && errorData.controllerUrl) {
|
|
88
|
+
lines.push(chalk.gray(`Controller URL: ${errorData.controllerUrl}`));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
lines.push(chalk.gray('The endpoint may not exist, the controller may be overloaded, or there may be a network issue.'));
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
/**
|
package/package.json
CHANGED
|
@@ -12,4 +12,47 @@ jobs:
|
|
|
12
12
|
with:
|
|
13
13
|
node-version: '20'
|
|
14
14
|
- name: Run tests
|
|
15
|
-
run: npm test
|
|
15
|
+
run: npm test
|
|
16
|
+
|
|
17
|
+
deploy:
|
|
18
|
+
name: Deploy to AI Fabrix
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
needs: test
|
|
21
|
+
# Note: This workflow deploys to ACR (Azure Container Registry) and Azure
|
|
22
|
+
# For local deployment, use 'aifabrix deploy {{appName}}' directly from your machine
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Setup Node.js
|
|
27
|
+
uses: actions/setup-node@v4
|
|
28
|
+
with:
|
|
29
|
+
node-version: '20'
|
|
30
|
+
|
|
31
|
+
- name: Install AI Fabrix Builder
|
|
32
|
+
run: npm install -g @aifabrix/builder
|
|
33
|
+
|
|
34
|
+
- name: Authenticate with Controller
|
|
35
|
+
run: |
|
|
36
|
+
set -e
|
|
37
|
+
aifabrix login \
|
|
38
|
+
--method credentials \
|
|
39
|
+
--app {{appName}} \
|
|
40
|
+
--client-id ${{ secrets.DEV_MISO_CLIENTID }} \
|
|
41
|
+
--client-secret ${{ secrets.DEV_MISO_CLIENTSECRET }} \
|
|
42
|
+
--controller ${{ secrets.MISO_CONTROLLER_URL }} \
|
|
43
|
+
--environment dev
|
|
44
|
+
|
|
45
|
+
- name: Validate Application Manifest
|
|
46
|
+
run: |
|
|
47
|
+
set -e
|
|
48
|
+
aifabrix validate {{appName}}
|
|
49
|
+
|
|
50
|
+
- name: Build Docker Image
|
|
51
|
+
run: |
|
|
52
|
+
set -e
|
|
53
|
+
aifabrix build {{appName}} --tag ${{ github.sha }}
|
|
54
|
+
|
|
55
|
+
- name: Deploy Application
|
|
56
|
+
run: |
|
|
57
|
+
set -e
|
|
58
|
+
aifabrix deploy {{appName}}
|
|
@@ -56,3 +56,47 @@ jobs:
|
|
|
56
56
|
body: Release {{appName}} ${{ github.ref }}
|
|
57
57
|
draft: false
|
|
58
58
|
prerelease: false
|
|
59
|
+
|
|
60
|
+
deploy:
|
|
61
|
+
name: Deploy to AI Fabrix
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
needs: create-release
|
|
64
|
+
# Note: This workflow deploys to ACR (Azure Container Registry) and Azure
|
|
65
|
+
# For local deployment, use 'aifabrix deploy {{appName}}' directly from your machine
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v4
|
|
68
|
+
|
|
69
|
+
- name: Setup Node.js
|
|
70
|
+
uses: actions/setup-node@v4
|
|
71
|
+
with:
|
|
72
|
+
node-version: '20'
|
|
73
|
+
|
|
74
|
+
- name: Install AI Fabrix Builder
|
|
75
|
+
run: npm install -g @aifabrix/builder
|
|
76
|
+
|
|
77
|
+
- name: Authenticate with Controller
|
|
78
|
+
run: |
|
|
79
|
+
set -e
|
|
80
|
+
aifabrix login \
|
|
81
|
+
--method credentials \
|
|
82
|
+
--app {{appName}} \
|
|
83
|
+
--client-id ${{ secrets.PRO_MISO_CLIENTID }} \
|
|
84
|
+
--client-secret ${{ secrets.PRO_MISO_CLIENTSECRET }} \
|
|
85
|
+
--controller ${{ secrets.MISO_CONTROLLER_URL }} \
|
|
86
|
+
--environment pro
|
|
87
|
+
|
|
88
|
+
- name: Validate Application Manifest
|
|
89
|
+
run: |
|
|
90
|
+
set -e
|
|
91
|
+
aifabrix validate {{appName}}
|
|
92
|
+
|
|
93
|
+
- name: Build Docker Image
|
|
94
|
+
run: |
|
|
95
|
+
set -e
|
|
96
|
+
TAG_VERSION=${GITHUB_REF#refs/tags/v}
|
|
97
|
+
aifabrix build {{appName}} --tag $TAG_VERSION
|
|
98
|
+
|
|
99
|
+
- name: Deploy Application
|
|
100
|
+
run: |
|
|
101
|
+
set -e
|
|
102
|
+
aifabrix deploy {{appName}}
|