@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/generator.js CHANGED
@@ -11,116 +11,13 @@
11
11
 
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
- const yaml = require('js-yaml');
15
- const Ajv = require('ajv');
16
- const _secrets = require('./secrets');
17
14
  const _keyGenerator = require('./key-generator');
18
15
  const _validator = require('./validator');
19
16
  const builders = require('./generator-builders');
20
17
  const { detectAppType, getDeployJsonPath } = require('./utils/paths');
21
18
  const splitFunctions = require('./generator-split');
22
-
23
- /**
24
- * Loads variables.yaml file
25
- * @param {string} variablesPath - Path to variables.yaml
26
- * @returns {Object} Parsed variables
27
- * @throws {Error} If file not found or invalid YAML
28
- */
29
- function loadVariables(variablesPath) {
30
- if (!fs.existsSync(variablesPath)) {
31
- throw new Error(`variables.yaml not found: ${variablesPath}`);
32
- }
33
-
34
- const variablesContent = fs.readFileSync(variablesPath, 'utf8');
35
- try {
36
- return { content: variablesContent, parsed: yaml.load(variablesContent) };
37
- } catch (error) {
38
- throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
39
- }
40
- }
41
-
42
- /**
43
- * Loads env.template file
44
- * @param {string} templatePath - Path to env.template
45
- * @returns {string} Template content
46
- * @throws {Error} If file not found
47
- */
48
- function loadEnvTemplate(templatePath) {
49
- if (!fs.existsSync(templatePath)) {
50
- throw new Error(`env.template not found: ${templatePath}`);
51
- }
52
- return fs.readFileSync(templatePath, 'utf8');
53
- }
54
-
55
- /**
56
- * Loads rbac.yaml file if it exists
57
- * @param {string} rbacPath - Path to rbac.yaml
58
- * @returns {Object|null} Parsed RBAC configuration or null
59
- * @throws {Error} If file exists but has invalid YAML
60
- */
61
- function loadRbac(rbacPath) {
62
- if (!fs.existsSync(rbacPath)) {
63
- return null;
64
- }
65
-
66
- const rbacContent = fs.readFileSync(rbacPath, 'utf8');
67
- try {
68
- return yaml.load(rbacContent);
69
- } catch (error) {
70
- throw new Error(`Invalid YAML syntax in rbac.yaml: ${error.message}`);
71
- }
72
- }
73
-
74
- /**
75
- * Generates external system <app-name>-deploy.json by loading the system JSON file
76
- * For external systems, the system JSON file is already created and we just need to reference it
77
- * @async
78
- * @function generateExternalSystemDeployJson
79
- * @param {string} appName - Name of the application
80
- * @param {string} appPath - Path to application directory (integration or builder)
81
- * @returns {Promise<string>} Path to generated <app-name>-deploy.json file
82
- * @throws {Error} If generation fails
83
- */
84
- async function generateExternalSystemDeployJson(appName, appPath) {
85
- if (!appName || typeof appName !== 'string') {
86
- throw new Error('App name is required and must be a string');
87
- }
88
-
89
- const variablesPath = path.join(appPath, 'variables.yaml');
90
- const { parsed: variables } = loadVariables(variablesPath);
91
-
92
- if (!variables.externalIntegration) {
93
- throw new Error('externalIntegration block not found in variables.yaml');
94
- }
95
-
96
- // For external systems, the system JSON file should be in the same folder
97
- // Check if it already exists (should be <app-name>-deploy.json)
98
- const deployJsonPath = getDeployJsonPath(appName, 'external', true);
99
- const systemFileName = variables.externalIntegration.systems && variables.externalIntegration.systems.length > 0
100
- ? variables.externalIntegration.systems[0]
101
- : `${appName}-deploy.json`;
102
-
103
- // Resolve system file path (schemaBasePath is usually './' for same folder)
104
- const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
105
- const systemFilePath = path.isAbsolute(schemaBasePath)
106
- ? path.join(schemaBasePath, systemFileName)
107
- : path.join(appPath, schemaBasePath, systemFileName);
108
-
109
- // If system file doesn't exist, throw error (it should be created manually or via external-system-generator)
110
- if (!fs.existsSync(systemFilePath)) {
111
- throw new Error(`External system file not found: ${systemFilePath}. Please create it first.`);
112
- }
113
-
114
- // Read the system JSON file
115
- const systemContent = await fs.promises.readFile(systemFilePath, 'utf8');
116
- const systemJson = JSON.parse(systemContent);
117
-
118
- // Write it as <app-name>-deploy.json (consistent naming)
119
- const jsonContent = JSON.stringify(systemJson, null, 2);
120
- await fs.promises.writeFile(deployJsonPath, jsonContent, { mode: 0o644, encoding: 'utf8' });
121
-
122
- return deployJsonPath;
123
- }
19
+ const { loadVariables, loadEnvTemplate, loadRbac, parseEnvironmentVariables } = require('./generator-helpers');
20
+ const { generateExternalSystemDeployJson, generateExternalSystemApplicationSchema } = require('./generator-external');
124
21
 
