Update code
This commit is contained in:
229
dev-assistant-mcp/dist/tools/devServer.js
vendored
Normal file
229
dev-assistant-mcp/dist/tools/devServer.js
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
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
|
||||
Reference in New Issue
Block a user