defmodule Foundry.Context.NodeBuilder do
@moduledoc """
Builds a NodeEntry from SparkMeta.ModuleInfo and project manifest.
"""
alias Foundry.SparkMeta.ModuleInfo
alias Foundry.Context.NodeEntry
@spec build(ModuleInfo.t(), manifest :: keyword(), pending_migrations :: boolean()) ::
NodeEntry.t()
def build(%ModuleInfo{} = info, manifest, pending_migrations) do
sensitive_modules = Keyword.get(manifest, :sensitive_resources, [])
module_str = format_module(info.module)
%NodeEntry{
id: module_str,
module: module_str,
type: to_string(info.type || :resource),
domain: derive_domain(info.module),
app: nil,
sensitive: info.module in sensitive_modules,
description: info.description,
attributes: info.attributes,
actions: info.actions,
rules: info.rules,
compliance: info.compliance,
adrs: info.adrs,
runbook: info.runbook,
test_coverage: %{property_tests: false, scenario_tests: false, e2e_tests: false},
data_layer: info.data_layer,
pending_migrations: pending_migrations,
paper_trail: info.paper_trail,
archival: info.archival,
state_machine: info.state_machine,
api_routes: info.api_routes,
telemetry_prefix: info.telemetry_prefix,
money_attributes: info.money_attributes,
authentication_subject: info.authentication_subject,
oban_queues: info.oban_queues,
rate_limited: info.rate_limited,
feature_flags: ensure_list(info.feature_flags),
steps: info.steps,
performs: info.performs,
outputs: info.outputs,
agent_steps: info.agent_steps,
relationships: info.relationships,
auth_strategies: info.auth_strategies,
side_effects: info.side_effects,
trigger_kind: Map.get(info, :trigger_kind),
last_modified: format_mtime(info.last_modified),
page_route: info.page_route,
page_group: info.page_group,
page_dynamic: info.page_dynamic,
page_subtype: info.page_subtype,
calls_actions: info.calls_actions
}
end
defp format_module(module) when is_atom(module) do
module |> Atom.to_string() |> String.replace_prefix("Elixir.", "")
end
defp format_module(module), do: module
defp format_mtime(nil), do: nil
defp format_mtime({{year, month, day}, {hour, minute, second}}) do
"#{year}-#{String.pad_leading(to_string(month), 2, "0")}-#{String.pad_leading(to_string(day), 2, "0")} #{String.pad_leading(to_string(hour), 2, "0")}:#{String.pad_leading(to_string(minute), 2, "0")}:#{String.pad_leading(to_string(second), 2, "0")}"
end
defp format_mtime(other), do: other
defp ensure_list(nil), do: []
defp ensure_list(list) when is_list(list), do: list
defp ensure_list(value), do: [value]
defp derive_domain(module) do
# "IgamingRef.Finance.Wallet" → "Finance"
module |> Module.split() |> Enum.drop(1) |> List.first() |> to_string()
end
end