From fa2cb627eae071864d7a756a2b7ffc0a7de58348 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Wed, 5 Nov 2025 19:48:08 +0100 Subject: [PATCH 1/9] `mix help app:APP`: Always load all Elixir built-in apps Before this patch it was basically impossible to do e.g.: `mix help app:ex_unit`. --- lib/mix/lib/mix/tasks/help.ex | 4 ++++ lib/mix/test/mix/tasks/help_test.exs | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/mix/lib/mix/tasks/help.ex b/lib/mix/lib/mix/tasks/help.ex index fe442239608..aad57cf864a 100644 --- a/lib/mix/lib/mix/tasks/help.ex +++ b/lib/mix/lib/mix/tasks/help.ex @@ -114,6 +114,10 @@ defmodule Mix.Tasks.Help do loadpaths!() app = String.to_atom(app) + if app in [:eex, :elixir, :ex_unit, :iex, :logger, :mix] do + Application.ensure_loaded(app) + end + if modules = Application.spec(app, :modules) do for module <- modules, not (module |> Atom.to_string() |> String.starts_with?("Elixir.Mix.Tasks.")), diff --git a/lib/mix/test/mix/tasks/help_test.exs b/lib/mix/test/mix/tasks/help_test.exs index 3d4f3a3a00a..de8157756bc 100644 --- a/lib/mix/test/mix/tasks/help_test.exs +++ b/lib/mix/test/mix/tasks/help_test.exs @@ -206,6 +206,21 @@ defmodule Mix.Tasks.HelpTest do end) end + test "help Elixir stdlib app", context do + apps = for {app, _, _} <- Application.loaded_applications(), do: app + assert :mix in apps + refute :iex in apps + + in_tmp(context.test, fn -> + output = + capture_io(fn -> + Mix.Tasks.Help.run(["app:iex"]) + end) + + assert output =~ "# IEx\n\nElixir's interactive shell." + end) + end + test "help unknown app:APP", context do in_tmp(context.test, fn -> Mix.Tasks.Help.run(["app:foobar"]) From f52edbbe4e59d73525b77ee6e72b0b83fe13929b Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Wed, 5 Nov 2025 23:14:54 +0100 Subject: [PATCH 2/9] Support OTP apps too --- lib/mix/lib/mix/tasks/help.ex | 7 ++---- lib/mix/test/mix/tasks/help_test.exs | 33 ++++++++++++---------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/lib/mix/lib/mix/tasks/help.ex b/lib/mix/lib/mix/tasks/help.ex index aad57cf864a..b8d59da72f2 100644 --- a/lib/mix/lib/mix/tasks/help.ex +++ b/lib/mix/lib/mix/tasks/help.ex @@ -113,15 +113,12 @@ defmodule Mix.Tasks.Help do def run(["app:" <> app]) do loadpaths!() app = String.to_atom(app) - - if app in [:eex, :elixir, :ex_unit, :iex, :logger, :mix] do - Application.ensure_loaded(app) - end + Application.ensure_loaded(app) if modules = Application.spec(app, :modules) do for module <- modules, not (module |> Atom.to_string() |> String.starts_with?("Elixir.Mix.Tasks.")), - {:docs_v1, _, :elixir, "text/markdown", %{"en" => <>}, _, _} <- + {:docs_v1, _, _, "text/markdown", %{"en" => <>}, _, _} <- [Code.fetch_docs(module)] do leading = doc |> String.split(["\n\n", "\r\n\r\n"], parts: 2) |> hd() "# #{inspect(module)}\n#{leading}\n" diff --git a/lib/mix/test/mix/tasks/help_test.exs b/lib/mix/test/mix/tasks/help_test.exs index de8157756bc..48d92c5a12e 100644 --- a/lib/mix/test/mix/tasks/help_test.exs +++ b/lib/mix/test/mix/tasks/help_test.exs @@ -195,30 +195,25 @@ defmodule Mix.Tasks.HelpTest do end) end - test "help app:APP", context do - in_tmp(context.test, fn -> - output = - capture_io(fn -> - Mix.Tasks.Help.run(["app:mix"]) - end) - - assert output =~ "# Mix\n\nMix is a build tool" - end) - end - - test "help Elixir stdlib app", context do + test "help Elixir/OTP app" do apps = for {app, _, _} <- Application.loaded_applications(), do: app assert :mix in apps refute :iex in apps + refute :ftp in apps - in_tmp(context.test, fn -> - output = - capture_io(fn -> - Mix.Tasks.Help.run(["app:iex"]) - end) + output = + capture_io(fn -> + Mix.Tasks.Help.run(["app:iex"]) + end) - assert output =~ "# IEx\n\nElixir's interactive shell." - end) + assert output =~ "# IEx\n\nElixir's interactive shell." + + output = + capture_io(fn -> + Mix.Tasks.Help.run(["app:ftp"]) + end) + + assert output =~ "# :ftp" end test "help unknown app:APP", context do From df94759dcbd2c89b1723b9ebb2242490d4a04337 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Wed, 5 Nov 2025 23:53:30 +0100 Subject: [PATCH 3/9] Use Mix.ensure_application!/1 --- lib/mix/lib/mix/tasks/help.ex | 34 ++++++++++++---------------- lib/mix/test/mix/tasks/help_test.exs | 7 +++--- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/lib/mix/lib/mix/tasks/help.ex b/lib/mix/lib/mix/tasks/help.ex index b8d59da72f2..03e76eef136 100644 --- a/lib/mix/lib/mix/tasks/help.ex +++ b/lib/mix/lib/mix/tasks/help.ex @@ -113,26 +113,22 @@ defmodule Mix.Tasks.Help do def run(["app:" <> app]) do loadpaths!() app = String.to_atom(app) - Application.ensure_loaded(app) - - if modules = Application.spec(app, :modules) do - for module <- modules, - not (module |> Atom.to_string() |> String.starts_with?("Elixir.Mix.Tasks.")), - {:docs_v1, _, _, "text/markdown", %{"en" => <>}, _, _} <- - [Code.fetch_docs(module)] do - leading = doc |> String.split(["\n\n", "\r\n\r\n"], parts: 2) |> hd() - "# #{inspect(module)}\n#{leading}\n" - end - |> case do - [] -> - Mix.shell().error("No modules with accessible documentation found for #{app}") + Mix.ensure_application!(app) + + for module <- Application.spec(app, :modules), + not (module |> Atom.to_string() |> String.starts_with?("Elixir.Mix.Tasks.")), + {:docs_v1, _, _, "text/markdown", %{"en" => <>}, _, _} <- + [Code.fetch_docs(module)] do + leading = doc |> String.split(["\n\n", "\r\n\r\n"], parts: 2) |> hd() + "# #{inspect(module)}\n#{leading}\n" + end + |> case do + [] -> + Mix.shell().error("No modules with accessible documentation found for #{app}") - listing -> - docs = listing |> Enum.sort() |> Enum.join() - IO.ANSI.Docs.print(docs, "text/markdown", ansi_opts()) - end - else - Mix.shell().error("Application #{app} does not exist or is not loaded") + listing -> + docs = listing |> Enum.sort() |> Enum.join() + IO.ANSI.Docs.print(docs, "text/markdown", ansi_opts()) end end diff --git a/lib/mix/test/mix/tasks/help_test.exs b/lib/mix/test/mix/tasks/help_test.exs index 48d92c5a12e..0c44dec8053 100644 --- a/lib/mix/test/mix/tasks/help_test.exs +++ b/lib/mix/test/mix/tasks/help_test.exs @@ -216,11 +216,10 @@ defmodule Mix.Tasks.HelpTest do assert output =~ "# :ftp" end - test "help unknown app:APP", context do - in_tmp(context.test, fn -> + test "help unknown app:APP" do + assert_raise Mix.Error, ~r/The application \"foobar\" could not be found/, fn -> Mix.Tasks.Help.run(["app:foobar"]) - assert_received {:mix_shell, :error, ["Application foobar does not exist or is not loaded"]} - end) + end end test "help Elixir MODULE", context do From 256ac590bc42652247cd0b28786a22ca2c6d95c8 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Wed, 5 Nov 2025 23:54:29 +0100 Subject: [PATCH 4/9] Update test descriptions --- lib/mix/test/mix/tasks/help_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mix/test/mix/tasks/help_test.exs b/lib/mix/test/mix/tasks/help_test.exs index 0c44dec8053..bbb1e2b707f 100644 --- a/lib/mix/test/mix/tasks/help_test.exs +++ b/lib/mix/test/mix/tasks/help_test.exs @@ -195,7 +195,7 @@ defmodule Mix.Tasks.HelpTest do end) end - test "help Elixir/OTP app" do + test "help app:APP" do apps = for {app, _, _} <- Application.loaded_applications(), do: app assert :mix in apps refute :iex in apps @@ -216,7 +216,7 @@ defmodule Mix.Tasks.HelpTest do assert output =~ "# :ftp" end - test "help unknown app:APP" do + test "help app:UNKNOWN" do assert_raise Mix.Error, ~r/The application \"foobar\" could not be found/, fn -> Mix.Tasks.Help.run(["app:foobar"]) end From 9ac95547fda431ff4d0c63effe6520197ee01169 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Thu, 6 Nov 2025 00:02:47 +0100 Subject: [PATCH 5/9] bring back in_tmp? --- lib/mix/test/mix/tasks/help_test.exs | 34 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/mix/test/mix/tasks/help_test.exs b/lib/mix/test/mix/tasks/help_test.exs index bbb1e2b707f..bb94433ac66 100644 --- a/lib/mix/test/mix/tasks/help_test.exs +++ b/lib/mix/test/mix/tasks/help_test.exs @@ -195,31 +195,35 @@ defmodule Mix.Tasks.HelpTest do end) end - test "help app:APP" do + test "help app:APP", context do apps = for {app, _, _} <- Application.loaded_applications(), do: app assert :mix in apps refute :iex in apps refute :ftp in apps - output = - capture_io(fn -> - Mix.Tasks.Help.run(["app:iex"]) - end) + in_tmp(context.test, fn -> + output = + capture_io(fn -> + Mix.Tasks.Help.run(["app:iex"]) + end) - assert output =~ "# IEx\n\nElixir's interactive shell." + assert output =~ "# IEx\n\nElixir's interactive shell." - output = - capture_io(fn -> - Mix.Tasks.Help.run(["app:ftp"]) - end) + output = + capture_io(fn -> + Mix.Tasks.Help.run(["app:ftp"]) + end) - assert output =~ "# :ftp" + assert output =~ "# :ftp" + end) end - test "help app:UNKNOWN" do - assert_raise Mix.Error, ~r/The application \"foobar\" could not be found/, fn -> - Mix.Tasks.Help.run(["app:foobar"]) - end + test "help app:UNKNOWN", context do + in_tmp(context.test, fn -> + assert_raise Mix.Error, ~r/The application \"foobar\" could not be found/, fn -> + Mix.Tasks.Help.run(["app:foobar"]) + end + end) end test "help Elixir MODULE", context do From 296a622e13f5a51206642a70b8423a3292c1e28d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 8 Nov 2025 11:57:30 +0100 Subject: [PATCH 6/9] Update help.ex --- lib/mix/lib/mix/tasks/help.ex | 208 +++++++++++++++++++++++++++++++--- 1 file changed, 195 insertions(+), 13 deletions(-) diff --git a/lib/mix/lib/mix/tasks/help.ex b/lib/mix/lib/mix/tasks/help.ex index 03e76eef136..b8a97cbd24b 100644 --- a/lib/mix/lib/mix/tasks/help.ex +++ b/lib/mix/lib/mix/tasks/help.ex @@ -1,3 +1,181 @@ +defmodule TS.MixProject do + use Mix.Project + + def project do + [ + app: :ts, + version: "0.1.0", + elixir: "~> 1.18", + elixirc_paths: elixirc_paths(Mix.env()), + start_permanent: Mix.env() == :prod, + aliases: aliases(), + deps: deps(), + listeners: [Phoenix.CodeReloader], + releases: releases(), + compilers: [:phoenix_live_view] ++ Mix.compilers() + ] + end + + def application do + [ + mod: {TS.Application, []}, + extra_applications: extra_applications(Mix.env()) + ] + end + + defp extra_applications(:test), do: [:logger] + defp extra_applications(_), do: [:logger, :runtime_tools, :os_mon] + + def cli do + [ + preferred_envs: [ + "test.e2e": :e2e, + "test.e2e.assets": :e2e, + "test.e2e.setup": :e2e, + "test.e2e.setup_without_playwright": :e2e + ] + ] + end + + defp elixirc_paths(env) when env in [:test, :e2e], do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + defp deps do + [ + {:tidewave, "~> 0.1", [only: [:dev]] ++ tidewave_opts()}, + {:oban, "~> 2.0"}, + {:phoenix, "~> 1.8.0"}, + {:phoenix_ecto, "~> 4.5"}, + {:ecto_sql, "~> 3.10"}, + {:postgrex, ">= 0.0.0"}, + {:phoenix_html, "~> 4.1"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_view, "~> 1.1"}, + {:bun, "~> 1.4", runtime: Mix.env() == :dev}, + {:remixicons, + github: "Remix-Design/RemixIcon", + sparse: "icons", + tag: "v4.6.0", + app: false, + compile: false, + depth: 1}, + {:swoosh, "~> 1.16"}, + {:finch, "~> 0.20"}, + {:req, "~> 0.5"}, + {:gettext, "~> 1.0"}, + {:jason, "~> 1.2"}, + {:bandit, "~> 1.5"}, + {:ueberauth, "~> 0.10.8"}, + {:ueberauth_github, "~> 0.8.3"}, + {:libcluster_postgres, "~> 0.2"}, + {:schematic, "~> 0.5.1"}, + {:req_sse, github: "wojtekmach/req_sse"}, + + # Blog + {:nimble_publisher, "~> 1.1", runtime: false}, + {:makeup, "~> 1.1", runtime: false}, + {:makeup_elixir, "~> 0.16", runtime: false}, + {:makeup_syntect, "~> 0.1", runtime: false}, + {:image, "~> 0.62.0", runtime: false}, + + # Test + {:mox, "~> 1.0", only: [:test, :e2e]}, + {:lazy_html, ">= 0.1.0", only: :test}, + + # Used for forwarding webhooks in dev + {:slipstream, "~> 1.2", only: :dev}, + + # Observability + {:oban_web, "~> 2.0"}, + {:phoenix_live_dashboard, "~> 0.8.3"}, + {:sentry, "~> 11.0"}, + {:opentelemetry, "~> 1.5"}, + {:opentelemetry_api, "~> 1.4"}, + {:opentelemetry_exporter, "~> 1.8"}, + {:opentelemetry_semantic_conventions, "~> 1.27"}, + # We require https://github.com/open-telemetry/opentelemetry-erlang-contrib/commit/0569321cfa06147fd1f0813460a836e0019c890b + {:opentelemetry_bandit, "~> 0.2.0", + github: "open-telemetry/opentelemetry-erlang-contrib", + sparse: "instrumentation/opentelemetry_bandit"}, + {:opentelemetry_phoenix, "~> 2.0"}, + {:opentelemetry_ecto, "~> 1.2"}, + {:telemetry_metrics, "~> 1.0"}, + {:telemetry_poller, "~> 1.0"} + ] + end + + defp tidewave_opts do + if path = System.get_env("TIDEWAVE_PATH") do + [path: path] + else + [github: "tidewave-ai/tidewave_phoenix"] + end + end + + defp aliases do + [ + setup: ["deps.get", "deps.loadpaths", "assets.setup", "assets.dev", "compile", "ecto.setup"], + "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], + "ecto.reset": ["ecto.drop", "ecto.setup"], + test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"], + "test.e2e": &e2e_test/1, + "test.e2e.setup": ["test.e2e.setup_without_playwright", "bun tc x playwright install"], + "test.e2e.assets": ["assets.setup", "assets.dev", "assets.build"], + "test.e2e.setup_without_playwright": ["ecto.setup", "assets.setup", "examples.setup"], + "examples.setup": [ + "cmd --cd examples/phoenix mix setup", + "cmd --cd examples/rails bundle install", + "cmd --cd examples/django scripts/setup.sh", + "cmd --cd examples/flask scripts/setup.sh", + "cmd --cd examples/fastapi scripts/setup.sh", + "cmd --cd examples/nextjs bun install", + "cmd --cd examples/phoenix/client/react bun install" + ], + "assets.setup": [ + "app.config --no-compile", + "bun.install --if-missing", + "bun assets install", + "bun tc install", + "bun tc run build" + ], + "assets.dev": ["bun acp_demo_agent install"], + "assets.build": ["bun css --minify", "bun js --minify"], + "assets.deploy": ["assets.build", "phx.digest"], + "format.all": [ + "format", + "bun tc run --silent format", + "bun acp_demo_agent run --silent format" + ] + ] + end + + defp releases do + [ + ts: [ + applications: [ + opentelemetry_exporter: :permanent, + opentelemetry: :temporary + ] + ] + ] + end + + defp e2e_test(args) do + # we wrap the playwright command to send it a sigint (Ctrl+C) when + # the BEAM exits, otherwise the started webservers don't properly shut down + {_, exit_status} = + System.cmd( + Path.expand("e2e-zombie.sh", __DIR__), + ["../_build/bun", "x", "playwright", "test"] ++ args, + cd: "tc", + stderr_to_stdout: true, + into: IO.stream(:stdio, :line) + ) + + System.stop(exit_status) + end +end + # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: 2021 The Elixir Team # SPDX-FileCopyrightText: 2012 Plataformatec @@ -115,20 +293,24 @@ defmodule Mix.Tasks.Help do app = String.to_atom(app) Mix.ensure_application!(app) - for module <- Application.spec(app, :modules), - not (module |> Atom.to_string() |> String.starts_with?("Elixir.Mix.Tasks.")), - {:docs_v1, _, _, "text/markdown", %{"en" => <>}, _, _} <- - [Code.fetch_docs(module)] do - leading = doc |> String.split(["\n\n", "\r\n\r\n"], parts: 2) |> hd() - "# #{inspect(module)}\n#{leading}\n" - end - |> case do - [] -> - Mix.shell().error("No modules with accessible documentation found for #{app}") + if modules = Application.spec(app, :modules) do + for module <- modules, + not (module |> Atom.to_string() |> String.starts_with?("Elixir.Mix.Tasks.")), + {:docs_v1, _, _, "text/markdown", %{"en" => <>}, _, _} <- + [Code.fetch_docs(module)] do + leading = doc |> String.split(["\n\n", "\r\n\r\n"], parts: 2) |> hd() + "# #{inspect(module)}\n#{leading}\n" + end + |> case do + [] -> + Mix.shell().error("No modules with accessible documentation found for #{app}") - listing -> - docs = listing |> Enum.sort() |> Enum.join() - IO.ANSI.Docs.print(docs, "text/markdown", ansi_opts()) + listing -> + docs = listing |> Enum.sort() |> Enum.join() + IO.ANSI.Docs.print(docs, "text/markdown", ansi_opts()) + end + else + Mix.shell().error("Application #{app} does not exist or is not loaded") end end From f583f912cb0a1611b0386f08e5d77e79e027de25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 8 Nov 2025 11:58:05 +0100 Subject: [PATCH 7/9] Remove TS.MixProject module and its contents --- lib/mix/lib/mix/tasks/help.ex | 178 ---------------------------------- 1 file changed, 178 deletions(-) diff --git a/lib/mix/lib/mix/tasks/help.ex b/lib/mix/lib/mix/tasks/help.ex index b8a97cbd24b..3eb784909cd 100644 --- a/lib/mix/lib/mix/tasks/help.ex +++ b/lib/mix/lib/mix/tasks/help.ex @@ -1,181 +1,3 @@ -defmodule TS.MixProject do - use Mix.Project - - def project do - [ - app: :ts, - version: "0.1.0", - elixir: "~> 1.18", - elixirc_paths: elixirc_paths(Mix.env()), - start_permanent: Mix.env() == :prod, - aliases: aliases(), - deps: deps(), - listeners: [Phoenix.CodeReloader], - releases: releases(), - compilers: [:phoenix_live_view] ++ Mix.compilers() - ] - end - - def application do - [ - mod: {TS.Application, []}, - extra_applications: extra_applications(Mix.env()) - ] - end - - defp extra_applications(:test), do: [:logger] - defp extra_applications(_), do: [:logger, :runtime_tools, :os_mon] - - def cli do - [ - preferred_envs: [ - "test.e2e": :e2e, - "test.e2e.assets": :e2e, - "test.e2e.setup": :e2e, - "test.e2e.setup_without_playwright": :e2e - ] - ] - end - - defp elixirc_paths(env) when env in [:test, :e2e], do: ["lib", "test/support"] - defp elixirc_paths(_), do: ["lib"] - - defp deps do - [ - {:tidewave, "~> 0.1", [only: [:dev]] ++ tidewave_opts()}, - {:oban, "~> 2.0"}, - {:phoenix, "~> 1.8.0"}, - {:phoenix_ecto, "~> 4.5"}, - {:ecto_sql, "~> 3.10"}, - {:postgrex, ">= 0.0.0"}, - {:phoenix_html, "~> 4.1"}, - {:phoenix_live_reload, "~> 1.2", only: :dev}, - {:phoenix_live_view, "~> 1.1"}, - {:bun, "~> 1.4", runtime: Mix.env() == :dev}, - {:remixicons, - github: "Remix-Design/RemixIcon", - sparse: "icons", - tag: "v4.6.0", - app: false, - compile: false, - depth: 1}, - {:swoosh, "~> 1.16"}, - {:finch, "~> 0.20"}, - {:req, "~> 0.5"}, - {:gettext, "~> 1.0"}, - {:jason, "~> 1.2"}, - {:bandit, "~> 1.5"}, - {:ueberauth, "~> 0.10.8"}, - {:ueberauth_github, "~> 0.8.3"}, - {:libcluster_postgres, "~> 0.2"}, - {:schematic, "~> 0.5.1"}, - {:req_sse, github: "wojtekmach/req_sse"}, - - # Blog - {:nimble_publisher, "~> 1.1", runtime: false}, - {:makeup, "~> 1.1", runtime: false}, - {:makeup_elixir, "~> 0.16", runtime: false}, - {:makeup_syntect, "~> 0.1", runtime: false}, - {:image, "~> 0.62.0", runtime: false}, - - # Test - {:mox, "~> 1.0", only: [:test, :e2e]}, - {:lazy_html, ">= 0.1.0", only: :test}, - - # Used for forwarding webhooks in dev - {:slipstream, "~> 1.2", only: :dev}, - - # Observability - {:oban_web, "~> 2.0"}, - {:phoenix_live_dashboard, "~> 0.8.3"}, - {:sentry, "~> 11.0"}, - {:opentelemetry, "~> 1.5"}, - {:opentelemetry_api, "~> 1.4"}, - {:opentelemetry_exporter, "~> 1.8"}, - {:opentelemetry_semantic_conventions, "~> 1.27"}, - # We require https://github.com/open-telemetry/opentelemetry-erlang-contrib/commit/0569321cfa06147fd1f0813460a836e0019c890b - {:opentelemetry_bandit, "~> 0.2.0", - github: "open-telemetry/opentelemetry-erlang-contrib", - sparse: "instrumentation/opentelemetry_bandit"}, - {:opentelemetry_phoenix, "~> 2.0"}, - {:opentelemetry_ecto, "~> 1.2"}, - {:telemetry_metrics, "~> 1.0"}, - {:telemetry_poller, "~> 1.0"} - ] - end - - defp tidewave_opts do - if path = System.get_env("TIDEWAVE_PATH") do - [path: path] - else - [github: "tidewave-ai/tidewave_phoenix"] - end - end - - defp aliases do - [ - setup: ["deps.get", "deps.loadpaths", "assets.setup", "assets.dev", "compile", "ecto.setup"], - "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], - "ecto.reset": ["ecto.drop", "ecto.setup"], - test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"], - "test.e2e": &e2e_test/1, - "test.e2e.setup": ["test.e2e.setup_without_playwright", "bun tc x playwright install"], - "test.e2e.assets": ["assets.setup", "assets.dev", "assets.build"], - "test.e2e.setup_without_playwright": ["ecto.setup", "assets.setup", "examples.setup"], - "examples.setup": [ - "cmd --cd examples/phoenix mix setup", - "cmd --cd examples/rails bundle install", - "cmd --cd examples/django scripts/setup.sh", - "cmd --cd examples/flask scripts/setup.sh", - "cmd --cd examples/fastapi scripts/setup.sh", - "cmd --cd examples/nextjs bun install", - "cmd --cd examples/phoenix/client/react bun install" - ], - "assets.setup": [ - "app.config --no-compile", - "bun.install --if-missing", - "bun assets install", - "bun tc install", - "bun tc run build" - ], - "assets.dev": ["bun acp_demo_agent install"], - "assets.build": ["bun css --minify", "bun js --minify"], - "assets.deploy": ["assets.build", "phx.digest"], - "format.all": [ - "format", - "bun tc run --silent format", - "bun acp_demo_agent run --silent format" - ] - ] - end - - defp releases do - [ - ts: [ - applications: [ - opentelemetry_exporter: :permanent, - opentelemetry: :temporary - ] - ] - ] - end - - defp e2e_test(args) do - # we wrap the playwright command to send it a sigint (Ctrl+C) when - # the BEAM exits, otherwise the started webservers don't properly shut down - {_, exit_status} = - System.cmd( - Path.expand("e2e-zombie.sh", __DIR__), - ["../_build/bun", "x", "playwright", "test"] ++ args, - cd: "tc", - stderr_to_stdout: true, - into: IO.stream(:stdio, :line) - ) - - System.stop(exit_status) - end -end - # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: 2021 The Elixir Team # SPDX-FileCopyrightText: 2012 Plataformatec From 11fd7a331af54c7c7c6b2958778bff5a921e8a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 8 Nov 2025 11:59:24 +0100 Subject: [PATCH 8/9] Apply suggestions from code review --- lib/mix/lib/mix/tasks/help.ex | 10 +++++++++- lib/mix/test/mix/tasks/help_test.exs | 14 ++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/mix/lib/mix/tasks/help.ex b/lib/mix/lib/mix/tasks/help.ex index 3eb784909cd..e3895756e7a 100644 --- a/lib/mix/lib/mix/tasks/help.ex +++ b/lib/mix/lib/mix/tasks/help.ex @@ -113,7 +113,15 @@ defmodule Mix.Tasks.Help do def run(["app:" <> app]) do loadpaths!() app = String.to_atom(app) - Mix.ensure_application!(app) + + # If the application is not available, attempt to load it from Erlang/Elixir + if is_nil(Application.spec(app, :vsn)) do + try do + Mix.ensure_application!(app) + rescue + _ -> :ok + end + end if modules = Application.spec(app, :modules) do for module <- modules, diff --git a/lib/mix/test/mix/tasks/help_test.exs b/lib/mix/test/mix/tasks/help_test.exs index bb94433ac66..419c96a9163 100644 --- a/lib/mix/test/mix/tasks/help_test.exs +++ b/lib/mix/test/mix/tasks/help_test.exs @@ -196,11 +196,6 @@ defmodule Mix.Tasks.HelpTest do end test "help app:APP", context do - apps = for {app, _, _} <- Application.loaded_applications(), do: app - assert :mix in apps - refute :iex in apps - refute :ftp in apps - in_tmp(context.test, fn -> output = capture_io(fn -> @@ -211,18 +206,17 @@ defmodule Mix.Tasks.HelpTest do output = capture_io(fn -> - Mix.Tasks.Help.run(["app:ftp"]) + Mix.Tasks.Help.run(["app:parsetools"]) end) - assert output =~ "# :ftp" + assert output =~ "# :leex" end) end test "help app:UNKNOWN", context do in_tmp(context.test, fn -> - assert_raise Mix.Error, ~r/The application \"foobar\" could not be found/, fn -> - Mix.Tasks.Help.run(["app:foobar"]) - end + Mix.Tasks.Help.run(["app:foobar"]) + assert_received {:mix_shell, :error, ["Application foobar does not exist or is not loaded"]} end) end From 3ec1ed979118d829a365ff2dd77781e89ac7e8e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 8 Nov 2025 12:11:02 +0100 Subject: [PATCH 9/9] Update lib/mix/test/mix/tasks/help_test.exs --- lib/mix/test/mix/tasks/help_test.exs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/mix/test/mix/tasks/help_test.exs b/lib/mix/test/mix/tasks/help_test.exs index 419c96a9163..c48f8c17a0a 100644 --- a/lib/mix/test/mix/tasks/help_test.exs +++ b/lib/mix/test/mix/tasks/help_test.exs @@ -204,12 +204,14 @@ defmodule Mix.Tasks.HelpTest do assert output =~ "# IEx\n\nElixir's interactive shell." - output = - capture_io(fn -> - Mix.Tasks.Help.run(["app:parsetools"]) - end) + if System.otp_release() >= "27" do + output = + capture_io(fn -> + Mix.Tasks.Help.run(["app:parsetools"]) + end) - assert output =~ "# :leex" + assert output =~ "# :leex" + end end) end