polydiff 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +108 -0
  3. package/bin/polydiff.mjs +175 -0
  4. package/dist/assets/IBMPlexSans-latin-BD64o3ke.woff2 +0 -0
  5. package/dist/assets/IBMPlexSans-latin-ext-KUPCjGsn.woff2 +0 -0
  6. package/dist/assets/Lilex-latin-5ZnsGwUI.woff2 +0 -0
  7. package/dist/assets/Lilex-latin-ext-Rgd1-08h.woff2 +0 -0
  8. package/dist/assets/abap-CLvhMVsD.js +1 -0
  9. package/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
  10. package/dist/assets/ada-C5qYipkI.js +1 -0
  11. package/dist/assets/ahk-CA9gdOdF.js +1 -0
  12. package/dist/assets/ahk2-_6rzW9BS.js +1 -0
  13. package/dist/assets/andromeeda-vGVdxbeo.js +1 -0
  14. package/dist/assets/angular-html-ezhpupwN.js +1 -0
  15. package/dist/assets/angular-ts-DGkBU2Ac.js +1 -0
  16. package/dist/assets/apache-U0d_L8uA.js +1 -0
  17. package/dist/assets/apex-KFGFyKwG.js +1 -0
  18. package/dist/assets/apl-CUGT4nIi.js +1 -0
  19. package/dist/assets/applescript-CCn79oCD.js +1 -0
  20. package/dist/assets/ara-4CJ0cIlV.js +1 -0
  21. package/dist/assets/asciidoc-SCjQUq34.js +1 -0
  22. package/dist/assets/asm-Cmm7eHzH.js +1 -0
  23. package/dist/assets/astro-C6f9OliD.js +1 -0
  24. package/dist/assets/aurora-x-CDeNXAV0.js +1 -0
  25. package/dist/assets/awk-BWXHIvNe.js +1 -0
  26. package/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
  27. package/dist/assets/ayu-light-C3h-C4tm.js +1 -0
  28. package/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  29. package/dist/assets/ballerina-B7ZEbQpA.js +1 -0
  30. package/dist/assets/bat-qdxAdBWQ.js +1 -0
  31. package/dist/assets/beancount-D-usSTwE.js +1 -0
  32. package/dist/assets/berry-DKpUyyne.js +1 -0
  33. package/dist/assets/bibtex-Ci_nEsc7.js +1 -0
  34. package/dist/assets/bicep-CUHmPFLl.js +1 -0
  35. package/dist/assets/bird2-CYIUgYqB.js +1 -0
  36. package/dist/assets/blade-71nbYGOJ.js +1 -0
  37. package/dist/assets/bsl-_VjlB9mv.js +1 -0
  38. package/dist/assets/c-Bpp83vUT.js +1 -0
  39. package/dist/assets/c3-D0apG41V.js +1 -0
  40. package/dist/assets/cadence-CQ2zXKGN.js +1 -0
  41. package/dist/assets/cairo-DLTphjLi.js +1 -0
  42. package/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  43. package/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  44. package/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  45. package/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  46. package/dist/assets/chapel-ASdwSMh4.js +1 -0
  47. package/dist/assets/clarity-CtfBBspg.js +1 -0
  48. package/dist/assets/clojure-DqKBuwfJ.js +1 -0
  49. package/dist/assets/cmake-Bj61d0ZC.js +1 -0
  50. package/dist/assets/cobol-DNxuGELk.js +1 -0
  51. package/dist/assets/codeowners-C8r90Shi.js +1 -0
  52. package/dist/assets/codeql-oeQT6MSM.js +1 -0
  53. package/dist/assets/coffee-CTyXBUCJ.js +1 -0
  54. package/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
  55. package/dist/assets/coq-DuBRPxYg.js +1 -0
  56. package/dist/assets/cpp-CzJZfqV4.js +1 -0
  57. package/dist/assets/crystal-BfqQdHxV.js +1 -0
  58. package/dist/assets/csharp-oqKa8noW.js +1 -0
  59. package/dist/assets/css-DlfMUpGK.js +1 -0
  60. package/dist/assets/csv-Dx-8-gkx.js +1 -0
  61. package/dist/assets/cue-CE9AQfxI.js +1 -0
  62. package/dist/assets/cypher-ClKdZ_lG.js +1 -0
  63. package/dist/assets/d-qD-0Kul2.js +1 -0
  64. package/dist/assets/dark-plus-Cs2F2srj.js +1 -0
  65. package/dist/assets/dart-CnvKMtbv.js +1 -0
  66. package/dist/assets/dax-BkyTk9wS.js +1 -0
  67. package/dist/assets/desktop-Dlh5hvp9.js +1 -0
  68. package/dist/assets/diff-woXpYk--.js +1 -0
  69. package/dist/assets/docker-IyjqRm3v.js +1 -0
  70. package/dist/assets/dotenv-_5a1GRtc.js +1 -0
  71. package/dist/assets/dracula-BHWKrbxM.js +1 -0
  72. package/dist/assets/dracula-soft-5eyTD99u.js +1 -0
  73. package/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
  74. package/dist/assets/edge-ZaCA-9tU.js +1 -0
  75. package/dist/assets/elixir-GeIrO3JP.js +1 -0
  76. package/dist/assets/elm-BetcOCtl.js +1 -0
  77. package/dist/assets/emacs-lisp-c_oH4hRZ.js +1 -0
  78. package/dist/assets/erb-Dru309ol.js +1 -0
  79. package/dist/assets/erlang-Cphh6RMH.js +1 -0
  80. package/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
  81. package/dist/assets/everforest-light-Df2xbC6M.js +1 -0
  82. package/dist/assets/fennel-DQxkIbk2.js +1 -0
  83. package/dist/assets/fish-BJitypiv.js +1 -0
  84. package/dist/assets/fluent-C03EYrpw.js +1 -0
  85. package/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  86. package/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
  87. package/dist/assets/fsharp-D13ZGOAj.js +1 -0
  88. package/dist/assets/gdresource-xMC7jr7x.js +1 -0
  89. package/dist/assets/gdscript-DYvRD3hg.js +1 -0
  90. package/dist/assets/gdshader-CBce3t8t.js +1 -0
  91. package/dist/assets/genie-CV2tkWYe.js +1 -0
  92. package/dist/assets/gherkin-DExj1W_8.js +1 -0
  93. package/dist/assets/git-commit-BSykSTBG.js +1 -0
  94. package/dist/assets/git-rebase-DJIWyVXc.js +1 -0
  95. package/dist/assets/github-dark-C-LZuMrd.js +1 -0
  96. package/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
  97. package/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  98. package/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  99. package/dist/assets/github-light-EUqPIrTm.js +1 -0
  100. package/dist/assets/github-light-default-BXViO-2h.js +1 -0
  101. package/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  102. package/dist/assets/gleam-CSRkHgEL.js +1 -0
  103. package/dist/assets/glimmer-js-DIqXjYAY.js +1 -0
  104. package/dist/assets/glimmer-ts-pnunO5g0.js +1 -0
  105. package/dist/assets/glsl-DWspuWDC.js +1 -0
  106. package/dist/assets/gn-ilITqXS6.js +1 -0
  107. package/dist/assets/gnuplot-7GGW24-e.js +1 -0
  108. package/dist/assets/go-rLFTqkRN.js +1 -0
  109. package/dist/assets/graphql-DjZ79Z4P.js +1 -0
  110. package/dist/assets/groovy-CacY0gHj.js +1 -0
  111. package/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  112. package/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  113. package/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  114. package/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  115. package/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  116. package/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  117. package/dist/assets/hack-iafWRwwB.js +1 -0
  118. package/dist/assets/haml-o-Lgpxml.js +1 -0
  119. package/dist/assets/handlebars-C8OUH7g6.js +1 -0
  120. package/dist/assets/haskell-D8IpX4py.js +1 -0
  121. package/dist/assets/haxe-C8oCnwJJ.js +1 -0
  122. package/dist/assets/hcl-Dh228itO.js +1 -0
  123. package/dist/assets/hjson-CxZEssPk.js +1 -0
  124. package/dist/assets/hlsl-Cvrh5tZx.js +1 -0
  125. package/dist/assets/horizon-CE9ld1lL.js +1 -0
  126. package/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
  127. package/dist/assets/houston-CsvMBhTu.js +1 -0
  128. package/dist/assets/html-CJzu28V2.js +1 -0
  129. package/dist/assets/html-derivative-Cb8okpVc.js +1 -0
  130. package/dist/assets/http-ttaoWHS4.js +1 -0
  131. package/dist/assets/hurl-DzAr45rI.js +1 -0
  132. package/dist/assets/hxml-Dcm88srW.js +1 -0
  133. package/dist/assets/hy-CZbG8q4J.js +1 -0
  134. package/dist/assets/imba-DsUTQ-LC.js +1 -0
  135. package/dist/assets/index-BzbDgOIS.css +1 -0
  136. package/dist/assets/index-CGIYnoUG.js +2800 -0
  137. package/dist/assets/ini-B5eOa1yu.js +1 -0
  138. package/dist/assets/java-B9M8FiDh.js +1 -0
  139. package/dist/assets/javascript-DsgDiit9.js +1 -0
  140. package/dist/assets/jinja-BpB_f_L5.js +1 -0
  141. package/dist/assets/jison-BBboHJLm.js +1 -0
  142. package/dist/assets/json-CjLBqelG.js +1 -0
  143. package/dist/assets/json5-BR5RXkoi.js +1 -0
  144. package/dist/assets/jsonc-CYpm1nAK.js +1 -0
  145. package/dist/assets/jsonl-CmCQp5Yx.js +1 -0
  146. package/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
  147. package/dist/assets/jssm-DXw9l8Rf.js +1 -0
  148. package/dist/assets/jsx-B86puyK5.js +1 -0
  149. package/dist/assets/julia-Dx4irgay.js +1 -0
  150. package/dist/assets/just-BaU8BkWf.js +1 -0
  151. package/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  152. package/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  153. package/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
  154. package/dist/assets/kdl-CsD5j6eV.js +1 -0
  155. package/dist/assets/kotlin-DhhofPvG.js +1 -0
  156. package/dist/assets/kusto-C7mF5XQf.js +1 -0
  157. package/dist/assets/laserwave-C_8bwKvT.js +1 -0
  158. package/dist/assets/latex-OCFtPaVb.js +1 -0
  159. package/dist/assets/lean-CewbzKMR.js +1 -0
  160. package/dist/assets/less-DVTAwKKz.js +1 -0
  161. package/dist/assets/light-plus-DVQuIRkW.js +1 -0
  162. package/dist/assets/liquid-B-HSosSR.js +1 -0
  163. package/dist/assets/llvm-DqHOMS-s.js +1 -0
  164. package/dist/assets/log-BNLmms1o.js +1 -0
  165. package/dist/assets/logo-Cluzi2Zq.js +1 -0
  166. package/dist/assets/lua-DmtpgLJe.js +1 -0
  167. package/dist/assets/luau-C2pBCSgt.js +1 -0
  168. package/dist/assets/make-Dixweg8N.js +1 -0
  169. package/dist/assets/markdown-BYOwaDjH.js +1 -0
  170. package/dist/assets/marko-DLvj6HlY.js +1 -0
  171. package/dist/assets/material-theme-Bm3Qr25_.js +1 -0
  172. package/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
  173. package/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
  174. package/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  175. package/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  176. package/dist/assets/matlab-D7qyCx1q.js +1 -0
  177. package/dist/assets/mdc-CLlpDeEH.js +1 -0
  178. package/dist/assets/mdx-DQZ5AkYe.js +1 -0
  179. package/dist/assets/mermaid-BxpfTIzt.js +1 -0
  180. package/dist/assets/min-dark-BSWPekZh.js +1 -0
  181. package/dist/assets/min-light-DDpmG2fV.js +1 -0
  182. package/dist/assets/mipsasm-BMqwQI7S.js +1 -0
  183. package/dist/assets/mojo-fg40_ntQ.js +1 -0
  184. package/dist/assets/monokai-B_bV9PQ9.js +1 -0
  185. package/dist/assets/moonbit-DgjMoLWr.js +1 -0
  186. package/dist/assets/move-BYnGhZoM.js +1 -0
  187. package/dist/assets/narrat-_X_XdTYD.js +1 -0
  188. package/dist/assets/nextflow-BJtWHP5T.js +1 -0
  189. package/dist/assets/nextflow-groovy-DJMQeKeT.js +1 -0
  190. package/dist/assets/nginx-B6eJiJtn.js +1 -0
  191. package/dist/assets/night-owl-DhmEMT88.js +1 -0
  192. package/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
  193. package/dist/assets/nim-BjTTIyjo.js +1 -0
  194. package/dist/assets/nix-IvuFDN5E.js +1 -0
  195. package/dist/assets/nord-Cb4Vim4T.js +1 -0
  196. package/dist/assets/nsis-CjNLYCLg.js +1 -0
  197. package/dist/assets/nushell-DHYfO66s.js +1 -0
  198. package/dist/assets/objective-c-D1A_Heim.js +1 -0
  199. package/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
  200. package/dist/assets/ocaml-O90oeIOV.js +1 -0
  201. package/dist/assets/odin-B1RWQWA5.js +1 -0
  202. package/dist/assets/one-dark-pro-CLwyXe_n.js +1 -0
  203. package/dist/assets/one-light-D7Lr4KcI.js +1 -0
  204. package/dist/assets/openscad-BUDT5pXO.js +1 -0
  205. package/dist/assets/org-BbxkHpL7.js +1 -0
  206. package/dist/assets/pascal-4ZHwLPI5.js +1 -0
  207. package/dist/assets/perl-7YBielDt.js +1 -0
  208. package/dist/assets/php--KPClthN.js +1 -0
  209. package/dist/assets/pierre-dark-CpLgRqie.js +1 -0
  210. package/dist/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  211. package/dist/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  212. package/dist/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  213. package/dist/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  214. package/dist/assets/pierre-light-CoaEpmwp.js +1 -0
  215. package/dist/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  216. package/dist/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  217. package/dist/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  218. package/dist/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  219. package/dist/assets/pkl-ot-7Btpt.js +1 -0
  220. package/dist/assets/plastic-DQwYfKfQ.js +1 -0
  221. package/dist/assets/plsql-DGHpHOYJ.js +1 -0
  222. package/dist/assets/po-BiJDBrnU.js +1 -0
  223. package/dist/assets/poimandres-DRFjx7u4.js +1 -0
  224. package/dist/assets/polar-C7UOKdEL.js +1 -0
  225. package/dist/assets/postcss-BXeXVLqQ.js +1 -0
  226. package/dist/assets/powerquery-DNMTfnFr.js +1 -0
  227. package/dist/assets/powershell-CCSX75yM.js +1 -0
  228. package/dist/assets/prisma-DMgNhnYS.js +1 -0
  229. package/dist/assets/prolog-iXnhIJG7.js +1 -0
  230. package/dist/assets/proto-DB4EqR-F.js +1 -0
  231. package/dist/assets/pug-C7XMl1L8.js +1 -0
  232. package/dist/assets/puppet-CDv2pdJW.js +1 -0
  233. package/dist/assets/purescript-9MfHhQsQ.js +1 -0
  234. package/dist/assets/python-gzcpVVnB.js +1 -0
  235. package/dist/assets/qml-CN9HGHWl.js +1 -0
  236. package/dist/assets/qmldir-DCQb3MpD.js +1 -0
  237. package/dist/assets/qss-Fe1Jh2GI.js +1 -0
  238. package/dist/assets/r-BR7Z2WHy.js +1 -0
  239. package/dist/assets/racket-DcIDlBhZ.js +1 -0
  240. package/dist/assets/raku-B3gFvitq.js +1 -0
  241. package/dist/assets/razor-C6ePkXhR.js +1 -0
  242. package/dist/assets/rbs-C0MpOrj4.js +1 -0
  243. package/dist/assets/red-DX1BQOs8.js +1 -0
  244. package/dist/assets/reg-CRGYupPL.js +1 -0
  245. package/dist/assets/regexp-4j9D_ZJm.js +1 -0
  246. package/dist/assets/rel-BtDbiS_P.js +1 -0
  247. package/dist/assets/riscv-Ckw8ddFX.js +1 -0
  248. package/dist/assets/ron-VUp2lXgN.js +1 -0
  249. package/dist/assets/rose-pine-aMsaMD1D.js +1 -0
  250. package/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  251. package/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  252. package/dist/assets/rosmsg-CAekHB0j.js +1 -0
  253. package/dist/assets/rst-DnVZxVCG.js +1 -0
  254. package/dist/assets/ruby-CcELT3hw.js +1 -0
  255. package/dist/assets/rust-Cfkwpbl8.js +1 -0
  256. package/dist/assets/sas-C0OZEEpB.js +1 -0
  257. package/dist/assets/sass-DXrisJhu.js +1 -0
  258. package/dist/assets/scala-BiFt_7mu.js +1 -0
  259. package/dist/assets/scheme-DQCgrYNe.js +1 -0
  260. package/dist/assets/scss-CsORv0BP.js +1 -0
  261. package/dist/assets/sdbl-bTVj8UrX.js +1 -0
  262. package/dist/assets/shaderlab-TOUzSsQk.js +1 -0
  263. package/dist/assets/shellscript-BTztjGqL.js +1 -0
  264. package/dist/assets/shellsession-Cl0t6nbx.js +1 -0
  265. package/dist/assets/slack-dark-DnToyrRv.js +1 -0
  266. package/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
  267. package/dist/assets/smalltalk-BHpQb5nM.js +1 -0
  268. package/dist/assets/smithy-Dgaz7aur.js +1 -0
  269. package/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
  270. package/dist/assets/solarized-dark-UdsmMDCL.js +1 -0
  271. package/dist/assets/solarized-light-C1IUL_tW.js +1 -0
  272. package/dist/assets/solidity-Bj7E1mRn.js +1 -0
  273. package/dist/assets/soy-Dlpwi8rU.js +1 -0
  274. package/dist/assets/sparql-D_iOobhT.js +1 -0
  275. package/dist/assets/splunk-BC2Px7Mm.js +1 -0
  276. package/dist/assets/sql-Dea5cDqJ.js +1 -0
  277. package/dist/assets/ssh-config-BgfXC-Er.js +1 -0
  278. package/dist/assets/stata-CI6WIEjb.js +1 -0
  279. package/dist/assets/stylus-B6D30XZt.js +1 -0
  280. package/dist/assets/surrealql-CMQ8lmEG.js +1 -0
  281. package/dist/assets/svelte-CL7YYnWK.js +1 -0
  282. package/dist/assets/swift-CyEgAFGc.js +1 -0
  283. package/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
  284. package/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
  285. package/dist/assets/systemd-BxMlprV5.js +1 -0
  286. package/dist/assets/talonscript-CohzipZa.js +1 -0
  287. package/dist/assets/tasl-DMoTqEGO.js +1 -0
  288. package/dist/assets/tcl-CZd0xW_V.js +1 -0
  289. package/dist/assets/templ-BeZbGaJP.js +1 -0
  290. package/dist/assets/terraform-DswuEJGm.js +1 -0
  291. package/dist/assets/tex-BSS8Q45r.js +1 -0
  292. package/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
  293. package/dist/assets/toml-CcmNWLt0.js +1 -0
  294. package/dist/assets/ts-tags-BBdnNf-a.js +1 -0
  295. package/dist/assets/tsv-sltzmVWM.js +1 -0
  296. package/dist/assets/tsx-c1m_nKLG.js +1 -0
  297. package/dist/assets/turtle-ByJddavk.js +1 -0
  298. package/dist/assets/twig-psqnv4mB.js +1 -0
  299. package/dist/assets/typescript-ByR3P8c1.js +1 -0
  300. package/dist/assets/typespec-BRdr0IET.js +1 -0
  301. package/dist/assets/typst-Dg-RDIXz.js +1 -0
  302. package/dist/assets/v-DBj4K9d_.js +1 -0
  303. package/dist/assets/vala-zf12oZj6.js +1 -0
  304. package/dist/assets/vb-DOwzaHZ0.js +1 -0
  305. package/dist/assets/verilog-B2gwz-sJ.js +1 -0
  306. package/dist/assets/vesper-DdrHHSXu.js +1 -0
  307. package/dist/assets/vhdl-BroJfC0k.js +1 -0
  308. package/dist/assets/viml-DvXPmvsu.js +1 -0
  309. package/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
  310. package/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  311. package/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  312. package/dist/assets/vue-DbsvoIfv.js +1 -0
  313. package/dist/assets/vue-html-n7sDnKkW.js +1 -0
  314. package/dist/assets/vue-vine-CUhVSAxO.js +1 -0
  315. package/dist/assets/vyper-CgoNMtux.js +1 -0
  316. package/dist/assets/wasm-BnjxR4X6.js +1 -0
  317. package/dist/assets/wasm-ByWQv1Qj.js +1 -0
  318. package/dist/assets/wenyan-C8pVoKbM.js +1 -0
  319. package/dist/assets/wgsl-BsKzXJz4.js +1 -0
  320. package/dist/assets/wikitext-ClFFjSW2.js +1 -0
  321. package/dist/assets/wit-DdvCle-K.js +1 -0
  322. package/dist/assets/wolfram-DLL8P-h_.js +1 -0
  323. package/dist/assets/worker-ChAoQz0Q.js +162 -0
  324. package/dist/assets/xml-mUdjR8gR.js +1 -0
  325. package/dist/assets/xsl-vKBVy2ER.js +1 -0
  326. package/dist/assets/yaml-BGDRkaBQ.js +1 -0
  327. package/dist/assets/zenscript-BnlCZFoB.js +1 -0
  328. package/dist/assets/zig-CMLA9XwU.js +1 -0
  329. package/dist/favicon.svg +18 -0
  330. package/dist/index.html +14 -0
  331. package/dist/licenses/ibmplexsans-OFL.txt +93 -0
  332. package/dist/licenses/lilex-OFL.txt +93 -0
  333. package/package.json +80 -0
  334. package/server/db.mjs +284 -0
  335. package/server/git.mjs +301 -0
  336. package/server/picker.mjs +113 -0
