@aifabrix/builder 2.31.0 → 2.31.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.
@@ -61,20 +61,83 @@ function validateEnvironment(environment) {
61
61
  }
62
62
  }
63
63
 
64
+ /**
65
+ * Validate credentials object structure
66
+ * @param {Object} credentials - Credentials object to validate
67
+ * @returns {boolean} True if credentials are valid
68
+ */
69
+ function isValidCredentials(credentials) {
70
+ return credentials &&
71
+ typeof credentials === 'object' &&
72
+ typeof credentials.clientId === 'string' &&
73
+ typeof credentials.clientSecret === 'string';
74
+ }
75
+
76
+ /**
77
+ * Extract credentials from API response
78
+ * Handles multiple response formats:
79
+ * 1. Direct format: { success: true, data: { credentials: {...} } }
80
+ * 2. Wrapped format: { success: true, data: { success: true, data: { credentials: {...} } } }
81
+ * @param {Object} response - API response from centralized API client
82
+ * @returns {Object|null} Object with credentials and message, or null if not found
83
+ */
84
+ function extractCredentials(response) {
85
+ // Note: response.data is already validated in validateResponse
86
+ const apiResponse = response.data;
87
+
88
+ // Try wrapped format first: response.data.data.credentials
89
+ if (apiResponse.data && apiResponse.data.credentials) {
90
+ const credentials = apiResponse.data.credentials;
91
+ if (isValidCredentials(credentials)) {
92
+ return {
93
+ credentials: credentials,
94
+ message: apiResponse.data.message || apiResponse.message
95
+ };
96
+ }
97
+ }
98
+
99
+ // Try direct format: response.data.credentials
100
+ if (apiResponse.credentials) {
101
+ const credentials = apiResponse.credentials;
102
+ if (isValidCredentials(credentials)) {
103
+ return {
104
+ credentials: credentials,
105
+ message: apiResponse.message
106
+ };
107
+ }
108
+ }
109
+
110
+ return null;
111
+ }
112
+
64
113
  /**
65
114
  * Validate API response structure
66
115
  * @param {Object} response - API response
116
+ * @returns {Object} Object with credentials and message
67
117
  * @throws {Error} If response structure is invalid
68
118
  */
