Created
November 12, 2025 01:56
-
-
Save mohit2152sharma/415fa206f790e1a243dc6991ffb064ae to your computer and use it in GitHub Desktop.
Setup pre-commit in python or node project
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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