Current section
Files
Jump to
Current section
Files
lib/mix/tasks/workspace.check.ex
defmodule Mix.Tasks.Workspace.Check do
@options_schema Workspace.Cli.options([
:workspace_path,
:config_path,
:verbose,
:project,
:exclude,
:tags,
:excluded_tags
])
@shortdoc "Runs configured checks on the current workspace"
@moduledoc """
Check the workspace using the configured checks.
## Command Line Options
#{CliOptions.docs(@options_schema, sort: true, sections: Workspace.CliOptions.doc_sections())}
"""
use Mix.Task
import Workspace.Cli
@impl Mix.Task
def run(argv) do
{opts, _args, _extra} = CliOptions.parse!(argv, @options_schema)
workspace = Mix.WorkspaceUtils.load_and_filter_workspace(opts)
ensure_checks(workspace.config[:checks])
log("running #{length(workspace.config[:checks])} workspace checks on the workspace")
newline()
workspace.config[:checks]
|> Enum.with_index(fn check, index -> Keyword.put(check, :index, index) end)
|> Enum.map(fn check -> run_check(check, workspace, opts) end)
|> maybe_set_exit_status()
end
defp run_check(check, workspace, opts) do
results = check[:module].check(workspace, check)
print_check_status(check, results, opts)
check_status(results)
end
defp ensure_checks(checks) do
if checks == [] do
Mix.raise("""
No checks configured in your workspace. In order to add a check add a `checks`
list in your workspace config and configure the required checks. For example:
checks: [
[
module: Workspace.Checks.ValidateProject,
description: "all projects must have a description set",
opts: [
validate: fn project ->
case project.config[:description] do
nil -> {:error, "no :description set"}
description when is_binary(description) -> {:ok, ""}
other -> {:error, "description must be binary}
end
end
]
]
}
""")
end
end
defp print_check_status(check, results, opts) do
index = check[:index]
status = check_status(results)
results = Enum.sort_by(results, & &1.project.app)
display_index = String.pad_leading("#{index}", 3, "0")
log_with_title(
highlight("C#{display_index}", [:bright, status_color(status)]),
highlight(check[:description], :bright),
separator: " ",
prefix: :header
)
for result <- results do
maybe_print_result(result, opts[:verbose])
end
end
defp maybe_print_result(result, verbose) do
cond do
verbose ->
print_result(result)
result.status in [:error, :warn] ->
print_result(result)
true ->
:ok
end
end
defp print_result(result) do
path = Workspace.Utils.Path.relative_to(result.project.path, File.cwd!())
log([
highlight(status_text(result.status), status_color(result.status)),
hl(":#{result.project.app}", :code),
check_message(result),
maybe_mix_project(result.status, path)
])
end
defp check_status(results) do
counts = Enum.group_by(results, fn result -> result.status end)
cond do
counts[:error] != nil -> :error
counts[:warn] != nil -> :warn
counts[:ok] == nil -> :skip
true -> :ok
end
end
defp status_text(:error), do: "ERROR "
defp status_text(:ok), do: "OK "
defp status_text(:skip), do: "SKIP "
defp status_text(:warn), do: "WARN "
defp check_message(%Workspace.Check.Result{status: :skip}), do: " - check skipped"
# format result handles only success and error, we want the error message for
# warnings
defp check_message(%Workspace.Check.Result{status: :warn} = result) do
Workspace.Check.Result.set_status(result, :error) |> check_message()
end
defp check_message(result) do
case result.module.format_result(result) do
[] -> []
"" -> []
message when is_binary(message) -> [" - ", message]
message when is_list(message) -> [" - " | message]
end
end
defp maybe_mix_project(:error, path), do: highlight([" ", path], [:reset, :faint])
defp maybe_mix_project(_other, _path), do: []
defp maybe_set_exit_status(check_results) do
failures = Enum.filter(check_results, fn result -> result == :error end)
if length(failures) > 0 do
Mix.raise("mix workspace.check failed - errors detected in #{length(failures)} checks")
end
end
end