dependabot-uv 0.397.0 → 0.399.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: 2a21c1fc9597ab5ac799394151df83043ad6c6d1b62fb408c0657a9a4e67c6bc
4
- data.tar.gz: 42f770b5019c335bba7129bb87d8a875502b07d2ecab72af74169d59a2327b29
3
+ metadata.gz: 7bbb68bb0a2bc28ad6791a412231ba074cbfaa658252e6e1c3cceac992a05469
4
+ data.tar.gz: cea56c236a2c71bcf1c0fb6fc5a342631acb720925122241a2ee3405c68b32df
5
5
  SHA512:
6
- metadata.gz: aabd1f1f406790c8a4fa4b2fe0418d03993c49b54a86be43e749d352dcfccc5e791587461bfa4a4bfc63c142d789097f40a9b012769ec21705b8b3cb96728fe2
7
- data.tar.gz: abb80225b2a528c131fea6c1119a15bbba6c15e50c3df8990c0497ed2e3775b60d9bd984d4b8715cdc58cbac104eaf2f50673567b7c1cae28f1a1fd0f7f35dfd
6
+ metadata.gz: 333c7cba768d01f88b1e075c3f639e4ab799802c63ac7bd6fa8cd5fae6564b27145a5e1c2881f7d3a8b550564baecb85f636bf2e6de14ecf1bd948655c0e30e4
7
+ data.tar.gz: 0a1647d7b40b5297f3320c37ff7e61922f6bbe627fdf76fd2b55ad2215545600852c833cd7f31b2067a6f8d54b961609c4a2567216457e145696ec5504f00cb6
@@ -1,13 +1,13 @@
1
1
  pip==26.2.1
2
2
  pip-tools==7.6.1
3
- flake8==7.3.0
3
+ flake8==7.4.1
4
4
  hashin==1.0.5
5
5
  pipenv==2024.4.1
6
6
  plette==2.2.1
7
- poetry==2.4.3
7
+ poetry==2.5.1
8
8
  # TODO: Replace 3p package `tomli` with 3.11's new stdlib `tomllib` once we drop support for Python 3.10.
9
9
  tomli==2.4.1
10
- uv==0.12.15
10
+ uv==0.12.18
11
11
 
12
12
  # Some dependencies will only install if Cython is present
13
13
  Cython==3.3.0
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -7,7 +7,7 @@ require "dependabot/dependency"
7
7
  require "dependabot/dependency_graphers"
8
8
  require "dependabot/dependency_graphers/base"
9
9
  require "dependabot/uv/file_parser"
10
- require "toml-rb"
10
+ require "dependabot/uv/lockfile_document"
11
11
 
12
12
  module Dependabot
13
13
  module Uv
@@ -42,19 +42,17 @@ module Dependabot
42
42
  sig { returns(T::Hash[String, T::Array[String]]) }
43
43
  def package_relationships
44
44
  @package_relationships ||= T.let(
45
- package_relationships_from_lockfile(T.must(T.must(uv_lock).content)),
45
+ package_relationships_from_lockfile(T.must(uv_lock)),
46
46
  T.nilable(T::Hash[String, T::Array[String]])
47
47
  )
48
48
  end
49
49
 
50
50
  sig { void }
51
51
  def prepare_from_lockfile!
52
- lockfile = T.must(uv_lock)
53
- parsed = TomlRB.parse(lockfile.content)
54
- packages = T.cast(parsed.fetch("package", []), T::Array[T.untyped])
55
- manifest = parsed.fetch("manifest", {})
52
+ document = LockfileDocument.from_file(T.must(uv_lock))
53
+ packages = document.graph_packages
56
54
 
57
- root_names = root_package_names(packages, manifest)
55
+ root_names = root_package_names(packages, document.workspace_members.to_set)
58
56
  direct_runtime, direct_dev = direct_dependency_names(packages, root_names)
59
57
 
60
58
  @dependencies = packages.filter_map do |pkg|
