@ghfs/cli 0.0.4 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/dist/cli.mjs +132 -3068
  2. package/dist/dir.d.mts +4 -0
  3. package/dist/dir.mjs +5 -0
  4. package/dist/{factory-DYHQBeCz.mjs → factory-DmX3S7tf.mjs} +350 -5
  5. package/dist/index.d.mts +2 -195
  6. package/dist/index.mjs +1 -1
  7. package/dist/provider-DCIsHVeA.d.mts +249 -0
  8. package/dist/server/index.d.mts +330 -0
  9. package/dist/server/index.mjs +2 -0
  10. package/dist/server-BwSU4kqr.mjs +3518 -0
  11. package/dist/ui/200.html +1 -1
  12. package/dist/ui/404.html +1 -1
  13. package/dist/ui/_nuxt/2UxHyX5q.js +1 -0
  14. package/dist/ui/_nuxt/32ctXXKs.js +1 -0
  15. package/dist/ui/_nuxt/3e1v2bzS.js +1 -0
  16. package/dist/ui/_nuxt/4A_iFExJ.js +1 -0
  17. package/dist/ui/_nuxt/5VeTt0Ye.js +1 -0
  18. package/dist/ui/_nuxt/5i3qLPDT.js +1 -0
  19. package/dist/ui/_nuxt/85-TOEBH.js +1 -0
  20. package/dist/ui/_nuxt/8syuri0t.js +1 -0
  21. package/dist/ui/_nuxt/B0m2ddpp.js +1 -0
  22. package/dist/ui/_nuxt/B1dDrJ26.js +1 -0
  23. package/dist/ui/_nuxt/B1yitclQ.js +1 -0
  24. package/dist/ui/_nuxt/B5q1GTce.js +1 -0
  25. package/dist/ui/_nuxt/B6aJPvgy.js +1 -0
  26. package/dist/ui/_nuxt/B6eN_cS4.js +1 -0
  27. package/dist/ui/_nuxt/B7c-h3xW.js +1 -0
  28. package/dist/ui/_nuxt/B7mTdjB0.js +1 -0
  29. package/dist/ui/_nuxt/BA47KaF1.js +1 -0
  30. package/dist/ui/_nuxt/BBf5iR-q.js +1 -0
  31. package/dist/ui/_nuxt/BEDo0Tqx.js +1 -0
  32. package/dist/ui/_nuxt/BERRCDM3.js +1 -0
  33. package/dist/ui/_nuxt/BETggiCN.js +1 -0
  34. package/dist/ui/_nuxt/BEwlwnbL.js +1 -0
  35. package/dist/ui/_nuxt/BFfxhgS-.js +1 -0
  36. package/dist/ui/_nuxt/BGJmEYvX.js +1 -0
  37. package/dist/ui/_nuxt/BH7IYjvW.js +1 -0
  38. package/dist/ui/_nuxt/BIGW1oBm.js +1 -0
  39. package/dist/ui/_nuxt/BIv1doCn.js +1 -0
  40. package/dist/ui/_nuxt/BJDFO7_C.js +1 -0
  41. package/dist/ui/_nuxt/BLtJtn59.js +1 -0
  42. package/dist/ui/_nuxt/BM1_JUlF.js +1 -0
  43. package/dist/ui/_nuxt/BMWR74SV.js +1 -0
  44. package/dist/ui/_nuxt/BPQ3VLAy.js +1 -0
  45. package/dist/ui/_nuxt/BQ8w6xss.js +1 -0
  46. package/dist/ui/_nuxt/BRHolxvo.js +1 -0
  47. package/dist/ui/_nuxt/BRZ36xJF.js +1 -0
  48. package/dist/ui/_nuxt/BTJTHyun.js +1 -0
  49. package/dist/ui/_nuxt/BTifaqeh.js +1 -0
  50. package/dist/ui/_nuxt/BUtzH8cE.js +1 -0
  51. package/dist/ui/_nuxt/BUw7H-hv.js +1 -0
  52. package/dist/ui/_nuxt/BV7otONQ.js +1 -0
  53. package/dist/ui/_nuxt/BVQ-GDCI.js +1 -0
  54. package/dist/ui/_nuxt/BVkGXMyj.js +1 -0
  55. package/dist/ui/_nuxt/BWvSN4gD.js +1 -0
  56. package/dist/ui/_nuxt/BXkSAIEj.js +1 -0
  57. package/dist/ui/_nuxt/BYunw83y.js +1 -0
  58. package/dist/ui/_nuxt/BZvkOJ9d.js +1 -0
  59. package/dist/ui/_nuxt/B_m7g4N7.js +1 -0
  60. package/dist/ui/_nuxt/BcOcwvcX.js +1 -0
  61. package/dist/ui/_nuxt/BcVCzyr7.js +1 -0
  62. package/dist/ui/_nuxt/BcllPdc-.js +1 -0
  63. package/dist/ui/_nuxt/BdImnpbu.js +1 -0
  64. package/dist/ui/_nuxt/BdnUsdx6.js +1 -0
  65. package/dist/ui/_nuxt/BeJSdlF9.js +1 -0
  66. package/dist/ui/_nuxt/BfHTSMKl.js +1 -0
  67. package/dist/ui/_nuxt/BfSCyJF4.js +1 -0
  68. package/dist/ui/_nuxt/BfjtVDDH.js +1 -0
  69. package/dist/ui/_nuxt/BgDCqdQA.js +1 -0
  70. package/dist/ui/_nuxt/BhOHFoWU.js +1 -0
  71. package/dist/ui/_nuxt/BhrAK1vL.js +1 -0
  72. package/dist/ui/_nuxt/BkioyH1T.js +1 -0
  73. package/dist/ui/_nuxt/Bkuqu6BP.js +1 -0
  74. package/dist/ui/_nuxt/BmXAJ9_W.js +1 -0
  75. package/dist/ui/_nuxt/Bmn6On1c.js +1 -0
  76. package/dist/ui/_nuxt/Bp3cYrEr.js +1 -0
  77. package/dist/ui/_nuxt/Bp6g37R7.js +1 -0
  78. package/dist/ui/_nuxt/BqNa2AkI.js +1 -0
  79. package/dist/ui/_nuxt/BqTXFGrv.js +1 -0
  80. package/dist/ui/_nuxt/BqYA7rlc.js +1 -0
  81. package/dist/ui/_nuxt/Br6cN0cg.js +1 -0
  82. package/dist/ui/_nuxt/BsS91CYL.js +1 -0
  83. package/dist/ui/_nuxt/BshV_Xbc.js +1 -0
  84. package/dist/ui/_nuxt/BspZqrRM.js +1 -0
  85. package/dist/ui/_nuxt/BtCnVYZw.js +1 -0
  86. package/dist/ui/_nuxt/BtOb2qkB.js +1 -0
  87. package/dist/ui/_nuxt/BthQWCQV.js +1 -0
  88. package/dist/ui/_nuxt/BtqSS_iP.js +1 -0
  89. package/dist/ui/_nuxt/Buea-lGh.js +1 -0
  90. package/dist/ui/_nuxt/Bv_4Rxtq.js +1 -0
  91. package/dist/ui/_nuxt/BvzEVeQv.js +1 -0
  92. package/dist/ui/_nuxt/Bw305WKR.js +1 -0
  93. package/dist/ui/_nuxt/BxgE0vQu.js +1 -0
  94. package/dist/ui/_nuxt/BzJJZx-M.js +1 -0
  95. package/dist/ui/_nuxt/C-HG3fhB.js +1 -0
  96. package/dist/ui/_nuxt/C-SQnVFl.js +1 -0
  97. package/dist/ui/_nuxt/C0HS_06l.js +1 -0
  98. package/dist/ui/_nuxt/C0hk2d4L.js +1 -0
  99. package/dist/ui/_nuxt/C151Ov-r.js +1 -0
  100. package/dist/ui/_nuxt/C27-OAKa.js +1 -0
  101. package/dist/ui/_nuxt/C2t-YnRu.js +1 -0
  102. package/dist/ui/_nuxt/C39BiMTA.js +1 -0
  103. package/dist/ui/_nuxt/C3B-1QV4.js +1 -0
  104. package/dist/ui/_nuxt/C3Wv6jpd.js +1 -0
  105. package/dist/ui/_nuxt/C3mMm8J8.js +1 -0
  106. package/dist/ui/_nuxt/C4EeE6gA.js +1 -0
  107. package/dist/ui/_nuxt/C4IJs8-o.js +1 -0
  108. package/dist/ui/_nuxt/C4gqWexZ.js +1 -0
  109. package/dist/ui/_nuxt/C4zNGwhW.js +1 -0
  110. package/dist/ui/_nuxt/C5BYcBs_.js +1 -0
  111. package/dist/ui/_nuxt/C5YyOfLZ.js +1 -0
  112. package/dist/ui/_nuxt/C7UmGho8.js +1 -0
  113. package/dist/ui/_nuxt/C7zT0LnQ.js +1 -0
  114. package/dist/ui/_nuxt/C8M2exoo.js +1 -0
  115. package/dist/ui/_nuxt/C8lEn-DE.js +1 -0
  116. package/dist/ui/_nuxt/C98Dy4si.js +1 -0
  117. package/dist/ui/_nuxt/C9dUb6Cb.js +1 -0
  118. package/dist/ui/_nuxt/C9oPPf7i.js +1 -0
  119. package/dist/ui/_nuxt/C9tS-k6U.js +1 -0
  120. package/dist/ui/_nuxt/CAFt9gP4.js +1 -0
  121. package/dist/ui/_nuxt/CANp3yme.js +1 -0
  122. package/dist/ui/_nuxt/CBaQQc7g.js +1 -0
  123. package/dist/ui/_nuxt/CDBrVQLm.js +1 -0
  124. package/dist/ui/_nuxt/CDVJQ6XC.js +1 -0
  125. package/dist/ui/_nuxt/CDx5xZoG.js +1 -0
  126. package/dist/ui/_nuxt/CEL-wOlO.js +1 -0
  127. package/dist/ui/_nuxt/CEu0bR-o.js +1 -0
  128. package/dist/ui/_nuxt/CFHQjOhq.js +1 -0
  129. package/dist/ui/_nuxt/CFbOZP3I.js +75 -0
  130. package/dist/ui/_nuxt/CG6Dc4jp.js +1 -0
  131. package/dist/ui/_nuxt/CG8Ifv2g.js +1 -0
  132. package/dist/ui/_nuxt/CH1njM8p.js +1 -0
  133. package/dist/ui/_nuxt/CHLpvVh8.js +1 -0
  134. package/dist/ui/_nuxt/CHM0blh-.js +1 -0
  135. package/dist/ui/_nuxt/CJc9bBzg.js +1 -0
  136. package/dist/ui/_nuxt/CKIfxQSi.js +1 -0
  137. package/dist/ui/_nuxt/CLj8gQPS.js +1 -0
  138. package/dist/ui/_nuxt/CLxacb5B.js +1 -0
  139. package/dist/ui/_nuxt/CMTm3GFP.js +1 -0
  140. package/dist/ui/_nuxt/CO1LY3CK.js +1 -0
  141. package/dist/ui/_nuxt/COcwbKMJ.js +1 -0
  142. package/dist/ui/_nuxt/COkxafJQ.js +1 -0
  143. package/dist/ui/_nuxt/COt5Ahok.js +1 -0
  144. package/dist/ui/_nuxt/CRBrlGZW.js +1 -0
  145. package/dist/ui/_nuxt/CS3Unz2-.js +1 -0
  146. package/dist/ui/_nuxt/CSXwinHm.js +1 -0
  147. package/dist/ui/_nuxt/CTRr51gU.js +1 -0
  148. package/dist/ui/_nuxt/CUuTKBJd.js +1 -0
  149. package/dist/ui/_nuxt/CVO1_9PV.js +1 -0
  150. package/dist/ui/_nuxt/CVdnzihN.js +1 -0
  151. package/dist/ui/_nuxt/CXtECtnM.js +1 -0
  152. package/dist/ui/_nuxt/CXvaQtF9.js +1 -0
  153. package/dist/ui/_nuxt/CYsAdtH9.js +1 -0
  154. package/dist/ui/_nuxt/CafNBF8u.js +1 -0
  155. package/dist/ui/_nuxt/CbFg5uaA.js +1 -0
  156. package/dist/ui/_nuxt/CbfX1IO0.js +1 -0
  157. package/dist/ui/_nuxt/CcsQSqEB.js +1 -0
  158. package/dist/ui/_nuxt/CeAyd5Ju.js +1 -0
  159. package/dist/ui/_nuxt/CeZK1NFH.js +1 -0
  160. package/dist/ui/_nuxt/CenWIFCC.js +1 -0
  161. package/dist/ui/_nuxt/CfQXZHmo.js +1 -0
  162. package/dist/ui/_nuxt/Cg-RD9OK.js +1 -0
  163. package/dist/ui/_nuxt/ChMvpjG-.js +1 -0
  164. package/dist/ui/_nuxt/Cj5Yp3dK.js +1 -0
  165. package/dist/ui/_nuxt/CkByrt1z.js +1 -0
  166. package/dist/ui/_nuxt/CkXjmgJE.js +1 -0
  167. package/dist/ui/_nuxt/CklMAg4u.js +1 -0
  168. package/dist/ui/_nuxt/Cmh6b_Ma.js +1 -0
  169. package/dist/ui/_nuxt/CnnmHF94.js +1 -0
  170. package/dist/ui/_nuxt/CnsnAmq5.js +1 -0
  171. package/dist/ui/_nuxt/Co6uUVPk.js +1 -0
  172. package/dist/ui/_nuxt/CoDkCxhg.js +1 -0
  173. package/dist/ui/_nuxt/Cp-IABpG.js +1 -0
  174. package/dist/ui/_nuxt/Cp8Y5tLI.js +1 -0
  175. package/dist/ui/_nuxt/Cq5zzVJU.js +1 -0
  176. package/dist/ui/_nuxt/CquLrc37.js +1 -0
  177. package/dist/ui/_nuxt/Cs0ovY-E.js +1 -0
  178. package/dist/ui/_nuxt/CsfeWuGM.js +1 -0
  179. package/dist/ui/_nuxt/Csfq5Kiy.js +1 -0
  180. package/dist/ui/_nuxt/CuPHTKiy.js +1 -0
  181. package/dist/ui/_nuxt/CufHLc7y.js +1 -0
  182. package/dist/ui/_nuxt/Cuk6v7N8.js +1 -0
  183. package/dist/ui/_nuxt/Cvjx9yec.js +1 -0
  184. package/dist/ui/_nuxt/CwoSXNpI.js +1 -0
  185. package/dist/ui/_nuxt/CxGSJlkm.js +1 -0
  186. package/dist/ui/_nuxt/CxbxFI8M.js +1 -0
  187. package/dist/ui/_nuxt/CyktbL80.js +1 -0
  188. package/dist/ui/_nuxt/CylS5w8V.js +1 -0
  189. package/dist/ui/_nuxt/Cz2AlsmD.js +1 -0
  190. package/dist/ui/_nuxt/CzTSHFRz.js +1 -0
  191. package/dist/ui/_nuxt/D-2ljcwZ.js +1 -0
  192. package/dist/ui/_nuxt/D0YGMca9.js +1 -0
  193. package/dist/ui/_nuxt/D0r3Knsf.js +1 -0
  194. package/dist/ui/_nuxt/D17OF-Vu.js +1 -0
  195. package/dist/ui/_nuxt/D1j8_8rp.js +1 -0
  196. package/dist/ui/_nuxt/D2j5LXpq.js +1 -0
  197. package/dist/ui/_nuxt/D3apom_0.js +1 -0
  198. package/dist/ui/_nuxt/D3lLCCz7.js +1 -0
  199. package/dist/ui/_nuxt/D4Qn4bBI.js +1 -0
  200. package/dist/ui/_nuxt/D4_iv3hh.js +1 -0
  201. package/dist/ui/_nuxt/D4h5O-jR.js +1 -0
  202. package/dist/ui/_nuxt/D5-asLiD.js +1 -0
  203. package/dist/ui/_nuxt/D53aC0YG.js +1 -0
  204. package/dist/ui/_nuxt/D5KoaKCx.js +1 -0
  205. package/dist/ui/_nuxt/D7o27uSR.js +1 -0
  206. package/dist/ui/_nuxt/D7oLnXFd.js +1 -0
  207. package/dist/ui/_nuxt/D82EKSYY.js +1 -0
  208. package/dist/ui/_nuxt/D82vCrfD.js +1 -0
  209. package/dist/ui/_nuxt/D87Tk5Gz.js +1 -0
  210. package/dist/ui/_nuxt/D8_7TLub.js +1 -0
  211. package/dist/ui/_nuxt/D8l8udqQ.js +1 -0
  212. package/dist/ui/_nuxt/D8rZpOte.js +1 -0
  213. package/dist/ui/_nuxt/D93ZcfNL.js +1 -0
  214. package/dist/ui/_nuxt/D97Zzqfu.js +1 -0
  215. package/dist/ui/_nuxt/DAi9KRSo.js +1 -0
  216. package/dist/ui/_nuxt/DElX3lK9.js +153 -0
  217. package/dist/ui/_nuxt/DFQXde-d.js +1 -0
  218. package/dist/ui/_nuxt/DFWUc33u.js +1 -0
  219. package/dist/ui/_nuxt/DFXneXwc.js +1 -0
  220. package/dist/ui/_nuxt/DGztddWO.js +1 -0
  221. package/dist/ui/_nuxt/DH5Ifo-i.js +1 -0
  222. package/dist/ui/_nuxt/DHCkPAjA.js +1 -0
  223. package/dist/ui/_nuxt/DHJKELXO.js +1 -0
  224. package/dist/ui/_nuxt/DHQR4-dF.js +1 -0
  225. package/dist/ui/_nuxt/DM8c43g1.js +1 -0
  226. package/dist/ui/_nuxt/DMk3OuwW.js +1 -0
  227. package/dist/ui/_nuxt/DMzUqQB5.js +1 -0
  228. package/dist/ui/_nuxt/DN-Z_aST.js +1 -0
  229. package/dist/ui/_nuxt/DQyhUUbL.js +1 -0
  230. package/dist/ui/_nuxt/DRg8JJMk.js +1 -0
  231. package/dist/ui/_nuxt/DRw_LuNl.js +1 -0
  232. package/dist/ui/_nuxt/DSSpBswy.js +1 -0
  233. package/dist/ui/_nuxt/DT3CNIhV.js +1 -0
  234. package/dist/ui/_nuxt/DU1UobuO.js +1 -0
  235. package/dist/ui/_nuxt/DUszq2jm.js +1 -0
  236. package/dist/ui/_nuxt/DV7GczEv.js +1 -0
  237. package/dist/ui/_nuxt/DVFEvuxE.js +1 -0
  238. package/dist/ui/_nuxt/DVMEJ2y_.js +1 -0
  239. package/dist/ui/_nuxt/DVxCFoDh.js +1 -0
  240. package/dist/ui/_nuxt/DWX1VroF.js +1 -0
  241. package/dist/ui/_nuxt/DWedfzmr.js +1 -0
  242. package/dist/ui/_nuxt/DWrx1Km3.js +1 -0
  243. package/dist/ui/_nuxt/DXbdFlpD.js +1 -0
  244. package/dist/ui/_nuxt/DXmwc3jG.js +1 -0
  245. package/dist/ui/_nuxt/DXvB9xmW.js +1 -0
  246. package/dist/ui/_nuxt/DYE7WIF3.js +1 -0
  247. package/dist/ui/_nuxt/DZ9PSxVF.js +1 -0
  248. package/dist/ui/_nuxt/DZxFcAj9.js +1 -0
  249. package/dist/ui/_nuxt/D_Q5rh1f.js +1 -0
  250. package/dist/ui/_nuxt/Da5cRb03.js +1 -0
  251. package/dist/ui/_nuxt/DbWOdLaN.js +1 -0
  252. package/dist/ui/_nuxt/Dc-5oQ3p.js +1 -0
  253. package/dist/ui/_nuxt/DcWK9jms.js +1 -0
  254. package/dist/ui/_nuxt/DcaNXYhu.js +1 -0
  255. package/dist/ui/_nuxt/Dd19v3D-.js +1 -0
  256. package/dist/ui/_nuxt/DdkO51Og.js +1 -0
  257. package/dist/ui/_nuxt/Ddv68eIx.js +1 -0
  258. package/dist/ui/_nuxt/Des-eS-w.js +1 -0
  259. package/dist/ui/_nuxt/Df6bDoY_.js +1 -0
  260. package/dist/ui/_nuxt/DfNjGIrv.js +1 -0
  261. package/dist/ui/_nuxt/DhaVEd23.js +1 -0
  262. package/dist/ui/_nuxt/DjAJT7YJ.js +1 -0
  263. package/dist/ui/_nuxt/DkFqJrB1.js +1 -0
  264. package/dist/ui/_nuxt/DkwncUOv.js +1 -0
  265. package/dist/ui/_nuxt/DlAUqK2U.js +1 -0
  266. package/dist/ui/_nuxt/DnF83RMp.js +1 -0
  267. package/dist/ui/_nuxt/DnULxvSX.js +1 -0
  268. package/dist/ui/_nuxt/Dpen1YoG.js +1 -0
  269. package/dist/ui/_nuxt/Dph4kLrZ.js +1 -0
  270. package/dist/ui/_nuxt/DqQDbK_p.js +1 -0
  271. package/dist/ui/_nuxt/DqwNpetd.js +1 -0
  272. package/dist/ui/_nuxt/DsOJ9woJ.js +1 -0
  273. package/dist/ui/_nuxt/Dspwwk_N.js +1 -0
  274. package/dist/ui/_nuxt/Dx-B1_4e.js +1 -0
  275. package/dist/ui/_nuxt/DyxjwDmM.js +1 -0
  276. package/dist/ui/_nuxt/E3gJ1_iC.js +1 -0
  277. package/dist/ui/_nuxt/GsRaNv29.js +1 -0
  278. package/dist/ui/_nuxt/IF9eRakj.js +1 -0
  279. package/dist/ui/_nuxt/IeuSbFQv.js +1 -0
  280. package/dist/ui/_nuxt/JZbLTBME.js +1 -0
  281. package/dist/ui/_nuxt/L9t79GZl.js +1 -0
  282. package/dist/ui/_nuxt/MzD3tlZU.js +1 -0
  283. package/dist/ui/_nuxt/P80f7IUj.js +1 -0
  284. package/dist/ui/_nuxt/Pmp26Uib.js +1 -0
  285. package/dist/ui/_nuxt/QIJgUcNo.js +1 -0
  286. package/dist/ui/_nuxt/VOosw3JB.js +1 -0
  287. package/dist/ui/_nuxt/Ve4PFQV2.js +1 -0
  288. package/dist/ui/_nuxt/W9tJ9s81.js +1 -0
  289. package/dist/ui/_nuxt/Yzrsuije.js +1 -0
  290. package/dist/ui/_nuxt/_H4v1dQx.js +1 -0
  291. package/dist/ui/_nuxt/_ykCGR6B.js +1 -0
  292. package/dist/ui/_nuxt/bCR0ucgS.js +1 -0
  293. package/dist/ui/_nuxt/bE4Kk8sk.js +1 -0
  294. package/dist/ui/_nuxt/bN70gL4F.js +1 -0
  295. package/dist/ui/_nuxt/builds/latest.json +1 -1
  296. package/dist/ui/_nuxt/builds/meta/12d68071-f4db-447f-b929-9033d66eadc3.json +1 -0
  297. package/dist/ui/_nuxt/dwOrl1Do.js +1 -0
  298. package/dist/ui/_nuxt/entry.DfrFFNxd.css +1 -0
  299. package/dist/ui/_nuxt/eo99z4R2.js +1 -0
  300. package/dist/ui/_nuxt/error-404.C93cll2q.css +1 -0
  301. package/dist/ui/_nuxt/error-500.CPQqaJsC.css +1 -0
  302. package/dist/ui/_nuxt/fuZLfV_i.js +1 -0
  303. package/dist/ui/_nuxt/g9-lgVsj.js +1 -0
  304. package/dist/ui/_nuxt/gcz8RCvz.js +1 -0
  305. package/dist/ui/_nuxt/hJgmCMqR.js +1 -0
  306. package/dist/ui/_nuxt/hegEt444.js +1 -0
  307. package/dist/ui/_nuxt/k_qm7-4y.js +1 -0
  308. package/dist/ui/_nuxt/lBpI9LM0.js +1 -0
  309. package/dist/ui/_nuxt/lXgVvXCa.js +1 -0
  310. package/dist/ui/_nuxt/mWjccvbQ.js +1 -0
  311. package/dist/ui/_nuxt/n2N0HUVH.js +1 -0
  312. package/dist/ui/_nuxt/oH_c3LbQ.js +1 -0
  313. package/dist/ui/_nuxt/qdsjHGoJ.js +1 -0
  314. package/dist/ui/_nuxt/rGO070M0.js +1 -0
  315. package/dist/ui/_nuxt/rZm6bMo-.js +1 -0
  316. package/dist/ui/_nuxt/rgL8RFrC.js +1 -0
  317. package/dist/ui/_nuxt/u5AG7uiY.js +1 -0
  318. package/dist/ui/_nuxt/uYugtg8r.js +1 -0
  319. package/dist/ui/_nuxt/vE_lwT2v.js +1 -0
  320. package/dist/ui/_nuxt/vGWfd6FD.js +1 -0
  321. package/dist/ui/_nuxt/wDzz0qaB.js +1 -0
  322. package/dist/ui/_nuxt/wEQ09or8.js +1 -0
  323. package/dist/ui/_nuxt/yv6CvBhz.js +1 -0
  324. package/dist/ui/index.html +1 -1
  325. package/package.json +21 -22
  326. package/dist/ui/_nuxt/BR_H8Y5I.js +0 -3
  327. package/dist/ui/_nuxt/Cr8ZhsI1.js +0 -3
  328. package/dist/ui/_nuxt/D7-M_06k.js +0 -1
  329. package/dist/ui/_nuxt/DFs1tY5L.js +0 -2
  330. package/dist/ui/_nuxt/DSg5v247.js +0 -1
  331. package/dist/ui/_nuxt/_eJq5waD.js +0 -1
  332. package/dist/ui/_nuxt/builds/meta/560d1a3b-6050-40f3-af12-eecaef34ee9e.json +0 -1
  333. package/dist/ui/_nuxt/entry.CpgMGkX_.css +0 -1
  334. package/dist/ui/_nuxt/error-404.Cl81wDDB.css +0 -1
  335. package/dist/ui/_nuxt/error-500.eY3F7Rvm.css +0 -1
