@@ -34,6 +34,11 @@ class VersionResolver
3434 T ::Hash [ String , T ::Array [ String ] ]
3535 )
3636
37+ # Maximum number of older versions to check when the latest version
38+ # triggers a pnpm trust downgrade. Prevents excessive subprocess spawning
39+ # for packages with many published versions.
40+ MAX_TRUST_DOWNGRADE_FALLBACK_ATTEMPTS = 5
41+
3742 # Error message returned by `yarn add` (for Yarn classic):
3843 # " > @reach/router@1.2.1" has incorrect peer dependency "react@15.x || 16.x || 16.4.0-alpha.0911da3"
3944 # "workspace-aggregator-<random-string> > test > react-dom@15.6.2" has incorrect peer dependency "react@^15.6.2"
@@ -251,6 +256,11 @@ def dependency_updates_from_full_unlock
251256 sig { returns ( T ::Boolean ) }
252257 attr_reader :raise_on_ignored
253258
259+ sig { returns ( T ::Boolean ) }
260+ def trust_downgrade_detected?
261+ @trust_downgrade_detected
262+ end
263+
254264 sig { params ( dep : Dependabot ::Dependency ) . returns ( PackageLatestVersionFinder ) }
255265 def latest_version_finder ( dep )
256266 @latest_version_finder [ dep ] ||=
@@ -277,6 +287,7 @@ def find_version_without_trust_downgrade
277287 . select { |v | current_version . nil? || v > current_version }
278288 . sort
279289 . reverse
290+ . first ( MAX_TRUST_DOWNGRADE_FALLBACK_ATTEMPTS )
280291
281292 candidate_versions . each do |candidate |
282293 next if version_has_trust_downgrade? ( candidate )
@@ -288,29 +299,37 @@ def find_version_without_trust_downgrade
288299 end
289300
290301 Dependabot . logger . info (
291- "No version of #{ dependency . name } found without pnpm trust downgrade"
302+ "No version of #{ dependency . name } found without pnpm trust downgrade " \
303+ "(checked #{ candidate_versions . length } versions)"
292304 )
293305 nil
294306 end
295307
296308 # Checks whether a specific version triggers pnpm trust downgrade by running
297309 # the peer dependency check and inspecting the trust_downgrade_detected flag.
310+ # Saves and restores instance state to avoid polluting the caller's context.
298311 sig { params ( version : Gem ::Version ) . returns ( T ::Boolean ) }
299312 def version_has_trust_downgrade? ( version )
313+ saved_trust_downgrade = @trust_downgrade_detected
314+ saved_peer_errors = @peer_dependency_errors
315+
300316 @trust_downgrade_detected = false
301317 @peer_dependency_errors = nil
302318
303319 fetch_peer_dependency_errors ( version : version )
304320
305- # fetch_peer_dependency_errors sets @trust_downgrade_detected as a side effect
306- # when pnpm returns ERR_PNPM_TRUST_DOWNGRADE
307- result = T . unsafe ( @trust_downgrade_detected )
321+ result = trust_downgrade_detected?
308322 if result
309323 Dependabot . logger . info (
310324 "pnpm trust downgrade also detected for #{ dependency . name } @#{ version } , skipping"
311325 )
312326 end
313327 result
328+ ensure
329+ # T.must needed because Sorbet considers locals potentially nil in ensure blocks;
330+ # @peer_dependency_errors doesn't need it since its type is already nilable.
331+ @trust_downgrade_detected = T . must ( saved_trust_downgrade )
332+ @peer_dependency_errors = saved_peer_errors
314333 end
315334
316335 # rubocop:disable Metrics/PerceivedComplexity
0 commit comments