refactor: reorganize package structure and decouple framework packages (#338)

* refactor: reorganize package structure and decouple framework packages

## Package Structure Reorganization
- Reorganized 55 packages into categorized subdirectories:
  - packages/framework/ - Generic framework (Laya/Cocos compatible)
  - packages/engine/ - ESEngine core modules
  - packages/rendering/ - Rendering modules (WASM dependent)
  - packages/physics/ - Physics modules
  - packages/streaming/ - World streaming
  - packages/network-ext/ - Network extensions
  - packages/editor/ - Editor framework and plugins
  - packages/rust/ - Rust WASM engine
  - packages/tools/ - Build tools and SDK

## Framework Package Decoupling
- Decoupled behavior-tree and blueprint packages from ESEngine dependencies
- Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent)
- ESEngine-specific code moved to esengine/ subpath exports
- Framework packages now usable with Cocos/Laya without ESEngine

## CI Configuration
- Updated CI to only type-check and lint framework packages
- Added type-check:framework and lint:framework scripts

## Breaking Changes
- Package import paths changed due to directory reorganization
- ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine')

* fix: update es-engine file path after directory reorganization

* docs: update README to focus on framework over engine

* ci: only build framework packages, remove Rust/WASM dependencies

* fix: remove esengine subpath from behavior-tree and blueprint builds

ESEngine integration code will only be available in full engine builds.
Framework packages are now purely engine-agnostic.

* fix: move network-protocols to framework, build both in CI

* fix: update workflow paths from packages/core to packages/framework/core

* fix: exclude esengine folder from type-check in behavior-tree and blueprint

* fix: update network tsconfig references to new paths

* fix: add test:ci:framework to only test framework packages in CI

* fix: only build core and math npm packages in CI

* fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
YHH
2025-12-26 14:50:35 +08:00
committed by GitHub
parent a84ff902e4
commit 155411e743
1936 changed files with 4147 additions and 11578 deletions

View File