package/server/db.mjs ADDED
@@ -0,0 +1,284 @@
1
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+
5
+ const directory =
6
+ process.env.POLYDIFF_HOME || path.join(os.homedir(), ".polydiff");
7
+ const file = path.join(directory, "db.json");
8
+ const recentLimit = 10;
9
+
10
+ export const defaults = {
11
+ theme: "system",
12
+ layout: "unified",
13
+ wrap: false,
14
+ wordDiff: "word-alt",
15
+ expansionLines: 20,
16
+ density: "compact",
17
+ scanDepth: 8,
18
+ scanBudget: 10000,
19
+ ignore: [],
20
+ };
21
+ const choices = {
22
+ theme: ["system", "light", "dark"],
23
+ layout: ["unified", "split"],
24
+ wordDiff: ["word-alt", "word", "char", "none"],
25
+ density: ["compact", "default", "relaxed"],
26
+ };
27
+ const ranges = {
28
+ expansionLines: [5, 200],
29
+ scanDepth: [1, 16],
30
+ scanBudget: [100, 200000],
31
+ };
32
+ const selections = ["changed", "all", "none", "custom"];
33
+
34
+ function blank() {
35
+ return {
36
+ version: 2,
37
+ revision: 0,
38
+ settings: { ...defaults },
39
+ activeTab: null,
40
+ tabs: [],
41
+ recent: [],
42
+ };
43
+ }
44
+ function cleanSettings(value) {
45
+ const settings = { ...defaults };
46
+ if (!value || typeof value !== "object") return settings;
47
+ for (const [key, allowed] of Object.entries(choices))
48
+ if (allowed.includes(value[key])) settings[key] = value[key];
49
+ for (const [key, [low, high]] of Object.entries(ranges)) {
50
+ const number = Math.round(Number(value[key]));
51
+ if (Number.isFinite(number))
52
+ settings[key] = Math.min(high, Math.max(low, number));
53
+ }
54
+ if (typeof value.wrap === "boolean") settings.wrap = value.wrap;
55
+ if (Array.isArray(value.ignore))
56
+ settings.ignore = [
57
+ ...new Set(
58
+ value.ignore.filter(
59
+ (name) =>
60
+ typeof name === "string" &&
61
+ name.length > 0 &&
62
+ name.length < 64 &&
63
+ !/[/\\]/.test(name),
64
+ ),
65
+ ),
66
+ ].slice(0, 100);
67
+ return settings;
68
+ }
69
+ function cleanRepos(value) {
70
+ if (!value || typeof value !== "object") return {};
71
+ const repos = {};
72
+ for (const [repo, settings] of Object.entries(value)) {
73
+ if (!path.isAbsolute(repo) || !settings || typeof settings !== "object")
74
+ continue;
75
+ const clean = {};
76
+ if (typeof settings.base === "string" && settings.base)
77
+ clean.base = settings.base;
78
+ if (
79
+ typeof settings.target === "string" &&
80
+ settings.target &&
81
+ settings.target !== "@working"
82
+ )
83
+ clean.target = settings.target;
84
+ if (typeof settings.selected === "boolean")
85
+ clean.selected = settings.selected;
86
+ if (Object.keys(clean).length) repos[repo] = clean;
87
+ }
88
+ return repos;
89
+ }
90
+ function cleanTab(value, seen, migrating) {
91
+ if (!value || typeof value !== "object") return null;
92
+ if (typeof value.id !== "string" || !value.id || seen.has(value.id))
93
+ return null;
94
+ if (typeof value.root !== "string" || !path.isAbsolute(value.root))
95
+ return null;
96
+ seen.add(value.id);
97
+ const repos = cleanRepos(value.repos);
98
+ // Version 1 had no selection mode. A tab where every repository was ticked
99
+ // by hand is a custom selection; anything else followed the changes.
100
+ const inferred =
101
+ migrating &&
102
+ Object.values(repos).some((settings) => settings.selected !== undefined)
103
+ ? "custom"
104
+ : "changed";
105
+ return {
106
+ id: value.id,
107
+ root: value.root,
108
+ selection: selections.includes(value.selection)
109
+ ? value.selection
110
+ : inferred,
111
+ repos,
112
+ };
113
+ }
114
+ export function sanitize(value) {
115
+ if (!value || typeof value !== "object") return blank();
116
+ const migrating = value.version !== 2;
117
+ const seen = new Set();
118
+ const list = (input) =>
119
+ (Array.isArray(input) ? input : [])
120
+ .map((tab) => cleanTab(tab, seen, migrating))
121
+ .filter(Boolean);
122
+ const tabs = list(value.tabs);
123
+ const recent = list(value.recent).slice(0, recentLimit);
124
+ const revision =
125
+ Number.isInteger(value.revision) && value.revision >= 0
126
+ ? value.revision
127
+ : 0;
128
+ return {
129
+ version: 2,
130
+ revision,
131
+ settings: cleanSettings(value.settings),
132
+ activeTab: tabs.some((tab) => tab.id === value.activeTab)
133
+ ? value.activeTab
134
+ : (tabs[0]?.id ?? null),
135
+ tabs,
136
+ recent,
137
+ };
138
+ }
139
+ function nextId(state) {
140
+ const taken = new Set([...state.tabs, ...state.recent].map((tab) => tab.id));
141
+ const stamp = Date.now().toString(36);
142
+ for (let suffix = 0; ; suffix++) {
143
+ const id = `t${stamp}${suffix ? suffix.toString(36) : ""}`;
144
+ if (!taken.has(id)) return id;
145
+ }
146
+ }
147
+ function insert(state, tab, index) {
148
+ return {
149
+ ...state,
150
+ activeTab: tab.id,
151
+ tabs: [...state.tabs.slice(0, index), tab, ...state.tabs.slice(index)],
152
+ };
153
+ }
154
+ export function apply(state, command) {
155
+ const op = command && typeof command === "object" ? command.op : null;
156
+ if (op === "settings")
157
+ return {
158
+ ...state,
159
+ settings: cleanSettings({ ...state.settings, ...command.settings }),
160
+ };
161
+ if (op === "open") {
162
+ if (typeof command.root !== "string" || !path.isAbsolute(command.root))
163
+ throw new Error("Choose a directory.");
164
+ if (command.focus) {
165
+ const existing = state.tabs.find((tab) => tab.root === command.root);
166
+ if (existing) return { ...state, activeTab: existing.id };
167
+ }
168
+ const tab = {
169
+ id: nextId(state),
170
+ root: command.root,
171
+ selection: "changed",
172
+ repos: {},
173
+ };
174
+ return insert(state, tab, state.tabs.length);
175
+ }
176
+ if (op === "duplicate") {
177
+ const index = state.tabs.findIndex((tab) => tab.id === command.id);
178
+ if (index < 0) return state;
179
+ const source = state.tabs[index];
180
+ return insert(
181
+ state,
182
+ { ...source, id: nextId(state), repos: { ...source.repos } },
183
+ index + 1,
184
+ );
185
+ }
186
+ if (op === "close") {
187
+ const index = state.tabs.findIndex((tab) => tab.id === command.id);
188
+ if (index < 0) return state;
189
+ const tabs = state.tabs.filter((tab) => tab.id !== command.id);
190
+ return {
191
+ ...state,
192
+ tabs,
193
+ activeTab:
194
+ state.activeTab === command.id
195
+ ? (tabs[Math.min(index, tabs.length - 1)]?.id ?? null)
196
+ : state.activeTab,
197
+ recent: [state.tabs[index], ...state.recent].slice(0, recentLimit),
198
+ };
199
+ }
200
+ if (op === "reopen") {
201
+ const [closed, ...recent] = state.recent;
202
+ if (!closed) return state;
203
+ return { ...insert(state, closed, state.tabs.length), recent };
204
+ }
205
+ if (op === "activate")
206
+ return state.tabs.some((tab) => tab.id === command.id)
207
+ ? { ...state, activeTab: command.id }
208
+ : state;
209
+ if (op === "tab") {
210
+ const current = state.tabs.find((tab) => tab.id === command.id);
211
+ if (!current) return state;
212
+ // Repository settings merge so that a repository missing from the latest
213
+ // scan keeps what it was given.
214
+ const tab = {
215
+ ...current,
216
+ selection: selections.includes(command.selection)
217
+ ? command.selection
218
+ : current.selection,
219
+ repos:
220
+ command.repos && typeof command.repos === "object"
221
+ ? cleanRepos({ ...current.repos, ...command.repos })
222
+ : current.repos,
223
+ };
224
+ return {
225
+ ...state,
226
+ tabs: state.tabs.map((item) => (item.id === command.id ? tab : item)),
227
+ };
228
+ }
229
+ throw new Error(`Unknown command: ${op}`);
230
+ }
231
+ function trim(state) {
232
+ const settings = {};
233
+ for (const [key, value] of Object.entries(state.settings))
234
+ if (JSON.stringify(value) !== JSON.stringify(defaults[key]))
235
+ settings[key] = value;
236
+ return { ...state, settings };
237
+ }
238
+ let queue = Promise.resolve();
239
+ async function load() {
240
+ let raw;
241
+ try {
242
+ raw = await readFile(file, "utf8");
243
+ } catch (error) {
244
+ if (error.code === "ENOENT") return blank();
245
+ throw error;
246
+ }
247
+ try {
248
+ return sanitize(JSON.parse(raw));
249
+ } catch {
250
+ await rename(file, `${file}.corrupt`).catch(() => {});
251
+ return blank();
252
+ }
253
+ }
254
+ export function read() {
255
+ return load();
256
+ }
257
+ async function persist(state) {
258
+ await mkdir(directory, { recursive: true });
259
+ const temp = `${file}.${process.pid}.tmp`;
260
+ await writeFile(temp, `${JSON.stringify(trim(state), null, 2)}\n`);
261
+ await rename(temp, file);
262
+ }
263
+ function update(change) {
264
+ const next = queue.then(async () => {
265
+ const current = await read();
266
+ const state = sanitize({
267
+ ...change(current),
268
+ revision: current.revision + 1,
269
+ });
270
+ await persist(state);
271
+ return state;
272
+ });
273
+ queue = next.then(
274
+ () => {},
275
+ () => {},
276
+ );
277
+ return next;
278
+ }
279
+ export function send(command) {
280
+ return update((state) => apply(state, command));
281
+ }
282
+ export function write(value) {
283
+ return update(() => sanitize(value));
284
+ }
package/server/git.mjs ADDED
@@ -0,0 +1,301 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { readdir, stat, lstat, readFile, realpath } from "node:fs/promises";
4
+ import path from "node:path";
5
+
6
+ const exec = promisify(execFile);
7
+ export async function git(cwd, args, allowed = [0]) {
8
+ try {
9
+ return (
10
+ await exec("git", ["-c", "core.quotePath=false", ...args], {
11
+ cwd,
12
+ maxBuffer: 16 * 1024 * 1024,
13
+ timeout: 30000,
14
+ env: {
15
+ ...process.env,
16
+ GIT_OPTIONAL_LOCKS: "0",
17
+ GIT_TERMINAL_PROMPT: "0",
18
+ },
19
+ })
20
+ ).stdout;
21
+ } catch (error) {
22
+ if (allowed.includes(error.code)) return error.stdout;
23
+ throw new Error(error.stderr?.trim() || error.message);
24
+ }
25
+ }
26
+ // Directory names that never hold a repository worth reviewing. Build output
27
+ // names like build, dist, and target are deliberately absent: they are also
28
+ // ordinary repository names.
29
+ const ignored = new Set([
30
+ ".git",
31
+ "node_modules",
32
+ "vendor",
33
+ ".next",
34
+ ".cache",
35
+ ".pnpm-store",
36
+ ".turbo",
37
+ ".parcel-cache",
38
+ ".svelte-kit",
39
+ "__pycache__",
40
+ "site-packages",
41
+ "venv",
42
+ ".venv",
43
+ ".tox",
44
+ ".mypy_cache",
45
+ ".pytest_cache",
46
+ ".ruff_cache",
47
+ ".gradle",
48
+ ".terraform",
49
+ "DerivedData",
50
+ "Pods",
51
+ ]);
52
+ // macOS bundles are directory trees presented as single files.
53
+ const bundle = /\.(app|framework|bundle|xcassets|photoslibrary)$/i;
54
+ export async function scan(
55
+ root,
56
+ { depth = 8, budget = 10000, ignore = [] } = {},
57
+ ) {
58
+ if (!(await stat(root)).isDirectory()) throw new Error("Choose a directory.");
59
+ const skip = ignore.length ? new Set([...ignored, ...ignore]) : ignored;
60
+ const repos = [];
61
+ const warnings = [];
62
+ let visited = 0;
63
+ let exhausted = false;
64
+ const tooDeep = [];
65
+ async function walk(dir, level) {
66
+ if (++visited > budget) {
67
+ exhausted = true;
68
+ return;
69
+ }
70
+ if (level > depth) {
71
+ tooDeep.push(dir);
72
+ return;
73
+ }
74
+ let entries;
75
+ try {
76
+ entries = await readdir(dir, { withFileTypes: true });
77
+ } catch {
78
+ warnings.push(`Could not read ${dir}`);
79
+ return;
80
+ }
81
+ if (entries.some((entry) => entry.name === ".git")) {
82
+ try {
83
+ const branch =
84
+ (
85
+ await git(dir, ["symbolic-ref", "--short", "-q", "HEAD"], [0, 1])
86
+ ).trim() || "Detached HEAD";
87
+ const refs = (
88
+ await git(dir, [
89
+ "for-each-ref",
90
+ "--format=%(refname)",
91
+ "refs/heads",
92
+ "refs/remotes",
93
+ ])
94
+ )
95
+ .trim()
96
+ .split("\n")
97
+ .filter(Boolean);
98
+ const local = [];
99
+ const remote = [];
100
+ for (const ref of refs) {
101
+ if (ref.startsWith("refs/heads/"))
102
+ local.push(ref.slice("refs/heads/".length));
103
+ else if (ref.startsWith("refs/remotes/")) {
104
+ const name = ref.slice("refs/remotes/".length);
105
+ if (name.includes("/") && !name.endsWith("/HEAD"))
106
+ remote.push(name);
107
+ }
108
+ }
109
+ const remoteHead = (
110
+ await git(
111
+ dir,
112
+ ["symbolic-ref", "--short", "-q", "refs/remotes/origin/HEAD"],
113
+ [0, 1],
114
+ )
115
+ ).trim();
116
+ const base = [
117
+ remoteHead,
118
+ "origin/main",
119
+ "main",
120
+ "origin/master",
121
+ "master",
122
+ "HEAD",
123
+ ].find(
124
+ (ref) =>
125
+ ref &&
126
+ (ref === "HEAD" || local.includes(ref) || remote.includes(ref)),
127
+ );
128
+ repos.push({
129
+ path: dir,
130
+ name: path.relative(root, dir) || path.basename(dir),
131
+ branch,
132
+ branches: { local, remote },
133
+ base,
134
+ worktree: entries.find((entry) => entry.name === ".git").isFile(),
135
+ });
136
+ } catch (error) {
137
+ warnings.push(`${dir}: ${error.message}`);
138
+ }
139
+ if (
140
+ entries.some(
141
+ (entry) => entry.name === ".worktrees" && entry.isDirectory(),
142
+ )
143
+ )
144
+ await walk(path.join(dir, ".worktrees"), level + 1);
145
+ return;
146
+ }
147
+ for (const entry of entries) {
148
+ if (
149
+ entry.isDirectory() &&
150
+ !skip.has(entry.name) &&
151
+ !bundle.test(entry.name)
152
+ )
153
+ await walk(path.join(dir, entry.name), level + 1);
154
+ if (visited > budget) break;
155
+ }
156
+ }
157
+ await walk(root, 0);
158
+ if (exhausted)
159
+ warnings.push(
160
+ `Stopped after ${budget.toLocaleString("en-US")} directories. Some repositories may be missing. Raise the directory budget in Settings to search further.`,
161
+ );
162
+ if (tooDeep.length)
163
+ warnings.push(
164
+ `Stopped at ${depth} directory levels in ${tooDeep.length} place${
165
+ tooDeep.length === 1 ? "" : "s"
166
+ }, such as ${path.relative(root, tooDeep[0])}. Raise the search depth in Settings to look deeper.`,
167
+ );
168
+ return {
169
+ root,
170
+ repos: repos.sort((a, b) => a.name.localeCompare(b.name)),
171
+ warnings,
172
+ };
173
+ }
174
+ export async function diff(repo, base, target) {
175
+ const resolve = async (ref) =>
176
+ (
177
+ await git(repo, [
178
+ "rev-parse",
179
+ "--verify",
180
+ "--end-of-options",
181
+ `${ref}^{commit}`,
182
+ ])
183
+ ).trim();
184
+ const baseHash = await resolve(base);
185
+ const targetHash = await resolve(target === "@working" ? "HEAD" : target);
186
+ const mergeBase = (
187
+ await git(repo, ["merge-base", baseHash, targetHash])
188
+ ).trim();
189
+ const args = [
190
+ "diff",
191
+ "--no-ext-diff",
192
+ "--no-textconv",
193
+ "--no-color",
194
+ "--find-renames",
195
+ mergeBase,
196
+ ];
197
+ if (target !== "@working") args.push(targetHash);
198
+ args.push("--");
199
+ let patch = await git(repo, args);
200
+ const warnings = [];
201
+ if (target === "@working") {
202
+ const untracked = (
203
+ await git(repo, ["ls-files", "--others", "--exclude-standard", "-z"])
204
+ )
205
+ .split("\0")
206
+ .filter(Boolean);
207
+ for (const file of untracked) {
208
+ try {
209
+ const info = await lstat(path.join(repo, file));
210
+ if (!info.isFile() || info.size > 1024 * 1024) {
211
+ warnings.push(
212
+ `Untracked file skipped (not a regular file or exceeds 1 MB): ${file}`,
213
+ );
214
+ continue;
215
+ }
216
+ patch += await git(
217
+ repo,
218
+ [
219
+ "diff",
220
+ "--no-index",
221
+ "--no-ext-diff",
222
+ "--no-textconv",
223
+ "--no-color",
224
+ "--",
225
+ "/dev/null",
226
+ file,
227
+ ],
228
+ [0, 1],
229
+ );
230
+ } catch (error) {
231
+ warnings.push(`${file}: ${error.message}`);
232
+ }
233
+ if (Buffer.byteLength(patch) > 16 * 1024 * 1024)
234
+ throw new Error("Diff exceeds 16 MB. Choose a narrower comparison.");
235
+ }
236
+ }
237
+ return {
238
+ patch,
239
+ mergeBase,
240
+ targetRef: target === "@working" ? "@working" : targetHash,
241
+ warnings,
242
+ };
243
+ }
244
+
245
+ // Full contents are loaded only when the diff renderer expands hidden context.
246
+ export async function fileContents(
247
+ repo,
248
+ { name, oldName = name, oldRef, newRef, expectedHash },
249
+ ) {
250
+ const root = await realpath(repo);
251
+ function validateName(value) {
252
+ if (
253
+ !value ||
254
+ path.isAbsolute(value) ||
255
+ value.split(/[\\/]/).some((part) => part === ".." || part === ".git")
256
+ )
257
+ throw new Error("Invalid file path.");
258
+ const resolved = path.resolve(root, value);
259
+ if (!resolved.startsWith(root + path.sep))
260
+ throw new Error("File is outside the repository.");
261
+ return resolved;
262
+ }
263
+ validateName(oldName);
264
+ const current = validateName(name);
265
+ if (
266
+ !/^[a-f0-9]{40,64}$/.test(oldRef) ||
267
+ (newRef !== "@working" && !/^[a-f0-9]{40,64}$/.test(newRef))
268
+ )
269
+ throw new Error("Invalid comparison revision.");
270
+ const oldContents = await git(root, ["show", `${oldRef}:${oldName}`]);
271
+ let newContents;
272
+ if (newRef === "@working") {
273
+ const resolved = await realpath(current);
274
+ const info = await lstat(current);
275
+ if (!resolved.startsWith(root + path.sep) || !info.isFile())
276
+ throw new Error("Cannot expand context for this file.");
277
+ if (info.size > 2 * 1024 * 1024)
278
+ throw new Error("Context expansion is limited to files under 2 MB.");
279
+ newContents = await readFile(current, "utf8");
280
+ if (expectedHash && !/^0+$/.test(expectedHash)) {
281
+ const hash = (
282
+ await git(root, ["hash-object", `--path=${name}`, "--", name])
283
+ ).trim();
284
+ if (!hash.startsWith(expectedHash))
285
+ throw new Error(
286
+ "This file changed since the diff was loaded. Refresh to expand its context.",
287
+ );
288
+ }
289
+ } else newContents = await git(root, ["show", `${newRef}:${name}`]);
290
+ if (
291
+ Buffer.byteLength(oldContents) > 2 * 1024 * 1024 ||
292
+ Buffer.byteLength(newContents) > 2 * 1024 * 1024
293
+ )
294
+ throw new Error("Context expansion is limited to files under 2 MB.");
295
+ if (oldContents.includes("\0") || newContents.includes("\0"))
296
+ throw new Error("Binary files do not have text context.");
297
+ return {
298
+ oldFile: { name: oldName, contents: oldContents },
299
+ newFile: { name, contents: newContents },
300
+ };
301
+ }
@@ -0,0 +1,113 @@
1
+ import { spawn } from "node:child_process";
2
+ import { stat } from "node:fs/promises";
3
+
4
+ const prompt = "Choose a workspace";
5
+
6
+ function run(command, args, signal) {
7
+ return new Promise((resolve, reject) => {
8
+ const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
9
+ let out = "";
10
+ let err = "";
11
+ const stop = () => child.kill();
12
+ signal?.addEventListener("abort", stop, { once: true });
13
+ child.stdout.on("data", (chunk) => (out += chunk));
14
+ child.stderr.on("data", (chunk) => (err += chunk));
15
+ child.on("error", (error) =>
16
+ reject(
17
+ Object.assign(
18
+ new Error(
19
+ error.code === "ENOENT"
20
+ ? `${command} is not available on this system.`
21
+ : error.message,
22
+ ),
23
+ { missing: error.code === "ENOENT" },
24
+ ),
25
+ ),
26
+ );
27
+ child.on("close", (code) => {
28
+ signal?.removeEventListener("abort", stop);
29
+ resolve({
30
+ code,
31
+ out: out.trim(),
32
+ err: err.trim(),
33
+ aborted: !!signal?.aborted,
34
+ });
35
+ });
36
+ });
37
+ }
38
+ async function directoryOrHome(value) {
39
+ try {
40
+ if (value && (await stat(value)).isDirectory()) return value;
41
+ } catch {
42
+ // Fall through to letting the dialog choose its own starting point.
43
+ }
44
+ return "";
45
+ }
46
+ async function mac(start, signal) {
47
+ const location = await directoryOrHome(start);
48
+ // Without activating first the dialog opens behind the browser window.
49
+ const script = [
50
+ 'tell application "System Events"',
51
+ "activate",
52
+ `POSIX path of (choose folder with prompt ${JSON.stringify(prompt)}${
53
+ location ? ` default location POSIX file ${JSON.stringify(location)}` : ""
54
+ })`,
55
+ "end tell",
56
+ ].join("\n");
57
+ const { code, out, err, aborted } = await run(
58
+ "osascript",
59
+ ["-e", script],
60
+ signal,
61
+ );
62
+ if (code === 0 && out) return { path: out.replace(/\/$/, "") };
63
+ if (aborted || err.includes("-128")) return { canceled: true };
64
+ throw new Error(err || "The folder dialog closed unexpectedly.");
65
+ }
66
+ async function linux(start, signal) {
67
+ const location = await directoryOrHome(start);
68
+ try {
69
+ const { code, out } = await run(
70
+ "zenity",
71
+ [
72
+ "--file-selection",
73
+ "--directory",
74
+ `--title=${prompt}`,
75
+ ...(location ? [`--filename=${location}/`] : []),
76
+ ],
77
+ signal,
78
+ );
79
+ if (code === 0 && out) return { path: out };
80
+ return { canceled: true };
81
+ } catch (error) {
82
+ if (!error.missing) throw error;
83
+ }
84
+ const { code, out } = await run(
85
+ "kdialog",
86
+ ["--title", prompt, "--getexistingdirectory", location || "."],
87
+ signal,
88
+ );
89
+ if (code === 0 && out) return { path: out };
90
+ return { canceled: true };
91
+ }
92
+ async function windows(start, signal) {
93
+ const location = await directoryOrHome(start);
94
+ const script = [
95
+ "Add-Type -AssemblyName System.Windows.Forms",
96
+ "$dialog = New-Object System.Windows.Forms.FolderBrowserDialog",
97
+ `$dialog.Description = ${JSON.stringify(prompt)}`,
98
+ ...(location ? [`$dialog.SelectedPath = ${JSON.stringify(location)}`] : []),
99
+ "if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $dialog.SelectedPath }",
100
+ ].join("; ");
101
+ const { code, out } = await run(
102
+ "powershell",
103
+ ["-NoProfile", "-STA", "-Command", script],
104
+ signal,
105
+ );
106
+ if (code === 0 && out) return { path: out };
107
+ return { canceled: true };
108
+ }
109
+ export function chooseDirectory(start, signal) {
110
+ if (process.platform === "darwin") return mac(start, signal);
111
+ if (process.platform === "win32") return windows(start, signal);
112
+ return linux(start, signal);
113
+ }