info.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use crate::helpers::{
  5. app_paths::{app_dir, tauri_dir},
  6. config::get as get_config,
  7. framework::infer_from_package_json as infer_framework,
  8. };
  9. use serde::Deserialize;
  10. use std::{
  11. collections::HashMap,
  12. fs::{read_dir, read_to_string},
  13. panic,
  14. path::{Path, PathBuf},
  15. process::Command,
  16. };
  17. #[derive(Deserialize)]
  18. struct YarnVersionInfo {
  19. data: Vec<String>,
  20. }
  21. #[derive(Clone, Deserialize)]
  22. struct CargoLockPackage {
  23. name: String,
  24. version: String,
  25. }
  26. #[derive(Deserialize)]
  27. struct CargoLock {
  28. package: Vec<CargoLockPackage>,
  29. }
  30. #[derive(Deserialize)]
  31. struct JsCliVersionMetadata {
  32. version: String,
  33. node: String,
  34. }
  35. #[derive(Deserialize)]
  36. #[serde(rename_all = "camelCase")]
  37. struct VersionMetadata {
  38. #[serde(rename = "cli.js")]
  39. js_cli: JsCliVersionMetadata,
  40. }
  41. #[derive(Clone, Deserialize)]
  42. struct CargoManifestDependencyPackage {
  43. version: Option<String>,
  44. path: Option<PathBuf>,
  45. #[serde(default)]
  46. features: Vec<String>,
  47. }
  48. #[derive(Clone, Deserialize)]
  49. #[serde(untagged)]
  50. enum CargoManifestDependency {
  51. Version(String),
  52. Package(CargoManifestDependencyPackage),
  53. }
  54. #[derive(Deserialize)]
  55. struct CargoManifestPackage {
  56. version: String,
  57. }
  58. #[derive(Deserialize)]
  59. struct CargoManifest {
  60. package: CargoManifestPackage,
  61. dependencies: HashMap<String, CargoManifestDependency>,
  62. }
  63. enum PackageManager {
  64. Npm,
  65. Pnpm,
  66. Yarn,
  67. }
  68. #[derive(Default)]
  69. pub struct Info;
  70. fn crate_latest_version(name: &str) -> Option<String> {
  71. let url = format!("https://docs.rs/crate/{}/", name);
  72. match ureq::get(&url).call() {
  73. Ok(response) => match (response.status(), response.header("location")) {
  74. (302, Some(location)) => Some(location.replace(&url, "")),
  75. _ => None,
  76. },
  77. Err(_) => None,
  78. }
  79. }
  80. fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result<Option<String>> {
  81. let mut cmd;
  82. match pm {
  83. PackageManager::Yarn => {
  84. #[cfg(target_os = "windows")]
  85. {
  86. cmd = Command::new("cmd");
  87. cmd.arg("/c").arg("yarn");
  88. }
  89. #[cfg(not(target_os = "windows"))]
  90. {
  91. cmd = Command::new("yarn")
  92. }
  93. let output = cmd
  94. .arg("info")
  95. .arg(name)
  96. .args(&["version", "--json"])
  97. .output()?;
  98. if output.status.success() {
  99. let stdout = String::from_utf8_lossy(&output.stdout);
  100. let info: YarnVersionInfo = serde_json::from_str(&stdout)?;
  101. Ok(Some(info.data.last().unwrap().to_string()))
  102. } else {
  103. Ok(None)
  104. }
  105. }
  106. PackageManager::Npm => {
  107. #[cfg(target_os = "windows")]
  108. {
  109. cmd = Command::new("cmd");
  110. cmd.arg("/c").arg("npm");
  111. }
  112. #[cfg(not(target_os = "windows"))]
  113. {
  114. cmd = Command::new("npm")
  115. }
  116. let output = cmd.arg("show").arg(name).arg("version").output()?;
  117. if output.status.success() {
  118. let stdout = String::from_utf8_lossy(&output.stdout);
  119. Ok(Some(stdout.replace("\n", "")))
  120. } else {
  121. Ok(None)
  122. }
  123. }
  124. PackageManager::Pnpm => {
  125. #[cfg(target_os = "windows")]
  126. {
  127. cmd = Command::new("cmd");
  128. cmd.arg("/c").arg("pnpm");
  129. }
  130. #[cfg(not(target_os = "windows"))]
  131. {
  132. cmd = Command::new("pnpm")
  133. }
  134. let output = cmd.arg("info").arg(name).arg("version").output()?;
  135. if output.status.success() {
  136. let stdout = String::from_utf8_lossy(&output.stdout);
  137. Ok(Some(stdout.replace("\n", "")))
  138. } else {
  139. Ok(None)
  140. }
  141. }
  142. }
  143. }
  144. fn npm_package_version<P: AsRef<Path>>(
  145. pm: &PackageManager,
  146. name: &str,
  147. app_dir: P,
  148. ) -> crate::Result<Option<String>> {
  149. let mut cmd;
  150. let output = match pm {
  151. PackageManager::Yarn => {
  152. #[cfg(target_os = "windows")]
  153. {
  154. cmd = Command::new("cmd");
  155. cmd.arg("/c").arg("yarn");
  156. }
  157. #[cfg(not(target_os = "windows"))]
  158. {
  159. cmd = Command::new("yarn")
  160. }
  161. cmd
  162. .args(&["list", "--pattern"])
  163. .arg(name)
  164. .args(&["--depth", "0"])
  165. .current_dir(app_dir)
  166. .output()?
  167. }
  168. PackageManager::Npm => {
  169. #[cfg(target_os = "windows")]
  170. {
  171. cmd = Command::new("cmd");
  172. cmd.arg("/c").arg("npm");
  173. }
  174. #[cfg(not(target_os = "windows"))]
  175. {
  176. cmd = Command::new("npm")
  177. }
  178. cmd
  179. .arg("list")
  180. .arg(name)
  181. .args(&["version", "--depth", "0"])
  182. .current_dir(app_dir)
  183. .output()?
  184. }
  185. PackageManager::Pnpm => {
  186. #[cfg(target_os = "windows")]
  187. {
  188. cmd = Command::new("cmd");
  189. cmd.arg("/c").arg("pnpm");
  190. }
  191. #[cfg(not(target_os = "windows"))]
  192. {
  193. cmd = Command::new("pnpm")
  194. }
  195. cmd
  196. .arg("list")
  197. .arg(name)
  198. .args(&["--parseable", "--depth", "0"])
  199. .current_dir(app_dir)
  200. .output()?
  201. }
  202. };
  203. if output.status.success() {
  204. let stdout = String::from_utf8_lossy(&output.stdout);
  205. let regex = regex::Regex::new("@([\\da-zA-Z\\-\\.]+)").unwrap();
  206. Ok(
  207. regex
  208. .captures_iter(&stdout)
  209. .last()
  210. .and_then(|cap| cap.get(1).map(|v| v.as_str().to_string())),
  211. )
  212. } else {
  213. Ok(None)
  214. }
  215. }
  216. fn get_version(command: &str, args: &[&str]) -> crate::Result<Option<String>> {
  217. let mut cmd;
  218. #[cfg(target_os = "windows")]
  219. {
  220. cmd = Command::new("cmd");
  221. cmd.arg("/c").arg(command);
  222. }
  223. #[cfg(not(target_os = "windows"))]
  224. {
  225. cmd = Command::new(command)
  226. }
  227. let output = cmd.args(args).arg("--version").output()?;
  228. let version = if output.status.success() {
  229. Some(
  230. String::from_utf8_lossy(&output.stdout)
  231. .replace("\n", "")
  232. .replace("\r", ""),
  233. )
  234. } else {
  235. None
  236. };
  237. Ok(version)
  238. }
  239. #[cfg(windows)]
  240. fn webview2_version() -> crate::Result<Option<String>> {
  241. let output = Command::new("powershell")
  242. .args(&["-NoProfile", "-Command"])
  243. .arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
  244. .output()?;
  245. let version = if output.status.success() {
  246. Some(String::from_utf8_lossy(&output.stdout).replace("\n", ""))
  247. } else {
  248. // check 32bit installation
  249. let output = Command::new("powershell")
  250. .args(&["-NoProfile", "-Command"])
  251. .arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
  252. .output()?;
  253. if output.status.success() {
  254. Some(String::from_utf8_lossy(&output.stdout).replace("\n", ""))
  255. } else {
  256. None
  257. }
  258. };
  259. Ok(version)
  260. }
  261. struct InfoBlock {
  262. section: bool,
  263. key: &'static str,
  264. value: Option<String>,
  265. suffix: Option<String>,
  266. }
  267. impl InfoBlock {
  268. fn new(key: &'static str) -> Self {
  269. Self {
  270. section: false,
  271. key,
  272. value: None,
  273. suffix: None,
  274. }
  275. }
  276. fn section(mut self) -> Self {
  277. self.section = true;
  278. self
  279. }
  280. fn value<V: Into<Option<String>>>(mut self, value: V) -> Self {
  281. self.value = value.into();
  282. self
  283. }
  284. fn suffix<S: Into<Option<String>>>(mut self, suffix: S) -> Self {
  285. self.suffix = suffix.into();
  286. self
  287. }
  288. fn display(&self) {
  289. if self.section {
  290. println!();
  291. }
  292. print!("{}", self.key);
  293. if let Some(value) = &self.value {
  294. print!(" - {}", value);
  295. }
  296. if let Some(suffix) = &self.suffix {
  297. print!("{}", suffix);
  298. }
  299. println!();
  300. }
  301. }
  302. struct VersionBlock {
  303. section: bool,
  304. key: &'static str,
  305. version: Option<String>,
  306. target_version: Option<String>,
  307. }
  308. impl VersionBlock {
  309. fn new<V: Into<Option<String>>>(key: &'static str, version: V) -> Self {
  310. Self {
  311. section: false,
  312. key,
  313. version: version.into(),
  314. target_version: None,
  315. }
  316. }
  317. fn target_version<V: Into<Option<String>>>(mut self, version: V) -> Self {
  318. self.target_version = version.into();
  319. self
  320. }
  321. fn display(&self) {
  322. if self.section {
  323. println!();
  324. }
  325. print!("{}", self.key);
  326. if let Some(version) = &self.version {
  327. print!(" - {}", version);
  328. } else {
  329. print!(" - Not installed");
  330. }
  331. if let (Some(version), Some(target_version)) = (&self.version, &self.target_version) {
  332. let version = semver::Version::parse(version).unwrap();
  333. let target_version = semver::Version::parse(target_version).unwrap();
  334. if version < target_version {
  335. print!(" (outdated, latest: {})", target_version);
  336. }
  337. }
  338. println!();
  339. }
  340. }
  341. impl Info {
  342. pub fn new() -> Self {
  343. Default::default()
  344. }
  345. pub fn run(self) -> crate::Result<()> {
  346. let os_info = os_info::get();
  347. InfoBlock {
  348. section: true,
  349. key: "Operating System",
  350. value: Some(format!(
  351. "{}, version {} {:?}",
  352. os_info.os_type(),
  353. os_info.version(),
  354. os_info.bitness()
  355. )),
  356. suffix: None,
  357. }
  358. .display();
  359. #[cfg(windows)]
  360. VersionBlock::new("Webview2", webview2_version().unwrap_or_default()).display();
  361. let hook = panic::take_hook();
  362. panic::set_hook(Box::new(|_info| {
  363. // do nothing
  364. }));
  365. let app_dir = panic::catch_unwind(app_dir).map(Some).unwrap_or_default();
  366. panic::set_hook(hook);
  367. let mut package_manager = PackageManager::Npm;
  368. if let Some(app_dir) = &app_dir {
  369. let file_names = read_dir(app_dir)
  370. .unwrap()
  371. .filter(|e| {
  372. e.as_ref()
  373. .unwrap()
  374. .metadata()
  375. .unwrap()
  376. .file_type()
  377. .is_file()
  378. })
  379. .map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
  380. .collect::<Vec<String>>();
  381. package_manager = get_package_manager(&file_names)?;
  382. }
  383. if let Some(node_version) = get_version("node", &[]).unwrap_or_default() {
  384. InfoBlock::new("Node.js environment").section().display();
  385. let metadata = serde_json::from_str::<VersionMetadata>(include_str!("../metadata.json"))?;
  386. VersionBlock::new(
  387. " Node.js",
  388. node_version.chars().skip(1).collect::<String>(),
  389. )
  390. .target_version(metadata.js_cli.node.replace(">= ", ""))
  391. .display();
  392. VersionBlock::new(" @tauri-apps/cli", metadata.js_cli.version)
  393. .target_version(npm_latest_version(&package_manager, "@tauri-apps/cli").unwrap_or_default())
  394. .display();
  395. if let Some(app_dir) = &app_dir {
  396. VersionBlock::new(
  397. " @tauri-apps/api",
  398. npm_package_version(&package_manager, "@tauri-apps/api", app_dir).unwrap_or_default(),
  399. )
  400. .target_version(npm_latest_version(&package_manager, "@tauri-apps/api").unwrap_or_default())
  401. .display();
  402. }
  403. InfoBlock::new("Global packages").section().display();
  404. VersionBlock::new(" npm", get_version("npm", &[]).unwrap_or_default()).display();
  405. VersionBlock::new(" pnpm", get_version("pnpm", &[]).unwrap_or_default()).display();
  406. VersionBlock::new(" yarn", get_version("yarn", &[]).unwrap_or_default()).display();
  407. }
  408. InfoBlock::new("Rust environment").section().display();
  409. VersionBlock::new(
  410. " rustc",
  411. get_version("rustc", &[]).unwrap_or_default().map(|v| {
  412. let mut s = v.split(' ');
  413. s.next();
  414. s.next().unwrap().to_string()
  415. }),
  416. )
  417. .display();
  418. VersionBlock::new(
  419. " cargo",
  420. get_version("cargo", &[]).unwrap_or_default().map(|v| {
  421. let mut s = v.split(' ');
  422. s.next();
  423. s.next().unwrap().to_string()
  424. }),
  425. )
  426. .display();
  427. if let Some(app_dir) = app_dir {
  428. InfoBlock::new("App directory structure")
  429. .section()
  430. .display();
  431. for entry in read_dir(app_dir)? {
  432. let entry = entry?;
  433. if entry.path().is_dir() {
  434. println!("/{}", entry.path().file_name().unwrap().to_string_lossy());
  435. }
  436. }
  437. InfoBlock::new("App").section().display();
  438. let tauri_dir = tauri_dir();
  439. let manifest: Option<CargoManifest> =
  440. if let Ok(manifest_contents) = read_to_string(tauri_dir.join("Cargo.toml")) {
  441. toml::from_str(&manifest_contents).ok()
  442. } else {
  443. None
  444. };
  445. let lock: Option<CargoLock> =
  446. if let Ok(lock_contents) = read_to_string(tauri_dir.join("Cargo.lock")) {
  447. toml::from_str(&lock_contents).ok()
  448. } else {
  449. None
  450. };
  451. let tauri_lock_packages: Vec<CargoLockPackage> = lock
  452. .as_ref()
  453. .map(|lock| {
  454. lock
  455. .package
  456. .iter()
  457. .filter(|p| p.name == "tauri")
  458. .cloned()
  459. .collect()
  460. })
  461. .unwrap_or_default();
  462. let (tauri_version_string, found_tauri_versions) =
  463. match (&manifest, &lock, tauri_lock_packages.len()) {
  464. (Some(_manifest), Some(_lock), 1) => {
  465. let tauri_lock_package = tauri_lock_packages.first().unwrap();
  466. (
  467. tauri_lock_package.version.clone(),
  468. vec![tauri_lock_package.version.clone()],
  469. )
  470. }
  471. (None, Some(_lock), 1) => {
  472. let tauri_lock_package = tauri_lock_packages.first().unwrap();
  473. (
  474. format!("{} (no manifest)", tauri_lock_package.version),
  475. vec![tauri_lock_package.version.clone()],
  476. )
  477. }
  478. _ => {
  479. let mut found_tauri_versions = Vec::new();
  480. let manifest_version = match manifest.and_then(|m| m.dependencies.get("tauri").cloned())
  481. {
  482. Some(tauri) => match tauri {
  483. CargoManifestDependency::Version(v) => {
  484. found_tauri_versions.push(v.clone());
  485. v
  486. }
  487. CargoManifestDependency::Package(p) => {
  488. if let Some(v) = p.version {
  489. found_tauri_versions.push(v.clone());
  490. v
  491. } else if let Some(p) = p.path {
  492. let manifest_path = tauri_dir.join(&p).join("Cargo.toml");
  493. let v = match read_to_string(&manifest_path)
  494. .map_err(|_| ())
  495. .and_then(|m| toml::from_str::<CargoManifest>(&m).map_err(|_| ()))
  496. {
  497. Ok(manifest) => manifest.package.version,
  498. Err(_) => "unknown version".to_string(),
  499. };
  500. format!("path:{:?} [{}]", p, v)
  501. } else {
  502. "unknown manifest".to_string()
  503. }
  504. }
  505. },
  506. None => "no manifest".to_string(),
  507. };
  508. let lock_version = match (lock, tauri_lock_packages.is_empty()) {
  509. (Some(_lock), true) => tauri_lock_packages
  510. .iter()
  511. .map(|p| p.version.clone())
  512. .collect::<Vec<String>>()
  513. .join(", "),
  514. (Some(_lock), false) => "unknown lockfile".to_string(),
  515. _ => "no lockfile".to_string(),
  516. };
  517. (
  518. format!("{} ({})", manifest_version, lock_version),
  519. found_tauri_versions,
  520. )
  521. }
  522. };
  523. let tauri_version = found_tauri_versions
  524. .into_iter()
  525. .map(|v| semver::Version::parse(&v).unwrap())
  526. .max();
  527. let suffix = match (tauri_version, crate_latest_version("tauri")) {
  528. (Some(version), Some(target_version)) => {
  529. let target_version = semver::Version::parse(&target_version).unwrap();
  530. if version < target_version {
  531. Some(format!(" (outdated, latest: {})", target_version))
  532. } else {
  533. None
  534. }
  535. }
  536. _ => None,
  537. };
  538. InfoBlock::new(" tauri.rs")
  539. .value(tauri_version_string)
  540. .suffix(suffix)
  541. .display();
  542. if let Ok(config) = get_config(None) {
  543. let config_guard = config.lock().unwrap();
  544. let config = config_guard.as_ref().unwrap();
  545. InfoBlock::new(" build-type")
  546. .value(if config.tauri.bundle.active {
  547. "bundle".to_string()
  548. } else {
  549. "build".to_string()
  550. })
  551. .display();
  552. InfoBlock::new(" CSP")
  553. .value(if let Some(security) = &config.tauri.security {
  554. security.csp.clone().unwrap_or_else(|| "unset".to_string())
  555. } else {
  556. "unset".to_string()
  557. })
  558. .display();
  559. InfoBlock::new(" distDir")
  560. .value(config.build.dist_dir.to_string())
  561. .display();
  562. InfoBlock::new(" devPath")
  563. .value(config.build.dev_path.to_string())
  564. .display();
  565. }
  566. if let Ok(package_json) = read_to_string(app_dir.join("package.json")) {
  567. let (framework, bundler) = infer_framework(&package_json);
  568. if let Some(framework) = framework {
  569. InfoBlock::new(" framework")
  570. .value(framework.to_string())
  571. .display();
  572. }
  573. if let Some(bundler) = bundler {
  574. InfoBlock::new(" bundler")
  575. .value(bundler.to_string())
  576. .display();
  577. }
  578. } else {
  579. println!("package.json not found");
  580. }
  581. }
  582. Ok(())
  583. }
  584. }
  585. fn get_package_manager<T: AsRef<str>>(file_names: &[T]) -> crate::Result<PackageManager> {
  586. let mut use_npm = false;
  587. let mut use_pnpm = false;
  588. let mut use_yarn = false;
  589. for name in file_names {
  590. if name.as_ref() == "package-lock.json" {
  591. use_npm = true;
  592. } else if name.as_ref() == "pnpm-lock.yaml" {
  593. use_pnpm = true;
  594. } else if name.as_ref() == "yarn.lock" {
  595. use_yarn = true;
  596. }
  597. }
  598. if !use_npm && !use_pnpm && !use_yarn {
  599. println!("WARNING: no lock files found, defaulting to npm");
  600. return Ok(PackageManager::Npm);
  601. }
  602. let mut found = Vec::new();
  603. if use_npm {
  604. found.push("npm");
  605. }
  606. if use_pnpm {
  607. found.push("pnpm");
  608. }
  609. if use_yarn {
  610. found.push("yarn");
  611. }
  612. if found.len() > 1 {
  613. return Err(anyhow::anyhow!(
  614. "only one package mangager should be used, but found {}\nplease remove unused package manager lock files",
  615. found.join(" and ")
  616. ));
  617. }
  618. if use_npm {
  619. Ok(PackageManager::Npm)
  620. } else if use_pnpm {
  621. Ok(PackageManager::Pnpm)
  622. } else {
  623. Ok(PackageManager::Yarn)
  624. }
  625. }
  626. #[cfg(test)]
  627. mod tests {
  628. use crate::info::get_package_manager;
  629. #[test]
  630. fn no_package_manager_lock_file() -> crate::Result<()> {
  631. let file_names = vec!["package.json"];
  632. let pm = get_package_manager(&file_names);
  633. match pm {
  634. Ok(_) => Ok(()),
  635. Err(m) => Err(m),
  636. }
  637. }
  638. #[test]
  639. fn package_managers_npm_and_yarn() -> crate::Result<()> {
  640. let file_names = vec!["package.json", "package-lock.json", "yarn.lock"];
  641. let pm = get_package_manager(&file_names);
  642. match pm {
  643. Ok(_) => panic!("expected error"),
  644. Err(m) => assert_eq!(
  645. m.to_string().as_str(),
  646. "only one package mangager should be used, but found npm and yarn\nplease remove unused package manager lock files"
  647. ),
  648. }
  649. Ok(())
  650. }
  651. #[test]
  652. fn package_managers_npm_and_pnpm() -> crate::Result<()> {
  653. let file_names = vec!["package.json", "package-lock.json", "pnpm-lock.yaml"];
  654. let pm = get_package_manager(&file_names);
  655. match pm {
  656. Ok(_) => panic!("expected error"),
  657. Err(m) => assert_eq!(
  658. m.to_string().as_str(),
  659. "only one package mangager should be used, but found npm and pnpm\nplease remove unused package manager lock files"
  660. ),
  661. }
  662. Ok(())
  663. }
  664. #[test]
  665. fn package_managers_pnpm_and_yarn() -> crate::Result<()> {
  666. let file_names = vec!["package.json", "pnpm-lock.yaml", "yarn.lock"];
  667. let pm = get_package_manager(&file_names);
  668. match pm {
  669. Ok(_) => panic!("expected error"),
  670. Err(m) => assert_eq!(
  671. m.to_string().as_str(),
  672. "only one package mangager should be used, but found pnpm and yarn\nplease remove unused package manager lock files"
  673. ),
  674. }
  675. Ok(())
  676. }
  677. #[test]
  678. fn package_managers_yarn() -> crate::Result<()> {
  679. let file_names = vec!["package.json", "yarn.lock"];
  680. let pm = get_package_manager(&file_names);
  681. match pm {
  682. Ok(_) => Ok(()),
  683. Err(m) => Err(m),
  684. }
  685. }
  686. }