lib/mix/tasks/tesseract_js.install_assets.ex

defmodule Mix.Tasks.TesseractJs.InstallAssets do
  @shortdoc "Copies tesseract_js's bundled JS files into your app's priv/static/"
  @moduledoc """
  Copies the package's bundled JS files (`tesseract.min.js`, `worker.min.js`,
  `tesseract_js.umd.js`) into your app's `priv/static/assets/vendor/tesseract/`,
  so Phoenix's static plug can serve them at the paths the HEEx components emit.

  Run this once after adding `tesseract_js` to your deps:

      mix deps.get
      mix tesseract_js.install_assets

  ## When to re-run

  Re-run after upgrading the `tesseract_js` package — the bundled
  `tesseract.min.js`, `worker.min.js`, or the wrapper UMD may have changed.

  ## Options

    * `--out` — output dir (defaults to `priv/static/assets/vendor/tesseract`).
    * `--force` — overwrite existing files even if identical.

  ## Why a copy step?

  Phoenix only serves files from the host app's `priv/static/` — dependencies'
  `priv/static/` directories aren't auto-mounted. Copying is the simplest,
  most explicit way to surface the files without endpoint config changes.
  """
  use Mix.Task

  @switches [out: :string, force: :boolean]
  @files ~w(tesseract.min.js worker.min.js tesseract_js.umd.js)

  @impl true
  def run(argv) do
    {opts, _rest} = OptionParser.parse!(argv, strict: @switches)

    src_dir = source_dir()
    out_dir = opts[:out] || default_out_dir()
    force? = !!opts[:force]

    File.mkdir_p!(out_dir)

    Mix.shell().info("→ Source: #{src_dir}")
    Mix.shell().info("→ Output: #{out_dir}")

    Enum.each(@files, fn name ->
      copy(Path.join(src_dir, name), Path.join(out_dir, name), force?)
    end)

    Mix.shell().info("")
    Mix.shell().info("Done. Don't forget to add the components to your layout:")
    Mix.shell().info("")
    Mix.shell().info("    <TesseractJs.Component.preload />")
    Mix.shell().info("    <TesseractJs.Component.script />")
  end

  defp copy(src, dest, force?) do
    cond do
      not File.exists?(src) ->
        Mix.raise("missing source file: #{src} — is the tesseract_js package installed?")

      File.exists?(dest) and not force? and same?(src, dest) ->
        Mix.shell().info("✓ unchanged: #{Path.basename(dest)}")

      true ->
        File.cp!(src, dest)
        Mix.shell().info("✓ copied:    #{Path.basename(dest)}")
    end
  end

  defp same?(a, b) do
    case {File.read(a), File.read(b)} do
      {{:ok, x}, {:ok, y}} -> x == y
      _ -> false
    end
  end

  defp source_dir do
    case :code.priv_dir(:tesseract_js) do
      {:error, _} ->
        Mix.raise("tesseract_js application not found — is it in your mix.exs deps?")

      priv when is_list(priv) ->
        Path.join(to_string(priv), "static")
    end
  end

  defp default_out_dir, do: Path.join(["priv", "static", "assets", "vendor", "tesseract"])
end