Skip to main content

lib/mix/tasks/scoria.assets.build.ex

defmodule Mix.Tasks.Scoria.Assets.Build do
  @shortdoc "Builds the self-contained Scoria dashboard asset bundle (CSS + JS)"
  @moduledoc """
  Assembles the precompiled, self-contained dashboard assets into `priv/static/scoria/`.

  The library ships these compiled artifacts so the dashboard renders fully styled and
  interactive in any host app — with no dependency on the host's Tailwind/esbuild pipeline
  (the Phoenix LiveDashboard / Oban Web model).

      $ mix scoria.assets.build

  CSS: concatenates `assets/css/*.css` (numeric order) under one cascade `@layer` declaration.
  JS:  concatenates the prebuilt Phoenix + LiveView + phoenix_html UMD bundles with
       `assets/js/scoria.js` (Scoria's own LiveSocket + hooks). No bundler binary required.

  Run before `mix hex.publish`; the output is committed and shipped via `package.files`.
  """
  use Mix.Task

  @layer_order "@layer scoria.reset, scoria.tokens, scoria.base, scoria.components, scoria.utilities, scoria.compat;\n"

  @impl Mix.Task
  def run(_args) do
    root = File.cwd!()
    out_dir = Path.join([root, "priv", "static", "scoria"])
    File.mkdir_p!(out_dir)

    build_css(root, out_dir)
    build_js(root, out_dir)

    Mix.shell().info([:green, "* scoria assets built -> priv/static/scoria/{app.css,app.js}"])
  end

  defp build_css(root, out_dir) do
    css_dir = Path.join([root, "assets", "css"])

    css =
      css_dir
      |> Path.join("*.css")
      |> Path.wildcard()
      |> Enum.sort()
      |> Enum.map_join("\n", &File.read!/1)

    header =
      "/* Scoria dashboard styles — generated by `mix scoria.assets.build`. Do not edit by hand. */\n"

    File.write!(Path.join(out_dir, "app.css"), header <> @layer_order <> css)
  end

  defp build_js(root, out_dir) do
    deps = Mix.Project.deps_path()

    sources = [
      Path.join([deps, "phoenix", "priv", "static", "phoenix.min.js"]),
      Path.join([deps, "phoenix_live_view", "priv", "static", "phoenix_live_view.min.js"]),
      Path.join([deps, "phoenix_html", "priv", "static", "phoenix_html.js"]),
      Path.join([root, "assets", "js", "scoria.js"])
    ]

    Enum.each(sources, fn path ->
      unless File.exists?(path), do: Mix.raise("scoria.assets.build: missing source #{path}")
    end)

    header =
      "/* Scoria dashboard client — generated by `mix scoria.assets.build`. Do not edit by hand. */\n"

    js = Enum.map_join(sources, "\n;\n", &File.read!/1)
    File.write!(Path.join(out_dir, "app.js"), header <> js)
  end
end