+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 255 of 355

๐Ÿ• Husky: Git Hooks Integration

Master husky: git hooks integration in TypeScript with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
30 min read

Prerequisites

  • Basic understanding of JavaScript ๐Ÿ“
  • TypeScript installation โšก
  • VS Code or preferred IDE ๐Ÿ’ป
  • Git version control knowledge ๐ŸŒฒ

What you'll learn

  • Understand git hooks and husky fundamentals ๐ŸŽฏ
  • Apply husky in real TypeScript projects ๐Ÿ—๏ธ
  • Debug common husky issues ๐Ÿ›
  • Write type-safe pre-commit workflows โœจ

๐ŸŽฏ Introduction

Welcome to the exciting world of Git hooks with Husky! ๐ŸŽ‰ In this guide, weโ€™ll explore how to automate your development workflow and catch issues before they reach your repository.

Youโ€™ll discover how Husky can transform your TypeScript development experience by automatically running tests ๐Ÿงช, linters ๐Ÿ”, and type checks โœ… every time you make a commit. Whether youโ€™re working solo or with a team ๐Ÿ‘ฅ, understanding Husky is essential for maintaining code quality and preventing bugs from sneaking into your codebase.

By the end of this tutorial, youโ€™ll feel confident setting up robust pre-commit workflows in your TypeScript projects! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Git Hooks and Husky

๐Ÿค” What are Git Hooks?

Git hooks are like automated security guards ๐Ÿ›ก๏ธ for your repository. Think of them as checkpoints at an airport โœˆ๏ธ that ensure everything is safe before letting you board (or in this case, commit your code).

In Git terms, hooks are scripts that run automatically at specific points in your Git workflow ๐Ÿ”„. This means you can:

  • โœจ Run tests before every commit
  • ๐Ÿš€ Check code formatting automatically
  • ๐Ÿ›ก๏ธ Prevent bad code from entering your repository
  • ๐Ÿ“ Enforce commit message standards

๐Ÿ’ก What is Husky?

Husky is like a friendly dog trainer ๐Ÿ• that makes managing Git hooks super easy! Instead of dealing with complex shell scripts hidden in .git/hooks, Husky lets you:

  1. Easy Setup ๐Ÿ”ง: Configure hooks with simple npm commands
  2. Version Control ๐Ÿ“ฆ: Keep your hooks in your repository
  3. Team Sharing ๐Ÿ‘ฅ: Everyone gets the same quality checks
  4. TypeScript Integration ๐Ÿ’™: Perfect for TypeScript projects

Real-world example: Imagine working on a team project ๐Ÿ‘จโ€๐Ÿ’ป๐Ÿ‘ฉโ€๐Ÿ’ป. With Husky, every developer automatically runs the same linting and testing checks before committing, ensuring consistent code quality!

๐Ÿ”ง Basic Installation and Setup

๐Ÿ“ฆ Installing Husky

Letโ€™s start with a friendly setup process:

# ๐Ÿš€ Install husky as a dev dependency
npm install --save-dev husky

# ๐ŸŽฏ Initialize husky in your project
npx husky install

# โœจ Make husky installation automatic for new contributors
npm pkg set scripts.prepare="husky install"

๐Ÿ’ก Explanation: The prepare script ensures husky gets set up automatically when someone runs npm install on your project!

๐ŸŽจ Project Structure

After installation, your project will look like this:

your-project/
โ”œโ”€โ”€ .husky/          # ๐Ÿ• Husky configuration folder
โ”‚   โ”œโ”€โ”€ _/          # ๐Ÿ“ Husky internal files
โ”‚   โ””โ”€โ”€ pre-commit  # ๐ŸŽฏ Pre-commit hook (we'll create this)
โ”œโ”€โ”€ package.json     # ๐Ÿ“ฆ Dependencies and scripts
โ””โ”€โ”€ src/            # ๐Ÿ’ป Your TypeScript code

๐ŸŽฏ Your First Hook

Letโ€™s create a simple pre-commit hook:

# ๐Ÿ› ๏ธ Create a pre-commit hook that runs TypeScript checks
npx husky add .husky/pre-commit "npm run typecheck"

This creates a file at .husky/pre-commit that looks like:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run typecheck

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: E-commerce TypeScript Project

Letโ€™s set up comprehensive quality checks for an online store:

