dependabot-maven 0.393.0 → 0.395.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: fff231d12e4c6ebdbdbf645bcc82c18a7b895ecc870ddb6aba549e4296f0ead6
4
- data.tar.gz: a0c24d6534e7bd9c48e7256bd6f03176713f75173f523ac75a8f389b022cc7ff
3
+ metadata.gz: e771ade81b0e14fc4b4dde7ae91ff62750cd0d015fb09016ea7c4a1805203263
4
+ data.tar.gz: 00c53d8f9d349c4d40a63036dbd67a7ab335e24edf97e9c0202b00d2825c7714
5
5
  SHA512:
6
- metadata.gz: 96a5bc8b3c845098794cec76a15763afde0d7b1d31caec84daf558d031a560fbd954d068b115dc9c0ba3a43806619d1d104f68b2d07ecd98c161c1d6bf3e99d1
7
- data.tar.gz: fa5d05a001407b8dcfc41c530caacb1b547c60a42831ca9bfdb6aa6ec6983c1d63178c5d4429a8450aaedbac1f51ad48c747d3f4c4e498054b2564842fe55f93
6
+ metadata.gz: e9c86ff9efd776b81e573960f89f3bc9759632f3084122137c51169299e7c6a2863af14abbd6efd9acacf91b4c097f204bd5edcd70ceb602c47ea7bbca532018
7
+ data.tar.gz: 9a6075fdf110e0110ee8bd5abc720768d118d67c6ce657d708769800bbc9f43d1eb431158353fba492a437add83cdcfaf3240ac1d6dea3fa97a587912d354fc9
@@ -4,6 +4,7 @@
4
4
  # Parses maven-wrapper.properties and emits Dependency objects for the two
5
5
  # tracked Maven coordinates: org.apache.maven:apache-maven (the maven distribution)
6
6
  # and org.apache.maven.wrapper:maven-wrapper (the wrapper plugin).
7
+ require "uri"
7
8
  require "dependabot/dependency_requirement"
8
9
  require "dependabot/maven/file_parser"
9
10
  require "dependabot/maven/distributions"
@@ -39,7 +40,8 @@ module Dependabot
39
40
  # - wrapperVersion property (>=3.3.1),
40
41
  # - version segment of wrapperUrl JAR filename (<3.3.0), or
41
42
  # - comment in the mvnw script body (3.3.0 only, see MWRAPPER-120 and MWRAPPER-134).
42
- # This field is mandatory and raises if none of the sources yield a version.
43
+ # When none of the sources yield a version, load_properties returns nil and the
44
+ # wrapper is skipped, so this field is only ever set to a resolved version.
43
45
  const :wrapper_version, String
44
46
 
45
47
  # The full JAR URL from the wrapperUrl property
