@aifabrix/builder 2.6.0 → 2.6.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/lib/cli.js +15 -0
- package/lib/commands/secrets-set.js +70 -0
- package/lib/utils/local-secrets.js +73 -1
- package/package.json +1 -1
package/lib/cli.js
CHANGED
|
@@ -21,6 +21,7 @@ const logger = require('./utils/logger');
|
|
|
21
21
|
const { validateCommand, handleCommandError } = require('./utils/cli-utils');
|
|
22
22
|
const { handleLogin } = require('./commands/login');
|
|
23
23
|
const { handleSecure } = require('./commands/secure');
|
|
24
|
+
const { handleSecretsSet } = require('./commands/secrets-set');
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Sets up all CLI commands on the Commander program instance
|
|
@@ -435,6 +436,20 @@ function setupCommands(program) {
|
|
|
435
436
|
}
|
|
436
437
|
});
|
|
437
438
|
|
|
439
|
+
// Secrets management commands
|
|
440
|
+
program
|
|
441
|
+
.command('secrets set <key> <value>')
|
|
442
|
+
.description('Set a secret value in secrets file')
|
|
443
|
+
.option('--shared', 'Save to general secrets file (from config.yaml aifabrix-secrets) instead of user secrets')
|
|
444
|
+
.action(async(key, value, options) => {
|
|
445
|
+
try {
|
|
446
|
+
await handleSecretsSet(key, value, options);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
handleCommandError(error, 'secrets set');
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
438
453
|
// Security command
|
|
439
454
|
program.command('secure')
|
|
440
455
|
.description('Encrypt secrets in secrets.local.yaml files for ISO 27001 compliance')
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - Secrets Set Command
|
|
3
|
+
*
|
|
4
|
+
* Handles setting secret values in secrets files
|
|
5
|
+
* Supports both user secrets and general secrets files
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Secrets set command implementation for AI Fabrix Builder
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const chalk = require('chalk');
|
|
15
|
+
const logger = require('../utils/logger');
|
|
16
|
+
const { getAifabrixSecretsPath } = require('../config');
|
|
17
|
+
const { saveLocalSecret, saveSecret } = require('../utils/local-secrets');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Handle secrets set command action
|
|
21
|
+
* Sets a secret value in either user secrets or general secrets file
|
|
22
|
+
*
|
|
23
|
+
* @async
|
|
24
|
+
* @function handleSecretsSet
|
|
25
|
+
* @param {string} key - Secret key name
|
|
26
|
+
* @param {string} value - Secret value (supports full URLs or environment variable interpolation)
|
|
27
|
+
* @param {Object} options - Command options
|
|
28
|
+
* @param {boolean} [options.shared] - If true, save to general secrets file
|
|
29
|
+
* @returns {Promise<void>} Resolves when secret is saved
|
|
30
|
+
* @throws {Error} If save fails or validation fails
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* await handleSecretsSet('keycloak-public-server-urlKeyVault', 'https://mydomain.com/keycloak', { shared: false });
|
|
34
|
+
* await handleSecretsSet('keycloak-public-server-urlKeyVault', 'https://${VAR}:8182', { shared: true });
|
|
35
|
+
*/
|
|
36
|
+
async function handleSecretsSet(key, value, options) {
|
|
37
|
+
if (!key || typeof key !== 'string') {
|
|
38
|
+
throw new Error('Secret key is required and must be a string');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (value === undefined || value === null || value === '') {
|
|
42
|
+
throw new Error('Secret value is required');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const isShared = options.shared || options['shared'] || false;
|
|
46
|
+
|
|
47
|
+
if (isShared) {
|
|
48
|
+
// Save to general secrets file
|
|
49
|
+
const generalSecretsPath = await getAifabrixSecretsPath();
|
|
50
|
+
if (!generalSecretsPath) {
|
|
51
|
+
throw new Error('General secrets file not configured. Set aifabrix-secrets in config.yaml or use without --shared flag for user secrets.');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Resolve path (handle absolute vs relative)
|
|
55
|
+
const resolvedPath = path.isAbsolute(generalSecretsPath)
|
|
56
|
+
? generalSecretsPath
|
|
57
|
+
: path.resolve(process.cwd(), generalSecretsPath);
|
|
58
|
+
|
|
59
|
+
await saveSecret(key, value, resolvedPath);
|
|
60
|
+
logger.log(chalk.green(`✓ Secret '${key}' saved to general secrets file: ${resolvedPath}`));
|
|
61
|
+
} else {
|
|
62
|
+
// Save to user secrets file
|
|
63
|
+
await saveLocalSecret(key, value);
|
|
64
|
+
const userSecretsPath = path.join(os.homedir(), '.aifabrix', 'secrets.local.yaml');
|
|
65
|
+
logger.log(chalk.green(`✓ Secret '${key}' saved to user secrets file: ${userSecretsPath}`));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = { handleSecretsSet };
|
|
70
|
+
|
|
@@ -77,6 +77,78 @@ async function saveLocalSecret(key, value) {
|
|
|
77
77
|
fs.writeFileSync(secretsPath, yamlContent, { mode: 0o600 });
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Saves a secret to a specified secrets file path
|
|
82
|
+
* Merges with existing secrets without overwriting other keys
|
|
83
|
+
*
|
|
84
|
+
* @async
|
|
85
|
+
* @function saveSecret
|
|
86
|
+
* @param {string} key - Secret key name
|
|
87
|
+
* @param {string} value - Secret value
|
|
88
|
+
* @param {string} secretsPath - Path to secrets file (absolute or relative)
|
|
89
|
+
* @returns {Promise<void>} Resolves when secret is saved
|
|
90
|
+
* @throws {Error} If save fails
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* await saveSecret('myapp-client-idKeyVault', 'client-id-value', '/path/to/secrets.yaml');
|
|
94
|
+
*/
|
|
95
|
+
async function saveSecret(key, value, secretsPath) {
|
|
96
|
+
if (!key || typeof key !== 'string') {
|
|
97
|
+
throw new Error('Secret key is required and must be a string');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (value === undefined || value === null) {
|
|
101
|
+
throw new Error('Secret value is required');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!secretsPath || typeof secretsPath !== 'string') {
|
|
105
|
+
throw new Error('Secrets path is required and must be a string');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Resolve path (handle absolute vs relative)
|
|
109
|
+
const resolvedPath = path.isAbsolute(secretsPath)
|
|
110
|
+
? secretsPath
|
|
111
|
+
: path.resolve(process.cwd(), secretsPath);
|
|
112
|
+
|
|
113
|
+
const secretsDir = path.dirname(resolvedPath);
|
|
114
|
+
|
|
115
|
+
// Create directory if needed
|
|
116
|
+
if (!fs.existsSync(secretsDir)) {
|
|
117
|
+
fs.mkdirSync(secretsDir, { recursive: true, mode: 0o700 });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Load existing secrets
|
|
121
|
+
let existingSecrets = {};
|
|
122
|
+
if (fs.existsSync(resolvedPath)) {
|
|
123
|
+
try {
|
|
124
|
+
const content = fs.readFileSync(resolvedPath, 'utf8');
|
|
125
|
+
existingSecrets = yaml.load(content) || {};
|
|
126
|
+
if (typeof existingSecrets !== 'object') {
|
|
127
|
+
existingSecrets = {};
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
logger.warn(`Warning: Could not read existing secrets file: ${error.message}`);
|
|
131
|
+
existingSecrets = {};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Merge with new secret
|
|
136
|
+
const updatedSecrets = {
|
|
137
|
+
...existingSecrets,
|
|
138
|
+
[key]: value
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Save to file
|
|
142
|
+
const yamlContent = yaml.dump(updatedSecrets, {
|
|
143
|
+
indent: 2,
|
|
144
|
+
lineWidth: 120,
|
|
145
|
+
noRefs: true,
|
|
146
|
+
sortKeys: false
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
fs.writeFileSync(resolvedPath, yamlContent, { mode: 0o600 });
|
|
150
|
+
}
|
|
151
|
+
|
|
80
152
|
/**
|
|
81
153
|
* Checks if a URL is localhost
|
|
82
154
|
* @function isLocalhost
|
|
@@ -92,5 +164,5 @@ function isLocalhost(url) {
|
|
|
92
164
|
return urlLower.includes('localhost') || urlLower.includes('127.0.0.1');
|
|
93
165
|
}
|
|
94
166
|
|
|
95
|
-
module.exports = { saveLocalSecret, isLocalhost };
|
|
167
|
+
module.exports = { saveLocalSecret, saveSecret, isLocalhost };
|
|
96
168
|
|