@ogpoyraz/wtx 0.8.4 → 0.8.6

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/cli.mjs +147 -15
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -31096,7 +31096,7 @@ function isMissingWorktreePathError(message) {
31096
31096
  return message.includes("ENOENT") || message.includes("No such file") || message.includes("does not exist") || message.includes("not a git repository") || message.includes("cannot change to");
31097
31097
  }
31098
31098
  function isRecoverableWorktreeRemoveError(message) {
31099
- return message.includes("not a working tree") || message.includes("already removed") || isMissingWorktreePathError(message);
31099
+ return message.includes("not a working tree") || message.includes("already removed") || message.includes("submodule") || message.includes("containing submodules") || isMissingWorktreePathError(message);
31100
31100
  }
31101
31101
  function removeExistingPath(wtPath, opts) {
31102
31102
  if (!opts.dryRun && fs23.existsSync(wtPath)) {
@@ -31253,7 +31253,30 @@ function registerRemoveCommand(program2) {
31253
31253
  stepSuccess("Worktree removed", wtPath);
31254
31254
  } catch (err) {
31255
31255
  const msg = errorMessage(err);
31256
- if (isRecoverableWorktreeRemoveError(msg)) {
31256
+ const isSubmodule = msg.includes("submodule") || msg.includes("containing submodules");
31257
+ if (isSubmodule && !options.force && !args.includes("--force")) {
31258
+ stepWarning("Worktree contains submodules — retrying with --force", msg.split(`
31259
+ `)[0] ?? msg);
31260
+ try {
31261
+ await gitExec(["-C", repo.mainPath, "worktree", "remove", "--force", wtPath], globalOpts);
31262
+ stepSuccess("Worktree removed", wtPath);
31263
+ } catch (forceErr) {
31264
+ const forceMsg = errorMessage(forceErr);
31265
+ if (isRecoverableWorktreeRemoveError(forceMsg)) {
31266
+ stepWarning("Worktree already removed or invalid", forceMsg);
31267
+ try {
31268
+ await gitExec(["-C", repo.mainPath, "worktree", "prune"], globalOpts);
31269
+ } catch (pruneErr) {
31270
+ stepWarning("Worktree prune failed", errorMessage(pruneErr));
31271
+ }
31272
+ removeExistingPath(wtPath, globalOpts);
31273
+ await removeLocalBranchBestEffort(repo.mainPath, branch, globalOpts);
31274
+ stepSuccess("Worktree removed", wtPath);
31275
+ } else {
31276
+ throw forceErr;
31277
+ }
31278
+ }
31279
+ } else if (isRecoverableWorktreeRemoveError(msg)) {
31257
31280
  stepWarning("Worktree already removed or invalid", msg);
31258
31281
  try {
31259
31282
  await gitExec(["-C", repo.mainPath, "worktree", "prune"], globalOpts);
@@ -32889,21 +32912,130 @@ function registerSkillCommand(program2) {
32889
32912
  }
32890
32913
 
32891
32914
  // src/commands/terminal.ts
32915
+ import { spawnSync as spawnSync3 } from "node:child_process";
32916
+ import fs33 from "node:fs";
32917
+ import path37 from "node:path";
32918
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
32919
+ function findEntry() {
32920
+ const candidates = [];
32921
+ if (process.argv[1])
32922
+ candidates.push(process.argv[1]);
32923
+ try {
32924
+ const thisPath = fileURLToPath4(import.meta.url);
32925
+ candidates.push(thisPath);
32926
+ candidates.push(path37.resolve(path37.dirname(thisPath), "../../dist/cli.mjs"));
32927
+ candidates.push(path37.resolve(path37.dirname(thisPath), "../cli.mjs"));
32928
+ } catch {}
32929
+ for (const p of candidates) {
32930
+ try {
32931
+ if (p.endsWith(".mjs") || p.endsWith(".js")) {
32932
+ if (fs33.existsSync(p) && fs33.statSync(p).isFile())
32933
+ return p;
32934
+ } else if (fs33.existsSync(p)) {
32935
+ const stat = fs33.statSync(p);
32936
+ if (stat.isFile()) {
32937
+ const head = fs33.readFileSync(p, "utf8").slice(0, 1024);
32938
+ if (head.includes("cli.mjs") || head.includes("#!/usr/bin/env node")) {
32939
+ if (p.endsWith("wtx") || p.endsWith("cli.mjs")) {
32940
+ const dir = path37.dirname(fs33.realpathSync(p));
32941
+ const cli = path37.join(dir, "cli.mjs");
32942
+ if (fs33.existsSync(cli))
32943
+ return cli;
32944
+ const cli2 = path37.resolve(dir, "../dist/cli.mjs");
32945
+ if (fs33.existsSync(cli2))
32946
+ return cli2;
32947
+ if (head.includes("import") || head.includes("commander"))
32948
+ return p;
32949
+ }
32950
+ }
32951
+ }
32952
+ }
32953
+ } catch {}
32954
+ }
32955
+ try {
32956
+ const thisPath = fileURLToPath4(import.meta.url);
32957
+ const pkgRoot = path37.resolve(path37.dirname(thisPath), "../..");
32958
+ const cli = path37.join(pkgRoot, "dist", "cli.mjs");
32959
+ if (fs33.existsSync(cli))
32960
+ return cli;
32961
+ } catch {}
32962
+ return null;
32963
+ }
32964
+ function tryRelaunchViaBun(forwardedArgs) {
32965
+ if (process.env.WTX_BUN_RERUN === "1")
32966
+ return null;
32967
+ const env2 = { ...process.env, WTX_BUN_RERUN: "1" };
32968
+ const entry = findEntry();
32969
+ if (entry) {
32970
+ const result = spawnSync3("bun", [entry, ...forwardedArgs], {
32971
+ stdio: "inherit",
32972
+ env: env2
32973
+ });
32974
+ if (!result.error) {
32975
+ process.exit(result.status ?? 0);
32976
+ }
32977
+ if (result.error.code !== "ENOENT") {
32978
+ process.stderr.write(`✗ Failed to relaunch via Bun: ${result.error.message}
32979
+ `);
32980
+ process.exit(1);
32981
+ }
32982
+ }
32983
+ const attempts = [
32984
+ { cmd: "bunx", args: ["--bun", "wtx", ...forwardedArgs] },
32985
+ { cmd: "bun", args: ["x", "--bun", "wtx", ...forwardedArgs] }
32986
+ ];
32987
+ for (const { cmd, args } of attempts) {
32988
+ const result = spawnSync3(cmd, args, { stdio: "inherit", env: env2 });
32989
+ if (!result.error) {
32990
+ process.exit(result.status ?? 0);
32991
+ }
32992
+ if (result.error.code !== "ENOENT") {
32993
+ process.stderr.write(`✗ Failed to relaunch via ${cmd}: ${result.error.message}
32994
+ `);
32995
+ process.exit(1);
32996
+ }
32997
+ }
32998
+ return null;
32999
+ }
32892
33000
  function registerTerminalCommand(program2) {
32893
33001
  program2.command("terminal").description("Interactive worktree dashboard (requires Bun)").action(async () => {
32894
33002
  const opts = program2.optsWithGlobals();
32895
33003
  if (typeof Bun === "undefined") {
32896
- process.stderr.write(`✗ wtx terminal dashboard requires the Bun runtime
33004
+ const forwarded = process.argv.slice(2);
33005
+ const relaunched = tryRelaunchViaBun(forwarded);
33006
+ if (relaunched === null) {
33007
+ const probe2 = spawnSync3("bun", ["--version"], { stdio: "ignore" });
33008
+ const hasBun = !probe2.error && probe2.status === 0;
33009
+ if (!hasBun) {
33010
+ process.stderr.write(`✗ wtx terminal dashboard requires the Bun runtime
32897
33011
  `);
32898
- process.stderr.write(` The TUI relies on Bun's FFI and React renderer which cannot run in Node/compiled binary.
33012
+ process.stderr.write(` The TUI relies on Bun's FFI and React renderer which cannot run in Node/compiled binary.
32899
33013
  `);
32900
- process.stderr.write(` To use the dashboard, run it via Bun:
33014
+ process.stderr.write(` Bun was not found on your PATH. Install it from https://bun.sh, then run:
32901
33015
  `);
32902
- process.stderr.write(` bunx --bun wtx terminal
33016
+ process.stderr.write(` wtx terminal
32903
33017
 
32904
33018
  `);
32905
- process.stderr.write(" Note: `wtx ls` provides a fast list view and works everywhere.\n");
32906
- process.exit(1);
33019
+ process.stderr.write(" Note: `wtx ls` provides a fast list view and works everywhere.\n");
33020
+ process.exit(1);
33021
+ }
33022
+ process.stderr.write(`✗ wtx terminal dashboard requires the Bun runtime
33023
+ `);
33024
+ process.stderr.write(` The TUI relies on Bun's FFI and React renderer which cannot run in Node/compiled binary.
33025
+ `);
33026
+ process.stderr.write(` Automatic relaunch via Bun failed. Try running directly:
33027
+ `);
33028
+ process.stderr.write(` bunx --bun wtx terminal
33029
+ `);
33030
+ const entry = findEntry();
33031
+ if (entry) {
33032
+ process.stderr.write(` bun ${entry} terminal
33033
+ `);
33034
+ }
33035
+ process.stderr.write("\n Note: `wtx ls` provides a fast list view and works everywhere.\n");
33036
+ process.exit(1);
33037
+ }
33038
+ return;
32907
33039
  }
32908
33040
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
32909
33041
  process.stderr.write(`✗ wtx terminal requires an interactive terminal
@@ -32924,7 +33056,7 @@ init_deps();
32924
33056
  import readline3 from "readline";
32925
33057
  init_path_safety();
32926
33058
  init_stack();
32927
- import fs33 from "fs";
33059
+ import fs34 from "fs";
32928
33060
  async function runMcpServer(opts = {}) {
32929
33061
  const input = opts.input ?? process.stdin;
32930
33062
  const output = opts.output ?? process.stdout;
@@ -33107,7 +33239,7 @@ async function handleToolCall(name, args, config2, _opts) {
33107
33239
  for (const wt of wts) {
33108
33240
  if (!wt.path.startsWith(repo.wtRoot))
33109
33241
  continue;
33110
- const dirtyCount = fs33.existsSync(wt.path) ? (await getDirtyFiles(wt.path)).length : 0;
33242
+ const dirtyCount = fs34.existsSync(wt.path) ? (await getDirtyFiles(wt.path)).length : 0;
33111
33243
  items.push({
33112
33244
  repo: repo.name,
33113
33245
  branch: wt.branch || null,
@@ -33135,7 +33267,7 @@ async function handleToolCall(name, args, config2, _opts) {
33135
33267
  }
33136
33268
  const repo = resolveRepos(config2, [args.repo])[0];
33137
33269
  const wtPath = getWorktreePath(repo, args.branch);
33138
- if (!fs33.existsSync(wtPath)) {
33270
+ if (!fs34.existsSync(wtPath)) {
33139
33271
  throw { isToolError: true, message: `Worktree not found for branch ${args.branch}` };
33140
33272
  }
33141
33273
  const mainBranch = await resolveMainBranch(repo, config2);
@@ -33254,7 +33386,7 @@ async function handleToolCall(name, args, config2, _opts) {
33254
33386
  if (children.length > 0 && args.force !== true) {
33255
33387
  throw { isToolError: true, message: `Branch has dependent worktrees: ${children.join(", ")}. Use force:true to override.` };
33256
33388
  }
33257
- if (!args.force && fs33.existsSync(wtPath)) {
33389
+ if (!args.force && fs34.existsSync(wtPath)) {
33258
33390
  const dirty = await getDirtyFiles(wtPath);
33259
33391
  if (dirty.length > 0) {
33260
33392
  throw { isToolError: true, message: `Worktree is dirty. Use force:true to remove it.` };
@@ -33273,7 +33405,7 @@ async function handleToolCall(name, args, config2, _opts) {
33273
33405
  }
33274
33406
  const repo = resolveRepos(config2, [args.repo])[0];
33275
33407
  const wtPath = getWorktreePath(repo, args.branch);
33276
- if (!fs33.existsSync(wtPath)) {
33408
+ if (!fs34.existsSync(wtPath)) {
33277
33409
  throw { isToolError: true, message: `Worktree not found for branch ${args.branch}` };
33278
33410
  }
33279
33411
  const mainBranch = await resolveMainBranch(repo, config2);
@@ -33495,7 +33627,7 @@ init_config();
33495
33627
  init_resolver();
33496
33628
  init_log();
33497
33629
  init_history();
33498
- import fs34 from "fs";
33630
+ import fs35 from "fs";
33499
33631
  var program2 = new Command;
33500
33632
  program2.name("wtx").description("Multi-repo git worktree manager").version(VERSION).option("-q, --quiet", "Suppress progress indicators", false).option("--verbose", "Show git commands as they run", false).option("--dry-run", "Show what would happen", false);
33501
33633
  var MUTATING_COMMANDS = new Set([
@@ -33599,7 +33731,7 @@ program2.command("_resolve-path <repo> <branch>", { hidden: true }).description(
33599
33731
  }
33600
33732
  const repo = repos[0];
33601
33733
  const wtPath = getWorktreePath(repo, branch);
33602
- if (!fs34.existsSync(wtPath)) {
33734
+ if (!fs35.existsSync(wtPath)) {
33603
33735
  process.stderr.write(`✗ No worktree at ${wtPath}
33604
33736
  `);
33605
33737
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogpoyraz/wtx",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "Multi-repo git worktree manager",
5
5
  "type": "module",
6
6
  "bin": {