Skip to content
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
22 changes: 18 additions & 4 deletions crates/socket-patch-cli/tests/e2e_vendor_yarn_berry_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,20 @@ fn copy_dir_recursive(src: &Path, dst: &Path) {

#[tokio::test(flavor = "multi_thread")]
async fn yarn_berry_vendor_fresh_checkout_immutable_check_cache_and_revert() {
run_berry_capstone(VendorDriver::VendorCli).await;
run_berry_capstone(VendorDriver::VendorCli, "").await;
}

/// #370: `compressionLevel: 0` with a trailing YAML comment is the default
/// to yarn (cacheKey `10c0`), so vendor must not refuse it as a
/// checksum-changing level. The whole capstone runs against that
/// `.yarnrc.yml`, fresh-checkout `--immutable` proof included.
#[tokio::test(flavor = "multi_thread")]
async fn yarn_berry_vendor_commented_default_compression_level() {
run_berry_capstone(
VendorDriver::VendorCli,
"compressionLevel: 0 # keep yarn default\n",
)
.await;
}

/// get-driven twin (v3.6): `get <uuid> --mode vendored` consumes the SAME
Expand All @@ -277,10 +290,11 @@ async fn yarn_berry_vendor_fresh_checkout_immutable_check_cache_and_revert() {
/// the `vendor` front door's contract (the capstone above).
#[tokio::test(flavor = "multi_thread")]
async fn berry_get_uuid_vendored_fresh_checkout_immutable() {
run_berry_capstone(VendorDriver::GetUuid).await;
run_berry_capstone(VendorDriver::GetUuid, "").await;
}

async fn run_berry_capstone(driver: VendorDriver) {
/// `yarnrc_extra` is appended to the capstone's `.yarnrc.yml`.
async fn run_berry_capstone(driver: VendorDriver, yarnrc_extra: &str) {
if !has_corepack_pm(yarn_berry()) {
skip!(
"SKIP e2e_vendor_yarn_berry_build ({driver:?}): `corepack {}` unavailable \
Expand All @@ -304,7 +318,7 @@ async fn run_berry_capstone(driver: VendorDriver) {
// (the only checksum recipe vendor reproduces offline — spike B4).
std::fs::write(
proj.join(".yarnrc.yml"),
"nodeLinker: node-modules\nenableGlobalCache: false\n",
format!("nodeLinker: node-modules\nenableGlobalCache: false\n{yarnrc_extra}"),
)
.unwrap();

Expand Down
9 changes: 9 additions & 0 deletions crates/socket-patch-core/src/patch/redirect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14450,6 +14450,15 @@ packages:
Err("redirect_yarn_berry_cache_unsupported".to_string())
);
assert_eq!(code(&crlf, Some("compressionLevel: 0\n")), Ok(()));
// #370: a trailing YAML comment is not part of the value.
assert_eq!(
code(&crlf, Some("compressionLevel: 0 # keep yarn default\n")),
Ok(())
);
assert_eq!(
code(&crlf, Some("compressionLevel: mixed # smaller cache\n")),
Err("redirect_yarn_berry_cache_unsupported".to_string())
);
}

/// The whole-file gates read the NORMALIZED lock: a CRLF lock at an
Expand Down
64 changes: 62 additions & 2 deletions crates/socket-patch-core/src/vendor/yarn_berry_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1348,11 +1348,25 @@ fn root_workspace_name(blocks: &[LockBlock]) -> Option<String> {
/// skipped the way yarn's YAML parser skips it — otherwise a knob on the
/// first line of a BOM'd file would read as unset (the offline-reproducible
/// default) while yarn applies it and every install fails YN0018.
///
/// The value is read as a YAML scalar: a quoted value ends at its closing
/// quote, and a plain value ends before a whitespace-separated `#` comment
/// (`compressionLevel: 0 # keep yarn default` is `0`, #370). A `#` with no
/// whitespace before it stays part of a plain value, as in YAML.
pub(crate) fn yarnrc_compression_level(rc: &str) -> Option<&str> {
let rc = rc.strip_prefix('\u{feff}').unwrap_or(rc);
rc.lines().find_map(|line| {
let rest = line.strip_prefix("compressionLevel:")?;
Some(rest.trim().trim_matches(['\'', '"']))
let rest = line.strip_prefix("compressionLevel:")?.trim();
if let Some(quote) = rest.chars().next().filter(|c| matches!(c, '\'' | '"')) {
if let Some(end) = rest[1..].find(quote) {
return Some(&rest[1..1 + end]);
}
}
let value = rest
.char_indices()
.find(|&(i, c)| c == '#' && rest[..i].ends_with([' ', '\t']))
.map_or(rest, |(i, _)| &rest[..i]);
Some(value.trim_end().trim_matches(['\'', '"']))
})
}

Expand Down Expand Up @@ -1858,6 +1872,21 @@ __metadata:
assert!(result.success, "{:?}", result.error);
}

/// #370: `compressionLevel: 0` with a trailing YAML comment is the
/// default yarn reads as `0`, so vendoring proceeds rather than refusing.
#[tokio::test]
async fn commented_default_compression_level_vendors() {
let fx = fixture().await;
tokio::fs::write(
fx.root().join(YARNRC),
"nodeLinker: node-modules\ncompressionLevel: 0 # keep yarn default\n",
)
.await
.unwrap();
let (result, _, _) = expect_done(fx.vendor(false).await);
assert!(result.success, "{:?}", result.error);
}

#[tokio::test]
async fn user_resolutions_entry_is_refused_never_overwritten() {
let pkg = B3_BEFORE_PKG.replace(
Expand Down Expand Up @@ -3963,6 +3992,37 @@ __metadata:
);
}

/// A trailing YAML comment is not part of the scalar (#370): yarn reads
/// `compressionLevel: 0 # keep yarn default` as `0`, quoted or not.
#[test]
fn yarnrc_compression_level_drops_a_trailing_comment() {
for (rc, level) in [
("compressionLevel: 0 # keep yarn default\n", "0"),
("compressionLevel: 0\t# tab-separated\r\n", "0"),
("compressionLevel: 0 #\n", "0"),
("compressionLevel: \"0\" # quoted\n", "0"),
("compressionLevel: '0'# quoted, no gap\n", "0"),
("compressionLevel: mixed # not the default\n", "mixed"),
("compressionLevel: 9 #\r\n", "9"),
] {
assert_eq!(yarnrc_compression_level(rc), Some(level), "{rc:?}");
}
}

/// A `#` with no whitespace before it is part of a plain scalar in YAML,
/// so `0#x` is not the default and must still refuse (fail closed).
#[test]
fn yarnrc_compression_level_keeps_an_unseparated_hash() {
assert_eq!(
yarnrc_compression_level("compressionLevel: 0#x\n"),
Some("0#x")
);
assert_eq!(
yarnrc_compression_level("compressionLevel: \"0 # in quotes\"\n"),
Some("0 # in quotes")
);
}

/// yarn 4.0.x spells `10c0` checksums bare, 4.1+ prefixed: a written
/// entry follows the lock (an `--immutable` install rejects a respelled
/// checksum with YN0028). A lock with no checksum keeps the prefix.
Expand Down
Loading