@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.
- package/lib/app-rotate-secret.js +113 -27
- package/package.json +4 -2
- package/scripts/install-local.js +210 -0
package/lib/app-rotate-secret.js
CHANGED
|
@@ -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
|
|
75
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
+
}
|