source-map-tauri 0.3.0

Static Tauri app scanner that emits Meilisearch-ready NDJSON
Documentation
use std::path::{Path, PathBuf};

use anyhow::Result;
use serde::Serialize;
use walkdir::{DirEntry, WalkDir};

use crate::config::{normalize_path, ResolvedConfig};

#[derive(Debug, Clone, Default)]
pub struct RepoDiscovery {
    pub frontend_files: Vec<PathBuf>,
    pub frontend_test_files: Vec<PathBuf>,
    pub rust_files: Vec<PathBuf>,
    pub rust_test_files: Vec<PathBuf>,
    pub guest_js_files: Vec<PathBuf>,
    pub plugin_rust_files: Vec<PathBuf>,
    pub tauri_configs: Vec<PathBuf>,
    pub capability_files: Vec<PathBuf>,
    pub permission_files: Vec<PathBuf>,
    pub package_json: Option<PathBuf>,
    pub tsconfig: Option<PathBuf>,
    pub vite_configs: Vec<PathBuf>,
}

#[derive(Debug, Clone, Serialize)]
pub struct DoctorReport {
    pub root: String,
    pub repo: String,
    pub src_tauri_exists: bool,
    pub cargo_toml_exists: bool,
    pub tauri_config_exists: bool,
    pub package_json_exists: bool,
    pub frontend_files_found: usize,
    pub capability_files_found: usize,
    pub permission_files_found: usize,
    pub plugin_roots_found: usize,
    pub sourcemap_support_hint: String,
}

fn has_segment(path: &str, segment: &str) -> bool {
    path.starts_with(&format!("{segment}/")) || path.contains(&format!("/{segment}/"))
}

fn is_ignored_dir(entry: &DirEntry, config: &ResolvedConfig) -> bool {
    if !entry.file_type().is_dir() {
        return false;
    }
    let name = entry.file_name().to_string_lossy();
    match name.as_ref() {
        ".git" => true,
        "node_modules" => !config.file.scan.include_node_modules,
        "target" => !config.file.scan.include_target,
        "dist" | "build" => !config.file.scan.include_dist,
        "vendor" => !config.file.scan.include_vendor,
        "coverage" | "logs" | "tmp" | "storage" => true,
        _ => false,
    }
}

pub fn discover(config: &ResolvedConfig) -> Result<RepoDiscovery> {
    let mut discovery = RepoDiscovery::default();
    let walker = WalkDir::new(&config.root)
        .into_iter()
        .filter_entry(|entry| !is_ignored_dir(entry, config));

    for item in walker.flatten().filter(|entry| entry.file_type().is_file()) {
        let path = item.path().to_path_buf();
        let normalized = normalize_path(&config.root, &path);
        let file_name = path
            .file_name()
            .and_then(|item| item.to_str())
            .unwrap_or_default();

        if normalized == "package.json" {
            discovery.package_json = Some(path.clone());
        }
        if normalized == "tsconfig.json" {
            discovery.tsconfig = Some(path.clone());
        }
        if file_name == "vite.config.ts" || file_name == "vite.config.js" {
            discovery.vite_configs.push(path.clone());
        }
        if normalized.ends_with("src-tauri/tauri.conf.json")
            || (normalized.contains("src-tauri/tauri.") && normalized.ends_with(".conf.json"))
        {
            discovery.tauri_configs.push(path.clone());
        }
        if has_segment(&normalized, "capabilities")
            && (normalized.ends_with(".json") || normalized.ends_with(".toml"))
        {
            discovery.capability_files.push(path.clone());
        }
        if has_segment(&normalized, "permissions")
            && (normalized.ends_with(".json") || normalized.ends_with(".toml"))
        {
            discovery.permission_files.push(path.clone());
        }

        let is_test = normalized.contains(".test.")
            || normalized.contains(".spec.")
            || normalized.contains("__tests__/");
        if normalized.ends_with(".ts")
            || normalized.ends_with(".tsx")
            || normalized.ends_with(".js")
            || normalized.ends_with(".jsx")
        {
            if has_segment(&normalized, "guest-js") || has_segment(&normalized, "dist-js") {
                discovery.guest_js_files.push(path.clone());
            } else if is_test {
                discovery.frontend_test_files.push(path.clone());
            } else if has_segment(&normalized, "src") {
                discovery.frontend_files.push(path.clone());
            }
        }

        if normalized.ends_with(".rs") {
            if has_segment(&normalized, "plugins") && has_segment(&normalized, "src") {
                discovery.plugin_rust_files.push(path.clone());
            } else if normalized.contains("/tests/") {
                discovery.rust_test_files.push(path.clone());
            } else {
                discovery.rust_files.push(path.clone());
            }
        }
    }

    Ok(discovery)
}

pub fn doctor(config: &ResolvedConfig) -> Result<DoctorReport> {
    let discovery = discover(config)?;
    let src_tauri = config.root.join("src-tauri");
    let plugin_root_count = config
        .file
        .project
        .plugin_roots
        .iter()
        .filter(|item| config.root.join(item).exists())
        .count();

    Ok(DoctorReport {
        root: normalize_path(Path::new("."), &config.root),
        repo: config.repo.clone(),
        src_tauri_exists: src_tauri.exists(),
        cargo_toml_exists: src_tauri.join("Cargo.toml").exists(),
        tauri_config_exists: !discovery.tauri_configs.is_empty(),
        package_json_exists: discovery.package_json.is_some(),
        frontend_files_found: discovery.frontend_files.len(),
        capability_files_found: discovery.capability_files.len(),
        permission_files_found: discovery.permission_files.len(),
        plugin_roots_found: plugin_root_count,
        sourcemap_support_hint: if discovery.vite_configs.is_empty() {
            "vite config not found".to_owned()
        } else {
            "vite config found; enable build.sourcemap for trace support".to_owned()
        },
    })
}