69
119
  function validateResponse(response) {
70
120
  if (!response.data || typeof response.data !== 'object') {
121
+ logger.error(chalk.red('❌ Invalid response: missing data'));
122
+ logger.error(chalk.gray('\nAPI response type:'), typeof response.data);
123
+ logger.error(chalk.gray('API response:'), JSON.stringify(response.data, null, 2));
124
+ logger.error(chalk.gray('\nFull response for debugging:'));
125
+ logger.error(chalk.gray(JSON.stringify(response, null, 2)));
71
126
  throw new Error('Invalid response: missing data');
72
127
  }
73
128
 
74
- const credentials = response.data.credentials;
75
- if (!credentials || typeof credentials !== 'object' || typeof credentials.clientId !== 'string' || typeof credentials.clientSecret !== 'string') {
129
+ const result = extractCredentials(response);
130
+
131
+ if (!result) {
132
+ logger.error(chalk.red('❌ Invalid response: missing or invalid credentials'));
133
+ logger.error(chalk.gray('\nAPI response type:'), typeof response.data);
134
+ logger.error(chalk.gray('API response:'), JSON.stringify(response.data, null, 2));
135
+ logger.error(chalk.gray('\nFull response for debugging:'));
136
+ logger.error(chalk.gray(JSON.stringify(response, null, 2)));
76
137
  throw new Error('Invalid response: missing or invalid credentials');
77
138
  }
139
+
140
+ return result;
78
141
  }
79
142
 
80
143
  /**
@@ -108,6 +171,47 @@ function displayRotationResults(appKey, environment, credentials, apiUrl, messag
108
171
  }
109
172
  }
110
173
 
174
+ /**
175
+ * Get device token from provided controller URL
176
+ * @async
177
+ * @param {string} controllerUrl - Controller URL
178
+ * @returns {Promise<Object|null>} Object with token and controllerUrl, or null if failed
179
+ */
180
+ async function getTokenFromUrl(controllerUrl) {
181
+ try {
182
+ const normalizedUrl = normalizeControllerUrl(controllerUrl);
183
+ const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
184
+ if (deviceToken && deviceToken.token) {
185
+ return {
186
+ token: deviceToken.token,
187
+ controllerUrl: deviceToken.controller || normalizedUrl
188
+ };
189
+ }
190
+ } catch (error) {
191
+ logger.error(chalk.red(`❌ Failed to authenticate with controller: ${controllerUrl}`));
192
+ logger.error(chalk.gray(`Error: ${error.message}`));
193
+ process.exit(1);
194
+ }
195
+ return null;
196
+ }
197
+
198
+ /**
199
+ * Validate and handle missing authentication token
200
+ * @param {string|null} token - Authentication token
201
+ * @param {string|null} controllerUrl - Controller URL
202
+ * @param {string} [providedUrl] - Original provided URL for error context
203
+ */
204
+ function validateAuthToken(token, controllerUrl, providedUrl) {
205
+ if (!token || !controllerUrl) {
206
+ const formattedError = formatAuthenticationError({
207
+ controllerUrl: providedUrl || undefined,
208
+ message: 'No valid authentication found'
209
+ });
210
+ logger.error(formattedError);
211
+ process.exit(1);
212
+ }
213
+ }
214
+
111
215
  /**
112
216
  * Get authentication token for rotation
113
217
  * @async
@@ -122,17 +226,10 @@ async function getRotationAuthToken(controllerUrl, config) {
122
226
 
123
227
  // If controller URL provided, try to get device token
124
228
  if (controllerUrl) {
125
- try {
126
- const normalizedUrl = normalizeControllerUrl(controllerUrl);
127
- const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
128
- if (deviceToken && deviceToken.token) {
129
- token = deviceToken.token;
130
- actualControllerUrl = deviceToken.controller || normalizedUrl;
131
- }
132
- } catch (error) {
133
- logger.error(chalk.red(`❌ Failed to authenticate with controller: ${controllerUrl}`));
134
- logger.error(chalk.gray(`Error: ${error.message}`));
135
- process.exit(1);
229
+ const tokenResult = await getTokenFromUrl(controllerUrl);
230
+ if (tokenResult) {
231
+ token = tokenResult.token;
232
+ actualControllerUrl = tokenResult.controllerUrl;
136
233
  }
137
234
  }
138
235
 
@@ -145,15 +242,7 @@ async function getRotationAuthToken(controllerUrl, config) {
145
242
  }
146
243
  }
147
244
 
148
- if (!token || !actualControllerUrl) {
149
- const formattedError = formatAuthenticationError({
150
- controllerUrl: controllerUrl || undefined,
151
- message: 'No valid authentication found'
152
- });
153
- logger.error(formattedError);
154
- process.exit(1);
155
- }
156
-
245
+ validateAuthToken(token, actualControllerUrl, controllerUrl);
157
246
  return { token, actualControllerUrl };
158
247
  }
159
248
 
@@ -228,11 +317,8 @@ async function rotateSecret(appKey, options) {
228
317
  process.exit(1);
229
318
  }
230
319
 
231
- // Validate response structure
232
- validateResponse(response);
233
-
234
- const credentials = response.data.credentials;
235
- const message = response.data.message;
320
+ // Validate response structure and extract credentials
321
+ const { credentials, message } = validateResponse(response);
236
322
 
237
323
  // Save credentials locally
238
324
  await saveCredentialsLocally(appKey, credentials, actualControllerUrl);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.31.0",
3
+ "version": "2.31.1",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -21,7 +21,9 @@
21
21
  "pack": "npm run build && npm pack",
22
22
  "validate": "npm run build",
23
23
  "prepublishOnly": "npm run validate",
24
- "precommit": "npm run lint:fix && npm run test"
24
+ "precommit": "npm run lint:fix && npm run test",
25
+ "install:local": "node scripts/install-local.js",
26
+ "uninstall:local": "node scripts/install-local.js uninstall"
25
27
  },
26
28
  "keywords": [
27
29
  "aifabrix",
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+
4
+ /**
5
+ * Install local package globally using npm link or pnpm link
6
+ * Automatically detects which package manager is being used
7
+ *
8
+ * @fileoverview Local installation script for @aifabrix/builder
9
+ * @author AI Fabrix Team
10
+ * @version 2.0.0
11
+ */
12
+
13
+ const { execSync } = require('child_process');
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ /**
18
+ * Detect which package manager is being used (pnpm or npm)
19
+ * @returns {string} 'pnpm' or 'npm'
20
+ */
21
+ function detectPackageManager() {
22
+ try {
23
+ // Check if pnpm is available
24
+ execSync('which pnpm', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
25
+ return 'pnpm';
26
+ } catch {
27
+ // Fall back to npm
28
+ return 'npm';
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Get currently installed version of aifabrix CLI
34
+ * @returns {string|null} Version string or null if not installed
35
+ */
36
+ function getCurrentVersion() {
37
+ try {
38
+ const version = execSync('aifabrix --version', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
39
+ return version;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Get version from local package.json
47
+ * @returns {string|null} Version string or null if not found
48
+ */
49
+ function getPackageVersion() {
50
+ try {
51
+ const packageJsonPath = path.join(__dirname, '..', 'package.json');
52
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
53
+ return packageJson.version;
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Display version comparison information
61
+ * @param {string|null} currentVersion - Currently installed version
62
+ * @param {string|null} packageVersion - Version being linked
63
+ * @returns {void}
64
+ */
65
+ function displayVersionInfo(currentVersion, packageVersion) {
66
+ if (currentVersion) {
67
+ console.log(`📦 Current installed version: ${currentVersion}`);
68
+ } else {
69
+ console.log('📦 No previous version detected (first install)');
70
+ }
71
+
72
+ if (packageVersion) {
73
+ if (currentVersion && currentVersion !== packageVersion) {
74
+ console.log(`🔄 Linking new version: ${packageVersion}`);
75
+ console.log(` Version change: ${currentVersion} → ${packageVersion}\n`);
76
+ } else if (currentVersion && currentVersion === packageVersion) {
77
+ console.log(`🔄 Linking version: ${packageVersion} (same version)\n`);
78
+ } else {
79
+ console.log(`🔄 Linking version: ${packageVersion}\n`);
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Display success message with version information
86
+ * @param {string|null} currentVersion - Version before linking
87
+ * @param {string|null} newVersion - Version after linking
88
+ * @returns {void}
89
+ */
90
+ function displaySuccessMessage(currentVersion, newVersion) {
91
+ console.log('\n✅ Successfully linked!');
92
+ if (currentVersion && newVersion && currentVersion !== newVersion) {
93
+ console.log(`📊 Version updated: ${currentVersion} → ${newVersion}`);
94
+ } else if (newVersion) {
95
+ console.log(`📊 Installed version: ${newVersion}`);
96
+ }
97
+ console.log('Run "aifabrix --version" to verify.');
98
+ }
99
+
100
+ /**
101
+ * Install local package globally
102
+ * @returns {void}
103
+ */
104
+ function installLocal() {
105
+ const pm = detectPackageManager();
106
+ const packageVersion = getPackageVersion();
107
+ const currentVersion = getCurrentVersion();
108
+
109
+ console.log(`Detected package manager: ${pm}\n`);
110
+
111
+ // Show version comparison
112
+ displayVersionInfo(currentVersion, packageVersion);
113
+
114
+ console.log('Linking @aifabrix/builder globally...\n');
115
+
116
+ try {
117
+ if (pm === 'pnpm') {
118
+ execSync('pnpm link --global', { stdio: 'inherit' });
119
+ } else {
120
+ execSync('npm link', { stdio: 'inherit' });
121
+ }
122
+
123
+ // Get new version after linking
124
+ const newVersion = getCurrentVersion();
125
+
126
+ displaySuccessMessage(currentVersion, newVersion);
127
+ } catch (error) {
128
+ console.error('\n❌ Failed to link package:', error.message);
129
+ process.exit(1);
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Display version information before unlinking
135
+ * @param {string|null} currentVersion - Currently installed version
136
+ * @param {string|null} packageVersion - Local package version
137
+ * @returns {void}
138
+ */
139
+ function displayUninstallVersionInfo(currentVersion, packageVersion) {
140
+ if (currentVersion) {
141
+ console.log(`📦 Current installed version: ${currentVersion}`);
142
+ } else {
143
+ console.log('📦 No installed version detected');
144
+ }
145
+
146
+ if (packageVersion) {
147
+ console.log(`📋 Local package version: ${packageVersion}`);
148
+ if (currentVersion && currentVersion === packageVersion) {
149
+ console.log(' (matches installed version)\n');
150
+ } else if (currentVersion && currentVersion !== packageVersion) {
151
+ console.log(` (installed: ${currentVersion}, local: ${packageVersion})\n`);
152
+ } else {
153
+ console.log('\n');
154
+ }
155
+ } else {
156
+ console.log('\n');
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Display success message after unlinking
162
+ * @param {string} pm - Package manager ('pnpm' or 'npm')
163
+ * @param {string|null} currentVersion - Version that was uninstalled
164
+ * @returns {void}
165
+ */
166
+ function displayUninstallSuccess(pm, currentVersion) {
167
+ console.log(`\n✅ Successfully unlinked with ${pm}!`);
168
+ if (currentVersion) {
169
+ console.log(`📊 Uninstalled version: ${currentVersion}`);
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Uninstall local package from global installation
175
+ * @returns {void}
176
+ */
177
+ function uninstallLocal() {
178
+ const pm = detectPackageManager();
179
+ const currentVersion = getCurrentVersion();
180
+ const packageVersion = getPackageVersion();
181
+
182
+ console.log(`Detected package manager: ${pm}\n`);
183
+
184
+ // Show version information before unlinking
185
+ displayUninstallVersionInfo(currentVersion, packageVersion);
186
+
187
+ console.log('Unlinking @aifabrix/builder globally...\n');
188
+
189
+ try {
190
+ if (pm === 'pnpm') {
191
+ execSync('pnpm unlink --global @aifabrix/builder', { stdio: 'inherit' });
192
+ displayUninstallSuccess(pm, currentVersion);
193
+ } else {
194
+ execSync('npm unlink -g @aifabrix/builder', { stdio: 'inherit' });
195
+ displayUninstallSuccess(pm, currentVersion);
196
+ }
197
+ } catch (error) {
198
+ console.error('\n❌ Failed to unlink package:', error.message);
199
+ process.exit(1);
200
+ }
201
+ }
202
+
203
+ // Main execution
204
+ const command = process.argv[2];
205
+
206
+ if (command === 'uninstall' || command === 'unlink') {
207
+ uninstallLocal();
208
+ } else {
209
+ installLocal();
210
+ }