125
22
  /**
126
23
  * Generates deployment JSON from application configuration files
@@ -188,139 +85,6 @@ async function generateDeployJson(appName) {
188
85
  return jsonPath;
189
86
  }
190
87
 
191
- /**
192
- * Validates portalInput structure against schema requirements
193
- * @param {Object} portalInput - Portal input configuration to validate
194
- * @param {string} variableName - Variable name for error messages
195
- * @throws {Error} If portalInput structure is invalid
196
- */
197
- function validatePortalInput(portalInput, variableName) {
198
- if (!portalInput || typeof portalInput !== 'object') {
199
- throw new Error(`Invalid portalInput for variable '${variableName}': must be an object`);
200
- }
201
-
202
- // Check required fields
203
- if (!portalInput.field || typeof portalInput.field !== 'string') {
204
- throw new Error(`Invalid portalInput for variable '${variableName}': field is required and must be a string`);
205
- }
206
-
207
- if (!portalInput.label || typeof portalInput.label !== 'string') {
208
- throw new Error(`Invalid portalInput for variable '${variableName}': label is required and must be a string`);
209
- }
210
-
211
- // Validate field type
212
- const validFieldTypes = ['password', 'text', 'textarea', 'select'];
213
- if (!validFieldTypes.includes(portalInput.field)) {
214
- throw new Error(`Invalid portalInput for variable '${variableName}': field must be one of: ${validFieldTypes.join(', ')}`);
215
- }
216
-
217
- // Validate select field requires options
218
- if (portalInput.field === 'select') {
219
- if (!portalInput.options || !Array.isArray(portalInput.options) || portalInput.options.length === 0) {
220
- throw new Error(`Invalid portalInput for variable '${variableName}': select field requires a non-empty options array`);
221
- }
222
- }
223
-
224
- // Validate optional fields
225
- if (portalInput.placeholder !== undefined && typeof portalInput.placeholder !== 'string') {
226
- throw new Error(`Invalid portalInput for variable '${variableName}': placeholder must be a string`);
227
- }
228
-
229
- if (portalInput.masked !== undefined && typeof portalInput.masked !== 'boolean') {
230
- throw new Error(`Invalid portalInput for variable '${variableName}': masked must be a boolean`);
231
- }
232
-
233
- if (portalInput.validation !== undefined) {
234
- if (typeof portalInput.validation !== 'object' || Array.isArray(portalInput.validation)) {
235
- throw new Error(`Invalid portalInput for variable '${variableName}': validation must be an object`);
236
- }
237
- }
238
-
239
- if (portalInput.options !== undefined && portalInput.field !== 'select') {
240
- // Options should only be present for select fields
241
- if (Array.isArray(portalInput.options) && portalInput.options.length > 0) {
242
- throw new Error(`Invalid portalInput for variable '${variableName}': options can only be used with select field type`);
243
- }
244
- }
245
- }
246
-
247
- /**
248
- * Parses environment variables from env.template and merges portalInput from variables.yaml
249
- * @param {string} envTemplate - Content of env.template file
250
- * @param {Object|null} [variablesConfig=null] - Optional configuration from variables.yaml
251
- * @returns {Array<Object>} Configuration array with merged portalInput
252
- * @throws {Error} If portalInput structure is invalid
253
- */
254
- function parseEnvironmentVariables(envTemplate, variablesConfig = null) {
255
- const configuration = [];
256
- const lines = envTemplate.split('\n');
257
-
258
- // Create a map of portalInput configurations by variable name
259
- const portalInputMap = new Map();
260
- if (variablesConfig && variablesConfig.configuration && Array.isArray(variablesConfig.configuration)) {
261
- for (const configItem of variablesConfig.configuration) {
262
- if (configItem.name && configItem.portalInput) {
263
- // Validate portalInput before adding to map
264
- validatePortalInput(configItem.portalInput, configItem.name);
265
- portalInputMap.set(configItem.name, configItem.portalInput);
266
- }
267
- }
268
- }
269
-
270
- for (const line of lines) {
271
- const trimmed = line.trim();
272
-
273
- // Skip empty lines and comments
274
- if (!trimmed || trimmed.startsWith('#')) {
275
- continue;
276
- }
277
-
278
- // Parse KEY=VALUE format
279
- const equalIndex = trimmed.indexOf('=');
280
- if (equalIndex === -1) {
281
- continue;
282
- }
283
-
284
- const key = trimmed.substring(0, equalIndex).trim();
285
- const value = trimmed.substring(equalIndex + 1).trim();
286
-
287
- if (!key || !value) {
288
- continue;
289
- }
290
-
291
- // Determine location and required status
292
- let location = 'variable';
293
- let required = false;
294
-
295
- if (value.startsWith('kv://')) {
296
- location = 'keyvault';
297
- required = true;
298
- }
299
-
300
- // Check if it's a sensitive variable
301
- const sensitiveKeys = ['password', 'secret', 'key', 'token', 'auth'];
302
- if (sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive))) {
303
- required = true;
304
- }
305
-
306
- const configItem = {
307
- name: key,
308
- value: value.replace('kv://', ''), // Remove kv:// prefix for KeyVault
309
- location,
310
- required
311
- };
312
-
313
- // Merge portalInput if it exists in variables.yaml
314
- if (portalInputMap.has(key)) {
315
- configItem.portalInput = portalInputMap.get(key);
316
- }
317
-
318
- configuration.push(configItem);
319
- }
320
-
321
- return configuration;
322
- }
323
-
324
88
  async function generateDeployJsonWithValidation(appName) {
325
89
  const jsonPath = await generateDeployJson(appName);
326
90
  const jsonContent = fs.readFileSync(jsonPath, 'utf8');
@@ -348,135 +112,6 @@ async function generateDeployJsonWithValidation(appName) {
348
112
  };
349
113
  }
