@rsktash/beads-ui 0.1.51 → 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.
- package/README.md +85 -144
- package/bin/bd-web +94 -0
- package/dist/assets/index-BBVIMXaM.js +73 -0
- package/dist/assets/index-SUXI6Mzt.css +1 -0
- package/dist/bd-favicon-16.svg +6 -0
- package/dist/bd-favicon-32.svg +6 -0
- package/dist/bd-logo-512.svg +6 -0
- package/dist/favicon.ico +0 -0
- package/dist/icon-180.png +0 -0
- package/dist/icon-192.png +0 -0
- package/dist/icon-512.png +0 -0
- package/dist/index.html +8 -5
- package/package.json +39 -29
- package/server/auth.js +68 -87
- package/server/db.js +91 -134
- package/server/dsn.js +130 -0
- package/server/index.js +151 -87
- package/server/queries.js +247 -0
- package/server/routes/auth.js +27 -0
- package/server/routes/issues.js +120 -0
- package/server/routes/stream.js +111 -0
- package/server/types.js +83 -0
- package/app/protocol.js +0 -216
- package/bin/bd-grep +0 -121
- package/bin/bd-ui +0 -19
- package/dist/assets/abap-DsBKuouk.js +0 -1
- package/dist/assets/actionscript-3-D_z4Izcz.js +0 -1
- package/dist/assets/ada-727ZlQH0.js +0 -1
- package/dist/assets/andromeeda-C3khCPGq.js +0 -1
- package/dist/assets/angular-html-LfdN0zeE.js +0 -1
- package/dist/assets/angular-ts-CKsD7JZE.js +0 -1
- package/dist/assets/apache-Dn00JSTd.js +0 -1
- package/dist/assets/apex-COJ4H7py.js +0 -1
- package/dist/assets/apl-BBq3IX1j.js +0 -1
- package/dist/assets/applescript-Bu5BbsvL.js +0 -1
- package/dist/assets/ara-7O62HKoU.js +0 -1
- package/dist/assets/asciidoc-BPT9niGB.js +0 -1
- package/dist/assets/asm-Dhn9LcZ4.js +0 -1
- package/dist/assets/astro-CqkE3fuf.js +0 -1
- package/dist/assets/aurora-x-D-2ljcwZ.js +0 -1
- package/dist/assets/awk-eg146-Ew.js +0 -1
- package/dist/assets/ayu-dark-Cv9koXgw.js +0 -1
- package/dist/assets/ballerina-Du268qiB.js +0 -1
- package/dist/assets/bat-fje9CFhw.js +0 -1
- package/dist/assets/beancount-BwXTMy5W.js +0 -1
- package/dist/assets/berry-3xVqZejG.js +0 -1
- package/dist/assets/bibtex-xW4inM5L.js +0 -1
- package/dist/assets/bicep-DHo0CJ0O.js +0 -1
- package/dist/assets/blade-a8OxSdnT.js +0 -1
- package/dist/assets/bsl-Dgyn0ogV.js +0 -1
- package/dist/assets/c-C3t2pwGQ.js +0 -1
- package/dist/assets/cadence-DNquZEk8.js +0 -1
- package/dist/assets/cairo--RitsXJZ.js +0 -1
- package/dist/assets/catppuccin-frappe-CD_QflpE.js +0 -1
- package/dist/assets/catppuccin-latte-DRW-0cLl.js +0 -1
- package/dist/assets/catppuccin-macchiato-C-_shW-Y.js +0 -1
- package/dist/assets/catppuccin-mocha-LGGdnPYs.js +0 -1
- package/dist/assets/clarity-BHOwM8T6.js +0 -1
- package/dist/assets/clojure-DxSadP1t.js +0 -1
- package/dist/assets/cmake-DbXoA79R.js +0 -1
- package/dist/assets/cobol-PTqiYgYu.js +0 -1
- package/dist/assets/codeowners-Bp6g37R7.js +0 -1
- package/dist/assets/codeql-sacFqUAJ.js +0 -1
- package/dist/assets/coffee-dyiR41kL.js +0 -1
- package/dist/assets/common-lisp-C7gG9l05.js +0 -1
- package/dist/assets/coq-Dsg_Bt_b.js +0 -1
- package/dist/assets/cpp-BksuvNSY.js +0 -1
- package/dist/assets/crystal-DtDmRg-F.js +0 -1
- package/dist/assets/csharp-D9R-vmeu.js +0 -1
- package/dist/assets/css-BPhBrDlE.js +0 -1
- package/dist/assets/csv-B0qRVHPH.js +0 -1
- package/dist/assets/cue-DtFQj3wx.js +0 -1
- package/dist/assets/cypher-m2LEI-9-.js +0 -1
- package/dist/assets/d-BoXegm-a.js +0 -1
- package/dist/assets/dark-plus-C3mMm8J8.js +0 -1
- package/dist/assets/dart-B9wLZaAG.js +0 -1
- package/dist/assets/dax-ClGRhx96.js +0 -1
- package/dist/assets/desktop-DEIpsLCJ.js +0 -1
- package/dist/assets/diff-BgYniUM_.js +0 -1
- package/dist/assets/docker-COcR7UxN.js +0 -1
- package/dist/assets/dotenv-BjQB5zDj.js +0 -1
- package/dist/assets/dracula-BzJJZx-M.js +0 -1
- package/dist/assets/dracula-soft-BXkSAIEj.js +0 -1
- package/dist/assets/dream-maker-C-nORZOA.js +0 -1
- package/dist/assets/edge-D5gP-w-T.js +0 -1
- package/dist/assets/elixir-CLiX3zqd.js +0 -1
- package/dist/assets/elm-CmHSxxaM.js +0 -1
- package/dist/assets/emacs-lisp-BX77sIaO.js +0 -1
- package/dist/assets/erb-BYTLMnw6.js +0 -1
- package/dist/assets/erlang-B-DoSBHF.js +0 -1
- package/dist/assets/everforest-dark-BgDCqdQA.js +0 -1
- package/dist/assets/everforest-light-C8M2exoo.js +0 -1
- package/dist/assets/fennel-bCA53EVm.js +0 -1
- package/dist/assets/fish-w-ucz2PV.js +0 -1
- package/dist/assets/fluent-Dayu4EKP.js +0 -1
- package/dist/assets/fortran-fixed-form-TqA4NnZg.js +0 -1
- package/dist/assets/fortran-free-form-DKXYxT9g.js +0 -1
- package/dist/assets/fsharp-XplgxFYe.js +0 -1
- package/dist/assets/gdresource-BHYsBjWJ.js +0 -1
- package/dist/assets/gdscript-DfxzS6Rs.js +0 -1
- package/dist/assets/gdshader-SKMF96pI.js +0 -1
- package/dist/assets/genie-ajMbGru0.js +0 -1
- package/dist/assets/gherkin--30QC5Em.js +0 -1
- package/dist/assets/git-commit-i4q6IMui.js +0 -1
- package/dist/assets/git-rebase-B-v9cOL2.js +0 -1
- package/dist/assets/github-dark-DHJKELXO.js +0 -1
- package/dist/assets/github-dark-default-Cuk6v7N8.js +0 -1
- package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +0 -1
- package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -1
- package/dist/assets/github-light-DAi9KRSo.js +0 -1
- package/dist/assets/github-light-default-D7oLnXFd.js +0 -1
- package/dist/assets/github-light-high-contrast-BfjtVDDH.js +0 -1
- package/dist/assets/gleam-B430Bg39.js +0 -1
- package/dist/assets/glimmer-js-D-cwc0-E.js +0 -1
- package/dist/assets/glimmer-ts-pgjy16dm.js +0 -1
- package/dist/assets/glsl-DBO2IWDn.js +0 -1
- package/dist/assets/gnuplot-CM8KxXT1.js +0 -1
- package/dist/assets/go-B1SYOhNW.js +0 -1
- package/dist/assets/graphql-cDcHW_If.js +0 -1
- package/dist/assets/groovy-DkBy-JyN.js +0 -1
- package/dist/assets/hack-D1yCygmZ.js +0 -1
- package/dist/assets/haml-B2EZWmdv.js +0 -1
- package/dist/assets/handlebars-BQGss363.js +0 -1
- package/dist/assets/haskell-BILxekzW.js +0 -1
- package/dist/assets/haxe-C5wWYbrZ.js +0 -1
- package/dist/assets/hcl-HzYwdGDm.js +0 -1
- package/dist/assets/hjson-T-Tgc4AT.js +0 -1
- package/dist/assets/hlsl-ifBTmRxC.js +0 -1
- package/dist/assets/houston-DnULxvSX.js +0 -1
- package/dist/assets/html-C2L_23MC.js +0 -1
- package/dist/assets/html-derivative-CSfWNPLT.js +0 -1
- package/dist/assets/http-FRrOvY1W.js +0 -1
- package/dist/assets/hxml-TIA70rKU.js +0 -1
- package/dist/assets/hy-BMj5Y0dO.js +0 -1
- package/dist/assets/imba-bv_oIlVt.js +0 -1
- package/dist/assets/index-CNDeKQGk.js +0 -125
- package/dist/assets/index-oO5WB2l2.css +0 -1
- package/dist/assets/ini-BjABl1g7.js +0 -1
- package/dist/assets/java-xI-RfyKK.js +0 -1
- package/dist/assets/javascript-ySlJ1b_l.js +0 -1
- package/dist/assets/jinja-DGy0s7-h.js +0 -1
- package/dist/assets/jison-BqZprYcd.js +0 -1
- package/dist/assets/json-BQoSv7ci.js +0 -1
- package/dist/assets/json5-w8dY5SsB.js +0 -1
- package/dist/assets/jsonc-TU54ms6u.js +0 -1
- package/dist/assets/jsonl-DREVFZK8.js +0 -1
- package/dist/assets/jsonnet-BfivnA6A.js +0 -1
- package/dist/assets/jssm-P4WzXJd0.js +0 -1
- package/dist/assets/jsx-BAng5TT0.js +0 -1
- package/dist/assets/julia-BBuGR-5E.js +0 -1
- package/dist/assets/kanagawa-dragon-CkXjmgJE.js +0 -1
- package/dist/assets/kanagawa-lotus-CfQXZHmo.js +0 -1
- package/dist/assets/kanagawa-wave-DWedfzmr.js +0 -1
- package/dist/assets/kotlin-B5lbUyaz.js +0 -1
- package/dist/assets/kusto-mebxcVVE.js +0 -1
- package/dist/assets/laserwave-DUszq2jm.js +0 -1
- package/dist/assets/latex-C-cWTeAZ.js +0 -1
- package/dist/assets/lean-XBlWyCtg.js +0 -1
- package/dist/assets/less-BfCpw3nA.js +0 -1
- package/dist/assets/light-plus-B7mTdjB0.js +0 -1
- package/dist/assets/liquid-D3W5UaiH.js +0 -1
- package/dist/assets/log-Cc5clBb7.js +0 -1
- package/dist/assets/logo-IuBKFhSY.js +0 -1
- package/dist/assets/lua-CvWAzNxB.js +0 -1
- package/dist/assets/luau-Du5NY7AG.js +0 -1
- package/dist/assets/make-Bvotw-X0.js +0 -1
- package/dist/assets/markdown-UIAJJxZW.js +0 -1
- package/dist/assets/marko-z0MBrx5-.js +0 -1
- package/dist/assets/material-theme-D5KoaKCx.js +0 -1
- package/dist/assets/material-theme-darker-BfHTSMKl.js +0 -1
- package/dist/assets/material-theme-lighter-B0m2ddpp.js +0 -1
- package/dist/assets/material-theme-ocean-CyktbL80.js +0 -1
- package/dist/assets/material-theme-palenight-Csfq5Kiy.js +0 -1
- package/dist/assets/matlab-D9-PGadD.js +0 -1
- package/dist/assets/mdc-DB_EDNY_.js +0 -1
- package/dist/assets/mdx-sdHcTMYB.js +0 -1
- package/dist/assets/mermaid-Ci6OQyBP.js +0 -1
- package/dist/assets/min-dark-CafNBF8u.js +0 -1
- package/dist/assets/min-light-CTRr51gU.js +0 -1
- package/dist/assets/mipsasm-BC5c_5Pe.js +0 -1
- package/dist/assets/mojo-Tz6hzZYG.js +0 -1
- package/dist/assets/monokai-D4h5O-jR.js +0 -1
- package/dist/assets/move-DB_GagMm.js +0 -1
- package/dist/assets/narrat-DLbgOhZU.js +0 -1
- package/dist/assets/nextflow-B0XVJmRM.js +0 -1
- package/dist/assets/nginx-D_VnBJ67.js +0 -1
- package/dist/assets/night-owl-C39BiMTA.js +0 -1
- package/dist/assets/nim-ZlGxZxc3.js +0 -1
- package/dist/assets/nix-shcSOmrb.js +0 -1
- package/dist/assets/nord-Ddv68eIx.js +0 -1
- package/dist/assets/nushell-D4Tzg5kh.js +0 -1
- package/dist/assets/objective-c-Deuh7S70.js +0 -1
- package/dist/assets/objective-cpp-BUEGK8hf.js +0 -1
- package/dist/assets/ocaml-BNioltXt.js +0 -1
- package/dist/assets/one-dark-pro-GBQ2dnAY.js +0 -1
- package/dist/assets/one-light-PoHY5YXO.js +0 -1
- package/dist/assets/pascal-JqZropPD.js +0 -1
- package/dist/assets/perl-CHQXSrWU.js +0 -1
- package/dist/assets/php-B5ebYQev.js +0 -1
- package/dist/assets/plastic-3e1v2bzS.js +0 -1
- package/dist/assets/plsql-LKU2TuZ1.js +0 -1
- package/dist/assets/po-BFLt1xDp.js +0 -1
- package/dist/assets/poimandres-CS3Unz2-.js +0 -1
- package/dist/assets/polar-DKykz6zU.js +0 -1
- package/dist/assets/postcss-B3ZDOciz.js +0 -1
- package/dist/assets/powerquery-CSHBycmS.js +0 -1
- package/dist/assets/powershell-BIEUsx6d.js +0 -1
- package/dist/assets/prisma-B48N-Iqd.js +0 -1
- package/dist/assets/prolog-BY-TUvya.js +0 -1
- package/dist/assets/proto-zocC4JxJ.js +0 -1
- package/dist/assets/pug-CM9l7STV.js +0 -1
- package/dist/assets/puppet-Cza_XSSt.js +0 -1
- package/dist/assets/purescript-Bg-kzb6g.js +0 -1
- package/dist/assets/python-DhUJRlN_.js +0 -1
- package/dist/assets/qml-D8XfuvdV.js +0 -1
- package/dist/assets/qmldir-C8lEn-DE.js +0 -1
- package/dist/assets/qss-DhMKtDLN.js +0 -1
- package/dist/assets/r-CwjWoCRV.js +0 -1
- package/dist/assets/racket-CzouJOBO.js +0 -1
- package/dist/assets/raku-B1bQXN8T.js +0 -1
- package/dist/assets/razor-CNLDkMZG.js +0 -1
- package/dist/assets/red-bN70gL4F.js +0 -1
- package/dist/assets/reg-5LuOXUq_.js +0 -1
- package/dist/assets/regexp-DWJ3fJO_.js +0 -1
- package/dist/assets/rel-DJlmqQ1C.js +0 -1
- package/dist/assets/riscv-QhoSD0DR.js +0 -1
- package/dist/assets/rose-pine-CmCqftbK.js +0 -1
- package/dist/assets/rose-pine-dawn-Ds-gbosJ.js +0 -1
- package/dist/assets/rose-pine-moon-CjDtw9vr.js +0 -1
- package/dist/assets/rst-4NLicBqY.js +0 -1
- package/dist/assets/ruby-DeZ3UC14.js +0 -1
- package/dist/assets/rust-Be6lgOlo.js +0 -1
- package/dist/assets/sas-BmTFh92c.js +0 -1
- package/dist/assets/sass-BJ4Li9vH.js +0 -1
- package/dist/assets/scala-DQVVAn-B.js +0 -1
- package/dist/assets/scheme-BJGe-b2p.js +0 -1
- package/dist/assets/scss-C31hgJw-.js +0 -1
- package/dist/assets/sdbl-BLhTXw86.js +0 -1
- package/dist/assets/shaderlab-B7qAK45m.js +0 -1
- package/dist/assets/shellscript-atvbtKCR.js +0 -1
- package/dist/assets/shellsession-C_rIy8kc.js +0 -1
- package/dist/assets/slack-dark-BthQWCQV.js +0 -1
- package/dist/assets/slack-ochin-DqwNpetd.js +0 -1
- package/dist/assets/smalltalk-DkLiglaE.js +0 -1
- package/dist/assets/snazzy-light-Bw305WKR.js +0 -1
- package/dist/assets/solarized-dark-DXbdFlpD.js +0 -1
- package/dist/assets/solarized-light-L9t79GZl.js +0 -1
- package/dist/assets/solidity-C1w2a3ep.js +0 -1
- package/dist/assets/soy-C-lX7w71.js +0 -1
- package/dist/assets/sparql-bYkjHRlG.js +0 -1
- package/dist/assets/splunk-Cf8iN4DR.js +0 -1
- package/dist/assets/sql-COK4E0Yg.js +0 -1
- package/dist/assets/ssh-config-BknIz3MU.js +0 -1
- package/dist/assets/stata-DorPZHa4.js +0 -1
- package/dist/assets/stylus-BeQkCIfX.js +0 -1
- package/dist/assets/svelte-MSaWC3Je.js +0 -1
- package/dist/assets/swift-BSxZ-RaX.js +0 -1
- package/dist/assets/synthwave-84-CbfX1IO0.js +0 -1
- package/dist/assets/system-verilog-C7L56vO4.js +0 -1
- package/dist/assets/systemd-CUnW07Te.js +0 -1
- package/dist/assets/talonscript-C1XDQQGZ.js +0 -1
- package/dist/assets/tasl-CQjiPCtT.js +0 -1
- package/dist/assets/tcl-DQ1-QYvQ.js +0 -1
- package/dist/assets/templ-dwX3ZSMB.js +0 -1
- package/dist/assets/terraform-BbSNqyBO.js +0 -1
- package/dist/assets/tex-rYs2v40G.js +0 -1
- package/dist/assets/tokyo-night-DBQeEorK.js +0 -1
- package/dist/assets/toml-CB2ApiWb.js +0 -1
- package/dist/assets/ts-tags-CipyTH0X.js +0 -1
- package/dist/assets/tsv-B_m7g4N7.js +0 -1
- package/dist/assets/tsx-B6W0miNI.js +0 -1
- package/dist/assets/turtle-BMR_PYu6.js +0 -1
- package/dist/assets/twig-NC5TFiHP.js +0 -1
- package/dist/assets/typescript-Dj6nwHGl.js +0 -1
- package/dist/assets/typespec-BpWG_bgh.js +0 -1
- package/dist/assets/typst-BVUVsWT6.js +0 -1
- package/dist/assets/v-CAQ2eGtk.js +0 -1
- package/dist/assets/vala-BFOHcciG.js +0 -1
- package/dist/assets/vb-CdO5JTpU.js +0 -1
- package/dist/assets/verilog-CJaU5se_.js +0 -1
- package/dist/assets/vesper-BEBZ7ncR.js +0 -1
- package/dist/assets/vhdl-DYoNaHQp.js +0 -1
- package/dist/assets/viml-m4uW47V2.js +0 -1
- package/dist/assets/vitesse-black-Bkuqu6BP.js +0 -1
- package/dist/assets/vitesse-dark-D0r3Knsf.js +0 -1
- package/dist/assets/vitesse-light-CVO1_9PV.js +0 -1
- package/dist/assets/vue-BuYVFjOK.js +0 -1
- package/dist/assets/vue-html-xdeiXROB.js +0 -1
- package/dist/assets/vyper-nyqBNV6O.js +0 -1
- package/dist/assets/wasm-C6j12Q_x.js +0 -1
- package/dist/assets/wasm-CG6Dc4jp.js +0 -1
- package/dist/assets/wenyan-7A4Fjokl.js +0 -1
- package/dist/assets/wgsl-CB0Krxn9.js +0 -1
- package/dist/assets/wikitext-DCE3LsBG.js +0 -1
- package/dist/assets/wolfram-C3FkfJm5.js +0 -1
- package/dist/assets/xml-e3z08dGr.js +0 -1
- package/dist/assets/xsl-Dd0NUgwM.js +0 -1
- package/dist/assets/yaml-CVw76BM1.js +0 -1
- package/dist/assets/zenscript-HnGAYVZD.js +0 -1
- package/dist/assets/zig-BVz_zdnA.js +0 -1
- package/server/app.js +0 -165
- package/server/app.test.js +0 -30
- package/server/bd.js +0 -227
- package/server/bd.test.js +0 -194
- package/server/cli/cli.test.js +0 -207
- package/server/cli/commands.integration.test.js +0 -148
- package/server/cli/commands.js +0 -285
- package/server/cli/commands.unit.test.js +0 -408
- package/server/cli/daemon.js +0 -340
- package/server/cli/daemon.test.js +0 -31
- package/server/cli/index.js +0 -135
- package/server/cli/open.js +0 -178
- package/server/cli/open.test.js +0 -26
- package/server/cli/usage.js +0 -49
- package/server/config.js +0 -36
- package/server/db.test.js +0 -169
- package/server/dolt-pool.js +0 -313
- package/server/dolt-queries.js +0 -781
- package/server/list-adapters.js +0 -421
- package/server/list-adapters.test.js +0 -208
- package/server/logging.js +0 -23
- package/server/registry-watcher.js +0 -200
- package/server/subscriptions.js +0 -299
- package/server/subscriptions.test.js +0 -128
- package/server/validators.js +0 -124
- package/server/watcher.js +0 -139
- package/server/watcher.test.js +0 -120
- package/server/ws.comments.test.js +0 -262
- package/server/ws.delete.test.js +0 -119
- package/server/ws.js +0 -1329
- package/server/ws.labels.test.js +0 -95
- package/server/ws.list-refresh.coalesce.test.js +0 -95
- package/server/ws.list-subscriptions.test.js +0 -403
- package/server/ws.mutation-window.test.js +0 -147
- package/server/ws.mutations.test.js +0 -389
- package/server/ws.test.js +0 -52
package/server/cli/commands.js
DELETED
|
@@ -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
|
-
});
|