@camaradesuk/git-worktree-tools 1.9.0 → 1.11.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 (531) hide show
  1. package/README.md +52 -31
  2. package/dist/cli/cleanpr.js +74 -53
  3. package/dist/cli/cleanpr.js.map +1 -1
  4. package/dist/cli/lswt.js +32 -56
  5. package/dist/cli/lswt.js.map +1 -1
  6. package/dist/cli/newpr.d.ts +13 -1
  7. package/dist/cli/newpr.d.ts.map +1 -1
  8. package/dist/cli/newpr.js +159 -153
  9. package/dist/cli/newpr.js.map +1 -1
  10. package/dist/cli/prs.d.ts +3 -10
  11. package/dist/cli/prs.d.ts.map +1 -1
  12. package/dist/cli/prs.js +6 -168
  13. package/dist/cli/prs.js.map +1 -1
  14. package/dist/cli/wt/clean.d.ts +6 -2
  15. package/dist/cli/wt/clean.d.ts.map +1 -1
  16. package/dist/cli/wt/clean.js +401 -20
  17. package/dist/cli/wt/clean.js.map +1 -1
  18. package/dist/cli/wt/completion.d.ts +3 -0
  19. package/dist/cli/wt/completion.d.ts.map +1 -1
  20. package/dist/cli/wt/completion.js +80 -9
  21. package/dist/cli/wt/completion.js.map +1 -1
  22. package/dist/cli/wt/config.d.ts +3 -1
  23. package/dist/cli/wt/config.d.ts.map +1 -1
  24. package/dist/cli/wt/config.js +323 -32
  25. package/dist/cli/wt/config.js.map +1 -1
  26. package/dist/cli/wt/interactive-menu.d.ts +2 -0
  27. package/dist/cli/wt/interactive-menu.d.ts.map +1 -1
  28. package/dist/cli/wt/interactive-menu.js +346 -73
  29. package/dist/cli/wt/interactive-menu.js.map +1 -1
  30. package/dist/cli/wt/link.d.ts +3 -1
  31. package/dist/cli/wt/link.d.ts.map +1 -1
  32. package/dist/cli/wt/link.js +125 -38
  33. package/dist/cli/wt/link.js.map +1 -1
  34. package/dist/cli/wt/list.d.ts +4 -1
  35. package/dist/cli/wt/list.d.ts.map +1 -1
  36. package/dist/cli/wt/list.js +85 -16
  37. package/dist/cli/wt/list.js.map +1 -1
  38. package/dist/cli/wt/new.d.ts +8 -2
  39. package/dist/cli/wt/new.d.ts.map +1 -1
  40. package/dist/cli/wt/new.js +91 -46
  41. package/dist/cli/wt/new.js.map +1 -1
  42. package/dist/cli/wt/prs.d.ts +2 -1
  43. package/dist/cli/wt/prs.d.ts.map +1 -1
  44. package/dist/cli/wt/prs.js +3 -164
  45. package/dist/cli/wt/prs.js.map +1 -1
  46. package/dist/cli/wt/run-command.d.ts +4 -2
  47. package/dist/cli/wt/run-command.d.ts.map +1 -1
  48. package/dist/cli/wt/run-command.js +6 -4
  49. package/dist/cli/wt/run-command.js.map +1 -1
  50. package/dist/cli/wt/state.d.ts +3 -1
  51. package/dist/cli/wt/state.d.ts.map +1 -1
  52. package/dist/cli/wt/state.js +74 -10
  53. package/dist/cli/wt/state.js.map +1 -1
  54. package/dist/cli/wt.d.ts.map +1 -1
  55. package/dist/cli/wt.js +50 -36
  56. package/dist/cli/wt.js.map +1 -1
  57. package/dist/cli/wtconfig.js +99 -22
  58. package/dist/cli/wtconfig.js.map +1 -1
  59. package/dist/cli/wtlink.js +85 -61
  60. package/dist/cli/wtlink.js.map +1 -1
  61. package/dist/cli/wtstate.js +21 -2
  62. package/dist/cli/wtstate.js.map +1 -1
  63. package/dist/lib/cleanpr/args.d.ts.map +1 -1
  64. package/dist/lib/cleanpr/args.js +20 -0
  65. package/dist/lib/cleanpr/args.js.map +1 -1
  66. package/dist/lib/cleanpr/types.d.ts +6 -0
  67. package/dist/lib/cleanpr/types.d.ts.map +1 -1
  68. package/dist/lib/colors.d.ts +5 -0
  69. package/dist/lib/colors.d.ts.map +1 -1
  70. package/dist/lib/colors.js +13 -6
  71. package/dist/lib/colors.js.map +1 -1
  72. package/dist/lib/constants.d.ts +12 -4
  73. package/dist/lib/constants.d.ts.map +1 -1
  74. package/dist/lib/constants.js +24 -5
  75. package/dist/lib/constants.js.map +1 -1
  76. package/dist/lib/deprecation.d.ts +18 -0
  77. package/dist/lib/deprecation.d.ts.map +1 -0
  78. package/dist/lib/deprecation.js +28 -0
  79. package/dist/lib/deprecation.js.map +1 -0
  80. package/dist/lib/logger.d.ts +40 -155
  81. package/dist/lib/logger.d.ts.map +1 -1
  82. package/dist/lib/logger.js +349 -420
  83. package/dist/lib/logger.js.map +1 -1
  84. package/dist/lib/lswt/args.d.ts.map +1 -1
  85. package/dist/lib/lswt/args.js +15 -1
  86. package/dist/lib/lswt/args.js.map +1 -1
  87. package/dist/lib/lswt/index.d.ts +1 -0
  88. package/dist/lib/lswt/index.d.ts.map +1 -1
  89. package/dist/lib/lswt/index.js +2 -0
  90. package/dist/lib/lswt/index.js.map +1 -1
  91. package/dist/lib/lswt/table.d.ts +15 -0
  92. package/dist/lib/lswt/table.d.ts.map +1 -0
  93. package/dist/lib/lswt/table.js +61 -0
  94. package/dist/lib/lswt/table.js.map +1 -0
  95. package/dist/lib/lswt/types.d.ts +4 -0
  96. package/dist/lib/lswt/types.d.ts.map +1 -1
  97. package/dist/lib/newpr/args.d.ts.map +1 -1
  98. package/dist/lib/newpr/args.js +21 -0
  99. package/dist/lib/newpr/args.js.map +1 -1
  100. package/dist/lib/newpr/types.d.ts +6 -0
  101. package/dist/lib/newpr/types.d.ts.map +1 -1
  102. package/dist/lib/prs/command.d.ts +21 -0
  103. package/dist/lib/prs/command.d.ts.map +1 -0
  104. package/dist/lib/prs/command.js +175 -0
  105. package/dist/lib/prs/command.js.map +1 -0
  106. package/dist/lib/prs/interactive.d.ts.map +1 -1
  107. package/dist/lib/prs/interactive.js +15 -2
  108. package/dist/lib/prs/interactive.js.map +1 -1
  109. package/dist/lib/prs/types.d.ts +15 -0
  110. package/dist/lib/prs/types.d.ts.map +1 -1
  111. package/dist/lib/ui/error.d.ts +31 -0
  112. package/dist/lib/ui/error.d.ts.map +1 -0
  113. package/dist/lib/ui/error.js +47 -0
  114. package/dist/lib/ui/error.js.map +1 -0
  115. package/dist/lib/ui/index.d.ts +15 -0
  116. package/dist/lib/ui/index.d.ts.map +1 -0
  117. package/dist/lib/ui/index.js +19 -0
  118. package/dist/lib/ui/index.js.map +1 -0
  119. package/dist/lib/ui/output.d.ts +18 -0
  120. package/dist/lib/ui/output.d.ts.map +1 -0
  121. package/dist/lib/ui/output.js +31 -0
  122. package/dist/lib/ui/output.js.map +1 -0
  123. package/dist/lib/ui/spinner.d.ts +10 -0
  124. package/dist/lib/ui/spinner.d.ts.map +1 -0
  125. package/dist/lib/ui/spinner.js +10 -0
  126. package/dist/lib/ui/spinner.js.map +1 -0
  127. package/dist/lib/ui/status.d.ts +65 -0
  128. package/dist/lib/ui/status.d.ts.map +1 -0
  129. package/dist/lib/ui/status.js +100 -0
  130. package/dist/lib/ui/status.js.map +1 -0
  131. package/dist/lib/ui/table.d.ts +39 -0
  132. package/dist/lib/ui/table.d.ts.map +1 -0
  133. package/dist/lib/ui/table.js +45 -0
  134. package/dist/lib/ui/table.js.map +1 -0
  135. package/dist/lib/ui/theme.d.ts +34 -0
  136. package/dist/lib/ui/theme.d.ts.map +1 -0
  137. package/dist/lib/ui/theme.js +37 -0
  138. package/dist/lib/ui/theme.js.map +1 -0
  139. package/dist/lib/wtlink/link-configs.js +7 -7
  140. package/dist/lib/wtlink/link-configs.js.map +1 -1
  141. package/dist/lib/wtlink/validate-manifest.d.ts.map +1 -1
  142. package/dist/lib/wtlink/validate-manifest.js +5 -5
  143. package/dist/lib/wtlink/validate-manifest.js.map +1 -1
  144. package/dist/lib/wtstate/args.d.ts.map +1 -1
  145. package/dist/lib/wtstate/args.js +2 -0
  146. package/dist/lib/wtstate/args.js.map +1 -1
  147. package/dist/mcp/server.d.ts +2 -1
  148. package/dist/mcp/server.d.ts.map +1 -1
  149. package/dist/mcp/server.js +264 -44
  150. package/dist/mcp/server.js.map +1 -1
  151. package/package.json +13 -3
  152. package/dist/api/list.test.d.ts +0 -5
  153. package/dist/api/list.test.d.ts.map +0 -1
  154. package/dist/api/list.test.js +0 -390
  155. package/dist/api/list.test.js.map +0 -1
  156. package/dist/cli/cleanpr.test.d.ts +0 -2
  157. package/dist/cli/cleanpr.test.d.ts.map +0 -1
  158. package/dist/cli/cleanpr.test.js +0 -954
  159. package/dist/cli/cleanpr.test.js.map +0 -1
  160. package/dist/cli/lswt.test.d.ts +0 -2
  161. package/dist/cli/lswt.test.d.ts.map +0 -1
  162. package/dist/cli/lswt.test.js +0 -386
  163. package/dist/cli/lswt.test.js.map +0 -1
  164. package/dist/cli/newpr.test.d.ts +0 -2
  165. package/dist/cli/newpr.test.d.ts.map +0 -1
  166. package/dist/cli/newpr.test.js +0 -1182
  167. package/dist/cli/newpr.test.js.map +0 -1
  168. package/dist/cli/prs.test.d.ts +0 -8
  169. package/dist/cli/prs.test.d.ts.map +0 -1
  170. package/dist/cli/prs.test.js +0 -410
  171. package/dist/cli/prs.test.js.map +0 -1
  172. package/dist/cli/wt/completion.test.d.ts +0 -5
  173. package/dist/cli/wt/completion.test.d.ts.map +0 -1
  174. package/dist/cli/wt/completion.test.js +0 -173
  175. package/dist/cli/wt/completion.test.js.map +0 -1
  176. package/dist/cli/wt/config.test.d.ts +0 -5
  177. package/dist/cli/wt/config.test.d.ts.map +0 -1
  178. package/dist/cli/wt/config.test.js +0 -260
  179. package/dist/cli/wt/config.test.js.map +0 -1
  180. package/dist/cli/wt/entry.test.d.ts +0 -8
  181. package/dist/cli/wt/entry.test.d.ts.map +0 -1
  182. package/dist/cli/wt/entry.test.js +0 -201
  183. package/dist/cli/wt/entry.test.js.map +0 -1
  184. package/dist/cli/wt/init.test.d.ts +0 -5
  185. package/dist/cli/wt/init.test.d.ts.map +0 -1
  186. package/dist/cli/wt/init.test.js +0 -165
  187. package/dist/cli/wt/init.test.js.map +0 -1
  188. package/dist/cli/wt/init.unit.test.d.ts +0 -5
  189. package/dist/cli/wt/init.unit.test.d.ts.map +0 -1
  190. package/dist/cli/wt/init.unit.test.js +0 -432
  191. package/dist/cli/wt/init.unit.test.js.map +0 -1
  192. package/dist/cli/wt/interactive-menu.test.d.ts +0 -10
  193. package/dist/cli/wt/interactive-menu.test.d.ts.map +0 -1
  194. package/dist/cli/wt/interactive-menu.test.js +0 -739
  195. package/dist/cli/wt/interactive-menu.test.js.map +0 -1
  196. package/dist/cli/wt/prs.test.d.ts +0 -5
  197. package/dist/cli/wt/prs.test.d.ts.map +0 -1
  198. package/dist/cli/wt/prs.test.js +0 -410
  199. package/dist/cli/wt/prs.test.js.map +0 -1
  200. package/dist/cli/wt/run-command.test.d.ts +0 -5
  201. package/dist/cli/wt/run-command.test.d.ts.map +0 -1
  202. package/dist/cli/wt/run-command.test.js +0 -88
  203. package/dist/cli/wt/run-command.test.js.map +0 -1
  204. package/dist/cli/wt/wt.test.d.ts +0 -8
  205. package/dist/cli/wt/wt.test.d.ts.map +0 -1
  206. package/dist/cli/wt/wt.test.js +0 -521
  207. package/dist/cli/wt/wt.test.js.map +0 -1
  208. package/dist/cli/wt.unit.test.d.ts +0 -7
  209. package/dist/cli/wt.unit.test.d.ts.map +0 -1
  210. package/dist/cli/wt.unit.test.js +0 -182
  211. package/dist/cli/wt.unit.test.js.map +0 -1
  212. package/dist/cli/wtconfig.test.d.ts +0 -5
  213. package/dist/cli/wtconfig.test.d.ts.map +0 -1
  214. package/dist/cli/wtconfig.test.js +0 -1289
  215. package/dist/cli/wtconfig.test.js.map +0 -1
  216. package/dist/cli/wtlink.test.d.ts +0 -2
  217. package/dist/cli/wtlink.test.d.ts.map +0 -1
  218. package/dist/cli/wtlink.test.js +0 -249
  219. package/dist/cli/wtlink.test.js.map +0 -1
  220. package/dist/cli/wtstate.test.d.ts +0 -5
  221. package/dist/cli/wtstate.test.d.ts.map +0 -1
  222. package/dist/cli/wtstate.test.js +0 -193
  223. package/dist/cli/wtstate.test.js.map +0 -1
  224. package/dist/e2e/cleanpr/cleanpr.e2e.test.d.ts +0 -2
  225. package/dist/e2e/cleanpr/cleanpr.e2e.test.d.ts.map +0 -1
  226. package/dist/e2e/cleanpr/cleanpr.e2e.test.js +0 -326
  227. package/dist/e2e/cleanpr/cleanpr.e2e.test.js.map +0 -1
  228. package/dist/e2e/cli.e2e.test.d.ts +0 -2
  229. package/dist/e2e/cli.e2e.test.d.ts.map +0 -1
  230. package/dist/e2e/cli.e2e.test.js +0 -417
  231. package/dist/e2e/cli.e2e.test.js.map +0 -1
  232. package/dist/e2e/lswt/lswt.e2e.test.d.ts +0 -2
  233. package/dist/e2e/lswt/lswt.e2e.test.d.ts.map +0 -1
  234. package/dist/e2e/lswt/lswt.e2e.test.js +0 -361
  235. package/dist/e2e/lswt/lswt.e2e.test.js.map +0 -1
  236. package/dist/e2e/newpr/newpr.e2e.test.d.ts +0 -2
  237. package/dist/e2e/newpr/newpr.e2e.test.d.ts.map +0 -1
  238. package/dist/e2e/newpr/newpr.e2e.test.js +0 -286
  239. package/dist/e2e/newpr/newpr.e2e.test.js.map +0 -1
  240. package/dist/e2e/newpr/scenarios.e2e.test.d.ts +0 -2
  241. package/dist/e2e/newpr/scenarios.e2e.test.d.ts.map +0 -1
  242. package/dist/e2e/newpr/scenarios.e2e.test.js +0 -426
  243. package/dist/e2e/newpr/scenarios.e2e.test.js.map +0 -1
  244. package/dist/e2e/newpr-full-flow.e2e.test.d.ts +0 -2
  245. package/dist/e2e/newpr-full-flow.e2e.test.d.ts.map +0 -1
  246. package/dist/e2e/newpr-full-flow.e2e.test.js +0 -280
  247. package/dist/e2e/newpr-full-flow.e2e.test.js.map +0 -1
  248. package/dist/e2e/prs/prs.e2e.test.d.ts +0 -7
  249. package/dist/e2e/prs/prs.e2e.test.d.ts.map +0 -1
  250. package/dist/e2e/prs/prs.e2e.test.js +0 -606
  251. package/dist/e2e/prs/prs.e2e.test.js.map +0 -1
  252. package/dist/e2e/workflows/pr-lifecycle.e2e.test.d.ts +0 -2
  253. package/dist/e2e/workflows/pr-lifecycle.e2e.test.d.ts.map +0 -1
  254. package/dist/e2e/workflows/pr-lifecycle.e2e.test.js +0 -298
  255. package/dist/e2e/workflows/pr-lifecycle.e2e.test.js.map +0 -1
  256. package/dist/e2e/wt/interactive-menu.e2e.test.d.ts +0 -8
  257. package/dist/e2e/wt/interactive-menu.e2e.test.d.ts.map +0 -1
  258. package/dist/e2e/wt/interactive-menu.e2e.test.js +0 -583
  259. package/dist/e2e/wt/interactive-menu.e2e.test.js.map +0 -1
  260. package/dist/e2e/wt/wt.e2e.test.d.ts +0 -9
  261. package/dist/e2e/wt/wt.e2e.test.d.ts.map +0 -1
  262. package/dist/e2e/wt/wt.e2e.test.js +0 -597
  263. package/dist/e2e/wt/wt.e2e.test.js.map +0 -1
  264. package/dist/e2e/wtlink/wtlink.e2e.test.d.ts +0 -2
  265. package/dist/e2e/wtlink/wtlink.e2e.test.d.ts.map +0 -1
  266. package/dist/e2e/wtlink/wtlink.e2e.test.js +0 -416
  267. package/dist/e2e/wtlink/wtlink.e2e.test.js.map +0 -1
  268. package/dist/integration/git.integration.test.d.ts +0 -2
  269. package/dist/integration/git.integration.test.d.ts.map +0 -1
  270. package/dist/integration/git.integration.test.js +0 -336
  271. package/dist/integration/git.integration.test.js.map +0 -1
  272. package/dist/integration/lswt-remote-pr.integration.test.d.ts +0 -2
  273. package/dist/integration/lswt-remote-pr.integration.test.d.ts.map +0 -1
  274. package/dist/integration/lswt-remote-pr.integration.test.js +0 -222
  275. package/dist/integration/lswt-remote-pr.integration.test.js.map +0 -1
  276. package/dist/integration/newpr-branchfrom-head.integration.test.d.ts +0 -2
  277. package/dist/integration/newpr-branchfrom-head.integration.test.d.ts.map +0 -1
  278. package/dist/integration/newpr-branchfrom-head.integration.test.js +0 -498
  279. package/dist/integration/newpr-branchfrom-head.integration.test.js.map +0 -1
  280. package/dist/integration/newpr.integration.test.d.ts +0 -2
  281. package/dist/integration/newpr.integration.test.d.ts.map +0 -1
  282. package/dist/integration/newpr.integration.test.js +0 -460
  283. package/dist/integration/newpr.integration.test.js.map +0 -1
  284. package/dist/integration/prs.integration.test.d.ts +0 -8
  285. package/dist/integration/prs.integration.test.d.ts.map +0 -1
  286. package/dist/integration/prs.integration.test.js +0 -478
  287. package/dist/integration/prs.integration.test.js.map +0 -1
  288. package/dist/lib/ai/base-provider.test.d.ts +0 -7
  289. package/dist/lib/ai/base-provider.test.d.ts.map +0 -1
  290. package/dist/lib/ai/base-provider.test.js +0 -319
  291. package/dist/lib/ai/base-provider.test.js.map +0 -1
  292. package/dist/lib/ai/cli-provider.test.d.ts +0 -5
  293. package/dist/lib/ai/cli-provider.test.d.ts.map +0 -1
  294. package/dist/lib/ai/cli-provider.test.js +0 -460
  295. package/dist/lib/ai/cli-provider.test.js.map +0 -1
  296. package/dist/lib/ai/fallback-provider.test.d.ts +0 -7
  297. package/dist/lib/ai/fallback-provider.test.d.ts.map +0 -1
  298. package/dist/lib/ai/fallback-provider.test.js +0 -165
  299. package/dist/lib/ai/fallback-provider.test.js.map +0 -1
  300. package/dist/lib/ai/generation-service.test.d.ts +0 -7
  301. package/dist/lib/ai/generation-service.test.d.ts.map +0 -1
  302. package/dist/lib/ai/generation-service.test.js +0 -213
  303. package/dist/lib/ai/generation-service.test.js.map +0 -1
  304. package/dist/lib/ai/provider-manager.test.d.ts +0 -5
  305. package/dist/lib/ai/provider-manager.test.d.ts.map +0 -1
  306. package/dist/lib/ai/provider-manager.test.js +0 -312
  307. package/dist/lib/ai/provider-manager.test.js.map +0 -1
  308. package/dist/lib/ai/repo-docs.test.d.ts +0 -5
  309. package/dist/lib/ai/repo-docs.test.d.ts.map +0 -1
  310. package/dist/lib/ai/repo-docs.test.js +0 -357
  311. package/dist/lib/ai/repo-docs.test.js.map +0 -1
  312. package/dist/lib/cleanpr/args.test.d.ts +0 -2
  313. package/dist/lib/cleanpr/args.test.d.ts.map +0 -1
  314. package/dist/lib/cleanpr/args.test.js +0 -269
  315. package/dist/lib/cleanpr/args.test.js.map +0 -1
  316. package/dist/lib/cleanpr/cleanup.test.d.ts +0 -2
  317. package/dist/lib/cleanpr/cleanup.test.d.ts.map +0 -1
  318. package/dist/lib/cleanpr/cleanup.test.js +0 -296
  319. package/dist/lib/cleanpr/cleanup.test.js.map +0 -1
  320. package/dist/lib/cleanpr/worktree-info.test.d.ts +0 -2
  321. package/dist/lib/cleanpr/worktree-info.test.d.ts.map +0 -1
  322. package/dist/lib/cleanpr/worktree-info.test.js +0 -228
  323. package/dist/lib/cleanpr/worktree-info.test.js.map +0 -1
  324. package/dist/lib/colors.test.d.ts +0 -2
  325. package/dist/lib/colors.test.d.ts.map +0 -1
  326. package/dist/lib/colors.test.js +0 -142
  327. package/dist/lib/colors.test.js.map +0 -1
  328. package/dist/lib/config-editor.test.d.ts +0 -11
  329. package/dist/lib/config-editor.test.d.ts.map +0 -1
  330. package/dist/lib/config-editor.test.js +0 -526
  331. package/dist/lib/config-editor.test.js.map +0 -1
  332. package/dist/lib/config-migration/detector.test.d.ts +0 -5
  333. package/dist/lib/config-migration/detector.test.d.ts.map +0 -1
  334. package/dist/lib/config-migration/detector.test.js +0 -201
  335. package/dist/lib/config-migration/detector.test.js.map +0 -1
  336. package/dist/lib/config-migration/reporter.test.d.ts +0 -5
  337. package/dist/lib/config-migration/reporter.test.d.ts.map +0 -1
  338. package/dist/lib/config-migration/reporter.test.js +0 -305
  339. package/dist/lib/config-migration/reporter.test.js.map +0 -1
  340. package/dist/lib/config-migration/runner.test.d.ts +0 -5
  341. package/dist/lib/config-migration/runner.test.d.ts.map +0 -1
  342. package/dist/lib/config-migration/runner.test.js +0 -235
  343. package/dist/lib/config-migration/runner.test.js.map +0 -1
  344. package/dist/lib/config-validation.test.d.ts +0 -5
  345. package/dist/lib/config-validation.test.d.ts.map +0 -1
  346. package/dist/lib/config-validation.test.js +0 -423
  347. package/dist/lib/config-validation.test.js.map +0 -1
  348. package/dist/lib/config.test.d.ts +0 -2
  349. package/dist/lib/config.test.d.ts.map +0 -1
  350. package/dist/lib/config.test.js +0 -566
  351. package/dist/lib/config.test.js.map +0 -1
  352. package/dist/lib/constants.test.d.ts +0 -5
  353. package/dist/lib/constants.test.d.ts.map +0 -1
  354. package/dist/lib/constants.test.js +0 -121
  355. package/dist/lib/constants.test.js.map +0 -1
  356. package/dist/lib/errors.test.d.ts +0 -2
  357. package/dist/lib/errors.test.d.ts.map +0 -1
  358. package/dist/lib/errors.test.js +0 -117
  359. package/dist/lib/errors.test.js.map +0 -1
  360. package/dist/lib/git.test.d.ts +0 -2
  361. package/dist/lib/git.test.d.ts.map +0 -1
  362. package/dist/lib/git.test.js +0 -608
  363. package/dist/lib/git.test.js.map +0 -1
  364. package/dist/lib/github.test.d.ts +0 -2
  365. package/dist/lib/github.test.d.ts.map +0 -1
  366. package/dist/lib/github.test.js +0 -441
  367. package/dist/lib/github.test.js.map +0 -1
  368. package/dist/lib/global-check.test.d.ts +0 -5
  369. package/dist/lib/global-check.test.d.ts.map +0 -1
  370. package/dist/lib/global-check.test.js +0 -150
  371. package/dist/lib/global-check.test.js.map +0 -1
  372. package/dist/lib/global-config.test.d.ts +0 -5
  373. package/dist/lib/global-config.test.d.ts.map +0 -1
  374. package/dist/lib/global-config.test.js +0 -282
  375. package/dist/lib/global-config.test.js.map +0 -1
  376. package/dist/lib/hooks/confirmation.test.d.ts +0 -7
  377. package/dist/lib/hooks/confirmation.test.d.ts.map +0 -1
  378. package/dist/lib/hooks/confirmation.test.js +0 -300
  379. package/dist/lib/hooks/confirmation.test.js.map +0 -1
  380. package/dist/lib/hooks/executor.test.d.ts +0 -5
  381. package/dist/lib/hooks/executor.test.d.ts.map +0 -1
  382. package/dist/lib/hooks/executor.test.js +0 -648
  383. package/dist/lib/hooks/executor.test.js.map +0 -1
  384. package/dist/lib/hooks/templates.test.d.ts +0 -5
  385. package/dist/lib/hooks/templates.test.d.ts.map +0 -1
  386. package/dist/lib/hooks/templates.test.js +0 -163
  387. package/dist/lib/hooks/templates.test.js.map +0 -1
  388. package/dist/lib/hooks/types.test.d.ts +0 -5
  389. package/dist/lib/hooks/types.test.d.ts.map +0 -1
  390. package/dist/lib/hooks/types.test.js +0 -132
  391. package/dist/lib/hooks/types.test.js.map +0 -1
  392. package/dist/lib/json-output.test.d.ts +0 -5
  393. package/dist/lib/json-output.test.d.ts.map +0 -1
  394. package/dist/lib/json-output.test.js +0 -261
  395. package/dist/lib/json-output.test.js.map +0 -1
  396. package/dist/lib/logger.test.d.ts +0 -5
  397. package/dist/lib/logger.test.d.ts.map +0 -1
  398. package/dist/lib/logger.test.js +0 -292
  399. package/dist/lib/logger.test.js.map +0 -1
  400. package/dist/lib/lswt/action-executors.test.d.ts +0 -2
  401. package/dist/lib/lswt/action-executors.test.d.ts.map +0 -1
  402. package/dist/lib/lswt/action-executors.test.js +0 -1127
  403. package/dist/lib/lswt/action-executors.test.js.map +0 -1
  404. package/dist/lib/lswt/actions.test.d.ts +0 -2
  405. package/dist/lib/lswt/actions.test.d.ts.map +0 -1
  406. package/dist/lib/lswt/actions.test.js +0 -497
  407. package/dist/lib/lswt/actions.test.js.map +0 -1
  408. package/dist/lib/lswt/args.test.d.ts +0 -2
  409. package/dist/lib/lswt/args.test.d.ts.map +0 -1
  410. package/dist/lib/lswt/args.test.js +0 -195
  411. package/dist/lib/lswt/args.test.js.map +0 -1
  412. package/dist/lib/lswt/environment.test.d.ts +0 -2
  413. package/dist/lib/lswt/environment.test.d.ts.map +0 -1
  414. package/dist/lib/lswt/environment.test.js +0 -544
  415. package/dist/lib/lswt/environment.test.js.map +0 -1
  416. package/dist/lib/lswt/formatters.test.d.ts +0 -2
  417. package/dist/lib/lswt/formatters.test.d.ts.map +0 -1
  418. package/dist/lib/lswt/formatters.test.js +0 -323
  419. package/dist/lib/lswt/formatters.test.js.map +0 -1
  420. package/dist/lib/lswt/fuzzy-search.test.d.ts +0 -5
  421. package/dist/lib/lswt/fuzzy-search.test.d.ts.map +0 -1
  422. package/dist/lib/lswt/fuzzy-search.test.js +0 -207
  423. package/dist/lib/lswt/fuzzy-search.test.js.map +0 -1
  424. package/dist/lib/lswt/interactive.test.d.ts +0 -2
  425. package/dist/lib/lswt/interactive.test.d.ts.map +0 -1
  426. package/dist/lib/lswt/interactive.test.js +0 -771
  427. package/dist/lib/lswt/interactive.test.js.map +0 -1
  428. package/dist/lib/lswt/worktree-info.test.d.ts +0 -2
  429. package/dist/lib/lswt/worktree-info.test.d.ts.map +0 -1
  430. package/dist/lib/lswt/worktree-info.test.js +0 -484
  431. package/dist/lib/lswt/worktree-info.test.js.map +0 -1
  432. package/dist/lib/newpr/action-deps.test.d.ts +0 -5
  433. package/dist/lib/newpr/action-deps.test.d.ts.map +0 -1
  434. package/dist/lib/newpr/action-deps.test.js +0 -111
  435. package/dist/lib/newpr/action-deps.test.js.map +0 -1
  436. package/dist/lib/newpr/actions.test.d.ts +0 -2
  437. package/dist/lib/newpr/actions.test.d.ts.map +0 -1
  438. package/dist/lib/newpr/actions.test.js +0 -254
  439. package/dist/lib/newpr/actions.test.js.map +0 -1
  440. package/dist/lib/newpr/args.test.d.ts +0 -2
  441. package/dist/lib/newpr/args.test.d.ts.map +0 -1
  442. package/dist/lib/newpr/args.test.js +0 -479
  443. package/dist/lib/newpr/args.test.js.map +0 -1
  444. package/dist/lib/newpr/hook-runner.test.d.ts +0 -7
  445. package/dist/lib/newpr/hook-runner.test.d.ts.map +0 -1
  446. package/dist/lib/newpr/hook-runner.test.js +0 -422
  447. package/dist/lib/newpr/hook-runner.test.js.map +0 -1
  448. package/dist/lib/newpr/plan-generator.test.d.ts +0 -7
  449. package/dist/lib/newpr/plan-generator.test.d.ts.map +0 -1
  450. package/dist/lib/newpr/plan-generator.test.js +0 -387
  451. package/dist/lib/newpr/plan-generator.test.js.map +0 -1
  452. package/dist/lib/newpr/scenario-handler.test.d.ts +0 -2
  453. package/dist/lib/newpr/scenario-handler.test.d.ts.map +0 -1
  454. package/dist/lib/newpr/scenario-handler.test.js +0 -256
  455. package/dist/lib/newpr/scenario-handler.test.js.map +0 -1
  456. package/dist/lib/prompts.test.d.ts +0 -2
  457. package/dist/lib/prompts.test.d.ts.map +0 -1
  458. package/dist/lib/prompts.test.js +0 -807
  459. package/dist/lib/prompts.test.js.map +0 -1
  460. package/dist/lib/prs/actions.test.d.ts +0 -5
  461. package/dist/lib/prs/actions.test.d.ts.map +0 -1
  462. package/dist/lib/prs/actions.test.js +0 -356
  463. package/dist/lib/prs/actions.test.js.map +0 -1
  464. package/dist/lib/prs/data.test.d.ts +0 -5
  465. package/dist/lib/prs/data.test.d.ts.map +0 -1
  466. package/dist/lib/prs/data.test.js +0 -417
  467. package/dist/lib/prs/data.test.js.map +0 -1
  468. package/dist/lib/prs/details.test.d.ts +0 -5
  469. package/dist/lib/prs/details.test.d.ts.map +0 -1
  470. package/dist/lib/prs/details.test.js +0 -325
  471. package/dist/lib/prs/details.test.js.map +0 -1
  472. package/dist/lib/prs/filters.test.d.ts +0 -5
  473. package/dist/lib/prs/filters.test.d.ts.map +0 -1
  474. package/dist/lib/prs/filters.test.js +0 -312
  475. package/dist/lib/prs/filters.test.js.map +0 -1
  476. package/dist/lib/prs/formatters.test.d.ts +0 -2
  477. package/dist/lib/prs/formatters.test.d.ts.map +0 -1
  478. package/dist/lib/prs/formatters.test.js +0 -387
  479. package/dist/lib/prs/formatters.test.js.map +0 -1
  480. package/dist/lib/prs/interactive.test.d.ts +0 -5
  481. package/dist/lib/prs/interactive.test.d.ts.map +0 -1
  482. package/dist/lib/prs/interactive.test.js +0 -364
  483. package/dist/lib/prs/interactive.test.js.map +0 -1
  484. package/dist/lib/schema.test.d.ts +0 -10
  485. package/dist/lib/schema.test.d.ts.map +0 -1
  486. package/dist/lib/schema.test.js +0 -309
  487. package/dist/lib/schema.test.js.map +0 -1
  488. package/dist/lib/state-detection.test.d.ts +0 -2
  489. package/dist/lib/state-detection.test.d.ts.map +0 -1
  490. package/dist/lib/state-detection.test.js +0 -451
  491. package/dist/lib/state-detection.test.js.map +0 -1
  492. package/dist/lib/wtconfig/config-manager.test.d.ts +0 -5
  493. package/dist/lib/wtconfig/config-manager.test.d.ts.map +0 -1
  494. package/dist/lib/wtconfig/config-manager.test.js +0 -501
  495. package/dist/lib/wtconfig/config-manager.test.js.map +0 -1
  496. package/dist/lib/wtconfig/environment.test.d.ts +0 -5
  497. package/dist/lib/wtconfig/environment.test.d.ts.map +0 -1
  498. package/dist/lib/wtconfig/environment.test.js +0 -285
  499. package/dist/lib/wtconfig/environment.test.js.map +0 -1
  500. package/dist/lib/wtlink/config-manifest.test.d.ts +0 -2
  501. package/dist/lib/wtlink/config-manifest.test.d.ts.map +0 -1
  502. package/dist/lib/wtlink/config-manifest.test.js +0 -486
  503. package/dist/lib/wtlink/config-manifest.test.js.map +0 -1
  504. package/dist/lib/wtlink/link-configs.test.d.ts +0 -2
  505. package/dist/lib/wtlink/link-configs.test.d.ts.map +0 -1
  506. package/dist/lib/wtlink/link-configs.test.js +0 -612
  507. package/dist/lib/wtlink/link-configs.test.js.map +0 -1
  508. package/dist/lib/wtlink/main-menu.test.d.ts +0 -5
  509. package/dist/lib/wtlink/main-menu.test.d.ts.map +0 -1
  510. package/dist/lib/wtlink/main-menu.test.js +0 -126
  511. package/dist/lib/wtlink/main-menu.test.js.map +0 -1
  512. package/dist/lib/wtlink/manage-manifest.test.d.ts +0 -2
  513. package/dist/lib/wtlink/manage-manifest.test.d.ts.map +0 -1
  514. package/dist/lib/wtlink/manage-manifest.test.js +0 -714
  515. package/dist/lib/wtlink/manage-manifest.test.js.map +0 -1
  516. package/dist/lib/wtlink/validate-manifest.test.d.ts +0 -2
  517. package/dist/lib/wtlink/validate-manifest.test.d.ts.map +0 -1
  518. package/dist/lib/wtlink/validate-manifest.test.js +0 -220
  519. package/dist/lib/wtlink/validate-manifest.test.js.map +0 -1
  520. package/dist/lib/wtstate/analyze.test.d.ts +0 -5
  521. package/dist/lib/wtstate/analyze.test.d.ts.map +0 -1
  522. package/dist/lib/wtstate/analyze.test.js +0 -282
  523. package/dist/lib/wtstate/analyze.test.js.map +0 -1
  524. package/dist/lib/wtstate/args.test.d.ts +0 -5
  525. package/dist/lib/wtstate/args.test.d.ts.map +0 -1
  526. package/dist/lib/wtstate/args.test.js +0 -120
  527. package/dist/lib/wtstate/args.test.js.map +0 -1
  528. package/dist/mcp/server.test.d.ts +0 -9
  529. package/dist/mcp/server.test.d.ts.map +0 -1
  530. package/dist/mcp/server.test.js +0 -439
  531. package/dist/mcp/server.test.js.map +0 -1
