branch-nexus 1.3.0 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # BranchNexus
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/branchnexus.svg)](https://www.npmjs.com/package/branchnexus)
4
- [![Node.js Version](https://img.shields.io/node/v/branchnexus.svg)](https://nodejs.org)
3
+ [![npm version](https://img.shields.io/npm/v/branch-nexus.svg)](https://www.npmjs.com/package/branch-nexus)
4
+ [![Node.js Version](https://img.shields.io/node/v/branch-nexus.svg)](https://nodejs.org)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
7
  **BranchNexus**, birden fazla Git branch'i aynı anda izole worktree'lerde açıp tmux panelleriyle yöneten bir CLI aracıdır.
@@ -25,13 +25,13 @@
25
25
  ## Kurulum
26
26
 
27
27
  ```bash
28
- npm install -g branchnexus
28
+ npm install -g branch-nexus
29
29
  ```
30
30
 
31
31
  Veya npx ile doğrudan çalıştırın:
32
32
 
33
33
  ```bash
34
- npx branchnexus init
34
+ npx branch-nexus init
35
35
  ```
36
36
 
37
37
  ## Hızlı Başlangıç
package/dist/cli.js CHANGED
@@ -707,11 +707,19 @@ async function runCommand(command, options = {}) {
707
707
  stderr: result.stderr
708
708
  };
709
709
  } catch (error) {
710
- const execaError = error;
710
+ if (error instanceof Error && "exitCode" in error) {
711
+ const execaError = error;
712
+ return {
713
+ exitCode: execaError.exitCode ?? 1,
714
+ stdout: execaError.stdout ?? "",
715
+ stderr: execaError.stderr ?? execaError.message
716
+ };
717
+ }
718
+ const message = error instanceof Error ? error.message : String(error);
711
719
  return {
712
- exitCode: execaError.exitCode ?? 1,
713
- stdout: execaError.stdout ?? "",
714
- stderr: execaError.stderr ?? execaError.message
720
+ exitCode: 1,
721
+ stdout: "",
722
+ stderr: message
715
723
  };
716
724
  }
717
725
  }
@@ -724,20 +732,38 @@ async function runCommandViaWSL(distribution, command, options = {}) {
724
732
  // ts-src/tmux/session.ts
725
733
  init_logger();
726
734
  init_errors();
735
+
736
+ // ts-src/utils/validators.ts
737
+ init_esm_shims();
738
+ function hasDistribution(dist) {
739
+ return dist !== void 0 && dist !== "";
740
+ }
741
+ function normalizePath(path3) {
742
+ return path3.replace(/\\/g, "/");
743
+ }
744
+ function posixPath(path3) {
745
+ let normalized = normalizePath(path3);
746
+ while (normalized.startsWith("//")) {
747
+ normalized = normalized.slice(1);
748
+ }
749
+ return normalized;
750
+ }
751
+
752
+ // ts-src/tmux/session.ts
727
753
  async function startSession(sessionName, commands, distribution) {
728
754
  const isWindows = detectPlatform() === "windows" /* WINDOWS */;
729
755
  logger.debug(`Starting tmux session: ${sessionName}`);
730
756
  for (const command of commands) {
731
- const wrappedCommand = isWindows && distribution !== void 0 && distribution !== "" ? buildWslCommand(distribution, command) : command;
757
+ const wrappedCommand = isWindows && hasDistribution(distribution) ? buildWslCommand(distribution, command) : command;
732
758
  logger.debug(`Executing tmux command: ${wrappedCommand.join(" ")}`);
733
- const result = isWindows && distribution !== void 0 && distribution !== "" ? await runCommandViaWSL(distribution, command) : await runCommand(wrappedCommand);
759
+ const result = isWindows && hasDistribution(distribution) ? await runCommandViaWSL(distribution, command) : await runCommand(wrappedCommand);
734
760
  if (result.exitCode !== 0) {
735
761
  const stderr = result.stderr.trim();
736
762
  const isNewSession = command[0] === "tmux" && command[1] === "new-session";
737
763
  if (isNewSession && stderr.toLowerCase().includes("duplicate session")) {
738
764
  logger.warn(`tmux session already exists: ${sessionName}, replacing`);
739
765
  await killSession(sessionName, distribution);
740
- const retryResult = isWindows && distribution !== void 0 && distribution !== "" ? await runCommandViaWSL(distribution, command) : await runCommand(wrappedCommand);
766
+ const retryResult = isWindows && hasDistribution(distribution) ? await runCommandViaWSL(distribution, command) : await runCommand(wrappedCommand);
741
767
  if (retryResult.exitCode !== 0) {
742
768
  throw new BranchNexusError(
743
769
  "Failed to initialize tmux session.",
@@ -760,7 +786,7 @@ async function killSession(sessionName, distribution) {
760
786
  const isWindows = detectPlatform() === "windows" /* WINDOWS */;
761
787
  const command = ["tmux", "kill-session", "-t", sessionName];
762
788
  logger.debug(`Killing tmux session: ${sessionName}`);
763
- const result = isWindows && distribution !== void 0 && distribution !== "" ? await runCommandViaWSL(distribution, command) : await runCommand(command);
789
+ const result = isWindows && hasDistribution(distribution) ? await runCommandViaWSL(distribution, command) : await runCommand(command);
764
790
  if (result.exitCode !== 0) {
765
791
  logger.warn(`Failed to kill session: ${result.stderr}`);
766
792
  }
@@ -769,7 +795,7 @@ async function listSessions(distribution) {
769
795
  const isWindows = detectPlatform() === "windows" /* WINDOWS */;
770
796
  const command = ["tmux", "list-sessions", "-F", "#{session_name}"];
771
797
  try {
772
- const result = isWindows && distribution !== void 0 && distribution !== "" ? await runCommandViaWSL(distribution, command) : await runCommand(command);
798
+ const result = isWindows && hasDistribution(distribution) ? await runCommandViaWSL(distribution, command) : await runCommand(command);
773
799
  if (result.exitCode !== 0) {
774
800
  return [];
775
801
  }
@@ -825,12 +851,15 @@ var WorktreeManager = class {
825
851
  const targetPath = this.commandPath(target);
826
852
  const pruneCmd = ["git", "-C", repoPath, "worktree", "prune"];
827
853
  try {
828
- if (distribution !== void 0 && distribution !== "") {
854
+ if (hasDistribution(distribution)) {
829
855
  await runCommandViaWSL(distribution, pruneCmd);
830
856
  } else {
831
857
  await runCommand(pruneCmd);
832
858
  }
833
- } catch {
859
+ } catch (error) {
860
+ logger.debug(
861
+ `Prune failed (non-critical): ${error instanceof Error ? error.message : String(error)}`
862
+ );
834
863
  }
835
864
  const existingWorktrees = await this.getWorktreesForBranch(
836
865
  assignment.repoPath,
@@ -856,15 +885,18 @@ var WorktreeManager = class {
856
885
  this.commandPath(existingPath)
857
886
  ];
858
887
  try {
859
- if (distribution !== void 0 && distribution !== "") {
888
+ if (hasDistribution(distribution)) {
860
889
  await runCommandViaWSL(distribution, removeCmd);
861
890
  } else {
862
891
  await runCommand(removeCmd);
863
892
  }
864
893
  logger.debug(`Removed stale worktree: ${existingPath}`);
865
- } catch {
894
+ } catch (removeError) {
895
+ logger.debug(
896
+ `Remove failed, trying prune: ${removeError instanceof Error ? removeError.message : String(removeError)}`
897
+ );
866
898
  const pruneCmd2 = ["git", "-C", repoPath, "worktree", "prune"];
867
- if (distribution !== void 0 && distribution !== "") {
899
+ if (hasDistribution(distribution)) {
868
900
  await runCommandViaWSL(distribution, pruneCmd2);
869
901
  } else {
870
902
  await runCommand(pruneCmd2);
@@ -874,7 +906,7 @@ var WorktreeManager = class {
874
906
  }
875
907
  const cmd = ["git", "-C", repoPath, "worktree", "add", targetPath, assignment.branch];
876
908
  try {
877
- if (distribution !== void 0 && distribution !== "") {
909
+ if (hasDistribution(distribution)) {
878
910
  await runCommandViaWSL(distribution, cmd);
879
911
  } else {
880
912
  await runCommand(cmd);
@@ -892,15 +924,6 @@ var WorktreeManager = class {
892
924
  );
893
925
  }
894
926
  }
895
- async getCurrentBranch(repoPath, distribution) {
896
- const cmd = ["git", "-C", repoPath, "branch", "--show-current"];
897
- try {
898
- const result = distribution !== void 0 && distribution !== "" ? await runCommandViaWSL(distribution, cmd) : await runCommand(cmd);
899
- return result.stdout.trim();
900
- } catch {
901
- return "";
902
- }
903
- }
904
927
  async materialize(assignments, distribution) {
905
928
  logger.debug(`Materializing ${assignments.length} worktree assignments`);
906
929
  const created = [];
@@ -912,7 +935,7 @@ var WorktreeManager = class {
912
935
  async checkDirty(worktree, distribution) {
913
936
  const cmd = ["git", "-C", this.commandPath(worktree.path), "status", "--porcelain"];
914
937
  try {
915
- const result = distribution !== void 0 && distribution !== "" ? await runCommandViaWSL(distribution, cmd) : await runCommand(cmd);
938
+ const result = hasDistribution(distribution) ? await runCommandViaWSL(distribution, cmd) : await runCommand(cmd);
916
939
  const dirty = result.stdout.trim().length > 0;
917
940
  logger.debug(`Dirty check path=${worktree.path} dirty=${dirty}`);
918
941
  return dirty;
@@ -945,7 +968,7 @@ var WorktreeManager = class {
945
968
  }
946
969
  cmd.push(worktreePath);
947
970
  try {
948
- if (options?.distribution !== void 0 && options.distribution !== "") {
971
+ if (hasDistribution(options?.distribution)) {
949
972
  await runCommandViaWSL(options.distribution, cmd);
950
973
  } else {
951
974
  await runCommand(cmd);
@@ -963,6 +986,14 @@ var WorktreeManager = class {
963
986
  }
964
987
  return removed;
965
988
  }
989
+ trackExisting(assignment, path3) {
990
+ logger.debug(
991
+ `Tracking existing worktree pane=${assignment.pane} branch=${assignment.branch} path=${path3}`
992
+ );
993
+ const managed = createManagedWorktree(assignment, path3);
994
+ this.managed.push(managed);
995
+ return managed;
996
+ }
966
997
  getManaged() {
967
998
  return [...this.managed];
968
999
  }
@@ -977,7 +1008,7 @@ var WorktreeManager = class {
977
1008
  async getWorktreesForBranch(repoPath, branch, distribution) {
978
1009
  const cmd = ["git", "-C", this.commandPath(repoPath), "worktree", "list", "--porcelain"];
979
1010
  try {
980
- const result = distribution !== void 0 && distribution !== "" ? await runCommandViaWSL(distribution, cmd) : await runCommand(cmd);
1011
+ const result = hasDistribution(distribution) ? await runCommandViaWSL(distribution, cmd) : await runCommand(cmd);
981
1012
  const worktrees = [];
982
1013
  let currentWorktree = "";
983
1014
  const expectedRef = branch.startsWith("refs/heads/") ? branch : `refs/heads/${branch}`;
@@ -994,7 +1025,10 @@ var WorktreeManager = class {
994
1025
  }
995
1026
  }
996
1027
  return worktrees;
997
- } catch {
1028
+ } catch (error) {
1029
+ logger.debug(
1030
+ `Failed to list worktrees for branch ${branch}: ${error instanceof Error ? error.message : String(error)}`
1031
+ );
998
1032
  return [];
999
1033
  }
1000
1034
  }
@@ -1005,19 +1039,6 @@ init_esm_shims();
1005
1039
  init_logger();
1006
1040
  init_errors();
1007
1041
 
1008
- // ts-src/utils/validators.ts
1009
- init_esm_shims();
1010
- function normalizePath(path3) {
1011
- return path3.replace(/\\/g, "/");
1012
- }
1013
- function posixPath(path3) {
1014
- let normalized = normalizePath(path3);
1015
- while (normalized.startsWith("//")) {
1016
- normalized = normalized.slice(1);
1017
- }
1018
- return normalized;
1019
- }
1020
-
1021
1042
  // ts-src/github/api.ts
1022
1043
  init_esm_shims();
1023
1044
  function parseGitHubUrl(url) {
@@ -1099,7 +1120,7 @@ var GitHubClient = class {
1099
1120
  };
1100
1121
 
1101
1122
  // ts-src/git/clone.ts
1102
- async function materializeRemoteBranch(repoPath, remoteBranch, _distribution) {
1123
+ async function materializeRemoteBranch(repoPath, remoteBranch) {
1103
1124
  const normalizedRepo = posixPath(repoPath);
1104
1125
  if (!remoteBranch.includes("/")) {
1105
1126
  throw new BranchNexusError(
@@ -1119,7 +1140,10 @@ async function materializeRemoteBranch(repoPath, remoteBranch, _distribution) {
1119
1140
  logger.debug(`Local branch already exists: ${localBranch}`);
1120
1141
  return localBranch;
1121
1142
  }
1122
- } catch {
1143
+ } catch (error) {
1144
+ logger.debug(
1145
+ `Branch check failed, continuing with creation: ${error instanceof Error ? error.message : String(error)}`
1146
+ );
1123
1147
  }
1124
1148
  try {
1125
1149
  await git.branch(["--track", localBranch, remoteBranch]);
@@ -1134,8 +1158,24 @@ async function materializeRemoteBranch(repoPath, remoteBranch, _distribution) {
1134
1158
  );
1135
1159
  }
1136
1160
  }
1137
- async function cloneRepository(url, targetPath) {
1138
- logger.debug(`Cloning repository ${url} to ${targetPath}`);
1161
+ async function cloneRepository(url, targetPath, token) {
1162
+ logger.debug(`Cloning repository to ${targetPath}`);
1163
+ if (token !== void 0 && token !== "" && url.startsWith("https://")) {
1164
+ const urlObj = new URL(url);
1165
+ const credentialHelper = `!f() { echo "username=x-access-token"; echo "password=${token}"; }; f`;
1166
+ try {
1167
+ await execa(
1168
+ "git",
1169
+ ["-c", `credential.${urlObj.origin}.helper=${credentialHelper}`, "clone", url, targetPath],
1170
+ { timeout: 12e4, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } }
1171
+ );
1172
+ logger.debug(`Cloned repository to ${targetPath}`);
1173
+ return;
1174
+ } catch (error) {
1175
+ const message = error instanceof Error ? error.message : String(error);
1176
+ throw new BranchNexusError(`Failed to clone repository: ${url}`, 5 /* GIT_ERROR */, message);
1177
+ }
1178
+ }
1139
1179
  const git = simpleGit2();
1140
1180
  try {
1141
1181
  await git.clone(url, targetPath);
@@ -1440,13 +1480,21 @@ function formatPreview(key, value) {
1440
1480
 
1441
1481
  // ts-src/prompts/github-browser.ts
1442
1482
  init_esm_shims();
1483
+
1484
+ // ts-src/utils/ansi.ts
1485
+ init_esm_shims();
1443
1486
  var ANSI_RE = /[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g;
1444
- var CLR = "\x1B[K";
1445
1487
  function stripAnsi(str) {
1446
- return str.replace(ANSI_RE, "").length;
1488
+ return str.replace(ANSI_RE, "");
1489
+ }
1490
+ function visibleLength(str) {
1491
+ return stripAnsi(str).length;
1447
1492
  }
1493
+
1494
+ // ts-src/prompts/github-browser.ts
1495
+ var CLR = "\x1B[K";
1448
1496
  function padToWidth(content, width) {
1449
- const pad = Math.max(0, width - stripAnsi(content));
1497
+ const pad = Math.max(0, width - visibleLength(content));
1450
1498
  return content + " ".repeat(pad);
1451
1499
  }
1452
1500
  function boxLine(content, width) {
@@ -1691,14 +1739,7 @@ var COLOR_OPTIONS = PALETTE_KEYS.map((key) => ({
1691
1739
  value: key,
1692
1740
  label: COLOR_PALETTES[key].label
1693
1741
  }));
1694
- var ANSI_RE2 = /[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g;
1695
1742
  var CLR2 = "\x1B[K";
1696
- function stripAnsi2(str) {
1697
- return str.replace(ANSI_RE2, "");
1698
- }
1699
- function visibleLength(str) {
1700
- return stripAnsi2(str).length;
1701
- }
1702
1743
  function padToWidth2(content, width) {
1703
1744
  const pad = Math.max(0, width - visibleLength(content));
1704
1745
  return content + " ".repeat(pad);
@@ -2418,7 +2459,7 @@ var HookRunner = class {
2418
2459
  logger.debug(`Executing hook command pane=${pane} command=${command}`);
2419
2460
  const isWindows = detectPlatform() === "windows" /* WINDOWS */;
2420
2461
  const cmd = ["bash", "-lc", command];
2421
- const finalCmd = isWindows && distribution !== void 0 && distribution !== "" ? buildWslCommand(distribution, cmd) : cmd;
2462
+ const finalCmd = isWindows && hasDistribution(distribution) ? buildWslCommand(distribution, cmd) : cmd;
2422
2463
  const result = await execa(finalCmd[0], finalCmd.slice(1), {
2423
2464
  timeout: this.timeoutSeconds * 1e3,
2424
2465
  reject: false,
@@ -2615,19 +2656,19 @@ async function ensureTmuxInstalled() {
2615
2656
  }
2616
2657
  let installCmd;
2617
2658
  try {
2618
- await execa("bash", ["-lc", "which apt-get"]);
2659
+ await execa("which", ["apt-get"]);
2619
2660
  installCmd = "apt-get update && apt-get install -y tmux";
2620
2661
  } catch {
2621
2662
  try {
2622
- await execa("bash", ["-lc", "which dnf"]);
2663
+ await execa("which", ["dnf"]);
2623
2664
  installCmd = "dnf install -y tmux";
2624
2665
  } catch {
2625
2666
  try {
2626
- await execa("bash", ["-lc", "which pacman"]);
2667
+ await execa("which", ["pacman"]);
2627
2668
  installCmd = "pacman -S --noconfirm tmux";
2628
2669
  } catch {
2629
2670
  try {
2630
- await execa("bash", ["-lc", "which apk"]);
2671
+ await execa("which", ["apk"]);
2631
2672
  installCmd = "apk add tmux";
2632
2673
  } catch {
2633
2674
  throw new BranchNexusError(
@@ -2641,29 +2682,28 @@ async function ensureTmuxInstalled() {
2641
2682
  }
2642
2683
  try {
2643
2684
  console.log(chalk9.dim(`\xC7al\u0131\u015Ft\u0131r\u0131l\u0131yor: sudo ${installCmd}`));
2644
- await execa("sudo", ["-n", "bash", "-lc", installCmd], { timeout: 18e4 });
2685
+ await execa("sudo", ["-n", "bash", "-c", installCmd], { timeout: 18e4 });
2645
2686
  console.log(chalk9.green("\u2705 tmux kuruldu!\n"));
2646
2687
  return;
2647
2688
  } catch {
2648
2689
  }
2649
- console.log(chalk9.cyan("Ba\u015Fka bir terminalde bu komutu \xE7al\u0131\u015Ft\u0131r\u0131n:\n"));
2650
- console.log(chalk9.bold.white(` sudo ${installCmd}
2651
- `));
2652
- console.log(chalk9.dim("tmux kurulumu bekleniyor..."));
2653
- for (let i = 0; i < 60; i++) {
2654
- await new Promise((r) => setTimeout(r, 2e3));
2690
+ try {
2691
+ console.log(chalk9.dim("Sudo \u015Fifresi gerekebilir:\n"));
2692
+ await execa("sudo", ["bash", "-c", installCmd], {
2693
+ stdio: "inherit",
2694
+ timeout: 18e4
2695
+ });
2655
2696
  const installed = await hasTmux();
2656
2697
  if (installed) {
2657
- console.log(chalk9.green("\n\u2705 tmux alg\u0131land\u0131!\n"));
2698
+ console.log(chalk9.green("\n\u2705 tmux kuruldu!\n"));
2658
2699
  return;
2659
2700
  }
2660
- process.stdout.write(".");
2701
+ } catch {
2661
2702
  }
2662
- console.log();
2663
2703
  throw new BranchNexusError(
2664
- "2 dakika i\xE7inde tmux kurulumu alg\u0131lanamad\u0131.",
2704
+ "tmux kurulumu ba\u015Far\u0131s\u0131z oldu.",
2665
2705
  6 /* TMUX_ERROR */,
2666
- "tmux kurduktan sonra tekrar deneyin."
2706
+ `Manuel kurulum: sudo ${installCmd}`
2667
2707
  );
2668
2708
  }
2669
2709
  async function runCommand2(options) {
@@ -2801,12 +2841,8 @@ async function runCommand2(options) {
2801
2841
  }
2802
2842
  }
2803
2843
  s.start(`Repo klonlan\u0131yor: ${rName}...`);
2804
- let cloneUrl = url;
2805
- if (token !== null && token !== "" && url.startsWith("https://")) {
2806
- cloneUrl = url.replace("https://", `https://${token}@`);
2807
- }
2808
2844
  try {
2809
- await cloneRepository(cloneUrl, localPath);
2845
+ await cloneRepository(url, localPath, token ?? void 0);
2810
2846
  s.stop("Klonland\u0131: " + localPath);
2811
2847
  } catch (error) {
2812
2848
  s.stop("Klonlama ba\u015Far\u0131s\u0131z");
@@ -2968,9 +3004,9 @@ async function runCommand2(options) {
2968
3004
  if (cleanup === "session" && result.worktrees.length > 0) {
2969
3005
  const worktreeManager = new WorktreeManager(worktreeBase, cleanup);
2970
3006
  for (const wt of result.worktrees) {
2971
- await worktreeManager.addWorktree(
3007
+ worktreeManager.trackExisting(
2972
3008
  { pane: wt.pane, repoPath: wt.repoPath, branch: wt.branch },
2973
- distribution || void 0
3009
+ wt.path
2974
3010
  );
2975
3011
  }
2976
3012
  const cleanupHandler = new SessionCleanupHandler(
@@ -3753,6 +3789,10 @@ async function main() {
3753
3789
  process.exit(4 /* RUNTIME_ERROR */);
3754
3790
  }
3755
3791
  }
3756
- void main();
3792
+ main().catch((error) => {
3793
+ const message = error instanceof Error ? error.message : String(error);
3794
+ console.error(chalk9.red(`Fatal error: ${message}`));
3795
+ process.exit(4 /* RUNTIME_ERROR */);
3796
+ });
3757
3797
  //# sourceMappingURL=cli.js.map
3758
3798
  //# sourceMappingURL=cli.js.map