improvement: change all kinds of things, build docs statically

This commit is contained in:
Zach Daniel 2023-01-17 21:33:49 -05:00
parent 9ac964694f
commit 48d999e125
29 changed files with 1527 additions and 448 deletions

View file

@ -29,8 +29,8 @@ COPY ./mix.lock .
COPY ./config/config.exs config/config.exs
COPY ./config/prod.exs config/prod.exs
COPY ./assets/tailwind.colors.json ./assets/tailwind.colors.json
RUN mix deps.get && \
mix deps.compile
RUN mix deps.get
RUN mix deps.compile
COPY ./lib ./lib
COPY ./priv ./priv
COPY ./assets ./assets

View file

@ -7,6 +7,20 @@
# General application configuration
import Config
config :ash_hq, AshHq.Docs.Cache,
# When using :shards as backend
# backend: :shards,
# GC interval for pushing new generation: 12 hrs
gc_interval: :timer.hours(12),
# Max 1 million entries in cache
max_size: 1_000_000,
# Max 2 GB of memory
allocated_memory: 1_000_000_000,
# GC min timeout: 10 sec
gc_cleanup_min_timeout: :timer.seconds(10),
# GC max timeout: 10 min
gc_cleanup_max_timeout: :timer.minutes(10)
config :ash_hq,
ecto_repos: [AshHq.Repo]

View file

@ -22,6 +22,10 @@ port = String.to_integer(System.get_env("PORT") || "4000")
config :ash_hq, url: System.get_env("ASH_HQ_URL") || "http://localhost:4000"
if config_env() != :dev do
config :logger, level: String.to_existing_atom(System.get_env("LOG_LEVEL") || "info")
end
if config_env() == :prod do
database_url =
System.get_env("DATABASE_URL") ||

View file

@ -166,10 +166,9 @@ defmodule AshHq.Accounts.User do
validate(confirm(:password, :password_confirmation))
validate {AshHq.Accounts.User.Validations.ValidateCurrentPassword,
argument: :current_password} do
only_when_valid?(true)
before_action?(true)
validate {AshAuthentication.Strategy.Password.PasswordValidation, password_argument: :current_password} do
only_when_valid? true
before_action? true
end
change(set_context(%{strategy_name: :password}))
@ -184,10 +183,9 @@ defmodule AshHq.Accounts.User do
allow_nil?(false)
end
validate {AshHq.Accounts.User.Validations.ValidateCurrentPassword,
argument: :current_password} do
only_when_valid?(true)
before_action?(true)
validate {AshAuthentication.Strategy.Password.PasswordValidation, password_argument: :current_password} do
only_when_valid? true
before_action? true
end
end

View file

@ -1,32 +0,0 @@
defmodule AshHq.Accounts.User.Validations.ValidateCurrentPassword do
@moduledoc """
Confirms that the provided password is valid.
This is useful for actions that should only be able to be taken on a given user if you know
their password (like changing the email, for example).
"""
use Ash.Resource.Validation
@impl true
def validate(changeset, opts) do
strategy = AshAuthentication.Info.strategy!(changeset.resource, :password)
plaintext_password = Ash.Changeset.get_argument(changeset, opts[:argument])
hashed_password = Map.get(changeset.data, strategy.hashed_password_field)
if hashed_password do
if strategy.hash_provider.valid?(plaintext_password, hashed_password) do
:ok
else
{:error, [field: opts[:argument], message: "is incorrect"]}
end
else
{:error,
[
field: opts[:argument],
message:
"has not been set. If you logged in with github and would like to set a password, please log out and use the forgot password flow."
]}
end
end
end

View file

@ -32,6 +32,7 @@ defmodule AshHq.Application do
AshHqWeb.Telemetry,
# Start the PubSub system
{Phoenix.PubSub, name: AshHq.PubSub},
{AshHq.Docs.Cache, []},
# Start the Endpoint (http/https)
AshHqWeb.Endpoint
# Start a worker by calling: AshHq.Worker.start_link(arg)

5
lib/ash_hq/docs/cache.ex Normal file
View file

@ -0,0 +1,5 @@
defmodule AshHq.Docs.Cache do
use Nebulex.Cache,
otp_app: :ash_hq,
adapter: Nebulex.Adapters.Local
end

View file

@ -4,13 +4,16 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Changes.RenderMarkdown do
"""
require Logger
require Ash.Query
use Ash.Resource.Change
use AshHqWeb, :verified_routes
def change(changeset, opts, _) do
Ash.Changeset.before_action(changeset, fn changeset ->
if Ash.Changeset.changing_attribute?(changeset, opts[:source]) do
source = Ash.Changeset.get_attribute(changeset, opts[:source])
text = remove_ash_hq_hidden_content(source)
libraries = AshHq.Docs.Library.read!()
text = process_text(source)
attribute = Ash.Resource.Info.attribute(changeset.resource, opts[:destination])
@ -21,8 +24,39 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Changes.RenderMarkdown do
changeset
end
current_module =
cond do
changeset.resource == AshHq.Docs.Module ->
Ash.Changeset.get_attribute(changeset, :name)
changeset.resource == AshHq.Docs.Function ->
AshHq.Docs.get!(
AshHq.Docs.Module,
Ash.Changeset.get_attribute(changeset, :module_id)
).name
true ->
nil
end
current_library =
case Ash.Changeset.get_attribute(changeset, :library_version_id) do
nil ->
nil
library_version_id ->
AshHq.Docs.Library
|> Ash.Query.select(:name)
|> Ash.Query.filter(versions.id == ^library_version_id)
|> AshHq.Docs.read_one!()
|> Map.get(:name)
end
case AshHq.Docs.Extensions.RenderMarkdown.as_html(
text,
libraries,
current_library,
current_module,
AshHq.Docs.Extensions.RenderMarkdown.header_ids?(changeset.resource)
) do
{:error, html_doc, error_messages} ->
@ -67,12 +101,18 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Changes.RenderMarkdown do
end)
end
defp remove_ash_hq_hidden_content(nil), do: nil
defp remove_ash_hq_hidden_content(strings) when is_list(strings) do
Enum.map(strings, &remove_ash_hq_hidden_content/1)
@doc false
def process_text(text) when is_list(text) do
Enum.map(text, &process_text(&1))
end
def process_text(text) do
text
|> remove_ash_hq_hidden_content()
end
defp remove_ash_hq_hidden_content(nil), do: nil
defp remove_ash_hq_hidden_content(string) do
string
|> String.split(~r/\<\!---.*ash-hq-hide-start.*--\>/)

View file

@ -3,18 +3,23 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Highlighter do
# Copied *directly* from nimble_publisher
# https://github.com/dashbitco/nimble_publisher/blob/v0.1.2/lib/nimble_publisher/highlighter.ex
use AshHqWeb, :verified_routes
@doc """
Highlights all code block in an already generated HTML document.
"""
def highlight(html) when is_list(html) do
Enum.map(html, &highlight/1)
def highlight(html, libraries, current_library, current_module) when is_list(html) do
Enum.map(html, &highlight(&1, libraries, current_library, current_module))
end
def highlight(html) do
def highlight(html, libraries, current_library, current_module) do
html
|> Floki.parse_document!()
|> Floki.traverse_and_update(fn
{"pre", _, [{"code", attrs, [body]}]} when is_binary(body) ->
{"a", attrs, contents} ->
{"a", rewrite_href_attr(attrs, current_library, libraries), contents}
{"code", attrs, [body]} when is_binary(body) ->
lexer =
find_value_class(attrs, fn class ->
case Makeup.Registry.fetch_lexer_by_name(class) do
@ -29,10 +34,9 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Highlighter do
nil ->
if find_value_class(attrs, &(&1 == "inline")) do
{:keep, maybe_highlight_module(body)}
{:keep, maybe_highlight_module(body, libraries, current_module)}
else
{:keep,
~s(<pre class="code-pre"><code class="text-black dark:text-white">#{body}</code></pre>)}
{:keep, ~s(<code class="text-black dark:text-white">#{body}</code>)}
end
end
@ -42,6 +46,95 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Highlighter do
|> AshHq.Docs.Extensions.RenderMarkdown.RawHTML.raw_html(pretty: true)
end
defp rewrite_href_attr(attrs, current_library, libraries) do
Enum.map(attrs, fn
{"href", value} ->
{"href", rewrite_href(value, current_library, libraries)}
other ->
other
end)
end
@doc false
def rewrite_href(value, current_library, libraries) do
uri = URI.parse(value)
case {uri, Path.split(String.trim_leading(uri.path || "", "/"))} |> IO.inspect() do
{%{host: "hexdocs.pm"}, [library, guide]} ->
if Enum.any?(libraries, &(&1.name == library)) do
if String.ends_with?(guide, ".html") do
name =
guide
|> String.trim_trailing(".html")
|> AshHqWeb.DocRoutes.sanitize_name()
url(~p"/docs/guides/#{library}/latest/#{name}")
end
else
value
end
{%{host: "hexdocs.pm"}, [library]} ->
if Enum.any?(libraries, &(&1.name == library)) do
url(~p"/docs/#{library}/latest")
else
value
end
{%{host: "hex.pm"}, ["packages", library]} ->
if Enum.any?(libraries, &(&1.name == library)) do
url(~p"/docs/#{library}/latest")
else
value
end
{%{host: "github.com"}, [owner, library]} ->
if Enum.any?(libraries, &(&1.name == library && &1.repo_org == owner)) do
url(~p"/docs/#{library}/latest")
else
value
end
{%{host: "github.com"}, [owner, library, "blob", _, "documentation", guide]} ->
github_guide_link(value, libraries, owner, library, guide)
{%{host: "github.com"}, [owner, library, "tree", _, "documentation", guide]} ->
github_guide_link(value, libraries, owner, library, guide)
{%{host: nil}, ["documentation", _type, guide]} ->
github_guide_link(value, libraries, nil, current_library, guide)
{%{host: nil}, [guide]} ->
github_guide_link(value, libraries, nil, current_library, guide)
_ ->
value
end
end
defp github_guide_link(value, _libraries, _owner, nil, _guide) do
value
end
defp github_guide_link(value, libraries, owner, library, guide) do
guide = String.trim_trailing(guide, ".md")
if owner do
if Enum.any?(libraries, &(&1.name == library && &1.repo_org == owner)) do
url(~p"/docs/guides/#{library}/latest/#{guide}")
else
value
end
else
if Enum.any?(libraries, &(&1.name == library)) do
url(~p"/docs/guides/#{library}/latest/#{guide}")
else
value
end
end
end
defp find_value_class(attrs, func) do
Enum.find_value(attrs, fn
{"class", classes} ->
@ -54,7 +147,7 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Highlighter do
end)
end
defp maybe_highlight_module(code) do
def maybe_highlight_module(code, libraries, current_module) do
code_without_c =
case code do
"c:" <> rest ->
@ -73,19 +166,101 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Highlighter do
nil
end
try_parse_multi([
{~s(data-fun-type="callback"), code_without_c},
{~s(data-fun-type="type"), code_without_type},
{nil, code}
])
code_without_dsl =
case code do
"d:" <> rest ->
rest
_ ->
nil
end
try_parse_multi(
[
{"callback", code_without_c},
{"type", code_without_type},
{"dsl", code_without_dsl},
{nil, code}
],
libraries,
current_module
)
end
defp try_parse_multi([{_, nil} | rest]), do: try_parse_multi(rest)
defp try_parse_multi([{_, nil} | rest], libraries, current_module),
do: try_parse_multi(rest, libraries, current_module)
defp try_parse_multi([{text, code} | rest]) do
defp try_parse_multi([{"dsl", code} | rest], libraries, current_module) do
code = String.trim(code)
with [code | maybe_anchor] when length(rest) in [0, 1] <- String.split(code, "|", trim: true),
{module, dsl_path} <-
code
|> String.split(".")
|> Enum.split_while(&capitalized?/1) do
if module == [] and !current_module do
~s[<code class="inline">#{code}</code>]
else
anchor =
case maybe_anchor do
[] ->
""
option ->
"##{option}"
end
module =
case module do
[] ->
current_module
module ->
Enum.join(module, ".")
end
dsl_dots = Enum.join(dsl_path, ".")
dsl_path = Enum.join(dsl_path, "/")
code =
~s[<code class="inline maybe-dsl text-black dark:text-white" data-module="#{module}" data-dsl="#{dsl_dots}">#{code}</code>]
case library_for(module, libraries) do
nil ->
code
library ->
link =
url(
~p'/docs/dsl/#{library.name}/latest/#{AshHqWeb.DocRoutes.sanitize_name(module)}'
) <> "/" <> dsl_path <> anchor
~s[<a href="#{link}">#{code}</a>]
end
end
else
_ ->
~s[<code class="inline">#{code}</code>]
end
end
defp try_parse_multi([{type, code} | rest], libraries, current_module) do
case Code.string_to_quoted(code) do
{:ok, {fun, _, []}} when is_atom(fun) ->
~s[<code #{text} class="inline maybe-local-call text-black dark:text-white" data-fun="#{fun}">#{code}</code>]
arity = 0
if current_module do
function_href(
~s[<code #{function_type(type)} class="inline maybe-call text-black dark:text-white" data-module="#{current_module}" data-fun="#{fun}" data-arity="#{arity}">#{code}</code>],
libraries,
type,
current_module,
fun,
arity
)
else
~s[<code #{function_type(type)} class="inline maybe-local-call text-black dark:text-white" data-fun="#{fun}" data-arity="#{arity}">#{code}</code>]
end
{:ok,
{:/, _,
@ -94,19 +269,44 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Highlighter do
arity
]}}
when is_atom(fun) and is_integer(arity) ->
~s[<code #{text} class="inline maybe-call text-black dark:text-white" data-module="#{Enum.join(parts, ".")}" data-fun="#{fun}" data-arity="#{arity}">#{code}</code>]
function_href(
~s[<code #{function_type(type)} class="inline maybe-call text-black dark:text-white" data-module="#{Enum.join(parts, ".")}" data-fun="#{fun}" data-arity="#{arity}">#{code}</code>],
libraries,
type,
Enum.join(parts, "."),
fun,
arity
)
{:ok, {:/, _, [{fun, _, nil}, arity]}} when is_atom(fun) and is_integer(arity) ->
~s[<code #{text} class="inline maybe-local-call text-black dark:text-white" data-fun="#{fun}" data-arity="#{arity}">#{code}</code>]
function_href(
if current_module do
~s[<code #{function_type(type)} class="inline maybe-call text-black dark:text-white" data-module="#{current_module}" data-fun="#{fun}" data-arity="#{arity}">#{code}</code>]
else
~s[<code #{function_type(type)} class="inline maybe-local-call text-black dark:text-white" data-fun="#{fun}" data-arity="#{arity}">#{code}</code>]
end,
libraries,
type,
current_module,
fun,
arity
)
{:ok, {:__aliases__, _, parts}} ->
~s[<code #{text} class="inline maybe-module text-black dark:text-white" data-module="#{Enum.join(parts, ".")}">#{code}</code>]
module_name = Enum.join(parts, ".")
module_href(
~s[<code class="inline maybe-module text-black dark:text-white" data-module="#{module_name}">#{code}</code>],
libraries,
type,
module_name
)
_ ->
if rest == [] do
~s[<code class="inline text-black dark:text-white">#{code}</code>]
else
try_parse_multi(rest)
try_parse_multi(rest, libraries, current_module)
end
end
rescue
@ -114,6 +314,63 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Highlighter do
~s[<code class="inline">#{code}</code>]
end
defp capitalized?(string) do
str =
string
|> String.graphemes()
|> Enum.at(0)
|> Kernel.||("")
String.downcase(str) != str
end
defp function_type(nil), do: nil
defp function_type(type) do
"data-fun-type=\"#{type}\""
end
defp function_href(contents, _libraries, nil, _, _, _), do: contents
defp function_href(contents, _libraries, _, nil, _, _), do: contents
defp function_href(contents, libraries, type, module_name, fun, arity) do
case library_for(module_name, libraries) do
nil ->
contents
library ->
link =
url(
~p'/docs/module/#{library.name}/latest/#{AshHqWeb.DocRoutes.sanitize_name(module_name)}'
) <>
"##{type}-#{AshHqWeb.DocRoutes.sanitize_name(fun)}-#{arity}"
~s[<a href="#{link}">#{contents}</a>]
end
end
defp module_href(contents, libraries, _type, module_name) do
case library_for(module_name, libraries) do
nil ->
contents
library ->
link =
url(
~p'/docs/module/#{library.name}/latest/#{AshHqWeb.DocRoutes.sanitize_name(module_name)}'
)
~s[<a href="#{link}">#{contents}</a>]
end
end
defp library_for(module, libraries) do
Enum.find(libraries, fn library ->
Enum.any?(library.module_prefixes, &String.starts_with?(module, &1 <> "."))
end)
end
defp render_code(lang, lexer, lexer_opts, code) do
highlighted =
code
@ -142,7 +399,7 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Highlighter do
"{{mix_dep:#{value}}}"
end)
~s(<pre class="code-pre"><code class="makeup #{lang} highlight">#{highlighted}</code></pre>)
~s(<code class="makeup #{lang} highlight">#{highlighted}</code>)
end
entities = [{"&amp;", ?&}, {"&lt;", ?<}, {"&gt;", ?>}, {"&quot;", ?"}, {"&#39;", ?'}]

View file

@ -32,50 +32,33 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown do
Spark.Dsl.Extension.get_opt(resource, [:render_markdown], :header_ids?, [])
end
def render!(%resource{} = record, key, on_demand? \\ false) do
cond do
render_attributes(resource)[key] ->
Map.get(record, render_attributes(resource)[key])
def as_html!(text, libraries, current_module, add_ids? \\ true)
on_demand? ->
case Map.get(record, key) do
value when is_list(value) ->
Enum.map(value, fn value ->
as_html!(value || "", header_ids?(resource))
end)
value ->
as_html!(value || "", header_ids?(resource))
end
true ->
raise "#{resource} dos not render #{key} as markdown. Pass the `on_demand?` argument as `true` to render it dynamically."
end
end
def as_html!(text, add_ids? \\ true)
def as_html!(nil, _) do
def as_html!(nil, _, _, _) do
""
end
def as_html!(%Ash.NotLoaded{}, _) do
def as_html!(%Ash.NotLoaded{}, _libraries, _, _) do
""
end
def as_html!(text, add_ids?) when is_list(text) do
Enum.map(text, &as_html!(&1, add_ids?))
def as_html!(text, libraries, current_module, add_ids?) when is_list(text) do
Enum.map(text, &as_html!(&1, libraries, current_module, add_ids?))
end
def as_html!(text, add_ids?) do
def as_html!(text, libraries, current_library, current_module, add_ids?) do
text
|> Earmark.as_html!(opts(add_ids?))
|> AshHq.Docs.Extensions.RenderMarkdown.Highlighter.highlight()
|> AshHq.Docs.Extensions.RenderMarkdown.Highlighter.highlight(
libraries,
current_library,
current_module
)
end
def as_html(text, add_ids?) when is_list(text) do
def as_html(text, libraries, current_module, add_ids?) when is_list(text) do
Enum.reduce_while(text, {:ok, [], []}, fn text, {:ok, list, errors} ->
case as_html(text, add_ids?) do
case as_html(text, libraries, current_module, add_ids?) do
{:ok, text, new_errors} ->
{:cont, {:ok, [text | list], errors ++ new_errors}}
@ -92,12 +75,18 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown do
end
end
def as_html(text, add_ids?) do
def as_html(text, libraries, current_library, current_module, add_ids?) do
text
|> Earmark.as_html(opts(add_ids?))
|> case do
{:ok, html_doc, errors} ->
{:ok, AshHq.Docs.Extensions.RenderMarkdown.Highlighter.highlight(html_doc), errors}
{:ok,
AshHq.Docs.Extensions.RenderMarkdown.Highlighter.highlight(
html_doc,
libraries,
current_library,
current_module
), errors}
{:error, html_doc, errors} ->
{:error, html_doc, errors}
@ -105,7 +94,7 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown do
end
defp opts(true) do
[postprocessor: &add_ids/1]
[postprocessor: &add_ids/1, escape: false]
end
defp opts(_) do

View file

@ -46,7 +46,7 @@ defmodule AshHq.Docs.Importer do
|> Enum.reject(&String.starts_with?(&1, "/_build"))
|> Enum.join(":")
for %{name: name, latest_version: latest_version} = library <-
for %{name: name, latest_version: latest_version, mix_project: mix_project} = library <-
AshHq.Docs.Library.read!(load: :latest_version, query: query) do
latest_version =
if latest_version do
@ -83,22 +83,21 @@ defmodule AshHq.Docs.Importer do
result =
try do
with_retry("#{name}: #{version}", fn ->
{_, 0} =
System.cmd(
"elixir",
[
Path.join([:code.priv_dir(:ash_hq), "scripts", "build_dsl_docs.exs"]),
name,
version,
file
],
env: %{"PATH" => path_var}
)
{_, 0} =
System.cmd(
"elixir",
[
Path.join([:code.priv_dir(:ash_hq), "scripts", "build_dsl_docs.exs"]),
name,
version,
file,
mix_project || Macro.camelize(name) <> ".MixProject"
],
env: %{"PATH" => path_var}
)
output = File.read!(file)
:erlang.binary_to_term(Base.decode64!(String.trim(output)))
end)
output = File.read!(file)
:erlang.binary_to_term(Base.decode64!(String.trim(output)))
after
File.rm_rf!(file)
end
@ -125,7 +124,7 @@ defmodule AshHq.Docs.Importer do
id: id,
extensions: result[:extensions],
doc: result[:doc],
guides: add_text(result[:guides], library.name, version),
guides: result[:guides],
modules: result[:modules],
mix_tasks: result[:mix_tasks]
}
@ -136,83 +135,6 @@ defmodule AshHq.Docs.Importer do
end
end
# sobelow_skip ["Misc.BinToTerm", "Traversal.FileModule"]
defp add_text([], _, _), do: []
# sobelow_skip ["Misc.BinToTerm", "Traversal.FileModule"]
defp add_text(guides, name, version) do
path = Path.expand("tmp")
tarball_path = Path.expand(Path.join(["tmp", "tarballs"]))
tar_path = Path.join(tarball_path, "#{name}-#{version}.tar")
untar_path = Path.join(path, "#{name}-#{version}")
contents_untar_path = Path.join(path, "#{name}-#{version}/contents")
contents_tar_path = Path.join([path, "#{name}-#{version}", "contents.tar.gz"])
try do
File.rm_rf!(tar_path)
File.rm_rf!(contents_untar_path)
File.mkdir_p!(untar_path)
File.mkdir_p!(contents_untar_path)
{_, 0} =
System.cmd(
"curl",
[
"-L",
"--create-dirs",
"-o",
"#{tar_path}",
"https://repo.hex.pm/tarballs/#{name}-#{version}.tar"
]
)
{_, 0} =
System.cmd(
"tar",
[
"-xf",
tar_path,
"-C",
untar_path
],
cd: "tmp"
)
{_, 0} =
System.cmd(
"tar",
[
"-xzf",
contents_tar_path
],
cd: contents_untar_path
)
Enum.map(guides, fn %{path: path} = guide ->
contents =
contents_untar_path
|> Path.join(path)
|> File.read!()
Map.put(guide, :text, contents)
end)
after
File.rm_rf!(tar_path)
File.rm_rf!(untar_path)
end
end
defp with_retry(context, func, retries \\ 3) do
func.()
rescue
_e ->
if retries == 1 do
Logger.error("Failed to import: #{context}")
else
with_retry(context, func, retries - 1)
end
end
defp filter_by_version(versions, latest_version) do
if latest_version do
Enum.take_while(versions, fn version ->

View file

@ -27,10 +27,6 @@ defmodule AshHq.Docs.Dsl do
default []
end
attribute :links, :map do
default %{}
end
attribute :examples, {:array, :string}
attribute :args, {:array, :string}
attribute :optional_args, {:array, :string} do

View file

@ -25,6 +25,13 @@ defmodule AshHq.Docs.Library do
default "ash-project"
end
attribute :module_prefixes, {:array, :string} do
allow_nil? false
default []
end
attribute :mix_project, :string
timestamps()
end
@ -35,6 +42,8 @@ defmodule AshHq.Docs.Library do
postgres do
table "libraries"
repo AshHq.Repo
migration_defaults module_prefixes: "[]"
end
actions do

View file

@ -12,6 +12,11 @@ defmodule AshHq.Docs.LibraryVersion do
allow_nil? false
end
attribute :hydrated, :boolean do
default false
allow_nil? false
end
timestamps()
end

View file

@ -0,0 +1,34 @@
# defmodule AshHq.Docs.SimplePostgresCache do
# @moduledoc """
# Simple experimental key/value based cache on top of the postgres data layer.
# Will be problematic if used with queries that reference things like
# the current time.
# """
# @behaviour Ash.DataLayer
# Code.ensure_compiled!(AshPostgres.DataLayer)
# for {function, arity} <- Ash.DataLayer.behaviour_info(:callbacks) do
# arguments = Macro.generate_unique_arguments(arity, __MODULE__)
# case {function, arity} do
# {:run_query, 2} ->
# def run_query(query, _resource) do
# if query.__ash_bindings__.context[:data_layer][:cache?] do
# case AshHq.Docs.Cache.get(query) do
# nil ->
# end
# else
# end
# end
# _ ->
# if function_exported?(AshPostgres.DataLayer, function, arity) do
# defdelegate unquote(function)(unquote_splicing(arguments)), to: AshPostgres.DataLayer
# end
# end
# end
# end

View file

@ -32,7 +32,7 @@ defmodule AshHqWeb.Components.Docs.Functions do
</div>
</div>
<div class="p-4">
{raw(rendered(@libraries, @selected_versions, function.html_for))}
{raw(rendered(function.html_for))}
</div>
</div>
</div>
@ -41,9 +41,8 @@ defmodule AshHqWeb.Components.Docs.Functions do
"""
end
defp rendered(libraries, selected_versions, html_for) do
libraries
|> render_replacements(selected_versions, html_for)
defp rendered(html_for) do
html_for
|> String.split("<!--- heads-end -->")
|> case do
[] ->

View file

@ -11,29 +11,29 @@ defmodule AshHqWeb.Pages.Docs do
alias Surface.Components.LivePatch
require Logger
prop change_versions, :event, required: true
prop selected_versions, :map, required: true
prop libraries, :list, default: []
prop uri, :string
prop remove_version, :event
prop add_version, :event
prop change_version, :event
prop params, :map, required: true
prop(change_versions, :event, required: true)
prop(selected_versions, :map, required: true)
prop(libraries, :list, default: [])
prop(uri, :string)
prop(remove_version, :event)
prop(add_version, :event)
prop(change_version, :event)
prop(params, :map, required: true)
data library, :any
data extension, :any
data docs, :any
data library_version, :any
data guide, :any
data doc_path, :list, default: []
data dsls, :list, default: []
data dsl, :any
data options, :list, default: []
data module, :any
data mix_task, :any
data positional_options, :list
data description, :string
data title, :string
data(library, :any)
data(extension, :any)
data(docs, :any)
data(library_version, :any)
data(guide, :any)
data(doc_path, :list, default: [])
data(dsls, :list, default: [])
data(dsl, :any)
data(options, :list, default: [])
data(module, :any)
data(mix_task, :any)
data(positional_options, :list)
data(description, :string)
data(title, :string)
@spec render(any) :: Phoenix.LiveView.Rendered.t()
def render(assigns) do
@ -133,18 +133,6 @@ defmodule AshHqWeb.Pages.Docs do
{/for}
</ul>
{/if}
{#if @dsl}
{#for {category, links} <- @dsl.links || %{}}
<h3>{String.capitalize(category)}</h3>
<ul>
{#for link <- links}
<li>
{raw(render_replacements(@libraries, @selected_versions, "{{link:#{link}}}"))}
</li>
{/for}
</ul>
{/for}
{/if}
</div>
{#if @module}
<Functions
@ -226,7 +214,6 @@ defmodule AshHqWeb.Pages.Docs do
<th>Default</th>
{/if}
<th>Doc</th>
<th>Links</th>
</tr>
{#for option <- positional_options(@options)}
<tr id={"#{option.sanitized_path}/#{option.name}"}>
@ -237,7 +224,10 @@ defmodule AshHqWeb.Pages.Docs do
</LivePatch>
<div class="flex flex-row space-x-2">
<CalloutText text={option.name} />
{render_tags(assigns, %{option | required: option.required && !Map.has_key?(@dsl.arg_defaults, option.name)})}
{render_tags(assigns, %{
option
| required: option.required && !Map.has_key?(@dsl.arg_defaults || %{}, option.name)
})}
</div>
</div>
</td>
@ -245,20 +235,11 @@ defmodule AshHqWeb.Pages.Docs do
{option.type}
</td>
{#if @dsl.arg_defaults not in [%{}, nil]}
<th>{Map.get(@dsl.arg_defaults, option.name)}</th>
<th>{Map.get(@dsl.arg_defaults || %{}, option.name)}</th>
{/if}
<td>
{raw(render_replacements(@libraries, @selected_versions, option.html_for))}
</td>
<td>
{raw(
Enum.map_join(
List.flatten(Map.values(option.links || %{})),
", ",
&render_replacements(@libraries, @selected_versions, "{{link:#{&1}}}")
)
)}
</td>
</tr>
{/for}
{/if}
@ -274,7 +255,6 @@ defmodule AshHqWeb.Pages.Docs do
<th>Type</th>
<th>Default</th>
<th>Doc</th>
<th>Links</th>
</tr>
{#for %{argument_index: nil} = option <- @options}
<tr id={"#{option.sanitized_path}/#{option.name}"}>
@ -296,15 +276,6 @@ defmodule AshHqWeb.Pages.Docs do
<td>
{raw(render_replacements(@libraries, @selected_versions, option.html_for))}
</td>
<td>
{raw(
Enum.map_join(
List.flatten(Map.values(option.links || %{})),
", ",
&render_replacements(@libraries, @selected_versions, "{{link:#{&1}}}")
)
)}
</td>
</tr>
{/for}
{/if}
@ -490,7 +461,9 @@ defmodule AshHqWeb.Pages.Docs do
AshHq.Docs.Extension
|> Ash.Query.sort(order: :asc)
|> Ash.Query.load(options: options_query, dsls: dsls_query)
|> load_for_search(socket.assigns[:params]["extension"])
|> load_for_search(
socket.assigns[:params]["extension"] || socket.assigns[:params]["module"]
)
new_libraries =
socket.assigns.libraries
@ -683,7 +656,20 @@ defmodule AshHqWeb.Pages.Docs do
end)
)
else
assign(socket, :extension, nil)
if socket.assigns.library_version && socket.assigns[:params]["module"] do
extensions = socket.assigns.library_version.extensions
assign(socket,
extension:
Enum.find(extensions, fn extension ->
extension.sanitized_name == socket.assigns[:params]["module"] ||
AshHqWeb.DocRoutes.sanitize_name(extension.target) ==
socket.assigns[:params]["module"]
end)
)
else
assign(socket, :extension, nil)
end
end
end
@ -734,7 +720,7 @@ defmodule AshHqWeb.Pages.Docs do
end
defp assign_module(socket) do
if socket.assigns.library && socket.assigns.library_version &&
if !socket.assigns.extension && socket.assigns.library && socket.assigns.library_version &&
socket.assigns[:params]["module"] do
module =
Enum.find(
@ -775,6 +761,8 @@ defmodule AshHqWeb.Pages.Docs do
defp assign_docs(socket) do
cond do
socket.assigns.module ->
send(self(), {:page_title, socket.assigns.module.name})
assign(socket,
docs: socket.assigns.module.html_for,
title: "Module: #{socket.assigns.module.name}",
@ -784,6 +772,8 @@ defmodule AshHqWeb.Pages.Docs do
)
socket.assigns.mix_task ->
send(self(), {:page_title, socket.assigns.module.name})
assign(socket,
docs: socket.assigns.mix_task.html_for,
title: "Mix Task: #{socket.assigns.mix_task.name}",
@ -793,6 +783,8 @@ defmodule AshHqWeb.Pages.Docs do
)
socket.assigns.dsl ->
send(self(), {:page_title, socket.assigns.module.name})
meta_name =
Enum.join(
[

View file

@ -5,37 +5,39 @@ defmodule AshHqWeb.Router do
import AshAdmin.Router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {AshHqWeb.LayoutView, :root}
plug :protect_from_forgery
plug AshHqWeb.SessionPlug
plug :assign_user_agent
plug :load_from_session
plug(:accepts, ["html"])
plug(:fetch_session)
plug(:fetch_live_flash)
plug(:put_root_layout, {AshHqWeb.LayoutView, :root})
plug(:protect_from_forgery)
plug(AshHqWeb.SessionPlug)
plug(:assign_user_agent)
plug(:load_from_session)
end
pipeline :api do
plug :accepts, ["json"]
plug :load_from_bearer
plug(:accepts, ["json"])
plug(:load_from_bearer)
end
pipeline :admin_basic_auth do
plug :basic_auth
plug(:basic_auth)
end
scope "/", AshHqWeb do
pipe_through :browser
reset_route []
pipe_through(:browser)
reset_route([])
sign_in_route overrides: [AshHqWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
sign_in_route(
overrides: [AshHqWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
)
sign_out_route AuthController
sign_out_route(AuthController)
auth_routes_for(AshHq.Accounts.User, to: AuthController)
end
scope "/", AshHqWeb do
pipe_through :browser
pipe_through(:browser)
live_session :main,
on_mount: [
@ -45,21 +47,21 @@ defmodule AshHqWeb.Router do
],
session: {AshAuthentication.Phoenix.LiveSession, :generate_session, []},
root_layout: {AshHqWeb.LayoutView, :root} do
live "/", AppViewLive, :home
live "/media", AppViewLive, :media
live "/blog", AppViewLive, :blog
live "/blog/:slug", AppViewLive, :blog
live "/docs/", AppViewLive, :docs_dsl
live "/docs/guides/:library/:version/*guide", AppViewLive, :docs_dsl
live "/docs/dsl/:library", AppViewLive, :docs_dsl
live "/docs/dsl/:library/:version", AppViewLive, :docs_dsl
live "/docs/dsl/:library/:version/:extension", AppViewLive, :docs_dsl
live "/docs/dsl/:library/:version/:extension/*dsl_path", AppViewLive, :docs_dsl
live "/docs/module/:library/:version/:module", AppViewLive, :docs_dsl
live "/docs/mix_task/:library/:version/:mix_task", AppViewLive, :docs_dsl
live "/docs/:library/:version", AppViewLive, :docs_dsl
live("/", AppViewLive, :home)
live("/media", AppViewLive, :media)
live("/blog", AppViewLive, :blog)
live("/blog/:slug", AppViewLive, :blog)
live("/docs/", AppViewLive, :docs_dsl)
live("/docs/guides/:library/:version/*guide", AppViewLive, :docs_dsl)
live("/docs/dsl/:library", AppViewLive, :docs_dsl)
live("/docs/dsl/:library/:version", AppViewLive, :docs_dsl)
live("/docs/dsl/:library/:version/:extension", AppViewLive, :docs_dsl)
live("/docs/dsl/:library/:version/:extension/*dsl_path", AppViewLive, :docs_dsl)
live("/docs/module/:library/:version/:module", AppViewLive, :docs_dsl)
live("/docs/mix_task/:library/:version/:mix_task", AppViewLive, :docs_dsl)
live("/docs/:library/:version", AppViewLive, :docs_dsl)
get "/unsubscribe", MailingListController, :unsubscribe
get("/unsubscribe", MailingListController, :unsubscribe)
end
live_session :authenticated_only,
@ -70,20 +72,22 @@ defmodule AshHqWeb.Router do
],
session: {AshAuthentication.Phoenix.LiveSession, :generate_session, []},
root_layout: {AshHqWeb.LayoutView, :root} do
live "/users/settings", AppViewLive, :user_settings
live("/users/settings", AppViewLive, :user_settings)
end
end
get "/rss", AshHqWeb.RssController, :rss
get("/rss", AshHqWeb.RssController, :rss)
## Api routes
scope "/" do
forward "/gql", Absinthe.Plug, schema: AshHqWeb.Schema
forward("/gql", Absinthe.Plug, schema: AshHqWeb.Schema)
forward "/playground",
Absinthe.Plug.GraphiQL,
schema: AshHqWeb.Schema,
interface: :playground
forward(
"/playground",
Absinthe.Plug.GraphiQL,
schema: AshHqWeb.Schema,
interface: :playground
)
end
# Enables LiveDashboard only for development
@ -97,17 +101,18 @@ defmodule AshHqWeb.Router do
scope "/" do
if Mix.env() in [:dev, :test] do
pipe_through [:browser]
pipe_through([:browser])
else
pipe_through [:browser, :admin_basic_auth]
pipe_through([:browser, :admin_basic_auth])
end
ash_admin("/admin")
live_dashboard "/dashboard",
live_dashboard("/dashboard",
metrics: AshHqWeb.Telemetry,
ecto_repos: [AshHq.Repo],
ecto_psql_extras_options: [long_running_queries: [threshold: "200 milliseconds"]]
)
end
# Enables the Swoosh mailbox preview in development.
@ -116,9 +121,9 @@ defmodule AshHqWeb.Router do
# node running the Phoenix server.
if Mix.env() == :dev do
scope "/dev" do
pipe_through [:browser]
pipe_through([:browser])
forward "/mailbox", Plug.Swoosh.MailboxPreview
forward("/mailbox", Plug.Swoosh.MailboxPreview)
end
end

View file

@ -12,22 +12,22 @@ defmodule AshHqWeb.AppViewLive do
import AshHqWeb.Tails
data configured_theme, :string, default: :system
data selected_versions, :map, default: %{}
data libraries, :list, default: []
data selected_types, :map, default: %{}
data current_user, :map
data(configured_theme, :string, default: :system)
data(selected_versions, :map, default: %{})
data(libraries, :list, default: [])
data(selected_types, :map, default: %{})
data(current_user, :map)
data library, :any, default: nil
data extension, :any, default: nil
data docs, :any, default: nil
data library_version, :any, default: nil
data guide, :any, default: nil
data doc_path, :list, default: []
data dsls, :list, default: []
data dsl, :any, default: nil
data options, :list, default: []
data module, :any, default: nil
data(library, :any, default: nil)
data(extension, :any, default: nil)
data(docs, :any, default: nil)
data(library_version, :any, default: nil)
data(guide, :any, default: nil)
data(doc_path, :list, default: [])
data(dsls, :list, default: [])
data(dsl, :any, default: nil)
data(options, :list, default: [])
data(module, :any, default: nil)
def render(%{platform: :ios} = assigns) do
~F"""
@ -85,7 +85,7 @@ defmodule AshHqWeb.AppViewLive do
<div
id="main-container"
class={
"w-full min-g-screen bg-white dark:bg-base-dark-850 dark:text-white flex flex-col items-stretch",
"w-full min-h-screen bg-white dark:bg-base-dark-850 dark:text-white flex flex-col items-stretch",
"h-screen overflow-y-auto": @live_action != :docs_dsl
}
>
@ -186,6 +186,10 @@ defmodule AshHqWeb.AppViewLive do
|> assign(params: params, uri: uri)}
end
def handle_info({:page_title, title}, socket) do
assign(socket, :page_title, "Ash Framework - #{title}")
end
def handle_event("remove_version", %{"library" => library}, socket) do
new_selected_versions = Map.put(socket.assigns.selected_versions, library, "")
@ -248,6 +252,8 @@ defmodule AshHqWeb.AppViewLive do
end
def mount(_params, session, socket) do
socket = assign(socket, :page_title, "Ash Framework")
socket =
assign_new(socket, :user_agent, fn _assigns ->
get_connect_params(socket)["user_agent"]

View file

@ -40,15 +40,12 @@ defmodule AshHq.MixProject do
defp deps do
[
{:ash, github: "ash-project/ash", override: true},
{:ash_postgres, "~> 1.1"},
{:ash_postgres, github: "ash-project/ash_postgres"},
{:ash_admin, github: "ash-project/ash_admin"},
{:ash_phoenix, github: "ash-project/ash_phoenix", override: true},
{:ash_graphql, github: "ash-project/ash_graphql"},
{:ash_json_api, github: "ash-project/ash_json_api"},
{:ash_authentication,
branch: "set-confirmed-field-to-nil",
github: "team-alembic/ash_authentication",
override: true},
{:ash_authentication, github: "team-alembic/ash_authentication", override: true},
{:ash_authentication_phoenix, github: "team-alembic/ash_authentication_phoenix"},
{:absinthe_plug, "~> 1.5"},
{:ash_blog, github: "ash-project/ash_blog"},
@ -61,6 +58,7 @@ defmodule AshHq.MixProject do
{:surface, "~> 0.9.1"},
{:surface_heroicons, "~> 0.6.0"},
{:ua_inspector, "~> 3.0"},
{:nebulex, "~> 2.4"},
# Syntax Highlighting
{:elixir_sense, github: "elixir-lsp/elixir_sense", only: [:dev, :test]},
{:makeup, "~> 1.1"},

View file

@ -1,16 +1,16 @@
%{
"absinthe": {:hex, :absinthe, "1.7.0", "36819e7b1fd5046c9c734f27fe7e564aed3bda59f0354c37cd2df88fd32dd014", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "566a5b5519afc9b29c4d367f0c6768162de3ec03e9bf9916f9dc2bcbe7c09643"},
"absinthe_plug": {:hex, :absinthe_plug, "1.5.8", "38d230641ba9dca8f72f1fed2dfc8abd53b3907d1996363da32434ab6ee5d6ab", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bbb04176647b735828861e7b2705465e53e2cf54ccf5a73ddd1ebd855f996e5a"},
"ash": {:git, "https://github.com/ash-project/ash.git", "1eaeacc7486e50d70138baaf65133cef5e8b5869", []},
"ash": {:git, "https://github.com/ash-project/ash.git", "22e07c06b5e0da531e7e6f632009290031429875", []},
"ash_admin": {:git, "https://github.com/ash-project/ash_admin.git", "cdb3b469abeeadddda884e7cfcf67c6fea10f9ef", []},
"ash_authentication": {:git, "https://github.com/team-alembic/ash_authentication.git", "5acbaeeb9c53f9953f5018dd59aff8176561460f", [branch: "set-confirmed-field-to-nil"]},
"ash_authentication_phoenix": {:git, "https://github.com/team-alembic/ash_authentication_phoenix.git", "fbe5272f874532b3119033ddb1dfb49d71f7b571", []},
"ash_authentication": {:git, "https://github.com/team-alembic/ash_authentication.git", "d4f3bec947b3cef160fa8e6768396884a3ee6149", []},
"ash_authentication_phoenix": {:git, "https://github.com/team-alembic/ash_authentication_phoenix.git", "14c38739cf56c4821797963b9ee261597a261b00", []},
"ash_blog": {:git, "https://github.com/ash-project/ash_blog.git", "9254773dfedabfc7987af6326a62885c24c3655b", []},
"ash_csv": {:git, "https://github.com/ash-project/ash_csv.git", "77187f6e4505ed4d88598bf87e56983a6a74a456", []},
"ash_graphql": {:git, "https://github.com/ash-project/ash_graphql.git", "09fe8ce9bbbdf03d12b17e5037235a7752326e59", []},
"ash_json_api": {:git, "https://github.com/ash-project/ash_json_api.git", "5594f18d07d0771b4bc8b3d3db99cc8c08958a66", []},
"ash_csv": {:git, "https://github.com/ash-project/ash_csv.git", "2a068cea47d13065f23a3686f4c28bf875353ffd", []},
"ash_graphql": {:git, "https://github.com/ash-project/ash_graphql.git", "c0f5f596cb0dad47f0e83e2be2411210a2d97112", []},
"ash_json_api": {:git, "https://github.com/ash-project/ash_json_api.git", "bfd80c2083a5ce0f61fa128e011ddf9a6e41bc14", []},
"ash_phoenix": {:git, "https://github.com/ash-project/ash_phoenix.git", "b205c9bb4f153f855420f11d766cfe515930631e", []},
"ash_postgres": {:hex, :ash_postgres, "1.2.2", "0da0d9e1878f8b24829a8971e497e7bafe5596aced3604ba39d96e70174a8b11", [:mix], [{:ash, ">= 2.4.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "8796f1de22863451a445f33a0874904145efeecfff75175f6c1ce62754c8df4e"},
"ash_postgres": {:git, "https://github.com/ash-project/ash_postgres.git", "7bf4cf80b17e04378fa85773435a5ac559e56c54", []},
"assent": {:hex, :assent, "0.2.1", "46ad0ed92b72330f38c60bc03c528e8408475dc386f48d4ecd18833cfa581b9f", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "58c558b6029ffa287e15b38c8e07cd99f0b24e4846c52abad0c0a6225c4873bc"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
@ -30,13 +30,14 @@
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"},
"doctor": {:hex, :doctor, "0.21.0", "20ef89355c67778e206225fe74913e96141c4d001cb04efdeba1a2a9704f1ab5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "a227831daa79784eb24cdeedfa403c46a4cb7d0eab0e31232ec654314447e4e0"},
"earmark": {:hex, :earmark, "1.5.0-pre1", "e04aca73692bc3cda3429d6df99c8dae2bf76411e5e76d006a4bc04ac81ef1c1", [:mix], [{:earmark_parser, "~> 1.4.21", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "26ec0473ad2ef995b9672f89309a7a4952887f69b78cfc7af14e320bc6546bfa"},
"earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
"ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"},
"ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"},
"eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"},
"elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
"elixir_make": {:hex, :elixir_make, "0.7.3", "c37fdae1b52d2cc51069713a58c2314877c1ad40800a57efb213f77b078a460d", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "24ada3e3996adbed1fa024ca14995ef2ba3d0d17b678b0f3f2b1f66e6ce2b274"},
"elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "ed875265f54994911774ac05c3a1b9adb65b80e7", []},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"},
@ -44,6 +45,7 @@
"ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"},
"ex_doc": {:hex, :ex_doc, "0.28.5", "3e52a6d2130ce74d096859e477b97080c156d0926701c13870a4e1f752363279", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d2c4b07133113e9aa3e9ba27efb9088ba900e9e51caa383919676afdf09ab181"},
"excoveralls": {:hex, :excoveralls, "0.14.6", "610e921e25b180a8538229ef547957f7e04bd3d3e9a55c7c5b7d24354abbba70", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "0eceddaa9785cfcefbf3cd37812705f9d8ad34a758e513bb975b081dce4eb11e"},
"faker": {:hex, :faker, "0.17.0", "671019d0652f63aefd8723b72167ecdb284baf7d47ad3a82a15e9b8a6df5d1fa", [:mix], [], "hexpm", "a7d4ad84a93fd25c5f5303510753789fc2433ff241bf3b4144d3f6f291658a6a"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"finch": {:hex, :finch, "0.10.2", "9ad27d68270d879f73f26604bb2e573d40f29bf0e907064a9a337f90a16a0312", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dd8b11b282072cec2ef30852283949c248bd5d2820c88d8acc89402b81db7550"},
"floki": {:hex, :floki, "0.33.1", "f20f1eb471e726342b45ccb68edb9486729e7df94da403936ea94a794f072781", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "461035fd125f13fdf30f243c85a0b1e50afbec876cbf1ceefe6fddd2e6d712c6"},
@ -69,7 +71,9 @@
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mimic": {:hex, :mimic, "1.7.4", "cd2772ffbc9edefe964bc668bfd4059487fa639a5b7f1cbdf4fd22946505aa4f", [:mix], [], "hexpm", "437c61041ecf8a7fae35763ce89859e4973bb0666e6ce76d75efc789204447c3"},
"mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"},
"nebulex": {:hex, :nebulex, "2.4.2", "b3d2d86d57b15896fb8e6d6dd49b4a9dee2eedd6eddfb3b69bfdb616a09c2817", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.0", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "c9f888e5770fd47614c95990d0a02c3515216d51dc72e3c830eaf28f5649ba52"},
"nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
@ -84,7 +88,7 @@
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
"phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"},
"phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
"picosat_elixir": {:hex, :picosat_elixir, "0.2.2", "1cacfdb4fb0c3ead5e5e9b1e98ac822a777f07eab35e29c3f8fc7086de2bfb36", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9d0cc569552cca417abea8270a54b71153a63be4b951ff249e94642f1c0f35d1"},
"picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"},
"plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},
"plug_content_security_policy": {:hex, :plug_content_security_policy, "0.2.1", "0a19c76307ad000b3757739c14b34b83ecccf7d0a3472e64e14797a20b62939b", [:mix], [{:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ceea10050671c0387c64526e2cb337ee08e12705c737eaed80439266df5b2e29"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
@ -95,7 +99,7 @@
"slugify": {:hex, :slugify, "1.3.1", "0d3b8b7e5c1eeaa960e44dce94382bee34a39b3ea239293e457a9c5b47cc6fd3", [:mix], [], "hexpm", "cb090bbeb056b312da3125e681d98933a360a70d327820e4b7f91645c4d8be76"},
"sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"},
"sourceror": {:hex, :sourceror, "0.11.2", "549ce48be666421ac60cfb7f59c8752e0d393baa0b14d06271d3f6a8c1b027ab", [:mix], [], "hexpm", "9ab659118896a36be6eec68ff7b0674cba372fc8e210b1e9dc8cf2b55bb70dfb"},
"spark": {:hex, :spark, "0.3.4", "0084ce931c0e444194d5198b6f872c74c85607b6e5672436056e9f5b6fa41139", [:mix], [{:nimble_options, "~> 0.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "7816d3a43916c5fac2bb2308f9a3442644950e5e269014e4cdc5b2cab0bab5e0"},
"spark": {:hex, :spark, "0.3.5", "99905e681156050a713218e2b57956870b88b660dff57e7ee061b0245fc5dd50", [:mix], [{:nimble_options, "~> 0.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f272fe581f37123bf90568974937dbc18ce575fc43580a5a658965b35b993520"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"},
"sunflower_ui": {:git, "https://github.com/zachdaniel/sunflower_ui.git", "3ec87f33e003693e6db2329f9d6d8ac59983cf17", []},
@ -104,7 +108,7 @@
"swoosh": {:hex, :swoosh, "1.8.1", "b1694d57c01852f50f7d4e6a74f5d119b2d8722d6a82f7288703c3e448ddbbf8", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e64a93d71d1e1e32db681cf7870697495c9cb2df4a5484f4d91ded326ccd3cbb"},
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
"tails": {:hex, :tails, "0.1.1", "4d912b8c4e5bf244f2e899fee76b2d2fb99d2c4740defaa00ed5570266ee87f4", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4d273173487f32db0040d901a312a6bf347ba5159b6c2fdf080830649839569c"},
"telemetry": {:hex, :telemetry, "1.2.0", "a8ce551485a9a3dac8d523542de130eafd12e40bbf76cf0ecd2528f24e812a44", [:rebar3], [], "hexpm", "1427e73667b9a2002cf1f26694c422d5c905df889023903c4518921d53e3e883"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},

View file

@ -0,0 +1,29 @@
defmodule AshHq.Repo.Migrations.MigrateResources41 do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:library_versions) do
add :hydrated, :boolean, null: false, default: false
end
alter table(:libraries) do
add :module_prefixes, {:array, :text}, null: false, default: []
end
end
def down do
alter table(:libraries) do
remove :module_prefixes
end
alter table(:library_versions) do
remove :hydrated
end
end
end

View file

@ -0,0 +1,38 @@
defmodule AshHq.Repo.Migrations.MigrateResources42 do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:libraries) do
add :mix_project, :text
end
# alter table(:dsls) do
# Attribute removal has been commented out to avoid data loss. See the migration generator documentation for more
# If you uncomment this, be sure to also uncomment the corresponding attribute *addition* in the `down` migration
# remove :links
#
# end
#
end
def down do
# alter table(:dsls) do
# This is the `down` migration of the statement:
#
# remove :links
#
#
# add :links, :map
# end
#
alter table(:libraries) do
remove :mix_project
end
end
end

View file

@ -3,6 +3,7 @@ AshHq.Docs.Library.create!(
name: "ash",
display_name: "Ash",
order: 0,
module_prefixes: ["Ash"],
description: """
The core framework, providing all the features and goodies that power and enable the rest of the ecosystem.
"""
@ -16,6 +17,7 @@ AshHq.Docs.Library.create!(
name: "ash_postgres",
display_name: "AshPostgres",
order: 10,
module_prefixes: ["AshPostgres"],
description: """
A PostgreSQL data layer for Ash resources, allowing for rich query capabilities and seamless persistence.
"""
@ -28,6 +30,7 @@ AshHq.Docs.Library.create!(
%{
name: "ash_phoenix",
display_name: "AshPhoenix",
module_prefixes: ["AshPhoenix"],
order: 20,
description: """
Utilities for using Ash resources with Phoenix Framework, from building forms to running queries in sockets & LiveViews.
@ -41,6 +44,7 @@ AshHq.Docs.Library.create!(
%{
name: "ash_graphql",
display_name: "AshGraphql",
module_prefixes: ["AshGraphql"],
order: 40,
description: """
A GraphQL extension that allows you to build a rich and customizable GraphQL API with minimal configuration required.
@ -54,6 +58,7 @@ AshHq.Docs.Library.create!(
%{
name: "ash_json_api",
display_name: "AshJsonApi",
module_prefixes: ["AshJsonApi"],
order: 50,
description: """
A JSON:API extension that allows you to effortlessly create a JSON:API spec compliant API.
@ -67,6 +72,7 @@ AshHq.Docs.Library.create!(
%{
name: "ash_authentication",
display_name: "AshAuthentication",
module_prefixes: ["AshAuthentication"],
order: 55,
repo_org: "team-alembic",
description: """
@ -81,6 +87,7 @@ AshHq.Docs.Library.create!(
%{
name: "ash_authentication_phoenix",
display_name: "AshAuthenticationPhoenix",
module_prefixes: ["AshAuthenticationPhoenix"],
order: 56,
repo_org: "team-alembic",
description: """
@ -95,6 +102,7 @@ AshHq.Docs.Library.create!(
%{
name: "ash_csv",
display_name: "AshCSV",
module_prefixes: ["AshCsv"],
order: 70,
description: """
A CSV data layer allowing resources to be queried from and persisted in a CSV file.
@ -108,6 +116,7 @@ AshHq.Docs.Library.create!(
%{
name: "ash_archival",
display_name: "AshArchival",
module_prefixes: ["AshArchival"],
order: 85,
description: """
A light-weight resource extension that modifies resources to simulate deletion by setting an `archived_at` attribute.
@ -122,6 +131,7 @@ AshHq.Docs.Library.create!(
%{
name: "spark",
display_name: "Spark",
module_prefixes: ["Spark"],
order: 100,
description: """
The core DSL and extension tooling, allowing for powerful, extensible DSLs with minimal effort.

View file

@ -0,0 +1,285 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "sanitized_path",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"uuid_generate_v4()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": false,
"default": "\"\"",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "doc",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "doc_html",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "imports",
"type": [
"array",
"text"
]
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "examples",
"type": [
"array",
"text"
]
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "args",
"type": [
"array",
"text"
]
},
{
"allow_nil?": true,
"default": "[]",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "optional_args",
"type": [
"array",
"text"
]
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "arg_defaults",
"type": "map"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "path",
"type": [
"array",
"text"
]
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "recursive_as",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "order",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "type",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"now()\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"now()\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "dsls_library_version_id_fkey",
"on_delete": "delete",
"on_update": null,
"schema": "public",
"table": "library_versions"
},
"size": null,
"source": "library_version_id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "dsls_extension_id_fkey",
"on_delete": null,
"on_update": null,
"schema": "public",
"table": "extensions"
},
"size": null,
"source": "extension_id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "dsls_dsl_id_fkey",
"on_delete": null,
"on_update": null,
"schema": "public",
"table": "dsls"
},
"size": null,
"source": "dsl_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [
{
"code?": false,
"down": "DROP INDEX dsls_name_lower_index;",
"name": "name_index",
"up": "CREATE INDEX dsls_name_lower_index ON dsls(lower(name));\n"
},
{
"code?": false,
"down": "DROP INDEX dsls_name_trigram_index;",
"name": "trigram_index",
"up": "CREATE INDEX dsls_name_trigram_index ON dsls USING GIST (name gist_trgm_ops);\n"
},
{
"code?": false,
"down": "DROP INDEX dsls_search_index;",
"name": "search_index",
"up": "CREATE INDEX dsls_search_index ON dsls USING GIN((\n setweight(to_tsvector('english', name), 'A') ||\n setweight(to_tsvector('english', doc), 'D')\n));\n"
}
],
"has_create_action": true,
"hash": "0B14F4C3EB4751D846DCFCD660770D2476F8A22F30636C18D0A545A6C158ED0D",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshHq.Repo",
"schema": null,
"table": "dsls"
}

View file

@ -0,0 +1,129 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"uuid_generate_v4()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "display_name",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "order",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "description",
"type": "text"
},
{
"allow_nil?": false,
"default": "\"ash-project\"",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "repo_org",
"type": "text"
},
{
"allow_nil?": false,
"default": "[]",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "module_prefixes",
"type": [
"array",
"text"
]
},
{
"allow_nil?": false,
"default": "fragment(\"now()\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"now()\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "FC92198B6E8B374CDFE92AC7D002E41DF2BF91397AD1F59C92A1069EB30381B7",
"identities": [
{
"base_filter": null,
"index_name": "libraries_unique_name_index",
"keys": [
"name"
],
"name": "unique_name"
},
{
"base_filter": null,
"index_name": "libraries_unique_order_index",
"keys": [
"order"
],
"name": "unique_order"
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshHq.Repo",
"schema": null,
"table": "libraries"
}

View file

@ -0,0 +1,139 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"uuid_generate_v4()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "display_name",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "order",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "description",
"type": "text"
},
{
"allow_nil?": false,
"default": "\"ash-project\"",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "repo_org",
"type": "text"
},
{
"allow_nil?": false,
"default": "[]",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "module_prefixes",
"type": [
"array",
"text"
]
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "mix_project",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"now()\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"now()\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "4074AA4197B032BDA64341BB2FEC32ADDED587E8021DF8A2370106B151907023",
"identities": [
{
"base_filter": null,
"index_name": "libraries_unique_name_index",
"keys": [
"name"
],
"name": "unique_name"
},
{
"base_filter": null,
"index_name": "libraries_unique_order_index",
"keys": [
"order"
],
"name": "unique_order"
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshHq.Repo",
"schema": null,
"table": "libraries"
}

View file

@ -0,0 +1,132 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "sanitized_version",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"uuid_generate_v4()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "version",
"type": "text"
},
{
"allow_nil?": false,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "hydrated",
"type": "boolean"
},
{
"allow_nil?": false,
"default": "fragment(\"now()\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"now()\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "library_versions_library_id_fkey",
"on_delete": null,
"on_update": null,
"schema": "public",
"table": "libraries"
},
"size": null,
"source": "library_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [
{
"code?": false,
"down": "DROP INDEX library_versions_name_lower_index;",
"name": "name_index",
"up": "CREATE INDEX library_versions_name_lower_index ON library_versions(lower(version));\n"
},
{
"code?": false,
"down": "DROP INDEX library_versions_name_trigram_index;",
"name": "trigram_index",
"up": "CREATE INDEX library_versions_name_trigram_index ON library_versions USING GIST (version gist_trgm_ops);\n"
},
{
"code?": false,
"down": "DROP INDEX library_versions_search_index;",
"name": "search_index",
"up": "CREATE INDEX library_versions_search_index ON library_versions USING GIN((\n to_tsvector('english', version)\n));\n"
}
],
"has_create_action": true,
"hash": "FEEDF65ECA522C910B790387521C468262CE0A0307C2ECE57CFEEFF2C201BF38",
"identities": [
{
"base_filter": null,
"index_name": "library_versions_unique_version_for_library_index",
"keys": [
"version",
"library_id"
],
"name": "unique_version_for_library"
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshHq.Repo",
"schema": null,
"table": "library_versions"
}

View file

@ -1,7 +1,9 @@
require Logger
[name, version, file] = System.argv()
[name, version, file, mix_project] = System.argv()
mix_project = Module.concat([mix_project])
Application.put_env(:dsl, :name, name)
Application.put_env(:ash, :use_all_identities_in_manage_relationship?, true)
Mix.install(
[
@ -177,8 +179,8 @@ defmodule Utils do
[]
end),
arg_defaults:
Enum.reduce(entity.args, %{},
fn {:optional, name, default}, acc ->
Enum.reduce(entity.args, %{}, fn
{:optional, name, default}, acc ->
Map.put(acc, name, inspect(default))
_, acc ->
@ -198,15 +200,16 @@ defmodule Utils do
defp add_argument_indices(values, arguments) do
Enum.map(values, fn value ->
case Enum.find_index(arguments, fn {:optional, name, _} ->
name == value.name
case Enum.find_index(arguments, fn
{:optional, name, _} ->
name == value.name
{:optional, name} ->
name == value.name
{:optional, name} ->
name == value.name
name ->
name == value.name
end) do
name ->
name == value.name
end) do
nil ->
value
@ -475,118 +478,186 @@ defmodule Utils do
defp type({:spark_type, type, _}), do: inspect(type)
defp type({:spark_type, type, _, _}), do: inspect(type)
def doc_index?(module) do
Spark.implements_behaviour?(module, Spark.DocIndex) &&
module.for_library() == Application.get_env(:dsl, :name)
end
end
dsls =
for [app] <- :ets.match(:ac_tab, {{:loaded, :"$1"}, :_}),
{:ok, modules} = :application.get_key(app, :modules),
module <- Enum.filter(modules, &Utils.doc_index?/1) do
module
end
case Enum.at(dsls, 0) do
nil ->
File.write!(file, Base.encode64(:erlang.term_to_binary(nil)))
dsl ->
extensions = dsl.extensions()
acc = %{
doc: Utils.module_docs(dsl),
guides: [],
modules: [],
mix_tasks: []
}
default_guide = Utils.try_apply(fn -> dsl.default_guide() end)
acc =
Utils.try_apply(fn -> dsl.guides() end, [])
|> Enum.with_index()
|> Enum.reduce(acc, fn {guide, order}, acc ->
Map.update!(acc, :guides, fn guides ->
guide =
guide
|> Map.put(:order, order)
|> Map.update!(:route, &String.trim_trailing(&1, ".md"))
guide =
if guide.route == default_guide.route do
Map.put(guide, :default, true)
else
guide
end
[guide | guides]
def modules_for(all_modules, modules) do
case modules do
%Regex{} = regex ->
Enum.filter(all_modules, fn module ->
Regex.match?(regex, inspect(module)) || Regex.match?(regex, to_string(module))
end)
end)
{:ok, all_modules} =
name
|> String.to_atom()
|> :application.get_key(:modules)
all_modules =
Enum.filter(all_modules, fn module ->
case Code.fetch_docs(module) do
{:docs_v1, _, _, _, type, _, _} when type != :hidden ->
true
_ ->
false
other ->
if is_list(modules) do
Enum.flat_map(modules, &modules_for(all_modules, &1))
else
List.wrap(other)
end
end)
end
end
acc =
Utils.try_apply(fn -> dsl.code_modules() end, [])
|> Enum.reduce(acc, fn {category, modules}, acc ->
modules =
case modules do
%Regex{} = regex ->
Enum.filter(all_modules, fn module ->
Regex.match?(regex, inspect(module)) || Regex.match?(regex, to_string(module))
end)
def guides(mix_project, name) do
root_dir = File.cwd!()
app_dir =
name
|> Application.app_dir()
|> Path.join("../../../../deps/#{name}")
|> Path.expand()
other ->
List.wrap(other)
try do
File.cd!(app_dir)
extras =
Enum.map(mix_project.project[:docs][:extras], fn {file, config} ->
config =
if config[:title] do
Keyword.put(config, :name, config[:title])
else
config
end
modules
{to_string(file), config}
file ->
title =
file
|> Path.basename(".md")
|> String.split(~r/[-_]/)
|> Enum.map(&String.capitalize/1)
|> Enum.join(" ")
|> case do
"F A Q" ->
"FAQ"
other ->
other
end
{to_string(file), name: title}
end)
groups_for_extras =
mix_project.project[:docs][:groups_for_extras]
|> List.wrap()
|> Kernel.++([{"Miscellaneous", ~r/.*/}])
groups_for_extras
|> Enum.reduce({extras, []}, fn {group, matcher}, {remaining_extras, acc} ->
matches_for_group =
matcher
|> List.wrap()
|> Enum.flat_map(&take_matching(remaining_extras, &1))
{remaining_extras -- matches_for_group, [{group, matches_for_group} | acc]}
end)
|> elem(1)
|> Enum.reverse()
|> Enum.flat_map(fn {category, matches} ->
matches
|> Enum.with_index()
|> Enum.reduce(acc, fn {module, order}, acc ->
Map.update!(acc, :modules, fn modules ->
[Utils.build_module(module, category, order) | modules]
end)
|> Enum.map(fn {{path, config}, index} ->
route =
path
|> Path.absname()
|> String.trim_leading(app_dir)
|> String.trim_leading("/documentation/")
|> String.trim_trailing(".md")
config
|> Map.new()
|> Map.put(:order, index)
|> Map.put(:route, route)
|> Map.put(:category, category)
|> Map.put(:text, File.read!(path))
end)
end)
after
File.cd!(root_dir)
end
end
acc =
Utils.try_apply(fn -> dsl.mix_tasks() end, [])
|> List.wrap()
|> Enum.reduce(acc, fn {category, mix_tasks}, acc ->
mix_tasks
|> Enum.with_index()
|> Enum.reduce(acc, fn {mix_task, order}, acc ->
Map.update!(acc, :mix_tasks, fn mix_tasks ->
[Utils.build_mix_task(mix_task, category, order) | mix_tasks]
end)
end)
end)
defp take_matching(extras, matcher) when is_binary(matcher) do
extras
|> Enum.filter(fn {name, _} ->
name == matcher
end)
end
data =
extensions
|> Enum.with_index()
|> Enum.reduce(acc, fn {extension, i}, acc ->
acc
|> Map.put_new(:extensions, [])
|> Map.update!(:extensions, fn extensions ->
[Utils.build(extension, i) | extensions]
end)
end)
File.write!(file, Base.encode64(:erlang.term_to_binary(data)))
defp take_matching(extras, %Regex{} = matcher) do
Enum.filter(extras, fn {name, _} -> Regex.match?(matcher, name) end)
end
end
acc = %{
extensions: [],
guides: Utils.guides(mix_project, String.to_atom(name)),
modules: [],
mix_tasks: [],
default_guide: mix_project.project[:docs][:spark][:default_guide]
}
extensions = mix_project.project[:docs][:spark][:extensions] || mix_project.project[:docs][:spark_extensions]
{:ok, all_modules} =
name
|> String.to_atom()
|> :application.get_key(:modules)
all_modules =
all_modules
|> Kernel.||([])
|> Enum.reject(fn module ->
Enum.find(extensions || [], &(&1.target == inspect(module)))
end)
all_modules =
Enum.filter(all_modules, fn module ->
case Code.fetch_docs(module) do
{:docs_v1, _, _, _, type, _, _} when type != :hidden ->
true
_ ->
false
end
end)
acc =
mix_project.project[:docs][:groups_for_modules]
|> Kernel.||([{"Miscellaneous", ~r/.*/}])
|> Enum.reduce(acc, fn {category, modules}, acc ->
modules =
Utils.modules_for(all_modules, modules)
modules
|> Enum.with_index()
|> Enum.reduce(acc, fn {module, order}, acc ->
Map.update!(acc, :modules, fn modules ->
[Utils.build_module(module, category, order) | modules]
end)
end)
end)
acc =
mix_project.project[:docs][:spark][:mix_tasks]
|> List.wrap()
|> Enum.reduce(acc, fn {category, mix_tasks}, acc ->
mix_tasks
|> Enum.with_index()
|> Enum.reduce(acc, fn {mix_task, order}, acc ->
Map.update!(acc, :mix_tasks, fn mix_tasks ->
[Utils.build_mix_task(mix_task, category, order) | mix_tasks]
end)
end)
end)
data =
extensions
|> Kernel.||([])
|> Enum.with_index()
|> Enum.reduce(acc, fn {extension, i}, acc ->
acc
|> Map.put_new(:extensions, [])
|> Map.update!(:extensions, fn extensions ->
[Utils.build(extension, i) | extensions]
end)
end)
File.write!(file, Base.encode64(:erlang.term_to_binary(data)))