api-tests-coverage 1.0.22 → 1.0.24

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 (175) hide show
  1. package/README.md +39 -0
  2. package/config.yaml.example +35 -0
  3. package/dist/dashboard/dist/assets/_basePickBy-BamgEusj.js +1 -0
  4. package/dist/dashboard/dist/assets/_basePickBy-D4Hl8chy.js +1 -0
  5. package/dist/dashboard/dist/assets/_baseUniq-BBhq12Ja.js +1 -0
  6. package/dist/dashboard/dist/assets/_baseUniq-BSUUnV_V.js +1 -0
  7. package/dist/dashboard/dist/assets/arc-Dh-qL1ea.js +1 -0
  8. package/dist/dashboard/dist/assets/arc-DhDluTY5.js +1 -0
  9. package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-BxQ_anmt.js +36 -0
  10. package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-DGlUU7dC.js +36 -0
  11. package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-CgXi3kEZ.js +122 -0
  12. package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-Krm3lc7z.js +122 -0
  13. package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-Cfd4OeWg.js +10 -0
  14. package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-Cr3xB15y.js +10 -0
  15. package/dist/dashboard/dist/assets/channel-DYAie-7m.js +1 -0
  16. package/dist/dashboard/dist/assets/channel-a4t2URTe.js +1 -0
  17. package/dist/dashboard/dist/assets/chunk-4BX2VUAB-BaW3__pI.js +1 -0
  18. package/dist/dashboard/dist/assets/chunk-4BX2VUAB-ljBQ5lHA.js +1 -0
  19. package/dist/dashboard/dist/assets/chunk-55IACEB6-Cikrdc3Q.js +1 -0
  20. package/dist/dashboard/dist/assets/chunk-55IACEB6-DyYevfEQ.js +1 -0
  21. package/dist/dashboard/dist/assets/chunk-B4BG7PRW-C2bwZFec.js +165 -0
  22. package/dist/dashboard/dist/assets/chunk-B4BG7PRW-dtHgbkmj.js +165 -0
  23. package/dist/dashboard/dist/assets/chunk-DI55MBZ5-Cv3hm2Ke.js +220 -0
  24. package/dist/dashboard/dist/assets/chunk-DI55MBZ5-DO0T2xne.js +220 -0
  25. package/dist/dashboard/dist/assets/chunk-FMBD7UC4-CCYA4j_f.js +15 -0
  26. package/dist/dashboard/dist/assets/chunk-FMBD7UC4-Ds1_OqKH.js +15 -0
  27. package/dist/dashboard/dist/assets/chunk-QN33PNHL-B6zkzIAo.js +1 -0
  28. package/dist/dashboard/dist/assets/chunk-QN33PNHL-Cdhqs7xo.js +1 -0
  29. package/dist/dashboard/dist/assets/chunk-QZHKN3VN-BzHw38Ki.js +1 -0
  30. package/dist/dashboard/dist/assets/chunk-QZHKN3VN-C7xuA6tl.js +1 -0
  31. package/dist/dashboard/dist/assets/chunk-TZMSLE5B-D_ea_wdP.js +1 -0
  32. package/dist/dashboard/dist/assets/chunk-TZMSLE5B-dkJ0rsgF.js +1 -0
  33. package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-B6SxXE6T.js +1 -0
  34. package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-DiIv5Pho.js +1 -0
  35. package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-B6SxXE6T.js +1 -0
  36. package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-DiIv5Pho.js +1 -0
  37. package/dist/dashboard/dist/assets/clone-B4LorrSy.js +1 -0
  38. package/dist/dashboard/dist/assets/clone-kcKg1tUH.js +1 -0
  39. package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-DasyAK5c.js +1 -0
  40. package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-jzGbyPIS.js +1 -0
  41. package/dist/dashboard/dist/assets/dagre-6UL2VRFP-D7rgvBx1.js +4 -0
  42. package/dist/dashboard/dist/assets/dagre-6UL2VRFP-m-5bs635.js +4 -0
  43. package/dist/dashboard/dist/assets/diagram-PSM6KHXK-2rYklqon.js +24 -0
  44. package/dist/dashboard/dist/assets/diagram-PSM6KHXK-CYFwwEdy.js +24 -0
  45. package/dist/dashboard/dist/assets/diagram-QEK2KX5R-CGrvALqm.js +43 -0
  46. package/dist/dashboard/dist/assets/diagram-QEK2KX5R-m4Fda1GA.js +43 -0
  47. package/dist/dashboard/dist/assets/diagram-S2PKOQOG-BDVk4AKU.js +24 -0
  48. package/dist/dashboard/dist/assets/diagram-S2PKOQOG-DA3c-QP4.js +24 -0
  49. package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-3-jAbxQ6.js +60 -0
  50. package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-BsYH8cLH.js +60 -0
  51. package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-Cfv1hkQB.js +162 -0
  52. package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-Da_JhBCy.js +162 -0
  53. package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-B7GZPGck.js +267 -0
  54. package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-D8FTswNn.js +267 -0
  55. package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-BFJR-ITH.js +65 -0
  56. package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-K8X-_4av.js +65 -0
  57. package/dist/dashboard/dist/assets/graph-CIvnjOQQ.js +1 -0
  58. package/dist/dashboard/dist/assets/graph-CfuGK9GG.js +1 -0
  59. package/dist/dashboard/dist/assets/index-BWX0sSZn.css +1 -0
  60. package/dist/dashboard/dist/assets/index-CbAFWEor.js +777 -0
  61. package/dist/dashboard/dist/assets/index-DS-KIxwV.js +777 -0
  62. package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-CaIaIUhT.js +2 -0
  63. package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-OcK0Lxgi.js +2 -0
  64. package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-D6dwPswq.js +139 -0
  65. package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-DTJukVOY.js +139 -0
  66. package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-CERyhhrH.js +89 -0
  67. package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-Di65fNuD.js +89 -0
  68. package/dist/dashboard/dist/assets/layout-DAt24RVX.js +1 -0
  69. package/dist/dashboard/dist/assets/layout-v7cCi3Fl.js +1 -0
  70. package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-BvNtTz8N.js +68 -0
  71. package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-DxI8MXCF.js +68 -0
  72. package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-BafKx3_Y.js +30 -0
  73. package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-Cjg80C_b.js +30 -0
  74. package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-BcZsArkk.js +7 -0
  75. package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-YtFFUYGD.js +7 -0
  76. package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-CqFAO2t6.js +64 -0
  77. package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-DLV2LTE5.js +64 -0
  78. package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-C6_Urrii.js +10 -0
  79. package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-CqSaCg-3.js +10 -0
  80. package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-6IXD1uqW.js +145 -0
  81. package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-D33UwAtz.js +145 -0
  82. package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-DSp83t9D.js +1 -0
  83. package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-DvSVQAfp.js +1 -0
  84. package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-BMFdt0QQ.js +1 -0
  85. package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-OTWrEpQO.js +1 -0
  86. package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-Cll7Nvth.js +61 -0
  87. package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-D5Bb3Jj7.js +61 -0
  88. package/dist/dashboard/dist/assets/treemap-GDKQZRPO-CKbkkwye.js +162 -0
  89. package/dist/dashboard/dist/assets/treemap-GDKQZRPO-DtqX8zNC.js +162 -0
  90. package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-C_Tlzchx.js +7 -0
  91. package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-zxwS9i0A.js +7 -0
  92. package/dist/dashboard/dist/index.html +2 -2
  93. package/dist/dashboard/dist/reports/coverage-summary.json +75 -1
  94. package/dist/dashboard/dist/reports/security-full.json +157 -0
  95. package/dist/src/compatibilityCoverage.d.ts +34 -15
  96. package/dist/src/compatibilityCoverage.d.ts.map +1 -1
  97. package/dist/src/compatibilityCoverage.js +387 -85
  98. package/dist/src/config/defaultConfig.d.ts.map +1 -1
  99. package/dist/src/config/defaultConfig.js +62 -0
  100. package/dist/src/config/schema.d.ts.map +1 -1
  101. package/dist/src/config/schema.js +1 -1
  102. package/dist/src/config/types.d.ts +81 -1
  103. package/dist/src/config/types.d.ts.map +1 -1
  104. package/dist/src/config/validateConfig.d.ts.map +1 -1
  105. package/dist/src/config/validateConfig.js +126 -0
  106. package/dist/src/contracts/compatibilityMatrix.d.ts +20 -0
  107. package/dist/src/contracts/compatibilityMatrix.d.ts.map +1 -0
  108. package/dist/src/contracts/compatibilityMatrix.js +198 -0
  109. package/dist/src/contracts/pactBrokerClient.d.ts +10 -0
  110. package/dist/src/contracts/pactBrokerClient.d.ts.map +1 -0
  111. package/dist/src/contracts/pactBrokerClient.js +117 -0
  112. package/dist/src/contracts/schemaEvolutionChecker.d.ts +17 -0
  113. package/dist/src/contracts/schemaEvolutionChecker.d.ts.map +1 -0
  114. package/dist/src/contracts/schemaEvolutionChecker.js +95 -0
  115. package/dist/src/contracts/springCloudContractParser.d.ts +10 -0
  116. package/dist/src/contracts/springCloudContractParser.d.ts.map +1 -0
  117. package/dist/src/contracts/springCloudContractParser.js +144 -0
  118. package/dist/src/discovery/fileClassifier.d.ts.map +1 -1
  119. package/dist/src/discovery/fileClassifier.js +25 -0
  120. package/dist/src/discovery/projectDiscovery.d.ts +2 -0
  121. package/dist/src/discovery/projectDiscovery.d.ts.map +1 -1
  122. package/dist/src/discovery/projectDiscovery.js +25 -25
  123. package/dist/src/index.js +233 -16
  124. package/dist/src/inference/routeInference.d.ts +10 -2
  125. package/dist/src/inference/routeInference.d.ts.map +1 -1
  126. package/dist/src/inference/routeInference.js +363 -62
  127. package/dist/src/languageDetection.d.ts.map +1 -1
  128. package/dist/src/languageDetection.js +21 -4
  129. package/dist/src/lib/index.d.ts +3 -0
  130. package/dist/src/lib/index.d.ts.map +1 -1
  131. package/dist/src/lib/index.js +3 -1
  132. package/dist/src/pipeline/stages/tia/parameterizedTestExpander.js +152 -79
  133. package/dist/src/pipeline/stages/tia/testEndpointMapper.d.ts +5 -1
  134. package/dist/src/pipeline/stages/tia/testEndpointMapper.d.ts.map +1 -1
  135. package/dist/src/pipeline/stages/tia/testEndpointMapper.js +356 -42
  136. package/dist/src/pipeline/stages/tia/testLayerClassifier.d.ts.map +1 -1
  137. package/dist/src/pipeline/stages/tia/testLayerClassifier.js +20 -5
  138. package/dist/src/pipeline/stages/tia/tiaStage.d.ts.map +1 -1
  139. package/dist/src/pipeline/stages/tia/tiaStage.js +3 -1
  140. package/dist/src/pipeline/stages/tia/types.d.ts +11 -2
  141. package/dist/src/pipeline/stages/tia/types.d.ts.map +1 -1
  142. package/dist/src/projectDefaults.d.ts +6 -0
  143. package/dist/src/projectDefaults.d.ts.map +1 -0
  144. package/dist/src/projectDefaults.js +43 -0
  145. package/dist/src/security/hub.d.ts +81 -0
  146. package/dist/src/security/hub.d.ts.map +1 -0
  147. package/dist/src/security/hub.js +420 -0
  148. package/dist/src/security/index.d.ts +1 -0
  149. package/dist/src/security/index.d.ts.map +1 -1
  150. package/dist/src/security/index.js +8 -2
  151. package/dist/src/security/normalizers/gitleaks.d.ts +7 -0
  152. package/dist/src/security/normalizers/gitleaks.d.ts.map +1 -0
  153. package/dist/src/security/normalizers/gitleaks.js +32 -0
  154. package/dist/src/security/scanners/gitleaks.d.ts +3 -0
  155. package/dist/src/security/scanners/gitleaks.d.ts.map +1 -0
  156. package/dist/src/security/scanners/gitleaks.js +105 -0
  157. package/dist/src/security/scanners/semgrep.d.ts.map +1 -1
  158. package/dist/src/security/scanners/semgrep.js +24 -2
  159. package/dist/src/security/scanners/trivy.d.ts.map +1 -1
  160. package/dist/src/security/scanners/trivy.js +24 -2
  161. package/dist/src/security/scanners/zap.d.ts.map +1 -1
  162. package/dist/src/security/scanners/zap.js +27 -2
  163. package/dist/src/security/types.d.ts +15 -1
  164. package/dist/src/security/types.d.ts.map +1 -1
  165. package/dist/src/streaming/schema/index.d.ts +23 -0
  166. package/dist/src/streaming/schema/index.d.ts.map +1 -0
  167. package/dist/src/streaming/schema/index.js +196 -0
  168. package/dist/src/summary/markdownRenderer.d.ts.map +1 -1
  169. package/dist/src/summary/markdownRenderer.js +15 -1
  170. package/dist/src/summary/summaryTypes.d.ts.map +1 -1
  171. package/dist/src/summary/summaryTypes.js +1 -0
  172. package/dist/src/unitAnalysis.d.ts +145 -0
  173. package/dist/src/unitAnalysis.d.ts.map +1 -0
  174. package/dist/src/unitAnalysis.js +1392 -0
  175. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -44,10 +44,16 @@ const errorCoverage_1 = require("./errorCoverage");
