dependabot-helm 0.384.0 → 0.386.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: 4c0ab4fed176ce9abcd0eaab214a521878aa723982aaf381767f4140520a64c3
4
- data.tar.gz: 4dd796cacde29cc8c1ce1f93fbfdeecb9c3b72844a0241ccf5af839409a9d606
3
+ metadata.gz: 4d830487e84adfa0aa822028c60564e4052db9bf39d261bf776df1b4214d62bf
4
+ data.tar.gz: c8677544088532a69e90f056281c28578f279e826ddc624f98b80709563212c3
5
5
  SHA512:
6
- metadata.gz: '05068c2c0d1c5d056b8abf741c0a56276b947cad741b6814ff1acad06086529d70d78a486089870d29d01afddf8f7acc834e0dfb29e4dacba23af8fa0eb5ea6f'
7
- data.tar.gz: b8d45216f93b97f496cfd362d57600abbf1d58a070bcc76dba64d217cb2c5892d3754eadc422f8807d207d7083852389969b470c03a92e067161dc95e2d6cafb
6
+ metadata.gz: bf871f1010bf02f2e455046ea4e076e8a433c47f11a043ff95e82975d5ff19584b9a8cc8a99eeb1f64c71489665b6da669d37e19c747bbebf4c162d3e7802fff
7
+ data.tar.gz: 06206be3e63e348226371f69a1f1ede06c14402f3648867dd9749d4bedc749a6b75e63a230ad14d15e47c0d8eb3a61c933c658165620f6c61a6316c5eb191cb5
@@ -39,13 +39,13 @@ module Dependabot
39
39
 
40
40
  sig do
41
41
  params(
42
- yaml: T::Hash[T.untyped, T.untyped],
42
+ yaml: T::Hash[String, Object],
43
43
  chart_file: Dependabot::DependencyFile,
44
44
  dependency_set: DependencySet
45
45
  ).void
46
46
  end
47
47
  def parse_dependencies(yaml, chart_file, dependency_set)
48
- yaml["dependencies"].each do |dep|
48
+ T.cast(yaml["dependencies"], T::Array[Object]).each do |dep|
49
49
  next unless dep.is_a?(Hash) && dep["name"] && dep["version"]
50
50
 
