Repository: phoenixframework/phoenix (elixir)

Issue:
The –no-migration flag, does not prevent the generation of mirgrations when used with phx.gen.context
### Environment

* Elixir version 1.16.0:
* Phoenix version 1.7.19:
* Debian 12:

### Actual behavior

Inclusion of --no-migration flag in mix `phx.gen.context` command does not prevent generation of migration files.

```
mix phx.gen.context Testex Testa testas --no-migration
* creating lib/schem_exp/testex/testa.ex
* creating priv/repo/migrations/20250210111130_create_testas.exs
* creating lib/schem_exp/testex.ex
* injecting lib/schem_exp/testex.ex
* creating test/schem_exp/testex_test.exs
* injecting test/schem_exp/testex_test.exs
* creating test/support/fixtures/testex_fixtures.ex
* injecting test/support/fixtures/testex_fixtures.ex
```

> Remember to update your repository by running migrations:

`    $ mix ecto.migrate`

Please note generation of `priv/repo/migrations/20250210111130_create_testas.exs` as well as prompt to use command  `$ mix ecto.migrate`

### Expected behavior

It is stated that the default gen settings can be overridden with use of appropriate options.

The expected behaviour occurs when used with mix `phx.gen.schema` 

```
mix phx.gen.schema Testa testas --no-migration
* creating lib/schem_exp/testa.ex
```

Excerpt from docs after running command mix help phx.gen.contexts


>     Default options
> 
>     This generator uses default options provided in the :generators configuration
>     of your application. These are the defaults:
> 
> config :your_app, :generators,
>   migration: true,
>   binary_id: false,
>   timestamp_type: :naive_datetime,
>   sample_binary_id: "11111111-1111-1111-1111-111111111111"
> 
>     You can override those options per invocation by providing corresponding
>     switches, e.g. --no-binary-id to use normal ids despite the default
>     configuration or --migration to force generation of the migration.

Amending at the default level also produces expected behaviour with context as well as schema generation.


Pull request description:
Allow skipping migration when generating context with `--no-migration`
fixes #6080 

first time contributing to Phoenix, so I hope I got it right ^^ 

Patch:
diff --git a/guides/introduction/community.md b/guides/introduction/community.md
index 5e4fc4a1b..17bafdc43 100644
--- a/guides/introduction/community.md
+++ b/guides/introduction/community.md
@@ -34,6 +34,8 @@ The Security Working Group of the Erlang Ecosystem Foundation also publishes in-
 
 ## Screencasts/Courses
 
