dependabot-bundler 0.387.0 → 0.388.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7cae66d19408e3b79a24a2a7b3f02eb6d6bb10108f26c907ecaa0c0c2c3e4985
4
- data.tar.gz: f25535c60a1fbb733644a2e0429cc9cac5a63150b757ac0b4f14788884f62f31
3
+ metadata.gz: 30d3868144d5663052459e012960b20f211b97ce0f097a2971c50b8d351c4176
4
+ data.tar.gz: bada129a0da00144bdd4f600ce5dee62a90ef73943cfb7b75e4f55f0c94d28f7
5
5
  SHA512:
6
- metadata.gz: 6acd7671bac7bd1de3aab4debc0f6ddf22a62606da55194e079715891b8c9171b19c2fc00e4fe7f5587024a4f747d4d796900e55c37447fb1e5f2c33b751bd12
7
- data.tar.gz: 32a6267dd3b4e3682a726dafdcfc043e0259b3fee6881f8fbee7b35ab85ad9393a3d21a54ea39024f8ca70796a725d85fe05e76e479a9925e391e8ee0d049198
6
+ metadata.gz: 5d7cbe81d255cac2f4c6c4709ee68446f060703ff23c9a1286250327b6db5717e1a9fbc1b3058eecc928eec9881576b04a7d161db3c95a8488ca072306dfb072
7
+ data.tar.gz: 232bd45a635a8ad61404d957776977d25a1cf342fbe42025eeb14b629f0ff694ffd3a08bf47c059cb4a6e2ac60588d3261271c5af888076b92a9a22ba7229d3d
@@ -65,12 +65,7 @@ module Dependabot
65
65
  command: command,
66
66
  function: function,
67
67
  args: args,
68
- env: {
69
- # Set BUNDLE_PATH to a thread-safe location
70
- "BUNDLE_PATH" => File.join(Dependabot::Utils::BUMP_TMP_DIR_PATH, ".bundle"),
71
- # Set GEM_HOME to where the proper version of Bundler is installed
72
- "GEM_HOME" => File.join(helpers_path, ".bundle")
73
- }
68
+ env: subprocess_env(helpers_path, options)
74
69
  )
75
70
  rescue SharedHelpers::HelperSubprocessFailed => e
76
71
  # TODO: Remove once we stop stubbing out the V2 native helper
@@ -85,6 +80,28 @@ module Dependabot
85
80
  File.join(native_helpers_root, "v#{bundler_major_version}")
86
81
  end
87
82
 
83
+ sig do
84
+ params(helpers_path: String, options: T::Hash[Symbol, T.untyped])
85
+ .returns(T::Hash[String, String])
86
+ end
87
+ def self.subprocess_env(helpers_path, options)
88
+ env = {
89
+ # Set BUNDLE_PATH to a thread-safe location
90
+ "BUNDLE_PATH" => File.join(Dependabot::Utils::BUMP_TMP_DIR_PATH, ".bundle"),
91
+ # Set GEM_HOME to where the proper version of Bundler is installed
92
+ "GEM_HOME" => File.join(helpers_path, ".bundle")
93
+ }
94
+
95
+ # Bundler 4+ enforces the Gemfile `source cooldown:` window natively, holding
96
+ # back releases (including transitive dependencies) younger than the configured
97
+ # number of days. Disable it only for security updates so vulnerability
98
+ # remediation is never blocked; normal runs keep native enforcement as the
99
+ # per-source source of truth.
100
+ env["BUNDLE_COOLDOWN"] = "0" if options.fetch(:security_updates_only, false)
101
+
102
+ env
103
+ end
104
+
88
105
  sig { returns(String) }
89
106
  def self.native_helpers_root
90
107
  helpers_root = ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", nil)
@@ -0,0 +1,110 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/dependency_file"
5
+ require "dependabot/security_advisory"
6
+ require "dependabot/package/release_cooldown_options"
7
+ require "dependabot/bundler/update_checker"
8
+
9
+ module Dependabot
10
+ module Bundler
11
+ class UpdateChecker < UpdateCheckers::Base
12
+ # Owns the cooldown policy for Bundler updates: it derives the effective
13
+ # `ReleaseCooldownOptions` from an optional `cooldown:` declared on a Gemfile
14
+ # `source` line and decides when Bundler's native cooldown must be disabled.
15
+ #
16
+ # Extraction is best-effort: it reads the manifest text rather than Bundler's
17
+ # evaluated source config, so uncommon declarations (see SOURCE_COOLDOWN_REGEX
18
+ # and #manifest_files) may be missed or understated. For regular updates
19
+ # Bundler's native cooldown stays enabled as the authoritative backstop, so the
20
+ # worst case is a missed update, never selecting a version native resolution
21
+ # rejects.
22
+ class CooldownOptionsBuilder
23
+ extend T::Sig
24
+
25
+ # Matches the common inline `source "<url>", cooldown: <days>` form. Uncommon
26
+ # forms are not handled: a dynamic URL, `cooldown:` on a wrapped line, or a
27
+ # non-literal/underscored value (e.g. `cooldown: 14_000` reads as `14`). Those
28
+ # Gemfiles fall back to Bundler's native cooldown during resolution, so a
29
+ # too-new release is rejected rather than selected — a missed update at worst,
30
+ # never an incorrect version.
31
+ SOURCE_COOLDOWN_REGEX =
32
+ /^\s*source\s*(?:\(\s*)?["'][^"']+["']\s*,[^\n#]*?\bcooldown:\s*(\d+)/
33
+
34
+ sig do
35
+ params(
36
+ dependency_files: T::Array[Dependabot::DependencyFile],
37
+ security_advisories: T::Array[Dependabot::SecurityAdvisory]
38
+ ).void
39
+ end
40
+ def initialize(dependency_files:, security_advisories:)
41
+ @dependency_files = dependency_files
42
+ @security_advisories = security_advisories
43
+ end
44
+
45
+ # Effective cooldown for target-dependency version selection. Target versions
46
+ # are fetched from the RubyGems API, which Bundler's native cooldown does not
47
+ # gate, so Dependabot applies the source cooldown here. Security updates must
48
+ # never be blocked, so the base config is returned unchanged.
49
+ sig do
50
+ params(base_cooldown: T.nilable(Dependabot::Package::ReleaseCooldownOptions))
51
+ .returns(T.nilable(Dependabot::Package::ReleaseCooldownOptions))
52
+ end
53
+ def release_cooldown_options(base_cooldown)
54
+ return base_cooldown if security_update?
55
+
56
+ source_days = source_cooldown_days
57
+ return base_cooldown if source_days.nil? || !source_days.positive?
58
+ return Dependabot::Package::ReleaseCooldownOptions.new(default_days: source_days) if base_cooldown.nil?
59
+
60
+ # The Gemfile `source cooldown:` applies uniformly to every candidate version,
61
+ # so treat it as a global floor: max every semver tier with it and drop
62
+ # include/exclude so no dependency can bypass it. Mirrors npm_and_yarn's
63
+ # `merge_cooldown_with_npmrc_floor`.
64
+ Dependabot::Package::ReleaseCooldownOptions.new(
65
+ default_days: [base_cooldown.default_days, source_days].max,
66
+ semver_major_days: [base_cooldown.semver_major_days, source_days].max,
67
+ semver_minor_days: [base_cooldown.semver_minor_days, source_days].max,
68
+ semver_patch_days: [base_cooldown.semver_patch_days, source_days].max,
69
+ include: [],
70
+ exclude: []
71
+ )
72
+ end
73
+
74
+ # Options for the native Bundler subprocess. Native cooldown stays enabled for
75
+ # regular updates (per-source, transitive-aware enforcement) and is disabled
76
+ # for security updates so remediation is never blocked.
77
+ sig { params(options: T::Hash[Symbol, T.anything]).returns(T::Hash[Symbol, T.anything]) }
78
+ def native_helper_options(options)
79
+ options.merge(security_updates_only: security_update?)
80
+ end
81
+
82
+ private
83
+
84
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
85
+ attr_reader :dependency_files
86
+
87
+ sig { returns(T::Boolean) }
88
+ def security_update?
89
+ @security_advisories.any?
90
+ end
91
+
92
+ sig { returns(T.nilable(Integer)) }
93
+ def source_cooldown_days
94
+ manifest_files.flat_map do |file|
95
+ T.must(file.content).scan(SOURCE_COOLDOWN_REGEX).flatten.map { |value| Integer(value, 10) }
96
+ end.max
97
+ end
98
+
99
+ # Support files (e.g. child Gemfiles fetched for `eval_gemfile`) and lockfiles
100
+ # are skipped, so a `cooldown:` declared only in an evaluated Gemfile is not seen
101
+ # here; Bundler's native cooldown still enforces it during resolution.
102
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
103
+ def manifest_files
104
+ dependency_files.reject(&:support_file?)
105
+ .reject { |file| file.name.end_with?(".lock", ".locked", ".gemspec", ".specification") }
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -10,13 +10,19 @@ require "dependabot/update_checkers/base"
10
10
 
11
11
  module Dependabot
12
12
  module Bundler
13
- class UpdateChecker < Dependabot::UpdateCheckers::Base
13
+ # The cooldown/native-option policy lives in CooldownOptionsBuilder, but this
14
+ # orchestrator still exceeds the class-length threshold because of its git and
15
+ # resolution surface. Splitting that further is out of scope here, and the other
16
+ # ecosystem update checkers (npm_and_yarn, bun, docker, python) disable this cop
17
+ # for the same reason, so the exception is retained deliberately.
18
+ class UpdateChecker < Dependabot::UpdateCheckers::Base # rubocop:disable Metrics/ClassLength
14
19
  require_relative "update_checker/force_updater"
15
20
  require_relative "update_checker/file_preparer"
16
21
  require_relative "update_checker/requirements_updater"
17
22
  require_relative "update_checker/version_resolver"
18
23
  require_relative "update_checker/latest_version_finder"
19
24
  require_relative "update_checker/conflicting_dependency_resolver"
25
+ require_relative "update_checker/cooldown_options_builder"
20
26
  extend T::Sig
21
27
 
22
28
  sig { override.returns(T.nilable(T.any(String, Dependabot::Bundler::Version))) }
@@ -120,13 +126,18 @@ module Dependabot
120
126
  end
121
127
  end
122
128
 
129
+ sig { returns(T.nilable(Dependabot::Package::ReleaseCooldownOptions)) }
130
+ def update_cooldown
131
+ cooldown_options_builder.release_cooldown_options(@update_cooldown)
132
+ end
133
+
123
134
  sig { override.returns(T::Array[T::Hash[String, String]]) }
124
135
  def conflicting_dependencies
125
136
  ConflictingDependencyResolver.new(
126
137
  dependency_files: dependency_files,
127
138
  repo_contents_path: repo_contents_path,
128
139
  credentials: credentials,
129
- options: options
140
+ options: native_bundler_options
130
141
  ).conflicting_dependencies(
131
142
  dependency: dependency,
132
143
  target_version: lowest_security_fix_version.to_s # Convert Version to String
@@ -135,6 +146,36 @@ module Dependabot
135
146
 
136
147
  private
137
148
 
149
+ # Options passed to the native Bundler subprocess. The security-update signal is
150
+ # derived from the advisories so native cooldown is disabled only for security
151
+ # remediation; see CooldownOptionsBuilder for the policy.
152
+ sig { returns(T::Hash[Symbol, T.anything]) }
153
+ def native_bundler_options
154
+ @native_bundler_options ||= T.let(
155
+ cooldown_options_builder.native_helper_options(options),
156
+ T.nilable(T::Hash[Symbol, T.anything])
157
+ )
158
+ end
159
+
160
+ sig { returns(CooldownOptionsBuilder) }
161
+ def cooldown_options_builder
162
+ @cooldown_options_builder ||= T.let(
163
+ CooldownOptionsBuilder.new(
164
+ dependency_files: dependency_files,
165
+ security_advisories: security_advisories
166
+ ),
167
+ T.nilable(CooldownOptionsBuilder)
168
+ )
169
+ end
170
+
171
+ # Bundler's Gemfile `source cooldown:` applies only to RubyGems remotes, so a
172
+ # git dependency's tag selection/resolvability must use the user-configured
173
+ # cooldown without the RubyGems source floor added by CooldownOptionsBuilder.
174
+ sig { returns(T.nilable(Dependabot::Package::ReleaseCooldownOptions)) }
175
+ def git_dependency_cooldown
176
+ @update_cooldown
177
+ end
178
+
138
179
  sig { returns(T::Boolean) }
139
180
  def requirements_unlocked?
140
181
  dependency.specific_requirements.none?
@@ -190,7 +231,7 @@ module Dependabot
190
231
  target_version: version,
191
232
  requirements_update_strategy: T.must(requirements_update_strategy),
192
233
  update_multiple_dependencies: false,
193
- options: options
234
+ options: native_bundler_options
194
235
  ).updated_dependencies
195
236
  true
196
237
  rescue Dependabot::DependencyFileNotResolvable
@@ -213,8 +254,8 @@ module Dependabot
213
254
  ignored_versions: ignored_versions,
214
255
  raise_on_ignored: raise_on_ignored,
215
256
  replacement_git_pin: tag,
216
- cooldown_options: update_cooldown,
217
- options: options
257
+ cooldown_options: git_dependency_cooldown,
258
+ options: native_bundler_options
218
259
  ).latest_resolvable_version_details
219
260
  true
220
261
  rescue Dependabot::DependencyFileNotResolvable
@@ -255,7 +296,7 @@ module Dependabot
255
296
  # If the dependency is pinned to a tag that looks like a version then
256
297
  # we want to update that tag. The latest version will then be the SHA
257
298
  # of the latest tag that looks like a version.
258
- latest_tag = git_commit_checker.local_tag_for_pinned_version_ref(update_cooldown)
299
+ latest_tag = git_commit_checker.local_tag_for_pinned_version_ref(git_dependency_cooldown)
259
300
  return latest_tag.fetch(:tag_sha) || dependency.version if latest_tag
260
301
 
261
302
  # If the dependency is pinned to a tag that doesn't look like a
@@ -279,7 +320,7 @@ module Dependabot
279
320
  # we want to update that tag. The latest version will then be the SHA
280
321
  # of the latest tag that looks like a version.
281
322
  if latest_git_tag_is_resolvable?
282
- new_tag = git_commit_checker.local_tag_for_pinned_version_ref(update_cooldown)
323
+ new_tag = git_commit_checker.local_tag_for_pinned_version_ref(git_dependency_cooldown)
283
324
  return new_tag&.fetch(:tag_sha)
284
325
  end
285
326
 
@@ -314,7 +355,7 @@ module Dependabot
314
355
 
315
356
  sig { returns(T::Boolean) }
316
357
  def latest_git_tag_is_resolvable?
317
- latest_tag_details = git_commit_checker.local_tag_for_pinned_version_ref(update_cooldown)
358
+ latest_tag_details = git_commit_checker.local_tag_for_pinned_version_ref(git_dependency_cooldown)
318
359
  return false unless latest_tag_details
319
360
 
320
361
  git_tag_resolvable?(latest_tag_details.fetch(:tag))
@@ -334,7 +375,7 @@ module Dependabot
334
375
 
335
376
  # Update the git tag if updating a pinned version
336
377
  if latest_git_tag_is_resolvable?
337
- new_tag = git_commit_checker.local_tag_for_pinned_version_ref(update_cooldown)
378
+ new_tag = git_commit_checker.local_tag_for_pinned_version_ref(git_dependency_cooldown)
338
379
  return T.must(dependency_source_details).merge(ref: T.must(new_tag).fetch(:tag))
339
380
  end
340
381
 
@@ -363,7 +404,7 @@ module Dependabot
363
404
  credentials: credentials,
364
405
  target_version: T.cast(latest_version, Dependabot::Version),
365
406
  requirements_update_strategy: T.must(requirements_update_strategy),
366
- options: options
407
+ options: native_bundler_options
367
408
  )
