pqm-cli 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 +254 -0
- package/bin/pqm.js +6 -0
- package/package.json +31 -0
- package/src/ai/analyzer/collector.js +191 -0
- package/src/ai/analyzer/dependency.js +269 -0
- package/src/ai/analyzer/index.js +234 -0
- package/src/ai/analyzer/quality.js +241 -0
- package/src/ai/analyzer/security.js +302 -0
- package/src/ai/index.js +16 -0
- package/src/ai/providers/bailian.js +121 -0
- package/src/ai/providers/base.js +177 -0
- package/src/ai/providers/deepseek.js +100 -0
- package/src/ai/providers/index.js +100 -0
- package/src/ai/providers/openai.js +100 -0
- package/src/builders/base.js +35 -0
- package/src/builders/rollup.js +47 -0
- package/src/builders/vite.js +47 -0
- package/src/cli.js +41 -0
- package/src/commands/ai.js +317 -0
- package/src/commands/build.js +24 -0
- package/src/commands/commit.js +68 -0
- package/src/commands/config.js +113 -0
- package/src/commands/doctor.js +146 -0
- package/src/commands/init.js +61 -0
- package/src/commands/login.js +37 -0
- package/src/commands/publish.js +250 -0
- package/src/commands/release.js +107 -0
- package/src/commands/scan.js +239 -0
- package/src/commands/status.js +129 -0
- package/src/commands/watch.js +170 -0
- package/src/commands/webhook.js +240 -0
- package/src/config/detector.js +82 -0
- package/src/config/global.js +136 -0
- package/src/config/loader.js +49 -0
- package/src/core/builder.js +88 -0
- package/src/index.js +5 -0
- package/src/logs/build.js +47 -0
- package/src/logs/manager.js +60 -0
- package/src/report/formatter.js +282 -0
- package/src/utils/http.js +130 -0
- package/src/utils/logger.js +24 -0
- package/src/utils/prompt.js +132 -0
- package/src/utils/spinner.js +134 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Severity } from '../../report/formatter.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Known vulnerable package patterns (simplified)
|
|
7
|
+
* In production, this should query an actual vulnerability database
|
|
8
|
+
*/
|
|
9
|
+
const KNOWN_VULNERABILITIES = {
|
|
10
|
+
// Example vulnerable packages - in production use npm audit or similar
|
|
11
|
+
'lodash': {
|
|
12
|
+
vulnerable: ['<4.17.21'],
|
|
13
|
+
severity: Severity.HIGH,
|
|
14
|
+
issue: '原型污染漏洞 (CVE-2021-23337)',
|
|
15
|
+
fixed: '4.17.21'
|
|
16
|
+
},
|
|
17
|
+
'event-source': {
|
|
18
|
+
vulnerable: ['<1.1.0'],
|
|
19
|
+
severity: Severity.HIGH,
|
|
20
|
+
issue: '正则表达式拒绝服务',
|
|
21
|
+
fixed: '1.1.0'
|
|
22
|
+
},
|
|
23
|
+
'minimist': {
|
|
24
|
+
vulnerable: ['<1.2.6'],
|
|
25
|
+
severity: Severity.MEDIUM,
|
|
26
|
+
issue: '原型污染漏洞',
|
|
27
|
+
fixed: '1.2.6'
|
|
28
|
+
},
|
|
29
|
+
'axios': {
|
|
30
|
+
vulnerable: ['<0.21.1'],
|
|
31
|
+
severity: Severity.HIGH,
|
|
32
|
+
issue: 'SSRF 漏洞',
|
|
33
|
+
fixed: '0.21.1'
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* License risk levels
|
|
39
|
+
*/
|
|
40
|
+
const LICENSE_RISKS = {
|
|
41
|
+
// Copyleft licenses
|
|
42
|
+
'GPL-3.0': { risk: Severity.MEDIUM, note: '强 Copyleft 许可证,需开源项目源码' },
|
|
43
|
+
'GPL-2.0': { risk: Severity.MEDIUM, note: '强 Copyleft 许可证,需开源项目源码' },
|
|
44
|
+
'AGPL-3.0': { risk: Severity.HIGH, note: '强 Copyleft,网络服务也需开源' },
|
|
45
|
+
'LGPL-3.0': { risk: Severity.LOW, note: '弱 Copyleft,链接库需遵守限制' },
|
|
46
|
+
|
|
47
|
+
// Permissive licenses (low risk)
|
|
48
|
+
'MIT': { risk: Severity.INFO, note: '宽松许可证,可自由使用' },
|
|
49
|
+
'Apache-2.0': { risk: Severity.INFO, note: '宽松许可证,可自由使用' },
|
|
50
|
+
'BSD-2-Clause': { risk: Severity.INFO, note: '宽松许可证,可自由使用' },
|
|
51
|
+
'BSD-3-Clause': { risk: Severity.INFO, note: '宽松许可证,可自由使用' },
|
|
52
|
+
'ISC': { risk: Severity.INFO, note: '宽松许可证,可自由使用' },
|
|
53
|
+
|
|
54
|
+
// Unknown or custom
|
|
55
|
+
'UNLICENSED': { risk: Severity.HIGH, note: '未授权,禁止使用' },
|
|
56
|
+
'SEE LICENSE': { risk: Severity.MEDIUM, note: '需查看具体许可证' },
|
|
57
|
+
'CUSTOM': { risk: Severity.MEDIUM, note: '自定义许可证,需审核' }
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Analyze package.json dependencies
|
|
62
|
+
* @param {string} projectPath - Project root path
|
|
63
|
+
* @returns {Array<Object>} Dependency issues
|
|
64
|
+
*/
|
|
65
|
+
export function analyzeDependencies(projectPath) {
|
|
66
|
+
const issues = [];
|
|
67
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
70
|
+
return [{
|
|
71
|
+
severity: Severity.INFO,
|
|
72
|
+
type: '无 package.json',
|
|
73
|
+
line: 1,
|
|
74
|
+
message: '未找到 package.json 文件',
|
|
75
|
+
suggestion: '如果是 Node.js 项目,请创建 package.json'
|
|
76
|
+
}];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
81
|
+
const dependencies = {
|
|
82
|
+
...packageJson.dependencies,
|
|
83
|
+
...packageJson.devDependencies,
|
|
84
|
+
...packageJson.peerDependencies,
|
|
85
|
+
...packageJson.optionalDependencies
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
Object.entries(dependencies).forEach(([name, version]) => {
|
|
89
|
+
// Clean version string
|
|
90
|
+
const cleanVersion = version.replace(/^[\^~>=<]+/, '');
|
|
91
|
+
|
|
92
|
+
// Check for known vulnerabilities
|
|
93
|
+
const vulnInfo = KNOWN_VULNERABILITIES[name];
|
|
94
|
+
if (vulnInfo) {
|
|
95
|
+
issues.push({
|
|
96
|
+
severity: vulnInfo.severity,
|
|
97
|
+
type: '安全漏洞',
|
|
98
|
+
package: name,
|
|
99
|
+
version,
|
|
100
|
+
message: `${name} ${version}: ${vulnInfo.issue}`,
|
|
101
|
+
suggestion: `升级到 ${vulnInfo.fixed} 或更高版本`
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check for deprecated versions
|
|
106
|
+
if (version === '*' || version === 'latest') {
|
|
107
|
+
issues.push({
|
|
108
|
+
severity: Severity.LOW,
|
|
109
|
+
type: '版本锁定',
|
|
110
|
+
package: name,
|
|
111
|
+
version,
|
|
112
|
+
message: `依赖 ${name} 使用 ${version} 版本,可能导致不稳定`,
|
|
113
|
+
suggestion: '锁定具体版本号'
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check for empty version
|
|
118
|
+
if (version === '' || version === '0.0.0') {
|
|
119
|
+
issues.push({
|
|
120
|
+
severity: Severity.MEDIUM,
|
|
121
|
+
type: '无效版本',
|
|
122
|
+
package: name,
|
|
123
|
+
version,
|
|
124
|
+
message: `依赖 ${name} 版本号无效`,
|
|
125
|
+
suggestion: '指定有效的版本号'
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Check for outdated packages by checking package-lock.json
|
|
131
|
+
const lockPath = path.join(projectPath, 'package-lock.json');
|
|
132
|
+
if (!fs.existsSync(lockPath) && Object.keys(packageJson.dependencies || {}).length > 0) {
|
|
133
|
+
issues.push({
|
|
134
|
+
severity: Severity.INFO,
|
|
135
|
+
type: '无 package-lock.json',
|
|
136
|
+
package: '',
|
|
137
|
+
version: '',
|
|
138
|
+
message: '缺少 package-lock.json,可能导致依赖版本不一致',
|
|
139
|
+
suggestion: '运行 npm install 生成 package-lock.json'
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check for security audit availability
|
|
144
|
+
// This is informational, not an issue
|
|
145
|
+
if (Object.keys(packageJson.dependencies || {}).length > 50) {
|
|
146
|
+
issues.push({
|
|
147
|
+
severity: Severity.INFO,
|
|
148
|
+
type: '依赖数量',
|
|
149
|
+
package: '',
|
|
150
|
+
version: '',
|
|
151
|
+
message: `项目依赖较多 (${Object.keys(dependencies).length} 个),建议定期审计`,
|
|
152
|
+
suggestion: '定期运行 npm audit 检查安全漏洞'
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
} catch (error) {
|
|
157
|
+
issues.push({
|
|
158
|
+
severity: Severity.MEDIUM,
|
|
159
|
+
type: '解析错误',
|
|
160
|
+
package: '',
|
|
161
|
+
version: '',
|
|
162
|
+
message: `无法解析 package.json: ${error.message}`,
|
|
163
|
+
suggestion: '检查 package.json 格式是否正确'
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return issues;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Analyze license compliance
|
|
172
|
+
* @param {string} projectPath - Project root path
|
|
173
|
+
* @returns {Array<Object>} License issues
|
|
174
|
+
*/
|
|
175
|
+
export function analyzeLicenses(projectPath) {
|
|
176
|
+
const issues = [];
|
|
177
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
178
|
+
|
|
179
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
180
|
+
return issues;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
185
|
+
const ownLicense = packageJson.license || 'UNLICENSED';
|
|
186
|
+
|
|
187
|
+
// Check project's own license
|
|
188
|
+
const licenseInfo = LICENSE_RISKS[ownLicense] || LICENSE_RISKS.CUSTOM;
|
|
189
|
+
if (licenseInfo.risk !== Severity.INFO) {
|
|
190
|
+
issues.push({
|
|
191
|
+
severity: licenseInfo.risk,
|
|
192
|
+
type: '许可证风险',
|
|
193
|
+
package: '(项目)',
|
|
194
|
+
version: '',
|
|
195
|
+
message: `项目许可证: ${ownLicense} - ${licenseInfo.note}`,
|
|
196
|
+
suggestion: '确保许可证符合项目需求'
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// If there's a license file, check it
|
|
201
|
+
const licenseFiles = ['LICENSE', 'LICENSE.md', 'license', 'license.txt'];
|
|
202
|
+
for (const file of licenseFiles) {
|
|
203
|
+
const licensePath = path.join(projectPath, file);
|
|
204
|
+
if (fs.existsSync(licensePath)) {
|
|
205
|
+
const content = fs.readFileSync(licensePath, 'utf-8');
|
|
206
|
+
if (content.length < 10) {
|
|
207
|
+
issues.push({
|
|
208
|
+
severity: Severity.MEDIUM,
|
|
209
|
+
type: '许可证文件',
|
|
210
|
+
package: '',
|
|
211
|
+
version: '',
|
|
212
|
+
message: 'LICENSE 文件内容过短或为空',
|
|
213
|
+
suggestion: '添加完整的许可证文本'
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
} catch (error) {
|
|
221
|
+
issues.push({
|
|
222
|
+
severity: Severity.LOW,
|
|
223
|
+
type: '许可证解析错误',
|
|
224
|
+
package: '',
|
|
225
|
+
version: '',
|
|
226
|
+
message: `无法检查许可证: ${error.message}`,
|
|
227
|
+
suggestion: '检查 package.json 中的 license 字段'
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return issues;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Run dependency analysis
|
|
236
|
+
* @param {string} projectPath - Project root path
|
|
237
|
+
* @param {Object} options - Analysis options
|
|
238
|
+
* @returns {Promise<Array<Object>>} Dependency issues
|
|
239
|
+
*/
|
|
240
|
+
export async function runDependencyAnalysis(projectPath, options = {}) {
|
|
241
|
+
const issues = [];
|
|
242
|
+
|
|
243
|
+
// Run pattern-based analysis
|
|
244
|
+
issues.push(...analyzeDependencies(projectPath));
|
|
245
|
+
issues.push(...analyzeLicenses(projectPath));
|
|
246
|
+
|
|
247
|
+
// Sort by severity
|
|
248
|
+
const severityOrder = {
|
|
249
|
+
[Severity.CRITICAL]: 0,
|
|
250
|
+
[Severity.HIGH]: 1,
|
|
251
|
+
[Severity.MEDIUM]: 2,
|
|
252
|
+
[Severity.LOW]: 3,
|
|
253
|
+
[Severity.INFO]: 4
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
issues.sort((a, b) => {
|
|
257
|
+
return (severityOrder[a.severity] || 5) - (severityOrder[b.severity] || 5);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return issues;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export default {
|
|
264
|
+
analyzeDependencies,
|
|
265
|
+
analyzeLicenses,
|
|
266
|
+
runDependencyAnalysis,
|
|
267
|
+
KNOWN_VULNERABILITIES,
|
|
268
|
+
LICENSE_RISKS
|
|
269
|
+
};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { runSecurityAnalysis } from './security.js';
|
|
2
|
+
import { runQualityAnalysis } from './quality.js';
|
|
3
|
+
import { runDependencyAnalysis } from './dependency.js';
|
|
4
|
+
import { Severity } from '../../report/formatter.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Analysis types
|
|
8
|
+
*/
|
|
9
|
+
export const AnalysisType = {
|
|
10
|
+
SECURITY: 'security',
|
|
11
|
+
QUALITY: 'quality',
|
|
12
|
+
DEPENDENCY: 'dependency',
|
|
13
|
+
FULL: 'full'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create analysis engine
|
|
18
|
+
* @param {Object} options - Engine options
|
|
19
|
+
* @returns {Object} Analysis engine instance
|
|
20
|
+
*/
|
|
21
|
+
export function createAnalyzer(options = {}) {
|
|
22
|
+
const {
|
|
23
|
+
provider,
|
|
24
|
+
useAI = false,
|
|
25
|
+
projectPath = process.cwd()
|
|
26
|
+
} = options;
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
provider,
|
|
30
|
+
useAI,
|
|
31
|
+
projectPath,
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Analyze a single file
|
|
35
|
+
* @param {string} filePath - File path
|
|
36
|
+
* @param {string} content - File content
|
|
37
|
+
* @param {string} type - Analysis type
|
|
38
|
+
* @returns {Promise<Array<Object>>} Issues
|
|
39
|
+
*/
|
|
40
|
+
async analyzeFile(filePath, content, type = AnalysisType.FULL) {
|
|
41
|
+
const language = detectLanguage(filePath);
|
|
42
|
+
const issues = [];
|
|
43
|
+
|
|
44
|
+
if (type === AnalysisType.SECURITY || type === AnalysisType.FULL) {
|
|
45
|
+
issues.push(...await runSecurityAnalysis(content, language, {
|
|
46
|
+
useAI: this.useAI,
|
|
47
|
+
provider: this.provider
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (type === AnalysisType.QUALITY || type === AnalysisType.FULL) {
|
|
52
|
+
issues.push(...await runQualityAnalysis(content, language, {
|
|
53
|
+
useAI: this.useAI,
|
|
54
|
+
provider: this.provider
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return issues;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Analyze dependencies
|
|
63
|
+
* @param {string} projectPath - Project path
|
|
64
|
+
* @returns {Promise<Array<Object>>} Issues
|
|
65
|
+
*/
|
|
66
|
+
async analyzeDependency(projectPath) {
|
|
67
|
+
return await runDependencyAnalysis(projectPath || this.projectPath);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Analyze multiple files
|
|
72
|
+
* @param {Array<Object>} files - Files to analyze [{path, content}]
|
|
73
|
+
* @param {string} type - Analysis type
|
|
74
|
+
* @returns {Promise<Object>} Analysis report
|
|
75
|
+
*/
|
|
76
|
+
async analyzeFiles(files, type = AnalysisType.FULL) {
|
|
77
|
+
const allIssues = [];
|
|
78
|
+
const byFile = {};
|
|
79
|
+
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
const fileIssues = await this.analyzeFile(file.path, file.content, type);
|
|
82
|
+
|
|
83
|
+
if (fileIssues.length > 0) {
|
|
84
|
+
byFile[file.path] = fileIssues;
|
|
85
|
+
allIssues.push(...fileIssues);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return this.createReport(allIssues, byFile);
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Run full project analysis
|
|
94
|
+
* @param {string} type - Analysis type
|
|
95
|
+
* @returns {Promise<Object>} Analysis report
|
|
96
|
+
*/
|
|
97
|
+
async runAnalysis(type = AnalysisType.FULL) {
|
|
98
|
+
const { collectFiles } = await import('./collector.js');
|
|
99
|
+
const files = await collectFiles(this.projectPath);
|
|
100
|
+
|
|
101
|
+
let report = {
|
|
102
|
+
summary: { critical: 0, high: 0, medium: 0, low: 0, info: 0 },
|
|
103
|
+
issues: [],
|
|
104
|
+
byFile: {}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Analyze code files
|
|
108
|
+
if (type === AnalysisType.SECURITY ||
|
|
109
|
+
type === AnalysisType.QUALITY ||
|
|
110
|
+
type === AnalysisType.FULL) {
|
|
111
|
+
const codeReport = await this.analyzeFiles(files, type);
|
|
112
|
+
report = combineReports(report, codeReport);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Analyze dependencies
|
|
116
|
+
if (type === AnalysisType.DEPENDENCY || type === AnalysisType.FULL) {
|
|
117
|
+
const depIssues = await this.analyzeDependency();
|
|
118
|
+
if (depIssues.length > 0) {
|
|
119
|
+
report.byFile['package.json'] = depIssues;
|
|
120
|
+
report.issues.push(...depIssues);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Calculate summary
|
|
125
|
+
report.summary = calculateSummary(report.issues);
|
|
126
|
+
|
|
127
|
+
return report;
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create analysis report
|
|
132
|
+
* @param {Array} issues - All issues
|
|
133
|
+
* @param {Object} byFile - Issues grouped by file
|
|
134
|
+
* @returns {Object} Report object
|
|
135
|
+
*/
|
|
136
|
+
createReport(issues, byFile) {
|
|
137
|
+
return {
|
|
138
|
+
summary: calculateSummary(issues),
|
|
139
|
+
issues,
|
|
140
|
+
byFile,
|
|
141
|
+
timestamp: new Date().toISOString()
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Detect programming language from file path
|
|
149
|
+
* @param {string} filePath - File path
|
|
150
|
+
* @returns {string} Language name
|
|
151
|
+
*/
|
|
152
|
+
function detectLanguage(filePath) {
|
|
153
|
+
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
154
|
+
|
|
155
|
+
const languageMap = {
|
|
156
|
+
js: 'javascript',
|
|
157
|
+
jsx: 'javascript',
|
|
158
|
+
ts: 'typescript',
|
|
159
|
+
tsx: 'typescript',
|
|
160
|
+
vue: 'vue',
|
|
161
|
+
py: 'python',
|
|
162
|
+
rb: 'ruby',
|
|
163
|
+
go: 'go',
|
|
164
|
+
java: 'java',
|
|
165
|
+
kt: 'kotlin',
|
|
166
|
+
rs: 'rust',
|
|
167
|
+
c: 'c',
|
|
168
|
+
cpp: 'cpp',
|
|
169
|
+
h: 'c',
|
|
170
|
+
hpp: 'cpp',
|
|
171
|
+
php: 'php',
|
|
172
|
+
cs: 'csharp',
|
|
173
|
+
swift: 'swift',
|
|
174
|
+
m: 'objectivec',
|
|
175
|
+
sh: 'shell',
|
|
176
|
+
bash: 'shell',
|
|
177
|
+
sql: 'sql',
|
|
178
|
+
html: 'html',
|
|
179
|
+
css: 'css',
|
|
180
|
+
scss: 'scss',
|
|
181
|
+
less: 'less',
|
|
182
|
+
json: 'json',
|
|
183
|
+
yaml: 'yaml',
|
|
184
|
+
yml: 'yaml',
|
|
185
|
+
md: 'markdown'
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return languageMap[ext] || 'unknown';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Calculate issue summary
|
|
193
|
+
* @param {Array} issues - Issues array
|
|
194
|
+
* @returns {Object} Summary object
|
|
195
|
+
*/
|
|
196
|
+
function calculateSummary(issues) {
|
|
197
|
+
const summary = {
|
|
198
|
+
critical: 0,
|
|
199
|
+
high: 0,
|
|
200
|
+
medium: 0,
|
|
201
|
+
low: 0,
|
|
202
|
+
info: 0
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
for (const issue of issues) {
|
|
206
|
+
const sev = issue.severity?.toLowerCase();
|
|
207
|
+
if (sev in summary) {
|
|
208
|
+
summary[sev]++;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return summary;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Combine two reports
|
|
217
|
+
* @param {Object} target - Target report
|
|
218
|
+
* @param {Object} source - Source report
|
|
219
|
+
* @returns {Object} Combined report
|
|
220
|
+
*/
|
|
221
|
+
function combineReports(target, source) {
|
|
222
|
+
return {
|
|
223
|
+
summary: calculateSummary([...target.issues, ...source.issues]),
|
|
224
|
+
issues: [...target.issues, ...source.issues],
|
|
225
|
+
byFile: { ...target.byFile, ...source.byFile },
|
|
226
|
+
timestamp: new Date().toISOString()
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export default {
|
|
231
|
+
createAnalyzer,
|
|
232
|
+
AnalysisType,
|
|
233
|
+
detectLanguage
|
|
234
|
+
};
|