dependabot-bun 0.296.0 → 0.296.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot/bun.rb +12 -2
  3. data/lib/dependabot/javascript/bun/file_fetcher.rb +77 -0
  4. data/lib/dependabot/javascript/bun/file_parser/bun_lock.rb +156 -0
  5. data/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +55 -0
  6. data/lib/dependabot/javascript/bun/file_parser.rb +74 -0
  7. data/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb +138 -0
  8. data/lib/dependabot/javascript/bun/file_updater.rb +75 -0
  9. data/lib/dependabot/javascript/bun/helpers.rb +72 -0
  10. data/lib/dependabot/javascript/bun/package_manager.rb +48 -0
  11. data/lib/dependabot/javascript/bun/requirement.rb +11 -0
  12. data/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +64 -0
  13. data/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb +47 -0
  14. data/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb +450 -0
  15. data/lib/dependabot/javascript/bun/update_checker/library_detector.rb +76 -0
  16. data/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb +203 -0
  17. data/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb +144 -0
  18. data/lib/dependabot/javascript/bun/update_checker/version_resolver.rb +525 -0
  19. data/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb +165 -0
  20. data/lib/dependabot/javascript/bun/update_checker.rb +440 -0
  21. data/lib/dependabot/javascript/bun/version.rb +11 -0
  22. data/lib/dependabot/javascript/shared/constraint_helper.rb +359 -0
  23. data/lib/dependabot/javascript/shared/dependency_files_filterer.rb +164 -0
  24. data/lib/dependabot/javascript/shared/file_fetcher.rb +283 -0
  25. data/lib/dependabot/javascript/shared/file_parser/lockfile_parser.rb +106 -0
  26. data/lib/dependabot/javascript/shared/file_parser.rb +454 -0
  27. data/lib/dependabot/javascript/shared/file_updater/npmrc_builder.rb +394 -0
  28. data/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb +87 -0
  29. data/lib/dependabot/javascript/shared/file_updater/package_json_updater.rb +376 -0
  30. data/lib/dependabot/javascript/shared/file_updater.rb +179 -0
  31. data/lib/dependabot/javascript/shared/language.rb +45 -0
  32. data/lib/dependabot/javascript/shared/metadata_finder.rb +209 -0
  33. data/lib/dependabot/javascript/shared/native_helpers.rb +21 -0
  34. data/lib/dependabot/javascript/shared/package_manager_detector.rb +72 -0
  35. data/lib/dependabot/javascript/shared/package_name.rb +118 -0
  36. data/lib/dependabot/javascript/shared/registry_helper.rb +190 -0
  37. data/lib/dependabot/javascript/shared/registry_parser.rb +93 -0
  38. data/lib/dependabot/javascript/shared/requirement.rb +144 -0
  39. data/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb +79 -0
  40. data/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb +87 -0
  41. data/lib/dependabot/javascript/shared/update_checker/registry_finder.rb +358 -0
  42. data/lib/dependabot/javascript/shared/version.rb +133 -0
  43. data/lib/dependabot/javascript/shared/version_selector.rb +60 -0
  44. data/lib/dependabot/javascript.rb +31 -0
  45. metadata +48 -17
  46. data/lib/dependabot/bun/file_fetcher.rb +0 -97
  47. data/lib/dependabot/bun/file_parser/bun_lock.rb +0 -148
  48. data/lib/dependabot/bun/helpers.rb +0 -79
  49. data/lib/dependabot/bun/language.rb +0 -45
  50. data/lib/dependabot/bun/package_manager.rb +0 -46
  51. data/lib/dependabot/bun/requirement.rb +0 -14
  52. data/lib/dependabot/bun/version.rb +0 -12
  53. data/lib/dependabot/javascript/file_fetcher_helper.rb +0 -245
  54. data/lib/dependabot/javascript/requirement.rb +0 -141
  55. data/lib/dependabot/javascript/version.rb +0 -135
