actr_cli/commands/
generate.rs

1//! # Code Generation Command
2//!
3//! Generate Rust Actor code from proto files, including:
4//! 1. Protobuf message types
5//! 2. Actor infrastructure code
6//! 3. User business logic scaffolds (with TODO comments)
7
8use crate::commands::Command;
9use crate::commands::SupportedLanguage;
10use crate::commands::codegen::{GenContext, execute_codegen};
11use crate::error::{ActrCliError, Result};
12// 只导入必要的类型,避免拉入不需要的依赖如 sqlite
13// use actr_framework::prelude::*;
14use async_trait::async_trait;
15use clap::Args;
16use std::path::{Path, PathBuf};
17use std::process::Command as StdCommand;
18use tracing::{debug, info, warn};
19
20#[derive(Args, Debug, Clone)]
21#[command(
22    about = "Generate code from proto files",
23    long_about = "Generate Actor code in multiple languages from proto files, including protobuf message types, Actor infrastructure code, and user business logic scaffolds"
24)]
25pub struct GenCommand {
26    /// Input proto file or directory
27    #[arg(short, long, default_value = "proto")]
28    pub input: PathBuf,
29
30    /// Output directory
31    #[arg(short, long, default_value = "src/generated")]
32    pub output: PathBuf,
33
34    /// Path to Actr.toml config file
35    #[arg(short, long, default_value = "Actr.toml")]
36    pub config: PathBuf,
37
38    /// Clean generated outputs before regenerating
39    #[arg(long = "clean")]
40    pub clean: bool,
41
42    /// Skip user code scaffold generation
43    #[arg(long = "no-scaffold")]
44    pub no_scaffold: bool,
45
46    /// Whether to overwrite existing user code files
47    #[arg(long)]
48    pub overwrite_user_code: bool,
49
50    /// Skip formatting
51    #[arg(long = "no-format")]
52    pub no_format: bool,
53
54    /// Debug mode: keep intermediate generated files
55    #[arg(long)]
56    pub debug: bool,
57
58    /// Target language for generation
59    #[arg(short, long, default_value = "rust")]
60    pub language: SupportedLanguage,
61}
62
63#[async_trait]
64impl Command for GenCommand {
65    async fn execute(&self) -> Result<()> {
66        info!(
67            "🚀 Start code generation (language: {:?})...",
68            self.language
69        );
70        let config = actr_config::ConfigParser::from_file(&self.config)
71            .map_err(|e| ActrCliError::config_error(format!("Failed to parse Actr.toml: {e}")))?;
72
73        let proto_files = self.preprocess()?;
74        if self.language != SupportedLanguage::Rust {
75            let context = GenContext {
76                proto_files,
77                input_path: self.input.clone(),
78                output: self.output.clone(),
79                config: config.clone(),
80                no_scaffold: self.no_scaffold,
81                overwrite_user_code: self.overwrite_user_code,
82                no_format: self.no_format,
83                debug: self.debug,
84            };
85            execute_codegen(self.language, &context).await?;
86            return Ok(());
87        }
88
89        // Step 5: Generate infrastructure code
90        self.generate_infrastructure_code(&proto_files, &config)
91            .await?;
92
93        // Step 6: Generate user code scaffold
94        if self.should_generate_scaffold() {
95            self.generate_user_code_scaffold(&proto_files).await?;
96        }
97
98        // Step 7: Format code
99        if self.should_format() {
100            self.format_generated_code().await?;
101        }
102
103        // Step 8: Validate generated code
104        self.validate_generated_code().await?;
105
106        info!("✅ Code generation completed!");
107        // Set all generated files to read-only only after generation, formatting, and validation are complete, to not interfere with rustfmt or other steps.
108        self.set_generated_files_readonly()?;
109        self.print_next_steps();
110
111        Ok(())
112    }
113}
114
115impl GenCommand {
116    fn preprocess(&self) -> Result<Vec<PathBuf>> {
117        // Step 1: Validate inputs
118        self.validate_inputs()?;
119
120        // Step 2: Clean old generation outputs (optional)
121        self.clean_generated_outputs()?;
122
123        // Step 3: Prepare output directories
124        self.prepare_output_dirs()?;
125
126        // Step 4: Discover proto files
127        let proto_files = self.discover_proto_files()?;
128        info!("📁 Found {} proto files", proto_files.len());
129
130        Ok(proto_files)
131    }
132
133    /// Whether user code scaffold should be generated
134    fn should_generate_scaffold(&self) -> bool {
135        !self.no_scaffold
136    }
137
138    /// Whether formatting should run
139    fn should_format(&self) -> bool {
140        !self.no_format
141    }
142
143    /// Remove previously generated files when --clean is used
144    fn clean_generated_outputs(&self) -> Result<()> {
145        use std::fs;
146
147        if !self.clean {
148            return Ok(());
149        }
150
151        if !self.output.exists() {
152            return Ok(());
153        }
154
155        info!("🧹 Cleaning old generation results: {:?}", self.output);
156
157        self.make_writable_recursive(&self.output)?;
158        fs::remove_dir_all(&self.output).map_err(|e| {
159            ActrCliError::config_error(format!("Failed to delete generation directory: {e}"))
160        })?;
161
162        Ok(())
163    }
164
165    /// Ensure all files are writable so removal works across platforms
166    #[allow(clippy::only_used_in_recursion)]
167    fn make_writable_recursive(&self, path: &Path) -> Result<()> {
168        use std::fs;
169
170        if path.is_file() {
171            let metadata = fs::metadata(path).map_err(|e| {
172                ActrCliError::config_error(format!("Failed to read file metadata: {e}"))
173            })?;
174            let mut permissions = metadata.permissions();
175
176            #[cfg(unix)]
177            {
178                use std::os::unix::fs::PermissionsExt;
179                let mode = permissions.mode();
180                permissions.set_mode(mode | 0o222);
181            }
182
183            #[cfg(not(unix))]
184            {
185                permissions.set_readonly(false);
186            }
187
188            fs::set_permissions(path, permissions).map_err(|e| {
189                ActrCliError::config_error(format!("Failed to reset file permissions: {e}"))
190            })?;
191        } else if path.is_dir() {
192            for entry in fs::read_dir(path)
193                .map_err(|e| ActrCliError::config_error(format!("Failed to read directory: {e}")))?
194            {
195                let entry = entry.map_err(|e| ActrCliError::config_error(e.to_string()))?;
196                self.make_writable_recursive(&entry.path())?;
197            }
198        }
199
200        Ok(())
201    }
202
203    /// 验证输入参数
204    fn validate_inputs(&self) -> Result<()> {
205        if !self.input.exists() {
206            return Err(ActrCliError::config_error(format!(
207                "Input path does not exist: {:?}",
208                self.input
209            )));
210        }
211
212        if self.input.is_file() && self.input.extension().unwrap_or_default() != "proto" {
213            warn!("Input file is not a .proto file: {:?}", self.input);
214        }
215
216        Ok(())
217    }
218
219    /// 准备输出目录
220    fn prepare_output_dirs(&self) -> Result<()> {
221        std::fs::create_dir_all(&self.output).map_err(|e| {
222            ActrCliError::config_error(format!("Failed to create output directory: {e}"))
223        })?;
224
225        if self.should_generate_scaffold() {
226            let user_code_dir = self.output.join("../");
227            std::fs::create_dir_all(&user_code_dir).map_err(|e| {
228                ActrCliError::config_error(format!("Failed to create user code directory: {e}"))
229            })?;
230        }
231
232        Ok(())
233    }
234
235    /// 发现 proto 文件
236    fn discover_proto_files(&self) -> Result<Vec<PathBuf>> {
237        let mut proto_files = Vec::new();
238
239        if self.input.is_file() {
240            proto_files.push(self.input.clone());
241        } else {
242            // 遍历目录查找 .proto 文件
243            for entry in std::fs::read_dir(&self.input).map_err(|e| {
244                ActrCliError::config_error(format!("Failed to read input directory: {e}"))
245            })? {
246                let entry = entry.map_err(|e| ActrCliError::config_error(e.to_string()))?;
247                let path = entry.path();
248
249                if path.extension().unwrap_or_default() == "proto" {
250                    proto_files.push(path);
251                }
252            }
253        }
254
255        if proto_files.is_empty() {
256            return Err(ActrCliError::config_error("No proto files found"));
257        }
258
259        Ok(proto_files)
260    }
261
262    /// 确保 protoc-gen-actrframework 插件可用
263    ///
264    /// 版本管理策略:
265    /// 1. 检查系统已安装版本
266    /// 2. 如果版本匹配 → 直接使用
267    /// 3. 如果版本不匹配或未安装 → 自动安装/升级
268    ///
269    /// 这种策略确保:
270    /// - 版本一致性:插件版本始终与 CLI 匹配
271    /// - 自动管理:无需手动安装或升级
272    /// - 简单明确:只看版本,不区分开发/生产环境
273    fn ensure_protoc_plugin(&self) -> Result<PathBuf> {
274        // Expected version (same as actr-framework-protoc-codegen)
275        const EXPECTED_VERSION: &str = env!("CARGO_PKG_VERSION");
276
277        // 1. Check installed version
278        let installed_version = self.check_installed_plugin_version()?;
279
280        match installed_version {
281            Some(version) if version == EXPECTED_VERSION => {
282                // Version matches, use it directly
283                info!("✅ Using installed protoc-gen-actrframework v{}", version);
284                let output = StdCommand::new("which")
285                    .arg("protoc-gen-actrframework")
286                    .output()
287                    .map_err(|e| {
288                        ActrCliError::command_error(format!("Failed to locate plugin: {e}"))
289                    })?;
290
291                let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
292                Ok(PathBuf::from(path))
293            }
294            Some(version) => {
295                // Version mismatch, upgrade needed
296                info!(
297                    "🔄 Version mismatch: installed v{}, need v{}",
298                    version, EXPECTED_VERSION
299                );
300                info!("🔨 Upgrading plugin...");
301                self.install_or_upgrade_plugin()
302            }
303            None => {
304                // Not installed, install it
305                info!("📦 protoc-gen-actrframework not found, installing...");
306                self.install_or_upgrade_plugin()
307            }
308        }
309    }
310
311    /// Check installed plugin version
312    fn check_installed_plugin_version(&self) -> Result<Option<String>> {
313        let output = StdCommand::new("protoc-gen-actrframework")
314            .arg("--version")
315            .output();
316
317        match output {
318            Ok(output) if output.status.success() => {
319                let version_info = String::from_utf8_lossy(&output.stdout);
320                // Parse "protoc-gen-actrframework 0.1.0"
321                let version = version_info
322                    .lines()
323                    .next()
324                    .and_then(|line| line.split_whitespace().nth(1))
325                    .map(|v| v.to_string());
326
327                debug!("Detected installed version: {:?}", version);
328                Ok(version)
329            }
330            _ => {
331                debug!("Plugin not found in PATH");
332                Ok(None)
333            }
334        }
335    }
336
337    /// Install or upgrade plugin from workspace
338    fn install_or_upgrade_plugin(&self) -> Result<PathBuf> {
339        // Find actr workspace
340        let current_dir = std::env::current_dir()?;
341        let workspace_root = current_dir.ancestors().find(|p| {
342            let is_workspace =
343                p.join("Cargo.toml").exists() && p.join("crates/framework-protoc-codegen").exists();
344            if is_workspace {
345                debug!("Found workspace root: {:?}", p);
346            }
347            is_workspace
348        });
349
350        let workspace_root = workspace_root.ok_or_else(|| {
351            ActrCliError::config_error(
352                "Cannot find actr workspace.\n\
353                 Please run this command from within an actr project or workspace.",
354            )
355        })?;
356
357        info!("🔍 Found actr workspace at: {}", workspace_root.display());
358
359        // Step 1: Build the plugin
360        info!("🔨 Building protoc-gen-actrframework...");
361        let mut build_cmd = StdCommand::new("cargo");
362        build_cmd
363            .arg("build")
364            .arg("-p")
365            .arg("actr-framework-protoc-codegen")
366            .arg("--bin")
367            .arg("protoc-gen-actrframework")
368            .current_dir(workspace_root);
369
370        debug!("Running: {:?}", build_cmd);
371        let output = build_cmd
372            .output()
373            .map_err(|e| ActrCliError::command_error(format!("Failed to build plugin: {e}")))?;
374
375        if !output.status.success() {
376            let stderr = String::from_utf8_lossy(&output.stderr);
377            return Err(ActrCliError::command_error(format!(
378                "Failed to build plugin:\n{stderr}"
379            )));
380        }
381
382        // Step 2: Install to ~/.cargo/bin/
383        info!("📦 Installing to ~/.cargo/bin/...");
384        let mut install_cmd = StdCommand::new("cargo");
385        install_cmd
386            .arg("install")
387            .arg("--path")
388            .arg(workspace_root.join("crates/framework-protoc-codegen"))
389            .arg("--bin")
390            .arg("protoc-gen-actrframework")
391            .arg("--force"); // Overwrite existing version
392
393        debug!("Running: {:?}", install_cmd);
394        let output = install_cmd
395            .output()
396            .map_err(|e| ActrCliError::command_error(format!("Failed to install plugin: {e}")))?;
397
398        if !output.status.success() {
399            let stderr = String::from_utf8_lossy(&output.stderr);
400            return Err(ActrCliError::command_error(format!(
401                "Failed to install plugin:\n{stderr}"
402            )));
403        }
404
405        info!("✅ Plugin installed successfully");
406
407        // Return the installed path
408        let which_output = StdCommand::new("which")
409            .arg("protoc-gen-actrframework")
410            .output()
411            .map_err(|e| {
412                ActrCliError::command_error(format!("Failed to locate installed plugin: {e}"))
413            })?;
414
415        let path = String::from_utf8_lossy(&which_output.stdout)
416            .trim()
417            .to_string();
418        Ok(PathBuf::from(path))
419    }
420
421    /// 生成基础设施代码
422    async fn generate_infrastructure_code(
423        &self,
424        proto_files: &[PathBuf],
425        config: &actr_config::Config,
426    ) -> Result<()> {
427        info!("🔧 Generating infrastructure code...");
428
429        // 确保 protoc 插件可用
430        let plugin_path = self.ensure_protoc_plugin()?;
431
432        let manufacturer = config.package.actr_type.manufacturer.clone();
433        debug!("Using manufacturer from Actr.toml: {}", manufacturer);
434
435        for proto_file in proto_files {
436            debug!("Processing proto file: {:?}", proto_file);
437
438            // 第一步:使用 prost 生成基础 protobuf 消息类型
439            let mut cmd = StdCommand::new("protoc");
440            cmd.arg(format!("--proto_path={}", self.input.display()))
441                .arg("--prost_opt=flat_output_dir")
442                .arg(format!("--prost_out={}", self.output.display()))
443                .arg(proto_file);
444
445            debug!("Executing protoc (prost): {:?}", cmd);
446            let output = cmd.output().map_err(|e| {
447                ActrCliError::command_error(format!("Failed to execute protoc (prost): {e}"))
448            })?;
449
450            if !output.status.success() {
451                let stderr = String::from_utf8_lossy(&output.stderr);
452                return Err(ActrCliError::command_error(format!(
453                    "protoc (prost) execution failed: {stderr}"
454                )));
455            }
456
457            // 第二步:使用 actrframework 插件生成 Actor 框架代码
458            let mut cmd = StdCommand::new("protoc");
459            cmd.arg(format!("--proto_path={}", self.input.display()))
460                .arg(format!(
461                    "--plugin=protoc-gen-actrframework={}",
462                    plugin_path.display()
463                ))
464                .arg(format!("--actrframework_opt=manufacturer={manufacturer}"))
465                .arg(format!("--actrframework_out={}", self.output.display()))
466                .arg(proto_file);
467
468            debug!("Executing protoc (actrframework): {:?}", cmd);
469            let output = cmd.output().map_err(|e| {
470                ActrCliError::command_error(format!(
471                    "Failed to execute protoc (actrframework): {e}"
472                ))
473            })?;
474
475            if !output.status.success() {
476                let stderr = String::from_utf8_lossy(&output.stderr);
477                return Err(ActrCliError::command_error(format!(
478                    "protoc (actrframework) execution failed: {stderr}"
479                )));
480            }
481
482            let stdout = String::from_utf8_lossy(&output.stdout);
483            if !stdout.is_empty() {
484                debug!("protoc output: {}", stdout);
485            }
486        }
487
488        // 生成 mod.rs
489        self.generate_mod_rs(proto_files).await?;
490
491        info!("✅ Infrastructure code generation completed");
492        Ok(())
493    }
494
495    /// 生成 mod.rs 文件
496    async fn generate_mod_rs(&self, _proto_files: &[PathBuf]) -> Result<()> {
497        let mod_path = self.output.join("mod.rs");
498
499        // 扫描实际生成的文件,而不是根据 proto 文件名猜测
500        let mut proto_modules = Vec::new();
501        let mut service_modules = Vec::new();
502
503        use std::fs;
504        for entry in fs::read_dir(&self.output).map_err(|e| {
505            ActrCliError::config_error(format!("Failed to read output directory: {e}"))
506        })? {
507            let entry = entry.map_err(|e| ActrCliError::config_error(e.to_string()))?;
508            let path = entry.path();
509
510            if path.is_file()
511                && path.extension().unwrap_or_default() == "rs"
512                && let Some(file_name) = path.file_stem().and_then(|s| s.to_str())
513            {
514                // 跳过 mod.rs 本身
515                if file_name == "mod" {
516                    continue;
517                }
518
519                // 区分 service_actor 文件和 proto 文件
520                if file_name.ends_with("_service_actor") {
521                    service_modules.push(format!("pub mod {file_name};"));
522                } else {
523                    proto_modules.push(format!("pub mod {file_name};"));
524                }
525            }
526        }
527
528        // 排序以保证生成的 mod.rs 内容稳定
529        proto_modules.sort();
530        service_modules.sort();
531
532        let mod_content = format!(
533            r#"//! Automatically generated code module
534//!
535//! This module is automatically generated by the `actr gen` command, including:
536//! - protobuf message type definitions
537//! - Actor framework code (router, traits)
538//!
539//! ⚠️ Do not manually modify files in this directory
540
541// Protobuf message types (generated by prost)
542{}
543
544// Actor framework code (generated by protoc-gen-actrframework)
545{}
546
547// Common types are defined in their respective modules, please import as needed
548"#,
549            proto_modules.join("\n"),
550            service_modules.join("\n"),
551        );
552
553        std::fs::write(&mod_path, mod_content)
554            .map_err(|e| ActrCliError::config_error(format!("Failed to write mod.rs: {e}")))?;
555
556        debug!("Generated mod.rs: {:?}", mod_path);
557        Ok(())
558    }
559
560    /// 将生成目录中的文件设置为只读
561    fn set_generated_files_readonly(&self) -> Result<()> {
562        use std::fs;
563
564        for entry in fs::read_dir(&self.output).map_err(|e| {
565            ActrCliError::config_error(format!("Failed to read output directory: {e}"))
566        })? {
567            let entry = entry.map_err(|e| ActrCliError::config_error(e.to_string()))?;
568            let path = entry.path();
569
570            if path.is_file() && path.extension().unwrap_or_default() == "rs" {
571                // 获取当前权限
572                let metadata = fs::metadata(&path).map_err(|e| {
573                    ActrCliError::config_error(format!("Failed to get file metadata: {e}"))
574                })?;
575                let mut permissions = metadata.permissions();
576
577                // 设置只读(移除写权限)
578                #[cfg(unix)]
579                {
580                    use std::os::unix::fs::PermissionsExt;
581                    let mode = permissions.mode();
582                    permissions.set_mode(mode & !0o222); // 移除所有写权限
583                }
584
585                #[cfg(not(unix))]
586                {
587                    permissions.set_readonly(true);
588                }
589
590                fs::set_permissions(&path, permissions).map_err(|e| {
591                    ActrCliError::config_error(format!("Failed to set file permissions: {e}"))
592                })?;
593
594                debug!("Set read-only attribute: {:?}", path);
595            }
596        }
597
598        Ok(())
599    }
600
601    /// Generate user code scaffold
602    async fn generate_user_code_scaffold(&self, proto_files: &[PathBuf]) -> Result<()> {
603        info!("📝 Generating user code scaffold...");
604
605        for proto_file in proto_files {
606            let service_name = proto_file
607                .file_stem()
608                .and_then(|s| s.to_str())
609                .ok_or_else(|| ActrCliError::config_error("Invalid proto file name"))?;
610
611            self.generate_service_scaffold(service_name).await?;
612        }
613
614        info!("✅ User code scaffold generation completed");
615        Ok(())
616    }
617
618    /// Generate scaffold for a specific service
619    async fn generate_service_scaffold(&self, service_name: &str) -> Result<()> {
620        let user_file_path = self
621            .output
622            .parent()
623            .unwrap_or_else(|| Path::new("src"))
624            .join(format!("{}_service.rs", service_name.to_lowercase()));
625
626        // If file exists and overwrite is not forced, skip
627        if user_file_path.exists() && !self.overwrite_user_code {
628            info!("⏭️  Skipping existing user code file: {:?}", user_file_path);
629            return Ok(());
630        }
631
632        let scaffold_content = self.generate_scaffold_content(service_name);
633
634        std::fs::write(&user_file_path, scaffold_content).map_err(|e| {
635            ActrCliError::config_error(format!("Failed to write user code scaffold: {e}"))
636        })?;
637
638        info!("📄 Generated user code scaffold: {:?}", user_file_path);
639        Ok(())
640    }
641
642    /// 生成用户代码框架内容
643    fn generate_scaffold_content(&self, service_name: &str) -> String {
644        let service_name_pascal = service_name
645            .split('_')
646            .map(|s| {
647                let mut chars = s.chars();
648                match chars.next() {
649                    None => String::new(),
650                    Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
651                }
652            })
653            .collect::<String>();
654
655        let template = format!(
656            r#"//! # {service_name_pascal} user business logic implementation
657//!
658//! This file is a user code scaffold automatically generated by the `actr gen` command.
659//! Please implement your specific business logic here.
660
661use crate::generated::{{{service_name_pascal}Handler, {service_name_pascal}Actor}};
662// 只导入必要的类型,避免拉入不需要的依赖如 sqlite
663// use actr_framework::prelude::*;
664use std::sync::Arc;
665
666/// Specific implementation of the {service_name_pascal} service
667/// 
668/// TODO: Add state fields you need, for example:
669/// - Database connection pool
670/// - Configuration information
671/// - Cache client
672/// - Logger, etc.
673pub struct My{service_name_pascal}Service {{
674    // TODO: Add your service state fields
675    // For example:
676    // pub db_pool: Arc<DatabasePool>,
677    // pub config: Arc<ServiceConfig>,
678    // pub metrics: Arc<Metrics>,
679}}
680
681impl My{service_name_pascal}Service {{
682    /// Create a new service instance
683    /// 
684    /// TODO: Modify constructor parameters as needed
685    pub fn new(/* TODO: Add necessary dependencies */) -> Self {{
686        Self {{
687            // TODO: Initialize your fields
688        }}
689    }}
690    
691    /// Create a service instance with default configuration (for testing)
692    pub fn default_for_testing() -> Self {{
693        Self {{
694            // TODO: Provide default values for testing
695        }}
696    }}
697}}
698
699// TODO: Implement all methods of the {service_name_pascal}Handler trait
700// Note: The impl_user_code_scaffold! macro has generated a basic scaffold for you,
701// you need to replace it with real business logic implementation.
702//
703// Example:
704// #[async_trait]
705// impl {service_name_pascal}Handler for My{service_name_pascal}Service {{
706//     async fn method_name(&self, req: RequestType) -> ActorResult<ResponseType> {{
707//         // 1. Validate input
708//         // 2. Execute business logic
709//         // 3. Return result
710//         todo!("Implement your business logic")
711//     }}
712// }}
713
714#[cfg(test)]
715mod tests {{
716    use super::*;
717    
718    #[tokio::test]
719    async fn test_service_creation() {{
720        let _service = My{service_name_pascal}Service::default_for_testing();
721        // TODO: Add your tests
722    }}
723    
724    // TODO: Add more test cases
725}}
726
727/*
728📚 User Guide
729
730## 🚀 Quick Start
731
7321. **Implement business logic**:
733   Implement all methods of the `{service_name_pascal}Handler` trait in `My{service_name_pascal}Service`
734
7352. **Add dependencies**:
736   Add dependencies you need in `Cargo.toml`, such as database clients, HTTP clients, etc.
737
7383. **Configure service**:
739   Modify the `new()` constructor to inject necessary dependencies
740
7414. **Start service**:
742   ```rust
743   #[tokio::main]
744   async fn main() -> ActorResult<()> {{
745       let service = My{service_name_pascal}Service::new(/* dependencies */);
746       
747       ActorSystem::new()
748           .attach(service)
749           .start()
750           .await
751   }}
752   ```
753
754## 🔧 Development Tips
755
756- Use `tracing` crate for logging
757- Implement error handling and retry logic
758- Add unit and integration tests
759- Consider using configuration files for environment variables
760- Implement health checks and metrics collection
761
762## 📖 More Resources
763
764- Actor-RTC Documentation: [Link]
765- API Reference: [Link]
766- Example Projects: [Link]
767*/
768"# // Service in example code
769        );
770
771        template
772    }
773
774    /// 格式化生成的代码
775    async fn format_generated_code(&self) -> Result<()> {
776        info!("🎨 Formatting generated code...");
777
778        let mut cmd = StdCommand::new("rustfmt");
779        cmd.arg("--edition")
780            .arg("2024")
781            .arg("--config")
782            .arg("max_width=100");
783
784        // 格式化生成目录中的所有 .rs 文件
785        for entry in std::fs::read_dir(&self.output).map_err(|e| {
786            ActrCliError::config_error(format!("Failed to read output directory: {e}"))
787        })? {
788            let entry = entry.map_err(|e| ActrCliError::config_error(e.to_string()))?;
789            let path = entry.path();
790
791            if path.extension().unwrap_or_default() == "rs" {
792                cmd.arg(&path);
793            }
794        }
795
796        let output = cmd
797            .output()
798            .map_err(|e| ActrCliError::command_error(format!("Failed to execute rustfmt: {e}")))?;
799
800        if !output.status.success() {
801            let stderr = String::from_utf8_lossy(&output.stderr);
802            warn!("rustfmt execution warning: {}", stderr);
803        } else {
804            info!("✅ Code formatting completed");
805        }
806
807        Ok(())
808    }
809
810    /// 验证生成的代码
811    async fn validate_generated_code(&self) -> Result<()> {
812        info!("🔍 Validating generated code...");
813
814        // 查找项目根目录(包含 Cargo.toml 的目录)
815        let project_root = self.find_project_root()?;
816
817        let mut cmd = StdCommand::new("cargo");
818        cmd.arg("check").arg("--quiet").current_dir(&project_root);
819
820        let output = cmd.output().map_err(|e| {
821            ActrCliError::command_error(format!("Failed to execute cargo check: {e}"))
822        })?;
823
824        if !output.status.success() {
825            let stderr = String::from_utf8_lossy(&output.stderr);
826            warn!(
827                "Generated code has compilation warnings or errors:\n{}",
828                stderr
829            );
830            info!("💡 This is usually normal because the user code scaffold contains TODO markers");
831        } else {
832            info!("✅ Code validation passed");
833        }
834
835        Ok(())
836    }
837
838    /// 查找项目根目录(包含 Cargo.toml 的目录)
839    fn find_project_root(&self) -> Result<PathBuf> {
840        let mut current = std::env::current_dir().map_err(ActrCliError::Io)?;
841
842        loop {
843            if current.join("Cargo.toml").exists() {
844                return Ok(current);
845            }
846
847            match current.parent() {
848                Some(parent) => current = parent.to_path_buf(),
849                None => break,
850            }
851        }
852
853        // 如果找不到 Cargo.toml,回退到当前目录
854        std::env::current_dir().map_err(ActrCliError::Io)
855    }
856
857    /// 打印后续步骤提示
858    fn print_next_steps(&self) {
859        println!("\n🎉 Code generation completed!");
860        println!("\n📋 Next steps:");
861        println!("1. 📖 View generated code: {:?}", self.output);
862        if self.should_generate_scaffold() {
863            println!(
864                "2. ✏️  Implement business logic: in the *_service.rs files in the src/ directory"
865            );
866            println!("3. 🔧 Add dependencies: add required packages in Cargo.toml");
867            println!("4. 🏗️  Build project: cargo build");
868            println!("5. 🧪 Run tests: cargo test");
869            println!("6. 🚀 Start service: cargo run");
870        } else {
871            println!("2. 🏗️  Build project: cargo build");
872            println!("3. 🧪 Run tests: cargo test");
873            println!("4. 🚀 Start service: cargo run");
874        }
875        println!("\n💡 Tip: Check the detailed user guide in the generated user code files");
876    }
877}