51
51
  parsed_line = {
@@ -95,7 +95,7 @@ module Dependabot
95
95
  next unless yaml.is_a?(Hash)
96
96
 
97
97
  find_images_in_hash(yaml).each do |image_details|
98
- parsed_line = extract_image_details(image_details[:image])
98
+ parsed_line = extract_image_details(T.must(image_details[:image]))
99
99
  next unless parsed_line
100
100
 
101
101
  version = version_from(parsed_line)
@@ -132,7 +132,7 @@ module Dependabot
132
132
  params(
133
133
  key: String,
134
134
  value: String,
135
- hash: T::Hash[T.untyped, T.untyped],
135
+ hash: T::Hash[String, Object],
136
136
  current_path: T::Array[String]
137
137
  ).returns(T::Array[T::Hash[Symbol, String]])
138
138
  end
@@ -153,7 +153,7 @@ module Dependabot
153
153
  end
154
154
 
155
155
  sig do
156
- params(value: T::Array[T.untyped], current_path: T::Array[String]).returns(T::Array[T::Hash[Symbol, String]])
156
+ params(value: T::Array[Object], current_path: T::Array[String]).returns(T::Array[T::Hash[Symbol, String]])
157
157
  end
158
158
  def handle_array_value(value, current_path)
159
159
  images = []
@@ -163,9 +163,10 @@ module Dependabot
163
163
  images
164
164
  end
165
165
 
166
- sig { params(hash: T.untyped, path: T.untyped).returns(T::Array[T.untyped]) }
166
+ sig { params(hash: Object, path: T::Array[String]).returns(T::Array[T::Hash[Symbol, String]]) }
167
167
  def find_images_in_hash(hash, path = [])
168
- images = []
168
+ images = T.let([], T::Array[T::Hash[Symbol, String]])
169
+ return images unless hash.is_a?(Hash)
169
170
 
170
171
  hash.each do |key, value|
171
172
  current_path = path + [key.to_s]
@@ -23,16 +23,16 @@ module Dependabot
23
23
  @dependency = dependency
24
24
  end
25
25
 
26
+ # Returns the file content with this dependency's entries rewritten. May
27
+ # return the content unchanged (e.g. a strategy that leaves an in-range
28
+ # constraint alone, or a dependency not present in this file); the file
29
+ # updater decides whether a file actually changed.
26
30
  sig { params(file: Dependabot::DependencyFile).returns(T.nilable(String)) }
27
31
  def updated_chart_yaml_content(file)
28
32
  content = file.content
29
33
  yaml_obj = YAML.safe_load(T.must(content))
30
34
 
31
- content = update_chart_dependencies(T.must(content), yaml_obj, file)
32
-
33
- raise "Expected content to change!" if content == file.content
34
-
35
- content
35
+ update_chart_dependencies(T.must(content), yaml_obj, file)
36
36
  end
37
37
 
38
38
  private
@@ -43,31 +43,91 @@ module Dependabot
43
43
  sig do
44
44
  params(
45
45
  content: String,
46
- yaml_obj: T::Hash[T.untyped, T.untyped],
46
+ yaml_obj: T::Hash[String, Object],
47
47
  file: Dependabot::DependencyFile
48
48
  ).returns(String)
49
49
  end
50
50
  def update_chart_dependencies(content, yaml_obj, file)
51
- if update_chart_dependency?(file) && yaml_obj["dependencies"]
52
- yaml_obj["dependencies"].each do |dep|
53
- next unless dep["name"] == dependency.name
54
-
55
- old_version = dep["version"].to_s
56
- new_version = dependency.version
57
-
58
- pattern = /
59
- (\s+-\s+name:\s+#{Regexp.escape(dependency.name)}.*?\n\s+)
60
- (version:\s+)
61
- ["']?#{Regexp.escape(old_version)}["']?
62
- /mx
63
- content = content.gsub(pattern) do |match|
64
- match.gsub(/version: ["']?#{Regexp.escape(old_version)}["']?/, "version: #{new_version}")
65
- end
51
+ return content unless update_chart_dependency?(file) && yaml_obj["dependencies"]
52
+
53
+ # Rewrite each entry once, scanning forward so repeated occurrences of
54
+ # the same chart update independently. A whole-file gsub per entry can
55
+ # alias — after entry A is rewritten to entry B's old version, B's pass
56
+ # would re-match the just-updated A.
57
+ cursor = 0
58
+ T.cast(yaml_obj["dependencies"], T::Array[T::Hash[String, Object]]).each do |dep|
59
+ next unless dep["name"] == dependency.name
60
+
61
+ old_version = dep["version"].to_s
62
+ new_requirement = updated_requirement_string(file, old_version) || dependency.version.to_s
63
+ # This occurrence's constraint is unchanged (the strategy left it
64
+ # alone) nothing to write for it.
65
+ next if new_requirement == old_version
66
+
67
+ new_content, cursor = replace_next_entry_version(
68
+ content,
69
+ cursor,
70
+ old_version,
71
+ yaml_safe_value(new_requirement)
72
+ )
73
+ # A changed constraint that produced no textual edit means the entry
74
+ # wasn't matched (e.g. an unusual name/version layout). Surface it
75
+ # rather than silently emitting a partial update.
76
+ if new_content == content
77
+ raise "Expected to update #{dependency.name} from #{old_version} to " \
78
+ "#{new_requirement} in #{file.name}, but no matching entry was found"
66
79
  end
80
+
81
+ content = new_content
67
82
  end
68
83
  content
69
84
  end
70
85
 
86
+ # Replaces this chart's next `version:` occurrence (at/after cursor) with
87
+ # new_version, returning the updated content and the position just past
88
+ # the rewrite so later entries match their own line. The name may be
89
+ # quoted; returns the content unchanged when no entry matches.
90
+ sig do
91
+ params(content: String, cursor: Integer, old_version: String, new_version: String)
92
+ .returns([String, Integer])
93
+ end
94
+ def replace_next_entry_version(content, cursor, old_version, new_version)
95
+ pattern = /
96
+ (\s+-\s+name:\s+["']?#{Regexp.escape(dependency.name)}["']?.*?\n\s+version:\s+)
97
+ ["']?#{Regexp.escape(old_version)}["']?
98
+ /mx
99
+ match = pattern.match(content, cursor)
100
+ return [content, cursor] unless match
101
+
102
+ rewritten = "#{match[1]}#{new_version}"
103
+ updated = T.must(content[0...match.begin(0)]) + rewritten + T.must(content[match.end(0)..])
104
+ [updated, match.begin(0) + rewritten.length]
105
+ end
106
+
107
+ # Wrap a requirement in double quotes when it would otherwise be
108
+ # ambiguous as a YAML plain scalar: any whitespace, or a leading
109
+ # YAML-indicator character (">", "<", "~", "|", etc.). Simple values
110
+ # (exact, caret) are left bare, preserving the previous output.
111
+ sig { params(value: String).returns(String) }
112
+ def yaml_safe_value(value)
113
+ return "\"#{value}\"" if value.match?(/\s/) || value.match?(/\A[>~<|&*!%@?:#,\[\]{}]/)
114
+
115
+ value
116
+ end
117
+
118
+ # The strategy-updated requirement string for a specific chart entry,
119
+ # matched by the entry's authored version (source[:tag]) so repeated
120
+ # occurrences of the same chart name each get their own update. Falls
121
+ # back to the first requirement for the file, then to nil (exact pin).
122
+ sig { params(file: Dependabot::DependencyFile, old_version: String).returns(T.nilable(String)) }
123
+ def updated_requirement_string(file, old_version)
124
+ reqs = dependency.requirements.select do |r|
125
+ r[:file] == file.name && r.dig(:metadata, :type) == :helm_chart
126
+ end
127
+ req = reqs.find { |r| r.dig(:source, :tag) == old_version } || reqs.first
128
+ req && req[:requirement]
129
+ end
130
+
71
131
  sig { params(file: Dependabot::DependencyFile).returns(T::Boolean) }
72
132
  def update_chart_dependency?(file)
73
133
  reqs = dependency.requirements.select { |r| r[:file] == file.name }
@@ -47,6 +47,11 @@ module Dependabot
47
47
 
48
48
  if file.name.match?(CHART_YAML_REGEXP)
49
49
  updated_content = chart_updater.updated_chart_yaml_content(file)
50
+ # A dependency may span several files/entries; when a strategy leaves
51
+ # this file's constraint unchanged (e.g. an already-in-range range),
52
+ # skip it rather than emit an identical file (which would raise).
53
+ next if updated_content == file.content
54
+
50
55
  updated_files << updated_file(
51
56
  file: file,
52
57
  content: T.must(updated_content)
@@ -0,0 +1,218 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/requirement"
7
+ require "dependabot/utils"
8
+ require "dependabot/helm/version"
9
+
10
+ module Dependabot
11
+ module Helm
12
+ # Parses Helm Chart.yaml SemVer range constraints (^, ~, x-ranges, hyphen
13
+ # ranges, || OR, space-AND and comma-AND). Adapted from
14
+ # Dependabot::NpmAndYarn::Requirement: Helm uses the same SemVer family
15
+ # (Masterminds), unlike the Gem-style Docker::Requirement registered for the
16
+ # docker-image path.
17
+ #
18
+ # NOTE: this class is intentionally NOT registered as the requirement class
19
+ # for "helm" (that remains Docker::Requirement, used by the docker-image
20
+ # path and ignore conditions). It is used explicitly by the range-preserving
21
+ # requirements updater.
22
+ class Requirement < Dependabot::Requirement
23
+ extend T::Sig
24
+
25
+ AND_SEPARATOR = T.let(/(?<=[a-zA-Z0-9*])\s+(?:&+\s+)?(?!\s*[|-])/, Regexp)
26
+ OR_SEPARATOR = T.let(/(?<=[a-zA-Z0-9*])\s*\|+/, Regexp)
27
+
28
+ # Allow an optional 'v' prefix and an optional '+<build metadata/digest>'
29
+ # suffix. Helm::Version supports the latter (e.g. "1.0.119807+<digest>"
30
+ # from OCI charts), so the requirement parser must accept it too or
31
+ # constraints containing it raise BadRequirementError.
32
+ quoted = OPS.keys.map { |k| Regexp.quote(k) }.join("|")
33
+ version_pattern = "v?#{Gem::Version::VERSION_PATTERN}(?:\\+[0-9A-Za-z\\-.]+)?"
34
+
35
+ PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze, String)
36
+ PATTERN = T.let(/\A#{PATTERN_RAW}\z/, Regexp)
37
+
38
+ # Always returns a Helm::Version (never the plain Gem::Version-backed
39
+ # DefaultRequirement), so satisfaction comparisons stay Helm::Version vs
40
+ # Helm::Version — Helm::Version#<=> has a strict sig that rejects plain
41
+ # Gem::Version operands.
42
+ sig do
43
+ params(obj: T.any(String, Gem::Version))
44
+ .returns(T::Array[T.any(String, Dependabot::Version)])
45
+ end
46
+ def self.parse(obj)
47
+ return ["=", Helm::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
48
+
49
+ unless (matches = PATTERN.match(obj.to_s))
50
+ raise BadRequirementError, "Illformed requirement [#{obj.inspect}]"
51
+ end
52
+
53
+ [matches[1] || "=", Helm::Version.new(T.must(matches[2]))]
54
+ end
55
+
56
+ # Returns an array of requirements. At least one requirement from the
57
+ # returned array must be satisfied for a version to be valid.
58
+ sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Requirement]) }
59
+ def self.requirements_array(requirement_string)
60
+ return [new(nil)] if requirement_string.nil?
61
+
62
+ # Parentheses are extremely rare in Helm constraints; strip them.
63
+ requirement_string = requirement_string.gsub(/[()]/, "")
64
+ requirement_string.strip.split(OR_SEPARATOR).map do |req_string|
65
+ new(req_string.strip.split(AND_SEPARATOR))
66
+ end
67
+ end
68
+
69
+ sig { params(requirements: T.nilable(T.any(String, T::Array[String]))).void }
70
+ def initialize(*requirements)
71
+ requirements = requirements.flatten.compact
72
+ # `new(nil)` (from `requirements_array(nil)`) means "match anything".
73
+ # Use a literal ">= 0" so it flows through our `parse` and is backed by a
74
+ # Helm::Version — Gem's DefaultRequirement uses a plain Gem::Version, which
75
+ # Helm::Version#<=>'s strict sig would reject.
76
+ requirements = [">= 0"] if requirements.empty?
77
+
78
+ requirements = requirements.flat_map { |req_string| req_string.split(",").map(&:strip) }
79
+ .flat_map { |req_string| convert_helm_constraint_to_ruby_constraint(req_string) }
80
+
81
+ super
82
+ end
83
+
84
+ private
85
+
86
+ sig { params(req_string: String).returns(T.any(String, T::Array[String])) }
87
+ def convert_helm_constraint_to_ruby_constraint(req_string)
88
+ # Leave dist-tags / non-numeric leading tokens untouched.
89
+ return req_string if req_string.match?(/^([A-Za-uw-z]|v[^\d])/)
90
+
91
+ # Wildcards combined with an operator need dedicated handling before the
92
+ # generic wildcard strip below (which would corrupt them).
93
+ wildcard = wildcard_ruby_constraint(req_string)
94
+ return wildcard if wildcard
95
+
96
+ dispatch_ruby_constraint(req_string.gsub(/(?:\.|^)[xX*]/, ""))
97
+ end
98
+
99
+ # Dispatches a wildcard-stripped constraint token to its converter.
100
+ sig { params(req_string: String).returns(T.any(String, T::Array[String])) }
101
+ def dispatch_ruby_constraint(req_string)
102
+ if req_string.empty? then ">= 0"
103
+ elsif req_string.start_with?("~>") then req_string
104
+ elsif req_string.start_with?("=") then req_string.gsub(/^=*/, "")
105
+ elsif req_string.start_with?("~") then convert_tilde_req(req_string)
106
+ elsif req_string.start_with?("^") then convert_caret_req(req_string)
107
+ elsif req_string.include?(" - ") then convert_hyphen_req(req_string)
108
+ elsif req_string.match?(/[<>]/) then req_string
109
+ else ruby_range(req_string)
110
+ end
111
+ end
112
+
113
+ # Handles wildcard constraints that a leading operator would otherwise
114
+ # corrupt. Returns nil when there is no operator+wildcard to expand.
115
+ # - "<=1.x"/">1.2.x": expand from the wildcard's [floor, ceiling) span
116
+ # (stripping the wildcard first would turn "<=1.x" into "<=1", which
117
+ # wrongly rejects 1.5.0 that Masterminds accepts).
118
+ # - "*"/"^*"/"~*": a bare wildcard means "any version"; any other
119
+ # conversion yields an empty bound.
120
+ sig { params(req_string: String).returns(T.nilable(String)) }
121
+ def wildcard_ruby_constraint(req_string)
122
+ if (m = req_string.match(/\A(<=|>=|<|>)\s*(\d+(?:\.\d+)*)\.[xX*]\z/))
123
+ return convert_wildcard_comparator(T.must(m[1]), T.must(m[2]))
124
+ end
125
+
126
+ return ">= 0" if req_string.match?(/\A[\^~<>=\s]*[xX*]+\z/)
127
+
128
+ nil
129
+ end
130
+
131
+ # Expands a comparator applied to a wildcard version ("1.x" spans
132
+ # [1.0.0, 2.0.0); "1.2.x" spans [1.2.0, 1.3.0)) per Masterminds semantics:
133
+ # >= X.x -> >= floor > X.x -> >= ceiling
134
+ # < X.x -> < floor <= X.x -> < ceiling
135
+ sig { params(operator: String, prefix: String).returns(String) }
136
+ def convert_wildcard_comparator(operator, prefix)
137
+ parts = prefix.split(".").map(&:to_i)
138
+ floor = (parts + [0, 0, 0]).first(3)
139
+ ceiling_parts = parts.dup
140
+ ceiling_parts[-1] = T.must(ceiling_parts[-1]) + 1
141
+ ceiling = (ceiling_parts + [0, 0, 0]).first(3)
142
+
143
+ case operator
144
+ when ">=" then ">= #{floor.join('.')}"
145
+ when ">" then ">= #{ceiling.join('.')}"
146
+ when "<" then "< #{floor.join('.')}"
147
+ else "< #{ceiling.join('.')}" # "<="
148
+ end
149
+ end
150
+
151
+ sig { params(req_string: String).returns(String) }
152
+ def convert_tilde_req(req_string)
153
+ version = req_string.gsub(/^~\>?[\s=]*/, "")
154
+ parts = version.split(".")
155
+ parts << "0" if parts.count < 3
156
+ "~> #{parts.join('.')}"
157
+ end
158
+
159
+ sig { params(req_string: String).returns(T::Array[String]) }
160
+ def convert_hyphen_req(req_string)
161
+ parts = req_string.split(/\s+-\s+/)
162
+ lower_bound = T.must(parts[0])
163
+ upper_bound = T.must(parts[1])
164
+ lower_bound_parts = lower_bound.split(".")
165
+ lower_bound_parts.fill("0", lower_bound_parts.length...3)
166
+
167
+ upper_bound_parts = upper_bound.split(".")
168
+ upper_bound_range =
169
+ if upper_bound_parts.length < 3
170
+ # When upper bound is a partial version treat these as an X-range:
171
+ # "1.0 - 2.0" includes the whole 2.0.x series, so always increment
172
+ # the last specified component (even when it is 0) before padding —
173
+ # otherwise the bound becomes "< 2.0.0.a" and excludes 2.0.0.
174
+ upper_bound_parts[-1] = (T.must(upper_bound_parts[-1]).to_i + 1).to_s
175
+ upper_bound_parts.fill("0", upper_bound_parts.length...3)
176
+ "< #{upper_bound_parts.join('.')}.a"
177
+ else
178
+ "<= #{upper_bound_parts.join('.')}"
179
+ end
180
+
181
+ [">= #{lower_bound_parts.join('.')}", upper_bound_range]
182
+ end
183
+
184
+ sig { params(req_string: String).returns(String) }
185
+ def ruby_range(req_string)
186
+ parts = req_string.split(".")
187
+ # If we have three or more parts then this is an exact match
188
+ return req_string if parts.count >= 3
189
+
190
+ # If we have fewer than three parts we do a partial match
191
+ parts << "0"
192
+ "~> #{parts.join('.')}"
193
+ end
194
+
195
+ sig { params(req_string: String).returns(T::Array[String]) }
196
+ def convert_caret_req(req_string)
197
+ version = req_string.gsub(/^\^[\s=]*/, "")
198
+ parts = version.split(".")
199
+ parts.fill("x", parts.length...3)
200
+ first_non_zero = parts.find { |d| d != "0" }
201
+ first_non_zero_index =
202
+ first_non_zero ? T.must(parts.index(first_non_zero)) : parts.count - 1
203
+ # If the requirement has a blank minor or patch version increment the
204
+ # previous index value with 1
205
+ first_non_zero_index -= 1 if first_non_zero == "x"
206
+ upper_bound = parts.map.with_index do |part, i|
207
+ if i < first_non_zero_index then part
208
+ elsif i == first_non_zero_index then (part.to_i + 1).to_s
209
+ elsif i > first_non_zero_index && i == 2 then "0.a"
210
+ else "0"
211
+ end
212
+ end.join(".")
213
+
214
+ [">= #{version}", "< #{upper_bound}"]
215
+ end
216
+ end
217
+ end
218
+ end
@@ -49,11 +49,11 @@ module Dependabot
49
49
 
50
50
  sig do
51
51
  params(
52
- versions: T::Array[T::Hash[String, T.untyped]],
52
+ versions: T::Array[T::Hash[String, Object]],
53
53
  repo_name: T.nilable(String),
54
54
  chart_name: T.nilable(String)
55
55
  )
56
- .returns(T::Array[T::Hash[String, T.untyped]])
56
+ .returns(T::Array[T::Hash[String, Object]])
57
57
  end
58
58
  def fetch_tag_and_release_date_helm_chart(versions, repo_name, chart_name)
59
59
  Dependabot.logger.info("Filtering versions in cooldown period from chart: #{repo_name}")
@@ -0,0 +1,273 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ ################################################################################
5
+ # Helm Chart.yaml dependency constraints use SemVer ranges (Masterminds), the #
6
+ # same family npm uses. This mirrors #
7
+ # Dependabot::NpmAndYarn::UpdateChecker::RequirementsUpdater, scoped to the #
8
+ # three strategies Helm supports, with the git-source / JSR branches removed. #
9
+ ################################################################################
10
+
11
+ require "sorbet-runtime"
12
+
13
+ require "dependabot/dependency_requirement"
14
+ require "dependabot/helm/requirement"
15
+ require "dependabot/helm/update_checker"
16
+ require "dependabot/helm/version"
17
+ require "dependabot/requirements_update_strategy"
18
+
19
+ module Dependabot
20
+ module Helm
21
+ class UpdateChecker
22
+ class RequirementsUpdater
23
+ extend T::Sig
24
+
25
+ # Possessive quantifiers (++) keep these linear on pathological input
26
+ # (no catastrophic/polynomial backtracking) while matching identically
27
+ # to the greedy forms for real version constraints. The trailing group
28
+ # consumes an optional '+<build metadata/digest>' suffix so rewrites
29
+ # replace it wholesale instead of leaving a stale suffix behind
30
+ # (e.g. "1.2.3+old" -> "1.5.0", not "1.5.0+old").
31
+ VERSION_REGEX = /[0-9]++(?:\.[A-Za-z0-9\-_]++)*+(?:\+[A-Za-z0-9\-_.]++)?/
32
+ SEPARATOR = /(?<=[a-zA-Z0-9*])[\s|]++(?![\s|-])/
33
+ # The version attached to an upper comparator (`<`/`<=`) or a hyphen
34
+ # endpoint — the bound that widening moves.
35
+ UPPER_COMPARATOR_REGEX = /<=?\s*(#{VERSION_REGEX})/
36
+ HYPHEN_UPPER_REGEX = /\s-\s+(#{VERSION_REGEX})/
37
+ ALLOWED_UPDATE_STRATEGIES = T.let(
38
+ [
39
+ RequirementsUpdateStrategy::LockfileOnly,
40
+ RequirementsUpdateStrategy::WidenRanges,
41
+ RequirementsUpdateStrategy::BumpVersions,
42
+ RequirementsUpdateStrategy::BumpVersionsIfNecessary
43
+ ].freeze,
44
+ T::Array[Dependabot::RequirementsUpdateStrategy]
45
+ )
46
+
47
+ sig do
48
+ params(
49
+ requirements: T::Array[Dependabot::DependencyRequirement],
50
+ update_strategy: Dependabot::RequirementsUpdateStrategy,
51
+ latest_resolvable_version: T.nilable(T.any(String, Gem::Version))
52
+ ).void
53
+ end
54
+ def initialize(requirements:, update_strategy:, latest_resolvable_version:)
55
+ @requirements = requirements
56
+ @update_strategy = update_strategy
57
+
58
+ check_update_strategy
59
+
60
+ return unless latest_resolvable_version
61
+
62
+ @latest_resolvable_version = T.let(
63
+ version_class.new(latest_resolvable_version),
64
+ Dependabot::Version
65
+ )
66
+ end
67
+
68
+ sig { returns(T::Array[Dependabot::DependencyRequirement]) }
69
+ def updated_requirements
70
+ return requirements if update_strategy.lockfile_only?
71
+
72
+ requirements.map do |req|
73
+ next req unless latest_resolvable_version
74
+ next req unless req[:requirement]
75
+ # Leave dist-tags / non-numeric leading tokens untouched.
76
+ next req if req[:requirement].match?(/^([A-Za-uw-z]|v[^\d])/)
77
+
78
+ case update_strategy
79
+ when RequirementsUpdateStrategy::WidenRanges then widen_requirement(req)
80
+ when RequirementsUpdateStrategy::BumpVersions then update_version_requirement(req)
81
+ when RequirementsUpdateStrategy::BumpVersionsIfNecessary
82
+ update_version_requirement_if_needed(req)
83
+ else raise "Unexpected update strategy: #{update_strategy}"
84
+ end
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ sig { returns(T::Array[Dependabot::DependencyRequirement]) }
91
+ attr_reader :requirements
92
+
93
+ sig { returns(Dependabot::RequirementsUpdateStrategy) }
94
+ attr_reader :update_strategy
95
+
96
+ sig { returns(T.nilable(Dependabot::Version)) }
97
+ attr_reader :latest_resolvable_version
98
+
99
+ sig { void }
100
+ def check_update_strategy
101
+ return if ALLOWED_UPDATE_STRATEGIES.include?(update_strategy)
102
+
103
+ raise "Unknown update strategy: #{update_strategy}"
104
+ end
105
+
106
+ sig { params(req: Dependabot::DependencyRequirement).returns(Dependabot::DependencyRequirement) }
107
+ def update_version_requirement(req)
108
+ current_requirement = req[:requirement]
109
+ return req if current_requirement.strip == ""
110
+
111
+ if current_requirement.match?(/(<|-\s)/i)
112
+ # Check every OR alternative, not just the first — a later branch may
113
+ # already permit the latest version.
114
+ return req if ruby_requirements(current_requirement).any? { |r| r.satisfied_by?(latest_resolvable_version) }
115
+
116
+ updated_req = update_range_requirement(current_requirement)
117
+ return T.cast(req.merge(requirement: updated_req), Dependabot::DependencyRequirement)
118
+ end
119
+
120
+ # Strict lower-bound (`>`) and not-equal (`!=`) constraints are already
121
+ # permitted by the resolved version, so leave them untouched. Rewriting
122
+ # the numeric part would exclude the very version we want
123
+ # (`>1.0.0` -> `>2.0.0`, `!=1.0.0` -> `!=2.0.0`) and drop any sibling
124
+ # comparators.
125
+ if current_requirement.match?(/!=|(?<![<>=])>(?!=)/) &&
126
+ ruby_requirements(current_requirement).any? { |r| r.satisfied_by?(latest_resolvable_version) }
127
+ return req
128
+ end
129
+
130
+ reqs = current_requirement.strip.split(SEPARATOR).map(&:strip)
131
+ T.cast(req.merge(requirement: update_version_string(reqs.first)), Dependabot::DependencyRequirement)
132
+ end
133
+
134
+ # Blank, or already permitted by the resolved version, under any strategy.
135
+ sig { params(req: Dependabot::DependencyRequirement).returns(T::Boolean) }
136
+ def already_satisfied?(req)
137
+ current_requirement = req[:requirement]
138
+ return true if current_requirement.strip == ""
139
+
140
+ ruby_requirements(current_requirement).any? { |r| r.satisfied_by?(latest_resolvable_version) }
141
+ end
142
+
143
+ sig { params(req: Dependabot::DependencyRequirement).returns(Dependabot::DependencyRequirement) }
144
+ def update_version_requirement_if_needed(req)
145
+ return req if already_satisfied?(req)
146
+
147
+ update_version_requirement(req)
148
+ end
149
+
150
+ sig { params(req: Dependabot::DependencyRequirement).returns(Dependabot::DependencyRequirement) }
151
+ def widen_requirement(req)
152
+ return req if already_satisfied?(req)
153
+
154
+ current_requirement = req[:requirement]
155
+ reqs = current_requirement.strip.split(SEPARATOR).map(&:strip)
156
+
157
+ updated_requirement =
158
+ if reqs.any? { |r| r.match?(/(<|-\s)/i) }
159
+ update_range_requirement(current_requirement)
160
+ elsif reqs.one?
161
+ update_version_string(current_requirement)
162
+ else
163
+ # An OR of caret/tilde/exact alternatives, none of which permit the
164
+ # latest version: widen by adding a new alternative rather than
165
+ # rewriting the authored ones (npm's widen_ranges behavior).
166
+ "#{current_requirement} || ^#{latest_resolvable_version}"
167
+ end
168
+
169
+ T.cast(req.merge(requirement: updated_requirement), Dependabot::DependencyRequirement)
170
+ end
171
+
172
+ sig { params(requirement_string: String).returns(T::Array[Helm::Requirement]) }
173
+ def ruby_requirements(requirement_string)
174
+ Helm::Requirement.requirements_array(requirement_string)
175
+ end
176
+
177
+ sig { params(req_string: String).returns(String) }
178
+ def update_range_requirement(req_string)
179
+ range_requirements =
180
+ req_string.split(SEPARATOR).select { |r| r.match?(/<|(\s++-\s++)/) }
181
+
182
+ if range_requirements.one?
183
+ range_requirement = T.must(range_requirements.first)
184
+ versions = range_requirement.scan(VERSION_REGEX)
185
+ # The bound to move is the operand of `<`/`<=` (or the hyphen
186
+ # endpoint), NOT the numerically-largest token — a comma-AND `!=`
187
+ # operand can be larger (e.g. "!=9.0.0,<2.0.0"). Substitute the
188
+ # *original* matched text, not the parsed version's normalized
189
+ # #to_s: Helm::Version normalizes "2.0.0-rc1" to "2.0.0", so subbing
190
+ # the normalized form would match the wrong span and leave a stale
191
+ # suffix ("<3.0.0-rc1"). Fall back to the max token only if neither
192
+ # an upper comparator nor a hyphen endpoint is present.
193
+ original_upper = T.cast(
194
+ range_requirement[UPPER_COMPARATOR_REGEX, 1] ||
195
+ range_requirement[HYPHEN_UPPER_REGEX, 1] ||
196
+ T.must(versions.max_by { |v| version_class.new(v.to_s) }),
197
+ String
198
+ )
199
+ new_upper_bound = update_greatest_version(
200
+ version_class.new(original_upper).to_s,
201
+ T.must(latest_resolvable_version)
202
+ )
203
+
204
+ # Anchor the substitution to the upper-bound operator (`<`/`<=`) or
205
+ # hyphen so it can't replace an earlier lower-bound operand that
206
+ # happens to share the same version string.
207
+ req_string.sub(/(<=?\s*|\s-\s+)#{Regexp.escape(original_upper)}/) do
208
+ "#{Regexp.last_match(1)}#{new_upper_bound}"
209
+ end
210
+ else
211
+ req_string + " || ^#{T.must(latest_resolvable_version)}"
212
+ end
213
+ end
214
+
215
+ sig { params(req_string: String).returns(String) }
216
+ def update_version_string(req_string)
217
+ latest = T.must(latest_resolvable_version).to_s
218
+ req_string
219
+ .sub(VERSION_REGEX) do |old_version|
220
+ if old_version.match?(/\d-/) || latest.match?(/\d-/)
221
+ latest
222
+ else
223
+ old_parts = old_version.split(".")
224
+ new_parts = latest.split(".")
225
+ # Iterate the authored segments so the constraint's shape is
226
+ # preserved even when the latest version has fewer segments
227
+ # (e.g. "1.2.x" + latest "4.5" -> "4.5.x", not "4.5").
228
+ old_parts.map.with_index do |old, i|
229
+ # Preserve an authored wildcard segment, keeping its case
230
+ # ("1.x" -> "4.x", "1.X" -> "4.X").
231
+ old.match?(/\A[xX]/) ? old : (new_parts[i] || "0")
232
+ end.join(".")
233
+ end
234
+ end
235
+ end
236
+
237
+ sig { params(old_version: String, version_to_be_permitted: Dependabot::Version).returns(String) }
238
+ def update_greatest_version(old_version, version_to_be_permitted)
239
+ version = version_class.new(old_version)
240
+ version = version.release if version.prerelease?
241
+
242
+ index_to_update =
243
+ version.segments.map.with_index { |seg, i| T.cast(seg, Integer).zero? ? 0 : i }.max || 0
244
+
245
+ version.segments.map.with_index do |_, index|
246
+ segment_value =
247
+ if index < index_to_update
248
+ permitted_segment(version_to_be_permitted, index)
249
+ elsif index == index_to_update
250
+ permitted_segment(version_to_be_permitted, index) + 1
251
+ else
252
+ 0
253
+ end
254
+ segment_value.to_s
255
+ end.join(".")
256
+ end
257
+
258
+ # A version segment as an Integer, treating missing trailing segments as
259
+ # 0 (the permitted version may have fewer segments than the old bound).
260
+ sig { params(version: Dependabot::Version, index: Integer).returns(Integer) }
261
+ def permitted_segment(version, index)
262
+ seg = version.segments[index]
263
+ seg.nil? ? 0 : T.cast(seg, Integer)
264
+ end
265
+
266
+ sig { returns(T.class_of(Helm::Version)) }
267
+ def version_class
268
+ Helm::Version
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
@@ -5,7 +5,9 @@ require "sorbet-runtime"
5
5
  require "dependabot/update_checkers"
6
6
  require "dependabot/update_checkers/base"
7
7
  require "dependabot/errors"
8
+ require "dependabot/requirements_update_strategy"
8
9
  require "dependabot/helm/version"
10
+ require "dependabot/helm/requirement"
9
11
  require "dependabot/docker/requirement"
10
12
  require "dependabot/shared/utils/credentials_finder"
11
13
  require "dependabot/shared_helpers"
@@ -16,10 +18,15 @@ require "dependabot/helm/helpers"
16
18
 
17
19
  module Dependabot
18
20
  module Helm
19
- class UpdateChecker < Dependabot::UpdateCheckers::Base
21
+ # ClassLength is disabled to match sibling ecosystems' update checkers
22
+ # (npm_and_yarn and bun disable it on the same class). The version-fetching
23
+ # helpers already delegate to update_checker/latest_version_resolver;
24
+ # further extraction would fragment the update-decision flow.
25
+ class UpdateChecker < Dependabot::UpdateCheckers::Base # rubocop:disable Metrics/ClassLength
20
26
  extend T::Sig
21
27
 
22
28
  require_relative "update_checker/latest_version_resolver"
29
+ require_relative "update_checker/requirements_updater"
23
30
 
24
31
  sig { override.returns(T.nilable(T.any(String, Gem::Version))) }
25
32
  def latest_version
@@ -41,17 +48,116 @@ module Dependabot
41
48
  return dependency.requirements unless latest_version
42
49
 
43
50
  updated_reqs = dependency.requirements.map do |req|
44
- updated_metadata = req.fetch(:metadata).dup
45
- updated_req = req.dup
46
- updated_req[:requirement] = latest_version.to_s if updated_metadata.key?(:type)
47
-
48
- updated_req
51
+ case req.dig(:metadata, :type)
52
+ when nil then req
53
+ when :helm_chart then updated_chart_requirement(req)
54
+ else req.merge(requirement: latest_version.to_s) # image deps: exact overwrite
55
+ end
49
56
  end
50
57
  wrap_requirements(updated_reqs)
51
58
  end
52
59
 
53
60
  private
54
61
 
62
+ # Helm stores each occurrence's constraint in the requirement's
63
+ # source[:tag], not the requirement field (the shared parser leaves it
64
+ # nil). Feed that constraint through the RequirementsUpdater so
65
+ # versioning-strategy is honored. When no strategy is set we default to
66
+ # BumpVersions, which preserves the authored operator and bumps the floor
67
+ # (e.g. `^1.0.0` -> `^1.0.5`); exact pins stay exact (`1.0.0` -> `1.5.0`).
68
+ sig { params(req: Dependabot::DependencyRequirement).returns(Dependabot::DependencyRequirement) }
69
+ def updated_chart_requirement(req)
70
+ current_constraint = chart_constraint_for(req)
71
+ synthetic = T.cast(req.merge(requirement: current_constraint), Dependabot::DependencyRequirement)
72
+
73
+ T.must(
74
+ RequirementsUpdater.new(
75
+ requirements: [synthetic],
76
+ update_strategy: resolved_update_strategy,
77
+ latest_resolvable_version: T.must(latest_version).to_s
78
+ ).updated_requirements.first
79
+ )
80
+ end
81
+
82
+ # The authored constraint for a single requirement. Prefer the
83
+ # requirement's own source[:tag] over dependency.version, since
84
+ # DependencySet may merge several same-named occurrences (different files
85
+ # or ranges) into one dependency with a single combined version.
86
+ sig { params(req: Dependabot::DependencyRequirement).returns(String) }
87
+ def chart_constraint_for(req)
88
+ (req[:requirement] || req.dig(:source, :tag) || dependency.version).to_s
89
+ end
90
+
91
+ sig { returns(Dependabot::RequirementsUpdateStrategy) }
92
+ def resolved_update_strategy
93
+ requirements_update_strategy || RequirementsUpdateStrategy::BumpVersions
94
+ end
95
+
96
+ # Overrides Base#current_version. For chart deps the Chart.yaml constraint
97
+ # lives in dependency.version, which may be a range with no single version;
98
+ # we anchor on its lower bound so candidate filtering has a concrete
99
+ # baseline. Non-chart deps (docker images) keep the base behavior.
100
+ sig { override.returns(T.nilable(Dependabot::Version)) }
101
+ def current_version
102
+ return super unless dependency_type == :helm_chart
103
+
104
+ @current_version ||= T.let(chart_anchor_version, T.nilable(Dependabot::Version))
105
+ end
106
+
107
+ # A concrete version to anchor candidate filtering on. A dependency may
108
+ # merge several same-named occurrences with different constraints, so anchor
109
+ # on the *lowest* occurrence — otherwise a higher-floored occurrence would
110
+ # gate out a release that a lower one still needs updated to.
111
+ sig { returns(Dependabot::Version) }
112
+ def chart_anchor_version
113
+ constraints = dependency.requirements
114
+ .select { |r| r.dig(:metadata, :type) == :helm_chart }
115
+ .map { |r| chart_constraint_for(r) }
116
+ constraints = [dependency.version.to_s] if constraints.empty?
117
+ T.must(constraints.map { |c| anchor_for_constraint(c) }.min)
118
+ end
119
+
120
+ # Anchors a single constraint on a concrete version. Single/lenient forms
121
+ # (^1.0.0, ~1.2.0, 1.0.0) parse directly; comparator/hyphen/OR ranges do
122
+ # not, so anchor on the lowest lower bound the Requirement parser reports
123
+ # (0 when the constraint has no lower bound, e.g. "<2.0.0").
124
+ sig { params(raw: String).returns(Dependabot::Version) }
125
+ def anchor_for_constraint(raw)
126
+ # A comparator/hyphen/OR constraint has no single version, and
127
+ # version_class.new would silently mis-read it ("<2.0.0" -> 2.0.0), so
128
+ # route those to the parser's lower bound.
129
+ return min_bound_anchor(raw) if raw.match?(/[<>]|!=|\s-\s|\|\|/)
130
+
131
+ begin
132
+ version_class.new(raw)
133
+ rescue StandardError
134
+ min_bound_anchor(raw)
135
+ end
136
+ end
137
+
138
+ # The lowest lower-bound version across the constraint's OR branches, or 0
139
+ # when any branch has no lower bound (that branch permits arbitrarily low
140
+ # versions, e.g. "<=2.0.0 || >=10.0.0"). Re-wrapped as a Helm::Version —
141
+ # min_version yields a base Dependabot::Version, but Helm::Version#<=> only
142
+ # accepts Helm operands.
143
+ sig { params(raw: String).returns(Dependabot::Version) }
144
+ def min_bound_anchor(raw)
145
+ floors = Helm::Requirement.requirements_array(raw).map { |req| branch_lower_bound(req) }
146
+ # A nil floor means some OR branch permits arbitrarily low versions
147
+ # (an upper-only or exclusion branch), so anchor at 0.
148
+ return version_class.new("0") if floors.any?(&:nil?)
149
+
150
+ floor = floors.min
151
+ floor ? version_class.new(floor.to_s) : version_class.new("0")
152
+ end
153
+
154
+ # The lowest version an OR branch permits: its >=/>/~> lower bound, or an
155
+ # exact (=) pin's version. Nil when the branch has no lower bound (</<=/!=).
156
+ sig { params(req: Dependabot::Requirement).returns(T.nilable(Gem::Version)) }
157
+ def branch_lower_bound(req)
158
+ req.min_version || req.requirements.filter_map { |op, v| v if op == "=" }.min
159
+ end
160
+
55
161
  sig { override.returns(T::Array[Dependabot::Dependency]) }
56
162
  def updated_dependencies_after_full_unlock
57
163
  raise NotImplementedError
@@ -77,11 +183,20 @@ module Dependabot
77
183
  valid_releases = latest_version_resolver
78
184
  .fetch_tag_and_release_date_helm_chart(valid_releases, repo_name, chart_name)
79
185
  end
80
- highest_release = valid_releases.max_by { |release| version_class.new(release["version"]) }
186
+ highest_release = valid_releases.max_by { |release| version_class.new(T.cast(release["version"], String)) }
81
187
  Dependabot.logger.info(
82
188
  "Found latest version #{T.must(highest_release)['version']} for #{chart_name} using helm search"
83
189
  )
84
- version_class.new(T.must(highest_release)["version"])
190
+ version_class.new(T.cast(T.must(highest_release)["version"], String))
191
+ end
192
+
193
+ sig do
194
+ params(index: T.nilable(T::Hash[String, Object]), chart_name: String)
195
+ .returns(T.nilable(T::Array[T::Hash[String, Object]]))
196
+ end
197
+ def chart_entries_from_index(index, chart_name)
198
+ entries = T.cast(index&.fetch("entries", nil), T.nilable(T::Hash[String, Object]))
199
+ T.cast(entries&.fetch(chart_name, nil), T.nilable(T::Array[T::Hash[String, Object]]))
85
200
  end
86
201
 
87
202
  sig { params(chart_name: String, repo_url: T.nilable(String)).returns(T.nilable(Gem::Version)) }
@@ -91,9 +206,10 @@ module Dependabot
91
206
 
92
207
  index_url = build_index_url(repo_url)
93
208
  index = fetch_helm_chart_index(index_url)
94
- return nil unless index && index["entries"] && index["entries"][chart_name]
209
+ chart_entries = chart_entries_from_index(index, chart_name)
210
+ return nil unless chart_entries
95
211
 
96
- all_versions = index["entries"][chart_name].map { |entry| entry["version"] }
212
+ all_versions = chart_entries.map { |entry| T.cast(entry["version"], String) }
97
213
  Dependabot.logger.info("Found #{all_versions.length} versions for #{chart_name} in index.yaml")
98
214
 
99
215
  valid_versions = filter_valid_versions(all_versions)
@@ -115,12 +231,16 @@ module Dependabot
115
231
  highest_version
116
232
  end
117
233
 
118
- sig { params(releases: T::Array[T::Hash[String, T.untyped]]).returns(T::Array[T::Hash[String, T.untyped]]) }
234
+ sig { params(releases: T::Array[T::Hash[String, Object]]).returns(T::Array[T::Hash[String, Object]]) }
119
235
  def filter_valid_releases(releases)
120
236
  releases.reject do |release|
121
- version_class.new(release["version"]) <= version_class.new(dependency.version) ||
237
+ release_version = version_class.new(T.cast(release["version"], String))
238
+ # Compare against current_version (the anchored floor) rather than
239
+ # dependency.version: for a range constraint (">=1.0.0 <2.0.0") the raw
240
+ # version string isn't a single parseable version.
241
+ release_version <= current_version ||
122
242
  ignore_requirements.any? do |r|
123
- r.instance_of?(Dependabot::Requirement) && r.satisfied_by?(version_class.new(release["version"]))
243
+ r.instance_of?(Dependabot::Requirement) && r.satisfied_by?(release_version)
124
244
  end
125
245
  end
126
246
  end
@@ -139,10 +259,37 @@ module Dependabot
139
259
  end
140
260
 
141
261
  sig { params(requirements_to_unlock: T.nilable(Symbol)).returns(T::Boolean) }
142
- def version_can_update?(requirements_to_unlock:) # rubocop:disable Lint/UnusedMethodArgument
262
+ def version_can_update?(requirements_to_unlock:)
143
263
  return false unless latest_version
264
+ # Non-chart deps (docker images) keep the base behavior — including its
265
+ # nilable current_version handling.
266
+ return super unless dependency_type == :helm_chart
267
+
268
+ return false unless version_class.new(latest_version.to_s) > T.must(current_version)
269
+
270
+ # For a chart dependency, an "update" means the authored Chart.yaml
271
+ # constraint actually changes. The RequirementsUpdater deliberately
272
+ # leaves some constraints untouched (an in-range comparator/hyphen range,
273
+ # or an OR range already satisfied by the latest version), so gate on
274
+ # whether it would produce a different constraint. Otherwise can_update?
275
+ # promises a change the file updater can't make, and it raises
276
+ # "Expected content to change!" on the unchanged Chart.yaml.
277
+ chart_requirement_changes?
278
+ end
144
279
 
145
- version_class.new(latest_version.to_s) > version_class.new(dependency.version)
280
+ # Whether running the authored chart constraint through the
281
+ # RequirementsUpdater yields a different constraint string for *any*
282
+ # requirement. A dependency may carry several requirements (same chart
283
+ # name across files/entries); an update is warranted if even one changes.
284
+ sig { returns(T::Boolean) }
285
+ def chart_requirement_changes?
286
+ chart_reqs = dependency.requirements.select { |r| r.dig(:metadata, :type) == :helm_chart }
287
+ chart_reqs = dependency.requirements if chart_reqs.empty?
288
+ return true if chart_reqs.empty?
289
+
290
+ chart_reqs.any? do |req|
291
+ updated_chart_requirement(req)[:requirement].to_s != chart_constraint_for(req)
292
+ end
146
293
  end
147
294
 
148
295
  sig { returns(T.nilable(T.any(String, Gem::Version))) }
@@ -170,7 +317,7 @@ module Dependabot
170
317
  chart_name: String,
171
318
  repo_name: T.nilable(String),
172
319
  repo_url: T.nilable(String)
173
- ).returns(T.nilable(T::Array[T::Hash[String, T.untyped]]))
320
+ ).returns(T.nilable(T::Array[T::Hash[String, Object]]))
174
321
  end
175
322
  def fetch_chart_releases(chart_name, repo_name = nil, repo_url = nil)
176
323
  Dependabot.logger.info("Fetching releases for Helm chart: #{chart_name}")
@@ -267,7 +414,7 @@ module Dependabot
267
414
  name
268
415
  end
269
416
 
270
- sig { params(index_url: String).returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
417
+ sig { params(index_url: String).returns(T.nilable(T::Hash[String, Object])) }
271
418
  def fetch_helm_chart_index(index_url)
272
419
  Dependabot.logger.info("Fetching Helm chart index from #{index_url}")
273
420
 
@@ -296,7 +443,7 @@ module Dependabot
296
443
  sig { params(all_versions: T::Array[String]).returns(T::Array[String]) }
297
444
  def filter_valid_versions(all_versions)
298
445
  all_versions.reject do |version|
299
- version_class.new(version) <= version_class.new(dependency.version) ||
446
+ version_class.new(version) <= current_version ||
300
447
  ignore_requirements.any? do |r|
301
448
  r.instance_of?(Dependabot::Requirement) && r.satisfied_by?(version_class.new(version))
302
449
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-helm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.384.0
4
+ version: 0.386.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.384.0
18
+ version: 0.386.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.384.0
25
+ version: 0.386.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: dependabot-docker
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 0.384.0
32
+ version: 0.386.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 0.384.0
39
+ version: 0.386.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: debug
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -265,15 +265,17 @@ files:
265
265
  - lib/dependabot/helm/helpers.rb
266
266
  - lib/dependabot/helm/package/package_details_fetcher.rb
267
267
  - lib/dependabot/helm/package_manager.rb
268
+ - lib/dependabot/helm/requirement.rb
268
269
  - lib/dependabot/helm/update_checker.rb
269
270
  - lib/dependabot/helm/update_checker/latest_version_resolver.rb
271
+ - lib/dependabot/helm/update_checker/requirements_updater.rb
270
272
  - lib/dependabot/helm/version.rb
271
273
  homepage: https://github.com/dependabot/dependabot-core
272
274
  licenses:
273
275
  - MIT
274
276
  metadata:
275
277
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
276
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.384.0
278
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.386.0
277
279
  rdoc_options: []
278
280
  require_paths:
279
281
  - lib