@ayushporwal/coja 0.0.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 (455) hide show
  1. package/bin/coja.js +2 -0
  2. package/dist/ai/chat-handler.js +115 -0
  3. package/dist/ai/chats.js +92 -0
  4. package/dist/ai/history.js +70 -0
  5. package/dist/ai/prompt.js +73 -0
  6. package/dist/ai/providers.js +81 -0
  7. package/dist/ai/title.js +77 -0
  8. package/dist/ai/tools.js +495 -0
  9. package/dist/ai/types.js +22 -0
  10. package/dist/app.js +142 -0
  11. package/dist/cli.js +99 -0
  12. package/dist/codex/connection.js +293 -0
  13. package/dist/codex/errors.js +52 -0
  14. package/dist/codex/models.js +97 -0
  15. package/dist/codex/oauth.js +217 -0
  16. package/dist/codex/protocol.js +49 -0
  17. package/dist/codex/provider.js +209 -0
  18. package/dist/context.js +8 -0
  19. package/dist/custom-providers.js +134 -0
  20. package/dist/data-dir.js +25 -0
  21. package/dist/db.js +65 -0
  22. package/dist/forge/forge.js +11 -0
  23. package/dist/forge/gh-cli.js +99 -0
  24. package/dist/forge/github.js +627 -0
  25. package/dist/forge/graphql.js +106 -0
  26. package/dist/forge/queries.js +258 -0
  27. package/dist/git/plumbing.js +519 -0
  28. package/dist/git/remote.js +47 -0
  29. package/dist/git/run.js +256 -0
  30. package/dist/open-browser.js +27 -0
  31. package/dist/paths.js +18 -0
  32. package/dist/pr/fetcher.js +173 -0
  33. package/dist/pr/objects.js +83 -0
  34. package/dist/pr/refs.js +17 -0
  35. package/dist/projects/add.js +111 -0
  36. package/dist/projects/store.js +37 -0
  37. package/dist/routes/chat.js +187 -0
  38. package/dist/routes/fs.js +65 -0
  39. package/dist/routes/git.js +75 -0
  40. package/dist/routes/guard.js +96 -0
  41. package/dist/routes/http.js +35 -0
  42. package/dist/routes/projects.js +51 -0
  43. package/dist/routes/pulls.js +64 -0
  44. package/dist/routes/review.js +113 -0
  45. package/dist/routes/setup.js +285 -0
  46. package/dist/secrets/providers.js +27 -0
  47. package/dist/secrets/store.js +190 -0
  48. package/dist/server.js +51 -0
  49. package/dist/services.js +15 -0
  50. package/dist/shared/api.js +186 -0
  51. package/package.json +35 -0
  52. package/public/assets/abap-CLvhMVsD.js +1 -0
  53. package/public/assets/actionscript-3-Cs6JJXT5.js +1 -0
  54. package/public/assets/ada-C5qYipkI.js +1 -0
  55. package/public/assets/ahk-CA9gdOdF.js +1 -0
  56. package/public/assets/ahk2-_6rzW9BS.js +1 -0
  57. package/public/assets/andromeeda-vGVdxbeo.js +1 -0
  58. package/public/assets/angular-html-BvGMhoM4.js +1 -0
  59. package/public/assets/angular-ts-D4cJlfHk.js +1 -0
  60. package/public/assets/apache-U0d_L8uA.js +1 -0
  61. package/public/assets/apex-KFGFyKwG.js +1 -0
  62. package/public/assets/apl-BAzDJNhF.js +1 -0
  63. package/public/assets/applescript-CCn79oCD.js +1 -0
  64. package/public/assets/ara-4CJ0cIlV.js +1 -0
  65. package/public/assets/asciidoc-SCjQUq34.js +1 -0
  66. package/public/assets/asm-Cmm7eHzH.js +1 -0
  67. package/public/assets/astro-tbl65Kw6.js +1 -0
  68. package/public/assets/aurora-x-CDeNXAV0.js +1 -0
  69. package/public/assets/awk-BWXHIvNe.js +1 -0
  70. package/public/assets/ayu-dark-DluEY0Gj.js +1 -0
  71. package/public/assets/ayu-light-C3h-C4tm.js +1 -0
  72. package/public/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  73. package/public/assets/ballerina-B7ZEbQpA.js +1 -0
  74. package/public/assets/bat-qdxAdBWQ.js +1 -0
  75. package/public/assets/beancount-D-usSTwE.js +1 -0
  76. package/public/assets/berry-DKpUyyne.js +1 -0
  77. package/public/assets/bibtex-Ci_nEsc7.js +1 -0
  78. package/public/assets/bicep-CUHmPFLl.js +1 -0
  79. package/public/assets/bird2-CYIUgYqB.js +1 -0
  80. package/public/assets/blade-Cy5u_P69.js +1 -0
  81. package/public/assets/bsl-_VjlB9mv.js +1 -0
  82. package/public/assets/c-xsYtGME5.js +1 -0
  83. package/public/assets/c3-D0apG41V.js +1 -0
  84. package/public/assets/cadence-CQ2zXKGN.js +1 -0
  85. package/public/assets/cairo-DLTphjLi.js +1 -0
  86. package/public/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  87. package/public/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  88. package/public/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  89. package/public/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  90. package/public/assets/chapel-DlXZg0HF.js +1 -0
  91. package/public/assets/clarity-CtfBBspg.js +1 -0
  92. package/public/assets/clojure-DqKBuwfJ.js +1 -0
  93. package/public/assets/cmake-Bj61d0ZC.js +1 -0
  94. package/public/assets/cobol-NNONwiOX.js +1 -0
  95. package/public/assets/codeowners-C8r90Shi.js +1 -0
  96. package/public/assets/codeql-oeQT6MSM.js +1 -0
  97. package/public/assets/coffee-O853MBTp.js +1 -0
  98. package/public/assets/common-lisp-Cv5bFMCO.js +1 -0
  99. package/public/assets/coq-DuBRPxYg.js +1 -0
  100. package/public/assets/cpp-DtWY3seq.js +1 -0
  101. package/public/assets/crystal-CcZ3zkyx.js +1 -0
  102. package/public/assets/csharp-oqKa8noW.js +1 -0
  103. package/public/assets/css-mSNQLTOb.js +1 -0
  104. package/public/assets/csv-Dx-8-gkx.js +1 -0
  105. package/public/assets/cue-CE9AQfxI.js +1 -0
  106. package/public/assets/cypher-ClKdZ_lG.js +1 -0
  107. package/public/assets/d-qD-0Kul2.js +1 -0
  108. package/public/assets/dark-plus-Cs2F2srj.js +1 -0
  109. package/public/assets/dart-CnvKMtbv.js +1 -0
  110. package/public/assets/dax-BkyTk9wS.js +1 -0
  111. package/public/assets/desktop-Dlh5hvp9.js +1 -0
  112. package/public/assets/diff-woXpYk--.js +1 -0
  113. package/public/assets/docker-IyjqRm3v.js +1 -0
  114. package/public/assets/dotenv-_5a1GRtc.js +1 -0
  115. package/public/assets/dracula-BHWKrbxM.js +1 -0
  116. package/public/assets/dracula-soft-5eyTD99u.js +1 -0
  117. package/public/assets/dream-maker-DW3nJb8Q.js +1 -0
  118. package/public/assets/edge-BA0HOmx0.js +1 -0
  119. package/public/assets/elixir-Buj0VxhK.js +1 -0
  120. package/public/assets/elm-lVH2iZsu.js +1 -0
  121. package/public/assets/emacs-lisp-c_oH4hRZ.js +1 -0
  122. package/public/assets/erb-CbQPls-2.js +1 -0
  123. package/public/assets/erlang-Cphh6RMH.js +1 -0
  124. package/public/assets/everforest-dark-sB-x3p7T.js +1 -0
  125. package/public/assets/everforest-light-Df2xbC6M.js +1 -0
  126. package/public/assets/fennel-DQxkIbk2.js +1 -0
  127. package/public/assets/fish-BJitypiv.js +1 -0
  128. package/public/assets/fluent-C03EYrpw.js +1 -0
  129. package/public/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  130. package/public/assets/fortran-free-form-CYNrtFtB.js +1 -0
  131. package/public/assets/fsharp-D13ZGOAj.js +1 -0
  132. package/public/assets/gdresource-xMC7jr7x.js +1 -0
  133. package/public/assets/gdscript-DYvRD3hg.js +1 -0
  134. package/public/assets/gdshader-CBce3t8t.js +1 -0
  135. package/public/assets/genie-CV2tkWYe.js +1 -0
  136. package/public/assets/gherkin-DExj1W_8.js +1 -0
  137. package/public/assets/git-commit-BSykSTBG.js +1 -0
  138. package/public/assets/git-rebase-DidgtTB0.js +1 -0
  139. package/public/assets/github-dark-C-LZuMrd.js +1 -0
  140. package/public/assets/github-dark-default-DXG-b-1a.js +1 -0
  141. package/public/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  142. package/public/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  143. package/public/assets/github-light-EUqPIrTm.js +1 -0
  144. package/public/assets/github-light-default-BXViO-2h.js +1 -0
  145. package/public/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  146. package/public/assets/gleam-CSRkHgEL.js +1 -0
  147. package/public/assets/glimmer-js-Cck5MdS-.js +1 -0
  148. package/public/assets/glimmer-ts-DMfQxyYr.js +1 -0
  149. package/public/assets/glsl-DPBFrb1h.js +1 -0
  150. package/public/assets/gn-ilITqXS6.js +1 -0
  151. package/public/assets/gnuplot-7GGW24-e.js +1 -0
  152. package/public/assets/go-rLFTqkRN.js +1 -0
  153. package/public/assets/graphql-BvPubeCr.js +1 -0
  154. package/public/assets/groovy-CacY0gHj.js +1 -0
  155. package/public/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  156. package/public/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  157. package/public/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  158. package/public/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  159. package/public/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  160. package/public/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  161. package/public/assets/hack-Drv5y81z.js +1 -0
  162. package/public/assets/haml-DcpINEk-.js +1 -0
  163. package/public/assets/handlebars-sj4UAwHW.js +1 -0
  164. package/public/assets/haskell-D8IpX4py.js +1 -0
  165. package/public/assets/haxe-C8oCnwJJ.js +1 -0
  166. package/public/assets/hcl-Dh228itO.js +1 -0
  167. package/public/assets/hjson-CxZEssPk.js +1 -0
  168. package/public/assets/hlsl-Cvrh5tZx.js +1 -0
  169. package/public/assets/horizon-CE9ld1lL.js +1 -0
  170. package/public/assets/horizon-bright-DSNQnXHK.js +1 -0
  171. package/public/assets/houston-CsvMBhTu.js +1 -0
  172. package/public/assets/html-DQjDPZbg.js +1 -0
  173. package/public/assets/html-derivative-DscSYlel.js +1 -0
  174. package/public/assets/http-CCrfmrUy.js +1 -0
  175. package/public/assets/hurl-DLm6Xdy2.js +1 -0
  176. package/public/assets/hxml-Dcm88srW.js +1 -0
  177. package/public/assets/hy-CZbG8q4J.js +1 -0
  178. package/public/assets/imba-DsUTQ-LC.js +1 -0
  179. package/public/assets/index-BDRMh7Hy.js +2918 -0
  180. package/public/assets/index-Cw-82hD0.css +1 -0
  181. package/public/assets/ini-B5eOa1yu.js +1 -0
  182. package/public/assets/java-CzpiG9dn.js +1 -0
  183. package/public/assets/javascript-2eedadNh.js +1 -0
  184. package/public/assets/jinja-Cch1Cycs.js +1 -0
  185. package/public/assets/jison-C3DQW9A-.js +1 -0
  186. package/public/assets/json-BhBCwuxI.js +1 -0
  187. package/public/assets/json5-BR5RXkoi.js +1 -0
  188. package/public/assets/jsonc-CYpm1nAK.js +1 -0
  189. package/public/assets/jsonl-CmCQp5Yx.js +1 -0
  190. package/public/assets/jsonnet-CJTPZ8u_.js +1 -0
  191. package/public/assets/jssm-DXw9l8Rf.js +1 -0
  192. package/public/assets/jsx-De1vU88o.js +1 -0
  193. package/public/assets/julia-BlJzw_Rq.js +1 -0
  194. package/public/assets/just-A2qsO4cM.js +1 -0
  195. package/public/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  196. package/public/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  197. package/public/assets/kanagawa-wave-BML5mF2J.js +1 -0
  198. package/public/assets/kdl-CsD5j6eV.js +1 -0
  199. package/public/assets/kotlin-DhhofPvG.js +1 -0
  200. package/public/assets/kusto-C7mF5XQf.js +1 -0
  201. package/public/assets/laserwave-C_8bwKvT.js +1 -0
  202. package/public/assets/latex-CDaOJk0y.js +1 -0
  203. package/public/assets/lean-CewbzKMR.js +1 -0
  204. package/public/assets/less-DVTAwKKz.js +1 -0
  205. package/public/assets/light-plus-DVQuIRkW.js +1 -0
  206. package/public/assets/liquid-BOnT7KYX.js +1 -0
  207. package/public/assets/llvm-DqHOMS-s.js +1 -0
  208. package/public/assets/log-BNLmms1o.js +1 -0
  209. package/public/assets/logo-Cluzi2Zq.js +1 -0
  210. package/public/assets/lottie-BT9QSO-G.js +2 -0
  211. package/public/assets/lua-CoLL9qBE.js +1 -0
  212. package/public/assets/luau-C2pBCSgt.js +1 -0
  213. package/public/assets/make-Dixweg8N.js +1 -0
  214. package/public/assets/markdown-BYOwaDjH.js +1 -0
  215. package/public/assets/marko-D8Xuri3Y.js +1 -0
  216. package/public/assets/material-theme-Bm3Qr25_.js +1 -0
  217. package/public/assets/material-theme-darker-2IIEA8gg.js +1 -0
  218. package/public/assets/material-theme-lighter-uhdI0v04.js +1 -0
  219. package/public/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  220. package/public/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  221. package/public/assets/matlab-D7qyCx1q.js +1 -0
  222. package/public/assets/mdc-BhVsbv7E.js +1 -0
  223. package/public/assets/mdx-DQZ5AkYe.js +1 -0
  224. package/public/assets/mermaid-BxpfTIzt.js +1 -0
  225. package/public/assets/min-dark-BSWPekZh.js +1 -0
  226. package/public/assets/min-light-DDpmG2fV.js +1 -0
  227. package/public/assets/mipsasm-BMqwQI7S.js +1 -0
  228. package/public/assets/mojo-fg40_ntQ.js +1 -0
  229. package/public/assets/monokai-B_bV9PQ9.js +1 -0
  230. package/public/assets/moonbit-DgjMoLWr.js +1 -0
  231. package/public/assets/move-BYnGhZoM.js +1 -0
  232. package/public/assets/narrat-_X_XdTYD.js +1 -0
  233. package/public/assets/nextflow-BJtWHP5T.js +1 -0
  234. package/public/assets/nextflow-groovy-DJMQeKeT.js +1 -0
  235. package/public/assets/nginx-CN1bxnHs.js +1 -0
  236. package/public/assets/night-owl-DhmEMT88.js +1 -0
  237. package/public/assets/night-owl-light-eJ-hLW7d.js +1 -0
  238. package/public/assets/nim-BKDc-Eam.js +1 -0
  239. package/public/assets/nix-IvuFDN5E.js +1 -0
  240. package/public/assets/nord-Cb4Vim4T.js +1 -0
  241. package/public/assets/nsis-CjNLYCLg.js +1 -0
  242. package/public/assets/nushell-DHYfO66s.js +1 -0
  243. package/public/assets/objective-c-D1A_Heim.js +1 -0
  244. package/public/assets/objective-cpp-BsSzOQcm.js +1 -0
  245. package/public/assets/ocaml-O90oeIOV.js +1 -0
  246. package/public/assets/odin-B1RWQWA5.js +1 -0
  247. package/public/assets/one-dark-pro-CLwyXe_n.js +1 -0
  248. package/public/assets/one-light-D7Lr4KcI.js +1 -0
  249. package/public/assets/openscad-BUDT5pXO.js +1 -0
  250. package/public/assets/org-CBldiceC.js +1 -0
  251. package/public/assets/pascal-4ZHwLPI5.js +1 -0
  252. package/public/assets/perl-KiFGXvwB.js +1 -0
  253. package/public/assets/php-CjFYgW_Q.js +1 -0
  254. package/public/assets/pierre-dark-CpLgRqie.js +1 -0
  255. package/public/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  256. package/public/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  257. package/public/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  258. package/public/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  259. package/public/assets/pierre-light-CoaEpmwp.js +1 -0
  260. package/public/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  261. package/public/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  262. package/public/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  263. package/public/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  264. package/public/assets/pkl-ot-7Btpt.js +1 -0
  265. package/public/assets/plastic-DQwYfKfQ.js +1 -0
  266. package/public/assets/plsql-DGHpHOYJ.js +1 -0
  267. package/public/assets/po-BiJDBrnU.js +1 -0
  268. package/public/assets/poimandres-DRFjx7u4.js +1 -0
  269. package/public/assets/polar-C7UOKdEL.js +1 -0
  270. package/public/assets/postcss-BXeXVLqQ.js +1 -0
  271. package/public/assets/powerquery-DNMTfnFr.js +1 -0
  272. package/public/assets/powershell-CCSX75yM.js +1 -0
  273. package/public/assets/prisma-DMgNhnYS.js +1 -0
  274. package/public/assets/prolog-iXnhIJG7.js +1 -0
  275. package/public/assets/proto-DB4EqR-F.js +1 -0
  276. package/public/assets/pug-CiQNzZtF.js +1 -0
  277. package/public/assets/puppet-CDv2pdJW.js +1 -0
  278. package/public/assets/purescript-9MfHhQsQ.js +1 -0
  279. package/public/assets/python-gzcpVVnB.js +1 -0
  280. package/public/assets/qml-BynhyZxj.js +1 -0
  281. package/public/assets/qmldir-DCQb3MpD.js +1 -0
  282. package/public/assets/qss-Fe1Jh2GI.js +1 -0
  283. package/public/assets/r-BydfSESc.js +1 -0
  284. package/public/assets/racket-DcIDlBhZ.js +1 -0
  285. package/public/assets/raku-B3gFvitq.js +1 -0
  286. package/public/assets/razor-Bfi-c6ag.js +1 -0
  287. package/public/assets/rbs-C0MpOrj4.js +1 -0
  288. package/public/assets/red-DX1BQOs8.js +1 -0
  289. package/public/assets/reg-CRGYupPL.js +1 -0
  290. package/public/assets/regexp-BFpups_T.js +1 -0
  291. package/public/assets/rel-BtDbiS_P.js +1 -0
  292. package/public/assets/riscv-Ckw8ddFX.js +1 -0
  293. package/public/assets/ron-VUp2lXgN.js +1 -0
  294. package/public/assets/rose-pine-aMsaMD1D.js +1 -0
  295. package/public/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  296. package/public/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  297. package/public/assets/rosmsg-CAekHB0j.js +1 -0
  298. package/public/assets/rst-Z5_5o5cf.js +1 -0
  299. package/public/assets/ruby-DyP6QZ5x.js +1 -0
  300. package/public/assets/rust-Cfkwpbl8.js +1 -0
  301. package/public/assets/sas-DzcEtbFW.js +1 -0
  302. package/public/assets/sass-DXrisJhu.js +1 -0
  303. package/public/assets/scala-BiFt_7mu.js +1 -0
  304. package/public/assets/scheme-DQCgrYNe.js +1 -0
  305. package/public/assets/scss-BVi50TCh.js +1 -0
  306. package/public/assets/sdbl-bTVj8UrX.js +1 -0
  307. package/public/assets/shaderlab-TOUzSsQk.js +1 -0
  308. package/public/assets/shellscript-DwBEbT4l.js +1 -0
  309. package/public/assets/shellsession-DtCkQKX0.js +1 -0
  310. package/public/assets/slack-dark-DnToyrRv.js +1 -0
  311. package/public/assets/slack-ochin-B2OO5cIa.js +1 -0
  312. package/public/assets/smalltalk-BHpQb5nM.js +1 -0
  313. package/public/assets/smithy-Dgaz7aur.js +1 -0
  314. package/public/assets/snazzy-light-4G7pJPwS.js +1 -0
  315. package/public/assets/solarized-dark-UdsmMDCL.js +1 -0
  316. package/public/assets/solarized-light-C1IUL_tW.js +1 -0
  317. package/public/assets/solidity-Bj7E1mRn.js +1 -0
  318. package/public/assets/soy-Col_eH-0.js +1 -0
  319. package/public/assets/sparql-D_iOobhT.js +1 -0
  320. package/public/assets/splunk-BC2Px7Mm.js +1 -0
  321. package/public/assets/sql-CF1geVQJ.js +1 -0
  322. package/public/assets/ssh-config-BgfXC-Er.js +1 -0
  323. package/public/assets/stata-JT7HPvq1.js +1 -0
  324. package/public/assets/stylus-B6D30XZt.js +1 -0
  325. package/public/assets/surrealql-kYIUYZug.js +1 -0
  326. package/public/assets/svelte-CsWZn_FS.js +1 -0
  327. package/public/assets/swift-CyEgAFGc.js +1 -0
  328. package/public/assets/synthwave-84-nFMaYfgc.js +1 -0
  329. package/public/assets/system-verilog-Dh5ihWbf.js +1 -0
  330. package/public/assets/systemd-BxMlprV5.js +1 -0
  331. package/public/assets/talonscript-CohzipZa.js +1 -0
  332. package/public/assets/tasl-DMoTqEGO.js +1 -0
  333. package/public/assets/tcl-CZd0xW_V.js +1 -0
  334. package/public/assets/templ-DCsT8WEJ.js +1 -0
  335. package/public/assets/terraform-DswuEJGm.js +1 -0
  336. package/public/assets/tex-ZyN9TW16.js +1 -0
  337. package/public/assets/tokyo-night-oM2G3aXe.js +1 -0
  338. package/public/assets/toml-CcmNWLt0.js +1 -0
  339. package/public/assets/ts-tags-ArLnwaTi.js +1 -0
  340. package/public/assets/tsv-sltzmVWM.js +1 -0
  341. package/public/assets/tsx-CXwa5__r.js +1 -0
  342. package/public/assets/turtle-ByJddavk.js +1 -0
  343. package/public/assets/twig-BsBKj8BU.js +1 -0
  344. package/public/assets/typescript-Brm8t4pO.js +1 -0
  345. package/public/assets/typespec-BRdr0IET.js +1 -0
  346. package/public/assets/typst-vNjrO6QE.js +1 -0
  347. package/public/assets/v-DBj4K9d_.js +1 -0
  348. package/public/assets/vala-zf12oZj6.js +1 -0
  349. package/public/assets/vb-DOwzaHZ0.js +1 -0
  350. package/public/assets/verilog-B2gwz-sJ.js +1 -0
  351. package/public/assets/vesper-DdrHHSXu.js +1 -0
  352. package/public/assets/vhdl-BroJfC0k.js +1 -0
  353. package/public/assets/viml-DvXPmvsu.js +1 -0
  354. package/public/assets/vitesse-black-fwtXNY1n.js +1 -0
  355. package/public/assets/vitesse-dark-BZCL-v6S.js +1 -0
  356. package/public/assets/vitesse-light-VbXTXTou.js +1 -0
  357. package/public/assets/vue-CcPq89qx.js +1 -0
  358. package/public/assets/vue-html-B5qW2N7E.js +1 -0
  359. package/public/assets/vue-vine-LYElP1mf.js +1 -0
  360. package/public/assets/vyper-CgoNMtux.js +1 -0
  361. package/public/assets/wasm-BnjxR4X6.js +1 -0
  362. package/public/assets/wasm-ByWQv1Qj.js +1 -0
  363. package/public/assets/wenyan-C8pVoKbM.js +1 -0
  364. package/public/assets/wgsl-BsKzXJz4.js +1 -0
  365. package/public/assets/wikitext-ClFFjSW2.js +1 -0
  366. package/public/assets/wit-DdvCle-K.js +1 -0
  367. package/public/assets/wolfram-DLL8P-h_.js +1 -0
  368. package/public/assets/xml-D5hvN78Q.js +1 -0
  369. package/public/assets/xsl-CBSZ35CX.js +1 -0
  370. package/public/assets/yaml-D4FTZq40.js +1 -0
  371. package/public/assets/zenscript-BnlCZFoB.js +1 -0
  372. package/public/assets/zig-CMLA9XwU.js +1 -0
  373. package/public/brand/coja/cj-dark.svg +1 -0
  374. package/public/brand/coja/cj-light.svg +1 -0
  375. package/public/brand/coja/cj-themeable.svg +1 -0
  376. package/public/brand/coja/lottie/cj-huge-dark.json +311 -0
  377. package/public/brand/coja/lottie/cj-huge-light.json +311 -0
  378. package/public/brand/coja/lottie/cj-large-dark.json +311 -0
  379. package/public/brand/coja/lottie/cj-large-light.json +311 -0
  380. package/public/brand/coja/lottie/cj-medium-dark.json +311 -0
  381. package/public/brand/coja/lottie/cj-medium-light.json +311 -0
  382. package/public/brand/coja/lottie/cj-small-dark.json +311 -0
  383. package/public/brand/coja/lottie/cj-small-light.json +311 -0
  384. package/public/brand/coja/manifest.json +172 -0
  385. package/public/brand/coja/themes/ember/dark/cj-huge.json +311 -0
  386. package/public/brand/coja/themes/ember/dark/cj-large.json +311 -0
  387. package/public/brand/coja/themes/ember/dark/cj-medium.json +311 -0
  388. package/public/brand/coja/themes/ember/dark/cj-small.json +311 -0
  389. package/public/brand/coja/themes/ember/dark/cj.svg +1 -0
  390. package/public/brand/coja/themes/ember/light/cj-huge.json +311 -0
  391. package/public/brand/coja/themes/ember/light/cj-large.json +311 -0
  392. package/public/brand/coja/themes/ember/light/cj-medium.json +311 -0
  393. package/public/brand/coja/themes/ember/light/cj-small.json +311 -0
  394. package/public/brand/coja/themes/ember/light/cj.svg +1 -0
  395. package/public/brand/coja/themes/grove/dark/cj-huge.json +311 -0
  396. package/public/brand/coja/themes/grove/dark/cj-large.json +311 -0
  397. package/public/brand/coja/themes/grove/dark/cj-medium.json +311 -0
  398. package/public/brand/coja/themes/grove/dark/cj-small.json +311 -0
  399. package/public/brand/coja/themes/grove/dark/cj.svg +1 -0
  400. package/public/brand/coja/themes/grove/light/cj-huge.json +311 -0
  401. package/public/brand/coja/themes/grove/light/cj-large.json +311 -0
  402. package/public/brand/coja/themes/grove/light/cj-medium.json +311 -0
  403. package/public/brand/coja/themes/grove/light/cj-small.json +311 -0
  404. package/public/brand/coja/themes/grove/light/cj.svg +1 -0
  405. package/public/brand/coja/themes/iris/dark/cj-huge.json +311 -0
  406. package/public/brand/coja/themes/iris/dark/cj-large.json +311 -0
  407. package/public/brand/coja/themes/iris/dark/cj-medium.json +311 -0
  408. package/public/brand/coja/themes/iris/dark/cj-small.json +311 -0
  409. package/public/brand/coja/themes/iris/dark/cj.svg +1 -0
  410. package/public/brand/coja/themes/iris/light/cj-huge.json +311 -0
  411. package/public/brand/coja/themes/iris/light/cj-large.json +311 -0
  412. package/public/brand/coja/themes/iris/light/cj-medium.json +311 -0
  413. package/public/brand/coja/themes/iris/light/cj-small.json +311 -0
  414. package/public/brand/coja/themes/iris/light/cj.svg +1 -0
  415. package/public/brand/coja/themes/mulberry/dark/cj-huge.json +311 -0
  416. package/public/brand/coja/themes/mulberry/dark/cj-large.json +311 -0
  417. package/public/brand/coja/themes/mulberry/dark/cj-medium.json +311 -0
  418. package/public/brand/coja/themes/mulberry/dark/cj-small.json +311 -0
  419. package/public/brand/coja/themes/mulberry/dark/cj.svg +1 -0
  420. package/public/brand/coja/themes/mulberry/light/cj-huge.json +311 -0
  421. package/public/brand/coja/themes/mulberry/light/cj-large.json +311 -0
  422. package/public/brand/coja/themes/mulberry/light/cj-medium.json +311 -0
  423. package/public/brand/coja/themes/mulberry/light/cj-small.json +311 -0
  424. package/public/brand/coja/themes/mulberry/light/cj.svg +1 -0
  425. package/public/brand/coja/themes/ocean/dark/cj-huge.json +311 -0
  426. package/public/brand/coja/themes/ocean/dark/cj-large.json +311 -0
  427. package/public/brand/coja/themes/ocean/dark/cj-medium.json +311 -0
  428. package/public/brand/coja/themes/ocean/dark/cj-small.json +311 -0
  429. package/public/brand/coja/themes/ocean/dark/cj.svg +1 -0
  430. package/public/brand/coja/themes/ocean/light/cj-huge.json +311 -0
  431. package/public/brand/coja/themes/ocean/light/cj-large.json +311 -0
  432. package/public/brand/coja/themes/ocean/light/cj-medium.json +311 -0
  433. package/public/brand/coja/themes/ocean/light/cj-small.json +311 -0
  434. package/public/brand/coja/themes/ocean/light/cj.svg +1 -0
  435. package/public/fonts/ibm-plex-sans/IBMPlexSans-Bold.woff2 +0 -0
  436. package/public/fonts/ibm-plex-sans/IBMPlexSans-BoldItalic.woff2 +0 -0
  437. package/public/fonts/ibm-plex-sans/IBMPlexSans-Italic.woff2 +0 -0
  438. package/public/fonts/ibm-plex-sans/IBMPlexSans-Medium.woff2 +0 -0
  439. package/public/fonts/ibm-plex-sans/IBMPlexSans-MediumItalic.woff2 +0 -0
  440. package/public/fonts/ibm-plex-sans/IBMPlexSans-Regular.woff2 +0 -0
  441. package/public/fonts/ibm-plex-sans/IBMPlexSans-SemiBold.woff2 +0 -0
  442. package/public/fonts/ibm-plex-sans/IBMPlexSans-SemiBoldItalic.woff2 +0 -0
  443. package/public/fonts/ibm-plex-sans/LICENSE.txt +93 -0
  444. package/public/fonts/ibm-plex-sans/PROVENANCE.md +10 -0
  445. package/public/fonts/maple-mono-nf/LICENSE.txt +93 -0
  446. package/public/fonts/maple-mono-nf/MapleMono-NF-Bold.woff2 +0 -0
  447. package/public/fonts/maple-mono-nf/MapleMono-NF-BoldItalic.woff2 +0 -0
  448. package/public/fonts/maple-mono-nf/MapleMono-NF-Italic.woff2 +0 -0
  449. package/public/fonts/maple-mono-nf/MapleMono-NF-Medium.woff2 +0 -0
  450. package/public/fonts/maple-mono-nf/MapleMono-NF-MediumItalic.woff2 +0 -0
  451. package/public/fonts/maple-mono-nf/MapleMono-NF-Regular.woff2 +0 -0
  452. package/public/fonts/maple-mono-nf/MapleMono-NF-SemiBold.woff2 +0 -0
  453. package/public/fonts/maple-mono-nf/MapleMono-NF-SemiBoldItalic.woff2 +0 -0
  454. package/public/fonts/maple-mono-nf/PROVENANCE.md +12 -0
  455. package/public/index.html +14 -0
@@ -0,0 +1,256 @@
1
+ import { execFile, spawn } from 'node:child_process';
2
+ import { badRequest } from '../routes/http.js';
3
+ /**
4
+ * The only module that spawns git.
5
+ *
6
+ * Every invocation is `execFile('git', argv)` (or `spawn` when stdout is
7
+ * streamed) — an argv array, never a shell — with prompts disabled and stable,
8
+ * untranslated output. Callers hand over arguments as data; pathspecs are
9
+ * validated with {@link assertPathspec} and always placed after a `--`,
10
+ * revisions with {@link assertRevision}, so no user-controlled string can ever
11
+ * be read as an option.
12
+ */
13
+ /** 64 MiB: enough for any patch or file we are willing to hold in memory. */
14
+ export const DEFAULT_MAX_BUFFER = 64 * 1024 * 1024;
15
+ /** Stderr kept on a GitError is capped so a runaway process cannot bloat error responses. */
16
+ const MAX_STDERR_CHARS = 4000;
17
+ /** A git invocation that failed: non-zero exit, timeout, abort, or git missing. */
18
+ export class GitError extends Error {
19
+ args;
20
+ exitCode;
21
+ stderr;
22
+ name = 'GitError';
23
+ constructor(
24
+ /** The argv git was started with (never includes secrets: credentials come from helpers). */
25
+ args,
26
+ /** Exit code, or null when git did not exit normally (killed, aborted, not found). */
27
+ exitCode, stderr, message) {
28
+ super(message);
29
+ this.args = args;
30
+ this.exitCode = exitCode;
31
+ this.stderr = stderr;
32
+ }
33
+ }
34
+ /**
35
+ * Run `git -C <repoPath> <args>` and resolve with its output. Rejects with a
36
+ * GitError, or with a 400 HttpError when `repoPath` could be read as an option.
37
+ */
38
+ export async function runGit(repoPath, args, opts = {}) {
39
+ return spawnGit(['-C', assertRepoPath(repoPath), ...args], opts);
40
+ }
41
+ /**
42
+ * Run `git <args>` without a repository (used for `git clone` and `git --version`). Everything
43
+ * else operates on an existing repository and must use {@link runGit}.
44
+ */
45
+ export async function runGitGlobal(args, opts = {}) {
46
+ return spawnGit(args, opts);
47
+ }
48
+ /**
49
+ * Run `git -C <repoPath> <args>` and hand stdout to `sink` chunk by chunk
50
+ * instead of buffering it, for output that may not fit in memory (counting the
51
+ * lines of a huge blob). Resolves once git exited successfully; rejects with a
52
+ * GitError exactly like {@link runGit}.
53
+ */
54
+ export async function runGitStreaming(repoPath, args, sink, opts = {}) {
55
+ const argv = ['-C', assertRepoPath(repoPath), ...args];
56
+ await new Promise((resolve, reject) => {
57
+ const child = spawn('git', argv, {
58
+ env: gitEnv(opts.env),
59
+ windowsHide: true,
60
+ signal: opts.signal,
61
+ timeout: opts.timeoutMs ?? 0,
62
+ stdio: ['ignore', 'pipe', 'pipe'],
63
+ });
64
+ let stderr = '';
65
+ let failure;
66
+ let settled = false;
67
+ const fail = (err) => {
68
+ if (settled)
69
+ return;
70
+ settled = true;
71
+ reject(err);
72
+ };
73
+ child.stdout.on('data', (chunk) => {
74
+ try {
75
+ sink(chunk);
76
+ }
77
+ catch (err) {
78
+ child.kill();
79
+ fail(new GitError(argv, null, redact(stderr), err instanceof Error ? err.message : String(err)));
80
+ }
81
+ });
82
+ child.stderr.on('data', (chunk) => {
83
+ if (stderr.length < MAX_STDERR_CHARS)
84
+ stderr += chunk.toString('utf8');
85
+ });
86
+ child.on('error', (err) => {
87
+ failure = err;
88
+ // A spawn failure (git missing) never reaches 'close'; an abort does, once git has exited.
89
+ if (child.pid === undefined)
90
+ fail(toGitError(argv, failure, redact(stderr)));
91
+ });
92
+ child.on('close', (code, signal) => {
93
+ if (settled)
94
+ return;
95
+ if (failure)
96
+ return fail(toGitError(argv, failure, redact(stderr)));
97
+ if (code === 0) {
98
+ settled = true;
99
+ resolve();
100
+ return;
101
+ }
102
+ fail(toGitError(argv, { code, killed: child.killed, signal }, redact(stderr)));
103
+ });
104
+ });
105
+ }
106
+ function spawnGit(argv, opts) {
107
+ const maxBuffer = opts.maxBuffer ?? DEFAULT_MAX_BUFFER;
108
+ return new Promise((resolve, reject) => {
109
+ const child = execFile('git', [...argv], {
110
+ encoding: 'buffer',
111
+ maxBuffer,
112
+ timeout: opts.timeoutMs ?? 0,
113
+ signal: opts.signal,
114
+ windowsHide: true,
115
+ env: gitEnv(opts.env),
116
+ }, (error, stdout, stderr) => {
117
+ const settle = () => {
118
+ const stderrText = redact(stderr.toString('utf8'));
119
+ if (!error) {
120
+ resolve({ stdout, stderr: stderrText, exitCode: 0, truncated: false });
121
+ return;
122
+ }
123
+ const err = error;
124
+ if (err.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER' && opts.truncateStdout) {
125
+ resolve({ stdout, stderr: stderrText, exitCode: 0, truncated: true });
126
+ return;
127
+ }
128
+ if (typeof err.code === 'number' && opts.okExitCodes?.includes(err.code)) {
129
+ resolve({ stdout, stderr: stderrText, exitCode: err.code, truncated: false });
130
+ return;
131
+ }
132
+ reject(toGitError(argv, err, stderrText));
133
+ };
134
+ // On abort, execFile reports before git has actually exited. Wait for it, so a caller
135
+ // that removes a half-written clone afterwards is not racing a still-running git.
136
+ if (error &&
137
+ isAbort(error) &&
138
+ child.exitCode === null &&
139
+ child.signalCode === null) {
140
+ child.once('close', settle);
141
+ }
142
+ else {
143
+ settle();
144
+ }
145
+ });
146
+ // git may exit before reading stdin (EPIPE); that is not an error for us.
147
+ child.stdin?.on('error', () => { });
148
+ if (opts.input === undefined)
149
+ child.stdin?.end();
150
+ else
151
+ child.stdin?.end(opts.input);
152
+ });
153
+ }
154
+ /** The environment every git child gets: the caller's, plus the fixed settings we rely on. */
155
+ function gitEnv(extra) {
156
+ return {
157
+ ...process.env,
158
+ ...extra,
159
+ // Never block on a username/password prompt; fail instead.
160
+ GIT_TERMINAL_PROMPT: '0',
161
+ // Untranslated, byte-stable messages and output.
162
+ LC_ALL: 'C',
163
+ // Read-only commands must not take the index lock in the user's checkout.
164
+ GIT_OPTIONAL_LOCKS: '0',
165
+ };
166
+ }
167
+ const isAbort = (err) => err.code === 'ABORT_ERR' || err.name === 'AbortError';
168
+ function toGitError(argv, err, stderr) {
169
+ const cmd = `git ${subcommand(argv)}`;
170
+ const detail = stderr.trim().split('\n').filter(Boolean).pop();
171
+ const suffix = detail ? `: ${detail}` : '';
172
+ if (err.code === 'ENOENT') {
173
+ return new GitError(argv, null, stderr, 'git is not installed or not on PATH');
174
+ }
175
+ if (isAbort(err)) {
176
+ return new GitError(argv, null, stderr, `${cmd} was aborted`);
177
+ }
178
+ if (err.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
179
+ return new GitError(argv, null, stderr, `${cmd} produced more output than allowed`);
180
+ }
181
+ if (err.killed || (err.signal && typeof err.code !== 'number')) {
182
+ return new GitError(argv, null, stderr, `${cmd} timed out (${err.signal ?? 'killed'})${suffix}`);
183
+ }
184
+ const exitCode = typeof err.code === 'number' ? err.code : null;
185
+ return new GitError(argv, exitCode, stderr, `${cmd} failed (exit ${exitCode ?? '?'})${suffix}`);
186
+ }
187
+ /** The git subcommand in an argv, skipping global options such as `-C <path>` and `-c k=v`. */
188
+ function subcommand(argv) {
189
+ for (let i = 0; i < argv.length; i++) {
190
+ const arg = argv[i];
191
+ if (arg === '-C' || arg === '-c') {
192
+ i++;
193
+ continue;
194
+ }
195
+ if (arg === undefined || arg.startsWith('-'))
196
+ continue;
197
+ return arg;
198
+ }
199
+ return '';
200
+ }
201
+ /** Strip `user:password@` from URLs and cap the length; stderr ends up in API responses. */
202
+ function redact(text) {
203
+ const clean = text.replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, '$1***@');
204
+ return clean.length > MAX_STDERR_CHARS ? `${clean.slice(0, MAX_STDERR_CHARS)}…` : clean;
205
+ }
206
+ function assertRepoPath(repoPath) {
207
+ if (repoPath.startsWith('-'))
208
+ throw badRequest('repository path must not start with "-"');
209
+ return repoPath;
210
+ }
211
+ /**
212
+ * Validate a repository-relative path before it is handed to git as a pathspec
213
+ * or as the `<path>` in `<rev>:<path>`. Returns the path unchanged.
214
+ *
215
+ * Rejects (HTTP 400): empty, control characters, a leading `-` (would be an
216
+ * option), a leading `:` (pathspec magic), absolute paths, and `.`/`..`
217
+ * segments. A single trailing slash is allowed (directory pathspecs).
218
+ */
219
+ export function assertPathspec(p) {
220
+ if (typeof p !== 'string' || p.length === 0)
221
+ throw badRequest('path is required');
222
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: rejecting control characters is the point
223
+ if (/[\0-\x1f\x7f]/.test(p))
224
+ throw badRequest('path contains control characters');
225
+ if (p.startsWith('-'))
226
+ throw badRequest(`path must not start with "-": ${p}`);
227
+ if (p.startsWith(':'))
228
+ throw badRequest(`path must not start with ":": ${p}`);
229
+ if (p.startsWith('/') || p.startsWith('\\') || /^[A-Za-z]:[\\/]/.test(p)) {
230
+ throw badRequest(`path must be relative to the repository root: ${p}`);
231
+ }
232
+ const segments = p.split('/');
233
+ segments.forEach((segment, i) => {
234
+ if (segment === '..')
235
+ throw badRequest(`path must not contain "..": ${p}`);
236
+ if (segment === '.')
237
+ throw badRequest(`path must not contain "." segments: ${p}`);
238
+ if (segment === '' && i !== segments.length - 1) {
239
+ throw badRequest(`path must not contain empty segments: ${p}`);
240
+ }
241
+ });
242
+ return p;
243
+ }
244
+ /**
245
+ * Validate a revision argument (an oid, a ref name, or a range such as `a..b`)
246
+ * so it can never be parsed as an option. Returns it unchanged.
247
+ */
248
+ export function assertRevision(rev) {
249
+ if (typeof rev !== 'string' || rev.length === 0)
250
+ throw badRequest('revision is required');
251
+ if (/[\0-\x20\x7f]/.test(rev))
252
+ throw badRequest('revision contains whitespace or control characters');
253
+ if (rev.startsWith('-'))
254
+ throw badRequest(`revision must not start with "-": ${rev}`);
255
+ return rev;
256
+ }
@@ -0,0 +1,27 @@
1
+ import { spawn } from 'node:child_process';
2
+ /**
3
+ * Open `url` in the user's default browser without any dependency.
4
+ * Best effort: failures are swallowed, the URL is printed anyway.
5
+ */
6
+ export function openBrowser(url) {
7
+ const [command, args] = commandFor(process.platform, url);
8
+ try {
9
+ const child = spawn(command, args, { stdio: 'ignore', detached: true });
10
+ child.on('error', () => { });
11
+ child.unref();
12
+ }
13
+ catch {
14
+ // ignore: the user can still open the printed URL by hand
15
+ }
16
+ }
17
+ function commandFor(platform, url) {
18
+ switch (platform) {
19
+ case 'darwin':
20
+ return ['open', [url]];
21
+ case 'win32':
22
+ // The empty string is the window title `start` would otherwise steal the URL for.
23
+ return ['cmd', ['/c', 'start', '', url]];
24
+ default:
25
+ return ['xdg-open', [url]];
26
+ }
27
+ }
package/dist/paths.js ADDED
@@ -0,0 +1,18 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ /**
5
+ * Absolute path of the `coja` package root (the directory holding package.json).
6
+ * Both `src/` (run via tsx) and `dist/` (built) sit one level below it, so the
7
+ * same relative hop works in either case.
8
+ */
9
+ export const packageRoot = fileURLToPath(new URL('..', import.meta.url));
10
+ /** Where the built web UI lives inside the published package. */
11
+ export function resolvePublicDir() {
12
+ return path.join(packageRoot, 'public');
13
+ }
14
+ export function packageVersion() {
15
+ const raw = readFileSync(path.join(packageRoot, 'package.json'), 'utf8');
16
+ const pkg = JSON.parse(raw);
17
+ return pkg.version;
18
+ }
@@ -0,0 +1,173 @@
1
+ import { nowIso } from '../db.js';
2
+ import { fetch as gitFetch, mergeBase, revParse } from '../git/plumbing.js';
3
+ import { GitError } from '../git/run.js';
4
+ import { githubPullHeadRef, prBaseRef, prHeadRef } from './refs.js';
5
+ const REMOTE = 'origin';
6
+ const key = (projectId, number) => `${projectId}#${number}`;
7
+ /**
8
+ * Brings a pull request's objects into a project's repository, lazily and in
9
+ * the background (design.md: "Fetch is lazy and non-blocking").
10
+ *
11
+ * `ensure` compares the namespaced refs with the oids GitHub reports and only
12
+ * fetches when they differ, so re-opening a PR whose head moved re-fetches
13
+ * while an unchanged PR is `ready` without touching the network. Status is
14
+ * kept in memory per `${projectId}#${number}`; the refs themselves persist in
15
+ * the repository.
16
+ *
17
+ * Requests are never dropped: when `ensure` is asked for other oids while a
18
+ * fetch is in flight, the newest oids are decided again as soon as that fetch
19
+ * finishes, so a head that moves mid-fetch still ends `ready` at the new oids.
20
+ */
21
+ export class PrFetcher {
22
+ statuses = new Map();
23
+ /** Background fetches, by key. */
24
+ inflight = new Map();
25
+ /** `ensure` calls still deciding whether to fetch, by key; concurrent callers share one. */
26
+ ensuring = new Map();
27
+ status(projectId, number) {
28
+ return this.statuses.get(key(projectId, number)) ?? { state: 'idle' };
29
+ }
30
+ /**
31
+ * Make sure `refs` are fetched. Returns immediately: `ready` when the
32
+ * repository already holds exactly these oids, otherwise `fetching` after
33
+ * starting a background fetch (or while one is running).
34
+ */
35
+ ensure(project, refs) {
36
+ const k = key(project.id, refs.number);
37
+ const running = this.inflight.get(k);
38
+ if (running) {
39
+ // The newest request wins; the oids already being fetched need nothing more.
40
+ running.next = sameOids(running.refs, refs) ? undefined : refs;
41
+ return Promise.resolve(this.status(project.id, refs.number));
42
+ }
43
+ const deciding = this.ensuring.get(k);
44
+ if (deciding) {
45
+ if (sameOids(deciding.refs, refs))
46
+ return deciding.done;
47
+ return deciding.done.then(() => this.ensure(project, refs));
48
+ }
49
+ return this.beginDecision(project, refs, k);
50
+ }
51
+ /** Resolve once nothing is pending or running for the PR (tests and eager callers). */
52
+ async waitFor(projectId, number) {
53
+ const k = key(projectId, number);
54
+ for (;;) {
55
+ const deciding = this.ensuring.get(k);
56
+ if (deciding) {
57
+ await deciding.done;
58
+ continue;
59
+ }
60
+ const running = this.inflight.get(k);
61
+ if (running) {
62
+ await running.done;
63
+ continue;
64
+ }
65
+ return this.status(projectId, number);
66
+ }
67
+ }
68
+ beginDecision(project, refs, k) {
69
+ const entry = {
70
+ refs,
71
+ done: this.decide(project, refs, k).finally(() => {
72
+ if (this.ensuring.get(k) === entry)
73
+ this.ensuring.delete(k);
74
+ }),
75
+ };
76
+ this.ensuring.set(k, entry);
77
+ return entry.done;
78
+ }
79
+ /** Never rejects: a failing check (e.g. no merge base) becomes an `error` status, like a failed fetch. */
80
+ async decide(project, refs, k) {
81
+ try {
82
+ const repo = project.path;
83
+ const n = refs.number;
84
+ const [head, base] = await Promise.all([
85
+ revParse(repo, prHeadRef(n)),
86
+ revParse(repo, prBaseRef(n)),
87
+ ]);
88
+ if (head && base && head === refs.headRefOid && base === refs.baseRefOid) {
89
+ const current = this.statuses.get(k);
90
+ if (current?.state === 'ready' && current.headOid === head && current.baseOid === base) {
91
+ return current;
92
+ }
93
+ const ready = {
94
+ state: 'ready',
95
+ headOid: head,
96
+ baseOid: base,
97
+ mergeBaseOid: await mergeBase(repo, base, head),
98
+ ...(current?.startedAt ? { startedAt: current.startedAt } : {}),
99
+ finishedAt: current?.finishedAt ?? nowIso(),
100
+ };
101
+ this.statuses.set(k, ready);
102
+ return ready;
103
+ }
104
+ return this.start(project, refs, k);
105
+ }
106
+ catch (err) {
107
+ const failed = { state: 'error', error: describe(err), finishedAt: nowIso() };
108
+ this.statuses.set(k, failed);
109
+ return failed;
110
+ }
111
+ }
112
+ start(project, refs, k) {
113
+ const startedAt = nowIso();
114
+ const fetching = { state: 'fetching', startedAt };
115
+ this.statuses.set(k, fetching);
116
+ const entry = {
117
+ refs,
118
+ done: this.runFetch(project, refs, startedAt).then((final) => {
119
+ this.statuses.set(k, final);
120
+ if (this.inflight.get(k) === entry)
121
+ this.inflight.delete(k);
122
+ // The oids changed while we fetched: check the repository against the newest ones.
123
+ if (entry.next)
124
+ void this.beginDecision(project, entry.next, k);
125
+ return final;
126
+ }),
127
+ };
128
+ this.inflight.set(k, entry);
129
+ return fetching;
130
+ }
131
+ /** Never rejects: failures become an `error` status. */
132
+ async runFetch(project, refs, startedAt) {
133
+ const repo = project.path;
134
+ const n = refs.number;
135
+ const headSpec = `+${githubPullHeadRef(n)}:${prHeadRef(n)}`;
136
+ try {
137
+ try {
138
+ // GitHub serves any reachable commit by full oid, so the base is pinned to
139
+ // exactly the oid the PR was compared against.
140
+ await gitFetch(repo, REMOTE, [headSpec, `+${refs.baseRefOid}:${prBaseRef(n)}`]);
141
+ }
142
+ catch (err) {
143
+ if (!(err instanceof GitError) || !isValidRefName(refs.baseRefName))
144
+ throw err;
145
+ // Servers that refuse wants by oid still serve the branch tip.
146
+ await gitFetch(repo, REMOTE, [headSpec, `+refs/heads/${refs.baseRefName}:${prBaseRef(n)}`]);
147
+ }
148
+ const [headOid, baseOid] = await Promise.all([
149
+ revParse(repo, prHeadRef(n)),
150
+ revParse(repo, prBaseRef(n)),
151
+ ]);
152
+ if (!headOid || !baseOid)
153
+ throw new Error(`refs for PR #${n} are missing after the fetch`);
154
+ const mergeBaseOid = await mergeBase(repo, baseOid, headOid);
155
+ return { state: 'ready', headOid, baseOid, mergeBaseOid, startedAt, finishedAt: nowIso() };
156
+ }
157
+ catch (err) {
158
+ return { state: 'error', error: describe(err), startedAt, finishedAt: nowIso() };
159
+ }
160
+ }
161
+ }
162
+ const sameOids = (a, b) => a.headRefOid === b.headRefOid && a.baseRefOid === b.baseRefOid;
163
+ function isValidRefName(name) {
164
+ return (name.length > 0 && !name.startsWith('-') && !/[\s~^:?*[\\]/.test(name) && !name.includes('..'));
165
+ }
166
+ /** Human-readable failure: git's stderr (already redacted by the runner) or the error message. */
167
+ function describe(err) {
168
+ if (err instanceof GitError) {
169
+ const stderr = err.stderr.trim();
170
+ return stderr ? `${err.message}\n${stderr}`.slice(0, 2000) : err.message;
171
+ }
172
+ return err instanceof Error ? err.message : String(err);
173
+ }
@@ -0,0 +1,83 @@
1
+ import { blobInfo, countLines, diff, diffNameStatus, showFile } from '../git/plumbing.js';
2
+ import { assertPathspec } from '../git/run.js';
3
+ import { badRequest, notFound } from '../routes/http.js';
4
+ /**
5
+ * Read-side views of a fetched pull request, all computed from git objects.
6
+ * `mergeBase` is the diff base (decisions.md), matching GitHub's own PR diff.
7
+ */
8
+ export function getChangedFiles(repo, mergeBase, head) {
9
+ return diffNameStatus(repo, mergeBase, head);
10
+ }
11
+ /**
12
+ * The unified patch for one file. Pass `previousPath` for renames so git sees
13
+ * both sides and can pair them; without it a rename shows as delete + add.
14
+ */
15
+ export async function getFileDiff(repo, mergeBase, head, path, previousPath) {
16
+ assertPathspec(path);
17
+ const paths = [path];
18
+ if (previousPath !== undefined && previousPath !== path)
19
+ paths.push(assertPathspec(previousPath));
20
+ const { patch, truncated } = await diff(repo, mergeBase, head, { paths });
21
+ const binary = !truncated && isBinaryPatch(patch);
22
+ return {
23
+ path,
24
+ ...(previousPath !== undefined ? { previousPath } : {}),
25
+ patch: binary || truncated ? '' : patch,
26
+ binary,
27
+ tooLarge: truncated,
28
+ };
29
+ }
30
+ const isBinaryPatch = (patch) => /^Binary files .* differ$/m.test(patch) || /^GIT binary patch$/m.test(patch);
31
+ /**
32
+ * A file's text at `oid`, optionally a 1-based inclusive line range (clamped
33
+ * to the file). 404 when the path does not exist at that commit; 400 for
34
+ * directories and binary files.
35
+ *
36
+ * Only the first `maxBytes` of a blob are read. When that cuts the file short,
37
+ * `text` keeps whole lines only (the cut line is dropped), `lineCount` is
38
+ * counted from the full blob, and `truncated` tells whether the requested text
39
+ * — the whole file or the range — reached past what was read.
40
+ */
41
+ export async function getBlob(repo, ref, oid, path, start, end, opts = {}) {
42
+ assertPathspec(path);
43
+ const info = await blobInfo(repo, oid, path);
44
+ if (!info.exists)
45
+ throw notFound(`${path} at ${ref}`);
46
+ if (info.type !== 'blob')
47
+ throw badRequest(`${path} is a ${info.type ?? 'non-file'}, not a file`);
48
+ const file = await showFile(repo, oid, path, opts.maxBytes === undefined ? {} : { maxBytes: opts.maxBytes });
49
+ if (file.binary)
50
+ throw badRequest(`${path} is a binary file`);
51
+ const lines = splitLines(file.text);
52
+ if (file.truncated && !file.text.endsWith('\n'))
53
+ lines.pop(); // the cut fell inside this line
54
+ const lineCount = file.truncated ? await countLines(repo, oid, path) : lines.length;
55
+ const base = { ref, oid, path, size: file.size, lineCount };
56
+ if (start === undefined && end === undefined) {
57
+ const text = file.truncated ? joinLines(lines) : file.text;
58
+ return { ...base, text, truncated: file.truncated };
59
+ }
60
+ if (lineCount === 0)
61
+ return { ...base, text: '', truncated: false };
62
+ const startLine = clamp(start ?? 1, 1, lineCount);
63
+ const endLine = clamp(end ?? lineCount, startLine, lineCount);
64
+ return {
65
+ ...base,
66
+ startLine,
67
+ endLine,
68
+ text: lines.slice(startLine - 1, endLine).join('\n'),
69
+ truncated: endLine > lines.length,
70
+ };
71
+ }
72
+ /** Lines of `text`; a trailing newline does not start an extra empty line. */
73
+ function splitLines(text) {
74
+ if (text === '')
75
+ return [];
76
+ const lines = text.split('\n');
77
+ if (lines[lines.length - 1] === '')
78
+ lines.pop();
79
+ return lines;
80
+ }
81
+ /** Inverse of {@link splitLines} for complete lines: each one newline-terminated. */
82
+ const joinLines = (lines) => (lines.length === 0 ? '' : `${lines.join('\n')}\n`);
83
+ const clamp = (n, min, max) => Math.min(Math.max(n, min), max);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Where fetched pull requests live inside a project's repository.
3
+ *
4
+ * Refs are namespaced under `refs/coja/` (decisions.md): the objects stay
5
+ * reachable (no gc), while the user's branches, remote-tracking refs and
6
+ * FETCH_HEAD are never written.
7
+ */
8
+ export const prHeadRef = (number) => `refs/coja/pull/${assertPrNumber(number)}/head`;
9
+ export const prBaseRef = (number) => `refs/coja/pull/${assertPrNumber(number)}/base`;
10
+ /** GitHub's read-only ref for a pull request's head commit (works for fork PRs). */
11
+ export const githubPullHeadRef = (number) => `refs/pull/${assertPrNumber(number)}/head`;
12
+ function assertPrNumber(number) {
13
+ if (!Number.isSafeInteger(number) || number <= 0) {
14
+ throw new RangeError(`invalid pull request number: ${String(number)}`);
15
+ }
16
+ return number;
17
+ }