import fs from "fs"; import path from "path"; import {spawn} from "child_process"; import {fileURLToPath, pathToFileURL} from "url"; // 获取 pluginManager.js 的目录 const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // .plugins.js 和 .plugins.example.js 在上级目录 const userConfigPath = path.join(__dirname, "../.plugins.js"); const exampleConfigPath = path.join(__dirname, "../.plugins.example.js"); // 尝试加载用户配置,如果没有就用 example let plugins = []; try { console.log(`检查插件配置文件: ${userConfigPath} 是否存在`); if (fs.existsSync(userConfigPath)) { plugins = (await import(pathToFileURL(userConfigPath).href)).default; console.log("[pluginManager] 使用用户 .plugins.js 配置"); } else if (fs.existsSync(exampleConfigPath)) { plugins = (await import(pathToFileURL(exampleConfigPath).href)).default; console.log("[pluginManager] 使用默认 .plugins.example.js 配置"); } } catch (err) { console.error("[pluginManager] 加载插件配置失败:", err); plugins = []; } /** * 获取插件对应的二进制文件路径 * @param {string} rootDir 项目根目录 * @param {string} pluginPath 插件目录路径 (例: plugins/req-proxy) * @param {string} pluginName 插件名 (例: req-proxy) */ function getPluginBinary(rootDir, pluginPath, pluginName) { const platform = process.platform; const binDir = path.join(rootDir, pluginPath); let binaryName = null; if (platform === "win32") { binaryName = `${pluginName}-win.exe`; } else if (platform === "linux") { binaryName = `${pluginName}-linux`; } else if (platform === "darwin") { binaryName = `${pluginName}-darwin`; } else if (platform === "android") { binaryName = `${pluginName}-android`; } else { console.log("[getPluginBinary] Unsupported platform: " + platform); return null; } return path.join(binDir, binaryName); } function ensureExecutable(filePath) { if (process.platform === "win32") { // Windows 不需要 chmod,直接返回 return; } try { const stats = fs.statSync(filePath); if (!(stats.mode & 0o111)) { fs.chmodSync(filePath, 0o755); console.log(`[pluginManager] 已为插件 ${filePath} 添加执行权限`); } } catch (err) { console.error(`[pluginManager] 无法设置执行权限: ${filePath}`, err.message); } } /** * 启动插件 * @param {Object} plugin 插件配置 * @param {string} rootDir 项目根目录 */ function startPlugin(plugin, rootDir) { if (!plugin.active) { // 这个检查主要用于直接调用startPlugin函数的情况 // 正常情况下startAllPlugins已经在调用前检查了激活状态 return null; } const binary = getPluginBinary(rootDir, plugin.path, plugin.name); if (!binary || !fs.existsSync(binary)) { console.error(`[pluginManager] 插件 ${plugin.name} 的二进制文件不存在: ${binary}`); return null; } console.log(`[pluginManager] 启动插件 ${plugin.name}: ${binary} ${plugin.params || ""}`); const args = plugin.params ? plugin.params.split(" ") : []; let proc; try { ensureExecutable(binary); // 用 pipe 方式,便于我们捕获插件日志 proc = spawn(binary, args, {stdio: ["ignore", "pipe", "pipe"]}); // 检查是否真的启动了 if (!proc || !proc.pid) { console.error(`[pluginManager] 插件 ${plugin.name} 启动失败 (无效的进程 PID)`); return null; } proc.stdout.on("data", (data) => { console.log(`[${plugin.name}]`, data.toString().trim()); }); proc.stderr.on("data", (data) => { console.log(`[${plugin.name}-STD]`, data.toString().trim()); }); proc.on("error", (err) => { if (err.code === "EACCES") { console.error(`[pluginManager] 插件 ${plugin.name} 无法执行: 没有执行权限,请运行: chmod +x ${binary}`); } else if (err.code === "ENOENT") { console.error(`[pluginManager] 插件 ${plugin.name} 启动失败: 找不到可执行文件 ${binary}`); } else { console.error(`[pluginManager] 插件 ${plugin.name} 运行中出错:`, err.message); } // 标记为“启动失败”,避免 exit 再重复打印 proc._failedToSpawn = true; }); proc.on("exit", (code, signal) => { if (proc._failedToSpawn) return; // 忽略 spawn 失败导致的 exit console.log(`[pluginManager] 插件 ${plugin.name} 退出 (code=${code}, signal=${signal})`); }); return proc; } catch (err) { console.error(`[pluginManager] 插件 ${plugin.name} 启动失败 (spawn 出错):`, err.message); return null; } } /** * 生成插件唯一 key * @param {Object} plugin 插件配置 * @param {number} index 插件在配置里的序号 */ function getProcessKey(plugin, index) { if (plugin.id) return plugin.id; // 用户自定义 id return `${plugin.name}#${index + 1}`; } /** * 启动所有插件 * @param {string} rootDir 项目根目录 */ export function startAllPlugins(rootDir = process.cwd()) { console.log("[pluginManager] 准备启动所有插件..."); const processes = {}; const inactivePlugins = []; plugins.forEach((plugin, index) => { const key = getProcessKey(plugin, index); // 先检查插件是否激活,未激活的插件收集到数组中 if (!plugin.active) { inactivePlugins.push(plugin.name); return; } const proc = startPlugin(plugin, rootDir); if (proc) { processes[key] = proc; console.log(`[pluginManager] 插件已启动并注册进程: ${key} (pid=${proc.pid})`); } else { console.error(`[pluginManager] 插件 ${key} 启动失败,未加入到 processes`); } }); // 如果有未激活的插件,在一行中显示 if (inactivePlugins.length > 0) { console.log(`[pluginManager] 跳过未激活的插件: [${inactivePlugins.map(name => `'${name}'`).join(',')}]`); } return processes; } /** * 停止指定插件 * @param {Object} processes 插件进程字典 * @param {string} key 插件唯一 key */ export function stopPlugin(processes, key) { const proc = processes[key]; if (!proc) { console.warn(`[pluginManager] 未找到插件进程: ${key}`); return; } console.log(`[pluginManager] 停止插件: ${key} (pid=${proc.pid})`); try { proc.kill("SIGTERM"); delete processes[key]; } catch (err) { console.error(`[pluginManager] 停止插件 ${key} 失败:`, err); } }