api-tests-coverage 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +703 -0
- package/config.yaml.example +227 -0
- package/dist/action/src/index.d.ts +2 -0
- package/dist/action/src/index.d.ts.map +1 -0
- package/dist/action/src/index.js +349 -0
- package/dist/action/src/prComment.d.ts +34 -0
- package/dist/action/src/prComment.d.ts.map +1 -0
- package/dist/action/src/prComment.js +146 -0
- package/dist/src/ast/astAnalysisOrchestrator.d.ts +36 -0
- package/dist/src/ast/astAnalysisOrchestrator.d.ts.map +1 -0
- package/dist/src/ast/astAnalysisOrchestrator.js +123 -0
- package/dist/src/ast/astTypes.d.ts +105 -0
- package/dist/src/ast/astTypes.d.ts.map +1 -0
- package/dist/src/ast/astTypes.js +9 -0
- package/dist/src/ast/languageAnalyzer.d.ts +46 -0
- package/dist/src/ast/languageAnalyzer.d.ts.map +1 -0
- package/dist/src/ast/languageAnalyzer.js +9 -0
- package/dist/src/ast/languageCapabilities.d.ts +24 -0
- package/dist/src/ast/languageCapabilities.d.ts.map +1 -0
- package/dist/src/ast/languageCapabilities.js +92 -0
- package/dist/src/ast/parseFile.d.ts +16 -0
- package/dist/src/ast/parseFile.d.ts.map +1 -0
- package/dist/src/ast/parseFile.js +65 -0
- package/dist/src/ast/parserRegistry.d.ts +39 -0
- package/dist/src/ast/parserRegistry.d.ts.map +1 -0
- package/dist/src/ast/parserRegistry.js +66 -0
- package/dist/src/buildSummary.d.ts +26 -0
- package/dist/src/buildSummary.d.ts.map +1 -0
- package/dist/src/buildSummary.js +193 -0
- package/dist/src/businessCoverage.d.ts +68 -0
- package/dist/src/businessCoverage.d.ts.map +1 -0
- package/dist/src/businessCoverage.js +290 -0
- package/dist/src/compatibilityCoverage.d.ts +83 -0
- package/dist/src/compatibilityCoverage.d.ts.map +1 -0
- package/dist/src/compatibilityCoverage.js +501 -0
- package/dist/src/config/defaultConfig.d.ts +9 -0
- package/dist/src/config/defaultConfig.d.ts.map +1 -0
- package/dist/src/config/defaultConfig.js +97 -0
- package/dist/src/config/index.d.ts +12 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +37 -0
- package/dist/src/config/loadConfig.d.ts +29 -0
- package/dist/src/config/loadConfig.d.ts.map +1 -0
- package/dist/src/config/loadConfig.js +135 -0
- package/dist/src/config/mergeConfig.d.ts +15 -0
- package/dist/src/config/mergeConfig.d.ts.map +1 -0
- package/dist/src/config/mergeConfig.js +57 -0
- package/dist/src/config/schema.d.ts +15 -0
- package/dist/src/config/schema.d.ts.map +1 -0
- package/dist/src/config/schema.js +30 -0
- package/dist/src/config/types.d.ts +175 -0
- package/dist/src/config/types.d.ts.map +1 -0
- package/dist/src/config/types.js +9 -0
- package/dist/src/config/validateConfig.d.ts +22 -0
- package/dist/src/config/validateConfig.d.ts.map +1 -0
- package/dist/src/config/validateConfig.js +171 -0
- package/dist/src/config.d.ts +168 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +204 -0
- package/dist/src/coverage/deep-analysis/callGraph.d.ts +67 -0
- package/dist/src/coverage/deep-analysis/callGraph.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/callGraph.js +275 -0
- package/dist/src/coverage/deep-analysis/deepEndpointResolver.d.ts +23 -0
- package/dist/src/coverage/deep-analysis/deepEndpointResolver.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/deepEndpointResolver.js +394 -0
- package/dist/src/coverage/deep-analysis/index.d.ts +17 -0
- package/dist/src/coverage/deep-analysis/index.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/index.js +63 -0
- package/dist/src/coverage/deep-analysis/resolveAssertions.d.ts +60 -0
- package/dist/src/coverage/deep-analysis/resolveAssertions.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveAssertions.js +121 -0
- package/dist/src/coverage/deep-analysis/resolveConstants.d.ts +36 -0
- package/dist/src/coverage/deep-analysis/resolveConstants.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveConstants.js +92 -0
- package/dist/src/coverage/deep-analysis/resolveEnums.d.ts +55 -0
- package/dist/src/coverage/deep-analysis/resolveEnums.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveEnums.js +152 -0
- package/dist/src/coverage/deep-analysis/resolveMethodChains.d.ts +70 -0
- package/dist/src/coverage/deep-analysis/resolveMethodChains.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveMethodChains.js +152 -0
- package/dist/src/coverage/deep-analysis/resolvePaths.d.ts +80 -0
- package/dist/src/coverage/deep-analysis/resolvePaths.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolvePaths.js +216 -0
- package/dist/src/coverage/deep-analysis/resolveRequestWrappers.d.ts +71 -0
- package/dist/src/coverage/deep-analysis/resolveRequestWrappers.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveRequestWrappers.js +226 -0
- package/dist/src/coverage/deep-analysis/symbolTable.d.ts +58 -0
- package/dist/src/coverage/deep-analysis/symbolTable.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/symbolTable.js +230 -0
- package/dist/src/coverage/deep-analysis/types.d.ts +122 -0
- package/dist/src/coverage/deep-analysis/types.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/types.js +21 -0
- package/dist/src/discovery/fileClassifier.d.ts +50 -0
- package/dist/src/discovery/fileClassifier.d.ts.map +1 -0
- package/dist/src/discovery/fileClassifier.js +238 -0
- package/dist/src/discovery/projectDiscovery.d.ts +66 -0
- package/dist/src/discovery/projectDiscovery.d.ts.map +1 -0
- package/dist/src/discovery/projectDiscovery.js +287 -0
- package/dist/src/endpointCoverage.d.ts +70 -0
- package/dist/src/endpointCoverage.d.ts.map +1 -0
- package/dist/src/endpointCoverage.js +381 -0
- package/dist/src/errorCoverage.d.ts +93 -0
- package/dist/src/errorCoverage.d.ts.map +1 -0
- package/dist/src/errorCoverage.js +698 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +1441 -0
- package/dist/src/inference/businessRuleInference.d.ts +63 -0
- package/dist/src/inference/businessRuleInference.d.ts.map +1 -0
- package/dist/src/inference/businessRuleInference.js +268 -0
- package/dist/src/inference/integrationFlowInference.d.ts +56 -0
- package/dist/src/inference/integrationFlowInference.d.ts.map +1 -0
- package/dist/src/inference/integrationFlowInference.js +266 -0
- package/dist/src/integrationCoverage.d.ts +72 -0
- package/dist/src/integrationCoverage.d.ts.map +1 -0
- package/dist/src/integrationCoverage.js +317 -0
- package/dist/src/intelligence/index.d.ts +20 -0
- package/dist/src/intelligence/index.d.ts.map +1 -0
- package/dist/src/intelligence/index.js +105 -0
- package/dist/src/intelligence/linkageEngine.d.ts +20 -0
- package/dist/src/intelligence/linkageEngine.d.ts.map +1 -0
- package/dist/src/intelligence/linkageEngine.js +522 -0
- package/dist/src/intelligence/markdownReporter.d.ts +12 -0
- package/dist/src/intelligence/markdownReporter.d.ts.map +1 -0
- package/dist/src/intelligence/markdownReporter.js +265 -0
- package/dist/src/intelligence/riskScoring.d.ts +53 -0
- package/dist/src/intelligence/riskScoring.d.ts.map +1 -0
- package/dist/src/intelligence/riskScoring.js +181 -0
- package/dist/src/intelligence/types.d.ts +121 -0
- package/dist/src/intelligence/types.d.ts.map +1 -0
- package/dist/src/intelligence/types.js +8 -0
- package/dist/src/languageDetection.d.ts +100 -0
- package/dist/src/languageDetection.d.ts.map +1 -0
- package/dist/src/languageDetection.js +349 -0
- package/dist/src/languages/java/index.d.ts +16 -0
- package/dist/src/languages/java/index.d.ts.map +1 -0
- package/dist/src/languages/java/index.js +103 -0
- package/dist/src/languages/java/parser.d.ts +7 -0
- package/dist/src/languages/java/parser.d.ts.map +1 -0
- package/dist/src/languages/java/parser.js +50 -0
- package/dist/src/languages/java/semanticBuilder.d.ts +21 -0
- package/dist/src/languages/java/semanticBuilder.d.ts.map +1 -0
- package/dist/src/languages/java/semanticBuilder.js +358 -0
- package/dist/src/languages/javascript/annotationExtractor.d.ts +20 -0
- package/dist/src/languages/javascript/annotationExtractor.d.ts.map +1 -0
- package/dist/src/languages/javascript/annotationExtractor.js +94 -0
- package/dist/src/languages/javascript/assertionResolver.d.ts +18 -0
- package/dist/src/languages/javascript/assertionResolver.d.ts.map +1 -0
- package/dist/src/languages/javascript/assertionResolver.js +150 -0
- package/dist/src/languages/javascript/callResolver.d.ts +23 -0
- package/dist/src/languages/javascript/callResolver.d.ts.map +1 -0
- package/dist/src/languages/javascript/callResolver.js +236 -0
- package/dist/src/languages/javascript/httpInteractionExtractor.d.ts +23 -0
- package/dist/src/languages/javascript/httpInteractionExtractor.d.ts.map +1 -0
- package/dist/src/languages/javascript/httpInteractionExtractor.js +205 -0
- package/dist/src/languages/javascript/index.d.ts +20 -0
- package/dist/src/languages/javascript/index.d.ts.map +1 -0
- package/dist/src/languages/javascript/index.js +136 -0
- package/dist/src/languages/javascript/parser.d.ts +14 -0
- package/dist/src/languages/javascript/parser.d.ts.map +1 -0
- package/dist/src/languages/javascript/parser.js +38 -0
- package/dist/src/languages/javascript/symbolResolver.d.ts +31 -0
- package/dist/src/languages/javascript/symbolResolver.d.ts.map +1 -0
- package/dist/src/languages/javascript/symbolResolver.js +183 -0
- package/dist/src/languages/kotlin/index.d.ts +16 -0
- package/dist/src/languages/kotlin/index.d.ts.map +1 -0
- package/dist/src/languages/kotlin/index.js +151 -0
- package/dist/src/languages/kotlin/parser.d.ts +11 -0
- package/dist/src/languages/kotlin/parser.d.ts.map +1 -0
- package/dist/src/languages/kotlin/parser.js +74 -0
- package/dist/src/languages/python/index.d.ts +15 -0
- package/dist/src/languages/python/index.d.ts.map +1 -0
- package/dist/src/languages/python/index.js +293 -0
- package/dist/src/languages/ruby/index.d.ts +15 -0
- package/dist/src/languages/ruby/index.d.ts.map +1 -0
- package/dist/src/languages/ruby/index.js +274 -0
- package/dist/src/languages/shared/treeSitterUtils.d.ts +43 -0
- package/dist/src/languages/shared/treeSitterUtils.d.ts.map +1 -0
- package/dist/src/languages/shared/treeSitterUtils.js +100 -0
- package/dist/src/languages/typescript/index.d.ts +14 -0
- package/dist/src/languages/typescript/index.d.ts.map +1 -0
- package/dist/src/languages/typescript/index.js +25 -0
- package/dist/src/lib/index.d.ts +228 -0
- package/dist/src/lib/index.d.ts.map +1 -0
- package/dist/src/lib/index.js +486 -0
- package/dist/src/mcp/client/index.d.ts +37 -0
- package/dist/src/mcp/client/index.d.ts.map +1 -0
- package/dist/src/mcp/client/index.js +235 -0
- package/dist/src/mcp/config.d.ts +50 -0
- package/dist/src/mcp/config.d.ts.map +1 -0
- package/dist/src/mcp/config.js +125 -0
- package/dist/src/mcp/events.d.ts +24 -0
- package/dist/src/mcp/events.d.ts.map +1 -0
- package/dist/src/mcp/events.js +48 -0
- package/dist/src/mcp/fallback/index.d.ts +50 -0
- package/dist/src/mcp/fallback/index.d.ts.map +1 -0
- package/dist/src/mcp/fallback/index.js +216 -0
- package/dist/src/mcp/index.d.ts +67 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +212 -0
- package/dist/src/mcp/normalizer.d.ts +21 -0
- package/dist/src/mcp/normalizer.d.ts.map +1 -0
- package/dist/src/mcp/normalizer.js +99 -0
- package/dist/src/mcp/prompts/index.d.ts +86 -0
- package/dist/src/mcp/prompts/index.d.ts.map +1 -0
- package/dist/src/mcp/prompts/index.js +304 -0
- package/dist/src/mcp/templates/index.d.ts +35 -0
- package/dist/src/mcp/templates/index.d.ts.map +1 -0
- package/dist/src/mcp/templates/index.js +143 -0
- package/dist/src/mcp/testing/mock-server/index.d.ts +47 -0
- package/dist/src/mcp/testing/mock-server/index.d.ts.map +1 -0
- package/dist/src/mcp/testing/mock-server/index.js +157 -0
- package/dist/src/mcp/types.d.ts +127 -0
- package/dist/src/mcp/types.d.ts.map +1 -0
- package/dist/src/mcp/types.js +8 -0
- package/dist/src/observability.d.ts +138 -0
- package/dist/src/observability.d.ts.map +1 -0
- package/dist/src/observability.js +519 -0
- package/dist/src/parameterCoverage.d.ts +75 -0
- package/dist/src/parameterCoverage.d.ts.map +1 -0
- package/dist/src/parameterCoverage.js +629 -0
- package/dist/src/perfResilienceCoverage.d.ts +155 -0
- package/dist/src/perfResilienceCoverage.d.ts.map +1 -0
- package/dist/src/perfResilienceCoverage.js +670 -0
- package/dist/src/pluginLoader.d.ts +51 -0
- package/dist/src/pluginLoader.d.ts.map +1 -0
- package/dist/src/pluginLoader.js +72 -0
- package/dist/src/publishing.d.ts +63 -0
- package/dist/src/publishing.d.ts.map +1 -0
- package/dist/src/publishing.js +379 -0
- package/dist/src/qualityGate.d.ts +58 -0
- package/dist/src/qualityGate.d.ts.map +1 -0
- package/dist/src/qualityGate.js +118 -0
- package/dist/src/reporting.d.ts +41 -0
- package/dist/src/reporting.d.ts.map +1 -0
- package/dist/src/reporting.js +278 -0
- package/dist/src/screenshots.d.ts +71 -0
- package/dist/src/screenshots.d.ts.map +1 -0
- package/dist/src/screenshots.js +141 -0
- package/dist/src/security/gate/index.d.ts +11 -0
- package/dist/src/security/gate/index.d.ts.map +1 -0
- package/dist/src/security/gate/index.js +65 -0
- package/dist/src/security/index.d.ts +30 -0
- package/dist/src/security/index.d.ts.map +1 -0
- package/dist/src/security/index.js +342 -0
- package/dist/src/security/normalizers/semgrep.d.ts +10 -0
- package/dist/src/security/normalizers/semgrep.d.ts.map +1 -0
- package/dist/src/security/normalizers/semgrep.js +104 -0
- package/dist/src/security/normalizers/trivy.d.ts +10 -0
- package/dist/src/security/normalizers/trivy.d.ts.map +1 -0
- package/dist/src/security/normalizers/trivy.js +78 -0
- package/dist/src/security/normalizers/zap.d.ts +10 -0
- package/dist/src/security/normalizers/zap.d.ts.map +1 -0
- package/dist/src/security/normalizers/zap.js +104 -0
- package/dist/src/security/scanners/semgrep.d.ts +6 -0
- package/dist/src/security/scanners/semgrep.d.ts.map +1 -0
- package/dist/src/security/scanners/semgrep.js +125 -0
- package/dist/src/security/scanners/trivy.d.ts +6 -0
- package/dist/src/security/scanners/trivy.d.ts.map +1 -0
- package/dist/src/security/scanners/trivy.js +115 -0
- package/dist/src/security/scanners/zap.d.ts +6 -0
- package/dist/src/security/scanners/zap.d.ts.map +1 -0
- package/dist/src/security/scanners/zap.js +135 -0
- package/dist/src/security/types.d.ts +146 -0
- package/dist/src/security/types.d.ts.map +1 -0
- package/dist/src/security/types.js +6 -0
- package/dist/src/securityCoverage.d.ts +116 -0
- package/dist/src/securityCoverage.d.ts.map +1 -0
- package/dist/src/securityCoverage.js +725 -0
- package/dist/src/summary/buildSummary.d.ts +28 -0
- package/dist/src/summary/buildSummary.d.ts.map +1 -0
- package/dist/src/summary/buildSummary.js +257 -0
- package/dist/src/summary/evaluateMetrics.d.ts +31 -0
- package/dist/src/summary/evaluateMetrics.d.ts.map +1 -0
- package/dist/src/summary/evaluateMetrics.js +118 -0
- package/dist/src/summary/index.d.ts +10 -0
- package/dist/src/summary/index.d.ts.map +1 -0
- package/dist/src/summary/index.js +22 -0
- package/dist/src/summary/markdownRenderer.d.ts +139 -0
- package/dist/src/summary/markdownRenderer.d.ts.map +1 -0
- package/dist/src/summary/markdownRenderer.js +459 -0
- package/dist/src/summary/prSummary.d.ts +24 -0
- package/dist/src/summary/prSummary.d.ts.map +1 -0
- package/dist/src/summary/prSummary.js +233 -0
- package/dist/src/summary/summaryTypes.d.ts +35 -0
- package/dist/src/summary/summaryTypes.d.ts.map +1 -0
- package/dist/src/summary/summaryTypes.js +27 -0
- package/package.json +84 -0
|
@@ -0,0 +1,1441 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const endpointCoverage_1 = require("./endpointCoverage");
|
|
40
|
+
const parameterCoverage_1 = require("./parameterCoverage");
|
|
41
|
+
const businessCoverage_1 = require("./businessCoverage");
|
|
42
|
+
const integrationCoverage_1 = require("./integrationCoverage");
|
|
43
|
+
const errorCoverage_1 = require("./errorCoverage");
|
|
44
|
+
const securityCoverage_1 = require("./securityCoverage");
|
|
45
|
+
const perfResilienceCoverage_1 = require("./perfResilienceCoverage");
|
|
46
|
+
const compatibilityCoverage_1 = require("./compatibilityCoverage");
|
|
47
|
+
const reporting_1 = require("./reporting");
|
|
48
|
+
const config_1 = require("./config");
|
|
49
|
+
const index_1 = require("./coverage/deep-analysis/index");
|
|
50
|
+
const index_2 = require("./security/index");
|
|
51
|
+
const pluginLoader_1 = require("./pluginLoader");
|
|
52
|
+
const observability_1 = require("./observability");
|
|
53
|
+
const languageDetection_1 = require("./languageDetection");
|
|
54
|
+
const index_3 = require("./intelligence/index");
|
|
55
|
+
const observability_2 = require("./observability");
|
|
56
|
+
const buildSummary_1 = require("./summary/buildSummary");
|
|
57
|
+
const prSummary_1 = require("./summary/prSummary");
|
|
58
|
+
const astAnalysisOrchestrator_1 = require("./ast/astAnalysisOrchestrator");
|
|
59
|
+
const projectDiscovery_1 = require("./discovery/projectDiscovery");
|
|
60
|
+
const businessRuleInference_1 = require("./inference/businessRuleInference");
|
|
61
|
+
const integrationFlowInference_1 = require("./inference/integrationFlowInference");
|
|
62
|
+
// Register all language AST analyzers at startup.
|
|
63
|
+
// This side-effect import ensures each language module's registerAnalyzer() call runs.
|
|
64
|
+
(0, astAnalysisOrchestrator_1.registerAllAnalyzers)();
|
|
65
|
+
const program = new commander_1.Command();
|
|
66
|
+
program
|
|
67
|
+
.name('api-tests-coverage-analyzer')
|
|
68
|
+
.description('Analyze API test coverage based on OpenAPI specs')
|
|
69
|
+
.version('0.1.0')
|
|
70
|
+
.option('--config <file>', 'Path to a coverage configuration file (default: config.yaml)')
|
|
71
|
+
.option('--log-level <level>', 'Log verbosity level: trace|debug|info|warn|error|silent', 'info')
|
|
72
|
+
.option('--metrics-port <port>', 'Start a Prometheus /metrics HTTP server on this port after analysis', parseInt)
|
|
73
|
+
.option('--service-name <name>', 'Service name label added to all Prometheus metrics', 'api-coverage-analyzer')
|
|
74
|
+
.option('--trace', 'Enable OpenTelemetry tracing (spans recorded in memory or exported via OTLP)')
|
|
75
|
+
.option('--trace-endpoint <url>', 'OTLP HTTP endpoint for trace export (e.g. http://localhost:4318)');
|
|
76
|
+
// ─── Config helper ─────────────────────────────────────────────────────────────
|
|
77
|
+
/**
|
|
78
|
+
* Load and return the resolved CoverageConfig for a command invocation.
|
|
79
|
+
*
|
|
80
|
+
* Loads via the central config loader (config.yaml) first. CLI threshold
|
|
81
|
+
* flags (non-zero values) take precedence and emit a deprecation warning.
|
|
82
|
+
* Legacy `testPatterns` / `plugins` are preserved via the old JSON loader
|
|
83
|
+
* for backward compatibility during the config.yaml migration.
|
|
84
|
+
*/
|
|
85
|
+
function loadCoverageConfig(configPath, cliThresholds) {
|
|
86
|
+
var _a, _b, _c, _d;
|
|
87
|
+
// Load via central config (config.yaml). Emits missing-config warning if absent.
|
|
88
|
+
const analyzerCfg = (0, config_1.loadCentralConfig)(configPath);
|
|
89
|
+
// Emit deprecation warnings for explicitly-set CLI threshold flags.
|
|
90
|
+
const activeCliThresholds = {};
|
|
91
|
+
for (const [key, value] of Object.entries(cliThresholds)) {
|
|
92
|
+
if (value > 0) {
|
|
93
|
+
process.stderr.write(`[DEPRECATED] --threshold-${key} CLI flag is deprecated. ` +
|
|
94
|
+
`Use thresholds.${key} in config.yaml instead.\n`);
|
|
95
|
+
activeCliThresholds[key] = value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Merge: central config thresholds < CLI threshold overrides.
|
|
99
|
+
const mergedThresholds = {
|
|
100
|
+
...analyzerCfg.thresholds,
|
|
101
|
+
...activeCliThresholds,
|
|
102
|
+
};
|
|
103
|
+
// For fields not covered by the new AnalyzerConfig schema (testPatterns,
|
|
104
|
+
// plugins, exclude), fall back to the legacy JSON config loader — these are
|
|
105
|
+
// only read when a legacy coverage.config.json is still present. They are
|
|
106
|
+
// not required and default to empty when absent.
|
|
107
|
+
let legacyTestPatterns = [];
|
|
108
|
+
let legacyPlugins = [];
|
|
109
|
+
let legacyExclude = { paths: [], methods: [] };
|
|
110
|
+
if (!configPath) {
|
|
111
|
+
try {
|
|
112
|
+
const legacyCfg = (0, config_1.resolveConfig)(undefined);
|
|
113
|
+
legacyTestPatterns = (_a = legacyCfg.testPatterns) !== null && _a !== void 0 ? _a : [];
|
|
114
|
+
legacyPlugins = (_b = legacyCfg.plugins) !== null && _b !== void 0 ? _b : [];
|
|
115
|
+
if (legacyCfg.exclude)
|
|
116
|
+
legacyExclude = {
|
|
117
|
+
paths: (_c = legacyCfg.exclude.paths) !== null && _c !== void 0 ? _c : [],
|
|
118
|
+
methods: (_d = legacyCfg.exclude.methods) !== null && _d !== void 0 ? _d : [],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// No legacy JSON config present — safe to ignore.
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
thresholds: mergedThresholds,
|
|
127
|
+
testPatterns: legacyTestPatterns,
|
|
128
|
+
plugins: legacyPlugins,
|
|
129
|
+
exclude: legacyExclude,
|
|
130
|
+
qualityGate: analyzerCfg.qualityGate,
|
|
131
|
+
mcp: analyzerCfg.mcp,
|
|
132
|
+
publishing: analyzerCfg.publishing
|
|
133
|
+
? {
|
|
134
|
+
enabled: analyzerCfg.publishing.enabled,
|
|
135
|
+
githubPages: analyzerCfg.publishing.githubPages,
|
|
136
|
+
}
|
|
137
|
+
: undefined,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Initialise all observability subsystems from the parent program options.
|
|
142
|
+
* Safe to call multiple times; subsequent calls are no-ops if already set up.
|
|
143
|
+
*/
|
|
144
|
+
function setupObservability() {
|
|
145
|
+
const opts = program.opts();
|
|
146
|
+
const logLevel = opts.logLevel || 'info';
|
|
147
|
+
const metricsPort = opts.metricsPort;
|
|
148
|
+
const serviceName = opts.serviceName || 'api-coverage-analyzer';
|
|
149
|
+
const traceEnabled = Boolean(opts.trace);
|
|
150
|
+
const traceEndpoint = opts.traceEndpoint;
|
|
151
|
+
(0, observability_1.initLogger)(logLevel);
|
|
152
|
+
(0, observability_1.initMetrics)(serviceName);
|
|
153
|
+
(0, observability_1.initTracing)(traceEnabled, traceEndpoint);
|
|
154
|
+
return { metricsPort, serviceName };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* After each command completes, record metrics, start the server (if requested),
|
|
158
|
+
* log results, and check thresholds.
|
|
159
|
+
*/
|
|
160
|
+
async function finaliseObservability(allResults, thresholds, metricsPort, serviceName) {
|
|
161
|
+
const logger = (0, observability_1.getLogger)();
|
|
162
|
+
// Record into Prometheus gauges
|
|
163
|
+
(0, observability_1.recordCoverageMetrics)(allResults, thresholds, serviceName);
|
|
164
|
+
// Structured log each result
|
|
165
|
+
for (const r of allResults) {
|
|
166
|
+
(0, observability_1.logCoverageResult)(logger, r, thresholds[r.type]);
|
|
167
|
+
}
|
|
168
|
+
// Log threshold breaches
|
|
169
|
+
for (const r of allResults) {
|
|
170
|
+
const t = thresholds[r.type];
|
|
171
|
+
if (t !== undefined && r.coveragePercent < t) {
|
|
172
|
+
(0, observability_1.logThresholdBreach)(logger, r.type, r.coveragePercent, t);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Start metrics HTTP server if requested
|
|
176
|
+
if (metricsPort) {
|
|
177
|
+
await (0, observability_1.startMetricsServer)(metricsPort);
|
|
178
|
+
logger.info({ event: 'metrics_server_start', port: metricsPort }, `Prometheus metrics available at http://localhost:${metricsPort}/metrics`);
|
|
179
|
+
console.log(`Prometheus metrics available at http://localhost:${metricsPort}/metrics`);
|
|
180
|
+
// Keep the server alive until the process is signalled; handle graceful shutdown
|
|
181
|
+
process.once('SIGINT', () => {
|
|
182
|
+
void (0, observability_1.stopMetricsServer)().then(() => process.exit(0));
|
|
183
|
+
});
|
|
184
|
+
process.once('SIGTERM', () => {
|
|
185
|
+
void (0, observability_1.stopMetricsServer)().then(() => process.exit(0));
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
program
|
|
190
|
+
.command('endpoint-coverage')
|
|
191
|
+
.description('Analyze which API endpoints are covered by integration tests')
|
|
192
|
+
.option('--spec <path>', 'Path to the OpenAPI/Swagger spec file', 'sample/openapi.yaml')
|
|
193
|
+
.option('--tests <glob>', 'Glob pattern for test files', 'tests/**/*.ts')
|
|
194
|
+
.option('--language <lang>', `Test language(s) to analyse. Accepted values: ${languageDetection_1.SUPPORTED_LANGUAGES.join(', ')}. ` +
|
|
195
|
+
'Use a comma-separated list or repeat the flag for multiple languages. ' +
|
|
196
|
+
"Default: 'auto' (inferred from file extensions).", (val, prev) => {
|
|
197
|
+
const parsed = (0, languageDetection_1.parseLanguageOption)(val);
|
|
198
|
+
return prev ? [...prev, ...parsed] : parsed;
|
|
199
|
+
}, [])
|
|
200
|
+
.option('--format <formats>', 'Comma-separated list of report formats: json,html,csv,junit (default: json,html)', 'json,html')
|
|
201
|
+
.option('--threshold-endpoint <percent>', 'Minimum required endpoint coverage percentage (0-100)', parseFloat, 0)
|
|
202
|
+
.action(async (options) => {
|
|
203
|
+
var _a, _b, _c;
|
|
204
|
+
const { metricsPort, serviceName } = setupObservability();
|
|
205
|
+
const logger = (0, observability_1.getLogger)();
|
|
206
|
+
const parentOpts = program.opts();
|
|
207
|
+
const config = loadCoverageConfig(parentOpts.config, {
|
|
208
|
+
endpoint: options.thresholdEndpoint,
|
|
209
|
+
});
|
|
210
|
+
const specPath = path.resolve(options.spec);
|
|
211
|
+
const languages = options.language.length > 0
|
|
212
|
+
? options.language
|
|
213
|
+
: ['auto'];
|
|
214
|
+
// Determine the test glob: config overrides CLI, and language overrides the default
|
|
215
|
+
let testsGlob;
|
|
216
|
+
if (config.testPatterns && config.testPatterns.length > 0) {
|
|
217
|
+
testsGlob = config.testPatterns[0];
|
|
218
|
+
}
|
|
219
|
+
else if (options.tests !== 'tests/**/*.ts') {
|
|
220
|
+
// User explicitly passed --tests
|
|
221
|
+
testsGlob = options.tests;
|
|
222
|
+
}
|
|
223
|
+
else if (!languages.includes('auto') && languages.length === 1) {
|
|
224
|
+
// Use language-specific default glob when a single language is specified
|
|
225
|
+
const langGlobs = (0, languageDetection_1.getDefaultGlobsForLanguage)(languages[0]);
|
|
226
|
+
testsGlob = langGlobs[0];
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
testsGlob = options.tests;
|
|
230
|
+
}
|
|
231
|
+
const reportsDir = path.resolve('reports');
|
|
232
|
+
const formats = (0, reporting_1.parseFormats)(options.format);
|
|
233
|
+
const span = (0, observability_1.startSpan)('endpoint-coverage', { specPath, testsGlob });
|
|
234
|
+
logger.info({ event: 'analysis_start', coverageType: 'endpoint', specPath, testsGlob, languages }, `Parsing spec: ${specPath}`);
|
|
235
|
+
console.log(`Parsing spec: ${specPath}`);
|
|
236
|
+
const endpoints = await (0, endpointCoverage_1.parseOpenApiSpec)(specPath);
|
|
237
|
+
console.log(`Analyzing tests matching: ${testsGlob} (language: ${languages.join(', ')})`);
|
|
238
|
+
const analyzerCfgForDeep = (0, config_1.loadCentralConfig)(parentOpts.config);
|
|
239
|
+
const deepCfg = (_a = analyzerCfgForDeep.scans.coverage) === null || _a === void 0 ? void 0 : _a.deepAnalysis;
|
|
240
|
+
const deepAnalysisConfig = {
|
|
241
|
+
...index_1.DEFAULT_DEEP_ANALYSIS_CONFIG,
|
|
242
|
+
...(deepCfg !== null && deepCfg !== void 0 ? deepCfg : {}),
|
|
243
|
+
};
|
|
244
|
+
const coverageMap = await (0, endpointCoverage_1.analyzeTestCoverage)(endpoints, testsGlob, languages, deepAnalysisConfig);
|
|
245
|
+
const report = (0, endpointCoverage_1.buildCoverageReport)(coverageMap);
|
|
246
|
+
// Write per-command legacy reports (JSON + HTML files named endpoint-coverage.*)
|
|
247
|
+
(0, endpointCoverage_1.generateReports)(report, reportsDir);
|
|
248
|
+
// Build standardised result and write multi-format summary reports
|
|
249
|
+
const result = {
|
|
250
|
+
type: 'endpoint',
|
|
251
|
+
totalItems: report.total,
|
|
252
|
+
coveredItems: report.covered,
|
|
253
|
+
coveragePercent: report.percentage,
|
|
254
|
+
details: report,
|
|
255
|
+
};
|
|
256
|
+
const thresholds = { ...((_b = config.thresholds) !== null && _b !== void 0 ? _b : {}) };
|
|
257
|
+
// Run plugins
|
|
258
|
+
const pluginContext = {
|
|
259
|
+
testPatterns: (_c = config.testPatterns) !== null && _c !== void 0 ? _c : [],
|
|
260
|
+
results: [result],
|
|
261
|
+
config,
|
|
262
|
+
};
|
|
263
|
+
const pluginResults = await (0, pluginLoader_1.runPlugins)(config, pluginContext);
|
|
264
|
+
const allResults = [result, ...pluginResults];
|
|
265
|
+
const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
|
|
266
|
+
(0, reporting_1.generateMultiFormatReports)(allResults, formats, reportsDir, thresholds, observabilityInfo);
|
|
267
|
+
console.log(`Endpoint coverage: ${report.covered}/${report.total} endpoints covered (${report.percentage}%)`);
|
|
268
|
+
console.log(`Reports written to: ${reportsDir}`);
|
|
269
|
+
span.end({ totalItems: report.total, coveredItems: report.covered, coveragePercent: report.percentage });
|
|
270
|
+
logger.info({ event: 'analysis_complete', coverageType: 'endpoint' }, 'Endpoint coverage analysis complete');
|
|
271
|
+
await finaliseObservability(allResults, thresholds, metricsPort, serviceName);
|
|
272
|
+
// Threshold check
|
|
273
|
+
const failures = (0, reporting_1.checkThresholds)(allResults, thresholds);
|
|
274
|
+
if (failures.length > 0) {
|
|
275
|
+
for (const msg of failures) {
|
|
276
|
+
console.error(`THRESHOLD FAILURE: ${msg}`);
|
|
277
|
+
}
|
|
278
|
+
process.exitCode = 1;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
program
|
|
282
|
+
.command('parameter-coverage')
|
|
283
|
+
.description('Analyze how thoroughly each API parameter is tested (valid, boundary, missing, invalid)')
|
|
284
|
+
.option('--spec <path>', 'Path to the OpenAPI/Swagger spec file', 'sample/openapi-parameters.yaml')
|
|
285
|
+
.option('--tests <glob>', 'Glob pattern for test files', 'sample/tests/**/*.ts')
|
|
286
|
+
.option('--format <formats>', 'Comma-separated list of report formats: json,html,csv,junit (default: json,html)', 'json,html')
|
|
287
|
+
.option('--threshold-parameter <percent>', 'Minimum required parameter coverage percentage (0-100)', parseFloat, 0)
|
|
288
|
+
.action(async (options) => {
|
|
289
|
+
var _a, _b, _c, _d;
|
|
290
|
+
const { metricsPort, serviceName } = setupObservability();
|
|
291
|
+
const logger = (0, observability_1.getLogger)();
|
|
292
|
+
const parentOpts = program.opts();
|
|
293
|
+
const config = loadCoverageConfig(parentOpts.config, {
|
|
294
|
+
parameter: options.thresholdParameter,
|
|
295
|
+
});
|
|
296
|
+
const specPath = path.resolve(options.spec);
|
|
297
|
+
const testsGlob = (config.testPatterns && config.testPatterns.length > 0)
|
|
298
|
+
? config.testPatterns[0]
|
|
299
|
+
: options.tests;
|
|
300
|
+
const reportsDir = path.resolve('reports');
|
|
301
|
+
const formats = (0, reporting_1.parseFormats)(options.format);
|
|
302
|
+
const span = (0, observability_1.startSpan)('parameter-coverage', { specPath, testsGlob });
|
|
303
|
+
logger.info({ event: 'analysis_start', coverageType: 'parameter', specPath }, `Parsing spec: ${specPath}`);
|
|
304
|
+
console.log(`Parsing spec: ${specPath}`);
|
|
305
|
+
const parameters = await (0, parameterCoverage_1.parseParameters)(specPath);
|
|
306
|
+
console.log(`Analyzing tests matching: ${testsGlob}`);
|
|
307
|
+
const analyzerCfgForParam = (0, config_1.loadCentralConfig)(parentOpts.config);
|
|
308
|
+
const astParamOptions = {
|
|
309
|
+
astConfig: (_a = analyzerCfgForParam.analysis.ast) !== null && _a !== void 0 ? _a : {},
|
|
310
|
+
deepConfig: (_b = analyzerCfgForParam.scans.coverage) === null || _b === void 0 ? void 0 : _b.deepAnalysis,
|
|
311
|
+
};
|
|
312
|
+
const coverages = await (0, parameterCoverage_1.analyzeParameterCoverage)(parameters, testsGlob, astParamOptions);
|
|
313
|
+
const report = (0, parameterCoverage_1.buildParameterCoverageReport)(coverages);
|
|
314
|
+
(0, parameterCoverage_1.generateParameterReports)(report, reportsDir);
|
|
315
|
+
const result = {
|
|
316
|
+
type: 'parameter',
|
|
317
|
+
totalItems: report.totalParameters,
|
|
318
|
+
coveredItems: coverages.filter((c) => c.ratio > 0).length,
|
|
319
|
+
coveragePercent: report.averageCoverage,
|
|
320
|
+
details: report,
|
|
321
|
+
};
|
|
322
|
+
const thresholds = { ...((_c = config.thresholds) !== null && _c !== void 0 ? _c : {}) };
|
|
323
|
+
// Run plugins
|
|
324
|
+
const pluginContext = {
|
|
325
|
+
testPatterns: (_d = config.testPatterns) !== null && _d !== void 0 ? _d : [],
|
|
326
|
+
results: [result],
|
|
327
|
+
config,
|
|
328
|
+
};
|
|
329
|
+
const pluginResults = await (0, pluginLoader_1.runPlugins)(config, pluginContext);
|
|
330
|
+
const allResults = [result, ...pluginResults];
|
|
331
|
+
const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
|
|
332
|
+
(0, reporting_1.generateMultiFormatReports)(allResults, formats, reportsDir, thresholds, observabilityInfo);
|
|
333
|
+
console.log(`Parameter coverage: ${report.totalParameters} parameters analysed, average coverage ${report.averageCoverage}%`);
|
|
334
|
+
console.log(`Reports written to: ${reportsDir}`);
|
|
335
|
+
span.end({ totalItems: report.totalParameters, coveragePercent: report.averageCoverage });
|
|
336
|
+
logger.info({ event: 'analysis_complete', coverageType: 'parameter' }, 'Parameter coverage analysis complete');
|
|
337
|
+
await finaliseObservability(allResults, thresholds, metricsPort, serviceName);
|
|
338
|
+
const failures = (0, reporting_1.checkThresholds)(allResults, thresholds);
|
|
339
|
+
if (failures.length > 0) {
|
|
340
|
+
for (const msg of failures) {
|
|
341
|
+
console.error(`THRESHOLD FAILURE: ${msg}`);
|
|
342
|
+
}
|
|
343
|
+
process.exitCode = 1;
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
program
|
|
347
|
+
.command('business-coverage')
|
|
348
|
+
.description('Analyze how well tests cover defined business rules and scenarios')
|
|
349
|
+
.option('--rules <file>', 'Path to the business rules definition file (YAML or JSON)', 'sample/business-rules.yaml')
|
|
350
|
+
.option('--tests <glob>', 'Glob pattern for test files', 'sample/tests/**/*.ts')
|
|
351
|
+
.option('--format <formats>', 'Comma-separated list of report formats: json,html,csv,junit (default: json,html)', 'json,html')
|
|
352
|
+
.option('--threshold-business <percent>', 'Minimum required business logic coverage percentage (0-100)', parseFloat, 0)
|
|
353
|
+
.action(async (options) => {
|
|
354
|
+
var _a, _b;
|
|
355
|
+
const { metricsPort, serviceName } = setupObservability();
|
|
356
|
+
const logger = (0, observability_1.getLogger)();
|
|
357
|
+
const parentOpts = program.opts();
|
|
358
|
+
const config = loadCoverageConfig(parentOpts.config, {
|
|
359
|
+
business: options.thresholdBusiness,
|
|
360
|
+
});
|
|
361
|
+
const rulesPath = path.resolve(options.rules);
|
|
362
|
+
const testsGlob = (config.testPatterns && config.testPatterns.length > 0)
|
|
363
|
+
? config.testPatterns[0]
|
|
364
|
+
: options.tests;
|
|
365
|
+
const reportsDir = path.resolve('reports');
|
|
366
|
+
const formats = (0, reporting_1.parseFormats)(options.format);
|
|
367
|
+
const span = (0, observability_1.startSpan)('business-coverage', { rulesPath, testsGlob });
|
|
368
|
+
logger.info({ event: 'analysis_start', coverageType: 'business', rulesPath }, `Parsing business rules: ${rulesPath}`);
|
|
369
|
+
console.log(`Parsing business rules: ${rulesPath}`);
|
|
370
|
+
const rules = (0, businessCoverage_1.parseBusinessRules)(rulesPath);
|
|
371
|
+
console.log(`Analyzing tests matching: ${testsGlob}`);
|
|
372
|
+
const coverages = await (0, businessCoverage_1.analyzeBusinessCoverage)(rules, testsGlob);
|
|
373
|
+
const report = (0, businessCoverage_1.buildBusinessCoverageReport)(coverages);
|
|
374
|
+
(0, businessCoverage_1.generateBusinessReports)(report, reportsDir);
|
|
375
|
+
const result = {
|
|
376
|
+
type: 'business',
|
|
377
|
+
totalItems: report.total,
|
|
378
|
+
coveredItems: report.covered,
|
|
379
|
+
coveragePercent: report.percentage,
|
|
380
|
+
details: report,
|
|
381
|
+
};
|
|
382
|
+
const thresholds = { ...((_a = config.thresholds) !== null && _a !== void 0 ? _a : {}) };
|
|
383
|
+
// Run plugins
|
|
384
|
+
const pluginContext = {
|
|
385
|
+
testPatterns: (_b = config.testPatterns) !== null && _b !== void 0 ? _b : [],
|
|
386
|
+
results: [result],
|
|
387
|
+
config,
|
|
388
|
+
};
|
|
389
|
+
const pluginResults = await (0, pluginLoader_1.runPlugins)(config, pluginContext);
|
|
390
|
+
const allResults = [result, ...pluginResults];
|
|
391
|
+
const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
|
|
392
|
+
(0, reporting_1.generateMultiFormatReports)(allResults, formats, reportsDir, thresholds, observabilityInfo);
|
|
393
|
+
console.log(`Business coverage: ${report.covered}/${report.total} rules covered (${report.percentage}%)`);
|
|
394
|
+
if (report.uncoveredRules.length > 0) {
|
|
395
|
+
console.log('Uncovered rules:');
|
|
396
|
+
for (const rule of report.uncoveredRules) {
|
|
397
|
+
console.log(` - ${rule.id}: ${rule.description}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
console.log(`Reports written to: ${reportsDir}`);
|
|
401
|
+
span.end({ totalItems: report.total, coveredItems: report.covered, coveragePercent: report.percentage });
|
|
402
|
+
logger.info({ event: 'analysis_complete', coverageType: 'business' }, 'Business coverage analysis complete');
|
|
403
|
+
await finaliseObservability(allResults, thresholds, metricsPort, serviceName);
|
|
404
|
+
const failures = (0, reporting_1.checkThresholds)(allResults, thresholds);
|
|
405
|
+
if (failures.length > 0) {
|
|
406
|
+
for (const msg of failures) {
|
|
407
|
+
console.error(`THRESHOLD FAILURE: ${msg}`);
|
|
408
|
+
}
|
|
409
|
+
process.exitCode = 1;
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
program
|
|
413
|
+
.command('integration-coverage')
|
|
414
|
+
.description('Analyze how well integration tests exercise defined end-to-end flows')
|
|
415
|
+
.option('--flows <file>', 'Path to the integration flows definition file (YAML or JSON)', 'sample/integration-flows.yaml')
|
|
416
|
+
.option('--tests <glob>', 'Glob pattern for test files', 'sample/tests/**/*.ts')
|
|
417
|
+
.option('--format <formats>', 'Comma-separated list of report formats: json,html,csv,junit (default: json,html)', 'json,html')
|
|
418
|
+
.option('--threshold-integration <percent>', 'Minimum required integration flow coverage percentage (0-100)', parseFloat, 0)
|
|
419
|
+
.action(async (options) => {
|
|
420
|
+
var _a, _b;
|
|
421
|
+
const { metricsPort, serviceName } = setupObservability();
|
|
422
|
+
const logger = (0, observability_1.getLogger)();
|
|
423
|
+
const parentOpts = program.opts();
|
|
424
|
+
const config = loadCoverageConfig(parentOpts.config, {
|
|
425
|
+
integration: options.thresholdIntegration,
|
|
426
|
+
});
|
|
427
|
+
const flowsPath = path.resolve(options.flows);
|
|
428
|
+
const testsGlob = (config.testPatterns && config.testPatterns.length > 0)
|
|
429
|
+
? config.testPatterns[0]
|
|
430
|
+
: options.tests;
|
|
431
|
+
const reportsDir = path.resolve('reports');
|
|
432
|
+
const formats = (0, reporting_1.parseFormats)(options.format);
|
|
433
|
+
const span = (0, observability_1.startSpan)('integration-coverage', { flowsPath, testsGlob });
|
|
434
|
+
logger.info({ event: 'analysis_start', coverageType: 'integration', flowsPath }, `Parsing integration flows: ${flowsPath}`);
|
|
435
|
+
console.log(`Parsing integration flows: ${flowsPath}`);
|
|
436
|
+
const flows = (0, integrationCoverage_1.parseIntegrationFlows)(flowsPath);
|
|
437
|
+
console.log(`Analyzing tests matching: ${testsGlob}`);
|
|
438
|
+
const coverages = await (0, integrationCoverage_1.analyzeIntegrationCoverage)(flows, testsGlob);
|
|
439
|
+
const report = (0, integrationCoverage_1.buildIntegrationCoverageReport)(coverages);
|
|
440
|
+
(0, integrationCoverage_1.generateIntegrationReports)(report, reportsDir);
|
|
441
|
+
const result = {
|
|
442
|
+
type: 'integration',
|
|
443
|
+
totalItems: report.total,
|
|
444
|
+
coveredItems: report.complete,
|
|
445
|
+
coveragePercent: report.percentage,
|
|
446
|
+
details: report,
|
|
447
|
+
};
|
|
448
|
+
const thresholds = { ...((_a = config.thresholds) !== null && _a !== void 0 ? _a : {}) };
|
|
449
|
+
// Run plugins
|
|
450
|
+
const pluginContext = {
|
|
451
|
+
testPatterns: (_b = config.testPatterns) !== null && _b !== void 0 ? _b : [],
|
|
452
|
+
results: [result],
|
|
453
|
+
config,
|
|
454
|
+
};
|
|
455
|
+
const pluginResults = await (0, pluginLoader_1.runPlugins)(config, pluginContext);
|
|
456
|
+
const allResults = [result, ...pluginResults];
|
|
457
|
+
const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
|
|
458
|
+
(0, reporting_1.generateMultiFormatReports)(allResults, formats, reportsDir, thresholds, observabilityInfo);
|
|
459
|
+
console.log(`Integration coverage: ${report.complete}/${report.total} flows complete, ${report.partial} partial, ${report.missing} missing (${report.percentage}%)`);
|
|
460
|
+
const partialFlows = coverages.filter((c) => c.status === 'partial');
|
|
461
|
+
if (partialFlows.length > 0) {
|
|
462
|
+
console.log('Partially covered flows:');
|
|
463
|
+
for (const fc of partialFlows) {
|
|
464
|
+
const uncoveredSteps = fc.steps.filter((s) => !s.covered).map((s) => s.step.id);
|
|
465
|
+
console.log(` - ${fc.flow.id}: ${fc.flow.name} (missing steps: ${uncoveredSteps.join(', ')})`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const missingFlows = coverages.filter((c) => c.status === 'missing');
|
|
469
|
+
if (missingFlows.length > 0) {
|
|
470
|
+
console.log('Missing flows:');
|
|
471
|
+
for (const fc of missingFlows) {
|
|
472
|
+
console.log(` - ${fc.flow.id}: ${fc.flow.name}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
console.log(`Reports written to: ${reportsDir}`);
|
|
476
|
+
span.end({ totalItems: report.total, coveredItems: report.complete, coveragePercent: report.percentage });
|
|
477
|
+
logger.info({ event: 'analysis_complete', coverageType: 'integration' }, 'Integration coverage analysis complete');
|
|
478
|
+
await finaliseObservability(allResults, thresholds, metricsPort, serviceName);
|
|
479
|
+
const failures = (0, reporting_1.checkThresholds)(allResults, thresholds);
|
|
480
|
+
if (failures.length > 0) {
|
|
481
|
+
for (const msg of failures) {
|
|
482
|
+
console.error(`THRESHOLD FAILURE: ${msg}`);
|
|
483
|
+
}
|
|
484
|
+
process.exitCode = 1;
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
program
|
|
488
|
+
.command('error-coverage')
|
|
489
|
+
.description('Analyze how thoroughly tests cover error handling and negative scenarios defined in the API spec')
|
|
490
|
+
.option('--spec <path>', 'Path to the OpenAPI/Swagger spec file', 'sample/openapi-errors.yaml')
|
|
491
|
+
.option('--tests <glob>', 'Glob pattern for test files', 'sample/tests/**/*.ts')
|
|
492
|
+
.option('--format <formats>', 'Comma-separated list of report formats: json,html,csv,junit (default: json,html)', 'json,html')
|
|
493
|
+
.option('--threshold-error <percent>', 'Minimum required error handling coverage percentage (0-100)', parseFloat, 0)
|
|
494
|
+
.action(async (options) => {
|
|
495
|
+
var _a, _b, _c, _d;
|
|
496
|
+
const { metricsPort, serviceName } = setupObservability();
|
|
497
|
+
const logger = (0, observability_1.getLogger)();
|
|
498
|
+
const parentOpts = program.opts();
|
|
499
|
+
const config = loadCoverageConfig(parentOpts.config, {
|
|
500
|
+
error: options.thresholdError,
|
|
501
|
+
});
|
|
502
|
+
const specPath = path.resolve(options.spec);
|
|
503
|
+
const testsGlob = (config.testPatterns && config.testPatterns.length > 0)
|
|
504
|
+
? config.testPatterns[0]
|
|
505
|
+
: options.tests;
|
|
506
|
+
const reportsDir = path.resolve('reports');
|
|
507
|
+
const formats = (0, reporting_1.parseFormats)(options.format);
|
|
508
|
+
const span = (0, observability_1.startSpan)('error-coverage', { specPath, testsGlob });
|
|
509
|
+
logger.info({ event: 'analysis_start', coverageType: 'error', specPath }, `Parsing spec: ${specPath}`);
|
|
510
|
+
console.log(`Parsing spec: ${specPath}`);
|
|
511
|
+
const scenarios = await (0, errorCoverage_1.parseErrorScenarios)(specPath);
|
|
512
|
+
console.log(`Found ${scenarios.length} error scenarios`);
|
|
513
|
+
console.log(`Analyzing tests matching: ${testsGlob}`);
|
|
514
|
+
const analyzerCfgForError = (0, config_1.loadCentralConfig)(parentOpts.config);
|
|
515
|
+
const astErrorOptions = {
|
|
516
|
+
astConfig: (_a = analyzerCfgForError.analysis.ast) !== null && _a !== void 0 ? _a : {},
|
|
517
|
+
deepConfig: (_b = analyzerCfgForError.scans.coverage) === null || _b === void 0 ? void 0 : _b.deepAnalysis,
|
|
518
|
+
};
|
|
519
|
+
const coverages = await (0, errorCoverage_1.analyzeErrorCoverage)(scenarios, testsGlob, astErrorOptions);
|
|
520
|
+
const report = (0, errorCoverage_1.buildErrorCoverageReport)(coverages);
|
|
521
|
+
(0, errorCoverage_1.generateErrorReports)(report, reportsDir);
|
|
522
|
+
const result = {
|
|
523
|
+
type: 'error',
|
|
524
|
+
totalItems: report.total,
|
|
525
|
+
coveredItems: report.covered,
|
|
526
|
+
coveragePercent: report.percentage,
|
|
527
|
+
details: report,
|
|
528
|
+
};
|
|
529
|
+
const thresholds = { ...((_c = config.thresholds) !== null && _c !== void 0 ? _c : {}) };
|
|
530
|
+
// Run plugins
|
|
531
|
+
const pluginContext = {
|
|
532
|
+
testPatterns: (_d = config.testPatterns) !== null && _d !== void 0 ? _d : [],
|
|
533
|
+
results: [result],
|
|
534
|
+
config,
|
|
535
|
+
};
|
|
536
|
+
const pluginResults = await (0, pluginLoader_1.runPlugins)(config, pluginContext);
|
|
537
|
+
const allResults = [result, ...pluginResults];
|
|
538
|
+
const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
|
|
539
|
+
(0, reporting_1.generateMultiFormatReports)(allResults, formats, reportsDir, thresholds, observabilityInfo);
|
|
540
|
+
console.log(`Error coverage: ${report.covered}/${report.total} error scenarios covered (${report.percentage}%)`);
|
|
541
|
+
// Print category summary
|
|
542
|
+
for (const [cat, summary] of Object.entries(report.categorySummary)) {
|
|
543
|
+
if (summary.total > 0) {
|
|
544
|
+
const pct = Math.round((summary.covered / summary.total) * 100);
|
|
545
|
+
console.log(` ${cat}: ${summary.covered}/${summary.total} (${pct}%)`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const uncovered = coverages.filter((c) => !c.covered);
|
|
549
|
+
if (uncovered.length > 0) {
|
|
550
|
+
console.log('Uncovered error scenarios:');
|
|
551
|
+
for (const c of uncovered) {
|
|
552
|
+
console.log(` - ${c.scenario.id}: ${c.scenario.description}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
console.log(`Reports written to: ${reportsDir}`);
|
|
556
|
+
span.end({ totalItems: report.total, coveredItems: report.covered, coveragePercent: report.percentage });
|
|
557
|
+
logger.info({ event: 'analysis_complete', coverageType: 'error' }, 'Error coverage analysis complete');
|
|
558
|
+
await finaliseObservability(allResults, thresholds, metricsPort, serviceName);
|
|
559
|
+
// Threshold check
|
|
560
|
+
const failures = (0, reporting_1.checkThresholds)(allResults, thresholds);
|
|
561
|
+
if (failures.length > 0) {
|
|
562
|
+
for (const msg of failures) {
|
|
563
|
+
console.error(`THRESHOLD FAILURE: ${msg}`);
|
|
564
|
+
}
|
|
565
|
+
process.exitCode = 1;
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
program
|
|
569
|
+
.command('security-coverage')
|
|
570
|
+
.description('Analyze how comprehensively tests cover security controls defined in the API spec')
|
|
571
|
+
.option('--spec <path>', 'Path to the OpenAPI/Swagger spec file', 'sample/openapi-security.yaml')
|
|
572
|
+
.option('--tests <glob>', 'Glob pattern for test files', 'sample/tests/**/*.ts')
|
|
573
|
+
.option('--scan-report <file>', 'Path to an external security scanner report (ZAP JSON/XML or generic JSON) for enrichment')
|
|
574
|
+
.option('--format <formats>', 'Comma-separated list of report formats: json,html,csv,junit (default: json,html)', 'json,html')
|
|
575
|
+
.option('--threshold-security <percent>', 'Minimum required security coverage percentage (0-100)', parseFloat, 0)
|
|
576
|
+
.action(async (options) => {
|
|
577
|
+
var _a, _b, _c, _d;
|
|
578
|
+
const { metricsPort, serviceName } = setupObservability();
|
|
579
|
+
const logger = (0, observability_1.getLogger)();
|
|
580
|
+
const parentOpts = program.opts();
|
|
581
|
+
const config = loadCoverageConfig(parentOpts.config, {
|
|
582
|
+
security: options.thresholdSecurity,
|
|
583
|
+
});
|
|
584
|
+
const specPath = path.resolve(options.spec);
|
|
585
|
+
const testsGlob = (config.testPatterns && config.testPatterns.length > 0)
|
|
586
|
+
? config.testPatterns[0]
|
|
587
|
+
: options.tests;
|
|
588
|
+
const scanReportPath = options.scanReport ? path.resolve(options.scanReport) : undefined;
|
|
589
|
+
const reportsDir = path.resolve('reports');
|
|
590
|
+
const formats = (0, reporting_1.parseFormats)(options.format);
|
|
591
|
+
const span = (0, observability_1.startSpan)('security-coverage', { specPath, testsGlob });
|
|
592
|
+
logger.info({ event: 'analysis_start', coverageType: 'security', specPath }, `Parsing spec: ${specPath}`);
|
|
593
|
+
console.log(`Parsing spec: ${specPath}`);
|
|
594
|
+
const controls = await (0, securityCoverage_1.parseSecurityControls)(specPath);
|
|
595
|
+
console.log(`Found ${controls.length} security controls`);
|
|
596
|
+
console.log(`Analyzing tests matching: ${testsGlob}`);
|
|
597
|
+
const analyzerCfgForSec = (0, config_1.loadCentralConfig)(parentOpts.config);
|
|
598
|
+
const astSecOptions = {
|
|
599
|
+
astConfig: (_a = analyzerCfgForSec.analysis.ast) !== null && _a !== void 0 ? _a : {},
|
|
600
|
+
deepConfig: (_b = analyzerCfgForSec.scans.coverage) === null || _b === void 0 ? void 0 : _b.deepAnalysis,
|
|
601
|
+
};
|
|
602
|
+
const coverages = await (0, securityCoverage_1.analyzeSecurityCoverage)(controls, testsGlob, scanReportPath, astSecOptions);
|
|
603
|
+
const report = (0, securityCoverage_1.buildSecurityCoverageReport)(coverages, scanReportPath ? coverages.filter((c) => c.coveredByScanReport).length : 0);
|
|
604
|
+
(0, securityCoverage_1.generateSecurityReports)(report, reportsDir);
|
|
605
|
+
const result = {
|
|
606
|
+
type: 'security',
|
|
607
|
+
totalItems: report.total,
|
|
608
|
+
coveredItems: report.covered,
|
|
609
|
+
coveragePercent: report.percentage,
|
|
610
|
+
details: report,
|
|
611
|
+
};
|
|
612
|
+
const thresholds = { ...((_c = config.thresholds) !== null && _c !== void 0 ? _c : {}) };
|
|
613
|
+
// Run plugins
|
|
614
|
+
const pluginContext = {
|
|
615
|
+
testPatterns: (_d = config.testPatterns) !== null && _d !== void 0 ? _d : [],
|
|
616
|
+
results: [result],
|
|
617
|
+
config,
|
|
618
|
+
};
|
|
619
|
+
const pluginResults = await (0, pluginLoader_1.runPlugins)(config, pluginContext);
|
|
620
|
+
const allResults = [result, ...pluginResults];
|
|
621
|
+
const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
|
|
622
|
+
(0, reporting_1.generateMultiFormatReports)(allResults, formats, reportsDir, thresholds, observabilityInfo);
|
|
623
|
+
console.log(`Security coverage: ${report.covered}/${report.total} controls covered (${report.percentage}%)`);
|
|
624
|
+
// Print per-category summary
|
|
625
|
+
for (const [cat, summary] of Object.entries(report.categorySummary)) {
|
|
626
|
+
if (summary.total > 0) {
|
|
627
|
+
const pct = Math.round((summary.covered / summary.total) * 100);
|
|
628
|
+
console.log(` ${cat}: ${summary.covered}/${summary.total} (${pct}%)`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (report.scanFindings > 0) {
|
|
632
|
+
console.log(` External scan findings credited: ${report.scanFindings}`);
|
|
633
|
+
}
|
|
634
|
+
const uncovered = coverages.filter((c) => !c.covered);
|
|
635
|
+
if (uncovered.length > 0) {
|
|
636
|
+
console.log('Uncovered security controls:');
|
|
637
|
+
for (const c of uncovered) {
|
|
638
|
+
console.log(` - ${c.control.id}: ${c.control.description}`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
console.log(`Reports written to: ${reportsDir}`);
|
|
642
|
+
span.end({ totalItems: report.total, coveredItems: report.covered, coveragePercent: report.percentage });
|
|
643
|
+
logger.info({ event: 'analysis_complete', coverageType: 'security' }, 'Security coverage analysis complete');
|
|
644
|
+
await finaliseObservability(allResults, thresholds, metricsPort, serviceName);
|
|
645
|
+
const failures = (0, reporting_1.checkThresholds)(allResults, thresholds);
|
|
646
|
+
if (failures.length > 0) {
|
|
647
|
+
for (const msg of failures) {
|
|
648
|
+
console.error(`THRESHOLD FAILURE: ${msg}`);
|
|
649
|
+
}
|
|
650
|
+
process.exitCode = 1;
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
program
|
|
654
|
+
.command('perf-resilience-coverage')
|
|
655
|
+
.description('Analyze how well tests cover performance under load and resilience to failure conditions')
|
|
656
|
+
.option('--spec <path>', 'Path to the OpenAPI/Swagger spec file (used to enumerate endpoints)', 'sample/openapi.yaml')
|
|
657
|
+
.option('--tests <glob>', 'Glob pattern for test files', 'sample/tests/**/*.ts')
|
|
658
|
+
.option('--load-results <paths>', 'Comma-separated paths to load-test result files (JMeter .jtl/.csv or k6 .json)')
|
|
659
|
+
.option('--threshold-response-ms <ms>', 'Maximum acceptable median response time in milliseconds', parseFloat, 500)
|
|
660
|
+
.option('--threshold-error-rate <rate>', 'Maximum acceptable error rate as a decimal fraction (0–1, e.g. 0.05 = 5%)', parseFloat, 0.05)
|
|
661
|
+
.option('--format <formats>', 'Comma-separated list of report formats: json,html,csv,junit (default: json,html)', 'json,html')
|
|
662
|
+
.option('--threshold-performance <percent>', 'Minimum required performance coverage percentage (0-100)', parseFloat, 0)
|
|
663
|
+
.option('--threshold-resilience <percent>', 'Minimum required resilience coverage percentage (0-100)', parseFloat, 0)
|
|
664
|
+
.action(async (options) => {
|
|
665
|
+
var _a, _b, _c, _d, _e;
|
|
666
|
+
const { metricsPort, serviceName } = setupObservability();
|
|
667
|
+
const logger = (0, observability_1.getLogger)();
|
|
668
|
+
const parentOpts = program.opts();
|
|
669
|
+
const config = loadCoverageConfig(parentOpts.config, {
|
|
670
|
+
performance: options.thresholdPerformance,
|
|
671
|
+
resilience: options.thresholdResilience,
|
|
672
|
+
});
|
|
673
|
+
const specPath = path.resolve(options.spec);
|
|
674
|
+
const testsGlob = (config.testPatterns && config.testPatterns.length > 0)
|
|
675
|
+
? config.testPatterns[0]
|
|
676
|
+
: options.tests;
|
|
677
|
+
const reportsDir = path.resolve('reports');
|
|
678
|
+
const formats = (0, reporting_1.parseFormats)(options.format);
|
|
679
|
+
const thresholdResponseMs = options.thresholdResponseMs;
|
|
680
|
+
const thresholdErrorRate = options.thresholdErrorRate;
|
|
681
|
+
const perfThresholds = {
|
|
682
|
+
responseMs: thresholdResponseMs,
|
|
683
|
+
errorRate: thresholdErrorRate,
|
|
684
|
+
};
|
|
685
|
+
const span = (0, observability_1.startSpan)('perf-resilience-coverage', { specPath, testsGlob });
|
|
686
|
+
logger.info({ event: 'analysis_start', coverageType: 'perf-resilience', specPath }, `Parsing spec: ${specPath}`);
|
|
687
|
+
console.log(`Parsing spec: ${specPath}`);
|
|
688
|
+
const endpoints = await (0, perfResilienceCoverage_1.parseEndpointsFromSpec)(specPath);
|
|
689
|
+
console.log(`Found ${endpoints.length} endpoints`);
|
|
690
|
+
// Load-test results
|
|
691
|
+
const loadResultPaths = options.loadResults
|
|
692
|
+
? options.loadResults.split(',').map((p) => path.resolve(p.trim()))
|
|
693
|
+
: [];
|
|
694
|
+
const metricsMap = loadResultPaths.length > 0
|
|
695
|
+
? (0, perfResilienceCoverage_1.parseLoadTestResults)(loadResultPaths)
|
|
696
|
+
: new Map();
|
|
697
|
+
if (loadResultPaths.length > 0) {
|
|
698
|
+
console.log(`Loaded ${metricsMap.size} metric entries from ${loadResultPaths.length} file(s)`);
|
|
699
|
+
}
|
|
700
|
+
// Performance coverage
|
|
701
|
+
const performanceCoverages = (0, perfResilienceCoverage_1.analyzePerformanceCoverage)(endpoints, metricsMap, perfThresholds);
|
|
702
|
+
// Resilience scenarios and coverage
|
|
703
|
+
const scenarios = (0, perfResilienceCoverage_1.buildResilienceScenarios)(endpoints);
|
|
704
|
+
console.log(`Analyzing resilience tests matching: ${testsGlob}`);
|
|
705
|
+
const resilienceCoverages = await (0, perfResilienceCoverage_1.analyzeResilienceCoverage)(scenarios, testsGlob);
|
|
706
|
+
// Build report
|
|
707
|
+
const report = (0, perfResilienceCoverage_1.buildPerfResilienceReport)(performanceCoverages, resilienceCoverages);
|
|
708
|
+
// Write dedicated JSON + HTML reports
|
|
709
|
+
(0, perfResilienceCoverage_1.generatePerfResilienceReports)(report, reportsDir);
|
|
710
|
+
// Build standardised results for multi-format output
|
|
711
|
+
const perfResult = {
|
|
712
|
+
type: 'performance',
|
|
713
|
+
totalItems: report.totalEndpoints,
|
|
714
|
+
coveredItems: report.endpointsWithLoadData,
|
|
715
|
+
coveragePercent: report.performanceCoveragePercent,
|
|
716
|
+
details: report,
|
|
717
|
+
};
|
|
718
|
+
const resilienceResult = {
|
|
719
|
+
type: 'resilience',
|
|
720
|
+
totalItems: report.totalResilienceScenarios,
|
|
721
|
+
coveredItems: report.coveredResilienceScenarios,
|
|
722
|
+
coveragePercent: report.resilienceCoveragePercent,
|
|
723
|
+
details: report,
|
|
724
|
+
};
|
|
725
|
+
const thresholds = { ...((_a = config.thresholds) !== null && _a !== void 0 ? _a : {}) };
|
|
726
|
+
// Run plugins
|
|
727
|
+
const pluginContext = {
|
|
728
|
+
testPatterns: (_b = config.testPatterns) !== null && _b !== void 0 ? _b : [],
|
|
729
|
+
results: [perfResult, resilienceResult],
|
|
730
|
+
config,
|
|
731
|
+
};
|
|
732
|
+
const pluginResults = await (0, pluginLoader_1.runPlugins)(config, pluginContext);
|
|
733
|
+
const allResults = [perfResult, resilienceResult, ...pluginResults];
|
|
734
|
+
const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
|
|
735
|
+
(0, reporting_1.generateMultiFormatReports)(allResults, formats, reportsDir, thresholds, observabilityInfo);
|
|
736
|
+
console.log(`Performance coverage: ${report.endpointsWithLoadData}/${report.totalEndpoints} endpoints with load-test data (${report.performanceCoveragePercent}%)`);
|
|
737
|
+
console.log(`Resilience coverage: ${report.coveredResilienceScenarios}/${report.totalResilienceScenarios} scenarios covered (${report.resilienceCoveragePercent}%)`);
|
|
738
|
+
// Per-category resilience summary
|
|
739
|
+
for (const [cat, summary] of Object.entries(report.resilienceCategorySummary)) {
|
|
740
|
+
if (summary.total > 0) {
|
|
741
|
+
const pct = Math.round((summary.covered / summary.total) * 100);
|
|
742
|
+
console.log(` ${cat}: ${summary.covered}/${summary.total} (${pct}%)`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
// Endpoints missing load-test data
|
|
746
|
+
const missingPerf = report.performanceCoverages.filter((c) => !c.hasLoadTestData);
|
|
747
|
+
if (missingPerf.length > 0) {
|
|
748
|
+
console.log('Endpoints missing load-test data:');
|
|
749
|
+
for (const c of missingPerf) {
|
|
750
|
+
console.log(` - ${c.endpoint.id}`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// Endpoints needing improvement
|
|
754
|
+
const needsImprovement = report.performanceCoverages.filter((c) => c.status === 'needs-improvement');
|
|
755
|
+
if (needsImprovement.length > 0) {
|
|
756
|
+
console.log('Endpoints not meeting performance thresholds:');
|
|
757
|
+
for (const c of needsImprovement) {
|
|
758
|
+
console.log(` - ${c.endpoint.id}: median=${(_c = c.metrics) === null || _c === void 0 ? void 0 : _c.median}ms (threshold: ${thresholdResponseMs}ms), errorRate=${(((_e = (_d = c.metrics) === null || _d === void 0 ? void 0 : _d.errorRate) !== null && _e !== void 0 ? _e : 0) * 100).toFixed(2)}% (threshold: ${(thresholdErrorRate * 100).toFixed(2)}%)`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
console.log(`Reports written to: ${reportsDir}`);
|
|
762
|
+
span.end({ coveragePercent: report.performanceCoveragePercent });
|
|
763
|
+
logger.info({ event: 'analysis_complete', coverageType: 'perf-resilience' }, 'Perf/resilience coverage analysis complete');
|
|
764
|
+
await finaliseObservability(allResults, thresholds, metricsPort, serviceName);
|
|
765
|
+
// Threshold check
|
|
766
|
+
const failures = (0, reporting_1.checkThresholds)(allResults, thresholds);
|
|
767
|
+
if (failures.length > 0) {
|
|
768
|
+
for (const msg of failures) {
|
|
769
|
+
console.error(`THRESHOLD FAILURE: ${msg}`);
|
|
770
|
+
}
|
|
771
|
+
process.exitCode = 1;
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
program
|
|
775
|
+
.command('compatibility-check')
|
|
776
|
+
.description('Compare two API spec versions for breaking/non-breaking changes and verify consumer-driven contracts against the new spec')
|
|
777
|
+
.option('--old-spec <path>', 'Path to the previous (published) OpenAPI/Swagger spec')
|
|
778
|
+
.option('--new-spec <path>', 'Path to the current spec to be published')
|
|
779
|
+
.option('--contracts <glob>', 'Glob pattern or directory for consumer contract files (e.g. Pact JSON files)')
|
|
780
|
+
.option('--threshold-compat <percent>', 'Minimum required compatibility percentage (0-100). Exits non-zero if not met.', parseFloat, 0)
|
|
781
|
+
.action(async (options) => {
|
|
782
|
+
const { metricsPort, serviceName } = setupObservability();
|
|
783
|
+
const logger = (0, observability_1.getLogger)();
|
|
784
|
+
const oldSpecPath = options.oldSpec ? path.resolve(options.oldSpec) : undefined;
|
|
785
|
+
const newSpecPath = options.newSpec ? path.resolve(options.newSpec) : undefined;
|
|
786
|
+
const contractsGlob = options.contracts;
|
|
787
|
+
const thresholdCompat = options.thresholdCompat;
|
|
788
|
+
const reportsDir = path.resolve('reports');
|
|
789
|
+
if (!oldSpecPath || !newSpecPath) {
|
|
790
|
+
console.error('ERROR: --old-spec and --new-spec are required.');
|
|
791
|
+
process.exitCode = 1;
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const span = (0, observability_1.startSpan)('compatibility-check', { oldSpecPath, newSpecPath });
|
|
795
|
+
logger.info({ event: 'analysis_start', coverageType: 'compatibility', oldSpecPath, newSpecPath }, `Loading old spec: ${oldSpecPath}`);
|
|
796
|
+
console.log(`Loading old spec: ${oldSpecPath}`);
|
|
797
|
+
const oldApi = await (0, compatibilityCoverage_1.loadSpec)(oldSpecPath);
|
|
798
|
+
console.log(`Loading new spec: ${newSpecPath}`);
|
|
799
|
+
const newApi = await (0, compatibilityCoverage_1.loadSpec)(newSpecPath);
|
|
800
|
+
// Compare specs
|
|
801
|
+
const changes = (0, compatibilityCoverage_1.compareSpecs)(oldApi, newApi);
|
|
802
|
+
const breakingChanges = changes.filter((c) => c.breaking);
|
|
803
|
+
const nonBreakingChanges = changes.filter((c) => !c.breaking);
|
|
804
|
+
// Load and verify contracts
|
|
805
|
+
const contracts = contractsGlob ? await (0, compatibilityCoverage_1.parseContractFiles)(contractsGlob) : [];
|
|
806
|
+
if (contracts.length > 0) {
|
|
807
|
+
console.log(`Loaded ${contracts.length} consumer contract(s)`);
|
|
808
|
+
}
|
|
809
|
+
const verificationResults = (0, compatibilityCoverage_1.verifyContracts)(contracts, newApi);
|
|
810
|
+
// Build and write reports
|
|
811
|
+
const report = (0, compatibilityCoverage_1.buildCompatibilityReport)(oldApi, newApi, changes, verificationResults, oldSpecPath, newSpecPath);
|
|
812
|
+
(0, compatibilityCoverage_1.generateCompatibilityReports)(report, reportsDir);
|
|
813
|
+
// Also write multi-format summary reports via the shared reporting module
|
|
814
|
+
const formats = (0, reporting_1.parseFormats)('json,html');
|
|
815
|
+
const uniqueAffectedEndpoints = new Set(breakingChanges.filter((c) => c.changeType !== 'added').map((c) => `${c.method}:${c.path}`)).size;
|
|
816
|
+
const endpointsUnaffectedByBreakingChanges = report.totalOldEndpoints - uniqueAffectedEndpoints;
|
|
817
|
+
const compatResult = {
|
|
818
|
+
type: 'compatibility',
|
|
819
|
+
totalItems: report.totalOldEndpoints,
|
|
820
|
+
coveredItems: endpointsUnaffectedByBreakingChanges,
|
|
821
|
+
coveragePercent: report.compatibilityPercent,
|
|
822
|
+
details: report,
|
|
823
|
+
};
|
|
824
|
+
const contractResult = {
|
|
825
|
+
type: 'contract-coverage',
|
|
826
|
+
totalItems: report.totalNewEndpoints,
|
|
827
|
+
coveredItems: report.contractCoveredEndpoints,
|
|
828
|
+
coveragePercent: report.contractCoveragePercent,
|
|
829
|
+
details: report,
|
|
830
|
+
};
|
|
831
|
+
const thresholds = {};
|
|
832
|
+
if (thresholdCompat > 0) {
|
|
833
|
+
thresholds['compatibility'] = thresholdCompat;
|
|
834
|
+
thresholds['contract-coverage'] = thresholdCompat;
|
|
835
|
+
}
|
|
836
|
+
const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
|
|
837
|
+
(0, reporting_1.generateMultiFormatReports)([compatResult, contractResult], formats, reportsDir, thresholds, observabilityInfo);
|
|
838
|
+
// Console summary
|
|
839
|
+
console.log(`\nCompatibility: ${report.compatibilityPercent}% (${breakingChanges.length} breaking change${breakingChanges.length !== 1 ? 's' : ''}, ${nonBreakingChanges.length} non-breaking)`);
|
|
840
|
+
if (breakingChanges.length > 0) {
|
|
841
|
+
console.log('Breaking changes:');
|
|
842
|
+
for (const c of breakingChanges) {
|
|
843
|
+
console.log(` ❌ ${c.description}`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (nonBreakingChanges.length > 0) {
|
|
847
|
+
console.log('Non-breaking changes:');
|
|
848
|
+
for (const c of nonBreakingChanges) {
|
|
849
|
+
console.log(` ✅ ${c.description}`);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (verificationResults.length > 0) {
|
|
853
|
+
const passedContracts = verificationResults.filter((r) => r.passed).length;
|
|
854
|
+
console.log(`\nContract verification: ${passedContracts}/${verificationResults.length} contracts passed`);
|
|
855
|
+
console.log(`Contract coverage: ${report.contractCoveredEndpoints}/${report.totalNewEndpoints} endpoints covered (${report.contractCoveragePercent}%)`);
|
|
856
|
+
for (const result of verificationResults) {
|
|
857
|
+
const failedInteractions = result.interactionResults.filter((ir) => !ir.passed);
|
|
858
|
+
if (failedInteractions.length > 0) {
|
|
859
|
+
console.log(` Contract [${result.contract.consumer} → ${result.contract.provider}] failed:`);
|
|
860
|
+
for (const ir of failedInteractions) {
|
|
861
|
+
console.log(` ❌ ${ir.interaction.description}: ${ir.reason}`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
console.log(`\nReports written to: ${reportsDir}`);
|
|
867
|
+
span.end({ coveragePercent: report.compatibilityPercent });
|
|
868
|
+
logger.info({ event: 'analysis_complete', coverageType: 'compatibility' }, 'Compatibility check complete');
|
|
869
|
+
await finaliseObservability([compatResult, contractResult], thresholds, metricsPort, serviceName);
|
|
870
|
+
// Threshold enforcement
|
|
871
|
+
const failures = (0, reporting_1.checkThresholds)([compatResult, contractResult], thresholds);
|
|
872
|
+
if (failures.length > 0) {
|
|
873
|
+
for (const msg of failures) {
|
|
874
|
+
console.error(`THRESHOLD FAILURE: ${msg}`);
|
|
875
|
+
}
|
|
876
|
+
process.exitCode = 1;
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
program
|
|
880
|
+
.command('security-scan')
|
|
881
|
+
.description('Run integrated security scanners (Semgrep, Trivy, ZAP) and evaluate a security gate')
|
|
882
|
+
.option('--workspace <path>', 'Root directory to scan (default: current working directory)', '.')
|
|
883
|
+
.option('--semgrep', 'Enable Semgrep SAST scanning (requires semgrep binary or --semgrep-report)')
|
|
884
|
+
.option('--semgrep-config <config>', 'Semgrep config/ruleset (e.g. p/default, p/security-audit)', 'p/default')
|
|
885
|
+
.option('--semgrep-report <file>', 'Import pre-generated Semgrep JSON report instead of running binary')
|
|
886
|
+
.option('--trivy', 'Enable Trivy vulnerability/secret/misconfig scanning (requires trivy binary or --trivy-report)')
|
|
887
|
+
.option('--trivy-scanners <list>', 'Comma-separated Trivy scanners: vuln,secret,misconfig', 'vuln,secret')
|
|
888
|
+
.option('--trivy-report <file>', 'Import pre-generated Trivy JSON report instead of running binary')
|
|
889
|
+
.option('--zap-report <file>', 'Import pre-generated ZAP JSON report (enables ZAP findings)')
|
|
890
|
+
.option('--fail-on-critical', 'Fail the gate if any CRITICAL finding exists')
|
|
891
|
+
.option('--fail-on-high', 'Fail the gate if any HIGH finding exists')
|
|
892
|
+
.option('--max-medium <n>', 'Maximum allowed MEDIUM findings', parseInt)
|
|
893
|
+
.option('--max-secrets <n>', 'Maximum allowed secrets (any severity)', parseInt)
|
|
894
|
+
.option('--max-misconfig-high <n>', 'Maximum allowed HIGH/CRITICAL misconfigurations', parseInt)
|
|
895
|
+
.option('--max-critical-vulns <n>', 'Maximum allowed CRITICAL vulnerabilities', parseInt)
|
|
896
|
+
.option('--max-high-vulns <n>', 'Maximum allowed HIGH vulnerabilities', parseInt)
|
|
897
|
+
.action(async (options) => {
|
|
898
|
+
var _a, _b;
|
|
899
|
+
const { metricsPort, serviceName } = setupObservability();
|
|
900
|
+
const logger = (0, observability_1.getLogger)();
|
|
901
|
+
const workspace = path.resolve((_a = options.workspace) !== null && _a !== void 0 ? _a : '.');
|
|
902
|
+
const reportsDir = path.resolve('reports');
|
|
903
|
+
// Build scanner configuration from CLI flags
|
|
904
|
+
const scanConfig = {
|
|
905
|
+
enabled: true,
|
|
906
|
+
workspace,
|
|
907
|
+
scanners: {},
|
|
908
|
+
gate: {},
|
|
909
|
+
};
|
|
910
|
+
// Semgrep
|
|
911
|
+
if (options.semgrep || options.semgrepReport) {
|
|
912
|
+
const mode = options.semgrepReport ? 'import' : 'embedded';
|
|
913
|
+
scanConfig.scanners.semgrep = {
|
|
914
|
+
enabled: true,
|
|
915
|
+
mode,
|
|
916
|
+
config: options.semgrepConfig,
|
|
917
|
+
reportPath: options.semgrepReport,
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
// Trivy
|
|
921
|
+
if (options.trivy || options.trivyReport) {
|
|
922
|
+
const mode = options.trivyReport ? 'import' : 'embedded';
|
|
923
|
+
const trivyScanners = ((_b = options.trivyScanners) !== null && _b !== void 0 ? _b : 'vuln,secret')
|
|
924
|
+
.split(',')
|
|
925
|
+
.map((s) => s.trim())
|
|
926
|
+
.filter((s) => ['vuln', 'misconfig', 'secret'].includes(s));
|
|
927
|
+
scanConfig.scanners.trivy = {
|
|
928
|
+
enabled: true,
|
|
929
|
+
mode,
|
|
930
|
+
scanners: trivyScanners,
|
|
931
|
+
reportPath: options.trivyReport,
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
// ZAP
|
|
935
|
+
if (options.zapReport) {
|
|
936
|
+
scanConfig.scanners.zap = {
|
|
937
|
+
enabled: true,
|
|
938
|
+
mode: 'import',
|
|
939
|
+
reportPath: options.zapReport,
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
// Gate configuration
|
|
943
|
+
if (options.failOnCritical)
|
|
944
|
+
scanConfig.gate.failOnCritical = true;
|
|
945
|
+
if (options.failOnHigh)
|
|
946
|
+
scanConfig.gate.failOnHigh = true;
|
|
947
|
+
if (options.maxMedium !== undefined)
|
|
948
|
+
scanConfig.gate.maxMedium = options.maxMedium;
|
|
949
|
+
if (options.maxSecrets !== undefined)
|
|
950
|
+
scanConfig.gate.maxSecrets = options.maxSecrets;
|
|
951
|
+
if (options.maxMisconfigHigh !== undefined)
|
|
952
|
+
scanConfig.gate.maxMisconfigHigh = options.maxMisconfigHigh;
|
|
953
|
+
if (options.maxCriticalVulns !== undefined)
|
|
954
|
+
scanConfig.gate.maxCriticalVulns = options.maxCriticalVulns;
|
|
955
|
+
if (options.maxHighVulns !== undefined)
|
|
956
|
+
scanConfig.gate.maxHighVulns = options.maxHighVulns;
|
|
957
|
+
// Remove empty gate/scanners objects if nothing was configured
|
|
958
|
+
if (Object.keys(scanConfig.gate).length === 0)
|
|
959
|
+
delete scanConfig.gate;
|
|
960
|
+
if (Object.keys(scanConfig.scanners).length === 0)
|
|
961
|
+
delete scanConfig.scanners;
|
|
962
|
+
const span = (0, observability_1.startSpan)('security-scan', { workspace });
|
|
963
|
+
logger.info({ event: 'analysis_start', coverageType: 'security-scan', workspace }, 'Starting security scan');
|
|
964
|
+
console.log(`Running security scan in workspace: ${workspace}`);
|
|
965
|
+
const summary = await (0, index_2.runSecurityScan)(scanConfig, reportsDir);
|
|
966
|
+
// Console summary
|
|
967
|
+
console.log(`\nSecurity Scan Results:`);
|
|
968
|
+
console.log(` Scanners run: ${summary.scannersRun.join(', ') || 'none'}`);
|
|
969
|
+
console.log(` Total findings: ${summary.totalFindings}`);
|
|
970
|
+
console.log(` CRITICAL: ${summary.bySeverity.CRITICAL}`);
|
|
971
|
+
console.log(` HIGH: ${summary.bySeverity.HIGH}`);
|
|
972
|
+
console.log(` MEDIUM: ${summary.bySeverity.MEDIUM}`);
|
|
973
|
+
console.log(` LOW: ${summary.bySeverity.LOW}`);
|
|
974
|
+
if (summary.gateResult) {
|
|
975
|
+
const gateStatus = summary.gateResult.passed ? '✅ PASSED' : '❌ FAILED';
|
|
976
|
+
console.log(`\nSecurity Gate: ${gateStatus}`);
|
|
977
|
+
if (!summary.gateResult.passed) {
|
|
978
|
+
for (const reason of summary.gateResult.reasons) {
|
|
979
|
+
console.error(` GATE FAILURE: ${reason}`);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
console.log(`\nReports written to: ${reportsDir}`);
|
|
984
|
+
span.end({ totalFindings: summary.totalFindings });
|
|
985
|
+
logger.info({ event: 'analysis_complete', coverageType: 'security-scan' }, 'Security scan complete');
|
|
986
|
+
const result = {
|
|
987
|
+
type: 'security-scan',
|
|
988
|
+
totalItems: summary.totalFindings,
|
|
989
|
+
coveredItems: summary.totalFindings,
|
|
990
|
+
coveragePercent: 100,
|
|
991
|
+
details: summary,
|
|
992
|
+
};
|
|
993
|
+
// Record security-specific Prometheus metrics (by severity/category/scanner + gate status)
|
|
994
|
+
(0, observability_1.recordSecurityScanMetrics)(summary, serviceName);
|
|
995
|
+
await finaliseObservability([result], {}, metricsPort, serviceName);
|
|
996
|
+
if (summary.gateResult && !summary.gateResult.passed) {
|
|
997
|
+
process.exitCode = 1;
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
// ─── coverage-intelligence command ──────────────────────────────────────────
|
|
1001
|
+
// ─── Intelligence details normalizer ─────────────────────────────────────────
|
|
1002
|
+
// The linkage engine expects `details` to be a flat array of
|
|
1003
|
+
// { endpoint?, covered, name?, ... } objects. Each coverage command stores
|
|
1004
|
+
// its raw report object in CoverageResult.details, so we normalise here at
|
|
1005
|
+
// read time to avoid touching the 11 coverage command implementations.
|
|
1006
|
+
function normalizeDetailsForIntelligence(type, raw) {
|
|
1007
|
+
if (Array.isArray(raw))
|
|
1008
|
+
return raw; // already normalised (e.g. dashboard sample)
|
|
1009
|
+
if (!raw || typeof raw !== 'object')
|
|
1010
|
+
return [];
|
|
1011
|
+
const obj = raw;
|
|
1012
|
+
switch (type) {
|
|
1013
|
+
case 'endpoint': {
|
|
1014
|
+
const eps = obj.endpoints;
|
|
1015
|
+
if (!Array.isArray(eps))
|
|
1016
|
+
return [];
|
|
1017
|
+
return eps.map((e) => {
|
|
1018
|
+
var _a;
|
|
1019
|
+
return ({
|
|
1020
|
+
endpoint: { method: e.method, path: e.path },
|
|
1021
|
+
covered: (_a = e.covered) !== null && _a !== void 0 ? _a : false,
|
|
1022
|
+
});
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
case 'parameter': {
|
|
1026
|
+
const params = obj.parameters;
|
|
1027
|
+
if (!Array.isArray(params))
|
|
1028
|
+
return [];
|
|
1029
|
+
return params.map((p) => {
|
|
1030
|
+
var _a, _b;
|
|
1031
|
+
const param = p.parameter;
|
|
1032
|
+
return {
|
|
1033
|
+
endpoint: param ? { method: param.method, path: param.path } : undefined,
|
|
1034
|
+
covered: ((_a = p.ratio) !== null && _a !== void 0 ? _a : 0) > 0,
|
|
1035
|
+
name: (_b = param === null || param === void 0 ? void 0 : param.name) !== null && _b !== void 0 ? _b : param === null || param === void 0 ? void 0 : param.id,
|
|
1036
|
+
};
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
case 'business': {
|
|
1040
|
+
const rules = obj.rules;
|
|
1041
|
+
if (!Array.isArray(rules))
|
|
1042
|
+
return [];
|
|
1043
|
+
return rules.flatMap((r) => {
|
|
1044
|
+
var _a, _b, _c;
|
|
1045
|
+
const endpoints = (_a = r.rule) === null || _a === void 0 ? void 0 : _a.endpoints;
|
|
1046
|
+
if (!Array.isArray(endpoints) || endpoints.length === 0) {
|
|
1047
|
+
return [{ covered: (_b = r.covered) !== null && _b !== void 0 ? _b : false, name: (_c = r.rule) === null || _c === void 0 ? void 0 : _c.id }];
|
|
1048
|
+
}
|
|
1049
|
+
return endpoints.map((ep) => {
|
|
1050
|
+
var _a, _b;
|
|
1051
|
+
const parts = ep.split(' ');
|
|
1052
|
+
return {
|
|
1053
|
+
endpoint: parts.length > 1 ? { method: parts[0], path: parts[1] } : { path: parts[0] },
|
|
1054
|
+
covered: (_a = r.covered) !== null && _a !== void 0 ? _a : false,
|
|
1055
|
+
name: (_b = r.rule) === null || _b === void 0 ? void 0 : _b.id,
|
|
1056
|
+
};
|
|
1057
|
+
});
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
case 'integration': {
|
|
1061
|
+
const flows = obj.flows;
|
|
1062
|
+
if (!Array.isArray(flows))
|
|
1063
|
+
return [];
|
|
1064
|
+
return flows.flatMap((f) => {
|
|
1065
|
+
var _a, _b;
|
|
1066
|
+
const steps = ((_b = (_a = f.flow) === null || _a === void 0 ? void 0 : _a.steps) !== null && _b !== void 0 ? _b : []);
|
|
1067
|
+
return steps
|
|
1068
|
+
.filter((s) => s.method && s.path)
|
|
1069
|
+
.map((s) => {
|
|
1070
|
+
var _a;
|
|
1071
|
+
return ({
|
|
1072
|
+
endpoint: { method: s.method, path: s.path },
|
|
1073
|
+
covered: f.status !== 'missing',
|
|
1074
|
+
name: ((_a = s.id) !== null && _a !== void 0 ? _a : s.name),
|
|
1075
|
+
});
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
case 'error': {
|
|
1080
|
+
const scenarios = obj.scenarios;
|
|
1081
|
+
if (!Array.isArray(scenarios))
|
|
1082
|
+
return [];
|
|
1083
|
+
return scenarios.map((s) => {
|
|
1084
|
+
var _a;
|
|
1085
|
+
const sc = s.scenario;
|
|
1086
|
+
return {
|
|
1087
|
+
endpoint: sc ? { method: sc.method, path: sc.path } : undefined,
|
|
1088
|
+
covered: (_a = s.covered) !== null && _a !== void 0 ? _a : false,
|
|
1089
|
+
errorCodes: (sc === null || sc === void 0 ? void 0 : sc.errorCode) ? [String(sc.errorCode)] : [],
|
|
1090
|
+
name: sc === null || sc === void 0 ? void 0 : sc.id,
|
|
1091
|
+
};
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
case 'security': {
|
|
1095
|
+
const controls = obj.controls;
|
|
1096
|
+
if (!Array.isArray(controls))
|
|
1097
|
+
return [];
|
|
1098
|
+
return controls
|
|
1099
|
+
.map((c) => {
|
|
1100
|
+
var _a, _b, _c, _d;
|
|
1101
|
+
const epStr = (_b = (_a = c.control) === null || _a === void 0 ? void 0 : _a.endpoint) !== null && _b !== void 0 ? _b : '';
|
|
1102
|
+
const parts = epStr.split(' ');
|
|
1103
|
+
return {
|
|
1104
|
+
endpoint: parts.length > 1 ? { method: parts[0], path: parts[1] } : undefined,
|
|
1105
|
+
covered: (_c = c.covered) !== null && _c !== void 0 ? _c : false,
|
|
1106
|
+
name: (_d = c.control) === null || _d === void 0 ? void 0 : _d.id,
|
|
1107
|
+
};
|
|
1108
|
+
})
|
|
1109
|
+
.filter((item) => { var _a; return (_a = item.endpoint) === null || _a === void 0 ? void 0 : _a.path; });
|
|
1110
|
+
}
|
|
1111
|
+
case 'performance': {
|
|
1112
|
+
const coverages = obj.performanceCoverages;
|
|
1113
|
+
if (!Array.isArray(coverages))
|
|
1114
|
+
return [];
|
|
1115
|
+
return coverages.map((c) => {
|
|
1116
|
+
var _a;
|
|
1117
|
+
const ep = c.endpoint;
|
|
1118
|
+
return {
|
|
1119
|
+
endpoint: ep ? { method: ep.method, path: ep.path } : undefined,
|
|
1120
|
+
covered: (_a = c.hasLoadTestData) !== null && _a !== void 0 ? _a : false,
|
|
1121
|
+
hasThreshold: true,
|
|
1122
|
+
name: ep === null || ep === void 0 ? void 0 : ep.id,
|
|
1123
|
+
};
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
case 'resilience': {
|
|
1127
|
+
const coverages = obj.resilienceCoverages;
|
|
1128
|
+
if (!Array.isArray(coverages))
|
|
1129
|
+
return [];
|
|
1130
|
+
return coverages
|
|
1131
|
+
.filter((c) => { var _a; return (_a = c.scenario) === null || _a === void 0 ? void 0 : _a.endpoint; })
|
|
1132
|
+
.map((c) => {
|
|
1133
|
+
var _a;
|
|
1134
|
+
const sc = c.scenario;
|
|
1135
|
+
return {
|
|
1136
|
+
endpoint: sc.endpoint,
|
|
1137
|
+
covered: (_a = c.covered) !== null && _a !== void 0 ? _a : false,
|
|
1138
|
+
resilience: true,
|
|
1139
|
+
name: sc.id,
|
|
1140
|
+
};
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
default:
|
|
1144
|
+
return [];
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
program
|
|
1148
|
+
.command('coverage-intelligence')
|
|
1149
|
+
.description('Run the coverage intelligence engine to identify functional findings and missing tests')
|
|
1150
|
+
.option('--reports-dir <dir>', 'Directory containing existing coverage reports to analyse', 'reports')
|
|
1151
|
+
.option('--out-dir <dir>', 'Output directory for intelligence reports', 'reports')
|
|
1152
|
+
.option('--project-name <name>', 'Project / service name', 'unknown')
|
|
1153
|
+
.option('--languages <langs>', 'Comma-separated list of languages (e.g. typescript,java)')
|
|
1154
|
+
.option('--frameworks <fws>', 'Comma-separated list of test frameworks (e.g. jest,rest-assured)')
|
|
1155
|
+
.action(async (options) => {
|
|
1156
|
+
const { metricsPort, serviceName } = setupObservability();
|
|
1157
|
+
const logger = (0, observability_1.getLogger)();
|
|
1158
|
+
const reportsDir = path.resolve(options.reportsDir);
|
|
1159
|
+
const outDir = path.resolve(options.outDir);
|
|
1160
|
+
const projectName = options.projectName;
|
|
1161
|
+
const languages = options.languages
|
|
1162
|
+
? options.languages.split(',').map((l) => l.trim())
|
|
1163
|
+
: [];
|
|
1164
|
+
const frameworks = options.frameworks
|
|
1165
|
+
? options.frameworks.split(',').map((f) => f.trim())
|
|
1166
|
+
: [];
|
|
1167
|
+
// Attempt to load existing coverage-summary.json from reportsDir
|
|
1168
|
+
let coverageResults = [];
|
|
1169
|
+
const summaryPath = path.join(reportsDir, 'coverage-summary.json');
|
|
1170
|
+
try {
|
|
1171
|
+
if (require('fs').existsSync(summaryPath)) {
|
|
1172
|
+
const raw = require('fs').readFileSync(summaryPath, 'utf-8');
|
|
1173
|
+
const parsed = JSON.parse(raw);
|
|
1174
|
+
if (Array.isArray(parsed.summary)) {
|
|
1175
|
+
coverageResults = parsed.summary.map((s) => {
|
|
1176
|
+
var _a, _b, _c, _d, _e;
|
|
1177
|
+
return ({
|
|
1178
|
+
type: s.type,
|
|
1179
|
+
totalItems: (_a = s.totalItems) !== null && _a !== void 0 ? _a : 0,
|
|
1180
|
+
coveredItems: (_b = s.coveredItems) !== null && _b !== void 0 ? _b : 0,
|
|
1181
|
+
coveragePercent: (_c = s.coveragePercent) !== null && _c !== void 0 ? _c : 0,
|
|
1182
|
+
details: normalizeDetailsForIntelligence(s.type, (_e = (_d = parsed.details) === null || _d === void 0 ? void 0 : _d[s.type]) !== null && _e !== void 0 ? _e : []),
|
|
1183
|
+
});
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
catch (err) {
|
|
1189
|
+
logger.warn({ event: 'intelligence_load_warning', error: String(err) }, 'Could not load coverage-summary.json');
|
|
1190
|
+
}
|
|
1191
|
+
const report = (0, index_3.runIntelligenceEngine)({
|
|
1192
|
+
coverageResults,
|
|
1193
|
+
languages,
|
|
1194
|
+
frameworks,
|
|
1195
|
+
projectName,
|
|
1196
|
+
outDir,
|
|
1197
|
+
});
|
|
1198
|
+
// Record intelligence metrics
|
|
1199
|
+
(0, observability_2.recordIntelligenceMetrics)({
|
|
1200
|
+
projectName,
|
|
1201
|
+
totalFindings: report.summary.totalFindings,
|
|
1202
|
+
totalRecommendations: report.summary.totalRecommendations,
|
|
1203
|
+
recommendationsByPriority: report.summary.recommendationsByPriority,
|
|
1204
|
+
maxRiskScore: report.summary.maxRiskScore,
|
|
1205
|
+
avgRiskScore: report.summary.avgRiskScore,
|
|
1206
|
+
criticalUncoveredItems: report.summary.criticalUncoveredItems,
|
|
1207
|
+
unprotectedSecurityFindings: report.summary.unprotectedSecurityFindings,
|
|
1208
|
+
languages,
|
|
1209
|
+
frameworks,
|
|
1210
|
+
}, projectName);
|
|
1211
|
+
console.log(`\n=== Coverage Intelligence Results ===`);
|
|
1212
|
+
console.log(` Project: ${projectName}`);
|
|
1213
|
+
console.log(` Functional Findings: ${report.summary.totalFindings}`);
|
|
1214
|
+
console.log(` Missing Test Recommendations: ${report.summary.totalRecommendations}`);
|
|
1215
|
+
console.log(` Max Risk Score: ${report.summary.maxRiskScore}`);
|
|
1216
|
+
console.log(` Avg Risk Score: ${report.summary.avgRiskScore}`);
|
|
1217
|
+
console.log(` Critical Uncovered Items: ${report.summary.criticalUncoveredItems}`);
|
|
1218
|
+
console.log(` Unprotected Security Findings: ${report.summary.unprotectedSecurityFindings}`);
|
|
1219
|
+
if (report.summary.recommendationsByPriority.P0 > 0) {
|
|
1220
|
+
console.log(`\n⚠️ P0 Recommendations: ${report.summary.recommendationsByPriority.P0} — immediate action required`);
|
|
1221
|
+
}
|
|
1222
|
+
console.log(`\nIntelligence reports written to: ${outDir}`);
|
|
1223
|
+
logger.info({ event: 'analysis_complete', coverageType: 'intelligence' }, 'Coverage intelligence analysis complete');
|
|
1224
|
+
await finaliseObservability([{
|
|
1225
|
+
type: 'intelligence',
|
|
1226
|
+
totalItems: report.summary.totalRecommendations,
|
|
1227
|
+
coveredItems: 0,
|
|
1228
|
+
coveragePercent: 0,
|
|
1229
|
+
details: report.summary,
|
|
1230
|
+
}], {}, metricsPort, serviceName);
|
|
1231
|
+
});
|
|
1232
|
+
// ─── coverage-summary-report command ─────────────────────────────────────────
|
|
1233
|
+
program
|
|
1234
|
+
.command('coverage-summary-report')
|
|
1235
|
+
.description('Generate build-summary.md and pr-summary.md from accumulated coverage-summary.json')
|
|
1236
|
+
.option('--reports-dir <dir>', 'Directory containing coverage-summary.json', 'reports')
|
|
1237
|
+
.option('--out-dir <dir>', 'Output directory for summary files', 'reports')
|
|
1238
|
+
.option('--project-name <name>', 'Project / service name', 'unknown')
|
|
1239
|
+
.option('--branch <branch>', 'Branch name for display in summary')
|
|
1240
|
+
.option('--commit-sha <sha>', 'Commit SHA for display in summary')
|
|
1241
|
+
.option('--build-id <id>', 'Build identifier for display in summary')
|
|
1242
|
+
.option('--write-step-summary', 'Append build-summary.md to $GITHUB_STEP_SUMMARY')
|
|
1243
|
+
.action(async (options) => {
|
|
1244
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
1245
|
+
const logger = (0, observability_1.getLogger)();
|
|
1246
|
+
const parentOpts = program.opts();
|
|
1247
|
+
const reportsDir = path.resolve(options.reportsDir);
|
|
1248
|
+
const outDir = path.resolve(options.outDir);
|
|
1249
|
+
const projectName = options.projectName;
|
|
1250
|
+
// Load thresholds from config
|
|
1251
|
+
const analyzerCfg = (0, config_1.loadCentralConfig)(parentOpts.config);
|
|
1252
|
+
const thresholds = ((_a = analyzerCfg.thresholds) !== null && _a !== void 0 ? _a : {});
|
|
1253
|
+
// Read accumulated coverage-summary.json
|
|
1254
|
+
let coverageResults = [];
|
|
1255
|
+
let qualityGateResult;
|
|
1256
|
+
const summaryPath = require('path').join(reportsDir, 'coverage-summary.json');
|
|
1257
|
+
try {
|
|
1258
|
+
if (require('fs').existsSync(summaryPath)) {
|
|
1259
|
+
const raw = require('fs').readFileSync(summaryPath, 'utf-8');
|
|
1260
|
+
const parsed = JSON.parse(raw);
|
|
1261
|
+
if (Array.isArray(parsed.summary)) {
|
|
1262
|
+
coverageResults = parsed.summary.map((s) => {
|
|
1263
|
+
var _a, _b, _c, _d;
|
|
1264
|
+
return ({
|
|
1265
|
+
type: s.type,
|
|
1266
|
+
totalItems: (_a = s.totalItems) !== null && _a !== void 0 ? _a : 0,
|
|
1267
|
+
coveredItems: (_b = s.coveredItems) !== null && _b !== void 0 ? _b : 0,
|
|
1268
|
+
coveragePercent: (_c = s.coveragePercent) !== null && _c !== void 0 ? _c : 0,
|
|
1269
|
+
details: (_d = s.details) !== null && _d !== void 0 ? _d : {},
|
|
1270
|
+
});
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
if (parsed.qualityGate && typeof parsed.qualityGate === 'object') {
|
|
1274
|
+
qualityGateResult = parsed.qualityGate;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
catch (err) {
|
|
1279
|
+
logger.warn({ event: 'summary_report_load_warning', error: String(err) }, 'Could not load coverage-summary.json');
|
|
1280
|
+
}
|
|
1281
|
+
// Optionally load coverage-intelligence.json for intelligence section
|
|
1282
|
+
let intelligenceSummary;
|
|
1283
|
+
const intelligencePath = require('path').join(reportsDir, 'coverage-intelligence.json');
|
|
1284
|
+
try {
|
|
1285
|
+
if (require('fs').existsSync(intelligencePath)) {
|
|
1286
|
+
const raw = require('fs').readFileSync(intelligencePath, 'utf-8');
|
|
1287
|
+
const parsed = JSON.parse(raw);
|
|
1288
|
+
if (parsed.summary && typeof parsed.summary === 'object') {
|
|
1289
|
+
const s = parsed.summary;
|
|
1290
|
+
intelligenceSummary = {
|
|
1291
|
+
totalFindings: (_b = s.totalFindings) !== null && _b !== void 0 ? _b : 0,
|
|
1292
|
+
totalRecommendations: (_c = s.totalRecommendations) !== null && _c !== void 0 ? _c : 0,
|
|
1293
|
+
maxRiskScore: (_d = s.maxRiskScore) !== null && _d !== void 0 ? _d : 0,
|
|
1294
|
+
avgRiskScore: (_e = s.avgRiskScore) !== null && _e !== void 0 ? _e : 0,
|
|
1295
|
+
criticalUncoveredItems: (_f = s.criticalUncoveredItems) !== null && _f !== void 0 ? _f : 0,
|
|
1296
|
+
unprotectedSecurityFindings: (_g = s.unprotectedSecurityFindings) !== null && _g !== void 0 ? _g : 0,
|
|
1297
|
+
recommendationsByPriority: (_h = s.recommendationsByPriority) !== null && _h !== void 0 ? _h : {},
|
|
1298
|
+
topRiskAreas: (_j = s.topRiskAreas) !== null && _j !== void 0 ? _j : [],
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
catch (_) {
|
|
1304
|
+
// intelligence report is optional — ignore read errors
|
|
1305
|
+
}
|
|
1306
|
+
const summaryInput = {
|
|
1307
|
+
results: coverageResults,
|
|
1308
|
+
qualityGate: qualityGateResult,
|
|
1309
|
+
thresholds,
|
|
1310
|
+
projectName,
|
|
1311
|
+
branch: options.branch,
|
|
1312
|
+
commitSha: options.commitSha,
|
|
1313
|
+
buildId: options.buildId,
|
|
1314
|
+
intelligenceSummary,
|
|
1315
|
+
};
|
|
1316
|
+
const buildResult = await (0, buildSummary_1.generateBuildSummary)(summaryInput, outDir);
|
|
1317
|
+
await (0, prSummary_1.generatePrSummary)(summaryInput, outDir);
|
|
1318
|
+
// Optionally write to GITHUB_STEP_SUMMARY
|
|
1319
|
+
if (options.writeStepSummary) {
|
|
1320
|
+
const stepSummaryPath = process.env['GITHUB_STEP_SUMMARY'];
|
|
1321
|
+
if (stepSummaryPath) {
|
|
1322
|
+
require('fs').appendFileSync(stepSummaryPath, buildResult.markdown + '\n', 'utf-8');
|
|
1323
|
+
console.log(`Step summary written to ${stepSummaryPath}`);
|
|
1324
|
+
}
|
|
1325
|
+
else {
|
|
1326
|
+
logger.warn({ event: 'step_summary_missing_env' }, 'GITHUB_STEP_SUMMARY env var not set; skipping step summary write');
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
console.log(`\n=== Coverage Summary Report ===`);
|
|
1330
|
+
console.log(` Project: ${projectName}`);
|
|
1331
|
+
console.log(` Metrics: ${coverageResults.length}`);
|
|
1332
|
+
if (qualityGateResult !== undefined) {
|
|
1333
|
+
console.log(` Overall: ${qualityGateResult.passed ? 'PASSED' : 'FAILED'}`);
|
|
1334
|
+
}
|
|
1335
|
+
console.log(`\nSummary reports written to: ${outDir}`);
|
|
1336
|
+
logger.info({ event: 'summary_report_complete' }, 'Coverage summary report generated');
|
|
1337
|
+
});
|
|
1338
|
+
// ─── analyze command ─────────────────────────────────────────────────────────
|
|
1339
|
+
program
|
|
1340
|
+
.command('analyze')
|
|
1341
|
+
.description('Zero-config full analysis: discover project artifacts, infer missing rules/flows, compute coverage.')
|
|
1342
|
+
.option('--root <dir>', 'Project root to analyze (default: current working directory)')
|
|
1343
|
+
.option('--reports-dir <dir>', 'Directory to write reports to (default: reports/)')
|
|
1344
|
+
.option('--export-inferred-rules', 'Export inferred rules/flows as editable YAML files')
|
|
1345
|
+
.option('--no-infer-business-rules', 'Disable business rule inference')
|
|
1346
|
+
.option('--no-infer-integration-flows', 'Disable integration flow inference')
|
|
1347
|
+
.action(async (options) => {
|
|
1348
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1349
|
+
const { metricsPort, serviceName } = setupObservability();
|
|
1350
|
+
const logger = (0, observability_1.getLogger)();
|
|
1351
|
+
const configPath = program.opts()['config'];
|
|
1352
|
+
const analyzerCfg = (0, config_1.loadCentralConfig)(configPath);
|
|
1353
|
+
const rootDir = (_a = options['root']) !== null && _a !== void 0 ? _a : process.cwd();
|
|
1354
|
+
const reportsDir = (_d = (_b = options['reportsDir']) !== null && _b !== void 0 ? _b : (_c = analyzerCfg.reports) === null || _c === void 0 ? void 0 : _c.outputDir) !== null && _d !== void 0 ? _d : 'reports';
|
|
1355
|
+
const doInferRules = options['inferBusinessRules'] !== false &&
|
|
1356
|
+
((_f = (_e = analyzerCfg.analysis) === null || _e === void 0 ? void 0 : _e.inferBusinessRules) !== null && _f !== void 0 ? _f : true);
|
|
1357
|
+
const doInferFlows = options['inferIntegrationFlows'] !== false &&
|
|
1358
|
+
((_h = (_g = analyzerCfg.analysis) === null || _g === void 0 ? void 0 : _g.inferIntegrationFlows) !== null && _h !== void 0 ? _h : true);
|
|
1359
|
+
const agnosticDisc = (_k = (_j = analyzerCfg.analysis) === null || _j === void 0 ? void 0 : _j.agnosticDiscovery) !== null && _k !== void 0 ? _k : true;
|
|
1360
|
+
logger.info({ event: 'analyze_start', rootDir }, 'Starting agnostic project analysis');
|
|
1361
|
+
// ── 1. Discover project artifacts ──────────────────────────────────────
|
|
1362
|
+
const artifacts = (0, projectDiscovery_1.discoverProject)({ rootDir });
|
|
1363
|
+
console.log('\n=== API Test Coverage Analyzer ===');
|
|
1364
|
+
console.log(`Project root: ${rootDir}`);
|
|
1365
|
+
console.log(`Languages detected: ${artifacts.languages.join(', ') || 'none'}`);
|
|
1366
|
+
console.log(`Frameworks detected: ${artifacts.frameworks.join(', ') || 'none'}`);
|
|
1367
|
+
console.log(`API specs found: ${artifacts.specs.length}`);
|
|
1368
|
+
console.log(`Test files found: ${artifacts.testFiles.length}`);
|
|
1369
|
+
console.log(`Service files found: ${artifacts.serviceFiles.length}`);
|
|
1370
|
+
if (!agnosticDisc && artifacts.specs.length === 0) {
|
|
1371
|
+
console.error('\n[ERROR] No API spec found and agnosticDiscovery is disabled. Aborting.');
|
|
1372
|
+
process.exitCode = 1;
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
const warnings = [];
|
|
1376
|
+
// ── 2. Business rule inference ─────────────────────────────────────────
|
|
1377
|
+
if (doInferRules) {
|
|
1378
|
+
console.log('\nNo business-rules.yaml file detected.');
|
|
1379
|
+
console.log('Business rules will be inferred automatically.');
|
|
1380
|
+
const rulesResult = (0, businessRuleInference_1.inferBusinessRules)(artifacts.serviceFiles, warnings);
|
|
1381
|
+
const rulesPath = (0, businessRuleInference_1.writeInferredBusinessRules)(rulesResult, reportsDir);
|
|
1382
|
+
console.log(`\nBusiness Rule Coverage`);
|
|
1383
|
+
console.log(` Source: inferred`);
|
|
1384
|
+
console.log(` Rules detected: ${rulesResult.rules.length}`);
|
|
1385
|
+
console.log(` Written to: ${rulesPath}`);
|
|
1386
|
+
if (options['exportInferredRules']) {
|
|
1387
|
+
const fsMod = require('fs');
|
|
1388
|
+
const yaml = rulesResult.rules.map((r) => [
|
|
1389
|
+
`- id: ${r.id}`,
|
|
1390
|
+
` name: ${r.name}`,
|
|
1391
|
+
` type: ${r.type}`,
|
|
1392
|
+
r.endpoint ? ` endpoint: "${r.endpoint}"` : null,
|
|
1393
|
+
` condition: "${r.condition.replace(/"/g, "'")}"`,
|
|
1394
|
+
` source: ${r.source_location}`,
|
|
1395
|
+
].filter(Boolean).join('\n')).join('\n');
|
|
1396
|
+
fsMod.writeFileSync(path.join(rootDir, 'generated-business-rules.yaml'), yaml, 'utf-8');
|
|
1397
|
+
console.log(' Exported: generated-business-rules.yaml');
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
// ── 3. Integration flow inference ──────────────────────────────────────
|
|
1401
|
+
if (doInferFlows) {
|
|
1402
|
+
console.log('\nNo integration-flows.yaml file detected.');
|
|
1403
|
+
console.log('Integration flows will be inferred automatically.');
|
|
1404
|
+
const flowsResult = (0, integrationFlowInference_1.inferIntegrationFlows)(artifacts.testFiles, warnings);
|
|
1405
|
+
const flowsPath = (0, integrationFlowInference_1.writeInferredIntegrationFlows)(flowsResult, reportsDir);
|
|
1406
|
+
console.log(`\nIntegration Flow Coverage`);
|
|
1407
|
+
console.log(` Source: inferred`);
|
|
1408
|
+
console.log(` Flows detected: ${flowsResult.flows.length}`);
|
|
1409
|
+
console.log(` Written to: ${flowsPath}`);
|
|
1410
|
+
if (options['exportInferredRules']) {
|
|
1411
|
+
const fs = require('fs');
|
|
1412
|
+
const yaml = flowsResult.flows.map((f) => [
|
|
1413
|
+
`- id: ${f.id}`,
|
|
1414
|
+
` name: "${f.name}"`,
|
|
1415
|
+
` steps:`,
|
|
1416
|
+
...f.steps.map((s) => ` - { method: ${s.method}, path: "${s.path}" }`),
|
|
1417
|
+
].join('\n')).join('\n');
|
|
1418
|
+
fs.writeFileSync(path.join(rootDir, 'generated-integration-flows.yaml'), yaml, 'utf-8');
|
|
1419
|
+
console.log(' Exported: generated-integration-flows.yaml');
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
// ── 4. Emit warnings ───────────────────────────────────────────────────
|
|
1423
|
+
for (const w of warnings) {
|
|
1424
|
+
console.warn(`[WARN] ${w}`);
|
|
1425
|
+
}
|
|
1426
|
+
// ── 5. Configuration override log ──────────────────────────────────────
|
|
1427
|
+
if (options['inferBusinessRules'] === false) {
|
|
1428
|
+
console.log('\nConfiguration override detected: business rule inference disabled via CLI');
|
|
1429
|
+
}
|
|
1430
|
+
if (options['inferIntegrationFlows'] === false) {
|
|
1431
|
+
console.log('\nConfiguration override detected: integration flow inference disabled via CLI');
|
|
1432
|
+
}
|
|
1433
|
+
logger.info({ event: 'analyze_complete', warnings: warnings.length }, 'Agnostic project analysis complete');
|
|
1434
|
+
await finaliseObservability([], {}, metricsPort, serviceName);
|
|
1435
|
+
});
|
|
1436
|
+
// Parse the command-line arguments
|
|
1437
|
+
program.parse(process.argv);
|
|
1438
|
+
// When invoked with no arguments (no subcommand), display help
|
|
1439
|
+
if (process.argv.length <= 2) {
|
|
1440
|
+
program.help();
|
|
1441
|
+
}
|