44
44
  const securityCoverage_1 = require("./securityCoverage");
45
45
  const perfResilienceCoverage_1 = require("./perfResilienceCoverage");
46
46
  const compatibilityCoverage_1 = require("./compatibilityCoverage");
47
+ const springCloudContractParser_1 = require("./contracts/springCloudContractParser");
48
+ const pactBrokerClient_1 = require("./contracts/pactBrokerClient");
49
+ const schema_1 = require("./streaming/schema");
50
+ const schemaEvolutionChecker_1 = require("./contracts/schemaEvolutionChecker");
51
+ const compatibilityMatrix_1 = require("./contracts/compatibilityMatrix");
47
52
  const reporting_1 = require("./reporting");
48
53
  const config_1 = require("./config");
49
54
  const index_1 = require("./coverage/deep-analysis/index");
50
55
  const index_2 = require("./security/index");
56
+ const hub_1 = require("./security/hub");
51
57
  const pluginLoader_1 = require("./pluginLoader");
52
58
  const observability_1 = require("./observability");
53
59
  const languageDetection_1 = require("./languageDetection");
@@ -63,6 +69,7 @@ const integrationFlowInference_1 = require("./inference/integrationFlowInference
63
69
  const routeInference_1 = require("./inference/routeInference");
64
70
  const scanManifest_1 = require("./inference/scanManifest");
65
71
  const serveDashboard_1 = require("./serveDashboard");
72
+ const unitAnalysis_1 = require("./unitAnalysis");
66
73
  // Register all language AST analyzers at startup.
67
74
  // This side-effect import ensures each language module's registerAnalyzer() call runs.
68
75
  (0, astAnalysisOrchestrator_1.registerAllAnalyzers)();
@@ -192,6 +199,63 @@ async function finaliseObservability(allResults, thresholds, metricsPort, servic
192
199
  });
