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