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,214 @@
/**
* Bundle runtime files for production build
* 为生产构建打包运行时文件
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const editorPath = path.resolve(__dirname, '..');
const rootPath = path.resolve(editorPath, '../..');
const bundleDir = path.join(editorPath, 'src-tauri', 'runtime');
// Create bundle directory
if (!fs.existsSync(bundleDir)) {
fs.mkdirSync(bundleDir, { recursive: true });
console.log(`Created bundle directory: ${bundleDir}`);
}
// Files to bundle
// 需要打包的文件
const filesToBundle = [
{
src: path.join(rootPath, 'packages/platform-web/dist/index.mjs'),
dst: path.join(bundleDir, 'platform-web.mjs')
},
{
src: path.join(rootPath, 'packages/engine/pkg/es_engine_bg.wasm'),
dst: path.join(bundleDir, 'es_engine_bg.wasm')
},
{
src: path.join(rootPath, 'packages/engine/pkg/es_engine.js'),
dst: path.join(bundleDir, 'es_engine.js')
}
];
// Type definition files for IDE intellisense
// 用于 IDE 智能感知的类型定义文件
const typesDir = path.join(bundleDir, 'types');
if (!fs.existsSync(typesDir)) {
fs.mkdirSync(typesDir, { recursive: true });
console.log(`Created types directory: ${typesDir}`);
}
const typeFilesToBundle = [
{
src: path.join(rootPath, 'packages/core/dist/index.d.ts'),
dst: path.join(typesDir, 'ecs-framework.d.ts')
},
{
src: path.join(rootPath, 'packages/engine-core/dist/index.d.ts'),
dst: path.join(typesDir, 'engine-core.d.ts')
}
];
// Copy files
let success = true;
for (const { src, dst } of filesToBundle) {
try {
if (!fs.existsSync(src)) {
console.error(`Source file not found: ${src}`);
console.log('Please build the runtime modules first:');
console.log(' npm run build --workspace=@esengine/platform-web');
console.log(' cd packages/engine && wasm-pack build --target web');
success = false;
continue;
}
fs.copyFileSync(src, dst);
const stats = fs.statSync(dst);
console.log(`✓ Bundled ${path.basename(dst)} (${(stats.size / 1024).toFixed(2)} KB)`);
} catch (error) {
console.error(`Failed to bundle ${path.basename(src)}: ${error.message}`);
success = false;
}
}
// Copy type definition files (optional - don't fail if not found)
// 复制类型定义文件(可选 - 找不到不报错)
for (const { src, dst } of typeFilesToBundle) {
try {
if (!fs.existsSync(src)) {
console.warn(`Type definition not found: ${src}`);
console.log(' Build packages first: pnpm --filter @esengine/core build');
continue;
}
fs.copyFileSync(src, dst);
const stats = fs.statSync(dst);
console.log(`✓ Bundled type definition ${path.basename(dst)} (${(stats.size / 1024).toFixed(2)} KB)`);
} catch (error) {
console.warn(`Failed to bundle type definition ${path.basename(src)}: ${error.message}`);
}
}
// Copy engine modules directory from dist/engine to src-tauri/engine
// 复制引擎模块目录从 dist/engine 到 src-tauri/engine
const engineSrcDir = path.join(editorPath, 'dist', 'engine');
const engineDstDir = path.join(editorPath, 'src-tauri', 'engine');
/**
* Recursively copy directory
* 递归复制目录
*/
function copyDirRecursive(src, dst) {
if (!fs.existsSync(src)) {
return false;
}
if (!fs.existsSync(dst)) {
fs.mkdirSync(dst, { recursive: true });
}
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const dstPath = path.join(dst, entry.name);
if (entry.isDirectory()) {
copyDirRecursive(srcPath, dstPath);
} else {
fs.copyFileSync(srcPath, dstPath);
}
}
return true;
}
if (fs.existsSync(engineSrcDir)) {
// Remove old engine directory if exists
if (fs.existsSync(engineDstDir)) {
fs.rmSync(engineDstDir, { recursive: true });
}
if (copyDirRecursive(engineSrcDir, engineDstDir)) {
// Count files
let fileCount = 0;
function countFiles(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
countFiles(path.join(dir, entry.name));
} else {
fileCount++;
}
}
}
countFiles(engineDstDir);
console.log(`✓ Copied engine modules directory (${fileCount} files)`);
}
} else {
console.warn(`Engine modules directory not found: ${engineSrcDir}`);
console.log(' Build editor-app first: pnpm --filter @esengine/editor-app build');
}
// Copy esbuild binary for user code compilation
// 复制 esbuild 二进制文件用于用户代码编译
const binDir = path.join(editorPath, 'src-tauri', 'bin');
if (!fs.existsSync(binDir)) {
fs.mkdirSync(binDir, { recursive: true });
console.log(`Created bin directory: ${binDir}`);
}
// Platform-specific esbuild binary paths
// 平台特定的 esbuild 二进制路径
const esbuildSources = {
win32: path.join(rootPath, 'node_modules/@esbuild/win32-x64/esbuild.exe'),
darwin: path.join(rootPath, 'node_modules/@esbuild/darwin-x64/bin/esbuild'),
linux: path.join(rootPath, 'node_modules/@esbuild/linux-x64/bin/esbuild'),
};
const platform = process.platform;
const esbuildSrc = esbuildSources[platform];
const esbuildDst = path.join(binDir, platform === 'win32' ? 'esbuild.exe' : 'esbuild');
let esbuildBundled = false;
if (esbuildSrc && fs.existsSync(esbuildSrc)) {
try {
fs.copyFileSync(esbuildSrc, esbuildDst);
// Ensure executable permission on Unix
if (platform !== 'win32') {
fs.chmodSync(esbuildDst, 0o755);
}
const stats = fs.statSync(esbuildDst);
console.log(`✓ Bundled esbuild binary (${(stats.size / 1024 / 1024).toFixed(2)} MB)`);
esbuildBundled = true;
} catch (error) {
console.warn(`Failed to bundle esbuild: ${error.message}`);
console.log(' User code compilation will require global esbuild installation');
}
} else {
console.warn(`esbuild binary not found for platform ${platform}: ${esbuildSrc}`);
console.log(' User code compilation will require global esbuild installation');
}
// Create a placeholder file if esbuild was not bundled
// Tauri requires resources patterns to match at least one file
// 如果 esbuild 没有打包,创建占位文件
// Tauri 要求资源模式至少匹配一个文件
if (!esbuildBundled) {
const placeholderPath = path.join(binDir, '.gitkeep');
fs.writeFileSync(placeholderPath, '# Placeholder for Tauri resources\n# esbuild binary will be bundled during release build\n');
console.log('✓ Created placeholder in bin directory');
}
if (!success) {
console.error('Runtime bundling failed');
process.exit(1);
}
console.log('Runtime files bundled successfully!');

