@aifabrix/builder 2.22.2 → 2.31.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.
Files changed (62) hide show
  1. package/jest.config.coverage.js +37 -0
  2. package/lib/api/pipeline.api.js +10 -9
  3. package/lib/app-deploy.js +36 -14
  4. package/lib/app-list.js +191 -71
  5. package/lib/app-prompts.js +77 -26
  6. package/lib/app-readme.js +123 -5
  7. package/lib/app-rotate-secret.js +101 -57
  8. package/lib/app-run-helpers.js +200 -172
  9. package/lib/app-run.js +137 -68
  10. package/lib/audit-logger.js +8 -7
  11. package/lib/build.js +161 -250
  12. package/lib/cli.js +73 -65
  13. package/lib/commands/login.js +45 -31
  14. package/lib/commands/logout.js +181 -0
  15. package/lib/commands/secure.js +59 -24
  16. package/lib/config.js +79 -45
  17. package/lib/datasource-deploy.js +89 -29
  18. package/lib/deployer.js +164 -129
  19. package/lib/diff.js +63 -21
  20. package/lib/environment-deploy.js +36 -19
  21. package/lib/external-system-deploy.js +134 -66
  22. package/lib/external-system-download.js +244 -171
  23. package/lib/external-system-test.js +199 -164
  24. package/lib/generator-external.js +145 -72
  25. package/lib/generator-helpers.js +49 -17
  26. package/lib/generator-split.js +105 -58
  27. package/lib/infra.js +101 -131
  28. package/lib/schema/application-schema.json +895 -896
  29. package/lib/schema/env-config.yaml +11 -4
  30. package/lib/template-validator.js +13 -4
  31. package/lib/utils/api.js +8 -8
  32. package/lib/utils/app-register-auth.js +36 -18
  33. package/lib/utils/app-run-containers.js +140 -0
  34. package/lib/utils/auth-headers.js +6 -6
  35. package/lib/utils/build-copy.js +60 -2
  36. package/lib/utils/build-helpers.js +94 -0
  37. package/lib/utils/cli-utils.js +177 -76
  38. package/lib/utils/compose-generator.js +12 -2
  39. package/lib/utils/config-tokens.js +151 -9
  40. package/lib/utils/deployment-errors.js +137 -69
  41. package/lib/utils/deployment-validation-helpers.js +103 -0
  42. package/lib/utils/docker-build.js +57 -0
  43. package/lib/utils/dockerfile-utils.js +13 -3
  44. package/lib/utils/env-copy.js +163 -94
  45. package/lib/utils/env-map.js +226 -86
  46. package/lib/utils/error-formatters/network-errors.js +0 -1
  47. package/lib/utils/external-system-display.js +14 -19
  48. package/lib/utils/external-system-env-helpers.js +107 -0
  49. package/lib/utils/external-system-test-helpers.js +144 -0
  50. package/lib/utils/health-check.js +10 -8
  51. package/lib/utils/infra-status.js +123 -0
  52. package/lib/utils/paths.js +228 -49
  53. package/lib/utils/schema-loader.js +125 -57
  54. package/lib/utils/token-manager.js +3 -3
  55. package/lib/utils/yaml-preserve.js +55 -16
  56. package/lib/validate.js +87 -89
  57. package/package.json +4 -4
  58. package/scripts/ci-fix.sh +19 -0
  59. package/scripts/ci-simulate.sh +19 -0
  60. package/templates/applications/miso-controller/test.yaml +1 -0
  61. package/templates/python/Dockerfile.hbs +8 -45
  62. package/templates/typescript/Dockerfile.hbs +8 -42
@@ -320,6 +320,48 @@ function buildScope(offline, customScope) {
320
320
  return defaultScope;
321
321
  }
322
322
 
