dependabot-vcpkg 0.398.0 → 0.400.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: 518a47b3ca02431938c1d73d84a1dcfefe2f1de41a7d626afdc081123bc470e2
4
- data.tar.gz: 317d51a65e62f67b835a6f9cabb754c0743c44783de8d4077383dd68926b935e
3
+ metadata.gz: eef88987d897016f4050ef6a6f7f01706a1ae69ebb500f751bf10e1f9492d477
4
+ data.tar.gz: a7f4001d7cec04609c37940363fd94f201e93dede0c74cf25d949b18c5bf29ee
5
5
  SHA512:
6
- metadata.gz: a0ac23f74427aa1cad0cd797a16bae12465498f65d48885c153679dad4bfea3e38be4ea220a74654c72eeae392dfaaa211be1698c474b1632f201c7303414a26
7
- data.tar.gz: ce1479969c093e4cc2d1cf1ceb39ab508f1e4be18110ed7980baf7b7787c858357e9edfed793444edf23c13ccd46976aec9f81ba6213694276711871ed82f927
6
+ metadata.gz: 74583f0eb7dfcb426e20191a874415027ec6be2dd37851db03966e2615e344ed34605dcf233eb9b0786c297b259406b47aa883d460bede9eb189c2bfd9cd7e63
7
+ data.tar.gz: 49a5e550db7e20e9ffe3b15f9d1fa5545af97c822a1e08dba5b4a0868ce48be68a666b42ac6f36f5182394e86863db97bc0b1c4a1f609502b35b63593440503b
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -11,6 +11,7 @@ require "dependabot/logger"
11
11
  require "dependabot/vcpkg"
12
12
  require "dependabot/vcpkg/language"
13
13
  require "dependabot/vcpkg/manifest_baseline"
14
+ require "dependabot/vcpkg/manifest_document"
14
15
  require "dependabot/vcpkg/package/versions_database"
15
16
  require "dependabot/vcpkg/package_manager"
16
17
  require "dependabot/vcpkg/version"
@@ -58,20 +59,20 @@ module Dependabot
58
59
  return [] unless dependency_file.content
59
60
 
60
61
  case dependency_file.name
61
- in VCPKG_JSON_FILENAME then parse_vcpkg_json(dependency_file)
62
- in VCPKG_CONFIGURATION_JSON_FILENAME then parse_vcpkg_configuration_json(dependency_file)
62
+ when VCPKG_JSON_FILENAME then parse_vcpkg_json(dependency_file)
63
+ when VCPKG_CONFIGURATION_JSON_FILENAME then parse_vcpkg_configuration_json(dependency_file)
63
64
  else []
64
65
  end
65
66
  end
66
67
 
67
68
  sig { params(dependency_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::Dependency]) }
68
69
  def parse_vcpkg_json(dependency_file)
69
- contents = T.must(dependency_file.content)
70
- parsed_json = JSON.parse(contents)
70
+ document = ManifestDocument.from_file(dependency_file)
71
71
 
72
72
  dependencies = []
73
73
 
74
- parsed_json["builtin-baseline"]&.then do |baseline|
74
+ baseline = document.builtin_baseline
75
+ if baseline
75
76
  dependencies << Dependabot::Dependency.new(
76
77
  name: VCPKG_DEFAULT_BASELINE_DEPENDENCY_NAME,
77
78
  version: baseline,
@@ -89,63 +90,38 @@ module Dependabot
89
90
  )
90
91
  end
91
92
 
92
- parsed_json["dependencies"]&.each do |dep|
93
- dependency = parse_port_dependency(dep:, dependency_file:)
93
+ document.ports.each do |port|
94
+ dependency = build_port_dependency(name: port.name, constraint: port.constraint, dependency_file:)
94
95
  dependencies << dependency if dependency
95
96
  end
96
97
 
97
98
  dependencies.compact
98
- rescue JSON::ParserError
99
- Dependabot.logger.warn("Failed to parse #{dependency_file.name}: #{dependency_file.content}")
100
- raise Dependabot::DependencyFileNotParseable, T.must(dependency_files.first).path
99
+ rescue Dependabot::DependencyFileNotParseable => e
100
+ Dependabot.logger.warn(e.message)
101
+ raise
101
102
  end
102
103
 
103
104
  sig { params(dependency_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::Dependency]) }
104
105
  def parse_vcpkg_configuration_json(dependency_file)
105
- contents = T.must(dependency_file.content)
106
- parsed_json = JSON.parse(contents)
106
+ document = ManifestDocument.from_file(dependency_file)
107
107
 
108
108
  dependencies = []
109
109
 
110
110
  # Parse default-registry if it exists
111
- parsed_json["default-registry"]&.then do |registry|
112
- dependency = parse_registry_dependency(registry:, dependency_file:, is_default: true)
113
- dependencies << dependency if dependency
111
+ default_registry = document.default_registry
112
+ if default_registry
113
+ dependencies << parse_registry_dependency(registry: default_registry, dependency_file:, is_default: true)
114
114
  end
115
115
 
