Skip to content

Commit ba2645f

Browse files
committed
Address PR review feedback for #15068
- bun: expose error_context on NoChangeError so observability picks it up - updater: emit "unknown" instead of empty-string for nil boolean metric tags via bool_tag helper - npm_and_yarn: detect fallback traces by stable fingerprint to avoid false positives from dep names containing "audit" - yarn lockfile updater: extract yarn berry audit-fix fallback into helper methods to drop Metrics/AbcSize + Metrics/MethodLength rubocop disables - pnpm lockfile updater: extract fallback logic into maybe_run_pnpm_fallback to drop Metrics/AbcSize rubocop disable - pnpm lockfile updater: add load-order require for dependabot/npm_and_yarn/file_updater
1 parent 40a5f6a commit ba2645f

5 files changed

Lines changed: 130 additions & 60 deletions

File tree

bun/lib/dependabot/bun/file_updater.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ class FileUpdater < Dependabot::FileUpdaters::Base
2020
class NoChangeError < StandardError
2121
extend T::Sig
2222

23+
sig { returns(T::Hash[Symbol, T.untyped]) }
24+
attr_reader :error_context
25+
2326
sig { params(message: String, error_context: T::Hash[Symbol, T.untyped]).void }
2427
def initialize(message:, error_context:)
2528
super(message)

npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater.rb

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,15 +241,24 @@ def error_context(updated_files:)
241241
.returns([T::Boolean, T.nilable(T::Boolean)])
242242
end
243243
def compute_fallback_state(traces)
244-
fallback_traces = traces.select do |t|
245-
t.command.include?("audit") || t.command.include?("--depth Infinity")
246-
end
244+
fallback_traces = traces.select { |t| fallback_trace?(t) }
247245
attempted = fallback_traces.any?
248246
succeeded =
249247
(fallback_traces.any? { |t| t.content_changed_after == true } if attempted)
250248
[attempted, succeeded]
251249
end
252250

251+
# Detect whether a trace represents a fallback invocation (audit-fix
252+
# or pnpm deep update). Match against the stable `fingerprint` (or
253+
# `command` when no fingerprint is set) rather than the raw command
254+
# so that user-controlled dependency names cannot false-positive
255+
# this check.
256+
sig { params(trace: CommandTrace).returns(T::Boolean) }
257+
def fallback_trace?(trace)
258+
key = trace.fingerprint || trace.command
259+
key.include?("audit") || key.include?("--depth Infinity")
260+
end
261+
253262
sig { returns(T::Array[CommandTrace]) }
254263
def aggregated_command_traces
255264
traces = T.let([], T::Array[CommandTrace])

npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater.rb

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# typed: strict
22
# frozen_string_literal: true
33

4+
require "dependabot/npm_and_yarn/file_updater"
45
require "dependabot/shared_helpers/command_trace"
56
require "dependabot/npm_and_yarn/helpers"
67
require "dependabot/npm_and_yarn/package/registry_finder"
@@ -151,7 +152,7 @@ def updated_pnpm_lock_content(pnpm_lock, updated_pnpm_workspace_content: nil)
151152
)
152153
.returns(String)
153154
end
154-
def run_pnpm_update(pnpm_lock:, updated_pnpm_workspace_content: nil) # rubocop:disable Metrics/AbcSize
155+
def run_pnpm_update(pnpm_lock:, updated_pnpm_workspace_content: nil)
155156
# Set dependency files and credentials for automatic env variable injection
156157
Helpers.dependency_files = dependency_files
157158
Helpers.credentials = credentials
@@ -160,36 +161,66 @@ def run_pnpm_update(pnpm_lock:, updated_pnpm_workspace_content: nil) # rubocop:d
160161
File.write(".npmrc", npmrc_content(pnpm_lock))
161162

162163
SharedHelpers.with_git_configured(credentials: credentials) do
163-
original_content = File.read(pnpm_lock.name)
164-
165-
if updated_pnpm_workspace_content
166-
File.write("pnpm-workspace.yaml", updated_pnpm_workspace_content["pnpm-workspace.yaml"])
167-
else
168-
run_pnpm_update_packages
169-
write_final_package_json_files
170-
end
171-
172-
run_pnpm_install
173-
174-
updated_content = File.read(pnpm_lock.name)
175-
@command_traces.last&.content_changed_after = updated_content != original_content
176-
if updated_content == original_content && Dependabot::Experiments.enabled?(:enable_audit_fix_fallback)
177-
run_pnpm_deep_update_fallback
178-
updated_content = File.read(pnpm_lock.name)
179-
@command_traces.last&.content_changed_after = updated_content != original_content
180-
end
181-
182-
if updated_content == original_content && Dependabot::Experiments.enabled?(:enable_audit_fix_fallback)
183-
run_pnpm_audit_fix_fallback(pnpm_lock, original_content)
184-
updated_content = File.read(pnpm_lock.name)
185-
@command_traces.last&.content_changed_after = updated_content != original_content
186-
end
187-
188-
updated_content
164+
run_pnpm_update_with_fallbacks(pnpm_lock, updated_pnpm_workspace_content)
189165
end
190166
end
191167
end
192168

