Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion lib/hex/remote_converger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion lib/hex/shell.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/mix/tasks/hex.audit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 11 additions & 5 deletions lib/mix/tasks/hex.info.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions test/hex/mix_task_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/hex/mix_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 3 additions & 3 deletions test/mix/tasks/hex.build_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
]}
Expand Down Expand Up @@ -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])
Expand Down
Loading