@aifabrix/builder 2.22.1 → 2.31.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/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 +101 -57
- 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/secrets-set.js +2 -2
- package/lib/commands/secure.js +61 -26
- 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/environment-checker.js +2 -2
- 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/local-secrets.js +3 -2
- package/lib/utils/paths.js +228 -49
- package/lib/utils/schema-loader.js +125 -57
- package/lib/utils/token-manager.js +10 -7
- package/lib/utils/yaml-preserve.js +55 -16
- package/lib/validate.js +87 -89
- package/package.json +4 -4
- package/scripts/ci-fix.sh +19 -0
- package/scripts/ci-simulate.sh +19 -0
- package/templates/applications/miso-controller/test.yaml +1 -0
- package/templates/python/Dockerfile.hbs +8 -45
- package/templates/typescript/Dockerfile.hbs +8 -42
package/lib/utils/env-copy.js
CHANGED
|
@@ -19,6 +19,163 @@ const { rewriteInfraEndpoints } = require('./env-endpoints');
|
|
|
19
19
|
const { buildEnvVarMap } = require('./env-map');
|
|
20
20
|
const { interpolateEnvVars } = require('./secrets-helpers');
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Read developer ID from config file synchronously
|
|
24
|
+
* @param {Object} config - Config object
|
|
25
|
+
* @returns {number|null} Developer ID or null if not found
|
|
26
|
+
*/
|
|
27
|
+
function readDeveloperIdFromConfig(config) {
|
|
28
|
+
const configPath = config && config.CONFIG_FILE ? config.CONFIG_FILE : null;
|
|
29
|
+
if (!configPath || !fs.existsSync(configPath)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const cfgContent = fs.readFileSync(configPath, 'utf8');
|
|
35
|
+
const cfg = yaml.load(cfgContent) || {};
|
|
36
|
+
const raw = cfg['developer-id'];
|
|
37
|
+
if (typeof raw === 'number') {
|
|
38
|
+
return raw;
|
|
39
|
+
}
|
|
40
|
+
if (typeof raw === 'string' && /^[0-9]+$/.test(raw)) {
|
|
41
|
+
return parseInt(raw, 10);
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// ignore, will fallback to 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolve output path for env file
|
|
52
|
+
* @param {string} rawOutputPath - Raw output path from variables.yaml
|
|
53
|
+
* @param {string} variablesPath - Path to variables.yaml
|
|
54
|
+
* @returns {string} Resolved output path
|
|
55
|
+
*/
|
|
56
|
+
function resolveEnvOutputPath(rawOutputPath, variablesPath) {
|
|
57
|
+
let outputPath;
|
|
58
|
+
if (path.isAbsolute(rawOutputPath)) {
|
|
59
|
+
outputPath = rawOutputPath;
|
|
60
|
+
} else {
|
|
61
|
+
const variablesDir = path.dirname(variablesPath);
|
|
62
|
+
outputPath = path.resolve(variablesDir, rawOutputPath);
|
|
63
|
+
}
|
|
64
|
+
if (!outputPath.endsWith('.env')) {
|
|
65
|
+
if (fs.existsSync(outputPath) && fs.statSync(outputPath).isDirectory()) {
|
|
66
|
+
outputPath = path.join(outputPath, '.env');
|
|
67
|
+
} else {
|
|
68
|
+
outputPath = path.join(outputPath, '.env');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return outputPath;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Calculate developer-specific app port
|
|
76
|
+
* @param {number} baseAppPort - Base application port
|
|
77
|
+
* @returns {number} Developer-specific app port
|
|
78
|
+
*/
|
|
79
|
+
function calculateDevAppPort(baseAppPort) {
|
|
80
|
+
const devIdRaw = process.env.AIFABRIX_DEVELOPERID;
|
|
81
|
+
let devIdNum = Number.isFinite(parseInt(devIdRaw, 10)) ? parseInt(devIdRaw, 10) : null;
|
|
82
|
+
try {
|
|
83
|
+
if (devIdNum === null) {
|
|
84
|
+
devIdNum = readDeveloperIdFromConfig(config) || 0;
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
devIdNum = 0;
|
|
88
|
+
}
|
|
89
|
+
return devIdNum === 0 ? baseAppPort : (baseAppPort + (devIdNum * 100));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Update PORT in env content
|
|
94
|
+
* @param {string} envContent - Environment file content
|
|
95
|
+
* @param {number} appPort - Application port
|
|
96
|
+
* @returns {string} Updated env content
|
|
97
|
+
*/
|
|
98
|
+
function updatePortInEnv(envContent, appPort) {
|
|
99
|
+
if (/^PORT\s*=.*$/m.test(envContent)) {
|
|
100
|
+
return envContent.replace(/^PORT\s*=\s*.*$/m, `PORT=${appPort}`);
|
|
101
|
+
}
|
|
102
|
+
return `${envContent}\nPORT=${appPort}\n`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Update localhost URLs in env content
|
|
107
|
+
* @param {string} envContent - Environment file content
|
|
108
|
+
* @param {number} baseAppPort - Base application port
|
|
109
|
+
* @param {number} appPort - Developer-specific application port
|
|
110
|
+
* @returns {string} Updated env content
|
|
111
|
+
*/
|
|
112
|
+
function updateLocalhostUrls(envContent, baseAppPort, appPort) {
|
|
113
|
+
const localhostUrlPattern = /(https?:\/\/localhost:)(\d+)(\b[^ \n]*)?/g;
|
|
114
|
+
return envContent.replace(localhostUrlPattern, (match, prefix, portNum, rest = '') => {
|
|
115
|
+
const num = parseInt(portNum, 10);
|
|
116
|
+
if (num === baseAppPort) {
|
|
117
|
+
return `${prefix}${appPort}${rest || ''}`;
|
|
118
|
+
}
|
|
119
|
+
return match;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Extract env vars from content for interpolation
|
|
125
|
+
* @param {string} envContent - Environment file content
|
|
126
|
+
* @param {Object} envVars - Existing env vars map
|
|
127
|
+
* @returns {Object} Updated env vars map
|
|
128
|
+
*/
|
|
129
|
+
function extractEnvVarsFromContent(envContent, envVars) {
|
|
130
|
+
const redisHostMatch = envContent.match(/^REDIS_HOST\s*=\s*([^\r\n$]+)/m);
|
|
131
|
+
const redisPortMatch = envContent.match(/^REDIS_PORT\s*=\s*([^\r\n$]+)/m);
|
|
132
|
+
const dbHostMatch = envContent.match(/^DB_HOST\s*=\s*([^\r\n$]+)/m);
|
|
133
|
+
const dbPortMatch = envContent.match(/^DB_PORT\s*=\s*([^\r\n$]+)/m);
|
|
134
|
+
if (redisHostMatch && redisHostMatch[1] && !redisHostMatch[1].includes('${')) {
|
|
135
|
+
envVars.REDIS_HOST = redisHostMatch[1].trim();
|
|
136
|
+
}
|
|
137
|
+
if (redisPortMatch && redisPortMatch[1] && !redisPortMatch[1].includes('${')) {
|
|
138
|
+
envVars.REDIS_PORT = redisPortMatch[1].trim();
|
|
139
|
+
}
|
|
140
|
+
if (dbHostMatch && dbHostMatch[1] && !dbHostMatch[1].includes('${')) {
|
|
141
|
+
envVars.DB_HOST = dbHostMatch[1].trim();
|
|
142
|
+
}
|
|
143
|
+
if (dbPortMatch && dbPortMatch[1] && !dbPortMatch[1].includes('${')) {
|
|
144
|
+
envVars.DB_PORT = dbPortMatch[1].trim();
|
|
145
|
+
}
|
|
146
|
+
return envVars;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Patch env content for local development
|
|
151
|
+
* @async
|
|
152
|
+
* @param {string} envContent - Original env content
|
|
153
|
+
* @param {Object} variables - Variables from variables.yaml
|
|
154
|
+
* @returns {Promise<string>} Patched env content
|
|
155
|
+
*/
|
|
156
|
+
async function patchEnvContentForLocal(envContent, variables) {
|
|
157
|
+
const baseAppPort = variables.build?.localPort || variables.port || 3000;
|
|
158
|
+
const appPort = calculateDevAppPort(baseAppPort);
|
|
159
|
+
const devIdNum = readDeveloperIdFromConfig(config) || 0;
|
|
160
|
+
const infraPorts = devConfig.getDevPorts(devIdNum);
|
|
161
|
+
|
|
162
|
+
// Update PORT
|
|
163
|
+
envContent = updatePortInEnv(envContent, appPort);
|
|
164
|
+
|
|
165
|
+
// Update localhost URLs
|
|
166
|
+
envContent = updateLocalhostUrls(envContent, baseAppPort, appPort);
|
|
167
|
+
|
|
168
|
+
// Rewrite infra endpoints
|
|
169
|
+
envContent = await rewriteInfraEndpoints(envContent, 'local', infraPorts);
|
|
170
|
+
|
|
171
|
+
// Interpolate ${VAR} references
|
|
172
|
+
const envVars = await buildEnvVarMap('local', null, devIdNum);
|
|
173
|
+
const updatedEnvVars = extractEnvVarsFromContent(envContent, envVars);
|
|
174
|
+
envContent = interpolateEnvVars(envContent, updatedEnvVars);
|
|
175
|
+
|
|
176
|
+
return envContent;
|
|
177
|
+
}
|
|
178
|
+
|
|
22
179
|
/**
|
|
23
180
|
* Process and optionally copy env file to envOutputPath if configured
|
|
24
181
|
* Regenerates .env file with env=local for local development (apps/.env)
|
|
@@ -38,113 +195,25 @@ async function processEnvVariables(envPath, variablesPath, appName, secretsPath)
|
|
|
38
195
|
if (!variables?.build?.envOutputPath || variables.build.envOutputPath === null) {
|
|
39
196
|
return;
|
|
40
197
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (path.isAbsolute(rawOutputPath)) {
|
|
45
|
-
outputPath = rawOutputPath;
|
|
46
|
-
} else {
|
|
47
|
-
const variablesDir = path.dirname(variablesPath);
|
|
48
|
-
outputPath = path.resolve(variablesDir, rawOutputPath);
|
|
49
|
-
}
|
|
50
|
-
if (!outputPath.endsWith('.env')) {
|
|
51
|
-
if (fs.existsSync(outputPath) && fs.statSync(outputPath).isDirectory()) {
|
|
52
|
-
outputPath = path.join(outputPath, '.env');
|
|
53
|
-
} else {
|
|
54
|
-
outputPath = path.join(outputPath, '.env');
|
|
55
|
-
}
|
|
56
|
-
}
|
|
198
|
+
|
|
199
|
+
// Resolve output path
|
|
200
|
+
const outputPath = resolveEnvOutputPath(variables.build.envOutputPath, variablesPath);
|
|
57
201
|
const outputDir = path.dirname(outputPath);
|
|
58
202
|
if (!fs.existsSync(outputDir)) {
|
|
59
203
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
60
204
|
}
|
|
61
205
|
|
|
62
206
|
// Regenerate .env file with env=local instead of copying docker-generated file
|
|
63
|
-
// This ensures all variables use localhost instead of docker service names
|
|
64
207
|
if (appName) {
|
|
65
208
|
const { generateEnvContent } = require('../secrets');
|
|
66
|
-
// Generate local .env content (without writing to builder/.env to avoid overwriting docker version)
|
|
67
209
|
const localEnvContent = await generateEnvContent(appName, secretsPath, 'local', false);
|
|
68
|
-
// Write to output path
|
|
69
210
|
fs.writeFileSync(outputPath, localEnvContent, { mode: 0o600 });
|
|
70
211
|
logger.log(chalk.green(`✓ Generated local .env at: ${variables.build.envOutputPath}`));
|
|
71
212
|
} else {
|
|
72
213
|
// Fallback: if appName not provided, use old patching approach
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const devIdRaw = process.env.AIFABRIX_DEVELOPERID;
|
|
77
|
-
// Best effort: parse from env first, otherwise rely on config (may throw if async, so guarded below)
|
|
78
|
-
let devIdNum = Number.isFinite(parseInt(devIdRaw, 10)) ? parseInt(devIdRaw, 10) : null;
|
|
79
|
-
try {
|
|
80
|
-
if (devIdNum === null) {
|
|
81
|
-
// Try to read developer-id from config file synchronously if present
|
|
82
|
-
const configPath = config && config.CONFIG_FILE ? config.CONFIG_FILE : null;
|
|
83
|
-
if (configPath && fs.existsSync(configPath)) {
|
|
84
|
-
try {
|
|
85
|
-
const cfgContent = fs.readFileSync(configPath, 'utf8');
|
|
86
|
-
const cfg = yaml.load(cfgContent) || {};
|
|
87
|
-
const raw = cfg['developer-id'];
|
|
88
|
-
if (typeof raw === 'number') {
|
|
89
|
-
devIdNum = raw;
|
|
90
|
-
} else if (typeof raw === 'string' && /^[0-9]+$/.test(raw)) {
|
|
91
|
-
devIdNum = parseInt(raw, 10);
|
|
92
|
-
}
|
|
93
|
-
} catch {
|
|
94
|
-
// ignore, will fallback to 0
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (devIdNum === null || Number.isNaN(devIdNum)) {
|
|
98
|
-
devIdNum = 0;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
} catch {
|
|
102
|
-
devIdNum = 0;
|
|
103
|
-
}
|
|
104
|
-
const appPort = devIdNum === 0 ? baseAppPort : (baseAppPort + (devIdNum * 100));
|
|
105
|
-
const infraPorts = devConfig.getDevPorts(devIdNum);
|
|
106
|
-
|
|
107
|
-
// Update PORT (replace or append)
|
|
108
|
-
if (/^PORT\s*=.*$/m.test(envContent)) {
|
|
109
|
-
envContent = envContent.replace(/^PORT\s*=\s*.*$/m, `PORT=${appPort}`);
|
|
110
|
-
} else {
|
|
111
|
-
envContent = `${envContent}\nPORT=${appPort}\n`;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Update localhost URLs that point to the base app port to the dev-specific app port
|
|
115
|
-
const localhostUrlPattern = /(https?:\/\/localhost:)(\d+)(\b[^ \n]*)?/g;
|
|
116
|
-
envContent = envContent.replace(localhostUrlPattern, (match, prefix, portNum, rest = '') => {
|
|
117
|
-
const num = parseInt(portNum, 10);
|
|
118
|
-
if (num === baseAppPort) {
|
|
119
|
-
return `${prefix}${appPort}${rest || ''}`;
|
|
120
|
-
}
|
|
121
|
-
return match;
|
|
122
|
-
});
|
|
123
|
-
// Rewrite infra endpoints using env-config mapping for local context
|
|
124
|
-
envContent = await rewriteInfraEndpoints(envContent, 'local', infraPorts);
|
|
125
|
-
// Interpolate ${VAR} references created by rewriteInfraEndpoints
|
|
126
|
-
// Extract actual values from updated content to use for interpolation
|
|
127
|
-
const envVars = await buildEnvVarMap('local', null, devIdNum);
|
|
128
|
-
// Extract REDIS_HOST, REDIS_PORT, DB_HOST, DB_PORT from updated content if present
|
|
129
|
-
// Only extract if the value doesn't contain ${VAR} references (to avoid circular interpolation)
|
|
130
|
-
const redisHostMatch = envContent.match(/^REDIS_HOST\s*=\s*([^\r\n$]+)/m);
|
|
131
|
-
const redisPortMatch = envContent.match(/^REDIS_PORT\s*=\s*([^\r\n$]+)/m);
|
|
132
|
-
const dbHostMatch = envContent.match(/^DB_HOST\s*=\s*([^\r\n$]+)/m);
|
|
133
|
-
const dbPortMatch = envContent.match(/^DB_PORT\s*=\s*([^\r\n$]+)/m);
|
|
134
|
-
if (redisHostMatch && redisHostMatch[1] && !redisHostMatch[1].includes('${')) {
|
|
135
|
-
envVars.REDIS_HOST = redisHostMatch[1].trim();
|
|
136
|
-
}
|
|
137
|
-
if (redisPortMatch && redisPortMatch[1] && !redisPortMatch[1].includes('${')) {
|
|
138
|
-
envVars.REDIS_PORT = redisPortMatch[1].trim();
|
|
139
|
-
}
|
|
140
|
-
if (dbHostMatch && dbHostMatch[1] && !dbHostMatch[1].includes('${')) {
|
|
141
|
-
envVars.DB_HOST = dbHostMatch[1].trim();
|
|
142
|
-
}
|
|
143
|
-
if (dbPortMatch && dbPortMatch[1] && !dbPortMatch[1].includes('${')) {
|
|
144
|
-
envVars.DB_PORT = dbPortMatch[1].trim();
|
|
145
|
-
}
|
|
146
|
-
envContent = interpolateEnvVars(envContent, envVars);
|
|
147
|
-
fs.writeFileSync(outputPath, envContent, { mode: 0o600 });
|
|
214
|
+
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
215
|
+
const patchedContent = await patchEnvContentForLocal(envContent, variables);
|
|
216
|
+
fs.writeFileSync(outputPath, patchedContent, { mode: 0o600 });
|
|
148
217
|
logger.log(chalk.green(`✓ Copied .env to: ${variables.build.envOutputPath}`));
|
|
149
218
|
}
|
|
150
219
|
}
|
package/lib/utils/env-map.js
CHANGED
|
@@ -15,70 +15,147 @@ const { loadEnvConfig } = require('./env-config-loader');
|
|
|
15
15
|
const config = require('../config');
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
* - Supports values like "host:port" by splitting into *_HOST (host) and *_PORT (port)
|
|
20
|
-
* - Merges overrides from ~/.aifabrix/config.yaml under environments.{env}
|
|
21
|
-
* - Applies aifabrix-localhost override for local context if configured
|
|
22
|
-
* - Applies developer-id adjustment to port variables for local context
|
|
18
|
+
* Load base environment variables from env-config.yaml
|
|
23
19
|
* @async
|
|
24
|
-
* @function
|
|
20
|
+
* @function loadBaseVars
|
|
25
21
|
* @param {'docker'|'local'} context - Environment context
|
|
26
|
-
* @
|
|
27
|
-
* @param {number|null} [developerId] - Optional developer ID for port adjustment. If not provided, will be fetched from config for local context.
|
|
28
|
-
* @returns {Promise<Object>} Map of variables for interpolation
|
|
22
|
+
* @returns {Promise<Object>} Base environment variables
|
|
29
23
|
*/
|
|
30
|
-
async function
|
|
31
|
-
// Load env-config (base + user override if configured)
|
|
32
|
-
let baseVars = {};
|
|
24
|
+
async function loadBaseVars(context) {
|
|
33
25
|
try {
|
|
34
26
|
const envCfg = await loadEnvConfig();
|
|
35
27
|
const envs = envCfg && envCfg.environments ? envCfg.environments : {};
|
|
36
|
-
|
|
28
|
+
return { ...(envs[context] || {}) };
|
|
37
29
|
} catch {
|
|
38
|
-
|
|
30
|
+
return {};
|
|
39
31
|
}
|
|
32
|
+
}
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Load override variables from ~/.aifabrix/config.yaml
|
|
36
|
+
* @function loadOverrideVars
|
|
37
|
+
* @param {'docker'|'local'} context - Environment context
|
|
38
|
+
* @param {Object} os - OS module instance
|
|
39
|
+
* @returns {Object} Override environment variables
|
|
40
|
+
*/
|
|
41
|
+
function loadOverrideVars(context, os) {
|
|
46
42
|
try {
|
|
47
43
|
const cfgPath = path.join(os.homedir(), '.aifabrix', 'config.yaml');
|
|
48
44
|
if (fs.existsSync(cfgPath)) {
|
|
49
45
|
const cfgContent = fs.readFileSync(cfgPath, 'utf8');
|
|
50
46
|
const cfg = yaml.load(cfgContent) || {};
|
|
51
47
|
if (cfg && cfg.environments && cfg.environments[context]) {
|
|
52
|
-
|
|
48
|
+
return { ...cfg.environments[context] };
|
|
53
49
|
}
|
|
54
50
|
}
|
|
55
51
|
} catch {
|
|
56
52
|
// ignore overrides on error
|
|
57
53
|
}
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Get localhost override value from config
|
|
59
|
+
* @function getLocalhostOverride
|
|
60
|
+
* @param {Object} os - OS module instance
|
|
61
|
+
* @returns {string|null} Localhost override value or null
|
|
62
|
+
*/
|
|
63
|
+
function getLocalhostOverride(os) {
|
|
64
|
+
try {
|
|
65
|
+
const cfgPath = path.join(os.homedir(), '.aifabrix', 'config.yaml');
|
|
66
|
+
if (fs.existsSync(cfgPath)) {
|
|
67
|
+
const cfgContent = fs.readFileSync(cfgPath, 'utf8');
|
|
68
|
+
const cfg = yaml.load(cfgContent) || {};
|
|
69
|
+
if (typeof cfg['aifabrix-localhost'] === 'string' && cfg['aifabrix-localhost'].trim().length > 0) {
|
|
70
|
+
return cfg['aifabrix-localhost'].trim();
|
|
70
71
|
}
|
|
71
|
-
} catch {
|
|
72
|
-
// ignore
|
|
73
72
|
}
|
|
73
|
+
} catch {
|
|
74
|
+
// ignore
|
|
74
75
|
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
75
78
|
|
|
76
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Get host value with localhost override applied if needed
|
|
81
|
+
* @function getHostValue
|
|
82
|
+
* @param {string} host - Original host value
|
|
83
|
+
* @param {'docker'|'local'} context - Environment context
|
|
84
|
+
* @param {string|null} localhostOverride - Localhost override value
|
|
85
|
+
* @returns {string} Host value with override applied
|
|
86
|
+
*/
|
|
87
|
+
function getHostValue(host, context, localhostOverride) {
|
|
88
|
+
if (context === 'local' && host === 'localhost' && localhostOverride) {
|
|
89
|
+
return localhostOverride;
|
|
90
|
+
}
|
|
91
|
+
return host;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Handle host:port value for key ending with _HOST
|
|
96
|
+
* @function handleHostPortWithHostSuffix
|
|
97
|
+
* @param {Object} result - Result object to update
|
|
98
|
+
* @param {string} key - Environment variable key
|
|
99
|
+
* @param {string} host - Host value
|
|
100
|
+
* @param {string} port - Port value
|
|
101
|
+
* @param {Object} options - Normalization options
|
|
102
|
+
* @param {'docker'|'local'} options.context - Environment context
|
|
103
|
+
* @param {string|null} options.localhostOverride - Localhost override value
|
|
104
|
+
*/
|
|
105
|
+
function handleHostPortWithHostSuffix(result, key, host, port, options) {
|
|
106
|
+
const root = key.replace(/_HOST$/, '');
|
|
107
|
+
const hostValue = getHostValue(host, options.context, options.localhostOverride);
|
|
108
|
+
result[key] = hostValue;
|
|
109
|
+
result[`${root}_PORT`] = port;
|
|
110
|
+
}
|
|
77
111
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Handle host:port value for generic key
|
|
114
|
+
* @function handleHostPortGeneric
|
|
115
|
+
* @param {Object} result - Result object to update
|
|
116
|
+
* @param {string} key - Environment variable key
|
|
117
|
+
* @param {string} host - Host value
|
|
118
|
+
* @param {string} port - Port value
|
|
119
|
+
* @param {Object} options - Normalization options
|
|
120
|
+
* @param {'docker'|'local'} options.context - Environment context
|
|
121
|
+
* @param {string|null} options.localhostOverride - Localhost override value
|
|
122
|
+
*/
|
|
123
|
+
function handleHostPortGeneric(result, key, host, port, options) {
|
|
124
|
+
const hostValue = getHostValue(host, options.context, options.localhostOverride);
|
|
125
|
+
result[`${key}_HOST`] = hostValue;
|
|
126
|
+
result[`${key}_PORT`] = port;
|
|
127
|
+
result[key] = hostValue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Handle plain value (non-host:port)
|
|
132
|
+
* @function handlePlainValue
|
|
133
|
+
* @param {Object} result - Result object to update
|
|
134
|
+
* @param {string} key - Environment variable key
|
|
135
|
+
* @param {string} rawVal - Raw value
|
|
136
|
+
* @param {Object} options - Normalization options
|
|
137
|
+
* @param {'docker'|'local'} options.context - Environment context
|
|
138
|
+
* @param {string|null} options.localhostOverride - Localhost override value
|
|
139
|
+
*/
|
|
140
|
+
function handlePlainValue(result, key, rawVal, options) {
|
|
141
|
+
let val = rawVal;
|
|
142
|
+
if (options.context === 'local' && /_HOST$/.test(key) && rawVal === 'localhost' && options.localhostOverride) {
|
|
143
|
+
val = options.localhostOverride;
|
|
144
|
+
}
|
|
145
|
+
result[key] = val;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Normalize environment variable map by splitting host:port values
|
|
150
|
+
* @function normalizeEnvVars
|
|
151
|
+
* @param {Object} merged - Merged environment variables
|
|
152
|
+
* @param {'docker'|'local'} context - Environment context
|
|
153
|
+
* @param {string|null} localhostOverride - Localhost override value
|
|
154
|
+
* @returns {Object} Normalized environment variables
|
|
155
|
+
*/
|
|
156
|
+
function normalizeEnvVars(merged, context, localhostOverride) {
|
|
81
157
|
const result = {};
|
|
158
|
+
const options = { context, localhostOverride };
|
|
82
159
|
for (const [key, rawVal] of Object.entries(merged)) {
|
|
83
160
|
if (typeof rawVal !== 'string') {
|
|
84
161
|
result[key] = rawVal;
|
|
@@ -89,70 +166,133 @@ async function buildEnvVarMap(context, osModule = null, developerId = null) {
|
|
|
89
166
|
const host = hostPortMatch[1];
|
|
90
167
|
const port = hostPortMatch[2];
|
|
91
168
|
if (/_HOST$/.test(key)) {
|
|
92
|
-
|
|
93
|
-
const root = key.replace(/_HOST$/, '');
|
|
94
|
-
const hostValue = context === 'local' && host === 'localhost' && localhostOverride ? localhostOverride : host;
|
|
95
|
-
result[key] = hostValue;
|
|
96
|
-
result[`${root}_PORT`] = port;
|
|
169
|
+
handleHostPortWithHostSuffix(result, key, host, port, options);
|
|
97
170
|
} else {
|
|
98
|
-
|
|
99
|
-
const hostValue = context === 'local' && host === 'localhost' && localhostOverride ? localhostOverride : host;
|
|
100
|
-
result[`${key}_HOST`] = hostValue;
|
|
101
|
-
result[`${key}_PORT`] = port;
|
|
102
|
-
result[key] = hostValue;
|
|
171
|
+
handleHostPortGeneric(result, key, host, port, options);
|
|
103
172
|
}
|
|
104
173
|
} else {
|
|
105
|
-
|
|
106
|
-
let val = rawVal;
|
|
107
|
-
if (context === 'local' && /_HOST$/.test(key) && rawVal === 'localhost' && localhostOverride) {
|
|
108
|
-
val = localhostOverride;
|
|
109
|
-
}
|
|
110
|
-
result[key] = val;
|
|
174
|
+
handlePlainValue(result, key, rawVal, options);
|
|
111
175
|
}
|
|
112
176
|
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
113
179
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Get developer ID number from parameter or config
|
|
182
|
+
* @async
|
|
183
|
+
* @function getDeveloperIdNumber
|
|
184
|
+
* @param {number|null} developerId - Optional developer ID parameter
|
|
185
|
+
* @returns {Promise<number>} Developer ID number (0 if not available)
|
|
186
|
+
*/
|
|
187
|
+
async function getDeveloperIdNumber(developerId) {
|
|
188
|
+
if (developerId !== null && developerId !== undefined) {
|
|
189
|
+
const parsed = typeof developerId === 'number' ? developerId : parseInt(developerId, 10);
|
|
190
|
+
if (!Number.isNaN(parsed)) {
|
|
191
|
+
return parsed;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const devId = await config.getDeveloperId();
|
|
196
|
+
if (devId !== null && devId !== undefined) {
|
|
197
|
+
const parsed = parseInt(devId, 10);
|
|
119
198
|
if (!Number.isNaN(parsed)) {
|
|
120
|
-
|
|
199
|
+
return parsed;
|
|
121
200
|
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
// ignore, will use 0
|
|
204
|
+
}
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Apply developer-id adjustment to port variables for local context
|
|
210
|
+
* @function applyLocalPortAdjustment
|
|
211
|
+
* @param {Object} result - Environment variable map
|
|
212
|
+
* @param {number} devIdNum - Developer ID number
|
|
213
|
+
*/
|
|
214
|
+
function applyLocalPortAdjustment(result, devIdNum) {
|
|
215
|
+
if (devIdNum === 0) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
for (const [key, value] of Object.entries(result)) {
|
|
219
|
+
if (/_PORT$/.test(key)) {
|
|
220
|
+
let portVal;
|
|
221
|
+
if (typeof value === 'string') {
|
|
222
|
+
portVal = parseInt(value, 10);
|
|
223
|
+
} else if (typeof value === 'number') {
|
|
224
|
+
portVal = value;
|
|
225
|
+
} else {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (!Number.isNaN(portVal)) {
|
|
229
|
+
result[key] = String(portVal + (devIdNum * 100));
|
|
134
230
|
}
|
|
135
231
|
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
136
234
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Calculate public ports for docker context
|
|
237
|
+
* @function calculateDockerPublicPorts
|
|
238
|
+
* @param {Object} result - Environment variable map
|
|
239
|
+
* @param {number} devIdNum - Developer ID number
|
|
240
|
+
*/
|
|
241
|
+
function calculateDockerPublicPorts(result, devIdNum) {
|
|
242
|
+
if (devIdNum <= 0) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
for (const [key, value] of Object.entries(result)) {
|
|
246
|
+
// Match any variable ending with _PORT (e.g., MISO_PORT, KEYCLOAK_PORT, DB_PORT)
|
|
247
|
+
if (/_PORT$/.test(key) && !/_PUBLIC_PORT$/.test(key)) {
|
|
248
|
+
const publicPortKey = key.replace(/_PORT$/, '_PUBLIC_PORT');
|
|
249
|
+
// Skip if public port already exists (allow manual override)
|
|
250
|
+
if (result[publicPortKey] === undefined) {
|
|
251
|
+
let portVal;
|
|
252
|
+
if (typeof value === 'string') {
|
|
253
|
+
portVal = parseInt(value, 10);
|
|
254
|
+
} else if (typeof value === 'number') {
|
|
255
|
+
portVal = value;
|
|
256
|
+
} else {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (!Number.isNaN(portVal)) {
|
|
260
|
+
result[publicPortKey] = String(portVal + (devIdNum * 100));
|
|
152
261
|
}
|
|
153
262
|
}
|
|
154
263
|
}
|
|
155
264
|
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Build environment variable map for interpolation based on env-config.yaml
|
|
269
|
+
* - Supports values like "host:port" by splitting into *_HOST (host) and *_PORT (port)
|
|
270
|
+
* - Merges overrides from ~/.aifabrix/config.yaml under environments.{env}
|
|
271
|
+
* - Applies aifabrix-localhost override for local context if configured
|
|
272
|
+
* - Applies developer-id adjustment to port variables for local context
|
|
273
|
+
* - Calculates *_PUBLIC_PORT for docker context (basePort + developer-id * 100)
|
|
274
|
+
* @async
|
|
275
|
+
* @function buildEnvVarMap
|
|
276
|
+
* @param {'docker'|'local'} context - Environment context
|
|
277
|
+
* @param {Object} [osModule] - Optional os module (for testing). If not provided, requires 'os'
|
|
278
|
+
* @param {number|null} [developerId] - Optional developer ID for port adjustment. If not provided, will be fetched from config for local context.
|
|
279
|
+
* @returns {Promise<Object>} Map of variables for interpolation
|
|
280
|
+
*/
|
|
281
|
+
async function buildEnvVarMap(context, osModule = null, developerId = null) {
|
|
282
|
+
const baseVars = await loadBaseVars(context);
|
|
283
|
+
const os = osModule || require('os');
|
|
284
|
+
const overrideVars = loadOverrideVars(context, os);
|
|
285
|
+
const localhostOverride = context === 'local' ? getLocalhostOverride(os) : null;
|
|
286
|
+
const merged = { ...baseVars, ...overrideVars };
|
|
287
|
+
const result = normalizeEnvVars(merged, context, localhostOverride);
|
|
288
|
+
|
|
289
|
+
if (context === 'local') {
|
|
290
|
+
const devIdNum = await getDeveloperIdNumber(developerId);
|
|
291
|
+
applyLocalPortAdjustment(result, devIdNum);
|
|
292
|
+
} else if (context === 'docker') {
|
|
293
|
+
const devIdNum = await getDeveloperIdNumber(developerId);
|
|
294
|
+
calculateDockerPublicPorts(result, devIdNum);
|
|
295
|
+
}
|
|
156
296
|
|
|
157
297
|
return result;
|
|
158
298
|
}
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const os = require('os');
|
|
15
14
|
const dockerUtils = require('./docker');
|
|
16
15
|
const { getActualSecretsPath } = require('./secrets-path');
|
|
17
16
|
|
|
@@ -93,7 +92,8 @@ async function checkSecrets() {
|
|
|
93
92
|
return { status: 'missing', paths: pathsChecked };
|
|
94
93
|
} catch (error) {
|
|
95
94
|
// Fallback to default path if there's an error
|
|
96
|
-
const
|
|
95
|
+
const pathsUtil = require('./paths');
|
|
96
|
+
const defaultPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.yaml');
|
|
97
97
|
return {
|
|
98
98
|
status: fs.existsSync(defaultPath) ? 'ok' : 'missing',
|
|
99
99
|
paths: [defaultPath]
|