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,130 @@
|
|
|
1
|
+
import https from 'https';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Make an HTTP/HTTPS request
|
|
6
|
+
* @param {Object} options - Request options
|
|
7
|
+
* @returns {Promise<Object>} Response object with data and status
|
|
8
|
+
*/
|
|
9
|
+
export function request(options) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const {
|
|
12
|
+
url,
|
|
13
|
+
method = 'GET',
|
|
14
|
+
headers = {},
|
|
15
|
+
body = null,
|
|
16
|
+
timeout = 30000
|
|
17
|
+
} = options;
|
|
18
|
+
|
|
19
|
+
// Parse URL
|
|
20
|
+
const urlObj = new URL(url);
|
|
21
|
+
const isHttps = urlObj.protocol === 'https:';
|
|
22
|
+
const client = isHttps ? https : http;
|
|
23
|
+
|
|
24
|
+
const reqOptions = {
|
|
25
|
+
hostname: urlObj.hostname,
|
|
26
|
+
port: urlObj.port || (isHttps ? 443 : 80),
|
|
27
|
+
path: urlObj.pathname + urlObj.search,
|
|
28
|
+
method: method.toUpperCase(),
|
|
29
|
+
headers: {
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
...headers
|
|
32
|
+
},
|
|
33
|
+
timeout
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const req = client.request(reqOptions, (res) => {
|
|
37
|
+
let data = '';
|
|
38
|
+
|
|
39
|
+
res.on('data', (chunk) => {
|
|
40
|
+
data += chunk;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
res.on('end', () => {
|
|
44
|
+
resolve({
|
|
45
|
+
status: res.statusCode,
|
|
46
|
+
headers: res.headers,
|
|
47
|
+
data: data
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
req.on('error', (err) => {
|
|
53
|
+
reject(new Error(`HTTP request failed: ${err.message}`));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
req.on('timeout', () => {
|
|
57
|
+
req.destroy();
|
|
58
|
+
reject(new Error('Request timeout'));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (body) {
|
|
62
|
+
req.write(typeof body === 'string' ? body : JSON.stringify(body));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
req.end();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Make a GET request
|
|
71
|
+
* @param {string} url - Request URL
|
|
72
|
+
* @param {Object} headers - Request headers
|
|
73
|
+
* @param {number} timeout - Timeout in milliseconds
|
|
74
|
+
* @returns {Promise<Object>} Parsed JSON response
|
|
75
|
+
*/
|
|
76
|
+
export async function get(url, headers = {}, timeout = 30000) {
|
|
77
|
+
const response = await request({ url, method: 'GET', headers, timeout });
|
|
78
|
+
|
|
79
|
+
if (response.status >= 400) {
|
|
80
|
+
throw new Error(`HTTP ${response.status}: ${response.data}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(response.data);
|
|
85
|
+
} catch {
|
|
86
|
+
return response.data;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Make a POST request
|
|
92
|
+
* @param {string} url - Request URL
|
|
93
|
+
* @param {Object} body - Request body
|
|
94
|
+
* @param {Object} headers - Request headers
|
|
95
|
+
* @param {number} timeout - Timeout in milliseconds
|
|
96
|
+
* @returns {Promise<Object>} Parsed JSON response
|
|
97
|
+
*/
|
|
98
|
+
export async function post(url, body = {}, headers = {}, timeout = 60000) {
|
|
99
|
+
const response = await request({
|
|
100
|
+
url,
|
|
101
|
+
method: 'POST',
|
|
102
|
+
body,
|
|
103
|
+
headers,
|
|
104
|
+
timeout
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (response.status >= 400) {
|
|
108
|
+
const errorData = response.data;
|
|
109
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(errorData);
|
|
112
|
+
errorMessage += `: ${parsed.message || parsed.error || JSON.stringify(parsed)}`;
|
|
113
|
+
} catch {
|
|
114
|
+
errorMessage += `: ${errorData}`;
|
|
115
|
+
}
|
|
116
|
+
throw new Error(errorMessage);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
return JSON.parse(response.data);
|
|
121
|
+
} catch {
|
|
122
|
+
return response.data;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default {
|
|
127
|
+
request,
|
|
128
|
+
get,
|
|
129
|
+
post
|
|
130
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export const logger = {
|
|
4
|
+
info: (msg) => console.log(chalk.blue('ℹ'), msg),
|
|
5
|
+
success: (msg) => console.log(chalk.green('✓'), msg),
|
|
6
|
+
warn: (msg) => console.log(chalk.yellow('⚠'), msg),
|
|
7
|
+
error: (msg) => console.log(chalk.red('✗'), msg),
|
|
8
|
+
watch: (file) => console.log(chalk.cyan('👁'), chalk.gray(file)),
|
|
9
|
+
build: (mode) => console.log(chalk.magenta('⚡'), `Building (${mode})...`),
|
|
10
|
+
debug: (msg) => {
|
|
11
|
+
if (process.env.PQM_DEBUG) {
|
|
12
|
+
console.log(chalk.gray('🔍'), chalk.gray(msg));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function formatTimestamp() {
|
|
18
|
+
return new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function logWithTime(msg, type = 'info') {
|
|
22
|
+
const timestamp = formatTimestamp();
|
|
23
|
+
logger[type](`[${timestamp}] ${msg}`);
|
|
24
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create readline interface
|
|
5
|
+
* @returns {readline.Interface}
|
|
6
|
+
*/
|
|
7
|
+
function createInterface() {
|
|
8
|
+
return readline.createInterface({
|
|
9
|
+
input: process.stdin,
|
|
10
|
+
output: process.stdout
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Prompt user for input
|
|
16
|
+
* @param {string} question - Question to ask
|
|
17
|
+
* @param {string} defaultValue - Default value if user presses enter
|
|
18
|
+
* @returns {Promise<string>} User's answer
|
|
19
|
+
*/
|
|
20
|
+
export function promptUser(question, defaultValue = '') {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const rl = createInterface();
|
|
23
|
+
const displayQuestion = defaultValue
|
|
24
|
+
? `${question} (${defaultValue}): `
|
|
25
|
+
: `${question}: `;
|
|
26
|
+
|
|
27
|
+
rl.question(displayQuestion, (answer) => {
|
|
28
|
+
rl.close();
|
|
29
|
+
resolve(answer.trim() || defaultValue);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Prompt user for password input (hidden)
|
|
36
|
+
* @param {string} question - Question to ask
|
|
37
|
+
* @returns {Promise<string>} User's password
|
|
38
|
+
*/
|
|
39
|
+
export function promptPassword(question) {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const rl = createInterface();
|
|
42
|
+
// Note: In a real terminal, we'd use mute mode for password
|
|
43
|
+
// For simplicity, we just use regular input
|
|
44
|
+
rl.question(`${question}: `, (answer) => {
|
|
45
|
+
rl.close();
|
|
46
|
+
resolve(answer.trim());
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Prompt user to select from options
|
|
53
|
+
* @param {string} question - Question to ask
|
|
54
|
+
* @param {Array<Object|string>} options - Options to choose from
|
|
55
|
+
* @param {number} defaultIndex - Default selection index
|
|
56
|
+
* @returns {Promise<Object|string>} Selected option
|
|
57
|
+
*/
|
|
58
|
+
export async function select(question, options, defaultIndex = 0) {
|
|
59
|
+
console.log(`\n${question}`);
|
|
60
|
+
console.log('');
|
|
61
|
+
|
|
62
|
+
options.forEach((opt, index) => {
|
|
63
|
+
const label = typeof opt === 'string' ? opt : opt.label || opt.name;
|
|
64
|
+
const marker = index === defaultIndex ? '>' : ' ';
|
|
65
|
+
console.log(` ${marker} ${index + 1}. ${label}`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
console.log('');
|
|
69
|
+
|
|
70
|
+
const answer = await promptUser('请选择 (输入序号)', String(defaultIndex + 1));
|
|
71
|
+
const index = parseInt(answer, 10) - 1;
|
|
72
|
+
|
|
73
|
+
if (isNaN(index) || index < 0 || index >= options.length) {
|
|
74
|
+
return options[defaultIndex];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return options[index];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Prompt user for confirmation
|
|
82
|
+
* @param {string} question - Question to ask
|
|
83
|
+
* @param {boolean} defaultValue - Default value
|
|
84
|
+
* @returns {Promise<boolean>} True if confirmed
|
|
85
|
+
*/
|
|
86
|
+
export async function confirm(question, defaultValue = false) {
|
|
87
|
+
const hint = defaultValue ? 'Y/n' : 'y/N';
|
|
88
|
+
const answer = await promptUser(`${question} (${hint})`, '');
|
|
89
|
+
|
|
90
|
+
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (answer.toLowerCase() === 'n' || answer.toLowerCase() === 'no') {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return defaultValue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Prompt for multiple inputs
|
|
102
|
+
* @param {Array<Object>} prompts - Array of prompt configurations
|
|
103
|
+
* @returns {Promise<Object>} Object with answers
|
|
104
|
+
*/
|
|
105
|
+
export async function promptMultiple(prompts) {
|
|
106
|
+
const answers = {};
|
|
107
|
+
|
|
108
|
+
for (const prompt of prompts) {
|
|
109
|
+
const { name, question, defaultValue = '', type = 'text' } = prompt;
|
|
110
|
+
|
|
111
|
+
let answer;
|
|
112
|
+
if (type === 'password') {
|
|
113
|
+
answer = await promptPassword(question);
|
|
114
|
+
} else if (type === 'confirm') {
|
|
115
|
+
answer = await confirm(question, defaultValue);
|
|
116
|
+
} else {
|
|
117
|
+
answer = await promptUser(question, defaultValue);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
answers[name] = answer;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return answers;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default {
|
|
127
|
+
promptUser,
|
|
128
|
+
promptPassword,
|
|
129
|
+
select,
|
|
130
|
+
confirm,
|
|
131
|
+
promptMultiple
|
|
132
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
// Try to load ora, fall back to SimpleSpinner if not available
|
|
4
|
+
let ora = null;
|
|
5
|
+
try {
|
|
6
|
+
const oraModule = await import('ora');
|
|
7
|
+
ora = oraModule.default;
|
|
8
|
+
} catch {
|
|
9
|
+
// ora not available, will use SimpleSpinner
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Simple spinner without ora dependency
|
|
13
|
+
export class SimpleSpinner {
|
|
14
|
+
constructor(text = '') {
|
|
15
|
+
this.text = text;
|
|
16
|
+
this.interval = null;
|
|
17
|
+
this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
18
|
+
this.frameIndex = 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
start(text) {
|
|
22
|
+
this.text = text || this.text;
|
|
23
|
+
process.stdout.write('\x1B[?25l'); // Hide cursor
|
|
24
|
+
this.interval = setInterval(() => {
|
|
25
|
+
process.stdout.write(`\r${chalk.cyan(this.frames[this.frameIndex])} ${this.text}`);
|
|
26
|
+
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
27
|
+
}, 80);
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
stop(clearLine = true) {
|
|
32
|
+
if (this.interval) {
|
|
33
|
+
clearInterval(this.interval);
|
|
34
|
+
this.interval = null;
|
|
35
|
+
}
|
|
36
|
+
if (clearLine) {
|
|
37
|
+
process.stdout.write('\r\x1B[K');
|
|
38
|
+
}
|
|
39
|
+
process.stdout.write('\x1B[?25h'); // Show cursor
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
succeed(text) {
|
|
44
|
+
this.stop(false);
|
|
45
|
+
process.stdout.write(`\r${chalk.green('✓')} ${text || this.text}\n`);
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fail(text) {
|
|
50
|
+
this.stop(false);
|
|
51
|
+
process.stdout.write(`\r${chalk.red('✗')} ${text || this.text}\n`);
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
warn(text) {
|
|
56
|
+
this.stop(false);
|
|
57
|
+
process.stdout.write(`\r${chalk.yellow('!')} ${text || this.text}\n`);
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
info(text) {
|
|
62
|
+
this.stop(false);
|
|
63
|
+
process.stdout.write(`\r${chalk.blue('i')} ${text || this.text}\n`);
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
update(text) {
|
|
68
|
+
this.text = text;
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Ora-based spinner (used when ora is available)
|
|
74
|
+
class OraSpinner {
|
|
75
|
+
constructor(text = '') {
|
|
76
|
+
this.spinner = null;
|
|
77
|
+
this.text = text;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
start(text) {
|
|
81
|
+
this.spinner = ora({
|
|
82
|
+
text: text || this.text,
|
|
83
|
+
spinner: 'dots'
|
|
84
|
+
}).start();
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
stop() {
|
|
89
|
+
if (this.spinner) {
|
|
90
|
+
this.spinner.stop();
|
|
91
|
+
}
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
succeed(text) {
|
|
96
|
+
if (this.spinner) {
|
|
97
|
+
this.spinner.succeed(chalk.green(text || this.text));
|
|
98
|
+
}
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fail(text) {
|
|
103
|
+
if (this.spinner) {
|
|
104
|
+
this.spinner.fail(chalk.red(text || this.text));
|
|
105
|
+
}
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
warn(text) {
|
|
110
|
+
if (this.spinner) {
|
|
111
|
+
this.spinner.warn(chalk.yellow(text || this.text));
|
|
112
|
+
}
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
info(text) {
|
|
117
|
+
if (this.spinner) {
|
|
118
|
+
this.spinner.info(chalk.blue(text || this.text));
|
|
119
|
+
}
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
update(text) {
|
|
124
|
+
if (this.spinner) {
|
|
125
|
+
this.spinner.text = text;
|
|
126
|
+
}
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Export appropriate Spinner based on ora availability
|
|
132
|
+
export const Spinner = ora ? OraSpinner : SimpleSpinner;
|
|
133
|
+
|
|
134
|
+
export default Spinner;
|