dependabot-bun 0.400.0 → 0.401.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 695147981009a25e1dfdc5b0bb7f6a312a81e7505b91ae1fcf4fe04565e05116
4
- data.tar.gz: 60327771f9e963e76b9f77f32ef8be93e945b52d3b94a0ee51eeaa260d30269a
3
+ metadata.gz: 6911ce58a858da72deaee55412e7a9f6f434181fb6eaafafc81a64b0e22fe31a
4
+ data.tar.gz: 5e63b632ac51775a4283fb4ca4166cd9276a12c13585bac4c411648b9901c667
5
5
  SHA512:
6
- metadata.gz: c21419897e7d901b04ebcfa4f4a09600b69ce29bef225d4884dde567b45c1d09162ef6d8622385d6ba43e358b26782cf182669fe827f5ea5298f3f2480c88a84
7
- data.tar.gz: 1fbfcb079150eef24eb36562200565e38cfd65654843258f54eb3d2fd21f4fb8b1cc0f3e64101fa4bf284dec22bb9f3ac456ba9cb946f250d7f12b55ca0981a2
6
+ metadata.gz: 3c11b9f089126fe325f9ee718521def2ff8ad105a49ba883116a76f423bd07de38930c33e63487b41842babe2375b44efad8943b26adb83c224c885f6ee499c9
7
+ data.tar.gz: bf9388f37be2732c47a40fe19b7d1613644c92747dafff37cf51e595ab3b496a6542b09de507fd2f3c14ec4e4dc46ce171a64936f872e14898b56caa81d6645d
@@ -30,6 +30,7 @@ module Dependabot
30
30
  require_relative "file_parser/lockfile_parser"
31
31
 
32
32
  DEPENDENCY_TYPES = %w(dependencies devDependencies optionalDependencies).freeze
33
+ DEFAULT_CATALOG = "default"
33
34
  ALIAS_PROTOCOL = "npm:"
34
35
  GIT_URL_REGEX = %r{
35
36
  (?<git_prefix>^|^git.*?|^github:|^bitbucket:|^gitlab:|github\.com/)
@@ -63,6 +64,7 @@ module Dependabot
63
64
  dependency_set = DependencySet.new
64
65
  dependency_set += manifest_dependencies
65
66
  dependency_set += lockfile_dependencies
67
+ dependency_set += catalog_dependencies
66
68
 
67
69
  dependencies = Helpers.dependencies_with_all_versions_metadata(dependency_set)
68
70
 
@@ -202,11 +204,34 @@ module Dependabot
202
204
  lockfile_parser.parse_set
203
205
  end
204
206
 
207
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
208
+ def catalog_dependencies
209
+ dependency_set = DependencySet.new
210
+ document = package_json_document
211
+ catalogs = [[DEFAULT_CATALOG, document.catalog], *document.catalogs.to_a]
212
+
213
+ catalogs.each do |catalog_name, entries|
214
+ entries.each do |name, requirement|
215
+ # Catalog entries are production dependencies, as pnpm catalogs are in npm_and_yarn.
216
+ dep = build_dependency(
217
+ file: package_json,
218
+ type: "dependencies",
219
+ name: name,
220
+ requirement: requirement,
221
+ catalog: catalog_name
222
+ )
223
+ dependency_set << dep if dep
224
+ end
225
+ end
226
+
227
+ dependency_set
228
+ end
229
+
205
230
  sig do
206
- params(file: DependencyFile, type: String, name: String, requirement: String)
231
+ params(file: DependencyFile, type: String, name: String, requirement: String, catalog: T.nilable(String))
207
232
  .returns(T.nilable(Dependency))
208
233
  end
209
- def build_dependency(file:, type:, name:, requirement:)
234
+ def build_dependency(file:, type:, name:, requirement:, catalog: nil)
210
235
  lockfile_details = lockfile_parser.lockfile_details(
211
236
  dependency_name: name,
212
237
  requirement: requirement,
@@ -243,11 +268,17 @@ module Dependabot
243
268
  requirement: requirement_for(requirement),
244
269
  file: file.name,
245
270
  groups: [type],
246
- source: source_for(name, requirement, lockfile_details)
271
+ source: source_for(name, requirement, lockfile_details),
272
+ **catalog_metadata(catalog)
247
273
  }]
248
274
  )
249
275
  end
250
276
 
277
+ sig { params(catalog: T.nilable(String)).returns(T::Hash[Symbol, T::Hash[Symbol, String]]) }
278
+ def catalog_metadata(catalog)
279
+ catalog ? { metadata: { catalog: catalog } } : {}
280
+ end
281
+
251
282
  sig { override.void }
252
283
  def check_required_files
253
284
  return if get_original_file(MANIFEST_FILENAME)
@@ -0,0 +1,127 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "sorbet-runtime"
6
+
7
+ require "dependabot/bun/file_updater"
8
+
9
+ module Dependabot
10
+ module Bun
11
+ class FileUpdater < Dependabot::FileUpdaters::Base
12
+ # Locates object members in JSON source text, so they can be edited in
13
+ # place without reformatting the document.
14
+ class JsonObjectMembers
15
+ extend T::Sig
16
+
17
+ # Maps each member of the JSON object opening at `start` whose value is
18
+ # an object to the range of that value in `content`.
19
+ sig { params(content: String, start: Integer).returns(T::Hash[String, T::Range[Integer]]) }
20
+ def self.objects(content, start)
21
+ new(content, start).objects
22
+ end
23
+
24
+ # Maps each member of the JSON object opening at `start` whose value is
25
+ # a string to the range of that value, quotes included, in `content`.
26
+ sig { params(content: String, start: Integer).returns(T::Hash[String, T::Range[Integer]]) }
27
+ def self.strings(content, start)
28
+ new(content, start).strings
29
+ end
30
+
31
+ sig { returns(T::Hash[String, T::Range[Integer]]) }
32
+ attr_reader :objects
33
+
34
+ sig { returns(T::Hash[String, T::Range[Integer]]) }
35
+ attr_reader :strings
36
+
37
+ sig { params(content: String, start: Integer).void }
38
+ def initialize(content, start)
39
+ @objects = T.let({}, T::Hash[String, T::Range[Integer]])
40
+ @strings = T.let({}, T::Hash[String, T::Range[Integer]])
41
+ @depth = T.let(0, Integer)
42
+ @key = T.let(nil, T.nilable(String))
43
+ @value_start = T.let(nil, T.nilable(Integer))
44
+ @string = T.let(nil, T.nilable(String))
45
+ @last_string = T.let(nil, T.nilable(String))
46
+ @escaped = T.let(false, T::Boolean)
47
+ scan(content, start)
48
+ end
49
+
50
+ private
51
+
52
+ sig { params(content: String, start: Integer).void }
53
+ def scan(content, start)
54
+ T.must(content[start..]).each_char.with_index(start) do |char, index|
55
+ if @string
56
+ read_string(@string, char, index)
57
+ else
58
+ read_structure(char, index)
59
+ break if @depth.zero?
60
+ end
61
+ end
62
+ end
63
+
64
+ sig { params(string: String, char: String, index: Integer).void }
65
+ def read_string(string, char, index)
66
+ if @escaped
67
+ @escaped = false
68
+ elsif char == "\\"
69
+ @escaped = true
70
+ elsif char == "\""
71
+ close_string(string, index)
72
+ return
73
+ end
74
+
75
+ string << char
76
+ end
77
+
78
+ sig { params(char: String, index: Integer).void }
79
+ def read_structure(char, index)
80
+ case char
81
+ when "\"" then open_string(index)
82
+ when ":" then @key = @last_string if @depth == 1
83
+ when "," then @key = nil if @depth == 1
84
+ when "{", "[" then enter(char, index)
85
+ when "}", "]" then leave(index)
86
+ end
87
+ end
88
+
89
+ sig { params(index: Integer).void }
90
+ def open_string(index)
91
+ @string = +""
92
+ @value_start = index if @depth == 1 && @key
93
+ end
94
+
95
+ sig { params(string: String, index: Integer).void }
96
+ def close_string(string, index)
97
+ value = T.cast(JSON.parse("\"#{string}\""), String)
98
+ @last_string = value
99
+ @string = nil
100
+ record(@strings, index) if @depth == 1
101
+ end
102
+
103
+ sig { params(char: String, index: Integer).void }
104
+ def enter(char, index)
105
+ @value_start = index if @depth == 1 && char == "{" && @key
106
+ @depth += 1
107
+ end
108
+
109
+ sig { params(index: Integer).void }
110
+ def leave(index)
111
+ @depth -= 1
112
+ record(@objects, index) if @depth == 1
113
+ end
114
+
115
+ sig { params(members: T::Hash[String, T::Range[Integer]], index: Integer).void }
116
+ def record(members, index)
117
+ key = @key
118
+ value_start = @value_start
119
+ return unless key && value_start
120
+
121
+ members[key] = value_start..index
122
+ @value_start = nil
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -3,7 +3,10 @@
3
3
 
4
4
  require "sorbet-runtime"
5
5
 
6
+ require "dependabot/bun/file_parser"
6
7
  require "dependabot/bun/file_updater"
8
+ require "dependabot/bun/file_updater/json_object_members"
9
+ require "dependabot/package/npm_package_json"
7
10
 
8
11
  module Dependabot
9
12
  module Bun
@@ -93,7 +96,10 @@ module Dependabot
93
96
  def old_requirement(dependency, new_requirement)
94
97
  T.must(dependency.previous_requirements)
95
98
  .select { |r| r.file == package_json.name }
96
- .find { |r| r.groups == new_requirement.groups }
99
+ .find do |r|
100
+ r.groups == new_requirement.groups &&
101
+ r.metadata_string("catalog") == new_requirement.metadata_string("catalog")
102
+ end
97
103
  end
98
104
 
