@aifabrix/builder 2.32.2 → 2.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/project-rules.mdc +8 -0
- package/README.md +36 -8
- package/bin/aifabrix.js +6 -8
- package/integration/hubspot/README.md +8 -7
- package/integration/hubspot/companies.json +2048 -0
- package/integration/hubspot/create-hubspot.js +665 -0
- package/integration/hubspot/{hubspot-deploy-company.json → hubspot-datasource-company.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-contact.json → hubspot-datasource-contact.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-deal.json → hubspot-datasource-deal.json} +1 -1
- package/integration/hubspot/hubspot-deploy.json +832 -81
- package/integration/hubspot/hubspot-system.json +99 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-credential-real.yaml +20 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +9 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-add-datasource.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-app-name.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-create.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-select.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-known-platform.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-app.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-mode.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-file.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-url.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-source.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-array-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +5 -0
- package/integration/hubspot/test-dataplane-down-helpers.js +246 -0
- package/integration/hubspot/test-dataplane-down-tests.js +419 -0
- package/integration/hubspot/test-dataplane-down.js +157 -0
- package/integration/hubspot/test.js +1517 -0
- package/integration/hubspot/variables.yaml +4 -4
- package/integration/hubspot/wizard-hubspot-e2e.yaml +16 -0
- package/integration/hubspot/wizard-hubspot-platform.yaml +8 -0
- package/lib/api/applications.api.js +1 -0
- package/lib/api/index.js +10 -5
- package/lib/api/types/wizard.types.js +176 -38
- package/lib/api/wizard.api.js +207 -38
- package/lib/app/deploy.js +116 -54
- package/lib/app/display.js +6 -5
- package/lib/app/dockerfile.js +2 -1
- package/lib/app/list.js +78 -37
- package/lib/app/prompts.js +9 -5
- package/lib/app/readme.js +41 -112
- package/lib/app/register.js +44 -9
- package/lib/app/rotate-secret.js +50 -32
- package/lib/cli.js +243 -65
- package/lib/commands/app.js +4 -9
- package/lib/commands/auth-config.js +125 -0
- package/lib/commands/auth-status.js +261 -0
- package/lib/commands/datasource.js +3 -6
- package/lib/commands/login-credentials.js +4 -4
- package/lib/commands/login-device.js +43 -29
- package/lib/commands/login.js +22 -13
- package/lib/commands/wizard-config-normalizer.js +92 -0
- package/lib/commands/wizard-core.js +515 -0
- package/lib/commands/wizard-dataplane.js +122 -0
- package/lib/commands/wizard-headless.js +115 -0
- package/lib/commands/wizard.js +129 -357
- package/lib/core/config.js +46 -0
- package/lib/core/secrets.js +3 -22
- package/lib/core/templates-env.js +1 -1
- package/lib/datasource/deploy.js +34 -23
- package/lib/datasource/list.js +8 -6
- package/lib/deployment/deployer.js +25 -0
- package/lib/deployment/environment.js +10 -13
- package/lib/external-system/delete.js +151 -0
- package/lib/external-system/deploy.js +54 -378
- package/lib/external-system/download-helpers.js +45 -65
- package/lib/external-system/download.js +34 -13
- package/lib/external-system/generator.js +11 -7
- package/lib/external-system/test-auth.js +5 -3
- package/lib/generator/builders.js +3 -1
- package/lib/generator/external-controller-manifest.js +157 -0
- package/lib/generator/external-schema-utils.js +236 -0
- package/lib/generator/external.js +55 -3
- package/lib/generator/index.js +22 -10
- package/lib/generator/wizard-prompts.js +33 -10
- package/lib/generator/wizard.js +69 -86
- package/lib/infrastructure/compose.js +100 -0
- package/lib/infrastructure/helpers.js +139 -0
- package/lib/infrastructure/index.js +52 -311
- package/lib/infrastructure/services.js +168 -0
- package/lib/schema/application-schema.json +24 -5
- package/lib/schema/external-datasource.schema.json +303 -17
- package/lib/schema/external-system.schema.json +1 -1
- package/lib/schema/wizard-config.schema.json +234 -0
- package/lib/utils/api.js +37 -42
- package/lib/utils/app-existence.js +42 -0
- package/lib/utils/app-register-config.js +7 -2
- package/lib/utils/app-register-display.js +2 -1
- package/lib/utils/auth-config-validator.js +92 -0
- package/lib/utils/cli-utils.js +3 -1
- package/lib/utils/command-header.js +43 -0
- package/lib/utils/compose-generator.js +113 -70
- package/lib/utils/controller-url.js +115 -0
- package/lib/utils/dataplane-health.js +115 -0
- package/lib/utils/dataplane-resolver.js +29 -0
- package/lib/utils/dev-config.js +6 -2
- package/lib/utils/env-copy.js +2 -1
- package/lib/utils/env-map.js +2 -1
- package/lib/utils/env-ports.js +2 -1
- package/lib/utils/env-template.js +1 -1
- package/lib/utils/error-formatter.js +149 -28
- package/lib/utils/external-readme.js +125 -0
- package/lib/utils/help-builder.js +190 -0
- package/lib/utils/infra-status.js +13 -3
- package/lib/utils/paths.js +17 -2
- package/lib/utils/port-resolver.js +111 -0
- package/lib/utils/secrets-helpers.js +3 -15
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/token-manager.js +69 -4
- package/lib/utils/variable-transformer.js +7 -2
- package/lib/validation/external-manifest-validator.js +202 -0
- package/lib/validation/validate-display.js +406 -0
- package/lib/validation/validate.js +159 -123
- package/lib/validation/validator.js +38 -4
- package/lib/validation/wizard-config-validator.js +267 -0
- package/package.json +4 -2
- package/templates/applications/README.md.hbs +19 -17
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/applications/miso-controller/rbac.yaml +7 -7
- package/templates/external-system/README.md.hbs +99 -0
- package/templates/external-system/external-system.json.hbs +1 -1
- package/templates/infra/compose.yaml.hbs +35 -0
- package/templates/python/docker-compose.hbs +26 -0
- package/templates/typescript/docker-compose.hbs +26 -0
|
@@ -1,431 +1,107 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External System Deployment Module
|
|
3
3
|
*
|
|
4
|
-
* Handles deployment of external systems
|
|
5
|
-
*
|
|
4
|
+
* Handles deployment of external systems via controller pipeline.
|
|
5
|
+
* Uses unified controller pipeline (same as regular apps) - no direct dataplane calls.
|
|
6
6
|
*
|
|
7
7
|
* @fileoverview External system deployment for AI Fabrix Builder
|
|
8
8
|
* @author AI Fabrix Team
|
|
9
9
|
* @version 2.0.0
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const fs = require('fs').promises;
|
|
13
|
-
const path = require('path');
|
|
14
12
|
const chalk = require('chalk');
|
|
15
|
-
const {
|
|
16
|
-
deployExternalSystemViaPipeline,
|
|
17
|
-
deployDatasourceViaPipeline,
|
|
18
|
-
uploadApplicationViaPipeline,
|
|
19
|
-
validateUploadViaPipeline,
|
|
20
|
-
publishUploadViaPipeline
|
|
21
|
-
} = require('../api/pipeline.api');
|
|
22
13
|
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
23
|
-
const { getConfig } = require('../core/config');
|
|
24
14
|
const logger = require('../utils/logger');
|
|
25
|
-
const {
|
|
26
|
-
const {
|
|
27
|
-
const {
|
|
28
|
-
const {
|
|
29
|
-
loadVariablesYaml,
|
|
30
|
-
validateSystemFiles,
|
|
31
|
-
validateDatasourceFiles,
|
|
32
|
-
extractSystemKey
|
|
33
|
-
} = require('./deploy-helpers');
|
|
15
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
16
|
+
const { generateControllerManifest } = require('../generator/external-controller-manifest');
|
|
17
|
+
const { validateExternalSystemComplete } = require('../validation/validate');
|
|
18
|
+
const { displayValidationResults } = require('../validation/validate-display');
|
|
34
19
|
|
|
35
20
|
/**
|
|
36
|
-
*
|
|
37
|
-
* @async
|
|
38
|
-
* @function loadVariablesYaml
|
|
39
|
-
* @param {string} appName - Application name
|
|
40
|
-
* @returns {Promise<Object>} Variables configuration
|
|
41
|
-
* @throws {Error} If file cannot be loaded
|
|
42
|
-
*/
|
|
43
|
-
/**
|
|
44
|
-
* Validates external system files exist
|
|
45
|
-
* @async
|
|
46
|
-
* @function validateExternalSystemFiles
|
|
47
|
-
* @param {string} appName - Application name
|
|
48
|
-
* @returns {Promise<Object>} Validation result with file paths
|
|
49
|
-
* @throws {Error} If validation fails
|
|
50
|
-
*/
|
|
51
|
-
|
|
52
|
-
async function validateExternalSystemFiles(appName) {
|
|
53
|
-
const variables = await loadVariablesYaml(appName);
|
|
54
|
-
|
|
55
|
-
if (!variables.externalIntegration) {
|
|
56
|
-
throw new Error('externalIntegration block not found in variables.yaml');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Detect app type and get correct path (integration or builder)
|
|
60
|
-
const { appPath } = await detectAppType(appName);
|
|
61
|
-
|
|
62
|
-
// For new structure, files are in same folder (schemaBasePath is usually './')
|
|
63
|
-
// For backward compatibility, support old schemas/ subfolder
|
|
64
|
-
const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
|
|
65
|
-
const schemasPath = path.isAbsolute(schemaBasePath)
|
|
66
|
-
? schemaBasePath
|
|
67
|
-
: path.join(appPath, schemaBasePath);
|
|
68
|
-
|
|
69
|
-
// Validate system files
|
|
70
|
-
const systemFilesList = variables.externalIntegration.systems || [];
|
|
71
|
-
if (systemFilesList.length === 0) {
|
|
72
|
-
throw new Error('No external system files specified in externalIntegration.systems');
|
|
73
|
-
}
|
|
74
|
-
const systemFiles = await validateSystemFiles(systemFilesList, appName, schemasPath);
|
|
75
|
-
|
|
76
|
-
// Validate datasource files
|
|
77
|
-
const datasourceFilesList = variables.externalIntegration.dataSources || [];
|
|
78
|
-
const datasourceFiles = await validateDatasourceFiles(datasourceFilesList, appPath, schemasPath);
|
|
79
|
-
|
|
80
|
-
// Extract systemKey from system file
|
|
81
|
-
const systemKey = extractSystemKey(systemFiles[0]);
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
systemFiles,
|
|
85
|
-
datasourceFiles,
|
|
86
|
-
systemKey
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Deploys external system to dataplane (build step - deploy, not publish)
|
|
92
|
-
* @async
|
|
93
|
-
* @function buildExternalSystem
|
|
94
|
-
* @param {string} appName - Application name
|
|
95
|
-
* @param {Object} options - Deployment options
|
|
96
|
-
* @returns {Promise<void>} Resolves when deployment completes
|
|
97
|
-
* @throws {Error} If deployment fails
|
|
98
|
-
*/
|
|
99
|
-
/**
|
|
100
|
-
* Validates and prepares deployment configuration
|
|
21
|
+
* Prepares deployment configuration (auth, controller URL, environment)
|
|
101
22
|
* @async
|
|
102
23
|
* @function prepareDeploymentConfig
|
|
103
24
|
* @param {string} appName - Application name
|
|
104
25
|
* @param {Object} options - Deployment options
|
|
105
26
|
* @returns {Promise<Object>} Deployment configuration
|
|
106
27
|
*/
|
|
107
|
-
async function prepareDeploymentConfig(appName,
|
|
108
|
-
const {
|
|
109
|
-
|
|
110
|
-
const
|
|
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);
|
|
114
|
-
|
|
115
|
-
if (!authConfig.token && !authConfig.clientId) {
|
|
116
|
-
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return { systemFiles, datasourceFiles, systemKey, environment, controllerUrl, authConfig };
|
|
120
|
-
}
|
|
121
|
-
|
|
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
|
-
}
|
|
138
|
-
|
|
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);
|
|
153
|
-
|
|
154
|
-
const systemResponse = await deployExternalSystemViaPipeline(dataplaneUrl, authConfig, systemJson);
|
|
155
|
-
|
|
156
|
-
if (!systemResponse.success) {
|
|
157
|
-
throw new Error(`Failed to deploy external system: ${systemResponse.error || systemResponse.formattedError}`);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
logger.log(chalk.green(`✓ External system deployed: ${systemKey}`));
|
|
161
|
-
}
|
|
162
|
-
|
|
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
|
-
|
|
177
|
-
const datasourceContent = await fs.readFile(datasourceFile, 'utf8');
|
|
178
|
-
const datasourceJson = JSON.parse(datasourceContent);
|
|
179
|
-
|
|
180
|
-
const datasourceResponse = await deployDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig, datasourceJson);
|
|
181
|
-
|
|
182
|
-
if (!datasourceResponse.success) {
|
|
183
|
-
throw new Error(`Failed to deploy datasource ${datasourceName}: ${datasourceResponse.error || datasourceResponse.formattedError}`);
|
|
184
|
-
}
|
|
185
|
-
|
|
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);
|
|
214
|
-
|
|
215
|
-
logger.log(chalk.green('\n✅ External system built successfully!'));
|
|
216
|
-
logger.log(chalk.blue(`System: ${systemKey}`));
|
|
217
|
-
logger.log(chalk.blue(`Datasources: ${datasourceFiles.length}`));
|
|
218
|
-
} catch (error) {
|
|
219
|
-
throw new Error(`Failed to build external system: ${error.message}`);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Validate deployment prerequisites
|
|
225
|
-
* @async
|
|
226
|
-
* @param {string} appName - Application name
|
|
227
|
-
* @returns {Promise<Object>} Validation result with systemFiles, datasourceFiles, and systemKey
|
|
228
|
-
*/
|
|
229
|
-
async function validateDeploymentPrerequisites(appName) {
|
|
230
|
-
const { systemFiles: _systemFiles, datasourceFiles, systemKey } = await validateExternalSystemFiles(appName);
|
|
231
|
-
return { systemFiles: _systemFiles, datasourceFiles, systemKey };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Prepare deployment files and get authentication
|
|
236
|
-
* @async
|
|
237
|
-
* @param {string} appName - Application name
|
|
238
|
-
* @param {Object} options - Deployment options
|
|
239
|
-
* @returns {Promise<Object>} Object with applicationSchema, authConfig, controllerUrl, environment, and systemKey
|
|
240
|
-
*/
|
|
241
|
-
async function prepareDeploymentFiles(appName, options) {
|
|
242
|
-
logger.log(chalk.blue('📋 Generating application schema...'));
|
|
243
|
-
const applicationSchema = await generateExternalSystemApplicationSchema(appName);
|
|
244
|
-
logger.log(chalk.green('✓ Application schema generated'));
|
|
245
|
-
|
|
246
|
-
const config = await getConfig();
|
|
247
|
-
const environment = options.environment || 'dev';
|
|
248
|
-
const controllerUrl = options.controller || config.deployment?.controllerUrl || 'http://localhost:3000';
|
|
28
|
+
async function prepareDeploymentConfig(appName, _options) {
|
|
29
|
+
const { resolveEnvironment } = require('../core/config');
|
|
30
|
+
const environment = await resolveEnvironment();
|
|
31
|
+
const controllerUrl = await resolveControllerUrl();
|
|
249
32
|
const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
|
|
250
33
|
|
|
251
34
|
if (!authConfig.token && !authConfig.clientId) {
|
|
252
35
|
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
|
|
253
36
|
}
|
|
254
37
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
return { applicationSchema, authConfig, controllerUrl, environment, systemKey };
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Upload application and get upload ID
|
|
262
|
-
* @async
|
|
263
|
-
* @param {string} dataplaneUrl - Dataplane URL
|
|
264
|
-
* @param {Object} authConfig - Authentication configuration
|
|
265
|
-
* @param {Object} applicationSchema - Application schema
|
|
266
|
-
* @returns {Promise<string>} Upload ID
|
|
267
|
-
* @throws {Error} If upload fails
|
|
268
|
-
*/
|
|
269
|
-
async function uploadApplication(dataplaneUrl, authConfig, applicationSchema) {
|
|
270
|
-
logger.log(chalk.blue('📤 Uploading application configuration...'));
|
|
271
|
-
const uploadResponse = await uploadApplicationViaPipeline(dataplaneUrl, authConfig, applicationSchema);
|
|
272
|
-
|
|
273
|
-
if (!uploadResponse.success || !uploadResponse.data) {
|
|
274
|
-
throw new Error(`Failed to upload application: ${uploadResponse.error || uploadResponse.formattedError || 'Unknown error'}`);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const uploadData = uploadResponse.data.data || uploadResponse.data;
|
|
278
|
-
const uploadId = uploadData.uploadId || uploadData.id;
|
|
279
|
-
|
|
280
|
-
if (!uploadId) {
|
|
281
|
-
throw new Error('Upload ID not found in upload response');
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
logger.log(chalk.green(`✓ Upload successful (ID: ${uploadId})`));
|
|
285
|
-
return uploadId;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Validate upload and display changes
|
|
290
|
-
* @async
|
|
291
|
-
* @param {string} dataplaneUrl - Dataplane URL
|
|
292
|
-
* @param {string} uploadId - Upload ID
|
|
293
|
-
* @param {Object} authConfig - Authentication configuration
|
|
294
|
-
* @returns {Promise<void>}
|
|
295
|
-
* @throws {Error} If validation fails
|
|
296
|
-
*/
|
|
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) {
|
|
304
|
-
logger.log(chalk.blue('\n📋 Changes to be published:'));
|
|
305
|
-
for (const change of changes) {
|
|
306
|
-
const changeType = change.type || 'unknown';
|
|
307
|
-
const changeEntity = change.entity || change.key || 'unknown';
|
|
308
|
-
const emoji = changeType === 'new' ? '➕' : changeType === 'modified' ? '✏️' : '🗑️';
|
|
309
|
-
logger.log(chalk.gray(` ${emoji} ${changeType}: ${changeEntity}`));
|
|
310
|
-
}
|
|
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);
|
|
336
|
-
|
|
337
|
-
if (validateData.summary) {
|
|
338
|
-
logger.log(chalk.blue(`\n📊 Summary: ${validateData.summary}`));
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
logger.log(chalk.green('✓ Validation successful'));
|
|
38
|
+
return { environment, controllerUrl, authConfig };
|
|
342
39
|
}
|
|
343
40
|
|
|
344
41
|
/**
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
* @param {string} uploadId - Upload ID
|
|
349
|
-
* @param {Object} authConfig - Authentication configuration
|
|
350
|
-
* @param {Object} options - Publish options
|
|
351
|
-
* @param {boolean} [options.generateMcpContract] - Generate MCP contract (default: true)
|
|
352
|
-
* @returns {Promise<Object>} Publish response data
|
|
353
|
-
* @throws {Error} If publish fails
|
|
354
|
-
*/
|
|
355
|
-
async function publishApplication(dataplaneUrl, uploadId, authConfig, options) {
|
|
356
|
-
const generateMcpContract = options.generateMcpContract !== false; // Default to true
|
|
357
|
-
logger.log(chalk.blue(`📢 Publishing application (MCP contract: ${generateMcpContract ? 'enabled' : 'disabled'})...`));
|
|
358
|
-
|
|
359
|
-
const publishResponse = await publishUploadViaPipeline(dataplaneUrl, uploadId, authConfig, { generateMcpContract });
|
|
360
|
-
|
|
361
|
-
if (!publishResponse.success || !publishResponse.data) {
|
|
362
|
-
throw new Error(`Failed to publish application: ${publishResponse.error || publishResponse.formattedError || 'Unknown error'}`);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return publishResponse.data.data || publishResponse.data;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Publishes external system to dataplane using application-level workflow
|
|
370
|
-
* Uses upload → validate → publish workflow for atomic deployment
|
|
42
|
+
* Deploys external system via controller pipeline (same as regular apps)
|
|
43
|
+
* Uses unified controller pipeline - no direct dataplane calls
|
|
44
|
+
*
|
|
371
45
|
* @async
|
|
372
46
|
* @function deployExternalSystem
|
|
373
47
|
* @param {string} appName - Application name
|
|
374
48
|
* @param {Object} options - Deployment options
|
|
375
49
|
* @param {string} [options.environment] - Environment (dev, tst, pro)
|
|
376
50
|
* @param {string} [options.controller] - Controller URL
|
|
377
|
-
* @param {boolean} [options.
|
|
378
|
-
* @param {
|
|
379
|
-
* @returns {Promise<
|
|
51
|
+
* @param {boolean} [options.poll] - Poll for deployment status
|
|
52
|
+
* @param {number} [options.pollInterval] - Polling interval in milliseconds (default: 500ms for external systems)
|
|
53
|
+
* @returns {Promise<Object>} Deployment result
|
|
380
54
|
* @throws {Error} If deployment fails
|
|
381
55
|
*/
|
|
382
56
|
async function deployExternalSystem(appName, options = {}) {
|
|
383
57
|
try {
|
|
384
|
-
logger.log(chalk.blue(`\n🚀
|
|
385
|
-
|
|
386
|
-
// Validate prerequisites
|
|
387
|
-
const { datasourceFiles } = await validateDeploymentPrerequisites(appName);
|
|
388
|
-
|
|
389
|
-
// Prepare deployment files and get authentication
|
|
390
|
-
const { applicationSchema, authConfig, controllerUrl, environment, systemKey } = await prepareDeploymentFiles(appName, options);
|
|
58
|
+
logger.log(chalk.blue(`\n🚀 Deploying external system: ${appName}`));
|
|
391
59
|
|
|
392
|
-
//
|
|
393
|
-
logger.log(chalk.blue('
|
|
394
|
-
const
|
|
395
|
-
logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
|
|
60
|
+
// Step 0: Validate before deployment (same as validate command)
|
|
61
|
+
logger.log(chalk.blue('🔍 Validating external system before deployment...'));
|
|
62
|
+
const validationResult = await validateExternalSystemComplete(appName);
|
|
396
63
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
// Step 2: Validate upload (optional, can be skipped)
|
|
401
|
-
if (!options.skipValidation) {
|
|
402
|
-
await validateUpload(dataplaneUrl, uploadId, authConfig);
|
|
403
|
-
} else {
|
|
404
|
-
logger.log(chalk.yellow('⚠ Skipping validation step'));
|
|
64
|
+
if (!validationResult.valid) {
|
|
65
|
+
displayValidationResults(validationResult);
|
|
66
|
+
throw new Error('Validation failed. Fix errors before deploying.');
|
|
405
67
|
}
|
|
406
68
|
|
|
407
|
-
|
|
408
|
-
|
|
69
|
+
logger.log(chalk.green('✓ Validation passed, proceeding with deployment...'));
|
|
70
|
+
|
|
71
|
+
// Step 1: Generate controller manifest (validated, ready for deployment)
|
|
72
|
+
const manifest = await generateControllerManifest(appName);
|
|
73
|
+
|
|
74
|
+
// Step 2: Get deployment configuration (auth, controller URL, etc.)
|
|
75
|
+
const { environment, controllerUrl, authConfig } = await prepareDeploymentConfig(appName, options);
|
|
76
|
+
|
|
77
|
+
// Step 3: Deploy via controller pipeline (same as regular apps)
|
|
78
|
+
// Use 500ms polling for external systems (faster than web apps which use 5000ms)
|
|
79
|
+
const deployer = require('../deployment/deployer');
|
|
80
|
+
const result = await deployer.deployToController(
|
|
81
|
+
manifest,
|
|
82
|
+
controllerUrl,
|
|
83
|
+
environment,
|
|
84
|
+
authConfig,
|
|
85
|
+
{
|
|
86
|
+
poll: options.poll,
|
|
87
|
+
pollInterval: options.pollInterval !== undefined ? options.pollInterval : 500,
|
|
88
|
+
pollMaxAttempts: options.pollMaxAttempts,
|
|
89
|
+
...options
|
|
90
|
+
}
|
|
91
|
+
);
|
|
409
92
|
|
|
410
93
|
// Display success summary
|
|
411
|
-
logger.log(chalk.green('\n✅ External system
|
|
412
|
-
logger.log(chalk.blue(`System: ${
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (publishData.dataSources && publishData.dataSources.length > 0) {
|
|
417
|
-
logger.log(chalk.blue(`Published datasources: ${publishData.dataSources.length}`));
|
|
418
|
-
} else {
|
|
419
|
-
logger.log(chalk.blue(`Datasources: ${datasourceFiles.length}`));
|
|
420
|
-
}
|
|
94
|
+
logger.log(chalk.green('\n✅ External system deployed successfully!'));
|
|
95
|
+
logger.log(chalk.blue(`System: ${manifest.key}`));
|
|
96
|
+
logger.log(chalk.blue(`Datasources: ${manifest.dataSources.length}`));
|
|
97
|
+
|
|
98
|
+
return result;
|
|
421
99
|
} catch (error) {
|
|
422
100
|
throw new Error(`Failed to deploy external system: ${error.message}`);
|
|
423
101
|
}
|
|
424
102
|
}
|
|
425
103
|
|
|
426
104
|
module.exports = {
|
|
427
|
-
|
|
428
|
-
deployExternalSystem,
|
|
429
|
-
validateExternalSystemFiles
|
|
105
|
+
deployExternalSystem
|
|
430
106
|
};
|
|
431
107
|
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* @version 2.0.0
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
const { generateExternalReadmeContent } = require('../utils/external-readme');
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
14
|
* Generates variables.yaml content for downloaded system
|
|
13
15
|
* @param {string} systemKey - System key
|
|
@@ -16,17 +18,31 @@
|
|
|
16
18
|
* @returns {Object} Variables YAML object
|
|
17
19
|
*/
|
|
18
20
|
function generateVariablesYaml(systemKey, application, dataSources) {
|
|
19
|
-
const systemFileName = `${systemKey}-
|
|
21
|
+
const systemFileName = `${systemKey}-system.json`;
|
|
20
22
|
const datasourceFiles = dataSources.map(ds => {
|
|
21
|
-
// Extract
|
|
22
|
-
const
|
|
23
|
-
|
|
23
|
+
// Extract datasource key (remove system key prefix if present)
|
|
24
|
+
const datasourceKey = ds.key || '';
|
|
25
|
+
let datasourceKeyOnly;
|
|
26
|
+
if (datasourceKey.startsWith(`${systemKey}-`)) {
|
|
27
|
+
datasourceKeyOnly = datasourceKey.substring(systemKey.length + 1);
|
|
28
|
+
} else {
|
|
29
|
+
const entityType = ds.entityType || ds.entityKey || datasourceKey.split('-').pop();
|
|
30
|
+
datasourceKeyOnly = entityType;
|
|
31
|
+
}
|
|
32
|
+
return `${systemKey}-datasource-${datasourceKeyOnly}.json`;
|
|
24
33
|
});
|
|
25
34
|
|
|
26
35
|
return {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
app: {
|
|
37
|
+
key: systemKey,
|
|
38
|
+
displayName: application.displayName || systemKey,
|
|
39
|
+
description: application.description || `External system integration for ${systemKey}`,
|
|
40
|
+
type: 'external'
|
|
41
|
+
},
|
|
42
|
+
deployment: {
|
|
43
|
+
controllerUrl: '',
|
|
44
|
+
environment: 'dev'
|
|
45
|
+
},
|
|
30
46
|
externalIntegration: {
|
|
31
47
|
schemaBasePath: './',
|
|
32
48
|
systems: [systemFileName],
|
|
@@ -45,66 +61,30 @@ function generateVariablesYaml(systemKey, application, dataSources) {
|
|
|
45
61
|
* @returns {string} README.md content
|
|
46
62
|
*/
|
|
47
63
|
function generateReadme(systemKey, application, dataSources) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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}`);
|
|
64
|
+
const datasources = (Array.isArray(dataSources) ? dataSources : []).map((ds, index) => {
|
|
65
|
+
const datasourceKey = ds.key || '';
|
|
66
|
+
let datasourceKeyOnly;
|
|
67
|
+
if (datasourceKey.startsWith(`${systemKey}-`)) {
|
|
68
|
+
datasourceKeyOnly = datasourceKey.substring(systemKey.length + 1);
|
|
69
|
+
} else {
|
|
70
|
+
const entityType = ds.entityType || ds.entityKey || datasourceKey.split('-').pop() || `entity${index + 1}`;
|
|
71
|
+
datasourceKeyOnly = entityType;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
entityType: datasourceKeyOnly,
|
|
75
|
+
displayName: ds.displayName || ds.name || ds.key || `Datasource ${index + 1}`,
|
|
76
|
+
fileName: `${systemKey}-datasource-${datasourceKeyOnly}.json`
|
|
77
|
+
};
|
|
72
78
|
});
|
|
73
79
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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');
|
|
80
|
+
return generateExternalReadmeContent({
|
|
81
|
+
appName: systemKey,
|
|
82
|
+
systemKey,
|
|
83
|
+
systemType: application.type,
|
|
84
|
+
displayName: application.displayName,
|
|
85
|
+
description: application.description,
|
|
86
|
+
datasources
|
|
87
|
+
});
|
|
108
88
|
}
|
|
109
89
|
|
|
110
90
|
module.exports = {
|