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,394 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Shared
7
+ class FileUpdater < Dependabot::FileUpdaters::Base
8
+ # Build a .npmrc file from the lockfile content, credentials, and any
9
+ # committed .npmrc
10
+ # We should refactor this to use UpdateChecker::RegistryFinder
11
+ class NpmrcBuilder
12
+ extend T::Sig
13
+
14
+ CENTRAL_REGISTRIES = T.let(
15
+ %w(
16
+ registry.npmjs.org
17
+ registry.yarnpkg.com
18
+ ).freeze,
19
+ T::Array[String]
20
+ )
21
+
22
+ SCOPED_REGISTRY = /^\s*@(?<scope>\S+):registry\s*=\s*(?<registry>\S+)/
23
+
24
+ sig do
25
+ params(
26
+ dependency_files: T::Array[Dependabot::DependencyFile],
27
+ credentials: T::Array[Dependabot::Credential],
28
+ dependencies: T::Array[Dependabot::Dependency]
29
+ ).void
30
+ end
31
+ def initialize(dependency_files:, credentials:, dependencies: [])
32
+ @dependency_files = dependency_files
33
+ @credentials = credentials
34
+ @dependencies = dependencies
35
+ end
36
+
37
+ # PROXY WORK
38
+ sig { returns(String) }
39
+ def npmrc_content
40
+ initial_content =
41
+ if npmrc_file then complete_npmrc_from_credentials
42
+ else
43
+ build_npmrc_content_from_lockfile
44
+ end
45
+
46
+ final_content = initial_content || ""
47
+
48
+ return final_content unless registry_credentials.any?
49
+
50
+ credential_lines_for_npmrc.each do |credential_line|
51
+ next if final_content.include?(credential_line)
52
+
53
+ final_content = [final_content, credential_line].reject(&:empty?).join("\n")
54
+ end
55
+
56
+ final_content
57
+ end
58
+
59
+ private
60
+
61
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
62
+ attr_reader :dependency_files
63
+
64
+ sig { returns(T::Array[Dependabot::Credential]) }
65
+ attr_reader :credentials
66
+
67
+ sig { returns(T::Array[Dependabot::Dependency]) }
68
+ attr_reader :dependencies
69
+
70
+ sig { returns(T.nilable(String)) }
71
+ def build_npmrc_content_from_lockfile
72
+ return unless yarn_lock || package_lock || shrinkwrap
73
+ return unless global_registry
74
+
75
+ registry = T.must(global_registry)["registry"]
76
+ registry = "https://#{registry}" unless registry&.start_with?("http")
77
+ "registry = #{registry}\n" \
78
+ "#{npmrc_global_registry_auth_line}" \
79
+ "always-auth = true"
80
+ end
81
+
82
+ sig { returns(T.nilable(String)) }
83
+ def build_yarnrc_content_from_lockfile
84
+ return unless yarn_lock || package_lock
85
+ return unless global_registry
86
+
87
+ "registry \"https://#{T.must(global_registry)['registry']}\"\n" \
88
+ "#{yarnrc_global_registry_auth_line}" \
89
+ "npmAlwaysAuth: true"
90
+ end
91
+
92
+ # rubocop:disable Metrics/PerceivedComplexity
93
+ # rubocop:disable Metrics/CyclomaticComplexity
94
+ # rubocop:disable Metrics/AbcSize
95
+ sig { returns(T.nilable(Dependabot::Credential)) }
96
+ def global_registry
97
+ return @global_registry if defined?(@global_registry)
98
+
99
+ @global_registry = T.let(
100
+ registry_credentials.find do |cred|
101
+ next false if CENTRAL_REGISTRIES.include?(cred["registry"])
102
+
103
+ # If all the URLs include this registry, it's global
104
+ next true if dependency_urls&.size&.positive? && dependency_urls&.all? do |url|
105
+ url.include?(T.must(cred["registry"]))
106
+ end
107
+
108
+ # Check if this registry has already been defined in .npmrc as a scoped registry
109
+ next false if npmrc_scoped_registries&.any? { |sr| sr.include?(T.must(cred["registry"])) }
110
+
111
+ next false if yarnrc_scoped_registries&.any? { |sr| sr.include?(T.must(cred["registry"])) }
112
+
113
+ # If any unscoped URLs include this registry, assume it's global
114
+ dependency_urls
115
+ &.reject { |u| u.include?("@") || u.include?("%40") }
116
+ &.any? { |url| url.include?(T.must(cred["registry"])) }
117
+ end,
118
+ T.nilable(Dependabot::Credential)
119
+ )
120
+ end
121
+ # rubocop:enable Metrics/PerceivedComplexity
122
+ # rubocop:enable Metrics/CyclomaticComplexity
123
+ # rubocop:enable Metrics/AbcSize
124
+
125
+ sig { returns(String) }
126
+ def npmrc_global_registry_auth_line
127
+ # This token is passed in from the Dependabot Config
128
+ # We write it to the .npmrc file so that it can be used by the VulnerabilityAuditor
129
+ token = global_registry&.fetch("token", nil)
130
+ return "" unless token
131
+
132
+ auth_line(token, global_registry&.fetch("registry")) + "\n"
133
+ end
134
+
135
+ sig { returns(String) }
136
+ def yarnrc_global_registry_auth_line
137
+ token = global_registry&.fetch("token", nil)
138
+ return "" unless token
139
+
140
+ if token.include?(":")
141
+ encoded_token = Base64.encode64(token).delete("\n")
142
+ "npmAuthIdent: \"#{encoded_token}\""
143
+ elsif Base64.decode64(token).ascii_only? &&
144
+ Base64.decode64(token).include?(":")
145
+ "npmAuthIdent: \"#{token.delete("\n")}\""
146
+ else
147
+ "npmAuthToken: \"#{token}\""
148
+ end
149
+ end
150
+
151
+ sig { returns(T.nilable(T::Array[String])) }
152
+ def dependency_urls
153
+ return @dependency_urls if defined?(@dependency_urls)
154
+
155
+ @dependency_urls = []
156
+
157
+ if dependencies.any?
158
+ @dependency_urls = dependencies.map do |dependency|
159
+ UpdateChecker::RegistryFinder.new(
160
+ dependency: dependency,
161
+ credentials: credentials,
162
+ rc_file: npmrc_file
163
+ ).dependency_url
164
+ end
165
+ return @dependency_urls
166
+ end
167
+
168
+ # The registry URL for Bintray goes into the lockfile in a
169
+ # modified format, so we modify it back before checking against
170
+ # our credentials
171
+ @dependency_urls = T.let(
172
+ @dependency_urls.map do |url|
173
+ url.gsub("dl.bintray.com//", "api.bintray.com/npm/")
174
+ end,
175
+ T.nilable(T::Array[String])
176
+ )
177
+ end
178
+
179
+ sig { returns(String) }
180
+ def complete_npmrc_from_credentials
181
+ # removes attribute timeout to allow for job update,
182
+ # having a timeout=xxxxx value is causing some jobs to fail
183
+ initial_content = T.must(T.must(npmrc_file).content)
184
+ .gsub(/^.*\$\{.*\}.*/, "").strip.gsub(/^timeout.*/, "").strip + "\n"
185
+
186
+ return initial_content unless yarn_lock || package_lock
187
+ return initial_content unless global_registry
188
+
189
+ registry = T.must(global_registry)["registry"]
190
+ registry = "https://#{registry}" unless registry&.start_with?("http")
191
+ initial_content +
192
+ "registry = #{registry}\n" \
193
+ "#{npmrc_global_registry_auth_line}" \
194
+ "always-auth = true\n"
195
+ end
196
+
197
+ sig { returns(String) }
198
+ def complete_yarnrc_from_credentials
199
+ initial_content = T.must(T.must(yarnrc_file).content)
200
+ .gsub(/^.*\$\{.*\}.*/, "").strip + "\n"
201
+ return initial_content unless yarn_lock || package_lock
202
+ return initial_content unless global_registry
203
+
204
+ initial_content +
205
+ "registry: \"https://#{T.must(global_registry)['registry']}\"\n" \
206
+ "#{yarnrc_global_registry_auth_line}" \
207
+ "npmAlwaysAuth: true\n"
208
+ end
209
+
210
+ sig { returns(T.nilable(String)) }
211
+ def build_npmrc_from_yarnrc
212
+ yarnrc_global_registry =
213
+ yarnrc_file&.content
214
+ &.lines
215
+ &.find { |line| line.match?(/^\s*registry\s/) }
216
+ &.match(UpdateChecker::RegistryFinder::YARN_GLOBAL_REGISTRY_REGEX)
217
+ &.named_captures&.fetch("registry")
218
+
219
+ return "registry = #{yarnrc_global_registry}\n" if yarnrc_global_registry
220
+
221
+ build_npmrc_content_from_lockfile
222
+ end
223
+
224
+ sig { returns(T.nilable(String)) }
225
+ def build_yarnrc_from_yarnrc
226
+ yarnrc_global_registry =
227
+ yarnrc_file&.content
228
+ &.lines
229
+ &.find { |line| line.match?(/^\s*registry\s/) }
230
+ &.match(/^\s*registry\s+"(?<registry>[^"]+)"/)
231
+ &.named_captures&.fetch("registry")
232
+
233
+ return "registry \"#{yarnrc_global_registry}\"\n" if yarnrc_global_registry
234
+
235
+ build_yarnrc_content_from_lockfile
236
+ end
237
+
238
+ sig { returns(T::Array[String]) }
239
+ def credential_lines_for_npmrc
240
+ lines = T.let([], T::Array[String])
241
+ registry_credentials.each do |cred|
242
+ registry = cred.fetch("registry")
243
+
244
+ lines += T.must(registry_scopes(registry)) if registry_scopes(registry)
245
+
246
+ token = cred.fetch("token", nil)
247
+ next unless token
248
+
249
+ lines << auth_line(token, registry)
250
+ end
251
+
252
+ return lines unless lines.any? { |str| str.include?("auth=") }
253
+
254
+ # Work around a suspected yarn bug
255
+ ["always-auth = true"] + lines
256
+ end
257
+
258
+ sig { params(token: String, registry: T.nilable(String)).returns(String) }
259
+ def auth_line(token, registry = nil)
260
+ auth = if token.include?(":")
261
+ encoded_token = Base64.encode64(token).delete("\n")
262
+ "_auth=#{encoded_token}"
263
+ elsif Base64.decode64(token).ascii_only? &&
264
+ Base64.decode64(token).include?(":")
265
+ "_auth=#{token.delete("\n")}"
266
+ else
267
+ "_authToken=#{token}"
268
+ end
269
+
270
+ return auth unless registry
271
+
272
+ # We need to ensure the registry uri ends with a trailing slash in the npmrc file
273
+ # but we do not want to add one if it already exists
274
+ registry_with_trailing_slash = registry.sub(%r{\/?$}, "/")
275
+
276
+ "//#{registry_with_trailing_slash}:#{auth}"
277
+ end
278
+
279
+ sig { returns(T.nilable(T::Array[String])) }
280
+ def npmrc_scoped_registries
281
+ return [] unless npmrc_file
282
+
283
+ @npmrc_scoped_registries ||= T.let(
284
+ T.must(T.must(npmrc_file).content).lines.select { |line| line.match?(SCOPED_REGISTRY) }
285
+ .filter_map { |line| line.match(SCOPED_REGISTRY)&.named_captures&.fetch("registry") },
286
+ T.nilable(T::Array[String])
287
+ )
288
+ end
289
+
290
+ sig { returns(T.nilable(T::Array[String])) }
291
+ def yarnrc_scoped_registries
292
+ return [] unless yarnrc_file
293
+
294
+ @yarnrc_scoped_registries ||= T.let(
295
+ T.must(T.must(yarnrc_file).content).lines.select { |line| line.match?(SCOPED_REGISTRY) }
296
+ .filter_map { |line| line.match(SCOPED_REGISTRY)&.named_captures&.fetch("registry") },
297
+ T.nilable(T::Array[String])
298
+ )
299
+ end
300
+
301
+ # rubocop:disable Metrics/PerceivedComplexity
302
+ sig { params(registry: String).returns(T.nilable(T::Array[String])) }
303
+ def registry_scopes(registry)
304
+ # Central registries don't just apply to scopes
305
+ return if CENTRAL_REGISTRIES.include?(registry)
306
+ return unless dependency_urls
307
+
308
+ other_regs =
309
+ registry_credentials.map { |c| c.fetch("registry") } -
310
+ [registry]
311
+ affected_urls =
312
+ dependency_urls
313
+ &.select do |url|
314
+ next false unless url.include?(registry)
315
+
316
+ other_regs.none? { |r| r.include?(registry) && url.include?(r) }
317
+ end
318
+
319
+ scopes = T.must(affected_urls).map do |url|
320
+ url.split(/\%40|@/)[1]&.split(%r{\%2[fF]|/})&.first
321
+ end.uniq
322
+
323
+ # Registry used for unscoped packages
324
+ return if scopes.include?(nil)
325
+
326
+ scopes.map { |scope| "@#{scope}:registry=https://#{registry}" }
327
+ end
328
+ # rubocop:enable Metrics/PerceivedComplexity
329
+
330
+ sig { returns(T::Array[Dependabot::Credential]) }
331
+ def registry_credentials
332
+ credentials.select { |cred| cred.fetch("type") == "npm_registry" }
333
+ end
334
+
335
+ sig { returns(T.nilable(T::Hash[String, T.untyped])) }
336
+ def parsed_package_lock
337
+ @parsed_package_lock ||= T.let(
338
+ JSON.parse(T.must(T.must(package_lock).content)),
339
+ T.nilable(T::Hash[String, T.untyped])
340
+ )
341
+ end
342
+
343
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
344
+ def npmrc_file
345
+ @npmrc_file ||= T.let(
346
+ dependency_files.find { |f| f.name.end_with?(".npmrc") },
347
+ T.nilable(Dependabot::DependencyFile)
348
+ )
349
+ end
350
+
351
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
352
+ def yarnrc_file
353
+ @yarnrc_file ||= T.let(
354
+ dependency_files.find { |f| f.name.end_with?(".yarnrc") },
355
+ T.nilable(Dependabot::DependencyFile)
356
+ )
357
+ end
358
+
359
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
360
+ def yarnrc_yml_file
361
+ @yarnrc_yml_file ||= T.let(
362
+ dependency_files.find { |f| f.name.end_with?(".yarnrc.yml") },
363
+ T.nilable(Dependabot::DependencyFile)
364
+ )
365
+ end
366
+
367
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
368
+ def yarn_lock
369
+ @yarn_lock ||= T.let(
370
+ dependency_files.find { |f| f.name == "yarn.lock" },
371
+ T.nilable(Dependabot::DependencyFile)
372
+ )
373
+ end
374
+
375
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
376
+ def package_lock
377
+ @package_lock ||= T.let(
378
+ dependency_files.find { |f| f.name == "package-lock.json" },
379
+ T.nilable(Dependabot::DependencyFile)
380
+ )
381
+ end
382
+
383
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
384
+ def shrinkwrap
385
+ @shrinkwrap ||= T.let(
386
+ dependency_files.find { |f| f.name == "npm-shrinkwrap.json" },
387
+ T.nilable(Dependabot::DependencyFile)
388
+ )
389
+ end
390
+ end
391
+ end
392
+ end
393
+ end
394
+ end
@@ -0,0 +1,87 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Shared
7
+ class FileUpdater
8
+ class PackageJsonPreparer
9
+ def initialize(package_json_content:)
10
+ @package_json_content = package_json_content
11
+ end
12
+
13
+ def prepared_content
14
+ content = package_json_content
15
+ content = replace_ssh_sources(content)
16
+ content = remove_workspace_path_prefixes(content)
17
+ content = remove_invalid_characters(content)
18
+ content
19
+ end
20
+
21
+ def replace_ssh_sources(content)
22
+ updated_content = content
23
+
24
+ git_ssh_requirements_to_swap.each do |req|
25
+ new_req = req.gsub(%r{git\+ssh://git@(.*?)[:/]}, 'https://\1/')
26
+ updated_content = updated_content.gsub(req, new_req)
27
+ end
28
+
29
+ updated_content
30
+ end
31
+
32
+ # A bug prevents Yarn recognising that a directory is part of a
33
+ # workspace if it is specified with a `./` prefix.
34
+ def remove_workspace_path_prefixes(content)
35
+ json = JSON.parse(content)
36
+ return content unless json.key?("workspaces")
37
+
38
+ workspace_object = json.fetch("workspaces")
39
+ paths_array =
40
+ if workspace_object.is_a?(Hash)
41
+ workspace_object.values_at("packages", "nohoist")
42
+ .flatten.compact
43
+ elsif workspace_object.is_a?(Array) then workspace_object
44
+ else
45
+ raise "Unexpected workspace object"
46
+ end
47
+
48
+ paths_array.each { |path| path.gsub!(%r{^\./}, "") }
49
+
50
+ JSON.pretty_generate(json)
51
+ end
52
+
53
+ def remove_invalid_characters(content)
54
+ content
55
+ .gsub(/\{\{[^\}]*?\}\}/, "something") # {{ nm }} syntax not allowed
56
+ .gsub(/(?<!\\)\\ /, " ") # escaped whitespace not allowed
57
+ .gsub(%r{^\s*//.*}, " ") # comments are not allowed
58
+ end
59
+
60
+ def swapped_ssh_requirements
61
+ git_ssh_requirements_to_swap
62
+ end
63
+
64
+ private
65
+
66
+ attr_reader :package_json_content
67
+
68
+ def git_ssh_requirements_to_swap
69
+ return @git_ssh_requirements_to_swap if @git_ssh_requirements_to_swap
70
+
71
+ @git_ssh_requirements_to_swap = []
72
+
73
+ FileParser.each_dependency(JSON.parse(package_json_content)) do |_, req, _t|
74
+ next unless req.is_a?(String)
75
+ next unless req.start_with?("git+ssh:")
76
+
77
+ req = req.split("#").first
78
+ @git_ssh_requirements_to_swap << req
79
+ end
80
+
81
+ @git_ssh_requirements_to_swap
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end