Files
bigwo/dev-assistant-mcp/dist/tools/devServer.js
2026-03-12 12:47:56 +08:00

229 lines
9.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { exec, spawn } from "child_process";
import { promisify } from "util";
import { existsSync, readFileSync } from "fs";
import { join } from "path";
const execAsync = promisify(exec);
// 进程管理器 - 跟踪所有启动的开发服务器
const managedProcesses = new Map();
export const devServerTool = {
name: "dev_server",
description: "开发服务器管理。启动/停止/重启开发服务器,查看运行状态和实时日志。支持自动检测项目的 dev 命令。",
inputSchema: {
type: "object",
properties: {
project_path: {
type: "string",
description: "项目根目录(绝对路径)",
},
action: {
type: "string",
description: "操作类型",
enum: ["start", "stop", "restart", "status", "logs", "list"],
},
command: {
type: "string",
description: "自定义启动命令(可选,默认自动检测 npm run dev 等)",
},
name: {
type: "string",
description: "服务器名称/标识(可选,默认使用目录名)",
},
tail: {
type: "number",
description: "显示最近几行日志(默认 30",
},
},
required: ["action"],
},
};
function getServerName(projectPath, name) {
if (name)
return name;
if (projectPath)
return projectPath.split(/[/\\]/).pop() || "server";
return "default";
}
function detectDevCommand(projectPath) {
const pkgPath = join(projectPath, "package.json");
if (existsSync(pkgPath)) {
try {
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
const scripts = pkg.scripts || {};
// 按优先级检测
for (const name of ["dev", "start:dev", "serve", "start"]) {
if (scripts[name])
return `npm run ${name}`;
}
}
catch { }
}
if (existsSync(join(projectPath, "manage.py"))) {
return "python manage.py runserver";
}
if (existsSync(join(projectPath, "main.py"))) {
return "python main.py";
}
if (existsSync(join(projectPath, "app.py"))) {
return "python app.py";
}
return null;
}
export async function executeDevServer(args) {
const { project_path, action, command, name, tail = 30 } = args;
switch (action) {
case "list": {
if (managedProcesses.size === 0) {
return "# 开发服务器\n\n_没有正在运行的服务器_";
}
const output = ["# 运行中的开发服务器", ""];
for (const [key, info] of managedProcesses) {
const running = !info.process.killed && info.process.exitCode === null;
const uptime = Math.round((Date.now() - info.startTime) / 1000);
output.push(`## ${running ? "🟢" : "🔴"} ${key}`, `- 命令: \`${info.command}\``, `- 目录: ${info.cwd}`, `- PID: ${info.process.pid}`, `- 运行时间: ${uptime}s`, `- 状态: ${running ? "运行中" : "已停止"}`, ``);
}
return output.join("\n");
}
case "start": {
if (!project_path)
return "❌ start 需要 project_path 参数";
const serverName = getServerName(project_path, name);
const existing = managedProcesses.get(serverName);
if (existing && !existing.process.killed && existing.process.exitCode === null) {
return `⚠️ 服务器 "${serverName}" 已在运行中 (PID: ${existing.process.pid})\n\n使用 action=restart 重启,或 action=stop 先停止`;
}
const startCmd = command || detectDevCommand(project_path);
if (!startCmd) {
return `❌ 未检测到启动命令,请手动指定 command 参数\n\n常见命令:\n- npm run dev\n- npm start\n- python app.py`;
}
// 解析命令
const isWin = process.platform === "win32";
const child = spawn(isWin ? "cmd" : "sh", [isWin ? "/c" : "-c", startCmd], {
cwd: project_path,
stdio: ["ignore", "pipe", "pipe"],
detached: false,
});
const logs = [];
const maxLogs = 200;
const addLog = (data, stream) => {
const lines = data.toString().split("\n").filter(Boolean);
for (const line of lines) {
logs.push(`[${stream}] ${line}`);
if (logs.length > maxLogs)
logs.shift();
}
};
child.stdout?.on("data", (data) => addLog(data, "out"));
child.stderr?.on("data", (data) => addLog(data, "err"));
managedProcesses.set(serverName, {
process: child,
command: startCmd,
cwd: project_path,
startTime: Date.now(),
logs,
});
// 等待一会检查是否立即崩溃
await new Promise((r) => setTimeout(r, 2000));
const crashed = child.exitCode !== null;
if (crashed) {
const output = logs.join("\n");
managedProcesses.delete(serverName);
return `# ❌ 服务器启动失败\n\n命令: \`${startCmd}\`\n退出码: ${child.exitCode}\n\n\`\`\`\n${output.slice(0, 3000)}\n\`\`\``;
}
return [
`# ✅ 服务器已启动`,
``,
`- 名称: ${serverName}`,
`- 命令: \`${startCmd}\``,
`- PID: ${child.pid}`,
`- 目录: ${project_path}`,
``,
`最近日志:`,
"```",
logs.slice(-10).join("\n") || "(等待输出...)",
"```",
``,
`💡 使用 \`dev_server action=logs\` 查看实时日志`,
].join("\n");
}
case "stop": {
const serverName = getServerName(project_path, name);
const info = managedProcesses.get(serverName);
if (!info) {
return `❌ 未找到服务器 "${serverName}"\n\n使用 action=list 查看所有运行中的服务器`;
}
const pid = info.process.pid;
try {
// Windows 需要 taskkill 杀进程树
if (process.platform === "win32" && pid) {
await execAsync(`taskkill /PID ${pid} /T /F`).catch(() => { });
}
else {
info.process.kill("SIGTERM");
}
}
catch { }
managedProcesses.delete(serverName);
return `# ✅ 服务器已停止\n\n- 名称: ${serverName}\n- PID: ${pid}`;
}
case "restart": {
const serverName = getServerName(project_path, name);
const info = managedProcesses.get(serverName);
if (info) {
try {
if (process.platform === "win32" && info.process.pid) {
await execAsync(`taskkill /PID ${info.process.pid} /T /F`).catch(() => { });
}
else {
info.process.kill("SIGTERM");
}
}
catch { }
managedProcesses.delete(serverName);
await new Promise((r) => setTimeout(r, 1000));
}
// 重新启动
return executeDevServer({
project_path: info?.cwd || project_path,
action: "start",
command: command || info?.command,
name: serverName,
});
}
case "status": {
const serverName = getServerName(project_path, name);
const info = managedProcesses.get(serverName);
if (!info) {
return `❌ 未找到服务器 "${serverName}"`;
}
const running = !info.process.killed && info.process.exitCode === null;
const uptime = Math.round((Date.now() - info.startTime) / 1000);
return [
`# ${running ? "🟢" : "🔴"} ${serverName}`,
``,
`- 状态: ${running ? "运行中" : `已停止 (退出码: ${info.process.exitCode})`}`,
`- 命令: \`${info.command}\``,
`- PID: ${info.process.pid}`,
`- 运行时间: ${uptime}s`,
`- 目录: ${info.cwd}`,
].join("\n");
}
case "logs": {
const serverName = getServerName(project_path, name);
const info = managedProcesses.get(serverName);
if (!info) {
return `❌ 未找到服务器 "${serverName}"`;
}
const recentLogs = info.logs.slice(-tail);
return [
`# 📋 ${serverName} 日志(最近 ${tail} 行)`,
``,
"```",
recentLogs.join("\n") || "(无日志)",
"```",
].join("\n");
}
default:
return `❌ 未知操作: ${action}`;
}
}
//# sourceMappingURL=devServer.js.map