defmodule Mix.Tasks.Igniter.Phx.Install do @moduledoc false use Igniter.Mix.Task @example "mix igniter.phx.install . --module MyApp --app my_app" # No docs yet, its only for testing # @shortdoc "Creates a new Phoenix project in the current application." # @moduledoc """ # #{@shortdoc} # # ## Example # # ```bash # #{@example} # ``` # # ## Options # # * `--app` - the name of the OTP application # # * `--module` - the name of the base module in # the generated skeleton # # * `--database` - specify the database adapter for Ecto. One of: # # * `postgres` - via https://github.com/elixir-ecto/postgrex # * `mysql` - via https://github.com/elixir-ecto/myxql # * `mssql` - via https://github.com/livehelpnow/tds # * `sqlite3` - via https://github.com/elixir-sqlite/ecto_sqlite3 # # Please check the driver docs for more information # and requirements. Defaults to "postgres". # # * `--adapter` - specify the http adapter. One of: # * `cowboy` - via https://github.com/elixir-plug/plug_cowboy # * `bandit` - via https://github.com/mtrudel/bandit # # Please check the adapter docs for more information # and requirements. Defaults to "bandit". # # * `--no-assets` - equivalent to `--no-esbuild` and `--no-tailwind` # # * `--no-dashboard` - do not include Phoenix.LiveDashboard # # * `--no-ecto` - do not generate Ecto files # # * `--no-esbuild` - do not include esbuild dependencies and assets. # We do not recommend setting this option, unless for API only # applications, as doing so requires you to manually add and # track JavaScript dependencies # # * `--no-gettext` - do not generate gettext files # # * `--no-html` - do not generate HTML views # # * `--no-live` - comment out LiveView socket setup in your Endpoint # and assets/js/app.js. Automatically disabled if --no-html is given # # * `--no-mailer` - do not generate Swoosh mailer files # # * `--no-tailwind` - do not include tailwind dependencies and assets. # The generated markup will still include Tailwind CSS classes, those # are left-in as reference for the subsequent styling of your layout # and components # # * `--binary-id` - use `binary_id` as primary key type in Ecto schemas # # * `--verbose` - use verbose output # # When passing the `--no-ecto` flag, Phoenix generators such as # `phx.gen.html`, `phx.gen.json`, `phx.gen.live`, and `phx.gen.context` # may no longer work as expected as they generate context files that rely # on Ecto for the database access. In those cases, you can pass the # `--no-context` flag to generate most of the HTML and JSON files # but skip the context, allowing you to fill in the blanks as desired. # # Similarly, if `--no-html` is given, the files generated by # `phx.gen.html` will no longer work, as important HTML components # will be missing. # # """ def info(_argv, _source) do %Igniter.Mix.Task.Info{ group: :igniter, example: @example, positional: [:base_path], schema: [ app: :string, module: :string, database: :string, adapter: :string, assets: :boolean, dashboard: :boolean, ecto: :boolean, esbuild: :boolean, gettext: :boolean, html: :boolean, live: :boolean, mailer: :boolean, tailwind: :boolean, binary_id: :boolean, verbose: :boolean ] } end def igniter(igniter) do elixir_version_check!() if !Code.ensure_loaded?(Phx.New.Generator) do Mix.raise(""" Phoenix installer is not available. Please install it before proceeding: mix archive.install hex phx_new """) end if igniter.args.options[:umbrella] do Mix.raise("Umbrella projects are not supported yet.") end %{base_path: base_path} = igniter.args.positional generate(igniter, base_path, {Phx.New.Single, Igniter.Phoenix.Single}, igniter.args.options) end defp generate(igniter, base_path, {phx_generator, igniter_generator}, opts) do project = apply(Phx.New.Project, :new, [base_path, opts]) |> phx_generator.prepare_project() project = apply(Phx.New.Generator, :put_binding, [project]) |> validate_project() igniter |> Igniter.compose_task("igniter.add_extension", ["phoenix"]) |> igniter_generator.generate(project) end defp validate_project(%{opts: opts} = project) do check_app_name!(project.app, !!opts[:app]) check_module_name_validity!(project.root_mod) project end defp check_app_name!(name, from_app_flag) do unless name =~ Regex.recompile!(~r/^[a-z][a-z0-9_]*$/) do extra = if from_app_flag do "" else ". The application name is inferred from the path, if you'd like to " <> "explicitly name the application then use the `--app APP` option." end Mix.raise( "Application name must start with a letter and have only lowercase " <> "letters, numbers and underscore, got: #{inspect(name)}" <> extra ) end end defp check_module_name_validity!(name) do unless inspect(name) =~ Regex.recompile!(~r/^[A-Z]\w*(\.[A-Z]\w*)*$/) do Mix.raise( "Module name must be a valid Elixir alias (for example: Foo.Bar), got: #{inspect(name)}" ) end end defp elixir_version_check! do unless Version.match?(System.version(), "~> 1.15") do Mix.raise( "mix igniter.phx.install requires at least Elixir v1.15\n " <> "You have #{System.version()}. Please update accordingly." ) end end end