@tokenbooks/wt 0.1.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 +395 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +71 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/doctor.d.ts +7 -0
- package/dist/commands/doctor.js +153 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.js +15 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/new.d.ts +8 -0
- package/dist/commands/new.js +160 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/remove.d.ts +7 -0
- package/dist/commands/remove.js +133 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/setup.d.ts +10 -0
- package/dist/commands/setup.js +157 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/core/database.d.ts +11 -0
- package/dist/core/database.js +69 -0
- package/dist/core/database.js.map +1 -0
- package/dist/core/env-patcher.d.ts +11 -0
- package/dist/core/env-patcher.js +133 -0
- package/dist/core/env-patcher.js.map +1 -0
- package/dist/core/git.d.ts +19 -0
- package/dist/core/git.js +102 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/registry.d.ts +20 -0
- package/dist/core/registry.js +103 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/slot-allocator.d.ts +17 -0
- package/dist/core/slot-allocator.js +37 -0
- package/dist/core/slot-allocator.js.map +1 -0
- package/dist/output.d.ts +11 -0
- package/dist/output.js +91 -0
- package/dist/output.js.map +1 -0
- package/dist/schemas/config.schema.d.ts +57 -0
- package/dist/schemas/config.schema.js +33 -0
- package/dist/schemas/config.schema.js.map +1 -0
- package/dist/schemas/registry.schema.d.ts +22 -0
- package/dist/schemas/registry.schema.js +19 -0
- package/dist/schemas/registry.schema.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
- package/skills/wt/SKILL.md +211 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ledgertech Inc.
|
|
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,395 @@
|
|
|
1
|
+
# wt — Git Worktree Environment Isolation
|
|
2
|
+
|
|
3
|
+
A CLI tool that gives each git worktree its own Postgres database, Redis database index, ports, and `.env` files. Prevents worktrees from corrupting each other's data.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
When you use `git worktree add` for parallel development, all worktrees share the same database, Redis instance, and ports. This means:
|
|
8
|
+
|
|
9
|
+
- Schema migrations in one worktree break another
|
|
10
|
+
- BullMQ/Redis queues collide across worktrees
|
|
11
|
+
- Two dev servers can't run simultaneously on the same port
|
|
12
|
+
- `.env` files point to the same resources everywhere
|
|
13
|
+
|
|
14
|
+
`wt` solves this by assigning each worktree an isolated **slot** (1–15) that determines its database name, Redis DB index, and port range.
|
|
15
|
+
|
|
16
|
+
## How It Works
|
|
17
|
+
|
|
18
|
+
Each worktree gets a numbered slot. The slot determines everything:
|
|
19
|
+
|
|
20
|
+
| Resource | Formula | Slot 0 (main) | Slot 1 | Slot 2 | Slot 3 |
|
|
21
|
+
|----------|---------|:-:|:-:|:-:|:-:|
|
|
22
|
+
| Database | `{baseName}_wt{slot}` | `mydb` | `mydb_wt1` | `mydb_wt2` | `mydb_wt3` |
|
|
23
|
+
| Redis DB | `slot` | `/0` | `/1` | `/2` | `/3` |
|
|
24
|
+
| Ports | `slot * stride + defaultPort` | 3000, 3001 | 3100, 3101 | 3200, 3201 | 3300, 3301 |
|
|
25
|
+
|
|
26
|
+
- **Database**: Created via `CREATE DATABASE ... TEMPLATE` (fast filesystem copy, not dump/restore)
|
|
27
|
+
- **Redis**: Uses a different DB index per worktree (Redis supports 0–15)
|
|
28
|
+
- **Ports**: Offset by `portStride` (default 100) per slot
|
|
29
|
+
- **Env files**: Copied from main worktree and patched with the slot's values
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### 1. Install
|
|
34
|
+
|
|
35
|
+
**Global** (available in all repos):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm add -g @tokenbooks/wt
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Per-project** (recommended for teams — version-locked in package.json):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm add -D @tokenbooks/wt
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Create `wt.config.json`
|
|
48
|
+
|
|
49
|
+
Create this file in your repository root and commit it. See [Configuration Reference](#configuration-reference) for full details.
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"baseDatabaseName": "myapp",
|
|
54
|
+
"baseWorktreePath": ".worktrees",
|
|
55
|
+
"portStride": 100,
|
|
56
|
+
"maxSlots": 15,
|
|
57
|
+
"services": [
|
|
58
|
+
{ "name": "web", "defaultPort": 3000 },
|
|
59
|
+
{ "name": "api", "defaultPort": 4000 }
|
|
60
|
+
],
|
|
61
|
+
"envFiles": [
|
|
62
|
+
{
|
|
63
|
+
"source": ".env",
|
|
64
|
+
"patches": [
|
|
65
|
+
{ "var": "DATABASE_URL", "type": "database" }
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"source": "backend/.env",
|
|
70
|
+
"patches": [
|
|
71
|
+
{ "var": "DATABASE_URL", "type": "database" },
|
|
72
|
+
{ "var": "REDIS_URL", "type": "redis" },
|
|
73
|
+
{ "var": "PORT", "type": "port", "service": "api" }
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"source": "frontend/.env",
|
|
78
|
+
"patches": [
|
|
79
|
+
{ "var": "PORT", "type": "port", "service": "web" },
|
|
80
|
+
{ "var": "API_URL", "type": "url", "service": "api" }
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"postSetup": ["npm install"],
|
|
85
|
+
"autoInstall": true
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. Add `.worktree-registry.json` to `.gitignore`
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
echo ".worktree-registry.json" >> .gitignore
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Use it
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Create a worktree with full isolation
|
|
99
|
+
wt new feat/my-feature
|
|
100
|
+
|
|
101
|
+
# List all worktree allocations
|
|
102
|
+
wt list
|
|
103
|
+
|
|
104
|
+
# Check health
|
|
105
|
+
wt doctor
|
|
106
|
+
|
|
107
|
+
# Clean up
|
|
108
|
+
wt remove feat-my-feature
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 5. Claude Code skill (optional)
|
|
112
|
+
|
|
113
|
+
The package ships with a `/wt` skill for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). To enable it, symlink from your project:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
mkdir -p .claude/skills
|
|
117
|
+
ln -s ../../node_modules/@tokenbooks/wt/skills/wt/SKILL.md .claude/skills/wt.md
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Then use `/wt init`, `/wt new feat/foo`, `/wt doctor`, etc. inside Claude Code.
|
|
121
|
+
|
|
122
|
+
## Commands
|
|
123
|
+
|
|
124
|
+
### `wt new <branch> [--slot N] [--no-install] [--json]`
|
|
125
|
+
|
|
126
|
+
Creates a new git worktree and sets up its isolated environment:
|
|
127
|
+
|
|
128
|
+
1. Allocates the next available slot (or uses `--slot N`)
|
|
129
|
+
2. Runs `git worktree add .worktrees/<slug> -b <branch>`
|
|
130
|
+
3. Creates a new Postgres database from the main DB as template
|
|
131
|
+
4. Copies all configured `.env` files, patching each with slot-specific values
|
|
132
|
+
5. Runs `postSetup` commands (unless `--no-install`)
|
|
133
|
+
|
|
134
|
+
### `wt setup [path] [--no-install] [--json]`
|
|
135
|
+
|
|
136
|
+
Sets up an existing worktree that was created manually or by another tool. Useful when:
|
|
137
|
+
|
|
138
|
+
- You ran `git worktree add` directly
|
|
139
|
+
- A worktree's env files need regenerating
|
|
140
|
+
- Called automatically by the `post-checkout` hook
|
|
141
|
+
|
|
142
|
+
If the worktree already has a slot allocation, it reuses it.
|
|
143
|
+
|
|
144
|
+
### `wt remove <path-or-slot> [--keep-db] [--json]`
|
|
145
|
+
|
|
146
|
+
Removes a worktree and cleans up its resources:
|
|
147
|
+
|
|
148
|
+
1. Drops the worktree's Postgres database (unless `--keep-db`)
|
|
149
|
+
2. Runs `git worktree remove`
|
|
150
|
+
3. Removes the allocation from the registry
|
|
151
|
+
|
|
152
|
+
Accepts either a path (`.worktrees/feat-my-feature`) or a slot number (`3`).
|
|
153
|
+
|
|
154
|
+
### `wt list [--json]`
|
|
155
|
+
|
|
156
|
+
Shows all worktree allocations with their slot, branch, database, Redis DB, ports, and status (ok/stale).
|
|
157
|
+
|
|
158
|
+
### `wt doctor [--fix] [--json]`
|
|
159
|
+
|
|
160
|
+
Runs diagnostics:
|
|
161
|
+
|
|
162
|
+
- **Stale entries**: Registry points to a path that no longer exists
|
|
163
|
+
- **Missing databases**: Allocated DB doesn't exist in Postgres
|
|
164
|
+
- **Missing env files**: Expected env files not found in worktree
|
|
165
|
+
- **Orphaned databases**: `{baseName}_wt*` databases not in the registry
|
|
166
|
+
|
|
167
|
+
Use `--fix` to auto-repair stale entries and drop orphaned databases.
|
|
168
|
+
|
|
169
|
+
### JSON output
|
|
170
|
+
|
|
171
|
+
All commands support `--json` for machine-readable output:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"success": true,
|
|
176
|
+
"data": { ... }
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Or on error:
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"success": false,
|
|
185
|
+
"error": { "code": "NO_SLOTS", "message": "All 15 slots are occupied." }
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Configuration Reference
|
|
190
|
+
|
|
191
|
+
### `wt.config.json`
|
|
192
|
+
|
|
193
|
+
This file lives in your repository root and is committed to version control.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
{
|
|
197
|
+
// Required: name of your main Postgres database
|
|
198
|
+
"baseDatabaseName": string,
|
|
199
|
+
|
|
200
|
+
// Directory for worktrees, relative to repo root (default: ".worktrees")
|
|
201
|
+
"baseWorktreePath": string,
|
|
202
|
+
|
|
203
|
+
// Port offset per slot (default: 100)
|
|
204
|
+
"portStride": number,
|
|
205
|
+
|
|
206
|
+
// Maximum number of concurrent worktrees (default: 15, max: 15)
|
|
207
|
+
"maxSlots": number,
|
|
208
|
+
|
|
209
|
+
// Services that need port allocation
|
|
210
|
+
"services": [
|
|
211
|
+
{ "name": string, "defaultPort": number }
|
|
212
|
+
],
|
|
213
|
+
|
|
214
|
+
// Env files to copy and patch for each worktree
|
|
215
|
+
"envFiles": [
|
|
216
|
+
{
|
|
217
|
+
"source": string, // Path relative to repo root
|
|
218
|
+
"patches": [
|
|
219
|
+
{
|
|
220
|
+
"var": string, // Env var name to patch
|
|
221
|
+
"type": string, // "database" | "redis" | "port" | "url"
|
|
222
|
+
"service": string // Required for "port" and "url" types
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
],
|
|
227
|
+
|
|
228
|
+
// Commands to run in the worktree after env setup (default: [])
|
|
229
|
+
"postSetup": string[],
|
|
230
|
+
|
|
231
|
+
// Whether to run postSetup automatically (default: true)
|
|
232
|
+
"autoInstall": boolean
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Patch Types
|
|
237
|
+
|
|
238
|
+
| Type | What it patches | Input | Output (slot 3) |
|
|
239
|
+
|------|----------------|-------|------------------|
|
|
240
|
+
| `database` | Replaces DB name in a Postgres URL | `postgresql://u:p@host:5432/myapp?schema=public` | `postgresql://u:p@host:5432/myapp_wt3?schema=public` |
|
|
241
|
+
| `redis` | Replaces or appends DB index in a Redis URL | `redis://:pass@host:6379/0` | `redis://:pass@host:6379/3` |
|
|
242
|
+
| `port` | Replaces the entire value with the allocated port | `4000` | `4300` |
|
|
243
|
+
| `url` | Replaces the port number inside a URL | `http://localhost:4000/api` | `http://localhost:4300/api` |
|
|
244
|
+
|
|
245
|
+
The `port` and `url` types require a `service` field that matches a name in `services`.
|
|
246
|
+
|
|
247
|
+
### `.worktree-registry.json`
|
|
248
|
+
|
|
249
|
+
Auto-managed file at the repo root. **Add to `.gitignore`** — it's machine-local.
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"version": 1,
|
|
254
|
+
"allocations": {
|
|
255
|
+
"1": {
|
|
256
|
+
"worktreePath": "/absolute/path/to/.worktrees/feat-auth",
|
|
257
|
+
"branchName": "feat/auth",
|
|
258
|
+
"dbName": "myapp_wt1",
|
|
259
|
+
"redisDb": 1,
|
|
260
|
+
"ports": { "web": 3100, "api": 4100 },
|
|
261
|
+
"createdAt": "2026-02-17T14:30:00Z"
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Git Hook: Automatic Setup
|
|
268
|
+
|
|
269
|
+
Add a `post-checkout` hook so `wt setup` runs automatically when switching branches inside a worktree. With [Husky](https://typicode.github.io/husky/):
|
|
270
|
+
|
|
271
|
+
Create `.husky/post-checkout`:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
#!/bin/bash
|
|
275
|
+
|
|
276
|
+
prev_head="$1"
|
|
277
|
+
new_head="$2"
|
|
278
|
+
is_branch="$3"
|
|
279
|
+
|
|
280
|
+
# Only run on branch checkout (not file checkout)
|
|
281
|
+
[ "$is_branch" = "0" ] && exit 0
|
|
282
|
+
|
|
283
|
+
# Detect if we're in a worktree (not main)
|
|
284
|
+
git_common=$(git rev-parse --git-common-dir 2>/dev/null)
|
|
285
|
+
git_dir=$(git rev-parse --git-dir 2>/dev/null)
|
|
286
|
+
[ "$git_common" = "$git_dir" ] && exit 0
|
|
287
|
+
|
|
288
|
+
# Resolve main worktree path
|
|
289
|
+
main_worktree=$(cd "$git_common/.." && pwd -P)
|
|
290
|
+
|
|
291
|
+
# Find wt: local install first, then global
|
|
292
|
+
wt_bin="$main_worktree/node_modules/.bin/wt"
|
|
293
|
+
if [ ! -f "$wt_bin" ]; then
|
|
294
|
+
wt_bin=$(command -v wt 2>/dev/null || true)
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
if [ -z "$wt_bin" ]; then
|
|
298
|
+
echo "Warning: wt CLI not found. Install globally (pnpm add -g @tokenbooks/wt) or locally (pnpm add -D @tokenbooks/wt)."
|
|
299
|
+
exit 0
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
echo "Setting up worktree environment..."
|
|
303
|
+
"$wt_bin" setup "$(pwd -P)" --json 2>/dev/null && echo "Worktree ready!" || {
|
|
304
|
+
echo "Warning: Auto-setup failed. Run 'wt setup' manually."
|
|
305
|
+
exit 0
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
The hook checks for a local install first (`node_modules/.bin/wt`), then falls back to a global `wt` on PATH.
|
|
310
|
+
|
|
311
|
+
## Setup Guide for LLM Agents
|
|
312
|
+
|
|
313
|
+
If you are an LLM agent setting up `wt` for a repository, follow these steps:
|
|
314
|
+
|
|
315
|
+
### Step 1: Discover the project structure
|
|
316
|
+
|
|
317
|
+
Identify these from the repository:
|
|
318
|
+
|
|
319
|
+
- **Database URL format**: Search `.env` files for `DATABASE_URL`. Extract the database name (the path segment after the port, before `?`).
|
|
320
|
+
- **Redis URL format**: Search for `REDIS_URL`. Note whether it ends with `/0` or has no DB index.
|
|
321
|
+
- **Services and ports**: Find all dev server commands and their default ports. Check `package.json` scripts, `docker-compose.yml`, and framework configs.
|
|
322
|
+
- **Env files**: List all `.env` files (not `.env.example`). These are the files that need patching.
|
|
323
|
+
|
|
324
|
+
### Step 2: Map env vars to patch types
|
|
325
|
+
|
|
326
|
+
For each `.env` file, identify which variables need patching:
|
|
327
|
+
|
|
328
|
+
| If the variable contains... | Use patch type |
|
|
329
|
+
|----------------------------|----------------|
|
|
330
|
+
| A Postgres connection URL (`postgresql://...`) | `database` |
|
|
331
|
+
| A Redis connection URL (`redis://...`) | `redis` |
|
|
332
|
+
| Just a port number (`3000`) | `port` + service name |
|
|
333
|
+
| A URL with a port (`http://localhost:3000/...`) | `url` + service name |
|
|
334
|
+
|
|
335
|
+
Variables that don't match any pattern (API keys, secrets, feature flags) should NOT be patched.
|
|
336
|
+
|
|
337
|
+
### Step 3: Build `wt.config.json`
|
|
338
|
+
|
|
339
|
+
Using the discovered information, construct the config:
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
1. baseDatabaseName = the DB name from the main DATABASE_URL
|
|
343
|
+
2. services = each dev server as { name, defaultPort }
|
|
344
|
+
3. envFiles = each .env file with its patches
|
|
345
|
+
4. postSetup = the install command for the package manager (npm install, pnpm install, etc.)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Validate that:
|
|
349
|
+
- Every `port` and `url` patch has a `service` that exists in `services`
|
|
350
|
+
- The `portStride` (default 100) doesn't cause port collisions with other local services
|
|
351
|
+
- `maxSlots * portStride` doesn't push ports into reserved ranges (e.g., above 65535)
|
|
352
|
+
|
|
353
|
+
### Step 4: Install and test
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
# Install wt
|
|
357
|
+
pnpm add -D @tokenbooks/wt
|
|
358
|
+
|
|
359
|
+
# Add to .gitignore
|
|
360
|
+
echo ".worktree-registry.json" >> .gitignore
|
|
361
|
+
|
|
362
|
+
# Verify
|
|
363
|
+
wt list # Should show "No worktree allocations found."
|
|
364
|
+
wt doctor # Should show "All checks passed."
|
|
365
|
+
|
|
366
|
+
# Smoke test (creates a real worktree + database)
|
|
367
|
+
wt new test/wt-smoke --no-install
|
|
368
|
+
wt list # Should show the new allocation
|
|
369
|
+
wt remove test-wt-smoke
|
|
370
|
+
wt list # Should be empty again
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Step 5: Add convenience scripts (optional)
|
|
374
|
+
|
|
375
|
+
```json
|
|
376
|
+
{
|
|
377
|
+
"scripts": {
|
|
378
|
+
"wt": "wt",
|
|
379
|
+
"wt:new": "wt new",
|
|
380
|
+
"wt:list": "wt list",
|
|
381
|
+
"wt:doctor": "wt doctor"
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Requirements
|
|
387
|
+
|
|
388
|
+
- Node.js >= 20.19.0
|
|
389
|
+
- PostgreSQL (running, accessible via `DATABASE_URL` in root `.env`)
|
|
390
|
+
- Redis (if using `redis` patch type)
|
|
391
|
+
- Git (for worktree operations)
|
|
392
|
+
|
|
393
|
+
## License
|
|
394
|
+
|
|
395
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const new_1 = require("./commands/new");
|
|
6
|
+
const setup_1 = require("./commands/setup");
|
|
7
|
+
const remove_1 = require("./commands/remove");
|
|
8
|
+
const list_1 = require("./commands/list");
|
|
9
|
+
const doctor_1 = require("./commands/doctor");
|
|
10
|
+
const git_1 = require("./core/git");
|
|
11
|
+
const program = new commander_1.Command();
|
|
12
|
+
program
|
|
13
|
+
.name('wt')
|
|
14
|
+
.description('Git worktree environment isolation CLI')
|
|
15
|
+
.version('0.1.0');
|
|
16
|
+
program
|
|
17
|
+
.command('new')
|
|
18
|
+
.description('Create a new worktree with isolated environment')
|
|
19
|
+
.argument('<branch>', 'Branch name to create or checkout')
|
|
20
|
+
.option('--slot <n>', 'Force a specific slot number')
|
|
21
|
+
.option('--no-install', 'Skip post-setup commands')
|
|
22
|
+
.option('--json', 'Output as JSON', false)
|
|
23
|
+
.action(async (branch, opts) => {
|
|
24
|
+
await (0, new_1.newCommand)(branch, {
|
|
25
|
+
json: opts.json,
|
|
26
|
+
install: opts.install,
|
|
27
|
+
slot: opts.slot,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
program
|
|
31
|
+
.command('setup')
|
|
32
|
+
.description('Set up environment for an existing worktree')
|
|
33
|
+
.argument('[path]', 'Worktree path (default: current directory)')
|
|
34
|
+
.option('--no-install', 'Skip post-setup commands')
|
|
35
|
+
.option('--json', 'Output as JSON', false)
|
|
36
|
+
.action(async (targetPath, opts) => {
|
|
37
|
+
await (0, setup_1.setupCommand)(targetPath, {
|
|
38
|
+
json: opts.json,
|
|
39
|
+
install: opts.install,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
program
|
|
43
|
+
.command('remove')
|
|
44
|
+
.description('Remove a worktree and clean up its resources')
|
|
45
|
+
.argument('<path-or-slot>', 'Worktree path or slot number')
|
|
46
|
+
.option('--keep-db', 'Keep the database (do not drop)', false)
|
|
47
|
+
.option('--json', 'Output as JSON', false)
|
|
48
|
+
.action(async (pathOrSlot, opts) => {
|
|
49
|
+
await (0, remove_1.removeCommand)(pathOrSlot, {
|
|
50
|
+
json: opts.json,
|
|
51
|
+
keepDb: opts.keepDb,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
program
|
|
55
|
+
.command('list')
|
|
56
|
+
.description('List all worktree allocations')
|
|
57
|
+
.option('--json', 'Output as JSON', false)
|
|
58
|
+
.action((opts) => {
|
|
59
|
+
const repoRoot = (0, git_1.getMainWorktreePath)();
|
|
60
|
+
(0, list_1.listCommand)(repoRoot, { json: opts.json });
|
|
61
|
+
});
|
|
62
|
+
program
|
|
63
|
+
.command('doctor')
|
|
64
|
+
.description('Diagnose and fix worktree environment issues')
|
|
65
|
+
.option('--fix', 'Auto-repair stale entries and orphaned databases', false)
|
|
66
|
+
.option('--json', 'Output as JSON', false)
|
|
67
|
+
.action(async (opts) => {
|
|
68
|
+
await (0, doctor_1.doctorCommand)({ json: opts.json, fix: opts.fix });
|
|
69
|
+
});
|
|
70
|
+
program.parse();
|
|
71
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.doctorCommand = doctorCommand;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const registry_1 = require("../core/registry");
|
|
40
|
+
const database_1 = require("../core/database");
|
|
41
|
+
const git_1 = require("../core/git");
|
|
42
|
+
const setup_1 = require("./setup");
|
|
43
|
+
const output_1 = require("../output");
|
|
44
|
+
/** Read DATABASE_URL from the main worktree's .env file */
|
|
45
|
+
function readDatabaseUrl(mainRoot) {
|
|
46
|
+
const envPath = path.join(mainRoot, '.env');
|
|
47
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
48
|
+
const match = content.match(/^DATABASE_URL=["']?([^"'\n]+)/m);
|
|
49
|
+
if (!match?.[1]) {
|
|
50
|
+
throw new Error('DATABASE_URL not found in .env');
|
|
51
|
+
}
|
|
52
|
+
return match[1];
|
|
53
|
+
}
|
|
54
|
+
/** Run diagnostics on the worktree registry and databases */
|
|
55
|
+
async function doctorCommand(options) {
|
|
56
|
+
try {
|
|
57
|
+
const mainRoot = (0, git_1.getMainWorktreePath)();
|
|
58
|
+
const config = (0, setup_1.loadConfig)(mainRoot);
|
|
59
|
+
let registry = (0, registry_1.readRegistry)(mainRoot);
|
|
60
|
+
const databaseUrl = readDatabaseUrl(mainRoot);
|
|
61
|
+
const issues = [];
|
|
62
|
+
// Check each allocation
|
|
63
|
+
for (const [slotStr, allocation] of Object.entries(registry.allocations)) {
|
|
64
|
+
const slot = Number(slotStr);
|
|
65
|
+
// Check if worktree path exists
|
|
66
|
+
if (!fs.existsSync(allocation.worktreePath)) {
|
|
67
|
+
const issue = {
|
|
68
|
+
type: 'stale_entry',
|
|
69
|
+
slot,
|
|
70
|
+
detail: `Path does not exist: ${allocation.worktreePath}`,
|
|
71
|
+
fixed: options.fix,
|
|
72
|
+
};
|
|
73
|
+
issues.push(issue);
|
|
74
|
+
if (options.fix) {
|
|
75
|
+
registry = (0, registry_1.removeAllocation)(registry, slot);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Check if database exists
|
|
79
|
+
const dbOk = await (0, database_1.databaseExists)(databaseUrl, allocation.dbName);
|
|
80
|
+
if (!dbOk) {
|
|
81
|
+
issues.push({
|
|
82
|
+
type: 'missing_db',
|
|
83
|
+
slot,
|
|
84
|
+
detail: `Database does not exist: ${allocation.dbName}`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// Check if env files exist in the worktree
|
|
88
|
+
if (fs.existsSync(allocation.worktreePath)) {
|
|
89
|
+
for (const envFile of config.envFiles) {
|
|
90
|
+
const envPath = path.join(allocation.worktreePath, envFile.source);
|
|
91
|
+
if (!fs.existsSync(envPath)) {
|
|
92
|
+
issues.push({
|
|
93
|
+
type: 'missing_env',
|
|
94
|
+
slot,
|
|
95
|
+
detail: `Missing env file: ${envFile.source}`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Detect orphaned databases
|
|
102
|
+
const pattern = `${config.baseDatabaseName}_wt%`;
|
|
103
|
+
const allDbs = await (0, database_1.listDatabasesByPattern)(databaseUrl, pattern);
|
|
104
|
+
const registeredDbNames = new Set(Object.values(registry.allocations).map((a) => a.dbName));
|
|
105
|
+
for (const dbName of allDbs) {
|
|
106
|
+
if (!registeredDbNames.has(dbName)) {
|
|
107
|
+
const issue = {
|
|
108
|
+
type: 'orphaned_db',
|
|
109
|
+
detail: `Orphaned database: ${dbName}`,
|
|
110
|
+
fixed: options.fix,
|
|
111
|
+
};
|
|
112
|
+
issues.push(issue);
|
|
113
|
+
if (options.fix) {
|
|
114
|
+
await (0, database_1.dropDatabase)(databaseUrl, dbName, config.baseDatabaseName);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Save registry if fixes were applied
|
|
119
|
+
if (options.fix) {
|
|
120
|
+
(0, registry_1.writeRegistry)(mainRoot, registry);
|
|
121
|
+
}
|
|
122
|
+
// Output
|
|
123
|
+
const result = { issues, totalIssues: issues.length, fixed: options.fix };
|
|
124
|
+
if (options.json) {
|
|
125
|
+
console.log((0, output_1.formatJson)((0, output_1.success)(result)));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (issues.length === 0) {
|
|
129
|
+
console.log('All checks passed. No issues found.');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
console.log(`Found ${issues.length} issue(s):\n`);
|
|
133
|
+
for (const issue of issues) {
|
|
134
|
+
const prefix = issue.fixed ? '[FIXED]' : '[ISSUE]';
|
|
135
|
+
const slotInfo = issue.slot !== undefined ? ` (slot ${issue.slot})` : '';
|
|
136
|
+
console.log(` ${prefix} ${issue.type}${slotInfo}: ${issue.detail}`);
|
|
137
|
+
}
|
|
138
|
+
if (!options.fix && issues.some((i) => i.type === 'stale_entry' || i.type === 'orphaned_db')) {
|
|
139
|
+
console.log('\nRun with --fix to auto-repair stale entries and orphaned databases.');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
144
|
+
if (options.json) {
|
|
145
|
+
console.log((0, output_1.formatJson)((0, output_1.error)('DOCTOR_FAILED', message)));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.error(`Doctor failed: ${message}`);
|
|
149
|
+
}
|
|
150
|
+
process.exitCode = 1;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|