@@ -73,14 +71,15 @@ module Dependabot
73
71
  @prepared = true
74
72
  end
75
73
 
76
- sig { params(lockfile_content: String).returns(T::Hash[String, T::Array[String]]) }
77
- def package_relationships_from_lockfile(lockfile_content)
78
- lockfile_packages(lockfile_content).each_with_object({}) do |package_data, rels|
79
- parent = lockfile_parent_name(package_data)
80
- next unless parent
74
+ sig { params(lockfile: Dependabot::DependencyFile).returns(T::Hash[String, T::Array[String]]) }
75
+ def package_relationships_from_lockfile(lockfile)
76
+ relationships = T.let({}, T::Hash[String, T::Array[String]])
77
+ LockfileDocument.from_file(lockfile).graph_packages.each_with_object(relationships) do |package, rels|
78
+ name = package.name
79
+ next unless name
81
80
 
82
- rels[parent] ||= []
83
- rels[parent].concat(lockfile_child_names(package_data))
81
+ parent = normalised_dependency_name(name)
82
+ (rels[parent] ||= []).concat(lockfile_child_names(package))
84
83
  end
85
84
  rescue StandardError => e
86
85
  errored_fetching_subdependencies!
@@ -89,48 +88,15 @@ module Dependabot
89
88
  {}
90
89
  end
91
90
 
92
- sig { params(lockfile_content: String).returns(T::Array[T.untyped]) }
93
- def lockfile_packages(lockfile_content)
94
- parsed = TomlRB.parse(lockfile_content)
95
- T.cast(parsed.fetch("package", []), T::Array[T.untyped])
96
- end
97
-
98
- sig { params(package_data: T.untyped).returns(T.nilable(String)) }
99
- def lockfile_parent_name(package_data)
100
- return unless package_data.is_a?(Hash)
101
-
102
- package_name = package_data["name"]
103
- return unless package_name.is_a?(String)
104
-
105
- normalised_dependency_name(package_name)
106
- end
107
-
108
91
  # Mirrors uv's `create_dependencies` (crates/uv-resolver/src/lock/export/cyclonedx_json.rs),
109
92
  # which chains a package's `dependencies`, `optional-dependencies`, and
110
93
  # `dev-dependencies` when building the SBOM dependency graph.
111
- sig { params(package_data: T.untyped).returns(T::Array[String]) }
112
- def lockfile_child_names(package_data)
113
- return [] unless package_data.is_a?(Hash)
114
-
115
- names = T.let([], T::Array[String])
116
- collect_dep_names(package_data["dependencies"], names)
117
- collect_dep_names_from_groups(package_data["optional-dependencies"], names)
118
- collect_dep_names_from_groups(package_data["dev-dependencies"], names)
94
+ sig { params(package: LockfileDocument::GraphPackage).returns(T::Array[String]) }
95
+ def lockfile_child_names(package)
96
+ names = package.dependencies + package.optional_dependencies + package.dev_dependencies
119
97
  names.map { |name| normalised_dependency_name(name) }.uniq
120
98
  end
121
99
 
122
- sig { params(dependency_data: T.untyped).returns(T.nilable(String)) }
123
- def lockfile_dependency_name(dependency_data)
124
- if dependency_data.is_a?(Hash)
125
- name = dependency_data["name"]
126
- return name if name.is_a?(String)
127
- end
128
-
129
- return dependency_data if dependency_data.is_a?(String)
130
-
131
- nil
132
- end
133
-
134
100
  # Identifies the workspace member packages whose `dependencies`,
135
101
  # `optional-dependencies`, and `dev-dependencies` arrays describe the
136
102
  # project's direct deps.
@@ -144,32 +110,17 @@ module Dependabot
144
110
  # Fallback for single-member workspaces (which omit `[manifest] members`):
145
111
  # match packages whose `source` is a local variant — `virtual`, `editable`,
