source_map_tauri/
discovery.rs1use std::path::{Path, PathBuf};
2
3use anyhow::Result;
4use serde::Serialize;
5use walkdir::{DirEntry, WalkDir};
6
7use crate::config::{normalize_path, ResolvedConfig};
8
9#[derive(Debug, Clone, Default)]
10pub struct RepoDiscovery {
11 pub frontend_files: Vec<PathBuf>,
12 pub frontend_test_files: Vec<PathBuf>,
13 pub rust_files: Vec<PathBuf>,
14 pub rust_test_files: Vec<PathBuf>,
15 pub guest_js_files: Vec<PathBuf>,
16 pub plugin_rust_files: Vec<PathBuf>,
17 pub tauri_configs: Vec<PathBuf>,
18 pub capability_files: Vec<PathBuf>,
19 pub permission_files: Vec<PathBuf>,
20 pub package_json: Option<PathBuf>,
21 pub tsconfig: Option<PathBuf>,
22 pub vite_configs: Vec<PathBuf>,
23}
24
25#[derive(Debug, Clone, Serialize)]
26pub struct DoctorReport {
27 pub root: String,
28 pub repo: String,
29 pub src_tauri_exists: bool,
30 pub cargo_toml_exists: bool,
31 pub tauri_config_exists: bool,
32 pub package_json_exists: bool,
33 pub frontend_files_found: usize,
34 pub capability_files_found: usize,
35 pub permission_files_found: usize,
36 pub plugin_roots_found: usize,
37 pub sourcemap_support_hint: String,
38}
39
40fn has_segment(path: &str, segment: &str) -> bool {
41 path.starts_with(&format!("{segment}/")) || path.contains(&format!("/{segment}/"))
42}
43
44fn is_ignored_dir(entry: &DirEntry, config: &ResolvedConfig) -> bool {
45 if !entry.file_type().is_dir() {
46 return false;
47 }
48 let name = entry.file_name().to_string_lossy();
49 match name.as_ref() {
50 ".git" => true,
51 "node_modules" => !config.file.scan.include_node_modules,
52 "target" => !config.file.scan.include_target,
53 "dist" | "build" => !config.file.scan.include_dist,
54 "vendor" => !config.file.scan.include_vendor,
55 "coverage" | "logs" | "tmp" | "storage" => true,
56 _ => false,
57 }
58}
59
60pub fn discover(config: &ResolvedConfig) -> Result<RepoDiscovery> {
61 let mut discovery = RepoDiscovery::default();
62 let walker = WalkDir::new(&config.root)
63 .into_iter()
64 .filter_entry(|entry| !is_ignored_dir(entry, config));
65
66 for item in walker.flatten().filter(|entry| entry.file_type().is_file()) {
67 let path = item.path().to_path_buf();
68 let normalized = normalize_path(&config.root, &path);
69 let file_name = path
70 .file_name()
71 .and_then(|item| item.to_str())
72 .unwrap_or_default();
73
74 if normalized == "package.json" {
75 discovery.package_json = Some(path.clone());
76 }
77 if normalized == "tsconfig.json" {
78 discovery.tsconfig = Some(path.clone());
79 }
80 if file_name == "vite.config.ts" || file_name == "vite.config.js" {
81 discovery.vite_configs.push(path.clone());
82 }
83 if normalized.ends_with("src-tauri/tauri.conf.json")
84 || (normalized.contains("src-tauri/tauri.") && normalized.ends_with(".conf.json"))
85 {
86 discovery.tauri_configs.push(path.clone());
87 }
88 if has_segment(&normalized, "capabilities")
89 && (normalized.ends_with(".json") || normalized.ends_with(".toml"))
90 {
91 discovery.capability_files.push(path.clone());
92 }
93 if has_segment(&normalized, "permissions")
94 && (normalized.ends_with(".json") || normalized.ends_with(".toml"))
95 {
96 discovery.permission_files.push(path.clone());
97 }
98
99 let is_test = normalized.contains(".test.")
100 || normalized.contains(".spec.")
101 || normalized.contains("__tests__/");
102 if normalized.ends_with(".ts")
103 || normalized.ends_with(".tsx")
104 || normalized.ends_with(".js")
105 || normalized.ends_with(".jsx")
106 {
107 if has_segment(&normalized, "guest-js") || has_segment(&normalized, "dist-js") {
108 discovery.guest_js_files.push(path.clone());
109 } else if is_test {
110 discovery.frontend_test_files.push(path.clone());
111 } else if has_segment(&normalized, "src") {
112 discovery.frontend_files.push(path.clone());
113 }
114 }
115
116 if normalized.ends_with(".rs") {
117 if has_segment(&normalized, "plugins") && has_segment(&normalized, "src") {
118 discovery.plugin_rust_files.push(path.clone());
119 } else if normalized.contains("/tests/") {
120 discovery.rust_test_files.push(path.clone());
121 } else {
122 discovery.rust_files.push(path.clone());
123 }
124 }
125 }
126
127 Ok(discovery)
128}
129
130pub fn doctor(config: &ResolvedConfig) -> Result<DoctorReport> {
131 let discovery = discover(config)?;
132 let src_tauri = config.root.join("src-tauri");
133 let plugin_root_count = config
134 .file
135 .project
136 .plugin_roots
137 .iter()
138 .filter(|item| config.root.join(item).exists())
139 .count();
140
141 Ok(DoctorReport {
142 root: normalize_path(Path::new("."), &config.root),
143 repo: config.repo.clone(),
144 src_tauri_exists: src_tauri.exists(),
145 cargo_toml_exists: src_tauri.join("Cargo.toml").exists(),
146 tauri_config_exists: !discovery.tauri_configs.is_empty(),
147 package_json_exists: discovery.package_json.is_some(),
148 frontend_files_found: discovery.frontend_files.len(),
149 capability_files_found: discovery.capability_files.len(),
150 permission_files_found: discovery.permission_files.len(),
151 plugin_roots_found: plugin_root_count,
152 sourcemap_support_hint: if discovery.vite_configs.is_empty() {
153 "vite config not found".to_owned()
154 } else {
155 "vite config found; enable build.sourcemap for trace support".to_owned()
156 },
157 })
158}