@aifabrix/builder 2.32.3 → 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/types/wizard.types.js +176 -38
- package/lib/api/wizard.api.js +161 -23
- 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 +17 -10
- package/lib/app/readme.js +41 -112
- package/lib/app/register.js +44 -9
- package/lib/app/rotate-secret.js +48 -31
- package/lib/cli.js +219 -70
- package/lib/commands/app.js +4 -9
- package/lib/commands/auth-config.js +125 -0
- package/lib/commands/auth-status.js +7 -8
- package/lib/commands/datasource.js +3 -6
- package/lib/commands/login-credentials.js +4 -4
- package/lib/commands/login-device.js +26 -17
- package/lib/commands/login.js +12 -10
- 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 +110 -332
- 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 +29 -21
- 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 +53 -378
- package/lib/external-system/download-helpers.js +45 -65
- package/lib/external-system/download.js +33 -13
- package/lib/external-system/generator.js +11 -7
- package/lib/external-system/test-auth.js +4 -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 +23 -4
- package/lib/schema/external-datasource.schema.json +2 -2
- package/lib/schema/wizard-config.schema.json +234 -0
- package/lib/utils/api.js +32 -50
- package/lib/utils/app-existence.js +42 -0
- package/lib/utils/app-register-config.js +7 -2
- package/lib/utils/auth-config-validator.js +92 -0
- package/lib/utils/command-header.js +43 -0
- package/lib/utils/compose-generator.js +113 -70
- package/lib/utils/controller-url.js +65 -17
- 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-ports.js +2 -1
- package/lib/utils/env-template.js +1 -1
- package/lib/utils/error-formatter.js +49 -0
- 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 +9 -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 +36 -3
- package/lib/validation/wizard-config-validator.js +267 -0
- package/package.json +4 -2
- package/templates/applications/README.md.hbs +18 -16
- 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/infra/compose.yaml.hbs +35 -0
- package/templates/python/docker-compose.hbs +26 -0
- package/templates/typescript/docker-compose.hbs +26 -0
|
@@ -16,7 +16,6 @@ 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');
|
|
@@ -125,17 +124,19 @@ function handlePartialDownload(systemKey, systemData, datasourceErrors) {
|
|
|
125
124
|
* @returns {Promise<Object>} Object with authConfig and dataplaneUrl
|
|
126
125
|
* @throws {Error} If authentication fails
|
|
127
126
|
*/
|
|
128
|
-
async function setupAuthenticationAndDataplane(systemKey,
|
|
129
|
-
const
|
|
130
|
-
const
|
|
127
|
+
async function setupAuthenticationAndDataplane(systemKey, _options, _config) {
|
|
128
|
+
const { resolveEnvironment } = require('../core/config');
|
|
129
|
+
const environment = await resolveEnvironment();
|
|
130
|
+
const controllerUrl = await resolveControllerUrl();
|
|
131
131
|
const authConfig = await getDeploymentAuth(controllerUrl, environment, systemKey);
|
|
132
132
|
|
|
133
133
|
if (!authConfig.token && !authConfig.clientId) {
|
|
134
134
|
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
138
|
+
logger.log(chalk.blue('🌐 Resolving dataplane URL...'));
|
|
139
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
139
140
|
logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
|
|
140
141
|
|
|
141
142
|
return { authConfig, dataplaneUrl };
|
|
@@ -159,8 +160,19 @@ async function downloadSystemConfiguration(dataplaneUrl, systemKey, authConfig)
|
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
const downloadData = response.data.data || response.data;
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
}
|
|
164
176
|
|
|
165
177
|
if (!application) {
|
|
166
178
|
throw new Error('Application configuration not found in download response');
|
|
@@ -189,7 +201,7 @@ async function downloadSystemConfiguration(dataplaneUrl, systemKey, authConfig)
|
|
|
189
201
|
* @returns {Promise<string>} System file path
|
|
190
202
|
*/
|
|
191
203
|
async function generateSystemFile(tempDir, systemKey, application) {
|
|
192
|
-
const systemFileName = `${systemKey}-
|
|
204
|
+
const systemFileName = `${systemKey}-system.json`;
|
|
193
205
|
const systemFilePath = path.join(tempDir, systemFileName);
|
|
194
206
|
await fs.writeFile(systemFilePath, JSON.stringify(application, null, 2), 'utf8');
|
|
195
207
|
return systemFilePath;
|
|
@@ -209,8 +221,16 @@ async function generateDatasourceFiles(tempDir, systemKey, dataSources) {
|
|
|
209
221
|
const datasourceFiles = [];
|
|
210
222
|
for (const datasource of dataSources) {
|
|
211
223
|
try {
|
|
212
|
-
const
|
|
213
|
-
|
|
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`;
|
|
214
234
|
const datasourceFilePath = path.join(tempDir, datasourceFileName);
|
|
215
235
|
await fs.writeFile(datasourceFilePath, JSON.stringify(datasource, null, 2), 'utf8');
|
|
216
236
|
datasourceFiles.push(datasourceFilePath);
|
|
@@ -284,7 +304,7 @@ async function moveFilesToFinalLocation(tempDir, finalPath, systemKey, filePaths
|
|
|
284
304
|
logger.log(chalk.blue(`📁 Creating directory: ${finalPath}`));
|
|
285
305
|
await fs.mkdir(finalPath, { recursive: true });
|
|
286
306
|
|
|
287
|
-
const systemFileName = `${systemKey}-
|
|
307
|
+
const systemFileName = `${systemKey}-system.json`;
|
|
288
308
|
const filesToMove = [
|
|
289
309
|
{ from: filePaths.systemFilePath, to: path.join(finalPath, systemFileName) },
|
|
290
310
|
{ from: filePaths.variablesPath, to: path.join(finalPath, 'variables.yaml') },
|
|
@@ -328,7 +348,7 @@ function handleDryRun(systemKey, dataplaneUrl) {
|
|
|
328
348
|
logger.log(chalk.yellow('\nWould create:'));
|
|
329
349
|
logger.log(chalk.gray(` integration/${systemKey}/`));
|
|
330
350
|
logger.log(chalk.gray(` integration/${systemKey}/variables.yaml`));
|
|
331
|
-
logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-
|
|
351
|
+
logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-system.json`));
|
|
332
352
|
logger.log(chalk.gray(` integration/${systemKey}/env.template`));
|
|
333
353
|
logger.log(chalk.gray(` integration/${systemKey}/README.md`));
|
|
334
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'
|
|
@@ -21,9 +21,10 @@ const { resolveControllerUrl } = require('../utils/controller-url');
|
|
|
21
21
|
* @returns {Promise<Object>} Object with authConfig and dataplaneUrl
|
|
22
22
|
* @throws {Error} If authentication fails
|
|
23
23
|
*/
|
|
24
|
-
async function setupIntegrationTestAuth(appName,
|
|
25
|
-
const
|
|
26
|
-
const
|
|
24
|
+
async function setupIntegrationTestAuth(appName, _options, _config) {
|
|
25
|
+
const { resolveEnvironment } = require('../core/config');
|
|
26
|
+
const environment = await resolveEnvironment();
|
|
27
|
+
const controllerUrl = await resolveControllerUrl();
|
|
27
28
|
const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
|
|
28
29
|
|
|
29
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
|
|