146
112
  # or `directory` — per the `SourceWire` enum in the same file.
147
- sig { params(packages: T::Array[T.untyped], manifest: T.untyped).returns(T::Set[String]) }
148
- def root_package_names(packages, manifest)
149
- declared = declared_workspace_members(manifest)
113
+ sig do
114
+ params(packages: T::Array[LockfileDocument::GraphPackage], declared: T::Set[String]).returns(T::Set[String])
115
+ end
116
+ def root_package_names(packages, declared)
150
117
  return declared unless declared.empty?
151
118
 
152
119
  packages.each_with_object(Set.new) do |pkg, set|
153
- next unless pkg.is_a?(Hash)
154
-
155
- source = pkg["source"]
156
- next unless source.is_a?(Hash)
157
- next unless source.key?("virtual") || source.key?("editable") || source.key?("directory")
120
+ next unless pkg.local_source
158
121
 
159
- name = pkg["name"]
160
- set << name if name.is_a?(String)
161
- end
162
- end
163
-
164
- sig { params(manifest: T.untyped).returns(T::Set[String]) }
165
- def declared_workspace_members(manifest)
166
- return Set.new unless manifest.is_a?(Hash)
167
-
168
- members = manifest["members"]
169
- return Set.new unless members.is_a?(Array)
170
-
171
- members.each_with_object(Set.new) do |name, set|
172
- set << name if name.is_a?(String)
122
+ name = pkg.name
123
+ set << name if name
173
124
  end
174
125
  end
175
126
 
@@ -180,7 +131,7 @@ module Dependabot
180
131
  # semantics because the dependency graph reports what *could* be installed, not what was
181
132
  # selected for a particular sync.
182
133
  sig do
183
- params(packages: T::Array[T.untyped], root_names: T::Set[String])
134
+ params(packages: T::Array[LockfileDocument::GraphPackage], root_names: T::Set[String])
184
135
  .returns([T::Set[String], T::Set[String]])
185
136
  end
186
137
  def direct_dependency_names(packages, root_names)
@@ -188,47 +139,29 @@ module Dependabot
188
139
  dev = T.let(Set.new, T::Set[String])
189
140
 
190
141
  packages.each do |pkg|
191
- next unless pkg.is_a?(Hash) && root_names.include?(pkg["name"])
142
+ name = pkg.name
143
+ next unless name && root_names.include?(name)
192
144
 
193
- collect_dep_names(pkg["dependencies"], runtime)
194
- collect_dep_names_from_groups(pkg["optional-dependencies"], runtime)
195
- collect_dep_names_from_groups(pkg["dev-dependencies"], dev)
145
+ runtime.merge(pkg.dependencies)
146
+ runtime.merge(pkg.optional_dependencies)
147
+ dev.merge(pkg.dev_dependencies)
196
148
  end
197
149
 
198
150
  [runtime, dev]
199
151
  end
200
152
 
201
- sig { params(entries: T.untyped, collection: T.any(T::Set[String], T::Array[String])).void }
202
- def collect_dep_names(entries, collection)
203
- return unless entries.is_a?(Array)
204
-
205
- entries.each do |entry|
206
- name = lockfile_dependency_name(entry)
207
- collection << name if name.is_a?(String)
208
- end
209
- end
210
-
211
- sig { params(groups: T.untyped, collection: T.any(T::Set[String], T::Array[String])).void }
212
- def collect_dep_names_from_groups(groups, collection)
213
- return unless groups.is_a?(Hash)
214
-
215
- groups.each_value { |entries| collect_dep_names(entries, collection) }
216
- end
217
-
218
153
  sig do
219
154
  params(
220
- pkg: T.untyped,
155
+ pkg: LockfileDocument::GraphPackage,
221
156
  root_names: T::Set[String],
222
157
  direct_runtime: T::Set[String],
223
158
  direct_dev: T::Set[String]
224
159
  ).returns(T.nilable(Dependabot::Dependency))
