@chen_258/audit-tool 1.0.2
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/dist/audit/index.js +231 -0
- package/dist/common/utils.js +46 -0
- package/dist/entry/index.js +26 -0
- package/dist/file/index.js +15 -0
- package/dist/mcp/index.js +13 -0
- package/dist/mcp/server.js +63 -0
- package/dist/package-json/index.js +68 -0
- package/package.json +40 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* 执行安全审计命令,获取到的审计结果格式化生成 md 文件 存入指定的输出路径
|
|
6
|
+
*/
|
|
7
|
+
class Audit {
|
|
8
|
+
constructor(inputPath, outputPath) {
|
|
9
|
+
Object.defineProperty(this, "inputPath", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: void 0
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(this, "outputPath", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
writable: true,
|
|
19
|
+
value: void 0
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(this, "cachedResult", {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
writable: true,
|
|
25
|
+
value: null
|
|
26
|
+
});
|
|
27
|
+
this.inputPath = inputPath;
|
|
28
|
+
this.outputPath = outputPath;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 执行 npm audit --json 并返回 JSON 字符串
|
|
32
|
+
* 结果会被缓存,避免重复执行
|
|
33
|
+
*/
|
|
34
|
+
command() {
|
|
35
|
+
if (this.cachedResult) {
|
|
36
|
+
return Promise.resolve(this.cachedResult);
|
|
37
|
+
}
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const childProcess = spawn('npm', ['audit', '--json'], {
|
|
40
|
+
cwd: this.inputPath,
|
|
41
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
42
|
+
shell: true,
|
|
43
|
+
});
|
|
44
|
+
let stdoutData = '';
|
|
45
|
+
let stderrData = '';
|
|
46
|
+
childProcess.stdout.on('data', (data) => {
|
|
47
|
+
stdoutData += data.toString();
|
|
48
|
+
});
|
|
49
|
+
childProcess.stderr.on('data', (data) => {
|
|
50
|
+
stderrData += data.toString();
|
|
51
|
+
});
|
|
52
|
+
childProcess.on('close', (code) => {
|
|
53
|
+
if (code === 0 || code === 1) {
|
|
54
|
+
try {
|
|
55
|
+
JSON.parse(stdoutData);
|
|
56
|
+
this.cachedResult = stdoutData;
|
|
57
|
+
resolve(stdoutData);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
process.stderr.write(`JSON 解析失败,stderr 内容: ${stderrData}\n`);
|
|
61
|
+
reject(new Error(`JSON 解析失败: ${error.message}\n原始输出前500字符: ${stdoutData.substring(0, 500)}`));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
reject(new Error(`npm audit 执行失败,退出码: ${code}\nstderr: ${stderrData}`));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
childProcess.on('error', (err) => {
|
|
69
|
+
reject(new Error(`npm audit 启动失败: ${err.message}`));
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async jsonFormat() {
|
|
74
|
+
const result = await this.command();
|
|
75
|
+
const jsonPath = path.join(this.inputPath, 'audit.json');
|
|
76
|
+
await fs.promises.writeFile(jsonPath, JSON.stringify(JSON.parse(result), null, 2));
|
|
77
|
+
process.stderr.write(`JSON 结果已保存到: ${jsonPath}\n`);
|
|
78
|
+
}
|
|
79
|
+
async generateMarkdownReport() {
|
|
80
|
+
try {
|
|
81
|
+
const result = await this.command();
|
|
82
|
+
const auditData = JSON.parse(result);
|
|
83
|
+
// 获取项目根目录名称
|
|
84
|
+
const projectName = path.basename(this.inputPath);
|
|
85
|
+
// 创建 Markdown 内容
|
|
86
|
+
let markdown = `# ${projectName} 项目安全审计报告\n\n`;
|
|
87
|
+
markdown += `**生成时间:** ${new Date().toLocaleString()}\n`;
|
|
88
|
+
markdown += `**审计路径:** ${process.cwd()}\n\n`;
|
|
89
|
+
// 漏洞统计 — 兼容 npm audit JSON 格式
|
|
90
|
+
const vulnerabilities = auditData.vulnerabilities || {};
|
|
91
|
+
const metadata = auditData.metadata || {};
|
|
92
|
+
const stats = metadata.vulnerabilities || {};
|
|
93
|
+
// 处理无漏洞场景(npm 可能返回空 vulnerabilities 且无 metadata)
|
|
94
|
+
const vulnCount = Object.keys(vulnerabilities).length;
|
|
95
|
+
if (vulnCount === 0 && (!stats.total || stats.total === 0)) {
|
|
96
|
+
markdown += `## 审计结果\n\n`;
|
|
97
|
+
markdown += `**未发现安全漏洞。** 所有依赖均通过安全审计。\n\n`;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
markdown += `## 漏洞统计\n\n`;
|
|
101
|
+
markdown += `| 严重程度 | 数量 |\n`;
|
|
102
|
+
markdown += `|----------|------|\n`;
|
|
103
|
+
markdown += `| **严重** | ${stats.critical || 0} |\n`;
|
|
104
|
+
markdown += `| **高危** | ${stats.high || 0} |\n`;
|
|
105
|
+
markdown += `| **中危** | ${stats.moderate || 0} |\n`;
|
|
106
|
+
markdown += `| **低危** | ${stats.low || 0} |\n`;
|
|
107
|
+
markdown += `| **信息** | ${stats.info || 0} |\n`;
|
|
108
|
+
markdown += `| **总计** | ${stats.total || vulnCount} |\n\n`;
|
|
109
|
+
// 按严重程度分组漏洞
|
|
110
|
+
const vulnerabilitiesBySeverity = {};
|
|
111
|
+
Object.entries(vulnerabilities).forEach(([key, vuln]) => {
|
|
112
|
+
const severity = vuln.severity || 'unknown';
|
|
113
|
+
if (!vulnerabilitiesBySeverity[severity]) {
|
|
114
|
+
vulnerabilitiesBySeverity[severity] = [];
|
|
115
|
+
}
|
|
116
|
+
vulnerabilitiesBySeverity[severity].push({
|
|
117
|
+
key,
|
|
118
|
+
...vuln
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
// 严重程度排序(从高到低)
|
|
122
|
+
const severityOrder = ['critical', 'high', 'moderate', 'low', 'info', 'unknown'];
|
|
123
|
+
severityOrder.forEach(severity => {
|
|
124
|
+
const vulnList = vulnerabilitiesBySeverity[severity];
|
|
125
|
+
if (vulnList && vulnList.length > 0) {
|
|
126
|
+
const severityZh = {
|
|
127
|
+
'critical': '严重',
|
|
128
|
+
'high': '高危',
|
|
129
|
+
'moderate': '中危',
|
|
130
|
+
'low': '低危',
|
|
131
|
+
'info': '信息',
|
|
132
|
+
'unknown': '未知'
|
|
133
|
+
}[severity] || severity;
|
|
134
|
+
markdown += `## ${severityZh} 漏洞\n\n`;
|
|
135
|
+
vulnList.forEach((vuln, index) => {
|
|
136
|
+
markdown += `### ${index + 1}. ${vuln.name} (${vuln.key})\n\n`;
|
|
137
|
+
markdown += `**漏洞编号:** ${vuln.via?.[0]?.url || vuln.via?.[0]?.id || vuln.via?.[0]?.title || 'N/A'}\n\n`;
|
|
138
|
+
if (vuln.cvss?.score) {
|
|
139
|
+
markdown += `**CVSS 分数:** ${vuln.cvss.score}\n`;
|
|
140
|
+
markdown += `**CVSS 等级:** ${vuln.cvss.severity}\n\n`;
|
|
141
|
+
}
|
|
142
|
+
markdown += `**漏洞等级:** ${vuln.severity}\n`;
|
|
143
|
+
markdown += `**依赖路径:** \`${vuln.effects?.join(' → ') || vuln.key}\`\n`;
|
|
144
|
+
markdown += `**受影响的版本:** ${vuln.range}\n`;
|
|
145
|
+
markdown += `**修复版本:** ${vuln.fixAvailable?.name ? `${vuln.fixAvailable.name}@${vuln.fixAvailable.version}` : '暂无修复'}\n\n`;
|
|
146
|
+
markdown += `**依赖关系:**\n`;
|
|
147
|
+
if (vuln.nodes && vuln.nodes.length > 0) {
|
|
148
|
+
vuln.nodes.forEach((node) => {
|
|
149
|
+
markdown += `- ${node}\n`;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
markdown += `- 直接依赖\n`;
|
|
154
|
+
}
|
|
155
|
+
markdown += `\n`;
|
|
156
|
+
if (vuln.range === '*' || vuln.range === '>=0.0.0') {
|
|
157
|
+
markdown += `⚠️ **警告:** 此依赖影响所有版本\n\n`;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
// 修复建议
|
|
164
|
+
markdown += `## 修复建议\n\n`;
|
|
165
|
+
if (auditData.actions && auditData.actions.length > 0) {
|
|
166
|
+
auditData.actions.forEach((action, index) => {
|
|
167
|
+
markdown += `${index + 1}. **${action.action}**\n`;
|
|
168
|
+
if (action.resolves && action.resolves.length > 0) {
|
|
169
|
+
action.resolves.forEach((resolve) => {
|
|
170
|
+
markdown += ` - 修复 \`${resolve.id}\`: ${resolve.path}\n`;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
markdown += `\n`;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
markdown += `1. 运行 \`npm audit fix\` 自动修复可修复的漏洞\n`;
|
|
178
|
+
markdown += `2. 运行 \`npm audit fix --force\` 强制修复所有漏洞(可能有破坏性变更)\n`;
|
|
179
|
+
markdown += `3. 手动更新受影响包的版本\n\n`;
|
|
180
|
+
}
|
|
181
|
+
// 保存 Markdown 文件
|
|
182
|
+
let mdPath;
|
|
183
|
+
if (this.outputPath) {
|
|
184
|
+
try {
|
|
185
|
+
const stat = await fs.promises.stat(this.outputPath);
|
|
186
|
+
if (stat.isDirectory()) {
|
|
187
|
+
mdPath = path.join(this.outputPath, 'audit.md');
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
mdPath = this.outputPath;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
if (this.outputPath.endsWith('/') || this.outputPath.endsWith('\\')) {
|
|
195
|
+
mdPath = path.join(this.outputPath, 'audit.md');
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
mdPath = this.outputPath;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
mdPath = path.join(this.inputPath, 'audit.md');
|
|
204
|
+
}
|
|
205
|
+
const dir = path.dirname(mdPath);
|
|
206
|
+
if (!fs.existsSync(dir)) {
|
|
207
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
208
|
+
}
|
|
209
|
+
await fs.promises.writeFile(mdPath, markdown, 'utf-8');
|
|
210
|
+
process.stderr.write(`Markdown 报告已生成: ${mdPath}\n`);
|
|
211
|
+
return markdown;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
process.stderr.write(`生成 Markdown 报告失败: ${error}\n`);
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async run() {
|
|
219
|
+
process.stderr.write('开始安全审计...\n');
|
|
220
|
+
await this.jsonFormat();
|
|
221
|
+
const report = await this.generateMarkdownReport();
|
|
222
|
+
process.stderr.write('安全审计完成,Markdown 报告已生成!\n');
|
|
223
|
+
return report;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
export async function audit(inputPath, outputPath) {
|
|
227
|
+
const auditInstance = new Audit(inputPath, outputPath);
|
|
228
|
+
const report = await auditInstance.run();
|
|
229
|
+
process.stderr.write('审计函数执行完成,程序可以继续...\n');
|
|
230
|
+
return report;
|
|
231
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import { dirname } from 'path';
|
|
3
|
+
import child_process from 'child_process';
|
|
4
|
+
/**
|
|
5
|
+
* 获取当前文件的目录名
|
|
6
|
+
* @param importMetaUrl - import.meta.url 的值
|
|
7
|
+
* @returns 当前文件的目录路径
|
|
8
|
+
*/
|
|
9
|
+
export function getDirname(importMetaUrl) {
|
|
10
|
+
const __filename = fileURLToPath(importMetaUrl);
|
|
11
|
+
return dirname(__filename);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 生成唯一ID
|
|
15
|
+
* @returns 唯一标识符字符串
|
|
16
|
+
*/
|
|
17
|
+
export function uniqueId() {
|
|
18
|
+
return Date.now().toString(36) + Math.random().toString(36).substring(2);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 执行shell命令
|
|
22
|
+
* @param command - 要执行的shell命令
|
|
23
|
+
* @param options - 执行选项
|
|
24
|
+
* @returns 执行结果
|
|
25
|
+
*/
|
|
26
|
+
export function runCommand(command, args, options) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const proc = child_process.spawn(command, args, {
|
|
29
|
+
cwd: options.path,
|
|
30
|
+
stdio: 'inherit',
|
|
31
|
+
shell: true,
|
|
32
|
+
...options
|
|
33
|
+
});
|
|
34
|
+
proc.on('close', (code) => {
|
|
35
|
+
if (code === 0) {
|
|
36
|
+
resolve(code);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
reject(new Error(`命令执行失败,退出码: ${code}`));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
proc.on('error', (err) => {
|
|
43
|
+
reject(err);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createPageJson, getPageJson } from '../package-json/index.js';
|
|
2
|
+
import { createWorkDir, deleteWorkDir } from '../file/index.js';
|
|
3
|
+
import { audit } from '../audit/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* CLI 入口 — 独立运行模式
|
|
6
|
+
* 直接在终端执行安全审计,结果输出为 Markdown 文件
|
|
7
|
+
*
|
|
8
|
+
* MCP 模式请使用: npm run mcp:start
|
|
9
|
+
*/
|
|
10
|
+
async function main(url) {
|
|
11
|
+
const workDir = await createWorkDir(); // 创建临时目录
|
|
12
|
+
// 获取本地或者远程工程文件的package.json
|
|
13
|
+
const resJson = await getPageJson(url);
|
|
14
|
+
//在工作目录生成临时的package.json(使用 pnpm 生成 lock 文件)
|
|
15
|
+
await createPageJson(workDir, resJson);
|
|
16
|
+
//安全审计 进入临时目录 执行 pnpm audit 执行安全审计
|
|
17
|
+
await audit(workDir, './audit.md');
|
|
18
|
+
await deleteWorkDir(workDir); // 删除临时目录
|
|
19
|
+
}
|
|
20
|
+
// 测试本地项目
|
|
21
|
+
main('https://raw.githubusercontent.com/djdjjsfsks/manage/main').then(() => {
|
|
22
|
+
console.log('✅ 程序执行完毕');
|
|
23
|
+
}).catch((error) => {
|
|
24
|
+
console.error('❌ 程序执行失败:', error.message);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { uniqueId, getDirname } from '../common/utils';
|
|
4
|
+
const __dirname = getDirname(import.meta.url); // 获取当前文件的目录名
|
|
5
|
+
const basePath = join(__dirname, './'); // 获取上级目录
|
|
6
|
+
const workBasePath = join(basePath, 'work'); // 定义工作目录路径
|
|
7
|
+
fs.mkdirSync(workBasePath, { recursive: true }); // 确保工作目录存在
|
|
8
|
+
export async function createWorkDir() {
|
|
9
|
+
const workDir = join(workBasePath, uniqueId());
|
|
10
|
+
await fs.promises.mkdir(workDir, { recursive: true }); // 创建工作目录
|
|
11
|
+
return workDir;
|
|
12
|
+
}
|
|
13
|
+
export async function deleteWorkDir(workDir) {
|
|
14
|
+
await fs.promises.rm(workDir, { recursive: true, force: true });
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { createAuditServer } from './server.js';
|
|
4
|
+
async function main() {
|
|
5
|
+
const server = createAuditServer();
|
|
6
|
+
const transport = new StdioServerTransport();
|
|
7
|
+
await server.connect(transport);
|
|
8
|
+
process.stderr.write('[mcp] npm-audit-server 已启动,等待客户端连接...\n');
|
|
9
|
+
}
|
|
10
|
+
main().catch((error) => {
|
|
11
|
+
process.stderr.write(`[mcp] 启动失败: ${error.message}\n`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { createPageJson, getPageJson } from '../package-json/index.js';
|
|
4
|
+
import { createWorkDir, deleteWorkDir } from '../file/index.js';
|
|
5
|
+
import { audit } from '../audit/index.js';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
export function createAuditServer() {
|
|
9
|
+
const server = new McpServer({
|
|
10
|
+
name: 'npm-audit-server',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
description: 'Node.js 项目依赖安全审计服务,基于 npm audit 对项目进行漏洞扫描',
|
|
13
|
+
});
|
|
14
|
+
server.tool('audit-project', '对指定 Node.js 项目执行依赖安全审计,返回 Markdown 格式的漏洞报告。支持远程 GitHub URL 或本地项目路径。', {
|
|
15
|
+
project_url: z.string().describe('项目的远程 URL(如 GitHub raw 地址)或本地项目绝对路径'),
|
|
16
|
+
}, async ({ project_url }) => {
|
|
17
|
+
let workDir = null;
|
|
18
|
+
try {
|
|
19
|
+
// 判断 project_url 是本地路径还是远程 URL
|
|
20
|
+
const isLocal = fs.existsSync(project_url);
|
|
21
|
+
let targetDir;
|
|
22
|
+
if (isLocal) {
|
|
23
|
+
// 本地路径:report 写入目标项目根目录
|
|
24
|
+
targetDir = path.resolve(project_url);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
// 远程 URL:report 写入 MCP 客户端工作目录(调用方项目)
|
|
28
|
+
targetDir = process.cwd();
|
|
29
|
+
}
|
|
30
|
+
// 1. 创建临时工作目录
|
|
31
|
+
workDir = await createWorkDir();
|
|
32
|
+
// 2. 获取目标项目的 package.json
|
|
33
|
+
const pkgJson = await getPageJson(project_url);
|
|
34
|
+
// 3. 在临时目录写入 package.json,优先复制源项目 lock 文件
|
|
35
|
+
const sourcePath = isLocal ? path.resolve(project_url) : undefined;
|
|
36
|
+
await createPageJson(workDir, pkgJson, sourcePath);
|
|
37
|
+
// 4. 执行 npm audit 并生成 Markdown 报告到目标目录
|
|
38
|
+
const reportPath = path.join(targetDir, 'audit.md');
|
|
39
|
+
const report = await audit(workDir, reportPath);
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: 'text', text: report }],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: 'text', text: `审计失败: ${error.message}` }],
|
|
47
|
+
isError: true,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
// 5. 无论成功失败,清理临时目录
|
|
52
|
+
if (workDir) {
|
|
53
|
+
try {
|
|
54
|
+
await deleteWorkDir(workDir);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// 清理失败不影响返回结果
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return server;
|
|
63
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { runCommand } from '../common/utils';
|
|
4
|
+
/**
|
|
5
|
+
* 获取package.json 文件内容
|
|
6
|
+
* @param url 判断当前项目的路径是远程还是本地
|
|
7
|
+
* @returns 返回获取到的package.json 文件内容
|
|
8
|
+
*/
|
|
9
|
+
export async function getPageJson(url) {
|
|
10
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
11
|
+
// 处理远程URL
|
|
12
|
+
return getRemotePageJson(url);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
// 处理本地路径
|
|
16
|
+
return getLocalPageJson(url);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 获取本地package.json 文件内容
|
|
21
|
+
* @param projectPath 本地路径
|
|
22
|
+
* @returns 返回获取到的package.json 文件内容
|
|
23
|
+
*/
|
|
24
|
+
async function getLocalPageJson(projectPath) {
|
|
25
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
26
|
+
const packageJson = await fs.promises.readFile(packageJsonPath, 'utf8');
|
|
27
|
+
return JSON.parse(packageJson);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 获取远程package.json 文件内容 比如gitHub
|
|
31
|
+
* @param url 远程URL
|
|
32
|
+
* @returns 返回获取到的package.json 文件内容
|
|
33
|
+
* https://github.com/djdjjsfsks/manage/blob/main
|
|
34
|
+
*/
|
|
35
|
+
async function getRemotePageJson(url) {
|
|
36
|
+
const response = await fetch(`${url}/package.json`);
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
throw new Error(`Failed to fetch package.json from ${url}`);
|
|
39
|
+
}
|
|
40
|
+
const packageJson = await response.text();
|
|
41
|
+
return JSON.parse(packageJson);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 创建package.json 文件,优先复制源项目的 package-lock.json,
|
|
45
|
+
* 仅在无 lock 文件时才执行 npm install 生成
|
|
46
|
+
* @param workDir 工作目录路径
|
|
47
|
+
* @param val 页面配置对象
|
|
48
|
+
* @param sourcePath 源项目路径(用于查找已有的 lock 文件)
|
|
49
|
+
*/
|
|
50
|
+
export async function createPageJson(workDir, val, sourcePath) {
|
|
51
|
+
const packageJsonPath = path.join(workDir, 'package.json');
|
|
52
|
+
await fs.promises.writeFile(packageJsonPath, JSON.stringify(val, null, 2));
|
|
53
|
+
const lockFileName = 'package-lock.json';
|
|
54
|
+
const sourceLockPath = sourcePath ? path.join(sourcePath, lockFileName) : null;
|
|
55
|
+
const workLockPath = path.join(workDir, lockFileName);
|
|
56
|
+
// 优先复制源项目已有的 lock 文件,跳过耗时的 npm install
|
|
57
|
+
if (sourceLockPath && fs.existsSync(sourceLockPath)) {
|
|
58
|
+
await fs.promises.copyFile(sourceLockPath, workLockPath);
|
|
59
|
+
process.stderr.write(`[mcp] 已复制 ${lockFileName},跳过 npm install\n`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// 无 lock 文件时才生成,添加优化标志加速
|
|
63
|
+
await runCommand('npm', [
|
|
64
|
+
'install', '--package-lock-only', '--force',
|
|
65
|
+
'--ignore-scripts', '--no-audit', '--no-fund',
|
|
66
|
+
], { path: workDir });
|
|
67
|
+
}
|
|
68
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chen_258/audit-tool",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/mcp/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"audit-tool": "./dist/mcp/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": "16.0.0"
|
|
14
|
+
},
|
|
15
|
+
"description": "基于 npm audit 的 MCP 安全审计服务器",
|
|
16
|
+
"keywords": ["mcp", "npm-audit", "security", "vulnerability"],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "vite",
|
|
20
|
+
"build": "tsc && vite build",
|
|
21
|
+
"preview": "vite preview",
|
|
22
|
+
"start": "tsx src/entry/index.ts",
|
|
23
|
+
"dev:ts": "tsx src/entry/index.ts",
|
|
24
|
+
"build:ts": "tsc --project tsconfig.tsnode.json",
|
|
25
|
+
"run:js": "node dist/entry/index.js",
|
|
26
|
+
"mcp:start": "tsx src/mcp/index.ts",
|
|
27
|
+
"prepublishOnly": "npm run build:ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
31
|
+
"zod": "^4.3.6"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^20.0.0",
|
|
35
|
+
"ts-node": "^10.9.2",
|
|
36
|
+
"tsx": "^4.19.2",
|
|
37
|
+
"typescript": "~5.9.3",
|
|
38
|
+
"vite": "^8.0.1"
|
|
39
|
+
}
|
|
40
|
+
}
|