@aifabrix/builder 2.7.0 → 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.
@@ -0,0 +1,262 @@
1
+ /**
2
+ * External System Deployment Module
3
+ *
4
+ * Handles deployment of external systems and datasources via pipeline API
5
+ * for external type applications.
6
+ *
7
+ * @fileoverview External system deployment for AI Fabrix Builder
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ const fs = require('fs').promises;
13
+ const path = require('path');
14
+ const yaml = require('js-yaml');
15
+ const chalk = require('chalk');
16
+ const { authenticatedApiCall } = require('./utils/api');
17
+ const { getDeploymentAuth } = require('./utils/token-manager');
18
+ const { getConfig } = require('./config');
19
+ const logger = require('./utils/logger');
20
+ const { getDataplaneUrl } = require('./datasource-deploy');
21
+
22
+ /**
23
+ * Loads variables.yaml for an application
24
+ * @async
25
+ * @function loadVariablesYaml
26
+ * @param {string} appName - Application name
27
+ * @returns {Promise<Object>} Variables configuration
28
+ * @throws {Error} If file cannot be loaded
29
+ */
30
+ async function loadVariablesYaml(appName) {
31
+ const variablesPath = path.join(process.cwd(), 'builder', appName, 'variables.yaml');
32
+ const content = await fs.readFile(variablesPath, 'utf8');
33
+ return yaml.load(content);
34
+ }
35
+
36
+ /**
37
+ * Validates external system files exist
38
+ * @async
39
+ * @function validateExternalSystemFiles
40
+ * @param {string} appName - Application name
41
+ * @returns {Promise<Object>} Validation result with file paths
42
+ * @throws {Error} If validation fails
43
+ */
44
+ async function validateExternalSystemFiles(appName) {
45
+ const variables = await loadVariablesYaml(appName);
46
+
47
+ if (!variables.externalIntegration) {
48
+ throw new Error('externalIntegration block not found in variables.yaml');
49
+ }
50
+
51
+ const appPath = path.join(process.cwd(), 'builder', appName);
52
+ const schemasPath = path.join(appPath, variables.externalIntegration.schemaBasePath || './schemas');
53
+
54
+ // Validate system files
55
+ const systemFiles = [];
56
+ if (variables.externalIntegration.systems && variables.externalIntegration.systems.length > 0) {
57
+ for (const systemFile of variables.externalIntegration.systems) {
58
+ const systemPath = path.join(schemasPath, systemFile);
59
+ try {
60
+ await fs.access(systemPath);
61
+ systemFiles.push(systemPath);
62
+ } catch {
63
+ throw new Error(`External system file not found: ${systemPath}`);
64
+ }
65
+ }
66
+ } else {
67
+ throw new Error('No external system files specified in externalIntegration.systems');
68
+ }
69
+
70
+ // Validate datasource files
71
+ const datasourceFiles = [];
72
+ if (variables.externalIntegration.dataSources && variables.externalIntegration.dataSources.length > 0) {
73
+ for (const datasourceFile of variables.externalIntegration.dataSources) {
74
+ const datasourcePath = path.join(schemasPath, datasourceFile);
75
+ try {
76
+ await fs.access(datasourcePath);
77
+ datasourceFiles.push(datasourcePath);
78
+ } catch {
79
+ throw new Error(`External datasource file not found: ${datasourcePath}`);
80
+ }
81
+ }
82
+ }
83
+
84
+ return {
85
+ systemFiles,
86
+ datasourceFiles,
87
+ systemKey: path.basename(systemFiles[0], '.json')
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Deploys external system to dataplane (build step - deploy, not publish)
93
+ * @async
94
+ * @function buildExternalSystem
95
+ * @param {string} appName - Application name
96
+ * @param {Object} options - Deployment options
97
+ * @returns {Promise<void>} Resolves when deployment completes
98
+ * @throws {Error} If deployment fails
99
+ */
100
+ async function buildExternalSystem(appName, options = {}) {
101
+ try {
102
+ logger.log(chalk.blue(`\n🔨 Building external system: ${appName}`));
103
+
104
+ // Validate files
105
+ const { systemFiles, datasourceFiles, systemKey } = await validateExternalSystemFiles(appName);
106
+
107
+ // Get authentication
108
+ const config = await getConfig();
109
+ const environment = options.environment || 'dev';
110
+ const controllerUrl = options.controller || config.deployment?.controllerUrl || 'http://localhost:3000';
111
+ const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
112
+
113
+ if (!authConfig.token && !authConfig.clientId) {
114
+ throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
115
+ }
116
+
117
+ // Get dataplane URL from controller
118
+ logger.log(chalk.blue('🌐 Getting dataplane URL from controller...'));
119
+ const dataplaneUrl = await getDataplaneUrl(controllerUrl, appName, environment, authConfig);
120
+ logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
121
+
122
+ // Deploy external system
123
+ logger.log(chalk.blue(`Deploying external system: ${systemKey}...`));
124
+ const systemContent = await fs.readFile(systemFiles[0], 'utf8');
125
+ const systemJson = JSON.parse(systemContent);
126
+
127
+ const systemResponse = await authenticatedApiCall(
128
+ `${dataplaneUrl}/api/v1/pipeline/deploy`,
129
+ {
130
+ method: 'POST',
131
+ body: JSON.stringify(systemJson)
132
+ },
133
+ authConfig.token
134
+ );
135
+
136
+ if (!systemResponse.success) {
137
+ throw new Error(`Failed to deploy external system: ${systemResponse.error || systemResponse.formattedError}`);
138
+ }
139
+
140
+ logger.log(chalk.green(`✓ External system deployed: ${systemKey}`));
141
+
142
+ // Deploy datasources
143
+ for (const datasourceFile of datasourceFiles) {
144
+ const datasourceName = path.basename(datasourceFile, '.json');
145
+ logger.log(chalk.blue(`Deploying datasource: ${datasourceName}...`));
146
+
147
+ const datasourceContent = await fs.readFile(datasourceFile, 'utf8');
148
+ const datasourceJson = JSON.parse(datasourceContent);
149
+
150
+ const datasourceResponse = await authenticatedApiCall(
151
+ `${dataplaneUrl}/api/v1/pipeline/${systemKey}/deploy`,
152
+ {
153
+ method: 'POST',
154
+ body: JSON.stringify(datasourceJson)
155
+ },
156
+ authConfig.token
157
+ );
158
+
159
+ if (!datasourceResponse.success) {
160
+ throw new Error(`Failed to deploy datasource ${datasourceName}: ${datasourceResponse.error || datasourceResponse.formattedError}`);
161
+ }
162
+
163
+ logger.log(chalk.green(`✓ Datasource deployed: ${datasourceName}`));
164
+ }
165
+
166
+ logger.log(chalk.green('\n✅ External system built successfully!'));
167
+ logger.log(chalk.blue(`System: ${systemKey}`));
168
+ logger.log(chalk.blue(`Datasources: ${datasourceFiles.length}`));
169
+ } catch (error) {
170
+ throw new Error(`Failed to build external system: ${error.message}`);
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Publishes external system to dataplane (deploy step - publish)
176
+ * @async
177
+ * @function deployExternalSystem
178
+ * @param {string} appName - Application name
179
+ * @param {Object} options - Deployment options
180
+ * @returns {Promise<void>} Resolves when deployment completes
181
+ * @throws {Error} If deployment fails
182
+ */
183
+ async function deployExternalSystem(appName, options = {}) {
184
+ try {
185
+ logger.log(chalk.blue(`\n🚀 Publishing external system: ${appName}`));
186
+
187
+ // Validate files
188
+ const { systemFiles, datasourceFiles, systemKey } = await validateExternalSystemFiles(appName);
189
+
190
+ // Get authentication
191
+ const config = await getConfig();
192
+ const environment = options.environment || 'dev';
193
+ const controllerUrl = options.controller || config.deployment?.controllerUrl || 'http://localhost:3000';
194
+ const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
195
+
196
+ if (!authConfig.token && !authConfig.clientId) {
197
+ throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
198
+ }
199
+
200
+ // Get dataplane URL from controller
201
+ logger.log(chalk.blue('🌐 Getting dataplane URL from controller...'));
202
+ const dataplaneUrl = await getDataplaneUrl(controllerUrl, appName, environment, authConfig);
203
+ logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
204
+
205
+ // Publish external system
206
+ logger.log(chalk.blue(`Publishing external system: ${systemKey}...`));
207
+ const systemContent = await fs.readFile(systemFiles[0], 'utf8');
208
+ const systemJson = JSON.parse(systemContent);
209
+
210
+ const systemResponse = await authenticatedApiCall(
211
+ `${dataplaneUrl}/api/v1/pipeline/publish`,
212
+ {
213
+ method: 'POST',
214
+ body: JSON.stringify(systemJson)
215
+ },
216
+ authConfig.token
217
+ );
218
+
219
+ if (!systemResponse.success) {
220
+ throw new Error(`Failed to publish external system: ${systemResponse.error || systemResponse.formattedError}`);
221
+ }
222
+
223
+ logger.log(chalk.green(`✓ External system published: ${systemKey}`));
224
+
225
+ // Publish datasources
226
+ for (const datasourceFile of datasourceFiles) {
227
+ const datasourceName = path.basename(datasourceFile, '.json');
228
+ logger.log(chalk.blue(`Publishing datasource: ${datasourceName}...`));
229
+
230
+ const datasourceContent = await fs.readFile(datasourceFile, 'utf8');
231
+ const datasourceJson = JSON.parse(datasourceContent);
232
+
233
+ const datasourceResponse = await authenticatedApiCall(
234
+ `${dataplaneUrl}/api/v1/pipeline/${systemKey}/publish`,
235
+ {
236
+ method: 'POST',
237
+ body: JSON.stringify(datasourceJson)
238
+ },
239
+ authConfig.token
240
+ );
241
+
242
+ if (!datasourceResponse.success) {
243
+ throw new Error(`Failed to publish datasource ${datasourceName}: ${datasourceResponse.error || datasourceResponse.formattedError}`);
244
+ }
245
+
246
+ logger.log(chalk.green(`✓ Datasource published: ${datasourceName}`));
247
+ }
248
+
249
+ logger.log(chalk.green('\n✅ External system published successfully!'));
250
+ logger.log(chalk.blue(`System: ${systemKey}`));
251
+ logger.log(chalk.blue(`Datasources: ${datasourceFiles.length}`));
252
+ } catch (error) {
253
+ throw new Error(`Failed to deploy external system: ${error.message}`);
254
+ }
255
+ }
256
+
257
+ module.exports = {
258
+ buildExternalSystem,
259
+ deployExternalSystem,
260
+ validateExternalSystemFiles
261
+ };
262
+
@@ -0,0 +1,187 @@
1
+ /**
2
+ * External System Template Generation Module
3
+ *
4
+ * Generates external system and datasource JSON files from Handlebars templates
5
+ * for external type applications.
6
+ *
7
+ * @fileoverview External system template generation for AI Fabrix Builder
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ const fs = require('fs').promises;
13
+ const path = require('path');
14
+ const handlebars = require('handlebars');
15
+ const yaml = require('js-yaml');
16
+ const chalk = require('chalk');
17
+ const logger = require('./utils/logger');
18
+
19
+ // Register Handlebars helper for equality check
20
+ handlebars.registerHelper('eq', (a, b) => a === b);
21
+
22
+ /**
23
+ * Generates external system JSON file from template
24
+ * @async
25
+ * @function generateExternalSystemTemplate
26
+ * @param {string} appPath - Application directory path
27
+ * @param {string} systemKey - System key
28
+ * @param {Object} config - System configuration
29
+ * @returns {Promise<string>} Path to generated file
30
+ * @throws {Error} If generation fails
31
+ */
32
+ async function generateExternalSystemTemplate(appPath, systemKey, config) {
33
+ try {
34
+ const templatePath = path.join(__dirname, '..', 'templates', 'external-system', 'external-system.json.hbs');
35
+ const templateContent = await fs.readFile(templatePath, 'utf8');
36
+ const template = handlebars.compile(templateContent);
37
+
38
+ const context = {
39
+ systemKey: systemKey,
40
+ systemDisplayName: config.systemDisplayName || systemKey.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
41
+ systemDescription: config.systemDescription || `External system integration for ${systemKey}`,
42
+ systemType: config.systemType || 'openapi',
43
+ authType: config.authType || 'apikey'
44
+ };
45
+
46
+ const rendered = template(context);
47
+ const schemasDir = path.join(appPath, 'schemas');
48
+ await fs.mkdir(schemasDir, { recursive: true });
49
+
50
+ const outputPath = path.join(schemasDir, `${systemKey}.json`);
51
+ await fs.writeFile(outputPath, rendered, 'utf8');
52
+
53
+ return outputPath;
54
+ } catch (error) {
55
+ throw new Error(`Failed to generate external system template: ${error.message}`);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Generates external datasource JSON file from template
61
+ * @async
62
+ * @function generateExternalDataSourceTemplate
63
+ * @param {string} appPath - Application directory path
64
+ * @param {string} datasourceKey - Datasource key
65
+ * @param {Object} config - Datasource configuration
66
+ * @returns {Promise<string>} Path to generated file
67
+ * @throws {Error} If generation fails
68
+ */
69
+ async function generateExternalDataSourceTemplate(appPath, datasourceKey, config) {
70
+ try {
71
+ const templatePath = path.join(__dirname, '..', 'templates', 'external-system', 'external-datasource.json.hbs');
72
+ const templateContent = await fs.readFile(templatePath, 'utf8');
73
+ const template = handlebars.compile(templateContent);
74
+
75
+ const context = {
76
+ datasourceKey: datasourceKey,
77
+ datasourceDisplayName: config.datasourceDisplayName || datasourceKey.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
78
+ datasourceDescription: config.datasourceDescription || `External datasource for ${datasourceKey}`,
79
+ systemKey: config.systemKey,
80
+ entityKey: config.entityKey || datasourceKey.split('-').pop(),
81
+ resourceType: config.resourceType || 'document',
82
+ systemType: config.systemType || 'openapi'
83
+ };
84
+
85
+ const rendered = template(context);
86
+ const schemasDir = path.join(appPath, 'schemas');
87
+ await fs.mkdir(schemasDir, { recursive: true });
88
+
89
+ const outputPath = path.join(schemasDir, `${datasourceKey}.json`);
90
+ await fs.writeFile(outputPath, rendered, 'utf8');
91
+
92
+ return outputPath;
93
+ } catch (error) {
94
+ throw new Error(`Failed to generate external datasource template: ${error.message}`);
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Generates all external system files (system + datasources)
100
+ * @async
101
+ * @function generateExternalSystemFiles
102
+ * @param {string} appPath - Application directory path
103
+ * @param {string} appName - Application name
104
+ * @param {Object} config - Configuration with external system details
105
+ * @returns {Promise<Object>} Object with system and datasource file paths
106
+ * @throws {Error} If generation fails
107
+ */
108
+ async function generateExternalSystemFiles(appPath, appName, config) {
109
+ try {
110
+ const systemKey = config.systemKey || appName;
111
+ const datasourceCount = config.datasourceCount || 1;
112
+
113
+ // Generate external system JSON
114
+ const systemPath = await generateExternalSystemTemplate(appPath, systemKey, config);
115
+ logger.log(chalk.green(`✓ Generated external system: ${path.basename(systemPath)}`));
116
+
117
+ // Generate datasource JSON files
118
+ const datasourcePaths = [];
119
+ const resourceTypes = ['customer', 'contact', 'person', 'document', 'deal'];
120
+
121
+ for (let i = 0; i < datasourceCount; i++) {
122
+ const entityKey = `entity${i + 1}`;
123
+ const datasourceKey = `${systemKey}-${entityKey}`;
124
+ const resourceType = resourceTypes[i % resourceTypes.length];
125
+
126
+ const datasourceConfig = {
127
+ systemKey: systemKey,
128
+ entityKey: entityKey,
129
+ resourceType: resourceType,
130
+ systemType: config.systemType || 'openapi',
131
+ datasourceDisplayName: `${config.systemDisplayName || systemKey} ${entityKey}`,
132
+ datasourceDescription: `External datasource for ${entityKey} entity`
133
+ };
134
+
135
+ const datasourcePath = await generateExternalDataSourceTemplate(appPath, datasourceKey, datasourceConfig);
136
+ datasourcePaths.push(datasourcePath);
137
+ logger.log(chalk.green(`✓ Generated datasource: ${path.basename(datasourcePath)}`));
138
+ }
139
+
140
+ // Update variables.yaml with externalIntegration block
141
+ await updateVariablesYamlWithExternalIntegration(appPath, systemKey, datasourcePaths);
142
+
143
+ return {
144
+ systemPath,
145
+ datasourcePaths
146
+ };
147
+ } catch (error) {
148
+ throw new Error(`Failed to generate external system files: ${error.message}`);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Updates variables.yaml with externalIntegration block
154
+ * @async
155
+ * @function updateVariablesYamlWithExternalIntegration
156
+ * @param {string} appPath - Application directory path
157
+ * @param {string} systemKey - System key
158
+ * @param {Array<string>} datasourcePaths - Array of datasource file paths
159
+ * @throws {Error} If update fails
160
+ */
161
+ async function updateVariablesYamlWithExternalIntegration(appPath, systemKey, datasourcePaths) {
162
+ try {
163
+ const variablesPath = path.join(appPath, 'variables.yaml');
164
+ const variablesContent = await fs.readFile(variablesPath, 'utf8');
165
+ const variables = yaml.load(variablesContent);
166
+
167
+ // Add externalIntegration block
168
+ variables.externalIntegration = {
169
+ schemaBasePath: './schemas',
170
+ systems: [`${systemKey}.json`],
171
+ dataSources: datasourcePaths.map(p => path.basename(p)),
172
+ autopublish: true,
173
+ version: '1.0.0'
174
+ };
175
+
176
+ await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }), 'utf8');
177
+ } catch (error) {
178
+ throw new Error(`Failed to update variables.yaml: ${error.message}`);
179
+ }
180
+ }
181
+
182
+ module.exports = {
183
+ generateExternalSystemTemplate,
184
+ generateExternalDataSourceTemplate,
185
+ generateExternalSystemFiles
186
+ };
187
+