Skip to main content

dampen_cli/commands/
run.rs

1#![allow(clippy::print_stderr, clippy::print_stdout)]
2
3//! Run command - launches development mode with interpreted execution
4//!
5//! This command wraps `cargo run` with the `interpreted` feature flag enabled,
6//! providing fast iteration with hot-reload capabilities.
7
8use std::path::Path;
9use std::process::Command;
10
11/// Run command arguments
12#[derive(clap::Args)]
13pub struct RunArgs {
14    /// Package to run (if workspace has multiple binaries)
15    #[arg(short, long)]
16    package: Option<String>,
17
18    /// Arguments to pass to the application
19    #[arg(last = true)]
20    app_args: Vec<String>,
21
22    /// Verbose output
23    #[arg(short, long)]
24    verbose: bool,
25
26    /// Run with release optimizations
27    #[arg(long)]
28    release: bool,
29
30    /// Additional features to enable (beyond interpreted)
31    #[arg(long, value_delimiter = ',')]
32    features: Vec<String>,
33}
34
35/// Execute the run command
36///
37/// This command launches the application:
38/// - Debug mode (default): Interpreted mode with hot-reload support
39/// - Release mode (--release): Codegen mode with full optimizations
40///
41/// # Mode Behavior
42///
43/// - **Interpreted Mode** (default): Runtime XML parsing with hot-reload support
44/// - **Codegen Mode** (--release): Compile-time code generation, zero runtime overhead
45///
46/// # Examples
47///
48/// ```bash
49/// # Debug mode (interpreted with hot-reload)
50/// dampen run
51///
52/// # Release mode (codegen optimized)
53/// dampen run --release
54///
55/// # Run specific package
56/// dampen run -p my-app
57///
58/// # Pass application arguments
59/// dampen run -- --window-size 800x600
60///
61/// # Enable additional features
62/// dampen run --features tokio,logging
63/// ```
64pub fn execute(args: &RunArgs) -> Result<(), String> {
65    // Run checks first (strict=false so warnings don't block, but errors do)
66    if args.verbose {
67        eprintln!("Running pre-flight checks...");
68    }
69
70    // Resolve UI directory based on package argument if present
71    let check_input = if let Some(ref pkg) = args.package {
72        crate::commands::check::resolve_package_ui_path(pkg)
73            .map(|p| p.to_string_lossy().to_string())
74    } else {
75        None
76    };
77
78    if let Err(e) = crate::commands::check::run_checks(check_input, false, args.verbose) {
79        return Err(format!("Pre-flight check failed: {}", e));
80    }
81
82    // Check if Cargo.toml exists
83
84    if !Path::new("Cargo.toml").exists() {
85        return Err("Cargo.toml not found. Are you in a Rust project directory?".to_string());
86    }
87
88    let mode = if args.release {
89        "codegen"
90    } else {
91        "interpreted"
92    };
93
94    if args.verbose {
95        eprintln!("Running in {} mode...", mode);
96        eprintln!(
97            "Profile: {}",
98            if args.release { "release" } else { "debug" }
99        );
100    }
101
102    // For release mode, we need to build first then run
103    if args.release {
104        // Check build.rs exists for codegen mode
105        // If package is specified, also check in examples/ directory
106        let build_rs_exists = Path::new("build.rs").exists()
107            || args
108                .package
109                .as_ref()
110                .is_some_and(|pkg| Path::new("examples").join(pkg).join("build.rs").exists());
111
112        if !build_rs_exists {
113            return Err(
114                "build.rs not found. Codegen mode requires build.rs for code generation.\n\
115                 Tip: Use 'dampen run' (without --release) for interpreted mode,\n\
116                 or ensure you're in the correct project directory."
117                    .to_string(),
118            );
119        }
120
121        // Build in release mode with codegen
122        let mut build_cmd = Command::new("cargo");
123        build_cmd.arg("build");
124
125        if let Some(ref package) = args.package {
126            build_cmd.arg("-p").arg(package);
127        }
128
129        if args.verbose {
130            build_cmd.arg("--verbose");
131        }
132
133        // Release mode: codegen with optimizations
134        build_cmd.arg("--release");
135        build_cmd.arg("--no-default-features");
136
137        let mut features = vec!["codegen".to_string()];
138        features.extend(args.features.clone());
139
140        build_cmd.arg("--features").arg(features.join(","));
141
142        if args.verbose {
143            let features_str = features.join(",");
144            eprintln!(
145                "Building: cargo build --release --no-default-features --features {}",
146                features_str
147            );
148        }
149
150        let build_status = build_cmd
151            .status()
152            .map_err(|e| format!("Failed to execute cargo build: {}", e))?;
153
154        if !build_status.success() {
155            return Err("Build failed".to_string());
156        }
157
158        if args.verbose {
159            eprintln!("Build successful! Now running application...");
160        }
161
162        // Run the built binary
163        let mut run_cmd = Command::new("cargo");
164        run_cmd.arg("run");
165
166        if let Some(ref package) = args.package {
167            run_cmd.arg("-p").arg(package);
168        }
169
170        if args.verbose {
171            run_cmd.arg("--verbose");
172        }
173
174        // In release mode, run release binary
175        run_cmd.arg("--release");
176
177        // Add application arguments if provided
178        if !args.app_args.is_empty() {
179            run_cmd.arg("--");
180            run_cmd.args(&args.app_args);
181        }
182
183        if args.verbose {
184            eprintln!("Running: cargo run --release");
185            if !args.app_args.is_empty() {
186                eprintln!("Application args: {:?}", args.app_args);
187            }
188        }
189
190        let run_status = run_cmd
191            .status()
192            .map_err(|e| format!("Failed to execute cargo run: {}", e))?;
193
194        if !run_status.success() {
195            return Err("Run command failed".to_string());
196        }
197
198        Ok(())
199    } else {
200        // Debug mode: use interpreted mode
201        let mut cmd = Command::new("cargo");
202        cmd.arg("run");
203
204        if let Some(ref package) = args.package {
205            cmd.arg("-p").arg(package);
206        }
207
208        if args.verbose {
209            cmd.arg("--verbose");
210        }
211
212        // Debug mode: use interpreted
213        let mut features = vec!["interpreted".to_string()];
214        features.extend(args.features.clone());
215
216        cmd.arg("--features").arg(features.join(","));
217
218        // Add application arguments if provided
219        if !args.app_args.is_empty() {
220            cmd.arg("--");
221            cmd.args(&args.app_args);
222        }
223
224        // Execute cargo run
225        if args.verbose {
226            let features_str = features.join(",");
227            eprintln!("Executing: cargo run --features {}", features_str);
228            if !args.app_args.is_empty() {
229                eprintln!("Application args: {:?}", args.app_args);
230            }
231        }
232
233        let status = cmd
234            .status()
235            .map_err(|e| format!("Failed to execute cargo: {}", e))?;
236
237        if !status.success() {
238            return Err("Run command failed".to_string());
239        }
240
241        Ok(())
242    }
243}