212 lines
5.9 KiB
TypeScript
212 lines
5.9 KiB
TypeScript
import { exec } from "child_process";
|
||
import { promisify } from "util";
|
||
import { platform, totalmem, freemem, cpus, hostname, userInfo } from "os";
|
||
import { existsSync } from "fs";
|
||
|
||
const execAsync = promisify(exec);
|
||
|
||
export const envCheckTool = {
|
||
name: "env_check",
|
||
description:
|
||
"环境检测。检查 Node.js、Python、Git 等工具版本,检测端口占用,查看系统资源(CPU/内存/磁盘),验证开发环境是否就绪。",
|
||
inputSchema: {
|
||
type: "object" as const,
|
||
properties: {
|
||
checks: {
|
||
type: "string",
|
||
description: "要检查的项目,逗号分隔(可选):all, node, python, git, ports, system, docker。默认 all。",
|
||
},
|
||
ports: {
|
||
type: "string",
|
||
description: "要检测的端口号,逗号分隔(如 3000,8080,5173)",
|
||
},
|
||
},
|
||
required: [],
|
||
},
|
||
};
|
||
|
||
async function getVersion(cmd: string): Promise<string> {
|
||
try {
|
||
const { stdout } = await execAsync(cmd, { timeout: 5000 });
|
||
return stdout.trim().split("\n")[0];
|
||
} catch {
|
||
return "❌ 未安装";
|
||
}
|
||
}
|
||
|
||
async function checkPort(port: number): Promise<boolean> {
|
||
try {
|
||
const cmd = process.platform === "win32"
|
||
? `netstat -ano | findstr :${port}`
|
||
: `lsof -i :${port} 2>/dev/null || ss -tlnp | grep :${port}`;
|
||
const { stdout } = await execAsync(cmd, { timeout: 5000 });
|
||
return stdout.trim().length > 0;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
async function getDiskSpace(): Promise<string> {
|
||
try {
|
||
const cmd = process.platform === "win32"
|
||
? 'powershell -command "Get-PSDrive -PSProvider FileSystem | Select-Object Name, @{N=\'Used(GB)\';E={[math]::Round($_.Used/1GB,1)}}, @{N=\'Free(GB)\';E={[math]::Round($_.Free/1GB,1)}} | Format-Table -AutoSize"'
|
||
: "df -h / /home 2>/dev/null";
|
||
const { stdout } = await execAsync(cmd, { timeout: 10000 });
|
||
return stdout.trim();
|
||
} catch {
|
||
return "无法获取";
|
||
}
|
||
}
|
||
|
||
export async function executeEnvCheck(args: {
|
||
checks?: string;
|
||
ports?: string;
|
||
}): Promise<string> {
|
||
const { checks = "all", ports } = args;
|
||
const checkList = checks.split(",").map((c) => c.trim().toLowerCase());
|
||
const doAll = checkList.includes("all");
|
||
|
||
const output: string[] = [
|
||
`# 环境检测报告`,
|
||
``,
|
||
`🖥️ ${hostname()} | ${platform()} | ${userInfo().username}`,
|
||
`📅 ${new Date().toLocaleString("zh-CN")}`,
|
||
``,
|
||
];
|
||
|
||
// 系统资源
|
||
if (doAll || checkList.includes("system")) {
|
||
const totalMem = (totalmem() / 1024 / 1024 / 1024).toFixed(1);
|
||
const freeMem = (freemem() / 1024 / 1024 / 1024).toFixed(1);
|
||
const usedMem = ((totalmem() - freemem()) / 1024 / 1024 / 1024).toFixed(1);
|
||
const memPercent = ((1 - freemem() / totalmem()) * 100).toFixed(0);
|
||
const cpuInfo = cpus();
|
||
const cpuModel = cpuInfo[0]?.model || "未知";
|
||
|
||
output.push(
|
||
`## 💻 系统资源`,
|
||
``,
|
||
`| 项目 | 值 |`,
|
||
`|------|------|`,
|
||
`| CPU | ${cpuModel} |`,
|
||
`| 核心数 | ${cpuInfo.length} |`,
|
||
`| 内存 | ${usedMem} / ${totalMem} GB (${memPercent}%) |`,
|
||
`| 空闲内存 | ${freeMem} GB |`,
|
||
``
|
||
);
|
||
|
||
const disk = await getDiskSpace();
|
||
if (disk !== "无法获取") {
|
||
output.push(`### 磁盘空间`, "```", disk, "```", ``);
|
||
}
|
||
}
|
||
|
||
// Node.js
|
||
if (doAll || checkList.includes("node")) {
|
||
const [nodeVer, npmVer, npxVer, yarnVer, pnpmVer] = await Promise.all([
|
||
getVersion("node --version"),
|
||
getVersion("npm --version"),
|
||
getVersion("npx --version"),
|
||
getVersion("yarn --version"),
|
||
getVersion("pnpm --version"),
|
||
]);
|
||
|
||
output.push(
|
||
`## 📦 Node.js 生态`,
|
||
``,
|
||
`| 工具 | 版本 |`,
|
||
`|------|------|`,
|
||
`| Node.js | ${nodeVer} |`,
|
||
`| npm | ${npmVer} |`,
|
||
`| npx | ${npxVer} |`,
|
||
`| yarn | ${yarnVer} |`,
|
||
`| pnpm | ${pnpmVer} |`,
|
||
``
|
||
);
|
||
}
|
||
|
||
// Python
|
||
if (doAll || checkList.includes("python")) {
|
||
const [pyVer, py3Ver, pipVer] = await Promise.all([
|
||
getVersion("python --version"),
|
||
getVersion("python3 --version"),
|
||
getVersion("pip --version"),
|
||
]);
|
||
|
||
output.push(
|
||
`## 🐍 Python 生态`,
|
||
``,
|
||
`| 工具 | 版本 |`,
|
||
`|------|------|`,
|
||
`| Python | ${pyVer} |`,
|
||
`| Python3 | ${py3Ver} |`,
|
||
`| pip | ${pipVer} |`,
|
||
``
|
||
);
|
||
}
|
||
|
||
// Git
|
||
if (doAll || checkList.includes("git")) {
|
||
const gitVer = await getVersion("git --version");
|
||
const gitUser = await getVersion("git config --global user.name");
|
||
const gitEmail = await getVersion("git config --global user.email");
|
||
|
||
output.push(
|
||
`## 🔧 Git`,
|
||
``,
|
||
`| 项目 | 值 |`,
|
||
`|------|------|`,
|
||
`| 版本 | ${gitVer} |`,
|
||
`| 用户名 | ${gitUser} |`,
|
||
`| 邮箱 | ${gitEmail} |`,
|
||
``
|
||
);
|
||
}
|
||
|
||
// Docker
|
||
if (doAll || checkList.includes("docker")) {
|
||
const [dockerVer, composeVer] = await Promise.all([
|
||
getVersion("docker --version"),
|
||
getVersion("docker compose version 2>&1 || docker-compose --version 2>&1"),
|
||
]);
|
||
|
||
output.push(
|
||
`## 🐳 Docker`,
|
||
``,
|
||
`| 工具 | 版本 |`,
|
||
`|------|------|`,
|
||
`| Docker | ${dockerVer} |`,
|
||
`| Compose | ${composeVer} |`,
|
||
``
|
||
);
|
||
}
|
||
|
||
// 端口检测
|
||
if (doAll || checkList.includes("ports") || ports) {
|
||
const portsToCheck = ports
|
||
? ports.split(",").map((p) => parseInt(p.trim())).filter(Boolean)
|
||
: [3000, 3001, 5173, 5174, 8080, 8081, 4000, 4173, 5000, 5500];
|
||
|
||
const portResults = await Promise.all(
|
||
portsToCheck.map(async (port) => ({
|
||
port,
|
||
occupied: await checkPort(port),
|
||
}))
|
||
);
|
||
|
||
output.push(
|
||
`## 🔌 端口状态`,
|
||
``,
|
||
`| 端口 | 状态 |`,
|
||
`|------|------|`,
|
||
...portResults.map((r) => `| ${r.port} | ${r.occupied ? "🔴 已占用" : "🟢 空闲"} |`),
|
||
``
|
||
);
|
||
}
|
||
|
||
// 总结
|
||
output.push(`---`, `_检测完成_`);
|
||
|
||
return output.join("\n");
|
||
}
|