defmodule Mix.Tasks.Lvn.Install do
@moduledoc "Installer Mix task for LiveView Native: `mix lvn.install`"
use Mix.Task
@requirements ["app.config"]
@template_projects_repo "https://github.com/liveview-native/liveview-native-template-projects"
@template_projects_version "0.0.1"
@shortdoc "Installs LiveView Native."
def run(_) do
# Define some paths for the host project
current_path = File.cwd!()
mix_config_path = Path.join(current_path, "mix.exs")
app_config_path = Path.join(current_path, "/config/config.exs")
{:ok, app_name} = infer_app_name(mix_config_path)
build_path = Path.join(current_path, "_build")
libs_path = Path.join(build_path, "dev/lib")
# Ask the user some questions about their app
preferred_route_input = IO.gets("What path should native clients connect to by default? Leave blank for default: \"/\")\n")
preferred_prod_url_input = IO.gets("What URL will you use in production? Leave blank for default: \"example.com\")\n")
preferred_route = String.trim(preferred_route_input)
_preferred_route = if preferred_route == "", do: "/", else: preferred_route
preferred_prod_url = String.trim(preferred_prod_url_input)
_preferred_prod_url = if preferred_prod_url == "", do: "example.com", else: preferred_prod_url
# Get a list of compiled libraries
libs = File.ls!(libs_path)
# Clone the liveview-native-template-projects repo. This repo contains
# templates for various native platforms in their respective tools
# (Xcode, Android Studio, etc.)
clone_template_projects()
template_projects_path = Path.join(build_path, "lvn_tmp/liveview-native-template-projects")
template_libs = File.ls!(template_projects_path)
# Find libraries compiled for the host project that have available
# template projects
supported_libs = Enum.filter(libs, &(&1 in template_libs))
# Run the install script for each template project. Install scripts are
# responsible for generating platform-specific template projects and return
# information about that platform to be applied to the host project's Mix
# configuration.
platform_names =
Enum.map(supported_libs, fn lib ->
status_message("configuring", "#{lib}")
# Run the project-specific install script, passing info about the host
# Phoenix project.
lib_path = Path.join(template_projects_path, "/#{lib}")
script_path = Path.join(lib_path, "/install.exs")
cmd_opts = [script_path, "--app-name", app_name, "--app-path", current_path, "--platform-lib-path", lib_path]
with {platform_name, 0} <- System.cmd("elixir", cmd_opts) do
String.trim(platform_name)
end
end)
generate_native_exs_if_needed(current_path, platform_names)
update_config_exs_if_needed(app_config_path)
# Clear _build path to ensure it's rebuilt with new Config
File.rm_rf(build_path)
IO.puts("\nYour Phoenix app is ready to use LiveView Native!\n")
IO.puts("Platform-specific project files have been placed in the \"native\" directory\n")
:ok
end
###
defp clone_template_projects do
with {:ok, current_path} <- File.cwd(),
tmp_path <- Path.join(current_path, "_build/lvn_tmp"),
_ <- File.rm_rf(tmp_path),
:ok <- File.mkdir(tmp_path)
do
status_message("downloading", "template project files")
System.cmd("git", ["clone", "-b", @template_projects_version, @template_projects_repo, tmp_path <> "/liveview-native-template-projects"])
end
end
defp infer_app_name(config_path) do
with {:ok, config} <- File.read(config_path),
{:ok, {:defmodule, _meta, [{:__aliases__, [line: 1], [namespace, :MixProject]}, _rest]}} <- Code.string_to_quoted(config)
do
{:ok, "#{namespace}"}
end
end
defp generate_native_exs_if_needed(current_path, platform_names) do
platform_names_string = Enum.join(platform_names, ",")
native_config_path = Path.join(current_path, "/config/native.exs")
if File.exists?(native_config_path) do
IO.puts("native.exs already exists, skipping...")
else
status_message("creating", "config/native.exs")
# Generate native.exs and write it to config path
lvn_configuration = native_exs_body(platform_names_string)
{:ok, native_config} = File.open(native_config_path, [:write])
IO.binwrite(native_config, lvn_configuration)
File.close(native_config)
:ok
end
end
defp update_config_exs_if_needed(app_config_path) do
# Update project's config.exs to import native.exs if needed.
import_string = "import_config \"native.exs\""
{:ok, app_config_body} = File.read(app_config_path)
if String.contains?(app_config_body, import_string) do
IO.puts("config.exs already imports native.exs, skipping...")
else
status_message("updating", "config/config.exs")
{:ok, app_config} = File.open(app_config_path, [:write])
updated_app_config_body = app_config_body <> "\n" <> import_string
IO.binwrite(app_config, updated_app_config_body)
File.close(app_config)
end
end
defp native_exs_body(platform_names_string) do
"""
# This file is responsible for configuring LiveView Native.
# It is auto-generated when running `mix lvn.install`.
import Config
config :live_view_native, plugins: [#{platform_names_string}]
"""
end
defp status_message(label, message) do
formatted_message = IO.ANSI.green() <> "* #{label} " <> IO.ANSI.reset() <> message
IO.puts(formatted_message)
end
end