@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,1182 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- // Mock modules before importing the CLI
3
- vi.mock('../lib/git.js', () => ({
4
- getRepoRoot: vi.fn(),
5
- getRepoName: vi.fn(),
6
- getMainWorktreeRoot: vi.fn(),
7
- fetch: vi.fn(),
8
- fetchAsync: vi.fn().mockResolvedValue(undefined),
9
- getCurrentBranch: vi.fn(),
10
- checkout: vi.fn(),
11
- exec: vi.fn(),
12
- push: vi.fn(),
13
- pushAsync: vi.fn().mockResolvedValue(undefined),
14
- commit: vi.fn(),
15
- add: vi.fn(),
16
- stash: vi.fn(),
17
- stashApply: vi.fn(),
18
- stashDrop: vi.fn(),
19
- stashPop: vi.fn(),
20
- addWorktree: vi.fn(),
21
- addWorktreeAsync: vi.fn().mockResolvedValue(undefined),
22
- getStagedFiles: vi.fn(),
23
- getUnstagedFiles: vi.fn(),
24
- getStatusOutput: vi.fn(),
25
- getCommitsAhead: vi.fn(),
26
- remoteBranchExists: vi.fn(),
27
- branchExists: vi.fn(),
28
- getChangedFiles: vi.fn(),
29
- getCommitMessages: vi.fn(),
30
- }));
31
- vi.mock('../lib/github.js', () => ({
32
- isGhInstalled: vi.fn(),
33
- isAuthenticated: vi.fn(),
34
- getPr: vi.fn(),
35
- getPrByBranch: vi.fn(),
36
- createPr: vi.fn(),
37
- }));
38
- vi.mock('../lib/prompts.js', () => ({
39
- promptChoiceIndex: vi.fn(),
40
- promptConfirm: vi.fn(),
41
- withSpinner: vi.fn(async (message, fn) => {
42
- return await fn();
43
- }),
44
- }));
45
- vi.mock('../lib/config.js', () => ({
46
- loadConfig: vi.fn(),
47
- generateBranchNameAsync: vi.fn(),
48
- generateWorktreePath: vi.fn(),
49
- generatePRContentAsync: vi.fn(),
50
- }));
51
- vi.mock('../lib/state-detection.js', () => ({
52
- analyzeGitState: vi.fn(),
53
- detectScenario: vi.fn(),
54
- }));
55
- // Create a mock hook runner that always allows hooks to pass
56
- const mockHookRunner = {
57
- runHook: vi.fn().mockResolvedValue(true),
58
- runCleanup: vi.fn().mockResolvedValue(undefined),
59
- updateContext: vi.fn(),
60
- hasConfiguredHooks: vi.fn().mockReturnValue(false),
61
- getConfiguredHooks: vi.fn().mockReturnValue([]),
62
- getContext: vi.fn().mockReturnValue({}),
63
- };
64
- vi.mock('../lib/newpr/index.js', () => ({
65
- parseArgs: vi.fn(),
66
- getHelpText: vi.fn(),
67
- getScenarioContext: vi.fn(),
68
- isPrWorktreeScenario: vi.fn(),
69
- isExistingBranchAction: vi.fn(),
70
- executeStateAction: vi.fn(),
71
- getBranchPoint: vi.fn(),
72
- getScenarioMessageLevel: vi.fn(),
73
- createHookRunner: vi.fn(() => mockHookRunner),
74
- createActionDeps: vi.fn(() => ({
75
- gitAdd: vi.fn(),
76
- gitStash: vi.fn(),
77
- gitPush: vi.fn(),
78
- gitCommit: vi.fn(),
79
- })),
80
- HookRunner: vi.fn(),
81
- runLifecycleHook: vi.fn().mockResolvedValue(true),
82
- }));
83
- vi.mock('fs', () => ({
84
- default: {
85
- existsSync: vi.fn(),
86
- symlinkSync: vi.fn(),
87
- },
88
- existsSync: vi.fn(),
89
- symlinkSync: vi.fn(),
90
- }));
91
- vi.mock('../lib/wtlink/config-manifest.js', () => ({
92
- getEnabledFiles: vi.fn(),
93
- }));
94
- vi.mock('../lib/wtlink/link-configs.js', () => ({
95
- run: vi.fn(),
96
- }));
97
- // Import after mocking
98
- import * as git from '../lib/git.js';
99
- import * as github from '../lib/github.js';
100
- import * as prompts from '../lib/prompts.js';
101
- import { loadConfig, generateBranchNameAsync, generateWorktreePath, generatePRContentAsync, } from '../lib/config.js';
102
- import { analyzeGitState, detectScenario } from '../lib/state-detection.js';
103
- import * as newpr from '../lib/newpr/index.js';
104
- import fs from 'fs';
105
- import { getEnabledFiles } from '../lib/wtlink/config-manifest.js';
106
- import { run as runWtlink } from '../lib/wtlink/link-configs.js';
107
- describe('cli/newpr', () => {
108
- let mockConsoleLog;
109
- let mockConsoleError;
110
- let mockProcessExit;
111
- let originalArgv;
112
- const defaultConfig = {
113
- configVersion: 1,
114
- baseBranch: 'main',
115
- worktreePattern: '{repo}.pr{number}',
116
- worktreeParent: '..',
117
- draftPr: false,
118
- sharedRepos: [],
119
- branchPrefix: 'feature',
120
- previewLabel: 'preview',
121
- syncPatterns: [],
122
- preferredEditor: 'auto',
123
- ai: { provider: 'none' },
124
- hooks: {},
125
- hookDefaults: { timeout: 30000, maxTimeout: 60000 },
126
- plugins: [],
127
- generators: {},
128
- integrations: {},
129
- logging: { level: 'info', timestamps: true },
130
- global: { warnNotGlobal: true },
131
- wtlink: { enabled: [], disabled: [] },
132
- linkConfigFiles: undefined,
133
- };
134
- const defaultOptions = {
135
- baseBranch: 'main',
136
- draft: false,
137
- installDeps: false,
138
- openEditor: false,
139
- runWtlink: false,
140
- json: false,
141
- nonInteractive: false,
142
- noHooks: false,
143
- };
144
- const makePrInfo = (overrides = {}) => ({
145
- number: 123,
146
- title: 'Test PR',
147
- state: 'OPEN',
148
- headBranch: 'feature-123',
149
- baseBranch: 'main',
150
- url: 'https://github.com/org/repo/pull/123',
151
- isDraft: false,
152
- ...overrides,
153
- });
154
- const makeGitState = (overrides = {}) => ({
155
- worktreeType: 'main_worktree',
156
- branchType: 'main',
157
- currentBranch: 'main',
158
- commitRelationship: 'same',
159
- workingTreeStatus: 'clean',
160
- localCommits: [],
161
- stagedFiles: [],
162
- unstagedFiles: [],
163
- repoRoot: '/repo',
164
- repoName: 'repo',
165
- ...overrides,
166
- });
167
- beforeEach(() => {
168
- vi.resetAllMocks();
169
- // Reset async git mocks (resetAllMocks clears implementations)
170
- vi.mocked(git.fetchAsync).mockResolvedValue(undefined);
171
- vi.mocked(git.pushAsync).mockResolvedValue(undefined);
172
- vi.mocked(git.addWorktreeAsync).mockResolvedValue(undefined);
173
- // Reset withSpinner mock (resetAllMocks clears the implementation)
174
- vi.mocked(prompts.withSpinner).mockImplementation(async (message, fn) => {
175
- return await fn();
176
- });
177
- // Reset the hook runner mock to allow all hooks to pass
178
- mockHookRunner.runHook.mockResolvedValue(true);
179
- mockHookRunner.runCleanup.mockResolvedValue(undefined);
180
- mockHookRunner.updateContext.mockReturnValue(undefined);
181
- mockHookRunner.hasConfiguredHooks.mockReturnValue(false);
182
- mockHookRunner.getConfiguredHooks.mockReturnValue([]);
183
- mockHookRunner.getContext.mockReturnValue({});
184
- // Reset createHookRunner to return the mock
185
- vi.mocked(newpr.createHookRunner).mockReturnValue(mockHookRunner);
186
- // Reset createActionDeps to return a mock deps object
187
- vi.mocked(newpr.createActionDeps).mockReturnValue({
188
- gitAdd: vi.fn(),
189
- gitStash: vi.fn(),
190
- gitPush: vi.fn(),
191
- gitCommit: vi.fn(),
192
- });
193
- // Default mocks for AI generation functions
194
- vi.mocked(generatePRContentAsync).mockResolvedValue({
195
- title: 'Test PR',
196
- description: '',
197
- aiGenerated: false,
198
- });
199
- vi.mocked(git.getChangedFiles).mockReturnValue([]);
200
- vi.mocked(git.getCommitMessages).mockReturnValue([]);
201
- // Default mocks for auto-link feature (throw by default to skip auto-link)
202
- vi.mocked(git.getMainWorktreeRoot).mockImplementation(() => {
203
- throw new Error('Mock: getMainWorktreeRoot not configured');
204
- });
205
- vi.mocked(getEnabledFiles).mockReturnValue([]);
206
- vi.mocked(runWtlink).mockResolvedValue(undefined);
207
- mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => { });
208
- mockConsoleError = vi.spyOn(console, 'error').mockImplementation(() => { });
209
- // @ts-expect-error - process.exit mock type is complex
210
- mockProcessExit = vi.spyOn(process, 'exit').mockImplementation((() => { }));
211
- originalArgv = process.argv;
212
- });
213
- afterEach(() => {
214
- mockConsoleLog.mockRestore();
215
- mockConsoleError.mockRestore();
216
- mockProcessExit.mockRestore();
217
- process.argv = originalArgv;
218
- vi.resetModules();
219
- });
220
- async function runCli(args = []) {
221
- process.argv = ['node', 'newpr', ...args];
222
- await import('./newpr.js');
223
- // Allow time for all async operations to complete
224
- await new Promise((resolve) => setTimeout(resolve, 100));
225
- }
226
- describe('help option', () => {
227
- it('prints help and exits 0 on --help', async () => {
228
- vi.mocked(newpr.parseArgs).mockReturnValue({ kind: 'help' });
229
- vi.mocked(newpr.getHelpText).mockReturnValue('Usage: newpr [options]');
230
- await runCli(['--help']);
231
- expect(newpr.getHelpText).toHaveBeenCalled();
232
- expect(mockConsoleLog).toHaveBeenCalledWith('Usage: newpr [options]');
233
- expect(mockProcessExit).toHaveBeenCalledWith(0);
234
- });
235
- });
236
- describe('error handling', () => {
237
- it('prints error and exits 1 on parse error', async () => {
238
- vi.mocked(newpr.parseArgs).mockReturnValue({
239
- kind: 'error',
240
- message: 'Invalid option: --invalid',
241
- });
242
- await runCli(['--invalid']);
243
- expect(mockConsoleError).toHaveBeenCalled();
244
- expect(mockProcessExit).toHaveBeenCalledWith(1);
245
- });
246
- it('exits 1 when gh not installed', async () => {
247
- vi.mocked(newpr.parseArgs).mockReturnValue({
248
- kind: 'success',
249
- options: { mode: 'new', description: 'test', ...defaultOptions },
250
- });
251
- vi.mocked(github.isGhInstalled).mockReturnValue(false);
252
- await runCli(['test']);
253
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('GitHub CLI'));
254
- expect(mockProcessExit).toHaveBeenCalledWith(1);
255
- });
256
- it('exits 1 when gh not authenticated', async () => {
257
- vi.mocked(newpr.parseArgs).mockReturnValue({
258
- kind: 'success',
259
- options: { mode: 'new', description: 'test', ...defaultOptions },
260
- });
261
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
262
- vi.mocked(github.isAuthenticated).mockReturnValue(false);
263
- await runCli(['test']);
264
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('not authenticated'));
265
- expect(mockProcessExit).toHaveBeenCalledWith(1);
266
- });
267
- });
268
- describe('--pr mode', () => {
269
- it('sets up worktree for existing PR', async () => {
270
- vi.mocked(newpr.parseArgs).mockReturnValue({
271
- kind: 'success',
272
- options: { mode: 'pr', prNumber: 123, ...defaultOptions },
273
- });
274
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
275
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
276
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
277
- vi.mocked(git.getRepoName).mockReturnValue('repo');
278
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
279
- vi.mocked(github.getPr).mockReturnValue(makePrInfo());
280
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
281
- vi.mocked(fs.existsSync).mockReturnValue(false);
282
- await runCli(['--pr', '123']);
283
- // Verify wiring: worktree path, branch, and options are correctly passed through
284
- // Non-JSON mode uses addWorktreeAsync
285
- expect(git.addWorktreeAsync).toHaveBeenCalledWith('/repo.pr123', // path from generateWorktreePath
286
- 'feature-123', // branch from PR info
287
- expect.objectContaining({
288
- createBranch: true,
289
- startPoint: 'origin/feature-123',
290
- }));
291
- expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('PR #123'));
292
- });
293
- it('exits 1 when PR not found', async () => {
294
- vi.mocked(newpr.parseArgs).mockReturnValue({
295
- kind: 'success',
296
- options: { mode: 'pr', prNumber: 999, ...defaultOptions },
297
- });
298
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
299
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
300
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
301
- vi.mocked(git.getRepoName).mockReturnValue('repo');
302
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
303
- vi.mocked(github.getPr).mockReturnValue(null);
304
- await runCli(['--pr', '999']);
305
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Could not find PR'));
306
- expect(mockProcessExit).toHaveBeenCalledWith(1);
307
- });
308
- });
309
- describe('--branch mode', () => {
310
- it('creates PR for existing branch', async () => {
311
- vi.mocked(newpr.parseArgs).mockReturnValue({
312
- kind: 'success',
313
- options: { mode: 'branch', branchName: 'my-feature', ...defaultOptions },
314
- });
315
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
316
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
317
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
318
- vi.mocked(git.getRepoName).mockReturnValue('repo');
319
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
320
- vi.mocked(git.remoteBranchExists).mockReturnValue(true);
321
- vi.mocked(github.getPrByBranch).mockReturnValue(null);
322
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 456 }));
323
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr456');
324
- vi.mocked(fs.existsSync).mockReturnValue(false);
325
- await runCli(['--branch', 'my-feature']);
326
- // Verify wiring: createPr receives correct branch and description
327
- expect(github.createPr).toHaveBeenCalledWith(expect.objectContaining({
328
- head: 'my-feature',
329
- base: 'main', // from config
330
- }));
331
- // Verify wiring: addWorktreeAsync receives correct path, branch, and options (non-JSON mode)
332
- expect(git.addWorktreeAsync).toHaveBeenCalledWith('/repo.pr456', // path from generateWorktreePath
333
- 'my-feature', // the branch name
334
- expect.objectContaining({
335
- createBranch: true,
336
- startPoint: 'origin/my-feature',
337
- }));
338
- });
339
- it('uses existing PR if branch already has one', async () => {
340
- const existingPr = makePrInfo({ number: 789 });
341
- vi.mocked(newpr.parseArgs).mockReturnValue({
342
- kind: 'success',
343
- options: { mode: 'branch', branchName: 'my-feature', ...defaultOptions },
344
- });
345
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
346
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
347
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
348
- vi.mocked(git.getRepoName).mockReturnValue('repo');
349
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
350
- vi.mocked(git.remoteBranchExists).mockReturnValue(true);
351
- vi.mocked(github.getPrByBranch).mockReturnValue(existingPr);
352
- vi.mocked(github.getPr).mockReturnValue(existingPr);
353
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr789');
354
- vi.mocked(fs.existsSync).mockReturnValue(false);
355
- await runCli(['--branch', 'my-feature']);
356
- expect(github.createPr).not.toHaveBeenCalled();
357
- expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('PR #789 already exists'));
358
- });
359
- });
360
- describe('new feature mode', () => {
361
- it('creates new branch, PR, and worktree', async () => {
362
- vi.mocked(newpr.parseArgs).mockReturnValue({
363
- kind: 'success',
364
- options: { mode: 'new', description: 'Add new feature', ...defaultOptions },
365
- });
366
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
367
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
368
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
369
- vi.mocked(git.getRepoName).mockReturnValue('repo');
370
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
371
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
372
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
373
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
374
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
375
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
376
- message: 'No changes detected',
377
- choices: [
378
- {
379
- label: 'Create empty commit',
380
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
381
- },
382
- { label: 'Cancel', action: null },
383
- ],
384
- });
385
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
386
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1); // 1-based index
387
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
388
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
389
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
390
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
391
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
392
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
393
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
394
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
395
- vi.mocked(generatePRContentAsync).mockResolvedValue({
396
- title: 'Add new feature',
397
- description: '',
398
- aiGenerated: false,
399
- });
400
- await runCli(['Add new feature']);
401
- // Verify wiring: checkout uses generated branch name and branch point with repoRoot
402
- expect(git.exec).toHaveBeenCalledWith([
403
- 'checkout',
404
- '-b',
405
- 'feature/add-new-feature', // from generateBranchNameAsync
406
- 'origin/main', // from getBranchPoint
407
- ], { cwd: '/repo' } // repoRoot from getRepoRoot()
408
- );
409
- // Verify wiring: createPr receives correct branch and title (from generatePRContentAsync)
410
- expect(github.createPr).toHaveBeenCalledWith(expect.objectContaining({
411
- head: 'feature/add-new-feature',
412
- base: 'main',
413
- title: 'Add new feature',
414
- }));
415
- // Verify wiring: addWorktreeAsync receives correct path, branch, and cwd option
416
- expect(git.addWorktreeAsync).toHaveBeenCalledWith('/repo.pr100', // path from generateWorktreePath
417
- 'feature/add-new-feature', // the branch name
418
- { cwd: '/repo' } // repoRoot from getRepoRoot()
419
- );
420
- });
421
- it('exits 1 when user cancels', async () => {
422
- vi.mocked(newpr.parseArgs).mockReturnValue({
423
- kind: 'success',
424
- options: { mode: 'new', description: 'Add new feature', ...defaultOptions },
425
- });
426
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
427
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
428
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
429
- vi.mocked(git.getRepoName).mockReturnValue('repo');
430
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
431
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
432
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
433
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
434
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
435
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
436
- message: 'No changes detected',
437
- choices: [
438
- {
439
- label: 'Create empty commit',
440
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
441
- },
442
- { label: 'Cancel', action: null },
443
- ],
444
- });
445
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
446
- // User selects Cancel (option 2 in 1-based, array index 1)
447
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(2); // 1-based index
448
- await runCli(['Add new feature']);
449
- expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('Aborted'));
450
- expect(mockProcessExit).toHaveBeenCalledWith(1);
451
- });
452
- it('shows helpful error when checkout fails due to conflicting changes', async () => {
453
- vi.mocked(newpr.parseArgs).mockReturnValue({
454
- kind: 'success',
455
- options: { mode: 'new', description: 'Add new feature', ...defaultOptions },
456
- });
457
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
458
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
459
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
460
- vi.mocked(git.getRepoName).mockReturnValue('repo');
461
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
462
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
463
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
464
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
465
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
466
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
467
- message: 'No changes detected',
468
- choices: [
469
- {
470
- label: 'Create empty commit',
471
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
472
- },
473
- { label: 'Cancel', action: null },
474
- ],
475
- });
476
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
477
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1); // 1-based index
478
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
479
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
480
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
481
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
482
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
483
- vi.mocked(git.getStagedFiles).mockReturnValue(['README.md']);
484
- vi.mocked(git.getUnstagedFiles).mockReturnValue([]);
485
- // Mock checkout to fail with a conflict error
486
- vi.mocked(git.exec).mockImplementation(() => {
487
- throw new Error("error: Your local changes to 'README.md' would be overwritten by checkout");
488
- });
489
- await runCli(['Add new feature']);
490
- // Verify helpful error messages are shown
491
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Checkout failed due to conflicting changes'));
492
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Your staged changes are preserved'));
493
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Commit your changes first'));
494
- expect(mockProcessExit).toHaveBeenCalledWith(1);
495
- });
496
- });
497
- describe('JSON output mode', () => {
498
- it('outputs JSON error when gh not installed with --json flag', async () => {
499
- vi.mocked(newpr.parseArgs).mockReturnValue({
500
- kind: 'success',
501
- options: { mode: 'new', description: 'test', ...defaultOptions, json: true },
502
- });
503
- vi.mocked(github.isGhInstalled).mockReturnValue(false);
504
- await runCli(['test', '--json']);
505
- // Should output JSON error, not plain text
506
- const jsonOutput = mockConsoleLog.mock.calls.find((call) => String(call[0]).includes('"success": false'));
507
- expect(jsonOutput).toBeDefined();
508
- expect(jsonOutput[0]).toContain('"code": "GH_NOT_INSTALLED"');
509
- expect(mockProcessExit).toHaveBeenCalledWith(1);
510
- });
511
- it('outputs JSON error when gh not authenticated with --json flag', async () => {
512
- vi.mocked(newpr.parseArgs).mockReturnValue({
513
- kind: 'success',
514
- options: { mode: 'new', description: 'test', ...defaultOptions, json: true },
515
- });
516
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
517
- vi.mocked(github.isAuthenticated).mockReturnValue(false);
518
- await runCli(['test', '--json']);
519
- const jsonOutput = mockConsoleLog.mock.calls.find((call) => String(call[0]).includes('"success": false'));
520
- expect(jsonOutput).toBeDefined();
521
- expect(jsonOutput[0]).toContain('"code": "GH_NOT_AUTHENTICATED"');
522
- expect(mockProcessExit).toHaveBeenCalledWith(1);
523
- });
524
- it('outputs JSON success for --pr mode', async () => {
525
- vi.mocked(newpr.parseArgs).mockReturnValue({
526
- kind: 'success',
527
- options: { mode: 'pr', prNumber: 123, ...defaultOptions, json: true },
528
- });
529
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
530
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
531
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
532
- vi.mocked(git.getRepoName).mockReturnValue('repo');
533
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
534
- vi.mocked(github.getPr).mockReturnValue(makePrInfo());
535
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
536
- vi.mocked(fs.existsSync).mockReturnValue(false);
537
- await runCli(['--pr', '123', '--json']);
538
- const jsonOutput = mockConsoleLog.mock.calls.find((call) => String(call[0]).includes('"success": true'));
539
- expect(jsonOutput).toBeDefined();
540
- expect(jsonOutput[0]).toContain('"prNumber": 123');
541
- });
542
- it('outputs JSON error when parse fails with --json flag', async () => {
543
- vi.mocked(newpr.parseArgs).mockReturnValue({
544
- kind: 'error',
545
- message: 'Missing required argument',
546
- });
547
- await runCli(['--json']);
548
- const jsonOutput = mockConsoleLog.mock.calls.find((call) => String(call[0]).includes('"success": false'));
549
- expect(jsonOutput).toBeDefined();
550
- expect(jsonOutput[0]).toContain('"code": "INVALID_ARGUMENT"');
551
- expect(mockProcessExit).toHaveBeenCalledWith(1);
552
- });
553
- });
554
- describe('non-interactive mode', () => {
555
- it('uses first available action when no --action specified', async () => {
556
- vi.mocked(newpr.parseArgs).mockReturnValue({
557
- kind: 'success',
558
- options: {
559
- mode: 'new',
560
- description: 'Add new feature',
561
- ...defaultOptions,
562
- nonInteractive: true,
563
- },
564
- });
565
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
566
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
567
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
568
- vi.mocked(git.getRepoName).mockReturnValue('repo');
569
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
570
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
571
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
572
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
573
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
574
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
575
- message: 'No changes detected',
576
- choices: [
577
- {
578
- label: 'Create empty commit',
579
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
580
- },
581
- { label: 'Cancel', action: null },
582
- ],
583
- });
584
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
585
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
586
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
587
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
588
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
589
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
590
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
591
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
592
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
593
- await runCli(['Add new feature', '--non-interactive']);
594
- // Should not prompt - should use first available action
595
- expect(prompts.promptChoiceIndex).not.toHaveBeenCalled();
596
- expect(newpr.executeStateAction).toHaveBeenCalledWith(expect.objectContaining({ action: 'empty_commit' }), expect.any(String), expect.any(String), expect.any(Object), expect.any(String));
597
- });
598
- it('uses specified action when --action is provided', async () => {
599
- vi.mocked(newpr.parseArgs).mockReturnValue({
600
- kind: 'success',
601
- options: {
602
- mode: 'new',
603
- description: 'Add new feature',
604
- ...defaultOptions,
605
- nonInteractive: true,
606
- action: 'commit_staged',
607
- },
608
- });
609
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
610
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
611
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
612
- vi.mocked(git.getRepoName).mockReturnValue('repo');
613
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
614
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
615
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState({
616
- workingTreeStatus: 'has_staged',
617
- stagedFiles: ['file.ts'],
618
- }));
619
- vi.mocked(detectScenario).mockReturnValue('main_staged_same');
620
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
621
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
622
- message: 'You have staged changes',
623
- choices: [
624
- {
625
- label: 'Commit staged changes',
626
- action: { action: 'commit_staged', branchFrom: 'origin_main', stashUnstaged: false },
627
- },
628
- {
629
- label: 'Create empty commit',
630
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
631
- },
632
- { label: 'Cancel', action: null },
633
- ],
634
- });
635
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('info');
636
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
637
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
638
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
639
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
640
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
641
- vi.mocked(git.getStagedFiles).mockReturnValue(['file.ts']);
642
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
643
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
644
- await runCli(['Add new feature', '--non-interactive', '--action', 'commit_staged']);
645
- expect(prompts.promptChoiceIndex).not.toHaveBeenCalled();
646
- expect(newpr.executeStateAction).toHaveBeenCalledWith(expect.objectContaining({ action: 'commit_staged' }), expect.any(String), expect.any(String), expect.any(Object), expect.any(String));
647
- });
648
- it('exits with error when --action specifies invalid action', async () => {
649
- vi.mocked(newpr.parseArgs).mockReturnValue({
650
- kind: 'success',
651
- options: {
652
- mode: 'new',
653
- description: 'Add new feature',
654
- ...defaultOptions,
655
- nonInteractive: true,
656
- action: 'invalid_action', // intentionally invalid for test
657
- },
658
- });
659
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
660
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
661
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
662
- vi.mocked(git.getRepoName).mockReturnValue('repo');
663
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
664
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
665
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
666
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
667
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
668
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
669
- message: 'No changes detected',
670
- choices: [
671
- {
672
- label: 'Create empty commit',
673
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
674
- },
675
- { label: 'Cancel', action: null },
676
- ],
677
- });
678
- await runCli(['Add new feature', '--non-interactive', '--action', 'invalid_action']);
679
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining("Action 'invalid_action' is not available"));
680
- expect(mockProcessExit).toHaveBeenCalledWith(1);
681
- });
682
- it('exits with error in non-interactive mode from PR worktree', async () => {
683
- vi.mocked(newpr.parseArgs).mockReturnValue({
684
- kind: 'success',
685
- options: {
686
- mode: 'new',
687
- description: 'Add new feature',
688
- ...defaultOptions,
689
- nonInteractive: true,
690
- },
691
- });
692
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
693
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
694
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo.pr123');
695
- vi.mocked(git.getRepoName).mockReturnValue('repo');
696
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
697
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
698
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState({
699
- worktreeType: 'pr_worktree',
700
- }));
701
- vi.mocked(detectScenario).mockReturnValue('pr_worktree');
702
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(true);
703
- await runCli(['Add new feature', '--non-interactive']);
704
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Cannot create PR from a PR worktree in non-interactive mode'));
705
- expect(mockProcessExit).toHaveBeenCalledWith(1);
706
- });
707
- it('outputs JSON error when --action is invalid with --json flag', async () => {
708
- vi.mocked(newpr.parseArgs).mockReturnValue({
709
- kind: 'success',
710
- options: {
711
- mode: 'new',
712
- description: 'Add new feature',
713
- ...defaultOptions,
714
- nonInteractive: true,
715
- action: 'invalid_action', // intentionally invalid for test
716
- json: true,
717
- },
718
- });
719
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
720
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
721
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
722
- vi.mocked(git.getRepoName).mockReturnValue('repo');
723
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
724
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
725
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
726
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
727
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
728
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
729
- message: 'No changes detected',
730
- choices: [
731
- {
732
- label: 'Create empty commit',
733
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
734
- },
735
- { label: 'Cancel', action: null },
736
- ],
737
- });
738
- await runCli([
739
- 'Add new feature',
740
- '--non-interactive',
741
- '--action',
742
- 'invalid_action',
743
- '--json',
744
- ]);
745
- const jsonOutput = mockConsoleLog.mock.calls.find((call) => String(call[0]).includes('"success": false'));
746
- expect(jsonOutput).toBeDefined();
747
- expect(jsonOutput[0]).toContain('"code": "INVALID_ACTION"');
748
- expect(mockProcessExit).toHaveBeenCalledWith(1);
749
- });
750
- });
751
- describe('PR not found handling', () => {
752
- it('outputs JSON error when PR not found with --json flag', async () => {
753
- vi.mocked(newpr.parseArgs).mockReturnValue({
754
- kind: 'success',
755
- options: { mode: 'pr', prNumber: 999, ...defaultOptions, json: true },
756
- });
757
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
758
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
759
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
760
- vi.mocked(git.getRepoName).mockReturnValue('repo');
761
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
762
- vi.mocked(github.getPr).mockReturnValue(null);
763
- await runCli(['--pr', '999', '--json']);
764
- // JSON mode outputs to console.log, not console.error
765
- expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('Could not find PR'));
766
- expect(mockProcessExit).toHaveBeenCalledWith(1);
767
- });
768
- });
769
- describe('user cancellation handling', () => {
770
- it('outputs JSON error when user cancels with --json flag', async () => {
771
- vi.mocked(newpr.parseArgs).mockReturnValue({
772
- kind: 'success',
773
- options: { mode: 'new', description: 'test', ...defaultOptions, json: true },
774
- });
775
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
776
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
777
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
778
- vi.mocked(git.getRepoName).mockReturnValue('repo');
779
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
780
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/test');
781
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
782
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
783
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
784
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
785
- message: 'No changes detected',
786
- choices: [
787
- {
788
- label: 'Create empty commit',
789
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
790
- },
791
- { label: 'Cancel', action: null },
792
- ],
793
- });
794
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
795
- // User selects Cancel (index 1 in array, so 2 in 1-based indexing)
796
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(2);
797
- await runCli(['test', '--json']);
798
- const jsonOutput = mockConsoleLog.mock.calls.find((call) => String(call[0]).includes('"success": false'));
799
- expect(jsonOutput).toBeDefined();
800
- expect(jsonOutput[0]).toContain('"code": "USER_CANCELLED"');
801
- expect(mockProcessExit).toHaveBeenCalledWith(1);
802
- });
803
- });
804
- describe('executeStateAction parameter verification', () => {
805
- it('passes repoRoot to executeStateAction for existing branch actions', async () => {
806
- const repoRoot = '/repo';
807
- vi.mocked(newpr.parseArgs).mockReturnValue({
808
- kind: 'success',
809
- options: { mode: 'new', description: 'Add new feature', ...defaultOptions },
810
- });
811
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
812
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
813
- vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
814
- vi.mocked(git.getRepoName).mockReturnValue('repo');
815
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
816
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
817
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState({
818
- currentBranch: 'feature/existing-branch',
819
- branchType: 'feature',
820
- workingTreeStatus: 'has_staged',
821
- stagedFiles: ['file.ts'],
822
- }));
823
- vi.mocked(detectScenario).mockReturnValue('branch_with_changes');
824
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
825
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
826
- message: 'You have uncommitted changes',
827
- choices: [
828
- {
829
- label: 'Commit all and create PR',
830
- action: { action: 'commit_all', branchFrom: 'head', stashUnstaged: false },
831
- },
832
- { label: 'Cancel', action: null },
833
- ],
834
- });
835
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('info');
836
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1); // 1-based index
837
- // KEY: This is the existing branch action path
838
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(true);
839
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
840
- vi.mocked(git.remoteBranchExists).mockReturnValue(true);
841
- vi.mocked(git.getCurrentBranch).mockReturnValue('feature/existing-branch');
842
- vi.mocked(github.getPrByBranch).mockReturnValue(null);
843
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
844
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
845
- await runCli(['Add new feature']);
846
- // Verify executeStateAction was called with repoRoot as the 5th parameter
847
- expect(newpr.executeStateAction).toHaveBeenCalledWith(expect.objectContaining({ action: 'commit_all' }), expect.any(String), 'feature/existing-branch', expect.any(Object), repoRoot // This is the critical parameter that was missing
848
- );
849
- });
850
- it('passes repoRoot to executeStateAction for new branch actions', async () => {
851
- const repoRoot = '/repo';
852
- vi.mocked(newpr.parseArgs).mockReturnValue({
853
- kind: 'success',
854
- options: { mode: 'new', description: 'Add new feature', ...defaultOptions },
855
- });
856
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
857
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
858
- vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
859
- vi.mocked(git.getRepoName).mockReturnValue('repo');
860
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
861
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
862
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
863
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
864
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
865
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
866
- message: 'No changes detected',
867
- choices: [
868
- {
869
- label: 'Create empty commit',
870
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
871
- },
872
- { label: 'Cancel', action: null },
873
- ],
874
- });
875
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
876
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1); // 1-based index
877
- // This is the new branch action path
878
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
879
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
880
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
881
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
882
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
883
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
884
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
885
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
886
- await runCli(['Add new feature']);
887
- // Verify executeStateAction was called with repoRoot as the 5th parameter
888
- expect(newpr.executeStateAction).toHaveBeenCalledWith(expect.objectContaining({ action: 'empty_commit' }), expect.any(String), 'feature/add-new-feature', expect.any(Object), repoRoot // This ensures git operations run from repo root
889
- );
890
- });
891
- });
892
- describe('Bug fix: git operations must use repoRoot (empty commit worktree bug)', () => {
893
- // These tests verify the fix for the bug where creating a PR worktree
894
- // with an empty commit failed because git.checkout() didn't switch back
895
- // to main (missing repoRoot parameter), causing git.addWorktree() to fail
896
- // with "branch already checked out" error.
897
- it('git.checkout after push must use repoRoot parameter', async () => {
898
- const repoRoot = '/repo';
899
- vi.mocked(newpr.parseArgs).mockReturnValue({
900
- kind: 'success',
901
- options: { mode: 'new', description: 'Test feature', ...defaultOptions },
902
- });
903
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
904
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
905
- vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
906
- vi.mocked(git.getRepoName).mockReturnValue('repo');
907
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
908
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feat/test-feature');
909
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
910
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
911
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
912
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
913
- message: 'No changes detected',
914
- choices: [
915
- {
916
- label: 'Continue with empty initial commit',
917
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
918
- },
919
- { label: 'Cancel', action: null },
920
- ],
921
- });
922
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
923
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1);
924
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
925
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
926
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
927
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
928
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
929
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
930
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
931
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
932
- await runCli(['Test feature']);
933
- // CRITICAL: git.checkout must be called with repoRoot to switch back to main
934
- // Without this, the branch remains checked out and worktree creation fails
935
- expect(git.checkout).toHaveBeenCalledWith('main', repoRoot);
936
- });
937
- it('git.addWorktreeAsync must use { cwd: repoRoot } option', async () => {
938
- const repoRoot = '/repo';
939
- vi.mocked(newpr.parseArgs).mockReturnValue({
940
- kind: 'success',
941
- options: { mode: 'new', description: 'Test feature', ...defaultOptions },
942
- });
943
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
944
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
945
- vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
946
- vi.mocked(git.getRepoName).mockReturnValue('repo');
947
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
948
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feat/test-feature');
949
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
950
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
951
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
952
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
953
- message: 'No changes detected',
954
- choices: [
955
- {
956
- label: 'Continue with empty initial commit',
957
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
958
- },
959
- { label: 'Cancel', action: null },
960
- ],
961
- });
962
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
963
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1);
964
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
965
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
966
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
967
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
968
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
969
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
970
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
971
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
972
- await runCli(['Test feature']);
973
- // CRITICAL: git.addWorktreeAsync must be called with { cwd: repoRoot }
974
- // to ensure worktree is created from the correct directory
975
- expect(git.addWorktreeAsync).toHaveBeenCalledWith('/repo.pr100', 'feat/test-feature', {
976
- cwd: repoRoot,
977
- });
978
- });
979
- it('git.pushAsync must use repoRoot parameter', async () => {
980
- const repoRoot = '/repo';
981
- vi.mocked(newpr.parseArgs).mockReturnValue({
982
- kind: 'success',
983
- options: { mode: 'new', description: 'Test feature', ...defaultOptions },
984
- });
985
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
986
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
987
- vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
988
- vi.mocked(git.getRepoName).mockReturnValue('repo');
989
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
990
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feat/test-feature');
991
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
992
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
993
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
994
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
995
- message: 'No changes detected',
996
- choices: [
997
- {
998
- label: 'Continue with empty initial commit',
999
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
1000
- },
1001
- { label: 'Cancel', action: null },
1002
- ],
1003
- });
1004
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
1005
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1);
1006
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
1007
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
1008
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
1009
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
1010
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
1011
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
1012
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
1013
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
1014
- await runCli(['Test feature']);
1015
- // git.pushAsync must be called with repoRoot to push from correct directory
1016
- expect(git.pushAsync).toHaveBeenCalledWith({ setUpstream: true, remote: 'origin', branch: 'feat/test-feature' }, repoRoot);
1017
- });
1018
- it('git.exec for branch checkout must use { cwd: repoRoot } option', async () => {
1019
- const repoRoot = '/repo';
1020
- vi.mocked(newpr.parseArgs).mockReturnValue({
1021
- kind: 'success',
1022
- options: { mode: 'new', description: 'Test feature', ...defaultOptions },
1023
- });
1024
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
1025
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
1026
- vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
1027
- vi.mocked(git.getRepoName).mockReturnValue('repo');
1028
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
1029
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feat/test-feature');
1030
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
1031
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
1032
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
1033
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
1034
- message: 'No changes detected',
1035
- choices: [
1036
- {
1037
- label: 'Continue with empty initial commit',
1038
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
1039
- },
1040
- { label: 'Cancel', action: null },
1041
- ],
1042
- });
1043
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
1044
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1);
1045
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
1046
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
1047
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
1048
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
1049
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
1050
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
1051
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
1052
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
1053
- await runCli(['Test feature']);
1054
- // git.exec for branch creation must use cwd option to ensure
1055
- // branch is created in the correct worktree
1056
- expect(git.exec).toHaveBeenCalledWith(['checkout', '-b', 'feat/test-feature', 'origin/main'], { cwd: repoRoot });
1057
- });
1058
- });
1059
- describe('auto-link config files (linkConfigFiles feature)', () => {
1060
- // Helper to set up all the mocks for a successful PR creation
1061
- const setupPrCreationMocks = (configOverrides = {}) => {
1062
- vi.mocked(newpr.parseArgs).mockReturnValue({
1063
- kind: 'success',
1064
- options: { mode: 'pr', prNumber: 123, ...defaultOptions },
1065
- });
1066
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
1067
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
1068
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
1069
- vi.mocked(git.getRepoName).mockReturnValue('repo');
1070
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/main-repo');
1071
- vi.mocked(loadConfig).mockReturnValue({ ...defaultConfig, ...configOverrides });
1072
- vi.mocked(github.getPr).mockReturnValue(makePrInfo());
1073
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
1074
- vi.mocked(fs.existsSync).mockReturnValue(false);
1075
- };
1076
- it('auto-links config files when linkConfigFiles is true', async () => {
1077
- setupPrCreationMocks({ linkConfigFiles: true });
1078
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local', '.vscode/settings.json']);
1079
- vi.mocked(runWtlink).mockResolvedValue(undefined);
1080
- await runCli(['--pr', '123']);
1081
- expect(getEnabledFiles).toHaveBeenCalledWith('/main-repo');
1082
- expect(runWtlink).toHaveBeenCalledWith(expect.objectContaining({
1083
- source: '/main-repo',
1084
- destination: '/repo.pr123',
1085
- dryRun: false,
1086
- yes: true,
1087
- }));
1088
- expect(prompts.promptConfirm).not.toHaveBeenCalled();
1089
- });
1090
- it('skips linking when linkConfigFiles is false', async () => {
1091
- setupPrCreationMocks({ linkConfigFiles: false });
1092
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local', '.vscode/settings.json']);
1093
- await runCli(['--pr', '123']);
1094
- expect(getEnabledFiles).toHaveBeenCalledWith('/main-repo');
1095
- expect(runWtlink).not.toHaveBeenCalled();
1096
- expect(prompts.promptConfirm).not.toHaveBeenCalled();
1097
- });
1098
- it('prompts user when linkConfigFiles is undefined in interactive mode', async () => {
1099
- setupPrCreationMocks({ linkConfigFiles: undefined });
1100
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
1101
- vi.mocked(prompts.promptConfirm).mockResolvedValue(true);
1102
- vi.mocked(runWtlink).mockResolvedValue(undefined);
1103
- await runCli(['--pr', '123']);
1104
- expect(getEnabledFiles).toHaveBeenCalledWith('/main-repo');
1105
- expect(prompts.promptConfirm).toHaveBeenCalledWith(expect.stringContaining('Link these config files'), true);
1106
- expect(runWtlink).toHaveBeenCalled();
1107
- });
1108
- it('skips linking when user declines the prompt', async () => {
1109
- setupPrCreationMocks({ linkConfigFiles: undefined });
1110
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
1111
- vi.mocked(prompts.promptConfirm).mockResolvedValue(false);
1112
- await runCli(['--pr', '123']);
1113
- expect(prompts.promptConfirm).toHaveBeenCalled();
1114
- expect(runWtlink).not.toHaveBeenCalled();
1115
- });
1116
- it('defaults to linking in non-interactive mode when linkConfigFiles is undefined', async () => {
1117
- vi.mocked(newpr.parseArgs).mockReturnValue({
1118
- kind: 'success',
1119
- options: { mode: 'pr', prNumber: 123, ...defaultOptions, nonInteractive: true },
1120
- });
1121
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
1122
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
1123
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
1124
- vi.mocked(git.getRepoName).mockReturnValue('repo');
1125
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/main-repo');
1126
- vi.mocked(loadConfig).mockReturnValue({ ...defaultConfig, linkConfigFiles: undefined });
1127
- vi.mocked(github.getPr).mockReturnValue(makePrInfo());
1128
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
1129
- vi.mocked(fs.existsSync).mockReturnValue(false);
1130
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
1131
- vi.mocked(runWtlink).mockResolvedValue(undefined);
1132
- await runCli(['--pr', '123', '--non-interactive']);
1133
- expect(prompts.promptConfirm).not.toHaveBeenCalled();
1134
- expect(runWtlink).toHaveBeenCalled();
1135
- });
1136
- it('defaults to linking in JSON mode when linkConfigFiles is undefined', async () => {
1137
- vi.mocked(newpr.parseArgs).mockReturnValue({
1138
- kind: 'success',
1139
- options: { mode: 'pr', prNumber: 123, ...defaultOptions, json: true },
1140
- });
1141
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
1142
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
1143
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
1144
- vi.mocked(git.getRepoName).mockReturnValue('repo');
1145
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/main-repo');
1146
- vi.mocked(loadConfig).mockReturnValue({ ...defaultConfig, linkConfigFiles: undefined });
1147
- vi.mocked(github.getPr).mockReturnValue(makePrInfo());
1148
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
1149
- vi.mocked(fs.existsSync).mockReturnValue(false);
1150
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
1151
- vi.mocked(runWtlink).mockResolvedValue(undefined);
1152
- await runCli(['--pr', '123', '--json']);
1153
- expect(prompts.promptConfirm).not.toHaveBeenCalled();
1154
- expect(runWtlink).toHaveBeenCalled();
1155
- });
1156
- it('does not link when there are no enabled files', async () => {
1157
- setupPrCreationMocks({ linkConfigFiles: true });
1158
- vi.mocked(getEnabledFiles).mockReturnValue([]);
1159
- await runCli(['--pr', '123']);
1160
- expect(runWtlink).not.toHaveBeenCalled();
1161
- expect(prompts.promptConfirm).not.toHaveBeenCalled();
1162
- });
1163
- it('handles linking errors gracefully', async () => {
1164
- setupPrCreationMocks({ linkConfigFiles: true });
1165
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
1166
- vi.mocked(runWtlink).mockRejectedValue(new Error('Permission denied'));
1167
- await runCli(['--pr', '123']);
1168
- // Should not crash, just warn about the failure
1169
- expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('Failed to link config files'));
1170
- });
1171
- it('skips config linking when main worktree cannot be determined', async () => {
1172
- setupPrCreationMocks({ linkConfigFiles: true });
1173
- vi.mocked(git.getMainWorktreeRoot).mockImplementation(() => {
1174
- throw new Error('Not in a git worktree');
1175
- });
1176
- await runCli(['--pr', '123']);
1177
- expect(getEnabledFiles).not.toHaveBeenCalled();
1178
- expect(runWtlink).not.toHaveBeenCalled();
1179
- });
1180
- });
1181
- });
1182
- //# sourceMappingURL=newpr.test.js.map