99
105
  sig { params(dependency: Dependabot::Dependency).returns(T::Array[Dependabot::DependencyRequirement]) }
@@ -131,6 +137,17 @@ module Dependabot
131
137
  .returns(String)
132
138
  end
133
139
  def update_package_json_declaration(package_json_content:, new_req:, dependency_name:, old_req:)
140
+ catalog = new_req.metadata_string("catalog")
141
+ if catalog
142
+ return update_catalog_declaration(
143
+ catalog: catalog,
144
+ content: package_json_content,
145
+ dependency_name: dependency_name,
146
+ old_req: old_req,
147
+ new_req: new_req
148
+ )
149
+ end
150
+
134
151
  original_line = declaration_line(
135
152
  dependency_name: dependency_name,
136
153
  dependency_req: old_req,
@@ -323,6 +340,72 @@ module Dependabot
323
340
  end
324
341
  end
325
342
 
343
+ # Bun reads catalogs only from `NpmPackageJson#catalog_container`, so
344
+ # the entry is looked up and replaced among those catalogs' members.
345
+ sig do
346
+ params(
347
+ catalog: String,
348
+ content: String,
349
+ dependency_name: String,
350
+ old_req: T.nilable(Dependabot::DependencyRequirement),
351
+ new_req: Dependabot::DependencyRequirement
352
+ )
353
+ .returns(String)
354
+ end
355
+ def update_catalog_declaration(catalog:, content:, dependency_name:, old_req:, new_req:)
356
+ value_ranges = catalog_ranges(catalog, content).filter_map do |range|
357
+ JsonObjectMembers.strings(content, range.begin)[dependency_name]
358
+ end
359
+
360
+ # Replacing from the end keeps the earlier ranges valid.
361
+ value_ranges.sort_by(&:begin).reverse.each_with_object(content.dup) do |range, updated|
362
+ value = updated_catalog_value(dependency_name, T.must(updated[range]), old_req, new_req)
363
+ updated[range] = value if value
364
+ end
365
+ end
366
+
367
+ # The default catalog is `catalog` together with `catalogs.default`.
368
+ sig { params(catalog: String, content: String).returns(T::Array[T::Range[Integer]]) }
369
+ def catalog_ranges(catalog, content)
370
+ root_start = content.index("{")
371
+ return [] unless root_start
372
+
373
+ fields = JsonObjectMembers.objects(content, root_start)
374
+ document = Dependabot::Package::NpmPackageJson.new(JSON.parse(content), package_json.path)
375
+ container = document.catalog_container
376
+ if container
377
+ container_range = fields[container]
378
+ return [] unless container_range
379
+
380
+ fields = JsonObjectMembers.objects(content, container_range.begin)
381
+ end
382
+
383
+ catalogs_range = fields["catalogs"]
384
+ [
385
+ (fields["catalog"] if catalog == FileParser::DEFAULT_CATALOG),
386
+ (JsonObjectMembers.objects(content, catalogs_range.begin)[catalog] if catalogs_range)
387
+ ].compact
388
+ end
389
+
390
+ # The value is matched and rewritten as a normalized declaration, so
391
+ # its source formatting never affects the match.
392
+ sig do
393
+ params(
394
+ dependency_name: String,
395
+ value: String,
396
+ old_req: T.nilable(Dependabot::DependencyRequirement),
397
+ new_req: Dependabot::DependencyRequirement
398
+ )
399
+ .returns(T.nilable(String))
400
+ end
401
+ def updated_catalog_value(dependency_name, value, old_req, new_req)
402
+ prefix = %("#{dependency_name}": )
403
+ line = prefix + JSON.generate(JSON.parse(value))
404
+ return if declaration_line(dependency_name: dependency_name, dependency_req: old_req, content: line).empty?
405
+
406
+ replacement_declaration_line(original_line: line, old_req: old_req, new_req: new_req).delete_prefix(prefix)
407
+ end
408
+
326
409
  sig { params(string: String).returns(Integer) }
327
410
  def closing_bracket_index(string)
328
411
  closes_required = 1
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-bun
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.400.0
4
+ version: 0.401.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.400.0
18
+ version: 0.401.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.400.0
25
+ version: 0.401.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -323,6 +323,7 @@ files:
323
323
  - lib/dependabot/bun/file_parser/lockfile_parser.rb
324
324
  - lib/dependabot/bun/file_updater.rb
325
325
  - lib/dependabot/bun/file_updater/bun_lockfile_updater.rb
326
+ - lib/dependabot/bun/file_updater/json_object_members.rb
326
327
  - lib/dependabot/bun/file_updater/npmrc_builder.rb
327
328
  - lib/dependabot/bun/file_updater/package_json_preparer.rb
328
329
  - lib/dependabot/bun/file_updater/package_json_updater.rb
@@ -355,7 +356,7 @@ licenses:
355
356
  - MIT
356
357
  metadata:
357
358
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
358
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.400.0
359
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.401.0
359
360
  rdoc_options: []
360
361
  require_paths:
361
362
  - lib