From 7d9715d1bd03a9e5172fa3ef35acbd8546370209 Mon Sep 17 00:00:00 2001 From: Robert Prehn <3952444+prehnRA@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:12:15 -0500 Subject: [PATCH] feat: Switch away from escript as the method, just use the app directory --- .gitlab-ci.yml | 17 ++--- config/config.exs | 5 -- lib/lain.ex | 124 +++++++++++++++++++------------ lib/lain/cli.ex | 27 ++++--- lib/lain/dev_server.ex | 6 +- lib/lain/link_log.ex | 44 +++++++---- lib/lain/markdown.ex | 25 ++++--- lib/lain/static.ex | 3 +- lib/mix/tasks/lain/dev_server.ex | 3 +- mix.exs | 13 ++-- mix.lock | 7 +- 11 files changed, 168 insertions(+), 106 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 49ac48e..98d741b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,7 +7,6 @@ pages: - mix deps.get - mix esbuild.install --if-missing - mix tailwind.install --if-missing - - mix tree_sitter.install --if-missing - (cd tree-sitter/_parsers/;git clone https://github.com/elixir-lang/tree-sitter-elixir; git clone https://github.com/camdencheek/tree-sitter-dockerfile) - mix assets.build - mix lain.build @@ -20,15 +19,15 @@ pages: # I wish we could just depend on the pages:deploy job that GitLab inserts, but apparently we can't reference it. wait: needs: - - "pages" + - "pages" script: - - sleep 10 + - sleep 10 ping: needs: - - "wait" + - "wait" script: - - mix local.hex --force - - mix local.rebar --force - - mix deps.get - - mix compile - - mix lain.ping + - mix local.hex --force + - mix local.rebar --force + - mix deps.get + - mix compile + - mix lain.ping diff --git a/config/config.exs b/config/config.exs index adc6b64..0dc43ba 100644 --- a/config/config.exs +++ b/config/config.exs @@ -25,11 +25,6 @@ config :esbuild, env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} ] -config :tree_sitter, - version: "0.20.8", - config_directory: "tree-sitter", - cacerts_path: Path.join([File.cwd!(), "_build", "castore.pem"]) - config :lain, host: "pre.hn", base: "https://pre.hn/" diff --git a/lib/lain.ex b/lib/lain.ex index 532f0d3..5ca8192 100644 --- a/lib/lain.ex +++ b/lib/lain.ex @@ -12,8 +12,6 @@ defmodule Lain do alias Lain.Frontmatter alias Lain.Markdown - @base_caller __ENV__ - def read(path) do path |> File.read!() @@ -53,9 +51,19 @@ defmodule Lain do end) end - def run(path) do - TreeSitter.install_and_run(["-V"]) + def serve(path) do + Application.put_env(:lain, :serve_endpoints, true, persistent: true) + Application.put_env(:lain, :built_path, Path.join(Path.expand(path), "build"), + persistent: true + ) + + run(path) + + :timer.sleep(:infinity) + end + + def run(path) do posts = path |> Path.join("**/*.md") @@ -77,7 +85,7 @@ defmodule Lain do make_feed(posts, path) make_sitemap(posts, path) - index = make_index(posts) + index = make_index(posts, path) Lain.LinkLog.run(path) Lain.Static.run(path) @@ -91,7 +99,12 @@ defmodule Lain do |> Enum.into(%{}) end - def make_index(posts) do + def make_index(posts, root_path) do + posts = + Enum.filter(posts, fn %{path: path} -> + String.starts_with?(path, Path.join(root_path, "posts")) + end) + assigns = %{posts: posts} body = @@ -123,29 +136,31 @@ defmodule Lain do EEx.eval_string( """ - - pre.hn - Robert Prehn's personal blog. - https://pre.hn - https://pre.hn/feed.rss + + https://pre.hn/feed.rss + <%= DateTime.utc_now() |> DateTime.to_iso8601() %> + pre.hn + Robert Prehn's personal blog. + <%= for post <- @posts do %> <%= HtmlSanitizeEx.strip_tags(post.title) %> - - <%= post.body %> - ]]> - + <%= post.body |> sanitize_html_for_rss.() |> xml_escape.() %> + <%= rss_date_format.(post.date) %> + <%= rss_date_format.(post[:updated_at] || post.date) %> + Robert Prehn <%= @base %><%= post.slug %>/ - <%= @base %><%= post.slug %>/ + <% end %> """, assigns: assigns, - rss_date_format: &rss_date_format/1 + rss_date_format: &rss_date_format/1, + sanitize_html_for_rss: &sanitize_html_for_rss/1, + xml_escape: &xml_escape/1 ) File.mkdir_p!(Path.join([path, "build"])) @@ -179,11 +194,33 @@ defmodule Lain do } def rss_date_format(date_string) do date = Date.from_iso8601!(date_string) + {:ok, datetime} = DateTime.new(date, %Time{hour: 0, minute: 0, second: 0}) - day = Map.get(@days_of_week, Date.day_of_week(date)) - month = Map.get(@months, date.month) + DateTime.to_iso8601(datetime) + end - "#{day}, #{date.day} #{month} #{date.year} 00:00:00 -0600" + def sanitize_html_for_rss(body_string) do + html = Floki.parse_fragment!(body_string) + + Floki.traverse_and_update(html, fn + {"iframe", _attrs, _children} = node -> + [src | _] = Floki.attribute(node, "src") + [title | _] = Floki.attribute(node, "title") + {"a", [{"href", src || "#"}], [title]} + + other -> + other + end) + |> Floki.raw_html() + end + + def xml_escape(xml_string) do + xml_string + |> String.replace("&", "&") + |> String.replace("<", "<") + |> String.replace(">", ">") + |> String.replace("\"", """) + |> String.replace("'", "'") end def make_sitemap(posts, path) do @@ -202,6 +239,15 @@ defmodule Lain do |> Stream.run() end + def render_template(path, assigns) do + EEx.eval_file(path, [assigns: assigns], + engine: Phoenix.LiveView.TagEngine, + tag_handler: Phoenix.LiveView.HTMLEngine, + caller: __ENV__, + source: File.read!(path) + ) + end + attr(:path, :string, required: true) attr(:title, :string, required: true) attr(:slug, :string, required: true) @@ -218,24 +264,13 @@ defmodule Lain do assigns = %{assigns | inner_block: inner_block} - EEx.eval_file(path, [assigns: assigns], - engine: Phoenix.LiveView.TagEngine, - tag_handler: Phoenix.LiveView.HTMLEngine, - caller: __ENV__, - source: File.read!(path) - ) + render_template(path, assigns) end - def menu(assigns) do - ~H""" - - """ + def menu(%{path: root_path} = assigns) do + path = Path.join([root_path, "templates", "menu.html.heex"]) + + render_template(path, assigns) end def write_page(%{slug: slug} = assigns, root_path) do @@ -248,16 +283,11 @@ defmodule Lain do Path.join([root_path, "build", slug, "index.html"]) end - ~H""" - <.layout path={root_path} title={@title} slug={@slug} description={assigns[:description]}> -
-
-

<%= @title %>

- <%= {:safe, @body} %> -
-
- - """ + assigns = Map.put(assigns, :root_path, root_path) + + root_path + |> Path.join("templates/page.html.heex") + |> render_template(assigns) |> rendered_to_string() |> then(&File.write!(path, &1)) end diff --git a/lib/lain/cli.ex b/lib/lain/cli.ex index 6f5f96b..ff4217d 100644 --- a/lib/lain/cli.ex +++ b/lib/lain/cli.ex @@ -10,30 +10,34 @@ defmodule Lain.CLI do @cacerts File.read!(@certpath) def main([command, path]) do - Application.put_env( - :tree_sitter, - :cacerts_path, - Path.join([File.cwd!(), "_build", "castore.pem"]) - ) - Application.put_env( :tailwind, :cacerts_path, Path.join([File.cwd!(), "_build", "castore.pem"]) ) - Application.ensure_all_started([:castore, :inets, :ssl, :tailwind]) - - args = [ + tailwind_args = [ "--config=#{Path.join(Path.expand(path), "tailwind.config.js")}", "--input=#{Path.join(Path.expand(path), "assets/app.css")}", "--output=#{Path.join(Path.expand(path), "build/assets/app.css")}", "--minify" ] + Application.put_all_env( + tailwind: [ + version: "3.2.7", + default: [ + args: tailwind_args, + cd: Path.expand(path) + ] + ] + ) + + Application.ensure_all_started([:castore, :inets, :ssl, :tailwind, :autumn]) + File.write!(Path.join("_build", "castore.pem"), @cacerts) - Tailwind.install_and_run(:default, args) + Tailwind.install_and_run(:default, tailwind_args) case command do "build" -> @@ -41,6 +45,9 @@ defmodule Lain.CLI do "clean" -> Lain.clean(path) + + "serve" -> + Lain.serve(path) end end end diff --git a/lib/lain/dev_server.ex b/lib/lain/dev_server.ex index 541c1b5..52b935a 100644 --- a/lib/lain/dev_server.ex +++ b/lib/lain/dev_server.ex @@ -3,7 +3,7 @@ defmodule PreDotDn.DevServer do plug(Plug.Logger) plug(:rewrite_dirs) - plug(Plug.Static, at: "", from: "priv/static") + plug(Plug.Static, at: "", from: {PreDotDn.DevServer, :path, []}) get "/health" do conn |> resp(200, "ok") |> halt() @@ -12,6 +12,10 @@ defmodule PreDotDn.DevServer do plug(:match) plug(:dispatch) + def path() do + Application.get_env(:lain, :built_path) + end + def rewrite_dirs(conn, _) do is_directory = case Enum.reverse(conn.path_info) do diff --git a/lib/lain/link_log.ex b/lib/lain/link_log.ex index 7b25f74..088be7e 100644 --- a/lib/lain/link_log.ex +++ b/lib/lain/link_log.ex @@ -1,5 +1,4 @@ defmodule Lain.LinkLog do - alias Lain.Markdown use Phoenix.Component import Phoenix.LiveViewTest, only: [rendered_to_string: 1] @@ -8,8 +7,10 @@ defmodule Lain.LinkLog do make_feed(links, path) + make_stylesheet(path) + links - |> make_index() + |> make_index(path) |> Lain.write_page(path) end @@ -51,25 +52,42 @@ defmodule Lain.LinkLog do assigns: assigns ) - path = Path.join([path, "build", "link-log", "feed.rss"]) + dir_path = Path.join([path, "build", "link-log"]) + File.mkdir_p!(dir_path) + path = Path.join([dir_path, "feed.rss"]) File.write!(path, body) end - def make_index(links) do - assigns = %{links: links} + def make_stylesheet(path) do + tailwind_args = [ + "--config=#{Path.join(Path.expand(path), "tailwind.config.js")}", + "--input=#{Path.join(Path.expand(path), "assets/app.css")}", + "--output=#{Path.join(Path.expand(path), "build/assets/app.css")}", + "--minify" + ] + + Application.put_all_env( + tailwind: [ + version: "3.2.7", + default: [ + args: tailwind_args, + cd: Path.expand(path) + ] + ] + ) + + Tailwind.install_and_run(:default, tailwind_args) + end + + def make_index(links, path) do + path = Path.join([path, "templates", "link.html.heex"]) + assigns = %{links: links, path: path} body = ~H""" <%= for link <- @links do %> -
- - <%= link["emoji"] || "🔗"%> <%= link["name"] %> - - -
+ <%= Lain.render_template(@path, %{link: link}) %> <% end %> """ |> rendered_to_string() diff --git a/lib/lain/markdown.ex b/lib/lain/markdown.ex index 91e4983..ea36963 100644 --- a/lib/lain/markdown.ex +++ b/lib/lain/markdown.ex @@ -1,5 +1,12 @@ defmodule Lain.Markdown do - @default_opts [pure_links: true, wikilinks: true, inner_html: false] + @default_opts [ + pure_links: true, + wikilinks: true, + inner_html: false, + escape: false, + math: true, + smartypants: true + ] def render(source, opts \\ []) do opts = Keyword.merge(@default_opts, opts, fn _key, _default_value, value -> value end) @@ -17,7 +24,12 @@ defmodule Lain.Markdown do other -> other end - |> Earmark.Transform.transform() + |> Earmark.Transform.transform(%{ + initial_indent: 0, + indent: 2, + compact_output: false, + escape: false + }) end defp transformer({"a", attrs, ignored, meta}) do @@ -60,14 +72,9 @@ defmodule Lain.Markdown do defp highlight([text], lang), do: highlight(text, lang) defp highlight(text, lang) do - {:ok, highlight} = TreeSitter.highlight_html(text, lang || "plain") + highlight = Autumn.highlight!(text, language: lang || "plain") - [_preamble, rest] = String.split(highlight, "") - [tbody, _rest] = String.split(rest, "
") - - table = "\n#{tbody}\n
" - - {"code", [], [table], %{verbatim: true}} + {"div", [], [highlight], %{verbatim: true}} end def floki2earmark(list) when is_list(list) do diff --git a/lib/lain/static.ex b/lib/lain/static.ex index e8a54eb..6a8875f 100644 --- a/lib/lain/static.ex +++ b/lib/lain/static.ex @@ -1,10 +1,11 @@ defmodule Lain.Static do def run(path) do path + |> Path.expand() |> Path.join("static/**/*") |> Path.wildcard() |> Enum.each(fn path -> - build_path = String.replace(path, "site", "priv") + build_path = String.replace(path, "static", "build") if File.dir?(path) do File.mkdir_p!(build_path) diff --git a/lib/mix/tasks/lain/dev_server.ex b/lib/mix/tasks/lain/dev_server.ex index 16f5e88..160565c 100644 --- a/lib/mix/tasks/lain/dev_server.ex +++ b/lib/mix/tasks/lain/dev_server.ex @@ -2,7 +2,6 @@ defmodule Mix.Tasks.Lain.DevServer do use Mix.Task def run(_args) do - Application.put_env(:lain, :serve_endpoints, true, persistent: true) - Mix.Tasks.Run.run(["--no-halt"]) + Lain.serve(".") end end diff --git a/mix.exs b/mix.exs index 62ec9b3..f8c288e 100644 --- a/mix.exs +++ b/mix.exs @@ -9,7 +9,7 @@ defmodule Lain.MixProject do start_permanent: Mix.env() == :prod, aliases: aliases(), deps: deps(), - escript: [main_module: Lain.CLI, include_priv_for: [:castore]] + escript: [main_module: Lain.CLI, include_priv_for: [:castore, :autumn]] ] end @@ -17,21 +17,21 @@ defmodule Lain.MixProject do def application do [ mod: {Lain.Application, []}, - extra_applications: [:castore, :logger] + extra_applications: [:castore, :logger, :autumn] ] end # Run "mix help deps" to learn about dependencies. defp deps do [ + {:autumn, path: "~/projects/autumn"}, {:yaml_elixir, "~> 2.9.0"}, {:esbuild, "~> 0.7.1"}, {:tailwind, "~> 0.2.1"}, {:phoenix_live_view, "~> 1.0.0"}, {:rustler, "~> 0.29.1"}, - {:tree_sitter, "~> 0.0.3"}, - {:earmark_parser, "~> 1.4"}, - {:earmark, "~> 1.4"}, + {:earmark_parser, "~> 1.4.43"}, + {:earmark, "~> 1.4.43"}, {:floki, "~> 0.36"}, {:html_sanitize_ex, "~> 1.4"}, {:phoenix_html, "~> 3.3.2"}, @@ -47,7 +47,8 @@ defmodule Lain.MixProject do def aliases() do [ "assets.build": ["esbuild default --minify", "tailwind default --minify"], - serve: ["do esbuild serve --serve + tailwind default --watch"] + serve: ["do esbuild serve --serve + tailwind default --watch"], + install: ["escript.build", "escript.install"] ] end end diff --git a/mix.lock b/mix.lock index e9d8b97..aa240ba 100644 --- a/mix.lock +++ b/mix.lock @@ -1,8 +1,9 @@ %{ + "autumn": {:hex, :autumn, "0.3.0", "5498696364c726563fc93f5f6d4bfc3739916cd29a63df8cbac3e957d00f53ac", [:mix], [{:rustler, "~> 0.29", [hex: :rustler, repo: "hexpm", optional: false]}, {:rustler_precompiled, "~> 0.6", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "cf78e150315cbe4b194f6ef1cfd9007db95ce47013c9a7e4305297ef3f17cbb0"}, "bandit": {:hex, :bandit, "1.6.4", "59cbc8e02f84fcad967bfed6b8a8261821c93a7ec4f835b46d1846b1120a91ec", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "8e156c009a77bb100fd78d5408684d01df1526f549c42614f8f9f27f44f1f7a7"}, - "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, + "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"}, "earmark": {:hex, :earmark, "1.4.43", "2024a0e9fe9bd5ef78fb9c87517de6c6d7deaf1cffdf6572fac3dd49cb34c433", [:mix], [], "hexpm", "958011ea938bc4018797bda3f8d0c871ab04621785bedc1e7188fb079dea2f5b"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.35", "437773ca9384edf69830e26e9e7b2e0d22d2596c4a6b17094a3b29f01ea65bb8", [:mix], [], "hexpm", "8652ba3cb85608d0d7aa2d21b45c6fad4ddc9a1f9a1f1b30ca3a246f0acc33f6"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"}, "esbuild": {:hex, :esbuild, "0.7.1", "fa0947e8c3c3c2f86c9bf7e791a0a385007ccd42b86885e8e893bdb6631f5169", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "66661cdf70b1378ee4dc16573fcee67750b59761b2605a0207c267ab9d19f13c"}, "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, @@ -18,12 +19,12 @@ "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "rustler": {:hex, :rustler, "0.29.1", "880f20ae3027bd7945def6cea767f5257bc926f33ff50c0d5d5a5315883c084d", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "109497d701861bfcd26eb8f5801fe327a8eef304f56a5b63ef61151ff44ac9b6"}, + "rustler_precompiled": {:hex, :rustler_precompiled, "0.8.2", "5f25cbe220a8fac3e7ad62e6f950fcdca5a5a5f8501835d2823e8c74bf4268d5", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "63d1bd5f8e23096d1ff851839923162096364bac8656a4a3c00d1fff8e83ee0a"}, "sitemapper": {:hex, :sitemapper, "0.7.0", "4aee7930327a9a01b1c9b81d1d42f60c1a295e9f420108eb2d130c317415abd7", [:mix], [{:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:xml_builder, "~> 2.1", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm", "60f7a684e5e9fe7f10ac5b69f48b0be2bcbba995afafcb3c143fc0c8ef1f223f"}, "tailwind": {:hex, :tailwind, "0.2.1", "83d8eadbe71a8e8f67861fe7f8d51658ecfb258387123afe4d9dc194eddc36b0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "e8a13f6107c95f73e58ed1b4221744e1eb5a093cd1da244432067e19c8c9a277"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "thousand_island": {:hex, :thousand_island, "1.3.9", "095db3e2650819443e33237891271943fad3b7f9ba341073947581362582ab5a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25ab4c07badadf7f87adb4ab414e0ed374e5f19e72503aa85132caa25776e54f"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, - "tree_sitter": {:hex, :tree_sitter, "0.0.2", "33218eeb40bf897f84d163a07b2373742db7dfee2e6601a60b5733fc6f33656f", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8bf71f50a7b1d0ac4b8936fc55d50a21386b8b319f3ceb6f59f1377314e8bb5d"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, "xml_builder": {:hex, :xml_builder, "2.2.0", "cc5f1eeefcfcde6e90a9b77fb6c490a20bc1b856a7010ce6396f6da9719cbbab", [:mix], [], "hexpm", "9d66d52fb917565d358166a4314078d39ef04d552904de96f8e73f68f64a62c9"},