@sanurb/ringi 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +505 -114
- package/dist/cli.mjs.map +1 -1
- package/dist/mcp.mjs +35 -39
- package/dist/mcp.mjs.map +1 -1
- package/dist/runtime.mjs +569 -428
- package/dist/runtime.mjs.map +1 -1
- package/package.json +9 -8
- package/server/nitro.json +17 -0
- package/server/public/assets/ClientOnly-QdfAxyFs.js +1 -0
- package/server/public/assets/_reviewId-CmXHvWLn.js +1 -0
- package/server/public/assets/_reviewId-DdOpDx4U.js +1 -0
- package/server/public/assets/abap-B1dkBSPn.js +1 -0
- package/server/public/assets/action-bar-DLRNvLjj.js +45 -0
- package/server/public/assets/actionscript-3-BT4ibYlP.js +1 -0
- package/server/public/assets/ada-CD92zeps.js +1 -0
- package/server/public/assets/andromeeda-DqSmgxi0.js +1 -0
- package/server/public/assets/angular-html-BDC0PfKr.js +1 -0
- package/server/public/assets/angular-ts-B9yoQMtj.js +1 -0
- package/server/public/assets/apache-D5suuoa_.js +1 -0
- package/server/public/assets/apex-BL-m4VHy.js +1 -0
- package/server/public/assets/apl-CldhY0Pn.js +1 -0
- package/server/public/assets/applescript-CLiBqvKT.js +1 -0
- package/server/public/assets/ara-LdDF8cmv.js +1 -0
- package/server/public/assets/asciidoc-2DZ9hC2N.js +1 -0
- package/server/public/assets/asm-0ZPGRSUy.js +1 -0
- package/server/public/assets/astro-DR6labZJ.js +1 -0
- package/server/public/assets/aurora-x-Da7Zfvbg.js +1 -0
- package/server/public/assets/awk-Bn0gn_B_.js +1 -0
- package/server/public/assets/ayu-dark-bqYKoqpM.js +1 -0
- package/server/public/assets/ayu-light-C45jTIzZ.js +1 -0
- package/server/public/assets/ayu-mirage-BV_FCTQi.js +1 -0
- package/server/public/assets/ballerina-D2nw_w8Q.js +1 -0
- package/server/public/assets/bat-ByUBN5gS.js +1 -0
- package/server/public/assets/beancount-BTb2W6Mp.js +1 -0
- package/server/public/assets/berry-5SO2uITG.js +1 -0
- package/server/public/assets/bibtex-CDBTNfUI.js +1 -0
- package/server/public/assets/bicep-fgxG_4rP.js +1 -0
- package/server/public/assets/bird2-BCwzDhwX.js +1 -0
- package/server/public/assets/blade-BBTRu2-g.js +1 -0
- package/server/public/assets/bsl-CG-fq4sc.js +1 -0
- package/server/public/assets/c-CEvNj7xl.js +1 -0
- package/server/public/assets/c3-Dlaci63_.js +1 -0
- package/server/public/assets/cadence-DHbRuEmm.js +1 -0
- package/server/public/assets/cairo-Ds0kTeYT.js +1 -0
- package/server/public/assets/catppuccin-frappe-DrL1fUuH.js +1 -0
- package/server/public/assets/catppuccin-latte-C0REgVjl.js +1 -0
- package/server/public/assets/catppuccin-macchiato-ChQpylWO.js +1 -0
- package/server/public/assets/catppuccin-mocha-Dd0JU1T0.js +1 -0
- package/server/public/assets/clarity-DMoTOm4G.js +1 -0
- package/server/public/assets/clojure-DBhE3PpS.js +1 -0
- package/server/public/assets/cmake-DwMc40Or.js +1 -0
- package/server/public/assets/cobol-BdNJSMRt.js +1 -0
- package/server/public/assets/codeowners-1lVr8wqV.js +1 -0
- package/server/public/assets/codeql-DWflolvo.js +1 -0
- package/server/public/assets/coffee-RA4xA24H.js +1 -0
- package/server/public/assets/common-lisp-CrVQ5xT-.js +1 -0
- package/server/public/assets/compiler-runtime-DZXZ41-q.js +1 -0
- package/server/public/assets/coq-CjfoyYSh.js +1 -0
- package/server/public/assets/cpp-BoW7e2Ow.js +1 -0
- package/server/public/assets/createServerFn-DTk395iP.js +9 -0
- package/server/public/assets/crystal-CYKRo3F9.js +1 -0
- package/server/public/assets/csharp-C7bIWP5y.js +1 -0
- package/server/public/assets/css-Ck2tii2d.js +1 -0
- package/server/public/assets/csv-DsAkDVtA.js +1 -0
- package/server/public/assets/cue-BWmQgbOB.js +1 -0
- package/server/public/assets/cypher-D-jVC50Q.js +1 -0
- package/server/public/assets/d-CaviyOrm.js +1 -0
- package/server/public/assets/dark-plus-DIrnwZt9.js +1 -0
- package/server/public/assets/dart-CZEi7JgC.js +1 -0
- package/server/public/assets/dax-BK-8zffy.js +1 -0
- package/server/public/assets/desktop-D3cjbL4D.js +1 -0
- package/server/public/assets/diff-sHAzLvlp.js +1 -0
- package/server/public/assets/docker--xs2Ng3w.js +1 -0
- package/server/public/assets/dotenv-Cm4nwcJ7.js +1 -0
- package/server/public/assets/dracula-CAUSusef.js +1 -0
- package/server/public/assets/dracula-soft-cjNkMFza.js +1 -0
- package/server/public/assets/dream-maker-fjmWTFCO.js +1 -0
- package/server/public/assets/edge-DxycC9wl.js +1 -0
- package/server/public/assets/elixir-B-50Er3p.js +1 -0
- package/server/public/assets/elm-B4-ygIVo.js +1 -0
- package/server/public/assets/emacs-lisp-CJzqStIa.js +1 -0
- package/server/public/assets/erb-DJvYE1L1.js +1 -0
- package/server/public/assets/erlang-C-m_88FN.js +1 -0
- package/server/public/assets/everforest-dark-DBpaSMx1.js +1 -0
- package/server/public/assets/everforest-light-CiGrXwia.js +1 -0
- package/server/public/assets/fennel-DRaXF7k8.js +1 -0
- package/server/public/assets/file-tree-CI3Xwwid.js +1907 -0
- package/server/public/assets/fish-Bn-Yh3Jj.js +1 -0
- package/server/public/assets/fluent-DF5F8Ks_.js +1 -0
- package/server/public/assets/fortran-fixed-form-Cx1lv7HN.js +1 -0
- package/server/public/assets/fortran-free-form-hCQHRqew.js +1 -0
- package/server/public/assets/fsharp-DC5k9sy2.js +1 -0
- package/server/public/assets/gdresource-D0EsKdgH.js +1 -0
- package/server/public/assets/gdscript-_C9_Hi_w.js +1 -0
- package/server/public/assets/gdshader-BW7b1X1Y.js +1 -0
- package/server/public/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/server/public/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/server/public/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/server/public/assets/genie-Ch_6TCHd.js +1 -0
- package/server/public/assets/gherkin-CaNUsmTq.js +1 -0
- package/server/public/assets/git-commit-BcFsuO5E.js +1 -0
- package/server/public/assets/git-rebase-ChGA-z50.js +1 -0
- package/server/public/assets/github-dark-B9ygjgg6.js +1 -0
- package/server/public/assets/github-dark-default-Br2bgYSx.js +1 -0
- package/server/public/assets/github-dark-dimmed-CmtqpPJ-.js +1 -0
- package/server/public/assets/github-dark-high-contrast-fSfmrZcC.js +1 -0
- package/server/public/assets/github-light-BZjUqfZl.js +1 -0
- package/server/public/assets/github-light-default-lIytXXhR.js +1 -0
- package/server/public/assets/github-light-high-contrast-BRrjFb7n.js +1 -0
- package/server/public/assets/gleam-DALMDpNs.js +1 -0
- package/server/public/assets/glimmer-js-maLb6ysA.js +1 -0
- package/server/public/assets/glimmer-ts-DGNr-OBA.js +1 -0
- package/server/public/assets/glsl-CmplqyQ1.js +1 -0
- package/server/public/assets/gn-DGjqrYN9.js +1 -0
- package/server/public/assets/gnuplot-BYckvgQI.js +1 -0
- package/server/public/assets/go-JycvP538.js +1 -0
- package/server/public/assets/graphql-VhP7n4--.js +1 -0
- package/server/public/assets/groovy-D5qMRONT.js +1 -0
- package/server/public/assets/gruvbox-dark-hard-M1dj1e6V.js +1 -0
- package/server/public/assets/gruvbox-dark-medium-cqq_ncQu.js +1 -0
- package/server/public/assets/gruvbox-dark-soft-B4QwL2a9.js +1 -0
- package/server/public/assets/gruvbox-light-hard-DLayMKOQ.js +1 -0
- package/server/public/assets/gruvbox-light-medium-D52XgPKf.js +1 -0
- package/server/public/assets/gruvbox-light-soft-Dola3KdD.js +1 -0
- package/server/public/assets/hack-BVSQ2bxM.js +1 -0
- package/server/public/assets/haml-CwTtRHoj.js +1 -0
- package/server/public/assets/handlebars-CcO01SVo.js +1 -0
- package/server/public/assets/haskell-ys7wPPEd.js +1 -0
- package/server/public/assets/haxe-94kiChn7.js +1 -0
- package/server/public/assets/hcl-DmHt_-wq.js +1 -0
- package/server/public/assets/hjson-xMmoJ0Gx.js +1 -0
- package/server/public/assets/hlsl-b-Pskdze.js +1 -0
- package/server/public/assets/horizon-BKMqttiR.js +1 -0
- package/server/public/assets/horizon-bright-HNkBlnm5.js +1 -0
- package/server/public/assets/houston-BkBSBSOQ.js +1 -0
- package/server/public/assets/html-derivative-Cz-cKMi2.js +1 -0
- package/server/public/assets/html-zQbUS8Is.js +1 -0
- package/server/public/assets/http-CaGQ9BgA.js +1 -0
- package/server/public/assets/hurl-BBoki9bg.js +1 -0
- package/server/public/assets/hxml-iQTOTWpM.js +1 -0
- package/server/public/assets/hy-DKl1XhBq.js +1 -0
- package/server/public/assets/imba-DPxkOTAg.js +1 -0
- package/server/public/assets/ini-lkLGq_1x.js +1 -0
- package/server/public/assets/java-LAx6oszV.js +1 -0
- package/server/public/assets/javascript-COqx-gKX.js +1 -0
- package/server/public/assets/jinja-x-G_qSCP.js +1 -0
- package/server/public/assets/jison-7oSeVkKJ.js +1 -0
- package/server/public/assets/json-sTLOVXhc.js +1 -0
- package/server/public/assets/json5-Cy6ypJuJ.js +1 -0
- package/server/public/assets/jsonc-Cw2ugYAK.js +1 -0
- package/server/public/assets/jsonl-Dp5_qBVH.js +1 -0
- package/server/public/assets/jsonnet-BTbmg_-u.js +1 -0
- package/server/public/assets/jssm-CnT7nPea.js +1 -0
- package/server/public/assets/jsx-zXeIBQLI.js +1 -0
- package/server/public/assets/julia-E-6Xm9nd.js +1 -0
- package/server/public/assets/just-D9n74gZy.js +1 -0
- package/server/public/assets/kanagawa-dragon-CxsBnuhV.js +1 -0
- package/server/public/assets/kanagawa-lotus-vHdxDDOS.js +1 -0
- package/server/public/assets/kanagawa-wave-CIkfTKWk.js +1 -0
- package/server/public/assets/kdl-BwK60g80.js +1 -0
- package/server/public/assets/kotlin-Dbd9Vi-v.js +1 -0
- package/server/public/assets/kusto-BuTk9usc.js +1 -0
- package/server/public/assets/laserwave-C0wf_d3o.js +1 -0
- package/server/public/assets/latex-D0t4RtEU.js +1 -0
- package/server/public/assets/lean-CYAW8bRN.js +1 -0
- package/server/public/assets/less-D4uen21c.js +1 -0
- package/server/public/assets/light-plus-oqYyWKEE.js +1 -0
- package/server/public/assets/liquid-BzXN12F6.js +1 -0
- package/server/public/assets/llvm-Z1xJzteV.js +1 -0
- package/server/public/assets/log-BGUxlsk3.js +1 -0
- package/server/public/assets/logo-wVUhvQ1b.js +1 -0
- package/server/public/assets/lua-B0Cg8RP4.js +1 -0
- package/server/public/assets/luau-rPFZzCmq.js +1 -0
- package/server/public/assets/main-FvxVz-kD.js +15 -0
- package/server/public/assets/make-BmPf6m0P.js +1 -0
- package/server/public/assets/markdown-AseU6zcW.js +1 -0
- package/server/public/assets/marko-BlRPXWOe.js +1 -0
- package/server/public/assets/material-theme-6_W6rQhR.js +1 -0
- package/server/public/assets/material-theme-darker-VPEo3Sem.js +1 -0
- package/server/public/assets/material-theme-lighter-CUhzCcZ9.js +1 -0
- package/server/public/assets/material-theme-ocean-B2JdsaGb.js +1 -0
- package/server/public/assets/material-theme-palenight-DhY-sklA.js +1 -0
- package/server/public/assets/matlab-BOj_BDQv.js +1 -0
- package/server/public/assets/mdc-FiVDZSZ4.js +1 -0
- package/server/public/assets/mdx-Cm6cDkDI.js +1 -0
- package/server/public/assets/mermaid-DLO-R4hv.js +1 -0
- package/server/public/assets/min-dark-D34a_pX7.js +1 -0
- package/server/public/assets/min-light-Cdd4KORE.js +1 -0
- package/server/public/assets/mipsasm-DYpHF-GA.js +1 -0
- package/server/public/assets/mojo-DqYVFv_G.js +1 -0
- package/server/public/assets/monokai-CDR4sQ2n.js +1 -0
- package/server/public/assets/moonbit-DRKee9wk.js +1 -0
- package/server/public/assets/move-DbRk6Vn9.js +1 -0
- package/server/public/assets/narrat-IOfmaXfb.js +1 -0
- package/server/public/assets/new-DOyplRwM.js +1 -0
- package/server/public/assets/nextflow-D-Ec_bsY.js +1 -0
- package/server/public/assets/nextflow-groovy-EYl0c2BQ.js +1 -0
- package/server/public/assets/nginx-3JLAqmJa.js +1 -0
- package/server/public/assets/night-owl-light-Bedht9b4.js +1 -0
- package/server/public/assets/night-owl-yQJ3-I0I.js +1 -0
- package/server/public/assets/nim-DyjFVMzT.js +1 -0
- package/server/public/assets/nix-C2IovEl2.js +1 -0
- package/server/public/assets/nord-BjZ63GNL.js +1 -0
- package/server/public/assets/nushell-BflTrRB5.js +1 -0
- package/server/public/assets/objective-c-GRClK1S7.js +1 -0
- package/server/public/assets/objective-cpp-l3qYw-v5.js +1 -0
- package/server/public/assets/ocaml-BBDyhyMH.js +1 -0
- package/server/public/assets/odin-jCJ7Js99.js +1 -0
- package/server/public/assets/one-dark-pro-PIx2Diul.js +1 -0
- package/server/public/assets/one-light-BFMEz49S.js +1 -0
- package/server/public/assets/openscad-Drf0LgCX.js +1 -0
- package/server/public/assets/pascal-BT2XAUTl.js +1 -0
- package/server/public/assets/perl-Dr47G_2Q.js +1 -0
- package/server/public/assets/php-BhBDWTJe.js +1 -0
- package/server/public/assets/pierre-dark-CTXzTLfO.js +1 -0
- package/server/public/assets/pierre-light-C_5rlJRo.js +1 -0
- package/server/public/assets/pkl-ML-dWShO.js +1 -0
- package/server/public/assets/plastic-BFI-Z5Z2.js +1 -0
- package/server/public/assets/plsql-0vd5cLro.js +1 -0
- package/server/public/assets/po-CbZ_uqQA.js +1 -0
- package/server/public/assets/poimandres-Cayhd01L.js +1 -0
- package/server/public/assets/polar-C4hfV8Nc.js +1 -0
- package/server/public/assets/postcss-osFUbTLw.js +1 -0
- package/server/public/assets/powerquery-CTlGUQPj.js +1 -0
- package/server/public/assets/powershell-DyZsOmuq.js +1 -0
- package/server/public/assets/preload-helper-D7oT-Xwl.js +20 -0
- package/server/public/assets/prisma-SS92PO_I.js +1 -0
- package/server/public/assets/prolog-B1O1NNVC.js +1 -0
- package/server/public/assets/proto-BWu3eZTs.js +1 -0
- package/server/public/assets/pug-Dij_IK5w.js +1 -0
- package/server/public/assets/puppet-tvtRVdr6.js +1 -0
- package/server/public/assets/purescript-Dtbpb7D-.js +1 -0
- package/server/public/assets/python-Dlk0Acio.js +1 -0
- package/server/public/assets/qml-qUwk3nhh.js +1 -0
- package/server/public/assets/qmldir-B-iEOngH.js +1 -0
- package/server/public/assets/qss-Ba0p-aHw.js +1 -0
- package/server/public/assets/r-WmtNicKM.js +1 -0
- package/server/public/assets/racket-BXDsxf2U.js +1 -0
- package/server/public/assets/raku-Dw1WWFXK.js +1 -0
- package/server/public/assets/razor-DaqiVx3Q.js +1 -0
- package/server/public/assets/red-BC3Ds49b.js +1 -0
- package/server/public/assets/reg-DXFHGaM4.js +1 -0
- package/server/public/assets/regexp-CiSWN5Ne.js +1 -0
- package/server/public/assets/rel-Dc5_Ytx2.js +1 -0
- package/server/public/assets/reviews-CJvVXRLH.js +1 -0
- package/server/public/assets/reviews-CfbuF6ib.js +1 -0
- package/server/public/assets/riscv-ZgswiWij.js +1 -0
- package/server/public/assets/ron-YghabWAH.js +1 -0
- package/server/public/assets/rose-pine-ByWLnVr3.js +1 -0
- package/server/public/assets/rose-pine-dawn-DBmeySrz.js +1 -0
- package/server/public/assets/rose-pine-moon-B9J-N3nK.js +1 -0
- package/server/public/assets/rosmsg-DTKmAsVH.js +1 -0
- package/server/public/assets/routes-DNxq1Fba.js +1 -0
- package/server/public/assets/routes-Dp0ODZ55.js +2 -0
- package/server/public/assets/rst-CP6xOYlY.js +1 -0
- package/server/public/assets/ruby-BXYLc1CM.js +1 -0
- package/server/public/assets/rust-nfXwuE6F.js +1 -0
- package/server/public/assets/sas-CFdtZutF.js +1 -0
- package/server/public/assets/sass-CbRjkld3.js +1 -0
- package/server/public/assets/scala-BzD3eypx.js +1 -0
- package/server/public/assets/scheme-D4d1PV1y.js +1 -0
- package/server/public/assets/scss-D8KhdObH.js +1 -0
- package/server/public/assets/sdbl-Cf-Ydnvx.js +1 -0
- package/server/public/assets/shaderlab-DGohHMiF.js +1 -0
- package/server/public/assets/shellscript-BHdEbumI.js +1 -0
- package/server/public/assets/shellsession-Dh-bxrap.js +1 -0
- package/server/public/assets/slack-dark-MszIyPZ2.js +1 -0
- package/server/public/assets/slack-ochin-tQ3Q0gE9.js +1 -0
- package/server/public/assets/smalltalk-_uWoArwn.js +1 -0
- package/server/public/assets/snazzy-light-9sniMEk5.js +1 -0
- package/server/public/assets/solarized-dark-CdD0Hxzv.js +1 -0
- package/server/public/assets/solarized-light-C-nsEdqF.js +1 -0
- package/server/public/assets/solidity-D6uC-xwP.js +1 -0
- package/server/public/assets/soy-cDuODfbT.js +1 -0
- package/server/public/assets/sparql-BgU2QITA.js +1 -0
- package/server/public/assets/splunk-LQYHRu14.js +1 -0
- package/server/public/assets/sql-CKZpK620.js +1 -0
- package/server/public/assets/ssh-config-B7BUl8Rd.js +1 -0
- package/server/public/assets/stata-BLJTbKOO.js +1 -0
- package/server/public/assets/styles-UDowwF7S.css +2 -0
- package/server/public/assets/stylus-Byjxdx_q.js +1 -0
- package/server/public/assets/surrealql-C96KvYaj.js +1 -0
- package/server/public/assets/svelte-Qnbj2GWx.js +1 -0
- package/server/public/assets/swift-BexLlMrU.js +1 -0
- package/server/public/assets/synthwave-84-BxMBwQMS.js +1 -0
- package/server/public/assets/system-verilog-DVGwm0mw.js +1 -0
- package/server/public/assets/systemd-H2IT3-p5.js +1 -0
- package/server/public/assets/talonscript-mKZIGM8n.js +1 -0
- package/server/public/assets/tasl-B7he_Ugr.js +1 -0
- package/server/public/assets/tcl-5mT3RxHH.js +1 -0
- package/server/public/assets/templ-CQPDll3D.js +1 -0
- package/server/public/assets/terraform-BZP0GLsT.js +1 -0
- package/server/public/assets/test-D7JRfog1.js +1 -0
- package/server/public/assets/tex-97QNLoBJ.js +1 -0
- package/server/public/assets/tokyo-night-CTPVdZt9.js +1 -0
- package/server/public/assets/toml-CTFA98he.js +1 -0
- package/server/public/assets/ts-tags-B8zlXe2n.js +1 -0
- package/server/public/assets/tsv-BayJtYdY.js +1 -0
- package/server/public/assets/tsx-VqRU8NCz.js +1 -0
- package/server/public/assets/turtle-TVCBh_kY.js +1 -0
- package/server/public/assets/twig-FTTF8rVk.js +1 -0
- package/server/public/assets/typescript-CuX0hIVY.js +1 -0
- package/server/public/assets/typespec-BUvaJDLF.js +1 -0
- package/server/public/assets/typst-8NBaY7Ec.js +1 -0
- package/server/public/assets/useStore-M3H8PB1v.js +1 -0
- package/server/public/assets/utils-DElCu2hq.js +1 -0
- package/server/public/assets/v-VihyTigi.js +1 -0
- package/server/public/assets/vala-DyFAPyX6.js +1 -0
- package/server/public/assets/vb-Dg1Iqi4J.js +1 -0
- package/server/public/assets/verilog-D2Xc-vhD.js +1 -0
- package/server/public/assets/vesper-DJbtqYNr.js +1 -0
- package/server/public/assets/vhdl-CU3BVeE7.js +1 -0
- package/server/public/assets/viml-hG2shuOW.js +1 -0
- package/server/public/assets/vitesse-black-DbG2gsc0.js +1 -0
- package/server/public/assets/vitesse-dark-B6WV4xXH.js +1 -0
- package/server/public/assets/vitesse-light-DC1pdD02.js +1 -0
- package/server/public/assets/vue-DXwaEU0U.js +1 -0
- package/server/public/assets/vue-html-QD7AJ6JJ.js +1 -0
- package/server/public/assets/vue-vine-Bh2m1D2Z.js +1 -0
- package/server/public/assets/vyper-C1wojIuk.js +1 -0
- package/server/public/assets/wasm-C6Y0s02M.js +1 -0
- package/server/public/assets/wasm-qTvCOSHz.js +1 -0
- package/server/public/assets/wenyan-BG5vPQF0.js +1 -0
- package/server/public/assets/wgsl-DrVb-Cub.js +1 -0
- package/server/public/assets/wikitext-PRC4s8sH.js +1 -0
- package/server/public/assets/wit-ChW5qvg_.js +1 -0
- package/server/public/assets/wolfram-B8mKuZSQ.js +1 -0
- package/server/public/assets/xml-BK-rcb5a.js +1 -0
- package/server/public/assets/xsl-dt-d2R7p.js +1 -0
- package/server/public/assets/yaml-UiXU3hGj.js +1 -0
- package/server/public/assets/zenscript-C-jEPC9j.js +1 -0
- package/server/public/assets/zig-EbnRGjcz.js +1 -0
- package/server/public/favicon.ico +0 -0
- package/server/public/logo192.png +0 -0
- package/server/public/logo512.png +0 -0
- package/server/public/manifest.json +25 -0
- package/server/public/robots.txt +3 -0
- package/server/public/tanstack-circle-logo.png +0 -0
- package/server/public/tanstack-word-logo-white.svg +1 -0
- package/server/server/_chunks/ssr-renderer.mjs +15 -0
- package/server/server/_libs/@floating-ui/core+[...].mjs +698 -0
- package/server/server/_libs/@floating-ui/dom+[...].mjs +644 -0
- package/server/server/_libs/@floating-ui/react-dom+[...].mjs +839 -0
- package/server/server/_libs/@pierre/diffs+[...].mjs +18578 -0
- package/server/server/_libs/@radix-ui/react-arrow+[...].mjs +174 -0
- package/server/server/_libs/@radix-ui/react-collection+[...].mjs +162 -0
- package/server/server/_libs/@radix-ui/react-dialog+[...].mjs +1666 -0
- package/server/server/_libs/@radix-ui/react-popper+[...].mjs +289 -0
- package/server/server/_libs/@radix-ui/react-radio-group+[...].mjs +420 -0
- package/server/server/_libs/@radix-ui/react-select+[...].mjs +990 -0
- package/server/server/_libs/@tanstack/react-router+[...].mjs +14113 -0
- package/server/server/_libs/_.mjs +2 -0
- package/server/server/_libs/chokidar+readdirp.mjs +1599 -0
- package/server/server/_libs/class-variance-authority+clsx.mjs +69 -0
- package/server/server/_libs/effect+[...].mjs +34047 -0
- package/server/server/_libs/h3+rou3+srvx.mjs +1195 -0
- package/server/server/_libs/hookable.mjs +41 -0
- package/server/server/_libs/lucide-react.mjs +298 -0
- package/server/server/_libs/pierre__theme.mjs +2668 -0
- package/server/server/_libs/radix-ui__number.mjs +6 -0
- package/server/server/_libs/radix-ui__primitive.mjs +9 -0
- package/server/server/_libs/radix-ui__react-direction.mjs +11 -0
- package/server/server/_libs/shiki.mjs +16 -0
- package/server/server/_libs/shikijs__langs.mjs +1355 -0
- package/server/server/_libs/shikijs__themes.mjs +262 -0
- package/server/server/_libs/tailwind-merge.mjs +1962 -0
- package/server/server/_libs/tanstack__history.mjs +342 -0
- package/server/server/_libs/tanstack__router-core.mjs +6 -0
- package/server/server/_libs/ufo.mjs +64 -0
- package/server/server/_reviewId-AWnOGz5k.mjs +33 -0
- package/server/server/_reviewId-Com4yOlc.mjs +29 -0
- package/server/server/_reviewId-DAhmekJ2.mjs +277 -0
- package/server/server/_reviewId-p9mhYVwa.mjs +18 -0
- package/server/server/_runtime.mjs +35 -0
- package/server/server/_ssr/action-bar-C68xGnWW.mjs +592 -0
- package/server/server/_ssr/api-handler-CstW2n82.mjs +189 -0
- package/server/server/_ssr/client-runtime-BoPuAEoA.mjs +245 -0
- package/server/server/_ssr/createServerRpc--0mcGlWK.mjs +12 -0
- package/server/server/_ssr/createSsrRpc-AwdiLXmF.mjs +16 -0
- package/server/server/_ssr/domain-rpc-3Ds9DPr0.mjs +287 -0
- package/server/server/_ssr/file-tree-CQ5w2GHh.mjs +1951 -0
- package/server/server/_ssr/load-scoped-diff-NL2XAcdz.mjs +45 -0
- package/server/server/_ssr/new-BKl_G2Ks.mjs +37 -0
- package/server/server/_ssr/new-BREdMFAM.mjs +12 -0
- package/server/server/_ssr/new-DCz5eHkb.mjs +137 -0
- package/server/server/_ssr/reviews-BL5Nsgst.mjs +7 -0
- package/server/server/_ssr/reviews-BoaEgGKs.mjs +100 -0
- package/server/server/_ssr/reviews-C7_NIhY8.mjs +19 -0
- package/server/server/_ssr/reviews-Dd69YBDa.mjs +12 -0
- package/server/server/_ssr/router-DLxN8FOm.mjs +415 -0
- package/server/server/_ssr/routes-D25G8OuS.mjs +80 -0
- package/server/server/_ssr/routes-lz0AN75A.mjs +929 -0
- package/server/server/_ssr/runtime-D9IbnMlF.mjs +1401 -0
- package/server/server/_ssr/server-runtime-D99qpmma.mjs +12 -0
- package/server/server/_ssr/ssr.mjs +5318 -0
- package/server/server/_ssr/start-BIQfOZtj.mjs +4 -0
- package/server/server/_ssr/test-CQdMYlqa.mjs +6 -0
- package/server/server/_ssr/todo-m_uUvxca.mjs +88 -0
- package/server/server/_ssr/use-keyboard-shortcuts-D5b1Mxpq.mjs +25 -0
- package/server/server/_ssr/utils-BuOt9_LA.mjs +8 -0
- package/server/server/_tanstack-start-manifest_v-CnL10NRH.mjs +71 -0
- package/server/server/index.mjs +2615 -0
- package/server/server/node_modules/detect-libc/lib/detect-libc.js +313 -0
- package/server/server/node_modules/detect-libc/lib/elf.js +39 -0
- package/server/server/node_modules/detect-libc/lib/filesystem.js +51 -0
- package/server/server/node_modules/detect-libc/lib/process.js +24 -0
- package/server/server/node_modules/detect-libc/package.json +44 -0
- package/server/server/node_modules/msgpackr-extract/index.js +1 -0
- package/server/server/node_modules/msgpackr-extract/package.json +50 -0
- package/server/server/node_modules/node-gyp-build-optional-packages/index.js +6 -0
- package/server/server/node_modules/node-gyp-build-optional-packages/node-gyp-build.js +236 -0
- package/server/server/node_modules/node-gyp-build-optional-packages/package.json +32 -0
- package/server/server/node_modules/tslib/modules/index.js +70 -0
- package/server/server/node_modules/tslib/modules/package.json +3 -0
- package/server/server/node_modules/tslib/package.json +47 -0
- package/server/server/node_modules/tslib/tslib.js +484 -0
- package/server/server/package.json +12 -0
|
@@ -0,0 +1,1401 @@
|
|
|
1
|
+
import { $ as acquireRelease, A as offer, Bt as isSome, D as fromQueue, Dt as try_, E as withDefault, Et as tryPromise, Ft as isSuccess, M as sliding, Nt as mergeAll, Pt as provide, R as NullOr, Rt as Service, S as make, St as sync, T as string, U as TaggedErrorClass, V as String$1, _t as orElseSucceed, at as catchTag, dt as gen, et as acquireUseRelease, ht as mapError, j as shutdown, jt as effect, kt as withSpan, lt as flatMap, mt as map, nt as as, rt as asVoid, tt as andThen, ut as fn, vt as promise, xt as succeed, yt as runFork, z as Number$1, zt as getOrNull } from "../_libs/effect+[...].mjs";
|
|
2
|
+
import { p as TodoNotFound, r as CommentNotFound, u as ReviewNotFound } from "./todo-m_uUvxca.mjs";
|
|
3
|
+
import { t as chokidar_default } from "../_libs/chokidar+readdirp.mjs";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { mkdirSync } from "node:fs";
|
|
6
|
+
import { dirname, join, relative } from "node:path";
|
|
7
|
+
import { execFile, spawn } from "node:child_process";
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { DatabaseSync } from "node:sqlite";
|
|
10
|
+
import { platform } from "node:os";
|
|
11
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/runtime-D9IbnMlF.js
|
|
12
|
+
var HUNK_HEADER = /@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
13
|
+
var splitIntoFiles = (diffText) => {
|
|
14
|
+
const files = [];
|
|
15
|
+
const lines = diffText.split("\n");
|
|
16
|
+
let current = [];
|
|
17
|
+
for (const line of lines) if (line.startsWith("diff --git")) {
|
|
18
|
+
if (current.length > 0) files.push(current.join("\n"));
|
|
19
|
+
current = [line];
|
|
20
|
+
} else current.push(line);
|
|
21
|
+
if (current.length > 0) files.push(current.join("\n"));
|
|
22
|
+
return files;
|
|
23
|
+
};
|
|
24
|
+
var parseHunks$1 = (lines) => {
|
|
25
|
+
const hunks = [];
|
|
26
|
+
let currentHunk = null;
|
|
27
|
+
let oldLineNum = 0;
|
|
28
|
+
let newLineNum = 0;
|
|
29
|
+
for (const line of lines) {
|
|
30
|
+
const match = line.match(HUNK_HEADER);
|
|
31
|
+
if (match) {
|
|
32
|
+
if (currentHunk) hunks.push(currentHunk);
|
|
33
|
+
const oldStart = Number.parseInt(match[1], 10);
|
|
34
|
+
const oldLines = Number.parseInt(match[2] ?? "1", 10);
|
|
35
|
+
const newStart = Number.parseInt(match[3], 10);
|
|
36
|
+
currentHunk = {
|
|
37
|
+
lines: [],
|
|
38
|
+
newLines: Number.parseInt(match[4] ?? "1", 10),
|
|
39
|
+
newStart,
|
|
40
|
+
oldLines,
|
|
41
|
+
oldStart
|
|
42
|
+
};
|
|
43
|
+
oldLineNum = oldStart;
|
|
44
|
+
newLineNum = newStart;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (!currentHunk) continue;
|
|
48
|
+
if (line.startsWith("+") && !line.startsWith("+++")) currentHunk.lines.push({
|
|
49
|
+
content: line.slice(1),
|
|
50
|
+
newLineNumber: newLineNum++,
|
|
51
|
+
oldLineNumber: null,
|
|
52
|
+
type: "added"
|
|
53
|
+
});
|
|
54
|
+
else if (line.startsWith("-") && !line.startsWith("---")) currentHunk.lines.push({
|
|
55
|
+
content: line.slice(1),
|
|
56
|
+
newLineNumber: null,
|
|
57
|
+
oldLineNumber: oldLineNum++,
|
|
58
|
+
type: "removed"
|
|
59
|
+
});
|
|
60
|
+
else if (line.startsWith(" ")) currentHunk.lines.push({
|
|
61
|
+
content: line.slice(1),
|
|
62
|
+
newLineNumber: newLineNum++,
|
|
63
|
+
oldLineNumber: oldLineNum++,
|
|
64
|
+
type: "context"
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (currentHunk) hunks.push(currentHunk);
|
|
68
|
+
return hunks;
|
|
69
|
+
};
|
|
70
|
+
var parseFileDiff = (fileDiff) => {
|
|
71
|
+
const lines = fileDiff.split("\n");
|
|
72
|
+
const diffLine = lines.find((l) => l.startsWith("diff --git"));
|
|
73
|
+
if (!diffLine) return null;
|
|
74
|
+
const pathMatch = diffLine.match(/diff --git a\/(.+) b\/(.+)/);
|
|
75
|
+
if (!pathMatch) return null;
|
|
76
|
+
const oldPath = pathMatch[1];
|
|
77
|
+
const newPath = pathMatch[2];
|
|
78
|
+
let status = "modified";
|
|
79
|
+
if (lines.some((l) => l.startsWith("deleted file mode"))) status = "deleted";
|
|
80
|
+
else if (lines.some((l) => l.startsWith("new file mode"))) status = "added";
|
|
81
|
+
else if (lines.some((l) => l.startsWith("rename from")) || oldPath !== newPath) status = "renamed";
|
|
82
|
+
const hunks = parseHunks$1(lines);
|
|
83
|
+
let additions = 0;
|
|
84
|
+
let deletions = 0;
|
|
85
|
+
for (const hunk of hunks) for (const line of hunk.lines) if (line.type === "added") additions++;
|
|
86
|
+
else if (line.type === "removed") deletions++;
|
|
87
|
+
return {
|
|
88
|
+
additions,
|
|
89
|
+
deletions,
|
|
90
|
+
hunks,
|
|
91
|
+
newPath,
|
|
92
|
+
oldPath,
|
|
93
|
+
status
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
/** Parse a full multi-file unified diff into structured DiffFile objects. */
|
|
97
|
+
var parseDiff = (diffText) => {
|
|
98
|
+
if (!diffText.trim()) return [];
|
|
99
|
+
const blocks = splitIntoFiles(diffText);
|
|
100
|
+
const files = [];
|
|
101
|
+
for (const block of blocks) {
|
|
102
|
+
const parsed = parseFileDiff(block);
|
|
103
|
+
if (parsed) files.push(parsed);
|
|
104
|
+
}
|
|
105
|
+
return files;
|
|
106
|
+
};
|
|
107
|
+
/** Aggregate stats from already-parsed files. */
|
|
108
|
+
var getDiffSummary = (files) => {
|
|
109
|
+
let totalAdditions = 0;
|
|
110
|
+
let totalDeletions = 0;
|
|
111
|
+
for (const file of files) {
|
|
112
|
+
totalAdditions += file.additions;
|
|
113
|
+
totalDeletions += file.deletions;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
filesAdded: files.filter((f) => f.status === "added").length,
|
|
117
|
+
filesDeleted: files.filter((f) => f.status === "deleted").length,
|
|
118
|
+
filesModified: files.filter((f) => f.status === "modified").length,
|
|
119
|
+
filesRenamed: files.filter((f) => f.status === "renamed").length,
|
|
120
|
+
totalAdditions,
|
|
121
|
+
totalDeletions,
|
|
122
|
+
totalFiles: files.length
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
var GitError = class extends TaggedErrorClass()("GitError", { message: String$1 }) {};
|
|
126
|
+
/** Max bytes to collect from a git command before truncating (200 MB). */
|
|
127
|
+
var MAX_STDOUT_BYTES$1 = 200 * 1024 * 1024;
|
|
128
|
+
var execGit = (args, repoPath) => tryPromise({
|
|
129
|
+
catch: (error) => new GitError({ message: String(error) }),
|
|
130
|
+
try: () => new Promise((resolve, reject) => {
|
|
131
|
+
const child = spawn("git", [...args], { cwd: repoPath });
|
|
132
|
+
const chunks = [];
|
|
133
|
+
let bytes = 0;
|
|
134
|
+
let truncated = false;
|
|
135
|
+
child.stdout.on("data", (chunk) => {
|
|
136
|
+
if (truncated) return;
|
|
137
|
+
bytes += chunk.length;
|
|
138
|
+
if (bytes > MAX_STDOUT_BYTES$1) {
|
|
139
|
+
truncated = true;
|
|
140
|
+
child.kill();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
chunks.push(chunk);
|
|
144
|
+
});
|
|
145
|
+
let stderr = "";
|
|
146
|
+
child.stderr.on("data", (chunk) => {
|
|
147
|
+
stderr += chunk.toString();
|
|
148
|
+
});
|
|
149
|
+
child.on("error", reject);
|
|
150
|
+
child.on("close", (code) => {
|
|
151
|
+
if (truncated) {
|
|
152
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (code !== 0) reject(/* @__PURE__ */ new Error(`git ${args[0]} exited with code ${code}: ${stderr}`));
|
|
156
|
+
else resolve(Buffer.concat(chunks).toString("utf8"));
|
|
157
|
+
});
|
|
158
|
+
})
|
|
159
|
+
});
|
|
160
|
+
/** Split git output into non-empty lines. */
|
|
161
|
+
var lines = (output) => output.trim().split("\n").filter(Boolean);
|
|
162
|
+
/** Parse name-status output (e.g. `M\tfile.ts`). */
|
|
163
|
+
var parseNameStatus = (output) => lines(output).map((line) => {
|
|
164
|
+
const [status, ...rest] = line.split(" ");
|
|
165
|
+
return {
|
|
166
|
+
path: rest.join(" "),
|
|
167
|
+
status
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
var GitService = class GitService extends Service()("@ringi/GitService") {
|
|
171
|
+
static Default = effect(GitService, gen(function* () {
|
|
172
|
+
const repoPath = yield* string("REPOSITORY_PATH").pipe(withDefault(process.cwd()));
|
|
173
|
+
const hasCommits = execGit(["rev-parse", "HEAD"], repoPath).pipe(as(true), catchTag("GitError", () => succeed(false)), withSpan("GitService.hasCommits"));
|
|
174
|
+
const getRepositoryInfo = gen(function* () {
|
|
175
|
+
const name = yield* execGit(["rev-parse", "--show-toplevel"], repoPath).pipe(map((s) => s.trim().split("/").pop() ?? "unknown"));
|
|
176
|
+
return {
|
|
177
|
+
branch: yield* execGit([
|
|
178
|
+
"rev-parse",
|
|
179
|
+
"--abbrev-ref",
|
|
180
|
+
"HEAD"
|
|
181
|
+
], repoPath).pipe(map((s) => s.trim())),
|
|
182
|
+
name,
|
|
183
|
+
path: repoPath,
|
|
184
|
+
remote: yield* execGit([
|
|
185
|
+
"config",
|
|
186
|
+
"--get",
|
|
187
|
+
"remote.origin.url"
|
|
188
|
+
], repoPath).pipe(map((s) => s.trim() || null), catchTag("GitError", () => succeed(null)))
|
|
189
|
+
};
|
|
190
|
+
}).pipe(withSpan("GitService.getRepositoryInfo"));
|
|
191
|
+
const getStagedDiff = execGit([
|
|
192
|
+
"diff",
|
|
193
|
+
"--cached",
|
|
194
|
+
"--no-color",
|
|
195
|
+
"--unified=3"
|
|
196
|
+
], repoPath).pipe(withSpan("GitService.getStagedDiff"));
|
|
197
|
+
const getUncommittedDiff = hasCommits.pipe(flatMap((has) => has ? execGit([
|
|
198
|
+
"diff",
|
|
199
|
+
"HEAD",
|
|
200
|
+
"--no-color",
|
|
201
|
+
"--unified=3"
|
|
202
|
+
], repoPath) : succeed("")), withSpan("GitService.getUncommittedDiff"));
|
|
203
|
+
const getUnstagedDiff = execGit([
|
|
204
|
+
"diff",
|
|
205
|
+
"--no-color",
|
|
206
|
+
"--unified=3"
|
|
207
|
+
], repoPath).pipe(withSpan("GitService.getUnstagedDiff"));
|
|
208
|
+
const getLastCommitDiff = hasCommits.pipe(flatMap((has) => has ? execGit([
|
|
209
|
+
"show",
|
|
210
|
+
"HEAD",
|
|
211
|
+
"--format=",
|
|
212
|
+
"--no-color",
|
|
213
|
+
"--unified=3"
|
|
214
|
+
], repoPath) : succeed("")), withSpan("GitService.getLastCommitDiff"));
|
|
215
|
+
const getBranchDiff = fn("GitService.getBranchDiff")(function* (branch) {
|
|
216
|
+
return yield* execGit([
|
|
217
|
+
"diff",
|
|
218
|
+
`${branch}...HEAD`,
|
|
219
|
+
"--no-color",
|
|
220
|
+
"--unified=3"
|
|
221
|
+
], repoPath);
|
|
222
|
+
});
|
|
223
|
+
const getCommitDiff = fn("GitService.getCommitDiff")(function* (shas) {
|
|
224
|
+
if (shas.length === 1) return yield* execGit([
|
|
225
|
+
"show",
|
|
226
|
+
shas[0],
|
|
227
|
+
"--format=",
|
|
228
|
+
"--no-color",
|
|
229
|
+
"--unified=3"
|
|
230
|
+
], repoPath);
|
|
231
|
+
const first = shas.at(-1);
|
|
232
|
+
const last = shas[0];
|
|
233
|
+
return yield* execGit([
|
|
234
|
+
"diff",
|
|
235
|
+
`${first}~1..${last}`,
|
|
236
|
+
"--no-color",
|
|
237
|
+
"--unified=3"
|
|
238
|
+
], repoPath);
|
|
239
|
+
});
|
|
240
|
+
const getStagedFiles = execGit([
|
|
241
|
+
"diff",
|
|
242
|
+
"--cached",
|
|
243
|
+
"--name-status"
|
|
244
|
+
], repoPath).pipe(map(parseNameStatus), withSpan("GitService.getStagedFiles"));
|
|
245
|
+
const getUncommittedFiles = hasCommits.pipe(flatMap((has) => has ? execGit([
|
|
246
|
+
"diff",
|
|
247
|
+
"HEAD",
|
|
248
|
+
"--name-status"
|
|
249
|
+
], repoPath).pipe(map(parseNameStatus)) : succeed([])), withSpan("GitService.getUncommittedFiles"));
|
|
250
|
+
const getUnstagedFiles = execGit(["diff", "--name-status"], repoPath).pipe(map(parseNameStatus), withSpan("GitService.getUnstagedFiles"));
|
|
251
|
+
const getLastCommitFiles = hasCommits.pipe(flatMap((has) => has ? execGit([
|
|
252
|
+
"show",
|
|
253
|
+
"HEAD",
|
|
254
|
+
"--format=",
|
|
255
|
+
"--name-status"
|
|
256
|
+
], repoPath).pipe(map(parseNameStatus)) : succeed([])), withSpan("GitService.getLastCommitFiles"));
|
|
257
|
+
const getFileContent = fn("GitService.getFileContent")(function* (filePath, version) {
|
|
258
|
+
switch (version) {
|
|
259
|
+
case "staged": return yield* execGit(["show", `:${filePath}`], repoPath);
|
|
260
|
+
case "head": return yield* execGit(["show", `HEAD:${filePath}`], repoPath);
|
|
261
|
+
default: return yield* tryPromise({
|
|
262
|
+
catch: (error) => new GitError({ message: `Failed to read ${filePath}: ${String(error)}` }),
|
|
263
|
+
try: () => readFile(join(repoPath, filePath), "utf8")
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
const getFileTree = fn("GitService.getFileTree")(function* (ref) {
|
|
268
|
+
return yield* execGit([
|
|
269
|
+
"ls-tree",
|
|
270
|
+
"-r",
|
|
271
|
+
"--name-only",
|
|
272
|
+
ref
|
|
273
|
+
], repoPath).pipe(map(lines));
|
|
274
|
+
});
|
|
275
|
+
const getBranches = execGit(["branch", "--format=%(refname:short) %(HEAD)"], repoPath).pipe(map((output) => lines(output).map((line) => {
|
|
276
|
+
const [name, head] = line.split(" ");
|
|
277
|
+
return {
|
|
278
|
+
current: head === "*",
|
|
279
|
+
name
|
|
280
|
+
};
|
|
281
|
+
})), withSpan("GitService.getBranches"));
|
|
282
|
+
const getCommits = fn("GitService.getCommits")(function* (opts) {
|
|
283
|
+
const limit = (opts.limit ?? 20) + 1;
|
|
284
|
+
const args = [
|
|
285
|
+
"log",
|
|
286
|
+
`--max-count=${limit}`,
|
|
287
|
+
`--skip=${opts.offset ?? 0}`,
|
|
288
|
+
"--format=%H %s %an %aI"
|
|
289
|
+
];
|
|
290
|
+
if (opts.search) args.push(`--grep=${opts.search}`, "-i");
|
|
291
|
+
const rows = lines(yield* execGit(args, repoPath));
|
|
292
|
+
const hasMore = rows.length === limit;
|
|
293
|
+
return {
|
|
294
|
+
commits: (hasMore ? rows.slice(0, -1) : rows).map((line) => {
|
|
295
|
+
const [hash, message, author, date] = line.split(" ");
|
|
296
|
+
return {
|
|
297
|
+
author,
|
|
298
|
+
date,
|
|
299
|
+
hash,
|
|
300
|
+
message
|
|
301
|
+
};
|
|
302
|
+
}),
|
|
303
|
+
hasMore
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
const stageFiles = fn("GitService.stageFiles")(function* (files) {
|
|
307
|
+
return yield* execGit([
|
|
308
|
+
"add",
|
|
309
|
+
"--",
|
|
310
|
+
...files
|
|
311
|
+
], repoPath).pipe(as(files));
|
|
312
|
+
});
|
|
313
|
+
const stageAll = execGit(["add", "-A"], repoPath).pipe(flatMap(() => getStagedFiles), map((files) => files.map((f) => f.path)), withSpan("GitService.stageAll"));
|
|
314
|
+
const unstageFiles = fn("GitService.unstageFiles")(function* (files) {
|
|
315
|
+
return yield* execGit([
|
|
316
|
+
"reset",
|
|
317
|
+
"HEAD",
|
|
318
|
+
"--",
|
|
319
|
+
...files
|
|
320
|
+
], repoPath).pipe(as(files));
|
|
321
|
+
});
|
|
322
|
+
const getRepositoryPath = execGit(["rev-parse", "--show-toplevel"], repoPath).pipe(map((s) => s.trim()), withSpan("GitService.getRepositoryPath"));
|
|
323
|
+
return GitService.of({
|
|
324
|
+
getBranchDiff,
|
|
325
|
+
getBranches,
|
|
326
|
+
getCommitDiff,
|
|
327
|
+
getCommits,
|
|
328
|
+
getFileContent,
|
|
329
|
+
getFileTree,
|
|
330
|
+
getLastCommitDiff,
|
|
331
|
+
getLastCommitFiles,
|
|
332
|
+
getRepositoryInfo,
|
|
333
|
+
getRepositoryPath,
|
|
334
|
+
getStagedDiff,
|
|
335
|
+
getStagedFiles,
|
|
336
|
+
getUncommittedDiff,
|
|
337
|
+
getUncommittedFiles,
|
|
338
|
+
getUnstagedDiff,
|
|
339
|
+
getUnstagedFiles,
|
|
340
|
+
hasCommits,
|
|
341
|
+
stageAll,
|
|
342
|
+
stageFiles,
|
|
343
|
+
unstageFiles
|
|
344
|
+
});
|
|
345
|
+
}));
|
|
346
|
+
};
|
|
347
|
+
var migrations = [
|
|
348
|
+
`CREATE TABLE IF NOT EXISTS reviews (
|
|
349
|
+
id TEXT PRIMARY KEY,
|
|
350
|
+
repository_path TEXT NOT NULL,
|
|
351
|
+
base_ref TEXT,
|
|
352
|
+
snapshot_data TEXT NOT NULL,
|
|
353
|
+
status TEXT DEFAULT 'in_progress',
|
|
354
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
355
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
356
|
+
) STRICT`,
|
|
357
|
+
`CREATE TABLE IF NOT EXISTS comments (
|
|
358
|
+
id TEXT PRIMARY KEY,
|
|
359
|
+
review_id TEXT NOT NULL REFERENCES reviews(id) ON DELETE CASCADE,
|
|
360
|
+
file_path TEXT NOT NULL,
|
|
361
|
+
line_number INTEGER,
|
|
362
|
+
line_type TEXT,
|
|
363
|
+
content TEXT NOT NULL,
|
|
364
|
+
suggestion TEXT,
|
|
365
|
+
resolved INTEGER DEFAULT 0,
|
|
366
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
367
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
368
|
+
) STRICT`,
|
|
369
|
+
`ALTER TABLE reviews ADD COLUMN source_type TEXT DEFAULT 'staged';
|
|
370
|
+
ALTER TABLE reviews ADD COLUMN source_ref TEXT`,
|
|
371
|
+
`CREATE TABLE IF NOT EXISTS todos (
|
|
372
|
+
id TEXT PRIMARY KEY,
|
|
373
|
+
content TEXT NOT NULL,
|
|
374
|
+
completed INTEGER DEFAULT 0,
|
|
375
|
+
review_id TEXT REFERENCES reviews(id) ON DELETE CASCADE,
|
|
376
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
377
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
378
|
+
) STRICT`,
|
|
379
|
+
`ALTER TABLE todos ADD COLUMN position INTEGER DEFAULT 0`,
|
|
380
|
+
`CREATE TABLE IF NOT EXISTS review_files (
|
|
381
|
+
id TEXT PRIMARY KEY,
|
|
382
|
+
review_id TEXT NOT NULL REFERENCES reviews(id) ON DELETE CASCADE,
|
|
383
|
+
file_path TEXT NOT NULL,
|
|
384
|
+
old_path TEXT,
|
|
385
|
+
status TEXT NOT NULL,
|
|
386
|
+
additions INTEGER NOT NULL DEFAULT 0,
|
|
387
|
+
deletions INTEGER NOT NULL DEFAULT 0,
|
|
388
|
+
hunks_data TEXT,
|
|
389
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
390
|
+
) STRICT`
|
|
391
|
+
];
|
|
392
|
+
/** Apply pending migrations using PRAGMA user_version as the version tracker. */
|
|
393
|
+
var runMigrations = (db) => {
|
|
394
|
+
const currentVersion = db.prepare("PRAGMA user_version").get().user_version;
|
|
395
|
+
for (let i = currentVersion; i < migrations.length; i++) {
|
|
396
|
+
const statements = migrations[i].split(";").map((s) => s.trim()).filter(Boolean);
|
|
397
|
+
for (const sql of statements) db.exec(sql);
|
|
398
|
+
db.exec(`PRAGMA user_version = ${i + 1}`);
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
/**
|
|
402
|
+
* Wraps `body` in a SQLite transaction: BEGIN before, COMMIT on success,
|
|
403
|
+
* ROLLBACK on any failure or interruption.
|
|
404
|
+
*/
|
|
405
|
+
var withTransaction = (db, body) => acquireUseRelease(sync(() => db.exec("BEGIN")), () => body, (_, exit) => sync(() => {
|
|
406
|
+
if (isSuccess(exit)) db.exec("COMMIT");
|
|
407
|
+
else db.exec("ROLLBACK");
|
|
408
|
+
}));
|
|
409
|
+
var SqliteService = class SqliteService extends Service()("@ringi/SqliteService") {
|
|
410
|
+
static Default = effect(SqliteService, gen(function* () {
|
|
411
|
+
const dbPath = yield* string("DB_PATH").pipe(withDefault(".ringi/reviews.db"));
|
|
412
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
413
|
+
const db = new DatabaseSync(dbPath);
|
|
414
|
+
db.exec("PRAGMA journal_mode=WAL");
|
|
415
|
+
db.exec("PRAGMA foreign_keys=ON");
|
|
416
|
+
runMigrations(db);
|
|
417
|
+
return SqliteService.of({ db });
|
|
418
|
+
}));
|
|
419
|
+
};
|
|
420
|
+
var rowToComment = (row) => ({
|
|
421
|
+
content: row.content,
|
|
422
|
+
createdAt: row.created_at,
|
|
423
|
+
filePath: row.file_path,
|
|
424
|
+
id: row.id,
|
|
425
|
+
lineNumber: row.line_number,
|
|
426
|
+
lineType: row.line_type,
|
|
427
|
+
resolved: row.resolved === 1,
|
|
428
|
+
reviewId: row.review_id,
|
|
429
|
+
suggestion: row.suggestion,
|
|
430
|
+
updatedAt: row.updated_at
|
|
431
|
+
});
|
|
432
|
+
var CommentRepo = class CommentRepo extends Service()("@ringi/CommentRepo") {
|
|
433
|
+
static Default = effect(CommentRepo, gen(function* () {
|
|
434
|
+
const { db } = yield* SqliteService;
|
|
435
|
+
const stmtFindById = db.prepare("SELECT * FROM comments WHERE id = ?");
|
|
436
|
+
const stmtFindByReview = db.prepare("SELECT * FROM comments WHERE review_id = ? ORDER BY created_at ASC");
|
|
437
|
+
const stmtFindByFile = db.prepare("SELECT * FROM comments WHERE review_id = ? AND file_path = ? ORDER BY line_number ASC, created_at ASC");
|
|
438
|
+
const stmtInsert = db.prepare(`INSERT INTO comments (id, review_id, file_path, line_number, line_type, content, suggestion, resolved, created_at, updated_at)
|
|
439
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, datetime('now'), datetime('now'))`);
|
|
440
|
+
const stmtDelete = db.prepare("DELETE FROM comments WHERE id = ?");
|
|
441
|
+
const stmtDeleteByReview = db.prepare("DELETE FROM comments WHERE review_id = ?");
|
|
442
|
+
const stmtSetResolved = db.prepare("UPDATE comments SET resolved = ?, updated_at = datetime('now') WHERE id = ?");
|
|
443
|
+
const stmtCountByReview = db.prepare(`SELECT
|
|
444
|
+
COUNT(*) as total,
|
|
445
|
+
SUM(CASE WHEN resolved = 1 THEN 1 ELSE 0 END) as resolved,
|
|
446
|
+
SUM(CASE WHEN resolved = 0 THEN 1 ELSE 0 END) as unresolved,
|
|
447
|
+
SUM(CASE WHEN suggestion IS NOT NULL THEN 1 ELSE 0 END) as with_suggestions
|
|
448
|
+
FROM comments WHERE review_id = ?`);
|
|
449
|
+
const findById = (id) => sync(() => {
|
|
450
|
+
const row = stmtFindById.get(id);
|
|
451
|
+
return row ? rowToComment(row) : null;
|
|
452
|
+
});
|
|
453
|
+
const findByReview = (reviewId) => sync(() => {
|
|
454
|
+
return stmtFindByReview.all(reviewId).map(rowToComment);
|
|
455
|
+
});
|
|
456
|
+
const findByFile = (reviewId, filePath) => sync(() => {
|
|
457
|
+
return stmtFindByFile.all(reviewId, filePath).map(rowToComment);
|
|
458
|
+
});
|
|
459
|
+
const create = (input) => sync(() => {
|
|
460
|
+
stmtInsert.run(input.id, input.reviewId, input.filePath, input.lineNumber, input.lineType, input.content, input.suggestion);
|
|
461
|
+
return rowToComment(stmtFindById.get(input.id));
|
|
462
|
+
});
|
|
463
|
+
const update = (id, updates) => sync(() => {
|
|
464
|
+
const setClauses = [];
|
|
465
|
+
const params = [];
|
|
466
|
+
if (updates.content !== void 0) {
|
|
467
|
+
setClauses.push("content = ?");
|
|
468
|
+
params.push(updates.content);
|
|
469
|
+
}
|
|
470
|
+
if (updates.suggestion !== void 0) {
|
|
471
|
+
setClauses.push("suggestion = ?");
|
|
472
|
+
params.push(updates.suggestion);
|
|
473
|
+
}
|
|
474
|
+
if (setClauses.length === 0) {
|
|
475
|
+
const row = stmtFindById.get(id);
|
|
476
|
+
return row ? rowToComment(row) : null;
|
|
477
|
+
}
|
|
478
|
+
setClauses.push("updated_at = datetime('now')");
|
|
479
|
+
params.push(id);
|
|
480
|
+
db.prepare(`UPDATE comments SET ${setClauses.join(", ")} WHERE id = ?`).run(...params);
|
|
481
|
+
const row = stmtFindById.get(id);
|
|
482
|
+
return row ? rowToComment(row) : null;
|
|
483
|
+
});
|
|
484
|
+
const setResolved = (id, resolved) => sync(() => {
|
|
485
|
+
stmtSetResolved.run(resolved ? 1 : 0, id);
|
|
486
|
+
const row = stmtFindById.get(id);
|
|
487
|
+
return row ? rowToComment(row) : null;
|
|
488
|
+
});
|
|
489
|
+
const remove = (id) => sync(() => {
|
|
490
|
+
const result = stmtDelete.run(id);
|
|
491
|
+
return Number(result.changes) > 0;
|
|
492
|
+
});
|
|
493
|
+
const removeByReview = (reviewId) => sync(() => {
|
|
494
|
+
const result = stmtDeleteByReview.run(reviewId);
|
|
495
|
+
return Number(result.changes);
|
|
496
|
+
});
|
|
497
|
+
const countByReview = (reviewId) => sync(() => {
|
|
498
|
+
const row = stmtCountByReview.get(reviewId);
|
|
499
|
+
return {
|
|
500
|
+
resolved: row.resolved,
|
|
501
|
+
total: row.total,
|
|
502
|
+
unresolved: row.unresolved,
|
|
503
|
+
withSuggestions: row.with_suggestions
|
|
504
|
+
};
|
|
505
|
+
});
|
|
506
|
+
return CommentRepo.of({
|
|
507
|
+
countByReview,
|
|
508
|
+
create,
|
|
509
|
+
findByFile,
|
|
510
|
+
findById,
|
|
511
|
+
findByReview,
|
|
512
|
+
remove,
|
|
513
|
+
removeByReview,
|
|
514
|
+
setResolved,
|
|
515
|
+
update
|
|
516
|
+
});
|
|
517
|
+
}));
|
|
518
|
+
};
|
|
519
|
+
var parseHunks = (hunksData) => hunksData == null ? succeed([]) : try_({
|
|
520
|
+
try: () => JSON.parse(hunksData),
|
|
521
|
+
catch: () => []
|
|
522
|
+
}).pipe(orElseSucceed(() => []));
|
|
523
|
+
var serializeHunks = (hunks) => JSON.stringify(hunks);
|
|
524
|
+
var ReviewFileRepo = class ReviewFileRepo extends Service()("@ringi/ReviewFileRepo") {
|
|
525
|
+
static Default = effect(ReviewFileRepo, gen(function* () {
|
|
526
|
+
const { db } = yield* SqliteService;
|
|
527
|
+
const stmtFindByReview = db.prepare(`SELECT id, review_id, file_path, old_path, status, additions, deletions, created_at
|
|
528
|
+
FROM review_files WHERE review_id = ? ORDER BY file_path`);
|
|
529
|
+
const stmtFindByReviewAndPath = db.prepare("SELECT * FROM review_files WHERE review_id = ? AND file_path = ?");
|
|
530
|
+
const stmtInsert = db.prepare(`INSERT INTO review_files (id, review_id, file_path, old_path, status, additions, deletions, hunks_data, created_at)
|
|
531
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`);
|
|
532
|
+
const stmtDeleteByReview = db.prepare("DELETE FROM review_files WHERE review_id = ?");
|
|
533
|
+
const stmtCountByReview = db.prepare("SELECT COUNT(*) as count FROM review_files WHERE review_id = ?");
|
|
534
|
+
const findByReview = (reviewId) => sync(() => stmtFindByReview.all(reviewId));
|
|
535
|
+
const findByReviewAndPath = (reviewId, filePath) => sync(() => {
|
|
536
|
+
return stmtFindByReviewAndPath.get(reviewId, filePath) ?? null;
|
|
537
|
+
});
|
|
538
|
+
const createBulk = (files) => withTransaction(db, sync(() => {
|
|
539
|
+
for (const f of files) stmtInsert.run(randomUUID(), f.reviewId, f.filePath, f.oldPath, f.status, f.additions, f.deletions, f.hunksData);
|
|
540
|
+
}));
|
|
541
|
+
const deleteByReview = (reviewId) => sync(() => {
|
|
542
|
+
const result = stmtDeleteByReview.run(reviewId);
|
|
543
|
+
return Number(result.changes);
|
|
544
|
+
});
|
|
545
|
+
const countByReview = (reviewId) => sync(() => {
|
|
546
|
+
return stmtCountByReview.get(reviewId).count;
|
|
547
|
+
});
|
|
548
|
+
return ReviewFileRepo.of({
|
|
549
|
+
countByReview,
|
|
550
|
+
createBulk,
|
|
551
|
+
deleteByReview,
|
|
552
|
+
findByReview,
|
|
553
|
+
findByReviewAndPath
|
|
554
|
+
});
|
|
555
|
+
}));
|
|
556
|
+
};
|
|
557
|
+
var rowToReview = (row) => ({
|
|
558
|
+
baseRef: row.base_ref,
|
|
559
|
+
createdAt: row.created_at,
|
|
560
|
+
id: row.id,
|
|
561
|
+
repositoryPath: row.repository_path,
|
|
562
|
+
snapshotData: row.snapshot_data,
|
|
563
|
+
sourceRef: row.source_ref,
|
|
564
|
+
sourceType: row.source_type,
|
|
565
|
+
status: row.status,
|
|
566
|
+
updatedAt: row.updated_at
|
|
567
|
+
});
|
|
568
|
+
var ReviewRepo = class ReviewRepo extends Service()("@ringi/ReviewRepo") {
|
|
569
|
+
static Default = effect(ReviewRepo, gen(function* () {
|
|
570
|
+
const { db } = yield* SqliteService;
|
|
571
|
+
const stmtFindById = db.prepare("SELECT * FROM reviews WHERE id = ?");
|
|
572
|
+
const stmtInsert = db.prepare(`INSERT INTO reviews (id, repository_path, base_ref, source_type, source_ref, snapshot_data, status, created_at, updated_at)
|
|
573
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`);
|
|
574
|
+
const stmtUpdate = db.prepare(`UPDATE reviews SET status = COALESCE(?, status), updated_at = datetime('now') WHERE id = ?`);
|
|
575
|
+
const stmtUpdateSnapshot = db.prepare(`UPDATE reviews SET snapshot_data = ?, updated_at = datetime('now') WHERE id = ?`);
|
|
576
|
+
const stmtDelete = db.prepare("DELETE FROM reviews WHERE id = ?");
|
|
577
|
+
const stmtCountAll = db.prepare("SELECT COUNT(*) as count FROM reviews");
|
|
578
|
+
const stmtCountByStatus = db.prepare("SELECT COUNT(*) as count FROM reviews WHERE status = ?");
|
|
579
|
+
const findById = (id) => sync(() => {
|
|
580
|
+
const row = stmtFindById.get(id);
|
|
581
|
+
return row ? rowToReview(row) : null;
|
|
582
|
+
});
|
|
583
|
+
const findAll = (opts = {}) => sync(() => {
|
|
584
|
+
const conditions = [];
|
|
585
|
+
const params = [];
|
|
586
|
+
if (opts.status != null) {
|
|
587
|
+
conditions.push("status = ?");
|
|
588
|
+
params.push(opts.status);
|
|
589
|
+
}
|
|
590
|
+
if (opts.repositoryPath != null) {
|
|
591
|
+
conditions.push("repository_path = ?");
|
|
592
|
+
params.push(opts.repositoryPath);
|
|
593
|
+
}
|
|
594
|
+
if (opts.sourceType != null) {
|
|
595
|
+
conditions.push("source_type = ?");
|
|
596
|
+
params.push(opts.sourceType);
|
|
597
|
+
}
|
|
598
|
+
const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
|
|
599
|
+
const page = opts.page ?? 1;
|
|
600
|
+
const pageSize = opts.pageSize ?? 20;
|
|
601
|
+
const offset = (page - 1) * pageSize;
|
|
602
|
+
const totalRow = db.prepare(`SELECT COUNT(*) as count FROM reviews${where}`).get(...params);
|
|
603
|
+
return {
|
|
604
|
+
data: db.prepare(`SELECT * FROM reviews${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...params, pageSize, offset).map(rowToReview),
|
|
605
|
+
total: totalRow.count
|
|
606
|
+
};
|
|
607
|
+
});
|
|
608
|
+
const create = (input) => sync(() => {
|
|
609
|
+
stmtInsert.run(input.id, input.repositoryPath, input.baseRef, input.sourceType, input.sourceRef, input.snapshotData, input.status);
|
|
610
|
+
return rowToReview(stmtFindById.get(input.id));
|
|
611
|
+
});
|
|
612
|
+
const update = (id, status) => sync(() => {
|
|
613
|
+
stmtUpdate.run(status, id);
|
|
614
|
+
const row = stmtFindById.get(id);
|
|
615
|
+
return row ? rowToReview(row) : null;
|
|
616
|
+
});
|
|
617
|
+
const updateSnapshotData = (id, snapshotData) => sync(() => {
|
|
618
|
+
stmtUpdateSnapshot.run(snapshotData, id);
|
|
619
|
+
const row = stmtFindById.get(id);
|
|
620
|
+
return row ? rowToReview(row) : null;
|
|
621
|
+
});
|
|
622
|
+
const remove = (id) => sync(() => {
|
|
623
|
+
const result = stmtDelete.run(id);
|
|
624
|
+
return Number(result.changes) > 0;
|
|
625
|
+
});
|
|
626
|
+
const countAll = () => sync(() => {
|
|
627
|
+
return stmtCountAll.get().count;
|
|
628
|
+
});
|
|
629
|
+
const countByStatus = (status) => sync(() => {
|
|
630
|
+
return stmtCountByStatus.get(status).count;
|
|
631
|
+
});
|
|
632
|
+
return ReviewRepo.of({
|
|
633
|
+
countAll,
|
|
634
|
+
countByStatus,
|
|
635
|
+
create,
|
|
636
|
+
findAll,
|
|
637
|
+
findById,
|
|
638
|
+
remove,
|
|
639
|
+
update,
|
|
640
|
+
updateSnapshotData
|
|
641
|
+
});
|
|
642
|
+
}));
|
|
643
|
+
};
|
|
644
|
+
var rowToTodo = (row) => ({
|
|
645
|
+
completed: row.completed === 1,
|
|
646
|
+
content: row.content,
|
|
647
|
+
createdAt: row.created_at,
|
|
648
|
+
id: row.id,
|
|
649
|
+
position: row.position,
|
|
650
|
+
reviewId: row.review_id,
|
|
651
|
+
updatedAt: row.updated_at
|
|
652
|
+
});
|
|
653
|
+
var TodoRepo = class TodoRepo extends Service()("@ringi/TodoRepo") {
|
|
654
|
+
static Default = effect(TodoRepo, gen(function* () {
|
|
655
|
+
const { db } = yield* SqliteService;
|
|
656
|
+
const stmtFindById = db.prepare("SELECT * FROM todos WHERE id = ?");
|
|
657
|
+
const stmtInsert = db.prepare(`INSERT INTO todos (id, content, completed, review_id, position, created_at, updated_at)
|
|
658
|
+
VALUES (?, ?, 0, ?, ?, datetime('now'), datetime('now'))`);
|
|
659
|
+
const stmtDelete = db.prepare("DELETE FROM todos WHERE id = ?");
|
|
660
|
+
const stmtDeleteCompleted = db.prepare("DELETE FROM todos WHERE completed = 1");
|
|
661
|
+
const stmtNextPosition = db.prepare("SELECT COALESCE(MAX(position), -1) + 1 AS next_pos FROM todos");
|
|
662
|
+
const stmtCountAll = db.prepare("SELECT COUNT(*) as count FROM todos");
|
|
663
|
+
const stmtCountCompleted = db.prepare("SELECT COUNT(*) as count FROM todos WHERE completed = 1");
|
|
664
|
+
const stmtCountPending = db.prepare("SELECT COUNT(*) as count FROM todos WHERE completed = 0");
|
|
665
|
+
const findById = (id) => sync(() => {
|
|
666
|
+
const row = stmtFindById.get(id);
|
|
667
|
+
return row ? rowToTodo(row) : null;
|
|
668
|
+
});
|
|
669
|
+
const findAll = (opts = {}) => sync(() => {
|
|
670
|
+
const conditions = [];
|
|
671
|
+
const params = [];
|
|
672
|
+
if (opts.reviewId != null) {
|
|
673
|
+
conditions.push("review_id = ?");
|
|
674
|
+
params.push(opts.reviewId);
|
|
675
|
+
}
|
|
676
|
+
if (opts.completed != null) {
|
|
677
|
+
conditions.push("completed = ?");
|
|
678
|
+
params.push(opts.completed ? 1 : 0);
|
|
679
|
+
}
|
|
680
|
+
const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
|
|
681
|
+
const totalRow = db.prepare(`SELECT COUNT(*) as count FROM todos${where}`).get(...params);
|
|
682
|
+
const limitClause = opts.limit != null ? ` LIMIT ? OFFSET ?` : "";
|
|
683
|
+
const queryParams = opts.limit != null ? [
|
|
684
|
+
...params,
|
|
685
|
+
opts.limit,
|
|
686
|
+
opts.offset ?? 0
|
|
687
|
+
] : params;
|
|
688
|
+
return {
|
|
689
|
+
data: db.prepare(`SELECT * FROM todos${where} ORDER BY position ASC${limitClause}`).all(...queryParams).map(rowToTodo),
|
|
690
|
+
total: totalRow.count
|
|
691
|
+
};
|
|
692
|
+
});
|
|
693
|
+
const create = (input) => sync(() => {
|
|
694
|
+
const { next_pos } = stmtNextPosition.get();
|
|
695
|
+
stmtInsert.run(input.id, input.content, input.reviewId, next_pos);
|
|
696
|
+
return rowToTodo(stmtFindById.get(input.id));
|
|
697
|
+
});
|
|
698
|
+
const update = (id, updates) => sync(() => {
|
|
699
|
+
const sets = [];
|
|
700
|
+
const params = [];
|
|
701
|
+
if (updates.content != null) {
|
|
702
|
+
sets.push("content = ?");
|
|
703
|
+
params.push(updates.content);
|
|
704
|
+
}
|
|
705
|
+
if (updates.completed != null) {
|
|
706
|
+
sets.push("completed = ?");
|
|
707
|
+
params.push(updates.completed ? 1 : 0);
|
|
708
|
+
}
|
|
709
|
+
if (sets.length === 0) {
|
|
710
|
+
const row = stmtFindById.get(id);
|
|
711
|
+
return row ? rowToTodo(row) : null;
|
|
712
|
+
}
|
|
713
|
+
sets.push("updated_at = datetime('now')");
|
|
714
|
+
params.push(id);
|
|
715
|
+
db.prepare(`UPDATE todos SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
716
|
+
const row = stmtFindById.get(id);
|
|
717
|
+
return row ? rowToTodo(row) : null;
|
|
718
|
+
});
|
|
719
|
+
const toggle = (id) => sync(() => {
|
|
720
|
+
const row = stmtFindById.get(id);
|
|
721
|
+
if (!row) return null;
|
|
722
|
+
const newCompleted = row.completed === 1 ? 0 : 1;
|
|
723
|
+
db.prepare("UPDATE todos SET completed = ?, updated_at = datetime('now') WHERE id = ?").run(newCompleted, id);
|
|
724
|
+
return rowToTodo(stmtFindById.get(id));
|
|
725
|
+
});
|
|
726
|
+
const remove = (id) => sync(() => {
|
|
727
|
+
const result = stmtDelete.run(id);
|
|
728
|
+
return Number(result.changes) > 0;
|
|
729
|
+
});
|
|
730
|
+
const removeCompleted = () => sync(() => {
|
|
731
|
+
const result = stmtDeleteCompleted.run();
|
|
732
|
+
return Number(result.changes);
|
|
733
|
+
});
|
|
734
|
+
const reorder = (orderedIds) => withTransaction(db, sync(() => {
|
|
735
|
+
const stmt = db.prepare("UPDATE todos SET position = ?, updated_at = datetime('now') WHERE id = ?");
|
|
736
|
+
let updated = 0;
|
|
737
|
+
for (let i = 0; i < orderedIds.length; i++) {
|
|
738
|
+
const result = stmt.run(i, orderedIds[i]);
|
|
739
|
+
updated += Number(result.changes);
|
|
740
|
+
}
|
|
741
|
+
return updated;
|
|
742
|
+
}));
|
|
743
|
+
const move = (id, newPosition) => gen(function* () {
|
|
744
|
+
const row = stmtFindById.get(id);
|
|
745
|
+
if (!row) return null;
|
|
746
|
+
const oldPosition = row.position;
|
|
747
|
+
yield* withTransaction(db, sync(() => {
|
|
748
|
+
if (newPosition < oldPosition) db.prepare("UPDATE todos SET position = position + 1, updated_at = datetime('now') WHERE position >= ? AND position < ? AND id != ?").run(newPosition, oldPosition, id);
|
|
749
|
+
else if (newPosition > oldPosition) db.prepare("UPDATE todos SET position = position - 1, updated_at = datetime('now') WHERE position > ? AND position <= ? AND id != ?").run(oldPosition, newPosition, id);
|
|
750
|
+
db.prepare("UPDATE todos SET position = ?, updated_at = datetime('now') WHERE id = ?").run(newPosition, id);
|
|
751
|
+
}));
|
|
752
|
+
return rowToTodo(stmtFindById.get(id));
|
|
753
|
+
});
|
|
754
|
+
const countAll = () => sync(() => {
|
|
755
|
+
return stmtCountAll.get().count;
|
|
756
|
+
});
|
|
757
|
+
const countCompleted = () => sync(() => {
|
|
758
|
+
return stmtCountCompleted.get().count;
|
|
759
|
+
});
|
|
760
|
+
const countPending = () => sync(() => {
|
|
761
|
+
return stmtCountPending.get().count;
|
|
762
|
+
});
|
|
763
|
+
return TodoRepo.of({
|
|
764
|
+
countAll,
|
|
765
|
+
countCompleted,
|
|
766
|
+
countPending,
|
|
767
|
+
create,
|
|
768
|
+
findAll,
|
|
769
|
+
findById,
|
|
770
|
+
move,
|
|
771
|
+
remove,
|
|
772
|
+
removeCompleted,
|
|
773
|
+
reorder,
|
|
774
|
+
toggle,
|
|
775
|
+
update
|
|
776
|
+
});
|
|
777
|
+
}));
|
|
778
|
+
};
|
|
779
|
+
var CommentService = class CommentService extends Service()("@ringi/CommentService") {
|
|
780
|
+
static Default = effect(CommentService, gen(function* () {
|
|
781
|
+
const repo = yield* CommentRepo;
|
|
782
|
+
const create = (reviewId, input) => {
|
|
783
|
+
const id = randomUUID();
|
|
784
|
+
return repo.create({
|
|
785
|
+
content: input.content,
|
|
786
|
+
filePath: input.filePath,
|
|
787
|
+
id,
|
|
788
|
+
lineNumber: input.lineNumber ?? null,
|
|
789
|
+
lineType: input.lineType ?? null,
|
|
790
|
+
reviewId,
|
|
791
|
+
suggestion: input.suggestion ?? null
|
|
792
|
+
});
|
|
793
|
+
};
|
|
794
|
+
const getById = (id) => gen(function* () {
|
|
795
|
+
const comment = yield* repo.findById(id);
|
|
796
|
+
if (!comment) return yield* new CommentNotFound({ id });
|
|
797
|
+
return comment;
|
|
798
|
+
});
|
|
799
|
+
const getByReview = (reviewId) => repo.findByReview(reviewId);
|
|
800
|
+
const getByFile = (reviewId, filePath) => repo.findByFile(reviewId, filePath);
|
|
801
|
+
const update = (id, input) => gen(function* () {
|
|
802
|
+
const updates = {};
|
|
803
|
+
if (input.content && isSome(input.content)) updates.content = input.content.value;
|
|
804
|
+
if (input.suggestion && isSome(input.suggestion)) updates.suggestion = input.suggestion.value;
|
|
805
|
+
const comment = yield* repo.update(id, updates);
|
|
806
|
+
if (!comment) return yield* new CommentNotFound({ id });
|
|
807
|
+
return comment;
|
|
808
|
+
});
|
|
809
|
+
const resolve = (id) => gen(function* () {
|
|
810
|
+
const comment = yield* repo.setResolved(id, true);
|
|
811
|
+
if (!comment) return yield* new CommentNotFound({ id });
|
|
812
|
+
return comment;
|
|
813
|
+
});
|
|
814
|
+
const unresolve = (id) => gen(function* () {
|
|
815
|
+
const comment = yield* repo.setResolved(id, false);
|
|
816
|
+
if (!comment) return yield* new CommentNotFound({ id });
|
|
817
|
+
return comment;
|
|
818
|
+
});
|
|
819
|
+
const remove = (id) => gen(function* () {
|
|
820
|
+
if (!(yield* repo.remove(id))) return yield* new CommentNotFound({ id });
|
|
821
|
+
return { success: true };
|
|
822
|
+
});
|
|
823
|
+
const getStats = (reviewId) => repo.countByReview(reviewId);
|
|
824
|
+
return CommentService.of({
|
|
825
|
+
create,
|
|
826
|
+
getByFile,
|
|
827
|
+
getById,
|
|
828
|
+
getByReview,
|
|
829
|
+
getStats,
|
|
830
|
+
remove,
|
|
831
|
+
resolve,
|
|
832
|
+
unresolve,
|
|
833
|
+
update
|
|
834
|
+
});
|
|
835
|
+
}));
|
|
836
|
+
};
|
|
837
|
+
var EventService = class EventService extends Service()("@ringi/EventService") {
|
|
838
|
+
static Default = effect(EventService, sync(() => {
|
|
839
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
840
|
+
const broadcast = (type, data) => gen(function* () {
|
|
841
|
+
const event = {
|
|
842
|
+
data,
|
|
843
|
+
timestamp: Date.now(),
|
|
844
|
+
type
|
|
845
|
+
};
|
|
846
|
+
for (const queue of subscribers) yield* offer(queue, event);
|
|
847
|
+
});
|
|
848
|
+
const subscribe = () => gen(function* () {
|
|
849
|
+
const queue = yield* sliding(100);
|
|
850
|
+
subscribers.add(queue);
|
|
851
|
+
return {
|
|
852
|
+
stream: fromQueue(queue),
|
|
853
|
+
unsubscribe: sync(() => {
|
|
854
|
+
subscribers.delete(queue);
|
|
855
|
+
}).pipe(andThen(shutdown(queue)))
|
|
856
|
+
};
|
|
857
|
+
});
|
|
858
|
+
const startFileWatcher = (repoPath) => acquireRelease(sync(() => {
|
|
859
|
+
let debounceTimer = null;
|
|
860
|
+
const watcher = chokidar_default.watch(repoPath, {
|
|
861
|
+
ignoreInitial: true,
|
|
862
|
+
ignored: [
|
|
863
|
+
"**/node_modules/**",
|
|
864
|
+
"**/.git/**",
|
|
865
|
+
"**/.ringi/**",
|
|
866
|
+
"**/dist/**"
|
|
867
|
+
],
|
|
868
|
+
persistent: true,
|
|
869
|
+
...platform() === "darwin" ? {
|
|
870
|
+
interval: 1e3,
|
|
871
|
+
usePolling: true
|
|
872
|
+
} : {}
|
|
873
|
+
});
|
|
874
|
+
const debouncedBroadcast = (filePath) => {
|
|
875
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
876
|
+
debounceTimer = setTimeout(() => {
|
|
877
|
+
runFork(broadcast("files", { path: relative(repoPath, filePath) }));
|
|
878
|
+
}, 300);
|
|
879
|
+
};
|
|
880
|
+
watcher.on("add", debouncedBroadcast);
|
|
881
|
+
watcher.on("change", debouncedBroadcast);
|
|
882
|
+
watcher.on("unlink", debouncedBroadcast);
|
|
883
|
+
return watcher;
|
|
884
|
+
}), (watcher) => promise(() => watcher.close()));
|
|
885
|
+
const getClientCount = () => sync(() => subscribers.size);
|
|
886
|
+
return EventService.of({
|
|
887
|
+
broadcast,
|
|
888
|
+
getClientCount,
|
|
889
|
+
startFileWatcher,
|
|
890
|
+
subscribe
|
|
891
|
+
});
|
|
892
|
+
}));
|
|
893
|
+
};
|
|
894
|
+
var ReviewError = class extends TaggedErrorClass()("ReviewError", {
|
|
895
|
+
code: String$1,
|
|
896
|
+
message: String$1
|
|
897
|
+
}) {};
|
|
898
|
+
var getHeadSha = (repoPath) => tryPromise({
|
|
899
|
+
catch: () => new ReviewError({
|
|
900
|
+
code: "GIT_ERROR",
|
|
901
|
+
message: "Failed to get HEAD"
|
|
902
|
+
}),
|
|
903
|
+
try: () => new Promise((resolve, reject) => {
|
|
904
|
+
execFile("git", ["rev-parse", "HEAD"], { cwd: repoPath }, (err, stdout) => {
|
|
905
|
+
if (err) reject(err);
|
|
906
|
+
else resolve(stdout.trim());
|
|
907
|
+
});
|
|
908
|
+
})
|
|
909
|
+
});
|
|
910
|
+
var parseSnapshotData = (s) => try_({
|
|
911
|
+
try: () => JSON.parse(s),
|
|
912
|
+
catch: () => ({})
|
|
913
|
+
}).pipe(orElseSucceed(() => ({})));
|
|
914
|
+
var ReviewService = class ReviewService extends Service()("@ringi/ReviewService") {
|
|
915
|
+
static Default = effect(ReviewService, gen(function* () {
|
|
916
|
+
const git = yield* GitService;
|
|
917
|
+
const repo = yield* ReviewRepo;
|
|
918
|
+
const fileRepo = yield* ReviewFileRepo;
|
|
919
|
+
const create = (input) => gen(function* () {
|
|
920
|
+
const repoPath = yield* git.getRepositoryPath;
|
|
921
|
+
if (!(yield* git.hasCommits)) return yield* new ReviewError({
|
|
922
|
+
code: "NO_COMMITS",
|
|
923
|
+
message: "Repository has no commits"
|
|
924
|
+
});
|
|
925
|
+
let diffText;
|
|
926
|
+
let baseRef = null;
|
|
927
|
+
const { sourceType, sourceRef } = input;
|
|
928
|
+
switch (sourceType) {
|
|
929
|
+
case "staged":
|
|
930
|
+
diffText = yield* git.getStagedDiff;
|
|
931
|
+
if (!diffText.trim()) return yield* new ReviewError({
|
|
932
|
+
code: "NO_STAGED_CHANGES",
|
|
933
|
+
message: "No staged changes"
|
|
934
|
+
});
|
|
935
|
+
baseRef = yield* getHeadSha(repoPath);
|
|
936
|
+
break;
|
|
937
|
+
case "branch":
|
|
938
|
+
if (!sourceRef) return yield* new ReviewError({
|
|
939
|
+
code: "INVALID_SOURCE",
|
|
940
|
+
message: "Branch name required"
|
|
941
|
+
});
|
|
942
|
+
diffText = yield* git.getBranchDiff(sourceRef);
|
|
943
|
+
baseRef = sourceRef;
|
|
944
|
+
break;
|
|
945
|
+
case "commits": {
|
|
946
|
+
if (!sourceRef) return yield* new ReviewError({
|
|
947
|
+
code: "INVALID_SOURCE",
|
|
948
|
+
message: "Commit SHAs required"
|
|
949
|
+
});
|
|
950
|
+
const shas = sourceRef.split(",").map((s) => s.trim()).filter(Boolean);
|
|
951
|
+
if (shas.length === 0) return yield* new ReviewError({
|
|
952
|
+
code: "INVALID_SOURCE",
|
|
953
|
+
message: "No valid commit SHAs"
|
|
954
|
+
});
|
|
955
|
+
diffText = yield* git.getCommitDiff(shas);
|
|
956
|
+
baseRef = shas.at(-1) ?? null;
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
default: return yield* new ReviewError({
|
|
960
|
+
code: "INVALID_SOURCE",
|
|
961
|
+
message: "Unsupported review source"
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
const files = parseDiff(diffText);
|
|
965
|
+
if (files.length === 0) return yield* new ReviewError({
|
|
966
|
+
code: "NO_CHANGES",
|
|
967
|
+
message: "No changes found"
|
|
968
|
+
});
|
|
969
|
+
const repoInfo = yield* git.getRepositoryInfo;
|
|
970
|
+
const reviewId = crypto.randomUUID();
|
|
971
|
+
const storeHunks = sourceType === "staged";
|
|
972
|
+
const fileInputs = files.map((f) => ({
|
|
973
|
+
additions: f.additions,
|
|
974
|
+
deletions: f.deletions,
|
|
975
|
+
filePath: f.newPath,
|
|
976
|
+
hunksData: storeHunks ? serializeHunks(f.hunks) : null,
|
|
977
|
+
oldPath: f.oldPath !== f.newPath ? f.oldPath : null,
|
|
978
|
+
reviewId,
|
|
979
|
+
status: f.status
|
|
980
|
+
}));
|
|
981
|
+
const snapshotData = JSON.stringify({
|
|
982
|
+
repository: repoInfo,
|
|
983
|
+
version: 2
|
|
984
|
+
});
|
|
985
|
+
const review = yield* repo.create({
|
|
986
|
+
baseRef,
|
|
987
|
+
id: reviewId,
|
|
988
|
+
repositoryPath: repoPath,
|
|
989
|
+
snapshotData,
|
|
990
|
+
sourceRef: sourceRef ?? null,
|
|
991
|
+
sourceType,
|
|
992
|
+
status: "in_progress"
|
|
993
|
+
});
|
|
994
|
+
yield* fileRepo.createBulk(fileInputs);
|
|
995
|
+
return review;
|
|
996
|
+
});
|
|
997
|
+
const list = (opts) => gen(function* () {
|
|
998
|
+
const page = opts.page ?? 1;
|
|
999
|
+
const pageSize = opts.pageSize ?? 20;
|
|
1000
|
+
const result = yield* repo.findAll({
|
|
1001
|
+
page,
|
|
1002
|
+
pageSize,
|
|
1003
|
+
repositoryPath: opts.repositoryPath,
|
|
1004
|
+
sourceType: opts.sourceType,
|
|
1005
|
+
status: opts.status
|
|
1006
|
+
});
|
|
1007
|
+
const reviews = [];
|
|
1008
|
+
for (const review of result.data) {
|
|
1009
|
+
const fileCount = yield* fileRepo.countByReview(review.id);
|
|
1010
|
+
const snapshot = yield* parseSnapshotData(review.snapshotData);
|
|
1011
|
+
reviews.push({
|
|
1012
|
+
...review,
|
|
1013
|
+
fileCount,
|
|
1014
|
+
repository: snapshot.repository ?? null
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
return {
|
|
1018
|
+
hasMore: page * pageSize < result.total,
|
|
1019
|
+
page,
|
|
1020
|
+
pageSize,
|
|
1021
|
+
reviews,
|
|
1022
|
+
total: result.total
|
|
1023
|
+
};
|
|
1024
|
+
});
|
|
1025
|
+
const getById = (id) => gen(function* () {
|
|
1026
|
+
const review = yield* repo.findById(id);
|
|
1027
|
+
if (!review) return yield* new ReviewNotFound({ id });
|
|
1028
|
+
const files = (yield* fileRepo.findByReview(id)).map((r) => ({
|
|
1029
|
+
additions: r.additions,
|
|
1030
|
+
deletions: r.deletions,
|
|
1031
|
+
filePath: r.file_path,
|
|
1032
|
+
id: r.id,
|
|
1033
|
+
oldPath: r.old_path,
|
|
1034
|
+
status: r.status
|
|
1035
|
+
}));
|
|
1036
|
+
const snapshot = yield* parseSnapshotData(review.snapshotData);
|
|
1037
|
+
const summary = getDiffSummary(files.map((f) => ({
|
|
1038
|
+
additions: f.additions,
|
|
1039
|
+
deletions: f.deletions,
|
|
1040
|
+
hunks: [],
|
|
1041
|
+
newPath: f.filePath,
|
|
1042
|
+
oldPath: f.oldPath ?? f.filePath,
|
|
1043
|
+
status: f.status
|
|
1044
|
+
})));
|
|
1045
|
+
return {
|
|
1046
|
+
...review,
|
|
1047
|
+
files,
|
|
1048
|
+
repository: snapshot.repository ?? null,
|
|
1049
|
+
summary
|
|
1050
|
+
};
|
|
1051
|
+
});
|
|
1052
|
+
const getFileHunks = (reviewId, filePath) => gen(function* () {
|
|
1053
|
+
const review = yield* repo.findById(reviewId);
|
|
1054
|
+
if (!review) return yield* new ReviewNotFound({ id: reviewId });
|
|
1055
|
+
const fileRecord = yield* fileRepo.findByReviewAndPath(reviewId, filePath);
|
|
1056
|
+
if (fileRecord?.hunks_data) return yield* parseHunks(fileRecord.hunks_data);
|
|
1057
|
+
if (review.sourceType === "branch" && review.sourceRef) return parseDiff(yield* git.getBranchDiff(review.sourceRef)).find((f) => f.newPath === filePath)?.hunks ?? [];
|
|
1058
|
+
if (review.sourceType === "commits" && review.sourceRef) {
|
|
1059
|
+
const shas = review.sourceRef.split(",").map((s) => s.trim());
|
|
1060
|
+
return parseDiff(yield* git.getCommitDiff(shas)).find((f) => f.newPath === filePath)?.hunks ?? [];
|
|
1061
|
+
}
|
|
1062
|
+
const snapshot = yield* parseSnapshotData(review.snapshotData);
|
|
1063
|
+
if (snapshot.files) return snapshot.files.find((f) => f.newPath === filePath)?.hunks ?? [];
|
|
1064
|
+
return [];
|
|
1065
|
+
});
|
|
1066
|
+
const update = (id, input) => gen(function* () {
|
|
1067
|
+
if (!(yield* repo.findById(id))) return yield* new ReviewNotFound({ id });
|
|
1068
|
+
const status = input.status ? getOrNull(input.status) : null;
|
|
1069
|
+
const review = yield* repo.update(id, status);
|
|
1070
|
+
if (!review) return yield* new ReviewNotFound({ id });
|
|
1071
|
+
return review;
|
|
1072
|
+
});
|
|
1073
|
+
const remove = (id) => gen(function* () {
|
|
1074
|
+
if (!(yield* repo.findById(id))) return yield* new ReviewNotFound({ id });
|
|
1075
|
+
yield* fileRepo.deleteByReview(id);
|
|
1076
|
+
yield* repo.remove(id);
|
|
1077
|
+
return { success: true };
|
|
1078
|
+
});
|
|
1079
|
+
const getStats = () => gen(function* () {
|
|
1080
|
+
const total = yield* repo.countAll();
|
|
1081
|
+
const inProgress = yield* repo.countByStatus("in_progress");
|
|
1082
|
+
return {
|
|
1083
|
+
approved: yield* repo.countByStatus("approved"),
|
|
1084
|
+
changesRequested: yield* repo.countByStatus("changes_requested"),
|
|
1085
|
+
inProgress,
|
|
1086
|
+
total
|
|
1087
|
+
};
|
|
1088
|
+
});
|
|
1089
|
+
return ReviewService.of({
|
|
1090
|
+
create,
|
|
1091
|
+
getById,
|
|
1092
|
+
getFileHunks,
|
|
1093
|
+
getStats,
|
|
1094
|
+
list,
|
|
1095
|
+
remove,
|
|
1096
|
+
update
|
|
1097
|
+
});
|
|
1098
|
+
}));
|
|
1099
|
+
};
|
|
1100
|
+
var TodoService = class TodoService extends Service()("@ringi/TodoService") {
|
|
1101
|
+
static Default = effect(TodoService, gen(function* () {
|
|
1102
|
+
const repo = yield* TodoRepo;
|
|
1103
|
+
const create = (input) => {
|
|
1104
|
+
const id = randomUUID();
|
|
1105
|
+
return repo.create({
|
|
1106
|
+
content: input.content,
|
|
1107
|
+
id,
|
|
1108
|
+
reviewId: input.reviewId ?? null
|
|
1109
|
+
});
|
|
1110
|
+
};
|
|
1111
|
+
const getById = (id) => gen(function* () {
|
|
1112
|
+
const todo = yield* repo.findById(id);
|
|
1113
|
+
if (!todo) return yield* new TodoNotFound({ id });
|
|
1114
|
+
return todo;
|
|
1115
|
+
});
|
|
1116
|
+
const list = (opts = {}) => gen(function* () {
|
|
1117
|
+
const result = yield* repo.findAll(opts);
|
|
1118
|
+
return {
|
|
1119
|
+
data: result.data,
|
|
1120
|
+
limit: opts.limit ?? null,
|
|
1121
|
+
offset: opts.offset ?? 0,
|
|
1122
|
+
total: result.total
|
|
1123
|
+
};
|
|
1124
|
+
});
|
|
1125
|
+
const update = (id, input) => gen(function* () {
|
|
1126
|
+
if (!(yield* repo.findById(id))) return yield* new TodoNotFound({ id });
|
|
1127
|
+
const updates = {};
|
|
1128
|
+
if (input.content && isSome(input.content)) updates.content = input.content.value;
|
|
1129
|
+
if (input.completed && isSome(input.completed)) updates.completed = input.completed.value;
|
|
1130
|
+
const todo = yield* repo.update(id, updates);
|
|
1131
|
+
if (!todo) return yield* new TodoNotFound({ id });
|
|
1132
|
+
return todo;
|
|
1133
|
+
});
|
|
1134
|
+
const toggle = (id) => gen(function* () {
|
|
1135
|
+
const todo = yield* repo.toggle(id);
|
|
1136
|
+
if (!todo) return yield* new TodoNotFound({ id });
|
|
1137
|
+
return todo;
|
|
1138
|
+
});
|
|
1139
|
+
const remove = (id) => gen(function* () {
|
|
1140
|
+
if (!(yield* repo.findById(id))) return yield* new TodoNotFound({ id });
|
|
1141
|
+
yield* repo.remove(id);
|
|
1142
|
+
return { success: true };
|
|
1143
|
+
});
|
|
1144
|
+
const removeCompleted = () => gen(function* () {
|
|
1145
|
+
return { deleted: yield* repo.removeCompleted() };
|
|
1146
|
+
});
|
|
1147
|
+
const reorder = (orderedIds) => gen(function* () {
|
|
1148
|
+
return { updated: yield* repo.reorder(orderedIds) };
|
|
1149
|
+
});
|
|
1150
|
+
const move = (id, position) => gen(function* () {
|
|
1151
|
+
const todo = yield* repo.move(id, position);
|
|
1152
|
+
if (!todo) return yield* new TodoNotFound({ id });
|
|
1153
|
+
return todo;
|
|
1154
|
+
});
|
|
1155
|
+
const getStats = () => gen(function* () {
|
|
1156
|
+
const total = yield* repo.countAll();
|
|
1157
|
+
return {
|
|
1158
|
+
completed: yield* repo.countCompleted(),
|
|
1159
|
+
pending: yield* repo.countPending(),
|
|
1160
|
+
total
|
|
1161
|
+
};
|
|
1162
|
+
});
|
|
1163
|
+
return TodoService.of({
|
|
1164
|
+
create,
|
|
1165
|
+
getById,
|
|
1166
|
+
getStats,
|
|
1167
|
+
list,
|
|
1168
|
+
move,
|
|
1169
|
+
remove,
|
|
1170
|
+
removeCompleted,
|
|
1171
|
+
reorder,
|
|
1172
|
+
toggle,
|
|
1173
|
+
update
|
|
1174
|
+
});
|
|
1175
|
+
}));
|
|
1176
|
+
};
|
|
1177
|
+
var ExportService = class ExportService extends Service()("@ringi/ExportService") {
|
|
1178
|
+
static Default = effect(ExportService, gen(function* () {
|
|
1179
|
+
const reviewSvc = yield* ReviewService;
|
|
1180
|
+
const commentSvc = yield* CommentService;
|
|
1181
|
+
const todoSvc = yield* TodoService;
|
|
1182
|
+
const exportReview = (reviewId) => gen(function* () {
|
|
1183
|
+
const review = yield* reviewSvc.getById(reviewId);
|
|
1184
|
+
const repo = review.repository;
|
|
1185
|
+
const repoName = repo?.name ?? "Unknown";
|
|
1186
|
+
const branch = repo?.branch ?? "unknown";
|
|
1187
|
+
const comments = yield* commentSvc.getByReview(reviewId);
|
|
1188
|
+
const commentStats = yield* commentSvc.getStats(reviewId);
|
|
1189
|
+
const todos = yield* todoSvc.list({ reviewId });
|
|
1190
|
+
const lines = [];
|
|
1191
|
+
lines.push(`# Code Review: ${repoName}`);
|
|
1192
|
+
lines.push("");
|
|
1193
|
+
lines.push(`**Status:** ${review.status}`);
|
|
1194
|
+
lines.push(`**Branch:** ${branch}`);
|
|
1195
|
+
lines.push(`**Created:** ${review.createdAt}`);
|
|
1196
|
+
if (review.files && review.files.length > 0) {
|
|
1197
|
+
lines.push("");
|
|
1198
|
+
lines.push("## Files Changed");
|
|
1199
|
+
lines.push("");
|
|
1200
|
+
lines.push("| File | Status | Additions | Deletions |");
|
|
1201
|
+
lines.push("|------|--------|-----------|-----------|");
|
|
1202
|
+
for (const f of review.files) {
|
|
1203
|
+
const statusLabel = f.status === "modified" ? "M" : f.status === "added" ? "A" : f.status === "deleted" ? "D" : f.status;
|
|
1204
|
+
lines.push(`| ${f.filePath} | ${statusLabel} | +${f.additions} | -${f.deletions} |`);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (comments.length > 0) {
|
|
1208
|
+
lines.push("");
|
|
1209
|
+
lines.push(`## Comments (${commentStats.total} total, ${commentStats.resolved} resolved)`);
|
|
1210
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1211
|
+
for (const c of comments) {
|
|
1212
|
+
const key = c.filePath ?? "(general)";
|
|
1213
|
+
const arr = byFile.get(key) ?? [];
|
|
1214
|
+
arr.push(c);
|
|
1215
|
+
byFile.set(key, arr);
|
|
1216
|
+
}
|
|
1217
|
+
for (const [filePath, fileComments] of byFile) {
|
|
1218
|
+
lines.push("");
|
|
1219
|
+
lines.push(`### ${filePath}`);
|
|
1220
|
+
for (const c of fileComments) {
|
|
1221
|
+
lines.push("");
|
|
1222
|
+
lines.push(`**Line ${c.lineNumber ?? "–"}** (${c.lineType ?? "context"})`);
|
|
1223
|
+
lines.push(`> ${c.content}`);
|
|
1224
|
+
if (c.suggestion) {
|
|
1225
|
+
lines.push("");
|
|
1226
|
+
lines.push("```suggestion");
|
|
1227
|
+
lines.push(c.suggestion);
|
|
1228
|
+
lines.push("```");
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if (todos.data.length > 0) {
|
|
1234
|
+
const completed = todos.data.filter((t) => t.completed).length;
|
|
1235
|
+
lines.push("");
|
|
1236
|
+
lines.push("---");
|
|
1237
|
+
lines.push("");
|
|
1238
|
+
lines.push(`## Todos (${todos.total} total, ${completed} completed)`);
|
|
1239
|
+
lines.push("");
|
|
1240
|
+
for (const t of todos.data) {
|
|
1241
|
+
const check = t.completed ? "x" : " ";
|
|
1242
|
+
lines.push(`- [${check}] ${t.content}`);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
lines.push("");
|
|
1246
|
+
return lines.join("\n");
|
|
1247
|
+
});
|
|
1248
|
+
return ExportService.of({ exportReview });
|
|
1249
|
+
}));
|
|
1250
|
+
};
|
|
1251
|
+
var GhNotInstalled = class extends TaggedErrorClass()("GhNotInstalled", { message: String$1 }) {};
|
|
1252
|
+
var GhAuthError = class extends TaggedErrorClass()("GhAuthError", {
|
|
1253
|
+
host: String$1,
|
|
1254
|
+
message: String$1
|
|
1255
|
+
}) {};
|
|
1256
|
+
var GhApiError = class extends TaggedErrorClass()("GhApiError", {
|
|
1257
|
+
message: String$1,
|
|
1258
|
+
statusCode: NullOr(Number$1)
|
|
1259
|
+
}) {};
|
|
1260
|
+
/** Max bytes to collect from gh output (50 MB — PR diffs can be large). */
|
|
1261
|
+
var MAX_STDOUT_BYTES = 50 * 1024 * 1024;
|
|
1262
|
+
var execGh = (args) => tryPromise({
|
|
1263
|
+
catch: (error) => new GhApiError({
|
|
1264
|
+
message: String(error),
|
|
1265
|
+
statusCode: null
|
|
1266
|
+
}),
|
|
1267
|
+
try: () => new Promise((resolve, reject) => {
|
|
1268
|
+
const child = spawn("gh", [...args], { stdio: [
|
|
1269
|
+
"ignore",
|
|
1270
|
+
"pipe",
|
|
1271
|
+
"pipe"
|
|
1272
|
+
] });
|
|
1273
|
+
const chunks = [];
|
|
1274
|
+
let bytes = 0;
|
|
1275
|
+
let truncated = false;
|
|
1276
|
+
child.stdout.on("data", (chunk) => {
|
|
1277
|
+
if (truncated) return;
|
|
1278
|
+
bytes += chunk.length;
|
|
1279
|
+
if (bytes > MAX_STDOUT_BYTES) {
|
|
1280
|
+
truncated = true;
|
|
1281
|
+
child.kill();
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
chunks.push(chunk);
|
|
1285
|
+
});
|
|
1286
|
+
let stderr = "";
|
|
1287
|
+
child.stderr.on("data", (chunk) => {
|
|
1288
|
+
stderr += chunk.toString();
|
|
1289
|
+
});
|
|
1290
|
+
child.on("error", (err) => {
|
|
1291
|
+
reject(/* @__PURE__ */ new Error(`Failed to spawn gh: ${err.message}`));
|
|
1292
|
+
});
|
|
1293
|
+
child.on("close", (code) => {
|
|
1294
|
+
if (truncated) {
|
|
1295
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
if (code !== 0) reject(/* @__PURE__ */ new Error(`gh ${args[0]} exited with code ${code}: ${stderr}`));
|
|
1299
|
+
else resolve(Buffer.concat(chunks).toString("utf8"));
|
|
1300
|
+
});
|
|
1301
|
+
})
|
|
1302
|
+
});
|
|
1303
|
+
/** PR metadata fields requested from gh in a single JSON call. */
|
|
1304
|
+
var PR_JSON_FIELDS = [
|
|
1305
|
+
"additions",
|
|
1306
|
+
"author",
|
|
1307
|
+
"baseRefName",
|
|
1308
|
+
"baseRefOid",
|
|
1309
|
+
"body",
|
|
1310
|
+
"changedFiles",
|
|
1311
|
+
"createdAt",
|
|
1312
|
+
"deletions",
|
|
1313
|
+
"headRefName",
|
|
1314
|
+
"headRefOid",
|
|
1315
|
+
"headRepository",
|
|
1316
|
+
"isDraft",
|
|
1317
|
+
"mergeable",
|
|
1318
|
+
"number",
|
|
1319
|
+
"reviewDecision",
|
|
1320
|
+
"state",
|
|
1321
|
+
"title",
|
|
1322
|
+
"updatedAt",
|
|
1323
|
+
"url"
|
|
1324
|
+
].join(",");
|
|
1325
|
+
var GhService = class GhService extends Service()("@ringi/GhService") {
|
|
1326
|
+
static Default = effect(GhService, gen(function* () {
|
|
1327
|
+
const ensureInstalled = execGh(["version"]).pipe(asVoid, mapError(() => new GhNotInstalled({ message: "GitHub CLI (gh) not found. Install: https://cli.github.com" })), withSpan("GhService.ensureInstalled"));
|
|
1328
|
+
const ensureAuthenticated = fn("GhService.ensureAuthenticated")(function* (host) {
|
|
1329
|
+
yield* execGh([
|
|
1330
|
+
"auth",
|
|
1331
|
+
"status",
|
|
1332
|
+
"--hostname",
|
|
1333
|
+
host
|
|
1334
|
+
]).pipe(asVoid, mapError(() => new GhAuthError({
|
|
1335
|
+
host,
|
|
1336
|
+
message: `Not authenticated to ${host}. Run: gh auth login --hostname ${host}`
|
|
1337
|
+
})));
|
|
1338
|
+
});
|
|
1339
|
+
const fetchPrMetadata = fn("GhService.fetchPrMetadata")(function* (target) {
|
|
1340
|
+
const json = yield* execGh([
|
|
1341
|
+
"pr",
|
|
1342
|
+
"view",
|
|
1343
|
+
String(target.prNumber),
|
|
1344
|
+
"--repo",
|
|
1345
|
+
target.nwoRef,
|
|
1346
|
+
"--json",
|
|
1347
|
+
PR_JSON_FIELDS
|
|
1348
|
+
]);
|
|
1349
|
+
return yield* try_({
|
|
1350
|
+
catch: () => new GhApiError({
|
|
1351
|
+
message: "Failed to parse PR metadata JSON from gh output",
|
|
1352
|
+
statusCode: null
|
|
1353
|
+
}),
|
|
1354
|
+
try: () => JSON.parse(json)
|
|
1355
|
+
});
|
|
1356
|
+
});
|
|
1357
|
+
const fetchPrDiff = fn("GhService.fetchPrDiff")(function* (target) {
|
|
1358
|
+
return yield* execGh([
|
|
1359
|
+
"pr",
|
|
1360
|
+
"diff",
|
|
1361
|
+
String(target.prNumber),
|
|
1362
|
+
"--repo",
|
|
1363
|
+
target.nwoRef
|
|
1364
|
+
]);
|
|
1365
|
+
});
|
|
1366
|
+
const fetchPrHeadOid = fn("GhService.fetchPrHeadOid")(function* (target) {
|
|
1367
|
+
const json = yield* execGh([
|
|
1368
|
+
"pr",
|
|
1369
|
+
"view",
|
|
1370
|
+
String(target.prNumber),
|
|
1371
|
+
"--repo",
|
|
1372
|
+
target.nwoRef,
|
|
1373
|
+
"--json",
|
|
1374
|
+
"headRefOid"
|
|
1375
|
+
]);
|
|
1376
|
+
return yield* try_({
|
|
1377
|
+
catch: () => new GhApiError({
|
|
1378
|
+
message: "Failed to parse head OID from gh output",
|
|
1379
|
+
statusCode: null
|
|
1380
|
+
}),
|
|
1381
|
+
try: () => JSON.parse(json).headRefOid
|
|
1382
|
+
});
|
|
1383
|
+
});
|
|
1384
|
+
return GhService.of({
|
|
1385
|
+
ensureAuthenticated,
|
|
1386
|
+
ensureInstalled,
|
|
1387
|
+
fetchPrDiff,
|
|
1388
|
+
fetchPrHeadOid,
|
|
1389
|
+
fetchPrMetadata
|
|
1390
|
+
});
|
|
1391
|
+
}));
|
|
1392
|
+
};
|
|
1393
|
+
var RepoLive = mergeAll(ReviewRepo.Default, ReviewFileRepo.Default, CommentRepo.Default, TodoRepo.Default).pipe(provide(SqliteService.Default));
|
|
1394
|
+
var CommentServiceLive = CommentService.Default.pipe(provide(CommentRepo.Default), provide(SqliteService.Default));
|
|
1395
|
+
var TodoServiceLive = TodoService.Default.pipe(provide(TodoRepo.Default), provide(SqliteService.Default));
|
|
1396
|
+
var ReviewServiceLive = ReviewService.Default.pipe(provide(ReviewRepo.Default), provide(ReviewFileRepo.Default), provide(GitService.Default), provide(SqliteService.Default));
|
|
1397
|
+
var ExportServiceLive = ExportService.Default.pipe(provide(ReviewServiceLive), provide(CommentServiceLive), provide(TodoServiceLive));
|
|
1398
|
+
var CoreLive = mergeAll(ReviewServiceLive, CommentServiceLive, TodoServiceLive, GitService.Default, GhService.Default, EventService.Default, ExportServiceLive, RepoLive, SqliteService.Default);
|
|
1399
|
+
var createCoreRuntime = () => make(CoreLive);
|
|
1400
|
+
//#endregion
|
|
1401
|
+
export { GitService as a, createCoreRuntime as c, ExportService as i, getDiffSummary as l, CoreLive as n, ReviewService as o, EventService as r, TodoService as s, CommentService as t, parseDiff as u };
|