This commit is contained in:
		
							parent
							
								
									f99f9a945c
								
							
						
					
					
						commit
						64f0d81eb5
					
				
							
								
								
									
										22
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
				
			||||||
@ -16,6 +16,7 @@ export default class ExamplePlugin implements IPlugin {
 | 
				
			|||||||
        // - Add new API endpoints
 | 
					        // - Add new API endpoints
 | 
				
			||||||
        // - Set up event listeners
 | 
					        // - Set up event listeners
 | 
				
			||||||
        // - Connect to external services
 | 
					        // - Connect to external services
 | 
				
			||||||
 | 
					        await Promise.resolve(); // Simulate async operation if needed
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async cleanup(): Promise<void> {
 | 
					    async cleanup(): Promise<void> {
 | 
				
			||||||
@ -26,5 +27,6 @@ export default class ExamplePlugin implements IPlugin {
 | 
				
			|||||||
        // - Close database connections
 | 
					        // - Close database connections
 | 
				
			||||||
        // - Clear timers/intervals
 | 
					        // - Clear timers/intervals
 | 
				
			||||||
        // - Remove event listeners
 | 
					        // - Remove event listeners
 | 
				
			||||||
 | 
					        await Promise.resolve(); // Simulate async operation if needed
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,11 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "name": "ExamplePlugin",
 | 
					  "name": "ExamplePlugin",
 | 
				
			||||||
    "version": "1.0.0",
 | 
					  "version": "1.0.0",
 | 
				
			||||||
    "description": "Example plugin for the server",
 | 
					  "description": "Example plugin for the server",
 | 
				
			||||||
    "author": "Your Name",
 | 
					  "author": "Your Name",
 | 
				
			||||||
    "main": "index.js",
 | 
					  "main": "index.js",
 | 
				
			||||||
    "dependencies": [],
 | 
					  "dependencies": [],
 | 
				
			||||||
    "config": {
 | 
					  "config": {
 | 
				
			||||||
        "enabled": false
 | 
					    "enabled": false
 | 
				
			||||||
    }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,11 +3,11 @@
 | 
				
			|||||||
 * This script runs during build time to discover all plugins and generate a registry
 | 
					 * This script runs during build time to discover all plugins and generate a registry
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fs = require('fs');
 | 
					const fs = require("fs");
 | 
				
			||||||
const path = require('path');
 | 
					const path = require("path");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PluginDiscovery {
 | 
					class PluginDiscovery {
 | 
				
			||||||
    constructor(pluginsDir = 'plugins', outputPath = 'build/plugin-registry.json') {
 | 
					    constructor(pluginsDir = "plugins", outputPath = "build/plugin-registry.json") {
 | 
				
			||||||
        this.pluginsDir = path.resolve(pluginsDir);
 | 
					        this.pluginsDir = path.resolve(pluginsDir);
 | 
				
			||||||
        this.outputPath = path.resolve(outputPath);
 | 
					        this.outputPath = path.resolve(outputPath);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -25,11 +25,12 @@ class PluginDiscovery {
 | 
				
			|||||||
        console.log(`🔍 Discovering plugins in: ${this.pluginsDir}`);
 | 
					        console.log(`🔍 Discovering plugins in: ${this.pluginsDir}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!fs.existsSync(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;
 | 
					            return registry;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const pluginDirs = fs.readdirSync(this.pluginsDir, { withFileTypes: true })
 | 
					        const pluginDirs = fs
 | 
				
			||||||
 | 
					            .readdirSync(this.pluginsDir, { withFileTypes: true })
 | 
				
			||||||
            .filter(dirent => dirent.isDirectory())
 | 
					            .filter(dirent => dirent.isDirectory())
 | 
				
			||||||
            .map(dirent => dirent.name);
 | 
					            .map(dirent => dirent.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -55,13 +56,13 @@ class PluginDiscovery {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    loadPluginManifest(pluginPath) {
 | 
					    loadPluginManifest(pluginPath) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const manifestPath = path.join(pluginPath, 'plugin.json');
 | 
					            const manifestPath = path.join(pluginPath, "plugin.json");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!fs.existsSync(manifestPath)) {
 | 
					            if (!fs.existsSync(manifestPath)) {
 | 
				
			||||||
                return null;
 | 
					                return null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const manifestContent = fs.readFileSync(manifestPath, 'utf-8');
 | 
					            const manifestContent = fs.readFileSync(manifestPath, "utf-8");
 | 
				
			||||||
            const manifest = JSON.parse(manifestContent);
 | 
					            const manifest = JSON.parse(manifestContent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Validate required fields
 | 
					            // Validate required fields
 | 
				
			||||||
@ -72,7 +73,7 @@ class PluginDiscovery {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            // Check if main file exists
 | 
					            // Check if main file exists
 | 
				
			||||||
            const mainFile = path.join(pluginPath, manifest.main);
 | 
					            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)) {
 | 
					            if (!fs.existsSync(mainFile) && !fs.existsSync(mainTsFile)) {
 | 
				
			||||||
                console.log(`⚠️  Main file not found: ${mainFile} or ${mainTsFile}`);
 | 
					                console.log(`⚠️  Main file not found: ${mainFile} or ${mainTsFile}`);
 | 
				
			||||||
@ -109,12 +110,12 @@ class PluginDiscovery {
 | 
				
			|||||||
     * Main execution function
 | 
					     * Main execution function
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    run() {
 | 
					    run() {
 | 
				
			||||||
        console.log('🚀 Starting plugin discovery...');
 | 
					        console.log("🚀 Starting plugin discovery...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const registry = this.discoverPlugins();
 | 
					        const registry = this.discoverPlugins();
 | 
				
			||||||
        this.saveRegistry(registry);
 | 
					        this.saveRegistry(registry);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        console.log('✨ Plugin discovery completed!');
 | 
					        console.log("✨ Plugin discovery completed!");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										18
									
								
								src/index.ts
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/index.ts
									
									
									
									
									
								
							@ -31,16 +31,18 @@ JSON.stringify = JSONStringify;
 | 
				
			|||||||
validateConfig();
 | 
					validateConfig();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Handle graceful shutdown
 | 
					// Handle graceful shutdown
 | 
				
			||||||
process.on('SIGINT', async () => {
 | 
					process.on("SIGINT", () => {
 | 
				
			||||||
    logger.info('Received SIGINT, starting graceful shutdown...');
 | 
					    logger.info("Received SIGINT, starting graceful shutdown...");
 | 
				
			||||||
    await pluginManager.cleanup();
 | 
					    void pluginManager.cleanup().then(() => {
 | 
				
			||||||
    process.exit(0);
 | 
					        process.exit(0);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
process.on('SIGTERM', async () => {
 | 
					process.on("SIGTERM", () => {
 | 
				
			||||||
    logger.info('Received SIGTERM, starting graceful shutdown...');
 | 
					    logger.info("Received SIGTERM, starting graceful shutdown...");
 | 
				
			||||||
    await pluginManager.cleanup();
 | 
					    void pluginManager.cleanup().then(() => {
 | 
				
			||||||
    process.exit(0);
 | 
					        process.exit(0);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mongoose
 | 
					mongoose
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,24 @@ import fs from "fs";
 | 
				
			|||||||
import path from "path";
 | 
					import path from "path";
 | 
				
			||||||
import { pathToFileURL } from "url";
 | 
					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 {
 | 
					export class PluginManager {
 | 
				
			||||||
    private plugins: Map<string, IPlugin> = new Map();
 | 
					    private plugins: Map<string, IPlugin> = new Map();
 | 
				
			||||||
    private pluginInfos: Map<string, PluginInfo> = new Map();
 | 
					    private pluginInfos: Map<string, PluginInfo> = new Map();
 | 
				
			||||||
@ -25,7 +43,7 @@ export class PluginManager {
 | 
				
			|||||||
                return;
 | 
					                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`);
 | 
					            logger.info(`Found ${pluginRegistry.plugins.length} plugins in registry`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for (const pluginPath of pluginRegistry.plugins) {
 | 
					            for (const pluginPath of pluginRegistry.plugins) {
 | 
				
			||||||
@ -51,13 +69,14 @@ export class PluginManager {
 | 
				
			|||||||
                return;
 | 
					                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
 | 
					            // Check if plugin is enabled from config
 | 
				
			||||||
 | 
					            const pluginConfig = manifest.config as PluginConfig | undefined;
 | 
				
			||||||
            const pluginInfo: PluginInfo = {
 | 
					            const pluginInfo: PluginInfo = {
 | 
				
			||||||
                manifest,
 | 
					                manifest,
 | 
				
			||||||
                path: absolutePath,
 | 
					                path: absolutePath,
 | 
				
			||||||
                enabled: manifest.config?.enabled ?? true
 | 
					                enabled: pluginConfig?.enabled ?? true
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!pluginInfo.enabled) {
 | 
					            if (!pluginInfo.enabled) {
 | 
				
			||||||
@ -74,15 +93,24 @@ export class PluginManager {
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const pluginModule = await import(pathToFileURL(mainFile).href);
 | 
					            const pluginModule = (await import(pathToFileURL(mainFile).href)) as PluginModule;
 | 
				
			||||||
            const PluginClass = pluginModule.default?.default || pluginModule.default || pluginModule[manifest.name];
 | 
					
 | 
				
			||||||
 | 
					            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) {
 | 
					            if (!PluginClass) {
 | 
				
			||||||
                logger.error(`Plugin class not found in ${mainFile}`);
 | 
					                logger.error(`Plugin class not found in ${mainFile}`);
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const plugin: IPlugin = new PluginClass();
 | 
					            const plugin = new PluginClass();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Validate plugin interface
 | 
					            // Validate plugin interface
 | 
				
			||||||
            if (!this.validatePlugin(plugin)) {
 | 
					            if (!this.validatePlugin(plugin)) {
 | 
				
			||||||
@ -91,7 +119,7 @@ export class PluginManager {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Initialize the plugin
 | 
					            // Initialize the plugin
 | 
				
			||||||
            await plugin.initialize();
 | 
					            await Promise.resolve(plugin.initialize());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.plugins.set(manifest.name, plugin);
 | 
					            this.plugins.set(manifest.name, plugin);
 | 
				
			||||||
            this.pluginInfos.set(manifest.name, pluginInfo);
 | 
					            this.pluginInfos.set(manifest.name, pluginInfo);
 | 
				
			||||||
@ -105,12 +133,16 @@ export class PluginManager {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Validate that a plugin implements the required interface
 | 
					     * Validate that a plugin implements the required interface
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private validatePlugin(plugin: any): plugin is IPlugin {
 | 
					    private validatePlugin(plugin: unknown): plugin is IPlugin {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            plugin &&
 | 
					            plugin != null &&
 | 
				
			||||||
            typeof plugin.name === "string" &&
 | 
					            typeof plugin === "object" &&
 | 
				
			||||||
            typeof plugin.version === "string" &&
 | 
					            "name" in plugin &&
 | 
				
			||||||
            typeof plugin.initialize === "function"
 | 
					            "version" in plugin &&
 | 
				
			||||||
 | 
					            "initialize" in plugin &&
 | 
				
			||||||
 | 
					            typeof (plugin as Record<string, unknown>).name === "string" &&
 | 
				
			||||||
 | 
					            typeof (plugin as Record<string, unknown>).version === "string" &&
 | 
				
			||||||
 | 
					            typeof (plugin as Record<string, unknown>).initialize === "function"
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -144,7 +176,7 @@ export class PluginManager {
 | 
				
			|||||||
        for (const [name, plugin] of this.plugins) {
 | 
					        for (const [name, plugin] of this.plugins) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                if (plugin.cleanup) {
 | 
					                if (plugin.cleanup) {
 | 
				
			||||||
                    await plugin.cleanup();
 | 
					                    await Promise.resolve(plugin.cleanup());
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                logger.info(`Cleaned up plugin: ${name}`);
 | 
					                logger.info(`Cleaned up plugin: ${name}`);
 | 
				
			||||||
            } catch (error) {
 | 
					            } catch (error) {
 | 
				
			||||||
 | 
				
			|||||||
@ -32,7 +32,7 @@ export interface IPlugin {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Plugin configuration
 | 
					     * Plugin configuration
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    config?: any;
 | 
					    config?: Record<string, unknown>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface PluginManifest {
 | 
					export interface PluginManifest {
 | 
				
			||||||
@ -42,7 +42,7 @@ export interface PluginManifest {
 | 
				
			|||||||
    author?: string;
 | 
					    author?: string;
 | 
				
			||||||
    main: string;
 | 
					    main: string;
 | 
				
			||||||
    dependencies?: string[];
 | 
					    dependencies?: string[];
 | 
				
			||||||
    config?: any;
 | 
					    config?: Record<string, unknown>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface PluginInfo {
 | 
					export interface PluginInfo {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user