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.
- package/README.md +39 -0
- package/config.yaml.example +35 -0
- package/dist/dashboard/dist/assets/_basePickBy-BamgEusj.js +1 -0
- package/dist/dashboard/dist/assets/_basePickBy-D4Hl8chy.js +1 -0
- package/dist/dashboard/dist/assets/_baseUniq-BBhq12Ja.js +1 -0
- package/dist/dashboard/dist/assets/_baseUniq-BSUUnV_V.js +1 -0
- package/dist/dashboard/dist/assets/arc-Dh-qL1ea.js +1 -0
- package/dist/dashboard/dist/assets/arc-DhDluTY5.js +1 -0
- package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-BxQ_anmt.js +36 -0
- package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-DGlUU7dC.js +36 -0
- package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-CgXi3kEZ.js +122 -0
- package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-Krm3lc7z.js +122 -0
- package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-Cfd4OeWg.js +10 -0
- package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-Cr3xB15y.js +10 -0
- package/dist/dashboard/dist/assets/channel-DYAie-7m.js +1 -0
- package/dist/dashboard/dist/assets/channel-a4t2URTe.js +1 -0
- package/dist/dashboard/dist/assets/chunk-4BX2VUAB-BaW3__pI.js +1 -0
- package/dist/dashboard/dist/assets/chunk-4BX2VUAB-ljBQ5lHA.js +1 -0
- package/dist/dashboard/dist/assets/chunk-55IACEB6-Cikrdc3Q.js +1 -0
- package/dist/dashboard/dist/assets/chunk-55IACEB6-DyYevfEQ.js +1 -0
- package/dist/dashboard/dist/assets/chunk-B4BG7PRW-C2bwZFec.js +165 -0
- package/dist/dashboard/dist/assets/chunk-B4BG7PRW-dtHgbkmj.js +165 -0
- package/dist/dashboard/dist/assets/chunk-DI55MBZ5-Cv3hm2Ke.js +220 -0
- package/dist/dashboard/dist/assets/chunk-DI55MBZ5-DO0T2xne.js +220 -0
- package/dist/dashboard/dist/assets/chunk-FMBD7UC4-CCYA4j_f.js +15 -0
- package/dist/dashboard/dist/assets/chunk-FMBD7UC4-Ds1_OqKH.js +15 -0
- package/dist/dashboard/dist/assets/chunk-QN33PNHL-B6zkzIAo.js +1 -0
- package/dist/dashboard/dist/assets/chunk-QN33PNHL-Cdhqs7xo.js +1 -0
- package/dist/dashboard/dist/assets/chunk-QZHKN3VN-BzHw38Ki.js +1 -0
- package/dist/dashboard/dist/assets/chunk-QZHKN3VN-C7xuA6tl.js +1 -0
- package/dist/dashboard/dist/assets/chunk-TZMSLE5B-D_ea_wdP.js +1 -0
- package/dist/dashboard/dist/assets/chunk-TZMSLE5B-dkJ0rsgF.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-B6SxXE6T.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-DiIv5Pho.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-B6SxXE6T.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-DiIv5Pho.js +1 -0
- package/dist/dashboard/dist/assets/clone-B4LorrSy.js +1 -0
- package/dist/dashboard/dist/assets/clone-kcKg1tUH.js +1 -0
- package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-DasyAK5c.js +1 -0
- package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-jzGbyPIS.js +1 -0
- package/dist/dashboard/dist/assets/dagre-6UL2VRFP-D7rgvBx1.js +4 -0
- package/dist/dashboard/dist/assets/dagre-6UL2VRFP-m-5bs635.js +4 -0
- package/dist/dashboard/dist/assets/diagram-PSM6KHXK-2rYklqon.js +24 -0
- package/dist/dashboard/dist/assets/diagram-PSM6KHXK-CYFwwEdy.js +24 -0
- package/dist/dashboard/dist/assets/diagram-QEK2KX5R-CGrvALqm.js +43 -0
- package/dist/dashboard/dist/assets/diagram-QEK2KX5R-m4Fda1GA.js +43 -0
- package/dist/dashboard/dist/assets/diagram-S2PKOQOG-BDVk4AKU.js +24 -0
- package/dist/dashboard/dist/assets/diagram-S2PKOQOG-DA3c-QP4.js +24 -0
- package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-3-jAbxQ6.js +60 -0
- package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-BsYH8cLH.js +60 -0
- package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-Cfv1hkQB.js +162 -0
- package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-Da_JhBCy.js +162 -0
- package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-B7GZPGck.js +267 -0
- package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-D8FTswNn.js +267 -0
- package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-BFJR-ITH.js +65 -0
- package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-K8X-_4av.js +65 -0
- package/dist/dashboard/dist/assets/graph-CIvnjOQQ.js +1 -0
- package/dist/dashboard/dist/assets/graph-CfuGK9GG.js +1 -0
- package/dist/dashboard/dist/assets/index-BWX0sSZn.css +1 -0
- package/dist/dashboard/dist/assets/index-CbAFWEor.js +777 -0
- package/dist/dashboard/dist/assets/index-DS-KIxwV.js +777 -0
- package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-CaIaIUhT.js +2 -0
- package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-OcK0Lxgi.js +2 -0
- package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-D6dwPswq.js +139 -0
- package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-DTJukVOY.js +139 -0
- package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-CERyhhrH.js +89 -0
- package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-Di65fNuD.js +89 -0
- package/dist/dashboard/dist/assets/layout-DAt24RVX.js +1 -0
- package/dist/dashboard/dist/assets/layout-v7cCi3Fl.js +1 -0
- package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-BvNtTz8N.js +68 -0
- package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-DxI8MXCF.js +68 -0
- package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-BafKx3_Y.js +30 -0
- package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-Cjg80C_b.js +30 -0
- package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-BcZsArkk.js +7 -0
- package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-YtFFUYGD.js +7 -0
- package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-CqFAO2t6.js +64 -0
- package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-DLV2LTE5.js +64 -0
- package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-C6_Urrii.js +10 -0
- package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-CqSaCg-3.js +10 -0
- package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-6IXD1uqW.js +145 -0
- package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-D33UwAtz.js +145 -0
- package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-DSp83t9D.js +1 -0
- package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-DvSVQAfp.js +1 -0
- package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-BMFdt0QQ.js +1 -0
- package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-OTWrEpQO.js +1 -0
- package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-Cll7Nvth.js +61 -0
- package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-D5Bb3Jj7.js +61 -0
- package/dist/dashboard/dist/assets/treemap-GDKQZRPO-CKbkkwye.js +162 -0
- package/dist/dashboard/dist/assets/treemap-GDKQZRPO-DtqX8zNC.js +162 -0
- package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-C_Tlzchx.js +7 -0
- package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-zxwS9i0A.js +7 -0
- package/dist/dashboard/dist/index.html +2 -2
- package/dist/dashboard/dist/reports/coverage-summary.json +75 -1
- package/dist/dashboard/dist/reports/security-full.json +157 -0
- package/dist/src/compatibilityCoverage.d.ts +34 -15
- package/dist/src/compatibilityCoverage.d.ts.map +1 -1
- package/dist/src/compatibilityCoverage.js +387 -85
- package/dist/src/config/defaultConfig.d.ts.map +1 -1
- package/dist/src/config/defaultConfig.js +62 -0
- package/dist/src/config/schema.d.ts.map +1 -1
- package/dist/src/config/schema.js +1 -1
- package/dist/src/config/types.d.ts +81 -1
- package/dist/src/config/types.d.ts.map +1 -1
- package/dist/src/config/validateConfig.d.ts.map +1 -1
- package/dist/src/config/validateConfig.js +126 -0
- package/dist/src/contracts/compatibilityMatrix.d.ts +20 -0
- package/dist/src/contracts/compatibilityMatrix.d.ts.map +1 -0
- package/dist/src/contracts/compatibilityMatrix.js +198 -0
- package/dist/src/contracts/pactBrokerClient.d.ts +10 -0
- package/dist/src/contracts/pactBrokerClient.d.ts.map +1 -0
- package/dist/src/contracts/pactBrokerClient.js +117 -0
- package/dist/src/contracts/schemaEvolutionChecker.d.ts +17 -0
- package/dist/src/contracts/schemaEvolutionChecker.d.ts.map +1 -0
- package/dist/src/contracts/schemaEvolutionChecker.js +95 -0
- package/dist/src/contracts/springCloudContractParser.d.ts +10 -0
- package/dist/src/contracts/springCloudContractParser.d.ts.map +1 -0
- package/dist/src/contracts/springCloudContractParser.js +144 -0
- package/dist/src/discovery/fileClassifier.d.ts.map +1 -1
- package/dist/src/discovery/fileClassifier.js +25 -0
- package/dist/src/discovery/projectDiscovery.d.ts +2 -0
- package/dist/src/discovery/projectDiscovery.d.ts.map +1 -1
- package/dist/src/discovery/projectDiscovery.js +25 -25
- package/dist/src/index.js +233 -16
- package/dist/src/inference/routeInference.d.ts +10 -2
- package/dist/src/inference/routeInference.d.ts.map +1 -1
- package/dist/src/inference/routeInference.js +363 -62
- package/dist/src/languageDetection.d.ts.map +1 -1
- package/dist/src/languageDetection.js +21 -4
- package/dist/src/lib/index.d.ts +3 -0
- package/dist/src/lib/index.d.ts.map +1 -1
- package/dist/src/lib/index.js +3 -1
- package/dist/src/pipeline/stages/tia/parameterizedTestExpander.js +152 -79
- package/dist/src/pipeline/stages/tia/testEndpointMapper.d.ts +5 -1
- package/dist/src/pipeline/stages/tia/testEndpointMapper.d.ts.map +1 -1
- package/dist/src/pipeline/stages/tia/testEndpointMapper.js +356 -42
- package/dist/src/pipeline/stages/tia/testLayerClassifier.d.ts.map +1 -1
- package/dist/src/pipeline/stages/tia/testLayerClassifier.js +20 -5
- package/dist/src/pipeline/stages/tia/tiaStage.d.ts.map +1 -1
- package/dist/src/pipeline/stages/tia/tiaStage.js +3 -1
- package/dist/src/pipeline/stages/tia/types.d.ts +11 -2
- package/dist/src/pipeline/stages/tia/types.d.ts.map +1 -1
- package/dist/src/projectDefaults.d.ts +6 -0
- package/dist/src/projectDefaults.d.ts.map +1 -0
- package/dist/src/projectDefaults.js +43 -0
- package/dist/src/security/hub.d.ts +81 -0
- package/dist/src/security/hub.d.ts.map +1 -0
- package/dist/src/security/hub.js +420 -0
- package/dist/src/security/index.d.ts +1 -0
- package/dist/src/security/index.d.ts.map +1 -1
- package/dist/src/security/index.js +8 -2
- package/dist/src/security/normalizers/gitleaks.d.ts +7 -0
- package/dist/src/security/normalizers/gitleaks.d.ts.map +1 -0
- package/dist/src/security/normalizers/gitleaks.js +32 -0
- package/dist/src/security/scanners/gitleaks.d.ts +3 -0
- package/dist/src/security/scanners/gitleaks.d.ts.map +1 -0
- package/dist/src/security/scanners/gitleaks.js +105 -0
- package/dist/src/security/scanners/semgrep.d.ts.map +1 -1
- package/dist/src/security/scanners/semgrep.js +24 -2
- package/dist/src/security/scanners/trivy.d.ts.map +1 -1
- package/dist/src/security/scanners/trivy.js +24 -2
- package/dist/src/security/scanners/zap.d.ts.map +1 -1
- package/dist/src/security/scanners/zap.js +27 -2
- package/dist/src/security/types.d.ts +15 -1
- package/dist/src/security/types.d.ts.map +1 -1
- package/dist/src/streaming/schema/index.d.ts +23 -0
- package/dist/src/streaming/schema/index.d.ts.map +1 -0
- package/dist/src/streaming/schema/index.js +196 -0
- package/dist/src/summary/markdownRenderer.d.ts.map +1 -1
- package/dist/src/summary/markdownRenderer.js +15 -1
- package/dist/src/summary/summaryTypes.d.ts.map +1 -1
- package/dist/src/summary/summaryTypes.js +1 -0
- package/dist/src/unitAnalysis.d.ts +145 -0
- package/dist/src/unitAnalysis.d.ts.map +1 -0
- package/dist/src/unitAnalysis.js +1392 -0
- 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'
|
|
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
|
-
:
|
|
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: (
|
|
606
|
-
deepConfig: (
|
|
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 = { ...((
|
|
701
|
+
const thresholds = { ...((_e = config.thresholds) !== null && _e !== void 0 ? _e : {}) };
|
|
619
702
|
// Run plugins
|
|
620
703
|
const pluginContext = {
|
|
621
|
-
testPatterns: (
|
|
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
|
|
812
|
-
|
|
813
|
-
|
|
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)(
|
|
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
|
-
|
|
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)({
|
|
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;
|
|
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"}
|