@@ -1,245 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module Dependabot
5
- module Javascript
6
- module FileFetcherHelper
7
- include Kernel
8
- extend T::Sig
9
-
10
- PATH_DEPENDENCY_STARTS = T.let(%w(file: / ./ ../ ~/).freeze, [String, String, String, String, String])
11
- PATH_DEPENDENCY_CLEAN_REGEX = /^file:|^link:/
12
- DEPENDENCY_TYPES = T.let(%w(dependencies devDependencies optionalDependencies).freeze, T::Array[String])
13
-
14
- sig { params(instance: Dependabot::Bun::FileFetcher).returns(T::Array[DependencyFile]) }
15
- def workspace_package_jsons(instance)
16
- @workspace_package_jsons ||= T.let(fetch_workspace_package_jsons(instance), T.nilable(T::Array[DependencyFile]))
17
- end
18
-
19
- sig do
20
- params(instance: Dependabot::Bun::FileFetcher, fetched_files: T::Array[DependencyFile])
21
- .returns(T::Array[DependencyFile])
22
- end
23
- def path_dependencies(instance, fetched_files)
24
- package_json_files = T.let([], T::Array[DependencyFile])
25
-
26
- path_dependency_details(instance, fetched_files).each do |name, path|
27
- # This happens with relative paths in the package-lock. Skipping it since it results
28
- # in /package.json which is outside of the project directory.
29
- next if path == "file:"
30
-
31
- path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "")
32
- raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/")
33
-
34
- filename = path
35
- filename = File.join(filename, MANIFEST_FILENAME)
36
- cleaned_name = Pathname.new(filename).cleanpath.to_path
37
- next if fetched_files.map(&:name).include?(cleaned_name)
38
-
39
- file = instance.fetch_file(filename, fetch_submodules: true)
40
- package_json_files << file
41
- end
42
-
43
- if package_json_files.any?
44
- package_json_files +=
45
- path_dependencies(instance, fetched_files + package_json_files)
46
- end
47
-
48
- package_json_files.tap { |fs| fs.each { |f| f.support_file = true } }
49
- end
50
-
51
- sig do
52
- params(instance: Dependabot::Bun::FileFetcher, fetched_files: T::Array[DependencyFile])
53
- .returns(T::Array[[String, String]])
54
- end
55
- def path_dependency_details(instance, fetched_files)
56
- package_json_path_deps = T.let([], T::Array[[String, String]])
57
-
58
- fetched_files.each do |file|
59
- package_json_path_deps +=
60
- path_dependency_details_from_manifest(instance, file)
61
- end
62
-
63
- [
64
- *package_json_path_deps
65
- ].uniq
66
- end
67
-
68
- # rubocop:disable Metrics/PerceivedComplexity
69
- # rubocop:disable Metrics/AbcSize
70
- sig { params(instance: Dependabot::Bun::FileFetcher, file: DependencyFile).returns(T::Array[[String, String]]) }
71
- def path_dependency_details_from_manifest(instance, file)
72
- return [] unless file.name.end_with?(MANIFEST_FILENAME)
73
-
74
- current_dir = file.name.rpartition("/").first
75
- current_dir = nil if current_dir == ""
76
-
77
- current_depth = File.join(instance.directory, file.name).split("/").count { |path| !path.empty? }
78
- path_to_directory = "../" * current_depth
79
-
80
- dep_types = DEPENDENCY_TYPES # TODO: Is this needed?
81
- parsed_manifest = JSON.parse(T.must(file.content))
82
- dependency_objects = parsed_manifest.values_at(*dep_types).compact
83
- # Fetch yarn "file:" path "resolutions" so the lockfile can be resolved
84
- resolution_objects = parsed_manifest.values_at("resolutions").compact
85
- manifest_objects = dependency_objects + resolution_objects
86
-
87
- raise Dependabot::DependencyFileNotParseable, file.path unless manifest_objects.all?(Hash)
88
-
89
- resolution_deps = resolution_objects.flat_map(&:to_a)
90
- .map do |path, value|
91
- # skip dependencies that contain invalid values such as inline comments, null, etc.
92
-
93
- unless value.is_a?(String)
94
- Dependabot.logger.warn("File fetcher: Skipping dependency \"#{path}\" " \
95
- "with value: \"#{value}\"")
96
-
97
- next
98
- end
99
-
100
- convert_dependency_path_to_name(path, value)
101
- end
102
-
103
- path_starts = PATH_DEPENDENCY_STARTS
104
- (dependency_objects.flat_map(&:to_a) + resolution_deps)
105
- .select { |_, v| v.is_a?(String) && v.start_with?(*path_starts) }
106
- .map do |name, path|
107
- path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "")
108
- raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/", "#{path_to_directory}..")
109
-
110
- path = File.join(current_dir, path) unless current_dir.nil?
111
- [name, Pathname.new(path).cleanpath.to_path]
112
- end
113
- rescue JSON::ParserError
114
- raise Dependabot::DependencyFileNotParseable, file.path
115
- end
116
- # rubocop:enable Metrics/AbcSize
117
- # rubocop:enable Metrics/PerceivedComplexity
118
-
119
- # Re-write the glob name to the targeted dependency name (which is used
120
- # in the lockfile), for example "parent-package/**/sub-dep/target-dep" >
121
- # "target-dep"
122
- sig { params(path: String, value: String).returns([String, String]) }
123
- def convert_dependency_path_to_name(path, value)
124
- # Picking the last two parts that might include a scope
125
- parts = path.split("/").last(2)
126
- parts.shift if parts.count == 2 && !T.must(parts.first).start_with?("@")
127
- [parts.join("/"), value]
128
- end
129
-
130
- sig { params(instance: Dependabot::Bun::FileFetcher).returns(T::Array[DependencyFile]) }
131
- def fetch_workspace_package_jsons(instance)
132
- parsed_manifest = parsed_package_json(instance)
133
- return [] unless parsed_manifest["workspaces"]
134
-
135
- workspace_paths(instance, parsed_manifest["workspaces"]).filter_map do |workspace|
136
- fetch_package_json_if_present(instance, workspace)
137
- end
138
- end
139
-
140
- sig { params(instance: Dependabot::Bun::FileFetcher, workspace_object: T.untyped).returns(T::Array[String]) }
141
- def workspace_paths(instance, workspace_object)
142
- paths_array =
143
- if workspace_object.is_a?(Hash)
144
- workspace_object.values_at("packages", "nohoist").flatten.compact
145
- elsif workspace_object.is_a?(Array) then workspace_object
146
- else
147
- [] # Invalid lerna.json, which must not be in use
148
- end
149
-
150
- paths_array.flat_map { |path| recursive_find_directories(instance, path) }
151
- end
152
-
153
- sig { params(instance: Dependabot::Bun::FileFetcher, glob: String).returns(T::Array[String]) }
154
- def find_directories(instance, glob)
155
- return [glob] unless glob.include?("*")
156
-
157
- unglobbed_path =
158
- glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*")
159
- .split("*")
160
- .first&.gsub(%r{(?<=/)[^/]*$}, "") || "."
161
-
162
- dir = instance.directory.gsub(%r{(^/|/$)}, "")
163
-
164
- paths =
165
- instance.fetch_repo_contents(dir: unglobbed_path, raise_errors: false)
166
- .select { |file| file.type == "dir" }
167
- .map { |f| f.path.gsub(%r{^/?#{Regexp.escape(dir)}/?}, "") }
168
-
169
- matching_paths(glob, paths)
170
- end
171
-
172
- sig { params(glob: String, paths: T::Array[String]).returns(T::Array[String]) }
173
- def matching_paths(glob, paths)
174
- glob = glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*")
175
- glob = "#{glob}/*" if glob.end_with?("**")
176
-
177
- paths.select { |filename| File.fnmatch?(glob, filename, File::FNM_PATHNAME) }
178
- end
179
-
180
- sig { params(instance: Dependabot::Bun::FileFetcher, glob: String, prefix: String).returns(T::Array[String]) }
181
- def recursive_find_directories(instance, glob, prefix = "")
182
- return [prefix + glob] unless glob.include?("*")
183
-
184
- glob = glob.gsub(%r{^\./}, "")
185
- glob_parts = glob.split("/")
186
-
187
- current_glob = glob_parts.first
188
- paths = find_directories(instance, prefix + T.must(current_glob))
189
- next_parts = current_glob == "**" ? glob_parts : glob_parts.drop(1)
190
- return paths if next_parts.empty?
191
-
192
- paths += paths.flat_map do |expanded_path|
193
- recursive_find_directories(instance, next_parts.join("/"), "#{expanded_path}/")
194
- end
195
-
196
- matching_paths(prefix + glob, paths)
197
- end
198
-
199
- sig { params(instance: Dependabot::Bun::FileFetcher, workspace: String).returns(T.nilable(DependencyFile)) }
200
- def fetch_package_json_if_present(instance, workspace)
201
- file = File.join(workspace, MANIFEST_FILENAME)
202
-
203
- begin
204
- instance.fetch_file(file)
205
- rescue Dependabot::DependencyFileNotFound
206
- # Not all paths matched by a workspace glob may contain a package.json
207
- # file. Ignore if that's the case
208
- nil
209
- end
210
- end
211
-
212
- sig { params(instance: Dependabot::Bun::FileFetcher).returns(DependencyFile) }
213
- def package_json(instance)
214
- @package_json ||= T.let(instance.fetch_file(Javascript::MANIFEST_FILENAME), T.nilable(DependencyFile))
215
- end
216
-
217
- sig { params(instance: Dependabot::Bun::FileFetcher).returns(T.untyped) }
218
- def parsed_package_json(instance)
219
- manifest_file = package_json(instance)
220
- parsed = JSON.parse(T.must(manifest_file.content))
221
- raise Dependabot::DependencyFileNotParseable, manifest_file.path unless parsed.is_a?(Hash)
222
-
223
- parsed
224
- rescue JSON::ParserError
225
- raise Dependabot::DependencyFileNotParseable, T.must(manifest_file).path
226
- end
227
-
228
- sig { params(instance: Dependabot::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) }
229
- def fetch_file_with_support(instance, filename)
230
- instance.fetch_file(filename).tap { |f| f.support_file = true }
231
- rescue Dependabot::DependencyFileNotFound
232
- nil
233
- end
234
-
235
- sig { params(instance: Dependabot::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) }
236
- def fetch_file_from_parent_directories(instance, filename)
237
- (1..instance.directory.split("/").count).each do |i|
238
- file = fetch_file_with_support(instance, ("../" * i) + filename)
239
- return file if file
240
- end
241
- nil
242
- end
243
- end
244
- end
245
- end
@@ -1,141 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- require "sorbet-runtime"
5
-
6
- require "dependabot/requirement"
7
- require "dependabot/utils"
8
- require "dependabot/npm_and_yarn/version"
9
-
10
- module Dependabot
11
- module Javascript
12
- class Requirement < Dependabot::Requirement
13
- extend T::Sig
14
-
15
- AND_SEPARATOR = /(?<=[a-zA-Z0-9*])\s+(?:&+\s+)?(?!\s*[|-])/
16
- OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|+/
17
-
18
- # Override the version pattern to allow a 'v' prefix
19
- quoted = OPS.keys.map { |k| Regexp.quote(k) }.join("|")
20
- version_pattern = "v?#{Javascript::Version::VERSION_PATTERN}"
21
-
22
- PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze
23
- PATTERN = /\A#{PATTERN_RAW}\z/
24
-
25
- def self.parse(obj)
26
- return ["=", nil] if obj.is_a?(String) && Version::VERSION_TAGS.include?(obj.strip)
27
- return ["=", Javascript::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
28
-
29
- unless (matches = PATTERN.match(obj.to_s))
30
- msg = "Illformed requirement [#{obj.inspect}]"
31
- raise BadRequirementError, msg
32
- end
33
-
34
- return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"
35
-
36
- [matches[1] || "=", Javascript::Version.new(T.must(matches[2]))]
37
- end
38
-
39
- # Returns an array of requirements. At least one requirement from the
40
- # returned array must be satisfied for a version to be valid.
41
- sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Requirement]) }
42
- def self.requirements_array(requirement_string)
43
- return [new(nil)] if requirement_string.nil?
44
-
45
- # Removing parentheses is technically wrong but they are extremely
46
- # rarely used.
47
- # TODO: Handle complicated parenthesised requirements
48
- requirement_string = requirement_string.gsub(/[()]/, "")
49
- requirement_string.strip.split(OR_SEPARATOR).map do |req_string|
50
- requirements = req_string.strip.split(AND_SEPARATOR)
51
- new(requirements)
52
- end
53
- end
54
-
55
- def initialize(*requirements)
56
- requirements = requirements.flatten
57
- .flat_map { |req_string| req_string.split(",").map(&:strip) }
58
- .flat_map { |req_string| convert_js_constraint_to_ruby_constraint(req_string) }
59
-
60
- super(requirements)
61
- end
62
-
63
- private
64
-
65
- def convert_js_constraint_to_ruby_constraint(req_string)
66
- return req_string if req_string.match?(/^([A-Za-uw-z]|v[^\d])/)
67
-
68
- req_string = req_string.gsub(/(?:\.|^)[xX*]/, "")
69
-
70
- if req_string.empty? then ">= 0"
71
- elsif req_string.start_with?("~>") then req_string
72
- elsif req_string.start_with?("=") then req_string.gsub(/^=*/, "")
73
- elsif req_string.start_with?("~") then convert_tilde_req(req_string)
74
- elsif req_string.start_with?("^") then convert_caret_req(req_string)
75
- elsif req_string.include?(" - ") then convert_hyphen_req(req_string)
76
- elsif req_string.match?(/[<>]/) then req_string
77
- else
78
- ruby_range(req_string)
79
- end
80
- end
81
-
82
- def convert_tilde_req(req_string)
83
- version = req_string.gsub(/^~\>?[\s=]*/, "")
84
- parts = version.split(".")
85
- parts << "0" if parts.count < 3
86
- "~> #{parts.join('.')}"
87
- end
88
-
89
- def convert_hyphen_req(req_string)
90
- lower_bound, upper_bound = req_string.split(/\s+-\s+/)
91
- lower_bound_parts = lower_bound.split(".")
92
- lower_bound_parts.fill("0", lower_bound_parts.length...3)
93
-
94
- upper_bound_parts = upper_bound.split(".")
95
- upper_bound_range =
96
- if upper_bound_parts.length < 3
97
- # When upper bound is a partial version treat these as an X-range
98
- upper_bound_parts[-1] = upper_bound_parts[-1].to_i + 1 if upper_bound_parts[-1].to_i.positive?
99
- upper_bound_parts.fill("0", upper_bound_parts.length...3)
100
- "< #{upper_bound_parts.join('.')}.a"
101
- else
102
- "<= #{upper_bound_parts.join('.')}"
103
- end
104
-
105
- [">= #{lower_bound_parts.join('.')}", upper_bound_range]
106
- end
107
-
108
- def ruby_range(req_string)
109
- parts = req_string.split(".")
110
- # If we have three or more parts then this is an exact match
111
- return req_string if parts.count >= 3
112
-
113
- # If we have fewer than three parts we do a partial match
114
- parts << "0"
115
- "~> #{parts.join('.')}"
116
- end
117
-
118
- def convert_caret_req(req_string)
119
- version = req_string.gsub(/^\^[\s=]*/, "")
120
- parts = version.split(".")
121
- parts.fill("x", parts.length...3)
122
- first_non_zero = parts.find { |d| d != "0" }
123
- first_non_zero_index =
124
- first_non_zero ? parts.index(first_non_zero) : parts.count - 1
125
- # If the requirement has a blank minor or patch version increment the
126
- # previous index value with 1
127
- first_non_zero_index -= 1 if first_non_zero == "x"
128
- upper_bound = parts.map.with_index do |part, i|
129
- if i < first_non_zero_index then part
130
- elsif i == first_non_zero_index then (part.to_i + 1).to_s
131
- elsif i > first_non_zero_index && i == 2 then "0.a"
132
- else
133
- 0
134
- end
135
- end.join(".")
136
-
137
- [">= #{version}", "< #{upper_bound}"]
138
- end
139
- end
140
- end
141
- end
@@ -1,135 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "dependabot/version"
5
- require "dependabot/utils"
6
- require "sorbet-runtime"
7
-
8
- # JavaScript pre-release versions use 1.0.1-rc1 syntax, which Gem::Version
9
- # converts into 1.0.1.pre.rc1. We override the `to_s` method to stop that
10
- # alteration.
11
- #
12
- # See https://semver.org/ for details of node's version syntax.
13
-
14
- module Dependabot
15
- module Javascript
16
- class Version < Dependabot::Version
17
- extend T::Sig
18
-
19
- sig { returns(T.nilable(String)) }
20
- attr_reader :build_info
21
-
22
- # These are possible npm versioning tags that can be used in place of a version.
23
- # See https://docs.npmjs.com/cli/v10/commands/npm-dist-tag#purpose for more details.
24
- VERSION_TAGS = T.let([
25
- "alpha", # Alpha version, early testing phase
26
- "beta", # Beta version, more stable than alpha
27
- "canary", # Canary version, often used for cutting-edge builds
28
- "dev", # Development version, ongoing development
29
- "experimental", # Experimental version, unstable and new features
30
- "latest", # Latest stable version, used by npm to identify the current version of a package
31
- "legacy", # Legacy version, older version maintained for compatibility
32
- "next", # Next version, used by some projects to identify the upcoming version
33
- "nightly", # Nightly build, daily builds often including latest changes
34
- "rc", # Release candidate, potential final version
35
- "release", # General release version
36
- "stable" # Stable version, thoroughly tested and stable
37
- ].freeze.map(&:freeze), T::Array[String])
38
-
39
- VERSION_PATTERN = T.let(Gem::Version::VERSION_PATTERN + '(\+[0-9a-zA-Z\-.]+)?', String)
40
- ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/
41
-
42
- sig { override.params(version: VersionParameter).returns(T::Boolean) }
43
- def self.correct?(version)
44
- version = version.gsub(/^v/, "") if version.is_a?(String)
45
-
46
- return false if version.nil?
47
-
48
- version.to_s.match?(ANCHORED_VERSION_PATTERN)
49
- end
50
-
51
- sig { params(version: VersionParameter).returns(VersionParameter) }
52
- def self.semver_for(version)
53
- # The next two lines are to guard against improperly formatted
54
- # versions in a lockfile, such as an empty string or additional
55
- # characters. NPM/yarn fixes these when running an update, so we can
56
- # safely ignore these versions.
57
- return if version == ""
58
- return unless correct?(version)
59
-
60
- version
61
- end
62
-
63
- sig { override.params(version: VersionParameter).void }
64
- def initialize(version)
65
- version = clean_version(version)
66
-
67
- @version_string = T.let(version.to_s, String)
68
-
69
- @build_info = T.let(nil, T.nilable(String))
70
-
71
- version, @build_info = version.to_s.split("+") if version.to_s.include?("+")
72
-
73
- super(T.must(version))
74
- end
75
-
76
- sig { params(version: VersionParameter).returns(VersionParameter) }
77
- def clean_version(version)
78
- # Check if version is a string before attempting to match
79
- if version.is_a?(String)
80
- # Matches @ followed by x.y.z (digits separated by dots)
81
- if (match = version.match(/@(\d+\.\d+\.\d+)/))
82
- version = match[1] # Just "4.5.3"
83
-
84
- # Extract version in case the output contains Corepack verbose data
85
- elsif version.include?("Corepack")
86
- version = T.must(T.must(version.tr("\n", " ").match(/(\d+\.\d+\.\d+)/))[-1])
87
- end
88
- version = version&.gsub(/^v/, "")
89
- end
90
-
91
- version
92
- end
93
-
94
- sig { override.params(version: VersionParameter).returns(Dependabot::Javascript::Version) }
95
- def self.new(version)
96
- T.cast(super, Dependabot::Javascript::Version)
97
- end
98
-
99
- sig { returns(Integer) }
100
- def major
101
- @major ||= T.let(segments[0].to_i, T.nilable(Integer))
102
- end
103
-
104
- sig { returns(Integer) }
105
- def minor
106
- @minor ||= T.let(segments[1].to_i, T.nilable(Integer))
107
- end
108
-
109
- sig { returns(Integer) }
110
- def patch
111
- @patch ||= T.let(segments[2].to_i, T.nilable(Integer))
112
- end
113
-
114
- sig { params(other: Dependabot::Javascript::Version).returns(T::Boolean) }
115
- def backwards_compatible_with?(other)
116
- case major
117
- when 0
118
- self == other
119
- else
120
- major == other.major && minor >= other.minor
121
- end
122
- end
123
-
124
- sig { override.returns(String) }
125
- def to_s
126
- @version_string
127
- end
128
-
129
- sig { override.returns(String) }
130
- def inspect
131
- "#<#{self.class} #{@version_string}>"
132
- end
133
- end
134
- end
135
- end