@@ -0,0 +1,268 @@
import { Core } from '@esengine/ecs-framework';
import {
EditorPluginManager,
UIRegistry,
MessageHub,
SerializerRegistry,
EditorPluginCategory,
PanelPosition
} from '../src';
import type { IEditorPlugin, ISerializer } from '../src';
class TestSerializer implements ISerializer<string> {
serialize(data: string): Uint8Array {
const encoder = new TextEncoder();
return encoder.encode(data);
}
deserialize(data: Uint8Array): string {
const decoder = new TextDecoder();
return decoder.decode(data);
}
getSupportedType(): string {
return 'string';
}
}
class TestEditorPlugin implements IEditorPlugin {
readonly name = 'test-editor-plugin';
readonly version = '1.0.0';
readonly displayName = 'Test Editor Plugin';
readonly category = EditorPluginCategory.Tool;
readonly description = 'A test plugin for editor';
async install(): Promise<void> {
// 测试安装
}
async uninstall(): Promise<void> {
// 测试卸载
}
registerMenuItems() {
return [
{
id: 'test-menu',
label: 'Test Menu',
onClick: () => {}
}
];
}
registerToolbar() {
return [
{
id: 'test-toolbar',
label: 'Test Toolbar',
groupId: 'test-group',
onClick: () => {}
}
];
}
registerPanels() {
return [
{
id: 'test-panel',
title: 'Test Panel',
position: PanelPosition.Left
}
];
}
getSerializers() {
return [new TestSerializer()];
}
}
describe('EditorPluginManager', () => {
let core: Core;
let pluginManager: EditorPluginManager;
let uiRegistry: UIRegistry;
let messageHub: MessageHub;
let serializerRegistry: SerializerRegistry;
beforeEach(() => {
core = Core.create({ debug: false });
uiRegistry = new UIRegistry();
messageHub = new MessageHub();
serializerRegistry = new SerializerRegistry();
Core.services.registerInstance(UIRegistry, uiRegistry);
Core.services.registerInstance(MessageHub, messageHub);
Core.services.registerInstance(SerializerRegistry, serializerRegistry);
pluginManager = new EditorPluginManager();
pluginManager.initialize(core, Core.services);
});
afterEach(() => {
pluginManager.dispose();
Core.destroy();
});
describe('基本功能', () => {
it('应该能够安装编辑器插件', async () => {
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
expect(pluginManager.isInstalled(plugin.name)).toBe(true);
expect(pluginManager.getEditorPlugin(plugin.name)).toBe(plugin);
});
it('应该能够卸载编辑器插件', async () => {
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
await pluginManager.uninstallEditor(plugin.name);
expect(pluginManager.isInstalled(plugin.name)).toBe(false);
expect(pluginManager.getEditorPlugin(plugin.name)).toBeUndefined();
});
it('应该能够获取插件元数据', async () => {
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
const metadata = pluginManager.getPluginMetadata(plugin.name);
expect(metadata).toBeDefined();
expect(metadata?.name).toBe(plugin.name);
expect(metadata?.displayName).toBe(plugin.displayName);
expect(metadata?.category).toBe(plugin.category);
expect(metadata?.enabled).toBe(true);
});
});
describe('UI 注册', () => {
it('应该注册菜单项', async () => {
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
const menu = uiRegistry.getMenu('test-menu');
expect(menu).toBeDefined();
expect(menu?.label).toBe('Test Menu');
});
it('应该注册工具栏项', async () => {
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
const toolbar = uiRegistry.getToolbarItem('test-toolbar');
expect(toolbar).toBeDefined();
expect(toolbar?.label).toBe('Test Toolbar');
});
it('应该注册面板', async () => {
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
const panel = uiRegistry.getPanel('test-panel');
expect(panel).toBeDefined();
expect(panel?.title).toBe('Test Panel');
});
it('卸载插件时应该注销 UI 元素', async () => {
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
await pluginManager.uninstallEditor(plugin.name);
expect(uiRegistry.getMenu('test-menu')).toBeUndefined();
expect(uiRegistry.getToolbarItem('test-toolbar')).toBeUndefined();
expect(uiRegistry.getPanel('test-panel')).toBeUndefined();
});
});
describe('序列化器注册', () => {
it('应该注册序列化器', async () => {
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
expect(serializerRegistry.has(plugin.name, 'string')).toBe(true);
});
it('应该能够使用序列化器', async () => {
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
const testData = 'Hello World';
const serialized = serializerRegistry.serialize(plugin.name, 'string', testData);
const deserialized = serializerRegistry.deserialize(plugin.name, 'string', serialized);
expect(deserialized).toBe(testData);
});
it('卸载插件时应该注销序列化器', async () => {
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
await pluginManager.uninstallEditor(plugin.name);
expect(serializerRegistry.has(plugin.name, 'string')).toBe(false);
});
});
describe('事件通知', () => {
it('应该发送插件安装事件', async () => {
let eventReceived = false;
messageHub.subscribe('plugin:installed', (data: any) => {
eventReceived = true;
expect(data.name).toBe('test-editor-plugin');
});
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
expect(eventReceived).toBe(true);
});
it('应该发送项目打开事件', async () => {
let eventReceived = false;
messageHub.subscribe('project:opened', (data: any) => {
eventReceived = true;
expect(data.path).toBe('/test/project');
});
await pluginManager.notifyProjectOpen('/test/project');
expect(eventReceived).toBe(true);
});
it('应该发送项目关闭事件', async () => {
let eventReceived = false;
messageHub.subscribe('project:closed', () => {
eventReceived = true;
});
await pluginManager.notifyProjectClose();
expect(eventReceived).toBe(true);
});
});
describe('插件管理', () => {
it('应该能够按类别获取插件', async () => {
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
const plugins = pluginManager.getPluginsByCategory(EditorPluginCategory.Tool);
expect(plugins.length).toBe(1);
expect(plugins[0]).toBe(plugin);
});
it('应该能够启用/禁用插件', async () => {
const plugin = new TestEditorPlugin();
await pluginManager.installEditor(plugin);
await pluginManager.disablePlugin(plugin.name);
let metadata = pluginManager.getPluginMetadata(plugin.name);
expect(metadata?.enabled).toBe(false);
await pluginManager.enablePlugin(plugin.name);
metadata = pluginManager.getPluginMetadata(plugin.name);
expect(metadata?.enabled).toBe(true);
});
});
});