Skip to content

Instantly share code, notes, and snippets.

@mohit2152sharma
Created November 12, 2025 01:56
Show Gist options
  • Select an option

  • Save mohit2152sharma/415fa206f790e1a243dc6991ffb064ae to your computer and use it in GitHub Desktop.

Select an option

Save mohit2152sharma/415fa206f790e1a243dc6991ffb064ae to your computer and use it in GitHub Desktop.
Setup pre-commit in python or node project
#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Helper functions
print_success() {
echo -e "${GREEN}✓ $1${NC}"
}
print_error() {
echo -e "${RED}✗ $1${NC}"
}
print_info() {
echo -e "${YELLOW}→ $1${NC}"
}
# Detect project type
detect_project_type() {
if [ -f "package.json" ]; then
echo "typescript"
elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ] || [ -f "setup.py" ]; then
echo "python"
else
echo "unknown"
fi
}
# Setup Python project
setup_python() {
print_info "Setting up Python project with pre-commit hooks..."
# Check for Python
if ! command -v python3 &>/dev/null; then
print_error "Python3 is not installed. Please install Python3 first."
exit 1
fi
# Install pre-commit (prefer brew on macOS, fall back to pip)
if ! command -v pre-commit &>/dev/null; then
if command -v brew &>/dev/null; then
print_info "Installing pre-commit via homebrew..."
brew install pre-commit
elif command -v pip3 &>/dev/null; then
print_info "Installing pre-commit via pip..."
pip3 install --user pre-commit
else
print_error "Cannot install pre-commit. Please install homebrew or pip3 first."
exit 1
fi
print_success "pre-commit installed"
else
print_info "pre-commit already installed"
fi
# Ensure user's local bin is in PATH
export PATH="$HOME/.local/bin:$PATH"
# Install Python linting/formatting tools
if command -v pip3 &>/dev/null; then
print_info "Installing Python dependencies (ruff, isort, mypy)..."
pip3 install --user ruff isort mypy 2>/dev/null || {
print_info "Note: Some packages may already be installed or require different installation method"
}
print_success "Python dependencies installed"
else
print_info "pip3 not available, skipping ruff/isort/mypy installation (they'll be managed by pre-commit)"
fi
# Create configuration files
create_python_configs
create_python_precommit_config
# Create secrets baseline BEFORE installing pre-commit hooks
# Initialize pre-commit
print_info "Installing pre-commit hooks..."
pre-commit install
print_success "Pre-commit hooks installed"
# Run pre-commit for the first time to verify setup
print_info "Running pre-commit hooks for the first time (this may take a moment)..."
if pre-commit run --all-files; then
print_success "All pre-commit hooks passed!"
else
print_info "Some hooks failed - this is normal for an initial run"
print_info "Fix any issues and run: pre-commit run --all-files"
fi
print_success "Python project setup complete!"
}
# Setup TypeScript project
setup_typescript() {
print_info "Setting up TypeScript project with pre-commit hooks..."
# Check for Node.js
if ! command -v node &>/dev/null; then
print_error "Node.js is not installed. Please install Node.js first."
exit 1
fi
# Check for npm
if ! command -v npm &>/dev/null; then
print_error "npm is not installed. Please install npm first."
exit 1
fi
print_info "Installing npm dependencies..."
npm install --save-dev eslint prettier typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-prettier eslint-plugin-prettier
print_success "npm dependencies installed"
# Install pre-commit (prefer brew on macOS, fall back to pip)
if ! command -v pre-commit &>/dev/null; then
if command -v brew &>/dev/null; then
print_info "Installing pre-commit via homebrew..."
brew install pre-commit
elif command -v pip3 &>/dev/null; then
print_info "Installing pre-commit via pip..."
pip3 install --user pre-commit
else
print_error "Cannot install pre-commit. Please install homebrew or pip3 first."
exit 1
fi
print_success "pre-commit installed"
else
print_info "pre-commit already installed"
fi
# Ensure user's local bin is in PATH
export PATH="$HOME/.local/bin:$PATH"
# Create configuration files
create_typescript_configs
create_typescript_precommit_config
# Initialize pre-commit
print_info "Installing pre-commit hooks..."
pre-commit install
print_success "Pre-commit hooks installed"
# Run pre-commit for the first time to verify setup
print_info "Running pre-commit hooks for the first time (this may take a moment)..."
if pre-commit run --all-files; then
print_success "All pre-commit hooks passed!"
else
print_info "Some hooks failed - this is normal for an initial run"
print_info "Fix any issues and run: pre-commit run --all-files"
fi
print_success "TypeScript project setup complete!"
}
# Create Python configuration files
create_python_configs() {
print_info "Creating Python configuration files..."
# Check if pyproject.toml exists
if [ -f "pyproject.toml" ]; then
print_info "pyproject.toml exists, adding tool configurations to it..."
# Check if [tool.ruff] section already exists
if ! grep -q "^\[tool\.ruff\]" pyproject.toml; then
cat >>pyproject.toml <<'EOF'
[tool.ruff]
# Set the line length to match black's default
line-length = 88
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default
select = ["E", "F", "W", "I", "N", "UP", "B", "A", "C4", "DTZ", "T10", "DJ", "EM", "ISC", "ICN", "G", "PIE", "T20", "Q", "RSE", "RET", "SIM", "TID", "TCH", "ARG", "PTH", "ERA", "PD", "PL", "TRY", "NPY", "RUF"]
# Allow autofix for all enabled rules
fixable = ["ALL"]
unfixable = []
# Exclude common directories
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".mypy_cache",
".nox",
".pants.d",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
]
# Target Python 3.8+
target-version = "py38"
[tool.ruff.format]
# Use black-compatible formatting
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
[tool.ruff.isort]
known-first-party = ["your_package_name"]
[tool.ruff.mccabe]
max-complexity = 10
EOF
print_success "Added [tool.ruff] configuration to pyproject.toml"
else
print_info "[tool.ruff] section already exists in pyproject.toml, skipping..."
fi
# Check if [tool.isort] section already exists
if ! grep -q "^\[tool\.isort\]" pyproject.toml; then
cat >>pyproject.toml <<'EOF'
[tool.isort]
profile = "black"
line_length = 88
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
ensure_newline_before_comments = true
EOF
print_success "Added [tool.isort] configuration to pyproject.toml"
else
print_info "[tool.isort] section already exists in pyproject.toml, skipping..."
fi
# Check if [tool.mypy] section already exists
if ! grep -q "^\[tool\.mypy\]" pyproject.toml; then
cat >>pyproject.toml <<'EOF'
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = false
disallow_incomplete_defs = false
check_untyped_defs = true
disallow_untyped_decorators = false
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "tests.*"
ignore_errors = true
EOF
print_success "Added [tool.mypy] configuration to pyproject.toml"
else
print_info "[tool.mypy] section already exists in pyproject.toml, skipping..."
fi
else
print_info "pyproject.toml does not exist, creating separate configuration files..."
# Create ruff.toml
cat >ruff.toml <<'EOF'
# Set the line length to match black's default
line-length = 88
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default
select = ["E", "F", "W", "I", "N", "UP", "B", "A", "C4", "DTZ", "T10", "DJ", "EM", "ISC", "ICN", "G", "PIE", "T20", "Q", "RSE", "RET", "SIM", "TID", "TCH", "ARG", "PTH", "ERA", "PD", "PL", "TRY", "NPY", "RUF"]
# Allow autofix for all enabled rules
fixable = ["ALL"]
unfixable = []
# Exclude common directories
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".mypy_cache",
".nox",
".pants.d",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
]
# Target Python 3.8+
target-version = "py38"
[format]
# Use black-compatible formatting
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
[isort]
known-first-party = ["your_package_name"]
[mccabe]
max-complexity = 10
EOF
print_success "Created ruff.toml"
# Create .isort.cfg
cat >.isort.cfg <<'EOF'
[settings]
profile = black
line_length = 88
multi_line_output = 3
include_trailing_comma = True
force_grid_wrap = 0
use_parentheses = True
ensure_newline_before_comments = True
EOF
print_success "Created .isort.cfg"
# Create mypy.ini
cat >mypy.ini <<'EOF'
[mypy]
python_version = 3.8
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = False
disallow_incomplete_defs = False
check_untyped_defs = True
disallow_untyped_decorators = False
no_implicit_optional = True
warn_redundant_casts = True
warn_unused_ignores = True
warn_no_return = True
warn_unreachable = True
strict_equality = True
ignore_missing_imports = True
[mypy-tests.*]
ignore_errors = True
EOF
print_success "Created mypy.ini"
fi
}
# Create Python pre-commit config
create_python_precommit_config() {
print_info "Creating .pre-commit-config.yaml for Python..."
cat >.pre-commit-config.yaml <<'EOF'
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.9
hooks:
# Run the linter
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
# Run the formatter
- id: ruff-format
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
name: isort (python)
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.7.1
hooks:
- id: mypy
additional_dependencies: []
args: [--ignore-missing-imports]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- id: debug-statements
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ['--baseline .secrets.baseline', '--exclude-files ./.venv/*', '--exclude-files ./.env']
EOF
print_success "Created .pre-commit-config.yaml"
}
# Create TypeScript configuration files
create_typescript_configs() {
print_info "Creating TypeScript configuration files..."
# Create .eslintrc.json
cat >.eslintrc.json <<'EOF'
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint", "prettier"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:prettier/recommended"
],
"rules": {
"prettier/prettier": "error",
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
],
"no-console": "warn"
},
"env": {
"node": true,
"es2022": true
}
}
EOF
print_success "Created .eslintrc.json"
# Create .prettierrc.json
cat >.prettierrc.json <<'EOF'
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 88,
"tabWidth": 2,
"useTabs": false,
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf"
}
EOF
print_success "Created .prettierrc.json"
# Create tsconfig.json if it doesn't exist
if [ ! -f "tsconfig.json" ]; then
cat >tsconfig.json <<'EOF'
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
}
EOF
print_success "Created tsconfig.json"
else
print_info "tsconfig.json already exists, skipping..."
fi
}
# Create TypeScript pre-commit config
create_typescript_precommit_config() {
print_info "Creating .pre-commit-config.yaml for TypeScript..."
cat >.pre-commit-config.yaml <<'EOF'
repos:
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier
types_or: [javascript, jsx, ts, tsx, json, yaml, markdown]
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.56.0
hooks:
- id: eslint
files: \.(js|jsx|ts|tsx)$
types: [file]
additional_dependencies:
- [email protected]
- [email protected]
- "@typescript-eslint/[email protected]"
- "@typescript-eslint/[email protected]"
- [email protected]
- [email protected]
- [email protected]
- repo: local
hooks:
- id: tsc
name: TypeScript Compiler
entry: npx tsc --noEmit
language: system
types: [ts, tsx]
pass_filenames: false
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-added-large-files
- id: check-merge-conflict
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ['--baseline .secrets.baseline', '--exclude-files ./node_modules/*', '--exclude-files ./.env']
exclude: package.lock.json
EOF
print_success "Created .pre-commit-config.yaml"
}
# Main execution
main() {
echo "============================================"
echo " Pre-commit Hook Setup Script"
echo "============================================"
echo ""
# Detect project type
PROJECT_TYPE=$(detect_project_type)
if [ "$PROJECT_TYPE" = "unknown" ]; then
print_error "Could not detect project type."
print_info "Please ensure you have either:"
print_info " - package.json (for TypeScript projects)"
print_info " - requirements.txt or pyproject.toml (for Python projects)"
exit 1
fi
print_info "Detected project type: $PROJECT_TYPE"
echo ""
# Setup based on project type
if [ "$PROJECT_TYPE" = "python" ]; then
setup_python
elif [ "$PROJECT_TYPE" = "typescript" ]; then
setup_typescript
fi
echo ""
echo "============================================"
print_success "Setup complete!"
echo "============================================"
echo ""
print_info "Your pre-commit hooks are now installed."
print_info "They will run automatically on 'git commit'."
print_info "To manually run hooks: pre-commit run --all-files"
echo ""
echo ""
print_info "Note: If 'pre-commit' command is not found, add this to your ~/.zshrc or ~/.bashrc:"
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
}
# Run main function
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment