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.install_hooks.ex.disabled
defmodule Mix.Tasks.Raxol.InstallHooks do
@moduledoc """
Install Git hooks for the Raxol pre-commit system.
This task installs the pre-commit hook that automatically runs
`mix raxol.pre_commit` before each commit.
## Usage
mix raxol.install_hooks
## Options
* `--force` - Overwrite existing hooks without asking
* `--backup` - Create backup of existing hooks
* `--check` - Only check if hooks are installed (dry run)
## Examples
# Install hooks (interactive if hooks exist)
mix raxol.install_hooks
# Force overwrite existing hooks
mix raxol.install_hooks --force
# Create backup of existing hooks
mix raxol.install_hooks --backup
# Check if hooks are installed
mix raxol.install_hooks --check
"""
use Mix.Task
@shortdoc "Install Git pre-commit hooks"
@pre_commit_hook """
#!/bin/sh
# Raxol pre-commit hook
# Generated by mix raxol.install_hooks
# Allow bypassing the hook with RAXOL_SKIP_CHECKS environment variable
if [ "$RAXOL_SKIP_CHECKS" = "true" ] || [ "$RAXOL_SKIP_CHECKS" = "1" ]; then
echo "Skipping Raxol pre-commit checks (RAXOL_SKIP_CHECKS is set)"
exit 0
fi
# Check if we're in a rebase
if [ -d "$(git rev-parse --git-dir)/rebase-merge" ] || [ -d "$(git rev-parse --git-dir)/rebase-apply" ]; then
echo "Skipping pre-commit checks during rebase"
exit 0
fi
# Run the pre-commit checks
echo "Running Raxol pre-commit checks..."
mix raxol.pre_commit
# Capture the exit code
EXIT_CODE=$?
# Exit with the same code
if [ $EXIT_CODE -ne 0 ]; then
echo ""
echo "Pre-commit checks failed. You can bypass with:"
echo " git commit --no-verify"
echo " RAXOL_SKIP_CHECKS=true git commit"
fi
exit $EXIT_CODE
"""
@impl Mix.Task
def run(args) do
{opts, _, _} =
OptionParser.parse(args,
switches: [
force: :boolean,
backup: :boolean,
check: :boolean
]
)
git_dir = get_git_directory()
hooks_dir = Path.join(git_dir, "hooks")
pre_commit_path = Path.join(hooks_dir, "pre-commit")
if opts[:check] do
check_hook_status(pre_commit_path)
else
install_hook(pre_commit_path, opts)
end
end
defp get_git_directory do
case System.cmd("git", ["rev-parse", "--git-dir"], stderr_to_stdout: true) do
{output, 0} ->
String.trim(output)
_ ->
Mix.raise(
"Not in a Git repository. Please run this command from a Git repository."
)
end
end
defp check_hook_status(hook_path) do
case File.exists?(hook_path) do
true ->
case File.read!(hook_path) do
content when content == @pre_commit_hook ->
IO.puts("✅ Raxol pre-commit hook is installed and up to date")
{:ok, :current}
content ->
case String.contains?(content, "mix raxol.pre_commit") do
true ->
IO.puts("⚠️ Raxol pre-commit hook is installed but outdated")
IO.puts(" Run 'mix raxol.install_hooks' to update")
{:ok, :outdated}
false ->
IO.puts("⚠️ A different pre-commit hook is installed")
IO.puts(
" Run 'mix raxol.install_hooks --backup' to install Raxol hook"
)
{:ok, :different}
end
end
false ->
IO.puts("❌ No pre-commit hook installed")
IO.puts(" Run 'mix raxol.install_hooks' to install")
{:ok, :missing}
end
end
defp install_hook(hook_path, opts) do
# Check if hook already exists
case File.exists?(hook_path) do
true ->
handle_existing_hook(hook_path, opts)
false ->
write_hook(hook_path)
end
end
defp handle_existing_hook(hook_path, opts) do
existing_content = File.read!(hook_path)
cond do
existing_content == @pre_commit_hook ->
IO.puts("✅ Raxol pre-commit hook is already installed and up to date")
{:ok, :already_installed}
opts[:force] ->
maybe_backup(hook_path, opts)
write_hook(hook_path)
String.contains?(existing_content, "mix raxol.pre_commit") ->
IO.puts("An older version of the Raxol pre-commit hook is installed.")
case prompt_user("Update to the latest version?") do
true ->
maybe_backup(hook_path, opts)
write_hook(hook_path)
false ->
IO.puts("Hook installation cancelled")
{:ok, :cancelled}
end
true ->
IO.puts("A different pre-commit hook already exists.")
IO.puts("Content preview:")
IO.puts(String.slice(existing_content, 0, 200) <> "...")
case prompt_user("Replace with Raxol pre-commit hook?") do
true ->
maybe_backup(hook_path, opts)
write_hook(hook_path)
false ->
IO.puts("Hook installation cancelled")
IO.puts(
"You can manually integrate Raxol by adding 'mix raxol.pre_commit' to your existing hook"
)
{:ok, :cancelled}
end
end
end
defp maybe_backup(hook_path, opts) do
case opts[:backup] do
true ->
backup_path = "#{hook_path}.backup.#{timestamp()}"
File.copy!(hook_path, backup_path)
IO.puts("📦 Created backup: #{backup_path}")
_ ->
:ok
end
end
defp write_hook(hook_path) do
# Ensure hooks directory exists
hook_path |> Path.dirname() |> File.mkdir_p!()
# Write the hook file
File.write!(hook_path, @pre_commit_hook)
# Make it executable
case System.cmd("chmod", ["+x", hook_path]) do
{_, 0} ->
IO.puts("✅ Successfully installed Raxol pre-commit hook")
IO.puts(" Location: #{hook_path}")
IO.puts("")
IO.puts("The hook will run automatically before each commit.")
IO.puts("To bypass: git commit --no-verify")
{:ok, :installed}
_ ->
Mix.raise(
"Failed to make hook executable. Please run: chmod +x #{hook_path}"
)
end
end
defp prompt_user(question) do
case IO.gets("#{question} [y/N]: ") do
:eof ->
false
response when is_binary(response) ->
response
|> String.trim()
|> String.downcase()
|> then(&(&1 in ["y", "yes"]))
_ ->
false
end
end
defp timestamp do
{{year, month, day}, {hour, minute, second}} = :calendar.local_time()
"#{year}#{pad(month)}#{pad(day)}_#{pad(hour)}#{pad(minute)}#{pad(second)}"
end
defp pad(num), do: num |> Integer.to_string() |> String.pad_leading(2, "0")
end