@rsktash/beads-ui 0.1.50 → 0.10.3

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/README.md +85 -144
  2. package/bin/bd-web +94 -0
  3. package/dist/assets/index-BBVIMXaM.js +73 -0
  4. package/dist/assets/index-SUXI6Mzt.css +1 -0
  5. package/dist/bd-favicon-16.svg +6 -0
  6. package/dist/bd-favicon-32.svg +6 -0
  7. package/dist/bd-logo-512.svg +6 -0
  8. package/dist/favicon.ico +0 -0
  9. package/dist/icon-180.png +0 -0
  10. package/dist/icon-192.png +0 -0
  11. package/dist/icon-512.png +0 -0
  12. package/dist/index.html +8 -5
  13. package/package.json +39 -29
  14. package/server/auth.js +68 -87
  15. package/server/db.js +91 -134
  16. package/server/dsn.js +130 -0
  17. package/server/index.js +151 -87
  18. package/server/queries.js +247 -0
  19. package/server/routes/auth.js +27 -0
  20. package/server/routes/issues.js +120 -0
  21. package/server/routes/stream.js +111 -0
  22. package/server/types.js +83 -0
  23. package/app/protocol.js +0 -216
  24. package/bin/bd-grep +0 -121
  25. package/bin/bd-ui +0 -19
  26. package/dist/assets/abap-DsBKuouk.js +0 -1
  27. package/dist/assets/actionscript-3-D_z4Izcz.js +0 -1
  28. package/dist/assets/ada-727ZlQH0.js +0 -1
  29. package/dist/assets/andromeeda-C3khCPGq.js +0 -1
  30. package/dist/assets/angular-html-LfdN0zeE.js +0 -1
  31. package/dist/assets/angular-ts-CKsD7JZE.js +0 -1
  32. package/dist/assets/apache-Dn00JSTd.js +0 -1
  33. package/dist/assets/apex-COJ4H7py.js +0 -1
  34. package/dist/assets/apl-BBq3IX1j.js +0 -1
  35. package/dist/assets/applescript-Bu5BbsvL.js +0 -1
  36. package/dist/assets/ara-7O62HKoU.js +0 -1
  37. package/dist/assets/asciidoc-BPT9niGB.js +0 -1
  38. package/dist/assets/asm-Dhn9LcZ4.js +0 -1
  39. package/dist/assets/astro-CqkE3fuf.js +0 -1
  40. package/dist/assets/aurora-x-D-2ljcwZ.js +0 -1
  41. package/dist/assets/awk-eg146-Ew.js +0 -1
  42. package/dist/assets/ayu-dark-Cv9koXgw.js +0 -1
  43. package/dist/assets/ballerina-Du268qiB.js +0 -1
  44. package/dist/assets/bat-fje9CFhw.js +0 -1
  45. package/dist/assets/beancount-BwXTMy5W.js +0 -1
  46. package/dist/assets/berry-3xVqZejG.js +0 -1
  47. package/dist/assets/bibtex-xW4inM5L.js +0 -1
  48. package/dist/assets/bicep-DHo0CJ0O.js +0 -1
  49. package/dist/assets/blade-a8OxSdnT.js +0 -1
  50. package/dist/assets/bsl-Dgyn0ogV.js +0 -1
  51. package/dist/assets/c-C3t2pwGQ.js +0 -1
  52. package/dist/assets/cadence-DNquZEk8.js +0 -1
  53. package/dist/assets/cairo--RitsXJZ.js +0 -1
  54. package/dist/assets/catppuccin-frappe-CD_QflpE.js +0 -1
  55. package/dist/assets/catppuccin-latte-DRW-0cLl.js +0 -1
  56. package/dist/assets/catppuccin-macchiato-C-_shW-Y.js +0 -1
  57. package/dist/assets/catppuccin-mocha-LGGdnPYs.js +0 -1
  58. package/dist/assets/clarity-BHOwM8T6.js +0 -1
  59. package/dist/assets/clojure-DxSadP1t.js +0 -1
  60. package/dist/assets/cmake-DbXoA79R.js +0 -1
  61. package/dist/assets/cobol-PTqiYgYu.js +0 -1
  62. package/dist/assets/codeowners-Bp6g37R7.js +0 -1
  63. package/dist/assets/codeql-sacFqUAJ.js +0 -1
  64. package/dist/assets/coffee-dyiR41kL.js +0 -1
  65. package/dist/assets/common-lisp-C7gG9l05.js +0 -1
  66. package/dist/assets/coq-Dsg_Bt_b.js +0 -1
  67. package/dist/assets/cpp-BksuvNSY.js +0 -1
  68. package/dist/assets/crystal-DtDmRg-F.js +0 -1
  69. package/dist/assets/csharp-D9R-vmeu.js +0 -1
  70. package/dist/assets/css-BPhBrDlE.js +0 -1
  71. package/dist/assets/csv-B0qRVHPH.js +0 -1
  72. package/dist/assets/cue-DtFQj3wx.js +0 -1
  73. package/dist/assets/cypher-m2LEI-9-.js +0 -1
  74. package/dist/assets/d-BoXegm-a.js +0 -1
  75. package/dist/assets/dark-plus-C3mMm8J8.js +0 -1
  76. package/dist/assets/dart-B9wLZaAG.js +0 -1
  77. package/dist/assets/dax-ClGRhx96.js +0 -1
  78. package/dist/assets/desktop-DEIpsLCJ.js +0 -1
  79. package/dist/assets/diff-BgYniUM_.js +0 -1
  80. package/dist/assets/docker-COcR7UxN.js +0 -1
  81. package/dist/assets/dotenv-BjQB5zDj.js +0 -1
  82. package/dist/assets/dracula-BzJJZx-M.js +0 -1
  83. package/dist/assets/dracula-soft-BXkSAIEj.js +0 -1
  84. package/dist/assets/dream-maker-C-nORZOA.js +0 -1
  85. package/dist/assets/edge-D5gP-w-T.js +0 -1
  86. package/dist/assets/elixir-CLiX3zqd.js +0 -1
  87. package/dist/assets/elm-CmHSxxaM.js +0 -1
  88. package/dist/assets/emacs-lisp-BX77sIaO.js +0 -1
  89. package/dist/assets/erb-BYTLMnw6.js +0 -1
  90. package/dist/assets/erlang-B-DoSBHF.js +0 -1
  91. package/dist/assets/everforest-dark-BgDCqdQA.js +0 -1
  92. package/dist/assets/everforest-light-C8M2exoo.js +0 -1
  93. package/dist/assets/fennel-bCA53EVm.js +0 -1
  94. package/dist/assets/fish-w-ucz2PV.js +0 -1
  95. package/dist/assets/fluent-Dayu4EKP.js +0 -1
  96. package/dist/assets/fortran-fixed-form-TqA4NnZg.js +0 -1
  97. package/dist/assets/fortran-free-form-DKXYxT9g.js +0 -1
  98. package/dist/assets/fsharp-XplgxFYe.js +0 -1
  99. package/dist/assets/gdresource-BHYsBjWJ.js +0 -1
  100. package/dist/assets/gdscript-DfxzS6Rs.js +0 -1
  101. package/dist/assets/gdshader-SKMF96pI.js +0 -1
  102. package/dist/assets/genie-ajMbGru0.js +0 -1
  103. package/dist/assets/gherkin--30QC5Em.js +0 -1
  104. package/dist/assets/git-commit-i4q6IMui.js +0 -1
  105. package/dist/assets/git-rebase-B-v9cOL2.js +0 -1
  106. package/dist/assets/github-dark-DHJKELXO.js +0 -1
  107. package/dist/assets/github-dark-default-Cuk6v7N8.js +0 -1
  108. package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +0 -1
  109. package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -1
  110. package/dist/assets/github-light-DAi9KRSo.js +0 -1
  111. package/dist/assets/github-light-default-D7oLnXFd.js +0 -1
  112. package/dist/assets/github-light-high-contrast-BfjtVDDH.js +0 -1
  113. package/dist/assets/gleam-B430Bg39.js +0 -1
  114. package/dist/assets/glimmer-js-D-cwc0-E.js +0 -1
  115. package/dist/assets/glimmer-ts-pgjy16dm.js +0 -1
  116. package/dist/assets/glsl-DBO2IWDn.js +0 -1
  117. package/dist/assets/gnuplot-CM8KxXT1.js +0 -1
  118. package/dist/assets/go-B1SYOhNW.js +0 -1
  119. package/dist/assets/graphql-cDcHW_If.js +0 -1
  120. package/dist/assets/groovy-DkBy-JyN.js +0 -1
  121. package/dist/assets/hack-D1yCygmZ.js +0 -1
  122. package/dist/assets/haml-B2EZWmdv.js +0 -1
  123. package/dist/assets/handlebars-BQGss363.js +0 -1
  124. package/dist/assets/haskell-BILxekzW.js +0 -1
  125. package/dist/assets/haxe-C5wWYbrZ.js +0 -1
  126. package/dist/assets/hcl-HzYwdGDm.js +0 -1
  127. package/dist/assets/hjson-T-Tgc4AT.js +0 -1
  128. package/dist/assets/hlsl-ifBTmRxC.js +0 -1
  129. package/dist/assets/houston-DnULxvSX.js +0 -1
  130. package/dist/assets/html-C2L_23MC.js +0 -1
  131. package/dist/assets/html-derivative-CSfWNPLT.js +0 -1
  132. package/dist/assets/http-FRrOvY1W.js +0 -1
  133. package/dist/assets/hxml-TIA70rKU.js +0 -1
  134. package/dist/assets/hy-BMj5Y0dO.js +0 -1
  135. package/dist/assets/imba-bv_oIlVt.js +0 -1
  136. package/dist/assets/index-BuQ1xTdM.css +0 -1
  137. package/dist/assets/index-DclShbpd.js +0 -125
  138. package/dist/assets/ini-BjABl1g7.js +0 -1
  139. package/dist/assets/java-xI-RfyKK.js +0 -1
  140. package/dist/assets/javascript-ySlJ1b_l.js +0 -1
  141. package/dist/assets/jinja-DGy0s7-h.js +0 -1
  142. package/dist/assets/jison-BqZprYcd.js +0 -1
  143. package/dist/assets/json-BQoSv7ci.js +0 -1
  144. package/dist/assets/json5-w8dY5SsB.js +0 -1
  145. package/dist/assets/jsonc-TU54ms6u.js +0 -1
  146. package/dist/assets/jsonl-DREVFZK8.js +0 -1
  147. package/dist/assets/jsonnet-BfivnA6A.js +0 -1
  148. package/dist/assets/jssm-P4WzXJd0.js +0 -1
  149. package/dist/assets/jsx-BAng5TT0.js +0 -1
  150. package/dist/assets/julia-BBuGR-5E.js +0 -1
  151. package/dist/assets/kanagawa-dragon-CkXjmgJE.js +0 -1
  152. package/dist/assets/kanagawa-lotus-CfQXZHmo.js +0 -1
  153. package/dist/assets/kanagawa-wave-DWedfzmr.js +0 -1
  154. package/dist/assets/kotlin-B5lbUyaz.js +0 -1
  155. package/dist/assets/kusto-mebxcVVE.js +0 -1
  156. package/dist/assets/laserwave-DUszq2jm.js +0 -1
  157. package/dist/assets/latex-C-cWTeAZ.js +0 -1
  158. package/dist/assets/lean-XBlWyCtg.js +0 -1
  159. package/dist/assets/less-BfCpw3nA.js +0 -1
  160. package/dist/assets/light-plus-B7mTdjB0.js +0 -1
  161. package/dist/assets/liquid-D3W5UaiH.js +0 -1
  162. package/dist/assets/log-Cc5clBb7.js +0 -1
  163. package/dist/assets/logo-IuBKFhSY.js +0 -1
  164. package/dist/assets/lua-CvWAzNxB.js +0 -1
  165. package/dist/assets/luau-Du5NY7AG.js +0 -1
  166. package/dist/assets/make-Bvotw-X0.js +0 -1
  167. package/dist/assets/markdown-UIAJJxZW.js +0 -1
  168. package/dist/assets/marko-z0MBrx5-.js +0 -1
  169. package/dist/assets/material-theme-D5KoaKCx.js +0 -1
  170. package/dist/assets/material-theme-darker-BfHTSMKl.js +0 -1
  171. package/dist/assets/material-theme-lighter-B0m2ddpp.js +0 -1
  172. package/dist/assets/material-theme-ocean-CyktbL80.js +0 -1
  173. package/dist/assets/material-theme-palenight-Csfq5Kiy.js +0 -1
  174. package/dist/assets/matlab-D9-PGadD.js +0 -1
  175. package/dist/assets/mdc-DB_EDNY_.js +0 -1
  176. package/dist/assets/mdx-sdHcTMYB.js +0 -1
  177. package/dist/assets/mermaid-Ci6OQyBP.js +0 -1
  178. package/dist/assets/min-dark-CafNBF8u.js +0 -1
  179. package/dist/assets/min-light-CTRr51gU.js +0 -1
  180. package/dist/assets/mipsasm-BC5c_5Pe.js +0 -1
  181. package/dist/assets/mojo-Tz6hzZYG.js +0 -1
  182. package/dist/assets/monokai-D4h5O-jR.js +0 -1
  183. package/dist/assets/move-DB_GagMm.js +0 -1
  184. package/dist/assets/narrat-DLbgOhZU.js +0 -1
  185. package/dist/assets/nextflow-B0XVJmRM.js +0 -1
  186. package/dist/assets/nginx-D_VnBJ67.js +0 -1
  187. package/dist/assets/night-owl-C39BiMTA.js +0 -1
  188. package/dist/assets/nim-ZlGxZxc3.js +0 -1
  189. package/dist/assets/nix-shcSOmrb.js +0 -1
  190. package/dist/assets/nord-Ddv68eIx.js +0 -1
  191. package/dist/assets/nushell-D4Tzg5kh.js +0 -1
  192. package/dist/assets/objective-c-Deuh7S70.js +0 -1
  193. package/dist/assets/objective-cpp-BUEGK8hf.js +0 -1
  194. package/dist/assets/ocaml-BNioltXt.js +0 -1
  195. package/dist/assets/one-dark-pro-GBQ2dnAY.js +0 -1
  196. package/dist/assets/one-light-PoHY5YXO.js +0 -1
  197. package/dist/assets/pascal-JqZropPD.js +0 -1
  198. package/dist/assets/perl-CHQXSrWU.js +0 -1
  199. package/dist/assets/php-B5ebYQev.js +0 -1
  200. package/dist/assets/plastic-3e1v2bzS.js +0 -1
  201. package/dist/assets/plsql-LKU2TuZ1.js +0 -1
  202. package/dist/assets/po-BFLt1xDp.js +0 -1
  203. package/dist/assets/poimandres-CS3Unz2-.js +0 -1
  204. package/dist/assets/polar-DKykz6zU.js +0 -1
  205. package/dist/assets/postcss-B3ZDOciz.js +0 -1
  206. package/dist/assets/powerquery-CSHBycmS.js +0 -1
  207. package/dist/assets/powershell-BIEUsx6d.js +0 -1
  208. package/dist/assets/prisma-B48N-Iqd.js +0 -1
  209. package/dist/assets/prolog-BY-TUvya.js +0 -1
  210. package/dist/assets/proto-zocC4JxJ.js +0 -1
  211. package/dist/assets/pug-CM9l7STV.js +0 -1
  212. package/dist/assets/puppet-Cza_XSSt.js +0 -1
  213. package/dist/assets/purescript-Bg-kzb6g.js +0 -1
  214. package/dist/assets/python-DhUJRlN_.js +0 -1
  215. package/dist/assets/qml-D8XfuvdV.js +0 -1
  216. package/dist/assets/qmldir-C8lEn-DE.js +0 -1
  217. package/dist/assets/qss-DhMKtDLN.js +0 -1
  218. package/dist/assets/r-CwjWoCRV.js +0 -1
  219. package/dist/assets/racket-CzouJOBO.js +0 -1
  220. package/dist/assets/raku-B1bQXN8T.js +0 -1
  221. package/dist/assets/razor-CNLDkMZG.js +0 -1
  222. package/dist/assets/red-bN70gL4F.js +0 -1
  223. package/dist/assets/reg-5LuOXUq_.js +0 -1
  224. package/dist/assets/regexp-DWJ3fJO_.js +0 -1
  225. package/dist/assets/rel-DJlmqQ1C.js +0 -1
  226. package/dist/assets/riscv-QhoSD0DR.js +0 -1
  227. package/dist/assets/rose-pine-CmCqftbK.js +0 -1
  228. package/dist/assets/rose-pine-dawn-Ds-gbosJ.js +0 -1
  229. package/dist/assets/rose-pine-moon-CjDtw9vr.js +0 -1
  230. package/dist/assets/rst-4NLicBqY.js +0 -1
  231. package/dist/assets/ruby-DeZ3UC14.js +0 -1
  232. package/dist/assets/rust-Be6lgOlo.js +0 -1
  233. package/dist/assets/sas-BmTFh92c.js +0 -1
  234. package/dist/assets/sass-BJ4Li9vH.js +0 -1
  235. package/dist/assets/scala-DQVVAn-B.js +0 -1
  236. package/dist/assets/scheme-BJGe-b2p.js +0 -1
  237. package/dist/assets/scss-C31hgJw-.js +0 -1
  238. package/dist/assets/sdbl-BLhTXw86.js +0 -1
  239. package/dist/assets/shaderlab-B7qAK45m.js +0 -1
  240. package/dist/assets/shellscript-atvbtKCR.js +0 -1
  241. package/dist/assets/shellsession-C_rIy8kc.js +0 -1
  242. package/dist/assets/slack-dark-BthQWCQV.js +0 -1
  243. package/dist/assets/slack-ochin-DqwNpetd.js +0 -1
  244. package/dist/assets/smalltalk-DkLiglaE.js +0 -1
  245. package/dist/assets/snazzy-light-Bw305WKR.js +0 -1
  246. package/dist/assets/solarized-dark-DXbdFlpD.js +0 -1
  247. package/dist/assets/solarized-light-L9t79GZl.js +0 -1
  248. package/dist/assets/solidity-C1w2a3ep.js +0 -1
  249. package/dist/assets/soy-C-lX7w71.js +0 -1
  250. package/dist/assets/sparql-bYkjHRlG.js +0 -1
  251. package/dist/assets/splunk-Cf8iN4DR.js +0 -1
  252. package/dist/assets/sql-COK4E0Yg.js +0 -1
  253. package/dist/assets/ssh-config-BknIz3MU.js +0 -1
  254. package/dist/assets/stata-DorPZHa4.js +0 -1
  255. package/dist/assets/stylus-BeQkCIfX.js +0 -1
  256. package/dist/assets/svelte-MSaWC3Je.js +0 -1
  257. package/dist/assets/swift-BSxZ-RaX.js +0 -1
  258. package/dist/assets/synthwave-84-CbfX1IO0.js +0 -1
  259. package/dist/assets/system-verilog-C7L56vO4.js +0 -1
  260. package/dist/assets/systemd-CUnW07Te.js +0 -1
  261. package/dist/assets/talonscript-C1XDQQGZ.js +0 -1
  262. package/dist/assets/tasl-CQjiPCtT.js +0 -1
  263. package/dist/assets/tcl-DQ1-QYvQ.js +0 -1
  264. package/dist/assets/templ-dwX3ZSMB.js +0 -1
  265. package/dist/assets/terraform-BbSNqyBO.js +0 -1
  266. package/dist/assets/tex-rYs2v40G.js +0 -1
  267. package/dist/assets/tokyo-night-DBQeEorK.js +0 -1
  268. package/dist/assets/toml-CB2ApiWb.js +0 -1
  269. package/dist/assets/ts-tags-CipyTH0X.js +0 -1
  270. package/dist/assets/tsv-B_m7g4N7.js +0 -1
  271. package/dist/assets/tsx-B6W0miNI.js +0 -1
  272. package/dist/assets/turtle-BMR_PYu6.js +0 -1
  273. package/dist/assets/twig-NC5TFiHP.js +0 -1
  274. package/dist/assets/typescript-Dj6nwHGl.js +0 -1
  275. package/dist/assets/typespec-BpWG_bgh.js +0 -1
  276. package/dist/assets/typst-BVUVsWT6.js +0 -1
  277. package/dist/assets/v-CAQ2eGtk.js +0 -1
  278. package/dist/assets/vala-BFOHcciG.js +0 -1
  279. package/dist/assets/vb-CdO5JTpU.js +0 -1
  280. package/dist/assets/verilog-CJaU5se_.js +0 -1
  281. package/dist/assets/vesper-BEBZ7ncR.js +0 -1
  282. package/dist/assets/vhdl-DYoNaHQp.js +0 -1
  283. package/dist/assets/viml-m4uW47V2.js +0 -1
  284. package/dist/assets/vitesse-black-Bkuqu6BP.js +0 -1
  285. package/dist/assets/vitesse-dark-D0r3Knsf.js +0 -1
  286. package/dist/assets/vitesse-light-CVO1_9PV.js +0 -1
  287. package/dist/assets/vue-BuYVFjOK.js +0 -1
  288. package/dist/assets/vue-html-xdeiXROB.js +0 -1
  289. package/dist/assets/vyper-nyqBNV6O.js +0 -1
  290. package/dist/assets/wasm-C6j12Q_x.js +0 -1
  291. package/dist/assets/wasm-CG6Dc4jp.js +0 -1
  292. package/dist/assets/wenyan-7A4Fjokl.js +0 -1
  293. package/dist/assets/wgsl-CB0Krxn9.js +0 -1
  294. package/dist/assets/wikitext-DCE3LsBG.js +0 -1
  295. package/dist/assets/wolfram-C3FkfJm5.js +0 -1
  296. package/dist/assets/xml-e3z08dGr.js +0 -1
  297. package/dist/assets/xsl-Dd0NUgwM.js +0 -1
  298. package/dist/assets/yaml-CVw76BM1.js +0 -1
  299. package/dist/assets/zenscript-HnGAYVZD.js +0 -1
  300. package/dist/assets/zig-BVz_zdnA.js +0 -1
  301. package/server/app.js +0 -165
  302. package/server/app.test.js +0 -30
  303. package/server/bd.js +0 -227
  304. package/server/bd.test.js +0 -194
  305. package/server/cli/cli.test.js +0 -207
  306. package/server/cli/commands.integration.test.js +0 -148
  307. package/server/cli/commands.js +0 -285
  308. package/server/cli/commands.unit.test.js +0 -408
  309. package/server/cli/daemon.js +0 -340
  310. package/server/cli/daemon.test.js +0 -31
  311. package/server/cli/index.js +0 -135
  312. package/server/cli/open.js +0 -178
  313. package/server/cli/open.test.js +0 -26
  314. package/server/cli/usage.js +0 -49
  315. package/server/config.js +0 -36
  316. package/server/db.test.js +0 -169
  317. package/server/dolt-pool.js +0 -313
  318. package/server/dolt-queries.js +0 -764
  319. package/server/list-adapters.js +0 -421
  320. package/server/list-adapters.test.js +0 -208
  321. package/server/logging.js +0 -23
  322. package/server/registry-watcher.js +0 -200
  323. package/server/subscriptions.js +0 -299
  324. package/server/subscriptions.test.js +0 -128
  325. package/server/validators.js +0 -124
  326. package/server/watcher.js +0 -139
  327. package/server/watcher.test.js +0 -120
  328. package/server/ws.comments.test.js +0 -262
  329. package/server/ws.delete.test.js +0 -119
  330. package/server/ws.js +0 -1329
  331. package/server/ws.labels.test.js +0 -95
  332. package/server/ws.list-refresh.coalesce.test.js +0 -95
  333. package/server/ws.list-subscriptions.test.js +0 -403
  334. package/server/ws.mutation-window.test.js +0 -147
  335. package/server/ws.mutations.test.js +0 -389
  336. package/server/ws.test.js +0 -52
