codeslick-cli 1.2.2 → 1.2.4
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/__tests__/threshold-handler.test.ts +175 -0
- package/dist/packages/cli/src/commands/scan.d.ts +11 -0
- package/dist/packages/cli/src/commands/scan.d.ts.map +1 -1
- package/dist/packages/cli/src/commands/scan.js +74 -5
- package/dist/packages/cli/src/commands/scan.js.map +1 -1
- package/dist/packages/cli/src/config/config-loader.d.ts +11 -0
- package/dist/packages/cli/src/config/config-loader.d.ts.map +1 -1
- package/dist/packages/cli/src/config/config-loader.js.map +1 -1
- package/dist/packages/cli/src/reporters/cli-reporter.d.ts +18 -0
- package/dist/packages/cli/src/reporters/cli-reporter.d.ts.map +1 -1
- package/dist/packages/cli/src/reporters/cli-reporter.js +115 -0
- package/dist/packages/cli/src/reporters/cli-reporter.js.map +1 -1
- package/dist/packages/cli/src/utils/test-runner.d.ts +84 -0
- package/dist/packages/cli/src/utils/test-runner.d.ts.map +1 -0
- package/dist/packages/cli/src/utils/test-runner.js +209 -0
- package/dist/packages/cli/src/utils/test-runner.js.map +1 -0
- package/dist/packages/cli/src/utils/threshold-handler.d.ts +40 -0
- package/dist/packages/cli/src/utils/threshold-handler.d.ts.map +1 -0
- package/dist/packages/cli/src/utils/threshold-handler.js +85 -0
- package/dist/packages/cli/src/utils/threshold-handler.js.map +1 -0
- package/dist/src/lib/analyzers/go-analyzer.d.ts +5 -0
- package/dist/src/lib/analyzers/go-analyzer.d.ts.map +1 -1
- package/dist/src/lib/analyzers/go-analyzer.js +47 -0
- package/dist/src/lib/analyzers/go-analyzer.js.map +1 -1
- package/dist/src/lib/analyzers/java-analyzer.d.ts +5 -0
- package/dist/src/lib/analyzers/java-analyzer.d.ts.map +1 -1
- package/dist/src/lib/analyzers/java-analyzer.js +48 -0
- package/dist/src/lib/analyzers/java-analyzer.js.map +1 -1
- package/dist/src/lib/analyzers/javascript-analyzer.d.ts +5 -0
- package/dist/src/lib/analyzers/javascript-analyzer.d.ts.map +1 -1
- package/dist/src/lib/analyzers/javascript-analyzer.js +48 -0
- package/dist/src/lib/analyzers/javascript-analyzer.js.map +1 -1
- package/dist/src/lib/analyzers/python-analyzer.d.ts +5 -0
- package/dist/src/lib/analyzers/python-analyzer.d.ts.map +1 -1
- package/dist/src/lib/analyzers/python-analyzer.js +55 -0
- package/dist/src/lib/analyzers/python-analyzer.js.map +1 -1
- package/dist/src/lib/analyzers/types.d.ts +4 -0
- package/dist/src/lib/analyzers/types.d.ts.map +1 -1
- package/dist/src/lib/analyzers/typescript-analyzer.d.ts +5 -0
- package/dist/src/lib/analyzers/typescript-analyzer.d.ts.map +1 -1
- package/dist/src/lib/analyzers/typescript-analyzer.js +48 -0
- package/dist/src/lib/analyzers/typescript-analyzer.js.map +1 -1
- package/dist/src/lib/github/types.d.ts +112 -0
- package/dist/src/lib/github/types.d.ts.map +1 -0
- package/dist/src/lib/github/types.js +34 -0
- package/dist/src/lib/github/types.js.map +1 -0
- package/dist/src/lib/security/epss-service.d.ts +63 -0
- package/dist/src/lib/security/epss-service.d.ts.map +1 -0
- package/dist/src/lib/security/epss-service.js +256 -0
- package/dist/src/lib/security/epss-service.js.map +1 -0
- package/dist/src/lib/security/threshold-evaluator.d.ts +73 -0
- package/dist/src/lib/security/threshold-evaluator.d.ts.map +1 -0
- package/dist/src/lib/security/threshold-evaluator.js +234 -0
- package/dist/src/lib/security/threshold-evaluator.js.map +1 -0
- package/dist/src/lib/security/triage-service.d.ts +76 -0
- package/dist/src/lib/security/triage-service.d.ts.map +1 -0
- package/dist/src/lib/security/triage-service.js +318 -0
- package/dist/src/lib/security/triage-service.js.map +1 -0
- package/dist/src/lib/types/index.d.ts +4 -0
- package/dist/src/lib/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/commands/scan.ts +100 -7
- package/src/config/config-loader.ts +15 -0
- package/src/reporters/cli-reporter.ts +132 -0
- package/src/utils/test-runner.ts +249 -0
- package/src/utils/threshold-handler.ts +99 -0
|
@@ -731,3 +731,135 @@ export function printBriefSummary(
|
|
|
731
731
|
console.log(chalk.gray(` Open: code ${reportPath}`));
|
|
732
732
|
console.log('');
|
|
733
733
|
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Print test execution start
|
|
737
|
+
* Winter Roadmap WR2: Test Execution Integration
|
|
738
|
+
*/
|
|
739
|
+
export function printTestStart(command: string): void {
|
|
740
|
+
console.log('');
|
|
741
|
+
console.log(chalk.bold('Running Tests'));
|
|
742
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
743
|
+
console.log(chalk.gray(` Command: ${command}`));
|
|
744
|
+
console.log('');
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Print test execution results
|
|
749
|
+
* Winter Roadmap WR2: Test Execution Integration
|
|
750
|
+
*/
|
|
751
|
+
export function printTestResult(result: {
|
|
752
|
+
success: boolean;
|
|
753
|
+
exitCode: number;
|
|
754
|
+
duration: number;
|
|
755
|
+
stdout: string;
|
|
756
|
+
stderr: string;
|
|
757
|
+
command: string;
|
|
758
|
+
timedOut: boolean;
|
|
759
|
+
}): void {
|
|
760
|
+
console.log('');
|
|
761
|
+
console.log(chalk.bold('Test Results') + chalk.gray(` (${(result.duration / 1000).toFixed(1)}s)`));
|
|
762
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
763
|
+
|
|
764
|
+
if (result.timedOut) {
|
|
765
|
+
console.log('');
|
|
766
|
+
console.log(chalk.red.bold(' ✖ TIMEOUT'));
|
|
767
|
+
console.log(chalk.red(` Tests exceeded timeout and were terminated`));
|
|
768
|
+
console.log('');
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (result.success) {
|
|
773
|
+
console.log('');
|
|
774
|
+
console.log(chalk.green.bold(' ✓ PASSED'));
|
|
775
|
+
console.log(chalk.green(` All tests passed successfully`));
|
|
776
|
+
console.log('');
|
|
777
|
+
|
|
778
|
+
// Try to extract test count from output
|
|
779
|
+
const testInfo = extractTestInfo(result.stdout + result.stderr);
|
|
780
|
+
if (testInfo) {
|
|
781
|
+
console.log(chalk.gray(` Tests run: ${testInfo.total}`));
|
|
782
|
+
console.log(chalk.gray(` Passed: ${testInfo.passed}`));
|
|
783
|
+
if (testInfo.failed > 0) {
|
|
784
|
+
console.log(chalk.gray(` Failed: ${testInfo.failed}`));
|
|
785
|
+
}
|
|
786
|
+
console.log('');
|
|
787
|
+
}
|
|
788
|
+
} else {
|
|
789
|
+
console.log('');
|
|
790
|
+
console.log(chalk.red.bold(' ✖ FAILED'));
|
|
791
|
+
console.log(chalk.red(` Tests failed with exit code ${result.exitCode}`));
|
|
792
|
+
console.log('');
|
|
793
|
+
|
|
794
|
+
// Try to extract test count from output
|
|
795
|
+
const testInfo = extractTestInfo(result.stdout + result.stderr);
|
|
796
|
+
if (testInfo) {
|
|
797
|
+
console.log(chalk.gray(` Tests run: ${testInfo.total}`));
|
|
798
|
+
console.log(chalk.green(` Passed: ${testInfo.passed}`));
|
|
799
|
+
console.log(chalk.red(` Failed: ${testInfo.failed}`));
|
|
800
|
+
console.log('');
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Show stderr if available (first 500 chars)
|
|
804
|
+
if (result.stderr && result.stderr.length > 0) {
|
|
805
|
+
const errorPreview = result.stderr.substring(0, 500);
|
|
806
|
+
console.log(chalk.gray(' Error output (first 500 chars):'));
|
|
807
|
+
console.log(chalk.gray(' ' + errorPreview.replace(/\n/g, '\n ')));
|
|
808
|
+
if (result.stderr.length > 500) {
|
|
809
|
+
console.log(chalk.gray(' ...'));
|
|
810
|
+
}
|
|
811
|
+
console.log('');
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Extract test count information from test output
|
|
818
|
+
*/
|
|
819
|
+
function extractTestInfo(output: string): { total: number; passed: number; failed: number } | null {
|
|
820
|
+
// Jest/Vitest format: "Tests: 5 passed, 5 total"
|
|
821
|
+
const jestMatch = output.match(/Tests:\s+(\d+)\s+passed(?:,\s+(\d+)\s+failed)?.*?(\d+)\s+total/i);
|
|
822
|
+
if (jestMatch) {
|
|
823
|
+
return {
|
|
824
|
+
passed: parseInt(jestMatch[1]),
|
|
825
|
+
failed: parseInt(jestMatch[2] || '0'),
|
|
826
|
+
total: parseInt(jestMatch[3]),
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Pytest format: "10 passed in 2.5s" or "5 passed, 2 failed in 1.2s"
|
|
831
|
+
const pytestMatch = output.match(/(\d+)\s+passed(?:,\s+(\d+)\s+failed)?/i);
|
|
832
|
+
if (pytestMatch) {
|
|
833
|
+
const passed = parseInt(pytestMatch[1]);
|
|
834
|
+
const failed = parseInt(pytestMatch[2] || '0');
|
|
835
|
+
return {
|
|
836
|
+
passed,
|
|
837
|
+
failed,
|
|
838
|
+
total: passed + failed,
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Go test format: "ok" or "FAIL"
|
|
843
|
+
const goMatch = output.match(/ok.*?(\d+)\s+tests?/i);
|
|
844
|
+
if (goMatch) {
|
|
845
|
+
return {
|
|
846
|
+
passed: parseInt(goMatch[1]),
|
|
847
|
+
failed: 0,
|
|
848
|
+
total: parseInt(goMatch[1]),
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Maven format: "Tests run: 10, Failures: 2, Errors: 0"
|
|
853
|
+
const mavenMatch = output.match(/Tests run:\s+(\d+),\s+Failures:\s+(\d+)/i);
|
|
854
|
+
if (mavenMatch) {
|
|
855
|
+
const total = parseInt(mavenMatch[1]);
|
|
856
|
+
const failed = parseInt(mavenMatch[2]);
|
|
857
|
+
return {
|
|
858
|
+
passed: total - failed,
|
|
859
|
+
failed,
|
|
860
|
+
total,
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Runner - Execute User Test Suite
|
|
3
|
+
*
|
|
4
|
+
* Runs user's existing test suite (npm test, pytest, go test, etc.)
|
|
5
|
+
* to verify that security scans or fixes don't break functionality.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Detect test command automatically (package.json, pytest, go test)
|
|
9
|
+
* - Custom test command support
|
|
10
|
+
* - Timeout handling
|
|
11
|
+
* - Exit code interpretation
|
|
12
|
+
* - Test output capture
|
|
13
|
+
*
|
|
14
|
+
* Winter Roadmap WR2: Test Execution Integration
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { exec } from 'child_process';
|
|
18
|
+
import { promisify } from 'util';
|
|
19
|
+
import { existsSync, readFileSync } from 'fs';
|
|
20
|
+
import { resolve } from 'path';
|
|
21
|
+
|
|
22
|
+
const execAsync = promisify(exec);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Test execution result
|
|
26
|
+
*/
|
|
27
|
+
export interface TestResult {
|
|
28
|
+
success: boolean;
|
|
29
|
+
exitCode: number;
|
|
30
|
+
duration: number; // milliseconds
|
|
31
|
+
stdout: string;
|
|
32
|
+
stderr: string;
|
|
33
|
+
command: string;
|
|
34
|
+
timedOut: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Auto-detect test command from project files
|
|
39
|
+
*
|
|
40
|
+
* Detection order:
|
|
41
|
+
* 1. package.json "test" script (npm test)
|
|
42
|
+
* 2. pytest.ini or setup.py (pytest)
|
|
43
|
+
* 3. go.mod (go test ./...)
|
|
44
|
+
* 4. pom.xml (mvn test)
|
|
45
|
+
*
|
|
46
|
+
* @param cwd - Current working directory
|
|
47
|
+
* @returns Detected test command or null
|
|
48
|
+
*/
|
|
49
|
+
export function detectTestCommand(cwd: string = process.cwd()): string | null {
|
|
50
|
+
// 1. Check for package.json with test script
|
|
51
|
+
const packageJsonPath = resolve(cwd, 'package.json');
|
|
52
|
+
if (existsSync(packageJsonPath)) {
|
|
53
|
+
try {
|
|
54
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
55
|
+
if (packageJson.scripts?.test) {
|
|
56
|
+
return 'npm test';
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// Invalid package.json, continue
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 2. Check for Python test setup
|
|
64
|
+
if (existsSync(resolve(cwd, 'pytest.ini')) ||
|
|
65
|
+
existsSync(resolve(cwd, 'setup.py')) ||
|
|
66
|
+
existsSync(resolve(cwd, 'pyproject.toml'))) {
|
|
67
|
+
return 'pytest';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 3. Check for Go modules
|
|
71
|
+
if (existsSync(resolve(cwd, 'go.mod'))) {
|
|
72
|
+
return 'go test ./...';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 4. Check for Maven/Java
|
|
76
|
+
if (existsSync(resolve(cwd, 'pom.xml'))) {
|
|
77
|
+
return 'mvn test';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 5. Check for Gradle/Java
|
|
81
|
+
if (existsSync(resolve(cwd, 'build.gradle')) ||
|
|
82
|
+
existsSync(resolve(cwd, 'build.gradle.kts'))) {
|
|
83
|
+
return './gradlew test';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Run user's test suite
|
|
91
|
+
*
|
|
92
|
+
* @param command - Test command to execute (auto-detected if not provided)
|
|
93
|
+
* @param options - Execution options
|
|
94
|
+
* @returns Test execution result
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* const result = await runTests('npm test', { timeout: 60000 });
|
|
98
|
+
* if (!result.success) {
|
|
99
|
+
* console.error('Tests failed:', result.stderr);
|
|
100
|
+
* }
|
|
101
|
+
*/
|
|
102
|
+
export async function runTests(
|
|
103
|
+
command?: string,
|
|
104
|
+
options: {
|
|
105
|
+
cwd?: string;
|
|
106
|
+
timeout?: number; // milliseconds
|
|
107
|
+
verbose?: boolean;
|
|
108
|
+
} = {}
|
|
109
|
+
): Promise<TestResult> {
|
|
110
|
+
const cwd = options.cwd || process.cwd();
|
|
111
|
+
const timeout = options.timeout || 300000; // Default: 5 minutes
|
|
112
|
+
|
|
113
|
+
// Auto-detect test command if not provided
|
|
114
|
+
const testCommand = command || detectTestCommand(cwd);
|
|
115
|
+
|
|
116
|
+
if (!testCommand) {
|
|
117
|
+
throw new Error('No test command found. Specify one using --test-command or add it to .codeslick.json');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const startTime = Date.now();
|
|
121
|
+
let timedOut = false;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const { stdout, stderr } = await execAsync(testCommand, {
|
|
125
|
+
cwd,
|
|
126
|
+
timeout,
|
|
127
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large test output
|
|
128
|
+
env: {
|
|
129
|
+
...process.env,
|
|
130
|
+
// Disable test coverage to speed up execution (optional)
|
|
131
|
+
NODE_ENV: process.env.NODE_ENV || 'test',
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const duration = Date.now() - startTime;
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
success: true,
|
|
139
|
+
exitCode: 0,
|
|
140
|
+
duration,
|
|
141
|
+
stdout: stdout.trim(),
|
|
142
|
+
stderr: stderr.trim(),
|
|
143
|
+
command: testCommand,
|
|
144
|
+
timedOut: false,
|
|
145
|
+
};
|
|
146
|
+
} catch (error: any) {
|
|
147
|
+
const duration = Date.now() - startTime;
|
|
148
|
+
|
|
149
|
+
// Check if timeout occurred
|
|
150
|
+
if (error.killed && error.signal === 'SIGTERM') {
|
|
151
|
+
timedOut = true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
success: false,
|
|
156
|
+
exitCode: error.code || 1,
|
|
157
|
+
duration,
|
|
158
|
+
stdout: error.stdout?.trim() || '',
|
|
159
|
+
stderr: error.stderr?.trim() || '',
|
|
160
|
+
command: testCommand,
|
|
161
|
+
timedOut,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Format test duration for display
|
|
168
|
+
*
|
|
169
|
+
* @param duration - Duration in milliseconds
|
|
170
|
+
* @returns Formatted duration string (e.g., "2.5s", "1m 30s")
|
|
171
|
+
*/
|
|
172
|
+
export function formatDuration(duration: number): string {
|
|
173
|
+
if (duration < 1000) {
|
|
174
|
+
return `${duration}ms`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (duration < 60000) {
|
|
178
|
+
return `${(duration / 1000).toFixed(1)}s`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const minutes = Math.floor(duration / 60000);
|
|
182
|
+
const seconds = Math.floor((duration % 60000) / 1000);
|
|
183
|
+
return `${minutes}m ${seconds}s`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Parse test output to extract test count information
|
|
188
|
+
*
|
|
189
|
+
* Supports common test framework output formats:
|
|
190
|
+
* - Jest/Vitest: "Tests: 10 passed, 10 total"
|
|
191
|
+
* - Pytest: "10 passed in 2.5s"
|
|
192
|
+
* - Go: "ok 10 tests"
|
|
193
|
+
* - Maven: "Tests run: 10, Failures: 0"
|
|
194
|
+
*
|
|
195
|
+
* @param output - Test stdout/stderr
|
|
196
|
+
* @param framework - Test framework (auto-detected if not provided)
|
|
197
|
+
* @returns Parsed test statistics or null
|
|
198
|
+
*/
|
|
199
|
+
export function parseTestOutput(output: string, framework?: string): {
|
|
200
|
+
passed: number;
|
|
201
|
+
failed: number;
|
|
202
|
+
total: number;
|
|
203
|
+
} | null {
|
|
204
|
+
// Jest/Vitest format: "Tests: 5 passed, 5 total"
|
|
205
|
+
const jestMatch = output.match(/Tests:\s+(\d+)\s+passed(?:,\s+(\d+)\s+failed)?.*?(\d+)\s+total/i);
|
|
206
|
+
if (jestMatch) {
|
|
207
|
+
return {
|
|
208
|
+
passed: parseInt(jestMatch[1]),
|
|
209
|
+
failed: parseInt(jestMatch[2] || '0'),
|
|
210
|
+
total: parseInt(jestMatch[3]),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Pytest format: "10 passed in 2.5s" or "5 passed, 2 failed in 1.2s"
|
|
215
|
+
const pytestMatch = output.match(/(\d+)\s+passed(?:,\s+(\d+)\s+failed)?/i);
|
|
216
|
+
if (pytestMatch) {
|
|
217
|
+
const passed = parseInt(pytestMatch[1]);
|
|
218
|
+
const failed = parseInt(pytestMatch[2] || '0');
|
|
219
|
+
return {
|
|
220
|
+
passed,
|
|
221
|
+
failed,
|
|
222
|
+
total: passed + failed,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Go test format: "ok" or "FAIL"
|
|
227
|
+
const goMatch = output.match(/ok.*?(\d+)\s+tests?/i);
|
|
228
|
+
if (goMatch) {
|
|
229
|
+
return {
|
|
230
|
+
passed: parseInt(goMatch[1]),
|
|
231
|
+
failed: 0,
|
|
232
|
+
total: parseInt(goMatch[1]),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Maven format: "Tests run: 10, Failures: 2, Errors: 0"
|
|
237
|
+
const mavenMatch = output.match(/Tests run:\s+(\d+),\s+Failures:\s+(\d+)/i);
|
|
238
|
+
if (mavenMatch) {
|
|
239
|
+
const total = parseInt(mavenMatch[1]);
|
|
240
|
+
const failed = parseInt(mavenMatch[2]);
|
|
241
|
+
return {
|
|
242
|
+
passed: total - failed,
|
|
243
|
+
failed,
|
|
244
|
+
total,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Threshold Handler - WR2 Integration
|
|
3
|
+
*
|
|
4
|
+
* Integrates the comprehensive threshold evaluation system (WR2) into CLI scans.
|
|
5
|
+
* Provides advanced threshold checking beyond simple severity filtering.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Critical/High vulnerability blocking
|
|
9
|
+
* - Maximum vulnerability count limits
|
|
10
|
+
* - EPSS score thresholds
|
|
11
|
+
* - Glob pattern exemptions
|
|
12
|
+
* - Custom failure messages
|
|
13
|
+
*
|
|
14
|
+
* Winter Roadmap WR2: Pass/Fail Thresholds
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { FileScanResult } from '../scanner/local-scanner';
|
|
18
|
+
import {
|
|
19
|
+
evaluateThresholds,
|
|
20
|
+
formatCLIOutput,
|
|
21
|
+
type ThresholdConfig,
|
|
22
|
+
type ThresholdResult,
|
|
23
|
+
} from '../../../../src/lib/security/threshold-evaluator';
|
|
24
|
+
import type { AggregatedResults, FileAnalysis } from '../../../../src/lib/github/types';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convert CLI scan results to AggregatedResults format
|
|
28
|
+
*
|
|
29
|
+
* The threshold evaluator expects AggregatedResults (used by GitHub App),
|
|
30
|
+
* but CLI uses FileScanResult. This function bridges the gap.
|
|
31
|
+
*/
|
|
32
|
+
export function convertToAggregatedResults(results: FileScanResult[]): AggregatedResults {
|
|
33
|
+
// Calculate totals
|
|
34
|
+
const totalVulnerabilities = results.reduce(
|
|
35
|
+
(sum, r) => sum + r.critical + r.high + r.medium + r.low,
|
|
36
|
+
0
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const criticalCount = results.reduce((sum, r) => sum + r.critical, 0);
|
|
40
|
+
const highCount = results.reduce((sum, r) => sum + r.high, 0);
|
|
41
|
+
const mediumCount = results.reduce((sum, r) => sum + r.medium, 0);
|
|
42
|
+
const lowCount = results.reduce((sum, r) => sum + r.low, 0);
|
|
43
|
+
|
|
44
|
+
// Convert FileScanResult[] to FileAnalysis[]
|
|
45
|
+
const fileResults: FileAnalysis[] = results.map((result) => ({
|
|
46
|
+
filename: result.relativePath,
|
|
47
|
+
language: result.language,
|
|
48
|
+
vulnerabilities: result.result.security?.vulnerabilities || [],
|
|
49
|
+
criticalCount: result.critical,
|
|
50
|
+
highCount: result.high,
|
|
51
|
+
mediumCount: result.medium,
|
|
52
|
+
lowCount: result.low,
|
|
53
|
+
syntaxErrors: result.result.syntax?.lineErrors?.length || 0,
|
|
54
|
+
syntaxErrorCount: result.result.syntax?.lineErrors?.filter((e: any) => e.severity === 'error').length || 0,
|
|
55
|
+
syntaxWarningCount: result.result.syntax?.lineErrors?.filter((e: any) => e.severity === 'warning').length || 0,
|
|
56
|
+
syntaxInfoCount: result.result.syntax?.lineErrors?.filter((e: any) => e.severity === 'info').length || 0,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
filesAnalyzed: results.length,
|
|
61
|
+
totalVulnerabilities,
|
|
62
|
+
criticalCount,
|
|
63
|
+
highCount,
|
|
64
|
+
mediumCount,
|
|
65
|
+
lowCount,
|
|
66
|
+
syntaxErrors: fileResults.reduce((sum, f) => sum + f.syntaxErrors, 0),
|
|
67
|
+
syntaxErrorCount: fileResults.reduce((sum, f) => sum + f.syntaxErrorCount, 0),
|
|
68
|
+
syntaxWarningCount: fileResults.reduce((sum, f) => sum + f.syntaxWarningCount, 0),
|
|
69
|
+
syntaxInfoCount: fileResults.reduce((sum, f) => sum + f.syntaxInfoCount, 0),
|
|
70
|
+
fileResults,
|
|
71
|
+
analyzedAt: new Date(),
|
|
72
|
+
prUrl: '', // Not applicable for CLI scans
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Evaluate thresholds for CLI scan results
|
|
78
|
+
*
|
|
79
|
+
* @param results - CLI scan results
|
|
80
|
+
* @param config - Threshold configuration
|
|
81
|
+
* @returns Threshold evaluation result
|
|
82
|
+
*/
|
|
83
|
+
export function evaluateCLIThresholds(
|
|
84
|
+
results: FileScanResult[],
|
|
85
|
+
config: ThresholdConfig
|
|
86
|
+
): ThresholdResult {
|
|
87
|
+
const aggregatedResults = convertToAggregatedResults(results);
|
|
88
|
+
return evaluateThresholds(aggregatedResults, config);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Print threshold evaluation result to CLI
|
|
93
|
+
*
|
|
94
|
+
* @param result - Threshold evaluation result
|
|
95
|
+
*/
|
|
96
|
+
export function printThresholdResult(result: ThresholdResult): void {
|
|
97
|
+
const output = formatCLIOutput(result);
|
|
98
|
+
console.log(output);
|
|
99
|
+
}
|