+  * [Free Bootcamp: Fullstack Elixir and Phoenix (by TechSchool - 2024)](https://techschool.dev/en/bootcamps/fullstack-elixir-and-phoenix)
+
   * [Learn Phoenix LiveView (by George Arrowsmith - 2024)](https://phoenixliveview.com)
 
   * [Phoenix LiveView Free Course (by The Pragmatic Studio - 2023)](https://pragmaticstudio.com/courses/phoenix-liveview)
diff --git a/lib/mix/tasks/phx.gen.context.ex b/lib/mix/tasks/phx.gen.context.ex
index 1dfad60e3..f20306d71 100644
--- a/lib/mix/tasks/phx.gen.context.ex
+++ b/lib/mix/tasks/phx.gen.context.ex
@@ -28,6 +28,8 @@ defmodule Mix.Tasks.Phx.Gen.Context do
   A migration file for the repository and test files for the context
   will also be generated.
 
+  The generated migration can be skipped with `--no-migration`.
+
   ## Generating without a schema
 
   In some cases, you may wish to bootstrap the context module and
@@ -90,7 +92,8 @@ defmodule Mix.Tasks.Phx.Gen.Context do
     prefix: :string,
     live: :boolean,
     compile: :boolean,
-    primary_key: :string
+    primary_key: :string,
+    migration: :boolean
   ]
 
   @default_opts [schema: true, context: true]
diff --git a/lib/phoenix/config.ex b/lib/phoenix/config.ex
index e31961059..7c68c6367 100644
--- a/lib/phoenix/config.ex
+++ b/lib/phoenix/config.ex
@@ -93,13 +93,6 @@ defmodule Phoenix.Config do
     merge(defaults, config)
   end
 
-  @doc """
-  Take 2 keyword lists and merge them recursively.
-
-  Used to merge configuration values into defaults.
-  """
-  def merge(a, b), do: Keyword.merge(a, b, &merger/3)
-
   defp fetch_config(otp_app, module) do
     case Application.fetch_env(otp_app, module) do
       {:ok, conf} -> conf
@@ -107,6 +100,13 @@ defmodule Phoenix.Config do
     end
   end
 
+  @doc """
+  Take 2 keyword lists and merge them recursively.
+
+  Used to merge configuration values into defaults.
+  """
+  def merge(a, b), do: Keyword.merge(a, b, &merger/3)
+
   defp merger(_k, v1, v2) do
     if Keyword.keyword?(v1) and Keyword.keyword?(v2) do
       Keyword.merge(v1, v2, &merger/3)
diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex
index 1419e1902..48ffae62b 100644
--- a/lib/phoenix/endpoint.ex
+++ b/lib/phoenix/endpoint.ex
@@ -421,11 +421,16 @@ defmodule Phoenix.Endpoint do
   defp config(opts) do
     quote do
       @otp_app unquote(opts)[:otp_app] || raise("endpoint expects :otp_app to be given")
-      var!(config) = Phoenix.Endpoint.Supervisor.config(@otp_app, __MODULE__)
-      var!(code_reloading?) = var!(config)[:code_reloader]
+
+      # Compile-time configuration checking
+      # This ensures that if a compile-time configuration is overwritten at runtime the application won't boot.
+      var!(code_reloading?) = Application.compile_env(@otp_app, [__MODULE__, :code_reloader], false)
+      var!(debug_errors?) =  Application.compile_env(@otp_app, [__MODULE__, :debug_errors], false)
+      var!(force_ssl) =  Application.compile_env(@otp_app, [__MODULE__, :force_ssl])
 
       # Avoid unused variable warnings
       _ = var!(code_reloading?)
+      _ = var!(force_ssl)
     end
   end
 
@@ -477,11 +482,11 @@ defmodule Phoenix.Endpoint do
 
       Module.register_attribute(__MODULE__, :phoenix_sockets, accumulate: true)
 
-      if force_ssl = Phoenix.Endpoint.__force_ssl__(__MODULE__, var!(config)) do
+      if force_ssl = Phoenix.Endpoint.__force_ssl__(__MODULE__, var!(force_ssl)) do
         plug Plug.SSL, force_ssl
       end
 
-      if var!(config)[:debug_errors] do
+      if var!(debug_errors?) do
         logo = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzEgNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgoJPHBhdGggZD0ibTI2LjM3MSAzMy40NzctLjU1Mi0uMWMtMy45Mi0uNzI5LTYuMzk3LTMuMS03LjU3LTYuODI5LS43MzMtMi4zMjQuNTk3LTQuMDM1IDMuMDM1LTQuMTQ4IDEuOTk1LS4wOTIgMy4zNjIgMS4wNTUgNC41NyAyLjM5IDEuNTU3IDEuNzIgMi45ODQgMy41NTggNC41MTQgNS4zMDUgMi4yMDIgMi41MTUgNC43OTcgNC4xMzQgOC4zNDcgMy42MzQgMy4xODMtLjQ0OCA1Ljk1OC0xLjcyNSA4LjM3MS0zLjgyOC4zNjMtLjMxNi43NjEtLjU5MiAxLjE0NC0uODg2bC0uMjQxLS4yODRjLTIuMDI3LjYzLTQuMDkzLjg0MS02LjIwNS43MzUtMy4xOTUtLjE2LTYuMjQtLjgyOC04Ljk2NC0yLjU4Mi0yLjQ4Ni0xLjYwMS00LjMxOS0zLjc0Ni01LjE5LTYuNjExLS43MDQtMi4zMTUuNzM2LTMuOTM0IDMuMTM1LTMuNi45NDguMTMzIDEuNzQ2LjU2IDIuNDYzIDEuMTY1LjU4My40OTMgMS4xNDMgMS4wMTUgMS43MzggMS40OTMgMi44IDIuMjUgNi43MTIgMi4zNzUgMTAuMjY1LS4wNjgtNS44NDItLjAyNi05LjgxNy0zLjI0LTEzLjMwOC03LjMxMy0xLjM2Ni0xLjU5NC0yLjctMy4yMTYtNC4wOTUtNC43ODUtMi42OTgtMy4wMzYtNS42OTItNS43MS05Ljc5LTYuNjIzQzEyLjgtLjYyMyA3Ljc0NS4xNCAyLjg5MyAyLjM2MSAxLjkyNiAyLjgwNC45OTcgMy4zMTkgMCA0LjE0OWMuNDk0IDAgLjc2My4wMDYgMS4wMzIgMCAyLjQ0Ni0uMDY0IDQuMjggMS4wMjMgNS42MDIgMy4wMjQuOTYyIDEuNDU3IDEuNDE1IDMuMTA0IDEuNzYxIDQuNzk4LjUxMyAyLjUxNS4yNDcgNS4wNzguNTQ0IDcuNjA1Ljc2MSA2LjQ5NCA0LjA4IDExLjAyNiAxMC4yNiAxMy4zNDYgMi4yNjcuODUyIDQuNTkxIDEuMTM1IDcuMTcyLjU1NVpNMTAuNzUxIDMuODUyYy0uOTc2LjI0Ni0xLjc1Ni0uMTQ4LTIuNTYtLjk2MiAxLjM3Ny0uMzQzIDIuNTkyLS40NzYgMy44OTctLjUyOC0uMTA3Ljg0OC0uNjA3IDEuMzA2LTEuMzM2IDEuNDlabTMyLjAwMiAzNy45MjRjLS4wODUtLjYyNi0uNjItLjkwMS0xLjA0LTEuMjI4LTEuODU3LTEuNDQ2LTQuMDMtMS45NTgtNi4zMzMtMi0xLjM3NS0uMDI2LTIuNzM1LS4xMjgtNC4wMzEtLjYxLS41OTUtLjIyLTEuMjYtLjUwNS0xLjI0NC0xLjI3Mi4wMTUtLjc4LjY5My0xIDEuMzEtMS4xODQuNTA1LS4xNSAxLjAyNi0uMjQ3IDEuNi0uMzgyLTEuNDYtLjkzNi0yLjg4Ni0xLjA2NS00Ljc4Ny0uMy0yLjk5MyAxLjIwMi01Ljk0MyAxLjA2LTguOTI2LS4wMTctMS42ODQtLjYwOC0zLjE3OS0xLjU2My00LjczNS0yLjQwOGwtLjA0My4wM2EyLjk2IDIuOTYgMCAwIDAgLjA0LS4wMjljLS4wMzgtLjExNy0uMTA3LS4xMi0uMTk3LS4wNTRsLjEyMi4xMDdjMS4yOSAyLjExNSAzLjAzNCAzLjgxNyA1LjAwNCA1LjI3MSAzLjc5MyAyLjggNy45MzYgNC40NzEgMTIuNzg0IDMuNzNBNjYuNzE0IDY2LjcxNCAwIDAgMSAzNyA0MC44NzdjMS45OC0uMTYgMy44NjYuMzk4IDUuNzUzLjg5OVptLTkuMTQtMzAuMzQ1Yy0uMTA1LS4wNzYtLjIwNi0uMjY2LS40Mi0uMDY5IDEuNzQ1IDIuMzYgMy45ODUgNC4wOTggNi42ODMgNS4xOTMgNC4zNTQgMS43NjcgOC43NzMgMi4wNyAxMy4yOTMuNTEgMy41MS0xLjIxIDYuMDMzLS4wMjggNy4zNDMgMy4zOC4xOS0zLjk1NS0yLjEzNy02LjgzNy01Ljg0My03LjQwMS0yLjA4NC0uMzE4LTQuMDEuMzczLTUuOTYyLjk0LTUuNDM0IDEuNTc1LTEwLjQ4NS43OTgtMTUuMDk0LTIuNTUzWm0yNy4wODUgMTUuNDI1Yy43MDguMDU5IDEuNDE2LjEyMyAyLjEyNC4xODUtMS42LTEuNDA1LTMuNTUtMS41MTctNS41MjMtMS40MDQtMy4wMDMuMTctNS4xNjcgMS45MDMtNy4xNCAzLjk3Mi0xLjczOSAxLjgyNC0zLjMxIDMuODctNS45MDMgNC42MDQuMDQzLjA3OC4wNTQuMTE3LjA2Ni4xMTcuMzUuMDA1LjY5OS4wMjEgMS4wNDcuMDA1IDMuNzY4LS4xNyA3LjMxNy0uOTY1IDEwLjE0LTMuNy44OS0uODYgMS42ODUtMS44MTcgMi41NDQtMi43MS43MTYtLjc0NiAxLjU4NC0xLjE1OSAyLjY0NS0xLjA3Wm0tOC43NTMtNC42N2MtMi44MTIuMjQ2LTUuMjU0IDEuNDA5LTcuNTQ4IDIuOTQzLTEuNzY2IDEuMTgtMy42NTQgMS43MzgtNS43NzYgMS4zNy0uMzc0LS4wNjYtLjc1LS4xMTQtMS4xMjQtLjE3bC0uMDEzLjE1NmMuMTM1LjA3LjI2NS4xNTEuNDA1LjIwNy4zNTQuMTQuNzAyLjMwOCAxLjA3LjM5NSA0LjA4My45NzEgNy45OTIuNDc0IDExLjUxNi0xLjgwMyAyLjIyMS0xLjQzNSA0LjUyMS0xLjcwNyA3LjAxMy0xLjMzNi4yNTIuMDM4LjUwMy4wODMuNzU2LjEwNy4yMzQuMDIyLjQ3OS4yNTUuNzk1LjAwMy0yLjE3OS0xLjU3NC00LjUyNi0yLjA5Ni03LjA5NC0xLjg3MlptLTEwLjA0OS05LjU0NGMxLjQ3NS4wNTEgMi45NDMtLjE0MiA0LjQ4Ni0xLjA1OS0uNDUyLjA0LS42NDMuMDQtLjgyNy4wNzYtMi4xMjYuNDI0LTQuMDMzLS4wNC01LjczMy0xLjM4My0uNjIzLS40OTMtMS4yNTctLjk3NC0xLjg4OS0xLjQ1Ny0yLjUwMy0xLjkxNC01LjM3NC0yLjU1NS04LjUxNC0yLjUuMDUuMTU0LjA1NC4yNi4xMDguMzE1IDMuNDE3IDMuNDU1IDcuMzcxIDUuODM2IDEyLjM2OSA2LjAwOFptMjQuNzI3IDE3LjczMWMtMi4xMTQtMi4wOTctNC45NTItMi4zNjctNy41NzgtLjUzNyAxLjczOC4wNzggMy4wNDMuNjMyIDQuMTAxIDEuNzI4LjM3NC4zODguNzYzLjc2OCAxLjE4MiAxLjEwNiAxLjYgMS4yOSA0LjMxMSAxLjM1MiA1Ljg5Ni4xNTUtMS44NjEtLjcyNi0xLjg2MS0uNzI2LTMuNjAxLTIuNDUyWm0tMjEuMDU4IDE2LjA2Yy0xLjg1OC0zLjQ2LTQuOTgxLTQuMjQtOC41OS00LjAwOGE5LjY2NyA5LjY2NyAwIDAgMSAyLjk3NyAxLjM5Yy44NC41ODYgMS41NDcgMS4zMTEgMi4yNDMgMi4wNTUgMS4zOCAxLjQ3MyAzLjUzNCAyLjM3NiA0Ljk2MiAyLjA3LS42NTYtLjQxMi0xLjIzOC0uODQ4LTEuNTkyLTEuNTA3Wm0xNy4yOS0xOS4zMmMwLS4wMjMuMDAxLS4wNDUuMDAzLS4wNjhsLS4wMDYuMDA2LjAwNi0uMDA2LS4wMzYtLjAwNC4wMjEuMDE4LjAxMi4wNTNabS0yMCAxNC43NDRhNy42MSA3LjYxIDAgMCAwLS4wNzItLjA0MS4xMjcuMTI3IDAgMCAwIC4wMTUuMDQzYy4wMDUuMDA4LjAzOCAwIC4wNTgtLjAwMlptLS4wNzItLjA0MS0uMDA4LS4wMzQtLjAwOC4wMS4wMDgtLjAxLS4wMjItLjAwNi4wMDUuMDI2LjAyNC4wMTRaIgogICAgICAgICAgICBmaWxsPSIjRkQ0RjAwIiAvPgo8L3N2Zz4K"
 
         use Plug.Debugger,
@@ -628,8 +633,8 @@ defmodule Phoenix.Endpoint do
   end
 
   @doc false
-  def __force_ssl__(module, config) do
-    if force_ssl = config[:force_ssl] do
+  def __force_ssl__(module, force_ssl) do
+    if force_ssl do
       Keyword.put_new(force_ssl, :host, {module, :host, []})
     end
   end
diff --git a/lib/phoenix/endpoint/supervisor.ex b/lib/phoenix/endpoint/supervisor.ex
index da2ffdb83..8f8a52bcf 100644
--- a/lib/phoenix/endpoint/supervisor.ex
+++ b/lib/phoenix/endpoint/supervisor.ex
@@ -27,7 +27,7 @@ defmodule Phoenix.Endpoint.Supervisor do
   @doc false
   def init({otp_app, mod, opts}) do
     default_conf = Phoenix.Config.merge(defaults(otp_app, mod), opts)
-    env_conf = config(otp_app, mod, default_conf)
+    env_conf = Phoenix.Config.from_env(otp_app, mod, default_conf)
 
     secret_conf =
       cond do
@@ -190,17 +190,6 @@ defmodule Phoenix.Endpoint.Supervisor do
     end
   end
 
-  @doc """
-  The endpoint configuration used at compile time.
-  """
-  def config(otp_app, endpoint) do
-    config(otp_app, endpoint, defaults(otp_app, endpoint))
-  end
-
-  defp config(otp_app, endpoint, defaults) do
-    Phoenix.Config.from_env(otp_app, endpoint, defaults)
-  end
-
   @doc """
   Checks if Endpoint's web server has been configured to start.
   """


Test patch:
diff --git a/test/mix/tasks/phx.gen.context_test.exs b/test/mix/tasks/phx.gen.context_test.exs
index f13c3421c..eb6c58716 100644
--- a/test/mix/tasks/phx.gen.context_test.exs
+++ b/test/mix/tasks/phx.gen.context_test.exs
@@ -367,6 +367,14 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
     end)
   end
 
+  test "generates schema with no migration option", config do
+    in_tmp_project(config.test, fn ->
+      Gen.Context.run(~w(Blog Post posts title:string --no-migration))
+
+      assert Path.wildcard("priv/repo/migrations/*") == []
+    end)
+  end
+
   test "generates context with no schema and repo option", config do
     in_tmp_project(config.test, fn ->
       Gen.Context.run(~w(Blog Post posts title:string --no-schema --repo=Foo.RepoX))
diff --git a/test/phoenix/endpoint/endpoint_test.exs b/test/phoenix/endpoint/endpoint_test.exs
index 1c8c24693..5a31718b3 100644
--- a/test/phoenix/endpoint/endpoint_test.exs
+++ b/test/phoenix/endpoint/endpoint_test.exs
@@ -25,9 +25,9 @@ defmodule Phoenix.Endpoint.EndpointTest do
     use Phoenix.Endpoint, otp_app: :phoenix
 
     # Assert endpoint variables
-    assert is_list(config)
     assert @otp_app == :phoenix
     assert code_reloading? == false
+    assert debug_errors? == false
   end
 
   defmodule NoConfigEndpoint do
diff --git a/test/phoenix/endpoint/supervisor_test.exs b/test/phoenix/endpoint/supervisor_test.exs
index 3b2b958db..a2ff64158 100644
--- a/test/phoenix/endpoint/supervisor_test.exs
+++ b/test/phoenix/endpoint/supervisor_test.exs
@@ -58,15 +58,6 @@ defmodule Phoenix.Endpoint.SupervisorTest do
 
   defp persistent!(endpoint), do: :persistent_term.get({Phoenix.Endpoint, endpoint})
 
-  test "loads router configuration" do
-    config = Supervisor.config(:phoenix, SupervisorApp.Endpoint)
-    assert config[:otp_app] == :phoenix
-    assert config[:custom] == true
-
-    assert config[:render_errors] ==
-             [view: SupervisorApp.ErrorView, accepts: ~w(html), layout: false]
-  end
-
   test "generates the static url based on the static host configuration" do
     assert persistent!(StaticURLEndpoint).static_url == "http://static.example.com"
   end