Skip to content

🪝 Think of `simple-git-hooks` but optimized for all-sized Bun projects.

License

Notifications You must be signed in to change notification settings

stacksjs/bun-git-hooks

Repository files navigation

Social Card of this repo

npm version GitHub Actions Commitizen friendly

bun-git-hooks

A Bun-optimized TypeScript library for managing Git hooks with a robust set of configuration options.

Features

  • 🎯 Simple Configuration Easy setup through multiple config file formats
  • 🔄 Automatic Installation Hooks are installed on package installation
  • 🛡️ Type Safe Written in TypeScript with comprehensive type definitions
  • 🔧 Flexible Config Supports .ts, .js, .mjs, .json configurations
  • 💪 Robust Handles complex Git workspace configurations
  • 🚫 Skip Option Environment variable to skip hook installation
  • 🧹 Cleanup Optional cleanup of unused hooks
  • 📦 Zero Dependencies Minimal footprint
  • Fast Built for Bun with performance in mind
  • 🔍 Verbose Mode Detailed logging for troubleshooting
  • 🔀 Staged Lint Run commands only on staged files that match specific patterns

Installation

bun add -D bun-git-hooks

Usage

Basic Configuration

Create a git-hooks.config.{ts,js,mjs,cjs,mts,cts,json} (git-hooks.ts works too) file in your project root:

// git-hooks.config.ts
import type { GitHooksConfig } from 'bun-git-hooks'

const config: GitHooksConfig = {
  'pre-commit': 'bun run lint && bun run test',
  'commit-msg': 'bun commitlint --edit $1',
  'pre-push': 'bun run build',
  'verbose': true,
}

export default config

JSON Configuration

{
  "git-hooks": {
    "pre-commit": "bun run lint && bun run test",
    "commit-msg": "bun commitlint --edit $1",
    "pre-push": "bun run build"
  }
}

CLI Usage

# Install hooks from config
git-hooks

# alternatively, trigger the CLI with bunx
bunx git-hooks
bunx bun-git-hooks

# Use specific config file
git-hooks ./custom-config.ts

# Remove all hooks
git-hooks uninstall

# Enable verbose logging
git-hooks --verbose

# Run staged lint for a specific hook manually
git-hooks run-staged-lint pre-commit

Environment Variables

Skip hook installation when needed:

# Skip hook installation
SKIP_INSTALL_GIT_HOOKS=1 bun install

# Skip hook execution
SKIP_BUN_GIT_HOOKS=1 git commit -m "skipping hooks"

# Set custom environment for hooks
BUN_GIT_HOOKS_RC=/path/to/env git-hooks

Advanced Configuration

export default {
  // Hook commands
  'pre-commit': 'bun run lint && bun run test',
  'commit-msg': 'bun commitlint --edit $1',

  // Preserve specific unused hooks
  'preserveUnused': ['post-merge', 'post-checkout'],

  // Configure multiple hooks
  'pre-push': [
    'bun run build',
    'bun run test:e2e'
  ].join(' && ')
}

Staged Lint (Lint Only Changed Files)

You can run linters and formatters only on staged files that match specific patterns, similar to lint-staged. This is particularly useful in pre-commit hooks to ensure quality checks run only on the files being committed.

Configuration

Add a stagedLint property to your hook configuration:

// git-hooks.config.ts
export default {
  'pre-commit': {
    stagedLint: {
      '*.js': 'eslint --fix',
      '*.{ts,tsx}': ['eslint --fix', 'prettier --write'],
      '*.css': 'stylelint --fix',
      '*.md': 'prettier --write'
    }
  },
  'verbose': true
}

Manual CLI Usage

You can also run the staged lint manually using the CLI:

# Run staged lint for pre-commit
git-hooks run-staged-lint pre-commit

# Run with verbose output
git-hooks run-staged-lint pre-commit --verbose

Pattern Matching

For each file pattern, you can specify either a single command or an array of commands that will run in sequence. The commands will only receive the staged files that match the pattern.

For example:

// git-hooks.config.ts
export default {
  '*.{js,jsx}': 'eslint --fix', // Run eslint only on JavaScript files
  '*.{ts,tsx}': ['eslint --fix', 'prettier --write'], // Run eslint and then prettier on TypeScript files
  '*.css': 'stylelint --fix', // Run stylelint only on CSS files
  '*.md': 'prettier --write' // Run prettier only on Markdown files
}

The output will show which files are being processed and which tasks are being run:

$ git commit

❯ Running tasks for staged files...
  ❯ *.js — 2 files
    ⠼ eslint --fix
  ❯ *.{ts,tsx} — 3 files
    ⠹ eslint --fix
    ⠹ prettier --write
  ❯ *.css — 1 file
    ⠼ stylelint --fix
  ❯ *.md — no files [SKIPPED]

Error Handling

The library provides clear error messages:

try {
  await setHooksFromConfig()
}
catch (err) {
  if (err.message.includes('Config was not found')) {
    console.error('Missing configuration file')
  }
  else if (err.message.includes('git root')) {
    console.error('Not a Git repository')
  }
}

TypeScript Support

Full TypeScript support with detailed type definitions:

interface GitHooksConfig {
  'pre-commit'?: string
  'pre-push'?: string
  'commit-msg'?: string
  'post-merge'?: string
  // ... other git hooks
  'preserveUnused'?: Array<string> | boolean
}

// Types are automatically inferred
const config: GitHooksConfig = {
  'pre-commit': 'bun run test',
  'preserveUnused': ['post-checkout']
}

Testing

bun test

Changelog

Please see our releases page for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Community

For help, discussion about best practices, or any other conversation that would benefit from being searchable:

Discussions on GitHub

For casual chit-chat with others using this package:

Join the Stacks Discord Server

Postcardware

“Software that is free, but hopes for a postcard.” We love receiving postcards from around the world showing where bun-git-hooks is being used! We showcase them on our website too.

Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎

Credits

Many thanks to simple-git-hooks and its contributors for inspiring this project.

Sponsors

We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.

License

The MIT License (MIT). Please see LICENSE for more information.

Made with 💙