@desplega.ai/wts 0.1.4 → 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.
Files changed (2) hide show
  1. package/dist/index.js +134 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1877,7 +1877,7 @@ var {
1877
1877
  // package.json
1878
1878
  var package_default = {
1879
1879
  name: "@desplega.ai/wts",
1880
- version: "0.1.4",
1880
+ version: "0.2.0",
1881
1881
  description: "Git worktree manager with tmux integration",
1882
1882
  type: "module",
1883
1883
  bin: {
@@ -2098,6 +2098,10 @@ async function removeWorktree(path, force = false, cwd) {
2098
2098
  async function pruneWorktrees(cwd) {
2099
2099
  await Bun.$`git worktree prune`.cwd(cwd ?? process.cwd());
2100
2100
  }
2101
+ async function deleteBranch(branch, force = false, cwd) {
2102
+ const flag = force ? "-D" : "-d";
2103
+ await Bun.$`git branch ${flag} ${branch}`.cwd(cwd ?? process.cwd());
2104
+ }
2101
2105
  function generateWorktreePath(baseDir, alias) {
2102
2106
  const dirName = generateWorktreeDirName(alias);
2103
2107
  return join(baseDir, dirName);
@@ -2610,7 +2614,8 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
2610
2614
  var source_default = chalk;
2611
2615
 
2612
2616
  // src/config/local.ts
2613
- import { join as join3 } from "node:path";
2617
+ import { homedir as homedir3 } from "node:os";
2618
+ import { isAbsolute, join as join3 } from "node:path";
2614
2619
 
2615
2620
  // src/config/global.ts
2616
2621
  import { homedir as homedir2 } from "node:os";
@@ -2620,7 +2625,7 @@ import { join as join2 } from "node:path";
2620
2625
  var DEFAULT_GLOBAL_CONFIG = {
2621
2626
  projects: {},
2622
2627
  defaults: {
2623
- worktreeDir: ".worktrees",
2628
+ worktreeDir: "~/.worktrees",
2624
2629
  tmuxWindowTemplate: "{project}-{alias}",
2625
2630
  autoTmux: false,
2626
2631
  autoClaude: false
@@ -2704,7 +2709,14 @@ async function resolveConfig(gitRoot) {
2704
2709
  };
2705
2710
  }
2706
2711
  function getWorktreeBaseDir(config) {
2707
- return join3(config.gitRoot, config.worktreeDir, config.projectName);
2712
+ let baseDir = config.worktreeDir;
2713
+ if (baseDir.startsWith("~")) {
2714
+ baseDir = join3(homedir3(), baseDir.slice(1));
2715
+ }
2716
+ if (isAbsolute(baseDir)) {
2717
+ return join3(baseDir, config.projectName);
2718
+ }
2719
+ return join3(config.gitRoot, baseDir, config.projectName);
2708
2720
  }
2709
2721
 
2710
2722
  // src/utils/prompts.ts
@@ -2799,7 +2811,7 @@ async function categorizeWorktrees(worktrees, gitRoot, options) {
2799
2811
  }
2800
2812
  return { merged, unmerged, stale, active };
2801
2813
  }
2802
- var cleanupCommand = new Command2("cleanup").description("Remove merged or stale worktrees").option("--dry-run", "Show what would be removed without removing").option("-f, --force", "Force removal without confirmation").option("--older-than <days>", "Include worktrees older than N days").option("--unmerged", "Include all unmerged worktrees").action(async (options) => {
2814
+ var cleanupCommand = new Command2("cleanup").description("Remove merged or stale worktrees").option("--dry-run", "Show what would be removed without removing").option("-f, --force", "Force removal without confirmation").option("--older-than <days>", "Include worktrees older than N days").option("--unmerged", "Include all unmerged worktrees").option("--delete-branches", "Also delete the associated branches").action(async (options) => {
2803
2815
  const gitRoot = await getGitRoot();
2804
2816
  if (!gitRoot) {
2805
2817
  console.error(source_default.red("Error: Not in a git repository"));
@@ -2851,24 +2863,38 @@ var cleanupCommand = new Command2("cleanup").description("Remove merged or stale
2851
2863
  }
2852
2864
  console.log();
2853
2865
  }
2866
+ if (options.deleteBranches) {
2867
+ console.log(source_default.dim("(branches will also be deleted)"));
2868
+ }
2854
2869
  if (options.dryRun) {
2855
2870
  console.log(source_default.dim("(dry run - no changes made)"));
2856
2871
  return;
2857
2872
  }
2858
2873
  if (!options.force) {
2859
- const shouldProceed = await confirm(`Remove ${toRemove.length} worktree(s)?`, false);
2874
+ const message = options.deleteBranches ? `Remove ${toRemove.length} worktree(s) and their branches?` : `Remove ${toRemove.length} worktree(s)?`;
2875
+ const shouldProceed = await confirm(message, false);
2860
2876
  if (!shouldProceed) {
2861
2877
  console.log(source_default.dim("Cancelled"));
2862
2878
  return;
2863
2879
  }
2864
2880
  }
2865
- let removed = 0;
2881
+ let removedWorktrees = 0;
2882
+ let removedBranches = 0;
2866
2883
  let failed = 0;
2867
2884
  for (const wt of toRemove) {
2868
2885
  try {
2869
2886
  console.log(source_default.dim(`Removing ${wt.alias ?? wt.branch}...`));
2870
2887
  await removeWorktree(wt.path, true, gitRoot);
2871
- removed++;
2888
+ removedWorktrees++;
2889
+ if (options.deleteBranches && wt.branch && wt.branch !== "detached") {
2890
+ try {
2891
+ await deleteBranch(wt.branch, true, gitRoot);
2892
+ removedBranches++;
2893
+ } catch (error) {
2894
+ console.error(source_default.yellow(` Warning: Could not delete branch ${wt.branch}:`));
2895
+ console.error(source_default.dim(` ${error instanceof Error ? error.message : String(error)}`));
2896
+ }
2897
+ }
2872
2898
  } catch (error) {
2873
2899
  console.error(source_default.red(` Failed to remove ${wt.alias ?? wt.branch}:`));
2874
2900
  console.error(source_default.dim(` ${error instanceof Error ? error.message : String(error)}`));
@@ -2876,8 +2902,11 @@ var cleanupCommand = new Command2("cleanup").description("Remove merged or stale
2876
2902
  }
2877
2903
  }
2878
2904
  console.log();
2879
- if (removed > 0) {
2880
- console.log(source_default.green(`Removed ${removed} worktree(s)`));
2905
+ if (removedWorktrees > 0) {
2906
+ console.log(source_default.green(`Removed ${removedWorktrees} worktree(s)`));
2907
+ }
2908
+ if (removedBranches > 0) {
2909
+ console.log(source_default.green(`Deleted ${removedBranches} branch(es)`));
2881
2910
  }
2882
2911
  if (failed > 0) {
2883
2912
  console.log(source_default.red(`Failed to remove ${failed} worktree(s)`));
@@ -3213,8 +3242,8 @@ Next steps:`));
3213
3242
  });
3214
3243
  async function promptForConfig(existing) {
3215
3244
  const config = {};
3216
- const worktreeDir = await prompt("Worktree directory", existing?.worktreeDir ?? ".worktrees");
3217
- if (worktreeDir !== ".worktrees") {
3245
+ const worktreeDir = await prompt("Worktree directory", existing?.worktreeDir ?? "~/.worktrees");
3246
+ if (worktreeDir !== "~/.worktrees") {
3218
3247
  config.worktreeDir = worktreeDir;
3219
3248
  }
3220
3249
  const autoTmux = await confirm("Auto-open tmux window on create?", existing?.autoTmux ?? false);
@@ -3292,7 +3321,7 @@ async function listAllProjects(jsonOutput) {
3292
3321
  }
3293
3322
  function printWorktreeTable(worktrees, projectName) {
3294
3323
  console.log(source_default.bold.blue(`${projectName}`));
3295
- console.log(source_default.dim("".repeat(60)));
3324
+ console.log(source_default.dim("-".repeat(60)));
3296
3325
  if (worktrees.length === 0) {
3297
3326
  console.log(source_default.dim(" No worktrees"));
3298
3327
  return;
@@ -3307,6 +3336,97 @@ function printWorktreeTable(worktrees, projectName) {
3307
3336
  }
3308
3337
  }
3309
3338
 
3339
+ // src/commands/merge.ts
3340
+ var mergeCommand = new Command2("merge").description("Merge a worktree branch into main").argument("[alias]", "Alias of the worktree to merge").option("--no-cleanup", "Skip cleanup prompt").option("-f, --force", "Skip confirmations (except cleanup)").action(async (alias, options) => {
3341
+ const gitRoot = await getGitRoot();
3342
+ if (!gitRoot) {
3343
+ console.error(source_default.red("Error: Not in a git repository"));
3344
+ process.exit(1);
3345
+ }
3346
+ const config = await resolveConfig(gitRoot);
3347
+ const worktrees = await listWorktrees(gitRoot, config.projectName);
3348
+ let worktree;
3349
+ if (alias) {
3350
+ worktree = await findWorktreeByAlias(alias, gitRoot);
3351
+ if (!worktree) {
3352
+ console.error(source_default.red(`Error: No worktree found with alias "${alias}"`));
3353
+ process.exit(1);
3354
+ }
3355
+ } else {
3356
+ const nonMain = worktrees.filter((wt) => !wt.isMain);
3357
+ if (nonMain.length === 0) {
3358
+ console.error(source_default.yellow("No worktrees to merge"));
3359
+ process.exit(1);
3360
+ }
3361
+ worktree = await selectWorktree(worktrees, { excludeMain: true });
3362
+ if (!worktree) {
3363
+ console.log(source_default.dim("Cancelled"));
3364
+ return;
3365
+ }
3366
+ }
3367
+ if (worktree.isMain) {
3368
+ console.error(source_default.red("Error: Cannot merge the main worktree"));
3369
+ process.exit(1);
3370
+ }
3371
+ const defaultBranch = await getDefaultBranch(gitRoot);
3372
+ const branchToMerge = worktree.branch;
3373
+ console.log(source_default.bold(`
3374
+ Merging ${source_default.cyan(branchToMerge)} into ${source_default.cyan(defaultBranch)}
3375
+ `));
3376
+ if (!options.force) {
3377
+ const proceed = await confirm(`Switch to ${defaultBranch}?`, true);
3378
+ if (!proceed) {
3379
+ console.log(source_default.dim("Cancelled"));
3380
+ return;
3381
+ }
3382
+ }
3383
+ console.log(source_default.dim(`Switching to ${defaultBranch}...`));
3384
+ await Bun.$`git checkout ${defaultBranch}`.cwd(gitRoot);
3385
+ if (!options.force) {
3386
+ const proceed = await confirm(`Pull latest ${defaultBranch}?`, true);
3387
+ if (!proceed) {
3388
+ console.log(source_default.dim("Cancelled"));
3389
+ return;
3390
+ }
3391
+ }
3392
+ console.log(source_default.dim(`Pulling latest...`));
3393
+ await Bun.$`git pull`.cwd(gitRoot);
3394
+ if (!options.force) {
3395
+ const proceed = await confirm(`Merge ${branchToMerge}?`, true);
3396
+ if (!proceed) {
3397
+ console.log(source_default.dim("Cancelled"));
3398
+ return;
3399
+ }
3400
+ }
3401
+ console.log(source_default.dim(`Merging ${branchToMerge}...`));
3402
+ await Bun.$`git merge ${branchToMerge}`.cwd(gitRoot);
3403
+ if (!options.force) {
3404
+ const proceed = await confirm(`Push to origin?`, true);
3405
+ if (!proceed) {
3406
+ console.log(source_default.dim("Skipped push"));
3407
+ } else {
3408
+ console.log(source_default.dim(`Pushing...`));
3409
+ await Bun.$`git push`.cwd(gitRoot);
3410
+ }
3411
+ } else {
3412
+ console.log(source_default.dim(`Pushing...`));
3413
+ await Bun.$`git push`.cwd(gitRoot);
3414
+ }
3415
+ console.log(source_default.green(`
3416
+ ✓ Merged ${branchToMerge} into ${defaultBranch}`));
3417
+ if (options.cleanup !== false) {
3418
+ const cleanup = await confirm(`
3419
+ Clean up worktree and branch?`, false);
3420
+ if (cleanup) {
3421
+ console.log(source_default.dim(`Removing worktree...`));
3422
+ await removeWorktree(worktree.path, true, gitRoot);
3423
+ console.log(source_default.dim(`Deleting branch ${branchToMerge}...`));
3424
+ await deleteBranch(branchToMerge, true, gitRoot);
3425
+ console.log(source_default.green(`✓ Cleaned up`));
3426
+ }
3427
+ }
3428
+ });
3429
+
3310
3430
  // src/commands/pr.ts
3311
3431
  async function isGhAvailable() {
3312
3432
  try {
@@ -3585,6 +3705,7 @@ program3.addCommand(initCommand);
3585
3705
  program3.addCommand(listCommand);
3586
3706
  program3.addCommand(createCommand3);
3587
3707
  program3.addCommand(deleteCommand);
3708
+ program3.addCommand(mergeCommand);
3588
3709
  program3.addCommand(cdCommand);
3589
3710
  program3.addCommand(switchCommand);
3590
3711
  program3.addCommand(prCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/wts",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Git worktree manager with tmux integration",
5
5
  "type": "module",
6
6
  "bin": {