View File

@@ -0,0 +1,47 @@
/**
* 清理开发服务器进程
* 用于 Windows 平台自动清理残留的 Vite 进程
*/
import { execSync } from 'child_process';
const PORT = 5173;
try {
console.log(`正在查找占用端口 ${PORT} 的进程...`);
// Windows 命令
const result = execSync(`netstat -ano | findstr :${PORT}`, { encoding: 'utf8' });
// 解析 PID
const lines = result.split('\n');
const pids = new Set();
for (const line of lines) {
if (line.includes('LISTENING')) {
const parts = line.trim().split(/\s+/);
const pid = parts[parts.length - 1];
if (pid && pid !== '0') {
pids.add(pid);
}
}
}
if (pids.size === 0) {
console.log(`✓ 端口 ${PORT} 未被占用`);
} else {
console.log(`发现 ${pids.size} 个进程占用端口 ${PORT}`);
for (const pid of pids) {
try {
// Windows 需要使用 /F /PID 而不是 //F //PID
execSync(`taskkill /F /PID ${pid}`, { encoding: 'utf8', stdio: 'ignore' });
console.log(`✓ 已终止进程 PID: ${pid}`);
} catch (e) {
console.log(`✗ 无法终止进程 PID: ${pid}`);
}
}
}
} catch (error) {
// 如果 netstat 没有找到结果,会抛出错误,这是正常的
console.log(`✓ 端口 ${PORT} 未被占用`);
}

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env node
/**
* 同步 package.json 和 tauri.conf.json 的版本号
* 在 npm version 命令执行后自动运行
*/
import { readFileSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// 读取 package.json
const packageJsonPath = join(__dirname, '../package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
const newVersion = packageJson.version;
// 读取 tauri.conf.json
const tauriConfigPath = join(__dirname, '../src-tauri/tauri.conf.json');
const tauriConfig = JSON.parse(readFileSync(tauriConfigPath, 'utf8'));
// 更新 tauri.conf.json 的版本号
const oldVersion = tauriConfig.version;
tauriConfig.version = newVersion;
// 写回文件(保持格式)
writeFileSync(tauriConfigPath, JSON.stringify(tauriConfig, null, 2) + '\n', 'utf8');
console.log(`✓ Version synced: ${oldVersion}${newVersion}`);
console.log(` - package.json: ${newVersion}`);
console.log(` - tauri.conf.json: ${newVersion}`);