@@ -388,11 +388,15 @@ async fn mount_hosted_mocks(
388388/// Serve, from `server` (as `SOCKET_NPM_REGISTRY`), the npm registry version
389389/// document the v5 upstream restore reads for DEP — mirrored from what the
390390/// PRISTINE classic lock recorded (`resolved "<tarball>#<sha1>"`,
391- /// `integrity`). The restore of a hosted classic entry must reproduce the
392- /// registry entry yarn wrote from exactly that document; mirroring it keeps
391+ /// `integrity`, or the SHA-1 fragment on pre-1.10 releases). The restore must
392+ /// reproduce the registry entry yarn wrote from that document; mirroring it keeps
393393/// the unwind hermetic (the binary's TLS stack need not reach the real
394- /// registry). Returns the registry base to hand the binary.
395- async fn mount_registry_from_classic_lock ( server : & MockServer , lock : & str ) -> String {
394+ /// registry). Returns the registry base and expected upstream lock. Hosted
395+ /// mode adds an integrity line even on pre-1.10 yarn, and v5 restores that
396+ /// line's registry hash without a saved fragment to recover its absence.
397+ async fn mount_registry_from_classic_lock ( server : & MockServer , lock : & str ) -> ( String , String ) {
398+ use base64:: Engine as _;
399+
396400 let block = lock
397401 . split ( "\n \n " )
398402 . find ( |b| {
@@ -404,23 +408,43 @@ async fn mount_registry_from_classic_lock(server: &MockServer, lock: &str) -> St
404408 . lines ( )
405409 . find_map ( |l| l. trim ( ) . strip_prefix ( & format ! ( "{name} " ) ) )
406410 . map ( |v| v. trim_matches ( '"' ) . to_string ( ) )
407- . unwrap_or_else ( || panic ! ( "no `{name}` in {block}" ) )
408411 } ;
409- let resolved = field ( "resolved" ) ;
412+ let resolved = field ( "resolved" ) . unwrap_or_else ( || panic ! ( "no `resolved` in {block}" ) ) ;
410413 let ( tarball, shasum) = resolved
411414 . split_once ( '#' )
412415 . map ( |( t, s) | ( t. to_string ( ) , Some ( s. to_string ( ) ) ) )
413416 . unwrap_or ( ( resolved. clone ( ) , None ) ) ;
417+ let integrity = field ( "integrity" ) . unwrap_or_else ( || {
418+ let sha1 = hex:: decode (
419+ shasum
420+ . as_ref ( )
421+ . expect ( "pre-1.10 yarn pins a SHA-1 fragment" ) ,
422+ )
423+ . expect ( "the resolved fragment is hex SHA-1" ) ;
424+ format ! (
425+ "sha1-{}" ,
426+ base64:: engine:: general_purpose:: STANDARD . encode( sha1)
427+ )
428+ } ) ;
429+ let upstream_lock = if field ( "integrity" ) . is_some ( ) {
430+ lock. to_string ( )
431+ } else {
432+ lock. replacen (
433+ & format ! ( " resolved \" {resolved}\" " ) ,
434+ & format ! ( " resolved \" {resolved}\" \n integrity {integrity}" ) ,
435+ 1 ,
436+ )
437+ } ;
414438 Mock :: given ( method ( "GET" ) )
415439 . and ( path ( format ! ( "/registry/{DEP}/{DEP_VERSION}" ) ) )
416440 . respond_with ( ResponseTemplate :: new ( 200 ) . set_body_json ( serde_json:: json!( {
417441 "name" : DEP ,
418442 "version" : DEP_VERSION ,
419- "dist" : { "tarball" : tarball, "integrity" : field ( " integrity" ) , "shasum" : shasum }
443+ "dist" : { "tarball" : tarball, "integrity" : integrity, "shasum" : shasum }
420444 } ) ) )
421445 . mount ( server)
422446 . await ;
423- format ! ( "{}/registry" , server. uri( ) )
447+ ( format ! ( "{}/registry" , server. uri( ) ) , upstream_lock )
424448}
425449
426450fn run_hosted_scan ( proj : & Path , server_uri : & str ) -> ( i32 , String , String ) {
@@ -642,7 +666,7 @@ fn assert_pure_vendored_and_round_trip(
642666 "fresh vendored install must carry the PATCHED bytes ({tag})"
643667 ) ;
644668
645- // (b) Round trip: `vendor --revert` restores the REGISTRY lock
669+ // (b) Round trip: `vendor --revert` restores the expected REGISTRY lock
646670 // byte-identically (pre-fix it restored the hosted fragment, with no CLI
647671 // path back to registry state).
648672 let ( code, stdout, stderr) = run_socket (
@@ -659,8 +683,8 @@ fn assert_pure_vendored_and_round_trip(
659683 assert_eq ! (
660684 std:: fs:: read( proj. join( "yarn.lock" ) ) . unwrap( ) ,
661685 lock_pristine,
662- "yarn.lock must be restored byte-identical to the pre-hosted \
663- REGISTRY pristine ({tag}); got:\n {}",
686+ "yarn.lock must be restored byte-identical to the expected \
687+ upstream REGISTRY lock ({tag}); got:\n {}",
664688 read( proj, "yarn.lock" )
665689 ) ;
666690 assert_eq ! (
@@ -809,7 +833,7 @@ async fn classic_hosted_then_vendored_takeover_round_trips_to_registry() {
809833 // upstream restore re-resolves the registry entry (mirrored from the
810834 // pristine lock), and the mock origin is named hosted via
811835 // --patch-server-url.
812- let registry =
836+ let ( registry, lock_upstream ) =
813837 mount_registry_from_classic_lock ( & server, & String :: from_utf8_lossy ( & lock_pristine) ) . await ;
814838 stage_patch ( & proj, & fx. orig , & fx. patched ) ;
815839 let ( code, stdout, stderr) = run_socket_env (
@@ -847,7 +871,7 @@ async fn classic_hosted_then_vendored_takeover_round_trips_to_registry() {
847871 "classic" ,
848872 false ,
849873 & hosted_url,
850- & lock_pristine ,
874+ lock_upstream . as_bytes ( ) ,
851875 & pkg_json_pristine,
852876 & stdout,
853877 ) ;
@@ -1055,10 +1079,10 @@ async fn classic_vendored_then_hosted_takeover_leaves_pure_hosted() {
10551079 )
10561080 } ) ;
10571081
1058- // The originals chain across migrations: `rollback` restores the hosted
1059- // pin's upstream registry entry, which is the pristine lock byte for
1060- // byte (online: the entry is re-resolved from the registry document) .
1061- let registry =
1082+ // Rollback re-resolves the upstream registry entry. Hosted mode added an
1083+ // integrity line even on pre-1.10 yarn; v5 has no saved fragment to tell
1084+ // whether it was originally absent, so it restores the registry hash .
1085+ let ( registry, lock_upstream ) =
10621086 mount_registry_from_classic_lock ( & server, & String :: from_utf8_lossy ( & lock_pristine) ) . await ;
10631087 let ( code, stdout, stderr) = run_socket_env (
10641088 & proj,
@@ -1076,8 +1100,27 @@ async fn classic_vendored_then_hosted_takeover_leaves_pure_hosted() {
10761100 assert_eq ! ( code, 0 , "rollback failed: {stdout}\n {stderr}" ) ;
10771101 assert_eq ! (
10781102 read( & proj, "yarn.lock" ) ,
1079- String :: from_utf8_lossy( & lock_pristine) ,
1080- "rollback lands on the pristine registry lock"
1103+ lock_upstream,
1104+ "rollback restores the registry lock, allowing the added upstream integrity"
1105+ ) ;
1106+ let fresh = fresh_checkout ( & proj, fx. tmp . path ( ) , "classic-rollback" , false ) ;
1107+ let fresh_cache = fx. tmp . path ( ) . join ( "fresh-cache-classic-rollback" ) ;
1108+ let ci = corepack (
1109+ & fresh,
1110+ & yarn_classic_vex:: yarn_classic ( ) ,
1111+ & [ "install" , "--frozen-lockfile" , "--no-progress" ] ,
1112+ & [ ( "YARN_CACHE_FOLDER" , fresh_cache. to_str ( ) . unwrap ( ) ) ] ,
1113+ ) ;
1114+ assert ! (
1115+ ci. status. success( ) ,
1116+ "fresh-checkout rollback install must succeed.\n stdout:\n {}\n stderr:\n {}" ,
1117+ String :: from_utf8_lossy( & ci. stdout) ,
1118+ String :: from_utf8_lossy( & ci. stderr) ,
1119+ ) ;
1120+ assert_eq ! (
1121+ std:: fs:: read( fresh. join( "node_modules" ) . join( DEP ) . join( "index.js" ) ) . unwrap( ) ,
1122+ fx. orig,
1123+ "rollback installs the pristine registry bytes"
10811124 ) ;
10821125}
10831126
0 commit comments