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.
- checksums.yaml +4 -4
- data/helpers/.eslintrc +11 -0
- data/helpers/README.md +29 -0
- data/helpers/build +26 -0
- data/helpers/jest.config.js +5 -0
- data/helpers/lib/npm/conflicting-dependency-parser.js +78 -0
- data/helpers/lib/npm/index.js +9 -0
- data/helpers/lib/npm/vulnerability-auditor.js +291 -0
- data/helpers/lib/npm6/helpers.js +25 -0
- data/helpers/lib/npm6/index.js +9 -0
- data/helpers/lib/npm6/peer-dependency-checker.js +111 -0
- data/helpers/lib/npm6/remove-dependencies-from-lockfile.js +22 -0
- data/helpers/lib/npm6/subdependency-updater.js +78 -0
- data/helpers/lib/npm6/updater.js +199 -0
- data/helpers/lib/pnpm/index.js +5 -0
- data/helpers/lib/pnpm/lockfile-parser.js +82 -0
- data/helpers/lib/yarn/conflicting-dependency-parser.js +176 -0
- data/helpers/lib/yarn/fix-duplicates.js +80 -0
- data/helpers/lib/yarn/helpers.js +54 -0
- data/helpers/lib/yarn/index.js +14 -0
- data/helpers/lib/yarn/lockfile-parser.js +21 -0
- data/helpers/lib/yarn/peer-dependency-checker.js +132 -0
- data/helpers/lib/yarn/replace-lockfile-declaration.js +57 -0
- data/helpers/lib/yarn/subdependency-updater.js +83 -0
- data/helpers/lib/yarn/updater.js +209 -0
- data/helpers/package-lock.json +28519 -0
- data/helpers/package.json +29 -0
- data/helpers/patches/npm++pacote+9.5.12.patch +14 -0
- data/helpers/run.js +30 -0
- data/helpers/test/npm6/conflicting-dependency-parser.test.js +66 -0
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package-lock.json +591 -0
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package.json +14 -0
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/nested/package-lock.json +188 -0
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/nested/package.json +14 -0
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/simple/package-lock.json +27 -0
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/simple/package.json +14 -0
- data/helpers/test/npm6/fixtures/updater/original/package-lock.json +16 -0
- data/helpers/test/npm6/fixtures/updater/original/package.json +9 -0
- data/helpers/test/npm6/fixtures/updater/updated/package-lock.json +16 -0
- data/helpers/test/npm6/helpers.js +21 -0
- data/helpers/test/npm6/updater.test.js +30 -0
- data/helpers/test/pnpm/fixtures/parser/empty_version/pnpm-lock.yaml +72 -0
- data/helpers/test/pnpm/fixtures/parser/no_lockfile_change/pnpm-lock.yaml +2744 -0
- data/helpers/test/pnpm/fixtures/parser/only_dev_dependencies/pnpm-lock.yaml +16 -0
- data/helpers/test/pnpm/fixtures/parser/peer_disambiguation/pnpm-lock.yaml +855 -0
- data/helpers/test/pnpm/lockfile-parser.test.js +62 -0
- data/helpers/test/yarn/conflicting-dependency-parser.test.js +83 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/package.json +14 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/yarn.lock +496 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/dev-dependencies/package.json +14 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/dev-dependencies/yarn.lock +21 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/nested/package.json +14 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/nested/yarn.lock +183 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/simple/package.json +14 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/simple/yarn.lock +21 -0
- data/helpers/test/yarn/fixtures/updater/illegal_character/package.json +8 -0
- data/helpers/test/yarn/fixtures/updater/illegal_character/yarn.lock +14 -0
- data/helpers/test/yarn/fixtures/updater/original/package.json +6 -0
- data/helpers/test/yarn/fixtures/updater/original/yarn.lock +11 -0
- data/helpers/test/yarn/fixtures/updater/updated/yarn.lock +12 -0
- data/helpers/test/yarn/fixtures/updater/with-version-comments/package.json +5 -0
- data/helpers/test/yarn/fixtures/updater/with-version-comments/yarn.lock +13 -0
- data/helpers/test/yarn/helpers.js +18 -0
- data/helpers/test/yarn/updater.test.js +117 -0
- data/lib/dependabot/bun/bun_package_manager.rb +47 -0
- data/lib/dependabot/bun/constraint_helper.rb +359 -0
- data/lib/dependabot/bun/dependency_files_filterer.rb +157 -0
- data/lib/dependabot/bun/file_fetcher/path_dependency_builder.rb +184 -0
- data/lib/dependabot/bun/file_fetcher.rb +402 -0
- data/lib/dependabot/bun/file_parser/bun_lock.rb +140 -0
- data/lib/dependabot/bun/file_parser/lockfile_parser.rb +105 -0
- data/lib/dependabot/bun/file_parser.rb +477 -0
- data/lib/dependabot/bun/file_updater/bun_lockfile_updater.rb +144 -0
- data/lib/dependabot/bun/file_updater/npmrc_builder.rb +256 -0
- data/lib/dependabot/bun/file_updater/package_json_preparer.rb +88 -0
- data/lib/dependabot/bun/file_updater/package_json_updater.rb +378 -0
- data/lib/dependabot/bun/file_updater.rb +203 -0
- data/lib/dependabot/bun/helpers.rb +93 -0
- data/lib/dependabot/bun/language.rb +45 -0
- data/lib/dependabot/bun/metadata_finder.rb +214 -0
- data/lib/dependabot/bun/native_helpers.rb +19 -0
- data/lib/dependabot/bun/package_manager.rb +280 -0
- data/lib/dependabot/bun/package_name.rb +118 -0
- data/lib/dependabot/bun/pnpm_package_manager.rb +55 -0
- data/lib/dependabot/bun/registry_helper.rb +188 -0
- data/lib/dependabot/bun/registry_parser.rb +93 -0
- data/lib/dependabot/bun/requirement.rb +146 -0
- data/lib/dependabot/bun/sub_dependency_files_filterer.rb +82 -0
- data/lib/dependabot/bun/update_checker/conflicting_dependency_resolver.rb +59 -0
- data/lib/dependabot/bun/update_checker/dependency_files_builder.rb +79 -0
- data/lib/dependabot/bun/update_checker/latest_version_finder.rb +448 -0
- data/lib/dependabot/bun/update_checker/library_detector.rb +76 -0
- data/lib/dependabot/bun/update_checker/registry_finder.rb +279 -0
- data/lib/dependabot/bun/update_checker/requirements_updater.rb +206 -0
- data/lib/dependabot/bun/update_checker/subdependency_version_resolver.rb +154 -0
- data/lib/dependabot/bun/update_checker/version_resolver.rb +583 -0
- data/lib/dependabot/bun/update_checker/vulnerability_auditor.rb +164 -0
- data/lib/dependabot/bun/update_checker.rb +455 -0
- data/lib/dependabot/bun/version.rb +138 -0
- data/lib/dependabot/bun/version_selector.rb +61 -0
- data/lib/dependabot/bun.rb +337 -35
- metadata +108 -65
- data/lib/dependabot/javascript/bun/file_fetcher.rb +0 -77
- data/lib/dependabot/javascript/bun/file_parser/bun_lock.rb +0 -156
- data/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +0 -55
- data/lib/dependabot/javascript/bun/file_parser.rb +0 -74
- data/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb +0 -138
- data/lib/dependabot/javascript/bun/file_updater.rb +0 -75
- data/lib/dependabot/javascript/bun/helpers.rb +0 -72
- data/lib/dependabot/javascript/bun/package_manager.rb +0 -48
- data/lib/dependabot/javascript/bun/requirement.rb +0 -11
- data/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +0 -64
- data/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb +0 -47
- data/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb +0 -450
- data/lib/dependabot/javascript/bun/update_checker/library_detector.rb +0 -76
- data/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb +0 -203
- data/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb +0 -144
- data/lib/dependabot/javascript/bun/update_checker/version_resolver.rb +0 -525
- data/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb +0 -165
- data/lib/dependabot/javascript/bun/update_checker.rb +0 -440
- data/lib/dependabot/javascript/bun/version.rb +0 -11
- data/lib/dependabot/javascript/shared/constraint_helper.rb +0 -359
- data/lib/dependabot/javascript/shared/dependency_files_filterer.rb +0 -164
- data/lib/dependabot/javascript/shared/file_fetcher.rb +0 -283
- data/lib/dependabot/javascript/shared/file_parser/lockfile_parser.rb +0 -106
- data/lib/dependabot/javascript/shared/file_parser.rb +0 -454
- data/lib/dependabot/javascript/shared/file_updater/npmrc_builder.rb +0 -394
- data/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb +0 -87
- data/lib/dependabot/javascript/shared/file_updater/package_json_updater.rb +0 -376
- data/lib/dependabot/javascript/shared/file_updater.rb +0 -179
- data/lib/dependabot/javascript/shared/language.rb +0 -45
- data/lib/dependabot/javascript/shared/metadata_finder.rb +0 -209
- data/lib/dependabot/javascript/shared/native_helpers.rb +0 -21
- data/lib/dependabot/javascript/shared/package_manager_detector.rb +0 -72
- data/lib/dependabot/javascript/shared/package_name.rb +0 -118
- data/lib/dependabot/javascript/shared/registry_helper.rb +0 -190
- data/lib/dependabot/javascript/shared/registry_parser.rb +0 -93
- data/lib/dependabot/javascript/shared/requirement.rb +0 -144
- data/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb +0 -79
- data/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb +0 -87
- data/lib/dependabot/javascript/shared/update_checker/registry_finder.rb +0 -358
- data/lib/dependabot/javascript/shared/version.rb +0 -133
- data/lib/dependabot/javascript/shared/version_selector.rb +0 -60
- 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
|