193
200
  }
194
201
  }
202
+ function configuredString(value) {
203
+ if (!value)
204
+ return undefined;
205
+ const trimmed = value.trim();
206
+ if (!trimmed || /^\$\{[^}]+\}$/.test(trimmed))
207
+ return undefined;
208
+ return trimmed;
209
+ }
210
+ function toConsumerContractFromSpring(springContracts, providerName) {
211
+ var _a;
212
+ const grouped = new Map();
213
+ for (const springContract of springContracts) {
214
+ const consumer = (0, springCloudContractParser_1.inferSpringConsumerName)(springContract.filePath);
215
+ const existing = (_a = grouped.get(consumer)) !== null && _a !== void 0 ? _a : {
216
+ consumer,
217
+ provider: providerName,
218
+ filePath: springContract.filePath,
219
+ interactions: [],
220
+ };
221
+ existing.interactions.push(springContract.interaction);
222
+ grouped.set(consumer, existing);
223
+ }
224
+ return [...grouped.values()];
225
+ }
226
+ function parseSchemaFileSafely(filePath) {
227
+ try {
228
+ return (0, schema_1.parseSchemaFile)(filePath);
229
+ }
230
+ catch (error) {
231
+ console.warn(`Skipping schema file ${filePath}; schema evolution analysis will continue without it: ${error instanceof Error ? error.message : String(error)}`);
232
+ return null;
233
+ }
234
+ }
235
+ function schemaTopicOrQueueName(filePath) {
236
+ return path.basename(filePath).replace(/(\.schema)?\.(avsc|proto|json)$/i, '');
237
+ }
238
+ async function loadSchemaEvolutionResults(oldSchemaGlob, newSchemaGlob) {
239
+ if (!oldSchemaGlob || !newSchemaGlob)
240
+ return [];
241
+ const oldFiles = await (0, schema_1.discoverSchemaFiles)(oldSchemaGlob);
242
+ const newFiles = await (0, schema_1.discoverSchemaFiles)(newSchemaGlob);
243
+ const oldMap = new Map();
244
+ for (const filePath of oldFiles) {
245
+ const parsed = parseSchemaFileSafely(filePath);
246
+ if (parsed)
247
+ oldMap.set(path.basename(filePath), parsed);
248
+ }
249
+ const results = [];
250
+ for (const filePath of newFiles) {
251
+ const parsed = parseSchemaFileSafely(filePath);
252
+ const previous = oldMap.get(path.basename(filePath));
253
+ if (parsed && previous) {
254
+ results.push((0, schemaEvolutionChecker_1.checkSchemaEvolution)(previous, parsed, schemaTopicOrQueueName(filePath)));
255
+ }
256
+ }
257
+ return results;
258
+ }
195
259
  program
