dependabot-bun 0.296.2 → 0.296.3

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,279 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "excon"
5
+ require "dependabot/bun/update_checker"
6
+ require "dependabot/registry_client"
7
+
8
+ module Dependabot
9
+ module Bun
10
+ class UpdateChecker
11
+ class RegistryFinder
12
+ CENTRAL_REGISTRIES = %w(
13
+ https://registry.npmjs.org
14
+ http://registry.npmjs.org
15
+ ).freeze
16
+ NPM_AUTH_TOKEN_REGEX = %r{//(?<registry>.*)/:_authToken=(?<token>.*)$}
17
+ NPM_GLOBAL_REGISTRY_REGEX = /^registry\s*=\s*['"]?(?<registry>.*?)['"]?$/
18
+ NPM_SCOPED_REGISTRY_REGEX = /^(?<scope>@[^:]+)\s*:registry\s*=\s*['"]?(?<registry>.*?)['"]?$/
19
+
20
+ def initialize(dependency:, credentials:, npmrc_file: nil)
21
+ @dependency = dependency
22
+ @credentials = credentials
23
+ @npmrc_file = npmrc_file
24
+ end
25
+
26
+ def registry
27
+ @registry ||= locked_registry || configured_registry || first_registry_with_dependency_details
28
+ end
29
+
30
+ def auth_headers
31
+ auth_header_for(auth_token)
32
+ end
33
+
34
+ def dependency_url
35
+ "#{registry_url}/#{escaped_dependency_name}"
36
+ end
37
+
38
+ def tarball_url(version)
39
+ version_without_build_metadata = version.to_s.gsub(/\+.*/, "")
40
+
41
+ # Dependency name needs to be unescaped since tarball URLs don't always work with escaped slashes
42
+ "#{registry_url}/#{dependency.name}/-/#{scopeless_name}-#{version_without_build_metadata}.tgz"
43
+ end
44
+
45
+ def self.central_registry?(registry)
46
+ CENTRAL_REGISTRIES.any? do |r|
47
+ r.include?(registry)
48
+ end
49
+ end
50
+
51
+ def registry_from_rc(dependency_name)
52
+ explicit_registry_from_rc(dependency_name) || global_registry
53
+ end
54
+
55
+ private
56
+
57
+ attr_reader :dependency
58
+ attr_reader :credentials
59
+ attr_reader :npmrc_file
60
+
61
+ def explicit_registry_from_rc(dependency_name)
62
+ if dependency_name.start_with?("@") && dependency_name.include?("/")
63
+ scope = dependency_name.split("/").first
64
+ scoped_registry(scope) || configured_global_registry
65
+ else
66
+ configured_global_registry
67
+ end
68
+ end
69
+
70
+ def first_registry_with_dependency_details
71
+ @first_registry_with_dependency_details ||=
72
+ known_registries.find do |details|
73
+ url = "#{details['registry'].gsub(%r{/+$}, '')}/#{escaped_dependency_name}"
74
+ url = "https://#{url}" unless url.start_with?("http")
75
+ response = Dependabot::RegistryClient.get(
76
+ url: url,
77
+ headers: auth_header_for(details["token"])
78
+ )
79
+ response.status < 400 && JSON.parse(response.body)
80
+ rescue Excon::Error::Timeout,
81
+ Excon::Error::Socket,
82
+ JSON::ParserError
83
+ nil
84
+ rescue URI::InvalidURIError => e
85
+ raise DependencyFileNotResolvable, e.message
86
+ end&.fetch("registry")
87
+
88
+ @first_registry_with_dependency_details ||= global_registry.sub(%r{/+$}, "").sub(%r{^.*?//}, "")
89
+ end
90
+
91
+ def registry_url
92
+ url =
93
+ if registry.start_with?("http")
94
+ registry
95
+ else
96
+ protocol =
97
+ if registry_source_url
98
+ registry_source_url.split("://").first
99
+ else
100
+ "https"
101
+ end
102
+
103
+ "#{protocol}://#{registry}"
104
+ end
105
+
106
+ url.gsub(%r{/+$}, "")
107
+ end
108
+
109
+ def auth_header_for(token)
110
+ return {} unless token
111
+
112
+ if token.include?(":")
113
+ encoded_token = Base64.encode64(token).delete("\n")
114
+ { "Authorization" => "Basic #{encoded_token}" }
115
+ elsif Base64.decode64(token).ascii_only? &&
116
+ Base64.decode64(token).include?(":")
117
+ { "Authorization" => "Basic #{token.delete("\n")}" }
118
+ else
119
+ { "Authorization" => "Bearer #{token}" }
120
+ end
121
+ end
122
+
123
+ def auth_token
124
+ known_registries
125
+ .find { |cred| cred["registry"] == registry }
126
+ &.fetch("token", nil)
127
+ end
128
+
129
+ def locked_registry
130
+ return unless registry_source_url
131
+
132
+ lockfile_registry =
133
+ registry_source_url
134
+ .gsub("https://", "")
135
+ .gsub("http://", "")
136
+ detailed_registry =
137
+ known_registries
138
+ .find { |h| h["registry"].include?(lockfile_registry) }
139
+ &.fetch("registry")
140
+
141
+ detailed_registry || lockfile_registry
142
+ end
143
+
144
+ def configured_registry
145
+ configured_registry_url = explicit_registry_from_rc(dependency.name)
146
+ return unless configured_registry_url
147
+
148
+ normalize_configured_registry(configured_registry_url)
149
+ end
150
+
151
+ def known_registries
152
+ @known_registries ||=
153
+ begin
154
+ registries = []
155
+ registries += credentials
156
+ .select { |cred| cred["type"] == "npm_registry" && cred["registry"] }
157
+ .tap { |arr| arr.each { |c| c["token"] ||= nil } }
158
+ registries += npmrc_registries
159
+
160
+ unique_registries(registries)
161
+ end
162
+ end
163
+
164
+ def npmrc_registries
165
+ return [] unless npmrc_file
166
+
167
+ registries = []
168
+ npmrc_file.content.scan(NPM_AUTH_TOKEN_REGEX) do
169
+ next if Regexp.last_match&.[](:registry)&.include?("${")
170
+
171
+ registry = T.must(Regexp.last_match)[:registry]
172
+ token = T.must(Regexp.last_match)[:token]&.strip
173
+
174
+ registries << {
175
+ "type" => "npm_registry",
176
+ "registry" => registry&.gsub(/\s+/, "%20"),
177
+ "token" => token
178
+ }
179
+ end
180
+
181
+ registries += npmrc_global_registries
182
+ end
183
+
184
+ def unique_registries(registries)
185
+ registries.uniq.reject do |registry|
186
+ next if registry["token"]
187
+
188
+ # Reject this entry if an identical one with a token exists
189
+ registries.any? do |r|
190
+ r["token"] && r["registry"] == registry["registry"]
191
+ end
192
+ end
193
+ end
194
+
195
+ def global_registry
196
+ return @global_registry if defined? @global_registry
197
+
198
+ @global_registry ||= configured_global_registry || "https://registry.npmjs.org"
199
+ end
200
+
201
+ def configured_global_registry
202
+ return @configured_global_registry if defined? @configured_global_registry
203
+
204
+ @configured_global_registry = npmrc_file && npmrc_global_registries.first&.fetch("url")
205
+ return @configured_global_registry if @configured_global_registry
206
+
207
+ replaces_base = credentials.find { |cred| cred["type"] == "npm_registry" && cred.replaces_base? }
208
+ if replaces_base
209
+ registry = replaces_base["registry"]
210
+ registry = "https://#{registry}" unless registry.start_with?("http")
211
+ return @configured_global_registry = registry
212
+ end
213
+
214
+ @configured_global_registry = nil
215
+ end
216
+
217
+ def npmrc_global_registries
218
+ global_rc_registries(npmrc_file, syntax: NPM_GLOBAL_REGISTRY_REGEX)
219
+ end
220
+
221
+ def scoped_registry(scope)
222
+ scoped_rc_registry(npmrc_file, syntax: NPM_SCOPED_REGISTRY_REGEX, scope: scope)
223
+ end
224
+
225
+ def global_rc_registries(file, syntax:)
226
+ registries = []
227
+
228
+ file.content.scan(syntax) do
229
+ next if Regexp.last_match&.[](:registry)&.include?("${")
230
+
231
+ url = T.must(T.must(Regexp.last_match)[:registry]).strip
232
+ registry = normalize_configured_registry(url)
233
+ registries << {
234
+ "type" => "npm_registry",
235
+ "registry" => registry,
236
+ "url" => url,
237
+ "token" => nil
238
+ }
239
+ end
240
+
241
+ registries
242
+ end
243
+
244
+ def scoped_rc_registry(file, syntax:, scope:)
245
+ file&.content.to_s.scan(syntax) do
246
+ next if Regexp.last_match&.[](:registry)&.include?("${") || Regexp.last_match&.[](:scope) != scope
247
+
248
+ return T.must(T.must(Regexp.last_match)[:registry]).strip
249
+ end
250
+
251
+ nil
252
+ end
253
+
254
+ # npm registries expect slashes to be escaped
255
+ def escaped_dependency_name
256
+ dependency.name.gsub("/", "%2F")
257
+ end
258
+
259
+ def scopeless_name
260
+ dependency.name.split("/").last
261
+ end
262
+
263
+ def registry_source_url
264
+ sources = dependency.requirements
265
+ .map { |r| r.fetch(:source) }.uniq.compact
266
+ .sort_by { |source| self.class.central_registry?(source[:url]) ? 1 : 0 }
267
+
268
+ sources.find { |s| s[:type] == "registry" }&.fetch(:url)
269
+ end
270
+
271
+ def normalize_configured_registry(url)
272
+ url.sub(%r{/+$}, "")
273
+ .sub(%r{^.*?//}, "")
274
+ .gsub(/\s+/, "%20")
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,206 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ ################################################################################
5
+ # For more details on npm version constraints, see: #
6
+ # https://docs.npmjs.com/misc/semver #
7
+ ################################################################################
8
+
9
+ require "dependabot/bun/requirement"
10
+ require "dependabot/bun/update_checker"
11
+ require "dependabot/bun/version"
12
+ require "dependabot/requirements_update_strategy"
13
+
14
+ module Dependabot
15
+ module Bun
16
+ class UpdateChecker
17
+ class RequirementsUpdater
18
+ VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)*/
19
+ SEPARATOR = /(?<=[a-zA-Z0-9*])[\s|]+(?![\s|-])/
20
+ ALLOWED_UPDATE_STRATEGIES = T.let(
21
+ [
22
+ RequirementsUpdateStrategy::LockfileOnly,
23
+ RequirementsUpdateStrategy::WidenRanges,
24
+ RequirementsUpdateStrategy::BumpVersions,
25
+ RequirementsUpdateStrategy::BumpVersionsIfNecessary
26
+ ].freeze,
27
+ T::Array[Dependabot::RequirementsUpdateStrategy]
28
+ )
29
+
30
+ def initialize(requirements:, updated_source:, update_strategy:,
31
+ latest_resolvable_version:)
32
+ @requirements = requirements
33
+ @updated_source = updated_source
34
+ @update_strategy = update_strategy
35
+
36
+ check_update_strategy
37
+
38
+ return unless latest_resolvable_version
39
+
40
+ @latest_resolvable_version =
41
+ version_class.new(latest_resolvable_version)
42
+ end
43
+
44
+ def updated_requirements
45
+ return requirements if update_strategy.lockfile_only?
46
+
47
+ requirements.map do |req|
48
+ req = req.merge(source: updated_source)
49
+ next req unless latest_resolvable_version
50
+ next initial_req_after_source_change(req) unless req[:requirement]
51
+ next req if req[:requirement].match?(/^([A-Za-uw-z]|v[^\d])/)
52
+
53
+ case update_strategy
54
+ when RequirementsUpdateStrategy::WidenRanges then widen_requirement(req)
55
+ when RequirementsUpdateStrategy::BumpVersions then update_version_requirement(req)
56
+ when RequirementsUpdateStrategy::BumpVersionsIfNecessary
57
+ update_version_requirement_if_needed(req)
58
+ else raise "Unexpected update strategy: #{update_strategy}"
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ attr_reader :requirements
66
+ attr_reader :updated_source
67
+ attr_reader :update_strategy
68
+ attr_reader :latest_resolvable_version
69
+
70
+ def check_update_strategy
71
+ return if ALLOWED_UPDATE_STRATEGIES.include?(update_strategy)
72
+
73
+ raise "Unknown update strategy: #{update_strategy}"
74
+ end
75
+
76
+ def updating_from_git_to_npm?
77
+ return false unless updated_source.nil?
78
+
79
+ original_source = requirements.filter_map { |r| r[:source] }.first
80
+ original_source&.fetch(:type) == "git"
81
+ end
82
+
83
+ def initial_req_after_source_change(req)
84
+ return req unless updating_from_git_to_npm?
85
+ return req unless req[:requirement].nil?
86
+
87
+ req.merge(requirement: "^#{latest_resolvable_version}")
88
+ end
89
+
90
+ def update_version_requirement(req)
91
+ current_requirement = req[:requirement]
92
+
93
+ if current_requirement.match?(/(<|-\s)/i)
94
+ ruby_req = ruby_requirements(current_requirement).first
95
+ return req if ruby_req.satisfied_by?(latest_resolvable_version)
96
+
97
+ updated_req = update_range_requirement(current_requirement)
98
+ return req.merge(requirement: updated_req)
99
+ end
100
+
101
+ reqs = current_requirement.strip.split(SEPARATOR).map(&:strip)
102
+ req.merge(requirement: update_version_string(reqs.first))
103
+ end
104
+
105
+ def update_version_requirement_if_needed(req)
106
+ current_requirement = req[:requirement]
107
+ version = latest_resolvable_version
108
+ return req if current_requirement.strip == ""
109
+
110
+ ruby_reqs = ruby_requirements(current_requirement)
111
+ return req if ruby_reqs.any? { |r| r.satisfied_by?(version) }
112
+
113
+ update_version_requirement(req)
114
+ end
115
+
116
+ def widen_requirement(req)
117
+ current_requirement = req[:requirement]
118
+ version = latest_resolvable_version
119
+ return req if current_requirement.strip == ""
120
+
121
+ ruby_reqs = ruby_requirements(current_requirement)
122
+ return req if ruby_reqs.any? { |r| r.satisfied_by?(version) }
123
+
124
+ reqs = current_requirement.strip.split(SEPARATOR).map(&:strip)
125
+
126
+ updated_requirement =
127
+ if reqs.any? { |r| r.match?(/(<|-\s)/i) }
128
+ update_range_requirement(current_requirement)
129
+ elsif current_requirement.strip.split(SEPARATOR).count == 1
130
+ update_version_string(current_requirement)
131
+ else
132
+ current_requirement
133
+ end
134
+
135
+ req.merge(requirement: updated_requirement)
136
+ end
137
+
138
+ def ruby_requirements(requirement_string)
139
+ Bun::Requirement
140
+ .requirements_array(requirement_string)
141
+ end
142
+
143
+ def update_range_requirement(req_string)
144
+ range_requirements =
145
+ req_string.split(SEPARATOR).select { |r| r.match?(/<|(\s+-\s+)/) }
146
+
147
+ if range_requirements.count == 1
148
+ range_requirement = range_requirements.first
149
+ versions = range_requirement.scan(VERSION_REGEX)
150
+ upper_bound = versions.map { |v| version_class.new(v) }.max
151
+ new_upper_bound = update_greatest_version(
152
+ upper_bound,
153
+ latest_resolvable_version
154
+ )
155
+
156
+ req_string.sub(
157
+ upper_bound.to_s,
158
+ new_upper_bound.to_s
159
+ )
160
+ else
161
+ req_string + " || ^#{latest_resolvable_version}"
162
+ end
163
+ end
164
+
165
+ def update_version_string(req_string)
166
+ req_string
167
+ .sub(VERSION_REGEX) do |old_version|
168
+ if old_version.match?(/\d-/) ||
169
+ latest_resolvable_version.to_s.match?(/\d-/)
170
+ latest_resolvable_version.to_s
171
+ else
172
+ old_parts = old_version.split(".")
173
+ new_parts = latest_resolvable_version.to_s.split(".")
174
+ .first(old_parts.count)
175
+ new_parts.map.with_index do |part, i|
176
+ old_parts[i].match?(/^x\b/) ? "x" : part
177
+ end.join(".")
178
+ end
179
+ end
180
+ end
181
+
182
+ def update_greatest_version(old_version, version_to_be_permitted)
183
+ version = version_class.new(old_version)
184
+ version = version.release if version.prerelease?
185
+
186
+ index_to_update =
187
+ version.segments.map.with_index { |seg, i| seg.zero? ? 0 : i }.max
188
+
189
+ version.segments.map.with_index do |_, index|
190
+ if index < index_to_update
191
+ version_to_be_permitted.segments[index]
192
+ elsif index == index_to_update
193
+ version_to_be_permitted.segments[index] + 1
194
+ else
195
+ 0
196
+ end
197
+ end.join(".")
198
+ end
199
+
200
+ def version_class
201
+ Bun::Version
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,154 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/dependency"
5
+ require "dependabot/errors"
6
+ require "dependabot/logger"
7
+ require "dependabot/bun/file_parser"
8
+ require "dependabot/bun/helpers"
9
+ require "dependabot/bun/native_helpers"
10
+ require "dependabot/bun/sub_dependency_files_filterer"
11
+ require "dependabot/bun/update_checker"
12
+ require "dependabot/bun/update_checker/dependency_files_builder"
13
+ require "dependabot/bun/version"
14
+ require "dependabot/shared_helpers"
15
+
16
+ module Dependabot
17
+ module Bun
18
+ class UpdateChecker
19
+ class SubdependencyVersionResolver
20
+ def initialize(dependency:, credentials:, dependency_files:,
21
+ ignored_versions:, latest_allowable_version:, repo_contents_path:)
22
+ @dependency = dependency
23
+ @credentials = credentials
24
+ @dependency_files = dependency_files
25
+ @ignored_versions = ignored_versions
26
+ @latest_allowable_version = latest_allowable_version
27
+ @repo_contents_path = repo_contents_path
28
+ end
29
+
30
+ def latest_resolvable_version
31
+ raise "Not a subdependency!" if dependency.requirements.any?
32
+ return if bundled_dependency?
33
+
34
+ base_dir = dependency_files.first.directory
35
+ SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
36
+ dependency_files_builder.write_temporary_dependency_files
37
+
38
+ updated_lockfiles = filtered_lockfiles.map do |lockfile|
39
+ updated_content = update_subdependency_in_lockfile(lockfile)
40
+ updated_lockfile = lockfile.dup
41
+ updated_lockfile.content = updated_content
42
+ updated_lockfile
43
+ end
44
+
45
+ version_from_updated_lockfiles(updated_lockfiles)
46
+ end
47
+ rescue SharedHelpers::HelperSubprocessFailed
48
+ # TODO: Move error handling logic from the FileUpdater to this class
49
+
50
+ # Return nil (no update possible) if an unknown error occurred
51
+ nil
52
+ end
53
+
54
+ private
55
+
56
+ attr_reader :dependency
57
+ attr_reader :credentials
58
+ attr_reader :dependency_files
59
+ attr_reader :ignored_versions
60
+ attr_reader :latest_allowable_version
61
+ attr_reader :repo_contents_path
62
+
63
+ def update_subdependency_in_lockfile(lockfile)
64
+ lockfile_name = Pathname.new(lockfile.name).basename.to_s
65
+ path = Pathname.new(lockfile.name).dirname.to_s
66
+
67
+ updated_files = (run_bun_updater(path, lockfile_name) if lockfile.name.end_with?("bun.lock"))
68
+
69
+ updated_files.fetch(lockfile_name)
70
+ end
71
+
72
+ def version_from_updated_lockfiles(updated_lockfiles)
73
+ updated_files = dependency_files -
74
+ dependency_files_builder.lockfiles +
75
+ updated_lockfiles
76
+
77
+ updated_version = Bun::FileParser.new(
78
+ dependency_files: updated_files,
79
+ source: nil,
80
+ credentials: credentials
81
+ ).parse.find { |d| d.name == dependency.name }&.version
82
+ return unless updated_version
83
+
84
+ version_class.new(updated_version)
85
+ end
86
+
87
+ def run_bun_updater(path, lockfile_name)
88
+ SharedHelpers.with_git_configured(credentials: credentials) do
89
+ Dir.chdir(path) do
90
+ Helpers.run_bun_command(
91
+ "update #{dependency.name} --save-text-lockfile",
92
+ fingerprint: "update <dependency_name> --save-text-lockfile"
93
+ )
94
+ { lockfile_name => File.read(lockfile_name) }
95
+ end
96
+ end
97
+ end
98
+
99
+ def version_class
100
+ dependency.version_class
101
+ end
102
+
103
+ def updated_dependency
104
+ Dependabot::Dependency.new(
105
+ name: dependency.name,
106
+ version: latest_allowable_version,
107
+ previous_version: dependency.version,
108
+ requirements: [],
109
+ package_manager: dependency.package_manager
110
+ )
111
+ end
112
+
113
+ def filtered_lockfiles
114
+ @filtered_lockfiles ||=
115
+ SubDependencyFilesFilterer.new(
116
+ dependency_files: dependency_files,
117
+ updated_dependencies: [updated_dependency]
118
+ ).files_requiring_update
119
+ end
120
+
121
+ def dependency_files_builder
122
+ @dependency_files_builder ||=
123
+ DependencyFilesBuilder.new(
124
+ dependency: dependency,
125
+ dependency_files: dependency_files,
126
+ credentials: credentials
127
+ )
128
+ end
129
+
130
+ # TODO: We should try and fix this by updating the parent that's not
131
+ # bundled. For this case: `chokidar > fsevents > node-pre-gyp > tar` we
132
+ # would need to update `fsevents`
133
+ #
134
+ # We shouldn't update bundled sub-dependencies as they have been bundled
135
+ # into the release at an exact version by a parent using
136
+ # `bundledDependencies`.
137
+ #
138
+ # For example, fsevents < 2 bundles node-pre-gyp meaning all it's
139
+ # sub-dependencies get bundled into the release tarball at publish time
140
+ # so you always get the same sub-dependency versions if you re-install a
141
+ # specific version of fsevents.
142
+ #
143
+ # Updating the sub-dependency by deleting the entry works but it gets
144
+ # removed from the bundled set of dependencies and moved top level
145
+ # resulting in a bunch of package duplication which is pretty confusing.
146
+ def bundled_dependency?
147
+ dependency.subdependency_metadata
148
+ &.any? { |h| h.fetch(:npm_bundled, false) } ||
149
+ false
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end