350
114
 
351
- /**
352
- * Generates application-schema.json structure for external systems
353
- * Combines system and datasource JSONs into application-level deployment format
354
- * @async
355
- * @function generateExternalSystemApplicationSchema
356
- * @param {string} appName - Application name
357
- * @returns {Promise<Object>} Application schema object
358
- * @throws {Error} If generation fails
359
- */
360
- async function generateExternalSystemApplicationSchema(appName) {
361
- if (!appName || typeof appName !== 'string') {
362
- throw new Error('App name is required and must be a string');
363
- }
364
-
365
- const { appPath } = await detectAppType(appName);
366
- const variablesPath = path.join(appPath, 'variables.yaml');
367
-
368
- // Load variables.yaml
369
- const { parsed: variables } = loadVariables(variablesPath);
370
-
371
- if (!variables.externalIntegration) {
372
- throw new Error('externalIntegration block not found in variables.yaml');
373
- }
374
-
375
- // Load system file
376
- const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
377
- const systemFiles = variables.externalIntegration.systems || [];
378
-
379
- if (systemFiles.length === 0) {
380
- throw new Error('No system files specified in externalIntegration.systems');
381
- }
382
-
383
- const systemFileName = systemFiles[0];
384
- const systemFilePath = path.isAbsolute(schemaBasePath)
385
- ? path.join(schemaBasePath, systemFileName)
386
- : path.join(appPath, schemaBasePath, systemFileName);
387
-
388
- if (!fs.existsSync(systemFilePath)) {
389
- throw new Error(`System file not found: ${systemFilePath}`);
390
- }
391
-
392
- const systemContent = await fs.promises.readFile(systemFilePath, 'utf8');
393
- const systemJson = JSON.parse(systemContent);
394
-
395
- // Load datasource files
396
- const datasourceFiles = variables.externalIntegration.dataSources || [];
397
- const datasourceJsons = [];
398
-
399
- for (const datasourceFile of datasourceFiles) {
400
- const datasourcePath = path.isAbsolute(schemaBasePath)
401
- ? path.join(schemaBasePath, datasourceFile)
402
- : path.join(appPath, schemaBasePath, datasourceFile);
403
-
404
- if (!fs.existsSync(datasourcePath)) {
405
- throw new Error(`Datasource file not found: ${datasourcePath}`);
406
- }
407
-
408
- const datasourceContent = await fs.promises.readFile(datasourcePath, 'utf8');
409
- const datasourceJson = JSON.parse(datasourceContent);
410
- datasourceJsons.push(datasourceJson);
411
- }
412
-
413
- // Build application-schema.json structure
414
- const applicationSchema = {
415
- version: variables.externalIntegration.version || '1.0.0',
416
- application: systemJson,
417
- dataSources: datasourceJsons
418
- };
419
-
420
- // Validate individual components against their schemas
421
- const externalSystemSchema = require('./schema/external-system.schema.json');
422
- const externalDatasourceSchema = require('./schema/external-datasource.schema.json');
423
-
424
- // For draft-2020-12 schemas, remove $schema to avoid AJV issues (similar to schema-loader.js)
425
- const datasourceSchemaToAdd = { ...externalDatasourceSchema };
426
- if (datasourceSchemaToAdd.$schema && datasourceSchemaToAdd.$schema.includes('2020-12')) {
427
- delete datasourceSchemaToAdd.$schema;
428
- }
429
-
430
- const ajv = new Ajv({ allErrors: true, strict: false, removeAdditional: false });
431
-
432
- // Validate application (system) against external-system schema
433
- const externalSystemSchemaId = externalSystemSchema.$id || 'https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-system.schema.json';
434
- ajv.addSchema(externalSystemSchema, externalSystemSchemaId);
435
- const validateSystem = ajv.compile(externalSystemSchema);
436
- const systemValid = validateSystem(systemJson);
437
-
438
- if (!systemValid) {
439
- // Filter out additionalProperties errors for required properties that aren't defined in schema
440
- // This handles schema inconsistencies where authentication is required but not defined in properties
441
- const filteredErrors = validateSystem.errors.filter(err => {
442
- if (err.keyword === 'additionalProperties' && err.params?.additionalProperty === 'authentication') {
443
- // Check if authentication is in required array
444
- const required = externalSystemSchema.required || [];
445
- if (required.includes('authentication')) {
446
- return false; // Ignore this error since authentication is required but not defined
447
- }
448
- }
449
- return true;
450
- });
451
-
452
- if (filteredErrors.length > 0) {
453
- const errors = filteredErrors.map(err => {
454
- const path = err.instancePath || err.schemaPath;
455
- return `${path} ${err.message}`;
456
- }).join(', ');
457
- throw new Error(`System JSON does not match external-system schema: ${errors}`);
458
- }
459
- }
460
-
461
- // Validate each datasource against external-datasource schema
462
- const externalDatasourceSchemaId = datasourceSchemaToAdd.$id || 'https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-datasource.schema.json';
463
- ajv.addSchema(datasourceSchemaToAdd, externalDatasourceSchemaId);
464
- const validateDatasource = ajv.compile(datasourceSchemaToAdd);
465
-
466
- for (let i = 0; i < datasourceJsons.length; i++) {
467
- const datasourceValid = validateDatasource(datasourceJsons[i]);
468
- if (!datasourceValid) {
469
- const errors = validateDatasource.errors.map(err => {
470
- const path = err.instancePath || err.schemaPath;
471
- return `${path} ${err.message}`;
472
- }).join(', ');
473
- throw new Error(`Datasource ${i + 1} (${datasourceJsons[i].key || 'unknown'}) does not match external-datasource schema: ${errors}`);
474
- }
475
- }
476
-
477
- return applicationSchema;
478
- }
479
-
480
115
  module.exports = {
481
116
  generateDeployJson,
482
117
  generateDeployJsonWithValidation,
@@ -7,7 +7,7 @@
7
7
  "key": "external-system-schema",
8
8
  "name": "External System Configuration Schema",
9
9
  "description": "JSON schema for validating ExternalSystem configuration files",
10
- "version": "1.0.0",
10
+ "version": "1.1.0",
11
11
  "type": "schema",
12
12
  "category": "integration",
13
13
  "author": "AI Fabrix Team",
@@ -27,6 +27,17 @@
27
27
  ],
