Skip to content

Repository files navigation

brewlock

🍺 brewlock 🔒

A version-locking wrapper for Homebrew that maintains a brew.lock file containing exact versions of all installed packages, enabling reproducible installations across machines.

Features

  • Transparent wrapper: Alias brewlock to brew and 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

Installation

Homebrew (recommended)

brew tap shipworthyai/tap
brew install brewlock

Note: The Homebrew formula provides Apple Silicon macOS and x86_64 Linux binaries.

From Source

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 link

Setup

Add 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).

Quick Start

# 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 docker

Usage

Normal Homebrew Commands

All 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.lock

Brewlock-Specific Commands

brew 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)

Bundle Install with Version Verification

brew bundle install # Installs from brew.lock with version checking

Syncing Across Machines

# 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 install

The brew.lock File

By 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

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 upgrade

Each 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 lock

Leave 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 lock

Leave 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 lock

Leave 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:

# This file is generated by running "brew install/upgrade/uninstall".
# Manual changes might be lost - proceed with caution!
{
"version": 1,
"tap": {
"homebrew/core": {
"commit": "a1b2c3d4e5f6...",
"official": true
},
"homebrew/cask": {
"commit": "f6e5d4c3b2a1...",
"official": true
},
"shipworthyai/tap": {
"url": "https://github.com/ShipWorthyAI/homebrew-tap.git",
"commit": "abc123def456..."
}
},
"brew": {
"git": {
"version": "2.52.0",
"installed": ["2.52.0", "2.51.0"],
"revision": 1,
"tap": "homebrew/core",
"pinned": false,
"dependencies": ["gettext", "pcre2"],
"sha256": "c19806bab...",
"installed_as_dependency": false,
"installed_on_request": true
},
"node": {
"version": "21.5.0",
"tap": "homebrew/core",
"installed_on_request": true
}
},
"cask": {
"docker": {
"version": "4.26.1,123456",
"tap": "homebrew/cask",
"sha256": "0a55468...",
"auto_updates": true
},
"visual-studio-code": {
"version": "1.85.1",
"tap": "homebrew/cask",
"auto_updates": true
}
},
"mas": {
"Xcode": {
"id": 497799835,
"version": "15.2"
}
}
}

Lock File Fields

Tap Fields

FieldDescription
urlCustom GitHub repo URL (for non-official taps)
commitGit commit SHA for reproducibility
officialWhether this is an official Homebrew tap

Brew (Formula) Fields

FieldDescription
versionLinked (active) version
installedAll installed versions in Cellar (if multiple)
revisionFormula revision (e.g., distinguishes 2.43.0 from 2.43.0_1)
tapSource tap (e.g., homebrew/core)
pinnedWhether the formula is pinned
dependenciesDirect dependencies
sha256Platform-specific bottle SHA256 for verification
installed_as_dependencyWhether installed as a dependency of another package
installed_on_requestWhether user explicitly installed it

Cask Fields

FieldDescription
versionInstalled cask version
tapSource tap (e.g., homebrew/cask)
sha256Download SHA256 for verification
auto_updatesWhether the app self-updates

Mac App Store (mas) Fields

FieldDescription
idApp Store ID (required for installation)
versionInstalled app version

How It Works

  1. Command Interception: brewlock parses brew commands to detect package modifications
  2. Passthrough Execution: The actual brew command is executed with live output streaming
  3. Lock File Update: After successful commands, brewlock queries installed versions and updates brew.lock
  4. Bundle Install: When running brew bundle install, brewlock checks versions and warns about mismatches

Version Installation Strategy

Homebrew doesn't natively support exact version installation for all packages. brewlock handles this pragmatically:

  • Versioned formulae: Uses brew install package@version syntax 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

Development

# 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:release

Project Structure

brewlock/
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

License

This project is open source and available under the MIT License.


Created by ShipWorthy ⛵️

About

🍺 The missing lock file for Homebrew 🔒

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages