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:
@@ -8,3 +8,4 @@ erl_crash.dump
|
||||
*.beam
|
||||
/tmp/
|
||||
.elixir_ls/
|
||||
.nrepl-port
|
||||
|
||||
@@ -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")}}}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user