196
260
  .command('endpoint-coverage')
197
261
  .description('Analyze which API endpoints are covered by integration tests')
@@ -575,12 +639,17 @@ program
575
639
  .command('security-coverage')
576
640
  .description('Analyze how comprehensively tests cover security controls defined in the API spec')
577
641
  .option('--spec <path>', 'Path to the OpenAPI/Swagger spec file', 'sample/openapi-security.yaml')
578
- .option('--tests <glob>', 'Glob pattern for test files', 'sample/tests/**/*.ts')
642
+ .option('--tests <glob>', 'Glob pattern for test files')
643
+ .option('--source <glob>', 'Glob pattern for source files used by the own security scanner', 'src/**/*.{js,jsx,ts,tsx,java,kt,py,rb,go,php,cs}')
579
644
  .option('--scan-report <file>', 'Path to an external security scanner report (ZAP JSON/XML or generic JSON) for enrichment')
645
+ .option('--semgrep-report <file>', 'Import a pre-generated Semgrep JSON report')
646
+ .option('--trivy-report <file>', 'Import a pre-generated Trivy JSON report')
647
+ .option('--gitleaks-report <file>', 'Import a pre-generated Gitleaks JSON report')
648
+ .option('--zap-report <file>', 'Import a pre-generated ZAP JSON report')
580
649
  .option('--format <formats>', 'Comma-separated list of report formats: json,html,csv,junit (default: json,html)', 'json,html')
581
650
  .option('--threshold-security <percent>', 'Minimum required security coverage percentage (0-100)', parseFloat, 0)
582
651
  .action(async (options) => {
583
- var _a, _b, _c, _d;
652
+ var _a, _b, _c, _d, _e, _f;
584
653
  const { metricsPort, serviceName } = setupObservability();
585
654
  const logger = (0, observability_1.getLogger)();
586
655
  const parentOpts = program.opts();
@@ -588,9 +657,9 @@ program
588
657
  security: options.thresholdSecurity,
589
658
  });
590
659
  const specPath = path.resolve(options.spec);
591
- const testsGlob = (config.testPatterns && config.testPatterns.length > 0)
660
+ const testsGlob = (_a = options.tests) !== null && _a !== void 0 ? _a : ((config.testPatterns && config.testPatterns.length > 0)
592
661
  ? config.testPatterns[0]
593
- : options.tests;
662
+ : 'sample/tests/**/*.ts');
594
663
  const scanReportPath = options.scanReport ? path.resolve(options.scanReport) : undefined;
595
664
  const reportsDir = path.resolve('reports');
596
665
  const formats = (0, reporting_1.parseFormats)(options.format);
@@ -602,12 +671,26 @@ program
602
671
  console.log(`Analyzing tests matching: ${testsGlob}`);
603
672
  const analyzerCfgForSec = (0, config_1.loadCentralConfig)(parentOpts.config);
