From bb1a67a3bec20b1c5602be2f574e580247a216f7 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 20 Sep 2025 13:19:17 -1000 Subject: [PATCH 1/2] Testing skip hooks option --- .lefthook.yml | 23 +++++++++++++++++++ CLAUDE.md | 12 ++++++++++ CONTRIBUTING.md | 24 +++++++++++++++----- Gemfile.development_dependencies | 1 + Gemfile.lock | 2 ++ bin/lefthook/check-trailing-newlines | 34 ++++++++++++++++++++++++++++ bin/lefthook/get-changed-files | 22 ++++++++++++++++++ bin/lefthook/prettier-format | 22 ++++++++++++++++++ bin/lefthook/ruby-autofix | 22 ++++++++++++++++++ bin/lefthook/ruby-lint | 23 +++++++++++++++++++ 10 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 .lefthook.yml create mode 100755 bin/lefthook/check-trailing-newlines create mode 100755 bin/lefthook/get-changed-files create mode 100755 bin/lefthook/prettier-format create mode 100755 bin/lefthook/ruby-autofix create mode 100755 bin/lefthook/ruby-lint diff --git a/.lefthook.yml b/.lefthook.yml new file mode 100644 index 0000000000..fe86b3b769 --- /dev/null +++ b/.lefthook.yml @@ -0,0 +1,23 @@ +# .lefthook.yml +# Fast pre-commit hooks that only check changed files +# Install with: bundle exec lefthook install + +pre-commit: + parallel: true + commands: + autofix: + run: bin/lefthook/ruby-autofix staged + + rubocop: + run: bin/lefthook/ruby-lint staged + + prettier: + run: bin/lefthook/prettier-format staged + + trailing-newlines: + run: bin/lefthook/check-trailing-newlines staged + +pre-push: + commands: + branch-lint: + run: bin/lefthook/ruby-lint branch diff --git a/CLAUDE.md b/CLAUDE.md index dd5c622141..6acaf47f3d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,6 +12,18 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co These requirements are non-negotiable. CI will fail if not followed. +**πŸš€ RECOMMENDED: Install Git hooks to automate these checks:** + +```bash +# Install Lefthook hooks (already included in Gemfile) +bundle install +bundle exec lefthook install +``` + +This will automatically run linting on **only the files you changed** before each commit - making it fast! + +**Note:** Git hooks are for React on Rails gem developers only, not for users who install the gem. + ## Development Commands ### Essential Commands diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b20d1d7321..2eecb9c292 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,6 +7,16 @@ ## Prerequisites - [Yalc](https://github.com/whitecolor/yalc) must be installed globally for most local development. +- **Git hooks setup** (REQUIRED for all contributors): + +```sh +cd react_on_rails/ +bundle install +bundle exec lefthook install +``` + +This sets up automatic linting that runs **only on files you changed** - making commits fast while preventing CI failures. + - After updating code via Git, to prepare all examples: ```sh @@ -457,7 +467,9 @@ This approach: ## Pre-Commit Requirements -**CRITICAL**: Before committing any changes, always run the following commands to ensure code quality: +**AUTOMATED**: If you've set up Lefthook (see Prerequisites), linting runs automatically on changed files before each commit. + +**MANUAL OPTION**: If you need to run linting manually: ```bash # Navigate to the main react_on_rails directory @@ -476,14 +488,14 @@ rake lint:rubocop rake lint ``` -**Automated checks:** +**Git hooks automatically run:** -- Format all JavaScript/TypeScript files with Prettier +- Format JavaScript/TypeScript files with Prettier (on changed files only) - Check and fix linting issues with ESLint -- Check and fix Ruby style issues with RuboCop -- Ensure all tests pass before pushing +- Check and fix Ruby style issues with RuboCop (on changed files only) +- Ensure trailing newlines on all files -**Tip**: Set up your IDE to run these automatically on save to catch issues early. +**Setup once**: `bundle exec lefthook install` (see Prerequisites above) ## πŸ€– Best Practices for AI Coding Agents diff --git a/Gemfile.development_dependencies b/Gemfile.development_dependencies index 1b4814c94a..d04a6fb099 100644 --- a/Gemfile.development_dependencies +++ b/Gemfile.development_dependencies @@ -38,6 +38,7 @@ group :development, :test do gem "rubocop-rspec", "~>2.26", require: false gem "scss_lint", require: false gem "spring", "~> 4.0" + gem "lefthook", require: false end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index f03f1a028b..387e96ab52 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -156,6 +156,7 @@ GEM launchy (3.0.1) addressable (~> 2.8) childprocess (~> 5.0) + lefthook (1.13.1) listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) @@ -412,6 +413,7 @@ DEPENDENCIES jbuilder jquery-rails launchy + lefthook listen package_json pry diff --git a/bin/lefthook/check-trailing-newlines b/bin/lefthook/check-trailing-newlines new file mode 100755 index 0000000000..2d14a62a15 --- /dev/null +++ b/bin/lefthook/check-trailing-newlines @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Check for trailing newlines on all changed files +set -euo pipefail + +CONTEXT="${1:-staged}" +files="$(bin/lefthook/get-changed-files "$CONTEXT" '.*')" + +if [ -z "$files" ]; then + echo "βœ… No files to check for trailing newlines" + exit 0 +fi + +echo "πŸ” Checking trailing newlines on $CONTEXT files..." + +failed_files="" +for file in $files; do + if [ -f "$file" ] && [ -s "$file" ]; then + if ! tail -c 1 "$file" | grep -q '^$'; then + echo "❌ Missing trailing newline: $file" + failed_files="$failed_files $file" + fi + fi +done + +if [ -n "$failed_files" ]; then + echo "" + echo "❌ Trailing newline check failed!" + echo "πŸ’‘ Add trailing newlines to:$failed_files" + echo "πŸ”§ Quick fix: for file in$failed_files; do echo >> \"\$file\"; done" + echo "🚫 Skip hook: git commit --no-verify" + exit 1 +fi + +echo "βœ… All files have proper trailing newlines" diff --git a/bin/lefthook/get-changed-files b/bin/lefthook/get-changed-files new file mode 100755 index 0000000000..be21f44bc4 --- /dev/null +++ b/bin/lefthook/get-changed-files @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Get changed files based on context (staged, branch, or all) +set -euo pipefail + +CONTEXT="${1:-staged}" +PATTERN="${2:-.*}" + +case "$CONTEXT" in + staged) + git diff --cached --name-only --diff-filter=ACM | grep -E "$PATTERN" || true + ;; + branch) + # Find base branch (prefer main over master) + base="origin/main" + git rev-parse --verify --quiet "$base" >/dev/null || base="origin/master" + git diff --name-only --diff-filter=ACM "$base"...HEAD | grep -E "$PATTERN" || true + ;; + *) + echo "Usage: $0 {staged|branch} [pattern]" >&2 + exit 1 + ;; +esac diff --git a/bin/lefthook/prettier-format b/bin/lefthook/prettier-format new file mode 100755 index 0000000000..ab8abe8fea --- /dev/null +++ b/bin/lefthook/prettier-format @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Format JS/TS/JSON/MD files with Prettier +set -euo pipefail + +CONTEXT="${1:-staged}" +files="$(bin/lefthook/get-changed-files "$CONTEXT" '\.(js|jsx|ts|tsx|json|md|yml|yaml)$')" + +if [ -z "$files" ]; then + echo "βœ… No files to format with Prettier" + exit 0 +fi + +echo "πŸ’… Prettier on $CONTEXT files:" +printf " %s\n" $files + +yarn run prettier --write $files + +# Re-stage files if running on staged context +if [ "$CONTEXT" = "staged" ]; then + echo $files | xargs -r git add + echo "βœ… Re-staged formatted files" +fi diff --git a/bin/lefthook/ruby-autofix b/bin/lefthook/ruby-autofix new file mode 100755 index 0000000000..51666ab6cb --- /dev/null +++ b/bin/lefthook/ruby-autofix @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Auto-fix Ruby files using rake autofix +set -euo pipefail + +CONTEXT="${1:-staged}" +files="$(bin/lefthook/get-changed-files "$CONTEXT" '\.(rb|rake|ru)$')" + +if [ -z "$files" ]; then + echo "βœ… No Ruby files to autofix" + exit 0 +fi + +echo "🎨 Autofix on $CONTEXT Ruby files:" +printf " %s\n" $files + +bundle exec rake autofix + +# Re-stage files if running on staged context +if [ "$CONTEXT" = "staged" ]; then + echo $files | xargs -r git add + echo "βœ… Re-staged formatted files" +fi diff --git a/bin/lefthook/ruby-lint b/bin/lefthook/ruby-lint new file mode 100755 index 0000000000..15b8086574 --- /dev/null +++ b/bin/lefthook/ruby-lint @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Lint Ruby files with RuboCop +set -euo pipefail + +CONTEXT="${1:-staged}" +files="$(bin/lefthook/get-changed-files "$CONTEXT" '\.(rb|rake|ru)$')" + +if [ -z "$files" ]; then + echo "βœ… No Ruby files to lint" + exit 0 +fi + +echo "πŸ” RuboCop on $CONTEXT Ruby files:" +printf " %s\n" $files + +if ! bundle exec rubocop --force-exclusion --display-cop-names -- $files; then + echo "" + echo "❌ RuboCop check failed!" + echo "πŸ’‘ Auto-fix: bundle exec rubocop --auto-correct --force-exclusion -- $files" + echo "🚫 Skip hook: git commit --no-verify" + exit 1 +fi +echo "βœ… RuboCop checks passed for Ruby files" From 3812b8f42fd633180b1f85dd790eafa7024711c2 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 20 Sep 2025 13:28:32 -1000 Subject: [PATCH 2/2] Implement automatic Git hooks with Lefthook for code quality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .lefthook.yml | 10 +++++----- CLAUDE.md | 10 ++-------- CONTRIBUTING.md | 14 ++++---------- bin/lefthook/check-trailing-newlines | 6 +++++- bin/lefthook/get-changed-files | 6 +++++- bin/lefthook/prettier-format | 10 +++++++--- bin/lefthook/ruby-autofix | 10 +++++++--- bin/lefthook/ruby-lint | 6 +++++- package.json | 3 ++- script/bootstrap | 6 ++++++ 10 files changed, 48 insertions(+), 33 deletions(-) diff --git a/.lefthook.yml b/.lefthook.yml index fe86b3b769..1010a9413f 100644 --- a/.lefthook.yml +++ b/.lefthook.yml @@ -1,21 +1,21 @@ # .lefthook.yml -# Fast pre-commit hooks that only check changed files +# Fast pre-commit hooks that check all changed files (staged + unstaged + untracked) # Install with: bundle exec lefthook install pre-commit: parallel: true commands: autofix: - run: bin/lefthook/ruby-autofix staged + run: bin/lefthook/ruby-autofix all-changed rubocop: - run: bin/lefthook/ruby-lint staged + run: bin/lefthook/ruby-lint all-changed prettier: - run: bin/lefthook/prettier-format staged + run: bin/lefthook/prettier-format all-changed trailing-newlines: - run: bin/lefthook/check-trailing-newlines staged + run: bin/lefthook/check-trailing-newlines all-changed pre-push: commands: diff --git a/CLAUDE.md b/CLAUDE.md index 6acaf47f3d..927c07beb4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,15 +12,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co These requirements are non-negotiable. CI will fail if not followed. -**πŸš€ RECOMMENDED: Install Git hooks to automate these checks:** +**πŸš€ AUTOMATIC: Git hooks are installed automatically during setup** -```bash -# Install Lefthook hooks (already included in Gemfile) -bundle install -bundle exec lefthook install -``` - -This will automatically run linting on **only the files you changed** before each commit - making it fast! +Git hooks will automatically run linting on **all changed files (staged + unstaged + untracked)** before each commit - making it fast while preventing CI failures! **Note:** Git hooks are for React on Rails gem developers only, not for users who install the gem. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2eecb9c292..82473521ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,15 +7,9 @@ ## Prerequisites - [Yalc](https://github.com/whitecolor/yalc) must be installed globally for most local development. -- **Git hooks setup** (REQUIRED for all contributors): +- **Git hooks setup** (automatic during normal setup): -```sh -cd react_on_rails/ -bundle install -bundle exec lefthook install -``` - -This sets up automatic linting that runs **only on files you changed** - making commits fast while preventing CI failures. +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. - After updating code via Git, to prepare all examples: @@ -492,10 +486,10 @@ rake lint - Format JavaScript/TypeScript files with Prettier (on changed files only) - Check and fix linting issues with ESLint -- Check and fix Ruby style issues with RuboCop (on changed files only) +- Check and fix Ruby style issues with RuboCop (on all changed files) - Ensure trailing newlines on all files -**Setup once**: `bundle exec lefthook install` (see Prerequisites above) +**Setup**: Automatic during normal development setup ## πŸ€– Best Practices for AI Coding Agents diff --git a/bin/lefthook/check-trailing-newlines b/bin/lefthook/check-trailing-newlines index 2d14a62a15..4cd6dd87ed 100755 --- a/bin/lefthook/check-trailing-newlines +++ b/bin/lefthook/check-trailing-newlines @@ -10,7 +10,11 @@ if [ -z "$files" ]; then exit 0 fi -echo "πŸ” Checking trailing newlines on $CONTEXT files..." +if [ "$CONTEXT" = "all-changed" ]; then + echo "πŸ” Checking trailing newlines on all changed files..." +else + echo "πŸ” Checking trailing newlines on $CONTEXT files..." +fi failed_files="" for file in $files; do diff --git a/bin/lefthook/get-changed-files b/bin/lefthook/get-changed-files index be21f44bc4..7d501f72a0 100755 --- a/bin/lefthook/get-changed-files +++ b/bin/lefthook/get-changed-files @@ -9,6 +9,10 @@ case "$CONTEXT" in staged) git diff --cached --name-only --diff-filter=ACM | grep -E "$PATTERN" || true ;; + all-changed) + # Get all changed files (staged + unstaged + untracked) vs working directory + (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 + ;; branch) # Find base branch (prefer main over master) base="origin/main" @@ -16,7 +20,7 @@ case "$CONTEXT" in git diff --name-only --diff-filter=ACM "$base"...HEAD | grep -E "$PATTERN" || true ;; *) - echo "Usage: $0 {staged|branch} [pattern]" >&2 + echo "Usage: $0 {staged|all-changed|branch} [pattern]" >&2 exit 1 ;; esac diff --git a/bin/lefthook/prettier-format b/bin/lefthook/prettier-format index ab8abe8fea..728f2f432c 100755 --- a/bin/lefthook/prettier-format +++ b/bin/lefthook/prettier-format @@ -10,13 +10,17 @@ if [ -z "$files" ]; then exit 0 fi -echo "πŸ’… Prettier on $CONTEXT files:" +if [ "$CONTEXT" = "all-changed" ]; then + echo "πŸ’… Prettier on all changed files:" +else + echo "πŸ’… Prettier on $CONTEXT files:" +fi printf " %s\n" $files yarn run prettier --write $files -# Re-stage files if running on staged context -if [ "$CONTEXT" = "staged" ]; then +# Re-stage files if running on staged or all-changed context +if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then echo $files | xargs -r git add echo "βœ… Re-staged formatted files" fi diff --git a/bin/lefthook/ruby-autofix b/bin/lefthook/ruby-autofix index 51666ab6cb..f3a257c798 100755 --- a/bin/lefthook/ruby-autofix +++ b/bin/lefthook/ruby-autofix @@ -10,13 +10,17 @@ if [ -z "$files" ]; then exit 0 fi -echo "🎨 Autofix on $CONTEXT Ruby files:" +if [ "$CONTEXT" = "all-changed" ]; then + echo "🎨 Autofix on all changed Ruby files:" +else + echo "🎨 Autofix on $CONTEXT Ruby files:" +fi printf " %s\n" $files bundle exec rake autofix -# Re-stage files if running on staged context -if [ "$CONTEXT" = "staged" ]; then +# Re-stage files if running on staged or all-changed context +if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then echo $files | xargs -r git add echo "βœ… Re-staged formatted files" fi diff --git a/bin/lefthook/ruby-lint b/bin/lefthook/ruby-lint index 15b8086574..7e987fdb93 100755 --- a/bin/lefthook/ruby-lint +++ b/bin/lefthook/ruby-lint @@ -10,7 +10,11 @@ if [ -z "$files" ]; then exit 0 fi -echo "πŸ” RuboCop on $CONTEXT Ruby files:" +if [ "$CONTEXT" = "all-changed" ]; then + echo "πŸ” RuboCop on all changed Ruby files:" +else + echo "πŸ” RuboCop on $CONTEXT Ruby files:" +fi printf " %s\n" $files if ! bundle exec rubocop --force-exclusion --display-cop-names -- $files; then diff --git a/package.json b/package.json index 3ce4e03cb5..9a33758804 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,8 @@ "type-check": "yarn run tsc --noEmit --noErrorTruncation", "release:patch": "node_package/scripts/release patch", "release:minor": "node_package/scripts/release minor", - "release:major": "node_package/scripts/release major" + "release:major": "node_package/scripts/release major", + "postinstall": "test -f .lefthook.yml && test -d .git && command -v bundle >/dev/null 2>&1 && bundle exec lefthook install || true" }, "repository": { "type": "git", diff --git a/script/bootstrap b/script/bootstrap index cb2a2e93cd..9b6543368a 100644 --- a/script/bootstrap +++ b/script/bootstrap @@ -30,4 +30,10 @@ if [ -f "Gemfile" ]; then bundle check --path vendor/gems 2>&1 >/dev/null || { bundle install --path vendor/gems --quiet --without production } + + # Install Git hooks for code quality (development only) + if [ -f ".lefthook.yml" ] && [ -d ".git" ]; then + echo "==> Installing Git hooks for code quality…" + bundle exec lefthook install + fi fi