systemprompt_cli/commands/web/
paths.rs1use anyhow::{Context, Result};
7use std::fs;
8use std::path::{Path, PathBuf};
9use systemprompt_config::ProfileBootstrap;
10use systemprompt_loader::ServicesRootBootstrap;
11use systemprompt_models::Profile;
12use systemprompt_models::validators::WebConfigRaw;
13
14const DEFAULT_TEMPLATES_PATH: &str = "web/templates";
15const DEFAULT_ASSETS_PATH: &str = "web/assets";
16
17#[derive(Debug)]
18pub struct WebPaths {
19 pub templates: PathBuf,
20 pub assets: PathBuf,
21}
22
23impl WebPaths {
24 pub fn resolve() -> Result<Self> {
25 let profile = ProfileBootstrap::get().context("Failed to get profile")?;
26 Self::resolve_from_profile(profile)
27 }
28
29 pub fn resolve_from_profile(profile: &Profile) -> Result<Self> {
30 let services_root = ServicesRootBootstrap::active_root_or(&profile.paths.services);
31 let web_config_path = services_root.join("web/config.yaml");
32
33 let (templates_path, assets_path) = match fs::read_to_string(&web_config_path) {
34 Ok(content) => {
35 let web_config: WebConfigRaw =
36 serde_yaml::from_str(&content).with_context(|| {
37 format!(
38 "Failed to parse web config at {}",
39 web_config_path.display()
40 )
41 })?;
42
43 match web_config.paths {
44 Some(paths) => (
45 paths
46 .templates
47 .unwrap_or_else(|| DEFAULT_TEMPLATES_PATH.to_owned()),
48 paths
49 .assets
50 .unwrap_or_else(|| DEFAULT_ASSETS_PATH.to_owned()),
51 ),
52 None => (
53 DEFAULT_TEMPLATES_PATH.to_owned(),
54 DEFAULT_ASSETS_PATH.to_owned(),
55 ),
56 }
57 },
58 Err(e) if e.kind() == std::io::ErrorKind::NotFound => (
59 DEFAULT_TEMPLATES_PATH.to_owned(),
60 DEFAULT_ASSETS_PATH.to_owned(),
61 ),
62 Err(e) => {
63 return Err(e).with_context(|| {
64 format!("Failed to read web config at {}", web_config_path.display())
65 });
66 },
67 };
68
69 let base = services_root.as_path();
70 let templates = normalize_under_services(&templates_path, base);
71 let assets = normalize_under_services(&assets_path, base);
72
73 Ok(Self { templates, assets })
74 }
75}
76
77fn normalize_under_services(raw: &str, base: &Path) -> PathBuf {
78 let candidate = Path::new(raw);
79 if candidate.is_absolute() {
80 return candidate.to_path_buf();
81 }
82 let stripped = candidate.strip_prefix("services").unwrap_or(candidate);
83 base.join(stripped)
84}