@@ -26,8 +26,8 @@ use super::npm::{by_uuid, fetch_dists, read_or_refuse, refuse_all_in};
2626use super :: { Ctx , FormatResult , HostedPin , View } ;
2727use crate :: vendor:: vlt_lock_text:: {
2828 default_registry_alias, entry_text, is_default_registry, nodes_block, parse_node_line,
29- registry_base, render_entry_line, render_tuple_with_slots, sniff_lock, split_dep_id ,
30- split_lines, DepIdEra , DepIdKind , LockSniff ,
29+ registry_base, registry_segment , render_entry_line, render_tuple_with_slots, sniff_lock,
30+ split_dep_id , split_lines, DepIdEra , DepIdKind , LockSniff ,
3131} ;
3232
3333/// One hosted node line to restore.
@@ -56,6 +56,7 @@ fn records_url(
5656 siblings : & [ ( DepIdEra , bool ) ] ,
5757 options : Option < & Map < String , Value > > ,
5858) -> bool {
59+ let segment = registry_segment ( era, segment) ;
5960 let same_era: Vec < bool > = siblings
6061 . iter ( )
6162 . filter ( |( e, _) | * e == era)
@@ -70,14 +71,15 @@ fn records_url(
7071 . and_then ( |o| o. get ( "registries" ) )
7172 . is_some_and ( Value :: is_object)
7273 && ( segment. is_empty ( ) || default_registry_alias ( options) == Some ( segment) )
73- && !under_configured_registry ( segment, name, options)
74+ && !under_configured_registry ( era , segment, name, options)
7475}
7576
7677/// Would a default-registry node on `segment` resolve under the lock's
7778/// recorded `options.registry`? vlt omits slot [3] for such a node
7879/// (`lockfile/save.ts`: `customRegistry = resolved && (!registry ||
7980/// !resolved.startsWith(registry))`).
8081fn under_configured_registry (
82+ era : DepIdEra ,
8183 segment : & str ,
8284 name : & str ,
8385 options : Option < & Map < String , Value > > ,
@@ -89,7 +91,7 @@ fn under_configured_registry(
8991 else {
9092 return false ;
9193 } ;
92- registry_base ( segment, name, options) . is_some_and ( |base| base. starts_with ( registry) )
94+ registry_base ( era , segment, name, options) . is_some_and ( |base| base. starts_with ( registry) )
9395}
9496
9597pub ( crate ) async fn restore (
@@ -173,7 +175,8 @@ pub(crate) async fn restore(
173175 let hosted = slot3. as_deref ( ) . and_then ( |u| ctx. hosted_uuid ( u) ) ;
174176 let Some ( uuid) = hosted else {
175177 if let Some ( dep_id) = dep_id. filter ( |d| {
176- d. kind == DepIdKind :: Registry && is_default_registry ( & d. first , options)
178+ d. kind == DepIdKind :: Registry
179+ && is_default_registry ( registry_segment ( d. era , & d. first ) , options)
177180 } ) {
178181 siblings. push ( ( dep_id. era , slot3. is_some ( ) ) ) ;
179182 }
@@ -205,7 +208,7 @@ pub(crate) async fn restore(
205208 ) ;
206209 continue ;
207210 } ;
208- if !is_default_registry ( & dep_id. first , options) {
211+ if !is_default_registry ( registry_segment ( dep_id . era , & dep_id. first ) , options) {
209212 result. refuse (
210213 & uuid,
211214 format ! (
@@ -252,7 +255,7 @@ pub(crate) async fn restore(
252255 let line = parse_node_line ( lines[ hit. line ] ) . expect ( "the hit line parsed above" ) ;
253256 let json = |s : & str | serde_json:: to_string ( s) . expect ( "a str serializes to JSON" ) ;
254257 let slot3 = if records_url ( hit. era , & hit. segment , & hit. name , & siblings, options) {
255- let Some ( base) = registry_base ( & hit. segment , & hit. name , options) else {
258+ let Some ( base) = registry_base ( hit . era , & hit. segment , & hit. name , options) else {
256259 result. refuse (
257260 & hit. uuid ,
258261 format ! ( "{rel} maps no registry for the `{}` segment" , hit. segment) ,
@@ -523,19 +526,21 @@ mod tests {
523526 let server = registry ( & [ ( "left-pad" , "1.3.0" , Some ( LP_UPSTREAM ) ) ] ) . await ;
524527 std:: env:: set_var ( "SOCKET_NPM_REGISTRY" , server. uri ( ) ) ;
525528 let url = hosted ( LP_UUID , "left-pad-1.3.0.tgz" ) ;
526- for ( segment, options, want) in REGISTRY_BASE_CASES {
529+ for ( era , segment, options, want) in REGISTRY_BASE_CASES {
527530 if segment. starts_with ( "http" ) {
528531 continue ;
529532 }
530533 // A same-era sibling that records slot [3] makes restore write
531534 // one for the pin.
535+ let delimiter = era. delimiter ( ) ;
536+ let lock_version = u8:: from ( * era == DepIdEra :: Tilde ) ;
532537 let text = format ! (
533- "{{\n \" lockfileVersion\" : 1 ,\n \" options\" : {options},\n \" nodes\" : {{\n \" ~{ segment}~ left-pad@1.3.0\" : [0,\" left-pad\" ,\" sha512-AA==\" ,\" {url}\" ],\n \" ~{ segment}~ ms@2.1.3\" : [0,\" ms\" ,\" sha512-M==\" ,\" https://x.example/ms/-/ms-2.1.3.tgz\" ]\n }},\n \" edges\" : {{}}\n }}\n "
538+ "{{\n \" lockfileVersion\" : {lock_version} ,\n \" options\" : {options},\n \" nodes\" : {{\n \" {delimiter}{ segment}{delimiter} left-pad@1.3.0\" : [0,\" left-pad\" ,\" sha512-AA==\" ,\" {url}\" ],\n \" {delimiter}{ segment}{delimiter} ms@2.1.3\" : [0,\" ms\" ,\" sha512-M==\" ,\" https://x.example/ms/-/ms-2.1.3.tgz\" ]\n }},\n \" edges\" : {{}}\n }}\n "
534539 ) ;
535540 let ( outcome, after) =
536541 run ( & text, & [ pin ( "pkg:npm/left-pad@1.3.0" , LP_UUID ) ] , false ) . await ;
537542 let opts = opts ( options) ;
538- if is_default_registry ( segment, Some ( & opts) ) {
543+ if is_default_registry ( registry_segment ( * era , segment) , Some ( & opts) ) {
539544 let want = want. expect ( "a default registry always has a base" ) ;
540545 assert ! (
541546 refused( & outcome) . is_empty( ) ,
@@ -558,6 +563,56 @@ mod tests {
558563 std:: env:: remove_var ( "SOCKET_NPM_REGISTRY" ) ;
559564 }
560565
566+ #[ tokio:: test]
567+ #[ serial_test:: serial]
568+ async fn restore_empty_tilde_resolution_and_sibling_admission ( ) {
569+ let server = registry ( & [ ( "left-pad" , "1.3.0" , Some ( LP_UPSTREAM ) ) ] ) . await ;
570+ std:: env:: set_var ( "SOCKET_NPM_REGISTRY" , server. uri ( ) ) ;
571+ let url = hosted ( LP_UUID , "left-pad-1.3.0.tgz" ) ;
572+ for ( segment, options, sibling, base) in [
573+ (
574+ "" ,
575+ r#"{"registry":"https://a.example/","registries":{"npm":"https://b.example/"}}"# ,
576+ "" ,
577+ "https://b.example/" ,
578+ ) ,
579+ (
580+ "npm" ,
581+ r#"{"registry":"https://a.example/","registries":{"npm":"https://b.example/"}}"# ,
582+ "" ,
583+ "https://b.example/" ,
584+ ) ,
585+ (
586+ "corp" ,
587+ r#"{"default-registry-alias":"corp","registries":{"npm":"https://b.example/","corp":"https://c.example/"}}"# ,
588+ // Empty tilde means npm, which is foreign here. Its lack
589+ // of slot 3 must not determine the corp node's convention.
590+ ",\n \" ~~ms@2.1.3\" : [0,\" ms\" ,\" sha512-M==\" ]" ,
591+ "https://c.example/" ,
592+ ) ,
593+ ] {
594+ let key = format ! ( "~{segment}~left-pad@1.3.0" ) ;
595+ let text = format ! (
596+ "{{\n \" lockfileVersion\" : 1,\n \" options\" : {options},\n \" nodes\" : {{\n \" {key}\" : [0,\" left-pad\" ,\" sha512-AA==\" ,\" {url}\" ]{sibling}\n }},\n \" edges\" : {{}}\n }}\n "
597+ ) ;
598+ let ( outcome, after) =
599+ run ( & text, & [ pin ( "pkg:npm/left-pad@1.3.0" , LP_UUID ) ] , false ) . await ;
600+ assert ! ( refused( & outcome) . is_empty( ) , "{:?}" , refused( & outcome) ) ;
601+ let after: Value = serde_json:: from_str ( & after) . unwrap ( ) ;
602+ assert_eq ! (
603+ after[ "nodes" ] [ & key] ,
604+ serde_json:: json!( [
605+ 0 ,
606+ "left-pad" ,
607+ LP_UPSTREAM ,
608+ format!( "{base}left-pad/-/left-pad-1.3.0.tgz" )
609+ ] ) ,
610+ "{segment:?} {options}"
611+ ) ;
612+ }
613+ std:: env:: remove_var ( "SOCKET_NPM_REGISTRY" ) ;
614+ }
615+
561616 #[ tokio:: test]
562617 #[ serial_test:: serial]
563618 async fn restore_honors_scope_for_slot3_and_configured_registry_omission ( ) {
0 commit comments