28
28
  "dependencies": [],
29
29
  "changelog": [
30
+ {
31
+ "version": "1.1.0",
32
+ "date": "2025-12-01T00:00:00Z",
33
+ "changes": [
34
+ "Added RBAC roles and permissions support",
35
+ "RBAC roles/permissions are registered with miso-controller during deployment",
36
+ "Roles support Azure AD group mapping via Groups property",
37
+ "Permissions reference roles and are used for access control"
38
+ ],
39
+ "breaking": false
40
+ },
30
41
  {
31
42
  "version": "1.0.0",
32
43
  "date": "2024-01-01T00:00:00Z",
@@ -274,6 +285,86 @@
274
285
  "items": {
275
286
  "type": "string"
276
287
  }
288
+ },
289
+ "roles": {
290
+ "type": "array",
291
+ "description": "External system roles for Azure AD group mapping",
292
+ "items": {
293
+ "type": "object",
294
+ "required": [
295
+ "name",
296
+ "value",
297
+ "description"
298
+ ],
299
+ "properties": {
300
+ "name": {
301
+ "type": "string",
302
+ "description": "Human-readable role name",
303
+ "minLength": 1,
304
+ "maxLength": 100
305
+ },
306
+ "value": {
307
+ "type": "string",
308
+ "description": "Role identifier (used in JWT and ACL)",
309
+ "pattern": "^[a-z-]+$"
310
+ },
311
+ "description": {
312
+ "type": "string",
313
+ "description": "Role description",
314
+ "minLength": 1,
315
+ "maxLength": 500
316
+ },
317
+ "Groups": {
318
+ "type": "array",
319
+ "description": "Azure AD groups mapped to this role",
320
+ "items": {
321
+ "type": "string",
322
+ "minLength": 1,
323
+ "maxLength": 100
324
+ }
325
+ }
326
+ },
327
+ "additionalProperties": false
328
+ }
329
+ },
330
+ "permissions": {
331
+ "type": "array",
332
+ "description": "External system permissions with role mappings for access control",
333
+ "items": {
334
+ "type": "object",
335
+ "required": [
336
+ "name",
337
+ "roles",
338
+ "description"
339
+ ],
340
+ "properties": {
341
+ "name": {
342
+ "type": "string",
343
+ "description": "Permission identifier (e.g., 'documentstore:read', 'flowise:dev:access')",
344
+ "pattern": "^[a-z0-9-:]+$",
345
+ "minLength": 1,
346
+ "maxLength": 100
347
+ },
348
+ "roles": {
349
+ "type": "array",
350
+ "description": "Roles that have this permission",
351
+ "items": {
352
+ "type": "string",
353
+ "pattern": "^[a-z-]+$",
354
+ "minLength": 1,
355
+ "maxLength": 50
356
+ },
357
+ "minItems": 1
358
+ },
359
+ "description": {
360
+ "type": "string",
361
+ "description": "Permission description",
362
+ "minLength": 1,
363
+ "maxLength": 500
364
+ }
365
+ },
366
+ "additionalProperties": false
367
+ }
277
368
  }
278
369
  },
