@aifabrix/builder 2.10.1 ā 2.20.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/.cursor/rules/project-rules.mdc +194 -0
- package/README.md +12 -0
- package/integration/hubspot/README.md +2 -2
- package/integration/hubspot/hubspot-deploy.json +12 -4
- package/lib/api/applications.api.js +164 -0
- package/lib/api/auth.api.js +304 -0
- package/lib/api/datasources-core.api.js +87 -0
- package/lib/api/datasources-extended.api.js +117 -0
- package/lib/api/datasources.api.js +13 -0
- package/lib/api/deployments.api.js +126 -0
- package/lib/api/environments.api.js +245 -0
- package/lib/api/external-systems.api.js +251 -0
- package/lib/api/index.js +221 -0
- package/lib/api/pipeline.api.js +234 -0
- package/lib/api/types/applications.types.js +136 -0
- package/lib/api/types/auth.types.js +218 -0
- package/lib/api/types/datasources.types.js +272 -0
- package/lib/api/types/deployments.types.js +184 -0
- package/lib/api/types/environments.types.js +197 -0
- package/lib/api/types/external-systems.types.js +244 -0
- package/lib/api/types/pipeline.types.js +125 -0
- package/lib/app-list.js +5 -7
- package/lib/app-register.js +70 -403
- package/lib/app-rotate-secret.js +4 -10
- package/lib/commands/login.js +19 -12
- package/lib/datasource-deploy.js +7 -30
- package/lib/datasource-list.js +9 -6
- package/lib/deployer.js +103 -135
- package/lib/environment-deploy.js +15 -26
- package/lib/external-system-deploy.js +12 -39
- package/lib/external-system-download.js +5 -13
- package/lib/external-system-test.js +9 -12
- package/lib/utils/api-error-handler.js +11 -453
- package/lib/utils/app-register-api.js +66 -0
- package/lib/utils/app-register-auth.js +72 -0
- package/lib/utils/app-register-config.js +205 -0
- package/lib/utils/app-register-display.js +69 -0
- package/lib/utils/app-register-validator.js +143 -0
- package/lib/utils/deployment-errors.js +88 -6
- package/lib/utils/device-code.js +1 -1
- package/lib/utils/error-formatters/error-parser.js +150 -0
- package/lib/utils/error-formatters/http-status-errors.js +189 -0
- package/lib/utils/error-formatters/network-errors.js +46 -0
- package/lib/utils/error-formatters/permission-errors.js +94 -0
- package/lib/utils/error-formatters/validation-errors.js +133 -0
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +1 -1
package/lib/app-register.js
CHANGED
|
@@ -8,376 +8,98 @@
|
|
|
8
8
|
* @version 2.0.0
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const fs = require('fs').promises;
|
|
12
|
-
const path = require('path');
|
|
13
11
|
const chalk = require('chalk');
|
|
14
|
-
const yaml = require('js-yaml');
|
|
15
|
-
const { getConfig } = require('./config');
|
|
16
|
-
const { authenticatedApiCall } = require('./utils/api');
|
|
17
|
-
const { formatApiError } = require('./utils/api-error-handler');
|
|
18
12
|
const logger = require('./utils/logger');
|
|
19
13
|
const { saveLocalSecret, isLocalhost } = require('./utils/local-secrets');
|
|
20
14
|
const { updateEnvTemplate } = require('./utils/env-template');
|
|
21
|
-
const { getOrRefreshDeviceToken } = require('./utils/token-manager');
|
|
22
|
-
const { detectAppType } = require('./utils/paths');
|
|
23
15
|
const { generateEnvFile } = require('./secrets');
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Extract valid enum values from application schema
|
|
35
|
-
const validTypes = applicationSchema.properties.type.enum || [];
|
|
36
|
-
const validRegistryModes = applicationSchema.properties.registryMode.enum || [];
|
|
37
|
-
const portConstraints = {
|
|
38
|
-
minimum: applicationSchema.properties.port?.minimum || 1,
|
|
39
|
-
maximum: applicationSchema.properties.port?.maximum || 65535
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Validation schema for application registration
|
|
44
|
-
* Validates according to application-schema.json
|
|
45
|
-
*/
|
|
46
|
-
const registerApplicationSchema = {
|
|
47
|
-
environmentId: (val) => {
|
|
48
|
-
if (!val || val.length < 1) {
|
|
49
|
-
throw new Error('Invalid environment ID format');
|
|
50
|
-
}
|
|
51
|
-
return val;
|
|
52
|
-
},
|
|
53
|
-
key: (val) => {
|
|
54
|
-
if (!val || val.length < 1) {
|
|
55
|
-
throw new Error('Application key is required');
|
|
56
|
-
}
|
|
57
|
-
const keyPattern = applicationSchema.properties.key.pattern;
|
|
58
|
-
const keyMaxLength = applicationSchema.properties.key.maxLength || 50;
|
|
59
|
-
if (val.length > keyMaxLength) {
|
|
60
|
-
throw new Error(`Application key must be at most ${keyMaxLength} characters`);
|
|
61
|
-
}
|
|
62
|
-
if (keyPattern && !new RegExp(keyPattern).test(val)) {
|
|
63
|
-
throw new Error('Application key must contain only lowercase letters, numbers, and hyphens');
|
|
64
|
-
}
|
|
65
|
-
return val;
|
|
66
|
-
},
|
|
67
|
-
displayName: (val) => {
|
|
68
|
-
if (!val || val.length < 1) {
|
|
69
|
-
throw new Error('Display name is required');
|
|
70
|
-
}
|
|
71
|
-
const displayNameMaxLength = applicationSchema.properties.displayName.maxLength || 100;
|
|
72
|
-
if (val.length > displayNameMaxLength) {
|
|
73
|
-
throw new Error(`Display name must be at most ${displayNameMaxLength} characters`);
|
|
74
|
-
}
|
|
75
|
-
return val;
|
|
76
|
-
},
|
|
77
|
-
description: (val) => val || undefined,
|
|
78
|
-
configuration: (val) => {
|
|
79
|
-
if (!val || !val.type || !validTypes.includes(val.type)) {
|
|
80
|
-
throw new Error(`Configuration type must be one of: ${validTypes.join(', ')}`);
|
|
81
|
-
}
|
|
82
|
-
if (!val.registryMode || !validRegistryModes.includes(val.registryMode)) {
|
|
83
|
-
throw new Error(`Registry mode must be one of: ${validRegistryModes.join(', ')}`);
|
|
84
|
-
}
|
|
85
|
-
// Port validation: skip for external type (external systems don't need ports)
|
|
86
|
-
// For other types, port is required and must be valid
|
|
87
|
-
if (val.type !== 'external') {
|
|
88
|
-
if (val.port === undefined || val.port === null) {
|
|
89
|
-
throw new Error('Port is required for non-external application types');
|
|
90
|
-
}
|
|
91
|
-
if (!Number.isInteger(val.port) || val.port < portConstraints.minimum || val.port > portConstraints.maximum) {
|
|
92
|
-
throw new Error(`Port must be an integer between ${portConstraints.minimum} and ${portConstraints.maximum}`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return val;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
16
|
+
const { registerApplicationSchema, validateAppRegistrationData } = require('./utils/app-register-validator');
|
|
17
|
+
const {
|
|
18
|
+
loadVariablesYaml,
|
|
19
|
+
createMinimalAppIfNeeded,
|
|
20
|
+
extractAppConfiguration
|
|
21
|
+
} = require('./utils/app-register-config');
|
|
22
|
+
const { checkAuthentication } = require('./utils/app-register-auth');
|
|
23
|
+
const { callRegisterApi } = require('./utils/app-register-api');
|
|
24
|
+
const { displayRegistrationResults, getEnvironmentPrefix } = require('./utils/app-register-display');
|
|
98
25
|
|
|
99
26
|
/**
|
|
100
|
-
*
|
|
101
|
-
* @
|
|
102
|
-
* @param {string} appKey - Application key
|
|
103
|
-
* @returns {Promise<{variables: Object, created: boolean}>} Variables and creation flag
|
|
104
|
-
*/
|
|
105
|
-
async function loadVariablesYaml(appKey) {
|
|
106
|
-
// Detect app type and get correct path (integration or builder)
|
|
107
|
-
const { appPath } = await detectAppType(appKey);
|
|
108
|
-
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
const variablesContent = await fs.readFile(variablesPath, 'utf-8');
|
|
112
|
-
return { variables: yaml.load(variablesContent), created: false };
|
|
113
|
-
} catch (error) {
|
|
114
|
-
if (error.code === 'ENOENT') {
|
|
115
|
-
logger.log(chalk.yellow(`ā ļø variables.yaml not found for ${appKey}`));
|
|
116
|
-
logger.log(chalk.yellow('š Creating minimal configuration...\n'));
|
|
117
|
-
return { variables: null, created: true };
|
|
118
|
-
}
|
|
119
|
-
throw new Error(`Failed to read variables.yaml: ${error.message}`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Create minimal application configuration if needed
|
|
125
|
-
* @async
|
|
126
|
-
* @param {string} appKey - Application key
|
|
27
|
+
* Build registration data payload from app configuration
|
|
28
|
+
* @param {Object} appConfig - Application configuration
|
|
127
29
|
* @param {Object} options - Registration options
|
|
128
|
-
* @returns {
|
|
30
|
+
* @returns {Object} Registration data payload
|
|
129
31
|
*/
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
await createApp(appKey, {
|
|
136
|
-
port: options.port,
|
|
137
|
-
language: 'typescript',
|
|
138
|
-
database: false,
|
|
139
|
-
redis: false,
|
|
140
|
-
storage: false,
|
|
141
|
-
authentication: false
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// Detect app type and get correct path (integration or builder)
|
|
145
|
-
const { appPath } = await detectAppType(appKey);
|
|
146
|
-
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
147
|
-
const variablesContent = await fs.readFile(variablesPath, 'utf-8');
|
|
148
|
-
return yaml.load(variablesContent);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Extract application configuration from variables.yaml
|
|
153
|
-
* @param {Object} variables - Variables from YAML file
|
|
154
|
-
* @param {string} appKey - Application key
|
|
155
|
-
* @param {Object} options - Registration options
|
|
156
|
-
* @returns {Object} Extracted configuration
|
|
157
|
-
*/
|
|
158
|
-
function extractAppConfiguration(variables, appKey, options) {
|
|
159
|
-
const appKeyFromFile = variables.app?.key || appKey;
|
|
160
|
-
const displayName = variables.app?.name || options.name || appKey;
|
|
161
|
-
const description = variables.app?.description || '';
|
|
162
|
-
|
|
163
|
-
// Handle external type
|
|
164
|
-
if (variables.app?.type === 'external') {
|
|
165
|
-
return {
|
|
166
|
-
appKey: appKeyFromFile,
|
|
167
|
-
displayName,
|
|
168
|
-
description,
|
|
169
|
-
appType: 'external',
|
|
170
|
-
registryMode: 'external',
|
|
171
|
-
port: null, // External systems don't need ports
|
|
172
|
-
language: null // External systems don't need language
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const appType = variables.build?.language === 'typescript' ? 'webapp' : 'service';
|
|
177
|
-
const registryMode = 'external';
|
|
178
|
-
const port = variables.build?.port || options.port || 3000;
|
|
179
|
-
const language = variables.build?.language || 'typescript';
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
appKey: appKeyFromFile,
|
|
183
|
-
displayName,
|
|
184
|
-
description,
|
|
185
|
-
appType,
|
|
186
|
-
registryMode,
|
|
187
|
-
port,
|
|
188
|
-
language
|
|
32
|
+
function buildRegistrationData(appConfig, options) {
|
|
33
|
+
const registrationData = {
|
|
34
|
+
key: appConfig.appKey,
|
|
35
|
+
displayName: appConfig.displayName,
|
|
36
|
+
type: appConfig.appType
|
|
189
37
|
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Validate application registration data
|
|
194
|
-
* @async
|
|
195
|
-
* @param {Object} config - Application configuration
|
|
196
|
-
* @param {string} originalAppKey - Original app key for error messages
|
|
197
|
-
* @throws {Error} If validation fails
|
|
198
|
-
*/
|
|
199
|
-
async function validateAppRegistrationData(config, originalAppKey) {
|
|
200
|
-
const missingFields = [];
|
|
201
|
-
if (!config.appKey) missingFields.push('app.key');
|
|
202
|
-
if (!config.displayName) missingFields.push('app.name');
|
|
203
|
-
|
|
204
|
-
if (missingFields.length > 0) {
|
|
205
|
-
logger.error(chalk.red('ā Missing required fields in variables.yaml:'));
|
|
206
|
-
missingFields.forEach(field => logger.error(chalk.red(` - ${field}`)));
|
|
207
|
-
// Detect app type to show correct path
|
|
208
|
-
const { appPath } = await detectAppType(originalAppKey);
|
|
209
|
-
const relativePath = path.relative(process.cwd(), appPath);
|
|
210
|
-
logger.error(chalk.red(`\n Please update ${relativePath}/variables.yaml and try again.`));
|
|
211
|
-
process.exit(1);
|
|
212
|
-
}
|
|
213
38
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
registerApplicationSchema.configuration({
|
|
218
|
-
type: config.appType,
|
|
219
|
-
registryMode: config.registryMode,
|
|
220
|
-
port: config.port
|
|
221
|
-
});
|
|
222
|
-
} catch (error) {
|
|
223
|
-
logger.error(chalk.red(`ā Invalid configuration: ${error.message}`));
|
|
224
|
-
process.exit(1);
|
|
39
|
+
// Add optional fields only if they have values
|
|
40
|
+
if (appConfig.description || options.description) {
|
|
41
|
+
registrationData.description = appConfig.description || options.description;
|
|
225
42
|
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Check if user is authenticated and get token
|
|
230
|
-
* @async
|
|
231
|
-
* @param {string} [controllerUrl] - Optional controller URL from variables.yaml
|
|
232
|
-
* @param {string} [environment] - Optional environment key
|
|
233
|
-
* @returns {Promise<{apiUrl: string, token: string}>} Configuration with API URL and token
|
|
234
|
-
*/
|
|
235
|
-
async function checkAuthentication(controllerUrl, environment) {
|
|
236
|
-
const config = await getConfig();
|
|
237
|
-
|
|
238
|
-
// Try to get controller URL from parameter, config, or device tokens
|
|
239
|
-
let finalControllerUrl = controllerUrl;
|
|
240
|
-
let token = null;
|
|
241
43
|
|
|
242
|
-
//
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
if (
|
|
246
|
-
|
|
247
|
-
finalControllerUrl = deviceToken.controller;
|
|
44
|
+
// Handle external type vs non-external types differently
|
|
45
|
+
if (appConfig.appType === 'external') {
|
|
46
|
+
// For external type: include externalIntegration, exclude registryMode/port/image
|
|
47
|
+
if (appConfig.externalIntegration) {
|
|
48
|
+
registrationData.externalIntegration = appConfig.externalIntegration;
|
|
248
49
|
}
|
|
249
|
-
}
|
|
50
|
+
} else {
|
|
51
|
+
// For non-external types: include registryMode, port, image
|
|
52
|
+
registrationData.registryMode = appConfig.registryMode;
|
|
250
53
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (deviceUrls.length > 0) {
|
|
255
|
-
// Use first available device token
|
|
256
|
-
finalControllerUrl = deviceUrls[0];
|
|
257
|
-
const deviceToken = await getOrRefreshDeviceToken(finalControllerUrl);
|
|
258
|
-
if (deviceToken && deviceToken.token) {
|
|
259
|
-
token = deviceToken.token;
|
|
260
|
-
finalControllerUrl = deviceToken.controller;
|
|
261
|
-
}
|
|
54
|
+
// Port is required for non-external types
|
|
55
|
+
if (appConfig.port) {
|
|
56
|
+
registrationData.port = appConfig.port;
|
|
262
57
|
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// If still no token, check for client token (requires environment and app)
|
|
266
|
-
if (!token && environment) {
|
|
267
|
-
// For app register, we don't have an app yet, so client tokens won't work
|
|
268
|
-
// This is expected - device tokens should be used for registration
|
|
269
|
-
}
|
|
270
58
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
59
|
+
// Image is required for non-external types
|
|
60
|
+
if (appConfig.image) {
|
|
61
|
+
registrationData.image = appConfig.image;
|
|
62
|
+
}
|
|
275
63
|
}
|
|
276
64
|
|
|
277
|
-
return
|
|
278
|
-
apiUrl: finalControllerUrl,
|
|
279
|
-
token: token
|
|
280
|
-
};
|
|
65
|
+
return registrationData;
|
|
281
66
|
}
|
|
282
67
|
|
|
283
68
|
/**
|
|
284
|
-
*
|
|
69
|
+
* Save credentials to local secrets if localhost
|
|
285
70
|
* @async
|
|
71
|
+
* @param {Object} responseData - Registration response data
|
|
286
72
|
* @param {string} apiUrl - API URL
|
|
287
|
-
* @param {string} token - Authentication token
|
|
288
|
-
* @param {string} environment - Environment ID
|
|
289
|
-
* @param {Object} registrationData - Registration data
|
|
290
|
-
* @returns {Promise<Object>} API response
|
|
291
73
|
*/
|
|
292
|
-
async function
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
{
|
|
296
|
-
method: 'POST',
|
|
297
|
-
body: JSON.stringify(registrationData)
|
|
298
|
-
},
|
|
299
|
-
token
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
if (!response.success) {
|
|
303
|
-
const formattedError = response.formattedError || formatApiError(response);
|
|
304
|
-
logger.error(formattedError);
|
|
305
|
-
process.exit(1);
|
|
74
|
+
async function saveLocalCredentials(responseData, apiUrl) {
|
|
75
|
+
if (!isLocalhost(apiUrl)) {
|
|
76
|
+
return;
|
|
306
77
|
}
|
|
307
78
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// 1. Direct format: { application: {...}, credentials: {...} }
|
|
312
|
-
// 2. Wrapped format: { success: true, data: { application: {...}, credentials: {...} } }
|
|
313
|
-
const apiResponse = response.data;
|
|
314
|
-
if (apiResponse && apiResponse.data && apiResponse.data.application) {
|
|
315
|
-
// Wrapped format: use apiResponse.data
|
|
316
|
-
return apiResponse.data;
|
|
317
|
-
} else if (apiResponse && apiResponse.application) {
|
|
318
|
-
// Direct format: use apiResponse directly
|
|
319
|
-
return apiResponse;
|
|
320
|
-
}
|
|
321
|
-
// Fallback: return apiResponse as-is (shouldn't happen, but handle gracefully)
|
|
322
|
-
logger.error(chalk.red('ā Invalid response: missing application data'));
|
|
323
|
-
logger.error(chalk.gray('\nFull response for debugging:'));
|
|
324
|
-
logger.error(chalk.gray(JSON.stringify(response, null, 2)));
|
|
325
|
-
process.exit(1);
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Get environment prefix for GitHub Secrets
|
|
331
|
-
* @param {string} environment - Environment key (e.g., 'dev', 'tst', 'pro', 'miso')
|
|
332
|
-
* @returns {string} Uppercase prefix (e.g., 'DEV', 'TST', 'PRO', 'MISO')
|
|
333
|
-
*/
|
|
334
|
-
function getEnvironmentPrefix(environment) {
|
|
335
|
-
if (!environment) {
|
|
336
|
-
return 'DEV';
|
|
337
|
-
}
|
|
338
|
-
// Convert to uppercase and handle common variations
|
|
339
|
-
const env = environment.toLowerCase();
|
|
340
|
-
if (env === 'dev' || env === 'development') {
|
|
341
|
-
return 'DEV';
|
|
342
|
-
}
|
|
343
|
-
if (env === 'tst' || env === 'test' || env === 'staging') {
|
|
344
|
-
return 'TST';
|
|
345
|
-
}
|
|
346
|
-
if (env === 'pro' || env === 'prod' || env === 'production') {
|
|
347
|
-
return 'PRO';
|
|
348
|
-
}
|
|
349
|
-
// For other environments (e.g., 'miso'), uppercase the entire string
|
|
350
|
-
// Use full string if 4 characters or less, otherwise use first 4 characters
|
|
351
|
-
const upper = environment.toUpperCase();
|
|
352
|
-
return upper.length <= 4 ? upper : upper.substring(0, 4);
|
|
353
|
-
}
|
|
79
|
+
const registeredAppKey = responseData.application.key;
|
|
80
|
+
const clientIdKey = `${registeredAppKey}-client-idKeyVault`;
|
|
81
|
+
const clientSecretKey = `${registeredAppKey}-client-secretKeyVault`;
|
|
354
82
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
* @param {string} apiUrl - API URL
|
|
359
|
-
* @param {string} environment - Environment key
|
|
360
|
-
*/
|
|
361
|
-
function displayRegistrationResults(data, apiUrl, environment) {
|
|
362
|
-
logger.log(chalk.green('ā
Application registered successfully!\n'));
|
|
363
|
-
logger.log(chalk.bold('š Application Details:'));
|
|
364
|
-
logger.log(` ID: ${data.application.id}`);
|
|
365
|
-
logger.log(` Key: ${data.application.key}`);
|
|
366
|
-
logger.log(` Display Name: ${data.application.displayName}\n`);
|
|
83
|
+
try {
|
|
84
|
+
await saveLocalSecret(clientIdKey, responseData.credentials.clientId);
|
|
85
|
+
await saveLocalSecret(clientSecretKey, responseData.credentials.clientSecret);
|
|
367
86
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
logger.log(chalk.yellow(` Client Secret: ${data.credentials.clientSecret}\n`));
|
|
87
|
+
// Update env.template
|
|
88
|
+
await updateEnvTemplate(registeredAppKey, clientIdKey, clientSecretKey, apiUrl);
|
|
371
89
|
|
|
372
|
-
|
|
90
|
+
// Regenerate .env file with updated credentials
|
|
91
|
+
try {
|
|
92
|
+
await generateEnvFile(registeredAppKey, null, 'local');
|
|
93
|
+
logger.log(chalk.green('ā .env file updated with new credentials'));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
logger.warn(chalk.yellow(`ā ļø Could not regenerate .env file: ${error.message}`));
|
|
96
|
+
}
|
|
373
97
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
logger.log(chalk.cyan(` ${envPrefix}_MISO_CLIENTID = ${data.credentials.clientId}`));
|
|
380
|
-
logger.log(chalk.cyan(` ${envPrefix}_MISO_CLIENTSECRET = ${data.credentials.clientSecret}\n`));
|
|
98
|
+
logger.log(chalk.green('\nā Credentials saved to ~/.aifabrix/secrets.local.yaml'));
|
|
99
|
+
logger.log(chalk.green('ā env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
|
|
100
|
+
} catch (error) {
|
|
101
|
+
logger.warn(chalk.yellow(`ā ļø Could not save credentials locally: ${error.message}`));
|
|
102
|
+
}
|
|
381
103
|
}
|
|
382
104
|
|
|
383
105
|
/**
|
|
@@ -396,49 +118,21 @@ async function registerApplication(appKey, options) {
|
|
|
396
118
|
|
|
397
119
|
// Load variables.yaml
|
|
398
120
|
const { variables, created } = await loadVariablesYaml(appKey);
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (created) {
|
|
403
|
-
finalVariables = await createMinimalAppIfNeeded(appKey, options);
|
|
404
|
-
}
|
|
121
|
+
const finalVariables = created
|
|
122
|
+
? await createMinimalAppIfNeeded(appKey, options)
|
|
123
|
+
: variables;
|
|
405
124
|
|
|
406
|
-
// Extract configuration
|
|
407
|
-
const appConfig = extractAppConfiguration(finalVariables, appKey, options);
|
|
408
|
-
|
|
409
|
-
// Validate configuration (pass original appKey for error messages)
|
|
125
|
+
// Extract and validate configuration
|
|
126
|
+
const appConfig = await extractAppConfiguration(finalVariables, appKey, options);
|
|
410
127
|
await validateAppRegistrationData(appConfig, appKey);
|
|
411
128
|
|
|
412
|
-
//
|
|
129
|
+
// Authenticate and get API configuration
|
|
413
130
|
const controllerUrl = finalVariables?.deployment?.controllerUrl;
|
|
414
|
-
|
|
415
|
-
// Check authentication (try device token first, supports registration flow)
|
|
416
131
|
const authConfig = await checkAuthentication(controllerUrl, options.environment);
|
|
417
|
-
|
|
418
|
-
// Validate environment
|
|
419
132
|
const environment = registerApplicationSchema.environmentId(options.environment);
|
|
420
133
|
|
|
421
|
-
// Prepare registration data to match OpenAPI RegisterApplicationRequest schema
|
|
422
|
-
// Schema: { key, displayName, description?, configuration: { type, registryMode, port?, image? } }
|
|
423
|
-
const registrationData = {
|
|
424
|
-
key: appConfig.appKey,
|
|
425
|
-
displayName: appConfig.displayName,
|
|
426
|
-
configuration: {
|
|
427
|
-
type: appConfig.appType,
|
|
428
|
-
registryMode: appConfig.registryMode
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
// Add optional fields only if they have values
|
|
433
|
-
if (appConfig.description || options.description) {
|
|
434
|
-
registrationData.description = appConfig.description || options.description;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
if (appConfig.port) {
|
|
438
|
-
registrationData.configuration.port = appConfig.port;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
134
|
// Register application
|
|
135
|
+
const registrationData = buildRegistrationData(appConfig, options);
|
|
442
136
|
const responseData = await callRegisterApi(
|
|
443
137
|
authConfig.apiUrl,
|
|
444
138
|
authConfig.token,
|
|
@@ -446,35 +140,8 @@ async function registerApplication(appKey, options) {
|
|
|
446
140
|
registrationData
|
|
447
141
|
);
|
|
448
142
|
|
|
449
|
-
// Save credentials
|
|
450
|
-
|
|
451
|
-
const registeredAppKey = responseData.application.key;
|
|
452
|
-
const clientIdKey = `${registeredAppKey}-client-idKeyVault`;
|
|
453
|
-
const clientSecretKey = `${registeredAppKey}-client-secretKeyVault`;
|
|
454
|
-
|
|
455
|
-
try {
|
|
456
|
-
await saveLocalSecret(clientIdKey, responseData.credentials.clientId);
|
|
457
|
-
await saveLocalSecret(clientSecretKey, responseData.credentials.clientSecret);
|
|
458
|
-
|
|
459
|
-
// Update env.template
|
|
460
|
-
await updateEnvTemplate(registeredAppKey, clientIdKey, clientSecretKey, authConfig.apiUrl);
|
|
461
|
-
|
|
462
|
-
// Regenerate .env file with updated credentials
|
|
463
|
-
try {
|
|
464
|
-
await generateEnvFile(registeredAppKey, null, 'local');
|
|
465
|
-
logger.log(chalk.green('ā .env file updated with new credentials'));
|
|
466
|
-
} catch (error) {
|
|
467
|
-
logger.warn(chalk.yellow(`ā ļø Could not regenerate .env file: ${error.message}`));
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
logger.log(chalk.green('\nā Credentials saved to ~/.aifabrix/secrets.local.yaml'));
|
|
471
|
-
logger.log(chalk.green('ā env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
|
|
472
|
-
} catch (error) {
|
|
473
|
-
logger.warn(chalk.yellow(`ā ļø Could not save credentials locally: ${error.message}`));
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// Display results
|
|
143
|
+
// Save credentials and display results
|
|
144
|
+
await saveLocalCredentials(responseData, authConfig.apiUrl);
|
|
478
145
|
displayRegistrationResults(responseData, authConfig.apiUrl, environment);
|
|
479
146
|
}
|
|
480
147
|
|
package/lib/app-rotate-secret.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const chalk = require('chalk');
|
|
12
12
|
const { getConfig } = require('./config');
|
|
13
13
|
const { getOrRefreshDeviceToken } = require('./utils/token-manager');
|
|
14
|
-
const {
|
|
14
|
+
const { rotateApplicationSecret } = require('./api/applications.api');
|
|
15
15
|
const { formatApiError } = require('./utils/api-error-handler');
|
|
16
16
|
const logger = require('./utils/logger');
|
|
17
17
|
const { saveLocalSecret, isLocalhost } = require('./utils/local-secrets');
|
|
@@ -115,15 +115,9 @@ async function rotateSecret(appKey, options) {
|
|
|
115
115
|
// Validate environment
|
|
116
116
|
validateEnvironment(options.environment);
|
|
117
117
|
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
const response = await
|
|
121
|
-
`${controllerUrl}/api/v1/environments/${encodeURIComponent(options.environment)}/applications/${encodeURIComponent(appKey)}/rotate-secret`,
|
|
122
|
-
{
|
|
123
|
-
method: 'POST'
|
|
124
|
-
},
|
|
125
|
-
token
|
|
126
|
-
);
|
|
118
|
+
// Use centralized API client
|
|
119
|
+
const authConfig = { type: 'bearer', token: token };
|
|
120
|
+
const response = await rotateApplicationSecret(controllerUrl, options.environment, appKey, authConfig);
|
|
127
121
|
|
|
128
122
|
if (!response.success) {
|
|
129
123
|
const formattedError = response.formattedError || formatApiError(response);
|
package/lib/commands/login.js
CHANGED
|
@@ -13,7 +13,8 @@ const inquirer = require('inquirer');
|
|
|
13
13
|
const chalk = require('chalk');
|
|
14
14
|
const ora = require('ora');
|
|
15
15
|
const { setCurrentEnvironment, saveDeviceToken, saveClientToken } = require('../config');
|
|
16
|
-
const {
|
|
16
|
+
const { getToken, initiateDeviceCodeFlow } = require('../api/auth.api');
|
|
17
|
+
const { pollDeviceCodeToken, displayDeviceCodeInfo } = require('../utils/api');
|
|
17
18
|
const { formatApiError } = require('../utils/api-error-handler');
|
|
18
19
|
const { loadClientCredentials } = require('../utils/token-manager');
|
|
19
20
|
const logger = require('../utils/logger');
|
|
@@ -187,16 +188,8 @@ async function handleCredentialsLogin(controllerUrl, appName, clientId, clientSe
|
|
|
187
188
|
credentials = await promptForCredentials(clientId, clientSecret);
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
const response = await makeApiCall(`${controllerUrl}/api/v1/auth/token`, {
|
|
193
|
-
method: 'POST',
|
|
194
|
-
headers: {
|
|
195
|
-
'Content-Type': 'application/json',
|
|
196
|
-
'x-client-id': credentials.clientId,
|
|
197
|
-
'x-client-secret': credentials.clientSecret
|
|
198
|
-
}
|
|
199
|
-
});
|
|
191
|
+
// Use centralized API client for token generation
|
|
192
|
+
const response = await getToken(credentials.clientId, credentials.clientSecret, controllerUrl);
|
|
200
193
|
|
|
201
194
|
if (!response.success) {
|
|
202
195
|
const formattedError = response.formattedError || formatApiError(response);
|
|
@@ -346,7 +339,21 @@ async function handleDeviceCodeLogin(controllerUrl, environment, offline, scope)
|
|
|
346
339
|
}
|
|
347
340
|
|
|
348
341
|
try {
|
|
349
|
-
|
|
342
|
+
// Use centralized API client for device code flow initiation
|
|
343
|
+
const deviceCodeApiResponse = await initiateDeviceCodeFlow(controllerUrl, envKey, requestScope);
|
|
344
|
+
|
|
345
|
+
// Handle API response format: { success: boolean, data: DeviceCodeResponse }
|
|
346
|
+
const apiResponse = deviceCodeApiResponse.data;
|
|
347
|
+
const deviceCodeData = apiResponse.data || apiResponse;
|
|
348
|
+
|
|
349
|
+
// Convert camelCase from API to snake_case for compatibility with existing code
|
|
350
|
+
const deviceCodeResponse = {
|
|
351
|
+
device_code: deviceCodeData.deviceCode || deviceCodeData.device_code,
|
|
352
|
+
user_code: deviceCodeData.userCode || deviceCodeData.user_code,
|
|
353
|
+
verification_uri: deviceCodeData.verificationUri || deviceCodeData.verification_uri,
|
|
354
|
+
expires_in: deviceCodeData.expiresIn || deviceCodeData.expires_in || 600,
|
|
355
|
+
interval: deviceCodeData.interval || 5
|
|
356
|
+
};
|
|
350
357
|
|
|
351
358
|
displayDeviceCodeInfo(deviceCodeResponse.user_code, deviceCodeResponse.verification_uri, logger, chalk);
|
|
352
359
|
|
package/lib/datasource-deploy.js
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const chalk = require('chalk');
|
|
14
14
|
const { getDeploymentAuth } = require('./utils/token-manager');
|
|
15
|
-
const {
|
|
15
|
+
const { getEnvironmentApplication } = require('./api/environments.api');
|
|
16
|
+
const { publishDatasourceViaPipeline } = require('./api/pipeline.api');
|
|
16
17
|
const { formatApiError } = require('./utils/api-error-handler');
|
|
17
18
|
const logger = require('./utils/logger');
|
|
18
19
|
const { validateDatasourceFile } = require('./datasource-validate');
|
|
@@ -30,18 +31,8 @@ const { validateDatasourceFile } = require('./datasource-validate');
|
|
|
30
31
|
* @throws {Error} If dataplane URL cannot be retrieved
|
|
31
32
|
*/
|
|
32
33
|
async function getDataplaneUrl(controllerUrl, appKey, environment, authConfig) {
|
|
33
|
-
// Call controller API to get application details
|
|
34
|
-
|
|
35
|
-
const endpoint = `${controllerUrl}/api/v1/environments/${environment}/applications/${appKey}`;
|
|
36
|
-
|
|
37
|
-
let response;
|
|
38
|
-
if (authConfig.type === 'bearer' && authConfig.token) {
|
|
39
|
-
response = await authenticatedApiCall(endpoint, {}, authConfig.token);
|
|
40
|
-
} else {
|
|
41
|
-
// For credentials, we'd need to use a different API call method
|
|
42
|
-
// For now, use bearer token approach
|
|
43
|
-
throw new Error('Bearer token authentication required for getting dataplane URL');
|
|
44
|
-
}
|
|
34
|
+
// Call controller API to get application details using centralized API client
|
|
35
|
+
const response = await getEnvironmentApplication(controllerUrl, environment, appKey, authConfig);
|
|
45
36
|
|
|
46
37
|
if (!response.success || !response.data) {
|
|
47
38
|
const formattedError = response.formattedError || formatApiError(response);
|
|
@@ -129,24 +120,10 @@ async function deployDatasource(appKey, filePath, options) {
|
|
|
129
120
|
const dataplaneUrl = await getDataplaneUrl(options.controller, appKey, options.environment, authConfig);
|
|
130
121
|
logger.log(chalk.green(`ā Dataplane URL: ${dataplaneUrl}`));
|
|
131
122
|
|
|
132
|
-
// Publish to dataplane
|
|
123
|
+
// Publish to dataplane using pipeline workflow endpoint
|
|
133
124
|
logger.log(chalk.blue('\nš Publishing datasource to dataplane...'));
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// Prepare publish request - send datasource configuration directly
|
|
137
|
-
let publishResponse;
|
|
138
|
-
if (authConfig.type === 'bearer' && authConfig.token) {
|
|
139
|
-
publishResponse = await authenticatedApiCall(
|
|
140
|
-
publishEndpoint,
|
|
141
|
-
{
|
|
142
|
-
method: 'POST',
|
|
143
|
-
body: JSON.stringify(datasourceConfig)
|
|
144
|
-
},
|
|
145
|
-
authConfig.token
|
|
146
|
-
);
|
|
147
|
-
} else {
|
|
148
|
-
throw new Error('Bearer token authentication required for dataplane publish');
|
|
149
|
-
}
|
|
125
|
+
|
|
126
|
+
const publishResponse = await publishDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig, datasourceConfig);
|
|
150
127
|
|
|
151
128
|
if (!publishResponse.success) {
|
|
152
129
|
const formattedError = publishResponse.formattedError || formatApiError(publishResponse);
|