dependabot-javascript 0.296.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/lib/dependabot/bun.rb +49 -0
  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 +39 -0
  45. metadata +327 -0
@@ -0,0 +1,93 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Shared
7
+ class RegistryParser
8
+ extend T::Sig
9
+
10
+ sig { params(resolved_url: String, credentials: T::Array[Dependabot::Credential]).void }
11
+ def initialize(resolved_url:, credentials:)
12
+ @resolved_url = resolved_url
13
+ @credentials = credentials
14
+ end
15
+
16
+ sig { params(name: String).returns(T::Hash[Symbol, T.untyped]) }
17
+ def registry_source_for(name)
18
+ url =
19
+ if resolved_url.include?("/~/")
20
+ # Gemfury format
21
+ resolved_url.split("/~/").first
22
+ elsif resolved_url.include?("/#{name}/-/#{name}")
23
+ # MyGet / Bintray format
24
+ T.must(resolved_url.split("/#{name}/-/#{name}").first)
25
+ .gsub("dl.bintray.com//", "api.bintray.com/npm/").
26
+ # GitLab format
27
+ gsub(%r{\/projects\/\d+}, "")
28
+ elsif resolved_url.include?("/#{name}/-/#{name.split('/').last}")
29
+ # Sonatype Nexus / Artifactory JFrog format
30
+ resolved_url.split("/#{name}/-/#{name.split('/').last}").first
31
+ elsif (cred_url = url_for_relevant_cred) then cred_url
32
+ else
33
+ T.must(resolved_url.split("/")[0..2]).join("/")
34
+ end
35
+
36
+ { type: "registry", url: url }
37
+ end
38
+
39
+ sig { returns(String) }
40
+ def dependency_name
41
+ url_base = if resolved_url.include?("/-/")
42
+ T.must(resolved_url.split("/-/").first)
43
+ else
44
+ resolved_url
45
+ end
46
+
47
+ package_name = url_base.gsub("%2F", "/").match(%r{@.*/})
48
+
49
+ return T.must(url_base.gsub("%2F", "/").split("/").last) unless package_name
50
+
51
+ "#{package_name}#{T.must(url_base.gsub('%2F', '/').split('/').last)}"
52
+ end
53
+
54
+ private
55
+
56
+ sig { returns(String) }
57
+ attr_reader :resolved_url
58
+
59
+ sig { returns(T::Array[Dependabot::Credential]) }
60
+ attr_reader :credentials
61
+
62
+ # rubocop:disable Metrics/PerceivedComplexity
63
+ sig { returns(T.nilable(String)) }
64
+ def url_for_relevant_cred
65
+ resolved_url_host = URI(resolved_url).host
66
+
67
+ credential_matching_url =
68
+ credentials
69
+ .select { |cred| cred["type"] == "npm_registry" && cred["registry"] }
70
+ .sort_by { |cred| cred.fetch("registry").length }
71
+ .find do |details|
72
+ next true if resolved_url_host == details["registry"]
73
+
74
+ uri = if details["registry"]&.include?("://")
75
+ URI(details.fetch("registry"))
76
+ else
77
+ URI("https://#{details['registry']}")
78
+ end
79
+ resolved_url_host == uri.host && resolved_url.include?(details.fetch("registry"))
80
+ end
81
+
82
+ return unless credential_matching_url
83
+
84
+ # Trim the resolved URL so that it ends at the same point as the
85
+ # credential registry
86
+ reg = credential_matching_url.fetch("registry")
87
+ resolved_url.gsub(/#{Regexp.quote(reg)}.*/, "") + reg
88
+ end
89
+ # rubocop:enable Metrics/PerceivedComplexity
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,144 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Shared
7
+ class Requirement < Dependabot::Requirement
8
+ extend T::Sig
9
+
10
+ AND_SEPARATOR = /(?<=[a-zA-Z0-9*])\s+(?:&+\s+)?(?!\s*[|-])/
11
+ OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|+/
12
+
13
+ # Override the version pattern to allow a 'v' prefix
14
+ quoted = OPS.keys.map { |k| Regexp.quote(k) }.join("|")
15
+ version_pattern = "v?#{Version::VERSION_PATTERN}"
16
+
17
+ PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze, String)
18
+ PATTERN = /\A#{PATTERN_RAW}\z/
19
+
20
+ sig { params(obj: T.untyped).returns(T::Array[T.untyped]) }
21
+ def self.parse(obj)
22
+ return ["=", nil] if obj.is_a?(String) && Version::VERSION_TAGS.include?(obj.strip)
23
+ return ["=", Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
24
+
25
+ unless (matches = PATTERN.match(obj.to_s))
26
+ msg = "Illformed requirement [#{obj.inspect}]"
27
+ raise BadRequirementError, msg
28
+ end
29
+
30
+ return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"
31
+
32
+ [matches[1] || "=", Version.new(T.must(matches[2]))]
33
+ end
34
+
35
+ # Returns an array of requirements. At least one requirement from the
36
+ # returned array must be satisfied for a version to be valid.
37
+ sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Requirement]) }
38
+ def self.requirements_array(requirement_string)
39
+ return [new([])] if requirement_string.nil?
40
+
41
+ # Removing parentheses is technically wrong but they are extremely
42
+ # rarely used.
43
+ # TODO: Handle complicated parenthesised requirements
44
+ requirement_string = requirement_string.gsub(/[()]/, "")
45
+ requirement_string.strip.split(OR_SEPARATOR).map do |req_string|
46
+ requirements = req_string.strip.split(AND_SEPARATOR)
47
+ new(requirements)
48
+ end
49
+ end
50
+
51
+ sig { params(requirements: T.any(String, T::Array[String])).void }
52
+ def initialize(*requirements)
53
+ requirements = requirements.flatten
54
+ .flat_map { |req_string| req_string.split(",").map(&:strip) }
55
+ .flat_map { |req_string| convert_js_constraint_to_ruby_constraint(req_string) }
56
+
57
+ super(requirements)
58
+ end
59
+
60
+ private
61
+
62
+ sig { params(req_string: String).returns(T.any(String, T::Array[String])) }
63
+ def convert_js_constraint_to_ruby_constraint(req_string)
64
+ return req_string if req_string.match?(/^([A-Za-uw-z]|v[^\d])/)
65
+
66
+ req_string = req_string.gsub(/(?:\.|^)[xX*]/, "")
67
+
68
+ if req_string.empty? then ">= 0"
69
+ elsif req_string.start_with?("~>") then req_string
70
+ elsif req_string.start_with?("=") then req_string.gsub(/^=*/, "")
71
+ elsif req_string.start_with?("~") then convert_tilde_req(req_string)
72
+ elsif req_string.start_with?("^") then convert_caret_req(req_string)
73
+ elsif req_string.include?(" - ") then convert_hyphen_req(req_string)
74
+ elsif req_string.match?(/[<>]/) then req_string
75
+ else
76
+ ruby_range(req_string)
77
+ end
78
+ end
79
+
80
+ sig { params(req_string: String).returns(String) }
81
+ def convert_tilde_req(req_string)
82
+ version = req_string.gsub(/^~\>?[\s=]*/, "")
83
+ parts = version.split(".")
84
+ parts << "0" if parts.count < 3
85
+ "~> #{parts.join('.')}"
86
+ end
87
+
88
+ sig { params(req_string: String).returns(T::Array[String]) }
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 && 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
+ sig { params(req_string: String).returns(String) }
109
+ def ruby_range(req_string)
110
+ parts = req_string.split(".")
111
+ # If we have three or more parts then this is an exact match
112
+ return req_string if parts.count >= 3
113
+
114
+ # If we have fewer than three parts we do a partial match
115
+ parts << "0"
116
+ "~> #{parts.join('.')}"
117
+ end
118
+
119
+ sig { params(req_string: String).returns(T::Array[String]) }
120
+ def convert_caret_req(req_string) # rubocop:disable Metrics/PerceivedComplexity
121
+ version = req_string.gsub(/^\^[\s=]*/, "")
122
+ parts = version.split(".")
123
+ parts.fill("x", parts.length...3)
124
+ first_non_zero = parts.find { |d| d != "0" }
125
+ first_non_zero_index =
126
+ first_non_zero ? parts.index(first_non_zero) : parts.count - 1
127
+ # If the requirement has a blank minor or patch version increment the
128
+ # previous index value with 1
129
+ first_non_zero_index -= 1 if first_non_zero_index && first_non_zero == "x"
130
+ upper_bound = parts.map.with_index do |part, i|
131
+ if i < T.must(first_non_zero_index) then part
132
+ elsif i == first_non_zero_index then (part.to_i + 1).to_s
133
+ elsif i > T.must(first_non_zero_index) && i == 2 then "0.a"
134
+ else
135
+ 0
136
+ end
137
+ end.join(".")
138
+
139
+ [">= #{version}", "< #{upper_bound}"]
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,79 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ # Used in the sub dependency version resolver and file updater to only run
5
+ # yarn/npm helpers on dependency files that require updates. This is useful for
6
+ # large monorepos with lots of sub-projects that don't all have the same
7
+ # dependencies.
8
+ module Dependabot
9
+ module Javascript
10
+ module Shared
11
+ class SubDependencyFilesFilterer
12
+ extend T::Sig
13
+
14
+ sig { params(dependency_files: T::Array[DependencyFile], updated_dependencies: T::Array[Dependency]).void }
15
+ def initialize(dependency_files:, updated_dependencies:)
16
+ @dependency_files = dependency_files
17
+ @updated_dependencies = updated_dependencies
18
+ end
19
+
20
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
21
+ def files_requiring_update
22
+ return T.must(@files_requiring_update) if defined? @files_requiring_update
23
+
24
+ files_requiring_update =
25
+ lockfiles.select do |lockfile|
26
+ lockfile_dependencies(lockfile).any? do |sub_dep|
27
+ updated_dependencies.any? do |updated_dep|
28
+ next false unless sub_dep.name == updated_dep.name
29
+
30
+ version_class.new(updated_dep.version) >
31
+ version_class.new(sub_dep.version)
32
+ end
33
+ end
34
+ end
35
+
36
+ @files_requiring_update ||= T.let(files_requiring_update, T.nilable(T::Array[DependencyFile]))
37
+ end
38
+
39
+ private
40
+
41
+ sig { returns(T::Array[DependencyFile]) }
42
+ attr_reader :dependency_files
43
+
44
+ sig { returns(T::Array[Dependency]) }
45
+ attr_reader :updated_dependencies
46
+
47
+ sig { params(lockfile: DependencyFile).returns(T::Array[Dependabot::Dependency]) }
48
+ def lockfile_dependencies(lockfile)
49
+ @lockfile_dependencies ||= T.let({}, T.nilable(T::Hash[String, T::Array[Dependency]]))
50
+ @lockfile_dependencies[lockfile.name] ||=
51
+ NpmAndYarn::FileParser::LockfileParser.new(
52
+ dependency_files: [lockfile]
53
+ ).parse
54
+ end
55
+
56
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
57
+ def lockfiles
58
+ dependency_files.select { |file| lockfile?(file) }
59
+ end
60
+
61
+ sig { params(file: DependencyFile).returns(T::Boolean) }
62
+ def lockfile?(file)
63
+ file.name.end_with?(
64
+ "package-lock.json",
65
+ "yarn.lock",
66
+ "npm-shrinkwrap.json",
67
+ "bun.lock",
68
+ "pnpm-lock.yaml"
69
+ )
70
+ end
71
+
72
+ sig { returns(T.class_of(Dependabot::NpmAndYarn::Version)) }
73
+ def version_class
74
+ NpmAndYarn::Version
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,87 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Shared
7
+ module UpdateChecker
8
+ class DependencyFilesBuilder
9
+ extend T::Helpers
10
+ extend T::Sig
11
+
12
+ abstract!
13
+
14
+ Credentials = T.type_alias { T::Array[Credential] }
15
+
16
+ sig do
17
+ params(
18
+ dependency: Dependency,
19
+ dependency_files: T::Array[DependencyFile],
20
+ credentials: Credentials
21
+ )
22
+ .void
23
+ end
24
+ def initialize(dependency:, dependency_files:, credentials:)
25
+ @dependency = T.let(dependency, Dependency)
26
+ @dependency_files = T.let(dependency_files, T::Array[DependencyFile])
27
+ @credentials = T.let(credentials, Credentials)
28
+ end
29
+
30
+ sig { void }
31
+ def write_temporary_dependency_files
32
+ write_lockfiles
33
+
34
+ File.write(".npmrc", npmrc_content)
35
+
36
+ package_files.each do |file|
37
+ path = file.name
38
+ FileUtils.mkdir_p(Pathname.new(path).dirname)
39
+ File.write(file.name, prepared_package_json_content(file))
40
+ end
41
+ end
42
+
43
+ sig { abstract.returns(T::Array[DependencyFile]) }
44
+ def lockfiles; end
45
+
46
+ sig { returns(T::Array[DependencyFile]) }
47
+ def package_files
48
+ @package_files ||= T.let(
49
+ dependency_files
50
+ .select { |f| f.name.end_with?("package.json") },
51
+ T.nilable(T::Array[DependencyFile])
52
+ )
53
+ end
54
+
55
+ private
56
+
57
+ sig { returns(Dependency) }
58
+ attr_reader :dependency
59
+
60
+ sig { returns(T::Array[DependencyFile]) }
61
+ attr_reader :dependency_files
62
+
63
+ sig { returns(Credentials) }
64
+ attr_reader :credentials
65
+
66
+ sig { abstract.returns(T::Array[DependencyFile]) }
67
+ def write_lockfiles; end
68
+
69
+ sig { params(file: DependencyFile).returns(String) }
70
+ def prepared_package_json_content(file)
71
+ FileUpdater::PackageJsonPreparer.new(
72
+ package_json_content: file.content
73
+ ).prepared_content
74
+ end
75
+
76
+ sig { returns(String) }
77
+ def npmrc_content
78
+ FileUpdater::NpmrcBuilder.new(
79
+ credentials: credentials,
80
+ dependency_files: dependency_files
81
+ ).npmrc_content
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end