@aifabrix/builder 2.22.2 → 2.31.1
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/jest.config.coverage.js +37 -0
- package/lib/api/pipeline.api.js +10 -9
- package/lib/app-deploy.js +36 -14
- package/lib/app-list.js +191 -71
- package/lib/app-prompts.js +77 -26
- package/lib/app-readme.js +123 -5
- package/lib/app-rotate-secret.js +210 -80
- package/lib/app-run-helpers.js +200 -172
- package/lib/app-run.js +137 -68
- package/lib/audit-logger.js +8 -7
- package/lib/build.js +161 -250
- package/lib/cli.js +73 -65
- package/lib/commands/login.js +45 -31
- package/lib/commands/logout.js +181 -0
- package/lib/commands/secure.js +59 -24
- package/lib/config.js +79 -45
- package/lib/datasource-deploy.js +89 -29
- package/lib/deployer.js +164 -129
- package/lib/diff.js +63 -21
- package/lib/environment-deploy.js +36 -19
- package/lib/external-system-deploy.js +134 -66
- package/lib/external-system-download.js +244 -171
- package/lib/external-system-test.js +199 -164
- package/lib/generator-external.js +145 -72
- package/lib/generator-helpers.js +49 -17
- package/lib/generator-split.js +105 -58
- package/lib/infra.js +101 -131
- package/lib/schema/application-schema.json +895 -896
- package/lib/schema/env-config.yaml +11 -4
- package/lib/template-validator.js +13 -4
- package/lib/utils/api.js +8 -8
- package/lib/utils/app-register-auth.js +36 -18
- package/lib/utils/app-run-containers.js +140 -0
- package/lib/utils/auth-headers.js +6 -6
- package/lib/utils/build-copy.js +60 -2
- package/lib/utils/build-helpers.js +94 -0
- package/lib/utils/cli-utils.js +177 -76
- package/lib/utils/compose-generator.js +12 -2
- package/lib/utils/config-tokens.js +151 -9
- package/lib/utils/deployment-errors.js +137 -69
- package/lib/utils/deployment-validation-helpers.js +103 -0
- package/lib/utils/docker-build.js +57 -0
- package/lib/utils/dockerfile-utils.js +13 -3
- package/lib/utils/env-copy.js +163 -94
- package/lib/utils/env-map.js +226 -86
- package/lib/utils/error-formatters/network-errors.js +0 -1
- package/lib/utils/external-system-display.js +14 -19
- package/lib/utils/external-system-env-helpers.js +107 -0
- package/lib/utils/external-system-test-helpers.js +144 -0
- package/lib/utils/health-check.js +10 -8
- package/lib/utils/infra-status.js +123 -0
- package/lib/utils/paths.js +228 -49
- package/lib/utils/schema-loader.js +125 -57
- package/lib/utils/token-manager.js +3 -3
- package/lib/utils/yaml-preserve.js +55 -16
- package/lib/validate.js +87 -89
- package/package.json +7 -5
- package/scripts/ci-fix.sh +19 -0
- package/scripts/ci-simulate.sh +19 -0
- package/scripts/install-local.js +210 -0
- package/templates/applications/miso-controller/test.yaml +1 -0
- package/templates/python/Dockerfile.hbs +8 -45
- package/templates/typescript/Dockerfile.hbs +8 -42
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
# Environment Configuration
|
|
2
2
|
# Defines host values for different deployment contexts
|
|
3
|
+
#
|
|
4
|
+
# Public Port Support (Docker Context):
|
|
5
|
+
# For docker context, any *_PORT variable automatically gets a corresponding *_PUBLIC_PORT
|
|
6
|
+
# calculated as: *_PUBLIC_PORT = *_PORT + (developer-id * 100)
|
|
7
|
+
# This enables developer-specific host access ports while maintaining internal container ports.
|
|
8
|
+
# Example: MISO_PORT=3000 (internal) -> MISO_PUBLIC_PORT=3100 (for developer-id 1)
|
|
9
|
+
# The pattern applies to all services (MISO, KEYCLOAK, DB, REDIS, etc.) automatically.
|
|
3
10
|
|
|
4
11
|
environments:
|
|
5
12
|
docker:
|
|
6
13
|
DB_HOST: postgres
|
|
7
|
-
DB_PORT: 5432
|
|
14
|
+
DB_PORT: 5432 # Internal port (container-to-container). DB_PUBLIC_PORT calculated automatically.
|
|
8
15
|
REDIS_HOST: redis
|
|
9
|
-
REDIS_PORT: 6379
|
|
16
|
+
REDIS_PORT: 6379 # Internal port (container-to-container). REDIS_PUBLIC_PORT calculated automatically.
|
|
10
17
|
MISO_HOST: miso-controller
|
|
11
|
-
MISO_PORT: 3000
|
|
18
|
+
MISO_PORT: 3000 # Internal port (container-to-container). MISO_PUBLIC_PORT calculated automatically.
|
|
12
19
|
KEYCLOAK_HOST: keycloak
|
|
13
|
-
KEYCLOAK_PORT: 8082
|
|
20
|
+
KEYCLOAK_PORT: 8082 # Internal port (container-to-container). KEYCLOAK_PUBLIC_PORT calculated automatically.
|
|
14
21
|
NODE_ENV: production
|
|
15
22
|
PYTHONUNBUFFERED: 1
|
|
16
23
|
PYTHONDONTWRITEBYTECODE: 1
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
const fs = require('fs').promises;
|
|
12
12
|
const fsSync = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
+
const { getProjectRoot } = require('./utils/paths');
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Validates that a template exists and contains files
|
|
@@ -23,7 +24,9 @@ async function validateTemplate(templateName) {
|
|
|
23
24
|
throw new Error('Template name is required and must be a string');
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
// Use getProjectRoot to reliably find templates in all environments (including CI)
|
|
28
|
+
const projectRoot = getProjectRoot();
|
|
29
|
+
const templatePath = path.join(projectRoot, 'templates', 'applications', templateName);
|
|
27
30
|
|
|
28
31
|
// Check if template folder exists
|
|
29
32
|
if (!fsSync.existsSync(templatePath)) {
|
|
@@ -63,7 +66,9 @@ async function copyTemplateFiles(templateName, appPath) {
|
|
|
63
66
|
// Validate template first
|
|
64
67
|
await validateTemplate(templateName);
|
|
65
68
|
|
|
66
|
-
|
|
69
|
+
// Use getProjectRoot to reliably find templates in all environments (including CI)
|
|
70
|
+
const projectRoot = getProjectRoot();
|
|
71
|
+
const templatePath = path.join(projectRoot, 'templates', 'applications', templateName);
|
|
67
72
|
const copiedFiles = [];
|
|
68
73
|
|
|
69
74
|
async function copyDirectory(sourceDir, targetDir) {
|
|
@@ -113,7 +118,9 @@ async function copyAppFiles(language, appPath) {
|
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
const normalizedLanguage = language.toLowerCase();
|
|
116
|
-
|
|
121
|
+
// Use getProjectRoot to reliably find templates in all environments (including CI)
|
|
122
|
+
const projectRoot = getProjectRoot();
|
|
123
|
+
const languageTemplatePath = path.join(projectRoot, 'templates', normalizedLanguage);
|
|
117
124
|
|
|
118
125
|
// Check if language template folder exists
|
|
119
126
|
if (!fsSync.existsSync(languageTemplatePath)) {
|
|
@@ -166,7 +173,9 @@ async function copyAppFiles(language, appPath) {
|
|
|
166
173
|
* @returns {Promise<string[]>} Array of available template names
|
|
167
174
|
*/
|
|
168
175
|
async function listAvailableTemplates() {
|
|
169
|
-
|
|
176
|
+
// Use getProjectRoot to reliably find templates in all environments (including CI)
|
|
177
|
+
const projectRoot = getProjectRoot();
|
|
178
|
+
const templatesDir = path.join(projectRoot, 'templates', 'applications');
|
|
170
179
|
|
|
171
180
|
if (!fsSync.existsSync(templatesDir)) {
|
|
172
181
|
return [];
|
package/lib/utils/api.js
CHANGED
|
@@ -27,14 +27,14 @@ async function logApiPerformance(params) {
|
|
|
27
27
|
// Log all API calls (both success and failure) to audit log for troubleshooting
|
|
28
28
|
// This helps track what API calls were made when errors occur
|
|
29
29
|
try {
|
|
30
|
-
await auditLogger.logApiCall(
|
|
31
|
-
params.url,
|
|
32
|
-
params.options,
|
|
33
|
-
params.statusCode,
|
|
34
|
-
params.duration,
|
|
35
|
-
params.success,
|
|
36
|
-
params.errorInfo || {}
|
|
37
|
-
);
|
|
30
|
+
await auditLogger.logApiCall({
|
|
31
|
+
url: params.url,
|
|
32
|
+
options: params.options,
|
|
33
|
+
statusCode: params.statusCode,
|
|
34
|
+
duration: params.duration,
|
|
35
|
+
success: params.success,
|
|
36
|
+
errorInfo: params.errorInfo || {}
|
|
37
|
+
});
|
|
38
38
|
} catch (logError) {
|
|
39
39
|
// Don't fail the API call if audit logging fails
|
|
40
40
|
// Silently continue - audit logging should never break functionality
|
|
@@ -47,6 +47,38 @@ function displayAuthenticationError(error = null, controllerUrlOrData = null) {
|
|
|
47
47
|
process.exit(1);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Find device token from config by trying each stored URL
|
|
52
|
+
* @async
|
|
53
|
+
* @param {Object} deviceConfig - Device configuration object
|
|
54
|
+
* @param {Array} attemptedUrls - Array to track attempted URLs
|
|
55
|
+
* @returns {Promise<Object|null>} Token result with token and controllerUrl, or null if not found
|
|
56
|
+
*/
|
|
57
|
+
async function findDeviceTokenFromConfig(deviceConfig, attemptedUrls) {
|
|
58
|
+
const deviceUrls = Object.keys(deviceConfig);
|
|
59
|
+
if (deviceUrls.length === 0) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const storedUrl of deviceUrls) {
|
|
64
|
+
attemptedUrls.push(storedUrl);
|
|
65
|
+
try {
|
|
66
|
+
const normalizedStoredUrl = normalizeControllerUrl(storedUrl);
|
|
67
|
+
const deviceToken = await getOrRefreshDeviceToken(normalizedStoredUrl);
|
|
68
|
+
if (deviceToken && deviceToken.token) {
|
|
69
|
+
return {
|
|
70
|
+
token: deviceToken.token,
|
|
71
|
+
controllerUrl: deviceToken.controller || normalizedStoredUrl
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
// Continue to next URL
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
50
82
|
/**
|
|
51
83
|
* Check if user is authenticated and get token
|
|
52
84
|
* @async
|
|
@@ -83,24 +115,10 @@ async function checkAuthentication(controllerUrl, environment) {
|
|
|
83
115
|
|
|
84
116
|
// If no token yet, try to find any device token in config
|
|
85
117
|
if (!token && config.device) {
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
attemptedUrls.push(storedUrl);
|
|
91
|
-
try {
|
|
92
|
-
const normalizedStoredUrl = normalizeControllerUrl(storedUrl);
|
|
93
|
-
const deviceToken = await getOrRefreshDeviceToken(normalizedStoredUrl);
|
|
94
|
-
if (deviceToken && deviceToken.token) {
|
|
95
|
-
token = deviceToken.token;
|
|
96
|
-
finalControllerUrl = deviceToken.controller || normalizedStoredUrl;
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
} catch (error) {
|
|
100
|
-
lastError = error;
|
|
101
|
-
// Continue to next URL
|
|
102
|
-
}
|
|
103
|
-
}
|
|
118
|
+
const tokenResult = await findDeviceTokenFromConfig(config.device, attemptedUrls);
|
|
119
|
+
if (tokenResult) {
|
|
120
|
+
token = tokenResult.token;
|
|
121
|
+
finalControllerUrl = tokenResult.controllerUrl;
|
|
104
122
|
}
|
|
105
123
|
}
|
|
106
124
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - App Run Container Helpers
|
|
3
|
+
*
|
|
4
|
+
* Container-related helper functions for application run workflow.
|
|
5
|
+
* Extracted from app-run-helpers.js to reduce file size.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Container helper functions for application run workflow
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { exec } = require('child_process');
|
|
13
|
+
const { promisify } = require('util');
|
|
14
|
+
const chalk = require('chalk');
|
|
15
|
+
const logger = require('./logger');
|
|
16
|
+
|
|
17
|
+
const execAsync = promisify(exec);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Checks if Docker image exists for the application
|
|
21
|
+
* @param {string} imageName - Image name (can include repository prefix)
|
|
22
|
+
* @param {string} tag - Image tag (default: latest)
|
|
23
|
+
* @param {boolean} [debug=false] - Enable debug logging
|
|
24
|
+
* @returns {Promise<boolean>} True if image exists
|
|
25
|
+
*/
|
|
26
|
+
async function checkImageExists(imageName, tag = 'latest', debug = false) {
|
|
27
|
+
try {
|
|
28
|
+
const fullImageName = `${imageName}:${tag}`;
|
|
29
|
+
const cmd = `docker images --format "{{.Repository}}:{{.Tag}}" --filter "reference=${fullImageName}"`;
|
|
30
|
+
if (debug) {
|
|
31
|
+
logger.log(chalk.gray(`[DEBUG] Executing: ${cmd}`));
|
|
32
|
+
}
|
|
33
|
+
const { stdout } = await execAsync(cmd);
|
|
34
|
+
const lines = stdout.trim().split('\n').filter(line => line.trim() !== '');
|
|
35
|
+
const exists = lines.some(line => line.trim() === fullImageName);
|
|
36
|
+
if (debug) {
|
|
37
|
+
logger.log(chalk.gray(`[DEBUG] Image ${fullImageName} exists: ${exists}`));
|
|
38
|
+
}
|
|
39
|
+
return exists;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (debug) {
|
|
42
|
+
logger.log(chalk.gray(`[DEBUG] Image check failed: ${error.message}`));
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Checks if container is already running
|
|
50
|
+
* @param {string} appName - Application name
|
|
51
|
+
* @param {number|string} developerId - Developer ID (0 = default infra, > 0 = developer-specific; string allowed)
|
|
52
|
+
* @param {boolean} [debug=false] - Enable debug logging
|
|
53
|
+
* @returns {Promise<boolean>} True if container is running
|
|
54
|
+
*/
|
|
55
|
+
async function checkContainerRunning(appName, developerId, debug = false) {
|
|
56
|
+
try {
|
|
57
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
58
|
+
const containerName = idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${developerId}-${appName}`;
|
|
59
|
+
const cmd = `docker ps --filter "name=${containerName}" --format "{{.Names}}"`;
|
|
60
|
+
if (debug) {
|
|
61
|
+
logger.log(chalk.gray(`[DEBUG] Executing: ${cmd}`));
|
|
62
|
+
}
|
|
63
|
+
const { stdout } = await execAsync(cmd);
|
|
64
|
+
const isRunning = stdout.trim() === containerName;
|
|
65
|
+
if (debug) {
|
|
66
|
+
logger.log(chalk.gray(`[DEBUG] Container ${containerName} running: ${isRunning}`));
|
|
67
|
+
if (isRunning) {
|
|
68
|
+
const statusCmd = `docker ps --filter "name=${containerName}" --format "{{.Status}}"`;
|
|
69
|
+
const { stdout: status } = await execAsync(statusCmd);
|
|
70
|
+
const portsCmd = `docker ps --filter "name=${containerName}" --format "{{.Ports}}"`;
|
|
71
|
+
const { stdout: ports } = await execAsync(portsCmd);
|
|
72
|
+
logger.log(chalk.gray(`[DEBUG] Container status: ${status.trim()}`));
|
|
73
|
+
logger.log(chalk.gray(`[DEBUG] Container ports: ${ports.trim()}`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return isRunning;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (debug) {
|
|
79
|
+
logger.log(chalk.gray(`[DEBUG] Container check failed: ${error.message}`));
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Stops and removes existing container
|
|
87
|
+
* @param {string} appName - Application name
|
|
88
|
+
* @param {number|string} developerId - Developer ID (0 = default infra, > 0 = developer-specific; string allowed)
|
|
89
|
+
* @param {boolean} [debug=false] - Enable debug logging
|
|
90
|
+
*/
|
|
91
|
+
async function stopAndRemoveContainer(appName, developerId, debug = false) {
|
|
92
|
+
try {
|
|
93
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
94
|
+
const containerName = idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${developerId}-${appName}`;
|
|
95
|
+
logger.log(chalk.yellow(`Stopping existing container ${containerName}...`));
|
|
96
|
+
const stopCmd = `docker stop ${containerName}`;
|
|
97
|
+
if (debug) {
|
|
98
|
+
logger.log(chalk.gray(`[DEBUG] Executing: ${stopCmd}`));
|
|
99
|
+
}
|
|
100
|
+
await execAsync(stopCmd);
|
|
101
|
+
const rmCmd = `docker rm ${containerName}`;
|
|
102
|
+
if (debug) {
|
|
103
|
+
logger.log(chalk.gray(`[DEBUG] Executing: ${rmCmd}`));
|
|
104
|
+
}
|
|
105
|
+
await execAsync(rmCmd);
|
|
106
|
+
logger.log(chalk.green(`✓ Container ${containerName} stopped and removed`));
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (debug) {
|
|
109
|
+
logger.log(chalk.gray(`[DEBUG] Stop/remove container error: ${error.message}`));
|
|
110
|
+
}
|
|
111
|
+
const idNum2 = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
112
|
+
const containerName = idNum2 === 0 ? `aifabrix-${appName}` : `aifabrix-dev${developerId}-${appName}`;
|
|
113
|
+
logger.log(chalk.gray(`Container ${containerName} was not running`));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Log container status for debugging
|
|
119
|
+
* @async
|
|
120
|
+
* @param {string} containerName - Container name
|
|
121
|
+
* @param {boolean} debug - Enable debug logging
|
|
122
|
+
*/
|
|
123
|
+
async function logContainerStatus(containerName, debug) {
|
|
124
|
+
if (debug) {
|
|
125
|
+
const statusCmd = `docker ps --filter "name=${containerName}" --format "{{.Status}}"`;
|
|
126
|
+
const { stdout: status } = await execAsync(statusCmd);
|
|
127
|
+
const portsCmd = `docker ps --filter "name=${containerName}" --format "{{.Ports}}"`;
|
|
128
|
+
const { stdout: ports } = await execAsync(portsCmd);
|
|
129
|
+
logger.log(chalk.gray(`[DEBUG] Container status: ${status.trim()}`));
|
|
130
|
+
logger.log(chalk.gray(`[DEBUG] Container ports: ${ports.trim()}`));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
checkImageExists,
|
|
136
|
+
checkContainerRunning,
|
|
137
|
+
stopAndRemoveContainer,
|
|
138
|
+
logContainerStatus
|
|
139
|
+
};
|
|
140
|
+
|
|
@@ -47,10 +47,10 @@ function createClientCredentialsHeaders(clientId, clientSecret) {
|
|
|
47
47
|
* Supports both Bearer token and client credentials authentication
|
|
48
48
|
*
|
|
49
49
|
* @param {Object} authConfig - Authentication configuration
|
|
50
|
-
* @param {string} authConfig.type - Auth type: 'bearer' or 'credentials'
|
|
50
|
+
* @param {string} authConfig.type - Auth type: 'bearer' or 'client-credentials'
|
|
51
51
|
* @param {string} [authConfig.token] - Bearer token (for type 'bearer')
|
|
52
|
-
* @param {string} [authConfig.clientId] - Client ID (for type 'credentials')
|
|
53
|
-
* @param {string} [authConfig.clientSecret] - Client secret (for type 'credentials')
|
|
52
|
+
* @param {string} [authConfig.clientId] - Client ID (for type 'client-credentials')
|
|
53
|
+
* @param {string} [authConfig.clientSecret] - Client secret (for type 'client-credentials')
|
|
54
54
|
* @returns {Object} Headers object with authentication
|
|
55
55
|
* @throws {Error} If auth config is invalid
|
|
56
56
|
*/
|
|
@@ -66,14 +66,14 @@ function createAuthHeaders(authConfig) {
|
|
|
66
66
|
return createBearerTokenHeaders(authConfig.token);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
if (authConfig.type === 'credentials') {
|
|
69
|
+
if (authConfig.type === 'client-credentials') {
|
|
70
70
|
if (!authConfig.clientId || !authConfig.clientSecret) {
|
|
71
|
-
throw new Error('Client ID and Client Secret are required for credentials authentication');
|
|
71
|
+
throw new Error('Client ID and Client Secret are required for client-credentials authentication');
|
|
72
72
|
}
|
|
73
73
|
return createClientCredentialsHeaders(authConfig.clientId, authConfig.clientSecret);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
throw new Error(`Invalid authentication type: ${authConfig.type}. Must be 'bearer' or 'credentials'`);
|
|
76
|
+
throw new Error(`Invalid authentication type: ${authConfig.type}. Must be 'bearer' or 'client-credentials'`);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
module.exports = {
|
package/lib/utils/build-copy.js
CHANGED
|
@@ -31,7 +31,7 @@ const paths = require('./paths');
|
|
|
31
31
|
* // Returns: '~/.aifabrix/applications-dev-1'
|
|
32
32
|
*/
|
|
33
33
|
async function copyBuilderToDevDirectory(appName, developerId) {
|
|
34
|
-
const builderPath =
|
|
34
|
+
const builderPath = paths.getBuilderPath(appName);
|
|
35
35
|
|
|
36
36
|
// Ensure builder directory exists
|
|
37
37
|
if (!fsSync.existsSync(builderPath)) {
|
|
@@ -140,10 +140,68 @@ function devDirectoryExists(appName, developerId) {
|
|
|
140
140
|
return fsSync.existsSync(devDir);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Copies application template files to dev directory
|
|
145
|
+
* Used when apps directory doesn't exist to ensure build can proceed
|
|
146
|
+
* @async
|
|
147
|
+
* @param {string} templatePath - Path to template directory
|
|
148
|
+
* @param {string} devDir - Target dev directory
|
|
149
|
+
* @param {string} _language - Language (typescript/python) - currently unused but kept for future use
|
|
150
|
+
* @throws {Error} If copying fails
|
|
151
|
+
*/
|
|
152
|
+
async function copyTemplateFilesToDevDir(templatePath, devDir, _language) {
|
|
153
|
+
if (!fsSync.existsSync(templatePath)) {
|
|
154
|
+
throw new Error(`Template path not found: ${templatePath}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Ensure dev directory exists before copying files
|
|
158
|
+
await fs.mkdir(devDir, { recursive: true });
|
|
159
|
+
|
|
160
|
+
const entries = await fs.readdir(templatePath);
|
|
161
|
+
|
|
162
|
+
// Copy only application files, skip Dockerfile and docker-compose templates
|
|
163
|
+
const appFiles = entries.filter(entry => {
|
|
164
|
+
const lowerEntry = entry.toLowerCase();
|
|
165
|
+
// Include .gitignore, exclude .hbs files and docker-related files
|
|
166
|
+
if (entry === '.gitignore') {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
if (lowerEntry.endsWith('.hbs')) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
if (lowerEntry.startsWith('dockerfile') || lowerEntry.includes('docker-compose')) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
if (entry.startsWith('.') && entry !== '.gitignore') {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
for (const entry of appFiles) {
|
|
182
|
+
const sourcePath = path.join(templatePath, entry);
|
|
183
|
+
const targetPath = path.join(devDir, entry);
|
|
184
|
+
|
|
185
|
+
// Skip if source file doesn't exist (e.g., .gitignore might not be in template)
|
|
186
|
+
try {
|
|
187
|
+
const entryStats = await fs.stat(sourcePath);
|
|
188
|
+
if (entryStats.isFile()) {
|
|
189
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
// Skip files that don't exist (e.g., .gitignore might not be in template)
|
|
193
|
+
if (error.code !== 'ENOENT') {
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
143
200
|
module.exports = {
|
|
144
201
|
copyBuilderToDevDirectory,
|
|
145
202
|
copyAppSourceFiles,
|
|
146
203
|
getDevDirectory,
|
|
147
|
-
devDirectoryExists
|
|
204
|
+
devDirectoryExists,
|
|
205
|
+
copyTemplateFilesToDevDir
|
|
148
206
|
};
|
|
149
207
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build Helper Functions
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for build operations.
|
|
5
|
+
* Separated from build.js to maintain file size limits.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Build helper functions
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const dockerfileUtils = require('./dockerfile-utils');
|
|
14
|
+
const logger = require('./logger');
|
|
15
|
+
const chalk = require('chalk');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Determine Dockerfile path (template, custom, or generate)
|
|
19
|
+
* @async
|
|
20
|
+
* @param {string} appName - Application name
|
|
21
|
+
* @param {Object} options - Options with language, config, buildConfig, contextPath, forceTemplate, devDir
|
|
22
|
+
* @param {Function} generateDockerfileFn - Function to generate Dockerfile
|
|
23
|
+
* @returns {Promise<string>} Path to Dockerfile
|
|
24
|
+
*/
|
|
25
|
+
async function determineDockerfile(appName, options, generateDockerfileFn) {
|
|
26
|
+
// Use dev directory if provided, otherwise fall back to builder directory
|
|
27
|
+
const searchPath = options.devDir || path.join(process.cwd(), 'builder', appName);
|
|
28
|
+
|
|
29
|
+
const templateDockerfile = dockerfileUtils.checkTemplateDockerfile(searchPath, appName, options.forceTemplate);
|
|
30
|
+
if (templateDockerfile) {
|
|
31
|
+
const relativePath = path.relative(process.cwd(), templateDockerfile);
|
|
32
|
+
logger.log(chalk.green(`✓ Using existing Dockerfile: ${relativePath}`));
|
|
33
|
+
return templateDockerfile;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const customDockerfile = dockerfileUtils.checkProjectDockerfile(searchPath, appName, options.buildConfig, options.contextPath, options.forceTemplate);
|
|
37
|
+
if (customDockerfile) {
|
|
38
|
+
logger.log(chalk.green(`✓ Using custom Dockerfile: ${options.buildConfig.dockerfile}`));
|
|
39
|
+
return customDockerfile;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Generate Dockerfile in dev directory if provided
|
|
43
|
+
const dockerfilePath = await generateDockerfileFn(appName, options.language, options.config, options.buildConfig, options.devDir);
|
|
44
|
+
const relativePath = path.relative(process.cwd(), dockerfilePath);
|
|
45
|
+
logger.log(chalk.green(`✓ Generated Dockerfile from template: ${relativePath}`));
|
|
46
|
+
return dockerfilePath;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Loads and validates configuration for build
|
|
51
|
+
* @async
|
|
52
|
+
* @param {string} appName - Application name
|
|
53
|
+
* @returns {Promise<Object>} Configuration object with config, imageName, and buildConfig
|
|
54
|
+
* @throws {Error} If configuration cannot be loaded or validated
|
|
55
|
+
*/
|
|
56
|
+
async function loadAndValidateConfig(appName) {
|
|
57
|
+
const { loadVariablesYaml } = require('../build');
|
|
58
|
+
const validator = require('../validator');
|
|
59
|
+
|
|
60
|
+
const variables = await loadVariablesYaml(appName);
|
|
61
|
+
|
|
62
|
+
// Validate configuration
|
|
63
|
+
const validation = await validator.validateVariables(appName);
|
|
64
|
+
if (!validation.valid) {
|
|
65
|
+
throw new Error(`Configuration validation failed:\n${validation.errors.join('\n')}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Extract image name
|
|
69
|
+
let imageName;
|
|
70
|
+
if (typeof variables.image === 'string') {
|
|
71
|
+
imageName = variables.image.split(':')[0];
|
|
72
|
+
} else if (variables.image?.name) {
|
|
73
|
+
imageName = variables.image.name;
|
|
74
|
+
} else if (variables.app?.key) {
|
|
75
|
+
imageName = variables.app.key;
|
|
76
|
+
} else {
|
|
77
|
+
imageName = appName;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Extract build config
|
|
81
|
+
const buildConfig = variables.build || {};
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
config: variables,
|
|
85
|
+
imageName,
|
|
86
|
+
buildConfig
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
determineDockerfile,
|
|
92
|
+
loadAndValidateConfig
|
|
93
|
+
};
|
|
94
|
+
|