116
116
  # Parse registries array if it exists
117
- parsed_json["registries"]&.each do |registry|
118
- dependency = parse_registry_dependency(registry:, dependency_file:, is_default: false)
119
- dependencies << dependency if dependency
117
+ document.registries.each do |registry|
118
+ dependencies << parse_registry_dependency(registry:, dependency_file:, is_default: false)
120
119
  end
121
120
 
122
121
  dependencies.compact
123
- rescue JSON::ParserError
124
- Dependabot.logger.warn("Failed to parse #{dependency_file.name}: #{dependency_file.content}")
125
- raise Dependabot::DependencyFileNotParseable, dependency_file.path
126
- end
127
-
128
- sig do
129
- params(
130
- dep: Object,
131
- dependency_file: Dependabot::DependencyFile
132
- )
133
- .returns(T.nilable(Dependabot::Dependency))
134
- end
135
- def parse_port_dependency(dep:, dependency_file:)
136
- case dep
137
- when String
138
- build_port_dependency(name: dep, constraint: nil, dependency_file:)
139
- when Hash
140
- name = dep["name"]
141
- return nil unless name.is_a?(String)
142
-
143
- constraint = dep[VCPKG_VERSION_CONSTRAINT_KEY]
144
- build_port_dependency(name:, constraint: constraint.is_a?(String) ? constraint : nil, dependency_file:)
145
- else
146
- Dependabot.logger.warn("Skipping unknown vcpkg dependency format: #{dep.inspect}")
147
- nil
148
- end
122
+ rescue Dependabot::DependencyFileNotParseable => e
123
+ Dependabot.logger.warn(e.message)
124
+ raise
149
125
  end
150
126
 
151
127
  # A port's version comes from its `version>=` constraint, the registry baseline, or both.
@@ -226,66 +202,28 @@ module Dependabot
226
202
 
227
203
  sig do
228
204
  params(
229
- registry: T::Hash[String, T.untyped],
205
+ registry: ManifestDocument::Registry,
230
206
  dependency_file: Dependabot::DependencyFile,
231
207
  is_default: T::Boolean
232
208
  )
233
- .returns(T.nilable(Dependabot::Dependency))
209
+ .returns(Dependabot::Dependency)
234
210
  end
235
- def parse_registry_dependency(registry:, dependency_file:, is_default: false) # rubocop:disable Metrics/MethodLength
236
- kind = registry["kind"]
237
- baseline = registry["baseline"]
238
-
239
- # Only track git and builtin registries
240
- return nil unless VCPKG_SUPPORTED_REGISTRY_TYPES.include?(kind)
241
- return nil unless baseline.is_a?(String)
211
+ def parse_registry_dependency(registry:, dependency_file:, is_default: false)
212
+ metadata = { default: is_default }
213
+ metadata[:builtin] = true if registry.builtin
242
214
 
243
- case kind
244
- when "git"
245
- repository = registry["repository"]
246
- return nil unless repository.is_a?(String)
247
-
248
- reference = registry["reference"] || "HEAD"
249
-
250
- Dependabot::Dependency.new(
251
- name: repository,
252
- version: baseline,
253
- package_manager: "vcpkg",
254
- requirements: [{
255
- requirement: nil,
256
- groups: [],
257
- source: {
258
- type: "git",
259
- url: repository,
260
- ref: reference
261
- },
262
- file: dependency_file.name
263
- }],
264
- metadata: {
265
- default: is_default
266
- }
267
- )
268
- when "builtin"
269
- Dependabot::Dependency.new(
270
- name: VCPKG_DEFAULT_BASELINE_DEPENDENCY_NAME,
271
- version: baseline,
272
- package_manager: "vcpkg",
273
- requirements: [{
274
- requirement: nil,
275
- groups: [],
276
- source: {
277
- type: "git",
278
- url: VCPKG_DEFAULT_BASELINE_URL,
279
- ref: VCPKG_DEFAULT_BASELINE_DEFAULT_BRANCH
280
- },
281
- file: dependency_file.name
282
- }],
283
- metadata: {
284
- builtin: true,
285
- default: is_default
286
- }
287
- )
288
- end
215
+ Dependabot::Dependency.new(
216
+ name: registry.name,
217
+ version: registry.baseline,
218
+ package_manager: "vcpkg",
219
+ requirements: [{
220
+ requirement: nil,
221
+ groups: [],
222
+ source: { type: "git", url: registry.repository, ref: registry.reference },
223
+ file: dependency_file.name
224
+ }],
225
+ metadata: metadata
226
+ )
289
227
  end
290
228
 
291
229
  # A project relying on a global vcpkg install has no baseline to update.
@@ -301,7 +239,7 @@ module Dependabot
301
239
  config = vcpkg_configuration_file
302
240
  if config.nil?
303
241
  synthetic_baseline_dependency(file_name: manifest.name)
304
- elsif default_registry.nil?
242
+ elsif !parsed_document(config)&.default_registry_present?
305
243
  synthetic_baseline_dependency(
306
244
  file_name: config.name,
307
245
  metadata: { default: true, create_default_registry: true }
@@ -310,7 +248,7 @@ module Dependabot
310
248
  end
311
249
 
312
250
  sig do
313
- params(file_name: String, metadata: T::Hash[Symbol, T.untyped]).returns(Dependabot::Dependency)
251
+ params(file_name: String, metadata: T::Hash[Symbol, T::Boolean]).returns(Dependabot::Dependency)
314
252
  end
315
253
  def synthetic_baseline_dependency(file_name:, metadata: {})
316
254
  Dependabot::Dependency.new(
@@ -341,30 +279,20 @@ module Dependabot
341
279
  manifest = vcpkg_manifest_file
342
280
  return false unless manifest
343
281
 
344
- parsed_json(manifest)&.dig("builtin-baseline").is_a?(String)
282
+ !parsed_document(manifest)&.builtin_baseline.nil?
345
283
  end
346
284
 
347
285
  sig { returns(T::Boolean) }
348
286
  def default_registry_baseline_present?
349
- registry = default_registry
350
- !!(registry && registry["baseline"].is_a?(String))
351
- end
352
-
353
- sig { returns(T.nilable(T::Hash[String, T.untyped])) }
354
- def default_registry
355
287
  config = vcpkg_configuration_file
356
- return nil unless config
288
+ return false unless config
357
289
 
358
- registry = parsed_json(config)&.dig("default-registry")
359
- registry.is_a?(Hash) ? registry : nil
290
+ !parsed_document(config)&.default_registry_baseline.nil?
360
291
  end
361
292
 
362
293
  sig { params(file: Dependabot::DependencyFile).returns(T::Boolean) }
363
294
  def manifest_declares_dependencies?(file)
364
- declared = parsed_json(file)&.dig("dependencies")
365
- return false unless declared.is_a?(Array)
366
-
367
- !declared.empty?
295
+ parsed_document(file)&.dependencies_declared? || false
368
296
  end
369
297
 
370
298
  sig { returns(T.nilable(Dependabot::DependencyFile)) }
@@ -377,14 +305,12 @@ module Dependabot
377
305
  dependency_files.find { |file| file.name == VCPKG_CONFIGURATION_JSON_FILENAME }
378
306
  end
379
307
 
380
- sig { params(file: Dependabot::DependencyFile).returns(T.nilable(T::Hash[String, T.untyped])) }
381
- def parsed_json(file)
382
- content = file.content
383
- return nil unless content
308
+ sig { params(file: Dependabot::DependencyFile).returns(T.nilable(ManifestDocument)) }
309
+ def parsed_document(file)
310
+ return nil unless file.content
384
311
 
385
- parsed = JSON.parse(content)
386
- parsed.is_a?(Hash) ? parsed : nil
387
- rescue JSON::ParserError
312
+ ManifestDocument.from_file(file)
313
+ rescue Dependabot::DependencyFileNotParseable
388
314
  nil
389
315
  end
390
316
 
@@ -1,13 +1,13 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
- require "json"
5
4
  require "sorbet-runtime"
6
5
 
7
6
  require "dependabot/file_updaters"
8
7
  require "dependabot/file_updaters/base"
9
8
  require "dependabot/vcpkg"
10
9
  require "dependabot/vcpkg/manifest_baseline"
10
+ require "dependabot/vcpkg/manifest_document"
11
11
  require "dependabot/vcpkg/version"
12
12
 
13
13
  module Dependabot
@@ -59,60 +59,50 @@ module Dependabot
59
59
 
60
60
  sig { params(file: Dependabot::DependencyFile).returns(String) }
61
61
  def updated_vcpkg_json_content(file)
62
- content = T.must(file.content)
63
- parsed_content = JSON.parse(content)
62
+ document = ManifestDocument.from_file(file)
64
63
 
65
64
  dependencies
66
65
  .filter_map { |dep| [dep, dep.requirements.find { |requirement| requirement.file == file.name }] }
67
66
  .select { |_, requirement| requirement }
68
- .each { |dependency, _| update_dependency_in_content(parsed_content, dependency, file.name) }
67
+ .each { |dependency, _| update_dependency_in_content(document, dependency, file.name) }
69
68
 
70
- apply_security_baseline(parsed_content, file.name)
69
+ apply_security_baseline(document, file.name)
71
70
 
72
- JSON.pretty_generate(parsed_content)
73
- rescue JSON::ParserError
74
- raise Dependabot::DependencyFileNotParseable, file.path
71
+ document.content
75
72
  end
76
73
 
77
74
  sig { params(file: Dependabot::DependencyFile).returns(String) }
78
75
  def updated_vcpkg_configuration_json_content(file)
79
- content = T.must(file.content)
80
- parsed_content = JSON.parse(content)
76
+ document = ManifestDocument.from_file(file)
81
77
 
82
78
  dependencies
83
79
  .filter_map { |dep| [dep, dep.requirements.find { |requirement| requirement.file == file.name }] }
84
80
  .select { |_, requirement| requirement }
85
- .each { |dependency, _| update_registry_dependency_in_content(parsed_content, dependency, file.name) }
81
+ .each { |dependency, _| update_registry_dependency_in_content(document, dependency, file.name) }
86
82
 
87
- apply_security_baseline(parsed_content, file.name)
83
+ apply_security_baseline(document, file.name)
88
84
 
89
- JSON.pretty_generate(parsed_content)
90
- rescue JSON::ParserError
91
- raise Dependabot::DependencyFileNotParseable, file.path
85
+ document.content
92
86
  end
93
87
 
94
- sig { params(content: T::Hash[String, T.untyped], dependency: Dependabot::Dependency, filename: String).void }
95
- def update_dependency_in_content(content, dependency, filename)
88
+ sig { params(document: ManifestDocument, dependency: Dependabot::Dependency, filename: String).void }
89
+ def update_dependency_in_content(document, dependency, filename)
96
90
  case dependency.name
97
91
  when VCPKG_DEFAULT_BASELINE_DEPENDENCY_NAME
98
- update_baseline_in_content(content, dependency, filename)
92
+ baseline = baseline_for(dependency, filename)
93
+ document.set_baseline(path: [VCPKG_BUILTIN_BASELINE_KEY], baseline: baseline) if baseline
99
94
  else
100
- update_port_dependency_in_content(content, dependency, filename)
95
+ update_port_dependency_in_content(document, dependency, filename)
101
96
  end
102
97
  end
103
98
 
104
- sig { params(content: T::Hash[String, T.untyped], dependency: Dependabot::Dependency, filename: String).void }
105
- def update_baseline_in_content(content, dependency, filename)
106
- update_baseline_field(content, dependency, filename, VCPKG_BUILTIN_BASELINE_KEY)
107
- end
108
-
109
- sig { params(content: T::Hash[String, T.untyped], dependency: Dependabot::Dependency, filename: String).void }
110
- def update_port_dependency_in_content(content, dependency, filename)
99
+ sig { params(document: ManifestDocument, dependency: Dependabot::Dependency, filename: String).void }
100
+ def update_port_dependency_in_content(document, dependency, filename)
111
101
  case remediation_for(dependency, filename)
112
- when :override then apply_override(content, dependency)
102
+ when :override then document.set_override(name: dependency.name, version: dependency.version)
113
103
  # A baseline bump moves the port's version floor, so the port entry needs no change.
114
104
  when :baseline then nil
115
- else update_version_constraint(content, dependency)
105
+ else document.set_port_version(name: dependency.name, version: dependency.version)
116
106
  end
117
107
  end
118
108
 
@@ -125,51 +115,6 @@ module Dependabot
125
115
  &.metadata_symbol("security_remediation")
126
116
  end
127
117
 
128
- sig { params(content: T::Hash[String, T.untyped], dependency: Dependabot::Dependency).void }
129
- def update_version_constraint(content, dependency)
130
- entries = content[VCPKG_DEPENDENCIES_KEY]
131
- return unless entries.is_a?(Array)
132
-
133
- index = entries.index { |entry| port_entry_name(entry) == dependency.name }
134
- return unless index
135
-
136
- entry = entries[index]
137
- # A port declared as a bare string has to become an object to carry a constraint.
138
- entry = { "name" => entry } if entry.is_a?(String)
139
- return unless entry.is_a?(Hash)
140
-
141
- entry[VCPKG_VERSION_CONSTRAINT_KEY] = dependency.version
142
- entries[index] = entry
143
- end
144
-
145
- # vcpkg cannot compare versions across schemes, so when no safe version shares the current
146
- # one's scheme, the only way to move the port is to pin it outright. `version` is the
147
- # scheme-agnostic key, and the port version belongs in it as a `#N` suffix: the separate
148
- # `port-version` key and the scheme-specific keys are both deprecated.
149
- # See https://learn.microsoft.com/vcpkg/reference/vcpkg-json#overrides
150
- sig { params(content: T::Hash[String, T.untyped], dependency: Dependabot::Dependency).void }
151
- def apply_override(content, dependency)
152
- entry = { "name" => dependency.name, "version" => dependency.version }
153
-
154
- overrides = content[VCPKG_OVERRIDES_KEY]
155
- unless overrides.is_a?(Array)
156
- overrides = []
157
- content[VCPKG_OVERRIDES_KEY] = overrides
158
- end
159
-
160
- index = overrides.index { |override| override.is_a?(Hash) && override["name"] == dependency.name }
161
- index ? overrides[index] = entry : overrides << entry
162
- end
163
-
164
- sig { params(entry: T.untyped).returns(T.nilable(String)) }
165
- def port_entry_name(entry)
166
- return entry if entry.is_a?(String)
167
- return nil unless entry.is_a?(Hash)
168
-
169
- name = entry["name"]
170
- name.is_a?(String) ? name : nil
171
- end
172
-
173
118
  # Where the fix wants the registry baseline moved to, if anywhere.
174
119
  sig { returns(T.nilable(T::Hash[Symbol, String])) }
175
120
  def security_baseline
@@ -193,19 +138,12 @@ module Dependabot
193
138
  { file: location.first, commit_sha: }
194
139
  end
195
140
 
196
- sig { params(content: T::Hash[String, T.untyped], filename: String).void }
197
- def apply_security_baseline(content, filename)
141
+ sig { params(document: ManifestDocument, filename: String).void }
142
+ def apply_security_baseline(document, filename)
198
143
  baseline = security_baseline
199
144
  return unless baseline && baseline[:file] == filename
200
145
 
201
- path = T.must(manifest_baseline.location).last
202
- key = path.last
203
- return unless key
204
-
205
- target = T.must(path[0...-1]).reduce(content) { |node, segment| node.is_a?(Hash) ? node[segment] : nil }
206
- return unless target.is_a?(Hash)
207
-
208
- target[key] = baseline[:commit_sha]
146
+ document.set_baseline(path: T.must(manifest_baseline.location).last, baseline: baseline.fetch(:commit_sha))
209
147
  end
210
148
 
211
149
  sig { returns(Dependabot::Vcpkg::ManifestBaseline) }
@@ -216,92 +154,27 @@ module Dependabot
216
154
  )
217
155
  end
218
156
 
219
- sig { params(content: T::Hash[String, T.untyped], dependency: Dependabot::Dependency, filename: String).void }
220
- def update_registry_dependency_in_content(content, dependency, filename)
221
- # Check if this is a default registry update based on metadata
157
+ sig { params(document: ManifestDocument, dependency: Dependabot::Dependency, filename: String).void }
158
+ def update_registry_dependency_in_content(document, dependency, filename)
159
+ baseline = baseline_for(dependency, filename)
160
+ return unless baseline
161
+
222
162
  if dependency.metadata[:default]
223
- update_default_registry(content, dependency, filename)
163
+ document.set_default_registry_baseline(
164
+ baseline: baseline, create: !!dependency.metadata[:create_default_registry]
165
+ )
224
166
  else
225
- # For registries array, find by repository URL
226
- update_registry_by_name(content, dependency, filename)
227
- end
228
- end
229
-
230
- sig { params(content: T::Hash[String, T.untyped], dependency: Dependabot::Dependency, filename: String).void }
231
- def update_default_registry(content, dependency, filename)
232
- default_registry = content["default-registry"]
233
- if default_registry.is_a?(Hash)
234
- update_baseline_field(default_registry, dependency, filename, "baseline")
235
- elsif dependency.metadata[:create_default_registry]
236
- created_registry = build_default_registry(dependency, filename)
237
- content["default-registry"] = created_registry if created_registry
167
+ document.set_registry_baseline(
168
+ baseline: baseline,
169
+ repository: dependency.requirements.first&.source_string("url"),
170
+ builtin: !!dependency.metadata[:builtin]
171
+ )
238
172
  end
239
173
  end
240
174
 
241
- sig do
242
- params(dependency: Dependabot::Dependency, filename: String)
243
- .returns(T.nilable(T::Hash[String, String]))
244
- end
245
- def build_default_registry(dependency, filename)
246
- requirement = dependency.requirements.find { |candidate| candidate.file == filename }
247
- return unless requirement
248
-
249
- baseline = requirement.source_string("ref")
250
- return unless baseline
251
-
252
- {
253
- "kind" => "git",
254
- "repository" => VCPKG_DEFAULT_REGISTRY_REPOSITORY,
255
- "baseline" => baseline
256
- }
257
- end
258
-
259
- sig { params(content: T::Hash[String, T.untyped], dependency: Dependabot::Dependency, filename: String).void }
260
- def update_registry_by_name(content, dependency, filename)
261
- registries = content["registries"]
262
- return unless registries.is_a?(Array)
263
-
264
- # Find registry based on dependency characteristics
265
- registry = find_target_registry(registries, dependency)
266
- return unless registry
267
-
268
- update_baseline_field(registry, dependency, filename, "baseline")
269
- end
270
-
271
- sig do
272
- params(
273
- target: T::Hash[String, T.untyped],
274
- dependency: Dependabot::Dependency,
275
- filename: String,
276
- field_name: String
277
- ).void
278
- end
279
- def update_baseline_field(target, dependency, filename, field_name)
280
- # Find the requirement for this specific file
281
- requirement = dependency.requirements.find { |candidate| candidate.file == filename }
282
- return unless requirement
283
-
284
- # Extract and validate the new baseline
285
- new_baseline = requirement.source_string("ref")
286
- target[field_name] = new_baseline if new_baseline
287
- end
288
-
289
- sig do
290
- params(
291
- registries: T::Array[T.untyped],
292
- dependency: Dependabot::Dependency
293
- )
294
- .returns(T.nilable(T::Hash[String, T.untyped]))
295
- end
296
- def find_target_registry(registries, dependency)
297
- if dependency.metadata[:builtin]
298
- # For builtin registries, find by kind
299
- registries.find { |r| r.is_a?(Hash) && r["kind"] == "builtin" }
300
- else
301
- # For git registries, find by repository URL
302
- repository_url = dependency.requirements.first&.source_string("url")
303
- registries.find { |r| r.is_a?(Hash) && r["repository"] == repository_url }
304
- end
175
+ sig { params(dependency: Dependabot::Dependency, filename: String).returns(T.nilable(String)) }
176
+ def baseline_for(dependency, filename)
177
+ dependency.requirements.find { |candidate| candidate.file == filename }&.source_string("ref")
305
178
  end
306
179
  end
307
180
  end
@@ -1,12 +1,12 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
- require "json"
5
4
  require "sorbet-runtime"
6
5
 
7
6
  require "dependabot/dependency_file"
8
7
 
9
8
  require "dependabot/vcpkg"
9
+ require "dependabot/vcpkg/manifest_document"
10
10
 
11
11
  module Dependabot
12
12
  module Vcpkg
@@ -52,36 +52,31 @@ module Dependabot
52
52
  manifest = vcpkg_manifest_file
53
53
  return nil unless manifest
54
54
 
55
- baseline = parsed_json(manifest)&.dig(VCPKG_BUILTIN_BASELINE_KEY)
56
- baseline.is_a?(String) ? baseline : nil
55
+ parsed_document(manifest)&.builtin_baseline
56
+ rescue Dependabot::DependencyFileNotParseable
57
+ nil
57
58
  end
58
59
 
59
60
  sig { returns(T.nilable(String)) }
60
61
  def default_registry_builtin_baseline
61
- registry = default_registry
62
- return nil unless registry
63
- return nil unless builtin_registry?(registry)
64
-
65
- baseline = registry["baseline"]
66
- baseline.is_a?(String) ? baseline : nil
67
- end
68
-
69
- sig { returns(T.nilable(T::Hash[String, Object])) }
70
- def default_registry
71
62
  config = vcpkg_configuration_file
72
63
  return nil unless config
73
64
 
74
- registry = parsed_json(config)&.dig("default-registry")
75
- registry.is_a?(Hash) ? registry : nil
65
+ document = parsed_document(config)
66
+ return nil unless document && builtin_registry?(document)
67
+
68
+ document.default_registry_baseline
69
+ rescue Dependabot::DependencyFileNotParseable
70
+ nil
76
71
  end
77
72
 
78
- sig { params(registry: T::Hash[String, Object]).returns(T::Boolean) }
79
- def builtin_registry?(registry)
80
- return true if registry["kind"] == "builtin"
81
- return false unless registry["kind"] == "git"
73
+ sig { params(document: ManifestDocument).returns(T::Boolean) }
74
+ def builtin_registry?(document)
75
+ return true if document.default_registry_kind == "builtin"
76
+ return false unless document.default_registry_kind == "git"
82
77
 
83
- repository = registry["repository"]
84
- return false unless repository.is_a?(String)
78
+ repository = document.default_registry_repository
79
+ return false unless repository
85
80
 
86
81
  official = [VCPKG_DEFAULT_REGISTRY_REPOSITORY, VCPKG_DEFAULT_BASELINE_URL]
87
82
  official.include?(repository.delete_suffix("/"))
@@ -97,14 +92,12 @@ module Dependabot
97
92
  dependency_files.find { |file| file.name == VCPKG_CONFIGURATION_JSON_FILENAME }
98
93
  end
99
94
 
100
- sig { params(file: Dependabot::DependencyFile).returns(T.nilable(T::Hash[String, Object])) }
101
- def parsed_json(file)
102
- content = file.content
103
- return nil unless content
95
+ sig { params(file: Dependabot::DependencyFile).returns(T.nilable(ManifestDocument)) }
96
+ def parsed_document(file)
97
+ return nil unless file.content
104
98
 
105
- parsed = JSON.parse(content)
106
- parsed.is_a?(Hash) ? parsed : nil
107
- rescue JSON::ParserError
99
+ ManifestDocument.from_file(file)
100
+ rescue Dependabot::DependencyFileNotParseable
108
101
  nil
109
102
  end
110
103
  end
@@ -0,0 +1,259 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "sorbet-runtime"
6
+ require "dependabot/dependency_file"
7
+ require "dependabot/errors"
8
+ require "dependabot/logger"
9
+ require "dependabot/vcpkg"
10
+
11
+ module Dependabot
12
+ module Vcpkg
13
+ class ManifestDocument
14
+ extend T::Sig
15
+
16
+ ObjectHash = T.type_alias { T::Hash[String, Object] }
17
+
18
+ class Port < T::ImmutableStruct
19
+ const :name, String
20
+ const :constraint, T.nilable(String)
21
+ end
22
+
23
+ class Registry < T::ImmutableStruct
24
+ const :name, String
25
+ const :baseline, String
26
+ const :repository, String
27
+ const :reference, String
28
+ const :builtin, T::Boolean
29
+ end
30
+
31
+ sig { params(file: DependencyFile).returns(ManifestDocument) }
32
+ def self.from_file(file)
33
+ new(file)
34
+ end
35
+
36
+ sig { params(file: DependencyFile).void }
37
+ def initialize(file)
38
+ @path = T.let(file.path, String)
39
+ @data = T.let(object(T.cast(JSON.parse(T.must(file.content)), Object), "root"), ObjectHash)
40
+ rescue JSON::ParserError
41
+ raise DependencyFileNotParseable.new(@path, "#{@path}: invalid JSON")
42
+ end
43
+
44
+ sig { returns(T.nilable(String)) }
45
+ def builtin_baseline
46
+ value = @data[VCPKG_BUILTIN_BASELINE_KEY]
47
+ return if value.nil?
48
+
49
+ string(value, VCPKG_BUILTIN_BASELINE_KEY)
50
+ end
51
+
52
+ sig { returns(T::Boolean) }
53
+ def dependencies_declared?
54
+ !array(@data[VCPKG_DEPENDENCIES_KEY], VCPKG_DEPENDENCIES_KEY).empty?
55
+ end
56
+
57
+ sig { returns(T::Array[Port]) }
58
+ def ports
59
+ array(@data[VCPKG_DEPENDENCIES_KEY], VCPKG_DEPENDENCIES_KEY).filter_map do |entry|
60
+ case entry
61
+ when String
62
+ Port.new(name: entry, constraint: nil)
63
+ when Hash
64
+ fields = object(entry, "dependency")
65
+ name = fields["name"]
66
+ next unless name.is_a?(String)
67
+
68
+ constraint = fields[VCPKG_VERSION_CONSTRAINT_KEY]
69
+ Port.new(name: name, constraint: constraint.is_a?(String) ? constraint : nil)
70
+ else
71
+ Dependabot.logger.warn("Skipping unknown vcpkg dependency format: #{entry.inspect}")
72
+ nil
73
+ end
74
+ end
75
+ end
76
+
77
+ sig { returns(T::Boolean) }
78
+ def default_registry_present?
79
+ !default_registry_fields.nil?
80
+ end
81
+
82
+ sig { returns(T.nilable(String)) }
83
+ def default_registry_kind
84
+ value = default_registry_fields&.[]("kind")
85
+ value if value.is_a?(String)
86
+ end
87
+
88
+ sig { returns(T.nilable(String)) }
89
+ def default_registry_repository
90
+ value = default_registry_fields&.[]("repository")
91
+ value if value.is_a?(String)
92
+ end
93
+
94
+ sig { returns(T.nilable(String)) }
95
+ def default_registry_baseline
96
+ value = default_registry_fields&.[]("baseline")
97
+ value if value.is_a?(String)
98
+ end
99
+
100
+ sig { returns(T.nilable(Registry)) }
101
+ def default_registry
102
+ fields = default_registry_fields
103
+ registry(fields, "default-registry") if fields
104
+ end
105
+
106
+ sig { returns(T::Array[Registry]) }
107
+ def registries
108
+ array(@data["registries"], "registries").each_with_index.filter_map do |entry, index|
109
+ field = "registries[#{index}]"
110
+ registry(object(entry, field), field)
111
+ end
112
+ end
113
+
114
+ sig { params(name: String, version: T.nilable(String)).void }
115
+ def set_port_version(name:, version:)
116
+ entries = array(@data[VCPKG_DEPENDENCIES_KEY], VCPKG_DEPENDENCIES_KEY)
117
+ index = entries.index { |entry| port_name(entry) == name }
118
+ return unless index
119
+
120
+ entry = entries.fetch(index)
121
+ fields = entry.is_a?(String) ? { "name" => entry } : object(entry, "dependencies[#{index}]")
122
+ fields[VCPKG_VERSION_CONSTRAINT_KEY] = version
123
+ entries[index] = fields
124
+ @data[VCPKG_DEPENDENCIES_KEY] = entries
125
+ end
126
+
127
+ # Overrides replace the whole pin so deprecated scheme/port-version fields cannot conflict.
128
+ sig { params(name: String, version: T.nilable(String)).void }
129
+ def set_override(name:, version:)
130
+ overrides = object_array(@data[VCPKG_OVERRIDES_KEY], VCPKG_OVERRIDES_KEY)
131
+ index = overrides.index { |entry| entry["name"] == name }
132
+ entry = { "name" => name, "version" => version }
133
+ index ? overrides[index] = entry : overrides << entry
134
+ @data[VCPKG_OVERRIDES_KEY] = overrides
135
+ end
136
+
137
+ sig { params(baseline: String, create: T::Boolean).void }
138
+ def set_default_registry_baseline(baseline:, create:)
139
+ fields = default_registry_fields
140
+ if fields
141
+ fields["baseline"] = baseline
142
+ elsif create
143
+ @data["default-registry"] = {
144
+ "kind" => "git",
145
+ "repository" => VCPKG_DEFAULT_REGISTRY_REPOSITORY,
146
+ "baseline" => baseline
147
+ }
148
+ end
149
+ end
150
+
151
+ sig { params(baseline: String, repository: T.nilable(String), builtin: T::Boolean).void }
152
+ def set_registry_baseline(baseline:, repository:, builtin:)
153
+ entries = object_array(@data["registries"], "registries")
154
+ registry = entries.find do |entry|
155
+ builtin ? entry["kind"] == "builtin" : entry["repository"] == repository
156
+ end
157
+ registry["baseline"] = baseline if registry
158
+ end
159
+
160
+ sig { params(path: T::Array[String], baseline: String).void }
161
+ def set_baseline(path:, baseline:)
162
+ target = @data
163
+ path[0...-1].to_a.each_with_index do |segment, index|
164
+ target = object(target[segment], path.take(index + 1).join("."))
165
+ end
166
+ target[T.must(path.last)] = baseline
167
+ end
168
+
169
+ sig { returns(String) }
170
+ def content
171
+ JSON.pretty_generate(@data)
172
+ end
173
+
174
+ private
175
+
176
+ sig { returns(T.nilable(ObjectHash)) }
177
+ def default_registry_fields
178
+ value = @data["default-registry"]
179
+ object(value, "default-registry") unless value.nil?
180
+ end
181
+
182
+ sig { params(fields: ObjectHash, field: String).returns(T.nilable(Registry)) }
183
+ def registry(fields, field)
184
+ kind = fields["kind"]
185
+ baseline = fields["baseline"]
186
+ return unless VCPKG_SUPPORTED_REGISTRY_TYPES.include?(kind) && baseline.is_a?(String)
187
+
188
+ if kind == "builtin"
189
+ Registry.new(
190
+ name: VCPKG_DEFAULT_BASELINE_DEPENDENCY_NAME,
191
+ baseline: baseline,
192
+ repository: VCPKG_DEFAULT_BASELINE_URL,
193
+ reference: VCPKG_DEFAULT_BASELINE_DEFAULT_BRANCH,
194
+ builtin: true
195
+ )
196
+ else
197
+ repository = fields["repository"]
198
+ return unless repository.is_a?(String)
199
+
200
+ Registry.new(
201
+ name: repository,
202
+ baseline: baseline,
203
+ repository: repository,
204
+ reference: string(fields["reference"] || "HEAD", "#{field}.reference"),
205
+ builtin: false
206
+ )
207
+ end
208
+ end
209
+
210
+ sig { params(entry: Object).returns(T.nilable(String)) }
211
+ def port_name(entry)
212
+ return entry if entry.is_a?(String)
213
+ return unless entry.is_a?(Hash)
214
+
215
+ name = object(entry, "dependency")["name"]
216
+ name if name.is_a?(String)
217
+ end
218
+
219
+ sig { params(value: Object, field: String).returns(ObjectHash) }
220
+ def object(value, field)
221
+ invalid(field, "must be an object") unless value.is_a?(Hash)
222
+
223
+ value.each_key do |raw_key|
224
+ invalid(field, "keys must be strings") unless T.cast(raw_key, Object).is_a?(String)
225
+ end
226
+ # Keep the parsed node's identity so nested edits reach the serialized document.
227
+ value
228
+ end
229
+
230
+ sig { params(value: Object, field: String).returns(T::Array[ObjectHash]) }
231
+ def object_array(value, field)
232
+ array(value, field).each_with_index.map do |entry, index|
233
+ object(entry, "#{field}[#{index}]")
234
+ end
235
+ end
236
+
237
+ sig { params(value: Object, field: String).returns(T::Array[Object]) }
238
+ def array(value, field)
239
+ return [] if value.nil?
240
+
241
+ invalid(field, "must be an array") unless value.is_a?(Array)
242
+
243
+ value.map { |entry| T.cast(entry, Object) }
244
+ end
245
+
246
+ sig { params(value: Object, field: String).returns(String) }
247
+ def string(value, field)
248
+ return value if value.is_a?(String)
249
+
250
+ invalid(field, "must be a string")
251
+ end
252
+
253
+ sig { params(field: String, message: String).returns(T.noreturn) }
254
+ def invalid(field, message)
255
+ raise DependencyFileNotParseable.new(@path, "#{@path}: #{field} #{message}")
256
+ end
257
+ end
258
+ end
259
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-vcpkg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.398.0
4
+ version: 0.400.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.398.0
18
+ version: 0.400.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.398.0
25
+ version: 0.400.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -245,6 +245,7 @@ files:
245
245
  - lib/dependabot/vcpkg/file_updater.rb
246
246
  - lib/dependabot/vcpkg/language.rb
247
247
  - lib/dependabot/vcpkg/manifest_baseline.rb
248
+ - lib/dependabot/vcpkg/manifest_document.rb
248
249
  - lib/dependabot/vcpkg/metadata_finder.rb
249
250
  - lib/dependabot/vcpkg/package/package_details_fetcher.rb
250
251
  - lib/dependabot/vcpkg/package/versions_database.rb
@@ -259,7 +260,7 @@ licenses:
259
260
  - MIT
260
261
  metadata:
261
262
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
262
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.398.0
263
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.400.0
263
264
  rdoc_options: []
264
265
  require_paths:
265
266
  - lib