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(¤t_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}