defmodule Vite do # https://registry.npmjs.org/vite/latest @latest_version "7.3.1" @moduledoc """ """ use Application require Logger @doc false def start(_, _) do unless Application.get_env(:vite, :env) do Logger.error(""" Vite env is not configured. Please set it in your config files: config :vite, :env, config_env() """) end Supervisor.start_link([], strategy: :one_for_one) end @doc false # Latest known version at the time of publishing. def latest_version, do: @latest_version @doc """ Returns the configuration for the given profile. Returns nil if the profile does not exist. """ def config_for!(profile) when is_atom(profile) do Application.get_env(:vite, profile) || raise ArgumentError, """ unknown vite profile. Make sure the profile is defined in your config/config.exs file, such as: config :vite, env: config_env(), version: "#{latest_version()}", #{profile}: [ args: ~w(js/app.js --bundle --target=es2016 --outdir=../priv/static/assets), cd: Path.expand("../assets", __DIR__), env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} ] """ end @spec run(binary, atom, [binary]) :: non_neg_integer def run(exec, profile, extra_args) when is_atom(profile) and is_list(extra_args) do config = config_for!(profile) args = config[:args] || [] if args == [] and extra_args == [] do raise "no arguments passed to vite" end opts = [ cd: config[:cd] || Path.expand("./assets", File.cwd!()), env: config[:env] || %{}, into: IO.stream(:stdio, :line), stderr_to_stdout: true ] System.cmd(exec, ["vite"] ++ args ++ extra_args, opts) |> elem(1) end @doc """ Installs, if not available, and then runs `bun`. Returns the same as `run/2`. """ def install_and_run(profile, args) do exec = System.find_executable("npx") || System.find_executable("bunx") if exec == nil do raise "no javascript runtime found. Please install 'node' or 'bun'." end installed_version() || start_unique_install_worker() run(exec, profile, args) end @doc """ Installs vite with `configured_version/0`. """ def install(using \\ "npm") do exec = System.find_executable(using) if exec == nil do raise "no pacakge management #{using} found. Please install it before." end opts = [ cd: Path.expand("./assets", File.cwd!()), into: IO.stream(:stdio, :line), stderr_to_stdout: true ] System.cmd(using, ["install", "vite@#{configured_version()}", "-D"], opts) |> elem(1) end @doc """ Returns the installed vite version. Returns `version_string` on success or `nil` when vite is not available. """ def installed_version() do case File.read(Path.expand("assets/package.json", File.cwd!())) do {:ok, pkg} -> pkg = Jason.decode!(pkg) get_in(pkg, ["devDependencies", "vite"]) {:error, _} -> nil end end @doc """ Returns the configured esbuild version. """ def configured_version do Application.get_env(:vite, :version, latest_version()) end defp start_unique_install_worker() do ref = __MODULE__.Supervisor |> Supervisor.start_child( Supervisor.child_spec({Task, &install/0}, restart: :transient, id: __MODULE__.Installer) ) |> case do {:ok, pid} -> pid {:error, {:already_started, pid}} -> pid end |> Process.monitor() receive do {:DOWN, ^ref, _, _, _} -> :ok end end end