Files
Adam 5cbc493cc5 Add bb.edn runner scripts and improve nREPL port handling
- 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>
2026-03-08 11:15:55 -04:00

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