From 788c3c92b0ad1754c32aff373d99991cb217676f Mon Sep 17 00:00:00 2001 From: Nuno Freire Date: Thu, 1 Sep 2022 14:56:28 +0100 Subject: [PATCH] Implement mix update script --- config/config.exs | 7 ++ lib/heroicons.ex | 116 ++++++++++++++++++++++++++++++ lib/mix/tasks/heroicons.ex | 29 ++++++++ lib/mix/tasks/heroicons/update.ex | 22 ++++++ mix.exs | 6 +- mix.lock | 1 + 6 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 config/config.exs create mode 100644 lib/mix/tasks/heroicons.ex create mode 100644 lib/mix/tasks/heroicons/update.ex diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..7581ebf --- /dev/null +++ b/config/config.exs @@ -0,0 +1,7 @@ +import Config + +config :heroicons, + version: "2.0.0", + another: [ + args: ["--version"] + ] diff --git a/lib/heroicons.ex b/lib/heroicons.ex index d766cba..0ef1e30 100644 --- a/lib/heroicons.ex +++ b/lib/heroicons.ex @@ -16,4 +16,120 @@ defmodule Heroicons do Heroicons are designed by [Steve Schoger](https://twitter.com/steveschoger) """ + + # https://github.com/tailwindlabs/heroicons/releases + @latest_version "2.0.0" + + @tmp_dir_name "heroicons-elixir" + + require Logger + + @doc false + # Latest known version at the time of publishing. + def latest_version, do: @latest_version + + @doc """ + Returns the configured Heroicons version. + """ + def configured_version do + Application.get_env(:heroicons, :version, latest_version()) + end + + @doc false + def svgs_path, do: Path.join(:code.priv_dir(:heroicons), "icons") + + def icons_path(style), do: Path.join(svgs_path(), style) + + def update do + version = configured_version() + tmp_dir = Path.join(System.tmp_dir!(), @tmp_dir_name) + + File.rm_rf!(tmp_dir) + File.mkdir_p!(tmp_dir) + + url = "https://github.com/tailwindlabs/heroicons/archive/refs/tags/v#{version}.zip" + archive = fetch_body!(url) + + case unpack_archive(".zip", archive, tmp_dir) do + :ok -> :ok + other -> raise "couldn't unpack archive: #{inspect(other)}" + end + + # Copy icon styles, mini, outline and solid, to priv folder + Path.join([tmp_dir, "heroicons-#{version}", "optimized"]) + |> File.ls!() + |> Enum.each(fn size -> + case size do + "20" -> copy_svg_files(size, "solid", "mini") + "24" -> Enum.each(File.ls!(), fn style -> copy_svg_files(size, style, style) end) + _ -> true + end + end) + end + + defp copy_svg_files(icons_size, src_dir_name, output_dir_name) do + tmp_dir = Path.join(System.tmp_dir!(), @tmp_dir_name) + svgs_temp_dir = Path.join([tmp_dir, "heroicons-#{configured_version()}", "optimized"]) + + File.cp_r!(Path.join([svgs_temp_dir, icons_size, src_dir_name]), icons_path(output_dir_name)) + end + + defp fetch_body!(url) do + url = String.to_charlist(url) + Logger.debug("Downloading heroicons from #{url}") + + {:ok, _} = Application.ensure_all_started(:inets) + {:ok, _} = Application.ensure_all_started(:ssl) + + if proxy = System.get_env("HTTP_PROXY") || System.get_env("http_proxy") do + Logger.debug("Using HTTP_PROXY: #{proxy}") + %{host: host, port: port} = URI.parse(proxy) + :httpc.set_options([{:proxy, {{String.to_charlist(host), port}, []}}]) + end + + if proxy = System.get_env("HTTPS_PROXY") || System.get_env("https_proxy") do + Logger.debug("Using HTTPS_PROXY: #{proxy}") + %{host: host, port: port} = URI.parse(proxy) + :httpc.set_options([{:https_proxy, {{String.to_charlist(host), port}, []}}]) + end + + # https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/inets + cacertfile = CAStore.file_path() |> String.to_charlist() + + http_options = [ + ssl: [ + verify: :verify_peer, + cacertfile: cacertfile, + depth: 2, + customize_hostname_check: [ + match_fun: :public_key.pkix_verify_hostname_match_fun(:https) + ], + versions: protocol_versions() + ] + ] + + options = [body_format: :binary] + + case :httpc.request(:get, {url, []}, http_options, options) do + {:ok, {{_, 200, _}, _headers, body}} -> + body + + other -> + raise "couldn't fetch #{url}: #{inspect(other)}" + end + end + + defp protocol_versions do + if otp_version() < 25, do: [:"tlsv1.2"], else: [:"tlsv1.2", :"tlsv1.3"] + end + + defp otp_version, do: :erlang.system_info(:otp_release) |> List.to_integer() + + defp unpack_archive(".zip", zip, cwd) do + with {:ok, _} <- :zip.unzip(zip, cwd: to_charlist(cwd)), do: :ok + end + + defp unpack_archive(_, tar, cwd) do + :erl_tar.extract({:binary, tar}, [:compressed, cwd: to_charlist(cwd)]) + end end diff --git a/lib/mix/tasks/heroicons.ex b/lib/mix/tasks/heroicons.ex new file mode 100644 index 0000000..4a5028a --- /dev/null +++ b/lib/mix/tasks/heroicons.ex @@ -0,0 +1,29 @@ +defmodule Mix.Tasks.Heroicons do + @moduledoc """ + Invokes heroicons mix utilities. + Usage: + $ mix heroicons + """ + + @shortdoc "Invokes heroicons mix utilities" + + use Mix.Task + + @impl true + def run(args) do + {_opts, args} = OptionParser.parse!(args, strict: []) + + case args do + [] -> help() + _ -> Mix.raise("Invalid arguments, expected: mix heroicons") + end + end + + defp help() do + Mix.Task.run("app.start") + Mix.shell().info("Heroicons v#{Application.spec(:heroicons, :vsn)}") + Mix.shell().info("Include Heroicons as SVG-strings in your Elixir/Phoenix project!") + Mix.shell().info("\nAvailable tasks:\n") + Mix.Tasks.Help.run(["--search", "heroicons."]) + end +end diff --git a/lib/mix/tasks/heroicons/update.ex b/lib/mix/tasks/heroicons/update.ex new file mode 100644 index 0000000..34deb08 --- /dev/null +++ b/lib/mix/tasks/heroicons/update.ex @@ -0,0 +1,22 @@ +defmodule Mix.Tasks.Heroicons.Update do + @moduledoc """ + Update heroicons. + By default, it downloads the latest version but you + can configure it in your config files, such as: + config :heroicons, :version, "#{Heroicons.latest_version()}" + """ + + @shortdoc "Update heroicons assets" + + use Mix.Task + + @impl true + def run(args) do + {_opts, args} = OptionParser.parse!(args, strict: []) + + case args do + [] -> Heroicons.update() + _ -> Mix.raise("Invalid arguments, expected: mix heroicons.update") + end + end +end diff --git a/mix.exs b/mix.exs index 06d193c..c0761d2 100644 --- a/mix.exs +++ b/mix.exs @@ -12,7 +12,8 @@ defmodule HeroiconsElixir.MixProject do source_url: "https://github.com/mveytsman/heroicons_elixir", description: "Phoenix components for Heroicons!", docs: docs(), - package: package() + package: package(), + xref: [exclude: [:httpc, :public_key]] ] end @@ -29,7 +30,8 @@ defmodule HeroiconsElixir.MixProject do {:phoenix_html, "~> 2.14 or ~> 3.0"}, {:phoenix_live_view, ">= 0.16.0", optional: true}, {:ex_doc, "~> 0.23", only: :dev, runtime: false}, - {:saxy, "~> 1.4", only: [:dev, :test]} + {:saxy, "~> 1.4", only: [:dev, :test]}, + {:castore, ">= 0.0.0"} ] end diff --git a/mix.lock b/mix.lock index ced9769..0680130 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,5 @@ %{ + "castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"}, "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"}, "ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"}, "floki": {:hex, :floki, "0.30.0", "22ebbe681a5d3777cdd830ca091b1b806d33c3449c26312eadca7f7be685c0c8", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "a9e128a4ca9bb71f11affa315b6768a9ad326d5996ff1e92acf1d7a01a10076a"},