Skip to content
Open
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
2 changes: 1 addition & 1 deletion crates/socket-patch-cli/CLI_CONTRACT.md

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions crates/socket-patch-cli/src/commands/scan/hosted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1728,8 +1728,8 @@ async fn vendored_takeover(
None
};
// Yarn berry twin of the bun gate: the berry rewriter's project-level
// refusals (mixed line endings, cacheKey, `.yarnrc.yml`
// compressionLevel) must be known before the takeover reverts a
// refusals (mixed yarn.lock / package.json line endings, cacheKey,
// `.yarnrc.yml` compressionLevel) must be known before the takeover reverts a
// vendored berry purl, or the revert strips the live vendored patch
// and the rewriter then refuses the lock. Only entries the
// vendor ledger wired through the yarn-berry backend are gated (the
Expand All @@ -1751,8 +1751,14 @@ async fn vendored_takeover(
)
.await
.ok();
let manifest = socket_patch_core::utils::fs::read_regular_to_string(
&common.cwd.join("package.json"),
)
.await
.ok();
socket_patch_core::patch::redirect::preflight_yarn_berry_hosted(
&lock,
manifest.as_deref(),
yarnrc.as_deref(),
)
.err()
Expand Down
46 changes: 46 additions & 0 deletions crates/socket-patch-cli/tests/in_process_redirect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,52 @@ async fn scan_redirect_refuses_a_mixed_line_ending_yarn_berry_lock() {
);
}

/// #628: the root `package.json` of a berry project is a file the hosted
/// rewrite edits (its `resolutions`), so a manifest mixing CRLF and LF is
/// refused like a mixed lock — the same decision vendored mode takes with
/// `vendor_yarn_berry_mixed_line_endings` — instead of being re-rendered in
/// its majority ending, which rewrote lines the user never touched and
/// left rollback no original bytes to restore. Nothing is written.
#[tokio::test]
#[serial]
async fn scan_redirect_refuses_a_mixed_line_ending_yarn_berry_manifest() {
let server = MockServer::start().await;
mock_discovery(&server).await;
mock_reference_with_berry(&server).await;
mock_view(&server).await;

let tmp = tempfile::tempdir().unwrap();
write_berry_project_spelled(tmp.path(), |t| t.to_string());
let pkg_path = tmp.path().join("package.json");
std::fs::write(
&pkg_path,
format!(
"{{\r\n \"name\": \"consumer\",\n \"version\": \"0.0.0\",\r\n \
\"dependencies\": {{ \"{NAME}\": \"^{VERSION}\" }}\r\n}}\r\n"
),
)
.unwrap();
let lock_path = tmp.path().join("yarn.lock");
let (pkg_before, lock_before) = (
std::fs::read(&pkg_path).unwrap(),
std::fs::read(&lock_path).unwrap(),
);

let env = run_redirect_subprocess(tmp.path(), &server.uri());
assert_eq!(env["redirect"]["redirected"], 0, "{env:#}");
let detail = redirect_warning_detail(&env, "redirect_yarn_berry_mixed_line_endings");
assert!(detail.contains("package.json"), "names the file: {detail}");
assert!(detail.contains("yarn install"), "remedy named: {detail}");
assert_eq!(std::fs::read(&pkg_path).unwrap(), pkg_before, "untouched");
assert_eq!(std::fs::read(&lock_path).unwrap(), lock_before, "untouched");
assert!(
!tmp.path()
.join(".socket/vendor/redirect-state.json")
.exists(),
"no ledger for a refused rewrite"
);
}

/// Classic (v1) yarn.lock with CRLF line endings (Windows `core.autocrlf`
/// checkout): the full hosted chain must repoint the TARGET entry — not
/// whichever entry sorts first — and keep every untouched line CRLF
Expand Down
9 changes: 9 additions & 0 deletions crates/socket-patch-cli/tests/in_process_vendor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1554,6 +1554,15 @@ async fn berry_takeovers_refuse_before_reverting_the_old_mode() {
"yarn.lock",
"redirect_yarn_berry_mixed_line_endings",
),
// #628: hosted mode re-renders the root manifest (its
// `resolutions`), so a mixed one is refused before the revert, the
// same decision the hosted→vendored leg below takes.
(
"mixed package.json",
mix,
"package.json",
"redirect_yarn_berry_mixed_line_endings",
),
(
"compressionLevel",
compression,
Expand Down
9 changes: 4 additions & 5 deletions crates/socket-patch-core/src/crawlers/gradle_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ pub fn hash_eq(dir_name: &str, sha1_hex: &str) -> bool {
/// Whether `bytes` are the pristine download Gradle stored in the hash
/// directory `dir_name` (their sha1 names it).
pub fn pristine(dir_name: &str, bytes: &[u8]) -> bool {
use sha1::{Digest, Sha1};
hash_eq(dir_name, &hex::encode(Sha1::digest(bytes)))
hash_eq(dir_name, &crate::utils::digest::sha1_hex_of(bytes))
}

/// Whether `path` is a version directory of a `files-2.1` tree
Expand Down Expand Up @@ -432,8 +431,6 @@ impl DerivedIndex {
/// The [`DerivedCopies`] of the jar `jar_leaf` whose pristine bytes
/// hash to `pristine_sha1`.
pub fn query(&self, jar_leaf: &str, pristine_sha1: &str) -> DerivedCopies {
use sha1::{Digest, Sha1};

let instrumented = format!("instrumented-{jar_leaf}");
let mut out = DerivedCopies {
incomplete: self.incomplete,
Expand All @@ -460,7 +457,9 @@ impl DerivedIndex {
out.stale.push(path.clone());
} else if name == jar_leaf || name == instrumented {
match crate::utils::fs::read_regular_to_bytes_sync(path) {
Ok(bytes) if hash_eq(&hex::encode(Sha1::digest(&bytes)), pristine_sha1) => {
Ok(bytes)
if hash_eq(&crate::utils::digest::sha1_hex_of(&bytes), pristine_sha1) =>
{
out.stale.push(path.clone())
}
Ok(_) => out.unknown.push(path.clone()),
Expand Down
Loading
Loading