Skip to content

Instantly share code, notes, and snippets.

@liyu1981
Created May 29, 2025 23:17
Show Gist options
  • Save liyu1981/4be0ee071da7d96f616d8d9fffd4901f to your computer and use it in GitHub Desktop.
Save liyu1981/4be0ee071da7d96f616d8d9fffd4901f to your computer and use it in GitHub Desktop.
Calculate deps which can upgraded in a mix project

useage

please this file in mix/tasks/

then

mix calc_deps_upgraded

will see something like

WARNING: mix hex.outdated failed with exit code 1, be caution when upgrade

🔁 Updated deps list (to be used in mix.exs):

{:aws, [git: "https://github.com/aws-beam/aws-elixir.git"]},
{:cachex, "~> 4.1.0"},
{:credo, "~> 1.7.12", [only: [:dev, :test], runtime: false]},
{:dialyxir, "~> 1.4.5", [only: [:dev, :test], runtime: false]},
{:earmark, "~> 1.4.47"},
{:ecto_sql, "~> 3.12.1"},
{:elixir_auth_google, "~> 1.6.5"},
{:elixir_uuid, "~> 1.2.1"},
{:esbuild, "~> 0.5", [runtime: true]},
{:ex_openai, ">=  1.8.0"},
{:finch, "~> 0.19.0"},
{:floki, ">= 0.30.0", [only: :test]},
{:gettext, "~> 0.20"},
{:goth, "~> 1.4.5"},
{:hackney, "~> 1.18"},
{:httpoison, "~> 2.2.3", [override: true]},
{:jason, "~> 1.2"},
{:phoenix_ecto, "~> 4.6.4"},
{:phoenix_html, "~> 4.2.1"},
{:phoenix_live_dashboard, "~> 0.8.7"},
{:phoenix_live_reload, "~> 1.2", [only: :dev]},
{:phoenix_live_view, "~> 1.0.11"},
{:phoenix, "~> 1.7.21"},
{:plug_cowboy, "~> 2.5"},
{:postgrex, ">= 0.20.0"},
{:swoosh, "~> 1.3"},
{:tailwind, "~> 0.2", [runtime: true]},
{:telemetry_metrics, "~> 1.1.0"},
{:telemetry_poller, "~> 1.0"},
{:tesla, "~> 1.4"},
{:tokenizers, "~> 0.5.1"},
{:dotenvy, "~> 1.0.0"},
{:deno_rider, "~> 0.2"},
{:poolboy, "~> 1.5"},
{:openai_ex, "~> 0.9.10"}

then can copy the output to mix.exs

defmodule Mix.Tasks.CalcDepsUpgraded do
use Mix.Task
@shortdoc "Upgrades dependencies in mix.exs"
def run(_) do
project = Mix.Project.config()
deps = project[:deps]
# IO.inspect(deps)
{output, exit_code} = System.cmd("mix", ["hex.outdated"])
IO.inspect({output, exit_code})
if exit_code != 0 do
IO.puts(
:stderr,
IO.ANSI.red() <>
"WARNING: mix hex.outdated failed with exit code #{exit_code}, be caution when upgrade" <>
IO.ANSI.reset()
)
end
# IO.puts(output)
outdated_deps = parse_outdated_output(output)
# IO.inspect(outdated_deps, label: "Parsed outdated dependencies")
new_deps =
Enum.map(deps, fn
{name, opts} = original when is_atom(name) and is_list(opts) ->
case find_latest_version(name, outdated_deps) do
nil ->
original
latest ->
# Replace version string in opts
updated_opts = Keyword.put(opts, :version, latest)
{name, updated_opts}
end
{name, version} = original when is_atom(name) and is_binary(version) ->
case find_latest_version(name, outdated_deps) do
nil ->
original
latest ->
{name, latest}
end
other ->
other
end)
IO.puts("\n🔁 Updated deps list (to be used in mix.exs):\n")
Enum.with_index(new_deps)
|> Enum.each(fn
{dep, idx} when idx < length(new_deps) - 1 ->
IO.puts("#{inspect(dep)},")
{dep, _idx} ->
IO.puts("#{inspect(dep)}")
end)
end
defp parse_outdated_output(output) do
output
|> String.split("\n")
|> Enum.filter(&String.match?(&1, ~r/^\s*\S+/))
|> Enum.map(fn line ->
# Match lines like:
# > plug 1.14.2 1.15.0 Minor
Regex.run(~r/^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/, line, capture: :all_but_first)
end)
|> Enum.reject(&is_nil/1)
|> Enum.map(fn [name, current, latest, level] ->
%{
name: name,
current: current,
latest: latest,
level: level
}
end)
end
defp find_latest_version(dep_name, outdated_list) do
case Enum.find(outdated_list, fn %{name: name} -> name == dep_name end) do
nil -> nil
%{latest: latest} -> latest
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment