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.
Files changed (43) hide show
  1. package/README.md +254 -0
  2. package/bin/pqm.js +6 -0
  3. package/package.json +31 -0
  4. package/src/ai/analyzer/collector.js +191 -0
  5. package/src/ai/analyzer/dependency.js +269 -0
  6. package/src/ai/analyzer/index.js +234 -0
  7. package/src/ai/analyzer/quality.js +241 -0
  8. package/src/ai/analyzer/security.js +302 -0
  9. package/src/ai/index.js +16 -0
  10. package/src/ai/providers/bailian.js +121 -0
  11. package/src/ai/providers/base.js +177 -0
  12. package/src/ai/providers/deepseek.js +100 -0
  13. package/src/ai/providers/index.js +100 -0
  14. package/src/ai/providers/openai.js +100 -0
  15. package/src/builders/base.js +35 -0
  16. package/src/builders/rollup.js +47 -0
  17. package/src/builders/vite.js +47 -0
  18. package/src/cli.js +41 -0
  19. package/src/commands/ai.js +317 -0
  20. package/src/commands/build.js +24 -0
  21. package/src/commands/commit.js +68 -0
  22. package/src/commands/config.js +113 -0
  23. package/src/commands/doctor.js +146 -0
  24. package/src/commands/init.js +61 -0
  25. package/src/commands/login.js +37 -0
  26. package/src/commands/publish.js +250 -0
  27. package/src/commands/release.js +107 -0
  28. package/src/commands/scan.js +239 -0
  29. package/src/commands/status.js +129 -0
  30. package/src/commands/watch.js +170 -0
  31. package/src/commands/webhook.js +240 -0
  32. package/src/config/detector.js +82 -0
  33. package/src/config/global.js +136 -0
  34. package/src/config/loader.js +49 -0
  35. package/src/core/builder.js +88 -0
  36. package/src/index.js +5 -0
  37. package/src/logs/build.js +47 -0
  38. package/src/logs/manager.js +60 -0
  39. package/src/report/formatter.js +282 -0
  40. package/src/utils/http.js +130 -0
  41. package/src/utils/logger.js +24 -0
  42. package/src/utils/prompt.js +132 -0
  43. 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
+ };