defmodule NPM.Security.TaskReporter do
@moduledoc """
Shared reporting helpers for npm security Mix tasks.
"""
alias NPM.Config
alias NPM.JSON
alias NPM.Security.Compromised
@type output_format :: :text | :json
@type policy :: :error | :warn | :off
@doc "Parse a task output format option."
@spec parse_format(String.t() | nil) :: {:ok, output_format()} | :error
def parse_format(nil), do: {:ok, :text}
def parse_format("text"), do: {:ok, :text}
def parse_format("json"), do: {:ok, :json}
def parse_format(_), do: :error
@doc "Parse a compromised-package task policy option."
@spec parse_policy(String.t() | nil) :: {:ok, policy()} | :error
def parse_policy(nil), do: {:ok, Config.compromised_policy()}
def parse_policy("error"), do: {:ok, :error}
def parse_policy("warn"), do: {:ok, :warn}
def parse_policy("off"), do: {:ok, :off}
def parse_policy(_), do: :error
@doc "Report compromised-package findings in text or JSON format."
@spec report([Compromised.finding()], output_format(), String.t(), String.t()) :: :ok
def report(findings, :text, empty_message, found_message) do
case findings do
[] ->
Mix.shell().info(empty_message)
_ ->
Mix.shell().error(found_message)
findings |> Compromised.format_findings() |> Enum.each(&Mix.shell().error(" #{&1}"))
end
end
def report(findings, :json, _empty_message, _found_message) do
findings
|> Enum.map(&Compromised.finding_to_json/1)
|> then(&%{"findings" => &1})
|> JSON.encode_pretty()
|> Mix.shell().info()
end
@doc "Raise according to policy when compromised-package findings exist."
@spec enforce([Compromised.finding()], policy()) :: :ok | no_return()
def enforce([], _policy), do: :ok
def enforce(_findings, :warn), do: :ok
def enforce(_findings, :off), do: :ok
def enforce(findings, :error), do: Mix.raise("Found #{length(findings)} compromised packages")
end