From c4dfd2114763938af9810fa8b9b99e5a7967b51e Mon Sep 17 00:00:00 2001 From: YZH Date: Sat, 10 Aug 2024 16:51:57 +0800 Subject: [PATCH 1/8] fix: fix two typo --- crates/volta-core/src/project/mod.rs | 10 ++++----- crates/volta-core/src/project/tests.rs | 28 +++++++++++++------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/volta-core/src/project/mod.rs b/crates/volta-core/src/project/mod.rs index 61c78d5a6..a1e27ad8f 100644 --- a/crates/volta-core/src/project/mod.rs +++ b/crates/volta-core/src/project/mod.rs @@ -36,12 +36,12 @@ impl LazyProject { } pub fn get(&self) -> Fallible> { - let project = self.project.get_or_try_init(Project::for_current_dir)?; + let project = self.project.get_or_try_init(Project::from_current_dir)?; Ok(project.as_ref()) } pub fn get_mut(&mut self) -> Fallible> { - let _ = self.project.get_or_try_init(Project::for_current_dir)?; + let _ = self.project.get_or_try_init(Project::from_current_dir)?; Ok(self.project.get_mut().unwrap().as_mut()) } } @@ -57,15 +57,15 @@ pub struct Project { impl Project { /// Creates an optional Project instance from the current directory - fn for_current_dir() -> Fallible> { + fn from_current_dir() -> Fallible> { let current_dir = env::current_dir().with_context(|| ErrorKind::CurrentDirError)?; - Self::for_dir(current_dir) + Self::from_dir(current_dir) } /// Creates an optional Project instance from the specified directory /// /// Will search ancestors to find a `package.json` and use that as the root of the project - fn for_dir(base_dir: PathBuf) -> Fallible> { + fn from_dir(base_dir: PathBuf) -> Fallible> { match find_closest_root(base_dir) { Some(mut project) => { project.push("package.json"); diff --git a/crates/volta-core/src/project/tests.rs b/crates/volta-core/src/project/tests.rs index a5f774727..61984a874 100644 --- a/crates/volta-core/src/project/tests.rs +++ b/crates/volta-core/src/project/tests.rs @@ -48,7 +48,7 @@ mod project { #[test] fn manifest_file() { let project_path = fixture_path(&["basic"]); - let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let test_project = Project::from_dir(project_path).unwrap().unwrap(); let expected = fixture_path(&["basic", "package.json"]); assert_eq!(test_project.manifest_file(), &expected); @@ -58,7 +58,7 @@ mod project { fn workspace_roots() { let project_path = fixture_path(&["nested", "subproject", "inner_project"]); let expected_base = project_path.clone(); - let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let test_project = Project::from_dir(project_path).unwrap().unwrap(); let expected = vec![ &*expected_base, @@ -72,7 +72,7 @@ mod project { #[test] fn platform_simple() { let project_path = fixture_path(&["basic"]); - let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let test_project = Project::from_dir(project_path).unwrap().unwrap(); let platform = test_project.platform().unwrap(); assert_eq!(platform.node, "6.11.1".parse().unwrap()); @@ -83,7 +83,7 @@ mod project { #[test] fn platform_workspace() { let project_path = fixture_path(&["nested", "subproject", "inner_project"]); - let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let test_project = Project::from_dir(project_path).unwrap().unwrap(); let platform = test_project.platform().unwrap(); // From the top level `nested/package.json` @@ -97,7 +97,7 @@ mod project { #[test] fn direct_dependencies_single() { let project_path = fixture_path(&["basic"]); - let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let test_project = Project::from_dir(project_path).unwrap().unwrap(); // eslint, rsvp, bin-1, and bin-2 are direct dependencies assert!(test_project.has_direct_dependency("eslint")); @@ -112,7 +112,7 @@ mod project { #[test] fn direct_dependencies_workspace() { let project_path = fixture_path(&["nested", "subproject", "inner_project"]); - let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let test_project = Project::from_dir(project_path).unwrap().unwrap(); // express and typescript are direct dependencies of the innermost project assert!(test_project.has_direct_dependency("express")); @@ -131,7 +131,7 @@ mod project { #[test] fn find_bin_single() { let project_path = fixture_path(&["basic"]); - let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let test_project = Project::from_dir(project_path).unwrap().unwrap(); assert_eq!( test_project.find_bin("rsvp"), @@ -145,7 +145,7 @@ mod project { fn find_bin_workspace() { // eslint, rsvp, tsc let project_path = fixture_path(&["nested", "subproject", "inner_project"]); - let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let test_project = Project::from_dir(project_path).unwrap().unwrap(); // eslint is a binary in the root workspace assert_eq!( @@ -185,7 +185,7 @@ mod project { fn detects_workspace_cycles() { // cycle-1 has a cycle with the original package.json let cycle_path = fixture_path(&["cycle-1"]); - let project_error = Project::for_dir(cycle_path).unwrap_err(); + let project_error = Project::from_dir(cycle_path).unwrap_err(); match project_error.kind() { ErrorKind::ExtensionCycleError { paths, duplicate } => { @@ -201,7 +201,7 @@ mod project { // cycle-2 has a cycle with 2 separate extensions, not including the original package.json let cycle_path = fixture_path(&["cycle-2"]); - let project_error = Project::for_dir(cycle_path).unwrap_err(); + let project_error = Project::from_dir(cycle_path).unwrap_err(); match project_error.kind() { ErrorKind::ExtensionCycleError { paths, duplicate } => { @@ -224,28 +224,28 @@ mod needs_yarn_run { #[test] fn project_does_not_need_yarn_run() { let project_path = fixture_path(&["basic"]); - let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let test_project = Project::from_dir(project_path).unwrap().unwrap(); assert!(!test_project.needs_yarn_run()); } #[test] fn project_has_yarnrc_yml() { let project_path = fixture_path(&["yarn", "yarnrc-yml"]); - let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let test_project = Project::from_dir(project_path).unwrap().unwrap(); assert!(test_project.needs_yarn_run()); } #[test] fn project_has_pnp_js() { let project_path = fixture_path(&["yarn", "pnp-js"]); - let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let test_project = Project::from_dir(project_path).unwrap().unwrap(); assert!(test_project.needs_yarn_run()); } #[test] fn project_has_pnp_cjs() { let project_path = fixture_path(&["yarn", "pnp-cjs"]); - let test_project = Project::for_dir(project_path).unwrap().unwrap(); + let test_project = Project::from_dir(project_path).unwrap().unwrap(); assert!(test_project.needs_yarn_run()); } } From 434576ede7d60a7ef03a548c2337617e9ae5c95c Mon Sep 17 00:00:00 2001 From: morishin Date: Sat, 20 Jan 2024 15:40:48 +0900 Subject: [PATCH 2/8] Enable `volta uninstall node@x.x.x` --- crates/volta-core/src/tool/mod.rs | 2 ++ crates/volta-core/src/tool/node/mod.rs | 21 +++++++++++++++++++-- crates/volta-core/src/tool/npm/mod.rs | 13 +++++++++++++ crates/volta-core/src/tool/package/mod.rs | 4 ++++ crates/volta-core/src/tool/pnpm/mod.rs | 14 ++++++++++++++ crates/volta-core/src/tool/yarn/mod.rs | 6 ++++++ src/command/uninstall.rs | 17 +---------------- 7 files changed, 59 insertions(+), 18 deletions(-) diff --git a/crates/volta-core/src/tool/mod.rs b/crates/volta-core/src/tool/mod.rs index a464107df..5c237d674 100644 --- a/crates/volta-core/src/tool/mod.rs +++ b/crates/volta-core/src/tool/mod.rs @@ -65,6 +65,8 @@ pub trait Tool: Display { fn install(self: Box, session: &mut Session) -> Fallible<()>; /// Pin a tool in the local project so that it is usable within the project fn pin(self: Box, session: &mut Session) -> Fallible<()>; + /// Uninstall a tool + fn uninstall(self: Box, session: &mut Session) -> Fallible<()>; } /// Specification for a tool and its associated version. diff --git a/crates/volta-core/src/tool/node/mod.rs b/crates/volta-core/src/tool/node/mod.rs index c9788be59..ca67a8c00 100644 --- a/crates/volta-core/src/tool/node/mod.rs +++ b/crates/volta-core/src/tool/node/mod.rs @@ -5,12 +5,14 @@ use super::{ info_pinned, info_project_version, FetchStatus, Tool, }; use crate::error::{ErrorKind, Fallible}; +use crate::fs::remove_dir_if_exists; use crate::inventory::node_available; +use crate::layout::volta_home; use crate::session::Session; -use crate::style::{note_prefix, tool_version}; +use crate::style::{note_prefix, success_prefix, tool_version}; use crate::sync::VoltaLock; use cfg_if::cfg_if; -use log::info; +use log::{info, warn}; use node_semver::Version; mod fetch; @@ -282,6 +284,21 @@ impl Tool for Node { Err(ErrorKind::NotInPackage.into()) } } + fn uninstall(self: Box, _session: &mut Session) -> Fallible<()> { + let home = volta_home()?; + // Acquire a lock on the Volta directory, if possible, to prevent concurrent changes + let _lock: Result = VoltaLock::acquire(); + + let node_dir = home.node_image_root_dir().join(self.version.to_string()); + if node_dir.exists() { + remove_dir_if_exists(&node_dir)?; + info!("{} 'node@{}' uninstalled", success_prefix(), self.version); + } else { + warn!("No version 'node@{}' found to uninstall", self.version); + } + + Ok(()) + } } impl Display for Node { diff --git a/crates/volta-core/src/tool/npm/mod.rs b/crates/volta-core/src/tool/npm/mod.rs index 2e4ca81fa..ec141b594 100644 --- a/crates/volta-core/src/tool/npm/mod.rs +++ b/crates/volta-core/src/tool/npm/mod.rs @@ -87,6 +87,12 @@ impl Tool for Npm { Err(ErrorKind::NotInPackage.into()) } } + fn uninstall(self: Box, _session: &mut Session) -> Fallible<()> { + Err(ErrorKind::Unimplemented { + feature: "Uninstalling npm".into(), + } + .into()) + } } impl Display for Npm { @@ -169,6 +175,13 @@ impl Tool for BundledNpm { None => Err(ErrorKind::NotInPackage.into()), } } + + fn uninstall(self: Box, _session: &mut Session) -> Fallible<()> { + Err(ErrorKind::Unimplemented { + feature: "Uninstalling bundled npm".into(), + } + .into()) + } } impl Display for BundledNpm { diff --git a/crates/volta-core/src/tool/package/mod.rs b/crates/volta-core/src/tool/package/mod.rs index c8ae39fc5..6e2592861 100644 --- a/crates/volta-core/src/tool/package/mod.rs +++ b/crates/volta-core/src/tool/package/mod.rs @@ -108,6 +108,10 @@ impl Tool for Package { fn pin(self: Box, _session: &mut Session) -> Fallible<()> { Err(ErrorKind::CannotPinPackage { package: self.name }.into()) } + + fn uninstall(self: Box, _session: &mut Session) -> Fallible<()> { + uninstall(&self.name) + } } impl Display for Package { diff --git a/crates/volta-core/src/tool/pnpm/mod.rs b/crates/volta-core/src/tool/pnpm/mod.rs index fda2d34c5..96a3ad835 100644 --- a/crates/volta-core/src/tool/pnpm/mod.rs +++ b/crates/volta-core/src/tool/pnpm/mod.rs @@ -1,4 +1,5 @@ use node_semver::Version; +use std::env; use std::fmt::{self, Display}; use crate::error::{ErrorKind, Fallible}; @@ -6,6 +7,7 @@ use crate::inventory::pnpm_available; use crate::session::Session; use crate::style::tool_version; use crate::sync::VoltaLock; +use crate::VOLTA_FEATURE_PNPM; use super::{ check_fetched, check_shim_reachable, debug_already_fetched, info_fetched, info_installed, @@ -15,6 +17,7 @@ use super::{ mod fetch; mod resolve; +use super::package::uninstall; pub use resolve::resolve; /// The Tool implementation for fetching and installing pnpm @@ -88,6 +91,17 @@ impl Tool for Pnpm { Err(ErrorKind::NotInPackage.into()) } } + + fn uninstall(self: Box, _session: &mut Session) -> Fallible<()> { + if env::var_os(VOLTA_FEATURE_PNPM).is_some() { + Err(ErrorKind::Unimplemented { + feature: "Uninstalling pnpm".into(), + } + .into()) + } else { + uninstall("pnpm") + } + } } impl Display for Pnpm { diff --git a/crates/volta-core/src/tool/yarn/mod.rs b/crates/volta-core/src/tool/yarn/mod.rs index ae18be030..3903cc744 100644 --- a/crates/volta-core/src/tool/yarn/mod.rs +++ b/crates/volta-core/src/tool/yarn/mod.rs @@ -86,6 +86,12 @@ impl Tool for Yarn { Err(ErrorKind::NotInPackage.into()) } } + fn uninstall(self: Box, _session: &mut Session) -> Fallible<()> { + Err(ErrorKind::Unimplemented { + feature: "Uninstalling yarn".into(), + } + .into()) + } } impl Display for Yarn { diff --git a/src/command/uninstall.rs b/src/command/uninstall.rs index 6ed1a83b1..824fc2e57 100644 --- a/src/command/uninstall.rs +++ b/src/command/uninstall.rs @@ -1,7 +1,6 @@ use volta_core::error::{ErrorKind, ExitCode, Fallible}; use volta_core::session::{ActivityKind, Session}; use volta_core::tool; -use volta_core::version::VersionSpec; use crate::command::Command; @@ -16,21 +15,7 @@ impl Command for Uninstall { session.add_event_start(ActivityKind::Uninstall); let tool = tool::Spec::try_from_str(&self.tool)?; - - // For packages, specifically report that we do not support uninstalling - // specific versions. For runtimes and package managers, we currently - // *intentionally* let this fall through to inform the user that we do - // not support uninstalling those *at all*. - if let tool::Spec::Package(_name, version) = &tool { - let VersionSpec::None = version else { - return Err(ErrorKind::Unimplemented { - feature: "uninstalling specific versions of tools".into(), - } - .into()); - }; - } - - tool.uninstall()?; + tool.resolve(session)?.uninstall(session)?; session.add_event_end(ActivityKind::Uninstall, ExitCode::Success); Ok(ExitCode::Success) From bea9d215818273352a18d9bda9a3fb51fcadc617 Mon Sep 17 00:00:00 2001 From: YZH Date: Tue, 20 Aug 2024 16:59:04 +0800 Subject: [PATCH 3/8] Enhance uninstall node --- src/command/uninstall.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/command/uninstall.rs b/src/command/uninstall.rs index 824fc2e57..8f44df90b 100644 --- a/src/command/uninstall.rs +++ b/src/command/uninstall.rs @@ -1,21 +1,23 @@ use volta_core::error::{ErrorKind, ExitCode, Fallible}; use volta_core::session::{ActivityKind, Session}; -use volta_core::tool; +use volta_core::tool::{self, Spec}; use crate::command::Command; #[derive(clap::Args)] pub(crate) struct Uninstall { - /// The tool to uninstall, like `ember-cli-update`, `typescript`, or - tool: String, + /// Tools to uninstall, like `node`, `yarn@latest` or `your-package`. + #[arg(value_name = "tool[@version]", required = true)] + tools: Vec, } impl Command for Uninstall { fn run(self, session: &mut Session) -> Fallible { session.add_event_start(ActivityKind::Uninstall); - let tool = tool::Spec::try_from_str(&self.tool)?; - tool.resolve(session)?.uninstall(session)?; + for tool in Spec::from_strings(&self.tools, "uninstall")? { + tool.resolve(session)?.uninstall(session)?; + } session.add_event_end(ActivityKind::Uninstall, ExitCode::Success); Ok(ExitCode::Success) From 61e0cefa6679a21696aa46eb69fe9d3a61f0ebc7 Mon Sep 17 00:00:00 2001 From: YZH Date: Tue, 20 Aug 2024 18:24:24 +0800 Subject: [PATCH 4/8] Fix uninstall package with specific version error --- crates/volta-core/src/tool/package/mod.rs | 10 ++++++++++ src/command/uninstall.rs | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/volta-core/src/tool/package/mod.rs b/crates/volta-core/src/tool/package/mod.rs index 6e2592861..cad2b0c1e 100644 --- a/crates/volta-core/src/tool/package/mod.rs +++ b/crates/volta-core/src/tool/package/mod.rs @@ -110,6 +110,16 @@ impl Tool for Package { } fn uninstall(self: Box, _session: &mut Session) -> Fallible<()> { + // For packages, specifically report that we do not support uninstalling + // specific versions. For package managers, we currently + // *intentionally* let this fall through to inform the user that we do + // not support uninstalling those *at all*. + let VersionSpec::None = &self.version else { + return Err(ErrorKind::Unimplemented { + feature: "uninstalling specific versions of tools".into(), + } + .into()); + }; uninstall(&self.name) } } diff --git a/src/command/uninstall.rs b/src/command/uninstall.rs index 8f44df90b..20d1beadd 100644 --- a/src/command/uninstall.rs +++ b/src/command/uninstall.rs @@ -1,6 +1,6 @@ -use volta_core::error::{ErrorKind, ExitCode, Fallible}; +use volta_core::error::{ExitCode, Fallible}; use volta_core::session::{ActivityKind, Session}; -use volta_core::tool::{self, Spec}; +use volta_core::tool::Spec; use crate::command::Command; From 8208b36aeaf4f7c2238752d07954250bef12b9e6 Mon Sep 17 00:00:00 2001 From: YZH Date: Tue, 20 Aug 2024 23:57:37 +0800 Subject: [PATCH 5/8] Add tests --- .vscode/settings.json | 3 +- tests/acceptance/volta_uninstall.rs | 64 ++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ad92582bd..597d1f000 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "editor.formatOnSave": true + "editor.formatOnSave": true, + "rust-analyzer.cargo.features": "all" } diff --git a/tests/acceptance/volta_uninstall.rs b/tests/acceptance/volta_uninstall.rs index 131b7e779..e838efeff 100644 --- a/tests/acceptance/volta_uninstall.rs +++ b/tests/acceptance/volta_uninstall.rs @@ -1,10 +1,46 @@ //! Tests for `volta uninstall`. use crate::support::sandbox::{sandbox, Sandbox}; +use cfg_if::cfg_if; use hamcrest2::assert_that; use hamcrest2::prelude::*; use test_support::matchers::execs; +fn platform_with_node_npm(node: &str, npm: &str) -> String { + format!( + r#"{{ + "node": {{ + "runtime": "{}", + "npm": "{}" + }}, + "pnpm": null, + "yarn": null +}}"#, + node, npm + ) +} +fn node_bin(version: &str) -> String { + cfg_if! { + if #[cfg(target_os = "windows")] { + format!( + r#"@echo off +echo Node version {} +echo node args: %* +"#, + version + ) + } else { + format!( + r#"#!/bin/sh +echo "Node version {}" +echo "node args: $@" +"#, + version + ) + } + } +} + const PKG_CONFIG_BASIC: &str = r#"{ "name": "cowsay", "version": "1.4.0", @@ -206,12 +242,30 @@ fn uninstall_package_orphaned_bins() { } #[test] -fn uninstall_runtime() { - let s = sandbox().build(); +fn uninstall_nonexistent_runtime() { + let s = sandbox().env(VOLTA_LOGLEVEL, "info").build(); assert_that!( - s.volta("uninstall node"), + s.volta("uninstall node@20.16.0"), execs() - .with_status(1) - .with_stderr_contains("[..]error: Uninstalling node is not supported yet.") + .with_status(0) + .with_stderr_contains("[..]No version 'node@20.16.0' found to uninstall") ) } + +#[test] +fn uninstall_runtime_basic() { + // basic uninstall - everything exists, and everything except the cached + // inventory files should not be deleted + let s = sandbox() + .platform(&platform_with_node_npm("20.16.0", "10.8.1")) + .setup_node_binary("20.16.0", "10.8.1", &node_bin("20.16.0")) + .env(VOLTA_LOGLEVEL, "info") + .build(); + + assert_that!( + s.volta("uninstall node@20.16.0"), + execs() + .with_status(0) + .with_stdout_contains("[..]'node@20.16.0' uninstalled") + ); +} From 287b96650278951c1db841ef3b605921dccb79dd Mon Sep 17 00:00:00 2001 From: YZH Date: Wed, 21 Aug 2024 00:12:11 +0800 Subject: [PATCH 6/8] Revert "fix: fix two typo" This reverts commit c4dfd2114763938af9810fa8b9b99e5a7967b51e. --- crates/volta-core/src/project/mod.rs | 10 ++++----- crates/volta-core/src/project/tests.rs | 28 +++++++++++++------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/volta-core/src/project/mod.rs b/crates/volta-core/src/project/mod.rs index a1e27ad8f..61c78d5a6 100644 --- a/crates/volta-core/src/project/mod.rs +++ b/crates/volta-core/src/project/mod.rs @@ -36,12 +36,12 @@ impl LazyProject { } pub fn get(&self) -> Fallible> { - let project = self.project.get_or_try_init(Project::from_current_dir)?; + let project = self.project.get_or_try_init(Project::for_current_dir)?; Ok(project.as_ref()) } pub fn get_mut(&mut self) -> Fallible> { - let _ = self.project.get_or_try_init(Project::from_current_dir)?; + let _ = self.project.get_or_try_init(Project::for_current_dir)?; Ok(self.project.get_mut().unwrap().as_mut()) } } @@ -57,15 +57,15 @@ pub struct Project { impl Project { /// Creates an optional Project instance from the current directory - fn from_current_dir() -> Fallible> { + fn for_current_dir() -> Fallible> { let current_dir = env::current_dir().with_context(|| ErrorKind::CurrentDirError)?; - Self::from_dir(current_dir) + Self::for_dir(current_dir) } /// Creates an optional Project instance from the specified directory /// /// Will search ancestors to find a `package.json` and use that as the root of the project - fn from_dir(base_dir: PathBuf) -> Fallible> { + fn for_dir(base_dir: PathBuf) -> Fallible> { match find_closest_root(base_dir) { Some(mut project) => { project.push("package.json"); diff --git a/crates/volta-core/src/project/tests.rs b/crates/volta-core/src/project/tests.rs index 61984a874..a5f774727 100644 --- a/crates/volta-core/src/project/tests.rs +++ b/crates/volta-core/src/project/tests.rs @@ -48,7 +48,7 @@ mod project { #[test] fn manifest_file() { let project_path = fixture_path(&["basic"]); - let test_project = Project::from_dir(project_path).unwrap().unwrap(); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); let expected = fixture_path(&["basic", "package.json"]); assert_eq!(test_project.manifest_file(), &expected); @@ -58,7 +58,7 @@ mod project { fn workspace_roots() { let project_path = fixture_path(&["nested", "subproject", "inner_project"]); let expected_base = project_path.clone(); - let test_project = Project::from_dir(project_path).unwrap().unwrap(); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); let expected = vec![ &*expected_base, @@ -72,7 +72,7 @@ mod project { #[test] fn platform_simple() { let project_path = fixture_path(&["basic"]); - let test_project = Project::from_dir(project_path).unwrap().unwrap(); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); let platform = test_project.platform().unwrap(); assert_eq!(platform.node, "6.11.1".parse().unwrap()); @@ -83,7 +83,7 @@ mod project { #[test] fn platform_workspace() { let project_path = fixture_path(&["nested", "subproject", "inner_project"]); - let test_project = Project::from_dir(project_path).unwrap().unwrap(); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); let platform = test_project.platform().unwrap(); // From the top level `nested/package.json` @@ -97,7 +97,7 @@ mod project { #[test] fn direct_dependencies_single() { let project_path = fixture_path(&["basic"]); - let test_project = Project::from_dir(project_path).unwrap().unwrap(); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); // eslint, rsvp, bin-1, and bin-2 are direct dependencies assert!(test_project.has_direct_dependency("eslint")); @@ -112,7 +112,7 @@ mod project { #[test] fn direct_dependencies_workspace() { let project_path = fixture_path(&["nested", "subproject", "inner_project"]); - let test_project = Project::from_dir(project_path).unwrap().unwrap(); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); // express and typescript are direct dependencies of the innermost project assert!(test_project.has_direct_dependency("express")); @@ -131,7 +131,7 @@ mod project { #[test] fn find_bin_single() { let project_path = fixture_path(&["basic"]); - let test_project = Project::from_dir(project_path).unwrap().unwrap(); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); assert_eq!( test_project.find_bin("rsvp"), @@ -145,7 +145,7 @@ mod project { fn find_bin_workspace() { // eslint, rsvp, tsc let project_path = fixture_path(&["nested", "subproject", "inner_project"]); - let test_project = Project::from_dir(project_path).unwrap().unwrap(); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); // eslint is a binary in the root workspace assert_eq!( @@ -185,7 +185,7 @@ mod project { fn detects_workspace_cycles() { // cycle-1 has a cycle with the original package.json let cycle_path = fixture_path(&["cycle-1"]); - let project_error = Project::from_dir(cycle_path).unwrap_err(); + let project_error = Project::for_dir(cycle_path).unwrap_err(); match project_error.kind() { ErrorKind::ExtensionCycleError { paths, duplicate } => { @@ -201,7 +201,7 @@ mod project { // cycle-2 has a cycle with 2 separate extensions, not including the original package.json let cycle_path = fixture_path(&["cycle-2"]); - let project_error = Project::from_dir(cycle_path).unwrap_err(); + let project_error = Project::for_dir(cycle_path).unwrap_err(); match project_error.kind() { ErrorKind::ExtensionCycleError { paths, duplicate } => { @@ -224,28 +224,28 @@ mod needs_yarn_run { #[test] fn project_does_not_need_yarn_run() { let project_path = fixture_path(&["basic"]); - let test_project = Project::from_dir(project_path).unwrap().unwrap(); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); assert!(!test_project.needs_yarn_run()); } #[test] fn project_has_yarnrc_yml() { let project_path = fixture_path(&["yarn", "yarnrc-yml"]); - let test_project = Project::from_dir(project_path).unwrap().unwrap(); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); assert!(test_project.needs_yarn_run()); } #[test] fn project_has_pnp_js() { let project_path = fixture_path(&["yarn", "pnp-js"]); - let test_project = Project::from_dir(project_path).unwrap().unwrap(); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); assert!(test_project.needs_yarn_run()); } #[test] fn project_has_pnp_cjs() { let project_path = fixture_path(&["yarn", "pnp-cjs"]); - let test_project = Project::from_dir(project_path).unwrap().unwrap(); + let test_project = Project::for_dir(project_path).unwrap().unwrap(); assert!(test_project.needs_yarn_run()); } } From e5bd9071b5293d91641b9f1e253a0d245e86f72f Mon Sep 17 00:00:00 2001 From: YZH Date: Mon, 2 Sep 2024 23:02:46 +0800 Subject: [PATCH 7/8] Add uninstall support for volta run --- crates/volta-core/src/run/executor.rs | 6 ++--- crates/volta-core/src/tool/mod.rs | 32 --------------------------- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/crates/volta-core/src/run/executor.rs b/crates/volta-core/src/run/executor.rs index 175223ed4..b2c210bee 100644 --- a/crates/volta-core/src/run/executor.rs +++ b/crates/volta-core/src/run/executor.rs @@ -77,7 +77,7 @@ impl Executor { Executor::PackageLink(cmd) => cmd.execute(session), Executor::PackageUpgrade(cmd) => cmd.execute(session), Executor::InternalInstall(cmd) => cmd.execute(session), - Executor::Uninstall(cmd) => cmd.execute(), + Executor::Uninstall(cmd) => cmd.execute(session), Executor::Multiple(executors) => { info!( "{} Volta is processing each package separately", @@ -543,14 +543,14 @@ impl UninstallCommand { } /// Runs the uninstall with Volta's internal uninstall logic - fn execute(self) -> Fallible { + fn execute(self, session: &mut Session) -> Fallible { info!( "{} using Volta to uninstall {}", note_prefix(), self.tool.name() ); - self.tool.uninstall()?; + self.tool.resolve(session)?.uninstall(session)?; Ok(ExitStatus::from_raw(0)) } diff --git a/crates/volta-core/src/tool/mod.rs b/crates/volta-core/src/tool/mod.rs index 5c237d674..8f2de8bbe 100644 --- a/crates/volta-core/src/tool/mod.rs +++ b/crates/volta-core/src/tool/mod.rs @@ -117,38 +117,6 @@ impl Spec { } } - /// Uninstall a tool, removing it from the local inventory - /// - /// This is implemented on Spec, instead of Resolved, because there is currently no need to - /// resolve the specific version before uninstalling a tool. - pub fn uninstall(self) -> Fallible<()> { - match self { - Spec::Node(_) => Err(ErrorKind::Unimplemented { - feature: "Uninstalling node".into(), - } - .into()), - Spec::Npm(_) => Err(ErrorKind::Unimplemented { - feature: "Uninstalling npm".into(), - } - .into()), - Spec::Pnpm(_) => { - if env::var_os(VOLTA_FEATURE_PNPM).is_some() { - Err(ErrorKind::Unimplemented { - feature: "Uninstalling pnpm".into(), - } - .into()) - } else { - package::uninstall("pnpm") - } - } - Spec::Yarn(_) => Err(ErrorKind::Unimplemented { - feature: "Uninstalling yarn".into(), - } - .into()), - Spec::Package(name, _) => package::uninstall(&name), - } - } - /// The name of the tool, without the version, used for messaging pub fn name(&self) -> &str { match self { From 7cfd2506d5dcedd38bf7b7272cca9eef1c71225d Mon Sep 17 00:00:00 2001 From: YZH Date: Tue, 3 Sep 2024 12:29:11 +0800 Subject: [PATCH 8/8] Delete tar --- crates/volta-core/src/tool/node/mod.rs | 27 ++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/volta-core/src/tool/node/mod.rs b/crates/volta-core/src/tool/node/mod.rs index ca67a8c00..a44d43c46 100644 --- a/crates/volta-core/src/tool/node/mod.rs +++ b/crates/volta-core/src/tool/node/mod.rs @@ -4,8 +4,8 @@ use super::{ check_fetched, check_shim_reachable, debug_already_fetched, info_fetched, info_installed, info_pinned, info_project_version, FetchStatus, Tool, }; -use crate::error::{ErrorKind, Fallible}; -use crate::fs::remove_dir_if_exists; +use crate::error::{Context, ErrorKind, Fallible}; +use crate::fs::{dir_entry_match, ok_if_not_found, remove_dir_if_exists, remove_file_if_exists}; use crate::inventory::node_available; use crate::layout::volta_home; use crate::session::Session; @@ -290,6 +290,29 @@ impl Tool for Node { let _lock: Result = VoltaLock::acquire(); let node_dir = home.node_image_root_dir().join(self.version.to_string()); + + dir_entry_match(home.node_inventory_dir(), |entry| { + let path = entry.path(); + + if path.is_file() { + match path.file_name().and_then(|name| name.to_str()) { + Some(file_name) if file_name.contains(&self.version.to_string()) => Some(path), + _ => None, + } + } else { + None + } + }) + .or_else(ok_if_not_found) + .with_context(|| ErrorKind::ReadDirError { + dir: home.node_inventory_dir().to_path_buf(), + }) + .map(|files| { + files.iter().for_each(|file| { + remove_file_if_exists(file); + }) + }); + if node_dir.exists() { remove_dir_if_exists(&node_dir)?; info!("{} 'node@{}' uninstalled", success_prefix(), self.version);