Skip to main content

runx_cli/
dev.rs

1use std::env;
2use std::path::{Path, PathBuf};
3use std::process::ExitCode;
4
5use runx_runtime::dev::DevLane;
6use runx_runtime::{DevLoopOptions, DevReport, DevReportStatus, render_dev_result, run_dev_once};
7
8use crate::router::DevPlan;
9
10pub fn run_native_dev(plan: DevPlan) -> ExitCode {
11    let current_dir = match env::current_dir() {
12        Ok(path) => path,
13        Err(error) => {
14            let _ignored =
15                crate::cli_io::write_stderr(&format!("runx: failed to resolve cwd: {error}\n"));
16            return ExitCode::from(1);
17        }
18    };
19    let root = resolve_root(&current_dir);
20
21    let mut options = DevLoopOptions::new(&root);
22    options.unit_path = plan
23        .root
24        .as_ref()
25        .map(|path| resolve_unit_path(&root, path));
26    if let Some(lane) = &plan.lane {
27        options.lane = DevLane::from(lane.as_str());
28    }
29    let report = match run_dev_once(&options) {
30        Ok(report) => report,
31        Err(error) => {
32            let _ignored = crate::cli_io::write_stderr(&format!("runx: dev failed: {error:?}\n"));
33            return ExitCode::from(1);
34        }
35    };
36
37    let exit_code = match report.status {
38        DevReportStatus::Success => 0,
39        DevReportStatus::Skipped => 0,
40        DevReportStatus::NeedsApproval => 0,
41        DevReportStatus::Failure => 1,
42    };
43
44    let stdout = match render_dev_stdout(&report, plan.json) {
45        Ok(stdout) => stdout,
46        Err(error) => {
47            let _ignored = crate::cli_io::write_stderr(&format!(
48                "runx: failed to serialize dev report: {error}\n"
49            ));
50            return ExitCode::from(1);
51        }
52    };
53
54    let _ignored = crate::cli_io::write_stdout(&stdout);
55    ExitCode::from(exit_code)
56}
57
58fn resolve_root(current_dir: &Path) -> PathBuf {
59    env::var("RUNX_PROJECT_DIR")
60        .or_else(|_| env::var("RUNX_CWD"))
61        .map(PathBuf::from)
62        .unwrap_or_else(|_| current_dir.to_path_buf())
63}
64
65fn resolve_unit_path(root: &Path, path: &Path) -> PathBuf {
66    if path.is_absolute() {
67        path.to_path_buf()
68    } else {
69        root.join(path)
70    }
71}
72
73fn render_dev_stdout(report: &DevReport, json: bool) -> Result<String, serde_json::Error> {
74    if json {
75        serde_json::to_string_pretty(report).map(|text| format!("{text}\n"))
76    } else {
77        Ok(render_dev_result(report))
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use runx_contracts::{
84        DevReportSchema, DoctorReport, DoctorReportSchema, DoctorStatus, DoctorSummary,
85    };
86    use runx_runtime::{DevReport, DevReportStatus};
87
88    use super::render_dev_stdout;
89
90    #[test]
91    fn dev_json_stdout_is_pretty_printed_like_ts_cli() -> Result<(), serde_json::Error> {
92        let report = DevReport {
93            schema: DevReportSchema::V1,
94            status: DevReportStatus::Skipped,
95            doctor: DoctorReport {
96                schema: DoctorReportSchema::V1,
97                status: DoctorStatus::Success,
98                summary: DoctorSummary {
99                    errors: 0,
100                    warnings: 0,
101                    infos: 0,
102                },
103                diagnostics: Vec::new(),
104            },
105            fixtures: Vec::new(),
106            receipt_id: None,
107        };
108
109        let stdout = render_dev_stdout(&report, true)?;
110
111        assert!(stdout.starts_with("{\n  \"schema\": \"runx.dev.v1\""));
112        assert!(stdout.contains("\n  \"fixtures\": []\n"));
113        assert!(stdout.ends_with('\n'));
114        Ok(())
115    }
116}