Skip to content

Instantly share code, notes, and snippets.

@aiwaiwa
Last active July 21, 2023 17:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save aiwaiwa/ac15f5d1019dfebfa342fafad4a9cb3d to your computer and use it in GitHub Desktop.
Save aiwaiwa/ac15f5d1019dfebfa342fafad4a9cb3d to your computer and use it in GitHub Desktop.
Compare Phoenix Versions with extra custom steps like mix phx.gen.live Accounts User users name:string
#
# Compare Phoenix releases with additional custom setup to enhance comparison.
#
# Usage:
# elixir <this_script_name.exs> 1.7.2
# (downloads 1.7.2 and compares with the closest previously downloaded version if available)
#
# elixir <this_script_name.exs> 1.7.2 1.7.7
# (downloads both versions and compares)
#
# To recreate the folders, pass --force.
#
# The script grabs a Phoenix version and places generated project
# under `phoenix/<version>/<project>` where <project> is a configurable name.
# It attempts to call a diff GUI, if there's a previous version found in `phoenix`
# or command line provides two version numbers to compare.
#
# To add set steps, modify PhoenixCompareConfig
# to bring in your diff tool and add additional
# steps after blank project creation.
#
# TODO: Work in temporary folders until end, then move to destination.
# TODO: Sanitize temporary folders.
# TODO: Patch migration file names to be precisely matching.
#
defmodule PhoenixCompareConfig do
@doc "project name to pass to mix phx.new <project_name>"
def project_name, do: "project"
@doc "Additional steps to execute on a new phoenix project. Caller expects :ok on success."
def steps(into_path, project_name) do
{_, 0} =
cmd(["mix phx.gen.live Accounts User users name:string"],
cd: Path.join(into_path, project_name),
stderr_to_stdout: true
)
# {_, 0} =
# cmd(["mix phx.gen.live Accounts CashFlow cash_flows user:references:users amount:integer currency:string"],
# cd: Path.join(into_path, project_name),
# stderr_to_stdout: true
# )
:ok
end
def exec_maybe_diff(%Version{} = previous, %Version{} = current) do
case previous do
nil ->
IO.puts(
"Skipping diff: no version < #{current} is found processed. Request it with this script and try again."
)
_ ->
IO.puts("#{previous} <-> #{current}")
case :os.type() do
{:win32, _} ->
Task.async(fn ->
cmd([
~S<C:\Program Files (x86)\WinMerge\WinMergeU.exe>,
"/r",
# Titles
"/dl",
"#{previous}",
"/dr",
"#{current}",
# Read only
"/wl",
"/wr",
Path.join("phoenix", ["#{previous}", "/", project_name()]),
Path.join("phoenix", ["#{current}", "/", project_name()])
])
end)
_ ->
IO.puts("Skipping diff: not implemented for #{:os.type()} yet. Please PR!")
end
end
end
def cmd([_ | _] = what, options \\ []) do
{cmd, args, options} =
case :os.type() do
{:win32, _} ->
{"cmd", ["/c"] ++ what, options}
# _os_name
# ------------
# :darwin
# ...
{:unix, _os_name} ->
[head | tail] = what
{head, tail, options}
end
# stream = IO.binstream(:standard_io, :line)
stream = IO.binstream(:stdio, :line)
options = Keyword.put_new(options, :into, stream)
System.cmd(cmd, args, options)
end
end
defmodule Execute do
import PhoenixCompareConfig
def already_exists_fail(version, is_force) do
path = phoenix_version_path(version)
case File.stat(path) do
{:ok, _stat} ->
case is_force do
true ->
case File.rm_rf(path) do
{:ok, _} -> :ok
{:error, reason, file} -> {:error, "#{file} failed to delete due to #{reason}"}
end
false ->
{:error, "#{path} already exists. Add --force to recreate"}
end
{:error, _reason} ->
:ok
end
end
def get_phx_new(%Version{} = version) do
{_, 0} =
cmd(["mix", "archive.install", "--force", "hex", "phx_new", "#{Version.to_string(version)}"])
:ok
end
def recreate_path(%Version{} = version) do
path = phoenix_version_path(version)
File.rm_rf!(path)
:ok = File.mkdir_p(path)
{:ok, path}
end
def initiate_phx_new(into_path, project_name) do
# mix phx.new <project_name>
{_, 0} =
cmd(["mix phx.new #{project_name} --no-install"],
cd: into_path,
stderr_to_stdout: true
)
{_, 0} =
cmd(["mix deps.get"],
cd: Path.join(into_path, project_name),
stderr_to_stdout: true
)
steps(into_path, project_name)
end
def arv_all_like_versions do
System.argv()
|> Enum.map(fn a ->
case Version.parse(a) do
{:ok, version} -> version
_ -> nil
end
end)
|> Enum.filter(fn v -> v != nil end)
end
def argv_has_force do
Enum.find_index(System.argv(), fn v -> v == "--force" end) != nil
end
def phoenix_version_path(%Version{} = version) do
# version = Version.to_string(version)
Path.join("phoenix", "#{version}")
end
def build(src) do
{_, 0} = cmd(["npm run build"], cd: src)
:ok
end
def available_prev_version(%Version{} = version) do
{:ok, versions} = File.ls("phoenix")
sorted = Enum.sort(versions, fn a, b -> Version.compare(a, b) == :lt end)
idx = Enum.find_index(sorted, fn v -> v == Version.to_string(version) end)
cond do
idx > 0 -> Version.parse!(Enum.at(sorted, idx - 1))
true -> nil
end
end
def maybe_diff(previous, current), do: PhoenixCompareConfig.exec_maybe_diff(previous, current)
def get_version(version, is_force) do
case Execute.already_exists_fail(version, is_force) do
:ok ->
:ok = Execute.get_phx_new(version)
{:ok, path} = Execute.recreate_path(version)
:ok = Execute.initiate_phx_new(path, PhoenixCompareConfig.project_name())
{:error, reason} ->
IO.puts(reason)
end
end
def main() do
is_force = Execute.argv_has_force()
versions = Execute.arv_all_like_versions()
case versions do
[a, b | _] ->
get_version(a, is_force)
get_version(b, is_force)
Execute.maybe_diff(a, b)
[a] ->
get_version(a, is_force)
b = Execute.available_prev_version(a)
Execute.maybe_diff(a, b)
_ -> exit("At least one Phoenix version is expected")
end
end
end
Execute.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment