@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
package/lib/generator/index.js
CHANGED
|
@@ -17,23 +17,26 @@ const builders = require('./builders');
|
|
|
17
17
|
const { detectAppType, getDeployJsonPath } = require('../utils/paths');
|
|
18
18
|
const splitFunctions = require('./split');
|
|
19
19
|
const { loadVariables, loadEnvTemplate, loadRbac, parseEnvironmentVariables } = require('./helpers');
|
|
20
|
-
const {
|
|
20
|
+
const { generateExternalSystemApplicationSchema, splitExternalApplicationSchema } = require('./external');
|
|
21
|
+
const { generateControllerManifest } = require('./external-controller-manifest');
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Generates deployment JSON from application configuration files
|
|
24
|
-
* Creates <app-name>-deploy.json for
|
|
25
|
-
* For external systems,
|
|
25
|
+
* Creates <app-name>-deploy.json for regular apps (consistent naming)
|
|
26
|
+
* For external systems, generates application-schema.json
|
|
26
27
|
* For regular apps, generates deployment manifest from variables.yaml, env.template, rbac.yaml
|
|
27
28
|
*
|
|
28
29
|
* @async
|
|
29
30
|
* @function generateDeployJson
|
|
30
31
|
* @param {string} appName - Name of the application
|
|
32
|
+
* @param {Object} [options] - Generation options
|
|
33
|
+
* @param {string} [options.type] - Forced application type (external)
|
|
31
34
|
* @returns {Promise<string>} Path to generated deployment JSON file
|
|
32
35
|
* @throws {Error} If generation fails or configuration is invalid
|
|
33
36
|
*
|
|
34
37
|
* @example
|
|
35
38
|
* const jsonPath = await generateDeployJson('myapp');
|
|
36
|
-
* // Returns: './builder/myapp/myapp-deploy.json' or './integration/hubspot/
|
|
39
|
+
* // Returns: './builder/myapp/myapp-deploy.json' or './integration/hubspot/application-schema.json'
|
|
37
40
|
*/
|
|
38
41
|
/**
|
|
39
42
|
* Loads configuration files for deployment generation
|
|
@@ -88,17 +91,25 @@ function buildAndValidateDeployment(appName, variables, envTemplate, rbac) {
|
|
|
88
91
|
return deployment;
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
async function generateDeployJson(appName) {
|
|
94
|
+
async function generateDeployJson(appName, options = {}) {
|
|
92
95
|
if (!appName || typeof appName !== 'string') {
|
|
93
96
|
throw new Error('App name is required and must be a string');
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
// Detect app type and get correct path (integration or builder)
|
|
97
|
-
const { isExternal, appPath, appType } = await detectAppType(appName);
|
|
100
|
+
const { isExternal, appPath, appType } = await detectAppType(appName, options);
|
|
98
101
|
|
|
99
102
|
// Check if app type is external
|
|
100
103
|
if (isExternal) {
|
|
101
|
-
|
|
104
|
+
// Generate controller-compatible manifest format
|
|
105
|
+
const manifest = await generateControllerManifest(appName);
|
|
106
|
+
|
|
107
|
+
// Determine system key for file naming
|
|
108
|
+
const systemKey = manifest.key || appName;
|
|
109
|
+
const deployJsonPath = path.join(appPath, `${systemKey}-deploy.json`);
|
|
110
|
+
|
|
111
|
+
await fs.promises.writeFile(deployJsonPath, JSON.stringify(manifest, null, 2), { mode: 0o644, encoding: 'utf8' });
|
|
112
|
+
return deployJsonPath;
|
|
102
113
|
}
|
|
103
114
|
|
|
104
115
|
// Regular app: generate deployment manifest
|
|
@@ -112,13 +123,13 @@ async function generateDeployJson(appName) {
|
|
|
112
123
|
return jsonPath;
|
|
113
124
|
}
|
|
114
125
|
|
|
115
|
-
async function generateDeployJsonWithValidation(appName) {
|
|
116
|
-
const jsonPath = await generateDeployJson(appName);
|
|
126
|
+
async function generateDeployJsonWithValidation(appName, options = {}) {
|
|
127
|
+
const jsonPath = await generateDeployJson(appName, options);
|
|
117
128
|
const jsonContent = fs.readFileSync(jsonPath, 'utf8');
|
|
118
129
|
const deployment = JSON.parse(jsonContent);
|
|
119
130
|
|
|
120
131
|
// Detect if this is an external system
|
|
121
|
-
const { isExternal } = await detectAppType(appName);
|
|
132
|
+
const { isExternal } = await detectAppType(appName, options);
|
|
122
133
|
|
|
123
134
|
// For external systems, skip deployment JSON validation (they use external system JSON structure)
|
|
124
135
|
if (isExternal) {
|
|
@@ -143,6 +154,7 @@ module.exports = {
|
|
|
143
154
|
generateDeployJson,
|
|
144
155
|
generateDeployJsonWithValidation,
|
|
145
156
|
generateExternalSystemApplicationSchema,
|
|
157
|
+
splitExternalApplicationSchema,
|
|
146
158
|
parseEnvironmentVariables,
|
|
147
159
|
splitDeployJson: splitFunctions.splitDeployJson,
|
|
148
160
|
extractEnvTemplate: splitFunctions.extractEnvTemplate,
|
|
@@ -29,6 +29,28 @@ async function promptForMode() {
|
|
|
29
29
|
return mode;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Prompt for existing system ID or key (for add-datasource mode)
|
|
34
|
+
* @async
|
|
35
|
+
* @function promptForSystemIdOrKey
|
|
36
|
+
* @returns {Promise<string>} System ID or key
|
|
37
|
+
*/
|
|
38
|
+
async function promptForSystemIdOrKey() {
|
|
39
|
+
const { systemIdOrKey } = await inquirer.prompt([
|
|
40
|
+
{
|
|
41
|
+
type: 'input',
|
|
42
|
+
name: 'systemIdOrKey',
|
|
43
|
+
message: 'Enter the existing system ID or key:',
|
|
44
|
+
validate: (input) => {
|
|
45
|
+
if (!input || typeof input !== 'string' || input.trim().length === 0) {
|
|
46
|
+
return 'System ID or key is required';
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
]);
|
|
52
|
+
return systemIdOrKey.trim();
|
|
53
|
+
}
|
|
32
54
|
/**
|
|
33
55
|
* Prompt for source type selection
|
|
34
56
|
* @async
|
|
@@ -195,19 +217,19 @@ async function promptForKnownPlatform(platforms = []) {
|
|
|
195
217
|
async function promptForUserIntent() {
|
|
196
218
|
const { intent } = await inquirer.prompt([
|
|
197
219
|
{
|
|
198
|
-
type: '
|
|
220
|
+
type: 'input',
|
|
199
221
|
name: 'intent',
|
|
200
|
-
message: '
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
222
|
+
message: 'Describe your primary use case (any text):',
|
|
223
|
+
default: 'general integration',
|
|
224
|
+
validate: (input) => {
|
|
225
|
+
if (!input || typeof input !== 'string' || input.trim().length === 0) {
|
|
226
|
+
return 'Intent is required';
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
208
230
|
}
|
|
209
231
|
]);
|
|
210
|
-
return intent;
|
|
232
|
+
return intent.trim();
|
|
211
233
|
}
|
|
212
234
|
|
|
213
235
|
/**
|
|
@@ -344,6 +366,7 @@ async function promptForAppName(defaultName) {
|
|
|
344
366
|
|
|
345
367
|
module.exports = {
|
|
346
368
|
promptForMode,
|
|
369
|
+
promptForSystemIdOrKey,
|
|
347
370
|
promptForSourceType,
|
|
348
371
|
promptForOpenApiFile,
|
|
349
372
|
promptForOpenApiUrl,
|
package/lib/generator/wizard.js
CHANGED
|
@@ -10,7 +10,7 @@ const yaml = require('js-yaml');
|
|
|
10
10
|
const Handlebars = require('handlebars');
|
|
11
11
|
const chalk = require('chalk');
|
|
12
12
|
const logger = require('../utils/logger');
|
|
13
|
-
const {
|
|
13
|
+
const { generateExternalReadmeContent } = require('../utils/external-readme');
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Generate files from dataplane-generated wizard configurations
|
|
@@ -33,7 +33,7 @@ const { generateExternalSystemApplicationSchema } = require('./external');
|
|
|
33
33
|
* @returns {Promise<string>} System file path
|
|
34
34
|
*/
|
|
35
35
|
async function writeSystemJsonFile(appPath, finalSystemKey, systemConfig) {
|
|
36
|
-
const systemFileName = `${finalSystemKey}-
|
|
36
|
+
const systemFileName = `${finalSystemKey}-system.json`;
|
|
37
37
|
const systemFilePath = path.join(appPath, systemFileName);
|
|
38
38
|
await fs.writeFile(systemFilePath, JSON.stringify(systemConfig, null, 2), 'utf8');
|
|
39
39
|
logger.log(chalk.green(`✓ Generated system file: ${systemFileName}`));
|
|
@@ -53,7 +53,12 @@ async function writeDatasourceJsonFiles(appPath, finalSystemKey, datasourceConfi
|
|
|
53
53
|
const datasourceFileNames = [];
|
|
54
54
|
for (const datasourceConfig of datasourceConfigs) {
|
|
55
55
|
const entityType = datasourceConfig.entityType || datasourceConfig.entityKey || datasourceConfig.key?.split('-').pop() || 'default';
|
|
56
|
-
const
|
|
56
|
+
const datasourceKey = datasourceConfig.key || `${finalSystemKey}-${entityType}`;
|
|
57
|
+
// Extract datasource key (remove system key prefix if present)
|
|
58
|
+
const datasourceKeyOnly = datasourceKey.includes('-') && datasourceKey.startsWith(`${finalSystemKey}-`)
|
|
59
|
+
? datasourceKey.substring(finalSystemKey.length + 1)
|
|
60
|
+
: entityType;
|
|
61
|
+
const datasourceFileName = `${finalSystemKey}-datasource-${datasourceKeyOnly}.json`;
|
|
57
62
|
const datasourceFilePath = path.join(appPath, datasourceFileName);
|
|
58
63
|
await fs.writeFile(datasourceFilePath, JSON.stringify(datasourceConfig, null, 2), 'utf8');
|
|
59
64
|
datasourceFileNames.push(datasourceFileName);
|
|
@@ -99,17 +104,18 @@ async function generateConfigFilesForWizard(params) {
|
|
|
99
104
|
// Generate deployment scripts
|
|
100
105
|
const deployScripts = await generateDeployScripts(appPath, finalSystemKey, systemFileName, datasourceFileNames);
|
|
101
106
|
|
|
102
|
-
// Generate
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
// Generate deployment manifest (<systemKey>-deploy.json) using controller format
|
|
108
|
+
const { generateControllerManifest } = require('./external-controller-manifest');
|
|
109
|
+
const manifest = await generateControllerManifest(appName, { appPath });
|
|
110
|
+
const deployManifestPath = path.join(appPath, `${finalSystemKey}-deploy.json`);
|
|
111
|
+
await fs.writeFile(deployManifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
112
|
+
logger.log(chalk.green(`✓ Generated deployment manifest: ${finalSystemKey}-deploy.json`));
|
|
107
113
|
|
|
108
114
|
return {
|
|
109
115
|
variablesPath: path.join(appPath, 'variables.yaml'),
|
|
110
116
|
envTemplatePath: path.join(appPath, 'env.template'),
|
|
111
117
|
readmePath: path.join(appPath, 'README.md'),
|
|
112
|
-
applicationSchemaPath,
|
|
118
|
+
applicationSchemaPath: deployManifestPath,
|
|
113
119
|
...deployScripts
|
|
114
120
|
};
|
|
115
121
|
}
|
|
@@ -123,23 +129,46 @@ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, sys
|
|
|
123
129
|
// Create directory if it doesn't exist
|
|
124
130
|
await fs.mkdir(appPath, { recursive: true });
|
|
125
131
|
|
|
126
|
-
//
|
|
127
|
-
|
|
132
|
+
// Use appName as the system key to ensure consistent naming
|
|
133
|
+
// Priority: appName > systemKey parameter > systemConfig.key
|
|
134
|
+
const finalSystemKey = appName;
|
|
135
|
+
|
|
136
|
+
// Generate displayName from appName (e.g., "my-hubspot" -> "My Hubspot")
|
|
137
|
+
const appDisplayName = appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
138
|
+
|
|
139
|
+
// Update system config to use the appName as key and displayName
|
|
140
|
+
const updatedSystemConfig = {
|
|
141
|
+
...systemConfig,
|
|
142
|
+
key: finalSystemKey,
|
|
143
|
+
displayName: appDisplayName
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Update datasource configs to use appName-based keys and systemKey
|
|
147
|
+
const updatedDatasourceConfigs = datasourceConfigs.map(ds => {
|
|
148
|
+
const entityType = ds.entityType || ds.entityKey || ds.key?.split('-').pop() || 'default';
|
|
149
|
+
const entityDisplayName = entityType.charAt(0).toUpperCase() + entityType.slice(1).replace(/-/g, ' ');
|
|
150
|
+
return {
|
|
151
|
+
...ds,
|
|
152
|
+
key: `${finalSystemKey}-${entityType}`,
|
|
153
|
+
systemKey: finalSystemKey,
|
|
154
|
+
displayName: `${appDisplayName} ${entityDisplayName}`
|
|
155
|
+
};
|
|
156
|
+
});
|
|
128
157
|
|
|
129
158
|
// Write system and datasource JSON files
|
|
130
|
-
const systemFilePath = await writeSystemJsonFile(appPath, finalSystemKey,
|
|
131
|
-
const datasourceFileNames = await writeDatasourceJsonFiles(appPath, finalSystemKey,
|
|
159
|
+
const systemFilePath = await writeSystemJsonFile(appPath, finalSystemKey, updatedSystemConfig);
|
|
160
|
+
const datasourceFileNames = await writeDatasourceJsonFiles(appPath, finalSystemKey, updatedDatasourceConfigs);
|
|
132
161
|
|
|
133
162
|
// Generate configuration files
|
|
134
|
-
const systemFileName = `${finalSystemKey}-
|
|
163
|
+
const systemFileName = `${finalSystemKey}-system.json`;
|
|
135
164
|
const configFiles = await generateConfigFilesForWizard({
|
|
136
165
|
appPath,
|
|
137
166
|
appName,
|
|
138
167
|
finalSystemKey,
|
|
139
168
|
systemFileName,
|
|
140
169
|
datasourceFileNames,
|
|
141
|
-
systemConfig,
|
|
142
|
-
datasourceConfigs,
|
|
170
|
+
systemConfig: updatedSystemConfig,
|
|
171
|
+
datasourceConfigs: updatedDatasourceConfigs,
|
|
143
172
|
aiGeneratedReadme
|
|
144
173
|
});
|
|
145
174
|
|
|
@@ -408,76 +437,30 @@ async function generateReadme(appPath, appName, systemKey, systemConfig, datasou
|
|
|
408
437
|
return;
|
|
409
438
|
}
|
|
410
439
|
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
'',
|
|
436
|
-
'## Deployment',
|
|
437
|
-
'',
|
|
438
|
-
'### Using Deployment Scripts',
|
|
439
|
-
'',
|
|
440
|
-
'You can deploy using the provided scripts:',
|
|
441
|
-
'',
|
|
442
|
-
'**Bash (Linux/macOS):**',
|
|
443
|
-
'```bash',
|
|
444
|
-
'./deploy.sh',
|
|
445
|
-
'```',
|
|
446
|
-
'',
|
|
447
|
-
'**PowerShell (Windows):**',
|
|
448
|
-
'```powershell',
|
|
449
|
-
'.\\deploy.ps1',
|
|
450
|
-
'```',
|
|
451
|
-
'',
|
|
452
|
-
'The scripts support environment variables:',
|
|
453
|
-
'- `ENVIRONMENT` - Environment key (default: dev)',
|
|
454
|
-
'- `CONTROLLER` - Controller URL (default: http://localhost:3000)',
|
|
455
|
-
'- `RUN_TESTS` - Set to "true" to run integration tests after deployment',
|
|
456
|
-
'',
|
|
457
|
-
'**Example:**',
|
|
458
|
-
'```bash',
|
|
459
|
-
'ENVIRONMENT=prod CONTROLLER=https://controller.example.com ./deploy.sh',
|
|
460
|
-
'```',
|
|
461
|
-
'',
|
|
462
|
-
'### Using CLI Directly',
|
|
463
|
-
'',
|
|
464
|
-
'To deploy this external system:',
|
|
465
|
-
'',
|
|
466
|
-
'```bash',
|
|
467
|
-
`aifabrix deploy ${appName}`,
|
|
468
|
-
'```',
|
|
469
|
-
'',
|
|
470
|
-
'## Configuration',
|
|
471
|
-
'',
|
|
472
|
-
'Update the environment variables in `env.template` and set the values in your secrets store.',
|
|
473
|
-
'',
|
|
474
|
-
'## Documentation',
|
|
475
|
-
'',
|
|
476
|
-
'For more information, see the [External Systems Documentation](../../docs/external-systems.md).'
|
|
477
|
-
];
|
|
478
|
-
|
|
479
|
-
await fs.writeFile(readmePath, lines.join('\n'), 'utf8');
|
|
480
|
-
logger.log(chalk.green('✓ Generated README.md'));
|
|
440
|
+
const datasources = (Array.isArray(datasourceConfigs) ? datasourceConfigs : []).map((ds, index) => {
|
|
441
|
+
const entityType = ds.entityType || ds.entityKey || ds.key?.split('-').pop() || `datasource${index + 1}`;
|
|
442
|
+
const datasourceKey = ds.key || `${systemKey}-${entityType}`;
|
|
443
|
+
const datasourceKeyOnly = datasourceKey.includes('-') && datasourceKey.startsWith(`${systemKey}-`)
|
|
444
|
+
? datasourceKey.substring(systemKey.length + 1)
|
|
445
|
+
: entityType;
|
|
446
|
+
return {
|
|
447
|
+
entityType,
|
|
448
|
+
displayName: ds.displayName || ds.name || ds.key || `Datasource ${index + 1}`,
|
|
449
|
+
fileName: `${systemKey}-datasource-${datasourceKeyOnly}.json`
|
|
450
|
+
};
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const readmeContent = generateExternalReadmeContent({
|
|
454
|
+
appName,
|
|
455
|
+
systemKey,
|
|
456
|
+
systemType: systemConfig.type || systemConfig.systemType,
|
|
457
|
+
displayName: systemConfig.displayName,
|
|
458
|
+
description: systemConfig.description,
|
|
459
|
+
datasources
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
await fs.writeFile(readmePath, readmeContent, 'utf8');
|
|
463
|
+
logger.log(chalk.green('✓ Generated README.md (template)'));
|
|
481
464
|
} catch (error) {
|
|
482
465
|
throw new Error(`Failed to generate README.md: ${error.message}`);
|
|
483
466
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder Infrastructure Compose File Generation
|
|
3
|
+
*
|
|
4
|
+
* Handles Docker Compose file generation from templates and Traefik configuration.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Compose file generation for infrastructure
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const handlebars = require('handlebars');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Builds Traefik configuration from environment variables
|
|
17
|
+
* @param {boolean} enabled - Whether Traefik should be included
|
|
18
|
+
* @returns {Object} Traefik configuration
|
|
19
|
+
*/
|
|
20
|
+
function buildTraefikConfig(enabled) {
|
|
21
|
+
return {
|
|
22
|
+
enabled: !!enabled,
|
|
23
|
+
certStore: process.env.TRAEFIK_CERT_STORE || null,
|
|
24
|
+
certFile: process.env.TRAEFIK_CERT_FILE || null,
|
|
25
|
+
keyFile: process.env.TRAEFIK_KEY_FILE || null
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validates Traefik configuration when enabled
|
|
31
|
+
* @param {Object} traefikConfig - Traefik configuration
|
|
32
|
+
* @returns {{valid: boolean, errors: string[]}} Validation result
|
|
33
|
+
*/
|
|
34
|
+
function validateTraefikConfig(traefikConfig) {
|
|
35
|
+
if (!traefikConfig || !traefikConfig.enabled) {
|
|
36
|
+
return { valid: true, errors: [] };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const errors = [];
|
|
40
|
+
|
|
41
|
+
if (traefikConfig.certStore) {
|
|
42
|
+
if (!traefikConfig.certFile || !traefikConfig.keyFile) {
|
|
43
|
+
errors.push('TRAEFIK_CERT_FILE and TRAEFIK_KEY_FILE are required when TRAEFIK_CERT_STORE is set');
|
|
44
|
+
} else {
|
|
45
|
+
if (!fs.existsSync(traefikConfig.certFile)) {
|
|
46
|
+
errors.push(`Certificate file not found: ${traefikConfig.certFile}`);
|
|
47
|
+
}
|
|
48
|
+
if (!fs.existsSync(traefikConfig.keyFile)) {
|
|
49
|
+
errors.push(`Private key file not found: ${traefikConfig.keyFile}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { valid: errors.length === 0, errors };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generate docker-compose file from template
|
|
59
|
+
* @param {string} templatePath - Path to compose template
|
|
60
|
+
* @param {string} devId - Developer ID
|
|
61
|
+
* @param {number} idNum - Developer ID number
|
|
62
|
+
* @param {Object} ports - Port configuration
|
|
63
|
+
* @param {string} infraDir - Infrastructure directory
|
|
64
|
+
* @param {Object} [options] - Additional options
|
|
65
|
+
* @param {Object|boolean} [options.traefik] - Traefik configuration
|
|
66
|
+
* @returns {string} Path to generated compose file
|
|
67
|
+
*/
|
|
68
|
+
function generateComposeFile(templatePath, devId, idNum, ports, infraDir, options = {}) {
|
|
69
|
+
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
70
|
+
const template = handlebars.compile(templateContent);
|
|
71
|
+
const networkName = idNum === 0 ? 'infra-aifabrix-network' : `infra-dev${devId}-aifabrix-network`;
|
|
72
|
+
const serversJsonPath = path.join(infraDir, 'servers.json');
|
|
73
|
+
const pgpassPath = path.join(infraDir, 'pgpass');
|
|
74
|
+
const traefikConfig = typeof options.traefik === 'object'
|
|
75
|
+
? options.traefik
|
|
76
|
+
: buildTraefikConfig(!!options.traefik);
|
|
77
|
+
const composeContent = template({
|
|
78
|
+
devId: devId,
|
|
79
|
+
postgresPort: ports.postgres,
|
|
80
|
+
redisPort: ports.redis,
|
|
81
|
+
pgadminPort: ports.pgadmin,
|
|
82
|
+
redisCommanderPort: ports.redisCommander,
|
|
83
|
+
traefikHttpPort: ports.traefikHttp,
|
|
84
|
+
traefikHttpsPort: ports.traefikHttps,
|
|
85
|
+
networkName: networkName,
|
|
86
|
+
serversJsonPath: serversJsonPath,
|
|
87
|
+
pgpassPath: pgpassPath,
|
|
88
|
+
infraDir: infraDir,
|
|
89
|
+
traefik: traefikConfig
|
|
90
|
+
});
|
|
91
|
+
const composePath = path.join(infraDir, 'compose.yaml');
|
|
92
|
+
fs.writeFileSync(composePath, composeContent);
|
|
93
|
+
return composePath;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
buildTraefikConfig,
|
|
98
|
+
validateTraefikConfig,
|
|
99
|
+
generateComposeFile
|
|
100
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder Infrastructure Helpers
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for infrastructure management including directory names,
|
|
5
|
+
* Docker availability checks, and pgAdmin configuration.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Helper functions for infrastructure management
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const handlebars = require('handlebars');
|
|
15
|
+
const secrets = require('../core/secrets');
|
|
16
|
+
const logger = require('../utils/logger');
|
|
17
|
+
const dockerUtils = require('../utils/docker');
|
|
18
|
+
const paths = require('../utils/paths');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Gets infrastructure directory name based on developer ID
|
|
22
|
+
* Dev 0: infra (no dev-0 suffix), Dev > 0: infra-dev{id}
|
|
23
|
+
* @param {number|string} devId - Developer ID
|
|
24
|
+
* @returns {string} Infrastructure directory name
|
|
25
|
+
*/
|
|
26
|
+
function getInfraDirName(devId) {
|
|
27
|
+
const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
|
|
28
|
+
return idNum === 0 ? 'infra' : `infra-dev${devId}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Gets Docker Compose project name based on developer ID
|
|
33
|
+
* Dev 0: infra (no dev-0 suffix), Dev > 0: infra-dev{id}
|
|
34
|
+
* @param {number|string} devId - Developer ID
|
|
35
|
+
* @returns {string} Docker Compose project name
|
|
36
|
+
*/
|
|
37
|
+
function getInfraProjectName(devId) {
|
|
38
|
+
const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
|
|
39
|
+
return idNum === 0 ? 'infra' : `infra-dev${devId}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check Docker availability
|
|
44
|
+
* @async
|
|
45
|
+
* @returns {Promise<void>}
|
|
46
|
+
* @throws {Error} If Docker is not available
|
|
47
|
+
*/
|
|
48
|
+
async function checkDockerAvailability() {
|
|
49
|
+
try {
|
|
50
|
+
await dockerUtils.ensureDockerAndCompose();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new Error('Docker or Docker Compose is not available. Please install and start Docker.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Ensure admin secrets file exists
|
|
58
|
+
* @async
|
|
59
|
+
* @returns {Promise<string>} Path to admin secrets file
|
|
60
|
+
*/
|
|
61
|
+
async function ensureAdminSecrets() {
|
|
62
|
+
const adminSecretsPath = path.join(paths.getAifabrixHome(), 'admin-secrets.env');
|
|
63
|
+
if (!fs.existsSync(adminSecretsPath)) {
|
|
64
|
+
logger.log('Generating admin-secrets.env...');
|
|
65
|
+
await secrets.generateAdminSecretsEnv();
|
|
66
|
+
}
|
|
67
|
+
return adminSecretsPath;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Generates pgAdmin4 configuration files (servers.json and pgpass)
|
|
72
|
+
* @param {string} infraDir - Infrastructure directory path
|
|
73
|
+
* @param {string} postgresPassword - PostgreSQL password
|
|
74
|
+
*/
|
|
75
|
+
function generatePgAdminConfig(infraDir, postgresPassword) {
|
|
76
|
+
const serversJsonTemplatePath = path.join(__dirname, '..', 'templates', 'infra', 'servers.json.hbs');
|
|
77
|
+
if (!fs.existsSync(serversJsonTemplatePath)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const serversJsonTemplateContent = fs.readFileSync(serversJsonTemplatePath, 'utf8');
|
|
82
|
+
const serversJsonTemplate = handlebars.compile(serversJsonTemplateContent);
|
|
83
|
+
const serversJsonContent = serversJsonTemplate({ postgresPassword });
|
|
84
|
+
const serversJsonPath = path.join(infraDir, 'servers.json');
|
|
85
|
+
fs.writeFileSync(serversJsonPath, serversJsonContent, { mode: 0o644 });
|
|
86
|
+
|
|
87
|
+
const pgpassContent = `postgres:5432:postgres:pgadmin:${postgresPassword}\n`;
|
|
88
|
+
const pgpassPath = path.join(infraDir, 'pgpass');
|
|
89
|
+
fs.writeFileSync(pgpassPath, pgpassContent, { mode: 0o600 });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Prepare infrastructure directory and extract postgres password
|
|
94
|
+
* @param {string} devId - Developer ID
|
|
95
|
+
* @param {string} adminSecretsPath - Path to admin secrets file
|
|
96
|
+
* @returns {Object} Object with infraDir and postgresPassword
|
|
97
|
+
*/
|
|
98
|
+
function prepareInfraDirectory(devId, adminSecretsPath) {
|
|
99
|
+
const aifabrixDir = paths.getAifabrixHome();
|
|
100
|
+
const infraDirName = getInfraDirName(devId);
|
|
101
|
+
const infraDir = path.join(aifabrixDir, infraDirName);
|
|
102
|
+
if (!fs.existsSync(infraDir)) {
|
|
103
|
+
fs.mkdirSync(infraDir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const adminSecretsContent = fs.readFileSync(adminSecretsPath, 'utf8');
|
|
107
|
+
const postgresPasswordMatch = adminSecretsContent.match(/^POSTGRES_PASSWORD=(.+)$/m);
|
|
108
|
+
const postgresPassword = postgresPasswordMatch ? postgresPasswordMatch[1] : '';
|
|
109
|
+
generatePgAdminConfig(infraDir, postgresPassword);
|
|
110
|
+
|
|
111
|
+
return { infraDir, postgresPassword };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Register Handlebars helper for equality comparison
|
|
116
|
+
*/
|
|
117
|
+
function registerHandlebarsHelper() {
|
|
118
|
+
handlebars.registerHelper('eq', (a, b) => {
|
|
119
|
+
// Handle null/undefined - treat as "0" for default infrastructure
|
|
120
|
+
if (a === null || a === undefined) a = '0';
|
|
121
|
+
if (b === null || b === undefined) b = '0';
|
|
122
|
+
const aNum = typeof a === 'string' && /^\d+$/.test(a) ? parseInt(a, 10) : a;
|
|
123
|
+
const bNum = typeof b === 'string' && /^\d+$/.test(b) ? parseInt(b, 10) : b;
|
|
124
|
+
if (typeof aNum === 'number' && typeof bNum === 'number') {
|
|
125
|
+
return aNum === bNum;
|
|
126
|
+
}
|
|
127
|
+
return a === b;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
getInfraDirName,
|
|
133
|
+
getInfraProjectName,
|
|
134
|
+
checkDockerAvailability,
|
|
135
|
+
ensureAdminSecrets,
|
|
136
|
+
generatePgAdminConfig,
|
|
137
|
+
prepareInfraDirectory,
|
|
138
|
+
registerHandlebarsHelper
|
|
139
|
+
};
|