From f057d467c5f46d8ddebf2eda1b3ddc877b60e25c Mon Sep 17 00:00:00 2001 From: KyleBarton Date: Tue, 6 Jan 2026 10:00:24 -0800 Subject: [PATCH] Remove implicit dependency on Node env for data_dir devcontainer CLI (#45589) Closes #45058 Release Notes: - Fixed implicit dependency on a system `node` for running devcontainers. --- crates/recent_projects/src/dev_container.rs | 175 +++++++++++++++----- 1 file changed, 129 insertions(+), 46 deletions(-) diff --git a/crates/recent_projects/src/dev_container.rs b/crates/recent_projects/src/dev_container.rs index 0e6b8b381df32d688e062948460707a5f8cfb552..abbebfcedd227b24353c6e63ca184af7125cdead 100644 --- a/crates/recent_projects/src/dev_container.rs +++ b/crates/recent_projects/src/dev_container.rs @@ -1,3 +1,4 @@ +use std::fmt::Display; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -53,7 +54,9 @@ async fn check_for_docker() -> Result<(), DevContainerError> { } } -async fn ensure_devcontainer_cli(node_runtime: NodeRuntime) -> Result { +async fn ensure_devcontainer_cli( + node_runtime: &NodeRuntime, +) -> Result<(PathBuf, bool), DevContainerError> { let mut command = util::command::new_smol_command(&dev_container_cli()); command.arg("--version"); @@ -63,23 +66,42 @@ async fn ensure_devcontainer_cli(node_runtime: NodeRuntime) -> Result log::error!( "Unable to find devcontainer CLI in Data dir. Will try to install. Error: {:?}", e - ); - } else { - log::info!("Found devcontainer CLI in Data dir"); - return Ok(datadir_cli_path.clone()); + ), + Ok(output) => { + if output.status.success() { + log::info!("Found devcontainer CLI in Data dir"); + return Ok((datadir_cli_path.clone(), false)); + } else { + log::error!( + "Could not run devcontainer CLI from data_dir. Will try once more to install. Output: {:?}", + output + ); + } + } } if let Err(e) = fs::create_dir_all(paths::devcontainer_dir()).await { @@ -101,7 +123,9 @@ async fn ensure_devcontainer_cli(node_runtime: NodeRuntime) -> Result Result, ) -> Result { - let mut command = util::command::new_smol_command(path_to_cli.display().to_string()); - command.arg("up"); - command.arg("--workspace-folder"); - command.arg(path.display().to_string()); + let Ok(node_runtime_path) = node_runtime.binary_path().await else { + log::error!("Unable to find node runtime path"); + return Err(DevContainerError::NodeRuntimeNotAvailable); + }; + + let mut command = if found_in_path { + let mut command = util::command::new_smol_command(path_to_cli.display().to_string()); + command.arg("up"); + command.arg("--workspace-folder"); + command.arg(path.display().to_string()); + command + } else { + let mut command = + util::command::new_smol_command(node_runtime_path.as_os_str().display().to_string()); + command.arg(path_to_cli.display().to_string()); + command.arg("up"); + command.arg("--workspace-folder"); + command.arg(path.display().to_string()); + command + }; + + log::debug!("Running full devcontainer up command: {:?}", command); match command.output().await { Ok(output) => { @@ -139,17 +183,20 @@ async fn devcontainer_up( DevContainerError::DevContainerParseFailed }) } else { - log::error!( + let message = format!( "Non-success status running devcontainer up for workspace: out: {:?}, err: {:?}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); - Err(DevContainerError::DevContainerUpFailed) + + log::error!("{}", &message); + Err(DevContainerError::DevContainerUpFailed(message)) } } Err(e) => { - log::error!("Error running devcontainer up: {:?}", e); - Err(DevContainerError::DevContainerUpFailed) + let message = format!("Error running devcontainer up: {:?}", e); + log::error!("{}", &message); + Err(DevContainerError::DevContainerUpFailed(message)) } } } @@ -174,17 +221,19 @@ async fn devcontainer_read_configuration( DevContainerError::DevContainerParseFailed }) } else { - log::error!( + let message = format!( "Non-success status running devcontainer read-configuration for workspace: out: {:?}, err: {:?}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); - Err(DevContainerError::DevContainerUpFailed) + log::error!("{}", &message); + Err(DevContainerError::DevContainerUpFailed(message)) } } Err(e) => { - log::error!("Error running devcontainer read-configuration: {:?}", e); - Err(DevContainerError::DevContainerUpFailed) + let message = format!("Error running devcontainer read-configuration: {:?}", e); + log::error!("{}", &message); + Err(DevContainerError::DevContainerUpFailed(message)) } } } @@ -235,34 +284,44 @@ pub(crate) async fn start_dev_container( ) -> Result<(Connection, String), DevContainerError> { check_for_docker().await?; - let path_to_devcontainer_cli = ensure_devcontainer_cli(node_runtime).await?; + let (path_to_devcontainer_cli, found_in_path) = ensure_devcontainer_cli(&node_runtime).await?; let Some(directory) = project_directory(cx) else { return Err(DevContainerError::DevContainerNotFound); }; - if let Ok(DevContainerUp { - container_id, - remote_workspace_folder, - .. - }) = devcontainer_up(&path_to_devcontainer_cli, directory.clone()).await + match devcontainer_up( + &path_to_devcontainer_cli, + found_in_path, + &node_runtime, + directory.clone(), + ) + .await { - let project_name = get_project_name( - &path_to_devcontainer_cli, - directory, - remote_workspace_folder.clone(), - container_id.clone(), - ) - .await?; + Ok(DevContainerUp { + container_id, + remote_workspace_folder, + .. + }) => { + let project_name = get_project_name( + &path_to_devcontainer_cli, + directory, + remote_workspace_folder.clone(), + container_id.clone(), + ) + .await?; - let connection = Connection::DevContainer(DevContainerConnection { - name: project_name.into(), - container_id: container_id.into(), - }); + let connection = Connection::DevContainer(DevContainerConnection { + name: project_name.into(), + container_id: container_id.into(), + }); - Ok((connection, remote_workspace_folder)) - } else { - Err(DevContainerError::DevContainerUpFailed) + Ok((connection, remote_workspace_folder)) + } + Err(err) => { + let message = format!("Failed with nested error: {}", err); + Err(DevContainerError::DevContainerUpFailed(message)) + } } } @@ -270,9 +329,33 @@ pub(crate) async fn start_dev_container( pub(crate) enum DevContainerError { DockerNotAvailable, DevContainerCliNotAvailable, - DevContainerUpFailed, + DevContainerUpFailed(String), DevContainerNotFound, DevContainerParseFailed, + NodeRuntimeNotAvailable, +} + +impl Display for DevContainerError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + DevContainerError::DockerNotAvailable => + "Docker CLI not found on $PATH".to_string(), + DevContainerError::DevContainerCliNotAvailable => + "Docker not found on path".to_string(), + DevContainerError::DevContainerUpFailed(message) => { + format!("DevContainer creation failed with error: {}", message) + } + DevContainerError::DevContainerNotFound => "TODO what".to_string(), + DevContainerError::DevContainerParseFailed => + "Failed to parse file .devcontainer/devcontainer.json".to_string(), + DevContainerError::NodeRuntimeNotAvailable => + "Cannot find a valid node runtime".to_string(), + } + ) + } } #[cfg(test)]