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
|
*.beam
|
||||||
/tmp/
|
/tmp/
|
||||||
.elixir_ls/
|
.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
|
mix clje.nrepl --port 7888 # specific port
|
||||||
|
|
||||||
Writes port to `.nrepl-port` file for editor auto-discovery.
|
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
|
use Mix.Task
|
||||||
|
|
||||||
@shortdoc "Start a CljElixir nREPL server"
|
@shortdoc "Start a CljElixir nREPL server"
|
||||||
|
@nrepl_port_file ".nrepl-port"
|
||||||
|
|
||||||
@impl Mix.Task
|
@impl Mix.Task
|
||||||
def run(args) do
|
def run(args) do
|
||||||
{opts, _, _} = OptionParser.parse(args, strict: [port: :integer])
|
{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("compile")
|
||||||
Mix.Task.run("app.start")
|
Mix.Task.run("app.start")
|
||||||
@@ -25,12 +45,43 @@ defmodule Mix.Tasks.Clje.Nrepl do
|
|||||||
{:ok, server} = CljElixir.NRepl.Server.start_link(port: port)
|
{:ok, server} = CljElixir.NRepl.Server.start_link(port: port)
|
||||||
actual_port = CljElixir.NRepl.Server.port(server)
|
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("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")
|
IO.puts("Press Ctrl+C to stop\n")
|
||||||
|
|
||||||
Process.sleep(:infinity)
|
Process.sleep(:infinity)
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user