fix(editor): fix build errors and refactor behavior-tree architecture (#394)

* docs: add editor-app README with setup instructions

* docs: add separate EN/CN editor setup guides

* fix(editor): fix build errors and refactor behavior-tree architecture

- Fix fairygui-editor tsconfig extends path and add missing tsconfig.build.json
- Refactor behavior-tree-editor to not depend on asset-system in runtime
  - Create local BehaviorTreeRuntimeModule for pure runtime logic
  - Move asset loader registration to editor module install()
  - Add BehaviorTreeLoader for asset system integration
- Fix rapier2d WASM loader to not pass arguments to init()
- Add WASM base64 loader config to rapier2d tsup.config
- Update README documentation and simplify setup steps
This commit is contained in:
YHH
2025-12-30 11:13:26 +08:00
committed by GitHub
parent d21caa974e
commit b28169b186
25 changed files with 852 additions and 625 deletions

View File

@@ -228,6 +228,7 @@ If you want a complete engine solution with rendering:
A visual editor built with Tauri for scene management: A visual editor built with Tauri for scene management:
- Download from [Releases](https://github.com/esengine/esengine/releases) - Download from [Releases](https://github.com/esengine/esengine/releases)
- [Build from source](./packages/editor/editor-app/README.md)
- Supports behavior tree editing, tilemap painting, visual scripting - Supports behavior tree editing, tilemap painting, visual scripting
## Project Structure ## Project Structure
@@ -281,6 +282,7 @@ pnpm test
- [ECS Framework Guide](./packages/framework/core/README.md) - [ECS Framework Guide](./packages/framework/core/README.md)
- [Behavior Tree Guide](./packages/framework/behavior-tree/README.md) - [Behavior Tree Guide](./packages/framework/behavior-tree/README.md)
- [Editor Setup Guide](./packages/editor/editor-app/README.md) ([中文](./packages/editor/editor-app/README_CN.md))
- [API Reference](https://esengine.cn/api/README) - [API Reference](https://esengine.cn/api/README)
## Community ## Community

View File

@@ -228,6 +228,7 @@ npm install @esengine/world-streaming # 世界流送
基于 Tauri 构建的可视化编辑器: 基于 Tauri 构建的可视化编辑器:
- 从 [Releases](https://github.com/esengine/esengine/releases) 下载 - 从 [Releases](https://github.com/esengine/esengine/releases) 下载
- [从源码构建](./packages/editor/editor-app/README.md)
- 支持行为树编辑、Tilemap 绘制、可视化脚本 - 支持行为树编辑、Tilemap 绘制、可视化脚本
## 项目结构 ## 项目结构
@@ -281,6 +282,7 @@ pnpm test
- [ECS 框架指南](./packages/framework/core/README.md) - [ECS 框架指南](./packages/framework/core/README.md)
- [行为树指南](./packages/framework/behavior-tree/README.md) - [行为树指南](./packages/framework/behavior-tree/README.md)
- [编辑器启动指南](./packages/editor/editor-app/README_CN.md) ([English](./packages/editor/editor-app/README.md))
- [API 参考](https://esengine.cn/api/README) - [API 参考](https://esengine.cn/api/README)
## 社区 ## 社区

View File

@@ -0,0 +1,86 @@
# ESEngine Editor
A cross-platform desktop visual editor built with Tauri 2.x + React 18.
## Prerequisites
Before running the editor, ensure you have the following installed:
- **Node.js** >= 18.x
- **pnpm** >= 10.x
- **Rust** >= 1.70 (for Tauri)
- **Platform-specific dependencies**:
- **Windows**: Microsoft Visual Studio C++ Build Tools
- **macOS**: Xcode Command Line Tools (`xcode-select --install`)
- **Linux**: See [Tauri prerequisites](https://tauri.app/v1/guides/getting-started/prerequisites)
## Quick Start
### 1. Clone and Install
```bash
git clone https://github.com/esengine/esengine.git
cd esengine
pnpm install
```
### 2. Build Dependencies
From the project root:
```bash
pnpm build:editor
```
### 3. Run Editor
```bash
cd packages/editor/editor-app
pnpm tauri:dev
```
## Available Scripts
| Script | Description |
|--------|-------------|
| `pnpm tauri:dev` | Run editor in development mode with hot-reload |
| `pnpm tauri:build` | Build production application |
| `pnpm build:sdk` | Build editor-runtime SDK |
## Project Structure
```
editor-app/
├── src/ # React application source
│ ├── components/ # UI components
│ ├── panels/ # Editor panels
│ └── services/ # Core services
├── src-tauri/ # Tauri (Rust) backend
├── public/ # Static assets
└── scripts/ # Build scripts
```
## Troubleshooting
### Build Errors
```bash
pnpm clean
pnpm install
pnpm build:editor
```
### Rust/Tauri Errors
```bash
rustup update
```
## Documentation
- [ESEngine Documentation](https://esengine.cn/)
- [Tauri Documentation](https://tauri.app/)
## License
MIT License

View File

@@ -0,0 +1,86 @@
# ESEngine 编辑器
基于 Tauri 2.x + React 18 构建的跨平台桌面可视化编辑器。
## 环境要求
运行编辑器前,请确保已安装以下环境:
- **Node.js** >= 18.x
- **pnpm** >= 10.x
- **Rust** >= 1.70 (Tauri 需要)
- **平台相关依赖**
- **Windows**: Microsoft Visual Studio C++ Build Tools
- **macOS**: Xcode Command Line Tools (`xcode-select --install`)
- **Linux**: 参考 [Tauri 环境配置](https://tauri.app/v1/guides/getting-started/prerequisites)
## 快速开始
### 1. 克隆并安装
```bash
git clone https://github.com/esengine/esengine.git
cd esengine
pnpm install
```
### 2. 构建依赖
在项目根目录执行:
```bash
pnpm build:editor
```
### 3. 启动编辑器
```bash
cd packages/editor/editor-app
pnpm tauri:dev
```
## 可用脚本
| 脚本 | 说明 |
|------|------|
| `pnpm tauri:dev` | 开发模式运行编辑器(支持热重载)|
| `pnpm tauri:build` | 构建生产版本应用 |
| `pnpm build:sdk` | 构建 editor-runtime SDK |
## 项目结构
```
editor-app/
├── src/ # React 应用源码
│ ├── components/ # UI 组件
│ ├── panels/ # 编辑器面板
│ └── services/ # 核心服务
├── src-tauri/ # Tauri (Rust) 后端
├── public/ # 静态资源
└── scripts/ # 构建脚本
```
## 常见问题
### 构建错误
```bash
pnpm clean
pnpm install
pnpm build:editor
```
### Rust/Tauri 错误
```bash
rustup update
```
## 文档
- [ESEngine 文档](https://esengine.cn/)
- [Tauri 文档](https://tauri.app/)
## 许可证
MIT License

View File

@@ -9,7 +9,7 @@
"build": "npm run build:sdk && tsc && vite build", "build": "npm run build:sdk && tsc && vite build",
"build:watch": "vite build --watch", "build:watch": "vite build --watch",
"tauri": "tauri", "tauri": "tauri",
"copy-modules": "node ../../scripts/copy-engine-modules.mjs", "copy-modules": "node ../../../scripts/copy-engine-modules.mjs",
"tauri:dev": "npm run build:sdk && npm run copy-modules && tauri dev", "tauri:dev": "npm run build:sdk && npm run copy-modules && tauri dev",
"bundle:runtime": "node scripts/bundle-runtime.mjs", "bundle:runtime": "node scripts/bundle-runtime.mjs",
"tauri:build": "npm run build:sdk && npm run copy-modules && npm run bundle:runtime && tauri build", "tauri:build": "npm run build:sdk && npm run copy-modules && npm run bundle:runtime && tauri build",

File diff suppressed because it is too large Load Diff

View File

@@ -10,16 +10,16 @@ name = "ecs_editor_lib"
crate-type = ["staticlib", "cdylib", "rlib"] crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies] [build-dependencies]
tauri-build = { version = "2.0", features = [] } tauri-build = { version = "2", features = [] }
[dependencies] [dependencies]
tauri = { version = "2.0", features = ["protocol-asset"] } tauri = { version = "2", features = ["protocol-asset"] }
tauri-plugin-shell = "2.0" tauri-plugin-shell = "2"
tauri-plugin-dialog = "2.0" tauri-plugin-dialog = "2"
tauri-plugin-fs = "2.0" tauri-plugin-fs = "2"
tauri-plugin-updater = "2" tauri-plugin-updater = "2"
tauri-plugin-http = "2.0" tauri-plugin-http = "2"
tauri-plugin-cli = "2.0" tauri-plugin-cli = "2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
glob = "0.3" glob = "0.3"

View File

@@ -30,6 +30,7 @@
"devDependencies": { "devDependencies": {
"@esengine/ecs-framework": "workspace:*", "@esengine/ecs-framework": "workspace:*",
"@esengine/engine-core": "workspace:*", "@esengine/engine-core": "workspace:*",
"@esengine/asset-system": "workspace:*",
"@esengine/editor-core": "workspace:*", "@esengine/editor-core": "workspace:*",
"@esengine/editor-runtime": "workspace:*", "@esengine/editor-runtime": "workspace:*",
"@esengine/node-editor": "workspace:*", "@esengine/node-editor": "workspace:*",

View File

@@ -0,0 +1,45 @@
/**
* @zh ESEngine 行为树运行时模块
* @en ESEngine Behavior Tree Runtime Module
*
* @zh 纯运行时模块,不依赖 asset-system。资产加载由编辑器在 install 时注册。
* @en Pure runtime module, no asset-system dependency. Asset loading is registered by editor during install.
*/
import type { IScene, ServiceContainer, IComponentRegistry } from '@esengine/ecs-framework';
import type { IRuntimeModule, SystemContext } from '@esengine/engine-core';
import {
BehaviorTreeRuntimeComponent,
BehaviorTreeExecutionSystem,
BehaviorTreeAssetManager,
GlobalBlackboardService,
BehaviorTreeSystemToken
} from '@esengine/behavior-tree';
export class BehaviorTreeRuntimeModule implements IRuntimeModule {
registerComponents(registry: IComponentRegistry): void {
registry.register(BehaviorTreeRuntimeComponent);
}
registerServices(services: ServiceContainer): void {
if (!services.isRegistered(GlobalBlackboardService)) {
services.registerSingleton(GlobalBlackboardService);
}
if (!services.isRegistered(BehaviorTreeAssetManager)) {
services.registerSingleton(BehaviorTreeAssetManager);
}
}
createSystems(scene: IScene, context: SystemContext): void {
const ecsServices = (context as { ecsServices?: ServiceContainer }).ecsServices;
const behaviorTreeSystem = new BehaviorTreeExecutionSystem(ecsServices);
if (context.isEditor) {
behaviorTreeSystem.enabled = false;
}
scene.addSystem(behaviorTreeSystem);
context.services.register(BehaviorTreeSystemToken, behaviorTreeSystem);
}
}

View File

@@ -30,8 +30,11 @@ import {
LocaleService, LocaleService,
} from '@esengine/editor-runtime'; } from '@esengine/editor-runtime';
// Runtime imports from @esengine/behavior-tree package // Runtime imports
import { BehaviorTreeRuntimeComponent, BehaviorTreeRuntimeModule } from '@esengine/behavior-tree'; import { BehaviorTreeRuntimeComponent, BehaviorTreeAssetType } from '@esengine/behavior-tree';
import { AssetManagerToken } from '@esengine/asset-system';
import { BehaviorTreeRuntimeModule } from './BehaviorTreeRuntimeModule';
import { BehaviorTreeLoader } from './runtime/BehaviorTreeLoader';
// Editor components and services // Editor components and services
import { BehaviorTreeService } from './services/BehaviorTreeService'; import { BehaviorTreeService } from './services/BehaviorTreeService';
@@ -71,6 +74,10 @@ export class BehaviorTreeEditorModule implements IEditorModuleLoader {
// 设置插件上下文 // 设置插件上下文
PluginContext.setServices(services); PluginContext.setServices(services);
// 注册行为树资产加载器到 AssetManager
// Register behavior tree asset loader to AssetManager
this.registerAssetLoader();
// 注册服务 // 注册服务
this.registerServices(services); this.registerServices(services);
@@ -92,6 +99,22 @@ export class BehaviorTreeEditorModule implements IEditorModuleLoader {
logger.info('BehaviorTree editor module installed'); logger.info('BehaviorTree editor module installed');
} }
/**
* 注册行为树资产加载器
* Register behavior tree asset loader
*/
private registerAssetLoader(): void {
try {
const assetManager = PluginAPI.resolve(AssetManagerToken);
if (assetManager) {
assetManager.registerLoader(BehaviorTreeAssetType, new BehaviorTreeLoader());
logger.info('BehaviorTree asset loader registered');
}
} catch (error) {
logger.warn('Failed to register asset loader:', error);
}
}
private registerAssetCreationMappings(services: ServiceContainer): void { private registerAssetCreationMappings(services: ServiceContainer): void {
try { try {
const fileActionRegistry = services.resolve<FileActionRegistry>(IFileActionRegistry); const fileActionRegistry = services.resolve<FileActionRegistry>(IFileActionRegistry);
@@ -376,7 +399,7 @@ export const BehaviorTreePlugin: IEditorPlugin = {
editorModule: new BehaviorTreeEditorModule(), editorModule: new BehaviorTreeEditorModule(),
}; };
export { BehaviorTreeRuntimeModule }; // BehaviorTreeRuntimeModule is internal, not re-exported
// Re-exports for editor functionality // Re-exports for editor functionality
export { PluginContext } from './PluginContext'; export { PluginContext } from './PluginContext';

View File

@@ -0,0 +1,61 @@
/**
* @zh ESEngine 资产加载器
* @en ESEngine asset loader
* @internal
*/
import { Core } from '@esengine/ecs-framework';
import {
BehaviorTreeAssetManager,
EditorToBehaviorTreeDataConverter,
BehaviorTreeAssetType,
type BehaviorTreeData
} from '@esengine/behavior-tree';
/**
* @zh 行为树资产接口
* @en Behavior tree asset interface
* @internal
*/
export interface IBehaviorTreeAsset {
data: BehaviorTreeData;
path: string;
}
/**
* @zh 行为树加载器
* @en Behavior tree loader implementing IAssetLoader interface
* @internal
*/
export class BehaviorTreeLoader {
readonly supportedType = BehaviorTreeAssetType;
readonly supportedExtensions = ['.btree'];
readonly contentType = 'text' as const;
async parse(content: { text?: string }, context: { metadata: { path: string } }): Promise<IBehaviorTreeAsset> {
if (!content.text) {
throw new Error('Behavior tree content is empty');
}
const treeData = EditorToBehaviorTreeDataConverter.fromEditorJSON(content.text);
const assetPath = context.metadata.path;
treeData.id = assetPath;
const btAssetManager = Core.services.tryResolve(BehaviorTreeAssetManager);
if (btAssetManager) {
btAssetManager.loadAsset(treeData);
}
return {
data: treeData,
path: assetPath
};
}
dispose(asset: IBehaviorTreeAsset): void {
const btAssetManager = Core.services.tryResolve(BehaviorTreeAssetManager);
if (btAssetManager && asset.data) {
btAssetManager.unloadAsset(asset.data.id);
}
}
}

View File

@@ -1,23 +1,15 @@
{ {
"extends": "../../../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "composite": false,
"module": "ES2020",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"outDir": "./dist",
"rootDir": "./src",
"jsx": "react-jsx", "jsx": "react-jsx",
"resolveJsonModule": true, "skipLibCheck": true,
"experimentalDecorators": true, "moduleResolution": "bundler"
"emitDecoratorMetadata": true
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist", "**/*.test.ts"]
} }

View File

@@ -2,6 +2,8 @@ import { defineConfig } from 'tsup';
import { editorOnlyPreset } from '../../../tools/build-config/src/presets/plugin-tsup'; import { editorOnlyPreset } from '../../../tools/build-config/src/presets/plugin-tsup';
export default defineConfig({ export default defineConfig({
...editorOnlyPreset(), ...editorOnlyPreset({
external: ['@esengine/asset-system']
}),
tsconfig: 'tsconfig.build.json' tsconfig: 'tsconfig.build.json'
}); });

View File

@@ -0,0 +1,13 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"composite": false,
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src",
"jsx": "react-jsx"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}

View File

@@ -1,6 +1,7 @@
{ {
"extends": "../build-config/tsconfig.json", "extends": "../../../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"composite": true,
"outDir": "./dist", "outDir": "./dist",
"rootDir": "./src", "rootDir": "./src",
"jsx": "react-jsx", "jsx": "react-jsx",

View File

@@ -5,6 +5,7 @@ export default defineConfig({
format: ['esm'], format: ['esm'],
dts: true, dts: true,
clean: true, clean: true,
tsconfig: 'tsconfig.build.json',
external: [ external: [
'react', 'react',
'react-dom', 'react-dom',

View File

@@ -121,9 +121,9 @@ export class WeChatRapier2DLoader implements IWasmLibraryLoader<RapierModule> {
// 导入 Rapier2D 标准版 // 导入 Rapier2D 标准版
const RAPIER = await import('@esengine/rapier2d'); const RAPIER = await import('@esengine/rapier2d');
// 初始化 WASM - 标准版需要提供 WASM 路径 // 初始化 WASM - WASM 已经作为 base64 嵌入到包中
const wasmPath = this._config.minigame?.wasmPath || 'wasm/rapier_wasm2d_bg.wasm'; // Initialize WASM - WASM is embedded as base64 in the package
await RAPIER.init(wasmPath); await RAPIER.init();
return RAPIER; return RAPIER;
} finally { } finally {

View File

@@ -53,10 +53,9 @@ export class WebRapier2DLoader implements IWasmLibraryLoader<RapierModule> {
// 动态导入标准版 // 动态导入标准版
const RAPIER = await import('@esengine/rapier2d'); const RAPIER = await import('@esengine/rapier2d');
// 初始化 WASM - 标准版需要提供 WASM 路径 // 初始化 WASM - WASM 已经作为 base64 嵌入到包中
// 构建时 WASM 文件会被复制到 wasm/ 目录 // Initialize WASM - WASM is embedded as base64 in the package
const wasmPath = this._config.web?.wasmPath || 'wasm/rapier_wasm2d_bg.wasm'; await RAPIER.init();
await RAPIER.init(wasmPath);
console.log(`[${this._config.name}] 加载完成`); console.log(`[${this._config.name}] 加载完成`);
return RAPIER; return RAPIER;

View File

@@ -19,11 +19,13 @@
], ],
"scripts": { "scripts": {
"gen:src": "node scripts/gen-src.mjs", "gen:src": "node scripts/gen-src.mjs",
"build": "pnpm gen:src && tsup", "build": "tsup",
"clean": "rimraf dist src" "build:regen": "pnpm gen:src && tsup",
"clean": "rimraf dist"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"devDependencies": { "devDependencies": {
"base64-js": "^1.5.1",
"rimraf": "^5.0.0", "rimraf": "^5.0.0",
"tsup": "^8.0.0", "tsup": "^8.0.0",
"typescript": "^5.0.0" "typescript": "^5.0.0"

View File

@@ -91,9 +91,8 @@ export class KinematicCharacterController {
*/ */
public setUp(vector: Vector) { public setUp(vector: Vector) {
let rawVect = VectorOps.intoRaw(vector); let rawVect = VectorOps.intoRaw(vector);
const result = this.raw.setUp(rawVect); return this.raw.setUp(rawVect);
rawVect.free(); rawVect.free();
return result;
} }
public applyImpulsesToDynamicBodies(): boolean { public applyImpulsesToDynamicBodies(): boolean {

View File

@@ -28,9 +28,6 @@ export class DynamicRayCastVehicleController {
bodies: RigidBodySet, bodies: RigidBodySet,
colliders: ColliderSet, colliders: ColliderSet,
) { ) {
if (typeof RawDynamicRayCastVehicleController === 'undefined') {
throw new Error('DynamicRayCastVehicleController is not available in 2D mode');
}
this.raw = new RawDynamicRayCastVehicleController(chassis.handle); this.raw = new RawDynamicRayCastVehicleController(chassis.handle);
this.broadPhase = broadPhase; this.broadPhase = broadPhase;
this.narrowPhase = narrowPhase; this.narrowPhase = narrowPhase;

View File

@@ -1,60 +1,12 @@
/** // @ts-ignore
* RAPIER initialization module with dynamic WASM loading support. import wasmBase64 from "../pkg/rapier_wasm2d_bg.wasm";
* RAPIER 初始化模块,支持动态 WASM 加载。
*/
import wasmInit from "../pkg/rapier_wasm2d"; import wasmInit from "../pkg/rapier_wasm2d";
import base64 from "base64-js";
/**
* Input types for WASM initialization.
* WASM 初始化的输入类型。
*/
export type InitInput =
| RequestInfo // URL string or Request object
| URL // URL object
| Response // Fetch Response object
| BufferSource // ArrayBuffer or TypedArray
| WebAssembly.Module; // Pre-compiled module
let initialized = false;
/** /**
* Initializes RAPIER. * Initializes RAPIER.
* Has to be called and awaited before using any library methods. * Has to be called and awaited before using any library methods.
*
* 初始化 RAPIER。
* 必须在使用任何库方法之前调用并等待。
*
* @param input - WASM source (required). Can be URL, Response, ArrayBuffer, etc.
* WASM 源(必需)。可以是 URL、Response、ArrayBuffer 等。
*
* @example
* // Load from URL | 从 URL 加载
* await RAPIER.init('wasm/rapier_wasm2d_bg.wasm');
*
* @example
* // Load from fetch response | 从 fetch 响应加载
* const response = await fetch('wasm/rapier_wasm2d_bg.wasm');
* await RAPIER.init(response);
*
* @example
* // Load from ArrayBuffer | 从 ArrayBuffer 加载
* const buffer = await fetch('wasm/rapier_wasm2d_bg.wasm').then(r => r.arrayBuffer());
* await RAPIER.init(buffer);
*/ */
export async function init(input?: InitInput): Promise<void> { export async function init() {
if (initialized) { await wasmInit(base64.toByteArray(wasmBase64 as unknown as string).buffer);
return;
}
await wasmInit(input);
initialized = true;
}
/**
* Check if RAPIER is already initialized.
* 检查 RAPIER 是否已初始化。
*/
export function isInitialized(): boolean {
return initialized;
} }

View File

@@ -28,7 +28,7 @@ export class VectorOps {
} }
// FIXME: type ram: RawVector? // FIXME: type ram: RawVector?
public static fromRaw(raw: RawVector): Vector | null { public static fromRaw(raw: RawVector): Vector {
if (!raw) return null; if (!raw) return null;
let res = VectorOps.new(raw.x, raw.y); let res = VectorOps.new(raw.x, raw.y);
@@ -56,7 +56,7 @@ export class RotationOps {
return 0.0; return 0.0;
} }
public static fromRaw(raw: RawRotation): Rotation | null { public static fromRaw(raw: RawRotation): Rotation {
if (!raw) return null; if (!raw) return null;
let res = raw.angle; let res = raw.angle;

View File

@@ -7,4 +7,7 @@ export default defineConfig({
sourcemap: true, sourcemap: true,
clean: true, clean: true,
external: ["../pkg/rapier_wasm2d.js"], external: ["../pkg/rapier_wasm2d.js"],
loader: {
".wasm": "base64",
},
}); });

6
pnpm-lock.yaml generated
View File

@@ -569,6 +569,9 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:../../../framework/behavior-tree version: link:../../../framework/behavior-tree
devDependencies: devDependencies:
'@esengine/asset-system':
specifier: workspace:*
version: link:../../../engine/asset-system
'@esengine/build-config': '@esengine/build-config':
specifier: workspace:* specifier: workspace:*
version: link:../../../tools/build-config version: link:../../../tools/build-config
@@ -1861,6 +1864,9 @@ importers:
packages/physics/rapier2d: packages/physics/rapier2d:
devDependencies: devDependencies:
base64-js:
specifier: ^1.5.1
version: 1.5.1
rimraf: rimraf:
specifier: ^5.0.0 specifier: ^5.0.0
version: 5.0.10 version: 5.0.10