@aifabrix/builder 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/bin/aifabrix.js +9 -3
- package/jest.config.integration.js +30 -0
- package/lib/app-config.js +157 -0
- package/lib/app-deploy.js +233 -82
- package/lib/app-dockerfile.js +112 -0
- package/lib/app-prompts.js +244 -0
- package/lib/app-push.js +172 -0
- package/lib/app-run.js +334 -133
- package/lib/app.js +208 -274
- package/lib/audit-logger.js +2 -0
- package/lib/build.js +209 -98
- package/lib/cli.js +76 -86
- package/lib/commands/app.js +414 -0
- package/lib/commands/login.js +304 -0
- package/lib/config.js +78 -0
- package/lib/deployer.js +225 -81
- package/lib/env-reader.js +45 -30
- package/lib/generator.js +308 -191
- package/lib/github-generator.js +67 -7
- package/lib/infra.js +156 -61
- package/lib/push.js +105 -10
- package/lib/schema/application-schema.json +30 -2
- package/lib/schema/infrastructure-schema.json +589 -0
- package/lib/secrets.js +229 -24
- package/lib/template-validator.js +205 -0
- package/lib/templates.js +305 -170
- package/lib/utils/api.js +329 -0
- package/lib/utils/cli-utils.js +97 -0
- package/lib/utils/dockerfile-utils.js +131 -0
- package/lib/utils/environment-checker.js +125 -0
- package/lib/utils/error-formatter.js +61 -0
- package/lib/utils/health-check.js +187 -0
- package/lib/utils/logger.js +53 -0
- package/lib/utils/template-helpers.js +223 -0
- package/lib/utils/variable-transformer.js +271 -0
- package/lib/validator.js +27 -112
- package/package.json +13 -10
- package/templates/README.md +75 -3
- package/templates/applications/keycloak/Dockerfile +36 -0
- package/templates/applications/keycloak/env.template +32 -0
- package/templates/applications/keycloak/rbac.yaml +37 -0
- package/templates/applications/keycloak/variables.yaml +56 -0
- package/templates/applications/miso-controller/Dockerfile +125 -0
- package/templates/applications/miso-controller/env.template +129 -0
- package/templates/applications/miso-controller/rbac.yaml +168 -0
- package/templates/applications/miso-controller/variables.yaml +56 -0
- package/templates/github/release.yaml.hbs +5 -26
- package/templates/github/steps/npm.hbs +24 -0
- package/templates/infra/compose.yaml +6 -6
- package/templates/python/docker-compose.hbs +19 -12
- package/templates/python/main.py +80 -0
- package/templates/python/requirements.txt +4 -0
- package/templates/typescript/Dockerfile.hbs +2 -2
- package/templates/typescript/docker-compose.hbs +19 -12
- package/templates/typescript/index.ts +116 -0
- package/templates/typescript/package.json +26 -0
- package/templates/typescript/tsconfig.json +24 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Formatting Utilities
|
|
3
|
+
*
|
|
4
|
+
* Formats validation errors into developer-friendly messages
|
|
5
|
+
* Converts technical schema errors into actionable advice
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Error formatting utilities for AI Fabrix Builder
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Formats a single validation error into a developer-friendly message
|
|
14
|
+
*
|
|
15
|
+
* @function formatSingleError
|
|
16
|
+
* @param {Object} error - Raw validation error from Ajv
|
|
17
|
+
* @returns {string} Formatted error message
|
|
18
|
+
*/
|
|
19
|
+
function formatSingleError(error) {
|
|
20
|
+
const path = error.instancePath ? error.instancePath.slice(1) : 'root';
|
|
21
|
+
const field = path ? `Field "${path}"` : 'Configuration';
|
|
22
|
+
|
|
23
|
+
const errorMessages = {
|
|
24
|
+
required: `${field}: Missing required property "${error.params.missingProperty}"`,
|
|
25
|
+
type: `${field}: Expected ${error.params.type}, got ${typeof error.data}`,
|
|
26
|
+
minimum: `${field}: Value must be at least ${error.params.limit}`,
|
|
27
|
+
maximum: `${field}: Value must be at most ${error.params.limit}`,
|
|
28
|
+
minLength: `${field}: Must be at least ${error.params.limit} characters`,
|
|
29
|
+
maxLength: `${field}: Must be at most ${error.params.limit} characters`,
|
|
30
|
+
pattern: `${field}: Invalid format`,
|
|
31
|
+
enum: `${field}: Must be one of: ${error.params.allowedValues?.join(', ') || 'unknown'}`
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return errorMessages[error.keyword] || `${field}: ${error.message}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Formats validation errors into developer-friendly messages
|
|
39
|
+
* Converts technical schema errors into actionable advice
|
|
40
|
+
*
|
|
41
|
+
* @function formatValidationErrors
|
|
42
|
+
* @param {Array} errors - Raw validation errors from Ajv
|
|
43
|
+
* @returns {Array} Formatted error messages
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const messages = formatValidationErrors(ajvErrors);
|
|
47
|
+
* // Returns: ['Port must be between 1 and 65535', 'Missing required field: displayName']
|
|
48
|
+
*/
|
|
49
|
+
function formatValidationErrors(errors) {
|
|
50
|
+
if (!Array.isArray(errors)) {
|
|
51
|
+
return ['Unknown validation error'];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return errors.map(formatSingleError);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
formatSingleError,
|
|
59
|
+
formatValidationErrors
|
|
60
|
+
};
|
|
61
|
+
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Check Utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles health check functionality for application containers
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Health check utilities for AI Fabrix Builder
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const http = require('http');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
const { exec } = require('child_process');
|
|
14
|
+
const { promisify } = require('util');
|
|
15
|
+
const logger = require('./logger');
|
|
16
|
+
|
|
17
|
+
const execAsync = promisify(exec);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Checks if db-init container exists and waits for it to complete
|
|
21
|
+
* @async
|
|
22
|
+
* @function waitForDbInit
|
|
23
|
+
* @param {string} appName - Application name
|
|
24
|
+
* @throws {Error} If db-init fails
|
|
25
|
+
*/
|
|
26
|
+
async function waitForDbInit(appName) {
|
|
27
|
+
const dbInitContainer = `aifabrix-${appName}-db-init`;
|
|
28
|
+
try {
|
|
29
|
+
const { stdout } = await execAsync(`docker ps -a --filter "name=${dbInitContainer}" --format "{{.Names}}"`);
|
|
30
|
+
if (stdout.trim() !== dbInitContainer) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { stdout: status } = await execAsync(`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`);
|
|
35
|
+
if (status.trim() === 'exited') {
|
|
36
|
+
const { stdout: exitCode } = await execAsync(`docker inspect --format='{{.State.ExitCode}}' ${dbInitContainer}`);
|
|
37
|
+
if (exitCode.trim() === '0') {
|
|
38
|
+
logger.log(chalk.green('✓ Database initialization already completed'));
|
|
39
|
+
} else {
|
|
40
|
+
logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode.trim()}`));
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
logger.log(chalk.blue('Waiting for database initialization to complete...'));
|
|
46
|
+
const maxDbInitAttempts = 30;
|
|
47
|
+
for (let dbInitAttempts = 0; dbInitAttempts < maxDbInitAttempts; dbInitAttempts++) {
|
|
48
|
+
const { stdout: currentStatus } = await execAsync(`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`);
|
|
49
|
+
if (currentStatus.trim() === 'exited') {
|
|
50
|
+
const { stdout: exitCode } = await execAsync(`docker inspect --format='{{.State.ExitCode}}' ${dbInitContainer}`);
|
|
51
|
+
if (exitCode.trim() === '0') {
|
|
52
|
+
logger.log(chalk.green('✓ Database initialization completed'));
|
|
53
|
+
} else {
|
|
54
|
+
logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode.trim()}`));
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// db-init container might not exist, which is fine
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Gets container port from Docker inspect
|
|
67
|
+
* @async
|
|
68
|
+
* @function getContainerPort
|
|
69
|
+
* @param {string} appName - Application name
|
|
70
|
+
* @returns {Promise<number>} Container port
|
|
71
|
+
*/
|
|
72
|
+
async function getContainerPort(appName) {
|
|
73
|
+
try {
|
|
74
|
+
const { stdout: portMapping } = await execAsync(`docker inspect --format='{{range .NetworkSettings.Ports}}{{range .}}{{.HostPort}}{{end}}{{end}}' aifabrix-${appName}`);
|
|
75
|
+
const ports = portMapping.trim().split('\n').filter(p => p);
|
|
76
|
+
if (ports.length > 0) {
|
|
77
|
+
return parseInt(ports[0], 10);
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
// Fall through to default
|
|
81
|
+
}
|
|
82
|
+
return 3000;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parses health check response
|
|
87
|
+
* @function parseHealthResponse
|
|
88
|
+
* @param {string} data - Response data
|
|
89
|
+
* @param {number} statusCode - HTTP status code
|
|
90
|
+
* @returns {boolean} True if healthy
|
|
91
|
+
*/
|
|
92
|
+
function parseHealthResponse(data, statusCode) {
|
|
93
|
+
try {
|
|
94
|
+
const health = JSON.parse(data);
|
|
95
|
+
if (health.status === 'UP') {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
if (health.status === 'ok') {
|
|
99
|
+
return health.database === 'connected' || !health.database;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return statusCode === 200;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Checks health endpoint
|
|
109
|
+
* @async
|
|
110
|
+
* @function checkHealthEndpoint
|
|
111
|
+
* @param {string} healthCheckUrl - Health check URL
|
|
112
|
+
* @returns {Promise<boolean>} True if healthy
|
|
113
|
+
* @throws {Error} If request fails with exception
|
|
114
|
+
*/
|
|
115
|
+
async function checkHealthEndpoint(healthCheckUrl) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
try {
|
|
118
|
+
const req = http.get(healthCheckUrl, { timeout: 5000 }, (res) => {
|
|
119
|
+
let data = '';
|
|
120
|
+
res.on('data', (chunk) => {
|
|
121
|
+
data += chunk;
|
|
122
|
+
});
|
|
123
|
+
res.on('end', () => {
|
|
124
|
+
resolve(parseHealthResponse(data, res.statusCode));
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
req.on('error', () => resolve(false));
|
|
128
|
+
req.on('timeout', () => {
|
|
129
|
+
req.destroy();
|
|
130
|
+
resolve(false);
|
|
131
|
+
});
|
|
132
|
+
} catch (error) {
|
|
133
|
+
// Re-throw exceptions (not just network errors)
|
|
134
|
+
reject(error);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Waits for application health check to pass
|
|
141
|
+
* Checks HTTP endpoint and waits for healthy response
|
|
142
|
+
*
|
|
143
|
+
* @async
|
|
144
|
+
* @function waitForHealthCheck
|
|
145
|
+
* @param {string} appName - Application name
|
|
146
|
+
* @param {number} timeout - Timeout in seconds (default: 90)
|
|
147
|
+
* @param {number} [port] - Application port (auto-detected if not provided)
|
|
148
|
+
* @param {Object} [config] - Application configuration
|
|
149
|
+
* @returns {Promise<void>} Resolves when health check passes
|
|
150
|
+
* @throws {Error} If health check times out
|
|
151
|
+
*/
|
|
152
|
+
async function waitForHealthCheck(appName, timeout = 90, port = null, config = null) {
|
|
153
|
+
await waitForDbInit(appName);
|
|
154
|
+
|
|
155
|
+
if (!port) {
|
|
156
|
+
port = await getContainerPort(appName);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const healthCheckPath = config?.healthCheck?.path || '/health';
|
|
160
|
+
const healthCheckUrl = `http://localhost:${port}${healthCheckPath}`;
|
|
161
|
+
const maxAttempts = timeout / 2;
|
|
162
|
+
|
|
163
|
+
for (let attempts = 0; attempts < maxAttempts; attempts++) {
|
|
164
|
+
try {
|
|
165
|
+
const healthCheckPassed = await checkHealthEndpoint(healthCheckUrl);
|
|
166
|
+
if (healthCheckPassed) {
|
|
167
|
+
logger.log(chalk.green('✓ Application is healthy'));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
// If exception occurs, continue retrying until timeout
|
|
172
|
+
// The error will be handled by timeout error below
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (attempts < maxAttempts - 1) {
|
|
176
|
+
logger.log(chalk.yellow(`Waiting for health check... (${attempts + 1}/${maxAttempts}) ${healthCheckUrl}`));
|
|
177
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
throw new Error(`Health check timeout after ${timeout} seconds`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
waitForHealthCheck
|
|
186
|
+
};
|
|
187
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Utility
|
|
3
|
+
*
|
|
4
|
+
* Centralized logging utility that wraps console methods
|
|
5
|
+
* Allows disabling eslint warnings in one place
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Logger utility for AI Fabrix Builder
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/* eslint-disable no-console */
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Logger utility that wraps console methods
|
|
16
|
+
* All console statements should use this logger to avoid eslint warnings
|
|
17
|
+
*/
|
|
18
|
+
const logger = {
|
|
19
|
+
/**
|
|
20
|
+
* Log informational message
|
|
21
|
+
* @param {...any} args - Arguments to log
|
|
22
|
+
*/
|
|
23
|
+
log: (...args) => {
|
|
24
|
+
console.log(...args);
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Log error message
|
|
29
|
+
* @param {...any} args - Arguments to log
|
|
30
|
+
*/
|
|
31
|
+
error: (...args) => {
|
|
32
|
+
console.error(...args);
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Log warning message
|
|
37
|
+
* @param {...any} args - Arguments to log
|
|
38
|
+
*/
|
|
39
|
+
warn: (...args) => {
|
|
40
|
+
console.warn(...args);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Log informational message (alias for log)
|
|
45
|
+
* @param {...any} args - Arguments to log
|
|
46
|
+
*/
|
|
47
|
+
info: (...args) => {
|
|
48
|
+
console.log(...args);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
module.exports = logger;
|
|
53
|
+
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Helper Utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles template variable loading, updating, and merging
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Template helper utilities for AI Fabrix Builder
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs').promises;
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const logger = require('./logger');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Loads template variables from template's variables.yaml file
|
|
18
|
+
* @async
|
|
19
|
+
* @function loadTemplateVariables
|
|
20
|
+
* @param {string} templateName - Template name
|
|
21
|
+
* @returns {Promise<Object|null>} Template variables or null if not found
|
|
22
|
+
*/
|
|
23
|
+
async function loadTemplateVariables(templateName) {
|
|
24
|
+
if (!templateName) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const yaml = require('js-yaml');
|
|
29
|
+
const templatePath = path.join(__dirname, '..', '..', 'templates', 'applications', templateName);
|
|
30
|
+
const templateVariablesPath = path.join(templatePath, 'variables.yaml');
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const templateContent = await fs.readFile(templateVariablesPath, 'utf8');
|
|
34
|
+
return yaml.load(templateContent);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// Template variables.yaml not found or invalid, continue without it
|
|
37
|
+
if (error.code !== 'ENOENT') {
|
|
38
|
+
logger.warn(chalk.yellow(`⚠️ Warning: Could not load template variables.yaml: ${error.message}`));
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Updates app key and display name in variables
|
|
46
|
+
* @function updateAppMetadata
|
|
47
|
+
* @param {Object} variables - Variables object
|
|
48
|
+
* @param {string} appName - Application name
|
|
49
|
+
*/
|
|
50
|
+
function updateAppMetadata(variables, appName) {
|
|
51
|
+
if (variables.app) {
|
|
52
|
+
variables.app.key = appName;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (variables.app?.displayName && variables.app.displayName.toLowerCase().includes('miso')) {
|
|
56
|
+
variables.app.displayName = appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Updates port in variables if provided
|
|
62
|
+
* @function updatePort
|
|
63
|
+
* @param {Object} variables - Variables object
|
|
64
|
+
* @param {Object} options - CLI options
|
|
65
|
+
* @param {Object} config - Final configuration
|
|
66
|
+
*/
|
|
67
|
+
function updatePort(variables, options, config) {
|
|
68
|
+
if (options.port && config.port && variables.port !== config.port) {
|
|
69
|
+
variables.port = config.port;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Updates build configuration
|
|
75
|
+
* @function updateBuildConfig
|
|
76
|
+
* @param {Object} variables - Variables object
|
|
77
|
+
*/
|
|
78
|
+
function updateBuildConfig(variables) {
|
|
79
|
+
if (variables.build && variables.build.envOutputPath) {
|
|
80
|
+
variables.build.envOutputPath = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Updates database configuration for --app flag
|
|
86
|
+
* @function updateDatabaseConfig
|
|
87
|
+
* @param {Object} variables - Variables object
|
|
88
|
+
* @param {Object} options - CLI options
|
|
89
|
+
* @param {string} appName - Application name
|
|
90
|
+
*/
|
|
91
|
+
function updateDatabaseConfig(variables, options, appName) {
|
|
92
|
+
if (!options.app || !variables.requires) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (variables.requires.databases) {
|
|
97
|
+
variables.requires.databases = [{ name: appName }];
|
|
98
|
+
} else if (variables.requires.database && !variables.requires.databases) {
|
|
99
|
+
variables.requires.databases = [{ name: appName }];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Updates variables.yaml file after copying from template
|
|
105
|
+
* Updates app.key, displayName, and port with actual values
|
|
106
|
+
* @async
|
|
107
|
+
* @function updateTemplateVariables
|
|
108
|
+
* @param {string} appPath - Application directory path
|
|
109
|
+
* @param {string} appName - Application name
|
|
110
|
+
* @param {Object} options - CLI options
|
|
111
|
+
* @param {Object} config - Final configuration
|
|
112
|
+
*/
|
|
113
|
+
async function updateTemplateVariables(appPath, appName, options, config) {
|
|
114
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
115
|
+
try {
|
|
116
|
+
const yaml = require('js-yaml');
|
|
117
|
+
const variablesContent = await fs.readFile(variablesPath, 'utf8');
|
|
118
|
+
const variables = yaml.load(variablesContent);
|
|
119
|
+
|
|
120
|
+
updateAppMetadata(variables, appName);
|
|
121
|
+
updatePort(variables, options, config);
|
|
122
|
+
updateBuildConfig(variables);
|
|
123
|
+
updateDatabaseConfig(variables, options, appName);
|
|
124
|
+
|
|
125
|
+
await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }));
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error.code !== 'ENOENT') {
|
|
128
|
+
logger.warn(chalk.yellow(`⚠️ Warning: Could not update variables.yaml: ${error.message}`));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Merges port from template variables if not in options
|
|
135
|
+
* @function mergePort
|
|
136
|
+
* @param {Object} merged - Merged options object
|
|
137
|
+
* @param {Object} templateVariables - Template variables
|
|
138
|
+
*/
|
|
139
|
+
function mergePort(merged, templateVariables) {
|
|
140
|
+
if (!merged.port && templateVariables.port) {
|
|
141
|
+
merged.port = templateVariables.port;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Merges language from template variables if not in options
|
|
147
|
+
* @function mergeLanguage
|
|
148
|
+
* @param {Object} merged - Merged options object
|
|
149
|
+
* @param {Object} templateVariables - Template variables
|
|
150
|
+
*/
|
|
151
|
+
function mergeLanguage(merged, templateVariables) {
|
|
152
|
+
if (!merged.language && templateVariables.build?.language) {
|
|
153
|
+
merged.language = templateVariables.build.language;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Merges service requirements from template variables if not in options
|
|
159
|
+
* @function mergeServices
|
|
160
|
+
* @param {Object} merged - Merged options object
|
|
161
|
+
* @param {Object} templateVariables - Template variables
|
|
162
|
+
*/
|
|
163
|
+
function mergeServices(merged, templateVariables) {
|
|
164
|
+
// Database: use template requires.database if not specified in options
|
|
165
|
+
if (!Object.prototype.hasOwnProperty.call(merged, 'database') &&
|
|
166
|
+
templateVariables.requires?.database !== undefined) {
|
|
167
|
+
merged.database = templateVariables.requires.database;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Redis: use template requires.redis if not specified in options
|
|
171
|
+
if (!Object.prototype.hasOwnProperty.call(merged, 'redis') &&
|
|
172
|
+
templateVariables.requires?.redis !== undefined) {
|
|
173
|
+
merged.redis = templateVariables.requires.redis;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Storage: use template requires.storage if not specified in options
|
|
177
|
+
if (!Object.prototype.hasOwnProperty.call(merged, 'storage') &&
|
|
178
|
+
templateVariables.requires?.storage !== undefined) {
|
|
179
|
+
merged.storage = templateVariables.requires.storage;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Merges authentication from template variables if not in options
|
|
185
|
+
* @function mergeAuthentication
|
|
186
|
+
* @param {Object} merged - Merged options object
|
|
187
|
+
* @param {Object} templateVariables - Template variables
|
|
188
|
+
*/
|
|
189
|
+
function mergeAuthentication(merged, templateVariables) {
|
|
190
|
+
if (!Object.prototype.hasOwnProperty.call(merged, 'authentication') &&
|
|
191
|
+
templateVariables.authentication !== undefined) {
|
|
192
|
+
merged.authentication = !!templateVariables.authentication;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Merges template variables into options
|
|
198
|
+
* @function mergeTemplateVariables
|
|
199
|
+
* @param {Object} options - User-provided options
|
|
200
|
+
* @param {Object} templateVariables - Template variables from variables.yaml
|
|
201
|
+
* @returns {Object} Merged options object
|
|
202
|
+
*/
|
|
203
|
+
function mergeTemplateVariables(options, templateVariables) {
|
|
204
|
+
if (!templateVariables) {
|
|
205
|
+
return options;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const merged = { ...options };
|
|
209
|
+
|
|
210
|
+
mergePort(merged, templateVariables);
|
|
211
|
+
mergeLanguage(merged, templateVariables);
|
|
212
|
+
mergeServices(merged, templateVariables);
|
|
213
|
+
mergeAuthentication(merged, templateVariables);
|
|
214
|
+
|
|
215
|
+
return merged;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
loadTemplateVariables,
|
|
220
|
+
updateTemplateVariables,
|
|
221
|
+
mergeTemplateVariables
|
|
222
|
+
};
|
|
223
|
+
|