604
673
  const astSecOptions = {
605
- astConfig: (_a = analyzerCfgForSec.analysis.ast) !== null && _a !== void 0 ? _a : {},
606
- deepConfig: (_b = analyzerCfgForSec.scans.coverage) === null || _b === void 0 ? void 0 : _b.deepAnalysis,
674
+ astConfig: (_b = analyzerCfgForSec.analysis.ast) !== null && _b !== void 0 ? _b : {},
675
+ deepConfig: (_c = analyzerCfgForSec.scans.coverage) === null || _c === void 0 ? void 0 : _c.deepAnalysis,
607
676
  };
608
677
  const coverages = await (0, securityCoverage_1.analyzeSecurityCoverage)(controls, testsGlob, scanReportPath, astSecOptions);
609
678
  const report = (0, securityCoverage_1.buildSecurityCoverageReport)(coverages, scanReportPath ? coverages.filter((c) => c.coveredByScanReport).length : 0);
610
679
  (0, securityCoverage_1.generateSecurityReports)(report, reportsDir);
680
+ const securityFullReport = await (0, hub_1.generateSecurityFullReport)({
681
+ specPath,
682
+ testsGlob,
683
+ sourceGlob: options.source,
684
+ reportsDir,
685
+ coverageReport: report,
686
+ coverages,
687
+ config: analyzerCfgForSec,
688
+ semgrepReport: options.semgrepReport,
689
+ trivyReport: options.trivyReport,
690
+ gitleaksReport: options.gitleaksReport,
691
+ zapReport: (_d = options.zapReport) !== null && _d !== void 0 ? _d : scanReportPath,
692
+ });
693
+ (0, hub_1.writeSecurityFullReport)(securityFullReport, reportsDir);
611
694
  const result = {
612
695
  type: 'security',
613
696
  totalItems: report.total,
@@ -615,10 +698,10 @@ program
615
698
  coveragePercent: report.percentage,
616
699
  details: report,
617
700
  };
618
- const thresholds = { ...((_c = config.thresholds) !== null && _c !== void 0 ? _c : {}) };
701
+ const thresholds = { ...((_e = config.thresholds) !== null && _e !== void 0 ? _e : {}) };
619
702
  // Run plugins
620
703
  const pluginContext = {
621
- testPatterns: (_d = config.testPatterns) !== null && _d !== void 0 ? _d : [],
704
+ testPatterns: (_f = config.testPatterns) !== null && _f !== void 0 ? _f : [],
622
705
  results: [result],
623
706
  config,
624
707
  };
@@ -783,15 +866,37 @@ program
783
866
  .option('--old-spec <path>', 'Path to the previous (published) OpenAPI/Swagger spec')
784
867
  .option('--new-spec <path>', 'Path to the current spec to be published')
785
868
  .option('--contracts <glob>', 'Glob pattern or directory for consumer contract files (e.g. Pact JSON files)')
869
+ .option('--pact-broker-url <url>', 'Pull contracts from Pact Broker')
870
+ .option('--pact-broker-token <token>', 'Pact Broker auth token')
871
+ .option('--provider-name <name>', 'Provider/service name')
872
+ .option('--publish-results', 'Publish verification results back to Pact Broker')
873
+ .option('--schema-registry <url>', 'Schema Registry URL')
874
+ .option('--schemas <glob>', 'Current schema files for evolution checks')
875
+ .option('--old-schemas <glob>', 'Previous schema versions for evolution checks')
786
876
  .option('--threshold-compat <percent>', 'Minimum required compatibility percentage (0-100). Exits non-zero if not met.', parseFloat, 0)
