mirror of
https://github.com/logseq/logseq.git
synced 2026-02-01 22:47:36 +00:00
impl 005-logseq-cli-output-and-db-worker-node-log.md (1)
This commit is contained in:
148
deps/cli/src/logseq/cli/common/mcp/tools.cljs
vendored
148
deps/cli/src/logseq/cli/common/mcp/tools.cljs
vendored
@@ -16,60 +16,71 @@
|
||||
[malli.core :as m]
|
||||
[malli.error :as me]))
|
||||
|
||||
(defn- ensure-db-graph
|
||||
[db]
|
||||
(when-not (ldb/db-based-graph? db)
|
||||
(throw (ex-info "This tool must be called on a DB graph" {}))))
|
||||
|
||||
(defn- minimal-list-item
|
||||
[e]
|
||||
(cond-> {:db/id (:db/id e)
|
||||
:block/title (:block/title e)
|
||||
:block/created-at (:block/created-at e)
|
||||
:block/updated-at (:block/updated-at e)}
|
||||
(:db/ident e) (assoc :db/ident (:db/ident e))))
|
||||
|
||||
(defn list-properties
|
||||
"Main fn for ListProperties tool"
|
||||
[db {:keys [expand include-built-in] :as options}]
|
||||
(ensure-db-graph db)
|
||||
(let [include-built-in? (if (contains? options :include-built-in) include-built-in true)]
|
||||
(->> (d/datoms db :avet :block/tags :logseq.class/Property)
|
||||
(map #(d/entity db (:e %)))
|
||||
(remove (fn [e]
|
||||
(and (not include-built-in?)
|
||||
(ldb/built-in? e))))
|
||||
#_((fn [x] (prn :prop-keys (distinct (mapcat keys x))) x))
|
||||
(map (fn [e]
|
||||
(if expand
|
||||
(cond-> (into {} e)
|
||||
true
|
||||
(dissoc e :block/tags :block/order :block/refs :block/name :db/index
|
||||
:logseq.property.embedding/hnsw-label-updated-at :logseq.property/default-value)
|
||||
true
|
||||
(update :block/uuid str)
|
||||
(:logseq.property/classes e)
|
||||
(update :logseq.property/classes #(mapv :db/ident %))
|
||||
(:logseq.property/description e)
|
||||
(update :logseq.property/description db-property/property-value-content))
|
||||
{:block/title (:block/title e)
|
||||
:block/uuid (str (:block/uuid e))}))))))
|
||||
(->> (d/datoms db :avet :block/tags :logseq.class/Property)
|
||||
(map #(d/entity db (:e %)))
|
||||
(remove (fn [e]
|
||||
(and (not include-built-in?)
|
||||
(ldb/built-in? e))))
|
||||
#_((fn [x] (prn :prop-keys (distinct (mapcat keys x))) x))
|
||||
(map (fn [e]
|
||||
(if expand
|
||||
(cond-> (into {} e)
|
||||
true
|
||||
(dissoc e :block/tags :block/order :block/refs :block/name :db/index
|
||||
:logseq.property.embedding/hnsw-label-updated-at :logseq.property/default-value)
|
||||
true
|
||||
(update :block/uuid str)
|
||||
(:logseq.property/classes e)
|
||||
(update :logseq.property/classes #(mapv :db/ident %))
|
||||
(:logseq.property/description e)
|
||||
(update :logseq.property/description db-property/property-value-content))
|
||||
(minimal-list-item e)))))))
|
||||
|
||||
(defn list-tags
|
||||
"Main fn for ListTags tool"
|
||||
[db {:keys [expand include-built-in] :as options}]
|
||||
(ensure-db-graph db)
|
||||
(let [include-built-in? (if (contains? options :include-built-in) include-built-in true)]
|
||||
(->> (d/datoms db :avet :block/tags :logseq.class/Tag)
|
||||
(map #(d/entity db (:e %)))
|
||||
(remove (fn [e]
|
||||
(and (not include-built-in?)
|
||||
(ldb/built-in? e))))
|
||||
(map (fn [e]
|
||||
(if expand
|
||||
(cond-> (into {} e)
|
||||
true
|
||||
(dissoc e :block/tags :block/order :block/refs :block/name
|
||||
:logseq.property.embedding/hnsw-label-updated-at)
|
||||
true
|
||||
(update :block/uuid str)
|
||||
(:logseq.property.class/extends e)
|
||||
(update :logseq.property.class/extends #(mapv :db/ident %))
|
||||
(:logseq.property.class/properties e)
|
||||
(update :logseq.property.class/properties #(mapv :db/ident %))
|
||||
(:logseq.property.view/type e)
|
||||
(assoc :logseq.property.view/type (:db/ident (:logseq.property.view/type e)))
|
||||
(:logseq.property/description e)
|
||||
(update :logseq.property/description db-property/property-value-content))
|
||||
{:block/title (:block/title e)
|
||||
:block/uuid (str (:block/uuid e))}))))))
|
||||
(->> (d/datoms db :avet :block/tags :logseq.class/Tag)
|
||||
(map #(d/entity db (:e %)))
|
||||
(remove (fn [e]
|
||||
(and (not include-built-in?)
|
||||
(ldb/built-in? e))))
|
||||
(map (fn [e]
|
||||
(if expand
|
||||
(cond-> (into {} e)
|
||||
true
|
||||
(dissoc e :block/tags :block/order :block/refs :block/name
|
||||
:logseq.property.embedding/hnsw-label-updated-at)
|
||||
true
|
||||
(update :block/uuid str)
|
||||
(:logseq.property.class/extends e)
|
||||
(update :logseq.property.class/extends #(mapv :db/ident %))
|
||||
(:logseq.property.class/properties e)
|
||||
(update :logseq.property.class/properties #(mapv :db/ident %))
|
||||
(:logseq.property.view/type e)
|
||||
(assoc :logseq.property.view/type (:db/ident (:logseq.property.view/type e)))
|
||||
(:logseq.property/description e)
|
||||
(update :logseq.property/description db-property/property-value-content))
|
||||
(minimal-list-item e)))))))
|
||||
|
||||
(defn- get-page-blocks
|
||||
[db page-id]
|
||||
@@ -118,32 +129,31 @@
|
||||
journal-only? (boolean journal-only)
|
||||
created-after-ms (parse-time created-after)
|
||||
updated-after-ms (parse-time updated-after)]
|
||||
(->> (d/datoms db :avet :block/name)
|
||||
(map #(d/entity db (:e %)))
|
||||
(remove (fn [e]
|
||||
(and (not include-hidden?)
|
||||
(entity-util/hidden? e))))
|
||||
(remove (fn [e]
|
||||
(let [is-journal? (ldb/journal? e)]
|
||||
(cond
|
||||
journal-only? (not is-journal?)
|
||||
(false? include-journal?) is-journal?
|
||||
:else false))))
|
||||
(remove (fn [e]
|
||||
(and created-after-ms
|
||||
(<= (:block/created-at e 0) created-after-ms))))
|
||||
(remove (fn [e]
|
||||
(and updated-after-ms
|
||||
(<= (:block/updated-at e 0) updated-after-ms))))
|
||||
(map (fn [e]
|
||||
(if expand
|
||||
(-> e
|
||||
;; Until there are options to limit pages, return minimal info to avoid
|
||||
;; exceeding max payload size
|
||||
(select-keys [:block/uuid :block/title :block/created-at :block/updated-at])
|
||||
(update :block/uuid str))
|
||||
{:block/title (:block/title e)
|
||||
:block/uuid (str (:block/uuid e))}))))))
|
||||
(->> (d/datoms db :avet :block/name)
|
||||
(map #(d/entity db (:e %)))
|
||||
(remove (fn [e]
|
||||
(and (not include-hidden?)
|
||||
(entity-util/hidden? e))))
|
||||
(remove (fn [e]
|
||||
(let [is-journal? (ldb/journal? e)]
|
||||
(cond
|
||||
journal-only? (not is-journal?)
|
||||
(false? include-journal?) is-journal?
|
||||
:else false))))
|
||||
(remove (fn [e]
|
||||
(and created-after-ms
|
||||
(<= (:block/created-at e 0) created-after-ms))))
|
||||
(remove (fn [e]
|
||||
(and updated-after-ms
|
||||
(<= (:block/updated-at e 0) updated-after-ms))))
|
||||
(map (fn [e]
|
||||
(if expand
|
||||
(-> e
|
||||
;; Until there are options to limit pages, return minimal info to avoid
|
||||
;; exceeding max payload size
|
||||
(select-keys [:db/id :db/ident :block/uuid :block/title :block/created-at :block/updated-at])
|
||||
(update :block/uuid str))
|
||||
(minimal-list-item e)))))))
|
||||
|
||||
;; upsert-nodes tool
|
||||
;; =================
|
||||
|
||||
@@ -9,6 +9,29 @@ Tech Stack: ClojureScript, babashka/cli, lambdaisland.glogi, Node.js fs/path.
|
||||
|
||||
Related: Builds on docs/agent-guide/004-logseq-cli-verb-subcommands.md and docs/agent-guide/003-db-worker-node-cli-orchestration.md.
|
||||
|
||||
## Human Output Specification
|
||||
|
||||
Target: plain text, no ANSI colors. Each command has a stable layout and ordering.
|
||||
|
||||
| Command | OK output (human) | Empty output | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| graph list | Table with header `GRAPH` and rows of graph names, followed by `Count: N` | Header + `Count: 0` | Data from `{:graphs [...]}` |
|
||||
| graph create | `Graph created: <graph>` | n/a | Use graph name from action/options |
|
||||
| graph switch | `Graph switched: <graph>` | n/a | Use graph name from action/options |
|
||||
| graph remove | `Graph removed: <graph>` | n/a | Use graph name from action/options |
|
||||
| graph validate | `Graph validated: <graph>` | n/a | Use graph name from action/options |
|
||||
| graph info | Lines: `Graph: <graph>`, `Created at: <ts>`, `Schema version: <v>` | n/a | Use `:logseq.kv/*` data; show `-` if missing |
|
||||
| server list | Table with header `REPO STATUS HOST PORT PID`, rows for servers, followed by `Count: N` | Header + `Count: 0` | Data from `{:servers [...]}` |
|
||||
| server status/start/stop/restart | `Server <status>: <repo>` + details line `Host: <host> Port: <port>` when available | n/a | Use `:status` keyword where present |
|
||||
| list page/tag/property | Table with header (fields vary by command) and rows, followed by `Count: N` | Header + `Count: 0` | Defaults: page/tag/property `ID TITLE UPDATED-AT CREATED-AT` (ID uses `:db/id`); if `:db/ident` present, include `IDENT` column |
|
||||
| add block | `Added blocks: <count> (repo: <repo>)` | n/a | Count = number of blocks submitted |
|
||||
| add page | `Added page: <page> (repo: <repo>)` | n/a | |
|
||||
| remove block | `Removed block: <block-id> (repo: <repo>)` | n/a | Prefer UUID if available |
|
||||
| remove page | `Removed page: <page> (repo: <repo>)` | n/a | |
|
||||
| search | Table with header `TYPE TITLE/CONTENT UUID UPDATED-AT CREATED-AT`, rows in stable order, followed by `Count: N` | Header + `Count: 0` | For block rows use content snippet; for tag/property rows omit timestamps |
|
||||
| show (text) | Raw tree text (no table), trimmed | n/a | For `--format json|edn`, keep existing structured output |
|
||||
| errors | `Error (<code>): <message>` + optional `Hint: <hint>` line | n/a | Ensure error codes are stable and consistent |
|
||||
|
||||
## Problem statement
|
||||
|
||||
The current logseq-cli human output is mostly raw pr-str output, which is hard to read and inconsistent across commands.
|
||||
|
||||
@@ -83,6 +83,7 @@ Subcommands:
|
||||
|
||||
Output formats:
|
||||
- Global `--output <human|json|edn>` (also accepted per subcommand)
|
||||
- Human output is plain text. List/search commands render tables with a final `Count: N` line. For list subcommands, the ID column uses `:db/id` (not UUID). If `:db/ident` exists, an `IDENT` column is included. Errors include error codes and may include a `Hint:` line. Use `--output json|edn` for structured output.
|
||||
|
||||
Examples:
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
(ns frontend.worker.db-worker-node
|
||||
"Node.js daemon entrypoint for db-worker."
|
||||
(:require ["http" :as http]
|
||||
(:require ["fs" :as fs]
|
||||
["http" :as http]
|
||||
["path" :as node-path]
|
||||
[clojure.string :as string]
|
||||
[frontend.worker.db-core :as db-core]
|
||||
[frontend.worker.db-worker-node-lock :as db-lock]
|
||||
@@ -8,13 +10,13 @@
|
||||
[frontend.worker.state :as worker-state]
|
||||
[goog.object :as gobj]
|
||||
[lambdaisland.glogi :as log]
|
||||
[lambdaisland.glogi.console :as glogi-console]
|
||||
[logseq.db :as ldb]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defonce ^:private *ready? (atom false))
|
||||
(defonce ^:private *sse-clients (atom #{}))
|
||||
(defonce ^:private *lock-info (atom nil))
|
||||
(defonce ^:private *file-handler (atom nil))
|
||||
|
||||
(defn- send-json!
|
||||
[^js res status payload]
|
||||
@@ -222,15 +224,82 @@
|
||||
(println " --repo <name> (required)")
|
||||
(println " --rtc-ws-url <url> (optional)")
|
||||
(println " --log-level <level> (default info)")
|
||||
(println " logs: <data-dir>/<graph-dir>/db-worker-node-YYYYMMDD.log (retains 7)")
|
||||
(println " --auth-token <token> (optional)"))
|
||||
|
||||
(defn- pad2
|
||||
[value]
|
||||
(if (< value 10)
|
||||
(str "0" value)
|
||||
(str value)))
|
||||
|
||||
(defn- yyyymmdd
|
||||
[^js date]
|
||||
(str (.getFullYear date)
|
||||
(pad2 (inc (.getMonth date)))
|
||||
(pad2 (.getDate date))))
|
||||
|
||||
(defn- log-path
|
||||
[data-dir repo]
|
||||
(let [data-dir (db-lock/resolve-data-dir data-dir)
|
||||
repo-dir (db-lock/repo-dir data-dir repo)
|
||||
date-str (yyyymmdd (js/Date.))]
|
||||
(node-path/join repo-dir (str "db-worker-node-" date-str ".log"))))
|
||||
|
||||
(defn- log-files
|
||||
[repo-dir]
|
||||
(->> (when (fs/existsSync repo-dir)
|
||||
(fs/readdirSync repo-dir))
|
||||
(filter (fn [^js name]
|
||||
(re-matches #"db-worker-node-\d{8}\.log" name)))
|
||||
(sort)))
|
||||
|
||||
(defn- enforce-log-retention!
|
||||
[repo-dir]
|
||||
(let [files (log-files repo-dir)
|
||||
excess (max 0 (- (count files) 7))]
|
||||
(doseq [name (take excess files)]
|
||||
(fs/unlinkSync (node-path/join repo-dir name)))))
|
||||
|
||||
(defn- format-log-line
|
||||
[{:keys [time level message logger-name exception]}]
|
||||
(let [ts (.toISOString (js/Date. time))
|
||||
base (str ts
|
||||
" ["
|
||||
(name level)
|
||||
"] ["
|
||||
logger-name
|
||||
"] "
|
||||
(pr-str message))]
|
||||
(str base (when exception (str " " (pr-str exception))) "\n")))
|
||||
|
||||
(defn- install-file-logger!
|
||||
[{:keys [data-dir repo log-level]}]
|
||||
(let [data-dir (db-lock/resolve-data-dir data-dir)
|
||||
repo-dir (db-lock/repo-dir data-dir repo)
|
||||
file-path (log-path data-dir repo)]
|
||||
(fs/mkdirSync repo-dir #js {:recursive true})
|
||||
(fs/writeFileSync file-path "" #js {:flag "a"})
|
||||
(enforce-log-retention! repo-dir)
|
||||
(when-let [handler @*file-handler]
|
||||
(log/remove-handler handler))
|
||||
(let [handler (fn [record]
|
||||
(fs/appendFileSync file-path (format-log-line record)))]
|
||||
(reset! *file-handler handler)
|
||||
(log/add-handler handler))
|
||||
(log/set-levels {:glogi/root log-level})
|
||||
file-path))
|
||||
|
||||
(defn start-daemon!
|
||||
[{:keys [data-dir repo rtc-ws-url auth-token]}]
|
||||
[{:keys [data-dir repo rtc-ws-url auth-token log-level]}]
|
||||
(let [host "127.0.0.1"
|
||||
port 0]
|
||||
(if-not (seq repo)
|
||||
(p/rejected (ex-info "repo is required" {:code :missing-repo}))
|
||||
(do
|
||||
(install-file-logger! {:data-dir data-dir
|
||||
:repo repo
|
||||
:log-level (keyword (or log-level "info"))})
|
||||
(reset! *ready? false)
|
||||
(set-main-thread-stub!)
|
||||
(-> (p/let [platform (platform-node/node-platform {:data-dir data-dir
|
||||
@@ -288,22 +357,20 @@
|
||||
|
||||
(defn main
|
||||
[]
|
||||
(let [{:keys [data-dir repo rtc-ws-url log-level auth-token help?]}
|
||||
(parse-args (.-argv js/process))
|
||||
log-level (keyword (or log-level "info"))]
|
||||
(let [{:keys [data-dir repo rtc-ws-url auth-token help?] :as opts}
|
||||
(parse-args (.-argv js/process))]
|
||||
(when help?
|
||||
(show-help!)
|
||||
(.exit js/process 0))
|
||||
(when-not (seq repo)
|
||||
(show-help!)
|
||||
(.exit js/process 1))
|
||||
(glogi-console/install!)
|
||||
(log/set-levels {:glogi/root log-level})
|
||||
(p/let [{:keys [stop!] :as daemon}
|
||||
(start-daemon! {:data-dir data-dir
|
||||
:repo repo
|
||||
:rtc-ws-url rtc-ws-url
|
||||
:auth-token auth-token})]
|
||||
:auth-token auth-token
|
||||
:log-level (:log-level opts)})]
|
||||
(log/info :db-worker-node-ready {:host (:host daemon) :port (:port daemon)})
|
||||
(let [shutdown (fn []
|
||||
(-> (stop!)
|
||||
|
||||
@@ -778,17 +778,20 @@
|
||||
(case command
|
||||
:graph-list
|
||||
{:ok? true
|
||||
:action {:type :graph-list}}
|
||||
:action {:type :graph-list
|
||||
:command :graph-list}}
|
||||
|
||||
:graph-create
|
||||
(if-not (seq graph)
|
||||
(missing-graph-error)
|
||||
{:ok? true
|
||||
:action {:type :invoke
|
||||
:command :graph-create
|
||||
:method "thread-api/create-or-open-db"
|
||||
:direct-pass? false
|
||||
:args [repo {}]
|
||||
:repo repo
|
||||
:graph (repo->graph repo)
|
||||
:allow-missing-graph true
|
||||
:persist-repo (repo->graph repo)}})
|
||||
|
||||
@@ -797,6 +800,7 @@
|
||||
(missing-graph-error)
|
||||
{:ok? true
|
||||
:action {:type :graph-switch
|
||||
:command :graph-switch
|
||||
:repo repo
|
||||
:graph (repo->graph repo)}})
|
||||
|
||||
@@ -805,26 +809,31 @@
|
||||
(missing-graph-error)
|
||||
{:ok? true
|
||||
:action {:type :invoke
|
||||
:command :graph-remove
|
||||
:method "thread-api/unsafe-unlink-db"
|
||||
:direct-pass? false
|
||||
:args [repo]
|
||||
:repo repo}})
|
||||
:repo repo
|
||||
:graph (repo->graph repo)}})
|
||||
|
||||
:graph-validate
|
||||
(if-not (seq repo)
|
||||
(missing-graph-error)
|
||||
{:ok? true
|
||||
:action {:type :invoke
|
||||
:command :graph-validate
|
||||
:method "thread-api/validate-db"
|
||||
:direct-pass? false
|
||||
:args [repo]
|
||||
:repo repo}})
|
||||
:repo repo
|
||||
:graph (repo->graph repo)}})
|
||||
|
||||
:graph-info
|
||||
(if-not (seq repo)
|
||||
(missing-graph-error)
|
||||
{:ok? true
|
||||
:action {:type :graph-info
|
||||
:command :graph-info
|
||||
:repo repo
|
||||
:graph (repo->graph repo)}})))
|
||||
|
||||
@@ -1369,29 +1378,32 @@
|
||||
|
||||
(defn execute
|
||||
[action config]
|
||||
(-> (p/let [check (ensure-existing-graph action config)]
|
||||
(if-not (:ok? check)
|
||||
{:status :error
|
||||
:error (:error check)}
|
||||
(case (:type action)
|
||||
:graph-list (execute-graph-list action config)
|
||||
:invoke (execute-invoke action config)
|
||||
:graph-switch (execute-graph-switch action config)
|
||||
:graph-info (execute-graph-info action config)
|
||||
:list-page (execute-list-page action config)
|
||||
:list-tag (execute-list-tag action config)
|
||||
:list-property (execute-list-property action config)
|
||||
:add-block (execute-add-block action config)
|
||||
:add-page (execute-add-page action config)
|
||||
:remove-block (execute-remove action config)
|
||||
:remove-page (execute-remove action config)
|
||||
:search (execute-search action config)
|
||||
:show (execute-show action config)
|
||||
:server-list (execute-server-list action config)
|
||||
:server-status (execute-server-status action config)
|
||||
:server-start (execute-server-start action config)
|
||||
:server-stop (execute-server-stop action config)
|
||||
:server-restart (execute-server-restart action config)
|
||||
{:status :error
|
||||
:error {:code :unknown-action
|
||||
:message "unknown action"}})))))
|
||||
(-> (p/let [check (ensure-existing-graph action config)
|
||||
result (if-not (:ok? check)
|
||||
{:status :error
|
||||
:error (:error check)}
|
||||
(case (:type action)
|
||||
:graph-list (execute-graph-list action config)
|
||||
:invoke (execute-invoke action config)
|
||||
:graph-switch (execute-graph-switch action config)
|
||||
:graph-info (execute-graph-info action config)
|
||||
:list-page (execute-list-page action config)
|
||||
:list-tag (execute-list-tag action config)
|
||||
:list-property (execute-list-property action config)
|
||||
:add-block (execute-add-block action config)
|
||||
:add-page (execute-add-page action config)
|
||||
:remove-block (execute-remove action config)
|
||||
:remove-page (execute-remove action config)
|
||||
:search (execute-search action config)
|
||||
:show (execute-show action config)
|
||||
:server-list (execute-server-list action config)
|
||||
:server-status (execute-server-status action config)
|
||||
:server-start (execute-server-start action config)
|
||||
:server-stop (execute-server-stop action config)
|
||||
:server-restart (execute-server-restart action config)
|
||||
{:status :error
|
||||
:error {:code :unknown-action
|
||||
:message "unknown action"}}))]
|
||||
(assoc result
|
||||
:command (or (:command action) (:type action))
|
||||
:context (select-keys action [:repo :graph :page :block :blocks])))))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns logseq.cli.format
|
||||
"Formatting helpers for CLI output."
|
||||
(:require [clojure.walk :as walk]))
|
||||
(:require [clojure.string :as string]
|
||||
[clojure.walk :as walk]))
|
||||
|
||||
(defn- normalize-json
|
||||
[value]
|
||||
@@ -22,18 +23,239 @@
|
||||
(set! (.-error obj) (clj->js (normalize-json (update error :code name)))))
|
||||
(js/JSON.stringify obj)))
|
||||
|
||||
(defn- pad-right
|
||||
[value width]
|
||||
(let [text (str value)
|
||||
missing (- width (count text))]
|
||||
(if (pos? missing)
|
||||
(str text (apply str (repeat missing " ")))
|
||||
text)))
|
||||
|
||||
(defn- normalize-cell
|
||||
[value]
|
||||
(cond
|
||||
(nil? value) "-"
|
||||
(keyword? value) (str value)
|
||||
:else (str value)))
|
||||
|
||||
(defn- render-table
|
||||
[headers rows]
|
||||
(let [normalized-rows (mapv (fn [row]
|
||||
(mapv normalize-cell row))
|
||||
rows)
|
||||
trim-right (fn [value]
|
||||
(string/replace value #"\s+$" ""))
|
||||
widths (mapv (fn [idx header]
|
||||
(apply max (count header)
|
||||
(map #(count (nth % idx)) normalized-rows)))
|
||||
(range (count headers))
|
||||
headers)
|
||||
render-row (fn [row]
|
||||
(->> (map pad-right row widths)
|
||||
(string/join " ")
|
||||
(trim-right)))
|
||||
lines (cons (render-row headers)
|
||||
(map render-row normalized-rows))]
|
||||
(string/join "\n" lines)))
|
||||
|
||||
(defn- format-counted-table
|
||||
[headers rows]
|
||||
(str (render-table headers rows)
|
||||
"\n"
|
||||
"Count: "
|
||||
(count rows)))
|
||||
|
||||
(defn- error-hint
|
||||
[{:keys [code]}]
|
||||
(case code
|
||||
:missing-graph "Use --graph <name>"
|
||||
:missing-repo "Use --repo <name>"
|
||||
:missing-content "Use --content or pass content as args"
|
||||
:missing-search-text "Provide search text or --text"
|
||||
nil))
|
||||
|
||||
(defn- format-error
|
||||
[error]
|
||||
(let [{:keys [code message]} error
|
||||
hint (error-hint error)]
|
||||
(cond-> (str "Error (" (name (or code :error)) "): " message)
|
||||
hint (str "\nHint: " hint))))
|
||||
|
||||
(defn- maybe-ident-header
|
||||
[items]
|
||||
(when (some :db/ident items)
|
||||
["IDENT"]))
|
||||
|
||||
(defn- parse-ts
|
||||
[value]
|
||||
(cond
|
||||
(number? value) value
|
||||
(string? value) (let [ms (js/Date.parse value)]
|
||||
(when-not (js/isNaN ms) ms))
|
||||
:else nil))
|
||||
|
||||
(defn- human-ago
|
||||
[value now-ms]
|
||||
(if-let [ts (parse-ts value)]
|
||||
(let [diff-ms (max 0 (- now-ms ts))
|
||||
secs (js/Math.floor (/ diff-ms 1000))
|
||||
mins (js/Math.floor (/ secs 60))
|
||||
hours (js/Math.floor (/ mins 60))
|
||||
days (js/Math.floor (/ hours 24))
|
||||
months (js/Math.floor (/ days 30))
|
||||
years (js/Math.floor (/ days 365))]
|
||||
(cond
|
||||
(< secs 60) (str secs "s ago")
|
||||
(< mins 60) (str mins "m ago")
|
||||
(< hours 24) (str hours "h ago")
|
||||
(< days 30) (str days "d ago")
|
||||
(< months 12) (str months "mo ago")
|
||||
:else (str years "y ago")))
|
||||
"-"))
|
||||
|
||||
(defn- format-list-row
|
||||
[item include-ident? now-ms]
|
||||
(let [base [(or (:db/id item) (:id item))
|
||||
(or (:title item) (:block/title item) (:name item))]
|
||||
with-ident (cond-> base
|
||||
include-ident? (conj (:db/ident item)))
|
||||
updated (human-ago (or (:updated-at item) (:block/updated-at item)) now-ms)
|
||||
created (human-ago (or (:created-at item) (:block/created-at item)) now-ms)]
|
||||
(conj with-ident updated created)))
|
||||
|
||||
(defn- format-list-page
|
||||
[items now-ms]
|
||||
(let [items (or items [])
|
||||
include-ident? (boolean (some :db/ident items))
|
||||
headers (into ["ID" "TITLE"]
|
||||
(concat (or (maybe-ident-header items) [])
|
||||
["UPDATED-AT" "CREATED-AT"]))]
|
||||
(format-counted-table
|
||||
headers
|
||||
(mapv #(format-list-row % include-ident? now-ms) items))))
|
||||
|
||||
(defn- format-list-tag-or-property
|
||||
[items now-ms]
|
||||
(let [items (or items [])
|
||||
include-ident? (boolean (some :db/ident items))
|
||||
headers (into ["ID" "TITLE"]
|
||||
(concat (or (maybe-ident-header items) [])
|
||||
["UPDATED-AT" "CREATED-AT"]))]
|
||||
(format-counted-table
|
||||
headers
|
||||
(mapv #(format-list-row % include-ident? now-ms) items))))
|
||||
|
||||
(defn- format-graph-list
|
||||
[graphs]
|
||||
(format-counted-table
|
||||
["GRAPH"]
|
||||
(mapv (fn [graph] [graph]) (or graphs []))))
|
||||
|
||||
(defn- format-server-list
|
||||
[servers]
|
||||
(format-counted-table
|
||||
["REPO" "STATUS" "HOST" "PORT" "PID"]
|
||||
(mapv (fn [server]
|
||||
[(:repo server)
|
||||
(:status server)
|
||||
(:host server)
|
||||
(:port server)
|
||||
(:pid server)])
|
||||
(or servers []))))
|
||||
|
||||
(defn- format-search-results
|
||||
[results]
|
||||
(format-counted-table
|
||||
["TYPE" "TITLE/CONTENT" "UUID" "UPDATED-AT" "CREATED-AT"]
|
||||
(mapv (fn [item]
|
||||
[(:type item)
|
||||
(or (:title item) (:content item))
|
||||
(:uuid item)
|
||||
(:updated-at item)
|
||||
(:created-at item)])
|
||||
(or results []))))
|
||||
|
||||
(defn- format-graph-info
|
||||
[{:keys [graph logseq.kv/graph-created-at logseq.kv/schema-version]}]
|
||||
(string/join "\n"
|
||||
[(str "Graph: " (or graph "-"))
|
||||
(str "Created at: " (or graph-created-at "-"))
|
||||
(str "Schema version: " (or schema-version "-"))]))
|
||||
|
||||
(defn- format-server-status
|
||||
[{:keys [repo status host port]}]
|
||||
(string/join "\n"
|
||||
(cond-> [(str "Server " (name (or status :unknown)) ": " repo)]
|
||||
(and host port) (conj (str "Host: " host " Port: " port)))))
|
||||
|
||||
(defn- format-server-action
|
||||
[command {:keys [repo status host port]}]
|
||||
(let [status (or status
|
||||
(case command
|
||||
:server-start :started
|
||||
:server-stop :stopped
|
||||
:server-restart :restarted
|
||||
:unknown))]
|
||||
(string/join "\n"
|
||||
(cond-> [(str "Server " (name status) ": " repo)]
|
||||
(and host port) (conj (str "Host: " host " Port: " port))))))
|
||||
|
||||
(defn- format-add-block
|
||||
[{:keys [repo blocks]}]
|
||||
(str "Added blocks: " (count blocks) " (repo: " repo ")"))
|
||||
|
||||
(defn- format-add-page
|
||||
[{:keys [repo page]}]
|
||||
(str "Added page: " page " (repo: " repo ")"))
|
||||
|
||||
(defn- format-remove-page
|
||||
[{:keys [repo page]}]
|
||||
(str "Removed page: " page " (repo: " repo ")"))
|
||||
|
||||
(defn- format-remove-block
|
||||
[{:keys [repo block]}]
|
||||
(str "Removed block: " block " (repo: " repo ")"))
|
||||
|
||||
(defn- format-graph-action
|
||||
[command {:keys [graph]}]
|
||||
(let [verb (case command
|
||||
:graph-create "created"
|
||||
:graph-switch "switched"
|
||||
:graph-remove "removed"
|
||||
:graph-validate "validated"
|
||||
"updated")]
|
||||
(str "Graph " verb ": " graph)))
|
||||
|
||||
(defn- ->human
|
||||
[{:keys [status data error]}]
|
||||
(case status
|
||||
:ok
|
||||
(if (and (map? data) (contains? data :message))
|
||||
(:message data)
|
||||
(pr-str data))
|
||||
[{:keys [status data error command context]} {:keys [now-ms]}]
|
||||
(let [now-ms (or now-ms (js/Date.now))]
|
||||
(case status
|
||||
:ok
|
||||
(case command
|
||||
:graph-list (format-graph-list (:graphs data))
|
||||
:graph-info (format-graph-info data)
|
||||
(:graph-create :graph-switch :graph-remove :graph-validate)
|
||||
(format-graph-action command context)
|
||||
:server-list (format-server-list (:servers data))
|
||||
:server-status (format-server-status data)
|
||||
(:server-start :server-stop :server-restart)
|
||||
(format-server-action command data)
|
||||
:list-page (format-list-page (:items data) now-ms)
|
||||
(:list-tag :list-property) (format-list-tag-or-property (:items data) now-ms)
|
||||
:add-block (format-add-block context)
|
||||
:add-page (format-add-page context)
|
||||
:remove-page (format-remove-page context)
|
||||
:remove-block (format-remove-block context)
|
||||
:search (format-search-results (:results data))
|
||||
:show (or (:message data) (pr-str data))
|
||||
(if (and (map? data) (contains? data :message))
|
||||
(:message data)
|
||||
(pr-str data)))
|
||||
|
||||
:error
|
||||
(str "error: " (:message error))
|
||||
:error
|
||||
(format-error error)
|
||||
|
||||
(pr-str {:status status :data data :error error})))
|
||||
(pr-str {:status status :data data :error error}))))
|
||||
|
||||
(defn- ->edn
|
||||
[{:keys [status data error]}]
|
||||
@@ -42,7 +264,7 @@
|
||||
(= status :error) (assoc :error error))))
|
||||
|
||||
(defn format-result
|
||||
[result {:keys [output-format]}]
|
||||
[result {:keys [output-format now-ms] :as opts}]
|
||||
(let [format (cond
|
||||
(= output-format :edn) :edn
|
||||
(= output-format :json) :json
|
||||
@@ -50,4 +272,4 @@
|
||||
(case format
|
||||
:json (->json result)
|
||||
:edn (->edn result)
|
||||
(->human result))))
|
||||
(->human result opts))))
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
(not (:ok? parsed))
|
||||
(p/resolved {:exit-code 1
|
||||
:output (format/format-result {:status :error
|
||||
:error (:error parsed)}
|
||||
:error (:error parsed)
|
||||
:command (:command parsed)}
|
||||
{})})
|
||||
|
||||
:else
|
||||
@@ -38,7 +39,10 @@
|
||||
(if-not (:ok? action-result)
|
||||
(p/resolved {:exit-code 1
|
||||
:output (format/format-result {:status :error
|
||||
:error (:error action-result)}
|
||||
:error (:error action-result)
|
||||
:command (:command parsed)
|
||||
:context (select-keys (:options parsed)
|
||||
[:repo :graph :page :block])}
|
||||
cfg)})
|
||||
(-> (commands/execute (:action action-result) cfg)
|
||||
(p/then (fn [result]
|
||||
|
||||
@@ -6,17 +6,10 @@
|
||||
["os" :as os]
|
||||
["path" :as node-path]
|
||||
[clojure.string :as string]
|
||||
[frontend.worker.db-worker-node :as db-worker-node]
|
||||
[frontend.worker-common.util :as worker-util]
|
||||
[lambdaisland.glogi :as log]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defonce ^:private *inproc-servers (atom {}))
|
||||
|
||||
(defn- inproc-enabled?
|
||||
[]
|
||||
(boolean (.-DEBUG js/goog)))
|
||||
|
||||
(defn- expand-home
|
||||
[path]
|
||||
(if (string/starts-with? path "~")
|
||||
@@ -172,20 +165,13 @@
|
||||
|
||||
(defn- spawn-server!
|
||||
[{:keys [repo data-dir]}]
|
||||
(let [script (node-path/join (js/process.cwd) "static" "db-worker-node.js")
|
||||
(let [script (node-path/join js/__dirname "db-worker-node.js")
|
||||
args #js [script "--repo" repo "--data-dir" data-dir]
|
||||
child (.spawn child-process "node" args #js {:detached true
|
||||
:stdio "ignore"})]
|
||||
(.unref child)
|
||||
child))
|
||||
|
||||
(defn- start-inproc-server!
|
||||
[{:keys [repo data-dir]}]
|
||||
(p/let [daemon (db-worker-node/start-daemon! {:data-dir data-dir
|
||||
:repo repo})]
|
||||
(swap! *inproc-servers assoc repo daemon)
|
||||
daemon))
|
||||
|
||||
(defn- ensure-server-started!
|
||||
[config repo]
|
||||
(let [data-dir (resolve-data-dir config)
|
||||
@@ -193,9 +179,7 @@
|
||||
(p/let [existing (read-lock path)
|
||||
_ (cleanup-stale-lock! path existing)
|
||||
_ (when (not (fs/existsSync path))
|
||||
(if (inproc-enabled?)
|
||||
(start-inproc-server! {:repo repo :data-dir data-dir})
|
||||
(spawn-server! {:repo repo :data-dir data-dir}))
|
||||
(spawn-server! {:repo repo :data-dir data-dir})
|
||||
(wait-for-lock path))
|
||||
lock (read-lock path)]
|
||||
(when-not lock
|
||||
@@ -232,7 +216,6 @@
|
||||
(p/resolved (not (fs/existsSync path))))
|
||||
{:timeout-ms 5000
|
||||
:interval-ms 200})
|
||||
(swap! *inproc-servers dissoc repo)
|
||||
{:ok? true
|
||||
:data {:repo repo}})
|
||||
(p/catch (fn [_]
|
||||
@@ -248,10 +231,8 @@
|
||||
{:ok? false
|
||||
:error {:code :server-stop-timeout
|
||||
:message "timed out stopping server"}}
|
||||
(do
|
||||
(swap! *inproc-servers dissoc repo)
|
||||
{:ok? true
|
||||
:data {:repo repo}}))))))))
|
||||
{:ok? true
|
||||
:data {:repo repo}})))))))
|
||||
|
||||
(defn start-server!
|
||||
[config repo]
|
||||
|
||||
@@ -79,6 +79,67 @@
|
||||
repo-dir (node-path/join data-dir (str "." pool-name))]
|
||||
(node-path/join repo-dir "db-worker.lock")))
|
||||
|
||||
(defn- pad2
|
||||
[value]
|
||||
(if (< value 10)
|
||||
(str "0" value)
|
||||
(str value)))
|
||||
|
||||
(defn- yyyymmdd
|
||||
[^js date]
|
||||
(str (.getFullYear date)
|
||||
(pad2 (inc (.getMonth date)))
|
||||
(pad2 (.getDate date))))
|
||||
|
||||
(defn- log-path
|
||||
[data-dir repo]
|
||||
(let [pool-name (worker-util/get-pool-name repo)
|
||||
repo-dir (node-path/join data-dir (str "." pool-name))
|
||||
date-str (yyyymmdd (js/Date.))]
|
||||
(node-path/join repo-dir (str "db-worker-node-" date-str ".log"))))
|
||||
|
||||
(deftest db-worker-node-creates-log-file
|
||||
(async done
|
||||
(let [daemon (atom nil)
|
||||
data-dir (node-helper/create-tmp-dir "db-worker-log")
|
||||
repo (str "logseq_db_log_" (subs (str (random-uuid)) 0 8))
|
||||
log-file (log-path data-dir repo)]
|
||||
(-> (p/let [{:keys [stop!]}
|
||||
(db-worker-node/start-daemon! {:data-dir data-dir
|
||||
:repo repo})
|
||||
_ (reset! daemon {:stop! stop!})
|
||||
_ (p/delay 50)]
|
||||
(is (fs/existsSync log-file)))
|
||||
(p/catch (fn [e]
|
||||
(is false (str "unexpected error: " e))))
|
||||
(p/finally (fn []
|
||||
(if-let [stop! (:stop! @daemon)]
|
||||
(-> (stop!) (p/finally (fn [] (done))))
|
||||
(done))))))))
|
||||
|
||||
(deftest db-worker-node-log-file-has-entries
|
||||
(async done
|
||||
(let [daemon (atom nil)
|
||||
data-dir (node-helper/create-tmp-dir "db-worker-log-entries")
|
||||
repo (str "logseq_db_log_entries_" (subs (str (random-uuid)) 0 8))
|
||||
log-file (log-path data-dir repo)]
|
||||
(-> (p/let [{:keys [host port stop!]}
|
||||
(db-worker-node/start-daemon! {:data-dir data-dir
|
||||
:repo repo})
|
||||
_ (reset! daemon {:stop! stop!})
|
||||
_ (invoke host port "thread-api/create-or-open-db" [repo {}])
|
||||
_ (p/delay 50)
|
||||
contents (when (fs/existsSync log-file)
|
||||
(.toString (fs/readFileSync log-file) "utf8"))]
|
||||
(is (fs/existsSync log-file))
|
||||
(is (pos? (count contents))))
|
||||
(p/catch (fn [e]
|
||||
(is false (str "unexpected error: " e))))
|
||||
(p/finally (fn []
|
||||
(if-let [stop! (:stop! @daemon)]
|
||||
(-> (stop!) (p/finally (fn [] (done))))
|
||||
(done))))))))
|
||||
|
||||
(deftest db-worker-node-parse-args-ignores-host-and-port
|
||||
(let [parse-args #'db-worker-node/parse-args
|
||||
result (parse-args #js ["node" "db-worker-node.js"
|
||||
|
||||
@@ -39,4 +39,133 @@
|
||||
(testing "human error (default)"
|
||||
(let [result (format/format-result {:status :error :error {:code :boom :message "nope"}}
|
||||
{:output-format nil})]
|
||||
(is (= "error: nope" result)))))
|
||||
(is (= "Error (boom): nope" result)))))
|
||||
|
||||
(deftest test-human-output-list-page
|
||||
(testing "list page renders a table with count"
|
||||
(let [result (format/format-result {:status :ok
|
||||
:command :list-page
|
||||
:data {:items [{:db/id 1
|
||||
:title "Alpha"
|
||||
:updated-at 90000
|
||||
:created-at 40000}]}}
|
||||
{:output-format nil
|
||||
:now-ms 100000})]
|
||||
(is (= (str "ID TITLE UPDATED-AT CREATED-AT\n"
|
||||
"1 Alpha 10s ago 1m ago\n"
|
||||
"Count: 1")
|
||||
result)))))
|
||||
|
||||
(deftest test-human-output-list-tag-property
|
||||
(testing "list tag uses ID column from :db/id"
|
||||
(let [result (format/format-result {:status :ok
|
||||
:command :list-tag
|
||||
:data {:items [{:block/title "Tag"
|
||||
:db/id 42
|
||||
:block/created-at 40000
|
||||
:block/updated-at 90000
|
||||
:db/ident :logseq.class/Tag}]}}
|
||||
{:output-format nil
|
||||
:now-ms 100000})]
|
||||
(is (= (str "ID TITLE IDENT UPDATED-AT CREATED-AT\n"
|
||||
"42 Tag :logseq.class/Tag 10s ago 1m ago\n"
|
||||
"Count: 1")
|
||||
result))))
|
||||
|
||||
(testing "list property uses ID column from :db/id"
|
||||
(let [result (format/format-result {:status :ok
|
||||
:command :list-property
|
||||
:data {:items [{:block/title "Prop"
|
||||
:db/id 99
|
||||
:block/created-at 40000
|
||||
:block/updated-at 90000}]}}
|
||||
{:output-format nil
|
||||
:now-ms 100000})]
|
||||
(is (= (str "ID TITLE UPDATED-AT CREATED-AT\n"
|
||||
"99 Prop 10s ago 1m ago\n"
|
||||
"Count: 1")
|
||||
result)))))
|
||||
|
||||
(deftest test-human-output-add-remove
|
||||
(testing "add block renders a succinct success line"
|
||||
(let [result (format/format-result {:status :ok
|
||||
:command :add-block
|
||||
:context {:repo "demo-repo"
|
||||
:blocks ["a" "b"]}
|
||||
:data {:result {:ok true}}}
|
||||
{:output-format nil})]
|
||||
(is (= "Added blocks: 2 (repo: demo-repo)" result))))
|
||||
|
||||
(testing "remove page renders a succinct success line"
|
||||
(let [result (format/format-result {:status :ok
|
||||
:command :remove-page
|
||||
:context {:repo "demo-repo"
|
||||
:page "Home"}
|
||||
:data {:result {:ok true}}}
|
||||
{:output-format nil})]
|
||||
(is (= "Removed page: Home (repo: demo-repo)" result)))))
|
||||
|
||||
(deftest test-human-output-graph-info
|
||||
(testing "graph info includes key metadata lines"
|
||||
(let [result (format/format-result {:status :ok
|
||||
:command :graph-info
|
||||
:data {:graph "demo-graph"
|
||||
:logseq.kv/graph-created-at 123
|
||||
:logseq.kv/schema-version 2}}
|
||||
{:output-format nil})]
|
||||
(is (= (str "Graph: demo-graph\n"
|
||||
"Created at: 123\n"
|
||||
"Schema version: 2")
|
||||
result)))))
|
||||
|
||||
(deftest test-human-output-server-status
|
||||
(testing "server status includes repo, status, host, port"
|
||||
(let [result (format/format-result {:status :ok
|
||||
:command :server-status
|
||||
:data {:repo "demo-repo"
|
||||
:status :ready
|
||||
:host "127.0.0.1"
|
||||
:port 1234}}
|
||||
{:output-format nil})]
|
||||
(is (= (str "Server ready: demo-repo\n"
|
||||
"Host: 127.0.0.1 Port: 1234")
|
||||
result)))))
|
||||
|
||||
(deftest test-human-output-search-and-show
|
||||
(testing "search renders a table with count"
|
||||
(let [result (format/format-result {:status :ok
|
||||
:command :search
|
||||
:data {:results [{:type "page"
|
||||
:title "Alpha"
|
||||
:uuid "u1"
|
||||
:updated-at 3
|
||||
:created-at 1}
|
||||
{:type "block"
|
||||
:content "Note"
|
||||
:uuid "u2"
|
||||
:updated-at 4
|
||||
:created-at 2}]}}
|
||||
{:output-format nil})]
|
||||
(is (= (str "TYPE TITLE/CONTENT UUID UPDATED-AT CREATED-AT\n"
|
||||
"page Alpha u1 3 1\n"
|
||||
"block Note u2 4 2\n"
|
||||
"Count: 2")
|
||||
result))))
|
||||
|
||||
(testing "show renders text payloads directly"
|
||||
(let [result (format/format-result {:status :ok
|
||||
:command :show
|
||||
:data {:message "Line 1\nLine 2"}}
|
||||
{:output-format nil})]
|
||||
(is (= "Line 1\nLine 2" result)))))
|
||||
|
||||
(deftest test-human-output-error-formatting
|
||||
(testing "errors include code and hint when available"
|
||||
(let [result (format/format-result {:status :error
|
||||
:command :graph-create
|
||||
:error {:code :missing-graph
|
||||
:message "graph name is required"}}
|
||||
{:output-format nil})]
|
||||
(is (= (str "Error (missing-graph): graph name is required\n"
|
||||
"Hint: Use --graph <name>")
|
||||
result)))))
|
||||
|
||||
@@ -128,3 +128,48 @@
|
||||
(p/catch (fn [e]
|
||||
(is false (str "unexpected error: " e))
|
||||
(done)))))))
|
||||
|
||||
(deftest test-cli-list-outputs-include-id
|
||||
(async done
|
||||
(let [data-dir (node-helper/create-tmp-dir "db-worker")]
|
||||
(-> (p/let [cfg-path (node-path/join (node-helper/create-tmp-dir "cli") "cli.edn")
|
||||
_ (fs/writeFileSync cfg-path "{:output-format :json}")
|
||||
_ (run-cli ["graph" "create" "--repo" "list-id-graph"] data-dir cfg-path)
|
||||
_ (run-cli ["add" "page" "--page" "TestPage"] data-dir cfg-path)
|
||||
list-page-result (run-cli ["list" "page"] data-dir cfg-path)
|
||||
list-page-payload (parse-json-output list-page-result)
|
||||
list-tag-result (run-cli ["list" "tag"] data-dir cfg-path)
|
||||
list-tag-payload (parse-json-output list-tag-result)
|
||||
list-property-result (run-cli ["list" "property"] data-dir cfg-path)
|
||||
list-property-payload (parse-json-output list-property-result)
|
||||
stop-result (run-cli ["server" "stop" "--repo" "list-id-graph"] data-dir cfg-path)
|
||||
stop-payload (parse-json-output stop-result)]
|
||||
(is (= "ok" (:status list-page-payload)))
|
||||
(is (every? #(contains? % :id) (get-in list-page-payload [:data :items])))
|
||||
(is (= "ok" (:status list-tag-payload)))
|
||||
(is (every? #(contains? % :id) (get-in list-tag-payload [:data :items])))
|
||||
(is (= "ok" (:status list-property-payload)))
|
||||
(is (every? #(contains? % :id) (get-in list-property-payload [:data :items])))
|
||||
(is (= "ok" (:status stop-payload)))
|
||||
(done))
|
||||
(p/catch (fn [e]
|
||||
(is false (str "unexpected error: " e))
|
||||
(done)))))))
|
||||
|
||||
(deftest test-cli-list-page-human-output
|
||||
(async done
|
||||
(let [data-dir (node-helper/create-tmp-dir "db-worker")]
|
||||
(-> (p/let [cfg-path (node-path/join (node-helper/create-tmp-dir "cli") "cli.edn")
|
||||
_ (fs/writeFileSync cfg-path "{:output-format :json}")
|
||||
_ (run-cli ["graph" "create" "--repo" "human-list-graph"] data-dir cfg-path)
|
||||
_ (run-cli ["add" "page" "--page" "TestPage"] data-dir cfg-path)
|
||||
list-page-result (run-cli ["list" "page" "--output" "human"] data-dir cfg-path)
|
||||
output (:output list-page-result)]
|
||||
(is (= 0 (:exit-code list-page-result)))
|
||||
(is (string/includes? output "TITLE"))
|
||||
(is (string/includes? output "TestPage"))
|
||||
(is (string/includes? output "Count:"))
|
||||
(done))
|
||||
(p/catch (fn [e]
|
||||
(is false (str "unexpected error: " e))
|
||||
(done)))))))
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
(deftest spawn-server-omits-host-and-port-flags
|
||||
(let [spawn-server! #'cli-server/spawn-server!
|
||||
captured (atom nil)
|
||||
original-spawn (.-spawn child-process)]
|
||||
original-spawn (.-spawn child-process)
|
||||
original-cwd (.cwd js/process)]
|
||||
(set! (.-spawn child-process)
|
||||
(fn [cmd args opts]
|
||||
(reset! captured {:cmd cmd
|
||||
@@ -19,15 +20,20 @@
|
||||
:opts (js->clj opts :keywordize-keys true)})
|
||||
(js-obj "unref" (fn [] nil))))
|
||||
(try
|
||||
(.chdir js/process "/")
|
||||
(spawn-server! {:repo "logseq_db_spawn_test"
|
||||
:data-dir "/tmp/logseq-db-worker"})
|
||||
(is (= "node" (:cmd @captured)))
|
||||
(is (= (node-path/join js/__dirname "db-worker-node.js")
|
||||
(first (:args @captured))))
|
||||
(is (some #{"--repo"} (:args @captured)))
|
||||
(is (some #{"--data-dir"} (:args @captured)))
|
||||
(is (not-any? #{"--host" "--port"} (:args @captured)))
|
||||
(finally
|
||||
(.chdir js/process original-cwd)
|
||||
(set! (.-spawn child-process) original-spawn)))))
|
||||
|
||||
|
||||
(deftest ensure-server-repairs-stale-lock
|
||||
(async done
|
||||
(let [data-dir (node-helper/create-tmp-dir "cli-server")
|
||||
|
||||
Reference in New Issue
Block a user