Skip to content

Commit 6935478

Browse files
add igniter upgrader for 1.0 -> 1.1 (#3889)
Co-authored-by: Zach Daniel <[email protected]>
1 parent c5e54b0 commit 6935478

File tree

6 files changed

+847
-5
lines changed

6 files changed

+847
-5
lines changed

.igniter.exs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# This is a configuration file for igniter.
2+
# For option documentation, see https://hexdocs.pm/igniter/Igniter.Project.IgniterConfig.html
3+
# To keep it up to date, use `mix igniter.setup`
4+
[
5+
module_location: :outside_matching_folder,
6+
extensions: [],
7+
deps_location: :last_list_literal,
8+
source_folders: ["lib", "test/support"],
9+
dont_move_files: [~r"lib/mix"]
10+
]
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
if Code.ensure_loaded?(Igniter) do
2+
defmodule Mix.Tasks.PhoenixLiveView.Upgrade do
3+
@moduledoc false
4+
5+
use Igniter.Mix.Task
6+
7+
@impl Igniter.Mix.Task
8+
def info(_argv, _composing_task) do
9+
%Igniter.Mix.Task.Info{
10+
# Groups allow for overlapping arguments for tasks by the same author
11+
# See the generators guide for more.
12+
group: :phoenix_live_view,
13+
# *other* dependencies to add
14+
# i.e `{:foo, "~> 2.0"}`
15+
adds_deps: [],
16+
# *other* dependencies to add and call their associated installers, if they exist
17+
# i.e `{:foo, "~> 2.0"}`
18+
installs: [],
19+
# a list of positional arguments, i.e `[:file]`
20+
positional: [:from, :to],
21+
# Other tasks your task composes using `Igniter.compose_task`, passing in the CLI argv
22+
# This ensures your option schema includes options from nested tasks
23+
composes: [],
24+
# `OptionParser` schema
25+
schema: [
26+
yes: :boolean
27+
],
28+
# Default values for the options in the `schema`
29+
defaults: [],
30+
# CLI aliases
31+
aliases: [],
32+
# A list of options in the schema that are required
33+
required: []
34+
}
35+
end
36+
37+
@impl Igniter.Mix.Task
38+
def igniter(igniter) do
39+
positional = igniter.args.positional
40+
options = igniter.args.options
41+
42+
upgrades =
43+
%{
44+
"1.1.0" => [&Phoenix.LiveView.Igniter.UpgradeTo1_1.run/2]
45+
}
46+
47+
# For each version that requires a change, add it to this map
48+
# Each key is a version that points at a list of functions that take an
49+
# igniter and options (i.e. flags or other custom options).
50+
# See the upgrades guide for more.
51+
Igniter.Upgrades.run(igniter, positional.from, positional.to, upgrades,
52+
custom_opts: options
53+
)
54+
end
55+
end
56+
else
57+
defmodule Mix.Tasks.PhoenixLiveView.Upgrade do
58+
@moduledoc false
59+
60+
use Mix.Task
61+
62+
@impl Mix.Task
63+
def run(_argv) do
64+
Mix.shell().error("""
65+
The task 'phoenix_live_view.upgrade' requires igniter. Please install igniter and try again.
66+
67+
For more information, see: https://hexdocs.pm/igniter/readme.html#installation
68+
""")
69+
70+
exit({:shutdown, 1})
71+
end
72+
end
73+
end
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
if Code.ensure_loaded?(Igniter) do
2+
defmodule Phoenix.LiveView.Igniter.UpgradeTo1_1 do
3+
@moduledoc false
4+
5+
def run(igniter, _opts) do
6+
igniter
7+
|> Igniter.Project.Deps.add_dep({:lazy_html, ">= 0.0.0", only: :test}, on_exists: :skip)
8+
|> Igniter.Project.MixProject.update(:project, [:compilers], fn
9+
nil ->
10+
{:ok, {:code, "[:phoenix_live_view] ++ Mix.compilers()"}}
11+
12+
zipper ->
13+
cond do
14+
Igniter.Code.List.list?(zipper) and
15+
!Igniter.Code.List.find_list_item_index(zipper, &(&1 == :phoenix_live_view)) ->
16+
Igniter.Code.List.prepend_to_list(zipper, :phoenix_live_view)
17+
18+
expected_compilers?(zipper) ->
19+
{:ok, zipper}
20+
21+
true ->
22+
{:warning,
23+
"""
24+
Failed to automatically configure compilers. Please add the following code to the project section of your mix.exs:
25+
26+
compilers: [:phoenix_live_view] ++ Mix.compilers()
27+
"""}
28+
end
29+
end)
30+
|> maybe_update_reloadable_compilers()
31+
|> maybe_update_esbuild_config()
32+
end
33+
34+
defp maybe_update_reloadable_compilers(igniter) do
35+
endpoint_mod = Igniter.Libs.Phoenix.web_module(igniter) |> Module.concat(Endpoint)
36+
app_name = Igniter.Project.Application.app_name(igniter)
37+
38+
warning = """
39+
You have `:reloadable_compilers` configured on your dev endpoint in config/dev.exs.
40+
41+
Ensure that `:phoenix_live_view` is set in there as the first entry!
42+
43+
config :#{app_name}, #{inspect(endpoint_mod)},
44+
reloadable_compilers: [:phoenix_live_view, :elixir, :app]
45+
"""
46+
47+
if Igniter.Project.Config.configures_key?(igniter, "dev.exs", app_name, [
48+
endpoint_mod,
49+
:reloadable_compilers
50+
]) do
51+
Igniter.Project.Config.configure(
52+
igniter,
53+
"dev.exs",
54+
app_name,
55+
[endpoint_mod, :reloadable_compilers],
56+
nil,
57+
updater: fn zipper ->
58+
if Igniter.Code.List.list?(zipper) do
59+
index =
60+
Igniter.Code.List.find_list_item_index(zipper, fn zipper ->
61+
case Igniter.Code.Common.expand_literal(zipper) do
62+
{:ok, :phoenix_live_view} -> true
63+
_ -> false
64+
end
65+
end)
66+
67+
cond do
68+
index == nil ->
69+
Igniter.Code.List.prepend_to_list(zipper, :phoenix_live_view)
70+
71+
index == 0 ->
72+
{:ok, zipper}
73+
74+
index > 0 ->
75+
zipper
76+
|> Igniter.Code.List.remove_index(index)
77+
|> case do
78+
{:ok, zipper} ->
79+
Igniter.Code.List.prepend_to_list(zipper, :phoenix_live_view)
80+
81+
:error ->
82+
{:warning, warning}
83+
end
84+
end
85+
else
86+
{:warning, warning}
87+
end
88+
end
89+
)
90+
else
91+
igniter
92+
end
93+
end
94+
95+
defp expected_compilers?(zipper) do
96+
Igniter.Code.Function.function_call?(zipper, {Kernel, :++}) &&
97+
Igniter.Code.Function.argument_equals?(zipper, 0, [:phoenix_live_view]) &&
98+
Igniter.Code.Function.argument_matches_predicate?(zipper, 1, fn zipper ->
99+
Igniter.Code.Function.function_call?(zipper, {Mix, :compilers})
100+
end)
101+
end
102+
103+
defp maybe_update_esbuild_config(igniter) do
104+
if igniter.args.options[:yes] ||
105+
Igniter.Util.IO.yes?(
106+
"Do you want to update your esbuild configuration for colocated hooks?"
107+
) do
108+
app_name = Igniter.Project.Application.app_name(igniter)
109+
110+
warning =
111+
"""
112+
Failed to update esbuild configuration for colocated hooks. Please manually:
113+
114+
1. append `--alias:@=.` to the `args` list
115+
2. configure `NODE_PATH` to be a list including `Mix.Project.build_path()`:
116+
117+
config :esbuild,
118+
#{app_name}: [
119+
args:
120+
~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=.),
121+
cd: "...",
122+
env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]}
123+
]
124+
"""
125+
126+
if Igniter.Project.Config.configures_key?(igniter, "config.exs", :esbuild, app_name) do
127+
config_exs_vsn = Rewrite.Source.version(igniter.rewrite.sources["config/config.exs"])
128+
129+
igniter =
130+
Igniter.Project.Config.configure(
131+
igniter,
132+
"config.exs",
133+
:esbuild,
134+
app_name,
135+
nil,
136+
updater: fn zipper ->
137+
if Igniter.Code.Keyword.keyword_has_path?(zipper, [:args]) and
138+
Igniter.Code.Keyword.keyword_has_path?(zipper, [:env]) do
139+
with {:ok, zipper} <- update_esbuild_args(zipper, warning),
140+
{:ok, zipper} <- update_esbuild_env(zipper) do
141+
{:ok, zipper}
142+
end
143+
else
144+
{:warning, warning}
145+
end
146+
end
147+
)
148+
149+
if config_exs_vsn ==
150+
Rewrite.Source.version(igniter.rewrite.sources["config/config.exs"]) do
151+
igniter
152+
else
153+
igniter
154+
|> Igniter.add_notice("""
155+
Final step for colocated hooks:
156+
157+
Add an import to your `app.js` and configure the hooks option of the LiveSocket:
158+
159+
...
160+
import {LiveSocket} from "phoenix_live_view"
161+
+ import {hooks as colocatedHooks} from "phoenix-colocated/#{app_name}"
162+
import topbar from "../vendor/topbar"
163+
...
164+
const liveSocket = new LiveSocket("/live", Socket, {
165+
longPollFallbackMs: 2500,
166+
params: {_csrf_token: csrfToken},
167+
+ hooks: {...colocatedHooks}
168+
})
169+
170+
""")
171+
end
172+
else
173+
igniter
174+
end
175+
else
176+
igniter
177+
end
178+
end
179+
180+
defp update_esbuild_args(zipper, warning) do
181+
Igniter.Code.Keyword.put_in_keyword(zipper, [:args], nil, fn zipper ->
182+
if Igniter.Code.List.list?(zipper) do
183+
Igniter.Code.List.append_new_to_list(zipper, "--alias:@=.")
184+
else
185+
# ~w()
186+
case zipper.node do
187+
{:sigil_w, _meta, [{:<<>>, _str_meta, [str]}, []]} ->
188+
if str =~ "--alias:@=." do
189+
{:ok, zipper}
190+
else
191+
{:ok,
192+
Igniter.Code.Common.replace_code(
193+
zipper,
194+
~s[~w(#{str <> " --alias:@=."})]
195+
)}
196+
end
197+
198+
_ ->
199+
{:warning, warning}
200+
end
201+
end
202+
end)
203+
end
204+
205+
defp update_esbuild_env(zipper) do
206+
Igniter.Code.Keyword.put_in_keyword(
207+
zipper,
208+
[:env],
209+
# we already checked that env is configured
210+
nil,
211+
fn zipper ->
212+
Igniter.Code.Map.put_in_map(
213+
zipper,
214+
["NODE_PATH"],
215+
~s<"[Path.expand("../deps", __DIR__), Mix.Project.build_path()])>,
216+
fn zipper ->
217+
if Igniter.Code.List.list?(zipper) do
218+
index =
219+
Igniter.Code.List.find_list_item_index(zipper, fn zipper ->
220+
if Igniter.Code.Function.function_call?(
221+
zipper,
222+
{Mix.Project, :build_path},
223+
0
224+
) do
225+
true
226+
end
227+
end)
228+
229+
if index do
230+
{:ok, zipper}
231+
else
232+
Igniter.Code.List.append_to_list(zipper, {:code, "Mix.Project.build_path()"})
233+
end
234+
else
235+
# If NODE_PATH is not a list, convert it to a list with the original value and Mix.Project.build_path()
236+
zipper
237+
|> Igniter.Code.Common.replace_code("[Mix.Project.build_path()]")
238+
|> Igniter.Code.List.prepend_to_list(zipper.node)
239+
end
240+
end
241+
)
242+
end
243+
)
244+
end
245+
end
246+
end

mix.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ defmodule Phoenix.LiveView.MixProject do
4444

4545
defp deps do
4646
[
47+
{:igniter, "~> 0.6 and >= 0.6.16", optional: true},
4748
{:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc"},
4849
{:plug, "~> 1.15"},
4950
{:phoenix_template, "~> 1.0"},

0 commit comments

Comments
 (0)