Skip to content

Commit f315a46

Browse files
committed
Fix workspace GVS lookup for relative node_modules paths
Canonicalize nm path before walking ancestors in enclosing_pnpm_modules_yaml_sync to handle relative paths like 'node_modules'. Without canonicalization, relative paths have parent '.' with only one ancestor, so skip(1) yields nothing and the workspace root's .modules.yaml is never found, causing workspace members to miss transitive GVS copies.
1 parent 7f952bd commit f315a46

66 files changed

Lines changed: 1080 additions & 429 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎crates/socket-patch-core/src/api/ranking.rs‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,14 @@ pub fn batch_supersedes(candidate: &BatchPatchInfo, applied: &BatchPatchInfo) ->
164164
/// classify a recorded patch (ALREADY vs UPGRADE) and to report
165165
/// `updates[]`, on the same records that pick the patch, so selection,
166166
/// classification and reporting cannot disagree.
167-
pub fn search_result_supersedes(candidate: &PatchSearchResult, recorded: &PatchSearchResult) -> bool {
168-
key_supersedes(&rank_search_result(candidate), &rank_search_result(recorded))
167+
pub fn search_result_supersedes(
168+
candidate: &PatchSearchResult,
169+
recorded: &PatchSearchResult,
170+
) -> bool {
171+
key_supersedes(
172+
&rank_search_result(candidate),
173+
&rank_search_result(recorded),
174+
)
169175
}
170176

171177
fn key_supersedes(c: &RankKey<'_>, a: &RankKey<'_>) -> bool {
@@ -371,12 +377,7 @@ mod tests {
371377
"2020-01-01T00:00:00Z",
372378
&["critical", "high"]
373379
),
374-
search_multi(
375-
"z_new_low",
376-
"free",
377-
"2026-08-01T00:00:00Z",
378-
&["low", "low"]
379-
),
380+
search_multi("z_new_low", "free", "2026-08-01T00:00:00Z", &["low", "low"]),
380381
]),
381382
"a_old_critical"
382383
);

