🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

git-permachine

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

git-permachine - npm Package Compare versions

Comparing version
0.1.3
to
0.1.4
+3
-1
package.json
{
"name": "git-permachine",
"version": "0.1.3",
"version": "0.1.4",
"description": "Automatically merge machine-specific config files with base configs using git hooks",

@@ -48,5 +48,7 @@ "type": "module",

"@types/bun": "latest",
"@types/node": "^25.0.6",
"bun-types": "latest"
},
"dependencies": {
"chokidar": "^5.0.0",
"glob": "^13.0.0",

@@ -53,0 +55,0 @@ "minimist": "^1.2.8",

+287
-273
# permachine
Automatically merge machine-specific configuration files with base configurations in git repositories. Like Husky for git hooks, but for config file management.
Per-machine config management with git for tools that don't support it natively. Automatically merge machine-specific configurations with a base config.
[![npm version](https://img.shields.io/npm/v/git-permachine.svg)](https://www.npmjs.com/package/git-permachine)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
## Problem
When working across multiple machines, you often need:
When syncing dotfiles across multiple machines, you often need:
- **Shared configuration** - Settings that work across all machines
- **Machine-specific overrides** - Local paths, API keys, ports, etc.
- **Automatic merging** - No manual copy-paste or merge steps
- **Git-friendly** - Base and machine configs in version control, output gitignored

@@ -16,4 +19,5 @@ ## Solution

`permachine` automatically:
1. Detects your machine name
2. Finds machine-specific config files (e.g., `config.homezone.json`)
2. Finds machine-specific config files (e.g. `config.my-laptop.json`, `config.workstation.json`)
3. Merges them with base configs (e.g., `config.base.json`)

@@ -27,2 +31,5 @@ 4. Outputs the final config (e.g., `config.json`)

```bash
# Install globally
npm install -g git-permachine
# In your repository

@@ -32,27 +39,39 @@ cd /path/to/your/repo

# Initialize (one-time setup)
npx permachine init
permachine init
# That's it! Your configs will now auto-merge on git operations
# That's it! Your configs will now auto-merge on git operations when a file ends with `.<machine-name>.<ext>`
```
## Installation
## CLI Reference
### Development (Local)
```
permachine - Automatically merge machine-specific config files
```bash
cd D:/projects/git-permachine
bun install
bun link
USAGE:
permachine <command> [options]
# In your target repo
cd /path/to/your/repo
bun link git-permachine
```
COMMANDS:
init Initialize permachine in current repository
merge Manually trigger merge operation
info Show information about current setup
uninstall Uninstall git hooks
watch Watch for file changes and auto-merge
### Production
OPTIONS:
--help, -h Show this help message
--version, -v Show version number
--silent, -s Suppress all output except errors (for merge command)
--legacy Use legacy .git/hooks wrapping (for init command)
--auto Auto-detect best installation method (for init command)
--no-gitignore Don't manage .gitignore or git tracking (for init/merge commands)
--debounce <ms> Debounce delay in milliseconds (for watch command, default: 300)
--verbose Show detailed file change events (for watch command)
```bash
npm install -g git-permachine
# or
bun add -g git-permachine
EXAMPLES:
permachine init
permachine merge --silent
permachine info
permachine uninstall
permachine watch
permachine watch --debounce 500 --verbose
```

@@ -64,45 +83,39 @@

Given machine name `homezone` (auto-detected):
Given machine name `my-laptop` (auto-detected from hostname):
| Purpose | Filename | In Git? |
|---------|----------|---------|
| Base config (shared) | `config.base.json` | ✅ Yes |
| Machine-specific | `config.homezone.json` | ✅ Yes |
| Final output (merged) | `config.json` | ❌ No (gitignored) |
| Purpose | Filename | In Git? |
| --------------------- | ----------------------- | ------------------ |
| Base config (shared) | `config.base.json` | ✅ Yes |
| Machine-specific | `config.my-laptop.json` | ✅ Yes |
| Final output (merged) | `config.json` | ❌ No (gitignored) |
Same pattern works for `.env` files:
| Purpose | Filename | In Git? |
|---------|----------|---------|
| Base config | `.env.base` | ✅ Yes |
| Machine-specific | `.env.homezone` | ✅ Yes |
| Final output | `.env` | ❌ No (gitignored) |
| Purpose | Filename | In Git? |
| ---------------- | ---------------- | ------------------ |
| Base config | `.env.base` | ✅ Yes |
| Machine-specific | `.env.my-laptop` | ✅ Yes |
| Final output | `.env` | ❌ No (gitignored) |
### Commands
### Basic Commands
#### `init` - Initialize in repository
#### Initialize in Repository
```bash
permachine init [options]
Options:
--legacy Use .git/hooks wrapping instead of core.hooksPath
--auto Auto-detect best installation method
--no-gitignore Don't manage .gitignore or git tracking
permachine init
```
**What it does:**
1. Detects machine name (e.g., `homezone`)
2. Installs git hooks (post-checkout, post-merge, post-commit)
3. Scans for existing `*.{machine}.*` files
4. Performs initial merge
5. **Automatically manages .gitignore:**
- Adds output files (e.g., `config.json`, `.env`) to `.gitignore`
- Removes already-tracked output files from git index (`git rm --cached`)
- Creates `.gitignore` if it doesn't exist
- Appends to existing `.gitignore` without duplicates
- Detects your machine name (e.g., `laptop`, `desktop`, `workstation`)
- Installs git hooks for automatic merging
- Scans for existing machine-specific files
- **Prompts for confirmation** if existing files will be overwritten
- Performs initial merge
- Adds output files to `.gitignore` and removes them from git tracking
**Example output:**
```
✓ Machine detected: homezone
✓ Machine detected: laptop
✓ Git hooks installed via core.hooksPath

@@ -119,25 +132,50 @@ ✓ Merged 2 file(s)

#### `merge` - Manually trigger merge
#### Manual Merge
```bash
permachine merge [options]
permachine merge
```
Options:
--silent Suppress all output except errors
--no-gitignore Don't manage .gitignore or git tracking
**Prompts for confirmation** if existing files will be overwritten. Useful for testing or running without git hooks.
#### Watch Mode
```bash
permachine watch
```
**What it does:**
- Scans for machine-specific files
- Merges with base configs
- Writes output files
- **Automatically updates .gitignore** (unless `--no-gitignore` is used)
Useful for:
- Testing merge logic
- Running manually without git hooks
- CI/CD pipelines
- Watches all base and machine-specific files for changes
- Automatically merges when you save any watched file
- Debounces rapid changes to prevent redundant merges
- Runs continuously until you stop it (Ctrl+C)
#### `info` - Show current setup
**Perfect for development** - just leave it running and your configs stay up-to-date as you type.
**Example output:**
```
✓ Machine detected: laptop
✓ Watching 4 file(s) for changes...
- config.base.json
- config.laptop.json
- .env.base
- .env.laptop
[12:34:56] Changed: config.laptop.json
[12:34:56] Merged config.base.json + config.laptop.json → config.json
✓ Ready
^C
✓ Stopped watching
```
**Options:**
- `--debounce <ms>` - Adjust debounce delay (default: 300ms)
- `--verbose` - Show detailed file change events
#### Check Setup
```bash

@@ -148,4 +186,5 @@ permachine info

**Example output:**
```
Machine name: homezone
Machine name: laptop
Repository: /path/to/repo

@@ -155,198 +194,166 @@ Hooks method: core.hooksPath

Tracked patterns: 2
- config.base.json + config.homezone.json → config.json
- .env.base + .env.homezone → .env
```
- config.base.json + config.laptop.json → config.json
- .env.base + .env.laptop → .env
#### `uninstall` - Remove git hooks
```bash
permachine uninstall
Output files: 2 total, 1 existing
Existing output files:
- config.json
```
Removes git hooks and restores original hooks (if using legacy mode).
## Cookbook / Recipes
## How It Works
### Recipe 1: VSCode Settings Per Machine
### 1. Machine Detection
Different settings for work laptop vs home desktop:
Automatically detects machine name across platforms:
- **Windows**: `COMPUTERNAME` environment variable
- **Linux/Mac**: `hostname()`
- Normalized to lowercase for consistency
```bash
# On work laptop (machine: "worklaptop")
.vscode/
├── settings.base.json # Shared: theme, font size
├── settings.worklaptop.json # Work paths, proxy settings
└── settings.json # ← Merged output (gitignored)
### 2. File Discovery
# On home desktop (machine: "desktop")
.vscode/
├── settings.base.json # Shared: theme, font size
├── settings.desktop.json # Home paths, no proxy
└── settings.json # ← Merged output (gitignored)
```
Scans repository for files matching `*.{machine}.*` pattern:
- `config.homezone.json` ✅
- `.env.homezone` ✅
- `settings.homezone.json` ✅
- Ignores `node_modules/`, `.git/`, `dist/`
**setup.base.json:**
### 3. Merging Strategy
#### JSON Files
- **Deep merge**: Machine config recursively overrides base
- **Arrays**: Replaced entirely (not merged by index)
- **Output**: 2-space indentation, ends with newline
Example:
```json
// config.base.json
{
"server": { "host": "localhost", "port": 3000 },
"logging": { "level": "info" }
"editor.fontSize": 14,
"workbench.colorTheme": "Dark+"
}
```
// config.homezone.json
{
"server": { "port": 8080 },
"database": { "password": "secret" }
}
**settings.worklaptop.json:**
// config.json (merged output)
```json
{
"server": { "host": "localhost", "port": 8080 },
"logging": { "level": "info" },
"database": { "password": "secret" }
"http.proxy": "http://proxy.company.com:8080",
"terminal.integrated.cwd": "C:/Projects"
}
```
#### ENV Files
- **Simple key-value merge**: Machine values override base
- **Preserves comments**: From base file
- **Quoted values**: Auto-quotes values with spaces or special chars
### Recipe 2: Environment Variables
Example:
Different database credentials per environment:
```bash
# .env.base
DATABASE_HOST=localhost
DATABASE_PORT=5432
API_KEY=default
# .env.base (shared defaults)
NODE_ENV=development
LOG_LEVEL=info
API_PORT=3000
# .env.homezone
DATABASE_PORT=3306
API_KEY=secret_key_123
# .env.laptop (local dev)
DATABASE_URL=postgresql://localhost:5432/myapp_dev
API_KEY=dev_key_123
# .env (merged output)
DATABASE_HOST=localhost
DATABASE_PORT=3306
API_KEY=secret_key_123
# .env.prodserver (production)
DATABASE_URL=postgresql://prod.db.com:5432/myapp
API_KEY=prod_key_xyz
# .env ← Merged output (gitignored)
```
### 4. Gitignore Management
### Recipe 3: Package.json Scripts
**Automatic on `init` and `merge`:**
- Adds output files to `.gitignore` (e.g., `config.json`, `.env`)
- Removes already-tracked files from git with `git rm --cached`
- Creates `.gitignore` if missing
- Appends to existing `.gitignore` without duplicates
- Preserves comments and formatting
- Normalizes paths (Windows `\` → `/`)
Different build scripts for different machines:
**Edge cases handled:**
- Files with spaces in names
- Nested directories (`config/app.json`)
- Mixed tracking states (some tracked, some not)
- Idempotent - safe to run multiple times
**Disable with `--no-gitignore`:**
```bash
permachine init --no-gitignore
permachine merge --no-gitignore
```
# package.base.json
{
"name": "my-app",
"version": "1.0.0",
"scripts": {
"test": "jest"
},
"dependencies": {
"express": "^4.18.0"
}
}
### 5. Git Hooks
# package.laptop.json (local development)
{
"scripts": {
"dev": "nodemon src/index.js",
"build": "webpack --mode development"
}
}
Two installation methods:
# package.buildserver.json (CI/CD)
{
"scripts": {
"build": "webpack --mode production",
"deploy": "aws s3 sync dist/ s3://my-bucket"
}
}
#### Preferred: `core.hooksPath`
```bash
git config core.hooksPath .permachine/hooks
# package.json ← Merged output
# Each machine gets appropriate scripts!
```
- Clean, modern approach
- No modification of `.git/hooks`
- Easy to uninstall
#### Legacy: `.git/hooks` wrapping
- Backs up existing hooks to `.git/hooks/*.pre-mcs`
- Wraps existing hooks (calls them after merge)
- Compatible with other git hook tools
### Recipe 4: Database Configuration
### 6. Automation
Multi-environment database setup:
Hooks run on:
- **post-checkout**: After switching branches
- **post-merge**: After `git pull` or `git merge`
- **post-commit**: After committing
Merge happens silently in background (only logs errors).
## Examples
### Example 1: OpenCode Configuration
```bash
cd C:\Users\josch\.config\opencode
# config/database.base.json
{
"pool": {
"min": 2,
"max": 10
},
"migrations": {
"directory": "./migrations"
}
}
# Initialize
permachine init
# config/database.laptop.json
{
"connection": {
"host": "localhost",
"port": 5432,
"database": "myapp_dev",
"user": "dev",
"password": "dev123"
}
}
# Machine detected: homezone
# Reorganize existing config
mv config.json config.homezone.json
# Create base config with shared settings
cat > config.base.json << EOF
# config/database.prodserver.json
{
"theme": "nightowl-transparent",
"autoupdate": true
"connection": {
"host": "db.production.com",
"port": 5432,
"database": "myapp_production",
"user": "produser",
"password": "secure_password_from_vault"
},
"pool": {
"min": 10,
"max": 50
}
}
EOF
# Add machine-specific settings to config.homezone.json
# ...edit file...
# Merge
permachine merge
# Output: config.json (gitignored)
# Future git operations auto-merge!
```
### Example 2: Multi-Environment Project
### Recipe 5: Multi-File Projects
```bash
# Different machines, different settings
# Machine: "workstation"
config.workstation.json → Development settings, localhost
.env.workstation → Local database credentials
Complex projects with multiple config files:
# Machine: "server"
config.server.json → Production settings, real domains
.env.server → Production database credentials
# Shared base
config.base.json → Common app settings
.env.base → Default environment variables
# Each machine gets its own merged config automatically!
```
### Example 3: Multiple Config Files
```bash
# Project structure
project/
├── config.base.json
├── config.homezone.json
├── config.laptop.json
├── settings/
│ ├── app.base.json
│ ├── app.homezone.json
│ ├── app.laptop.json
│ ├── database.base.json
│ └── database.homezone.json
└── .env.base
.env.homezone
│ └── database.laptop.json
├── .env.base
└── .env.laptop
# All files auto-merge on git operations:
# After `permachine init`, all files auto-merge:
# - config.json

@@ -358,63 +365,31 @@ # - settings/app.json

## Supported File Types
## How It Works
- ✅ **JSON** (`.json`)
- ✅ **ENV** (`.env`, `.env.*`)
- 🔜 **YAML** (future)
- 🔜 **TOML** (future)
`permachine` uses a simple three-step process:
## Error Handling
1. **Machine Detection** - Automatically detects your machine name from hostname (Windows: `COMPUTERNAME`, Linux/Mac: `hostname()`)
### Base missing, machine exists
→ Uses machine file only
2. **File Discovery** - Scans your repository for files matching the pattern `*.{machine}.*` (e.g., `config.laptop.json`, `.env.desktop`)
### Machine missing, base exists
→ Uses base file only (rare, scanner looks for machine files)
3. **Smart Merging** - Merges base and machine-specific configs:
### Both missing
→ Skips silently
- **JSON**: Deep recursive merge (machine values override base)
- **ENV**: Key-value merge with comment preservation
### Parse error
→ Logs error with file path, skips merge
4. **Gitignore Management** - Automatically adds output files to `.gitignore` and removes already-tracked files from git
### Write error
→ Logs error, doesn't crash
5. **Git Hooks** - Installs hooks to auto-merge on checkout, merge, and commit operations
## Development
For detailed implementation information, see [CONTRIBUTING.md](CONTRIBUTING.md).
### Setup
## Supported File Types
```bash
git clone <repo>
cd permachine
bun install
```
| Type | Extensions | Merge Strategy | Status |
| ----- | --------------------- | --------------------------------- | ------------ |
| JSON | `.json` | Deep recursive merge | ✅ Supported |
| JSONC | `.json` with comments | Deep merge + comment preservation | ✅ Supported |
| ENV | `.env`, `.env.*` | Key-value override | ✅ Supported |
| YAML | `.yaml`, `.yml` | Deep recursive merge | 🔜 Planned |
| TOML | `.toml` | Deep recursive merge | 🔜 Planned |
### Run Tests
```bash
# All tests
bun test
# Watch mode
bun test --watch
# Specific test file
bun test tests/unit/json-adapter.test.ts
```
### Build
```bash
bun run build
```
### Run Locally
```bash
bun run dev init
bun run dev merge
bun run dev info
```
## Troubleshooting

@@ -425,2 +400,3 @@

**Check hook installation:**
```bash

@@ -431,2 +407,3 @@ permachine info

**Verify git config:**
```bash

@@ -438,2 +415,3 @@ git config --get core.hooksPath

**Check hook files exist:**
```bash

@@ -446,2 +424,3 @@ ls .permachine/hooks/

**Run manually to see errors:**
```bash

@@ -451,3 +430,4 @@ permachine merge

**Check machine name:**
**Check machine name matches your files:**
```bash

@@ -458,5 +438,20 @@ permachine info

### Wrong machine name detected
Machine names are auto-detected from your system hostname. To verify:
```bash
# Windows
echo %COMPUTERNAME%
# Linux/Mac
hostname
```
Files must match this name (case-insensitive).
### Conflicts with other git hook tools
**Use legacy mode:**
If you use Husky or other hook managers, use legacy mode:
```bash

@@ -467,14 +462,32 @@ permachine uninstall

This wraps existing hooks instead of replacing them.
### Output file not being gitignored
By default, `permachine init` and `permachine merge` automatically add output files to `.gitignore`. If this isn't working:
1. Check if `.gitignore` exists and contains your output files
2. Verify the file was removed from git tracking: `git ls-files config.json` (should return nothing)
3. If you used `--no-gitignore`, re-run without that flag
To manually fix:
```bash
echo "config.json" >> .gitignore
git rm --cached config.json
```
## Contributing
Contributions welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass (`bun test`)
5. Submit a pull request
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for:
- Development setup
- Architecture overview
- Testing guidelines
- Code standards
- How to submit PRs
## License
MIT © JosXa
MIT © [JosXa](https://github.com/JosXa)

@@ -484,3 +497,3 @@ ## Roadmap

- [x] JSON support
- [x] ENV support
- [x] ENV support
- [x] JSONC support (comments & trailing commas)

@@ -490,4 +503,5 @@ - [x] Git hooks (hooksPath & legacy)

- [x] CLI interface
- [x] Comprehensive tests (74 tests)
- [x] Comprehensive tests (81 tests)
- [x] npm package publication
- [x] Watch mode for development
- [ ] YAML support

@@ -497,3 +511,2 @@ - [ ] TOML support

- [ ] Config file for patterns
- [ ] Watch mode for development
- [ ] Dry-run mode

@@ -504,3 +517,4 @@

Inspired by:
- [Husky](https://github.com/typicode/husky) - Git hooks made easy
- The need for machine-specific configurations across development environments

Sorry, the diff of this file is too big to display