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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7384e20ca998f4af49001eb34de080505f3abff092e4faa921424ad6b65299e
4
- data.tar.gz: 3c7f12c0289d6a8e811d0d51c6d24af81a319e59ba722fe773d623433ddb6ab9
3
+ metadata.gz: '009515ba93dc3e414380b797fe7b3aa17a388d024e5406e74291c901600f1a0c'
4
+ data.tar.gz: b511053c1259a770b1f7a2d649cf357bec4abcab55ad3aacb34b96ec40482893
5
5
  SHA512:
6
- metadata.gz: f8da50ec13cdb3fead27bd2e0c20e019650a8f38a9817be6fb17f8ec5ceef8b7ffb19b7e6805a7f40787de1c4d029d7692e35149a1fc10901d2f48d043c4a02b
7
- data.tar.gz: f6f258473e1bff627f213be788e692c6ddfaced18172fdc6910307f504c12554ed88b5447163932e4d909b9be856aaa5921669e1f572de0e81c3ff6d865781a5
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
- %r{Could not transfer artifact (?<artifact>[^ ]+) from/to (?<repository_name>[^ ]+) \((?<repository_url>[^ ]+)\): status code: (?<status_code>[0-9]+)} # rubocop:disable Layout/LineLength
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
- SharedHelpers.run_shell_command(cmd, env: env, cwd: run_cwd)
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 do |r|
82
- released?(r.version)
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
- potential_source_urls = [
71
- pom.at_css("project > url")&.content,
72
- pom.at_css("project > scm > url")&.content,
73
- pom.at_css("project > issueManagement > url")&.content
74
- ].compact
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.392.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.392.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.392.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: '0.22'
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: '0.22'
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.392.0
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