@@ -65,7 +67,21 @@ module Dependabot
65
67
  DIST_URL_VERSION_REGEX = %r{
66
68
  /apache-maven-(?<version>[^/?#]+)-(?:bin|src)\.(?:zip|tar\.gz)(?:[?#].*)?\z
67
69
  }x
68
- class UnparseableDistributionUrl < RuntimeError; end
70
+
71
+ # Apache Maven Wrapper JAR as it appears in a wrapperUrl path. Anchored to the artifact
72
+ # directory, a numeric version directory, and a `maven-wrapper-<version>.jar` filename whose
73
+ # version equals that directory (via the \k<version> backreference), mirroring Maven's
74
+ # mandatory repository layout. This rejects foreign JARs sharing the directory (e.g.
75
+ # .../maven-wrapper/3.3.4/foreign-3.3.4.jar), non-artifact filenames
76
+ # (.../3.3.4/maven-wrapper-foreign.jar) and mismatched coordinates
77
+ # (.../3.3.4/maven-wrapper-9.9.9.jar). Matched on the path only (not the host, query, or
78
+ # fragment) so wrappers proxied through private registries are still recognised, while
79
+ # wrappers from other vendors (e.g. legacy io.takari) are not. Applied in apache_wrapper_url?.
80
+ WRAPPER_COORDINATE_REGEX = %r{
81
+ /org/apache/maven/wrapper/maven-wrapper/ # Apache Maven Wrapper artifact directory
82
+ (?<version>\d+\.\d+(?:\.\d+)?(?:-\w+)*)/ # version directory
83
+ maven-wrapper-\k<version>\.jar\z # artifact JAR named for that same version
84
+ }xi
69
85
 
70
86
  sig do
71
87
  params(
@@ -77,23 +93,11 @@ module Dependabot
77
93
  content = properties_file.content
78
94
  return [] unless content
79
95
 
80
- distribution_url = get_property_value(content, "distributionUrl")
81
- if distribution_url&.include?("mvnd")
82
- Dependabot.logger.warn("Maven daemon (mvnd) distribution is not supported, skipping wrapper update")
83
- return []
84
- end
85
-
86
- begin
87
- props = load_properties(content, script_files: script_files)
88
- rescue UnparseableDistributionUrl => e
89
- Dependabot.logger.warn("#{e.message}, skipping wrapper update")
90
- return []
91
- end
92
-
93
- if props.wrapper_url&.include?("takari")
94
- Dependabot.logger.warn("The Takari distribution is not supported, skipping wrapper update")
95
- return []
96
- end
96
+ # load_properties returns nil for wrappers we cannot safely update (missing
97
+ # mandatory data or an unsupported vendor/distribution). The reason is logged there,
98
+ # and we skip the wrapper without disrupting ordinary POM dependency parsing.
99
+ props = load_properties(content, script_files: script_files)
100
+ return [] unless props
97
101
 
98
102
  file_name = properties_file.name
99
103
  has_debug_scripts = debug_scripts?(script_files)
@@ -252,35 +256,65 @@ module Dependabot
252
256
  script_files.any? { |f| debug_scripts.any? { |s| f.name.end_with?(s) } }
253
257
  end
254
258
 
255
- sig { params(content: String, script_files: T::Array[DependencyFile]).returns(WrapperProperties) }
259
+ sig { params(content: String, script_files: T::Array[DependencyFile]).returns(T.nilable(WrapperProperties)) }
256
260
  def self.load_properties(content, script_files: [])
257
- distribution_url = get_property_value!(content, "distributionUrl")
261
+ distribution_url = get_property_value(content, "distributionUrl")
262
+ return skip_wrapper("distributionUrl property is missing") unless distribution_url
263
+
264
+ return skip_wrapper("Maven daemon (mvnd) distribution is not supported") if distribution_url.include?("mvnd")
265
+
258
266
  distribution_version = extract_distribution_version(distribution_url)
259
- distribution_sha256_sum = get_property_value(content, "distributionSha256Sum")
267
+ return skip_wrapper("could not extract Maven version from distributionUrl") unless distribution_version
268
+
260
269
  wrapper_url = get_property_value(content, "wrapperUrl")
261
- wrapper_sha256_sum = get_property_value(content, "wrapperSha256Sum")
262
- distribution_type = resolve_distribution_type(content, wrapper_url)
270
+ if wrapper_url && !apache_wrapper_url?(wrapper_url)
271
+ return skip_wrapper("wrapperUrl is not an Apache Maven Wrapper")
272
+ end
273
+
263
274
  wrapper_version = resolve_wrapper_version(content, wrapper_url, script_files)
275
+ unless wrapper_version
276
+ return skip_wrapper(
277
+ "could not determine Maven Wrapper version from wrapperVersion, wrapperUrl, or script files"
278
+ )
279
+ end
264
280
 
265
281
  WrapperProperties.new(
266
282
  distribution_url: distribution_url,
267
283
  distribution_version: distribution_version,
268
- distribution_sha256_sum: distribution_sha256_sum,
269
- wrapper_sha256_sum: wrapper_sha256_sum,
284
+ distribution_sha256_sum: get_property_value(content, "distributionSha256Sum"),
285
+ wrapper_sha256_sum: get_property_value(content, "wrapperSha256Sum"),
270
286
  wrapper_version: wrapper_version,
271
287
  wrapper_url: wrapper_url,
272
- distribution_type: distribution_type
288
+ distribution_type: resolve_distribution_type(content, wrapper_url)
273
289
  )
274
290
  end
275
291
 
276
- sig { params(content: String).returns(String) }
292
+ # Signals that the wrapper cannot be updated: logs the reason and returns nil so the
293
+ # caller treats the wrapper as absent rather than aborting the whole Maven parse.
294
+ sig { params(reason: String).returns(NilClass) }
295
+ def self.skip_wrapper(reason)
296
+ Dependabot.logger.warn("#{reason}, skipping Maven Wrapper update")
297
+ nil
298
+ end
299
+
300
+ sig { params(content: String).returns(T.nilable(String)) }
277
301
  def self.extract_distribution_version(content)
278
302
  match = content.match(DIST_URL_VERSION_REGEX)
279
- unless match && match[:version]
280
- raise UnparseableDistributionUrl, "Could not extract Maven version from distributionUrl"
281
- end
303
+ match && match[:version]
304
+ end
282
305
 
283
- T.must(match[:version])
306
+ # True when the wrapperUrl points at the Apache Maven Wrapper artifact. The coordinate is
307
+ # matched against the URL path only (never the query or fragment) so that a non-Apache JAR
308
+ # cannot slip through the allowlist by carrying the coordinate in a `?redirect=...` query.
309
+ # A malformed URL that cannot be parsed is treated as not-Apache.
310
+ sig { params(url: String).returns(T::Boolean) }
311
+ def self.apache_wrapper_url?(url)
312
+ path = URI.parse(url).path
313
+ return false unless path
314
+
315
+ path.match?(WRAPPER_COORDINATE_REGEX)
316
+ rescue URI::InvalidURIError
317
+ false
284
318
  end
285
319
 
286
320
  sig { params(content: String, target_key: String).returns(T.nilable(String)) }
@@ -324,15 +358,6 @@ module Dependabot
324
358
  end
325
359
  end
326
360
 
327
- sig { params(content: String, target_key: String).returns(String) }
328
- def self.get_property_value!(content, target_key)
329
- value = get_property_value(content, target_key)
330
-
331
- raise "Missing mandatory property: #{target_key}" if value.nil?
332
-
333
- value
334
- end
335
-
336
361
  private_class_method :build_distribution_dependency
337
362
  private_class_method :build_wrapper_dependency
338
363
  private_class_method :build_distribution_requirements
@@ -353,7 +378,7 @@ module Dependabot
353
378
  content: String,
354
379
  wrapper_url: T.nilable(String),
355
380
  script_files: T::Array[DependencyFile]
356
- ).returns(String)
381
+ ).returns(T.nilable(String))
357
382
  end
358
383
  def self.resolve_wrapper_version(content, wrapper_url, script_files)
359
384
  version = get_property_value(content, "wrapperVersion")
@@ -367,10 +392,6 @@ module Dependabot
367
392
  version = load_wrapper_version_from_scripts(script_files)
368
393
  end
369
394
 
370
- if version.nil?
371
- raise "Could not determine Maven Wrapper version from wrapperVersion, wrapperUrl, or script files"
372
- end
373
-
374
395
  version
375
396
  end
376
397
 
@@ -401,6 +422,8 @@ module Dependabot
401
422
  private_class_method :resolve_wrapper_version
402
423
  private_class_method :parse_version_from_wrapper_url
403
424
  private_class_method :load_wrapper_version_from_scripts
425
+ private_class_method :skip_wrapper
426
+ private_class_method :apache_wrapper_url?
404
427
  end
405
428
  end
406
429
  end
@@ -17,7 +17,7 @@ require "dependabot/errors"
17
17
  # - http://maven.apache.org/pom.html
18
18
  module Dependabot
19
19
  module Maven
20
- # rubocop:disable Metrics/ClassLength
20
+ # rubocop:disable-next Metrics/ClassLength
21
21
  class FileParser < Dependabot::FileParsers::Base
22
22
  extend T::Sig
23
23
 
@@ -658,7 +658,6 @@ module Dependabot
658
658
  doc.css(PLUGIN_SELECTOR, PLUGIN_ARTIFACT_ITEMS_SELECTOR)
659
659
  end
660
660
  end
661
- # rubocop:enable Metrics/ClassLength
662
661
  end
663
662
  end
664
663
 
@@ -222,6 +222,7 @@ module Dependabot
222
222
  wrapper_plugin_version: wrapper_version,
223
223
  env: build_env,
224
224
  distribution_type: distribution_type,
225
+ registry_base: resolved_registry_base,
225
226
  extra_args: extra_args,
226
227
  cwd: wrapper_dir
227
228
  )
