forked from OpenWF/SpaceNinjaServer
Fix Lint
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
|
@ -9,22 +9,24 @@ export default class ExamplePlugin implements IPlugin {
|
|||||||
|
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
logger.info(`[${this.name}] Plugin initialized successfully!`);
|
logger.info(`[${this.name}] Plugin initialized successfully!`);
|
||||||
|
|
||||||
// Add your plugin initialization logic here
|
// Add your plugin initialization logic here
|
||||||
// For example:
|
// For example:
|
||||||
// - Register new routes
|
// - Register new routes
|
||||||
// - 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> {
|
||||||
logger.info(`[${this.name}] Plugin cleanup completed`);
|
logger.info(`[${this.name}] Plugin cleanup completed`);
|
||||||
|
|
||||||
// Add your cleanup logic here
|
// Add your cleanup logic here
|
||||||
// For example:
|
// For example:
|
||||||
// - 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,8 +73,8 @@ 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}`);
|
||||||
return null;
|
return null;
|
||||||
@ -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) {
|
||||||
@ -45,19 +63,20 @@ export class PluginManager {
|
|||||||
try {
|
try {
|
||||||
const absolutePath = path.resolve(pluginPath);
|
const absolutePath = path.resolve(pluginPath);
|
||||||
const manifestPath = path.join(absolutePath, "plugin.json");
|
const manifestPath = path.join(absolutePath, "plugin.json");
|
||||||
|
|
||||||
if (!fs.existsSync(manifestPath)) {
|
if (!fs.existsSync(manifestPath)) {
|
||||||
logger.warn(`Plugin manifest not found: ${manifestPath}`);
|
logger.warn(`Plugin manifest not found: ${manifestPath}`);
|
||||||
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,16 +93,25 @@ 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)) {
|
||||||
logger.error(`Plugin ${manifest.name} does not implement required interface`);
|
logger.error(`Plugin ${manifest.name} does not implement required interface`);
|
||||||
@ -91,11 +119,11 @@ 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);
|
||||||
|
|
||||||
logger.info(`Loaded plugin: ${manifest.name} v${manifest.version}`);
|
logger.info(`Loaded plugin: ${manifest.name} v${manifest.version}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to load plugin from ${pluginPath}:`, 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
|
* 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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,18 +172,18 @@ export class PluginManager {
|
|||||||
*/
|
*/
|
||||||
async cleanup(): Promise<void> {
|
async cleanup(): Promise<void> {
|
||||||
logger.info("Cleaning up plugins...");
|
logger.info("Cleaning up plugins...");
|
||||||
|
|
||||||
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) {
|
||||||
logger.error(`Failed to cleanup plugin ${name}:`, error);
|
logger.error(`Failed to cleanup plugin ${name}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.plugins.clear();
|
this.plugins.clear();
|
||||||
this.pluginInfos.clear();
|
this.pluginInfos.clear();
|
||||||
}
|
}
|
||||||
|
@ -3,36 +3,36 @@ export interface IPlugin {
|
|||||||
* Plugin name
|
* Plugin name
|
||||||
*/
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin version
|
* Plugin version
|
||||||
*/
|
*/
|
||||||
version: string;
|
version: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin description
|
* Plugin description
|
||||||
*/
|
*/
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin author
|
* Plugin author
|
||||||
*/
|
*/
|
||||||
author?: string;
|
author?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the plugin
|
* Initialize the plugin
|
||||||
*/
|
*/
|
||||||
initialize(): Promise<void> | void;
|
initialize(): Promise<void> | void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleanup plugin resources
|
* Cleanup plugin resources
|
||||||
*/
|
*/
|
||||||
cleanup?(): Promise<void> | void;
|
cleanup?(): Promise<void> | void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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