From 8338a57a7bf9137df268a72949723d72f202d6e6 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 22 Jul 2023 12:13:53 -0700 Subject: [PATCH] Support yarn berry. --- crates/node-depman/src/proto.rs | 108 +++++++++++++--------- crates/node-depman/tests/download_test.rs | 57 +++++++++++- crates/node-depman/tests/versions_test.rs | 34 +++++++ 3 files changed, 153 insertions(+), 46 deletions(-) create mode 100644 crates/node-depman/tests/versions_test.rs diff --git a/crates/node-depman/src/proto.rs b/crates/node-depman/src/proto.rs index 2dc3c49..dc15efb 100644 --- a/crates/node-depman/src/proto.rs +++ b/crates/node-depman/src/proto.rs @@ -19,6 +19,30 @@ enum PackageManager { Yarn, } +impl PackageManager { + pub fn from(env: &Environment) -> PackageManager { + if env.id.to_lowercase().contains("yarn") { + PackageManager::Yarn + } else if env.id.to_lowercase().contains("pnpm") { + PackageManager::Pnpm + } else { + PackageManager::Npm + } + } + + pub fn get_package_name(&self, version: &str) -> String { + if self.is_yarn_berry(version) { + "@yarnpkg/cli-dist".into() + } else { + self.to_string() + } + } + + pub fn is_yarn_berry(&self, version: &str) -> bool { + self == &PackageManager::Yarn && (!version.starts_with('1') || version == "berry") + } +} + impl fmt::Display for PackageManager { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -29,19 +53,9 @@ impl fmt::Display for PackageManager { } } -fn get_package_manager(env: &Environment) -> PackageManager { - if env.id.to_lowercase().contains("yarn") { - PackageManager::Yarn - } else if env.id.to_lowercase().contains("pnpm") { - PackageManager::Pnpm - } else { - PackageManager::Npm - } -} - #[plugin_fn] pub fn register_tool(Json(input): Json) -> FnResult> { - let manager = get_package_manager(&input.env); + let manager = PackageManager::from(&input.env); Ok(Json(ToolMetadataOutput { name: manager.to_string(), @@ -54,18 +68,27 @@ pub fn register_tool(Json(input): Json) -> FnResult, ) -> FnResult> { - let manager = get_package_manager(&input.env); - let version = input.env.version; + let version = &input.env.version; + let manager = PackageManager::from(&input.env); + let package_name = manager.get_package_name(version); + + // Derive values based on package manager + let mut archive_prefix = "package".to_owned(); + let mut package_without_scope = package_name.clone(); + + if manager == PackageManager::Yarn && !manager.is_yarn_berry(version) { + archive_prefix = format!("yarn-v{version}"); + } + + if package_without_scope.contains('/') { + package_without_scope = package_without_scope.split('/').nth(1).unwrap().to_owned(); + } Ok(Json(DownloadPrebuiltOutput { - archive_prefix: Some(if manager == PackageManager::Yarn { - format!("yarn-v{version}") - } else { - "package".into() - }), + archive_prefix: Some(archive_prefix), download_url: format!( - "https://registry.npmjs.org/{name}/-/{name}-{version}.tgz", - name = manager.to_string() + "https://registry.npmjs.org/{}/-/{}-{version}.tgz", + package_name, package_without_scope, ), ..DownloadPrebuiltOutput::default() })) @@ -75,12 +98,12 @@ pub fn download_prebuilt( pub fn locate_bins(Json(input): Json) -> FnResult> { let mut bin_path = None; let package_path = input.tool_dir.join("package.json"); - let manager = get_package_manager(&input.env); + let manager = PackageManager::from(&input.env); + let manager_name = manager.to_string(); // Extract the binary from the `package.json` if package_path.exists() { let package_json: PackageJson = json::from_slice(&fs::read(package_path)?)?; - let package_name = manager.to_string(); if let Some(bin_field) = package_json.bin { match bin_field { @@ -88,7 +111,7 @@ pub fn locate_bins(Json(input): Json) -> FnResult { - if let Some(bin) = map.get(&package_name) { + if let Some(bin) = map.get(&manager_name) { bin_path = Some(bin.to_owned()); } } @@ -108,7 +131,7 @@ pub fn locate_bins(Json(input): Json) -> FnResult) -> FnResult> { let mut output = LoadVersionsOutput::default(); - let manager = get_package_manager(&input.env); + let manager = PackageManager::from(&input.env); + let package_name = manager.get_package_name(&input.initial); + let response: RegistryResponse = - fetch_url_with_cache(format!("https://registry.npmjs.org/{}/", manager))?; + fetch_url_with_cache(format!("https://registry.npmjs.org/{}/", package_name))?; for item in response.versions.values() { output.versions.push(Version::parse(&item.version)?); @@ -154,6 +179,10 @@ pub fn load_versions(Json(input): Json) -> FnResult) -> FnResult, ) -> FnResult> { - let manager = get_package_manager(&input.env); + let manager = PackageManager::from(&input.env); let mut output = ResolveVersionOutput::default(); match manager { - // When the alias "bundled" is provided, we should install the npm - // version that comes bundled with the current Node.js version. PackageManager::Npm => { + // When the alias "bundled" is provided, we should install the npm + // version that comes bundled with the current Node.js version. if input.initial == "bundled" { let node_version = unsafe { exec_command(Json(ExecCommandInput::new("node", ["--version"])))?.0 }; @@ -195,21 +224,10 @@ pub fn resolve_version( } } - // Yarn is installed through npm, but only v1 exists in the npm registry, - // even if a consumer is using Yarn 2/3. https://www.npmjs.com/package/yarn - // Yarn >= 2 works differently than normal packages, as their runtime code - // is stored *within* the repository, and the v1 package detects it. - // Because of this, we need to always install the v1 package! PackageManager::Yarn => { - if !input.initial.starts_with('1') { - unsafe { - trace(Json( - "Found Yarn v2+, installing latest v1 from registry for compatibility" - .into(), - ))?; - } - - output.candidate = Some("1.22.19".into()) + // Latest currently resolves to a v4-rc version... + if input.initial == "berry" { + output.candidate = Some("3".into()); } } @@ -221,7 +239,7 @@ pub fn resolve_version( #[plugin_fn] pub fn create_shims(Json(input): Json) -> FnResult> { - let manager = get_package_manager(&input.env); + let manager = PackageManager::from(&input.env); let mut global_shims = HashMap::::new(); let mut local_shims = HashMap::::new(); @@ -284,7 +302,7 @@ pub fn parse_version_file( Json(input): Json, ) -> FnResult> { let mut version = None; - let manager = get_package_manager(&input.env); + let manager = PackageManager::from(&input.env); if input.file == "package.json" { let package_json: PackageJson = json::from_str(&input.content)?; diff --git a/crates/node-depman/tests/download_test.rs b/crates/node-depman/tests/download_test.rs index 6708dfc..ac5f75b 100644 --- a/crates/node-depman/tests/download_test.rs +++ b/crates/node-depman/tests/download_test.rs @@ -146,7 +146,7 @@ mod yarn { #[test] fn locates_default_bin() { let sandbox = create_empty_sandbox(); - let plugin = create_plugin("pnpm-test", sandbox.path()); + let plugin = create_plugin("yarn-test", sandbox.path()); assert_eq!( plugin @@ -166,6 +166,61 @@ mod yarn { } } +mod yarn_berry { + use super::*; + + generate_download_install_tests!("yarn-test", "3.6.1"); + + #[test] + fn supports_prebuilt() { + let sandbox = create_empty_sandbox(); + let plugin = create_plugin("yarn-test", sandbox.path()); + + assert_eq!( + plugin.download_prebuilt(DownloadPrebuiltInput { + env: Environment { + arch: HostArch::X64, + id: "yarn".into(), + os: HostOS::MacOS, + version: "3.6.1".into(), + ..Default::default() + } + }), + DownloadPrebuiltOutput { + archive_prefix: Some("package".into()), + bin_path: None, + checksum_name: None, + checksum_url: None, + download_name: None, + download_url: "https://registry.npmjs.org/@yarnpkg/cli-dist/-/cli-dist-3.6.1.tgz" + .into() + } + ); + } + + #[test] + fn locates_default_bin() { + let sandbox = create_empty_sandbox(); + let plugin = create_plugin("yarn-test", sandbox.path()); + + assert_eq!( + plugin + .locate_bins(LocateBinsInput { + env: Environment { + arch: HostArch::X64, + id: "yarn".into(), + os: HostOS::MacOS, + version: "3.6.1".into(), + ..Default::default() + }, + tool_dir: PathBuf::new() + }) + .bin_path, + Some("bin/yarn".into()) + ); + } +} + #[test] fn locates_bin_from_package_json_bin() { let sandbox = create_empty_sandbox(); diff --git a/crates/node-depman/tests/versions_test.rs b/crates/node-depman/tests/versions_test.rs new file mode 100644 index 0000000..8548457 --- /dev/null +++ b/crates/node-depman/tests/versions_test.rs @@ -0,0 +1,34 @@ +// use proto_pdk::*; +use proto_pdk_test_utils::{create_plugin, generate_resolve_versions_tests}; +use starbase_sandbox::create_empty_sandbox; + +mod npm { + use super::*; + + generate_resolve_versions_tests!("npm-test", { + "7" => "7.24.2", + "8.1" => "8.1.4", + "9.7.2" => "9.7.2", + }); +} + +mod pnpm { + use super::*; + + generate_resolve_versions_tests!("pnpm-test", { + "7" => "7.33.5", + "8.1" => "8.1.1", + "dev" => "6.23.7-202112041634", + }); +} + +mod yarn { + use super::*; + + generate_resolve_versions_tests!("yarn-test", { + "1" => "1.22.19", + "2" => "2.4.2", + "3" => "3.6.1", + "berry" => "3.6.1", + }); +}