git-worktree-utils 1.0.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 +155 -0
- package/dist/add.js +27 -0
- package/dist/cli.js +84 -0
- package/dist/find.js +17 -0
- package/dist/list.js +4 -0
- package/dist/remove.js +30 -0
- package/dist/rename.js +35 -0
- package/dist/sync-env.js +38 -0
- package/dist/utils-D1mQhe1g.js +5167 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 avi747av
|
|
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,155 @@
|
|
|
1
|
+
# git-worktree-utils
|
|
2
|
+
|
|
3
|
+
A safe git worktree management CLI with automatic environment file syncing.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Safety checks** - Blocks operations with uncommitted changes or unpushed commits
|
|
8
|
+
- **Branch protection** - Warns if a branch is already checked out elsewhere
|
|
9
|
+
- **Main repo protection** - Prevents accidental operations on the main repository
|
|
10
|
+
- **Automatic .env copying** - Copies all `.env*` files when creating new worktrees
|
|
11
|
+
- **Environment syncing** - Sync `.env` files across worktrees with one command
|
|
12
|
+
- **Fuzzy search** - Find worktrees by partial name match
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g git-worktree-utils
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Unified CLI
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
wt <command> [options]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Individual Commands
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
wt-add [options]
|
|
32
|
+
wt-list
|
|
33
|
+
wt-find [options]
|
|
34
|
+
wt-rename [options]
|
|
35
|
+
wt-remove [options]
|
|
36
|
+
wt-sync-env [options]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
### `wt add` - Create a new worktree
|
|
42
|
+
|
|
43
|
+
Creates a new git worktree and automatically copies all `.env*` files from project directories.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
wt add --dirName=my-feature --branchName=feature/my-feature
|
|
47
|
+
wt add --dirName=bugfix-123 # uses dirName as branch name
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Options:**
|
|
51
|
+
- `--dirName=<name>` - Directory name for the worktree (required)
|
|
52
|
+
- `--branchName=<name>` - Branch name (defaults to dirName)
|
|
53
|
+
|
|
54
|
+
### `wt list` (alias: `wt ls`)
|
|
55
|
+
|
|
56
|
+
Lists all git worktrees.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
wt list
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `wt find` (alias: `wt search`)
|
|
63
|
+
|
|
64
|
+
Search worktrees by partial name match (case-insensitive).
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
wt find --search=feature
|
|
68
|
+
wt find --search=FS-1234
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `wt rename` (alias: `wt mv`)
|
|
72
|
+
|
|
73
|
+
Rename a worktree directory with safety checks.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
wt rename --oldDirName=old-name --newDirName=new-name
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Safety checks:**
|
|
80
|
+
- Blocks if uncommitted changes exist
|
|
81
|
+
- Blocks if unpushed commits exist
|
|
82
|
+
- Prevents renaming the main repository
|
|
83
|
+
|
|
84
|
+
### `wt remove` (alias: `wt rm`)
|
|
85
|
+
|
|
86
|
+
Remove a worktree with safety checks.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
wt remove --dirName=old-feature
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Safety checks:**
|
|
93
|
+
- Blocks if uncommitted changes exist
|
|
94
|
+
- Blocks if unpushed commits exist
|
|
95
|
+
- Prevents removing the main repository
|
|
96
|
+
|
|
97
|
+
### `wt sync-env` (alias: `wt sync`)
|
|
98
|
+
|
|
99
|
+
Sync `.env*` files from current worktree to other worktrees.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
wt sync-env --to=other-worktree # sync to specific worktree
|
|
103
|
+
wt sync-env --all # sync to ALL other worktrees
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## How .env Copying Works
|
|
107
|
+
|
|
108
|
+
When creating a worktree or syncing, the tool:
|
|
109
|
+
|
|
110
|
+
1. Finds all directories containing `package.json` or `project.json` (excluding `node_modules`)
|
|
111
|
+
2. Copies all `.env*` files (`.env`, `.env.local`, `.env.development`, etc.) to the same relative path in the target worktree
|
|
112
|
+
|
|
113
|
+
This is especially useful for monorepos where environment files are scattered across multiple projects.
|
|
114
|
+
|
|
115
|
+
## Examples
|
|
116
|
+
|
|
117
|
+
### Typical Workflow
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Create a new feature worktree
|
|
121
|
+
wt add --dirName=my-feature --branchName=feature/awesome
|
|
122
|
+
|
|
123
|
+
# ... work on the feature, add new .env variables ...
|
|
124
|
+
|
|
125
|
+
# Sync your .env changes to all other worktrees
|
|
126
|
+
wt sync-env --all
|
|
127
|
+
|
|
128
|
+
# Find a specific worktree
|
|
129
|
+
wt find --search=awesome
|
|
130
|
+
|
|
131
|
+
# Clean up when done
|
|
132
|
+
wt remove --dirName=my-feature
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Use with npm scripts
|
|
136
|
+
|
|
137
|
+
Add to your `package.json`:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"scripts": {
|
|
142
|
+
"wt:add": "wt add",
|
|
143
|
+
"wt:list": "wt list",
|
|
144
|
+
"wt:sync": "wt sync-env --all"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
|
152
|
+
|
|
153
|
+
## Author
|
|
154
|
+
|
|
155
|
+
Avi Weiss
|
package/dist/add.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { p as parseArgs, e as exitWithError, i as isBranchCheckedOut, g as getMainRepoRoot, c as copyEnvFiles } from "./utils-D1mQhe1g.js";
|
|
4
|
+
const args = parseArgs(process.argv.slice(2));
|
|
5
|
+
const dirName = args["dirName"];
|
|
6
|
+
const branchName = args["branchName"];
|
|
7
|
+
if (!dirName) {
|
|
8
|
+
exitWithError("Missing --dirName=...");
|
|
9
|
+
}
|
|
10
|
+
const branch = branchName || dirName;
|
|
11
|
+
const { checkedOut, location } = isBranchCheckedOut(branch);
|
|
12
|
+
if (checkedOut) {
|
|
13
|
+
console.error(`Branch '${branch}' is already checked out at:`);
|
|
14
|
+
console.error(location);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const targetPath = `../${dirName}`;
|
|
18
|
+
try {
|
|
19
|
+
console.log(`Creating worktree at ${targetPath} for branch ${branch}...`);
|
|
20
|
+
execSync(`git worktree add ${targetPath} ${branch}`, { stdio: "inherit" });
|
|
21
|
+
} catch {
|
|
22
|
+
exitWithError("Failed to create worktree");
|
|
23
|
+
}
|
|
24
|
+
console.log("\nCopying .env files...");
|
|
25
|
+
const mainRoot = getMainRepoRoot();
|
|
26
|
+
const count = copyEnvFiles(mainRoot, `../${dirName}`);
|
|
27
|
+
console.log(`Done copying ${count} .env file(s)`);
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { dirname, resolve } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const command = args[0];
|
|
7
|
+
const commands = {
|
|
8
|
+
add: "add.js",
|
|
9
|
+
list: "list.js",
|
|
10
|
+
ls: "list.js",
|
|
11
|
+
find: "find.js",
|
|
12
|
+
search: "find.js",
|
|
13
|
+
rename: "rename.js",
|
|
14
|
+
mv: "rename.js",
|
|
15
|
+
remove: "remove.js",
|
|
16
|
+
rm: "remove.js",
|
|
17
|
+
"sync-env": "sync-env.js",
|
|
18
|
+
sync: "sync-env.js"
|
|
19
|
+
};
|
|
20
|
+
function showHelp() {
|
|
21
|
+
console.log(`
|
|
22
|
+
git-worktree-utils - Safe git worktree management CLI
|
|
23
|
+
|
|
24
|
+
Usage: wt <command> [options]
|
|
25
|
+
|
|
26
|
+
Commands:
|
|
27
|
+
add Create a new worktree (with .env file copying)
|
|
28
|
+
list, ls List all worktrees
|
|
29
|
+
find Search worktrees by name
|
|
30
|
+
rename, mv Rename a worktree directory
|
|
31
|
+
remove, rm Remove a worktree (with safety checks)
|
|
32
|
+
sync-env Sync .env files to other worktrees
|
|
33
|
+
|
|
34
|
+
Options for 'add':
|
|
35
|
+
--dirName=<name> Directory name for the worktree (required)
|
|
36
|
+
--branchName=<name> Branch name (defaults to dirName)
|
|
37
|
+
|
|
38
|
+
Options for 'find':
|
|
39
|
+
--search=<term> Search term (case-insensitive)
|
|
40
|
+
|
|
41
|
+
Options for 'rename':
|
|
42
|
+
--oldDirName=<name> Current directory name (required)
|
|
43
|
+
--newDirName=<name> New directory name (required)
|
|
44
|
+
|
|
45
|
+
Options for 'remove':
|
|
46
|
+
--dirName=<name> Directory name to remove (required)
|
|
47
|
+
|
|
48
|
+
Options for 'sync-env':
|
|
49
|
+
--to=<dirName> Sync to specific worktree
|
|
50
|
+
--all Sync to all other worktrees
|
|
51
|
+
|
|
52
|
+
Safety Features:
|
|
53
|
+
- Blocks operations on main repository
|
|
54
|
+
- Blocks remove/rename with uncommitted changes
|
|
55
|
+
- Blocks remove/rename with unpushed commits
|
|
56
|
+
- Checks if branch is already checked out elsewhere
|
|
57
|
+
- Automatically copies .env files when creating worktrees
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
wt add --dirName=my-feature --branchName=feature/my-feature
|
|
61
|
+
wt add --dirName=bugfix-123
|
|
62
|
+
wt find --search=feature
|
|
63
|
+
wt sync-env --all
|
|
64
|
+
wt remove --dirName=old-feature
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
67
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
68
|
+
showHelp();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
const scriptFile = commands[command];
|
|
72
|
+
if (!scriptFile) {
|
|
73
|
+
console.error(`Unknown command: ${command}`);
|
|
74
|
+
console.error('Run "wt help" for usage information');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
78
|
+
const scriptPath = resolve(__dirname$1, scriptFile);
|
|
79
|
+
const forwardArgs = args.slice(1).join(" ");
|
|
80
|
+
try {
|
|
81
|
+
execSync(`node "${scriptPath}" ${forwardArgs}`, { stdio: "inherit" });
|
|
82
|
+
} catch (error) {
|
|
83
|
+
process.exit(error.status || 1);
|
|
84
|
+
}
|
package/dist/find.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { p as parseArgs } from "./utils-D1mQhe1g.js";
|
|
4
|
+
const args = parseArgs(process.argv.slice(2));
|
|
5
|
+
const search = args["search"];
|
|
6
|
+
const list = execSync("git worktree list", { encoding: "utf-8" }).trim();
|
|
7
|
+
if (!search) {
|
|
8
|
+
console.log(list);
|
|
9
|
+
process.exit(0);
|
|
10
|
+
}
|
|
11
|
+
const lines = list.split("\n");
|
|
12
|
+
const matches = lines.filter((line) => line.toLowerCase().includes(search.toLowerCase()));
|
|
13
|
+
if (matches.length === 0) {
|
|
14
|
+
console.error(`No worktree found matching: ${search}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
console.log(matches.join("\n"));
|
package/dist/list.js
ADDED
package/dist/remove.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { existsSync, renameSync, rmSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { p as parseArgs, e as exitWithError, a as isMainRepository, h as hasUncommittedChanges, b as hasUnpushedCommits } from "./utils-D1mQhe1g.js";
|
|
6
|
+
const args = parseArgs(process.argv.slice(2));
|
|
7
|
+
const dirName = args["dirName"];
|
|
8
|
+
if (!dirName) {
|
|
9
|
+
exitWithError("Missing --dirName=...");
|
|
10
|
+
}
|
|
11
|
+
const path = join("..", dirName);
|
|
12
|
+
if (!existsSync(path)) {
|
|
13
|
+
exitWithError(`Worktree not found: ${path}`);
|
|
14
|
+
}
|
|
15
|
+
if (isMainRepository(path)) {
|
|
16
|
+
exitWithError("Cannot remove: that path is the main repository");
|
|
17
|
+
}
|
|
18
|
+
if (hasUncommittedChanges(path)) {
|
|
19
|
+
exitWithError("Cannot remove: uncommitted changes in worktree");
|
|
20
|
+
}
|
|
21
|
+
if (hasUnpushedCommits(path)) {
|
|
22
|
+
exitWithError("Cannot remove: unpushed commits in worktree");
|
|
23
|
+
}
|
|
24
|
+
const tmpDir = `/tmp/worktree_${process.pid}`;
|
|
25
|
+
renameSync(path, tmpDir);
|
|
26
|
+
setImmediate(() => {
|
|
27
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
28
|
+
});
|
|
29
|
+
execSync("git worktree prune", { stdio: "inherit" });
|
|
30
|
+
console.log(`Removed worktree: ${dirName}`);
|
package/dist/rename.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { existsSync, renameSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { p as parseArgs, e as exitWithError, a as isMainRepository, h as hasUncommittedChanges, b as hasUnpushedCommits } from "./utils-D1mQhe1g.js";
|
|
6
|
+
const args = parseArgs(process.argv.slice(2));
|
|
7
|
+
const oldDirName = args["oldDirName"];
|
|
8
|
+
const newDirName = args["newDirName"];
|
|
9
|
+
if (!oldDirName) {
|
|
10
|
+
exitWithError("Missing --oldDirName=...");
|
|
11
|
+
}
|
|
12
|
+
if (!newDirName) {
|
|
13
|
+
exitWithError("Missing --newDirName=...");
|
|
14
|
+
}
|
|
15
|
+
const pathOld = join("..", oldDirName);
|
|
16
|
+
const pathNew = join("..", newDirName);
|
|
17
|
+
if (!existsSync(pathOld)) {
|
|
18
|
+
exitWithError(`Worktree not found: ${pathOld}`);
|
|
19
|
+
}
|
|
20
|
+
if (existsSync(pathNew)) {
|
|
21
|
+
exitWithError(`Destination already exists: ${pathNew}`);
|
|
22
|
+
}
|
|
23
|
+
if (isMainRepository(pathOld)) {
|
|
24
|
+
exitWithError("Cannot rename: that path is the main repository");
|
|
25
|
+
}
|
|
26
|
+
if (hasUncommittedChanges(pathOld)) {
|
|
27
|
+
exitWithError("Cannot rename: uncommitted changes in worktree");
|
|
28
|
+
}
|
|
29
|
+
if (hasUnpushedCommits(pathOld)) {
|
|
30
|
+
exitWithError("Cannot rename: unpushed commits in worktree");
|
|
31
|
+
}
|
|
32
|
+
renameSync(pathOld, pathNew);
|
|
33
|
+
const absPathNew = execSync(`cd "${pathNew}" && pwd`, { encoding: "utf-8" }).trim();
|
|
34
|
+
execSync(`git worktree repair "${absPathNew}"`, { stdio: "inherit" });
|
|
35
|
+
console.log(`Renamed worktree from ${oldDirName} to ${newDirName}`);
|
package/dist/sync-env.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { p as parseArgs, d as getCurrentWorktreeRoot, e as exitWithError, f as getWorktreeListPaths, c as copyEnvFiles } from "./utils-D1mQhe1g.js";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
const args = parseArgs(process.argv.slice(2));
|
|
6
|
+
const to = args["to"];
|
|
7
|
+
const all = args["all"];
|
|
8
|
+
if (!to && !all) {
|
|
9
|
+
console.log("Usage:");
|
|
10
|
+
console.log(" npm run worktree:sync-env -- --to=<dirName> # sync to specific worktree");
|
|
11
|
+
console.log(" npm run worktree:sync-env -- --all # sync to all other worktrees");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const currentRoot = getCurrentWorktreeRoot();
|
|
15
|
+
let targets = [];
|
|
16
|
+
if (to) {
|
|
17
|
+
const targetPath = join("..", to);
|
|
18
|
+
if (!existsSync(targetPath)) {
|
|
19
|
+
exitWithError(`Worktree not found: ${targetPath}`);
|
|
20
|
+
}
|
|
21
|
+
const absPath = require("path").resolve(targetPath);
|
|
22
|
+
targets = [absPath];
|
|
23
|
+
} else if (all) {
|
|
24
|
+
targets = getWorktreeListPaths().filter((p) => p !== currentRoot);
|
|
25
|
+
}
|
|
26
|
+
if (targets.length === 0) {
|
|
27
|
+
console.log("No target worktrees found");
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
console.log("Syncing .env files from current worktree...\n");
|
|
31
|
+
let totalCount = 0;
|
|
32
|
+
for (const target of targets) {
|
|
33
|
+
console.log(`To: ${target}`);
|
|
34
|
+
const count = copyEnvFiles(currentRoot, target);
|
|
35
|
+
totalCount += count;
|
|
36
|
+
console.log("");
|
|
37
|
+
}
|
|
38
|
+
console.log(`Done syncing ${totalCount} .env file(s) to ${targets.length} worktree(s)`);
|