refactor: 代码规范化与依赖清理 (#317)
* refactor(deps): 统一编辑器包依赖配置 & 优化分层架构 - 将 ecs-engine-bindgen 提升为 Layer 1 核心包 - 统一 9 个编辑器包的依赖声明模式 - 清理废弃的包目录 (ui, ui-editor, network-*) * refactor(tokens): 修复 PrefabService 令牌冲突 & 补充 module.json - 将 editor-core 的 PrefabServiceToken 改名为 EditorPrefabServiceToken 避免与 asset-system 的 PrefabServiceToken 冲突 (Symbol.for 冲突) - 为 mesh-3d 添加 module.json - 为 world-streaming 添加 module.json * refactor(editor-core): 整理导出结构 & 添加 blueprint tokens.ts - 按功能分组整理 editor-core 的 65 行导出 - 添加清晰的分组注释 (中英双语) - 为 blueprint 添加占位符 tokens.ts * chore(editor): 为 14 个编辑器插件包添加 module.json 统一编辑器包的模块配置,包含: - isEditorPlugin 标识 - runtimeModule 关联 - exports 导出清单 (inspectors, panels, gizmos) * refactor(core): 改进类型安全 - 减少 as any 使用 - 添加 GlobalTypes.ts 定义小游戏平台和 Chrome API 类型 - SoAStorage 使用 IComponentTypeMetadata 替代 as any - PlatformDetector 使用类型安全的平台检测 - 添加 ISoAStorageStats/ISoAFieldStats 接口 * feat(editor): 添加 EditorServicesContext 解决 prop drilling - 新增 contexts/EditorServicesContext.tsx 提供统一服务访问 - App.tsx 包裹 EditorServicesProvider - 提供 useEditorServices/useMessageHub 等便捷 hooks - SceneHierarchy 添加迁移注释,后续可移除 props * docs(editor): 澄清 inspector 目录架构关系 - inspector/ 标记为内部实现,添加 @deprecated 警告 - inspectors/ 标记为公共 API 入口点 - 添加架构说明文档 * refactor(editor): 添加全局类型声明消除 window as any - 创建 editor-app/src/global.d.ts 声明 Window 接口扩展 - 创建 editor-core/src/global.d.ts 声明 Window 接口扩展 - 更新 App.tsx 使用类型安全的 window 属性访问 - 更新 PluginLoader.ts 使用 window.__ESENGINE_PLUGINS__ - 更新 PluginSDKRegistry.ts 使用 window.__ESENGINE_SDK__ - 更新 UserCodeService.ts 使用类型安全的全局变量访问 * refactor(editor): 提取项目和场景操作到独立 hooks - 创建 useProjectActions hook 封装项目操作 - 创建 useSceneActions hook 封装场景操作 - 为渐进式重构 App.tsx 做准备 * refactor(editor): 清理冗余代码和未使用文件 删除的目录和文件: - application/state/ - 重复的状态管理(与 stores/ 重复) - 8 个孤立 CSS 文件(对应组件不存在) - AssetBrowser.tsx - 仅为 ContentBrowser 的向后兼容包装 - AssetPicker.tsx - 未被使用 - AssetPickerDialog.tsx (顶级) - 已被 dialogs/ 版本取代 - EntityInspector.tsx (顶级) - 已被 inspectors/views/ 版本取代 修复: - 移除 App.tsx 中未使用的导入 - 更新 application/index.ts 移除已删除模块 - 修复 useProjectActions.ts 的 MutableRefObject 类型 * refactor(editor): 统一 inspectors 模块导出结构 - 在 inspectors/index.ts 重新导出 PropertyInspector - 创建 inspectors/fields/index.ts barrel export - 导出 views、fields、common 子模块 - 更新 EntityInspector 使用统一入口导入 * refactor(editor): 删除废弃的 Profiler 组件 删除未使用的组件(共 1059 行): - ProfilerPanel.tsx (229 行) - ProfilerWindow.tsx (589 行) - ProfilerDockPanel.tsx (241 行) - ProfilerPanel.css - ProfilerDockPanel.css 保留:AdvancedProfiler + AdvancedProfilerWindow(正在使用) * refactor(runtime-core): 统一依赖处理与插件状态管理 - 新增 DependencyUtils 统一拓扑排序和依赖验证 - 新增 PluginState 定义插件生命周期状态机 - 合并 UnifiedPluginLoader 到 PluginLoader - 清理 index.ts 移除不必要的 Token re-exports - 新增 RuntimeMode/UserCodeRealm/ImportMapGenerator * refactor(editor-core): 使用统一的 ImportMapGenerator - WebBuildPipeline 使用 runtime-core 的 generateImportMap - UserCodeService 添加 ImportMap 相关接口 * feat(compiler): 增强 esbuild 查找策略 - 支持本地 node_modules、pnpm exec、npx、全局多种来源 - EngineService 使用 RuntimeMode * refactor(runtime-core): 简化 GameRuntime 代码 - 合并 _disableGameLogicSystems/_enableGameLogicSystems 为 _setGameLogicSystemsEnabled - 精简本地 Token 定义的注释 * refactor(editor-core): 引入 BaseRegistry 基类消除代码重复 - 新增 BaseRegistry 和 PrioritizedRegistry 基类 - 重构 CompilerRegistry, InspectorRegistry, FieldEditorRegistry - 统一注册表的日志记录和错误处理 * refactor(editor-core): 扩展 BaseRegistry 重构 - ComponentInspectorRegistry 继承 PrioritizedRegistry - EditorComponentRegistry 继承 BaseRegistry - EntityCreationRegistry 继承 BaseRegistry - PropertyRendererRegistry 继承 PrioritizedRegistry - 导出 BaseRegistry 基类供外部使用 - 统一双语注释格式 * refactor(editor-core): 代码优雅性优化 CommandManager: - 提取 tryMergeWithLast() 和 pushToUndoStack() 消除重复代码 - 统一双语注释格式 FileActionRegistry: - 提取 normalizeExtension() 消除扩展名规范化重复 - 统一私有属性命名风格(_前缀) - 使用 createRegistryToken 统一 Token 创建 BaseRegistry: - 添加 IOrdered 接口 - 添加 sortByOrder() 排序辅助方法 EntityCreationRegistry: - 使用 sortByOrder() 简化排序逻辑 * refactor(editor-core): 统一日志系统 & 代码规范优化 - GizmoRegistry: 使用 createLogger 替代 console.warn - VirtualNodeRegistry: 使用 createLogger 替代 console.warn - WindowRegistry: 使用 logger、添加 _ 前缀、导出 IWindowRegistry token - EditorViewportService: 使用 createLogger 替代 console.warn - ComponentActionRegistry: 使用 logger、添加 _ 前缀、返回值改进 - SettingsRegistry: 使用 logger、提取 ensureCategory/ensureSection 方法 - 添加 WindowRegistry 到主导出 * refactor(editor-core): ModuleRegistry 使用 logger 替代 console * refactor(editor-core): SerializerRegistry/UIRegistry 添加 token 和 _ 前缀 * refactor(editor-core): UIRegistry 代码优雅性 & Token 命名统一 - UIRegistry: 提取 _sortByOrder 消除 6 处重复排序逻辑 - UIRegistry: 添加分节注释和双语文档 - FieldEditorRegistry: Token 重命名为 FieldEditorRegistryToken - PropertyRendererRegistry: Token 重命名为 PropertyRendererRegistryToken * refactor(core): 统一日志系统 - console 替换为 logger - ComponentSerializer: 使用 logger 替代 console.warn - ComponentRegistry: console.warn → logger.warn (已有 logger) - SceneSerializer: 添加 logger,替换 console.warn/error - SystemScheduler: 添加 logger,替换 console.warn - VersionMigration: 添加 logger,替换所有 console.warn - RuntimeModeService: console.error → logger.error - Core.ts: _logger 改为 readonly,双语错误消息 - SceneSerializer 修复:使用 getComponentTypeName 替代 constructor.name * fix(core): 修复 constructor.name 压缩后失效问题 - Scene.ts: 使用 system.systemName 替代 system.constructor.name - CommandBuffer.ts: 使用 getComponentTypeName() 替代 constructor.name * refactor(editor-core): 代码规范优化 - 私有方法命名 & 日志统一 - BuildService: console → logger - FileActionRegistry: 添加 logger, 私有方法 _ 前缀 - SettingsRegistry: 私有方法 _ 前缀 (ensureCategory → _ensureCategory) * refactor(core): Scene.ts 私有变量命名规范化 - logger → _logger (遵循私有变量 _ 前缀规范) * refactor(editor-core): 服务类私有成员命名规范化 - CommandManager: 私有变量/方法添加 _ 前缀 - undoStack/redoStack/config/isExecuting - tryMergeWithLast/pushToUndoStack - LocaleService: 私有变量/方法添加 _ 前缀 - currentLocale/translations/changeListeners - deepMerge/getNestedValue/loadSavedLocale/saveLocale * refactor(core): 私有成员命名规范化 & 单例模式优化 - Component.ts: _idGenerator 私有静态变量规范化 - PlatformManager.ts: _instance, _adapter, _logger 规范化 - AutoProfiler.ts: _instance, _config 及所有私有方法规范化 - ProfilerSDK.ts: _instance, _config 及所有私有方法规范化 - ComponentPoolManager: _instance, _pools, _usageTracker 规范化 - GlobalEventBus: _instance 规范化 - 添加中英双语 JSDoc 注释 * refactor(editor-app,behavior-tree-editor): 私有成员 & 单例模式命名规范化 editor-app: - EngineService: private static instance → _instance - EditorEngineSync: 所有私有成员添加 _ 前缀 - RuntimeResolver: 所有私有成员和方法添加 _ 前缀 - SettingsService: 所有私有成员和方法添加 _ 前缀 behavior-tree-editor: - GlobalBlackboardService: 所有私有成员和方法添加 _ 前缀 - NotificationService: private static instance → _instance - NodeRegistryService: 所有私有成员和方法添加 _ 前缀 - TreeStateAdapter: private static instance → _instance * fix(editor-runtime): 添加 editor-core 到 external 避免传递依赖问题 将 @esengine/editor-core 添加到 vite external 配置, 避免 editor-core → runtime-core → ecs-engine-bindgen 的传递依赖 被错误地打包进 editor-runtime.js,导致 CI 构建失败。 * fix(core): 修复空接口 lint 错误 将 IByteDanceMiniGameAPI、IAlipayMiniGameAPI、IBaiduMiniGameAPI 从空接口改为类型别名,修复 no-empty-object-type 规则报错
This commit is contained in:
@@ -171,33 +171,148 @@ pub async fn check_environment() -> Result<EnvironmentCheckResult, String> {
|
||||
/// Check esbuild availability and get its status.
|
||||
/// 检查 esbuild 可用性并获取其状态。
|
||||
///
|
||||
/// Only checks for globally installed esbuild (via npm -g).
|
||||
/// 只检测通过 npm 全局安装的 esbuild。
|
||||
/// Checks multiple sources: bundled, local node_modules, pnpm, npx, global.
|
||||
/// 检查多个来源:内置、本地 node_modules、pnpm、npx、全局。
|
||||
fn check_esbuild_status() -> ToolStatus {
|
||||
let global_esbuild = if cfg!(windows) { "esbuild.cmd" } else { "esbuild" };
|
||||
|
||||
match get_esbuild_version(global_esbuild) {
|
||||
Ok(version) => {
|
||||
// Try to find esbuild from any available source
|
||||
// 尝试从任何可用来源查找 esbuild
|
||||
match find_esbuild_with_source(None) {
|
||||
Ok(info) => {
|
||||
let display_path = if info.prefix_args.is_empty() {
|
||||
info.cmd.clone()
|
||||
} else {
|
||||
format!("{} {}", info.cmd, info.prefix_args.join(" "))
|
||||
};
|
||||
ToolStatus {
|
||||
available: true,
|
||||
version: Some(version),
|
||||
path: Some(global_esbuild.to_string()),
|
||||
source: Some("global".to_string()),
|
||||
version: Some(info.version),
|
||||
path: Some(display_path),
|
||||
source: Some(info.source),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
Err(e) => {
|
||||
ToolStatus {
|
||||
available: false,
|
||||
version: None,
|
||||
path: None,
|
||||
source: None,
|
||||
error: Some("esbuild not installed globally. Please install: npm install -g esbuild | 未全局安装 esbuild,请安装: npm install -g esbuild".to_string()),
|
||||
error: Some(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Esbuild execution info.
|
||||
/// Esbuild 执行信息。
|
||||
#[derive(Debug, Clone)]
|
||||
struct EsbuildInfo {
|
||||
/// Command to run (e.g., "esbuild.cmd", "pnpm.cmd")
|
||||
/// 要运行的命令
|
||||
cmd: String,
|
||||
/// Prefix args before esbuild args (e.g., ["exec", "esbuild"] for pnpm)
|
||||
/// esbuild 参数前的前缀参数
|
||||
prefix_args: Vec<String>,
|
||||
/// Source of esbuild: "local", "pnpm", "npx", "global"
|
||||
/// esbuild 来源
|
||||
source: String,
|
||||
/// Version string
|
||||
/// 版本字符串
|
||||
version: String,
|
||||
}
|
||||
|
||||
/// Find esbuild with source information.
|
||||
/// 查找 esbuild 并返回来源信息。
|
||||
///
|
||||
/// Returns EsbuildInfo on success.
|
||||
/// 成功时返回 EsbuildInfo。
|
||||
fn find_esbuild_with_source(project_root: Option<&str>) -> Result<EsbuildInfo, String> {
|
||||
// 1. Check local node_modules/.bin/esbuild (project-specific)
|
||||
// 1. 检查本地 node_modules/.bin/esbuild(项目特定)
|
||||
if let Some(root) = project_root {
|
||||
let local_esbuild = if cfg!(windows) {
|
||||
Path::new(root).join("node_modules").join(".bin").join("esbuild.cmd")
|
||||
} else {
|
||||
Path::new(root).join("node_modules").join(".bin").join("esbuild")
|
||||
};
|
||||
|
||||
if local_esbuild.exists() {
|
||||
let path_str = local_esbuild.to_string_lossy().to_string();
|
||||
if let Ok(version) = get_esbuild_version(&path_str) {
|
||||
println!("[Compiler] Found local esbuild: {}", path_str);
|
||||
return Ok(EsbuildInfo {
|
||||
cmd: path_str,
|
||||
prefix_args: vec![],
|
||||
source: "local".to_string(),
|
||||
version,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Try pnpm exec esbuild (works with pnpm workspaces)
|
||||
// 2. 尝试 pnpm exec esbuild(支持 pnpm 工作区)
|
||||
if let Ok(version) = try_package_manager_esbuild("pnpm", &["exec", "esbuild", "--version"], project_root) {
|
||||
let cmd = if cfg!(windows) { "pnpm.cmd" } else { "pnpm" };
|
||||
println!("[Compiler] Found esbuild via pnpm");
|
||||
return Ok(EsbuildInfo {
|
||||
cmd: cmd.to_string(),
|
||||
prefix_args: vec!["exec".to_string(), "esbuild".to_string()],
|
||||
source: "pnpm".to_string(),
|
||||
version,
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Try npx esbuild (works with npm projects)
|
||||
// 3. 尝试 npx esbuild(支持 npm 项目)
|
||||
if let Ok(version) = try_package_manager_esbuild("npx", &["esbuild", "--version"], project_root) {
|
||||
let cmd = if cfg!(windows) { "npx.cmd" } else { "npx" };
|
||||
println!("[Compiler] Found esbuild via npx");
|
||||
return Ok(EsbuildInfo {
|
||||
cmd: cmd.to_string(),
|
||||
prefix_args: vec!["esbuild".to_string()],
|
||||
source: "npx".to_string(),
|
||||
version,
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Fall back to global esbuild
|
||||
// 4. 回退到全局 esbuild
|
||||
let global_esbuild = if cfg!(windows) { "esbuild.cmd" } else { "esbuild" };
|
||||
if let Ok(version) = get_esbuild_version(global_esbuild) {
|
||||
println!("[Compiler] Found global esbuild");
|
||||
return Ok(EsbuildInfo {
|
||||
cmd: global_esbuild.to_string(),
|
||||
prefix_args: vec![],
|
||||
source: "global".to_string(),
|
||||
version,
|
||||
});
|
||||
}
|
||||
|
||||
Err("esbuild not found. Install locally (pnpm add -D esbuild) or globally (npm i -g esbuild) | 未找到 esbuild,请本地安装 (pnpm add -D esbuild) 或全局安装 (npm i -g esbuild)".to_string())
|
||||
}
|
||||
|
||||
/// Try to get esbuild version via package manager (pnpm/npx).
|
||||
/// 尝试通过包管理器获取 esbuild 版本。
|
||||
fn try_package_manager_esbuild(cmd: &str, args: &[&str], project_root: Option<&str>) -> Result<String, String> {
|
||||
let cmd_name = if cfg!(windows) { format!("{}.cmd", cmd) } else { cmd.to_string() };
|
||||
|
||||
let mut command = Command::new(&cmd_name);
|
||||
command.args(args);
|
||||
|
||||
if let Some(root) = project_root {
|
||||
command.current_dir(root);
|
||||
}
|
||||
|
||||
match command.output() {
|
||||
Ok(output) if output.status.success() => {
|
||||
let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
Ok(version)
|
||||
}
|
||||
_ => Err(format!("{} esbuild not available", cmd))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get esbuild version.
|
||||
/// 获取 esbuild 版本。
|
||||
fn get_esbuild_version(esbuild_path: &str) -> Result<String, String> {
|
||||
@@ -224,11 +339,11 @@ fn get_esbuild_version(esbuild_path: &str) -> Result<String, String> {
|
||||
/// Compilation result | 编译结果
|
||||
#[command]
|
||||
pub async fn compile_typescript(options: CompileOptions) -> Result<CompileResult, String> {
|
||||
// Check if esbuild is available | 检查 esbuild 是否可用
|
||||
let esbuild_path = find_esbuild(&options.project_root)?;
|
||||
// Find esbuild with execution info | 查找 esbuild 并获取执行信息
|
||||
let esbuild_info = find_esbuild_with_source(Some(&options.project_root))?;
|
||||
|
||||
// Build esbuild arguments | 构建 esbuild 参数
|
||||
let mut args = vec![
|
||||
let mut esbuild_args = vec![
|
||||
options.entry_path.clone(),
|
||||
"--bundle".to_string(),
|
||||
format!("--outfile={}", options.output_path),
|
||||
@@ -239,38 +354,42 @@ pub async fn compile_typescript(options: CompileOptions) -> Result<CompileResult
|
||||
|
||||
// Add source map option | 添加 source map 选项
|
||||
if options.source_map {
|
||||
args.push("--sourcemap".to_string());
|
||||
esbuild_args.push("--sourcemap".to_string());
|
||||
}
|
||||
|
||||
// Add minify option | 添加压缩选项
|
||||
if options.minify {
|
||||
args.push("--minify".to_string());
|
||||
esbuild_args.push("--minify".to_string());
|
||||
}
|
||||
|
||||
// Add global name for IIFE format | 添加 IIFE 格式的全局名称
|
||||
if let Some(ref global_name) = options.global_name {
|
||||
args.push(format!("--global-name={}", global_name));
|
||||
esbuild_args.push(format!("--global-name={}", global_name));
|
||||
}
|
||||
|
||||
// Add external dependencies | 添加外部依赖
|
||||
for external in &options.external {
|
||||
args.push(format!("--external:{}", external));
|
||||
esbuild_args.push(format!("--external:{}", external));
|
||||
}
|
||||
|
||||
// Add module aliases | 添加模块别名
|
||||
if let Some(ref aliases) = options.alias {
|
||||
for (from, to) in aliases {
|
||||
args.push(format!("--alias:{}={}", from, to));
|
||||
esbuild_args.push(format!("--alias:{}={}", from, to));
|
||||
}
|
||||
}
|
||||
|
||||
// Combine prefix args with esbuild args | 合并前缀参数和 esbuild 参数
|
||||
let mut all_args: Vec<String> = esbuild_info.prefix_args.clone();
|
||||
all_args.extend(esbuild_args);
|
||||
|
||||
// Build full command string for error reporting | 构建完整命令字符串用于错误报告
|
||||
let cmd_str = format!("{} {}", esbuild_path, args.join(" "));
|
||||
println!("[Compiler] Running: {}", cmd_str);
|
||||
let cmd_str = format!("{} {}", esbuild_info.cmd, all_args.join(" "));
|
||||
println!("[Compiler] Running: {} (source: {})", cmd_str, esbuild_info.source);
|
||||
|
||||
// Run esbuild | 运行 esbuild
|
||||
let output = Command::new(&esbuild_path)
|
||||
.args(&args)
|
||||
let output = Command::new(&esbuild_info.cmd)
|
||||
.args(&all_args)
|
||||
.current_dir(&options.project_root)
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to run esbuild | 运行 esbuild 失败: {}", e))?;
|
||||
@@ -324,6 +443,229 @@ pub async fn compile_typescript(options: CompileOptions) -> Result<CompileResult
|
||||
}
|
||||
}
|
||||
|
||||
/// Type check options.
|
||||
/// 类型检查选项。
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TypeCheckOptions {
|
||||
/// Project root directory | 项目根目录
|
||||
pub project_root: String,
|
||||
/// TypeScript config path (optional) | TypeScript 配置路径(可选)
|
||||
pub tsconfig_path: Option<String>,
|
||||
/// Files to check (optional, if not set checks whole project)
|
||||
/// 要检查的文件(可选,如果未设置则检查整个项目)
|
||||
pub files: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Type check result.
|
||||
/// 类型检查结果。
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TypeCheckResult {
|
||||
/// Whether type check passed | 类型检查是否通过
|
||||
pub success: bool,
|
||||
/// Type errors | 类型错误
|
||||
pub errors: Vec<CompileError>,
|
||||
/// Duration in milliseconds | 耗时(毫秒)
|
||||
pub duration_ms: u64,
|
||||
}
|
||||
|
||||
/// Check TypeScript types using tsc.
|
||||
/// 使用 tsc 检查 TypeScript 类型。
|
||||
///
|
||||
/// Runs tsc --noEmit to check types without generating output files.
|
||||
/// 运行 tsc --noEmit 检查类型但不生成输出文件。
|
||||
#[command]
|
||||
pub async fn check_types(options: TypeCheckOptions) -> Result<TypeCheckResult, String> {
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
// Find tsc executable | 查找 tsc 可执行文件
|
||||
let tsc_info = find_tsc_with_source(Some(&options.project_root))?;
|
||||
|
||||
// Build tsc arguments | 构建 tsc 参数
|
||||
let mut tsc_args: Vec<String> = tsc_info.prefix_args.clone();
|
||||
tsc_args.push("--noEmit".to_string());
|
||||
tsc_args.push("--pretty".to_string());
|
||||
tsc_args.push("false".to_string());
|
||||
|
||||
// Use project tsconfig if specified | 使用项目 tsconfig(如果指定)
|
||||
if let Some(ref tsconfig) = options.tsconfig_path {
|
||||
tsc_args.push("--project".to_string());
|
||||
tsc_args.push(tsconfig.clone());
|
||||
}
|
||||
|
||||
// Add specific files if provided | 添加特定文件(如果提供)
|
||||
if let Some(ref files) = options.files {
|
||||
for file in files {
|
||||
tsc_args.push(file.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let cmd_str = format!("{} {}", tsc_info.cmd, tsc_args.join(" "));
|
||||
println!("[TypeCheck] Running: {} (source: {})", cmd_str, tsc_info.source);
|
||||
|
||||
// Run tsc | 运行 tsc
|
||||
let output = Command::new(&tsc_info.cmd)
|
||||
.args(&tsc_args)
|
||||
.current_dir(&options.project_root)
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to run tsc | 运行 tsc 失败: {}", e))?;
|
||||
|
||||
let duration_ms = start.elapsed().as_millis() as u64;
|
||||
|
||||
if output.status.success() {
|
||||
println!("[TypeCheck] Type check passed in {}ms", duration_ms);
|
||||
Ok(TypeCheckResult {
|
||||
success: true,
|
||||
errors: vec![],
|
||||
duration_ms,
|
||||
})
|
||||
} else {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
println!("[TypeCheck] Type check failed in {}ms", duration_ms);
|
||||
|
||||
// Parse tsc output | 解析 tsc 输出
|
||||
let errors = parse_tsc_errors(&stdout, &stderr);
|
||||
|
||||
Ok(TypeCheckResult {
|
||||
success: false,
|
||||
errors,
|
||||
duration_ms,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// TSC execution info (similar to EsbuildInfo).
|
||||
/// TSC 执行信息。
|
||||
#[derive(Debug, Clone)]
|
||||
struct TscInfo {
|
||||
cmd: String,
|
||||
prefix_args: Vec<String>,
|
||||
source: String,
|
||||
}
|
||||
|
||||
/// Find tsc executable.
|
||||
/// 查找 tsc 可执行文件。
|
||||
fn find_tsc_with_source(project_root: Option<&str>) -> Result<TscInfo, String> {
|
||||
// 1. Check local node_modules/.bin/tsc
|
||||
if let Some(root) = project_root {
|
||||
let local_tsc = if cfg!(windows) {
|
||||
Path::new(root).join("node_modules").join(".bin").join("tsc.cmd")
|
||||
} else {
|
||||
Path::new(root).join("node_modules").join(".bin").join("tsc")
|
||||
};
|
||||
|
||||
if local_tsc.exists() {
|
||||
let path_str = local_tsc.to_string_lossy().to_string();
|
||||
println!("[TypeCheck] Found local tsc: {}", path_str);
|
||||
return Ok(TscInfo {
|
||||
cmd: path_str,
|
||||
prefix_args: vec![],
|
||||
source: "local".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Try pnpm exec tsc
|
||||
if try_tsc_via_package_manager("pnpm", &["exec", "tsc", "--version"], project_root).is_ok() {
|
||||
let cmd = if cfg!(windows) { "pnpm.cmd" } else { "pnpm" };
|
||||
println!("[TypeCheck] Found tsc via pnpm");
|
||||
return Ok(TscInfo {
|
||||
cmd: cmd.to_string(),
|
||||
prefix_args: vec!["exec".to_string(), "tsc".to_string()],
|
||||
source: "pnpm".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Try npx tsc
|
||||
if try_tsc_via_package_manager("npx", &["tsc", "--version"], project_root).is_ok() {
|
||||
let cmd = if cfg!(windows) { "npx.cmd" } else { "npx" };
|
||||
println!("[TypeCheck] Found tsc via npx");
|
||||
return Ok(TscInfo {
|
||||
cmd: cmd.to_string(),
|
||||
prefix_args: vec!["tsc".to_string()],
|
||||
source: "npx".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Try global tsc
|
||||
let global_tsc = if cfg!(windows) { "tsc.cmd" } else { "tsc" };
|
||||
if Command::new(global_tsc).arg("--version").output().map(|o| o.status.success()).unwrap_or(false) {
|
||||
println!("[TypeCheck] Found global tsc");
|
||||
return Ok(TscInfo {
|
||||
cmd: global_tsc.to_string(),
|
||||
prefix_args: vec![],
|
||||
source: "global".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Err("TypeScript not found. Install locally (pnpm add -D typescript) or globally (npm i -g typescript) | 未找到 TypeScript,请本地安装 (pnpm add -D typescript) 或全局安装 (npm i -g typescript)".to_string())
|
||||
}
|
||||
|
||||
/// Try to run tsc via package manager.
|
||||
/// 尝试通过包管理器运行 tsc。
|
||||
fn try_tsc_via_package_manager(cmd: &str, args: &[&str], project_root: Option<&str>) -> Result<(), String> {
|
||||
let cmd_name = if cfg!(windows) { format!("{}.cmd", cmd) } else { cmd.to_string() };
|
||||
|
||||
let mut command = Command::new(&cmd_name);
|
||||
command.args(args);
|
||||
|
||||
if let Some(root) = project_root {
|
||||
command.current_dir(root);
|
||||
}
|
||||
|
||||
match command.output() {
|
||||
Ok(output) if output.status.success() => Ok(()),
|
||||
_ => Err(format!("{} tsc not available", cmd))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse tsc error output.
|
||||
/// 解析 tsc 错误输出。
|
||||
fn parse_tsc_errors(stdout: &str, stderr: &str) -> Vec<CompileError> {
|
||||
let mut errors = Vec::new();
|
||||
let combined = format!("{}\n{}", stdout, stderr);
|
||||
|
||||
// tsc output format: file(line,col): error TSxxxx: message
|
||||
// tsc 输出格式: 文件(行,列): error TSxxxx: 消息
|
||||
let re = regex::Regex::new(r"(.+?)\((\d+),(\d+)\):\s*error\s+TS\d+:\s*(.+)").ok();
|
||||
|
||||
for line in combined.lines() {
|
||||
if let Some(ref regex) = re {
|
||||
if let Some(caps) = regex.captures(line) {
|
||||
errors.push(CompileError {
|
||||
file: Some(caps.get(1).map_or("", |m| m.as_str()).to_string()),
|
||||
line: caps.get(2).and_then(|m| m.as_str().parse().ok()),
|
||||
column: caps.get(3).and_then(|m| m.as_str().parse().ok()),
|
||||
message: caps.get(4).map_or("", |m| m.as_str()).to_string(),
|
||||
});
|
||||
}
|
||||
} else if line.contains("error TS") {
|
||||
// Fallback: just capture the error line
|
||||
errors.push(CompileError {
|
||||
message: line.to_string(),
|
||||
file: None,
|
||||
line: None,
|
||||
column: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If no errors parsed but output is not empty, add raw output
|
||||
if errors.is_empty() && !combined.trim().is_empty() {
|
||||
errors.push(CompileError {
|
||||
message: combined.trim().to_string(),
|
||||
file: None,
|
||||
line: None,
|
||||
column: None,
|
||||
});
|
||||
}
|
||||
|
||||
errors
|
||||
}
|
||||
|
||||
/// Watch for file changes in scripts directory.
|
||||
/// 监视脚本目录中的文件变更。
|
||||
///
|
||||
@@ -695,26 +1037,13 @@ pub async fn stop_watch_scripts(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Find esbuild executable path.
|
||||
/// 查找 esbuild 可执行文件路径。
|
||||
/// Find esbuild executable path (deprecated, use find_esbuild_with_source).
|
||||
/// 查找 esbuild 可执行文件路径(已弃用,使用 find_esbuild_with_source)。
|
||||
///
|
||||
/// Only uses globally installed esbuild (npm -g).
|
||||
/// 只使用全局安装的 esbuild (npm -g)。
|
||||
fn find_esbuild(_project_root: &str) -> Result<String, String> {
|
||||
let global_esbuild = if cfg!(windows) { "esbuild.cmd" } else { "esbuild" };
|
||||
|
||||
// Check if global esbuild exists | 检查全局 esbuild 是否存在
|
||||
let check = Command::new(global_esbuild)
|
||||
.arg("--version")
|
||||
.output();
|
||||
|
||||
match check {
|
||||
Ok(output) if output.status.success() => {
|
||||
println!("[Compiler] Using global esbuild");
|
||||
Ok(global_esbuild.to_string())
|
||||
},
|
||||
_ => Err("esbuild not installed globally. Please install: npm install -g esbuild | 未全局安装 esbuild,请安装: npm install -g esbuild".to_string())
|
||||
}
|
||||
/// Kept for backward compatibility - returns just the command string.
|
||||
/// 保留用于向后兼容 - 仅返回命令字符串。
|
||||
fn find_esbuild(project_root: &str) -> Result<EsbuildInfo, String> {
|
||||
find_esbuild_with_source(Some(project_root))
|
||||
}
|
||||
|
||||
/// Parse esbuild error output.
|
||||
|
||||
@@ -94,6 +94,7 @@ fn main() {
|
||||
commands::generate_qrcode,
|
||||
// User code compilation | 用户代码编译
|
||||
commands::compile_typescript,
|
||||
commands::check_types,
|
||||
commands::watch_scripts,
|
||||
commands::watch_assets,
|
||||
commands::stop_watch_scripts,
|
||||
|
||||
Reference in New Issue
Block a user