@@ -288,8 +289,10 @@ module Dependabot
288
289
  end
289
290
 
290
291
  # Builds the environment hash passed to the native wrapper command.
291
- # Sets the proxy host and, for a private/mirror registry, MVNW_REPOURL. Registry auth is
292
- # injected by Dependabot's proxy, so no username/password is set here (core never receives them).
292
+ # Sets only the proxy host. Registry routing is handled by the native
293
+ # helper's generated settings mirror (see `resolved_registry_base`), and
294
+ # registry auth is injected by Dependabot's proxy, so no username/password
295
+ # is set here (core never receives them).
293
296
  sig { returns(T::Hash[String, String]) }
294
297
  def build_env
295
298
  env = T.let({}, T::Hash[String, String])
@@ -299,9 +302,6 @@ module Dependabot
299
302
  env["PROXY_HOST"] = proxy_url.host.to_s
300
303
  end
301
304
 
302
- registry_base, cred = resolve_registry_base_and_credential
303
- env.merge!(build_registry_env(registry_base, cred))
304
-
305
305
  if Dependabot.logger.debug?
306
306
  env["MVNW_VERBOSE"] = "true"
307
307
  Dependabot.logger.debug "build_env result: #{env}"
@@ -310,51 +310,18 @@ module Dependabot
310
310
  env