225
160
  end
226
161
  def build_dependency(pkg, root_names, direct_runtime, direct_dev)
227
- return unless pkg.is_a?(Hash)
228
-
229
- name = pkg["name"]
230
- version = pkg["version"]
231
- return unless name.is_a?(String) && version.is_a?(String)
162
+ name = pkg.name
163
+ version = pkg.version
164
+ return unless name && version
232
165
 
233
166
  # Root project packages get requirements: [] (indirect, runtime) to
234
167
  # match the prior FileParser-derived behaviour where uv.lock packages
@@ -16,8 +16,8 @@ require "dependabot/uv/name_normaliser"
16
16
  require "dependabot/uv/requirements_file_matcher"
17
17
  require "dependabot/uv/language_version_manager"
18
18
  require "dependabot/uv/package_manager"
19
+ require "dependabot/uv/lockfile_document"
19
20
  require "dependabot/python/file_parser/pep_dependency"
20
- require "toml-rb"
21
21
 
22
22
  module Dependabot
23
23
  module Uv
@@ -199,15 +199,10 @@ module Dependabot
199
199
  dependency_set = DependencySet.new
200
200
 
201
201
  uv_lock_files.each do |file|
202
- lockfile_content = TomlRB.parse(file.content)
203
- packages = lockfile_content.fetch("package", [])
204
-
205
- packages.each do |package_data|
206
- next unless package_data.is_a?(Hash) && package_data["name"] && package_data["version"]
207
-
202
+ LockfileDocument.from_file(file).each_dependency do |package|
208
203
  dependency_set << Dependency.new(
209
- name: normalised_name(package_data["name"]),
210
- version: package_data["version"],
204
+ name: normalised_name(package.name),
205
+ version: package.version,
211
206
  requirements: [], # Lock files don't contain requirements
212
207
  package_manager: "uv"
213
208
  )
@@ -322,9 +322,19 @@ module Dependabot
322
322
  content = replace_header_with_original(updated_content, T.must(file.content))
323
323
  content = remove_new_warnings(content, T.must(file.content))
324
324
  content = update_hashes_if_required(content, T.must(file.content))
325
+ content = scrub_index_url_credentials(content)
325
326
  replace_absolute_file_paths(content, T.must(file.content))
326
327
  end
327
328
 
