@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
|
@@ -16,12 +16,12 @@ const yaml = require('js-yaml');
|
|
|
16
16
|
const chalk = require('chalk');
|
|
17
17
|
const { getExternalSystemConfig } = require('../api/external-systems.api');
|
|
18
18
|
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
19
|
-
const { getDataplaneUrl } = require('../datasource/deploy');
|
|
20
19
|
const { getConfig } = require('../core/config');
|
|
21
20
|
const { detectAppType } = require('../utils/paths');
|
|
22
21
|
const logger = require('../utils/logger');
|
|
23
22
|
const { generateEnvTemplate } = require('../utils/external-system-env-helpers');
|
|
24
23
|
const { generateVariablesYaml, generateReadme } = require('./download-helpers');
|
|
24
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Validates system type from downloaded application
|
|
@@ -124,17 +124,19 @@ function handlePartialDownload(systemKey, systemData, datasourceErrors) {
|
|
|
124
124
|
* @returns {Promise<Object>} Object with authConfig and dataplaneUrl
|
|
125
125
|
* @throws {Error} If authentication fails
|
|
126
126
|
*/
|
|
127
|
-
async function setupAuthenticationAndDataplane(systemKey,
|
|
128
|
-
const
|
|
129
|
-
const
|
|
127
|
+
async function setupAuthenticationAndDataplane(systemKey, _options, _config) {
|
|
128
|
+
const { resolveEnvironment } = require('../core/config');
|
|
129
|
+
const environment = await resolveEnvironment();
|
|
130
|
+
const controllerUrl = await resolveControllerUrl();
|
|
130
131
|
const authConfig = await getDeploymentAuth(controllerUrl, environment, systemKey);
|
|
131
132
|
|
|
132
133
|
if (!authConfig.token && !authConfig.clientId) {
|
|
133
134
|
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
138
|
+
logger.log(chalk.blue('🌐 Resolving dataplane URL...'));
|
|
139
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
138
140
|
logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
|
|
139
141
|
|
|
140
142
|
return { authConfig, dataplaneUrl };
|
|
@@ -158,8 +160,19 @@ async function downloadSystemConfiguration(dataplaneUrl, systemKey, authConfig)
|
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
const downloadData = response.data.data || response.data;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
let application = downloadData.application;
|
|
164
|
+
let dataSources = downloadData.dataSources || [];
|
|
165
|
+
|
|
166
|
+
// Handle case where datasources are inline in application.configuration.dataSources
|
|
167
|
+
if (!application && downloadData.configuration) {
|
|
168
|
+
application = downloadData;
|
|
169
|
+
}
|
|
170
|
+
if (application && application.configuration && Array.isArray(application.configuration.dataSources)) {
|
|
171
|
+
dataSources = [...dataSources, ...application.configuration.dataSources];
|
|
172
|
+
// Remove inline datasources from application to avoid duplication
|
|
173
|
+
const { dataSources: _inlineDataSources, ...configWithoutDataSources } = application.configuration;
|
|
174
|
+
application = { ...application, configuration: configWithoutDataSources };
|
|
175
|
+
}
|
|
163
176
|
|
|
164
177
|
if (!application) {
|
|
165
178
|
throw new Error('Application configuration not found in download response');
|
|
@@ -188,7 +201,7 @@ async function downloadSystemConfiguration(dataplaneUrl, systemKey, authConfig)
|
|
|
188
201
|
* @returns {Promise<string>} System file path
|
|
189
202
|
*/
|
|
190
203
|
async function generateSystemFile(tempDir, systemKey, application) {
|
|
191
|
-
const systemFileName = `${systemKey}-
|
|
204
|
+
const systemFileName = `${systemKey}-system.json`;
|
|
192
205
|
const systemFilePath = path.join(tempDir, systemFileName);
|
|
193
206
|
await fs.writeFile(systemFilePath, JSON.stringify(application, null, 2), 'utf8');
|
|
194
207
|
return systemFilePath;
|
|
@@ -208,8 +221,16 @@ async function generateDatasourceFiles(tempDir, systemKey, dataSources) {
|
|
|
208
221
|
const datasourceFiles = [];
|
|
209
222
|
for (const datasource of dataSources) {
|
|
210
223
|
try {
|
|
211
|
-
const
|
|
212
|
-
|
|
224
|
+
const datasourceKey = datasource.key || '';
|
|
225
|
+
// Extract datasource key (remove system key prefix if present)
|
|
226
|
+
let datasourceKeyOnly;
|
|
227
|
+
if (datasourceKey.startsWith(`${systemKey}-`)) {
|
|
228
|
+
datasourceKeyOnly = datasourceKey.substring(systemKey.length + 1);
|
|
229
|
+
} else {
|
|
230
|
+
const entityType = datasource.entityType || datasource.entityKey || datasourceKey.split('-').pop();
|
|
231
|
+
datasourceKeyOnly = entityType;
|
|
232
|
+
}
|
|
233
|
+
const datasourceFileName = `${systemKey}-datasource-${datasourceKeyOnly}.json`;
|
|
213
234
|
const datasourceFilePath = path.join(tempDir, datasourceFileName);
|
|
214
235
|
await fs.writeFile(datasourceFilePath, JSON.stringify(datasource, null, 2), 'utf8');
|
|
215
236
|
datasourceFiles.push(datasourceFilePath);
|
|
@@ -283,7 +304,7 @@ async function moveFilesToFinalLocation(tempDir, finalPath, systemKey, filePaths
|
|
|
283
304
|
logger.log(chalk.blue(`📁 Creating directory: ${finalPath}`));
|
|
284
305
|
await fs.mkdir(finalPath, { recursive: true });
|
|
285
306
|
|
|
286
|
-
const systemFileName = `${systemKey}-
|
|
307
|
+
const systemFileName = `${systemKey}-system.json`;
|
|
287
308
|
const filesToMove = [
|
|
288
309
|
{ from: filePaths.systemFilePath, to: path.join(finalPath, systemFileName) },
|
|
289
310
|
{ from: filePaths.variablesPath, to: path.join(finalPath, 'variables.yaml') },
|
|
@@ -327,7 +348,7 @@ function handleDryRun(systemKey, dataplaneUrl) {
|
|
|
327
348
|
logger.log(chalk.yellow('\nWould create:'));
|
|
328
349
|
logger.log(chalk.gray(` integration/${systemKey}/`));
|
|
329
350
|
logger.log(chalk.gray(` integration/${systemKey}/variables.yaml`));
|
|
330
|
-
logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-
|
|
351
|
+
logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-system.json`));
|
|
331
352
|
logger.log(chalk.gray(` integration/${systemKey}/env.template`));
|
|
332
353
|
logger.log(chalk.gray(` integration/${systemKey}/README.md`));
|
|
333
354
|
}
|
|
@@ -48,8 +48,8 @@ async function generateExternalSystemTemplate(appPath, systemKey, config) {
|
|
|
48
48
|
const rendered = template(context);
|
|
49
49
|
|
|
50
50
|
// Generate in same folder as variables.yaml (new structure)
|
|
51
|
-
// Use naming: <app-name>-
|
|
52
|
-
const outputPath = path.join(appPath, `${systemKey}-
|
|
51
|
+
// Use naming: <app-name>-system.json
|
|
52
|
+
const outputPath = path.join(appPath, `${systemKey}-system.json`);
|
|
53
53
|
await fs.writeFile(outputPath, rendered, 'utf8');
|
|
54
54
|
|
|
55
55
|
return outputPath;
|
|
@@ -89,8 +89,12 @@ async function generateExternalDataSourceTemplate(appPath, datasourceKey, config
|
|
|
89
89
|
const rendered = template(context);
|
|
90
90
|
|
|
91
91
|
// Generate in same folder as variables.yaml (new structure)
|
|
92
|
-
// Use naming: <app-name>-
|
|
93
|
-
|
|
92
|
+
// Use naming: <app-name>-datasource-<datasource-key>.json
|
|
93
|
+
// Extract datasource key (remove system key prefix if present)
|
|
94
|
+
const datasourceKeyOnly = datasourceKey.includes('-') && datasourceKey.startsWith(`${config.systemKey}-`)
|
|
95
|
+
? datasourceKey.substring(config.systemKey.length + 1)
|
|
96
|
+
: datasourceKey;
|
|
97
|
+
const outputPath = path.join(appPath, `${config.systemKey}-datasource-${datasourceKeyOnly}.json`);
|
|
94
98
|
await fs.writeFile(outputPath, rendered, 'utf8');
|
|
95
99
|
|
|
96
100
|
return outputPath;
|
|
@@ -139,8 +143,8 @@ async function generateExternalSystemFiles(appPath, appName, config) {
|
|
|
139
143
|
attributes: config.attributes || {}
|
|
140
144
|
};
|
|
141
145
|
|
|
142
|
-
// Generate with
|
|
143
|
-
const datasourcePath = await generateExternalDataSourceTemplate(appPath,
|
|
146
|
+
// Generate with new naming: <app-name>-datasource-<entity-key>.json
|
|
147
|
+
const datasourcePath = await generateExternalDataSourceTemplate(appPath, datasourceKey, datasourceConfig);
|
|
144
148
|
datasourcePaths.push(datasourcePath);
|
|
145
149
|
logger.log(chalk.green(`✓ Generated datasource: ${path.basename(datasourcePath)}`));
|
|
146
150
|
}
|
|
@@ -176,7 +180,7 @@ async function updateVariablesYamlWithExternalIntegration(appPath, systemKey, da
|
|
|
176
180
|
// Files are in same folder, so schemaBasePath is './'
|
|
177
181
|
variables.externalIntegration = {
|
|
178
182
|
schemaBasePath: './',
|
|
179
|
-
systems: [`${systemKey}-
|
|
183
|
+
systems: [`${systemKey}-system.json`],
|
|
180
184
|
dataSources: datasourcePaths.map(p => path.basename(p)),
|
|
181
185
|
autopublish: true,
|
|
182
186
|
version: '1.0.0'
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
12
12
|
const { getDataplaneUrl } = require('../datasource/deploy');
|
|
13
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Setup authentication and get dataplane URL for integration tests
|
|
@@ -20,9 +21,10 @@ const { getDataplaneUrl } = require('../datasource/deploy');
|
|
|
20
21
|
* @returns {Promise<Object>} Object with authConfig and dataplaneUrl
|
|
21
22
|
* @throws {Error} If authentication fails
|
|
22
23
|
*/
|
|
23
|
-
async function setupIntegrationTestAuth(appName,
|
|
24
|
-
const
|
|
25
|
-
const
|
|
24
|
+
async function setupIntegrationTestAuth(appName, _options, _config) {
|
|
25
|
+
const { resolveEnvironment } = require('../core/config');
|
|
26
|
+
const environment = await resolveEnvironment();
|
|
27
|
+
const controllerUrl = await resolveControllerUrl();
|
|
26
28
|
const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
|
|
27
29
|
|
|
28
30
|
if (!authConfig.token && !authConfig.clientId) {
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* @version 2.0.0
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
const { getContainerPort } = require('../utils/port-resolver');
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
14
|
* Sanitizes authentication type - map keycloak to azure (schema allows: azure, local, none)
|
|
13
15
|
* @function sanitizeAuthType
|
|
@@ -169,7 +171,7 @@ function buildBaseDeployment(appName, variables, filteredConfiguration) {
|
|
|
169
171
|
return {
|
|
170
172
|
...appMetadata,
|
|
171
173
|
...imageConfig,
|
|
172
|
-
port: variables
|
|
174
|
+
port: getContainerPort(variables, 3000),
|
|
173
175
|
...requirementsConfig,
|
|
174
176
|
configuration: filteredConfiguration
|
|
175
177
|
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External System Controller Manifest Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates controller-compatible deployment manifest for external systems.
|
|
5
|
+
* Creates manifest with inline system + dataSources in controller format.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Controller manifest generation for external systems
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { detectAppType } = require('../utils/paths');
|
|
14
|
+
const { generateDeploymentKeyFromJson } = require('../core/key-generator');
|
|
15
|
+
const { loadSystemFile, loadDatasourceFiles } = require('./external');
|
|
16
|
+
const { loadVariables, loadRbac } = require('./helpers');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Merges RBAC into system JSON
|
|
20
|
+
* @function mergeRbacIntoSystemJson
|
|
21
|
+
* @param {Object} systemJson - System JSON object
|
|
22
|
+
* @param {Object|null} rbac - RBAC configuration
|
|
23
|
+
*/
|
|
24
|
+
function mergeRbacIntoSystemJson(systemJson, rbac) {
|
|
25
|
+
if (!rbac) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Priority: roles/permissions in system JSON > rbac.yaml (if both exist, prefer JSON)
|
|
30
|
+
if (rbac.roles && (!systemJson.roles || systemJson.roles.length === 0)) {
|
|
31
|
+
systemJson.roles = rbac.roles;
|
|
32
|
+
}
|
|
33
|
+
if (rbac.permissions && (!systemJson.permissions || systemJson.permissions.length === 0)) {
|
|
34
|
+
systemJson.permissions = rbac.permissions;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Resolves application path from options or detection
|
|
40
|
+
* @async
|
|
41
|
+
* @function resolveAppPath
|
|
42
|
+
* @param {string} appName - Application name
|
|
43
|
+
* @param {Object} options - Options with optional appPath
|
|
44
|
+
* @returns {Promise<string>} Application path
|
|
45
|
+
*/
|
|
46
|
+
async function resolveAppPath(appName, options) {
|
|
47
|
+
if (options.appPath) {
|
|
48
|
+
return options.appPath;
|
|
49
|
+
}
|
|
50
|
+
const detected = await detectAppType(appName, { type: 'external' });
|
|
51
|
+
return detected.appPath;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extracts app metadata from variables
|
|
56
|
+
* @function extractAppMetadata
|
|
57
|
+
* @param {Object} variables - Parsed variables.yaml
|
|
58
|
+
* @param {string} appName - Application name
|
|
59
|
+
* @returns {Object} App metadata { appKey, displayName, description }
|
|
60
|
+
*/
|
|
61
|
+
function extractAppMetadata(variables, appName) {
|
|
62
|
+
return {
|
|
63
|
+
appKey: variables.app?.key || appName,
|
|
64
|
+
displayName: variables.app?.displayName || appName,
|
|
65
|
+
description: variables.app?.description || `External system integration for ${appName}`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Loads and merges system with RBAC
|
|
71
|
+
* @async
|
|
72
|
+
* @function loadSystemWithRbac
|
|
73
|
+
* @param {string} appPath - Application path
|
|
74
|
+
* @param {string} schemaBasePath - Schema base path
|
|
75
|
+
* @param {string} systemFile - System file path
|
|
76
|
+
* @returns {Promise<Object>} System JSON with RBAC merged
|
|
77
|
+
*/
|
|
78
|
+
async function loadSystemWithRbac(appPath, schemaBasePath, systemFile) {
|
|
79
|
+
const systemJson = await loadSystemFile(appPath, schemaBasePath, systemFile);
|
|
80
|
+
const rbacPath = path.join(appPath, 'rbac.yaml');
|
|
81
|
+
const rbac = loadRbac(rbacPath);
|
|
82
|
+
mergeRbacIntoSystemJson(systemJson, rbac);
|
|
83
|
+
return systemJson;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Generates controller-compatible deployment manifest for external systems
|
|
88
|
+
* Creates manifest with inline system + dataSources in controller format
|
|
89
|
+
*
|
|
90
|
+
* @async
|
|
91
|
+
* @function generateControllerManifest
|
|
92
|
+
* @param {string} appName - Application name
|
|
93
|
+
* @param {Object} [options] - Optional parameters
|
|
94
|
+
* @param {string} [options.appPath] - Application path (if provided, skips detection)
|
|
95
|
+
* @returns {Promise<Object>} Controller manifest object
|
|
96
|
+
* @throws {Error} If generation fails
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* const manifest = await generateControllerManifest('my-hubspot');
|
|
100
|
+
* // Returns: { key, displayName, description, type: "external", system: {...}, dataSources: [...], deploymentKey }
|
|
101
|
+
*/
|
|
102
|
+
async function generateControllerManifest(appName, options = {}) {
|
|
103
|
+
if (!appName || typeof appName !== 'string') {
|
|
104
|
+
throw new Error('App name is required and must be a string');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const appPath = await resolveAppPath(appName, options);
|
|
108
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
109
|
+
const { parsed: variables } = loadVariables(variablesPath);
|
|
110
|
+
|
|
111
|
+
if (!variables.externalIntegration) {
|
|
112
|
+
throw new Error('externalIntegration block not found in variables.yaml');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const metadata = extractAppMetadata(variables, appName);
|
|
116
|
+
const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
|
|
117
|
+
const systemFiles = variables.externalIntegration.systems || [];
|
|
118
|
+
|
|
119
|
+
if (systemFiles.length === 0) {
|
|
120
|
+
throw new Error('No system files specified in externalIntegration.systems');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const systemJson = await loadSystemWithRbac(appPath, schemaBasePath, systemFiles[0]);
|
|
124
|
+
const datasourceFiles = variables.externalIntegration.dataSources || [];
|
|
125
|
+
const datasourceJsons = await loadDatasourceFiles(appPath, schemaBasePath, datasourceFiles);
|
|
126
|
+
|
|
127
|
+
// Build externalIntegration block (required by application schema for type: "external")
|
|
128
|
+
const externalIntegration = {
|
|
129
|
+
schemaBasePath: schemaBasePath,
|
|
130
|
+
systems: systemFiles,
|
|
131
|
+
dataSources: datasourceFiles,
|
|
132
|
+
autopublish: variables.externalIntegration.autopublish !== false, // default true
|
|
133
|
+
version: variables.externalIntegration.version || '1.0.0'
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const manifest = {
|
|
137
|
+
key: metadata.appKey,
|
|
138
|
+
displayName: metadata.displayName,
|
|
139
|
+
description: metadata.description,
|
|
140
|
+
type: 'external',
|
|
141
|
+
externalIntegration: externalIntegration,
|
|
142
|
+
// Inline system and dataSources for atomic deployment (optional but recommended)
|
|
143
|
+
system: systemJson,
|
|
144
|
+
dataSources: datasourceJsons,
|
|
145
|
+
// Explicitly set to false to satisfy conditional schema requirements
|
|
146
|
+
requiresDatabase: false,
|
|
147
|
+
requiresRedis: false,
|
|
148
|
+
requiresStorage: false
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
manifest.deploymentKey = generateDeploymentKeyFromJson(manifest);
|
|
152
|
+
return manifest;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = {
|
|
156
|
+
generateControllerManifest
|
|
157
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External Schema Split Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helpers for splitting application-schema.json into component files.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview External schema split utilities for AI Fabrix Builder
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const yaml = require('js-yaml');
|
|
15
|
+
const { generateEnvTemplate } = require('../utils/external-system-env-helpers');
|
|
16
|
+
const { extractRbacYaml } = require('./split');
|
|
17
|
+
const { generateExternalReadmeContent } = require('../utils/external-readme');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parses application-schema.json content
|
|
21
|
+
* @function parseApplicationSchema
|
|
22
|
+
* @param {string} schemaPath - Schema file path
|
|
23
|
+
* @param {string} content - Raw JSON content
|
|
24
|
+
* @returns {Object} Parsed schema details
|
|
25
|
+
* @throws {Error} If JSON or schema structure is invalid
|
|
26
|
+
*/
|
|
27
|
+
function parseApplicationSchema(schemaPath, content) {
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = JSON.parse(content);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(`Invalid JSON syntax in ${schemaPath}: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const application = parsed.application;
|
|
36
|
+
const dataSources = Array.isArray(parsed.dataSources) ? parsed.dataSources : [];
|
|
37
|
+
if (!application || typeof application !== 'object') {
|
|
38
|
+
throw new Error('application-schema.json must include an "application" object');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { application, dataSources, version: parsed.version };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Extracts system key from application schema
|
|
46
|
+
* @function getSystemKey
|
|
47
|
+
* @param {Object} application - Application schema object
|
|
48
|
+
* @returns {string} System key
|
|
49
|
+
* @throws {Error} If system key is missing
|
|
50
|
+
*/
|
|
51
|
+
function getSystemKey(application) {
|
|
52
|
+
const systemKey = application.key;
|
|
53
|
+
if (!systemKey || typeof systemKey !== 'string') {
|
|
54
|
+
throw new Error('application.key is required to split external system files');
|
|
55
|
+
}
|
|
56
|
+
return systemKey;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Writes JSON file with formatting
|
|
61
|
+
* @async
|
|
62
|
+
* @function writeJsonFile
|
|
63
|
+
* @param {string} filePath - File path
|
|
64
|
+
* @param {Object} data - JSON data
|
|
65
|
+
* @returns {Promise<void>} Resolves when file is written
|
|
66
|
+
*/
|
|
67
|
+
async function writeJsonFile(filePath, data) {
|
|
68
|
+
await fs.promises.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolves datasource entity type
|
|
73
|
+
* @function getDatasourceEntityType
|
|
74
|
+
* @param {Object} datasource - Datasource schema
|
|
75
|
+
* @param {number} index - Datasource index
|
|
76
|
+
* @returns {string} Entity type
|
|
77
|
+
*/
|
|
78
|
+
function getDatasourceEntityType(datasource, index) {
|
|
79
|
+
return (
|
|
80
|
+
datasource.entityType ||
|
|
81
|
+
datasource.entityKey ||
|
|
82
|
+
datasource.key?.split('-').pop() ||
|
|
83
|
+
`entity${index + 1}`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Builds datasource file name
|
|
89
|
+
* @function getDatasourceFileName
|
|
90
|
+
* @param {string} systemKey - System key
|
|
91
|
+
* @param {Object} datasource - Datasource schema
|
|
92
|
+
* @param {number} index - Datasource index
|
|
93
|
+
* @returns {string} Datasource file name
|
|
94
|
+
*/
|
|
95
|
+
function getDatasourceFileName(systemKey, datasource, index) {
|
|
96
|
+
const datasourceKey = datasource.key || '';
|
|
97
|
+
// Extract datasource key (remove system key prefix if present)
|
|
98
|
+
let datasourceKeyOnly;
|
|
99
|
+
if (datasourceKey.startsWith(`${systemKey}-`)) {
|
|
100
|
+
datasourceKeyOnly = datasourceKey.substring(systemKey.length + 1);
|
|
101
|
+
} else if (datasourceKey.startsWith(`${systemKey}-deploy-`)) {
|
|
102
|
+
// Support old naming format during migration
|
|
103
|
+
datasourceKeyOnly = datasourceKey.substring(`${systemKey}-deploy-`.length);
|
|
104
|
+
} else {
|
|
105
|
+
datasourceKeyOnly = getDatasourceEntityType(datasource, index);
|
|
106
|
+
}
|
|
107
|
+
return `${systemKey}-datasource-${datasourceKeyOnly}.json`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Writes datasource files and returns file names
|
|
112
|
+
* @async
|
|
113
|
+
* @function writeDatasourceFiles
|
|
114
|
+
* @param {string} outputDir - Output directory
|
|
115
|
+
* @param {string} systemKey - System key
|
|
116
|
+
* @param {Object[]} dataSources - Datasource schemas
|
|
117
|
+
* @returns {Promise<string[]>} Datasource file names
|
|
118
|
+
*/
|
|
119
|
+
async function writeDatasourceFiles(outputDir, systemKey, dataSources) {
|
|
120
|
+
const datasourceFileNames = [];
|
|
121
|
+
for (let i = 0; i < dataSources.length; i += 1) {
|
|
122
|
+
const datasource = dataSources[i];
|
|
123
|
+
const datasourceFileName = getDatasourceFileName(systemKey, datasource, i);
|
|
124
|
+
const datasourceFilePath = path.join(outputDir, datasourceFileName);
|
|
125
|
+
await writeJsonFile(datasourceFilePath, datasource);
|
|
126
|
+
datasourceFileNames.push(datasourceFileName);
|
|
127
|
+
}
|
|
128
|
+
return datasourceFileNames;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Builds variables.yaml content for external integrations
|
|
133
|
+
* @function buildExternalVariables
|
|
134
|
+
* @param {string} systemKey - System key
|
|
135
|
+
* @param {Object} application - Application schema
|
|
136
|
+
* @param {string} systemFileName - System file name
|
|
137
|
+
* @param {string[]} datasourceFileNames - Datasource file names
|
|
138
|
+
* @param {string} version - Schema version
|
|
139
|
+
* @returns {Object} Variables content
|
|
140
|
+
*/
|
|
141
|
+
function buildExternalVariables(systemKey, application, systemFileName, datasourceFileNames, version) {
|
|
142
|
+
return {
|
|
143
|
+
app: {
|
|
144
|
+
key: systemKey,
|
|
145
|
+
displayName: application.displayName || systemKey,
|
|
146
|
+
description: application.description || `External system integration for ${systemKey}`,
|
|
147
|
+
type: 'external'
|
|
148
|
+
},
|
|
149
|
+
deployment: {
|
|
150
|
+
controllerUrl: '',
|
|
151
|
+
environment: 'dev'
|
|
152
|
+
},
|
|
153
|
+
externalIntegration: {
|
|
154
|
+
schemaBasePath: './',
|
|
155
|
+
systems: [systemFileName],
|
|
156
|
+
dataSources: datasourceFileNames,
|
|
157
|
+
autopublish: true,
|
|
158
|
+
version
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Writes YAML file
|
|
165
|
+
* @async
|
|
166
|
+
* @function writeYamlFile
|
|
167
|
+
* @param {string} filePath - File path
|
|
168
|
+
* @param {Object} data - YAML data
|
|
169
|
+
* @param {Object} options - YAML options
|
|
170
|
+
* @returns {Promise<void>} Resolves when file is written
|
|
171
|
+
*/
|
|
172
|
+
async function writeYamlFile(filePath, data, options) {
|
|
173
|
+
const yamlContent = yaml.dump(data, options);
|
|
174
|
+
await fs.promises.writeFile(filePath, yamlContent, 'utf8');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Writes split files for external application schemas
|
|
179
|
+
* @async
|
|
180
|
+
* @function writeSplitExternalSchemaFiles
|
|
181
|
+
* @param {Object} params - Split parameters
|
|
182
|
+
* @param {string} params.outputDir - Output directory
|
|
183
|
+
* @param {string} params.systemKey - System key
|
|
184
|
+
* @param {Object} params.application - Application schema
|
|
185
|
+
* @param {Object[]} params.dataSources - Datasource schemas
|
|
186
|
+
* @param {string} params.version - Schema version
|
|
187
|
+
* @returns {Promise<Object>} Paths to generated files
|
|
188
|
+
*/
|
|
189
|
+
async function writeSplitExternalSchemaFiles({ outputDir, systemKey, application, dataSources, version }) {
|
|
190
|
+
const systemFileName = `${systemKey}-system.json`;
|
|
191
|
+
const systemFilePath = path.join(outputDir, systemFileName);
|
|
192
|
+
await writeJsonFile(systemFilePath, application);
|
|
193
|
+
|
|
194
|
+
const datasourceFileNames = await writeDatasourceFiles(outputDir, systemKey, dataSources);
|
|
195
|
+
const variables = buildExternalVariables(systemKey, application, systemFileName, datasourceFileNames, version);
|
|
196
|
+
|
|
197
|
+
const variablesPath = path.join(outputDir, 'variables.yaml');
|
|
198
|
+
await writeYamlFile(variablesPath, variables, { indent: 2, lineWidth: 120, noRefs: true });
|
|
199
|
+
|
|
200
|
+
const envTemplatePath = path.join(outputDir, 'env.template');
|
|
201
|
+
const envTemplate = generateEnvTemplate(application);
|
|
202
|
+
await fs.promises.writeFile(envTemplatePath, envTemplate, 'utf8');
|
|
203
|
+
|
|
204
|
+
const rbac = extractRbacYaml(application);
|
|
205
|
+
let rbacPath = null;
|
|
206
|
+
if (rbac) {
|
|
207
|
+
rbacPath = path.join(outputDir, 'rbac.yml');
|
|
208
|
+
await writeYamlFile(rbacPath, rbac, { indent: 2, lineWidth: -1 });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const readmeContent = generateExternalReadmeContent({
|
|
212
|
+
appName: systemKey,
|
|
213
|
+
systemKey,
|
|
214
|
+
systemType: application.type,
|
|
215
|
+
displayName: application.displayName,
|
|
216
|
+
description: application.description,
|
|
217
|
+
datasources: dataSources
|
|
218
|
+
});
|
|
219
|
+
const readmePath = path.join(outputDir, 'README.md');
|
|
220
|
+
await fs.promises.writeFile(readmePath, readmeContent, 'utf8');
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
systemFile: systemFilePath,
|
|
224
|
+
datasourceFiles: datasourceFileNames.map(name => path.join(outputDir, name)),
|
|
225
|
+
variables: variablesPath,
|
|
226
|
+
envTemplate: envTemplatePath,
|
|
227
|
+
rbac: rbacPath,
|
|
228
|
+
readme: readmePath
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = {
|
|
233
|
+
parseApplicationSchema,
|
|
234
|
+
getSystemKey,
|
|
235
|
+
writeSplitExternalSchemaFiles
|
|
236
|
+
};
|
|
@@ -13,6 +13,11 @@ const path = require('path');
|
|
|
13
13
|
const Ajv = require('ajv');
|
|
14
14
|
const { detectAppType, getDeployJsonPath } = require('../utils/paths');
|
|
15
15
|
const { loadVariables, loadRbac } = require('./helpers');
|
|
16
|
+
const {
|
|
17
|
+
parseApplicationSchema,
|
|
18
|
+
getSystemKey,
|
|
19
|
+
writeSplitExternalSchemaFiles
|
|
20
|
+
} = require('./external-schema-utils');
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
23
|
* Generates external system <app-name>-deploy.json by loading the system JSON file
|
|
@@ -36,15 +41,26 @@ const { loadVariables, loadRbac } = require('./helpers');
|
|
|
36
41
|
function resolveSystemFilePath(variables, appPath, appName) {
|
|
37
42
|
const systemFileName = variables.externalIntegration.systems && variables.externalIntegration.systems.length > 0
|
|
38
43
|
? variables.externalIntegration.systems[0]
|
|
39
|
-
: `${appName}-
|
|
44
|
+
: `${appName}-system.json`;
|
|
40
45
|
|
|
41
46
|
const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
|
|
42
47
|
const systemFilePath = path.isAbsolute(schemaBasePath)
|
|
43
48
|
? path.join(schemaBasePath, systemFileName)
|
|
44
49
|
: path.join(appPath, schemaBasePath, systemFileName);
|
|
45
50
|
|
|
51
|
+
// Support both old and new naming for backward compatibility
|
|
46
52
|
if (!fs.existsSync(systemFilePath)) {
|
|
47
|
-
|
|
53
|
+
// Try old naming format
|
|
54
|
+
const oldSystemFileName = systemFileName.replace(/-system\.json$/, '-deploy.json');
|
|
55
|
+
const oldSystemFilePath = path.isAbsolute(schemaBasePath)
|
|
56
|
+
? path.join(schemaBasePath, oldSystemFileName)
|
|
57
|
+
: path.join(appPath, schemaBasePath, oldSystemFileName);
|
|
58
|
+
|
|
59
|
+
if (fs.existsSync(oldSystemFilePath)) {
|
|
60
|
+
return oldSystemFilePath;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
throw new Error(`External system file not found: ${systemFilePath} (also checked: ${oldSystemFilePath}). Please create it first.`);
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
return systemFilePath;
|
|
@@ -358,8 +374,44 @@ async function generateExternalSystemApplicationSchema(appName) {
|
|
|
358
374
|
return applicationSchema;
|
|
359
375
|
}
|
|
360
376
|
|
|
377
|
+
/**
|
|
378
|
+
* Splits application-schema.json into component files for external systems
|
|
379
|
+
* @async
|
|
380
|
+
* @function splitExternalApplicationSchema
|
|
381
|
+
* @param {string} schemaPath - Path to application-schema.json
|
|
382
|
+
* @param {string} outputDir - Output directory for split files
|
|
383
|
+
* @returns {Promise<Object>} Paths to generated files
|
|
384
|
+
* @throws {Error} If schema is invalid or files cannot be written
|
|
385
|
+
*/
|
|
386
|
+
async function splitExternalApplicationSchema(schemaPath, outputDir) {
|
|
387
|
+
if (!schemaPath || typeof schemaPath !== 'string') {
|
|
388
|
+
throw new Error('Schema path is required and must be a string');
|
|
389
|
+
}
|
|
390
|
+
if (!outputDir || typeof outputDir !== 'string') {
|
|
391
|
+
throw new Error('Output directory is required and must be a string');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const content = await fs.promises.readFile(schemaPath, 'utf8');
|
|
395
|
+
const { application, dataSources, version } = parseApplicationSchema(schemaPath, content);
|
|
396
|
+
const systemKey = getSystemKey(application);
|
|
397
|
+
|
|
398
|
+
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
399
|
+
|
|
400
|
+
const schemaVersion = version || application.version || '1.0.0';
|
|
401
|
+
return writeSplitExternalSchemaFiles({
|
|
402
|
+
outputDir,
|
|
403
|
+
systemKey,
|
|
404
|
+
application,
|
|
405
|
+
dataSources,
|
|
406
|
+
version: schemaVersion
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
361
410
|
module.exports = {
|
|
362
411
|
generateExternalSystemDeployJson,
|
|
363
|
-
generateExternalSystemApplicationSchema
|
|
412
|
+
generateExternalSystemApplicationSchema,
|
|
413
|
+
splitExternalApplicationSchema,
|
|
414
|
+
loadSystemFile,
|
|
415
|
+
loadDatasourceFiles
|
|
364
416
|
};
|
|
365
417
|
|