877
+ .option('--threshold-contract <percent>', 'Minimum required contract coverage percentage (default: 100).', parseFloat, 100)
878
+ .option('--fail-on-breaking', 'Exit 1 if BREAKING changes are found (default: true)', true)
787
879
  .action(async (options) => {
880
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
788
881
  const { metricsPort, serviceName } = setupObservability();
789
882
  const logger = (0, observability_1.getLogger)();
883
+ const parentOpts = program.opts();
884
+ const analyzerCfg = (0, config_1.loadCentralConfig)(parentOpts.config);
790
885
  const oldSpecPath = options.oldSpec ? path.resolve(options.oldSpec) : undefined;
791
886
  const newSpecPath = options.newSpec ? path.resolve(options.newSpec) : undefined;
792
887
  const contractsGlob = options.contracts;
793
888
  const thresholdCompat = options.thresholdCompat;
889
+ const thresholdContract = options.thresholdContract;
890
+ const failOnBreaking = options.failOnBreaking !== false;
794
891
  const reportsDir = path.resolve('reports');
892
+ const configuredProviderName = (_a = configuredString(options.providerName)) !== null && _a !== void 0 ? _a : configuredString((_b = analyzerCfg.contracts.pactBroker) === null || _b === void 0 ? void 0 : _b.providerName);
893
+ const providerName = configuredProviderName !== null && configuredProviderName !== void 0 ? configuredProviderName : path.basename(process.cwd());
894
+ const pactBrokerUrl = (_c = configuredString(options.pactBrokerUrl)) !== null && _c !== void 0 ? _c : configuredString((_d = analyzerCfg.contracts.pactBroker) === null || _d === void 0 ? void 0 : _d.url);
895
+ const pactBrokerToken = (_e = configuredString(options.pactBrokerToken)) !== null && _e !== void 0 ? _e : configuredString((_f = analyzerCfg.contracts.pactBroker) === null || _f === void 0 ? void 0 : _f.token);
896
+ const publishResults = Boolean(options.publishResults) || ((_g = analyzerCfg.contracts.pactBroker) === null || _g === void 0 ? void 0 : _g.publishResults) === true;
897
+ const brokerEnabled = Boolean(options.pactBrokerUrl) || ((_h = analyzerCfg.contracts.pactBroker) === null || _h === void 0 ? void 0 : _h.enabled) === true;
898
+ const schemaRegistryUrl = (_j = configuredString(options.schemaRegistry)) !== null && _j !== void 0 ? _j : configuredString((_k = analyzerCfg.contracts.schemaRegistry) === null || _k === void 0 ? void 0 : _k.url);
899
+ const schemaEvolutionResults = await loadSchemaEvolutionResults(options.oldSchemas, options.schemas);
795
900
  if (!oldSpecPath || !newSpecPath) {
796
901
  console.error('ERROR: --old-spec and --new-spec are required.');
797
902
  process.exitCode = 1;
@@ -808,14 +913,42 @@ program
808
913
  const breakingChanges = changes.filter((c) => c.breaking);
809
914
  const nonBreakingChanges = changes.filter((c) => !c.breaking);
810
915
  // Load and verify contracts
811
- const contracts = contractsGlob ? await (0, compatibilityCoverage_1.parseContractFiles)(contractsGlob) : [];
812
- if (contracts.length > 0) {
813
- console.log(`Loaded ${contracts.length} consumer contract(s)`);
916
+ const localContracts = contractsGlob ? await (0, compatibilityCoverage_1.parseContractFiles)(contractsGlob) : [];
917
+ const springContracts = await (0, springCloudContractParser_1.parseSpringCloudContracts)(contractsGlob !== null && contractsGlob !== void 0 ? contractsGlob : '');
918
+ const brokerContracts = brokerEnabled && pactBrokerUrl
919
+ ? await (0, pactBrokerClient_1.fetchContractsFromBroker)({
920
+ url: pactBrokerUrl,
921
+ token: pactBrokerToken,
922
+ providerName,
923
+ publishResults,
924
+ })
925
+ : [];
926
+ const allPactContracts = [...localContracts, ...brokerContracts].filter((contract) => configuredProviderName ? contract.provider === configuredProviderName : true);
927
+ const springAsContracts = toConsumerContractFromSpring(springContracts, providerName);
928
+ const allContracts = [...allPactContracts, ...springAsContracts];
929
+ if (allContracts.length > 0) {
930
+ console.log(`Loaded ${allContracts.length} consumer contract(s)`);
931
+ }
932
+ if (schemaRegistryUrl) {
933
+ console.log(`Schema Registry configured: ${schemaRegistryUrl}`);
814
934
  }
815
- const verificationResults = (0, compatibilityCoverage_1.verifyContracts)(contracts, newApi);
935
+ const verificationResults = (0, compatibilityCoverage_1.verifyContracts)(allContracts, newApi);
936
+ const matrix = (0, compatibilityMatrix_1.buildCompatibilityMatrix)(changes, allPactContracts, springContracts, providerName);
816
937
  // Build and write reports
817
- const report = (0, compatibilityCoverage_1.buildCompatibilityReport)(oldApi, newApi, changes, verificationResults, oldSpecPath, newSpecPath);
938
+ const report = (0, compatibilityCoverage_1.buildCompatibilityReport)(oldApi, newApi, changes, verificationResults, oldSpecPath, newSpecPath, {
939
+ springCloudContracts: springContracts,
940
+ schemaEvolutionResults,
941
+ matrix,
942
+ });
818
943
  (0, compatibilityCoverage_1.generateCompatibilityReports)(report, reportsDir);
944
+ if (publishResults && pactBrokerUrl) {
945
+ await (0, pactBrokerClient_1.publishVerificationResults)({
946
+ url: pactBrokerUrl,
947
+ token: pactBrokerToken,
948
+ providerName,
949
+ publishResults,
950
+ }, verificationResults);
951
+ }
819
952
  // Also write multi-format summary reports via the shared reporting module
820
953
  const formats = (0, reporting_1.parseFormats)('json,html');
821
954
  const uniqueAffectedEndpoints = new Set(breakingChanges.filter((c) => c.changeType !== 'added').map((c) => `${c.method}:${c.path}`)).size;
@@ -837,7 +970,9 @@ program
837
970
  const thresholds = {};
838
971
  if (thresholdCompat > 0) {
839
972
  thresholds['compatibility'] = thresholdCompat;
840
- thresholds['contract-coverage'] = thresholdCompat;
973
+ }
974
+ if (thresholdContract > 0) {
975
+ thresholds['contract-coverage'] = thresholdContract;
841
976
  }
842
977
  const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
843
978
  (0, reporting_1.generateMultiFormatReports)([compatResult, contractResult], formats, reportsDir, thresholds, observabilityInfo);
@@ -855,6 +990,10 @@ program
855
990
  console.log(` ✅ ${c.description}`);
856
991
  }
857
992
  }
993
+ console.log(`Can Deploy: ${(0, compatibilityCoverage_1.computeCanDeploy)(report) ? 'YES' : 'NO'}`);
994
+ if (report.deployBlockers.length > 0) {
995
+ console.log(`Deploy blockers: ${report.deployBlockers.join('; ')}`);
996
+ }
858
997
  if (verificationResults.length > 0) {
859
998
  const passedContracts = verificationResults.filter((r) => r.passed).length;
860
999
  console.log(`\nContract verification: ${passedContracts}/${verificationResults.length} contracts passed`);
@@ -881,6 +1020,35 @@ program
881
1020
  }
882
1021
  process.exitCode = 1;
883
1022
  }
1023
+ if (failOnBreaking && report.breakingChanges.some((change) => change.severity === 'BREAKING')) {
1024
+ process.exitCode = 1;
1025
+ }
1026
+ });
1027
+ program
1028
+ .command('contract-matrix')
1029
+ .description('Build a consumer compatibility matrix from OpenAPI diffs and contract sources')
1030
+ .option('--spec <path>', 'Path to the current OpenAPI/Swagger spec')
1031
+ .option('--old-spec <path>', 'Path to the previous OpenAPI/Swagger spec')
1032
+ .option('--contracts <glob>', 'Contract files (Pact JSON and/or Spring Cloud Contract files)')
1033
+ .action(async (options) => {
1034
+ const specPath = options.spec ? path.resolve(options.spec) : undefined;
1035
+ const oldSpecPath = options.oldSpec ? path.resolve(options.oldSpec) : undefined;
1036
+ if (!specPath || !oldSpecPath) {
1037
+ console.error('ERROR: --spec and --old-spec are required.');
1038
+ process.exitCode = 1;
1039
+ return;
1040
+ }
1041
+ const oldApi = await (0, compatibilityCoverage_1.loadSpec)(oldSpecPath);
1042
+ const newApi = await (0, compatibilityCoverage_1.loadSpec)(specPath);
1043
+ const contractsGlob = options.contracts;
1044
+ const pactContracts = contractsGlob ? await (0, compatibilityCoverage_1.parseContractFiles)(contractsGlob) : [];
1045
+ const springContracts = await (0, springCloudContractParser_1.parseSpringCloudContracts)(contractsGlob !== null && contractsGlob !== void 0 ? contractsGlob : '');
1046
+ const changes = (0, compatibilityCoverage_1.compareSpecs)(oldApi, newApi);
1047
+ const matrix = (0, compatibilityMatrix_1.buildCompatibilityMatrix)(changes, pactContracts, springContracts);
1048
+ (0, compatibilityMatrix_1.writeCompatibilityMatrixReports)(matrix, path.resolve('reports'));
1049
+ console.log(`Can Deploy: ${matrix.canDeploy ? 'YES' : 'NO'}`);
1050
+ console.log(`Breaking consumers: ${matrix.breakingConsumers.join(', ') || 'None'}`);
1051
+ console.log(`Safe consumers: ${matrix.safeToDeployFor.join(', ') || 'None'}`);
884
1052
  });
