feat: Switch away from escript as the method, just use the app directory

This commit is contained in:
Robert Prehn 2025-03-31 18:12:15 -05:00
parent bc1526316f
commit 7d9715d1bd
No known key found for this signature in database
11 changed files with 168 additions and 106 deletions

View file

@ -7,7 +7,6 @@ pages:
- mix deps.get - mix deps.get
- mix esbuild.install --if-missing - mix esbuild.install --if-missing
- mix tailwind.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) - (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 assets.build
- mix lain.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. # I wish we could just depend on the pages:deploy job that GitLab inserts, but apparently we can't reference it.
wait: wait:
needs: needs:
- "pages" - "pages"
script: script:
- sleep 10 - sleep 10
ping: ping:
needs: needs:
- "wait" - "wait"
script: script:
- mix local.hex --force - mix local.hex --force
- mix local.rebar --force - mix local.rebar --force
- mix deps.get - mix deps.get
- mix compile - mix compile
- mix lain.ping - mix lain.ping

View file

@ -25,11 +25,6 @@ config :esbuild,
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} 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, config :lain,
host: "pre.hn", host: "pre.hn",
base: "https://pre.hn/" base: "https://pre.hn/"

View file

@ -12,8 +12,6 @@ defmodule Lain do
alias Lain.Frontmatter alias Lain.Frontmatter
alias Lain.Markdown alias Lain.Markdown
@base_caller __ENV__
def read(path) do def read(path) do
path path
|> File.read!() |> File.read!()
@ -53,9 +51,19 @@ defmodule Lain do
end) end)
end end
def run(path) do def serve(path) do
TreeSitter.install_and_run(["-V"]) 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 = posts =
path path
|> Path.join("**/*.md") |> Path.join("**/*.md")
@ -77,7 +85,7 @@ defmodule Lain do
make_feed(posts, path) make_feed(posts, path)
make_sitemap(posts, path) make_sitemap(posts, path)
index = make_index(posts) index = make_index(posts, path)
Lain.LinkLog.run(path) Lain.LinkLog.run(path)
Lain.Static.run(path) Lain.Static.run(path)
@ -91,7 +99,12 @@ defmodule Lain do
|> Enum.into(%{}) |> Enum.into(%{})
end 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} assigns = %{posts: posts}
body = body =
@ -123,29 +136,31 @@ defmodule Lain do
EEx.eval_string( EEx.eval_string(
""" """
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<feed version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <feed xmlns="http://www.w3.org/2005/Atom">
<title>pre.hn</title> <id>https://pre.hn/feed.rss</id>
<subtitle>Robert Prehn's personal blog.</subtitle> <updated><%= DateTime.utc_now() |> DateTime.to_iso8601() %></updated>
<link rel="alternative" type="text/html">https://pre.hn</link> <title>pre.hn</title>
<link rel="self" type="application/rss+xml">https://pre.hn/feed.rss</link> <subtitle>Robert Prehn's personal blog.</subtitle>
<link href="https://pre.hn/feed.rss" rel="self" type="application/rss+xml"></link>
<%= for post <- @posts do %> <%= for post <- @posts do %>
<entry> <entry>
<title><%= HtmlSanitizeEx.strip_tags(post.title) %></title> <title><%= HtmlSanitizeEx.strip_tags(post.title) %></title>
<content type="xhtml" xml:lang="en" <content type="html"><%= post.body |> sanitize_html_for_rss.() |> xml_escape.() %>
xml:base="https://pre.hn/"><![CDATA[<html><body> </content>
<%= post.body %>
</body></html>]]>
</description>
<published><%= rss_date_format.(post.date) %></published> <published><%= rss_date_format.(post.date) %></published>
<updated><%= rss_date_format.(post[:updated_at] || post.date) %></updated>
<author><name>Robert Prehn</name></author>
<id><%= @base %><%= post.slug %>/</id> <id><%= @base %><%= post.slug %>/</id>
<link rel="alternate" type="text/html"><%= @base %><%= post.slug %>/</link> <link type="text/html" href="<%= @base %><%= post.slug %>/"></link>
</entry> </entry>
<% end %> <% end %>
</feed> </feed>
""", """,
assigns: assigns, 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"])) File.mkdir_p!(Path.join([path, "build"]))
@ -179,11 +194,33 @@ defmodule Lain do
} }
def rss_date_format(date_string) do def rss_date_format(date_string) do
date = Date.from_iso8601!(date_string) 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)) DateTime.to_iso8601(datetime)
month = Map.get(@months, date.month) 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("&", "&amp;")
|> String.replace("<", "&lt;")
|> String.replace(">", "&gt;")
|> String.replace("\"", "&quot;")
|> String.replace("'", "&apos;")
end end
def make_sitemap(posts, path) do def make_sitemap(posts, path) do
@ -202,6 +239,15 @@ defmodule Lain do
|> Stream.run() |> Stream.run()
end 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(:path, :string, required: true)
attr(:title, :string, required: true) attr(:title, :string, required: true)
attr(:slug, :string, required: true) attr(:slug, :string, required: true)
@ -218,24 +264,13 @@ defmodule Lain do
assigns = %{assigns | inner_block: inner_block} assigns = %{assigns | inner_block: inner_block}
EEx.eval_file(path, [assigns: assigns], render_template(path, assigns)
engine: Phoenix.LiveView.TagEngine,
tag_handler: Phoenix.LiveView.HTMLEngine,
caller: __ENV__,
source: File.read!(path)
)
end end
def menu(assigns) do def menu(%{path: root_path} = assigns) do
~H""" path = Path.join([root_path, "templates", "menu.html.heex"])
<nav class="text-light flex justify-center">
<div class="container flex"> render_template(path, assigns)
<a class={"box rounded-tr !mt-[28px] !-mb-[2px] !border-b-light #{if @slug == "index", do: "current"}"} href="/">About Me</a>
<a class={"box rounded-tr !mt-[28px] !-mb-[2px] !border-b-light #{if @slug == "blog", do: "current"}"} href="/blog">Blog</a>
<a class={"box rounded-tr !mt-[28px] !-mb-[2px] !border-b-light #{if @slug == "link-log", do: "current"}"} href="/link-log">Link Log</a>
</div>
</nav>
"""
end end
def write_page(%{slug: slug} = assigns, root_path) do def write_page(%{slug: slug} = assigns, root_path) do
@ -248,16 +283,11 @@ defmodule Lain do
Path.join([root_path, "build", slug, "index.html"]) Path.join([root_path, "build", slug, "index.html"])
end end
~H""" assigns = Map.put(assigns, :root_path, root_path)
<.layout path={root_path} title={@title} slug={@slug} description={assigns[:description]}>
<main class="container mx-auto z-10 relative"> root_path
<section class="box border border-light"> |> Path.join("templates/page.html.heex")
<h1><%= @title %></h1> |> render_template(assigns)
<%= {:safe, @body} %>
</section>
</main>
</.layout>
"""
|> rendered_to_string() |> rendered_to_string()
|> then(&File.write!(path, &1)) |> then(&File.write!(path, &1))
end end

View file

@ -10,30 +10,34 @@ defmodule Lain.CLI do
@cacerts File.read!(@certpath) @cacerts File.read!(@certpath)
def main([command, path]) do def main([command, path]) do
Application.put_env(
:tree_sitter,
:cacerts_path,
Path.join([File.cwd!(), "_build", "castore.pem"])
)
Application.put_env( Application.put_env(
:tailwind, :tailwind,
:cacerts_path, :cacerts_path,
Path.join([File.cwd!(), "_build", "castore.pem"]) Path.join([File.cwd!(), "_build", "castore.pem"])
) )
Application.ensure_all_started([:castore, :inets, :ssl, :tailwind]) tailwind_args = [
args = [
"--config=#{Path.join(Path.expand(path), "tailwind.config.js")}", "--config=#{Path.join(Path.expand(path), "tailwind.config.js")}",
"--input=#{Path.join(Path.expand(path), "assets/app.css")}", "--input=#{Path.join(Path.expand(path), "assets/app.css")}",
"--output=#{Path.join(Path.expand(path), "build/assets/app.css")}", "--output=#{Path.join(Path.expand(path), "build/assets/app.css")}",
"--minify" "--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) File.write!(Path.join("_build", "castore.pem"), @cacerts)
Tailwind.install_and_run(:default, args) Tailwind.install_and_run(:default, tailwind_args)
case command do case command do
"build" -> "build" ->
@ -41,6 +45,9 @@ defmodule Lain.CLI do
"clean" -> "clean" ->
Lain.clean(path) Lain.clean(path)
"serve" ->
Lain.serve(path)
end end
end end
end end

View file

@ -3,7 +3,7 @@ defmodule PreDotDn.DevServer do
plug(Plug.Logger) plug(Plug.Logger)
plug(:rewrite_dirs) plug(:rewrite_dirs)
plug(Plug.Static, at: "", from: "priv/static") plug(Plug.Static, at: "", from: {PreDotDn.DevServer, :path, []})
get "/health" do get "/health" do
conn |> resp(200, "ok") |> halt() conn |> resp(200, "ok") |> halt()
@ -12,6 +12,10 @@ defmodule PreDotDn.DevServer do
plug(:match) plug(:match)
plug(:dispatch) plug(:dispatch)
def path() do
Application.get_env(:lain, :built_path)
end
def rewrite_dirs(conn, _) do def rewrite_dirs(conn, _) do
is_directory = is_directory =
case Enum.reverse(conn.path_info) do case Enum.reverse(conn.path_info) do

View file

@ -1,5 +1,4 @@
defmodule Lain.LinkLog do defmodule Lain.LinkLog do
alias Lain.Markdown
use Phoenix.Component use Phoenix.Component
import Phoenix.LiveViewTest, only: [rendered_to_string: 1] import Phoenix.LiveViewTest, only: [rendered_to_string: 1]
@ -8,8 +7,10 @@ defmodule Lain.LinkLog do
make_feed(links, path) make_feed(links, path)
make_stylesheet(path)
links links
|> make_index() |> make_index(path)
|> Lain.write_page(path) |> Lain.write_page(path)
end end
@ -51,25 +52,42 @@ defmodule Lain.LinkLog do
assigns: assigns 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) File.write!(path, body)
end end
def make_index(links) do def make_stylesheet(path) do
assigns = %{links: links} 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 = body =
~H""" ~H"""
<%= for link <- @links do %> <%= for link <- @links do %>
<div style="margin-bottom: 1rem"> <%= Lain.render_template(@path, %{link: link}) %>
<a class="text-ellipsis overflow-hidden inline-block" style="padding-left: 2rem;text-indent: -2rem;" href={link["url"]}>
<%= link["emoji"] || "🔗"%> <%= link["name"] %>
</a>
<div class="link-log-body">
<%= {:safe, Markdown.render(link["summary"])} %>
</div>
</div>
<% end %> <% end %>
""" """
|> rendered_to_string() |> rendered_to_string()

View file

@ -1,5 +1,12 @@
defmodule Lain.Markdown do 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 def render(source, opts \\ []) do
opts = Keyword.merge(@default_opts, opts, fn _key, _default_value, value -> value end) opts = Keyword.merge(@default_opts, opts, fn _key, _default_value, value -> value end)
@ -17,7 +24,12 @@ defmodule Lain.Markdown do
other -> other ->
other other
end end
|> Earmark.Transform.transform() |> Earmark.Transform.transform(%{
initial_indent: 0,
indent: 2,
compact_output: false,
escape: false
})
end end
defp transformer({"a", attrs, ignored, meta}) do 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: highlight(text, lang)
defp highlight(text, lang) do 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, "<table>") {"div", [], [highlight], %{verbatim: true}}
[tbody, _rest] = String.split(rest, "</table>")
table = "<table>\n#{tbody}\n</table>"
{"code", [], [table], %{verbatim: true}}
end end
def floki2earmark(list) when is_list(list) do def floki2earmark(list) when is_list(list) do

View file

@ -1,10 +1,11 @@
defmodule Lain.Static do defmodule Lain.Static do
def run(path) do def run(path) do
path path
|> Path.expand()
|> Path.join("static/**/*") |> Path.join("static/**/*")
|> Path.wildcard() |> Path.wildcard()
|> Enum.each(fn path -> |> Enum.each(fn path ->
build_path = String.replace(path, "site", "priv") build_path = String.replace(path, "static", "build")
if File.dir?(path) do if File.dir?(path) do
File.mkdir_p!(build_path) File.mkdir_p!(build_path)

View file

@ -2,7 +2,6 @@ defmodule Mix.Tasks.Lain.DevServer do
use Mix.Task use Mix.Task
def run(_args) do def run(_args) do
Application.put_env(:lain, :serve_endpoints, true, persistent: true) Lain.serve(".")
Mix.Tasks.Run.run(["--no-halt"])
end end
end end

13
mix.exs
View file

@ -9,7 +9,7 @@ defmodule Lain.MixProject do
start_permanent: Mix.env() == :prod, start_permanent: Mix.env() == :prod,
aliases: aliases(), aliases: aliases(),
deps: deps(), deps: deps(),
escript: [main_module: Lain.CLI, include_priv_for: [:castore]] escript: [main_module: Lain.CLI, include_priv_for: [:castore, :autumn]]
] ]
end end
@ -17,21 +17,21 @@ defmodule Lain.MixProject do
def application do def application do
[ [
mod: {Lain.Application, []}, mod: {Lain.Application, []},
extra_applications: [:castore, :logger] extra_applications: [:castore, :logger, :autumn]
] ]
end end
# Run "mix help deps" to learn about dependencies. # Run "mix help deps" to learn about dependencies.
defp deps do defp deps do
[ [
{:autumn, path: "~/projects/autumn"},
{:yaml_elixir, "~> 2.9.0"}, {:yaml_elixir, "~> 2.9.0"},
{:esbuild, "~> 0.7.1"}, {:esbuild, "~> 0.7.1"},
{:tailwind, "~> 0.2.1"}, {:tailwind, "~> 0.2.1"},
{:phoenix_live_view, "~> 1.0.0"}, {:phoenix_live_view, "~> 1.0.0"},
{:rustler, "~> 0.29.1"}, {:rustler, "~> 0.29.1"},
{:tree_sitter, "~> 0.0.3"}, {:earmark_parser, "~> 1.4.43"},
{:earmark_parser, "~> 1.4"}, {:earmark, "~> 1.4.43"},
{:earmark, "~> 1.4"},
{:floki, "~> 0.36"}, {:floki, "~> 0.36"},
{:html_sanitize_ex, "~> 1.4"}, {:html_sanitize_ex, "~> 1.4"},
{:phoenix_html, "~> 3.3.2"}, {:phoenix_html, "~> 3.3.2"},
@ -47,7 +47,8 @@ defmodule Lain.MixProject do
def aliases() do def aliases() do
[ [
"assets.build": ["esbuild default --minify", "tailwind default --minify"], "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
end end

View file

@ -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"}, "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": {: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"}, "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"}, "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"},
"hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, "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": {: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"}, "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": {: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"}, "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"}, "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"}, "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"}, "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"}, "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": {: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"}, "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"}, "xml_builder": {:hex, :xml_builder, "2.2.0", "cc5f1eeefcfcde6e90a9b77fb6c490a20bc1b856a7010ce6396f6da9719cbbab", [:mix], [], "hexpm", "9d66d52fb917565d358166a4314078d39ef04d552904de96f8e73f68f64a62c9"},