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 =
User-facing alert message displayed on Windows when required gems are missing.
Instructs users to update Ruby 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 =
User-facing alert message displayed on macOS and Linux when required gems are missing.
Instructs users to run 'bundle install' from their 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, presented to Windows users as a download link.
'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
- .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.
303 304 305 306 307 308 309 310 311 |
# File 'documented/gemcheck.rb', line 303 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.
526 527 528 529 530 531 532 533 534 535 536 |
# File 'documented/gemcheck.rb', line 526 def alert_linux(body) 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) else warn "!!ALERT!! #{body}" end end |
.alert_macos(body) ⇒ void
This method returns an undefined value.
507 508 509 510 511 512 513 514 515 516 |
# File 'documented/gemcheck.rb', line 507 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.
497 498 499 500 501 502 503 |
# File 'documented/gemcheck.rb', line 497 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.
293 294 295 296 297 |
# File 'documented/gemcheck.rb', line 293 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.
329 330 331 332 333 334 335 336 337 338 339 |
# File 'documented/gemcheck.rb', line 329 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
235 236 237 238 239 240 241 242 243 244 |
# File 'documented/gemcheck.rb', line 235 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.
115 116 117 |
# File 'documented/gemcheck.rb', line 115 def bundler_recovery_supported?(groups) BundlerRecovery.supported? && Array(groups).map(&:to_sym) == [:default] end |
.cmd_available?(cmd) ⇒ Boolean
540 541 542 |
# File 'documented/gemcheck.rb', line 540 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 /.
95 96 97 98 99 100 101 |
# File 'documented/gemcheck.rb', line 95 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.
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'documented/gemcheck.rb', line 211 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.
200 201 202 203 204 205 206 207 |
# File 'documented/gemcheck.rb', line 200 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.
458 459 460 461 462 463 464 |
# File 'documented/gemcheck.rb', line 458 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.
191 192 193 194 195 196 |
# File 'documented/gemcheck.rb', line 191 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.
434 435 436 437 438 439 440 441 442 443 444 445 |
# File 'documented/gemcheck.rb', line 434 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.
520 521 522 |
# File 'documented/gemcheck.rb', line 520 def macos_dialog_body(body) body.split("\n").map(&:inspect).join(' & return & ') end |
.message ⇒ String
Returns the message appropriate for the current platform.
314 315 316 317 318 319 |
# File 'documented/gemcheck.rb', line 314 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).
282 283 284 285 286 287 288 289 290 |
# File 'documented/gemcheck.rb', line 282 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.
490 491 492 493 |
# File 'documented/gemcheck.rb', line 490 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.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'documented/gemcheck.rb', line 158 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.
141 142 143 144 145 146 147 148 149 |
# File 'documented/gemcheck.rb', line 141 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
248 249 250 251 252 253 254 |
# File 'documented/gemcheck.rb', line 248 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.
180 181 182 183 184 185 186 187 |
# File 'documented/gemcheck.rb', line 180 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.
271 272 273 274 |
# File 'documented/gemcheck.rb', line 271 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.
260 261 262 263 264 265 |
# File 'documented/gemcheck.rb', line 260 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.
553 554 555 556 557 558 559 560 561 |
# File 'documented/gemcheck.rb', line 553 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.
450 451 452 453 454 |
# File 'documented/gemcheck.rb', line 450 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.
107 108 109 |
# File 'documented/gemcheck.rb', line 107 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.
478 479 480 481 482 483 484 485 486 |
# File 'documented/gemcheck.rb', line 478 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.
126 127 128 129 130 |
# File 'documented/gemcheck.rb', line 126 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.
51 52 53 54 55 56 57 58 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 |
# File 'documented/gemcheck.rb', line 51 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.
469 470 471 472 473 |
# File 'documented/gemcheck.rb', line 469 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.
355 356 357 358 |
# File 'documented/gemcheck.rb', line 355 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.
367 368 369 370 371 372 373 374 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 |
# File 'documented/gemcheck.rb', line 367 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.
347 348 349 |
# File 'documented/gemcheck.rb', line 347 def write_recovery_log(missing:, groups:, units:) write_log(missing: missing, groups: groups, event: 'recovery', recovery_units: units) end |