From 79469698c045b3b3a5c48a9d542e2763cb3d4b82 Mon Sep 17 00:00:00 2001 From: Liang Mi Date: Tue, 26 May 2026 21:30:49 +0800 Subject: [PATCH 1/7] implement --- crates/vite_global_cli/src/cli.rs | 3 +- .../src/commands/global/install.rs | 23 +++++-- .../src/commands/global/mod.rs | 65 +++++++++++++++---- .../src/commands/global/outdated.rs | 5 +- 4 files changed, 77 insertions(+), 19 deletions(-) diff --git a/crates/vite_global_cli/src/cli.rs b/crates/vite_global_cli/src/cli.rs index a0f4e0abfa..741c3e9a9a 100644 --- a/crates/vite_global_cli/src/cli.rs +++ b/crates/vite_global_cli/src/cli.rs @@ -671,7 +671,8 @@ async fn managed_update( continue; } - let (package_name, _) = global::parse_package_spec(package); + // It is not a local package, so `parse_package_spec` there won't return `Err()` + let (package_name, _) = global::parse_package_spec(package).unwrap(); if PackageMetadata::load(&package_name).await?.is_some() { managed_specs.push(package.clone()); } else { diff --git a/crates/vite_global_cli/src/commands/global/install.rs b/crates/vite_global_cli/src/commands/global/install.rs index b28bdc39e2..5029a13d30 100644 --- a/crates/vite_global_cli/src/commands/global/install.rs +++ b/crates/vite_global_cli/src/commands/global/install.rs @@ -105,7 +105,11 @@ pub async fn install( let mut packages = IndexMap::::new(); for package_spec in package_specs { // Parse package spec (e.g., "typescript", "typescript@5.0.0", "@scope/pkg") - let (package_name, _version_spec) = parse_package_spec(package_spec); + + let (package_name, _version_spec) = match parse_package_spec(package_spec) { + Ok(result) => result, + Err(error) => return Err((Some(package_spec.clone()), error)), + }; packages.insert(package_name, Package { spec: package_spec, staging_dir: None }); } let packages_count = packages.len(); @@ -395,7 +399,7 @@ async fn install_one( /// 1. Try to use PackageMetadata for binary list /// 2. Fallback to scanning BinConfig files for orphaned binaries pub async fn uninstall(package_name: &str, dry_run: bool) -> Result<(), Error> { - let (package_name, _) = parse_package_spec(package_name); + let (package_name, _) = parse_package_spec(package_name)?; // Phase 1: Try to use PackageMetadata for binary list let bins = if let Some(metadata) = PackageMetadata::load(&package_name).await? { @@ -881,28 +885,35 @@ mod tests { #[test] fn test_parse_package_spec_simple() { - let (name, version) = parse_package_spec("typescript"); + let (name, version) = parse_package_spec("typescript").unwrap(); assert_eq!(name, "typescript"); assert_eq!(version, None); } #[test] fn test_parse_package_spec_with_version() { - let (name, version) = parse_package_spec("typescript@5.0.0"); + let (name, version) = parse_package_spec("typescript@5.0.0").unwrap(); assert_eq!(name, "typescript"); assert_eq!(version, Some("5.0.0".to_string())); } #[test] fn test_parse_package_spec_scoped() { - let (name, version) = parse_package_spec("@types/node"); + let (name, version) = parse_package_spec("@types/node").unwrap(); assert_eq!(name, "@types/node"); assert_eq!(version, None); } #[test] fn test_parse_package_spec_scoped_with_version() { - let (name, version) = parse_package_spec("@types/node@20.0.0"); + let (name, version) = parse_package_spec("@types/node@20.0.0").unwrap(); + assert_eq!(name, "@types/node"); + assert_eq!(version, Some("20.0.0".to_string())); + } + + #[test] + fn test_parse_package_spec_local() { + let (name, version) = parse_package_spec("./packages/cli").unwrap(); assert_eq!(name, "@types/node"); assert_eq!(version, Some("20.0.0".to_string())); } diff --git a/crates/vite_global_cli/src/commands/global/mod.rs b/crates/vite_global_cli/src/commands/global/mod.rs index 4915a68d37..3c49c01dda 100644 --- a/crates/vite_global_cli/src/commands/global/mod.rs +++ b/crates/vite_global_cli/src/commands/global/mod.rs @@ -133,20 +133,63 @@ pub(crate) fn is_local_package_spec(spec: &str) -> bool { } /// Parse package spec into name and optional version. -pub(crate) fn parse_package_spec(spec: &str) -> (String, Option) { - if spec.starts_with('@') { - if let Some(idx) = spec[1..].find('@') { - let idx = idx + 1; - return (spec[..idx].to_string(), Some(spec[idx + 1..].to_string())); +/// For local packages, it will read the package.json and return the real package name +/// +/// It will never return an `Err()` if it is not a local package +pub(crate) fn parse_package_spec(spec: &str) -> Result<(String, Option), Error> { + if is_local_package_spec(spec) { + let path_spec = spec.strip_prefix("file:").unwrap_or(spec); + let path = std::path::Path::new(path_spec); + let package_dir = if path.is_absolute() { + AbsolutePathBuf::new(path.to_path_buf()).ok_or_else(|| { + Error::ConfigError(format!("Invalid local package path {spec}").into()) + })? + } else { + current_dir() + .map_err(|error| { + Error::ConfigError(format!("Cannot get current directory: {error}").into()) + })? + .join(path) + }; + let package_json_path = package_dir.join("package.json"); + let package_json_content = + std::fs::read_to_string(package_json_path.as_path()).map_err(|error| { + Error::ConfigError( + format!( + "Failed to read package.json for local package {spec} at {}: {error}", + package_json_path.as_path().display() + ) + .into(), + ) + })?; + let package_json: serde_json::Value = + serde_json::from_str(&package_json_content).map_err(Error::JsonError)?; + let Some(package_name) = package_json.get("name").and_then(|name| name.as_str()) else { + return Err(Error::ConfigError( + format!( + "Local package {spec} must have a string name in {}", + package_json_path.as_path().display() + ) + .into(), + )); + }; + + Ok((package_name.to_string(), None)) + } else { + if spec.starts_with('@') { + if let Some(idx) = spec[1..].find('@') { + let idx = idx + 1; + return Ok((spec[..idx].to_string(), Some(spec[idx + 1..].to_string()))); + } + return Ok((spec.to_string(), None)); } - return (spec.to_string(), None); - } - if let Some(idx) = spec.find('@') { - return (spec[..idx].to_string(), Some(spec[idx + 1..].to_string())); - } + if let Some(idx) = spec.find('@') { + return Ok((spec[..idx].to_string(), Some(spec[idx + 1..].to_string()))); + } - (spec.to_string(), None) + Ok((spec.to_string(), None)) + } } fn parse_npm_view_version(stdout: &[u8]) -> Result { diff --git a/crates/vite_global_cli/src/commands/global/outdated.rs b/crates/vite_global_cli/src/commands/global/outdated.rs index 233473caf0..e8b65d83a7 100644 --- a/crates/vite_global_cli/src/commands/global/outdated.rs +++ b/crates/vite_global_cli/src/commands/global/outdated.rs @@ -50,7 +50,10 @@ pub async fn get_outdated_packages( let installed = if !packages.is_empty() { let mut installed = Vec::new(); for package in packages { - let (package_name, _) = parse_package_spec(package); + let Ok((package_name, _)) = parse_package_spec(package) else { + // Silently skip, follow npm's behavior + continue; + }; if let Some(metadata) = PackageMetadata::load(&package_name).await? { installed.push((metadata, Some(package.clone()))); } From f017f86dd6a40323765515997a0207478740fb59 Mon Sep 17 00:00:00 2001 From: Liang Mi Date: Tue, 26 May 2026 21:42:23 +0800 Subject: [PATCH 2/7] tests --- .../command-env-install-global-dot/bin.js | 1 + .../command-env-install-global-dot/package.json | 7 +++++++ .../command-env-install-global-dot/snap.txt | 9 +++++++++ .../command-env-install-global-dot/steps.json | 4 ++++ 4 files changed, 21 insertions(+) create mode 100644 packages/cli/snap-tests-global/command-env-install-global-dot/bin.js create mode 100644 packages/cli/snap-tests-global/command-env-install-global-dot/package.json create mode 100644 packages/cli/snap-tests-global/command-env-install-global-dot/snap.txt create mode 100644 packages/cli/snap-tests-global/command-env-install-global-dot/steps.json diff --git a/packages/cli/snap-tests-global/command-env-install-global-dot/bin.js b/packages/cli/snap-tests-global/command-env-install-global-dot/bin.js new file mode 100644 index 0000000000..09a50e9911 --- /dev/null +++ b/packages/cli/snap-tests-global/command-env-install-global-dot/bin.js @@ -0,0 +1 @@ +console.log('The package is installed successfully'); diff --git a/packages/cli/snap-tests-global/command-env-install-global-dot/package.json b/packages/cli/snap-tests-global/command-env-install-global-dot/package.json new file mode 100644 index 0000000000..ce66fe0be3 --- /dev/null +++ b/packages/cli/snap-tests-global/command-env-install-global-dot/package.json @@ -0,0 +1,7 @@ +{ + "name": "just-a-normal-package", + "version": "0.0.0", + "bin": { + "just-a-normal-package": "./bin.js" + } +} diff --git a/packages/cli/snap-tests-global/command-env-install-global-dot/snap.txt b/packages/cli/snap-tests-global/command-env-install-global-dot/snap.txt new file mode 100644 index 0000000000..7626b07491 --- /dev/null +++ b/packages/cli/snap-tests-global/command-env-install-global-dot/snap.txt @@ -0,0 +1,9 @@ +> vp install -g . +info: Installing 1 global package with Node.js +✓ Installed just-a-normal-package + Bins: just-a-normal-package + +> vp list -g just-a-normal-package +Package Node version Binaries +--- --- --- +just-a-normal-package@ just-a-normal-package diff --git a/packages/cli/snap-tests-global/command-env-install-global-dot/steps.json b/packages/cli/snap-tests-global/command-env-install-global-dot/steps.json new file mode 100644 index 0000000000..c64271f53a --- /dev/null +++ b/packages/cli/snap-tests-global/command-env-install-global-dot/steps.json @@ -0,0 +1,4 @@ +{ + "commands": ["vp install -g .", "vp list -g just-a-normal-package"], + "after": ["vp remove -g just-a-normal-package"] +} From b71ca5af7c9038e3d893ee3dca621076df3b0840 Mon Sep 17 00:00:00 2001 From: Liang Mi Date: Tue, 26 May 2026 21:51:59 +0800 Subject: [PATCH 3/7] as we have snap tests already --- crates/vite_global_cli/src/commands/global/install.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/vite_global_cli/src/commands/global/install.rs b/crates/vite_global_cli/src/commands/global/install.rs index 5029a13d30..8175af906d 100644 --- a/crates/vite_global_cli/src/commands/global/install.rs +++ b/crates/vite_global_cli/src/commands/global/install.rs @@ -911,13 +911,6 @@ mod tests { assert_eq!(version, Some("20.0.0".to_string())); } - #[test] - fn test_parse_package_spec_local() { - let (name, version) = parse_package_spec("./packages/cli").unwrap(); - assert_eq!(name, "@types/node"); - assert_eq!(version, Some("20.0.0".to_string())); - } - #[test] fn test_is_javascript_binary_with_js_extension() { use tempfile::TempDir; From 41103da353f8e5a89d7c981af61b67d73252c779 Mon Sep 17 00:00:00 2001 From: Liang Mi Date: Tue, 26 May 2026 22:32:19 +0800 Subject: [PATCH 4/7] adjust --- crates/vite_global_cli/src/cli.rs | 2 +- .../src/commands/global/install.rs | 48 +++++--- .../src/commands/global/mod.rs | 104 +++++++++--------- .../src/commands/global/outdated.rs | 2 +- 4 files changed, 82 insertions(+), 74 deletions(-) diff --git a/crates/vite_global_cli/src/cli.rs b/crates/vite_global_cli/src/cli.rs index 741c3e9a9a..e8e08d542f 100644 --- a/crates/vite_global_cli/src/cli.rs +++ b/crates/vite_global_cli/src/cli.rs @@ -672,7 +672,7 @@ async fn managed_update( } // It is not a local package, so `parse_package_spec` there won't return `Err()` - let (package_name, _) = global::parse_package_spec(package).unwrap(); + let (package_name, _) = global::parse_package_spec(package, None, None).await.unwrap(); if PackageMetadata::load(&package_name).await?.is_some() { managed_specs.push(package.clone()); } else { diff --git a/crates/vite_global_cli/src/commands/global/install.rs b/crates/vite_global_cli/src/commands/global/install.rs index 8175af906d..ed10945b96 100644 --- a/crates/vite_global_cli/src/commands/global/install.rs +++ b/crates/vite_global_cli/src/commands/global/install.rs @@ -26,7 +26,7 @@ use crate::{ }, package_metadata::PackageMetadata, }, - global::{CORE_SHIMS, parse_package_spec}, + global::{CORE_SHIMS, is_local_package_spec, parse_package_spec}, }, error::Error, }; @@ -106,10 +106,11 @@ pub async fn install( for package_spec in package_specs { // Parse package spec (e.g., "typescript", "typescript@5.0.0", "@scope/pkg") - let (package_name, _version_spec) = match parse_package_spec(package_spec) { - Ok(result) => result, - Err(error) => return Err((Some(package_spec.clone()), error)), - }; + let (package_name, _version_spec) = + match parse_package_spec(package_spec, Some(&npm_path), Some(&node_bin_dir)).await { + Ok(result) => result, + Err(error) => return Err((Some(package_spec.clone()), error)), + }; packages.insert(package_name, Package { spec: package_spec, staging_dir: None }); } let packages_count = packages.len(); @@ -399,7 +400,18 @@ async fn install_one( /// 1. Try to use PackageMetadata for binary list /// 2. Fallback to scanning BinConfig files for orphaned binaries pub async fn uninstall(package_name: &str, dry_run: bool) -> Result<(), Error> { - let (package_name, _) = parse_package_spec(package_name)?; + if is_local_package_spec(package_name) { + // We can't resolve local packages for uninstall, follow npm's behavior + return Err(Error::ConfigError( + format!( + "Local path {} can't be resolved, please enter a package name instead", + package_name + ) + .into(), + )); + } + + let (package_name, _) = parse_package_spec(package_name, None, None).await.unwrap(); // Phase 1: Try to use PackageMetadata for binary list let bins = if let Some(metadata) = PackageMetadata::load(&package_name).await? { @@ -883,30 +895,30 @@ mod tests { assert!(!is_local_package_spec("@scope/pkg@1.0.0")); } - #[test] - fn test_parse_package_spec_simple() { - let (name, version) = parse_package_spec("typescript").unwrap(); + #[tokio::test] + async fn test_parse_package_spec_simple() { + let (name, version) = parse_package_spec("typescript", None, None).await.unwrap(); assert_eq!(name, "typescript"); assert_eq!(version, None); } - #[test] - fn test_parse_package_spec_with_version() { - let (name, version) = parse_package_spec("typescript@5.0.0").unwrap(); + #[tokio::test] + async fn test_parse_package_spec_with_version() { + let (name, version) = parse_package_spec("typescript@5.0.0", None, None).await.unwrap(); assert_eq!(name, "typescript"); assert_eq!(version, Some("5.0.0".to_string())); } - #[test] - fn test_parse_package_spec_scoped() { - let (name, version) = parse_package_spec("@types/node").unwrap(); + #[tokio::test] + async fn test_parse_package_spec_scoped() { + let (name, version) = parse_package_spec("@types/node", None, None).await.unwrap(); assert_eq!(name, "@types/node"); assert_eq!(version, None); } - #[test] - fn test_parse_package_spec_scoped_with_version() { - let (name, version) = parse_package_spec("@types/node@20.0.0").unwrap(); + #[tokio::test] + async fn test_parse_package_spec_scoped_with_version() { + let (name, version) = parse_package_spec("@types/node@20.0.0", None, None).await.unwrap(); assert_eq!(name, "@types/node"); assert_eq!(version, Some("20.0.0".to_string())); } diff --git a/crates/vite_global_cli/src/commands/global/mod.rs b/crates/vite_global_cli/src/commands/global/mod.rs index 3c49c01dda..0b6a0857e9 100644 --- a/crates/vite_global_cli/src/commands/global/mod.rs +++ b/crates/vite_global_cli/src/commands/global/mod.rs @@ -48,25 +48,36 @@ impl NpmRegistry { } async fn latest_package_version(&self, package_spec: &str) -> Result { - let output = Command::new(self.npm_path.as_path()) - .args(["view", package_spec, "version", "--json"]) - .env("PATH", format_path_prepended(self.node_bin_dir.as_path())) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output() - .await?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); - return Err(Error::ConfigError( - format!("npm view failed for {package_spec}: {stderr}").into(), - )); - } + let output = npm_view(&self.npm_path, &self.node_bin_dir, package_spec, "version").await?; - parse_npm_view_version(&output.stdout) + parse_npm_view_version(&output) } } +async fn npm_view( + npm_path: &AbsolutePathBuf, + node_bin_dir: &AbsolutePathBuf, + package_spec: &str, + field: &str, +) -> Result, Error> { + let output = Command::new(npm_path.as_path()) + .args(["view", package_spec, field, "--json"]) + .env("PATH", format_path_prepended(node_bin_dir.as_path())) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + return Err(Error::ConfigError( + format!("npm view failed for {package_spec}: {stderr}").into(), + )); + } + + Ok(output.stdout) +} + pub(crate) async fn latest_package_versions( specs: &[String], concurrency: usize, @@ -133,48 +144,20 @@ pub(crate) fn is_local_package_spec(spec: &str) -> bool { } /// Parse package spec into name and optional version. -/// For local packages, it will read the package.json and return the real package name +/// For local packages, resolve the real package name with `npm view` when npm paths are available. /// /// It will never return an `Err()` if it is not a local package -pub(crate) fn parse_package_spec(spec: &str) -> Result<(String, Option), Error> { +/// Missing `npm_path` or `node_bin_dir` may cause panic, unless you are sure it is not a local paclkage +pub(crate) async fn parse_package_spec( + spec: &str, + npm_path: Option<&AbsolutePathBuf>, + node_bin_dir: Option<&AbsolutePathBuf>, +) -> Result<(String, Option), Error> { if is_local_package_spec(spec) { - let path_spec = spec.strip_prefix("file:").unwrap_or(spec); - let path = std::path::Path::new(path_spec); - let package_dir = if path.is_absolute() { - AbsolutePathBuf::new(path.to_path_buf()).ok_or_else(|| { - Error::ConfigError(format!("Invalid local package path {spec}").into()) - })? - } else { - current_dir() - .map_err(|error| { - Error::ConfigError(format!("Cannot get current directory: {error}").into()) - })? - .join(path) - }; - let package_json_path = package_dir.join("package.json"); - let package_json_content = - std::fs::read_to_string(package_json_path.as_path()).map_err(|error| { - Error::ConfigError( - format!( - "Failed to read package.json for local package {spec} at {}: {error}", - package_json_path.as_path().display() - ) - .into(), - ) - })?; - let package_json: serde_json::Value = - serde_json::from_str(&package_json_content).map_err(Error::JsonError)?; - let Some(package_name) = package_json.get("name").and_then(|name| name.as_str()) else { - return Err(Error::ConfigError( - format!( - "Local package {spec} must have a string name in {}", - package_json_path.as_path().display() - ) - .into(), - )); - }; - - Ok((package_name.to_string(), None)) + let npm_path = npm_path.unwrap(); + let node_bin_dir = node_bin_dir.unwrap(); + let output = npm_view(npm_path, node_bin_dir, spec, "name").await?; + Ok((parse_npm_view_string(&output, "name")?, None)) } else { if spec.starts_with('@') { if let Some(idx) = spec[1..].find('@') { @@ -192,6 +175,19 @@ pub(crate) fn parse_package_spec(spec: &str) -> Result<(String, Option), } } +fn parse_npm_view_string(stdout: &[u8], field: &str) -> Result { + let raw = String::from_utf8_lossy(stdout); + let trimmed = raw.trim(); + if trimmed.is_empty() { + return Err(Error::ConfigError(format!("npm view returned an empty {field}").into())); + } + + match serde_json::from_str::(trimmed) { + Ok(serde_json::Value::String(value)) => Ok(value), + _ => Ok(trimmed.to_string()), + } +} + fn parse_npm_view_version(stdout: &[u8]) -> Result { let raw = String::from_utf8_lossy(stdout); let trimmed = raw.trim(); diff --git a/crates/vite_global_cli/src/commands/global/outdated.rs b/crates/vite_global_cli/src/commands/global/outdated.rs index e8b65d83a7..bf15f005a5 100644 --- a/crates/vite_global_cli/src/commands/global/outdated.rs +++ b/crates/vite_global_cli/src/commands/global/outdated.rs @@ -50,7 +50,7 @@ pub async fn get_outdated_packages( let installed = if !packages.is_empty() { let mut installed = Vec::new(); for package in packages { - let Ok((package_name, _)) = parse_package_spec(package) else { + let Ok((package_name, _)) = parse_package_spec(package, None, None).await else { // Silently skip, follow npm's behavior continue; }; From 1ea22851c5c7edb3ec73749626379d668762c42e Mon Sep 17 00:00:00 2001 From: Liang Mi Date: Tue, 26 May 2026 22:37:35 +0800 Subject: [PATCH 5/7] adjust --- .../command-env-install-global-dot/steps.json | 4 ---- .../bin.js | 0 .../package.json | 0 .../snap.txt | 0 .../command-env-install-global-local/steps.json | 8 ++++++++ 5 files changed, 8 insertions(+), 4 deletions(-) delete mode 100644 packages/cli/snap-tests-global/command-env-install-global-dot/steps.json rename packages/cli/snap-tests-global/{command-env-install-global-dot => command-env-install-global-local}/bin.js (100%) rename packages/cli/snap-tests-global/{command-env-install-global-dot => command-env-install-global-local}/package.json (100%) rename packages/cli/snap-tests-global/{command-env-install-global-dot => command-env-install-global-local}/snap.txt (100%) create mode 100644 packages/cli/snap-tests-global/command-env-install-global-local/steps.json diff --git a/packages/cli/snap-tests-global/command-env-install-global-dot/steps.json b/packages/cli/snap-tests-global/command-env-install-global-dot/steps.json deleted file mode 100644 index c64271f53a..0000000000 --- a/packages/cli/snap-tests-global/command-env-install-global-dot/steps.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "commands": ["vp install -g .", "vp list -g just-a-normal-package"], - "after": ["vp remove -g just-a-normal-package"] -} diff --git a/packages/cli/snap-tests-global/command-env-install-global-dot/bin.js b/packages/cli/snap-tests-global/command-env-install-global-local/bin.js similarity index 100% rename from packages/cli/snap-tests-global/command-env-install-global-dot/bin.js rename to packages/cli/snap-tests-global/command-env-install-global-local/bin.js diff --git a/packages/cli/snap-tests-global/command-env-install-global-dot/package.json b/packages/cli/snap-tests-global/command-env-install-global-local/package.json similarity index 100% rename from packages/cli/snap-tests-global/command-env-install-global-dot/package.json rename to packages/cli/snap-tests-global/command-env-install-global-local/package.json diff --git a/packages/cli/snap-tests-global/command-env-install-global-dot/snap.txt b/packages/cli/snap-tests-global/command-env-install-global-local/snap.txt similarity index 100% rename from packages/cli/snap-tests-global/command-env-install-global-dot/snap.txt rename to packages/cli/snap-tests-global/command-env-install-global-local/snap.txt diff --git a/packages/cli/snap-tests-global/command-env-install-global-local/steps.json b/packages/cli/snap-tests-global/command-env-install-global-local/steps.json new file mode 100644 index 0000000000..0d15d23805 --- /dev/null +++ b/packages/cli/snap-tests-global/command-env-install-global-local/steps.json @@ -0,0 +1,8 @@ +{ + "commands": [ + "vp install -g .", + "vp install -g ./another-package.tgz", + "vp list -g just-a-normal-package another-normal-package" + ], + "after": ["vp remove -g just-a-normal-package another-normal-package"] +} From 04646f166d0e01999ee416ebda2baad8d6f12c9c Mon Sep 17 00:00:00 2001 From: Liang Mi Date: Tue, 26 May 2026 22:46:04 +0800 Subject: [PATCH 6/7] tarball --- Cargo.lock | 2 + crates/vite_global_cli/Cargo.toml | 2 + crates/vite_global_cli/src/cli.rs | 2 +- .../src/commands/global/install.rs | 35 +++--- .../src/commands/global/mod.rs | 119 ++++++++++++++---- .../src/commands/global/outdated.rs | 2 +- .../another-package.tgz | Bin 0 -> 246 bytes .../command-env-install-global-local/snap.txt | 10 ++ .../steps.json | 3 +- 9 files changed, 133 insertions(+), 42 deletions(-) create mode 100644 packages/cli/snap-tests-global/command-env-install-global-local/another-package.tgz diff --git a/Cargo.lock b/Cargo.lock index 41e9e34fea..be431a5252 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7526,6 +7526,7 @@ dependencies = [ "clap_complete", "crossterm", "directories", + "flate2", "futures", "indexmap", "indicatif", @@ -7535,6 +7536,7 @@ dependencies = [ "serde", "serde_json", "serial_test", + "tar", "tempfile", "thiserror 2.0.18", "tokio", diff --git a/crates/vite_global_cli/Cargo.toml b/crates/vite_global_cli/Cargo.toml index ab914d5911..656558b306 100644 --- a/crates/vite_global_cli/Cargo.toml +++ b/crates/vite_global_cli/Cargo.toml @@ -17,10 +17,12 @@ clap = { workspace = true, features = ["derive"] } clap_complete = { workspace = true, features = ["unstable-dynamic"] } directories = { workspace = true } futures = { workspace = true } +flate2 = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } node-semver = { workspace = true } thiserror = { workspace = true } +tar = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } owo-colors = { workspace = true } diff --git a/crates/vite_global_cli/src/cli.rs b/crates/vite_global_cli/src/cli.rs index e8e08d542f..741c3e9a9a 100644 --- a/crates/vite_global_cli/src/cli.rs +++ b/crates/vite_global_cli/src/cli.rs @@ -672,7 +672,7 @@ async fn managed_update( } // It is not a local package, so `parse_package_spec` there won't return `Err()` - let (package_name, _) = global::parse_package_spec(package, None, None).await.unwrap(); + let (package_name, _) = global::parse_package_spec(package).unwrap(); if PackageMetadata::load(&package_name).await?.is_some() { managed_specs.push(package.clone()); } else { diff --git a/crates/vite_global_cli/src/commands/global/install.rs b/crates/vite_global_cli/src/commands/global/install.rs index ed10945b96..aa8a6639e8 100644 --- a/crates/vite_global_cli/src/commands/global/install.rs +++ b/crates/vite_global_cli/src/commands/global/install.rs @@ -106,11 +106,10 @@ pub async fn install( for package_spec in package_specs { // Parse package spec (e.g., "typescript", "typescript@5.0.0", "@scope/pkg") - let (package_name, _version_spec) = - match parse_package_spec(package_spec, Some(&npm_path), Some(&node_bin_dir)).await { - Ok(result) => result, - Err(error) => return Err((Some(package_spec.clone()), error)), - }; + let (package_name, _version_spec) = match parse_package_spec(package_spec) { + Ok(result) => result, + Err(error) => return Err((Some(package_spec.clone()), error)), + }; packages.insert(package_name, Package { spec: package_spec, staging_dir: None }); } let packages_count = packages.len(); @@ -411,7 +410,7 @@ pub async fn uninstall(package_name: &str, dry_run: bool) -> Result<(), Error> { )); } - let (package_name, _) = parse_package_spec(package_name, None, None).await.unwrap(); + let (package_name, _) = parse_package_spec(package_name).unwrap(); // Phase 1: Try to use PackageMetadata for binary list let bins = if let Some(metadata) = PackageMetadata::load(&package_name).await? { @@ -895,30 +894,30 @@ mod tests { assert!(!is_local_package_spec("@scope/pkg@1.0.0")); } - #[tokio::test] - async fn test_parse_package_spec_simple() { - let (name, version) = parse_package_spec("typescript", None, None).await.unwrap(); + #[test] + fn test_parse_package_spec_simple() { + let (name, version) = parse_package_spec("typescript").unwrap(); assert_eq!(name, "typescript"); assert_eq!(version, None); } - #[tokio::test] - async fn test_parse_package_spec_with_version() { - let (name, version) = parse_package_spec("typescript@5.0.0", None, None).await.unwrap(); + #[test] + fn test_parse_package_spec_with_version() { + let (name, version) = parse_package_spec("typescript@5.0.0").unwrap(); assert_eq!(name, "typescript"); assert_eq!(version, Some("5.0.0".to_string())); } - #[tokio::test] - async fn test_parse_package_spec_scoped() { - let (name, version) = parse_package_spec("@types/node", None, None).await.unwrap(); + #[test] + fn test_parse_package_spec_scoped() { + let (name, version) = parse_package_spec("@types/node").unwrap(); assert_eq!(name, "@types/node"); assert_eq!(version, None); } - #[tokio::test] - async fn test_parse_package_spec_scoped_with_version() { - let (name, version) = parse_package_spec("@types/node@20.0.0", None, None).await.unwrap(); + #[test] + fn test_parse_package_spec_scoped_with_version() { + let (name, version) = parse_package_spec("@types/node@20.0.0").unwrap(); assert_eq!(name, "@types/node"); assert_eq!(version, Some("20.0.0".to_string())); } diff --git a/crates/vite_global_cli/src/commands/global/mod.rs b/crates/vite_global_cli/src/commands/global/mod.rs index 0b6a0857e9..5d9234c131 100644 --- a/crates/vite_global_cli/src/commands/global/mod.rs +++ b/crates/vite_global_cli/src/commands/global/mod.rs @@ -1,9 +1,17 @@ //! Managed global package utilities. -use std::{collections::HashMap, io::IsTerminal, process::Stdio, time::Duration}; - +use std::{ + collections::HashMap, + fs::File, + io::{IsTerminal, Read}, + process::Stdio, + time::Duration, +}; + +use flate2::read::GzDecoder; use futures::{StreamExt, stream::FuturesUnordered}; use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}; +use tar::Archive; use tokio::process::Command; use vite_path::{AbsolutePathBuf, current_dir}; use vite_shared::format_path_prepended; @@ -144,20 +152,19 @@ pub(crate) fn is_local_package_spec(spec: &str) -> bool { } /// Parse package spec into name and optional version. -/// For local packages, resolve the real package name with `npm view` when npm paths are available. +/// For local packages, read package.json from a directory or package tarball. /// /// It will never return an `Err()` if it is not a local package -/// Missing `npm_path` or `node_bin_dir` may cause panic, unless you are sure it is not a local paclkage -pub(crate) async fn parse_package_spec( - spec: &str, - npm_path: Option<&AbsolutePathBuf>, - node_bin_dir: Option<&AbsolutePathBuf>, -) -> Result<(String, Option), Error> { +pub(crate) fn parse_package_spec(spec: &str) -> Result<(String, Option), Error> { if is_local_package_spec(spec) { - let npm_path = npm_path.unwrap(); - let node_bin_dir = node_bin_dir.unwrap(); - let output = npm_view(npm_path, node_bin_dir, spec, "name").await?; - Ok((parse_npm_view_string(&output, "name")?, None)) + let package_json = read_local_package_json(spec)?; + let Some(package_name) = package_json.get("name").and_then(|name| name.as_str()) else { + return Err(Error::ConfigError( + format!("Local package {spec} must have a string name in package.json").into(), + )); + }; + + Ok((package_name.to_string(), None)) } else { if spec.starts_with('@') { if let Some(idx) = spec[1..].find('@') { @@ -175,17 +182,87 @@ pub(crate) async fn parse_package_spec( } } -fn parse_npm_view_string(stdout: &[u8], field: &str) -> Result { - let raw = String::from_utf8_lossy(stdout); - let trimmed = raw.trim(); - if trimmed.is_empty() { - return Err(Error::ConfigError(format!("npm view returned an empty {field}").into())); +fn resolve_local_package_path(spec: &str) -> Result { + let path_spec = spec.strip_prefix("file:").unwrap_or(spec); + let path = std::path::Path::new(path_spec); + if path.is_absolute() { + AbsolutePathBuf::new(path.to_path_buf()) + .ok_or_else(|| Error::ConfigError(format!("Invalid local package path {spec}").into())) + } else { + Ok(current_dir() + .map_err(|error| { + Error::ConfigError(format!("Cannot get current directory: {error}").into()) + })? + .join(path)) } +} - match serde_json::from_str::(trimmed) { - Ok(serde_json::Value::String(value)) => Ok(value), - _ => Ok(trimmed.to_string()), +fn read_local_package_json(spec: &str) -> Result { + let package_path = resolve_local_package_path(spec)?; + if package_path.as_path().is_file() && is_package_tarball(package_path.as_path()) { + return read_package_json_from_tarball(spec, &package_path); + } + + let package_json_path = package_path.join("package.json"); + let package_json_content = + std::fs::read_to_string(package_json_path.as_path()).map_err(|error| { + Error::ConfigError( + format!( + "Failed to read package.json for local package {spec} at {}: {error}", + package_json_path.as_path().display() + ) + .into(), + ) + })?; + serde_json::from_str(&package_json_content).map_err(Error::JsonError) +} + +fn is_package_tarball(path: &std::path::Path) -> bool { + let path = path.to_string_lossy(); + path.ends_with(".tgz") || path.ends_with(".tar.gz") +} + +fn read_package_json_from_tarball( + spec: &str, + package_path: &AbsolutePathBuf, +) -> Result { + let file = File::open(package_path.as_path()).map_err(|error| { + Error::ConfigError( + format!( + "Failed to read package tarball {spec} at {}: {error}", + package_path.as_path().display() + ) + .into(), + ) + })?; + let decoder = GzDecoder::new(file); + let mut archive = Archive::new(decoder); + + for entry in archive.entries().map_err(|error| { + Error::ConfigError(format!("Failed to read package tarball {spec}: {error}").into()) + })? { + let mut entry = entry.map_err(|error| { + Error::ConfigError(format!("Failed to read package tarball {spec}: {error}").into()) + })?; + let path = entry.path().map_err(|error| { + Error::ConfigError(format!("Failed to read package tarball {spec}: {error}").into()) + })?; + if path.as_ref() != std::path::Path::new("package/package.json") { + continue; + } + + let mut package_json_content = String::new(); + entry.read_to_string(&mut package_json_content).map_err(|error| { + Error::ConfigError( + format!("Failed to read package.json from package tarball {spec}: {error}").into(), + ) + })?; + return serde_json::from_str(&package_json_content).map_err(Error::JsonError); } + + Err(Error::ConfigError( + format!("Package tarball {spec} must contain package/package.json").into(), + )) } fn parse_npm_view_version(stdout: &[u8]) -> Result { diff --git a/crates/vite_global_cli/src/commands/global/outdated.rs b/crates/vite_global_cli/src/commands/global/outdated.rs index bf15f005a5..e8b65d83a7 100644 --- a/crates/vite_global_cli/src/commands/global/outdated.rs +++ b/crates/vite_global_cli/src/commands/global/outdated.rs @@ -50,7 +50,7 @@ pub async fn get_outdated_packages( let installed = if !packages.is_empty() { let mut installed = Vec::new(); for package in packages { - let Ok((package_name, _)) = parse_package_spec(package, None, None).await else { + let Ok((package_name, _)) = parse_package_spec(package) else { // Silently skip, follow npm's behavior continue; }; diff --git a/packages/cli/snap-tests-global/command-env-install-global-local/another-package.tgz b/packages/cli/snap-tests-global/command-env-install-global-local/another-package.tgz new file mode 100644 index 0000000000000000000000000000000000000000..306df432738a949e7e7de5e0aad9a4a576ad6f45 GIT binary patch literal 246 zcmVthaZ)!~u%|vsTvLt4M0R!yrTFe8 z-JGP}s-on(4E#(&hRi>6H0|DO?0n^d*b%!5h}b9vf9bz8ufP^cqm)`>4GbkN8U%*y z1`O1jjv@X-k%;NhIX(z&3>3W;K5kz4dx-tkc5ob<0vv%0)En>Z8fa)+OY|{#|HUV7 z6)QXe`K#wSiXFvp1^?^1zUY6c^|}A0QnL8}7JOF#JeY&!OW ✓ Installed just-a-normal-package Bins: just-a-normal-package +> vp install -g ./another-package.tgz +info: Installing 1 global package with Node.js +✓ Installed another-normal-package + Bins: another-normal-package + > vp list -g just-a-normal-package Package Node version Binaries --- --- --- just-a-normal-package@ just-a-normal-package + +> vp list -g another-normal-package +Package Node version Binaries +--- --- --- +another-normal-package@ another-normal-package diff --git a/packages/cli/snap-tests-global/command-env-install-global-local/steps.json b/packages/cli/snap-tests-global/command-env-install-global-local/steps.json index 0d15d23805..4377b56a05 100644 --- a/packages/cli/snap-tests-global/command-env-install-global-local/steps.json +++ b/packages/cli/snap-tests-global/command-env-install-global-local/steps.json @@ -2,7 +2,8 @@ "commands": [ "vp install -g .", "vp install -g ./another-package.tgz", - "vp list -g just-a-normal-package another-normal-package" + "vp list -g just-a-normal-package", + "vp list -g another-normal-package" ], "after": ["vp remove -g just-a-normal-package another-normal-package"] } From f1daa2779f05f883911c7d4665b48a74514f383e Mon Sep 17 00:00:00 2001 From: Liang Mi Date: Tue, 26 May 2026 22:50:35 +0800 Subject: [PATCH 7/7] snapshots --- .../command-env-install-conflict/snap.txt | 8 ++++---- .../command-env-install-global-local/snap.txt | 4 ++-- .../command-env-install-node-version/snap.txt | 4 ++-- .../command-env-install-parallel/snap.txt | 4 ++-- .../command-env-install-version-alias/snap.txt | 4 ++-- .../env-install-binary-conflict/snap.txt | 18 +++++++++--------- .../npm-global-install-already-linked/snap.txt | 4 ++-- .../npm-global-uninstall-vp-managed/snap.txt | 4 ++-- .../shim-recursive-package-binary/snap.txt | 2 +- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/cli/snap-tests-global/command-env-install-conflict/snap.txt b/packages/cli/snap-tests-global/command-env-install-conflict/snap.txt index dd89567f74..200704587c 100644 --- a/packages/cli/snap-tests-global/command-env-install-conflict/snap.txt +++ b/packages/cli/snap-tests-global/command-env-install-conflict/snap.txt @@ -1,7 +1,7 @@ > vp install -g ./conflict-pkg # Install package with conflicting binary name (uses cwd version) info: Installing 1 global package with Node.js -warn: Package './conflict-pkg' provides 'node' binary, but it conflicts with a core shim. Skipping. -✓ Installed ./conflict-pkg +warn: Package 'conflict-pkg' provides 'node' binary, but it conflicts with a core shim. Skipping. +✓ Installed conflict-pkg Bins: conflict-cli, node > vp remove -g conflict-pkg # Cleanup @@ -9,8 +9,8 @@ Uninstalled conflict-pkg > vp install -g --node 20 ./conflict-pkg # Install with specific Node.js version info: Installing 1 global package with Node.js -warn: Package './conflict-pkg' provides 'node' binary, but it conflicts with a core shim. Skipping. -✓ Installed ./conflict-pkg +warn: Package 'conflict-pkg' provides 'node' binary, but it conflicts with a core shim. Skipping. +✓ Installed conflict-pkg Bins: conflict-cli, node > vp remove -g conflict-pkg # Cleanup diff --git a/packages/cli/snap-tests-global/command-env-install-global-local/snap.txt b/packages/cli/snap-tests-global/command-env-install-global-local/snap.txt index c86cd0cf71..45da4b38d4 100644 --- a/packages/cli/snap-tests-global/command-env-install-global-local/snap.txt +++ b/packages/cli/snap-tests-global/command-env-install-global-local/snap.txt @@ -11,9 +11,9 @@ info: Installing 1 global package with Node.js > vp list -g just-a-normal-package Package Node version Binaries --- --- --- -just-a-normal-package@ just-a-normal-package +just-a-normal-package@ just-a-normal-package > vp list -g another-normal-package Package Node version Binaries --- --- --- -another-normal-package@ another-normal-package +another-normal-package@ another-normal-package diff --git a/packages/cli/snap-tests-global/command-env-install-node-version/snap.txt b/packages/cli/snap-tests-global/command-env-install-node-version/snap.txt index 40f1c040a8..a24c129abd 100644 --- a/packages/cli/snap-tests-global/command-env-install-node-version/snap.txt +++ b/packages/cli/snap-tests-global/command-env-install-node-version/snap.txt @@ -1,6 +1,6 @@ > vp install -g --node 22 ./command-env-install-node-version-pkg # Install with Node.js 22 info: Installing 1 global package with Node.js -✓ Installed ./command-env-install-node-version-pkg +✓ Installed command-env-install-node-version-pkg Bins: command-env-install-node-version-pkg-cli > cat $VP_HOME/bins/command-env-install-node-version-pkg-cli.json | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8')); console.log('Node major:', d.nodeVersion.split('.')[0])" # Verify Node 22 @@ -11,7 +11,7 @@ Uninstalled command-env-install-node-version-pkg > vp install -g --node 20 ./command-env-install-node-version-pkg # Install with Node.js 20 info: Installing 1 global package with Node.js -✓ Installed ./command-env-install-node-version-pkg +✓ Installed command-env-install-node-version-pkg Bins: command-env-install-node-version-pkg-cli > cat $VP_HOME/bins/command-env-install-node-version-pkg-cli.json | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8')); console.log('Node major:', d.nodeVersion.split('.')[0])" # Verify Node 20 diff --git a/packages/cli/snap-tests-global/command-env-install-parallel/snap.txt b/packages/cli/snap-tests-global/command-env-install-parallel/snap.txt index 721dd1d80b..4dcf1fbef2 100644 --- a/packages/cli/snap-tests-global/command-env-install-parallel/snap.txt +++ b/packages/cli/snap-tests-global/command-env-install-parallel/snap.txt @@ -1,9 +1,9 @@ > vp install -g --concurrency 1 ./parallel-pkg-a ./parallel-pkg-b # Install multiple global packages info: Installing 2 global packages with Node.js -✓ Installed ./parallel-pkg-a +✓ Installed parallel-pkg-a Bins: parallel-a -✓ Installed ./parallel-pkg-b +✓ Installed parallel-pkg-b Bins: parallel-b > parallel-a && parallel-b # Both binaries should be callable diff --git a/packages/cli/snap-tests-global/command-env-install-version-alias/snap.txt b/packages/cli/snap-tests-global/command-env-install-version-alias/snap.txt index 58510ae5c6..c8152d1d73 100644 --- a/packages/cli/snap-tests-global/command-env-install-version-alias/snap.txt +++ b/packages/cli/snap-tests-global/command-env-install-version-alias/snap.txt @@ -1,6 +1,6 @@ > vp install -g --node lts ./command-env-install-version-alias-pkg # Install with LTS alias info: Installing 1 global package with Node.js -✓ Installed ./command-env-install-version-alias-pkg +✓ Installed command-env-install-version-alias-pkg Bins: command-env-install-version-alias-pkg-cli > cat $VP_HOME/bins/command-env-install-version-alias-pkg-cli.json | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8')); const v=parseInt(d.nodeVersion.split('.')[0]); console.log('LTS major >= 20:', v >= 20)" # Verify LTS version @@ -11,7 +11,7 @@ Uninstalled command-env-install-version-alias-pkg > vp install -g --node latest ./command-env-install-version-alias-pkg # Install with latest alias info: Installing 1 global package with Node.js -✓ Installed ./command-env-install-version-alias-pkg +✓ Installed command-env-install-version-alias-pkg Bins: command-env-install-version-alias-pkg-cli > cat $VP_HOME/bins/command-env-install-version-alias-pkg-cli.json | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8')); const v=parseInt(d.nodeVersion.split('.')[0]); console.log('Latest major >= 20:', v >= 20)" # Verify latest version diff --git a/packages/cli/snap-tests-global/env-install-binary-conflict/snap.txt b/packages/cli/snap-tests-global/env-install-binary-conflict/snap.txt index 185390d749..3355ed668a 100644 --- a/packages/cli/snap-tests-global/env-install-binary-conflict/snap.txt +++ b/packages/cli/snap-tests-global/env-install-binary-conflict/snap.txt @@ -1,41 +1,41 @@ > vp install -g ./env-binary-conflict-pkg-a # Install pkg-a which provides env-binary-conflict-cli binary info: Installing 1 global package with Node.js -✓ Installed ./env-binary-conflict-pkg-a +✓ Installed env-binary-conflict-pkg-a Bins: env-binary-conflict-cli > cat $VP_HOME/bins/env-binary-conflict-cli.json # Bin config should point to pkg-a { "name": "env-binary-conflict-cli", - "package": "./env-binary-conflict-pkg-a", + "package": "env-binary-conflict-pkg-a", "version": "1.0.0", "nodeVersion": "22.22.0", "source": "vp" } [1]> vp install -g ./env-binary-conflict-pkg-b # Try to install pkg-b without force - should fail info: Installing 1 global package with Node.js -error: Failed to install ./env-binary-conflict-pkg-b: Executable 'env-binary-conflict-cli' is already installed by ./env-binary-conflict-pkg-a +error: Failed to install env-binary-conflict-pkg-b: Executable 'env-binary-conflict-cli' is already installed by env-binary-conflict-pkg-a -Please remove ./env-binary-conflict-pkg-a before installing ./env-binary-conflict-pkg-b, or use --force to auto-replace +Please remove env-binary-conflict-pkg-a before installing env-binary-conflict-pkg-b, or use --force to auto-replace > cat $VP_HOME/bins/env-binary-conflict-cli.json # Bin config should still point to pkg-a { "name": "env-binary-conflict-cli", - "package": "./env-binary-conflict-pkg-a", + "package": "env-binary-conflict-pkg-a", "version": "1.0.0", "nodeVersion": "22.22.0", "source": "vp" } > vp install -g --force ./env-binary-conflict-pkg-b # Force install pkg-b - should auto-uninstall pkg-a info: Installing 1 global package with Node.js -Uninstalling ./env-binary-conflict-pkg-a (conflicts with ./env-binary-conflict-pkg-b)... -Uninstalled ./env-binary-conflict-pkg-a -✓ Installed ./env-binary-conflict-pkg-b +Uninstalling env-binary-conflict-pkg-a (conflicts with env-binary-conflict-pkg-b)... +Uninstalled env-binary-conflict-pkg-a +✓ Installed env-binary-conflict-pkg-b Bins: env-binary-conflict-cli > cat $VP_HOME/bins/env-binary-conflict-cli.json # Bin config should now point to pkg-b { "name": "env-binary-conflict-cli", - "package": "./env-binary-conflict-pkg-b", + "package": "env-binary-conflict-pkg-b", "version": "2.0.0", "nodeVersion": "22.22.0", "source": "vp" diff --git a/packages/cli/snap-tests-global/npm-global-install-already-linked/snap.txt b/packages/cli/snap-tests-global/npm-global-install-already-linked/snap.txt index 9650a8d433..57a939d2c4 100644 --- a/packages/cli/snap-tests-global/npm-global-install-already-linked/snap.txt +++ b/packages/cli/snap-tests-global/npm-global-install-already-linked/snap.txt @@ -3,7 +3,7 @@ > vp install -g ./npm-global-linked-pkg # First install via vp (creates managed shim) info: Installing 1 global package with Node.js -✓ Installed ./npm-global-linked-pkg +✓ Installed npm-global-linked-pkg Bins: npm-global-linked-cli > npm-global-linked-cli # Should be callable via the link @@ -12,7 +12,7 @@ npm-global-linked-cli works > npm install -g ./npm-global-linked-pkg # Should NOT show hint (binary already exists) added 1 package in ms -Skipped 'npm-global-linked-cli': managed by `vp install -g ./npm-global-linked-pkg`. Run `vp uninstall -g ./npm-global-linked-pkg` to remove it first. +Skipped 'npm-global-linked-cli': managed by `vp install -g npm-global-linked-pkg`. Run `vp uninstall -g npm-global-linked-pkg` to remove it first. > vp remove -g npm-global-linked-pkg # Cleanup Uninstalled npm-global-linked-pkg diff --git a/packages/cli/snap-tests-global/npm-global-uninstall-vp-managed/snap.txt b/packages/cli/snap-tests-global/npm-global-uninstall-vp-managed/snap.txt index 6d08f8a9a5..6fe653db93 100644 --- a/packages/cli/snap-tests-global/npm-global-uninstall-vp-managed/snap.txt +++ b/packages/cli/snap-tests-global/npm-global-uninstall-vp-managed/snap.txt @@ -1,12 +1,12 @@ > vp install -g ./npm-global-vp-managed-pkg # Install via vp (creates managed shim) info: Installing 1 global package with Node.js -✓ Installed ./npm-global-vp-managed-pkg +✓ Installed npm-global-vp-managed-pkg Bins: npm-global-vp-managed-cli > npm install -g ./npm-global-vp-managed-pkg # npm install (should warn about conflict) added 1 package in ms -Skipped 'npm-global-vp-managed-cli': managed by `vp install -g ./npm-global-vp-managed-pkg`. Run `vp uninstall -g ./npm-global-vp-managed-pkg` to remove it first. +Skipped 'npm-global-vp-managed-cli': managed by `vp install -g npm-global-vp-managed-pkg`. Run `vp uninstall -g npm-global-vp-managed-pkg` to remove it first. > npm uninstall -g npm-global-vp-managed-pkg # npm uninstall should NOT remove the vp-managed shim diff --git a/packages/cli/snap-tests-global/shim-recursive-package-binary/snap.txt b/packages/cli/snap-tests-global/shim-recursive-package-binary/snap.txt index a8a56719e0..8744528a8a 100644 --- a/packages/cli/snap-tests-global/shim-recursive-package-binary/snap.txt +++ b/packages/cli/snap-tests-global/shim-recursive-package-binary/snap.txt @@ -1,6 +1,6 @@ > vp install -g ./recursive-cli-pkg # Install test package info: Installing 1 global package with Node.js -✓ Installed ./recursive-cli-pkg +✓ Installed recursive-cli-pkg Bins: recursive-cli > recursive-cli # Outer call triggers recursive inner call through shim