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>
This commit is contained in:
2026-03-08 11:15:55 -04:00
parent 7e82efd7ec
commit 5cbc493cc5
3 changed files with 82 additions and 3 deletions
+1
View File
@@ -8,3 +8,4 @@ erl_crash.dump
*.beam
/tmp/
.elixir_ls/
.nrepl-port
+27
View File
@@ -0,0 +1,27 @@
{:tasks
{compile {:doc "Compile all .clje and .ex files"
:task (shell "mix compile")}
test {:doc "Run all tests"
:task (shell "mix test")}
test:trace {:doc "Run all tests with trace output"
:task (shell "mix test --trace")}
repl {:doc "Start interactive CljElixir REPL"
:task (shell "rlwrap mix clje.repl")}
repl:basic {:doc "Start REPL without rlwrap"
:task (shell "mix clje.repl")}
nrepl {:doc "Start nREPL server (random port)"
:task (shell "mix clje.nrepl")}
nrepl:port {:doc "Start nREPL on port 7888"
:task (shell "mix clje.nrepl --port 7888")}
clean {:doc "Clean build artifacts"
:task (shell "mix clean")}
fmt {:doc "Format Elixir source files"
:task (shell "mix format")}}}
+54 -3
View File
@@ -8,16 +8,36 @@ defmodule Mix.Tasks.Clje.Nrepl do
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])
port = Keyword.get(opts, :port, 0)
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")
@@ -25,12 +45,43 @@ defmodule Mix.Tasks.Clje.Nrepl do
{:ok, server} = CljElixir.NRepl.Server.start_link(port: port)
actual_port = CljElixir.NRepl.Server.port(server)
File.write!(".nrepl-port", Integer.to_string(actual_port))
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")
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