@@ -2192,3 +2192,125 @@ async fn issue_1630_berry_exec_and_git_copies_are_named() {
21922192 }
21932193 }
21942194}
2195+
2196+ /// REGRESSION: the in-memory engine (no consent gate in front of it, unlike
2197+ /// the CLI) pinned a package the project patches itself with pnpm
2198+ /// (`patchedDependencies`, in `pnpm-workspace.yaml`, `package.json`'s
2199+ /// `pnpm` field or the lock), yarn (a `patch:` descriptor in
2200+ /// `package.json`, or the locator `yarn.lock` resolves) or patch-package,
2201+ /// so the install stacked the user's diff on the hosted
2202+ /// patch. Such a candidate is now withheld from every rewriter, skipped
2203+ /// `user_patch_conflict` with a warning, and its lock left untouched;
2204+ /// another package's patch withholds nothing.
2205+ #[ tokio:: test]
2206+ async fn user_patched_npm_packages_are_never_pinned ( ) {
2207+ let berry_wrapper =
2208+ "\" left-pad\" : \" patch:left-pad@npm%3A^1.3.0#./.yarn/patches/left-pad.patch\" " ;
2209+ let cases: Vec < ( & str , & str , String , & str ) > = vec ! [
2210+ (
2211+ "redirect/npm/pnpm/basic" ,
2212+ "pnpm-workspace.yaml" ,
2213+ "patchedDependencies:\n left-pad@1.3.0: patches/left-pad@1.3.0.patch\n " . into( ) ,
2214+ "pnpm-lock.yaml" ,
2215+ ) ,
2216+ (
2217+ "redirect/npm/pnpm/basic" ,
2218+ "package.json" ,
2219+ r#"{"pnpm":{"patchedDependencies":{"left-pad":"patches/left-pad.patch"}}}"# . into( ) ,
2220+ "pnpm-lock.yaml" ,
2221+ ) ,
2222+ (
2223+ "redirect/npm/yarn-berry/basic" ,
2224+ "package.json" ,
2225+ String :: from_utf8(
2226+ fixture_files( & fixtures_root( ) . join( "redirect/npm/yarn-berry/basic/input" ) )
2227+ [ "package.json" ]
2228+ . clone( ) ,
2229+ )
2230+ . unwrap( )
2231+ . replacen( "\" left-pad\" : \" ^1.3.0\" " , berry_wrapper, 1 ) ,
2232+ "yarn.lock" ,
2233+ ) ,
2234+ (
2235+ "redirect/npm/yarn-berry/basic" ,
2236+ "yarn.lock" ,
2237+ format!(
2238+ "{}\n \" left-pad@patch:left-pad@npm%3A1.3.0#./.yarn/patches/lp.patch\" :\n \
2239+ version: 1.3.0\n resolution: \" left-pad@patch:left-pad@npm%3A1.3.0#./.yarn/\
2240+ patches/lp.patch::version=1.3.0&hash=abc123\" \n languageName: node\n \
2241+ linkType: hard\n ",
2242+ String :: from_utf8(
2243+ fixture_files( & fixtures_root( ) . join( "redirect/npm/yarn-berry/basic/input" ) )
2244+ [ "yarn.lock" ]
2245+ . clone( )
2246+ )
2247+ . unwrap( )
2248+ ) ,
2249+ "yarn.lock" ,
2250+ ) ,
2251+ (
2252+ NPM_FIXTURE ,
2253+ "patches/left-pad+1.3.0.patch" ,
2254+ "diff\n " . into( ) ,
2255+ "package-lock.json" ,
2256+ ) ,
2257+ ] ;
2258+ for ( fixture, file, text, lock) in cases {
2259+ let dir = fixtures_root ( ) . join ( fixture) ;
2260+ let server = MockServer :: start ( ) . await ;
2261+ mount_api (
2262+ & server,
2263+ & patches_from_overrides ( & dir. join ( "overrides.json" ) , None ) ,
2264+ )
2265+ . await ;
2266+ let files = fixture_files ( & dir. join ( "input" ) ) ;
2267+ let control = run_engine ( & server, build_input ( & files, & [ ] , options ( false ) ) ) . await ;
2268+ assert_eq ! (
2269+ control. projects[ 0 ] . redirected. len( ) ,
2270+ 1 ,
2271+ "{fixture}: control {:?}" ,
2272+ control. projects[ 0 ] . skipped
2273+ ) ;
2274+
2275+ let mut files = files;
2276+ files. insert ( file. to_string ( ) , text. clone ( ) . into_bytes ( ) ) ;
2277+ let output = run_engine ( & server, build_input ( & files, & [ ] , options ( false ) ) ) . await ;
2278+ let project = & output. projects [ 0 ] ;
2279+ assert ! ( project. error. is_none( ) , "{fixture}: {:?}" , project. error) ;
2280+ assert ! (
2281+ project. redirected. is_empty( ) ,
2282+ "{fixture} {file}: {:?}" ,
2283+ project. redirected
2284+ ) ;
2285+ let skip = project
2286+ . skipped
2287+ . iter ( )
2288+ . find ( |s| s. reason == "user_patch_conflict" )
2289+ . unwrap_or_else ( || panic ! ( "{fixture} {file}: {:?}" , project. skipped) ) ;
2290+ assert_eq ! ( skip. purl, "pkg:npm/left-pad@1.3.0" ) ;
2291+ assert ! (
2292+ skip. detail. as_deref( ) . is_some_and( |d| d. contains( file) ) ,
2293+ "{fixture}: {:?}" ,
2294+ skip. detail
2295+ ) ;
2296+ let warnings = project. redirect [ "warnings" ] . as_array ( ) . unwrap ( ) ;
2297+ assert ! (
2298+ warnings. iter( ) . any( |w| w[ "code" ] == "user_patch_conflict" ) ,
2299+ "{fixture}: {warnings:?}"
2300+ ) ;
2301+ assert ! (
2302+ output. changed_files. iter( ) . all( |f| f. path != lock) ,
2303+ "{fixture}: {lock} must stay untouched"
2304+ ) ;
2305+
2306+ // Another package's patch withholds nothing.
2307+ let mut files = fixture_files ( & dir. join ( "input" ) ) ;
2308+ files. insert (
2309+ "pnpm-workspace.yaml" . into ( ) ,
2310+ b"patchedDependencies:\n is-number@7.0.0: patches/is-number.patch\n " . to_vec ( ) ,
2311+ ) ;
2312+ files. insert ( "patches/is-number+7.0.0.patch" . into ( ) , b"diff\n " . to_vec ( ) ) ;
2313+ let output = run_engine ( & server, build_input ( & files, & [ ] , options ( false ) ) ) . await ;
2314+ assert_eq ! ( output. projects[ 0 ] . redirected. len( ) , 1 , "{fixture}: other" ) ;
2315+ }
2316+ }
0 commit comments