279
370
  "additionalProperties": false
@@ -25,9 +25,10 @@ const { formatNetworkError } = require('./error-formatters/network-errors');
25
25
  /**
26
26
  * Formats error for display in CLI
27
27
  * @param {Object} apiResponse - API response object from makeApiCall
28
+ * @param {string} [controllerUrl] - Controller URL (optional)
28
29
  * @returns {string} Formatted error message
29
30
  */
30
- function formatApiError(apiResponse) {
31
+ function formatApiError(apiResponse, controllerUrl = null) {
31
32
  if (!apiResponse || apiResponse.success !== false) {
32
33
  return chalk.red('❌ Unknown error occurred');
33
34
  }
@@ -41,7 +42,13 @@ function formatApiError(apiResponse) {
41
42
  const statusCode = apiResponse.status || 0;
42
43
  const isNetworkError = apiResponse.network === true;
43
44
 
44
- const parsed = parseErrorResponse(errorResponse, statusCode, isNetworkError);
45
+ // Add controller URL to error data
46
+ // Handle both object and string error responses
47
+ const errorData = typeof errorResponse === 'object' && errorResponse !== null
48
+ ? { ...errorResponse, controllerUrl: controllerUrl }
49
+ : { message: String(errorResponse || ''), controllerUrl: controllerUrl };
50
+
51
+ const parsed = parseErrorResponse(errorData, statusCode, isNetworkError);
45
52
  return parsed.formatted;
46
53
  }
47
54
 
@@ -25,41 +25,51 @@ const { formatApiError } = require('./api-error-handler');
25
25
  async function callRegisterApi(apiUrl, token, environment, registrationData) {
26
26
  // Use centralized API client
27
27
  const authConfig = { type: 'bearer', token: token };
28
- const response = await registerApplication(apiUrl, environment, authConfig, registrationData);
28
+ try {
29
+ const response = await registerApplication(apiUrl, environment, authConfig, registrationData);
29
30
 
30
- if (!response.success) {
31
- const formattedError = response.formattedError || formatApiError(response);
32
- logger.error(formattedError);
31
+ if (!response.success) {
32
+ const formattedError = response.formattedError || formatApiError(response, apiUrl);
33
+ logger.error(formattedError);
34
+ logger.error(chalk.gray(`\nController URL: ${apiUrl}`));
33
35
 
34
- // For validation errors (400, 422), show the request payload for debugging
35
- if (response.status === 400 || response.status === 422) {
36
- logger.error(chalk.gray('\nRequest payload:'));
37
- logger.error(chalk.gray(JSON.stringify(registrationData, null, 2)));
38
- logger.error('');
39
- logger.error(chalk.gray('Check your variables.yaml file and ensure all required fields are correctly set.'));
36
+ // For validation errors (400, 422), show the request payload for debugging
37
+ if (response.status === 400 || response.status === 422) {
38
+ logger.error(chalk.gray('\nRequest payload:'));
39
+ logger.error(chalk.gray(JSON.stringify(registrationData, null, 2)));
40
+ logger.error('');
41
+ logger.error(chalk.gray('Check your variables.yaml file and ensure all required fields are correctly set.'));
42
+ }
43
+
44
+ process.exit(1);
40
45
  }
41
46
 
47
+ // Handle API response structure:
48
+ // registerApplication returns: { success: true, data: <API response> }
49
+ // API response can be:
50
+ // 1. Direct format: { application: {...}, credentials: {...} }
51
+ // 2. Wrapped format: { success: true, data: { application: {...}, credentials: {...} } }
52
+ const apiResponse = response.data;
53
+ if (apiResponse && apiResponse.data && apiResponse.data.application) {
54
+ // Wrapped format: use apiResponse.data
55
+ return apiResponse.data;
56
+ } else if (apiResponse && apiResponse.application) {
57
+ // Direct format: use apiResponse directly
58
+ return apiResponse;
59
+ }
60
+ // Fallback: return apiResponse as-is (shouldn't happen, but handle gracefully)
61
+ logger.error(chalk.red('❌ Invalid response: missing application data'));
62
+ logger.error(chalk.gray(`\nController URL: ${apiUrl}`));
63
+ logger.error(chalk.gray('\nFull response for debugging:'));
64
+ logger.error(chalk.gray(JSON.stringify(response, null, 2)));
42
65
  process.exit(1);
66
+ } catch (error) {
67
+ // Include controller URL in error context
68
+ logger.error(chalk.red('❌ Registration API call failed'));
69
+ logger.error(chalk.gray(`Controller URL: ${apiUrl}`));
70
+ logger.error(chalk.gray(`Error: ${error.message}`));
71
+ throw error;
43
72
  }
44
-
45
- // Handle API response structure:
46
- // registerApplication returns: { success: true, data: <API response> }
47
- // API response can be:
48
- // 1. Direct format: { application: {...}, credentials: {...} }
49
- // 2. Wrapped format: { success: true, data: { application: {...}, credentials: {...} } }
50
- const apiResponse = response.data;
51
- if (apiResponse && apiResponse.data && apiResponse.data.application) {
52
- // Wrapped format: use apiResponse.data
53
- return apiResponse.data;
54
- } else if (apiResponse && apiResponse.application) {
55
- // Direct format: use apiResponse directly
56
- return apiResponse;
57
- }
58
- // Fallback: return apiResponse as-is (shouldn't happen, but handle gracefully)
59
- logger.error(chalk.red('❌ Invalid response: missing application data'));
60
- logger.error(chalk.gray('\nFull response for debugging:'));
61
- logger.error(chalk.gray(JSON.stringify(response, null, 2)));
62
- process.exit(1);
63
73
  }
64
74
 
65
75
  module.exports = { callRegisterApi };