actr_cli/commands/
generate.rs

1//! # 代码生成命令
2//!
3//! 从 proto 文件生成 Rust Actor 代码,包括:
4//! 1. protobuf 消息类型
5//! 2. Actor 基础设施代码
6//! 3. 用户业务逻辑框架(带 TODO 注释)
7
8use crate::commands::Command;
9use crate::error::{ActrCliError, Result};
10// 只导入必要的类型,避免拉入不需要的依赖如 sqlite
11// use actr_framework::prelude::*;
12use async_trait::async_trait;
13use clap::Parser;
14use std::path::{Path, PathBuf};
15use std::process::Command as StdCommand;
16use tracing::{debug, info, warn};
17
18#[derive(Parser, Debug, Clone)]
19pub struct GenCommand {
20    /// 输入的 proto 文件或目录
21    #[arg(short, long, default_value = "proto")]
22    pub input: PathBuf,
23
24    /// 输出目录
25    #[arg(short, long, default_value = "src/generated")]
26    pub output: PathBuf,
27
28    /// 是否生成用户代码框架
29    #[arg(long, default_value = "true")]
30    pub generate_scaffold: bool,
31
32    /// 是否覆盖已存在的用户代码文件
33    #[arg(long)]
34    pub overwrite_user_code: bool,
35
36    /// 是否运行 rustfmt 格式化生成的代码
37    #[arg(long, default_value = "true")]
38    pub format: bool,
39
40    /// 调试模式:保留中间生成文件
41    #[arg(long)]
42    pub debug: bool,
43}
44
45#[async_trait]
46impl Command for GenCommand {
47    async fn execute(&self) -> Result<()> {
48        info!("🚀 开始代码生成...");
49
50        // 1. 验证输入
51        self.validate_inputs()?;
52
53        // 2. 准备输出目录
54        self.prepare_output_dirs()?;
55
56        // 3. 发现 proto 文件
57        let proto_files = self.discover_proto_files()?;
58        info!("📁 发现 {} 个 proto 文件", proto_files.len());
59
60        // 4. 生成基础设施代码
61        self.generate_infrastructure_code(&proto_files).await?;
62
63        // 5. 生成用户代码框架
64        if self.generate_scaffold {
65            self.generate_user_code_scaffold(&proto_files).await?;
66        }
67
68        // 6. 格式化代码
69        if self.format {
70            self.format_generated_code().await?;
71        }
72
73        // 7. 验证生成的代码
74        self.validate_generated_code().await?;
75
76        info!("✅ 代码生成完成!");
77        self.print_next_steps();
78
79        Ok(())
80    }
81}
82
83impl GenCommand {
84    /// 读取 Actr.toml 中的 manufacturer
85    fn read_manufacturer(&self) -> Result<String> {
86        use std::fs;
87
88        // Look for Actr.toml in current directory
89        let config_path = PathBuf::from("Actr.toml");
90        if !config_path.exists() {
91            warn!("Actr.toml not found, using default manufacturer 'acme'");
92            return Ok("acme".to_string());
93        }
94
95        // Read and parse TOML directly
96        let content = fs::read_to_string(&config_path)
97            .map_err(|e| ActrCliError::config_error(format!("Failed to read Actr.toml: {e}")))?;
98
99        let raw_config: actr::config::RawConfig = toml::from_str(&content)
100            .map_err(|e| ActrCliError::config_error(format!("Failed to parse Actr.toml: {e}")))?;
101
102        Ok(raw_config.package.manufacturer)
103    }
104
105    /// 验证输入参数
106    fn validate_inputs(&self) -> Result<()> {
107        if !self.input.exists() {
108            return Err(ActrCliError::config_error(format!(
109                "输入路径不存在: {:?}",
110                self.input
111            )));
112        }
113
114        if self.input.is_file() && self.input.extension().unwrap_or_default() != "proto" {
115            warn!("输入文件不是 .proto 文件: {:?}", self.input);
116        }
117
118        Ok(())
119    }
120
121    /// 准备输出目录
122    fn prepare_output_dirs(&self) -> Result<()> {
123        std::fs::create_dir_all(&self.output)
124            .map_err(|e| ActrCliError::config_error(format!("创建输出目录失败: {e}")))?;
125
126        if self.generate_scaffold {
127            let user_code_dir = self.output.join("../");
128            std::fs::create_dir_all(&user_code_dir)
129                .map_err(|e| ActrCliError::config_error(format!("创建用户代码目录失败: {e}")))?;
130        }
131
132        Ok(())
133    }
134
135    /// 发现 proto 文件
136    fn discover_proto_files(&self) -> Result<Vec<PathBuf>> {
137        let mut proto_files = Vec::new();
138
139        if self.input.is_file() {
140            proto_files.push(self.input.clone());
141        } else {
142            // 遍历目录查找 .proto 文件
143            for entry in std::fs::read_dir(&self.input)
144                .map_err(|e| ActrCliError::config_error(format!("读取输入目录失败: {e}")))?
145            {
146                let entry = entry.map_err(|e| ActrCliError::config_error(e.to_string()))?;
147                let path = entry.path();
148
149                if path.extension().unwrap_or_default() == "proto" {
150                    proto_files.push(path);
151                }
152            }
153        }
154
155        if proto_files.is_empty() {
156            return Err(ActrCliError::config_error("未找到 proto 文件"));
157        }
158
159        Ok(proto_files)
160    }
161
162    /// 确保 protoc-gen-actrframework 插件可用
163    ///
164    /// 版本管理策略:
165    /// 1. 检查系统已安装版本
166    /// 2. 如果版本匹配 → 直接使用
167    /// 3. 如果版本不匹配或未安装 → 自动安装/升级
168    ///
169    /// 这种策略确保:
170    /// - 版本一致性:插件版本始终与 CLI 匹配
171    /// - 自动管理:无需手动安装或升级
172    /// - 简单明确:只看版本,不区分开发/生产环境
173    fn ensure_protoc_plugin(&self) -> Result<PathBuf> {
174        // Expected version (same as actr-framework-protoc-codegen)
175        const EXPECTED_VERSION: &str = env!("CARGO_PKG_VERSION");
176
177        // 1. Check installed version
178        let installed_version = self.check_installed_plugin_version()?;
179
180        match installed_version {
181            Some(version) if version == EXPECTED_VERSION => {
182                // Version matches, use it directly
183                info!("✅ Using installed protoc-gen-actrframework v{}", version);
184                let output = StdCommand::new("which")
185                    .arg("protoc-gen-actrframework")
186                    .output()
187                    .map_err(|e| {
188                        ActrCliError::command_error(format!("Failed to locate plugin: {e}"))
189                    })?;
190
191                let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
192                Ok(PathBuf::from(path))
193            }
194            Some(version) => {
195                // Version mismatch, upgrade needed
196                info!(
197                    "🔄 Version mismatch: installed v{}, need v{}",
198                    version, EXPECTED_VERSION
199                );
200                info!("🔨 Upgrading plugin...");
201                self.install_or_upgrade_plugin()
202            }
203            None => {
204                // Not installed, install it
205                info!("📦 protoc-gen-actrframework not found, installing...");
206                self.install_or_upgrade_plugin()
207            }
208        }
209    }
210
211    /// Check installed plugin version
212    fn check_installed_plugin_version(&self) -> Result<Option<String>> {
213        let output = StdCommand::new("protoc-gen-actrframework")
214            .arg("--version")
215            .output();
216
217        match output {
218            Ok(output) if output.status.success() => {
219                let version_info = String::from_utf8_lossy(&output.stdout);
220                // Parse "protoc-gen-actrframework 0.1.0"
221                let version = version_info
222                    .lines()
223                    .next()
224                    .and_then(|line| line.split_whitespace().nth(1))
225                    .map(|v| v.to_string());
226
227                debug!("Detected installed version: {:?}", version);
228                Ok(version)
229            }
230            _ => {
231                debug!("Plugin not found in PATH");
232                Ok(None)
233            }
234        }
235    }
236
237    /// Install or upgrade plugin from workspace
238    fn install_or_upgrade_plugin(&self) -> Result<PathBuf> {
239        // Find actr workspace
240        let current_dir = std::env::current_dir()?;
241        let workspace_root = current_dir.ancestors().find(|p| {
242            let is_workspace =
243                p.join("Cargo.toml").exists() && p.join("crates/framework-protoc-codegen").exists();
244            if is_workspace {
245                debug!("Found workspace root: {:?}", p);
246            }
247            is_workspace
248        });
249
250        let workspace_root = workspace_root.ok_or_else(|| {
251            ActrCliError::config_error(
252                "Cannot find actr workspace.\n\
253                 Please run this command from within an actr project or workspace.",
254            )
255        })?;
256
257        info!("🔍 Found actr workspace at: {}", workspace_root.display());
258
259        // Step 1: Build the plugin
260        info!("🔨 Building protoc-gen-actrframework...");
261        let mut build_cmd = StdCommand::new("cargo");
262        build_cmd
263            .arg("build")
264            .arg("-p")
265            .arg("actr-framework-protoc-codegen")
266            .arg("--bin")
267            .arg("protoc-gen-actrframework")
268            .current_dir(workspace_root);
269
270        debug!("Running: {:?}", build_cmd);
271        let output = build_cmd
272            .output()
273            .map_err(|e| ActrCliError::command_error(format!("Failed to build plugin: {e}")))?;
274
275        if !output.status.success() {
276            let stderr = String::from_utf8_lossy(&output.stderr);
277            return Err(ActrCliError::command_error(format!(
278                "Failed to build plugin:\n{stderr}"
279            )));
280        }
281
282        // Step 2: Install to ~/.cargo/bin/
283        info!("📦 Installing to ~/.cargo/bin/...");
284        let mut install_cmd = StdCommand::new("cargo");
285        install_cmd
286            .arg("install")
287            .arg("--path")
288            .arg(workspace_root.join("crates/framework-protoc-codegen"))
289            .arg("--bin")
290            .arg("protoc-gen-actrframework")
291            .arg("--force"); // Overwrite existing version
292
293        debug!("Running: {:?}", install_cmd);
294        let output = install_cmd
295            .output()
296            .map_err(|e| ActrCliError::command_error(format!("Failed to install plugin: {e}")))?;
297
298        if !output.status.success() {
299            let stderr = String::from_utf8_lossy(&output.stderr);
300            return Err(ActrCliError::command_error(format!(
301                "Failed to install plugin:\n{stderr}"
302            )));
303        }
304
305        info!("✅ Plugin installed successfully");
306
307        // Return the installed path
308        let which_output = StdCommand::new("which")
309            .arg("protoc-gen-actrframework")
310            .output()
311            .map_err(|e| {
312                ActrCliError::command_error(format!("Failed to locate installed plugin: {e}"))
313            })?;
314
315        let path = String::from_utf8_lossy(&which_output.stdout)
316            .trim()
317            .to_string();
318        Ok(PathBuf::from(path))
319    }
320
321    /// 生成基础设施代码
322    async fn generate_infrastructure_code(&self, proto_files: &[PathBuf]) -> Result<()> {
323        info!("🔧 生成基础设施代码...");
324
325        // 确保 protoc 插件可用
326        let plugin_path = self.ensure_protoc_plugin()?;
327
328        // 读取 Actr.toml 获取 manufacturer
329        let manufacturer = self.read_manufacturer()?;
330        debug!("Using manufacturer from Actr.toml: {}", manufacturer);
331
332        for proto_file in proto_files {
333            debug!("处理 proto 文件: {:?}", proto_file);
334
335            // 第一步:使用 prost 生成基础 protobuf 消息类型
336            let mut cmd = StdCommand::new("protoc");
337            cmd.arg(format!("--proto_path={}", self.input.display()))
338                .arg(format!("--prost_out={}", self.output.display()))
339                .arg(proto_file);
340
341            debug!("执行 protoc (prost): {:?}", cmd);
342            let output = cmd.output().map_err(|e| {
343                ActrCliError::command_error(format!("执行 protoc (prost) 失败: {e}"))
344            })?;
345
346            if !output.status.success() {
347                let stderr = String::from_utf8_lossy(&output.stderr);
348                return Err(ActrCliError::command_error(format!(
349                    "protoc (prost) 执行失败: {stderr}"
350                )));
351            }
352
353            // 第二步:使用 actrframework 插件生成 Actor 框架代码
354            let mut cmd = StdCommand::new("protoc");
355            cmd.arg(format!("--proto_path={}", self.input.display()))
356                .arg(format!(
357                    "--plugin=protoc-gen-actrframework={}",
358                    plugin_path.display()
359                ))
360                .arg(format!("--actrframework_opt=manufacturer={manufacturer}"))
361                .arg(format!("--actrframework_out={}", self.output.display()))
362                .arg(proto_file);
363
364            debug!("执行 protoc (actrframework): {:?}", cmd);
365            let output = cmd.output().map_err(|e| {
366                ActrCliError::command_error(format!("执行 protoc (actrframework) 失败: {e}"))
367            })?;
368
369            if !output.status.success() {
370                let stderr = String::from_utf8_lossy(&output.stderr);
371                return Err(ActrCliError::command_error(format!(
372                    "protoc (actrframework) 执行失败: {stderr}"
373                )));
374            }
375
376            let stdout = String::from_utf8_lossy(&output.stdout);
377            if !stdout.is_empty() {
378                debug!("protoc 输出: {}", stdout);
379            }
380        }
381
382        // 生成 mod.rs
383        self.generate_mod_rs(proto_files).await?;
384
385        // 为生成的文件添加只读属性(防止误修改)
386        self.set_generated_files_readonly()?;
387
388        info!("✅ 基础设施代码生成完成");
389        Ok(())
390    }
391
392    /// 生成 mod.rs 文件
393    async fn generate_mod_rs(&self, _proto_files: &[PathBuf]) -> Result<()> {
394        let mod_path = self.output.join("mod.rs");
395
396        // 扫描实际生成的文件,而不是根据 proto 文件名猜测
397        let mut proto_modules = Vec::new();
398        let mut service_modules = Vec::new();
399
400        use std::fs;
401        for entry in fs::read_dir(&self.output)
402            .map_err(|e| ActrCliError::config_error(format!("读取输出目录失败: {e}")))?
403        {
404            let entry = entry.map_err(|e| ActrCliError::config_error(e.to_string()))?;
405            let path = entry.path();
406
407            if path.is_file() && path.extension().unwrap_or_default() == "rs" {
408                if let Some(file_name) = path.file_stem().and_then(|s| s.to_str()) {
409                    // 跳过 mod.rs 本身
410                    if file_name == "mod" {
411                        continue;
412                    }
413
414                    // 区分 service_actor 文件和 proto 文件
415                    if file_name.ends_with("_service_actor") {
416                        service_modules.push(format!("pub mod {file_name};"));
417                    } else {
418                        proto_modules.push(format!("pub mod {file_name};"));
419                    }
420                }
421            }
422        }
423
424        // 排序以保证生成的 mod.rs 内容稳定
425        proto_modules.sort();
426        service_modules.sort();
427
428        let mod_content = format!(
429            r#"//! 自动生成的代码模块
430//!
431//! 此模块由 `actr gen` 命令自动生成,包括:
432//! - protobuf 消息类型定义
433//! - Actor 框架代码(路由器、trait)
434//!
435//! ⚠️  请勿手动修改此目录中的文件
436
437// Protobuf 消息类型(由 prost 生成)
438{}
439
440// Actor 框架代码(由 protoc-gen-actrframework 生成)
441{}
442
443// 常用类型会在各自的模块中定义,请按需导入
444"#,
445            proto_modules.join("\n"),
446            service_modules.join("\n"),
447        );
448
449        std::fs::write(&mod_path, mod_content)
450            .map_err(|e| ActrCliError::config_error(format!("写入 mod.rs 失败: {e}")))?;
451
452        debug!("生成 mod.rs: {:?}", mod_path);
453        Ok(())
454    }
455
456    /// 将生成目录中的文件设置为只读
457    fn set_generated_files_readonly(&self) -> Result<()> {
458        use std::fs;
459
460        for entry in fs::read_dir(&self.output)
461            .map_err(|e| ActrCliError::config_error(format!("读取输出目录失败: {e}")))?
462        {
463            let entry = entry.map_err(|e| ActrCliError::config_error(e.to_string()))?;
464            let path = entry.path();
465
466            if path.is_file() && path.extension().unwrap_or_default() == "rs" {
467                // 获取当前权限
468                let metadata = fs::metadata(&path)
469                    .map_err(|e| ActrCliError::config_error(format!("获取文件元数据失败: {e}")))?;
470                let mut permissions = metadata.permissions();
471
472                // 设置只读(移除写权限)
473                #[cfg(unix)]
474                {
475                    use std::os::unix::fs::PermissionsExt;
476                    let mode = permissions.mode();
477                    permissions.set_mode(mode & !0o222); // 移除所有写权限
478                }
479
480                #[cfg(not(unix))]
481                {
482                    permissions.set_readonly(true);
483                }
484
485                fs::set_permissions(&path, permissions)
486                    .map_err(|e| ActrCliError::config_error(format!("设置文件权限失败: {e}")))?;
487
488                debug!("设置只读属性: {:?}", path);
489            }
490        }
491
492        Ok(())
493    }
494
495    /// 生成用户代码框架
496    async fn generate_user_code_scaffold(&self, proto_files: &[PathBuf]) -> Result<()> {
497        info!("📝 生成用户代码框架...");
498
499        for proto_file in proto_files {
500            let service_name = proto_file
501                .file_stem()
502                .and_then(|s| s.to_str())
503                .ok_or_else(|| ActrCliError::config_error("无效的 proto 文件名"))?;
504
505            self.generate_service_scaffold(service_name).await?;
506        }
507
508        info!("✅ 用户代码框架生成完成");
509        Ok(())
510    }
511
512    /// 为特定服务生成用户代码框架
513    async fn generate_service_scaffold(&self, service_name: &str) -> Result<()> {
514        let user_file_path = self
515            .output
516            .parent()
517            .unwrap_or_else(|| Path::new("src"))
518            .join(format!("{}_service.rs", service_name.to_lowercase()));
519
520        // 如果文件已存在且不强制覆盖,跳过
521        if user_file_path.exists() && !self.overwrite_user_code {
522            info!("⏭️  跳过已存在的用户代码文件: {:?}", user_file_path);
523            return Ok(());
524        }
525
526        let scaffold_content = self.generate_scaffold_content(service_name);
527
528        std::fs::write(&user_file_path, scaffold_content)
529            .map_err(|e| ActrCliError::config_error(format!("写入用户代码框架失败: {e}")))?;
530
531        info!("📄 生成用户代码框架: {:?}", user_file_path);
532        Ok(())
533    }
534
535    /// 生成用户代码框架内容
536    fn generate_scaffold_content(&self, service_name: &str) -> String {
537        let service_name_pascal = service_name
538            .split('_')
539            .map(|s| {
540                let mut chars = s.chars();
541                match chars.next() {
542                    None => String::new(),
543                    Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
544                }
545            })
546            .collect::<String>();
547
548        let template = format!(
549            r#"//! # {service_name_pascal} 用户业务逻辑实现
550//!
551//! 这个文件是由 `actr gen` 命令自动生成的用户代码框架。
552//! 请在这里实现您的具体业务逻辑。
553
554use crate::generated::{{{service_name_pascal}Handler, {service_name_pascal}Actor}};
555// 只导入必要的类型,避免拉入不需要的依赖如 sqlite
556// use actr_framework::prelude::*;
557use std::sync::Arc;
558
559/// {service_name_pascal} 服务的具体实现
560/// 
561/// TODO: 添加您需要的状态字段,例如:
562/// - 数据库连接池
563/// - 配置信息
564/// - 缓存客户端
565/// - 日志记录器等
566pub struct My{service_name_pascal}Service {{
567    // TODO: 添加您的服务状态字段
568    // 例如:
569    // pub db_pool: Arc<DatabasePool>,
570    // pub config: Arc<ServiceConfig>,
571    // pub metrics: Arc<Metrics>,
572}}
573
574impl My{service_name_pascal}Service {{
575    /// 创建新的服务实例
576    /// 
577    /// TODO: 根据您的需要修改构造函数参数
578    pub fn new(/* TODO: 添加必要的依赖 */) -> Self {{
579        Self {{
580            // TODO: 初始化您的字段
581        }}
582    }}
583    
584    /// 使用默认配置创建服务实例(用于测试)
585    pub fn default_for_testing() -> Self {{
586        Self {{
587            // TODO: 提供测试用的默认值
588        }}
589    }}
590}}
591
592// TODO: 实现 {service_name_pascal}Handler trait 的所有方法
593// 注意:impl_user_code_scaffold! 宏已经为您生成了基础框架,
594// 您需要将其替换为真实的业务逻辑实现。
595//
596// 示例:
597// #[async_trait]
598// impl {service_name_pascal}Handler for My{service_name_pascal}Service {{
599//     async fn method_name(&self, req: RequestType) -> ActorResult<ResponseType> {{
600//         // 1. 验证输入
601//         // 2. 执行业务逻辑
602//         // 3. 返回结果
603//         todo!("实现您的业务逻辑")
604//     }}
605// }}
606
607#[cfg(test)]
608mod tests {{
609    use super::*;
610    
611    #[tokio::test]
612    async fn test_service_creation() {{
613        let _service = My{service_name_pascal}Service::default_for_testing();
614        // TODO: 添加您的测试
615    }}
616    
617    // TODO: 添加更多测试用例
618}}
619
620/*
621📚 使用指南
622
623## 🚀 快速开始
624
6251. **实现业务逻辑**:
626   在 `My{service_name_pascal}Service` 中实现 `{service_name_pascal}Handler` trait 的所有方法
627
6282. **添加依赖**:
629   在 `Cargo.toml` 中添加您需要的依赖,例如数据库客户端、HTTP 客户端等
630
6313. **配置服务**:
632   修改 `new()` 构造函数,注入必要的依赖
633
6344. **启动服务**:
635   ```rust
636   #[tokio::main]
637   async fn main() -> ActorResult<()> {{
638       let service = My{service_name_pascal}Service::new(/* 依赖 */);
639       
640       ActorSystem::new()
641           .attach(service)
642           .start()
643           .await
644   }}
645   ```
646
647## 🔧 开发提示
648
649- 使用 `tracing` crate 进行日志记录
650- 实现错误处理和重试逻辑
651- 添加单元测试和集成测试
652- 考虑使用配置文件管理环境变量
653- 实现健康检查和指标收集
654
655## 📖 更多资源
656
657- Actor-RTC 文档: [链接]
658- API 参考: [链接]
659- 示例项目: [链接]
660*/
661"# // 示例代码中的 Service
662        );
663
664        template
665    }
666
667    /// 格式化生成的代码
668    async fn format_generated_code(&self) -> Result<()> {
669        info!("🎨 格式化生成的代码...");
670
671        let mut cmd = StdCommand::new("rustfmt");
672        cmd.arg("--edition")
673            .arg("2024")
674            .arg("--config")
675            .arg("max_width=100");
676
677        // 格式化生成目录中的所有 .rs 文件
678        for entry in std::fs::read_dir(&self.output)
679            .map_err(|e| ActrCliError::config_error(format!("读取输出目录失败: {e}")))?
680        {
681            let entry = entry.map_err(|e| ActrCliError::config_error(e.to_string()))?;
682            let path = entry.path();
683
684            if path.extension().unwrap_or_default() == "rs" {
685                cmd.arg(&path);
686            }
687        }
688
689        let output = cmd
690            .output()
691            .map_err(|e| ActrCliError::command_error(format!("执行 rustfmt 失败: {e}")))?;
692
693        if !output.status.success() {
694            let stderr = String::from_utf8_lossy(&output.stderr);
695            warn!("rustfmt 执行警告: {}", stderr);
696        } else {
697            info!("✅ 代码格式化完成");
698        }
699
700        Ok(())
701    }
702
703    /// 验证生成的代码
704    async fn validate_generated_code(&self) -> Result<()> {
705        info!("🔍 验证生成的代码...");
706
707        // 查找项目根目录(包含 Cargo.toml 的目录)
708        let project_root = self.find_project_root()?;
709
710        let mut cmd = StdCommand::new("cargo");
711        cmd.arg("check").arg("--quiet").current_dir(&project_root);
712
713        let output = cmd
714            .output()
715            .map_err(|e| ActrCliError::command_error(format!("执行 cargo check 失败: {e}")))?;
716
717        if !output.status.success() {
718            let stderr = String::from_utf8_lossy(&output.stderr);
719            warn!("生成的代码存在编译警告或错误:\n{}", stderr);
720            info!("💡 这通常是正常的,因为用户代码框架包含 TODO 标记");
721        } else {
722            info!("✅ 代码验证通过");
723        }
724
725        Ok(())
726    }
727
728    /// 查找项目根目录(包含 Cargo.toml 的目录)
729    fn find_project_root(&self) -> Result<PathBuf> {
730        let mut current = std::env::current_dir().map_err(ActrCliError::Io)?;
731
732        loop {
733            if current.join("Cargo.toml").exists() {
734                return Ok(current);
735            }
736
737            match current.parent() {
738                Some(parent) => current = parent.to_path_buf(),
739                None => break,
740            }
741        }
742
743        // 如果找不到 Cargo.toml,回退到当前目录
744        std::env::current_dir().map_err(ActrCliError::Io)
745    }
746
747    /// 打印后续步骤提示
748    fn print_next_steps(&self) {
749        println!("\n🎉 代码生成完成!");
750        println!("\n📋 后续步骤:");
751        println!("1. 📖 查看生成的代码: {:?}", self.output);
752        if self.generate_scaffold {
753            println!("2. ✏️  实现业务逻辑: 在 src/ 目录下的 *_service.rs 文件中");
754            println!("3. 🔧 添加依赖: 在 Cargo.toml 中添加需要的依赖包");
755        }
756        println!("4. 🏗️  编译项目: cargo build");
757        println!("5. 🧪 运行测试: cargo test");
758        println!("6. 🚀 启动服务: cargo run");
759        println!("\n💡 提示: 查看生成的用户代码文件中的详细使用指南");
760    }
761}