@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,519 @@
1
+ import { badRequest } from '../routes/http.js';
2
+ import { assertPathspec, assertRevision, GitError, runGit, runGitGlobal, runGitStreaming, } from './run.js';
3
+ /**
4
+ * Typed wrappers over git plumbing. Every function takes `repo`, a directory
5
+ * usable with `git -C` (the user's clone or an app-managed bare clone), and
6
+ * reads objects only: nothing here checks out, writes blobs to disk, or
7
+ * touches the user's branches. The only writes are `fetch` into the
8
+ * `refs/coja/*` namespace and `cloneBare` into the app data directory.
9
+ */
10
+ const MiB = 1024 * 1024;
11
+ /** `gh` supplies the token for https fetches at call time; nothing is written to disk. */
12
+ export const GH_CREDENTIAL_HELPER = '!gh auth git-credential';
13
+ /** git considers a file binary when its first 8000 bytes contain a NUL. */
14
+ const BINARY_PROBE_BYTES = 8000;
15
+ /** Network operations (fetch, clone) are killed after this long. */
16
+ export const DEFAULT_NETWORK_TIMEOUT_MS = 15 * 60 * 1000;
17
+ // ---------------------------------------------------------------------------
18
+ // Toolchain
19
+ // ---------------------------------------------------------------------------
20
+ /** Oldest supported git: `fetch --no-write-fetch-head`, used for every PR fetch, arrived in 2.29. */
21
+ export const MIN_GIT_VERSION = '2.29';
22
+ /** `[major, minor, patch]` from `git --version` output (any build suffix ignored), or null. */
23
+ export function parseGitVersion(output) {
24
+ const m = /^git version (\d+)\.(\d+)(?:\.(\d+))?/.exec(output.trim());
25
+ if (!m)
26
+ return null;
27
+ return [Number(m[1]), Number(m[2]), Number(m[3] ?? 0)];
28
+ }
29
+ /** Why `git --version` output is unacceptable, phrased for the user; null when it is fine. */
30
+ export function gitVersionProblem(output) {
31
+ const version = parseGitVersion(output);
32
+ if (!version) {
33
+ return `could not understand \`git --version\` output "${output.trim()}"; coja needs git ${MIN_GIT_VERSION} or newer`;
34
+ }
35
+ const [min, ...rest] = MIN_GIT_VERSION.split('.').map(Number);
36
+ const [major, minor, patch] = version;
37
+ const tooOld = major < (min ?? 0) ||
38
+ (major === min && minor < (rest[0] ?? 0)) ||
39
+ (major === min && minor === rest[0] && patch < (rest[1] ?? 0));
40
+ if (tooOld) {
41
+ return `git ${major}.${minor}.${patch} is too old: coja needs git ${MIN_GIT_VERSION} or newer (for \`git fetch --no-write-fetch-head\`). Upgrade git and try again`;
42
+ }
43
+ return null;
44
+ }
45
+ /** Fail fast at startup unless the `git` on PATH is at least {@link MIN_GIT_VERSION}. */
46
+ export async function assertGitVersion() {
47
+ let output;
48
+ try {
49
+ output = (await runGitGlobal(['--version'])).stdout.toString('utf8');
50
+ }
51
+ catch (err) {
52
+ const reason = err instanceof Error ? err.message : String(err);
53
+ throw new Error(`git is required but could not be run (${reason}). Install git ${MIN_GIT_VERSION} or newer and make sure it is on PATH`);
54
+ }
55
+ const problem = gitVersionProblem(output);
56
+ if (problem)
57
+ throw new Error(problem);
58
+ }
59
+ // ---------------------------------------------------------------------------
60
+ // Repository facts
61
+ // ---------------------------------------------------------------------------
62
+ /** True when `dir` is inside a git repository (working tree or bare). */
63
+ export async function isGitRepo(dir) {
64
+ return (await tryGit(dir, ['rev-parse', '--git-dir'])) !== null;
65
+ }
66
+ export async function isBareRepo(dir) {
67
+ const out = await tryGit(dir, ['rev-parse', '--is-bare-repository']);
68
+ return out?.trim() === 'true';
69
+ }
70
+ /** Absolute path of the working tree root; null for a bare repository. */
71
+ export async function topLevel(repo) {
72
+ const out = await tryGit(repo, ['rev-parse', '--show-toplevel']);
73
+ const top = out?.trim();
74
+ return top ? top : null;
75
+ }
76
+ /** The configured URL of `remote` (as written in the config, before any insteadOf rewriting). */
77
+ export async function remoteUrl(repo, remote = 'origin') {
78
+ const res = await runGit(repo, ['config', '--get', `remote.${remote}.url`], { okExitCodes: [1] });
79
+ if (res.exitCode !== 0)
80
+ return null;
81
+ const url = res.stdout.toString('utf8').trim();
82
+ return url ? url : null;
83
+ }
84
+ /** Resolve `ref` to a commit oid; null when it does not exist or is not a commit. */
85
+ export async function revParse(repo, ref) {
86
+ assertRevision(ref);
87
+ const res = await runGit(repo, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], {
88
+ okExitCodes: [1],
89
+ });
90
+ if (res.exitCode !== 0)
91
+ return null;
92
+ const oid = res.stdout.toString('utf8').trim();
93
+ return oid ? oid : null;
94
+ }
95
+ export async function mergeBase(repo, a, b) {
96
+ assertRevision(a);
97
+ assertRevision(b);
98
+ const args = ['merge-base', a, b];
99
+ const res = await runGit(repo, args, { okExitCodes: [1] });
100
+ if (res.exitCode !== 0) {
101
+ throw new GitError(args, 1, res.stderr, `${a} and ${b} have no common ancestor`);
102
+ }
103
+ return res.stdout.toString('utf8').trim();
104
+ }
105
+ /**
106
+ * `git fetch` the given refspecs from `remote`. Callers pass explicit,
107
+ * namespaced destinations (`+refs/pull/1/head:refs/coja/pull/1/head`), so the
108
+ * user's branches and FETCH_HEAD are never written; tags and pruning are off so
109
+ * the fetch touches nothing beyond those refs.
110
+ */
111
+ export async function fetch(repo, remote, refspecs, opts = {}) {
112
+ if (refspecs.length === 0)
113
+ throw new Error('fetch: at least one refspec is required');
114
+ for (const arg of [remote, ...refspecs]) {
115
+ if (arg.startsWith('-') || arg.length === 0)
116
+ throw new Error(`fetch: invalid argument ${arg}`);
117
+ }
118
+ await runGit(repo, [
119
+ 'fetch',
120
+ '--no-tags',
121
+ '--no-prune',
122
+ '--no-recurse-submodules',
123
+ '--no-write-fetch-head',
124
+ '--quiet',
125
+ '--',
126
+ remote,
127
+ ...refspecs,
128
+ ], { signal: opts.signal, timeoutMs: opts.timeoutMs ?? DEFAULT_NETWORK_TIMEOUT_MS });
129
+ }
130
+ /**
131
+ * `git clone --bare` `url` into `dir` (which must not exist yet), with `gh` as
132
+ * the repository's credential helper — both for the clone itself and for later
133
+ * fetches of private repositories. No token is ever written to disk.
134
+ *
135
+ * The clone is killed after `timeoutMs` (default {@link DEFAULT_NETWORK_TIMEOUT_MS})
136
+ * or when `signal` aborts, and the promise settles only once git has exited,
137
+ * so the caller can remove the half-written directory without racing it.
138
+ */
139
+ export async function cloneBare(url, dir, opts = {}) {
140
+ if (url.startsWith('-') || dir.startsWith('-'))
141
+ throw badRequest('invalid clone arguments');
142
+ await runGitGlobal([
143
+ 'clone',
144
+ '--bare',
145
+ '--quiet',
146
+ // Reset inherited helpers so this repository authenticates through gh only.
147
+ '-c',
148
+ 'credential.helper=',
149
+ '-c',
150
+ `credential.helper=${GH_CREDENTIAL_HELPER}`,
151
+ '--',
152
+ url,
153
+ dir,
154
+ ], { signal: opts.signal, timeoutMs: opts.timeoutMs ?? DEFAULT_NETWORK_TIMEOUT_MS });
155
+ // `clone -c` persists the values above; make sure the helper is really there.
156
+ const helpers = await runGit(dir, ['config', '--get-all', 'credential.helper'], {
157
+ okExitCodes: [1],
158
+ });
159
+ if (!helpers.stdout.toString('utf8').split('\n').includes(GH_CREDENTIAL_HELPER)) {
160
+ await runGit(dir, ['config', '--add', 'credential.helper', GH_CREDENTIAL_HELPER]);
161
+ }
162
+ }
163
+ /** Type and size of `<oid>:<path>` via `cat-file --batch-check`; `exists: false` when the path is absent. */
164
+ export async function blobInfo(repo, oid, path) {
165
+ assertRevision(oid);
166
+ assertPathspec(path);
167
+ const res = await runGit(repo, ['cat-file', '--batch-check'], { input: `${oid}:${path}\n` });
168
+ const line = res.stdout.toString('utf8').split('\n')[0] ?? '';
169
+ const m = /^([0-9a-f]{40,64}) (blob|tree|commit|tag) (\d+)$/.exec(line);
170
+ if (!m)
171
+ return { exists: false, type: null, size: null, oid: null };
172
+ return { exists: true, type: m[2], size: Number(m[3]), oid: m[1] ?? null };
173
+ }
174
+ /** Content of the blob at `<oid>:<path>` (`git cat-file blob`). Fails with GitError when it is not a blob. */
175
+ export async function showFile(repo, oid, path, opts = {}) {
176
+ assertRevision(oid);
177
+ assertPathspec(path);
178
+ const maxBytes = opts.maxBytes ?? 5 * MiB;
179
+ const res = await runGit(repo, ['cat-file', 'blob', `${oid}:${path}`], {
180
+ maxBuffer: maxBytes,
181
+ truncateStdout: true,
182
+ });
183
+ const buf = res.stdout;
184
+ const binary = buf.subarray(0, BINARY_PROBE_BYTES).includes(0);
185
+ let size = buf.byteLength;
186
+ if (res.truncated)
187
+ size = (await blobInfo(repo, oid, path)).size ?? size;
188
+ return { text: binary ? '' : buf.toString('utf8'), binary, size, truncated: res.truncated };
189
+ }
190
+ /**
191
+ * Number of lines in the blob at `<oid>:<path>`, counted while streaming
192
+ * `git cat-file blob` so a blob of any size costs no memory. Counts the way
193
+ * the UI splits text: a final line without a trailing newline counts, an empty
194
+ * blob has none. Fails with GitError when the object is not a blob.
195
+ */
196
+ export async function countLines(repo, oid, path) {
197
+ assertRevision(oid);
198
+ assertPathspec(path);
199
+ const NEWLINE = 0x0a;
200
+ let newlines = 0;
201
+ let bytes = 0;
202
+ let lastByte = NEWLINE;
203
+ await runGitStreaming(repo, ['cat-file', 'blob', `${oid}:${path}`], (chunk) => {
204
+ if (chunk.byteLength === 0)
205
+ return;
206
+ bytes += chunk.byteLength;
207
+ for (let i = chunk.indexOf(NEWLINE); i !== -1; i = chunk.indexOf(NEWLINE, i + 1))
208
+ newlines++;
209
+ lastByte = chunk[chunk.byteLength - 1] ?? lastByte;
210
+ });
211
+ if (bytes === 0)
212
+ return 0;
213
+ return newlines + (lastByte === NEWLINE ? 0 : 1);
214
+ }
215
+ /** List a tree (`ls-tree -z -l`), recursively by default, optionally limited to `path`. */
216
+ export async function lsTree(repo, oid, opts = {}) {
217
+ assertRevision(oid);
218
+ const args = ['--literal-pathspecs', 'ls-tree', '-z', '-l', '--full-tree'];
219
+ if (opts.recursive ?? true)
220
+ args.push('-r');
221
+ args.push(oid);
222
+ if (opts.path !== undefined)
223
+ args.push('--', assertPathspec(opts.path));
224
+ const out = (await runGit(repo, args)).stdout.toString('utf8');
225
+ const entries = [];
226
+ for (const record of out.split('\0')) {
227
+ if (!record)
228
+ continue;
229
+ const tab = record.indexOf('\t');
230
+ if (tab === -1)
231
+ continue;
232
+ const [mode, type, entryOid, size] = record.slice(0, tab).split(/\s+/);
233
+ if (!mode || !type || !entryOid)
234
+ continue;
235
+ if (type !== 'blob' && type !== 'tree' && type !== 'commit')
236
+ continue;
237
+ entries.push({
238
+ path: record.slice(tab + 1),
239
+ mode,
240
+ type,
241
+ oid: entryOid,
242
+ size: size === undefined || size === '-' ? null : Number(size),
243
+ });
244
+ }
245
+ return entries;
246
+ }
247
+ /** Search the tree at `oid` (`git grep -n -I`). Binary files are skipped; exit code 1 means no matches. */
248
+ export async function grep(repo, oid, pattern, opts = {}) {
249
+ assertRevision(oid);
250
+ if (typeof pattern !== 'string' || pattern.length === 0)
251
+ throw badRequest('pattern is required');
252
+ const maxMatches = opts.maxMatches ?? 200;
253
+ const args = ['grep', '-n', '-I', '--no-color', '-z', '--full-name'];
254
+ if (opts.ignoreCase)
255
+ args.push('-i');
256
+ args.push(opts.fixedStrings ? '-F' : '-E');
257
+ // The pattern follows `-e`, so a pattern starting with `-` is never an option.
258
+ args.push('-e', pattern, oid, '--');
259
+ if (opts.pathspec !== undefined)
260
+ args.push(assertPathspec(opts.pathspec));
261
+ const res = await runGit(repo, args, {
262
+ okExitCodes: [1],
263
+ maxBuffer: 16 * MiB,
264
+ truncateStdout: true,
265
+ });
266
+ if (res.exitCode === 1)
267
+ return { matches: [], truncated: false };
268
+ const prefix = `${oid}:`;
269
+ const lines = res.stdout.toString('utf8').split('\n');
270
+ if (res.truncated)
271
+ lines.pop(); // the last line was cut mid-way
272
+ const matches = [];
273
+ let truncated = res.truncated;
274
+ for (const line of lines) {
275
+ if (!line)
276
+ continue;
277
+ if (matches.length >= maxMatches) {
278
+ truncated = true;
279
+ break;
280
+ }
281
+ // With -z: `<rev>:<path>\0<line>\0<text>`
282
+ const firstNul = line.indexOf('\0');
283
+ const secondNul = firstNul === -1 ? -1 : line.indexOf('\0', firstNul + 1);
284
+ if (firstNul === -1 || secondNul === -1)
285
+ continue;
286
+ const name = line.slice(0, firstNul);
287
+ matches.push({
288
+ path: name.startsWith(prefix) ? name.slice(prefix.length) : name,
289
+ line: Number(line.slice(firstNul + 1, secondNul)),
290
+ text: line.slice(secondNul + 1),
291
+ });
292
+ }
293
+ return { matches, truncated };
294
+ }
295
+ /**
296
+ * Unified patch between two commits (`git diff <base> <head>`), with standard
297
+ * `a/` `b/` prefixes regardless of the user's diff.* configuration so the UI's
298
+ * parser always sees canonical git output.
299
+ */
300
+ export async function diff(repo, base, head, opts = {}) {
301
+ assertRevision(base);
302
+ assertRevision(head);
303
+ const unified = opts.unified ?? 3;
304
+ if (!Number.isInteger(unified) || unified < 0)
305
+ throw badRequest('unified must be a non-negative integer');
306
+ const args = [
307
+ '--literal-pathspecs',
308
+ 'diff',
309
+ '--no-color',
310
+ '--no-ext-diff',
311
+ '--no-textconv',
312
+ '--no-relative',
313
+ '--find-renames',
314
+ '--src-prefix=a/',
315
+ '--dst-prefix=b/',
316
+ '--submodule=short',
317
+ `-U${unified}`,
318
+ base,
319
+ head,
320
+ ];
321
+ if (opts.paths && opts.paths.length > 0)
322
+ args.push('--', ...opts.paths.map(assertPathspec));
323
+ const res = await runGit(repo, args, {
324
+ maxBuffer: opts.maxBytes ?? 2 * MiB,
325
+ truncateStdout: true,
326
+ });
327
+ if (res.truncated)
328
+ return { patch: '', truncated: true };
329
+ return { patch: res.stdout.toString('utf8'), truncated: false };
330
+ }
331
+ const CHANGE_STATUSES = new Set(['A', 'D', 'M', 'R', 'C', 'T']);
332
+ /** Changed files between two commits with rename detection (`diff --name-status --find-renames -z`). */
333
+ export async function diffNameStatus(repo, base, head) {
334
+ assertRevision(base);
335
+ assertRevision(head);
336
+ const res = await runGit(repo, [
337
+ 'diff',
338
+ '--name-status',
339
+ '--find-renames',
340
+ '-z',
341
+ '--no-color',
342
+ '--no-ext-diff',
343
+ '--no-relative',
344
+ base,
345
+ head,
346
+ ]);
347
+ const tokens = res.stdout.toString('utf8').split('\0');
348
+ const files = [];
349
+ for (let i = 0; i < tokens.length;) {
350
+ const rawStatus = tokens[i++];
351
+ if (!rawStatus)
352
+ continue;
353
+ const letter = rawStatus[0] ?? '';
354
+ // `R100`/`C075` carry a similarity score and are followed by <old>\0<new>.
355
+ if (letter === 'R' || letter === 'C') {
356
+ const previousPath = tokens[i++];
357
+ const path = tokens[i++];
358
+ if (previousPath === undefined || path === undefined)
359
+ break;
360
+ files.push({ path, previousPath, status: letter });
361
+ continue;
362
+ }
363
+ const path = tokens[i++];
364
+ if (path === undefined)
365
+ break;
366
+ files.push({ path, status: CHANGE_STATUSES.has(letter) ? letter : 'M' });
367
+ }
368
+ return files;
369
+ }
370
+ const LOG_FIELD = '\x1f';
371
+ const LOG_RECORD = '\x1e';
372
+ const LOG_FORMAT = `${['%H', '%h', '%an', '%ae', '%aI', '%s', '%b'].join('%x1f')}%x1e`;
373
+ /** Commit history (`git log`), newest first, optionally for a range such as `base..head` and a path. */
374
+ export async function log(repo, opts = {}) {
375
+ const maxCount = opts.maxCount ?? 50;
376
+ if (!Number.isInteger(maxCount) || maxCount <= 0)
377
+ throw badRequest('maxCount must be a positive integer');
378
+ const args = [
379
+ '--literal-pathspecs',
380
+ 'log',
381
+ '--no-color',
382
+ '--no-show-signature',
383
+ '--date=iso-strict',
384
+ `--format=${LOG_FORMAT}`,
385
+ '-n',
386
+ String(maxCount),
387
+ ];
388
+ if (opts.range !== undefined)
389
+ args.push(assertRevision(opts.range));
390
+ if (opts.path !== undefined)
391
+ args.push('--', assertPathspec(opts.path));
392
+ const out = (await runGit(repo, args)).stdout.toString('utf8');
393
+ const entries = [];
394
+ for (const raw of out.split(LOG_RECORD)) {
395
+ const record = raw.replace(/^\n/, '');
396
+ if (!record)
397
+ continue;
398
+ const fields = splitN(record, LOG_FIELD, 7);
399
+ const [oid, abbreviatedOid, authorName, authorEmail, authoredAt, subject, body] = fields;
400
+ if (!oid || abbreviatedOid === undefined)
401
+ continue;
402
+ entries.push({
403
+ oid,
404
+ abbreviatedOid,
405
+ authorName: authorName ?? '',
406
+ authorEmail: authorEmail ?? '',
407
+ authoredAt: authoredAt ?? '',
408
+ subject: subject ?? '',
409
+ body: (body ?? '').trimEnd(),
410
+ });
411
+ }
412
+ return entries;
413
+ }
414
+ /** Split into at most `n` parts; the last part keeps any further separators. */
415
+ function splitN(s, sep, n) {
416
+ const parts = [];
417
+ let start = 0;
418
+ while (parts.length < n - 1) {
419
+ const idx = s.indexOf(sep, start);
420
+ if (idx === -1)
421
+ break;
422
+ parts.push(s.slice(start, idx));
423
+ start = idx + sep.length;
424
+ }
425
+ parts.push(s.slice(start));
426
+ return parts;
427
+ }
428
+ /** Per-line authorship (`git blame --porcelain`), optionally limited to an inclusive line range. */
429
+ export async function blame(repo, oid, path, opts = {}) {
430
+ assertRevision(oid);
431
+ assertPathspec(path);
432
+ // Literal pathspecs: `[id]` in a path is a directory name, never a glob.
433
+ const args = ['--literal-pathspecs', 'blame', '--porcelain'];
434
+ if (opts.startLine !== undefined || opts.endLine !== undefined) {
435
+ const start = opts.startLine ?? 1;
436
+ const end = opts.endLine;
437
+ if (!Number.isInteger(start) || start < 1)
438
+ throw badRequest('startLine must be a positive integer');
439
+ if (end !== undefined && (!Number.isInteger(end) || end < start)) {
440
+ throw badRequest('endLine must be an integer >= startLine');
441
+ }
442
+ args.push('-L', `${start},${end ?? ''}`);
443
+ }
444
+ args.push(oid, '--', path);
445
+ const out = (await runGit(repo, args)).stdout.toString('utf8');
446
+ return parseBlamePorcelain(out);
447
+ }
448
+ function parseBlamePorcelain(out) {
449
+ const lines = out.split('\n');
450
+ const commits = new Map();
451
+ const result = [];
452
+ let i = 0;
453
+ while (i < lines.length) {
454
+ const header = lines[i];
455
+ if (!header) {
456
+ i++;
457
+ continue;
458
+ }
459
+ const m = /^([0-9a-f]{40,64}) \d+ (\d+)(?: \d+)?$/.exec(header);
460
+ if (!m?.[1] || !m[2])
461
+ throw new Error(`unexpected git blame output: ${header}`);
462
+ const sha = m[1];
463
+ const finalLine = Number(m[2]);
464
+ i++;
465
+ const info = commits.get(sha) ?? { author: '', authorTime: 0, authorTz: '+0000', summary: '' };
466
+ // Header key/value lines appear the first time a commit is seen; the content line starts with a tab.
467
+ while (i < lines.length && !(lines[i] ?? '').startsWith('\t')) {
468
+ const kv = lines[i] ?? '';
469
+ const sp = kv.indexOf(' ');
470
+ const key = sp === -1 ? kv : kv.slice(0, sp);
471
+ const value = sp === -1 ? '' : kv.slice(sp + 1);
472
+ if (key === 'author')
473
+ info.author = value;
474
+ else if (key === 'author-time')
475
+ info.authorTime = Number(value);
476
+ else if (key === 'author-tz')
477
+ info.authorTz = value;
478
+ else if (key === 'summary')
479
+ info.summary = value;
480
+ i++;
481
+ }
482
+ commits.set(sha, info);
483
+ const content = (lines[i] ?? '').slice(1);
484
+ i++;
485
+ result.push({
486
+ line: finalLine,
487
+ oid: sha,
488
+ abbreviatedOid: sha.slice(0, 7),
489
+ author: info.author,
490
+ authorTime: epochToIso(info.authorTime, info.authorTz),
491
+ summary: info.summary,
492
+ content,
493
+ });
494
+ }
495
+ return result;
496
+ }
497
+ /** `1704164645` + `+0200` → `2024-01-02T05:04:05+02:00`; UTC is written as `Z`, like git's `%aI`. */
498
+ function epochToIso(epochSeconds, tz) {
499
+ const m = /^([+-])(\d{2})(\d{2})$/.exec(tz);
500
+ const sign = m?.[1] === '-' ? -1 : 1;
501
+ const hh = m?.[2] ?? '00';
502
+ const mm = m?.[3] ?? '00';
503
+ const offsetSeconds = sign * (Number(hh) * 3600 + Number(mm) * 60);
504
+ const shifted = new Date((epochSeconds + offsetSeconds) * 1000);
505
+ const zone = offsetSeconds === 0 ? 'Z' : `${sign < 0 ? '-' : '+'}${hh}:${mm}`;
506
+ return `${shifted.toISOString().slice(0, 19)}${zone}`;
507
+ }
508
+ // ---------------------------------------------------------------------------
509
+ /** Run a read-only query; null when git exits non-zero (e.g. not a repository). Other failures propagate. */
510
+ async function tryGit(dir, args) {
511
+ try {
512
+ return (await runGit(dir, args)).stdout.toString('utf8');
513
+ }
514
+ catch (err) {
515
+ if (err instanceof GitError && err.exitCode !== null)
516
+ return null;
517
+ throw err;
518
+ }
519
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Recognise GitHub repositories in the URL forms git stores for remotes and in
3
+ * the `owner/repo` shorthand users type. Anything that is not clearly a
4
+ * github.com repository yields null; callers turn that into a 400.
5
+ */
6
+ /** GitHub owner and repository names: alphanumerics, `-`, `_`, `.`; never `.` or `..`. */
7
+ const NAME = /^[A-Za-z0-9_.-]+$/;
8
+ // scp-like syntax: [user@]github.com:owner/repo(.git)
9
+ const SCP_FORM = /^(?:[\w.-]+@)?github\.com:(.+)$/i;
10
+ // URL syntax: [scheme://][user@]github.com[:port]/owner/repo(.git)(/)
11
+ const URL_FORM = /^(?:(?:ssh|https?|git|git\+ssh|ssh\+git):\/\/)?(?:[^@/\s]+@)?(?:www\.)?github\.com(?::\d+)?\/(.+)$/i;
12
+ /**
13
+ * Parse a remote URL into `{ owner, repo }` when it points at github.com.
14
+ *
15
+ * Accepts `git@github.com:o/r.git`, `ssh://git@github.com/o/r.git`,
16
+ * `https://github.com/o/r(.git)(/)`, `git://github.com/o/r` and the bare
17
+ * `github.com/o/r`. Returns null for anything else.
18
+ */
19
+ export function parseGitHubRemote(url) {
20
+ const input = url.trim();
21
+ const scp = SCP_FORM.exec(input);
22
+ const rest = scp?.[1] ?? URL_FORM.exec(input)?.[1];
23
+ return rest === undefined ? null : splitOwnerRepo(rest);
24
+ }
25
+ /**
26
+ * Parse what a user types to add a project by clone: `owner/repo`, or any URL
27
+ * form {@link parseGitHubRemote} accepts.
28
+ */
29
+ export function parseRepoSlug(input) {
30
+ const slug = input.trim();
31
+ if (/^[^/:@\s]+\/[^/:@\s]+$/.test(slug))
32
+ return splitOwnerRepo(slug);
33
+ return parseGitHubRemote(slug);
34
+ }
35
+ function splitOwnerRepo(rest) {
36
+ let p = rest.replace(/\/+$/, '');
37
+ if (p.toLowerCase().endsWith('.git'))
38
+ p = p.slice(0, -'.git'.length);
39
+ const parts = p.split('/');
40
+ if (parts.length !== 2)
41
+ return null;
42
+ const [owner, repo] = parts;
43
+ if (!owner || !repo || !isName(owner) || !isName(repo))
44
+ return null;
45
+ return { owner, repo };
46
+ }
47
+ const isName = (s) => NAME.test(s) && s !== '.' && s !== '..';