Module: Echoes::Installer

Defined in:
lib/echoes/installer.rb

Overview

Puts a thin wrapper Echoes.app / EchoesEmbed.app into ~/Applications/ so the user can launch echoes from Spotlight, Dock, or Cmd-Space without digging into the gem’s install path. The wrapper’s only job is to exec into the real launcher inside the gem — the gem’s launcher already does all the interesting work (arch forcing on Apple Silicon, Ruby probing, $LOAD_PATH bootstrap).

The gem path is baked into the wrapper at install time. After ‘gem update echoes` the user should re-run `echoes install` to refresh the shortcut to the new gem location.

Constant Summary collapse

USER_APPS_DIR =
File.expand_path('~/Applications')
BUNDLES =
%w[Echoes.app EchoesEmbed.app].freeze

Class Method Summary collapse

Class Method Details

.default_source_rootObject



68
69
70
71
72
# File 'lib/echoes/installer.rb', line 68

def default_source_root
  # File is at <gem-root>/lib/echoes/installer.rb; two `..`s land
  # at the gem root where Echoes.app / EchoesEmbed.app live.
  File.expand_path('../..', __dir__)
end

.install(source_root: default_source_root, target_dir: USER_APPS_DIR) ⇒ Object

‘source_root` is the directory containing the .app bundles —normally the gem’s own root, computed from this file’s path. Overridable for tests.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/echoes/installer.rb', line 26

def install(source_root: default_source_root, target_dir: USER_APPS_DIR)
  FileUtils.mkdir_p(target_dir)

  installed = []
  BUNDLES.each do |bundle|
    source = File.join(source_root, bundle)
    unless Dir.exist?(source)
      warn "echoes install: #{bundle} not found at #{source} (skipping)"
      next
    end
    target = File.join(target_dir, bundle)
    write_wrapper(source, target)
    installed << target
  end

  if installed.empty?
    puts 'echoes install: nothing to do.'
  else
    puts 'Installed:'
    installed.each { |t| puts "  #{t}" }
    puts
    puts 'Re-run `echoes install` after `gem update echoes` to refresh the shortcuts.'
  end
  installed
end

.uninstall(target_dir: USER_APPS_DIR) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/echoes/installer.rb', line 52

def uninstall(target_dir: USER_APPS_DIR)
  removed = []
  BUNDLES.each do |bundle|
    target = File.join(target_dir, bundle)
    next unless Dir.exist?(target)
    FileUtils.remove_entry(target)
    removed << target
  end
  if removed.empty?
    puts 'echoes uninstall: nothing to remove.'
  else
    removed.each { |t| puts "Removed #{t}" }
  end
  removed
end

.write_wrapper(source_bundle, target_bundle) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/echoes/installer.rb', line 74

def write_wrapper(source_bundle, target_bundle)
  bundle_name = File.basename(target_bundle)
  executable  = bundle_name.delete_suffix('.app')

  FileUtils.mkdir_p(File.join(target_bundle, 'Contents', 'MacOS'))

  source_plist = File.join(source_bundle, 'Contents', 'Info.plist')
  if File.exist?(source_plist)
    FileUtils.cp(source_plist, File.join(target_bundle, 'Contents', 'Info.plist'))
  end

  script_path = File.join(target_bundle, 'Contents', 'MacOS', executable)
  File.write(script_path, <<~SHELL)
    #!/bin/bash
    # Auto-generated by `echoes install`. Re-run after `gem update
    # echoes` so this points at the current installed location.
    exec "#{source_bundle}/Contents/MacOS/#{executable}"
  SHELL
  File.chmod(0o755, script_path)
end