diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a06c4fe..42c1fecc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## v2.5.2-dev +### Enhancements + +* Print warnings to standard error instead of standard output, keeping stdout clean for machine-readable output such as `mix hex.outdated --json`. Warning-colored lines that are part of a command's regular output, such as retirement notices in `mix hex.info` and the `mix deps.get` dependency listing, remain on standard output + ## v2.5.1 (2026-07-09) ### Enhancements diff --git a/lib/hex/remote_converger.ex b/lib/hex/remote_converger.ex index 1405ae87..3741ca67 100644 --- a/lib/hex/remote_converger.ex +++ b/lib/hex/remote_converger.ex @@ -660,7 +660,10 @@ defmodule Hex.RemoteConverger do Hex.Shell.info(Hex.Shell.format(line)) if retired do - Hex.Shell.warn(" #{Hex.Utils.package_retirement_message(retired)}") + # Part of the stdout dependency listing, so only warning-colored + # instead of Hex.Shell.warn/1 which prints to stderr. + retirement_message = " #{Hex.Utils.package_retirement_message(retired)}" + Hex.Shell.info(Hex.Shell.format([:yellow, retirement_message, :reset])) end advisories diff --git a/lib/hex/shell.ex b/lib/hex/shell.ex index 3f2ce948..d3ea0343 100644 --- a/lib/hex/shell.ex +++ b/lib/hex/shell.ex @@ -6,9 +6,11 @@ defmodule Hex.Shell do Mix.shell().info(output) end + # Warnings are diagnostics, so print them to stderr, keeping stdout + # clean for machine-readable output such as `mix hex.outdated --json`. def warn(output) do validate_output!(output) - Mix.shell().info([IO.ANSI.yellow(), output, IO.ANSI.reset()]) + Mix.shell().error([IO.ANSI.yellow(), output, IO.ANSI.reset()]) end def error(output) do diff --git a/lib/mix/tasks/hex.audit.ex b/lib/mix/tasks/hex.audit.ex index aa9fda7a..53ca1037 100644 --- a/lib/mix/tasks/hex.audit.ex +++ b/lib/mix/tasks/hex.audit.ex @@ -217,7 +217,7 @@ defmodule Mix.Tasks.Hex.Audit do :ok warnings -> - Hex.Shell.info("") + Hex.Shell.warn("") Enum.each(warnings, &Hex.Shell.warn/1) end end diff --git a/lib/mix/tasks/hex.info.ex b/lib/mix/tasks/hex.info.ex index 6e4676d0..251af248 100644 --- a/lib/mix/tasks/hex.info.ex +++ b/lib/mix/tasks/hex.info.ex @@ -249,11 +249,17 @@ defmodule Mix.Tasks.Hex.Info do message: release["retirement"]["message"] } - Hex.Shell.warn([ - [:bright, "This version has been retired"], - [:normal, ": "], - [:normal, Hex.Utils.package_retirement_message(retirement)] - ]) + # Part of the stdout package report, so only warning-colored instead + # of Hex.Shell.warn/1 which prints to stderr. + Hex.Shell.info( + Hex.Shell.format([ + :yellow, + [:bright, "This version has been retired"], + [:normal, ": "], + [:normal, Hex.Utils.package_retirement_message(retirement)], + :reset + ]) + ) end defp print_publisher(release) do diff --git a/test/hex/mix_task_test.exs b/test/hex/mix_task_test.exs index 312287ee..b1dc3391 100644 --- a/test/hex/mix_task_test.exs +++ b/test/hex/mix_task_test.exs @@ -912,7 +912,7 @@ defmodule Hex.MixTaskTest do Mix.Task.run("deps.get") - assert_received {:mix_shell, :info, + assert_received {:mix_shell, :error, ["\e[33mex_doc is missing its version requirement, use \">= 0.0.0\"" <> _]} end) end @@ -925,10 +925,10 @@ defmodule Hex.MixTaskTest do Mix.Task.run("deps.get") - assert_received {:mix_shell, :info, + assert_received {:mix_shell, :error, ["\e[33mex_doc is missing its version requirement, use \">= 0.0.0\"" <> _]} - assert_received {:mix_shell, :info, + assert_received {:mix_shell, :error, ["\e[33mex_doc is using unknown options: :dir, :typo\e[0m"]} end) end diff --git a/test/hex/mix_test.exs b/test/hex/mix_test.exs index 16611d1c..83664ff2 100644 --- a/test/hex/mix_test.exs +++ b/test/hex/mix_test.exs @@ -13,7 +13,7 @@ defmodule Hex.MixTest do test "from_lock/1 warns on newer lock versions" do message = - {:mix_shell, :info, + {:mix_shell, :error, [ "\e[33mThe mix.lock file was generated with a newer version of Hex. " <> "Update your client by running `mix local.hex` to avoid losing data.\e[0m" diff --git a/test/mix/tasks/hex.build_test.exs b/test/mix/tasks/hex.build_test.exs index 8d24f254..2411263c 100644 --- a/test/mix/tasks/hex.build_test.exs +++ b/test/mix/tasks/hex.build_test.exs @@ -47,7 +47,7 @@ defmodule Mix.Tasks.Hex.BuildTest do File.write!("myfile.txt", "hello") Mix.Tasks.Hex.Build.run([]) - assert_received {:mix_shell, :info, ["\e[33m\nYou have not included any licenses\n\e[0m"]} + assert_received {:mix_shell, :error, ["\e[33m\nYou have not included any licenses\n\e[0m"]} assert package_created?("release_missing_licenses-0.0.1") end) after @@ -63,7 +63,7 @@ defmodule Mix.Tasks.Hex.BuildTest do File.write!("myfile.txt", "hello") Mix.Tasks.Hex.Build.run([]) - assert_received {:mix_shell, :info, + assert_received {:mix_shell, :error, [ "\e[33mThe following licenses are not recognized by SPDX:\n * CustomLicense\n\nValid license identifiers are available from https://spdx.org/licenses\e[0m" ]} @@ -480,7 +480,7 @@ defmodule Mix.Tasks.Hex.BuildTest do "\e[33mMix project configuration :organization belongs under the :package key, " <> "did you misplace it?\e[0m" - assert_received {:mix_shell, :info, [^message]} + assert_received {:mix_shell, :error, [^message]} end) after purge([ReleaseOrganizationWrongLocation.MixProject])