Skip to content

Commit 5a2ddaa

Browse files
committed
Print warnings to stderr instead of stdout
Hex.Shell.warn/1 wrote to stdout via Mix.shell().info/1, so warnings like the expired-authentication notice or the stale mix.lock version warning polluted machine-readable stdout output such as `mix hex.outdated --json`. Route warn/1 through Mix.shell().error/1, which prints to stderr, keeping the yellow warning color. Warning-colored lines that are part of a command's regular stdout output (the retirement reason in the `mix deps.get` dependency listing and the retirement notice in `mix hex.info`) stay on stdout and are now printed as yellow-formatted info lines instead.
1 parent d61c28a commit 5a2ddaa

8 files changed

Lines changed: 30 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## v2.5.2-dev
44

5+
### Enhancements
6+
7+
* 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
8+
59
## v2.5.1 (2026-07-09)
610

711
### Enhancements

lib/hex/remote_converger.ex

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,10 @@ defmodule Hex.RemoteConverger do
660660
Hex.Shell.info(Hex.Shell.format(line))
661661

662662
if retired do
663-
Hex.Shell.warn(" #{Hex.Utils.package_retirement_message(retired)}")
663+
# Part of the stdout dependency listing, so only warning-colored
664+
# instead of Hex.Shell.warn/1 which prints to stderr.
665+
retirement_message = " #{Hex.Utils.package_retirement_message(retired)}"
666+
Hex.Shell.info(Hex.Shell.format([:yellow, retirement_message, :reset]))
664667
end
665668

666669
advisories

lib/hex/shell.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ defmodule Hex.Shell do
66
Mix.shell().info(output)
77
end
88

9+
# Warnings are diagnostics, so print them to stderr, keeping stdout
10+
# clean for machine-readable output such as `mix hex.outdated --json`.
911
def warn(output) do
1012
validate_output!(output)
11-
Mix.shell().info([IO.ANSI.yellow(), output, IO.ANSI.reset()])
13+
Mix.shell().error([IO.ANSI.yellow(), output, IO.ANSI.reset()])
1214
end
1315

1416
def error(output) do

lib/mix/tasks/hex.audit.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ defmodule Mix.Tasks.Hex.Audit do
217217
:ok
218218

219219
warnings ->
220-
Hex.Shell.info("")
220+
Hex.Shell.warn("")
221221
Enum.each(warnings, &Hex.Shell.warn/1)
222222
end
223223
end

lib/mix/tasks/hex.info.ex

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,17 @@ defmodule Mix.Tasks.Hex.Info do
249249
message: release["retirement"]["message"]
250250
}
251251

252-
Hex.Shell.warn([
253-
[:bright, "This version has been retired"],
254-
[:normal, ": "],
255-
[:normal, Hex.Utils.package_retirement_message(retirement)]
256-
])
252+
# Part of the stdout package report, so only warning-colored instead
253+
# of Hex.Shell.warn/1 which prints to stderr.
254+
Hex.Shell.info(
255+
Hex.Shell.format([
256+
:yellow,
257+
[:bright, "This version has been retired"],
258+
[:normal, ": "],
259+
[:normal, Hex.Utils.package_retirement_message(retirement)],
260+
:reset
261+
])
262+
)
257263
end
258264

259265
defp print_publisher(release) do

test/hex/mix_task_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@ defmodule Hex.MixTaskTest do
912912

913913
Mix.Task.run("deps.get")
914914

915-
assert_received {:mix_shell, :info,
915+
assert_received {:mix_shell, :error,
916916
["\e[33mex_doc is missing its version requirement, use \">= 0.0.0\"" <> _]}
917917
end)
918918
end
@@ -925,10 +925,10 @@ defmodule Hex.MixTaskTest do
925925

926926
Mix.Task.run("deps.get")
927927

928-
assert_received {:mix_shell, :info,
928+
assert_received {:mix_shell, :error,
929929
["\e[33mex_doc is missing its version requirement, use \">= 0.0.0\"" <> _]}
930930

931-
assert_received {:mix_shell, :info,
931+
assert_received {:mix_shell, :error,
932932
["\e[33mex_doc is using unknown options: :dir, :typo\e[0m"]}
933933
end)
934934
end

test/hex/mix_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ defmodule Hex.MixTest do
1313

1414
test "from_lock/1 warns on newer lock versions" do
1515
message =
16-
{:mix_shell, :info,
16+
{:mix_shell, :error,
1717
[
1818
"\e[33mThe mix.lock file was generated with a newer version of Hex. " <>
1919
"Update your client by running `mix local.hex` to avoid losing data.\e[0m"

test/mix/tasks/hex.build_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ defmodule Mix.Tasks.Hex.BuildTest do
4747
File.write!("myfile.txt", "hello")
4848
Mix.Tasks.Hex.Build.run([])
4949

50-
assert_received {:mix_shell, :info, ["\e[33m\nYou have not included any licenses\n\e[0m"]}
50+
assert_received {:mix_shell, :error, ["\e[33m\nYou have not included any licenses\n\e[0m"]}
5151
assert package_created?("release_missing_licenses-0.0.1")
5252
end)
5353
after
@@ -63,7 +63,7 @@ defmodule Mix.Tasks.Hex.BuildTest do
6363
File.write!("myfile.txt", "hello")
6464
Mix.Tasks.Hex.Build.run([])
6565

66-
assert_received {:mix_shell, :info,
66+
assert_received {:mix_shell, :error,
6767
[
6868
"\e[33mThe following licenses are not recognized by SPDX:\n * CustomLicense\n\nValid license identifiers are available from https://spdx.org/licenses\e[0m"
6969
]}
@@ -480,7 +480,7 @@ defmodule Mix.Tasks.Hex.BuildTest do
480480
"\e[33mMix project configuration :organization belongs under the :package key, " <>
481481
"did you misplace it?\e[0m"
482482

483-
assert_received {:mix_shell, :info, [^message]}
483+
assert_received {:mix_shell, :error, [^message]}
484484
end)
485485
after
486486
purge([ReleaseOrganizationWrongLocation.MixProject])

0 commit comments

Comments
 (0)