Packages
Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/raxol.check.ex
defmodule Mix.Tasks.Raxol.Check do
@moduledoc """
Run comprehensive quality checks on the Raxol codebase.
This task runs multiple quality checks in sequence:
1. Compilation check
2. Format check
3. Credo analysis
4. Dialyzer (if available)
5. Security audit
6. Test suite
## Usage
mix raxol.check [OPTIONS]
## Options
* `--only CHECKS` - Run only specific checks (comma-separated)
* `--skip CHECKS` - Skip specific checks (comma-separated)
* `--strict` - Run all checks in strict mode
* `--quick` - Run only fast checks (skip dialyzer)
## Examples
# Run all checks
mix raxol.check
# Run only format and compile checks
mix raxol.check --only compile,format
# Skip dialyzer
mix raxol.check --skip dialyzer
# Quick check (no dialyzer)
mix raxol.check --quick
"""
use Mix.Task
require Logger
alias Raxol.CLI.Colors
alias Raxol.CLI.ErrorDisplay
alias Raxol.CLI.Spinner
@shortdoc "Run comprehensive quality checks"
@all_checks [:compile, :format, :credo, :dialyzer, :security, :test]
@quick_checks [:compile, :format, :credo, :test]
@impl Mix.Task
def run(args) do
{opts, _, _} =
OptionParser.parse(args,
strict: [
only: :string,
skip: :string,
strict: :boolean,
quick: :boolean
]
)
checks = determine_checks(opts)
total_checks = length(checks)
Mix.shell().info(Colors.section_header("Raxol Quality Checks"))
Mix.shell().info(
Colors.muted("Running #{total_checks} checks: #{Enum.join(checks, ", ")}")
)
Mix.shell().info("")
results =
checks
|> Enum.with_index(1)
|> Enum.map(fn {check, index} ->
Spinner.step(index, total_checks, "#{check}")
run_check(check)
end)
print_summary(results)
# Only fail on actual errors, not warnings or skipped checks
case Enum.any?(results, fn {_, status} -> status == :error end) do
true -> Mix.raise("Some checks failed")
false -> :ok
end
end
defp determine_checks(opts) do
base_checks =
cond do
opts[:only] ->
parse_check_list(opts[:only])
opts[:quick] ->
@quick_checks
true ->
@all_checks
end
# Apply skip filter after determining base checks
case opts[:skip] do
nil ->
base_checks
skip_str ->
skip = parse_check_list(skip_str)
Enum.reject(base_checks, &(&1 in skip))
end
end
defp parse_check_list(str) do
str
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.map(&String.to_atom/1)
end
defp run_check(:compile) do
Mix.shell().info(Colors.subsection_header("Compilation check"))
try do
System.put_env("TMPDIR", "/tmp")
System.put_env("SKIP_TERMBOX2_TESTS", "true")
System.put_env("MIX_ENV", "test")
Mix.Task.run("compile", ["--warnings-as-errors"])
Mix.shell().info(
" " <> Colors.format_success("Compilation successful")
)
{:compile, :ok}
rescue
e ->
Mix.shell().error(
" " <>
Colors.format_error("Compilation failed", Exception.message(e))
)
# Display enhanced error with suggestions
ErrorDisplay.display_error(e, %{
check: :compile,
operation: "compilation"
})
{:compile, :error}
end
end
defp run_check(:format) do
Mix.shell().info(Colors.subsection_header("Code formatting"))
case Mix.shell().cmd("mix format --check-formatted") do
0 ->
Mix.shell().info(
" " <> Colors.format_success("Code is properly formatted")
)
{:format, :ok}
_ ->
Mix.shell().info(
" " <> Colors.format_warning("Code formatting issues found")
)
Mix.shell().info(" " <> Colors.format_fix("Fix", "mix format"))
{:format, :warning}
end
end
defp run_check(:credo) do
Mix.shell().info(Colors.subsection_header("Credo analysis"))
case Code.ensure_loaded?(Credo) do
true ->
case Mix.shell().cmd("mix credo --strict") do
0 ->
Mix.shell().info(
" " <> Colors.format_success("Credo analysis passed")
)
{:credo, :ok}
_ ->
Mix.shell().info(
" " <> Colors.format_warning("Credo found issues")
)
{:credo, :warning}
end
false ->
Mix.shell().info(" " <> Colors.format_skip("Credo not available"))
{:credo, :skipped}
end
end
defp run_check(:dialyzer) do
Mix.shell().info(Colors.subsection_header("Dialyzer"))
case Code.ensure_loaded?(Dialyxir) do
true ->
case Mix.shell().cmd("mix dialyzer") do
0 ->
Mix.shell().info(
" " <> Colors.format_success("Dialyzer analysis passed")
)
{:dialyzer, :ok}
_ ->
Mix.shell().info(
" " <> Colors.format_warning("Dialyzer found issues")
)
{:dialyzer, :warning}
end
false ->
Mix.shell().info(" " <> Colors.format_skip("Dialyzer not available"))
{:dialyzer, :skipped}
end
end
defp run_check(:security) do
Mix.shell().info(Colors.subsection_header("Security audit"))
case Code.ensure_loaded?(Sobelow) do
true ->
case Mix.shell().cmd("mix sobelow --config") do
0 ->
Mix.shell().info(
" " <> Colors.format_success("Security audit passed")
)
{:security, :ok}
_ ->
Mix.shell().info(
" " <> Colors.format_warning("Security issues found")
)
{:security, :warning}
end
false ->
Mix.shell().info(" " <> Colors.format_skip("Sobelow not available"))
{:security, :skipped}
end
end
defp run_check(:test) do
Mix.shell().info(Colors.subsection_header("Test suite"))
System.put_env("TMPDIR", "/tmp")
System.put_env("SKIP_TERMBOX2_TESTS", "true")
System.put_env("MIX_ENV", "test")
# Exclude slow tests and benchmarks
case Mix.shell().cmd(
"mix test --exclude slow --exclude integration --exclude docker --exclude benchmark --exclude skip_on_ci --max-failures 10"
) do
0 ->
Mix.shell().info(" " <> Colors.format_success("All tests passed"))
{:test, :ok}
exit_code ->
Mix.shell().error(" " <> Colors.format_error("Some tests failed"))
# Display enhanced error with suggestions
ErrorDisplay.display_error(
{:error, "Test suite failed with exit code #{exit_code}"},
%{check: :test, operation: "test_suite", exit_code: exit_code}
)
Mix.shell().info("")
Mix.shell().info(" " <> Colors.info("Quick actions:"))
Mix.shell().info(
" " <>
Colors.format_fix("Re-run failed tests", "mix test --failed")
)
Mix.shell().info(
" " <> Colors.format_fix("Run with seed 0", "mix test --seed 0")
)
Mix.shell().info(
" " <> Colors.format_fix("Increase verbosity", "mix test --trace")
)
Mix.shell().info("")
{:test, :error}
end
end
defp run_check(unknown) do
Mix.shell().error(Colors.format_error("Unknown check: #{unknown}"))
{unknown, :error}
end
defp print_summary(results) do
Mix.shell().info("")
Mix.shell().info(Colors.divider("=", 40))
Mix.shell().info(Colors.bold("Check Summary"))
Mix.shell().info(Colors.divider("=", 40))
Enum.each(results, fn {check, status} ->
indicator = Colors.status_indicator(status)
Mix.shell().info(" #{check}: #{indicator}")
end)
# Show counts
counts = count_results(results)
Mix.shell().info("")
Mix.shell().info(format_counts(counts))
Mix.shell().info("")
end
defp count_results(results) do
Enum.reduce(results, %{ok: 0, warning: 0, error: 0, skipped: 0}, fn {_check,
status},
acc ->
Map.update(acc, status, 1, &(&1 + 1))
end)
end
defp format_counts(%{
ok: ok,
warning: warning,
error: error,
skipped: skipped
}) do
parts =
[
{ok, Colors.success("#{ok} passed")},
{warning, Colors.warning("#{warning} warnings")},
{error, Colors.error("#{error} failed")},
{skipped, Colors.muted("#{skipped} skipped")}
]
|> Enum.filter(fn {count, _} -> count > 0 end)
|> Enum.map(fn {_, str} -> str end)
Enum.join(parts, " | ")
end
end