UNPKG

mira-app-server

Version:

Mira Server - standalone server application using mira-app-core

850 lines 40.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.PluginRoutes = void 0; const express_1 = require("express"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const child_process_1 = require("child_process"); class PluginRoutes { constructor(backend) { this.backend = backend; this.router = (0, express_1.Router)(); this.setupRoutes(); } /** * 通用方法:将插件数据格式化为标准格式 */ formatPluginInfo(plugin, libraryId) { return { id: `${libraryId}`, // 使用 libraryId 作为插件ID pluginName: plugin.name, name: plugin.name, version: plugin.version || '1.0.0', description: plugin.description || '', author: plugin.author || 'Unknown', status: plugin.status || 'active', configurable: plugin.configurable || false, dependencies: plugin.dependencies || [], main: plugin.main || 'index.js', libraryId: libraryId, createdAt: plugin.createdAt || new Date().toISOString(), updatedAt: plugin.updatedAt || new Date().toISOString(), icon: plugin.icon || null, title: plugin.title || plugin.name, category: plugin.category || 'general', tags: plugin.tags || [] }; } /** * 通用方法:获取所有插件信息 */ getAllPlugins() { const plugins = []; for (const [id, libraryObj] of Object.entries(this.backend.libraries.getLibraries())) { if (libraryObj.pluginManager) { const libraryPlugins = libraryObj.pluginManager.getPluginsList(); plugins.push(...libraryPlugins.map((plugin) => this.formatPluginInfo(plugin, id))); } } return plugins; } /** * 通用方法:通过ID查找插件 * 支持格式: 纯插件名(需提供libraryId)、libraryId-pluginName、或直接libraryId */ findPluginById(id, queryLibraryId) { // 优先使用查询参数中的 libraryId + 插件名查找 if (queryLibraryId) { const libraryObj = this.backend.libraries.getLibrary(queryLibraryId); if (libraryObj && libraryObj.pluginManager) { const libraryPlugins = libraryObj.pluginManager.getPluginsList(); const foundPlugin = libraryPlugins.find((p) => p.name === id); if (foundPlugin) { const plugin = this.formatPluginInfo(foundPlugin, queryLibraryId); return { plugin, libraryObj }; } } } // 尝试通过 libraryId 查找 const libraryObj = this.backend.libraries.getLibrary(id); if (libraryObj && libraryObj.pluginManager) { const libraryPlugins = libraryObj.pluginManager.getPluginsList(); if (libraryPlugins.length > 0) { const plugin = this.formatPluginInfo(libraryPlugins[0], id); return { plugin, libraryObj }; } } // 如果没找到,遍历所有库查找插件名 for (const [libId, lib] of Object.entries(this.backend.libraries.getLibraries())) { if (lib.pluginManager) { const libraryPlugins = lib.pluginManager.getPluginsList(); const foundPlugin = libraryPlugins.find((p) => p.name === id); if (foundPlugin) { const plugin = this.formatPluginInfo(foundPlugin, libId); return { plugin, libraryObj: lib }; } } } return null; } /** * 通用方法:验证库是否存在且有插件管理器 */ validateLibrary(libraryId) { const library = this.backend.libraries.getLibrary(libraryId); if (!library) { return { valid: false, error: `Library ${libraryId} not found`, library: null }; } if (!library.pluginManager) { return { valid: false, error: '素材库未初始化,请检查是否被禁用!', library: null }; } return { valid: true, error: null, library }; } /** * 通用方法:读取插件配置文件 */ readPluginsConfig(pluginsDir) { const pluginsConfigPath = path.join(pluginsDir, 'plugins.json'); try { if (fs.existsSync(pluginsConfigPath)) { return JSON.parse(fs.readFileSync(pluginsConfigPath, 'utf-8')); } } catch (error) { console.error('Error reading plugins config:', error); } return []; } /** * 通用方法:写入插件配置文件 */ writePluginsConfig(pluginsDir, config) { const pluginsConfigPath = path.join(pluginsDir, 'plugins.json'); try { fs.writeFileSync(pluginsConfigPath, JSON.stringify(config, null, 2)); return true; } catch (error) { console.error('Error writing plugins config:', error); return false; } } setupRoutes() { // 获取素材库当前启用、且提供 Web 前端的服务端插件 this.router.get('/web', async (req, res) => { try { const libraryId = typeof req.query.libraryId === 'string' ? req.query.libraryId : ''; if (!libraryId) { return res.status(400).json({ error: 'Library ID is required' }); } const validation = this.validateLibrary(libraryId); if (!validation.valid) { return res.status(404).json({ error: validation.error }); } const plugins = validation.library.pluginManager.getLoadedWebPlugins().map((plugin) => ({ ...plugin, libraryId, url: `/server-plugins/${encodeURIComponent(libraryId)}/${encodeURIComponent(plugin.serverPluginName)}`, })); res.json(plugins); } catch (error) { console.error('Error getting server Web plugins:', error); res.status(500).json({ error: 'Failed to get server Web plugins' }); } }); // 获取插件列表 this.router.get('/', async (req, res) => { try { const plugins = this.getAllPlugins(); res.json(plugins); } catch (error) { console.error('Error getting plugins:', error); res.status(500).json({ error: 'Failed to get plugins' }); } }); // 按素材库分组获取插件列表 this.router.get('/by-library', async (req, res) => { try { const librariesWithPlugins = []; for (const [id, libraryObj] of Object.entries(this.backend.libraries.getLibraries())) { const libraryInfo = { id: id, name: libraryObj.libraryService?.config.name || id, description: `素材库: ${id}`, plugins: [] }; if (libraryObj.pluginManager) { const libraryPlugins = libraryObj.pluginManager.getPluginsList(); libraryInfo.plugins = libraryPlugins.map((plugin) => this.formatPluginInfo(plugin, id)); } librariesWithPlugins.push(libraryInfo); } res.json(librariesWithPlugins); } catch (error) { console.error('Error getting plugins by library:', error); res.status(500).json({ error: 'Failed to get plugins by library' }); } }); // 安装插件(从npm) this.router.post('/install', async (req, res) => { try { const { name, version = 'latest', libraryId, proxy, npmSource, registry } = req.body; if (!name) { return res.status(400).json({ error: 'Plugin name is required' }); } if (!libraryId) { return res.status(400).json({ error: 'Library ID is required' }); } // 验证库 const validation = this.validateLibrary(libraryId); if (!validation.valid) { return res.status(400).json({ error: validation.error }); } const library = validation.library; let pluginsDir = library.pluginManager.pluginsDir; // 确保插件目录存在 if (!fs.existsSync(pluginsDir)) { fs.mkdirSync(pluginsDir, { recursive: true }); } const packageName = version === 'latest' ? name : `${name}@${version}`; // 支持通过环境变量设置npm代理 const env = { ...process.env }; if (proxy) { env.HTTP_PROXY = proxy; env.HTTPS_PROXY = proxy; } else if (!env.HTTP_PROXY && !env.HTTPS_PROXY) { env.HTTP_PROXY = 'http://127.0.0.1:7890'; env.HTTPS_PROXY = 'http://127.0.0.1:7890'; } // npm 源映射: 优先用请求传入的自定义 registry, 否则按 npmSource 映射 const registryMap = { npmmirror: 'https://registry.npmmirror.com', npm: 'https://registry.npmjs.org', }; const useRegistry = registry || registryMap[npmSource || 'npmmirror'] || registryMap.npmmirror; // 初始化npm项目 (0, child_process_1.spawnSync)('npm', ['init', '-y'], { cwd: pluginsDir, stdio: 'pipe', shell: true }); const npmProcess = (0, child_process_1.spawn)('npm', ['install', packageName, '-save', '--registry', useRegistry], { cwd: pluginsDir, stdio: 'pipe', env, shell: true, timeout: 60000 }); let output = ''; let errorOutput = ''; let isTimedOut = false; const timeoutId = setTimeout(() => { isTimedOut = true; npmProcess.kill('SIGTERM'); console.error('npm install timeout after 60 seconds'); }, 60000); npmProcess.stdout.on('data', (data) => { output += data.toString(); }); npmProcess.stderr.on('data', (data) => { errorOutput += data.toString(); }); npmProcess.on('close', async (code) => { clearTimeout(timeoutId); if (isTimedOut) { return res.status(500).json({ error: 'Plugin installation timeout (60s). Please check your network connection.', details: 'Installation was terminated due to timeout' }); } if (code === 0) { try { const pluginDir = path.join(pluginsDir, 'node_modules', name); let packageInfo = {}; try { const packageJsonPath = path.join(pluginDir, 'package.json'); if (fs.existsSync(packageJsonPath)) { packageInfo = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); } } catch (error) { console.error('Error reading package.json:', error); } await library.pluginManager.addPlugin({ name: name, enabled: false, path: `node_modules/${name}`, registry: useRegistry }); res.json({ message: 'Plugin installed successfully', plugin: { name, version: packageInfo.version || version, ...packageInfo } }); } catch (error) { console.error('Error configuring plugin:', error); res.status(500).json({ error: 'Plugin installed but configuration failed' }); } } else { console.error('npm install failed:', errorOutput); const isTimeout = errorOutput.includes('timeout') || errorOutput.includes('ETIMEDOUT') || (code === null && errorOutput === ''); res.status(500).json({ error: isTimeout ? 'Plugin installation timeout (60s). Please check your network connection.' : 'Failed to install plugin', details: errorOutput || 'Installation timeout' }); } }); } catch (error) { console.error('Error installing plugin:', error); res.status(500).json({ error: 'Failed to install plugin' }); } }); // 同步插件 meta (从 package.json 重新提取并持久化到 plugins.json) this.router.post('/sync-meta', async (req, res) => { try { const { libraryId } = req.body; if (!libraryId) { return res.status(400).json({ error: 'Library ID is required' }); } const validation = this.validateLibrary(libraryId); if (!validation.valid) { return res.status(400).json({ error: validation.error }); } const count = validation.library.pluginManager.syncPluginsMeta(); res.json({ success: true, count }); } catch (error) { console.error('Error syncing plugin meta:', error); res.status(500).json({ error: 'Failed to sync plugin meta' }); } }); // 上传安装插件 this.router.post('/upload', async (req, res) => { try { const multer = require('multer'); const yauzl = require('yauzl'); // 配置multer用于文件上传 const storage = multer.memoryStorage(); const upload = multer({ storage: storage, limits: { fileSize: 100 * 1024 * 1024 }, // 限制文件大小为100MB fileFilter: (req, file, cb) => { // 只允许zip和tar.gz文件 if (file.mimetype === 'application/zip' || file.mimetype === 'application/x-zip-compressed' || file.originalname.endsWith('.zip') || file.originalname.endsWith('.tar.gz')) { cb(null, true); } else { cb(new Error('只支持 .zip 和 .tar.gz 格式的插件包'), false); } } }).single('file'); // 使用multer处理文件上传 upload(req, res, async (err) => { if (err) { console.error('File upload error:', err); return res.status(400).json({ error: err.message || 'File upload failed' }); } const file = req.file; // 修正:使用 req.file 而不是 req.body.file const libraryId = req.body.libraryId; if (!file) { return res.status(400).json({ error: 'No file uploaded' }); } if (!libraryId) { return res.status(400).json({ error: 'No library ID provided' }); } try { // 确定插件安装目录 const library = this.backend.libraries.getLibrary(libraryId); if (!library) { return res.status(400).json({ error: `Library ${libraryId} not found` }); } let pluginsDir = library.pluginManager.pluginsDir; if (!library.pluginManager) { return res.status(400).json({ error: `素材库未初始化,请检查是否被禁用!` }); } // 确保插件目录存在 if (!fs.existsSync(pluginsDir)) { fs.mkdirSync(pluginsDir, { recursive: true }); } // 创建临时文件 const tempFilePath = path.join(pluginsDir, `temp_${Date.now()}_${file.originalname}`); fs.writeFileSync(tempFilePath, file.buffer); let pluginName = ''; let packageInfo = {}; try { if (file.originalname.endsWith('.zip')) { // 处理ZIP文件 await new Promise((resolve, reject) => { yauzl.open(tempFilePath, { lazyEntries: true }, (err, zipfile) => { if (err) return reject(err); zipfile.readEntry(); zipfile.on('entry', (entry) => { if (/\/$/.test(entry.fileName)) { // 目录 zipfile.readEntry(); } else { // 文件 const outputPath = path.join(pluginsDir, entry.fileName); const outputDir = path.dirname(outputPath); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } zipfile.openReadStream(entry, (err, readStream) => { if (err) return reject(err); const writeStream = fs.createWriteStream(outputPath); readStream.pipe(writeStream); writeStream.on('finish', () => { // 检查是否是package.json文件(暂时不需要) // if (entry.fileName.endsWith('package.json') && !entry.fileName.includes('/')) { // try { // packageInfo = JSON.parse(fs.readFileSync(outputPath, 'utf-8')); // pluginName = packageInfo.name || path.basename(entry.fileName, '.json'); // } catch (e) { // console.warn('Error reading package.json:', e); // } // } zipfile.readEntry(); }); writeStream.on('error', reject); }); } }); zipfile.on('end', () => { resolve(); }); zipfile.on('error', reject); }); }); } else { // 暂不支持tar.gz,返回错误 throw new Error('目前仅支持 .zip 格式的插件包'); } // 删除临时文件 if (fs.existsSync(tempFilePath)) { fs.unlinkSync(tempFilePath); } // 解压后从 package.json 识别真实插件名与顶层目录, 让 addPlugin 能提取 meta const baseName = path.basename(file.originalname, path.extname(file.originalname)); let pluginPath = baseName; for (const entry of fs.readdirSync(pluginsDir)) { const pj = path.join(pluginsDir, entry, 'package.json'); if (fs.existsSync(pj)) { try { packageInfo = JSON.parse(fs.readFileSync(pj, 'utf-8')); pluginPath = entry; break; } catch (e) { console.warn('Error reading package.json:', e); } } } // zip 无顶层目录时, package.json 直接在 pluginsDir 根的兜底 if (!packageInfo.name) { const rootPj = path.join(pluginsDir, 'package.json'); if (fs.existsSync(rootPj)) { try { packageInfo = JSON.parse(fs.readFileSync(rootPj, 'utf-8')); } catch (e) { /* ignore */ } } } pluginName = packageInfo.name || baseName; // 为指定库的插件管理器添加新插件 (addPlugin 会自动提取并持久化 meta) const library = this.backend.libraries.getLibrary(libraryId); if (library && library.pluginManager) { await library.pluginManager.addPlugin({ name: pluginName, enabled: false, path: pluginPath }); } res.json({ message: '插件上传安装成功', plugin: { name: pluginName, version: packageInfo.version || '1.0.0', ...packageInfo } }); } catch (extractError) { // 删除临时文件 if (fs.existsSync(tempFilePath)) { fs.unlinkSync(tempFilePath); } throw extractError; } } catch (error) { console.error('Error processing uploaded plugin:', error); res.status(500).json({ error: 'Failed to process uploaded plugin', details: error.message }); } }); } catch (error) { console.error('Error uploading plugin:', error); res.status(500).json({ error: 'Failed to upload plugin' }); } }); // 插件状态切换 (POST方法,避免URL字符冲突) this.router.post('/toggle-status', async (req, res) => { try { const { libraryId, pluginName, status } = req.body; if (!libraryId || !pluginName || !status) { return res.status(400).json({ error: 'Library ID, plugin name and status are required' }); } const validation = this.validateLibrary(libraryId); if (!validation.valid) { return res.status(404).json({ error: validation.error }); } const library = validation.library; let config = this.readPluginsConfig(library.pluginManager.pluginsDir); const pluginIndex = config.findIndex((p) => p.name === pluginName); if (pluginIndex === -1) { return res.status(404).json({ error: 'Plugin not found in config' }); } config[pluginIndex].enabled = status === 'active'; config[pluginIndex].status = status; if (!this.writePluginsConfig(library.pluginManager.pluginsDir, config)) { return res.status(500).json({ error: 'Failed to update plugin config' }); } // 精确处理插件加载/卸载 try { if (status === 'active') { const pluginConfig = { name: pluginName, enabled: true, path: config[pluginIndex].path }; await library.pluginManager.loadPlugin(pluginConfig, false); } else { library.pluginManager.unloadPlugin(pluginName); } } catch (error) { console.error('Error managing plugin:', error); } res.json({ success: true, message: `Plugin ${status === 'active' ? 'activated' : 'deactivated'} successfully`, pluginName: pluginName, status: status }); } catch (error) { console.error('Error toggling plugin status:', error); res.status(500).json({ error: 'Failed to toggle plugin status' }); } }); // 启动插件 this.router.post('/:id/start', async (req, res) => { try { const { id } = req.params; const result = this.findPluginById(id, req.query.libraryId); if (!result) { return res.status(404).json({ error: 'Plugin not found', id: id }); } const { plugin, libraryObj } = result; let config = this.readPluginsConfig(libraryObj.pluginManager.pluginsDir); const pluginIndex = config.findIndex((p) => p.name === plugin.pluginName); if (pluginIndex === -1) { return res.status(404).json({ error: 'Plugin not found in config' }); } config[pluginIndex].enabled = true; config[pluginIndex].status = 'active'; if (!this.writePluginsConfig(libraryObj.pluginManager.pluginsDir, config)) { return res.status(500).json({ error: 'Failed to update plugin config' }); } try { const pluginConfig = { name: plugin.pluginName, enabled: true, path: config[pluginIndex].path }; await libraryObj.pluginManager.loadPlugin(pluginConfig, false); } catch (error) { console.error('Error starting plugin:', error); return res.status(500).json({ error: 'Failed to start plugin' }); } res.json({ success: true, message: 'Plugin started successfully', pluginId: id, status: 'active' }); } catch (error) { console.error('Error starting plugin:', error); res.status(500).json({ error: 'Failed to start plugin' }); } }); // 停止插件 this.router.post('/:id/stop', async (req, res) => { try { const { id } = req.params; const result = this.findPluginById(id, req.query.libraryId); if (!result) { return res.status(404).json({ error: 'Plugin not found', id: id }); } const { plugin, libraryObj } = result; let config = this.readPluginsConfig(libraryObj.pluginManager.pluginsDir); const pluginIndex = config.findIndex((p) => p.name === plugin.pluginName); if (pluginIndex === -1) { return res.status(404).json({ error: 'Plugin not found in config' }); } config[pluginIndex].enabled = false; config[pluginIndex].status = 'inactive'; if (!this.writePluginsConfig(libraryObj.pluginManager.pluginsDir, config)) { return res.status(500).json({ error: 'Failed to update plugin config' }); } try { libraryObj.pluginManager.unloadPlugin(plugin.pluginName); } catch (error) { console.error('Error stopping plugin:', error); return res.status(500).json({ error: 'Failed to stop plugin' }); } res.json({ success: true, message: 'Plugin stopped successfully', pluginId: id, status: 'inactive' }); } catch (error) { console.error('Error stopping plugin:', error); res.status(500).json({ error: 'Failed to stop plugin' }); } }); // 获取插件配置 this.router.get('/:id/config', async (req, res) => { try { const { id } = req.params; const result = this.findPluginById(id, req.query.libraryId); if (!result) { return res.status(404).json({ error: 'Plugin not found', id: id }); } const { plugin, libraryObj } = result; const pluginDir = libraryObj.pluginManager.getPluginDir(plugin.pluginName); const configPath = path.join(pluginDir, 'config.json'); let config = {}; if (fs.existsSync(configPath)) { try { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch (error) { res.json({}); } } res.json(config); } catch (error) { console.error('Error getting plugin config:', error); res.status(500).json({ error: 'Failed to get plugin config' }); } }); // 更新插件配置 this.router.put('/:id/config', async (req, res) => { try { const { id } = req.params; const config = req.body; const result = this.findPluginById(id, req.query.libraryId); if (!result) { return res.status(404).json({ error: 'Plugin not found', id: id }); } const { plugin, libraryObj } = result; const pluginDir = libraryObj.pluginManager.getPluginDir(plugin.pluginName); const configPath = path.join(pluginDir, 'config.json'); try { fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); } catch (error) { console.error('Error writing config file:', error); return res.status(500).json({ error: 'Failed to write config file' }); } res.json({ success: true, message: 'Plugin config updated successfully' }); } catch (error) { console.error('Error updating plugin config:', error); res.status(500).json({ error: 'Failed to update plugin config' }); } }); // 卸载插件 this.router.delete('/:id', async (req, res) => { try { const { id } = req.params; const result = this.findPluginById(id, req.query.libraryId); if (!result) { return res.status(404).json({ error: 'Plugin not found', id: id }); } const { plugin, libraryObj } = result; let config = this.readPluginsConfig(libraryObj.pluginManager.pluginsDir); // 从配置中移除 const newConfig = config.filter((p) => p.name !== plugin.pluginName); if (!this.writePluginsConfig(libraryObj.pluginManager.pluginsDir, newConfig)) { return res.status(500).json({ error: 'Failed to update plugin config' }); } // 尝试使用npm卸载(如果是npm安装的) const { spawn } = require('child_process'); const npmProcess = spawn('npm', ['uninstall', plugin.pluginName], { cwd: libraryObj.pluginManager.pluginsDir, stdio: 'pipe', shell: true }); npmProcess.on('close', (code) => { if (code === 0) { console.log(`Plugin ${plugin.pluginName} uninstalled successfully`); } else { console.warn(`Failed to uninstall plugin ${plugin.pluginName} via npm`); } }); res.json({ success: true, message: 'Plugin uninstalled successfully', pluginId: id, pluginName: plugin.pluginName }); } catch (error) { console.error('Error uninstalling plugin:', error); res.status(500).json({ error: 'Failed to uninstall plugin' }); } }); // 获取插件图标 this.router.get('/:pluginName/icon/:filename', async (req, res) => { try { const { pluginName, filename } = req.params; // 查找插件所在的库 let pluginDir = null; for (const [id, libraryObj] of Object.entries(this.backend.libraries.getLibraries())) { if (libraryObj.pluginManager) { const testPluginDir = libraryObj.pluginManager.getPluginDir(pluginName); const iconPath = path.join(testPluginDir, filename); if (fs.existsSync(iconPath)) { pluginDir = testPluginDir; break; } } } if (!pluginDir) { return res.status(404).json({ error: 'Plugin icon not found' }); } const iconPath = path.join(pluginDir, filename); if (!fs.existsSync(iconPath)) { return res.status(404).json({ error: 'Icon file not found' }); } // 设置合适的Content-Type const ext = path.extname(filename).toLowerCase(); const mimeTypes = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.svg': 'image/svg+xml', '.ico': 'image/x-icon' }; const contentType = mimeTypes[ext] || 'application/octet-stream'; res.setHeader('Content-Type', contentType); res.setHeader('Cache-Control', 'public, max-age=86400'); // 缓存1天 const fileStream = fs.createReadStream(iconPath); fileStream.pipe(res); } catch (error) { console.error('Error serving plugin icon:', error); res.status(500).json({ error: 'Failed to serve plugin icon' }); } }); // 获取单个插件信息 - 放在最后避免路由冲突 this.router.get('/:id', async (req, res) => { try { const { id } = req.params; const result = this.findPluginById(id, req.query.libraryId); if (!result) { return res.status(404).json({ error: 'Plugin not found', id: id, message: `Plugin with ID '${id}' does not exist` }); } res.json(result.plugin); } catch (error) { console.error('Error getting plugin:', error); res.status(500).json({ error: 'Failed to get plugin information' }); } }); } getRouter() { return this.router; } } exports.PluginRoutes = PluginRoutes; //# sourceMappingURL=PluginRoutes.js.map