Skip to content

Commit dcca723

Browse files
authored
Add smart pre-commit hooks to prevent CI failures (#1792)
* Testing skip hooks option * Implement automatic Git hooks with Lefthook for code quality - Replace manual hook installation with modular Lefthook configuration - Check all changed files (staged + unstaged + untracked) for comprehensive coverage - Add separate scripts in bin/lefthook/ for testability and maintainability - Automatic installation via script/bootstrap and package.json postinstall - Provide helpful error messages with fix commands and skip options - Parallel execution of linting checks for performance Prevents CI failures by catching linting issues locally before commits.
1 parent 658e0ac commit dcca723

File tree

12 files changed

+195
-7
lines changed

12 files changed

+195
-7
lines changed

.lefthook.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# .lefthook.yml
2+
# Fast pre-commit hooks that check all changed files (staged + unstaged + untracked)
3+
# Install with: bundle exec lefthook install
4+
5+
pre-commit:
6+
parallel: true
7+
commands:
8+
autofix:
9+
run: bin/lefthook/ruby-autofix all-changed
10+
11+
rubocop:
12+
run: bin/lefthook/ruby-lint all-changed
13+
14+
prettier:
15+
run: bin/lefthook/prettier-format all-changed
16+
17+
trailing-newlines:
18+
run: bin/lefthook/check-trailing-newlines all-changed
19+
20+
pre-push:
21+
commands:
22+
branch-lint:
23+
run: bin/lefthook/ruby-lint branch

CLAUDE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
1212

1313
These requirements are non-negotiable. CI will fail if not followed.
1414

15+
**🚀 AUTOMATIC: Git hooks are installed automatically during setup**
16+
17+
Git hooks will automatically run linting on **all changed files (staged + unstaged + untracked)** before each commit - making it fast while preventing CI failures!
18+
19+
**Note:** Git hooks are for React on Rails gem developers only, not for users who install the gem.
20+
1521
## Development Commands
1622

1723
### Essential Commands

CONTRIBUTING.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
## Prerequisites
88

99
- [Yalc](https://github.com/whitecolor/yalc) must be installed globally for most local development.
10+
- **Git hooks setup** (automatic during normal setup):
11+
12+
Git hooks are installed automatically when you run the standard setup commands. They will run automatic linting on **all changed files (staged + unstaged + untracked)** - making commits fast while preventing CI failures.
13+
1014
- After updating code via Git, to prepare all examples:
1115

1216
```sh
@@ -457,7 +461,9 @@ This approach:
457461

458462
## Pre-Commit Requirements
459463

460-
**CRITICAL**: Before committing any changes, always run the following commands to ensure code quality:
464+
**AUTOMATED**: If you've set up Lefthook (see Prerequisites), linting runs automatically on changed files before each commit.
465+
466+
**MANUAL OPTION**: If you need to run linting manually:
461467

462468
```bash
463469
# Navigate to the main react_on_rails directory
@@ -476,14 +482,14 @@ rake lint:rubocop
476482
rake lint
477483
```
478484

479-
**Automated checks:**
485+
**Git hooks automatically run:**
480486

481-
- Format all JavaScript/TypeScript files with Prettier
487+
- Format JavaScript/TypeScript files with Prettier (on changed files only)
482488
- Check and fix linting issues with ESLint
483-
- Check and fix Ruby style issues with RuboCop
484-
- Ensure all tests pass before pushing
489+
- Check and fix Ruby style issues with RuboCop (on all changed files)
490+
- Ensure trailing newlines on all files
485491

486-
**Tip**: Set up your IDE to run these automatically on save to catch issues early.
492+
**Setup**: Automatic during normal development setup
487493

488494
## 🤖 Best Practices for AI Coding Agents
489495

Gemfile.development_dependencies

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ group :development, :test do
3838
gem "rubocop-rspec", "~>2.26", require: false
3939
gem "scss_lint", require: false
4040
gem "spring", "~> 4.0"
41+
gem "lefthook", require: false
4142
end
4243

4344
group :test do

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ GEM
156156
launchy (3.0.1)
157157
addressable (~> 2.8)
158158
childprocess (~> 5.0)
159+
lefthook (1.13.1)
159160
listen (3.9.0)
160161
rb-fsevent (~> 0.10, >= 0.10.3)
161162
rb-inotify (~> 0.9, >= 0.9.10)
@@ -412,6 +413,7 @@ DEPENDENCIES
412413
jbuilder
413414
jquery-rails
414415
launchy
416+
lefthook
415417
listen
416418
package_json
417419
pry
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env bash
2+
# Check for trailing newlines on all changed files
3+
set -euo pipefail
4+
5+
CONTEXT="${1:-staged}"
6+
files="$(bin/lefthook/get-changed-files "$CONTEXT" '.*')"
7+
8+
if [ -z "$files" ]; then
9+
echo "✅ No files to check for trailing newlines"
10+
exit 0
11+
fi
12+
13+
if [ "$CONTEXT" = "all-changed" ]; then
14+
echo "🔍 Checking trailing newlines on all changed files..."
15+
else
16+
echo "🔍 Checking trailing newlines on $CONTEXT files..."
17+
fi
18+
19+
failed_files=""
20+
for file in $files; do
21+
if [ -f "$file" ] && [ -s "$file" ]; then
22+
if ! tail -c 1 "$file" | grep -q '^$'; then
23+
echo "❌ Missing trailing newline: $file"
24+
failed_files="$failed_files $file"
25+
fi
26+
fi
27+
done
28+
29+
if [ -n "$failed_files" ]; then
30+
echo ""
31+
echo "❌ Trailing newline check failed!"
32+
echo "💡 Add trailing newlines to:$failed_files"
33+
echo "🔧 Quick fix: for file in$failed_files; do echo >> \"\$file\"; done"
34+
echo "🚫 Skip hook: git commit --no-verify"
35+
exit 1
36+
fi
37+
38+
echo "✅ All files have proper trailing newlines"

bin/lefthook/get-changed-files

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
# Get changed files based on context (staged, branch, or all)
3+
set -euo pipefail
4+
5+
CONTEXT="${1:-staged}"
6+
PATTERN="${2:-.*}"
7+
8+
case "$CONTEXT" in
9+
staged)
10+
git diff --cached --name-only --diff-filter=ACM | grep -E "$PATTERN" || true
11+
;;
12+
all-changed)
13+
# Get all changed files (staged + unstaged + untracked) vs working directory
14+
(git diff --cached --name-only --diff-filter=ACM; git diff --name-only --diff-filter=ACM; git ls-files --others --exclude-standard) | sort -u | grep -E "$PATTERN" || true
15+
;;
16+
branch)
17+
# Find base branch (prefer main over master)
18+
base="origin/main"
19+
git rev-parse --verify --quiet "$base" >/dev/null || base="origin/master"
20+
git diff --name-only --diff-filter=ACM "$base"...HEAD | grep -E "$PATTERN" || true
21+
;;
22+
*)
23+
echo "Usage: $0 {staged|all-changed|branch} [pattern]" >&2
24+
exit 1
25+
;;
26+
esac

bin/lefthook/prettier-format

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
# Format JS/TS/JSON/MD files with Prettier
3+
set -euo pipefail
4+
5+
CONTEXT="${1:-staged}"
6+
files="$(bin/lefthook/get-changed-files "$CONTEXT" '\.(js|jsx|ts|tsx|json|md|yml|yaml)$')"
7+
8+
if [ -z "$files" ]; then
9+
echo "✅ No files to format with Prettier"
10+
exit 0
11+
fi
12+
13+
if [ "$CONTEXT" = "all-changed" ]; then
14+
echo "💅 Prettier on all changed files:"
15+
else
16+
echo "💅 Prettier on $CONTEXT files:"
17+
fi
18+
printf " %s\n" $files
19+
20+
yarn run prettier --write $files
21+
22+
# Re-stage files if running on staged or all-changed context
23+
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
24+
echo $files | xargs -r git add
25+
echo "✅ Re-staged formatted files"
26+
fi

bin/lefthook/ruby-autofix

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
# Auto-fix Ruby files using rake autofix
3+
set -euo pipefail
4+
5+
CONTEXT="${1:-staged}"
6+
files="$(bin/lefthook/get-changed-files "$CONTEXT" '\.(rb|rake|ru)$')"
7+
8+
if [ -z "$files" ]; then
9+
echo "✅ No Ruby files to autofix"
10+
exit 0
11+
fi
12+
13+
if [ "$CONTEXT" = "all-changed" ]; then
14+
echo "🎨 Autofix on all changed Ruby files:"
15+
else
16+
echo "🎨 Autofix on $CONTEXT Ruby files:"
17+
fi
18+
printf " %s\n" $files
19+
20+
bundle exec rake autofix
21+
22+
# Re-stage files if running on staged or all-changed context
23+
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
24+
echo $files | xargs -r git add
25+
echo "✅ Re-staged formatted files"
26+
fi

bin/lefthook/ruby-lint

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env bash
2+
# Lint Ruby files with RuboCop
3+
set -euo pipefail
4+
5+
CONTEXT="${1:-staged}"
6+
files="$(bin/lefthook/get-changed-files "$CONTEXT" '\.(rb|rake|ru)$')"
7+
8+
if [ -z "$files" ]; then
9+
echo "✅ No Ruby files to lint"
10+
exit 0
11+
fi
12+
13+
if [ "$CONTEXT" = "all-changed" ]; then
14+
echo "🔍 RuboCop on all changed Ruby files:"
15+
else
16+
echo "🔍 RuboCop on $CONTEXT Ruby files:"
17+
fi
18+
printf " %s\n" $files
19+
20+
if ! bundle exec rubocop --force-exclusion --display-cop-names -- $files; then
21+
echo ""
22+
echo "❌ RuboCop check failed!"
23+
echo "💡 Auto-fix: bundle exec rubocop --auto-correct --force-exclusion -- $files"
24+
echo "🚫 Skip hook: git commit --no-verify"
25+
exit 1
26+
fi
27+
echo "✅ RuboCop checks passed for Ruby files"

0 commit comments

Comments
 (0)