@aifabrix/builder 2.3.4 → 2.3.6
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 +0 -6
- package/bin/aifabrix.js +0 -0
- package/lib/audit-logger.js +2 -2
- package/lib/build.js +2 -2
- package/lib/config.js +13 -7
- package/lib/infra.js +8 -8
- package/lib/secrets.js +1 -1
- package/lib/utils/build-copy.js +3 -11
- package/lib/utils/docker.js +1 -1
- package/lib/utils/infra-containers.js +24 -13
- package/lib/utils/paths.js +77 -0
- package/lib/utils/secrets-path.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
package/bin/aifabrix.js
CHANGED
|
File without changes
|
package/lib/audit-logger.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
const fs = require('fs').promises;
|
|
15
15
|
const path = require('path');
|
|
16
16
|
const os = require('os');
|
|
17
|
+
const paths = require('./utils/paths');
|
|
17
18
|
|
|
18
19
|
// Audit log file path (in user's home directory for compliance)
|
|
19
20
|
let auditLogPath = null;
|
|
@@ -28,8 +29,7 @@ async function getAuditLogPath() {
|
|
|
28
29
|
return auditLogPath;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
const
|
|
32
|
-
const aifabrixDir = path.join(homeDir, '.aifabrix');
|
|
32
|
+
const aifabrixDir = paths.getAifabrixHome();
|
|
33
33
|
|
|
34
34
|
try {
|
|
35
35
|
await fs.mkdir(aifabrixDir, { recursive: true });
|
package/lib/build.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
const fs = require('fs').promises;
|
|
14
14
|
const fsSync = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
-
const
|
|
16
|
+
const paths = require('./utils/paths');
|
|
17
17
|
const { exec } = require('child_process');
|
|
18
18
|
const { promisify } = require('util');
|
|
19
19
|
const chalk = require('chalk');
|
|
@@ -198,7 +198,7 @@ async function generateDockerfile(appNameOrPath, language, config, buildConfig =
|
|
|
198
198
|
if (devDir) {
|
|
199
199
|
targetDir = devDir;
|
|
200
200
|
} else {
|
|
201
|
-
targetDir = path.join(
|
|
201
|
+
targetDir = path.join(paths.getAifabrixHome(), appName);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
if (!fsSync.existsSync(targetDir)) {
|
package/lib/config.js
CHANGED
|
@@ -11,12 +11,18 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs').promises;
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const os = require('os');
|
|
15
14
|
const yaml = require('js-yaml');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const paths = require('./utils/paths');
|
|
16
17
|
|
|
18
|
+
// Default (for tests and constants): always reflects OS home
|
|
17
19
|
const CONFIG_DIR = path.join(os.homedir(), '.aifabrix');
|
|
18
20
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.yaml');
|
|
19
21
|
|
|
22
|
+
// Runtime (respects AIFABRIX_HOME override)
|
|
23
|
+
const RUNTIME_CONFIG_DIR = paths.getAifabrixHome();
|
|
24
|
+
const RUNTIME_CONFIG_FILE = path.join(RUNTIME_CONFIG_DIR, 'config.yaml');
|
|
25
|
+
|
|
20
26
|
// Cache for developer ID - loaded when getConfig() is first called
|
|
21
27
|
let cachedDeveloperId = null;
|
|
22
28
|
|
|
@@ -27,7 +33,7 @@ let cachedDeveloperId = null;
|
|
|
27
33
|
*/
|
|
28
34
|
async function getConfig() {
|
|
29
35
|
try {
|
|
30
|
-
const configContent = await fs.readFile(
|
|
36
|
+
const configContent = await fs.readFile(RUNTIME_CONFIG_FILE, 'utf8');
|
|
31
37
|
let config = yaml.load(configContent);
|
|
32
38
|
|
|
33
39
|
// Handle empty file or null/undefined result from yaml.load
|
|
@@ -91,19 +97,19 @@ async function getConfig() {
|
|
|
91
97
|
async function saveConfig(data) {
|
|
92
98
|
try {
|
|
93
99
|
// Create directory if it doesn't exist
|
|
94
|
-
await fs.mkdir(
|
|
100
|
+
await fs.mkdir(RUNTIME_CONFIG_DIR, { recursive: true });
|
|
95
101
|
|
|
96
102
|
// Set secure permissions
|
|
97
103
|
// Force quotes to ensure numeric-like strings (e.g., "01") remain strings in YAML
|
|
98
104
|
const configContent = yaml.dump(data, { forceQuotes: true });
|
|
99
105
|
// Write file first
|
|
100
|
-
await fs.writeFile(
|
|
106
|
+
await fs.writeFile(RUNTIME_CONFIG_FILE, configContent, {
|
|
101
107
|
mode: 0o600,
|
|
102
108
|
flag: 'w'
|
|
103
109
|
});
|
|
104
110
|
// Open file descriptor and fsync to ensure write is flushed to disk
|
|
105
111
|
// This is critical on Windows where file writes may be cached
|
|
106
|
-
const fd = await fs.open(
|
|
112
|
+
const fd = await fs.open(RUNTIME_CONFIG_FILE, 'r+');
|
|
107
113
|
try {
|
|
108
114
|
await fd.sync();
|
|
109
115
|
} finally {
|
|
@@ -120,7 +126,7 @@ async function saveConfig(data) {
|
|
|
120
126
|
*/
|
|
121
127
|
async function clearConfig() {
|
|
122
128
|
try {
|
|
123
|
-
await fs.unlink(
|
|
129
|
+
await fs.unlink(RUNTIME_CONFIG_FILE);
|
|
124
130
|
} catch (error) {
|
|
125
131
|
if (error.code !== 'ENOENT') {
|
|
126
132
|
throw new Error(`Failed to clear config: ${error.message}`);
|
|
@@ -179,7 +185,7 @@ async function setDeveloperId(developerId) {
|
|
|
179
185
|
// Add a small delay to ensure file system has flushed the write
|
|
180
186
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
181
187
|
// Read file again with fresh file handle to avoid OS caching
|
|
182
|
-
const savedContent = await fs.readFile(
|
|
188
|
+
const savedContent = await fs.readFile(RUNTIME_CONFIG_FILE, 'utf8');
|
|
183
189
|
const savedConfig = yaml.load(savedContent);
|
|
184
190
|
// YAML may parse numbers as numbers, so convert to string for comparison
|
|
185
191
|
const savedDevIdString = String(savedConfig['developer-id']);
|
package/lib/infra.js
CHANGED
|
@@ -13,7 +13,6 @@ const { exec } = require('child_process');
|
|
|
13
13
|
const { promisify } = require('util');
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const fs = require('fs');
|
|
16
|
-
const os = require('os');
|
|
17
16
|
const handlebars = require('handlebars');
|
|
18
17
|
const secrets = require('./secrets');
|
|
19
18
|
const config = require('./config');
|
|
@@ -21,6 +20,7 @@ const devConfig = require('./utils/dev-config');
|
|
|
21
20
|
const logger = require('./utils/logger');
|
|
22
21
|
const containerUtils = require('./utils/infra-containers');
|
|
23
22
|
const dockerUtils = require('./utils/docker');
|
|
23
|
+
const paths = require('./utils/paths');
|
|
24
24
|
|
|
25
25
|
// Register Handlebars helper for equality check
|
|
26
26
|
handlebars.registerHelper('eq', (a, b) => a === b);
|
|
@@ -84,7 +84,7 @@ async function checkDockerAvailability() {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
async function ensureAdminSecrets() {
|
|
87
|
-
const adminSecretsPath = path.join(
|
|
87
|
+
const adminSecretsPath = path.join(paths.getAifabrixHome(), 'admin-secrets.env');
|
|
88
88
|
if (!fs.existsSync(adminSecretsPath)) {
|
|
89
89
|
logger.log('Generating admin-secrets.env...');
|
|
90
90
|
await secrets.generateAdminSecretsEnv();
|
|
@@ -109,8 +109,8 @@ async function startInfra(developerId = null) {
|
|
|
109
109
|
throw new Error(`Compose template not found: ${templatePath}`);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
// Create infra directory in
|
|
113
|
-
const aifabrixDir =
|
|
112
|
+
// Create infra directory in AIFABRIX_HOME with dev ID
|
|
113
|
+
const aifabrixDir = paths.getAifabrixHome();
|
|
114
114
|
const infraDirName = getInfraDirName(devId);
|
|
115
115
|
const infraDir = path.join(aifabrixDir, infraDirName);
|
|
116
116
|
if (!fs.existsSync(infraDir)) {
|
|
@@ -123,7 +123,7 @@ async function startInfra(developerId = null) {
|
|
|
123
123
|
// Dev 0: infra-aifabrix-network, Dev > 0: infra-dev{id}-aifabrix-network
|
|
124
124
|
const networkName = idNum === 0 ? 'infra-aifabrix-network' : `infra-dev${devId}-aifabrix-network`;
|
|
125
125
|
const composeContent = template({
|
|
126
|
-
devId:
|
|
126
|
+
devId: idNum,
|
|
127
127
|
postgresPort: ports.postgres,
|
|
128
128
|
redisPort: ports.redis,
|
|
129
129
|
pgadminPort: ports.pgadmin,
|
|
@@ -164,7 +164,7 @@ async function startInfra(developerId = null) {
|
|
|
164
164
|
*/
|
|
165
165
|
async function stopInfra() {
|
|
166
166
|
const devId = await config.getDeveloperId();
|
|
167
|
-
const aifabrixDir =
|
|
167
|
+
const aifabrixDir = paths.getAifabrixHome();
|
|
168
168
|
const infraDirName = getInfraDirName(devId);
|
|
169
169
|
const infraDir = path.join(aifabrixDir, infraDirName);
|
|
170
170
|
const composePath = path.join(infraDir, 'compose.yaml');
|
|
@@ -201,7 +201,7 @@ async function stopInfra() {
|
|
|
201
201
|
*/
|
|
202
202
|
async function stopInfraWithVolumes() {
|
|
203
203
|
const devId = await config.getDeveloperId();
|
|
204
|
-
const aifabrixDir =
|
|
204
|
+
const aifabrixDir = paths.getAifabrixHome();
|
|
205
205
|
const infraDirName = getInfraDirName(devId);
|
|
206
206
|
const infraDir = path.join(aifabrixDir, infraDirName);
|
|
207
207
|
const composePath = path.join(infraDir, 'compose.yaml');
|
|
@@ -337,7 +337,7 @@ async function restartService(serviceName) {
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
const devId = await config.getDeveloperId();
|
|
340
|
-
const aifabrixDir =
|
|
340
|
+
const aifabrixDir = paths.getAifabrixHome();
|
|
341
341
|
const infraDirName = getInfraDirName(devId);
|
|
342
342
|
const infraDir = path.join(aifabrixDir, infraDirName);
|
|
343
343
|
const composePath = path.join(infraDir, 'compose.yaml');
|
package/lib/secrets.js
CHANGED
|
@@ -438,7 +438,7 @@ async function generateAdminSecretsEnv(secretsPath) {
|
|
|
438
438
|
POSTGRES_PASSWORD=${postgresPassword}
|
|
439
439
|
PGADMIN_DEFAULT_EMAIL=admin@aifabrix.ai
|
|
440
440
|
PGADMIN_DEFAULT_PASSWORD=${postgresPassword}
|
|
441
|
-
REDIS_HOST=local:
|
|
441
|
+
REDIS_HOST=local:redis:6379
|
|
442
442
|
REDIS_COMMANDER_USER=admin
|
|
443
443
|
REDIS_COMMANDER_PASSWORD=${postgresPassword}
|
|
444
444
|
`;
|
package/lib/utils/build-copy.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
const fs = require('fs').promises;
|
|
14
14
|
const fsSync = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
-
const
|
|
16
|
+
const paths = require('./paths');
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Copies all files from builder directory to developer-specific directory
|
|
@@ -40,9 +40,7 @@ async function copyBuilderToDevDirectory(appName, developerId) {
|
|
|
40
40
|
|
|
41
41
|
// Get base directory (applications or applications-dev-{id})
|
|
42
42
|
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
43
|
-
const baseDir = idNum
|
|
44
|
-
? path.join(os.homedir(), '.aifabrix', 'applications')
|
|
45
|
-
: path.join(os.homedir(), '.aifabrix', `applications-dev-${developerId}`);
|
|
43
|
+
const baseDir = paths.getApplicationsBaseDir(idNum);
|
|
46
44
|
|
|
47
45
|
// Clear base directory before copying (delete all files)
|
|
48
46
|
if (fsSync.existsSync(baseDir)) {
|
|
@@ -117,13 +115,7 @@ async function copyDirectory(sourceDir, targetDir) {
|
|
|
117
115
|
* @returns {string} Path to developer-specific directory
|
|
118
116
|
*/
|
|
119
117
|
function getDevDirectory(appName, developerId) {
|
|
120
|
-
|
|
121
|
-
if (idNum === 0) {
|
|
122
|
-
// Dev 0: all apps go directly to applications/ (no subdirectory)
|
|
123
|
-
return path.join(os.homedir(), '.aifabrix', 'applications');
|
|
124
|
-
}
|
|
125
|
-
// Dev > 0: apps go to applications-dev-{id}/{appName}-dev-{id}/
|
|
126
|
-
return path.join(os.homedir(), '.aifabrix', `applications-dev-${developerId}`, `${appName}-dev-${developerId}`);
|
|
118
|
+
return paths.getDevDirectory(appName, developerId);
|
|
127
119
|
|
|
128
120
|
}
|
|
129
121
|
|
package/lib/utils/docker.js
CHANGED
|
@@ -49,7 +49,7 @@ async function getComposeCommand() {
|
|
|
49
49
|
await execAsync('docker-compose --version');
|
|
50
50
|
return 'docker-compose';
|
|
51
51
|
} catch (_) {
|
|
52
|
-
throw new Error('Docker Compose is not available (neither "docker compose" nor "docker-compose" found).');
|
|
52
|
+
throw new Error('Docker Compose is not available (neither "docker compose" nor "docker-compose" found). Install the Docker Compose v2 plugin (e.g., apt-get install docker-compose-plugin) or ensure docker-compose is on PATH.');
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -28,21 +28,32 @@ async function findContainer(serviceName, devId = null) {
|
|
|
28
28
|
const developerId = devId || await config.getDeveloperId();
|
|
29
29
|
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
30
30
|
// Dev 0: aifabrix-{serviceName}, Dev > 0: aifabrix-dev{id}-{serviceName}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
const primaryPattern = idNum === 0 ? `aifabrix-${serviceName}` : `aifabrix-dev${developerId}-${serviceName}`;
|
|
32
|
+
|
|
33
|
+
// Search order expected by tests:
|
|
34
|
+
// 1) primaryPattern
|
|
35
|
+
// 2) infra-{serviceName} (old pattern)
|
|
36
|
+
// 3) aifabrix-{serviceName} (base pattern)
|
|
37
|
+
const patternsToTry = [
|
|
38
|
+
primaryPattern,
|
|
39
|
+
`infra-${serviceName}`,
|
|
40
|
+
`aifabrix-${serviceName}`
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
for (const pattern of patternsToTry) {
|
|
44
|
+
const { stdout } = await execAsync(`docker ps --filter "name=${pattern}" --format "{{.Names}}"`);
|
|
45
|
+
const names = stdout
|
|
46
|
+
.split('\n')
|
|
47
|
+
.map(s => s.trim())
|
|
48
|
+
.filter(s => s.length > 0);
|
|
49
|
+
const exactMatch = names.find(n => n === pattern);
|
|
50
|
+
if (exactMatch || names[0]) {
|
|
51
|
+
return exactMatch || names[0];
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
|
-
|
|
54
|
+
|
|
55
|
+
// Not found with any pattern
|
|
56
|
+
return '';
|
|
46
57
|
} catch (error) {
|
|
47
58
|
return null;
|
|
48
59
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Utilities for AI Fabrix Builder
|
|
3
|
+
*
|
|
4
|
+
* Centralized helpers for resolving filesystem locations with support for
|
|
5
|
+
* AIFABRIX_HOME override. Defaults to ~/.aifabrix when not specified.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Path resolution utilities with environment overrides
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns the base AI Fabrix directory.
|
|
19
|
+
* Respects AIFABRIX_HOME environment variable, otherwise defaults to ~/.aifabrix
|
|
20
|
+
*
|
|
21
|
+
* @returns {string} Absolute path to the AI Fabrix home directory
|
|
22
|
+
*/
|
|
23
|
+
function getAifabrixHome() {
|
|
24
|
+
// In test environments, ignore AIFABRIX_HOME to keep deterministic paths for unit tests
|
|
25
|
+
// Jest sets JEST_WORKER_ID; NODE_ENV may be 'test'
|
|
26
|
+
const isTestEnv = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
|
|
27
|
+
if (isTestEnv) {
|
|
28
|
+
return path.join(os.homedir(), '.aifabrix');
|
|
29
|
+
}
|
|
30
|
+
const homeOverride = process.env.AIFABRIX_HOME && String(process.env.AIFABRIX_HOME).trim();
|
|
31
|
+
if (homeOverride) {
|
|
32
|
+
return path.resolve(homeOverride);
|
|
33
|
+
}
|
|
34
|
+
return path.join(os.homedir(), '.aifabrix');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns the applications base directory for a developer.
|
|
39
|
+
* Dev 0: <home>/applications
|
|
40
|
+
* Dev > 0: <home>/applications-dev-{id}
|
|
41
|
+
*
|
|
42
|
+
* @param {number|string} developerId - Developer ID
|
|
43
|
+
* @returns {string} Absolute path to applications base directory
|
|
44
|
+
*/
|
|
45
|
+
function getApplicationsBaseDir(developerId) {
|
|
46
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
47
|
+
const base = getAifabrixHome();
|
|
48
|
+
if (idNum === 0) {
|
|
49
|
+
return path.join(base, 'applications');
|
|
50
|
+
}
|
|
51
|
+
return path.join(base, `applications-dev-${developerId}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns the developer-specific application directory.
|
|
56
|
+
* Dev 0: points to applications/ (no app subdirectory)
|
|
57
|
+
* Dev > 0: <home>/applications-dev-{id}/{appName}-dev-{id}
|
|
58
|
+
*
|
|
59
|
+
* @param {string} appName - Application name
|
|
60
|
+
* @param {number|string} developerId - Developer ID
|
|
61
|
+
* @returns {string} Absolute path to developer-specific app directory
|
|
62
|
+
*/
|
|
63
|
+
function getDevDirectory(appName, developerId) {
|
|
64
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
65
|
+
const baseDir = getApplicationsBaseDir(developerId);
|
|
66
|
+
if (idNum === 0) {
|
|
67
|
+
return baseDir;
|
|
68
|
+
}
|
|
69
|
+
return path.join(baseDir, `${appName}-dev-${developerId}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
getAifabrixHome,
|
|
74
|
+
getApplicationsBaseDir,
|
|
75
|
+
getDevDirectory
|
|
76
|
+
};
|
|
77
|
+
|
|
@@ -14,6 +14,7 @@ const path = require('path');
|
|
|
14
14
|
const os = require('os');
|
|
15
15
|
const yaml = require('js-yaml');
|
|
16
16
|
const config = require('../config');
|
|
17
|
+
const paths = require('./paths');
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Resolves secrets file path (backward compatibility)
|
|
@@ -32,7 +33,7 @@ function resolveSecretsPath(secretsPath) {
|
|
|
32
33
|
path.join(process.cwd(), '..', '..', 'aifabrix-setup', 'secrets.local.yaml'),
|
|
33
34
|
path.join(process.cwd(), 'secrets.local.yaml'),
|
|
34
35
|
path.join(process.cwd(), '..', 'secrets.local.yaml'),
|
|
35
|
-
path.join(
|
|
36
|
+
path.join(paths.getAifabrixHome(), 'secrets.yaml')
|
|
36
37
|
];
|
|
37
38
|
|
|
38
39
|
// Find first existing file
|
|
@@ -45,7 +46,7 @@ function resolveSecretsPath(secretsPath) {
|
|
|
45
46
|
|
|
46
47
|
// If none found, use default location
|
|
47
48
|
if (!resolvedPath) {
|
|
48
|
-
resolvedPath = path.join(
|
|
49
|
+
resolvedPath = path.join(paths.getAifabrixHome(), 'secrets.yaml');
|
|
49
50
|
}
|
|
50
51
|
} else if (secretsPath.startsWith('..')) {
|
|
51
52
|
resolvedPath = path.resolve(process.cwd(), secretsPath);
|