-
Notifications
You must be signed in to change notification settings - Fork 215
feat: support yarn v1 detection from lockfile #1885
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ use std::{ | |
| collections::HashMap, | ||
| env, fmt, | ||
| fs::{self, File}, | ||
| io::{self, BufReader, IsTerminal, Write}, | ||
| io::{self, BufRead, BufReader, IsTerminal, Write}, | ||
| path::Path, | ||
| }; | ||
|
|
||
|
|
@@ -291,10 +291,20 @@ pub fn get_package_manager_type_and_version( | |
| return Ok((PackageManagerType::Pnpm, version, None, source)); | ||
| } | ||
|
|
||
| // if yarn.lock or .yarnrc.yml exists, use yarn@latest | ||
| // if yarn.lock exists, detect version from lockfile | ||
| let yarn_lock_path = workspace_root.path.join("yarn.lock"); | ||
| if is_exists_file(&yarn_lock_path)? { | ||
| let version = if is_yarn_lockfile_v1(&yarn_lock_path) { | ||
| Str::from(">=1.0.0 <2.0.0") | ||
|
Comment on lines
+296
to
+298
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a workspace has both Useful? React with 👍 / 👎. |
||
| } else { | ||
| version // "latest" | ||
| }; | ||
| return Ok((PackageManagerType::Yarn, version, None, source)); | ||
| } | ||
|
|
||
| // if .yarnrc.yml exists, use yarn@latest | ||
| let yarnrc_yml_path = workspace_root.path.join(".yarnrc.yml"); | ||
| if is_exists_file(&yarn_lock_path)? || is_exists_file(&yarnrc_yml_path)? { | ||
| if is_exists_file(&yarnrc_yml_path)? { | ||
| return Ok((PackageManagerType::Yarn, version, None, source)); | ||
| } | ||
|
|
||
|
|
@@ -619,6 +629,21 @@ fn is_exists_file(path: impl AsRef<Path>) -> Result<bool, Error> { | |
| } | ||
| } | ||
|
|
||
| /// Detect whether a `yarn.lock` file is for yarn v1 or not. | ||
| /// | ||
| /// See https://github.com/yarnpkg/yarn/blob/c2dda503f3759b5be5f0e24ecd9cf5c97a540147/src/lockfile/parse.js#L28 | ||
| fn is_yarn_lockfile_v1(path: impl AsRef<Path>) -> bool { | ||
| let Ok(file) = File::open(path.as_ref()) else { | ||
| return false; | ||
| }; | ||
| let reader = BufReader::new(file); | ||
| reader | ||
| .lines() | ||
| .take(2) | ||
| .filter_map(|line| line.ok()) | ||
| .any(|line| line.trim() == "# yarn lockfile v1") | ||
| } | ||
|
|
||
| /// Whether a managed package manager install is complete (usable on the current | ||
| /// platform). | ||
| /// | ||
|
|
@@ -2045,10 +2070,57 @@ mod tests { | |
| let entry = &package_json["devEngines"]["packageManager"]; | ||
| assert_eq!(entry["name"].as_str().unwrap(), "yarn"); | ||
| assert_eq!(entry["onFail"].as_str().unwrap(), "download"); | ||
| // resolved to yarn v1 | ||
| assert!( | ||
| entry["version"].as_str().unwrap().starts_with("1."), | ||
| "expected yarn v1, got version {:?}", | ||
| entry["version"] | ||
| ); | ||
| // keep other fields | ||
| assert_eq!(package_json["name"].as_str().unwrap(), "test-package"); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_detect_package_manager_with_yarn_berry_lock() { | ||
| let temp_dir = create_temp_dir(); | ||
| let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); | ||
| let package_content = r#"{"name": "test-package"}"#; | ||
| create_package_json(&temp_dir_path, package_content); | ||
|
|
||
| // Create yarn.lock | ||
| fs::write( | ||
| temp_dir_path.join("yarn.lock"), | ||
| "__metadata:\n version: 10\n cacheKey: 10c0\n", | ||
| ) | ||
| .expect("Failed to write berry yarn.lock"); | ||
|
|
||
| let result = PackageManager::builder(temp_dir_path.to_absolute_path_buf()) | ||
| .build() | ||
| .await | ||
| .expect("Should detect yarn"); | ||
| assert_eq!(result.bin_name, "yarn"); | ||
| assert!( | ||
| result.get_bin_prefix().ends_with("yarn/bin"), | ||
| "bin_prefix should end with yarn/bin, but got {:?}", | ||
| result.get_bin_prefix() | ||
| ); | ||
| // auto-pin writes devEngines.packageManager | ||
| let package_json_path = temp_dir_path.join("package.json"); | ||
| let package_json: serde_json::Value = | ||
| serde_json::from_slice(&fs::read(&package_json_path).unwrap()).unwrap(); | ||
| println!("package_json: {package_json:?}"); | ||
| let entry = &package_json["devEngines"]["packageManager"]; | ||
| assert_eq!(entry["name"].as_str().unwrap(), "yarn"); | ||
| assert_eq!(entry["onFail"].as_str().unwrap(), "download"); | ||
| // resolved to latest yarn berry (2.x+), not v1 | ||
| assert!( | ||
| !entry["version"].as_str().unwrap().starts_with("1."), | ||
| "expected yarn berry, got version {:?}", | ||
| entry["version"] | ||
| ); | ||
| assert_eq!(package_json["name"].as_str().unwrap(), "test-package"); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| #[cfg(not(windows))] // FIXME | ||
| async fn test_detect_package_manager_with_package_lock_json() { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a project has only a Yarn v1
yarn.lockand the user's VP cache already contains an older Yarn 1.x install, returning this range flows intodownload_package_manager, whose range resolver checksfind_cached_package_manager_versionbefore the registry and returns that cached version. Because this detection source isLockfileOrConfig,build()then auto-pins the stale cached version intodevEngines.packageManager, so a lockfile-only project can be stuck on an arbitrary old Yarn 1.x instead of the latest compatible Yarn v1 release.Useful? React with 👍 / 👎.