Procházet zdrojové kódy

fix(cli.rs): pnpm tauri info exits with error (fix #2509) (#2510)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Barry Simons před 4 roky
rodič
revize
2026134f47
2 změnil soubory, kde provedl 259 přidání a 74 odebrání
  1. 5 0
      .changes/tooling-fix-pnpm-info-error.md
  2. 254 74
      tooling/cli.rs/src/info.rs

+ 5 - 0
.changes/tooling-fix-pnpm-info-error.md

@@ -0,0 +1,5 @@
+---
+'cli.rs': patch
+---
+
+Fixes pnpm error when running `pnpm tauri info`.

+ 254 - 74
tooling/cli.rs/src/info.rs

@@ -72,6 +72,12 @@ struct CargoManifest {
   dependencies: HashMap<String, CargoManifestDependency>,
 }
 
+enum PackageManager {
+  Npm,
+  Pnpm,
+  Yarn,
+}
+
 #[derive(Default)]
 pub struct Info;
 
@@ -86,96 +92,141 @@ fn crate_latest_version(name: &str) -> Option<String> {
   }
 }
 
-fn npm_latest_version(use_yarn: bool, name: &str) -> crate::Result<Option<String>> {
+fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result<Option<String>> {
   let mut cmd;
-  if use_yarn {
-    #[cfg(target_os = "windows")]
-    {
-      cmd = Command::new("cmd");
-      cmd.arg("/c").arg("yarn");
-    }
+  match pm {
+    PackageManager::Yarn => {
+      #[cfg(target_os = "windows")]
+      {
+        cmd = Command::new("cmd");
+        cmd.arg("/c").arg("yarn");
+      }
 
-    #[cfg(not(target_os = "windows"))]
-    {
-      cmd = Command::new("yarn")
-    }
+      #[cfg(not(target_os = "windows"))]
+      {
+        cmd = Command::new("yarn")
+      }
 
-    let output = cmd
-      .arg("info")
-      .arg(name)
-      .args(&["version", "--json"])
-      .output()?;
-    if output.status.success() {
-      let stdout = String::from_utf8_lossy(&output.stdout);
-      let info: YarnVersionInfo = serde_json::from_str(&stdout)?;
-      Ok(Some(info.data.last().unwrap().to_string()))
-    } else {
-      Ok(None)
-    }
-  } else {
-    #[cfg(target_os = "windows")]
-    {
-      cmd = Command::new("cmd");
-      cmd.arg("/c").arg("npm");
+      let output = cmd
+        .arg("info")
+        .arg(name)
+        .args(&["version", "--json"])
+        .output()?;
+      if output.status.success() {
+        let stdout = String::from_utf8_lossy(&output.stdout);
+        let info: YarnVersionInfo = serde_json::from_str(&stdout)?;
+        Ok(Some(info.data.last().unwrap().to_string()))
+      } else {
+        Ok(None)
+      }
     }
+    PackageManager::Npm => {
+      #[cfg(target_os = "windows")]
+      {
+        cmd = Command::new("cmd");
+        cmd.arg("/c").arg("npm");
+      }
 
-    #[cfg(not(target_os = "windows"))]
-    {
-      cmd = Command::new("npm")
+      #[cfg(not(target_os = "windows"))]
+      {
+        cmd = Command::new("npm")
+      }
+
+      let output = cmd.arg("show").arg(name).arg("version").output()?;
+      if output.status.success() {
+        let stdout = String::from_utf8_lossy(&output.stdout);
+        Ok(Some(stdout.replace("\n", "")))
+      } else {
+        Ok(None)
+      }
     }
+    PackageManager::Pnpm => {
+      #[cfg(target_os = "windows")]
+      {
+        cmd = Command::new("cmd");
+        cmd.arg("/c").arg("pnpm");
+      }
 
-    let output = cmd.arg("show").arg(name).arg("version").output()?;
-    if output.status.success() {
-      let stdout = String::from_utf8_lossy(&output.stdout);
-      Ok(Some(stdout.replace("\n", "")))
-    } else {
-      Ok(None)
+      #[cfg(not(target_os = "windows"))]
+      {
+        cmd = Command::new("pnpm")
+      }
+
+      let output = cmd.arg("info").arg(name).arg("version").output()?;
+      if output.status.success() {
+        let stdout = String::from_utf8_lossy(&output.stdout);
+        Ok(Some(stdout.replace("\n", "")))
+      } else {
+        Ok(None)
+      }
     }
   }
 }
 
 fn npm_package_version<P: AsRef<Path>>(
-  use_yarn: bool,
+  pm: &PackageManager,
   name: &str,
   app_dir: P,
 ) -> crate::Result<Option<String>> {
   let mut cmd;
-  let output = if use_yarn {
-    #[cfg(target_os = "windows")]
-    {
-      cmd = Command::new("cmd");
-      cmd.arg("/c").arg("yarn");
-    }
+  let output = match pm {
+    PackageManager::Yarn => {
+      #[cfg(target_os = "windows")]
+      {
+        cmd = Command::new("cmd");
+        cmd.arg("/c").arg("yarn");
+      }
 
-    #[cfg(not(target_os = "windows"))]
-    {
-      cmd = Command::new("yarn")
-    }
+      #[cfg(not(target_os = "windows"))]
+      {
+        cmd = Command::new("yarn")
+      }
 
-    cmd
-      .args(&["list", "--pattern"])
-      .arg(name)
-      .args(&["--depth", "0"])
-      .current_dir(app_dir)
-      .output()?
-  } else {
-    #[cfg(target_os = "windows")]
-    {
-      cmd = Command::new("cmd");
-      cmd.arg("/c").arg("npm");
+      cmd
+        .args(&["list", "--pattern"])
+        .arg(name)
+        .args(&["--depth", "0"])
+        .current_dir(app_dir)
+        .output()?
     }
+    PackageManager::Npm => {
+      #[cfg(target_os = "windows")]
+      {
+        cmd = Command::new("cmd");
+        cmd.arg("/c").arg("npm");
+      }
+
+      #[cfg(not(target_os = "windows"))]
+      {
+        cmd = Command::new("npm")
+      }
 
-    #[cfg(not(target_os = "windows"))]
-    {
-      cmd = Command::new("npm")
+      cmd
+        .arg("list")
+        .arg(name)
+        .args(&["version", "--depth", "0"])
+        .current_dir(app_dir)
+        .output()?
     }
+    PackageManager::Pnpm => {
+      #[cfg(target_os = "windows")]
+      {
+        cmd = Command::new("cmd");
+        cmd.arg("/c").arg("pnpm");
+      }
+
+      #[cfg(not(target_os = "windows"))]
+      {
+        cmd = Command::new("pnpm")
+      }
 
-    cmd
-      .arg("list")
-      .arg(name)
-      .args(&["version", "--depth", "0"])
-      .current_dir(app_dir)
-      .output()?
+      cmd
+        .arg("list")
+        .arg(name)
+        .args(&["--parseable", "--depth", "0"])
+        .current_dir(app_dir)
+        .output()?
+    }
   };
   if output.status.success() {
     let stdout = String::from_utf8_lossy(&output.stdout);
@@ -360,9 +411,22 @@ impl Info {
     let app_dir = panic::catch_unwind(app_dir).map(Some).unwrap_or_default();
     panic::set_hook(hook);
 
-    let use_yarn = app_dir
-      .map(|dir| dir.join("yarn.lock").exists())
-      .unwrap_or_default();
+    let mut package_manager = PackageManager::Npm;
+    if let Some(app_dir) = &app_dir {
+      let file_names = read_dir(app_dir)
+        .unwrap()
+        .filter(|e| {
+          e.as_ref()
+            .unwrap()
+            .metadata()
+            .unwrap()
+            .file_type()
+            .is_file()
+        })
+        .map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
+        .collect::<Vec<String>>();
+      package_manager = get_package_manager(&file_names)?;
+    }
 
     if let Some(node_version) = get_version("node", &[]).unwrap_or_default() {
       InfoBlock::new("Node.js environment").section().display();
@@ -375,20 +439,21 @@ impl Info {
       .display();
 
       VersionBlock::new("  @tauri-apps/cli", metadata.js_cli.version)
-        .target_version(npm_latest_version(use_yarn, "@tauri-apps/cli").unwrap_or_default())
+        .target_version(npm_latest_version(&package_manager, "@tauri-apps/cli").unwrap_or_default())
         .display();
       if let Some(app_dir) = &app_dir {
         VersionBlock::new(
           "  @tauri-apps/api",
-          npm_package_version(use_yarn, "@tauri-apps/api", app_dir).unwrap_or_default(),
+          npm_package_version(&package_manager, "@tauri-apps/api", app_dir).unwrap_or_default(),
         )
-        .target_version(npm_latest_version(use_yarn, "@tauri-apps/api").unwrap_or_default())
+        .target_version(npm_latest_version(&package_manager, "@tauri-apps/api").unwrap_or_default())
         .display();
       }
 
       InfoBlock::new("Global packages").section().display();
 
       VersionBlock::new("  npm", get_version("npm", &[]).unwrap_or_default()).display();
+      VersionBlock::new("  pnpm", get_version("pnpm", &[]).unwrap_or_default()).display();
       VersionBlock::new("  yarn", get_version("yarn", &[]).unwrap_or_default()).display();
     }
 
@@ -576,3 +641,118 @@ impl Info {
     Ok(())
   }
 }
+
+fn get_package_manager<T: AsRef<str>>(file_names: &[T]) -> crate::Result<PackageManager> {
+  let mut use_npm = false;
+  let mut use_pnpm = false;
+  let mut use_yarn = false;
+
+  for name in file_names {
+    if name.as_ref() == "package-lock.json" {
+      use_npm = true;
+    } else if name.as_ref() == "pnpm-lock.yaml" {
+      use_pnpm = true;
+    } else if name.as_ref() == "yarn.lock" {
+      use_yarn = true;
+    }
+  }
+
+  if !use_npm && !use_pnpm && !use_yarn {
+    println!("WARNING: no lock files found, defaulting to npm");
+    return Ok(PackageManager::Npm);
+  }
+
+  let mut found = Vec::new();
+
+  if use_npm {
+    found.push("npm");
+  }
+  if use_pnpm {
+    found.push("pnpm");
+  }
+  if use_yarn {
+    found.push("yarn");
+  }
+
+  if found.len() > 1 {
+    return Err(anyhow::anyhow!(
+      "only one package mangager should be used, but found {}\nplease remove unused package manager lock files",
+      found.join(" and ")
+    ));
+  }
+
+  if use_npm {
+    Ok(PackageManager::Npm)
+  } else if use_pnpm {
+    Ok(PackageManager::Pnpm)
+  } else {
+    Ok(PackageManager::Yarn)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::info::get_package_manager;
+
+  #[test]
+  fn no_package_manager_lock_file() -> crate::Result<()> {
+    let file_names = vec!["package.json"];
+    let pm = get_package_manager(&file_names);
+    match pm {
+      Ok(_) => Ok(()),
+      Err(m) => Err(m),
+    }
+  }
+
+  #[test]
+  fn package_managers_npm_and_yarn() -> crate::Result<()> {
+    let file_names = vec!["package.json", "package-lock.json", "yarn.lock"];
+    let pm = get_package_manager(&file_names);
+    match pm {
+      Ok(_) => panic!("expected error"),
+      Err(m) => assert_eq!(
+        m.to_string().as_str(),
+        "only one package mangager should be used, but found npm and yarn\nplease remove unused package manager lock files"
+      ),
+    }
+    Ok(())
+  }
+
+  #[test]
+  fn package_managers_npm_and_pnpm() -> crate::Result<()> {
+    let file_names = vec!["package.json", "package-lock.json", "pnpm-lock.yaml"];
+    let pm = get_package_manager(&file_names);
+    match pm {
+      Ok(_) => panic!("expected error"),
+      Err(m) => assert_eq!(
+        m.to_string().as_str(),
+        "only one package mangager should be used, but found npm and pnpm\nplease remove unused package manager lock files"
+      ),
+    }
+    Ok(())
+  }
+
+  #[test]
+  fn package_managers_pnpm_and_yarn() -> crate::Result<()> {
+    let file_names = vec!["package.json", "pnpm-lock.yaml", "yarn.lock"];
+    let pm = get_package_manager(&file_names);
+    match pm {
+      Ok(_) => panic!("expected error"),
+      Err(m) => assert_eq!(
+        m.to_string().as_str(),
+        "only one package mangager should be used, but found pnpm and yarn\nplease remove unused package manager lock files"
+      ),
+    }
+    Ok(())
+  }
+
+  #[test]
+  fn package_managers_yarn() -> crate::Result<()> {
+    let file_names = vec!["package.json", "yarn.lock"];
+    let pm = get_package_manager(&file_names);
+    match pm {
+      Ok(_) => Ok(()),
+      Err(m) => Err(m),
+    }
+  }
+}