Skip to content
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

fix(prune): fix Yarn1 entries getting merged erroneously #9627

Merged
merged 2 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions crates/turborepo-lockfiles/fixtures/gh_8849.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==

emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==

is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==

prettier@^3.2.5:
version "3.3.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105"
integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==

"string-width-cjs@npm:[email protected]":
version "4.2.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"

[email protected]:
version "4.2.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"

strip-ansi@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

[email protected]:
version "2.0.9"
resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-2.0.9.tgz#dc7bb92060a41b92155195dba5850c9669fa765a"
integrity sha512-owlGsOaExuVGBUfrnJwjkL1BWlvefjSKczEAcpLx4BI7Oh6ttakOi+JyomkPkFlYElRpjbvlR2gP8WIn6M/+xQ==

[email protected]:
version "2.0.9"
resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-2.0.9.tgz#6e5ce2c0f03999c6ec0116d5525841107da3078b"
integrity sha512-XAXkKkePth5ZPPE/9G9tTnPQx0C8UTkGWmNGYkpmGgRr8NedW+HrPsi9N0HcjzzIH9A4TpNYvtiV+WcwdaEjKA==

[email protected]:
version "2.0.9"
resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-2.0.9.tgz#e00e5e1b1cffab23c58888e7c397e108dc24fe2f"
integrity sha512-l9wSgEjrCFM1aG16zItBsZ206ZlhSSx1owB8Cgskfv0XyIXRGHRkluihiaxkp+UeU5WoEfz4EN5toc+ICA0q0w==

[email protected]:
version "2.0.9"
resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-2.0.9.tgz#d240e4f0a784d03f1a79fd9e6c4e83abd9aa57c7"
integrity sha512-gRnjxXRne18B27SwxXMqL3fJu7jw/8kBrOBTBNRSmZZiG1Uu3nbnP7b4lgrA/bCku6C0Wligwqurvtpq6+nFHA==

[email protected]:
version "2.0.9"
resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-2.0.9.tgz#d52835302e722cc7de670b90aca55ce2b3516879"
integrity sha512-ZVo0apxUvaRq4Vm1qhsfqKKhtRgReYlBVf9MQvVU1O9AoyydEQvLDO1ryqpXDZWpcHoFxHAQc9msjAMtE5K2lA==

[email protected]:
version "2.0.9"
resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-2.0.9.tgz#45f0aa685514ec1cc753a559924e003b22b24bb7"
integrity sha512-sGRz7c5Pey6y7y9OKi8ypbWNuIRPF9y8xcMqL56OZifSUSo+X2EOsOleR9MKxQXVaqHPGOUKWsE6y8hxBi9pag==

turbo@^2.0.9:
version "2.0.9"
resolved "https://registry.yarnpkg.com/turbo/-/turbo-2.0.9.tgz#fa0ab576c4cb9a8fc9db648e9ac9adfe10a22ae5"
integrity sha512-QaLaUL1CqblSKKPgLrFW3lZWkWG4pGBQNW+q1ScJB5v1D/nFWtsrD/yZljW/bdawg90ihi4/ftQJ3h6fz1FamA==
optionalDependencies:
turbo-darwin-64 "2.0.9"
turbo-darwin-arm64 "2.0.9"
turbo-linux-64 "2.0.9"
turbo-linux-arm64 "2.0.9"
turbo-windows-64 "2.0.9"
turbo-windows-arm64 "2.0.9"

