mira-app-core
Version:
Core library for Mira TypeScript project - provides base functionality without auto-execution
596 lines • 31 kB
JavaScript
;
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();
}
setupRoutes() {
// 获取插件列表
this.router.get('/', async (req, res) => {
try {
const plugins = [];
for (const [id, libraryObj] of Object.entries(this.backend.libraries.libraries)) {
if (libraryObj.pluginManager) {
// 获取该库的插件信息
const libraryPlugins = libraryObj.pluginManager.getPluginsList();
plugins.push(...libraryPlugins.map((plugin) => ({
id: id, // 使用纯library ID作为插件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: id,
createdAt: plugin.createdAt || new Date().toISOString(),
updatedAt: plugin.updatedAt || new Date().toISOString(),
icon: plugin.icon || null,
category: plugin.category || 'general',
tags: plugin.tags || []
})));
}
}
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.libraries)) {
const libraryInfo = {
id: id,
name: libraryObj.libraryService?.config.name || id,
description: `素材库: ${id}`, // TODO: desc
plugins: []
};
if (libraryObj.pluginManager) {
// 获取该库的插件信息
const libraryPlugins = libraryObj.pluginManager.getPluginsList();
libraryInfo.plugins = libraryPlugins.map((plugin) => ({
id: id, // 使用纯library ID作为插件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: id,
createdAt: plugin.createdAt || new Date().toISOString(),
updatedAt: plugin.updatedAt || new Date().toISOString(),
icon: plugin.icon || null,
category: plugin.category || 'general',
tags: plugin.tags || []
}));
}
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 } = 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' });
}
// 如果指定了库ID,安装到对应库的插件目录
const library = this.backend.libraries.libraries[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 packageName = version === 'latest' ? name : `${name}@${version}`;
// 支持通过环境变量设置npm代理
const env = { ...process.env };
// 如果设置了 HTTP_PROXY/HTTPS_PROXY 环境变量,则传递给子进程
// 也支持自定义代理端口(如 127.0.0.1:7890)
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项目
(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'], {
cwd: pluginsDir,
stdio: 'pipe',
env,
shell: true, // 使用 shell 模式,兼容所有平台
timeout: 60000 // 设置超时时间为60秒
});
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}`
});
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' });
}
});
// 上传安装插件
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.libraries[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);
}
// 如果没有找到插件名称,使用文件名
if (!pluginName) {
pluginName = path.basename(file.originalname, path.extname(file.originalname));
}
// 为指定库或所有库的插件管理器添加新插件
const library = this.backend.libraries.libraries[libraryId];
if (library && library.pluginManager) {
await library.pluginManager.addPlugin({
name: pluginName,
enabled: false,
path: 'node_modules/' + pluginName
});
}
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 libraryObj = this.backend.libraries.get(libraryId);
if (!libraryObj || !libraryObj.pluginManager) {
return res.status(404).json({ error: 'Library or plugin manager not found' });
}
// 更新插件状态
const pluginsConfigPath = path.join(libraryObj.pluginManager.pluginsDir, 'plugins.json');
let config = [];
try {
if (fs.existsSync(pluginsConfigPath)) {
config = JSON.parse(fs.readFileSync(pluginsConfigPath, 'utf-8'));
}
}
catch (error) {
console.error('Error reading plugins config:', error);
config = [];
}
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;
// 写回配置文件
try {
fs.writeFileSync(pluginsConfigPath, JSON.stringify(config, null, 2));
}
catch (error) {
console.error('Error writing plugins config:', error);
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 libraryObj.pluginManager.loadPlugin(pluginConfig, false);
}
else {
// 禁用插件:卸载插件
libraryObj.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.get('/:id/config', async (req, res) => {
try {
const { id } = req.params;
const [libraryId, pluginName] = id.split('-', 2);
const libraryObj = this.backend.libraries.get(libraryId);
if (!libraryObj || !libraryObj.pluginManager) {
return res.status(404).json({ error: 'Library or plugin manager not found' });
}
const pluginDir = libraryObj.pluginManager.getPluginDir(pluginName);
const configPath = path.join(pluginDir, 'config.json');
let config = {};
if (fs.existsSync(configPath)) {
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
}
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 [libraryId, pluginName] = id.split('-', 2);
const libraryObj = this.backend.libraries.get(libraryId);
if (!libraryObj || !libraryObj.pluginManager) {
return res.status(404).json({ error: 'Library or plugin manager not found' });
}
const pluginDir = libraryObj.pluginManager.getPluginDir(pluginName);
const configPath = path.join(pluginDir, 'config.json');
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
res.json({ 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 [libraryId, pluginName] = id.split('-', 2);
const libraryObj = this.backend.libraries.get(libraryId);
if (!libraryObj || !libraryObj.pluginManager) {
return res.status(404).json({ error: 'Library or plugin manager not found' });
}
// 先从配置中移除
const pluginsConfigPath = path.join(libraryObj.pluginManager.pluginsDir, 'plugins.json');
const config = JSON.parse(fs.readFileSync(pluginsConfigPath, 'utf-8'));
const newConfig = config.filter((p) => p.name !== pluginName);
fs.writeFileSync(pluginsConfigPath, JSON.stringify(newConfig, null, 2));
// 尝试使用npm卸载(如果是npm安装的)
const { spawn } = require('child_process');
// 使用 shell 模式执行 npm 命令,兼容所有平台
const npmProcess = spawn('npm', ['uninstall', pluginName], {
cwd: libraryObj.pluginManager.pluginsDir,
stdio: 'pipe',
shell: true // 使用 shell 模式,兼容所有平台
});
npmProcess.on('close', (code) => {
if (code === 0) {
console.log(`Plugin ${pluginName} uninstalled successfully`);
}
else {
console.warn(`Failed to uninstall plugin ${pluginName} via npm`);
}
});
res.json({ message: 'Plugin uninstalled successfully' });
}
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.libraries)) {
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' });
}
});
}
getRouter() {
return this.router;
}
}
exports.PluginRoutes = PluginRoutes;
//# sourceMappingURL=PluginRoutes.js.map