Module: Lich::GemCheck
- Defined in:
- documented/gemcheck.rb
Overview
Verifies bundled gems are installed at Lich startup and alerts the user via a native OS dialog (with a log-file fallback) when any are missing. Runs once during boot, before scripts load.
Defined Under Namespace
Classes: ConsentError
Constant Summary collapse
- WINDOWS_MESSAGE =
Alert message shown to Windows users when required gems are missing.
Instructs the user to update to the latest Ruby version using the Ruby4Lich5 installer.
"You're missing required Ruby gems!\n\n" \ "Please update to the latest Ruby version using the\n" \ "Ruby4Lich5 installer."
- UNIX_MESSAGE =
Alert message shown to Unix/Linux/macOS users when required gems are missing.
Instructs the user to run
bundle installfrom the Lich5 folder. "You're missing required Ruby gems!\n\n" \ "Please run 'bundle install' from your Lich5 folder."
- TITLE =
'Lich5: Missing Ruby Gems'- RELEASE_URL =
URL to the latest Lich5 release on GitHub.
Shown to Windows users in the missing-gem alert dialog as the download link when they choose to open it.
'https://github.com/elanthia-online/lich-5/releases/latest'- LOG_FILENAME =
'lich5-missing-gems.log'- CONSENT_TIMEOUT_SECONDS =
120- BUNDLER_RECOVERY_TIMEOUT_SECONDS =
120- ALERT_TIMEOUT_SECONDS =
120
Class Method Summary collapse
- .alert(missing: [], groups: [:default], error: nil) ⇒ void
-
.alert_linux(body) ⇒ void
Reports missing gems on stderr, and additionally in a GUI dialog when there is no terminal that report could have reached.
- .alert_macos(body) ⇒ void
- .alert_windows(body) ⇒ void
-
.all_groups ⇒ Array<Symbol>
All groups declared in the Gemfile.
-
.build_alert_body(missing, error) ⇒ String
Composes the alert dialog body: platform message + a bulleted list of detected missing gems, plus the underlying error when one was captured.
- .build_recovery_prompt(units) ⇒ String
-
.bundler_recovery_supported?(groups) ⇒ Boolean
The initial non-Windows recovery is deliberately macOS-only and only covers default runtime gems.
- .cmd_available?(cmd) ⇒ Boolean
-
.configure_gemfile! ⇒ void
Ensures Bundler resolves Lich's Gemfile even when the app is launched from another working directory, such as macOS app launch from /.
-
.confirm_bundler_recovery(gem_names) ⇒ Symbol
:approved, :declined, :timed_out, or :unavailable.
-
.confirm_recovery_units(units) ⇒ Symbol
:approved, :declined, or :unavailable.
-
.confirm_windows(body) ⇒ Symbol
:approved, :declined, or :timed_out.
-
.consent_failure_reason(decision) ⇒ String
Loggable reason for not installing.
-
.dependency_report(groups) ⇒ String
Builds a per-dependency status line for every gem in the requested groups: name, requirement, and whether it's installed.
-
.macos_dialog_body(body) ⇒ String
AppleScript expression retaining each line break.
-
.message ⇒ String
The message appropriate for the current platform.
-
.missing_gems(groups = [:default]) ⇒ Array<String>
Names of gems declared in the Gemfile that are not installed at any version satisfying the declared requirement, scoped to the given groups.
- .notice_windows(body) ⇒ void
-
.recover_with_bundler_consent!(gem_names, groups: [:default]) ⇒ BundlerRecovery::Result?
Runs a consented Bundler repair for the default non-GTK runtime gems on macOS.
-
.recover_with_consent!(gem_names, force: false, groups: [:default]) ⇒ DependencyRecovery::Result?
Fetches and validates the manifest, requests consent for each affected recovery unit, then performs the download and installation only after all units were approved.
- .recovery_unit_label(unit) ⇒ String
-
.recovery_units_approved?(units, groups) ⇒ Boolean
Requests one consent decision for every affected unit before any artifact download or installation begins.
- .report_bundler_consent_failure(missing, groups, reason) ⇒ void
- .report_consent_failure(units, groups, reason) ⇒ void
-
.run_with_timeout(cmd, timeout) ⇒ Boolean
Runs an external command that would otherwise block indefinitely (a GUI dialog with no one present to dismiss it, e.g. a dead or forwarded X display) and forcibly reclaims control once the timeout elapses.
-
.safe_call { ... } ⇒ Object, String
Wraps a block, returning its result or a placeholder string on error.
-
.self_healing_supported? ⇒ Boolean
Ruby4Lich5 currently publishes and validates recovery artifacts only for the Windows runtime.
-
.show_notice(body) ⇒ void
Shows an error without the normal missing-gem alert's release-page link.
-
.startup_groups(argv = ARGV) ⇒ Array<Symbol>
Chooses the dependency groups that must be present before normal startup.
-
.verify!(*groups) ⇒ void
Verifies every gem required by the requested Bundler groups is installed, alerting the user (native OS dialog, with a log-file fallback) and exiting when any remain missing after one manifest-backed recovery attempt.
-
.windows_popup(body, flags) ⇒ Integer
WScript Popup result code.
- .write_bundler_recovery_log(missing:, groups:, result:) ⇒ void
- .write_log(missing: [], groups: [:default], error: nil, event: 'failure', recovery_units: nil, recovery_note: nil) ⇒ void
-
.write_recovery_log(missing:, groups:, units:) ⇒ void
Records a successful user-approved recovery attempt so self-healing is observable even when Lich subsequently starts without an error dialog.
Class Method Details
.alert(missing: [], groups: [:default], error: nil) ⇒ void
This method returns an undefined value.
311 312 313 314 315 316 317 318 319 |
# File 'documented/gemcheck.rb', line 311 def alert(missing: [], groups: [:default], error: nil) write_log(missing: missing, groups: groups, error: error) body = build_alert_body(missing, error) case RUBY_PLATFORM when /mswin|mingw|cygwin/ then alert_windows(body) when /darwin/ then alert_macos(body) else alert_linux(body) end end |
.alert_linux(body) ⇒ void
This method returns an undefined value.
Reports missing gems on stderr, and additionally in a GUI dialog when there is no terminal that report could have reached.
The dialog is only ever waited on, never merely shown: a launcher start
has no other channel, so run_with_timeout keeps the process alive long
enough for the dialog to be read. From a terminal that wait is what makes
the failure look like a hang -- the dialog is easily missed (another
workspace, behind other windows, or not rendering at all), it blocks for
ALERT_TIMEOUT_SECONDS, and the child's output is discarded, so the user
waits two minutes and never learns why. Worst for the early-exit CLI
commands (--help, --version, --active-sessions, ...): they are
dispatched well after this check runs, so while it blocks they cannot be
reached at all.
Both streams are probed because a terminal launch may redirect either one.
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 |
# File 'documented/gemcheck.rb', line 551 def alert_linux(body) # Always say it on stderr, whether or not a dialog follows. It costs # nothing, it is the whole message rather than a pointer to it, and when # stderr is captured (a launcher's output, a service log) it is the only # record that survives -- the dialog text goes nowhere else. warn("!!ALERT!! #{body}") # Only wait on a dialog when there was no terminal to have said it in. return if $stdout.isatty || $stderr.isatty if cmd_available?('zenity') run_with_timeout(['zenity', '--info', '--title', TITLE, '--text', body], ALERT_TIMEOUT_SECONDS) elsif cmd_available?('kdialog') run_with_timeout(['kdialog', '--title', TITLE, '--msgbox', body], ALERT_TIMEOUT_SECONDS) elsif cmd_available?('xmessage') run_with_timeout(['xmessage', '-center', body], ALERT_TIMEOUT_SECONDS) end # No dialog tool available needs no fallback branch any more -- the # unconditional warn above is the fallback. end |
.alert_macos(body) ⇒ void
This method returns an undefined value.
515 516 517 518 519 520 521 522 523 524 |
# File 'documented/gemcheck.rb', line 515 def alert_macos(body) script = %(display dialog #{macos_dialog_body(body)} ) + %(with title #{TITLE.inspect} ) + %(buttons {"OK"} default button "OK" with icon caution) IO.popen(['osascript', '-'], 'r+') do |io| io.write(script) io.close_write io.read end end |
.alert_windows(body) ⇒ void
This method returns an undefined value.
505 506 507 508 509 510 511 |
# File 'documented/gemcheck.rb', line 505 def alert_windows(body) require 'win32ole' shell = WIN32OLE.new('WScript.Shell') result = shell.Popup("#{body}\nClick OK to open the download page.", ALERT_TIMEOUT_SECONDS, TITLE, 1 + 64) # OK/Cancel + Information icon shell.Run(RELEASE_URL) if result == 1 end |
.all_groups ⇒ Array<Symbol>
Returns all groups declared in the Gemfile.
301 302 303 304 305 |
# File 'documented/gemcheck.rb', line 301 def all_groups Bundler.definition.groups rescue StandardError [:default] end |
.build_alert_body(missing, error) ⇒ String
Composes the alert dialog body: platform message + a bulleted list of
detected missing gems, plus the underlying error when one was captured.
The error is always shown when present: a failed require 'gtk3' may
mean a native DLL failed to load even though every gem is installed, so
hiding the message behind the missing-gems list misdiagnoses the fault.
337 338 339 340 341 342 343 344 345 346 347 |
# File 'documented/gemcheck.rb', line 337 def build_alert_body(missing, error) parts = [] if missing.any? parts << "Missing gems:\n - #{missing.join("\n - ")}" end if error parts << "Underlying error:\n #{error..lines.first.to_s.strip}" end parts << "See #{File.join(TEMP_DIR, LOG_FILENAME)} for details." if defined?(TEMP_DIR) parts.join("\n\n") end |
.build_recovery_prompt(units) ⇒ String
243 244 245 246 247 248 249 250 251 252 |
# File 'documented/gemcheck.rb', line 243 def build_recovery_prompt(units) listed_units = Array(units).map do |unit| members = Array(unit['members']) details = members.length > 1 ? ": #{members.join(', ')}" : '' " - #{recovery_unit_label(unit)}#{details}" end "Required Ruby gems are not installed:\n#{listed_units.join("\n")}\n\n" \ "Lich can download and install the approved, hash-verified packages now.\n\n" \ 'Install now?' end |
.bundler_recovery_supported?(groups) ⇒ Boolean
The initial non-Windows recovery is deliberately macOS-only and only covers default runtime gems. GTK remains outside this Bundler path.
123 124 125 |
# File 'documented/gemcheck.rb', line 123 def bundler_recovery_supported?(groups) BundlerRecovery.supported? && Array(groups).map(&:to_sym) == [:default] end |
.cmd_available?(cmd) ⇒ Boolean
574 575 576 |
# File 'documented/gemcheck.rb', line 574 def cmd_available?(cmd) system('which', cmd, out: File::NULL, err: File::NULL) end |
.configure_gemfile! ⇒ void
This method returns an undefined value.
Ensures Bundler resolves Lich's Gemfile even when the app is launched from another working directory, such as macOS app launch from /.
103 104 105 106 107 108 109 |
# File 'documented/gemcheck.rb', line 103 def configure_gemfile! return if ENV['BUNDLE_GEMFILE'] && !ENV['BUNDLE_GEMFILE'].empty? return unless defined?(LICH_DIR) gemfile = File.join(LICH_DIR, 'Gemfile') ENV['BUNDLE_GEMFILE'] = gemfile if File.file?(gemfile) end |
.confirm_bundler_recovery(gem_names) ⇒ Symbol
Returns :approved, :declined, :timed_out, or :unavailable.
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'documented/gemcheck.rb', line 219 def confirm_bundler_recovery(gem_names) return :unavailable unless BundlerRecovery.supported? body = "Required Ruby gems are not installed:\n#{Array(gem_names).map { |name| " - #{name}" }.join("\n")}\n\n" \ "Lich can install the non-GTK runtime bundle to its private directory now.\n" \ "Your Gemfile and Gemfile.lock will not be changed.\n\nInstall now?" script = %(display dialog #{macos_dialog_body(body)} with title #{TITLE.inspect} ) + %(buttons {"Install", "Cancel"} default button "Install" with icon caution ) + "giving up after #{BUNDLER_RECOVERY_TIMEOUT_SECONDS}" output = IO.popen(['osascript', '-'], 'r+') do |io| io.write(script) io.close_write io.read end return :timed_out if output.include?('gave up:true') return :approved if output.include?('button returned:Install') :declined rescue StandardError :unavailable end |
.confirm_recovery_units(units) ⇒ Symbol
Returns :approved, :declined, or :unavailable.
208 209 210 211 212 213 214 215 |
# File 'documented/gemcheck.rb', line 208 def confirm_recovery_units(units) return :unavailable unless self_healing_supported? body = build_recovery_prompt(units) confirm_windows(body) rescue StandardError :unavailable end |
.confirm_windows(body) ⇒ Symbol
Returns :approved, :declined, or :timed_out.
466 467 468 469 470 471 472 |
# File 'documented/gemcheck.rb', line 466 def confirm_windows(body) result = windows_popup(body, 4 + 32) # Yes/No buttons + question icon return :approved if result == 6 # Yes return :timed_out if result == -1 # WScript Popup timeout :declined end |
.consent_failure_reason(decision) ⇒ String
Returns loggable reason for not installing.
199 200 201 202 203 204 |
# File 'documented/gemcheck.rb', line 199 def (decision) return 'user consent not available' if decision == :unavailable return 'user consent timed out' if decision == :timed_out 'user declined installation' end |
.dependency_report(groups) ⇒ String
Builds a per-dependency status line for every gem in the requested groups: name, requirement, and whether it's installed. This is the single most useful piece of debug output when the detector disagrees with Bundler.
442 443 444 445 446 447 448 449 450 451 452 453 |
# File 'documented/gemcheck.rb', line 442 def dependency_report(groups) deps = Bundler.definition.current_dependencies.select do |dep| (dep.groups & groups).any? end return '(none)' if deps.empty? deps.sort_by(&:name).map do |dep| installed = Gem::Specification.find_all_by_name(dep.name, dep.requirement) status = installed.any? ? "OK (#{installed.map(&:version).join(', ')})" : 'MISSING' "#{dep.name.ljust(24)} #{dep.requirement.to_s.ljust(20)} #{status}" end.join("\n") end |
.macos_dialog_body(body) ⇒ String
Returns AppleScript expression retaining each line break.
528 529 530 |
# File 'documented/gemcheck.rb', line 528 def macos_dialog_body(body) body.split("\n").map(&:inspect).join(' & return & ') end |
.message ⇒ String
Returns the message appropriate for the current platform.
322 323 324 325 326 327 |
# File 'documented/gemcheck.rb', line 322 def case RUBY_PLATFORM when /mswin|mingw|cygwin/ then WINDOWS_MESSAGE else UNIX_MESSAGE end end |
.missing_gems(groups = [:default]) ⇒ Array<String>
Names of gems declared in the Gemfile that are not installed at any version satisfying the declared requirement, scoped to the given groups. Relies on installed Gem::Specifications; git/path-sourced gems are not detected here (the current Gemfile has none).
290 291 292 293 294 295 296 297 298 |
# File 'documented/gemcheck.rb', line 290 def missing_gems(groups = [:default]) Bundler.definition.current_dependencies.select do |dep| (dep.groups & groups).any? end.reject do |dep| Gem::Specification.find_all_by_name(dep.name, dep.requirement).any? end.map(&:name).sort.uniq rescue StandardError [] end |
.notice_windows(body) ⇒ void
This method returns an undefined value.
498 499 500 501 |
# File 'documented/gemcheck.rb', line 498 def notice_windows(body) require 'win32ole' WIN32OLE.new('WScript.Shell').Popup(body, CONSENT_TIMEOUT_SECONDS, TITLE, 64) # OK + information icon end |
.recover_with_bundler_consent!(gem_names, groups: [:default]) ⇒ BundlerRecovery::Result?
Runs a consented Bundler repair for the default non-GTK runtime gems on macOS. The recovery object uses a shipped lockfile when present, or resolves only within staging before making the private RubyGems home active.
166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'documented/gemcheck.rb', line 166 def (gem_names, groups: [:default]) recovery = BundlerRecovery.new(lich_dir: LICH_DIR) if (reason = recovery.preflight(gem_names)) return BundlerRecovery::Result.new(error: reason) end decision = confirm_bundler_recovery(gem_names) unless decision == :approved (gem_names, groups, (decision)) return nil end recovery.recover(gem_names) end |
.recover_with_consent!(gem_names, force: false, groups: [:default]) ⇒ DependencyRecovery::Result?
Fetches and validates the manifest, requests consent for each affected recovery unit, then performs the download and installation only after all units were approved. Returns nil when consent was declined or unavailable; that case is already logged and presented to the user.
149 150 151 152 153 154 155 156 157 |
# File 'documented/gemcheck.rb', line 149 def (gem_names, force: false, groups: [:default]) recovery = DependencyRecovery.new plan = recovery.recovery_plan(gem_names) return DependencyRecovery::Result.new(installed_gems: [], error: plan.error) unless plan.success? return nil unless recovery_units_approved?(plan.units, groups) write_recovery_log(missing: gem_names, groups: groups, units: plan.units) recovery.recover(gem_names, force: force, plan: plan) end |
.recovery_unit_label(unit) ⇒ String
256 257 258 259 260 261 262 |
# File 'documented/gemcheck.rb', line 256 def recovery_unit_label(unit) members = Array(unit['members']) return "#{members.first} gem" if members.length == 1 return 'GTK3 runtime bundle' if unit['id'] == 'gtk3-runtime' "#{unit.fetch('id').tr('-', ' ')} bundle" end |
.recovery_units_approved?(units, groups) ⇒ Boolean
Requests one consent decision for every affected unit before any artifact download or installation begins. This prevents a declined bundle from leaving an earlier approved unit partially installed.
188 189 190 191 192 193 194 195 |
# File 'documented/gemcheck.rb', line 188 def recovery_units_approved?(units, groups) decision = confirm_recovery_units(units) return true if decision == :approved reason = (decision) (units, groups, reason) false end |
.report_bundler_consent_failure(missing, groups, reason) ⇒ void
This method returns an undefined value.
279 280 281 282 |
# File 'documented/gemcheck.rb', line 279 def (missing, groups, reason) write_log(missing: missing, groups: groups, error: ConsentError.new(reason)) show_notice("Required gem#{'s' if missing.length != 1} #{missing.join(', ')} not installed. Exiting.") end |
.report_consent_failure(units, groups, reason) ⇒ void
This method returns an undefined value.
268 269 270 271 272 273 |
# File 'documented/gemcheck.rb', line 268 def (units, groups, reason) error = ConsentError.new(reason) missing = units.flat_map { |unit| Array(unit['members']) }.uniq write_log(missing: missing, groups: groups, error: error) show_notice("Required gem#{'s' if missing.length != 1} #{missing.join(', ')} not installed. Exiting.") end |
.run_with_timeout(cmd, timeout) ⇒ Boolean
Runs an external command that would otherwise block indefinitely (a GUI dialog with no one present to dismiss it, e.g. a dead or forwarded X display) and forcibly reclaims control once the timeout elapses. Wrapping a blocking Kernel#system call in Timeout can't do this safely, since the spawned child keeps running as an orphan; spawning it ourselves lets us kill it directly.
587 588 589 590 591 592 593 594 595 |
# File 'documented/gemcheck.rb', line 587 def run_with_timeout(cmd, timeout) pid = Process.spawn(*cmd, out: File::NULL, err: File::NULL) Timeout.timeout(timeout) { Process.wait(pid) } true rescue Timeout::Error Process.kill('TERM', pid) Process.wait(pid) false end |
.safe_call { ... } ⇒ Object, String
Wraps a block, returning its result or a placeholder string on error.
458 459 460 461 462 |
# File 'documented/gemcheck.rb', line 458 def safe_call yield rescue StandardError => e "(unavailable: #{e.class}: #{e.})" end |
.self_healing_supported? ⇒ Boolean
Ruby4Lich5 currently publishes and validates recovery artifacts only for the Windows runtime. Other platforms retain the ordinary missing-gem warning and never fetch the recovery manifest.
115 116 117 |
# File 'documented/gemcheck.rb', line 115 def self_healing_supported? Gem.win_platform? end |
.show_notice(body) ⇒ void
This method returns an undefined value.
Shows an error without the normal missing-gem alert's release-page link.
486 487 488 489 490 491 492 493 494 |
# File 'documented/gemcheck.rb', line 486 def show_notice(body) case RUBY_PLATFORM when /mswin|mingw|cygwin/ then notice_windows(body) when /darwin/ then alert_macos(body) else alert_linux(body) end rescue StandardError warn "!!ALERT!! #{body}" end |
.startup_groups(argv = ARGV) ⇒ Array<Symbol>
Chooses the dependency groups that must be present before normal startup. Only Windows verifies GTK here, because Ruby4Lich5 publishes a Windows recovery unit for it. On every platform, init.rb treats GTK as required unless the user explicitly passes --no-gtk or --no-gui.
134 135 136 137 138 |
# File 'documented/gemcheck.rb', line 134 def startup_groups(argv = ARGV) groups = [:default] groups << :gtk if self_healing_supported? && !Array(argv).any? { |arg| arg.match?(/^--no-(?:gtk|gui)$/i) } groups end |
.verify!(*groups) ⇒ void
This method returns an undefined value.
Verifies every gem required by the requested Bundler groups is installed, alerting the user (native OS dialog, with a log-file fallback) and exiting when any remain missing after one manifest-backed recovery attempt. This remains a presence check only: it does not call Bundler.setup or lock the load path, leaving scripts free to require gems they install at runtime.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'documented/gemcheck.rb', line 59 def verify!(*groups) groups = [:default] if groups.empty? configure_gemfile! missing = missing_gems(groups) return if missing.empty? if self_healing_supported? result = (missing, groups: groups) exit 1 unless result exit 0 if result.restart_required if result.success? missing = missing_gems(groups) return if missing.empty? alert(missing: missing, groups: groups) else alert(missing: missing, groups: groups, error: DependencyRecovery::Error.new(result.error)) end exit 1 end if bundler_recovery_supported?(groups) result = (missing, groups: groups) exit 1 unless result if result.success? write_bundler_recovery_log(missing: missing, groups: groups, result: result) exit(result.restart_required ? 0 : 1) end alert(missing: missing, groups: groups, error: DependencyRecovery::Error.new(result.error)) exit 1 else alert(missing: missing, groups: groups) exit 1 end end |
.windows_popup(body, flags) ⇒ Integer
Returns WScript Popup result code.
477 478 479 480 481 |
# File 'documented/gemcheck.rb', line 477 def windows_popup(body, flags) require 'win32ole' shell = WIN32OLE.new('WScript.Shell') shell.Popup(body, CONSENT_TIMEOUT_SECONDS, TITLE, flags) end |
.write_bundler_recovery_log(missing:, groups:, result:) ⇒ void
This method returns an undefined value.
363 364 365 366 |
# File 'documented/gemcheck.rb', line 363 def write_bundler_recovery_log(missing:, groups:, result:) write_log(missing: missing, groups: groups, event: 'Bundler recovery', recovery_note: "Private non-GTK bundle staged and activated. Details: #{result.log_path}") end |
.write_log(missing: [], groups: [:default], error: nil, event: 'failure', recovery_units: nil, recovery_note: nil) ⇒ void
This method returns an undefined value.
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'documented/gemcheck.rb', line 375 def write_log(missing: [], groups: [:default], error: nil, event: 'failure', recovery_units: nil, recovery_note: nil) log_path = File.join(TEMP_DIR, LOG_FILENAME) # verify! can run before init.rb creates TEMP_DIR (fresh install), so # ensure the directory exists or the alert would cite a log we never wrote. Dir.mkdir(TEMP_DIR) unless File.exist?(TEMP_DIR) File.open(log_path, 'a') do |f| f.puts "[#{Time.now}] Lich5 GemCheck #{event}" f.puts .gsub(/^/, ' ') if event == 'failure' f.puts if recovery_units f.puts ' Approved manifest recovery units:' recovery_units.each do |unit| f.puts " - #{recovery_unit_label(unit)}: #{Array(unit['members']).join(', ')}" end f.puts end if recovery_note f.puts " Recovery: #{recovery_note}" f.puts end f.puts ' Diagnostics:' f.puts " Ruby: #{RUBY_DESCRIPTION}" f.puts " Bundler: #{safe_call { Bundler::VERSION }}" f.puts " Gemfile: #{safe_call { Bundler.default_gemfile }}" f.puts " Lockfile: #{safe_call { Bundler.default_lockfile }}" f.puts " Working dir: #{Dir.pwd}" f.puts " Groups checked: #{groups.inspect}" f.puts " All groups: #{all_groups.inspect}" f.puts if missing.any? f.puts ' Missing gems (detected):' missing.each { |name| f.puts " - #{name}" } else f.puts ' Missing gems (detected): none identified by GemCheck' end f.puts if error f.puts ' Bundler error:' f.puts " Class: #{error.class}" error..each_line { |line| f.puts " #{line.chomp}" } f.puts end f.puts ' Declared dependencies in requested groups:' safe_call { dependency_report(groups) }.to_s.each_line do |line| f.puts " #{line.chomp}" end f.puts f.puts " Download: #{RELEASE_URL}" if event == 'failure' && RUBY_PLATFORM =~ /mswin|mingw|cygwin/ f.puts end rescue StandardError # Filesystem write failed; continue to GUI attempt. end |
.write_recovery_log(missing:, groups:, units:) ⇒ void
This method returns an undefined value.
Records a successful user-approved recovery attempt so self-healing is observable even when Lich subsequently starts without an error dialog.
355 356 357 |
# File 'documented/gemcheck.rb', line 355 def write_recovery_log(missing:, groups:, units:) write_log(missing: missing, groups: groups, event: 'recovery', recovery_units: units) end |