329
+ # Index URLs are passed to `uv pip compile` with credentials embedded so
330
+ # the private index can be fetched, and uv emits those URLs verbatim
331
+ # into the compiled file when `--emit-index-url` is active. Strip any
332
+ # userinfo so tokens are never written into the regenerated file.
333
+ sig { params(content: String).returns(String) }
334
+ def scrub_index_url_credentials(content)
335
+ content.gsub(%r{^(--(?:extra-)?index-url[= ]https?://)[^/\s]+@}, "\\1")
336
+ end
337
+
328
338
  sig { params(updated_content: String, original_content: String).returns(String) }
329
339
  def replace_header_with_original(updated_content, original_content)
330
340
  original_header_lines =
@@ -0,0 +1,167 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "toml-rb"
6
+
7
+ require "dependabot/dependency_file"
8
+
9
+ module Dependabot
10
+ module Uv
11
+ class LockfileDocument
12
+ extend T::Sig
13
+
14
+ LOCAL_SOURCE_KEYS = %w(virtual editable directory).freeze
15
+
16
+ class GraphPackage < T::ImmutableStruct
17
+ const :name, T.nilable(String)
18
+ const :version, T.nilable(String)
19
+ const :local_source, T::Boolean
20
+ const :dependencies, T::Array[String]
21
+ const :optional_dependencies, T::Array[String]
22
+ const :dev_dependencies, T::Array[String]
23
+ end
24
+
25
+ class LockedPackage < T::ImmutableStruct
26
+ const :name, String
27
+ const :version, String
28
+ end
29
+
30
+ sig { params(file: Dependabot::DependencyFile).returns(LockfileDocument) }
31
+ def self.from_file(file)
32
+ new(data: T.cast(TomlRB.parse(T.must(file.content)), Object), context: file.path)
33
+ end
34
+
35
+ sig { params(data: Object, context: String).void }
36
+ def initialize(data:, context:)
37
+ @context = context
38
+ @data = T.let(object_hash(data), T::Hash[String, Object])
39
+ end
40
+
41
+ sig { returns(T::Array[String]) }
42
+ def workspace_members
43
+ manifest = @data["manifest"]
44
+ return [] unless manifest.is_a?(Hash)
45
+
46
+ members = T.cast(manifest["members"], Object)
47
+ return [] unless members.is_a?(Array)
48
+
49
+ members.filter_map { |member| string_or_nil(T.cast(member, Object)) }
50
+ end
51
+
52
+ sig { returns(T::Array[GraphPackage]) }
53
+ def graph_packages
54
+ package_entries.filter_map do |entry|
55
+ next unless entry.is_a?(Hash)
56
+
57
+ package = object_hash(entry)
58
+ source = package["source"]
59
+ GraphPackage.new(
60
+ name: string_or_nil(package["name"]),
61
+ version: string_or_nil(package["version"]),
62
+ local_source: source.is_a?(Hash) && LOCAL_SOURCE_KEYS.any? { |key| source.key?(key) },
63
+ dependencies: dependency_names(package["dependencies"]),
64
+ optional_dependencies: grouped_dependency_names(package["optional-dependencies"]),
65
+ dev_dependencies: grouped_dependency_names(package["dev-dependencies"])
66
+ )
67
+ end
68
+ end
69
+
70
+ sig { params(block: T.proc.params(package: LockedPackage).void).void }
71
+ def each_dependency(&block)
72
+ # The file parser historically skips the pairs yielded by an object-shaped package section.
73
+ return if @data["package"].is_a?(Hash)
74
+
75
+ package_entries.each_with_index do |entry, index|
76
+ next unless entry.is_a?(Hash)
77
+
78
+ package = object_hash(entry)
79
+ name = package["name"]
80
+ version = package["version"]
81
+ next unless name && version
82
+
83
+ yield(LockedPackage.new(
84
+ name: string(name, "package[#{index}].name"),
85
+ version: string(version, "package[#{index}].version")
86
+ ))
87
+ end
88
+ end
89
+
90
+ sig { returns(T::Array[LockedPackage]) }
91
+ def resolution_packages
92
+ return [] if @data["package"].nil?
93
+
94
+ package_entries.each_with_index.filter_map do |entry, index|
95
+ # String#[] cannot produce a valid version from the "version" lookup.
96
+ next if entry.is_a?(String)
97
+
98
+ package = object_hash(entry)
99
+ name = optional_string(package["name"], "package[#{index}].name")
100
+ version = optional_string(package["version"], "package[#{index}].version")
101
+ next unless name && version
102
+
103
+ LockedPackage.new(name: name, version: version)
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ sig { returns(T::Array[Object]) }
110
+ def package_entries
111
+ packages = @data.fetch("package", [])
112
+ raise TypeError, "#{@context} package must be an array" unless packages.is_a?(Array)
113
+
114
+ packages.map { |entry| T.cast(entry, Object) }
115
+ end
116
+
117
+ sig { params(value: Object).returns(T::Hash[String, Object]) }
118
+ def object_hash(value)
119
+ raise TypeError, "#{@context} must be an object" unless value.is_a?(Hash)
120
+
121
+ value.to_h do |raw_key, raw_value|
122
+ key = T.cast(raw_key, Object)
123
+ raise TypeError, "#{@context} keys must be strings" unless key.is_a?(String)
124
+
125
+ [key, T.cast(raw_value, Object)]
126
+ end
127
+ end
128
+
129
+ sig { params(value: Object).returns(T.nilable(String)) }
130
+ def string_or_nil(value)
131
+ value if value.is_a?(String)
132
+ end
133
+
134
+ sig { params(value: Object, field: String).returns(T.nilable(String)) }
135
+ def optional_string(value, field)
136
+ return if value.nil?
137
+
138
+ string(value, field)
139
+ end
140
+
141
+ sig { params(value: Object, field: String).returns(String) }
142
+ def string(value, field)
143
+ return value if value.is_a?(String)
144
+
145
+ raise TypeError, "#{@context} #{field} must be a string"
146
+ end
147
+
148
+ sig { params(entries: Object).returns(T::Array[String]) }
149
+ def dependency_names(entries)
150
+ return [] unless entries.is_a?(Array)
151
+
152
+ entries.filter_map do |raw_entry|
153
+ entry = T.cast(raw_entry, Object)
154
+ name = entry.is_a?(Hash) ? T.cast(entry["name"], Object) : entry
155
+ string_or_nil(name)
156
+ end
157
+ end
158
+
159
+ sig { params(groups: Object).returns(T::Array[String]) }
160
+ def grouped_dependency_names(groups)
161
+ return [] unless groups.is_a?(Hash)
162
+
163
+ groups.values.flat_map { |entries| dependency_names(T.cast(entries, Object)) }
164
+ end
165
+ end
166
+ end
167
+ end
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
5
- require "toml-rb"
5
+ require "dependabot/uv/lockfile_document"
6
6
 
7
7
  require "dependabot/errors"
8
8
  require "dependabot/package/release_cooldown_options"
@@ -278,20 +278,13 @@ module Dependabot
278
278
  .returns(T::Array[Dependabot::Uv::Version])
279
279
  end
280
280
  def locked_versions(lockfile)
281
- parsed = T.cast(TomlRB.parse(T.must(lockfile.content)), T::Hash[String, Object])
282
- packages = T.cast(parsed["package"], T.nilable(T::Array[T::Hash[String, Object]])) || []
283
281
  dependency_name = NameNormaliser.normalise(dependency.name)
284
- versions = T.let([], T::Array[Dependabot::Uv::Version])
285
- packages.each do |package|
286
- package_name = T.cast(package["name"], T.nilable(String))
287
- package_version = T.cast(package["version"], T.nilable(String))
288
- next unless package_name && NameNormaliser.normalise(package_name) == dependency_name
289
- next unless Uv::Version.correct?(package_version)
290
-
291
- versions << Uv::Version.new(package_version)
292
- end
282
+ LockfileDocument.from_file(lockfile).resolution_packages.filter_map do |package|
283
+ next unless NameNormaliser.normalise(package.name) == dependency_name
284
+ next unless Uv::Version.correct?(package.version)
293
285
 
294
- versions
286
+ Uv::Version.new(package.version)
287
+ end
295
288
  end
296
289
 
297
290
  sig { returns(T.nilable(Dependabot::DependencyFile)) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-uv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.397.0
4
+ version: 0.399.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.397.0
18
+ version: 0.399.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.397.0
25
+ version: 0.399.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: dependabot-python
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 0.397.0
32
+ version: 0.399.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 0.397.0
39
+ version: 0.399.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: debug
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -280,6 +280,7 @@ files:
280
280
  - lib/dependabot/uv/file_updater/version_config_parser.rb
281
281
  - lib/dependabot/uv/language.rb
282
282
  - lib/dependabot/uv/language_version_manager.rb
283
+ - lib/dependabot/uv/lockfile_document.rb
283
284
  - lib/dependabot/uv/metadata_finder.rb
284
285
  - lib/dependabot/uv/name_normaliser.rb
285
286
  - lib/dependabot/uv/native_helpers.rb
@@ -301,7 +302,7 @@ licenses:
301
302
  - MIT
302
303
  metadata:
303
304
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
304
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.397.0
305
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.399.0
305
306
  rdoc_options: []
306
307
  require_paths:
307
308
  - lib