Current section

Files

Jump to
accrue_admin lib mix tasks accrue_admin.assets.build.ex
Raw

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

defmodule Mix.Tasks.AccrueAdmin.Assets.Build do
@shortdoc "Rebuild the private AccrueAdmin asset bundle"
@moduledoc """
Rebuilds the package-local CSS and JS bundle committed under `priv/static/`.
This task intentionally stays inside `accrue_admin/`:
* reads source files from `assets/`
* writes only `priv/static/accrue_admin.css` and `priv/static/accrue_admin.js`
* does not require host-app Tailwind or JS bootstrap changes
## Examples
mix accrue_admin.assets.build
"""
use Mix.Task
@runner_env_key :accrue_admin_assets_build_runner
@tailwind_version "tailwindcss@3.4.17"
@esbuild_version "esbuild@0.25.3"
defmodule Runner do
@moduledoc false
@callback run(String.t(), [String.t()], keyword()) :: {:ok, integer()} | {:error, term()}
end
defmodule ShellRunner do
@moduledoc false
@behaviour Runner
@impl true
def run(command, args, opts) do
{_, status} =
System.cmd(command, args,
cd: Keyword.fetch!(opts, :cd),
env: [{"BROWSERSLIST_IGNORE_OLD_DATA", "1"}],
stderr_to_stdout: true,
into: IO.stream(:stdio, :line)
)
{:ok, status}
rescue
error -> {:error, error}
end
end
@impl Mix.Task
def run(_argv) do
Mix.Task.run("loadpaths")
root = File.cwd!()
File.mkdir_p!(Path.join(root, "priv/static"))
runner = Application.get_env(:accrue_admin, @runner_env_key, ShellRunner)
run_step!(runner, "tailwind", "npx", tailwind_args(root), cd: root)
run_step!(runner, "esbuild", "npx", esbuild_args(root), cd: root)
Mix.shell().info("Rebuilt AccrueAdmin assets in priv/static/")
end
defp tailwind_args(root) do
[
"--yes",
@tailwind_version,
"--config",
Path.join(root, "assets/tailwind.config.js"),
"--input",
Path.join(root, "assets/css/app.css"),
"--output",
Path.join(root, "priv/static/accrue_admin.css"),
"--minify"
]
end
defp esbuild_args(root) do
[
"--yes",
@esbuild_version,
Path.join(root, "assets/js/app.js"),
"--bundle",
"--format=esm",
"--minify",
"--outfile=" <> Path.join(root, "priv/static/accrue_admin.js")
]
end
defp run_step!(runner, label, command, args, opts) do
case runner.run(command, args, opts) do
{:ok, 0} ->
:ok
{:ok, status} ->
Mix.raise("#{label} build failed with exit status #{status}")
{:error, reason} ->
Mix.raise("#{label} build failed: #{Exception.message(reason)}")
end
end
end