defmodule BDS.CLI.Install do @moduledoc """ Installs the `bds-cli` launcher into `~/.local/bin` (issue #25), used by the install buttons in the GUI and TUI settings. The installed file is a two-line shim exec'ing the release's `cli/bin/bds-cli` launcher (shipped via `rel/overlays`), so the CLI always runs the same release — and therefore the same settings and cache database — as the installed application. """ @launcher_name "bds-cli" @doc """ Writes the `~/.local/bin/bds-cli` shim. Returns `{:ok, installed_path}`, or `{:error, :no_release}` when not running from a packaged release (dev runs have no launcher to point at). """ @spec install(keyword()) :: {:ok, Path.t()} | {:error, :no_release | File.posix()} def install(opts \\ []) do bin_dir = Keyword.get(opts, :bin_dir, Path.expand("~/.local/bin")) with {:ok, launcher} <- launcher_path(Keyword.get(opts, :release_root)) do installed = Path.join(bin_dir, @launcher_name) with :ok <- File.mkdir_p(bin_dir), :ok <- File.write(installed, shim_script(launcher)), :ok <- File.chmod(installed, 0o755) do {:ok, installed} end end end @doc """ The release's CLI launcher script, resolved from `RELEASE_ROOT` (set by release scripts) or the code root dir (the release root when running a release). `{:error, :no_release}` when neither contains the launcher. """ @spec launcher_path(Path.t() | nil) :: {:ok, Path.t()} | {:error, :no_release} def launcher_path(release_root \\ nil) do [release_root, System.get_env("RELEASE_ROOT"), to_string(:code.root_dir())] |> Enum.reject(&(&1 in [nil, ""])) |> Enum.map(&Path.join([&1, "cli", "bin", @launcher_name])) |> Enum.find(&File.regular?/1) |> case do nil -> {:error, :no_release} path -> {:ok, path} end end defp shim_script(launcher) do """ #!/bin/sh exec "#{launcher}" "$@" """ end end