// ๐Ÿช Our e-commerce types
interface Product {
  id: string;
  name: string;
  price: number;
  category: 'electronics' | 'clothing' | 'books';
  inStock: boolean;
  emoji: string; // Every product needs personality! 
}

interface ShoppingCart {
  id: string;
  items: CartItem[];
  total: number;
  customerEmail: string;
}

interface CartItem {
  product: Product;
  quantity: number;
}

// ๐Ÿ›๏ธ Shopping cart service
class CartService {
  private carts: Map<string, ShoppingCart> = new Map();
  
  // โž• Add item to cart
  addToCart(cartId: string, product: Product, quantity: number): void {
    const cart = this.carts.get(cartId);
    if (!cart) {
      throw new Error(`๐Ÿšซ Cart ${cartId} not found!`);
    }
    
    if (!product.inStock) {
      throw new Error(`๐Ÿ“ฆ ${product.name} is out of stock!`);
    }
    
    const existingItem = cart.items.find(item => item.product.id === product.id);
    if (existingItem) {
      existingItem.quantity += quantity;
    } else {
      cart.items.push({ product, quantity });
    }
    
    this.updateTotal(cart);
    console.log(`โœ… Added ${product.emoji} ${product.name} to cart!`);
  }
  
  // ๐Ÿ’ฐ Calculate total
  private updateTotal(cart: ShoppingCart): void {
    cart.total = cart.items.reduce((sum, item) => 
      sum + (item.product.price * item.quantity), 0
    );
  }
}

package.json scripts for this project:

{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "lint": "eslint src/**/*.ts",
    "format": "prettier --write src/**/*.ts",
    "test": "jest",
    "build": "tsc"
  }
}

๐Ÿ• Husky hooks setup:

# ๐ŸŽฏ Pre-commit: Run all quality checks
npx husky add .husky/pre-commit "npm run lint && npm run typecheck && npm run test"

# ๐Ÿš€ Pre-push: Ensure build works
npx husky add .husky/pre-push "npm run build"

# ๐Ÿ“ Commit message validation
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

๐ŸŽฎ Example 2: Game Development Project

Letโ€™s make our game development workflow bulletproof:

// ๐ŸŽฏ Game state management
interface GameState {
  player: Player;
  enemies: Enemy[];
  score: number;
  level: number;
  powerUps: PowerUp[];
}

interface Player {
  id: string;
  name: string;
  health: number;
  position: Position;
  inventory: Item[];
  emoji: string;
}

interface Enemy {
  id: string;
  type: 'goblin' | 'dragon' | 'skeleton';
  health: number;
  damage: number;
  position: Position;
  emoji: string;
}

interface Position {
  x: number;
  y: number;
}

// ๐ŸŽฎ Game engine
class GameEngine {
  private state: GameState;
  
  constructor(playerName: string) {
    this.state = {
      player: {
        id: 'player1',
        name: playerName,
        health: 100,
        position: { x: 0, y: 0 },
        inventory: [],
        emoji: '๐Ÿง™โ€โ™‚๏ธ'
      },
      enemies: [],
      score: 0,
      level: 1,
      powerUps: []
    };
  }
  
  // โš”๏ธ Battle logic
  attack(targetId: string): boolean {
    const enemy = this.state.enemies.find(e => e.id === targetId);
    if (!enemy) {
      console.log('๐Ÿšซ Enemy not found!');
      return false;
    }
    
    const damage = Math.floor(Math.random() * 20) + 10;
    enemy.health -= damage;
    console.log(`โš”๏ธ Hit ${enemy.emoji} for ${damage} damage!`);
    
    if (enemy.health <= 0) {
      this.defeatEnemy(enemy);
      return true;
    }
    
    return false;
  }
  
  // ๐Ÿ† Victory!
  private defeatEnemy(enemy: Enemy): void {
    this.state.enemies = this.state.enemies.filter(e => e.id !== enemy.id);
    this.state.score += 100;
    console.log(`๐ŸŽ‰ Defeated ${enemy.emoji}! Score: ${this.state.score}`);
  }
}

๐Ÿ”ง Advanced Husky Configuration:

# ๐Ÿงช Run specific tests for game files
npx husky add .husky/pre-commit "npm run test:game && npm run lint:strict"

# ๐ŸŽจ Auto-format game assets
npx husky add .husky/pre-commit "npm run format:assets"

# ๐Ÿ—๏ธ Build and test game bundle
npx husky add .husky/pre-push "npm run build:game && npm run test:integration"