323
+ /**
324
+ * Validate device code API response
325
+ * @param {Object} deviceCodeApiResponse - API response
326
+ * @throws {Error} If response is invalid
327
+ */
328
+ function validateDeviceCodeResponse(deviceCodeApiResponse) {
329
+ if (!deviceCodeApiResponse) {
330
+ throw new Error('Device code flow initiation returned no response');
331
+ }
332
+
333
+ if (!deviceCodeApiResponse.success) {
334
+ const errorMessage = deviceCodeApiResponse.formattedError ||
335
+ deviceCodeApiResponse.error ||
336
+ 'Device code flow initiation failed';
337
+ const error = new Error(errorMessage);
338
+ if (deviceCodeApiResponse.formattedError) {
339
+ error.formattedError = deviceCodeApiResponse.formattedError;
340
+ }
341
+ throw error;
342
+ }
343
+
344
+ if (!deviceCodeApiResponse.data) {
345
+ throw new Error('Device code flow initiation returned no data');
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Convert API response to device code format
351
+ * @param {Object} apiResponse - API response data
352
+ * @returns {Object} Device code response in snake_case format
353
+ */
354
+ function convertDeviceCodeResponse(apiResponse) {
355
+ const deviceCodeData = apiResponse.data || apiResponse;
356
+ return {
357
+ device_code: deviceCodeData.deviceCode || deviceCodeData.device_code,
358
+ user_code: deviceCodeData.userCode || deviceCodeData.user_code,
359
+ verification_uri: deviceCodeData.verificationUri || deviceCodeData.verification_uri,
360
+ expires_in: deviceCodeData.expiresIn || deviceCodeData.expires_in || 600,
361
+ interval: deviceCodeData.interval || 5
362
+ };
363
+ }
364
+
323
365
  /**
324
366
  * Handle device code flow login
325
367
  * @async
@@ -343,39 +385,11 @@ async function handleDeviceCodeLogin(controllerUrl, environment, offline, scope)
343
385
  const deviceCodeApiResponse = await initiateDeviceCodeFlow(controllerUrl, envKey, requestScope);
344
386
 
345
387
  // Validate response structure
346
- if (!deviceCodeApiResponse) {
347
- throw new Error('Device code flow initiation returned no response');
348
- }
349
-
350
- // Check if API call was successful
351
- if (!deviceCodeApiResponse.success) {
352
- const errorMessage = deviceCodeApiResponse.formattedError ||
353
- deviceCodeApiResponse.error ||
354
- 'Device code flow initiation failed';
355
- const error = new Error(errorMessage);
356
- // Preserve formattedError for better error display
357
- if (deviceCodeApiResponse.formattedError) {
358
- error.formattedError = deviceCodeApiResponse.formattedError;
359
- }
360
- throw error;
361
- }
362
-
363
- // Handle API response format: { success: boolean, data: DeviceCodeResponse }
364
- if (!deviceCodeApiResponse.data) {
365
- throw new Error('Device code flow initiation returned no data');
366
- }
388
+ validateDeviceCodeResponse(deviceCodeApiResponse);
367
389
 
390
+ // Convert API response to device code format
368
391
  const apiResponse = deviceCodeApiResponse.data;
369
- const deviceCodeData = apiResponse.data || apiResponse;
370
-
371
- // Convert camelCase from API to snake_case for compatibility with existing code
372
- const deviceCodeResponse = {
373
- device_code: deviceCodeData.deviceCode || deviceCodeData.device_code,
374
- user_code: deviceCodeData.userCode || deviceCodeData.user_code,
375
- verification_uri: deviceCodeData.verificationUri || deviceCodeData.verification_uri,
376
- expires_in: deviceCodeData.expiresIn || deviceCodeData.expires_in || 600,
377
- interval: deviceCodeData.interval || 5
378
- };
392
+ const deviceCodeResponse = convertDeviceCodeResponse(apiResponse);
379
393
 
380
394
  displayDeviceCodeInfo(deviceCodeResponse.user_code, deviceCodeResponse.verification_uri, logger, chalk);
381
395
 
@@ -0,0 +1,181 @@
1
+ /**
2
+ * AI Fabrix Builder - Logout Command
3
+ *
4
+ * Handles clearing authentication tokens from config.yaml
5
+ * Supports clearing all tokens or specific tokens based on options
6
+ *
7
+ * @fileoverview Logout command implementation for AI Fabrix Builder
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ const chalk = require('chalk');
13
+ const {
14
+ clearDeviceToken,
15
+ clearAllDeviceTokens,
16
+ clearClientToken,
17
+ clearAllClientTokens,
18
+ clearClientTokensForEnvironment,
19
+ normalizeControllerUrl
20
+ } = require('../config');
21
+ const logger = require('../utils/logger');
22
+ const os = require('os');
23
+ const path = require('path');
24
+
25
+ /**
26
+ * Validate environment key format
27
+ * @param {string} envKey - Environment key to validate
28
+ * @throws {Error} If environment key format is invalid
29
+ */
30
+ function validateEnvironmentKey(envKey) {
31
+ if (!/^[a-z0-9-_]+$/i.test(envKey)) {
32
+ throw new Error('Environment key must contain only letters, numbers, hyphens, and underscores');
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Validate controller URL format
38
+ * @param {string} url - Controller URL to validate
39
+ * @throws {Error} If URL format is invalid
40
+ */
41
+ function validateControllerUrl(url) {
42
+ if (!url || typeof url !== 'string' || url.trim().length === 0) {
43
+ throw new Error('Controller URL is required and must be a non-empty string');
44
+ }
45
+ const normalized = normalizeControllerUrl(url);
46
+ // Check for valid URL format: http:// or https:// followed by valid hostname
47
+ // Hostname should be localhost or contain at least one dot (domain)
48
+ try {
49
+ const urlObj = new URL(normalized);
50
+ const hostname = urlObj.hostname;
51
+ // Allow localhost or hostnames with at least one dot (domain)
52
+ if (hostname !== 'localhost' && !hostname.includes('.')) {
53
+ throw new Error('Controller URL must be a valid HTTP or HTTPS URL');
54
+ }
55
+ } catch (urlError) {
56
+ throw new Error('Controller URL must be a valid HTTP or HTTPS URL');
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Clear device tokens based on options
62
+ * @async
63
+ * @param {Object} options - Logout options
64
+ * @returns {Promise<number>} Number of device tokens cleared
65
+ */
66
+ async function clearDeviceTokens(options) {
67
+ if (options.controller) {
68
+ // Clear specific controller device token
69
+ const cleared = await clearDeviceToken(options.controller);
70
+ if (cleared) {
71
+ logger.log(chalk.green(`✓ Cleared device token for controller: ${options.controller}`));
72
+ return 1;
73
+ }
74
+ logger.log(chalk.gray(` No device token found for controller: ${options.controller}`));
75
+ return 0;
76
+ }
77
+
78
+ if (!options.environment && !options.app) {
79
+ // Clear all device tokens (only when no environment/app specified)
80
+ const cleared = await clearAllDeviceTokens();
81
+ if (cleared > 0) {
82
+ logger.log(chalk.green(`✓ Cleared ${cleared} device token(s)`));
83
+ } else {
84
+ logger.log(chalk.gray(' No device tokens found'));
85
+ }
86
+ return cleared;
87
+ }
88
+
89
+ return 0;
90
+ }
91
+
92
+ /**
93
+ * Clear client tokens based on options
94
+ * @async
95
+ * @param {Object} options - Logout options
96
+ * @returns {Promise<number>} Number of client tokens cleared
97
+ */
98
+ async function clearClientTokens(options) {
99
+ if (options.app && options.environment) {
100
+ // Clear specific app token in environment
101
+ const cleared = await clearClientToken(options.environment, options.app);
102
+ if (cleared) {
103
+ logger.log(chalk.green(`✓ Cleared client token for app '${options.app}' in environment '${options.environment}'`));
104
+ return 1;
105
+ }
106
+ logger.log(chalk.gray(` No client token found for app '${options.app}' in environment '${options.environment}'`));
107
+ return 0;
108
+ }
109
+
110
+ if (options.environment && !options.app) {
111
+ // Clear all client tokens for environment
112
+ const cleared = await clearClientTokensForEnvironment(options.environment);
113
+ if (cleared > 0) {
114
+ logger.log(chalk.green(`✓ Cleared ${cleared} client token(s) for environment '${options.environment}'`));
115
+ } else {
116
+ logger.log(chalk.gray(` No client tokens found for environment '${options.environment}'`));
117
+ }
118
+ return cleared;
119
+ }
120
+
121
+ if (!options.controller && !options.environment && !options.app) {
122
+ // Clear all client tokens (only when no specific options specified)
123
+ const cleared = await clearAllClientTokens();
124
+ if (cleared > 0) {
125
+ logger.log(chalk.green(`✓ Cleared ${cleared} client token(s)`));
126
+ } else {
127
+ logger.log(chalk.gray(' No client tokens found'));
128
+ }
129
+ return cleared;
130
+ }
131
+
132
+ return 0;
133
+ }
134
+
135
+ /**
136
+ * Handle logout command action
137
+ * @async
138
+ * @function handleLogout
139
+ * @param {Object} options - Logout options
140
+ * @param {string} [options.controller] - Clear device tokens for specific controller
141
+ * @param {string} [options.environment] - Clear client tokens for specific environment
142
+ * @param {string} [options.app] - Clear client tokens for specific app (requires --environment)
143
+ * @returns {Promise<void>} Resolves when logout completes
144
+ * @throws {Error} If logout fails or options are invalid
145
+ */
146
+ async function handleLogout(options) {
147
+ const configPath = path.join(os.homedir(), '.aifabrix', 'config.yaml');
148
+ logger.log(chalk.blue('\n🔓 Clearing authentication tokens...\n'));
149
+
150
+ // Validate options
151
+ if (options.app && !options.environment) {
152
+ throw new Error('--app requires --environment option');
153
+ }
154
+
155
+ if ('controller' in options && options.controller !== undefined && options.controller !== null) {
156
+ validateControllerUrl(options.controller);
157
+ }
158
+
159
+ if ('environment' in options && options.environment !== undefined && options.environment !== null) {
160
+ validateEnvironmentKey(options.environment);
161
+ }
162
+
163
+ // Clear device tokens
164
+ const deviceTokensCleared = await clearDeviceTokens(options);
165
+
166
+ // Clear client tokens
167
+ const clientTokensCleared = await clearClientTokens(options);
168
+
169
+ // Summary
170
+ const totalCleared = deviceTokensCleared + clientTokensCleared;
171
+ if (totalCleared > 0) {
172
+ logger.log(chalk.green('\n✅ Successfully cleared tokens!'));
173
+ logger.log(chalk.gray(`Config file: ${configPath}\n`));
174
+ } else {
175
+ logger.log(chalk.yellow('\n⚠️ No tokens found to clear'));
176
+ logger.log(chalk.gray(`Config file: ${configPath}\n`));
177
+ }
178
+ }
179
+
180
+ module.exports = { handleLogout };
181
+
@@ -120,20 +120,12 @@ async function promptForEncryptionKey() {
120
120
  }
121
121
 
122
122
  /**
123
- * Handle secure command action
124
- * Sets encryption key and encrypts all secrets files
125
- *
123
+ * Get encryption key from options or prompt user
126
124
  * @async
127
- * @function handleSecure
128
125
  * @param {Object} options - Command options
129
- * @param {string} [options.secretsEncryption] - Encryption key (optional, will prompt if not provided)
130
- * @returns {Promise<void>} Resolves when encryption completes
131
- * @throws {Error} If encryption fails
126
+ * @returns {Promise<string>} Encryption key
132
127
  */
133
- async function handleSecure(options) {
134
- logger.log(chalk.blue('\n🔐 Securing secrets files...\n'));
135
-
136
- // Get or prompt for encryption key
128
+ async function getEncryptionKey(options) {
137
129
  let encryptionKey = options.secretsEncryption || options['secrets-encryption'];
138
130
  if (!encryptionKey) {
139
131
  // Check if key already exists in config
@@ -164,19 +156,17 @@ async function handleSecure(options) {
164
156
  await setSecretsEncryptionKey(encryptionKey);
165
157
  logger.log(chalk.green('✓ Encryption key saved to config.yaml'));
166
158
  }
159
+ return encryptionKey;
160
+ }
167
161
 
168
- // Find all secrets files
169
- const secretsFiles = await findSecretsFiles();
170
-
171
- if (secretsFiles.length === 0) {
172
- logger.log(chalk.yellow('⚠️ No secrets files found to encrypt'));
173
- logger.log(chalk.gray(' Create ~/.aifabrix/secrets.local.yaml or configure aifabrix-secrets in config.yaml'));
174
- return;
175
- }
176
-
177
- logger.log(chalk.gray(`Found ${secretsFiles.length} secrets file(s) to process:\n`));
178
-
179
- // Encrypt each file
162
+ /**
163
+ * Process and encrypt all secrets files
164
+ * @async
165
+ * @param {Array} secretsFiles - Array of secrets file objects
166
+ * @param {string} encryptionKey - Encryption key
167
+ * @returns {Promise<Object>} Object with totalEncrypted and totalValues
168
+ */
169
+ async function processSecretsFiles(secretsFiles, encryptionKey) {
180
170
  let totalEncrypted = 0;
181
171
  let totalValues = 0;
182
172
 
@@ -197,11 +187,56 @@ async function handleSecure(options) {
197
187
  }
198
188
  }
199
189
 
190
+ return { totalEncrypted, totalValues };
191
+ }
192
+
193
+ /**
194
+ * Display encryption summary
195
+ * @param {number} filesCount - Number of files processed
196
+ * @param {number} totalEncrypted - Number of values encrypted
197
+ * @param {number} totalValues - Total number of values
198
+ */
199
+ function displayEncryptionSummary(filesCount, totalEncrypted, totalValues) {
200
200
  logger.log(chalk.green('\n✅ Encryption complete!'));
201
- logger.log(chalk.gray(` Files processed: ${secretsFiles.length}`));
201
+ logger.log(chalk.gray(` Files processed: ${filesCount}`));
202
202
  logger.log(chalk.gray(` Values encrypted: ${totalEncrypted} of ${totalValues} total`));
203
203
  logger.log(chalk.gray(' Encryption key stored in: ~/.aifabrix/config.yaml\n'));
204
204
  }
205
205
 
206
+ /**
207
+ * Handle secure command action
208
+ * Sets encryption key and encrypts all secrets files
209
+ *
210
+ * @async
211
+ * @function handleSecure
212
+ * @param {Object} options - Command options
213
+ * @param {string} [options.secretsEncryption] - Encryption key (optional, will prompt if not provided)
214
+ * @returns {Promise<void>} Resolves when encryption completes
215
+ * @throws {Error} If encryption fails
216
+ */
217
+ async function handleSecure(options) {
218
+ logger.log(chalk.blue('\n🔐 Securing secrets files...\n'));
219
+
220
+ // Get or prompt for encryption key
221
+ const encryptionKey = await getEncryptionKey(options);
222
+
223
+ // Find all secrets files
224
+ const secretsFiles = await findSecretsFiles();
225
+
226
+ if (secretsFiles.length === 0) {
227
+ logger.log(chalk.yellow('⚠️ No secrets files found to encrypt'));
228
+ logger.log(chalk.gray(' Create ~/.aifabrix/secrets.local.yaml or configure aifabrix-secrets in config.yaml'));
229
+ return;
230
+ }
231
+
232
+ logger.log(chalk.gray(`Found ${secretsFiles.length} secrets file(s) to process:\n`));
233
+
234
+ // Process and encrypt all files
235
+ const { totalEncrypted, totalValues } = await processSecretsFiles(secretsFiles, encryptionKey);
236
+
237
+ // Display summary
238
+ displayEncryptionSummary(secretsFiles.length, totalEncrypted, totalValues);
239
+ }
240
+
206
241
  module.exports = { handleSecure };
207
242
 
package/lib/config.js CHANGED
@@ -47,6 +47,71 @@ function normalizeControllerUrl(url) {
47
47
  return normalized;
48
48
  }
49
49
 
50
+ /**
51
+ * Validate and normalize developer ID
52
+ * @param {*} developerId - Developer ID value (can be string, number, undefined, or null)
53
+ * @returns {string} Normalized developer ID as string
54
+ * @throws {Error} If developer ID is invalid
55
+ */
56
+ function validateAndNormalizeDeveloperId(developerId) {
57
+ const DEV_ID_DIGITS_REGEX = /^[0-9]+$/;
58
+
59
+ if (typeof developerId === 'undefined' || developerId === null) {
60
+ return '0';
61
+ }
62
+
63
+ if (typeof developerId === 'number') {
64
+ if (developerId < 0 || !Number.isFinite(developerId)) {
65
+ throw new Error(`Invalid developer-id value: "${developerId}". Must be a non-negative digit string.`);
66
+ }
67
+ return String(developerId);
68
+ }
69
+
70
+ if (typeof developerId === 'string') {
71
+ if (!DEV_ID_DIGITS_REGEX.test(developerId)) {
72
+ throw new Error(`Invalid developer-id value: "${developerId}". Must contain only digits 0-9.`);
73
+ }
74
+ return developerId;
75
+ }
76
+
77
+ throw new Error(`Invalid developer-id value type: ${typeof developerId}. Must be a non-negative digit string.`);
78
+ }
79
+
80
+ /**
81
+ * Ensure default config values exist
82
+ * @param {Object} config - Configuration object
83
+ * @returns {Object} Config with defaults applied
84
+ */
85
+ function applyConfigDefaults(config) {
86
+ // Ensure environment defaults to 'dev' if not set
87
+ if (typeof config.environment === 'undefined') {
88
+ config.environment = 'dev';
89
+ }
90
+ // Ensure environments object exists
91
+ if (typeof config.environments !== 'object' || config.environments === null) {
92
+ config.environments = {};
93
+ }
94
+ // Ensure device object exists at root level
95
+ if (typeof config.device !== 'object' || config.device === null) {
96
+ config.device = {};
97
+ }
98
+ return config;
99
+ }
100
+
101
+ /**
102
+ * Get default config when file doesn't exist
103
+ * @returns {Object} Default configuration
104
+ */
105
+ function getDefaultConfig() {
106
+ cachedDeveloperId = '0';
107
+ return {
108
+ 'developer-id': '0',
109
+ environment: 'dev',
110
+ environments: {},
111
+ device: {}
112
+ };
113
+ }
114
+
50
115
  async function getConfig() {
51
116
  try {
52
117
  const configContent = await fs.readFile(RUNTIME_CONFIG_FILE, 'utf8');
@@ -57,49 +122,18 @@ async function getConfig() {
57
122
  config = {};
58
123
  }
59
124
 
60
- // Ensure developer-id exists as a digit-only string (default "0") and validate
61
- const DEV_ID_DIGITS_REGEX = /^[0-9]+$/;
62
- if (typeof config['developer-id'] === 'undefined' || config['developer-id'] === null) {
63
- config['developer-id'] = '0';
64
- } else if (typeof config['developer-id'] === 'number') {
65
- // Convert numeric to string to preserve type consistency
66
- if (config['developer-id'] < 0 || !Number.isFinite(config['developer-id'])) {
67
- throw new Error(`Invalid developer-id value: "${config['developer-id']}". Must be a non-negative digit string.`);
68
- }
69
- config['developer-id'] = String(config['developer-id']);
70
- } else if (typeof config['developer-id'] === 'string') {
71
- if (!DEV_ID_DIGITS_REGEX.test(config['developer-id'])) {
72
- throw new Error(`Invalid developer-id value: "${config['developer-id']}". Must contain only digits 0-9.`);
73
- }
74
- } else {
75
- throw new Error(`Invalid developer-id value type: ${typeof config['developer-id']}. Must be a non-negative digit string.`);
76
- }
125
+ // Validate and normalize developer ID
126
+ config['developer-id'] = validateAndNormalizeDeveloperId(config['developer-id']);
127
+
128
+ // Apply defaults
129
+ config = applyConfigDefaults(config);
77
130
 
78
- // Ensure environment defaults to 'dev' if not set
79
- if (typeof config.environment === 'undefined') {
80
- config.environment = 'dev';
81
- }
82
- // Ensure environments object exists
83
- if (typeof config.environments !== 'object' || config.environments === null) {
84
- config.environments = {};
85
- }
86
- // Ensure device object exists at root level
87
- if (typeof config.device !== 'object' || config.device === null) {
88
- config.device = {};
89
- }
90
131
  // Cache developer ID as property for easy access (string, default "0")
91
132
  cachedDeveloperId = config['developer-id'] !== undefined ? config['developer-id'] : '0';
92
133
  return config;
93
134
  } catch (error) {
94
135
  if (error.code === 'ENOENT') {
95
- // Default developer ID is "0", default environment is 'dev'
96
- cachedDeveloperId = '0';
97
- return {
98
- 'developer-id': '0',
99
- environment: 'dev',
100
- environments: {},
101
- device: {}
102
- };
136
+ return getDefaultConfig();
103
137
  }
104
138
  throw new Error(`Failed to read config: ${error.message}`);
105
139
  }
@@ -344,14 +378,14 @@ Object.defineProperty(exportsObj, 'developerId', {
344
378
 
345
379
  // Token management functions - created after dependencies are defined
346
380
  const { createTokenManagementFunctions } = require('./utils/config-tokens');
347
- const tokenFunctions = createTokenManagementFunctions(
348
- getConfig,
349
- saveConfig,
350
- getSecretsEncryptionKey,
351
- encryptTokenValue,
352
- decryptTokenValue,
353
- require('./utils/token-encryption').isTokenEncrypted
354
- );
381
+ const tokenFunctions = createTokenManagementFunctions({
382
+ getConfigFn: getConfig,
383
+ saveConfigFn: saveConfig,
384
+ getSecretsEncryptionKeyFn: getSecretsEncryptionKey,
385
+ encryptTokenValueFn: encryptTokenValue,
386
+ decryptTokenValueFn: decryptTokenValue,
387
+ isTokenEncryptedFn: require('./utils/token-encryption').isTokenEncrypted
388
+ });
355
389
  Object.assign(exportsObj, tokenFunctions);
356
390
 
357
391
  // Path configuration functions - created after getConfig/saveConfig are defined