the_code_graph_cli/commands/
watch.rs1use 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 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 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 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}