- bb.edn with compile/test/repl/nrepl/clean/fmt tasks - nREPL checks .nrepl-port for existing server before starting - Cleans up .nrepl-port on shutdown - Add .nrepl-port to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
88 lines
2.2 KiB
Elixir
88 lines
2.2 KiB
Elixir
defmodule Mix.Tasks.Clje.Nrepl do
|
|
@moduledoc """
|
|
Starts an nREPL server for CljElixir.
|
|
|
|
## Usage
|
|
|
|
mix clje.nrepl # random port
|
|
mix clje.nrepl --port 7888 # specific port
|
|
|
|
Writes port to `.nrepl-port` file for editor auto-discovery.
|
|
Cleans up `.nrepl-port` on shutdown.
|
|
|
|
If `.nrepl-port` exists and a server is already listening on that port,
|
|
prints the existing port and exits rather than starting a duplicate.
|
|
"""
|
|
|
|
use Mix.Task
|
|
|
|
@shortdoc "Start a CljElixir nREPL server"
|
|
@nrepl_port_file ".nrepl-port"
|
|
|
|
@impl Mix.Task
|
|
def run(args) do
|
|
{opts, _, _} = OptionParser.parse(args, strict: [port: :integer])
|
|
requested_port = Keyword.get(opts, :port)
|
|
|
|
# Check for existing server via .nrepl-port
|
|
if requested_port == nil and File.exists?(@nrepl_port_file) do
|
|
case check_existing_server() do
|
|
{:ok, existing_port} ->
|
|
IO.puts("nREPL server already running on port #{existing_port}")
|
|
IO.puts("Connect with: nrepl://127.0.0.1:#{existing_port}")
|
|
System.halt(0)
|
|
|
|
:stale ->
|
|
File.rm(@nrepl_port_file)
|
|
end
|
|
end
|
|
|
|
port = requested_port || 0
|
|
|
|
Mix.Task.run("compile")
|
|
Mix.Task.run("app.start")
|
|
|
|
{:ok, server} = CljElixir.NRepl.Server.start_link(port: port)
|
|
actual_port = CljElixir.NRepl.Server.port(server)
|
|
|
|
File.write!(@nrepl_port_file, Integer.to_string(actual_port))
|
|
|
|
# Clean up .nrepl-port on shutdown
|
|
cleanup_on_exit()
|
|
|
|
IO.puts("nREPL server started on port #{actual_port} on host 127.0.0.1")
|
|
IO.puts("Port written to #{@nrepl_port_file}")
|
|
IO.puts("Press Ctrl+C to stop\n")
|
|
|
|
Process.sleep(:infinity)
|
|
end
|
|
|
|
defp check_existing_server do
|
|
with {:ok, content} <- File.read(@nrepl_port_file),
|
|
{port, ""} <- Integer.parse(String.trim(content)),
|
|
true <- port_alive?(port) do
|
|
{:ok, port}
|
|
else
|
|
_ -> :stale
|
|
end
|
|
end
|
|
|
|
defp port_alive?(port) do
|
|
case :gen_tcp.connect(~c"127.0.0.1", port, [:binary], 500) do
|
|
{:ok, socket} ->
|
|
:gen_tcp.close(socket)
|
|
true
|
|
|
|
{:error, _} ->
|
|
false
|
|
end
|
|
end
|
|
|
|
defp cleanup_on_exit do
|
|
# Trap exits so we can clean up the port file
|
|
System.at_exit(fn _status ->
|
|
File.rm(@nrepl_port_file)
|
|
end)
|
|
end
|
|
end
|