dependabot-bun 0.296.2 → 0.297.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/.eslintrc +11 -0
  3. data/helpers/README.md +29 -0
  4. data/helpers/build +26 -0
  5. data/helpers/jest.config.js +5 -0
  6. data/helpers/lib/npm/conflicting-dependency-parser.js +78 -0
  7. data/helpers/lib/npm/index.js +9 -0
  8. data/helpers/lib/npm/vulnerability-auditor.js +291 -0
  9. data/helpers/lib/npm6/helpers.js +25 -0
  10. data/helpers/lib/npm6/index.js +9 -0
  11. data/helpers/lib/npm6/peer-dependency-checker.js +111 -0
  12. data/helpers/lib/npm6/remove-dependencies-from-lockfile.js +22 -0
  13. data/helpers/lib/npm6/subdependency-updater.js +78 -0
  14. data/helpers/lib/npm6/updater.js +199 -0
  15. data/helpers/lib/pnpm/index.js +5 -0
  16. data/helpers/lib/pnpm/lockfile-parser.js +82 -0
  17. data/helpers/lib/yarn/conflicting-dependency-parser.js +176 -0
  18. data/helpers/lib/yarn/fix-duplicates.js +80 -0
  19. data/helpers/lib/yarn/helpers.js +54 -0
  20. data/helpers/lib/yarn/index.js +14 -0
  21. data/helpers/lib/yarn/lockfile-parser.js +21 -0
  22. data/helpers/lib/yarn/peer-dependency-checker.js +132 -0
  23. data/helpers/lib/yarn/replace-lockfile-declaration.js +57 -0
  24. data/helpers/lib/yarn/subdependency-updater.js +83 -0
  25. data/helpers/lib/yarn/updater.js +209 -0
  26. data/helpers/package-lock.json +28519 -0
  27. data/helpers/package.json +29 -0
  28. data/helpers/patches/npm++pacote+9.5.12.patch +14 -0
  29. data/helpers/run.js +30 -0
  30. data/helpers/test/npm6/conflicting-dependency-parser.test.js +66 -0
  31. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package-lock.json +591 -0
  32. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package.json +14 -0
  33. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/nested/package-lock.json +188 -0
  34. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/nested/package.json +14 -0
  35. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/simple/package-lock.json +27 -0
  36. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/simple/package.json +14 -0
  37. data/helpers/test/npm6/fixtures/updater/original/package-lock.json +16 -0
  38. data/helpers/test/npm6/fixtures/updater/original/package.json +9 -0
  39. data/helpers/test/npm6/fixtures/updater/updated/package-lock.json +16 -0
  40. data/helpers/test/npm6/helpers.js +21 -0
  41. data/helpers/test/npm6/updater.test.js +30 -0
  42. data/helpers/test/pnpm/fixtures/parser/empty_version/pnpm-lock.yaml +72 -0
  43. data/helpers/test/pnpm/fixtures/parser/no_lockfile_change/pnpm-lock.yaml +2744 -0
  44. data/helpers/test/pnpm/fixtures/parser/only_dev_dependencies/pnpm-lock.yaml +16 -0
  45. data/helpers/test/pnpm/fixtures/parser/peer_disambiguation/pnpm-lock.yaml +855 -0
  46. data/helpers/test/pnpm/lockfile-parser.test.js +62 -0
  47. data/helpers/test/yarn/conflicting-dependency-parser.test.js +83 -0
  48. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/package.json +14 -0
  49. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/yarn.lock +496 -0
  50. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/dev-dependencies/package.json +14 -0
  51. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/dev-dependencies/yarn.lock +21 -0
  52. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/nested/package.json +14 -0
  53. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/nested/yarn.lock +183 -0
  54. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/simple/package.json +14 -0
  55. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/simple/yarn.lock +21 -0
  56. data/helpers/test/yarn/fixtures/updater/illegal_character/package.json +8 -0
  57. data/helpers/test/yarn/fixtures/updater/illegal_character/yarn.lock +14 -0
  58. data/helpers/test/yarn/fixtures/updater/original/package.json +6 -0
  59. data/helpers/test/yarn/fixtures/updater/original/yarn.lock +11 -0
  60. data/helpers/test/yarn/fixtures/updater/updated/yarn.lock +12 -0
  61. data/helpers/test/yarn/fixtures/updater/with-version-comments/package.json +5 -0
  62. data/helpers/test/yarn/fixtures/updater/with-version-comments/yarn.lock +13 -0
  63. data/helpers/test/yarn/helpers.js +18 -0
  64. data/helpers/test/yarn/updater.test.js +117 -0
  65. data/lib/dependabot/bun/bun_package_manager.rb +47 -0
  66. data/lib/dependabot/bun/constraint_helper.rb +359 -0
  67. data/lib/dependabot/bun/dependency_files_filterer.rb +157 -0
  68. data/lib/dependabot/bun/file_fetcher/path_dependency_builder.rb +184 -0
  69. data/lib/dependabot/bun/file_fetcher.rb +402 -0
  70. data/lib/dependabot/bun/file_parser/bun_lock.rb +140 -0
  71. data/lib/dependabot/bun/file_parser/lockfile_parser.rb +105 -0
  72. data/lib/dependabot/bun/file_parser.rb +477 -0
  73. data/lib/dependabot/bun/file_updater/bun_lockfile_updater.rb +144 -0
  74. data/lib/dependabot/bun/file_updater/npmrc_builder.rb +256 -0
  75. data/lib/dependabot/bun/file_updater/package_json_preparer.rb +88 -0
  76. data/lib/dependabot/bun/file_updater/package_json_updater.rb +378 -0
  77. data/lib/dependabot/bun/file_updater.rb +203 -0
  78. data/lib/dependabot/bun/helpers.rb +93 -0
  79. data/lib/dependabot/bun/language.rb +45 -0
  80. data/lib/dependabot/bun/metadata_finder.rb +214 -0
  81. data/lib/dependabot/bun/native_helpers.rb +19 -0
  82. data/lib/dependabot/bun/package_manager.rb +280 -0
  83. data/lib/dependabot/bun/package_name.rb +118 -0
  84. data/lib/dependabot/bun/pnpm_package_manager.rb +55 -0
  85. data/lib/dependabot/bun/registry_helper.rb +188 -0
  86. data/lib/dependabot/bun/registry_parser.rb +93 -0
  87. data/lib/dependabot/bun/requirement.rb +146 -0
  88. data/lib/dependabot/bun/sub_dependency_files_filterer.rb +82 -0
  89. data/lib/dependabot/bun/update_checker/conflicting_dependency_resolver.rb +59 -0
  90. data/lib/dependabot/bun/update_checker/dependency_files_builder.rb +79 -0
  91. data/lib/dependabot/bun/update_checker/latest_version_finder.rb +448 -0
  92. data/lib/dependabot/bun/update_checker/library_detector.rb +76 -0
  93. data/lib/dependabot/bun/update_checker/registry_finder.rb +279 -0
  94. data/lib/dependabot/bun/update_checker/requirements_updater.rb +206 -0
  95. data/lib/dependabot/bun/update_checker/subdependency_version_resolver.rb +154 -0
  96. data/lib/dependabot/bun/update_checker/version_resolver.rb +583 -0
  97. data/lib/dependabot/bun/update_checker/vulnerability_auditor.rb +164 -0
  98. data/lib/dependabot/bun/update_checker.rb +455 -0
  99. data/lib/dependabot/bun/version.rb +138 -0
  100. data/lib/dependabot/bun/version_selector.rb +61 -0
  101. data/lib/dependabot/bun.rb +337 -35
  102. metadata +108 -65
  103. data/lib/dependabot/javascript/bun/file_fetcher.rb +0 -77
  104. data/lib/dependabot/javascript/bun/file_parser/bun_lock.rb +0 -156
  105. data/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +0 -55
  106. data/lib/dependabot/javascript/bun/file_parser.rb +0 -74
  107. data/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb +0 -138
  108. data/lib/dependabot/javascript/bun/file_updater.rb +0 -75
  109. data/lib/dependabot/javascript/bun/helpers.rb +0 -72
  110. data/lib/dependabot/javascript/bun/package_manager.rb +0 -48
  111. data/lib/dependabot/javascript/bun/requirement.rb +0 -11
  112. data/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +0 -64
  113. data/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb +0 -47
  114. data/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb +0 -450
  115. data/lib/dependabot/javascript/bun/update_checker/library_detector.rb +0 -76
  116. data/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb +0 -203
  117. data/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb +0 -144
  118. data/lib/dependabot/javascript/bun/update_checker/version_resolver.rb +0 -525
  119. data/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb +0 -165
  120. data/lib/dependabot/javascript/bun/update_checker.rb +0 -440
  121. data/lib/dependabot/javascript/bun/version.rb +0 -11
  122. data/lib/dependabot/javascript/shared/constraint_helper.rb +0 -359
  123. data/lib/dependabot/javascript/shared/dependency_files_filterer.rb +0 -164
  124. data/lib/dependabot/javascript/shared/file_fetcher.rb +0 -283
  125. data/lib/dependabot/javascript/shared/file_parser/lockfile_parser.rb +0 -106
  126. data/lib/dependabot/javascript/shared/file_parser.rb +0 -454
  127. data/lib/dependabot/javascript/shared/file_updater/npmrc_builder.rb +0 -394
  128. data/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb +0 -87
  129. data/lib/dependabot/javascript/shared/file_updater/package_json_updater.rb +0 -376
  130. data/lib/dependabot/javascript/shared/file_updater.rb +0 -179
  131. data/lib/dependabot/javascript/shared/language.rb +0 -45
  132. data/lib/dependabot/javascript/shared/metadata_finder.rb +0 -209
  133. data/lib/dependabot/javascript/shared/native_helpers.rb +0 -21
  134. data/lib/dependabot/javascript/shared/package_manager_detector.rb +0 -72
  135. data/lib/dependabot/javascript/shared/package_name.rb +0 -118
  136. data/lib/dependabot/javascript/shared/registry_helper.rb +0 -190
  137. data/lib/dependabot/javascript/shared/registry_parser.rb +0 -93
  138. data/lib/dependabot/javascript/shared/requirement.rb +0 -144
  139. data/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb +0 -79
  140. data/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb +0 -87
  141. data/lib/dependabot/javascript/shared/update_checker/registry_finder.rb +0 -358
  142. data/lib/dependabot/javascript/shared/version.rb +0 -133
  143. data/lib/dependabot/javascript/shared/version_selector.rb +0 -60
  144. data/lib/dependabot/javascript.rb +0 -39
