@aifabrix/builder 2.31.1 → 2.32.2

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.
Files changed (118) hide show
  1. package/README.md +9 -9
  2. package/integration/hubspot/README.md +2 -2
  3. package/integration/hubspot/hubspot-deploy-company.json +17 -14
  4. package/integration/hubspot/hubspot-deploy-contact.json +19 -16
  5. package/integration/hubspot/hubspot-deploy-deal.json +21 -18
  6. package/lib/api/types/datasources.types.js +31 -5
  7. package/lib/api/types/wizard.types.js +142 -0
  8. package/lib/api/wizard.api.js +177 -0
  9. package/lib/{app-config.js → app/config.js} +4 -4
  10. package/lib/{app-deploy.js → app/deploy.js} +8 -8
  11. package/lib/app/display.js +90 -0
  12. package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
  13. package/lib/{app-down.js → app/down.js} +4 -4
  14. package/lib/app/helpers.js +218 -0
  15. package/lib/app/index.js +298 -0
  16. package/lib/{app-list.js → app/list.js} +6 -6
  17. package/lib/{app-push.js → app/push.js} +4 -4
  18. package/lib/{app-readme.js → app/readme.js} +34 -13
  19. package/lib/{app-register.js → app/register.js} +9 -9
  20. package/lib/{app-rotate-secret.js → app/rotate-secret.js} +10 -10
  21. package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
  22. package/lib/{app-run.js → app/run.js} +6 -6
  23. package/lib/{build.js → build/index.js} +59 -32
  24. package/lib/build/package.json +7 -0
  25. package/lib/cli.js +245 -179
  26. package/lib/commands/app.js +3 -3
  27. package/lib/commands/datasource.js +4 -4
  28. package/lib/commands/login-credentials.js +209 -0
  29. package/lib/commands/login-device.js +254 -0
  30. package/lib/commands/login.js +67 -378
  31. package/lib/commands/logout.js +1 -1
  32. package/lib/commands/secrets-set.js +1 -1
  33. package/lib/commands/secure.js +2 -2
  34. package/lib/commands/wizard.js +498 -0
  35. package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
  36. package/lib/{config.js → core/config.js} +28 -26
  37. package/lib/{diff.js → core/diff.js} +157 -72
  38. package/lib/{secrets.js → core/secrets.js} +86 -49
  39. package/lib/{templates.js → core/templates-env.js} +14 -222
  40. package/lib/core/templates.js +279 -0
  41. package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
  42. package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
  43. package/lib/datasource/list.js +223 -0
  44. package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
  45. package/lib/{deployer.js → deployment/deployer.js} +48 -18
  46. package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
  47. package/lib/{push.js → deployment/push.js} +1 -1
  48. package/lib/external-system/deploy-helpers.js +145 -0
  49. package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
  50. package/lib/external-system/download-helpers.js +114 -0
  51. package/lib/{external-system-download.js → external-system/download.js} +92 -135
  52. package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
  53. package/lib/external-system/test-auth.js +40 -0
  54. package/lib/external-system/test-execution.js +84 -0
  55. package/lib/external-system/test-helpers.js +109 -0
  56. package/lib/{external-system-test.js → external-system/test.js} +174 -192
  57. package/lib/{generator-builders.js → generator/builders.js} +87 -10
  58. package/lib/{generator-external.js → generator/external.js} +115 -52
  59. package/lib/{github-generator.js → generator/github.js} +116 -15
  60. package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
  61. package/lib/{generator.js → generator/index.js} +49 -22
  62. package/lib/{generator-split.js → generator/split.js} +108 -55
  63. package/lib/generator/wizard-prompts.js +357 -0
  64. package/lib/generator/wizard.js +490 -0
  65. package/lib/{infra.js → infrastructure/index.js} +49 -22
  66. package/lib/schema/external-datasource.schema.json +158 -136
  67. package/lib/schema/external-system.schema.json +43 -1
  68. package/lib/utils/api.js +9 -5
  69. package/lib/utils/app-register-api.js +60 -32
  70. package/lib/utils/app-register-auth.js +172 -47
  71. package/lib/utils/app-register-config.js +130 -59
  72. package/lib/utils/app-run-containers.js +29 -8
  73. package/lib/utils/build-helpers.js +1 -1
  74. package/lib/utils/cli-utils.js +78 -30
  75. package/lib/utils/compose-generator.js +145 -65
  76. package/lib/utils/config-paths.js +2 -0
  77. package/lib/utils/deployment-errors.js +1 -1
  78. package/lib/utils/device-code.js +99 -41
  79. package/lib/utils/env-config-loader.js +1 -1
  80. package/lib/utils/env-copy.js +21 -18
  81. package/lib/utils/env-endpoints.js +115 -67
  82. package/lib/utils/env-map.js +13 -14
  83. package/lib/utils/env-ports.js +45 -25
  84. package/lib/utils/env-template.js +84 -42
  85. package/lib/utils/error-formatter.js +26 -9
  86. package/lib/utils/error-formatters/error-parser.js +90 -4
  87. package/lib/utils/error-formatters/http-status-errors.js +54 -17
  88. package/lib/utils/error-formatters/network-errors.js +103 -26
  89. package/lib/utils/external-system-display.js +184 -90
  90. package/lib/utils/external-system-validators.js +164 -42
  91. package/lib/utils/file-upload.js +109 -0
  92. package/lib/utils/health-check.js +199 -83
  93. package/lib/utils/infra-containers.js +1 -1
  94. package/lib/utils/infra-status.js +66 -15
  95. package/lib/utils/local-secrets.js +45 -25
  96. package/lib/utils/paths.js +45 -33
  97. package/lib/utils/schema-loader.js +42 -25
  98. package/lib/utils/schema-resolver.js +123 -74
  99. package/lib/utils/secrets-encryption.js +62 -25
  100. package/lib/utils/secrets-helpers.js +126 -63
  101. package/lib/utils/secrets-path.js +1 -1
  102. package/lib/utils/secrets-url.js +1 -1
  103. package/lib/utils/token-manager-refresh.js +181 -0
  104. package/lib/utils/token-manager.js +76 -123
  105. package/lib/utils/variable-transformer.js +154 -77
  106. package/lib/utils/yaml-preserve.js +41 -47
  107. package/lib/{template-validator.js → validation/template.js} +54 -23
  108. package/lib/{validate.js → validation/validate.js} +205 -125
  109. package/lib/{validator.js → validation/validator.js} +58 -39
  110. package/package.json +31 -2
  111. package/templates/external-system/deploy.ps1.hbs +34 -0
  112. package/templates/external-system/deploy.sh.hbs +34 -0
  113. package/templates/external-system/external-datasource.json.hbs +31 -12
  114. package/lib/app.js +0 -467
  115. package/lib/datasource-list.js +0 -141
  116. /package/lib/{app-prompts.js → app/prompts.js} +0 -0
  117. /package/lib/{env-reader.js → core/env-reader.js} +0 -0
  118. /package/lib/{key-generator.js → core/key-generator.js} +0 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * External System Deployment Helpers
