@aifabrix/builder 2.21.0 → 2.22.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/app-list.js CHANGED
@@ -9,10 +9,11 @@
9
9
  */
10
10
 
11
11
  const chalk = require('chalk');
12
- const { getConfig } = require('./config');
12
+ const { getConfig, normalizeControllerUrl } = require('./config');
13
13
  const { getOrRefreshDeviceToken } = require('./utils/token-manager');
14
14
  const { listEnvironmentApplications } = require('./api/environments.api');
15
15
  const { formatApiError } = require('./utils/api-error-handler');
16
+ const { formatAuthenticationError } = require('./utils/error-formatters/http-status-errors');
16
17
  const logger = require('./utils/logger');
17
18
 
18
19
  /**
@@ -82,48 +83,85 @@ function displayApplications(applications, environment) {
82
83
  * @async
83
84
  * @param {Object} options - Command options
84
85
  * @param {string} options.environment - Environment ID or key
86
+ * @param {string} [options.controller] - Controller URL (optional, uses configured controller if not provided)
85
87
  * @throws {Error} If listing fails
86
88
  */
87
89
  async function listApplications(options) {
88
90
  const config = await getConfig();
89
91
 
90
- // Try to get device token
91
- let controllerUrl = null;
92
+ // Get controller URL with priority: options.controller > device tokens
93
+ const controllerUrl = options.controller || null;
92
94
  let token = null;
95
+ let actualControllerUrl = null;
93
96
 
94
- if (config.device) {
95
- const deviceUrls = Object.keys(config.device);
96
- if (deviceUrls.length > 0) {
97
- controllerUrl = deviceUrls[0];
98
- const deviceToken = await getOrRefreshDeviceToken(controllerUrl);
97
+ // If controller URL provided, try to get device token
98
+ if (controllerUrl) {
99
+ try {
100
+ const normalizedUrl = normalizeControllerUrl(controllerUrl);
101
+ const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
99
102
  if (deviceToken && deviceToken.token) {
100
103
  token = deviceToken.token;
101
- controllerUrl = deviceToken.controller;
104
+ actualControllerUrl = deviceToken.controller || normalizedUrl;
105
+ }
106
+ } catch (error) {
107
+ // Show which controller URL failed
108
+ logger.error(chalk.red(`❌ Failed to authenticate with controller: ${controllerUrl}`));
109
+ logger.error(chalk.gray(`Error: ${error.message}`));
110
+ process.exit(1);
111
+ }
112
+ }
113
+
114
+ // If no token yet, try to find any device token in config
115
+ if (!token && config.device) {
116
+ const deviceUrls = Object.keys(config.device);
117
+ if (deviceUrls.length > 0) {
118
+ for (const storedUrl of deviceUrls) {
119
+ try {
120
+ const normalizedStoredUrl = normalizeControllerUrl(storedUrl);
121
+ const deviceToken = await getOrRefreshDeviceToken(normalizedStoredUrl);
122
+ if (deviceToken && deviceToken.token) {
123
+ token = deviceToken.token;
124
+ actualControllerUrl = deviceToken.controller || normalizedStoredUrl;
125
+ break;
126
+ }
127
+ } catch (error) {
128
+ // Continue to next URL
129
+ }
102
130
  }
103
131
  }
104
132
  }
105
133
 
106
- if (!token || !controllerUrl) {
107
- logger.error(chalk.red('❌ Not logged in. Run: aifabrix login'));
108
- logger.error(chalk.gray(' Use device code flow: aifabrix login --method device --controller <url>'));
134
+ if (!token || !actualControllerUrl) {
135
+ const formattedError = formatAuthenticationError({
136
+ controllerUrl: controllerUrl || undefined,
137
+ message: 'No valid authentication found'
138
+ });
139
+ logger.error(formattedError);
109
140
  process.exit(1);
110
141
  }
111
142
 
112
143
  // Use centralized API client
113
144
  const authConfig = { type: 'bearer', token: token };
114
- const response = await listEnvironmentApplications(controllerUrl, options.environment, authConfig);
145
+ try {
146
+ const response = await listEnvironmentApplications(actualControllerUrl, options.environment, authConfig);
147
+
148
+ if (!response.success || !response.data) {
149
+ const formattedError = response.formattedError || formatApiError(response, actualControllerUrl);
150
+ logger.error(formattedError);
151
+ logger.error(chalk.gray(`\nController URL: ${actualControllerUrl}`));
152
+ // Log full response for debugging
153
+ logger.error(chalk.gray('\nFull response for debugging:'));
154
+ logger.error(chalk.gray(JSON.stringify(response, null, 2)));
155
+ process.exit(1);
156
+ }
115
157
 
116
- if (!response.success || !response.data) {
117
- const formattedError = response.formattedError || formatApiError(response);
118
- logger.error(formattedError);
119
- // Log full response for debugging
120
- logger.error(chalk.gray('\nFull response for debugging:'));
121
- logger.error(chalk.gray(JSON.stringify(response, null, 2)));
158
+ const applications = extractApplications(response);
159
+ displayApplications(applications, options.environment);
160
+ } catch (error) {
161
+ logger.error(chalk.red(`❌ Failed to list applications from controller: ${actualControllerUrl}`));
162
+ logger.error(chalk.gray(`Error: ${error.message}`));
122
163
  process.exit(1);
123
164
  }
124
-
125
- const applications = extractApplications(response);
126
- displayApplications(applications, options.environment);
127
165
  }
128
166
 
129
167
  module.exports = { listApplications };
@@ -108,6 +108,7 @@ async function saveLocalCredentials(responseData, apiUrl) {
108
108
  * @param {string} appKey - Application key
109
109
  * @param {Object} options - Registration options
110
110
  * @param {string} options.environment - Environment ID or key
111
+ * @param {string} [options.controller] - Controller URL (overrides variables.yaml)
111
112
  * @param {number} [options.port] - Application port
112
113
  * @param {string} [options.name] - Override display name
113
114
  * @param {string} [options.description] - Override description
@@ -126,8 +127,8 @@ async function registerApplication(appKey, options) {
126
127
  const appConfig = await extractAppConfiguration(finalVariables, appKey, options);
127
128
  await validateAppRegistrationData(appConfig, appKey);
128
129
 
129
- // Authenticate and get API configuration
130
- const controllerUrl = finalVariables?.deployment?.controllerUrl;
130
+ // Get controller URL with priority: options.controller > variables.yaml > device tokens
131
+ const controllerUrl = options.controller || finalVariables?.deployment?.controllerUrl;
131
132
  const authConfig = await checkAuthentication(controllerUrl, options.environment);
132
133
  const environment = registerApplicationSchema.environmentId(options.environment);
133
134
 
@@ -9,10 +9,11 @@
9
9
  */
10
10
 
11
11
  const chalk = require('chalk');
12
- const { getConfig } = require('./config');
12
+ const { getConfig, normalizeControllerUrl } = require('./config');
13
13
  const { getOrRefreshDeviceToken } = require('./utils/token-manager');
14
14
  const { rotateApplicationSecret } = require('./api/applications.api');
15
15
  const { formatApiError } = require('./utils/api-error-handler');
16
+ const { formatAuthenticationError } = require('./utils/error-formatters/http-status-errors');
16
17
  const logger = require('./utils/logger');
17
18
  const { saveLocalSecret, isLocalhost } = require('./utils/local-secrets');
18
19
  const { updateEnvTemplate } = require('./utils/env-template');
@@ -83,6 +84,7 @@ function displayRotationResults(appKey, environment, credentials, apiUrl, messag
83
84
  * @param {string} appKey - Application key
84
85
  * @param {Object} options - Command options
85
86
  * @param {string} options.environment - Environment ID or key
87
+ * @param {string} [options.controller] - Controller URL (optional, uses configured controller if not provided)
86
88
  * @throws {Error} If rotation fails
87
89
  */
88
90
  async function rotateSecret(appKey, options) {
@@ -90,25 +92,54 @@ async function rotateSecret(appKey, options) {
90
92
 
91
93
  const config = await getConfig();
92
94
 
93
- // Try to get device token
94
- let controllerUrl = null;
95
+ // Get controller URL with priority: options.controller > device tokens
96
+ const controllerUrl = options.controller || null;
95
97
  let token = null;
98
+ let actualControllerUrl = null;
96
99
 
97
- if (config.device) {
98
- const deviceUrls = Object.keys(config.device);
99
- if (deviceUrls.length > 0) {
100
- controllerUrl = deviceUrls[0];
101
- const deviceToken = await getOrRefreshDeviceToken(controllerUrl);
100
+ // If controller URL provided, try to get device token
101
+ if (controllerUrl) {
102
+ try {
103
+ const normalizedUrl = normalizeControllerUrl(controllerUrl);
104
+ const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
102
105
  if (deviceToken && deviceToken.token) {
103
106
  token = deviceToken.token;
104
- controllerUrl = deviceToken.controller;
107
+ actualControllerUrl = deviceToken.controller || normalizedUrl;
108
+ }
109
+ } catch (error) {
110
+ // Show which controller URL failed
111
+ logger.error(chalk.red(`❌ Failed to authenticate with controller: ${controllerUrl}`));
112
+ logger.error(chalk.gray(`Error: ${error.message}`));
113
+ process.exit(1);
114
+ }
115
+ }
116
+
117
+ // If no token yet, try to find any device token in config
118
+ if (!token && config.device) {
119
+ const deviceUrls = Object.keys(config.device);
120
+ if (deviceUrls.length > 0) {
121
+ for (const storedUrl of deviceUrls) {
122
+ try {
123
+ const normalizedStoredUrl = normalizeControllerUrl(storedUrl);
124
+ const deviceToken = await getOrRefreshDeviceToken(normalizedStoredUrl);
125
+ if (deviceToken && deviceToken.token) {
126
+ token = deviceToken.token;
127
+ actualControllerUrl = deviceToken.controller || normalizedStoredUrl;
128
+ break;
129
+ }
130
+ } catch (error) {
131
+ // Continue to next URL
132
+ }
105
133
  }
106
134
  }
107
135
  }
108
136
 
109
- if (!token || !controllerUrl) {
110
- logger.error(chalk.red('❌ Not logged in. Run: aifabrix login'));
111
- logger.error(chalk.gray(' Use device code flow: aifabrix login --method device --controller <url>'));
137
+ if (!token || !actualControllerUrl) {
138
+ const formattedError = formatAuthenticationError({
139
+ controllerUrl: controllerUrl || undefined,
140
+ message: 'No valid authentication found'
141
+ });
142
+ logger.error(formattedError);
112
143
  process.exit(1);
113
144
  }
114
145
 
@@ -117,51 +148,58 @@ async function rotateSecret(appKey, options) {
117
148
 
118
149
  // Use centralized API client
119
150
  const authConfig = { type: 'bearer', token: token };
120
- const response = await rotateApplicationSecret(controllerUrl, options.environment, appKey, authConfig);
151
+ try {
152
+ const response = await rotateApplicationSecret(actualControllerUrl, options.environment, appKey, authConfig);
121
153
 
122
- if (!response.success) {
123
- const formattedError = response.formattedError || formatApiError(response);
124
- logger.error(formattedError);
125
- process.exit(1);
126
- }
154
+ if (!response.success) {
155
+ const formattedError = response.formattedError || formatApiError(response, actualControllerUrl);
156
+ logger.error(formattedError);
157
+ logger.error(chalk.gray(`\nController URL: ${actualControllerUrl}`));
158
+ process.exit(1);
159
+ }
127
160
 
128
- // Validate response structure
129
- validateResponse(response);
161
+ // Validate response structure
162
+ validateResponse(response);
130
163
 
131
- const credentials = response.data.credentials;
132
- const message = response.data.message;
164
+ const credentials = response.data.credentials;
165
+ const message = response.data.message;
133
166
 
134
- // Save credentials to local secrets (always save when rotating)
135
- const clientIdKey = `${appKey}-client-idKeyVault`;
136
- const clientSecretKey = `${appKey}-client-secretKeyVault`;
167
+ // Save credentials to local secrets (always save when rotating)
168
+ const clientIdKey = `${appKey}-client-idKeyVault`;
169
+ const clientSecretKey = `${appKey}-client-secretKeyVault`;
137
170
 
138
- try {
139
- await saveLocalSecret(clientIdKey, credentials.clientId);
140
- await saveLocalSecret(clientSecretKey, credentials.clientSecret);
141
-
142
- // Update env.template if localhost
143
- if (isLocalhost(controllerUrl)) {
144
- await updateEnvTemplate(appKey, clientIdKey, clientSecretKey, controllerUrl);
145
-
146
- // Regenerate .env file with updated credentials
147
- try {
148
- await generateEnvFile(appKey, null, 'local');
149
- logger.log(chalk.green('✓ .env file updated with new credentials'));
150
- } catch (error) {
151
- logger.warn(chalk.yellow(`⚠️ Could not regenerate .env file: ${error.message}`));
152
- }
171
+ try {
172
+ await saveLocalSecret(clientIdKey, credentials.clientId);
173
+ await saveLocalSecret(clientSecretKey, credentials.clientSecret);
174
+
175
+ // Update env.template if localhost
176
+ if (isLocalhost(actualControllerUrl)) {
177
+ await updateEnvTemplate(appKey, clientIdKey, clientSecretKey, actualControllerUrl);
153
178
 
154
- logger.log(chalk.green('\n✓ Credentials saved to ~/.aifabrix/secrets.local.yaml'));
155
- logger.log(chalk.green('✓ env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
156
- } else {
157
- logger.log(chalk.green('\nCredentials saved to ~/.aifabrix/secrets.local.yaml\n'));
179
+ // Regenerate .env file with updated credentials
180
+ try {
181
+ await generateEnvFile(appKey, null, 'local');
182
+ logger.log(chalk.green('✓ .env file updated with new credentials'));
183
+ } catch (error) {
184
+ logger.warn(chalk.yellow(`⚠️ Could not regenerate .env file: ${error.message}`));
185
+ }
186
+
187
+ logger.log(chalk.green('\n✓ Credentials saved to ~/.aifabrix/secrets.local.yaml'));
188
+ logger.log(chalk.green('✓ env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
189
+ } else {
190
+ logger.log(chalk.green('\n✓ Credentials saved to ~/.aifabrix/secrets.local.yaml\n'));
191
+ }
192
+ } catch (error) {
193
+ logger.warn(chalk.yellow(`⚠️ Could not save credentials locally: ${error.message}`));
158
194
  }
195
+
196
+ // Display results
197
+ displayRotationResults(appKey, options.environment, credentials, actualControllerUrl, message);
159
198
  } catch (error) {
160
- logger.warn(chalk.yellow(`⚠️ Could not save credentials locally: ${error.message}`));
199
+ logger.error(chalk.red(`❌ Failed to rotate secret via controller: ${actualControllerUrl}`));
200
+ logger.error(chalk.gray(`Error: ${error.message}`));
201
+ process.exit(1);
161
202
  }
162
-
163
- // Display results
164
- displayRotationResults(appKey, options.environment, credentials, controllerUrl, message);
165
203
  }
166
204
 
167
205
  module.exports = { rotateSecret };
package/lib/cli.js CHANGED
@@ -340,7 +340,33 @@ function setupCommands(program) {
340
340
  }
341
341
  });
342
342
 
343
- program.command('app split-json <app-name>')
343
+ program.command('json <app>')
344
+ .description('Generate deployment JSON (aifabrix-deploy.json for normal apps, application-schema.json for external systems)')
345
+ .action(async(appName) => {
346
+ try {
347
+ const result = await generator.generateDeployJsonWithValidation(appName);
348
+ if (result.success) {
349
+ const fileName = result.path.includes('application-schema.json') ? 'application-schema.json' : 'deployment JSON';
350
+ logger.log(`✓ Generated ${fileName}: ${result.path}`);
351
+
352
+ if (result.validation.warnings && result.validation.warnings.length > 0) {
353
+ logger.log('\n⚠️ Warnings:');
354
+ result.validation.warnings.forEach(warning => logger.log(` • ${warning}`));
355
+ }
356
+ } else {
357
+ logger.log('❌ Validation failed:');
358
+ if (result.validation.errors && result.validation.errors.length > 0) {
359
+ result.validation.errors.forEach(error => logger.log(` • ${error}`));
360
+ }
361
+ process.exit(1);
362
+ }
363
+ } catch (error) {
364
+ handleCommandError(error, 'json');
365
+ process.exit(1);
366
+ }
367
+ });
368
+
369
+ program.command('split-json <app>')
344
370
  .description('Split deployment JSON into component files (env.template, variables.yaml, rbac.yml, README.md)')
345
371
  .option('-o, --output <dir>', 'Output directory for component files (defaults to same directory as JSON)')
346
372
  .action(async(appName, options) => {
@@ -365,33 +391,7 @@ function setupCommands(program) {
365
391
  }
366
392
  logger.log(` • README.md: ${result.readme}`);
367
393
  } catch (error) {
368
- handleCommandError(error, 'app split-json');
369
- process.exit(1);
370
- }
371
- });
372
-
373
- program.command('json <app>')
374
- .description('Generate deployment JSON (aifabrix-deploy.json for normal apps, application-schema.json for external systems)')
375
- .action(async(appName) => {
376
- try {
377
- const result = await generator.generateDeployJsonWithValidation(appName);
378
- if (result.success) {
379
- const fileName = result.path.includes('application-schema.json') ? 'application-schema.json' : 'deployment JSON';
380
- logger.log(`✓ Generated ${fileName}: ${result.path}`);
381
-
382
- if (result.validation.warnings && result.validation.warnings.length > 0) {
383
- logger.log('\n⚠️ Warnings:');
384
- result.validation.warnings.forEach(warning => logger.log(` • ${warning}`));
385
- }
386
- } else {
387
- logger.log('❌ Validation failed:');
388
- if (result.validation.errors && result.validation.errors.length > 0) {
389
- result.validation.errors.forEach(error => logger.log(` • ${error}`));
390
- }
391
- process.exit(1);
392
- }
393
- } catch (error) {
394
- handleCommandError(error, 'json');
394
+ handleCommandError(error, 'split-json');
395
395
  process.exit(1);
396
396
  }
397
397
  });
@@ -29,6 +29,7 @@ function setupAppCommands(program) {
29
29
  .command('register <appKey>')
30
30
  .description('Register application and get pipeline credentials')
31
31
  .requiredOption('-e, --environment <env>', 'Environment ID or key')
32
+ .option('-c, --controller <url>', 'Controller URL (overrides variables.yaml)')
32
33
  .option('-p, --port <port>', 'Application port (default: from variables.yaml)')
33
34
  .option('-n, --name <name>', 'Override display name')
34
35
  .option('-d, --description <desc>', 'Override description')
@@ -46,6 +47,7 @@ function setupAppCommands(program) {
46
47
  .command('list')
47
48
  .description('List applications')
48
49
  .requiredOption('-e, --environment <env>', 'Environment ID or key')
50
+ .option('-c, --controller <url>', 'Controller URL (optional, uses configured controller if not provided)')
49
51
  .action(async(options) => {
50
52
  try {
51
53
  await listApplications(options);
@@ -60,6 +62,7 @@ function setupAppCommands(program) {
60
62
  .command('rotate-secret <appKey>')
61
63
  .description('Rotate pipeline ClientSecret for an application')
62
64
  .requiredOption('-e, --environment <env>', 'Environment ID or key')
65
+ .option('-c, --controller <url>', 'Controller URL (optional, uses configured controller if not provided)')
63
66
  .action(async(appKey, options) => {
64
67
  try {
65
68
  await rotateSecret(appKey, options);
package/lib/config.js CHANGED
@@ -27,6 +27,26 @@ const RUNTIME_CONFIG_FILE = path.join(RUNTIME_CONFIG_DIR, 'config.yaml');
27
27
  // Cache for developer ID - loaded when getConfig() is first called
28
28
  let cachedDeveloperId = null;
29
29
 
30
+ /**
31
+ * Normalize controller URL for consistent storage and lookup
32
+ * Removes trailing slashes and normalizes the URL format
33
+ * @param {string} url - Controller URL to normalize
34
+ * @returns {string} Normalized controller URL
35
+ */
36
+ function normalizeControllerUrl(url) {
37
+ if (!url || typeof url !== 'string') {
38
+ return url;
39
+ }
40
+ // Remove trailing slashes
41
+ let normalized = url.trim().replace(/\/+$/, '');
42
+ // Ensure it starts with http:// or https://
43
+ if (!normalized.match(/^https?:\/\//)) {
44
+ // If it doesn't start with protocol, assume http://
45
+ normalized = `http://${normalized}`;
46
+ }
47
+ return normalized;
48
+ }
49
+
30
50
  async function getConfig() {
31
51
  try {
32
52
  const configContent = await fs.readFile(RUNTIME_CONFIG_FILE, 'utf8');
@@ -231,131 +251,7 @@ async function decryptTokenValue(value) {
231
251
  return value;
232
252
  }
233
253
  }
234
- /**
235
- * Get device token for controller
236
- * @param {string} controllerUrl - Controller URL
237
- * @returns {Promise<{controller: string, token: string, refreshToken: string, expiresAt: string}|null>} Device token info or null
238
- */
239
- async function getDeviceToken(controllerUrl) {
240
- const config = await getConfig();
241
- if (!config.device || !config.device[controllerUrl]) return null;
242
- const deviceToken = config.device[controllerUrl];
243
-
244
- // Migration: If tokens are plain text and encryption key exists, encrypt them first
245
- const encryptionKey = await getSecretsEncryptionKey();
246
- if (encryptionKey) {
247
- let needsSave = false;
248
-
249
- if (deviceToken.token && !isTokenEncrypted(deviceToken.token)) {
250
- // Token is plain text, encrypt it
251
- deviceToken.token = await encryptTokenValue(deviceToken.token);
252
- needsSave = true;
253
- }
254
-
255
- if (deviceToken.refreshToken && !isTokenEncrypted(deviceToken.refreshToken)) {
256
- // Refresh token is plain text, encrypt it
257
- deviceToken.refreshToken = await encryptTokenValue(deviceToken.refreshToken);
258
- needsSave = true;
259
- }
260
-
261
- if (needsSave) {
262
- // Save encrypted tokens back to config
263
- await saveConfig(config);
264
- }
265
- }
266
- // Decrypt tokens if encrypted (for return value)
267
- const token = deviceToken.token ? await decryptTokenValue(deviceToken.token) : undefined;
268
- const refreshToken = deviceToken.refreshToken ? await decryptTokenValue(deviceToken.refreshToken) : null;
269
-
270
- return {
271
- controller: controllerUrl,
272
- token: token,
273
- refreshToken: refreshToken,
274
- expiresAt: deviceToken.expiresAt
275
- };
276
- }
277
-
278
- /**
279
- * Get client token for environment and app
280
- * @param {string} environment - Environment key
281
- * @param {string} appName - Application name
282
- * @returns {Promise<{controller: string, token: string, expiresAt: string}|null>} Client token info or null
283
- */
284
- async function getClientToken(environment, appName) {
285
- const config = await getConfig();
286
- if (!config.environments || !config.environments[environment]) return null;
287
- if (!config.environments[environment].clients || !config.environments[environment].clients[appName]) return null;
288
-
289
- const clientToken = config.environments[environment].clients[appName];
290
-
291
- // Migration: If token is plain text and encryption key exists, encrypt it first
292
- const encryptionKey = await getSecretsEncryptionKey();
293
- if (encryptionKey && clientToken.token && !isTokenEncrypted(clientToken.token)) {
294
- // Token is plain text, encrypt it
295
- clientToken.token = await encryptTokenValue(clientToken.token);
296
- // Save encrypted token back to config
297
- await saveConfig(config);
298
- }
299
-
300
- // Decrypt token if encrypted (for return value)
301
- const token = await decryptTokenValue(clientToken.token);
302
-
303
- return {
304
- controller: clientToken.controller,
305
- token: token,
306
- expiresAt: clientToken.expiresAt
307
- };
308
- }
309
-
310
- /**
311
- * Save device token for controller (root level)
312
- * @param {string} controllerUrl - Controller URL (used as key)
313
- * @param {string} token - Device access token
314
- * @param {string} refreshToken - Refresh token for token renewal
315
- * @param {string} expiresAt - ISO timestamp string
316
- * @returns {Promise<void>}
317
- */
318
- async function saveDeviceToken(controllerUrl, token, refreshToken, expiresAt) {
319
- const config = await getConfig();
320
- if (!config.device) config.device = {};
321
-
322
- // Encrypt tokens before saving
323
- const encryptedToken = await encryptTokenValue(token);
324
- const encryptedRefreshToken = refreshToken ? await encryptTokenValue(refreshToken) : null;
325
-
326
- config.device[controllerUrl] = {
327
- token: encryptedToken,
328
- refreshToken: encryptedRefreshToken,
329
- expiresAt
330
- };
331
- await saveConfig(config);
332
- }
333
-
334
- /**
335
- * Save client token for environment and app
336
- * @param {string} environment - Environment key
337
- * @param {string} appName - Application name
338
- * @param {string} controllerUrl - Controller URL
339
- * @param {string} token - Client token
340
- * @param {string} expiresAt - ISO timestamp string
341
- * @returns {Promise<void>}
342
- */
343
- async function saveClientToken(environment, appName, controllerUrl, token, expiresAt) {
344
- const config = await getConfig();
345
- if (!config.environments) config.environments = {};
346
- if (!config.environments[environment]) config.environments[environment] = { clients: {} };
347
- if (!config.environments[environment].clients) config.environments[environment].clients = {};
348
-
349
- // Encrypt token before saving
350
- const encryptedToken = await encryptTokenValue(token);
351
-
352
- config.environments[environment].clients[appName] = {
353
- controller: controllerUrl,
354
- token: encryptedToken,
355
- expiresAt
356
- };
357
- await saveConfig(config);
358
- }
254
+ // Token management functions moved to lib/utils/config-tokens.js to reduce file size
359
255
 
360
256
  /**
361
257
  * Initialize and load developer ID
@@ -412,44 +308,6 @@ async function setSecretsPath(secretsPath) {
412
308
  await saveConfig(config);
413
309
  }
414
310
 
415
- async function getPathConfig(key) {
416
- const config = await getConfig();
417
- return config[key] || null;
418
- }
419
-
420
- async function setPathConfig(key, value, errorMsg) {
421
- if (!value || typeof value !== 'string') {
422
- throw new Error(errorMsg);
423
- }
424
- const config = await getConfig();
425
- config[key] = value;
426
- await saveConfig(config);
427
- }
428
-
429
- async function getAifabrixHomeOverride() {
430
- return getPathConfig('aifabrix-home');
431
- }
432
-
433
- async function setAifabrixHomeOverride(homePath) {
434
- await setPathConfig('aifabrix-home', homePath, 'Home path is required and must be a string');
435
- }
436
-
437
- async function getAifabrixSecretsPath() {
438
- return getPathConfig('aifabrix-secrets');
439
- }
440
-
441
- async function setAifabrixSecretsPath(secretsPath) {
442
- await setPathConfig('aifabrix-secrets', secretsPath, 'Secrets path is required and must be a string');
443
- }
444
-
445
- async function getAifabrixEnvConfigPath() {
446
- return getPathConfig('aifabrix-env-config');
447
- }
448
-
449
- async function setAifabrixEnvConfigPath(envConfigPath) {
450
- await setPathConfig('aifabrix-env-config', envConfigPath, 'Env config path is required and must be a string');
451
- }
452
-
453
311
  // Create exports object
454
312
  const exportsObj = {
455
313
  getConfig,
@@ -462,22 +320,13 @@ const exportsObj = {
462
320
  setCurrentEnvironment,
463
321
  isTokenExpired,
464
322
  shouldRefreshToken,
465
- getDeviceToken,
466
- getClientToken,
467
- saveDeviceToken,
468
- saveClientToken,
469
323
  encryptTokenValue,
470
324
  decryptTokenValue,
471
325
  getSecretsEncryptionKey,
472
326
  setSecretsEncryptionKey,
473
327
  getSecretsPath,
474
328
  setSecretsPath,
475
- getAifabrixHomeOverride,
476
- setAifabrixHomeOverride,
477
- getAifabrixSecretsPath,
478
- setAifabrixSecretsPath,
479
- getAifabrixEnvConfigPath,
480
- setAifabrixEnvConfigPath,
329
+ normalizeControllerUrl,
481
330
  CONFIG_DIR,
482
331
  CONFIG_FILE
483
332
  };
@@ -492,4 +341,22 @@ Object.defineProperty(exportsObj, 'developerId', {
492
341
  enumerable: true,
493
342
  configurable: true
494
343
  });
344
+
345
+ // Token management functions - created after dependencies are defined
346
+ 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
+ );
355
+ Object.assign(exportsObj, tokenFunctions);
356
+
357
+ // Path configuration functions - created after getConfig/saveConfig are defined
358
+ const { createPathConfigFunctions } = require('./utils/config-paths');
359
+ const pathConfigFunctions = createPathConfigFunctions(getConfig, saveConfig);
360
+ Object.assign(exportsObj, pathConfigFunctions);
361
+
495
362
  module.exports = exportsObj;
@@ -40,7 +40,9 @@ async function generateExternalSystemTemplate(appPath, systemKey, config) {
40
40
  systemDisplayName: config.systemDisplayName || systemKey.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
41
41
  systemDescription: config.systemDescription || `External system integration for ${systemKey}`,
42
42
  systemType: config.systemType || 'openapi',
43
- authType: config.authType || 'apikey'
43
+ authType: config.authType || 'apikey',
44
+ roles: config.roles || null,
45
+ permissions: config.permissions || null
44
46
  };
45
47
 
46
48
  const rendered = template(context);