@aifabrix/builder 2.32.2 → 2.33.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.
Files changed (130) hide show
  1. package/.cursor/rules/project-rules.mdc +8 -0
  2. package/README.md +36 -8
  3. package/bin/aifabrix.js +6 -8
  4. package/integration/hubspot/README.md +8 -7
  5. package/integration/hubspot/companies.json +2048 -0
  6. package/integration/hubspot/create-hubspot.js +665 -0
  7. package/integration/hubspot/{hubspot-deploy-company.json → hubspot-datasource-company.json} +1 -1
  8. package/integration/hubspot/{hubspot-deploy-contact.json → hubspot-datasource-contact.json} +1 -1
  9. package/integration/hubspot/{hubspot-deploy-deal.json → hubspot-datasource-deal.json} +1 -1
  10. package/integration/hubspot/hubspot-deploy.json +832 -81
  11. package/integration/hubspot/hubspot-system.json +99 -0
  12. package/integration/hubspot/test-artifacts/wizard-hubspot-credential-real.yaml +20 -0
  13. package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +9 -0
  14. package/integration/hubspot/test-artifacts/wizard-invalid-add-datasource.yaml +5 -0
  15. package/integration/hubspot/test-artifacts/wizard-invalid-app-name.yaml +5 -0
  16. package/integration/hubspot/test-artifacts/wizard-invalid-credential-create.yaml +7 -0
  17. package/integration/hubspot/test-artifacts/wizard-invalid-credential-select.yaml +7 -0
  18. package/integration/hubspot/test-artifacts/wizard-invalid-known-platform.yaml +4 -0
  19. package/integration/hubspot/test-artifacts/wizard-invalid-missing-app.yaml +4 -0
  20. package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
  21. package/integration/hubspot/test-artifacts/wizard-invalid-mode.yaml +5 -0
  22. package/integration/hubspot/test-artifacts/wizard-invalid-openapi-file.yaml +5 -0
  23. package/integration/hubspot/test-artifacts/wizard-invalid-openapi-url.yaml +4 -0
  24. package/integration/hubspot/test-artifacts/wizard-invalid-source.yaml +4 -0
  25. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-array-test.yaml +5 -0
  26. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
  27. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
  28. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-test.yaml +5 -0
  29. package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-test.yaml +5 -0
  30. package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +5 -0
  31. package/integration/hubspot/test-dataplane-down-helpers.js +246 -0
  32. package/integration/hubspot/test-dataplane-down-tests.js +419 -0
  33. package/integration/hubspot/test-dataplane-down.js +157 -0
  34. package/integration/hubspot/test.js +1517 -0
  35. package/integration/hubspot/variables.yaml +4 -4
  36. package/integration/hubspot/wizard-hubspot-e2e.yaml +16 -0
  37. package/integration/hubspot/wizard-hubspot-platform.yaml +8 -0
  38. package/lib/api/applications.api.js +1 -0
  39. package/lib/api/index.js +10 -5
  40. package/lib/api/types/wizard.types.js +176 -38
  41. package/lib/api/wizard.api.js +207 -38
  42. package/lib/app/deploy.js +116 -54
  43. package/lib/app/display.js +6 -5
  44. package/lib/app/dockerfile.js +2 -1
  45. package/lib/app/list.js +78 -37
  46. package/lib/app/prompts.js +9 -5
  47. package/lib/app/readme.js +41 -112
  48. package/lib/app/register.js +44 -9
  49. package/lib/app/rotate-secret.js +50 -32
  50. package/lib/cli.js +243 -65
  51. package/lib/commands/app.js +4 -9
  52. package/lib/commands/auth-config.js +125 -0
  53. package/lib/commands/auth-status.js +261 -0
  54. package/lib/commands/datasource.js +3 -6
  55. package/lib/commands/login-credentials.js +4 -4
  56. package/lib/commands/login-device.js +43 -29
  57. package/lib/commands/login.js +22 -13
  58. package/lib/commands/wizard-config-normalizer.js +92 -0
  59. package/lib/commands/wizard-core.js +515 -0
  60. package/lib/commands/wizard-dataplane.js +122 -0
  61. package/lib/commands/wizard-headless.js +115 -0
  62. package/lib/commands/wizard.js +129 -357
  63. package/lib/core/config.js +46 -0
  64. package/lib/core/secrets.js +3 -22
  65. package/lib/core/templates-env.js +1 -1
  66. package/lib/datasource/deploy.js +34 -23
  67. package/lib/datasource/list.js +8 -6
  68. package/lib/deployment/deployer.js +25 -0
  69. package/lib/deployment/environment.js +10 -13
  70. package/lib/external-system/delete.js +151 -0
  71. package/lib/external-system/deploy.js +54 -378
  72. package/lib/external-system/download-helpers.js +45 -65
  73. package/lib/external-system/download.js +34 -13
  74. package/lib/external-system/generator.js +11 -7
  75. package/lib/external-system/test-auth.js +5 -3
  76. package/lib/generator/builders.js +3 -1
  77. package/lib/generator/external-controller-manifest.js +157 -0
  78. package/lib/generator/external-schema-utils.js +236 -0
  79. package/lib/generator/external.js +55 -3
  80. package/lib/generator/index.js +22 -10
  81. package/lib/generator/wizard-prompts.js +33 -10
  82. package/lib/generator/wizard.js +69 -86
  83. package/lib/infrastructure/compose.js +100 -0
  84. package/lib/infrastructure/helpers.js +139 -0
  85. package/lib/infrastructure/index.js +52 -311
  86. package/lib/infrastructure/services.js +168 -0
  87. package/lib/schema/application-schema.json +24 -5
  88. package/lib/schema/external-datasource.schema.json +303 -17
  89. package/lib/schema/external-system.schema.json +1 -1
  90. package/lib/schema/wizard-config.schema.json +234 -0
  91. package/lib/utils/api.js +37 -42
  92. package/lib/utils/app-existence.js +42 -0
  93. package/lib/utils/app-register-config.js +7 -2
  94. package/lib/utils/app-register-display.js +2 -1
  95. package/lib/utils/auth-config-validator.js +92 -0
  96. package/lib/utils/cli-utils.js +3 -1
  97. package/lib/utils/command-header.js +43 -0
  98. package/lib/utils/compose-generator.js +113 -70
  99. package/lib/utils/controller-url.js +115 -0
  100. package/lib/utils/dataplane-health.js +115 -0
  101. package/lib/utils/dataplane-resolver.js +29 -0
  102. package/lib/utils/dev-config.js +6 -2
  103. package/lib/utils/env-copy.js +2 -1
  104. package/lib/utils/env-map.js +2 -1
  105. package/lib/utils/env-ports.js +2 -1
  106. package/lib/utils/env-template.js +1 -1
  107. package/lib/utils/error-formatter.js +149 -28
  108. package/lib/utils/external-readme.js +125 -0
  109. package/lib/utils/help-builder.js +190 -0
  110. package/lib/utils/infra-status.js +13 -3
  111. package/lib/utils/paths.js +17 -2
  112. package/lib/utils/port-resolver.js +111 -0
  113. package/lib/utils/secrets-helpers.js +3 -15
  114. package/lib/utils/secrets-utils.js +2 -2
  115. package/lib/utils/token-manager.js +69 -4
  116. package/lib/utils/variable-transformer.js +7 -2
  117. package/lib/validation/external-manifest-validator.js +202 -0
  118. package/lib/validation/validate-display.js +406 -0
  119. package/lib/validation/validate.js +159 -123
  120. package/lib/validation/validator.js +38 -4
  121. package/lib/validation/wizard-config-validator.js +267 -0
  122. package/package.json +4 -2
  123. package/templates/applications/README.md.hbs +19 -17
  124. package/templates/applications/miso-controller/env.template +1 -1
  125. package/templates/applications/miso-controller/rbac.yaml +7 -7
  126. package/templates/external-system/README.md.hbs +99 -0
  127. package/templates/external-system/external-system.json.hbs +1 -1
  128. package/templates/infra/compose.yaml.hbs +35 -0
  129. package/templates/python/docker-compose.hbs +26 -0
  130. package/templates/typescript/docker-compose.hbs +26 -0
