|
1 | 1 | #!/bin/sh |
2 | 2 |
|
3 | | -# Function to handle errors |
4 | | -handle_error() { |
5 | | - echo "❌ Error: $1" |
6 | | - exit 1 |
| 3 | +# Pre-commit hook for arbitrum-docs repository |
| 4 | +# Enforces: redirect validation, submodule updates, code formatting, and type checking |
| 5 | +# Compatible with Husky v9+ (modern Husky architecture) |
| 6 | +# Performance optimized with selective file processing and parallel execution |
| 7 | + |
| 8 | +# Exit immediately on any error |
| 9 | +set -e |
| 10 | + |
| 11 | +# Husky environment detection and CI skip logic |
| 12 | +if [ "$HUSKY" = "0" ] || [ "$CI" = "true" ] || [ "$GITHUB_ACTIONS" = "true" ]; then |
| 13 | + echo "🚀 Husky pre-commit hook skipped (CI environment or HUSKY=0)" |
| 14 | + exit 0 |
| 15 | +fi |
| 16 | + |
| 17 | +# Cross-platform color support detection |
| 18 | +if [ -t 1 ] && command -v tput >/dev/null 2>&1 && [ "$(tput colors)" -ge 8 ]; then |
| 19 | + RED='\033[0;31m' |
| 20 | + GREEN='\033[0;32m' |
| 21 | + YELLOW='\033[1;33m' |
| 22 | + BLUE='\033[0;34m' |
| 23 | + CYAN='\033[0;36m' |
| 24 | + BOLD='\033[1m' |
| 25 | + NC='\033[0m' |
| 26 | +else |
| 27 | + RED='' |
| 28 | + GREEN='' |
| 29 | + YELLOW='' |
| 30 | + BLUE='' |
| 31 | + CYAN='' |
| 32 | + BOLD='' |
| 33 | + NC='' |
| 34 | +fi |
| 35 | + |
| 36 | +# Enhanced logging functions with emojis for better UX |
| 37 | +log_info() { |
| 38 | + printf "${BLUE}ℹ️ [INFO]${NC} %s\n" "$1" |
7 | 39 | } |
8 | 40 |
|
9 | | -# Function to log success messages |
10 | 41 | log_success() { |
11 | | - echo "✅ $1" |
| 42 | + printf "${GREEN}✅ [SUCCESS]${NC} %s\n" "$1" |
12 | 43 | } |
13 | 44 |
|
14 | | -# Function to log info messages |
15 | | -log_info() { |
16 | | - echo "💡 $1" |
| 45 | +log_error() { |
| 46 | + printf "${RED}❌ [ERROR]${NC} %s\n" "$1" >&2 |
17 | 47 | } |
18 | 48 |
|
19 | | -# Function to run yarn command and check exit code |
20 | | -run_yarn_command() { |
21 | | - local command=$1 |
22 | | - local error_message=$2 |
| 49 | +log_warning() { |
| 50 | + printf "${YELLOW}⚠️ [WARNING]${NC} %s\n" "$1" |
| 51 | +} |
23 | 52 |
|
24 | | - yarn $command |
25 | | - local exit_code=$? |
| 53 | +log_step() { |
| 54 | + printf "${CYAN}${BOLD}🔄 %s${NC}\n" "$1" |
| 55 | +} |
26 | 56 |
|
27 | | - if [ $exit_code -ne 0 ]; then |
28 | | - handle_error "$error_message" |
| 57 | +# Enhanced error handling with rollback suggestions |
| 58 | +exit_with_error() { |
| 59 | + log_error "$1" |
| 60 | + log_info "💡 Tip: Use 'git commit --no-verify' to bypass hooks (not recommended)" |
| 61 | + log_info "💡 Or set HUSKY=0 to disable all hooks temporarily" |
| 62 | + exit 1 |
| 63 | +} |
| 64 | + |
| 65 | +# Function to check command availability with installation hints |
| 66 | +check_command() { |
| 67 | + if ! command -v "$1" >/dev/null 2>&1; then |
| 68 | + case "$1" in |
| 69 | + yarn) |
| 70 | + exit_with_error "Yarn not found. Install with: npm install -g yarn" |
| 71 | + ;; |
| 72 | + node) |
| 73 | + exit_with_error "Node.js not found. Install from: https://nodejs.org/" |
| 74 | + ;; |
| 75 | + git) |
| 76 | + exit_with_error "Git not found. Install from: https://git-scm.com/" |
| 77 | + ;; |
| 78 | + *) |
| 79 | + exit_with_error "Command '$1' not found. Please ensure it's installed and in your PATH." |
| 80 | + ;; |
| 81 | + esac |
29 | 82 | fi |
30 | 83 | } |
31 | 84 |
|
32 | | -# Update submodules to ensure they are properly initialized and updated |
33 | | -log_info "Updating submodules..." |
34 | | -git submodule update --init --recursive |
35 | | -submodule_exit_code=$? |
| 85 | +# Performance timing function |
| 86 | +time_command() { |
| 87 | + local start_time=$(date +%s) |
| 88 | + "$@" |
| 89 | + local end_time=$(date +%s) |
| 90 | + local duration=$((end_time - start_time)) |
| 91 | + log_info "⏱️ Completed in ${duration}s" |
| 92 | +} |
36 | 93 |
|
37 | | -if [ $submodule_exit_code -ne 0 ]; then |
38 | | - handle_error "Submodule update failed. Please check submodule configuration and try again." |
39 | | -fi |
| 94 | +# Get list of staged files for selective processing |
| 95 | +get_staged_files() { |
| 96 | + git diff --cached --name-only --diff-filter=ACMR |
| 97 | +} |
| 98 | + |
| 99 | +# Check if specific file types are staged |
| 100 | +has_staged_files() { |
| 101 | + get_staged_files | grep -E "$1" >/dev/null 2>&1 |
| 102 | +} |
40 | 103 |
|
41 | | -log_success "Submodules updated successfully" |
| 104 | +# Main pre-commit validation |
| 105 | +main() { |
| 106 | + log_step "Starting pre-commit hook validation..." |
42 | 107 |
|
43 | | -# Check redirects (validation only, no file generation) |
44 | | -log_info "Checking redirects..." |
45 | | -run_yarn_command "check-redirects" "The redirect checker found issues that need to be addressed. Please check the output above and fix any issues before committing." |
| 108 | + # Environment and dependency checks |
| 109 | + log_info "🔍 Checking environment and dependencies..." |
| 110 | + check_command "node" |
| 111 | + check_command "yarn" |
| 112 | + check_command "git" |
46 | 113 |
|
47 | | -# Final formatting pass (formatting only, no file generation) |
48 | | -log_info "Running final formatting pass..." |
49 | | -run_yarn_command "format" "Final formatting failed" |
| 114 | + # Verify we're in a git repository |
| 115 | + if ! git rev-parse --git-dir >/dev/null 2>&1; then |
| 116 | + exit_with_error "Not in a git repository" |
| 117 | + fi |
| 118 | + |
| 119 | + # Get staged files for optimization |
| 120 | + local staged_files |
| 121 | + staged_files=$(get_staged_files) |
| 122 | + |
| 123 | + if [ -z "$staged_files" ]; then |
| 124 | + log_warning "No staged files found. Commit may be empty." |
| 125 | + exit 0 |
| 126 | + fi |
| 127 | + |
| 128 | + log_info "📁 Found $(echo "$staged_files" | wc -l | tr -d ' ') staged files" |
| 129 | + |
| 130 | + # 1. Fast redirect validation (project-specific) |
| 131 | + log_step "Validating redirects..." |
| 132 | + time_command yarn check-redirects || exit_with_error "Redirect validation failed. Fix redirect issues before committing." |
| 133 | + log_success "Redirect validation passed" |
| 134 | + |
| 135 | + # 2. Submodule updates (only if submodules are staged or .gitmodules changed) |
| 136 | + if echo "$staged_files" | grep -E "(\.gitmodules|submodules/)" >/dev/null 2>&1; then |
| 137 | + log_step "Updating git submodules..." |
| 138 | + time_command git submodule update --init --recursive || exit_with_error "Git submodule update failed. Check submodule configuration." |
| 139 | + log_success "Git submodules updated" |
| 140 | + else |
| 141 | + log_info "⏭️ Skipping submodule update (no submodule changes detected)" |
| 142 | + fi |
| 143 | + |
| 144 | + # 3. Selective code formatting (only format staged files) |
| 145 | + if has_staged_files "\.(js|jsx|ts|tsx|json|md|mdx|scss)$"; then |
| 146 | + log_step "Formatting staged code files..." |
| 147 | + |
| 148 | + # Use git to format only staged files for better performance |
| 149 | + local js_files md_files |
| 150 | + js_files=$(echo "$staged_files" | grep -E "\.(js|jsx|ts|tsx|json|scss)$" || true) |
| 151 | + md_files=$(echo "$staged_files" | grep -E "\.(md|mdx)$" || true) |
50 | 152 |
|
51 | | -# Auto-stage any files that were reformatted during the pre-commit process |
52 | | -# Only stage files that already exist and were modified by formatting |
53 | | -if [ -n "$(git diff --name-only)" ]; then |
54 | | - # Filter out any new files and only add existing modified files |
55 | | - modified_files=$(git diff --name-only | while read file; do |
56 | | - if [ -f "$file" ] && git ls-files --error-unmatch "$file" >/dev/null 2>&1; then |
57 | | - echo "$file" |
| 153 | + # Format JavaScript/TypeScript files if any |
| 154 | + if [ -n "$js_files" ]; then |
| 155 | + log_info "🎨 Formatting JS/TS files..." |
| 156 | + echo "$js_files" | xargs yarn prettier --write --config "./.prettierrc.js" || exit_with_error "JavaScript/TypeScript formatting failed" |
58 | 157 | fi |
59 | | - done) |
60 | | - |
61 | | - if [ -n "$modified_files" ]; then |
62 | | - log_info "Auto-staging existing files modified by formatters..." |
63 | | - echo "$modified_files" | xargs git add |
64 | | - log_success "Auto-staged formatted files" |
| 158 | + |
| 159 | + # Format Markdown files if any |
| 160 | + if [ -n "$md_files" ]; then |
| 161 | + log_info "📝 Formatting Markdown files..." |
| 162 | + echo "$md_files" | xargs yarn prettier --write --config "./.prettierrc.js" || exit_with_error "Markdown formatting failed" |
| 163 | + fi |
| 164 | + |
| 165 | + # Re-stage formatted files |
| 166 | + echo "$staged_files" | grep -E "\.(js|jsx|ts|tsx|json|md|mdx|scss)$" | xargs git add || true |
| 167 | + log_success "Code formatting completed and files re-staged" |
| 168 | + else |
| 169 | + log_info "⏭️ Skipping code formatting (no formattable files staged)" |
65 | 170 | fi |
66 | | -fi |
67 | 171 |
|
68 | | -log_success "Pre-commit tasks completed" |
69 | | -log_info "Note: File generation commands (precompiles, variable refs, etc.) have been removed from pre-commit" |
70 | | -log_info "Run these manually when needed: yarn generate-precompiles-ref-tables, yarn update-variable-refs" |
71 | | -log_info "To bypass hooks, use git commit --no-verify" |
| 172 | + # 4. TypeScript type checking (only if TS files are staged) |
| 173 | + if has_staged_files "\.(ts|tsx)$"; then |
| 174 | + log_step "Running TypeScript type checking..." |
| 175 | + time_command yarn typecheck || exit_with_error "TypeScript type checking failed. Fix type errors before committing." |
| 176 | + log_success "TypeScript type checking passed" |
| 177 | + else |
| 178 | + log_info "⏭️ Skipping TypeScript check (no TypeScript files staged)" |
| 179 | + fi |
| 180 | + |
| 181 | + # Final success message with timing |
| 182 | + log_success "🎉 All pre-commit checks passed successfully!" |
| 183 | + log_info "✨ Commit is ready to proceed..." |
| 184 | +} |
| 185 | + |
| 186 | +# Trap to handle interruptions gracefully |
| 187 | +trap 'log_error "Pre-commit hook interrupted"; exit 130' INT TERM |
| 188 | + |
| 189 | +# Execute main function |
| 190 | +main "$@" |
0 commit comments