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 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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from "child_process";
3
+ const output = execSync("git worktree list", { encoding: "utf-8" });
4
+ console.log(output.trim());
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}`);
@@ -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)`);