@@ -1,1127 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { executeAction, createDefaultExecutorDeps, formatBranchAsTitle, } from './action-executors.js';
3
- // Mock inquirer
4
- vi.mock('inquirer', () => ({
5
- default: {
6
- prompt: vi.fn(),
7
- },
8
- }));
9
- // Mock github
10
- vi.mock('../github.js', () => ({
11
- getPr: vi.fn(),
12
- getPrByBranch: vi.fn(),
13
- createPr: vi.fn(),
14
- }));
15
- // Mock git
16
- vi.mock('../git.js', () => ({
17
- getRepoRoot: vi.fn(),
18
- removeWorktree: vi.fn(),
19
- getMainWorktreeRoot: vi.fn(),
20
- addWorktree: vi.fn(),
21
- deleteBranch: vi.fn(),
22
- exec: vi.fn(),
23
- }));
24
- // Mock child_process for spawn/spawnSync
25
- vi.mock('child_process', async () => {
26
- const actual = await vi.importActual('child_process');
27
- return {
28
- ...actual,
29
- spawnSync: vi.fn(),
30
- spawn: actual.spawn,
31
- };
32
- });
33
- import inquirer from 'inquirer';
34
- import * as github from '../github.js';
35
- import * as git from '../git.js';
36
- import { spawnSync } from 'child_process';
37
- describe('lswt/action-executors', () => {
38
- beforeEach(() => {
39
- vi.clearAllMocks();
40
- // Reset spawnSync mock for WSL path conversion
41
- vi.mocked(spawnSync).mockReturnValue({
42
- status: 0,
43
- stdout: '\\\\wsl.localhost\\Ubuntu\\home\\user\\repo',
44
- stderr: '',
45
- pid: 0,
46
- signal: null,
47
- output: ['', '\\\\wsl.localhost\\Ubuntu\\home\\user\\repo', ''],
48
- });
49
- });
50
- afterEach(() => {
51
- vi.restoreAllMocks();
52
- });
53
- const makeWorktree = (overrides = {}) => ({
54
- path: '/home/user/repo',
55
- name: 'repo',
56
- branch: 'main',
57
- commit: 'abc123',
58
- type: 'main',
59
- prNumber: null,
60
- prState: null,
61
- isDraft: null,
62
- hasChanges: false,
63
- ...overrides,
64
- });
65
- const makeEnv = (overrides = {}) => ({
66
- hasVscode: true,
67
- hasCursor: false,
68
- defaultEditor: 'vscode',
69
- platform: 'linux',
70
- isInteractive: true,
71
- shell: '/bin/bash',
72
- gitVersion: { major: 2, minor: 39, patch: 0, raw: 'git version 2.39.0' },
73
- isWSL: false,
74
- ...overrides,
75
- });
76
- const makeConfig = (overrides = {}) => ({
77
- baseBranch: 'main',
78
- worktreePattern: '{repo}.pr{number}',
79
- worktreeParent: '..',
80
- draftPr: false,
81
- sharedRepos: [],
82
- branchPrefix: 'feature',
83
- previewLabel: 'preview',
84
- syncPatterns: [],
85
- preferredEditor: 'auto',
86
- ai: { provider: 'none' },
87
- hooks: {},
88
- hookDefaults: { timeout: 30000, maxTimeout: 60000 },
89
- plugins: [],
90
- generators: {},
91
- integrations: {},
92
- logging: { level: 'info', timestamps: true },
93
- global: { warnNotGlobal: true },
94
- ...overrides,
95
- });
96
- const makeDeps = (overrides = {}) => ({
97
- execCommand: vi.fn(),
98
- spawnDetached: vi.fn(),
99
- copyToClipboard: vi.fn(),
100
- openUrl: vi.fn(),
101
- wslPathToWindows: vi.fn().mockReturnValue('\\\\wsl.localhost\\Ubuntu\\home\\user\\repo'),
102
- ...overrides,
103
- });
104
- describe('createDefaultExecutorDeps', () => {
105
- it('returns an object with all required methods', () => {
106
- const deps = createDefaultExecutorDeps();
107
- expect(deps).toHaveProperty('execCommand');
108
- expect(deps).toHaveProperty('spawnDetached');
109
- expect(deps).toHaveProperty('copyToClipboard');
110
- expect(deps).toHaveProperty('openUrl');
111
- expect(deps).toHaveProperty('wslPathToWindows');
112
- expect(typeof deps.execCommand).toBe('function');
113
- expect(typeof deps.spawnDetached).toBe('function');
114
- expect(typeof deps.copyToClipboard).toBe('function');
115
- expect(typeof deps.openUrl).toBe('function');
116
- expect(typeof deps.wslPathToWindows).toBe('function');
117
- });
118
- });
119
- describe('executeAction', () => {
120
- describe('back action', () => {
121
- it('returns success with no message', async () => {
122
- const result = await executeAction('back', makeWorktree(), makeEnv(), makeConfig());
123
- expect(result).toEqual({ success: true });
124
- });
125
- });
126
- describe('exit action', () => {
127
- it('returns success with shouldExit true', async () => {
128
- const result = await executeAction('exit', makeWorktree(), makeEnv(), makeConfig());
129
- expect(result).toEqual({ success: true, shouldExit: true });
130
- });
131
- });
132
- describe('open_editor action', () => {
133
- it('spawns VSCode when preferredEditor is vscode', async () => {
134
- const deps = makeDeps();
135
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
136
- const env = makeEnv({ hasVscode: true, defaultEditor: 'vscode' });
137
- const config = makeConfig({ preferredEditor: 'vscode' });
138
- const result = await executeAction('open_editor', worktree, env, config, deps);
139
- expect(deps.spawnDetached).toHaveBeenCalledWith('code', ['/home/user/repo.pr1']);
140
- expect(result.success).toBe(true);
141
- expect(result.message).toContain('VSCode');
142
- });
143
- it('spawns Cursor when preferredEditor is cursor', async () => {
144
- const deps = makeDeps();
145
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
146
- const env = makeEnv({ hasCursor: true, defaultEditor: 'cursor' });
147
- const config = makeConfig({ preferredEditor: 'cursor' });
148
- const result = await executeAction('open_editor', worktree, env, config, deps);
149
- expect(deps.spawnDetached).toHaveBeenCalledWith('cursor', ['/home/user/repo.pr1']);
150
- expect(result.success).toBe(true);
151
- expect(result.message).toContain('Cursor');
152
- });
153
- it('uses VSCode when preferredEditor is auto and VSCode available', async () => {
154
- const deps = makeDeps();
155
- const worktree = makeWorktree();
156
- const env = makeEnv({ hasVscode: true, hasCursor: false, defaultEditor: 'vscode' });
157
- const config = makeConfig({ preferredEditor: 'auto' });
158
- const result = await executeAction('open_editor', worktree, env, config, deps);
159
- expect(deps.spawnDetached).toHaveBeenCalledWith('code', expect.any(Array));
160
- expect(result.success).toBe(true);
161
- });
162
- it('uses Cursor when preferredEditor is auto and only Cursor available', async () => {
163
- const deps = makeDeps();
164
- const worktree = makeWorktree();
165
- const env = makeEnv({ hasVscode: false, hasCursor: true, defaultEditor: 'cursor' });
166
- const config = makeConfig({ preferredEditor: 'auto' });
167
- const result = await executeAction('open_editor', worktree, env, config, deps);
168
- expect(deps.spawnDetached).toHaveBeenCalledWith('cursor', expect.any(Array));
169
- expect(result.success).toBe(true);
170
- });
171
- it('returns error when no editor is available', async () => {
172
- const deps = makeDeps();
173
- const worktree = makeWorktree();
174
- const env = makeEnv({ hasVscode: false, hasCursor: false, defaultEditor: null });
175
- const config = makeConfig({ preferredEditor: 'auto' });
176
- const result = await executeAction('open_editor', worktree, env, config, deps);
177
- expect(result.success).toBe(false);
178
- expect(result.message).toContain('No editor found');
179
- expect(deps.spawnDetached).not.toHaveBeenCalled();
180
- });
181
- it('handles spawn errors gracefully', async () => {
182
- const deps = makeDeps({
183
- spawnDetached: vi.fn().mockImplementation(() => {
184
- throw new Error('spawn failed');
185
- }),
186
- });
187
- const worktree = makeWorktree();
188
- const env = makeEnv({ hasVscode: true, defaultEditor: 'vscode' });
189
- const config = makeConfig();
190
- const result = await executeAction('open_editor', worktree, env, config, deps);
191
- expect(result.success).toBe(false);
192
- expect(result.message).toContain('Failed to open editor');
193
- });
194
- });
195
- describe('copy_path action', () => {
196
- it('copies path to clipboard', async () => {
197
- const deps = makeDeps();
198
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
199
- const result = await executeAction('copy_path', worktree, makeEnv(), makeConfig(), deps);
200
- expect(deps.copyToClipboard).toHaveBeenCalledWith('/home/user/repo.pr1');
201
- expect(result.success).toBe(true);
202
- expect(result.message).toContain('Copied');
203
- expect(result.message).toContain('/home/user/repo.pr1');
204
- });
205
- it('handles clipboard errors gracefully', async () => {
206
- const deps = makeDeps({
207
- copyToClipboard: vi.fn().mockImplementation(() => {
208
- throw new Error('clipboard unavailable');
209
- }),
210
- });
211
- const worktree = makeWorktree();
212
- const result = await executeAction('copy_path', worktree, makeEnv(), makeConfig(), deps);
213
- expect(result.success).toBe(false);
214
- expect(result.message).toContain('Failed to copy');
215
- });
216
- });
217
- describe('show_details action', () => {
218
- it('returns success (details are printed to console)', async () => {
219
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
220
- const worktree = makeWorktree({
221
- type: 'pr',
222
- prNumber: 42,
223
- prState: 'OPEN',
224
- branch: 'feature-42',
225
- });
226
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
227
- expect(result.success).toBe(true);
228
- consoleSpy.mockRestore();
229
- });
230
- });
231
- describe('open_pr_url action', () => {
232
- it('returns error when worktree has no PR number', async () => {
233
- const deps = makeDeps();
234
- const worktree = makeWorktree({ type: 'branch', prNumber: null });
235
- const result = await executeAction('open_pr_url', worktree, makeEnv(), makeConfig(), deps);
236
- expect(result.success).toBe(false);
237
- expect(result.message).toContain('No PR associated');
238
- expect(deps.openUrl).not.toHaveBeenCalled();
239
- });
240
- });
241
- });
242
- describe('createDefaultExecutorDeps', () => {
243
- it('returns object with all required methods', () => {
244
- const deps = createDefaultExecutorDeps();
245
- expect(deps).toHaveProperty('execCommand');
246
- expect(deps).toHaveProperty('spawnDetached');
247
- expect(deps).toHaveProperty('copyToClipboard');
248
- expect(deps).toHaveProperty('openUrl');
249
- expect(deps).toHaveProperty('wslPathToWindows');
250
- expect(typeof deps.execCommand).toBe('function');
251
- expect(typeof deps.spawnDetached).toBe('function');
252
- expect(typeof deps.copyToClipboard).toBe('function');
253
- expect(typeof deps.openUrl).toBe('function');
254
- expect(typeof deps.wslPathToWindows).toBe('function');
255
- });
256
- });
257
- describe('open_terminal action', () => {
258
- it('spawns terminal on Linux', async () => {
259
- const deps = makeDeps();
260
- const worktree = makeWorktree({ path: '/home/user/repo' });
261
- const env = makeEnv({ platform: 'linux' });
262
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
263
- // Should attempt to spawn a terminal
264
- expect(deps.spawnDetached).toHaveBeenCalled();
265
- expect(result.success).toBe(true);
266
- expect(result.message).toContain('terminal');
267
- });
268
- it('uses Windows Terminal via cmd.exe in WSL', async () => {
269
- const deps = makeDeps();
270
- const worktree = makeWorktree({ path: '/home/user/repo' });
271
- const env = makeEnv({ platform: 'linux', isWSL: true });
272
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
273
- // Should call deps.wslPathToWindows to convert path
274
- expect(deps.wslPathToWindows).toHaveBeenCalledWith('/home/user/repo');
275
- // Should try to use cmd.exe to launch Windows Terminal
276
- expect(deps.execCommand).toHaveBeenCalledWith(expect.stringContaining('cmd.exe'));
277
- expect(result.success).toBe(true);
278
- });
279
- it('shows cd command fallback when WSL Windows Terminal fails', async () => {
280
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
281
- const deps = makeDeps({
282
- execCommand: vi.fn().mockImplementation(() => {
283
- throw new Error('cmd.exe failed');
284
- }),
285
- });
286
- const worktree = makeWorktree({ path: '/home/user/repo' });
287
- const env = makeEnv({ platform: 'linux', isWSL: true });
288
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
289
- // Should fall back to showing cd command
290
- expect(result.success).toBe(true);
291
- expect(result.message).toContain('copy the cd command');
292
- // Should print cd command to console
293
- const output = consoleSpy.mock.calls.map((c) => String(c[0])).join('\n');
294
- expect(output).toContain('cd');
295
- consoleSpy.mockRestore();
296
- });
297
- it('uses osascript on macOS', async () => {
298
- const deps = makeDeps();
299
- const worktree = makeWorktree({ path: '/Users/user/repo' });
300
- const env = makeEnv({ platform: 'darwin' });
301
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
302
- expect(deps.execCommand).toHaveBeenCalledWith(expect.stringContaining('osascript'));
303
- expect(result.success).toBe(true);
304
- });
305
- it('tries Windows Terminal on Windows', async () => {
306
- const deps = makeDeps();
307
- const worktree = makeWorktree({ path: 'C:\\Users\\user\\repo' });
308
- const env = makeEnv({ platform: 'win32' });
309
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
310
- // Should try wt first
311
- expect(deps.spawnDetached).toHaveBeenCalledWith('wt', expect.any(Array));
312
- expect(result.success).toBe(true);
313
- });
314
- it('falls back to cmd on Windows when wt fails', async () => {
315
- let callCount = 0;
316
- const deps = makeDeps({
317
- spawnDetached: vi.fn().mockImplementation((cmd) => {
318
- callCount++;
319
- if (cmd === 'wt') {
320
- throw new Error('wt not found');
321
- }
322
- }),
323
- });
324
- const worktree = makeWorktree({ path: 'C:\\Users\\user\\repo' });
325
- const env = makeEnv({ platform: 'win32' });
326
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
327
- // Should have tried wt, then cmd
328
- expect(callCount).toBe(2);
329
- expect(result.success).toBe(true);
330
- });
331
- it('handles no terminal available on Linux', async () => {
332
- const deps = makeDeps({
333
- spawnDetached: vi.fn().mockImplementation(() => {
334
- throw new Error('terminal not found');
335
- }),
336
- });
337
- const worktree = makeWorktree();
338
- const env = makeEnv({ platform: 'linux' });
339
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
340
- expect(result.success).toBe(false);
341
- expect(result.message).toContain('No terminal emulator found');
342
- });
343
- });
344
- describe('remove_worktree action', () => {
345
- it('returns error for main worktree', async () => {
346
- const worktree = makeWorktree({ type: 'main' });
347
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
348
- expect(result.success).toBe(false);
349
- expect(result.message).toContain('Cannot remove main worktree');
350
- });
351
- it('cancels when user declines confirmation', async () => {
352
- vi.mocked(inquirer.prompt).mockResolvedValue({ confirm: false });
353
- const worktree = makeWorktree({
354
- type: 'branch',
355
- branch: 'feature-branch',
356
- name: 'feature-branch',
357
- });
358
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
359
- expect(result.success).toBe(true);
360
- expect(result.message).toBe('Cancelled');
361
- expect(git.removeWorktree).not.toHaveBeenCalled();
362
- });
363
- it('removes worktree when user confirms', async () => {
364
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
365
- vi.mocked(inquirer.prompt).mockResolvedValue({ confirm: true });
366
- vi.mocked(git.removeWorktree).mockImplementation(() => { });
367
- const worktree = makeWorktree({
368
- type: 'branch',
369
- branch: 'feature-branch',
370
- name: 'my-worktree',
371
- });
372
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
373
- expect(result.success).toBe(true);
374
- expect(result.message).toContain('Removed worktree');
375
- expect(result.shouldRefresh).toBe(true);
376
- expect(git.removeWorktree).toHaveBeenCalledWith(worktree.path);
377
- consoleSpy.mockRestore();
378
- });
379
- it('warns about uncommitted changes', async () => {
380
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
381
- vi.mocked(inquirer.prompt).mockResolvedValue({ confirm: false });
382
- const worktree = makeWorktree({
383
- type: 'branch',
384
- branch: 'dirty-branch',
385
- hasChanges: true,
386
- });
387
- await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
388
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
389
- expect(output).toContain('uncommitted changes');
390
- consoleSpy.mockRestore();
391
- });
392
- it('prompts to delete branch for merged PR worktrees', async () => {
393
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
394
- vi.mocked(inquirer.prompt)
395
- .mockResolvedValueOnce({ confirm: true })
396
- .mockResolvedValueOnce({ shouldDelete: true });
397
- vi.mocked(git.removeWorktree).mockImplementation(() => { });
398
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/home/user/repo');
399
- const worktree = makeWorktree({
400
- type: 'pr',
401
- prNumber: 42,
402
- prState: 'MERGED',
403
- branch: 'feature-42',
404
- name: 'repo.pr42',
405
- });
406
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
407
- expect(result.success).toBe(true);
408
- expect(inquirer.prompt).toHaveBeenCalledTimes(2);
409
- consoleSpy.mockRestore();
410
- });
411
- it('prompts to delete branch for closed PR worktrees', async () => {
412
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
413
- vi.mocked(inquirer.prompt)
414
- .mockResolvedValueOnce({ confirm: true })
415
- .mockResolvedValueOnce({ shouldDelete: false });
416
- vi.mocked(git.removeWorktree).mockImplementation(() => { });
417
- const worktree = makeWorktree({
418
- type: 'pr',
419
- prNumber: 42,
420
- prState: 'CLOSED',
421
- branch: 'feature-42',
422
- name: 'repo.pr42',
423
- });
424
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
425
- expect(result.success).toBe(true);
426
- expect(inquirer.prompt).toHaveBeenCalledTimes(2);
427
- consoleSpy.mockRestore();
428
- });
429
- it('continues successfully when branch deletion fails', async () => {
430
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
431
- vi.mocked(inquirer.prompt)
432
- .mockResolvedValueOnce({ confirm: true })
433
- .mockResolvedValueOnce({ shouldDelete: true });
434
- vi.mocked(git.removeWorktree).mockImplementation(() => { });
435
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/home/user/repo');
436
- // Branch deletion fails (branch might not exist locally)
437
- vi.mocked(git.deleteBranch).mockImplementation(() => {
438
- throw new Error('Branch not found');
439
- });
440
- const worktree = makeWorktree({
441
- type: 'pr',
442
- prNumber: 42,
443
- prState: 'MERGED',
444
- branch: 'feature-42',
445
- name: 'repo.pr42',
446
- });
447
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
448
- // Should still succeed even though branch deletion failed
449
- expect(result.success).toBe(true);
450
- expect(result.message).toContain('Removed worktree');
451
- consoleSpy.mockRestore();
452
- });
453
- it('handles removal failure', async () => {
454
- vi.mocked(inquirer.prompt).mockResolvedValue({ confirm: true });
455
- vi.mocked(git.removeWorktree).mockImplementation(() => {
456
- throw new Error('Worktree has changes');
457
- });
458
- const worktree = makeWorktree({
459
- type: 'branch',
460
- branch: 'feature-branch',
461
- name: 'my-worktree',
462
- });
463
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
464
- expect(result.success).toBe(false);
465
- expect(result.message).toContain('Failed to remove worktree');
466
- });
467
- });
468
- describe('create_pr action', () => {
469
- it('returns error for detached HEAD', async () => {
470
- const worktree = makeWorktree({ type: 'detached', branch: null });
471
- const result = await executeAction('create_pr', worktree, makeEnv(), makeConfig(), makeDeps());
472
- expect(result.success).toBe(false);
473
- expect(result.message).toContain('detached HEAD');
474
- });
475
- it('returns error when PR already exists for branch', async () => {
476
- vi.mocked(github.getPrByBranch).mockReturnValue({
477
- number: 42,
478
- url: 'https://github.com/owner/repo/pull/42',
479
- state: 'OPEN',
480
- isDraft: false,
481
- title: 'Existing PR',
482
- headBranch: 'feature-branch',
483
- baseBranch: 'main',
484
- });
485
- const worktree = makeWorktree({
486
- type: 'branch',
487
- branch: 'feature-branch',
488
- });
489
- const result = await executeAction('create_pr', worktree, makeEnv(), makeConfig(), makeDeps());
490
- expect(result.success).toBe(false);
491
- expect(result.message).toContain('PR already exists');
492
- expect(result.message).toContain('#42');
493
- });
494
- it('creates PR successfully with configured draftPr', async () => {
495
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
496
- vi.mocked(github.getPrByBranch).mockReturnValue(null);
497
- vi.mocked(github.createPr).mockReturnValue({
498
- number: 123,
499
- url: 'https://github.com/owner/repo/pull/123',
500
- state: 'OPEN',
501
- isDraft: true,
502
- title: 'New PR',
503
- headBranch: 'feature-branch',
504
- baseBranch: 'main',
505
- });
506
- vi.mocked(inquirer.prompt).mockResolvedValue({ title: 'My New PR' });
507
- const worktree = makeWorktree({
508
- type: 'branch',
509
- branch: 'feature-branch',
510
- });
511
- const config = makeConfig({ draftPr: true });
512
- const result = await executeAction('create_pr', worktree, makeEnv(), config, makeDeps());
513
- expect(result.success).toBe(true);
514
- expect(result.message).toContain('Created PR #123');
515
- expect(result.shouldRefresh).toBe(true);
516
- expect(github.createPr).toHaveBeenCalledWith(expect.objectContaining({ draft: true }), expect.any(String));
517
- consoleSpy.mockRestore();
518
- });
519
- it('prompts for draft status when not configured', async () => {
520
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
521
- vi.mocked(github.getPrByBranch).mockReturnValue(null);
522
- vi.mocked(github.createPr).mockReturnValue({
523
- number: 123,
524
- url: 'https://github.com/owner/repo/pull/123',
525
- state: 'OPEN',
526
- isDraft: false,
527
- title: 'New PR',
528
- headBranch: 'feature-branch',
529
- baseBranch: 'main',
530
- });
531
- vi.mocked(inquirer.prompt)
532
- .mockResolvedValueOnce({ title: 'My PR Title' })
533
- .mockResolvedValueOnce({ draft: false });
534
- const worktree = makeWorktree({
535
- type: 'branch',
536
- branch: 'feature-branch',
537
- });
538
- const config = makeConfig({ draftPr: undefined });
539
- const result = await executeAction('create_pr', worktree, makeEnv(), config, makeDeps());
540
- expect(result.success).toBe(true);
541
- expect(inquirer.prompt).toHaveBeenCalledTimes(2);
542
- consoleSpy.mockRestore();
543
- });
544
- it('handles PR creation failure', async () => {
545
- vi.mocked(github.getPrByBranch).mockReturnValue(null);
546
- vi.mocked(github.createPr).mockImplementation(() => {
547
- throw new Error('GitHub API error');
548
- });
549
- vi.mocked(inquirer.prompt).mockResolvedValue({ title: 'My PR' });
550
- const worktree = makeWorktree({
551
- type: 'branch',
552
- branch: 'feature-branch',
553
- });
554
- const config = makeConfig({ draftPr: false });
555
- const result = await executeAction('create_pr', worktree, makeEnv(), config, makeDeps());
556
- expect(result.success).toBe(false);
557
- expect(result.message).toContain('Failed to create PR');
558
- });
559
- });
560
- describe('link_configs action', () => {
561
- it('returns error when repo root not found', async () => {
562
- vi.mocked(git.getRepoRoot).mockReturnValue(null);
563
- const worktree = makeWorktree({
564
- type: 'branch',
565
- branch: 'feature-branch',
566
- });
567
- const result = await executeAction('link_configs', worktree, makeEnv(), makeConfig(), makeDeps());
568
- expect(result.success).toBe(false);
569
- expect(result.message).toContain('Could not find repository root');
570
- });
571
- it('returns error for main worktree', async () => {
572
- vi.mocked(git.getRepoRoot).mockReturnValue('/home/user/repo');
573
- const worktree = makeWorktree({ type: 'main' });
574
- const result = await executeAction('link_configs', worktree, makeEnv(), makeConfig(), makeDeps());
575
- expect(result.success).toBe(false);
576
- expect(result.message).toContain('Cannot link configs to main worktree');
577
- });
578
- it('handles link configs failure', async () => {
579
- vi.mocked(git.getRepoRoot).mockReturnValue('/home/user/repo');
580
- // Mock the dynamic import to throw
581
- vi.doMock('../wtlink/link-configs.js', () => ({
582
- run: vi.fn().mockRejectedValue(new Error('Link failed')),
583
- }));
584
- const worktree = makeWorktree({
585
- type: 'branch',
586
- branch: 'feature-branch',
587
- path: '/home/user/feature-branch',
588
- });
589
- const result = await executeAction('link_configs', worktree, makeEnv(), makeConfig(), makeDeps());
590
- // Either succeeds or fails with proper message
591
- if (!result.success) {
592
- expect(result.message).toContain('Failed to link configs');
593
- }
594
- });
595
- it('successfully links configs', async () => {
596
- vi.mocked(git.getRepoRoot).mockReturnValue('/home/user/repo');
597
- // Mock the dynamic import to succeed
598
- vi.doMock('../wtlink/link-configs.js', () => ({
599
- run: vi.fn().mockResolvedValue(undefined),
600
- }));
601
- const worktree = makeWorktree({
602
- type: 'branch',
603
- branch: 'feature-branch',
604
- path: '/home/user/feature-branch',
605
- });
606
- const result = await executeAction('link_configs', worktree, makeEnv(), makeConfig(), makeDeps());
607
- // Should succeed
608
- expect(result.success).toBe(true);
609
- expect(result.message).toContain('linked successfully');
610
- });
611
- });
612
- describe('show_details action', () => {
613
- it('shows draft indicator for draft PRs', async () => {
614
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
615
- const worktree = makeWorktree({
616
- type: 'pr',
617
- prNumber: 42,
618
- prState: 'OPEN',
619
- isDraft: true,
620
- branch: 'feature-42',
621
- });
622
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
623
- expect(result.success).toBe(true);
624
- // Check that draft was mentioned in output
625
- const calls = consoleSpy.mock.calls.map((call) => String(call[0]));
626
- expect(calls.some((c) => c.includes('Draft'))).toBe(true);
627
- consoleSpy.mockRestore();
628
- });
629
- it('shows branch worktree details', async () => {
630
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
631
- const worktree = makeWorktree({
632
- type: 'branch',
633
- branch: 'feature-branch',
634
- hasChanges: true,
635
- });
636
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
637
- expect(result.success).toBe(true);
638
- consoleSpy.mockRestore();
639
- });
640
- it('shows detached worktree details', async () => {
641
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
642
- const worktree = makeWorktree({
643
- type: 'detached',
644
- branch: null,
645
- });
646
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
647
- expect(result.success).toBe(true);
648
- consoleSpy.mockRestore();
649
- });
650
- it('shows all worktree fields in output', async () => {
651
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
652
- const worktree = makeWorktree({
653
- path: '/home/user/repo.pr1',
654
- name: 'repo.pr1',
655
- type: 'pr',
656
- prNumber: 123,
657
- prState: 'OPEN',
658
- branch: 'feat/test',
659
- commit: 'abc123def',
660
- hasChanges: false,
661
- });
662
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
663
- expect(result.success).toBe(true);
664
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
665
- expect(output).toContain('Path');
666
- expect(output).toContain('Name');
667
- expect(output).toContain('Branch');
668
- expect(output).toContain('Commit');
669
- expect(output).toContain('Type');
670
- consoleSpy.mockRestore();
671
- });
672
- it('shows clean status for worktree without changes', async () => {
673
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
674
- const worktree = makeWorktree({
675
- type: 'branch',
676
- branch: 'clean-branch',
677
- hasChanges: false,
678
- });
679
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
680
- expect(result.success).toBe(true);
681
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
682
- expect(output).toContain('Clean');
683
- consoleSpy.mockRestore();
684
- });
685
- it('shows uncommitted changes warning', async () => {
686
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
687
- const worktree = makeWorktree({
688
- type: 'branch',
689
- branch: 'dirty-branch',
690
- hasChanges: true,
691
- });
692
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
693
- expect(result.success).toBe(true);
694
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
695
- expect(output).toContain('uncommitted');
696
- consoleSpy.mockRestore();
697
- });
698
- it('shows PR URL from github.getPr when prUrl is not stored', async () => {
699
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
700
- vi.mocked(github.getPr).mockReturnValue({
701
- number: 42,
702
- url: 'https://github.com/owner/repo/pull/42',
703
- state: 'OPEN',
704
- isDraft: false,
705
- title: 'Test PR',
706
- headBranch: 'feature-42',
707
- baseBranch: 'main',
708
- });
709
- const worktree = makeWorktree({
710
- type: 'pr',
711
- prNumber: 42,
712
- prState: 'OPEN',
713
- branch: 'feature-42',
714
- // prUrl is not set
715
- });
716
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
717
- expect(result.success).toBe(true);
718
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
719
- expect(output).toContain('https://github.com/owner/repo/pull/42');
720
- consoleSpy.mockRestore();
721
- });
722
- it('handles PR URL fetch failure gracefully', async () => {
723
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
724
- vi.mocked(github.getPr).mockReturnValue(null);
725
- const worktree = makeWorktree({
726
- type: 'pr',
727
- prNumber: 42,
728
- prState: 'OPEN',
729
- branch: 'feature-42',
730
- // prUrl is not set
731
- });
732
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
733
- // Should succeed even if PR URL fetch fails
734
- expect(result.success).toBe(true);
735
- consoleSpy.mockRestore();
736
- });
737
- it('shows recent commits when git log succeeds', async () => {
738
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
739
- vi.mocked(git.exec).mockReturnValue('abc1234 First commit\ndef5678 Second commit\nghi9012 Third commit');
740
- const worktree = makeWorktree({
741
- type: 'branch',
742
- branch: 'feature-branch',
743
- });
744
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
745
- expect(result.success).toBe(true);
746
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
747
- expect(output).toContain('Recent commits');
748
- expect(output).toContain('First commit');
749
- consoleSpy.mockRestore();
750
- });
751
- it('handles git log failure gracefully', async () => {
752
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
753
- vi.mocked(git.exec).mockImplementation(() => {
754
- throw new Error('git log failed');
755
- });
756
- const worktree = makeWorktree({
757
- type: 'branch',
758
- branch: 'feature-branch',
759
- });
760
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
761
- // Should succeed even if git log fails
762
- expect(result.success).toBe(true);
763
- consoleSpy.mockRestore();
764
- });
765
- });
766
- describe('open_pr_url action with mocked github', () => {
767
- it('returns error when PR is not found', async () => {
768
- vi.mocked(github.getPr).mockReturnValue(null);
769
- const deps = makeDeps();
770
- const worktree = makeWorktree({
771
- type: 'pr',
772
- prNumber: 999,
773
- prState: 'OPEN',
774
- });
775
- const result = await executeAction('open_pr_url', worktree, makeEnv(), makeConfig(), deps);
776
- expect(result.success).toBe(false);
777
- expect(result.message).toContain('Could not find PR');
778
- });
779
- it('opens PR URL successfully when PR is found', async () => {
780
- vi.mocked(github.getPr).mockReturnValue({
781
- number: 42,
782
- url: 'https://github.com/owner/repo/pull/42',
783
- state: 'OPEN',
784
- isDraft: false,
785
- title: 'Test PR',
786
- headBranch: 'feature-42',
787
- baseBranch: 'main',
788
- });
789
- const deps = makeDeps();
790
- const worktree = makeWorktree({
791
- type: 'pr',
792
- prNumber: 42,
793
- prState: 'OPEN',
794
- });
795
- const result = await executeAction('open_pr_url', worktree, makeEnv(), makeConfig(), deps);
796
- expect(result.success).toBe(true);
797
- expect(result.message).toContain('Opened PR');
798
- expect(deps.openUrl).toHaveBeenCalledWith('https://github.com/owner/repo/pull/42');
799
- });
800
- it('handles error when opening URL fails', async () => {
801
- vi.mocked(github.getPr).mockReturnValue({
802
- number: 42,
803
- url: 'https://github.com/owner/repo/pull/42',
804
- state: 'OPEN',
805
- isDraft: false,
806
- title: 'Test PR',
807
- headBranch: 'feature-42',
808
- baseBranch: 'main',
809
- });
810
- const deps = makeDeps({
811
- openUrl: vi.fn().mockImplementation(() => {
812
- throw new Error('Failed to open browser');
813
- }),
814
- });
815
- const worktree = makeWorktree({
816
- type: 'pr',
817
- prNumber: 42,
818
- prState: 'OPEN',
819
- });
820
- const result = await executeAction('open_pr_url', worktree, makeEnv(), makeConfig(), deps);
821
- expect(result.success).toBe(false);
822
- expect(result.message).toContain('Failed to open PR');
823
- });
824
- });
825
- describe('open_editor action edge cases', () => {
826
- it('prefers vscode when preferredEditor is vscode even if cursor available', async () => {
827
- const deps = makeDeps();
828
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
829
- const env = makeEnv({ hasVscode: true, hasCursor: true, defaultEditor: 'vscode' });
830
- const config = makeConfig({ preferredEditor: 'vscode' });
831
- const result = await executeAction('open_editor', worktree, env, config, deps);
832
- expect(deps.spawnDetached).toHaveBeenCalledWith('code', ['/home/user/repo.pr1']);
833
- expect(result.success).toBe(true);
834
- });
835
- it('prefers cursor when preferredEditor is cursor even if vscode available', async () => {
836
- const deps = makeDeps();
837
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
838
- const env = makeEnv({ hasVscode: true, hasCursor: true, defaultEditor: 'vscode' });
839
- const config = makeConfig({ preferredEditor: 'cursor' });
840
- const result = await executeAction('open_editor', worktree, env, config, deps);
841
- expect(deps.spawnDetached).toHaveBeenCalledWith('cursor', ['/home/user/repo.pr1']);
842
- expect(result.success).toBe(true);
843
- });
844
- it('falls back to cursor when preferredEditor is vscode but vscode not installed', async () => {
845
- const deps = makeDeps();
846
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
847
- const env = makeEnv({ hasVscode: false, hasCursor: true, defaultEditor: 'cursor' });
848
- const config = makeConfig({ preferredEditor: 'vscode' });
849
- const result = await executeAction('open_editor', worktree, env, config, deps);
850
- expect(deps.spawnDetached).toHaveBeenCalledWith('cursor', ['/home/user/repo.pr1']);
851
- expect(result.success).toBe(true);
852
- expect(result.message).toContain('Cursor');
853
- });
854
- it('falls back to vscode when preferredEditor is cursor but cursor not installed', async () => {
855
- const deps = makeDeps();
856
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
857
- const env = makeEnv({ hasVscode: true, hasCursor: false, defaultEditor: 'vscode' });
858
- const config = makeConfig({ preferredEditor: 'cursor' });
859
- const result = await executeAction('open_editor', worktree, env, config, deps);
860
- expect(deps.spawnDetached).toHaveBeenCalledWith('code', ['/home/user/repo.pr1']);
861
- expect(result.success).toBe(true);
862
- expect(result.message).toContain('VSCode');
863
- });
864
- });
865
- describe('copy_path action', () => {
866
- it('copies the correct path', async () => {
867
- const deps = makeDeps();
868
- const worktree = makeWorktree({ path: '/home/user/my-project' });
869
- const result = await executeAction('copy_path', worktree, makeEnv(), makeConfig(), deps);
870
- expect(deps.copyToClipboard).toHaveBeenCalledWith('/home/user/my-project');
871
- expect(result.success).toBe(true);
872
- expect(result.message).toContain('/home/user/my-project');
873
- });
874
- });
875
- describe('formatBranchAsTitle', () => {
876
- it('removes feat/ prefix', () => {
877
- expect(formatBranchAsTitle('feat/add-new-api')).toBe('Add new api');
878
- });
879
- it('removes fix/ prefix', () => {
880
- expect(formatBranchAsTitle('fix/bad-login')).toBe('Bad login');
881
- });
882
- it('removes chore/ prefix', () => {
883
- expect(formatBranchAsTitle('chore/update-deps')).toBe('Update deps');
884
- });
885
- it('removes docs/ prefix', () => {
886
- expect(formatBranchAsTitle('docs/add-guide')).toBe('Add guide');
887
- });
888
- it('removes refactor/ prefix', () => {
889
- expect(formatBranchAsTitle('refactor/clean-code')).toBe('Clean code');
890
- });
891
- it('removes test/ prefix', () => {
892
- expect(formatBranchAsTitle('test/add-more-tests')).toBe('Add more tests');
893
- });
894
- it('removes style/ prefix', () => {
895
- expect(formatBranchAsTitle('style/fix-lint')).toBe('Fix lint');
896
- });
897
- it('removes feature/ prefix', () => {
898
- expect(formatBranchAsTitle('feature/new-api')).toBe('New api');
899
- });
900
- it('removes bugfix/ prefix', () => {
901
- expect(formatBranchAsTitle('bugfix/fix-null')).toBe('Fix null');
902
- });
903
- it('removes trailing random suffixes', () => {
904
- expect(formatBranchAsTitle('feat/add-login-abc123')).toBe('Add login');
905
- expect(formatBranchAsTitle('add-feature-xyz789')).toBe('Add feature');
906
- });
907
- it('replaces hyphens with spaces', () => {
908
- // Note: 'branch' is 6 chars so it gets removed as a suffix
909
- expect(formatBranchAsTitle('my-feature-thing')).toBe('My feature thing');
910
- });
911
- it('replaces underscores with spaces', () => {
912
- expect(formatBranchAsTitle('my_new_api')).toBe('My new api');
913
- });
914
- it('capitalizes first letter', () => {
915
- expect(formatBranchAsTitle('lower')).toBe('Lower');
916
- });
917
- it('removes trailing 6+ char random suffix', () => {
918
- // Words like 'branch' (6 chars) are also removed - intentional behavior
919
- expect(formatBranchAsTitle('my-feature-branch')).toBe('My feature');
920
- });
921
- it('handles already capitalized input', () => {
922
- expect(formatBranchAsTitle('Already-Capitalized')).toBe('Already Capitalized');
923
- });
924
- it('handles simple branch name', () => {
925
- expect(formatBranchAsTitle('main')).toBe('Main');
926
- });
927
- it('handles complex branch names', () => {
928
- expect(formatBranchAsTitle('feat/make-lswt-more-interactive-b5y1o2')).toBe('Make lswt more interactive');
929
- });
930
- it('handles mixed separators', () => {
931
- expect(formatBranchAsTitle('my_feature-branch_name')).toBe('My feature branch name');
932
- });
933
- });
934
- describe('checkout_pr action', () => {
935
- it('returns error for non-remote_pr worktree type', async () => {
936
- const worktree = makeWorktree({
937
- type: 'pr',
938
- prNumber: 42,
939
- prState: 'OPEN',
940
- });
941
- const result = await executeAction('checkout_pr', worktree, makeEnv(), makeConfig(), makeDeps());
942
- expect(result.success).toBe(false);
943
- expect(result.message).toContain('Can only checkout remote PRs');
944
- });
945
- it('returns error when worktree has no PR number', async () => {
946
- const worktree = makeWorktree({
947
- type: 'remote_pr',
948
- prNumber: null,
949
- prState: 'OPEN',
950
- });
951
- const result = await executeAction('checkout_pr', worktree, makeEnv(), makeConfig(), makeDeps());
952
- expect(result.success).toBe(false);
953
- expect(result.message).toContain('Can only checkout remote PRs');
954
- });
955
- it('returns error when worktree has no branch', async () => {
956
- const worktree = makeWorktree({
957
- type: 'remote_pr',
958
- prNumber: 42,
959
- prState: 'OPEN',
960
- branch: null,
961
- });
962
- const result = await executeAction('checkout_pr', worktree, makeEnv(), makeConfig(), makeDeps());
963
- expect(result.success).toBe(false);
964
- expect(result.message).toContain('no associated branch');
965
- });
966
- it('returns error when repo root cannot be found', async () => {
967
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue(null);
968
- const worktree = makeWorktree({
969
- type: 'remote_pr',
970
- prNumber: 42,
971
- prState: 'OPEN',
972
- branch: 'feat/remote-feature',
973
- });
974
- const result = await executeAction('checkout_pr', worktree, makeEnv(), makeConfig(), makeDeps());
975
- expect(result.success).toBe(false);
976
- expect(result.message).toContain('Could not find repository root');
977
- });
978
- it('successfully creates worktree for remote PR', async () => {
979
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
980
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/home/user/repo');
981
- vi.mocked(git.addWorktree).mockImplementation(() => { });
982
- // Mock git.exec to return empty string (git fetch succeeds)
983
- vi.mocked(git.exec).mockReturnValue('');
984
- const worktree = makeWorktree({
985
- type: 'remote_pr',
986
- prNumber: 42,
987
- prState: 'OPEN',
988
- branch: 'feat/remote-feature',
989
- prTitle: 'Add remote feature',
990
- prUrl: 'https://github.com/owner/repo/pull/42',
991
- });
992
- const config = makeConfig({ worktreePattern: '{repo}.pr{number}', worktreeParent: '..' });
993
- const result = await executeAction('checkout_pr', worktree, makeEnv(), config, makeDeps());
994
- expect(result.success).toBe(true);
995
- expect(result.message).toContain('Created worktree for PR #42');
996
- expect(result.shouldRefresh).toBe(true);
997
- expect(git.addWorktree).toHaveBeenCalledWith(expect.stringContaining('.pr42'), 'feat/remote-feature', expect.objectContaining({ cwd: '/home/user/repo' }));
998
- consoleSpy.mockRestore();
999
- });
1000
- it('handles git fetch failure', async () => {
1001
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
1002
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/home/user/repo');
1003
- // Mock git.exec to throw (git fetch fails)
1004
- vi.mocked(git.exec).mockImplementation(() => {
1005
- throw new Error('Failed to fetch branch');
1006
- });
1007
- const worktree = makeWorktree({
1008
- type: 'remote_pr',
1009
- prNumber: 42,
1010
- prState: 'OPEN',
1011
- branch: 'feat/remote-feature',
1012
- });
1013
- const result = await executeAction('checkout_pr', worktree, makeEnv(), makeConfig(), makeDeps());
1014
- expect(result.success).toBe(false);
1015
- expect(result.message).toContain('Failed to checkout PR');
1016
- consoleSpy.mockRestore();
1017
- });
1018
- });
1019
- describe('show_details action for remote_pr', () => {
1020
- it('shows PR title for remote PRs', async () => {
1021
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
1022
- const worktree = makeWorktree({
1023
- type: 'remote_pr',
1024
- prNumber: 42,
1025
- prState: 'OPEN',
1026
- branch: 'feat/remote-feature',
1027
- prTitle: 'Add amazing new feature',
1028
- prUrl: 'https://github.com/owner/repo/pull/42',
1029
- });
1030
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
1031
- expect(result.success).toBe(true);
1032
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
1033
- expect(output).toContain('Add amazing new feature');
1034
- consoleSpy.mockRestore();
1035
- });
1036
- it('shows PR URL for remote PRs', async () => {
1037
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
1038
- const worktree = makeWorktree({
1039
- type: 'remote_pr',
1040
- prNumber: 42,
1041
- prState: 'OPEN',
1042
- branch: 'feat/remote-feature',
1043
- prTitle: 'Add feature',
1044
- prUrl: 'https://github.com/owner/repo/pull/42',
1045
- });
1046
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
1047
- expect(result.success).toBe(true);
1048
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
1049
- expect(output).toContain('https://github.com/owner/repo/pull/42');
1050
- consoleSpy.mockRestore();
1051
- });
1052
- it('shows message about no local checkout for remote PRs', async () => {
1053
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
1054
- const worktree = makeWorktree({
1055
- type: 'remote_pr',
1056
- prNumber: 42,
1057
- prState: 'OPEN',
1058
- branch: 'feat/remote-feature',
1059
- prTitle: 'Add feature',
1060
- prUrl: 'https://github.com/owner/repo/pull/42',
1061
- });
1062
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
1063
- expect(result.success).toBe(true);
1064
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
1065
- expect(output).toContain('No local checkout');
1066
- consoleSpy.mockRestore();
1067
- });
1068
- it('does not show "Changes" line for remote PRs', async () => {
1069
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
1070
- const worktree = makeWorktree({
1071
- type: 'remote_pr',
1072
- prNumber: 42,
1073
- prState: 'OPEN',
1074
- branch: 'feat/remote-feature',
1075
- prTitle: 'Add feature',
1076
- prUrl: 'https://github.com/owner/repo/pull/42',
1077
- hasChanges: false,
1078
- });
1079
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
1080
- expect(result.success).toBe(true);
1081
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
1082
- // For remote PRs, "Changes:" line should not appear since there's no local path
1083
- expect(output).not.toMatch(/Changes:.*Clean/);
1084
- consoleSpy.mockRestore();
1085
- });
1086
- });
1087
- describe('open_pr_url action for remote_pr', () => {
1088
- it('uses stored prUrl for remote PRs', async () => {
1089
- const deps = makeDeps();
1090
- const worktree = makeWorktree({
1091
- type: 'remote_pr',
1092
- prNumber: 42,
1093
- prState: 'OPEN',
1094
- prUrl: 'https://github.com/owner/repo/pull/42',
1095
- });
1096
- const result = await executeAction('open_pr_url', worktree, makeEnv(), makeConfig(), deps);
1097
- expect(result.success).toBe(true);
1098
- expect(result.message).toContain('Opened PR #42');
1099
- expect(deps.openUrl).toHaveBeenCalledWith('https://github.com/owner/repo/pull/42');
1100
- // Should not call github.getPr since we have the URL stored
1101
- expect(github.getPr).not.toHaveBeenCalled();
1102
- });
1103
- it('falls back to fetching URL when prUrl is not stored', async () => {
1104
- vi.mocked(github.getPr).mockReturnValue({
1105
- number: 42,
1106
- url: 'https://github.com/owner/repo/pull/42',
1107
- state: 'OPEN',
1108
- isDraft: false,
1109
- title: 'Test PR',
1110
- headBranch: 'feature-42',
1111
- baseBranch: 'main',
1112
- });
1113
- const deps = makeDeps();
1114
- const worktree = makeWorktree({
1115
- type: 'remote_pr',
1116
- prNumber: 42,
1117
- prState: 'OPEN',
1118
- prUrl: undefined, // No stored URL
1119
- });
1120
- const result = await executeAction('open_pr_url', worktree, makeEnv(), makeConfig(), deps);
1121
- expect(result.success).toBe(true);
1122
- expect(github.getPr).toHaveBeenCalledWith(42);
1123
- expect(deps.openUrl).toHaveBeenCalledWith('https://github.com/owner/repo/pull/42');
1124
- });
1125
- });
1126
- });
1127
- //# sourceMappingURL=action-executors.test.js.map