Current section
Files
Jump to
Current section
Files
lib/mix/tasks/phx/install/core.ex
defmodule Mix.Tasks.Phx.Install.Core do
@shortdoc "Installs core Phoenix application structure"
@moduledoc """
Installs the core Phoenix application structure.
This task sets up:
- OTP Application module with supervision tree
- Config files (config.exs, dev.exs, test.exs, prod.exs, runtime.exs)
- Formatter configuration
- Test helper
## Usage
mix phx.install.core
This task is typically called by `mix phx.install` rather than directly.
"""
use Igniter.Mix.Task
@impl Igniter.Mix.Task
def info(_argv, _composing_task) do
%Igniter.Mix.Task.Info{
group: :phoenix,
example: "mix phx.install.core",
adds_deps: [{:phoenix, "~> 1.7"}],
schema: [
signing_salt: :string,
secret_key_base_dev: :string,
secret_key_base_test: :string
]
}
end
@impl Igniter.Mix.Task
def igniter(igniter) do
app_name = Igniter.Project.Application.app_name(igniter)
app_module = Igniter.Project.Module.module_name_prefix(igniter)
web_module = Igniter.Libs.Phoenix.web_module(igniter)
endpoint_module = Module.concat(web_module, Endpoint)
opts = igniter.args.options
secrets = %{
signing_salt: opts[:signing_salt] || PhxInstall.random_string(8),
secret_key_base_dev: opts[:secret_key_base_dev] || PhxInstall.random_string(64),
secret_key_base_test: opts[:secret_key_base_test] || PhxInstall.random_string(64)
}
application_module = Module.concat(app_module, Application)
igniter
|> Igniter.Project.Deps.add_dep({:phoenix, "~> 1.7"})
|> add_phoenix_extension()
|> set_application_mod(application_module)
|> create_application_module(app_name, app_module, web_module, endpoint_module)
|> set_project_listeners()
|> configure_base_config(app_name, app_module, web_module, endpoint_module, secrets)
|> configure_dev(app_name, endpoint_module, secrets)
|> configure_test(app_name, endpoint_module, secrets)
|> configure_prod(app_name, endpoint_module)
|> configure_runtime(app_name, endpoint_module)
|> configure_formatter()
|> create_test_helper()
end
defp set_application_mod(igniter, application_module) do
Igniter.update_elixir_file(igniter, "mix.exs", fn zipper ->
with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Mix.Project),
{:ok, zipper} <- Igniter.Code.Function.move_to_def(zipper, :application, 0) do
zipper
|> Igniter.Code.Common.rightmost()
|> Igniter.Code.Keyword.set_keyword_key(
:mod,
{application_module, []},
&replace_application_mod(&1, application_module)
)
end
end)
end
defp replace_application_mod(zipper, application_module) do
code =
{application_module, []}
|> Sourceror.to_string()
|> Sourceror.parse_string!()
{:ok, Igniter.Code.Common.replace_code(zipper, code)}
end
defp set_project_listeners(igniter) do
Igniter.Project.MixProject.update(igniter, :project, [:listeners], fn
nil -> {:ok, {:code, Sourceror.parse_string!("[Phoenix.CodeReloader]")}}
zipper -> {:ok, zipper}
end)
end
defp add_phoenix_extension(igniter) do
Igniter.Project.IgniterConfig.add_extension(igniter, Igniter.Extensions.Phoenix)
end
defp create_application_module(igniter, app_name, app_module, web_module, endpoint_module) do
application_module = Module.concat(app_module, Application)
telemetry_module = Module.concat(web_module, :Telemetry)
pubsub_name = Module.concat(app_module, :PubSub)
dns_cluster_opts =
quote do
[query: Application.get_env(unquote(app_name), :dns_cluster_query) || :ignore]
end
igniter
|> Igniter.Project.Module.find_and_update_or_create_module(
application_module,
"""
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = []
opts = [strategy: :one_for_one, name: #{inspect(Module.concat(app_module, :Supervisor))}]
Supervisor.start_link(children, opts)
end
@impl true
def config_change(changed, _new, removed) do
#{inspect(endpoint_module)}.config_change(changed, removed)
:ok
end
""",
fn zipper -> {:ok, zipper} end
)
|> Igniter.Project.Application.add_new_child(telemetry_module)
|> Igniter.Project.Application.add_new_child(
{DNSCluster, {:code, dns_cluster_opts}},
after: [telemetry_module]
)
|> Igniter.Project.Application.add_new_child(
{Phoenix.PubSub, [name: pubsub_name]},
after: [telemetry_module, DNSCluster]
)
|> Igniter.Project.Application.add_new_child(
endpoint_module,
after: [telemetry_module, DNSCluster, Phoenix.PubSub]
)
end
defp configure_base_config(igniter, app_name, app_module, web_module, endpoint_module, secrets) do
igniter
|> Igniter.Project.Config.configure(
"config.exs",
app_name,
[endpoint_module],
{:code,
Sourceror.parse_string!("""
[
url: [host: "localhost"],
adapter: Bandit.PhoenixAdapter,
render_errors: [
formats: [json: #{inspect(Module.concat(web_module, :ErrorJSON))}],
layout: false
],
pubsub_server: #{inspect(Module.concat(app_module, :PubSub))},
live_view: [signing_salt: #{inspect(secrets.signing_salt)}]
]
""")},
updater: fn zipper -> {:ok, zipper} end
)
|> Igniter.Project.Config.configure(
"config.exs",
:logger,
[:default_formatter],
{:code,
Sourceror.parse_string!("""
[format: "$time $metadata[$level] $message\\n", metadata: [:request_id]]
""")}
)
|> Igniter.Project.Config.configure(
"config.exs",
:phoenix,
[:json_library],
Jason
)
end
defp configure_dev(igniter, app_name, endpoint_module, secrets) do
igniter
|> Igniter.Project.Config.configure(
"dev.exs",
app_name,
[endpoint_module],
{:code,
Sourceror.parse_string!("""
[
http: [ip: {127, 0, 0, 1}, port: 4000],
check_origin: false,
code_reloader: true,
debug_errors: true,
secret_key_base: #{inspect(secrets.secret_key_base_dev)}
]
""")},
updater: fn zipper -> {:ok, zipper} end
)
|> Igniter.Project.Config.configure("dev.exs", app_name, [:dev_routes], true)
|> Igniter.Project.Config.configure(
"dev.exs",
:logger,
[:default_formatter, :format],
"[$level] $message\n"
)
|> Igniter.Project.Config.configure("dev.exs", :phoenix, [:stacktrace_depth], 20)
|> Igniter.Project.Config.configure("dev.exs", :phoenix, [:plug_init_mode], :runtime)
end
defp configure_test(igniter, app_name, endpoint_module, secrets) do
igniter
|> Igniter.Project.Config.configure(
"test.exs",
app_name,
[endpoint_module],
{:code,
Sourceror.parse_string!("""
[
http: [ip: {127, 0, 0, 1}, port: 4002],
secret_key_base: #{inspect(secrets.secret_key_base_test)},
server: false
]
""")},
updater: fn zipper -> {:ok, zipper} end
)
|> Igniter.Project.Config.configure("test.exs", :logger, [:level], :warning)
|> Igniter.Project.Config.configure("test.exs", :phoenix, [:plug_init_mode], :runtime)
end
defp configure_prod(igniter, app_name, endpoint_module) do
igniter
|> Igniter.Project.Config.configure(
"prod.exs",
app_name,
[endpoint_module],
{:code,
Sourceror.parse_string!("""
[
force_ssl: [rewrite_on: [:x_forwarded_proto]],
exclude: [hosts: ["localhost", "127.0.0.1"]]
]
""")}
)
|> Igniter.Project.Config.configure("prod.exs", :logger, [:level], :info)
end
defp configure_runtime(igniter, app_name, endpoint_module) do
runtime_content = """
import Config
if System.get_env("PHX_SERVER") do
config #{inspect(app_name)}, #{inspect(endpoint_module)}, server: true
end
config #{inspect(app_name)}, #{inspect(endpoint_module)},
http: [port: String.to_integer(System.get_env("PORT", "4000"))]
if config_env() == :prod do
secret_key_base =
System.get_env("SECRET_KEY_BASE") ||
raise \"\"\"
environment variable SECRET_KEY_BASE is missing.
You can generate one by calling: mix phx.gen.secret
\"\"\"
host = System.get_env("PHX_HOST") || "example.com"
config #{inspect(app_name)}, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
config #{inspect(app_name)}, #{inspect(endpoint_module)},
url: [host: host, port: 443, scheme: "https"],
http: [ip: {0, 0, 0, 0, 0, 0, 0, 0}],
secret_key_base: secret_key_base
end
"""
Igniter.create_or_update_elixir_file(
igniter,
"config/runtime.exs",
runtime_content,
fn zipper -> {:ok, zipper} end
)
end
defp configure_formatter(igniter) do
Igniter.Project.Formatter.import_dep(igniter, :phoenix)
end
defp create_test_helper(igniter) do
content = """
ExUnit.start()
"""
Igniter.create_or_update_elixir_file(
igniter,
"test/test_helper.exs",
content,
fn zipper -> {:ok, zipper} end
)
end
end