dependabot-bun 0.395.0 → 0.396.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: '0648b7baf73e363870c4861430535dc9f35abf4152baa7d8c7005a8c24a0db17'
4
- data.tar.gz: 4fc09e11fbebaf9dd3ca41200fc727b6b01e5bfff5533922bd6965cd3e83c6aa
3
+ metadata.gz: 8d8c5792f04904252b5cafdfc832e3ffd55327fed710c2192bf9d10c8201ef21
4
+ data.tar.gz: 18f707c30fc183e89236ed35019a5c7cbb6a8bbeea8be17f0c291544bb881a7a
5
5
  SHA512:
6
- metadata.gz: 254c7ce06535deeef02648b0620e3f199a538e934bd1ce24cbd8c454cfe33fb8dcfb1efe8f14ead0d6d208e85505235e3706c9e1da06a925bdd1022e3dc1da1e
7
- data.tar.gz: 5ff486306f46d916352f1daa4d0a7288861e84b2dba0b27b614a0beaf07345f78852b2f3c4a6bcce41aa257b60b1cb96aa62e6d735026be411b44596b25bedde
6
+ metadata.gz: a100f03e67e2dde0fe734fa91c66137508583cbfb38f1006e5c86afce7b511ac383819c04be14b3b0bfb9946fef72c2bc7222e7c066cb7bca7767206c8d4d82e
7
+ data.tar.gz: 0a67133acfc05a31241115d8a35be5f978d6a63dd6b01b19ca1509cdd0eadb213e3783b0f0bf348d12f1f151bd04558631d1713f20e9f28ad9ffa06c426bda24
@@ -1,7 +1,8 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/utils"
5
+ require "dependabot/package/npm_package_json"
5
6
  require "dependabot/bun/file_parser/lockfile_parser"
6
7
  require "sorbet-runtime"
7
8
 
@@ -87,7 +88,7 @@ module Dependabot
87
88
  def workspaces_lockfile?(lockfile)
88
89
  return false unless ["yarn.lock", "package-lock.json", "pnpm-lock.yaml", "bun.lock"].include?(lockfile.name)
89
90
 
90
- return false unless parsed_root_package_json["workspaces"] || dependency_files.any? do |file|
91
+ return false unless root_package_json_document.workspaces? || dependency_files.any? do |file|
91
92
  file.name.end_with?("pnpm-workspace.yaml") && File.dirname(file.name) == File.dirname(lockfile.name)
92
93
  end
93
94
 
@@ -114,14 +115,14 @@ module Dependabot
114
115
  )
115
116
  end
116
117
 