@@ -0,0 +1,79 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/bun/file_updater/npmrc_builder"
5
+ require "dependabot/bun/file_updater/package_json_preparer"
6
+
7
+ module Dependabot
8
+ module Bun
9
+ class UpdateChecker
10
+ class DependencyFilesBuilder
11
+ def initialize(dependency:, dependency_files:, credentials:)
12
+ @dependency = dependency
13
+ @dependency_files = dependency_files
14
+ @credentials = credentials
15
+ end
16
+
17
+ def write_temporary_dependency_files
18
+ write_lockfiles
19
+
20
+ File.write(".npmrc", npmrc_content)
21
+
22
+ package_files.each do |file|
23
+ path = file.name
24
+ FileUtils.mkdir_p(Pathname.new(path).dirname)
25
+ File.write(file.name, prepared_package_json_content(file))
26
+ end
27
+ end
28
+
29
+ def bun_locks
30
+ @bun_locks ||=
31
+ dependency_files
32
+ .select { |f| f.name.end_with?("bun.lock") }
33
+ end
34
+
35
+ def root_bun_lock
36
+ @root_bun_lock ||=
37
+ dependency_files
38
+ .find { |f| f.name == "bun.lock" }
39
+ end
40
+
41
+ def lockfiles
42
+ [*bun_locks]
43
+ end
44
+
45
+ def package_files
46
+ @package_files ||=
47
+ dependency_files
48
+ .select { |f| f.name.end_with?("package.json") }
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :dependency
54
+ attr_reader :dependency_files
55
+ attr_reader :credentials
56
+
57
+ def write_lockfiles
58
+ bun_locks.each do |f|
59
+ FileUtils.mkdir_p(Pathname.new(f.name).dirname)
60
+ File.write(f.name, f.content)
61
+ end
62
+ end
63
+
64
+ def prepared_package_json_content(file)
65
+ Bun::FileUpdater::PackageJsonPreparer.new(
66
+ package_json_content: file.content
67
+ ).prepared_content
68
+ end
69
+
70
+ def npmrc_content
71
+ Bun::FileUpdater::NpmrcBuilder.new(
72
+ credentials: credentials,
73
+ dependency_files: dependency_files
74
+ ).npmrc_content
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,448 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "excon"
5
+ require "dependabot/bun/update_checker"
6
+ require "dependabot/update_checkers/version_filters"
7
+ require "dependabot/bun/update_checker/registry_finder"
8
+ require "dependabot/bun/version"
9
+ require "dependabot/bun/requirement"
10
+ require "dependabot/shared_helpers"
11
+ require "dependabot/errors"
12
+ require "sorbet-runtime"
13
+
14
+ module Dependabot
15
+ module Bun
16
+ class UpdateChecker
17
+ class LatestVersionFinder
18
+ extend T::Sig
19
+
20
+ def initialize(dependency:, credentials:, dependency_files:,
21
+ ignored_versions:, security_advisories:,
22
+ raise_on_ignored: false)
23
+ @dependency = dependency
24
+ @credentials = credentials
25
+ @dependency_files = dependency_files
26
+ @ignored_versions = ignored_versions
27
+ @raise_on_ignored = raise_on_ignored
28
+ @security_advisories = security_advisories
29
+ end
30
+
31
+ def latest_version_from_registry
32
+ return unless valid_npm_details?
33
+ return version_from_dist_tags if version_from_dist_tags
34
+ return if specified_dist_tag_requirement?
35
+
36
+ possible_versions.find { |v| !yanked?(v) }
37
+ rescue Excon::Error::Socket, Excon::Error::Timeout, RegistryError
38
+ raise if dependency_registry == "registry.npmjs.org"
39
+ # Custom registries can be flaky. We don't want to make that
40
+ # our problem, so we quietly return `nil` here.
41
+ end
42
+
43
+ def latest_version_with_no_unlock
44
+ return unless valid_npm_details?
45
+ return version_from_dist_tags if specified_dist_tag_requirement?
46
+
47
+ in_range_versions = filter_out_of_range_versions(possible_versions)
48
+ in_range_versions.find { |version| !yanked?(version) }
49
+ rescue Excon::Error::Socket, Excon::Error::Timeout
50
+ raise if dependency_registry == "registry.npmjs.org"
51
+ # Sometimes custom registries are flaky. We don't want to make that
52
+ # our problem, so we quietly return `nil` here.
53
+ end
54
+
55
+ def lowest_security_fix_version
56
+ return unless valid_npm_details?
57
+
58
+ secure_versions =
59
+ if specified_dist_tag_requirement?
60
+ [version_from_dist_tags].compact
61
+ else
62
+ possible_versions(filter_ignored: false)
63
+ end
64
+
65
+ secure_versions = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions(secure_versions,
66
+ security_advisories)
67
+ secure_versions = filter_ignored_versions(secure_versions)
68
+ secure_versions = filter_lower_versions(secure_versions)
69
+
70
+ secure_versions.reverse.find { |version| !yanked?(version) }
71
+ rescue Excon::Error::Socket, Excon::Error::Timeout
72
+ raise if dependency_registry == "registry.npmjs.org"
73
+ # Sometimes custom registries are flaky. We don't want to make that
74
+ # our problem, so we quietly return `nil` here.
75
+ end
76
+
77
+ def possible_previous_versions_with_details
78
+ @possible_previous_versions_with_details ||= npm_details.fetch("versions", {})
79
+ .transform_keys { |k| version_class.new(k) }
80
+ .reject do |v, _|
81
+ v.prerelease? && !related_to_current_pre?(v)
82
+ end
83
+ .sort_by(&:first).reverse
84
+ end
85
+
86
+ def possible_versions_with_details(filter_ignored: true)
87
+ versions = possible_previous_versions_with_details
88
+ .reject { |_, details| details["deprecated"] }
89
+
90
+ return filter_ignored_versions(versions) if filter_ignored
91
+
92
+ versions
93
+ end
94
+
95
+ def possible_versions(filter_ignored: true)
96
+ possible_versions_with_details(filter_ignored: filter_ignored)
97
+ .map(&:first)
98
+ end
99
+
100
+ private
101
+
102
+ attr_reader :dependency
103
+ attr_reader :credentials
104
+ attr_reader :dependency_files
105
+ attr_reader :ignored_versions
106
+ attr_reader :security_advisories
107
+
108
+ def valid_npm_details?
109
+ !npm_details&.fetch("dist-tags", nil).nil?
110
+ end
111
+
112
+ sig { params(versions_array: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
113
+ def filter_ignored_versions(versions_array)
114
+ filtered = versions_array.reject do |v, _|
115
+ ignore_requirements.any? { |r| r.satisfied_by?(v) }
116
+ end
117
+
118
+ if @raise_on_ignored && filter_lower_versions(filtered).empty? && filter_lower_versions(versions_array).any?
119
+ raise AllVersionsIgnored
120
+ end
121
+
122
+ if versions_array.count > filtered.count
123
+ diff = versions_array.count - filtered.count
124
+ Dependabot.logger.info("Filtered out #{diff} ignored versions")
125
+ end
126
+
127
+ filtered
128
+ end
129
+
130
+ sig { params(versions_array: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
131
+ def filter_out_of_range_versions(versions_array)
132
+ reqs = dependency.requirements.filter_map do |r|
133
+ Bun::Requirement.requirements_array(r.fetch(:requirement))
134
+ end
135
+
136
+ versions_array
137
+ .select { |v| reqs.all? { |r| r.any? { |o| o.satisfied_by?(v) } } }
138
+ end
139
+
140
+ sig { params(versions_array: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
141
+ def filter_lower_versions(versions_array)
142
+ return versions_array unless dependency.numeric_version
143
+
144
+ versions_array
145
+ .select { |version, _| version > dependency.numeric_version }
146
+ end
147
+
148
+ def version_from_dist_tags
149
+ dist_tags = npm_details["dist-tags"].keys
150
+
151
+ # Check if a dist tag was specified as a requirement. If it was, and
152
+ # it exists, use it.
153
+ dist_tag_req = dependency.requirements
154
+ .find { |r| dist_tags.include?(r[:requirement]) }
155
+ &.fetch(:requirement)
156
+
157
+ if dist_tag_req
158
+ tag_vers =
159
+ version_class.new(npm_details["dist-tags"][dist_tag_req])
160
+ return tag_vers unless yanked?(tag_vers)
161
+ end
162
+
163
+ # Use the latest dist tag unless there's a reason not to
164
+ return nil unless npm_details["dist-tags"]["latest"]
165
+
166
+ latest = version_class.new(npm_details["dist-tags"]["latest"])
167
+
168
+ wants_latest_dist_tag?(latest) ? latest : nil
169
+ end
170
+
171
+ def related_to_current_pre?(version)
172
+ current_version = dependency.numeric_version
173
+ if current_version&.prerelease? &&
174
+ current_version&.release == version.release
175
+ return true
176
+ end
177
+
178
+ dependency.requirements.any? do |req|
179
+ next unless req[:requirement]&.match?(/\d-[A-Za-z]/)
180
+
181
+ Bun::Requirement
182
+ .requirements_array(req.fetch(:requirement))
183
+ .any? do |r|
184
+ r.requirements.any? { |a| a.last.release == version.release }
185
+ end
186
+ rescue Gem::Requirement::BadRequirementError
187
+ false
188
+ end
189
+ end
190
+
191
+ def specified_dist_tag_requirement?
192
+ dependency.requirements.any? do |req|
193
+ next false if req[:requirement].nil?
194
+ next false unless req[:requirement].match?(/^[A-Za-z]/)
195
+
196
+ !req[:requirement].match?(/^v\d/i)
197
+ end
198
+ end
199
+
200
+ def wants_latest_dist_tag?(latest_version)
201
+ ver = latest_version
202
+ return false if related_to_current_pre?(ver) ^ ver.prerelease?
203
+ return false if current_version_greater_than?(ver)
204
+ return false if current_requirement_greater_than?(ver)
205
+ return false if ignore_requirements.any? { |r| r.satisfied_by?(ver) }
206
+ return false if yanked?(ver)
207
+
208
+ true
209
+ end
210
+
211
+ def current_version_greater_than?(version)
212
+ return false unless dependency.numeric_version
213
+
214
+ dependency.numeric_version > version
215
+ end
216
+
217
+ def current_requirement_greater_than?(version)
218
+ dependency.requirements.any? do |req|
219
+ next false unless req[:requirement]
220
+
221
+ req_version = req[:requirement].sub(/^\^|~|>=?/, "")
222
+ next false unless version_class.correct?(req_version)
223
+
224
+ version_class.new(req_version) > version
225
+ end
226
+ end
227
+
228
+ def yanked?(version)
229
+ @yanked ||= {}
230
+ return @yanked[version] if @yanked.key?(version)
231
+
232
+ @yanked[version] =
233
+ begin
234
+ if dependency_registry == "registry.npmjs.org"
235
+ status = Dependabot::RegistryClient.head(
236
+ url: registry_finder.tarball_url(version),
237
+ headers: registry_auth_headers
238
+ ).status
239
+ else
240
+ status = Dependabot::RegistryClient.get(
241
+ url: dependency_url + "/#{version}",
242
+ headers: registry_auth_headers
243
+ ).status
244
+
245
+ if status == 404
246
+ # Some registries don't handle escaped package names properly
247
+ status = Dependabot::RegistryClient.get(
248
+ url: dependency_url.gsub("%2F", "/") + "/#{version}",
249
+ headers: registry_auth_headers
250
+ ).status
251
+ end
252
+ end
253
+
254
+ version_not_found = status == 404
255
+ version_not_found && version_endpoint_working?
256
+ rescue Excon::Error::Timeout, Excon::Error::Socket
257
+ # Give the benefit of the doubt if the registry is playing up
258
+ false
259
+ end
260
+ end
261
+
262
+ def version_endpoint_working?
263
+ return true if dependency_registry == "registry.npmjs.org"
264
+
265
+ return @version_endpoint_working if defined?(@version_endpoint_working)
266
+
267
+ @version_endpoint_working =
268
+ begin
269
+ Dependabot::RegistryClient.get(
270
+ url: dependency_url + "/latest",
271
+ headers: registry_auth_headers
272
+ ).status < 400
273
+ rescue Excon::Error::Timeout, Excon::Error::Socket
274
+ # Give the benefit of the doubt if the registry is playing up
275
+ true
276
+ end
277
+ end
278
+
279
+ def npm_details
280
+ return @npm_details if defined?(@npm_details)
281
+
282
+ @npm_details = fetch_npm_details
283
+ end
284
+
285
+ def fetch_npm_details
286
+ npm_response = fetch_npm_response
287
+
288
+ check_npm_response(npm_response)
289
+ JSON.parse(npm_response.body)
290
+ rescue JSON::ParserError,
291
+ Excon::Error::Timeout,
292
+ Excon::Error::Socket,
293
+ RegistryError => e
294
+ if git_dependency?
295
+ nil
296
+ else
297
+ raise_npm_details_error(e)
298
+ end
299
+ end
300
+
301
+ def fetch_npm_response
302
+ response = Dependabot::RegistryClient.get(
303
+ url: dependency_url,
304
+ headers: registry_auth_headers
305
+ )
306
+ return response unless response.status == 500
307
+ return response unless registry_auth_headers["Authorization"]
308
+
309
+ auth = registry_auth_headers["Authorization"]
310
+ return response unless auth.start_with?("Basic")
311
+
312
+ decoded_token = Base64.decode64(auth.gsub("Basic ", ""))
313
+ return unless decoded_token.include?(":")
314
+
315
+ username, password = decoded_token.split(":")
316
+ Dependabot::RegistryClient.get(
317
+ url: dependency_url,
318
+ options: {
319
+ user: username,
320
+ password: password
321
+ }
322
+ )
323
+ rescue URI::InvalidURIError => e
324
+ raise DependencyFileNotResolvable, e.message
325
+ end
326
+
327
+ def check_npm_response(npm_response)
328
+ return if git_dependency?
329
+
330
+ if private_dependency_not_reachable?(npm_response)
331
+ raise PrivateSourceAuthenticationFailure, dependency_registry
332
+ end
333
+
334
+ # handles scenario when private registry returns a server error 5xx
335
+ if private_dependency_server_error?(npm_response)
336
+ msg = "Server error #{npm_response.status} returned while accessing registry" \
337
+ " #{dependency_registry}."
338
+ raise DependencyFileNotResolvable, msg
339
+ end
340
+
341
+ status = npm_response.status
342
+
343
+ # handles issue when status 200 is returned from registry but with an invalid JSON object
344
+ if status.to_s.start_with?("2") && response_invalid_json?(npm_response)
345
+ msg = "Invalid JSON object returned from registry #{dependency_registry}."
346
+ Dependabot.logger.warn("#{msg} Response body (truncated) : #{npm_response.body[0..500]}...")
347
+ raise DependencyFileNotResolvable, msg
348
+ end
349
+
350
+ return if status.to_s.start_with?("2")
351
+
352
+ # Ignore 404s from the registry for updates where a lockfile doesn't
353
+ # need to be generated. The 404 won't cause problems later.
354
+ return if status == 404 && dependency.version.nil?
355
+
356
+ msg = "Got #{status} response with body #{npm_response.body}"
357
+ raise RegistryError.new(status, msg)
358
+ end
359
+
360
+ def raise_npm_details_error(error)
361
+ raise if dependency_registry == "registry.npmjs.org"
362
+ raise unless error.is_a?(Excon::Error::Timeout)
363
+
364
+ raise PrivateSourceTimedOut, dependency_registry
365
+ end
366
+
367
+ def private_dependency_not_reachable?(npm_response)
368
+ return true if npm_response.body.start_with?(/user ".*?" is not a /)
369
+ return false unless [401, 402, 403, 404].include?(npm_response.status)
370
+
371
+ # Check whether this dependency is (likely to be) private
372
+ if dependency_registry == "registry.npmjs.org"
373
+ return false unless dependency.name.start_with?("@")
374
+
375
+ web_response = Dependabot::RegistryClient.get(url: "https://www.npmjs.com/package/#{dependency.name}")
376
+ # NOTE: returns 429 when the login page is rate limited
377
+ return web_response.body.include?("Forgot password?") ||
378
+ web_response.status == 429
379
+ end
380
+
381
+ true
382
+ end
383
+
384
+ def private_dependency_server_error?(npm_response)
385
+ if [500, 501, 502, 503].include?(npm_response.status)
386
+ Dependabot.logger.warn("#{dependency_registry} returned code #{npm_response.status} with " \
387
+ "body #{npm_response.body}.")
388
+ return true
389
+ end
390
+ false
391
+ end
392
+
393
+ def response_invalid_json?(npm_response)
394
+ result = JSON.parse(npm_response.body)
395
+ result.is_a?(Hash) || result.is_a?(Array)
396
+ false
397
+ rescue JSON::ParserError, TypeError
398
+ true
399
+ end
400
+
401
+ def dependency_url
402
+ registry_finder.dependency_url
403
+ end
404
+
405
+ def dependency_registry
406
+ registry_finder.registry
407
+ end
408
+
409
+ def registry_auth_headers
410
+ registry_finder.auth_headers
411
+ end
412
+
413
+ def registry_finder
414
+ @registry_finder ||= RegistryFinder.new(
415
+ dependency: dependency,
416
+ credentials: credentials,
417
+ npmrc_file: npmrc_file
418
+ )
419
+ end
420
+
421
+ def ignore_requirements
422
+ ignored_versions.flat_map { |req| requirement_class.requirements_array(req) }
423
+ end
424
+
425
+ def version_class
426
+ dependency.version_class
427
+ end
428
+
429
+ def requirement_class
430
+ dependency.requirement_class
431
+ end
432
+
433
+ def npmrc_file
434
+ dependency_files.find { |f| f.name.end_with?(".npmrc") }
435
+ end
436
+
437
+ # TODO: Remove need for me
438
+ def git_dependency?
439
+ # ignored_version/raise_on_ignored are irrelevant.
440
+ GitCommitChecker.new(
441
+ dependency: dependency,
442
+ credentials: credentials
443
+ ).git_dependency?
444
+ end
445
+ end
446
+ end
447
+ end
448
+ end
@@ -0,0 +1,76 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "excon"
5
+ require "dependabot/bun/update_checker"
6
+ require "dependabot/shared_helpers"
7
+
8
+ module Dependabot
9
+ module Bun
10
+ class UpdateChecker
11
+ class LibraryDetector
12
+ def initialize(package_json_file:, credentials:, dependency_files:)
13
+ @package_json_file = package_json_file
14
+ @credentials = credentials
15
+ @dependency_files = dependency_files
16
+ end
17
+
18
+ def library?
19
+ return false unless package_json_may_be_for_library?
20
+
21
+ npm_response_matches_package_json?
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :package_json_file
27
+ attr_reader :credentials
28
+ attr_reader :dependency_files
29
+
30
+ def package_json_may_be_for_library?
31
+ return false unless project_name
32
+ return false if project_name.match?(/\{\{.*\}\}/)
33
+ return false unless parsed_package_json["version"]
34
+ return false if parsed_package_json["private"]
35
+
36
+ true
37
+ end
38
+
39
+ def npm_response_matches_package_json?
40
+ project_description = parsed_package_json["description"]
41
+ return false unless project_description
42
+
43
+ # Check if the project is listed on npm. If it is, it's a library
44
+ url = "#{registry.chomp('/')}/#{escaped_project_name}"
45
+ @project_npm_response ||= Dependabot::RegistryClient.get(url: url)
46
+ return false unless @project_npm_response.status == 200
47
+
48
+ @project_npm_response.body.dup.force_encoding("UTF-8").encode
49
+ .include?(project_description)
50
+ rescue Excon::Error::Socket, Excon::Error::Timeout, URI::InvalidURIError
51
+ false
52
+ end
53
+
54
+ def project_name
55
+ parsed_package_json.fetch("name", nil)
56
+ end
57
+
58
+ def escaped_project_name
59
+ project_name&.gsub("/", "%2F")
60
+ end
61
+
62
+ def parsed_package_json
63
+ @parsed_package_json ||= JSON.parse(package_json_file.content)
64
+ end
65
+
66
+ def registry
67
+ Bun::UpdateChecker::RegistryFinder.new(
68
+ dependency: nil,
69
+ credentials: credentials,
70
+ npmrc_file: dependency_files.find { |f| f.name.end_with?(".npmrc") }
71
+ ).registry_from_rc(project_name)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end