@aifabrix/builder 2.3.5 → 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.
@@ -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 homeDir = os.homedir();
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 os = require('os');
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(os.homedir(), '.aifabrix', appName);
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(CONFIG_FILE, 'utf8');
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(CONFIG_DIR, { recursive: true });
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(CONFIG_FILE, configContent, {
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(CONFIG_FILE, 'r+');
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(CONFIG_FILE);
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(CONFIG_FILE, 'utf8');
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(os.homedir(), '.aifabrix', 'admin-secrets.env');
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 ~/.aifabrix with dev ID
113
- const aifabrixDir = path.join(os.homedir(), '.aifabrix');
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)) {
@@ -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 = path.join(os.homedir(), '.aifabrix');
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 = path.join(os.homedir(), '.aifabrix');
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 = path.join(os.homedir(), '.aifabrix');
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');
@@ -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 os = require('os');
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 === 0
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
- const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
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
 
@@ -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
 
@@ -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(os.homedir(), '.aifabrix', 'secrets.yaml')
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(os.homedir(), '.aifabrix', 'secrets.yaml');
49
+ resolvedPath = path.join(paths.getAifabrixHome(), 'secrets.yaml');
49
50
  }
50
51
  } else if (secretsPath.startsWith('..')) {
51
52
  resolvedPath = path.resolve(process.cwd(), secretsPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.3.5",
3
+ "version": "2.3.6",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {