worktree-add 1.0.0 → 1.1.1

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/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # worktree-add
2
+
3
+ Create a Git worktree next to your current repo for a branch, copy useful local files, install deps, and open it in your editor.
4
+
5
+ ## What it does
6
+
7
+ Running `worktree-add <branch>` from inside a repo:
8
+
9
+ 1. Normalizes `<branch>` (supports `origin/foo`, `refs/heads/foo`, etc.).
10
+ 2. Refuses if that branch is already checked out in any worktree.
11
+ 3. Picks a destination next to your current repo: `../<repo>-<safe-branch>`.
12
+ 4. If the destination exists, asks before removing it with `rm -rf`.
13
+ 5. Fetches `origin/<branch>` when needed and creates a git worktree:
14
+ - reuses an existing local branch
15
+ - or creates a tracking branch from `origin/<branch>`
16
+ - or creates a new branch from the current `HEAD`
17
+ 6. Copies untracked / ignored files into the new worktree, skipping heavy stuff
18
+ (`node_modules`, `dist`, `.next`, caches, virtualenvs, etc.).
19
+ 7. Detects your package manager and installs dependencies with lockfile‑safe flags
20
+ (`npm ci`, `pnpm install --frozen-lockfile`, `yarn install --immutable`, etc.).
21
+ 8. If the project uses Next.js and supports it, runs `next typegen`.
22
+ 9. Opens the new worktree in your editor.
23
+
24
+ Your original checkout is left untouched.
25
+
26
+ ## Requirements
27
+
28
+ - Node.js ≥ 22.14.0
29
+ - Git with `git worktree` support
30
+ - POSIX‑like shell (uses `rm -rf` for cleaning the destination)
31
+
32
+ ## Install / run
33
+
34
+ You usually don’t need a global install.
35
+
36
+ ```bash
37
+ # inside /my/path/my-app
38
+ # one‑off
39
+ npx worktree-add feature/my-branch
40
+
41
+ # or install globally
42
+ pnpm add -g worktree-add # or: npm i -g worktree-add
43
+ worktree-add feature/my-branch
44
+ ```
45
+
46
+ Run it from anywhere inside an existing worktree of the repo.
47
+
48
+ ## Usage
49
+
50
+ ```bash
51
+ # inside /my/path/my-app
52
+ worktree-add <branch>
53
+ ```
54
+
55
+ Run this inside an existing worktree of your project—the tool discovers the repo root from your current directory and creates the sibling worktree next to it.
56
+
57
+ Example:
58
+
59
+ ```bash
60
+ # inside /my/path/my-app
61
+ # reuses a local branch, tracks origin/<branch> if it exists, otherwise creates a new branch from current HEAD
62
+ worktree-add feature/login-form
63
+ ```
64
+
65
+ A new branch from the current HEAD is created only when the branch does not already
66
+ exist locally or on `origin/`.
67
+
68
+ Destination directory (assuming repo named `my-app`):
69
+
70
+ ```text
71
+ /my/path/my-app # current checkout
72
+ /my/path/my-app-feature-login-form # new worktree for branch "feature/login-form"
73
+ ```
74
+
75
+ ## Editor control
76
+
77
+ By default the tool opens the new worktree in:
78
+
79
+ 1. `--editor <command>` if passed
80
+ 2. `WORKTREE_ADD_EDITOR` env var
81
+ 3. otherwise `code`
82
+
83
+ Only simple command names are allowed (no `;`, `&`, pipes, etc.) to avoid shell injection. Examples:
84
+
85
+ ```bash
86
+ worktree-add feature/foo -e code
87
+ WORKTREE_ADD_EDITOR=vim worktree-add feature/foo
88
+ ```
89
+
90
+ If launching the editor fails, the worktree still stays created and ready.
91
+
92
+ Tip: add a shell helper with your preferred editor in your shell profile. Example snippet to add:
93
+
94
+ ```bash
95
+ worktree-add() { WORKTREE_ADD_EDITOR=cursor command worktree-add "$@" }
96
+ ```
97
+
98
+ Add it to your shell profile:
99
+
100
+ - zsh: `~/.zshrc` or `~/.zprofile`
101
+ - bash: `~/.bashrc` or `~/.bash_profile`
102
+ - fish: `~/.config/fish/config.fish`
103
+
104
+ ## License
105
+
106
+ MIT
package/dist/cli.d.ts CHANGED
@@ -6,4 +6,10 @@
6
6
  * Note: the destination is placed next to whichever worktree you run this from,
7
7
  * not necessarily the original "main" checkout.
8
8
  */
9
+ interface ResolveEditorInput {
10
+ optionEditor?: string;
11
+ environmentEditor?: string;
12
+ }
13
+ export declare function resolveEditor({ optionEditor, environmentEditor, }?: ResolveEditorInput): string;
14
+ export declare function isEditorCommandSafe(editor: string): boolean;
9
15
  export {};
package/dist/cli.js CHANGED
@@ -15,7 +15,23 @@ import { copyUntrackedFiles } from "./worktree/untracked-file-copy.js";
15
15
  import { fetchRemoteBranch, createWorktree } from "./git/worktree-creation.js";
16
16
  import { handleExistingDirectory } from "./worktree/destination-directory.js";
17
17
  import { setupProject } from "./project/setup.js";
18
- async function main(branchRaw) {
18
+ export function resolveEditor({ optionEditor, environmentEditor, } = {}) {
19
+ const normalizedOption = optionEditor?.trim();
20
+ const normalizedEnvironment = environmentEditor?.trim();
21
+ return normalizedOption || normalizedEnvironment || "code";
22
+ }
23
+ export function isEditorCommandSafe(editor) {
24
+ const normalized = editor.normalize("NFKC");
25
+ const trimmed = normalized.trim();
26
+ // Reject empty or excessively long commands
27
+ if (trimmed.length === 0 || trimmed.length > 256) {
28
+ return false;
29
+ }
30
+ // Reject common shell metacharacters, whitespace, and control characters to prevent injection
31
+ const unsafeCharacters = /[\\;&|`$(){}<>\s]|\p{Cc}/u;
32
+ return !unsafeCharacters.test(trimmed);
33
+ }
34
+ async function main(branchRaw, options) {
19
35
  const branch = normalizeBranchName(branchRaw);
20
36
  // Prevent attempting to add a worktree for a branch that is already checked out
21
37
  const existingWorktree = findWorktreeByBranchName(branch);
@@ -39,22 +55,40 @@ async function main(branchRaw) {
39
55
  await copyUntrackedFiles(repoRoot, destinationDirectory);
40
56
  // Step 4: Install dependencies and run project-specific setup
41
57
  await setupProject(destinationDirectory);
42
- // Step 6: Open the new worktree in Cursor
43
- console.log("➤ Opening Cursor …");
44
- spawnSync("cursor", [destinationDirectory], { stdio: "inherit" });
58
+ // Step 5: Open the new worktree in the editor
59
+ const editor = resolveEditor({
60
+ optionEditor: options.editor,
61
+ environmentEditor: process.env.WORKTREE_ADD_EDITOR,
62
+ });
63
+ // Validate editor command to prevent injection attacks
64
+ if (!isEditorCommandSafe(editor)) {
65
+ exitWithMessage("Invalid editor command: shell metacharacters not allowed.\n" +
66
+ "Please use a simple editor name (e.g., 'code', 'cursor', 'vim').");
67
+ }
68
+ console.log(`➤ Opening ${editor} …`);
69
+ const result = spawnSync(editor, [destinationDirectory], {
70
+ stdio: "inherit",
71
+ });
72
+ if (result.error) {
73
+ console.error(`Failed to open ${editor}: ${result.error.message}`);
74
+ console.error("The worktree was created successfully, but the editor could not be opened.");
75
+ }
45
76
  }
46
77
  const program = new Command()
47
78
  .name(packageJson.name)
48
79
  .description("Create or reuse a Git worktree for a branch as a sibling of the current worktree")
49
80
  .version(packageJson.version)
50
81
  .argument("<branch>", "branch name for the worktree")
51
- .action(async (branch) => {
82
+ .option("-e, --editor <command>", "Editor to open the worktree with (default: WORKTREE_ADD_EDITOR env var or 'code')")
83
+ .action(async (branch, options) => {
52
84
  try {
53
- await main(branch);
85
+ await main(branch, options);
54
86
  }
55
87
  catch (error) {
56
88
  console.error(error);
57
89
  process.exitCode = 1;
58
90
  }
59
91
  });
60
- program.parse();
92
+ if (!process.env.VITEST) {
93
+ program.parse();
94
+ }
package/package.json CHANGED
@@ -2,12 +2,16 @@
2
2
  "name": "worktree-add",
3
3
  "author": "Łukasz Jerciński",
4
4
  "license": "MIT",
5
- "version": "1.0.0",
6
- "description": "",
5
+ "version": "1.1.1",
6
+ "description": "Create a Git worktree next to your current repo for a branch, copy useful local files, install deps, and open it in your editor.",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/Jercik/worktree-add.git"
10
10
  },
11
+ "homepage": "https://github.com/Jercik/worktree-add#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/Jercik/worktree-add/issues"
14
+ },
11
15
  "type": "module",
12
16
  "bin": {
13
17
  "worktree-add": "bin/worktree-add"
@@ -35,10 +39,17 @@
35
39
  "knip": "knip",
36
40
  "fta:check": "fta-check --threshold 55"
37
41
  },
38
- "keywords": [],
42
+ "keywords": [
43
+ "git",
44
+ "worktree",
45
+ "cli",
46
+ "workflow",
47
+ "git-worktree",
48
+ "development"
49
+ ],
39
50
  "packageManager": "pnpm@10.22.0",
40
51
  "engines": {
41
- "node": ">=22.0.0"
52
+ "node": ">=22.14.0"
42
53
  },
43
54
  "dependencies": {
44
55
  "commander": "^14.0.2",