@aifabrix/builder 2.3.5 → 2.4.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/lib/audit-logger.js +2 -2
- package/lib/build.js +2 -2
- package/lib/config.js +68 -9
- package/lib/infra.js +7 -7
- package/lib/secrets.js +3 -3
- package/lib/utils/build-copy.js +3 -11
- package/lib/utils/docker.js +1 -1
- package/lib/utils/paths.js +97 -0
- package/lib/utils/secrets-path.js +19 -41
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +6 -4
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,19 @@
|
|
|
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
|
+
// Avoid importing paths here to prevent circular dependency.
|
|
17
|
+
// Config location is always under OS home at ~/.aifabrix/config.yaml
|
|
16
18
|
|
|
19
|
+
// Default (for tests and constants): always reflects OS home
|
|
17
20
|
const CONFIG_DIR = path.join(os.homedir(), '.aifabrix');
|
|
18
21
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.yaml');
|
|
19
22
|
|
|
23
|
+
// Runtime config directory (always under OS home)
|
|
24
|
+
const RUNTIME_CONFIG_DIR = path.join(os.homedir(), '.aifabrix');
|
|
25
|
+
const RUNTIME_CONFIG_FILE = path.join(RUNTIME_CONFIG_DIR, 'config.yaml');
|
|
26
|
+
|
|
20
27
|
// Cache for developer ID - loaded when getConfig() is first called
|
|
21
28
|
let cachedDeveloperId = null;
|
|
22
29
|
|
|
@@ -27,7 +34,7 @@ let cachedDeveloperId = null;
|
|
|
27
34
|
*/
|
|
28
35
|
async function getConfig() {
|
|
29
36
|
try {
|
|
30
|
-
const configContent = await fs.readFile(
|
|
37
|
+
const configContent = await fs.readFile(RUNTIME_CONFIG_FILE, 'utf8');
|
|
31
38
|
let config = yaml.load(configContent);
|
|
32
39
|
|
|
33
40
|
// Handle empty file or null/undefined result from yaml.load
|
|
@@ -91,19 +98,19 @@ async function getConfig() {
|
|
|
91
98
|
async function saveConfig(data) {
|
|
92
99
|
try {
|
|
93
100
|
// Create directory if it doesn't exist
|
|
94
|
-
await fs.mkdir(
|
|
101
|
+
await fs.mkdir(RUNTIME_CONFIG_DIR, { recursive: true });
|
|
95
102
|
|
|
96
103
|
// Set secure permissions
|
|
97
104
|
// Force quotes to ensure numeric-like strings (e.g., "01") remain strings in YAML
|
|
98
105
|
const configContent = yaml.dump(data, { forceQuotes: true });
|
|
99
106
|
// Write file first
|
|
100
|
-
await fs.writeFile(
|
|
107
|
+
await fs.writeFile(RUNTIME_CONFIG_FILE, configContent, {
|
|
101
108
|
mode: 0o600,
|
|
102
109
|
flag: 'w'
|
|
103
110
|
});
|
|
104
111
|
// Open file descriptor and fsync to ensure write is flushed to disk
|
|
105
112
|
// This is critical on Windows where file writes may be cached
|
|
106
|
-
const fd = await fs.open(
|
|
113
|
+
const fd = await fs.open(RUNTIME_CONFIG_FILE, 'r+');
|
|
107
114
|
try {
|
|
108
115
|
await fd.sync();
|
|
109
116
|
} finally {
|
|
@@ -120,7 +127,7 @@ async function saveConfig(data) {
|
|
|
120
127
|
*/
|
|
121
128
|
async function clearConfig() {
|
|
122
129
|
try {
|
|
123
|
-
await fs.unlink(
|
|
130
|
+
await fs.unlink(RUNTIME_CONFIG_FILE);
|
|
124
131
|
} catch (error) {
|
|
125
132
|
if (error.code !== 'ENOENT') {
|
|
126
133
|
throw new Error(`Failed to clear config: ${error.message}`);
|
|
@@ -179,7 +186,7 @@ async function setDeveloperId(developerId) {
|
|
|
179
186
|
// Add a small delay to ensure file system has flushed the write
|
|
180
187
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
181
188
|
// Read file again with fresh file handle to avoid OS caching
|
|
182
|
-
const savedContent = await fs.readFile(
|
|
189
|
+
const savedContent = await fs.readFile(RUNTIME_CONFIG_FILE, 'utf8');
|
|
183
190
|
const savedConfig = yaml.load(savedContent);
|
|
184
191
|
// YAML may parse numbers as numbers, so convert to string for comparison
|
|
185
192
|
const savedDevIdString = String(savedConfig['developer-id']);
|
|
@@ -362,7 +369,8 @@ async function setSecretsEncryptionKey(key) {
|
|
|
362
369
|
*/
|
|
363
370
|
async function getSecretsPath() {
|
|
364
371
|
const config = await getConfig();
|
|
365
|
-
|
|
372
|
+
// Backward compatibility: prefer new key, fallback to legacy
|
|
373
|
+
return config['aifabrix-secrets'] || config['secrets-path'] || null;
|
|
366
374
|
}
|
|
367
375
|
|
|
368
376
|
/**
|
|
@@ -376,7 +384,54 @@ async function setSecretsPath(secretsPath) {
|
|
|
376
384
|
}
|
|
377
385
|
|
|
378
386
|
const config = await getConfig();
|
|
379
|
-
|
|
387
|
+
// Store under new canonical key
|
|
388
|
+
config['aifabrix-secrets'] = secretsPath;
|
|
389
|
+
await saveConfig(config);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Get aifabrix-home override from configuration
|
|
394
|
+
* @returns {Promise<string|null>} Home override path or null if not set
|
|
395
|
+
*/
|
|
396
|
+
async function getAifabrixHomeOverride() {
|
|
397
|
+
const config = await getConfig();
|
|
398
|
+
return config['aifabrix-home'] || null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Set aifabrix-home override in configuration
|
|
403
|
+
* @param {string} homePath - Base directory path for AI Fabrix files
|
|
404
|
+
* @returns {Promise<void>}
|
|
405
|
+
*/
|
|
406
|
+
async function setAifabrixHomeOverride(homePath) {
|
|
407
|
+
if (!homePath || typeof homePath !== 'string') {
|
|
408
|
+
throw new Error('Home path is required and must be a string');
|
|
409
|
+
}
|
|
410
|
+
const config = await getConfig();
|
|
411
|
+
config['aifabrix-home'] = homePath;
|
|
412
|
+
await saveConfig(config);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Get aifabrix-secrets path from configuration (canonical)
|
|
417
|
+
* @returns {Promise<string|null>} Secrets path or null if not set
|
|
418
|
+
*/
|
|
419
|
+
async function getAifabrixSecretsPath() {
|
|
420
|
+
const config = await getConfig();
|
|
421
|
+
return config['aifabrix-secrets'] || null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Set aifabrix-secrets path in configuration (canonical)
|
|
426
|
+
* @param {string} secretsPath - Path to default secrets file
|
|
427
|
+
* @returns {Promise<void>}
|
|
428
|
+
*/
|
|
429
|
+
async function setAifabrixSecretsPath(secretsPath) {
|
|
430
|
+
if (!secretsPath || typeof secretsPath !== 'string') {
|
|
431
|
+
throw new Error('Secrets path is required and must be a string');
|
|
432
|
+
}
|
|
433
|
+
const config = await getConfig();
|
|
434
|
+
config['aifabrix-secrets'] = secretsPath;
|
|
380
435
|
await saveConfig(config);
|
|
381
436
|
}
|
|
382
437
|
|
|
@@ -399,6 +454,10 @@ const exportsObj = {
|
|
|
399
454
|
setSecretsEncryptionKey,
|
|
400
455
|
getSecretsPath,
|
|
401
456
|
setSecretsPath,
|
|
457
|
+
getAifabrixHomeOverride,
|
|
458
|
+
setAifabrixHomeOverride,
|
|
459
|
+
getAifabrixSecretsPath,
|
|
460
|
+
setAifabrixSecretsPath,
|
|
402
461
|
CONFIG_DIR,
|
|
403
462
|
CONFIG_FILE
|
|
404
463
|
};
|
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)) {
|
|
@@ -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
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const yaml = require('js-yaml');
|
|
15
|
-
const os = require('os');
|
|
16
15
|
const chalk = require('chalk');
|
|
17
16
|
const logger = require('./utils/logger');
|
|
18
17
|
const config = require('./config');
|
|
@@ -33,6 +32,7 @@ const {
|
|
|
33
32
|
resolveUrlPort
|
|
34
33
|
} = require('./utils/secrets-utils');
|
|
35
34
|
const { decryptSecret, isEncrypted } = require('./utils/secrets-encryption');
|
|
35
|
+
const pathsUtil = require('./utils/paths');
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Loads environment configuration for docker/local context
|
|
@@ -414,7 +414,7 @@ async function generateAdminSecretsEnv(secretsPath) {
|
|
|
414
414
|
secrets = await loadSecrets(secretsPath);
|
|
415
415
|
} catch (error) {
|
|
416
416
|
// If secrets file doesn't exist, create default secrets
|
|
417
|
-
const defaultSecretsPath = secretsPath || path.join(
|
|
417
|
+
const defaultSecretsPath = secretsPath || path.join(pathsUtil.getAifabrixHome(), 'secrets.yaml');
|
|
418
418
|
|
|
419
419
|
if (!fs.existsSync(defaultSecretsPath)) {
|
|
420
420
|
logger.log('Creating default secrets file...');
|
|
@@ -425,7 +425,7 @@ async function generateAdminSecretsEnv(secretsPath) {
|
|
|
425
425
|
}
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
-
const aifabrixDir =
|
|
428
|
+
const aifabrixDir = pathsUtil.getAifabrixHome();
|
|
429
429
|
const adminEnvPath = path.join(aifabrixDir, 'admin-secrets.env');
|
|
430
430
|
|
|
431
431
|
if (!fs.existsSync(aifabrixDir)) {
|
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
|
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
const fs = require('fs');
|
|
17
|
+
const yaml = require('js-yaml');
|
|
18
|
+
|
|
19
|
+
function safeHomedir() {
|
|
20
|
+
try {
|
|
21
|
+
if (typeof os.homedir === 'function') {
|
|
22
|
+
const hd = os.homedir();
|
|
23
|
+
if (typeof hd === 'string' && hd.length > 0) {
|
|
24
|
+
return hd;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// ignore
|
|
29
|
+
}
|
|
30
|
+
return process.env.HOME || process.env.USERPROFILE || '/';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns the base AI Fabrix directory.
|
|
35
|
+
* Resolved from config.yaml `aifabrix-home` (stored under OS home).
|
|
36
|
+
* Falls back to ~/.aifabrix when not specified.
|
|
37
|
+
*
|
|
38
|
+
* @returns {string} Absolute path to the AI Fabrix home directory
|
|
39
|
+
*/
|
|
40
|
+
function getAifabrixHome() {
|
|
41
|
+
try {
|
|
42
|
+
const configPath = path.join(safeHomedir(), '.aifabrix', 'config.yaml');
|
|
43
|
+
if (fs.existsSync(configPath)) {
|
|
44
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
45
|
+
const config = yaml.load(content) || {};
|
|
46
|
+
const homeOverride = config && typeof config['aifabrix-home'] === 'string' ? config['aifabrix-home'].trim() : '';
|
|
47
|
+
if (homeOverride) {
|
|
48
|
+
return path.resolve(homeOverride);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// Ignore errors and fall back to default
|
|
53
|
+
}
|
|
54
|
+
return path.join(safeHomedir(), '.aifabrix');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns the applications base directory for a developer.
|
|
59
|
+
* Dev 0: <home>/applications
|
|
60
|
+
* Dev > 0: <home>/applications-dev-{id}
|
|
61
|
+
*
|
|
62
|
+
* @param {number|string} developerId - Developer ID
|
|
63
|
+
* @returns {string} Absolute path to applications base directory
|
|
64
|
+
*/
|
|
65
|
+
function getApplicationsBaseDir(developerId) {
|
|
66
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
67
|
+
const base = getAifabrixHome();
|
|
68
|
+
if (idNum === 0) {
|
|
69
|
+
return path.join(base, 'applications');
|
|
70
|
+
}
|
|
71
|
+
return path.join(base, `applications-dev-${developerId}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns the developer-specific application directory.
|
|
76
|
+
* Dev 0: points to applications/ (no app subdirectory)
|
|
77
|
+
* Dev > 0: <home>/applications-dev-{id}/{appName}-dev-{id}
|
|
78
|
+
*
|
|
79
|
+
* @param {string} appName - Application name
|
|
80
|
+
* @param {number|string} developerId - Developer ID
|
|
81
|
+
* @returns {string} Absolute path to developer-specific app directory
|
|
82
|
+
*/
|
|
83
|
+
function getDevDirectory(appName, developerId) {
|
|
84
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
85
|
+
const baseDir = getApplicationsBaseDir(developerId);
|
|
86
|
+
if (idNum === 0) {
|
|
87
|
+
return baseDir;
|
|
88
|
+
}
|
|
89
|
+
return path.join(baseDir, `${appName}-dev-${developerId}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
getAifabrixHome,
|
|
94
|
+
getApplicationsBaseDir,
|
|
95
|
+
getDevDirectory
|
|
96
|
+
};
|
|
97
|
+
|
|
@@ -11,53 +11,32 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const os = require('os');
|
|
15
14
|
const yaml = require('js-yaml');
|
|
16
15
|
const config = require('../config');
|
|
16
|
+
const paths = require('./paths');
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Resolves secrets file path
|
|
20
|
-
*
|
|
19
|
+
* Resolves secrets file path when an explicit path is provided.
|
|
20
|
+
* If not provided, returns default fallback under <home>/secrets.yaml.
|
|
21
21
|
* @function resolveSecretsPath
|
|
22
22
|
* @param {string} [secretsPath] - Path to secrets file (optional)
|
|
23
23
|
* @returns {string} Resolved secrets file path
|
|
24
24
|
*/
|
|
25
25
|
function resolveSecretsPath(secretsPath) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!resolvedPath) {
|
|
29
|
-
// Check common locations for secrets.local.yaml
|
|
30
|
-
const commonLocations = [
|
|
31
|
-
path.join(process.cwd(), '..', 'aifabrix-setup', 'secrets.local.yaml'),
|
|
32
|
-
path.join(process.cwd(), '..', '..', 'aifabrix-setup', 'secrets.local.yaml'),
|
|
33
|
-
path.join(process.cwd(), 'secrets.local.yaml'),
|
|
34
|
-
path.join(process.cwd(), '..', 'secrets.local.yaml'),
|
|
35
|
-
path.join(os.homedir(), '.aifabrix', 'secrets.yaml')
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
// Find first existing file
|
|
39
|
-
for (const location of commonLocations) {
|
|
40
|
-
if (fs.existsSync(location)) {
|
|
41
|
-
resolvedPath = location;
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// If none found, use default location
|
|
47
|
-
if (!resolvedPath) {
|
|
48
|
-
resolvedPath = path.join(os.homedir(), '.aifabrix', 'secrets.yaml');
|
|
49
|
-
}
|
|
50
|
-
} else if (secretsPath.startsWith('..')) {
|
|
51
|
-
resolvedPath = path.resolve(process.cwd(), secretsPath);
|
|
26
|
+
if (secretsPath && secretsPath.startsWith('..')) {
|
|
27
|
+
return path.resolve(process.cwd(), secretsPath);
|
|
52
28
|
}
|
|
53
|
-
|
|
54
|
-
|
|
29
|
+
if (secretsPath) {
|
|
30
|
+
return secretsPath;
|
|
31
|
+
}
|
|
32
|
+
// Default fallback
|
|
33
|
+
return path.join(paths.getAifabrixHome(), 'secrets.yaml');
|
|
55
34
|
}
|
|
56
35
|
|
|
57
36
|
/**
|
|
58
37
|
* Determines the actual secrets file paths that loadSecrets would use
|
|
59
38
|
* Mirrors the cascading lookup logic from loadSecrets
|
|
60
|
-
*
|
|
39
|
+
* Uses config.yaml for default secrets path as fallback
|
|
61
40
|
*
|
|
62
41
|
* @async
|
|
63
42
|
* @function getActualSecretsPath
|
|
@@ -77,8 +56,8 @@ async function getActualSecretsPath(secretsPath, appName) {
|
|
|
77
56
|
};
|
|
78
57
|
}
|
|
79
58
|
|
|
80
|
-
// Cascading lookup: user's file first
|
|
81
|
-
const userSecretsPath = path.join(
|
|
59
|
+
// Cascading lookup: user's file first (under configured home)
|
|
60
|
+
const userSecretsPath = path.join(paths.getAifabrixHome(), 'secrets.local.yaml');
|
|
82
61
|
|
|
83
62
|
// Check build.secrets from variables.yaml if appName provided
|
|
84
63
|
let buildSecretsPath = null;
|
|
@@ -101,15 +80,14 @@ async function getActualSecretsPath(secretsPath, appName) {
|
|
|
101
80
|
}
|
|
102
81
|
}
|
|
103
82
|
|
|
104
|
-
// If no build.secrets found in variables.yaml, check config.yaml for
|
|
83
|
+
// If no build.secrets found in variables.yaml, check config.yaml for canonical secrets path
|
|
105
84
|
if (!buildSecretsPath) {
|
|
106
85
|
try {
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
: path.resolve(process.cwd(), generalSecretsPath);
|
|
86
|
+
const canonicalSecretsPath = await config.getAifabrixSecretsPath();
|
|
87
|
+
if (canonicalSecretsPath) {
|
|
88
|
+
buildSecretsPath = path.isAbsolute(canonicalSecretsPath)
|
|
89
|
+
? canonicalSecretsPath
|
|
90
|
+
: path.resolve(process.cwd(), canonicalSecretsPath);
|
|
113
91
|
}
|
|
114
92
|
} catch (error) {
|
|
115
93
|
// Ignore errors, continue
|
package/package.json
CHANGED
|
@@ -148,11 +148,13 @@ aifabrix login --method credentials --app {{appName}} --environment dev
|
|
|
148
148
|
aifabrix login --method credentials --app {{appName}} --client-id $CLIENT_ID --client-secret $CLIENT_SECRET --environment dev
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
-
###
|
|
151
|
+
### Configuration
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
Set overrides in `~/.aifabrix/config.yaml`:
|
|
154
|
+
|
|
155
|
+
```yaml
|
|
156
|
+
aifabrix-home: "/custom/path"
|
|
157
|
+
aifabrix-secrets: "/path/to/secrets.yaml"
|
|
156
158
|
```
|
|
157
159
|
|
|
158
160
|
---
|