typescript@^5.4.5:
version "5.5.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
2 changes: 2 additions & 0 deletions crates/turborepo-lockfiles/src/yarn1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,11 @@ mod test {

const MINIMAL: &str = include_str!("../../fixtures/yarn1.lock");
const FULL: &str = include_str!("../../fixtures/yarn1full.lock");
const GH_8849: &str = include_str!("../../fixtures/gh_8849.lock");

#[test_case(MINIMAL ; "minimal lockfile")]
#[test_case(FULL ; "full lockfile")]
#[test_case(GH_8849 ; "gh 8849")]
fn test_roundtrip(input: &str) {
let lockfile = Yarn1Lockfile::from_str(input).unwrap();
assert_eq!(input, lockfile.to_string());
Expand Down
103 changes: 90 additions & 13 deletions crates/turborepo-lockfiles/src/yarn1/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,32 @@ use super::{Entry, Yarn1Lockfile};

const INDENT: &str = " ";

fn reverse_seen_keys<'a>(
seen_keys: &'a HashMap<&'a str, String>,
) -> HashMap<&'a str, HashSet<&'a str>> {
let mut reverse_lookup = HashMap::new();
for (key, value) in seen_keys.iter() {
let keys: &mut HashSet<&str> = reverse_lookup.entry(value.as_str()).or_default();
keys.insert(key);
}
reverse_lookup
}

impl Yarn1Lockfile {
fn reverse_lookup(&self) -> HashMap<&Entry, HashSet<&str>> {
let mut reverse_lookup = HashMap::new();
for (key, value) in self.inner.iter() {
let keys: &mut HashSet<&str> = reverse_lookup.entry(value).or_default();
keys.insert(key);
// Map from keys to seen keys
// A "seen key" just entry.resolved with the key's package name appended to it
// See https://github.com/yarnpkg/yarn/pull/9023/
fn seen_keys(&self) -> HashMap<&str, String> {
let mut seen_keys = HashMap::new();
for (key, entry) in &self.inner {
let Some(resolved) = entry.resolved.as_deref() else {
continue;
};
let pkg_name = Pattern::new(key).name;
let seen_key = format!("{resolved}#{pkg_name}");
seen_keys.insert(key.as_str(), seen_key);
}
reverse_lookup
seen_keys
}
}

Expand All @@ -25,18 +43,31 @@ impl fmt::Display for Yarn1Lockfile {
"# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n# yarn lockfile \
v1\n\n",
)?;
let reverse_lookup = self.reverse_lookup();
let seen_keys = self.seen_keys();
// A map from seen_keys to keys
let reverse_lookup = reverse_seen_keys(&seen_keys);
let mut added_keys: HashSet<&str> = HashSet::with_capacity(self.inner.len());
for (key, entry) in self.inner.iter() {
if added_keys.contains(key.as_str()) {
let seen_key = seen_keys.get(key.as_str());
let seen_pattern = seen_key.map_or(false, |key| added_keys.contains(key.as_str()));
if seen_pattern {
continue;
}

let all_keys = reverse_lookup
.get(entry)
.expect("entry in lockfile should appear as a key in reverse lookup");
added_keys.extend(all_keys);
let mut keys = all_keys.iter().copied().collect::<Vec<_>>();
let mut keys = match seen_key {
Some(seen_key) => {
added_keys.insert(seen_key);
let all_keys = reverse_lookup
.get(seen_key.as_str())
.expect("entry in lockfile should appear as a key in reverse lookup");
all_keys.iter().copied().collect::<Vec<_>>()
}
None => {
// If there isn't a seen key, then there won't be any merged entries so we can
// just add the key as is
vec![key.as_str()]
}
};
// Keys must be sorted before they get wrapped
keys.sort();

Expand Down Expand Up @@ -111,6 +142,52 @@ impl fmt::Display for Entry {
}
}

#[allow(dead_code)]
struct Pattern {
name: String,
range: String,
has_version: bool,
}

impl Pattern {
// This is an exact port of JS code. It is intentionally keeps JS-isms to make
// patching easier in the future https://github.com/yarnpkg/yarn/blob/3c3ef8278121c0598c61caf8023d9bb2af888152/src/util/normalize-pattern.js
fn new(pattern: &str) -> Self {
let mut name = pattern;
let mut range = "latest".to_owned();
let mut has_version = false;
let mut is_scoped = false;
if name.starts_with('@') {
is_scoped = true;
name = &name[1..];
}

let mut parts: Vec<_> = name.split('@').collect();
if parts.len() > 1 {
name = parts.remove(0);
range = parts.join("@");

if !range.is_empty() {
has_version = true;
} else {
range = "*".to_owned();
}
}

let name = if is_scoped {
format!("@{name}")
} else {
name.to_owned()
};

Self {
name,
range,
has_version,
}
}
}

#[derive(Debug, Clone, Copy)]
enum LeadingNewline {
First,
Expand Down
Loading