885
1053
  program
886
1054
  .command('security-scan')
@@ -1416,7 +1584,10 @@ program
1416
1584
  const agnosticDisc = (_k = (_j = analyzerCfg.analysis) === null || _j === void 0 ? void 0 : _j.agnosticDiscovery) !== null && _k !== void 0 ? _k : true;
1417
1585
  logger.info({ event: 'analyze_start', rootDir }, 'Starting agnostic project analysis');
1418
1586
  // ── 1. Discover project artifacts ──────────────────────────────────────
1419
- const artifacts = (0, projectDiscovery_1.discoverProject)({ rootDir });
1587
+ const artifacts = (0, projectDiscovery_1.discoverProject)({
1588
+ rootDir,
1589
+ generatedSourceDirs: analyzerCfg.project.generatedSourceDirs,
1590
+ });
1420
1591
  console.log('\n=== API Test Coverage Analyzer ===');
1421
1592
  console.log(`Project root: ${rootDir}`);
1422
1593
  console.log(`Languages detected: ${artifacts.languages.join(', ') || 'none'}`);
@@ -1592,7 +1763,10 @@ program
1592
1763
  warnings.push('No API spec files found; attempting route inference for endpoint/error coverage.');
1593
1764
  // ── 4a-alt. Inferred route endpoint coverage ──────────────────────────
1594
1765
  try {
1595
- const routeResult = (0, routeInference_1.inferRoutes)(artifacts.serviceFiles);
1766
+ const routeResult = (0, routeInference_1.inferRoutes)(artifacts.serviceFiles, {
1767
+ generatedSourceDirs: analyzerCfg.project.generatedSourceDirs,
1768
+ openApiFirst: analyzerCfg.project.openApiFirst,
1769
+ });
1596
1770
  if (routeResult.routes.length > 0) {
1597
1771
  const routesPath = (0, routeInference_1.writeInferredRoutes)(routeResult, reportsDir);
1598
1772
  console.log(`\nRoute Inference`);
@@ -2114,6 +2288,49 @@ program
2114
2288
  // Keep the process alive — the HTTP server holds the event loop open
2115
2289
  }
2116
2290
  });
