diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..345740e8 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,22 @@ +# Build output +build/ + +# Node modules +node_modules/ + +# Logs +logs/ + +# Environment files +.env +.env.local +.env.*.local + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/plugins/ExamplePlugin/index.ts b/plugins/ExamplePlugin/index.ts index 184934db..6024f9c4 100644 --- a/plugins/ExamplePlugin/index.ts +++ b/plugins/ExamplePlugin/index.ts @@ -9,22 +9,24 @@ export default class ExamplePlugin implements IPlugin { async initialize(): Promise { 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 + await Promise.resolve(); // Simulate async operation if needed } async cleanup(): Promise { logger.info(`[${this.name}] Plugin cleanup completed`); - + // Add your cleanup logic here // For example: // - Close database connections // - Clear timers/intervals // - Remove event listeners + await Promise.resolve(); // Simulate async operation if needed } } diff --git a/plugins/ExamplePlugin/plugin.json b/plugins/ExamplePlugin/plugin.json index 8b3450da..3d182670 100644 --- a/plugins/ExamplePlugin/plugin.json +++ b/plugins/ExamplePlugin/plugin.json @@ -1,11 +1,11 @@ { - "name": "ExamplePlugin", - "version": "1.0.0", - "description": "Example plugin for the server", - "author": "Your Name", - "main": "index.js", - "dependencies": [], - "config": { - "enabled": false - } + "name": "ExamplePlugin", + "version": "1.0.0", + "description": "Example plugin for the server", + "author": "Your Name", + "main": "index.js", + "dependencies": [], + "config": { + "enabled": false + } } diff --git a/scripts/plugin-discovery.js b/scripts/plugin-discovery.js index 45f3c174..98b5b15b 100644 --- a/scripts/plugin-discovery.js +++ b/scripts/plugin-discovery.js @@ -3,11 +3,11 @@ * This script runs during build time to discover all plugins and generate a registry */ -const fs = require('fs'); -const path = require('path'); +const fs = require("fs"); +const path = require("path"); class PluginDiscovery { - constructor(pluginsDir = 'plugins', outputPath = 'build/plugin-registry.json') { + constructor(pluginsDir = "plugins", outputPath = "build/plugin-registry.json") { this.pluginsDir = path.resolve(pluginsDir); this.outputPath = path.resolve(outputPath); } @@ -25,11 +25,12 @@ class PluginDiscovery { console.log(`🔍 Discovering plugins in: ${this.pluginsDir}`); if (!fs.existsSync(this.pluginsDir)) { - console.log('⚠️ Plugins directory not found, creating empty registry'); + console.log("⚠️ Plugins directory not found, creating empty registry"); return registry; } - const pluginDirs = fs.readdirSync(this.pluginsDir, { withFileTypes: true }) + const pluginDirs = fs + .readdirSync(this.pluginsDir, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); @@ -55,13 +56,13 @@ class PluginDiscovery { */ loadPluginManifest(pluginPath) { try { - const manifestPath = path.join(pluginPath, 'plugin.json'); - + const manifestPath = path.join(pluginPath, "plugin.json"); + if (!fs.existsSync(manifestPath)) { return null; } - const manifestContent = fs.readFileSync(manifestPath, 'utf-8'); + const manifestContent = fs.readFileSync(manifestPath, "utf-8"); const manifest = JSON.parse(manifestContent); // Validate required fields @@ -72,8 +73,8 @@ class PluginDiscovery { // Check if main file exists const mainFile = path.join(pluginPath, manifest.main); - const mainTsFile = mainFile.replace(/\.js$/, '.ts'); - + const mainTsFile = mainFile.replace(/\.js$/, ".ts"); + if (!fs.existsSync(mainFile) && !fs.existsSync(mainTsFile)) { console.log(`⚠️ Main file not found: ${mainFile} or ${mainTsFile}`); return null; @@ -109,12 +110,12 @@ class PluginDiscovery { * Main execution function */ run() { - console.log('🚀 Starting plugin discovery...'); - + console.log("🚀 Starting plugin discovery..."); + const registry = this.discoverPlugins(); this.saveRegistry(registry); - - console.log('✨ Plugin discovery completed!'); + + console.log("✨ Plugin discovery completed!"); } } diff --git a/src/index.ts b/src/index.ts index b0fa08ae..d2cde64c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,16 +31,18 @@ JSON.stringify = JSONStringify; validateConfig(); // Handle graceful shutdown -process.on('SIGINT', async () => { - logger.info('Received SIGINT, starting graceful shutdown...'); - await pluginManager.cleanup(); - process.exit(0); +process.on("SIGINT", () => { + logger.info("Received SIGINT, starting graceful shutdown..."); + void pluginManager.cleanup().then(() => { + process.exit(0); + }); }); -process.on('SIGTERM', async () => { - logger.info('Received SIGTERM, starting graceful shutdown...'); - await pluginManager.cleanup(); - process.exit(0); +process.on("SIGTERM", () => { + logger.info("Received SIGTERM, starting graceful shutdown..."); + void pluginManager.cleanup().then(() => { + process.exit(0); + }); }); mongoose diff --git a/src/managers/pluginManager.ts b/src/managers/pluginManager.ts index 4d6f4ab3..ffac0045 100644 --- a/src/managers/pluginManager.ts +++ b/src/managers/pluginManager.ts @@ -4,6 +4,24 @@ import fs from "fs"; import path from "path"; import { pathToFileURL } from "url"; +interface PluginRegistry { + plugins: string[]; +} + +interface PluginModule { + default?: + | (new () => IPlugin) + | { + default?: new () => IPlugin; + }; + [key: string]: unknown; +} + +interface PluginConfig { + enabled?: boolean; + [key: string]: unknown; +} + export class PluginManager { private plugins: Map = new Map(); private pluginInfos: Map = new Map(); @@ -25,7 +43,7 @@ export class PluginManager { return; } - const pluginRegistry = JSON.parse(fs.readFileSync(pluginRegistryPath, "utf-8")); + const pluginRegistry = JSON.parse(fs.readFileSync(pluginRegistryPath, "utf-8")) as PluginRegistry; logger.info(`Found ${pluginRegistry.plugins.length} plugins in registry`); for (const pluginPath of pluginRegistry.plugins) { @@ -45,19 +63,20 @@ export class PluginManager { 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")); - + const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as PluginManifest; + // Check if plugin is enabled from config + const pluginConfig = manifest.config as PluginConfig | undefined; const pluginInfo: PluginInfo = { manifest, path: absolutePath, - enabled: manifest.config?.enabled ?? true + enabled: pluginConfig?.enabled ?? true }; if (!pluginInfo.enabled) { @@ -74,16 +93,25 @@ export class PluginManager { return; } - const pluginModule = await import(pathToFileURL(mainFile).href); - const PluginClass = pluginModule.default?.default || pluginModule.default || pluginModule[manifest.name]; - + const pluginModule = (await import(pathToFileURL(mainFile).href)) as PluginModule; + + let PluginClass: (new () => IPlugin) | undefined; + + if (typeof pluginModule.default === "function") { + PluginClass = pluginModule.default; + } else if (typeof pluginModule.default === "object" && pluginModule.default.default) { + PluginClass = pluginModule.default.default; + } else if (pluginModule[manifest.name]) { + PluginClass = pluginModule[manifest.name] as new () => IPlugin; + } + if (!PluginClass) { logger.error(`Plugin class not found in ${mainFile}`); return; } - const plugin: IPlugin = new PluginClass(); - + const plugin = new PluginClass(); + // Validate plugin interface if (!this.validatePlugin(plugin)) { logger.error(`Plugin ${manifest.name} does not implement required interface`); @@ -91,11 +119,11 @@ export class PluginManager { } // Initialize the plugin - await plugin.initialize(); - + await Promise.resolve(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); @@ -105,12 +133,16 @@ export class PluginManager { /** * Validate that a plugin implements the required interface */ - private validatePlugin(plugin: any): plugin is IPlugin { + private validatePlugin(plugin: unknown): plugin is IPlugin { return ( - plugin && - typeof plugin.name === "string" && - typeof plugin.version === "string" && - typeof plugin.initialize === "function" + plugin != null && + typeof plugin === "object" && + "name" in plugin && + "version" in plugin && + "initialize" in plugin && + typeof (plugin as Record).name === "string" && + typeof (plugin as Record).version === "string" && + typeof (plugin as Record).initialize === "function" ); } @@ -140,18 +172,18 @@ export class PluginManager { */ async cleanup(): Promise { logger.info("Cleaning up plugins..."); - + for (const [name, plugin] of this.plugins) { try { if (plugin.cleanup) { - await plugin.cleanup(); + await Promise.resolve(plugin.cleanup()); } logger.info(`Cleaned up plugin: ${name}`); } catch (error) { logger.error(`Failed to cleanup plugin ${name}:`, error); } } - + this.plugins.clear(); this.pluginInfos.clear(); } diff --git a/src/types/pluginTypes.ts b/src/types/pluginTypes.ts index e95836f6..c7bb7e09 100644 --- a/src/types/pluginTypes.ts +++ b/src/types/pluginTypes.ts @@ -3,36 +3,36 @@ 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; - + /** * Cleanup plugin resources */ cleanup?(): Promise | void; - + /** * Plugin configuration */ - config?: any; + config?: Record; } export interface PluginManifest { @@ -42,7 +42,7 @@ export interface PluginManifest { author?: string; main: string; dependencies?: string[]; - config?: any; + config?: Record; } export interface PluginInfo {