@aifabrix/builder 2.22.1 ā 2.31.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/jest.config.coverage.js +37 -0
- package/lib/api/pipeline.api.js +10 -9
- package/lib/app-deploy.js +36 -14
- package/lib/app-list.js +191 -71
- package/lib/app-prompts.js +77 -26
- package/lib/app-readme.js +123 -5
- package/lib/app-rotate-secret.js +101 -57
- package/lib/app-run-helpers.js +200 -172
- package/lib/app-run.js +137 -68
- package/lib/audit-logger.js +8 -7
- package/lib/build.js +161 -250
- package/lib/cli.js +73 -65
- package/lib/commands/login.js +45 -31
- package/lib/commands/logout.js +181 -0
- package/lib/commands/secrets-set.js +2 -2
- package/lib/commands/secure.js +61 -26
- package/lib/config.js +79 -45
- package/lib/datasource-deploy.js +89 -29
- package/lib/deployer.js +164 -129
- package/lib/diff.js +63 -21
- package/lib/environment-deploy.js +36 -19
- package/lib/external-system-deploy.js +134 -66
- package/lib/external-system-download.js +244 -171
- package/lib/external-system-test.js +199 -164
- package/lib/generator-external.js +145 -72
- package/lib/generator-helpers.js +49 -17
- package/lib/generator-split.js +105 -58
- package/lib/infra.js +101 -131
- package/lib/schema/application-schema.json +895 -896
- package/lib/schema/env-config.yaml +11 -4
- package/lib/template-validator.js +13 -4
- package/lib/utils/api.js +8 -8
- package/lib/utils/app-register-auth.js +36 -18
- package/lib/utils/app-run-containers.js +140 -0
- package/lib/utils/auth-headers.js +6 -6
- package/lib/utils/build-copy.js +60 -2
- package/lib/utils/build-helpers.js +94 -0
- package/lib/utils/cli-utils.js +177 -76
- package/lib/utils/compose-generator.js +12 -2
- package/lib/utils/config-tokens.js +151 -9
- package/lib/utils/deployment-errors.js +137 -69
- package/lib/utils/deployment-validation-helpers.js +103 -0
- package/lib/utils/docker-build.js +57 -0
- package/lib/utils/dockerfile-utils.js +13 -3
- package/lib/utils/env-copy.js +163 -94
- package/lib/utils/env-map.js +226 -86
- package/lib/utils/environment-checker.js +2 -2
- package/lib/utils/error-formatters/network-errors.js +0 -1
- package/lib/utils/external-system-display.js +14 -19
- package/lib/utils/external-system-env-helpers.js +107 -0
- package/lib/utils/external-system-test-helpers.js +144 -0
- package/lib/utils/health-check.js +10 -8
- package/lib/utils/infra-status.js +123 -0
- package/lib/utils/local-secrets.js +3 -2
- package/lib/utils/paths.js +228 -49
- package/lib/utils/schema-loader.js +125 -57
- package/lib/utils/token-manager.js +10 -7
- package/lib/utils/yaml-preserve.js +55 -16
- package/lib/validate.js +87 -89
- package/package.json +4 -4
- package/scripts/ci-fix.sh +19 -0
- package/scripts/ci-simulate.sh +19 -0
- package/templates/applications/miso-controller/test.yaml +1 -0
- package/templates/python/Dockerfile.hbs +8 -45
- package/templates/typescript/Dockerfile.hbs +8 -42
|
@@ -20,6 +20,7 @@ const { getDataplaneUrl } = require('./datasource-deploy');
|
|
|
20
20
|
const { getConfig } = require('./config');
|
|
21
21
|
const { detectAppType } = require('./utils/paths');
|
|
22
22
|
const logger = require('./utils/logger');
|
|
23
|
+
const { generateEnvTemplate } = require('./utils/external-system-env-helpers');
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Validates system type from downloaded application
|
|
@@ -95,55 +96,11 @@ function handlePartialDownload(systemKey, systemData, datasourceErrors) {
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
/**
|
|
98
|
-
*
|
|
99
|
-
* @param {Object}
|
|
100
|
-
* @
|
|
99
|
+
* Extract OAuth2 environment variables
|
|
100
|
+
* @param {Object} oauth2 - OAuth2 configuration
|
|
101
|
+
* @param {string} systemKey - System key
|
|
102
|
+
* @param {Array<string>} lines - Lines array to append to
|
|
101
103
|
*/
|
|
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
104
|
|
|
148
105
|
/**
|
|
149
106
|
* Generates variables.yaml with externalIntegration block
|
|
@@ -244,6 +201,235 @@ function generateReadme(systemKey, application, dataSources) {
|
|
|
244
201
|
return lines.join('\n');
|
|
245
202
|
}
|
|
246
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Setup authentication and get dataplane URL
|
|
206
|
+
* @async
|
|
207
|
+
* @param {string} systemKey - System key
|
|
208
|
+
* @param {Object} options - Download options
|
|
209
|
+
* @param {Object} config - Configuration object
|
|
210
|
+
* @returns {Promise<Object>} Object with authConfig and dataplaneUrl
|
|
211
|
+
* @throws {Error} If authentication fails
|
|
212
|
+
*/
|
|
213
|
+
async function setupAuthenticationAndDataplane(systemKey, options, config) {
|
|
214
|
+
const environment = options.environment || 'dev';
|
|
215
|
+
const controllerUrl = options.controller || config.deployment?.controllerUrl || 'http://localhost:3000';
|
|
216
|
+
const authConfig = await getDeploymentAuth(controllerUrl, environment, systemKey);
|
|
217
|
+
|
|
218
|
+
if (!authConfig.token && !authConfig.clientId) {
|
|
219
|
+
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
logger.log(chalk.blue('š Getting dataplane URL from controller...'));
|
|
223
|
+
const dataplaneUrl = await getDataplaneUrl(controllerUrl, systemKey, environment, authConfig);
|
|
224
|
+
logger.log(chalk.green(`ā Dataplane URL: ${dataplaneUrl}`));
|
|
225
|
+
|
|
226
|
+
return { authConfig, dataplaneUrl };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Download system configuration from dataplane
|
|
231
|
+
* @async
|
|
232
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
233
|
+
* @param {string} systemKey - System key
|
|
234
|
+
* @param {Object} authConfig - Authentication configuration
|
|
235
|
+
* @returns {Promise<Object>} Object with application and dataSources
|
|
236
|
+
* @throws {Error} If download fails
|
|
237
|
+
*/
|
|
238
|
+
async function downloadSystemConfiguration(dataplaneUrl, systemKey, authConfig) {
|
|
239
|
+
logger.log(chalk.blue(`š” Downloading system configuration: ${systemKey}`));
|
|
240
|
+
const response = await getExternalSystemConfig(dataplaneUrl, systemKey, authConfig);
|
|
241
|
+
|
|
242
|
+
if (!response.success || !response.data) {
|
|
243
|
+
throw new Error(`Failed to download system configuration: ${response.error || response.formattedError || 'Unknown error'}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const downloadData = response.data.data || response.data;
|
|
247
|
+
const application = downloadData.application;
|
|
248
|
+
const dataSources = downloadData.dataSources || [];
|
|
249
|
+
|
|
250
|
+
if (!application) {
|
|
251
|
+
throw new Error('Application configuration not found in download response');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { application, dataSources };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Generate files in temporary directory
|
|
259
|
+
* @async
|
|
260
|
+
* @param {string} tempDir - Temporary directory path
|
|
261
|
+
* @param {string} systemKey - System key
|
|
262
|
+
* @param {Object} application - Application configuration
|
|
263
|
+
* @param {Array} dataSources - Array of datasource configurations
|
|
264
|
+
* @returns {Promise<Object>} Object with file paths
|
|
265
|
+
* @throws {Error} If file generation fails
|
|
266
|
+
*/
|
|
267
|
+
async function generateFilesInTempDir(tempDir, systemKey, application, dataSources) {
|
|
268
|
+
const systemFileName = `${systemKey}-deploy.json`;
|
|
269
|
+
const systemFilePath = path.join(tempDir, systemFileName);
|
|
270
|
+
await fs.writeFile(systemFilePath, JSON.stringify(application, null, 2), 'utf8');
|
|
271
|
+
|
|
272
|
+
// Generate datasource files
|
|
273
|
+
const datasourceErrors = [];
|
|
274
|
+
const datasourceFiles = [];
|
|
275
|
+
for (const datasource of dataSources) {
|
|
276
|
+
try {
|
|
277
|
+
const entityKey = datasource.entityKey || datasource.key.split('-').pop();
|
|
278
|
+
const datasourceFileName = `${systemKey}-deploy-${entityKey}.json`;
|
|
279
|
+
const datasourceFilePath = path.join(tempDir, datasourceFileName);
|
|
280
|
+
await fs.writeFile(datasourceFilePath, JSON.stringify(datasource, null, 2), 'utf8');
|
|
281
|
+
datasourceFiles.push(datasourceFilePath);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
datasourceErrors.push(new Error(`Failed to write datasource ${datasource.key}: ${error.message}`));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Handle partial downloads
|
|
288
|
+
if (datasourceErrors.length > 0) {
|
|
289
|
+
handlePartialDownload(systemKey, application, datasourceErrors);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Generate variables.yaml
|
|
293
|
+
const variables = generateVariablesYaml(systemKey, application, dataSources);
|
|
294
|
+
const variablesPath = path.join(tempDir, 'variables.yaml');
|
|
295
|
+
await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }), 'utf8');
|
|
296
|
+
|
|
297
|
+
// Generate env.template
|
|
298
|
+
const envTemplate = generateEnvTemplate(application);
|
|
299
|
+
const envTemplatePath = path.join(tempDir, 'env.template');
|
|
300
|
+
await fs.writeFile(envTemplatePath, envTemplate, 'utf8');
|
|
301
|
+
|
|
302
|
+
// Generate README.md
|
|
303
|
+
const readme = generateReadme(systemKey, application, dataSources);
|
|
304
|
+
const readmePath = path.join(tempDir, 'README.md');
|
|
305
|
+
await fs.writeFile(readmePath, readme, 'utf8');
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
systemFilePath,
|
|
309
|
+
variablesPath,
|
|
310
|
+
envTemplatePath,
|
|
311
|
+
readmePath,
|
|
312
|
+
datasourceFiles
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Move files from temporary directory to final location
|
|
318
|
+
* @async
|
|
319
|
+
* @param {string} tempDir - Temporary directory path
|
|
320
|
+
* @param {string} finalPath - Final destination path
|
|
321
|
+
* @param {string} systemKey - System key
|
|
322
|
+
* @param {Object} filePaths - Object with file paths
|
|
323
|
+
* @throws {Error} If file move fails
|
|
324
|
+
*/
|
|
325
|
+
async function moveFilesToFinalLocation(tempDir, finalPath, systemKey, filePaths) {
|
|
326
|
+
logger.log(chalk.blue(`š Creating directory: ${finalPath}`));
|
|
327
|
+
await fs.mkdir(finalPath, { recursive: true });
|
|
328
|
+
|
|
329
|
+
const systemFileName = `${systemKey}-deploy.json`;
|
|
330
|
+
const filesToMove = [
|
|
331
|
+
{ from: filePaths.systemFilePath, to: path.join(finalPath, systemFileName) },
|
|
332
|
+
{ from: filePaths.variablesPath, to: path.join(finalPath, 'variables.yaml') },
|
|
333
|
+
{ from: filePaths.envTemplatePath, to: path.join(finalPath, 'env.template') },
|
|
334
|
+
{ from: filePaths.readmePath, to: path.join(finalPath, 'README.md') }
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
for (const dsFile of filePaths.datasourceFiles) {
|
|
338
|
+
const fileName = path.basename(dsFile);
|
|
339
|
+
filesToMove.push({ from: dsFile, to: path.join(finalPath, fileName) });
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
for (const file of filesToMove) {
|
|
343
|
+
await fs.copyFile(file.from, file.to);
|
|
344
|
+
logger.log(chalk.green(`ā Created: ${path.relative(process.cwd(), file.to)}`));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Validate system key format
|
|
350
|
+
* @param {string} systemKey - System key to validate
|
|
351
|
+
* @throws {Error} If system key format is invalid
|
|
352
|
+
*/
|
|
353
|
+
function validateSystemKeyFormat(systemKey) {
|
|
354
|
+
if (!systemKey || typeof systemKey !== 'string') {
|
|
355
|
+
throw new Error('System key is required and must be a string');
|
|
356
|
+
}
|
|
357
|
+
if (!/^[a-z0-9-_]+$/.test(systemKey)) {
|
|
358
|
+
throw new Error('System key must contain only lowercase letters, numbers, hyphens, and underscores');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Handle dry run mode
|
|
364
|
+
* @param {string} systemKey - System key
|
|
365
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
366
|
+
*/
|
|
367
|
+
function handleDryRun(systemKey, dataplaneUrl) {
|
|
368
|
+
logger.log(chalk.yellow('š Dry run mode - would download from:'));
|
|
369
|
+
logger.log(chalk.gray(` ${dataplaneUrl}/api/v1/external/systems/${systemKey}/config`));
|
|
370
|
+
logger.log(chalk.yellow('\nWould create:'));
|
|
371
|
+
logger.log(chalk.gray(` integration/${systemKey}/`));
|
|
372
|
+
logger.log(chalk.gray(` integration/${systemKey}/variables.yaml`));
|
|
373
|
+
logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-deploy.json`));
|
|
374
|
+
logger.log(chalk.gray(` integration/${systemKey}/env.template`));
|
|
375
|
+
logger.log(chalk.gray(` integration/${systemKey}/README.md`));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Validate and log downloaded data
|
|
380
|
+
* @param {Object} application - Application configuration
|
|
381
|
+
* @param {Array} dataSources - Array of datasource configurations
|
|
382
|
+
* @returns {string} System type
|
|
383
|
+
*/
|
|
384
|
+
function validateAndLogDownloadedData(application, dataSources) {
|
|
385
|
+
logger.log(chalk.blue('š Validating downloaded data...'));
|
|
386
|
+
validateDownloadedData(application, dataSources);
|
|
387
|
+
const systemType = validateSystemType(application);
|
|
388
|
+
logger.log(chalk.green(`ā System type: ${systemType}`));
|
|
389
|
+
logger.log(chalk.green(`ā Found ${dataSources.length} datasource(s)`));
|
|
390
|
+
return systemType;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Process downloaded system (generate files, move, cleanup)
|
|
395
|
+
* @async
|
|
396
|
+
* @param {string} systemKey - System key
|
|
397
|
+
* @param {Object} application - Application configuration
|
|
398
|
+
* @param {Array} dataSources - Array of datasource configurations
|
|
399
|
+
* @param {string} tempDir - Temporary directory path
|
|
400
|
+
* @returns {Promise<string>} Final destination path
|
|
401
|
+
* @throws {Error} If processing fails
|
|
402
|
+
*/
|
|
403
|
+
async function processDownloadedSystem(systemKey, application, dataSources, tempDir) {
|
|
404
|
+
// Generate files in temporary folder first
|
|
405
|
+
const filePaths = await generateFilesInTempDir(tempDir, systemKey, application, dataSources);
|
|
406
|
+
|
|
407
|
+
// Determine final destination (integration folder)
|
|
408
|
+
const { appPath } = await detectAppType(systemKey);
|
|
409
|
+
const finalPath = appPath || path.join(process.cwd(), 'integration', systemKey);
|
|
410
|
+
|
|
411
|
+
// Move files from temp to final location
|
|
412
|
+
await moveFilesToFinalLocation(tempDir, finalPath, systemKey, filePaths);
|
|
413
|
+
|
|
414
|
+
// Clean up temporary folder
|
|
415
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
416
|
+
|
|
417
|
+
return finalPath;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Display download success message
|
|
422
|
+
* @param {string} systemKey - System key
|
|
423
|
+
* @param {string} finalPath - Final destination path
|
|
424
|
+
* @param {number} datasourceCount - Number of datasources
|
|
425
|
+
*/
|
|
426
|
+
function displayDownloadSuccess(systemKey, finalPath, datasourceCount) {
|
|
427
|
+
logger.log(chalk.green('\nā
External system downloaded successfully!'));
|
|
428
|
+
logger.log(chalk.blue(`Location: ${finalPath}`));
|
|
429
|
+
logger.log(chalk.blue(`System: ${systemKey}`));
|
|
430
|
+
logger.log(chalk.blue(`Datasources: ${datasourceCount}`));
|
|
431
|
+
}
|
|
432
|
+
|
|
247
433
|
/**
|
|
248
434
|
* Downloads external system from dataplane to local development structure
|
|
249
435
|
* @async
|
|
@@ -257,147 +443,34 @@ function generateReadme(systemKey, application, dataSources) {
|
|
|
257
443
|
* @throws {Error} If download fails
|
|
258
444
|
*/
|
|
259
445
|
async function downloadExternalSystem(systemKey, options = {}) {
|
|
260
|
-
|
|
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
|
-
}
|
|
446
|
+
validateSystemKeyFormat(systemKey);
|
|
268
447
|
|
|
269
448
|
try {
|
|
270
449
|
logger.log(chalk.blue(`\nš„ Downloading external system: ${systemKey}`));
|
|
271
450
|
|
|
272
|
-
// Get authentication
|
|
451
|
+
// Get authentication and dataplane URL
|
|
273
452
|
const config = await getConfig();
|
|
274
|
-
const
|
|
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 using centralized API client
|
|
288
|
-
logger.log(chalk.blue(`š” Downloading system configuration: ${systemKey}`));
|
|
453
|
+
const { authConfig, dataplaneUrl } = await setupAuthenticationAndDataplane(systemKey, options, config);
|
|
289
454
|
|
|
455
|
+
// Handle dry run
|
|
290
456
|
if (options.dryRun) {
|
|
291
|
-
|
|
292
|
-
logger.log(chalk.gray(` ${dataplaneUrl}/api/v1/external/systems/${systemKey}/config`));
|
|
293
|
-
logger.log(chalk.yellow('\nWould create:'));
|
|
294
|
-
logger.log(chalk.gray(` integration/${systemKey}/`));
|
|
295
|
-
logger.log(chalk.gray(` integration/${systemKey}/variables.yaml`));
|
|
296
|
-
logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-deploy.json`));
|
|
297
|
-
logger.log(chalk.gray(` integration/${systemKey}/env.template`));
|
|
298
|
-
logger.log(chalk.gray(` integration/${systemKey}/README.md`));
|
|
457
|
+
handleDryRun(systemKey, dataplaneUrl);
|
|
299
458
|
return;
|
|
300
459
|
}
|
|
301
460
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (!response.success || !response.data) {
|
|
305
|
-
throw new Error(`Failed to download system configuration: ${response.error || response.formattedError || 'Unknown error'}`);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const downloadData = response.data.data || response.data;
|
|
309
|
-
const application = downloadData.application;
|
|
310
|
-
const dataSources = downloadData.dataSources || [];
|
|
311
|
-
|
|
312
|
-
if (!application) {
|
|
313
|
-
throw new Error('Application configuration not found in download response');
|
|
314
|
-
}
|
|
461
|
+
// Download system configuration
|
|
462
|
+
const { application, dataSources } = await downloadSystemConfiguration(dataplaneUrl, systemKey, authConfig);
|
|
315
463
|
|
|
316
464
|
// Validate downloaded data
|
|
317
|
-
|
|
318
|
-
validateDownloadedData(application, dataSources);
|
|
319
|
-
const systemType = validateSystemType(application);
|
|
320
|
-
logger.log(chalk.green(`ā System type: ${systemType}`));
|
|
321
|
-
logger.log(chalk.green(`ā Found ${dataSources.length} datasource(s)`));
|
|
465
|
+
validateAndLogDownloadedData(application, dataSources);
|
|
322
466
|
|
|
323
467
|
// Create temporary folder for validation
|
|
324
468
|
const tempDir = path.join(os.tmpdir(), `aifabrix-download-${systemKey}-${Date.now()}`);
|
|
325
469
|
await fs.mkdir(tempDir, { recursive: true });
|
|
326
470
|
|
|
327
471
|
try {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const systemFilePath = path.join(tempDir, systemFileName);
|
|
331
|
-
await fs.writeFile(systemFilePath, JSON.stringify(application, null, 2), 'utf8');
|
|
332
|
-
|
|
333
|
-
// Generate datasource files
|
|
334
|
-
const datasourceErrors = [];
|
|
335
|
-
const datasourceFiles = [];
|
|
336
|
-
for (const datasource of dataSources) {
|
|
337
|
-
try {
|
|
338
|
-
const entityKey = datasource.entityKey || datasource.key.split('-').pop();
|
|
339
|
-
const datasourceFileName = `${systemKey}-deploy-${entityKey}.json`;
|
|
340
|
-
const datasourceFilePath = path.join(tempDir, datasourceFileName);
|
|
341
|
-
await fs.writeFile(datasourceFilePath, JSON.stringify(datasource, null, 2), 'utf8');
|
|
342
|
-
datasourceFiles.push(datasourceFilePath);
|
|
343
|
-
} catch (error) {
|
|
344
|
-
datasourceErrors.push(new Error(`Failed to write datasource ${datasource.key}: ${error.message}`));
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Handle partial downloads
|
|
349
|
-
if (datasourceErrors.length > 0) {
|
|
350
|
-
handlePartialDownload(systemKey, application, datasourceErrors);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Generate variables.yaml
|
|
354
|
-
const variables = generateVariablesYaml(systemKey, application, dataSources);
|
|
355
|
-
const variablesPath = path.join(tempDir, 'variables.yaml');
|
|
356
|
-
await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }), 'utf8');
|
|
357
|
-
|
|
358
|
-
// Generate env.template
|
|
359
|
-
const envTemplate = generateEnvTemplate(application);
|
|
360
|
-
const envTemplatePath = path.join(tempDir, 'env.template');
|
|
361
|
-
await fs.writeFile(envTemplatePath, envTemplate, 'utf8');
|
|
362
|
-
|
|
363
|
-
// Generate README.md
|
|
364
|
-
const readme = generateReadme(systemKey, application, dataSources);
|
|
365
|
-
const readmePath = path.join(tempDir, 'README.md');
|
|
366
|
-
await fs.writeFile(readmePath, readme, 'utf8');
|
|
367
|
-
|
|
368
|
-
// Determine final destination (integration folder)
|
|
369
|
-
const { appPath } = await detectAppType(systemKey);
|
|
370
|
-
const finalPath = appPath || path.join(process.cwd(), 'integration', systemKey);
|
|
371
|
-
|
|
372
|
-
// Create final directory
|
|
373
|
-
await fs.mkdir(finalPath, { recursive: true });
|
|
374
|
-
|
|
375
|
-
// Move files from temp to final location
|
|
376
|
-
logger.log(chalk.blue(`š Creating directory: ${finalPath}`));
|
|
377
|
-
const filesToMove = [
|
|
378
|
-
{ from: systemFilePath, to: path.join(finalPath, systemFileName) },
|
|
379
|
-
{ from: variablesPath, to: path.join(finalPath, 'variables.yaml') },
|
|
380
|
-
{ from: envTemplatePath, to: path.join(finalPath, 'env.template') },
|
|
381
|
-
{ from: readmePath, to: path.join(finalPath, 'README.md') }
|
|
382
|
-
];
|
|
383
|
-
|
|
384
|
-
for (const dsFile of datasourceFiles) {
|
|
385
|
-
const fileName = path.basename(dsFile);
|
|
386
|
-
filesToMove.push({ from: dsFile, to: path.join(finalPath, fileName) });
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
for (const file of filesToMove) {
|
|
390
|
-
await fs.copyFile(file.from, file.to);
|
|
391
|
-
logger.log(chalk.green(`ā Created: ${path.relative(process.cwd(), file.to)}`));
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Clean up temporary folder
|
|
395
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
396
|
-
|
|
397
|
-
logger.log(chalk.green('\nā
External system downloaded successfully!'));
|
|
398
|
-
logger.log(chalk.blue(`Location: ${finalPath}`));
|
|
399
|
-
logger.log(chalk.blue(`System: ${systemKey}`));
|
|
400
|
-
logger.log(chalk.blue(`Datasources: ${dataSources.length}`));
|
|
472
|
+
const finalPath = await processDownloadedSystem(systemKey, application, dataSources, tempDir);
|
|
473
|
+
displayDownloadSuccess(systemKey, finalPath, dataSources.length);
|
|
401
474
|
} catch (error) {
|
|
402
475
|
// Clean up temporary folder on error
|
|
403
476
|
try {
|