GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix(vendor): match bun tuples on version, keep CRLF (#180) · SocketDev/socket-patch@52a65d2

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • crates/socket-patch-core/src/vendor
    • bun_lock.rs
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎crates/socket-patch-core/src/vendor/bun_lock.rs‎

Copy file name to clipboardExpand all lines: crates/socket-patch-core/src/vendor/bun_lock.rs
+200-7Lines changed: 200 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ use crate::vendor::bun_lock_text::{
4141
};
4242

4343
use super::common::{already_patched_result, refused};
44-
use super::npm_common::{done_failure, guard_coordinates, guard_revert_uuid_dir, stage_patch_pack};
44+
use super::npm_common::{
45+
done_failure, guard_coordinates, guard_revert_uuid_dir, stage_patch_pack, tgz_rel_leaf,
46+
};
4547
use super::path::parse_vendor_path;
4648
use super::state::{
4749
write_marker, VendorArtifact, VendorEntry, VendorMarker, WiringAction, WiringRecord,
@@ -107,9 +109,10 @@ pub(crate) async fn vendor_bun(
107109

108110
// ── 3. Pre-flight: at least one rewritable instance ──────────────────
109111
let target_spec = format!("{name}@{version}");
112+
let target_leaf = tgz_rel_leaf(name, version);
110113
let has_match = entries
111114
.iter()
112-
.any(|e| classify(e, &target_spec, name).is_some());
115+
.any(|e| classify(e, &target_spec, name, &target_leaf).is_some());
113116
if !has_match {
114117
return refused(
115118
"vendor_lock_entry_not_found",
@@ -166,7 +169,7 @@ pub(crate) async fn vendor_bun(
166169
let mut wiring: Vec<WiringRecord> = Vec::new();
167170
let mut changed = false;
168171
for entry in &entries {
169-
let Some(shape) = classify(entry, &target_spec, name) else {
172+
let Some(shape) = classify(entry, &target_spec, name, &target_leaf) else {
170173
continue;
171174
};
172175
let (deps_verbatim, was_ours) = match shape {
@@ -181,8 +184,16 @@ pub(crate) async fn vendor_bun(
181184
}
182185
};
183186
let original_line = lines[entry.line_idx].clone();
187+
// Lines come from a bare `split('\n')`, so a CRLF lock's lines carry
188+
// a trailing `\r` (the grammar trims it away when parsing). Re-emit
189+
// it verbatim: the surgery must never mix line endings.
190+
let cr = if original_line.ends_with('\r') {
191+
"\r"
192+
} else {
193+
""
194+
};
184195
let new_line = format!(
185-
"{indent}{key}: [\"{name}@{rel_tgz}\", {deps}, \"{integrity}\"]{comma}",
196+
"{indent}{key}: [\"{name}@{rel_tgz}\", {deps}, \"{integrity}\"]{comma}{cr}",
186197
indent = entry.indent,
187198
key = entry.key_raw,
188199
deps = deps_verbatim,
@@ -422,8 +433,17 @@ enum TupleShape {
422433

423434
/// Classify an entry against the target: `Some(Registry)` for the exact
424435
/// `name@version` registry tuple, `Some(Ours{..})` for one of our own
425-
/// `.socket/vendor/npm/` tuples for the same package, `None` otherwise.
426-
fn classify(entry: &BunEntry, target_spec: &str, name: &str) -> Option<TupleShape> {
436+
/// `.socket/vendor/npm/` tuples for the same `name@version` (any uuid),
437+
/// `None` otherwise. The Ours arm matches on the uuid-independent tarball
438+
/// leaf, NOT the name alone: a vendored tuple for ANOTHER version of the
439+
/// same package is someone else's edit (two patched versions can coexist in
440+
/// one lock — nested instances) and must never be cross-clobbered.
441+
fn classify(
442+
entry: &BunEntry,
443+
target_spec: &str,
444+
name: &str,
445+
target_leaf: &str,
446+
) -> Option<TupleShape> {
427447
let spec = decode_json_string(entry.elems.first()?)?;
428448
match entry.elems.len() {
429449
4 if spec == target_spec
@@ -439,7 +459,7 @@ fn classify(entry: &BunEntry, target_spec: &str, name: &str) -> Option<TupleShap
439459
return None;
440460
}
441461
let parts = parse_vendor_path(path)?;
442-
(parts.eco == "npm").then(|| TupleShape::Ours {
462+
(parts.eco == "npm" && parts.leaf == target_leaf).then(|| TupleShape::Ours {
443463
path: path.to_string(),
444464
})
445465
}
@@ -845,6 +865,179 @@ mod tests {
845865
);
846866
}
847867

868+
const UUID_B: &str = "aaaaaaaa-1111-4111-8111-111111111111";
869+
870+
/// Two patched versions of ONE package must vendor independently: the
871+
/// second pass must never classify the first pass's vendored tuple (same
872+
/// name, OTHER version) as its own stale edit and cross-clobber it.
873+
#[tokio::test]
874+
async fn two_vendored_versions_of_one_package_do_not_cross_clobber() {
875+
// Nested haspad/[email protected] (fx.record, UUID) + root [email protected]
876+
// (record_b, UUID_B), each installed where its lock entry says.
877+
let fx = fixture_with(
878+
BN4C_BEFORE_LOCK,
879+
"node_modules/haspad/node_modules/left-pad",
880+
)
881+
.await;
882+
let root_installed = fx.root().join("node_modules/left-pad");
883+
tokio::fs::create_dir_all(&root_installed).await.unwrap();
884+
tokio::fs::write(
885+
root_installed.join("package.json"),
886+
br#"{"name":"left-pad","version":"1.2.0"}"#,
887+
)
888+
.await
889+
.unwrap();
890+
tokio::fs::write(root_installed.join("index.js"), ORIG_INDEX)
891+
.await
892+
.unwrap();
893+
let mut record_b = fx.record.clone();
894+
record_b.uuid = UUID_B.to_string();
895+
896+
let (result_a, entry_a, _) = expect_done(fx.vendor(false).await);
897+
assert!(result_a.success, "{:?}", result_a.error);
898+
let entry_a = entry_a.unwrap();
899+
900+
let blobs = fx.root().join(".socket/blobs");
901+
let sources = PatchSources::blobs_only(&blobs);
902+
let (result_b, entry_b, _) = expect_done(
903+
vendor_bun(
904+
"pkg:npm/[email protected]",
905+
&root_installed,
906+
fx.root(),
907+
&record_b,
908+
&sources,
909+
"2026-06-09T00:00:00Z",
910+
false,
911+
false,
912+
None,
913+
)
914+
.await,
915+
);
916+
assert!(result_b.success, "{:?}", result_b.error);
917+
let entry_b = entry_b.unwrap();
918+
919+
// Each instance points at ITS version's tarball under ITS uuid.
920+
let live = fx.read_lock().await;
921+
assert!(
922+
live.contains(&format!(
923+
"\"haspad/left-pad\": [\"[email protected]/vendor/npm/{UUID}/left-pad-1.3.0.tgz\""
924+
)),
925+
"the 1.3.0 instance must survive the 1.2.0 pass untouched: {live}"
926+
);
927+
assert!(
928+
live.contains(&format!(
929+
"\"left-pad\": [\"[email protected]/vendor/npm/{UUID_B}/left-pad-1.2.0.tgz\""
930+
)),
931+
"the 1.2.0 instance lands under its own uuid: {live}"
932+
);
933+
// The second pass edits exactly ONE entry and keeps its true
934+
// pre-vendor registry original (revert data).
935+
assert_eq!(entry_b.wiring.len(), 1, "{:?}", entry_b.wiring);
936+
assert_eq!(entry_b.wiring[0].key.as_deref(), Some("left-pad"));
937+
assert!(
938+
entry_b.wiring[0]
939+
.original
940+
.as_ref()
941+
.and_then(Value::as_str)
942+
.is_some_and(|o| o.contains("\"[email protected]\"")),
943+
"original = the registry 1.2.0 tuple: {:?}",
944+
entry_b.wiring[0].original
945+
);
946+
947+
// Full revert round-trips the lock byte-exactly, no drift warnings.
948+
for entry in [&entry_b, &entry_a] {
949+
let outcome = revert_bun(entry, fx.root(), false).await;
950+
assert!(outcome.success, "{:?}", outcome.error);
951+
assert!(outcome.warnings.is_empty(), "{:?}", outcome.warnings);
952+
}
953+
assert_eq!(fx.read_lock().await, BN4C_BEFORE_LOCK, "lock byte-restored");
954+
assert!(!fx
955+
.root()
956+
.join(format!(".socket/vendor/npm/{UUID}"))
957+
.exists());
958+
assert!(!fx
959+
.root()
960+
.join(format!(".socket/vendor/npm/{UUID_B}"))
961+
.exists());
962+
}
963+
964+
/// Pre-flight: a lock whose only same-name entries are vendored tuples
965+
/// for ANOTHER version has nothing rewritable — refuse, never rewrite.
966+
#[tokio::test]
967+
async fn preflight_refuses_when_only_other_version_vendored_tuples_exist() {
968+
// BN3_AFTER_LOCK's only left-pad entry is a vendored 1.3.0 3-tuple;
969+
// target 1.2.0.
970+
let fx = fixture_with(BN3_AFTER_LOCK, "node_modules/left-pad").await;
971+
tokio::fs::write(
972+
fx.installed.join("package.json"),
973+
br#"{"name":"left-pad","version":"1.2.0"}"#,
974+
)
975+
.await
976+
.unwrap();
977+
let mut record = fx.record.clone();
978+
record.uuid = UUID_B.to_string();
979+
let blobs = fx.root().join(".socket/blobs");
980+
let sources = PatchSources::blobs_only(&blobs);
981+
let outcome = vendor_bun(
982+
"pkg:npm/[email protected]",
983+
&fx.installed,
984+
fx.root(),
985+
&record,
986+
&sources,
987+
"2026-06-09T00:00:00Z",
988+
false,
989+
false,
990+
None,
991+
)
992+
.await;
993+
expect_refused(outcome, "vendor_lock_entry_not_found");
994+
assert_eq!(
995+
fx.read_lock().await,
996+
BN3_AFTER_LOCK,
997+
"the other version's vendored tuple is never touched"
998+
);
999+
assert!(!fx
1000+
.root()
1001+
.join(format!(".socket/vendor/npm/{UUID_B}"))
1002+
.exists());
1003+
}
1004+
1005+
/// A CRLF lock must stay CRLF: the rewritten entry line re-emits its
1006+
/// trailing `\r`, and revert byte-restores the CRLF original.
1007+
#[tokio::test]
1008+
async fn crlf_lock_keeps_crlf_on_rewritten_lines_and_reverts_byte_exact() {
1009+
let crlf_before = BN3_BEFORE_LOCK.replace('\n', "\r\n");
1010+
let fx = fixture_with(&crlf_before, "node_modules/left-pad").await;
1011+
let (result, entry, _) = expect_done(fx.vendor(false).await);
1012+
assert!(result.success, "{:?}", result.error);
1013+
let entry = entry.unwrap();
1014+
1015+
let actual = fx.actual_integrity().await;
1016+
assert_eq!(
1017+
fx.read_lock().await,
1018+
BN3_AFTER_LOCK
1019+
.replace(SPIKE_INTEGRITY, &actual)
1020+
.replace('\n', "\r\n"),
1021+
"every line — including the rewritten one — still ends \\r\\n"
1022+
);
1023+
1024+
// In-sync re-run stays byte-stable.
1025+
let lock_first = fx.read_lock().await;
1026+
let (result, rerun_entry, _) = expect_done(fx.vendor(false).await);
1027+
assert!(result.success, "{:?}", result.error);
1028+
assert!(rerun_entry.is_none(), "in-sync re-run records nothing");
1029+
assert_eq!(fx.read_lock().await, lock_first);
1030+
1031+
let outcome = revert_bun(&entry, fx.root(), false).await;
1032+
assert!(outcome.success, "{:?}", outcome.error);
1033+
assert!(outcome.warnings.is_empty(), "{:?}", outcome.warnings);
1034+
assert_eq!(
1035+
fx.read_lock().await,
1036+
crlf_before,
1037+
"CRLF original byte-restored"
1038+
);
1039+
}
1040+
8481041
#[tokio::test]
8491042
async fn no_matching_entry_is_refused() {
8501043
// The lock only knows [email protected]; the exact 1.3.0 tuple is

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.