Packages
mob_dev
0.6.21
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.37
0.3.35
0.3.34
0.3.33
0.3.28
0.3.26
0.3.23
0.3.21
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Development tooling for the Mob mobile framework
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/mob/adopt/mob_app.ex
defmodule Mix.Tasks.Mob.Adopt.MobApp do
@shortdoc "Generates lib/<app>/mob_app.ex + src/<app>.erl (on-device BEAM entry)"
@moduledoc """
Generates the on-device BEAM entry point invoked by Mob's native
shell at app launch:
- `lib/<app>/mob_app.ex` — the entry module
- `src/<app>.erl` — Erlang bootstrap that calls
`<App>.MobApp.start/0`
- `mix.exs` patches: `erlc_paths: ["src"]` + `erlc_options: [:debug_info]`
so the Erlang bootstrap gets compiled
Two flavours of `mob_app.ex`:
- **LiveView** (default) — calls
`Application.ensure_all_started(:<app>)` which boots the host
Phoenix endpoint, runs Ecto migrations, sets up the on-device
runtime config. `secret_key_base` is read from
`config/dev.exs` if available (so it matches the host dev
server) or freshly generated.
- **Thin client** (with `--no-live-view`) — uses `use Mob.App` with
`navigation/1` + `on_start/0` callbacks. Does NOT boot Phoenix
on-device; the WebView points at a deployed Phoenix server (set
`config :mob, host_url: ...`). The device's BEAM is just the
native interop layer.
## Options
- `--no-live-view` — generate the thin-client `mob_app.ex` instead
of the LiveView-flavoured one. Pairs with the `bridge` sub-task
being skipped under the same flag.
Other orchestrator flags accepted but inert.
## Idempotency
- Files are created with `on_exists: :skip`. Re-running won't
overwrite — delete first if you want to switch between LV and
thin flavours.
- `erlc_paths` / `erlc_options` injection checks string presence in
`mix.exs` before patching.
Typically called by `mix mob.adopt`, not directly.
"""
use Igniter.Mix.Task
alias Igniter.Project.Application, as: ProjectApplication
alias MobDev.Adopt.{Generator, Patcher}
alias MobDev.AdoptGuard
@common_schema [
ios: :boolean,
android: :boolean,
local: :boolean,
python: :boolean,
host_url: :string,
live_view: :boolean
]
@common_defaults [ios: true, android: true, live_view: true]
@impl Igniter.Mix.Task
def info(_argv, _composing_task) do
%Igniter.Mix.Task.Info{
group: :mob,
example: "mix mob.adopt.mob_app",
schema: @common_schema,
defaults: @common_defaults
}
end
@impl Igniter.Mix.Task
def igniter(igniter) do
# Guard call is idempotent — orchestrator runs the same checks but
# `prepare_for_write` dedupes issues. Defends direct invocation.
igniter = AdoptGuard.check(igniter, AdoptGuard.mode_from(igniter.args.options))
if igniter.issues != [] do
igniter
else
generate(igniter)
end
end
defp generate(igniter) do
app_name = ProjectApplication.app_name(igniter) |> to_string()
module_name = Macro.camelize(app_name)
live_view? = Keyword.get(igniter.args.options, :live_view, true)
mob_app_content = build_mob_app_content(live_view?, module_name, app_name)
erl_content = Patcher.erlang_entry_content(module_name, app_name)
igniter
|> Igniter.create_new_file("lib/#{app_name}/mob_app.ex", mob_app_content, on_exists: :skip)
|> Igniter.create_new_file("src/#{app_name}.erl", erl_content, on_exists: :skip)
|> patch_erlc_paths()
end
defp build_mob_app_content(true = _live_view, module_name, app_name) do
secret_key_base =
Generator.extract_secret_key_base(File.cwd!()) ||
Generator.generate_secret_key_base()
signing_salt = Generator.generate_signing_salt()
Patcher.mob_live_app_content(module_name, app_name, secret_key_base, signing_salt)
end
defp build_mob_app_content(false = _live_view, module_name, app_name) do
Patcher.mob_app_content_thin(module_name, app_name)
end
# Adds erlc_paths: ["src"] and erlc_options: [:debug_info] to the host
# mix.exs def project. Text-based for resilience — keyword-list AST
# manipulation inside `def project do [...]` is fragile across Phoenix
# versions. Idempotent via String.contains? checks.
defp patch_erlc_paths(igniter) do
Igniter.update_file(igniter, "mix.exs", fn source ->
content = Rewrite.Source.get(source, :content)
Rewrite.Source.update(source, :content, inject_erlc(content))
end)
end
@doc false
@spec inject_erlc(String.t()) :: String.t()
def inject_erlc(content) do
content
|> maybe_inject_key("erlc_paths", ~s(erlc_paths: ["src"],))
|> maybe_inject_key("erlc_options", ~s(erlc_options: [:debug_info],))
end
defp maybe_inject_key(content, key, snippet) do
if String.contains?(content, key) do
content
else
Regex.replace(
Regex.compile!("(def project do\\s*\\[)"),
content,
"\\1\n #{snippet}",
global: false
)
end
end
end