defmodule Mix.Tasks.Npm.Exec do alias NPM.Node.Exec alias NPM.Node.Runner @shortdoc "Execute a package binary" @moduledoc """ Execute a binary from `node_modules/.bin/`. mix npm.exec eslint . mix npm.exec tsc --version Similar to `npx` but only runs locally installed binaries. """ use Mix.Task @impl true def run([command | args]) do Application.ensure_all_started(:req) case Exec.which(command, "node_modules") do {:ok, bin_path} -> execute(bin_path, args) {:error, :not_found} -> Mix.shell().error("Binary #{command} not found in node_modules/.bin/") Mix.shell().info("Run `mix npm.install` to install packages.") end end def run([]) do bin_dir = "node_modules/.bin" if File.exists?(bin_dir) do case File.ls(bin_dir) do {:ok, entries} -> Mix.shell().info("Available binaries:") Enum.each(Enum.sort(entries), &Mix.shell().info(" #{&1}")) {:error, _} -> Mix.shell().info("No binaries found.") end else Mix.shell().info("No node_modules/.bin/ directory. Run `mix npm.install` first.") end end defp execute(bin_path, args) do {output, status} = Runner.run(Path.expand(bin_path), args, node_modules_dir: "node_modules", cd: File.cwd!() ) if output != "", do: IO.write(output) case status do 0 -> :ok code -> Mix.shell().error("Exited with code #{code}") exit({:shutdown, code}) end end end