|
| 1 | +import fs from "fs"; |
| 2 | +import path from "path"; |
| 3 | +import {spawn} from "child_process"; |
| 4 | +import {fileURLToPath, pathToFileURL} from "url"; |
| 5 | + |
| 6 | +// 获取 pluginManager.js 的目录 |
| 7 | +const __filename = fileURLToPath(import.meta.url); |
| 8 | +const __dirname = path.dirname(__filename); |
| 9 | + |
| 10 | +// plugin.js 和 plugin.example.js 在上级目录 |
| 11 | +const userConfigPath = path.join(__dirname, "../plugin.js"); |
| 12 | +const exampleConfigPath = path.join(__dirname, "../plugin.example.js"); |
| 13 | + |
| 14 | +// 尝试加载用户配置,如果没有就用 example |
| 15 | +let plugins = []; |
| 16 | +try { |
| 17 | + console.log(`检查插件配置文件: ${userConfigPath} 是否存在`); |
| 18 | + if (fs.existsSync(userConfigPath)) { |
| 19 | + plugins = (await import(pathToFileURL(userConfigPath).href)).default; |
| 20 | + console.log("[pluginManager] 使用用户 plugin.js 配置"); |
| 21 | + } else if (fs.existsSync(exampleConfigPath)) { |
| 22 | + plugins = (await import(pathToFileURL(exampleConfigPath).href)).default; |
| 23 | + console.log("[pluginManager] 使用默认 plugin.example.js 配置"); |
| 24 | + } |
| 25 | +} catch (err) { |
| 26 | + console.error("[pluginManager] 加载插件配置失败:", err); |
| 27 | + plugins = []; |
| 28 | +} |
| 29 | + |
| 30 | +/** |
| 31 | + * 获取插件对应的二进制文件路径 |
| 32 | + * @param {string} rootDir 项目根目录 |
| 33 | + * @param {string} pluginPath 插件目录路径 (例: plugins/req-proxy) |
| 34 | + * @param {string} pluginName 插件名 (例: req-proxy) |
| 35 | + */ |
| 36 | +function getPluginBinary(rootDir, pluginPath, pluginName) { |
| 37 | + const platform = process.platform; |
| 38 | + const binDir = path.join(rootDir, pluginPath); |
| 39 | + |
| 40 | + let binaryName = null; |
| 41 | + if (platform === "win32") { |
| 42 | + binaryName = `${pluginName}-windows.exe`; |
| 43 | + } else if (platform === "linux") { |
| 44 | + binaryName = `${pluginName}-linux`; |
| 45 | + } else if (platform === "darwin") { |
| 46 | + binaryName = `${pluginName}-darwin`; |
| 47 | + } else if (platform === "android") { |
| 48 | + binaryName = `${pluginName}-android`; |
| 49 | + } else { |
| 50 | + console.log("[getPluginBinary] Unsupported platform: " + platform); |
| 51 | + return null; |
| 52 | + } |
| 53 | + |
| 54 | + return path.join(binDir, binaryName); |
| 55 | +} |
| 56 | + |
| 57 | +/** |
| 58 | + * 启动插件 |
| 59 | + * @param {Object} plugin 插件配置 |
| 60 | + * @param {string} rootDir 项目根目录 |
| 61 | + */ |
| 62 | +function startPlugin(plugin, rootDir) { |
| 63 | + if (!plugin.active) { |
| 64 | + console.log(`[pluginManager] 插件 ${plugin.name} 未激活,跳过`); |
| 65 | + return null; |
| 66 | + } |
| 67 | + |
| 68 | + const binary = getPluginBinary(rootDir, plugin.path, plugin.name); |
| 69 | + if (!binary || !fs.existsSync(binary)) { |
| 70 | + console.error(`[pluginManager] 插件 ${plugin.name} 的二进制文件不存在: ${binary}`); |
| 71 | + return null; |
| 72 | + } |
| 73 | + |
| 74 | + console.log(`[pluginManager] 启动插件 ${plugin.name}: ${binary} ${plugin.params || ""}`); |
| 75 | + |
| 76 | + const args = plugin.params ? plugin.params.split(" ") : []; |
| 77 | + let proc; |
| 78 | + |
| 79 | + try { |
| 80 | + proc = spawn(binary, args, { |
| 81 | + stdio: ["ignore", "pipe", "pipe"], |
| 82 | + }); |
| 83 | + } catch (err) { |
| 84 | + console.error(`[pluginManager] 插件 ${plugin.name} 启动失败 (spawn 出错):`, err.message); |
| 85 | + return null; |
| 86 | + } |
| 87 | + |
| 88 | + // 检查是否真的启动了 |
| 89 | + if (!proc || !proc.pid) { |
| 90 | + console.error(`[pluginManager] 插件 ${plugin.name} 启动失败 (无效的进程 PID)`); |
| 91 | + return null; |
| 92 | + } |
| 93 | + |
| 94 | + proc.stdout.on("data", (data) => { |
| 95 | + console.log(`[${plugin.name}]`, data.toString().trim()); |
| 96 | + }); |
| 97 | + |
| 98 | + proc.stderr.on("data", (data) => { |
| 99 | + console.error(`[${plugin.name}-STD]`, data.toString().trim()); |
| 100 | + }); |
| 101 | + |
| 102 | + proc.on("exit", (code, signal) => { |
| 103 | + console.log(`[pluginManager] 插件 ${plugin.name} 退出 (code=${code}, signal=${signal})`); |
| 104 | + }); |
| 105 | + |
| 106 | + proc.on("error", (err) => { |
| 107 | + console.error(`[pluginManager] 插件 ${plugin.name} 运行中出错:`, err.message); |
| 108 | + }); |
| 109 | + |
| 110 | + return proc; |
| 111 | +} |
| 112 | + |
| 113 | +/** |
| 114 | + * 启动所有插件 |
| 115 | + * @param {string} rootDir 项目根目录 |
| 116 | + */ |
| 117 | +export function startAllPlugins(rootDir = process.cwd()) { |
| 118 | + console.log("[pluginManager] 准备启动所有插件..."); |
| 119 | + const processes = {}; |
| 120 | + for (const plugin of plugins) { |
| 121 | + const proc = startPlugin(plugin, rootDir); |
| 122 | + if (proc) { |
| 123 | + processes[plugin.name] = proc; |
| 124 | + } else { |
| 125 | + console.error(`[pluginManager] 插件 ${plugin.name} 启动失败,未加入到 processes`); |
| 126 | + } |
| 127 | + } |
| 128 | + return processes; |
| 129 | +} |
0 commit comments