@aifabrix/builder 2.8.0 ā 2.10.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/integration/hubspot/README.md +136 -0
- package/integration/hubspot/env.template +9 -0
- package/integration/hubspot/hubspot-deploy-company.json +200 -0
- package/integration/hubspot/hubspot-deploy-contact.json +228 -0
- package/integration/hubspot/hubspot-deploy-deal.json +248 -0
- package/integration/hubspot/hubspot-deploy.json +91 -0
- package/integration/hubspot/variables.yaml +17 -0
- package/lib/app-config.js +4 -3
- package/lib/app-deploy.js +8 -20
- package/lib/app-dockerfile.js +7 -9
- package/lib/app-prompts.js +6 -5
- package/lib/app-push.js +9 -9
- package/lib/app-register.js +23 -5
- package/lib/app-rotate-secret.js +10 -0
- package/lib/app-run.js +5 -11
- package/lib/app.js +42 -14
- package/lib/build.js +20 -16
- package/lib/cli.js +61 -2
- package/lib/commands/login.js +7 -1
- package/lib/datasource-deploy.js +14 -20
- package/lib/external-system-deploy.js +123 -40
- package/lib/external-system-download.js +431 -0
- package/lib/external-system-generator.js +13 -10
- package/lib/external-system-test.js +446 -0
- package/lib/generator-builders.js +323 -0
- package/lib/generator.js +200 -292
- package/lib/schema/application-schema.json +853 -852
- package/lib/schema/env-config.yaml +9 -1
- package/lib/schema/external-datasource.schema.json +823 -49
- package/lib/schema/external-system.schema.json +96 -78
- package/lib/templates.js +36 -5
- package/lib/utils/api-error-handler.js +12 -12
- package/lib/utils/cli-utils.js +4 -4
- package/lib/utils/device-code.js +65 -2
- package/lib/utils/env-template.js +5 -4
- package/lib/utils/external-system-display.js +159 -0
- package/lib/utils/external-system-validators.js +245 -0
- package/lib/utils/paths.js +151 -1
- package/lib/utils/schema-resolver.js +7 -2
- package/lib/validator.js +5 -2
- package/package.json +1 -1
- package/templates/applications/keycloak/env.template +8 -2
- package/templates/applications/keycloak/variables.yaml +3 -3
- package/templates/applications/miso-controller/env.template +23 -10
- package/templates/applications/miso-controller/rbac.yaml +263 -213
- package/templates/applications/miso-controller/variables.yaml +3 -3
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External System Download Module
|
|
3
|
+
*
|
|
4
|
+
* Downloads external systems from dataplane to local development structure.
|
|
5
|
+
* Supports downloading system configuration and datasources for local development.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview External system download functionality 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 os = require('os');
|
|
15
|
+
const yaml = require('js-yaml');
|
|
16
|
+
const chalk = require('chalk');
|
|
17
|
+
const { authenticatedApiCall } = require('./utils/api');
|
|
18
|
+
const { getDeploymentAuth } = require('./utils/token-manager');
|
|
19
|
+
const { getDataplaneUrl } = require('./datasource-deploy');
|
|
20
|
+
const { getConfig } = require('./config');
|
|
21
|
+
const { detectAppType } = require('./utils/paths');
|
|
22
|
+
const logger = require('./utils/logger');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validates system type from downloaded application
|
|
26
|
+
* @param {Object} application - External system configuration
|
|
27
|
+
* @returns {string} System type (openapi, mcp, custom)
|
|
28
|
+
* @throws {Error} If system type is invalid
|
|
29
|
+
*/
|
|
30
|
+
function validateSystemType(application) {
|
|
31
|
+
if (!application || typeof application !== 'object') {
|
|
32
|
+
throw new Error('Application configuration is required');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const validTypes = ['openapi', 'mcp', 'custom'];
|
|
36
|
+
const systemType = application.type;
|
|
37
|
+
|
|
38
|
+
if (!systemType || !validTypes.includes(systemType)) {
|
|
39
|
+
throw new Error(`Invalid system type: ${systemType}. Must be one of: ${validTypes.join(', ')}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return systemType;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Validates downloaded data structure before writing files
|
|
47
|
+
* @param {Object} application - External system configuration
|
|
48
|
+
* @param {Array} dataSources - Array of datasource configurations
|
|
49
|
+
* @throws {Error} If validation fails
|
|
50
|
+
*/
|
|
51
|
+
function validateDownloadedData(application, dataSources) {
|
|
52
|
+
if (!application || typeof application !== 'object') {
|
|
53
|
+
throw new Error('Application configuration is required');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!application.key || typeof application.key !== 'string') {
|
|
57
|
+
throw new Error('Application key is required');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!Array.isArray(dataSources)) {
|
|
61
|
+
throw new Error('DataSources must be an array');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Validate each datasource has required fields
|
|
65
|
+
for (const datasource of dataSources) {
|
|
66
|
+
if (!datasource.key || typeof datasource.key !== 'string') {
|
|
67
|
+
throw new Error('Datasource key is required for all datasources');
|
|
68
|
+
}
|
|
69
|
+
if (!datasource.systemKey || typeof datasource.systemKey !== 'string') {
|
|
70
|
+
throw new Error('Datasource systemKey is required for all datasources');
|
|
71
|
+
}
|
|
72
|
+
if (datasource.systemKey !== application.key) {
|
|
73
|
+
throw new Error(`Datasource systemKey (${datasource.systemKey}) does not match application key (${application.key})`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Handles partial download errors gracefully
|
|
80
|
+
* @param {string} systemKey - System key
|
|
81
|
+
* @param {Object} systemData - System data that was successfully downloaded
|
|
82
|
+
* @param {Array<Error>} datasourceErrors - Array of errors from datasource downloads
|
|
83
|
+
* @throws {Error} Aggregated error message
|
|
84
|
+
*/
|
|
85
|
+
function handlePartialDownload(systemKey, systemData, datasourceErrors) {
|
|
86
|
+
if (datasourceErrors.length === 0) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const errorMessages = datasourceErrors.map(err => err.message).join('\n - ');
|
|
91
|
+
throw new Error(
|
|
92
|
+
`Partial download completed for system '${systemKey}', but some datasources failed:\n - ${errorMessages}\n\n` +
|
|
93
|
+
'System configuration was downloaded successfully. You may need to download datasources separately.'
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Extracts environment variables from authentication configuration
|
|
99
|
+
* @param {Object} application - External system configuration
|
|
100
|
+
* @returns {string} Environment variables template content
|
|
101
|
+
*/
|
|
102
|
+
function generateEnvTemplate(application) {
|
|
103
|
+
const lines = ['# Environment variables for external system'];
|
|
104
|
+
lines.push(`# System: ${application.key || 'unknown'}`);
|
|
105
|
+
lines.push('');
|
|
106
|
+
|
|
107
|
+
if (!application.authentication) {
|
|
108
|
+
return lines.join('\n');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const auth = application.authentication;
|
|
112
|
+
|
|
113
|
+
// OAuth2 configuration
|
|
114
|
+
if (auth.type === 'oauth2' && auth.oauth2) {
|
|
115
|
+
if (auth.oauth2.clientId && auth.oauth2.clientId.includes('{{')) {
|
|
116
|
+
const key = auth.oauth2.clientId.replace(/[{}]/g, '').trim();
|
|
117
|
+
lines.push(`${key}=kv://secrets/${application.key}/client-id`);
|
|
118
|
+
}
|
|
119
|
+
if (auth.oauth2.clientSecret && auth.oauth2.clientSecret.includes('{{')) {
|
|
120
|
+
const key = auth.oauth2.clientSecret.replace(/[{}]/g, '').trim();
|
|
121
|
+
lines.push(`${key}=kv://secrets/${application.key}/client-secret`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// API Key configuration
|
|
126
|
+
if (auth.type === 'apikey' && auth.apikey) {
|
|
127
|
+
if (auth.apikey.key && auth.apikey.key.includes('{{')) {
|
|
128
|
+
const key = auth.apikey.key.replace(/[{}]/g, '').trim();
|
|
129
|
+
lines.push(`${key}=kv://secrets/${application.key}/api-key`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Basic Auth configuration
|
|
134
|
+
if (auth.type === 'basic' && auth.basic) {
|
|
135
|
+
if (auth.basic.username && auth.basic.username.includes('{{')) {
|
|
136
|
+
const key = auth.basic.username.replace(/[{}]/g, '').trim();
|
|
137
|
+
lines.push(`${key}=kv://secrets/${application.key}/username`);
|
|
138
|
+
}
|
|
139
|
+
if (auth.basic.password && auth.basic.password.includes('{{')) {
|
|
140
|
+
const key = auth.basic.password.replace(/[{}]/g, '').trim();
|
|
141
|
+
lines.push(`${key}=kv://secrets/${application.key}/password`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return lines.join('\n');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Generates variables.yaml with externalIntegration block
|
|
150
|
+
* @param {string} systemKey - System key
|
|
151
|
+
* @param {Object} application - External system configuration
|
|
152
|
+
* @param {Array} dataSources - Array of datasource configurations
|
|
153
|
+
* @returns {Object} Variables YAML object
|
|
154
|
+
*/
|
|
155
|
+
function generateVariablesYaml(systemKey, application, dataSources) {
|
|
156
|
+
const systemFileName = `${systemKey}-deploy.json`;
|
|
157
|
+
const datasourceFiles = dataSources.map(ds => {
|
|
158
|
+
// Extract entity key from datasource key or use entityKey
|
|
159
|
+
const entityKey = ds.entityKey || ds.key.split('-').pop();
|
|
160
|
+
return `${systemKey}-deploy-${entityKey}.json`;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
name: systemKey,
|
|
165
|
+
displayName: application.displayName || systemKey,
|
|
166
|
+
description: application.description || `External system integration for ${systemKey}`,
|
|
167
|
+
externalIntegration: {
|
|
168
|
+
schemaBasePath: './',
|
|
169
|
+
systems: [systemFileName],
|
|
170
|
+
dataSources: datasourceFiles,
|
|
171
|
+
autopublish: false,
|
|
172
|
+
version: application.version || '1.0.0'
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Generates README.md with setup instructions
|
|
179
|
+
* @param {string} systemKey - System key
|
|
180
|
+
* @param {Object} application - External system configuration
|
|
181
|
+
* @param {Array} dataSources - Array of datasource configurations
|
|
182
|
+
* @returns {string} README.md content
|
|
183
|
+
*/
|
|
184
|
+
function generateReadme(systemKey, application, dataSources) {
|
|
185
|
+
const displayName = application.displayName || systemKey;
|
|
186
|
+
const description = application.description || `External system integration for ${systemKey}`;
|
|
187
|
+
const systemType = application.type || 'unknown';
|
|
188
|
+
|
|
189
|
+
const lines = [
|
|
190
|
+
`# ${displayName}`,
|
|
191
|
+
'',
|
|
192
|
+
description,
|
|
193
|
+
'',
|
|
194
|
+
'## System Information',
|
|
195
|
+
'',
|
|
196
|
+
`- **System Key**: \`${systemKey}\``,
|
|
197
|
+
`- **System Type**: \`${systemType}\``,
|
|
198
|
+
`- **Datasources**: ${dataSources.length}`,
|
|
199
|
+
'',
|
|
200
|
+
'## Files',
|
|
201
|
+
'',
|
|
202
|
+
'- `variables.yaml` - Application configuration with externalIntegration block',
|
|
203
|
+
`- \`${systemKey}-deploy.json\` - External system definition`
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
dataSources.forEach(ds => {
|
|
207
|
+
const entityKey = ds.entityKey || ds.key.split('-').pop();
|
|
208
|
+
lines.push(`- \`${systemKey}-deploy-${entityKey}.json\` - Datasource: ${ds.displayName || ds.key}`);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
lines.push(
|
|
212
|
+
'- `env.template` - Environment variables template',
|
|
213
|
+
'',
|
|
214
|
+
'## Setup Instructions',
|
|
215
|
+
'',
|
|
216
|
+
'1. Review and update configuration files as needed',
|
|
217
|
+
'2. Set up environment variables in `env.template`',
|
|
218
|
+
'3. Run unit tests: `aifabrix test ${systemKey}`',
|
|
219
|
+
'4. Run integration tests: `aifabrix test-integration ${systemKey}`',
|
|
220
|
+
'5. Deploy: `aifabrix deploy ${systemKey} --environment dev`',
|
|
221
|
+
'',
|
|
222
|
+
'## Testing',
|
|
223
|
+
'',
|
|
224
|
+
'### Unit Tests',
|
|
225
|
+
'Run local validation without API calls:',
|
|
226
|
+
'```bash',
|
|
227
|
+
`aifabrix test ${systemKey}`,
|
|
228
|
+
'```',
|
|
229
|
+
'',
|
|
230
|
+
'### Integration Tests',
|
|
231
|
+
'Run integration tests via dataplane:',
|
|
232
|
+
'```bash',
|
|
233
|
+
`aifabrix test-integration ${systemKey} --environment dev`,
|
|
234
|
+
'```',
|
|
235
|
+
'',
|
|
236
|
+
'## Deployment',
|
|
237
|
+
'',
|
|
238
|
+
'Deploy to dataplane via miso-controller:',
|
|
239
|
+
'```bash',
|
|
240
|
+
`aifabrix deploy ${systemKey} --environment dev`,
|
|
241
|
+
'```'
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
return lines.join('\n');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Downloads external system from dataplane to local development structure
|
|
249
|
+
* @async
|
|
250
|
+
* @function downloadExternalSystem
|
|
251
|
+
* @param {string} systemKey - System key or ID
|
|
252
|
+
* @param {Object} options - Download options
|
|
253
|
+
* @param {string} [options.environment] - Environment (dev, tst, pro)
|
|
254
|
+
* @param {string} [options.controller] - Controller URL
|
|
255
|
+
* @param {boolean} [options.dryRun] - Show what would be downloaded without actually downloading
|
|
256
|
+
* @returns {Promise<void>} Resolves when download completes
|
|
257
|
+
* @throws {Error} If download fails
|
|
258
|
+
*/
|
|
259
|
+
async function downloadExternalSystem(systemKey, options = {}) {
|
|
260
|
+
if (!systemKey || typeof systemKey !== 'string') {
|
|
261
|
+
throw new Error('System key is required and must be a string');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Validate system key format (alphanumeric, hyphens, underscores)
|
|
265
|
+
if (!/^[a-z0-9-_]+$/.test(systemKey)) {
|
|
266
|
+
throw new Error('System key must contain only lowercase letters, numbers, hyphens, and underscores');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
logger.log(chalk.blue(`\nš„ Downloading external system: ${systemKey}`));
|
|
271
|
+
|
|
272
|
+
// Get authentication
|
|
273
|
+
const config = await getConfig();
|
|
274
|
+
const environment = options.environment || 'dev';
|
|
275
|
+
const controllerUrl = options.controller || config.deployment?.controllerUrl || 'http://localhost:3000';
|
|
276
|
+
const authConfig = await getDeploymentAuth(controllerUrl, environment, systemKey);
|
|
277
|
+
|
|
278
|
+
if (!authConfig.token && !authConfig.clientId) {
|
|
279
|
+
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Get dataplane URL from controller
|
|
283
|
+
logger.log(chalk.blue('š Getting dataplane URL from controller...'));
|
|
284
|
+
const dataplaneUrl = await getDataplaneUrl(controllerUrl, systemKey, environment, authConfig);
|
|
285
|
+
logger.log(chalk.green(`ā Dataplane URL: ${dataplaneUrl}`));
|
|
286
|
+
|
|
287
|
+
// Download system configuration
|
|
288
|
+
// Note: Verify this endpoint exists. Alternative: GET /api/v1/pipeline/{systemIdOrKey}
|
|
289
|
+
const downloadEndpoint = `${dataplaneUrl}/api/v1/external/systems/${systemKey}/config`;
|
|
290
|
+
logger.log(chalk.blue(`š” Downloading from: ${downloadEndpoint}`));
|
|
291
|
+
|
|
292
|
+
if (options.dryRun) {
|
|
293
|
+
logger.log(chalk.yellow('š Dry run mode - would download from:'));
|
|
294
|
+
logger.log(chalk.gray(` ${downloadEndpoint}`));
|
|
295
|
+
logger.log(chalk.yellow('\nWould create:'));
|
|
296
|
+
logger.log(chalk.gray(` integration/${systemKey}/`));
|
|
297
|
+
logger.log(chalk.gray(` integration/${systemKey}/variables.yaml`));
|
|
298
|
+
logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-deploy.json`));
|
|
299
|
+
logger.log(chalk.gray(` integration/${systemKey}/env.template`));
|
|
300
|
+
logger.log(chalk.gray(` integration/${systemKey}/README.md`));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const response = await authenticatedApiCall(
|
|
305
|
+
downloadEndpoint,
|
|
306
|
+
{
|
|
307
|
+
method: 'GET'
|
|
308
|
+
},
|
|
309
|
+
authConfig.token
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
if (!response.success || !response.data) {
|
|
313
|
+
throw new Error(`Failed to download system configuration: ${response.error || response.formattedError || 'Unknown error'}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const downloadData = response.data.data || response.data;
|
|
317
|
+
const application = downloadData.application;
|
|
318
|
+
const dataSources = downloadData.dataSources || [];
|
|
319
|
+
|
|
320
|
+
if (!application) {
|
|
321
|
+
throw new Error('Application configuration not found in download response');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Validate downloaded data
|
|
325
|
+
logger.log(chalk.blue('š Validating downloaded data...'));
|
|
326
|
+
validateDownloadedData(application, dataSources);
|
|
327
|
+
const systemType = validateSystemType(application);
|
|
328
|
+
logger.log(chalk.green(`ā System type: ${systemType}`));
|
|
329
|
+
logger.log(chalk.green(`ā Found ${dataSources.length} datasource(s)`));
|
|
330
|
+
|
|
331
|
+
// Create temporary folder for validation
|
|
332
|
+
const tempDir = path.join(os.tmpdir(), `aifabrix-download-${systemKey}-${Date.now()}`);
|
|
333
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
// Generate files in temporary folder first
|
|
337
|
+
const systemFileName = `${systemKey}-deploy.json`;
|
|
338
|
+
const systemFilePath = path.join(tempDir, systemFileName);
|
|
339
|
+
await fs.writeFile(systemFilePath, JSON.stringify(application, null, 2), 'utf8');
|
|
340
|
+
|
|
341
|
+
// Generate datasource files
|
|
342
|
+
const datasourceErrors = [];
|
|
343
|
+
const datasourceFiles = [];
|
|
344
|
+
for (const datasource of dataSources) {
|
|
345
|
+
try {
|
|
346
|
+
const entityKey = datasource.entityKey || datasource.key.split('-').pop();
|
|
347
|
+
const datasourceFileName = `${systemKey}-deploy-${entityKey}.json`;
|
|
348
|
+
const datasourceFilePath = path.join(tempDir, datasourceFileName);
|
|
349
|
+
await fs.writeFile(datasourceFilePath, JSON.stringify(datasource, null, 2), 'utf8');
|
|
350
|
+
datasourceFiles.push(datasourceFilePath);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
datasourceErrors.push(new Error(`Failed to write datasource ${datasource.key}: ${error.message}`));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Handle partial downloads
|
|
357
|
+
if (datasourceErrors.length > 0) {
|
|
358
|
+
handlePartialDownload(systemKey, application, datasourceErrors);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Generate variables.yaml
|
|
362
|
+
const variables = generateVariablesYaml(systemKey, application, dataSources);
|
|
363
|
+
const variablesPath = path.join(tempDir, 'variables.yaml');
|
|
364
|
+
await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }), 'utf8');
|
|
365
|
+
|
|
366
|
+
// Generate env.template
|
|
367
|
+
const envTemplate = generateEnvTemplate(application);
|
|
368
|
+
const envTemplatePath = path.join(tempDir, 'env.template');
|
|
369
|
+
await fs.writeFile(envTemplatePath, envTemplate, 'utf8');
|
|
370
|
+
|
|
371
|
+
// Generate README.md
|
|
372
|
+
const readme = generateReadme(systemKey, application, dataSources);
|
|
373
|
+
const readmePath = path.join(tempDir, 'README.md');
|
|
374
|
+
await fs.writeFile(readmePath, readme, 'utf8');
|
|
375
|
+
|
|
376
|
+
// Determine final destination (integration folder)
|
|
377
|
+
const { appPath } = await detectAppType(systemKey);
|
|
378
|
+
const finalPath = appPath || path.join(process.cwd(), 'integration', systemKey);
|
|
379
|
+
|
|
380
|
+
// Create final directory
|
|
381
|
+
await fs.mkdir(finalPath, { recursive: true });
|
|
382
|
+
|
|
383
|
+
// Move files from temp to final location
|
|
384
|
+
logger.log(chalk.blue(`š Creating directory: ${finalPath}`));
|
|
385
|
+
const filesToMove = [
|
|
386
|
+
{ from: systemFilePath, to: path.join(finalPath, systemFileName) },
|
|
387
|
+
{ from: variablesPath, to: path.join(finalPath, 'variables.yaml') },
|
|
388
|
+
{ from: envTemplatePath, to: path.join(finalPath, 'env.template') },
|
|
389
|
+
{ from: readmePath, to: path.join(finalPath, 'README.md') }
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
for (const dsFile of datasourceFiles) {
|
|
393
|
+
const fileName = path.basename(dsFile);
|
|
394
|
+
filesToMove.push({ from: dsFile, to: path.join(finalPath, fileName) });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
for (const file of filesToMove) {
|
|
398
|
+
await fs.copyFile(file.from, file.to);
|
|
399
|
+
logger.log(chalk.green(`ā Created: ${path.relative(process.cwd(), file.to)}`));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Clean up temporary folder
|
|
403
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
404
|
+
|
|
405
|
+
logger.log(chalk.green('\nā
External system downloaded successfully!'));
|
|
406
|
+
logger.log(chalk.blue(`Location: ${finalPath}`));
|
|
407
|
+
logger.log(chalk.blue(`System: ${systemKey}`));
|
|
408
|
+
logger.log(chalk.blue(`Datasources: ${dataSources.length}`));
|
|
409
|
+
} catch (error) {
|
|
410
|
+
// Clean up temporary folder on error
|
|
411
|
+
try {
|
|
412
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
413
|
+
} catch {
|
|
414
|
+
// Ignore cleanup errors
|
|
415
|
+
}
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
418
|
+
} catch (error) {
|
|
419
|
+
throw new Error(`Failed to download external system: ${error.message}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
module.exports = {
|
|
424
|
+
downloadExternalSystem,
|
|
425
|
+
validateSystemType,
|
|
426
|
+
validateDownloadedData,
|
|
427
|
+
generateVariablesYaml,
|
|
428
|
+
generateEnvTemplate,
|
|
429
|
+
generateReadme,
|
|
430
|
+
handlePartialDownload
|
|
431
|
+
};
|
|
@@ -44,10 +44,10 @@ async function generateExternalSystemTemplate(appPath, systemKey, config) {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
const rendered = template(context);
|
|
47
|
-
const schemasDir = path.join(appPath, 'schemas');
|
|
48
|
-
await fs.mkdir(schemasDir, { recursive: true });
|
|
49
47
|
|
|
50
|
-
|
|
48
|
+
// Generate in same folder as variables.yaml (new structure)
|
|
49
|
+
// Use naming: <app-name>-deploy.json
|
|
50
|
+
const outputPath = path.join(appPath, `${systemKey}-deploy.json`);
|
|
51
51
|
await fs.writeFile(outputPath, rendered, 'utf8');
|
|
52
52
|
|
|
53
53
|
return outputPath;
|
|
@@ -83,10 +83,10 @@ async function generateExternalDataSourceTemplate(appPath, datasourceKey, config
|
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
const rendered = template(context);
|
|
86
|
-
const schemasDir = path.join(appPath, 'schemas');
|
|
87
|
-
await fs.mkdir(schemasDir, { recursive: true });
|
|
88
86
|
|
|
89
|
-
|
|
87
|
+
// Generate in same folder as variables.yaml (new structure)
|
|
88
|
+
// Use naming: <app-name>-deploy-<datasource-key>.json
|
|
89
|
+
const outputPath = path.join(appPath, `${datasourceKey}-deploy.json`);
|
|
90
90
|
await fs.writeFile(outputPath, rendered, 'utf8');
|
|
91
91
|
|
|
92
92
|
return outputPath;
|
|
@@ -120,7 +120,8 @@ async function generateExternalSystemFiles(appPath, appName, config) {
|
|
|
120
120
|
|
|
121
121
|
for (let i = 0; i < datasourceCount; i++) {
|
|
122
122
|
const entityKey = `entity${i + 1}`;
|
|
123
|
-
|
|
123
|
+
// For datasource key, use just the entity key (will be prefixed with app-name-deploy-)
|
|
124
|
+
const datasourceKey = entityKey;
|
|
124
125
|
const resourceType = resourceTypes[i % resourceTypes.length];
|
|
125
126
|
|
|
126
127
|
const datasourceConfig = {
|
|
@@ -132,7 +133,8 @@ async function generateExternalSystemFiles(appPath, appName, config) {
|
|
|
132
133
|
datasourceDescription: `External datasource for ${entityKey} entity`
|
|
133
134
|
};
|
|
134
135
|
|
|
135
|
-
|
|
136
|
+
// Generate with full naming: <app-name>-deploy-<entity-key>.json
|
|
137
|
+
const datasourcePath = await generateExternalDataSourceTemplate(appPath, `${systemKey}-deploy-${datasourceKey}`, datasourceConfig);
|
|
136
138
|
datasourcePaths.push(datasourcePath);
|
|
137
139
|
logger.log(chalk.green(`ā Generated datasource: ${path.basename(datasourcePath)}`));
|
|
138
140
|
}
|
|
@@ -165,9 +167,10 @@ async function updateVariablesYamlWithExternalIntegration(appPath, systemKey, da
|
|
|
165
167
|
const variables = yaml.load(variablesContent);
|
|
166
168
|
|
|
167
169
|
// Add externalIntegration block
|
|
170
|
+
// Files are in same folder, so schemaBasePath is './'
|
|
168
171
|
variables.externalIntegration = {
|
|
169
|
-
schemaBasePath: './
|
|
170
|
-
systems: [`${systemKey}.json`],
|
|
172
|
+
schemaBasePath: './',
|
|
173
|
+
systems: [`${systemKey}-deploy.json`],
|
|
171
174
|
dataSources: datasourcePaths.map(p => path.basename(p)),
|
|
172
175
|
autopublish: true,
|
|
173
176
|
version: '1.0.0'
|