@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 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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {