defmodule Mix.Tasks.Scoria.ReleasePreview do
use Mix.Task
@shortdoc "Builds docs and verifies the bounded package release surface"
@required_package_paths [
"README.md",
"LICENSE",
"mix.exs",
"lib/scoria.ex",
"priv/repo/migrations/20260511000100_create_workflow_tables.exs",
"priv/repo/knowledge_migrations/20260511000300_create_knowledge_tables.exs",
"docs/adoption_lanes.md",
"docs/phoenix_runtime_example.md",
"docs/bounded_handoffs.md",
"docs/semantic_fast_path.md",
"docs/operator_verification.md"
]
@release_preview_output_dir Path.join(["tmp", "scoria-release-preview"])
def required_package_paths, do: @required_package_paths
def release_preview_output_dir, do: @release_preview_output_dir
@impl Mix.Task
def run(_args) do
Mix.Task.run("loadpaths")
output_dir = release_preview_output_dir()
File.rm_rf!(output_dir)
Mix.shell().info("==> Building publish-facing docs")
Mix.Task.reenable("docs")
Mix.Task.run("docs")
Mix.shell().info("==> Building unpacked Hex preview")
{output, status} =
System.cmd("mix", ["hex.build", "--unpack", "--output", output_dir],
cd: File.cwd!(),
stderr_to_stdout: true
)
if status != 0 do
Mix.raise("hex preview failed:\n#{output}")
end
unpack_root = unpack_root!(output_dir)
case missing_required_paths(unpack_root) do
[] ->
Mix.shell().info("==> Release preview passed")
missing ->
Mix.raise("""
release preview is missing required package paths:
#{Enum.map_join(missing, "\n", &"* #{&1}")}
""")
end
end
defp unpack_root!(output_dir) do
if File.regular?(Path.join(output_dir, "mix.exs")) do
output_dir
else
output_dir
|> File.ls!()
|> Enum.map(&Path.join(output_dir, &1))
|> Enum.find(&(File.dir?(&1) and File.regular?(Path.join(&1, "mix.exs")))) ||
Mix.raise("could not find unpacked package root in #{output_dir}")
end
end
defp missing_required_paths(unpack_root) do
Enum.reject(@required_package_paths, fn relative_path ->
File.exists?(Path.join(unpack_root, relative_path))
end)
end
end