@aifabrix/builder 2.3.6 → 2.5.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/README.md +3 -0
- package/lib/app-down.js +123 -0
- package/lib/app.js +4 -2
- package/lib/build.js +19 -13
- package/lib/cli.js +52 -9
- package/lib/config.js +83 -8
- package/lib/env-reader.js +3 -2
- package/lib/generator.js +0 -9
- package/lib/infra.js +30 -3
- package/lib/schema/application-schema.json +0 -15
- package/lib/schema/env-config.yaml +8 -8
- package/lib/secrets.js +167 -253
- package/lib/templates.js +10 -18
- package/lib/utils/api-error-handler.js +182 -147
- package/lib/utils/api.js +144 -354
- package/lib/utils/build-copy.js +6 -13
- package/lib/utils/compose-generator.js +2 -1
- package/lib/utils/device-code.js +349 -0
- package/lib/utils/env-config-loader.js +102 -0
- package/lib/utils/env-copy.js +131 -0
- package/lib/utils/env-endpoints.js +209 -0
- package/lib/utils/env-map.js +116 -0
- package/lib/utils/env-ports.js +60 -0
- package/lib/utils/environment-checker.js +39 -6
- package/lib/utils/image-name.js +49 -0
- package/lib/utils/paths.js +40 -18
- package/lib/utils/secrets-generator.js +3 -3
- package/lib/utils/secrets-helpers.js +359 -0
- package/lib/utils/secrets-path.js +24 -71
- package/lib/utils/secrets-url.js +38 -0
- package/lib/utils/secrets-utils.js +0 -41
- package/lib/utils/variable-transformer.js +0 -9
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +9 -5
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/infra/compose.yaml +4 -0
- package/templates/infra/compose.yaml.hbs +9 -4
package/lib/utils/api.js
CHANGED
|
@@ -15,24 +15,151 @@ const auditLogger = require('../audit-logger');
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Logs API request performance metrics and errors to audit log
|
|
18
|
-
* @param {
|
|
19
|
-
* @param {
|
|
20
|
-
* @param {
|
|
21
|
-
* @param {number}
|
|
22
|
-
* @param {
|
|
23
|
-
* @param {
|
|
18
|
+
* @param {Object} params - Performance logging parameters
|
|
19
|
+
* @param {string} params.url - API endpoint URL
|
|
20
|
+
* @param {Object} params.options - Fetch options
|
|
21
|
+
* @param {number} params.statusCode - HTTP status code
|
|
22
|
+
* @param {number} params.duration - Request duration in milliseconds
|
|
23
|
+
* @param {boolean} params.success - Whether the request was successful
|
|
24
|
+
* @param {Object} [params.errorInfo] - Error information (if failed)
|
|
24
25
|
*/
|
|
25
|
-
async function logApiPerformance(
|
|
26
|
+
async function logApiPerformance(params) {
|
|
26
27
|
// Log all API calls (both success and failure) to audit log for troubleshooting
|
|
27
28
|
// This helps track what API calls were made when errors occur
|
|
28
29
|
try {
|
|
29
|
-
await auditLogger.logApiCall(
|
|
30
|
+
await auditLogger.logApiCall(
|
|
31
|
+
params.url,
|
|
32
|
+
params.options,
|
|
33
|
+
params.statusCode,
|
|
34
|
+
params.duration,
|
|
35
|
+
params.success,
|
|
36
|
+
params.errorInfo || {}
|
|
37
|
+
);
|
|
30
38
|
} catch (logError) {
|
|
31
39
|
// Don't fail the API call if audit logging fails
|
|
32
40
|
// Silently continue - audit logging should never break functionality
|
|
33
41
|
}
|
|
34
42
|
}
|
|
35
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Parses error response text into error data
|
|
46
|
+
* @param {string} errorText - Error response text
|
|
47
|
+
* @param {number} status - HTTP status code
|
|
48
|
+
* @param {string} statusText - HTTP status text
|
|
49
|
+
* @returns {Object|string} Parsed error data
|
|
50
|
+
*/
|
|
51
|
+
function parseErrorText(errorText, status, statusText) {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(errorText);
|
|
54
|
+
} catch {
|
|
55
|
+
return errorText || `HTTP ${status}: ${statusText}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Handles error response from API
|
|
61
|
+
* @async
|
|
62
|
+
* @function handleErrorResponse
|
|
63
|
+
* @param {Object} response - Fetch response object
|
|
64
|
+
* @param {string} url - API endpoint URL
|
|
65
|
+
* @param {Object} options - Fetch options
|
|
66
|
+
* @param {number} duration - Request duration
|
|
67
|
+
* @returns {Promise<Object>} Error response object
|
|
68
|
+
*/
|
|
69
|
+
async function handleErrorResponse(response, url, options, duration) {
|
|
70
|
+
const errorText = await response.text();
|
|
71
|
+
const errorData = parseErrorText(errorText, response.status, response.statusText);
|
|
72
|
+
const parsedError = parseErrorResponse(errorData, response.status, false);
|
|
73
|
+
|
|
74
|
+
await logApiPerformance({
|
|
75
|
+
url,
|
|
76
|
+
options,
|
|
77
|
+
statusCode: response.status,
|
|
78
|
+
duration,
|
|
79
|
+
success: false,
|
|
80
|
+
errorInfo: {
|
|
81
|
+
errorType: parsedError.type,
|
|
82
|
+
errorMessage: parsedError.message,
|
|
83
|
+
errorData: parsedError.data,
|
|
84
|
+
correlationId: parsedError.data?.correlationId
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
error: parsedError.message,
|
|
91
|
+
errorData: parsedError.data,
|
|
92
|
+
errorType: parsedError.type,
|
|
93
|
+
formattedError: parsedError.formatted,
|
|
94
|
+
status: response.status
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Handles successful response from API
|
|
100
|
+
* @async
|
|
101
|
+
* @function handleSuccessResponse
|
|
102
|
+
* @param {Object} response - Fetch response object
|
|
103
|
+
* @param {string} url - API endpoint URL
|
|
104
|
+
* @param {Object} options - Fetch options
|
|
105
|
+
* @param {number} duration - Request duration
|
|
106
|
+
* @returns {Promise<Object>} Success response object
|
|
107
|
+
*/
|
|
108
|
+
async function handleSuccessResponse(response, url, options, duration) {
|
|
109
|
+
await logApiPerformance({
|
|
110
|
+
url,
|
|
111
|
+
options,
|
|
112
|
+
statusCode: response.status,
|
|
113
|
+
duration,
|
|
114
|
+
success: true
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const contentType = response.headers.get('content-type');
|
|
118
|
+
if (contentType && contentType.includes('application/json')) {
|
|
119
|
+
const data = await response.json();
|
|
120
|
+
return { success: true, data, status: response.status };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const text = await response.text();
|
|
124
|
+
return { success: true, data: text, status: response.status };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Handles network error from API call
|
|
129
|
+
* @async
|
|
130
|
+
* @function handleNetworkError
|
|
131
|
+
* @param {Error} error - Network error
|
|
132
|
+
* @param {string} url - API endpoint URL
|
|
133
|
+
* @param {Object} options - Fetch options
|
|
134
|
+
* @param {number} duration - Request duration
|
|
135
|
+
* @returns {Promise<Object>} Error response object
|
|
136
|
+
*/
|
|
137
|
+
async function handleNetworkError(error, url, options, duration) {
|
|
138
|
+
const parsedError = parseErrorResponse(error.message, 0, true);
|
|
139
|
+
|
|
140
|
+
await logApiPerformance({
|
|
141
|
+
url,
|
|
142
|
+
options,
|
|
143
|
+
statusCode: 0,
|
|
144
|
+
duration,
|
|
145
|
+
success: false,
|
|
146
|
+
errorInfo: {
|
|
147
|
+
errorType: parsedError.type,
|
|
148
|
+
errorMessage: parsedError.message,
|
|
149
|
+
network: true
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
success: false,
|
|
155
|
+
error: parsedError.message,
|
|
156
|
+
errorData: parsedError.data,
|
|
157
|
+
errorType: parsedError.type,
|
|
158
|
+
formattedError: parsedError.formatted,
|
|
159
|
+
network: true
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
36
163
|
/**
|
|
37
164
|
* Make an API call with proper error handling
|
|
38
165
|
* @param {string} url - API endpoint URL
|
|
@@ -47,76 +174,13 @@ async function makeApiCall(url, options = {}) {
|
|
|
47
174
|
const duration = Date.now() - startTime;
|
|
48
175
|
|
|
49
176
|
if (!response.ok) {
|
|
50
|
-
|
|
51
|
-
let errorData;
|
|
52
|
-
try {
|
|
53
|
-
errorData = JSON.parse(errorText);
|
|
54
|
-
} catch {
|
|
55
|
-
errorData = errorText || `HTTP ${response.status}: ${response.statusText}`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Parse error using error handler
|
|
59
|
-
const parsedError = parseErrorResponse(errorData, response.status, false);
|
|
60
|
-
|
|
61
|
-
// Log error to audit log
|
|
62
|
-
await logApiPerformance(url, options, response.status, duration, false, {
|
|
63
|
-
errorType: parsedError.type,
|
|
64
|
-
errorMessage: parsedError.message,
|
|
65
|
-
errorData: parsedError.data,
|
|
66
|
-
correlationId: parsedError.data?.correlationId
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
success: false,
|
|
71
|
-
error: parsedError.message,
|
|
72
|
-
errorData: parsedError.data,
|
|
73
|
-
errorType: parsedError.type,
|
|
74
|
-
formattedError: parsedError.formatted,
|
|
75
|
-
status: response.status
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Log successful API call to audit log
|
|
80
|
-
await logApiPerformance(url, options, response.status, duration, true);
|
|
81
|
-
|
|
82
|
-
const contentType = response.headers.get('content-type');
|
|
83
|
-
if (contentType && contentType.includes('application/json')) {
|
|
84
|
-
const data = await response.json();
|
|
85
|
-
return {
|
|
86
|
-
success: true,
|
|
87
|
-
data,
|
|
88
|
-
status: response.status
|
|
89
|
-
};
|
|
177
|
+
return await handleErrorResponse(response, url, options, duration);
|
|
90
178
|
}
|
|
91
179
|
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
success: true,
|
|
95
|
-
data: text,
|
|
96
|
-
status: response.status
|
|
97
|
-
};
|
|
98
|
-
|
|
180
|
+
return await handleSuccessResponse(response, url, options, duration);
|
|
99
181
|
} catch (error) {
|
|
100
182
|
const duration = Date.now() - startTime;
|
|
101
|
-
|
|
102
|
-
// Parse network error using error handler
|
|
103
|
-
const parsedError = parseErrorResponse(error.message, 0, true);
|
|
104
|
-
|
|
105
|
-
// Log network error to audit log
|
|
106
|
-
await logApiPerformance(url, options, 0, duration, false, {
|
|
107
|
-
errorType: parsedError.type,
|
|
108
|
-
errorMessage: parsedError.message,
|
|
109
|
-
network: true
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
success: false,
|
|
114
|
-
error: parsedError.message,
|
|
115
|
-
errorData: parsedError.data,
|
|
116
|
-
errorType: parsedError.type,
|
|
117
|
-
formattedError: parsedError.formatted,
|
|
118
|
-
network: true
|
|
119
|
-
};
|
|
183
|
+
return await handleNetworkError(error, url, options, duration);
|
|
120
184
|
}
|
|
121
185
|
}
|
|
122
186
|
|
|
@@ -186,286 +250,12 @@ async function authenticatedApiCall(url, options = {}, token) {
|
|
|
186
250
|
return response;
|
|
187
251
|
}
|
|
188
252
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
* @throws {Error} If response is invalid
|
|
196
|
-
*/
|
|
197
|
-
function parseDeviceCodeResponse(response) {
|
|
198
|
-
// OpenAPI spec: { success: boolean, data: DeviceCodeResponse, timestamp: string }
|
|
199
|
-
const apiResponse = response.data;
|
|
200
|
-
const responseData = apiResponse.data || apiResponse;
|
|
201
|
-
|
|
202
|
-
// OpenAPI spec uses camelCase: deviceCode, userCode, verificationUri, expiresIn, interval
|
|
203
|
-
const deviceCode = responseData.deviceCode;
|
|
204
|
-
const userCode = responseData.userCode;
|
|
205
|
-
const verificationUri = responseData.verificationUri;
|
|
206
|
-
const expiresIn = responseData.expiresIn || 600;
|
|
207
|
-
const interval = responseData.interval || 5;
|
|
208
|
-
|
|
209
|
-
if (!deviceCode || !userCode || !verificationUri) {
|
|
210
|
-
throw new Error('Invalid device code response: missing required fields');
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Return in snake_case for internal consistency (used by existing code)
|
|
214
|
-
return {
|
|
215
|
-
device_code: deviceCode,
|
|
216
|
-
user_code: userCode,
|
|
217
|
-
verification_uri: verificationUri,
|
|
218
|
-
expires_in: expiresIn,
|
|
219
|
-
interval: interval
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Initiates OAuth2 Device Code Flow
|
|
225
|
-
* Calls the device code endpoint to get device_code and user_code
|
|
226
|
-
*
|
|
227
|
-
* @async
|
|
228
|
-
* @function initiateDeviceCodeFlow
|
|
229
|
-
* @param {string} controllerUrl - Base URL of the controller
|
|
230
|
-
* @param {string} environment - Environment key (e.g., 'miso', 'dev', 'tst', 'pro')
|
|
231
|
-
* @returns {Promise<Object>} Device code response with device_code, user_code, verification_uri, expires_in, interval
|
|
232
|
-
* @throws {Error} If initiation fails
|
|
233
|
-
*/
|
|
234
|
-
async function initiateDeviceCodeFlow(controllerUrl, environment) {
|
|
235
|
-
if (!environment || typeof environment !== 'string') {
|
|
236
|
-
throw new Error('Environment key is required');
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const url = `${controllerUrl}/api/v1/auth/login?environment=${encodeURIComponent(environment)}`;
|
|
240
|
-
const response = await makeApiCall(url, {
|
|
241
|
-
method: 'POST',
|
|
242
|
-
headers: {
|
|
243
|
-
'Content-Type': 'application/json'
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
if (!response.success) {
|
|
248
|
-
throw new Error(`Device code initiation failed: ${response.error || 'Unknown error'}`);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return parseDeviceCodeResponse(response);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Checks if token has expired based on elapsed time
|
|
256
|
-
* @function checkTokenExpiration
|
|
257
|
-
* @param {number} startTime - Start time in milliseconds
|
|
258
|
-
* @param {number} expiresIn - Expiration time in seconds
|
|
259
|
-
* @throws {Error} If token has expired
|
|
260
|
-
*/
|
|
261
|
-
function checkTokenExpiration(startTime, expiresIn) {
|
|
262
|
-
const maxWaitTime = (expiresIn + 30) * 1000;
|
|
263
|
-
if (Date.now() - startTime > maxWaitTime) {
|
|
264
|
-
throw new Error('Device code expired: Maximum polling time exceeded');
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Parses token response from API
|
|
270
|
-
* Matches OpenAPI DeviceCodeTokenResponse schema (camelCase)
|
|
271
|
-
* @function parseTokenResponse
|
|
272
|
-
* @param {Object} response - API response object
|
|
273
|
-
* @returns {Object|null} Parsed token response or null if pending
|
|
274
|
-
*/
|
|
275
|
-
function parseTokenResponse(response) {
|
|
276
|
-
// OpenAPI spec: { success: boolean, data: DeviceCodeTokenResponse, timestamp: string }
|
|
277
|
-
const apiResponse = response.data;
|
|
278
|
-
const responseData = apiResponse.data || apiResponse;
|
|
279
|
-
|
|
280
|
-
const error = responseData.error || apiResponse.error;
|
|
281
|
-
if (error === 'authorization_pending' || error === 'slow_down') {
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// OpenAPI spec uses camelCase: accessToken, refreshToken, expiresIn
|
|
286
|
-
const accessToken = responseData.accessToken;
|
|
287
|
-
const refreshToken = responseData.refreshToken;
|
|
288
|
-
const expiresIn = responseData.expiresIn || 3600;
|
|
289
|
-
|
|
290
|
-
if (!accessToken) {
|
|
291
|
-
throw new Error('Invalid token response: missing accessToken');
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Return in snake_case for internal consistency (used by existing code)
|
|
295
|
-
return {
|
|
296
|
-
access_token: accessToken,
|
|
297
|
-
refresh_token: refreshToken,
|
|
298
|
-
expires_in: expiresIn
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Handles polling errors
|
|
304
|
-
* @function handlePollingErrors
|
|
305
|
-
* @param {string} error - Error code
|
|
306
|
-
* @param {number} status - HTTP status code
|
|
307
|
-
* @throws {Error} For fatal errors
|
|
308
|
-
* @returns {boolean} True if should continue polling
|
|
309
|
-
*/
|
|
310
|
-
function handlePollingErrors(error, status) {
|
|
311
|
-
if (error === 'authorization_pending' || status === 202) {
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Check error field first, then status code
|
|
316
|
-
if (error === 'authorization_declined') {
|
|
317
|
-
throw new Error('Authorization declined: User denied the request');
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (error === 'expired_token' || status === 410) {
|
|
321
|
-
throw new Error('Device code expired: Please restart the authentication process');
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (error === 'slow_down') {
|
|
325
|
-
return true;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
throw new Error(`Token polling failed: ${error}`);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Waits for next polling interval
|
|
333
|
-
* @async
|
|
334
|
-
* @function waitForNextPoll
|
|
335
|
-
* @param {number} interval - Polling interval in seconds
|
|
336
|
-
* @param {boolean} slowDown - Whether to slow down
|
|
337
|
-
*/
|
|
338
|
-
async function waitForNextPoll(interval, slowDown) {
|
|
339
|
-
const waitInterval = slowDown ? interval * 2 : interval;
|
|
340
|
-
await new Promise(resolve => setTimeout(resolve, waitInterval * 1000));
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Polls for token during Device Code Flow
|
|
345
|
-
* Continuously polls the token endpoint until user approves or flow expires
|
|
346
|
-
*
|
|
347
|
-
* @async
|
|
348
|
-
* @function pollDeviceCodeToken
|
|
349
|
-
* @param {string} controllerUrl - Base URL of the controller
|
|
350
|
-
* @param {string} deviceCode - Device code from initiation
|
|
351
|
-
* @param {number} interval - Polling interval in seconds
|
|
352
|
-
* @param {number} expiresIn - Expiration time in seconds
|
|
353
|
-
* @param {Function} [onPoll] - Optional callback called on each poll attempt
|
|
354
|
-
* @returns {Promise<Object>} Token response with access_token, refresh_token, expires_in
|
|
355
|
-
* @throws {Error} If polling fails or token is expired/declined
|
|
356
|
-
*/
|
|
357
|
-
async function pollDeviceCodeToken(controllerUrl, deviceCode, interval, expiresIn, onPoll) {
|
|
358
|
-
if (!deviceCode || typeof deviceCode !== 'string') {
|
|
359
|
-
throw new Error('Device code is required');
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const url = `${controllerUrl}/api/v1/auth/login/device/token`;
|
|
363
|
-
const startTime = Date.now();
|
|
364
|
-
|
|
365
|
-
// eslint-disable-next-line no-constant-condition
|
|
366
|
-
while (true) {
|
|
367
|
-
checkTokenExpiration(startTime, expiresIn);
|
|
368
|
-
|
|
369
|
-
if (onPoll) {
|
|
370
|
-
onPoll();
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const response = await makeApiCall(url, {
|
|
374
|
-
method: 'POST',
|
|
375
|
-
headers: {
|
|
376
|
-
'Content-Type': 'application/json'
|
|
377
|
-
},
|
|
378
|
-
body: JSON.stringify({
|
|
379
|
-
deviceCode: deviceCode
|
|
380
|
-
})
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
if (response.success) {
|
|
384
|
-
const tokenResponse = parseTokenResponse(response);
|
|
385
|
-
if (tokenResponse) {
|
|
386
|
-
return tokenResponse;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const apiResponse = response.data;
|
|
390
|
-
const responseData = apiResponse.data || apiResponse;
|
|
391
|
-
const error = responseData.error || apiResponse.error;
|
|
392
|
-
const slowDown = error === 'slow_down';
|
|
393
|
-
await waitForNextPoll(interval, slowDown);
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const apiResponse = response.data || {};
|
|
398
|
-
const errorData = typeof apiResponse === 'object' ? apiResponse : {};
|
|
399
|
-
const error = errorData.error || response.error || 'Unknown error';
|
|
400
|
-
const shouldContinue = handlePollingErrors(error, response.status);
|
|
401
|
-
|
|
402
|
-
if (shouldContinue) {
|
|
403
|
-
const slowDown = error === 'slow_down';
|
|
404
|
-
await waitForNextPoll(interval, slowDown);
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Displays device code information to the user
|
|
412
|
-
* Formats user code and verification URL for easy reading
|
|
413
|
-
*
|
|
414
|
-
* @function displayDeviceCodeInfo
|
|
415
|
-
* @param {string} userCode - User code to display
|
|
416
|
-
* @param {string} verificationUri - Verification URL
|
|
417
|
-
* @param {Object} logger - Logger instance with log method
|
|
418
|
-
* @param {Object} chalk - Chalk instance for colored output
|
|
419
|
-
*/
|
|
420
|
-
function displayDeviceCodeInfo(userCode, verificationUri, logger, chalk) {
|
|
421
|
-
logger.log(chalk.cyan('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
422
|
-
logger.log(chalk.cyan(' Device Code Flow Authentication'));
|
|
423
|
-
logger.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
424
|
-
logger.log(chalk.yellow('To complete authentication:'));
|
|
425
|
-
logger.log(chalk.gray(' 1. Visit: ') + chalk.blue.underline(verificationUri));
|
|
426
|
-
logger.log(chalk.gray(' 2. Enter code: ') + chalk.bold.cyan(userCode));
|
|
427
|
-
logger.log(chalk.gray(' 3. Approve the request\n'));
|
|
428
|
-
logger.log(chalk.gray('Waiting for approval...'));
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Refresh device code access token using refresh token
|
|
433
|
-
* Uses OpenAPI /api/v1/auth/login/device/refresh endpoint
|
|
434
|
-
*
|
|
435
|
-
* @async
|
|
436
|
-
* @function refreshDeviceToken
|
|
437
|
-
* @param {string} controllerUrl - Base URL of the controller
|
|
438
|
-
* @param {string} refreshToken - Refresh token from previous authentication
|
|
439
|
-
* @returns {Promise<Object>} Token response with access_token, refresh_token, expires_in
|
|
440
|
-
* @throws {Error} If refresh fails or refresh token is invalid/expired
|
|
441
|
-
*/
|
|
442
|
-
async function refreshDeviceToken(controllerUrl, refreshToken) {
|
|
443
|
-
if (!refreshToken || typeof refreshToken !== 'string') {
|
|
444
|
-
throw new Error('Refresh token is required');
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const url = `${controllerUrl}/api/v1/auth/login/device/refresh`;
|
|
448
|
-
const response = await makeApiCall(url, {
|
|
449
|
-
method: 'POST',
|
|
450
|
-
headers: {
|
|
451
|
-
'Content-Type': 'application/json'
|
|
452
|
-
},
|
|
453
|
-
body: JSON.stringify({ refreshToken })
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
if (!response.success) {
|
|
457
|
-
const errorMsg = response.error || 'Unknown error';
|
|
458
|
-
throw new Error(`Failed to refresh token: ${errorMsg}`);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// Parse response using existing parseTokenResponse function
|
|
462
|
-
const tokenResponse = parseTokenResponse(response);
|
|
463
|
-
if (!tokenResponse) {
|
|
464
|
-
throw new Error('Invalid refresh token response');
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
return tokenResponse;
|
|
468
|
-
}
|
|
253
|
+
const {
|
|
254
|
+
initiateDeviceCodeFlow,
|
|
255
|
+
pollDeviceCodeToken,
|
|
256
|
+
displayDeviceCodeInfo,
|
|
257
|
+
refreshDeviceToken
|
|
258
|
+
} = require('./device-code');
|
|
469
259
|
|
|
470
260
|
module.exports = {
|
|
471
261
|
makeApiCall,
|
package/lib/utils/build-copy.js
CHANGED
|
@@ -28,7 +28,7 @@ const paths = require('./paths');
|
|
|
28
28
|
*
|
|
29
29
|
* @example
|
|
30
30
|
* const devPath = await copyBuilderToDevDirectory('myapp', 1);
|
|
31
|
-
* // Returns: '~/.aifabrix/applications-dev-1
|
|
31
|
+
* // Returns: '~/.aifabrix/applications-dev-1'
|
|
32
32
|
*/
|
|
33
33
|
async function copyBuilderToDevDirectory(appName, developerId) {
|
|
34
34
|
const builderPath = path.join(process.cwd(), 'builder', appName);
|
|
@@ -38,9 +38,8 @@ async function copyBuilderToDevDirectory(appName, developerId) {
|
|
|
38
38
|
throw new Error(`Builder directory not found: ${builderPath}\nRun 'aifabrix create ${appName}' first`);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
// Get base directory (applications or applications-dev-{id})
|
|
42
|
-
const
|
|
43
|
-
const baseDir = paths.getApplicationsBaseDir(idNum);
|
|
41
|
+
// Get base directory (applications or applications-dev-{id}) using raw developerId text
|
|
42
|
+
const baseDir = paths.getApplicationsBaseDir(developerId);
|
|
44
43
|
|
|
45
44
|
// Clear base directory before copying (delete all files)
|
|
46
45
|
if (fsSync.existsSync(baseDir)) {
|
|
@@ -56,20 +55,14 @@ async function copyBuilderToDevDirectory(appName, developerId) {
|
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
// Get target directory using getDevDirectory()
|
|
58
|
+
// Get target directory using getDevDirectory() (root of applications folder)
|
|
60
59
|
const devDir = getDevDirectory(appName, developerId);
|
|
61
60
|
|
|
62
61
|
// Create target directory
|
|
63
62
|
await fs.mkdir(devDir, { recursive: true });
|
|
64
63
|
|
|
65
|
-
// Copy files
|
|
66
|
-
|
|
67
|
-
// Dev 0: Copy contents from builder/{appName}/ directly to applications/
|
|
68
|
-
await copyDirectory(builderPath, devDir);
|
|
69
|
-
} else {
|
|
70
|
-
// Dev > 0: Copy builder/{appName}/ to applications-dev-{id}/{appName}-dev-{id}/
|
|
71
|
-
await copyDirectory(builderPath, devDir);
|
|
72
|
-
}
|
|
64
|
+
// Copy files to root of applications folder (same behavior for all dev IDs)
|
|
65
|
+
await copyDirectory(builderPath, devDir);
|
|
73
66
|
|
|
74
67
|
return devDir;
|
|
75
68
|
}
|
|
@@ -349,7 +349,8 @@ async function generateDockerCompose(appName, appConfig, options) {
|
|
|
349
349
|
...networksConfig,
|
|
350
350
|
envFile: envFileAbsolutePath,
|
|
351
351
|
databasePasswords: databasePasswords,
|
|
352
|
-
|
|
352
|
+
// IMPORTANT: pass numeric devId to templates so (eq devId 0) works correctly
|
|
353
|
+
devId: idNum,
|
|
353
354
|
networkName: networkName,
|
|
354
355
|
containerName: containerName
|
|
355
356
|
};
|