368
409
  end
369
410
 
@@ -398,7 +439,7 @@ module Dependabot
398
439
  unlock_requirement: unlock_requirement,
399
440
  latest_allowable_version: latest_version,
400
441
  cooldown_options: update_cooldown,
401
- options: options
442
+ options: native_bundler_options
402
443
  )
403
444
  end
404
445
 
@@ -420,7 +461,7 @@ module Dependabot
420
461
  raise_on_ignored: raise_on_ignored,
421
462
  security_advisories: security_advisories,
422
463
  cooldown_options: update_cooldown,
423
- options: options
464
+ options: native_bundler_options
424
465
  )
425
466
  end
426
467
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.387.0
4
+ version: 0.388.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.387.0
18
+ version: 0.388.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.387.0
25
+ version: 0.388.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: parallel
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -340,6 +340,7 @@ files:
340
340
  - lib/dependabot/bundler/requirement.rb
341
341
  - lib/dependabot/bundler/update_checker.rb
342
342
  - lib/dependabot/bundler/update_checker/conflicting_dependency_resolver.rb
343
+ - lib/dependabot/bundler/update_checker/cooldown_options_builder.rb
343
344
  - lib/dependabot/bundler/update_checker/file_preparer.rb
344
345
  - lib/dependabot/bundler/update_checker/force_updater.rb
345
346
  - lib/dependabot/bundler/update_checker/latest_version_finder.rb
@@ -353,7 +354,7 @@ licenses:
353
354
  - MIT
354
355
  metadata:
355
356
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
356
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.387.0
357
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.388.0
357
358
  rdoc_options: []
358
359
  require_paths:
359
360
  - lib