311
311
  end
312
312
 
313
- sig do
314
- returns([T.nilable(String), T.nilable(Dependabot::Credential)])
315
- end
316
- def resolve_registry_base_and_credential
317
- properties = wrapper_properties_file&.content.to_s
318
- dist_url = effective_distribution_url(properties)
319
- Dependabot.logger.debug "Effective distribution URL: #{dist_url}"
320
-
321
- registry_base_regex = %r{^(https?://[^/]+(?:/[^/]+)*)/org/apache/maven/apache-maven/}
322
- registry_base = dist_url&.match(registry_base_regex)&.captures&.first
323
- Dependabot.logger.debug "Extracted registry base: #{registry_base || '(none)'}"
324
-
325
- cred = maven_registry_credential(registry_base)
326
- Dependabot.logger.debug "Matched credential: #{cred ? "url=#{cred.fetch('url', '(none)')}" : '(none)'}"
327
-
328
- [registry_base, cred]
329
- end
330
-
331
- sig do
332
- params(registry_base: T.nilable(String), cred: T.nilable(Dependabot::Credential))
333
- .returns(T::Hash[String, String])
334
- end
335
- def build_registry_env(registry_base, cred)
336
- if registry_base
337
- { "MVNW_REPOURL" => registry_base }
338
- elsif cred&.replaces_base?
339
- { "MVNW_REPOURL" => cred.fetch("url").chomp("/") }
340
- else
341
- {}
342
- end
343
- end
344
-
345
- sig { params(registry_base: T.nilable(String)).returns(T.nilable(Dependabot::Credential)) }
346
- def maven_registry_credential(registry_base)
347
- maven_creds = @credentials.select { |c| c["type"] == "maven_repository" }
348
-
349
- if registry_base
350
- url_matches = maven_creds.select do |c|
351
- cred_url = c.fetch("url", "").chomp("/")
352
- !cred_url.empty? && registry_base.start_with?(cred_url)
353
- end
354
- return url_matches.max_by { |c| c.fetch("url", "").length } if url_matches.any?
355
- end
356
-
357
- maven_creds.find(&:replaces_base?)
313
+ # The registry that actually served the release we're updating to. `source_url` on the
314
+ # resolved requirement is `release.url` from version resolution
315
+ # (base_version_finder: `{ version: release.version, source_url: release.url }`), so it is
316
+ # the one place `mvn` is guaranteed to find this version. The native regeneration mirrors
317
+ # plugin/distribution resolution there instead of falling back to Central and hanging behind
318
+ # a no-egress registry. Nil only when no version resolved, leaving the Central default.
319
+ #
320
+ # A trailing slash is stripped so Maven appends the artifact path to a canonical base
321
+ # (`base/org/...`, not `base//org/...`) and registries don't 301-redirect every request.
322
+ sig { returns(T.nilable(String)) }
323
+ def resolved_registry_base
324
+ dependency.requirements.filter_map { |req| req.metadata_string("source_url") }.first&.chomp("/")
358
325
  end
359
326
 
360
327
  sig { params(buildfile: DependencyFile).returns(String) }
@@ -3,9 +3,12 @@
3
3
 
4
4
  require "fileutils"
5
5
  require "open3"
6
+ require "tempfile"
6
7
  require "uri"
7
8
  require "sorbet-runtime"
8
9
  require "nokogiri"
10
+ require "dependabot/errors"
11
+ require "dependabot/command_helpers"
9
12
  require "dependabot/shared_helpers"
10
13
 
11
14
  module Dependabot
@@ -13,6 +16,30 @@ module Dependabot
13
16
  module NativeHelpers
14
17
  extend T::Sig
15
18
 
19
+ # Matches Maven's "Could not transfer artifact" failures, capturing the
20
+ # repository URL and HTTP status so we can classify auth vs. other errors.
21
+ TRANSFER_FAILURE_REGEX =
22
+ %r{Could not transfer artifact (?<artifact>[^ ]+) from/to (?<repository_name>[^ ]+) \((?<repository_url>[^ ]+)\): status code: (?<status_code>[0-9]+)} # rubocop:disable Layout/LineLength
23
+
24
+ # Matches Maven's "Plugin ... could not be resolved" failures, used to
25
+ # detect when the wrapper plugin itself is unavailable behind the proxy.
26
+ WRAPPER_PLUGIN_UNRESOLVED_REGEX =
27
+ /Plugin org\.apache\.maven\.plugins:maven-wrapper-plugin[^ ]* .*could not be resolved/
28
+
29
+ # Upper bound on the length of the Maven error summary included in raised errors,
30
+ # to avoid oversized error payloads while retaining the relevant failure detail.
31
+ MAX_ERROR_SUMMARY_LENGTH = 2_000
32
+
33
+ # Matches ANSI/VT100 control sequences. Maven can be configured to emit colored
34
+ # output (e.g. `-Dstyle.color=always`), wrapping markers like `[ERROR]` in escape
35
+ # codes; we strip these so classification and the surfaced summary see plain text.
36
+ ANSI_ESCAPE_REGEX = %r{\e\[[0-9;?]*[ -/]*[@-~]}
37
+
38
+ # Bounded inactivity timeout for the wrapper download. `run_shell_command`'s watchdog
39
+ # resets on output, and with transfer progress restored a healthy download keeps it
40
+ # alive, so this only trips on genuine silence — well below the 900s DEFAULT.
41
+ WRAPPER_DOWNLOAD_TIMEOUT = CommandHelpers::TIMEOUTS::LONG_RUNNING
42
+
16
43
  pom_path = File.join(__dir__, "pom.xml")
17
44
 
18
45
  version = File.open(pom_path) do |f|
@@ -43,9 +70,8 @@ module Dependabot
43
70
 
44
71
  sig { params(output: String).void }
45
72
  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")
73
+ if (match = output.match(TRANSFER_FAILURE_REGEX)) &&
74
+ (match[:status_code] == "403" || match[:status_code] == "401")
49
75
  raise Dependabot::PrivateSourceAuthenticationFailure, match[:repository_url]
50
76
  end
51
77
 
@@ -67,21 +93,32 @@ module Dependabot
67
93
  wrapper_plugin_version: String,
68
94
  env: T::Hash[String, String],
69
95
  distribution_type: String,
96
+ registry_base: T.nilable(String),
70
97
  extra_args: T::Array[String],
71
98
  cwd: T.nilable(String)
72
99
  ).void
73
100
  end
74
- def self.run_mvnw_wrapper(version:, wrapper_plugin_version:, env:, distribution_type:, extra_args: [], cwd: nil)
101
+ def self.run_mvnw_wrapper(
102
+ version:,
103
+ wrapper_plugin_version:,
104
+ env:,
105
+ distribution_type:,
106
+ registry_base: nil,
107
+ extra_args: [],
108
+ cwd: nil
109
+ )
75
110
  # Use the fully-qualified plugin goal so the exact plugin version is
76
111
  # invoked regardless of the project's plugin group configuration.
77
112
  plugin_goal = "org.apache.maven.plugins:maven-wrapper-plugin:" \
78
113
  "#{wrapper_plugin_version}:wrapper"
79
114
 
115
+ # Do NOT add `--no-transfer-progress`: Maven's transfer progress is the liveness signal
116
+ # that keeps `run_shell_command`'s inactivity watchdog alive during a large download.
117
+ # Suppressing it made a healthy-but-slow fetch look hung and get killed at the timeout.
80
118
  standard_args = [
81
119
  plugin_goal,
82
120
  "-Dmaven=#{version}",
83
- "-Dtype=#{distribution_type}",
84
- "--no-transfer-progress"
121
+ "-Dtype=#{distribution_type}"
85
122
  ] + extra_args
86
123
 
87
124
  # Pass the argument vector directly instead of a pre-joined shell string.
@@ -91,7 +128,118 @@ module Dependabot
91
128
  # to parse. An argument vector is executed without an intermediate shell.
92
129
  cmd = ["mvn"] + standard_args
93
130
  run_cwd = cwd && cwd != "." ? cwd : nil
94
- SharedHelpers.run_shell_command(cmd, env: env, cwd: run_cwd)
131
+
132
+ # Route the native `mvn`'s plugin/distribution resolution to the registry that served the
133
+ # resolved version via a generated settings mirror; without it `mvn` falls back to Central
134
+ # and hangs behind a no-egress registry. Absent when no version resolved, leaving the baked
135
+ # Central default. The bounded timeout is explained on WRAPPER_DOWNLOAD_TIMEOUT.
136
+ settings_file = registry_base && wrapper_settings_file(registry_base)
137
+ settings_args = settings_file ? ["-s", T.must(settings_file.path)] : []
138
+ begin
139
+ output = SharedHelpers.run_shell_command(
140
+ cmd + settings_args,
141
+ env: env,
142
+ cwd: run_cwd,
143
+ timeout: WRAPPER_DOWNLOAD_TIMEOUT
144
+ )
145
+ ensure
146
+ settings_file&.close! # deletes the temp file
147
+ end
148
+ Dependabot.logger.info("mvn wrapper output: STDOUT:#{output}")
149
+ output
150
+ rescue SharedHelpers::HelperSubprocessFailed => e
151
+ # `run_shell_command` raises HelperSubprocessFailed on a non-zero exit, and the
152
+ # updater sanitizes that into an opaque `SubprocessFailed` that only reports the
153
+ # command and hides the real Maven output. Log the full output and re-raise a
154
+ # classified Dependabot error so operators get an actionable message, mirroring
155
+ # the `run_mvn_dependency_tree_plugin` path.
156
+ Dependabot.logger.warn("mvn wrapper command failed:\n#{e.message}")
157
+ handle_wrapper_error(e)
158
+ end
159
+
160
+ # Writes a temporary Maven settings file that keeps the proxy block and adds a
161
+ # mirror pointing every external repository at the resolved base. Registry
162
+ # auth still flows through the Dependabot proxy; no credentials are written here.
163
+ # The caller is responsible for deleting the returned file (via `close!`).
164
+ sig { params(registry_base: String).returns(Tempfile) }
165
+ def self.wrapper_settings_file(registry_base)
166
+ file = Tempfile.new(["dependabot-mvn-wrapper-settings", ".xml"])
167
+ file.write(wrapper_settings_xml(registry_base))
168
+ file.close
169
+ file
170
+ end
171
+
172
+ # Builds the settings XML used for the wrapper invocation: the same proxy block as
173
+ # the baked settings (auth is injected by the proxy) plus a `mirrorOf=external:*`
174
+ # mirror so plugin, transitive POM, and distribution resolution all route through
175
+ # the resolved base instead of Central.
176
+ sig { params(registry_base: String).returns(String) }
177
+ def self.wrapper_settings_xml(registry_base)
178
+ <<~XML
179
+ <settings>
180
+ <proxies>
181
+ <proxy>
182
+ <id>dependabot-proxy</id>
183
+ <active>true</active>
184
+ <protocol>http</protocol>
185
+ <host>${env.PROXY_HOST}</host>
186
+ <port>1080</port>
187
+ </proxy>
188
+ </proxies>
189
+ <mirrors>
190
+ <mirror>
191
+ <id>dependabot-wrapper-mirror</id>
192
+ <mirrorOf>external:*</mirrorOf>
193
+ <url>#{registry_base.encode(xml: :text)}</url>
194
+ </mirror>
195
+ </mirrors>
196
+ </settings>
197
+ XML
198
+ end
199
+
200
+ # Classifies a failed Maven Wrapper invocation into an actionable Dependabot
201
+ # error. Known auth and plugin-resolution failures are mapped to their specific
202
+ # error types, and genuine Maven diagnostics are surfaced via MisconfiguredTooling.
203
+ # `run_shell_command` also reports inactivity timeouts and a missing `mvn`
204
+ # executable as HelperSubprocessFailed; those carry no Maven `[ERROR]` markers, so
205
+ # we re-raise the original error and let it follow normal unknown-error routing
206
+ # instead of mislabelling infrastructure/runtime failures as tooling misconfigurations.
207
+ sig { params(error: SharedHelpers::HelperSubprocessFailed).returns(T.noreturn) }
208
+ def self.handle_wrapper_error(error)
209
+ # Strip ANSI color codes up front so classification and the surfaced summary see
210
+ # plain text even when Maven is configured to emit colored output.
211
+ output = error.message.gsub(ANSI_ESCAPE_REGEX, "")
212
+
213
+ if (match = output.match(TRANSFER_FAILURE_REGEX)) &&
214
+ (match[:status_code] == "403" || match[:status_code] == "401")
215
+ raise Dependabot::PrivateSourceAuthenticationFailure, match[:repository_url]
216
+ end
217
+
218
+ if output.match?(WRAPPER_PLUGIN_UNRESOLVED_REGEX)
219
+ raise Dependabot::DependencyFileNotResolvable, "Could not resolve the Maven Wrapper plugin."
220
+ end
221
+
222
+ # Only reclassify when Maven emitted its own `[ERROR]` diagnostics. Otherwise the
223
+ # failure is not a Maven misconfiguration (e.g. timeout or missing executable), so
224
+ # re-raise the original error; its full output is already in the job log.
225
+ summary = mvn_error_summary(output)
226
+ raise error unless summary
227
+
228
+ raise Dependabot::MisconfiguredTooling.new("Maven Wrapper", summary)
229
+ end
230
+
231
+ # Extracts Maven's own `[ERROR]` lines from the combined tool output so that raised
232
+ # errors surface the relevant failure reason without leaking unrelated build noise or
233
+ # arbitrary subprocess output (which may contain sensitive file contents or paths) into
234
+ # the reported error. Returns nil when Maven produced no `[ERROR]` diagnostics, and caps
235
+ # the length to keep error payloads reasonable.
236
+ sig { params(output: String).returns(T.nilable(String)) }
237
+ def self.mvn_error_summary(output)
238
+ error_lines = output.lines.map(&:chomp).select { |line| line.include?("[ERROR]") }
239
+ return nil if error_lines.empty?
240
+
241
+ summary = error_lines.join("\n")
242
+ summary.length > MAX_ERROR_SUMMARY_LENGTH ? "#{summary[0, MAX_ERROR_SUMMARY_LENGTH]}..." : summary
95
243
  end
96
244
  end
97
245
  end
@@ -134,9 +134,7 @@ module Dependabot
134
134
  sig { returns(String) }
135
135
  def property_name
136
136
  @property_name ||= T.let(
137
- dependency.requirements
138
- .find { |r| r.metadata_string("property_name") }
139
- &.metadata_string("property_name"),
137
+ property_requirement.metadata_string("property_name"),
140
138
  T.nilable(String)
141
139
  )
142
140
 
@@ -148,13 +146,33 @@ module Dependabot
148
146
  sig { returns(T.nilable(String)) }
149
147
  def property_source
150
148
  @property_source ||= T.let(
151
- dependency.requirements
152
- .find { |r| r.metadata_string("property_name") == property_name }
153
- &.metadata_string("property_source"),
149
+ property_requirement.metadata_string("property_source"),
154
150
  T.nilable(String)
155
151
  )
156
152
  end
157
153
 
154
+ sig { returns(Dependabot::DependencyRequirement) }
155
+ def property_requirement
156
+ property_requirements = dependency.requirements.select { |r| r.metadata_string("property_name") }
157
+ matching_requirement = property_requirements.find do |requirement|
158
+ normalized_requirement_version(requirement) == dependency.version
159
+ end
160
+ requirement = matching_requirement || property_requirements.first
161
+
162
+ raise "No requirement with a property name!" unless requirement
163
+
164
+ requirement
165
+ end
166
+
167
+ sig { params(requirement: Dependabot::DependencyRequirement).returns(T.nilable(String)) }
168
+ def normalized_requirement_version(requirement)
169
+ requirement_string = requirement.requirement_string
170
+ return unless requirement_string
171
+ return if requirement_string.include?(",")
172
+
173
+ requirement_string.gsub(/[\(\)\[\]]/, "").strip
174
+ end
175
+
158
176
  sig { params(string: String).returns(T::Boolean) }
159
177
  def includes_property_reference?(string)
160
178
  string.match?(Maven::FileParser::PROPERTY_REGEX)
@@ -86,13 +86,13 @@ module Dependabot
86
86
  sig { returns(T::Array[Dependabot::DependencyRequirement]) }
87
87
  attr_reader :requirements
88
88
 
89
- # Bumps a wrapper (maven-wrapper.properties) requirement. As well as the requirement
90
- # string, it updates the fields the FileUpdater actually reads so a real update regenerates
91
- # the *new* release rather than the old one:
92
- # - distributionUrl -> metadata[:distribution_version] and the versioned source url
93
- # - wrapperVersion -> metadata[:wrapper_version]
94
- # The wrapperUrl tag-along requirement (present only on the distribution dependency) is left
95
- # otherwise untouched, since bumping the distribution does not change the wrapper JAR.
89
+ # Bumps a wrapper (maven-wrapper.properties) requirement to the resolved version, keeping the
90
+ # fields the FileUpdater reads in sync so it regenerates the *new* release, not the old one:
91
+ # - distributionUrl -> metadata[:distribution_version] + the versioned source url
92
+ # - wrapperVersion -> metadata[:wrapper_version]
93
+ # - both -> metadata[:source_url] (registry that served the version, so the
94
+ # FileUpdater can mirror the native `mvn` there)
95
+ # - wrapperUrl -> left untouched (tag-along; bumping the distribution doesn't move it)
96
96
  sig do
97
97
  params(req: Dependabot::DependencyRequirement)
98
98
  .returns(Dependabot::DependencyRequirement)
@@ -105,15 +105,32 @@ module Dependabot
105
105
  when "distributionUrl"
106
106
  updated = Dependabot::DependencyRequirement.create(req.merge(requirement: new_version))
107
107
  updated = merge_metadata_version(updated, :distribution_version, new_version)
108
- merge_source_url(updated, old_version, new_version)
108
+ updated = merge_source_url(updated, old_version, new_version)
109
+ merge_registry_source_url(updated)
109
110
  when "wrapperVersion"
110
111
  updated = Dependabot::DependencyRequirement.create(req.merge(requirement: new_version))
111
- merge_metadata_version(updated, :wrapper_version, new_version)
112
+ updated = merge_metadata_version(updated, :wrapper_version, new_version)
113
+ merge_registry_source_url(updated)
112
114
  else
113
115
  req
114
116
  end
115
117
  end
116
118
 
119
+ # Stamps the registry that served the resolved version (source_url = release.url) onto the
120
+ # wrapper requirement metadata, so the FileUpdater can mirror the native `mvn` regeneration
121
+ # to the same registry the version was resolved and vetted against. Nil when no version
122
+ # resolved (no key added), leaving the wrapper's Central default.
123
+ sig do
124
+ params(req: Dependabot::DependencyRequirement).returns(Dependabot::DependencyRequirement)
125
+ end
126
+ def merge_registry_source_url(req)
127
+ base = source_url
128
+ return req unless base
129
+
130
+ metadata = req.metadata || {}
131
+ Dependabot::DependencyRequirement.create(req.merge(metadata: metadata.merge(source_url: base)))
132
+ end
133
+
117
134
  sig do
118
135
  params(req: Dependabot::DependencyRequirement, key: Symbol, new_version: String)
119
136
  .returns(Dependabot::DependencyRequirement)
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.393.0
4
+ version: 0.395.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.393.0
18
+ version: 0.395.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.393.0
25
+ version: 0.395.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rexml
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -183,28 +183,28 @@ 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
- name: turbo_tests
195
+ name: turbo_tests2
196
196
  requirement: !ruby/object:Gem::Requirement
197
197
  requirements:
198
198
  - - "~>"
199
199
  - !ruby/object:Gem::Version
200
- version: 2.2.5
200
+ version: 3.2.7
201
201
  type: :development
202
202
  prerelease: false
203
203
  version_requirements: !ruby/object:Gem::Requirement
204
204
  requirements:
205
205
  - - "~>"
206
206
  - !ruby/object:Gem::Version
207
- version: 2.2.5
207
+ version: 3.2.7
208
208
  - !ruby/object:Gem::Dependency
209
209
  name: vcr
210
210
  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.393.0
299
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.395.0
300
300
  rdoc_options: []
301
301
  require_paths:
302
302
  - lib