@@ -11,13 +11,14 @@
11
11
 
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
- const chalk = require('chalk');
15
14
  const validator = require('./validator');
16
15
  const { resolveExternalFiles } = require('../utils/schema-resolver');
17
16
  const { loadExternalSystemSchema, loadExternalDataSourceSchema, detectSchemaType } = require('../utils/schema-loader');
18
17
  const { formatValidationErrors } = require('../utils/error-formatter');
19
18
  const { detectAppType } = require('../utils/paths');
20
- const logger = require('../utils/logger');
19
+ const { displayValidationResults } = require('./validate-display');
20
+ const { generateControllerManifest } = require('../generator/external-controller-manifest');
21
+ const { validateControllerManifest } = require('./external-manifest-validator');
21
22
 
22
23
  /**
23
24
  * Validates a file path (detects type and validates)
@@ -204,6 +205,8 @@ async function validateExternalFile(filePath, type) {
204
205
  * @async
205
206
  * @function validateAppOrFile
206
207
  * @param {string} appOrFile - Application name or file path
208
+ * @param {Object} [options] - Validation options
209
+ * @param {string} [options.type] - Forced application type (external)
207
210
  * @returns {Promise<Object>} Validation result with aggregated results
208
211
  * @throws {Error} If validation fails
209
212
  *
@@ -277,158 +280,191 @@ function loadVariablesAndCheckExternalIntegration(variablesPath, appValidation)
277
280
  return null;
278
281
  }
279
282
 
280
- async function validateAppOrFile(appOrFile) {
281
- if (!appOrFile || typeof appOrFile !== 'string') {
282
- throw new Error('App name or file path is required');
283
- }
284
-
285
- const isFilePath = fs.existsSync(appOrFile) && fs.statSync(appOrFile).isFile();
286
- if (isFilePath) {
287
- return await validateFilePath(appOrFile);
288
- }
289
-
290
- const appName = appOrFile;
291
- const { appPath, isExternal } = await detectAppType(appName);
292
- const appValidation = await validator.validateApplication(appName);
293
- const rbacValidation = await validateRbacForExternalSystem(isExternal, appName);
294
-
295
- const variablesPath = path.join(appPath, 'variables.yaml');
296
- const earlyReturn = loadVariablesAndCheckExternalIntegration(variablesPath, appValidation);
297
- if (earlyReturn) {
298
- return earlyReturn;
283
+ /**
284
+ * Validates application configuration step
285
+ * @async
286
+ * @function validateApplicationStep
287
+ * @param {string} appName - Application name
288
+ * @returns {Promise<Object>} Application validation result
289
+ */
290
+ async function validateApplicationStep(appName) {
291
+ try {
292
+ const appValidation = await validator.validateApplication(appName);
293
+ return {
294
+ valid: appValidation.valid,
295
+ errors: appValidation.errors || [],
296
+ warnings: appValidation.warnings || []
297
+ };
298
+ } catch (error) {
299
+ return {
300
+ valid: false,
301
+ errors: [error.message],
302
+ warnings: []
303
+ };
299
304
  }
300
-
301
- const externalValidations = await validateExternalFilesForApp(appName);
302
- return aggregateValidationResults(appValidation, externalValidations, rbacValidation);
303
305
  }
