feat(plugins): implement plugin system with discovery and management
This commit is contained in:
		
							parent
							
								
									1a2d8ab19a
								
							
						
					
					
						commit
						f99f9a945c
					
				@ -5,10 +5,10 @@
 | 
				
			|||||||
  "main": "index.ts",
 | 
					  "main": "index.ts",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js",
 | 
					    "start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js",
 | 
				
			||||||
    "build": "tsgo --sourceMap && ncp static/webui build/static/webui",
 | 
					    "build": "node scripts/plugin-discovery.js && tsgo --sourceMap && ncp static/webui build/static/webui && ncp plugins build/plugins",
 | 
				
			||||||
    "build:tsc": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
 | 
					    "build:tsc": "node scripts/plugin-discovery.js && tsc --incremental --sourceMap && ncp static/webui build/static/webui && ncp plugins build/plugins",
 | 
				
			||||||
    "build:dev": "tsgo --sourceMap",
 | 
					    "build:dev": "node scripts/plugin-discovery.js && tsgo --sourceMap",
 | 
				
			||||||
    "build:dev:tsc": "tsc --incremental --sourceMap",
 | 
					    "build:dev:tsc": "node scripts/plugin-discovery.js && tsc --incremental --sourceMap",
 | 
				
			||||||
    "build-and-start": "npm run build && npm run start",
 | 
					    "build-and-start": "npm run build && npm run start",
 | 
				
			||||||
    "build-and-start:bun": "npm run verify && npm run bun-run",
 | 
					    "build-and-start:bun": "npm run verify && npm run bun-run",
 | 
				
			||||||
    "dev": "node scripts/dev.js",
 | 
					    "dev": "node scripts/dev.js",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								plugins/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								plugins/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					!.gitignore
 | 
				
			||||||
 | 
					!ExamplePlugin/
 | 
				
			||||||
 | 
					!ExamplePlugin/**
 | 
				
			||||||
							
								
								
									
										30
									
								
								plugins/ExamplePlugin/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								plugins/ExamplePlugin/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					import { IPlugin } from "@/src/types/pluginTypes";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class ExamplePlugin implements IPlugin {
 | 
				
			||||||
 | 
					    public name = "ExamplePlugin";
 | 
				
			||||||
 | 
					    public version = "1.0.0";
 | 
				
			||||||
 | 
					    public description = "Example plugin for the server";
 | 
				
			||||||
 | 
					    public author = "Your Name";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async initialize(): Promise<void> {
 | 
				
			||||||
 | 
					        logger.info(`[${this.name}] Plugin initialized successfully!`);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Add your plugin initialization logic here
 | 
				
			||||||
 | 
					        // For example:
 | 
				
			||||||
 | 
					        // - Register new routes
 | 
				
			||||||
 | 
					        // - Add new API endpoints
 | 
				
			||||||
 | 
					        // - Set up event listeners
 | 
				
			||||||
 | 
					        // - Connect to external services
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async cleanup(): Promise<void> {
 | 
				
			||||||
 | 
					        logger.info(`[${this.name}] Plugin cleanup completed`);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Add your cleanup logic here
 | 
				
			||||||
 | 
					        // For example:
 | 
				
			||||||
 | 
					        // - Close database connections
 | 
				
			||||||
 | 
					        // - Clear timers/intervals
 | 
				
			||||||
 | 
					        // - Remove event listeners
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								plugins/ExamplePlugin/plugin.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								plugins/ExamplePlugin/plugin.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "name": "ExamplePlugin",
 | 
				
			||||||
 | 
					    "version": "1.0.0",
 | 
				
			||||||
 | 
					    "description": "Example plugin for the server",
 | 
				
			||||||
 | 
					    "author": "Your Name",
 | 
				
			||||||
 | 
					    "main": "index.js",
 | 
				
			||||||
 | 
					    "dependencies": [],
 | 
				
			||||||
 | 
					    "config": {
 | 
				
			||||||
 | 
					        "enabled": false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										127
									
								
								scripts/plugin-discovery.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								scripts/plugin-discovery.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,127 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Plugin Discovery Script
 | 
				
			||||||
 | 
					 * This script runs during build time to discover all plugins and generate a registry
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fs = require('fs');
 | 
				
			||||||
 | 
					const path = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PluginDiscovery {
 | 
				
			||||||
 | 
					    constructor(pluginsDir = 'plugins', outputPath = 'build/plugin-registry.json') {
 | 
				
			||||||
 | 
					        this.pluginsDir = path.resolve(pluginsDir);
 | 
				
			||||||
 | 
					        this.outputPath = path.resolve(outputPath);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Discover all plugins in the plugins directory
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    discoverPlugins() {
 | 
				
			||||||
 | 
					        const registry = {
 | 
				
			||||||
 | 
					            plugins: [],
 | 
				
			||||||
 | 
					            manifest: {},
 | 
				
			||||||
 | 
					            buildTime: new Date().toISOString()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log(`🔍 Discovering plugins in: ${this.pluginsDir}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!fs.existsSync(this.pluginsDir)) {
 | 
				
			||||||
 | 
					            console.log('⚠️  Plugins directory not found, creating empty registry');
 | 
				
			||||||
 | 
					            return registry;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const pluginDirs = fs.readdirSync(this.pluginsDir, { withFileTypes: true })
 | 
				
			||||||
 | 
					            .filter(dirent => dirent.isDirectory())
 | 
				
			||||||
 | 
					            .map(dirent => dirent.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const pluginDir of pluginDirs) {
 | 
				
			||||||
 | 
					            const pluginPath = path.join(this.pluginsDir, pluginDir);
 | 
				
			||||||
 | 
					            const manifest = this.loadPluginManifest(pluginPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (manifest) {
 | 
				
			||||||
 | 
					                registry.plugins.push(pluginPath);
 | 
				
			||||||
 | 
					                registry.manifest[manifest.name] = manifest;
 | 
				
			||||||
 | 
					                console.log(`✅ Found plugin: ${manifest.name} v${manifest.version}`);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.log(`❌ Invalid plugin: ${pluginDir} (missing or invalid plugin.json)`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log(`📦 Discovered ${registry.plugins.length} plugins`);
 | 
				
			||||||
 | 
					        return registry;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Load and validate plugin manifest
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    loadPluginManifest(pluginPath) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const manifestPath = path.join(pluginPath, 'plugin.json');
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!fs.existsSync(manifestPath)) {
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const manifestContent = fs.readFileSync(manifestPath, 'utf-8');
 | 
				
			||||||
 | 
					            const manifest = JSON.parse(manifestContent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Validate required fields
 | 
				
			||||||
 | 
					            if (!manifest.name || !manifest.version || !manifest.main) {
 | 
				
			||||||
 | 
					                console.log(`⚠️  Invalid manifest in ${pluginPath}: missing required fields (name, version, main)`);
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check if main file exists
 | 
				
			||||||
 | 
					            const mainFile = path.join(pluginPath, manifest.main);
 | 
				
			||||||
 | 
					            const mainTsFile = mainFile.replace(/\.js$/, '.ts');
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!fs.existsSync(mainFile) && !fs.existsSync(mainTsFile)) {
 | 
				
			||||||
 | 
					                console.log(`⚠️  Main file not found: ${mainFile} or ${mainTsFile}`);
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return manifest;
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            console.log(`❌ Failed to load manifest from ${pluginPath}:`, error.message);
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Save the plugin registry to file
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    saveRegistry(registry) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            // Ensure output directory exists
 | 
				
			||||||
 | 
					            const outputDir = path.dirname(this.outputPath);
 | 
				
			||||||
 | 
					            if (!fs.existsSync(outputDir)) {
 | 
				
			||||||
 | 
					                fs.mkdirSync(outputDir, { recursive: true });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            fs.writeFileSync(this.outputPath, JSON.stringify(registry, null, 2));
 | 
				
			||||||
 | 
					            console.log(`💾 Plugin registry saved to: ${this.outputPath}`);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            console.error(`❌ Failed to save plugin registry:`, error.message);
 | 
				
			||||||
 | 
					            process.exit(1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Main execution function
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    run() {
 | 
				
			||||||
 | 
					        console.log('🚀 Starting plugin discovery...');
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        const registry = this.discoverPlugins();
 | 
				
			||||||
 | 
					        this.saveRegistry(registry);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        console.log('✨ Plugin discovery completed!');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Run the plugin discovery if this script is executed directly
 | 
				
			||||||
 | 
					if (require.main === module) {
 | 
				
			||||||
 | 
					    const discovery = new PluginDiscovery();
 | 
				
			||||||
 | 
					    discovery.run();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = { PluginDiscovery };
 | 
				
			||||||
							
								
								
									
										20
									
								
								src/index.ts
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								src/index.ts
									
									
									
									
									
								
							@ -23,18 +23,36 @@ import { startWebServer } from "./services/webService";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
 | 
					import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
 | 
				
			||||||
import { updateWorldStateCollections } from "./services/worldStateService";
 | 
					import { updateWorldStateCollections } from "./services/worldStateService";
 | 
				
			||||||
 | 
					import { pluginManager } from "@/src/managers/pluginManager";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Patch JSON.stringify to work flawlessly with Bigints.
 | 
					// Patch JSON.stringify to work flawlessly with Bigints.
 | 
				
			||||||
JSON.stringify = JSONStringify;
 | 
					JSON.stringify = JSONStringify;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
validateConfig();
 | 
					validateConfig();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Handle graceful shutdown
 | 
				
			||||||
 | 
					process.on('SIGINT', async () => {
 | 
				
			||||||
 | 
					    logger.info('Received SIGINT, starting graceful shutdown...');
 | 
				
			||||||
 | 
					    await pluginManager.cleanup();
 | 
				
			||||||
 | 
					    process.exit(0);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					process.on('SIGTERM', async () => {
 | 
				
			||||||
 | 
					    logger.info('Received SIGTERM, starting graceful shutdown...');
 | 
				
			||||||
 | 
					    await pluginManager.cleanup();
 | 
				
			||||||
 | 
					    process.exit(0);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mongoose
 | 
					mongoose
 | 
				
			||||||
    .connect(config.mongodbUrl)
 | 
					    .connect(config.mongodbUrl)
 | 
				
			||||||
    .then(() => {
 | 
					    .then(async () => {
 | 
				
			||||||
        logger.info("Connected to MongoDB");
 | 
					        logger.info("Connected to MongoDB");
 | 
				
			||||||
        syncConfigWithDatabase();
 | 
					        syncConfigWithDatabase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Initialize plugins before starting the web server
 | 
				
			||||||
 | 
					        logger.info("Loading plugins...");
 | 
				
			||||||
 | 
					        await pluginManager.loadPlugins();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        startWebServer();
 | 
					        startWebServer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void updateWorldStateCollections();
 | 
					        void updateWorldStateCollections();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										161
									
								
								src/managers/pluginManager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								src/managers/pluginManager.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,161 @@
 | 
				
			|||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					import { IPlugin, PluginInfo, PluginManifest } from "@/src/types/pluginTypes";
 | 
				
			||||||
 | 
					import fs from "fs";
 | 
				
			||||||
 | 
					import path from "path";
 | 
				
			||||||
 | 
					import { pathToFileURL } from "url";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class PluginManager {
 | 
				
			||||||
 | 
					    private plugins: Map<string, IPlugin> = new Map();
 | 
				
			||||||
 | 
					    private pluginInfos: Map<string, PluginInfo> = new Map();
 | 
				
			||||||
 | 
					    private pluginsDir: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(pluginsDir: string = "plugins") {
 | 
				
			||||||
 | 
					        this.pluginsDir = path.resolve(pluginsDir);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Load all plugins from the plugins directory
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async loadPlugins(): Promise<void> {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            // Load plugin registry generated at build time
 | 
				
			||||||
 | 
					            const pluginRegistryPath = path.resolve("build/plugin-registry.json");
 | 
				
			||||||
 | 
					            if (!fs.existsSync(pluginRegistryPath)) {
 | 
				
			||||||
 | 
					                logger.info("No plugin registry found, skipping plugin loading");
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const pluginRegistry = JSON.parse(fs.readFileSync(pluginRegistryPath, "utf-8"));
 | 
				
			||||||
 | 
					            logger.info(`Found ${pluginRegistry.plugins.length} plugins in registry`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (const pluginPath of pluginRegistry.plugins) {
 | 
				
			||||||
 | 
					                await this.loadPlugin(pluginPath);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            logger.info(`Successfully loaded ${this.plugins.size} plugins`);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            logger.error("Failed to load plugins:", error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Load a single plugin
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private async loadPlugin(pluginPath: string): Promise<void> {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const absolutePath = path.resolve(pluginPath);
 | 
				
			||||||
 | 
					            const manifestPath = path.join(absolutePath, "plugin.json");
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!fs.existsSync(manifestPath)) {
 | 
				
			||||||
 | 
					                logger.warn(`Plugin manifest not found: ${manifestPath}`);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const manifest: PluginManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Check if plugin is enabled from config
 | 
				
			||||||
 | 
					            const pluginInfo: PluginInfo = {
 | 
				
			||||||
 | 
					                manifest,
 | 
				
			||||||
 | 
					                path: absolutePath,
 | 
				
			||||||
 | 
					                enabled: manifest.config?.enabled ?? true
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!pluginInfo.enabled) {
 | 
				
			||||||
 | 
					                logger.info(`Plugin ${manifest.name} is disabled, skipping`);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Load the plugin module from build directory
 | 
				
			||||||
 | 
					            const pluginName = path.basename(absolutePath);
 | 
				
			||||||
 | 
					            const buildPluginPath = path.resolve("build/plugins", pluginName);
 | 
				
			||||||
 | 
					            const mainFile = path.join(buildPluginPath, manifest.main || "index.js");
 | 
				
			||||||
 | 
					            if (!fs.existsSync(mainFile)) {
 | 
				
			||||||
 | 
					                logger.error(`Plugin main file not found: ${mainFile}`);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const pluginModule = await import(pathToFileURL(mainFile).href);
 | 
				
			||||||
 | 
					            const PluginClass = pluginModule.default?.default || pluginModule.default || pluginModule[manifest.name];
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!PluginClass) {
 | 
				
			||||||
 | 
					                logger.error(`Plugin class not found in ${mainFile}`);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const plugin: IPlugin = new PluginClass();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Validate plugin interface
 | 
				
			||||||
 | 
					            if (!this.validatePlugin(plugin)) {
 | 
				
			||||||
 | 
					                logger.error(`Plugin ${manifest.name} does not implement required interface`);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Initialize the plugin
 | 
				
			||||||
 | 
					            await plugin.initialize();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            this.plugins.set(manifest.name, plugin);
 | 
				
			||||||
 | 
					            this.pluginInfos.set(manifest.name, pluginInfo);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            logger.info(`Loaded plugin: ${manifest.name} v${manifest.version}`);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            logger.error(`Failed to load plugin from ${pluginPath}:`, error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Validate that a plugin implements the required interface
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private validatePlugin(plugin: any): plugin is IPlugin {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            plugin &&
 | 
				
			||||||
 | 
					            typeof plugin.name === "string" &&
 | 
				
			||||||
 | 
					            typeof plugin.version === "string" &&
 | 
				
			||||||
 | 
					            typeof plugin.initialize === "function"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get a plugin by name
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getPlugin(name: string): IPlugin | undefined {
 | 
				
			||||||
 | 
					        return this.plugins.get(name);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get all loaded plugins
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getAllPlugins(): Map<string, IPlugin> {
 | 
				
			||||||
 | 
					        return new Map(this.plugins);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get plugin info
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getPluginInfo(name: string): PluginInfo | undefined {
 | 
				
			||||||
 | 
					        return this.pluginInfos.get(name);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Cleanup all plugins
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async cleanup(): Promise<void> {
 | 
				
			||||||
 | 
					        logger.info("Cleaning up plugins...");
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        for (const [name, plugin] of this.plugins) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                if (plugin.cleanup) {
 | 
				
			||||||
 | 
					                    await plugin.cleanup();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                logger.info(`Cleaned up plugin: ${name}`);
 | 
				
			||||||
 | 
					            } catch (error) {
 | 
				
			||||||
 | 
					                logger.error(`Failed to cleanup plugin ${name}:`, error);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        this.plugins.clear();
 | 
				
			||||||
 | 
					        this.pluginInfos.clear();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Global plugin manager instance
 | 
				
			||||||
 | 
					export const pluginManager = new PluginManager();
 | 
				
			||||||
							
								
								
									
										52
									
								
								src/types/pluginTypes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/types/pluginTypes.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					export interface IPlugin {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Plugin name
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Plugin version
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    version: string;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Plugin description
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    description?: string;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Plugin author
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    author?: string;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Initialize the plugin
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    initialize(): Promise<void> | void;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Cleanup plugin resources
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    cleanup?(): Promise<void> | void;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Plugin configuration
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    config?: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface PluginManifest {
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    version: string;
 | 
				
			||||||
 | 
					    description?: string;
 | 
				
			||||||
 | 
					    author?: string;
 | 
				
			||||||
 | 
					    main: string;
 | 
				
			||||||
 | 
					    dependencies?: string[];
 | 
				
			||||||
 | 
					    config?: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface PluginInfo {
 | 
				
			||||||
 | 
					    manifest: PluginManifest;
 | 
				
			||||||
 | 
					    path: string;
 | 
				
			||||||
 | 
					    enabled: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user