169+
sig do
170+
params(
171+
pnpm_lock: Dependabot::DependencyFile,
172+
updated_pnpm_workspace_content: T.nilable(T::Hash[String, T.nilable(String)])
173+
).returns(String)
174+
end
175+
def run_pnpm_update_with_fallbacks(pnpm_lock, updated_pnpm_workspace_content)
176+
original_content = File.read(pnpm_lock.name)
177+
178+
if updated_pnpm_workspace_content
179+
File.write("pnpm-workspace.yaml", updated_pnpm_workspace_content["pnpm-workspace.yaml"])
180+
else
181+
run_pnpm_update_packages
182+
write_final_package_json_files
183+
end
184+
185+
run_pnpm_install
186+
updated_content = note_content_change(pnpm_lock, original_content)
187+
188+
updated_content = maybe_run_pnpm_fallback(pnpm_lock, original_content, updated_content) do
189+
run_pnpm_deep_update_fallback
190+
end
191+
192+
maybe_run_pnpm_fallback(pnpm_lock, original_content, updated_content) do
193+
run_pnpm_audit_fix_fallback(pnpm_lock, original_content)
194+
end
195+
end
196+
197+
# Re-reads the lockfile, sets `content_changed_after` on the most
198+
# recent CommandTrace, and returns the current lockfile content.
199+
sig { params(pnpm_lock: Dependabot::DependencyFile, original_content: String).returns(String) }
200+
def note_content_change(pnpm_lock, original_content)
201+
updated_content = File.read(pnpm_lock.name)
202+
@command_traces.last&.content_changed_after = updated_content != original_content
203+
updated_content
204+
end
205+
206+
# Runs the supplied fallback only if the lockfile is still
207+
# unchanged and the audit-fix experiment is enabled.
208+
sig do
209+
params(
210+
pnpm_lock: Dependabot::DependencyFile,
211+
original_content: String,
212+
updated_content: String,
213+
block: T.proc.void
214+
).returns(String)
215+
end
216+
def maybe_run_pnpm_fallback(pnpm_lock, original_content, updated_content, &block)
217+
return updated_content unless updated_content == original_content
218+
return updated_content unless Dependabot::Experiments.enabled?(:enable_audit_fix_fallback)
219+
220+
block.call # rubocop:disable Performance/RedundantBlockCall
221+
note_content_change(pnpm_lock, original_content)
222+
end
223+
193224
sig { returns(T.nilable(String)) }
194225
def run_pnpm_update_packages
195226
dependency_updates = dependencies.map do |d|

npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -263,19 +263,31 @@ def requirements_changed?(dependency_name)
263263
end
264264

265265
sig { params(yarn_lock: Dependabot::DependencyFile).returns(T::Hash[String, String]) }
266-
# rubocop:disable Metrics/AbcSize
267-
# rubocop:disable Metrics/MethodLength
268266
def run_yarn_berry_subdependency_updater(yarn_lock:)
269267
dep = T.must(sub_dependencies.first)
270-
update = "#{dep.name}@#{dep.version}"
268+
original_content = File.read(yarn_lock.name)
269+
270+
run_yarn_berry_subdep_commands(dep)
271+
272+
updated_content = File.read(yarn_lock.name)
273+
@command_traces.last&.content_changed_after = updated_content != original_content
274+
275+
if updated_content == original_content && Dependabot::Experiments.enabled?(:enable_audit_fix_fallback)
276+
updated_content = run_yarn_berry_audit_fix_fallback(yarn_lock, dep, original_content)
277+
end
271278