@@ -1,285 +0,0 @@
1
- import { getConfig } from '../config.js';
2
- import { resolveWorkspaceDatabase } from '../db.js';
3
- import {
4
- detectListeningPort,
5
- findAvailablePort,
6
- isProcessRunning,
7
- printServerUrl,
8
- readPidFile,
9
- removePidFile,
10
- startDaemon,
11
- terminateProcess
12
- } from './daemon.js';
13
- import {
14
- fetchWorkspacesFromServer,
15
- openUrl,
16
- registerWorkspaceWithServer,
17
- waitForServer
18
- } from './open.js';
19
-
20
- const RESTART_SERVER_READY_MS = 400;
21
-
22
- const STARTUP_SETTLE_MS = 200;
23
- const REGISTER_RETRY_ATTEMPTS = 5;
24
- const REGISTER_RETRY_DELAY_MS = 150;
25
-
26
- /**
27
- * Handle `start` command. Idempotent when already running.
28
- * - Spawns a detached server process, writes PID file, returns 0.
29
- * - If already running (PID file present and process alive), prints URL and returns 0.
30
- *
31
- * @param {{ open?: boolean, is_debug?: boolean, host?: string, port?: number }} [options]
32
- * @returns {Promise<number>} Exit code (0 on success)
33
- */
34
- export async function handleStart(options) {
35
- // Default: do not open a browser unless explicitly requested via `open: true`.
36
- const should_open = options?.open === true;
37
- const cwd = process.cwd();
38
-
39
- // Set env vars early so getConfig() reflects CLI overrides in ALL branches,
40
- // including the "already running" path that registers workspaces via HTTP.
41
- if (options?.host) {
42
- process.env.HOST = options.host;
43
- }
44
- if (options?.port) {
45
- process.env.PORT = String(options.port);
46
- }
47
-
48
- const existing_pid = readPidFile();
49
- if (existing_pid && isProcessRunning(existing_pid)) {
50
- // Server is already running - register this workspace dynamically
51
- const { url } = getConfig();
52
- const registered = await registerCurrentWorkspace(url, cwd);
53
- if (registered) {
54
- console.log('Workspace registered: %s', cwd);
55
- }
56
- console.warn('Server is already running.');
57
- if (should_open) {
58
- await openUrl(url);
59
- }
60
- return 0;
61
- }
62
- if (existing_pid && !isProcessRunning(existing_pid)) {
63
- // stale PID file
64
- removePidFile();
65
- }
66
-
67
- const { port: config_port, host: config_host } = getConfig();
68
-
69
- // When the user did not pass an explicit --port, check whether the default
70
- // port is already in use. If something is already listening, try to register
71
- // with it first — it may be an existing bdui instance we can reuse.
72
- // Only auto-increment to the next port if registration fails.
73
- let effective_port = options?.port;
74
- if (!effective_port) {
75
- const available = await findAvailablePort(config_port, config_host);
76
- if (available === null) {
77
- console.error(
78
- 'No available port found (tried %d–%d).',
79
- config_port,
80
- config_port + 9
81
- );
82
- return 1;
83
- }
84
- if (available !== config_port) {
85
- // Default port is busy — try to register with whatever is there.
86
- const existing_url = `http://${config_host}:${config_port}`;
87
- const registered = await registerCurrentWorkspace(existing_url, cwd);
88
- if (registered) {
89
- console.log('Workspace registered with existing server: %s', cwd);
90
- if (should_open) {
91
- await openUrl(existing_url);
92
- }
93
- return 0;
94
- }
95
- // Not a bdui instance — auto-increment to the next available port.
96
- console.log('Port %d in use, using %d instead.', config_port, available);
97
- effective_port = available;
98
- }
99
- }
100
-
101
- // Set PORT env so getConfig() returns the correct URL for registration
102
- if (effective_port) {
103
- process.env.PORT = String(effective_port);
104
- }
105
- const { url } = getConfig();
106
-
107
- const started = startDaemon({
108
- is_debug: options?.is_debug,
109
- host: options?.host,
110
- port: effective_port
111
- });
112
- if (started && started.pid > 0) {
113
- // Give the spawned daemon a brief moment to fail fast (for example EADDRINUSE).
114
- await sleep(STARTUP_SETTLE_MS);
115
-
116
- if (!isProcessRunning(started.pid)) {
117
- removePidFile();
118
-
119
- // If another server is already running at the configured URL, register this
120
- // workspace there so it appears in the picker instead of silently missing.
121
- const registered = await registerCurrentWorkspaceWithRetry(url, cwd);
122
- if (registered) {
123
- console.warn(
124
- 'Daemon exited early; registered workspace with existing server: %s',
125
- cwd
126
- );
127
- return 0;
128
- }
129
- return 1;
130
- }
131
-
132
- // Register against the currently reachable server to ensure this workspace
133
- // appears in the picker even when startup races with other daemons.
134
- void registerCurrentWorkspaceWithRetry(url, cwd);
135
-
136
- printServerUrl();
137
- // Auto-open the browser once for a fresh daemon start
138
- if (should_open) {
139
- // Wait briefly for the server to accept connections (single retry window)
140
- await waitForServer(url, 600);
141
- // Best-effort open; ignore result
142
- await openUrl(url);
143
- }
144
- return 0;
145
- }
146
-
147
- return 1;
148
- }
149
-
150
- /**
151
- * @param {number} ms
152
- * @returns {Promise<void>}
153
- */
154
- function sleep(ms) {
155
- return new Promise((resolve) => {
156
- setTimeout(() => {
157
- resolve();
158
- }, ms);
159
- });
160
- }
161
-
162
- /**
163
- * @param {string} url
164
- * @param {string} cwd
165
- * @returns {Promise<boolean>}
166
- */
167
- async function registerCurrentWorkspace(url, cwd) {
168
- const workspace_database = resolveWorkspaceDatabase({ cwd });
169
- if (
170
- workspace_database.source === 'home-default' ||
171
- !workspace_database.exists
172
- ) {
173
- return false;
174
- }
175
-
176
- return registerWorkspaceWithServer(url, {
177
- path: cwd,
178
- database: workspace_database.path
179
- });
180
- }
181
-
182
- /**
183
- * @param {string} url
184
- * @param {string} cwd
185
- * @returns {Promise<boolean>}
186
- */
187
- async function registerCurrentWorkspaceWithRetry(url, cwd) {
188
- for (let i = 0; i < REGISTER_RETRY_ATTEMPTS; i++) {
189
- const registered = await registerCurrentWorkspace(url, cwd);
190
- if (registered) {
191
- return true;
192
- }
193
- if (i < REGISTER_RETRY_ATTEMPTS - 1) {
194
- await sleep(REGISTER_RETRY_DELAY_MS);
195
- }
196
- }
197
- return false;
198
- }
199
-
200
- /**
201
- * Handle `stop` command.
202
- * - Sends SIGTERM and waits for exit (with SIGKILL fallback), removes PID file.
203
- * - Returns 2 if not running.
204
- *
205
- * @returns {Promise<number>} Exit code
206
- */
207
- export async function handleStop() {
208
- const existing_pid = readPidFile();
209
- if (!existing_pid) {
210
- return 2;
211
- }
212
-
213
- if (!isProcessRunning(existing_pid)) {
214
- // stale PID file
215
- removePidFile();
216
- return 2;
217
- }
218
-
219
- const terminated = await terminateProcess(existing_pid, 5000);
220
- if (terminated) {
221
- removePidFile();
222
- return 0;
223
- }
224
-
225
- // Not terminated within timeout
226
- return 1;
227
- }
228
-
229
- /**
230
- * Handle `restart` command: stop (ignore not-running) then start.
231
- * Accepts the same options as `handleStart` and passes them through,
232
- * so restart only opens a browser when `open` is explicitly true.
233
- *
234
- * When the user does not pass explicit `--port`, the restart detects the
235
- * port the running daemon is listening on and reuses it.
236
- *
237
- * @param {{ open?: boolean, host?: string, port?: number }} [options]
238
- * @returns {Promise<number>}
239
- */
240
- export async function handleRestart(options) {
241
- // Capture state from the running server before stopping it.
242
- let detected_port = null;
243
- /** @type {Array<{ path: string, database: string }>} */
244
- let saved_workspaces = [];
245
- const existing_pid = readPidFile();
246
- if (existing_pid && isProcessRunning(existing_pid)) {
247
- detected_port = detectListeningPort(existing_pid);
248
-
249
- const { url } = getConfig();
250
- saved_workspaces = await fetchWorkspacesFromServer(url);
251
- }
252
-
253
- const stop_code = await handleStop();
254
- // 0 = stopped, 2 = not running; both are acceptable to proceed
255
- if (stop_code !== 0 && stop_code !== 2) {
256
- return 1;
257
- }
258
-
259
- // Reuse detected port unless the user explicitly passed one.
260
- const merged_options = { ...options };
261
- if (!merged_options.port && detected_port) {
262
- merged_options.port = detected_port;
263
- }
264
-
265
- const start_code = await handleStart(merged_options);
266
- if (start_code !== 0) {
267
- return 1;
268
- }
269
-
270
- // Re-register workspaces from the previous server.
271
- if (saved_workspaces.length > 0) {
272
- const { url } = getConfig();
273
- await waitForServer(url, RESTART_SERVER_READY_MS);
274
- for (const ws of saved_workspaces) {
275
- if (ws.path && ws.database) {
276
- await registerWorkspaceWithServer(url, {
277
- path: ws.path,
278
- database: ws.database
279
- });
280
- }
281
- }
282
- }
283
-
284
- return 0;
285
- }
@@ -1,408 +0,0 @@
1
- import path from 'node:path';
2
- import { afterEach, describe, expect, test, vi } from 'vitest';
3
- import { handleRestart, handleStart, handleStop } from './commands.js';
4
- import * as daemon from './daemon.js';
5
- import * as open from './open.js';
6
-
7
- // Mock open.js to avoid external effects
8
- vi.mock('./open.js', () => ({
9
- openUrl: async () => true,
10
- waitForServer: async () => {},
11
- fetchWorkspacesFromServer: vi.fn(async () => []),
12
- registerWorkspaceWithServer: vi.fn(async () => true)
13
- }));
14
-
15
- // Mock db resolution
16
- vi.mock('../db.js', () => ({
17
- resolveDbPath: () => ({
18
- path: path.join(process.cwd(), '.beads', 'workspace.db'),
19
- source: 'nearest',
20
- exists: true
21
- }),
22
- resolveWorkspaceDatabase: () => ({
23
- path: path.join(process.cwd(), '.beads'),
24
- source: 'metadata',
25
- exists: true
26
- })
27
- }));
28
-
29
- // Mock config - mirrors real getConfig() so env var overrides are testable
30
- vi.mock('../config.js', () => ({
31
- getConfig: () => {
32
- const port = Number.parseInt(process.env.PORT || '', 10) || 3000;
33
- const host = process.env.HOST || '127.0.0.1';
34
- return { host, port, url: `http://${host}:${port}` };
35
- }
36
- }));
37
-
38
- afterEach(() => {
39
- delete process.env.PORT;
40
- delete process.env.HOST;
41
- });
42
-
43
- describe('handleStart (unit)', () => {
44
- test('returns 1 when daemon start fails', async () => {
45
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(null);
46
- vi.spyOn(daemon, 'isProcessRunning').mockReturnValue(false);
47
- vi.spyOn(daemon, 'findAvailablePort').mockResolvedValue(3000);
48
- vi.spyOn(daemon, 'startDaemon').mockReturnValue(null);
49
-
50
- const code = await handleStart({ open: false });
51
-
52
- expect(code).toBe(1);
53
- });
54
-
55
- test('returns 0 when already running', async () => {
56
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(12345);
57
- vi.spyOn(daemon, 'isProcessRunning').mockReturnValue(true);
58
- const print_url = vi
59
- .spyOn(daemon, 'printServerUrl')
60
- .mockImplementation(() => {});
61
-
62
- const code = await handleStart({ open: false });
63
-
64
- expect(code).toBe(0);
65
- expect(print_url).not.toHaveBeenCalled();
66
- });
67
-
68
- test('registers workspace from metadata when already running', async () => {
69
- const register_workspace_with_server =
70
- /** @type {import('vitest').Mock} */ (open.registerWorkspaceWithServer);
71
- register_workspace_with_server.mockReset();
72
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(12345);
73
- vi.spyOn(daemon, 'isProcessRunning').mockReturnValue(true);
74
-
75
- const code = await handleStart({ open: false });
76
-
77
- expect(code).toBe(0);
78
- expect(register_workspace_with_server).toHaveBeenCalledTimes(1);
79
- expect(register_workspace_with_server).toHaveBeenCalledWith(
80
- 'http://127.0.0.1:3000',
81
- {
82
- path: process.cwd(),
83
- database: path.join(process.cwd(), '.beads')
84
- }
85
- );
86
- });
87
-
88
- test('registers workspace at custom port when already running', async () => {
89
- const register_workspace_with_server =
90
- /** @type {import('vitest').Mock} */ (open.registerWorkspaceWithServer);
91
- register_workspace_with_server.mockReset();
92
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(12345);
93
- vi.spyOn(daemon, 'isProcessRunning').mockReturnValue(true);
94
-
95
- const code = await handleStart({ open: false, port: 3030 });
96
-
97
- expect(code).toBe(0);
98
- expect(register_workspace_with_server).toHaveBeenCalledTimes(1);
99
- expect(register_workspace_with_server).toHaveBeenCalledWith(
100
- 'http://127.0.0.1:3030',
101
- {
102
- path: process.cwd(),
103
- database: path.join(process.cwd(), '.beads')
104
- }
105
- );
106
- });
107
-
108
- test('registers workspace with existing server when spawned daemon exits early', async () => {
109
- const register_workspace_with_server =
110
- /** @type {import('vitest').Mock} */ (open.registerWorkspaceWithServer);
111
- register_workspace_with_server.mockReset();
112
-
113
- const remove_pid = vi
114
- .spyOn(daemon, 'removePidFile')
115
- .mockImplementation(() => {});
116
-
117
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(null);
118
- vi.spyOn(daemon, 'findAvailablePort').mockResolvedValue(3000);
119
- vi.spyOn(daemon, 'startDaemon').mockReturnValue({ pid: 7777 });
120
- vi.spyOn(daemon, 'isProcessRunning').mockImplementation((pid) => pid === 1);
121
-
122
- const code = await handleStart({ open: false });
123
-
124
- expect(code).toBe(0);
125
- expect(remove_pid).toHaveBeenCalledTimes(1);
126
- expect(register_workspace_with_server).toHaveBeenCalledTimes(1);
127
- expect(register_workspace_with_server).toHaveBeenCalledWith(
128
- 'http://127.0.0.1:3000',
129
- {
130
- path: process.cwd(),
131
- database: path.join(process.cwd(), '.beads')
132
- }
133
- );
134
- });
135
-
136
- test('attempts workspace registration after successful daemon start', async () => {
137
- const register_workspace_with_server =
138
- /** @type {import('vitest').Mock} */ (open.registerWorkspaceWithServer);
139
- register_workspace_with_server.mockReset();
140
-
141
- const print_url = vi
142
- .spyOn(daemon, 'printServerUrl')
143
- .mockImplementation(() => {});
144
-
145
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(null);
146
- vi.spyOn(daemon, 'findAvailablePort').mockResolvedValue(3000);
147
- vi.spyOn(daemon, 'startDaemon').mockReturnValue({ pid: 4321 });
148
- vi.spyOn(daemon, 'isProcessRunning').mockImplementation(
149
- (pid) => pid === 4321
150
- );
151
-
152
- const code = await handleStart({ open: false });
153
-
154
- expect(code).toBe(0);
155
- expect(print_url).toHaveBeenCalledTimes(1);
156
- expect(register_workspace_with_server).toHaveBeenCalledTimes(1);
157
- expect(register_workspace_with_server).toHaveBeenCalledWith(
158
- 'http://127.0.0.1:3000',
159
- {
160
- path: process.cwd(),
161
- database: path.join(process.cwd(), '.beads')
162
- }
163
- );
164
- });
165
- });
166
-
167
- describe('handleStop (unit)', () => {
168
- test('returns 2 when not running and no PID file', async () => {
169
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(null);
170
-
171
- const code = await handleStop();
172
-
173
- expect(code).toBe(2);
174
- });
175
-
176
- test('returns 2 on stale PID and removes file', async () => {
177
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(1111);
178
- vi.spyOn(daemon, 'isProcessRunning').mockReturnValue(false);
179
- const remove_pid = vi
180
- .spyOn(daemon, 'removePidFile')
181
- .mockImplementation(() => {});
182
-
183
- const code = await handleStop();
184
-
185
- expect(code).toBe(2);
186
- expect(remove_pid).toHaveBeenCalledTimes(1);
187
- });
188
-
189
- test('returns 0 when process terminates and removes PID', async () => {
190
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(2222);
191
- vi.spyOn(daemon, 'isProcessRunning').mockReturnValue(true);
192
- vi.spyOn(daemon, 'terminateProcess').mockResolvedValue(true);
193
- const remove_pid = vi
194
- .spyOn(daemon, 'removePidFile')
195
- .mockImplementation(() => {});
196
-
197
- const code = await handleStop();
198
-
199
- expect(code).toBe(0);
200
- expect(remove_pid).toHaveBeenCalledTimes(1);
201
- });
202
- });
203
-
204
- describe('handleRestart (unit)', () => {
205
- test('reuses detected port when no explicit port given', async () => {
206
- // First call: restart reads PID (running daemon)
207
- // Second call: handleStop reads PID (to terminate)
208
- // Third call: handleStart reads PID (no existing daemon after stop)
209
- vi.spyOn(daemon, 'readPidFile')
210
- .mockReturnValueOnce(3333) // restart: detect port
211
- .mockReturnValueOnce(3333) // handleStop: find process
212
- .mockReturnValueOnce(null); // handleStart: no existing
213
- vi.spyOn(daemon, 'detectListeningPort').mockReturnValue(4000);
214
- vi.spyOn(daemon, 'findAvailablePort').mockResolvedValue(4000);
215
- vi.spyOn(daemon, 'isProcessRunning').mockImplementation(
216
- (pid) => pid === 3333 || pid === 5555
217
- );
218
- vi.spyOn(daemon, 'terminateProcess').mockResolvedValue(true);
219
- vi.spyOn(daemon, 'removePidFile').mockImplementation(() => {});
220
- vi.spyOn(daemon, 'printServerUrl').mockImplementation(() => {});
221
-
222
- const start_daemon = vi
223
- .spyOn(daemon, 'startDaemon')
224
- .mockReturnValue({ pid: 5555 });
225
-
226
- const code = await handleRestart();
227
-
228
- expect(code).toBe(0);
229
- expect(start_daemon).toHaveBeenCalledWith(
230
- expect.objectContaining({ port: 4000 })
231
- );
232
- });
233
-
234
- test('explicit port overrides detected port', async () => {
235
- vi.spyOn(daemon, 'readPidFile')
236
- .mockReturnValueOnce(3333)
237
- .mockReturnValueOnce(3333)
238
- .mockReturnValueOnce(null);
239
- vi.spyOn(daemon, 'detectListeningPort').mockReturnValue(4000);
240
- vi.spyOn(daemon, 'isProcessRunning').mockImplementation(
241
- (pid) => pid === 3333 || pid === 6666
242
- );
243
- vi.spyOn(daemon, 'terminateProcess').mockResolvedValue(true);
244
- vi.spyOn(daemon, 'removePidFile').mockImplementation(() => {});
245
-
246
- const start_daemon = vi
247
- .spyOn(daemon, 'startDaemon')
248
- .mockReturnValue({ pid: 6666 });
249
-
250
- const code = await handleRestart({ port: 9999 });
251
-
252
- expect(code).toBe(0);
253
- expect(start_daemon).toHaveBeenCalledWith(
254
- expect.objectContaining({ port: 9999 })
255
- );
256
- });
257
-
258
- test('falls back to default when port detection fails', async () => {
259
- vi.spyOn(daemon, 'readPidFile')
260
- .mockReturnValueOnce(3333)
261
- .mockReturnValueOnce(3333)
262
- .mockReturnValueOnce(null);
263
- vi.spyOn(daemon, 'detectListeningPort').mockReturnValue(null);
264
- vi.spyOn(daemon, 'findAvailablePort').mockResolvedValue(3000);
265
- vi.spyOn(daemon, 'isProcessRunning').mockImplementation(
266
- (pid) => pid === 3333 || pid === 7777
267
- );
268
- vi.spyOn(daemon, 'terminateProcess').mockResolvedValue(true);
269
- vi.spyOn(daemon, 'removePidFile').mockImplementation(() => {});
270
- vi.spyOn(daemon, 'printServerUrl').mockImplementation(() => {});
271
-
272
- const start_daemon = vi
273
- .spyOn(daemon, 'startDaemon')
274
- .mockReturnValue({ pid: 7777 });
275
-
276
- const code = await handleRestart();
277
-
278
- expect(code).toBe(0);
279
- // port should not be set — falls through to default
280
- expect(start_daemon.mock.calls[0]?.[0]).toEqual(
281
- expect.not.objectContaining({ port: expect.any(Number) })
282
- );
283
- });
284
-
285
- test('re-registers workspaces from previous server after restart', async () => {
286
- const fetch_workspaces = /** @type {import('vitest').Mock} */ (
287
- open.fetchWorkspacesFromServer
288
- );
289
- fetch_workspaces.mockResolvedValueOnce([
290
- { path: '/project/a', database: '/project/a/.beads' },
291
- { path: '/project/b', database: '/project/b/.beads' }
292
- ]);
293
-
294
- const register_workspace = /** @type {import('vitest').Mock} */ (
295
- open.registerWorkspaceWithServer
296
- );
297
- register_workspace.mockReset();
298
-
299
- vi.spyOn(daemon, 'readPidFile')
300
- .mockReturnValueOnce(3333) // restart: detect port
301
- .mockReturnValueOnce(3333) // handleStop: find process
302
- .mockReturnValueOnce(null); // handleStart: no existing
303
- vi.spyOn(daemon, 'detectListeningPort').mockReturnValue(null);
304
- vi.spyOn(daemon, 'isProcessRunning').mockImplementation(
305
- (pid) => pid === 3333 || pid === 9999
306
- );
307
- vi.spyOn(daemon, 'terminateProcess').mockResolvedValue(true);
308
- vi.spyOn(daemon, 'removePidFile').mockImplementation(() => {});
309
- vi.spyOn(daemon, 'printServerUrl').mockImplementation(() => {});
310
- vi.spyOn(daemon, 'startDaemon').mockReturnValue({ pid: 9999 });
311
-
312
- const code = await handleRestart();
313
-
314
- expect(code).toBe(0);
315
- // The cwd workspace is registered by handleStart, plus the two saved ones
316
- expect(register_workspace).toHaveBeenCalledWith('http://127.0.0.1:3000', {
317
- path: '/project/a',
318
- database: '/project/a/.beads'
319
- });
320
- expect(register_workspace).toHaveBeenCalledWith('http://127.0.0.1:3000', {
321
- path: '/project/b',
322
- database: '/project/b/.beads'
323
- });
324
- });
325
- });
326
-
327
- describe('port auto-increment (unit)', () => {
328
- test('auto-increments port when default is in use by non-bdui', async () => {
329
- const register_workspace = /** @type {import('vitest').Mock} */ (
330
- open.registerWorkspaceWithServer
331
- );
332
- // Registration fails — not a bdui instance on that port
333
- register_workspace.mockResolvedValueOnce(false);
334
-
335
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(null);
336
- vi.spyOn(daemon, 'findAvailablePort').mockResolvedValue(3001);
337
- vi.spyOn(daemon, 'isProcessRunning').mockImplementation(
338
- (pid) => pid === 8888
339
- );
340
- vi.spyOn(daemon, 'printServerUrl').mockImplementation(() => {});
341
-
342
- const start_daemon = vi
343
- .spyOn(daemon, 'startDaemon')
344
- .mockReturnValue({ pid: 8888 });
345
-
346
- const code = await handleStart({ open: false });
347
-
348
- expect(code).toBe(0);
349
- expect(start_daemon).toHaveBeenCalledWith(
350
- expect.objectContaining({ port: 3001 })
351
- );
352
- });
353
-
354
- test('reuses existing bdui when default port is occupied', async () => {
355
- const register_workspace = /** @type {import('vitest').Mock} */ (
356
- open.registerWorkspaceWithServer
357
- );
358
- // Registration succeeds — existing bdui on that port
359
- register_workspace.mockResolvedValueOnce(true);
360
-
361
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(null);
362
- vi.spyOn(daemon, 'findAvailablePort').mockResolvedValue(3001);
363
-
364
- const start_daemon = vi
365
- .spyOn(daemon, 'startDaemon')
366
- .mockReturnValue({ pid: 8888 });
367
-
368
- const code = await handleStart({ open: false });
369
-
370
- expect(code).toBe(0);
371
- // Should NOT have started a new daemon — reused existing
372
- expect(start_daemon).not.toHaveBeenCalled();
373
- });
374
-
375
- test('does not auto-increment when explicit port is given', async () => {
376
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(null);
377
- vi.spyOn(daemon, 'isProcessRunning').mockImplementation(
378
- (pid) => pid === 8888
379
- );
380
- vi.spyOn(daemon, 'printServerUrl').mockImplementation(() => {});
381
-
382
- const find_port = vi
383
- .spyOn(daemon, 'findAvailablePort')
384
- .mockResolvedValue(5000);
385
-
386
- const start_daemon = vi
387
- .spyOn(daemon, 'startDaemon')
388
- .mockReturnValue({ pid: 8888 });
389
-
390
- const code = await handleStart({ open: false, port: 5000 });
391
-
392
- expect(code).toBe(0);
393
- // findAvailablePort should not be called when port is explicit
394
- expect(find_port).not.toHaveBeenCalled();
395
- expect(start_daemon).toHaveBeenCalledWith(
396
- expect.objectContaining({ port: 5000 })
397
- );
398
- });
399
-
400
- test('returns 1 when no port is available', async () => {
401
- vi.spyOn(daemon, 'readPidFile').mockReturnValue(null);
402
- vi.spyOn(daemon, 'findAvailablePort').mockResolvedValue(null);
403
-
404
- const code = await handleStart({ open: false });
405
-
406
- expect(code).toBe(1);
407
- });
408
- });