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 path
export BREWLOCK=/path/to/your/brew.lock
# Example: Store in your dotfiles repo
export 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 β΅οΈ