279+
{ yarn_lock.name => updated_content }
280+
end
281+
282+
sig { params(dep: Dependabot::Dependency).void }
283+
def run_yarn_berry_subdep_commands(dep)
284+
update = "#{dep.name}@#{dep.version}"
272285
commands = [
273286
["add #{update} #{yarn_berry_args}".strip, "add <update> #{yarn_berry_args}".strip],
274287
["dedupe #{dep.name} #{yarn_berry_args}".strip, "dedupe <dep_name> #{yarn_berry_args}".strip],
275288
["remove #{dep.name} #{yarn_berry_args}".strip, "remove <dep_name> #{yarn_berry_args}".strip]
276289
]
277290

278-
original_content = File.read(yarn_lock.name)
279291
fingerprint_batch = commands.map { |_cmd, fp| fp }.join(" && ")
280292
CommandTrace.record(
281293
traces: @command_traces,
@@ -285,33 +297,35 @@ def run_yarn_berry_subdependency_updater(yarn_lock:)
285297
) do
286298
Helpers.run_yarn_commands(*commands)
287299
end
300+
end
288301

289-
updated_content = File.read(yarn_lock.name)
290-
@command_traces.last&.content_changed_after = updated_content != original_content
291-
if updated_content == original_content && Dependabot::Experiments.enabled?(:enable_audit_fix_fallback)
292-
begin
293-
CommandTrace.record(
294-
traces: @command_traces,
295-
package_manager: "yarn",
296-
command: "npm audit --fix --mode update-lockfile",
297-
fingerprint: "npm audit --fix --mode update-lockfile"
298-
) do
299-
NativeHelpers.run_yarn_audit_fix_command
300-
end
301-
dep.metadata[:audit_fix_used] = true
302-
rescue SharedHelpers::HelperSubprocessFailed
303-
Dependabot.logger.info(
304-
"yarn npm audit --fix failed or partially fixed — continuing with any changes made"
305-
)
302+
sig do
303+
params(
304+
yarn_lock: Dependabot::DependencyFile,
305+
dep: Dependabot::Dependency,
306+
original_content: String
307+
).returns(String)
308+
end
309+
def run_yarn_berry_audit_fix_fallback(yarn_lock, dep, original_content)
310+
begin
311+
CommandTrace.record(
312+
traces: @command_traces,
313+
package_manager: "yarn",
314+
command: "npm audit --fix --mode update-lockfile",
315+
fingerprint: "npm audit --fix --mode update-lockfile"
316+
) do
317+
NativeHelpers.run_yarn_audit_fix_command
306318
end
307-
updated_content = File.read(yarn_lock.name)
308-
@command_traces.last&.content_changed_after = updated_content != original_content
319+
dep.metadata[:audit_fix_used] = true
320+
rescue SharedHelpers::HelperSubprocessFailed
321+
Dependabot.logger.info(
322+
"yarn npm audit --fix failed or partially fixed — continuing with any changes made"
323+
)
309324
end
310-
311-
{ yarn_lock.name => updated_content }
325+
updated_content = File.read(yarn_lock.name)
326+
@command_traces.last&.content_changed_after = updated_content != original_content
327+
updated_content
312328
end
313-
# rubocop:enable Metrics/MethodLength
314-
# rubocop:enable Metrics/AbcSize
315329

316330
sig { returns(String) }
317331
def yarn_berry_args

updater/lib/dependabot/updater/error_handler.rb

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,26 @@ def maybe_emit_no_change_metric(error_details)
196196
tags: {
197197
package_manager: job.package_manager,
198198
reason: detail[:reason]&.to_s || "unknown",
199-
commands_succeeded: detail[:commands_succeeded].to_s,
200-
fallback_attempted: detail[:fallback_attempted].to_s,
201-
fallback_succeeded: detail[:fallback_succeeded].to_s
199+
commands_succeeded: bool_tag(detail[:commands_succeeded]),
200+
fallback_attempted: bool_tag(detail[:fallback_attempted]),
201+
fallback_succeeded: bool_tag(detail[:fallback_succeeded])
202202
}
203203
)
204204
end
205205

206+
# Convert a tri-state boolean (true / false / nil) into a low-
207+
# cardinality string suitable for a metric tag value. Empty strings
208+
# are treated as invalid tag values by many backends, so we surface
209+
# `"unknown"` instead of the default `nil.to_s == ""`.
210+
sig { params(value: T.untyped).returns(String) }
211+
def bool_tag(value)
212+
case value
213+
when true then "true"
214+
when false then "false"
215+
else "unknown"
216+
end
217+
end
218+
206219
# Emit one info line per command trace + truncated stdout/stderr at
207220
# debug level. Stays a no-op if the error detail does not include
208221
# trace data (e.g. older payloads).

0 commit comments

Comments
 (0)