Skip to content

Commit 67efa91

Browse files
authored
Merge pull request #1193 from mikrostew/fetch-yarn3
Fetch and resolve Yarn3 using @yarnpkg/cli-dist
2 parents 7b8bc90 + e9d3d34 commit 67efa91

24 files changed

+674
-164
lines changed

crates/volta-core/src/error/kind.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,11 @@ pub enum ErrorKind {
402402
/// Thrown when the shim binary is called directly, not through a symlink
403403
RunShimDirectly,
404404

405+
/// Thrown when there was an error setting a tool to executable
406+
SetToolExecutable {
407+
tool: String,
408+
},
409+
405410
/// Thrown when there was an error copying an unpacked tool to the image directory
406411
SetupToolImageError {
407412
tool: String,
@@ -494,6 +499,9 @@ pub enum ErrorKind {
494499
#[cfg(windows)]
495500
WriteUserPathError,
496501

502+
/// Thrown when a user attempts to install a version of Yarn2
503+
Yarn2NotSupported,
504+
497505
/// Thrown when there is an error fetching the latest version of Yarn
498506
YarnLatestFetchError {
499507
from_url: String,
@@ -1207,6 +1215,13 @@ Please verify your internet connection.",
12071215
12081216
Please use the existing shims provided by Volta (node, yarn, etc.) to run tools."
12091217
),
1218+
ErrorKind::SetToolExecutable { tool } => write!(
1219+
f,
1220+
r#"Could not set "{}" to executable
1221+
1222+
{}"#,
1223+
tool, PERMISSIONS_CTA
1224+
),
12101225
ErrorKind::SetupToolImageError { tool, version, dir } => write!(
12111226
f,
12121227
"Could not create environment for {} v{}
@@ -1361,6 +1376,12 @@ to {}
13611376
"Could not write Path environment variable.
13621377
13631378
Please ensure you have permissions to edit your environment variables."
1379+
),
1380+
ErrorKind::Yarn2NotSupported => write!(
1381+
f,
1382+
"Yarn version 2 is not recommended for use, and not supported by Volta.
1383+
1384+
Please use version 3 or greater instead."
13641385
),
13651386
ErrorKind::YarnLatestFetchError { from_url } => write!(
13661387
f,
@@ -1474,6 +1495,7 @@ impl ErrorKind {
14741495
ErrorKind::RegistryFetchError { .. } => ExitCode::NetworkError,
14751496
ErrorKind::RunShimDirectly => ExitCode::InvalidArguments,
14761497
ErrorKind::SetupToolImageError { .. } => ExitCode::FileSystemError,
1498+
ErrorKind::SetToolExecutable { .. } => ExitCode::FileSystemError,
14771499
ErrorKind::ShimCreateError { .. } => ExitCode::FileSystemError,
14781500
ErrorKind::ShimRemoveError { .. } => ExitCode::FileSystemError,
14791501
ErrorKind::StringifyBinConfigError => ExitCode::UnknownError,
@@ -1493,6 +1515,7 @@ impl ErrorKind {
14931515
ErrorKind::WritePlatformError { .. } => ExitCode::FileSystemError,
14941516
#[cfg(windows)]
14951517
ErrorKind::WriteUserPathError => ExitCode::EnvironmentError,
1518+
ErrorKind::Yarn2NotSupported => ExitCode::NoVersionMatch,
14961519
ErrorKind::YarnLatestFetchError { .. } => ExitCode::NetworkError,
14971520
ErrorKind::YarnVersionNotFound { .. } => ExitCode::NoVersionMatch,
14981521
}

crates/volta-core/src/tool/npm/resolve.rs

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
//! Provides resolution of npm Version requirements into specific versions
22
33
use super::super::registry::{
4-
public_registry_index, PackageDetails, PackageIndex, RawPackageMetadata,
5-
NPM_ABBREVIATED_ACCEPT_HEADER,
4+
fetch_public_index, public_registry_index, PackageDetails, PackageIndex,
65
};
7-
use super::super::registry_fetch_error;
8-
use crate::error::{Context, ErrorKind, Fallible};
6+
use crate::error::{ErrorKind, Fallible};
97
use crate::hook::ToolHooks;
108
use crate::session::Session;
11-
use crate::style::progress_spinner;
129
use crate::tool::Npm;
1310
use crate::version::{VersionSpec, VersionTag};
14-
use attohttpc::header::ACCEPT;
15-
use attohttpc::Response;
1611
use log::debug;
1712
use semver::{Version, VersionReq};
1813

@@ -41,16 +36,7 @@ fn fetch_npm_index(hooks: Option<&ToolHooks<Npm>>) -> Fallible<(String, PackageI
4136
_ => public_registry_index("npm"),
4237
};
4338

44-
let spinner = progress_spinner(format!("Fetching public registry: {}", url));
45-
let metadata: RawPackageMetadata = attohttpc::get(&url)
46-
.header(ACCEPT, NPM_ABBREVIATED_ACCEPT_HEADER)
47-
.send()
48-
.and_then(Response::error_for_status)
49-
.and_then(Response::json)
50-
.with_context(registry_fetch_error("npm", &url))?;
51-
52-
spinner.finish_and_clear();
53-
Ok((url, metadata.into()))
39+
fetch_public_index(url, "npm")
5440
}
5541

5642
fn resolve_tag(tag: &str, hooks: Option<&ToolHooks<Npm>>) -> Fallible<Version> {

crates/volta-core/src/tool/registry.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
use std::collections::HashMap;
22
use std::path::{Path, PathBuf};
33

4+
use super::registry_fetch_error;
45
use crate::error::{Context, ErrorKind, Fallible};
56
use crate::fs::read_dir_eager;
7+
use crate::style::progress_spinner;
68
use crate::version::{hashmap_version_serde, version_serde};
9+
use attohttpc::header::ACCEPT;
10+
use attohttpc::Response;
711
use cfg_if::cfg_if;
812
use semver::Version;
913
use serde::Deserialize;
@@ -30,6 +34,19 @@ cfg_if! {
3034
}
3135
}
3236

37+
pub fn fetch_public_index(url: String, name: &str) -> Fallible<(String, PackageIndex)> {
38+
let spinner = progress_spinner(format!("Fetching public registry: {}", url));
39+
let metadata: RawPackageMetadata = attohttpc::get(&url)
40+
.header(ACCEPT, NPM_ABBREVIATED_ACCEPT_HEADER)
41+
.send()
42+
.and_then(Response::error_for_status)
43+
.and_then(Response::json)
44+
.with_context(registry_fetch_error(name, &url))?;
45+
46+
spinner.finish_and_clear();
47+
Ok((url, metadata.into()))
48+
}
49+
3350
pub fn public_registry_package(package: &str, version: &str) -> String {
3451
format!(
3552
"{}/-/{}-{}.tgz",
@@ -39,6 +56,18 @@ pub fn public_registry_package(package: &str, version: &str) -> String {
3956
)
4057
}
4158

59+
// need package and filename for namespaced tools like @yarnpkg/cli-dist, which is located at
60+
// https://registry.npmjs.org/@yarnpkg/cli-dist/-/cli-dist-1.2.3.tgz
61+
pub fn scoped_public_registry_package(scope: &str, package: &str, version: &str) -> String {
62+
format!(
63+
"{}/{}/-/{}-{}.tgz",
64+
public_registry_index(scope),
65+
package,
66+
package,
67+
version
68+
)
69+
}
70+
4271
/// Figure out the unpacked package directory name dynamically
4372
///
4473
/// Packages typically extract to a "package" directory, but not always

crates/volta-core/src/tool/yarn/fetch.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
//! Provides fetcher for Yarn distributions
22
3+
use std::env;
34
use std::fs::File;
45
use std::path::Path;
56

67
use super::super::download_tool_error;
7-
use super::super::registry::{find_unpack_dir, public_registry_package};
8+
use super::super::registry::{
9+
find_unpack_dir, public_registry_package, scoped_public_registry_package,
10+
};
811
use crate::error::{Context, ErrorKind, Fallible};
9-
use crate::fs::{create_staging_dir, create_staging_file, rename};
12+
use crate::fs::{create_staging_dir, create_staging_file, rename, set_executable};
1013
use crate::hook::ToolHooks;
1114
use crate::layout::volta_home;
1215
use crate::style::{progress_bar, tool_version};
@@ -79,11 +82,14 @@ fn unpack_archive(archive: Box<dyn Archive>, version: &Version) -> Fallible<()>
7982
version: version_string.clone(),
8083
})?;
8184

85+
let unpack_dir = find_unpack_dir(temp.path())?;
86+
// "bin/yarn" is not executable in the @yarnpkg/cli-dist package
87+
ensure_bin_is_executable(&unpack_dir, "yarn")?;
88+
8289
let dest = volta_home()?.yarn_image_dir(&version_string);
8390
ensure_containing_dir_exists(&dest)
8491
.with_context(|| ErrorKind::ContainingDirError { path: dest.clone() })?;
8592

86-
let unpack_dir = find_unpack_dir(temp.path())?;
8793
rename(unpack_dir, &dest).with_context(|| ErrorKind::SetupToolImageError {
8894
tool: "Yarn".into(),
8995
version: version_string.clone(),
@@ -122,7 +128,17 @@ fn determine_remote_url(version: &Version, hooks: Option<&ToolHooks<Yarn>>) -> F
122128
let distro_file_name = Yarn::archive_filename(&version_str);
123129
hook.resolve(version, &distro_file_name)
124130
}
125-
_ => Ok(public_registry_package("yarn", &version_str)),
131+
_ => {
132+
if env::var_os("VOLTA_FEATURE_YARN_3").is_some() && version.major >= 2 {
133+
Ok(scoped_public_registry_package(
134+
"@yarnpkg",
135+
"cli-dist",
136+
&version_str,
137+
))
138+
} else {
139+
Ok(public_registry_package("yarn", &version_str))
140+
}
141+
}
126142
}
127143
}
128144

@@ -138,3 +154,8 @@ fn fetch_remote_distro(
138154
url,
139155
))
140156
}
157+
158+
fn ensure_bin_is_executable(unpack_dir: &Path, tool: &str) -> Fallible<()> {
159+
let exec_path = unpack_dir.join("bin").join(tool);
160+
set_executable(&exec_path).with_context(|| ErrorKind::SetToolExecutable { tool: tool.into() })
161+
}

