Skip to main content

the_code_graph_cli/commands/
watch.rs

1use domain::error::Result;
2use domain::use_cases::index::IndexUseCase;
3use storage::SqliteStore;
4use watch::daemon;
5
6use crate::adapters::fs::RealFileSystem;
7use crate::adapters::git::ShellGitProvider;
8use crate::adapters::parse::RayonParseProvider;
9use crate::config::load_config;
10use crate::project::{ensure_data_dir, find_project_root};
11
12use super::WatchArgs;
13
14pub fn run_watch(args: &WatchArgs) -> Result<()> {
15    let root = match &args.path {
16        Some(p) => p.clone(),
17        None => find_project_root(&std::env::current_dir().map_err(|e| {
18            domain::error::CodeGraphError::FileSystem {
19                path: ".".into(),
20                source: e,
21            }
22        })?)?,
23    };
24    let data_dir = ensure_data_dir(&root)?;
25    let config = load_config(&root)?;
26    let debounce_ms = config
27        .watch
28        .as_ref()
29        .and_then(|w| w.debounce_ms)
30        .unwrap_or(100);
31
32    if args.status {
33        return show_status(&data_dir);
34    }
35    if args.stop {
36        return daemon::stop_daemon(&data_dir);
37    }
38    if args.daemon {
39        return daemon::start_daemon(&root, &data_dir);
40    }
41
42    // Build adapters
43    let db_path = data_dir.join("graph.db");
44    let store = SqliteStore::open(&db_path)
45        .map_err(|e| domain::error::CodeGraphError::Storage(format!("{e}")))?;
46    let fs = RealFileSystem;
47    let git = ShellGitProvider::new(root.clone());
48    let parser = RayonParseProvider::new();
49    let use_case = IndexUseCase::new(store, parser, fs, git);
50
51    if args.daemon_internal {
52        return daemon::run_daemon(use_case, &root, &data_dir, debounce_ms);
53    }
54
55    // Default: foreground mode
56    daemon::run_foreground(use_case, &root, &data_dir, debounce_ms)
57}
58
59fn show_status(data_dir: &std::path::Path) -> Result<()> {
60    match daemon::daemon_status(data_dir) {
61        daemon::DaemonStatus::Running(pid) => {
62            eprintln!("Daemon running (PID {pid})");
63        }
64        daemon::DaemonStatus::Stopped => {
65            eprintln!("Daemon stopped");
66        }
67    }
68    Ok(())
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn watch_status_when_not_running() {
77        let tmp = tempfile::tempdir().unwrap();
78        let root = tmp.path();
79        std::fs::create_dir(root.join(".git")).unwrap();
80        std::fs::create_dir_all(root.join(".code-graph")).unwrap();
81
82        let args = WatchArgs {
83            daemon: false,
84            status: true,
85            stop: false,
86            daemon_internal: false,
87            path: Some(root.to_path_buf()),
88        };
89        let result = run_watch(&args);
90        assert!(result.is_ok());
91    }
92
93    #[test]
94    fn watch_stop_when_not_running() {
95        let tmp = tempfile::tempdir().unwrap();
96        let root = tmp.path();
97        std::fs::create_dir(root.join(".git")).unwrap();
98        std::fs::create_dir_all(root.join(".code-graph")).unwrap();
99
100        let args = WatchArgs {
101            daemon: false,
102            status: false,
103            stop: true,
104            daemon_internal: false,
105            path: Some(root.to_path_buf()),
106        };
107        let result = run_watch(&args);
108        assert!(result.is_ok());
109    }
110
111    #[test]
112    fn ensure_fresh_skips_when_daemon_running() {
113        let tmp = tempfile::tempdir().unwrap();
114        let data_dir = tmp.path().join(".code-graph");
115        std::fs::create_dir_all(&data_dir).unwrap();
116
117        // Write current PID to simulate running daemon
118        watch::pid::write_pid(&data_dir, std::process::id()).unwrap();
119
120        let store = domain::test_support::InMemoryGraphStore::new();
121        let parser = domain::test_support::MockParseProvider::new(vec![]);
122        let fs = domain::test_support::MockFileSystem::new(vec![]);
123        let git =
124            domain::test_support::MockGitProvider::with_modified(vec![std::path::PathBuf::from(
125                "src/a.ts",
126            )]);
127
128        let result = watch::freshness::ensure_fresh(
129            &store,
130            &parser,
131            &fs,
132            &git,
133            std::path::Path::new("/project"),
134            &data_dir,
135        );
136        assert!(result.is_ok());
137    }
138}