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,177 @@
1
+ /**
2
+ * Base AI Provider class
3
+ * All AI providers must extend this class
4
+ */
5
+ export class BaseAIProvider {
6
+ constructor(config = {}) {
7
+ this.config = config;
8
+ this.apiKey = config.apiKey;
9
+ this.model = config.model || this.getDefaultModel();
10
+ this.endpoint = config.endpoint || this.getDefaultEndpoint();
11
+ }
12
+
13
+ /**
14
+ * Get default model for this provider
15
+ * @returns {string} Default model name
16
+ */
17
+ getDefaultModel() {
18
+ throw new Error('Method getDefaultModel() must be implemented');
19
+ }
20
+
21
+ /**
22
+ * Get default API endpoint for this provider
23
+ * @returns {string} Default endpoint URL
24
+ */
25
+ getDefaultEndpoint() {
26
+ throw new Error('Method getDefaultEndpoint() must be implemented');
27
+ }
28
+
29
+ /**
30
+ * Send a chat message to the AI
31
+ * @param {string} prompt - Prompt to send
32
+ * @param {Object} options - Additional options
33
+ * @returns {Promise<string>} AI response
34
+ */
35
+ async chat(prompt, options = {}) {
36
+ throw new Error('Method chat() must be implemented');
37
+ }
38
+
39
+ /**
40
+ * Analyze code for security issues
41
+ * @param {string} code - Code to analyze
42
+ * @param {string} language - Programming language
43
+ * @param {Object} options - Additional options
44
+ * @returns {Promise<Object>} Analysis result
45
+ */
46
+ async analyzeCode(code, language, options = {}) {
47
+ const prompt = this.buildAnalysisPrompt(code, language, options);
48
+ const response = await this.chat(prompt, options);
49
+
50
+ try {
51
+ // Try to parse JSON response
52
+ return JSON.parse(response);
53
+ } catch {
54
+ // If not JSON, return as text
55
+ return {
56
+ raw: true,
57
+ analysis: response
58
+ };
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Build analysis prompt
64
+ * @param {string} code - Code to analyze
65
+ * @param {string} language - Programming language
66
+ * @param {Object} options - Additional options
67
+ * @returns {string} Prompt string
68
+ */
69
+ buildAnalysisPrompt(code, language, options) {
70
+ const analysisType = options.type || 'security';
71
+
72
+ const prompts = {
73
+ security: `请分析以下 ${language} 代码的安全漏洞,重点关注:
74
+ 1. XSS (跨站脚本攻击)
75
+ 2. SQL注入
76
+ 3. 命令注入
77
+ 4. eval 使用
78
+ 5. 硬编码密钥
79
+ 6. 路径遍历
80
+ 7. 不安全的反序列化
81
+
82
+ 代码:
83
+ \`\`\`${language}
84
+ ${code}
85
+ \`\`\`
86
+
87
+ 请以JSON格式返回结果:
88
+ {
89
+ "issues": [
90
+ {
91
+ "severity": "critical|high|medium|low|info",
92
+ "type": "漏洞类型",
93
+ "line": 行号,
94
+ "message": "问题描述",
95
+ "suggestion": "修复建议"
96
+ }
97
+ ]
98
+ }`,
99
+
100
+ quality: `请分析以下 ${language} 代码的代码质量,重点关注:
101
+ 1. 代码复杂度
102
+ 2. 命名规范
103
+ 3. 重复代码
104
+ 4. TODO/FIXME 标记
105
+ 5. 最佳实践
106
+
107
+ 代码:
108
+ \`\`\`${language}
109
+ ${code}
110
+ \`\`\`
111
+
112
+ 请以JSON格式返回结果:
113
+ {
114
+ "issues": [
115
+ {
116
+ "severity": "info|low|medium",
117
+ "type": "问题类型",
118
+ "line": 行号,
119
+ "message": "问题描述",
120
+ "suggestion": "改进建议"
121
+ }
122
+ ]
123
+ }`,
124
+
125
+ dependency: `请分析以下依赖项的安全风险:
126
+ ${code}
127
+
128
+ 请以JSON格式返回结果:
129
+ {
130
+ "issues": [
131
+ {
132
+ "severity": "critical|high|medium|low|info",
133
+ "type": "风险类型",
134
+ "package": "包名",
135
+ "version": "版本",
136
+ "message": "问题描述",
137
+ "suggestion": "建议"
138
+ }
139
+ ]
140
+ }`
141
+ };
142
+
143
+ return prompts[analysisType] || prompts.security;
144
+ }
145
+
146
+ /**
147
+ * Validate provider configuration
148
+ * @returns {Promise<boolean>} True if configuration is valid
149
+ */
150
+ async validateConfig() {
151
+ if (!this.apiKey) {
152
+ return false;
153
+ }
154
+
155
+ try {
156
+ await this.chat('Hello', { maxTokens: 10 });
157
+ return true;
158
+ } catch {
159
+ return false;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Get provider information
165
+ * @returns {Object} Provider info
166
+ */
167
+ getInfo() {
168
+ return {
169
+ name: this.constructor.name.replace('Provider', '').toLowerCase(),
170
+ model: this.model,
171
+ endpoint: this.endpoint,
172
+ configured: !!this.apiKey
173
+ };
174
+ }
175
+ }
176
+
177
+ export default BaseAIProvider;
@@ -0,0 +1,100 @@
1
+ import { BaseAIProvider } from './base.js';
2
+ import { post } from '../../utils/http.js';
3
+
4
+ /**
5
+ * DeepSeek Provider
6
+ * API Documentation: https://platform.deepseek.com/api-docs/
7
+ */
8
+ export class DeepSeekProvider extends BaseAIProvider {
9
+ constructor(config = {}) {
10
+ super(config);
11
+ this.endpoint = config.endpoint || 'https://api.deepseek.com/v1';
12
+ this.model = config.model || 'deepseek-chat';
13
+ }
14
+
15
+ getDefaultModel() {
16
+ return 'deepseek-chat';
17
+ }
18
+
19
+ getDefaultEndpoint() {
20
+ return 'https://api.deepseek.com/v1';
21
+ }
22
+
23
+ async chat(prompt, options = {}) {
24
+ const url = `${this.endpoint}/chat/completions`;
25
+
26
+ const body = {
27
+ model: this.model,
28
+ messages: [
29
+ {
30
+ role: 'user',
31
+ content: prompt
32
+ }
33
+ ],
34
+ max_tokens: options.maxTokens || 4096,
35
+ temperature: options.temperature || 0.7
36
+ };
37
+
38
+ const headers = {
39
+ 'Authorization': `Bearer ${this.apiKey}`,
40
+ 'Content-Type': 'application/json'
41
+ };
42
+
43
+ try {
44
+ const response = await post(url, body, headers, 60000);
45
+
46
+ if (response.choices?.[0]?.message?.content) {
47
+ return response.choices[0].message.content;
48
+ }
49
+
50
+ throw new Error('Unexpected response format from DeepSeek API');
51
+ } catch (error) {
52
+ throw new Error(`DeepSeek API error: ${error.message}`);
53
+ }
54
+ }
55
+
56
+ async validateConfig() {
57
+ if (!this.apiKey) {
58
+ return false;
59
+ }
60
+
61
+ // Basic API key format check for DeepSeek
62
+ if (!this.apiKey.startsWith('sk-')) {
63
+ return false;
64
+ }
65
+
66
+ try {
67
+ // Try a minimal request to validate
68
+ const url = `${this.endpoint}/chat/completions`;
69
+ const body = {
70
+ model: this.model,
71
+ messages: [{ role: 'user', content: 'Hi' }],
72
+ max_tokens: 10
73
+ };
74
+
75
+ const headers = {
76
+ 'Authorization': `Bearer ${this.apiKey}`,
77
+ 'Content-Type': 'application/json'
78
+ };
79
+
80
+ await post(url, body, headers, 10000);
81
+ return true;
82
+ } catch (error) {
83
+ return !error.message.includes('HTTP 401') && !error.message.includes('HTTP 403');
84
+ }
85
+ }
86
+
87
+ getInfo() {
88
+ return {
89
+ name: 'deepseek',
90
+ displayName: 'DeepSeek',
91
+ model: this.model,
92
+ models: ['deepseek-chat', 'deepseek-coder'],
93
+ endpoint: this.endpoint,
94
+ configured: !!this.apiKey,
95
+ free: false
96
+ };
97
+ }
98
+ }
99
+
100
+ export default DeepSeekProvider;
@@ -0,0 +1,100 @@
1
+ import { BailianProvider } from './bailian.js';
2
+ import { OpenAIProvider } from './openai.js';
3
+ import { DeepSeekProvider } from './deepseek.js';
4
+
5
+ /**
6
+ * Supported providers
7
+ */
8
+ const providers = {
9
+ bailian: {
10
+ name: 'bailian',
11
+ displayName: '阿里云百炼',
12
+ description: '阿里云大模型平台,提供免费额度',
13
+ Provider: BailianProvider,
14
+ defaultModel: 'qwen-turbo',
15
+ models: ['qwen-turbo', 'qwen-plus', 'qwen-max', 'qwen-max-longcontext'],
16
+ website: 'https://dashscope.console.aliyun.com/',
17
+ free: true
18
+ },
19
+ openai: {
20
+ name: 'openai',
21
+ displayName: 'OpenAI',
22
+ description: 'OpenAI GPT 系列模型',
23
+ Provider: OpenAIProvider,
24
+ defaultModel: 'gpt-4o-mini',
25
+ models: ['gpt-4o-mini', 'gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo'],
26
+ website: 'https://platform.openai.com/',
27
+ free: false
28
+ },
29
+ deepseek: {
30
+ name: 'deepseek',
31
+ displayName: 'DeepSeek',
32
+ description: 'DeepSeek 深度求索模型',
33
+ Provider: DeepSeekProvider,
34
+ defaultModel: 'deepseek-chat',
35
+ models: ['deepseek-chat', 'deepseek-coder'],
36
+ website: 'https://platform.deepseek.com/',
37
+ free: false
38
+ }
39
+ };
40
+
41
+ /**
42
+ * Create a provider instance
43
+ * @param {string} name - Provider name
44
+ * @param {Object} config - Provider configuration
45
+ * @returns {Object} Provider instance
46
+ */
47
+ export function createProvider(name, config = {}) {
48
+ const providerInfo = providers[name.toLowerCase()];
49
+
50
+ if (!providerInfo) {
51
+ throw new Error(`Unknown provider: ${name}. Supported: ${Object.keys(providers).join(', ')}`);
52
+ }
53
+
54
+ const ProviderClass = providerInfo.Provider;
55
+ return new ProviderClass({
56
+ ...config,
57
+ model: config.model || providerInfo.defaultModel
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Get list of supported providers
63
+ * @returns {Array<Object>} Provider list
64
+ */
65
+ export function getSupportedProviders() {
66
+ return Object.entries(providers).map(([key, info]) => ({
67
+ name: key,
68
+ displayName: info.displayName,
69
+ description: info.description,
70
+ defaultModel: info.defaultModel,
71
+ models: info.models,
72
+ website: info.website,
73
+ free: info.free
74
+ }));
75
+ }
76
+
77
+ /**
78
+ * Check if a provider is supported
79
+ * @param {string} name - Provider name
80
+ * @returns {boolean} True if supported
81
+ */
82
+ export function isProviderSupported(name) {
83
+ return name.toLowerCase() in providers;
84
+ }
85
+
86
+ /**
87
+ * Get provider info
88
+ * @param {string} name - Provider name
89
+ * @returns {Object|null} Provider info
90
+ */
91
+ export function getProviderInfo(name) {
92
+ return providers[name.toLowerCase()] || null;
93
+ }
94
+
95
+ export default {
96
+ createProvider,
97
+ getSupportedProviders,
98
+ isProviderSupported,
99
+ getProviderInfo
100
+ };
@@ -0,0 +1,100 @@
1
+ import { BaseAIProvider } from './base.js';
2
+ import { post } from '../../utils/http.js';
3
+
4
+ /**
5
+ * OpenAI Provider
6
+ * API Documentation: https://platform.openai.com/docs/api-reference
7
+ */
8
+ export class OpenAIProvider extends BaseAIProvider {
9
+ constructor(config = {}) {
10
+ super(config);
11
+ this.endpoint = config.endpoint || 'https://api.openai.com/v1';
12
+ this.model = config.model || 'gpt-4o-mini';
13
+ }
14
+
15
+ getDefaultModel() {
16
+ return 'gpt-4o-mini';
17
+ }
18
+
19
+ getDefaultEndpoint() {
20
+ return 'https://api.openai.com/v1';
21
+ }
22
+
23
+ async chat(prompt, options = {}) {
24
+ const url = `${this.endpoint}/chat/completions`;
25
+
26
+ const body = {
27
+ model: this.model,
28
+ messages: [
29
+ {
30
+ role: 'user',
31
+ content: prompt
32
+ }
33
+ ],
34
+ max_tokens: options.maxTokens || 4096,
35
+ temperature: options.temperature || 0.7
36
+ };
37
+
38
+ const headers = {
39
+ 'Authorization': `Bearer ${this.apiKey}`,
40
+ 'Content-Type': 'application/json'
41
+ };
42
+
43
+ try {
44
+ const response = await post(url, body, headers, 60000);
45
+
46
+ if (response.choices?.[0]?.message?.content) {
47
+ return response.choices[0].message.content;
48
+ }
49
+
50
+ throw new Error('Unexpected response format from OpenAI API');
51
+ } catch (error) {
52
+ throw new Error(`OpenAI API error: ${error.message}`);
53
+ }
54
+ }
55
+
56
+ async validateConfig() {
57
+ if (!this.apiKey) {
58
+ return false;
59
+ }
60
+
61
+ // Basic API key format check for OpenAI
62
+ if (!this.apiKey.startsWith('sk-')) {
63
+ return false;
64
+ }
65
+
66
+ try {
67
+ // Try a minimal request to validate
68
+ const url = `${this.endpoint}/chat/completions`;
69
+ const body = {
70
+ model: this.model,
71
+ messages: [{ role: 'user', content: 'Hi' }],
72
+ max_tokens: 10
73
+ };
74
+
75
+ const headers = {
76
+ 'Authorization': `Bearer ${this.apiKey}`,
77
+ 'Content-Type': 'application/json'
78
+ };
79
+
80
+ await post(url, body, headers, 10000);
81
+ return true;
82
+ } catch (error) {
83
+ return !error.message.includes('HTTP 401') && !error.message.includes('HTTP 403');
84
+ }
85
+ }
86
+
87
+ getInfo() {
88
+ return {
89
+ name: 'openai',
90
+ displayName: 'OpenAI',
91
+ model: this.model,
92
+ models: ['gpt-4o-mini', 'gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo'],
93
+ endpoint: this.endpoint,
94
+ configured: !!this.apiKey,
95
+ free: false
96
+ };
97
+ }
98
+ }
99
+
100
+ export default OpenAIProvider;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Base builder interface
3
+ */
4
+ export class BaseBuilder {
5
+ constructor(options = {}) {
6
+ this.options = options;
7
+ this.name = 'base';
8
+ }
9
+
10
+ async build(context) {
11
+ throw new Error('build() must be implemented by subclass');
12
+ }
13
+
14
+ async incrementalBuild(changedFiles, context) {
15
+ throw new Error('incrementalBuild() must be implemented by subclass');
16
+ }
17
+
18
+ getOutputFiles() {
19
+ return [];
20
+ }
21
+
22
+ supportsIncremental() {
23
+ return false;
24
+ }
25
+
26
+ log(message) {
27
+ console.log(`[${this.name}] ${message}`);
28
+ }
29
+
30
+ error(message) {
31
+ console.error(`[${this.name}] ERROR: ${message}`);
32
+ }
33
+ }
34
+
35
+ export default BaseBuilder;
@@ -0,0 +1,47 @@
1
+ import { spawn } from 'child_process';
2
+ import BaseBuilder from './base.js';
3
+
4
+ export class RollupBuilder extends BaseBuilder {
5
+ constructor(options = {}) {
6
+ super(options);
7
+ this.name = 'rollup';
8
+ }
9
+
10
+ supportsIncremental() {
11
+ return true;
12
+ }
13
+
14
+ async build(context) {
15
+ this.log('Starting Rollup build...');
16
+
17
+ return new Promise((resolve, reject) => {
18
+ const child = spawn('npx', ['rollup', '-c'], {
19
+ cwd: process.cwd(),
20
+ stdio: 'inherit',
21
+ shell: true
22
+ });
23
+
24
+ child.on('close', (code) => {
25
+ if (code === 0) {
26
+ this.log('Build completed successfully');
27
+ resolve({ success: true });
28
+ } else {
29
+ reject(new Error(`Build failed with code ${code}`));
30
+ }
31
+ });
32
+
33
+ child.on('error', (err) => {
34
+ reject(err);
35
+ });
36
+ });
37
+ }
38
+
39
+ async incrementalBuild(changedFiles, context) {
40
+ // Rollup has better support for incremental builds
41
+ // through watch mode or caching
42
+ this.log(`Incremental build for ${changedFiles.length} files`);
43
+ return this.build(context);
44
+ }
45
+ }
46
+
47
+ export default RollupBuilder;
@@ -0,0 +1,47 @@
1
+ import { spawn } from 'child_process';
2
+ import BaseBuilder from './base.js';
3
+
4
+ export class ViteBuilder extends BaseBuilder {
5
+ constructor(options = {}) {
6
+ super(options);
7
+ this.name = 'vite';
8
+ }
9
+
10
+ supportsIncremental() {
11
+ return true;
12
+ }
13
+
14
+ async build(context) {
15
+ this.log('Starting Vite build...');
16
+
17
+ return new Promise((resolve, reject) => {
18
+ const child = spawn('npx', ['vite', 'build'], {
19
+ cwd: process.cwd(),
20
+ stdio: 'inherit',
21
+ shell: true
22
+ });
23
+
24
+ child.on('close', (code) => {
25
+ if (code === 0) {
26
+ this.log('Build completed successfully');
27
+ resolve({ success: true });
28
+ } else {
29
+ reject(new Error(`Build failed with code ${code}`));
30
+ }
31
+ });
32
+
33
+ child.on('error', (err) => {
34
+ reject(err);
35
+ });
36
+ });
37
+ }
38
+
39
+ async incrementalBuild(changedFiles, context) {
40
+ // Vite doesn't have native incremental build,
41
+ // but we can use the build with modified config
42
+ this.log(`Incremental build for ${changedFiles.length} files`);
43
+ return this.build(context);
44
+ }
45
+ }
46
+
47
+ export default ViteBuilder;
package/src/cli.js ADDED
@@ -0,0 +1,41 @@
1
+ import watch from './commands/watch.js';
2
+ import build from './commands/build.js';
3
+ import init from './commands/init.js';
4
+ import config from './commands/config.js';
5
+ import doctor from './commands/doctor.js';
6
+ import commit from './commands/commit.js';
7
+ import release from './commands/release.js';
8
+ import publish from './commands/publish.js';
9
+ import webhook from './commands/webhook.js';
10
+ import status from './commands/status.js';
11
+ import login from './commands/login.js';
12
+ import scan from './commands/scan.js';
13
+ import ai from './commands/ai.js';
14
+
15
+ export function registerCommands(program) {
16
+ program
17
+ .name('pqm')
18
+ .description('Package Quality Monitor CLI - 文件监控、构建、发布一体化工具')
19
+ .version('1.0.0');
20
+
21
+ // 开发流程命令
22
+ watch(program);
23
+ build(program);
24
+ commit(program);
25
+ release(program);
26
+
27
+ // 发布命令
28
+ login(program);
29
+ publish(program);
30
+ webhook(program);
31
+
32
+ // 工具命令
33
+ init(program);
34
+ config(program);
35
+ doctor(program);
36
+ status(program);
37
+
38
+ // AI 驱动分析命令
39
+ scan(program);
40
+ ai(program);
41
+ }