A version-locking wrapper for Homebrew that maintains a brew.lock file containing exact versions of all installed packages, enabling reproducible installations across machines.
- Transparent wrapper: Alias
brewlocktobrewand use Homebrew normally - Automatic version tracking: Every install/uninstall/upgrade updates
brew.lock - Version-locked bundle install: Install packages with version verification
- Full package support: Tracks formulae, casks, taps (with commit SHAs), and Mac App Store apps
- JSONC format: Human-readable lock file with comment support
- Custom tap URLs: Tracks custom tap repository URLs for reproducibility
brew tap shipworthyai/tap
brew install brewlockNote: The Homebrew formula provides Apple Silicon macOS and x86_64 Linux binaries.
If you want to build from source or contribute to development:
# Clone the repository
git clone https://github.com/ShipWorthyAI/brewlock.git
cd brewlock
# Install dependencies
bun install
# Build the binary
bun run build
# Or link for development
bun linkAdd this alias to your shell configuration to use brewlock as a transparent wrapper for Homebrew:
# For zsh (add to ~/.zshrc):alias brew='brewlock'# For bash (add to ~/.bashrc):alias brew='brewlock'# For fish (add to ~/.config/fish/config.fish):alias brew 'brewlock'After adding the alias, restart your shell or run source ~/.zshrc (or equivalent).
# Install brewlock
brew tap shipworthyai/tap
brew install brewlock
# Set up the alias (add to your ~/.zshrc)echo"alias brew='brewlock'">>~/.zshrc
source~/.zshrc
# Generate a lock file from your current packages
brew lock
# Use brew normally - brewlock tracks versions automatically
brew install git
brew install --cask dockerAll standard brew commands work transparently:
brew install git # Installs git and updates brew.lock
brew install --cask docker # Installs Docker and updates brew.lock
brew upgrade # Upgrades all and regenerates brew.lock
brew uninstall node # Removes node and updates brew.lockbrew lock # Generate brew.lock from currently installed packages
brew check # Verify installed versions match brew.lock
brew help# Show help message (or brewlock help for brewlock-specific help)brew bundle install # Installs from brew.lock with version checking# Option 1: Point BREWLOCK directly to your dotfiles (recommended)# Add to your shell config (~/.zshrc or ~/.bashrc):export BREWLOCK="$HOME/dotfiles/brew.lock"# Option 2: Copy (or symlink) the lock file to your dotfiles
cp ~/brew.lock ~/dotfiles/
cd~/dotfiles && git add brew.lock && git commit -m "Update brew.lock"# On a new machine: restore from brew.lock
cp ~/dotfiles/brew.lock ~/brew.lock
brew bundle installBy default, the lock file is stored at ~/brew.lock (in your home directory). You can customize this location using the BREWLOCK environment variable:
# Set a custom lock file pathexport BREWLOCK=/path/to/your/brew.lock
# Example: Store in your dotfiles repoexport BREWLOCK="$HOME/dotfiles/brew.lock"Performance tracing is enabled by default and writes newline-delimited JSON
spans to ~/.brewlock/performance.ndjson. This is useful when comparing
updateLockFile runs before and after optimization work:
# Disable tracing for one command
BREWLOCK_PERF=0 brew install git
# Choose a log path for a measurement run
BREWLOCK_PERF_LOG=/tmp/brewlock-perf.ndjson brew upgradeEach span includes timing, parent span IDs, command metadata, exit codes, and lock-file entry counts where applicable.
Direct-git tap metadata is the default. To compare against Homebrew's
brew tap-info --json path, override the backend:
BREWLOCK_TAP_INFO_BACKEND=brew brew lockLeave BREWLOCK_TAP_INFO_BACKEND unset to use the default direct-git backend.
Local formula metadata and version lookups are the default. To compare against
Homebrew's brew info --json=v2 --installed and
brew info --json=v2 <formula> paths, override the backend:
BREWLOCK_FORMULA_INFO_BACKEND=brew brew lockLeave BREWLOCK_FORMULA_INFO_BACKEND unset to use the default local backend.
Local cask metadata and version lookups are the default. To compare against
Homebrew's brew info --cask --json=v2 --installed and
brew info --cask --json=v2 <cask> paths, override the backend:
BREWLOCK_CASK_INFO_BACKEND=brew brew lockLeave BREWLOCK_CASK_INFO_BACKEND unset to use the default local backend.
Generated lock files include a warning header followed by a JSONC body organized by package type:
| Field | Description |
|---|---|
url | Custom GitHub repo URL (for non-official taps) |
commit | Git commit SHA for reproducibility |
official | Whether this is an official Homebrew tap |
| Field | Description |
|---|---|
version | Linked (active) version |
installed | All installed versions in Cellar (if multiple) |
revision | Formula revision (e.g., distinguishes 2.43.0 from 2.43.0_1) |
tap | Source tap (e.g., homebrew/core) |
pinned | Whether the formula is pinned |
dependencies | Direct dependencies |
sha256 | Platform-specific bottle SHA256 for verification |
installed_as_dependency | Whether installed as a dependency of another package |
installed_on_request | Whether user explicitly installed it |
| Field | Description |
|---|---|
version | Installed cask version |
tap | Source tap (e.g., homebrew/cask) |
sha256 | Download SHA256 for verification |
auto_updates | Whether the app self-updates |
| Field | Description |
|---|---|
id | App Store ID (required for installation) |
version | Installed app version |
- Command Interception: brewlock parses brew commands to detect package modifications
- Passthrough Execution: The actual brew command is executed with live output streaming
- Lock File Update: After successful commands, brewlock queries installed versions and updates
brew.lock - Bundle Install: When running
brew bundle install, brewlock checks versions and warns about mismatches
Homebrew doesn't natively support exact version installation for all packages. brewlock handles this pragmatically:
- Versioned formulae: Uses
brew install package@versionsyntax when available (e.g.,python@3.11) - Current version match: Skips installation if the installed version matches the lock file
- Version mismatch: Warns when the installed version differs from the lock file
- Casks: Tracks versions but casks are typically pinned by their formula
# Install dependencies
bun install
# Run tests
bun test# Lint
bun run lint
# Type check
bun run typecheck
# Build binary
bun run build
# Run the CLI directly (without building)
bun run src/index.ts --help
# Build release tarball
bun run build:releasebrewlock/
src/
index.ts # CLI entry point
parser.ts # Command parser
executor.ts # Brew command executor
lock-manager.ts # Lock file read/write (JSONC format)
version-resolver.ts # Version querying
bundle-install.ts # Bundle install with versions
types.ts # Shared type definitions
tests/
mocks/ # Test mocking utilities
*.test.ts # Test files
package.json
tsconfig.json
This project is open source and available under the MIT License.
Created by ShipWorthy ⛵️
