@aifabrix/builder 2.6.3 ā 2.8.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.
- package/.cursor/rules/project-rules.mdc +680 -0
- package/bin/aifabrix.js +4 -0
- package/lib/app-config.js +10 -0
- package/lib/app-deploy.js +18 -0
- package/lib/app-dockerfile.js +15 -0
- package/lib/app-prompts.js +172 -9
- package/lib/app-push.js +15 -0
- package/lib/app-register.js +14 -0
- package/lib/app-run.js +25 -0
- package/lib/app.js +30 -13
- package/lib/audit-logger.js +9 -4
- package/lib/build.js +8 -0
- package/lib/cli.js +99 -2
- package/lib/commands/datasource.js +94 -0
- package/lib/commands/login.js +40 -3
- package/lib/config.js +121 -114
- package/lib/datasource-deploy.js +182 -0
- package/lib/datasource-diff.js +73 -0
- package/lib/datasource-list.js +138 -0
- package/lib/datasource-validate.js +63 -0
- package/lib/diff.js +266 -0
- package/lib/environment-deploy.js +305 -0
- package/lib/external-system-deploy.js +262 -0
- package/lib/external-system-generator.js +187 -0
- package/lib/schema/application-schema.json +869 -698
- package/lib/schema/external-datasource.schema.json +512 -0
- package/lib/schema/external-system.schema.json +262 -0
- package/lib/schema/infrastructure-schema.json +1 -1
- package/lib/secrets.js +20 -1
- package/lib/templates.js +32 -1
- package/lib/utils/device-code.js +10 -2
- package/lib/utils/env-copy.js +24 -0
- package/lib/utils/env-endpoints.js +50 -11
- package/lib/utils/schema-loader.js +220 -0
- package/lib/utils/schema-resolver.js +174 -0
- package/lib/utils/secrets-helpers.js +65 -17
- package/lib/utils/token-encryption.js +68 -0
- package/lib/validate.js +299 -0
- package/lib/validator.js +47 -3
- package/package.json +1 -1
- package/tatus +181 -0
- package/templates/external-system/external-datasource.json.hbs +55 -0
- package/templates/external-system/external-system.json.hbs +37 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datasource Deployment
|
|
3
|
+
*
|
|
4
|
+
* Deploys datasource to dataplane via controller API.
|
|
5
|
+
* Gets dataplane URL from controller, then deploys to dataplane.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Datasource deployment for AI Fabrix Builder
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const { getDeploymentAuth } = require('./utils/token-manager');
|
|
15
|
+
const { authenticatedApiCall } = require('./utils/api');
|
|
16
|
+
const { formatApiError } = require('./utils/api-error-handler');
|
|
17
|
+
const logger = require('./utils/logger');
|
|
18
|
+
const { validateDatasourceFile } = require('./datasource-validate');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Gets dataplane URL from controller by fetching application details
|
|
22
|
+
*
|
|
23
|
+
* @async
|
|
24
|
+
* @function getDataplaneUrl
|
|
25
|
+
* @param {string} controllerUrl - Controller URL
|
|
26
|
+
* @param {string} appKey - Application key
|
|
27
|
+
* @param {string} environment - Environment key
|
|
28
|
+
* @param {Object} authConfig - Authentication configuration
|
|
29
|
+
* @returns {Promise<string>} Dataplane URL
|
|
30
|
+
* @throws {Error} If dataplane URL cannot be retrieved
|
|
31
|
+
*/
|
|
32
|
+
async function getDataplaneUrl(controllerUrl, appKey, environment, authConfig) {
|
|
33
|
+
// Call controller API to get application details
|
|
34
|
+
// Expected: GET /api/v1/environments/{env}/applications/{appKey}
|
|
35
|
+
const endpoint = `${controllerUrl}/api/v1/environments/${environment}/applications/${appKey}`;
|
|
36
|
+
|
|
37
|
+
let response;
|
|
38
|
+
if (authConfig.type === 'bearer' && authConfig.token) {
|
|
39
|
+
response = await authenticatedApiCall(endpoint, {}, authConfig.token);
|
|
40
|
+
} else {
|
|
41
|
+
// For credentials, we'd need to use a different API call method
|
|
42
|
+
// For now, use bearer token approach
|
|
43
|
+
throw new Error('Bearer token authentication required for getting dataplane URL');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!response.success || !response.data) {
|
|
47
|
+
const formattedError = response.formattedError || formatApiError(response);
|
|
48
|
+
throw new Error(`Failed to get application from controller: ${formattedError}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Extract dataplane URL from application response
|
|
52
|
+
// This is a placeholder - actual response structure may vary
|
|
53
|
+
const application = response.data.data || response.data;
|
|
54
|
+
const dataplaneUrl = application.dataplaneUrl || application.dataplane?.url || application.configuration?.dataplaneUrl;
|
|
55
|
+
|
|
56
|
+
if (!dataplaneUrl) {
|
|
57
|
+
logger.error(chalk.red('ā Dataplane URL not found in application response'));
|
|
58
|
+
logger.error(chalk.gray('\nApplication response:'));
|
|
59
|
+
logger.error(chalk.gray(JSON.stringify(application, null, 2)));
|
|
60
|
+
throw new Error('Dataplane URL not found in application configuration');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return dataplaneUrl;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Deploys datasource to dataplane
|
|
68
|
+
*
|
|
69
|
+
* @async
|
|
70
|
+
* @function deployDatasource
|
|
71
|
+
* @param {string} appKey - Application key
|
|
72
|
+
* @param {string} filePath - Path to datasource JSON file
|
|
73
|
+
* @param {Object} options - Deployment options
|
|
74
|
+
* @param {string} options.controller - Controller URL
|
|
75
|
+
* @param {string} options.environment - Environment key
|
|
76
|
+
* @returns {Promise<Object>} Deployment result
|
|
77
|
+
* @throws {Error} If deployment fails
|
|
78
|
+
*/
|
|
79
|
+
async function deployDatasource(appKey, filePath, options) {
|
|
80
|
+
if (!appKey || typeof appKey !== 'string') {
|
|
81
|
+
throw new Error('Application key is required');
|
|
82
|
+
}
|
|
83
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
84
|
+
throw new Error('File path is required');
|
|
85
|
+
}
|
|
86
|
+
if (!options.controller) {
|
|
87
|
+
throw new Error('Controller URL is required (--controller)');
|
|
88
|
+
}
|
|
89
|
+
if (!options.environment) {
|
|
90
|
+
throw new Error('Environment is required (-e, --environment)');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
logger.log(chalk.blue('š Deploying datasource...\n'));
|
|
94
|
+
|
|
95
|
+
// Validate datasource file
|
|
96
|
+
logger.log(chalk.blue('š Validating datasource file...'));
|
|
97
|
+
const validation = await validateDatasourceFile(filePath);
|
|
98
|
+
if (!validation.valid) {
|
|
99
|
+
logger.error(chalk.red('ā Datasource validation failed:'));
|
|
100
|
+
validation.errors.forEach(error => {
|
|
101
|
+
logger.error(chalk.red(` ⢠${error}`));
|
|
102
|
+
});
|
|
103
|
+
throw new Error('Datasource file validation failed');
|
|
104
|
+
}
|
|
105
|
+
logger.log(chalk.green('ā Datasource file is valid'));
|
|
106
|
+
|
|
107
|
+
// Load datasource configuration
|
|
108
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
109
|
+
let datasourceConfig;
|
|
110
|
+
try {
|
|
111
|
+
datasourceConfig = JSON.parse(content);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
throw new Error(`Failed to parse datasource file: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Extract systemKey
|
|
117
|
+
const systemKey = datasourceConfig.systemKey;
|
|
118
|
+
if (!systemKey) {
|
|
119
|
+
throw new Error('systemKey is required in datasource configuration');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Get authentication
|
|
123
|
+
logger.log(chalk.blue('š Getting authentication...'));
|
|
124
|
+
const authConfig = await getDeploymentAuth(options.controller, options.environment, appKey);
|
|
125
|
+
logger.log(chalk.green('ā Authentication successful'));
|
|
126
|
+
|
|
127
|
+
// Get dataplane URL from controller
|
|
128
|
+
logger.log(chalk.blue('š Getting dataplane URL from controller...'));
|
|
129
|
+
const dataplaneUrl = await getDataplaneUrl(options.controller, appKey, options.environment, authConfig);
|
|
130
|
+
logger.log(chalk.green(`ā Dataplane URL: ${dataplaneUrl}`));
|
|
131
|
+
|
|
132
|
+
// Deploy to dataplane
|
|
133
|
+
logger.log(chalk.blue('\nš Deploying to dataplane...'));
|
|
134
|
+
const deployEndpoint = `${dataplaneUrl}/api/v1/pipeline/${systemKey}/deploy`;
|
|
135
|
+
|
|
136
|
+
// Prepare deployment request
|
|
137
|
+
const deployRequest = {
|
|
138
|
+
datasource: datasourceConfig
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Make API call to dataplane
|
|
142
|
+
// This is a placeholder - actual API structure may vary
|
|
143
|
+
let deployResponse;
|
|
144
|
+
if (authConfig.type === 'bearer' && authConfig.token) {
|
|
145
|
+
deployResponse = await authenticatedApiCall(
|
|
146
|
+
deployEndpoint,
|
|
147
|
+
{
|
|
148
|
+
method: 'POST',
|
|
149
|
+
body: JSON.stringify(deployRequest)
|
|
150
|
+
},
|
|
151
|
+
authConfig.token
|
|
152
|
+
);
|
|
153
|
+
} else {
|
|
154
|
+
throw new Error('Bearer token authentication required for dataplane deployment');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!deployResponse.success) {
|
|
158
|
+
const formattedError = deployResponse.formattedError || formatApiError(deployResponse);
|
|
159
|
+
logger.error(chalk.red('ā Deployment failed:'));
|
|
160
|
+
logger.error(formattedError);
|
|
161
|
+
throw new Error(`Dataplane deployment failed: ${formattedError}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
logger.log(chalk.green('\nā Datasource deployed successfully!'));
|
|
165
|
+
logger.log(chalk.blue(`\nDatasource: ${datasourceConfig.key || datasourceConfig.displayName}`));
|
|
166
|
+
logger.log(chalk.blue(`System: ${systemKey}`));
|
|
167
|
+
logger.log(chalk.blue(`Environment: ${options.environment}`));
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
success: true,
|
|
171
|
+
datasourceKey: datasourceConfig.key,
|
|
172
|
+
systemKey: systemKey,
|
|
173
|
+
environment: options.environment,
|
|
174
|
+
dataplaneUrl: dataplaneUrl
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = {
|
|
179
|
+
deployDatasource,
|
|
180
|
+
getDataplaneUrl
|
|
181
|
+
};
|
|
182
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datasource Diff Command
|
|
3
|
+
*
|
|
4
|
+
* Compares two datasource configuration files.
|
|
5
|
+
* Specialized for dataplane deployment validation.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Datasource comparison for AI Fabrix Builder
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
const { compareFiles, formatDiffOutput } = require('./diff');
|
|
14
|
+
const logger = require('./utils/logger');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Compares two datasource files with focus on dataplane-relevant fields
|
|
18
|
+
*
|
|
19
|
+
* @async
|
|
20
|
+
* @function compareDatasources
|
|
21
|
+
* @param {string} file1 - Path to first datasource file
|
|
22
|
+
* @param {string} file2 - Path to second datasource file
|
|
23
|
+
* @returns {Promise<void>}
|
|
24
|
+
* @throws {Error} If comparison fails
|
|
25
|
+
*/
|
|
26
|
+
async function compareDatasources(file1, file2) {
|
|
27
|
+
const result = await compareFiles(file1, file2);
|
|
28
|
+
|
|
29
|
+
// Filter and highlight dataplane-relevant changes
|
|
30
|
+
const dataplaneRelevant = {
|
|
31
|
+
fieldMappings: result.changed.filter(c => c.path.includes('fieldMappings')),
|
|
32
|
+
exposed: result.changed.filter(c => c.path.includes('exposed')),
|
|
33
|
+
sync: result.changed.filter(c => c.path.includes('sync')),
|
|
34
|
+
openapi: result.changed.filter(c => c.path.includes('openapi')),
|
|
35
|
+
mcp: result.changed.filter(c => c.path.includes('mcp'))
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Display standard diff
|
|
39
|
+
formatDiffOutput(result);
|
|
40
|
+
|
|
41
|
+
// Display dataplane-specific highlights
|
|
42
|
+
const hasDataplaneChanges = Object.values(dataplaneRelevant).some(arr => arr.length > 0);
|
|
43
|
+
|
|
44
|
+
if (hasDataplaneChanges) {
|
|
45
|
+
logger.log(chalk.blue('\nš Dataplane-Relevant Changes:'));
|
|
46
|
+
|
|
47
|
+
if (dataplaneRelevant.fieldMappings.length > 0) {
|
|
48
|
+
logger.log(chalk.yellow(` ⢠Field Mappings: ${dataplaneRelevant.fieldMappings.length} changes`));
|
|
49
|
+
}
|
|
50
|
+
if (dataplaneRelevant.exposed.length > 0) {
|
|
51
|
+
logger.log(chalk.yellow(` ⢠Exposed Fields: ${dataplaneRelevant.exposed.length} changes`));
|
|
52
|
+
}
|
|
53
|
+
if (dataplaneRelevant.sync.length > 0) {
|
|
54
|
+
logger.log(chalk.yellow(` ⢠Sync Configuration: ${dataplaneRelevant.sync.length} changes`));
|
|
55
|
+
}
|
|
56
|
+
if (dataplaneRelevant.openapi.length > 0) {
|
|
57
|
+
logger.log(chalk.yellow(` ⢠OpenAPI Configuration: ${dataplaneRelevant.openapi.length} changes`));
|
|
58
|
+
}
|
|
59
|
+
if (dataplaneRelevant.mcp.length > 0) {
|
|
60
|
+
logger.log(chalk.yellow(` ⢠MCP Configuration: ${dataplaneRelevant.mcp.length} changes`));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Exit with appropriate code
|
|
65
|
+
if (!result.identical) {
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
compareDatasources
|
|
72
|
+
};
|
|
73
|
+
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datasource List Command
|
|
3
|
+
*
|
|
4
|
+
* Lists datasources from an environment via controller API.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Datasource listing for AI Fabrix Builder
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const { getConfig } = require('./config');
|
|
13
|
+
const { getOrRefreshDeviceToken } = require('./utils/token-manager');
|
|
14
|
+
const { authenticatedApiCall } = require('./utils/api');
|
|
15
|
+
const { formatApiError } = require('./utils/api-error-handler');
|
|
16
|
+
const logger = require('./utils/logger');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extracts datasources array from API response
|
|
20
|
+
* Handles multiple response formats similar to applications list
|
|
21
|
+
*
|
|
22
|
+
* @function extractDatasources
|
|
23
|
+
* @param {Object} response - API response from authenticatedApiCall
|
|
24
|
+
* @returns {Array} Array of datasources
|
|
25
|
+
* @throws {Error} If response format is invalid
|
|
26
|
+
*/
|
|
27
|
+
function extractDatasources(response) {
|
|
28
|
+
const apiResponse = response.data;
|
|
29
|
+
let datasources;
|
|
30
|
+
|
|
31
|
+
// Check if apiResponse.data is an array (wrapped format)
|
|
32
|
+
if (apiResponse && apiResponse.data && Array.isArray(apiResponse.data)) {
|
|
33
|
+
datasources = apiResponse.data;
|
|
34
|
+
} else if (Array.isArray(apiResponse)) {
|
|
35
|
+
// Check if apiResponse is directly an array
|
|
36
|
+
datasources = apiResponse;
|
|
37
|
+
} else if (apiResponse && Array.isArray(apiResponse.items)) {
|
|
38
|
+
// Check if apiResponse.items is an array (paginated format)
|
|
39
|
+
datasources = apiResponse.items;
|
|
40
|
+
} else if (apiResponse && apiResponse.data && apiResponse.data.items && Array.isArray(apiResponse.data.items)) {
|
|
41
|
+
// Check if apiResponse.data.items is an array (wrapped paginated format)
|
|
42
|
+
datasources = apiResponse.data.items;
|
|
43
|
+
} else {
|
|
44
|
+
logger.error(chalk.red('ā Invalid response: expected data array or items array'));
|
|
45
|
+
logger.error(chalk.gray('\nAPI response type:'), typeof apiResponse);
|
|
46
|
+
logger.error(chalk.gray('API response:'), JSON.stringify(apiResponse, null, 2));
|
|
47
|
+
throw new Error('Invalid API response format: expected array of datasources');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return datasources;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Displays datasources in a formatted table
|
|
55
|
+
*
|
|
56
|
+
* @function displayDatasources
|
|
57
|
+
* @param {Array} datasources - Array of datasource objects
|
|
58
|
+
* @param {string} environment - Environment key
|
|
59
|
+
*/
|
|
60
|
+
function displayDatasources(datasources, environment) {
|
|
61
|
+
if (datasources.length === 0) {
|
|
62
|
+
logger.log(chalk.yellow(`\nNo datasources found in environment: ${environment}`));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
logger.log(chalk.blue(`\nš Datasources in environment: ${environment}\n`));
|
|
67
|
+
logger.log(chalk.gray('Key'.padEnd(30) + 'Display Name'.padEnd(30) + 'System Key'.padEnd(20) + 'Version'.padEnd(15) + 'Status'));
|
|
68
|
+
logger.log(chalk.gray('-'.repeat(120)));
|
|
69
|
+
|
|
70
|
+
datasources.forEach((ds) => {
|
|
71
|
+
const key = (ds.key || 'N/A').padEnd(30);
|
|
72
|
+
const displayName = (ds.displayName || 'N/A').padEnd(30);
|
|
73
|
+
const systemKey = (ds.systemKey || 'N/A').padEnd(20);
|
|
74
|
+
const version = (ds.version || 'N/A').padEnd(15);
|
|
75
|
+
const status = ds.enabled !== false ? chalk.green('enabled') : chalk.red('disabled');
|
|
76
|
+
logger.log(`${key}${displayName}${systemKey}${version}${status}`);
|
|
77
|
+
});
|
|
78
|
+
logger.log('');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Lists datasources from an environment
|
|
83
|
+
*
|
|
84
|
+
* @async
|
|
85
|
+
* @function listDatasources
|
|
86
|
+
* @param {Object} options - Command options
|
|
87
|
+
* @param {string} options.environment - Environment ID or key
|
|
88
|
+
* @throws {Error} If listing fails
|
|
89
|
+
*/
|
|
90
|
+
async function listDatasources(options) {
|
|
91
|
+
const config = await getConfig();
|
|
92
|
+
|
|
93
|
+
// Try to get device token
|
|
94
|
+
let controllerUrl = null;
|
|
95
|
+
let token = null;
|
|
96
|
+
|
|
97
|
+
if (config.device) {
|
|
98
|
+
const deviceUrls = Object.keys(config.device);
|
|
99
|
+
if (deviceUrls.length > 0) {
|
|
100
|
+
controllerUrl = deviceUrls[0];
|
|
101
|
+
const deviceToken = await getOrRefreshDeviceToken(controllerUrl);
|
|
102
|
+
if (deviceToken && deviceToken.token) {
|
|
103
|
+
token = deviceToken.token;
|
|
104
|
+
controllerUrl = deviceToken.controller;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!token || !controllerUrl) {
|
|
110
|
+
logger.error(chalk.red('ā Not logged in. Run: aifabrix login'));
|
|
111
|
+
logger.error(chalk.gray(' Use device code flow: aifabrix login --method device --controller <url>'));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Call controller API - using placeholder endpoint until full specs available
|
|
116
|
+
// Expected: GET /api/v1/environments/{env}/datasources
|
|
117
|
+
const endpoint = `${controllerUrl}/api/v1/environments/${options.environment}/datasources`;
|
|
118
|
+
const response = await authenticatedApiCall(endpoint, {}, token);
|
|
119
|
+
|
|
120
|
+
if (!response.success || !response.data) {
|
|
121
|
+
const formattedError = response.formattedError || formatApiError(response);
|
|
122
|
+
logger.error(formattedError);
|
|
123
|
+
logger.error(chalk.gray('\nFull response for debugging:'));
|
|
124
|
+
logger.error(chalk.gray(JSON.stringify(response, null, 2)));
|
|
125
|
+
process.exit(1);
|
|
126
|
+
return; // Ensure we don't continue after exit
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const datasources = extractDatasources(response);
|
|
130
|
+
displayDatasources(datasources, options.environment);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
listDatasources,
|
|
135
|
+
displayDatasources,
|
|
136
|
+
extractDatasources
|
|
137
|
+
};
|
|
138
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datasource Validation
|
|
3
|
+
*
|
|
4
|
+
* Validates external datasource JSON files against schema.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Datasource validation for AI Fabrix Builder
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const { loadExternalDataSourceSchema } = require('./utils/schema-loader');
|
|
13
|
+
const { formatValidationErrors } = require('./utils/error-formatter');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validates a datasource file against external-datasource schema
|
|
17
|
+
*
|
|
18
|
+
* @async
|
|
19
|
+
* @function validateDatasourceFile
|
|
20
|
+
* @param {string} filePath - Path to the datasource JSON file
|
|
21
|
+
* @returns {Promise<Object>} Validation result with errors and warnings
|
|
22
|
+
* @throws {Error} If file cannot be read or parsed
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const result = await validateDatasourceFile('./hubspot-deal.json');
|
|
26
|
+
* // Returns: { valid: true, errors: [], warnings: [] }
|
|
27
|
+
*/
|
|
28
|
+
async function validateDatasourceFile(filePath) {
|
|
29
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
30
|
+
throw new Error('File path is required and must be a string');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(filePath)) {
|
|
34
|
+
throw new Error(`File not found: ${filePath}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
38
|
+
let parsed;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
parsed = JSON.parse(content);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return {
|
|
44
|
+
valid: false,
|
|
45
|
+
errors: [`Invalid JSON syntax: ${error.message}`],
|
|
46
|
+
warnings: []
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const validate = loadExternalDataSourceSchema();
|
|
51
|
+
const valid = validate(parsed);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
valid,
|
|
55
|
+
errors: valid ? [] : formatValidationErrors(validate.errors),
|
|
56
|
+
warnings: []
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
validateDatasourceFile
|
|
62
|
+
};
|
|
63
|
+
|