1919//!
2020//! Every hosted instance of a pin is restored together, and every other
2121//! byte of the lock (flags, trailing slots, indent, comma, `\r`) is kept.
22+ //! Default-registry admission and sibling conventions use the same raw
23+ //! segment policy as forward rewrite, heal and vendor. Only the shared
24+ //! `registry_base` normalizes a modern empty segment when resolving a URL.
2225
2326use serde_json:: { Map , Value } ;
2427
2528use super :: npm:: { by_uuid, fetch_dists, read_or_refuse, refuse_all_in} ;
2629use super :: { Ctx , FormatResult , HostedPin , View } ;
2730use crate :: vendor:: vlt_lock_text:: {
2831 default_registry_alias, entry_text, is_default_registry, nodes_block, parse_node_line,
29- registry_base, registry_segment , render_entry_line, render_tuple_with_slots, sniff_lock,
30- split_dep_id , split_lines, DepIdEra , DepIdKind , LockSniff ,
32+ registry_base, render_entry_line, render_tuple_with_slots, sniff_lock, split_dep_id ,
33+ split_lines, DepIdEra , DepIdKind , LockSniff ,
3134} ;
3235
3336/// One hosted node line to restore.
@@ -56,7 +59,6 @@ fn records_url(
5659 siblings : & [ ( DepIdEra , bool ) ] ,
5760 options : Option < & Map < String , Value > > ,
5861) -> bool {
59- let segment = registry_segment ( era, segment) ;
6062 let same_era: Vec < bool > = siblings
6163 . iter ( )
6264 . filter ( |( e, _) | * e == era)
@@ -175,8 +177,7 @@ pub(crate) async fn restore(
175177 let hosted = slot3. as_deref ( ) . and_then ( |u| ctx. hosted_uuid ( u) ) ;
176178 let Some ( uuid) = hosted else {
177179 if let Some ( dep_id) = dep_id. filter ( |d| {
178- d. kind == DepIdKind :: Registry
179- && is_default_registry ( registry_segment ( d. era , & d. first ) , options)
180+ d. kind == DepIdKind :: Registry && is_default_registry ( & d. first , options)
180181 } ) {
181182 siblings. push ( ( dep_id. era , slot3. is_some ( ) ) ) ;
182183 }
@@ -208,7 +209,7 @@ pub(crate) async fn restore(
208209 ) ;
209210 continue ;
210211 } ;
211- if !is_default_registry ( registry_segment ( dep_id . era , & dep_id. first ) , options) {
212+ if !is_default_registry ( & dep_id. first , options) {
212213 result. refuse (
213214 & uuid,
214215 format ! (
@@ -540,8 +541,8 @@ mod tests {
540541 let ( outcome, after) =
541542 run ( & text, & [ pin ( "pkg:npm/left-pad@1.3.0" , LP_UUID ) ] , false ) . await ;
542543 let opts = opts ( options) ;
543- if is_default_registry ( registry_segment ( * era , segment) , Some ( & opts) ) {
544- let want = want . expect ( "a default registry always has a base" ) ;
544+ let admitted = is_default_registry ( segment, Some ( & opts) ) ;
545+ if let ( true , Some ( want) ) = ( admitted , * want ) {
545546 assert ! (
546547 refused( & outcome) . is_empty( ) ,
547548 "{segment:?} {options}: {:?}" ,
@@ -553,10 +554,12 @@ mod tests {
553554 assert ! ( after. contains( & upstream) , "{segment:?} {options}: {after}" ) ;
554555 } else {
555556 let why = refused ( & outcome) ;
556- assert ! (
557- why[ 0 ] . contains( "not on vlt's default registry" ) ,
558- "{segment:?} {options}: {why:?}"
559- ) ;
557+ let reason = if admitted {
558+ "maps no registry"
559+ } else {
560+ "not on vlt's default registry"
561+ } ;
562+ assert ! ( why[ 0 ] . contains( reason) , "{segment:?} {options}: {why:?}" ) ;
560563 assert_eq ! ( after, text) ;
561564 }
562565 }
@@ -565,7 +568,7 @@ mod tests {
565568
566569 #[ tokio:: test]
567570 #[ serial_test:: serial]
568- async fn restore_empty_tilde_resolution_and_sibling_admission ( ) {
571+ async fn restore_empty_tilde_resolution_and_raw_sibling_convention ( ) {
569572 let server = registry ( & [ ( "left-pad" , "1.3.0" , Some ( LP_UPSTREAM ) ) ] ) . await ;
570573 std:: env:: set_var ( "SOCKET_NPM_REGISTRY" , server. uri ( ) ) ;
571574 let url = hosted ( LP_UUID , "left-pad-1.3.0.tgz" ) ;
@@ -574,21 +577,21 @@ mod tests {
574577 "" ,
575578 r#"{"registry":"https://a.example/","registries":{"npm":"https://b.example/"}}"# ,
576579 "" ,
577- "https://b.example/" ,
580+ Some ( "https://b.example/" ) ,
578581 ) ,
579582 (
580583 "npm" ,
581584 r#"{"registry":"https://a.example/","registries":{"npm":"https://b.example/"}}"# ,
582585 "" ,
583- "https://b.example/" ,
586+ Some ( "https://b.example/" ) ,
584587 ) ,
585588 (
586589 "corp" ,
587590 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 .
591+ // Keep forward rewrite's raw-empty admission policy when
592+ // choosing siblings, so its 3-tuple convention is retained .
590593 ",\n \" ~~ms@2.1.3\" : [0,\" ms\" ,\" sha512-M==\" ]" ,
591- "https://c.example/" ,
594+ None ,
592595 ) ,
593596 ] {
594597 let key = format ! ( "~{segment}~left-pad@1.3.0" ) ;
@@ -599,16 +602,61 @@ mod tests {
599602 run ( & text, & [ pin ( "pkg:npm/left-pad@1.3.0" , LP_UUID ) ] , false ) . await ;
600603 assert ! ( refused( & outcome) . is_empty( ) , "{:?}" , refused( & outcome) ) ;
601604 let after: Value = serde_json:: from_str ( & after) . unwrap ( ) ;
602- assert_eq ! (
603- after[ "nodes" ] [ & key] ,
604- serde_json:: json!( [
605+ let expected = match base {
606+ Some ( base) => serde_json:: json!( [
605607 0 ,
606608 "left-pad" ,
607609 LP_UPSTREAM ,
608610 format!( "{base}left-pad/-/left-pad-1.3.0.tgz" )
609611 ] ) ,
610- "{segment:?} {options}"
612+ None => serde_json:: json!( [ 0 , "left-pad" , LP_UPSTREAM ] ) ,
613+ } ;
614+ assert_eq ! ( after[ "nodes" ] [ & key] , expected, "{segment:?} {options}" ) ;
615+ }
616+ std:: env:: remove_var ( "SOCKET_NPM_REGISTRY" ) ;
617+ }
618+
619+ #[ tokio:: test]
620+ #[ serial_test:: serial]
621+ async fn rewritten_empty_tilde_under_custom_alias_restores_byte_for_byte ( ) {
622+ use crate :: patch:: redirect:: { DepOverride , RewriteResult } ;
623+
624+ let server = registry ( & [ ( "left-pad" , "1.3.0" , Some ( LP_UPSTREAM ) ) ] ) . await ;
625+ std:: env:: set_var ( "SOCKET_NPM_REGISTRY" , server. uri ( ) ) ;
626+ let url = hosted ( LP_UUID , "left-pad-1.3.0.tgz" ) ;
627+ let dep: DepOverride = serde_json:: from_value ( serde_json:: json!( {
628+ "ecosystem" : "npm" ,
629+ "name" : "left-pad" ,
630+ "version" : "1.3.0" ,
631+ "token" : "t" ,
632+ "patchUuid" : LP_UUID ,
633+ "artifactUrl" : url,
634+ "integrity" : { "sha512" : "sha512-PATCHED==" } ,
635+ } ) )
636+ . unwrap ( ) ;
637+ for sibling in [
638+ "" ,
639+ ",\n \" ~~ms@2.1.3\" : [0,\" ms\" ,\" sha512-M==\" ,\" https://b.example/ms/-/ms-2.1.3.tgz\" ]" ,
640+ ] {
641+ let original = format ! (
642+ "{{\n \" lockfileVersion\" : 1,\n \" options\" : {{\" default-registry-alias\" :\" corp\" ,\" registry\" :\" https://a.example/\" ,\" registries\" :{{\" corp\" :\" https://a.example/\" ,\" npm\" :\" https://b.example/\" }}}},\n \" nodes\" : {{\n \" ~~left-pad@1.3.0\" : [0,\" left-pad\" ,\" {LP_UPSTREAM}\" ,\" https://b.example/left-pad/-/left-pad-1.3.0.tgz\" ]{sibling}\n }},\n \" edges\" : {{}}\n }}\n "
611643 ) ;
644+ let files = [ ( "vlt-lock.json" . to_string ( ) , original. clone ( ) ) ]
645+ . into_iter ( )
646+ . collect ( ) ;
647+ let mut rewritten = RewriteResult :: default ( ) ;
648+ crate :: patch:: redirect:: vlt:: rewrite_vlt_lock (
649+ & files,
650+ std:: slice:: from_ref ( & dep) ,
651+ false ,
652+ & mut rewritten,
653+ ) ;
654+ let pinned = rewritten. files . get ( "vlt-lock.json" ) . expect ( "forward rewrite admits the raw empty segment" ) ;
655+ assert ! ( pinned. contains( & url) , "{pinned}" ) ;
656+ let ( outcome, restored) =
657+ run ( pinned, & [ pin ( "pkg:npm/left-pad@1.3.0" , LP_UUID ) ] , false ) . await ;
658+ assert ! ( refused( & outcome) . is_empty( ) , "{:?}" , refused( & outcome) ) ;
659+ assert_eq ! ( restored, original) ;
612660 }
613661 std:: env:: remove_var ( "SOCKET_NPM_REGISTRY" ) ;
614662 }
0 commit comments