3
+ *
4
+ * Helper functions for external system deployment validation
5
+ *
6
+ * @fileoverview Deployment helper utilities for external system deployment
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ const fs = require('fs').promises;
12
+ const fsSync = require('fs');
13
+ const path = require('path');
14
+ const yaml = require('js-yaml');
15
+ const { detectAppType, getDeployJsonPath } = require('../utils/paths');
16
+
17
+ /**
18
+ * Loads variables.yaml for an application
19
+ * @async
20
+ * @function loadVariablesYaml
21
+ * @param {string} appName - Application name
22
+ * @returns {Promise<Object>} Variables configuration
23
+ * @throws {Error} If file cannot be loaded
24
+ */
25
+ async function loadVariablesYaml(appName) {
26
+ // Detect app type and get correct path (integration or builder)
27
+ const { appPath } = await detectAppType(appName);
28
+ const variablesPath = path.join(appPath, 'variables.yaml');
29
+ const content = await fs.readFile(variablesPath, 'utf8');
30
+ return yaml.load(content);
31
+ }
32
+
33
+ /**
34
+ * Validates a single system file
35
+ * @async
36
+ * @function validateSingleSystemFile
37
+ * @param {string} systemFile - System file name
38
+ * @param {string} appName - Application name
39
+ * @param {string} schemasPath - Schemas path
40
+ * @returns {Promise<string>} Validated system file path
41
+ * @throws {Error} If file not found
42
+ */
43
+ async function validateSingleSystemFile(systemFile, appName, schemasPath) {
44
+ // Try new naming first: <app-name>-deploy.json in same folder
45
+ const newSystemPath = getDeployJsonPath(appName, 'external', true);
46
+ if (fsSync.existsSync(newSystemPath)) {
47
+ return newSystemPath;
48
+ }
49
+
50
+ // Fall back to specified path
51
+ const systemPath = path.join(schemasPath, systemFile);
52
+ try {
53
+ await fs.access(systemPath);
54
+ return systemPath;
55
+ } catch {
56
+ throw new Error(`External system file not found: ${systemPath} (also checked: ${newSystemPath})`);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Validates system files
62
+ * @async
63
+ * @function validateSystemFiles
64
+ * @param {string[]} systemFiles - Array of system file names
65
+ * @param {string} appName - Application name
66
+ * @param {string} schemasPath - Schemas path
67
+ * @returns {Promise<string[]>} Array of validated system file paths
68
+ */
69
+ async function validateSystemFiles(systemFiles, appName, schemasPath) {
70
+ const validatedFiles = [];
71
+ for (const systemFile of systemFiles) {
72
+ const validatedPath = await validateSingleSystemFile(systemFile, appName, schemasPath);
73
+ validatedFiles.push(validatedPath);
74
+ }
75
+ return validatedFiles;
76
+ }
77
+
78
+ /**
79
+ * Validates a single datasource file
80
+ * @async
81
+ * @function validateSingleDatasourceFile
82
+ * @param {string} datasourceFile - Datasource file name
83
+ * @param {string} appPath - Application path
84
+ * @param {string} schemasPath - Schemas path
85
+ * @returns {Promise<string>} Validated datasource file path
86
+ * @throws {Error} If file not found
87
+ */
88
+ async function validateSingleDatasourceFile(datasourceFile, appPath, schemasPath) {
89
+ // Try same folder first (new structure)
90
+ const datasourcePath = path.join(appPath, datasourceFile);
91
+ try {
92
+ await fs.access(datasourcePath);
93
+ return datasourcePath;
94
+ } catch {
95
+ // Fall back to schemaBasePath
96
+ const fallbackPath = path.join(schemasPath, datasourceFile);
97
+ try {
98
+ await fs.access(fallbackPath);
99
+ return fallbackPath;
100
+ } catch {
101
+ throw new Error(`External datasource file not found: ${datasourcePath} or ${fallbackPath}`);
102
+ }
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Validates datasource files
108
+ * @async
109
+ * @function validateDatasourceFiles
110
+ * @param {string[]} datasourceFiles - Array of datasource file names
111
+ * @param {string} appPath - Application path
112
+ * @param {string} schemasPath - Schemas path
113
+ * @returns {Promise<string[]>} Array of validated datasource file paths
114
+ */
115
+ async function validateDatasourceFiles(datasourceFiles, appPath, schemasPath) {
116
+ const validatedFiles = [];
117
+ for (const datasourceFile of datasourceFiles) {
118
+ const validatedPath = await validateSingleDatasourceFile(datasourceFile, appPath, schemasPath);
119
+ validatedFiles.push(validatedPath);
120
+ }
121
+ return validatedFiles;
122
+ }
123
+
124
+ /**
125
+ * Extracts system key from system file path
126
+ * @function extractSystemKey
127
+ * @param {string} systemFilePath - System file path
128
+ * @returns {string} System key
129
+ */
130
+ function extractSystemKey(systemFilePath) {
131
+ // Normalize path separators first (handles Windows backslashes)
132
+ const normalizedPath = systemFilePath.replace(/\\/g, '/');
133
+ const systemFileName = path.basename(normalizedPath, '.json');
134
+ return systemFileName.replace(/-deploy$/, '');
135
+ }
136
+
137
+ module.exports = {
138
+ loadVariablesYaml,
139
+ validateSingleSystemFile,
140
+ validateSystemFiles,
141
+ validateSingleDatasourceFile,
142
+ validateDatasourceFiles,
143
+ extractSystemKey
144
+ };
145
+
@@ -10,9 +10,7 @@
10
10
  */
11
11
 
12
12
  const fs = require('fs').promises;
13
- const fsSync = require('fs');
14
13
  const path = require('path');
15
- const yaml = require('js-yaml');
16
14
  const chalk = require('chalk');
17
15
  const {
18
16
  deployExternalSystemViaPipeline,
@@ -20,13 +18,19 @@ const {
20
18
  uploadApplicationViaPipeline,
21
19
  validateUploadViaPipeline,
22
20
  publishUploadViaPipeline
23
- } = require('./api/pipeline.api');
24
- const { getDeploymentAuth } = require('./utils/token-manager');
25
- const { getConfig } = require('./config');
26
- const logger = require('./utils/logger');
27
- const { getDataplaneUrl } = require('./datasource-deploy');
28
- const { detectAppType, getDeployJsonPath } = require('./utils/paths');
29
- const { generateExternalSystemApplicationSchema } = require('./generator');
21
+ } = require('../api/pipeline.api');
22
+ const { getDeploymentAuth } = require('../utils/token-manager');
23
+ const { getConfig } = require('../core/config');
24
+ const logger = require('../utils/logger');
25
+ const { getDataplaneUrl } = require('../datasource/deploy');
26
+ const { detectAppType } = require('../utils/paths');
27
+ const { generateExternalSystemApplicationSchema } = require('../generator/external');
28
+ const {
29
+ loadVariablesYaml,
30
+ validateSystemFiles,
31
+ validateDatasourceFiles,
32
+ extractSystemKey
33
+ } = require('./deploy-helpers');
30
34
 
31
35
  /**
32
36
  * Loads variables.yaml for an application
@@ -36,14 +40,6 @@ const { generateExternalSystemApplicationSchema } = require('./generator');
36
40
  * @returns {Promise<Object>} Variables configuration
37
41
  * @throws {Error} If file cannot be loaded
38
42
  */
39
- async function loadVariablesYaml(appName) {
40
- // Detect app type and get correct path (integration or builder)
41
- const { appPath } = await detectAppType(appName);
42
- const variablesPath = path.join(appPath, 'variables.yaml');
43
- const content = await fs.readFile(variablesPath, 'utf8');
44
- return yaml.load(content);
45
- }
46
-
47
43
  /**
48
44
  * Validates external system files exist
49
45
  * @async
@@ -52,6 +48,7 @@ async function loadVariablesYaml(appName) {
52
48
  * @returns {Promise<Object>} Validation result with file paths
53
49
  * @throws {Error} If validation fails
54
50
  */
51
+
55
52
  async function validateExternalSystemFiles(appName) {
56
53
  const variables = await loadVariablesYaml(appName);
57
54
 
@@ -70,53 +67,18 @@ async function validateExternalSystemFiles(appName) {
70
67
  : path.join(appPath, schemaBasePath);
71
68
 
72
69
  // Validate system files
73
- const systemFiles = [];
74
- if (variables.externalIntegration.systems && variables.externalIntegration.systems.length > 0) {
75
- for (const systemFile of variables.externalIntegration.systems) {
76
- // Try new naming first: <app-name>-deploy.json in same folder
77
- const newSystemPath = getDeployJsonPath(appName, 'external', true);
78
- if (fsSync.existsSync(newSystemPath)) {
79
- systemFiles.push(newSystemPath);
80
- } else {
81
- // Fall back to specified path
82
- const systemPath = path.join(schemasPath, systemFile);
83
- try {
84
- await fs.access(systemPath);
85
- systemFiles.push(systemPath);
86
- } catch {
87
- throw new Error(`External system file not found: ${systemPath} (also checked: ${newSystemPath})`);
88
- }
89
- }
90
- }
91
- } else {
70
+ const systemFilesList = variables.externalIntegration.systems || [];
71
+ if (systemFilesList.length === 0) {
92
72
  throw new Error('No external system files specified in externalIntegration.systems');
93
73
  }
74
+ const systemFiles = await validateSystemFiles(systemFilesList, appName, schemasPath);
94
75
 
95
- // Validate datasource files (naming: <app-name>-deploy-<datasource-key>.json)
96
- const datasourceFiles = [];
97
- if (variables.externalIntegration.dataSources && variables.externalIntegration.dataSources.length > 0) {
98
- for (const datasourceFile of variables.externalIntegration.dataSources) {
99
- // Try same folder first (new structure)
100
- const datasourcePath = path.join(appPath, datasourceFile);
101
- try {
102
- await fs.access(datasourcePath);
103
- datasourceFiles.push(datasourcePath);
104
- } catch {
105
- // Fall back to schemaBasePath
106
- const fallbackPath = path.join(schemasPath, datasourceFile);
107
- try {
108
- await fs.access(fallbackPath);
109
- datasourceFiles.push(fallbackPath);
110
- } catch {
111
- throw new Error(`External datasource file not found: ${datasourcePath} or ${fallbackPath}`);
112
- }
113
- }
114
- }
115
- }
76
+ // Validate datasource files
77
+ const datasourceFilesList = variables.externalIntegration.dataSources || [];
78
+ const datasourceFiles = await validateDatasourceFiles(datasourceFilesList, appPath, schemasPath);
116
79
 
117
- // Extract systemKey from system file (remove -deploy.json suffix if present)
118
- const systemFileName = path.basename(systemFiles[0], '.json');
119
- const systemKey = systemFileName.replace(/-deploy$/, '');
80
+ // Extract systemKey from system file
81
+ const systemKey = extractSystemKey(systemFiles[0]);
120
82
 
121
83
  return {
122
84
  systemFiles,
@@ -134,57 +96,121 @@ async function validateExternalSystemFiles(appName) {
134
96
  * @returns {Promise<void>} Resolves when deployment completes
135
97
  * @throws {Error} If deployment fails
136
98
  */
137
- async function buildExternalSystem(appName, options = {}) {
138
- try {
139
- logger.log(chalk.blue(`\n🔨 Building external system: ${appName}`));
99
+ /**
100
+ * Validates and prepares deployment configuration
101
+ * @async
102
+ * @function prepareDeploymentConfig
103
+ * @param {string} appName - Application name
104
+ * @param {Object} options - Deployment options
105
+ * @returns {Promise<Object>} Deployment configuration
106
+ */
107
+ async function prepareDeploymentConfig(appName, options) {
108
+ const { systemFiles, datasourceFiles, systemKey } = await validateExternalSystemFiles(appName);
140
109
 
141
- // Validate files
142
- const { systemFiles, datasourceFiles, systemKey } = await validateExternalSystemFiles(appName);
110
+ const config = await getConfig();
111
+ const environment = options.environment || 'dev';
112
+ const controllerUrl = options.controller || config.deployment?.controllerUrl || 'http://localhost:3000';
113
+ const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
143
114
 
144
- // Get authentication
145
- const config = await getConfig();
146
- const environment = options.environment || 'dev';
147
- const controllerUrl = options.controller || config.deployment?.controllerUrl || 'http://localhost:3000';
148
- const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
115
+ if (!authConfig.token && !authConfig.clientId) {
116
+ throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
117
+ }
149
118
 
150
- if (!authConfig.token && !authConfig.clientId) {
151
- throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
152
- }
119
+ return { systemFiles, datasourceFiles, systemKey, environment, controllerUrl, authConfig };
120
+ }
153
121
 
154
- // Get dataplane URL from controller
155
- logger.log(chalk.blue('🌐 Getting dataplane URL from controller...'));
156
- const dataplaneUrl = await getDataplaneUrl(controllerUrl, appName, environment, authConfig);
157
- logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
122
+ /**
123
+ * Gets dataplane URL from controller
124
+ * @async
125
+ * @function getDataplaneUrlForDeployment
126
+ * @param {string} controllerUrl - Controller URL
127
+ * @param {string} appName - Application name
128
+ * @param {string} environment - Environment key
129
+ * @param {Object} authConfig - Authentication configuration
130
+ * @returns {Promise<string>} Dataplane URL
131
+ */
132
+ async function getDataplaneUrlForDeployment(controllerUrl, appName, environment, authConfig) {
133
+ logger.log(chalk.blue('🌐 Getting dataplane URL from controller...'));
134
+ const dataplaneUrl = await getDataplaneUrl(controllerUrl, appName, environment, authConfig);
135
+ logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
136
+ return dataplaneUrl;
137
+ }
158
138
 
159
- // Deploy external system
160
- logger.log(chalk.blue(`Deploying external system: ${systemKey}...`));
161
- const systemContent = await fs.readFile(systemFiles[0], 'utf8');
162
- const systemJson = JSON.parse(systemContent);
139
+ /**
140
+ * Deploys external system via pipeline
141
+ * @async
142
+ * @function deploySystem
143
+ * @param {string} dataplaneUrl - Dataplane URL
144
+ * @param {Object} authConfig - Authentication configuration
145
+ * @param {string} systemFilePath - Path to system file
146
+ * @param {string} systemKey - System key
147
+ * @returns {Promise<void>}
148
+ */
149
+ async function deploySystem(dataplaneUrl, authConfig, systemFilePath, systemKey) {
150
+ logger.log(chalk.blue(`Deploying external system: ${systemKey}...`));
151
+ const systemContent = await fs.readFile(systemFilePath, 'utf8');
152
+ const systemJson = JSON.parse(systemContent);
163
153
 
164
- const systemResponse = await deployExternalSystemViaPipeline(dataplaneUrl, authConfig, systemJson);
154
+ const systemResponse = await deployExternalSystemViaPipeline(dataplaneUrl, authConfig, systemJson);
165
155
 
166
- if (!systemResponse.success) {
167
- throw new Error(`Failed to deploy external system: ${systemResponse.error || systemResponse.formattedError}`);
168
- }
156
+ if (!systemResponse.success) {
157
+ throw new Error(`Failed to deploy external system: ${systemResponse.error || systemResponse.formattedError}`);
158
+ }
169
159
 
170
- logger.log(chalk.green(`✓ External system deployed: ${systemKey}`));
160
+ logger.log(chalk.green(`✓ External system deployed: ${systemKey}`));
161
+ }
171
162
 
172
- // Deploy datasources
173
- for (const datasourceFile of datasourceFiles) {
174
- const datasourceName = path.basename(datasourceFile, '.json');
175
- logger.log(chalk.blue(`Deploying datasource: ${datasourceName}...`));
163
+ /**
164
+ * Deploys a single datasource via pipeline
165
+ * @async
166
+ * @function deploySingleDatasource
167
+ * @param {string} dataplaneUrl - Dataplane URL
168
+ * @param {string} systemKey - System key
169
+ * @param {Object} authConfig - Authentication configuration
170
+ * @param {string} datasourceFile - Path to datasource file
171
+ * @returns {Promise<void>}
172
+ */
173
+ async function deploySingleDatasource(dataplaneUrl, systemKey, authConfig, datasourceFile) {
174
+ const datasourceName = path.basename(datasourceFile, '.json');
175
+ logger.log(chalk.blue(`Deploying datasource: ${datasourceName}...`));
176
176
 
177
- const datasourceContent = await fs.readFile(datasourceFile, 'utf8');
178
- const datasourceJson = JSON.parse(datasourceContent);
177
+ const datasourceContent = await fs.readFile(datasourceFile, 'utf8');
178
+ const datasourceJson = JSON.parse(datasourceContent);
179
179
 
180
- const datasourceResponse = await deployDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig, datasourceJson);
180
+ const datasourceResponse = await deployDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig, datasourceJson);
181
181
 
182
- if (!datasourceResponse.success) {
183
- throw new Error(`Failed to deploy datasource ${datasourceName}: ${datasourceResponse.error || datasourceResponse.formattedError}`);
184
- }
182
+ if (!datasourceResponse.success) {
183
+ throw new Error(`Failed to deploy datasource ${datasourceName}: ${datasourceResponse.error || datasourceResponse.formattedError}`);
184
+ }
185
185
 
186
- logger.log(chalk.green(`✓ Datasource deployed: ${datasourceName}`));
187
- }
186
+ logger.log(chalk.green(`✓ Datasource deployed: ${datasourceName}`));
187
+ }
188
+
189
+ /**
190
+ * Deploys all datasources
191
+ * @async
192
+ * @function deployAllDatasources
193
+ * @param {string} dataplaneUrl - Dataplane URL
194
+ * @param {string} systemKey - System key
195
+ * @param {Object} authConfig - Authentication configuration
196
+ * @param {string[]} datasourceFiles - Array of datasource file paths
197
+ * @returns {Promise<void>}
198
+ */
199
+ async function deployAllDatasources(dataplaneUrl, systemKey, authConfig, datasourceFiles) {
200
+ for (const datasourceFile of datasourceFiles) {
201
+ await deploySingleDatasource(dataplaneUrl, systemKey, authConfig, datasourceFile);
202
+ }
203
+ }
204
+
205
+ async function buildExternalSystem(appName, options = {}) {
206
+ try {
207
+ logger.log(chalk.blue(`\n🔨 Building external system: ${appName}`));
208
+
209
+ const { systemFiles, datasourceFiles, systemKey, environment, controllerUrl, authConfig } = await prepareDeploymentConfig(appName, options);
210
+ const dataplaneUrl = await getDataplaneUrlForDeployment(controllerUrl, appName, environment, authConfig);
211
+
212
+ await deploySystem(dataplaneUrl, authConfig, systemFiles[0], systemKey);
213
+ await deployAllDatasources(dataplaneUrl, systemKey, authConfig, datasourceFiles);
188
214
 
189
215
  logger.log(chalk.green('\n✅ External system built successfully!'));
190
216
  logger.log(chalk.blue(`System: ${systemKey}`));
@@ -268,26 +294,45 @@ async function uploadApplication(dataplaneUrl, authConfig, applicationSchema) {
268
294
  * @returns {Promise<void>}
269
295
  * @throws {Error} If validation fails
270
296
  */
271
- async function validateUpload(dataplaneUrl, uploadId, authConfig) {
272
- logger.log(chalk.blue('🔍 Validating upload...'));
273
- const validateResponse = await validateUploadViaPipeline(dataplaneUrl, uploadId, authConfig);
274
-
275
- if (!validateResponse.success || !validateResponse.data) {
276
- throw new Error(`Validation failed: ${validateResponse.error || validateResponse.formattedError || 'Unknown error'}`);
277
- }
278
-
279
- const validateData = validateResponse.data.data || validateResponse.data;
280
-
281
- // Display changes
282
- if (validateData.changes && validateData.changes.length > 0) {
297
+ /**
298
+ * Displays validation changes
299
+ * @function displayValidationChanges
300
+ * @param {Object[]} changes - Array of changes
301
+ */
302
+ function displayValidationChanges(changes) {
303
+ if (changes && changes.length > 0) {
283
304
  logger.log(chalk.blue('\n📋 Changes to be published:'));
284
- for (const change of validateData.changes) {
305
+ for (const change of changes) {
285
306
  const changeType = change.type || 'unknown';
286
307
  const changeEntity = change.entity || change.key || 'unknown';
287
308
  const emoji = changeType === 'new' ? '➕' : changeType === 'modified' ? '✏️' : '🗑️';
288
309
  logger.log(chalk.gray(` ${emoji} ${changeType}: ${changeEntity}`));
289
310
  }
290
311
  }
312
+ }
313
+
314
+ /**
315
+ * Validates upload response
316
+ * @function validateUploadResponse
317
+ * @param {Object} validateResponse - Validation response
318
+ * @returns {Object} Validation data
319
+ * @throws {Error} If validation failed
320
+ */
321
+ function validateUploadResponse(validateResponse) {
322
+ if (!validateResponse.success || !validateResponse.data) {
323
+ throw new Error(`Validation failed: ${validateResponse.error || validateResponse.formattedError || 'Unknown error'}`);
324
+ }
325
+
326
+ return validateResponse.data.data || validateResponse.data;
327
+ }
328
+
329
+ async function validateUpload(dataplaneUrl, uploadId, authConfig) {
330
+ logger.log(chalk.blue('🔍 Validating upload...'));
331
+ const validateResponse = await validateUploadViaPipeline(dataplaneUrl, uploadId, authConfig);
332
+
333
+ const validateData = validateUploadResponse(validateResponse);
334
+
335
+ displayValidationChanges(validateData.changes);
291
336
 
292
337
  if (validateData.summary) {
293
338
  logger.log(chalk.blue(`\n📊 Summary: ${validateData.summary}`));
@@ -0,0 +1,114 @@
1
+ /**
2
+ * External System Download Helpers
3
+ *
4
+ * Helper functions for external system download file generation
5
+ *
6
+ * @fileoverview Download helper utilities for external system download
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ /**
12
+ * Generates variables.yaml content for downloaded system
13
+ * @param {string} systemKey - System key
14
+ * @param {Object} application - External system configuration
15
+ * @param {Array} dataSources - Array of datasource configurations
16
+ * @returns {Object} Variables YAML object
17
+ */
18
+ function generateVariablesYaml(systemKey, application, dataSources) {
19
+ const systemFileName = `${systemKey}-deploy.json`;
20
+ const datasourceFiles = dataSources.map(ds => {
21
+ // Extract entity type from datasource key or use entityType/entityKey (backward compatibility)
22
+ const entityType = ds.entityType || ds.entityKey || ds.key.split('-').pop();
23
+ return `${systemKey}-deploy-${entityType}.json`;
24
+ });
25
+
26
+ return {
27
+ name: systemKey,
28
+ displayName: application.displayName || systemKey,
29
+ description: application.description || `External system integration for ${systemKey}`,
30
+ externalIntegration: {
31
+ schemaBasePath: './',
32
+ systems: [systemFileName],
33
+ dataSources: datasourceFiles,
34
+ autopublish: false,
35
+ version: application.version || '1.0.0'
36
+ }
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Generates README.md with setup instructions
42
+ * @param {string} systemKey - System key
43
+ * @param {Object} application - External system configuration
44
+ * @param {Array} dataSources - Array of datasource configurations
45
+ * @returns {string} README.md content
46
+ */
47
+ function generateReadme(systemKey, application, dataSources) {
48
+ const displayName = application.displayName || systemKey;
49
+ const description = application.description || `External system integration for ${systemKey}`;
50
+ const systemType = application.type || 'unknown';
51
+
52
+ const lines = [
53
+ `# ${displayName}`,
54
+ '',
55
+ description,
56
+ '',
57
+ '## System Information',
58
+ '',
59
+ `- **System Key**: \`${systemKey}\``,
60
+ `- **System Type**: \`${systemType}\``,
61
+ `- **Datasources**: ${dataSources.length}`,
62
+ '',
63
+ '## Files',
64
+ '',
65
+ '- `variables.yaml` - Application configuration with externalIntegration block',
66
+ `- \`${systemKey}-deploy.json\` - External system definition`
67
+ ];
68
+
69
+ dataSources.forEach(ds => {
70
+ const entityType = ds.entityType || ds.entityKey || ds.key.split('-').pop();
71
+ lines.push(`- \`${systemKey}-deploy-${entityType}.json\` - Datasource: ${ds.displayName || ds.key}`);
72
+ });
73
+
74
+ lines.push(
75
+ '- `env.template` - Environment variables template',
76
+ '',
77
+ '## Setup Instructions',
78
+ '',
79
+ '1. Review and update configuration files as needed',
80
+ '2. Set up environment variables in `env.template`',
81
+ '3. Run unit tests: `aifabrix test ${systemKey}`',
82
+ '4. Run integration tests: `aifabrix test-integration ${systemKey}`',
83
+ '5. Deploy: `aifabrix deploy ${systemKey} --environment dev`',
84
+ '',
85
+ '## Testing',
86
+ '',
87
+ '### Unit Tests',
88
+ 'Run local validation without API calls:',
89
+ '```bash',
90
+ `aifabrix test ${systemKey}`,
91
+ '```',
92
+ '',
93
+ '### Integration Tests',
94
+ 'Run integration tests via dataplane:',
95
+ '```bash',
96
+ `aifabrix test-integration ${systemKey} --environment dev`,
97
+ '```',
98
+ '',
99
+ '## Deployment',
100
+ '',
101
+ 'Deploy to dataplane via miso-controller:',
102
+ '```bash',
103
+ `aifabrix deploy ${systemKey} --environment dev`,
104
+ '```'
105
+ );
106
+
107
+ return lines.join('\n');
108
+ }
109
+
110
+ module.exports = {
111
+ generateVariablesYaml,
112
+ generateReadme
113
+ };
114
+