From dc92297be759d5bfa79b84fd407a5dea7828e87a Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Wed, 16 Jul 2025 22:12:32 -0600 Subject: [PATCH 01/19] feat: allow Watch Mode with --root-turbo-json --- crates/turborepo-lib/src/cli/mod.rs | 14 +- crates/turborepo-lib/src/commands/daemon.rs | 30 +++- crates/turborepo-lib/src/commands/info.rs | 7 +- crates/turborepo-lib/src/daemon/connector.rs | 132 +++++++++++++++--- crates/turborepo-lib/src/daemon/server.rs | 26 +++- crates/turborepo-lib/src/diagnostics.rs | 1 + .../src/package_changes_watcher.rs | 73 +++++++++- crates/turborepo-lib/src/run/builder.rs | 8 +- crates/turborepo-lib/src/run/watch.rs | 27 ++-- crates/turborepo-lsp/src/lib.rs | 8 +- 10 files changed, 280 insertions(+), 46 deletions(-) diff --git a/crates/turborepo-lib/src/cli/mod.rs b/crates/turborepo-lib/src/cli/mod.rs index 81339812a1059..ff0798afff034 100644 --- a/crates/turborepo-lib/src/cli/mod.rs +++ b/crates/turborepo-lib/src/cli/mod.rs @@ -611,6 +611,9 @@ pub enum Command { /// Set the idle timeout for turbod #[clap(long, default_value_t = String::from("4h0m0s"))] idle_time: String, + /// Path to a custom turbo.json file to watch + #[clap(long)] + turbo_json_path: Option, #[clap(subcommand)] command: Option, }, @@ -1428,7 +1431,11 @@ pub async fn run( Ok(clone::run(cwd, url, dir.as_deref(), *ci, *local, *depth)?) } #[allow(unused_variables)] - Command::Daemon { command, idle_time } => { + Command::Daemon { + command, + idle_time, + turbo_json_path, + } => { let event = CommandEventBuilder::new("daemon").with_parent(&root_telemetry); event.track_call(); let base = CommandBase::new(cli_args.clone(), repo_root, version, color_config)?; @@ -1436,7 +1443,10 @@ pub async fn run( match command { Some(command) => daemon::daemon_client(command, &base).await, - None => daemon::daemon_server(&base, idle_time, logger).await, + None => { + daemon::daemon_server(&base, idle_time, turbo_json_path.as_deref(), logger) + .await + } }?; Ok(0) diff --git a/crates/turborepo-lib/src/commands/daemon.rs b/crates/turborepo-lib/src/commands/daemon.rs index ce1ac1030b43f..757fe5d617694 100644 --- a/crates/turborepo-lib/src/commands/daemon.rs +++ b/crates/turborepo-lib/src/commands/daemon.rs @@ -33,7 +33,12 @@ pub async fn daemon_client(command: &DaemonCommand, base: &CommandBase) -> Resul DaemonCommand::Clean { .. } => (false, true), }; - let connector = DaemonConnector::new(can_start_server, can_kill_server, &base.repo_root); + let connector = DaemonConnector { + can_start_server, + can_kill_server, + paths: crate::daemon::Paths::from_repo_root(&base.repo_root), + custom_turbo_json_path: None, + }; match command { DaemonCommand::Restart => { @@ -275,6 +280,7 @@ fn log_filename(base_filename: &str) -> Result { pub async fn daemon_server( base: &CommandBase, idle_time: &String, + turbo_json_path: Option<&camino::Utf8Path>, logging: &TurboSubscriber, ) -> Result<(), DaemonError> { let paths = Paths::from_repo_root(&base.repo_root); @@ -298,8 +304,26 @@ pub async fn daemon_server( } CloseReason::Interrupt }); - let server = - crate::daemon::TurboGrpcService::new(base.repo_root.clone(), paths, timeout, exit_signal); + let custom_turbo_json_path = match turbo_json_path { + Some(p) => match turbopath::AbsoluteSystemPathBuf::from_cwd(p) { + Ok(path) => Some(path), + Err(e) => { + tracing::error!("Failed to convert custom turbo.json path: {}", e); + return Err(DaemonError::Unavailable(format!( + "Invalid turbo.json path: {}", + e + ))); + } + }, + None => None, + }; + let server = crate::daemon::TurboGrpcService::new( + base.repo_root.clone(), + paths, + timeout, + exit_signal, + custom_turbo_json_path, + ); let reason = server.serve().await?; diff --git a/crates/turborepo-lib/src/commands/info.rs b/crates/turborepo-lib/src/commands/info.rs index 394ac1b65a518..659decc5a7d75 100644 --- a/crates/turborepo-lib/src/commands/info.rs +++ b/crates/turborepo-lib/src/commands/info.rs @@ -20,7 +20,12 @@ fn is_wsl() -> bool { pub async fn run(base: CommandBase) { let system = System::new_all(); - let connector = DaemonConnector::new(false, false, &base.repo_root); + let connector = DaemonConnector { + can_start_server: false, + can_kill_server: false, + paths: crate::daemon::Paths::from_repo_root(&base.repo_root), + custom_turbo_json_path: None, + }; let daemon_status = match connector.connect().await { Ok(_status) => "Running", Err(DaemonConnectorError::NotRunning) => "Not running", diff --git a/crates/turborepo-lib/src/daemon/connector.rs b/crates/turborepo-lib/src/daemon/connector.rs index a5c941b7a2239..2958f506badd2 100644 --- a/crates/turborepo-lib/src/daemon/connector.rs +++ b/crates/turborepo-lib/src/daemon/connector.rs @@ -29,6 +29,9 @@ pub enum DaemonConnectorError { /// The daemon is not running and will not be started. #[error("daemon is not running")] NotRunning, + /// Cannot kill daemon (insufficient permissions). + #[error("cannot kill daemon")] + CannotKill, /// There was an issue connecting to the socket. #[error("unable to connect to socket: {0}")] Socket(#[from] tonic::transport::Error), @@ -66,6 +69,8 @@ pub struct DaemonConnector { /// in the event of a version mismatch). pub can_kill_server: bool, pub paths: Paths, + /// Optional custom turbo.json path to watch + pub custom_turbo_json_path: Option, } impl DaemonConnector { @@ -79,9 +84,15 @@ impl DaemonConnector { can_start_server, can_kill_server, paths, + custom_turbo_json_path: None, } } + pub fn with_custom_turbo_json_path(mut self, path: turbopath::AbsoluteSystemPathBuf) -> Self { + self.custom_turbo_json_path = Some(path); + self + } + const CONNECT_RETRY_MAX: usize = 3; const CONNECT_TIMEOUT: Duration = Duration::from_secs(1); const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(1); @@ -139,10 +150,8 @@ impl DaemonConnector { )) } - /// Gets the PID of the daemon process. - /// - /// If a daemon is not running, it starts one. - async fn get_or_start_daemon(&self) -> Result { + /// Check if a daemon is currently running and return its PID + async fn existing_daemon(&self) -> Result { debug!("looking for pid in lockfile: {:?}", self.paths.pid_file); let pidfile = self.pid_lock(); @@ -152,23 +161,73 @@ impl DaemonConnector { debug!("found pid: {}", pid); Ok(sysinfo::Pid::from(pid as usize)) } - None if self.can_start_server => { - debug!("no pid found, starting daemon"); - Self::start_daemon().await - } None => Err(DaemonConnectorError::NotRunning), } } + /// Kill an existing daemon + async fn kill_daemon(&self) -> Result<(), DaemonConnectorError> { + if !self.can_kill_server { + return Err(DaemonConnectorError::CannotKill); + } + + let pid = self.existing_daemon().await?; + debug!("killing existing daemon with pid: {}", pid); + + // Try to connect and shut down gracefully first + match self.get_connection(self.paths.sock_file.clone()).await { + Ok(conn) => { + let client = DaemonClient::new(conn); + self.kill_live_server(client, pid).await + } + Err(_) => { + // If we can't connect, kill it forcefully + self.kill_dead_server(pid).await + } + } + } + + /// Gets or starts a daemon process, returning its PID. + /// If a daemon is not running, it starts one. + async fn get_or_start_daemon(&self) -> Result { + let existing_pid = self.existing_daemon().await; + + match (existing_pid, &self.custom_turbo_json_path) { + // If daemon is running but we have a custom turbo.json path, + // we need to restart to ensure the daemon has the correct configuration + (Ok(pid), Some(_)) => { + tracing::info!( + "Found existing daemon (pid: {}) but custom turbo.json path specified. \ + Restarting daemon to ensure correct configuration.", + pid + ); + self.kill_daemon().await?; + Self::start_daemon(self.custom_turbo_json_path.as_ref()).await + } + // If daemon is running and no custom path, use existing daemon + (Ok(pid), None) => Ok(pid), + // If daemon is not running, start it (with or without custom path) + (Err(_), _) => Self::start_daemon(self.custom_turbo_json_path.as_ref()).await, + } + } + /// Starts the daemon process, returning its PID. - async fn start_daemon() -> Result { + async fn start_daemon( + custom_turbo_json_path: Option<&turbopath::AbsoluteSystemPathBuf>, + ) -> Result { let binary_path = std::env::current_exe().map_err(|e| DaemonConnectorError::Fork(e.into()))?; // this creates a new process group for the given command // in a cross platform way, directing all output to /dev/null - let mut group = tokio::process::Command::new(binary_path) - .arg("--skip-infer") - .arg("daemon") + let mut command = tokio::process::Command::new(binary_path); + command.arg("--skip-infer").arg("daemon"); + + // Pass custom turbo.json path if specified + if let Some(path) = custom_turbo_json_path { + command.arg("--turbo-json-path").arg(path.as_str()); + } + + let mut group = command .stderr(Stdio::null()) .stdout(Stdio::null()) .group() @@ -437,7 +496,12 @@ mod test { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root); + let connector = DaemonConnector { + can_start_server: false, + can_kill_server: false, + paths: Paths::from_repo_root(&repo_root), + custom_turbo_json_path: None, + }; connector.paths.pid_file.ensure_dir().unwrap(); connector .paths @@ -455,7 +519,12 @@ mod test { async fn handles_missing_server_connect() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root); + let connector = DaemonConnector { + can_start_server: false, + can_kill_server: false, + paths: Paths::from_repo_root(&repo_root), + custom_turbo_json_path: None, + }; assert_matches!( connector.connect().await, @@ -467,7 +536,12 @@ mod test { async fn handles_kill_dead_server_missing_pid() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root); + let connector = DaemonConnector { + can_start_server: false, + can_kill_server: false, + paths: Paths::from_repo_root(&repo_root), + custom_turbo_json_path: None, + }; assert_matches!( connector.kill_dead_server(Pid::from(usize::MAX)).await, @@ -479,7 +553,12 @@ mod test { async fn handles_kill_dead_server_missing_process() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root); + let connector = DaemonConnector { + can_start_server: false, + can_kill_server: false, + paths: Paths::from_repo_root(&repo_root), + custom_turbo_json_path: None, + }; connector.paths.pid_file.ensure_dir().unwrap(); connector @@ -505,7 +584,12 @@ mod test { async fn handles_kill_dead_server_wrong_process() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root); + let connector = DaemonConnector { + can_start_server: false, + can_kill_server: false, + paths: Paths::from_repo_root(&repo_root), + custom_turbo_json_path: None, + }; let proc = tokio::process::Command::new(NODE_EXE) .stdout(Stdio::null()) @@ -542,7 +626,12 @@ mod test { async fn handles_kill_dead_server() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, true, &repo_root); + let connector = DaemonConnector { + can_start_server: false, + can_kill_server: true, + paths: Paths::from_repo_root(&repo_root), + custom_turbo_json_path: None, + }; let proc = tokio::process::Command::new(NODE_EXE) .stdout(Stdio::null()) @@ -682,7 +771,12 @@ mod test { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root); + let connector = DaemonConnector { + can_start_server: false, + can_kill_server: false, + paths: Paths::from_repo_root(&repo_root), + custom_turbo_json_path: None, + }; let mut client = Endpoint::try_from("http://[::]:50051") .expect("this is a valid uri") diff --git a/crates/turborepo-lib/src/daemon/server.rs b/crates/turborepo-lib/src/daemon/server.rs index 923d31a797592..9c68bc70fef77 100644 --- a/crates/turborepo-lib/src/daemon/server.rs +++ b/crates/turborepo-lib/src/daemon/server.rs @@ -67,6 +67,7 @@ pub struct FileWatching { pub package_watcher: Arc, pub package_changes_watcher: OnceLock>, pub hash_watcher: Arc, + custom_turbo_json_path: Option, } #[derive(Debug, Error)] @@ -111,7 +112,10 @@ impl FileWatching { /// waiting for the filewatcher to be ready. Using `OptionalWatch`, /// dependent services can wait for resources they need to become /// available, and the server can start up without waiting for them. - pub fn new(repo_root: AbsoluteSystemPathBuf) -> Result { + pub fn new( + repo_root: AbsoluteSystemPathBuf, + custom_turbo_json_path: Option, + ) -> Result { let watcher = Arc::new(FileSystemWatcher::new_with_default_cookie_dir(&repo_root)?); let recv = watcher.watch(); @@ -144,6 +148,7 @@ impl FileWatching { package_watcher, package_changes_watcher: OnceLock::new(), hash_watcher, + custom_turbo_json_path, }) } @@ -155,6 +160,7 @@ impl FileWatching { self.repo_root.clone(), recv, self.hash_watcher.clone(), + self.custom_turbo_json_path.clone(), )) }) .clone() @@ -169,6 +175,7 @@ pub struct TurboGrpcService { paths: Paths, timeout: Duration, external_shutdown: S, + custom_turbo_json_path: Option, } impl TurboGrpcService @@ -186,6 +193,7 @@ where paths: Paths, timeout: Duration, external_shutdown: S, + custom_turbo_json_path: Option, ) -> Self { // Run the actual service. It takes ownership of the struct given to it, // so we use a private struct with just the pieces of state needed to handle @@ -195,6 +203,7 @@ where paths, timeout, external_shutdown, + custom_turbo_json_path, } } @@ -204,6 +213,7 @@ where paths, repo_root, timeout, + custom_turbo_json_path, } = self; // A channel to trigger the shutdown of the gRPC server. This is handed out @@ -211,8 +221,12 @@ where // well as available to the gRPC server itself to handle the shutdown RPC. let (trigger_shutdown, mut shutdown_signal) = mpsc::channel::<()>(1); - let (service, exit_root_watch, watch_root_handle) = - TurboGrpcServiceInner::new(repo_root.clone(), trigger_shutdown, paths.log_file); + let (service, exit_root_watch, watch_root_handle) = TurboGrpcServiceInner::new( + repo_root.clone(), + trigger_shutdown, + paths.log_file, + custom_turbo_json_path, + ); let running = Arc::new(AtomicBool::new(true)); let (_pid_lock, stream) = @@ -290,12 +304,13 @@ impl TurboGrpcServiceInner { repo_root: AbsoluteSystemPathBuf, trigger_shutdown: mpsc::Sender<()>, log_file: AbsoluteSystemPathBuf, + custom_turbo_json_path: Option, ) -> ( Self, oneshot::Sender<()>, JoinHandle>, ) { - let file_watching = FileWatching::new(repo_root.clone()).unwrap(); + let file_watching = FileWatching::new(repo_root.clone(), custom_turbo_json_path).unwrap(); tracing::debug!("initing package discovery"); // Note that we're cloning the Arc, not the package watcher itself @@ -778,6 +793,7 @@ mod test { paths.clone(), Duration::from_secs(60 * 60), exit_signal, + None, ); // the package watcher reads data from the package.json file @@ -835,6 +851,7 @@ mod test { paths.clone(), Duration::from_millis(10), exit_signal, + None, ); // the package watcher reads data from the package.json file @@ -892,6 +909,7 @@ mod test { paths, Duration::from_secs(60 * 60), exit_signal, + None, ); let handle = tokio::task::spawn(server.serve()); diff --git a/crates/turborepo-lib/src/diagnostics.rs b/crates/turborepo-lib/src/diagnostics.rs index 3eb4fd32138b6..f7e8661821239 100644 --- a/crates/turborepo-lib/src/diagnostics.rs +++ b/crates/turborepo-lib/src/diagnostics.rs @@ -317,6 +317,7 @@ impl Diagnostic for DaemonDiagnostic { can_kill_server: false, can_start_server: true, paths, + custom_turbo_json_path: None, }; let mut client = match connector.connect().await { diff --git a/crates/turborepo-lib/src/package_changes_watcher.rs b/crates/turborepo-lib/src/package_changes_watcher.rs index 9ae12381fbc19..4bca44dd2c74b 100644 --- a/crates/turborepo-lib/src/package_changes_watcher.rs +++ b/crates/turborepo-lib/src/package_changes_watcher.rs @@ -47,6 +47,7 @@ impl PackageChangesWatcher { repo_root: AbsoluteSystemPathBuf, file_events_lazy: OptionalWatch>>, hash_watcher: Arc, + custom_turbo_json_path: Option, ) -> Self { let (exit_tx, exit_rx) = oneshot::channel(); let (package_change_events_tx, package_change_events_rx) = @@ -56,6 +57,7 @@ impl PackageChangesWatcher { file_events_lazy, package_change_events_tx, hash_watcher, + custom_turbo_json_path, ); let _handle = tokio::spawn(subscriber.watch(exit_rx)); @@ -98,6 +100,7 @@ struct Subscriber { repo_root: AbsoluteSystemPathBuf, package_change_events_tx: broadcast::Sender, hash_watcher: Arc, + custom_turbo_json_path: Option, } // This is a workaround because `ignore` doesn't match against a path's @@ -146,13 +149,37 @@ impl Subscriber { file_events_lazy: OptionalWatch>>, package_change_events_tx: broadcast::Sender, hash_watcher: Arc, + custom_turbo_json_path: Option, ) -> Self { + // Try to canonicalize the custom path to match what the file watcher reports + let normalized_custom_path = + custom_turbo_json_path.and_then(|path| match path.to_realpath() { + Ok(real_path) => { + tracing::info!( + "PackageChangesWatcher: monitoring custom turbo.json at: {} \ + (canonicalized: {})", + path, + real_path + ); + Some(real_path) + } + Err(e) => { + tracing::warn!( + "Failed to canonicalize custom turbo.json path {}: {}, using original path", + path, + e + ); + Some(path) + } + }); + Subscriber { repo_root, file_events_lazy, changed_files: Default::default(), package_change_events_tx, hash_watcher, + custom_turbo_json_path: normalized_custom_path, } } @@ -260,8 +287,20 @@ impl Subscriber { self.changed_files.lock().await.borrow_mut().deref_mut() { for path in paths { - if let Some(path) = path.to_str() { - trie.insert(path.to_string(), ()); + if let Some(path_str) = path.to_str() { + // Log file changes for custom turbo.json debugging + if let Some(ref custom_path) = self.custom_turbo_json_path { + if path_str == custom_path.as_str() + || path_str.ends_with("turbo.json") + || path_str.ends_with("turbo.jsonc") + { + tracing::info!( + "File watcher detected change: {}", + path_str + ); + } + } + trie.insert(path_str.to_string(), ()); } } } @@ -353,9 +392,33 @@ impl Subscriber { let turbo_json_path = self.repo_root.join_component(CONFIG_FILE); let turbo_jsonc_path = self.repo_root.join_component(CONFIG_FILE_JSONC); - if trie.get(turbo_json_path.as_str()).is_some() - || trie.get(turbo_jsonc_path.as_str()).is_some() - { + let standard_config_changed = trie.get(turbo_json_path.as_str()).is_some() + || trie.get(turbo_jsonc_path.as_str()).is_some(); + + let custom_config_changed = self + .custom_turbo_json_path + .as_ref() + .map(|path| { + let path_str = path.as_str(); + let found = trie.get(path_str).is_some(); + tracing::debug!( + "Checking custom turbo.json path '{}' in trie, found: {}", + path_str, + found + ); + if !found { + // Also try with the path keys for debugging + let trie_keys: Vec<_> = trie.keys().take(10).collect(); + tracing::debug!( + "Trie contains these paths (first 10): {:?}", + trie_keys + ); + } + found + }) + .unwrap_or(false); + + if standard_config_changed || custom_config_changed { tracing::info!( "Detected change to turbo configuration file. Triggering rediscovery." ); diff --git a/crates/turborepo-lib/src/run/builder.rs b/crates/turborepo-lib/src/run/builder.rs index f41edb772eedf..5789f20dd7d41 100644 --- a/crates/turborepo-lib/src/run/builder.rs +++ b/crates/turborepo-lib/src/run/builder.rs @@ -262,8 +262,12 @@ impl RunBuilder { (_, Some(true)) | (false, None) => { let can_start_server = true; let can_kill_server = true; - let connector = - DaemonConnector::new(can_start_server, can_kill_server, &self.repo_root); + let connector = DaemonConnector { + can_start_server, + can_kill_server, + paths: crate::daemon::Paths::from_repo_root(&self.repo_root), + custom_turbo_json_path: None, + }; match (connector.connect().await, self.opts.run_opts.daemon) { (Ok(client), _) => { run_telemetry.track_daemon_init(DaemonInitStatus::Started); diff --git a/crates/turborepo-lib/src/run/watch.rs b/crates/turborepo-lib/src/run/watch.rs index ffd9baa2c73c2..2344ede1e8344 100644 --- a/crates/turborepo-lib/src/run/watch.rs +++ b/crates/turborepo-lib/src/run/watch.rs @@ -103,8 +103,7 @@ pub enum Error { UI(#[from] turborepo_ui::Error), #[error("Could not connect to UI thread: {0}")] UISend(String), - #[error("Cannot use non-standard turbo configuration at {0} with Watch Mode.")] - NonStandardTurboJsonPath(String), + #[error("Invalid config: {0}")] Config(#[from] crate::config::Error), #[error(transparent)] @@ -122,11 +121,8 @@ impl WatchClient { let standard_config_path = resolve_turbo_config_path(&base.repo_root)?; - if base.opts.repo_opts.root_turbo_json_path != standard_config_path { - return Err(Error::NonStandardTurboJsonPath( - base.opts.repo_opts.root_turbo_json_path.to_string(), - )); - } + // Note: We now support watching custom turbo.json paths, so this check is + // removed if matches!(base.opts.run_opts.daemon, Some(false)) { warn!("daemon is required for watch, ignoring request to disable daemon"); @@ -143,12 +139,27 @@ impl WatchClient { let (ui_sender, ui_handle) = run.start_ui()?.unzip(); - let connector = DaemonConnector { + let mut connector = DaemonConnector { can_start_server: true, can_kill_server: true, paths: DaemonPaths::from_repo_root(&base.repo_root), + custom_turbo_json_path: None, }; + // If using a non-standard turbo.json path, pass it to the daemon + if base.opts.repo_opts.root_turbo_json_path != standard_config_path { + tracing::info!( + "Using custom turbo.json path: {} (standard: {})", + base.opts.repo_opts.root_turbo_json_path, + standard_config_path + ); + // The root_turbo_json_path is already an AbsoluteSystemPathBuf, so use it + // directly + let custom_path = base.opts.repo_opts.root_turbo_json_path.clone(); + tracing::info!("Passing custom turbo.json path to daemon: {}", custom_path); + connector = connector.with_custom_turbo_json_path(custom_path); + } + Ok(Self { base, run, diff --git a/crates/turborepo-lsp/src/lib.rs b/crates/turborepo-lsp/src/lib.rs index 729fcec1058a3..50b39b1b6f811 100644 --- a/crates/turborepo-lsp/src/lib.rs +++ b/crates/turborepo-lsp/src/lib.rs @@ -85,8 +85,12 @@ impl LanguageServer for Backend { || { let can_start_server = true; let can_kill_server = false; - let connector = - DaemonConnector::new(can_start_server, can_kill_server, &repo_root); + let connector = DaemonConnector { + can_start_server, + can_kill_server, + paths: DaemonPaths::from_repo_root(&repo_root), + custom_turbo_json_path: None, + }; connector.connect() }, ) From 4e7ae1d891fc215c195303a04fd7ef792776c321 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Thu, 17 Jul 2025 19:56:37 -0600 Subject: [PATCH 02/19] WIP 9308f --- crates/turborepo-lib/src/daemon/connector.rs | 23 -------------------- 1 file changed, 23 deletions(-) diff --git a/crates/turborepo-lib/src/daemon/connector.rs b/crates/turborepo-lib/src/daemon/connector.rs index 2958f506badd2..ad33b9612721d 100644 --- a/crates/turborepo-lib/src/daemon/connector.rs +++ b/crates/turborepo-lib/src/daemon/connector.rs @@ -165,28 +165,6 @@ impl DaemonConnector { } } - /// Kill an existing daemon - async fn kill_daemon(&self) -> Result<(), DaemonConnectorError> { - if !self.can_kill_server { - return Err(DaemonConnectorError::CannotKill); - } - - let pid = self.existing_daemon().await?; - debug!("killing existing daemon with pid: {}", pid); - - // Try to connect and shut down gracefully first - match self.get_connection(self.paths.sock_file.clone()).await { - Ok(conn) => { - let client = DaemonClient::new(conn); - self.kill_live_server(client, pid).await - } - Err(_) => { - // If we can't connect, kill it forcefully - self.kill_dead_server(pid).await - } - } - } - /// Gets or starts a daemon process, returning its PID. /// If a daemon is not running, it starts one. async fn get_or_start_daemon(&self) -> Result { @@ -201,7 +179,6 @@ impl DaemonConnector { Restarting daemon to ensure correct configuration.", pid ); - self.kill_daemon().await?; Self::start_daemon(self.custom_turbo_json_path.as_ref()).await } // If daemon is running and no custom path, use existing daemon From 57e58911c138d0e19a846ddeb16b7da35265a94d Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Thu, 17 Jul 2025 20:03:00 -0600 Subject: [PATCH 03/19] WIP 7a526 --- crates/turborepo-lib/src/cli/mod.rs | 2 +- crates/turborepo-lib/src/daemon/connector.rs | 3 --- crates/turborepo-lib/src/package_changes_watcher.rs | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/turborepo-lib/src/cli/mod.rs b/crates/turborepo-lib/src/cli/mod.rs index ff0798afff034..93205ed9f22a1 100644 --- a/crates/turborepo-lib/src/cli/mod.rs +++ b/crates/turborepo-lib/src/cli/mod.rs @@ -611,7 +611,7 @@ pub enum Command { /// Set the idle timeout for turbod #[clap(long, default_value_t = String::from("4h0m0s"))] idle_time: String, - /// Path to a custom turbo.json file to watch + /// Path to a custom turbo.json file to watch from --root-turbo-json #[clap(long)] turbo_json_path: Option, #[clap(subcommand)] diff --git a/crates/turborepo-lib/src/daemon/connector.rs b/crates/turborepo-lib/src/daemon/connector.rs index ad33b9612721d..8a3ba4cb21be8 100644 --- a/crates/turborepo-lib/src/daemon/connector.rs +++ b/crates/turborepo-lib/src/daemon/connector.rs @@ -29,9 +29,6 @@ pub enum DaemonConnectorError { /// The daemon is not running and will not be started. #[error("daemon is not running")] NotRunning, - /// Cannot kill daemon (insufficient permissions). - #[error("cannot kill daemon")] - CannotKill, /// There was an issue connecting to the socket. #[error("unable to connect to socket: {0}")] Socket(#[from] tonic::transport::Error), diff --git a/crates/turborepo-lib/src/package_changes_watcher.rs b/crates/turborepo-lib/src/package_changes_watcher.rs index 4bca44dd2c74b..cdbd313ea16bd 100644 --- a/crates/turborepo-lib/src/package_changes_watcher.rs +++ b/crates/turborepo-lib/src/package_changes_watcher.rs @@ -288,7 +288,6 @@ impl Subscriber { { for path in paths { if let Some(path_str) = path.to_str() { - // Log file changes for custom turbo.json debugging if let Some(ref custom_path) = self.custom_turbo_json_path { if path_str == custom_path.as_str() || path_str.ends_with("turbo.json") From 1521d538a5666710b0b146fb6b6376057d2f04ba Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Thu, 17 Jul 2025 20:14:25 -0600 Subject: [PATCH 04/19] WIP e198b --- crates/turborepo-lib/src/commands/info.rs | 7 +------ crates/turborepo-lib/src/daemon/connector.rs | 3 ++- crates/turborepo-lib/src/run/builder.rs | 8 ++------ crates/turborepo-lsp/src/lib.rs | 8 ++++---- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/crates/turborepo-lib/src/commands/info.rs b/crates/turborepo-lib/src/commands/info.rs index 659decc5a7d75..4a4686705f62a 100644 --- a/crates/turborepo-lib/src/commands/info.rs +++ b/crates/turborepo-lib/src/commands/info.rs @@ -20,12 +20,7 @@ fn is_wsl() -> bool { pub async fn run(base: CommandBase) { let system = System::new_all(); - let connector = DaemonConnector { - can_start_server: false, - can_kill_server: false, - paths: crate::daemon::Paths::from_repo_root(&base.repo_root), - custom_turbo_json_path: None, - }; + let connector = DaemonConnector::new(false, false, &base.repo_root, None); let daemon_status = match connector.connect().await { Ok(_status) => "Running", Err(DaemonConnectorError::NotRunning) => "Not running", diff --git a/crates/turborepo-lib/src/daemon/connector.rs b/crates/turborepo-lib/src/daemon/connector.rs index 8a3ba4cb21be8..3fa5a8e52fc13 100644 --- a/crates/turborepo-lib/src/daemon/connector.rs +++ b/crates/turborepo-lib/src/daemon/connector.rs @@ -75,13 +75,14 @@ impl DaemonConnector { can_start_server: bool, can_kill_server: bool, repo_root: &AbsoluteSystemPath, + custom_turbo_json_path: Option<&turbopath::AbsoluteSystemPathBuf>, ) -> Self { let paths = Paths::from_repo_root(repo_root); Self { can_start_server, can_kill_server, paths, - custom_turbo_json_path: None, + custom_turbo_json_path: custom_turbo_json_path.cloned(), } } diff --git a/crates/turborepo-lib/src/run/builder.rs b/crates/turborepo-lib/src/run/builder.rs index 5789f20dd7d41..35918b76c94bc 100644 --- a/crates/turborepo-lib/src/run/builder.rs +++ b/crates/turborepo-lib/src/run/builder.rs @@ -262,12 +262,8 @@ impl RunBuilder { (_, Some(true)) | (false, None) => { let can_start_server = true; let can_kill_server = true; - let connector = DaemonConnector { - can_start_server, - can_kill_server, - paths: crate::daemon::Paths::from_repo_root(&self.repo_root), - custom_turbo_json_path: None, - }; + let connector = + DaemonConnector::new(can_start_server, can_kill_server, &self.repo_root, None); match (connector.connect().await, self.opts.run_opts.daemon) { (Ok(client), _) => { run_telemetry.track_daemon_init(DaemonInitStatus::Started); diff --git a/crates/turborepo-lsp/src/lib.rs b/crates/turborepo-lsp/src/lib.rs index 50b39b1b6f811..162effd0e74ce 100644 --- a/crates/turborepo-lsp/src/lib.rs +++ b/crates/turborepo-lsp/src/lib.rs @@ -85,12 +85,12 @@ impl LanguageServer for Backend { || { let can_start_server = true; let can_kill_server = false; - let connector = DaemonConnector { + let connector = DaemonConnector::new( can_start_server, can_kill_server, - paths: DaemonPaths::from_repo_root(&repo_root), - custom_turbo_json_path: None, - }; + &repo_root, + None, + ); connector.connect() }, ) From d8c787e7f3d55a7aa9787473e548a63bf560368e Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Thu, 17 Jul 2025 20:17:56 -0600 Subject: [PATCH 05/19] WIP d73b0 --- crates/turborepo-lib/src/commands/daemon.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/turborepo-lib/src/commands/daemon.rs b/crates/turborepo-lib/src/commands/daemon.rs index 757fe5d617694..206a4bb712de2 100644 --- a/crates/turborepo-lib/src/commands/daemon.rs +++ b/crates/turborepo-lib/src/commands/daemon.rs @@ -33,12 +33,7 @@ pub async fn daemon_client(command: &DaemonCommand, base: &CommandBase) -> Resul DaemonCommand::Clean { .. } => (false, true), }; - let connector = DaemonConnector { - can_start_server, - can_kill_server, - paths: crate::daemon::Paths::from_repo_root(&base.repo_root), - custom_turbo_json_path: None, - }; + let connector = DaemonConnector::new(can_start_server, can_kill_server, &base.repo_root, None); match command { DaemonCommand::Restart => { From 5a0eabf0c94ea1a94a13b3e6b3afd0955821d626 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Thu, 17 Jul 2025 20:24:50 -0600 Subject: [PATCH 06/19] WIP e05c4 --- crates/turborepo-lib/src/daemon/connector.rs | 34 +++++--------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/crates/turborepo-lib/src/daemon/connector.rs b/crates/turborepo-lib/src/daemon/connector.rs index 3fa5a8e52fc13..cc6c35982d9b6 100644 --- a/crates/turborepo-lib/src/daemon/connector.rs +++ b/crates/turborepo-lib/src/daemon/connector.rs @@ -148,8 +148,9 @@ impl DaemonConnector { )) } - /// Check if a daemon is currently running and return its PID - async fn existing_daemon(&self) -> Result { + /// Gets or starts a daemon process, returning its PID. + /// If a daemon is not running, it starts one. + async fn get_or_start_daemon(&self) -> Result { debug!("looking for pid in lockfile: {:?}", self.paths.pid_file); let pidfile = self.pid_lock(); @@ -159,36 +160,17 @@ impl DaemonConnector { debug!("found pid: {}", pid); Ok(sysinfo::Pid::from(pid as usize)) } - None => Err(DaemonConnectorError::NotRunning), - } - } - - /// Gets or starts a daemon process, returning its PID. - /// If a daemon is not running, it starts one. - async fn get_or_start_daemon(&self) -> Result { - let existing_pid = self.existing_daemon().await; - - match (existing_pid, &self.custom_turbo_json_path) { - // If daemon is running but we have a custom turbo.json path, - // we need to restart to ensure the daemon has the correct configuration - (Ok(pid), Some(_)) => { - tracing::info!( - "Found existing daemon (pid: {}) but custom turbo.json path specified. \ - Restarting daemon to ensure correct configuration.", - pid - ); - Self::start_daemon(self.custom_turbo_json_path.as_ref()).await + None if self.can_start_server => { + debug!("no pid found, starting daemon"); + Self::start_daemon(&self.custom_turbo_json_path).await } - // If daemon is running and no custom path, use existing daemon - (Ok(pid), None) => Ok(pid), - // If daemon is not running, start it (with or without custom path) - (Err(_), _) => Self::start_daemon(self.custom_turbo_json_path.as_ref()).await, + None => Err(DaemonConnectorError::NotRunning), } } /// Starts the daemon process, returning its PID. async fn start_daemon( - custom_turbo_json_path: Option<&turbopath::AbsoluteSystemPathBuf>, + custom_turbo_json_path: &Option, ) -> Result { let binary_path = std::env::current_exe().map_err(|e| DaemonConnectorError::Fork(e.into()))?; From f42550530fcccdb8cf55637afbabeb6a4954e272 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Thu, 17 Jul 2025 20:25:58 -0600 Subject: [PATCH 07/19] WIP 22aff --- crates/turborepo-lib/src/daemon/connector.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/turborepo-lib/src/daemon/connector.rs b/crates/turborepo-lib/src/daemon/connector.rs index cc6c35982d9b6..580800459a11c 100644 --- a/crates/turborepo-lib/src/daemon/connector.rs +++ b/crates/turborepo-lib/src/daemon/connector.rs @@ -148,7 +148,8 @@ impl DaemonConnector { )) } - /// Gets or starts a daemon process, returning its PID. + /// Gets the PID of the daemon process. + /// /// If a daemon is not running, it starts one. async fn get_or_start_daemon(&self) -> Result { debug!("looking for pid in lockfile: {:?}", self.paths.pid_file); From 0fec8f0a4d44b3d66a80d9469d05244d038c6af8 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Thu, 17 Jul 2025 20:30:46 -0600 Subject: [PATCH 08/19] WIP ccc28 --- crates/turborepo-lib/src/daemon/connector.rs | 49 +++----------------- 1 file changed, 7 insertions(+), 42 deletions(-) diff --git a/crates/turborepo-lib/src/daemon/connector.rs b/crates/turborepo-lib/src/daemon/connector.rs index 580800459a11c..0642143dfd1ef 100644 --- a/crates/turborepo-lib/src/daemon/connector.rs +++ b/crates/turborepo-lib/src/daemon/connector.rs @@ -454,12 +454,7 @@ mod test { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector { - can_start_server: false, - can_kill_server: false, - paths: Paths::from_repo_root(&repo_root), - custom_turbo_json_path: None, - }; + let connector = DaemonConnector::new(false, false, &repo_root, None); connector.paths.pid_file.ensure_dir().unwrap(); connector .paths @@ -477,12 +472,7 @@ mod test { async fn handles_missing_server_connect() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector { - can_start_server: false, - can_kill_server: false, - paths: Paths::from_repo_root(&repo_root), - custom_turbo_json_path: None, - }; + let connector = DaemonConnector::new(false, false, &repo_root, None); assert_matches!( connector.connect().await, @@ -494,12 +484,7 @@ mod test { async fn handles_kill_dead_server_missing_pid() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector { - can_start_server: false, - can_kill_server: false, - paths: Paths::from_repo_root(&repo_root), - custom_turbo_json_path: None, - }; + let connector = DaemonConnector::new(false, false, &repo_root, None); assert_matches!( connector.kill_dead_server(Pid::from(usize::MAX)).await, @@ -511,12 +496,7 @@ mod test { async fn handles_kill_dead_server_missing_process() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector { - can_start_server: false, - can_kill_server: false, - paths: Paths::from_repo_root(&repo_root), - custom_turbo_json_path: None, - }; + let connector = DaemonConnector::new(false, false, &repo_root, None); connector.paths.pid_file.ensure_dir().unwrap(); connector @@ -542,12 +522,7 @@ mod test { async fn handles_kill_dead_server_wrong_process() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector { - can_start_server: false, - can_kill_server: false, - paths: Paths::from_repo_root(&repo_root), - custom_turbo_json_path: None, - }; + let connector = DaemonConnector::new(false, false, &repo_root, None); let proc = tokio::process::Command::new(NODE_EXE) .stdout(Stdio::null()) @@ -584,12 +559,7 @@ mod test { async fn handles_kill_dead_server() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector { - can_start_server: false, - can_kill_server: true, - paths: Paths::from_repo_root(&repo_root), - custom_turbo_json_path: None, - }; + let connector = DaemonConnector::new(false, true, &repo_root, None); let proc = tokio::process::Command::new(NODE_EXE) .stdout(Stdio::null()) @@ -729,12 +699,7 @@ mod test { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector { - can_start_server: false, - can_kill_server: false, - paths: Paths::from_repo_root(&repo_root), - custom_turbo_json_path: None, - }; + let connector = DaemonConnector::new(false, false, &repo_root, None); let mut client = Endpoint::try_from("http://[::]:50051") .expect("this is a valid uri") From a15110b5308f2cacb29483664696b5a89cae7c62 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Thu, 17 Jul 2025 21:22:57 -0600 Subject: [PATCH 09/19] WIP e4eb1 --- .../src/package_changes_watcher.rs | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/crates/turborepo-lib/src/package_changes_watcher.rs b/crates/turborepo-lib/src/package_changes_watcher.rs index cdbd313ea16bd..f785eb23e61ab 100644 --- a/crates/turborepo-lib/src/package_changes_watcher.rs +++ b/crates/turborepo-lib/src/package_changes_watcher.rs @@ -152,26 +152,25 @@ impl Subscriber { custom_turbo_json_path: Option, ) -> Self { // Try to canonicalize the custom path to match what the file watcher reports - let normalized_custom_path = - custom_turbo_json_path.and_then(|path| match path.to_realpath() { - Ok(real_path) => { - tracing::info!( - "PackageChangesWatcher: monitoring custom turbo.json at: {} \ - (canonicalized: {})", - path, - real_path - ); - Some(real_path) - } - Err(e) => { - tracing::warn!( - "Failed to canonicalize custom turbo.json path {}: {}, using original path", - path, - e - ); - Some(path) - } - }); + let normalized_custom_path = custom_turbo_json_path.map(|path| match path.to_realpath() { + Ok(real_path) => { + tracing::info!( + "PackageChangesWatcher: monitoring custom turbo.json at: {} (canonicalized: \ + {})", + path, + real_path + ); + real_path + } + Err(e) => { + tracing::warn!( + "Failed to canonicalize custom turbo.json path {}: {}, using original path", + path, + e + ); + path + } + }); Subscriber { repo_root, From 0b38d3c19b1c51a74a2823bf3676b9be0116edba Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Mon, 21 Jul 2025 20:36:35 -0600 Subject: [PATCH 10/19] warning if path is outside repo --- crates/turborepo-lib/src/commands/daemon.rs | 2 +- crates/turborepo-lib/src/commands/info.rs | 2 +- crates/turborepo-lib/src/daemon/connector.rs | 17 ++++---- .../src/package_changes_watcher.rs | 41 ++++++++++++------- crates/turborepo-lib/src/run/builder.rs | 2 +- crates/turborepo-lsp/src/lib.rs | 8 +--- 6 files changed, 39 insertions(+), 33 deletions(-) diff --git a/crates/turborepo-lib/src/commands/daemon.rs b/crates/turborepo-lib/src/commands/daemon.rs index 206a4bb712de2..acb35c53795f3 100644 --- a/crates/turborepo-lib/src/commands/daemon.rs +++ b/crates/turborepo-lib/src/commands/daemon.rs @@ -33,7 +33,7 @@ pub async fn daemon_client(command: &DaemonCommand, base: &CommandBase) -> Resul DaemonCommand::Clean { .. } => (false, true), }; - let connector = DaemonConnector::new(can_start_server, can_kill_server, &base.repo_root, None); + let connector = DaemonConnector::new(can_start_server, can_kill_server, &base.repo_root); match command { DaemonCommand::Restart => { diff --git a/crates/turborepo-lib/src/commands/info.rs b/crates/turborepo-lib/src/commands/info.rs index 4a4686705f62a..394ac1b65a518 100644 --- a/crates/turborepo-lib/src/commands/info.rs +++ b/crates/turborepo-lib/src/commands/info.rs @@ -20,7 +20,7 @@ fn is_wsl() -> bool { pub async fn run(base: CommandBase) { let system = System::new_all(); - let connector = DaemonConnector::new(false, false, &base.repo_root, None); + let connector = DaemonConnector::new(false, false, &base.repo_root); let daemon_status = match connector.connect().await { Ok(_status) => "Running", Err(DaemonConnectorError::NotRunning) => "Not running", diff --git a/crates/turborepo-lib/src/daemon/connector.rs b/crates/turborepo-lib/src/daemon/connector.rs index 0642143dfd1ef..769c2b6c01f1d 100644 --- a/crates/turborepo-lib/src/daemon/connector.rs +++ b/crates/turborepo-lib/src/daemon/connector.rs @@ -75,14 +75,13 @@ impl DaemonConnector { can_start_server: bool, can_kill_server: bool, repo_root: &AbsoluteSystemPath, - custom_turbo_json_path: Option<&turbopath::AbsoluteSystemPathBuf>, ) -> Self { let paths = Paths::from_repo_root(repo_root); Self { can_start_server, can_kill_server, paths, - custom_turbo_json_path: custom_turbo_json_path.cloned(), + custom_turbo_json_path: None, } } @@ -454,7 +453,7 @@ mod test { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root, None); + let connector = DaemonConnector::new(false, false, &repo_root); connector.paths.pid_file.ensure_dir().unwrap(); connector .paths @@ -472,7 +471,7 @@ mod test { async fn handles_missing_server_connect() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root, None); + let connector = DaemonConnector::new(false, false, &repo_root); assert_matches!( connector.connect().await, @@ -484,7 +483,7 @@ mod test { async fn handles_kill_dead_server_missing_pid() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root, None); + let connector = DaemonConnector::new(false, false, &repo_root); assert_matches!( connector.kill_dead_server(Pid::from(usize::MAX)).await, @@ -496,7 +495,7 @@ mod test { async fn handles_kill_dead_server_missing_process() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root, None); + let connector = DaemonConnector::new(false, false, &repo_root); connector.paths.pid_file.ensure_dir().unwrap(); connector @@ -522,7 +521,7 @@ mod test { async fn handles_kill_dead_server_wrong_process() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root, None); + let connector = DaemonConnector::new(false, false, &repo_root); let proc = tokio::process::Command::new(NODE_EXE) .stdout(Stdio::null()) @@ -559,7 +558,7 @@ mod test { async fn handles_kill_dead_server() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, true, &repo_root, None); + let connector = DaemonConnector::new(false, true, &repo_root); let proc = tokio::process::Command::new(NODE_EXE) .stdout(Stdio::null()) @@ -699,7 +698,7 @@ mod test { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root, None); + let connector = DaemonConnector::new(false, false, &repo_root); let mut client = Endpoint::try_from("http://[::]:50051") .expect("this is a valid uri") diff --git a/crates/turborepo-lib/src/package_changes_watcher.rs b/crates/turborepo-lib/src/package_changes_watcher.rs index f785eb23e61ab..57085602fb0f3 100644 --- a/crates/turborepo-lib/src/package_changes_watcher.rs +++ b/crates/turborepo-lib/src/package_changes_watcher.rs @@ -152,23 +152,34 @@ impl Subscriber { custom_turbo_json_path: Option, ) -> Self { // Try to canonicalize the custom path to match what the file watcher reports - let normalized_custom_path = custom_turbo_json_path.map(|path| match path.to_realpath() { - Ok(real_path) => { - tracing::info!( - "PackageChangesWatcher: monitoring custom turbo.json at: {} (canonicalized: \ - {})", - path, - real_path - ); - real_path - } - Err(e) => { + let normalized_custom_path = custom_turbo_json_path.map(|path| { + // Check if the custom turbo.json path is outside the repository + if repo_root.anchor(&path).is_err() { tracing::warn!( - "Failed to canonicalize custom turbo.json path {}: {}, using original path", - path, - e + "turbo.json is located outside of repository at {}. Changes to this file will \ + not be watched and may not trigger rebuilds in watch mode.", + path ); - path + } + + match path.to_realpath() { + Ok(real_path) => { + tracing::info!( + "PackageChangesWatcher: monitoring custom turbo.json at: {} \ + (canonicalized: {})", + path, + real_path + ); + real_path + } + Err(e) => { + tracing::warn!( + "Failed to canonicalize custom turbo.json path {}: {}, using original path", + path, + e + ); + path + } } }); diff --git a/crates/turborepo-lib/src/run/builder.rs b/crates/turborepo-lib/src/run/builder.rs index 35918b76c94bc..f41edb772eedf 100644 --- a/crates/turborepo-lib/src/run/builder.rs +++ b/crates/turborepo-lib/src/run/builder.rs @@ -263,7 +263,7 @@ impl RunBuilder { let can_start_server = true; let can_kill_server = true; let connector = - DaemonConnector::new(can_start_server, can_kill_server, &self.repo_root, None); + DaemonConnector::new(can_start_server, can_kill_server, &self.repo_root); match (connector.connect().await, self.opts.run_opts.daemon) { (Ok(client), _) => { run_telemetry.track_daemon_init(DaemonInitStatus::Started); diff --git a/crates/turborepo-lsp/src/lib.rs b/crates/turborepo-lsp/src/lib.rs index 162effd0e74ce..729fcec1058a3 100644 --- a/crates/turborepo-lsp/src/lib.rs +++ b/crates/turborepo-lsp/src/lib.rs @@ -85,12 +85,8 @@ impl LanguageServer for Backend { || { let can_start_server = true; let can_kill_server = false; - let connector = DaemonConnector::new( - can_start_server, - can_kill_server, - &repo_root, - None, - ); + let connector = + DaemonConnector::new(can_start_server, can_kill_server, &repo_root); connector.connect() }, ) From 004af4260da96ed375fdf156f198304631fc4600 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Mon, 21 Jul 2025 20:41:24 -0600 Subject: [PATCH 11/19] WIP 0c10b --- .../src/package_changes_watcher.rs | 31 ++----------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/crates/turborepo-lib/src/package_changes_watcher.rs b/crates/turborepo-lib/src/package_changes_watcher.rs index 57085602fb0f3..840706a0b1b8e 100644 --- a/crates/turborepo-lib/src/package_changes_watcher.rs +++ b/crates/turborepo-lib/src/package_changes_watcher.rs @@ -297,19 +297,8 @@ impl Subscriber { self.changed_files.lock().await.borrow_mut().deref_mut() { for path in paths { - if let Some(path_str) = path.to_str() { - if let Some(ref custom_path) = self.custom_turbo_json_path { - if path_str == custom_path.as_str() - || path_str.ends_with("turbo.json") - || path_str.ends_with("turbo.jsonc") - { - tracing::info!( - "File watcher detected change: {}", - path_str - ); - } - } - trie.insert(path_str.to_string(), ()); + if let Some(path) = path.to_str() { + trie.insert(path.to_string(), ()); } } } @@ -409,21 +398,7 @@ impl Subscriber { .as_ref() .map(|path| { let path_str = path.as_str(); - let found = trie.get(path_str).is_some(); - tracing::debug!( - "Checking custom turbo.json path '{}' in trie, found: {}", - path_str, - found - ); - if !found { - // Also try with the path keys for debugging - let trie_keys: Vec<_> = trie.keys().take(10).collect(); - tracing::debug!( - "Trie contains these paths (first 10): {:?}", - trie_keys - ); - } - found + trie.get(path_str).is_some() }) .unwrap_or(false); From 3f9b7243979caf9c5bb17fab00cb8736341b5d92 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Mon, 21 Jul 2025 20:42:16 -0600 Subject: [PATCH 12/19] WIP 53e85 --- crates/turborepo-lib/src/run/watch.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/turborepo-lib/src/run/watch.rs b/crates/turborepo-lib/src/run/watch.rs index 2344ede1e8344..19db04ac29510 100644 --- a/crates/turborepo-lib/src/run/watch.rs +++ b/crates/turborepo-lib/src/run/watch.rs @@ -103,7 +103,6 @@ pub enum Error { UI(#[from] turborepo_ui::Error), #[error("Could not connect to UI thread: {0}")] UISend(String), - #[error("Invalid config: {0}")] Config(#[from] crate::config::Error), #[error(transparent)] @@ -121,9 +120,6 @@ impl WatchClient { let standard_config_path = resolve_turbo_config_path(&base.repo_root)?; - // Note: We now support watching custom turbo.json paths, so this check is - // removed - if matches!(base.opts.run_opts.daemon, Some(false)) { warn!("daemon is required for watch, ignoring request to disable daemon"); } From 77f949f549e48f86fd7e6c04e98fdebc0a282323 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Mon, 21 Jul 2025 21:08:41 -0600 Subject: [PATCH 13/19] WIP 2726e --- crates/turborepo-lib/src/cli/mod.rs | 4 ++- crates/turborepo-lib/src/commands/daemon.rs | 27 +++++++++++++++-- crates/turborepo-lib/src/commands/info.rs | 2 +- crates/turborepo-lib/src/daemon/connector.rs | 22 ++++++-------- crates/turborepo-lib/src/run/builder.rs | 8 +++-- crates/turborepo-lib/src/run/watch.rs | 31 ++++++++++---------- crates/turborepo-lsp/src/lib.rs | 8 +++-- 7 files changed, 65 insertions(+), 37 deletions(-) diff --git a/crates/turborepo-lib/src/cli/mod.rs b/crates/turborepo-lib/src/cli/mod.rs index 93205ed9f22a1..e8ac96eb7dadd 100644 --- a/crates/turborepo-lib/src/cli/mod.rs +++ b/crates/turborepo-lib/src/cli/mod.rs @@ -1442,7 +1442,9 @@ pub async fn run( event.track_ui_mode(base.opts.run_opts.ui_mode); match command { - Some(command) => daemon::daemon_client(command, &base).await, + Some(command) => { + daemon::daemon_client(command, &base, turbo_json_path.as_deref()).await + } None => { daemon::daemon_server(&base, idle_time, turbo_json_path.as_deref(), logger) .await diff --git a/crates/turborepo-lib/src/commands/daemon.rs b/crates/turborepo-lib/src/commands/daemon.rs index acb35c53795f3..f692da25844ce 100644 --- a/crates/turborepo-lib/src/commands/daemon.rs +++ b/crates/turborepo-lib/src/commands/daemon.rs @@ -25,7 +25,11 @@ const DAEMON_NOT_RUNNING_MESSAGE: &str = "daemon is not running, run `turbo daemon start` to start it"; /// Runs the daemon command. -pub async fn daemon_client(command: &DaemonCommand, base: &CommandBase) -> Result<(), DaemonError> { +pub async fn daemon_client( + command: &DaemonCommand, + base: &CommandBase, + custom_turbo_json_path: Option<&camino::Utf8Path>, +) -> Result<(), DaemonError> { let (can_start_server, can_kill_server) = match command { DaemonCommand::Status { .. } | DaemonCommand::Logs => (false, false), DaemonCommand::Stop => (false, true), @@ -33,7 +37,26 @@ pub async fn daemon_client(command: &DaemonCommand, base: &CommandBase) -> Resul DaemonCommand::Clean { .. } => (false, true), }; - let connector = DaemonConnector::new(can_start_server, can_kill_server, &base.repo_root); + let custom_turbo_json_path = match custom_turbo_json_path { + Some(p) => match turbopath::AbsoluteSystemPathBuf::from_cwd(p) { + Ok(path) => Some(path), + Err(e) => { + tracing::error!("Failed to convert custom turbo.json path: {}", e); + return Err(DaemonError::Unavailable(format!( + "Invalid turbo.json path: {}", + e + ))); + } + }, + None => None, + }; + + let connector = DaemonConnector::new( + can_start_server, + can_kill_server, + &base.repo_root, + custom_turbo_json_path, + ); match command { DaemonCommand::Restart => { diff --git a/crates/turborepo-lib/src/commands/info.rs b/crates/turborepo-lib/src/commands/info.rs index 394ac1b65a518..4a4686705f62a 100644 --- a/crates/turborepo-lib/src/commands/info.rs +++ b/crates/turborepo-lib/src/commands/info.rs @@ -20,7 +20,7 @@ fn is_wsl() -> bool { pub async fn run(base: CommandBase) { let system = System::new_all(); - let connector = DaemonConnector::new(false, false, &base.repo_root); + let connector = DaemonConnector::new(false, false, &base.repo_root, None); let daemon_status = match connector.connect().await { Ok(_status) => "Running", Err(DaemonConnectorError::NotRunning) => "Not running", diff --git a/crates/turborepo-lib/src/daemon/connector.rs b/crates/turborepo-lib/src/daemon/connector.rs index 769c2b6c01f1d..bf9e377af1579 100644 --- a/crates/turborepo-lib/src/daemon/connector.rs +++ b/crates/turborepo-lib/src/daemon/connector.rs @@ -75,21 +75,17 @@ impl DaemonConnector { can_start_server: bool, can_kill_server: bool, repo_root: &AbsoluteSystemPath, + custom_turbo_json_path: Option, ) -> Self { let paths = Paths::from_repo_root(repo_root); Self { can_start_server, can_kill_server, paths, - custom_turbo_json_path: None, + custom_turbo_json_path, } } - pub fn with_custom_turbo_json_path(mut self, path: turbopath::AbsoluteSystemPathBuf) -> Self { - self.custom_turbo_json_path = Some(path); - self - } - const CONNECT_RETRY_MAX: usize = 3; const CONNECT_TIMEOUT: Duration = Duration::from_secs(1); const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(1); @@ -453,7 +449,7 @@ mod test { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root); + let connector = DaemonConnector::new(false, false, &repo_root, None); connector.paths.pid_file.ensure_dir().unwrap(); connector .paths @@ -471,7 +467,7 @@ mod test { async fn handles_missing_server_connect() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root); + let connector = DaemonConnector::new(false, false, &repo_root, None); assert_matches!( connector.connect().await, @@ -483,7 +479,7 @@ mod test { async fn handles_kill_dead_server_missing_pid() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root); + let connector = DaemonConnector::new(false, false, &repo_root, None); assert_matches!( connector.kill_dead_server(Pid::from(usize::MAX)).await, @@ -495,7 +491,7 @@ mod test { async fn handles_kill_dead_server_missing_process() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root); + let connector = DaemonConnector::new(false, false, &repo_root, None); connector.paths.pid_file.ensure_dir().unwrap(); connector @@ -521,7 +517,7 @@ mod test { async fn handles_kill_dead_server_wrong_process() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root); + let connector = DaemonConnector::new(false, false, &repo_root, None); let proc = tokio::process::Command::new(NODE_EXE) .stdout(Stdio::null()) @@ -558,7 +554,7 @@ mod test { async fn handles_kill_dead_server() { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, true, &repo_root); + let connector = DaemonConnector::new(false, true, &repo_root, None); let proc = tokio::process::Command::new(NODE_EXE) .stdout(Stdio::null()) @@ -698,7 +694,7 @@ mod test { let tmp_dir = tempfile::tempdir().unwrap(); let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap(); - let connector = DaemonConnector::new(false, false, &repo_root); + let connector = DaemonConnector::new(false, false, &repo_root, None); let mut client = Endpoint::try_from("http://[::]:50051") .expect("this is a valid uri") diff --git a/crates/turborepo-lib/src/run/builder.rs b/crates/turborepo-lib/src/run/builder.rs index f41edb772eedf..3c65e1bfa5c83 100644 --- a/crates/turborepo-lib/src/run/builder.rs +++ b/crates/turborepo-lib/src/run/builder.rs @@ -262,8 +262,12 @@ impl RunBuilder { (_, Some(true)) | (false, None) => { let can_start_server = true; let can_kill_server = true; - let connector = - DaemonConnector::new(can_start_server, can_kill_server, &self.repo_root); + let connector = DaemonConnector::new( + can_start_server, + can_kill_server, + &self.repo_root, + Some(self.opts.repo_opts.root_turbo_json_path.clone()), + ); match (connector.connect().await, self.opts.run_opts.daemon) { (Ok(client), _) => { run_telemetry.track_daemon_init(DaemonInitStatus::Started); diff --git a/crates/turborepo-lib/src/run/watch.rs b/crates/turborepo-lib/src/run/watch.rs index 19db04ac29510..b9807bc3d99a3 100644 --- a/crates/turborepo-lib/src/run/watch.rs +++ b/crates/turborepo-lib/src/run/watch.rs @@ -135,27 +135,26 @@ impl WatchClient { let (ui_sender, ui_handle) = run.start_ui()?.unzip(); - let mut connector = DaemonConnector { + // Determine if we're using a custom turbo.json path + let custom_turbo_json_path = + if base.opts.repo_opts.root_turbo_json_path != standard_config_path { + tracing::info!( + "Using custom turbo.json path: {} (standard: {})", + base.opts.repo_opts.root_turbo_json_path, + standard_config_path + ); + Some(base.opts.repo_opts.root_turbo_json_path.clone()) + } else { + None + }; + + let connector = DaemonConnector { can_start_server: true, can_kill_server: true, paths: DaemonPaths::from_repo_root(&base.repo_root), - custom_turbo_json_path: None, + custom_turbo_json_path, }; - // If using a non-standard turbo.json path, pass it to the daemon - if base.opts.repo_opts.root_turbo_json_path != standard_config_path { - tracing::info!( - "Using custom turbo.json path: {} (standard: {})", - base.opts.repo_opts.root_turbo_json_path, - standard_config_path - ); - // The root_turbo_json_path is already an AbsoluteSystemPathBuf, so use it - // directly - let custom_path = base.opts.repo_opts.root_turbo_json_path.clone(); - tracing::info!("Passing custom turbo.json path to daemon: {}", custom_path); - connector = connector.with_custom_turbo_json_path(custom_path); - } - Ok(Self { base, run, diff --git a/crates/turborepo-lsp/src/lib.rs b/crates/turborepo-lsp/src/lib.rs index 729fcec1058a3..162effd0e74ce 100644 --- a/crates/turborepo-lsp/src/lib.rs +++ b/crates/turborepo-lsp/src/lib.rs @@ -85,8 +85,12 @@ impl LanguageServer for Backend { || { let can_start_server = true; let can_kill_server = false; - let connector = - DaemonConnector::new(can_start_server, can_kill_server, &repo_root); + let connector = DaemonConnector::new( + can_start_server, + can_kill_server, + &repo_root, + None, + ); connector.connect() }, ) From ff4b9ddfb1c78cacbf76f6f3b65f6693c57cfbc7 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Mon, 21 Jul 2025 21:11:30 -0600 Subject: [PATCH 14/19] WIP 7ae4a --- crates/turborepo-lib/src/commands/daemon.rs | 47 +++++++++------------ 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/crates/turborepo-lib/src/commands/daemon.rs b/crates/turborepo-lib/src/commands/daemon.rs index f692da25844ce..7c2f9cabf3ce3 100644 --- a/crates/turborepo-lib/src/commands/daemon.rs +++ b/crates/turborepo-lib/src/commands/daemon.rs @@ -24,6 +24,25 @@ use crate::{ const DAEMON_NOT_RUNNING_MESSAGE: &str = "daemon is not running, run `turbo daemon start` to start it"; +/// Converts an optional turbo.json path to an absolute system path. +fn convert_turbo_json_path( + path: Option<&camino::Utf8Path>, +) -> Result, DaemonError> { + match path { + Some(p) => match turbopath::AbsoluteSystemPathBuf::from_cwd(p) { + Ok(path) => Ok(Some(path)), + Err(e) => { + tracing::error!("Failed to convert custom turbo.json path: {}", e); + Err(DaemonError::Unavailable(format!( + "Invalid turbo.json path: {}", + e + ))) + } + }, + None => Ok(None), + } +} + /// Runs the daemon command. pub async fn daemon_client( command: &DaemonCommand, @@ -37,19 +56,7 @@ pub async fn daemon_client( DaemonCommand::Clean { .. } => (false, true), }; - let custom_turbo_json_path = match custom_turbo_json_path { - Some(p) => match turbopath::AbsoluteSystemPathBuf::from_cwd(p) { - Ok(path) => Some(path), - Err(e) => { - tracing::error!("Failed to convert custom turbo.json path: {}", e); - return Err(DaemonError::Unavailable(format!( - "Invalid turbo.json path: {}", - e - ))); - } - }, - None => None, - }; + let custom_turbo_json_path = convert_turbo_json_path(custom_turbo_json_path)?; let connector = DaemonConnector::new( can_start_server, @@ -322,19 +329,7 @@ pub async fn daemon_server( } CloseReason::Interrupt }); - let custom_turbo_json_path = match turbo_json_path { - Some(p) => match turbopath::AbsoluteSystemPathBuf::from_cwd(p) { - Ok(path) => Some(path), - Err(e) => { - tracing::error!("Failed to convert custom turbo.json path: {}", e); - return Err(DaemonError::Unavailable(format!( - "Invalid turbo.json path: {}", - e - ))); - } - }, - None => None, - }; + let custom_turbo_json_path = convert_turbo_json_path(turbo_json_path)?; let server = crate::daemon::TurboGrpcService::new( base.repo_root.clone(), paths, From 2bd9380af04e60ef1dda09ec4293a31a3120dcec Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Mon, 21 Jul 2025 21:49:52 -0600 Subject: [PATCH 15/19] WIP f6429 --- .../src/package_changes_watcher.rs | 42 +++++++++++-------- crates/turborepo-lib/src/run/builder.rs | 18 +++++++- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/crates/turborepo-lib/src/package_changes_watcher.rs b/crates/turborepo-lib/src/package_changes_watcher.rs index 840706a0b1b8e..9f538c9f8b77a 100644 --- a/crates/turborepo-lib/src/package_changes_watcher.rs +++ b/crates/turborepo-lib/src/package_changes_watcher.rs @@ -208,25 +208,31 @@ impl Subscriber { return None; }; - let turbo_json_path = self.repo_root.join_component(CONFIG_FILE); - let turbo_jsonc_path = self.repo_root.join_component(CONFIG_FILE_JSONC); - - let turbo_json_exists = turbo_json_path.exists(); - let turbo_jsonc_exists = turbo_jsonc_path.exists(); - - // TODO: Dedupe places where we search for turbo.json and turbo.jsonc - // There are now several places where we do this in the codebase - let config_path = match (turbo_json_exists, turbo_jsonc_exists) { - (true, true) => { - tracing::warn!( - "Found both turbo.json and turbo.jsonc in {}. Using turbo.json for watching.", - self.repo_root - ); - turbo_json_path + // Use custom turbo.json path if provided, otherwise use standard paths + let config_path = if let Some(custom_path) = &self.custom_turbo_json_path { + custom_path.clone() + } else { + let turbo_json_path = self.repo_root.join_component(CONFIG_FILE); + let turbo_jsonc_path = self.repo_root.join_component(CONFIG_FILE_JSONC); + + let turbo_json_exists = turbo_json_path.exists(); + let turbo_jsonc_exists = turbo_jsonc_path.exists(); + + // TODO: Dedupe places where we search for turbo.json and turbo.jsonc + // There are now several places where we do this in the codebase + match (turbo_json_exists, turbo_jsonc_exists) { + (true, true) => { + tracing::warn!( + "Found both turbo.json and turbo.jsonc in {}. Using turbo.json for \ + watching.", + self.repo_root + ); + turbo_json_path + } + (true, false) => turbo_json_path, + (false, true) => turbo_jsonc_path, + (false, false) => turbo_json_path, // Default to turbo.json } - (true, false) => turbo_json_path, - (false, true) => turbo_jsonc_path, - (false, false) => turbo_json_path, // Default to turbo.json }; let root_turbo_json = TurboJsonLoader::workspace( diff --git a/crates/turborepo-lib/src/run/builder.rs b/crates/turborepo-lib/src/run/builder.rs index 3c65e1bfa5c83..eb52c31c903c5 100644 --- a/crates/turborepo-lib/src/run/builder.rs +++ b/crates/turborepo-lib/src/run/builder.rs @@ -47,7 +47,7 @@ use crate::{ opts::Opts, run::{scope, task_access::TaskAccess, task_id::TaskName, Error, Run, RunCache}, shim::TurboState, - turbo_json::{TurboJson, TurboJsonLoader, UIMode}, + turbo_json::{resolve_turbo_config_path, TurboJson, TurboJsonLoader, UIMode}, DaemonConnector, }; @@ -262,12 +262,26 @@ impl RunBuilder { (_, Some(true)) | (false, None) => { let can_start_server = true; let can_kill_server = true; + + // Determine custom turbo.json path if different from standard path + let custom_turbo_json_path = + if let Ok(standard_path) = resolve_turbo_config_path(&self.repo_root) { + if self.opts.repo_opts.root_turbo_json_path != standard_path { + Some(self.opts.repo_opts.root_turbo_json_path.clone()) + } else { + None + } + } else { + None + }; + let connector = DaemonConnector::new( can_start_server, can_kill_server, &self.repo_root, - Some(self.opts.repo_opts.root_turbo_json_path.clone()), + custom_turbo_json_path, ); + match (connector.connect().await, self.opts.run_opts.daemon) { (Ok(client), _) => { run_telemetry.track_daemon_init(DaemonInitStatus::Started); From f87417c1632161fd1f106c49f724b693824bd163 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Mon, 21 Jul 2025 21:52:51 -0600 Subject: [PATCH 16/19] WIP e2ba6 --- crates/turborepo-lib/src/cli/mod.rs | 5 ++--- crates/turborepo-lib/src/commands/daemon.rs | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/turborepo-lib/src/cli/mod.rs b/crates/turborepo-lib/src/cli/mod.rs index e8ac96eb7dadd..ad7d915f4b9a2 100644 --- a/crates/turborepo-lib/src/cli/mod.rs +++ b/crates/turborepo-lib/src/cli/mod.rs @@ -1443,11 +1443,10 @@ pub async fn run( match command { Some(command) => { - daemon::daemon_client(command, &base, turbo_json_path.as_deref()).await + daemon::daemon_client(command, &base, turbo_json_path.clone()).await } None => { - daemon::daemon_server(&base, idle_time, turbo_json_path.as_deref(), logger) - .await + daemon::daemon_server(&base, idle_time, turbo_json_path.clone(), logger).await } }?; diff --git a/crates/turborepo-lib/src/commands/daemon.rs b/crates/turborepo-lib/src/commands/daemon.rs index 7c2f9cabf3ce3..452ef49097cd9 100644 --- a/crates/turborepo-lib/src/commands/daemon.rs +++ b/crates/turborepo-lib/src/commands/daemon.rs @@ -47,7 +47,7 @@ fn convert_turbo_json_path( pub async fn daemon_client( command: &DaemonCommand, base: &CommandBase, - custom_turbo_json_path: Option<&camino::Utf8Path>, + custom_turbo_json_path: Option, ) -> Result<(), DaemonError> { let (can_start_server, can_kill_server) = match command { DaemonCommand::Status { .. } | DaemonCommand::Logs => (false, false), @@ -56,7 +56,7 @@ pub async fn daemon_client( DaemonCommand::Clean { .. } => (false, true), }; - let custom_turbo_json_path = convert_turbo_json_path(custom_turbo_json_path)?; + let custom_turbo_json_path = convert_turbo_json_path(custom_turbo_json_path.as_deref())?; let connector = DaemonConnector::new( can_start_server, @@ -305,7 +305,7 @@ fn log_filename(base_filename: &str) -> Result { pub async fn daemon_server( base: &CommandBase, idle_time: &String, - turbo_json_path: Option<&camino::Utf8Path>, + turbo_json_path: Option, logging: &TurboSubscriber, ) -> Result<(), DaemonError> { let paths = Paths::from_repo_root(&base.repo_root); @@ -329,7 +329,7 @@ pub async fn daemon_server( } CloseReason::Interrupt }); - let custom_turbo_json_path = convert_turbo_json_path(turbo_json_path)?; + let custom_turbo_json_path = convert_turbo_json_path(turbo_json_path.as_deref())?; let server = crate::daemon::TurboGrpcService::new( base.repo_root.clone(), paths, From ca9f0f6c9990c40469ebf242c0b3435bb44e8254 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Mon, 21 Jul 2025 22:00:02 -0600 Subject: [PATCH 17/19] WIP 9d35e --- .../src/package_changes_watcher.rs | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/crates/turborepo-lib/src/package_changes_watcher.rs b/crates/turborepo-lib/src/package_changes_watcher.rs index 9f538c9f8b77a..2020b2f332ab9 100644 --- a/crates/turborepo-lib/src/package_changes_watcher.rs +++ b/crates/turborepo-lib/src/package_changes_watcher.rs @@ -23,7 +23,9 @@ use turborepo_repository::{ }; use turborepo_scm::GitHashes; -use crate::turbo_json::{TurboJson, TurboJsonLoader, CONFIG_FILE, CONFIG_FILE_JSONC}; +use crate::turbo_json::{ + resolve_turbo_config_path, TurboJson, TurboJsonLoader, CONFIG_FILE, CONFIG_FILE_JSONC, +}; #[derive(Clone)] pub enum PackageChangeEvent { @@ -212,26 +214,19 @@ impl Subscriber { let config_path = if let Some(custom_path) = &self.custom_turbo_json_path { custom_path.clone() } else { - let turbo_json_path = self.repo_root.join_component(CONFIG_FILE); - let turbo_jsonc_path = self.repo_root.join_component(CONFIG_FILE_JSONC); - - let turbo_json_exists = turbo_json_path.exists(); - let turbo_jsonc_exists = turbo_jsonc_path.exists(); - - // TODO: Dedupe places where we search for turbo.json and turbo.jsonc - // There are now several places where we do this in the codebase - match (turbo_json_exists, turbo_jsonc_exists) { - (true, true) => { + match resolve_turbo_config_path(&self.repo_root) { + Ok(path) => path, + Err(_) => { + // TODO: caIf both turbo.json and turbo.jsonc exist, log warning and default to + // turbo.json to preserve existing behavior for file + // watching tracing::warn!( "Found both turbo.json and turbo.jsonc in {}. Using turbo.json for \ watching.", self.repo_root ); - turbo_json_path + self.repo_root.join_component(CONFIG_FILE) } - (true, false) => turbo_json_path, - (false, true) => turbo_jsonc_path, - (false, false) => turbo_json_path, // Default to turbo.json } }; From d3143a086257a16b5754f47a050a695e9685c813 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Mon, 21 Jul 2025 22:00:30 -0600 Subject: [PATCH 18/19] WIP a0e13 --- crates/turborepo-lib/src/package_changes_watcher.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/turborepo-lib/src/package_changes_watcher.rs b/crates/turborepo-lib/src/package_changes_watcher.rs index 2020b2f332ab9..c0245b590f05a 100644 --- a/crates/turborepo-lib/src/package_changes_watcher.rs +++ b/crates/turborepo-lib/src/package_changes_watcher.rs @@ -217,9 +217,9 @@ impl Subscriber { match resolve_turbo_config_path(&self.repo_root) { Ok(path) => path, Err(_) => { - // TODO: caIf both turbo.json and turbo.jsonc exist, log warning and default to + // TODO: If both turbo.json and turbo.jsonc exist, log warning and default to // turbo.json to preserve existing behavior for file - // watching + // watching prior to refactoring. tracing::warn!( "Found both turbo.json and turbo.jsonc in {}. Using turbo.json for \ watching.", From 5f4b845360ac99a9f5fd6c5fe2aed4dc193ac025 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Mon, 21 Jul 2025 22:01:25 -0600 Subject: [PATCH 19/19] WIP 22592 --- crates/turborepo-lib/src/package_changes_watcher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/turborepo-lib/src/package_changes_watcher.rs b/crates/turborepo-lib/src/package_changes_watcher.rs index c0245b590f05a..a902f5f36dabe 100644 --- a/crates/turborepo-lib/src/package_changes_watcher.rs +++ b/crates/turborepo-lib/src/package_changes_watcher.rs @@ -159,7 +159,7 @@ impl Subscriber { if repo_root.anchor(&path).is_err() { tracing::warn!( "turbo.json is located outside of repository at {}. Changes to this file will \ - not be watched and may not trigger rebuilds in watch mode.", + not be watched.", path ); }