dependabot-maven 0.392.0 → 0.394.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 +4 -4
- data/lib/dependabot/maven/native_helpers.rb +79 -4
- data/lib/dependabot/maven/package/package_details_fetcher.rb +98 -0
- data/lib/dependabot/maven/shared/base_version_finder.rb +50 -3
- data/lib/dependabot/maven/shared/shared_metadata_finder.rb +18 -5
- data/lib/dependabot/maven/update_checker/version_finder.rb +14 -0
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '009515ba93dc3e414380b797fe7b3aa17a388d024e5406e74291c901600f1a0c'
|
|
4
|
+
data.tar.gz: b511053c1259a770b1f7a2d649cf357bec4abcab55ad3aacb34b96ec40482893
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 36c134e6d66d0d445df02dca60b7ae8afe60dd585e7475e8c4668656b22bc6ca3b8917251110772709075112142cc7ffd109564573f5231fcb130adba9aaa2cf
|
|
7
|
+
data.tar.gz: dd730e81dec85d5abb73e201c832087ac4e0f75a5a09284046b87d203a208415f0588e3f9b04c58a588ecf1a55e7c70673551b20a43fd59a3b27bf1a48ea5d7f
|
|
@@ -6,6 +6,7 @@ require "open3"
|
|
|
6
6
|
require "uri"
|
|
7
7
|
require "sorbet-runtime"
|
|
8
8
|
require "nokogiri"
|
|
9
|
+
require "dependabot/errors"
|
|
9
10
|
require "dependabot/shared_helpers"
|
|
10
11
|
|
|
11
12
|
module Dependabot
|
|
@@ -13,6 +14,25 @@ module Dependabot
|
|
|
13
14
|
module NativeHelpers
|
|
14
15
|
extend T::Sig
|
|
15
16
|
|
|
17
|
+
# Matches Maven's "Could not transfer artifact" failures, capturing the
|
|
18
|
+
# repository URL and HTTP status so we can classify auth vs. other errors.
|
|
19
|
+
TRANSFER_FAILURE_REGEX =
|
|
20
|
+
%r{Could not transfer artifact (?<artifact>[^ ]+) from/to (?<repository_name>[^ ]+) \((?<repository_url>[^ ]+)\): status code: (?<status_code>[0-9]+)} # rubocop:disable Layout/LineLength
|
|
21
|
+
|
|
22
|
+
# Matches Maven's "Plugin ... could not be resolved" failures, used to
|
|
23
|
+
# detect when the wrapper plugin itself is unavailable behind the proxy.
|
|
24
|
+
WRAPPER_PLUGIN_UNRESOLVED_REGEX =
|
|
25
|
+
/Plugin org\.apache\.maven\.plugins:maven-wrapper-plugin[^ ]* .*could not be resolved/
|
|
26
|
+
|
|
27
|
+
# Upper bound on the length of the Maven error summary included in raised errors,
|
|
28
|
+
# to avoid oversized error payloads while retaining the relevant failure detail.
|
|
29
|
+
MAX_ERROR_SUMMARY_LENGTH = 2_000
|
|
30
|
+
|
|
31
|
+
# Matches ANSI/VT100 control sequences. Maven can be configured to emit colored
|
|
32
|
+
# output (e.g. `-Dstyle.color=always`), wrapping markers like `[ERROR]` in escape
|
|
33
|
+
# codes; we strip these so classification and the surfaced summary see plain text.
|
|
34
|
+
ANSI_ESCAPE_REGEX = %r{\e\[[0-9;?]*[ -/]*[@-~]}
|
|
35
|
+
|
|
16
36
|
pom_path = File.join(__dir__, "pom.xml")
|
|
17
37
|
|
|
18
38
|
version = File.open(pom_path) do |f|
|
|
@@ -43,9 +63,8 @@ module Dependabot
|
|
|
43
63
|
|
|
44
64
|
sig { params(output: String).void }
|
|
45
65
|
def self.handle_tool_error(output)
|
|
46
|
-
if (match = output.match(
|
|
47
|
-
|
|
48
|
-
)) && (match[:status_code] == "403" || match[:status_code] == "401")
|
|
66
|
+
if (match = output.match(TRANSFER_FAILURE_REGEX)) &&
|
|
67
|
+
(match[:status_code] == "403" || match[:status_code] == "401")
|
|
49
68
|
raise Dependabot::PrivateSourceAuthenticationFailure, match[:repository_url]
|
|
50
69
|
end
|
|
51
70
|
|
|
@@ -91,7 +110,63 @@ module Dependabot
|
|
|
91
110
|
# to parse. An argument vector is executed without an intermediate shell.
|
|
92
111
|
cmd = ["mvn"] + standard_args
|
|
93
112
|
run_cwd = cwd && cwd != "." ? cwd : nil
|
|
94
|
-
|
|
113
|
+
|
|
114
|
+
output = SharedHelpers.run_shell_command(cmd, env: env, cwd: run_cwd)
|
|
115
|
+
Dependabot.logger.info("mvn wrapper output: STDOUT:#{output}")
|
|
116
|
+
output
|
|
117
|
+
rescue SharedHelpers::HelperSubprocessFailed => e
|
|
118
|
+
# `run_shell_command` raises HelperSubprocessFailed on a non-zero exit, and the
|
|
119
|
+
# updater sanitizes that into an opaque `SubprocessFailed` that only reports the
|
|
120
|
+
# command and hides the real Maven output. Log the full output and re-raise a
|
|
121
|
+
# classified Dependabot error so operators get an actionable message, mirroring
|
|
122
|
+
# the `run_mvn_dependency_tree_plugin` path.
|
|
123
|
+
Dependabot.logger.warn("mvn wrapper command failed:\n#{e.message}")
|
|
124
|
+
handle_wrapper_error(e)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Classifies a failed Maven Wrapper invocation into an actionable Dependabot
|
|
128
|
+
# error. Known auth and plugin-resolution failures are mapped to their specific
|
|
129
|
+
# error types, and genuine Maven diagnostics are surfaced via MisconfiguredTooling.
|
|
130
|
+
# `run_shell_command` also reports inactivity timeouts and a missing `mvn`
|
|
131
|
+
# executable as HelperSubprocessFailed; those carry no Maven `[ERROR]` markers, so
|
|
132
|
+
# we re-raise the original error and let it follow normal unknown-error routing
|
|
133
|
+
# instead of mislabelling infrastructure/runtime failures as tooling misconfigurations.
|
|
134
|
+
sig { params(error: SharedHelpers::HelperSubprocessFailed).returns(T.noreturn) }
|
|
135
|
+
def self.handle_wrapper_error(error)
|
|
136
|
+
# Strip ANSI color codes up front so classification and the surfaced summary see
|
|
137
|
+
# plain text even when Maven is configured to emit colored output.
|
|
138
|
+
output = error.message.gsub(ANSI_ESCAPE_REGEX, "")
|
|
139
|
+
|
|
140
|
+
if (match = output.match(TRANSFER_FAILURE_REGEX)) &&
|
|
141
|
+
(match[:status_code] == "403" || match[:status_code] == "401")
|
|
142
|
+
raise Dependabot::PrivateSourceAuthenticationFailure, match[:repository_url]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if output.match?(WRAPPER_PLUGIN_UNRESOLVED_REGEX)
|
|
146
|
+
raise Dependabot::DependencyFileNotResolvable, "Could not resolve the Maven Wrapper plugin."
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Only reclassify when Maven emitted its own `[ERROR]` diagnostics. Otherwise the
|
|
150
|
+
# failure is not a Maven misconfiguration (e.g. timeout or missing executable), so
|
|
151
|
+
# re-raise the original error; its full output is already in the job log.
|
|
152
|
+
summary = mvn_error_summary(output)
|
|
153
|
+
raise error unless summary
|
|
154
|
+
|
|
155
|
+
raise Dependabot::MisconfiguredTooling.new("Maven Wrapper", summary)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Extracts Maven's own `[ERROR]` lines from the combined tool output so that raised
|
|
159
|
+
# errors surface the relevant failure reason without leaking unrelated build noise or
|
|
160
|
+
# arbitrary subprocess output (which may contain sensitive file contents or paths) into
|
|
161
|
+
# the reported error. Returns nil when Maven produced no `[ERROR]` diagnostics, and caps
|
|
162
|
+
# the length to keep error payloads reasonable.
|
|
163
|
+
sig { params(output: String).returns(T.nilable(String)) }
|
|
164
|
+
def self.mvn_error_summary(output)
|
|
165
|
+
error_lines = output.lines.map(&:chomp).select { |line| line.include?("[ERROR]") }
|
|
166
|
+
return nil if error_lines.empty?
|
|
167
|
+
|
|
168
|
+
summary = error_lines.join("\n")
|
|
169
|
+
summary.length > MAX_ERROR_SUMMARY_LENGTH ? "#{summary[0, MAX_ERROR_SUMMARY_LENGTH]}..." : summary
|
|
95
170
|
end
|
|
96
171
|
end
|
|
97
172
|
end
|
|
@@ -35,6 +35,8 @@ module Dependabot
|
|
|
35
35
|
@repository_finder = T.let(nil, T.nilable(Maven::FileParser::RepositoriesFinder))
|
|
36
36
|
@repositories_cache = T.let(nil, T.nilable(T::Array[RepositoryDetails]))
|
|
37
37
|
@package_details = T.let(nil, T.nilable(Dependabot::Package::PackageDetails))
|
|
38
|
+
@release_date_cache = T.let({}, T::Hash[String, T.nilable(Time)])
|
|
39
|
+
@fallback_logged = T.let(false, T::Boolean)
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
sig { override.returns(Dependabot::Dependency) }
|
|
@@ -71,6 +73,25 @@ module Dependabot
|
|
|
71
73
|
fetch.releases
|
|
72
74
|
end
|
|
73
75
|
|
|
76
|
+
sig { params(release: Dependabot::Package::PackageRelease).returns(Dependabot::Package::PackageRelease) }
|
|
77
|
+
def fetch_release_metadata(release:)
|
|
78
|
+
return release if release.released_at
|
|
79
|
+
|
|
80
|
+
url = release.url
|
|
81
|
+
return release unless url
|
|
82
|
+
|
|
83
|
+
cache_key = "#{url}\0#{release.version}"
|
|
84
|
+
released_at = if @release_date_cache.key?(cache_key)
|
|
85
|
+
@release_date_cache[cache_key]
|
|
86
|
+
else
|
|
87
|
+
@release_date_cache[cache_key] =
|
|
88
|
+
fetch_pom_last_modified(repository_url: url, version: release.version)
|
|
89
|
+
end
|
|
90
|
+
return release unless released_at
|
|
91
|
+
|
|
92
|
+
build_release_with_date(release, released_at)
|
|
93
|
+
end
|
|
94
|
+
|
|
74
95
|
# Assembles the list of Maven repositories to search: credential repos + POM repos.
|
|
75
96
|
sig { override.returns(T::Array[RepositoryDetails]) }
|
|
76
97
|
def repositories
|
|
@@ -93,6 +114,83 @@ module Dependabot
|
|
|
93
114
|
|
|
94
115
|
private
|
|
95
116
|
|
|
117
|
+
sig do
|
|
118
|
+
params(
|
|
119
|
+
repository_url: String,
|
|
120
|
+
version: Dependabot::Version
|
|
121
|
+
).returns(T.nilable(Time))
|
|
122
|
+
end
|
|
123
|
+
def fetch_pom_last_modified(repository_url:, version:)
|
|
124
|
+
# release.url is the exact repository URL recorded when the version was
|
|
125
|
+
# discovered, so an exact match is sufficient (no trailing-slash cleanup).
|
|
126
|
+
repository_details = repositories.find do |details|
|
|
127
|
+
self.repository_url(details) == repository_url
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
unless repository_details
|
|
131
|
+
Dependabot.logger.debug(
|
|
132
|
+
"No configured repository matches #{repository_url} for #{dependency.name}; " \
|
|
133
|
+
"skipping POM Last-Modified fallback"
|
|
134
|
+
)
|
|
135
|
+
return
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
response = Dependabot::RegistryClient.head(
|
|
139
|
+
url: dependency_pom_url(repository_url, version),
|
|
140
|
+
headers: repository_auth_headers(repository_details)
|
|
141
|
+
)
|
|
142
|
+
return unless response.status < 400
|
|
143
|
+
|
|
144
|
+
last_modified = response.headers["Last-Modified"] || response.headers["last-modified"]
|
|
145
|
+
return unless last_modified
|
|
146
|
+
|
|
147
|
+
released_at = Time.httpdate(last_modified)
|
|
148
|
+
log_fallback_hit(repository_url: repository_url)
|
|
149
|
+
Dependabot.logger.debug(
|
|
150
|
+
"Using POM Last-Modified fallback for #{dependency.name} version #{version} from " \
|
|
151
|
+
"#{repository_url}: #{released_at}"
|
|
152
|
+
)
|
|
153
|
+
released_at
|
|
154
|
+
rescue StandardError => e
|
|
155
|
+
Dependabot.logger.debug(
|
|
156
|
+
"Failed POM Last-Modified fallback for #{dependency.name} version #{version} from " \
|
|
157
|
+
"#{repository_url}: #{e.message}"
|
|
158
|
+
)
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
sig { params(repository_url: String).void }
|
|
163
|
+
def log_fallback_hit(repository_url:)
|
|
164
|
+
return if @fallback_logged
|
|
165
|
+
|
|
166
|
+
Dependabot.logger.info(
|
|
167
|
+
"Using POM Last-Modified fallback release dates for #{dependency.name} from #{repository_url}"
|
|
168
|
+
)
|
|
169
|
+
@fallback_logged = true
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
sig do
|
|
173
|
+
params(
|
|
174
|
+
release: Dependabot::Package::PackageRelease,
|
|
175
|
+
released_at: Time
|
|
176
|
+
).returns(Dependabot::Package::PackageRelease)
|
|
177
|
+
end
|
|
178
|
+
def build_release_with_date(release, released_at)
|
|
179
|
+
Dependabot::Package::PackageRelease.new(
|
|
180
|
+
version: release.version,
|
|
181
|
+
released_at: released_at,
|
|
182
|
+
latest: release.latest,
|
|
183
|
+
yanked: release.yanked,
|
|
184
|
+
yanked_reason: release.yanked_reason,
|
|
185
|
+
downloads: release.downloads,
|
|
186
|
+
url: release.url,
|
|
187
|
+
package_type: release.package_type,
|
|
188
|
+
language: release.language,
|
|
189
|
+
tag: release.tag,
|
|
190
|
+
details: release.details
|
|
191
|
+
)
|
|
192
|
+
end
|
|
193
|
+
|
|
96
194
|
sig { returns(Maven::FileParser::RepositoriesFinder) }
|
|
97
195
|
def repository_finder
|
|
98
196
|
return @repository_finder if @repository_finder
|
|
@@ -75,12 +75,31 @@ module Dependabot
|
|
|
75
75
|
possible_releases = filter_date_based_versions(possible_releases)
|
|
76
76
|
possible_releases = filter_version_types(possible_releases)
|
|
77
77
|
possible_releases = filter_ignored_versions(possible_releases)
|
|
78
|
-
possible_releases = filter_by_cooldown(possible_releases)
|
|
79
78
|
possible_releases_reverse = possible_releases.reverse
|
|
80
79
|
|
|
81
|
-
possible_releases_reverse.find
|
|
82
|
-
|
|
80
|
+
return possible_releases_reverse.find { |r| released?(r.version) } unless cooldown_options
|
|
81
|
+
|
|
82
|
+
cooldown_filtered_releases = 0
|
|
83
|
+
latest_release = possible_releases_reverse.find do |release|
|
|
84
|
+
if in_cooldown_period?(release)
|
|
85
|
+
Dependabot.logger.info("Filtered out (cooldown) : #{release}")
|
|
86
|
+
cooldown_filtered_releases += 1
|
|
87
|
+
next false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
released?(release.version)
|
|
83
91
|
end
|
|
92
|
+
|
|
93
|
+
if cooldown_filtered_releases.positive?
|
|
94
|
+
Dependabot.logger.info(
|
|
95
|
+
"Filtered out #{cooldown_filtered_releases} version(s) due to cooldown"
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
latest_release ||= current_version_fallback_release(
|
|
100
|
+
possible_releases_reverse, cooldown_filtered_releases
|
|
101
|
+
)
|
|
102
|
+
latest_release
|
|
84
103
|
end
|
|
85
104
|
|
|
86
105
|
sig do
|
|
@@ -104,6 +123,34 @@ module Dependabot
|
|
|
104
123
|
|
|
105
124
|
private
|
|
106
125
|
|
|
126
|
+
# When every candidate was filtered out by cooldown, stay on the current
|
|
127
|
+
# version (if released) instead of reporting no latest version at all.
|
|
128
|
+
sig do
|
|
129
|
+
params(
|
|
130
|
+
possible_releases: T::Array[Dependabot::Package::PackageRelease],
|
|
131
|
+
cooldown_filtered_releases: Integer
|
|
132
|
+
).returns(T.nilable(Dependabot::Package::PackageRelease))
|
|
133
|
+
end
|
|
134
|
+
def current_version_fallback_release(possible_releases, cooldown_filtered_releases)
|
|
135
|
+
return if possible_releases.empty?
|
|
136
|
+
return unless cooldown_filtered_releases == possible_releases.length
|
|
137
|
+
|
|
138
|
+
current_version_str = dependency.version
|
|
139
|
+
return unless current_version_str
|
|
140
|
+
|
|
141
|
+
Dependabot.logger.info(
|
|
142
|
+
"All versions filtered by cooldown for #{dependency.name}, " \
|
|
143
|
+
"falling back to current version #{current_version_str}"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
current_release = Dependabot::Package::PackageRelease.new(
|
|
147
|
+
version: version_class.new(current_version_str),
|
|
148
|
+
released_at: nil,
|
|
149
|
+
tag: nil
|
|
150
|
+
)
|
|
151
|
+
current_release if released?(current_release.version)
|
|
152
|
+
end
|
|
153
|
+
|
|
107
154
|
sig { abstract.params(version: Dependabot::Version).returns(T::Boolean) }
|
|
108
155
|
def released?(version); end
|
|
109
156
|
end
|
|
@@ -67,11 +67,24 @@ module Dependabot
|
|
|
67
67
|
|
|
68
68
|
sig { params(pom: Nokogiri::XML::Document).returns(T.nilable(Dependabot::Source)) }
|
|
69
69
|
def look_up_source_in_pom(pom)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
source_selectors =
|
|
71
|
+
if Dependabot::Experiments.enabled?(:prioritize_scm_source)
|
|
72
|
+
# Prefer scm url, followed by project url, and finally issue management url (#2084)
|
|
73
|
+
[
|
|
74
|
+
"project > scm > url",
|
|
75
|
+
"project > url",
|
|
76
|
+
"project > issueManagement > url"
|
|
77
|
+
]
|
|
78
|
+
else
|
|
79
|
+
[
|
|
80
|
+
"project > url",
|
|
81
|
+
"project > scm > url",
|
|
82
|
+
"project > issueManagement > url"
|
|
83
|
+
]
|
|
84
|
+
end
|
|
85
|
+
potential_source_urls = source_selectors.filter_map do |selector|
|
|
86
|
+
pom.at_css(selector)&.content
|
|
87
|
+
end
|
|
75
88
|
|
|
76
89
|
source_url = potential_source_urls.find { |url| Source.from_url(url) }
|
|
77
90
|
source_url ||= source_from_anywhere_in_pom(pom)
|
|
@@ -58,6 +58,20 @@ module Dependabot
|
|
|
58
58
|
@package_details ||= package_details_fetcher.fetch
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
protected
|
|
62
|
+
|
|
63
|
+
sig { override.params(release: Dependabot::Package::PackageRelease).returns(T::Boolean) }
|
|
64
|
+
def in_cooldown_period?(release)
|
|
65
|
+
return super if release.released_at
|
|
66
|
+
return false if Dependabot::UpdateCheckers::CooldownCalculation.skip_cooldown?(
|
|
67
|
+
cooldown_options,
|
|
68
|
+
dependency.name,
|
|
69
|
+
cooldown_enabled: cooldown_enabled?
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
super(package_details_fetcher.fetch_release_metadata(release: release))
|
|
73
|
+
end
|
|
74
|
+
|
|
61
75
|
private
|
|
62
76
|
|
|
63
77
|
sig { override.params(version: Dependabot::Version).returns(T::Boolean) }
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dependabot-maven
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.394.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.
|
|
18
|
+
version: 0.394.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.
|
|
25
|
+
version: 0.394.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: rexml
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -183,14 +183,14 @@ dependencies:
|
|
|
183
183
|
requirements:
|
|
184
184
|
- - "~>"
|
|
185
185
|
- !ruby/object:Gem::Version
|
|
186
|
-
version: '
|
|
186
|
+
version: '1.1'
|
|
187
187
|
type: :development
|
|
188
188
|
prerelease: false
|
|
189
189
|
version_requirements: !ruby/object:Gem::Requirement
|
|
190
190
|
requirements:
|
|
191
191
|
- - "~>"
|
|
192
192
|
- !ruby/object:Gem::Version
|
|
193
|
-
version: '
|
|
193
|
+
version: '1.1'
|
|
194
194
|
- !ruby/object:Gem::Dependency
|
|
195
195
|
name: turbo_tests
|
|
196
196
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -296,7 +296,7 @@ licenses:
|
|
|
296
296
|
- MIT
|
|
297
297
|
metadata:
|
|
298
298
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
299
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
299
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.394.0
|
|
300
300
|
rdoc_options: []
|
|
301
301
|
require_paths:
|
|
302
302
|
- lib
|