๐Ÿš€ Advanced Husky Configurations

๐Ÿง™โ€โ™‚๏ธ Conditional Hooks with lint-staged

For better performance, only check changed files:

# ๐Ÿ“ฆ Install lint-staged
npm install --save-dev lint-staged

package.json configuration:

{
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix",
      "prettier --write",
      "tsc --noEmit"
    ],
    "*.{json,md}": [
      "prettier --write"
    ]
  },
  "scripts": {
    "pre-commit": "lint-staged"
  }
}

๐Ÿ• Updated husky hook:

# โšก Only check changed files (much faster!)
npx husky add .husky/pre-commit "npm run pre-commit"

๐Ÿ—๏ธ Complex Workflow Example

For enterprise projects with multiple checks:

// ๐Ÿข Enterprise configuration types
interface HuskyConfig {
  hooks: {
    'pre-commit': string[];
    'pre-push': string[];
    'commit-msg': string[];
  };
  lintStaged: {
    [pattern: string]: string[];
  };
  commitlint: {
    extends: string[];
    rules: Record<string, any>;
  };
}

// ๐Ÿ“‹ Configuration factory
class HuskyConfigFactory {
  static createTypeScriptConfig(): HuskyConfig {
    return {
      hooks: {
        'pre-commit': [
          'lint-staged',
          'npm run test:unit',
          'npm run typecheck'
        ],
        'pre-push': [
          'npm run test:integration',
          'npm run build',
          'npm run security-check'
        ],
        'commit-msg': [
          'commitlint --edit $1'
        ]
      },
      lintStaged: {
        '*.{ts,tsx}': [
          'eslint --fix',
          'prettier --write',
          'tsc --noEmit'
        ],
        '*.{json,md,yml}': [
          'prettier --write'
        ]
      },
      commitlint: {
        extends: ['@commitlint/config-conventional'],
        rules: {
          'type-enum': [2, 'always', [
            'feat', 'fix', 'docs', 'style', 'refactor', 
            'test', 'chore', 'perf', 'ci', 'build'
          ]]
        }
      }
    };
  }
}

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Hooks Not Running

# โŒ Wrong - forgetting to install husky
git commit -m "my commit"
# No checks run! ๐Ÿ˜ฐ

# โœ… Correct - ensure husky is installed
npm run prepare  # This runs 'husky install'
git commit -m "feat: add awesome feature โœจ"
# ๐ŸŽ‰ All checks run!

๐Ÿคฏ Pitfall 2: Slow Pre-commit Hooks

# โŒ Slow - checking entire codebase
npx husky add .husky/pre-commit "npm run lint && npm run test"

# โœ… Fast - only check changed files
npx husky add .husky/pre-commit "lint-staged"

๐Ÿ“‹ lint-staged config:

{
  "lint-staged": {
    "*.ts": ["eslint --fix", "prettier --write"]
  }
}

๐Ÿšซ Pitfall 3: Broken Hooks Stop Development

# ๐Ÿ›ก๏ธ Add escape hatch for emergencies
git commit --no-verify -m "hotfix: critical bug fix"

โš ๏ธ Warning: Only use --no-verify in genuine emergencies!

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Keep Hooks Fast: Use lint-staged for speed
  2. ๐Ÿ“ Document Your Hooks: Explain what each hook does
  3. ๐Ÿ›ก๏ธ Donโ€™t Block Everything: Allow emergency commits
  4. ๐ŸŽจ Make Hooks Helpful: Auto-fix issues when possible
  5. โœจ Test Your Hooks: Ensure they work in different scenarios
  6. ๐Ÿ‘ฅ Team Communication: Explain hooks to new team members

๐ŸŽจ Example Documentation

# ๐Ÿ• Our Husky Setup

## Pre-commit Hooks
- โœ… TypeScript type checking
- ๐ŸŽจ ESLint with auto-fix
- ๐Ÿ“ Prettier code formatting
- ๐Ÿงช Unit tests for changed files

## Pre-push Hooks  
- ๐Ÿ—๏ธ Full build test
- ๐Ÿ”’ Security vulnerability scan
- ๐Ÿ“Š Bundle size check

## Emergency Override
Use `git commit --no-verify` only for critical hotfixes! ๐Ÿšจ

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Set Up a Complete Husky Workflow

Create a robust development workflow for a TypeScript project:

๐Ÿ“‹ Requirements:

  • โœ… Pre-commit: Lint, format, and type-check changed files
  • ๐Ÿงช Pre-commit: Run relevant tests
  • ๐Ÿ—๏ธ Pre-push: Ensure the project builds
  • ๐Ÿ“ Commit message: Enforce conventional commits
  • ๐Ÿš€ Only process changed files for speed

๐ŸŽฎ Bonus Points:

  • Add a commit message emoji guide
  • Create a security check hook
  • Set up automatic dependency updates validation
  • Add a hook to check bundle size

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
# ๐Ÿš€ Complete setup script!

# 1. Install dependencies
npm install --save-dev husky lint-staged @commitlint/cli @commitlint/config-conventional

# 2. Initialize husky
npx husky install
npm pkg set scripts.prepare="husky install"

# 3. Create pre-commit hook
npx husky add .husky/pre-commit "lint-staged"

# 4. Create pre-push hook
npx husky add .husky/pre-push "npm run build && npm run test:integration"

# 5. Create commit message hook
npx husky add .husky/commit-msg "npx --no -- commitlint --edit \$1"

package.json configuration:

{
  "scripts": {
    "prepare": "husky install",
    "typecheck": "tsc --noEmit",
    "lint": "eslint src/**/*.{ts,tsx}",
    "lint:fix": "eslint src/**/*.{ts,tsx} --fix",
    "format": "prettier --write src/**/*.{ts,tsx,json,md}",
    "test": "jest",
    "test:integration": "jest --config=jest.integration.config.js",
    "build": "tsc && vite build",
    "bundle-size": "bundlesize"
  },
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix",
      "prettier --write",
      "tsc --noEmit"
    ],
    "*.{json,md}": [
      "prettier --write"
    ]
  },
  "commitlint": {
    "extends": ["@commitlint/config-conventional"],
    "rules": {
      "type-enum": [2, "always", [
        "feat", "fix", "docs", "style", "refactor",
        "test", "chore", "perf", "ci", "build"
      ]],
      "subject-case": [2, "always", "sentence-case"]
    }
  },
  "bundlesize": [
    {
      "path": "./dist/bundle.js",
      "maxSize": "100kb"
    }
  ]
}

๐Ÿ”’ Security check script (.husky/pre-push):

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

echo "๐Ÿ—๏ธ Building project..."
npm run build

echo "๐Ÿงช Running integration tests..."
npm run test:integration

echo "๐Ÿ”’ Checking for security vulnerabilities..."
npm audit --audit-level high

echo "๐Ÿ“ฆ Checking bundle size..."
npm run bundle-size

echo "โœ… All pre-push checks passed! ๐ŸŽ‰"

๐Ÿ“ Commit message guide (docs/COMMIT_GUIDE.md):

# ๐Ÿ“ Commit Message Guide

Use this format: `type(scope): description`

## Types
- feat โœจ: New feature
- fix ๐Ÿ›: Bug fix  
- docs ๐Ÿ“š: Documentation
- style ๐ŸŽจ: Code style changes
- refactor โ™ป๏ธ: Code refactoring
- test ๐Ÿงช: Adding tests
- chore ๐Ÿ”ง: Maintenance tasks

## Examples
- `feat(auth): add user login ๐Ÿ”`
- `fix(cart): resolve checkout bug ๐Ÿ›’`
- `docs(readme): update installation guide ๐Ÿ“–`

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much about Husky and Git hooks! Hereโ€™s what you can now do:

  • โœ… Set up Husky in any TypeScript project ๐Ÿ’ช
  • โœ… Create pre-commit workflows that catch issues early ๐Ÿ›ก๏ธ
  • โœ… Optimize hook performance with lint-staged ๐Ÿš€
  • โœ… Enforce code quality across your entire team ๐ŸŽฏ
  • โœ… Debug common hook issues like a pro ๐Ÿ›
  • โœ… Build bulletproof development workflows โš”๏ธ

Remember: Husky is your development assistant, not your enemy! Itโ€™s here to help you ship better code faster. ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered Git hooks with Husky!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Set up Husky in your current TypeScript project
  2. ๐Ÿ—๏ธ Create a comprehensive pre-commit workflow
  3. ๐Ÿ“š Move on to our next tutorial: ESLint with TypeScript
  4. ๐ŸŒŸ Share your Husky setup with your team!

Remember: Every smooth deployment started with good development practices. Keep automating, keep improving, and most importantly, keep coding! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿ•โœจ