插件系统
This commit is contained in:
@@ -10,6 +10,8 @@ import { createLogger } from './Utils/Logger';
|
||||
import { SceneManager } from './ECS/SceneManager';
|
||||
import { IScene } from './ECS/IScene';
|
||||
import { ServiceContainer } from './Core/ServiceContainer';
|
||||
import { PluginManager } from './Core/PluginManager';
|
||||
import { IPlugin } from './Core/Plugin';
|
||||
|
||||
/**
|
||||
* 游戏引擎核心类
|
||||
@@ -120,6 +122,13 @@ export class Core {
|
||||
*/
|
||||
private _sceneManager: SceneManager;
|
||||
|
||||
/**
|
||||
* 插件管理器
|
||||
*
|
||||
* 管理所有插件的生命周期。
|
||||
*/
|
||||
private _pluginManager: PluginManager;
|
||||
|
||||
/**
|
||||
* Core配置
|
||||
*/
|
||||
@@ -163,6 +172,11 @@ export class Core {
|
||||
this._sceneManager = new SceneManager();
|
||||
this._serviceContainer.registerInstance(SceneManager, this._sceneManager);
|
||||
|
||||
// 初始化插件管理器
|
||||
this._pluginManager = new PluginManager();
|
||||
this._pluginManager.initialize(this, this._serviceContainer);
|
||||
this._serviceContainer.registerInstance(PluginManager, this._pluginManager);
|
||||
|
||||
Core.entitySystemsEnabled = this._config.enableEntitySystems ?? true;
|
||||
this.debug = this._config.debug ?? true;
|
||||
|
||||
@@ -470,6 +484,90 @@ export class Core {
|
||||
return this._instance?._config.debugConfig?.enabled || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装插件
|
||||
*
|
||||
* @param plugin - 插件实例
|
||||
* @throws 如果Core实例未创建或插件安装失败
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* Core.create({ debug: true });
|
||||
*
|
||||
* // 安装插件
|
||||
* await Core.installPlugin(new MyPlugin());
|
||||
* ```
|
||||
*/
|
||||
public static async installPlugin(plugin: IPlugin): Promise<void> {
|
||||
if (!this._instance) {
|
||||
throw new Error('Core实例未创建,请先调用Core.create()');
|
||||
}
|
||||
|
||||
await this._instance._pluginManager.install(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载插件
|
||||
*
|
||||
* @param name - 插件名称
|
||||
* @throws 如果Core实例未创建或插件卸载失败
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* await Core.uninstallPlugin('my-plugin');
|
||||
* ```
|
||||
*/
|
||||
public static async uninstallPlugin(name: string): Promise<void> {
|
||||
if (!this._instance) {
|
||||
throw new Error('Core实例未创建,请先调用Core.create()');
|
||||
}
|
||||
|
||||
await this._instance._pluginManager.uninstall(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件实例
|
||||
*
|
||||
* @param name - 插件名称
|
||||
* @returns 插件实例,如果未安装则返回undefined
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const myPlugin = Core.getPlugin('my-plugin');
|
||||
* if (myPlugin) {
|
||||
* console.log(myPlugin.version);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
public static getPlugin(name: string): IPlugin | undefined {
|
||||
if (!this._instance) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._instance._pluginManager.getPlugin(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查插件是否已安装
|
||||
*
|
||||
* @param name - 插件名称
|
||||
* @returns 是否已安装
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* if (Core.isPluginInstalled('my-plugin')) {
|
||||
* console.log('Plugin is installed');
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
public static isPluginInstalled(name: string): boolean {
|
||||
if (!this._instance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._instance._pluginManager.isInstalled(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化核心系统
|
||||
*
|
||||
|
||||
124
packages/core/src/Core/Plugin.ts
Normal file
124
packages/core/src/Core/Plugin.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import type { Core } from '../Core';
|
||||
import type { ServiceContainer } from './ServiceContainer';
|
||||
|
||||
/**
|
||||
* 插件状态
|
||||
*/
|
||||
export enum PluginState {
|
||||
/**
|
||||
* 未安装
|
||||
*/
|
||||
NotInstalled = 'not_installed',
|
||||
|
||||
/**
|
||||
* 已安装
|
||||
*/
|
||||
Installed = 'installed',
|
||||
|
||||
/**
|
||||
* 安装失败
|
||||
*/
|
||||
Failed = 'failed'
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件接口
|
||||
*
|
||||
* 所有插件都必须实现此接口。
|
||||
* 插件提供了一种扩展框架功能的标准方式。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class MyPlugin implements IPlugin {
|
||||
* readonly name = 'my-plugin';
|
||||
* readonly version = '1.0.0';
|
||||
* readonly dependencies = ['other-plugin'];
|
||||
*
|
||||
* async install(core: Core, services: ServiceContainer) {
|
||||
* // 注册服务
|
||||
* services.registerSingleton(MyService);
|
||||
*
|
||||
* // 添加系统
|
||||
* const world = core.getWorld();
|
||||
* if (world) {
|
||||
* world.addSystem(new MySystem());
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* async uninstall() {
|
||||
* // 清理资源
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface IPlugin {
|
||||
/**
|
||||
* 插件唯一名称
|
||||
*
|
||||
* 用于依赖解析和插件管理。
|
||||
*/
|
||||
readonly name: string;
|
||||
|
||||
/**
|
||||
* 插件版本
|
||||
*
|
||||
* 遵循语义化版本规范 (semver)。
|
||||
*/
|
||||
readonly version: string;
|
||||
|
||||
/**
|
||||
* 依赖的其他插件名称列表
|
||||
*
|
||||
* 这些插件必须在当前插件之前安装。
|
||||
*/
|
||||
readonly dependencies?: readonly string[];
|
||||
|
||||
/**
|
||||
* 安装插件
|
||||
*
|
||||
* 在此方法中初始化插件,注册服务、系统等。
|
||||
* 可以是同步或异步的。
|
||||
*
|
||||
* @param core - Core实例,用于访问World等
|
||||
* @param services - 服务容器,用于注册服务
|
||||
*/
|
||||
install(core: Core, services: ServiceContainer): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* 卸载插件
|
||||
*
|
||||
* 清理插件占用的资源。
|
||||
* 可以是同步或异步的。
|
||||
*/
|
||||
uninstall(): void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件元数据
|
||||
*/
|
||||
export interface IPluginMetadata {
|
||||
/**
|
||||
* 插件名称
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* 插件版本
|
||||
*/
|
||||
version: string;
|
||||
|
||||
/**
|
||||
* 插件状态
|
||||
*/
|
||||
state: PluginState;
|
||||
|
||||
/**
|
||||
* 安装时间戳
|
||||
*/
|
||||
installedAt?: number;
|
||||
|
||||
/**
|
||||
* 错误信息(如果安装失败)
|
||||
*/
|
||||
error?: string;
|
||||
}
|
||||
266
packages/core/src/Core/PluginManager.ts
Normal file
266
packages/core/src/Core/PluginManager.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import { IPlugin, IPluginMetadata, PluginState } from './Plugin';
|
||||
import type { IService } from './ServiceContainer';
|
||||
import type { Core } from '../Core';
|
||||
import type { ServiceContainer } from './ServiceContainer';
|
||||
import { createLogger } from '../Utils/Logger';
|
||||
|
||||
const logger = createLogger('PluginManager');
|
||||
|
||||
/**
|
||||
* 插件管理器
|
||||
*
|
||||
* 负责插件的注册、安装、卸载和生命周期管理。
|
||||
* 支持依赖检查和异步加载。
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const core = Core.create();
|
||||
* const pluginManager = core.getService(PluginManager);
|
||||
*
|
||||
* // 注册插件
|
||||
* await pluginManager.install(new MyPlugin());
|
||||
*
|
||||
* // 查询插件
|
||||
* const plugin = pluginManager.getPlugin('my-plugin');
|
||||
*
|
||||
* // 卸载插件
|
||||
* await pluginManager.uninstall('my-plugin');
|
||||
* ```
|
||||
*/
|
||||
export class PluginManager implements IService {
|
||||
/**
|
||||
* 已安装的插件
|
||||
*/
|
||||
private _plugins: Map<string, IPlugin> = new Map();
|
||||
|
||||
/**
|
||||
* 插件元数据
|
||||
*/
|
||||
private _metadata: Map<string, IPluginMetadata> = new Map();
|
||||
|
||||
/**
|
||||
* Core实例引用
|
||||
*/
|
||||
private _core: Core | null = null;
|
||||
|
||||
/**
|
||||
* 服务容器引用
|
||||
*/
|
||||
private _services: ServiceContainer | null = null;
|
||||
|
||||
/**
|
||||
* 初始化插件管理器
|
||||
*
|
||||
* @param core - Core实例
|
||||
* @param services - 服务容器
|
||||
*/
|
||||
public initialize(core: Core, services: ServiceContainer): void {
|
||||
this._core = core;
|
||||
this._services = services;
|
||||
logger.info('PluginManager initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装插件
|
||||
*
|
||||
* 会自动检查依赖并按正确顺序安装。
|
||||
*
|
||||
* @param plugin - 插件实例
|
||||
* @throws 如果依赖检查失败或安装失败
|
||||
*/
|
||||
public async install(plugin: IPlugin): Promise<void> {
|
||||
if (!this._core || !this._services) {
|
||||
throw new Error('PluginManager not initialized. Call initialize() first.');
|
||||
}
|
||||
|
||||
// 检查是否已安装
|
||||
if (this._plugins.has(plugin.name)) {
|
||||
logger.warn(`Plugin ${plugin.name} is already installed`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查依赖
|
||||
if (plugin.dependencies && plugin.dependencies.length > 0) {
|
||||
this._checkDependencies(plugin);
|
||||
}
|
||||
|
||||
// 创建元数据
|
||||
const metadata: IPluginMetadata = {
|
||||
name: plugin.name,
|
||||
version: plugin.version,
|
||||
state: PluginState.NotInstalled,
|
||||
installedAt: Date.now()
|
||||
};
|
||||
|
||||
this._metadata.set(plugin.name, metadata);
|
||||
|
||||
try {
|
||||
// 调用插件的安装方法
|
||||
logger.info(`Installing plugin: ${plugin.name} v${plugin.version}`);
|
||||
await plugin.install(this._core, this._services);
|
||||
|
||||
// 标记为已安装
|
||||
this._plugins.set(plugin.name, plugin);
|
||||
metadata.state = PluginState.Installed;
|
||||
|
||||
logger.info(`Plugin ${plugin.name} installed successfully`);
|
||||
} catch (error) {
|
||||
// 安装失败
|
||||
metadata.state = PluginState.Failed;
|
||||
metadata.error = error instanceof Error ? error.message : String(error);
|
||||
|
||||
logger.error(`Failed to install plugin ${plugin.name}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载插件
|
||||
*
|
||||
* @param name - 插件名称
|
||||
* @throws 如果插件未安装或卸载失败
|
||||
*/
|
||||
public async uninstall(name: string): Promise<void> {
|
||||
const plugin = this._plugins.get(name);
|
||||
if (!plugin) {
|
||||
throw new Error(`Plugin ${name} is not installed`);
|
||||
}
|
||||
|
||||
// 检查是否有其他插件依赖此插件
|
||||
this._checkDependents(name);
|
||||
|
||||
try {
|
||||
logger.info(`Uninstalling plugin: ${name}`);
|
||||
await plugin.uninstall();
|
||||
|
||||
// 从注册表中移除
|
||||
this._plugins.delete(name);
|
||||
this._metadata.delete(name);
|
||||
|
||||
logger.info(`Plugin ${name} uninstalled successfully`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to uninstall plugin ${name}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件实例
|
||||
*
|
||||
* @param name - 插件名称
|
||||
* @returns 插件实例,如果未安装则返回undefined
|
||||
*/
|
||||
public getPlugin(name: string): IPlugin | undefined {
|
||||
return this._plugins.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件元数据
|
||||
*
|
||||
* @param name - 插件名称
|
||||
* @returns 插件元数据,如果未安装则返回undefined
|
||||
*/
|
||||
public getMetadata(name: string): IPluginMetadata | undefined {
|
||||
return this._metadata.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已安装的插件
|
||||
*
|
||||
* @returns 插件列表
|
||||
*/
|
||||
public getAllPlugins(): IPlugin[] {
|
||||
return Array.from(this._plugins.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有插件元数据
|
||||
*
|
||||
* @returns 元数据列表
|
||||
*/
|
||||
public getAllMetadata(): IPluginMetadata[] {
|
||||
return Array.from(this._metadata.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查插件是否已安装
|
||||
*
|
||||
* @param name - 插件名称
|
||||
* @returns 是否已安装
|
||||
*/
|
||||
public isInstalled(name: string): boolean {
|
||||
return this._plugins.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查插件依赖
|
||||
*
|
||||
* @param plugin - 插件实例
|
||||
* @throws 如果依赖未满足
|
||||
*/
|
||||
private _checkDependencies(plugin: IPlugin): void {
|
||||
if (!plugin.dependencies) {
|
||||
return;
|
||||
}
|
||||
|
||||
const missingDeps: string[] = [];
|
||||
|
||||
for (const dep of plugin.dependencies) {
|
||||
if (!this._plugins.has(dep)) {
|
||||
missingDeps.push(dep);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingDeps.length > 0) {
|
||||
throw new Error(
|
||||
`Plugin ${plugin.name} has unmet dependencies: ${missingDeps.join(', ')}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有其他插件依赖指定插件
|
||||
*
|
||||
* @param name - 插件名称
|
||||
* @throws 如果有其他插件依赖此插件
|
||||
*/
|
||||
private _checkDependents(name: string): void {
|
||||
const dependents: string[] = [];
|
||||
|
||||
for (const plugin of this._plugins.values()) {
|
||||
if (plugin.dependencies && plugin.dependencies.includes(name)) {
|
||||
dependents.push(plugin.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (dependents.length > 0) {
|
||||
throw new Error(
|
||||
`Cannot uninstall plugin ${name}: it is required by ${dependents.join(', ')}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
public dispose(): void {
|
||||
// 卸载所有插件(逆序,先卸载依赖项)
|
||||
const plugins = Array.from(this._plugins.values()).reverse();
|
||||
|
||||
for (const plugin of plugins) {
|
||||
try {
|
||||
logger.info(`Disposing plugin: ${plugin.name}`);
|
||||
plugin.uninstall();
|
||||
} catch (error) {
|
||||
logger.error(`Error disposing plugin ${plugin.name}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
this._plugins.clear();
|
||||
this._metadata.clear();
|
||||
this._core = null;
|
||||
this._services = null;
|
||||
|
||||
logger.info('PluginManager disposed');
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,11 @@ export { Core } from './Core';
|
||||
export { ServiceContainer, ServiceLifetime } from './Core/ServiceContainer';
|
||||
export type { IService, ServiceType } from './Core/ServiceContainer';
|
||||
|
||||
// 插件系统
|
||||
export { PluginManager } from './Core/PluginManager';
|
||||
export { PluginState } from './Core/Plugin';
|
||||
export type { IPlugin, IPluginMetadata } from './Core/Plugin';
|
||||
|
||||
// 依赖注入
|
||||
export {
|
||||
Injectable,
|
||||
|
||||
396
packages/core/tests/Core/PluginSystem.test.ts
Normal file
396
packages/core/tests/Core/PluginSystem.test.ts
Normal file
@@ -0,0 +1,396 @@
|
||||
import { Core } from '../../src/Core';
|
||||
import { IPlugin } from '../../src/Core/Plugin';
|
||||
import { PluginManager } from '../../src/Core/PluginManager';
|
||||
import type { ServiceContainer } from '../../src/Core/ServiceContainer';
|
||||
|
||||
describe('插件系统', () => {
|
||||
let core: Core;
|
||||
|
||||
beforeEach(() => {
|
||||
core = Core.create({ debug: false });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Core.destroy();
|
||||
});
|
||||
|
||||
describe('基本功能', () => {
|
||||
it('应该能够安装插件', async () => {
|
||||
class TestPlugin implements IPlugin {
|
||||
readonly name = 'test-plugin';
|
||||
readonly version = '1.0.0';
|
||||
installed = false;
|
||||
|
||||
install() {
|
||||
this.installed = true;
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
this.installed = false;
|
||||
}
|
||||
}
|
||||
|
||||
const plugin = new TestPlugin();
|
||||
await Core.installPlugin(plugin);
|
||||
|
||||
expect(plugin.installed).toBe(true);
|
||||
expect(Core.isPluginInstalled('test-plugin')).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能够卸载插件', async () => {
|
||||
class TestPlugin implements IPlugin {
|
||||
readonly name = 'test-plugin';
|
||||
readonly version = '1.0.0';
|
||||
installed = false;
|
||||
|
||||
install() {
|
||||
this.installed = true;
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
this.installed = false;
|
||||
}
|
||||
}
|
||||
|
||||
const plugin = new TestPlugin();
|
||||
await Core.installPlugin(plugin);
|
||||
expect(plugin.installed).toBe(true);
|
||||
|
||||
await Core.uninstallPlugin('test-plugin');
|
||||
expect(plugin.installed).toBe(false);
|
||||
expect(Core.isPluginInstalled('test-plugin')).toBe(false);
|
||||
});
|
||||
|
||||
it('应该能够获取已安装的插件', async () => {
|
||||
class TestPlugin implements IPlugin {
|
||||
readonly name = 'test-plugin';
|
||||
readonly version = '1.0.0';
|
||||
|
||||
install() {}
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
const plugin = new TestPlugin();
|
||||
await Core.installPlugin(plugin);
|
||||
|
||||
const installed = Core.getPlugin('test-plugin');
|
||||
expect(installed).toBe(plugin);
|
||||
expect(installed?.version).toBe('1.0.0');
|
||||
});
|
||||
|
||||
it('应该拒绝重复安装同一个插件', async () => {
|
||||
class TestPlugin implements IPlugin {
|
||||
readonly name = 'test-plugin';
|
||||
readonly version = '1.0.0';
|
||||
|
||||
install() {}
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
const plugin1 = new TestPlugin();
|
||||
const plugin2 = new TestPlugin();
|
||||
|
||||
await Core.installPlugin(plugin1);
|
||||
await Core.installPlugin(plugin2);
|
||||
|
||||
expect(Core.getPlugin('test-plugin')).toBe(plugin1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('依赖管理', () => {
|
||||
it('应该检查插件依赖', async () => {
|
||||
class BasePlugin implements IPlugin {
|
||||
readonly name = 'base-plugin';
|
||||
readonly version = '1.0.0';
|
||||
|
||||
install() {}
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
class DependentPlugin implements IPlugin {
|
||||
readonly name = 'dependent-plugin';
|
||||
readonly version = '1.0.0';
|
||||
readonly dependencies = ['base-plugin'] as const;
|
||||
|
||||
install() {}
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
const dependentPlugin = new DependentPlugin();
|
||||
|
||||
await expect(Core.installPlugin(dependentPlugin)).rejects.toThrow(
|
||||
'unmet dependencies'
|
||||
);
|
||||
});
|
||||
|
||||
it('应该允许按正确顺序安装有依赖的插件', async () => {
|
||||
class BasePlugin implements IPlugin {
|
||||
readonly name = 'base-plugin';
|
||||
readonly version = '1.0.0';
|
||||
|
||||
install() {}
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
class DependentPlugin implements IPlugin {
|
||||
readonly name = 'dependent-plugin';
|
||||
readonly version = '1.0.0';
|
||||
readonly dependencies = ['base-plugin'] as const;
|
||||
|
||||
install() {}
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
const basePlugin = new BasePlugin();
|
||||
const dependentPlugin = new DependentPlugin();
|
||||
|
||||
await Core.installPlugin(basePlugin);
|
||||
await Core.installPlugin(dependentPlugin);
|
||||
|
||||
expect(Core.isPluginInstalled('base-plugin')).toBe(true);
|
||||
expect(Core.isPluginInstalled('dependent-plugin')).toBe(true);
|
||||
});
|
||||
|
||||
it('应该防止卸载被依赖的插件', async () => {
|
||||
class BasePlugin implements IPlugin {
|
||||
readonly name = 'base-plugin';
|
||||
readonly version = '1.0.0';
|
||||
|
||||
install() {}
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
class DependentPlugin implements IPlugin {
|
||||
readonly name = 'dependent-plugin';
|
||||
readonly version = '1.0.0';
|
||||
readonly dependencies = ['base-plugin'] as const;
|
||||
|
||||
install() {}
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
await Core.installPlugin(new BasePlugin());
|
||||
await Core.installPlugin(new DependentPlugin());
|
||||
|
||||
await expect(Core.uninstallPlugin('base-plugin')).rejects.toThrow(
|
||||
'required by'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('服务注册', () => {
|
||||
it('应该允许插件注册服务', async () => {
|
||||
class TestService {
|
||||
public value = 42;
|
||||
dispose() {}
|
||||
}
|
||||
|
||||
class ServicePlugin implements IPlugin {
|
||||
readonly name = 'service-plugin';
|
||||
readonly version = '1.0.0';
|
||||
|
||||
install(core: Core, services: ServiceContainer) {
|
||||
services.registerSingleton(TestService);
|
||||
}
|
||||
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
await Core.installPlugin(new ServicePlugin());
|
||||
|
||||
const service = Core.services.resolve(TestService);
|
||||
expect(service.value).toBe(42);
|
||||
});
|
||||
|
||||
it('应该允许插件访问Core和ServiceContainer', async () => {
|
||||
let capturedCore: Core | null = null;
|
||||
let capturedServices: ServiceContainer | null = null;
|
||||
|
||||
class TestPlugin implements IPlugin {
|
||||
readonly name = 'test-plugin';
|
||||
readonly version = '1.0.0';
|
||||
|
||||
install(core: Core, services: ServiceContainer) {
|
||||
capturedCore = core;
|
||||
capturedServices = services;
|
||||
}
|
||||
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
await Core.installPlugin(new TestPlugin());
|
||||
|
||||
expect(capturedCore).toBe(Core.Instance);
|
||||
expect(capturedServices).toBe(Core.services);
|
||||
});
|
||||
});
|
||||
|
||||
describe('异步插件', () => {
|
||||
it('应该支持异步安装', async () => {
|
||||
class AsyncPlugin implements IPlugin {
|
||||
readonly name = 'async-plugin';
|
||||
readonly version = '1.0.0';
|
||||
initialized = false;
|
||||
|
||||
async install() {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
const plugin = new AsyncPlugin();
|
||||
await Core.installPlugin(plugin);
|
||||
|
||||
expect(plugin.initialized).toBe(true);
|
||||
});
|
||||
|
||||
it('应该支持异步卸载', async () => {
|
||||
class AsyncPlugin implements IPlugin {
|
||||
readonly name = 'async-plugin';
|
||||
readonly version = '1.0.0';
|
||||
cleaned = false;
|
||||
|
||||
install() {}
|
||||
|
||||
async uninstall() {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
this.cleaned = true;
|
||||
}
|
||||
}
|
||||
|
||||
const plugin = new AsyncPlugin();
|
||||
await Core.installPlugin(plugin);
|
||||
await Core.uninstallPlugin('async-plugin');
|
||||
|
||||
expect(plugin.cleaned).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('错误处理', () => {
|
||||
it('应该捕获安装错误', async () => {
|
||||
class FailingPlugin implements IPlugin {
|
||||
readonly name = 'failing-plugin';
|
||||
readonly version = '1.0.0';
|
||||
|
||||
install() {
|
||||
throw new Error('安装失败');
|
||||
}
|
||||
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
await expect(Core.installPlugin(new FailingPlugin())).rejects.toThrow(
|
||||
'安装失败'
|
||||
);
|
||||
|
||||
expect(Core.isPluginInstalled('failing-plugin')).toBe(false);
|
||||
});
|
||||
|
||||
it('应该捕获卸载错误', async () => {
|
||||
class FailingPlugin implements IPlugin {
|
||||
readonly name = 'failing-plugin';
|
||||
readonly version = '1.0.0';
|
||||
|
||||
install() {}
|
||||
|
||||
uninstall() {
|
||||
throw new Error('卸载失败');
|
||||
}
|
||||
}
|
||||
|
||||
await Core.installPlugin(new FailingPlugin());
|
||||
|
||||
await expect(Core.uninstallPlugin('failing-plugin')).rejects.toThrow(
|
||||
'卸载失败'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PluginManager直接使用', () => {
|
||||
it('应该能够从ServiceContainer获取PluginManager', () => {
|
||||
const pluginManager = Core.services.resolve(PluginManager);
|
||||
expect(pluginManager).toBeDefined();
|
||||
});
|
||||
|
||||
it('应该能够获取所有插件', async () => {
|
||||
class Plugin1 implements IPlugin {
|
||||
readonly name = 'plugin1';
|
||||
readonly version = '1.0.0';
|
||||
install() {}
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
class Plugin2 implements IPlugin {
|
||||
readonly name = 'plugin2';
|
||||
readonly version = '2.0.0';
|
||||
install() {}
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
await Core.installPlugin(new Plugin1());
|
||||
await Core.installPlugin(new Plugin2());
|
||||
|
||||
const pluginManager = Core.services.resolve(PluginManager);
|
||||
const allPlugins = pluginManager.getAllPlugins();
|
||||
|
||||
expect(allPlugins).toHaveLength(2);
|
||||
expect(allPlugins.map(p => p.name)).toContain('plugin1');
|
||||
expect(allPlugins.map(p => p.name)).toContain('plugin2');
|
||||
});
|
||||
|
||||
it('应该能够获取插件元数据', async () => {
|
||||
class TestPlugin implements IPlugin {
|
||||
readonly name = 'test-plugin';
|
||||
readonly version = '1.0.0';
|
||||
install() {}
|
||||
uninstall() {}
|
||||
}
|
||||
|
||||
await Core.installPlugin(new TestPlugin());
|
||||
|
||||
const pluginManager = Core.services.resolve(PluginManager);
|
||||
const metadata = pluginManager.getMetadata('test-plugin');
|
||||
|
||||
expect(metadata).toBeDefined();
|
||||
expect(metadata?.name).toBe('test-plugin');
|
||||
expect(metadata?.version).toBe('1.0.0');
|
||||
expect(metadata?.state).toBe('installed');
|
||||
expect(metadata?.installedAt).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('生命周期', () => {
|
||||
it('应该在Core销毁时卸载所有插件', async () => {
|
||||
const uninstallCalls: string[] = [];
|
||||
|
||||
class Plugin1 implements IPlugin {
|
||||
readonly name = 'plugin1';
|
||||
readonly version = '1.0.0';
|
||||
install() {}
|
||||
uninstall() {
|
||||
uninstallCalls.push('plugin1');
|
||||
}
|
||||
}
|
||||
|
||||
class Plugin2 implements IPlugin {
|
||||
readonly name = 'plugin2';
|
||||
readonly version = '1.0.0';
|
||||
readonly dependencies = ['plugin1'] as const;
|
||||
install() {}
|
||||
uninstall() {
|
||||
uninstallCalls.push('plugin2');
|
||||
}
|
||||
}
|
||||
|
||||
await Core.installPlugin(new Plugin1());
|
||||
await Core.installPlugin(new Plugin2());
|
||||
|
||||
Core.destroy();
|
||||
|
||||
expect(uninstallCalls).toContain('plugin1');
|
||||
expect(uninstallCalls).toContain('plugin2');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user