@@ -0,0 +1,3518 @@
1
+ import { a as formatIssueNumber, c as CodedError, d as diagnostics, l as formatInline, n as normalizeReactions, t as createRepositoryProvider, u as log } from "./factory-DmX3S7tf.mjs";
2
+ import { distDir } from "./dir.mjs";
3
+ import process from "node:process";
4
+ import { basename, dirname, isAbsolute, join, resolve } from "pathe";
5
+ import { execFile, spawn } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+ import { existsSync } from "node:fs";
8
+ import { createJiti } from "jiti";
9
+ import { access, mkdir, readFile, readdir, rename, rm, stat, watch, writeFile } from "node:fs/promises";
10
+ import * as v from "valibot";
11
+ import { parse, stringify } from "yaml";
12
+ import { randomBytes } from "node:crypto";
13
+ import { Buffer } from "node:buffer";
14
+ import { createServer } from "node:http";
15
+ import { createBirpc } from "birpc";
16
+ import { getPort } from "get-port-please";
17
+ import { createApp, createRouter, eventHandler, serveStatic, setResponseHeader, setResponseStatus, toNodeListener } from "h3";
18
+ import { parse as parse$1, stringify as stringify$1 } from "structured-clone-es";
19
+ import { WebSocketServer } from "ws";
20
+ import { fileURLToPath } from "node:url";
21
+ import { hash } from "ohash";
22
+ //#region src/constants.ts
23
+ const CONFIG_FILE_CANDIDATES = [
24
+ "ghfs.config.ts",
25
+ "ghfs.config.mts",
26
+ "ghfs.config.mjs",
27
+ "ghfs.config.js",
28
+ "ghfs.config.cjs"
29
+ ];
30
+ const ISSUE_DIR_NAME = "issues";
31
+ const PULL_DIR_NAME = "pulls";
32
+ const CLOSED_DIR_NAME = "closed";
33
+ const SYNC_STATE_FILE_NAME = ".sync.json";
34
+ const ISSUES_INDEX_FILE_NAME = "issues.md";
35
+ const PULLS_INDEX_FILE_NAME = "pulls.md";
36
+ const REPO_SNAPSHOT_FILE_NAME = "repo.json";
37
+ const EXECUTE_FILE_NAME = "execute.yml";
38
+ const EXECUTE_MD_FILE_NAME = "execute.md";
39
+ const EXECUTE_SCHEMA_RELATIVE_PATH = "schema/execute.schema.json";
40
+ //#endregion
41
+ //#region src/config/load.ts
42
+ async function loadUserConfig(cwd) {
43
+ const configPath = findConfigFile(cwd);
44
+ if (!configPath) return { config: {} };
45
+ return {
46
+ path: configPath,
47
+ config: extractUserConfig(await createJiti(resolve(cwd, "ghfs.config.ts"), { interopDefault: true }).import(configPath))
48
+ };
49
+ }
50
+ function extractUserConfig(loaded) {
51
+ if (!loaded || typeof loaded !== "object") return {};
52
+ if ("default" in loaded) {
53
+ const config = loaded.default;
54
+ if (config && typeof config === "object") return config;
55
+ return {};
56
+ }
57
+ return loaded;
58
+ }
59
+ async function resolveConfig(options = {}) {
60
+ const cwd = options.cwd ?? process.cwd();
61
+ const overrides = options.overrides ?? {};
62
+ const { config: userConfig } = await loadUserConfig(cwd);
63
+ const merged = mergeUserConfig(userConfig, overrides);
64
+ const directory = merged.directory ?? ".ghfs";
65
+ const configuredToken = merged.auth?.token?.trim() || "";
66
+ const repo = merged.repo?.trim() || "";
67
+ const issuesEnabled = merged.sync?.issues ?? true;
68
+ const pullsEnabled = merged.sync?.pulls ?? true;
69
+ const closedMode = merged.sync?.closed ?? false;
70
+ const patchesMode = merged.sync?.patches ?? "open";
71
+ return {
72
+ cwd,
73
+ repo,
74
+ directory,
75
+ auth: { token: configuredToken },
76
+ sync: {
77
+ issues: issuesEnabled,
78
+ pulls: pullsEnabled,
79
+ closed: closedMode,
80
+ patches: patchesMode
81
+ }
82
+ };
83
+ }
84
+ function getStorageDirAbsolute(config) {
85
+ return resolve(config.cwd, config.directory);
86
+ }
87
+ function getExecuteFile(config) {
88
+ return join(config.directory, EXECUTE_FILE_NAME);
89
+ }
90
+ function findConfigFile(cwd) {
91
+ for (const candidate of CONFIG_FILE_CANDIDATES) {
92
+ const fullPath = resolve(cwd, candidate);
93
+ if (existsSync(fullPath)) return fullPath;
94
+ }
95
+ }
96
+ function mergeUserConfig(base, overrides) {
97
+ return {
98
+ ...base,
99
+ ...overrides,
100
+ auth: {
101
+ ...base.auth,
102
+ ...overrides.auth
103
+ },
104
+ sync: {
105
+ ...base.sync,
106
+ ...overrides.sync
107
+ }
108
+ };
109
+ }
110
+ //#endregion
111
+ //#region src/utils/fs.ts
112
+ async function pathExists(path) {
113
+ try {
114
+ await access(path);
115
+ return true;
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+ async function writeFileEnsured(path, content) {
121
+ await mkdir(dirname(path), { recursive: true });
122
+ await writeFile(path, content, "utf8");
123
+ }
124
+ async function removePath(path) {
125
+ await rm(path, { force: true });
126
+ }
127
+ async function movePath(from, to) {
128
+ await mkdir(dirname(to), { recursive: true });
129
+ await rename(from, to);
130
+ }
131
+ async function removePatchIfExists(storageDirAbsolute, number) {
132
+ const pullsDir = join(storageDirAbsolute, PULL_DIR_NAME);
133
+ let entries;
134
+ try {
135
+ entries = await readdir(pullsDir, { withFileTypes: true });
136
+ } catch {
137
+ return 0;
138
+ }
139
+ const padded = String(number).padStart(5, "0");
140
+ let removed = 0;
141
+ for (const entry of entries) {
142
+ if (!entry.isFile()) continue;
143
+ const fileName = entry.name;
144
+ const isLegacyPatch = fileName === `${number}.patch`;
145
+ const isCurrentPatch = fileName.startsWith(`${padded}-`) && fileName.endsWith(".patch");
146
+ if (!isLegacyPatch && !isCurrentPatch) continue;
147
+ await removePath(join(pullsDir, fileName));
148
+ removed += 1;
149
+ }
150
+ return removed;
151
+ }
152
+ //#endregion
153
+ //#region src/execute/actions.ts
154
+ const ACTIONS_SUPPORTED = [
155
+ "close",
156
+ "close-with-comment",
157
+ "reopen",
158
+ "set-title",
159
+ "set-body",
160
+ "add-comment",
161
+ "add-labels",
162
+ "remove-labels",
163
+ "set-labels",
164
+ "add-assignees",
165
+ "remove-assignees",
166
+ "set-assignees",
167
+ "set-milestone",
168
+ "clear-milestone",
169
+ "lock",
170
+ "unlock",
171
+ "request-reviewers",
172
+ "remove-reviewers",
173
+ "mark-ready-for-review",
174
+ "convert-to-draft"
175
+ ];
176
+ const ACTIONS_ALIAS_MAP = {
177
+ "open": "reopen",
178
+ "closes": "close",
179
+ "close-comment": "close-with-comment",
180
+ "comment-close": "close-with-comment",
181
+ "close-and-comment": "close-with-comment",
182
+ "comment-and-close": "close-with-comment",
183
+ "label": "add-labels",
184
+ "labels": "add-labels",
185
+ "tag": "add-labels",
186
+ "tags": "add-labels",
187
+ "add-tag": "add-labels",
188
+ "assign": "add-assignees",
189
+ "assignee": "add-assignees",
190
+ "assignees": "add-assignees",
191
+ "body": "set-body",
192
+ "title": "set-title",
193
+ "retitle": "set-title",
194
+ "ready": "mark-ready-for-review",
195
+ "undraft": "mark-ready-for-review",
196
+ "draft": "convert-to-draft",
197
+ "comment": "add-comment"
198
+ };
199
+ const ACTIONS_COLOR_HEX = {
200
+ "close": "#ef4444",
201
+ "close-with-comment": "#fb7185",
202
+ "reopen": "#22c55e",
203
+ "set-title": "#3b82f6",
204
+ "set-body": "#06b6d4",
205
+ "add-comment": "#f97316",
206
+ "add-labels": "#84cc16",
207
+ "remove-labels": "#f43f5e",
208
+ "set-labels": "#eab308",
209
+ "add-assignees": "#10b981",
210
+ "remove-assignees": "#fb7185",
211
+ "set-assignees": "#0ea5e9",
212
+ "set-milestone": "#6366f1",
213
+ "clear-milestone": "#f59e0b",
214
+ "lock": "#a855f7",
215
+ "unlock": "#14b8a6",
216
+ "request-reviewers": "#38bdf8",
217
+ "remove-reviewers": "#e879f9",
218
+ "mark-ready-for-review": "#34d399",
219
+ "convert-to-draft": "#f472b6"
220
+ };
221
+ const ACTION_NAMES_SET = new Set(ACTIONS_SUPPORTED);
222
+ const ACTION_ALIASES = Object.keys(ACTIONS_ALIAS_MAP);
223
+ const ACTION_INPUTS = [...ACTIONS_SUPPORTED, ...ACTION_ALIASES];
224
+ function resolveActionName(action) {
225
+ const normalized = normalizeActionInput(action);
226
+ if (!normalized) return void 0;
227
+ if (ACTION_NAMES_SET.has(normalized)) return normalized;
228
+ return ACTIONS_ALIAS_MAP[normalized];
229
+ }
230
+ function normalizeActionInput(action) {
231
+ return action.trim().toLowerCase();
232
+ }
233
+ //#endregion
234
+ //#region src/execute/schema.ts
235
+ const executeSchema = {
236
+ $id: "https://ghfs.dev/schema/execute.json",
237
+ type: "array",
238
+ items: {
239
+ type: "object",
240
+ additionalProperties: true,
241
+ required: ["number", "action"],
242
+ properties: {
243
+ number: { type: "number" },
244
+ action: {
245
+ type: "string",
246
+ enum: [...ACTION_INPUTS]
247
+ },
248
+ ifUnchangedSince: {
249
+ type: "string",
250
+ format: "date-time"
251
+ },
252
+ title: { type: "string" },
253
+ body: { type: "string" },
254
+ labels: {
255
+ type: "array",
256
+ items: { type: "string" }
257
+ },
258
+ assignees: {
259
+ type: "array",
260
+ items: { type: "string" }
261
+ },
262
+ milestone: { anyOf: [{ type: "string" }, { type: "number" }] },
263
+ reviewers: {
264
+ type: "array",
265
+ items: { type: "string" }
266
+ },
267
+ reason: {
268
+ type: "string",
269
+ enum: [
270
+ "resolved",
271
+ "off-topic",
272
+ "too heated",
273
+ "too-heated",
274
+ "spam"
275
+ ]
276
+ }
277
+ }
278
+ }
279
+ };
280
+ const EXECUTE_FILE_PLACEHOLDER = [
281
+ `# yaml-language-server: $schema=./${EXECUTE_SCHEMA_RELATIVE_PATH}`,
282
+ "# Add operations as YAML list items, then run: `ghfs execute`, examples:",
283
+ "#",
284
+ "# - action: close",
285
+ "# number: 123",
286
+ ""
287
+ ].join("\n");
288
+ const EXECUTE_MD_FILE_PLACEHOLDER = [
289
+ "<!-- Add one action per line, then run: `ghfs execute`, examples: -->",
290
+ "",
291
+ "<!-- close #123 #124 -->",
292
+ "<!-- label #123 bug, triage -->",
293
+ "<!-- assign #123 antfu -->",
294
+ "<!-- comment #123 \"Need more context\" -->",
295
+ "<!-- close-comment #123 \"Closing as completed\" -->",
296
+ "<!-- set-title #123 \"new title\" -->",
297
+ "<!-- add-tag #123 foo, bar -->",
298
+ ""
299
+ ].join("\n");
300
+ async function writeExecuteSchema(storageDirAbsolute) {
301
+ const schemaPath = getExecuteSchemaPath(storageDirAbsolute);
302
+ await mkdir(dirname(schemaPath), { recursive: true });
303
+ await writeFile(schemaPath, `${JSON.stringify(executeSchema, null, 2)}\n`, "utf8");
304
+ return schemaPath;
305
+ }
306
+ async function ensureExecuteArtifacts(executeFilePath) {
307
+ const storageDirAbsolute = dirname(executeFilePath);
308
+ const [schemaPath] = await Promise.all([
309
+ ensureExecuteSchema(storageDirAbsolute),
310
+ ensureExecuteFile(executeFilePath),
311
+ ensureExecuteMdFile(storageDirAbsolute)
312
+ ]);
313
+ return {
314
+ executeFilePath,
315
+ schemaPath
316
+ };
317
+ }
318
+ async function ensureExecuteSchema(storageDirAbsolute) {
319
+ const schemaPath = getExecuteSchemaPath(storageDirAbsolute);
320
+ if (await pathExists(schemaPath)) return schemaPath;
321
+ return writeExecuteSchema(storageDirAbsolute);
322
+ }
323
+ async function ensureExecuteFile(executeFilePath) {
324
+ if (await pathExists(executeFilePath)) return;
325
+ await mkdir(dirname(executeFilePath), { recursive: true });
326
+ await writeFile(executeFilePath, EXECUTE_FILE_PLACEHOLDER, "utf8");
327
+ }
328
+ async function ensureExecuteMdFile(storageDirAbsolute) {
329
+ const executeMdPath = join(storageDirAbsolute, EXECUTE_MD_FILE_NAME);
330
+ if (await pathExists(executeMdPath)) return;
331
+ await mkdir(dirname(executeMdPath), { recursive: true });
332
+ await writeFile(executeMdPath, EXECUTE_MD_FILE_PLACEHOLDER, "utf8");
333
+ }
334
+ function getExecuteSchemaPath(storageDirAbsolute) {
335
+ return join(storageDirAbsolute, EXECUTE_SCHEMA_RELATIVE_PATH);
336
+ }
337
+ //#endregion
338
+ //#region src/execute/validate.ts
339
+ const executeOpSchema = v.looseObject({
340
+ number: v.number(),
341
+ action: v.string(),
342
+ ifUnchangedSince: v.optional(v.string()),
343
+ title: v.optional(v.string()),
344
+ body: v.optional(v.string()),
345
+ labels: v.optional(v.array(v.string())),
346
+ assignees: v.optional(v.array(v.string())),
347
+ milestone: v.optional(v.union([v.string(), v.number()])),
348
+ reviewers: v.optional(v.array(v.string())),
349
+ reason: v.optional(v.picklist([
350
+ "resolved",
351
+ "off-topic",
352
+ "too heated",
353
+ "too-heated",
354
+ "spam"
355
+ ]))
356
+ });
357
+ const executeFileSchema = v.array(executeOpSchema);
358
+ async function readAndValidateExecuteFileWithSource(path) {
359
+ const raw = await readFile(path, "utf8");
360
+ let parsed;
361
+ try {
362
+ parsed = parse(raw || "[]") || [];
363
+ } catch (error) {
364
+ throw new CodedError(log.GHFS0105({ detail: error.message }, { cause: error }));
365
+ }
366
+ const parsedResult = v.safeParse(executeFileSchema, parsed);
367
+ if (!parsedResult.success) {
368
+ const message = parsedResult.issues.map((issue) => {
369
+ const path = issue.path?.map((segment) => String(segment.key)).join(".");
370
+ return `${path ? `${path}: ` : ""}${issue.message}`;
371
+ }).join("; ");
372
+ throw new CodedError(log.GHFS0106({ detail: message }));
373
+ }
374
+ const { pending, sourceActions, actionErrors } = normalizeActionInputs(parsedResult.output);
375
+ if (actionErrors.length) throw new CodedError(log.GHFS0107({ detail: actionErrors.join("; ") }));
376
+ const customErrors = validateExecuteRules(pending);
377
+ if (customErrors.length) throw new CodedError(log.GHFS0108({ detail: customErrors.join("; ") }));
378
+ return {
379
+ ops: pending,
380
+ sourceActions
381
+ };
382
+ }
383
+ async function writeExecuteFile(path, pending) {
384
+ const content = stringify(pending);
385
+ await mkdir(dirname(path), { recursive: true });
386
+ await writeFile(path, content.endsWith("\n") ? content : `${content}\n`, "utf8");
387
+ }
388
+ function validateExecuteRules(pending) {
389
+ const errors = [];
390
+ for (const [index, op] of pending.entries()) {
391
+ const key = `[${index}]`;
392
+ errors.push(...validateOperationRules(key, op));
393
+ }
394
+ return errors;
395
+ }
396
+ function validateOperationRules(key, op) {
397
+ const errors = [];
398
+ if (!Number.isInteger(op.number) || op.number <= 0) errors.push(`${key}: number must be a positive integer`);
399
+ switch (op.action) {
400
+ case "set-title":
401
+ if (!isNonEmptyString(op.title)) errors.push(`${key}: set-title requires title`);
402
+ break;
403
+ case "set-body":
404
+ case "add-comment":
405
+ case "close-with-comment":
406
+ if (!isNonEmptyString(op.body)) errors.push(`${key}: ${op.action} requires body`);
407
+ break;
408
+ case "add-labels":
409
+ case "remove-labels":
410
+ case "set-labels":
411
+ if (!isStringArray(op.labels)) errors.push(`${key}: ${op.action} requires labels[]`);
412
+ break;
413
+ case "add-assignees":
414
+ case "remove-assignees":
415
+ case "set-assignees":
416
+ if (!isStringArray(op.assignees)) errors.push(`${key}: ${op.action} requires assignees[]`);
417
+ break;
418
+ case "set-milestone":
419
+ if (!(typeof op.milestone === "string" || typeof op.milestone === "number")) errors.push(`${key}: set-milestone requires milestone`);
420
+ break;
421
+ case "request-reviewers":
422
+ case "remove-reviewers":
423
+ if (!isStringArray(op.reviewers)) errors.push(`${key}: ${op.action} requires reviewers[]`);
424
+ break;
425
+ default: break;
426
+ }
427
+ if (op.ifUnchangedSince && Number.isNaN(Date.parse(op.ifUnchangedSince))) errors.push(`${key}: ifUnchangedSince must be a valid datetime`);
428
+ return errors;
429
+ }
430
+ function isNonEmptyString(value) {
431
+ return typeof value === "string" && value.trim().length > 0;
432
+ }
433
+ function isStringArray(value) {
434
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string") && value.length > 0;
435
+ }
436
+ function normalizeActionInputs(pending) {
437
+ const normalized = [];
438
+ const sourceActions = [];
439
+ const actionErrors = [];
440
+ for (const [index, op] of pending.entries()) {
441
+ const sourceAction = op.action;
442
+ sourceActions.push(sourceAction);
443
+ const action = resolveActionName(sourceAction);
444
+ if (!action) {
445
+ actionErrors.push(`[${index}]: unknown action: ${sourceAction}`);
446
+ continue;
447
+ }
448
+ normalized.push({
449
+ ...op,
450
+ action
451
+ });
452
+ }
453
+ return {
454
+ pending: normalized,
455
+ sourceActions,
456
+ actionErrors
457
+ };
458
+ }
459
+ //#endregion
460
+ //#region src/execute/sources/execute-md.ts
461
+ const MULTI_SIMPLE_ACTIONS = new Set([
462
+ "close",
463
+ "reopen",
464
+ "clear-milestone",
465
+ "unlock",
466
+ "mark-ready-for-review",
467
+ "convert-to-draft"
468
+ ]);
469
+ function parseExecuteMdLine(line) {
470
+ const trimmed = line.trim();
471
+ if (!trimmed || isCommentLine(trimmed)) return void 0;
472
+ const tokens = tokenizeCommand(trimmed);
473
+ if (!tokens) return {
474
+ kind: "warning",
475
+ message: formatInline(diagnostics.GHFS0150())
476
+ };
477
+ if (tokens.length === 0) return void 0;
478
+ const [commandInput, ...args] = tokens;
479
+ const command = resolveActionName(commandInput);
480
+ if (!command) return {
481
+ kind: "warning",
482
+ message: formatInline(diagnostics.GHFS0151({ command: commandInput }))
483
+ };
484
+ if (command === "set-title") return parseSetTitle(args);
485
+ if (command === "add-labels") return parseAddLabels(args, commandInput);
486
+ if (command === "add-assignees") return parseAddAssignees(args, commandInput);
487
+ if (command === "add-comment") return parseAddComment(args, commandInput);
488
+ if (command === "close-with-comment") return parseCloseWithComment(args, commandInput);
489
+ if (MULTI_SIMPLE_ACTIONS.has(command)) return parseMultiSimpleAction(command, args, commandInput);
490
+ return {
491
+ kind: "warning",
492
+ message: formatInline(diagnostics.GHFS0151({ command: commandInput }))
493
+ };
494
+ }
495
+ async function readExecuteMdFile(path) {
496
+ if (!await pathExists(path)) return parseExecuteMd("");
497
+ return parseExecuteMd(await readFile(path, "utf8"));
498
+ }
499
+ function parseExecuteMd(raw) {
500
+ const lines = raw.split(/\r?\n/);
501
+ const ops = [];
502
+ const parsedLines = [];
503
+ const warnings = [];
504
+ let inHtmlCommentBlock = false;
505
+ for (const [lineIndex, rawLine] of lines.entries()) {
506
+ const trimmed = rawLine.trim();
507
+ if (inHtmlCommentBlock) {
508
+ parsedLines.push({
509
+ kind: "raw",
510
+ raw: rawLine
511
+ });
512
+ if (trimmed.includes("-->")) inHtmlCommentBlock = false;
513
+ continue;
514
+ }
515
+ if (trimmed.startsWith("<!--")) {
516
+ parsedLines.push({
517
+ kind: "raw",
518
+ raw: rawLine
519
+ });
520
+ if (!trimmed.includes("-->")) inHtmlCommentBlock = true;
521
+ continue;
522
+ }
523
+ const parsed = parseExecuteMdLine(rawLine);
524
+ if (!parsed) {
525
+ parsedLines.push({
526
+ kind: "raw",
527
+ raw: rawLine
528
+ });
529
+ continue;
530
+ }
531
+ if (parsed.kind === "warning") {
532
+ warnings.push(`execute-md line ${lineIndex + 1}: ${parsed.message}`);
533
+ parsedLines.push({
534
+ kind: "raw",
535
+ raw: rawLine
536
+ });
537
+ continue;
538
+ }
539
+ if (parsed.kind === "single") {
540
+ const opIndex = ops.length;
541
+ ops.push(parsed.op);
542
+ parsedLines.push({
543
+ kind: "single",
544
+ raw: rawLine,
545
+ opIndex
546
+ });
547
+ continue;
548
+ }
549
+ const opIndexes = [];
550
+ for (const number of parsed.numbers) {
551
+ opIndexes.push(ops.length);
552
+ ops.push({
553
+ action: parsed.action,
554
+ number
555
+ });
556
+ }
557
+ parsedLines.push({
558
+ kind: "multi",
559
+ action: parsed.action,
560
+ command: parsed.command,
561
+ opIndexes
562
+ });
563
+ }
564
+ return {
565
+ ops,
566
+ warnings,
567
+ lines: parsedLines
568
+ };
569
+ }
570
+ function stringifyExecuteMd(parsed, remainingOpIndexes) {
571
+ const lines = [];
572
+ for (const line of parsed.lines) {
573
+ if (line.kind === "raw") {
574
+ lines.push(line.raw);
575
+ continue;
576
+ }
577
+ if (line.kind === "single") {
578
+ if (remainingOpIndexes.has(line.opIndex)) lines.push(line.raw);
579
+ continue;
580
+ }
581
+ const numbers = line.opIndexes.filter((index) => remainingOpIndexes.has(index)).map((index) => parsed.ops[index]?.number).filter((value) => typeof value === "number");
582
+ if (numbers.length > 0) lines.push(`${line.command} ${numbers.map((number) => `#${number}`).join(" ")}`);
583
+ }
584
+ return `${lines.join("\n")}\n`;
585
+ }
586
+ function parseSetTitle(args) {
587
+ if (args.length !== 2) return {
588
+ kind: "warning",
589
+ message: formatInline(diagnostics.GHFS0152({
590
+ command: "set-title",
591
+ syntax: "set-title #<number> \"<title>\""
592
+ }))
593
+ };
594
+ const number = parseIssueRef(args[0]);
595
+ if (!number) return {
596
+ kind: "warning",
597
+ message: formatInline(diagnostics.GHFS0153({ command: "set-title" }))
598
+ };
599
+ return {
600
+ kind: "single",
601
+ op: {
602
+ action: "set-title",
603
+ number,
604
+ title: args[1]
605
+ }
606
+ };
607
+ }
608
+ function parseAddLabels(args, command) {
609
+ if (args.length < 2) return {
610
+ kind: "warning",
611
+ message: formatInline(diagnostics.GHFS0152({
612
+ command,
613
+ syntax: `${command} #<number> <label1, label2>`
614
+ }))
615
+ };
616
+ const number = parseIssueRef(args[0]);
617
+ if (!number) return {
618
+ kind: "warning",
619
+ message: formatInline(diagnostics.GHFS0153({ command }))
620
+ };
621
+ const labels = args.slice(1).flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean);
622
+ if (labels.length === 0) return {
623
+ kind: "warning",
624
+ message: formatInline(diagnostics.GHFS0154({ command }))
625
+ };
626
+ return {
627
+ kind: "single",
628
+ op: {
629
+ action: "add-labels",
630
+ number,
631
+ labels
632
+ }
633
+ };
634
+ }
635
+ function parseAddAssignees(args, command) {
636
+ if (args.length < 2) return {
637
+ kind: "warning",
638
+ message: formatInline(diagnostics.GHFS0152({
639
+ command,
640
+ syntax: `${command} #<number> <assignee1, assignee2>`
641
+ }))
642
+ };
643
+ const number = parseIssueRef(args[0]);
644
+ if (!number) return {
645
+ kind: "warning",
646
+ message: formatInline(diagnostics.GHFS0153({ command }))
647
+ };
648
+ const assignees = args.slice(1).flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean);
649
+ if (assignees.length === 0) return {
650
+ kind: "warning",
651
+ message: formatInline(diagnostics.GHFS0155({ command }))
652
+ };
653
+ return {
654
+ kind: "single",
655
+ op: {
656
+ action: "add-assignees",
657
+ number,
658
+ assignees
659
+ }
660
+ };
661
+ }
662
+ function parseAddComment(args, command) {
663
+ if (args.length < 2) return {
664
+ kind: "warning",
665
+ message: formatInline(diagnostics.GHFS0152({
666
+ command,
667
+ syntax: `${command} #<number> "<comment>"`
668
+ }))
669
+ };
670
+ const number = parseIssueRef(args[0]);
671
+ if (!number) return {
672
+ kind: "warning",
673
+ message: formatInline(diagnostics.GHFS0153({ command }))
674
+ };
675
+ const body = args.slice(1).join(" ").trim();
676
+ if (!body) return {
677
+ kind: "warning",
678
+ message: formatInline(diagnostics.GHFS0156({ command }))
679
+ };
680
+ return {
681
+ kind: "single",
682
+ op: {
683
+ action: "add-comment",
684
+ number,
685
+ body
686
+ }
687
+ };
688
+ }
689
+ function parseCloseWithComment(args, command) {
690
+ if (args.length < 2) return {
691
+ kind: "warning",
692
+ message: formatInline(diagnostics.GHFS0152({
693
+ command,
694
+ syntax: `${command} #<number> "<comment>"`
695
+ }))
696
+ };
697
+ const number = parseIssueRef(args[0]);
698
+ if (!number) return {
699
+ kind: "warning",
700
+ message: formatInline(diagnostics.GHFS0153({ command }))
701
+ };
702
+ const body = args.slice(1).join(" ").trim();
703
+ if (!body) return {
704
+ kind: "warning",
705
+ message: formatInline(diagnostics.GHFS0156({ command }))
706
+ };
707
+ return {
708
+ kind: "single",
709
+ op: {
710
+ action: "close-with-comment",
711
+ number,
712
+ body
713
+ }
714
+ };
715
+ }
716
+ function parseMultiSimpleAction(action, args, command) {
717
+ const numbers = args.map(parseIssueRef);
718
+ if (numbers.length === 0 || numbers.some((number) => !number)) return {
719
+ kind: "warning",
720
+ message: formatInline(diagnostics.GHFS0157({ command }))
721
+ };
722
+ return {
723
+ kind: "multi",
724
+ action,
725
+ command,
726
+ numbers
727
+ };
728
+ }
729
+ function parseIssueRef(value) {
730
+ const match = value.match(/^#(\d+)$/);
731
+ if (!match) return void 0;
732
+ const number = Number.parseInt(match[1], 10);
733
+ if (!Number.isInteger(number) || number <= 0) return void 0;
734
+ return number;
735
+ }
736
+ function tokenizeCommand(value) {
737
+ const tokens = [];
738
+ let index = 0;
739
+ while (index < value.length) {
740
+ while (index < value.length && /\s/.test(value[index])) index += 1;
741
+ if (index >= value.length) break;
742
+ if (value[index] === "\"") {
743
+ index += 1;
744
+ let token = "";
745
+ let closed = false;
746
+ while (index < value.length) {
747
+ const char = value[index];
748
+ if (char === "\\") {
749
+ const next = value[index + 1];
750
+ if (next === "\"" || next === "\\") {
751
+ token += next;
752
+ index += 2;
753
+ continue;
754
+ }
755
+ }
756
+ if (char === "\"") {
757
+ closed = true;
758
+ index += 1;
759
+ break;
760
+ }
761
+ token += char;
762
+ index += 1;
763
+ }
764
+ if (!closed) return void 0;
765
+ tokens.push(token);
766
+ continue;
767
+ }
768
+ const start = index;
769
+ while (index < value.length && !/\s/.test(value[index])) index += 1;
770
+ tokens.push(value.slice(start, index));
771
+ }
772
+ return tokens;
773
+ }
774
+ function isCommentLine(trimmed) {
775
+ return trimmed.startsWith("#") || trimmed.startsWith("//") || trimmed.startsWith("<!--");
776
+ }
777
+ //#endregion
778
+ //#region package.json
779
+ var version = "0.1.1";
780
+ //#endregion
781
+ //#region src/meta.ts
782
+ const GHFS_NAME = "ghfs";
783
+ const GHFS_VERSION = version;
784
+ //#endregion
785
+ //#region src/sync/state.ts
786
+ function getSyncStatePath(storageDirAbsolute) {
787
+ return join(storageDirAbsolute, SYNC_STATE_FILE_NAME);
788
+ }
789
+ async function loadSyncState(storageDirAbsolute) {
790
+ const path = getSyncStatePath(storageDirAbsolute);
791
+ try {
792
+ const raw = await readFile(path, "utf8");
793
+ const parsed = JSON.parse(raw);
794
+ if (parsed.version !== 2) return createEmptySyncState();
795
+ return {
796
+ version: 2,
797
+ items: normalizeItems(parsed.items),
798
+ executions: normalizeExecutions(parsed.executions),
799
+ ghfsVersion: typeof parsed.ghfsVersion === "string" ? parsed.ghfsVersion : void 0,
800
+ repo: parsed.repo,
801
+ lastSyncedAt: parsed.lastSyncedAt,
802
+ lastSince: parsed.lastSince,
803
+ lastRepoUpdatedAt: parsed.lastRepoUpdatedAt,
804
+ lastSyncRun: parsed.lastSyncRun
805
+ };
806
+ } catch {
807
+ return createEmptySyncState();
808
+ }
809
+ }
810
+ function normalizeItems(items) {
811
+ if (!items) return {};
812
+ if (typeof items !== "object" || Array.isArray(items)) return {};
813
+ const normalizedItems = {};
814
+ for (const [key, item] of Object.entries(items)) {
815
+ const normalizedItem = normalizeItem(item);
816
+ if (!normalizedItem) continue;
817
+ normalizedItems[key] = normalizedItem;
818
+ }
819
+ return normalizedItems;
820
+ }
821
+ function normalizeExecutions(executions) {
822
+ if (!Array.isArray(executions)) return [];
823
+ return executions.map((execution) => {
824
+ if (!execution || typeof execution !== "object") return execution;
825
+ const typedExecution = execution;
826
+ if (typedExecution.mode === "dry-run") return {
827
+ ...typedExecution,
828
+ mode: "report"
829
+ };
830
+ return typedExecution;
831
+ });
832
+ }
833
+ async function saveSyncState(storageDirAbsolute, state) {
834
+ await mkdir(storageDirAbsolute, { recursive: true });
835
+ const normalizedState = {
836
+ ...state,
837
+ ghfsVersion: state.ghfsVersion ?? GHFS_VERSION
838
+ };
839
+ await writeFile(getSyncStatePath(storageDirAbsolute), `${JSON.stringify(normalizedState, null, 2)}\n`, "utf8");
840
+ }
841
+ function createEmptySyncState() {
842
+ return {
843
+ version: 2,
844
+ items: {},
845
+ executions: []
846
+ };
847
+ }
848
+ function appendExecution(state, result, limit = 20) {
849
+ const nextExecutions = [result, ...state.executions].slice(0, limit);
850
+ return {
851
+ ...state,
852
+ executions: nextExecutions
853
+ };
854
+ }
855
+ function normalizeItem(item) {
856
+ if (!item || typeof item !== "object") return void 0;
857
+ if (!item.lastUpdatedAt || !item.lastSyncedAt || !item.filePath) return void 0;
858
+ if (!item.data || !item.data.item) return void 0;
859
+ const comments = Array.isArray(item.data.comments) ? item.data.comments : [];
860
+ const rawItem = item.data.item;
861
+ const commits = Array.isArray(item.data.commits) ? item.data.commits : void 0;
862
+ const timeline = Array.isArray(item.data.timeline) ? item.data.timeline : void 0;
863
+ return {
864
+ ...item,
865
+ data: {
866
+ ...item.data,
867
+ item: {
868
+ ...rawItem,
869
+ labels: Array.isArray(rawItem.labels) ? rawItem.labels.filter((v) => typeof v === "string") : [],
870
+ assignees: Array.isArray(rawItem.assignees) ? rawItem.assignees.filter((v) => typeof v === "string") : [],
871
+ reactions: normalizeReactions(rawItem.reactions)
872
+ },
873
+ comments: comments.filter((comment) => comment && typeof comment === "object").map((comment) => ({
874
+ ...comment,
875
+ reactions: normalizeReactions(comment.reactions)
876
+ })),
877
+ ...commits ? { commits } : {},
878
+ ...timeline ? { timeline } : {}
879
+ }
880
+ };
881
+ }
882
+ //#endregion
883
+ //#region src/execute/sources/per-item.ts
884
+ async function loadPerItemSource(storageDir) {
885
+ const syncState = await loadSyncState(storageDir);
886
+ const ops = [];
887
+ const warnings = [];
888
+ const repo = syncState.repo;
889
+ for (const tracked of Object.values(syncState.items)) {
890
+ const markdownPath = join(storageDir, tracked.filePath);
891
+ if (!await pathExists(markdownPath)) {
892
+ warnings.push(formatInline(diagnostics.GHFS0160({
893
+ issue: formatIssueNumber(tracked.number, { repo }),
894
+ path: tracked.filePath
895
+ })));
896
+ continue;
897
+ }
898
+ const frontmatter = parseFrontmatter(await readFile(markdownPath, "utf8"));
899
+ if (!frontmatter) {
900
+ warnings.push(formatInline(diagnostics.GHFS0161({ issue: formatIssueNumber(tracked.number, { repo }) })));
901
+ continue;
902
+ }
903
+ const trackedItem = tracked.data.item;
904
+ const itemOps = computePerItemOps({
905
+ number: tracked.number,
906
+ current: {
907
+ title: trackedItem.title,
908
+ state: trackedItem.state,
909
+ labels: trackedItem.labels,
910
+ assignees: trackedItem.assignees,
911
+ milestone: trackedItem.milestone
912
+ },
913
+ desired: frontmatter,
914
+ updatedAt: trackedItem.updatedAt
915
+ });
916
+ ops.push(...itemOps);
917
+ }
918
+ return {
919
+ ops,
920
+ warnings
921
+ };
922
+ }
923
+ function computePerItemOps(input) {
924
+ const ops = [];
925
+ const ifUnchangedSince = input.updatedAt;
926
+ if (input.current.title !== input.desired.title) ops.push({
927
+ action: "set-title",
928
+ number: input.number,
929
+ title: input.desired.title,
930
+ ifUnchangedSince
931
+ });
932
+ if (input.current.state !== input.desired.state) ops.push({
933
+ action: input.desired.state === "closed" ? "close" : "reopen",
934
+ number: input.number,
935
+ ifUnchangedSince
936
+ });
937
+ if (!sameStringSet(input.current.labels, input.desired.labels)) {
938
+ const additions = diffStrings(input.desired.labels, input.current.labels);
939
+ const deletions = diffStrings(input.current.labels, input.desired.labels);
940
+ if (additions.length > 0 && deletions.length > 0) ops.push({
941
+ action: "set-labels",
942
+ number: input.number,
943
+ labels: input.desired.labels,
944
+ ifUnchangedSince
945
+ });
946
+ else if (additions.length > 0) ops.push({
947
+ action: "add-labels",
948
+ number: input.number,
949
+ labels: additions,
950
+ ifUnchangedSince
951
+ });
952
+ else if (deletions.length > 0) ops.push({
953
+ action: "remove-labels",
954
+ number: input.number,
955
+ labels: deletions,
956
+ ifUnchangedSince
957
+ });
958
+ }
959
+ if (!sameStringSet(input.current.assignees, input.desired.assignees)) {
960
+ if (input.desired.assignees.length > 0) ops.push({
961
+ action: "set-assignees",
962
+ number: input.number,
963
+ assignees: input.desired.assignees,
964
+ ifUnchangedSince
965
+ });
966
+ else if (input.current.assignees.length > 0) ops.push({
967
+ action: "remove-assignees",
968
+ number: input.number,
969
+ assignees: input.current.assignees,
970
+ ifUnchangedSince
971
+ });
972
+ }
973
+ if (normalizeMilestone(input.current.milestone) !== normalizeMilestone(input.desired.milestone)) if (input.desired.milestone) ops.push({
974
+ action: "set-milestone",
975
+ number: input.number,
976
+ milestone: input.desired.milestone,
977
+ ifUnchangedSince
978
+ });
979
+ else ops.push({
980
+ action: "clear-milestone",
981
+ number: input.number,
982
+ ifUnchangedSince
983
+ });
984
+ return ops;
985
+ }
986
+ function parseFrontmatter(raw) {
987
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
988
+ if (!match) return void 0;
989
+ let parsed;
990
+ try {
991
+ parsed = parse(match[1]);
992
+ } catch {
993
+ return;
994
+ }
995
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
996
+ const data = parsed;
997
+ const title = typeof data.title === "string" && data.title.trim().length > 0 ? data.title.trim() : void 0;
998
+ const state = data.state === "open" || data.state === "closed" ? data.state : void 0;
999
+ if (!title || !state) return void 0;
1000
+ return {
1001
+ title,
1002
+ state,
1003
+ labels: normalizeStringArray(data.labels ?? data.tags),
1004
+ assignees: normalizeStringArray(data.assignees),
1005
+ milestone: normalizeMilestone(data.milestone)
1006
+ };
1007
+ }
1008
+ function normalizeStringArray(value) {
1009
+ if (!Array.isArray(value)) return [];
1010
+ const unique = /* @__PURE__ */ new Set();
1011
+ for (const entry of value) {
1012
+ if (typeof entry !== "string") continue;
1013
+ const normalized = entry.trim();
1014
+ if (!normalized) continue;
1015
+ unique.add(normalized);
1016
+ }
1017
+ return [...unique];
1018
+ }
1019
+ function sameStringSet(left, right) {
1020
+ if (left.length !== right.length) return false;
1021
+ const sortedLeft = [...left].sort();
1022
+ const sortedRight = [...right].sort();
1023
+ return sortedLeft.every((value, index) => value === sortedRight[index]);
1024
+ }
1025
+ function normalizeMilestone(value) {
1026
+ if (typeof value !== "string") return null;
1027
+ const normalized = value.trim();
1028
+ return normalized.length > 0 ? normalized : null;
1029
+ }
1030
+ function diffStrings(source, target) {
1031
+ const targetSet = new Set(target);
1032
+ return source.filter((value) => !targetSet.has(value));
1033
+ }
1034
+ //#endregion
1035
+ //#region src/execute/sources/index.ts
1036
+ async function loadExecuteSources(executeFilePath) {
1037
+ const storageDir = dirname(executeFilePath);
1038
+ const executeMdPath = join(storageDir, EXECUTE_MD_FILE_NAME);
1039
+ const yml = await readAndValidateExecuteFileWithSource(executeFilePath);
1040
+ const ymlOps = yml.ops;
1041
+ const executeMd = await readExecuteMdFile(executeMdPath);
1042
+ const perItem = await loadPerItemSource(storageDir);
1043
+ const mergedOps = [
1044
+ ...ymlOps,
1045
+ ...executeMd.ops,
1046
+ ...perItem.ops
1047
+ ];
1048
+ const customErrors = validateExecuteRules(mergedOps);
1049
+ if (customErrors.length) throw new CodedError(log.GHFS0108({ detail: customErrors.join("; ") }));
1050
+ return {
1051
+ ops: mergedOps,
1052
+ warnings: [...executeMd.warnings, ...perItem.warnings],
1053
+ async writeRemaining(remainingIndexes) {
1054
+ await writeExecuteFile(executeFilePath, ymlOps.map((op, index) => ({
1055
+ op,
1056
+ index
1057
+ })).filter((item) => remainingIndexes.has(item.index)).map(({ op, index }) => ({
1058
+ ...op,
1059
+ action: yml.sourceActions[index] ?? op.action
1060
+ })));
1061
+ if (!await pathExists(executeMdPath)) return;
1062
+ const mdOffset = ymlOps.length;
1063
+ const mdRemaining = /* @__PURE__ */ new Set();
1064
+ for (const index of remainingIndexes) if (index >= mdOffset) mdRemaining.add(index - mdOffset);
1065
+ await writeFile(executeMdPath, stringifyExecuteMd(executeMd, mdRemaining), "utf-8");
1066
+ }
1067
+ };
1068
+ }
1069
+ //#endregion
1070
+ //#region src/execute/index.ts
1071
+ function createCancelledError() {
1072
+ return new CodedError(log.GHFS0102());
1073
+ }
1074
+ function isExecuteCancelledError(error) {
1075
+ return error instanceof CodedError && error.diagnostic.code === "GHFS0102";
1076
+ }
1077
+ async function executePendingChanges(options) {
1078
+ try {
1079
+ await ensureExecuteArtifacts(options.executeFilePath);
1080
+ const sources = await loadExecuteSources(options.executeFilePath);
1081
+ const allOps = sources.ops;
1082
+ for (const warning of sources.warnings) options.onWarning?.(warning);
1083
+ if (allOps.length === 0) return {
1084
+ runId: createRunId(),
1085
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1086
+ mode: "report",
1087
+ repo: options.repo,
1088
+ planned: 0,
1089
+ applied: 0,
1090
+ failed: 0,
1091
+ details: []
1092
+ };
1093
+ const interactive = process.stdin.isTTY && !options.nonInteractive;
1094
+ if (interactive && !options.prompts) throw new CodedError(log.GHFS0100());
1095
+ const selected = Array.isArray(options.selectedIndexes) ? selectOperationsByIndexes(allOps, options.selectedIndexes) : interactive ? await selectOperations(allOps, options.prompts) : allOps.map((op, index) => ({
1096
+ op,
1097
+ index
1098
+ }));
1099
+ const runId = createRunId();
1100
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
1101
+ const mode = options.apply ? "apply" : "report";
1102
+ options.reporter?.onStart?.({
1103
+ repo: options.repo,
1104
+ mode,
1105
+ planned: selected.length
1106
+ });
1107
+ if (selected.length === 0) {
1108
+ const result = {
1109
+ runId,
1110
+ createdAt,
1111
+ mode,
1112
+ repo: options.repo,
1113
+ planned: 0,
1114
+ applied: 0,
1115
+ failed: 0,
1116
+ details: []
1117
+ };
1118
+ options.reporter?.onComplete?.({ result });
1119
+ return result;
1120
+ }
1121
+ options.onPlan?.(selected.map((item) => item.op));
1122
+ if (!options.apply) {
1123
+ const result = {
1124
+ runId,
1125
+ createdAt,
1126
+ mode: "report",
1127
+ repo: options.repo,
1128
+ planned: selected.length,
1129
+ applied: 0,
1130
+ failed: 0,
1131
+ details: selected.map(({ op, index }) => ({
1132
+ op: index + 1,
1133
+ action: op.action,
1134
+ number: op.number,
1135
+ status: "planned",
1136
+ message: describeExecutionAction(op.action, op.number)
1137
+ }))
1138
+ };
1139
+ options.reporter?.onComplete?.({ result });
1140
+ return result;
1141
+ }
1142
+ if (interactive) {
1143
+ if (!await confirmApply(selected.length, options.prompts)) throw createCancelledError();
1144
+ }
1145
+ const provider = options.provider ?? createRepositoryProvider({
1146
+ token: options.token,
1147
+ repo: options.repo
1148
+ });
1149
+ const details = [];
1150
+ const appliedIndexes = /* @__PURE__ */ new Set();
1151
+ let applied = 0;
1152
+ let failed = 0;
1153
+ for (const { op, index } of selected) try {
1154
+ const target = await applyOperation(provider, op);
1155
+ appliedIndexes.add(index);
1156
+ await persistRemainingOps(sources.writeRemaining, allOps, appliedIndexes);
1157
+ const detail = {
1158
+ op: index + 1,
1159
+ action: op.action,
1160
+ number: op.number,
1161
+ target,
1162
+ status: "applied",
1163
+ message: describeExecutionAction(op.action, op.number)
1164
+ };
1165
+ details.push(detail);
1166
+ applied += 1;
1167
+ options.reporter?.onProgress?.({
1168
+ repo: options.repo,
1169
+ mode: "apply",
1170
+ planned: selected.length,
1171
+ completed: details.length,
1172
+ applied,
1173
+ failed,
1174
+ detail
1175
+ });
1176
+ } catch (error) {
1177
+ failed += 1;
1178
+ const detail = {
1179
+ op: index + 1,
1180
+ action: op.action,
1181
+ number: op.number,
1182
+ status: "failed",
1183
+ message: error.message
1184
+ };
1185
+ details.push(detail);
1186
+ options.reporter?.onProgress?.({
1187
+ repo: options.repo,
1188
+ mode: "apply",
1189
+ planned: selected.length,
1190
+ completed: details.length,
1191
+ applied,
1192
+ failed,
1193
+ detail
1194
+ });
1195
+ if (!options.continueOnError) break;
1196
+ }
1197
+ const result = {
1198
+ runId,
1199
+ createdAt,
1200
+ mode: "apply",
1201
+ repo: options.repo,
1202
+ planned: selected.length,
1203
+ applied,
1204
+ failed,
1205
+ details
1206
+ };
1207
+ options.reporter?.onComplete?.({ result });
1208
+ return result;
1209
+ } catch (error) {
1210
+ options.reporter?.onError?.({ error });
1211
+ throw error;
1212
+ }
1213
+ }
1214
+ async function persistRemainingOps(writeRemaining, allOps, appliedIndexes) {
1215
+ const remainingIndexes = /* @__PURE__ */ new Set();
1216
+ for (const [index] of allOps.entries()) if (!appliedIndexes.has(index)) remainingIndexes.add(index);
1217
+ await writeRemaining(remainingIndexes);
1218
+ }
1219
+ async function applyOperation(provider, op) {
1220
+ const item = await provider.fetchItemSnapshot(op.number);
1221
+ const isPull = item.kind === "pull";
1222
+ if (op.ifUnchangedSince) {
1223
+ const remoteUpdatedAt = item.updatedAt;
1224
+ if (remoteUpdatedAt && new Date(remoteUpdatedAt).getTime() > new Date(op.ifUnchangedSince).getTime()) throw new CodedError(log.GHFS0101({ remoteUpdatedAt }));
1225
+ }
1226
+ switch (op.action) {
1227
+ case "close":
1228
+ await provider.actionClose(op.number);
1229
+ break;
1230
+ case "reopen":
1231
+ await provider.actionReopen(op.number);
1232
+ break;
1233
+ case "set-title":
1234
+ await provider.actionSetTitle(op.number, op.title);
1235
+ break;
1236
+ case "set-body":
1237
+ await provider.actionSetBody(op.number, op.body);
1238
+ break;
1239
+ case "add-comment":
1240
+ await provider.actionAddComment(op.number, op.body);
1241
+ break;
1242
+ case "close-with-comment":
1243
+ await provider.actionAddComment(op.number, op.body);
1244
+ await provider.actionClose(op.number);
1245
+ break;
1246
+ case "add-labels":
1247
+ await provider.actionAddLabels(op.number, op.labels);
1248
+ break;
1249
+ case "remove-labels":
1250
+ await provider.actionRemoveLabels(op.number, op.labels);
1251
+ break;
1252
+ case "set-labels":
1253
+ await provider.actionSetLabels(op.number, op.labels);
1254
+ break;
1255
+ case "add-assignees":
1256
+ await provider.actionAddAssignees(op.number, op.assignees);
1257
+ break;
1258
+ case "remove-assignees":
1259
+ await provider.actionRemoveAssignees(op.number, op.assignees);
1260
+ break;
1261
+ case "set-assignees":
1262
+ await provider.actionSetAssignees(op.number, op.assignees);
1263
+ break;
1264
+ case "set-milestone":
1265
+ await provider.actionSetMilestone(op.number, op.milestone);
1266
+ break;
1267
+ case "clear-milestone":
1268
+ await provider.actionClearMilestone(op.number);
1269
+ break;
1270
+ case "lock":
1271
+ await provider.actionLock(op.number, op.reason);
1272
+ break;
1273
+ case "unlock":
1274
+ await provider.actionUnlock(op.number);
1275
+ break;
1276
+ case "request-reviewers":
1277
+ ensurePullAction(op.action, op.number, isPull);
1278
+ await provider.actionRequestReviewers(op.number, op.reviewers);
1279
+ break;
1280
+ case "remove-reviewers":
1281
+ ensurePullAction(op.action, op.number, isPull);
1282
+ await provider.actionRemoveReviewers(op.number, op.reviewers);
1283
+ break;
1284
+ case "mark-ready-for-review":
1285
+ ensurePullAction(op.action, op.number, isPull);
1286
+ await provider.actionMarkReadyForReview(op.number);
1287
+ break;
1288
+ case "convert-to-draft":
1289
+ ensurePullAction(op.action, op.number, isPull);
1290
+ await provider.actionConvertToDraft(op.number);
1291
+ break;
1292
+ default: throw new CodedError(log.GHFS0103({ action: String(op.action) }));
1293
+ }
1294
+ return item.kind;
1295
+ }
1296
+ function createRunId() {
1297
+ return `run_${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "")}_${Math.random().toString(36).slice(2, 7)}`;
1298
+ }
1299
+ async function selectOperations(ops, prompts) {
1300
+ const selectedIndexes = await prompts.selectOperations(ops);
1301
+ if (!selectedIndexes) throw createCancelledError();
1302
+ const selectedIndexesSet = new Set(selectedIndexes);
1303
+ return ops.map((op, index) => ({
1304
+ op,
1305
+ index
1306
+ })).filter((item) => selectedIndexesSet.has(item.index));
1307
+ }
1308
+ async function confirmApply(count, prompts) {
1309
+ const result = await prompts.confirmApply(count);
1310
+ if (result == null) return false;
1311
+ return result;
1312
+ }
1313
+ function selectOperationsByIndexes(ops, selectedIndexes) {
1314
+ const selectedSet = /* @__PURE__ */ new Set();
1315
+ for (const index of selectedIndexes) if (Number.isInteger(index) && index >= 0 && index < ops.length) selectedSet.add(index);
1316
+ return ops.map((op, index) => ({
1317
+ op,
1318
+ index
1319
+ })).filter((item) => selectedSet.has(item.index));
1320
+ }
1321
+ function ensurePullAction(action, number, isPull) {
1322
+ if (!isPull) throw new CodedError(log.GHFS0104({
1323
+ action,
1324
+ issue: `#${number}`
1325
+ }));
1326
+ }
1327
+ function describeExecutionAction(action, number) {
1328
+ return `${action} #${number}`;
1329
+ }
1330
+ //#endregion
1331
+ //#region src/sync/execution-log.ts
1332
+ async function appendExecutionResult(storageDirAbsolute, result) {
1333
+ await saveSyncState(storageDirAbsolute, appendExecution(await loadSyncState(storageDirAbsolute), result));
1334
+ }
1335
+ //#endregion
1336
+ //#region src/utils/sync.ts
1337
+ function resolveSince(options, syncState) {
1338
+ if (options.full) return void 0;
1339
+ if (options.since) return options.since;
1340
+ return syncState.lastSyncedAt;
1341
+ }
1342
+ function normalizeIssueNumbers(numbers) {
1343
+ if (!numbers) return void 0;
1344
+ return [...new Set(numbers.filter((number) => Number.isInteger(number) && number > 0))];
1345
+ }
1346
+ //#endregion
1347
+ //#region src/sync/markdown.ts
1348
+ const FIELDS_ALWAYS_KEEP = new Set(["labels", "assignees"]);
1349
+ const FIELDS_ALWAYS_EXCLUDE = new Set(["repo", "kind"]);
1350
+ const REACTION_FIELDS = [
1351
+ {
1352
+ key: "plusOne",
1353
+ emoji: "👍"
1354
+ },
1355
+ {
1356
+ key: "minusOne",
1357
+ emoji: "👎"
1358
+ },
1359
+ {
1360
+ key: "laugh",
1361
+ emoji: "😄"
1362
+ },
1363
+ {
1364
+ key: "hooray",
1365
+ emoji: "🎉"
1366
+ },
1367
+ {
1368
+ key: "confused",
1369
+ emoji: "😕"
1370
+ },
1371
+ {
1372
+ key: "heart",
1373
+ emoji: "❤️"
1374
+ },
1375
+ {
1376
+ key: "rocket",
1377
+ emoji: "🚀"
1378
+ },
1379
+ {
1380
+ key: "eyes",
1381
+ emoji: "👀"
1382
+ }
1383
+ ];
1384
+ function renderIssueMarkdown(input) {
1385
+ const url = input.url || `https://github.com/${input.repo}/${input.kind === "pull" ? "pull" : "issues"}/${input.number}`;
1386
+ const frontmatter = {
1387
+ repo: input.repo,
1388
+ number: input.number,
1389
+ kind: input.kind,
1390
+ url,
1391
+ state: input.state,
1392
+ title: input.title,
1393
+ author: input.author,
1394
+ labels: input.labels,
1395
+ assignees: input.assignees,
1396
+ milestone: input.milestone,
1397
+ created_at: input.createdAt,
1398
+ updated_at: input.updatedAt,
1399
+ closed_at: input.closedAt,
1400
+ last_synced_at: input.lastSyncedAt,
1401
+ reactions: formatReactionsFrontmatter(input.reactions),
1402
+ is_draft: input.pr?.isDraft,
1403
+ merged: input.pr?.merged,
1404
+ merged_at: input.pr?.mergedAt,
1405
+ base_ref: input.pr?.baseRef,
1406
+ head_ref: input.pr?.headRef,
1407
+ reviewers_requested: input.pr?.requestedReviewers
1408
+ };
1409
+ const compactFrontmatter = Object.fromEntries(Object.entries(frontmatter).filter(([key, value]) => {
1410
+ if (FIELDS_ALWAYS_EXCLUDE.has(key)) return false;
1411
+ if (FIELDS_ALWAYS_KEEP.has(key)) return true;
1412
+ if (value === void 0 || value === null || value === false) return false;
1413
+ if (Array.isArray(value)) return value.length > 0;
1414
+ return true;
1415
+ }));
1416
+ const sections = [
1417
+ `# ${input.title}`,
1418
+ "",
1419
+ "## Description",
1420
+ "",
1421
+ input.body?.trim() || "_No description._"
1422
+ ];
1423
+ const bodyReactionsLine = formatReactionsLine(input.reactions);
1424
+ if (bodyReactionsLine) {
1425
+ sections.push("");
1426
+ sections.push(bodyReactionsLine);
1427
+ }
1428
+ sections.push("");
1429
+ sections.push("---");
1430
+ sections.push("");
1431
+ sections.push("## Comments");
1432
+ sections.push("");
1433
+ if (input.comments.length === 0) sections.push("_No comments._");
1434
+ else for (const [index, comment] of input.comments.entries()) {
1435
+ if (index > 0) {
1436
+ sections.push("---");
1437
+ sections.push("");
1438
+ }
1439
+ sections.push(`### @${comment.author} on ${comment.createdAt}`);
1440
+ sections.push(`<!-- comment-id:${comment.id} updated:${comment.updatedAt} -->`);
1441
+ sections.push("");
1442
+ sections.push(comment.body?.trim() || "_No content._");
1443
+ const reactionsLine = formatReactionsLine(comment.reactions);
1444
+ if (reactionsLine) {
1445
+ sections.push("");
1446
+ sections.push(reactionsLine);
1447
+ }
1448
+ sections.push("");
1449
+ }
1450
+ return [
1451
+ "---",
1452
+ stringify(compactFrontmatter).trimEnd(),
1453
+ "---",
1454
+ "",
1455
+ ...sections,
1456
+ ""
1457
+ ].join("\n");
1458
+ }
1459
+ function formatReactionsLine(reactions) {
1460
+ const entries = getReactionEntries(reactions);
1461
+ if (entries.length === 0) return void 0;
1462
+ return `> ${entries.map((entry) => `\`${entry.emoji} ${entry.count}\``).join(" | ")}`;
1463
+ }
1464
+ function formatReactionsFrontmatter(reactions) {
1465
+ const entries = getReactionEntries(reactions);
1466
+ if (entries.length === 0) return void 0;
1467
+ return Object.fromEntries(entries.map((entry) => [entry.emoji, entry.count]));
1468
+ }
1469
+ function getReactionEntries(reactions) {
1470
+ const normalized = normalizeReactions(reactions);
1471
+ return REACTION_FIELDS.map(({ key, emoji }) => {
1472
+ const count = normalized[key];
1473
+ if (!count) return void 0;
1474
+ return {
1475
+ emoji,
1476
+ count
1477
+ };
1478
+ }).filter((entry) => Boolean(entry));
1479
+ }
1480
+ //#endregion
1481
+ //#region src/utils/string.ts
1482
+ function slugifyTitle(title, maxLength = 48) {
1483
+ const normalized = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1484
+ if (!normalized) return "item";
1485
+ return normalized.slice(0, maxLength).replace(/-+$/g, "") || "item";
1486
+ }
1487
+ //#endregion
1488
+ //#region src/sync/paths.ts
1489
+ const FILE_NUMBER_PAD_LENGTH = 5;
1490
+ const MAX_SLUG_LENGTH = 48;
1491
+ function getIssueMarkdownPath(storageDirAbsolute, number, state, title) {
1492
+ const fileName = getItemFileName(number, title);
1493
+ if (state === "closed") return join(storageDirAbsolute, ISSUE_DIR_NAME, CLOSED_DIR_NAME, fileName);
1494
+ return join(storageDirAbsolute, ISSUE_DIR_NAME, fileName);
1495
+ }
1496
+ function getPullMarkdownPath(storageDirAbsolute, number, state, title) {
1497
+ const fileName = getItemFileName(number, title);
1498
+ if (state === "closed") return join(storageDirAbsolute, PULL_DIR_NAME, CLOSED_DIR_NAME, fileName);
1499
+ return join(storageDirAbsolute, PULL_DIR_NAME, fileName);
1500
+ }
1501
+ function getItemMarkdownPath(storageDirAbsolute, kind, number, state, title) {
1502
+ if (kind === "pull") return getPullMarkdownPath(storageDirAbsolute, number, state, title);
1503
+ return getIssueMarkdownPath(storageDirAbsolute, number, state, title);
1504
+ }
1505
+ function getItemFileName(number, title) {
1506
+ return `${String(number).padStart(FILE_NUMBER_PAD_LENGTH, "0")}-${slugifyTitle(title, MAX_SLUG_LENGTH)}.md`;
1507
+ }
1508
+ function getPrPatchPath(storageDirAbsolute, number, title) {
1509
+ return join(storageDirAbsolute, PULL_DIR_NAME, getItemFileName(number, title).replace(/\.md$/, ".patch"));
1510
+ }
1511
+ //#endregion
1512
+ //#region src/sync/sync-repository-utils.ts
1513
+ function createCounters(scanned = 0, selected = 0) {
1514
+ return {
1515
+ scanned,
1516
+ selected,
1517
+ processed: 0,
1518
+ skipped: 0,
1519
+ written: 0,
1520
+ moved: 0,
1521
+ patchesWritten: 0,
1522
+ patchesDeleted: 0
1523
+ };
1524
+ }
1525
+ function addItemStats(counters, stats) {
1526
+ counters.skipped += stats.skipped;
1527
+ counters.written += stats.written;
1528
+ counters.moved += stats.moved;
1529
+ counters.patchesWritten += stats.patchesWritten;
1530
+ counters.patchesDeleted += stats.patchesDeleted;
1531
+ }
1532
+ function shouldSyncKind(sync, kind) {
1533
+ return kind === "issue" ? sync.issues : sync.pulls;
1534
+ }
1535
+ function shouldSyncIssue(sync, issue) {
1536
+ return shouldSyncKind(sync, issue.kind);
1537
+ }
1538
+ function resolvePatchPlan(patchesMode, kind, state) {
1539
+ if (kind !== "pull") return {
1540
+ shouldWritePatch: false,
1541
+ shouldDeletePatch: false
1542
+ };
1543
+ const shouldWritePatch = patchesMode === "all" || patchesMode === "open" && state === "open";
1544
+ return {
1545
+ shouldWritePatch,
1546
+ shouldDeletePatch: !shouldWritePatch
1547
+ };
1548
+ }
1549
+ function shouldSyncPrDetails(sync, kind, state) {
1550
+ if (kind === "issue") return true;
1551
+ return sync.patches === "all" || sync.patches === "open" && state === "open";
1552
+ }
1553
+ function relativeToStorage(storageDirAbsolute, absolutePath) {
1554
+ if (absolutePath.startsWith(storageDirAbsolute)) return absolutePath.slice(storageDirAbsolute.length + 1);
1555
+ return basename(absolutePath);
1556
+ }
1557
+ //#endregion
1558
+ //#region src/sync/sync-repository-storage.ts
1559
+ async function resolveIssuePaths(storageDirAbsolute, kind, number, title, state, trackedFilePath) {
1560
+ const closedPath = getItemMarkdownPath(storageDirAbsolute, kind, number, "closed", title);
1561
+ const openPath = getItemMarkdownPath(storageDirAbsolute, kind, number, "open", title);
1562
+ const hasClosedFile = await pathExists(closedPath);
1563
+ const hasOpenFile = await pathExists(openPath);
1564
+ const trackedPath = resolveTrackedPath(storageDirAbsolute, trackedFilePath);
1565
+ const hasTrackedFile = trackedPath ? await pathExists(trackedPath) : false;
1566
+ const targetPath = getItemMarkdownPath(storageDirAbsolute, kind, number, state, title);
1567
+ const hasTargetFile = state === "open" ? hasOpenFile : hasClosedFile;
1568
+ const matchedPaths = await findMatchedMarkdownPaths(storageDirAbsolute, kind, number, [
1569
+ openPath,
1570
+ closedPath,
1571
+ trackedPath
1572
+ ]);
1573
+ return {
1574
+ openPath,
1575
+ closedPath,
1576
+ targetPath,
1577
+ patchPath: getPrPatchPath(storageDirAbsolute, number, title),
1578
+ trackedPath,
1579
+ hasOpenFile,
1580
+ hasClosedFile,
1581
+ hasTrackedFile,
1582
+ matchedPaths,
1583
+ hasLocalFile: hasOpenFile || hasClosedFile || hasTrackedFile || matchedPaths.length > 0,
1584
+ hasTargetFile
1585
+ };
1586
+ }
1587
+ async function moveMarkdownByState(paths, state) {
1588
+ const sourcePath = resolveMoveSourcePath(paths, state);
1589
+ if (!sourcePath) return 0;
1590
+ if (sourcePath === paths.targetPath) return 0;
1591
+ if (await pathExists(paths.targetPath)) return 0;
1592
+ await movePath(sourcePath, paths.targetPath);
1593
+ return 1;
1594
+ }
1595
+ function resolveMoveSourcePath(paths, state) {
1596
+ if (paths.hasTrackedFile && paths.trackedPath && paths.trackedPath !== paths.targetPath) return paths.trackedPath;
1597
+ if (state === "open" && paths.hasClosedFile && paths.closedPath !== paths.targetPath) return paths.closedPath;
1598
+ if (state === "closed" && paths.hasOpenFile && paths.openPath !== paths.targetPath) return paths.openPath;
1599
+ if (paths.hasOpenFile && paths.openPath !== paths.targetPath) return paths.openPath;
1600
+ if (paths.hasClosedFile && paths.closedPath !== paths.targetPath) return paths.closedPath;
1601
+ return paths.matchedPaths.find((path) => path !== paths.targetPath);
1602
+ }
1603
+ function getExistingMarkdownPaths(paths) {
1604
+ const markdownPaths = /* @__PURE__ */ new Set();
1605
+ if (paths.hasOpenFile) markdownPaths.add(paths.openPath);
1606
+ if (paths.hasClosedFile) markdownPaths.add(paths.closedPath);
1607
+ if (paths.hasTrackedFile && paths.trackedPath) markdownPaths.add(paths.trackedPath);
1608
+ for (const matchedPath of paths.matchedPaths) markdownPaths.add(matchedPath);
1609
+ return [...markdownPaths];
1610
+ }
1611
+ function resolveTrackedPath(storageDirAbsolute, trackedFilePath) {
1612
+ if (!trackedFilePath) return void 0;
1613
+ if (isAbsolute(trackedFilePath)) return trackedFilePath;
1614
+ return join(storageDirAbsolute, trackedFilePath);
1615
+ }
1616
+ function resolveTrackedPathOrJoin(storageDirAbsolute, trackedFilePath) {
1617
+ return resolveTrackedPath(storageDirAbsolute, trackedFilePath) ?? join(storageDirAbsolute, trackedFilePath);
1618
+ }
1619
+ async function findMatchedMarkdownPaths(storageDirAbsolute, kind, number, knownPaths) {
1620
+ const matchedPaths = /* @__PURE__ */ new Set();
1621
+ const knownPathSet = new Set(knownPaths.filter(Boolean));
1622
+ const padded = String(number).padStart(5, "0");
1623
+ const kindDir = kind === "issue" ? ISSUE_DIR_NAME : PULL_DIR_NAME;
1624
+ for (const stateDir of ["", CLOSED_DIR_NAME]) {
1625
+ const dir = stateDir ? join(storageDirAbsolute, kindDir, stateDir) : join(storageDirAbsolute, kindDir);
1626
+ let files;
1627
+ try {
1628
+ files = await readdir(dir);
1629
+ } catch {
1630
+ continue;
1631
+ }
1632
+ for (const fileName of files) {
1633
+ if (!fileName.startsWith(`${padded}-`) || !fileName.endsWith(".md")) continue;
1634
+ const fullPath = join(dir, fileName);
1635
+ if (!knownPathSet.has(fullPath)) matchedPaths.add(fullPath);
1636
+ }
1637
+ }
1638
+ return [...matchedPaths];
1639
+ }
1640
+ function updateTrackedItem(context, number, kind, state, issueUpdatedAt, markdownPath, patchPath, data) {
1641
+ context.syncState.items[String(number)] = {
1642
+ number,
1643
+ kind,
1644
+ state,
1645
+ lastUpdatedAt: issueUpdatedAt,
1646
+ lastSyncedAt: context.syncedAt,
1647
+ filePath: relativeToStorage(context.storageDirAbsolute, markdownPath),
1648
+ patchPath: patchPath ? relativeToStorage(context.storageDirAbsolute, patchPath) : void 0,
1649
+ data
1650
+ };
1651
+ }
1652
+ async function pruneTrackedClosedItems(storageDirAbsolute, syncState, sync) {
1653
+ if (sync.issues) await rm(join(storageDirAbsolute, ISSUE_DIR_NAME, CLOSED_DIR_NAME), {
1654
+ recursive: true,
1655
+ force: true
1656
+ });
1657
+ if (sync.pulls) await rm(join(storageDirAbsolute, PULL_DIR_NAME, CLOSED_DIR_NAME), {
1658
+ recursive: true,
1659
+ force: true
1660
+ });
1661
+ let patchesDeleted = 0;
1662
+ for (const item of Object.values(syncState.items)) {
1663
+ if (item.state !== "closed") continue;
1664
+ if (!shouldSyncKind(sync, item.kind)) continue;
1665
+ await removePath(resolveTrackedPathOrJoin(storageDirAbsolute, item.filePath));
1666
+ if (item.kind === "pull") patchesDeleted += await removePatchIfExists(storageDirAbsolute, item.number);
1667
+ delete syncState.items[String(item.number)];
1668
+ }
1669
+ return patchesDeleted;
1670
+ }
1671
+ async function pruneMissingOpenTrackedItems(storageDirAbsolute, syncState, openNumbers, sync) {
1672
+ let patchesDeleted = 0;
1673
+ for (const item of Object.values(syncState.items)) {
1674
+ if (item.state !== "open") continue;
1675
+ if (!shouldSyncKind(sync, item.kind)) continue;
1676
+ if (openNumbers.has(item.number)) continue;
1677
+ await removePath(resolveTrackedPathOrJoin(storageDirAbsolute, item.filePath));
1678
+ if (item.kind === "pull") patchesDeleted += await removePatchIfExists(storageDirAbsolute, item.number);
1679
+ delete syncState.items[String(item.number)];
1680
+ }
1681
+ return patchesDeleted;
1682
+ }
1683
+ //#endregion
1684
+ //#region src/sync/sync-repository-item.ts
1685
+ async function prepareIssueCandidateSync(context, issue) {
1686
+ const number = issue.number;
1687
+ const kind = issue.kind;
1688
+ const state = issue.state;
1689
+ const tracked = context.syncState.items[String(number)];
1690
+ const paths = await resolveIssuePaths(context.storageDirAbsolute, kind, number, issue.title, state, tracked?.filePath);
1691
+ const patchPlan = resolvePatchPlan(context.config.sync.patches, kind, state);
1692
+ if (state === "closed" && context.config.sync.closed === false) {
1693
+ delete context.syncState.items[String(number)];
1694
+ return {
1695
+ number,
1696
+ kind,
1697
+ state,
1698
+ action: "remove",
1699
+ paths,
1700
+ patchPlan
1701
+ };
1702
+ }
1703
+ if (state === "closed" && context.config.sync.closed === true && !paths.hasLocalFile) {
1704
+ delete context.syncState.items[String(number)];
1705
+ return {
1706
+ number,
1707
+ kind,
1708
+ state,
1709
+ action: "remove",
1710
+ paths,
1711
+ patchPlan
1712
+ };
1713
+ }
1714
+ const needsDetails = shouldSyncPrDetails(context.config.sync, kind, state);
1715
+ const hasCanonicalData = Boolean(tracked?.data && (kind !== "pull" || tracked.data.pull) && (!needsDetails || tracked.data.timeline && (kind !== "pull" || tracked.data.commits)));
1716
+ const shouldRefetch = !tracked || tracked.lastUpdatedAt !== issue.updatedAt || !hasCanonicalData;
1717
+ const data = shouldRefetch ? await fetchCanonicalData(context, issue) : tracked.data;
1718
+ updateTrackedItem(context, number, kind, state, issue.updatedAt, paths.targetPath, patchPlan.shouldWritePatch ? paths.patchPath : void 0, data);
1719
+ return {
1720
+ number,
1721
+ kind,
1722
+ state,
1723
+ action: resolveSyncAction(shouldRefetch, paths, state),
1724
+ paths,
1725
+ patchPlan
1726
+ };
1727
+ }
1728
+ async function materializePreparedIssue(context, candidate) {
1729
+ const { number, kind, state, action, patchPlan, paths } = candidate;
1730
+ if (action === "remove") {
1731
+ for (const markdownPath of getExistingMarkdownPaths(paths)) await removePath(markdownPath);
1732
+ let patchesDeleted = 0;
1733
+ if (kind === "pull") patchesDeleted += await removePatchIfExists(context.storageDirAbsolute, number);
1734
+ return {
1735
+ kind,
1736
+ action,
1737
+ skipped: 0,
1738
+ written: 0,
1739
+ moved: 0,
1740
+ patchesWritten: 0,
1741
+ patchesDeleted
1742
+ };
1743
+ }
1744
+ if (action === "skip") {
1745
+ let patchesDeleted = 0;
1746
+ if (patchPlan.shouldDeletePatch) patchesDeleted += await removePatchIfExists(context.storageDirAbsolute, number);
1747
+ return {
1748
+ kind,
1749
+ action,
1750
+ skipped: 1,
1751
+ written: 0,
1752
+ moved: 0,
1753
+ patchesWritten: 0,
1754
+ patchesDeleted
1755
+ };
1756
+ }
1757
+ const tracked = context.syncState.items[String(number)];
1758
+ if (!tracked) throw new CodedError(log.GHFS0401({ issue: formatIssueNumber(number, {
1759
+ repo: context.repoSlug,
1760
+ kind
1761
+ }) }));
1762
+ const markdown = buildTrackedMarkdown(context, tracked);
1763
+ const moved = await moveMarkdownByState(paths, state);
1764
+ await writeFileEnsured(paths.targetPath, markdown);
1765
+ const patchStats = await syncPatchByPlan(context, number, paths.patchPath, patchPlan);
1766
+ return {
1767
+ kind,
1768
+ action,
1769
+ skipped: 0,
1770
+ written: 1,
1771
+ moved,
1772
+ patchesWritten: patchStats.patchesWritten,
1773
+ patchesDeleted: patchStats.patchesDeleted
1774
+ };
1775
+ }
1776
+ async function rematerializeTrackedMarkdown(context) {
1777
+ let processed = 0;
1778
+ let written = 0;
1779
+ let moved = 0;
1780
+ for (const tracked of Object.values(context.syncState.items)) {
1781
+ const paths = await resolveIssuePaths(context.storageDirAbsolute, tracked.kind, tracked.number, tracked.data.item.title, tracked.state, tracked.filePath);
1782
+ moved += await moveMarkdownByState(paths, tracked.state);
1783
+ await writeFileEnsured(paths.targetPath, buildTrackedMarkdown(context, tracked));
1784
+ tracked.filePath = relativeToStorage(context.storageDirAbsolute, paths.targetPath);
1785
+ tracked.lastSyncedAt = context.syncedAt;
1786
+ processed += 1;
1787
+ written += 1;
1788
+ }
1789
+ return {
1790
+ processed,
1791
+ written,
1792
+ moved
1793
+ };
1794
+ }
1795
+ async function reconcileMarkdownFilesByScan(context) {
1796
+ let written = 0;
1797
+ let moved = 0;
1798
+ const expectedPaths = /* @__PURE__ */ new Set();
1799
+ for (const tracked of Object.values(context.syncState.items)) {
1800
+ const paths = await resolveIssuePaths(context.storageDirAbsolute, tracked.kind, tracked.number, tracked.data.item.title, tracked.state, tracked.filePath);
1801
+ expectedPaths.add(paths.targetPath);
1802
+ const movedByState = !paths.hasTargetFile ? await moveMarkdownByState(paths, tracked.state) : 0;
1803
+ let changed = movedByState > 0;
1804
+ if (!await pathExists(paths.targetPath)) {
1805
+ await writeFileEnsured(paths.targetPath, buildTrackedMarkdown(context, tracked));
1806
+ written += 1;
1807
+ changed = true;
1808
+ }
1809
+ tracked.filePath = relativeToStorage(context.storageDirAbsolute, paths.targetPath);
1810
+ if (changed) tracked.lastSyncedAt = context.syncedAt;
1811
+ moved += movedByState;
1812
+ }
1813
+ moved += await moveExtraMarkdownFilesToClosed(context.storageDirAbsolute, expectedPaths);
1814
+ return {
1815
+ written,
1816
+ moved
1817
+ };
1818
+ }
1819
+ function resolveSyncAction(shouldRefetch, paths, state) {
1820
+ if (shouldRefetch) return "refetch";
1821
+ if (paths.hasTargetFile) return "skip";
1822
+ if (resolveMoveSourcePath(paths, state)) return "move";
1823
+ return "create";
1824
+ }
1825
+ async function fetchCanonicalData(context, issue) {
1826
+ const includeDetails = shouldSyncPrDetails(context.config.sync, issue.kind, issue.state);
1827
+ const [comments, pull, commits, timeline] = await Promise.all([
1828
+ context.provider.fetchComments(issue.number),
1829
+ issue.kind === "pull" ? context.provider.fetchPullMetadata(issue.number) : Promise.resolve(void 0),
1830
+ issue.kind === "pull" && includeDetails ? context.provider.fetchPullCommits(issue.number) : Promise.resolve(void 0),
1831
+ includeDetails ? context.provider.fetchTimeline(issue.number) : Promise.resolve(void 0)
1832
+ ]);
1833
+ return {
1834
+ item: issue,
1835
+ comments,
1836
+ pull,
1837
+ commits,
1838
+ timeline
1839
+ };
1840
+ }
1841
+ async function syncPatchByPlan(context, number, patchPath, patchPlan) {
1842
+ let patchesWritten = 0;
1843
+ let patchesDeleted = 0;
1844
+ if (patchPlan.shouldWritePatch) {
1845
+ const patch = await context.provider.fetchPullPatch(number);
1846
+ await removePatchIfExists(context.storageDirAbsolute, number);
1847
+ await writeFileEnsured(patchPath, patch);
1848
+ patchesWritten += 1;
1849
+ }
1850
+ if (patchPlan.shouldDeletePatch) patchesDeleted += await removePatchIfExists(context.storageDirAbsolute, number);
1851
+ return {
1852
+ patchesWritten,
1853
+ patchesDeleted
1854
+ };
1855
+ }
1856
+ function buildTrackedMarkdown(context, tracked) {
1857
+ return renderIssueMarkdown({
1858
+ repo: context.repoSlug,
1859
+ number: tracked.data.item.number,
1860
+ kind: tracked.data.item.kind,
1861
+ url: tracked.data.item.url,
1862
+ state: tracked.data.item.state,
1863
+ title: tracked.data.item.title,
1864
+ body: tracked.data.item.body ?? "",
1865
+ author: tracked.data.item.author ?? "unknown",
1866
+ labels: tracked.data.item.labels,
1867
+ assignees: tracked.data.item.assignees,
1868
+ milestone: tracked.data.item.milestone,
1869
+ createdAt: tracked.data.item.createdAt,
1870
+ updatedAt: tracked.data.item.updatedAt,
1871
+ closedAt: tracked.data.item.closedAt,
1872
+ lastSyncedAt: context.syncedAt,
1873
+ reactions: normalizeReactions(tracked.data.item.reactions),
1874
+ comments: tracked.data.comments.map((comment) => ({
1875
+ id: comment.id,
1876
+ author: comment.author ?? "unknown",
1877
+ body: comment.body ?? "",
1878
+ createdAt: comment.createdAt,
1879
+ updatedAt: comment.updatedAt,
1880
+ reactions: normalizeReactions(comment.reactions)
1881
+ })),
1882
+ pr: tracked.data.pull
1883
+ });
1884
+ }
1885
+ async function moveExtraMarkdownFilesToClosed(storageDirAbsolute, expectedPaths) {
1886
+ let moved = 0;
1887
+ moved += await moveOpenMarkdownFilesToClosed(join(storageDirAbsolute, ISSUE_DIR_NAME), expectedPaths);
1888
+ moved += await moveOpenMarkdownFilesToClosed(join(storageDirAbsolute, PULL_DIR_NAME), expectedPaths);
1889
+ return moved;
1890
+ }
1891
+ async function moveOpenMarkdownFilesToClosed(kindDirAbsolute, expectedPaths) {
1892
+ let moved = 0;
1893
+ const openFiles = await listOpenMarkdownFiles(kindDirAbsolute);
1894
+ const closedDirAbsolute = join(kindDirAbsolute, CLOSED_DIR_NAME);
1895
+ for (const markdownPath of openFiles) {
1896
+ if (expectedPaths.has(markdownPath)) continue;
1897
+ await movePath(markdownPath, await resolveUniqueClosedTarget(closedDirAbsolute, basename(markdownPath)));
1898
+ moved += 1;
1899
+ }
1900
+ return moved;
1901
+ }
1902
+ async function listOpenMarkdownFiles(kindDirAbsolute) {
1903
+ try {
1904
+ return (await readdir(kindDirAbsolute, {
1905
+ withFileTypes: true,
1906
+ encoding: "utf8"
1907
+ })).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => join(kindDirAbsolute, entry.name));
1908
+ } catch {
1909
+ return [];
1910
+ }
1911
+ }
1912
+ async function resolveUniqueClosedTarget(closedDirAbsolute, fileName) {
1913
+ const baseName = fileName.replace(/\.md$/i, "");
1914
+ let candidate = join(closedDirAbsolute, fileName);
1915
+ let index = 1;
1916
+ while (await pathExists(candidate)) {
1917
+ candidate = join(closedDirAbsolute, `${baseName}-extra-${index}.md`);
1918
+ index += 1;
1919
+ }
1920
+ return candidate;
1921
+ }
1922
+ //#endregion
1923
+ //#region src/sync/sync-repository-provider.ts
1924
+ async function fetchIssueCandidatesByPagination(context, since) {
1925
+ const issues = [];
1926
+ let scanned = 0;
1927
+ const allOpenNumbers = context.config.sync.closed === false && !since ? /* @__PURE__ */ new Set() : void 0;
1928
+ const state = context.config.sync.closed === false ? "open" : "all";
1929
+ for await (const page of context.provider.paginateItems({
1930
+ state,
1931
+ since
1932
+ })) for (const issue of page) {
1933
+ if (!shouldSyncIssue(context.config.sync, issue)) continue;
1934
+ scanned += 1;
1935
+ issues.push(issue);
1936
+ if (state === "open" && allOpenNumbers) allOpenNumbers.add(issue.number);
1937
+ }
1938
+ return {
1939
+ issues,
1940
+ scanned,
1941
+ allOpenNumbers
1942
+ };
1943
+ }
1944
+ async function fetchIssueCandidatesByNumbers(context, numbers) {
1945
+ const issues = (await context.provider.fetchItemsByNumbers(numbers)).filter((issue) => shouldSyncIssue(context.config.sync, issue));
1946
+ return {
1947
+ issues,
1948
+ scanned: issues.length
1949
+ };
1950
+ }
1951
+ //#endregion
1952
+ //#region src/utils/markdown.ts
1953
+ function getTimestamp(value) {
1954
+ const timestamp = Date.parse(value);
1955
+ if (Number.isFinite(timestamp)) return timestamp;
1956
+ return Number.NEGATIVE_INFINITY;
1957
+ }
1958
+ function renderRowsTable(rows) {
1959
+ const lines = ["| Number | Title | Labels | Updated | File |", "| --- | --- | --- | --- | --- |"];
1960
+ if (rows.length === 0) {
1961
+ lines.push("| - | - | - | - | - |");
1962
+ return lines;
1963
+ }
1964
+ for (const row of rows) {
1965
+ const labels = row.labels.length ? row.labels.map((label) => `\`${escapeInlineCode(label)}\``).join(", ") : "-";
1966
+ lines.push(`| #${row.number} | ${escapeTableCell(row.title)} | ${labels} | ${escapeTableCell(row.updatedAt)} | [${row.filePath}](${row.filePath}) |`);
1967
+ }
1968
+ return lines;
1969
+ }
1970
+ function escapeTableCell(value) {
1971
+ return value.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").trim() || "-";
1972
+ }
1973
+ function escapeInlineCode(value) {
1974
+ return value.replace(/`/g, "\\`");
1975
+ }
1976
+ //#endregion
1977
+ //#region src/sync/sync-repository-snapshot.ts
1978
+ async function writeRepoSnapshot(context) {
1979
+ const repoSnapshot = await buildRepoSnapshot(context);
1980
+ await mkdir(context.storageDirAbsolute, { recursive: true });
1981
+ await writeFile(join(context.storageDirAbsolute, REPO_SNAPSHOT_FILE_NAME), `${JSON.stringify(repoSnapshot, null, 2)}\n`, "utf8");
1982
+ }
1983
+ async function writeRepositoryIndexes(context) {
1984
+ const [issuesMarkdown, pullsMarkdown] = await Promise.all([renderIndexMarkdown(context, "issue"), renderIndexMarkdown(context, "pull")]);
1985
+ await mkdir(context.storageDirAbsolute, { recursive: true });
1986
+ await Promise.all([writeFile(join(context.storageDirAbsolute, ISSUES_INDEX_FILE_NAME), issuesMarkdown, "utf8"), writeFile(join(context.storageDirAbsolute, PULLS_INDEX_FILE_NAME), pullsMarkdown, "utf8")]);
1987
+ }
1988
+ async function renderIndexMarkdown(context, kind) {
1989
+ const rows = Object.values(context.syncState.items).filter((item) => item.kind === kind).map((item) => readIndexRow(item));
1990
+ const openRows = sortRows(rows.filter((row) => row.state === "open"));
1991
+ const closedRows = sortRows(rows.filter((row) => row.state === "closed"));
1992
+ return [
1993
+ `# ${kind === "issue" ? "Issues" : "Pull Requests"}`,
1994
+ "",
1995
+ `- repo: ${context.repoSlug}`,
1996
+ `- synced_at: ${context.syncedAt}`,
1997
+ `- total: ${rows.length}`,
1998
+ `- open: ${openRows.length}`,
1999
+ `- closed: ${closedRows.length}`,
2000
+ "",
2001
+ `## Open (${openRows.length})`,
2002
+ "",
2003
+ ...renderRowsTable(openRows),
2004
+ "",
2005
+ `## Closed (${closedRows.length})`,
2006
+ "",
2007
+ ...renderRowsTable(closedRows),
2008
+ ""
2009
+ ].join("\n");
2010
+ }
2011
+ function readIndexRow(item) {
2012
+ return {
2013
+ number: item.number,
2014
+ state: item.state,
2015
+ title: item.data.item.title,
2016
+ labels: item.data.item.labels,
2017
+ updatedAt: item.data.item.updatedAt,
2018
+ filePath: item.filePath
2019
+ };
2020
+ }
2021
+ function sortRows(rows) {
2022
+ return [...rows].sort((left, right) => {
2023
+ const updatedDiff = getTimestamp(right.updatedAt) - getTimestamp(left.updatedAt);
2024
+ if (updatedDiff !== 0) return updatedDiff;
2025
+ return right.number - left.number;
2026
+ });
2027
+ }
2028
+ async function buildRepoSnapshot(context) {
2029
+ const [repoResult, labelsResult, milestonesResult] = await Promise.all([
2030
+ context.provider.fetchRepository(),
2031
+ context.provider.fetchRepositoryLabels(),
2032
+ context.provider.fetchRepositoryMilestones()
2033
+ ]);
2034
+ const repository = repoResult;
2035
+ const labels = labelsResult.map((label) => ({
2036
+ name: label.name,
2037
+ color: label.color,
2038
+ description: label.description ?? null,
2039
+ default: Boolean(label.default)
2040
+ })).sort((left, right) => left.name.localeCompare(right.name));
2041
+ const milestones = milestonesResult.map((milestone) => ({
2042
+ number: milestone.number,
2043
+ title: milestone.title,
2044
+ state: milestone.state,
2045
+ description: milestone.description ?? null,
2046
+ due_on: milestone.due_on,
2047
+ open_issues: milestone.open_issues,
2048
+ closed_issues: milestone.closed_issues,
2049
+ created_at: milestone.created_at,
2050
+ updated_at: milestone.updated_at,
2051
+ closed_at: milestone.closed_at
2052
+ })).sort((left, right) => left.number - right.number);
2053
+ return {
2054
+ repo: context.repoSlug,
2055
+ synced_at: context.syncedAt,
2056
+ repository: {
2057
+ owner: repository.owner.login,
2058
+ name: repository.name,
2059
+ full_name: repository.full_name,
2060
+ description: repository.description ?? null,
2061
+ private: repository.private,
2062
+ archived: repository.archived,
2063
+ default_branch: repository.default_branch,
2064
+ html_url: repository.html_url,
2065
+ fork: repository.fork,
2066
+ open_issues_count: repository.open_issues_count,
2067
+ has_issues: repository.has_issues,
2068
+ has_projects: repository.has_projects,
2069
+ has_wiki: repository.has_wiki,
2070
+ created_at: repository.created_at,
2071
+ updated_at: repository.updated_at,
2072
+ pushed_at: repository.pushed_at
2073
+ },
2074
+ labels,
2075
+ milestones
2076
+ };
2077
+ }
2078
+ //#endregion
2079
+ //#region src/sync/sync-repository.ts
2080
+ async function syncRepository(options) {
2081
+ const reporter = options.reporter;
2082
+ const startedAt = /* @__PURE__ */ new Date();
2083
+ const startedAtIso = startedAt.toISOString();
2084
+ const runId = createSyncRunId();
2085
+ const provider = options.provider ?? createRepositoryProvider({
2086
+ token: options.token,
2087
+ repo: options.repo
2088
+ });
2089
+ const storageDirAbsolute = resolve(options.config.cwd, options.config.directory);
2090
+ const targetNumbers = normalizeIssueNumbers(options.numbers);
2091
+ const counters = createCounters();
2092
+ const stageDurations = createStageDurations();
2093
+ let errorReported = false;
2094
+ reporter?.onStart?.({
2095
+ repo: options.repo,
2096
+ startedAt: startedAtIso,
2097
+ numbersCount: targetNumbers?.length,
2098
+ snapshot: cloneSnapshot(counters)
2099
+ });
2100
+ let context;
2101
+ let since;
2102
+ let repoUpdatedAt;
2103
+ let candidates = {
2104
+ issues: [],
2105
+ scanned: 0
2106
+ };
2107
+ const preparedCandidates = [];
2108
+ let updatedIssues = 0;
2109
+ let updatedPulls = 0;
2110
+ let ghfsVersionMismatch = false;
2111
+ let previousGhfsVersion;
2112
+ const runStage = async (stage, message, fn) => {
2113
+ reporter?.onStageStart?.({
2114
+ stage,
2115
+ message,
2116
+ snapshot: cloneSnapshot(counters)
2117
+ });
2118
+ const stageStartedAt = Date.now();
2119
+ try {
2120
+ const result = await fn();
2121
+ const durationMs = Date.now() - stageStartedAt;
2122
+ stageDurations[stage] = durationMs;
2123
+ reporter?.onStageEnd?.({
2124
+ stage,
2125
+ message,
2126
+ durationMs,
2127
+ snapshot: cloneSnapshot(counters)
2128
+ });
2129
+ return result;
2130
+ } catch (error) {
2131
+ errorReported = true;
2132
+ stageDurations[stage] = Date.now() - stageStartedAt;
2133
+ reporter?.onError?.({
2134
+ stage,
2135
+ error,
2136
+ snapshot: cloneSnapshot(counters)
2137
+ });
2138
+ throw error;
2139
+ }
2140
+ };
2141
+ try {
2142
+ let shouldEarlyReturn = false;
2143
+ await runStage("metadata", "Fetch repository metadata", async () => {
2144
+ const syncState = await loadSyncState(storageDirAbsolute);
2145
+ since = targetNumbers ? void 0 : resolveSince(options, syncState);
2146
+ const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
2147
+ context = {
2148
+ provider,
2149
+ repoSlug: options.repo,
2150
+ storageDirAbsolute,
2151
+ config: options.config,
2152
+ syncState,
2153
+ syncedAt,
2154
+ totalIssues: 0,
2155
+ totalPulls: 0
2156
+ };
2157
+ previousGhfsVersion = syncState.ghfsVersion;
2158
+ ghfsVersionMismatch = syncState.ghfsVersion !== GHFS_VERSION;
2159
+ if (targetNumbers) return;
2160
+ repoUpdatedAt = (await provider.fetchRepository()).updated_at;
2161
+ if (!options.full && syncState.lastRepoUpdatedAt && syncState.lastRepoUpdatedAt === repoUpdatedAt) shouldEarlyReturn = true;
2162
+ reporter?.onStageUpdate?.({
2163
+ stage: "metadata",
2164
+ snapshot: cloneSnapshot(counters),
2165
+ message: `since=${since ?? "(full)"} repoUpdatedAt=${repoUpdatedAt}`
2166
+ });
2167
+ });
2168
+ assertContext(context);
2169
+ const syncContext = context;
2170
+ if (!shouldEarlyReturn) {
2171
+ await runStage("pagination", "Pagination", async () => {
2172
+ const paginatedSince = options.full ? void 0 : since;
2173
+ candidates = targetNumbers ? await fetchIssueCandidatesByNumbers(syncContext, targetNumbers) : await fetchIssueCandidatesByPagination(syncContext, paginatedSince);
2174
+ counters.scanned = candidates.scanned;
2175
+ counters.selected = candidates.issues.length;
2176
+ reporter?.onStageUpdate?.({
2177
+ stage: "pagination",
2178
+ snapshot: cloneSnapshot(counters),
2179
+ message: `scanned=${counters.scanned} selected=${counters.selected}`
2180
+ });
2181
+ });
2182
+ await runStage("fetch", "Fetch updated issues/PRs", async () => {
2183
+ for (const issue of candidates.issues) {
2184
+ const prepared = await prepareIssueCandidateSync(syncContext, issue);
2185
+ preparedCandidates.push(prepared);
2186
+ counters.processed += 1;
2187
+ if (prepared.action === "refetch" || prepared.action === "create") if (prepared.kind === "issue") updatedIssues += 1;
2188
+ else updatedPulls += 1;
2189
+ reporter?.onStageUpdate?.({
2190
+ stage: "fetch",
2191
+ snapshot: cloneSnapshot(counters),
2192
+ message: `${formatIssueNumber(issue.number, {
2193
+ repo: options.repo,
2194
+ kind: issue.kind
2195
+ })} ${prepared.kind} ${prepared.action}`
2196
+ });
2197
+ }
2198
+ });
2199
+ syncContext.syncState.repo = options.repo;
2200
+ if (!targetNumbers) {
2201
+ syncContext.syncState.lastSyncedAt = syncContext.syncedAt;
2202
+ syncContext.syncState.lastSince = since;
2203
+ syncContext.syncState.lastRepoUpdatedAt = repoUpdatedAt;
2204
+ }
2205
+ await saveSyncState(syncContext.storageDirAbsolute, syncContext.syncState);
2206
+ await runStage("materialize", "Materialize local files", async () => {
2207
+ for (const prepared of preparedCandidates) addItemStats(counters, await materializePreparedIssue(syncContext, prepared));
2208
+ });
2209
+ await runStage("prune", "Prune stale local artifacts", async () => {
2210
+ if (options.config.sync.closed === false) counters.patchesDeleted += await pruneTrackedClosedItems(syncContext.storageDirAbsolute, syncContext.syncState, options.config.sync);
2211
+ if (!targetNumbers && options.config.sync.closed === false && candidates.allOpenNumbers) counters.patchesDeleted += await pruneMissingOpenTrackedItems(syncContext.storageDirAbsolute, syncContext.syncState, candidates.allOpenNumbers, options.config.sync);
2212
+ reporter?.onStageUpdate?.({
2213
+ stage: "prune",
2214
+ snapshot: cloneSnapshot(counters),
2215
+ message: `patchesDeleted=${counters.patchesDeleted}`
2216
+ });
2217
+ });
2218
+ }
2219
+ await runStage("save", "Save sync state", async () => {
2220
+ if (ghfsVersionMismatch) {
2221
+ const rematerialized = await rematerializeTrackedMarkdown(syncContext);
2222
+ reporter?.onStageUpdate?.({
2223
+ stage: "save",
2224
+ snapshot: cloneSnapshot(counters),
2225
+ message: `regenerated=${rematerialized.written} version=${previousGhfsVersion ?? "(none)"}->${GHFS_VERSION}`
2226
+ });
2227
+ }
2228
+ const scanStats = await reconcileMarkdownFilesByScan(syncContext);
2229
+ counters.written += scanStats.written;
2230
+ counters.moved += scanStats.moved;
2231
+ reporter?.onStageUpdate?.({
2232
+ stage: "save",
2233
+ snapshot: cloneSnapshot(counters),
2234
+ message: `scan-fixed written=${scanStats.written} moved=${scanStats.moved}`
2235
+ });
2236
+ if (!shouldEarlyReturn) await writeRepoSnapshot(syncContext);
2237
+ if (!shouldEarlyReturn || ghfsVersionMismatch) await writeRepositoryIndexes(syncContext);
2238
+ syncContext.syncState.ghfsVersion = GHFS_VERSION;
2239
+ await saveSyncState(syncContext.storageDirAbsolute, syncContext.syncState);
2240
+ });
2241
+ const totals = computeTotals(syncContext.syncState.items);
2242
+ syncContext.totalIssues = totals.totalIssues;
2243
+ syncContext.totalPulls = totals.totalPulls;
2244
+ const finishedAt = /* @__PURE__ */ new Date();
2245
+ const durationMs = Math.max(0, finishedAt.getTime() - startedAt.getTime());
2246
+ const requestCount = provider.getRequestCount();
2247
+ const summary = {
2248
+ repo: options.repo,
2249
+ since,
2250
+ syncedAt: syncContext.syncedAt,
2251
+ totalIssues: totals.totalIssues,
2252
+ totalPulls: totals.totalPulls,
2253
+ updatedIssues,
2254
+ updatedPulls,
2255
+ trackedItems: totals.trackedItems,
2256
+ requestCount,
2257
+ scanned: counters.scanned,
2258
+ selected: counters.selected,
2259
+ processed: counters.processed,
2260
+ skipped: counters.skipped,
2261
+ written: counters.written,
2262
+ moved: counters.moved,
2263
+ patchesWritten: counters.patchesWritten,
2264
+ patchesDeleted: counters.patchesDeleted,
2265
+ durationMs
2266
+ };
2267
+ syncContext.syncState.lastSyncRun = {
2268
+ runId,
2269
+ repo: options.repo,
2270
+ startedAt: startedAtIso,
2271
+ finishedAt: finishedAt.toISOString(),
2272
+ durationMs,
2273
+ requestCount,
2274
+ since,
2275
+ numbersCount: targetNumbers?.length,
2276
+ counters: cloneSnapshot(counters),
2277
+ stages: { ...stageDurations }
2278
+ };
2279
+ await saveSyncState(syncContext.storageDirAbsolute, syncContext.syncState);
2280
+ reporter?.onComplete?.({
2281
+ summary,
2282
+ stages: { ...stageDurations }
2283
+ });
2284
+ return summary;
2285
+ } catch (error) {
2286
+ if (!errorReported) reporter?.onError?.({
2287
+ error,
2288
+ snapshot: cloneSnapshot(counters)
2289
+ });
2290
+ throw error;
2291
+ }
2292
+ }
2293
+ function assertContext(context) {
2294
+ if (!context) throw new CodedError(log.GHFS0400());
2295
+ }
2296
+ function createSyncRunId() {
2297
+ return `sync_${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "")}_${randomBytes(3).toString("hex")}`;
2298
+ }
2299
+ function createStageDurations() {
2300
+ return {
2301
+ metadata: 0,
2302
+ pagination: 0,
2303
+ fetch: 0,
2304
+ materialize: 0,
2305
+ prune: 0,
2306
+ save: 0
2307
+ };
2308
+ }
2309
+ function cloneSnapshot(counters) {
2310
+ return {
2311
+ scanned: counters.scanned,
2312
+ selected: counters.selected,
2313
+ processed: counters.processed,
2314
+ skipped: counters.skipped,
2315
+ written: counters.written,
2316
+ moved: counters.moved,
2317
+ patchesWritten: counters.patchesWritten,
2318
+ patchesDeleted: counters.patchesDeleted
2319
+ };
2320
+ }
2321
+ function computeTotals(items) {
2322
+ let totalIssues = 0;
2323
+ let totalPulls = 0;
2324
+ for (const item of Object.values(items)) if (item.kind === "issue") totalIssues += 1;
2325
+ else totalPulls += 1;
2326
+ return {
2327
+ totalIssues,
2328
+ totalPulls,
2329
+ trackedItems: totalIssues + totalPulls
2330
+ };
2331
+ }
2332
+ //#endregion
2333
+ //#region src/server/poller.ts
2334
+ function createRemotePoller(options) {
2335
+ const intervalMs = options.intervalMs ?? 6e4;
2336
+ let current = options.initial ?? {
2337
+ downCount: 0,
2338
+ checkedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
2339
+ stale: true,
2340
+ message: "Not checked yet"
2341
+ };
2342
+ let stopped = false;
2343
+ let timer;
2344
+ let failures = 0;
2345
+ let inFlight;
2346
+ function setStatus(next) {
2347
+ current = next;
2348
+ options.onUpdate(current);
2349
+ return current;
2350
+ }
2351
+ async function doCheck() {
2352
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
2353
+ const since = options.getSince();
2354
+ if (!since) return setStatus({
2355
+ downCount: 0,
2356
+ checkedAt,
2357
+ stale: true,
2358
+ message: "Not synced yet"
2359
+ });
2360
+ let provider;
2361
+ try {
2362
+ provider = await options.getProvider();
2363
+ } catch (error) {
2364
+ return setStatus({
2365
+ downCount: current.downCount,
2366
+ checkedAt,
2367
+ stale: true,
2368
+ message: error.message
2369
+ });
2370
+ }
2371
+ if (!provider) return setStatus({
2372
+ downCount: 0,
2373
+ checkedAt,
2374
+ stale: true,
2375
+ message: "Missing GitHub token"
2376
+ });
2377
+ try {
2378
+ const counts = await provider.countUpdatedSince(since);
2379
+ failures = 0;
2380
+ return setStatus({
2381
+ downCount: counts.issues + counts.pulls,
2382
+ checkedAt,
2383
+ stale: false
2384
+ });
2385
+ } catch (error) {
2386
+ failures += 1;
2387
+ return setStatus({
2388
+ downCount: current.downCount,
2389
+ checkedAt,
2390
+ stale: true,
2391
+ message: error.message
2392
+ });
2393
+ }
2394
+ }
2395
+ async function check() {
2396
+ if (inFlight) return inFlight;
2397
+ inFlight = doCheck();
2398
+ try {
2399
+ return await inFlight;
2400
+ } finally {
2401
+ inFlight = void 0;
2402
+ schedule();
2403
+ }
2404
+ }
2405
+ function schedule() {
2406
+ if (stopped) return;
2407
+ if (timer) clearTimeout(timer);
2408
+ const backoff = failures === 0 ? intervalMs : Math.min(intervalMs * 2 ** Math.min(failures, 4), intervalMs * 16);
2409
+ timer = setTimeout(() => {
2410
+ check().catch(() => {});
2411
+ }, backoff);
2412
+ }
2413
+ timer = setTimeout(() => {
2414
+ check().catch(() => {});
2415
+ }, 2e3);
2416
+ return {
2417
+ getCurrent: () => current,
2418
+ checkNow: check,
2419
+ close: () => {
2420
+ stopped = true;
2421
+ if (timer) clearTimeout(timer);
2422
+ }
2423
+ };
2424
+ }
2425
+ //#endregion
2426
+ //#region src/server/portless.ts
2427
+ const execFileAsync = promisify(execFile);
2428
+ function slugifyRepoName(repo) {
2429
+ return (repo.includes("/") ? repo.slice(repo.lastIndexOf("/") + 1) : repo).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "app";
2430
+ }
2431
+ function resolvePortlessCliPath() {
2432
+ try {
2433
+ return resolve(dirname(fileURLToPath(import.meta.resolve("portless"))), "cli.js");
2434
+ } catch {
2435
+ return;
2436
+ }
2437
+ }
2438
+ async function runPortless(args) {
2439
+ const cliPath = resolvePortlessCliPath();
2440
+ if (!cliPath) throw new CodedError(log.GHFS0206({ detail: "portless package is not installed" }));
2441
+ try {
2442
+ const { stdout } = await execFileAsync(process.execPath, [cliPath, ...args], { env: {
2443
+ ...process.env,
2444
+ FORCE_COLOR: "0"
2445
+ } });
2446
+ return stdout;
2447
+ } catch (error) {
2448
+ const err = error;
2449
+ const detail = (err.stderr || err.stdout || err.message || "").toString().trim();
2450
+ throw new CodedError(log.GHFS0206({ detail: detail ? `portless failed: ${detail}` : "portless failed" }, { cause: error }));
2451
+ }
2452
+ }
2453
+ async function registerPortlessRoute(options) {
2454
+ const namespace = options.namespace ?? "ghfs";
2455
+ const tld = options.tld ?? "localhost";
2456
+ const hostname = `${options.subdomain}.${namespace}`;
2457
+ const fallbackUrl = `https://${hostname}.${tld}`;
2458
+ await runPortless([
2459
+ "alias",
2460
+ hostname,
2461
+ String(options.port)
2462
+ ]);
2463
+ let url = fallbackUrl;
2464
+ try {
2465
+ const resolved = (await runPortless(["get", hostname])).trim().split(/\r?\n/).pop()?.trim();
2466
+ if (resolved && /^https?:\/\//.test(resolved)) url = resolved;
2467
+ } catch {}
2468
+ let unregistered = false;
2469
+ return {
2470
+ hostname,
2471
+ url,
2472
+ async unregister() {
2473
+ if (unregistered) return;
2474
+ unregistered = true;
2475
+ try {
2476
+ await runPortless([
2477
+ "alias",
2478
+ "--remove",
2479
+ hostname
2480
+ ]);
2481
+ } catch {}
2482
+ }
2483
+ };
2484
+ }
2485
+ //#endregion
2486
+ //#region src/server/queue-builder.ts
2487
+ async function buildQueueState(options) {
2488
+ const { storageDirAbsolute, executeFilePath } = options;
2489
+ const executeMdPath = join(storageDirAbsolute, EXECUTE_MD_FILE_NAME);
2490
+ const [yml, md, perItem, syncState] = await Promise.all([
2491
+ readYmlOps(executeFilePath),
2492
+ readMdOps(executeMdPath),
2493
+ loadPerItemSource(storageDirAbsolute),
2494
+ loadSyncState(storageDirAbsolute)
2495
+ ]);
2496
+ const entries = [];
2497
+ for (const [index, op] of yml.ops.entries()) entries.push(buildEntry(op, "execute.yml", index));
2498
+ for (const [index, op] of md.ops.entries()) entries.push(buildEntry(op, "execute.md", index));
2499
+ for (const [index, op] of perItem.ops.entries()) {
2500
+ const filePath = syncState.items[String(op.number)]?.filePath;
2501
+ entries.push(buildEntry(op, "per-item", index, filePath));
2502
+ }
2503
+ return {
2504
+ entries,
2505
+ warnings: [
2506
+ ...yml.warnings,
2507
+ ...md.warnings,
2508
+ ...perItem.warnings
2509
+ ],
2510
+ upCount: entries.length
2511
+ };
2512
+ }
2513
+ async function readYmlOps(path) {
2514
+ if (!await pathExists(path)) return {
2515
+ ops: [],
2516
+ warnings: []
2517
+ };
2518
+ try {
2519
+ return {
2520
+ ops: (await readAndValidateExecuteFileWithSource(path)).ops,
2521
+ warnings: []
2522
+ };
2523
+ } catch (error) {
2524
+ return {
2525
+ ops: [],
2526
+ warnings: [`execute.yml: ${error.message}`]
2527
+ };
2528
+ }
2529
+ }
2530
+ async function readMdOps(path) {
2531
+ const parsed = await readExecuteMdFile(path);
2532
+ return {
2533
+ ops: parsed.ops,
2534
+ warnings: parsed.warnings
2535
+ };
2536
+ }
2537
+ function buildEntry(op, source, index, filePath) {
2538
+ return {
2539
+ id: hash({
2540
+ source,
2541
+ index,
2542
+ action: op.action,
2543
+ number: op.number
2544
+ }),
2545
+ source,
2546
+ index,
2547
+ op,
2548
+ filePath
2549
+ };
2550
+ }
2551
+ //#endregion
2552
+ //#region src/server/handlers/editor.ts
2553
+ function createEditorHandler(ctx) {
2554
+ return async function openInEditor(filePath) {
2555
+ const absolute = isAbsolute(filePath) ? filePath : resolve(ctx.storageDirAbsolute, filePath);
2556
+ const editor = process.env.EDITOR || process.env.VISUAL || "code";
2557
+ return new Promise((resolvePromise, rejectPromise) => {
2558
+ const child = spawn(editor, [absolute], {
2559
+ stdio: "ignore",
2560
+ detached: true
2561
+ });
2562
+ child.on("error", rejectPromise);
2563
+ child.unref();
2564
+ resolvePromise();
2565
+ });
2566
+ };
2567
+ }
2568
+ //#endregion
2569
+ //#region src/server/handlers/execute.ts
2570
+ function createExecuteHandler(ctx) {
2571
+ let running = false;
2572
+ return async function executeQueue(options) {
2573
+ if (running) throw new CodedError(log.GHFS0201());
2574
+ running = true;
2575
+ try {
2576
+ const token = await ctx.getToken();
2577
+ const selectedIndexes = await resolveSelectedIndexes(ctx, options.entryIds);
2578
+ ctx.broadcast.onExecuteStart({ planned: selectedIndexes?.length ?? -1 });
2579
+ return await executePendingChanges({
2580
+ config: ctx.config,
2581
+ repo: ctx.repo,
2582
+ token,
2583
+ executeFilePath: ctx.executeFilePath,
2584
+ apply: true,
2585
+ nonInteractive: true,
2586
+ continueOnError: options.continueOnError ?? true,
2587
+ selectedIndexes,
2588
+ reporter: {
2589
+ onStart(event) {
2590
+ ctx.broadcast.onExecuteStart({ planned: event.planned });
2591
+ },
2592
+ onProgress(event) {
2593
+ ctx.broadcast.onExecuteProgress({
2594
+ completed: event.completed,
2595
+ planned: event.planned,
2596
+ applied: event.applied,
2597
+ failed: event.failed,
2598
+ detail: event.detail
2599
+ });
2600
+ },
2601
+ onComplete(event) {
2602
+ ctx.broadcast.onExecuteComplete(event.result);
2603
+ },
2604
+ onError(event) {
2605
+ const message = event.error instanceof Error ? event.error.message : String(event.error);
2606
+ ctx.broadcast.onExecuteError(message);
2607
+ }
2608
+ }
2609
+ });
2610
+ } finally {
2611
+ running = false;
2612
+ }
2613
+ };
2614
+ }
2615
+ async function resolveSelectedIndexes(ctx, entryIds) {
2616
+ if (!entryIds || entryIds.length === 0) return void 0;
2617
+ const queue = await buildQueueState({
2618
+ storageDirAbsolute: ctx.storageDirAbsolute,
2619
+ executeFilePath: ctx.executeFilePath
2620
+ });
2621
+ const ids = new Set(entryIds);
2622
+ const indexes = [];
2623
+ queue.entries.forEach((entry, globalIndex) => {
2624
+ if (ids.has(entry.id)) indexes.push(globalIndex);
2625
+ });
2626
+ return indexes;
2627
+ }
2628
+ //#endregion
2629
+ //#region src/execute/compress.ts
2630
+ function createEmptyState() {
2631
+ return {
2632
+ title: null,
2633
+ body: null,
2634
+ stateChange: null,
2635
+ extraComments: [],
2636
+ labels: {
2637
+ mode: "delta",
2638
+ set: [],
2639
+ add: [],
2640
+ remove: []
2641
+ },
2642
+ assignees: {
2643
+ mode: "delta",
2644
+ set: [],
2645
+ add: [],
2646
+ remove: []
2647
+ },
2648
+ reviewers: {
2649
+ add: [],
2650
+ remove: []
2651
+ },
2652
+ milestone: null,
2653
+ lock: null,
2654
+ draft: null,
2655
+ ifUnchangedSince: void 0
2656
+ };
2657
+ }
2658
+ function addUnique(list, value) {
2659
+ if (!list.includes(value)) list.push(value);
2660
+ }
2661
+ function removeFromList(list, value) {
2662
+ const idx = list.indexOf(value);
2663
+ if (idx >= 0) list.splice(idx, 1);
2664
+ }
2665
+ function mergeIfUnchangedSince(current, next) {
2666
+ if (!next) return current;
2667
+ if (!current) return next;
2668
+ try {
2669
+ return new Date(next).getTime() < new Date(current).getTime() ? next : current;
2670
+ } catch {
2671
+ return current;
2672
+ }
2673
+ }
2674
+ function applyOp(state, op) {
2675
+ state.ifUnchangedSince = mergeIfUnchangedSince(state.ifUnchangedSince, op.ifUnchangedSince);
2676
+ switch (op.action) {
2677
+ case "close": {
2678
+ if (state.stateChange?.kind === "reopen") {
2679
+ state.stateChange = null;
2680
+ break;
2681
+ }
2682
+ const body = state.stateChange?.kind === "close" ? state.stateChange.body : void 0;
2683
+ state.stateChange = body ? {
2684
+ kind: "close",
2685
+ body
2686
+ } : { kind: "close" };
2687
+ break;
2688
+ }
2689
+ case "reopen":
2690
+ if (state.stateChange?.kind === "close") {
2691
+ state.stateChange = null;
2692
+ break;
2693
+ }
2694
+ state.stateChange = { kind: "reopen" };
2695
+ break;
2696
+ case "close-with-comment":
2697
+ if (state.stateChange?.kind === "reopen") {
2698
+ addUnique(state.extraComments, op.body);
2699
+ state.stateChange = null;
2700
+ break;
2701
+ }
2702
+ if (state.stateChange?.kind === "close" && state.stateChange.body && state.stateChange.body !== op.body) addUnique(state.extraComments, state.stateChange.body);
2703
+ state.stateChange = {
2704
+ kind: "close",
2705
+ body: op.body
2706
+ };
2707
+ break;
2708
+ case "set-title":
2709
+ state.title = op.title;
2710
+ break;
2711
+ case "set-body":
2712
+ state.body = op.body;
2713
+ break;
2714
+ case "add-comment":
2715
+ if (state.stateChange?.kind === "close" && state.stateChange.body === op.body) break;
2716
+ addUnique(state.extraComments, op.body);
2717
+ break;
2718
+ case "add-labels":
2719
+ if (state.labels.mode === "set") for (const l of op.labels) addUnique(state.labels.set, l);
2720
+ else for (const l of op.labels) if (state.labels.remove.includes(l)) removeFromList(state.labels.remove, l);
2721
+ else addUnique(state.labels.add, l);
2722
+ break;
2723
+ case "remove-labels":
2724
+ if (state.labels.mode === "set") state.labels.set = state.labels.set.filter((l) => !op.labels.includes(l));
2725
+ else for (const l of op.labels) if (state.labels.add.includes(l)) removeFromList(state.labels.add, l);
2726
+ else addUnique(state.labels.remove, l);
2727
+ break;
2728
+ case "set-labels":
2729
+ state.labels = {
2730
+ mode: "set",
2731
+ set: [...op.labels],
2732
+ add: [],
2733
+ remove: []
2734
+ };
2735
+ break;
2736
+ case "add-assignees":
2737
+ if (state.assignees.mode === "set") for (const a of op.assignees) addUnique(state.assignees.set, a);
2738
+ else for (const a of op.assignees) if (state.assignees.remove.includes(a)) removeFromList(state.assignees.remove, a);
2739
+ else addUnique(state.assignees.add, a);
2740
+ break;
2741
+ case "remove-assignees":
2742
+ if (state.assignees.mode === "set") state.assignees.set = state.assignees.set.filter((a) => !op.assignees.includes(a));
2743
+ else for (const a of op.assignees) if (state.assignees.add.includes(a)) removeFromList(state.assignees.add, a);
2744
+ else addUnique(state.assignees.remove, a);
2745
+ break;
2746
+ case "set-assignees":
2747
+ state.assignees = {
2748
+ mode: "set",
2749
+ set: [...op.assignees],
2750
+ add: [],
2751
+ remove: []
2752
+ };
2753
+ break;
2754
+ case "set-milestone":
2755
+ state.milestone = { set: op.milestone };
2756
+ break;
2757
+ case "clear-milestone":
2758
+ state.milestone = "clear";
2759
+ break;
2760
+ case "lock":
2761
+ state.lock = {
2762
+ lock: true,
2763
+ reason: op.reason
2764
+ };
2765
+ break;
2766
+ case "unlock":
2767
+ state.lock = state.lock?.lock === true ? null : { lock: false };
2768
+ break;
2769
+ case "convert-to-draft":
2770
+ state.draft = state.draft === "ready" ? null : "draft";
2771
+ break;
2772
+ case "mark-ready-for-review":
2773
+ state.draft = state.draft === "draft" ? null : "ready";
2774
+ break;
2775
+ case "request-reviewers":
2776
+ for (const r of op.reviewers) if (state.reviewers.remove.includes(r)) removeFromList(state.reviewers.remove, r);
2777
+ else addUnique(state.reviewers.add, r);
2778
+ break;
2779
+ case "remove-reviewers":
2780
+ for (const r of op.reviewers) if (state.reviewers.add.includes(r)) removeFromList(state.reviewers.add, r);
2781
+ else addUnique(state.reviewers.remove, r);
2782
+ break;
2783
+ }
2784
+ }
2785
+ function emit(number, state) {
2786
+ const ifUnchangedSince = state.ifUnchangedSince;
2787
+ const withBase = (op) => ifUnchangedSince ? {
2788
+ ...op,
2789
+ ifUnchangedSince
2790
+ } : op;
2791
+ const out = [];
2792
+ if (state.title != null) out.push(withBase({
2793
+ action: "set-title",
2794
+ number,
2795
+ title: state.title
2796
+ }));
2797
+ if (state.body != null) out.push(withBase({
2798
+ action: "set-body",
2799
+ number,
2800
+ body: state.body
2801
+ }));
2802
+ if (state.labels.mode === "set") out.push(withBase({
2803
+ action: "set-labels",
2804
+ number,
2805
+ labels: [...state.labels.set]
2806
+ }));
2807
+ else {
2808
+ if (state.labels.add.length) out.push(withBase({
2809
+ action: "add-labels",
2810
+ number,
2811
+ labels: [...state.labels.add]
2812
+ }));
2813
+ if (state.labels.remove.length) out.push(withBase({
2814
+ action: "remove-labels",
2815
+ number,
2816
+ labels: [...state.labels.remove]
2817
+ }));
2818
+ }
2819
+ if (state.assignees.mode === "set") out.push(withBase({
2820
+ action: "set-assignees",
2821
+ number,
2822
+ assignees: [...state.assignees.set]
2823
+ }));
2824
+ else {
2825
+ if (state.assignees.add.length) out.push(withBase({
2826
+ action: "add-assignees",
2827
+ number,
2828
+ assignees: [...state.assignees.add]
2829
+ }));
2830
+ if (state.assignees.remove.length) out.push(withBase({
2831
+ action: "remove-assignees",
2832
+ number,
2833
+ assignees: [...state.assignees.remove]
2834
+ }));
2835
+ }
2836
+ if (state.milestone === "clear") out.push(withBase({
2837
+ action: "clear-milestone",
2838
+ number
2839
+ }));
2840
+ else if (state.milestone && "set" in state.milestone) out.push(withBase({
2841
+ action: "set-milestone",
2842
+ number,
2843
+ milestone: state.milestone.set
2844
+ }));
2845
+ if (state.reviewers.add.length) out.push(withBase({
2846
+ action: "request-reviewers",
2847
+ number,
2848
+ reviewers: [...state.reviewers.add]
2849
+ }));
2850
+ if (state.reviewers.remove.length) out.push(withBase({
2851
+ action: "remove-reviewers",
2852
+ number,
2853
+ reviewers: [...state.reviewers.remove]
2854
+ }));
2855
+ if (state.draft === "draft") out.push(withBase({
2856
+ action: "convert-to-draft",
2857
+ number
2858
+ }));
2859
+ else if (state.draft === "ready") out.push(withBase({
2860
+ action: "mark-ready-for-review",
2861
+ number
2862
+ }));
2863
+ for (const body of state.extraComments) out.push(withBase({
2864
+ action: "add-comment",
2865
+ number,
2866
+ body
2867
+ }));
2868
+ if (state.stateChange?.kind === "close") if (state.stateChange.body != null) out.push(withBase({
2869
+ action: "close-with-comment",
2870
+ number,
2871
+ body: state.stateChange.body
2872
+ }));
2873
+ else out.push(withBase({
2874
+ action: "close",
2875
+ number
2876
+ }));
2877
+ else if (state.stateChange?.kind === "reopen") out.push(withBase({
2878
+ action: "reopen",
2879
+ number
2880
+ }));
2881
+ if (state.lock?.lock === true) out.push(withBase({
2882
+ action: "lock",
2883
+ number,
2884
+ ...state.lock.reason ? { reason: state.lock.reason } : {}
2885
+ }));
2886
+ else if (state.lock?.lock === false) out.push(withBase({
2887
+ action: "unlock",
2888
+ number
2889
+ }));
2890
+ return out;
2891
+ }
2892
+ /**
2893
+ * Compress a list of pending ops by merging/cancelling redundant ones per-item.
2894
+ *
2895
+ * - `close` + `reopen` cancel each other.
2896
+ * - `close-with-comment("a")` + `close-with-comment("b")` keeps "b" bundled and demotes "a" to a standalone add-comment.
2897
+ * - `close-with-comment("a")` + `reopen` cancels entirely (the body is dropped — if users want to keep the comment they can add it explicitly).
2898
+ * - Adjacent `add-labels` / `remove-labels` merge; overlapping pairs cancel out.
2899
+ * - `set-title` / `set-body` / `set-labels` / `set-assignees` / `set-milestone` / `clear-milestone` follow last-write-wins.
2900
+ * - `add-comment` bodies dedupe.
2901
+ * - `lock`/`unlock` and `convert-to-draft`/`mark-ready-for-review` cancel when paired in that order.
2902
+ * - `ifUnchangedSince` is preserved as the earliest (most conservative) value across ops in the group.
2903
+ *
2904
+ * Groups are emitted in the order their number first appeared in the input.
2905
+ */
2906
+ function compressOps(ops) {
2907
+ const groups = /* @__PURE__ */ new Map();
2908
+ let order = 0;
2909
+ for (const op of ops) {
2910
+ let entry = groups.get(op.number);
2911
+ if (!entry) {
2912
+ entry = {
2913
+ order: order++,
2914
+ list: []
2915
+ };
2916
+ groups.set(op.number, entry);
2917
+ }
2918
+ entry.list.push(op);
2919
+ }
2920
+ const sorted = [...groups.entries()].sort((a, b) => a[1].order - b[1].order);
2921
+ const result = [];
2922
+ for (const [number, { list }] of sorted) {
2923
+ const state = createEmptyState();
2924
+ for (const op of list) applyOp(state, op);
2925
+ result.push(...emit(number, state));
2926
+ }
2927
+ return result;
2928
+ }
2929
+ //#endregion
2930
+ //#region src/server/queue-writer.ts
2931
+ async function addQueueOp(options, op) {
2932
+ await ensureExecuteArtifacts(options.executeFilePath);
2933
+ const next = compressOps([...(await readAndValidateExecuteFileWithSource(options.executeFilePath)).ops, op]);
2934
+ await writeExecuteFile(options.executeFilePath, next);
2935
+ return buildQueueState(options);
2936
+ }
2937
+ async function removeQueueOp(options, id) {
2938
+ const entry = (await buildQueueState(options)).entries.find((e) => e.id === id);
2939
+ if (!entry) throw new CodedError(log.GHFS0202({ id }));
2940
+ if (entry.source === "per-item") throw new CodedError(log.GHFS0203({ target: entry.filePath ?? "the markdown file" }));
2941
+ if (entry.source === "execute.yml") {
2942
+ const next = (await readAndValidateExecuteFileWithSource(options.executeFilePath)).ops.filter((_, index) => index !== entry.index);
2943
+ await writeExecuteFile(options.executeFilePath, next);
2944
+ return buildQueueState(options);
2945
+ }
2946
+ const mdPath = join(options.storageDirAbsolute, EXECUTE_MD_FILE_NAME);
2947
+ if (!await pathExists(mdPath)) throw new CodedError(log.GHFS0204());
2948
+ const parsed = await readExecuteMdFile(mdPath);
2949
+ const remaining = /* @__PURE__ */ new Set();
2950
+ for (let i = 0; i < parsed.ops.length; i += 1) if (i !== entry.index) remaining.add(i);
2951
+ await writeFile(mdPath, stringifyExecuteMd(parsed, remaining), "utf-8");
2952
+ return buildQueueState(options);
2953
+ }
2954
+ async function updateQueueOp(options, id, op) {
2955
+ const entry = (await buildQueueState(options)).entries.find((e) => e.id === id);
2956
+ if (!entry) throw new CodedError(log.GHFS0202({ id }));
2957
+ if (entry.source !== "execute.yml") throw new CodedError(log.GHFS0205({ source: entry.source }));
2958
+ const replaced = (await readAndValidateExecuteFileWithSource(options.executeFilePath)).ops.map((existing, index) => index === entry.index ? op : existing);
2959
+ await writeExecuteFile(options.executeFilePath, compressOps(replaced));
2960
+ return buildQueueState(options);
2961
+ }
2962
+ async function clearQueue(options) {
2963
+ await ensureExecuteArtifacts(options.executeFilePath);
2964
+ await writeExecuteFile(options.executeFilePath, []);
2965
+ return buildQueueState(options);
2966
+ }
2967
+ //#endregion
2968
+ //#region src/server/handlers/queue.ts
2969
+ function createQueueHandlers(ctx) {
2970
+ const options = {
2971
+ storageDirAbsolute: ctx.storageDirAbsolute,
2972
+ executeFilePath: ctx.executeFilePath
2973
+ };
2974
+ return {
2975
+ addQueueOp: (op) => addQueueOp(options, op),
2976
+ updateQueueOp: (id, op) => updateQueueOp(options, id, op),
2977
+ removeQueueOp: (id) => removeQueueOp(options, id),
2978
+ clearQueue: () => clearQueue(options)
2979
+ };
2980
+ }
2981
+ //#endregion
2982
+ //#region src/server/handlers/remote.ts
2983
+ function createRemoteHandler(ctx) {
2984
+ return async function checkRemote() {
2985
+ return ctx.poller.checkNow();
2986
+ };
2987
+ }
2988
+ //#endregion
2989
+ //#region src/sync/repo-snapshot.ts
2990
+ async function loadRepoSnapshot(storageDirAbsolute) {
2991
+ try {
2992
+ const raw = await readFile(join(storageDirAbsolute, REPO_SNAPSHOT_FILE_NAME), "utf8");
2993
+ return JSON.parse(raw);
2994
+ } catch {
2995
+ return null;
2996
+ }
2997
+ }
2998
+ //#endregion
2999
+ //#region src/server/ui-state.ts
3000
+ const UI_STATE_FILE = ".ui.json";
3001
+ function createEmptyUiState() {
3002
+ return { drafts: {} };
3003
+ }
3004
+ function normalizePrTab(value) {
3005
+ if (value === "conversation" || value === "commits" || value === "changes") return value;
3006
+ }
3007
+ function normalizeUserOverride(value) {
3008
+ if (!value || typeof value !== "object") return void 0;
3009
+ const raw = value;
3010
+ const out = {};
3011
+ if (typeof raw.login === "string" && raw.login.trim()) out.login = raw.login.trim();
3012
+ if (typeof raw.name === "string" && raw.name.trim()) out.name = raw.name.trim();
3013
+ if (typeof raw.avatarUrl === "string" && raw.avatarUrl.startsWith("https://")) out.avatarUrl = raw.avatarUrl.trim();
3014
+ return Object.keys(out).length > 0 ? out : void 0;
3015
+ }
3016
+ async function loadUiState(storageDirAbsolute) {
3017
+ try {
3018
+ const raw = await readFile(join(storageDirAbsolute, UI_STATE_FILE), "utf8");
3019
+ const parsed = JSON.parse(raw);
3020
+ const drafts = {};
3021
+ if (parsed.drafts && typeof parsed.drafts === "object") {
3022
+ for (const [key, value] of Object.entries(parsed.drafts)) if (typeof value === "string" && value.length > 0) drafts[key] = value;
3023
+ }
3024
+ return {
3025
+ drafts,
3026
+ listPaneSize: typeof parsed.listPaneSize === "number" ? parsed.listPaneSize : void 0,
3027
+ lastPrTab: normalizePrTab(parsed.lastPrTab),
3028
+ userOverride: normalizeUserOverride(parsed.userOverride)
3029
+ };
3030
+ } catch {
3031
+ return createEmptyUiState();
3032
+ }
3033
+ }
3034
+ async function saveUiState(storageDirAbsolute, state) {
3035
+ await mkdir(storageDirAbsolute, { recursive: true });
3036
+ const tab = normalizePrTab(state.lastPrTab);
3037
+ const override = normalizeUserOverride(state.userOverride);
3038
+ const clean = {
3039
+ drafts: { ...state.drafts },
3040
+ ...state.listPaneSize != null ? { listPaneSize: state.listPaneSize } : {},
3041
+ ...tab ? { lastPrTab: tab } : {},
3042
+ ...override ? { userOverride: override } : {}
3043
+ };
3044
+ await writeFile(join(storageDirAbsolute, UI_STATE_FILE), `${JSON.stringify(clean, null, 2)}\n`, "utf8");
3045
+ }
3046
+ //#endregion
3047
+ //#region src/server/handlers/state.ts
3048
+ function createStateHandlers(ctx) {
3049
+ async function getRepoMeta() {
3050
+ const syncState = await loadSyncState(ctx.storageDirAbsolute);
3051
+ let hasToken = false;
3052
+ try {
3053
+ const token = await ctx.getToken();
3054
+ hasToken = Boolean(token);
3055
+ } catch {
3056
+ hasToken = false;
3057
+ }
3058
+ return {
3059
+ repo: ctx.repo,
3060
+ storageDir: ctx.config.directory,
3061
+ ghfsVersion: GHFS_VERSION,
3062
+ lastSyncedAt: syncState.lastSyncedAt,
3063
+ lastSince: syncState.lastSince,
3064
+ hasToken
3065
+ };
3066
+ }
3067
+ async function getSyncState() {
3068
+ return loadSyncState(ctx.storageDirAbsolute);
3069
+ }
3070
+ async function getQueue() {
3071
+ return buildQueueState({
3072
+ storageDirAbsolute: ctx.storageDirAbsolute,
3073
+ executeFilePath: ctx.executeFilePath
3074
+ });
3075
+ }
3076
+ async function getInitialPayload() {
3077
+ const [repo, syncState, queue, uiState, snapshot] = await Promise.all([
3078
+ getRepoMeta(),
3079
+ getSyncState(),
3080
+ getQueue(),
3081
+ loadUiState(ctx.storageDirAbsolute),
3082
+ loadRepoSnapshot(ctx.storageDirAbsolute)
3083
+ ]);
3084
+ const repositoryLabels = (snapshot?.labels ?? []).map((label) => ({
3085
+ name: label.name,
3086
+ color: label.color,
3087
+ description: label.description
3088
+ }));
3089
+ const currentUser = await resolveCurrentUser(ctx, uiState);
3090
+ return {
3091
+ repo,
3092
+ syncState,
3093
+ queue,
3094
+ remote: ctx.poller.getCurrent(),
3095
+ recentExecutions: syncState.executions ?? [],
3096
+ uiState,
3097
+ repositoryLabels,
3098
+ currentUser
3099
+ };
3100
+ }
3101
+ async function getPullPatch(number) {
3102
+ const tracked = (await loadSyncState(ctx.storageDirAbsolute)).items[String(number)];
3103
+ if (!tracked?.patchPath) return null;
3104
+ try {
3105
+ return await readFile(join(ctx.storageDirAbsolute, tracked.patchPath), "utf8");
3106
+ } catch (err) {
3107
+ if (err.code === "ENOENT") return null;
3108
+ throw err;
3109
+ }
3110
+ }
3111
+ return {
3112
+ getInitialPayload,
3113
+ getSyncState,
3114
+ getQueue,
3115
+ getRepoMeta,
3116
+ saveUiState: (state) => saveUiState(ctx.storageDirAbsolute, state),
3117
+ getPullPatch
3118
+ };
3119
+ }
3120
+ async function resolveCurrentUser(ctx, uiState) {
3121
+ const override = uiState.userOverride;
3122
+ let fetched = null;
3123
+ try {
3124
+ const user = await (await ctx.getProvider())?.fetchAuthenticatedUser();
3125
+ if (user) fetched = {
3126
+ login: user.login,
3127
+ name: user.name,
3128
+ avatarUrl: user.avatarUrl
3129
+ };
3130
+ } catch {
3131
+ fetched = null;
3132
+ }
3133
+ if (!override && !fetched) return null;
3134
+ const login = override?.login ?? fetched?.login;
3135
+ if (!login) return null;
3136
+ return {
3137
+ login,
3138
+ name: override?.name ?? fetched?.name ?? null,
3139
+ avatarUrl: override?.avatarUrl ?? fetched?.avatarUrl ?? `https://avatars.githubusercontent.com/${login}`
3140
+ };
3141
+ }
3142
+ //#endregion
3143
+ //#region src/server/handlers/sync.ts
3144
+ function createSyncHandler(ctx) {
3145
+ let running = false;
3146
+ return async function triggerSync(options) {
3147
+ if (running) throw new CodedError(log.GHFS0200());
3148
+ running = true;
3149
+ try {
3150
+ const token = await ctx.getToken();
3151
+ return await syncRepository({
3152
+ config: ctx.config,
3153
+ repo: ctx.repo,
3154
+ token,
3155
+ full: options.full,
3156
+ since: options.since,
3157
+ numbers: options.numbers,
3158
+ reporter: {
3159
+ onStageStart(event) {
3160
+ ctx.broadcast.onSyncStageStart({
3161
+ stage: event.stage,
3162
+ message: event.message
3163
+ });
3164
+ },
3165
+ onStageUpdate(event) {
3166
+ ctx.broadcast.onSyncProgress({
3167
+ stage: event.stage,
3168
+ message: event.message,
3169
+ snapshot: event.snapshot
3170
+ });
3171
+ },
3172
+ onStageEnd(event) {
3173
+ ctx.broadcast.onSyncStageEnd({
3174
+ stage: event.stage,
3175
+ durationMs: event.durationMs
3176
+ });
3177
+ },
3178
+ onComplete(event) {
3179
+ ctx.broadcast.onSyncComplete(event.summary);
3180
+ },
3181
+ onError(event) {
3182
+ const message = event.error instanceof Error ? event.error.message : String(event.error);
3183
+ ctx.broadcast.onSyncError(message);
3184
+ }
3185
+ }
3186
+ });
3187
+ } finally {
3188
+ running = false;
3189
+ }
3190
+ };
3191
+ }
3192
+ //#endregion
3193
+ //#region src/server/rpc.ts
3194
+ function createServerFunctions(ctx) {
3195
+ const state = createStateHandlers(ctx);
3196
+ const queue = createQueueHandlers(ctx);
3197
+ return {
3198
+ getInitialPayload: state.getInitialPayload,
3199
+ getSyncState: state.getSyncState,
3200
+ getQueue: state.getQueue,
3201
+ getRepoMeta: state.getRepoMeta,
3202
+ triggerSync: createSyncHandler(ctx),
3203
+ executeQueue: createExecuteHandler(ctx),
3204
+ addQueueOp: queue.addQueueOp,
3205
+ updateQueueOp: queue.updateQueueOp,
3206
+ removeQueueOp: queue.removeQueueOp,
3207
+ clearQueue: queue.clearQueue,
3208
+ checkRemote: createRemoteHandler(ctx),
3209
+ openInEditor: createEditorHandler(ctx),
3210
+ saveUiState: state.saveUiState,
3211
+ getPullPatch: state.getPullPatch
3212
+ };
3213
+ }
3214
+ //#endregion
3215
+ //#region src/server/static.ts
3216
+ const MIME_TYPES = {
3217
+ css: "text/css; charset=utf-8",
3218
+ html: "text/html; charset=utf-8",
3219
+ ico: "image/x-icon",
3220
+ jpeg: "image/jpeg",
3221
+ jpg: "image/jpeg",
3222
+ js: "application/javascript; charset=utf-8",
3223
+ json: "application/json; charset=utf-8",
3224
+ map: "application/json; charset=utf-8",
3225
+ mjs: "application/javascript; charset=utf-8",
3226
+ png: "image/png",
3227
+ svg: "image/svg+xml; charset=utf-8",
3228
+ txt: "text/plain; charset=utf-8",
3229
+ webp: "image/webp",
3230
+ woff: "font/woff",
3231
+ woff2: "font/woff2",
3232
+ xml: "application/xml; charset=utf-8"
3233
+ };
3234
+ function mimeTypeFor(id) {
3235
+ const dot = id.lastIndexOf(".");
3236
+ if (dot < 0) return void 0;
3237
+ return MIME_TYPES[id.slice(dot + 1).toLowerCase()];
3238
+ }
3239
+ async function createStaticHandler(staticDir) {
3240
+ async function metaFor(id) {
3241
+ let stats;
3242
+ try {
3243
+ stats = await stat(resolve(staticDir, `.${id}`));
3244
+ } catch {
3245
+ return;
3246
+ }
3247
+ if (!stats.isFile()) return void 0;
3248
+ return {
3249
+ mtime: stats.mtimeMs,
3250
+ size: stats.size,
3251
+ type: mimeTypeFor(id)
3252
+ };
3253
+ }
3254
+ const spaFallback = await readFallback(staticDir);
3255
+ return eventHandler(async (event) => {
3256
+ if (await serveStatic(event, {
3257
+ getContents: (id) => readFile(resolve(staticDir, `.${id}`)),
3258
+ getMeta: (id) => metaFor(id),
3259
+ indexNames: ["/index.html"],
3260
+ fallthrough: true
3261
+ }) !== false) return;
3262
+ if (spaFallback) {
3263
+ setResponseHeader(event, "content-type", "text/html; charset=utf-8");
3264
+ return spaFallback;
3265
+ }
3266
+ setResponseHeader(event, "content-type", "text/plain; charset=utf-8");
3267
+ return `ghfs UI assets not found at ${staticDir}. Run "pnpm build" first.\n`;
3268
+ });
3269
+ }
3270
+ async function readFallback(staticDir) {
3271
+ for (const name of ["200.html", "index.html"]) {
3272
+ const candidate = resolve(staticDir, name);
3273
+ if (await pathExists(candidate)) return readFile(candidate, "utf-8");
3274
+ }
3275
+ }
3276
+ //#endregion
3277
+ //#region src/server/watcher.ts
3278
+ async function createGhfsWatcher(options) {
3279
+ const { storageDirAbsolute, onSyncStateChange, onQueueChange } = options;
3280
+ const debounceMs = options.debounceMs ?? 150;
3281
+ await mkdir(storageDirAbsolute, { recursive: true });
3282
+ let syncTimer;
3283
+ let queueTimer;
3284
+ const flushSync = () => {
3285
+ syncTimer = void 0;
3286
+ try {
3287
+ onSyncStateChange();
3288
+ } catch {}
3289
+ };
3290
+ const flushQueue = () => {
3291
+ queueTimer = void 0;
3292
+ try {
3293
+ onQueueChange();
3294
+ } catch {}
3295
+ };
3296
+ const scheduleSync = () => {
3297
+ if (syncTimer) clearTimeout(syncTimer);
3298
+ syncTimer = setTimeout(flushSync, debounceMs);
3299
+ };
3300
+ const scheduleQueue = () => {
3301
+ if (queueTimer) clearTimeout(queueTimer);
3302
+ queueTimer = setTimeout(flushQueue, debounceMs);
3303
+ };
3304
+ const controller = new AbortController();
3305
+ const iterator = watch(storageDirAbsolute, {
3306
+ recursive: true,
3307
+ signal: controller.signal
3308
+ });
3309
+ const pump = async () => {
3310
+ try {
3311
+ for await (const event of iterator) {
3312
+ const filename = event.filename ? event.filename.replace(/\\/g, "/") : "";
3313
+ if (!filename) continue;
3314
+ const basename = filename.split("/").pop() ?? "";
3315
+ if (basename === ".sync.json") {
3316
+ scheduleSync();
3317
+ scheduleQueue();
3318
+ continue;
3319
+ }
3320
+ if (basename === "execute.yml" || basename === "execute.md") {
3321
+ scheduleQueue();
3322
+ continue;
3323
+ }
3324
+ if (basename.endsWith(".md")) scheduleQueue();
3325
+ }
3326
+ } catch (error) {
3327
+ if (error.name !== "AbortError") throw error;
3328
+ }
3329
+ };
3330
+ pump();
3331
+ return { close: async () => {
3332
+ controller.abort();
3333
+ if (syncTimer) clearTimeout(syncTimer);
3334
+ if (queueTimer) clearTimeout(queueTimer);
3335
+ } };
3336
+ }
3337
+ //#endregion
3338
+ //#region src/server/index.ts
3339
+ const EVENT_NAMES = [
3340
+ "onSyncStageStart",
3341
+ "onSyncProgress",
3342
+ "onSyncStageEnd",
3343
+ "onSyncComplete",
3344
+ "onSyncError",
3345
+ "onExecuteStart",
3346
+ "onExecuteProgress",
3347
+ "onExecuteComplete",
3348
+ "onExecuteError",
3349
+ "onSyncStateChange",
3350
+ "onQueueChange",
3351
+ "onRemoteStatusChange"
3352
+ ];
3353
+ async function createUiServer(options) {
3354
+ const storageDirAbsolute = resolve(options.config.cwd, options.config.directory);
3355
+ const executeFilePath = resolve(options.config.cwd, getExecuteFile(options.config));
3356
+ let cachedToken = options.initialToken ?? "";
3357
+ async function getToken() {
3358
+ if (cachedToken) return cachedToken;
3359
+ if (options.onRequestToken) {
3360
+ cachedToken = await options.onRequestToken();
3361
+ return cachedToken;
3362
+ }
3363
+ throw new CodedError(log.GHFS0001());
3364
+ }
3365
+ let cachedProvider;
3366
+ async function getProvider() {
3367
+ if (cachedProvider) return cachedProvider;
3368
+ let token;
3369
+ try {
3370
+ token = await getToken();
3371
+ } catch {
3372
+ return null;
3373
+ }
3374
+ if (!token) return null;
3375
+ cachedProvider = createRepositoryProvider({
3376
+ token,
3377
+ repo: options.repo
3378
+ });
3379
+ return cachedProvider;
3380
+ }
3381
+ let lastSyncedAt = (await loadSyncState(storageDirAbsolute)).lastSyncedAt;
3382
+ const clients = /* @__PURE__ */ new Set();
3383
+ const broadcast = createBroadcast(clients);
3384
+ const poller = createRemotePoller({
3385
+ intervalMs: options.pollerIntervalMs,
3386
+ getProvider,
3387
+ getSince: () => lastSyncedAt,
3388
+ onUpdate: (status) => broadcast.onRemoteStatusChange(status)
3389
+ });
3390
+ const serverFunctions = createServerFunctions({
3391
+ config: options.config,
3392
+ repo: options.repo,
3393
+ storageDirAbsolute,
3394
+ executeFilePath,
3395
+ getToken,
3396
+ getProvider,
3397
+ broadcast,
3398
+ poller
3399
+ });
3400
+ const watcher = await createGhfsWatcher({
3401
+ storageDirAbsolute,
3402
+ onSyncStateChange: async () => {
3403
+ const state = await loadSyncState(storageDirAbsolute);
3404
+ lastSyncedAt = state.lastSyncedAt;
3405
+ broadcast.onSyncStateChange(state);
3406
+ },
3407
+ onQueueChange: async () => {
3408
+ const queue = await buildQueueState({
3409
+ storageDirAbsolute,
3410
+ executeFilePath
3411
+ });
3412
+ broadcast.onQueueChange(queue);
3413
+ }
3414
+ });
3415
+ const app = createApp();
3416
+ const router = createRouter();
3417
+ router.get("/api/metadata.json", eventHandler(() => ({
3418
+ repo: options.repo,
3419
+ storageDir: options.config.directory,
3420
+ ghfsVersion: GHFS_VERSION,
3421
+ wsPath: "/__ws",
3422
+ devMode: Boolean(options.devMode)
3423
+ })));
3424
+ app.use(router);
3425
+ if (options.devMode) app.use("/", eventHandler((event) => {
3426
+ setResponseStatus(event, 200);
3427
+ setResponseHeader(event, "content-type", "text/plain; charset=utf-8");
3428
+ return `ghfs ui dev mode — open http://localhost:7711 for the Vite-powered UI.\n`;
3429
+ }));
3430
+ else {
3431
+ const staticDir = join(distDir, "ui");
3432
+ app.use("/", await createStaticHandler(staticDir));
3433
+ }
3434
+ const port = await getPort({
3435
+ port: options.port,
3436
+ portRange: [options.port, options.port + 19],
3437
+ host: options.host
3438
+ });
3439
+ const httpServer = createServer(toNodeListener(app));
3440
+ const wss = new WebSocketServer({
3441
+ server: httpServer,
3442
+ path: "/__ws"
3443
+ });
3444
+ wss.on("connection", (socket) => {
3445
+ const rpc = createBirpc(serverFunctions, {
3446
+ post: (data) => socket.send(data),
3447
+ on: (fn) => socket.on("message", (raw) => {
3448
+ fn(rawToString(raw));
3449
+ }),
3450
+ serialize: (data) => stringify$1(data),
3451
+ deserialize: (raw) => parse$1(rawToString(raw)),
3452
+ timeout: 12e4,
3453
+ eventNames: EVENT_NAMES
3454
+ });
3455
+ clients.add(rpc);
3456
+ socket.on("close", () => {
3457
+ clients.delete(rpc);
3458
+ });
3459
+ socket.on("error", () => {
3460
+ clients.delete(rpc);
3461
+ });
3462
+ });
3463
+ await new Promise((resolvePromise, rejectPromise) => {
3464
+ httpServer.once("error", rejectPromise);
3465
+ httpServer.listen(port, options.host, () => {
3466
+ httpServer.off("error", rejectPromise);
3467
+ resolvePromise();
3468
+ });
3469
+ });
3470
+ const directUrl = `http://${options.host === "0.0.0.0" ? "localhost" : options.host}:${port}`;
3471
+ let portlessUrl;
3472
+ let portlessUnregister;
3473
+ if (options.portless?.enabled) try {
3474
+ const route = await registerPortlessRoute({
3475
+ subdomain: options.portless.subdomain,
3476
+ namespace: options.portless.namespace,
3477
+ port
3478
+ });
3479
+ portlessUrl = route.url;
3480
+ portlessUnregister = route.unregister;
3481
+ } catch (error) {
3482
+ const message = error.message || String(error);
3483
+ options.logger?.info?.(`portless unavailable (${message}); falling back to ${directUrl}`);
3484
+ }
3485
+ return {
3486
+ url: portlessUrl ?? directUrl,
3487
+ directUrl,
3488
+ portlessUrl,
3489
+ port,
3490
+ host: options.host,
3491
+ close: async () => {
3492
+ if (portlessUnregister) await portlessUnregister();
3493
+ poller.close();
3494
+ await watcher.close();
3495
+ await new Promise((resolveClose) => wss.close(() => resolveClose()));
3496
+ await new Promise((resolveClose) => httpServer.close(() => resolveClose()));
3497
+ }
3498
+ };
3499
+ }
3500
+ function createBroadcast(clients) {
3501
+ return new Proxy({}, { get(_, prop) {
3502
+ if (typeof prop !== "string") return void 0;
3503
+ return (...args) => {
3504
+ for (const rpc of clients) try {
3505
+ const fn = rpc[prop];
3506
+ if (typeof fn === "function") fn(...args);
3507
+ } catch {}
3508
+ };
3509
+ } });
3510
+ }
3511
+ function rawToString(raw) {
3512
+ if (typeof raw === "string") return raw;
3513
+ if (raw instanceof Uint8Array) return Buffer.from(raw).toString("utf-8");
3514
+ if (Array.isArray(raw)) return Buffer.concat(raw).toString("utf-8");
3515
+ return String(raw);
3516
+ }
3517
+ //#endregion
3518
+ export { executePendingChanges as a, GHFS_NAME as c, ACTIONS_COLOR_HEX as d, pathExists as f, resolveConfig as h, appendExecutionResult as i, GHFS_VERSION as l, getStorageDirAbsolute as m, slugifyRepoName as n, isExecuteCancelledError as o, getExecuteFile as p, syncRepository as r, loadSyncState as s, createUiServer as t, ensureExecuteArtifacts as u };