From a04147a8b44a89a07ff426d2581d00aef75d2f55 Mon Sep 17 00:00:00 2001 From: Daniel Andrews Date: Sat, 15 Mar 2025 20:49:40 -0500 Subject: [PATCH 1/4] chore: Remove unrelated code, clean up all the things --- CHANGELOG.md | 15 +++++ README.md | 18 ++++- lib/nodejs/worker.ex | 32 ++++++--- test/js/debug_test/debug_logger.js | 10 +++ test/nodejs_debug_mode_test.exs | 105 +++++++++++++++++++++++++++++ 5 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 test/js/debug_test/debug_logger.js create mode 100644 test/nodejs_debug_mode_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 188ee8c..1a2349c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [3.1.3] + +### Added +- Added `debug_mode` configuration option to handle Node.js stdout/stderr messages +- Implemented proper `handle_info/2` callback to handle messages from Node.js processes +- Added safeguards to reset_terminal to prevent errors during termination with invalid ports + +### Fixed +- Fixed "unexpected message in handle_info/2" errors when Node.js emits debug messages +- Fixed potential crashes during termination when a port becomes invalid + +### Contributors +- @francois-codes for the initial implementation +- Revelry team for refinements + ## [3.1.2] ### Changed diff --git a/README.md b/README.md index 07b17ce..df81bea 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,22 @@ directory containing your JavaScript modules. supervisor(NodeJS, [[path: "/node_app_root", pool_size: 4]]) ``` +### Debug Mode + +When working with Node.js applications, you may encounter debug messages or warnings from the Node.js runtime, especially when using inspector or debugging tools. To properly handle these messages: + +```elixir +# In your config/dev.exs or other appropriate config file +config :nodejs, debug_mode: true +``` + +When `debug_mode` is enabled: +- Node.js stdout/stderr messages will be logged at the info level +- Messages like "Debugger listening on..." will not cause errors +- All Node.js processes will log their output through Elixir's Logger + +This is particularly useful during development or when debugging Node.js integration issues. + ### Calling JavaScript module functions with `NodeJS.call(module, args \\ [])`. If the module exports a function directly, like this: @@ -111,4 +127,4 @@ module.exports = async function echo(x, delay = 1000) { } ``` -https://github.com/revelrylabs/elixir-nodejs/blob/master/test/js/slow-async-echo.js \ No newline at end of file +https://github.com/revelrylabs/elixir-nodejs/blob/master/test/js/slow-async-echo.js diff --git a/lib/nodejs/worker.ex b/lib/nodejs/worker.ex index cb8dc40..fad7bec 100644 --- a/lib/nodejs/worker.ex +++ b/lib/nodejs/worker.ex @@ -124,21 +124,28 @@ defmodule NodeJS.Worker do end end - defp env do - Mix.env() - rescue - _ -> :release + # Determines if debug mode is enabled via application configuration + defp debug_mode? do + Application.get_env(:nodejs, :debug_mode, false) end - def handle_info({_pid, data}, state) do - with :dev <- env(), - {_, {:eol, msg}} <- data do + # Handles any messages from the Node.js process + # When debug_mode is enabled, these messages (like Node.js debug info) + # will be logged at info level + @doc false + def handle_info({_pid, {:data, {_flag, msg}} = data}, state) do + if debug_mode?() do Logger.info("NodeJS: #{msg}") end {:noreply, state} end + # Catch-all handler for other messages + def handle_info(_message, state) do + {:noreply, state} + end + defp decode(data) do data |> to_string() @@ -149,9 +156,16 @@ defmodule NodeJS.Worker do end end + # Safely resets the terminal, handling potential errors if + # the port is already closed or invalid defp reset_terminal(port) do - Port.command(port, "\x1b[0m\x1b[?7h\x1b[?25h\x1b[H\x1b[2J") - Port.command(port, "\x1b[!p\x1b[?47l") + try do + Port.command(port, "\x1b[0m\x1b[?7h\x1b[?25h\x1b[H\x1b[2J") + Port.command(port, "\x1b[!p\x1b[?47l") + rescue + _ -> + Logger.debug("NodeJS: Could not reset terminal - port may be closed") + end end @doc false diff --git a/test/js/debug_test/debug_logger.js b/test/js/debug_test/debug_logger.js new file mode 100644 index 0000000..99fbefd --- /dev/null +++ b/test/js/debug_test/debug_logger.js @@ -0,0 +1,10 @@ +// This file outputs debugging information to stdout +console.log("Debug message: Module loading"); +console.debug("Debug message: Initializing module"); + +module.exports = function testFunction(input) { + console.log(`Debug message: Function called with input: ${input}`); + console.debug("Debug message: Processing input"); + + return `Processed: ${input}`; +}; diff --git a/test/nodejs_debug_mode_test.exs b/test/nodejs_debug_mode_test.exs new file mode 100644 index 0000000..f1b186e --- /dev/null +++ b/test/nodejs_debug_mode_test.exs @@ -0,0 +1,105 @@ +defmodule NodeJS.DebugModeTest do + use ExUnit.Case + import ExUnit.CaptureLog + + describe "debug_mode functionality" do + test "logs Node.js stdout messages when debug_mode is enabled" do + defmodule TestDebugHandler do + use GenServer + require Logger + + def start_link do + GenServer.start_link(__MODULE__, nil) + end + + def init(_) do + {:ok, nil} + end + + def debug_mode? do + Application.get_env(:nodejs, :debug_mode, false) + end + + # The implementation from worker.ex + def handle_info({_pid, {:data, {_flag, msg}} = _data}, state) do + if debug_mode?() do + Logger.info("NodeJS: #{msg}") + end + + {:noreply, state} + end + end + + # Start the test process + {:ok, pid} = TestDebugHandler.start_link() + + # Test with debug_mode disabled (default) + log_without_debug = + capture_log(fn -> + # Simulate debugger message from Node.js + send(pid, {self(), {:data, {:eol, "Debugger listening on ws://127.0.0.1:9229/abc123"}}}) + # Wait for any potential logging + Process.sleep(50) + end) + + # Verify no logging occurred + refute log_without_debug =~ "NodeJS: Debugger listening" + + # Enable debug_mode + Application.put_env(:nodejs, :debug_mode, true) + + # Test with debug_mode enabled + log_with_debug = + capture_log(fn -> + # Simulate debugger message from Node.js + send(pid, {self(), {:data, {:eol, "Debugger listening on ws://127.0.0.1:9229/abc123"}}}) + # Wait for any potential logging + Process.sleep(50) + end) + + # Clean up + Application.delete_env(:nodejs, :debug_mode) + + # Verify logging occurred + assert log_with_debug =~ "NodeJS: Debugger listening" + end + end + + describe "port safety" do + test "reset_terminal handles invalid ports gracefully" do + defmodule TestPortHandler do + use GenServer + require Logger + + def start_link do + GenServer.start_link(__MODULE__, nil) + end + + def init(_) do + {:ok, nil} + end + + # The implementation from worker.ex + def reset_terminal(port) do + try do + Port.command(port, "\x1b[0m\x1b[?7h\x1b[?25h\x1b[H\x1b[2J") + Port.command(port, "\x1b[!p\x1b[?47l") + rescue + _ -> + Logger.debug("NodeJS: Could not reset terminal - port may be closed") + end + end + end + + log = + capture_log(fn -> + # Try to reset an invalid port + TestPortHandler.reset_terminal(:invalid_port) + Process.sleep(50) + end) + + # Verify proper error handling + assert log =~ "NodeJS: Could not reset terminal" + end + end +end From db2a3b99708c1a98123e0e84c365625729ea17c3 Mon Sep 17 00:00:00 2001 From: Daniel Andrews Date: Sat, 15 Mar 2025 20:51:49 -0500 Subject: [PATCH 2/4] chore: :zap: --- lib/nodejs/worker.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nodejs/worker.ex b/lib/nodejs/worker.ex index fad7bec..199a2c7 100644 --- a/lib/nodejs/worker.ex +++ b/lib/nodejs/worker.ex @@ -133,7 +133,7 @@ defmodule NodeJS.Worker do # When debug_mode is enabled, these messages (like Node.js debug info) # will be logged at info level @doc false - def handle_info({_pid, {:data, {_flag, msg}} = data}, state) do + def handle_info({_pid, {:data, {_flag, msg}}}, state) do if debug_mode?() do Logger.info("NodeJS: #{msg}") end From b91f0ade20c5a23c7bb4e1c7a5cf032ac127ea70 Mon Sep 17 00:00:00 2001 From: Daniel Andrews Date: Sat, 15 Mar 2025 23:55:31 -0500 Subject: [PATCH 3/4] chore: Better tests --- test/js/debug_test/debug_logger.js | 10 --- test/nodejs_debug_mode_test.exs | 105 ----------------------------- test/nodejs_test.exs | 41 +++++++++++ 3 files changed, 41 insertions(+), 115 deletions(-) delete mode 100644 test/js/debug_test/debug_logger.js delete mode 100644 test/nodejs_debug_mode_test.exs diff --git a/test/js/debug_test/debug_logger.js b/test/js/debug_test/debug_logger.js deleted file mode 100644 index 99fbefd..0000000 --- a/test/js/debug_test/debug_logger.js +++ /dev/null @@ -1,10 +0,0 @@ -// This file outputs debugging information to stdout -console.log("Debug message: Module loading"); -console.debug("Debug message: Initializing module"); - -module.exports = function testFunction(input) { - console.log(`Debug message: Function called with input: ${input}`); - console.debug("Debug message: Processing input"); - - return `Processed: ${input}`; -}; diff --git a/test/nodejs_debug_mode_test.exs b/test/nodejs_debug_mode_test.exs deleted file mode 100644 index f1b186e..0000000 --- a/test/nodejs_debug_mode_test.exs +++ /dev/null @@ -1,105 +0,0 @@ -defmodule NodeJS.DebugModeTest do - use ExUnit.Case - import ExUnit.CaptureLog - - describe "debug_mode functionality" do - test "logs Node.js stdout messages when debug_mode is enabled" do - defmodule TestDebugHandler do - use GenServer - require Logger - - def start_link do - GenServer.start_link(__MODULE__, nil) - end - - def init(_) do - {:ok, nil} - end - - def debug_mode? do - Application.get_env(:nodejs, :debug_mode, false) - end - - # The implementation from worker.ex - def handle_info({_pid, {:data, {_flag, msg}} = _data}, state) do - if debug_mode?() do - Logger.info("NodeJS: #{msg}") - end - - {:noreply, state} - end - end - - # Start the test process - {:ok, pid} = TestDebugHandler.start_link() - - # Test with debug_mode disabled (default) - log_without_debug = - capture_log(fn -> - # Simulate debugger message from Node.js - send(pid, {self(), {:data, {:eol, "Debugger listening on ws://127.0.0.1:9229/abc123"}}}) - # Wait for any potential logging - Process.sleep(50) - end) - - # Verify no logging occurred - refute log_without_debug =~ "NodeJS: Debugger listening" - - # Enable debug_mode - Application.put_env(:nodejs, :debug_mode, true) - - # Test with debug_mode enabled - log_with_debug = - capture_log(fn -> - # Simulate debugger message from Node.js - send(pid, {self(), {:data, {:eol, "Debugger listening on ws://127.0.0.1:9229/abc123"}}}) - # Wait for any potential logging - Process.sleep(50) - end) - - # Clean up - Application.delete_env(:nodejs, :debug_mode) - - # Verify logging occurred - assert log_with_debug =~ "NodeJS: Debugger listening" - end - end - - describe "port safety" do - test "reset_terminal handles invalid ports gracefully" do - defmodule TestPortHandler do - use GenServer - require Logger - - def start_link do - GenServer.start_link(__MODULE__, nil) - end - - def init(_) do - {:ok, nil} - end - - # The implementation from worker.ex - def reset_terminal(port) do - try do - Port.command(port, "\x1b[0m\x1b[?7h\x1b[?25h\x1b[H\x1b[2J") - Port.command(port, "\x1b[!p\x1b[?47l") - rescue - _ -> - Logger.debug("NodeJS: Could not reset terminal - port may be closed") - end - end - end - - log = - capture_log(fn -> - # Try to reset an invalid port - TestPortHandler.reset_terminal(:invalid_port) - Process.sleep(50) - end) - - # Verify proper error handling - assert log =~ "NodeJS: Could not reset terminal" - end - end -end diff --git a/test/nodejs_test.exs b/test/nodejs_test.exs index 928e180..713e5f3 100644 --- a/test/nodejs_test.exs +++ b/test/nodejs_test.exs @@ -277,4 +277,45 @@ defmodule NodeJS.Test do assert Enum.all?(results, &match?({:ok, "clean output"}, &1)) end end + + describe "debug mode" do + test "handles debug messages without crashing" do + File.mkdir_p!("test/js/debug_test") + + File.write!("test/js/debug_test/debug_logger.js", """ + // This file outputs debugging information to stdout + console.log("Debug message: Module loading"); + console.debug("Debug message: Initializing module"); + + module.exports = function testFunction(input) { + console.log(`Debug message: Function called with input: ${input}`); + console.debug("Debug message: Processing input"); + + return `Processed: ${input}`; + }; + """) + + # With debug_mode disabled, function still works despite debug output + result = NodeJS.call("debug_test/debug_logger", ["test input"]) + assert {:ok, "Processed: test input"} = result + + # Enable debug_mode to verify it works in that mode too + original_setting = Application.get_env(:nodejs, :debug_mode) + Application.put_env(:nodejs, :debug_mode, true) + + # Function still works with debug_mode enabled + result = NodeJS.call("debug_test/debug_logger", ["test input"]) + assert {:ok, "Processed: test input"} = result + + # Restore original setting + if is_nil(original_setting) do + Application.delete_env(:nodejs, :debug_mode) + else + Application.put_env(:nodejs, :debug_mode, original_setting) + end + + # Clean up + File.rm!("test/js/debug_test/debug_logger.js") + end + end end From 29ed17ac1ae136243aca80f3632ac305d599d479 Mon Sep 17 00:00:00 2001 From: Daniel Andrews Date: Sat, 15 Mar 2025 23:56:46 -0500 Subject: [PATCH 4/4] chore: Bump patch version --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 6a779f2..4fb1eeb 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule NodeJS.MixProject do def project do [ app: :nodejs, - version: "3.1.2", + version: "3.1.3", elixir: "~> 1.12", start_permanent: Mix.env() == :prod, deps: deps(),