permachine 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +474 -0
- package/dist/cli.js +8504 -0
- package/package.json +60 -0
- package/templates/hooks/post-checkout +7 -0
- package/templates/hooks/post-commit +7 -0
- package/templates/hooks/post-merge +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JosXa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# permachine
|
|
2
|
+
|
|
3
|
+
Per-machine config management with git for tools that don't support it natively. Automatically merge machine-specific configurations with a base config.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/permachine)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Problem
|
|
9
|
+
|
|
10
|
+
When syncing dotfiles across multiple machines, you often need:
|
|
11
|
+
|
|
12
|
+
- **Shared configuration** - Settings that work across all machines
|
|
13
|
+
- **Machine-specific overrides** - Local paths, API keys, ports, etc.
|
|
14
|
+
- **Automatic merging** - No manual copy-paste or merge steps
|
|
15
|
+
|
|
16
|
+
## Solution
|
|
17
|
+
|
|
18
|
+
`permachine` automatically:
|
|
19
|
+
|
|
20
|
+
1. Detects your machine name
|
|
21
|
+
2. Finds machine-specific config files (e.g. `config.my-laptop.json`, `config.workstation.json`)
|
|
22
|
+
3. Merges them with base configs (e.g., `config.base.json`)
|
|
23
|
+
4. Outputs the final config (e.g., `config.json`)
|
|
24
|
+
5. **Manages .gitignore** - Adds output files and removes from git tracking
|
|
25
|
+
6. Runs automatically on git operations via hooks
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Install globally
|
|
31
|
+
npm install -g permachine
|
|
32
|
+
|
|
33
|
+
# In your repository
|
|
34
|
+
cd /path/to/your/repo
|
|
35
|
+
|
|
36
|
+
# Initialize (one-time setup)
|
|
37
|
+
permachine init
|
|
38
|
+
|
|
39
|
+
# That's it! Your configs will now auto-merge on git operations when a file ends with `.<machine-name>.<ext>`
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## CLI Reference
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
permachine - Automatically merge machine-specific config files
|
|
46
|
+
|
|
47
|
+
USAGE:
|
|
48
|
+
permachine <command> [options]
|
|
49
|
+
|
|
50
|
+
COMMANDS:
|
|
51
|
+
init Initialize permachine in current repository
|
|
52
|
+
merge Manually trigger merge operation
|
|
53
|
+
info Show information about current setup
|
|
54
|
+
uninstall Uninstall git hooks
|
|
55
|
+
watch Watch for file changes and auto-merge
|
|
56
|
+
|
|
57
|
+
OPTIONS:
|
|
58
|
+
--help, -h Show this help message
|
|
59
|
+
--version, -v Show version number
|
|
60
|
+
--silent, -s Suppress all output except errors (for merge command)
|
|
61
|
+
--legacy Use legacy .git/hooks wrapping (for init command)
|
|
62
|
+
--auto Auto-detect best installation method (for init command)
|
|
63
|
+
--no-gitignore Don't manage .gitignore or git tracking (for init/merge commands)
|
|
64
|
+
--debounce <ms> Debounce delay in milliseconds (for watch command, default: 300)
|
|
65
|
+
--verbose Show detailed file change events (for watch command)
|
|
66
|
+
|
|
67
|
+
EXAMPLES:
|
|
68
|
+
permachine init
|
|
69
|
+
permachine merge --silent
|
|
70
|
+
permachine info
|
|
71
|
+
permachine uninstall
|
|
72
|
+
permachine watch
|
|
73
|
+
permachine watch --debounce 500 --verbose
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
### File Naming Convention
|
|
79
|
+
|
|
80
|
+
Given machine name `my-laptop` (auto-detected from hostname):
|
|
81
|
+
|
|
82
|
+
| Purpose | Filename | In Git? |
|
|
83
|
+
| --------------------- | ----------------------- | ------------------ |
|
|
84
|
+
| Base config (shared) | `config.base.json` | ✅ Yes |
|
|
85
|
+
| Machine-specific | `config.my-laptop.json` | ✅ Yes |
|
|
86
|
+
| Final output (merged) | `config.json` | ❌ No (gitignored) |
|
|
87
|
+
|
|
88
|
+
Same pattern works for `.env` files:
|
|
89
|
+
|
|
90
|
+
| Purpose | Filename | In Git? |
|
|
91
|
+
| ---------------- | ---------------- | ------------------ |
|
|
92
|
+
| Base config | `.env.base` | ✅ Yes |
|
|
93
|
+
| Machine-specific | `.env.my-laptop` | ✅ Yes |
|
|
94
|
+
| Final output | `.env` | ❌ No (gitignored) |
|
|
95
|
+
|
|
96
|
+
### Basic Commands
|
|
97
|
+
|
|
98
|
+
#### Initialize in Repository
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
permachine init
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**What it does:**
|
|
105
|
+
|
|
106
|
+
- Detects your machine name (e.g., `laptop`, `desktop`, `workstation`)
|
|
107
|
+
- Installs git hooks for automatic merging
|
|
108
|
+
- Scans for existing machine-specific files
|
|
109
|
+
- **Prompts for confirmation** if existing files will be overwritten
|
|
110
|
+
- Performs initial merge
|
|
111
|
+
- Adds output files to `.gitignore` and removes them from git tracking
|
|
112
|
+
|
|
113
|
+
**Example output:**
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
✓ Machine detected: laptop
|
|
117
|
+
✓ Git hooks installed via core.hooksPath
|
|
118
|
+
✓ Merged 2 file(s)
|
|
119
|
+
✓ Added 2 file(s) to .gitignore
|
|
120
|
+
✓ Removed 1 file(s) from git tracking
|
|
121
|
+
|
|
122
|
+
Git hooks will auto-merge on:
|
|
123
|
+
- checkout (switching branches)
|
|
124
|
+
- merge (git pull/merge)
|
|
125
|
+
- commit
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Manual Merge
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
permachine merge
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Prompts for confirmation** if existing files will be overwritten. Useful for testing or running without git hooks.
|
|
135
|
+
|
|
136
|
+
#### Watch Mode
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
permachine watch
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**What it does:**
|
|
143
|
+
|
|
144
|
+
- Watches all base and machine-specific files for changes
|
|
145
|
+
- Automatically merges when you save any watched file
|
|
146
|
+
|
|
147
|
+
#### Check Setup
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
permachine info
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Example output:**
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
Machine name: laptop
|
|
157
|
+
Repository: /path/to/repo
|
|
158
|
+
Hooks method: core.hooksPath
|
|
159
|
+
Hooks path: .permachine/hooks
|
|
160
|
+
Tracked patterns: 2
|
|
161
|
+
- config.base.json + config.laptop.json → config.json
|
|
162
|
+
- .env.base + .env.laptop → .env
|
|
163
|
+
|
|
164
|
+
Output files: 2 total, 1 existing
|
|
165
|
+
Existing output files:
|
|
166
|
+
- config.json
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Cookbook / Recipes
|
|
170
|
+
|
|
171
|
+
### Recipe 1: VSCode Settings Per Machine
|
|
172
|
+
|
|
173
|
+
Different settings for work laptop vs home desktop:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# On work laptop (machine: "worklaptop")
|
|
177
|
+
.vscode/
|
|
178
|
+
├── settings.base.json # Shared: theme, font size
|
|
179
|
+
├── settings.worklaptop.json # Work paths, proxy settings
|
|
180
|
+
└── settings.json # ← Merged output (gitignored)
|
|
181
|
+
|
|
182
|
+
# On home desktop (machine: "desktop")
|
|
183
|
+
.vscode/
|
|
184
|
+
├── settings.base.json # Shared: theme, font size
|
|
185
|
+
├── settings.desktop.json # Home paths, no proxy
|
|
186
|
+
└── settings.json # ← Merged output (gitignored)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**setup.base.json:**
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"editor.fontSize": 14,
|
|
194
|
+
"workbench.colorTheme": "Dark+"
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**settings.worklaptop.json:**
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"http.proxy": "http://proxy.company.com:8080",
|
|
203
|
+
"terminal.integrated.cwd": "C:/Projects"
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Recipe 2: Environment Variables
|
|
208
|
+
|
|
209
|
+
Different database credentials per environment:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# .env.base (shared defaults)
|
|
213
|
+
NODE_ENV=development
|
|
214
|
+
LOG_LEVEL=info
|
|
215
|
+
API_PORT=3000
|
|
216
|
+
|
|
217
|
+
# .env.laptop (local dev)
|
|
218
|
+
DATABASE_URL=postgresql://localhost:5432/myapp_dev
|
|
219
|
+
API_KEY=dev_key_123
|
|
220
|
+
|
|
221
|
+
# .env.prodserver (production)
|
|
222
|
+
DATABASE_URL=postgresql://prod.db.com:5432/myapp
|
|
223
|
+
API_KEY=prod_key_xyz
|
|
224
|
+
|
|
225
|
+
# .env ← Merged output (gitignored)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Recipe 3: Package.json Scripts
|
|
229
|
+
|
|
230
|
+
Different build scripts for different machines:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# package.base.json
|
|
234
|
+
{
|
|
235
|
+
"name": "my-app",
|
|
236
|
+
"version": "1.0.0",
|
|
237
|
+
"scripts": {
|
|
238
|
+
"test": "jest"
|
|
239
|
+
},
|
|
240
|
+
"dependencies": {
|
|
241
|
+
"express": "^4.18.0"
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# package.laptop.json (local development)
|
|
246
|
+
{
|
|
247
|
+
"scripts": {
|
|
248
|
+
"dev": "nodemon src/index.js",
|
|
249
|
+
"build": "webpack --mode development"
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# package.buildserver.json (CI/CD)
|
|
254
|
+
{
|
|
255
|
+
"scripts": {
|
|
256
|
+
"build": "webpack --mode production",
|
|
257
|
+
"deploy": "aws s3 sync dist/ s3://my-bucket"
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# package.json ← Merged output
|
|
262
|
+
# Each machine gets appropriate scripts!
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Recipe 4: Database Configuration
|
|
266
|
+
|
|
267
|
+
Multi-environment database setup:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
# config/database.base.json
|
|
271
|
+
{
|
|
272
|
+
"pool": {
|
|
273
|
+
"min": 2,
|
|
274
|
+
"max": 10
|
|
275
|
+
},
|
|
276
|
+
"migrations": {
|
|
277
|
+
"directory": "./migrations"
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
# config/database.laptop.json
|
|
282
|
+
{
|
|
283
|
+
"connection": {
|
|
284
|
+
"host": "localhost",
|
|
285
|
+
"port": 5432,
|
|
286
|
+
"database": "myapp_dev",
|
|
287
|
+
"user": "dev",
|
|
288
|
+
"password": "dev123"
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# config/database.prodserver.json
|
|
293
|
+
{
|
|
294
|
+
"connection": {
|
|
295
|
+
"host": "db.production.com",
|
|
296
|
+
"port": 5432,
|
|
297
|
+
"database": "myapp_production",
|
|
298
|
+
"user": "produser",
|
|
299
|
+
"password": "secure_password_from_vault"
|
|
300
|
+
},
|
|
301
|
+
"pool": {
|
|
302
|
+
"min": 10,
|
|
303
|
+
"max": 50
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Recipe 5: Multi-File Projects
|
|
309
|
+
|
|
310
|
+
Complex projects with multiple config files:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
project/
|
|
314
|
+
├── config.base.json
|
|
315
|
+
├── config.laptop.json
|
|
316
|
+
├── settings/
|
|
317
|
+
│ ├── app.base.json
|
|
318
|
+
│ ├── app.laptop.json
|
|
319
|
+
│ ├── database.base.json
|
|
320
|
+
│ └── database.laptop.json
|
|
321
|
+
├── .env.base
|
|
322
|
+
└── .env.laptop
|
|
323
|
+
|
|
324
|
+
# After `permachine init`, all files auto-merge:
|
|
325
|
+
# - config.json
|
|
326
|
+
# - settings/app.json
|
|
327
|
+
# - settings/database.json
|
|
328
|
+
# - .env
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## How It Works
|
|
332
|
+
|
|
333
|
+
`permachine` uses a simple three-step process:
|
|
334
|
+
|
|
335
|
+
1. **Machine Detection** - Automatically detects your machine name from hostname (Windows: `COMPUTERNAME`, Linux/Mac: `hostname()`)
|
|
336
|
+
|
|
337
|
+
2. **File Discovery** - Scans your repository for files matching the pattern `*.{machine}.*` (e.g., `config.laptop.json`, `.env.desktop`)
|
|
338
|
+
|
|
339
|
+
3. **Smart Merging** - Merges base and machine-specific configs:
|
|
340
|
+
|
|
341
|
+
- **JSON**: Deep recursive merge (machine values override base)
|
|
342
|
+
- **ENV**: Key-value merge with comment preservation
|
|
343
|
+
|
|
344
|
+
4. **Gitignore Management** - Automatically adds output files to `.gitignore` and removes already-tracked files from git
|
|
345
|
+
|
|
346
|
+
5. **Git Hooks** - Installs hooks to auto-merge on checkout, merge, and commit operations
|
|
347
|
+
|
|
348
|
+
For detailed implementation information, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
349
|
+
|
|
350
|
+
## Supported File Types
|
|
351
|
+
|
|
352
|
+
| Type | Extensions | Merge Strategy | Status |
|
|
353
|
+
| ----- | --------------------- | --------------------------------- | ------------ |
|
|
354
|
+
| JSON | `.json` | Deep recursive merge | ✅ Supported |
|
|
355
|
+
| JSONC | `.json` with comments | Deep merge + comment preservation | ✅ Supported |
|
|
356
|
+
| ENV | `.env`, `.env.*` | Key-value override | ✅ Supported |
|
|
357
|
+
| YAML | `.yaml`, `.yml` | Deep recursive merge | 🔜 Planned |
|
|
358
|
+
| TOML | `.toml` | Deep recursive merge | 🔜 Planned |
|
|
359
|
+
|
|
360
|
+
## Troubleshooting
|
|
361
|
+
|
|
362
|
+
### Hooks not running
|
|
363
|
+
|
|
364
|
+
**Check hook installation:**
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
permachine info
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Verify git config:**
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
git config --get core.hooksPath
|
|
374
|
+
# Should output: .permachine/hooks
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**Check hook files exist:**
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
ls .permachine/hooks/
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Merge not happening
|
|
384
|
+
|
|
385
|
+
**Run manually to see errors:**
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
permachine merge
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Check machine name matches your files:**
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
permachine info
|
|
395
|
+
# Verify "Machine name" matches your file pattern
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Wrong machine name detected
|
|
399
|
+
|
|
400
|
+
Machine names are auto-detected from your system hostname. To verify:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
# Windows
|
|
404
|
+
echo %COMPUTERNAME%
|
|
405
|
+
|
|
406
|
+
# Linux/Mac
|
|
407
|
+
hostname
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Files must match this name (case-insensitive).
|
|
411
|
+
|
|
412
|
+
### Conflicts with other git hook tools
|
|
413
|
+
|
|
414
|
+
If you use Husky or other hook managers, use legacy mode:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
permachine uninstall
|
|
418
|
+
permachine init --legacy
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
This wraps existing hooks instead of replacing them.
|
|
422
|
+
|
|
423
|
+
### Output file not being gitignored
|
|
424
|
+
|
|
425
|
+
By default, `permachine init` and `permachine merge` automatically add output files to `.gitignore`. If this isn't working:
|
|
426
|
+
|
|
427
|
+
1. Check if `.gitignore` exists and contains your output files
|
|
428
|
+
2. Verify the file was removed from git tracking: `git ls-files config.json` (should return nothing)
|
|
429
|
+
3. If you used `--no-gitignore`, re-run without that flag
|
|
430
|
+
|
|
431
|
+
To manually fix:
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
echo "config.json" >> .gitignore
|
|
435
|
+
git rm --cached config.json
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Contributing
|
|
439
|
+
|
|
440
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for:
|
|
441
|
+
|
|
442
|
+
- Development setup
|
|
443
|
+
- Architecture overview
|
|
444
|
+
- Testing guidelines
|
|
445
|
+
- Code standards
|
|
446
|
+
- How to submit PRs
|
|
447
|
+
|
|
448
|
+
## License
|
|
449
|
+
|
|
450
|
+
MIT © [JosXa](https://github.com/JosXa)
|
|
451
|
+
|
|
452
|
+
## Roadmap
|
|
453
|
+
|
|
454
|
+
- [x] JSON support
|
|
455
|
+
- [x] ENV support
|
|
456
|
+
- [x] JSONC support (comments & trailing commas)
|
|
457
|
+
- [x] Git hooks (hooksPath & legacy)
|
|
458
|
+
- [x] Automatic .gitignore management
|
|
459
|
+
- [x] CLI interface
|
|
460
|
+
- [x] Comprehensive tests (81 tests)
|
|
461
|
+
- [x] npm package publication
|
|
462
|
+
- [x] Watch mode for development
|
|
463
|
+
- [ ] YAML support
|
|
464
|
+
- [ ] TOML support
|
|
465
|
+
- [ ] Custom merge strategies
|
|
466
|
+
- [ ] Config file for patterns
|
|
467
|
+
- [ ] Dry-run mode
|
|
468
|
+
|
|
469
|
+
## Credits
|
|
470
|
+
|
|
471
|
+
Inspired by:
|
|
472
|
+
|
|
473
|
+
- [Husky](https://github.com/typicode/husky) - Git hooks made easy
|
|
474
|
+
- The need for machine-specific configurations across development environments
|