Module 11: Git Hooks and Automation

08. Mini-Project: Complete Automation Setup

Project description

In this mini-project you will create a production-ready automation system with Husky, lint-staged, Commitizen, custom hooks, and comprehensive quality gates. You will implement linting, testing, secret detection, conventional commits, and branch protection. This is the setup professional teams use.

This project is your template for any future project.


Project objectives

By completing this project, you will have:

Configured Husky for shared hooks
Integrated lint-staged for fast linting
Implemented Commitizen for conventional commits
Created custom hooks (secrets, tickets, branch naming)
Configured commitlint for validation
Added pre-commit checks (lint, format, secrets)
Added pre-push checks (tests, branch protection)
Documented the setup for the team
Tested the whole system end-to-end

Estimated time: 60-90 minutes


Initial setup (10 min)

Step 1: Create project

mkdir automation-project
cd automation-project
npm init -y
git init

Step 2: Install dependencies

npm install --save-dev \
  husky \
  lint-staged \
  commitizen \
  cz-conventional-changelog \
  @commitlint/cli \
  @commitlint/config-conventional \
  eslint \
  prettier

Step 3: Initialize tools

# Husky
npx husky install
npm set-script prepare "husky install"

# Commitizen
npx commitizen init cz-conventional-changelog --save-dev --save-exact

# ESLint
npx eslint --init
# Choose: problems, esm, none, no, node, JSON

# Prettier
cat > .prettierrc << 'EOF'
{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "es5"
}
EOF

Phase 1: Lint-staged setup (10 min)

Configure lint-staged:

// .lintstagedrc.js
module.exports = {
  '*.js': [
    'eslint --fix',
    'prettier --write'
  ],
  '*.{json,md,yml}': [
    'prettier --write'
  ]
};

Add pre-commit hook:

npx husky add .husky/pre-commit "npx lint-staged"

Test:

# Create test file
echo "const x=1" > test.js

# Stage and commit
git add test.js
npm run commit

# Should auto-fix formatting

Phase 2: Commitizen + commitlint (10 min)

Configure commitlint:

// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'fix',
        'docs',
        'style',
        'refactor',
        'test',
        'chore',
        'perf',
        'ci',
        'build',
        'revert'
      ]
    ],
    'subject-case': [2, 'always', 'sentence-case'],
    'header-max-length': [2, 'always', 100]
  }
};

Add commit-msg hook:

npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

Test:

# Valid commit
npm run commit
# Follow prompts

# Invalid commit (should fail)
git commit -m "bad commit" --no-verify
git commit -m "update readme"
# Should be blocked by commit-msg hook

Phase 3: Secret detection (10 min)

Create secret detection script:

#!/bin/bash
# scripts/check-secrets.sh

echo "🔍 Scanning for secrets..."

# Patterns
patterns=(
    "api[_-]?key"
    "password"
    "secret"
    "token"
    "private[_-]?key"
    "bearer"
)

# Build regex
regex=$(IFS="|"; echo "${patterns[*]}")

# Check staged files
if git diff --cached --name-only | xargs grep -iE "$regex" 2>/dev/null; then
    echo ""
    echo "❌ Potential secrets detected!"
    echo ""
    echo "Remove secrets before committing."
    echo "Use environment variables instead."
    echo ""
    echo "If false positive: git commit --no-verify"
    exit 1
fi

echo "✅ No secrets detected"
chmod +x scripts/check-secrets.sh

Update pre-commit hook:

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

# 1. Lint staged files
npx lint-staged

# 2. Check for secrets
./scripts/check-secrets.sh

Test:

# Should fail
echo "API_KEY=abc123" > .env
git add .env
git commit -m "test"

# Should pass
echo "# Configuration" > config.txt
git add config.txt
npm run commit

Phase 4: Branch naming (10 min)

Create branch validation script:

#!/bin/bash
# scripts/check-branch-name.sh

branch=$(git symbolic-ref --short HEAD)

# Valid pattern
valid="^(feature|fix|hotfix|release|chore)/[a-z0-9-]+$"

if [[ ! $branch =~ $valid ]]; then
    echo "❌ Invalid branch name: $branch"
    echo ""
    echo "Branch naming convention:"
    echo "  feature/description"
    echo "  fix/description"
    echo "  hotfix/description"
    echo "  release/version"
    echo "  chore/description"
    echo ""
    echo "Examples:"
    echo "  feature/user-authentication"
    echo "  fix/login-validation"
    echo ""
    exit 1
fi

echo "✅ Branch name valid: $branch"
chmod +x scripts/check-branch-name.sh

Add pre-push hook:

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

# Check branch name
./scripts/check-branch-name.sh

Test:

# Should fail
git checkout -b invalid
git push  # Blocked

# Should pass
git checkout -b feature/automation-setup
git push  # Allowed

Phase 5: Ticket validation (10 min)

Create ticket validation:

#!/bin/bash
# scripts/check-ticket.sh

commit_msg_file=$1
commit_msg=$(cat "$commit_msg_file")

# Pattern: [PROJ-123] or #123
if ! echo "$commit_msg" | grep -qE "(\[[A-Z]+-[0-9]+\]|#[0-9]+)"; then
    echo "❌ Commit must include ticket reference"
    echo ""
    echo "Valid formats:"
    echo "  [PROJ-123] Your message"
    echo "  Fixes #123: Your message"
    echo ""
    exit 1
fi

echo "✅ Ticket reference found"
chmod +x scripts/check-ticket.sh

Update commit-msg hook:

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

# 1. Validate conventional commits
npx --no -- commitlint --edit "$1"

# 2. Validate ticket reference
./scripts/check-ticket.sh "$1"

Update Commitizen config:

// .cz-config.js (if using cz-customizable)
module.exports = {
  types: [
    { value: 'feat', name: 'feat:     New feature' },
    { value: 'fix', name: 'fix:      Bug fix' },
    { value: 'docs', name: 'docs:     Documentation' },
    { value: 'style', name: 'style:    Code style' },
    { value: 'refactor', name: 'refactor: Refactor' },
    { value: 'test', name: 'test:     Tests' },
    { value: 'chore', name: 'chore:    Chores' },
  ],
  
  messages: {
    type: 'Select the type of change:',
    scope: 'Scope (optional):',
    customScope: 'Custom scope:',
    subject: 'Short description:',
    body: 'Longer description (optional):',
    breaking: 'Breaking changes (optional):',
    footer: 'Issues (e.g. #123, [PROJ-456]):',
    confirmCommit: 'Confirm commit?',
  },
  
  allowBreakingChanges: ['feat', 'fix'],
};

Phase 6: Testing integration (10 min)

Add test script:

// tests/example.test.js
const sum = (a, b) => a + b;

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

Install Jest:

npm install --save-dev jest

# Add script
npm set-script test "jest"

Update pre-push hook:

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

echo "🚀 Pre-push checks..."

# 1. Branch name
./scripts/check-branch-name.sh

# 2. Run tests
echo "\n🧪 Running tests..."
npm test

echo "\n✅ All pre-push checks passed!"

Phase 7: Documentation (10 min)

Create CONTRIBUTING.md:

# Contributing Guide

## Setup

1. Clone repository
2. Install dependencies: `npm install`
3. Hooks will install automatically via Husky

## Development Workflow

### Committing

Use Commitizen for consistent commits:

```bash
npm run commit

Or:

git cz

This opens an interactive prompt.

Commit Format

Commits must follow Conventional Commits:

type(scope): Short description

Longer description (optional)

Closes #123

Types: feat, fix, docs, style, refactor, test, chore

Ticket: All commits must reference a ticket: [PROJ-123] or #123

Branch Naming

Branches must follow:

feature/description
fix/description
hotfix/description
release/version
chore/description

Examples:

  • feature/user-authentication
  • fix/login-validation

Pre-commit Checks

Automatically run:

  • ESLint (auto-fix)
  • Prettier (auto-format)
  • Secret detection

Pre-push Checks

Automatically run:

  • Branch name validation
  • Full test suite

Bypassing Hooks

In emergencies only:

git commit --no-verify -m "hotfix: Critical fix"

Document why in commit message.

Quality Gates

  • ✅ Code style (ESLint + Prettier)
  • ✅ Conventional commits
  • ✅ Ticket references
  • ✅ Branch naming
  • ✅ No secrets
  • ✅ Tests passing

---

### **Update README.md:**

```markdown
# Automation Project

Production-ready Git automation setup.

## Quick Start

```bash
# Install
npm install

# Commit
npm run commit

# Push
git push

Features

  • Husky - Shared Git hooks
  • lint-staged - Fast linting (staged files only)
  • Commitizen - Interactive conventional commits
  • commitlint - Commit message validation
  • ESLint + Prettier - Code quality
  • Secret detection - Prevent credential leaks
  • Branch naming - Enforce naming conventions
  • Ticket validation - Require ticket references
  • Automated tests - Pre-push testing

Contributing

See CONTRIBUTING.md


---

## Phase 8: Complete testing (10 min)

### **Test checklist:**

```bash
# ✅ 1. Lint auto-fix
echo "const x=1" > src/index.js
git add src/index.js
npm run commit
# Should format code

# ✅ 2. Secret detection
echo "API_KEY=secret" > .env
git add .env
git commit -m "test"
# Should block

# ✅ 3. Conventional commits
git commit -m "bad format"
# Should block

npm run commit
# Should work

# ✅ 4. Ticket validation
# (Will be enforced by commit-msg)

# ✅ 5. Branch naming
git checkout -b invalid-branch
git push
# Should block

git checkout -b feature/test
git push
# Should work

# ✅ 6. Tests
# Write failing test
echo "test('fail', () => expect(1).toBe(2));" > tests/fail.test.js
git add tests/
git commit -m "[PROJ-1] test: Add failing test"
git push
# Should block

Final project structure

automation-project/
├── .husky/
│   ├── _/
│   ├── pre-commit       # lint-staged + secrets
│   ├── commit-msg       # commitlint + ticket
│   └── pre-push         # branch + tests
├── scripts/
│   ├── check-secrets.sh
│   ├── check-branch-name.sh
│   └── check-ticket.sh
├── tests/
│   └── example.test.js
├── src/
│   └── index.js
├── .eslintrc.json
├── .prettierrc
├── .lintstagedrc.js
├── commitlint.config.js
├── package.json
├── CONTRIBUTING.md
└── README.md

Final package.json

{
  "name": "automation-project",
  "scripts": {
    "prepare": "husky install",
    "commit": "cz",
    "lint": "eslint .",
    "format": "prettier --write .",
    "test": "jest"
  },
  "devDependencies": {
    "@commitlint/cli": "^17.0.0",
    "@commitlint/config-conventional": "^17.0.0",
    "commitizen": "^4.2.0",
    "cz-conventional-changelog": "^3.3.0",
    "eslint": "^8.0.0",
    "husky": "^8.0.0",
    "jest": "^29.0.0",
    "lint-staged": "^13.0.0",
    "prettier": "^2.8.0"
  },
  "config": {
    "commitizen": {
      "path": "cz-conventional-changelog"
    }
  }
}

Final validation

Complete checklist:

  • Husky installed and working
  • lint-staged configured
  • Commitizen working (npm run commit)
  • commitlint validating messages
  • Secret detection blocking secrets
  • Branch naming enforced
  • Ticket validation working
  • Tests running pre-push
  • Documentation complete
  • Team can clone and use immediately

Optional extensions

1. Add TypeScript:

npm install --save-dev typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin

# Update .lintstagedrc.js
'*.{js,ts}': ['eslint --fix', 'prettier --write']

2. Add test coverage threshold:

// jest.config.js
module.exports = {
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
};

3. Add Docker check:

#!/bin/bash
# scripts/check-docker.sh

if git diff --cached --name-only | grep -q "Dockerfile"; then
    echo "🐳 Dockerfile changed, validating..."
    docker build -t test . --dry-run
fi

4. Add changelog generation:

npm install --save-dev standard-version

# Add script
npm set-script release "standard-version"

# Generate changelog
npm run release

Success criteria

  • Husky hooks shared via .husky/
  • lint-staged speeds up pre-commit
  • Commitizen enforces format
  • commitlint validates messages
  • Secrets blocked automatically
  • Branch naming enforced
  • Tickets required in commits
  • Tests run before push
  • Documentation complete
  • Team onboarding < 5 minutes

Project summary

You completed:

Complete automation stack
Husky + lint-staged + Commitizen
Custom validation hooks
Secret detection
Branch + ticket validation
Pre-commit + pre-push gates
Production-ready setup
Team documentation

Skills demonstrated:

  • Hook integration mastery
  • Automation setup expertise
  • Team workflow design
  • Quality gate implementation

Next steps

This setup is your template for future projects:

  1. Fork this repo as a template
  2. Customize it for your stack (TS, React, etc.)
  3. Share it with your team
  4. Iterate based on feedback

Module 11 summary

You completed the full Git Hooks and Automation module:

Native Git hooks (pre-commit, commit-msg, pre-push)
Husky for shared hooks
lint-staged for performance
Commitizen for conventional commits
Advanced custom hooks
Bypass strategies and troubleshooting
Complete production setup

Next module: Module 12: GitHub Actions CI/CD


Additional resources

  1. Husky Best Practices - Official guide
  2. lint-staged Examples - Real-world configs
  3. Git Hooks Collection - Community hooks

Total project time: 60-90 minutes

Congratulations! You have completed Module 11 - Git Hooks and Automation.