117
- sig { returns(T::Hash[String, T.untyped]) }
118
- def parsed_root_package_json
119
- @parsed_root_package_json ||= T.let(
118
+ sig { returns(Dependabot::Package::NpmPackageJson) }
119
+ def root_package_json_document
120
+ @root_package_json_document ||= T.let(
120
121
  begin
121
122
  package = T.must(dependency_files.find { |f| f.name == "package.json" })
122
- JSON.parse(T.must(package.content))
123
+ Dependabot::Package::NpmPackageJson.from_file(package)
123
124
  end,
124
- T.nilable(T::Hash[String, T.untyped])
125
+ T.nilable(Dependabot::Package::NpmPackageJson)
125
126
  )
126
127
  end
127
128
 
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -110,17 +110,15 @@ module Dependabot
110
110
  def fetch_package_relationships
111
111
  return {} unless lockfile
112
112
 
113
- parsed_lockfile = FileParser::BunLock.new(T.must(lockfile)).parsed
114
- packages = parsed_lockfile.fetch("packages", nil)
115
- return {} unless packages.is_a?(Hash)
113
+ packages = FileParser::BunLock.new(T.must(lockfile)).records
114
+ return {} unless packages
116
115
 
117
- # bun.lock entries are arrays: ["{name}@{version}", registry, {details}, integrity]
118
- packages.each_with_object({}) do |(_key, entry), rels|
119
- next unless entry.is_a?(Array) && entry.first.is_a?(String)
116
+ packages.each_with_object({}) do |(_key, record), rels|
117
+ next unless record.graph_compatible?
120
118
 
121
- parent_name = T.must(T.cast(entry.first, String).split(/(?<=\w)\@/).first)
122
- children = entry.dig(2, "dependencies")&.keys
123
- next unless children&.any?
119
+ parent_name = record.name
120
+ children = record.dependency_names
121
+ next if children.empty?
124
122
 
125
123
  rels[parent_name] = children
126
124
  end
@@ -0,0 +1,101 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/bun/file_parser/bun_lock"
6
+ require "dependabot/package/npm_lockfile_details"
7
+
8
+ module Dependabot
9
+ module Bun
10
+ class FileParser < Dependabot::FileParsers::Base
11
+ class BunLock
12
+ class Record
13
+ extend T::Sig
14
+
15
+ sig { params(data: Object, path: String, context: String).void }
16
+ def initialize(data, path:, context:)
17
+ @data = data
18
+ @path = path
19
+ @context = context
20
+ @entry = T.let(nil, T.nilable(T::Array[Object]))
21
+ end
22
+
23
+ sig { returns(T::Boolean) }
24
+ def graph_compatible?
25
+ data = @data
26
+ data.is_a?(Array) && T.cast(data.first, Object).is_a?(String)
27
+ end
28
+
29
+ sig { returns(String) }
30
+ def name
31
+ name = resolution_label.split(/(?<=\w)\@/).first
32
+ invalid!("#{@context}[0] must contain a package name") unless name
33
+
34
+ name
35
+ end
36
+
37
+ sig { returns(T.nilable(String)) }
38
+ def version
39
+ Version.semver_for(package_resolution)&.to_s
40
+ end
41
+
42
+ sig { returns(Dependabot::Package::NpmLockfileDetails) }
43
+ def lookup_details
44
+ semver = version
45
+ Dependabot::Package::NpmLockfileDetails.new(
46
+ version: semver,
47
+ resolution: semver ? nil : package_resolution
48
+ )
49
+ end
50
+
51
+ sig { returns(T::Array[String]) }
52
+ def dependency_names
53
+ details = entry[2]
54
+ return [] if details.nil?
55
+
56
+ invalid!("#{@context} details must be an object") unless details.is_a?(Hash)
57
+ dependencies = T.cast(details["dependencies"], Object)
58
+ return [] if dependencies.nil?
59
+
60
+ invalid!("#{@context}.dependencies must be an object") unless dependencies.is_a?(Hash)
61
+ dependencies.keys.map do |raw_name|
62
+ child = T.cast(raw_name, Object)
63
+ invalid!("#{@context}.dependencies keys must be strings") unless child.is_a?(String)
64
+
65
+ child
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ sig { returns(T::Array[Object]) }
72
+ def entry
73
+ return @entry if @entry
74
+
75
+ data = @data
76
+ invalid!("#{@context} must be an array") unless data.is_a?(Array)
77
+ @entry = data.map { |value| T.cast(value, Object) }
78
+ end
79
+
80
+ sig { returns(String) }
81
+ def resolution_label
82
+ label = entry.first
83
+ invalid!("#{@context}[0] must be a string") unless label.is_a?(String)
84
+
85
+ label
86
+ end
87
+
88
+ sig { returns(T.nilable(String)) }
89
+ def package_resolution
90
+ resolution_label.split(/(?<=\w)\@/)[1]
91
+ end
92
+
93
+ sig { params(message: String).returns(T.noreturn) }
94
+ def invalid!(message)
95
+ raise DependencyFileNotParseable.new(@path, "Invalid bun.lock file: #{message}")
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,8 +1,9 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "yaml"
5
5
  require "dependabot/errors"
6
+ require "dependabot/bun/file_parser"
6
7
  require "dependabot/bun/bun_package_manager"
7
8
  require "dependabot/bun/helpers"
8
9
  require "sorbet-runtime"
@@ -13,22 +14,19 @@ module Dependabot
13
14
  class BunLock
14
15
  extend T::Sig
15
16
 
17
+ require_relative "bun_lock/record"
18
+
16
19
  sig { params(dependency_file: DependencyFile).void }
17
20
  def initialize(dependency_file)
18
21
  @dependency_file = dependency_file
22
+ @parsed = T.let(nil, T.nilable(T::Hash[Object, Object]))
23
+ @records = T.let(nil, T.nilable(T::Hash[String, Record]))
19
24
  end
20
25
 
21
- sig { returns(T::Hash[String, T.untyped]) }
26
+ sig { returns(T::Hash[Object, Object]) }
22
27
  def parsed
23
28
  @parsed ||= begin
24
- content = begin
25
- # Since bun.lock is a JSONC file, which is a subset of YAML, we can use YAML to parse it
26
- YAML.load(T.must(@dependency_file.content))
27
- rescue Psych::SyntaxError => e
28
- raise_invalid!("malformed JSONC at line #{e.line}, column #{e.column}")
29
- end
30
- raise_invalid!("expected to be an object") unless content.is_a?(Hash)
31
-
29
+ content = parse_document
32
30
  version = content["lockfileVersion"]
33
31
  raise_invalid!("expected 'lockfileVersion' to be an integer") unless version.is_a?(Integer)
34
32
  raise_invalid!("expected 'lockfileVersion' to be >= 0") unless version >= 0
@@ -45,7 +43,23 @@ module Dependabot
45
43
  end
46
44
  end
47
45
 
48
- T.let(content, T.untyped)
46
+ content
47
+ end
48
+ end
49
+ private :parsed
50
+
51
+ sig { returns(T.nilable(T::Hash[String, Record])) }
52
+ def records
53
+ return @records if @records
54
+
55
+ packages = parsed["packages"]
56
+ return unless packages.is_a?(Hash)
57
+
58
+ @records = packages.to_h do |raw_key, value|
59
+ key = T.cast(raw_key, Object)
60
+ raise_invalid!("expected package keys to be strings") unless key.is_a?(String)
61
+
62
+ [key, Record.new(T.cast(value, Object), path: @dependency_file.path, context: "packages.#{key}")]
49
63
  end
50
64
  end
51
65
 
@@ -56,24 +70,19 @@ module Dependabot
56
70
  # bun.lock v0 format:
57
71
  # https://github.com/oven-sh/bun/blob/c130df6c589fdf28f9f3c7f23ed9901140bc9349/src/install/bun.lock.zig#L595-L605
58
72
 
59
- packages = parsed["packages"]
60
- raise_invalid!("expected 'packages' to be an object") unless packages.is_a?(Hash)
61
-
62
- packages.each do |key, details|
63
- raise_invalid!("expected 'packages.#{key}' to be an array") unless details.is_a?(Array)
73
+ packages = records
74
+ raise_invalid!("expected 'packages' to be an object") unless packages
64
75
 
65
- resolution = details.first
66
- raise_invalid!("expected 'packages.#{key}[0]' to be a string") unless resolution.is_a?(String)
67
-
68
- name, version = resolution.split(/(?<=\w)\@/)
76
+ packages.each_value do |record|
77
+ name = record.name
69
78
  next if name.empty?
70
79
 
71
- semver = Version.semver_for(version)
80
+ semver = record.version
72
81
  next unless semver
73
82
 
74
83
  dependency_set << Dependency.new(
75
84
  name: name,
76
- version: semver.to_s,
85
+ version: semver,
77
86
  package_manager: "bun",
78
87
  requirements: []
79
88
  )
@@ -83,33 +92,27 @@ module Dependabot
83
92
  end
84
93
 
85
94
  sig do
86
- params(dependency_name: String, requirement: T.untyped, _manifest_name: String)
87
- .returns(T.nilable(T::Hash[String, T.untyped]))
95
+ params(dependency_name: String, _requirement: T.nilable(String), _manifest_name: String)
96
+ .returns(T.nilable(Dependabot::Package::NpmLockfileDetails))
88
97
  end
89
- def details(dependency_name, requirement, _manifest_name)
90
- packages = parsed["packages"]
91
- return unless packages.is_a?(Hash)
92
-
93
- candidates =
94
- packages
95
- .select { |name, _| name == dependency_name }
96
- .values
97
-
98
- # If there's only one entry for this dependency, use it, even if
99
- # the requirement in the lockfile doesn't match
100
- if candidates.one?
101
- parse_details(candidates.first)
102
- else
103
- candidate = candidates.find do |label, _|
104
- label.scan(/(?<=\w)\@(?:npm:)?([^\s,]+)/).flatten.include?(requirement)
105
- end&.last
106
- parse_details(candidate)
107
- end
98
+ def details(dependency_name, _requirement, _manifest_name)
99
+ records&.[](dependency_name)&.lookup_details
108
100
  end
109
101
 
110
102
  private
111
103
 
112
- sig { params(message: String).void }
104
+ sig { returns(T::Hash[Object, Object]) }
105
+ def parse_document
106
+ # Since bun.lock is a JSONC file, which is a subset of YAML, we can use YAML to parse it
107
+ content = T.cast(YAML.load(T.must(@dependency_file.content)), Object)
108
+ raise_invalid!("expected to be an object") unless content.is_a?(Hash)
109
+
110
+ content.to_h { |key, value| [T.cast(key, Object), T.cast(value, Object)] }
111
+ rescue Psych::SyntaxError => e
112
+ raise_invalid!("malformed JSONC at line #{e.line}, column #{e.column}")
113
+ end
114
+
115
+ sig { params(message: String).returns(T.noreturn) }
113
116
  def raise_invalid!(message)
114
117
  raise Dependabot::DependencyFileNotParseable.new(@dependency_file.path, "Invalid bun.lock file: #{message}")
115
118
  end
@@ -124,40 +127,6 @@ module Dependabot
124
127
  "The bun version Dependabot runs supports up to " \
125
128
  "#{BunPackageManager::MAX_SUPPORTED_LOCKFILE_VERSION}."
126
129
  end
127
-
128
- sig do
129
- params(entry: T.nilable(T::Array[T.untyped])).returns(T.nilable(T::Hash[String, T.untyped]))
130
- end
131
- def parse_details(entry)
132
- return unless entry.is_a?(Array)
133
-
134
- # Either:
135
- # - "{name}@{version}", registry, details, integrity
136
- # - "{name}@{resolution}", details
137
- resolution = entry.first
138
- return unless resolution.is_a?(String)
139
-
140
- name, version = resolution.split(/(?<=\w)\@/)
141
- semver = Version.semver_for(version)
142
-
143
- if semver
144
- registry, details, integrity = entry[1..3]
145
- {
146
- "name" => name,
147
- "version" => semver.to_s,
148
- "registry" => registry,
149
- "details" => details,
150
- "integrity" => integrity
151
- }
152
- else
153
- details = entry[1]
154
- {
155
- "name" => name,
156
- "resolution" => version,
157
- "details" => details
158
- }
159
- end
160
- end
161
130
  end
162
131
  end
163
132
  end
@@ -4,6 +4,7 @@
4
4
  require "dependabot/dependency_file"
5
5
  require "dependabot/bun/file_parser"
6
6
  require "dependabot/bun/helpers"
7
+ require "dependabot/package/npm_lockfile_details"
7
8
  require "sorbet-runtime"
8
9
 
9
10
  module Dependabot
@@ -45,10 +46,10 @@ module Dependabot
45
46
 
46
47
  sig do
47
48
  params(dependency_name: String, requirement: T.nilable(String), manifest_name: String)
48
- .returns(T.nilable(T::Hash[String, T.untyped]))
49
+ .returns(T.nilable(Dependabot::Package::NpmLockfileDetails))
49
50
  end
50
51
  def lockfile_details(dependency_name:, requirement:, manifest_name:)
51
- details = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
52
+ details = T.let(nil, T.nilable(Dependabot::Package::NpmLockfileDetails))
52
53
  potential_lockfiles_for_manifest(manifest_name).each do |lockfile|
53
54
  details = lockfile_for(lockfile).details(dependency_name, requirement, manifest_name)
54
55
 
@@ -5,6 +5,8 @@
5
5
 
6
6
  require "cgi/escape"
7
7
  require "dependabot/dependency"
8
+ require "dependabot/package/npm_lockfile_details"
9
+ require "dependabot/package/npm_package_json"
8
10
  require "dependabot/file_parsers"
9
11
  require "dependabot/file_parsers/base"
10
12
  require "dependabot/shared_helpers"
@@ -98,7 +100,7 @@ module Dependabot
98
100
  def package_manager_helper
99
101
  @package_manager_helper ||= T.let(
100
102
  PackageManagerHelper.new(
101
- Dependabot::Package::NpmPackageManagerConfig.from_package_json(parsed_package_json),
103
+ package_json_document.package_manager_config,
102
104
  lockfiles,
103
105
  registry_config_files,
104
106
  credentials
@@ -121,9 +123,9 @@ module Dependabot
121
123
  }
122
124
  end
123
125
 
124
- sig { returns(T.untyped) }
125
- def parsed_package_json
126
- JSON.parse(T.must(package_json.content))
126
+ sig { returns(Dependabot::Package::NpmPackageJson) }
127
+ def package_json_document
128
+ Dependabot::Package::NpmPackageJson.from_file(package_json)
127
129
  rescue JSON::ParserError
128
130
  raise Dependabot::DependencyFileNotParseable, package_json.path
129
131
  end
@@ -162,16 +164,14 @@ module Dependabot
162
164
  dependency_set = DependencySet.new
163
165
 
164
166
  package_files.each do |file|
165
- json = JSON.parse(T.must(file.content))
167
+ manifest = Dependabot::Package::NpmPackageJson.from_file(file)
166
168
 
167
169
  # TODO: Currently, Dependabot can't handle flat dependency files
168
170
  # (and will error at the FileUpdater stage, because the
169
171
  # UpdateChecker doesn't take account of flat resolution).
170
- next if json["flat"]
171
-
172
- self.class.each_dependency(json) do |name, requirement, type|
173
- next unless requirement.is_a?(String)
172
+ next if manifest.flat?
174
173
 
174
+ manifest.each_dependency do |name, requirement, type|
175
175
  # Skip dependencies using Yarn workspace cross-references as requirements
176
176
  next if requirement.start_with?("workspace:", "catalog:")
177
177
 
@@ -202,7 +202,7 @@ module Dependabot
202
202
  end
203
203
 
204
204
  sig do
205
- params(file: DependencyFile, type: T.untyped, name: String, requirement: String)
205
+ params(file: DependencyFile, type: String, name: String, requirement: String)
206
206
  .returns(T.nilable(Dependency))
207
207
  end
208
208
  def build_dependency(file:, type:, name:, requirement:)
@@ -303,14 +303,14 @@ module Dependabot
303
303
  def workspace_package_names
304
304
  @workspace_package_names ||= T.let(
305
305
  package_files.filter_map do |f|
306
- JSON.parse(T.must(f.content))["name"]
306
+ Dependabot::Package::NpmPackageJson.from_file(f).name
307
307
  end,
308
308
  T.nilable(T::Array[String])
309
309
  )
310
310
  end
311
311
 
312
312
  sig do
313
- params(requirement: String, lockfile_details: T.nilable(T::Hash[String, T.untyped]))
313
+ params(requirement: String, lockfile_details: T.nilable(Dependabot::Package::NpmLockfileDetails))
314
314
  .returns(T.nilable(T.any(String, Integer, Gem::Version)))
315
315
  end
316
316
  def version_for(requirement, lockfile_details)
@@ -332,10 +332,10 @@ module Dependabot
332
332
  end
333
333
  end
334
334
 
335
- sig { params(lockfile_details: T.nilable(T::Hash[String, T.untyped])).returns(T.nilable(String)) }
335
+ sig { params(lockfile_details: T.nilable(Dependabot::Package::NpmLockfileDetails)).returns(T.nilable(String)) }
336
336
  def git_revision_for(lockfile_details)
337
- version = T.cast(lockfile_details&.fetch("version", nil), T.nilable(String))
338
- resolved = T.cast(lockfile_details&.fetch("resolved", nil), T.nilable(String))
337
+ version = lockfile_details&.version
338
+ resolved = lockfile_details&.resolved
339
339
  [
340
340
  version&.split("#")&.last,
341
341
  resolved&.split("#")&.last,
@@ -375,11 +375,11 @@ module Dependabot
375
375
  end
376
376
 
377
377
  sig do
378
- params(lockfile_details: T.nilable(T::Hash[String, T.untyped]))
378
+ params(lockfile_details: T.nilable(Dependabot::Package::NpmLockfileDetails))
379
379
  .returns(T.nilable(T.any(String, Integer, Gem::Version)))
380
380
  end
381
381
  def lockfile_version_for(lockfile_details)
382
- semver_version_for(lockfile_details&.fetch("version", ""))
382
+ semver_version_for(lockfile_details&.version)
383
383
  end
384
384
 
385
385
  sig { params(version: T.nilable(String)).returns(T.nilable(T.any(String, Integer, Gem::Version))) }
@@ -398,17 +398,17 @@ module Dependabot
398
398
  end
399
399
 
400
400
  sig do
401
- params(name: String, requirement: String, lockfile_details: T.nilable(T::Hash[String, T.untyped]))
401
+ params(name: String, requirement: String, lockfile_details: T.nilable(Dependabot::Package::NpmLockfileDetails))
402
402
  .returns(T.nilable(T::Hash[Symbol, T.untyped]))
403
403
  end
404
404
  def source_for(name, requirement, lockfile_details)
405
405
  return git_source_for(requirement) if git_url?(requirement)
406
406
 
407
- resolved_url = lockfile_details&.fetch("resolved", nil)
407
+ resolved_url = lockfile_details&.resolved
408
408
 
409
- resolution = lockfile_details&.fetch("resolution", nil)
410
- package_match = resolution&.match(/__archiveUrl=(?<package_url>.+)/)
411
- resolved_url = CGI.unescape(package_match.named_captures.fetch("package_url", "")) if package_match
409
+ resolution = lockfile_details&.resolution
410
+ package_url = resolution&.[](/__archiveUrl=(.+)/, 1)
411
+ resolved_url = CGI.unescape(package_url) if package_url
412
412
 
413
413
  return unless resolved_url
414
414
  return unless resolved_url.start_with?("http")
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.395.0
4
+ version: 0.396.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.395.0
18
+ version: 0.396.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.395.0
25
+ version: 0.396.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -319,6 +319,7 @@ files:
319
319
  - lib/dependabot/bun/file_fetcher/path_dependency_builder.rb
320
320
  - lib/dependabot/bun/file_parser.rb
321
321
  - lib/dependabot/bun/file_parser/bun_lock.rb
322
+ - lib/dependabot/bun/file_parser/bun_lock/record.rb
322
323
  - lib/dependabot/bun/file_parser/lockfile_parser.rb
323
324
  - lib/dependabot/bun/file_updater.rb
324
325
  - lib/dependabot/bun/file_updater/bun_lockfile_updater.rb
@@ -354,7 +355,7 @@ licenses:
354
355
  - MIT
355
356
  metadata:
356
357
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
357
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.395.0
358
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.396.0
358
359
  rdoc_options: []
359
360
  require_paths:
360
361
  - lib