@aifabrix/builder 2.4.0 → 2.5.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/README.md +3 -0
- package/lib/app-down.js +123 -0
- package/lib/app.js +4 -2
- package/lib/build.js +19 -13
- package/lib/cli.js +52 -9
- package/lib/commands/secure.js +5 -40
- package/lib/config.js +26 -4
- package/lib/env-reader.js +3 -2
- package/lib/generator.js +0 -9
- package/lib/infra.js +30 -3
- package/lib/schema/application-schema.json +0 -15
- package/lib/schema/env-config.yaml +8 -8
- package/lib/secrets.js +167 -253
- package/lib/templates.js +10 -18
- package/lib/utils/api-error-handler.js +182 -147
- package/lib/utils/api.js +144 -354
- package/lib/utils/build-copy.js +6 -13
- package/lib/utils/compose-generator.js +2 -1
- package/lib/utils/device-code.js +349 -0
- package/lib/utils/env-config-loader.js +102 -0
- package/lib/utils/env-copy.js +131 -0
- package/lib/utils/env-endpoints.js +209 -0
- package/lib/utils/env-map.js +116 -0
- package/lib/utils/env-ports.js +60 -0
- package/lib/utils/environment-checker.js +39 -6
- package/lib/utils/image-name.js +49 -0
- package/lib/utils/paths.js +22 -20
- package/lib/utils/secrets-generator.js +3 -3
- package/lib/utils/secrets-helpers.js +359 -0
- package/lib/utils/secrets-path.js +12 -36
- package/lib/utils/secrets-url.js +38 -0
- package/lib/utils/secrets-utils.js +1 -42
- package/lib/utils/variable-transformer.js +0 -9
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +4 -2
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/infra/compose.yaml +4 -0
- package/templates/infra/compose.yaml.hbs +9 -4
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment endpoint rewriting utilities
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Rewrites infra endpoints using env-config and developer offsets
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const yaml = require('js-yaml');
|
|
14
|
+
const config = require('../config');
|
|
15
|
+
const devConfig = require('../utils/dev-config');
|
|
16
|
+
const { loadEnvConfig } = require('./env-config-loader');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns the environment hosts mapping from env-config.yaml
|
|
20
|
+
* @async
|
|
21
|
+
* @param {'docker'|'local'} context
|
|
22
|
+
* @returns {Promise<Object>}
|
|
23
|
+
*/
|
|
24
|
+
async function getEnvHosts(context) {
|
|
25
|
+
const envCfg = await loadEnvConfig();
|
|
26
|
+
const envs = envCfg && envCfg.environments ? envCfg.environments : {};
|
|
27
|
+
return envs[context] || {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Rewrites infra endpoints (REDIS_URL/REDIS_HOST/REDIS_PORT, DB_HOST/DB_PORT, etc.) based on env-config and context
|
|
32
|
+
* Uses getEnvHosts() to get all service values dynamically, avoiding hardcoded values
|
|
33
|
+
* @async
|
|
34
|
+
* @function rewriteInfraEndpoints
|
|
35
|
+
* @param {string} envContent - .env file content
|
|
36
|
+
* @param {'docker'|'local'} context - Environment context
|
|
37
|
+
* @param {{redis:number, postgres:number}} [devPorts] - Ports object with developer-id adjusted ports (optional)
|
|
38
|
+
* @returns {Promise<string>} Updated content
|
|
39
|
+
*/
|
|
40
|
+
async function rewriteInfraEndpoints(envContent, context, devPorts) {
|
|
41
|
+
// Get all service values from config system (includes env-config.yaml + user env-config file)
|
|
42
|
+
let hosts = await getEnvHosts(context);
|
|
43
|
+
|
|
44
|
+
// Apply config.yaml → environments.{context} override (if exists)
|
|
45
|
+
try {
|
|
46
|
+
const os = require('os');
|
|
47
|
+
const cfgPath = path.join(os.homedir(), '.aifabrix', 'config.yaml');
|
|
48
|
+
if (fs.existsSync(cfgPath)) {
|
|
49
|
+
const cfgContent = fs.readFileSync(cfgPath, 'utf8');
|
|
50
|
+
const cfg = yaml.load(cfgContent) || {};
|
|
51
|
+
if (cfg && cfg.environments && cfg.environments[context]) {
|
|
52
|
+
hosts = { ...hosts, ...cfg.environments[context] };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// Ignore config.yaml read errors, continue with env-config values
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Helper to split host:port values
|
|
60
|
+
const splitHost = (value) => {
|
|
61
|
+
if (typeof value !== 'string') return { host: undefined, port: undefined };
|
|
62
|
+
const m = value.match(/^([^:]+):(\d+)$/);
|
|
63
|
+
if (m) return { host: m[1], port: parseInt(m[2], 10) };
|
|
64
|
+
return { host: value, port: undefined };
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Get aifabrix-localhost override for local context
|
|
68
|
+
let localhostOverride = null;
|
|
69
|
+
if (context === 'local') {
|
|
70
|
+
try {
|
|
71
|
+
const os = require('os');
|
|
72
|
+
const cfgPath = path.join(os.homedir(), '.aifabrix', 'config.yaml');
|
|
73
|
+
if (fs.existsSync(cfgPath)) {
|
|
74
|
+
const cfgContent = fs.readFileSync(cfgPath, 'utf8');
|
|
75
|
+
const cfg = yaml.load(cfgContent) || {};
|
|
76
|
+
if (typeof cfg['aifabrix-localhost'] === 'string' && cfg['aifabrix-localhost'].trim().length > 0) {
|
|
77
|
+
localhostOverride = cfg['aifabrix-localhost'].trim();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
// ignore override errors
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Helper to get port value from config or devPorts, with developer-id adjustment
|
|
86
|
+
const getServicePort = async(portKey, serviceName) => {
|
|
87
|
+
// If devPorts provided, use it (already has developer-id adjustment)
|
|
88
|
+
if (devPorts && typeof devPorts[serviceName] === 'number') {
|
|
89
|
+
return devPorts[serviceName];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Get base port from config
|
|
93
|
+
let basePort = null;
|
|
94
|
+
if (hosts[portKey] !== undefined && hosts[portKey] !== null) {
|
|
95
|
+
const portVal = typeof hosts[portKey] === 'number' ? hosts[portKey] : parseInt(hosts[portKey], 10);
|
|
96
|
+
if (!Number.isNaN(portVal)) {
|
|
97
|
+
basePort = portVal;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Last resort fallback to devConfig (only if not in config)
|
|
102
|
+
if (basePort === null || basePort === undefined) {
|
|
103
|
+
const basePorts = devConfig.getBasePorts();
|
|
104
|
+
basePort = basePorts[serviceName];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Apply developer-id adjustment
|
|
108
|
+
try {
|
|
109
|
+
const devId = await config.getDeveloperId();
|
|
110
|
+
let devIdNum = 0;
|
|
111
|
+
if (devId !== null && devId !== undefined) {
|
|
112
|
+
const parsed = parseInt(devId, 10);
|
|
113
|
+
if (!Number.isNaN(parsed)) {
|
|
114
|
+
devIdNum = parsed;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return devIdNum === 0 ? basePort : (basePort + (devIdNum * 100));
|
|
118
|
+
} catch {
|
|
119
|
+
return basePort;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Get Redis configuration
|
|
124
|
+
const redisParts = splitHost(hosts.REDIS_HOST);
|
|
125
|
+
let redisHost = redisParts.host || hosts.REDIS_HOST;
|
|
126
|
+
// Fallback to default if not in config
|
|
127
|
+
if (!redisHost) {
|
|
128
|
+
redisHost = context === 'docker' ? 'redis' : 'localhost';
|
|
129
|
+
}
|
|
130
|
+
if (context === 'local' && localhostOverride && redisHost === 'localhost') {
|
|
131
|
+
redisHost = localhostOverride;
|
|
132
|
+
}
|
|
133
|
+
const redisPort = await getServicePort('REDIS_PORT', 'redis');
|
|
134
|
+
|
|
135
|
+
// Get DB configuration
|
|
136
|
+
let dbHost = hosts.DB_HOST;
|
|
137
|
+
// Fallback to default if not in config
|
|
138
|
+
if (!dbHost) {
|
|
139
|
+
dbHost = context === 'docker' ? 'postgres' : 'localhost';
|
|
140
|
+
}
|
|
141
|
+
const finalDbHost = (context === 'local' && localhostOverride && dbHost === 'localhost') ? localhostOverride : dbHost;
|
|
142
|
+
const dbPort = await getServicePort('DB_PORT', 'postgres');
|
|
143
|
+
|
|
144
|
+
let updated = envContent;
|
|
145
|
+
|
|
146
|
+
// Update REDIS_URL if present
|
|
147
|
+
if (/^REDIS_URL\s*=.*$/m.test(updated)) {
|
|
148
|
+
const m = updated.match(/^REDIS_URL\s*=\s*redis:\/\/([^:\s]+):\d+/m);
|
|
149
|
+
const currentHost = m && m[1] ? m[1] : null;
|
|
150
|
+
const targetHost = redisHost || currentHost;
|
|
151
|
+
if (targetHost) {
|
|
152
|
+
updated = updated.replace(
|
|
153
|
+
/^REDIS_URL\s*=\s*.*$/m,
|
|
154
|
+
`REDIS_URL=redis://${targetHost}:${redisPort}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Update REDIS_HOST if present
|
|
160
|
+
if (/^REDIS_HOST\s*=.*$/m.test(updated)) {
|
|
161
|
+
const hostPortMatch = updated.match(/^REDIS_HOST\s*=\s*([a-zA-Z0-9_.-]+):\d+$/m);
|
|
162
|
+
const hasPortPattern = !!hostPortMatch;
|
|
163
|
+
if (hasPortPattern) {
|
|
164
|
+
updated = updated.replace(
|
|
165
|
+
/^REDIS_HOST\s*=\s*.*$/m,
|
|
166
|
+
`REDIS_HOST=${redisHost}:${redisPort}`
|
|
167
|
+
);
|
|
168
|
+
} else {
|
|
169
|
+
updated = updated.replace(/^REDIS_HOST\s*=\s*.*$/m, `REDIS_HOST=${redisHost}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Update REDIS_PORT if present
|
|
174
|
+
if (/^REDIS_PORT\s*=.*$/m.test(updated)) {
|
|
175
|
+
updated = updated.replace(
|
|
176
|
+
/^REDIS_PORT\s*=\s*.*$/m,
|
|
177
|
+
`REDIS_PORT=${redisPort}`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Update DB_HOST if present
|
|
182
|
+
if (/^DB_HOST\s*=.*$/m.test(updated)) {
|
|
183
|
+
updated = updated.replace(/^DB_HOST\s*=\s*.*$/m, `DB_HOST=${finalDbHost}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Update DB_PORT if present
|
|
187
|
+
if (/^DB_PORT\s*=.*$/m.test(updated)) {
|
|
188
|
+
updated = updated.replace(
|
|
189
|
+
/^DB_PORT\s*=\s*.*$/m,
|
|
190
|
+
`DB_PORT=${dbPort}`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Update DATABASE_PORT if present (some templates use DATABASE_PORT instead of DB_PORT)
|
|
195
|
+
if (/^DATABASE_PORT\s*=.*$/m.test(updated)) {
|
|
196
|
+
updated = updated.replace(
|
|
197
|
+
/^DATABASE_PORT\s*=\s*.*$/m,
|
|
198
|
+
`DATABASE_PORT=${dbPort}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return updated;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = {
|
|
206
|
+
rewriteInfraEndpoints,
|
|
207
|
+
getEnvHosts
|
|
208
|
+
};
|
|
209
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variable mapping utilities
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Build interpolation map from env-config.yaml and config overrides
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const yaml = require('js-yaml');
|
|
14
|
+
const { loadEnvConfig } = require('./env-config-loader');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build environment variable map for interpolation based on env-config.yaml
|
|
18
|
+
* - Supports values like "host:port" by splitting into *_HOST (host) and *_PORT (port)
|
|
19
|
+
* - Merges overrides from ~/.aifabrix/config.yaml under environments.{env}
|
|
20
|
+
* - Applies aifabrix-localhost override for local context if configured
|
|
21
|
+
* @async
|
|
22
|
+
* @function buildEnvVarMap
|
|
23
|
+
* @param {'docker'|'local'} context - Environment context
|
|
24
|
+
* @param {Object} [osModule] - Optional os module (for testing). If not provided, requires 'os'
|
|
25
|
+
* @returns {Promise<Object>} Map of variables for interpolation
|
|
26
|
+
*/
|
|
27
|
+
async function buildEnvVarMap(context, osModule = null) {
|
|
28
|
+
// Load env-config (base + user override if configured)
|
|
29
|
+
let baseVars = {};
|
|
30
|
+
try {
|
|
31
|
+
const envCfg = await loadEnvConfig();
|
|
32
|
+
const envs = envCfg && envCfg.environments ? envCfg.environments : {};
|
|
33
|
+
baseVars = { ...(envs[context] || {}) };
|
|
34
|
+
} catch {
|
|
35
|
+
baseVars = {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get os module - use provided one or require it
|
|
39
|
+
const os = osModule || require('os');
|
|
40
|
+
|
|
41
|
+
// Merge overrides from ~/.aifabrix/config.yaml
|
|
42
|
+
let overrideVars = {};
|
|
43
|
+
try {
|
|
44
|
+
const cfgPath = path.join(os.homedir(), '.aifabrix', 'config.yaml');
|
|
45
|
+
if (fs.existsSync(cfgPath)) {
|
|
46
|
+
const cfgContent = fs.readFileSync(cfgPath, 'utf8');
|
|
47
|
+
const cfg = yaml.load(cfgContent) || {};
|
|
48
|
+
if (cfg && cfg.environments && cfg.environments[context]) {
|
|
49
|
+
overrideVars = { ...cfg.environments[context] };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// ignore overrides on error
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Apply aifabrix-localhost override for local hostnames
|
|
57
|
+
let localhostOverride = null;
|
|
58
|
+
if (context === 'local') {
|
|
59
|
+
try {
|
|
60
|
+
const cfgPath = path.join(os.homedir(), '.aifabrix', 'config.yaml');
|
|
61
|
+
if (fs.existsSync(cfgPath)) {
|
|
62
|
+
const cfgContent = fs.readFileSync(cfgPath, 'utf8');
|
|
63
|
+
const cfg = yaml.load(cfgContent) || {};
|
|
64
|
+
if (typeof cfg['aifabrix-localhost'] === 'string' && cfg['aifabrix-localhost'].trim().length > 0) {
|
|
65
|
+
localhostOverride = cfg['aifabrix-localhost'].trim();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// ignore
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const merged = { ...baseVars, ...overrideVars };
|
|
74
|
+
|
|
75
|
+
// Normalize map: if VAR value is "host:port" and VAR ends with "_HOST",
|
|
76
|
+
// expose VAR as host only and also provide "<ROOT>_PORT"
|
|
77
|
+
// If VAR value is "host:port" but VAR doesn't end with "_HOST", still split to VAR_HOST/VAR_PORT
|
|
78
|
+
const result = {};
|
|
79
|
+
for (const [key, rawVal] of Object.entries(merged)) {
|
|
80
|
+
if (typeof rawVal !== 'string') {
|
|
81
|
+
result[key] = rawVal;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const hostPortMatch = rawVal.match(/^([A-Za-z0-9._-]+):(\d+)$/);
|
|
85
|
+
if (hostPortMatch) {
|
|
86
|
+
const host = hostPortMatch[1];
|
|
87
|
+
const port = hostPortMatch[2];
|
|
88
|
+
if (/_HOST$/.test(key)) {
|
|
89
|
+
// Example: DB_HOST: "postgres:5432" -> DB_HOST="postgres", DB_PORT="5432"
|
|
90
|
+
const root = key.replace(/_HOST$/, '');
|
|
91
|
+
const hostValue = context === 'local' && host === 'localhost' && localhostOverride ? localhostOverride : host;
|
|
92
|
+
result[key] = hostValue;
|
|
93
|
+
result[`${root}_PORT`] = port;
|
|
94
|
+
} else {
|
|
95
|
+
// Generic key with host:port -> expose KEY_HOST and KEY_PORT, and keep KEY as host
|
|
96
|
+
const hostValue = context === 'local' && host === 'localhost' && localhostOverride ? localhostOverride : host;
|
|
97
|
+
result[`${key}_HOST`] = hostValue;
|
|
98
|
+
result[`${key}_PORT`] = port;
|
|
99
|
+
result[key] = hostValue;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
// Plain value
|
|
103
|
+
let val = rawVal;
|
|
104
|
+
if (context === 'local' && /_HOST$/.test(key) && rawVal === 'localhost' && localhostOverride) {
|
|
105
|
+
val = localhostOverride;
|
|
106
|
+
}
|
|
107
|
+
result[key] = val;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
buildEnvVarMap
|
|
115
|
+
};
|
|
116
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment port utilities
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Update container PORT based on variables.yaml and developer-id offset
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const yaml = require('js-yaml');
|
|
13
|
+
const config = require('../config');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Update PORT in the container's .env file to use variables.port (+offset)
|
|
17
|
+
* @function updateContainerPortInEnvFile
|
|
18
|
+
* @param {string} envPath - Path to .env
|
|
19
|
+
* @param {string} variablesPath - Path to variables.yaml
|
|
20
|
+
*/
|
|
21
|
+
function updateContainerPortInEnvFile(envPath, variablesPath) {
|
|
22
|
+
if (!fs.existsSync(variablesPath)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const variablesContent = fs.readFileSync(variablesPath, 'utf8');
|
|
26
|
+
const variables = yaml.load(variablesContent);
|
|
27
|
+
// Base port from variables
|
|
28
|
+
const basePort = variables?.port || 3000;
|
|
29
|
+
// Determine developer-id (prefer env var for sync context, fallback to config file)
|
|
30
|
+
let devIdNum = 0;
|
|
31
|
+
const devIdRaw = process.env.AIFABRIX_DEVELOPERID;
|
|
32
|
+
if (devIdRaw && /^[0-9]+$/.test(devIdRaw)) {
|
|
33
|
+
devIdNum = parseInt(devIdRaw, 10);
|
|
34
|
+
} else {
|
|
35
|
+
try {
|
|
36
|
+
const cfgPath = config && config.CONFIG_FILE ? config.CONFIG_FILE : null;
|
|
37
|
+
if (cfgPath && fs.existsSync(cfgPath)) {
|
|
38
|
+
const cfgContent = fs.readFileSync(cfgPath, 'utf8');
|
|
39
|
+
const cfg = yaml.load(cfgContent) || {};
|
|
40
|
+
const raw = cfg['developer-id'];
|
|
41
|
+
if (typeof raw === 'number') {
|
|
42
|
+
devIdNum = raw;
|
|
43
|
+
} else if (typeof raw === 'string' && /^[0-9]+$/.test(raw)) {
|
|
44
|
+
devIdNum = parseInt(raw, 10);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
// ignore, will use 0
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const port = devIdNum > 0 ? (basePort + devIdNum * 100) : basePort;
|
|
52
|
+
let envContent = fs.readFileSync(envPath, 'utf8');
|
|
53
|
+
envContent = envContent.replace(/^PORT\s*=\s*.*$/m, `PORT=${port}`);
|
|
54
|
+
fs.writeFileSync(envPath, envContent, { mode: 0o600 });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
updateContainerPortInEnvFile
|
|
59
|
+
};
|
|
60
|
+
|
|
@@ -13,6 +13,7 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const os = require('os');
|
|
15
15
|
const dockerUtils = require('./docker');
|
|
16
|
+
const { getActualSecretsPath } = require('./secrets-path');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Checks if Docker is installed and available
|
|
@@ -62,13 +63,42 @@ async function checkPorts() {
|
|
|
62
63
|
|
|
63
64
|
/**
|
|
64
65
|
* Checks if secrets file exists
|
|
66
|
+
* Checks both the default path and the path configured in config.yaml (aifabrix-secrets)
|
|
65
67
|
*
|
|
68
|
+
* @async
|
|
66
69
|
* @function checkSecrets
|
|
67
|
-
* @returns {
|
|
70
|
+
* @returns {Promise<Object>} Object with status ('ok' or 'missing') and paths checked
|
|
71
|
+
* @returns {string} returns.status - 'ok' if secrets file exists, 'missing' otherwise
|
|
72
|
+
* @returns {string[]} returns.paths - Array of paths that were checked
|
|
68
73
|
*/
|
|
69
|
-
function checkSecrets() {
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
async function checkSecrets() {
|
|
75
|
+
try {
|
|
76
|
+
const { userPath, buildPath } = await getActualSecretsPath();
|
|
77
|
+
const pathsChecked = [];
|
|
78
|
+
|
|
79
|
+
// Check user path (default: ~/.aifabrix/secrets.local.yaml)
|
|
80
|
+
if (fs.existsSync(userPath)) {
|
|
81
|
+
return { status: 'ok', paths: [userPath] };
|
|
82
|
+
}
|
|
83
|
+
pathsChecked.push(userPath);
|
|
84
|
+
|
|
85
|
+
// Check build path (from config.yaml aifabrix-secrets)
|
|
86
|
+
if (buildPath && fs.existsSync(buildPath)) {
|
|
87
|
+
return { status: 'ok', paths: [buildPath] };
|
|
88
|
+
}
|
|
89
|
+
if (buildPath) {
|
|
90
|
+
pathsChecked.push(buildPath);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { status: 'missing', paths: pathsChecked };
|
|
94
|
+
} catch (error) {
|
|
95
|
+
// Fallback to default path if there's an error
|
|
96
|
+
const defaultPath = path.join(os.homedir(), '.aifabrix', 'secrets.yaml');
|
|
97
|
+
return {
|
|
98
|
+
status: fs.existsSync(defaultPath) ? 'ok' : 'missing',
|
|
99
|
+
paths: [defaultPath]
|
|
100
|
+
};
|
|
101
|
+
}
|
|
72
102
|
}
|
|
73
103
|
|
|
74
104
|
/**
|
|
@@ -105,9 +135,12 @@ async function checkEnvironment() {
|
|
|
105
135
|
}
|
|
106
136
|
|
|
107
137
|
// Check secrets
|
|
108
|
-
|
|
138
|
+
const secretsCheck = await checkSecrets();
|
|
139
|
+
result.secrets = secretsCheck.status;
|
|
109
140
|
if (result.secrets === 'missing') {
|
|
110
|
-
|
|
141
|
+
// Show the actual paths that were checked
|
|
142
|
+
const pathsList = secretsCheck.paths.map(p => p).join(' or ');
|
|
143
|
+
result.recommendations.push(`Create secrets file: ${pathsList}`);
|
|
111
144
|
}
|
|
112
145
|
|
|
113
146
|
return result;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image name utilities
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Helper functions for computing developer-scoped Docker image names
|
|
5
|
+
* based on the current developer identifier. Ensures consistent local build naming.
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Builds a developer-scoped image name for local Docker builds.
|
|
14
|
+
* Format: "<base>-dev<developerId>".
|
|
15
|
+
* If developerId is missing, non-numeric, or 0 → "<base>-extra".
|
|
16
|
+
*
|
|
17
|
+
* @function buildDevImageName
|
|
18
|
+
* @param {string} baseName - Base image name (no registry), e.g., "myapp"
|
|
19
|
+
* @param {(string|number|null|undefined)} developerId - Developer identifier
|
|
20
|
+
* @returns {string} Developer-scoped image name
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* buildDevImageName('myapp', 123) // "myapp-dev123"
|
|
24
|
+
* buildDevImageName('myapp', '0') // "myapp-extra"
|
|
25
|
+
* buildDevImageName('myapp') // "myapp-extra"
|
|
26
|
+
*/
|
|
27
|
+
function buildDevImageName(baseName, developerId) {
|
|
28
|
+
const id =
|
|
29
|
+
typeof developerId === 'number'
|
|
30
|
+
? developerId
|
|
31
|
+
: typeof developerId === 'string'
|
|
32
|
+
? parseInt(developerId, 10)
|
|
33
|
+
: NaN;
|
|
34
|
+
|
|
35
|
+
if (!baseName || typeof baseName !== 'string') {
|
|
36
|
+
throw new Error('Base image name is required and must be a string');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!Number.isFinite(id) || id === 0) {
|
|
40
|
+
return `${baseName}-extra`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return `${baseName}-dev${id}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
buildDevImageName
|
|
48
|
+
};
|
|
49
|
+
|
package/lib/utils/paths.js
CHANGED
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
14
|
const path = require('path');
|
|
15
|
-
const os = require('os');
|
|
16
15
|
const fs = require('fs');
|
|
17
16
|
const yaml = require('js-yaml');
|
|
18
17
|
|
|
19
18
|
function safeHomedir() {
|
|
20
19
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const osMod = require('os');
|
|
21
|
+
if (typeof osMod.homedir === 'function') {
|
|
22
|
+
const hd = osMod.homedir();
|
|
23
23
|
if (typeof hd === 'string' && hd.length > 0) {
|
|
24
24
|
return hd;
|
|
25
25
|
}
|
|
@@ -38,18 +38,21 @@ function safeHomedir() {
|
|
|
38
38
|
* @returns {string} Absolute path to the AI Fabrix home directory
|
|
39
39
|
*/
|
|
40
40
|
function getAifabrixHome() {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
const isTestEnv = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
|
|
42
|
+
if (!isTestEnv) {
|
|
43
|
+
try {
|
|
44
|
+
const configPath = path.join(safeHomedir(), '.aifabrix', 'config.yaml');
|
|
45
|
+
if (fs.existsSync(configPath)) {
|
|
46
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
47
|
+
const config = yaml.load(content) || {};
|
|
48
|
+
const homeOverride = config && typeof config['aifabrix-home'] === 'string' ? config['aifabrix-home'].trim() : '';
|
|
49
|
+
if (homeOverride) {
|
|
50
|
+
return path.resolve(homeOverride);
|
|
51
|
+
}
|
|
49
52
|
}
|
|
53
|
+
} catch {
|
|
54
|
+
// Ignore errors and fall back to default
|
|
50
55
|
}
|
|
51
|
-
} catch {
|
|
52
|
-
// Ignore errors and fall back to default
|
|
53
56
|
}
|
|
54
57
|
return path.join(safeHomedir(), '.aifabrix');
|
|
55
58
|
}
|
|
@@ -73,20 +76,19 @@ function getApplicationsBaseDir(developerId) {
|
|
|
73
76
|
|
|
74
77
|
/**
|
|
75
78
|
* Returns the developer-specific application directory.
|
|
76
|
-
* Dev 0: points to applications/ (
|
|
77
|
-
* Dev > 0: <home>/applications-dev-{id}
|
|
79
|
+
* Dev 0: points to applications/ (root)
|
|
80
|
+
* Dev > 0: <home>/applications-dev-{id} (root)
|
|
78
81
|
*
|
|
79
82
|
* @param {string} appName - Application name
|
|
80
83
|
* @param {number|string} developerId - Developer ID
|
|
81
84
|
* @returns {string} Absolute path to developer-specific app directory
|
|
82
85
|
*/
|
|
83
86
|
function getDevDirectory(appName, developerId) {
|
|
84
|
-
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
85
87
|
const baseDir = getApplicationsBaseDir(developerId);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
return
|
|
88
|
+
// All files should be generated at the root of the applications folder
|
|
89
|
+
// Dev 0: <home>/applications
|
|
90
|
+
// Dev > 0: <home>/applications-dev-{id}
|
|
91
|
+
return baseDir;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
module.exports = {
|
|
@@ -64,7 +64,7 @@ function generateSecretValue(key) {
|
|
|
64
64
|
if (dbUrlMatch) {
|
|
65
65
|
const appName = dbUrlMatch[1];
|
|
66
66
|
const dbName = appName.replace(/-/g, '_');
|
|
67
|
-
return `postgresql://${dbName}_user:${dbName}_pass123@\${DB_HOST}
|
|
67
|
+
return `postgresql://${dbName}_user:${dbName}_pass123@\${DB_HOST}:\${DB_PORT}/${dbName}`;
|
|
68
68
|
}
|
|
69
69
|
return '';
|
|
70
70
|
}
|
|
@@ -188,11 +188,11 @@ postgres-passwordKeyVault: "admin123"
|
|
|
188
188
|
|
|
189
189
|
# Redis Secrets
|
|
190
190
|
redis-passwordKeyVault: ""
|
|
191
|
-
redis-urlKeyVault: "redis://\${REDIS_HOST}
|
|
191
|
+
redis-urlKeyVault: "redis://\${REDIS_HOST}:\${REDIS_PORT}"
|
|
192
192
|
|
|
193
193
|
# Keycloak Secrets
|
|
194
194
|
keycloak-admin-passwordKeyVault: "admin123"
|
|
195
|
-
keycloak-auth-server-urlKeyVault: "http://\${KEYCLOAK_HOST}
|
|
195
|
+
keycloak-auth-server-urlKeyVault: "http://\${KEYCLOAK_HOST}:\${KEYCLOAK_PORT}"
|
|
196
196
|
`;
|
|
197
197
|
|
|
198
198
|
fs.writeFileSync(resolvedPath, defaultSecrets, { mode: 0o600 });
|