crates/volta-core/src/tool/yarn/resolve.rs

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//! Provides resolution of Yarn requirements into specific versions
22
3+
use std::env;
4+
35
use super::super::registry::{
4-
public_registry_index, PackageDetails, PackageIndex, RawPackageMetadata,
5-
NPM_ABBREVIATED_ACCEPT_HEADER,
6+
fetch_public_index, public_registry_index, PackageDetails, PackageIndex,
67
};
78
use super::super::registry_fetch_error;
89
use super::metadata::{RawYarnIndex, YarnIndex};
@@ -12,7 +13,6 @@ use crate::session::Session;
1213
use crate::style::progress_spinner;
1314
use crate::tool::Yarn;
1415
use crate::version::{parse_version, VersionSpec, VersionTag};
15-
use attohttpc::header::ACCEPT;
1616
use attohttpc::Response;
1717
use log::debug;
1818
use semver::{Version, VersionReq};
@@ -69,23 +69,30 @@ fn resolve_semver(matching: VersionReq, hooks: Option<&ToolHooks<Yarn>>) -> Fall
6969
}
7070
}
7171

72-
fn fetch_yarn_index() -> Fallible<(String, PackageIndex)> {
73-
let url = public_registry_index("yarn");
74-
let spinner = progress_spinner(format!("Fetching public registry: {}", url));
75-
let metadata: RawPackageMetadata = attohttpc::get(&url)
76-
.header(ACCEPT, NPM_ABBREVIATED_ACCEPT_HEADER)
77-
.send()
78-
.and_then(Response::error_for_status)
79-
.and_then(Response::json)
80-
.with_context(registry_fetch_error("Yarn", &url))?;
81-
82-
spinner.finish_and_clear();
83-
Ok((url, metadata.into()))
72+
fn fetch_yarn_index(package: &str) -> Fallible<(String, PackageIndex)> {
73+
let url = public_registry_index(package);
74+
fetch_public_index(url, "Yarn")
8475
}
8576

8677
fn resolve_custom_tag(tag: String) -> Fallible<Version> {
87-
let (url, mut index) = fetch_yarn_index()?;
78+
if env::var_os("VOLTA_FEATURE_YARN_3").is_some() {
79+
// first try yarn2+, which uses "@yarnpkg/cli-dist" instead of "yarn"
80+
let (url, mut index) = fetch_yarn_index("@yarnpkg/cli-dist")?;
8881

82+
if let Some(version) = index.tags.remove(&tag) {
83+
debug!("Found yarn@{} matching tag '{}' from {}", version, tag, url);
84+
if version.major == 2 {
85+
return Err(ErrorKind::Yarn2NotSupported.into());
86+
}
87+
return Ok(version);
88+
}
89+
debug!(
90+
"Did not find yarn matching tag '{}' from @yarnpkg/cli-dist",
91+
tag
92+
);
93+
}
94+
95+
let (url, mut index) = fetch_yarn_index("yarn")?;
8996
match index.tags.remove(&tag) {
9097
Some(version) => {
9198
debug!("Found yarn@{} matching tag '{}' from {}", version, tag, url);
@@ -109,7 +116,40 @@ fn resolve_latest_legacy(url: String) -> Fallible<Version> {
109116
}
110117

111118
fn resolve_semver_from_registry(matching: VersionReq) -> Fallible<Version> {
112-
let (url, index) = fetch_yarn_index()?;
119+
if env::var_os("VOLTA_FEATURE_YARN_3").is_some() {
120+
// first try yarn2+, which uses "@yarnpkg/cli-dist" instead of "yarn"
121+
let (url, index) = fetch_yarn_index("@yarnpkg/cli-dist")?;
122+
let matching_entries: Vec<PackageDetails> = index
123+
.entries
124+
.into_iter()
125+
.filter(|PackageDetails { version, .. }| matching.matches(version))
126+
.collect();
127+
128+
if !matching_entries.is_empty() {
129+
let details_opt = matching_entries
130+
.iter()
131+
.find(|PackageDetails { version, .. }| version.major >= 3);
132+
133+
match details_opt {
134+
Some(details) => {
135+
debug!(
136+
"Found yarn@{} matching requirement '{}' from {}",
137+
details.version, matching, url
138+
);
139+
return Ok(details.version.clone());
140+
}
141+
None => {
142+
return Err(ErrorKind::Yarn2NotSupported.into());
143+
}
144+
}
145+
}
146+
debug!(
147+
"Did not find yarn matching requirement '{}' from {}",
148+
matching, url
149+
);
150+
}
151+
152+
let (url, index) = fetch_yarn_index("yarn")?;
113153

114154
let details_opt = index
115155
.entries
@@ -124,6 +164,7 @@ fn resolve_semver_from_registry(matching: VersionReq) -> Fallible<Version> {
124164
);
125165
Ok(details.version)
126166
}
167+
// at this point Yarn is not found in either registry
127168
None => Err(ErrorKind::YarnVersionNotFound {
128169
matching: matching.to_string(),
129170
}

tests/acceptance/corrupted_download.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::support::sandbox::{sandbox, DistroMetadata, NodeFixture, YarnFixture};
1+
use crate::support::sandbox::{sandbox, DistroMetadata, NodeFixture, Yarn1Fixture};
22
use hamcrest2::assert_that;
33
use hamcrest2::prelude::*;
44
use semver::Version;
@@ -25,7 +25,7 @@ const NODE_VERSION_FIXTURES: [DistroMetadata; 2] = [
2525
},
2626
];
2727

28-
const YARN_VERSION_INFO: &str = r#"{
28+
const YARN_1_VERSION_INFO: &str = r#"{
2929
"name":"yarn",
3030
"dist-tags": { "latest": "1.2.42" },
3131
"versions": {
@@ -34,7 +34,7 @@ const YARN_VERSION_INFO: &str = r#"{
3434
}
3535
}"#;
3636

37-
const YARN_VERSION_FIXTURES: [DistroMetadata; 2] = [
37+
const YARN_1_VERSION_FIXTURES: [DistroMetadata; 2] = [
3838
DistroMetadata {
3939
version: "0.0.1",
4040
compressed_size: 10,
@@ -81,9 +81,9 @@ fn install_valid_node_saves_to_inventory() {
8181
fn install_corrupted_yarn_leaves_inventory_unchanged() {
8282
let s = sandbox()
8383
.node_available_versions(NODE_VERSION_INFO)
84-
.yarn_available_versions(YARN_VERSION_INFO)
84+
.yarn_1_available_versions(YARN_1_VERSION_INFO)
8585
.distro_mocks::<NodeFixture>(&NODE_VERSION_FIXTURES)
86-
.distro_mocks::<YarnFixture>(&YARN_VERSION_FIXTURES)
86+
.distro_mocks::<Yarn1Fixture>(&YARN_1_VERSION_FIXTURES)
8787
.build();
8888

8989
assert_that!(
@@ -99,9 +99,9 @@ fn install_valid_yarn_saves_to_inventory() {
9999
let s = sandbox()
100100
.platform(r#"{ "node": { "runtime": "1.2.3", "npm": null }, "yarn": null }"#)
101101
.node_available_versions(NODE_VERSION_INFO)
102-
.yarn_available_versions(YARN_VERSION_INFO)
102+
.yarn_1_available_versions(YARN_1_VERSION_INFO)
103103
.distro_mocks::<NodeFixture>(&NODE_VERSION_FIXTURES)
104-
.distro_mocks::<YarnFixture>(&YARN_VERSION_FIXTURES)
104+
.distro_mocks::<Yarn1Fixture>(&YARN_1_VERSION_FIXTURES)
105105
.build();
106106

107107
assert_that!(

0 commit comments

Comments
 (0)