‎crates/socket-patch-core/src/crawlers/npm_crawler.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,7 @@ fn pnpm_global_virtual_store_of_sync(nm: &Path) -> Option<PathBuf> {
769769
/// The nearest `<ancestor>/node_modules/.modules.yaml` above the importer
770770
/// holding `nm` (a pnpm workspace root's record, for a member).
771771
fn enclosing_pnpm_modules_yaml_sync(nm: &Path) -> Option<PathBuf> {
772+
let nm = std::fs::canonicalize(nm).ok()?;
772773
nm.parent()?.ancestors().skip(1).find_map(|dir| {
773774
let candidate = dir.join("node_modules").join(PNPM_MODULES_YAML);
774775
std::fs::symlink_metadata(&candidate)

‎crates/socket-patch-core/src/crawlers/python_crawler.rs‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -590,9 +590,7 @@ async fn pdm_saved_interpreter(cwd: &Path) -> Option<PathBuf> {
590590
let saved = match read_regular_to_string(&cwd.join(".pdm-python")).await {
591591
Ok(text) => text.trim().to_string(),
592592
Err(_) => {
593-
let text = read_regular_to_string(&cwd.join(".pdm.toml"))
594-
.await
595-
.ok()?;
593+
let text = read_regular_to_string(&cwd.join(".pdm.toml")).await.ok()?;
596594
let doc = text.parse::<toml_edit::DocumentMut>().ok()?;
597595
doc.get("python")?.get("path")?.as_str()?.trim().to_string()
598596
}
@@ -3215,7 +3213,11 @@ mod tests {
32153213
fake_venv(&tmp.path().join("uv-env"), "venv");
32163214
let uv_env = env_of(&[(
32173215
"UV_PROJECT_ENVIRONMENT",
3218-
tmp.path().join("uv-env").join("venv").to_string_lossy().into_owned(),
3216+
tmp.path()
3217+
.join("uv-env")
3218+
.join("venv")
3219+
.to_string_lossy()
3220+
.into_owned(),
32193221
)]);
32203222
assert_eq!(
32213223
find_local_venv_site_packages_with(&project, &uv_env).await,

‎crates/socket-patch-core/src/formats/cargo/mod.rs‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ use crate::utils::purl::simple_purl;
3434
use crate::vendor::cargo_tag;
3535
use crate::vendor::lock_inventory::{LockIntegrity, LockfileEntry, SourceKind};
3636

37-
3837
// ── entry model ──
3938

4039
/// The `[metadata]` key a v1 lock files `name`+`version`'s checksum under.
@@ -332,7 +331,6 @@ pub(crate) fn parse_ref(spelled: &str) -> (&str, Option<&str>, Option<&str>) {
332331
(name, version, source)
333332
}
334333

335-
336334
// ── the model ──
337335

338336
/// One `Cargo.lock`, parsed once (see the module docs).
@@ -500,7 +498,13 @@ impl CargoLock {
500498
uuid: &str,
501499
copy_tagged: bool,
502500
) -> CopyClaim<'_> {
503-
vendored_copy_claim(&self.packages, &self.unused, name, version, uuid, copy_tagged)
501+
vendored_copy_claim(
502+
&self.packages,
503+
&self.unused,
504+
name,
505+
version,
506+
uuid,
507+
copy_tagged,
508+
)
504509
}
505510
}
506-

‎crates/socket-patch-core/src/formats/composer/mod.rs‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ use crate::utils::digest::sha1_hex;
2222
use crate::vendor::lock_inventory::{http_url, LockIntegrity, LockfileEntry, SourceKind};
2323
use crate::vendor::path::{parse_vendor_path, VendorPathParts};
2424

25-
2625
// ── entry model ──
2726

2827
/// One entry of a parsed `composer.lock` (see [`composer_lock_packages`]).
@@ -107,7 +106,6 @@ pub(crate) fn composer_lock_packages(doc: &Value) -> Vec<ComposerLockPackage<'_>
107106
out
108107
}
109108

110-
111109
// ── the model ──
112110

113111
/// One `composer.lock`, read once (see the module docs).
@@ -177,4 +175,3 @@ impl<'a> ComposerLock<'a> {
177175
out
178176
}
179177
}
180-

‎crates/socket-patch-core/src/formats/gem/hosted.rs‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,4 +304,3 @@ pub(crate) fn checksum_entry_span(lock: &str, name: &str, version: &str) -> Opti
304304
}
305305
None
306306
}
307-

‎crates/socket-patch-core/src/formats/gem/mod.rs‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ use crate::utils::digest::sha256_hex;
2727
use crate::utils::purl::simple_purl;
2828
use crate::vendor::lock_inventory::{http_url, LockIntegrity, LockfileEntry, SourceKind};
2929

30-
3130
/// The Bundler lockfiles, legacy spelling first: `Gemfile.lock` and
3231
/// `gems.locked` (what bundler writes instead when the manifest is
3332
/// `gems.rb`).
@@ -208,7 +207,6 @@ impl<'t> GemfileLock<'t> {
208207
}
209208
}
210209

211-
212210
/// Where a rubygems-compatible registry at `base` (no trailing `/`) serves
213211
/// `name`-`version`'s `.gem` — the inventory's resolved URL and ledger
214212
/// recovery's fetch URL. `None` for a non-http(s) base.

‎crates/socket-patch-core/src/formats/mod.rs‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@
2626
//! [`registry()`] is the one table of which project files carry a lock or
2727
//! its wiring, and in which roles.
2828
29+
pub(crate) mod bun;
2930
pub mod cargo;
3031
pub mod composer;
3132
pub mod gem;
3233
pub(crate) mod maven;
3334
pub(crate) mod nuget;
3435
pub mod pnpm;
35-
pub(crate) mod bun;
3636
pub mod registry;
3737
pub mod yarn;
3838

@@ -81,7 +81,11 @@ mod architecture_tests {
8181
.filter(|l| !l.trim_start().starts_with("//"))
8282
.collect::<Vec<_>>()
8383
.join("\n");
84-
let used: Vec<&str> = IMPURE.iter().copied().filter(|n| code.contains(n)).collect();
84+
let used: Vec<&str> = IMPURE
85+
.iter()
86+
.copied()
87+
.filter(|n| code.contains(n))
88+
.collect();
8589
assert!(
8690
used.is_empty(),
8791
"{}: a format model uses {used:?} — models are pure (module docs)",

‎crates/socket-patch-core/src/formats/pnpm/mod.rs‎

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ use crate::utils::digest::is_sri_pin;
3939
use crate::vendor::lock_inventory::{http_url, LockIntegrity, LockfileEntry};
4040
use crate::vendor::path::parse_vendor_path;
4141

42-
4342
// ── entry model ──
4443

4544
/// One `packages:` entry of a pnpm lock, read with the entry grammar
@@ -283,7 +282,10 @@ fn lock_versions(text: &str) -> impl Iterator<Item = (Option<u32>, u32)> + '_ {
283282
let value = rest.trim().trim_matches(|c| c == '\'' || c == '"');
284283
let mut parts = value.split('.');
285284
let major = parts.next().and_then(|m| m.parse::<u32>().ok());
286-
let minor = parts.next().and_then(|m| m.parse::<u32>().ok()).unwrap_or(0);
285+
let minor = parts
286+
.next()
287+
.and_then(|m| m.parse::<u32>().ok())
288+
.unwrap_or(0);
287289
Some((major, minor))
288290
})
289291
}
@@ -303,7 +305,8 @@ pub fn lock_version_major(text: &str) -> Option<u32> {
303305
/// rejects it): a `shrinkwrapVersion` lock (pnpm 1–2) or lockfileVersion
304306
/// 5.0–5.2 (pnpm 3–5). Later locks never get the `--store` note.
305307
pub fn may_need_store_flag(text: &str) -> bool {
306-
text.lines().any(|line| line.starts_with("shrinkwrapVersion:"))
308+
text.lines()
309+
.any(|line| line.starts_with("shrinkwrapVersion:"))
307310
|| lock_versions(text).any(|(major, minor)| major == Some(5) && minor <= 2)
308311
}
309312

@@ -491,7 +494,9 @@ pub(crate) fn vendored_npm_uuids(text: &str) -> HashSet<String> {
491494
if !in_section {
492495
continue;
493496
}
494-
if let Some(uuid) = lines::parse_key_line(line, 2).and_then(|(key, _, _)| vendored_npm_uuid(key)) {
497+
if let Some(uuid) =
498+
lines::parse_key_line(line, 2).and_then(|(key, _, _)| vendored_npm_uuid(key))
499+
{
495500
out.insert(uuid);
496501
}
497502
}
@@ -508,17 +513,52 @@ mod tests {
508513
fn resolves_reads_every_key_generation_boundary_anchored() {
509514
let lock = |keys: &str| format!("lockfileVersion: '9.0'\n\npackages:\n\n{keys}");
510515
let yes = [
511-
(" left-pad@1.3.0:\n resolution: {integrity: sha512-x}\n", "left-pad", "1.3.0"),
512-
(" /left-pad@1.3.0:\n resolution: {}\n", "left-pad", "1.3.0"),
513-
(" /left-pad/1.3.0:\n resolution: {}\n", "left-pad", "1.3.0"),
514-
(" 'left-pad@1.3.0(react@18.0.0)':\n dev: false\n", "left-pad", "1.3.0"),
515-
(" /left-pad/1.3.0_react@18.0.0:\n dev: false\n", "left-pad", "1.3.0"),
516-
(" '@scope/name@1.0.0':\n dev: false\n", "@scope/name", "1.0.0"),
517-
(" /@scope/name@1.0.0:\n dev: false\n", "@scope/name", "1.0.0"),
518-
(" /@scope/name/1.0.0:\n dev: false\n", "@scope/name", "1.0.0"),
516+
(
517+
" left-pad@1.3.0:\n resolution: {integrity: sha512-x}\n",
518+
"left-pad",
519+
"1.3.0",
520+
),
521+
(
522+
" /left-pad@1.3.0:\n resolution: {}\n",
523+
"left-pad",
524+
"1.3.0",
525+
),
526+
(
527+
" /left-pad/1.3.0:\n resolution: {}\n",
528+
"left-pad",
529+
"1.3.0",
530+
),
531+
(
532+
" 'left-pad@1.3.0(react@18.0.0)':\n dev: false\n",
533+
"left-pad",
534+
"1.3.0",
535+
),
536+
(
537+
" /left-pad/1.3.0_react@18.0.0:\n dev: false\n",
538+
"left-pad",
539+
"1.3.0",
540+
),
541+
(
542+
" '@scope/name@1.0.0':\n dev: false\n",
543+
"@scope/name",
544+
"1.0.0",
545+
),
546+
(
547+
" /@scope/name@1.0.0:\n dev: false\n",
548+
"@scope/name",
549+
"1.0.0",
550+
),
551+
(
552+
" /@scope/name/1.0.0:\n dev: false\n",
553+
"@scope/name",
554+
"1.0.0",
555+
),
519556
];
520557
for (keys, name, version) in yes {
521-
assert!(PnpmLock::parse(&lock(keys)).resolves(name, version), "{keys}");
558+
assert!(
559+
PnpmLock::parse(&lock(keys)).resolves(name, version),
560+
"{keys}"
561+
);
522562
}
523563
let no = [
524564
(" left-pad@1.3.0-beta.1:\n dev: false\n", "left-pad", "1.3.0"),
@@ -534,7 +574,10 @@ mod tests {
534574
),
535575
];
536576
for (keys, name, version) in no {
537-
assert!(!PnpmLock::parse(&lock(keys)).resolves(name, version), "{keys}");
577+
assert!(
578+
!PnpmLock::parse(&lock(keys)).resolves(name, version),
579+
"{keys}"
580+
);
538581
}
539582
// Keys outside `packages:` (importers, overrides) resolve nothing.
540583
let importers = "lockfileVersion: '9.0'\n\nimporters:\n\n left-pad@1.3.0:\n x: y\n";
@@ -557,7 +600,10 @@ mod tests {
557600
let other = "22222222-2222-4222-8222-222222222222";
558601
assert!(!PnpmLock::parse(text).vendored_in_use(other));
559602
let crlf = text.replace('\n', "\r\n");
560-
assert!(PnpmLock::parse(&crlf).vendored_in_use(UUID), "CRLF reads like LF");
603+
assert!(
604+
PnpmLock::parse(&crlf).vendored_in_use(UUID),
605+
"CRLF reads like LF"
606+
);
561607
}
562608
// An overrides declaration alone is not usage.
563609
let overrides = format!(

‎crates/socket-patch-core/src/formats/registry.rs‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ const fn row(path: &'static str, ecosystem: &'static str, roles: u8) -> FormatFi
6767
const REGISTRY: &[FormatFile] = &[
6868
// ── npm family ──
6969
row("package-lock.json", "npm", HOSTED | VENDORED | PROBE | ROOT),
70-
row("npm-shrinkwrap.json", "npm", HOSTED | VENDORED | PROBE | ROOT),
70+
row(
71+
"npm-shrinkwrap.json",
72+
"npm",
73+
HOSTED | VENDORED | PROBE | ROOT,
74+
),
7175
row(
7276
"pnpm-lock.yaml",
7377
"npm",
@@ -118,7 +122,11 @@ const REGISTRY: &[FormatFile] = &[
118122
row(".cargo/config", "cargo", HOSTED | VENDORED | PROBE),
119123
// ── composer ──
120124
row("composer.json", "composer", VENDORED),
121-
row("composer.lock", "composer", HOSTED | VENDORED | PROBE | ROOT),
125+
row(
126+
"composer.lock",
127+
"composer",
128+
HOSTED | VENDORED | PROBE | ROOT,
129+
),
122130
// ── nuget ──
123131
row("nuget.config", "nuget", HOSTED | PROBE),
124132
row("NuGet.config", "nuget", HOSTED | PROBE),
@@ -213,7 +221,11 @@ mod tests {
213221
paths.dedup();
214222
assert_eq!(before, paths.len(), "duplicate registry path");
215223
for f in REGISTRY.iter().filter(|f| f.has(ROOT)) {
216-
assert!(!f.path.contains('/'), "{}: a root marker is a basename", f.path);
224+
assert!(
225+
!f.path.contains('/'),
226+
"{}: a root marker is a basename",
227+
f.path
228+
);
217229
}
218230
}
219231

0 commit comments

Comments
 (0)