2291
+ // ─── unit-analysis command ────────────────────────────────────────────────────
2292
+ program
2293
+ .command('unit-analysis')
2294
+ .description('Analyze unit test coverage reports, mutation testing, smells, slow tests, and independence')
2295
+ .option('--root <dir>', 'Project root to analyze (default: current working directory)')
2296
+ .option('--reports-dir <dir>', 'Directory to write reports to (default: reports/)')
2297
+ .option('--format <formats>', 'Comma-separated list of report formats: json,html,csv,junit (default: json,html)', 'json,html')
2298
+ .action(async (options) => {
2299
+ var _a, _b, _c;
2300
+ const { metricsPort, serviceName } = setupObservability();
2301
+ const logger = (0, observability_1.getLogger)();
2302
+ const configPath = program.opts()['config'];
2303
+ const analyzerCfg = (0, config_1.loadCentralConfig)(configPath);
2304
+ const rootDir = path.resolve((_a = options['root']) !== null && _a !== void 0 ? _a : process.cwd());
2305
+ const reportsDir = path.resolve((_c = (_b = options['reportsDir']) !== null && _b !== void 0 ? _b : analyzerCfg.reports.outputDir) !== null && _c !== void 0 ? _c : 'reports');
2306
+ const formats = (0, reporting_1.parseFormats)(options['format']);
2307
+ const artifacts = (0, projectDiscovery_1.discoverProject)({
2308
+ rootDir,
2309
+ generatedSourceDirs: analyzerCfg.project.generatedSourceDirs,
2310
+ });
2311
+ logger.info({ event: 'unit_analysis_start', rootDir }, 'Starting unit analysis');
2312
+ console.log(`Running unit analysis in: ${rootDir}`);
2313
+ const execution = await (0, unitAnalysis_1.analyzeUnitTests)({
2314
+ rootDir,
2315
+ testFiles: artifacts.testFiles,
2316
+ serviceFiles: artifacts.serviceFiles,
2317
+ languages: artifacts.languages,
2318
+ config: analyzerCfg.unitAnalysis,
2319
+ });
2320
+ (0, reporting_1.generateMultiFormatReports)([execution.result], formats, reportsDir, {}, (0, observability_1.buildObservabilityInfo)(metricsPort));
2321
+ for (const warning of execution.warnings) {
2322
+ console.warn(`[WARN] ${warning}`);
2323
+ }
2324
+ for (const failure of execution.failures) {
2325
+ console.error(`[FAIL] ${failure}`);
2326
+ }
2327
+ if (execution.failures.length > 0) {
2328
+ process.exitCode = 1;
2329
+ }
2330
+ console.log(`Unit analysis complete: ${execution.result.coveredItems}/${execution.result.totalItems} unit items covered (${execution.result.coveragePercent}%)`);
2331
+ console.log(`Reports written to: ${reportsDir}`);
2332
+ await finaliseObservability([execution.result], {}, metricsPort, serviceName);
2333
+ });
2117
2334
  // ─── event-coverage command ───────────────────────────────────────────────────
2118
2335
  program
2119
2336
  .command('event-coverage')
@@ -32,16 +32,24 @@ export interface RouteInferenceResult {
32
32
  filesAnalyzed: number;
33
33
  warnings: string[];
34
34
  }
35
+ export interface RouteInferenceOptions {
36
+ generatedSourceDirs?: string[];
37
+ openApiFirst?: {
38
+ enabled?: boolean;
39
+ generatedInterfaceSuffixes?: string[];
40
+ excludeFromEndpoints?: string[];
41
+ };
42
+ }
35
43
  /**
36
44
  * Infer routes from a single source file.
37
45
  * Routes are deduplicated by method:path within the file.
38
46
  * Code-based patterns take priority over JSDoc annotations for the same method+path.
39
47
  */
40
- export declare function inferRoutesFromFile(filePath: string): InferredRoute[];
48
+ export declare function inferRoutesFromFile(filePath: string, options?: RouteInferenceOptions): InferredRoute[];
41
49
  /**
42
50
  * Infer routes from a set of service source files.
43
51
  */
44
- export declare function inferRoutes(serviceFiles: string[]): RouteInferenceResult;
52
+ export declare function inferRoutes(serviceFiles: string[], options?: RouteInferenceOptions): RouteInferenceResult;
45
53
  /**
46
54
  * Write inferred routes to the reports directory.
47
55
  * Returns the path of the written file.
@@ -1 +1 @@
1
- {"version":3,"file":"routeInference.d.ts","sourceRoot":"","sources":["../../../src/inference/routeInference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,eAAO,MAAM,YAAY,uEAAwE,CAAC;AAClG,MAAM,MAAM,UAAU,GAAG,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAqFD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CAkIrE;AAiWD;;GAEG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,oBAAoB,CAgBxE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAY5F"}
1
+ {"version":3,"file":"routeInference.d.ts","sourceRoot":"","sources":["../../../src/inference/routeInference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAaH,eAAO,MAAM,YAAY,uEAAwE,CAAC;AAClG,MAAM,MAAM,UAAU,GAAG,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,YAAY,CAAC,EAAE;QACb,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;QACtC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;KACjC,CAAC;CACH;AAqFD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,aAAa,EAAE,CAkI1G;AA6qBD;;GAEG;AACH,wBAAgB,WAAW,CACzB,YAAY,EAAE,MAAM,EAAE,EACtB,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAgBtB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAY5F"}