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,283 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Shared
7
+ class FileFetcher < Dependabot::FileFetchers::Base
8
+ extend T::Sig
9
+
10
+ abstract!
11
+
12
+ PATH_DEPENDENCY_STARTS = T.let(%w(file: / ./ ../ ~/).freeze, [String, String, String, String, String])
13
+ PATH_DEPENDENCY_CLEAN_REGEX = /^file:|^link:/
14
+ DEPENDENCY_TYPES = T.let(%w(dependencies devDependencies optionalDependencies).freeze, T::Array[String])
15
+
16
+ sig { params(instance: FileFetcher).returns(T::Array[DependencyFile]) }
17
+ def workspace_package_jsons(instance)
18
+ @workspace_package_jsons ||= T.let(fetch_workspace_package_jsons(instance),
19
+ T.nilable(T::Array[DependencyFile]))
20
+ end
21
+
22
+ sig do
23
+ params(instance: FileFetcher, fetched_files: T::Array[DependencyFile])
24
+ .returns(T::Array[DependencyFile])
25
+ end
26
+ def path_dependencies(instance, fetched_files)
27
+ package_json_files = T.let([], T::Array[DependencyFile])
28
+
29
+ path_dependency_details(instance, fetched_files).each do |name, path|
30
+ # This happens with relative paths in the package-lock. Skipping it since it results
31
+ # in /package.json which is outside of the project directory.
32
+ next if path == "file:"
33
+
34
+ path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "")
35
+ raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/")
36
+
37
+ filename = path
38
+ filename = File.join(filename, MANIFEST_FILENAME)
39
+ cleaned_name = Pathname.new(filename).cleanpath.to_path
40
+ next if fetched_files.map(&:name).include?(cleaned_name)
41
+
42
+ file = instance.fetch_file(filename, fetch_submodules: true)
43
+ package_json_files << file
44
+ end
45
+
46
+ if package_json_files.any?
47
+ package_json_files +=
48
+ path_dependencies(instance, fetched_files + package_json_files)
49
+ end
50
+
51
+ package_json_files.tap { |fs| fs.each { |f| f.support_file = true } }
52
+ end
53
+
54
+ sig do
55
+ params(instance: FileFetcher, fetched_files: T::Array[DependencyFile])
56
+ .returns(T::Array[[String, String]])
57
+ end
58
+ def path_dependency_details(instance, fetched_files)
59
+ package_json_path_deps = T.let([], T::Array[[String, String]])
60
+
61
+ fetched_files.each do |file|
62
+ package_json_path_deps +=
63
+ path_dependency_details_from_manifest(instance, file)
64
+ end
65
+
66
+ [
67
+ *package_json_path_deps
68
+ ].uniq
69
+ end
70
+
71
+ # rubocop:disable Metrics/PerceivedComplexity
72
+ # rubocop:disable Metrics/AbcSize
73
+ sig do
74
+ params(instance: FileFetcher,
75
+ file: DependencyFile).returns(T::Array[[String, String]])
76
+ end
77
+ def path_dependency_details_from_manifest(instance, file)
78
+ return [] unless file.name.end_with?(MANIFEST_FILENAME)
79
+
80
+ current_dir = file.name.rpartition("/").first
81
+ current_dir = nil if current_dir == ""
82
+
83
+ current_depth = File.join(instance.directory, file.name).split("/").count { |path| !path.empty? }
84
+ path_to_directory = "../" * current_depth
85
+
86
+ dep_types = DEPENDENCY_TYPES # TODO: Is this needed?
87
+ parsed_manifest = JSON.parse(T.must(file.content))
88
+ dependency_objects = parsed_manifest.values_at(*dep_types).compact
89
+ # Fetch yarn "file:" path "resolutions" so the lockfile can be resolved
90
+ resolution_objects = parsed_manifest.values_at("resolutions").compact
91
+ manifest_objects = dependency_objects + resolution_objects
92
+
93
+ raise Dependabot::DependencyFileNotParseable, file.path unless manifest_objects.all?(Hash)
94
+
95
+ resolution_deps = resolution_objects.flat_map(&:to_a)
96
+ .map do |path, value|
97
+ # skip dependencies that contain invalid values such as inline comments, null, etc.
98
+
99
+ unless value.is_a?(String)
100
+ Dependabot.logger.warn("File fetcher: Skipping dependency \"#{path}\" " \
101
+ "with value: \"#{value}\"")
102
+
103
+ next
104
+ end
105
+
106
+ convert_dependency_path_to_name(path, value)
107
+ end
108
+
109
+ path_starts = PATH_DEPENDENCY_STARTS
110
+ (dependency_objects.flat_map(&:to_a) + resolution_deps)
111
+ .select { |_, v| v.is_a?(String) && v.start_with?(*path_starts) }
112
+ .map do |name, path|
113
+ path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "")
114
+ raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/",
115
+ "#{path_to_directory}..")
116
+
117
+ path = File.join(current_dir, path) unless current_dir.nil?
118
+ [name, Pathname.new(path).cleanpath.to_path]
119
+ end
120
+ rescue JSON::ParserError
121
+ raise Dependabot::DependencyFileNotParseable, file.path
122
+ end
123
+ # rubocop:enable Metrics/AbcSize
124
+ # rubocop:enable Metrics/PerceivedComplexity
125
+
126
+ # Re-write the glob name to the targeted dependency name (which is used
127
+ # in the lockfile), for example "parent-package/**/sub-dep/target-dep" >
128
+ # "target-dep"
129
+ sig { params(path: String, value: String).returns([String, String]) }
130
+ def convert_dependency_path_to_name(path, value)
131
+ # Picking the last two parts that might include a scope
132
+ parts = path.split("/").last(2)
133
+ parts.shift if parts.count == 2 && !T.must(parts.first).start_with?("@")
134
+ [parts.join("/"), value]
135
+ end
136
+
137
+ sig { params(instance: FileFetcher).returns(T::Array[DependencyFile]) }
138
+ def fetch_workspace_package_jsons(instance)
139
+ parsed_manifest = parsed_package_json(instance)
140
+ return [] unless parsed_manifest["workspaces"]
141
+
142
+ workspace_paths(instance, parsed_manifest["workspaces"]).filter_map do |workspace|
143
+ fetch_package_json_if_present(instance, workspace)
144
+ end
145
+ end
146
+
147
+ sig do
148
+ params(instance: FileFetcher,
149
+ workspace_object: T.untyped).returns(T::Array[String])
150
+ end
151
+ def workspace_paths(instance, workspace_object)
152
+ paths_array =
153
+ if workspace_object.is_a?(Hash)
154
+ workspace_object.values_at("packages", "nohoist").flatten.compact
155
+ elsif workspace_object.is_a?(Array) then workspace_object
156
+ else
157
+ [] # Invalid lerna.json, which must not be in use
158
+ end
159
+
160
+ paths_array.flat_map { |path| recursive_find_directories(instance, path) }
161
+ end
162
+
163
+ sig { params(instance: FileFetcher, glob: String).returns(T::Array[String]) }
164
+ def find_directories(instance, glob)
165
+ return [glob] unless glob.include?("*")
166
+
167
+ unglobbed_path =
168
+ glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*")
169
+ .split("*")
170
+ .first&.gsub(%r{(?<=/)[^/]*$}, "") || "."
171
+
172
+ dir = instance.directory.gsub(%r{(^/|/$)}, "")
173
+
174
+ paths =
175
+ instance.fetch_repo_contents(dir: unglobbed_path, raise_errors: false)
176
+ .select { |file| file.type == "dir" }
177
+ .map { |f| f.path.gsub(%r{^/?#{Regexp.escape(dir)}/?}, "") }
178
+
179
+ matching_paths(glob, paths)
180
+ end
181
+
182
+ sig { params(glob: String, paths: T::Array[String]).returns(T::Array[String]) }
183
+ def matching_paths(glob, paths)
184
+ glob = glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*")
185
+ glob = "#{glob}/*" if glob.end_with?("**")
186
+
187
+ paths.select { |filename| File.fnmatch?(glob, filename, File::FNM_PATHNAME) }
188
+ end
189
+
190
+ sig do
191
+ params(instance: FileFetcher, glob: String,
192
+ prefix: String).returns(T::Array[String])
193
+ end
194
+ def recursive_find_directories(instance, glob, prefix = "")
195
+ return [prefix + glob] unless glob.include?("*")
196
+
197
+ glob = glob.gsub(%r{^\./}, "")
198
+ glob_parts = glob.split("/")
199
+
200
+ current_glob = glob_parts.first
201
+ paths = find_directories(instance, prefix + T.must(current_glob))
202
+ next_parts = current_glob == "**" ? glob_parts : glob_parts.drop(1)
203
+ return paths if next_parts.empty?
204
+
205
+ paths += paths.flat_map do |expanded_path|
206
+ recursive_find_directories(instance, next_parts.join("/"), "#{expanded_path}/")
207
+ end
208
+
209
+ matching_paths(prefix + glob, paths)
210
+ end
211
+
212
+ sig do
213
+ params(instance: FileFetcher, workspace: String).returns(T.nilable(DependencyFile))
214
+ end
215
+ def fetch_package_json_if_present(instance, workspace)
216
+ file = File.join(workspace, MANIFEST_FILENAME)
217
+
218
+ begin
219
+ instance.fetch_file(file)
220
+ rescue Dependabot::DependencyFileNotFound
221
+ # Not all paths matched by a workspace glob may contain a package.json
222
+ # file. Ignore if that's the case
223
+ nil
224
+ end
225
+ end
226
+
227
+ sig { params(instance: FileFetcher).returns(DependencyFile) }
228
+ def package_json(instance)
229
+ @package_json ||= T.let(instance.fetch_file(Javascript::MANIFEST_FILENAME), T.nilable(DependencyFile))
230
+ end
231
+
232
+ sig { params(instance: FileFetcher).returns(T.untyped) }
233
+ def parsed_package_json(instance)
234
+ manifest_file = package_json(instance)
235
+ parsed = JSON.parse(T.must(manifest_file.content))
236
+ raise Dependabot::DependencyFileNotParseable, manifest_file.path unless parsed.is_a?(Hash)
237
+
238
+ parsed
239
+ rescue JSON::ParserError
240
+ raise Dependabot::DependencyFileNotParseable, T.must(manifest_file).path
241
+ end
242
+
243
+ sig { params(filename: String, fetch_submodules: T::Boolean).returns(DependencyFile) }
244
+ def fetch_file(filename, fetch_submodules: false)
245
+ fetch_file_from_host(filename, fetch_submodules: fetch_submodules)
246
+ end
247
+
248
+ sig do
249
+ params(
250
+ dir: T.any(Pathname, String),
251
+ ignore_base_directory: T::Boolean,
252
+ raise_errors: T::Boolean,
253
+ fetch_submodules: T::Boolean
254
+ )
255
+ .returns(T::Array[T.untyped])
256
+ end
257
+ def fetch_repo_contents(dir: ".", ignore_base_directory: false, raise_errors: true, fetch_submodules: false)
258
+ repo_contents(dir: dir, ignore_base_directory:, raise_errors:, fetch_submodules:)
259
+ end
260
+
261
+ sig do
262
+ params(instance: FileFetcher, filename: String).returns(T.nilable(DependencyFile))
263
+ end
264
+ def fetch_file_with_support(instance, filename)
265
+ instance.fetch_file(filename).tap { |f| f.support_file = true }
266
+ rescue Dependabot::DependencyFileNotFound
267
+ nil
268
+ end
269
+
270
+ sig do
271
+ params(instance: FileFetcher, filename: String).returns(T.nilable(DependencyFile))
272
+ end
273
+ def fetch_file_from_parent_directories(instance, filename)
274
+ (1..instance.directory.split("/").count).each do |i|
275
+ file = fetch_file_with_support(instance, ("../" * i) + filename)
276
+ return file if file
277
+ end
278
+ nil
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,106 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Shared
7
+ class FileParser
8
+ class Lockfile
9
+ extend T::Helpers
10
+ extend T::Sig
11
+
12
+ abstract!
13
+
14
+ sig { params(dependency_file: DependencyFile).void }
15
+ def initialize(dependency_file)
16
+ @dependency_file = dependency_file
17
+ end
18
+
19
+ sig { abstract.returns(T::Hash[String, T.untyped]) }
20
+ def parsed; end
21
+
22
+ sig { abstract.returns(Dependabot::FileParsers::Base::DependencySet) }
23
+ def dependencies; end
24
+
25
+ sig do
26
+ abstract.params(
27
+ dependency_name: String,
28
+ requirement: T.untyped,
29
+ manifest_name: String
30
+ )
31
+ .returns(T.nilable(T::Hash[String, T.untyped]))
32
+ end
33
+ def details(dependency_name, requirement, manifest_name); end
34
+ end
35
+
36
+ class LockfileParser
37
+ extend T::Helpers
38
+ extend T::Sig
39
+
40
+ abstract!
41
+
42
+ sig { params(dependency_files: T::Array[DependencyFile]).void }
43
+ def initialize(dependency_files:)
44
+ @dependency_files = dependency_files
45
+ end
46
+
47
+ sig { abstract.returns(Dependabot::FileParsers::Base::DependencySet) }
48
+ def parse_set; end
49
+
50
+ sig { returns(T::Array[Dependency]) }
51
+ def parse
52
+ FileParser.dependencies_with_all_versions_metadata(parse_set)
53
+ end
54
+
55
+ sig do
56
+ params(
57
+ dependency_name: String,
58
+ requirement: T.nilable(String), manifest_name: String
59
+ )
60
+ .returns(T.nilable(T::Hash[String, T.untyped]))
61
+ end
62
+ def lockfile_details(dependency_name:, requirement:, manifest_name:)
63
+ details = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
64
+ potential_lockfiles_for_manifest(manifest_name).each do |lockfile|
65
+ details = lockfile_for(lockfile).details(dependency_name, requirement, manifest_name)
66
+
67
+ break if details
68
+ end
69
+
70
+ details
71
+ end
72
+
73
+ private
74
+
75
+ sig { returns(T::Array[DependencyFile]) }
76
+ attr_reader :dependency_files
77
+
78
+ sig { params(manifest_filename: String).returns(T::Array[DependencyFile]) }
79
+ def potential_lockfiles_for_manifest(manifest_filename)
80
+ dir_name = File.dirname(manifest_filename)
81
+ possible_lockfile_names = default_lockfiles.map do |f|
82
+ Pathname.new(File.join(dir_name, f)).cleanpath.to_path
83
+ end + default_lockfiles
84
+
85
+ possible_lockfile_names.uniq
86
+ .filter_map { |nm| dependency_files.find { |f| f.name == nm } }
87
+ end
88
+
89
+ sig { abstract.params(file: DependencyFile).returns(Lockfile) }
90
+ def lockfile_for(file); end
91
+
92
+ sig { abstract.returns(T::Array[String]) }
93
+ def default_lockfiles; end
94
+
95
+ sig { params(extension: String).returns(T::Array[DependencyFile]) }
96
+ def select_files_by_extension(extension)
97
+ dependency_files.select { |f| f.name.end_with?(extension) }
98
+ end
99
+
100
+ sig { abstract.returns(T.class_of(Version)) }
101
+ def version_class; end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end