304
306
 
305
307
  /**
306
- * Displays validation results in a user-friendly format
307
- *
308
- * @function displayValidationResults
309
- * @param {Object} result - Validation result from validateAppOrFile
310
- */
311
- /**
312
- * Displays application validation results
313
- * @function displayApplicationValidation
314
- * @param {Object} application - Application validation result
308
+ * Validates individual component files step
309
+ * @async
310
+ * @function validateComponentsStep
311
+ * @param {string} appName - Application name
312
+ * @returns {Promise<Object>} Components validation result
315
313
  */
316
- function displayApplicationValidation(application) {
317
- if (!application) {
318
- return;
319
- }
314
+ async function validateComponentsStep(appName) {
315
+ try {
316
+ const externalValidations = await validateExternalFilesForApp(appName);
317
+ const componentErrors = [];
318
+ const componentWarnings = [];
319
+ const componentFiles = [];
320
+
321
+ externalValidations.forEach(validation => {
322
+ componentFiles.push({
323
+ file: validation.file,
324
+ type: validation.type,
325
+ valid: validation.valid,
326
+ path: validation.path
327
+ });
320
328
 
321
- logger.log(chalk.blue('\nApplication:'));
322
- if (application.valid) {
323
- logger.log(chalk.green(' ✓ Application configuration is valid'));
324
- } else {
325
- logger.log(chalk.red(' Application configuration has errors:'));
326
- application.errors.forEach(error => {
327
- logger.log(chalk.red(` • ${error}`));
328
- });
329
- }
330
- if (application.warnings && application.warnings.length > 0) {
331
- application.warnings.forEach(warning => {
332
- logger.log(chalk.yellow(` ⚠ ${warning}`));
329
+ if (!validation.valid) {
330
+ componentErrors.push(`${validation.file} (${validation.type}): ${validation.errors.join(', ')}`);
331
+ }
332
+ if (validation.warnings && validation.warnings.length > 0) {
333
+ componentWarnings.push(`${validation.file}: ${validation.warnings.join(', ')}`);
334
+ }
333
335
  });
336
+
337
+ return {
338
+ valid: componentErrors.length === 0,
339
+ errors: componentErrors,
340
+ warnings: componentWarnings,
341
+ files: componentFiles
342
+ };
343
+ } catch (error) {
344
+ return {
345
+ valid: false,
346
+ errors: [error.message],
347
+ warnings: [],
348
+ files: []
349
+ };
334
350
  }
335
351
  }
336
352
 
337
353
  /**
338
- * Displays external files validation results
339
- * @function displayExternalFilesValidation
340
- * @param {Array} externalFiles - External files validation results
354
+ * Validates full deployment manifest step
355
+ * @async
356
+ * @function validateManifestStep
357
+ * @param {string} appName - Application name
358
+ * @returns {Promise<Object>} Manifest validation result
341
359
  */
342
- function displayExternalFilesValidation(externalFiles) {
343
- if (!externalFiles || externalFiles.length === 0) {
344
- return;
360
+ async function validateManifestStep(appName) {
361
+ try {
362
+ const manifest = await generateControllerManifest(appName);
363
+ const manifestValidation = await validateControllerManifest(manifest);
364
+ return {
365
+ valid: manifestValidation.valid,
366
+ errors: manifestValidation.errors || [],
367
+ warnings: manifestValidation.warnings || []
368
+ };
369
+ } catch (error) {
370
+ return {
371
+ valid: false,
372
+ errors: [`Failed to validate manifest: ${error.message}`],
373
+ warnings: []
374
+ };
345
375
  }
346
-
347
- logger.log(chalk.blue('\nExternal Integration Files:'));
348
- externalFiles.forEach(file => {
349
- if (file.valid) {
350
- logger.log(chalk.green(` ✓ ${file.file} (${file.type})`));
351
- } else {
352
- logger.log(chalk.red(` ✗ ${file.file} (${file.type}):`));
353
- file.errors.forEach(error => {
354
- logger.log(chalk.red(` • ${error}`));
355
- });
356
- }
357
- if (file.warnings && file.warnings.length > 0) {
358
- file.warnings.forEach(warning => {
359
- logger.log(chalk.yellow(` ⚠ ${warning}`));
360
- });
361
- }
362
- });
363
376
  }
364
377
 
365
378
  /**
366
- * Displays RBAC validation results
367
- * @function displayRbacValidation
368
- * @param {Object} rbac - RBAC validation result
379
+ * Validates external system completely (components + full manifest)
380
+ * Performs step-by-step validation: application config → components → full manifest
381
+ *
382
+ * @async
383
+ * @function validateExternalSystemComplete
384
+ * @param {string} appName - Application name
385
+ * @returns {Promise<Object>} Complete validation result with step-by-step results
386
+ * @throws {Error} If validation fails critically
387
+ *
388
+ * @example
389
+ * const result = await validateExternalSystemComplete('my-hubspot');
390
+ * // Returns: { valid: true, errors: [], warnings: [], steps: {...} }
369
391
  */
370
- function displayRbacValidation(rbac) {
371
- if (!rbac) {
372
- return;
392
+ async function validateExternalSystemComplete(appName) {
393
+ if (!appName || typeof appName !== 'string') {
394
+ throw new Error('App name is required and must be a string');
373
395
  }
374
396
 
375
- logger.log(chalk.blue('\nRBAC Configuration:'));
376
- if (rbac.valid) {
377
- logger.log(chalk.green(' ✓ RBAC configuration is valid'));
378
- } else {
379
- logger.log(chalk.red(' ✗ RBAC configuration has errors:'));
380
- rbac.errors.forEach(error => {
381
- logger.log(chalk.red(` • ${error}`));
382
- });
383
- }
384
- if (rbac.warnings && rbac.warnings.length > 0) {
385
- rbac.warnings.forEach(warning => {
386
- logger.log(chalk.yellow(` ⚠ ${warning}`));
387
- });
397
+ const steps = {
398
+ application: { valid: false, errors: [], warnings: [] },
399
+ components: { valid: false, errors: [], warnings: [], files: [] },
400
+ manifest: { valid: false, errors: [], warnings: [] }
401
+ };
402
+
403
+ // Step 1: Validate Application Config
404
+ steps.application = await validateApplicationStep(appName);
405
+
406
+ // Step 2: Validate Individual Components
407
+ steps.components = await validateComponentsStep(appName);
408
+
409
+ // If components have errors, return early (don't validate manifest)
410
+ if (!steps.components.valid) {
411
+ return {
412
+ valid: false,
413
+ errors: [...steps.application.errors, ...steps.components.errors],
414
+ warnings: [...steps.application.warnings, ...steps.components.warnings],
415
+ steps
416
+ };
388
417
  }
418
+
419
+ // Step 3 & 4: Generate and Validate Full Manifest (only if Step 2 passes)
420
+ steps.manifest = await validateManifestStep(appName);
421
+
422
+ // Aggregate Results
423
+ const allErrors = [...steps.application.errors, ...steps.components.errors, ...steps.manifest.errors];
424
+ const allWarnings = [...steps.application.warnings, ...steps.components.warnings, ...steps.manifest.warnings];
425
+
426
+ return {
427
+ valid: allErrors.length === 0,
428
+ errors: allErrors,
429
+ warnings: allWarnings,
430
+ steps
431
+ };
389
432
  }
390
433
 
391
- function displayValidationResults(result) {
392
- if (result.valid) {
393
- logger.log(chalk.green('\n✓ Validation passed!'));
394
- } else {
395
- logger.log(chalk.red('\n✗ Validation failed!'));
434
+ async function validateAppOrFile(appOrFile, options = {}) {
435
+ if (!appOrFile || typeof appOrFile !== 'string') {
436
+ throw new Error('App name or file path is required');
396
437
  }
397
438
 
398
- displayApplicationValidation(result.application);
399
- displayExternalFilesValidation(result.externalFiles);
400
- displayRbacValidation(result.rbac);
401
-
402
- // Display file validation (for direct file validation)
403
- if (result.file) {
404
- logger.log(chalk.blue(`\nFile: ${result.file}`));
405
- logger.log(chalk.blue(`Type: ${result.type}`));
406
- if (result.valid) {
407
- logger.log(chalk.green(' ✓ File is valid'));
408
- } else {
409
- logger.log(chalk.red(' ✗ File has errors:'));
410
- result.errors.forEach(error => {
411
- logger.log(chalk.red(` • ${error}`));
412
- });
413
- }
414
- if (result.warnings && result.warnings.length > 0) {
415
- result.warnings.forEach(warning => {
416
- logger.log(chalk.yellow(` ⚠ ${warning}`));
417
- });
418
- }
439
+ const isFilePath = fs.existsSync(appOrFile) && fs.statSync(appOrFile).isFile();
440
+ if (isFilePath) {
441
+ return await validateFilePath(appOrFile);
419
442
  }
420
443
 
421
- // Display aggregated warnings
422
- if (result.warnings && result.warnings.length > 0) {
423
- logger.log(chalk.yellow('\nWarnings:'));
424
- result.warnings.forEach(warning => {
425
- logger.log(chalk.yellow(` • ${warning}`));
426
- });
444
+ const appName = appOrFile;
445
+ const { appPath, isExternal } = await detectAppType(appName, options);
446
+
447
+ // For external systems with --type external flag, use new unified validation
448
+ if (isExternal && options.type === 'external') {
449
+ return await validateExternalSystemComplete(appName);
450
+ }
451
+
452
+ const appValidation = await validator.validateApplication(appName);
453
+ const rbacValidation = await validateRbacForExternalSystem(isExternal, appName);
454
+
455
+ const variablesPath = path.join(appPath, 'variables.yaml');
456
+ const earlyReturn = loadVariablesAndCheckExternalIntegration(variablesPath, appValidation);
457
+ if (earlyReturn) {
458
+ return earlyReturn;
427
459
  }
460
+
461
+ const externalValidations = await validateExternalFilesForApp(appName);
462
+ return aggregateValidationResults(appValidation, externalValidations, rbacValidation);
428
463
  }
429
464
 
430
465
  module.exports = {
431
466
  validateAppOrFile,
467
+ validateExternalSystemComplete,
432
468
  displayValidationResults,
433
469
  validateExternalFile,
434
470
  validateExternalFilesForApp,
@@ -98,6 +98,27 @@ function validateExternalIntegrationBlock(variables, errors) {
98
98
  }
99
99
  }
100
100
 
101
+ /**
102
+ * Validates frontDoorRouting configuration
103
+ * @function validateFrontDoorRouting
104
+ * @param {Object} variables - Variables object
105
+ * @param {string[]} errors - Errors array to append to
106
+ */
107
+ function validateFrontDoorRouting(variables, errors) {
108
+ const frontDoor = variables.frontDoorRouting;
109
+ if (!frontDoor) {
110
+ return;
111
+ }
112
+
113
+ if (frontDoor.enabled === true && (!frontDoor.host || typeof frontDoor.host !== 'string')) {
114
+ errors.push('frontDoorRouting.host is required when frontDoorRouting.enabled is true');
115
+ }
116
+
117
+ if (frontDoor.pattern && !String(frontDoor.pattern).startsWith('/')) {
118
+ errors.push('frontDoorRouting.pattern must start with "/"');
119
+ }
120
+ }
121
+
101
122
  async function validateVariables(appName) {
102
123
  if (!appName || typeof appName !== 'string') {
103
124
  throw new Error('App name is required and must be a string');
@@ -115,6 +136,8 @@ async function validateVariables(appName) {
115
136
  validateExternalIntegrationBlock(variables, errors);
116
137
  }
117
138
 
139
+ validateFrontDoorRouting(variables, errors);
140
+
118
141
  return {
119
142
  valid: valid && errors.length === 0,
120
143
  errors,
@@ -152,6 +175,10 @@ function validateRoles(roles) {
152
175
  } else {
153
176
  roleNames.add(role.value);
154
177
  }
178
+ // Reject Groups (capital G) - must use groups (lowercase)
179
+ if (role.Groups !== undefined) {
180
+ errors.push(`Role at index ${index} uses 'Groups' (capital G) but must use 'groups' (lowercase) for schema compatibility`);
181
+ }
155
182
  });
156
183
  return errors;
157
184
  }
@@ -229,7 +256,9 @@ async function validateEnvTemplate(appName) {
229
256
  throw new Error('App name is required and must be a string');
230
257
  }
231
258
 
232
- const templatePath = path.join(process.cwd(), 'builder', appName, 'env.template');
259
+ // Support both builder/ and integration/ directories using detectAppType
260
+ const { appPath } = await detectAppType(appName);
261
+ const templatePath = path.join(appPath, 'env.template');
233
262
 
234
263
  if (!fs.existsSync(templatePath)) {
235
264
  throw new Error(`env.template not found: ${templatePath}`);
@@ -296,7 +325,8 @@ function validateDeploymentJson(deployment) {
296
325
  };
297
326
  }
298
327
 
299
- const ajv = new Ajv({ allErrors: true, strict: false });
328
+ // verbose: true includes the actual data value in error objects for better error messages
329
+ const ajv = new Ajv({ allErrors: true, strict: false, verbose: true });
300
330
  // Register external schemas with their $id (GitHub raw URLs)
301
331
  // Create copies to avoid modifying the original schemas
302
332
  const externalSystemSchemaCopy = { ...externalSystemSchema };
@@ -340,15 +370,19 @@ async function validateApplication(appName) {
340
370
  const env = await validateEnvTemplate(appName);
341
371
 
342
372
  const valid = variables.valid && rbac.valid && env.valid;
373
+ const errors = [...(variables.errors || []), ...(rbac.errors || []), ...(env.errors || [])];
374
+ const warnings = [...(variables.warnings || []), ...(rbac.warnings || []), ...(env.warnings || [])];
343
375
 
344
376
  return {
345
377
  valid,
346
378
  variables,
347
379
  rbac,
348
380
  env,
381
+ errors,
382
+ warnings,
349
383
  summary: {
350
- totalErrors: variables.errors.length + rbac.errors.length + env.errors.length,
351
- totalWarnings: variables.warnings.length + rbac.warnings.length + env.warnings.length
384
+ totalErrors: errors.length,
385
+ totalWarnings: warnings.length
352
386
  }
353
387
  };
354
388
  }